Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

W 12689960 New field at messages: MessageId #1938

Merged
merged 2 commits into from
Feb 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,14 @@ trait MessageModel
)
)

val MessageId: Field = Field(
Str,
ApiContract + "messageId",
ModelDoc(
ModelVocabularies.ApiContract, "messageId", "Unique Id for request and response message"
)
)

}

object MessageModel extends MessageModel {
Expand All @@ -93,7 +101,8 @@ object MessageModel extends MessageModel {
Documentation,
IsAbstract,
HeaderExamples,
HeaderSchema
HeaderSchema,
MessageId
) ++ LinkableElementModel.fields ++ DomainElementModel.fields

override val `type`: List[ValueType] = ApiContract + "Message" :: DomainElementModel.`type`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ class AsyncApiMessageContentEmitter(message: Message, isTrait: Boolean = false,
fs.entry(MessageModel.Title).map(f => result += ValueEmitter("title", f))
fs.entry(MessageModel.Summary).map(f => result += ValueEmitter("summary", f))
fs.entry(MessageModel.Description).map(f => result += ValueEmitter("description", f))
fs.entry(MessageModel.MessageId).map(f=>result += ValueEmitter("messageId", f) )
fs.entry(MessageModel.Tags)
.map(f => result += TagsEmitter("tags", f.array.values.asInstanceOf[Seq[Tag]], ordering))
fs.entry(MessageModel.Documentation)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ object Async2WebApiContext {
wrapped,
declarations,
mutable.HashSet.empty,
mutable.HashSet.empty,
options,
settings(spec),
bindingSet(spec),
Expand All @@ -133,9 +134,9 @@ object Async2WebApiContext {
case AsyncApi21 => ctx => Async21VersionFactory()(ctx)
case AsyncApi22 => ctx => Async22VersionFactory()(ctx)
case AsyncApi23 => ctx => Async23VersionFactory()(ctx)
case AsyncApi24 => ctx => Async23VersionFactory()(ctx)
case AsyncApi25 => ctx => Async23VersionFactory()(ctx)
case AsyncApi26 => ctx => Async23VersionFactory()(ctx)
case AsyncApi24 => ctx => Async24VersionFactory()(ctx)
case AsyncApi25 => ctx => Async24VersionFactory()(ctx)
case AsyncApi26 => ctx => Async24VersionFactory()(ctx)
}

private def bindingSet(spec: Spec): AsyncValidBindingSet = spec match {
Expand All @@ -155,6 +156,7 @@ class Async2WebApiContext private (
private val wrapped: ParserContext,
private val ds: Option[AsyncWebApiDeclarations] = None,
private val operationIds: mutable.Set[String] = mutable.HashSet(),
private val messageIds: mutable.Set[String] = mutable.HashSet(),
options: ParsingOptions = ParsingOptions(),
settings: Async2Settings,
bindings: AsyncValidBindingSet,
Expand All @@ -166,6 +168,7 @@ class Async2WebApiContext private (
wrapped,
ds,
operationIds,
messageIds,
settings,
bindings
) {
Expand All @@ -178,6 +181,7 @@ class Async2WebApiContext private (
this,
Some(declarations),
operationIds,
messageIds,
options,
settings,
bindings,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,3 +86,16 @@ class Async23VersionFactory()(implicit ctx: AsyncWebApiContext) extends Async21V
object Async23VersionFactory {
def apply()(implicit ctx: AsyncWebApiContext): Async23VersionFactory = new Async23VersionFactory()(ctx)
}

class Async24VersionFactory()(implicit ctx: AsyncWebApiContext) extends Async23VersionFactory{
override def messageParser(
entryLike: YMapEntryLike,
parent: String,
messageType: Option[MessageType],
isTrait: Boolean
)(implicit ctx: AsyncWebApiContext): AsyncMessageParser = Async24MessageParser(entryLike, parent, messageType, isTrait)
}

object Async24VersionFactory {
def apply()(implicit ctx: AsyncWebApiContext): Async24VersionFactory = new Async24VersionFactory()(ctx)
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ abstract class AsyncWebApiContext(
private val wrapped: ParserContext,
private val ds: Option[AsyncWebApiDeclarations] = None,
private val operationIds: mutable.Set[String] = mutable.HashSet(),
private val messageIds: mutable.Set[String] = mutable.HashSet(),
specSettings: SpecSettings,
protected val bindings: AsyncValidBindingSet
) extends OasLikeWebApiContext(loc, refs, options, wrapped, ds, operationIds, specSettings = specSettings) {
Expand Down Expand Up @@ -50,4 +51,13 @@ abstract class AsyncWebApiContext(
)

def validBindingSet(): AsyncValidBindingSet = bindings

def registerMessageId(messageId: String): Boolean = {
if (messageIds.contains(messageId)) {
false
} else {
messageIds.add(messageId)
true
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ object Async20Syntax extends SpecSyntax {
"clientId",
"bindingVersion"
),
"message" -> Set(
"message20" -> Set(
"headers",
"payload",
"correlationId",
Expand All @@ -144,6 +144,23 @@ object Async20Syntax extends SpecSyntax {
"examples",
"traits"
),
"message24" -> Set(
"headers",
"payload",
"correlationId",
"schemaFormat",
"contentType",
"name",
"title",
"summary",
"description",
"tags",
"externalDocs",
"bindings",
"examples",
"traits",
"messageId"
),
"message examples" -> Set(
"headers",
"payload"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import amf.apicontract.internal.metamodel.domain.MessageModel.IsAbstract
import amf.apicontract.internal.metamodel.domain.{MessageModel, OperationModel, PayloadModel}
import amf.apicontract.internal.spec.async.emitters.domain.MessageExamplePair
import amf.apicontract.internal.spec.async.parser.bindings.AsyncMessageBindingsParser
import amf.apicontract.internal.spec.async.parser.context.AsyncWebApiContext
import amf.apicontract.internal.spec.async.parser.context.{Async2WebApiContext, AsyncWebApiContext}
import amf.apicontract.internal.spec.async.{MessageType, Publish, Subscribe}
import amf.apicontract.internal.spec.common.WebApiDeclarations.ErrorMessage
import amf.apicontract.internal.spec.common.parser.SpecParserOps
Expand All @@ -26,6 +26,7 @@ import amf.shapes.internal.domain.resolution.ExampleTracking.tracking
import amf.shapes.internal.spec.common.JSONSchemaDraft7SchemaVersion
import amf.shapes.internal.spec.common.parser._
import org.yaml.model.{YMap, YMapEntry, YNode, YSequence}
import amf.apicontract.internal.validation.definitions.ParserSideValidations.DuplicatedMessageId

trait AsyncMessageParser {
def parse(): Message
Expand All @@ -52,6 +53,16 @@ object Async21MessageParser {
}
}

object Async24MessageParser {
def apply(entryLike: YMapEntryLike, parent: String, messageType: Option[MessageType], isTrait: Boolean = false)(
implicit ctx: AsyncWebApiContext
): AsyncMessageParser = {
val populator = if (isTrait) Async24MessageTraitPopulator() else Async24ConcreteMessagePopulator(parent)
val finder = if (isTrait) MessageTraitFinder() else MessageFinder()
new Async20MessageParser(entryLike, parent, messageType, populator, finder, isTrait)(ctx)
}
}

class Async20MessageParser(
entryLike: YMapEntryLike,
parent: String,
Expand Down Expand Up @@ -146,6 +157,7 @@ case class AsyncMultipleMessageParser(map: YMap, parent: String, messageType: Me
}

abstract class Async2MessagePopulator()(implicit ctx: AsyncWebApiContext) extends SpecParserOps {
protected val closeShapeName: String

def populate(map: YMap, message: Message): Message = {
map.key("name", MessageModel.DisplayName in message)
Expand Down Expand Up @@ -220,7 +232,7 @@ abstract class Async2MessagePopulator()(implicit ctx: AsyncWebApiContext) extend
if (shouldParsePayloadModel(map))
parsePayload(map, message)

ctx.closedShape(message, map, "message")
ctx.closedShape(message, map, closeShapeName)
AnnotationParser(message, map).parse()
message
}
Expand Down Expand Up @@ -287,8 +299,13 @@ abstract class Async2MessagePopulator()(implicit ctx: AsyncWebApiContext) extend
}

protected def addExampleNaming(node: YMap, example: Example): Example = example

}
abstract class Async21MessagePopulator()(implicit ctx: AsyncWebApiContext) extends Async2MessagePopulator {

abstract class Async20MessagePopulator()(implicit ctx: AsyncWebApiContext) extends Async2MessagePopulator{
override protected val closeShapeName: String = "message20"
}
abstract class Async21MessagePopulator()(implicit ctx: AsyncWebApiContext) extends Async20MessagePopulator {

override protected def addExampleNaming(node: YMap, example: Example): Example = {
node.key("name", (ExampleModel.DisplayName in example).allowingAnnotations)
Expand All @@ -298,6 +315,29 @@ abstract class Async21MessagePopulator()(implicit ctx: AsyncWebApiContext) exten
}
}

abstract class Async24MessagePopulator()(implicit ctx: AsyncWebApiContext) extends Async21MessagePopulator {
override protected val closeShapeName: String = "message24"

override def populate(map: YMap, message: Message): Message = {
super.populate(map, message)
map.key("messageId").foreach { entry =>
val messageId = entry.value.toString()
if (!ctx.registerMessageId(messageId))
ctx.eh.violation(
ParserSideValidations.DuplicatedMessageId, message, s"Duplicated message id '$messageId'", entry.value.location
)
parseMessageId(map, message)
}
message
}

private def parseMessageId(map: YMap, message: Message): Unit = {
map.key("messageId", MessageModel.MessageId in message)
}


}

trait AsyncMessageTraitPopulator {
protected def innerPopulate(map: YMap, message: Message)(implicit ctx: AsyncWebApiContext): Message = {
message.setWithoutId(IsAbstract, AmfScalar(true), Annotations.synthesized())
Expand All @@ -316,7 +356,18 @@ case class Async21MessageTraitPopulator()(implicit ctx: AsyncWebApiContext)
override def populate(map: YMap, message: Message): Message = innerPopulate(map,super.populate(map, message))
}
case class Async20MessageTraitPopulator()(implicit ctx: AsyncWebApiContext)
extends Async2MessagePopulator()
extends Async20MessagePopulator()
with AsyncMessageTraitPopulator {

override protected def parseTraits(map: YMap, message: Message): Unit = Unit

override protected def parseSchema(map: YMap, payload: Payload): Unit = Unit

override def populate(map: YMap, message: Message): Message = innerPopulate(map, super.populate(map, message))
}

case class Async24MessageTraitPopulator()(implicit ctx: AsyncWebApiContext)
extends Async24MessagePopulator()
with AsyncMessageTraitPopulator {

override protected def parseTraits(map: YMap, message: Message): Unit = Unit
Expand Down Expand Up @@ -355,7 +406,15 @@ case class Async21ConcreteMessagePopulator(parentId: String)(implicit ctx: Async
}

case class Async20ConcreteMessagePopulator(parentId: String)(implicit ctx: AsyncWebApiContext)
extends Async2MessagePopulator() with AsyncConcreteMessagePopulator() {
extends Async20MessagePopulator() with AsyncConcreteMessagePopulator() {

override protected def parseTraits(map: YMap, message: Message): Unit = innerParseTrait(map, message, parentId)

def parseSchema(map: YMap, payload: Payload): Unit = innerParseSchema(map, payload)
}

case class Async24ConcreteMessagePopulator(parentId: String)(implicit ctx: AsyncWebApiContext)
extends Async24MessagePopulator() with AsyncConcreteMessagePopulator() {

override protected def parseTraits(map: YMap, message: Message): Unit = innerParseTrait(map, message, parentId)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,11 @@ object ParserSideValidations extends Validations {
"Duplicated operation id"
)

val DuplicatedMessageId = validation(
"duplicated-message-id",
"duplicated messageId"
)

val SchemasDeprecated = validation(
"schemas-deprecated",
"'schemas' keyword it's deprecated for 1.0 version, should use 'types' instead"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
asyncapi: 2.4.0
info:
title: Best API in the world
version: 0.1.0
channels:
OperationCreationQueue:
subscribe:
description: testuli
components:
messages:
SomeMessage:
messageId: SomeMessage

Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
asyncapi: '2.4.0'
info:
title: Example API with Duplicated MessageId
version: '1.0.0'
description: This API demonstrates a duplicated messageId in an inline message and a referenced message.
servers:
production:
url: api.example.com
protocol: wss
channels:
channel1:
subscribe:
message:
messageId: duplicateMessageId
name: inlineMessage
summary: This is an inline message with a duplicated messageId.
payload:
type: object
properties:
property1:
type: string
channel2:
subscribe:
message:
$ref: '#/components/messages/referencedMessage'
components:
messages:
referencedMessage:
messageId: duplicateMessageId
name: referencedMessage
summary: This is a referenced message with a duplicated messageId.
payload:
type: object
properties:
property2:
type: string
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
asyncapi: 2.2.0
info:
title: Best API in the world
version: 0.1.0
channels:
OperationCreationQueue:
subscribe:
description: testuli
components:
messages:
SomeMessage:
messageId: SomeMessage
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
asyncapi: 2.4.0
info:
title: Best API in the world
version: 0.1.0
channels:
OperationCreationQueue:
subscribe:
description: testuli
components:
messages:
SomeMessage:
messageId: SomeMessage
AnotherMessage:
messageId: SomeMessage
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
asyncapi: 2.4.0
info:
title: Best API in the world
version: 0.1.0
channels:
OperationCreationQueue:
subscribe:
description: testuli
components:
messages:
SomeMessage:
messageId: SomeMessage
AnotherMessage:
messageId: AnotherMessage
Loading