Skip to content

Commit

Permalink
Added severity
Browse files Browse the repository at this point in the history
  • Loading branch information
labra committed Aug 18, 2018
1 parent 718dcf7 commit dff5856
Show file tree
Hide file tree
Showing 16 changed files with 218 additions and 101 deletions.
67 changes: 67 additions & 0 deletions modules/shacl/src/main/scala/es/weso/shacl/MessageMap.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package es.weso.shacl
import cats._
import cats.implicits._
import es.weso.rdf.nodes.{Lang, LangLiteral, RDFNode, StringLiteral}

case class MessageMap(mmap: Map[Option[Lang], String]) {

def getRDFNodes: List[RDFNode] = mmap.toList.map {
case (maybeLang, str) =>
maybeLang match {
case None => StringLiteral(str)
case Some(lang) => LangLiteral(str, lang)
}
}

def addMessage(node: RDFNode): Either[String, MessageMap] = node match {
case StringLiteral(str) => mmap.get(None) match {
case None => Right(MessageMap(mmap.updated(None, str)))
case Some(other) => Left(s"Trying to create two messages without language tag: $other and $str")
}
case LangLiteral(str,lang) => mmap.get(Some(lang)) match {
case None => Right(MessageMap(mmap.updated(Some(lang), str)))
case Some(other) => Left(s"Trying to create two messages with same language tag ($lang): $other and $str")
}
case _ => Left(s"Node $node must be a string or a language tagged string to be a message")
}

override def toString(): String = Show[MessageMap].show(this)

}

