From 4e4d3e094654db4bb066a61fbed803a38c48b3f4 Mon Sep 17 00:00:00 2001 From: Alex Wichmann Date: Fri, 8 Sep 2023 13:22:37 +0200 Subject: [PATCH 1/5] add ci run to vnext --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 447ff2b5..d43f060d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,7 +1,7 @@ name: Build & Test on: push: - branches: [ main ] + branches: [ main, vnext ] paths: - 'src/**' - '!**/*.md' From 7f535f72a417409fb3bbaaaef554e47b1acc93d1 Mon Sep 17 00:00:00 2001 From: Alex Wichmann Date: Fri, 8 Sep 2023 13:29:33 +0200 Subject: [PATCH 2/5] Update ci.yml --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d43f060d..4f8d2c69 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,7 +6,7 @@ on: - 'src/**' - '!**/*.md' pull_request: - branches: [ main ] + branches: [ main, vnext ] paths: - 'src/**' - '!**/*.md' From 5b6465474ae09d42a27377bf04d58fdbd1dd8a59 Mon Sep 17 00:00:00 2001 From: Gadam8 <44494964+Gadam8@users.noreply.github.com> Date: Mon, 25 Sep 2023 17:38:57 +0100 Subject: [PATCH 3/5] feat(bindings): update FilterPolicy to match AWS API (#128) Co-authored-by: adam.gloyne --- src/LEGO.AsyncAPI.Bindings/Sns/Consumer.cs | 18 ++++- .../Sns/FilterPolicy.cs | 30 ------- .../Sns/SnsOperationBinding.cs | 8 +- .../Bindings/Sns/SnsBindings_Should.cs | 78 ++++++++----------- 4 files changed, 50 insertions(+), 84 deletions(-) delete mode 100644 src/LEGO.AsyncAPI.Bindings/Sns/FilterPolicy.cs diff --git a/src/LEGO.AsyncAPI.Bindings/Sns/Consumer.cs b/src/LEGO.AsyncAPI.Bindings/Sns/Consumer.cs index 46548977..a38174bc 100644 --- a/src/LEGO.AsyncAPI.Bindings/Sns/Consumer.cs +++ b/src/LEGO.AsyncAPI.Bindings/Sns/Consumer.cs @@ -3,6 +3,7 @@ namespace LEGO.AsyncAPI.Bindings.Sns using System; using System.Collections.Generic; using LEGO.AsyncAPI.Attributes; + using LEGO.AsyncAPI.Models.Any; using LEGO.AsyncAPI.Models.Interfaces; using LEGO.AsyncAPI.Writers; @@ -20,8 +21,14 @@ public class Consumer : IAsyncApiExtensible /// /// Only receive a subset of messages from the channel, determined by this policy. + /// Depending on the FilterPolicyScope, a map of either a message attribute or message body to an array of possible matches. The match may be a simple string for an exact match, but it may also be an object that represents a constraint and values for that constraint. /// - public FilterPolicy FilterPolicy { get; set; } + public IAsyncApiAny FilterPolicy { get; set; } + + /// + /// Determines whether the FilterPolicy applies to MessageAttributes or MessageBody. + /// + public FilterPolicyScope FilterPolicyScope { get; set; } /// /// If true AWS SNS attributes are removed from the body, and for SQS, SNS message attributes are copied to SQS message attributes. If false the SNS attributes are included in the body. @@ -55,7 +62,8 @@ public void Serialize(IAsyncApiWriter writer) writer.WriteStartObject(); writer.WriteRequiredProperty("protocol", this.Protocol.GetDisplayName()); writer.WriteRequiredObject("endpoint", this.Endpoint, (w, e) => e.Serialize(w)); - writer.WriteOptionalObject("filterPolicy", this.FilterPolicy, (w, f) => f.Serialize(w)); + writer.WriteOptionalObject("filterPolicy", this.FilterPolicy, (w, f) => f.Write(w)); + writer.WriteOptionalProperty("filterPolicyScope", this.FilterPolicyScope.GetDisplayName()); writer.WriteRequiredProperty("rawMessageDelivery", this.RawMessageDelivery); writer.WriteOptionalObject("redrivePolicy", this.RedrivePolicy, (w, p) => p.Serialize(w)); writer.WriteOptionalObject("deliveryPolicy", this.DeliveryPolicy, (w, p) => p.Serialize(w)); @@ -77,4 +85,10 @@ public enum Protocol [Display("lambda")] Lambda, [Display("firehose")] Firehose, } + + public enum FilterPolicyScope + { + [Display("MessageAttributes")] MessageAttributes, + [Display("MessageBody")] MessageBody, + } } \ No newline at end of file diff --git a/src/LEGO.AsyncAPI.Bindings/Sns/FilterPolicy.cs b/src/LEGO.AsyncAPI.Bindings/Sns/FilterPolicy.cs deleted file mode 100644 index 47530cc0..00000000 --- a/src/LEGO.AsyncAPI.Bindings/Sns/FilterPolicy.cs +++ /dev/null @@ -1,30 +0,0 @@ -namespace LEGO.AsyncAPI.Bindings.Sns -{ - using System; - using System.Collections.Generic; - using LEGO.AsyncAPI.Models.Interfaces; - using LEGO.AsyncAPI.Writers; - - public class FilterPolicy : IAsyncApiExtensible - { - /// - /// A map of a message attribute to an array of possible matches. The match may be a simple string for an exact match, but it may also be an object that represents a constraint and values for that constraint. - /// - public IAsyncApiAny Attributes { get; set; } - - public IDictionary Extensions { get; set; } = new Dictionary(); - - public void Serialize(IAsyncApiWriter writer) - { - if (writer is null) - { - throw new ArgumentNullException(nameof(writer)); - } - - writer.WriteStartObject(); - writer.WriteRequiredObject("attributes", this.Attributes, (w, a) => w.WriteAny(a)); - writer.WriteExtensions(this.Extensions); - writer.WriteEndObject(); - } - } -} \ No newline at end of file diff --git a/src/LEGO.AsyncAPI.Bindings/Sns/SnsOperationBinding.cs b/src/LEGO.AsyncAPI.Bindings/Sns/SnsOperationBinding.cs index d35a46f5..da077f2d 100644 --- a/src/LEGO.AsyncAPI.Bindings/Sns/SnsOperationBinding.cs +++ b/src/LEGO.AsyncAPI.Bindings/Sns/SnsOperationBinding.cs @@ -48,18 +48,14 @@ public class SnsOperationBinding : OperationBinding { { "protocol", (a, n) => { a.Protocol = n.GetScalarValue().GetEnumFromDisplayName(); } }, { "endpoint", (a, n) => { a.Endpoint = n.ParseMapWithExtensions(this.identifierFixFields); } }, - { "filterPolicy", (a, n) => { a.FilterPolicy = n.ParseMapWithExtensions(this.filterPolicyFixedFields); } }, + { "filterPolicy", (a, n) => { a.FilterPolicy = n.CreateAny(); } }, + { "filterPolicyScope", (a, n) => { a.FilterPolicyScope = n.GetScalarValue().GetEnumFromDisplayName(); } }, { "rawMessageDelivery", (a, n) => { a.RawMessageDelivery = n.GetBooleanValue(); } }, { "redrivePolicy", (a, n) => { a.RedrivePolicy = n.ParseMapWithExtensions(this.redrivePolicyFixedFields); } }, { "deliveryPolicy", (a, n) => { a.DeliveryPolicy = n.ParseMapWithExtensions(this.deliveryPolicyFixedFields); } }, { "displayName", (a, n) => { a.DisplayName = n.GetScalarValue(); } }, }; - private FixedFieldMap filterPolicyFixedFields => new() - { - { "attributes", (a, n) => { a.Attributes = n.CreateAny(); } }, - }; - private FixedFieldMap redrivePolicyFixedFields => new() { { "deadLetterQueue", (a, n) => { a.DeadLetterQueue = n.ParseMapWithExtensions(identifierFixFields); } }, diff --git a/test/LEGO.AsyncAPI.Tests/Bindings/Sns/SnsBindings_Should.cs b/test/LEGO.AsyncAPI.Tests/Bindings/Sns/SnsBindings_Should.cs index 7a2269bb..948dbb7e 100644 --- a/test/LEGO.AsyncAPI.Tests/Bindings/Sns/SnsBindings_Should.cs +++ b/test/LEGO.AsyncAPI.Tests/Bindings/Sns/SnsBindings_Should.cs @@ -167,20 +167,18 @@ public void SnsOperationBinding_WithFilledObject_SerializesAndDeserializes() x-identifierExtension: identifierXPropertyName: identifierXPropertyValue filterPolicy: - attributes: - store: - - asyncapi_corp - contact: dec.kolakowski - event: - - anything-but: order_cancelled - order_key: - transient: by_area - customer_interests: - - rugby - - football - - baseball - x-filterPolicyExtension: - filterPolicyXPropertyName: filterPolicyXPropertyValue + store: + - asyncapi_corp + contact: dec.kolakowski + event: + - anything-but: order_cancelled + order_key: + transient: by_area + customer_interests: + - rugby + - football + - baseball + filterPolicyScope: MessageAttributes rawMessageDelivery: false redrivePolicy: deadLetterQueue: @@ -253,47 +251,35 @@ public void SnsOperationBinding_WithFilledObject_SerializesAndDeserializes() }, }, }, - FilterPolicy = new FilterPolicy() - { - Attributes = new AsyncApiObject() + FilterPolicy = new AsyncApiObject() + { + { "store", new AsyncApiArray() { new AsyncApiString("asyncapi_corp") } }, + { "contact", new AsyncApiString("dec.kolakowski") }, { - { "store", new AsyncApiArray() { new AsyncApiString("asyncapi_corp") } }, - { "contact", new AsyncApiString("dec.kolakowski") }, - { - "event", new AsyncApiArray() - { - new AsyncApiObject() - { - { "anything-but", new AsyncApiString("order_cancelled") }, - }, - } - }, + "event", new AsyncApiArray() { - "order_key", new AsyncApiObject() + new AsyncApiObject() { - { "transient", new AsyncApiString("by_area") }, - } - }, + { "anything-but", new AsyncApiString("order_cancelled") }, + }, + } + }, + { + "order_key", new AsyncApiObject() { - "customer_interests", new AsyncApiArray() - { - new AsyncApiString("rugby"), - new AsyncApiString("football"), - new AsyncApiString("baseball"), - } - }, + { "transient", new AsyncApiString("by_area") }, + } }, - Extensions = new Dictionary() { + "customer_interests", new AsyncApiArray() { - "x-filterPolicyExtension", - new AsyncApiObject() - { - { "filterPolicyXPropertyName", new AsyncApiString("filterPolicyXPropertyValue") }, - } - }, + new AsyncApiString("rugby"), + new AsyncApiString("football"), + new AsyncApiString("baseball"), + } }, }, + FilterPolicyScope = FilterPolicyScope.MessageAttributes, RawMessageDelivery = false, RedrivePolicy = new RedrivePolicy() { From f923c65a5d80d8c277631581d9529363e83d87b0 Mon Sep 17 00:00:00 2001 From: "lego-10-01-06[bot]" <119427331+lego-10-01-06[bot]@users.noreply.github.com> Date: Wed, 27 Sep 2023 11:35:12 +0000 Subject: [PATCH 4/5] chore: update CHANGELOG.md --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index efb62f45..4e5bda60 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +# [4.1.0](https://github.com/LEGO/AsyncAPI.NET/compare/v4.0.2...v4.1.0) (2023-09-27) + + +### Features + +* **bindings:** update FilterPolicy to match AWS API ([#128](https://github.com/LEGO/AsyncAPI.NET/issues/128)) ([5b64654](https://github.com/LEGO/AsyncAPI.NET/commit/5b6465474ae09d42a27377bf04d58fdbd1dd8a59)) + ## [4.0.2](https://github.com/LEGO/AsyncAPI.NET/compare/v4.0.1...v4.0.2) (2023-08-01) From e8eef0b5a321bfe37ed600de7fc5516502514d3a Mon Sep 17 00:00:00 2001 From: "Alex W. Carlsen" Date: Wed, 11 Oct 2023 17:03:31 +0200 Subject: [PATCH 5/5] Allow any type of reference --- ...eader.cs => AsyncApiJsonDocumentReader.cs} | 9 +-- .../AsyncApiReaderSettings.cs | 2 +- .../V2/AsyncApiV2VersionService.cs | 28 ++++++-- src/LEGO.AsyncAPI/Models/AsyncApiDocument.cs | 2 +- src/LEGO.AsyncAPI/Models/AsyncApiReference.cs | 45 ++++++++++++- .../Services/AsyncApiReferenceResolver.cs | 15 ++++- test/LEGO.AsyncAPI.Tests/ReferenceTests.cs | 64 +++++++++++++++++++ 7 files changed, 149 insertions(+), 16 deletions(-) rename src/LEGO.AsyncAPI.Readers/{AsyncApiYamlDocumentReader.cs => AsyncApiJsonDocumentReader.cs} (96%) create mode 100644 test/LEGO.AsyncAPI.Tests/ReferenceTests.cs diff --git a/src/LEGO.AsyncAPI.Readers/AsyncApiYamlDocumentReader.cs b/src/LEGO.AsyncAPI.Readers/AsyncApiJsonDocumentReader.cs similarity index 96% rename from src/LEGO.AsyncAPI.Readers/AsyncApiYamlDocumentReader.cs rename to src/LEGO.AsyncAPI.Readers/AsyncApiJsonDocumentReader.cs index fccdf3e3..5c0826af 100644 --- a/src/LEGO.AsyncAPI.Readers/AsyncApiYamlDocumentReader.cs +++ b/src/LEGO.AsyncAPI.Readers/AsyncApiJsonDocumentReader.cs @@ -1,10 +1,11 @@ -// Copyright (c) The LEGO Group. All rights reserved. +// Copyright (c) The LEGO Group. All rights reserved. namespace LEGO.AsyncAPI.Readers { using System.Collections.Generic; using System.Linq; using System.Text.Json.Nodes; + using System.Threading; using System.Threading.Tasks; using LEGO.AsyncAPI.Exceptions; using LEGO.AsyncAPI.Extensions; @@ -76,7 +77,7 @@ public AsyncApiDocument Read(JsonNode input, out AsyncApiDiagnostic diagnostic) return document; } - public Task ReadAsync(JsonNode input) + public async Task ReadAsync(JsonNode input, CancellationToken cancellationToken = default) { var diagnostic = new AsyncApiDiagnostic(); var context = new ParsingContext(diagnostic) @@ -106,11 +107,11 @@ public Task ReadAsync(JsonNode input) } } - return Task.FromResult(new ReadResult + return new ReadResult { AsyncApiDocument = document, AsyncApiDiagnostic = diagnostic, - }); + }; } private void ResolveReferences(AsyncApiDiagnostic diagnostic, AsyncApiDocument document) diff --git a/src/LEGO.AsyncAPI.Readers/AsyncApiReaderSettings.cs b/src/LEGO.AsyncAPI.Readers/AsyncApiReaderSettings.cs index 134acc75..7f759107 100644 --- a/src/LEGO.AsyncAPI.Readers/AsyncApiReaderSettings.cs +++ b/src/LEGO.AsyncAPI.Readers/AsyncApiReaderSettings.cs @@ -18,7 +18,7 @@ public enum ReferenceResolutionSetting DoNotResolveReferences, /// - /// ResolveAllReferences, effectively inlining them. + /// Resolve internal component references and inline them. /// ResolveReferences, } diff --git a/src/LEGO.AsyncAPI.Readers/V2/AsyncApiV2VersionService.cs b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiV2VersionService.cs index 92a34c64..42562d9a 100644 --- a/src/LEGO.AsyncAPI.Readers/V2/AsyncApiV2VersionService.cs +++ b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiV2VersionService.cs @@ -67,9 +67,21 @@ public AsyncApiReference ConvertToAsyncApiReference( Id = reference, }; } + + var asyncApiReference = new AsyncApiReference(); + if (reference.StartsWith("/")) + { + asyncApiReference.IsFragment = true; + } + + asyncApiReference.ExternalResource = segments[0]; + + return asyncApiReference; + } else if (segments.Length == 2) { + // Local reference if (reference.StartsWith("#")) { try @@ -84,7 +96,7 @@ public AsyncApiReference ConvertToAsyncApiReference( } var id = segments[1]; - + var asyncApiReference = new AsyncApiReference(); if (id.StartsWith("/components/")) { var localSegments = segments[1].Split('/'); @@ -103,12 +115,16 @@ public AsyncApiReference ConvertToAsyncApiReference( id = localSegments[3]; } - - return new AsyncApiReference + else { - Type = type, - Id = id, - }; + asyncApiReference.IsFragment = true; + } + + asyncApiReference.ExternalResource = segments[0]; + asyncApiReference.Type = type; + asyncApiReference.Id = id; + + return asyncApiReference; } } diff --git a/src/LEGO.AsyncAPI/Models/AsyncApiDocument.cs b/src/LEGO.AsyncAPI/Models/AsyncApiDocument.cs index b28168e2..1a5b7c71 100644 --- a/src/LEGO.AsyncAPI/Models/AsyncApiDocument.cs +++ b/src/LEGO.AsyncAPI/Models/AsyncApiDocument.cs @@ -150,7 +150,7 @@ internal T ResolveReference(AsyncApiReference reference) where T : class, IAs return this.ResolveReference(reference) as T; } - public IAsyncApiReferenceable ResolveReference(AsyncApiReference reference) + internal IAsyncApiReferenceable ResolveReference(AsyncApiReference reference) { if (reference == null) { diff --git a/src/LEGO.AsyncAPI/Models/AsyncApiReference.cs b/src/LEGO.AsyncAPI/Models/AsyncApiReference.cs index 2403b4ad..4f9660d1 100644 --- a/src/LEGO.AsyncAPI/Models/AsyncApiReference.cs +++ b/src/LEGO.AsyncAPI/Models/AsyncApiReference.cs @@ -11,6 +11,14 @@ namespace LEGO.AsyncAPI.Models /// public class AsyncApiReference : IAsyncApiSerializable { + /// + /// External resource in the reference. + /// It maybe: + /// 1. a absolute/relative file path, for example: ../commons/pet.json + /// 2. a Url, for example: http://localhost/pet.json + /// + public string ExternalResource { get; set; } + /// /// Gets or sets the element type referenced. /// @@ -27,17 +35,37 @@ public class AsyncApiReference : IAsyncApiSerializable public AsyncApiDocument HostDocument { get; set; } = null; /// - /// Gets the full reference string for v2.3. + /// Gets a flag indicating whether a file is a valid OpenAPI document or a fragment + /// + public bool IsFragment { get; set; } = false; + + /// + /// Gets a flag indicating whether this reference is an external reference. + /// + public bool IsExternal => this.ExternalResource != null; + + /// + /// Gets the full reference string for v2. /// public string Reference { get { + if (this.IsExternal) + { + return this.GetExternalReferenceV2(); + } + if (!this.Type.HasValue) { throw new ArgumentNullException(nameof(this.Type)); } + //if (this.Type == ReferenceType.SecurityScheme) + //{ + // return this.Id; + //} + return "#/components/" + this.Type.GetDisplayName() + "/" + this.Id; } } @@ -67,6 +95,21 @@ public void SerializeV2(IAsyncApiWriter writer) writer.WriteEndObject(); } + private string GetExternalReferenceV2() + { + if (this.Id != null) + { + if (this.IsFragment) + { + return this.ExternalResource + "#" + this.Id; + } + + return this.ExternalResource + "#/components/" + this.Type.GetDisplayName() + "/" + this.Id; + } + + return this.ExternalResource; + } + public void Write(IAsyncApiWriter writer) { this.SerializeV2(writer); diff --git a/src/LEGO.AsyncAPI/Services/AsyncApiReferenceResolver.cs b/src/LEGO.AsyncAPI/Services/AsyncApiReferenceResolver.cs index ca345b3f..88808cb8 100644 --- a/src/LEGO.AsyncAPI/Services/AsyncApiReferenceResolver.cs +++ b/src/LEGO.AsyncAPI/Services/AsyncApiReferenceResolver.cs @@ -154,7 +154,7 @@ public override void Visit(AsyncApiSchema schema) this.ResolveMap(schema.Properties); } - private void ResolveObject(T entity, Action assign) where T : class, IAsyncApiReferenceable + private void ResolveObject(T entity, Action assign) where T : class, IAsyncApiReferenceable, new() { if (entity == null) { @@ -184,7 +184,7 @@ private void ResolveObject(T entity, Action assign) where T : class, IAsyn } } - private void ResolveMap(IDictionary map) where T : class, IAsyncApiReferenceable + private void ResolveMap(IDictionary map) where T : class, IAsyncApiReferenceable, new() { if (map == null) { @@ -201,8 +201,17 @@ private void ResolveMap(IDictionary map) where T : class, IAsyncAp } } - private T ResolveReference(AsyncApiReference reference) where T : class, IAsyncApiReferenceable + private T ResolveReference(AsyncApiReference reference) where T : class, IAsyncApiReferenceable, new() { + if (reference.IsExternal) + { + return new () + { + UnresolvedReference = true, + Reference = reference, + }; + } + try { return this.currentDocument.ResolveReference(reference) as T; diff --git a/test/LEGO.AsyncAPI.Tests/ReferenceTests.cs b/test/LEGO.AsyncAPI.Tests/ReferenceTests.cs new file mode 100644 index 00000000..8a12a8ca --- /dev/null +++ b/test/LEGO.AsyncAPI.Tests/ReferenceTests.cs @@ -0,0 +1,64 @@ +// Copyright (c) The LEGO Group. All rights reserved. + +namespace LEGO.AsyncAPI.Tests +{ + using FluentAssertions; + using LEGO.AsyncAPI.Models; + using LEGO.AsyncAPI.Readers; + using NUnit.Framework; + + public class AsyncApiReference_Should + { + + [Test] + public void AsyncApiReference_WithExternalFragmentReference_AllowReference() + { + var actual = @"payload: + $ref: 'http://example.com/some-resource#/path/to/external/fragment' +"; + var reader = new AsyncApiStringReader(); + var deserialized = reader.ReadFragment(actual, AsyncApiVersion.AsyncApi2_0, out var diagnostic); + + diagnostic.Errors.Should().BeEmpty(); + var reference = deserialized.Payload.Reference; + reference.ExternalResource.Should().Be("http://example.com/some-resource"); + reference.Id.Should().Be("/path/to/external/fragment"); + reference.IsFragment.Should().BeTrue(); + reference.IsExternal.Should().BeTrue(); + } + + [Test] + public void AsyncApiReference_WithFragmentReference_AllowReference() + { + var actual = @"payload: + $ref: '/fragments/myFragment' +"; + var reader = new AsyncApiStringReader(); + var deserialized = reader.ReadFragment(actual, AsyncApiVersion.AsyncApi2_0, out var diagnostic); + + diagnostic.Errors.Should().BeEmpty(); + var reference = deserialized.Payload.Reference; + reference.ExternalResource.Should().Be("/fragments/myFragment"); + reference.Id.Should().BeNull(); + reference.IsFragment.Should().BeTrue(); + reference.IsExternal.Should().BeTrue(); + } + + [Test] + public void AsyncApiReference_WithExternalReference_AllowsReferenceDoesNotResolve() + { + var actual = @"payload: + $ref: http://example.com/json.json +"; + + var reader = new AsyncApiStringReader(); + var deserialized = reader.ReadFragment(actual, AsyncApiVersion.AsyncApi2_0, out var diagnostic); + diagnostic.Errors.Should().BeEmpty(); + var reference = deserialized.Payload.Reference; + reference.ExternalResource.Should().Be("http://example.com/json.json"); + reference.Id.Should().BeNull(); + reference.IsExternal.Should().BeTrue(); + diagnostic.Errors.Should().BeEmpty(); + } + } +} \ No newline at end of file