diff --git a/modules/shacl/src/main/scala/es/weso/shacl/MessageMap.scala b/modules/shacl/src/main/scala/es/weso/shacl/MessageMap.scala new file mode 100644 index 00000000..29899734 --- /dev/null +++ b/modules/shacl/src/main/scala/es/weso/shacl/MessageMap.scala @@ -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 + +} \ No newline at end of file diff --git a/modules/shacl/src/main/scala/es/weso/shacl/SHACLPrefixes.scala b/modules/shacl/src/main/scala/es/weso/shacl/SHACLPrefixes.scala index f80ed0d0..e6b34ac8 100644 --- a/modules/shacl/src/main/scala/es/weso/shacl/SHACLPrefixes.scala +++ b/modules/shacl/src/main/scala/es/weso/shacl/SHACLPrefixes.scala @@ -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" diff --git a/modules/shacl/src/main/scala/es/weso/shacl/Shape.scala b/modules/shacl/src/main/scala/es/weso/shacl/Shape.scala index fa3b8f05..81416f12 100644 --- a/modules/shacl/src/main/scala/es/weso/shacl/Shape.scala +++ b/modules/shacl/src/main/scala/es/weso/shacl/Shape.scala @@ -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 { @@ -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 = { @@ -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 @@ -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 @@ -91,7 +95,8 @@ object Shape { closed = false, ignoredProperties = List(), deactivated = false, - message = Map() + message = MessageMap.empty, + severity = None ) def emptyPropertyShape( @@ -105,6 +110,7 @@ object Shape { closed = false, ignoredProperties = List(), deactivated = false, - message = Map() + message = MessageMap.empty, + severity = None ) } diff --git a/modules/shacl/src/main/scala/es/weso/shacl/converter/RDF2Shacl.scala b/modules/shacl/src/main/scala/es/weso/shacl/converter/RDF2Shacl.scala index 3b324b4a..0dbfd2c4 100644 --- a/modules/shacl/src/main/scala/es/weso/shacl/converter/RDF2Shacl.scala +++ b/modules/shacl/src/main/scala/es/weso/shacl/converter/RDF2Shacl.scala @@ -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 { @@ -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) @@ -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) } @@ -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) @@ -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) diff --git a/modules/shacl/src/main/scala/es/weso/shacl/converter/Shacl2RDF.scala b/modules/shacl/src/main/scala/es/weso/shacl/converter/Shacl2RDF.scala index b8c4ef14..186f5848 100644 --- a/modules/shacl/src/main/scala/es/weso/shacl/converter/Shacl2RDF.scala +++ b/modules/shacl/src/main/scala/es/weso/shacl/converter/Shacl2RDF.scala @@ -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 { @@ -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)) @@ -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) diff --git a/modules/shacl/src/main/scala/es/weso/shacl/report/Severity.scala b/modules/shacl/src/main/scala/es/weso/shacl/report/Severity.scala index 4c6e2c98..1189f356 100644 --- a/modules/shacl/src/main/scala/es/weso/shacl/report/Severity.scala +++ b/modules/shacl/src/main/scala/es/weso/shacl/report/Severity.scala @@ -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 diff --git a/modules/shacl/src/main/scala/es/weso/shacl/report/ValidationReport2RDF.scala b/modules/shacl/src/main/scala/es/weso/shacl/report/ValidationReport2RDF.scala index bf711274..1e81db4c 100644 --- a/modules/shacl/src/main/scala/es/weso/shacl/report/ValidationReport2RDF.scala +++ b/modules/shacl/src/main/scala/es/weso/shacl/report/ValidationReport2RDF.scala @@ -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(()) diff --git a/modules/shacl/src/main/scala/es/weso/shacl/report/ValidationResult.scala b/modules/shacl/src/main/scala/es/weso/shacl/report/ValidationResult.scala index 80c44a87..f521074a 100644 --- a/modules/shacl/src/main/scala/es/weso/shacl/report/ValidationResult.scala +++ b/modules/shacl/src/main/scala/es/weso/shacl/report/ValidationResult.scala @@ -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, @@ -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) diff --git a/modules/shacl/src/main/scala/es/weso/shacl/validator/Attempt.scala b/modules/shacl/src/main/scala/es/weso/shacl/validator/Attempt.scala index b242a9b4..90540c53 100644 --- a/modules/shacl/src/main/scala/es/weso/shacl/validator/Attempt.scala +++ b/modules/shacl/src/main/scala/es/weso/shacl/validator/Attempt.scala @@ -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 } diff --git a/modules/shacl/src/main/scala/es/weso/shacl/validator/AttemptInfo.scala b/modules/shacl/src/main/scala/es/weso/shacl/validator/AttemptInfo.scala new file mode 100644 index 00000000..59f6b757 --- /dev/null +++ b/modules/shacl/src/main/scala/es/weso/shacl/validator/AttemptInfo.scala @@ -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}]" + } +} diff --git a/modules/shacl/src/main/scala/es/weso/shacl/validator/Evidence.scala b/modules/shacl/src/main/scala/es/weso/shacl/validator/Evidence.scala index 238763a2..3eb61876 100644 --- a/modules/shacl/src/main/scala/es/weso/shacl/validator/Evidence.scala +++ b/modules/shacl/src/main/scala/es/weso/shacl/validator/Evidence.scala @@ -1,6 +1,8 @@ package es.weso.shacl.validator import cats._ +import es.weso.rdf.nodes.RDFNode +import es.weso.shacl.ShapeRef case class Evidences(ls: List[Evidence]) @@ -8,13 +10,16 @@ abstract class Evidence { override def toString = Evidence.evidenceShow.show(this) } -case class NodeShapeEvidence(pair: NodeShapePair, msg: String) extends Evidence +case class NodeShapeEvidence(node: RDFNode, + shape: ShapeRef, + msg: String + ) extends Evidence case class MsgEvidence(msg: String) extends Evidence object Evidence { implicit val evidenceShow = new Show[Evidence] { def show(e: Evidence) = e match { - case NodeShapeEvidence(pair, msg) => s"${pair}: $msg" + case NodeShapeEvidence(node, shape, msg) => s"$node@${shape.id}: $msg" case MsgEvidence(msg) => msg } } diff --git a/modules/shacl/src/main/scala/es/weso/shacl/validator/NodeShapePair.scala b/modules/shacl/src/main/scala/es/weso/shacl/validator/NodeShapePair.scala deleted file mode 100644 index f5d30fea..00000000 --- a/modules/shacl/src/main/scala/es/weso/shacl/validator/NodeShapePair.scala +++ /dev/null @@ -1,17 +0,0 @@ -package es.weso.shacl.validator - -import cats._ -import es.weso.rdf.nodes._ -import es.weso.shacl.ShapeRef - -case class NodeShapePair(node: RDFNode, shape: ShapeRef) { - - override def toString = NodeShapePair.nodeShapeShow.show(this) - -} - -object NodeShapePair { - implicit val nodeShapeShow = new Show[NodeShapePair] { - def show(ns: NodeShapePair) = s"[${ns.node},${ns.shape.showId}]" - } -} diff --git a/modules/shacl/src/main/scala/es/weso/shacl/validator/Validator.scala b/modules/shacl/src/main/scala/es/weso/shacl/validator/Validator.scala index 267ff95f..1d661a6e 100644 --- a/modules/shacl/src/main/scala/es/weso/shacl/validator/Validator.scala +++ b/modules/shacl/src/main/scala/es/weso/shacl/validator/Validator.scala @@ -11,7 +11,7 @@ import es.weso.utils.{MyLogging, RegEx} import es.weso.shacl.showShacl._ import SHACLChecker.{logger, _} import es.weso.rdf.operations.Comparisons -import es.weso.shacl.report.ValidationResult +import es.weso.shacl.report.{Severity, ValidationResult} import es.weso.shacl.report.ValidationResult._ import es.weso.rdf.operations.Comparisons._ @@ -112,10 +112,13 @@ case class Validator(schema: Schema) extends MyLogging { case ps: PropertyShape => nodePropertyShape(node,ps) } + private def getSeverity(s: Shape): Severity = + s.severity.getOrElse(Severity.defaultSeverity) + def nodeNodeShape(node: RDFNode, ns: NodeShape): CheckTyping = { logger.debug(s"Node $node - NodeShape ${ns.showId}") logger.debug(s"Node shape is deactivated? (${ns.deactivated})") - val attempt = Attempt(NodeShapePair(node, ShapeRef(ns.id)), None) + val attempt = Attempt(node, ShapeRef(ns.id), ns.message, getSeverity(ns), None) for { t0 <- getTyping t <- runLocal(checkNodeShape(ns)(attempt)(node), _.addType(node, ns)) @@ -133,7 +136,7 @@ case class Validator(schema: Schema) extends MyLogging { def nodePropertyShape(node: RDFNode, ps: PropertyShape): CheckTyping = { logger.debug(s"Node $node - PropertyShape ${ps.showId}") val path = ps.path - val attempt = Attempt(NodeShapePair(node, ShapeRef(ps.id)), Some(path)) + val attempt = Attempt(node, ShapeRef(ps.id), ps.message, getSeverity(ps), Some(path)) if (ps.deactivated) for { t <- addEvidence(attempt, s"Property shape ${ps.showId} is deactivated") } yield (t,true) @@ -204,8 +207,9 @@ case class Validator(schema: Schema) extends MyLogging { rdf <- getRDF os = rdf.objectsWithPath(node, path).toList _ <- debug(s"checkPropertyShapePath: os=$os\nnode: $node, path=${path.show}") + shape <- getShapeRef(sref,attempt,node) r <- checkAllWithTyping(os,(o: RDFNode) => { - val newAttempt = Attempt(NodeShapePair(o, sref), Some(path)) + val newAttempt = Attempt(o, sref, shape.message, getSeverity(shape), Some(path)) checkPropertyShape(newAttempt)(o)(ps) }) } yield r @@ -276,9 +280,10 @@ case class Validator(schema: Schema) extends MyLogging { ps <- getPropertyShapeRef(psref, attempt, node) rdf <- getRDF os = rdf.objectsWithPath(node, path).toList + shape <- getShapeRef(psref,attempt,node) _ <- debug(s"propertyShape2PropertyChecker: $os for $path") check: CheckTyping = checkValues(os, o => { - val newAttempt = Attempt(NodeShapePair(o, psref), Some(path)) + val newAttempt = Attempt(o, psref, shape.message, getSeverity(shape), Some(path)) checkPropertyShape(newAttempt)(o)(ps) } ) @@ -588,7 +593,7 @@ case class Validator(schema: Schema) extends MyLogging { private def filterConformShapes(values: Seq[RDFNode], shapes: Seq[ShapeRef], attempt: Attempt): Check[Seq[RDFNode]] = { logger.debug(s"FilterConformShapes(values=$values, shapes=$shapes)") def checkValuesShapes: Check[List[(RDFNode, Boolean)]] = { - values.toList.map(value => conformsNodeShapes(value, shapes, attempt)).sequence + sequence(values.toList.map(value => conformsNodeShapes(value, shapes, attempt))) } for { cs <- checkValuesShapes @@ -613,22 +618,9 @@ case class Validator(schema: Schema) extends MyLogging { } private def or(sRefs: Seq[ShapeRef]): NodeChecker = attempt => node => { - /* val checks: List[CheckTyping] = sRefs.toList.map(s => { - nodeShapeRef(node, s, attempt) - }) */ val last: CheckTyping = fail(s"None of the components of or pass") - for { - // t0 <- getTyping - r <- checkSomeFlag(sRefs.toStream, - (sref: ShapeRef) => nodeShapeRef(node, sref, attempt), - last - ) -/* t2 <- { - logger.debug(s"@@@ Inside or. result of checkSome:\n${showResult(t1)}") - addEvidence(attempt, s"$node passes or(${sRefs.map(_.showId).mkString(",")})") - } */ - // t3 <- combineTypings(Seq(t1, t2)) - } yield r + def fn(sref: ShapeRef): CheckTyping = nodeShapeRef(node, sref, attempt) + checkSomeFlag(sRefs.toStream,fn,last) } private def not(sref: ShapeRef): NodeChecker = attempt => node => { @@ -771,12 +763,11 @@ case class Validator(schema: Schema) extends MyLogging { case _ => err(expectedPropertyShape(node, attempt, s"Expected shape $shape to be a property shape")) } private def addEvidence(attempt: Attempt, msg: String): Check[ShapeTyping] = { - val nodeShape = attempt.nodeShape for { t <- getTyping - shape <- getShapeRef(nodeShape.shape, attempt, attempt.node) - _ <- addLog(List(NodeShapeEvidence(nodeShape, msg))) - } yield t.addEvidence(nodeShape.node, shape, msg) + shape <- getShapeRef(attempt.shapeRef, attempt, attempt.node) + _ <- addLog(List(NodeShapeEvidence(attempt.node, attempt.shapeRef, msg))) + } yield t.addEvidence(attempt.node, shape, msg) } private def addNotEvidence(attempt: Attempt, @@ -784,13 +775,13 @@ case class Validator(schema: Schema) extends MyLogging { msg: String ): Check[ShapeTyping] = { val node = attempt.node - // val sref = attempt.shapeRef + val sref = attempt.shapeRef for { t <- getTyping - sref <- getShapeRef(attempt.nodeShape.shape, attempt, node) - _ <- addLog(List(NodeShapeEvidence(attempt.nodeShape, msg))) + shape <- getShapeRef(sref, attempt, node) + _ <- addLog(List(NodeShapeEvidence(attempt.node, sref, msg))) } yield { - t.addNotEvidence(node, sref, e) + t.addNotEvidence(node, shape, e) } } diff --git a/modules/shacl/src/test/resources/shacl/report/message.ttl b/modules/shacl/src/test/resources/shacl/report/message.ttl new file mode 100644 index 00000000..508aa135 --- /dev/null +++ b/modules/shacl/src/test/resources/shacl/report/message.ttl @@ -0,0 +1,21 @@ +prefix xsd: +prefix : +prefix sh: + +:MyShape + a sh:NodeShape ; + sh:targetNode :MyInstance ; + sh:property :p1 ; + sh:property :p2 . + +:p1 sh:path :myProperty ; + sh:minCount 1 ; + sh:datatype xsd:string ; + sh:severity sh:Warning . +:p2 sh:path :myProperty ; + sh:maxLength 10 ; + sh:message "Too many characters"@en ; + sh:message "Zu viele Zeichen"@de . + +:MyInstance + :myProperty "http://toomanycharacters"^^xsd:anyURI . \ No newline at end of file diff --git a/modules/shacl/src/test/scala/es/weso/shacl/AbstractSyntaxTest.scala b/modules/shacl/src/test/scala/es/weso/shacl/AbstractSyntaxTest.scala index 67cac6c1..a76806f1 100644 --- a/modules/shacl/src/test/scala/es/weso/shacl/AbstractSyntaxTest.scala +++ b/modules/shacl/src/test/scala/es/weso/shacl/AbstractSyntaxTest.scala @@ -17,7 +17,7 @@ class AbstractSyntaxTest extends FunSpec with Matchers { false, List(), false, - Map() + MessageMap.empty ) shape.id should be(id) diff --git a/modules/shacl/src/test/scala/es/weso/shacl/validator/SHACLCheckerTest.scala b/modules/shacl/src/test/scala/es/weso/shacl/validator/SHACLCheckerTest.scala index d8801f62..538968b7 100644 --- a/modules/shacl/src/test/scala/es/weso/shacl/validator/SHACLCheckerTest.scala +++ b/modules/shacl/src/test/scala/es/weso/shacl/validator/SHACLCheckerTest.scala @@ -7,8 +7,8 @@ import cats.implicits._ import es.weso.rdf.RDFReader import es.weso.rdf.jena.RDFAsJenaModel import es.weso.rdf.nodes._ -import es.weso.shacl.{Shape, ShapeRef} -import es.weso.shacl.report.ValidationResult +import es.weso.shacl.{MessageMap, Shape, ShapeRef} +import es.weso.shacl.report.{Severity, ValidationResult} // import es.weso.shacl.validator.ShapeTyping._ // import es.weso.typing.Typing @@ -22,7 +22,11 @@ class SHACLCheckerTest extends FunSpec with Matchers with TryValues with OptionV def mkErr(str: String): ValidationResult = - ValidationResult.basic("",StringLiteral(str),Attempt(NodeShapePair(StringLiteral(str),ShapeRef(StringLiteral(str))),None),str) + ValidationResult.basic("",StringLiteral(str),Attempt(StringLiteral(str), + ShapeRef(StringLiteral(str)), + MessageMap.empty, + Severity.defaultSeverity,None + ),str) type T = (RDFNode, Shape, Boolean)