object MessageMap {

def fromString(msg: String): MessageMap = {
MessageMap(Map(None -> msg))
}

def fromRDFNodes(nodes: List[RDFNode]): Either[String, MessageMap] = {
val zero: Either[String,MessageMap] = Right(MessageMap(Map()))
def cmb(rest: Either[String,MessageMap], x: RDFNode): Either[String,MessageMap] = for {
mmap <- rest
r <- mmap.addMessage(x)
} yield r
nodes.foldLeft(zero)(cmb)
}

implicit def monoidMessageMap: Monoid[MessageMap] = new Monoid[MessageMap] {
override def empty: MessageMap = MessageMap(Map())

override def combine(m1: MessageMap, m2: MessageMap): MessageMap =
MessageMap(m1.mmap |+| m2.mmap)
}

implicit def showMessageMap: Show[MessageMap] = new Show[MessageMap] {
override def show(m: MessageMap): String = {
m.mmap.toList.map { case (maybeLang,msg) =>
maybeLang match {
case None => msg
case Some(lang) => s"$msg@$lang"
}
}.mkString(",")
}
}

def empty: MessageMap = Monoid[MessageMap].empty

}
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ object SHACLPrefixes {
lazy val sh_result: IRI = sh + "result"
lazy val sh_resultPath: IRI = sh + "resultPath"
lazy val sh_resultSeverity: IRI = sh + "resultSeverity"
lazy val sh_resultMessage: IRI = sh + "resultMessage"
lazy val sh_severity: IRI = sh + "severity"
lazy val sh_sourceConstraintComponent: IRI = sh + "sourceConstraintComponent"
lazy val sh_sourceShape: IRI = sh + "sourceShape"
lazy val sh_value: IRI = sh + "value"
Expand Down
16 changes: 11 additions & 5 deletions modules/shacl/src/main/scala/es/weso/shacl/Shape.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package es.weso.shacl

import es.weso.rdf.nodes._
import es.weso.rdf.path.SHACLPath
import es.weso.shacl.report.Severity


sealed abstract class Shape {
Expand All @@ -11,7 +12,8 @@ sealed abstract class Shape {
def propertyShapes: Seq[ShapeRef]
def closed: Boolean
def deactivated: Boolean
def message: Map[Option[Lang],String]
def message: MessageMap
def severity: Option[Severity]
def ignoredProperties: List[IRI]

def hasId(iri: IRI): Boolean = {
Expand Down Expand Up @@ -56,7 +58,8 @@ case class NodeShape(
closed: Boolean,
ignoredProperties: List[IRI],
deactivated: Boolean,
message: Map[Option[Lang],String]
message: MessageMap,
severity: Option[Severity]
) extends Shape {

def isPropertyConstraint = false
Expand All @@ -72,7 +75,8 @@ case class PropertyShape(
closed: Boolean,
ignoredProperties: List[IRI],
deactivated: Boolean,
message: Map[Option[Lang],String]
message: MessageMap,
severity: Option[Severity]
) extends Shape {

def isPropertyConstraint = true
Expand All @@ -91,7 +95,8 @@ object Shape {
closed = false,
ignoredProperties = List(),
deactivated = false,
message = Map()
message = MessageMap.empty,
severity = None
)

def emptyPropertyShape(
Expand All @@ -105,6 +110,7 @@ object Shape {
closed = false,
ignoredProperties = List(),
deactivated = false,
message = Map()
message = MessageMap.empty,
severity = None
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ import es.weso.rdf.parser.RDFParser
import es.weso.rdf.path._
import es.weso.shacl.SHACLPrefixes._
import es.weso.shacl._
import es.weso.shacl.report._

import scala.util.{ Failure, Success, Try }
import scala.util.{Failure, Success, Try}

object RDF2Shacl extends RDFParser with LazyLogging {

Expand Down Expand Up @@ -95,6 +96,7 @@ object RDF2Shacl extends RDFParser with LazyLogging {
closed <- booleanFromPredicateOptional(sh_closed)(n, rdf)
deactivated <- booleanFromPredicateOptional(sh_deactivated)(n,rdf)
message <- parseMessage(n, rdf)
severity <- parseSeverity(n,rdf)
ignoredNodes <- {
println(s"Parsing deactivated: $deactivated for node: $n") ;
rdfListForPredicateOptional(sh_ignoredProperties)(n, rdf)
Expand All @@ -110,38 +112,31 @@ object RDF2Shacl extends RDFParser with LazyLogging {
closed = closed.getOrElse(false),
ignoredProperties = ignoredIRIs,
deactivated = deactivated.getOrElse(false),
message = message
message = message,
severity = severity
)
val sref = ShapeRef(n)
parsedShapes += (sref -> shape)
sref
}

private def parseMessage: RDFParser[Map[Option[Lang],String]] = (n,rdf) => for {
private def parseSeverity: RDFParser[Option[Severity]] = (n,rdf) => for {
maybeIri <- iriFromPredicateOptional(sh_severity)(n,rdf)
} yield maybeIri match {
case Some(`sh_Violation`) => Some(ViolationSeverity)
case Some(`sh_Warning`) => Some(WarningSeverity)
case Some(`sh_Info`) => Some(InfoSeverity)
case Some(iri) => Some(GenericSeverity(iri))
case None => None
}

private def parseMessage: RDFParser[MessageMap] = (n,rdf) => for {
nodes <- objectsFromPredicate(sh_message)(n,rdf)
map <- cnvMessages(nodes)(n,rdf)
} yield map

private def cnvMessages(ns: Set[RDFNode]): RDFParser[Map[Option[Lang], String]] = (n,rdf) => {
val zero: Either[String,Map[Option[Lang], String]] = Right(Map())
def cmb(next: Either[String,Map[Option[Lang], String]], x: RDFNode): Either[String,Map[Option[Lang], String]] = x match {
case StringLiteral(str) => for {
m <- next
updated <- m.get(None) match {
case None => Right(m.updated(None, str))
case Some(other) => Left(s"Two values for predicate sh:message: $str and $other for node $n")
}
} yield updated
case LangLiteral(str,lang) => for {
m <- next
updated <- m.get(Some(lang)) match {
case None => Right(m.updated(Some(lang), str))
case Some(other) => Left(s"Two values for predicate sh:message: $str and $other for node $n for lang $lang")
}
} yield updated
case _ => Left(s"Node $x must be a string or a language tagged string")
}
ns.foldLeft(zero)(cmb)
private def cnvMessages(ns: Set[RDFNode]): RDFParser[MessageMap] = (n,rdf) => {
MessageMap.fromRDFNodes(ns.toList)
}


Expand All @@ -157,6 +152,7 @@ object RDF2Shacl extends RDFParser with LazyLogging {
ignoredNodes <- rdfListForPredicateOptional(sh_ignoredProperties)(n, rdf)
deactivated <- booleanFromPredicateOptional(sh_deactivated)(n, rdf)
message <- parseMessage(n, rdf)
severity <- parseSeverity(n,rdf)
ignoredIRIs <- {
println(s"Parsing deactivated: $deactivated for node: $n")
nodes2iris(ignoredNodes)
Expand All @@ -171,7 +167,8 @@ object RDF2Shacl extends RDFParser with LazyLogging {
closed = closed.getOrElse(false),
ignoredProperties = ignoredIRIs,
deactivated = deactivated.getOrElse(false),
message = message
message = message,
severity = severity
)

val sref = ShapeRef(n)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import es.weso.rdf.PREFIXES._
import es.weso.rdf.{RDFBuilder, nodes}
import es.weso.rdf.saver.RDFSaver
import es.weso.shacl._
import es.weso.shacl.report.Severity

class Shacl2RDF extends RDFSaver with LazyLogging {

Expand Down Expand Up @@ -88,6 +89,7 @@ class Shacl2RDF extends RDFSaver with LazyLogging {
_ <- deactivated(shapeNode, ps.deactivated)
_ <- ignoredProperties(shapeNode, ps.ignoredProperties)
_ <- message(shapeNode, ps.message)
_ <- severity(shapeNode, ps.severity)
pathNode <- makePath(ps.path)
_ <- addTriple(shapeNode, sh_path, pathNode)
_ <- saveList(ps.components.toList, component(shapeNode))
Expand All @@ -103,15 +105,19 @@ class Shacl2RDF extends RDFSaver with LazyLogging {
_ <- ignoredProperties(shapeNode, n.ignoredProperties)
_ <- saveList(n.components, component(shapeNode))
_ <- message(shapeNode, n.message)
_ <- severity(shapeNode, n.severity)
} yield shapeNode

private def message(n: RDFNode, message: Map[Option[Lang],String]): RDFSaver[Unit] =
sequence(message.toList.map {
case (maybeLang, msg) => maybeLang match {
case None => addTriple(n,sh_message,StringLiteral(msg))
case Some(lang) => addTriple(n,sh_message, LangLiteral(msg,lang))
}
}).map(_ => ())
private def message(n: RDFNode, message: MessageMap): RDFSaver[Unit] =
sequence(message.getRDFNodes.map(addTriple(n,sh_message,_))
).map(_ => ())

private def severity(n: RDFNode, severity: Option[Severity]): RDFSaver[Unit] =
severity match {
case None => ok(())
case Some(s) => addTriple(n,sh_severity,s.toIRI)
}


private def component(id: RDFNode)(c: Component): RDFSaver[Unit] = c match {
case ClassComponent(v) => addTriple(id, sh_class, v)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ case object WarningSeverity extends Severity {
case object InfoSeverity extends Severity {
override def toIRI: IRI = sh_Info
}
case class GenericSeverity(iri: IRI) extends Severity {
override def toIRI: IRI = iri
}

object Severity {
val defaultSeverity = ViolationSeverity
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ class ValidationReport2RDF extends RDFSaver with LazyLogging {
_ <- addTriple(node, sh_sourceConstraintComponent, vr.sourceConstraintComponent)
_ <- addTriple(node, sh_sourceShape, vr.sourceShape.id)
_ <- addTripleObjects(node, sh_value, vr.values.toList)
_ <- addTripleObjects(node, sh_resultMessage, vr.messageMap.getRDFNodes)
_ <- saveList(vr.message.toList, message(node))
_ <- vr.focusPath match {
case None => ok(())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ import es.weso.rdf.path._
import es.weso.shacl._
import es.weso.shacl.validator.Attempt


// TODO: Refactor this code creating Classes for each error?

abstract class AbstractResult

class ValidationResult(val focusNode: RDFNode,
Expand All @@ -15,35 +18,38 @@ class ValidationResult(val focusNode: RDFNode,
val sourceShape: ShapeRef,
val values: Seq[RDFNode],
val message: Seq[LiteralValue],
val messageMap: MessageMap,
val details: Seq[AbstractResult]
) {
override def toString = s"Violation error on $focusNode: ${message.mkString(",")}"
}


object ValidationResult {

def basic(suffix: String, focusNode: RDFNode, attempt: Attempt, msg: String) =
def basic(suffix: String, focusNode: RDFNode, attempt: Attempt, msg: String,
messageMap: MessageMap = MessageMap.empty
) =
new ValidationResult(
sourceConstraintComponent = sh + suffix,
focusNode = focusNode,
resultSeverity = Severity.defaultSeverity,
resultSeverity = attempt.severity,
sourceShape = attempt.shapeRef,
values = Seq(),
focusPath = attempt.path,
message = Seq(LiteralValue(
StringLiteral(msg)
)),
message = Seq(LiteralValue(StringLiteral(msg))),
messageMap = messageMap,
details = Seq()
)

def notFoundShapeRef(node: RDFNode, attempt: Attempt, msg: String) =
basic("NotFoundShapeRef", node, attempt, msg)
basic("NotFoundShapeRef", node, attempt, msg, MessageMap.fromString(msg))

def expectedPropertyShape(node: RDFNode, attempt: Attempt, msg: String) =
basic("ExpectedPropertyShape", node, attempt, msg)
basic("ExpectedPropertyShape", node, attempt, msg, MessageMap.fromString(msg))

def shapesFailed(node: RDFNode, shape: Shape, ps: Set[Shape], attempt: Attempt, msg: String) =
basic("ShapesFailed", node, attempt, msg)
basic("ShapesFailed", node, attempt, msg, MessageMap.fromString(msg))

def regexError(node: RDFNode, attempt: Attempt, msg: String) =
basic("RegEx error", node, attempt, msg)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,19 @@ package es.weso.shacl.validator

import es.weso.rdf.nodes._
import es.weso.rdf.path.SHACLPath
import es.weso.shacl.ShapeRef
import es.weso.shacl.{MessageMap, ShapeRef}
import es.weso.shacl.report.Severity
/**
* Represents current validation attempt
* It contains the node and a shape
* It may contain a predicate, path or nothing
*/
case class Attempt(nodeShape: NodeShapePair, path: Option[SHACLPath]) {
def node: RDFNode = nodeShape.node
def shapeId: RDFNode = nodeShape.shape.id
def shapeRef: ShapeRef = nodeShape.shape
case class Attempt(node: RDFNode,
shapeRef: ShapeRef,
messageMap: MessageMap,
severity: Severity,
path: Option[SHACLPath]
) {
def shapeId: RDFNode = shapeRef.id
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package es.weso.shacl.validator

import cats._
import es.weso.rdf.nodes._
import es.weso.shacl.report.Severity
import es.weso.shacl.{MessageMap, ShapeRef}

case class AttemptInfo(node: RDFNode,
shape: ShapeRef,
messageMap: MessageMap,
severity: Severity) {

override def toString = AttemptInfo.nodeShapeShow.show(this)

}

object AttemptInfo {
implicit val nodeShapeShow = new Show[AttemptInfo] {
def show(ns: AttemptInfo) = s"[${ns.node},${ns.shape.showId}]"
}
}
Loading

0 comments on commit dff5856

Please sign in to comment.