From 7f984b0fc726a4a7c443a4be9059a09b38597325 Mon Sep 17 00:00:00 2001 From: Nicolas Schejtman Date: Fri, 8 Apr 2022 12:28:49 -0300 Subject: [PATCH 1/7] W-10671678: add abstract operation classes to deferred set in RDF parsing --- .../scala/amf/rdf/internal/DefaultNodeClassSorter.scala | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/amf-rdf/shared/src/main/scala/amf/rdf/internal/DefaultNodeClassSorter.scala b/amf-rdf/shared/src/main/scala/amf/rdf/internal/DefaultNodeClassSorter.scala index 20513c04c..4fb68b808 100644 --- a/amf-rdf/shared/src/main/scala/amf/rdf/internal/DefaultNodeClassSorter.scala +++ b/amf-rdf/shared/src/main/scala/amf/rdf/internal/DefaultNodeClassSorter.scala @@ -12,7 +12,12 @@ class DefaultNodeClassSorter() { (Namespace.Document + "Unit").iri(), (Namespace.Shacl + "Shape").iri(), (Namespace.Shapes + "Shape").iri(), - (Namespace.ApiContract + "Message").iri() + (Namespace.ApiContract + "Message").iri(), + (Namespace.Core + "Operation").iri(), + (Namespace.Core + "Parameter").iri(), + (Namespace.Core + "Payload").iri(), + (Namespace.Core + "Request").iri(), + (Namespace.Core + "Response").iri() ) def sortedClassesOf(node: Node): Seq[String] = { From 1c58a5fc65ce30bba73f32f5542eab0b922a3d8e Mon Sep 17 00:00:00 2001 From: Nicolas Schejtman Date: Tue, 12 Apr 2022 18:08:15 -0300 Subject: [PATCH 2/7] W-10984850: filter non-applicable validations when validating enum values in dialect definition --- .../validate/DialectEnumValidator.scala | 38 ++-- .../invalid.json | 38 ++++ .../report.json | 180 ++++++++++++++++++ .../valid.json | 38 ++++ .../invalid.json | 24 +++ .../report.json | 64 +++++++ .../valid.json | 24 +++ .../DialectDefinitionValidationTest.scala | 16 ++ 8 files changed, 406 insertions(+), 16 deletions(-) create mode 100644 amf-aml/shared/src/test/resources/vocabularies2/instances/invalids/dialect-enum-multiple-validations/invalid.json create mode 100644 amf-aml/shared/src/test/resources/vocabularies2/instances/invalids/dialect-enum-multiple-validations/report.json create mode 100644 amf-aml/shared/src/test/resources/vocabularies2/instances/invalids/dialect-enum-multiple-validations/valid.json create mode 100644 amf-aml/shared/src/test/resources/vocabularies2/instances/invalids/dialect-enum-single-validations/invalid.json create mode 100644 amf-aml/shared/src/test/resources/vocabularies2/instances/invalids/dialect-enum-single-validations/report.json create mode 100644 amf-aml/shared/src/test/resources/vocabularies2/instances/invalids/dialect-enum-single-validations/valid.json diff --git a/amf-aml/shared/src/main/scala/amf/aml/internal/validate/DialectEnumValidator.scala b/amf-aml/shared/src/main/scala/amf/aml/internal/validate/DialectEnumValidator.scala index a87bb00db..659bad734 100644 --- a/amf-aml/shared/src/main/scala/amf/aml/internal/validate/DialectEnumValidator.scala +++ b/amf-aml/shared/src/main/scala/amf/aml/internal/validate/DialectEnumValidator.scala @@ -20,29 +20,35 @@ import org.yaml.model.{YNode, YScalar} case class DialectEnumValidator() extends ShaclReportAdaptation { def validate(dialect: Dialect): AMFValidationReport = { - val eh = new SyamlAMFErrorHandler(DefaultErrorHandler()) val candidates = LiteralCandidateCollector.collect(dialect) val reports = candidates.map { candidate => - val validations = - new AMFDialectValidations(dialect)(DefaultNodeMappableFinder(dialect)).propertyValidations(candidate.node) - val nodes = candidate.enums.flatMap(toNode) - parseErrors(eh, candidate, nodes) - val reports = nodes.map { - case (_, scalar) => - val report = validate(scalar, candidate, validations) - adaptToAmfReport(dialect, - AmlProfile, - report, - scalar.annotations.location(), - LexicalInformation(scalar.annotations.lexical())) - } - val mergedReport: AMFValidationReport = mergeReports(dialect, reports) - mergedReport.copy(results = mergedReport.results ++ eh.getResults) + val eh = new SyamlAMFErrorHandler(DefaultErrorHandler()) // create error handler for each candidate to avoid duplicating errors in report + validateCandidate(dialect, eh, candidate) } val mergedReport = mergeReports(dialect, reports) mergedReport } + private def validateCandidate(dialect: Dialect, eh: SyamlAMFErrorHandler, candidate: AmlValidationCandidate) = { + val validations = + new AMFDialectValidations(dialect)(DefaultNodeMappableFinder(dialect)) + .propertyValidations(candidate.node) + .filter(_.propertyConstraints.exists(_.ramlPropertyId == candidate.mapping.nodePropertyMapping().value())) // should optimize this + val nodes = candidate.enums.flatMap(toNode) + parseErrors(eh, candidate, nodes) + val reports = nodes.map { + case (_, scalar) => + val report = validate(scalar, candidate, validations) + adaptToAmfReport(dialect, + AmlProfile, + report, + scalar.annotations.location(), + LexicalInformation(scalar.annotations.lexical())) + } + val mergedReport: AMFValidationReport = mergeReports(dialect, reports) + mergedReport.copy(results = mergedReport.results ++ eh.getResults) + } + private def mergeReports(dialect: Dialect, reports: Seq[AMFValidationReport]) = { val mergedReport = reports.foldLeft(AMFValidationReport.empty(dialect.id, AmlProfile)) { (acc, curr) => acc.merge(curr) diff --git a/amf-aml/shared/src/test/resources/vocabularies2/instances/invalids/dialect-enum-multiple-validations/invalid.json b/amf-aml/shared/src/test/resources/vocabularies2/instances/invalids/dialect-enum-multiple-validations/invalid.json new file mode 100644 index 000000000..2e1696a51 --- /dev/null +++ b/amf-aml/shared/src/test/resources/vocabularies2/instances/invalids/dialect-enum-multiple-validations/invalid.json @@ -0,0 +1,38 @@ +{ + "$type": "Dialect 1.0", + "nodeMappings": { + "MyNodeMapping": { + "mapping": { + "propertyA": { + "range": "integer", + "mandatory": true, + "enum": [ + "some-enum-value" + ] + }, + "propertyB": { + "range": "boolean", + "mandatory": false, + "enum": [ + 60 + ] + }, + "propertyC": { + "range": "boolean", + "mandatory": false, + "enum": [ + 10000 + ] + } + }, + "additionalProperties": true + } + }, + "dialect": "Test", + "version": "1.0", + "documents": { + "root": { + "encodes": "MyNodeMapping" + } + } +} \ No newline at end of file diff --git a/amf-aml/shared/src/test/resources/vocabularies2/instances/invalids/dialect-enum-multiple-validations/report.json b/amf-aml/shared/src/test/resources/vocabularies2/instances/invalids/dialect-enum-multiple-validations/report.json new file mode 100644 index 000000000..f70eae67d --- /dev/null +++ b/amf-aml/shared/src/test/resources/vocabularies2/instances/invalids/dialect-enum-multiple-validations/report.json @@ -0,0 +1,180 @@ +{ + "@type": "http://www.w3.org/ns/shacl#ValidationReport", + "http://www.w3.org/ns/shacl#conforms": false, + "http://www.w3.org/ns/shacl#result": [ + { + "@type": "http://www.w3.org/ns/shacl#ValidationResult", + "http://www.w3.org/ns/shacl#resultSeverity": { + "@id": "http://www.w3.org/ns/shacl#Violation" + }, + "http://www.w3.org/ns/shacl#focusNode": { + "@id": "anId" + }, + "http://www.w3.org/ns/shacl#resultPath": { + "@id": "http://a.ml/vocabularies/data#propertyA" + }, + "http://www.w3.org/ns/shacl#resultMessage": "Cannot find expected range for property http://a.ml/vocabularies/data#propertyA (propertyA). Found 'http://www.w3.org/2001/XMLSchema#string', expected 'http://www.w3.org/2001/XMLSchema#integer'", + "http://www.w3.org/ns/shacl#sourceShape": { + "@id": "http://a.ml/vocabularies/amf/aml#inconsistent-property-range-value" + }, + "http://a.ml/vocabularies/amf/parser#lexicalPosition": { + "@type": "http://a.ml/vocabularies/amf/parser#Position", + "http://a.ml/vocabularies/amf/parser#start": { + "@type": "http://a.ml/vocabularies/amf/parser#Location", + "http://a.ml/vocabularies/amf/parser#line": 10, + "http://a.ml/vocabularies/amf/parser#column": 12 + }, + "http://a.ml/vocabularies/amf/parser#end": { + "@type": "http://a.ml/vocabularies/amf/parser#Location", + "http://a.ml/vocabularies/amf/parser#line": 10, + "http://a.ml/vocabularies/amf/parser#column": 29 + } + } + }, + { + "@type": "http://www.w3.org/ns/shacl#ValidationResult", + "http://www.w3.org/ns/shacl#resultSeverity": { + "@id": "http://www.w3.org/ns/shacl#Violation" + }, + "http://www.w3.org/ns/shacl#focusNode": { + "@id": "file://amf-aml/shared/src/test/resources/vocabularies2/instances/invalids/dialect-enum-multiple-validations/invalid.json" + }, + "http://www.w3.org/ns/shacl#resultPath": { + "@id": "http://a.ml/vocabularies/data#propertyA" + }, + "http://www.w3.org/ns/shacl#resultMessage": "Property 'propertyA' value must be of type http://www.w3.org/2001/XMLSchema#integer", + "http://www.w3.org/ns/shacl#sourceShape": { + "@id": "file://amf-aml/shared/src/test/resources/vocabularies2/instances/invalids/dialect-enum-multiple-validations/invalid.json#/declarations/MyNodeMapping_propertyA_dataRange_validation" + }, + "http://a.ml/vocabularies/amf/parser#lexicalPosition": { + "@type": "http://a.ml/vocabularies/amf/parser#Position", + "http://a.ml/vocabularies/amf/parser#start": { + "@type": "http://a.ml/vocabularies/amf/parser#Location", + "http://a.ml/vocabularies/amf/parser#line": 10, + "http://a.ml/vocabularies/amf/parser#column": 12 + }, + "http://a.ml/vocabularies/amf/parser#end": { + "@type": "http://a.ml/vocabularies/amf/parser#Location", + "http://a.ml/vocabularies/amf/parser#line": 10, + "http://a.ml/vocabularies/amf/parser#column": 29 + } + } + }, + { + "@type": "http://www.w3.org/ns/shacl#ValidationResult", + "http://www.w3.org/ns/shacl#resultSeverity": { + "@id": "http://www.w3.org/ns/shacl#Violation" + }, + "http://www.w3.org/ns/shacl#focusNode": { + "@id": "anId" + }, + "http://www.w3.org/ns/shacl#resultPath": { + "@id": "http://a.ml/vocabularies/data#propertyB" + }, + "http://www.w3.org/ns/shacl#resultMessage": "Cannot find expected range for property http://a.ml/vocabularies/data#propertyB (propertyB). Found 'http://www.w3.org/2001/XMLSchema#integer', expected 'http://www.w3.org/2001/XMLSchema#boolean'", + "http://www.w3.org/ns/shacl#sourceShape": { + "@id": "http://a.ml/vocabularies/amf/aml#inconsistent-property-range-value" + }, + "http://a.ml/vocabularies/amf/parser#lexicalPosition": { + "@type": "http://a.ml/vocabularies/amf/parser#Position", + "http://a.ml/vocabularies/amf/parser#start": { + "@type": "http://a.ml/vocabularies/amf/parser#Location", + "http://a.ml/vocabularies/amf/parser#line": 17, + "http://a.ml/vocabularies/amf/parser#column": 12 + }, + "http://a.ml/vocabularies/amf/parser#end": { + "@type": "http://a.ml/vocabularies/amf/parser#Location", + "http://a.ml/vocabularies/amf/parser#line": 17, + "http://a.ml/vocabularies/amf/parser#column": 14 + } + } + }, + { + "@type": "http://www.w3.org/ns/shacl#ValidationResult", + "http://www.w3.org/ns/shacl#resultSeverity": { + "@id": "http://www.w3.org/ns/shacl#Violation" + }, + "http://www.w3.org/ns/shacl#focusNode": { + "@id": "file://amf-aml/shared/src/test/resources/vocabularies2/instances/invalids/dialect-enum-multiple-validations/invalid.json" + }, + "http://www.w3.org/ns/shacl#resultPath": { + "@id": "http://a.ml/vocabularies/data#propertyB" + }, + "http://www.w3.org/ns/shacl#resultMessage": "Property 'propertyB' value must be of type http://www.w3.org/2001/XMLSchema#boolean", + "http://www.w3.org/ns/shacl#sourceShape": { + "@id": "file://amf-aml/shared/src/test/resources/vocabularies2/instances/invalids/dialect-enum-multiple-validations/invalid.json#/declarations/MyNodeMapping_propertyB_dataRange_validation" + }, + "http://a.ml/vocabularies/amf/parser#lexicalPosition": { + "@type": "http://a.ml/vocabularies/amf/parser#Position", + "http://a.ml/vocabularies/amf/parser#start": { + "@type": "http://a.ml/vocabularies/amf/parser#Location", + "http://a.ml/vocabularies/amf/parser#line": 17, + "http://a.ml/vocabularies/amf/parser#column": 12 + }, + "http://a.ml/vocabularies/amf/parser#end": { + "@type": "http://a.ml/vocabularies/amf/parser#Location", + "http://a.ml/vocabularies/amf/parser#line": 17, + "http://a.ml/vocabularies/amf/parser#column": 14 + } + } + }, + { + "@type": "http://www.w3.org/ns/shacl#ValidationResult", + "http://www.w3.org/ns/shacl#resultSeverity": { + "@id": "http://www.w3.org/ns/shacl#Violation" + }, + "http://www.w3.org/ns/shacl#focusNode": { + "@id": "anId" + }, + "http://www.w3.org/ns/shacl#resultPath": { + "@id": "http://a.ml/vocabularies/data#propertyC" + }, + "http://www.w3.org/ns/shacl#resultMessage": "Cannot find expected range for property http://a.ml/vocabularies/data#propertyC (propertyC). Found 'http://www.w3.org/2001/XMLSchema#integer', expected 'http://www.w3.org/2001/XMLSchema#boolean'", + "http://www.w3.org/ns/shacl#sourceShape": { + "@id": "http://a.ml/vocabularies/amf/aml#inconsistent-property-range-value" + }, + "http://a.ml/vocabularies/amf/parser#lexicalPosition": { + "@type": "http://a.ml/vocabularies/amf/parser#Position", + "http://a.ml/vocabularies/amf/parser#start": { + "@type": "http://a.ml/vocabularies/amf/parser#Location", + "http://a.ml/vocabularies/amf/parser#line": 24, + "http://a.ml/vocabularies/amf/parser#column": 12 + }, + "http://a.ml/vocabularies/amf/parser#end": { + "@type": "http://a.ml/vocabularies/amf/parser#Location", + "http://a.ml/vocabularies/amf/parser#line": 24, + "http://a.ml/vocabularies/amf/parser#column": 17 + } + } + }, + { + "@type": "http://www.w3.org/ns/shacl#ValidationResult", + "http://www.w3.org/ns/shacl#resultSeverity": { + "@id": "http://www.w3.org/ns/shacl#Violation" + }, + "http://www.w3.org/ns/shacl#focusNode": { + "@id": "file://amf-aml/shared/src/test/resources/vocabularies2/instances/invalids/dialect-enum-multiple-validations/invalid.json" + }, + "http://www.w3.org/ns/shacl#resultPath": { + "@id": "http://a.ml/vocabularies/data#propertyC" + }, + "http://www.w3.org/ns/shacl#resultMessage": "Property 'propertyC' value must be of type http://www.w3.org/2001/XMLSchema#boolean", + "http://www.w3.org/ns/shacl#sourceShape": { + "@id": "file://amf-aml/shared/src/test/resources/vocabularies2/instances/invalids/dialect-enum-multiple-validations/invalid.json#/declarations/MyNodeMapping_propertyC_dataRange_validation" + }, + "http://a.ml/vocabularies/amf/parser#lexicalPosition": { + "@type": "http://a.ml/vocabularies/amf/parser#Position", + "http://a.ml/vocabularies/amf/parser#start": { + "@type": "http://a.ml/vocabularies/amf/parser#Location", + "http://a.ml/vocabularies/amf/parser#line": 24, + "http://a.ml/vocabularies/amf/parser#column": 12 + }, + "http://a.ml/vocabularies/amf/parser#end": { + "@type": "http://a.ml/vocabularies/amf/parser#Location", + "http://a.ml/vocabularies/amf/parser#line": 24, + "http://a.ml/vocabularies/amf/parser#column": 17 + } + } + } + ] +} diff --git a/amf-aml/shared/src/test/resources/vocabularies2/instances/invalids/dialect-enum-multiple-validations/valid.json b/amf-aml/shared/src/test/resources/vocabularies2/instances/invalids/dialect-enum-multiple-validations/valid.json new file mode 100644 index 000000000..a9283c39d --- /dev/null +++ b/amf-aml/shared/src/test/resources/vocabularies2/instances/invalids/dialect-enum-multiple-validations/valid.json @@ -0,0 +1,38 @@ +{ + "$type": "Dialect 1.0", + "nodeMappings": { + "MyNodeMapping": { + "mapping": { + "propertyA": { + "range": "string", + "mandatory": true, + "enum": [ + "some-enum-value" + ] + }, + "propertyB": { + "range": "integer", + "mandatory": false, + "enum": [ + 60 + ] + }, + "propertyC": { + "range": "integer", + "mandatory": false, + "enum": [ + 10000 + ] + } + }, + "additionalProperties": true + } + }, + "dialect": "Test", + "version": "1.0", + "documents": { + "root": { + "encodes": "MyNodeMapping" + } + } +} \ No newline at end of file diff --git a/amf-aml/shared/src/test/resources/vocabularies2/instances/invalids/dialect-enum-single-validations/invalid.json b/amf-aml/shared/src/test/resources/vocabularies2/instances/invalids/dialect-enum-single-validations/invalid.json new file mode 100644 index 000000000..9a83fb0cd --- /dev/null +++ b/amf-aml/shared/src/test/resources/vocabularies2/instances/invalids/dialect-enum-single-validations/invalid.json @@ -0,0 +1,24 @@ +{ + "$type": "Dialect 1.0", + "nodeMappings": { + "MyNodeMapping": { + "mapping": { + "propertyA": { + "range": "integer", + "mandatory": true, + "enum": [ + "some-enum-value" + ] + } + }, + "additionalProperties": true + } + }, + "dialect": "Test", + "version": "1.0", + "documents": { + "root": { + "encodes": "MyNodeMapping" + } + } +} \ No newline at end of file diff --git a/amf-aml/shared/src/test/resources/vocabularies2/instances/invalids/dialect-enum-single-validations/report.json b/amf-aml/shared/src/test/resources/vocabularies2/instances/invalids/dialect-enum-single-validations/report.json new file mode 100644 index 000000000..464c56b68 --- /dev/null +++ b/amf-aml/shared/src/test/resources/vocabularies2/instances/invalids/dialect-enum-single-validations/report.json @@ -0,0 +1,64 @@ +{ + "@type": "http://www.w3.org/ns/shacl#ValidationReport", + "http://www.w3.org/ns/shacl#conforms": false, + "http://www.w3.org/ns/shacl#result": [ + { + "@type": "http://www.w3.org/ns/shacl#ValidationResult", + "http://www.w3.org/ns/shacl#resultSeverity": { + "@id": "http://www.w3.org/ns/shacl#Violation" + }, + "http://www.w3.org/ns/shacl#focusNode": { + "@id": "anId" + }, + "http://www.w3.org/ns/shacl#resultPath": { + "@id": "http://a.ml/vocabularies/data#propertyA" + }, + "http://www.w3.org/ns/shacl#resultMessage": "Cannot find expected range for property http://a.ml/vocabularies/data#propertyA (propertyA). Found 'http://www.w3.org/2001/XMLSchema#string', expected 'http://www.w3.org/2001/XMLSchema#integer'", + "http://www.w3.org/ns/shacl#sourceShape": { + "@id": "http://a.ml/vocabularies/amf/aml#inconsistent-property-range-value" + }, + "http://a.ml/vocabularies/amf/parser#lexicalPosition": { + "@type": "http://a.ml/vocabularies/amf/parser#Position", + "http://a.ml/vocabularies/amf/parser#start": { + "@type": "http://a.ml/vocabularies/amf/parser#Location", + "http://a.ml/vocabularies/amf/parser#line": 10, + "http://a.ml/vocabularies/amf/parser#column": 12 + }, + "http://a.ml/vocabularies/amf/parser#end": { + "@type": "http://a.ml/vocabularies/amf/parser#Location", + "http://a.ml/vocabularies/amf/parser#line": 10, + "http://a.ml/vocabularies/amf/parser#column": 29 + } + } + }, + { + "@type": "http://www.w3.org/ns/shacl#ValidationResult", + "http://www.w3.org/ns/shacl#resultSeverity": { + "@id": "http://www.w3.org/ns/shacl#Violation" + }, + "http://www.w3.org/ns/shacl#focusNode": { + "@id": "file://amf-aml/shared/src/test/resources/vocabularies2/instances/invalids/dialect-enum-single-validations/invalid.json" + }, + "http://www.w3.org/ns/shacl#resultPath": { + "@id": "http://a.ml/vocabularies/data#propertyA" + }, + "http://www.w3.org/ns/shacl#resultMessage": "Property 'propertyA' value must be of type http://www.w3.org/2001/XMLSchema#integer", + "http://www.w3.org/ns/shacl#sourceShape": { + "@id": "file://amf-aml/shared/src/test/resources/vocabularies2/instances/invalids/dialect-enum-single-validations/invalid.json#/declarations/MyNodeMapping_propertyA_dataRange_validation" + }, + "http://a.ml/vocabularies/amf/parser#lexicalPosition": { + "@type": "http://a.ml/vocabularies/amf/parser#Position", + "http://a.ml/vocabularies/amf/parser#start": { + "@type": "http://a.ml/vocabularies/amf/parser#Location", + "http://a.ml/vocabularies/amf/parser#line": 10, + "http://a.ml/vocabularies/amf/parser#column": 12 + }, + "http://a.ml/vocabularies/amf/parser#end": { + "@type": "http://a.ml/vocabularies/amf/parser#Location", + "http://a.ml/vocabularies/amf/parser#line": 10, + "http://a.ml/vocabularies/amf/parser#column": 29 + } + } + } + ] +} diff --git a/amf-aml/shared/src/test/resources/vocabularies2/instances/invalids/dialect-enum-single-validations/valid.json b/amf-aml/shared/src/test/resources/vocabularies2/instances/invalids/dialect-enum-single-validations/valid.json new file mode 100644 index 000000000..d4f9525a9 --- /dev/null +++ b/amf-aml/shared/src/test/resources/vocabularies2/instances/invalids/dialect-enum-single-validations/valid.json @@ -0,0 +1,24 @@ +{ + "$type": "Dialect 1.0", + "nodeMappings": { + "MyNodeMapping": { + "mapping": { + "propertyA": { + "range": "string", + "mandatory": true, + "enum": [ + "some-enum-value" + ] + } + }, + "additionalProperties": true + } + }, + "dialect": "Test", + "version": "1.0", + "documents": { + "root": { + "encodes": "MyNodeMapping" + } + } +} \ No newline at end of file diff --git a/amf-aml/shared/src/test/scala/amf/testing/validation/DialectDefinitionValidationTest.scala b/amf-aml/shared/src/test/scala/amf/testing/validation/DialectDefinitionValidationTest.scala index 9c42fb306..8b00b98ae 100644 --- a/amf-aml/shared/src/test/scala/amf/testing/validation/DialectDefinitionValidationTest.scala +++ b/amf-aml/shared/src/test/scala/amf/testing/validation/DialectDefinitionValidationTest.scala @@ -170,4 +170,20 @@ class DialectDefinitionValidationTest extends AsyncFunSuite with Matchers with D validate("../../../dialects/enum-dialect-validation-incorrect-type/dialect.yaml", Some("../../dialects/enum-dialect-validation-incorrect-type/report.json")) } + + test("Node mapping with multiple properties with enums - Valid") { + validate("/dialect-enum-multiple-validations/valid.json", None) + } + + test("Node mapping with multiple properties with enums - Invalid") { + validate("/dialect-enum-multiple-validations/invalid.json", Some("/dialect-enum-multiple-validations/report.json")) + } + + test("Node mapping with single properties with enums - Valid") { + validate("/dialect-enum-single-validations/valid.json", None) + } + + test("Node mapping with single properties with enums - Invalid") { + validate("/dialect-enum-single-validations/invalid.json", Some("/dialect-enum-single-validations/report.json")) + } } From ca7b7694ebd3b789095af2406137473ac07fb202 Mon Sep 17 00:00:00 2001 From: Loose Date: Tue, 12 Apr 2022 17:37:40 -0300 Subject: [PATCH 3/7] W-10974844 - Added minItems parsing and emission and mandatory field --- .../model/domain/PropertyMapping.scala | 65 +- .../model/domain/PropertyLikeMapping.scala | 4 + .../domain/PropertyLikeMappingModel.scala | 20 +- .../domain/PropertyMappingModel.scala | 16 +- .../property/like/MandatoryParser.scala | 78 +- .../dialects/PropertyMappingEmitter.scala | 63 +- .../min-items/dialect.cycled.jsonld.yaml | 33 + .../dialects/min-items/dialect.cycled.yaml | 33 + .../min-items/dialect.expanded.jsonld | 880 ++++++++++++++++++ .../min-items/dialect.flattened.jsonld | 627 +++++++++++++ .../dialects/min-items/dialect.yaml | 34 + .../testing/parsing/DialectsParsingTest.scala | 2 + 12 files changed, 1769 insertions(+), 86 deletions(-) create mode 100644 amf-aml/shared/src/test/resources/vocabularies2/dialects/min-items/dialect.cycled.jsonld.yaml create mode 100644 amf-aml/shared/src/test/resources/vocabularies2/dialects/min-items/dialect.cycled.yaml create mode 100644 amf-aml/shared/src/test/resources/vocabularies2/dialects/min-items/dialect.expanded.jsonld create mode 100644 amf-aml/shared/src/test/resources/vocabularies2/dialects/min-items/dialect.flattened.jsonld create mode 100644 amf-aml/shared/src/test/resources/vocabularies2/dialects/min-items/dialect.yaml diff --git a/amf-aml/shared/src/main/scala/amf/aml/client/platform/model/domain/PropertyMapping.scala b/amf-aml/shared/src/main/scala/amf/aml/client/platform/model/domain/PropertyMapping.scala index fb3fec142..7a154617f 100644 --- a/amf-aml/shared/src/main/scala/amf/aml/client/platform/model/domain/PropertyMapping.scala +++ b/amf-aml/shared/src/main/scala/amf/aml/client/platform/model/domain/PropertyMapping.scala @@ -24,96 +24,101 @@ case class PropertyMapping(override private[amf] val _internal: InternalProperty @JSExportTopLevel("PropertyMapping") def this() = this(InternalPropertyMapping()) + def name(): StrField = _internal.name() + def nodePropertyMapping(): StrField = _internal.nodePropertyMapping() + def literalRange(): StrField = _internal.literalRange() + def objectRange(): ClientList[StrField] = _internal.objectRange().asClient + def mapKeyProperty(): StrField = _internal.mapKeyProperty() + def mapValueProperty(): StrField = _internal.mapKeyProperty() + def minCount(): IntField = _internal.minCount() + def pattern(): StrField = _internal.pattern() + def minimum(): DoubleField = _internal.minimum() + def maximum(): DoubleField = _internal.maximum() + def allowMultiple(): BoolField = _internal.allowMultiple() + def enum(): ClientList[AnyField] = _internal.enum().asClient + def sorted(): BoolField = _internal.sorted() + def typeDiscriminatorName(): StrField = _internal.typeDiscriminatorName() + def externallyLinkable(): BoolField = _internal.externallyLinkable() + def mandatory(): BoolField = _internal.mandatory() + def typeDiscriminator(): ClientMap[String] = Option(_internal.typeDiscriminator()) match { + case Some(m) => + m.foldLeft(mutable.Map[String, String]()) { + case (acc, (k, v)) => + acc.put(k, v) + acc + } + .asClient + case None => mutable.Map[String, String]().asClient + } + def withName(name: String): PropertyMapping = { _internal.withName(name) this } - def name(): StrField = _internal.name() def withNodePropertyMapping(propertyId: String): PropertyMapping = { _internal.withNodePropertyMapping(propertyId) this } - def nodePropertyMapping(): StrField = _internal.nodePropertyMapping() def withLiteralRange(range: String): PropertyMapping = { _internal.withLiteralRange(range) this } - def literalRange(): StrField = _internal.literalRange() def withObjectRange(range: ClientList[String]): PropertyMapping = { _internal.withObjectRange(range.asInternal) this } - def objectRange(): ClientList[StrField] = _internal.objectRange().asClient - def mapKeyProperty(): StrField = _internal.mapKeyProperty() def withMapKeyProperty(key: String): PropertyMapping = { _internal.withMapKeyProperty(key) this } - def mapValueProperty(): StrField = _internal.mapKeyProperty() def withMapValueProperty(value: String): PropertyMapping = { _internal.withMapValueProperty(value) this } - def minCount(): IntField = _internal.minCount() def withMinCount(minCount: Int): PropertyMapping = { _internal.withMinCount(minCount) this } - def pattern(): StrField = _internal.pattern() def withPattern(pattern: String): PropertyMapping = { _internal.withPattern(pattern) this } - def minimum(): DoubleField = _internal.minimum() def withMinimum(min: Double): PropertyMapping = { _internal.withMinimum(min) this } - def maximum(): DoubleField = _internal.maximum() def withMaximum(max: Double): PropertyMapping = { _internal.withMaximum(max) this } - def allowMultiple(): BoolField = _internal.allowMultiple() def withAllowMultiple(allow: Boolean): PropertyMapping = { _internal.withAllowMultiple(allow) this } - def enum(): ClientList[AnyField] = _internal.enum().asClient def withEnum(values: ClientList[Any]): PropertyMapping = { _internal.withEnum(values.asInternal) this } - def sorted(): BoolField = _internal.sorted() def withSorted(sorted: Boolean): PropertyMapping = { _internal.withSorted(sorted) this } - def typeDiscriminator(): ClientMap[String] = Option(_internal.typeDiscriminator()) match { - case Some(m) => - m.foldLeft(mutable.Map[String, String]()) { - case (acc, (k, v)) => - acc.put(k, v) - acc - } - .asClient - case None => mutable.Map[String, String]().asClient - } - def withTypeDiscriminator(typesMapping: ClientMap[String]): PropertyMapping = { _internal.withTypeDiscriminator(typesMapping.asInternal) this } - - def typeDiscriminatorName(): StrField = _internal.typeDiscriminatorName() - def withTypeDiscriminatorName(name: String): PropertyMapping = { _internal.withTypeDiscriminatorName(name) this } - - def withExternallyLinkable(linkable: Boolean): PropertyMapping = _internal.withExternallyLinkable(linkable) - def externallyLinkable(): BoolField = _internal.externallyLinkable() + def withExternallyLinkable(linkable: Boolean): PropertyMapping = { + _internal.withExternallyLinkable(linkable) + this + } + def withMandatory(mandatory: Boolean): PropertyMapping = { + _internal.withMandatory(mandatory) + this + } def classification(): String = { _internal.classification() match { diff --git a/amf-aml/shared/src/main/scala/amf/aml/client/scala/model/domain/PropertyLikeMapping.scala b/amf-aml/shared/src/main/scala/amf/aml/client/scala/model/domain/PropertyLikeMapping.scala index 345bbf686..4362b3e8e 100644 --- a/amf-aml/shared/src/main/scala/amf/aml/client/scala/model/domain/PropertyLikeMapping.scala +++ b/amf-aml/shared/src/main/scala/amf/aml/client/scala/model/domain/PropertyLikeMapping.scala @@ -22,6 +22,7 @@ trait PropertyLikeMapping[M <: PropertyLikeMappingModel] def enum(): Seq[AnyField] = fields.field(meta.Enum) def unique(): BoolField = fields.field(meta.Unique) def externallyLinkable(): BoolField = fields.field(meta.ExternallyLinkable) + def mandatory(): BoolField = fields.field(meta.Mandatory) def nodesInRange: Seq[String] = { val range = objectRange() if (range.isEmpty) { @@ -45,10 +46,13 @@ trait PropertyLikeMapping[M <: PropertyLikeMappingModel] def withSorted(sorted: Boolean): this.type = set(meta.Sorted, sorted) def withUnique(unique: Boolean): this.type = set(meta.Unique, unique) def withExternallyLinkable(linkable: Boolean): this.type = set(meta.ExternallyLinkable, linkable) + def withMandatory(mandatory: Boolean): this.type = set(meta.Mandatory, mandatory) def classification(): PropertyClassification = PropertyLikeMappingClassifier.classification(this) def toField(): Field = PropertyLikeMappingToFieldConverter.convert(this) + private[amf] def isMultiple: Boolean = allowMultiple().option().getOrElse(false) + def meta: M } diff --git a/amf-aml/shared/src/main/scala/amf/aml/internal/metamodel/domain/PropertyLikeMappingModel.scala b/amf-aml/shared/src/main/scala/amf/aml/internal/metamodel/domain/PropertyLikeMappingModel.scala index 2e84813fc..00c25748d 100644 --- a/amf-aml/shared/src/main/scala/amf/aml/internal/metamodel/domain/PropertyLikeMappingModel.scala +++ b/amf-aml/shared/src/main/scala/amf/aml/internal/metamodel/domain/PropertyLikeMappingModel.scala @@ -1,15 +1,9 @@ package amf.aml.internal.metamodel.domain +import amf.core.client.scala.vocabulary.Namespace import amf.core.internal.metamodel.Field import amf.core.internal.metamodel.Type.{Any, Bool, Double, Int, Iri, SortedArray, Str} -import amf.core.internal.metamodel.domain.{ - DataNodeModel, - DomainElementModel, - ExternalModelVocabularies, - ModelDoc, - ModelVocabularies -} -import amf.core.client.scala.vocabulary.Namespace +import amf.core.internal.metamodel.domain.{DomainElementModel, ExternalModelVocabularies, ModelDoc, ModelVocabularies} /** * Mappings form with which graph properties can be derived (annotation mappings, property mappings) @@ -84,4 +78,14 @@ trait PropertyLikeMappingModel extends DomainElementModel with HasObjectRangeMod Namespace.Meta + "externallyLinkable", ModelDoc(ModelVocabularies.Meta, "linkable", "Marks this object property as supporting external links") ) + + val Mandatory: Field = Field( + Bool, + Namespace.Shacl + "mandatory", + ModelDoc( + ExternalModelVocabularies.Shacl, + "mandatory", + "Mandatory constraint over the property. Different from minCount because it only checks the presence of property" + ) + ) } diff --git a/amf-aml/shared/src/main/scala/amf/aml/internal/metamodel/domain/PropertyMappingModel.scala b/amf-aml/shared/src/main/scala/amf/aml/internal/metamodel/domain/PropertyMappingModel.scala index 1ba9e4b72..bd7e35ff8 100644 --- a/amf-aml/shared/src/main/scala/amf/aml/internal/metamodel/domain/PropertyMappingModel.scala +++ b/amf-aml/shared/src/main/scala/amf/aml/internal/metamodel/domain/PropertyMappingModel.scala @@ -1,17 +1,11 @@ package amf.aml.internal.metamodel.domain -import amf.core.internal.metamodel.Field -import amf.core.internal.metamodel.Type.{Any, Bool, Double, Int, Iri, SortedArray, Str} -import amf.core.internal.metamodel.domain.{ - DomainElementModel, - ExternalModelVocabularies, - ModelDoc, - ModelVocabularies, - ShapeModel -} +import amf.aml.client.scala.model.domain.PropertyMapping import amf.core.client.scala.model.domain.AmfObject import amf.core.client.scala.vocabulary.{Namespace, ValueType} -import amf.aml.client.scala.model.domain.PropertyMapping +import amf.core.internal.metamodel.Field +import amf.core.internal.metamodel.Type.{Iri, Str} +import amf.core.internal.metamodel.domain.{DomainElementModel, ModelDoc, ModelVocabularies, ShapeModel} object PropertyMappingModel extends DomainElementModel @@ -50,7 +44,7 @@ object PropertyMappingModel NodePropertyMapping :: Name :: LiteralRange :: ObjectRange :: MapKeyProperty :: MapValueProperty :: MapTermKeyProperty :: MapTermValueProperty :: MinCount :: Pattern :: Minimum :: Maximum :: AllowMultiple :: Sorted :: Enum :: TypeDiscriminator :: - Unique :: ExternallyLinkable :: TypeDiscriminatorName :: MergePolicy :: ShapeModel.Default :: DomainElementModel.fields + Unique :: ExternallyLinkable :: Mandatory :: TypeDiscriminatorName :: MergePolicy :: ShapeModel.Default :: DomainElementModel.fields override def modelInstance: AmfObject = PropertyMapping() diff --git a/amf-aml/shared/src/main/scala/amf/aml/internal/parse/dialects/property/like/MandatoryParser.scala b/amf-aml/shared/src/main/scala/amf/aml/internal/parse/dialects/property/like/MandatoryParser.scala index af75d4833..9291d39c1 100644 --- a/amf-aml/shared/src/main/scala/amf/aml/internal/parse/dialects/property/like/MandatoryParser.scala +++ b/amf-aml/shared/src/main/scala/amf/aml/internal/parse/dialects/property/like/MandatoryParser.scala @@ -6,21 +6,77 @@ import amf.core.internal.parser.domain.{Annotations, ScalarNode} import amf.aml.internal.metamodel.domain.PropertyLikeMappingModel import amf.aml.client.scala.model.domain.PropertyLikeMapping import amf.aml.internal.parse.dialects.DialectContext -import org.yaml.model.YMap +import amf.core.internal.annotations.VirtualElement +import org.yaml.model.{YMap, YMapEntry} + +import scala.language.implicitConversions case class MandatoryParser(map: YMap, propertyLikeMapping: PropertyLikeMapping[_ <: PropertyLikeMappingModel])( implicit val ctx: DialectContext) { def parse(): Unit = { - map.key( - "mandatory", - entry => { - val required = ScalarNode(entry.value).boolean().toBool - val value = if (required) 1 else 0 - propertyLikeMapping.set(propertyLikeMapping.meta.MinCount, - AmfScalar(value, Annotations(entry.value)), - Annotations(entry)) - } - ) + + // If I have minItems and mandatory ==> minCount = minItems & mandatory = mandatory + // If I have only mandatory ==> minCount = mandatory.toInt + // If I have only minItems ==> minCount = minItems & mandatory = false + + // This combined logic is to allow to validate empty arrays ([]) + + val minItems = parseMinItems + val mandatory = parseMandatory + val existsMinItems = minItems.nonEmpty + val existsMandatory = mandatory.nonEmpty + + // If minItems key exists, it will always be saved in MinCount field + if (existsMinItems && propertyLikeMapping.isMultiple) { + val minItemsValue = minItems.get.value + val minItemsEntry = minItems.get.entry + propertyLikeMapping.set(propertyLikeMapping.meta.MinCount, + AmfScalar(minItemsValue, Annotations(minItemsEntry.value)), + Annotations(minItemsEntry)) + } + + // Mandatory key will be processed based on the presence or not of minItems key + if (existsMandatory) { + val mandatoryValue = mandatory.get.value + val mandatoryEntry = mandatory.get.entry + // If mandatory and minItems keys exist, mandatory will be saved as a boolean in Mandatory field + if (existsMinItems) { + propertyLikeMapping.set(propertyLikeMapping.meta.Mandatory, + AmfScalar(mandatoryValue.toBoolean, Annotations(mandatoryEntry.value)), + Annotations(mandatoryEntry)) + } else { + // If mandatory key exists but minItems key not, mandatory will be saved in MinCount field (the original behavior) + propertyLikeMapping.set(propertyLikeMapping.meta.MinCount, + AmfScalar(mandatoryValue, Annotations(mandatoryEntry.value)), + Annotations(mandatoryEntry)) + } + } else if (existsMinItems) + propertyLikeMapping.set(propertyLikeMapping.meta.Mandatory, AmfScalar(false), Annotations(VirtualElement())) + // If minItems key exists but mandatory key not, the field Mandatory will be set as false + } + private def parseMandatory: Option[ParsedEntry] = map.key("mandatory") match { + case Some(entry) => + val required = ScalarNode(entry.value).boolean().toBool + val value = if (required) 1 else 0 + Some(ParsedEntry(value, entry)) + case None => None + } + + private def parseMinItems: Option[ParsedEntry] = map.key("minItems") match { + case Some(entry) => + val value = ScalarNode(entry.value).integer().toNumber.intValue() + Some(ParsedEntry(value, entry)) + case None => None + } + + private case class ParsedEntry(value: Int, entry: YMapEntry) + + class asBoolean(i: Int) { + def toBoolean: Boolean = i == 1 + } + + implicit def convertIntToBoolean(i: Int): asBoolean = new asBoolean(i) + } diff --git a/amf-aml/shared/src/main/scala/amf/aml/internal/render/emitters/dialects/PropertyMappingEmitter.scala b/amf-aml/shared/src/main/scala/amf/aml/internal/render/emitters/dialects/PropertyMappingEmitter.scala index 81d31dca7..caebd1048 100644 --- a/amf-aml/shared/src/main/scala/amf/aml/internal/render/emitters/dialects/PropertyMappingEmitter.scala +++ b/amf-aml/shared/src/main/scala/amf/aml/internal/render/emitters/dialects/PropertyMappingEmitter.scala @@ -1,29 +1,18 @@ package amf.aml.internal.render.emitters.dialects +import amf.aml.client.scala.model.document.Dialect +import amf.aml.client.scala.model.domain.{PropertyLikeMapping, PropertyMapping} +import amf.aml.internal.metamodel.domain.{PropertyLikeMappingModel, PropertyMappingModel} +import amf.aml.internal.render.emitters.instances.NodeMappableFinder import amf.core.client.common.position.Position import amf.core.client.common.position.Position.ZERO import amf.core.client.scala.model.domain.AmfScalar import amf.core.client.scala.vocabulary.Namespace import amf.core.internal.annotations.LexicalInformation -import amf.core.internal.render.BaseEmitters.{ - ArrayEmitter, - EntryPartEmitter, - MapEntryEmitter, - ScalarEmitter, - ValueEmitter, - pos, - traverse -} +import amf.core.internal.parser.domain.{Annotations, FieldEntry} +import amf.core.internal.render.BaseEmitters.{MapEntryEmitter, ScalarEmitter, pos, traverse} import amf.core.internal.render.SpecOrdering import amf.core.internal.render.emitters.EntryEmitter -import amf.aml.internal.render.emitters.instances.NodeMappableFinder -import amf.aml.internal.metamodel.domain.{PropertyLikeMappingModel, PropertyMappingModel} -import amf.aml.client.scala.model.document.Dialect -import amf.aml.client.scala.model.domain.{PropertyLikeMapping, PropertyMapping, WithDefaultFacet} -import amf.core.client.scala.errorhandling.IgnoringErrorHandler -import amf.core.internal.datanode.DataNodeEmitter -import amf.core.internal.metamodel.domain.ShapeModel -import amf.core.internal.parser.domain.{Annotations, FieldEntry} import org.mulesoft.common.collections.FilterType import org.yaml.model.YDocument.EntryBuilder import org.yaml.model.YType @@ -120,15 +109,6 @@ case class PropertyLikeMappingEmitter[T <: PropertyLikeMappingModel]( result ++= Seq(MapEntryEmitter("unique", value.toString, YType.Bool, pos)) } - propertyLikeMapping.fields.entry(PropertyMappingModel.MinCount) foreach { entry => - val value = entry.value.value.asInstanceOf[AmfScalar].value - val pos = fieldPos(propertyLikeMapping, entry.field) - value match { - case 0 => result ++= Seq(MapEntryEmitter("mandatory", "false", YType.Bool, pos)) - case 1 => result ++= Seq(MapEntryEmitter("mandatory", "true", YType.Bool, pos)) - } - } - propertyLikeMapping.fields.entry(PropertyMappingModel.Pattern) foreach { entry => val value = entry.value.value.asInstanceOf[AmfScalar].value.toString val pos = fieldPos(propertyLikeMapping, entry.field) @@ -172,6 +152,7 @@ case class PropertyLikeMappingEmitter[T <: PropertyLikeMappingModel]( } } + result ++= MandatoryEmitter(propertyLikeMapping).emitters() result ++= emitDiscriminator(propertyLikeMapping) result } @@ -201,6 +182,36 @@ case class EnumEmitter(entry: FieldEntry, ordering: SpecOrdering) extends EntryE override def position(): Position = pos(entry.value.annotations) } +case class MandatoryEmitter[T <: PropertyLikeMappingModel](propertyMapping: PropertyLikeMapping[T]) + extends PosExtractor { + + def emitters(): Seq[EntryEmitter] = { + + var emitters: Seq[EntryEmitter] = Seq() + + propertyMapping.mandatory().option() match { + case Some(mandatory) => + val mandatoryPos = fieldPos(propertyMapping, PropertyMappingModel.Mandatory) + propertyMapping.minCount().option() match { + case Some(minCount) => + val minCountPos = fieldPos(propertyMapping, PropertyMappingModel.MinCount) + emitters ++= Seq(MapEntryEmitter("minItems", minCount.toString, YType.Int, minCountPos), + MapEntryEmitter("mandatory", mandatory.toString, YType.Bool, mandatoryPos)) + case None => Seq(MapEntryEmitter("mandatory", mandatory.toString, YType.Bool, mandatoryPos)) + } + case None => + propertyMapping.minCount().option() match { + case Some(minCount) => + val minCountPos = fieldPos(propertyMapping, PropertyMappingModel.MinCount) + emitters ++= Seq(MapEntryEmitter("mandatory", (minCount == 1).toString, YType.Bool, minCountPos)) + case None => // Nothing to do + } + } + + emitters + } +} + case class PropertyMappingEmitter( dialect: Dialect, propertyMapping: PropertyMapping, diff --git a/amf-aml/shared/src/test/resources/vocabularies2/dialects/min-items/dialect.cycled.jsonld.yaml b/amf-aml/shared/src/test/resources/vocabularies2/dialects/min-items/dialect.cycled.jsonld.yaml new file mode 100644 index 000000000..8dd0878fb --- /dev/null +++ b/amf-aml/shared/src/test/resources/vocabularies2/dialects/min-items/dialect.cycled.jsonld.yaml @@ -0,0 +1,33 @@ +#%Dialect 1.0 +dialect: Mapping MinItems properties +version: "1.0" +documents: + root: + encodes: "#/declarations/Root" +nodeMappings: + Root: + mapping: + Items0MandatoryTrue: + range: string + allowMultiple: true + minItems: 0 + mandatory: true + Items5MandatoryTrue: + range: string + allowMultiple: true + minItems: 5 + mandatory: true + Items0MandatoryFalse: + range: string + allowMultiple: true + minItems: 0 + mandatory: false + Items5NoMandatory: + range: string + allowMultiple: true + minItems: 5 + mandatory: false + NoItemsMandatoryTrue: + range: string + allowMultiple: true + mandatory: true diff --git a/amf-aml/shared/src/test/resources/vocabularies2/dialects/min-items/dialect.cycled.yaml b/amf-aml/shared/src/test/resources/vocabularies2/dialects/min-items/dialect.cycled.yaml new file mode 100644 index 000000000..d4be0f7da --- /dev/null +++ b/amf-aml/shared/src/test/resources/vocabularies2/dialects/min-items/dialect.cycled.yaml @@ -0,0 +1,33 @@ +#%Dialect 1.0 +dialect: Mapping MinItems properties +version: "1.0" +documents: + root: + encodes: Root +nodeMappings: + Root: + mapping: + Items0MandatoryTrue: + range: string + allowMultiple: true + minItems: 0 + mandatory: true + Items5MandatoryTrue: + range: string + allowMultiple: true + minItems: 5 + mandatory: true + Items0MandatoryFalse: + range: string + allowMultiple: true + minItems: 0 + mandatory: false + Items5NoMandatory: + range: string + allowMultiple: true + minItems: 5 + mandatory: false + NoItemsMandatoryTrue: + range: string + allowMultiple: true + mandatory: true diff --git a/amf-aml/shared/src/test/resources/vocabularies2/dialects/min-items/dialect.expanded.jsonld b/amf-aml/shared/src/test/resources/vocabularies2/dialects/min-items/dialect.expanded.jsonld new file mode 100644 index 000000000..96539238e --- /dev/null +++ b/amf-aml/shared/src/test/resources/vocabularies2/dialects/min-items/dialect.expanded.jsonld @@ -0,0 +1,880 @@ +[ + { + "@id": "", + "@type": [ + "meta:Dialect", + "doc:Document", + "doc:Fragment", + "doc:Module", + "doc:Unit" + ], + "core:name": [ + { + "@value": "Mapping MinItems properties" + } + ], + "core:version": [ + { + "@value": "1.0" + } + ], + "meta:documents": [ + { + "@id": "/documents", + "@type": [ + "meta:DocumentsModel", + "doc:DomainElement" + ], + "meta:rootDocument": [ + { + "@id": "#/documents/root", + "@type": [ + "meta:DocumentMapping", + "doc:DomainElement" + ], + "core:name": [ + { + "@value": "Mapping MinItems properties 1.0" + } + ], + "meta:encodedNode": [ + { + "@id": "#/declarations/Root" + } + ], + "sourcemaps:sources": [ + { + "@id": "#/documents/root/source-map", + "@type": [ + "sourcemaps:SourceMap" + ], + "sourcemaps:lexical": [ + { + "@id": "#/documents/root/source-map/lexical/element_1", + "sourcemaps:element": [ + { + "@value": "file://amf-aml/shared/src/test/resources/vocabularies2/dialects/min-items/dialect.yaml#/documents/root" + } + ], + "sourcemaps:value": [ + { + "@value": "[(6,7)-(9,0)]" + } + ] + }, + { + "@id": "#/documents/root/source-map/lexical/element_0", + "sourcemaps:element": [ + { + "@value": "meta:encodedNode" + } + ], + "sourcemaps:value": [ + { + "@value": "[(7,4)-(9,0)]" + } + ] + } + ] + } + ] + } + ], + "sourcemaps:sources": [ + { + "@id": "/documents#/source-map", + "@type": [ + "sourcemaps:SourceMap" + ], + "sourcemaps:lexical": [ + { + "@id": "/documents#/source-map/lexical/element_1", + "sourcemaps:element": [ + { + "@value": "file://amf-aml/shared/src/test/resources/vocabularies2/dialects/min-items/dialect.yaml/documents" + } + ], + "sourcemaps:value": [ + { + "@value": "[(6,0)-(9,0)]" + } + ] + }, + { + "@id": "/documents#/source-map/lexical/element_0", + "sourcemaps:element": [ + { + "@value": "meta:rootDocument" + } + ], + "sourcemaps:value": [ + { + "@value": "[(6,2)-(9,0)]" + } + ] + } + ] + } + ] + } + ], + "doc:location": [ + { + "@value": "file://amf-aml/shared/src/test/resources/vocabularies2/dialects/min-items/dialect.yaml" + } + ], + "doc:root": [ + { + "@value": true + } + ], + "doc:processingData": [ + { + "@id": "#/BaseUnitProcessingData", + "@type": [ + "doc:BaseUnitProcessingData" + ], + "doc:transformed": [ + { + "@value": false + } + ], + "doc:sourceSpec": [ + { + "@value": "AML 1.0" + } + ] + } + ], + "sourcemaps:sources": [ + { + "@id": "#/source-map", + "@type": [ + "sourcemaps:SourceMap" + ], + "sourcemaps:lexical": [ + { + "@id": "#/source-map/lexical/element_3", + "sourcemaps:element": [ + { + "@value": "core:version" + } + ], + "sourcemaps:value": [ + { + "@value": "[(3,0)-(5,0)]" + } + ] + }, + { + "@id": "#/source-map/lexical/element_1", + "sourcemaps:element": [ + { + "@value": "core:name" + } + ], + "sourcemaps:value": [ + { + "@value": "[(2,0)-(3,0)]" + } + ] + }, + { + "@id": "#/source-map/lexical/element_0", + "sourcemaps:element": [ + { + "@value": "meta:documents" + } + ], + "sourcemaps:value": [ + { + "@value": "[(5,0)-(9,0)]" + } + ] + }, + { + "@id": "#/source-map/lexical/element_2", + "sourcemaps:element": [ + { + "@value": "file://amf-aml/shared/src/test/resources/vocabularies2/dialects/min-items/dialect.yaml" + } + ], + "sourcemaps:value": [ + { + "@value": "[(2,0)-(35,0)]" + } + ] + } + ] + } + ], + "doc:declares": [ + { + "@id": "#/declarations/Root", + "@type": [ + "meta:NodeMapping", + "shacl:Shape", + "doc:DomainElement" + ], + "core:name": [ + { + "@value": "Root" + } + ], + "shacl:property": [ + { + "@id": "#/declarations/Root/property/Items0MandatoryFalse", + "@type": [ + "meta:NodePropertyMapping", + "doc:DomainElement" + ], + "shacl:path": [ + { + "@id": "http://a.ml/vocabularies/data#Items0MandatoryFalse" + } + ], + "core:name": [ + { + "@value": "Items0MandatoryFalse" + } + ], + "shacl:datatype": [ + { + "@id": "http://www.w3.org/2001/XMLSchema#string" + } + ], + "shacl:minCount": [ + { + "@value": 0 + } + ], + "meta:allowMultiple": [ + { + "@value": true + } + ], + "shacl:mandatory": [ + { + "@value": false + } + ], + "sourcemaps:sources": [ + { + "@id": "#/declarations/Root/property/Items0MandatoryFalse/source-map", + "@type": [ + "sourcemaps:SourceMap" + ], + "sourcemaps:lexical": [ + { + "@id": "#/declarations/Root/property/Items0MandatoryFalse/source-map/lexical/element_5", + "sourcemaps:element": [ + { + "@value": "meta:allowMultiple" + } + ], + "sourcemaps:value": [ + { + "@value": "[(24,8)-(25,0)]" + } + ] + }, + { + "@id": "#/declarations/Root/property/Items0MandatoryFalse/source-map/lexical/element_3", + "sourcemaps:element": [ + { + "@value": "file://amf-aml/shared/src/test/resources/vocabularies2/dialects/min-items/dialect.yaml#/declarations/Root/property/Items0MandatoryFalse" + } + ], + "sourcemaps:value": [ + { + "@value": "[(22,27)-(27,0)]" + } + ] + }, + { + "@id": "#/declarations/Root/property/Items0MandatoryFalse/source-map/lexical/element_1", + "sourcemaps:element": [ + { + "@value": "shacl:minCount" + } + ], + "sourcemaps:value": [ + { + "@value": "[(25,8)-(26,0)]" + } + ] + }, + { + "@id": "#/declarations/Root/property/Items0MandatoryFalse/source-map/lexical/element_0", + "sourcemaps:element": [ + { + "@value": "shacl:mandatory" + } + ], + "sourcemaps:value": [ + { + "@value": "[(26,8)-(27,0)]" + } + ] + }, + { + "@id": "#/declarations/Root/property/Items0MandatoryFalse/source-map/lexical/element_2", + "sourcemaps:element": [ + { + "@value": "core:name" + } + ], + "sourcemaps:value": [ + { + "@value": "[(22,6)-(22,26)]" + } + ] + }, + { + "@id": "#/declarations/Root/property/Items0MandatoryFalse/source-map/lexical/element_4", + "sourcemaps:element": [ + { + "@value": "shacl:datatype" + } + ], + "sourcemaps:value": [ + { + "@value": "[(23,8)-(24,0)]" + } + ] + } + ] + } + ] + }, + { + "@id": "#/declarations/Root/property/Items5MandatoryTrue", + "@type": [ + "meta:NodePropertyMapping", + "doc:DomainElement" + ], + "shacl:path": [ + { + "@id": "http://a.ml/vocabularies/data#Items5MandatoryTrue" + } + ], + "core:name": [ + { + "@value": "Items5MandatoryTrue" + } + ], + "shacl:datatype": [ + { + "@id": "http://www.w3.org/2001/XMLSchema#string" + } + ], + "shacl:minCount": [ + { + "@value": 5 + } + ], + "meta:allowMultiple": [ + { + "@value": true + } + ], + "shacl:mandatory": [ + { + "@value": true + } + ], + "sourcemaps:sources": [ + { + "@id": "#/declarations/Root/property/Items5MandatoryTrue/source-map", + "@type": [ + "sourcemaps:SourceMap" + ], + "sourcemaps:lexical": [ + { + "@id": "#/declarations/Root/property/Items5MandatoryTrue/source-map/lexical/element_5", + "sourcemaps:element": [ + { + "@value": "meta:allowMultiple" + } + ], + "sourcemaps:value": [ + { + "@value": "[(19,8)-(20,0)]" + } + ] + }, + { + "@id": "#/declarations/Root/property/Items5MandatoryTrue/source-map/lexical/element_3", + "sourcemaps:element": [ + { + "@value": "file://amf-aml/shared/src/test/resources/vocabularies2/dialects/min-items/dialect.yaml#/declarations/Root/property/Items5MandatoryTrue" + } + ], + "sourcemaps:value": [ + { + "@value": "[(17,26)-(22,0)]" + } + ] + }, + { + "@id": "#/declarations/Root/property/Items5MandatoryTrue/source-map/lexical/element_1", + "sourcemaps:element": [ + { + "@value": "shacl:minCount" + } + ], + "sourcemaps:value": [ + { + "@value": "[(20,8)-(21,0)]" + } + ] + }, + { + "@id": "#/declarations/Root/property/Items5MandatoryTrue/source-map/lexical/element_0", + "sourcemaps:element": [ + { + "@value": "shacl:mandatory" + } + ], + "sourcemaps:value": [ + { + "@value": "[(21,8)-(22,0)]" + } + ] + }, + { + "@id": "#/declarations/Root/property/Items5MandatoryTrue/source-map/lexical/element_2", + "sourcemaps:element": [ + { + "@value": "core:name" + } + ], + "sourcemaps:value": [ + { + "@value": "[(17,6)-(17,25)]" + } + ] + }, + { + "@id": "#/declarations/Root/property/Items5MandatoryTrue/source-map/lexical/element_4", + "sourcemaps:element": [ + { + "@value": "shacl:datatype" + } + ], + "sourcemaps:value": [ + { + "@value": "[(18,8)-(19,0)]" + } + ] + } + ] + } + ] + }, + { + "@id": "#/declarations/Root/property/Items0MandatoryTrue", + "@type": [ + "meta:NodePropertyMapping", + "doc:DomainElement" + ], + "shacl:path": [ + { + "@id": "http://a.ml/vocabularies/data#Items0MandatoryTrue" + } + ], + "core:name": [ + { + "@value": "Items0MandatoryTrue" + } + ], + "shacl:datatype": [ + { + "@id": "http://www.w3.org/2001/XMLSchema#string" + } + ], + "shacl:minCount": [ + { + "@value": 0 + } + ], + "meta:allowMultiple": [ + { + "@value": true + } + ], + "shacl:mandatory": [ + { + "@value": true + } + ], + "sourcemaps:sources": [ + { + "@id": "#/declarations/Root/property/Items0MandatoryTrue/source-map", + "@type": [ + "sourcemaps:SourceMap" + ], + "sourcemaps:lexical": [ + { + "@id": "#/declarations/Root/property/Items0MandatoryTrue/source-map/lexical/element_5", + "sourcemaps:element": [ + { + "@value": "meta:allowMultiple" + } + ], + "sourcemaps:value": [ + { + "@value": "[(14,8)-(15,0)]" + } + ] + }, + { + "@id": "#/declarations/Root/property/Items0MandatoryTrue/source-map/lexical/element_3", + "sourcemaps:element": [ + { + "@value": "file://amf-aml/shared/src/test/resources/vocabularies2/dialects/min-items/dialect.yaml#/declarations/Root/property/Items0MandatoryTrue" + } + ], + "sourcemaps:value": [ + { + "@value": "[(12,26)-(17,0)]" + } + ] + }, + { + "@id": "#/declarations/Root/property/Items0MandatoryTrue/source-map/lexical/element_1", + "sourcemaps:element": [ + { + "@value": "shacl:minCount" + } + ], + "sourcemaps:value": [ + { + "@value": "[(15,8)-(16,0)]" + } + ] + }, + { + "@id": "#/declarations/Root/property/Items0MandatoryTrue/source-map/lexical/element_0", + "sourcemaps:element": [ + { + "@value": "shacl:mandatory" + } + ], + "sourcemaps:value": [ + { + "@value": "[(16,8)-(17,0)]" + } + ] + }, + { + "@id": "#/declarations/Root/property/Items0MandatoryTrue/source-map/lexical/element_2", + "sourcemaps:element": [ + { + "@value": "core:name" + } + ], + "sourcemaps:value": [ + { + "@value": "[(12,6)-(12,25)]" + } + ] + }, + { + "@id": "#/declarations/Root/property/Items0MandatoryTrue/source-map/lexical/element_4", + "sourcemaps:element": [ + { + "@value": "shacl:datatype" + } + ], + "sourcemaps:value": [ + { + "@value": "[(13,8)-(14,0)]" + } + ] + } + ] + } + ] + }, + { + "@id": "#/declarations/Root/property/NoItemsMandatoryTrue", + "@type": [ + "meta:NodePropertyMapping", + "doc:DomainElement" + ], + "shacl:path": [ + { + "@id": "http://a.ml/vocabularies/data#NoItemsMandatoryTrue" + } + ], + "core:name": [ + { + "@value": "NoItemsMandatoryTrue" + } + ], + "shacl:datatype": [ + { + "@id": "http://www.w3.org/2001/XMLSchema#string" + } + ], + "shacl:minCount": [ + { + "@value": 1 + } + ], + "meta:allowMultiple": [ + { + "@value": true + } + ], + "sourcemaps:sources": [ + { + "@id": "#/declarations/Root/property/NoItemsMandatoryTrue/source-map", + "@type": [ + "sourcemaps:SourceMap" + ], + "sourcemaps:lexical": [ + { + "@id": "#/declarations/Root/property/NoItemsMandatoryTrue/source-map/lexical/element_4", + "sourcemaps:element": [ + { + "@value": "shacl:minCount" + } + ], + "sourcemaps:value": [ + { + "@value": "[(34,8)-(35,0)]" + } + ] + }, + { + "@id": "#/declarations/Root/property/NoItemsMandatoryTrue/source-map/lexical/element_2", + "sourcemaps:element": [ + { + "@value": "file://amf-aml/shared/src/test/resources/vocabularies2/dialects/min-items/dialect.yaml#/declarations/Root/property/NoItemsMandatoryTrue" + } + ], + "sourcemaps:value": [ + { + "@value": "[(31,27)-(35,0)]" + } + ] + }, + { + "@id": "#/declarations/Root/property/NoItemsMandatoryTrue/source-map/lexical/element_0", + "sourcemaps:element": [ + { + "@value": "meta:allowMultiple" + } + ], + "sourcemaps:value": [ + { + "@value": "[(33,8)-(34,0)]" + } + ] + }, + { + "@id": "#/declarations/Root/property/NoItemsMandatoryTrue/source-map/lexical/element_1", + "sourcemaps:element": [ + { + "@value": "shacl:datatype" + } + ], + "sourcemaps:value": [ + { + "@value": "[(32,8)-(33,0)]" + } + ] + }, + { + "@id": "#/declarations/Root/property/NoItemsMandatoryTrue/source-map/lexical/element_3", + "sourcemaps:element": [ + { + "@value": "core:name" + } + ], + "sourcemaps:value": [ + { + "@value": "[(31,6)-(31,26)]" + } + ] + } + ] + } + ] + }, + { + "@id": "#/declarations/Root/property/Items5NoMandatory", + "@type": [ + "meta:NodePropertyMapping", + "doc:DomainElement" + ], + "shacl:path": [ + { + "@id": "http://a.ml/vocabularies/data#Items5NoMandatory" + } + ], + "core:name": [ + { + "@value": "Items5NoMandatory" + } + ], + "shacl:datatype": [ + { + "@id": "http://www.w3.org/2001/XMLSchema#string" + } + ], + "shacl:minCount": [ + { + "@value": 5 + } + ], + "meta:allowMultiple": [ + { + "@value": true + } + ], + "shacl:mandatory": [ + { + "@value": false + } + ], + "sourcemaps:sources": [ + { + "@id": "#/declarations/Root/property/Items5NoMandatory/source-map", + "@type": [ + "sourcemaps:SourceMap" + ], + "sourcemaps:lexical": [ + { + "@id": "#/declarations/Root/property/Items5NoMandatory/source-map/lexical/element_4", + "sourcemaps:element": [ + { + "@value": "shacl:minCount" + } + ], + "sourcemaps:value": [ + { + "@value": "[(30,8)-(31,0)]" + } + ] + }, + { + "@id": "#/declarations/Root/property/Items5NoMandatory/source-map/lexical/element_2", + "sourcemaps:element": [ + { + "@value": "file://amf-aml/shared/src/test/resources/vocabularies2/dialects/min-items/dialect.yaml#/declarations/Root/property/Items5NoMandatory" + } + ], + "sourcemaps:value": [ + { + "@value": "[(27,24)-(31,0)]" + } + ] + }, + { + "@id": "#/declarations/Root/property/Items5NoMandatory/source-map/lexical/element_0", + "sourcemaps:element": [ + { + "@value": "meta:allowMultiple" + } + ], + "sourcemaps:value": [ + { + "@value": "[(29,8)-(30,0)]" + } + ] + }, + { + "@id": "#/declarations/Root/property/Items5NoMandatory/source-map/lexical/element_1", + "sourcemaps:element": [ + { + "@value": "shacl:datatype" + } + ], + "sourcemaps:value": [ + { + "@value": "[(28,8)-(29,0)]" + } + ] + }, + { + "@id": "#/declarations/Root/property/Items5NoMandatory/source-map/lexical/element_3", + "sourcemaps:element": [ + { + "@value": "core:name" + } + ], + "sourcemaps:value": [ + { + "@value": "[(27,6)-(27,23)]" + } + ] + } + ] + } + ] + } + ], + "sourcemaps:sources": [ + { + "@id": "#/declarations/Root/source-map", + "@type": [ + "sourcemaps:SourceMap" + ], + "sourcemaps:lexical": [ + { + "@id": "#/declarations/Root/source-map/lexical/element_2", + "sourcemaps:element": [ + { + "@value": "core:name" + } + ], + "sourcemaps:value": [ + { + "@value": "[(10,2)-(10,6)]" + } + ] + }, + { + "@id": "#/declarations/Root/source-map/lexical/element_0", + "sourcemaps:element": [ + { + "@value": "shacl:property" + } + ], + "sourcemaps:value": [ + { + "@value": "[(11,4)-(35,0)]" + } + ] + }, + { + "@id": "#/declarations/Root/source-map/lexical/element_1", + "sourcemaps:element": [ + { + "@value": "file://amf-aml/shared/src/test/resources/vocabularies2/dialects/min-items/dialect.yaml#/declarations/Root" + } + ], + "sourcemaps:value": [ + { + "@value": "[(10,2)-(35,0)]" + } + ] + } + ] + } + ] + } + ], + "@context": { + "@base": "file://amf-aml/shared/src/test/resources/vocabularies2/dialects/min-items/dialect.yaml", + "shacl": "http://www.w3.org/ns/shacl#", + "doc": "http://a.ml/vocabularies/document#", + "core": "http://a.ml/vocabularies/core#", + "sourcemaps": "http://a.ml/vocabularies/document-source-maps#", + "meta": "http://a.ml/vocabularies/meta#" + } + } +] diff --git a/amf-aml/shared/src/test/resources/vocabularies2/dialects/min-items/dialect.flattened.jsonld b/amf-aml/shared/src/test/resources/vocabularies2/dialects/min-items/dialect.flattened.jsonld new file mode 100644 index 000000000..7b086724a --- /dev/null +++ b/amf-aml/shared/src/test/resources/vocabularies2/dialects/min-items/dialect.flattened.jsonld @@ -0,0 +1,627 @@ +{ + "@graph": [ + { + "@id": "#/BaseUnitProcessingData", + "@type": [ + "doc:BaseUnitProcessingData" + ], + "doc:transformed": false, + "doc:sourceSpec": "AML 1.0" + }, + { + "@id": "/documents", + "@type": [ + "meta:DocumentsModel", + "doc:DomainElement" + ], + "meta:rootDocument": { + "@id": "#/documents/root" + }, + "sourcemaps:sources": [ + { + "@id": "/documents#/source-map" + } + ] + }, + { + "@id": "#/documents/root", + "@type": [ + "meta:DocumentMapping", + "doc:DomainElement" + ], + "core:name": "Mapping MinItems properties 1.0", + "meta:encodedNode": [ + { + "@id": "#/declarations/Root" + } + ], + "sourcemaps:sources": [ + { + "@id": "#/documents/root/source-map" + } + ] + }, + { + "@id": "/documents#/source-map", + "@type": [ + "sourcemaps:SourceMap" + ], + "sourcemaps:lexical": [ + { + "@id": "/documents#/source-map/lexical/element_1" + }, + { + "@id": "/documents#/source-map/lexical/element_0" + } + ] + }, + { + "@id": "#/documents/root/source-map", + "@type": [ + "sourcemaps:SourceMap" + ], + "sourcemaps:lexical": [ + { + "@id": "#/documents/root/source-map/lexical/element_1" + }, + { + "@id": "#/documents/root/source-map/lexical/element_0" + } + ] + }, + { + "@id": "/documents#/source-map/lexical/element_1", + "sourcemaps:element": "file://amf-aml/shared/src/test/resources/vocabularies2/dialects/min-items/dialect.yaml/documents", + "sourcemaps:value": "[(6,0)-(9,0)]" + }, + { + "@id": "/documents#/source-map/lexical/element_0", + "sourcemaps:element": "meta:rootDocument", + "sourcemaps:value": "[(6,2)-(9,0)]" + }, + { + "@id": "#/documents/root/source-map/lexical/element_1", + "sourcemaps:element": "file://amf-aml/shared/src/test/resources/vocabularies2/dialects/min-items/dialect.yaml#/documents/root", + "sourcemaps:value": "[(6,7)-(9,0)]" + }, + { + "@id": "#/documents/root/source-map/lexical/element_0", + "sourcemaps:element": "meta:encodedNode", + "sourcemaps:value": "[(7,4)-(9,0)]" + }, + { + "@id": "", + "doc:declares": [ + { + "@id": "#/declarations/Root" + } + ], + "@type": [ + "meta:Dialect", + "doc:Document", + "doc:Fragment", + "doc:Module", + "doc:Unit" + ], + "core:name": "Mapping MinItems properties", + "core:version": "1.0", + "meta:documents": { + "@id": "/documents" + }, + "doc:location": "file://amf-aml/shared/src/test/resources/vocabularies2/dialects/min-items/dialect.yaml", + "doc:root": true, + "doc:processingData": { + "@id": "#/BaseUnitProcessingData" + }, + "sourcemaps:sources": [ + { + "@id": "#/source-map" + } + ] + }, + { + "@id": "#/declarations/Root", + "@type": [ + "meta:NodeMapping", + "shacl:Shape", + "doc:DomainElement" + ], + "core:name": "Root", + "shacl:property": [ + { + "@id": "#/declarations/Root/property/Items0MandatoryFalse" + }, + { + "@id": "#/declarations/Root/property/Items5MandatoryTrue" + }, + { + "@id": "#/declarations/Root/property/Items0MandatoryTrue" + }, + { + "@id": "#/declarations/Root/property/NoItemsMandatoryTrue" + }, + { + "@id": "#/declarations/Root/property/Items5NoMandatory" + } + ], + "sourcemaps:sources": [ + { + "@id": "#/declarations/Root/source-map" + } + ] + }, + { + "@id": "#/source-map", + "@type": [ + "sourcemaps:SourceMap" + ], + "sourcemaps:lexical": [ + { + "@id": "#/source-map/lexical/element_3" + }, + { + "@id": "#/source-map/lexical/element_1" + }, + { + "@id": "#/source-map/lexical/element_0" + }, + { + "@id": "#/source-map/lexical/element_2" + } + ] + }, + { + "@id": "#/declarations/Root/property/Items0MandatoryFalse", + "@type": [ + "meta:NodePropertyMapping", + "doc:DomainElement" + ], + "shacl:path": [ + { + "@id": "http://a.ml/vocabularies/data#Items0MandatoryFalse" + } + ], + "core:name": "Items0MandatoryFalse", + "shacl:datatype": [ + { + "@id": "http://www.w3.org/2001/XMLSchema#string" + } + ], + "shacl:minCount": 0, + "meta:allowMultiple": true, + "shacl:mandatory": false, + "sourcemaps:sources": [ + { + "@id": "#/declarations/Root/property/Items0MandatoryFalse/source-map" + } + ] + }, + { + "@id": "#/declarations/Root/property/Items5MandatoryTrue", + "@type": [ + "meta:NodePropertyMapping", + "doc:DomainElement" + ], + "shacl:path": [ + { + "@id": "http://a.ml/vocabularies/data#Items5MandatoryTrue" + } + ], + "core:name": "Items5MandatoryTrue", + "shacl:datatype": [ + { + "@id": "http://www.w3.org/2001/XMLSchema#string" + } + ], + "shacl:minCount": 5, + "meta:allowMultiple": true, + "shacl:mandatory": true, + "sourcemaps:sources": [ + { + "@id": "#/declarations/Root/property/Items5MandatoryTrue/source-map" + } + ] + }, + { + "@id": "#/declarations/Root/property/Items0MandatoryTrue", + "@type": [ + "meta:NodePropertyMapping", + "doc:DomainElement" + ], + "shacl:path": [ + { + "@id": "http://a.ml/vocabularies/data#Items0MandatoryTrue" + } + ], + "core:name": "Items0MandatoryTrue", + "shacl:datatype": [ + { + "@id": "http://www.w3.org/2001/XMLSchema#string" + } + ], + "shacl:minCount": 0, + "meta:allowMultiple": true, + "shacl:mandatory": true, + "sourcemaps:sources": [ + { + "@id": "#/declarations/Root/property/Items0MandatoryTrue/source-map" + } + ] + }, + { + "@id": "#/declarations/Root/property/NoItemsMandatoryTrue", + "@type": [ + "meta:NodePropertyMapping", + "doc:DomainElement" + ], + "shacl:path": [ + { + "@id": "http://a.ml/vocabularies/data#NoItemsMandatoryTrue" + } + ], + "core:name": "NoItemsMandatoryTrue", + "shacl:datatype": [ + { + "@id": "http://www.w3.org/2001/XMLSchema#string" + } + ], + "shacl:minCount": 1, + "meta:allowMultiple": true, + "sourcemaps:sources": [ + { + "@id": "#/declarations/Root/property/NoItemsMandatoryTrue/source-map" + } + ] + }, + { + "@id": "#/declarations/Root/property/Items5NoMandatory", + "@type": [ + "meta:NodePropertyMapping", + "doc:DomainElement" + ], + "shacl:path": [ + { + "@id": "http://a.ml/vocabularies/data#Items5NoMandatory" + } + ], + "core:name": "Items5NoMandatory", + "shacl:datatype": [ + { + "@id": "http://www.w3.org/2001/XMLSchema#string" + } + ], + "shacl:minCount": 5, + "meta:allowMultiple": true, + "shacl:mandatory": false, + "sourcemaps:sources": [ + { + "@id": "#/declarations/Root/property/Items5NoMandatory/source-map" + } + ] + }, + { + "@id": "#/declarations/Root/source-map", + "@type": [ + "sourcemaps:SourceMap" + ], + "sourcemaps:lexical": [ + { + "@id": "#/declarations/Root/source-map/lexical/element_2" + }, + { + "@id": "#/declarations/Root/source-map/lexical/element_0" + }, + { + "@id": "#/declarations/Root/source-map/lexical/element_1" + } + ] + }, + { + "@id": "#/source-map/lexical/element_3", + "sourcemaps:element": "core:version", + "sourcemaps:value": "[(3,0)-(5,0)]" + }, + { + "@id": "#/source-map/lexical/element_1", + "sourcemaps:element": "core:name", + "sourcemaps:value": "[(2,0)-(3,0)]" + }, + { + "@id": "#/source-map/lexical/element_0", + "sourcemaps:element": "meta:documents", + "sourcemaps:value": "[(5,0)-(9,0)]" + }, + { + "@id": "#/source-map/lexical/element_2", + "sourcemaps:element": "file://amf-aml/shared/src/test/resources/vocabularies2/dialects/min-items/dialect.yaml", + "sourcemaps:value": "[(2,0)-(35,0)]" + }, + { + "@id": "#/declarations/Root/property/Items0MandatoryFalse/source-map", + "@type": [ + "sourcemaps:SourceMap" + ], + "sourcemaps:lexical": [ + { + "@id": "#/declarations/Root/property/Items0MandatoryFalse/source-map/lexical/element_5" + }, + { + "@id": "#/declarations/Root/property/Items0MandatoryFalse/source-map/lexical/element_3" + }, + { + "@id": "#/declarations/Root/property/Items0MandatoryFalse/source-map/lexical/element_1" + }, + { + "@id": "#/declarations/Root/property/Items0MandatoryFalse/source-map/lexical/element_0" + }, + { + "@id": "#/declarations/Root/property/Items0MandatoryFalse/source-map/lexical/element_2" + }, + { + "@id": "#/declarations/Root/property/Items0MandatoryFalse/source-map/lexical/element_4" + } + ] + }, + { + "@id": "#/declarations/Root/property/Items5MandatoryTrue/source-map", + "@type": [ + "sourcemaps:SourceMap" + ], + "sourcemaps:lexical": [ + { + "@id": "#/declarations/Root/property/Items5MandatoryTrue/source-map/lexical/element_5" + }, + { + "@id": "#/declarations/Root/property/Items5MandatoryTrue/source-map/lexical/element_3" + }, + { + "@id": "#/declarations/Root/property/Items5MandatoryTrue/source-map/lexical/element_1" + }, + { + "@id": "#/declarations/Root/property/Items5MandatoryTrue/source-map/lexical/element_0" + }, + { + "@id": "#/declarations/Root/property/Items5MandatoryTrue/source-map/lexical/element_2" + }, + { + "@id": "#/declarations/Root/property/Items5MandatoryTrue/source-map/lexical/element_4" + } + ] + }, + { + "@id": "#/declarations/Root/property/Items0MandatoryTrue/source-map", + "@type": [ + "sourcemaps:SourceMap" + ], + "sourcemaps:lexical": [ + { + "@id": "#/declarations/Root/property/Items0MandatoryTrue/source-map/lexical/element_5" + }, + { + "@id": "#/declarations/Root/property/Items0MandatoryTrue/source-map/lexical/element_3" + }, + { + "@id": "#/declarations/Root/property/Items0MandatoryTrue/source-map/lexical/element_1" + }, + { + "@id": "#/declarations/Root/property/Items0MandatoryTrue/source-map/lexical/element_0" + }, + { + "@id": "#/declarations/Root/property/Items0MandatoryTrue/source-map/lexical/element_2" + }, + { + "@id": "#/declarations/Root/property/Items0MandatoryTrue/source-map/lexical/element_4" + } + ] + }, + { + "@id": "#/declarations/Root/property/NoItemsMandatoryTrue/source-map", + "@type": [ + "sourcemaps:SourceMap" + ], + "sourcemaps:lexical": [ + { + "@id": "#/declarations/Root/property/NoItemsMandatoryTrue/source-map/lexical/element_4" + }, + { + "@id": "#/declarations/Root/property/NoItemsMandatoryTrue/source-map/lexical/element_2" + }, + { + "@id": "#/declarations/Root/property/NoItemsMandatoryTrue/source-map/lexical/element_0" + }, + { + "@id": "#/declarations/Root/property/NoItemsMandatoryTrue/source-map/lexical/element_1" + }, + { + "@id": "#/declarations/Root/property/NoItemsMandatoryTrue/source-map/lexical/element_3" + } + ] + }, + { + "@id": "#/declarations/Root/property/Items5NoMandatory/source-map", + "@type": [ + "sourcemaps:SourceMap" + ], + "sourcemaps:lexical": [ + { + "@id": "#/declarations/Root/property/Items5NoMandatory/source-map/lexical/element_4" + }, + { + "@id": "#/declarations/Root/property/Items5NoMandatory/source-map/lexical/element_2" + }, + { + "@id": "#/declarations/Root/property/Items5NoMandatory/source-map/lexical/element_0" + }, + { + "@id": "#/declarations/Root/property/Items5NoMandatory/source-map/lexical/element_1" + }, + { + "@id": "#/declarations/Root/property/Items5NoMandatory/source-map/lexical/element_3" + } + ] + }, + { + "@id": "#/declarations/Root/source-map/lexical/element_2", + "sourcemaps:element": "core:name", + "sourcemaps:value": "[(10,2)-(10,6)]" + }, + { + "@id": "#/declarations/Root/source-map/lexical/element_0", + "sourcemaps:element": "shacl:property", + "sourcemaps:value": "[(11,4)-(35,0)]" + }, + { + "@id": "#/declarations/Root/source-map/lexical/element_1", + "sourcemaps:element": "file://amf-aml/shared/src/test/resources/vocabularies2/dialects/min-items/dialect.yaml#/declarations/Root", + "sourcemaps:value": "[(10,2)-(35,0)]" + }, + { + "@id": "#/declarations/Root/property/Items0MandatoryFalse/source-map/lexical/element_5", + "sourcemaps:element": "meta:allowMultiple", + "sourcemaps:value": "[(24,8)-(25,0)]" + }, + { + "@id": "#/declarations/Root/property/Items0MandatoryFalse/source-map/lexical/element_3", + "sourcemaps:element": "file://amf-aml/shared/src/test/resources/vocabularies2/dialects/min-items/dialect.yaml#/declarations/Root/property/Items0MandatoryFalse", + "sourcemaps:value": "[(22,27)-(27,0)]" + }, + { + "@id": "#/declarations/Root/property/Items0MandatoryFalse/source-map/lexical/element_1", + "sourcemaps:element": "shacl:minCount", + "sourcemaps:value": "[(25,8)-(26,0)]" + }, + { + "@id": "#/declarations/Root/property/Items0MandatoryFalse/source-map/lexical/element_0", + "sourcemaps:element": "shacl:mandatory", + "sourcemaps:value": "[(26,8)-(27,0)]" + }, + { + "@id": "#/declarations/Root/property/Items0MandatoryFalse/source-map/lexical/element_2", + "sourcemaps:element": "core:name", + "sourcemaps:value": "[(22,6)-(22,26)]" + }, + { + "@id": "#/declarations/Root/property/Items0MandatoryFalse/source-map/lexical/element_4", + "sourcemaps:element": "shacl:datatype", + "sourcemaps:value": "[(23,8)-(24,0)]" + }, + { + "@id": "#/declarations/Root/property/Items5MandatoryTrue/source-map/lexical/element_5", + "sourcemaps:element": "meta:allowMultiple", + "sourcemaps:value": "[(19,8)-(20,0)]" + }, + { + "@id": "#/declarations/Root/property/Items5MandatoryTrue/source-map/lexical/element_3", + "sourcemaps:element": "file://amf-aml/shared/src/test/resources/vocabularies2/dialects/min-items/dialect.yaml#/declarations/Root/property/Items5MandatoryTrue", + "sourcemaps:value": "[(17,26)-(22,0)]" + }, + { + "@id": "#/declarations/Root/property/Items5MandatoryTrue/source-map/lexical/element_1", + "sourcemaps:element": "shacl:minCount", + "sourcemaps:value": "[(20,8)-(21,0)]" + }, + { + "@id": "#/declarations/Root/property/Items5MandatoryTrue/source-map/lexical/element_0", + "sourcemaps:element": "shacl:mandatory", + "sourcemaps:value": "[(21,8)-(22,0)]" + }, + { + "@id": "#/declarations/Root/property/Items5MandatoryTrue/source-map/lexical/element_2", + "sourcemaps:element": "core:name", + "sourcemaps:value": "[(17,6)-(17,25)]" + }, + { + "@id": "#/declarations/Root/property/Items5MandatoryTrue/source-map/lexical/element_4", + "sourcemaps:element": "shacl:datatype", + "sourcemaps:value": "[(18,8)-(19,0)]" + }, + { + "@id": "#/declarations/Root/property/Items0MandatoryTrue/source-map/lexical/element_5", + "sourcemaps:element": "meta:allowMultiple", + "sourcemaps:value": "[(14,8)-(15,0)]" + }, + { + "@id": "#/declarations/Root/property/Items0MandatoryTrue/source-map/lexical/element_3", + "sourcemaps:element": "file://amf-aml/shared/src/test/resources/vocabularies2/dialects/min-items/dialect.yaml#/declarations/Root/property/Items0MandatoryTrue", + "sourcemaps:value": "[(12,26)-(17,0)]" + }, + { + "@id": "#/declarations/Root/property/Items0MandatoryTrue/source-map/lexical/element_1", + "sourcemaps:element": "shacl:minCount", + "sourcemaps:value": "[(15,8)-(16,0)]" + }, + { + "@id": "#/declarations/Root/property/Items0MandatoryTrue/source-map/lexical/element_0", + "sourcemaps:element": "shacl:mandatory", + "sourcemaps:value": "[(16,8)-(17,0)]" + }, + { + "@id": "#/declarations/Root/property/Items0MandatoryTrue/source-map/lexical/element_2", + "sourcemaps:element": "core:name", + "sourcemaps:value": "[(12,6)-(12,25)]" + }, + { + "@id": "#/declarations/Root/property/Items0MandatoryTrue/source-map/lexical/element_4", + "sourcemaps:element": "shacl:datatype", + "sourcemaps:value": "[(13,8)-(14,0)]" + }, + { + "@id": "#/declarations/Root/property/NoItemsMandatoryTrue/source-map/lexical/element_4", + "sourcemaps:element": "shacl:minCount", + "sourcemaps:value": "[(34,8)-(35,0)]" + }, + { + "@id": "#/declarations/Root/property/NoItemsMandatoryTrue/source-map/lexical/element_2", + "sourcemaps:element": "file://amf-aml/shared/src/test/resources/vocabularies2/dialects/min-items/dialect.yaml#/declarations/Root/property/NoItemsMandatoryTrue", + "sourcemaps:value": "[(31,27)-(35,0)]" + }, + { + "@id": "#/declarations/Root/property/NoItemsMandatoryTrue/source-map/lexical/element_0", + "sourcemaps:element": "meta:allowMultiple", + "sourcemaps:value": "[(33,8)-(34,0)]" + }, + { + "@id": "#/declarations/Root/property/NoItemsMandatoryTrue/source-map/lexical/element_1", + "sourcemaps:element": "shacl:datatype", + "sourcemaps:value": "[(32,8)-(33,0)]" + }, + { + "@id": "#/declarations/Root/property/NoItemsMandatoryTrue/source-map/lexical/element_3", + "sourcemaps:element": "core:name", + "sourcemaps:value": "[(31,6)-(31,26)]" + }, + { + "@id": "#/declarations/Root/property/Items5NoMandatory/source-map/lexical/element_4", + "sourcemaps:element": "shacl:minCount", + "sourcemaps:value": "[(30,8)-(31,0)]" + }, + { + "@id": "#/declarations/Root/property/Items5NoMandatory/source-map/lexical/element_2", + "sourcemaps:element": "file://amf-aml/shared/src/test/resources/vocabularies2/dialects/min-items/dialect.yaml#/declarations/Root/property/Items5NoMandatory", + "sourcemaps:value": "[(27,24)-(31,0)]" + }, + { + "@id": "#/declarations/Root/property/Items5NoMandatory/source-map/lexical/element_0", + "sourcemaps:element": "meta:allowMultiple", + "sourcemaps:value": "[(29,8)-(30,0)]" + }, + { + "@id": "#/declarations/Root/property/Items5NoMandatory/source-map/lexical/element_1", + "sourcemaps:element": "shacl:datatype", + "sourcemaps:value": "[(28,8)-(29,0)]" + }, + { + "@id": "#/declarations/Root/property/Items5NoMandatory/source-map/lexical/element_3", + "sourcemaps:element": "core:name", + "sourcemaps:value": "[(27,6)-(27,23)]" + } + ], + "@context": { + "@base": "file://amf-aml/shared/src/test/resources/vocabularies2/dialects/min-items/dialect.yaml", + "shacl": "http://www.w3.org/ns/shacl#", + "doc": "http://a.ml/vocabularies/document#", + "core": "http://a.ml/vocabularies/core#", + "sourcemaps": "http://a.ml/vocabularies/document-source-maps#", + "meta": "http://a.ml/vocabularies/meta#" + } +} diff --git a/amf-aml/shared/src/test/resources/vocabularies2/dialects/min-items/dialect.yaml b/amf-aml/shared/src/test/resources/vocabularies2/dialects/min-items/dialect.yaml new file mode 100644 index 000000000..e87bb9cd8 --- /dev/null +++ b/amf-aml/shared/src/test/resources/vocabularies2/dialects/min-items/dialect.yaml @@ -0,0 +1,34 @@ +#%Dialect 1.0 +dialect: Mapping MinItems properties +version: 1.0 + +documents: + root: + encodes: Root + +nodeMappings: + Root: + mapping: + Items0MandatoryTrue: + range: string + allowMultiple: true + minItems: 0 + mandatory: true + Items5MandatoryTrue: + range: string + allowMultiple: true + minItems: 5 + mandatory: true + Items0MandatoryFalse: + range: string + allowMultiple: true + minItems: 0 + mandatory: false + Items5NoMandatory: + range: string + allowMultiple: true + minItems: 5 + NoItemsMandatoryTrue: + range: string + allowMultiple: true + mandatory: true diff --git a/amf-aml/shared/src/test/scala/amf/testing/parsing/DialectsParsingTest.scala b/amf-aml/shared/src/test/scala/amf/testing/parsing/DialectsParsingTest.scala index 1cc9dd945..58349b6c0 100644 --- a/amf-aml/shared/src/test/scala/amf/testing/parsing/DialectsParsingTest.scala +++ b/amf-aml/shared/src/test/scala/amf/testing/parsing/DialectsParsingTest.scala @@ -499,4 +499,6 @@ trait DialectsParsingTest extends DialectTests { // TODO cycle from JSON-LD to YAML is affected by W-10890167 multiCycleTest("Extended mapping 3", s"$basePath/extended-mapping-3") + multiCycleTest("MinItems facet", s"$basePath/min-items") + } From ed5a8ae37679e97dc43213d908912a10b013c3bb Mon Sep 17 00:00:00 2001 From: Loose Date: Wed, 13 Apr 2022 00:48:20 -0300 Subject: [PATCH 4/7] W-10974996 - Added new constraints to validate minItems + mandatory --- .../parse/dialects/DialectSyntax.scala | 3 +- .../validate/AMFDialectValidations.scala | 6 +- .../minItems-0-mandatory-true/dialect.yaml | 15 +++++ .../instance-invalid.yaml | 1 + .../instance-valid.yaml | 2 + .../minItems-0-mandatory-true/report.json | 35 +++++++++++ .../minItems-n-mandatory-false/dialect.yaml | 14 +++++ .../instance-invalid.yaml | 4 ++ .../instance-valid-1.yaml | 1 + .../instance-valid-2.yaml | 8 +++ .../minItems-n-mandatory-false/report.json | 35 +++++++++++ .../minItems-n-mandatory-true/dialect.yaml | 15 +++++ .../instance-invalid.yaml | 1 + .../instance-valid.yaml | 8 +++ .../minItems-n-mandatory-true/report.json | 35 +++++++++++ .../DialectInstancesValidationTest.scala | 38 +++++++++++ .../shacl/custom/CustomShaclValidator.scala | 63 ++++++++++++++++--- 17 files changed, 273 insertions(+), 11 deletions(-) create mode 100644 amf-aml/shared/src/test/resources/vocabularies2/validation/minItems-0-mandatory-true/dialect.yaml create mode 100644 amf-aml/shared/src/test/resources/vocabularies2/validation/minItems-0-mandatory-true/instance-invalid.yaml create mode 100644 amf-aml/shared/src/test/resources/vocabularies2/validation/minItems-0-mandatory-true/instance-valid.yaml create mode 100644 amf-aml/shared/src/test/resources/vocabularies2/validation/minItems-0-mandatory-true/report.json create mode 100644 amf-aml/shared/src/test/resources/vocabularies2/validation/minItems-n-mandatory-false/dialect.yaml create mode 100644 amf-aml/shared/src/test/resources/vocabularies2/validation/minItems-n-mandatory-false/instance-invalid.yaml create mode 100644 amf-aml/shared/src/test/resources/vocabularies2/validation/minItems-n-mandatory-false/instance-valid-1.yaml create mode 100644 amf-aml/shared/src/test/resources/vocabularies2/validation/minItems-n-mandatory-false/instance-valid-2.yaml create mode 100644 amf-aml/shared/src/test/resources/vocabularies2/validation/minItems-n-mandatory-false/report.json create mode 100644 amf-aml/shared/src/test/resources/vocabularies2/validation/minItems-n-mandatory-true/dialect.yaml create mode 100644 amf-aml/shared/src/test/resources/vocabularies2/validation/minItems-n-mandatory-true/instance-invalid.yaml create mode 100644 amf-aml/shared/src/test/resources/vocabularies2/validation/minItems-n-mandatory-true/instance-valid.yaml create mode 100644 amf-aml/shared/src/test/resources/vocabularies2/validation/minItems-n-mandatory-true/report.json diff --git a/amf-aml/shared/src/main/scala/amf/aml/internal/parse/dialects/DialectSyntax.scala b/amf-aml/shared/src/main/scala/amf/aml/internal/parse/dialects/DialectSyntax.scala index 22855f227..c6d3cf09b 100644 --- a/amf-aml/shared/src/main/scala/amf/aml/internal/parse/dialects/DialectSyntax.scala +++ b/amf-aml/shared/src/main/scala/amf/aml/internal/parse/dialects/DialectSyntax.scala @@ -65,7 +65,8 @@ trait DialectSyntax { this: DialectContext => "enum" -> false, "typeDiscriminatorName" -> false, "typeDiscriminator" -> false, - "unique" -> false + "unique" -> false, + "minItems" -> false ) val annotationMapping: Map[String, Required] = propertyLikeMapping ++ Map( diff --git a/amf-aml/shared/src/main/scala/amf/aml/internal/validate/AMFDialectValidations.scala b/amf-aml/shared/src/main/scala/amf/aml/internal/validate/AMFDialectValidations.scala index 80226137f..05391f8d7 100644 --- a/amf-aml/shared/src/main/scala/amf/aml/internal/validate/AMFDialectValidations.scala +++ b/amf-aml/shared/src/main/scala/amf/aml/internal/validate/AMFDialectValidations.scala @@ -133,7 +133,8 @@ class AMFDialectValidations(val dialect: Dialect)(implicit val nodeMappableFinde ) } - if (prop.minCount().nonNull && prop.minCount().value() > 0) { + // Mandatory field will be validated along with minCount + if (prop.minCount().nonNull) { val message = s"Property '${finalPropName}' is mandatory" validations += new ValidationSpecification( name = validationId(node, finalPropName.value(), "required"), @@ -146,7 +147,8 @@ class AMFDialectValidations(val dialect: Dialect)(implicit val nodeMappableFinde ramlPropertyId = prop.nodePropertyMapping().value(), name = validationId(node, finalPropName.value(), "required") + "/prop", message = Some(message), - minCount = Some("1") + minCount = prop.minCount().option().map(_.toString), + mandatory = prop.mandatory().option().map(_.toString) )) ) } diff --git a/amf-aml/shared/src/test/resources/vocabularies2/validation/minItems-0-mandatory-true/dialect.yaml b/amf-aml/shared/src/test/resources/vocabularies2/validation/minItems-0-mandatory-true/dialect.yaml new file mode 100644 index 000000000..a1755add4 --- /dev/null +++ b/amf-aml/shared/src/test/resources/vocabularies2/validation/minItems-0-mandatory-true/dialect.yaml @@ -0,0 +1,15 @@ +#%Dialect 1.0 +dialect: MinItems +version: "1.0" +documents: + root: + encodes: Root + +nodeMappings: + Root: + mapping: + arrayProperty: + range: string + allowMultiple: true + minItems: 0 + mandatory: true \ No newline at end of file diff --git a/amf-aml/shared/src/test/resources/vocabularies2/validation/minItems-0-mandatory-true/instance-invalid.yaml b/amf-aml/shared/src/test/resources/vocabularies2/validation/minItems-0-mandatory-true/instance-invalid.yaml new file mode 100644 index 000000000..7cb400276 --- /dev/null +++ b/amf-aml/shared/src/test/resources/vocabularies2/validation/minItems-0-mandatory-true/instance-invalid.yaml @@ -0,0 +1 @@ +#%MinItems 1.0 \ No newline at end of file diff --git a/amf-aml/shared/src/test/resources/vocabularies2/validation/minItems-0-mandatory-true/instance-valid.yaml b/amf-aml/shared/src/test/resources/vocabularies2/validation/minItems-0-mandatory-true/instance-valid.yaml new file mode 100644 index 000000000..7d7dd6230 --- /dev/null +++ b/amf-aml/shared/src/test/resources/vocabularies2/validation/minItems-0-mandatory-true/instance-valid.yaml @@ -0,0 +1,2 @@ +#%MinItems 1.0 +arrayProperty: [] \ No newline at end of file diff --git a/amf-aml/shared/src/test/resources/vocabularies2/validation/minItems-0-mandatory-true/report.json b/amf-aml/shared/src/test/resources/vocabularies2/validation/minItems-0-mandatory-true/report.json new file mode 100644 index 000000000..2ac54a103 --- /dev/null +++ b/amf-aml/shared/src/test/resources/vocabularies2/validation/minItems-0-mandatory-true/report.json @@ -0,0 +1,35 @@ +{ + "@type": "http://www.w3.org/ns/shacl#ValidationReport", + "http://www.w3.org/ns/shacl#conforms": false, + "http://www.w3.org/ns/shacl#result": [ + { + "@type": "http://www.w3.org/ns/shacl#ValidationResult", + "http://www.w3.org/ns/shacl#resultSeverity": { + "@id": "http://www.w3.org/ns/shacl#Violation" + }, + "http://www.w3.org/ns/shacl#focusNode": { + "@id": "file://amf-aml/shared/src/test/resources/vocabularies2/validation/minItems-0-mandatory-true/instance-invalid.yaml#/encodes" + }, + "http://www.w3.org/ns/shacl#resultPath": { + "@id": "http://a.ml/vocabularies/data#arrayProperty" + }, + "http://www.w3.org/ns/shacl#resultMessage": "Property 'arrayProperty' is mandatory", + "http://www.w3.org/ns/shacl#sourceShape": { + "@id": "http://a.ml/vocabularies/data#file://amf-aml/shared/src/test/resources/vocabularies2/validation/minItems-0-mandatory-true/dialect.yaml#/declarations/Root_arrayProperty_required_validation" + }, + "http://a.ml/vocabularies/amf/parser#lexicalPosition": { + "@type": "http://a.ml/vocabularies/amf/parser#Position", + "http://a.ml/vocabularies/amf/parser#start": { + "@type": "http://a.ml/vocabularies/amf/parser#Location", + "http://a.ml/vocabularies/amf/parser#line": 1, + "http://a.ml/vocabularies/amf/parser#column": 0 + }, + "http://a.ml/vocabularies/amf/parser#end": { + "@type": "http://a.ml/vocabularies/amf/parser#Location", + "http://a.ml/vocabularies/amf/parser#line": 1, + "http://a.ml/vocabularies/amf/parser#column": 0 + } + } + } + ] +} diff --git a/amf-aml/shared/src/test/resources/vocabularies2/validation/minItems-n-mandatory-false/dialect.yaml b/amf-aml/shared/src/test/resources/vocabularies2/validation/minItems-n-mandatory-false/dialect.yaml new file mode 100644 index 000000000..12e9b903c --- /dev/null +++ b/amf-aml/shared/src/test/resources/vocabularies2/validation/minItems-n-mandatory-false/dialect.yaml @@ -0,0 +1,14 @@ +#%Dialect 1.0 +dialect: MinItems +version: "1.0" +documents: + root: + encodes: Root + +nodeMappings: + Root: + mapping: + arrayProperty: + range: string + allowMultiple: true + minItems: 5 \ No newline at end of file diff --git a/amf-aml/shared/src/test/resources/vocabularies2/validation/minItems-n-mandatory-false/instance-invalid.yaml b/amf-aml/shared/src/test/resources/vocabularies2/validation/minItems-n-mandatory-false/instance-invalid.yaml new file mode 100644 index 000000000..45bb79b03 --- /dev/null +++ b/amf-aml/shared/src/test/resources/vocabularies2/validation/minItems-n-mandatory-false/instance-invalid.yaml @@ -0,0 +1,4 @@ +#%MinItems 1.0 +arrayProperty: + - a + - b \ No newline at end of file diff --git a/amf-aml/shared/src/test/resources/vocabularies2/validation/minItems-n-mandatory-false/instance-valid-1.yaml b/amf-aml/shared/src/test/resources/vocabularies2/validation/minItems-n-mandatory-false/instance-valid-1.yaml new file mode 100644 index 000000000..7cb400276 --- /dev/null +++ b/amf-aml/shared/src/test/resources/vocabularies2/validation/minItems-n-mandatory-false/instance-valid-1.yaml @@ -0,0 +1 @@ +#%MinItems 1.0 \ No newline at end of file diff --git a/amf-aml/shared/src/test/resources/vocabularies2/validation/minItems-n-mandatory-false/instance-valid-2.yaml b/amf-aml/shared/src/test/resources/vocabularies2/validation/minItems-n-mandatory-false/instance-valid-2.yaml new file mode 100644 index 000000000..b01a2238c --- /dev/null +++ b/amf-aml/shared/src/test/resources/vocabularies2/validation/minItems-n-mandatory-false/instance-valid-2.yaml @@ -0,0 +1,8 @@ +#%MinItems 1.0 +arrayProperty: + - a + - b + - c + - d + - e + - f \ No newline at end of file diff --git a/amf-aml/shared/src/test/resources/vocabularies2/validation/minItems-n-mandatory-false/report.json b/amf-aml/shared/src/test/resources/vocabularies2/validation/minItems-n-mandatory-false/report.json new file mode 100644 index 000000000..87ceae51f --- /dev/null +++ b/amf-aml/shared/src/test/resources/vocabularies2/validation/minItems-n-mandatory-false/report.json @@ -0,0 +1,35 @@ +{ + "@type": "http://www.w3.org/ns/shacl#ValidationReport", + "http://www.w3.org/ns/shacl#conforms": false, + "http://www.w3.org/ns/shacl#result": [ + { + "@type": "http://www.w3.org/ns/shacl#ValidationResult", + "http://www.w3.org/ns/shacl#resultSeverity": { + "@id": "http://www.w3.org/ns/shacl#Violation" + }, + "http://www.w3.org/ns/shacl#focusNode": { + "@id": "file://amf-aml/shared/src/test/resources/vocabularies2/validation/minItems-n-mandatory-false/instance-invalid.yaml#/encodes" + }, + "http://www.w3.org/ns/shacl#resultPath": { + "@id": "http://a.ml/vocabularies/data#arrayProperty" + }, + "http://www.w3.org/ns/shacl#resultMessage": "Property 'arrayProperty' is mandatory", + "http://www.w3.org/ns/shacl#sourceShape": { + "@id": "http://a.ml/vocabularies/data#file://amf-aml/shared/src/test/resources/vocabularies2/validation/minItems-n-mandatory-false/dialect.yaml#/declarations/Root_arrayProperty_required_validation" + }, + "http://a.ml/vocabularies/amf/parser#lexicalPosition": { + "@type": "http://a.ml/vocabularies/amf/parser#Position", + "http://a.ml/vocabularies/amf/parser#start": { + "@type": "http://a.ml/vocabularies/amf/parser#Location", + "http://a.ml/vocabularies/amf/parser#line": 2, + "http://a.ml/vocabularies/amf/parser#column": 14 + }, + "http://a.ml/vocabularies/amf/parser#end": { + "@type": "http://a.ml/vocabularies/amf/parser#Location", + "http://a.ml/vocabularies/amf/parser#line": 4, + "http://a.ml/vocabularies/amf/parser#column": 5 + } + } + } + ] +} diff --git a/amf-aml/shared/src/test/resources/vocabularies2/validation/minItems-n-mandatory-true/dialect.yaml b/amf-aml/shared/src/test/resources/vocabularies2/validation/minItems-n-mandatory-true/dialect.yaml new file mode 100644 index 000000000..e2b37e28f --- /dev/null +++ b/amf-aml/shared/src/test/resources/vocabularies2/validation/minItems-n-mandatory-true/dialect.yaml @@ -0,0 +1,15 @@ +#%Dialect 1.0 +dialect: MinItems +version: "1.0" +documents: + root: + encodes: Root + +nodeMappings: + Root: + mapping: + arrayProperty: + range: string + allowMultiple: true + minItems: 5 + mandatory: true \ No newline at end of file diff --git a/amf-aml/shared/src/test/resources/vocabularies2/validation/minItems-n-mandatory-true/instance-invalid.yaml b/amf-aml/shared/src/test/resources/vocabularies2/validation/minItems-n-mandatory-true/instance-invalid.yaml new file mode 100644 index 000000000..7cb400276 --- /dev/null +++ b/amf-aml/shared/src/test/resources/vocabularies2/validation/minItems-n-mandatory-true/instance-invalid.yaml @@ -0,0 +1 @@ +#%MinItems 1.0 \ No newline at end of file diff --git a/amf-aml/shared/src/test/resources/vocabularies2/validation/minItems-n-mandatory-true/instance-valid.yaml b/amf-aml/shared/src/test/resources/vocabularies2/validation/minItems-n-mandatory-true/instance-valid.yaml new file mode 100644 index 000000000..b01a2238c --- /dev/null +++ b/amf-aml/shared/src/test/resources/vocabularies2/validation/minItems-n-mandatory-true/instance-valid.yaml @@ -0,0 +1,8 @@ +#%MinItems 1.0 +arrayProperty: + - a + - b + - c + - d + - e + - f \ No newline at end of file diff --git a/amf-aml/shared/src/test/resources/vocabularies2/validation/minItems-n-mandatory-true/report.json b/amf-aml/shared/src/test/resources/vocabularies2/validation/minItems-n-mandatory-true/report.json new file mode 100644 index 000000000..5159fc1e6 --- /dev/null +++ b/amf-aml/shared/src/test/resources/vocabularies2/validation/minItems-n-mandatory-true/report.json @@ -0,0 +1,35 @@ +{ + "@type": "http://www.w3.org/ns/shacl#ValidationReport", + "http://www.w3.org/ns/shacl#conforms": false, + "http://www.w3.org/ns/shacl#result": [ + { + "@type": "http://www.w3.org/ns/shacl#ValidationResult", + "http://www.w3.org/ns/shacl#resultSeverity": { + "@id": "http://www.w3.org/ns/shacl#Violation" + }, + "http://www.w3.org/ns/shacl#focusNode": { + "@id": "file://amf-aml/shared/src/test/resources/vocabularies2/validation/minItems-n-mandatory-true/instance-invalid.yaml#/encodes" + }, + "http://www.w3.org/ns/shacl#resultPath": { + "@id": "http://a.ml/vocabularies/data#arrayProperty" + }, + "http://www.w3.org/ns/shacl#resultMessage": "Property 'arrayProperty' is mandatory", + "http://www.w3.org/ns/shacl#sourceShape": { + "@id": "http://a.ml/vocabularies/data#file://amf-aml/shared/src/test/resources/vocabularies2/validation/minItems-n-mandatory-true/dialect.yaml#/declarations/Root_arrayProperty_required_validation" + }, + "http://a.ml/vocabularies/amf/parser#lexicalPosition": { + "@type": "http://a.ml/vocabularies/amf/parser#Position", + "http://a.ml/vocabularies/amf/parser#start": { + "@type": "http://a.ml/vocabularies/amf/parser#Location", + "http://a.ml/vocabularies/amf/parser#line": 1, + "http://a.ml/vocabularies/amf/parser#column": 0 + }, + "http://a.ml/vocabularies/amf/parser#end": { + "@type": "http://a.ml/vocabularies/amf/parser#Location", + "http://a.ml/vocabularies/amf/parser#line": 1, + "http://a.ml/vocabularies/amf/parser#column": 0 + } + } + } + ] +} diff --git a/amf-aml/shared/src/test/scala/amf/testing/validation/DialectInstancesValidationTest.scala b/amf-aml/shared/src/test/scala/amf/testing/validation/DialectInstancesValidationTest.scala index 5482c5186..150a36e2d 100644 --- a/amf-aml/shared/src/test/scala/amf/testing/validation/DialectInstancesValidationTest.scala +++ b/amf-aml/shared/src/test/scala/amf/testing/validation/DialectInstancesValidationTest.scala @@ -255,4 +255,42 @@ trait DialectInstancesValidationTest extends DialectInstanceValidation { test("Enum with boolean values") { validate("dialect.yaml", "instance.yaml", Some("report.json"), path = s"$basePath/enum-boolean") } + + test("MinItems 0 with mandatory true valid") { + validate("dialect.yaml", "instance-valid.yaml", None, path = s"$basePath/minItems-0-mandatory-true") + } + + test("MinItems 0 with mandatory true invalid") { + validate("dialect.yaml", + "instance-invalid.yaml", + Some("report.json"), + path = s"$basePath/minItems-0-mandatory-true") + } + + test("MinItems n with mandatory false valid 1") { + validate("dialect.yaml", "instance-valid-1.yaml", None, path = s"$basePath/minItems-n-mandatory-false") + } + + test("MinItems n with mandatory false valid 2") { + validate("dialect.yaml", "instance-valid-2.yaml", None, path = s"$basePath/minItems-n-mandatory-false") + } + + test("MinItems n with mandatory false invalid") { + validate("dialect.yaml", + "instance-invalid.yaml", + Some("report.json"), + path = s"$basePath/minItems-n-mandatory-false") + } + + test("MinItems n with mandatory true valid") { + validate("dialect.yaml", "instance-valid.yaml", None, path = s"$basePath/minItems-n-mandatory-true") + } + + test("MinItems n with mandatory true invalid") { + validate("dialect.yaml", + "instance-invalid.yaml", + Some("report.json"), + path = s"$basePath/minItems-n-mandatory-true") + } + } diff --git a/amf-validation/shared/src/main/scala/amf/validation/internal/shacl/custom/CustomShaclValidator.scala b/amf-validation/shared/src/main/scala/amf/validation/internal/shacl/custom/CustomShaclValidator.scala index fa5256143..746645a68 100644 --- a/amf-validation/shared/src/main/scala/amf/validation/internal/shacl/custom/CustomShaclValidator.scala +++ b/amf-validation/shared/src/main/scala/amf/validation/internal/shacl/custom/CustomShaclValidator.scala @@ -1,11 +1,10 @@ package amf.validation.internal.shacl.custom import amf.core.client.common.validation.MessageStyle -import amf.core.client.scala.model.{DataType, ValueField} +import amf.core.client.scala.model.DataType import amf.core.client.scala.model.document.BaseUnit import amf.core.client.scala.model.domain._ import amf.core.client.scala.vocabulary.Namespace -import amf.core.internal.annotations.SourceAST import amf.core.internal.metamodel.Field import amf.core.internal.parser.domain.Annotations import amf.core.internal.utils._ @@ -15,13 +14,9 @@ import amf.validation.internal.shacl.custom.CustomShaclValidator.{ CustomShaclFunctions, ValidationInfo } -import amf.validation.internal.shacl.custom.DefaultElementExtractor.{extractElement, toNativeScalar} import org.mulesoft.common.time.SimpleDateTime -import org.yaml.model.YScalar import java.net.URISyntaxException -import scala.collection.mutable -import scala.concurrent.{ExecutionContext, Future} object CustomShaclValidator { @@ -230,8 +225,37 @@ class CustomShaclValidator(customFunctions: CustomShaclFunctions, case _ => } propertyConstraint.minCount match { - case Some(_) => validateMinCount(validationSpecification, propertyConstraint, element, reportBuilder) - case _ => + case Some(minCountValue) => + val minCount = minCountValue.toInt + propertyConstraint.mandatory match { + case Some(mandatoryValue) => + // If I have minCount and mandatory, I know I am in an array + val mandatory = mandatoryValue == "true" + // If minCount is 0 and it is mandatory, this comes from minItems = 0 + mandatory = true + // I need to check only the presence of the property, empty arrays are valid + if (minCount == 0 && mandatory) + validateArrayPropertyLengthAndPresence(validationSpecification, + propertyConstraint, + element, + reportBuilder, + mustBePresent = mandatory) + // If minCount is > 0 and it is not mandatory, this comes from minItems = n + // I need to check only the length of the array, but only if it is present + if (minCount > 0 && !mandatory) + validateArrayPropertyLengthAndPresence(validationSpecification, + propertyConstraint, + element, + reportBuilder, + minItems = Some(minCount)) + // If minCount is > 0 and it is mandatory, this comes from minItems = n + mandatory = true + // I need to check the presence and length of the array, the original constraint will handle it + if (minCount > 0 && mandatory) + validateMinCount(validationSpecification, propertyConstraint, element, reportBuilder) + case None => + // If there is no mandatory key, I run the original constraint + validateMinCount(validationSpecification, propertyConstraint, element, reportBuilder) + } + case _ => } propertyConstraint.maxLength match { case Some(_) => validateMaxLength(validationSpecification, propertyConstraint, element, reportBuilder) @@ -318,6 +342,29 @@ class CustomShaclValidator(customFunctions: CustomShaclFunctions, } } + private def validateArrayPropertyLengthAndPresence(validationSpecification: ValidationSpecification, + propertyConstraint: PropertyConstraint, + parentElement: DomainElement, + reportBuilder: ReportBuilder, + mustBePresent: Boolean = false, + minItems: Option[Int] = None): Unit = { + extractor.extractPropertyValue(propertyConstraint, parentElement) match { + // The key is present and it is an array + case Some(ExtractedPropertyValue(arr: AmfArray, _)) => + minItems match { + case Some(minLength) => + // The key is present and I should check the array length + if (!(arr.values.length >= minLength)) + reportFailure(validationSpecification, propertyConstraint, parentElement.id, reportBuilder) + + case None => // Only need to check that the key is present + } + case _ => + // The key is not present missing + if (mustBePresent) reportFailure(validationSpecification, propertyConstraint, parentElement.id, reportBuilder) + } + } + private def validateMinCount(validationSpecification: ValidationSpecification, propertyConstraint: PropertyConstraint, parentElement: DomainElement, From 84543e1d1913338883b141cc1651e422eeda094d Mon Sep 17 00:00:00 2001 From: Loose Date: Wed, 13 Apr 2022 11:23:34 -0300 Subject: [PATCH 5/7] W-10974844 - Changes and fixes --- .../property/like/MandatoryParser.scala | 18 +++++++--------- .../minItems-n-mandatory-false/report.json | 2 +- .../shacl/custom/CustomShaclValidator.scala | 21 ++++++++++++++++--- 3 files changed, 26 insertions(+), 15 deletions(-) diff --git a/amf-aml/shared/src/main/scala/amf/aml/internal/parse/dialects/property/like/MandatoryParser.scala b/amf-aml/shared/src/main/scala/amf/aml/internal/parse/dialects/property/like/MandatoryParser.scala index 9291d39c1..da007c3ce 100644 --- a/amf-aml/shared/src/main/scala/amf/aml/internal/parse/dialects/property/like/MandatoryParser.scala +++ b/amf-aml/shared/src/main/scala/amf/aml/internal/parse/dialects/property/like/MandatoryParser.scala @@ -56,19 +56,15 @@ case class MandatoryParser(map: YMap, propertyLikeMapping: PropertyLikeMapping[_ } - private def parseMandatory: Option[ParsedEntry] = map.key("mandatory") match { - case Some(entry) => - val required = ScalarNode(entry.value).boolean().toBool - val value = if (required) 1 else 0 - Some(ParsedEntry(value, entry)) - case None => None + private def parseMandatory: Option[ParsedEntry] = map.key("mandatory").map { entry => + val required = ScalarNode(entry.value).boolean().toBool + val value = if (required) 1 else 0 + ParsedEntry(value, entry) } - private def parseMinItems: Option[ParsedEntry] = map.key("minItems") match { - case Some(entry) => - val value = ScalarNode(entry.value).integer().toNumber.intValue() - Some(ParsedEntry(value, entry)) - case None => None + private def parseMinItems: Option[ParsedEntry] = map.key("minItems").map { entry => + val value = ScalarNode(entry.value).integer().toNumber.intValue() + ParsedEntry(value, entry) } private case class ParsedEntry(value: Int, entry: YMapEntry) diff --git a/amf-aml/shared/src/test/resources/vocabularies2/validation/minItems-n-mandatory-false/report.json b/amf-aml/shared/src/test/resources/vocabularies2/validation/minItems-n-mandatory-false/report.json index 87ceae51f..e654af70d 100644 --- a/amf-aml/shared/src/test/resources/vocabularies2/validation/minItems-n-mandatory-false/report.json +++ b/amf-aml/shared/src/test/resources/vocabularies2/validation/minItems-n-mandatory-false/report.json @@ -13,7 +13,7 @@ "http://www.w3.org/ns/shacl#resultPath": { "@id": "http://a.ml/vocabularies/data#arrayProperty" }, - "http://www.w3.org/ns/shacl#resultMessage": "Property 'arrayProperty' is mandatory", + "http://www.w3.org/ns/shacl#resultMessage": "Array must have a minimum of 5 items", "http://www.w3.org/ns/shacl#sourceShape": { "@id": "http://a.ml/vocabularies/data#file://amf-aml/shared/src/test/resources/vocabularies2/validation/minItems-n-mandatory-false/dialect.yaml#/declarations/Root_arrayProperty_required_validation" }, diff --git a/amf-validation/shared/src/main/scala/amf/validation/internal/shacl/custom/CustomShaclValidator.scala b/amf-validation/shared/src/main/scala/amf/validation/internal/shacl/custom/CustomShaclValidator.scala index 746645a68..29f6f3f5b 100644 --- a/amf-validation/shared/src/main/scala/amf/validation/internal/shacl/custom/CustomShaclValidator.scala +++ b/amf-validation/shared/src/main/scala/amf/validation/internal/shacl/custom/CustomShaclValidator.scala @@ -250,7 +250,12 @@ class CustomShaclValidator(customFunctions: CustomShaclFunctions, // If minCount is > 0 and it is mandatory, this comes from minItems = n + mandatory = true // I need to check the presence and length of the array, the original constraint will handle it if (minCount > 0 && mandatory) - validateMinCount(validationSpecification, propertyConstraint, element, reportBuilder) + validateArrayPropertyLengthAndPresence(validationSpecification, + propertyConstraint, + element, + reportBuilder, + mustBePresent = mandatory, + minItems = Some(minCount)) case None => // If there is no mandatory key, I run the original constraint validateMinCount(validationSpecification, propertyConstraint, element, reportBuilder) @@ -354,8 +359,18 @@ class CustomShaclValidator(customFunctions: CustomShaclFunctions, minItems match { case Some(minLength) => // The key is present and I should check the array length - if (!(arr.values.length >= minLength)) - reportFailure(validationSpecification, propertyConstraint, parentElement.id, reportBuilder) + if (!(arr.values.length >= minLength)) { + reportFailure(validationSpecification.copy(message = s"Array must have a minimum of $minLength items"), + propertyConstraint, + parentElement.id, + reportBuilder) + +// reportFailure(validationSpecification, +// parentElement.id, +// "", +// Some(s"Array must have a minimum of $minLength items"), +// reportBuilder) + } case None => // Only need to check that the key is present } From ee7fe8ebdbdde624f90c6aa220b0b7f2e4a3f74e Mon Sep 17 00:00:00 2001 From: hghianni Date: Wed, 27 Apr 2022 18:46:03 -0300 Subject: [PATCH 6/7] Publish 6.0.8-RC.0 --- Jenkinsfile | 1 + build.sbt | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index b0144f797..62400a4d4 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -41,6 +41,7 @@ pipeline { anyOf { branch 'master' branch 'develop' + branch 'release/*' } } steps { diff --git a/build.sbt b/build.sbt index d01837404..5aee31f27 100644 --- a/build.sbt +++ b/build.sbt @@ -7,7 +7,7 @@ val ivyLocal = Resolver.file("ivy", file(Path.userHome.absolutePath + "/.ivy2/lo name := "amf-aml" -ThisBuild / version := "6.1.0-SNAPSHOT" +ThisBuild / version := "6.0.8-RC.0" ThisBuild / scalaVersion := "2.12.11" publish := {} @@ -35,7 +35,7 @@ lazy val workspaceDirectory: File = case _ => Path.userHome / "mulesoft" } -val amfCoreVersion = "5.1.0-SNAPSHOT" +val amfCoreVersion = "5.0.8-RC.0" lazy val amfCoreJVMRef = ProjectRef(workspaceDirectory / "amf-core", "coreJVM") lazy val amfCoreJSRef = ProjectRef(workspaceDirectory / "amf-core", "coreJS") From 2bd72428796d5fba9ecf4df87a0b79596ad8b4f2 Mon Sep 17 00:00:00 2001 From: hghianni Date: Tue, 3 May 2022 15:31:27 -0300 Subject: [PATCH 7/7] Publish 6.0.8 --- Jenkinsfile | 1 - build.sbt | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 62400a4d4..b0144f797 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -41,7 +41,6 @@ pipeline { anyOf { branch 'master' branch 'develop' - branch 'release/*' } } steps { diff --git a/build.sbt b/build.sbt index 5aee31f27..5bb1e1e7c 100644 --- a/build.sbt +++ b/build.sbt @@ -7,7 +7,7 @@ val ivyLocal = Resolver.file("ivy", file(Path.userHome.absolutePath + "/.ivy2/lo name := "amf-aml" -ThisBuild / version := "6.0.8-RC.0" +ThisBuild / version := "6.0.8" ThisBuild / scalaVersion := "2.12.11" publish := {} @@ -35,7 +35,7 @@ lazy val workspaceDirectory: File = case _ => Path.userHome / "mulesoft" } -val amfCoreVersion = "5.0.8-RC.0" +val amfCoreVersion = "5.0.8" lazy val amfCoreJVMRef = ProjectRef(workspaceDirectory / "amf-core", "coreJVM") lazy val amfCoreJSRef = ProjectRef(workspaceDirectory / "amf-core", "coreJS")