diff --git a/src/LEGO.AsyncAPI.Readers/V3/AsyncApiV3VersionService.cs b/src/LEGO.AsyncAPI.Readers/V3/AsyncApiV3VersionService.cs index dd0122d..8eba7bd 100644 --- a/src/LEGO.AsyncAPI.Readers/V3/AsyncApiV3VersionService.cs +++ b/src/LEGO.AsyncAPI.Readers/V3/AsyncApiV3VersionService.cs @@ -79,7 +79,7 @@ public AsyncApiReference ConvertToAsyncApiReference( public AsyncApiDocument LoadDocument(RootNode rootNode) { - return AsyncApiV2Deserializer.LoadAsyncApi(rootNode); + return AsyncApiV3Deserializer.LoadAsyncApi(rootNode); } public T LoadElement(ParseNode node) diff --git a/src/LEGO.AsyncAPI/Models/References/AsyncApiMessageReference.cs b/src/LEGO.AsyncAPI/Models/References/AsyncApiMessageReference.cs index b0801fd..414f54a 100644 --- a/src/LEGO.AsyncAPI/Models/References/AsyncApiMessageReference.cs +++ b/src/LEGO.AsyncAPI/Models/References/AsyncApiMessageReference.cs @@ -71,6 +71,11 @@ public AsyncApiMessageReference(string reference) public bool Equals(AsyncApiMessageReference other) { + if (other.Target is AsyncApiMessageReference reference) + { + return this.Equals(reference); + } + return this.Target == other.Target; } diff --git a/src/LEGO.AsyncAPI/Validation/AsyncApiValidator.cs b/src/LEGO.AsyncAPI/Validation/AsyncApiValidator.cs index 1b9bfc0..64a8e1d 100644 --- a/src/LEGO.AsyncAPI/Validation/AsyncApiValidator.cs +++ b/src/LEGO.AsyncAPI/Validation/AsyncApiValidator.cs @@ -143,6 +143,12 @@ public void AddWarning(AsyncApiValidatorWarning warning) /// The object to be validated. public override void Visit(AsyncApiServer item) => this.Validate(item); + /// + /// Execute validation rules against an . + /// + /// The object to be validated. + public override void Visit(AsyncApiOperation item) => this.Validate(item); + public override void Visit(IServerBinding item) => this.Validate(item); public override void Visit(IChannelBinding item) => this.Validate(item); diff --git a/src/LEGO.AsyncAPI/Validation/Rules/AsyncApiOperationRules.cs b/src/LEGO.AsyncAPI/Validation/Rules/AsyncApiOperationRules.cs index 61607d7..b229d15 100644 --- a/src/LEGO.AsyncAPI/Validation/Rules/AsyncApiOperationRules.cs +++ b/src/LEGO.AsyncAPI/Validation/Rules/AsyncApiOperationRules.cs @@ -1,5 +1,6 @@ // Copyright (c) The LEGO Group. All rights reserved. +using System.Collections.Generic; using System.Linq; namespace LEGO.AsyncAPI.Validation.Rules @@ -46,22 +47,14 @@ public static class AsyncApiOperationRules } var channels = - context.RootDocument.Channels.Values.Where(channel => channel.Equals(operation.Channel)); + context.RootDocument.Channels.Values.Where(channel => operation.Channel.Equals(channel)); - var referencedChannel = channels.FirstOrDefault(c => c.Equals(operation.Channel)); + var referencedChannel = channels.FirstOrDefault(c => operation.Channel.Equals(c)); if (referencedChannel == null) { context.CreateError( "OperationChannelRef", string.Format(Resource.Validation_OperationMustReferenceValidChannel, operation.Title)); - return; - } - if (!operation.Messages.All(refMessage => referencedChannel.Messages.Any(message => message.Equals(refMessage)))) - { - context.CreateError( - "OperationChannelRef", - string.Format(Resource.Validation_OperationMessagesMustReferenceOperationChannel, operation.Title)); - return; } }); @@ -69,17 +62,35 @@ public static class AsyncApiOperationRules new ValidationRule( (context, operation) => { + if (context.RootDocument?.Operations.Values.FirstOrDefault(op => op == operation) is null) + { + return; + } + var channels = - context.RootDocument.Channels.Values.Where(channel => channel.Equals(operation.Channel)); + context.RootDocument.Channels.Values.Where(channel => operation.Channel.Equals(channel)); + + var referencedChannel = channels.FirstOrDefault(c => operation.Channel.Equals(c)); + + if (referencedChannel == null) + { + return; + } - var referencedChannel = channels.FirstOrDefault(c => c.Equals(operation.Channel)); - if (!operation.Messages.All(refMessage => referencedChannel.Messages.Any(message => message.Equals(refMessage)))) + if (!AllOperationsMessagesReferencesChannelMessages(operation.Messages, referencedChannel.Messages.Values)) { context.CreateError( "OperationChannelRef", string.Format(Resource.Validation_OperationMessagesMustReferenceOperationChannel, operation.Title)); - return; } }); + + private static bool AllOperationsMessagesReferencesChannelMessages( + IList operationMessages, ICollection channelMessages) => + operationMessages.All(opMessage => OperationMessageReferencesAnyChannelMessage(opMessage, channelMessages)); + + private static bool OperationMessageReferencesAnyChannelMessage( + AsyncApiMessageReference operationMessage, ICollection channelMessages) => + channelMessages.Any(channelMessage => channelMessage.Equals(operationMessage)); } } \ No newline at end of file diff --git a/src/LEGO.AsyncAPI/Writers/AsyncApiWriterSettings.cs b/src/LEGO.AsyncAPI/Writers/AsyncApiWriterSettings.cs index 85d3607..45de537 100644 --- a/src/LEGO.AsyncAPI/Writers/AsyncApiWriterSettings.cs +++ b/src/LEGO.AsyncAPI/Writers/AsyncApiWriterSettings.cs @@ -69,7 +69,7 @@ public ReferenceInlineSetting ReferenceInline /// /// Returns back if the refernece should be inlined or not. /// - /// The refernece. + /// The reference. /// True if it should be inlined otherwise false. public bool ShouldInlineReference(AsyncApiReference reference) { diff --git a/test/LEGO.AsyncAPI.Tests/Validation/ValidationRuleTests.cs b/test/LEGO.AsyncAPI.Tests/Validation/ValidationRuleTests.cs index 407858c..ceb9484 100644 --- a/test/LEGO.AsyncAPI.Tests/Validation/ValidationRuleTests.cs +++ b/test/LEGO.AsyncAPI.Tests/Validation/ValidationRuleTests.cs @@ -1,89 +1,169 @@ // Copyright (c) The LEGO Group. All rights reserved. -namespace LEGO.AsyncAPI.Tests.Validation +namespace LEGO.AsyncAPI.Tests.Validation; + +using System.Linq; +using FluentAssertions; +using LEGO.AsyncAPI.Readers; +using NUnit.Framework; + +public class ValidationRuleTests { - using FluentAssertions; - using LEGO.AsyncAPI.Readers; - using NUnit.Framework; - using System.Linq; + [Test] + public void V2_OperationId_WithNonUniqueKey_DiagnosticsError() + { + var input = + """ + asyncapi: 2.6.0 + info: + title: Chat Application + version: 1.0.0 + servers: + testing: + url: test.mosquitto.org:1883 + protocol: mqtt + description: Test broker + channels: + chat/{personId}: + publish: + operationId: onMessageReceieved + message: + name: text + payload: + type: string + chat/{personIdentity}: + publish: + operationId: onMessageReceieved + message: + name: text + payload: + type: string + """; + + new AsyncApiStringReader().Read(input, out var diagnostic); + diagnostic.Errors.First().Message.Should().Be("OperationId: 'onMessageReceieved' is not unique."); + diagnostic.Errors.First().Pointer.Should().Be("#/channels/chat~1{personIdentity}"); + } + + [Test] + public void V3_OperationChannel_NotReferencingARootChannel_DiagnosticsError() + { + var input = + """ + asyncapi: 3.0.0 + info: + title: Chat Application + version: 1.0.0 + servers: + testing: + host: test.mosquitto.org:1883 + protocol: mqtt + description: Test broker + channels: + chatPersonId: + address: chat.{personId} + messages: + messageReceived: + name: text + payload: + type: string + operations: + onMessageReceived: + title: Message received + channel: + $ref: '#/components/channels/secondChannel' + messages: + - $ref: '#/channels/chatPersonId/messages/messageReceived' + components: + channels: + secondChannel: + address: chat.{secondChannel} + """; - public class ValidationRuleTests - { - [Test] - public void V2_OperationId_WithNonUniqueKey_DiagnosticsError() - { - var input = - """ - asyncapi: 2.6.0 - info: - title: Chat Application - version: 1.0.0 - servers: - testing: - url: test.mosquitto.org:1883 - protocol: mqtt - description: Test broker - channels: - chat/{personId}: - publish: - operationId: onMessageReceieved - message: - name: text - payload: - type: string - chat/{personIdentity}: - publish: - operationId: onMessageReceieved - message: - name: text - payload: - type: string - """; + new AsyncApiStringReader().Read(input, out var diagnostic); + diagnostic.Errors.First().Message.Should().Be("The operation 'Message received' MUST point to a channel definition located in the root Channels Object."); + diagnostic.Errors.First().Pointer.Should().Be("#/operations/onMessageReceived"); + } - var document = new AsyncApiStringReader().Read(input, out var diagnostic); - diagnostic.Errors.First().Message.Should().Be("OperationId: 'onMessageReceieved' is not unique."); - diagnostic.Errors.First().Pointer.Should().Be("#/channels/chat~1{personIdentity}"); - } + [Test] + public void V3_OperationMessage_NotReferencingARootChannel_DiagnosticsError() + { + var input = + """ + asyncapi: 3.0.0 + info: + title: Chat Application + version: 1.0.0 + servers: + testing: + host: test.mosquitto.org:1883 + protocol: mqtt + description: Test broker + channels: + chatPersonId: + address: chat.{personId} + messages: + messageReceived: + name: text + payload: + type: string + operations: + onMessageReceived: + title: Message received + channel: + $ref: '#/channels/chatPersonId' + messages: + - $ref: '#/components/messages/messageSent' + components: + messages: + messageSent: + name: text + payload: + type: string + """; - [Test] - [TestCase("chat")] - [TestCase("/some/chat/{personId}")] - [TestCase("chat-{personId}")] - [TestCase("chat-{person_id}")] - [TestCase("chat-{person%2Did}")] - [TestCase("chat-{personId2}")] - public void ChannelKey_WithValidKey_Success(string channelKey) - { - var input = - $""" - asyncapi: 2.6.0 - info: - title: Chat Application - version: 1.0.0 - servers: - testing: - url: test.mosquitto.org:1883 - protocol: mqtt - description: Test broker - channels: - {channelKey}: - publish: - operationId: onMessageReceieved - message: - name: text - payload: - type: string - subscribe: - operationId: sendMessage - message: - name: text - payload: - type: string - """; + new AsyncApiStringReader().Read(input, out var diagnostic); + diagnostic.Errors.First().Message.Should().Be("The messages of operation 'Message received' MUST be a subset of the referenced channels messages."); + diagnostic.Errors.First().Pointer.Should().Be("#/operations/onMessageReceived"); + } - var document = new AsyncApiStringReader().Read(input, out var diagnostic); - diagnostic.Errors.Should().BeEmpty(); - } - } + [Test] + [TestCase("chat")] + [TestCase("/some/chat/{personId}")] + [TestCase("chat-{personId}")] + [TestCase("chat-{person_id}")] + [TestCase("chat-{person%2Did}")] + [TestCase("chat-{personId2}")] + public void ChannelKey_WithValidKey_Success(string channelKey) + { + var input = + $""" + asyncapi: 2.6.0 + info: + title: Chat Application + version: 1.0.0 + servers: + testing: + url: test.mosquitto.org:1883 + protocol: mqtt + description: Test broker + channels: + {channelKey}: + publish: + operationId: onMessageReceieved + message: + name: text + payload: + type: string + subscribe: + operationId: sendMessage + message: + name: text + payload: + type: string + """; -} + new AsyncApiStringReader().Read(input, out var diagnostic); + diagnostic.Errors.Should().BeEmpty(); + } +} \ No newline at end of file