Skip to content

Commit

Permalink
Merge pull request #499 from aml-org/W-10881317/computed-instance-par…
Browse files Browse the repository at this point in the history
…sing

W-10881317/computed-instance-parsing: implemented instance parsing for allOf, oneOf and if-then-else
  • Loading branch information
looseale authored Apr 1, 2022
2 parents 9132c59 + 24428ed commit 652373c
Show file tree
Hide file tree
Showing 58 changed files with 1,956 additions and 121 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import amf.aml.internal.metamodel.document.DialectInstanceModel
import amf.aml.client.scala.model.document.{DialectInstanceFragment, DialectInstanceProcessingData}
import amf.aml.client.scala.model.domain.{DialectDomainElement, DocumentsModel}
import amf.aml.internal.parse.instances.DialectInstanceParser.encodedElementDefaultId
import amf.aml.internal.parse.instances.parser.InstanceNodeParser
import amf.aml.internal.parse.instances.parser.InstanceElementParser
import amf.aml.internal.validate.DialectValidations.DialectError
import com.github.ghik.silencer.silent

Expand Down Expand Up @@ -47,7 +47,7 @@ class DialectInstanceFragmentParser(root: Root)(implicit override val ctx: Diale
ctx.findNodeMapping(documentMapping.encoded().value()) match {
case Some(nodeMapping) =>
val path = dialectInstanceFragment.id + "#"
InstanceNodeParser(root).parse(path, path + "/", map, nodeMapping, Map(), givenAnnotations = None)
InstanceElementParser(root).parse(path, path + "/", map, nodeMapping, Map(), givenAnnotations = None)
case _ =>
emptyElementWithViolation(s"Could not find node mapping for: ${documentMapping.encoded().value()}")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -157,11 +157,11 @@ class DialectInstanceParser(val root: Root)(implicit val ctx: DialectInstanceCon
entry.value.as[YMap].entries.foreach { declarationEntry =>
val declarationName = declarationEntry.key.as[YScalar].text
val id = pathSegment(declarationsId, List(declarationName))
val node = InstanceNodeParser(root).parse(declarationsId,
id,
declarationEntry.value,
nodeMapping,
givenAnnotations = Some(Annotations(declarationEntry)))
val node = InstanceElementParser(root).parse(declarationsId,
id,
declarationEntry.value,
nodeMapping,
givenAnnotations = Some(Annotations(declarationEntry)))

// lookup by ref name
node.set(DialectDomainElementModel.DeclarationName,
Expand All @@ -187,13 +187,13 @@ class DialectInstanceParser(val root: Root)(implicit val ctx: DialectInstanceCon
val additionalKey =
if (documents.keyProperty().value()) Some(ctx.dialect.name().value())
else None
InstanceNodeParser(root).parse(path,
encodedElementDefaultId(dialectInstance),
map,
nodeMapping,
rootNode = true,
givenAnnotations = None,
additionalKey = additionalKey)
InstanceElementParser(root).parse(path,
encodedElementDefaultId(dialectInstance),
map,
nodeMapping,
rootNode = true,
givenAnnotations = None,
additionalKey = additionalKey)
case _ =>
emptyElementWithViolation(s"Could not find node mapping for: ${mapping.encoded().value()}")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,23 +145,6 @@ object InstanceNodeIdHandling {

trait InstanceNodeIdHandling extends BaseDirectiveOverride { this: DialectInstanceParser =>

protected def generateNodeId(node: DialectDomainElement,
nodeMap: YMap,
path: Seq[String],
defaultId: String,
mapping: NodeMapping,
additionalProperties: Map[String, Any] = Map(),
rootNode: Boolean): String = {
InstanceNodeIdHandling.generateNodeId(node,
nodeMap,
path,
defaultId,
mapping,
additionalProperties,
rootNode,
root)
}

protected def idTemplate(node: DialectDomainElement,
nodeMap: YMap,
path: Seq[String],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ object DialectExtensionParser {
ctx.nestedDialects ++= Seq(dialect)
ctx.withCurrentDialect(dialect) {
val dialectDomainElement =
nodeParser(id, nestedObjectId, propertyEntry.value, nodeMapping, Map.empty)
nodeParser(id, nestedObjectId, propertyEntry.value, nodeMapping, Map.empty, false)
node.withObjectField(property, dialectDomainElement, Right(propertyEntry))
}
case None =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,26 +6,29 @@ import amf.aml.internal.parse.instances.ClosedInstanceNode.checkNode
import amf.aml.internal.parse.instances.DialectInstanceParser.{computeParsingScheme, emptyElement, typesFrom}
import amf.aml.internal.parse.instances.InstanceNodeIdHandling.generateNodeId
import amf.aml.internal.parse.instances.parser.IncludeNodeParser.resolveLink
import amf.aml.internal.parse.instances.parser.applicable.ApplicableMappingFinder
import amf.aml.internal.parse.instances.{DialectInstanceContext, NodeMappableHelper}
import amf.aml.internal.validate.DialectValidations.DialectError
import amf.core.client.common.validation.AMFStyle
import amf.core.client.scala.errorhandling.DefaultErrorHandler
import amf.core.client.scala.model.domain.DomainElement
import amf.core.client.scala.parse.document.SyamlParsedDocument
import amf.core.client.scala.validation.AMFValidationResult
import amf.core.internal.parser.domain.Annotations
import amf.core.internal.parser.{Root, YMapOps}
import amf.core.internal.utils.AmfStrings
import amf.validation.internal.shacl.custom.CustomShaclValidator
import org.yaml.model._

case class InstanceNodeParser(root: Root)(implicit ctx: DialectInstanceContext) extends NodeMappableHelper {
import scala.language.{higherKinds, implicitConversions}

case class InstanceElementParser(root: Root)(implicit ctx: DialectInstanceContext) extends NodeMappableHelper {

private val map: YMap = root.parsed.asInstanceOf[SyamlParsedDocument].document.as[YMap]

def parse(path: String, id: String, entry: YNode, mapping: NodeMappable, additionalProperties: Map[String, Any])(
implicit ctx: DialectInstanceContext): DialectDomainElement =
parse(path, id, entry, mapping, additionalProperties, givenAnnotations = None)
def parse(path: String,
id: String,
entry: YNode,
mapping: NodeMappable,
additionalProperties: Map[String, Any],
parseAllOf: Boolean)(implicit ctx: DialectInstanceContext): DialectDomainElement =
parse(path, id, entry, mapping, additionalProperties, givenAnnotations = None, parseAllOf = parseAllOf)

def parse(path: String,
defaultId: String,
Expand All @@ -35,7 +38,7 @@ case class InstanceNodeParser(root: Root)(implicit ctx: DialectInstanceContext)
rootNode: Boolean = false,
givenAnnotations: Option[Annotations],
additionalKey: Option[String] = None,
)(implicit ctx: DialectInstanceContext): DialectDomainElement = {
parseAllOf: Boolean = true)(implicit ctx: DialectInstanceContext): DialectDomainElement = {
val result: DialectDomainElement = ast.tagType match {
case YType.Map =>
val astMap = ast.as[YMap]
Expand All @@ -47,7 +50,8 @@ case class InstanceNodeParser(root: Root)(implicit ctx: DialectInstanceContext)
additionalProperties,
rootNode,
givenAnnotations,
additionalKey)
additionalKey,
parseAllOf)

case YType.Str => resolveLink(ast, mappable, defaultId, givenAnnotations)
case YType.Include => resolveLink(ast, mappable, defaultId, givenAnnotations)
Expand All @@ -59,8 +63,11 @@ case class InstanceNodeParser(root: Root)(implicit ctx: DialectInstanceContext)
// if we are parsing a patch document we mark the node as abstract

mappable match {
case anyMappable: AnyMapping if anyMappable.and.nonEmpty || anyMappable.or.nonEmpty || anyMappable.ifMapping.option().nonEmpty => // don't do anything
case _ => assignDefinedByAndTypes(mappable, result)
case anyMappable: AnyMapping
if anyMappable.and.nonEmpty || anyMappable.or.nonEmpty || anyMappable.ifMapping
.option()
.nonEmpty => // don't do anything
case _ => assignDefinedByAndTypes(mappable, result)
}
if (ctx.isPatch) result.withAbstract(true)
result
Expand Down Expand Up @@ -94,14 +101,31 @@ case class InstanceNodeParser(root: Root)(implicit ctx: DialectInstanceContext)
additionalProperties: Map[String, Any],
rootNode: Boolean,
givenAnnotations: Option[Annotations],
additionalKey: Option[String])(implicit ctx: DialectInstanceContext) = {
additionalKey: Option[String],
parseAllOf: Boolean = true)(implicit ctx: DialectInstanceContext) = {
computeParsingScheme(astMap) match {
case "$ref" => RefNodeParser.parse(defaultId, astMap, mappable, root)
case "$include" => IncludeNodeParser.parse(ast, mappable, defaultId, givenAnnotations)
case _ =>
mappable match {
case conditional: AnyMapping if conditional.ifMapping.nonEmpty =>
parseConditionally(path, defaultId, astMap, conditional)
case any: AnyMapping
if parseAllOf && (any.and.nonEmpty || any.or.nonEmpty || any.ifMapping.option().nonEmpty) =>
val applicableMapping = ApplicableMappingFinder(root).find(map, any)
applicableMapping
.map { foundMapping =>
parseWithNodeMapping(defaultId,
astMap,
ast,
additionalProperties,
rootNode,
givenAnnotations,
additionalKey,
foundMapping)
}
.getOrElse {
// TODO: add error
emptyElement(defaultId, astMap, mappable, givenAnnotations)
}
case mapping: NodeMapping =>
parseWithNodeMapping(defaultId,
astMap,
Expand All @@ -118,61 +142,6 @@ case class InstanceNodeParser(root: Root)(implicit ctx: DialectInstanceContext)
}
}

object IfThenElseBranchCriteria {
def choose(map: YMap, mapping: AnyMapping)(implicit ctx: DialectInstanceContext): Option[String] = {
mapping.ifMapping.option()
.flatMap(ifMappingId => choose(map, ifMappingId, mapping))
}

def choose(map: YMap, ifMappingId: String, mapping: AnyMapping)(implicit ctx: DialectInstanceContext): Option[String] = {
ctx.findNodeMapping(ifMappingId).flatMap { ifMapping =>
val (ifParsedNode, conformsParsing) = couldParse(map, ifMapping)
if (!conformsParsing) return mapping.elseMapping.option()
else {
val report = validateParsed(ifMapping, ifParsedNode)
val isIfCompliant = report.conforms
if (isIfCompliant) return mapping.thenMapping.option()
else return mapping.elseMapping.option()
}
}
}
private def couldParse(map: YMap, ifMapping: ctx.NodeMappable) = {
val nextContext = ctx.copy(DefaultErrorHandler())
val ifParsed = parse("", "if", map, ifMapping, Map.empty)(nextContext)
val conforms = ignoreClosedShapeErrors(nextContext.eh.getResults).isEmpty
(ifParsed, conforms)
}

private def ignoreClosedShapeErrors(results: Seq[AMFValidationResult]): Seq[AMFValidationResult] = {
results.filterNot(_.validationId.contains("closed"))
}

private def validateParsed(ifMapping: ctx.NodeMappable, ifParsed: DialectDomainElement) = {
val mappingsInTree = AmlSubGraphCollector.collect(ifMapping.id, ctx.dialect)
val validator = new CustomShaclValidator(Map.empty, AMFStyle)
val validations = ctx.constraints
.map(p => p.validations.filter(x => x.targetClass.intersect(mappingsInTree).nonEmpty))
.getOrElse(Nil)
val report = validator.validate(ifParsed, validations)
report
}
}

private def parseConditionally(path: String,
defaultId: String,
astMap: YMap,
mappable: AnyMapping): DialectDomainElement = {
IfThenElseBranchCriteria
.choose(astMap, mappable)
.flatMap(ctx.findNodeMapping)
.map(mapping => parse(path, defaultId, astMap, mapping, Map.empty)) match {
case Some(mapping) => mapping
case None =>
// TODO: Violation
DialectDomainElement().withId(defaultId)
}
}

private def parseWithNodeMapping(defaultId: String,
astMap: YMap,
ast: YNode,
Expand All @@ -181,28 +150,31 @@ case class InstanceNodeParser(root: Root)(implicit ctx: DialectInstanceContext)
givenAnnotations: Option[Annotations],
additionalKey: Option[String],
mapping: NodeMapping)(implicit ctx: DialectInstanceContext) = {
val annotations = givenAnnotations.getOrElse(Annotations(ast))
val annotations = givenAnnotations.getOrElse(Annotations(ast))
val node: DialectDomainElement = DialectDomainElement(defaultId.urlDecoded, mapping, annotations)
val finalId = generateNodeId(node, astMap, Seq(defaultId), defaultId, mapping, additionalProperties, rootNode, root)
val finalId =
generateNodeId(node, astMap, Seq(defaultId), defaultId, mapping, additionalProperties, rootNode, root)
node.withId(finalId)
parseAnnotations(astMap, node, ctx.declarations)
mapping.propertiesMapping().foreach { propertyMapping =>
val propertyName = propertyMapping.name().value()
astMap.key(propertyName) match {
case Some(entry) =>
val nestedId =
if (Option(ctx.dialect.documents()).flatMap(_.selfEncoded().option()).getOrElse(false) && rootNode)
defaultId + "#/"
else defaultId
parseProperty(nestedId, entry, propertyMapping, node)
case None => // ignore
astMap.key(propertyName).foreach { entry =>
val nestedId = computeNestedPropertyId(defaultId, rootNode, ctx)
parseProperty(nestedId, entry, propertyMapping, node)
}
}
val shouldErrorOnExtraProperties = mapping.closed.option().getOrElse(true) // default behaviour is to error out
if (shouldErrorOnExtraProperties) checkNodeForAdditionalKeys(finalId, mapping.id, astMap.map, mapping, astMap, rootNode, additionalKey)
if (shouldErrorOnExtraProperties)
checkNodeForAdditionalKeys(finalId, mapping.id, astMap.map, mapping, astMap, rootNode, additionalKey)
node
}

private def computeNestedPropertyId(defaultId: String, rootNode: Boolean, ctx: DialectInstanceContext) = {
if (Option(ctx.dialect.documents()).flatMap(_.selfEncoded().option()).getOrElse(false) && rootNode)
defaultId + "#/"
else defaultId
}

private def parseProperty(id: String,
propertyEntry: YMapEntry,
property: PropertyMapping,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import scala.language.higherKinds

object ObjectCollectionPropertyParser extends NodeMappableHelper {

type NodeParser = (String, String, YNode, NodeMappable, Map[String, Any]) => DialectDomainElement
type NodeParser = (String, String, YNode, NodeMappable, Map[String, Any], Boolean) => DialectDomainElement

type ObjectUnionParser[T <: DomainElement] =
(String, Seq[String], YNode, NodeWithDiscriminator[_], Map[String, Any]) => DialectDomainElement
Expand Down Expand Up @@ -51,7 +51,7 @@ object ObjectCollectionPropertyParser extends NodeMappableHelper {
ctx.dialect.declares.find(_.id == range.head) match {
case Some(nodeMapping: NodeMappable) =>
val dialectDomainElement =
nodeParser(id, nestedObjectId, elementNode, nodeMapping, additionalProperties)
nodeParser(id, nestedObjectId, elementNode, nodeMapping, additionalProperties, false)
checkDuplicated(dialectDomainElement, elementNode, idsMap)
Some(dialectDomainElement)
case _ => None
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ object ObjectMapPropertyParser extends NodeMappableHelper {
case range: Seq[String] if range.size == 1 =>
ctx.dialect.declares.find(_.id == range.head) match {
case Some(nodeMapping: NodeMappable) if keyEntry.value.tagType != YType.Null =>
Some(nodeParser(id, nestedObjectId, keyEntry.value, nodeMapping, keyAdditionalProperties))
Some(nodeParser(id, nestedObjectId, keyEntry.value, nodeMapping, keyAdditionalProperties, false))
case _ => None
}
case _ => None
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ object SimpleObjectPropertyParser {
ctx.dialect.declares.find(_.id == rangeId) match {
case Some(nodeMapping: NodeMappable) =>
val dialectDomainElement =
nodeParser(id, nestedObjectId, propertyEntry.value, nodeMapping, additionalProperties)
nodeParser(id, nestedObjectId, propertyEntry.value, nodeMapping, additionalProperties, false)
node.withObjectField(property, dialectDomainElement, Right(propertyEntry))
case _ => // ignore
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package amf.aml.internal.parse.instances.parser.applicable

import amf.aml.client.scala.model.domain.AnyMapping
import amf.aml.internal.parse.instances.DialectInstanceContext
import ApplicableMapping.aggregate
import amf.aml.internal.render.emitters.instances.DialectIndex
import amf.core.internal.parser.Root
import org.yaml.model.YMap

object AndFinderPath extends FinderPath {
override def walk(mapping: AnyMapping, map: YMap, index: DialectIndex, finder: ApplicableMappingFinder)(
implicit ctx: DialectInstanceContext): ApplicableMapping = {
if (mapping.and.isEmpty) return ApplicableMapping.empty
val andMappings = findMappingsFor(mapping.and, index)
val aggregateMapping = aggregate(andMappings.map(node => finder.find(map, node, index)))
aggregateMapping
}
}
Loading

0 comments on commit 652373c

Please sign in to comment.