diff --git a/amf-api-contract/shared/src/main/scala/amf/apicontract/internal/validation/payload/collector/ExtensionsCollector.scala b/amf-api-contract/shared/src/main/scala/amf/apicontract/internal/validation/payload/collector/ExtensionsCollector.scala index 3e3e0699b1..ea8ab7b10b 100644 --- a/amf-api-contract/shared/src/main/scala/amf/apicontract/internal/validation/payload/collector/ExtensionsCollector.scala +++ b/amf-api-contract/shared/src/main/scala/amf/apicontract/internal/validation/payload/collector/ExtensionsCollector.scala @@ -1,5 +1,6 @@ package amf.apicontract.internal.validation.payload.collector +import amf.aml.client.scala.model.domain.DialectDomainElement import amf.core.client.scala.model.document.PayloadFragment import amf.core.client.scala.model.domain.extensions.{DomainExtension, Extension, ShapeExtension} import amf.core.client.scala.model.domain.{AmfElement, AmfScalar} @@ -23,7 +24,7 @@ object ExtensionsCollector extends ValidationCandidateCollector { element match { case extension: DomainExtension if Option(extension.definedBy).exists(definition => { Option(definition.schema).isDefined && resolveAnnotation(s"(${definition.name.value()})").isDefined - }) => + }) && !isSemanticExtension(extension) => Seq(extension) case shapeExtension: ShapeExtension if Option(shapeExtension.definedBy).isDefined && Option(shapeExtension.obtainSchema).isDefined => @@ -35,4 +36,8 @@ object ExtensionsCollector extends ValidationCandidateCollector { } case _ => Nil } + + // If it is a SemEx I don't want to validate the candidates + private def isSemanticExtension(extension: DomainExtension) = + extension.fields.fields().exists(_.value.value.isInstanceOf[DialectDomainElement]) } diff --git a/amf-cli/shared/src/test/resources/semantic/api-endpoint-operation.raml b/amf-cli/shared/src/test/resources/semantic/api-endpoint-operation.raml new file mode 100644 index 0000000000..a047954714 --- /dev/null +++ b/amf-cli/shared/src/test/resources/semantic/api-endpoint-operation.raml @@ -0,0 +1,15 @@ +#%RAML 1.0 +title: Something + +annotationTypes: + pagination: any + +/sample: + (pagination): + pageSize: 15 + get: + (pagination): + pageSize: 10 + responses: + 200: + description: A Response \ No newline at end of file diff --git a/amf-cli/shared/src/test/resources/semantic/dialect-endpoint-operation.yaml b/amf-cli/shared/src/test/resources/semantic/dialect-endpoint-operation.yaml new file mode 100644 index 0000000000..ebfca048e5 --- /dev/null +++ b/amf-cli/shared/src/test/resources/semantic/dialect-endpoint-operation.yaml @@ -0,0 +1,29 @@ +#%Dialect 1.0 +dialect: Pagination Test +version: 1.0 + +external: + apiContract: http://a.ml/vocabularies/apiContract# + aml: http://a.ml/vocab# + +documents: {} + +annotationMappings: + PaginationAnnotation: + domain: + - apiContract.EndPoint + - apiContract.Operation + propertyTerm: aml.pagination + range: Pagination + +nodeMappings: + Pagination: + classTerm: aml.Pagination + mapping: + pageSize: + propertyTerm: aml.PageSize + range: integer + mandatory: true + +extensions: + pagination: PaginationAnnotation diff --git a/amf-cli/shared/src/test/resources/validations/raml/annotation-mixed-1.raml b/amf-cli/shared/src/test/resources/validations/raml/annotation-mixed-1.raml new file mode 100644 index 0000000000..a93c944c91 --- /dev/null +++ b/amf-cli/shared/src/test/resources/validations/raml/annotation-mixed-1.raml @@ -0,0 +1,17 @@ +#%RAML 1.0 +title: something + +annotationTypes: + something: + type: string + +/test: + (other): 44 + get: + responses: + (something): blablabla + 200: + body: + application/json: + (something): sarasa + type: string diff --git a/amf-cli/shared/src/test/resources/validations/raml/annotation-mixed-2.raml b/amf-cli/shared/src/test/resources/validations/raml/annotation-mixed-2.raml new file mode 100644 index 0000000000..acad895120 --- /dev/null +++ b/amf-cli/shared/src/test/resources/validations/raml/annotation-mixed-2.raml @@ -0,0 +1,17 @@ +#%RAML 1.0 +title: something + +annotationTypes: + other: + type: any + +/test: + (other): 44 + get: + responses: + (something): blablabla + 200: + body: + application/json: + (something): sarasa + type: string diff --git a/amf-cli/shared/src/test/resources/validations/raml/annotation-with-definition-multiple.raml b/amf-cli/shared/src/test/resources/validations/raml/annotation-with-definition-multiple.raml new file mode 100644 index 0000000000..829be139e6 --- /dev/null +++ b/amf-cli/shared/src/test/resources/validations/raml/annotation-with-definition-multiple.raml @@ -0,0 +1,19 @@ +#%RAML 1.0 +title: something + +annotationTypes: + something: + type: string + other: + type: any + +/test: + (other): 44 + get: + responses: + (something): blablabla + 200: + body: + application/json: + (something): sarasa + type: string diff --git a/amf-cli/shared/src/test/resources/validations/raml/annotation-with-definition.raml b/amf-cli/shared/src/test/resources/validations/raml/annotation-with-definition.raml new file mode 100644 index 0000000000..72cc7d5fa0 --- /dev/null +++ b/amf-cli/shared/src/test/resources/validations/raml/annotation-with-definition.raml @@ -0,0 +1,15 @@ +#%RAML 1.0 +title: something + +annotationTypes: + something: + type: string + +/test: + get: + responses: + 200: + body: + application/json: + (something): sarasa + type: string diff --git a/amf-cli/shared/src/test/resources/validations/raml/annotation-without-definition.raml b/amf-cli/shared/src/test/resources/validations/raml/annotation-without-definition.raml new file mode 100644 index 0000000000..d149456a74 --- /dev/null +++ b/amf-cli/shared/src/test/resources/validations/raml/annotation-without-definition.raml @@ -0,0 +1,11 @@ +#%RAML 1.0 +title: something + +/test: + get: + responses: + 200: + body: + application/json: + (something): sarasa + type: string diff --git a/amf-cli/shared/src/test/resources/validations/reports/model/annotation-mixed-1.report b/amf-cli/shared/src/test/resources/validations/reports/model/annotation-mixed-1.report new file mode 100644 index 0000000000..55b13baf41 --- /dev/null +++ b/amf-cli/shared/src/test/resources/validations/reports/model/annotation-mixed-1.report @@ -0,0 +1,14 @@ +ModelId: file://amf-cli/shared/src/test/resources/validations/raml/annotation-mixed-1.raml +Profile: RAML 1.0 +Conforms: false +Number of results: 1 + +Level: Violation + +- Constraint: http://a.ml/vocabularies/amf/parser#DomainProperty-schema-minCount + Message: type is mandatory for a RAML annotationType + Severity: Violation + Target: file://amf-cli/shared/src/test/resources/validations/raml/annotation-mixed-1.raml#/web-api/endpoint/%2Ftest/customDomainProperties/other/other + Property: http://a.ml/vocabularies/shapes#schema + Range: [(9,2)-(10,0)] + Location: file://amf-cli/shared/src/test/resources/validations/raml/annotation-mixed-1.raml diff --git a/amf-cli/shared/src/test/resources/validations/reports/model/annotation-mixed-2.report b/amf-cli/shared/src/test/resources/validations/reports/model/annotation-mixed-2.report new file mode 100644 index 0000000000..578c3baf6a --- /dev/null +++ b/amf-cli/shared/src/test/resources/validations/reports/model/annotation-mixed-2.report @@ -0,0 +1,14 @@ +ModelId: file://amf-cli/shared/src/test/resources/validations/raml/annotation-mixed-2.raml +Profile: RAML 1.0 +Conforms: false +Number of results: 1 + +Level: Violation + +- Constraint: http://a.ml/vocabularies/amf/parser#DomainProperty-schema-minCount + Message: type is mandatory for a RAML annotationType + Severity: Violation + Target: file://amf-cli/shared/src/test/resources/validations/raml/annotation-mixed-2.raml#/web-api/endpoint/%2Ftest/supportedOperation/get/returns/resp/200/payload/application%2Fjson/scalar/schema/customDomainProperties/something/something + Property: http://a.ml/vocabularies/shapes#schema + Range: [(16,12)-(17,0)] + Location: file://amf-cli/shared/src/test/resources/validations/raml/annotation-mixed-2.raml diff --git a/amf-cli/shared/src/test/resources/validations/reports/model/annotation-without-definition.report b/amf-cli/shared/src/test/resources/validations/reports/model/annotation-without-definition.report new file mode 100644 index 0000000000..6eafe2cf3b --- /dev/null +++ b/amf-cli/shared/src/test/resources/validations/reports/model/annotation-without-definition.report @@ -0,0 +1,14 @@ +ModelId: file://amf-cli/shared/src/test/resources/validations/raml/annotation-without-definition.raml +Profile: RAML 1.0 +Conforms: false +Number of results: 1 + +Level: Violation + +- Constraint: http://a.ml/vocabularies/amf/parser#DomainProperty-schema-minCount + Message: type is mandatory for a RAML annotationType + Severity: Violation + Target: file://amf-cli/shared/src/test/resources/validations/raml/annotation-without-definition.raml#/web-api/endpoint/%2Ftest/supportedOperation/get/returns/resp/200/payload/application%2Fjson/scalar/schema/customDomainProperties/something/something + Property: http://a.ml/vocabularies/shapes#schema + Range: [(10,12)-(11,0)] + Location: file://amf-cli/shared/src/test/resources/validations/raml/annotation-without-definition.raml diff --git a/amf-cli/shared/src/test/scala/amf/semantic/SemanticExtensionTest.scala b/amf-cli/shared/src/test/scala/amf/semantic/SemanticExtensionTest.scala index 578b68718a..5acddd6201 100644 --- a/amf-cli/shared/src/test/scala/amf/semantic/SemanticExtensionTest.scala +++ b/amf-cli/shared/src/test/scala/amf/semantic/SemanticExtensionTest.scala @@ -1,23 +1,12 @@ package amf.semantic -import amf.aml.client.scala.model.document.Dialect -import amf.aml.client.scala.{AMLConfiguration, AMLDialectResult} -import amf.apicontract.client.scala.{AMFConfiguration, AMFLibraryResult, APIConfiguration} import amf.apicontract.client.scala.model.domain.api.Api -import amf.core.client.scala.config.{CachedReference, UnitCache} -import amf.core.client.scala.errorhandling.UnhandledErrorHandler import amf.core.client.scala.model.document.Document -import amf.core.client.scala.model.domain.extensions.DomainExtension -import amf.core.internal.annotations.LexicalInformation -import amf.core.internal.parser.domain.Value -import amf.core.internal.remote.{AsyncApi20, Oas20, Oas30, Raml10, Spec} -import org.mulesoft.antlrast.unsafe.PlatformSecrets -import org.scalatest.Assertion +import amf.core.internal.remote.{AsyncApi20, Oas20, Oas30, Raml10} import org.scalatest.funsuite.AsyncFunSuite import org.scalatest.matchers.should.Matchers -import amf.core.client.scala.model.document.Module -import scala.concurrent.{ExecutionContext, Future} +import scala.concurrent.ExecutionContext class SemanticExtensionTest extends AsyncFunSuite with SemanticExtensionParseTest with Matchers { @@ -48,10 +37,24 @@ class SemanticExtensionTest extends AsyncFunSuite with SemanticExtensionParseTes } } + test("Apply same SemEx to Endpoint and Operation") { + assertModel("dialect-endpoint-operation.yaml", "api-endpoint-operation.raml", Raml10) { doc => + lookupEndpoint(doc) + lookupOperation(doc) + } + } + private def lookupOperation(document: Document) = { val extension = document.encodes.asInstanceOf[Api].endPoints.head.operations.head.customDomainProperties.head assertPaginationExtension(extension, 10) } + + private def lookupEndpoint(document: Document) = { + val extension = + document.encodes.asInstanceOf[Api].endPoints.head.customDomainProperties.head + + assertPaginationExtension(extension, 15) + } } diff --git a/amf-cli/shared/src/test/scala/amf/semantic/SemanticExtensionValidationTest.scala b/amf-cli/shared/src/test/scala/amf/semantic/SemanticExtensionValidationTest.scala index df66b5cec9..cb68fcf947 100644 --- a/amf-cli/shared/src/test/scala/amf/semantic/SemanticExtensionValidationTest.scala +++ b/amf-cli/shared/src/test/scala/amf/semantic/SemanticExtensionValidationTest.scala @@ -92,6 +92,17 @@ class SemanticExtensionValidationTest extends MultiPlatformReportGenTest { } } + test("Validate valid multiple SemEx in the same branch at a RAML 1.0") { + getConfig("../dialect-endpoint-operation.yaml", RAMLConfiguration.RAML10()).flatMap { config => + validate( + "../api-endpoint-operation.raml", + None, + overridedHint = Some(Raml10YamlHint), + configOverride = Some(config) + ) + } + } + private def getConfig( dialect: String, baseConfig: AMFConfiguration = APIConfiguration.API() diff --git a/amf-cli/shared/src/test/scala/amf/validation/RamlModelUniquePlatformReportTest.scala b/amf-cli/shared/src/test/scala/amf/validation/RamlModelUniquePlatformReportTest.scala index 4624fdc441..ee4153f6ee 100644 --- a/amf-cli/shared/src/test/scala/amf/validation/RamlModelUniquePlatformReportTest.scala +++ b/amf-cli/shared/src/test/scala/amf/validation/RamlModelUniquePlatformReportTest.scala @@ -729,4 +729,25 @@ class RamlModelUniquePlatformReportTest extends UniquePlatformReportGenTest { test("json with object as key throws violation") { validate("/raml/json-example-with-object-as-key.raml", Some("json-example-with-object-as-key.report")) } + + test("annotation without definition") { + validate("/raml/annotation-without-definition.raml", Some("annotation-without-definition.report")) + } + + test("annotation with definition") { + validate("/raml/annotation-with-definition.raml", None) + } + + test("multiple annotations with definition in the same branch") { + validate("/raml/annotation-with-definition-multiple.raml", None) + } + + test("multiple annotations without definition 1") { + validate("/raml/annotation-mixed-1.raml", Some("annotation-mixed-1.report")) + } + + // TODO this should show 2 errors and is showing only 1 + test("multiple annotations without definition 2") { + validate("/raml/annotation-mixed-2.raml", Some("annotation-mixed-2.report")) + } } diff --git a/amf-shapes/shared/src/main/scala/amf/shapes/internal/spec/common/parser/AnnotationParser.scala b/amf-shapes/shared/src/main/scala/amf/shapes/internal/spec/common/parser/AnnotationParser.scala index c5eb3e4a8c..39d8309c6f 100644 --- a/amf-shapes/shared/src/main/scala/amf/shapes/internal/spec/common/parser/AnnotationParser.scala +++ b/amf-shapes/shared/src/main/scala/amf/shapes/internal/spec/common/parser/AnnotationParser.scala @@ -4,13 +4,13 @@ import amf.aml.internal.semantic.{SemanticExtensionsFacade, SemanticExtensionsFa import amf.core.client.scala.model.domain.extensions.{CustomDomainProperty, DomainExtension} import amf.core.client.scala.model.domain.{AmfArray, AmfObject} import amf.core.client.scala.parse.document.{ErrorHandlingContext, ParserContext} -import amf.core.internal.annotations.{LexicalInformation, SourceAST} import amf.core.internal.datanode.{DataNodeParser, DataNodeParserContext} import amf.core.internal.metamodel.domain.DomainElementModel import amf.core.internal.metamodel.domain.DomainElementModel.CustomDomainProperties import amf.core.internal.metamodel.domain.extensions.DomainExtensionModel import amf.core.internal.parser.domain._ import amf.core.internal.parser.{LimitedParseConfig, YMapOps} +import amf.shapes.client.scala.model.domain.AnyShape import amf.shapes.internal.annotations.OrphanOasExtension import amf.shapes.internal.spec.ShapeParserContext import amf.shapes.internal.spec.common.parser.AnnotationParser.parseExtensions @@ -76,12 +76,15 @@ object AnnotationParser { semanticParser: Option[SemanticExtensionsFacade] )(implicit ctx: ErrorHandlingContext): Option[DomainExtension] = { semanticParser.flatMap { parser => - val nextCtx = ParserContext(config = LimitedParseConfig(ctx.eh, parser.registry)) - parser.parse(elementTypes, entry, nextCtx, "nonImportantId") + val nextCtx = ParserContext(config = LimitedParseConfig(ctx.eh, parser.registry)) + val maybeExtension = parser.parse(elementTypes, entry, nextCtx, "nonImportantId") + // Inject and anyShape inside the SemEx to avoid validation of annotationType definition + maybeExtension.foreach(_.definedBy.withSchema(AnyShape())) + maybeExtension } } - private def entryKey(entry: YMapEntry) = { + private def entryKey(entry: YMapEntry): String = { entry.key.asOption[YScalar].map(_.text).getOrElse(entry.key.toString) } }