Skip to content

Commit

Permalink
Merge pull request #1343 from aml-org/aliased-semex
Browse files Browse the repository at this point in the history
Support aliased semex through companion libs
  • Loading branch information
tomsfernandez authored Mar 25, 2022
2 parents 3fd451e + 784d014 commit 9dbddae
Show file tree
Hide file tree
Showing 14 changed files with 324 additions and 130 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package amf.apicontract.internal.spec.common

import amf.aml.client.scala.model.document.Dialect
import amf.apicontract.client.scala.model.document.DataTypeFragment
import amf.apicontract.client.scala.model.domain._
import amf.apicontract.client.scala.model.domain.bindings.{
Expand Down Expand Up @@ -27,6 +28,7 @@ import amf.core.client.scala.model.domain.{DataNode, DomainElement, ObjectNode,
import amf.core.client.scala.parse.document.EmptyFutureDeclarations
import amf.core.internal.annotations.{DeclaredElement, DeclaredHeader, ErrorDeclaration}
import amf.core.internal.parser.domain._
import amf.core.internal.utils.QName
import amf.shapes.client.scala.model.domain.Example
import amf.shapes.client.scala.model.domain.{AnyShape, CreativeWork, Example}
import amf.shapes.internal.domain.metamodel.CreativeWorkModel
Expand All @@ -36,33 +38,35 @@ import org.yaml.model.{YNode, YPart}
/**
* Declarations object.
*/
class WebApiDeclarations(val alias: Option[String],
var libs: Map[String, WebApiDeclarations] = Map(),
var frags: Map[String, FragmentRef] = Map(),
var shapes: Map[String, Shape] = Map(),
var anns: Map[String, CustomDomainProperty] = Map(),
var resourceTypes: Map[String, ResourceType] = Map(),
var parameters: Map[String, Parameter] = Map(),
var payloads: Map[String, Payload] = Map(),
var traits: Map[String, Trait] = Map(),
var securitySchemes: Map[String, SecurityScheme] = Map(),
var responses: Map[String, Response] = Map(),
var examples: Map[String, Example] = Map(),
var requests: Map[String, Request] = Map(),
var headers: Map[String, Parameter] = Map(),
var links: Map[String, TemplatedLink] = Map(),
var correlationIds: Map[String, CorrelationId] = Map(),
var callbacks: Map[String, List[Callback]] = Map(),
var messages: Map[String, Message] = Map(),
var messageBindings: Map[String, MessageBindings] = Map(),
var operationBindings: Map[String, OperationBindings] = Map(),
var channelBindings: Map[String, ChannelBindings] = Map(),
var serverBindings: Map[String, ServerBindings] = Map(),
var operationTraits: Map[String, Operation] = Map(),
var messageTraits: Map[String, Message] = Map(),
val errorHandler: AMFErrorHandler,
val futureDeclarations: FutureDeclarations,
var others: Map[String, BaseUnit] = Map())
class WebApiDeclarations(
val alias: Option[String],
var libs: Map[String, WebApiDeclarations] = Map(), // TODO: sync this with libraries, is confusing and we end having tw different maps (one of them is not being filled)
var frags: Map[String, FragmentRef] = Map(),
var shapes: Map[String, Shape] = Map(),
var anns: Map[String, CustomDomainProperty] = Map(),
var resourceTypes: Map[String, ResourceType] = Map(),
var parameters: Map[String, Parameter] = Map(),
var payloads: Map[String, Payload] = Map(),
var traits: Map[String, Trait] = Map(),
var securitySchemes: Map[String, SecurityScheme] = Map(),
var responses: Map[String, Response] = Map(),
var examples: Map[String, Example] = Map(),
var requests: Map[String, Request] = Map(),
var headers: Map[String, Parameter] = Map(),
var links: Map[String, TemplatedLink] = Map(),
var correlationIds: Map[String, CorrelationId] = Map(),
var callbacks: Map[String, List[Callback]] = Map(),
var messages: Map[String, Message] = Map(),
var messageBindings: Map[String, MessageBindings] = Map(),
var operationBindings: Map[String, OperationBindings] = Map(),
var channelBindings: Map[String, ChannelBindings] = Map(),
var serverBindings: Map[String, ServerBindings] = Map(),
var operationTraits: Map[String, Operation] = Map(),
var messageTraits: Map[String, Message] = Map(),
val errorHandler: AMFErrorHandler,
val futureDeclarations: FutureDeclarations,
var others: Map[String, BaseUnit] = Map(),
var extensions: Map[String, Dialect] = Map())
extends Declarations(libs, frags, anns, errorHandler, futureDeclarations = futureDeclarations) {

def promoteExternaltoDataTypeFragment(text: String, fullRef: String, shape: Shape): Shape = {
Expand All @@ -81,32 +85,33 @@ class WebApiDeclarations(val alias: Option[String],
}

protected def mergeParts(other: WebApiDeclarations, merged: WebApiDeclarations): Unit = {
libs.foreach { case (k, s) => merged.libs += (k -> s) }
other.libs.foreach { case (k, s) => merged.libs += (k -> s) }
frags.foreach { case (k, s) => merged.frags += (k -> s) }
other.frags.foreach { case (k, s) => merged.frags += (k -> s) }
libraries.foreach { case (k, s) => merged.libraries += (k -> s) }
other.libraries.foreach { case (k, s) => merged.libraries += (k -> s) }
fragments.foreach { case (k, s) => merged.fragments += (k -> s) }
other.fragments.foreach { case (k, s) => merged.fragments += (k -> s) }
shapes.foreach { case (k, s) => merged.shapes += (k -> s) }
other.shapes.foreach { case (k, s) => merged.shapes += (k -> s) }
anns.foreach { case (k, s) => merged.anns += (k -> s) }
other.anns.foreach { case (k, s) => merged.anns += (k -> s) }
annotations.foreach { case (k, s) => merged.annotations += (k -> s) }
other.annotations.foreach { case (k, s) => merged.annotations += (k -> s) }
resourceTypes.foreach { case (k, s) => merged.resourceTypes += (k -> s) }
other.resourceTypes.foreach { case (k, s) => merged.resourceTypes += (k -> s) }
parameters.foreach { case (k, s) => merged.parameters += (k -> s) }
other.parameters.foreach { case (k, s) => merged.parameters += (k -> s) }
payloads.foreach { case (k, s) => merged.payloads += (k -> s) }
other.payloads.foreach { case (k, s) => merged.payloads += (k -> s) }
traits.foreach { case (k, s) => merged.traits += (k -> s) }
other.traits.foreach { case (k, s) => merged.traits += (k -> s) }
libs.foreach { case (k, s) => merged.libs += (k -> s) }
other.libs.foreach { case (k, s) => merged.libs += (k -> s) }
frags.foreach { case (k, s) => merged.frags += (k -> s) }
other.frags.foreach { case (k, s) => merged.frags += (k -> s) }
libraries.foreach { case (k, s) => merged.libraries += (k -> s) }
other.libraries.foreach { case (k, s) => merged.libraries += (k -> s) }
fragments.foreach { case (k, s) => merged.fragments += (k -> s) }
other.fragments.foreach { case (k, s) => merged.fragments += (k -> s) }
shapes.foreach { case (k, s) => merged.shapes += (k -> s) }
other.shapes.foreach { case (k, s) => merged.shapes += (k -> s) }
anns.foreach { case (k, s) => merged.anns += (k -> s) }
other.anns.foreach { case (k, s) => merged.anns += (k -> s) }
annotations.foreach { case (k, s) => merged.annotations += (k -> s) }
other.annotations.foreach { case (k, s) => merged.annotations += (k -> s) }
resourceTypes.foreach { case (k, s) => merged.resourceTypes += (k -> s) }
other.resourceTypes.foreach { case (k, s) => merged.resourceTypes += (k -> s) }
parameters.foreach { case (k, s) => merged.parameters += (k -> s) }
other.parameters.foreach { case (k, s) => merged.parameters += (k -> s) }
payloads.foreach { case (k, s) => merged.payloads += (k -> s) }
other.payloads.foreach { case (k, s) => merged.payloads += (k -> s) }
traits.foreach { case (k, s) => merged.traits += (k -> s) }
other.traits.foreach { case (k, s) => merged.traits += (k -> s) }
securitySchemes.foreach { case (k, s) => merged.securitySchemes += (k -> s) }
other.securitySchemes.foreach { case (k, s) => merged.securitySchemes += (k -> s) }
responses.foreach { case (k, s) => merged.responses += (k -> s) }
other.responses.foreach { case (k, s) => merged.responses += (k -> s) }
responses.foreach { case (k, s) => merged.responses += (k -> s) }
other.responses.foreach { case (k, s) => merged.responses += (k -> s) }
extensions.foreach { case (k, s) => merged.extensions = merged.extensions + (k -> s) }
}

def merge(other: WebApiDeclarations): WebApiDeclarations = {
Expand All @@ -123,6 +128,11 @@ class WebApiDeclarations(val alias: Option[String],
shapes = shapes + (s.name.value() -> s)
}

def +=(extension: Map[String, Dialect]): WebApiDeclarations = {
extensions = extensions ++ extension
this
}

override def +=(element: DomainElement): WebApiDeclarations = {
// future declarations are used for shapes, and therefore only resolved for that case
element match {
Expand Down Expand Up @@ -298,6 +308,14 @@ class WebApiDeclarations(val alias: Option[String],
case o: Operation => o
}

def findDialect(key: String): Option[Dialect] = {
val fqn = QName(key)
if (fqn.isQualified) {
val maybeDeclarations: Option[WebApiDeclarations] = libs.get(fqn.qualification)
maybeDeclarations.flatMap(_.findDialect(fqn.name))
} else extensions.get(key)
}

def findMessageTrait(key: String, scope: SearchScope.Scope): Option[Message] =
findForType(key, _.asInstanceOf[WebApiDeclarations].messageTraits, scope) collect {
case o: Message => o
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
package amf.apicontract.internal.spec.common.parser

import amf.aml.client.scala.model.document.Dialect
import amf.apicontract.internal.spec.common.WebApiDeclarations
import amf.apicontract.internal.spec.raml.parser.document
import amf.apicontract.internal.validation.definitions.ParserSideValidations.InvalidModuleType
import amf.core.client.scala.model.document._
import amf.core.client.scala.parse.document._
import amf.core.internal.annotations.{Aliases, ReferencedInfo}
import amf.core.internal.parser.{Root, YMapOps}
import amf.core.internal.validation.CoreValidations.ExpectedModule
import org.mulesoft.common.collections.FilterType
import org.yaml.model.{YMap, YScalar, YType}

/**
Expand All @@ -19,10 +22,19 @@ case class WebApiRegister()(implicit ctx: WebApiContext) extends CollectionSideE
case d: Module =>
val library = ctx.declarations.getOrCreateLibrary(alias)
d.declares.foreach(library += _)
collectExtensions(library, d)
case fragment: Fragment => ctx.declarations += (alias -> fragment)
case _ => // ignore
}
}

private def collectExtensions(library: WebApiDeclarations, l: Module): Unit = {
l.references
.filterType[Dialect]
.foreach { d =>
library += d.extensionIndex
}
}
}

abstract class CommonReferencesParser(references: Seq[ParsedReference])(implicit ctx: WebApiContext) {
Expand All @@ -41,8 +53,11 @@ abstract class CommonReferencesParser(references: Seq[ParsedReference])(implicit
protected def parseLibraries(declarations: ReferenceCollector[BaseUnit]): Unit
}

case class ReferencesParser(baseUnit: BaseUnit, rootLoc: String, key: String, map: YMap, references: Seq[ParsedReference])(
implicit ctx: WebApiContext)
case class ReferencesParser(baseUnit: BaseUnit,
rootLoc: String,
key: String,
map: YMap,
references: Seq[ParsedReference])(implicit ctx: WebApiContext)
extends CommonReferencesParser(references) {

private def target(url: String): Option[BaseUnit] =
Expand Down Expand Up @@ -74,13 +89,15 @@ case class ReferencesParser(baseUnit: BaseUnit, rootLoc: String, key: String, ma
})
case YType.Null =>
case _ =>
ctx.eh.violation(InvalidModuleType, rootLoc, s"Invalid ast type for uses: ${entry.value.tagType}", entry.value.location)
ctx.eh.violation(InvalidModuleType,
rootLoc,
s"Invalid ast type for uses: ${entry.value.tagType}",
entry.value.location)
}
)
}

private def collectAlias(module: BaseUnit,
alias: (Aliases.Alias, ReferencedInfo)): BaseUnit = {
private def collectAlias(module: BaseUnit, alias: (Aliases.Alias, ReferencedInfo)): BaseUnit = {
module.annotations.find(classOf[Aliases]) match {
case Some(aliases) =>
module.annotations.reject(_.isInstanceOf[Aliases])
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package amf.apicontract.internal.spec.common.parser

import amf.aml.client.scala.model.document.Dialect
import amf.aml.internal.parse.common.DeclarationContext
import amf.aml.internal.semantic.SemanticExtensionsFacade
import amf.aml.internal.registries.AMLRegistry
import amf.aml.internal.semantic.{SemanticExtensionsFacade, SemanticExtensionsFacadeBuilder}
import amf.apicontract.internal.spec.common.emitter.SpecAwareContext
import amf.apicontract.internal.spec.common.{OasParameter, WebApiDeclarations}
import amf.apicontract.internal.spec.oas.parser.context.OasWebApiContext
Expand All @@ -20,7 +22,7 @@ import amf.core.internal.parser.domain.{Annotations, FragmentRef, SearchScope}
import amf.core.internal.plugins.syntax.{SYamlAMFParserErrorHandler, SyamlAMFErrorHandler}
import amf.core.internal.remote.Spec
import amf.core.internal.unsafe.PlatformSecrets
import amf.core.internal.utils.{AliasCounter, IdCounter}
import amf.core.internal.utils.{AliasCounter, IdCounter, QName}
import amf.shapes.client.scala.model.domain.AnyShape
import amf.shapes.internal.spec.common.parser.{SpecSyntax, YMapEntryLike}
import amf.shapes.internal.spec.common.{JSONSchemaDraft4SchemaVersion, SchemaVersion}
Expand All @@ -40,8 +42,16 @@ abstract class ExtensionsContext(val loc: String,
extends ParserContext(loc, refs, wrapped.futureDeclarations, wrapped.config)
with DataNodeParserContext {

private def getExtensionsMap: Map[String, Dialect] = wrapped.config.registryContext.getRegistry match {
case amlRegistry: AMLRegistry => amlRegistry.getExtensionRegistry
case _ => Map.empty
}

val declarations: WebApiDeclarations = declarationsOption.getOrElse(
new WebApiDeclarations(None, errorHandler = eh, futureDeclarations = futureDeclarations))
new WebApiDeclarations(None,
errorHandler = eh,
futureDeclarations = futureDeclarations,
extensions = getExtensionsMap))

override def findAnnotation(key: String, scope: SearchScope.Scope): Option[CustomDomainProperty] =
declarations.findAnnotation(key, scope)
Expand Down Expand Up @@ -76,7 +86,7 @@ abstract class WebApiContext(loc: String,
val syntax: SpecSyntax
val spec: Spec

val extensionsFacade: SemanticExtensionsFacade = SemanticExtensionsFacade(wrapped.config)
val extensionsFacadeBuilder: SemanticExtensionsFacadeBuilder = WebApiSemanticExtensionsFacadeBuilder()

var localJSONSchemaContext: Option[YNode] = wrapped match {
case wac: WebApiContext => wac.localJSONSchemaContext
Expand Down Expand Up @@ -209,4 +219,16 @@ abstract class WebApiContext(loc: String,
isWarning: Boolean = false): Unit =
if (isWarning) eh.warning(ClosedShapeSpecificationWarning, node, message, entry.location)
else eh.violation(ClosedShapeSpecification, node, message, entry.location)

case class WebApiSemanticExtensionsFacadeBuilder() extends SemanticExtensionsFacadeBuilder {
override def extensionName(name: String): SemanticExtensionsFacade = {
val fqn = QName(name)
val dialect = if (fqn.isQualified) {
val maybeDeclarations: Option[WebApiDeclarations] =
declarations.libraries.get(fqn.qualification).collectFirst({ case w: WebApiDeclarations => w })
maybeDeclarations.flatMap(_.extensions.get(fqn.name))
} else declarations.extensions.get(name)
dialect.map(SemanticExtensionsFacade(fqn.name, _)).getOrElse(SemanticExtensionsFacade(name, wrapped.config))
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package amf.apicontract.internal.spec.common.parser

import amf.aml.internal.semantic.SemanticExtensionsFacade
import amf.aml.internal.semantic.{SemanticExtensionsFacade, SemanticExtensionsFacadeBuilder}
import amf.apicontract.internal.spec.async.parser.context.AsyncWebApiContext
import amf.apicontract.internal.spec.common.OasWebApiDeclarations
import amf.apicontract.internal.spec.jsonschema.JsonSchemaWebApiContext
Expand Down Expand Up @@ -39,7 +39,7 @@ case class WebApiShapeParserContextAdapter(ctx: WebApiContext) extends ShapePars

override def syntax: SpecSyntax = ctx.syntax

override def extensionsFacade: SemanticExtensionsFacade = ctx.extensionsFacade
override def extensionsFacadeBuilder: SemanticExtensionsFacadeBuilder = ctx.extensionsFacadeBuilder

override def closedRamlTypeShape(shape: Shape, ast: YMap, shapeType: String, typeInfo: TypeInfo): Unit = ctx match {
case ramlCtx: RamlWebApiContext => ramlCtx.closedRamlTypeShape(shape, ast, shapeType, typeInfo)
Expand Down
11 changes: 11 additions & 0 deletions amf-cli/shared/src/test/resources/semantic/aliased/api.raml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#%RAML 1.0
title: Something
uses:
lib: extension.raml
/sample:
get:
responses:
"200":
(lib.pagination):
pageSize: 5
description: A Response
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#%RAML 1.0 Library
annotationTypes:
pagination: any
27 changes: 27 additions & 0 deletions amf-cli/shared/src/test/resources/semantic/aliased/extension.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#%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.Response
propertyTerm: aml.pagination
range: Pagination

nodeMappings:
Pagination:
classTerm: aml.Pagination
mapping:
pageSize:
propertyTerm: aml.PageSize
range: integer
mandatory: true

extensions:
pagination: PaginationAnnotation
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package amf.semantic

import amf.core.internal.remote.Raml10
import org.scalatest.funsuite.AsyncFunSuite
import org.scalatest.matchers.should.Matchers

import scala.concurrent.ExecutionContext

class AliasedSemanticExtensionTest extends AsyncFunSuite with SemanticExtensionParseTest {

override implicit val executionContext: ExecutionContext = ExecutionContext.Implicits.global

override protected val basePath = "file://amf-cli/shared/src/test/resources/semantic/aliased/"

test("Apply semantic extension to RAML 1.0") {
assertModel("extension.yaml", "api.raml", Raml10) { lookupResponse }
}

}
Loading

0 comments on commit 9dbddae

Please sign in to comment.