From fb50d00192896f9ac26e65df1e991854b33aa17c Mon Sep 17 00:00:00 2001 From: Dec Kolakowski <51292634+dpwdec@users.noreply.github.com> Date: Tue, 9 Jul 2024 14:44:14 +0100 Subject: [PATCH 01/18] =?UTF-8?q?fix:=20correct=20typing=20of=20exclusive?= =?UTF-8?q?=20maximums=20and=20minimums=20for=20draft7=20jso=E2=80=A6=20(#?= =?UTF-8?q?188)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../V2/AsyncApiSchemaDeserializer.cs | 10 ++++++++-- src/LEGO.AsyncAPI/Models/AsyncApiSchema.cs | 4 ++-- .../Models/AsyncApiSchema_Should.cs | 10 +++++----- 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/src/LEGO.AsyncAPI.Readers/V2/AsyncApiSchemaDeserializer.cs b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiSchemaDeserializer.cs index b99fdef8..1934cb0d 100644 --- a/src/LEGO.AsyncAPI.Readers/V2/AsyncApiSchemaDeserializer.cs +++ b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiSchemaDeserializer.cs @@ -61,7 +61,10 @@ public class JsonSchemaDeserializer } }, { - "exclusiveMaximum", (a, n) => { a.ExclusiveMaximum = bool.Parse(n.GetScalarValue()); } + "exclusiveMaximum", (a, n) => + { + a.ExclusiveMaximum = double.Parse(n.GetScalarValue(), NumberStyles.Float, n.Context.Settings.CultureInfo); + } }, { "minimum", @@ -71,7 +74,10 @@ public class JsonSchemaDeserializer } }, { - "exclusiveMinimum", (a, n) => { a.ExclusiveMinimum = bool.Parse(n.GetScalarValue()); } + "exclusiveMinimum", (a, n) => + { + a.ExclusiveMinimum = double.Parse(n.GetScalarValue(), NumberStyles.Float, n.Context.Settings.CultureInfo); + } }, { "maxLength", (a, n) => { a.MaxLength = int.Parse(n.GetScalarValue(), n.Context.Settings.CultureInfo); } diff --git a/src/LEGO.AsyncAPI/Models/AsyncApiSchema.cs b/src/LEGO.AsyncAPI/Models/AsyncApiSchema.cs index 3244017b..0931c953 100644 --- a/src/LEGO.AsyncAPI/Models/AsyncApiSchema.cs +++ b/src/LEGO.AsyncAPI/Models/AsyncApiSchema.cs @@ -42,7 +42,7 @@ public class AsyncApiSchema : IAsyncApiReferenceable, IAsyncApiExtensible, IAsyn /// /// follow JSON Schema definition: https://json-schema.org/draft-07/json-schema-release-notes.html. /// - public bool? ExclusiveMaximum { get; set; } + public double? ExclusiveMaximum { get; set; } /// /// follow JSON Schema definition: https://json-schema.org/draft-07/json-schema-release-notes.html. @@ -52,7 +52,7 @@ public class AsyncApiSchema : IAsyncApiReferenceable, IAsyncApiExtensible, IAsyn /// /// follow JSON Schema definition: https://json-schema.org/draft-07/json-schema-release-notes.html. /// - public bool? ExclusiveMinimum { get; set; } + public double? ExclusiveMinimum { get; set; } /// /// follow JSON Schema definition: https://json-schema.org/draft-07/json-schema-release-notes.html. diff --git a/test/LEGO.AsyncAPI.Tests/Models/AsyncApiSchema_Should.cs b/test/LEGO.AsyncAPI.Tests/Models/AsyncApiSchema_Should.cs index 830a44bc..ec7c023f 100644 --- a/test/LEGO.AsyncAPI.Tests/Models/AsyncApiSchema_Should.cs +++ b/test/LEGO.AsyncAPI.Tests/Models/AsyncApiSchema_Should.cs @@ -21,7 +21,7 @@ public class AsyncApiSchema_Should : TestBase Title = "title1", MultipleOf = 3, Maximum = 42, - ExclusiveMinimum = true, + ExclusiveMinimum = 42, Minimum = 10, Default = new AsyncApiAny(15), Type = SchemaType.Integer, @@ -37,7 +37,7 @@ public class AsyncApiSchema_Should : TestBase Title = "title1", MultipleOf = 3, Maximum = double.MaxValue, - ExclusiveMinimum = true, + ExclusiveMinimum = double.MinValue, Minimum = double.MinValue, Default = new AsyncApiAny(15), Type = SchemaType.Integer, @@ -211,7 +211,7 @@ public class AsyncApiSchema_Should : TestBase Title = "title1", MultipleOf = 3, Maximum = 42, - ExclusiveMinimum = true, + ExclusiveMinimum = 42, Minimum = 10, Default = new AsyncApiAny(15), Type = SchemaType.Integer, @@ -307,7 +307,7 @@ public void SerializeAsJson_WithAdvancedSchemaNumber_V2Works() "type": "integer", "maximum": 42, "minimum": 10, - "exclusiveMinimum": true, + "exclusiveMinimum": 42, "multipleOf": 3, "default": 15, "nullable": true, @@ -335,7 +335,7 @@ public void SerializeAsJson_WithAdvancedSchemaBigNumbers_V2Works() "type": "integer", "maximum": 1.7976931348623157E+308, "minimum": -1.7976931348623157E+308, - "exclusiveMinimum": true, + "exclusiveMinimum": -1.7976931348623157E+308, "multipleOf": 3, "default": 15, "nullable": true, From a554e419149b261ab8b5768743921614131a9270 Mon Sep 17 00:00:00 2001 From: Alex Wichmann Date: Mon, 29 Jul 2024 11:49:38 +0200 Subject: [PATCH 02/18] refactor: improve bindings TryGetValue (#190) --- ...dings.cs => AsyncApiBindings{TBinding}.cs} | 27 ------ src/LEGO.AsyncAPI/Models/BindingExtensions.cs | 62 +++++++++++++ .../Bindings/BindingExtensions_Should.cs | 89 +++++++++++++++++++ .../WebSockets/WebSocketBindings_Should.cs | 4 +- 4 files changed, 153 insertions(+), 29 deletions(-) rename src/LEGO.AsyncAPI/Models/{AsyncApiBindings.cs => AsyncApiBindings{TBinding}.cs} (57%) create mode 100644 src/LEGO.AsyncAPI/Models/BindingExtensions.cs create mode 100644 test/LEGO.AsyncAPI.Tests/Bindings/BindingExtensions_Should.cs diff --git a/src/LEGO.AsyncAPI/Models/AsyncApiBindings.cs b/src/LEGO.AsyncAPI/Models/AsyncApiBindings{TBinding}.cs similarity index 57% rename from src/LEGO.AsyncAPI/Models/AsyncApiBindings.cs rename to src/LEGO.AsyncAPI/Models/AsyncApiBindings{TBinding}.cs index 6aaa389a..f11858aa 100644 --- a/src/LEGO.AsyncAPI/Models/AsyncApiBindings.cs +++ b/src/LEGO.AsyncAPI/Models/AsyncApiBindings{TBinding}.cs @@ -7,33 +7,6 @@ namespace LEGO.AsyncAPI.Models using LEGO.AsyncAPI.Models.Interfaces; using LEGO.AsyncAPI.Writers; - public static class BindingExtensions - { - public static bool TryGetValue(this AsyncApiBindings bindings, out IServerBinding binding) - where TBinding : IServerBinding - { - return bindings.TryGetValue(Activator.CreateInstance().BindingKey, out binding); - } - - public static bool TryGetValue(this AsyncApiBindings bindings, out IChannelBinding binding) - where TBinding : IChannelBinding - { - return bindings.TryGetValue(Activator.CreateInstance().BindingKey, out binding); - } - - public static bool TryGetValue(this AsyncApiBindings bindings, out IOperationBinding binding) - where TBinding : IOperationBinding - { - return bindings.TryGetValue(Activator.CreateInstance().BindingKey, out binding); - } - - public static bool TryGetValue(this AsyncApiBindings bindings, out IMessageBinding binding) - where TBinding : IMessageBinding - { - return bindings.TryGetValue(Activator.CreateInstance().BindingKey, out binding); - } - } - public class AsyncApiBindings : Dictionary, IAsyncApiReferenceable where TBinding : IBinding { diff --git a/src/LEGO.AsyncAPI/Models/BindingExtensions.cs b/src/LEGO.AsyncAPI/Models/BindingExtensions.cs new file mode 100644 index 00000000..3bcab20f --- /dev/null +++ b/src/LEGO.AsyncAPI/Models/BindingExtensions.cs @@ -0,0 +1,62 @@ +// Copyright (c) The LEGO Group. All rights reserved. + +namespace LEGO.AsyncAPI.Models +{ + using System; + using LEGO.AsyncAPI.Models.Interfaces; + + public static class BindingExtensions + { + public static bool TryGetValue(this AsyncApiBindings bindings, out TBinding binding) + where TBinding : class, IServerBinding + { + if (bindings.TryGetValue(Activator.CreateInstance().BindingKey, out var serverBinding)) + { + binding = serverBinding as TBinding; + return true; + } + + binding = default; + return false; + } + + public static bool TryGetValue(this AsyncApiBindings bindings, out TBinding binding) + where TBinding : class, IChannelBinding + { + if (bindings.TryGetValue(Activator.CreateInstance().BindingKey, out var channelBinding)) + { + binding = channelBinding as TBinding; + return true; + } + + binding = default; + return false; + } + + public static bool TryGetValue(this AsyncApiBindings bindings, out TBinding binding) + where TBinding : class, IOperationBinding + { + if (bindings.TryGetValue(Activator.CreateInstance().BindingKey, out var operationBinding)) + { + binding = operationBinding as TBinding; + return true; + } + + binding = default; + return false; + } + + public static bool TryGetValue(this AsyncApiBindings bindings, out TBinding binding) + where TBinding : class, IMessageBinding + { + if (bindings.TryGetValue(Activator.CreateInstance().BindingKey, out var messageBinding)) + { + binding = messageBinding as TBinding; + return true; + } + + binding = default; + return false; + } + } +} diff --git a/test/LEGO.AsyncAPI.Tests/Bindings/BindingExtensions_Should.cs b/test/LEGO.AsyncAPI.Tests/Bindings/BindingExtensions_Should.cs new file mode 100644 index 00000000..3806fc03 --- /dev/null +++ b/test/LEGO.AsyncAPI.Tests/Bindings/BindingExtensions_Should.cs @@ -0,0 +1,89 @@ +// Copyright (c) The LEGO Group. All rights reserved. + +namespace LEGO.AsyncAPI.Tests.Bindings.WebSockets +{ + using System.Linq; + using FluentAssertions; + using LEGO.AsyncAPI.Bindings.MQTT; + using LEGO.AsyncAPI.Bindings.Pulsar; + using LEGO.AsyncAPI.Bindings.WebSockets; + using LEGO.AsyncAPI.Models; + using NUnit.Framework; + + public class BindingExtensions_Should + { + [Test] + public void TryGetValue_WithChannelBinding_ReturnsBinding() + { + var channel = new AsyncApiChannel(); + channel.Bindings.Add(new WebSocketsChannelBinding + { + Method = "POST", + Query = new AsyncApiSchema + { + Description = "this mah query", + }, + Headers = new AsyncApiSchema + { + Description = "this mah binding", + }, + }); + + var result = channel.Bindings.TryGetValue(out var channelBinding); + result.Should().BeTrue(); + channelBinding.Should().NotBeNull(); + channelBinding.Should().BeEquivalentTo(channel.Bindings.First().Value); + } + + [Test] + public void TryGetValue_WithServerBinding_ReturnsBinding() + { + var server = new AsyncApiServer(); + server.Bindings.Add(new PulsarServerBinding + { + Tenant = "test tenant", + }); + + var result = server.Bindings.TryGetValue(out var serverBinding); + result.Should().BeTrue(); + serverBinding.Should().NotBeNull(); + serverBinding.Should().BeEquivalentTo(server.Bindings.First().Value); + } + + [Test] + public void TryGetValue_WithOperationBinding_ReturnsBinding() + { + var operation = new AsyncApiOperation(); + operation.Bindings.Add(new MQTTOperationBinding + { + QoS = 23, + MessageExpiryInterval = 1, + Retain = true, + }); + + var result = operation.Bindings.TryGetValue(out var operationBinding); + result.Should().BeTrue(); + operationBinding.Should().NotBeNull(); + operationBinding.Should().BeEquivalentTo(operation.Bindings.First().Value); + } + + [Test] + public void TryGetValue_WithMessageBinding_ReturnsBinding() + { + var message = new AsyncApiMessage(); + message.Bindings.Add(new MQTTMessageBinding + { + PayloadFormatIndicator = 2, + CorrelationData = new AsyncApiSchema + { + Description = "Test", + }, + }); + + var result = message.Bindings.TryGetValue(out var messageBinding); + result.Should().BeTrue(); + messageBinding.Should().NotBeNull(); + messageBinding.Should().BeEquivalentTo(message.Bindings.First().Value); + } + } +} diff --git a/test/LEGO.AsyncAPI.Tests/Bindings/WebSockets/WebSocketBindings_Should.cs b/test/LEGO.AsyncAPI.Tests/Bindings/WebSockets/WebSocketBindings_Should.cs index b8aef976..c37ada58 100644 --- a/test/LEGO.AsyncAPI.Tests/Bindings/WebSockets/WebSocketBindings_Should.cs +++ b/test/LEGO.AsyncAPI.Tests/Bindings/WebSockets/WebSocketBindings_Should.cs @@ -8,8 +8,8 @@ namespace LEGO.AsyncAPI.Tests.Bindings.WebSockets using LEGO.AsyncAPI.Models; using LEGO.AsyncAPI.Readers; using NUnit.Framework; - - internal class WebSocketBindings_Should : TestBase + + public class WebSocketBindings_Should : TestBase { [Test] public void WebSocketChannelBinding_WithFilledObject_SerializesAndDeserializes() From e1830b99d41076b236759354b086d5872d5d70ce Mon Sep 17 00:00:00 2001 From: "lego-10-01-06[bot]" <119427331+lego-10-01-06[bot]@users.noreply.github.com> Date: Mon, 29 Jul 2024 09:50:34 +0000 Subject: [PATCH 03/18] chore: update CHANGELOG.md --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 505c9b09..c2b85fba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +## [5.2.2](https://github.com/LEGO/AsyncAPI.NET/compare/v5.2.1...v5.2.2) (2024-07-29) + + +### Bug Fixes + +* correct typing of exclusive maximums and minimums for draft7 jso… ([#188](https://github.com/LEGO/AsyncAPI.NET/issues/188)) ([fb50d00](https://github.com/LEGO/AsyncAPI.NET/commit/fb50d00192896f9ac26e65df1e991854b33aa17c)) +* resolving wrong reference ([#180](https://github.com/LEGO/AsyncAPI.NET/issues/180)) ([47685cd](https://github.com/LEGO/AsyncAPI.NET/commit/47685cd19c7e58391625be043b1e5d82c49eedc8)) + ## [5.2.1](https://github.com/LEGO/AsyncAPI.NET/compare/v5.2.0...v5.2.1) (2024-06-12) From b8307c57a6f9bc7c546702c24dffdfb1833aa5d3 Mon Sep 17 00:00:00 2001 From: Alex Wichmann Date: Mon, 29 Jul 2024 13:58:02 +0200 Subject: [PATCH 04/18] fix: add missing walk and visit methods for bindings. (#191) --- .../Services/AsyncApiVisitorBase.cs | 32 +++++ src/LEGO.AsyncAPI/Services/AsyncApiWalker.cs | 127 ++++++++++++++++-- src/LEGO.AsyncAPI/Services/CurrentKeys.cs | 8 +- .../Validation/AsyncApiValidator.cs | 8 ++ 4 files changed, 165 insertions(+), 10 deletions(-) diff --git a/src/LEGO.AsyncAPI/Services/AsyncApiVisitorBase.cs b/src/LEGO.AsyncAPI/Services/AsyncApiVisitorBase.cs index 8e3dc241..899731d0 100644 --- a/src/LEGO.AsyncAPI/Services/AsyncApiVisitorBase.cs +++ b/src/LEGO.AsyncAPI/Services/AsyncApiVisitorBase.cs @@ -246,6 +246,38 @@ public virtual void Visit(IDictionary channels) { } + public virtual void Visit(AsyncApiBindings bindings) + { + } + + public virtual void Visit(IServerBinding binding) + { + } + + public virtual void Visit(AsyncApiBindings bindings) + { + } + + public virtual void Visit(IChannelBinding binding) + { + } + + public virtual void Visit(AsyncApiBindings bindings) + { + } + + public virtual void Visit(IOperationBinding binding) + { + } + + public virtual void Visit(AsyncApiBindings bindings) + { + } + + public virtual void Visit(IMessageBinding binding) + { + } + public virtual void Visit(AsyncApiChannel channel) { } diff --git a/src/LEGO.AsyncAPI/Services/AsyncApiWalker.cs b/src/LEGO.AsyncAPI/Services/AsyncApiWalker.cs index fc5c5186..844ab7e9 100644 --- a/src/LEGO.AsyncAPI/Services/AsyncApiWalker.cs +++ b/src/LEGO.AsyncAPI/Services/AsyncApiWalker.cs @@ -74,15 +74,48 @@ internal void Walk(AsyncApiComponents components) }); this.Walk(AsyncApiConstants.ServerBindings, () => - { - if (components.ServerBindings != null) - { - foreach (var item in components.ServerBindings) - { - this.Walk(item.Key, () => this.Walk(item.Value, isComponent: true)); - } - } - }); + { + if (components.ServerBindings != null) + { + foreach (var item in components.ServerBindings) + { + this.Walk(item.Key, () => this.Walk(item.Value, isComponent: true)); + } + } + }); + + this.Walk(AsyncApiConstants.ChannelBindings, () => + { + if (components.ChannelBindings != null) + { + foreach (var item in components.ChannelBindings) + { + this.Walk(item.Key, () => this.Walk(item.Value, isComponent: true)); + } + } + }); + + this.Walk(AsyncApiConstants.OperationBindings, () => + { + if (components.OperationBindings != null) + { + foreach (var item in components.OperationBindings) + { + this.Walk(item.Key, () => this.Walk(item.Value, isComponent: true)); + } + } + }); + + this.Walk(AsyncApiConstants.MessageBindings, () => + { + if (components.MessageBindings != null) + { + foreach (var item in components.MessageBindings) + { + this.Walk(item.Key, () => this.Walk(item.Value, isComponent: true)); + } + } + }); this.Walk(AsyncApiConstants.Parameters, () => { @@ -562,6 +595,25 @@ internal void Walk(AsyncApiBindings serverBindings, bool isCompo } this.visitor.Visit(serverBindings); + if (serverBindings != null) + { + foreach (var binding in serverBindings) + { + this.visitor.CurrentKeys.ServerBinding = binding.Key; + this.Walk(binding.Key, () => this.Walk(binding.Value)); + this.visitor.CurrentKeys.ServerBinding = null; + } + } + } + + internal void Walk(IServerBinding binding) + { + if (binding == null) + { + return; + } + + this.visitor.Visit(binding); } internal void Walk(AsyncApiBindings channelBindings, bool isComponent = false) @@ -572,6 +624,25 @@ internal void Walk(AsyncApiBindings channelBindings, bool isCom } this.visitor.Visit(channelBindings); + if (channelBindings != null) + { + foreach (var binding in channelBindings) + { + this.visitor.CurrentKeys.ChannelBinding = binding.Key; + this.Walk(binding.Key, () => this.Walk(binding.Value)); + this.visitor.CurrentKeys.ChannelBinding = null; + } + } + } + + internal void Walk(IChannelBinding binding) + { + if (binding == null) + { + return; + } + + this.visitor.Visit(binding); } internal void Walk(AsyncApiBindings operationBindings, bool isComponent = false) @@ -582,6 +653,25 @@ internal void Walk(AsyncApiBindings operationBindings, bool i } this.visitor.Visit(operationBindings); + if (operationBindings != null) + { + foreach (var binding in operationBindings) + { + this.visitor.CurrentKeys.OperationBinding = binding.Key; + this.Walk(binding.Key, () => this.Walk(binding.Value)); + this.visitor.CurrentKeys.OperationBinding = null; + } + } + } + + internal void Walk(IOperationBinding binding) + { + if (binding == null) + { + return; + } + + this.visitor.Visit(binding); } internal void Walk(AsyncApiBindings messageBindings, bool isComponent = false) @@ -592,6 +682,25 @@ internal void Walk(AsyncApiBindings messageBindings, bool isCom } this.visitor.Visit(messageBindings); + if (messageBindings != null) + { + foreach (var binding in messageBindings) + { + this.visitor.CurrentKeys.MessageBinding = binding.Key; + this.Walk(binding.Key, () => this.Walk(binding.Value)); + this.visitor.CurrentKeys.MessageBinding = null; + } + } + } + + internal void Walk(IMessageBinding binding) + { + if (binding == null) + { + return; + } + + this.visitor.Visit(binding); } internal void Walk(IList examples) diff --git a/src/LEGO.AsyncAPI/Services/CurrentKeys.cs b/src/LEGO.AsyncAPI/Services/CurrentKeys.cs index 3544eb21..f610aabb 100644 --- a/src/LEGO.AsyncAPI/Services/CurrentKeys.cs +++ b/src/LEGO.AsyncAPI/Services/CurrentKeys.cs @@ -4,7 +4,13 @@ namespace LEGO.AsyncAPI.Services { public class CurrentKeys { - public string ServerBindings { get; internal set; } + public string ServerBinding { get; internal set; } + + public string ChannelBinding { get; internal set; } + + public string OperationBinding { get; internal set; } + + public string MessageBinding { get; internal set; } public string Channel { get; internal set; } diff --git a/src/LEGO.AsyncAPI/Validation/AsyncApiValidator.cs b/src/LEGO.AsyncAPI/Validation/AsyncApiValidator.cs index 4ce96627..3f8bf392 100644 --- a/src/LEGO.AsyncAPI/Validation/AsyncApiValidator.cs +++ b/src/LEGO.AsyncAPI/Validation/AsyncApiValidator.cs @@ -136,6 +136,14 @@ public void AddWarning(AsyncApiValidatorWarning warning) /// The object to be validated. public override void Visit(AsyncApiServer item) => this.Validate(item); + public override void Visit(IServerBinding item) => this.Validate(item); + + public override void Visit(IChannelBinding item) => this.Validate(item); + + public override void Visit(IOperationBinding item) => this.Validate(item); + + public override void Visit(IMessageBinding item) => this.Validate(item); + /// /// Execute validation rules against an . /// From ba16297b2e4ba96b4259a2e85033894360593405 Mon Sep 17 00:00:00 2001 From: "lego-10-01-06[bot]" <119427331+lego-10-01-06[bot]@users.noreply.github.com> Date: Mon, 29 Jul 2024 11:59:34 +0000 Subject: [PATCH 05/18] chore: update CHANGELOG.md --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c2b85fba..e8d4454c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [5.2.3](https://github.com/LEGO/AsyncAPI.NET/compare/v5.2.2...v5.2.3) (2024-07-29) + + +### Bug Fixes + +* add missing walk and visit methods for bindings. ([#191](https://github.com/LEGO/AsyncAPI.NET/issues/191)) ([b8307c5](https://github.com/LEGO/AsyncAPI.NET/commit/b8307c57a6f9bc7c546702c24dffdfb1833aa5d3)) + ## [5.2.2](https://github.com/LEGO/AsyncAPI.NET/compare/v5.2.1...v5.2.2) (2024-07-29) From 52165f213502d9436e25a4e761804b5796b5de8c Mon Sep 17 00:00:00 2001 From: VisualBean Date: Mon, 29 Jul 2024 14:54:58 +0200 Subject: [PATCH 06/18] fix: remove persistence nullability --- .../Pulsar/PulsarChannelBinding.cs | 2 +- .../Bindings/Pulsar/PulsarBindings_Should.cs | 27 ------------------- 2 files changed, 1 insertion(+), 28 deletions(-) diff --git a/src/LEGO.AsyncAPI.Bindings/Pulsar/PulsarChannelBinding.cs b/src/LEGO.AsyncAPI.Bindings/Pulsar/PulsarChannelBinding.cs index c673a8e8..88435b74 100644 --- a/src/LEGO.AsyncAPI.Bindings/Pulsar/PulsarChannelBinding.cs +++ b/src/LEGO.AsyncAPI.Bindings/Pulsar/PulsarChannelBinding.cs @@ -19,7 +19,7 @@ public class PulsarChannelBinding : ChannelBinding /// /// persistence of the topic in Pulsar persistent or non-persistent. /// - public Persistence? Persistence { get; set; } + public Persistence Persistence { get; set; } /// /// Topic compaction threshold given in bytes. diff --git a/test/LEGO.AsyncAPI.Tests/Bindings/Pulsar/PulsarBindings_Should.cs b/test/LEGO.AsyncAPI.Tests/Bindings/Pulsar/PulsarBindings_Should.cs index e8a80ac1..7e69a0b7 100644 --- a/test/LEGO.AsyncAPI.Tests/Bindings/Pulsar/PulsarBindings_Should.cs +++ b/test/LEGO.AsyncAPI.Tests/Bindings/Pulsar/PulsarBindings_Should.cs @@ -88,33 +88,6 @@ public void PulsarChannelBindingNamespaceDefaultToNull() Assert.AreEqual(null, ((PulsarChannelBinding)binding.Bindings["pulsar"]).Namespace); } - [Test] - public void PulsarChannelBindingPropertiesExceptNamespaceDefaultToNull() - { - // Arrange - var actual = - """ - bindings: - pulsar: - namespace: staging - """; - - // Act - // Assert - var settings = new AsyncApiReaderSettings(); - settings.Bindings = BindingsCollection.Pulsar; - var binding = new AsyncApiStringReader(settings).ReadFragment(actual, AsyncApiVersion.AsyncApi2_0, out _); - var pulsarBinding = ((PulsarChannelBinding)binding.Bindings["pulsar"]); - - Assert.AreEqual("staging", pulsarBinding.Namespace); - Assert.AreEqual(null, pulsarBinding.Persistence); - Assert.AreEqual(null, pulsarBinding.Compaction); - Assert.AreEqual(null, pulsarBinding.GeoReplication); - Assert.AreEqual(null, pulsarBinding.Retention); - Assert.AreEqual(null, pulsarBinding.TTL); - Assert.AreEqual(null, pulsarBinding.Deduplication); - } - [Test] public void PulsarServerBinding_WithFilledObject_SerializesAndDeserializes() { From c90f41ddee9cb3b358469c17abe932caa6cce0db Mon Sep 17 00:00:00 2001 From: "lego-10-01-06[bot]" <119427331+lego-10-01-06[bot]@users.noreply.github.com> Date: Mon, 29 Jul 2024 13:02:21 +0000 Subject: [PATCH 07/18] chore: update CHANGELOG.md --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e8d4454c..7dea98ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [5.2.4](https://github.com/LEGO/AsyncAPI.NET/compare/v5.2.3...v5.2.4) (2024-07-29) + + +### Bug Fixes + +* remove persistence nullability ([52165f2](https://github.com/LEGO/AsyncAPI.NET/commit/52165f213502d9436e25a4e761804b5796b5de8c)) + ## [5.2.3](https://github.com/LEGO/AsyncAPI.NET/compare/v5.2.2...v5.2.3) (2024-07-29) From 129622a33c0428e172bdeebce6819b0802d871f1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Aug 2024 11:09:32 +0200 Subject: [PATCH 08/18] chore(deps): bump System.Text.Json from 8.0.2 to 8.0.4 in /src/LEGO.AsyncAPI (#189) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Alex Wichmann --- src/LEGO.AsyncAPI/LEGO.AsyncAPI.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/LEGO.AsyncAPI/LEGO.AsyncAPI.csproj b/src/LEGO.AsyncAPI/LEGO.AsyncAPI.csproj index 560c0f98..99e69016 100644 --- a/src/LEGO.AsyncAPI/LEGO.AsyncAPI.csproj +++ b/src/LEGO.AsyncAPI/LEGO.AsyncAPI.csproj @@ -19,7 +19,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + <_Parameter1>$(MSBuildProjectName).Tests From 9cc759fde50fcc02185ddd9200ce8ab0b3f80584 Mon Sep 17 00:00:00 2001 From: Adam Gloyne <44494964+Gadam8@users.noreply.github.com> Date: Thu, 15 Aug 2024 16:44:45 +0100 Subject: [PATCH 09/18] feat: extend AWS policy (#187) --- src/LEGO.AsyncAPI.Bindings/Sns/Principal.cs | 63 ++++++++++++ .../Sns/PrincipalObject.cs | 27 ++++++ .../Sns/PrincipalStar.cs | 24 +++++ .../Sns/SnsChannelBinding.cs | 4 +- src/LEGO.AsyncAPI.Bindings/Sns/Statement.cs | 26 +++-- src/LEGO.AsyncAPI.Bindings/Sqs/Principal.cs | 65 +++++++++++++ .../Sqs/PrincipalObject.cs | 27 ++++++ .../Sqs/PrincipalStar.cs | 24 +++++ .../Sqs/SqsChannelBinding.cs | 4 +- .../Sqs/SqsOperationBinding.cs | 4 +- src/LEGO.AsyncAPI.Bindings/Sqs/Statement.cs | 25 ++++- .../StringOrStringList.cs | 4 +- .../Bindings/Sns/SnsBindings_Should.cs | 57 ++++++++--- .../Bindings/Sqs/SqsBindings_should.cs | 96 ++++++++++++++----- 14 files changed, 395 insertions(+), 55 deletions(-) create mode 100644 src/LEGO.AsyncAPI.Bindings/Sns/Principal.cs create mode 100644 src/LEGO.AsyncAPI.Bindings/Sns/PrincipalObject.cs create mode 100644 src/LEGO.AsyncAPI.Bindings/Sns/PrincipalStar.cs create mode 100644 src/LEGO.AsyncAPI.Bindings/Sqs/Principal.cs create mode 100644 src/LEGO.AsyncAPI.Bindings/Sqs/PrincipalObject.cs create mode 100644 src/LEGO.AsyncAPI.Bindings/Sqs/PrincipalStar.cs diff --git a/src/LEGO.AsyncAPI.Bindings/Sns/Principal.cs b/src/LEGO.AsyncAPI.Bindings/Sns/Principal.cs new file mode 100644 index 00000000..a803f6c2 --- /dev/null +++ b/src/LEGO.AsyncAPI.Bindings/Sns/Principal.cs @@ -0,0 +1,63 @@ +namespace LEGO.AsyncAPI.Bindings.Sns; + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.Json; +using System.Text.Json.Nodes; +using LEGO.AsyncAPI.Models.Interfaces; +using LEGO.AsyncAPI.Readers.ParseNodes; +using LEGO.AsyncAPI.Writers; + +public abstract class Principal : IAsyncApiElement +{ + public abstract void Serialize(IAsyncApiWriter writer); + + public static Principal Parse(ParseNode node) + { + switch (node) + { + case ValueNode: + var nodeValue = node.GetScalarValue(); + if (!IsStarString(nodeValue)) + { + throw new ArgumentException($"An error occured while parsing a {nameof(Principal)} node. " + + $"Principal value without a property name can only be a string value of '*'."); + } + + return new PrincipalStar(); + + case MapNode mapNode: + { + var propertyNode = mapNode.First(); + if (!IsValidPrincipalProperty(propertyNode.Name)) + { + throw new ArgumentException($"An error occured while parsing a {nameof(Principal)} node. " + + $"Node should contain a valid AWS principal property name."); + } + + var principalValue = new KeyValuePair( + propertyNode.Name, + StringOrStringList.Parse(propertyNode.Value)); + + return new PrincipalObject(principalValue); + } + + default: + throw new ArgumentException($"An error occured while parsing a {nameof(Principal)} node. " + + $"Node should contain a string value of '*' or a valid AWS principal property."); + } + } + + private static bool IsStarString(JsonNode value) + { + var element = JsonDocument.Parse(value.ToJsonString()).RootElement; + + return element.ValueKind == JsonValueKind.String && element.ValueEquals("*"); + } + + private static bool IsValidPrincipalProperty(string property) + { + return new[] { "AWS", "Service" }.Contains(property); + } +} \ No newline at end of file diff --git a/src/LEGO.AsyncAPI.Bindings/Sns/PrincipalObject.cs b/src/LEGO.AsyncAPI.Bindings/Sns/PrincipalObject.cs new file mode 100644 index 00000000..a25c198f --- /dev/null +++ b/src/LEGO.AsyncAPI.Bindings/Sns/PrincipalObject.cs @@ -0,0 +1,27 @@ +namespace LEGO.AsyncAPI.Bindings.Sns; + +using System; +using System.Collections.Generic; +using LEGO.AsyncAPI.Writers; + +public class PrincipalObject : Principal +{ + private KeyValuePair PrincipalValue; + + public PrincipalObject(KeyValuePair principalValue) + { + this.PrincipalValue = principalValue; + } + + public override void Serialize(IAsyncApiWriter writer) + { + if (writer is null) + { + throw new ArgumentNullException(nameof(writer)); + } + + writer.WriteStartObject(); + writer.WriteRequiredObject(this.PrincipalValue.Key, this.PrincipalValue.Value, (w, t) => t.Value.Write(w)); + writer.WriteEndObject(); + } +} \ No newline at end of file diff --git a/src/LEGO.AsyncAPI.Bindings/Sns/PrincipalStar.cs b/src/LEGO.AsyncAPI.Bindings/Sns/PrincipalStar.cs new file mode 100644 index 00000000..533e9fb7 --- /dev/null +++ b/src/LEGO.AsyncAPI.Bindings/Sns/PrincipalStar.cs @@ -0,0 +1,24 @@ +namespace LEGO.AsyncAPI.Bindings.Sns; + +using System; +using LEGO.AsyncAPI.Writers; + +public class PrincipalStar : Principal +{ + private string PrincipalValue; + + public PrincipalStar() + { + this.PrincipalValue = "*"; + } + + public override void Serialize(IAsyncApiWriter writer) + { + if (writer is null) + { + throw new ArgumentNullException(nameof(writer)); + } + + writer.WriteValue(this.PrincipalValue); + } +} \ No newline at end of file diff --git a/src/LEGO.AsyncAPI.Bindings/Sns/SnsChannelBinding.cs b/src/LEGO.AsyncAPI.Bindings/Sns/SnsChannelBinding.cs index 13676168..4d8668c9 100644 --- a/src/LEGO.AsyncAPI.Bindings/Sns/SnsChannelBinding.cs +++ b/src/LEGO.AsyncAPI.Bindings/Sns/SnsChannelBinding.cs @@ -57,8 +57,10 @@ public class SnsChannelBinding : ChannelBinding private static FixedFieldMap statementFixedFields = new() { { "effect", (a, n) => { a.Effect = n.GetScalarValue().GetEnumFromDisplayName(); } }, - { "principal", (a, n) => { a.Principal = StringOrStringList.Parse(n); } }, + { "principal", (a, n) => { a.Principal = Principal.Parse(n); } }, { "action", (a, n) => { a.Action = StringOrStringList.Parse(n); } }, + { "resource", (a, n) => { a.Resource = StringOrStringList.Parse(n); } }, + { "condition", (a, n) => { a.Condition = n.CreateAny(); } }, }; /// diff --git a/src/LEGO.AsyncAPI.Bindings/Sns/Statement.cs b/src/LEGO.AsyncAPI.Bindings/Sns/Statement.cs index 7f3771f0..170fe371 100644 --- a/src/LEGO.AsyncAPI.Bindings/Sns/Statement.cs +++ b/src/LEGO.AsyncAPI.Bindings/Sns/Statement.cs @@ -1,28 +1,40 @@ // Copyright (c) The LEGO Group. All rights reserved. - namespace LEGO.AsyncAPI.Bindings.Sns { using System; using System.Collections.Generic; using LEGO.AsyncAPI.Attributes; + using LEGO.AsyncAPI.Models; using LEGO.AsyncAPI.Models.Interfaces; using LEGO.AsyncAPI.Writers; public class Statement : IAsyncApiExtensible { + /// + /// Indicates whether the policy allows or denies access. + /// public Effect Effect { get; set; } /// - /// The AWS account or resource ARN that this statement applies to. + /// The AWS account(s) or resource ARN(s) that this statement applies to. /// - // public StringOrStringList Principal { get; set; } - public StringOrStringList Principal { get; set; } + public Principal Principal { get; set; } /// - /// The SNS permission being allowed or denied e.g. sns:Publish + /// The SNS permission being allowed or denied e.g. sns:Publish. /// public StringOrStringList Action { get; set; } + /// + /// The resource(s) that this policy applies to. + /// + public StringOrStringList? Resource { get; set; } + + /// + /// Specific circumstances under which the policy grants permission. + /// + public AsyncApiAny? Condition { get; set; } + public IDictionary Extensions { get; set; } = new Dictionary(); public void Serialize(IAsyncApiWriter writer) @@ -34,8 +46,10 @@ public void Serialize(IAsyncApiWriter writer) writer.WriteStartObject(); writer.WriteRequiredProperty("effect", this.Effect.GetDisplayName()); - writer.WriteRequiredObject("principal", this.Principal, (w, t) => t.Value.Write(w)); + writer.WriteRequiredObject("principal", this.Principal, (w, t) => t.Serialize(w)); writer.WriteRequiredObject("action", this.Action, (w, t) => t.Value.Write(w)); + writer.WriteOptionalObject("resource", this.Resource, (w, t) => t?.Value.Write(w)); + writer.WriteOptionalObject("condition", this.Condition, (w, t) => t?.Write(w)); writer.WriteExtensions(this.Extensions); writer.WriteEndObject(); } diff --git a/src/LEGO.AsyncAPI.Bindings/Sqs/Principal.cs b/src/LEGO.AsyncAPI.Bindings/Sqs/Principal.cs new file mode 100644 index 00000000..2821f952 --- /dev/null +++ b/src/LEGO.AsyncAPI.Bindings/Sqs/Principal.cs @@ -0,0 +1,65 @@ +// Copyright (c) The LEGO Group. All rights reserved. + +namespace LEGO.AsyncAPI.Bindings.Sqs; + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.Json; +using System.Text.Json.Nodes; +using LEGO.AsyncAPI.Models.Interfaces; +using LEGO.AsyncAPI.Readers.ParseNodes; +using LEGO.AsyncAPI.Writers; + +public abstract class Principal : IAsyncApiElement +{ + public abstract void Serialize(IAsyncApiWriter writer); + + public static Principal Parse(ParseNode node) + { + switch (node) + { + case ValueNode: + var nodeValue = node.GetScalarValue(); + if (!IsStarString(nodeValue)) + { + throw new ArgumentException($"An error occured while parsing a {nameof(Principal)} node. " + + $"Principal value without a property name can only be a string value of '*'."); + } + + return new PrincipalStar(); + + case MapNode mapNode: + { + var propertyNode = mapNode.First(); + if (!IsValidPrincipalProperty(propertyNode.Name)) + { + throw new ArgumentException($"An error occured while parsing a {nameof(Principal)} node. " + + $"Node should contain a valid AWS principal property name."); + } + + var principalValue = new KeyValuePair( + propertyNode.Name, + StringOrStringList.Parse(propertyNode.Value)); + + return new PrincipalObject(principalValue); + } + + default: + throw new ArgumentException($"An error occured while parsing a {nameof(Principal)} node. " + + $"Node should contain a string value of '*' or a valid AWS principal property."); + } + } + + private static bool IsStarString(JsonNode value) + { + var element = JsonDocument.Parse(value.ToJsonString()).RootElement; + + return element.ValueKind == JsonValueKind.String && element.ValueEquals("*"); + } + + private static bool IsValidPrincipalProperty(string property) + { + return new[] { "AWS", "Service" }.Contains(property); + } +} \ No newline at end of file diff --git a/src/LEGO.AsyncAPI.Bindings/Sqs/PrincipalObject.cs b/src/LEGO.AsyncAPI.Bindings/Sqs/PrincipalObject.cs new file mode 100644 index 00000000..2652060d --- /dev/null +++ b/src/LEGO.AsyncAPI.Bindings/Sqs/PrincipalObject.cs @@ -0,0 +1,27 @@ +namespace LEGO.AsyncAPI.Bindings.Sqs; + +using System; +using System.Collections.Generic; +using LEGO.AsyncAPI.Writers; + +public class PrincipalObject : Principal +{ + private KeyValuePair PrincipalValue; + + public PrincipalObject(KeyValuePair principalValue) + { + this.PrincipalValue = principalValue; + } + + public override void Serialize(IAsyncApiWriter writer) + { + if (writer is null) + { + throw new ArgumentNullException(nameof(writer)); + } + + writer.WriteStartObject(); + writer.WriteRequiredObject(this.PrincipalValue.Key, this.PrincipalValue.Value, (w, t) => t.Value.Write(w)); + writer.WriteEndObject(); + } +} \ No newline at end of file diff --git a/src/LEGO.AsyncAPI.Bindings/Sqs/PrincipalStar.cs b/src/LEGO.AsyncAPI.Bindings/Sqs/PrincipalStar.cs new file mode 100644 index 00000000..9e54bc5a --- /dev/null +++ b/src/LEGO.AsyncAPI.Bindings/Sqs/PrincipalStar.cs @@ -0,0 +1,24 @@ +namespace LEGO.AsyncAPI.Bindings.Sqs; + +using System; +using LEGO.AsyncAPI.Writers; + +public class PrincipalStar : Principal +{ + private string PrincipalValue; + + public PrincipalStar() + { + this.PrincipalValue = "*"; + } + + public override void Serialize(IAsyncApiWriter writer) + { + if (writer is null) + { + throw new ArgumentNullException(nameof(writer)); + } + + writer.WriteValue(this.PrincipalValue); + } +} \ No newline at end of file diff --git a/src/LEGO.AsyncAPI.Bindings/Sqs/SqsChannelBinding.cs b/src/LEGO.AsyncAPI.Bindings/Sqs/SqsChannelBinding.cs index 6f98da99..f0b24be7 100644 --- a/src/LEGO.AsyncAPI.Bindings/Sqs/SqsChannelBinding.cs +++ b/src/LEGO.AsyncAPI.Bindings/Sqs/SqsChannelBinding.cs @@ -64,8 +64,10 @@ public class SqsChannelBinding : ChannelBinding private static FixedFieldMap statementFixedFields = new() { { "effect", (a, n) => { a.Effect = n.GetScalarValue().GetEnumFromDisplayName(); } }, - { "principal", (a, n) => { a.Principal = StringOrStringList.Parse(n); } }, + { "principal", (a, n) => { a.Principal = Principal.Parse(n); } }, { "action", (a, n) => { a.Action = StringOrStringList.Parse(n); } }, + { "resource", (a, n) => { a.Resource = StringOrStringList.Parse(n); } }, + { "condition", (a, n) => { a.Condition = n.CreateAny(); } }, }; public override void SerializeProperties(IAsyncApiWriter writer) diff --git a/src/LEGO.AsyncAPI.Bindings/Sqs/SqsOperationBinding.cs b/src/LEGO.AsyncAPI.Bindings/Sqs/SqsOperationBinding.cs index d8eb43dd..ed278013 100644 --- a/src/LEGO.AsyncAPI.Bindings/Sqs/SqsOperationBinding.cs +++ b/src/LEGO.AsyncAPI.Bindings/Sqs/SqsOperationBinding.cs @@ -56,8 +56,10 @@ public class SqsOperationBinding : OperationBinding private static FixedFieldMap statementFixedFields = new() { { "effect", (a, n) => { a.Effect = n.GetScalarValue().GetEnumFromDisplayName(); } }, - { "principal", (a, n) => { a.Principal = StringOrStringList.Parse(n); } }, + { "principal", (a, n) => { a.Principal = Principal.Parse(n); } }, { "action", (a, n) => { a.Action = StringOrStringList.Parse(n); } }, + { "resource", (a, n) => { a.Resource = StringOrStringList.Parse(n); } }, + { "condition", (a, n) => { a.Condition = n.CreateAny(); } }, }; public override void SerializeProperties(IAsyncApiWriter writer) diff --git a/src/LEGO.AsyncAPI.Bindings/Sqs/Statement.cs b/src/LEGO.AsyncAPI.Bindings/Sqs/Statement.cs index 9518d2d4..4abc05a6 100644 --- a/src/LEGO.AsyncAPI.Bindings/Sqs/Statement.cs +++ b/src/LEGO.AsyncAPI.Bindings/Sqs/Statement.cs @@ -5,24 +5,37 @@ namespace LEGO.AsyncAPI.Bindings.Sqs using System; using System.Collections.Generic; using LEGO.AsyncAPI.Attributes; + using LEGO.AsyncAPI.Models; using LEGO.AsyncAPI.Models.Interfaces; using LEGO.AsyncAPI.Writers; public class Statement : IAsyncApiExtensible { + /// + /// Indicates whether the policy allows or denies access. + /// public Effect Effect { get; set; } /// - /// The AWS account or resource ARN that this statement applies to. + /// The AWS account(s) or resource ARN(s) that this statement applies to. /// - // public StringOrStringList Principal { get; set; } - public StringOrStringList Principal { get; set; } + public Principal Principal { get; set; } /// - /// The SNS permission being allowed or denied e.g. sns:Publish + /// The SNS permission being allowed or denied e.g. sns:Publish. /// public StringOrStringList Action { get; set; } + /// + /// The resource(s) that this policy applies to. + /// + public StringOrStringList? Resource { get; set; } + + /// + /// Specific circumstances under which the policy grants permission. + /// + public AsyncApiAny? Condition { get; set; } + public IDictionary Extensions { get; set; } = new Dictionary(); public void Serialize(IAsyncApiWriter writer) @@ -34,8 +47,10 @@ public void Serialize(IAsyncApiWriter writer) writer.WriteStartObject(); writer.WriteRequiredProperty("effect", this.Effect.GetDisplayName()); - writer.WriteRequiredObject("principal", this.Principal, (w, t) => t.Value.Write(w)); + writer.WriteRequiredObject("principal", this.Principal, (w, t) => t.Serialize(w)); writer.WriteRequiredObject("action", this.Action, (w, t) => t.Value.Write(w)); + writer.WriteOptionalObject("resource", this.Resource, (w, t) => t?.Value.Write(w)); + writer.WriteOptionalObject("condition", this.Condition, (w, t) => t?.Write(w)); writer.WriteExtensions(this.Extensions); writer.WriteEndObject(); } diff --git a/src/LEGO.AsyncAPI.Bindings/StringOrStringList.cs b/src/LEGO.AsyncAPI.Bindings/StringOrStringList.cs index 6be69094..b9946f08 100644 --- a/src/LEGO.AsyncAPI.Bindings/StringOrStringList.cs +++ b/src/LEGO.AsyncAPI.Bindings/StringOrStringList.cs @@ -30,10 +30,10 @@ public static StringOrStringList Parse(ParseNode node) { case ValueNode: return new StringOrStringList(new AsyncApiAny(node.GetScalarValue())); - case ListNode: + case ListNode listNode: { var jsonArray = new JsonArray(); - foreach (var item in node as ListNode) + foreach (var item in listNode) { jsonArray.Add(item.GetScalarValue()); } diff --git a/test/LEGO.AsyncAPI.Tests/Bindings/Sns/SnsBindings_Should.cs b/test/LEGO.AsyncAPI.Tests/Bindings/Sns/SnsBindings_Should.cs index 6d4f5779..fbb3622e 100644 --- a/test/LEGO.AsyncAPI.Tests/Bindings/Sns/SnsBindings_Should.cs +++ b/test/LEGO.AsyncAPI.Tests/Bindings/Sns/SnsBindings_Should.cs @@ -31,15 +31,24 @@ public void SnsChannelBinding_WithFilledObject_SerializesAndDeserializes() policy: statements: - effect: Deny - principal: arn:aws:iam::123456789012:user/alex.wichmann + principal: '*' action: - sns:Publish - sns:Delete + condition: + StringEquals: + aws:username: + - johndoe + - mrsmith - effect: Allow principal: - - arn:aws:iam::123456789012:user/alex.wichmann - - arn:aws:iam::123456789012:user/dec.kolakowski + AWS: + - arn:aws:iam::123456789012:user/alex.wichmann + - arn:aws:iam::123456789012:user/dec.kolakowski action: sns:Create + condition: + NumericLessThanEquals: + aws:MultiFactorAuthAge: '3600' x-statementExtension: statementXPropertyName: statementXPropertyValue x-policyExtension: @@ -77,22 +86,38 @@ public void SnsChannelBinding_WithFilledObject_SerializesAndDeserializes() new Statement() { Effect = Effect.Deny, - Principal = new StringOrStringList(new AsyncApiAny("arn:aws:iam::123456789012:user/alex.wichmann")), + Principal = new PrincipalStar(), Action = new StringOrStringList(new AsyncApiAny(new List() { "sns:Publish", "sns:Delete", })), + Condition = new AsyncApiAny(new Dictionary() + { + { + "StringEquals", new Dictionary>() + { + { "aws:username", new List() { "johndoe", "mrsmith" } }, + } + }, + }), }, new Statement() { Effect = Effect.Allow, - Principal = new StringOrStringList(new AsyncApiAny(new List() - { - "arn:aws:iam::123456789012:user/alex.wichmann", - "arn:aws:iam::123456789012:user/dec.kolakowski", - })), + Principal = new PrincipalObject(new KeyValuePair( + "AWS", new StringOrStringList(new AsyncApiAny(new List + { "arn:aws:iam::123456789012:user/alex.wichmann", "arn:aws:iam::123456789012:user/dec.kolakowski" })))), Action = new StringOrStringList(new AsyncApiAny("sns:Create")), + Condition = new AsyncApiAny(new Dictionary() + { + { + "NumericLessThanEquals", new Dictionary() + { + { "aws:MultiFactorAuthAge", "3600" }, + } + }, + }), Extensions = new Dictionary() { { @@ -137,8 +162,11 @@ public void SnsChannelBinding_WithFilledObject_SerializesAndDeserializes() var actual = channel.SerializeAsYaml(AsyncApiVersion.AsyncApi2_0); // Assert - var settings = new AsyncApiReaderSettings(); - settings.Bindings = BindingsCollection.Sns; + var settings = new AsyncApiReaderSettings + { + Bindings = BindingsCollection.Sns, + }; + var binding = new AsyncApiStringReader(settings).ReadFragment(actual, AsyncApiVersion.AsyncApi2_0, out _); // Assert @@ -381,8 +409,11 @@ public void SnsOperationBinding_WithFilledObject_SerializesAndDeserializes() var actual = operation.SerializeAsYaml(AsyncApiVersion.AsyncApi2_0); // Assert - var settings = new AsyncApiReaderSettings(); - settings.Bindings = BindingsCollection.Sns; + var settings = new AsyncApiReaderSettings + { + Bindings = BindingsCollection.Sns, + }; + var binding = new AsyncApiStringReader(settings).ReadFragment(actual, AsyncApiVersion.AsyncApi2_0, out _); var binding2 = new AsyncApiStringReader(settings).ReadFragment(expected, AsyncApiVersion.AsyncApi2_0, out _); binding2.Bindings.First().Value.Extensions.TryGetValue("x-bindingExtension", out IAsyncApiExtension any); diff --git a/test/LEGO.AsyncAPI.Tests/Bindings/Sqs/SqsBindings_should.cs b/test/LEGO.AsyncAPI.Tests/Bindings/Sqs/SqsBindings_should.cs index c031621c..3a0337a3 100644 --- a/test/LEGO.AsyncAPI.Tests/Bindings/Sqs/SqsBindings_should.cs +++ b/test/LEGO.AsyncAPI.Tests/Bindings/Sqs/SqsBindings_should.cs @@ -3,6 +3,7 @@ namespace LEGO.AsyncAPI.Tests.Bindings.Sqs { using System.Collections.Generic; + using System.Linq; using FluentAssertions; using LEGO.AsyncAPI.Bindings; using LEGO.AsyncAPI.Bindings.Sqs; @@ -42,17 +43,27 @@ public void SqsChannelBinding_WithFilledObject_SerializesAndDeserializes() policy: statements: - effect: deny - principal: arn:aws:iam::123456789012:user/alex.wichmann + principal: + AWS: arn:aws:iam::123456789012:user/alex.wichmann action: - sqs:SendMessage - sqs:ReceiveMessage + condition: + StringEquals: + aws:username: + - johndoe + - mrsmith x-statementExtension: statementXPropertyName: statementXPropertyValue - effect: allow principal: - - arn:aws:iam::123456789012:user/alex.wichmann - - arn:aws:iam::123456789012:user/dec.kolakowski + AWS: + - arn:aws:iam::123456789012:user/alex.wichmann + - arn:aws:iam::123456789012:user/dec.kolakowski action: sqs:CreateQueue + condition: + NumericLessThanEquals: + aws:MultiFactorAuthAge: '3600' x-policyExtension: policyXPropertyName: policyXPropertyValue tags: @@ -69,7 +80,8 @@ public void SqsChannelBinding_WithFilledObject_SerializesAndDeserializes() policy: statements: - effect: allow - principal: arn:aws:iam::123456789012:user/alex.wichmann + principal: + Service: s3.amazonaws.com action: - sqs:* x-internalObject: @@ -124,12 +136,22 @@ public void SqsChannelBinding_WithFilledObject_SerializesAndDeserializes() new Statement() { Effect = Effect.Deny, - Principal = new StringOrStringList(new AsyncApiAny("arn:aws:iam::123456789012:user/alex.wichmann")), + Principal = new PrincipalObject(new KeyValuePair( + "AWS", new StringOrStringList(new AsyncApiAny("arn:aws:iam::123456789012:user/alex.wichmann")))), Action = new StringOrStringList(new AsyncApiAny(new List { "sqs:SendMessage", "sqs:ReceiveMessage", })), + Condition = new AsyncApiAny(new Dictionary() + { + { + "StringEquals", new Dictionary>() + { + { "aws:username", new List() { "johndoe", "mrsmith" } }, + } + }, + }), Extensions = new Dictionary() { { @@ -144,12 +166,19 @@ public void SqsChannelBinding_WithFilledObject_SerializesAndDeserializes() new Statement() { Effect = Effect.Allow, - Principal = new StringOrStringList(new AsyncApiAny(new List - { - "arn:aws:iam::123456789012:user/alex.wichmann", - "arn:aws:iam::123456789012:user/dec.kolakowski", - })), + Principal = new PrincipalObject(new KeyValuePair( + "AWS", new StringOrStringList(new AsyncApiAny(new List + { "arn:aws:iam::123456789012:user/alex.wichmann", "arn:aws:iam::123456789012:user/dec.kolakowski" })))), Action = new StringOrStringList(new AsyncApiAny("sqs:CreateQueue")), + Condition = new AsyncApiAny(new Dictionary() + { + { + "NumericLessThanEquals", new Dictionary() + { + { "aws:MultiFactorAuthAge", "3600" }, + } + }, + }), }, }, Extensions = new Dictionary() @@ -194,7 +223,8 @@ public void SqsChannelBinding_WithFilledObject_SerializesAndDeserializes() new Statement() { Effect = Effect.Allow, - Principal = new StringOrStringList(new AsyncApiAny("arn:aws:iam::123456789012:user/alex.wichmann")), + Principal = new PrincipalObject(new KeyValuePair( + "Service", new StringOrStringList(new AsyncApiAny("s3.amazonaws.com")))), Action = new StringOrStringList(new AsyncApiAny(new List { "sqs:*", @@ -218,8 +248,10 @@ public void SqsChannelBinding_WithFilledObject_SerializesAndDeserializes() var actual = channel.SerializeAsYaml(AsyncApiVersion.AsyncApi2_0); // Assert - var settings = new AsyncApiReaderSettings(); - settings.Bindings = BindingsCollection.Sqs; + var settings = new AsyncApiReaderSettings + { + Bindings = BindingsCollection.Sqs, + }; var binding = new AsyncApiStringReader(settings).ReadFragment(actual, AsyncApiVersion.AsyncApi2_0, out _); @@ -227,6 +259,9 @@ public void SqsChannelBinding_WithFilledObject_SerializesAndDeserializes() actual.Should() .BePlatformAgnosticEquivalentTo(expected); binding.Should().BeEquivalentTo(channel); + + var expectedSqsBinding = (SqsChannelBinding)channel.Bindings.Values.First(); + expectedSqsBinding.Should().BeEquivalentTo((SqsChannelBinding)binding.Bindings.Values.First(), options => options.IgnoringCyclicReferences()); } [Test] @@ -254,7 +289,8 @@ public void SqsOperationBinding_WithFilledObject_SerializesAndDeserializes() policy: statements: - effect: deny - principal: arn:aws:iam::123456789012:user/alex.wichmann + principal: + AWS: arn:aws:iam::123456789012:user/alex.wichmann action: - sqs:SendMessage - sqs:ReceiveMessage @@ -262,8 +298,9 @@ public void SqsOperationBinding_WithFilledObject_SerializesAndDeserializes() statementXPropertyName: statementXPropertyValue - effect: allow principal: - - arn:aws:iam::123456789012:user/alex.wichmann - - arn:aws:iam::123456789012:user/dec.kolakowski + AWS: + - arn:aws:iam::123456789012:user/alex.wichmann + - arn:aws:iam::123456789012:user/dec.kolakowski action: sqs:CreateQueue x-policyExtension: policyXPropertyName: policyXPropertyValue @@ -280,7 +317,8 @@ public void SqsOperationBinding_WithFilledObject_SerializesAndDeserializes() policy: statements: - effect: allow - principal: arn:aws:iam::123456789012:user/alex.wichmann + principal: + AWS: arn:aws:iam::123456789012:user/alex.wichmann action: - sqs:* x-queueExtension: @@ -339,7 +377,8 @@ public void SqsOperationBinding_WithFilledObject_SerializesAndDeserializes() new Statement() { Effect = Effect.Deny, - Principal = new StringOrStringList(new AsyncApiAny("arn:aws:iam::123456789012:user/alex.wichmann")), + Principal = new PrincipalObject(new KeyValuePair( + "AWS", new StringOrStringList(new AsyncApiAny("arn:aws:iam::123456789012:user/alex.wichmann")))), Action = new StringOrStringList(new AsyncApiAny(new List() { "sqs:SendMessage", @@ -359,11 +398,9 @@ public void SqsOperationBinding_WithFilledObject_SerializesAndDeserializes() new Statement() { Effect = Effect.Allow, - Principal = new StringOrStringList(new AsyncApiAny(new List - { - "arn:aws:iam::123456789012:user/alex.wichmann", - "arn:aws:iam::123456789012:user/dec.kolakowski", - })), + Principal = new PrincipalObject(new KeyValuePair( + "AWS", new StringOrStringList(new AsyncApiAny(new List + { "arn:aws:iam::123456789012:user/alex.wichmann", "arn:aws:iam::123456789012:user/dec.kolakowski" })))), Action = new StringOrStringList(new AsyncApiAny("sqs:CreateQueue")), }, }, @@ -409,7 +446,8 @@ public void SqsOperationBinding_WithFilledObject_SerializesAndDeserializes() new Statement() { Effect = Effect.Allow, - Principal = new StringOrStringList(new AsyncApiAny("arn:aws:iam::123456789012:user/alex.wichmann")), + Principal = new PrincipalObject(new KeyValuePair( + "AWS", new StringOrStringList(new AsyncApiAny("arn:aws:iam::123456789012:user/alex.wichmann")))), Action = new StringOrStringList(new AsyncApiAny(new List { "sqs:*", @@ -444,14 +482,20 @@ public void SqsOperationBinding_WithFilledObject_SerializesAndDeserializes() var actual = operation.SerializeAsYaml(AsyncApiVersion.AsyncApi2_0); // Assert - var settings = new AsyncApiReaderSettings(); - settings.Bindings = BindingsCollection.Sqs; + var settings = new AsyncApiReaderSettings + { + Bindings = BindingsCollection.Sqs, + }; + var binding = new AsyncApiStringReader(settings).ReadFragment(actual, AsyncApiVersion.AsyncApi2_0, out _); // Assert actual.Should() .BePlatformAgnosticEquivalentTo(expected); binding.Should().BeEquivalentTo(operation); + + var expectedSqsBinding = (SqsOperationBinding)operation.Bindings.Values.First(); + expectedSqsBinding.Should().BeEquivalentTo((SqsOperationBinding)binding.Bindings.Values.First(), options => options.IgnoringCyclicReferences()); } } } \ No newline at end of file From 38390d87aefbf1707ec17514e721a034bff2f561 Mon Sep 17 00:00:00 2001 From: Alex Wichmann Date: Fri, 16 Aug 2024 13:14:18 +0200 Subject: [PATCH 10/18] Add beta link to bindings. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 593dbd88..ce65fd33 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ Install the NuGet packages: ### AsyncAPI.Bindings [![Nuget](https://img.shields.io/nuget/v/AsyncAPI.NET.Bindings?label=AsyncAPI.Bindings&style=for-the-badge)](https://www.nuget.org/packages/AsyncAPI.NET.Bindings/) - +[![Nuget](https://img.shields.io/nuget/vpre/AsyncAPI.NET.Bindings?label=AsyncAPI.Bindings-Preview&style=for-the-badge)](https://www.nuget.org/packages/AsyncAPI.NET.Bindings/) ## Example Usage Main classes to know: From 57c0c3365f4d215860d8b599fd9d18e6e5185d54 Mon Sep 17 00:00:00 2001 From: Adam Gloyne <44494964+Gadam8@users.noreply.github.com> Date: Tue, 20 Aug 2024 13:01:50 +0100 Subject: [PATCH 11/18] chore: make principal values public (#192) Co-authored-by: adam.gloyne --- src/LEGO.AsyncAPI.Bindings/Sns/PrincipalObject.cs | 8 ++++---- src/LEGO.AsyncAPI.Bindings/Sns/PrincipalStar.cs | 6 +++--- src/LEGO.AsyncAPI.Bindings/Sqs/PrincipalObject.cs | 8 ++++---- src/LEGO.AsyncAPI.Bindings/Sqs/PrincipalStar.cs | 6 +++--- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/LEGO.AsyncAPI.Bindings/Sns/PrincipalObject.cs b/src/LEGO.AsyncAPI.Bindings/Sns/PrincipalObject.cs index a25c198f..209be8bf 100644 --- a/src/LEGO.AsyncAPI.Bindings/Sns/PrincipalObject.cs +++ b/src/LEGO.AsyncAPI.Bindings/Sns/PrincipalObject.cs @@ -6,11 +6,11 @@ namespace LEGO.AsyncAPI.Bindings.Sns; public class PrincipalObject : Principal { - private KeyValuePair PrincipalValue; + public KeyValuePair Value { get; private set; } - public PrincipalObject(KeyValuePair principalValue) + public PrincipalObject(KeyValuePair value) { - this.PrincipalValue = principalValue; + this.Value = value; } public override void Serialize(IAsyncApiWriter writer) @@ -21,7 +21,7 @@ public override void Serialize(IAsyncApiWriter writer) } writer.WriteStartObject(); - writer.WriteRequiredObject(this.PrincipalValue.Key, this.PrincipalValue.Value, (w, t) => t.Value.Write(w)); + writer.WriteRequiredObject(this.Value.Key, this.Value.Value, (w, t) => t.Value.Write(w)); writer.WriteEndObject(); } } \ No newline at end of file diff --git a/src/LEGO.AsyncAPI.Bindings/Sns/PrincipalStar.cs b/src/LEGO.AsyncAPI.Bindings/Sns/PrincipalStar.cs index 533e9fb7..c885d252 100644 --- a/src/LEGO.AsyncAPI.Bindings/Sns/PrincipalStar.cs +++ b/src/LEGO.AsyncAPI.Bindings/Sns/PrincipalStar.cs @@ -5,11 +5,11 @@ namespace LEGO.AsyncAPI.Bindings.Sns; public class PrincipalStar : Principal { - private string PrincipalValue; + public string Value { get; private set; } public PrincipalStar() { - this.PrincipalValue = "*"; + this.Value = "*"; } public override void Serialize(IAsyncApiWriter writer) @@ -19,6 +19,6 @@ public override void Serialize(IAsyncApiWriter writer) throw new ArgumentNullException(nameof(writer)); } - writer.WriteValue(this.PrincipalValue); + writer.WriteValue(this.Value); } } \ No newline at end of file diff --git a/src/LEGO.AsyncAPI.Bindings/Sqs/PrincipalObject.cs b/src/LEGO.AsyncAPI.Bindings/Sqs/PrincipalObject.cs index 2652060d..61e6e546 100644 --- a/src/LEGO.AsyncAPI.Bindings/Sqs/PrincipalObject.cs +++ b/src/LEGO.AsyncAPI.Bindings/Sqs/PrincipalObject.cs @@ -6,11 +6,11 @@ namespace LEGO.AsyncAPI.Bindings.Sqs; public class PrincipalObject : Principal { - private KeyValuePair PrincipalValue; + public KeyValuePair Value { get; private set; } - public PrincipalObject(KeyValuePair principalValue) + public PrincipalObject(KeyValuePair value) { - this.PrincipalValue = principalValue; + this.Value = value; } public override void Serialize(IAsyncApiWriter writer) @@ -21,7 +21,7 @@ public override void Serialize(IAsyncApiWriter writer) } writer.WriteStartObject(); - writer.WriteRequiredObject(this.PrincipalValue.Key, this.PrincipalValue.Value, (w, t) => t.Value.Write(w)); + writer.WriteRequiredObject(this.Value.Key, this.Value.Value, (w, t) => t.Value.Write(w)); writer.WriteEndObject(); } } \ No newline at end of file diff --git a/src/LEGO.AsyncAPI.Bindings/Sqs/PrincipalStar.cs b/src/LEGO.AsyncAPI.Bindings/Sqs/PrincipalStar.cs index 9e54bc5a..1705b966 100644 --- a/src/LEGO.AsyncAPI.Bindings/Sqs/PrincipalStar.cs +++ b/src/LEGO.AsyncAPI.Bindings/Sqs/PrincipalStar.cs @@ -5,11 +5,11 @@ namespace LEGO.AsyncAPI.Bindings.Sqs; public class PrincipalStar : Principal { - private string PrincipalValue; + public string Value { get; private set; } public PrincipalStar() { - this.PrincipalValue = "*"; + this.Value = "*"; } public override void Serialize(IAsyncApiWriter writer) @@ -19,6 +19,6 @@ public override void Serialize(IAsyncApiWriter writer) throw new ArgumentNullException(nameof(writer)); } - writer.WriteValue(this.PrincipalValue); + writer.WriteValue(this.Value); } } \ No newline at end of file From 030d603673bbde25a3e6cdd99c63f45159f4e9fd Mon Sep 17 00:00:00 2001 From: Adam Gloyne <44494964+Gadam8@users.noreply.github.com> Date: Wed, 4 Sep 2024 17:36:37 +0100 Subject: [PATCH 12/18] chore: make aws condition value explicit (#193) Co-authored-by: adam.gloyne --- src/LEGO.AsyncAPI.Bindings/Sns/Condition.cs | 67 +++++++++++++++++++ .../Sns/SnsChannelBinding.cs | 2 +- src/LEGO.AsyncAPI.Bindings/Sns/Statement.cs | 4 +- src/LEGO.AsyncAPI.Bindings/Sqs/Condition.cs | 67 +++++++++++++++++++ .../Sqs/SqsChannelBinding.cs | 2 +- .../Sqs/SqsOperationBinding.cs | 2 +- src/LEGO.AsyncAPI.Bindings/Sqs/Statement.cs | 4 +- .../Bindings/Sns/SnsBindings_Should.cs | 16 +++-- .../Bindings/Sqs/SqsBindings_should.cs | 16 +++-- 9 files changed, 161 insertions(+), 19 deletions(-) create mode 100644 src/LEGO.AsyncAPI.Bindings/Sns/Condition.cs create mode 100644 src/LEGO.AsyncAPI.Bindings/Sqs/Condition.cs diff --git a/src/LEGO.AsyncAPI.Bindings/Sns/Condition.cs b/src/LEGO.AsyncAPI.Bindings/Sns/Condition.cs new file mode 100644 index 00000000..38b21da9 --- /dev/null +++ b/src/LEGO.AsyncAPI.Bindings/Sns/Condition.cs @@ -0,0 +1,67 @@ +// Copyright (c) The LEGO Group. All rights reserved. + +namespace LEGO.AsyncAPI.Bindings.Sns; + +using System; +using System.Collections.Generic; +using System.Linq; +using LEGO.AsyncAPI.Models.Interfaces; +using LEGO.AsyncAPI.Readers.ParseNodes; +using LEGO.AsyncAPI.Writers; + +public class Condition : IAsyncApiElement +{ + public Dictionary> Value { get; private set; } + + public Condition(Dictionary> value) + { + this.Value = value; + } + + public void Serialize(IAsyncApiWriter writer) + { + if (writer is null) + { + throw new ArgumentNullException(nameof(writer)); + } + + writer.WriteStartObject(); + foreach (var conditionValue in this.Value) + { + writer.WriteRequiredMap(conditionValue.Key, conditionValue.Value, (w, t) => t.Value.Write(w)); + } + + writer.WriteEndObject(); + } + + public static Condition Parse(ParseNode node) + { + switch (node) + { + case MapNode mapNode: + { + var conditionValues = new Dictionary>(); + foreach (var conditionNode in mapNode) + { + switch (conditionNode.Value) + { + case MapNode conditionValueNode: + conditionValues.Add(conditionNode.Name, new Dictionary(conditionValueNode.Select(x => + new KeyValuePair(x.Name, StringOrStringList.Parse(x.Value))) + .ToDictionary(x => x.Key, x => x.Value))); + break; + default: + throw new ArgumentException($"An error occured while parsing a {nameof(Condition)} node. " + + $"AWS condition values should be one or more key value pairs."); + } + } + + return new Condition(conditionValues); + } + + default: + throw new ArgumentException($"An error occured while parsing a {nameof(Condition)} node. " + + $"Node should contain a collection of condition types."); + } + } +} \ No newline at end of file diff --git a/src/LEGO.AsyncAPI.Bindings/Sns/SnsChannelBinding.cs b/src/LEGO.AsyncAPI.Bindings/Sns/SnsChannelBinding.cs index 4d8668c9..4394cdd1 100644 --- a/src/LEGO.AsyncAPI.Bindings/Sns/SnsChannelBinding.cs +++ b/src/LEGO.AsyncAPI.Bindings/Sns/SnsChannelBinding.cs @@ -60,7 +60,7 @@ public class SnsChannelBinding : ChannelBinding { "principal", (a, n) => { a.Principal = Principal.Parse(n); } }, { "action", (a, n) => { a.Action = StringOrStringList.Parse(n); } }, { "resource", (a, n) => { a.Resource = StringOrStringList.Parse(n); } }, - { "condition", (a, n) => { a.Condition = n.CreateAny(); } }, + { "condition", (a, n) => { a.Condition = Condition.Parse(n); } }, }; /// diff --git a/src/LEGO.AsyncAPI.Bindings/Sns/Statement.cs b/src/LEGO.AsyncAPI.Bindings/Sns/Statement.cs index 170fe371..da93cfbf 100644 --- a/src/LEGO.AsyncAPI.Bindings/Sns/Statement.cs +++ b/src/LEGO.AsyncAPI.Bindings/Sns/Statement.cs @@ -33,7 +33,7 @@ public class Statement : IAsyncApiExtensible /// /// Specific circumstances under which the policy grants permission. /// - public AsyncApiAny? Condition { get; set; } + public Condition Condition { get; set; } public IDictionary Extensions { get; set; } = new Dictionary(); @@ -49,7 +49,7 @@ public void Serialize(IAsyncApiWriter writer) writer.WriteRequiredObject("principal", this.Principal, (w, t) => t.Serialize(w)); writer.WriteRequiredObject("action", this.Action, (w, t) => t.Value.Write(w)); writer.WriteOptionalObject("resource", this.Resource, (w, t) => t?.Value.Write(w)); - writer.WriteOptionalObject("condition", this.Condition, (w, t) => t?.Write(w)); + writer.WriteOptionalObject("condition", this.Condition, (w, t) => t.Serialize(w)); writer.WriteExtensions(this.Extensions); writer.WriteEndObject(); } diff --git a/src/LEGO.AsyncAPI.Bindings/Sqs/Condition.cs b/src/LEGO.AsyncAPI.Bindings/Sqs/Condition.cs new file mode 100644 index 00000000..93bdf733 --- /dev/null +++ b/src/LEGO.AsyncAPI.Bindings/Sqs/Condition.cs @@ -0,0 +1,67 @@ +// Copyright (c) The LEGO Group. All rights reserved. + +namespace LEGO.AsyncAPI.Bindings.Sqs; + +using System; +using System.Collections.Generic; +using System.Linq; +using LEGO.AsyncAPI.Models.Interfaces; +using LEGO.AsyncAPI.Readers.ParseNodes; +using LEGO.AsyncAPI.Writers; + +public class Condition : IAsyncApiElement +{ + public Dictionary> Value { get; private set; } + + public Condition(Dictionary> value) + { + this.Value = value; + } + + public void Serialize(IAsyncApiWriter writer) + { + if (writer is null) + { + throw new ArgumentNullException(nameof(writer)); + } + + writer.WriteStartObject(); + foreach (var conditionValue in this.Value) + { + writer.WriteRequiredMap(conditionValue.Key, conditionValue.Value, (w, t) => t.Value.Write(w)); + } + + writer.WriteEndObject(); + } + + public static Condition Parse(ParseNode node) + { + switch (node) + { + case MapNode mapNode: + { + var conditionValues = new Dictionary>(); + foreach (var conditionNode in mapNode) + { + switch (conditionNode.Value) + { + case MapNode conditionValueNode: + conditionValues.Add(conditionNode.Name, new Dictionary(conditionValueNode.Select(x => + new KeyValuePair(x.Name, StringOrStringList.Parse(x.Value))) + .ToDictionary(x => x.Key, x => x.Value))); + break; + default: + throw new ArgumentException($"An error occured while parsing a {nameof(Condition)} node. " + + $"AWS condition values should be one or more key value pairs."); + } + } + + return new Condition(conditionValues); + } + + default: + throw new ArgumentException($"An error occured while parsing a {nameof(Condition)} node. " + + $"Node should contain a collection of AWS condition types."); + } + } +} \ No newline at end of file diff --git a/src/LEGO.AsyncAPI.Bindings/Sqs/SqsChannelBinding.cs b/src/LEGO.AsyncAPI.Bindings/Sqs/SqsChannelBinding.cs index f0b24be7..bd806071 100644 --- a/src/LEGO.AsyncAPI.Bindings/Sqs/SqsChannelBinding.cs +++ b/src/LEGO.AsyncAPI.Bindings/Sqs/SqsChannelBinding.cs @@ -67,7 +67,7 @@ public class SqsChannelBinding : ChannelBinding { "principal", (a, n) => { a.Principal = Principal.Parse(n); } }, { "action", (a, n) => { a.Action = StringOrStringList.Parse(n); } }, { "resource", (a, n) => { a.Resource = StringOrStringList.Parse(n); } }, - { "condition", (a, n) => { a.Condition = n.CreateAny(); } }, + { "condition", (a, n) => { a.Condition = Condition.Parse(n); } }, }; public override void SerializeProperties(IAsyncApiWriter writer) diff --git a/src/LEGO.AsyncAPI.Bindings/Sqs/SqsOperationBinding.cs b/src/LEGO.AsyncAPI.Bindings/Sqs/SqsOperationBinding.cs index ed278013..0beb89b8 100644 --- a/src/LEGO.AsyncAPI.Bindings/Sqs/SqsOperationBinding.cs +++ b/src/LEGO.AsyncAPI.Bindings/Sqs/SqsOperationBinding.cs @@ -59,7 +59,7 @@ public class SqsOperationBinding : OperationBinding { "principal", (a, n) => { a.Principal = Principal.Parse(n); } }, { "action", (a, n) => { a.Action = StringOrStringList.Parse(n); } }, { "resource", (a, n) => { a.Resource = StringOrStringList.Parse(n); } }, - { "condition", (a, n) => { a.Condition = n.CreateAny(); } }, + { "condition", (a, n) => { a.Condition = Condition.Parse(n); } }, }; public override void SerializeProperties(IAsyncApiWriter writer) diff --git a/src/LEGO.AsyncAPI.Bindings/Sqs/Statement.cs b/src/LEGO.AsyncAPI.Bindings/Sqs/Statement.cs index 4abc05a6..4a9c5303 100644 --- a/src/LEGO.AsyncAPI.Bindings/Sqs/Statement.cs +++ b/src/LEGO.AsyncAPI.Bindings/Sqs/Statement.cs @@ -34,7 +34,7 @@ public class Statement : IAsyncApiExtensible /// /// Specific circumstances under which the policy grants permission. /// - public AsyncApiAny? Condition { get; set; } + public Condition Condition { get; set; } public IDictionary Extensions { get; set; } = new Dictionary(); @@ -50,7 +50,7 @@ public void Serialize(IAsyncApiWriter writer) writer.WriteRequiredObject("principal", this.Principal, (w, t) => t.Serialize(w)); writer.WriteRequiredObject("action", this.Action, (w, t) => t.Value.Write(w)); writer.WriteOptionalObject("resource", this.Resource, (w, t) => t?.Value.Write(w)); - writer.WriteOptionalObject("condition", this.Condition, (w, t) => t?.Write(w)); + writer.WriteOptionalObject("condition", this.Condition, (w, t) => t.Serialize(w)); writer.WriteExtensions(this.Extensions); writer.WriteEndObject(); } diff --git a/test/LEGO.AsyncAPI.Tests/Bindings/Sns/SnsBindings_Should.cs b/test/LEGO.AsyncAPI.Tests/Bindings/Sns/SnsBindings_Should.cs index fbb3622e..daed9e88 100644 --- a/test/LEGO.AsyncAPI.Tests/Bindings/Sns/SnsBindings_Should.cs +++ b/test/LEGO.AsyncAPI.Tests/Bindings/Sns/SnsBindings_Should.cs @@ -92,12 +92,14 @@ public void SnsChannelBinding_WithFilledObject_SerializesAndDeserializes() "sns:Publish", "sns:Delete", })), - Condition = new AsyncApiAny(new Dictionary() + Condition = new Condition(new Dictionary> { { - "StringEquals", new Dictionary>() + "StringEquals", new Dictionary { - { "aws:username", new List() { "johndoe", "mrsmith" } }, + { + "aws:username", new StringOrStringList(new AsyncApiAny(new List() { "johndoe", "mrsmith" })) + }, } }, }), @@ -109,12 +111,14 @@ public void SnsChannelBinding_WithFilledObject_SerializesAndDeserializes() "AWS", new StringOrStringList(new AsyncApiAny(new List { "arn:aws:iam::123456789012:user/alex.wichmann", "arn:aws:iam::123456789012:user/dec.kolakowski" })))), Action = new StringOrStringList(new AsyncApiAny("sns:Create")), - Condition = new AsyncApiAny(new Dictionary() + Condition = new Condition(new Dictionary> { { - "NumericLessThanEquals", new Dictionary() + "NumericLessThanEquals", new Dictionary { - { "aws:MultiFactorAuthAge", "3600" }, + { + "aws:MultiFactorAuthAge", new StringOrStringList(new AsyncApiAny("3600")) + }, } }, }), diff --git a/test/LEGO.AsyncAPI.Tests/Bindings/Sqs/SqsBindings_should.cs b/test/LEGO.AsyncAPI.Tests/Bindings/Sqs/SqsBindings_should.cs index 3a0337a3..c3f7ff9d 100644 --- a/test/LEGO.AsyncAPI.Tests/Bindings/Sqs/SqsBindings_should.cs +++ b/test/LEGO.AsyncAPI.Tests/Bindings/Sqs/SqsBindings_should.cs @@ -143,12 +143,14 @@ public void SqsChannelBinding_WithFilledObject_SerializesAndDeserializes() "sqs:SendMessage", "sqs:ReceiveMessage", })), - Condition = new AsyncApiAny(new Dictionary() + Condition = new Condition(new Dictionary> { { - "StringEquals", new Dictionary>() + "StringEquals", new Dictionary { - { "aws:username", new List() { "johndoe", "mrsmith" } }, + { + "aws:username", new StringOrStringList(new AsyncApiAny(new List { "johndoe", "mrsmith" })) + }, } }, }), @@ -170,12 +172,14 @@ public void SqsChannelBinding_WithFilledObject_SerializesAndDeserializes() "AWS", new StringOrStringList(new AsyncApiAny(new List { "arn:aws:iam::123456789012:user/alex.wichmann", "arn:aws:iam::123456789012:user/dec.kolakowski" })))), Action = new StringOrStringList(new AsyncApiAny("sqs:CreateQueue")), - Condition = new AsyncApiAny(new Dictionary() + Condition = new Condition(new Dictionary> { { - "NumericLessThanEquals", new Dictionary() + "NumericLessThanEquals", new Dictionary { - { "aws:MultiFactorAuthAge", "3600" }, + { + "aws:MultiFactorAuthAge", new StringOrStringList(new AsyncApiAny("3600")) + }, } }, }), From 68657dd2d035a1fd5468844c37c3ee85f019cfe6 Mon Sep 17 00:00:00 2001 From: Alex Wichmann Date: Wed, 8 Jan 2025 10:05:05 +0100 Subject: [PATCH 13/18] Update and rename release-internal.yml to release-beta.yml --- .github/workflows/{release-internal.yml => release-beta.yml} | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) rename .github/workflows/{release-internal.yml => release-beta.yml} (97%) diff --git a/.github/workflows/release-internal.yml b/.github/workflows/release-beta.yml similarity index 97% rename from .github/workflows/release-internal.yml rename to .github/workflows/release-beta.yml index 1231b9e6..9cbebb05 100644 --- a/.github/workflows/release-internal.yml +++ b/.github/workflows/release-beta.yml @@ -1,7 +1,9 @@ name: Publish beta NuGet package on: workflow_dispatch: - + push: + branches: + - vnext jobs: check: runs-on: ubuntu-latest From 57c40958d8d57e7055d13282243dc2c04434011d Mon Sep 17 00:00:00 2001 From: VisualBean Date: Fri, 24 Jan 2025 12:47:38 +0100 Subject: [PATCH 14/18] chore: add Ulrik as codeowner chore: add Ulrik as codeowner --- CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CODEOWNERS b/CODEOWNERS index f1759cb1..2e87be63 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -1 +1 @@ -* @VisualBean +* @VisualBean @UlrikSandberg From e6b9bf4b8f6631fa8862848cc9288dd93c8035e1 Mon Sep 17 00:00:00 2001 From: UlrikSandberg Date: Thu, 20 Feb 2025 10:19:02 +0100 Subject: [PATCH 15/18] fix: dont return early for required map (#206) --- .../Writers/AsyncApiWriterExtensions.cs | 5 +-- .../AsyncApiDocumentV2Tests.cs | 31 +++++++++++++++++++ 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/src/LEGO.AsyncAPI/Writers/AsyncApiWriterExtensions.cs b/src/LEGO.AsyncAPI/Writers/AsyncApiWriterExtensions.cs index 67c4737a..25777d51 100644 --- a/src/LEGO.AsyncAPI/Writers/AsyncApiWriterExtensions.cs +++ b/src/LEGO.AsyncAPI/Writers/AsyncApiWriterExtensions.cs @@ -285,10 +285,7 @@ public static void WriteRequiredMap( Action action) where T : IAsyncApiElement { - if (elements != null && elements.Any()) - { - writer.WriteMapInternal(name, elements, action); - } + writer.WriteMapInternal(name, elements, action); } /// diff --git a/test/LEGO.AsyncAPI.Tests/AsyncApiDocumentV2Tests.cs b/test/LEGO.AsyncAPI.Tests/AsyncApiDocumentV2Tests.cs index 63e818b4..6e1a9c70 100644 --- a/test/LEGO.AsyncAPI.Tests/AsyncApiDocumentV2Tests.cs +++ b/test/LEGO.AsyncAPI.Tests/AsyncApiDocumentV2Tests.cs @@ -1325,5 +1325,36 @@ public void Serializev2_WithBindings_Serializes() Assert.AreEqual("this mah binding", httpBinding.Headers.Description); } + + + + [Test] + public void SerializeV2_EmptyChannelObject_DeserializeAndSerializePreserveChannelObject() + { + // Arrange + var spec = """ + asyncapi: 2.6.0 + info: + title: Spec with missing channel info + description: test description + servers: + production: + url: example.com + protocol: pulsar+ssl + description: test description + channels: { } + """; + + var settings = new AsyncApiReaderSettings(); + var reader = new AsyncApiStringReader(settings); + + // Act + var deserialized = reader.Read(spec, out var diagnostic); + var actual = deserialized.Serialize(AsyncApiVersion.AsyncApi2_0, AsyncApiFormat.Yaml); + + // Assert + actual.Should() + .BePlatformAgnosticEquivalentTo(spec); + } } } \ No newline at end of file From 24cf1a976986d095a20d0094130184dcdd542be8 Mon Sep 17 00:00:00 2001 From: DominikKaloc Date: Mon, 8 Sep 2025 09:41:41 +0200 Subject: [PATCH 16/18] fix: empty channels should be allowed (#208) --- src/LEGO.AsyncAPI/Models/AsyncApiDocument.cs | 4 ++-- .../Validation/Rules/AsyncApiDocumentRules.cs | 2 +- test/LEGO.AsyncAPI.Tests/AsyncApiDocumentBuilder.cs | 7 ++++++- test/LEGO.AsyncAPI.Tests/AsyncApiDocumentV2Tests.cs | 8 ++++++++ 4 files changed, 17 insertions(+), 4 deletions(-) diff --git a/src/LEGO.AsyncAPI/Models/AsyncApiDocument.cs b/src/LEGO.AsyncAPI/Models/AsyncApiDocument.cs index 5be55202..b6488883 100644 --- a/src/LEGO.AsyncAPI/Models/AsyncApiDocument.cs +++ b/src/LEGO.AsyncAPI/Models/AsyncApiDocument.cs @@ -6,8 +6,8 @@ namespace LEGO.AsyncAPI.Models using System.Collections.Generic; using LEGO.AsyncAPI.Exceptions; using LEGO.AsyncAPI.Models.Interfaces; - using LEGO.AsyncAPI.Writers; using LEGO.AsyncAPI.Services; + using LEGO.AsyncAPI.Writers; /// /// This is the root document object for the API specification. It combines resource listing and API declaration together into one document. @@ -46,7 +46,7 @@ public class AsyncApiDocument : IAsyncApiExtensible, IAsyncApiSerializable /// /// REQUIRED. The available channels and messages for the API. /// - public IDictionary Channels { get; set; } = new Dictionary(); + public IDictionary Channels { get; set; } /// /// an element to hold various schemas for the specification. diff --git a/src/LEGO.AsyncAPI/Validation/Rules/AsyncApiDocumentRules.cs b/src/LEGO.AsyncAPI/Validation/Rules/AsyncApiDocumentRules.cs index 27076369..264c1611 100644 --- a/src/LEGO.AsyncAPI/Validation/Rules/AsyncApiDocumentRules.cs +++ b/src/LEGO.AsyncAPI/Validation/Rules/AsyncApiDocumentRules.cs @@ -30,7 +30,7 @@ public static class AsyncApiDocumentRules context.Exit(); context.Enter("channels"); - if (document.Channels == null || !document.Channels.Keys.Any()) + if (document.Channels == null) { context.CreateError( nameof(DocumentRequiredFields), diff --git a/test/LEGO.AsyncAPI.Tests/AsyncApiDocumentBuilder.cs b/test/LEGO.AsyncAPI.Tests/AsyncApiDocumentBuilder.cs index 3f2ee429..886fb7bf 100644 --- a/test/LEGO.AsyncAPI.Tests/AsyncApiDocumentBuilder.cs +++ b/test/LEGO.AsyncAPI.Tests/AsyncApiDocumentBuilder.cs @@ -2,9 +2,10 @@ namespace LEGO.AsyncAPI.Tests { + using System; + using System.Collections.Generic; using LEGO.AsyncAPI.Models; using LEGO.AsyncAPI.Models.Interfaces; - using System; internal class AsyncApiDocumentBuilder { @@ -42,6 +43,10 @@ public AsyncApiDocumentBuilder WithDefaultContentType(string contentType = "appl public AsyncApiDocumentBuilder WithChannel(string key, AsyncApiChannel channel) { + if (this.document.Channels == null) + { + this.document.Channels = new Dictionary(); + } this.document.Channels.Add(key, channel); return this; } diff --git a/test/LEGO.AsyncAPI.Tests/AsyncApiDocumentV2Tests.cs b/test/LEGO.AsyncAPI.Tests/AsyncApiDocumentV2Tests.cs index 6e1a9c70..13ae85e5 100644 --- a/test/LEGO.AsyncAPI.Tests/AsyncApiDocumentV2Tests.cs +++ b/test/LEGO.AsyncAPI.Tests/AsyncApiDocumentV2Tests.cs @@ -1202,6 +1202,10 @@ public void Serialize_WithBindingReferences_SerializesDeserializes() }, }, }; + if (doc.Channels == null) + { + doc.Channels = new Dictionary(); + } doc.Channels.Add( "testChannel", new AsyncApiChannel @@ -1260,6 +1264,10 @@ public void Serializev2_WithBindings_Serializes() Protocol = "pulsar+ssl", Url = "example.com", }); + if (doc.Channels == null) + { + doc.Channels = new Dictionary(); + } doc.Channels.Add( "testChannel", new AsyncApiChannel From 47fee3ffbc9017abdf8304aa78f5cb452027f280 Mon Sep 17 00:00:00 2001 From: DominikKaloc Date: Thu, 25 Sep 2025 13:22:55 +0200 Subject: [PATCH 17/18] chore: manual release - custom version (#210) --- .github/workflows/release-manual.yml | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 .github/workflows/release-manual.yml diff --git a/.github/workflows/release-manual.yml b/.github/workflows/release-manual.yml new file mode 100644 index 00000000..0eeca64b --- /dev/null +++ b/.github/workflows/release-manual.yml @@ -0,0 +1,28 @@ +name: Publish custom NuGet package version +on: + workflow_dispatch: + inputs: + package_version: + description: 'NuGet package version (e.g. 6.0.0-beta.1041)' + required: true + +jobs: + pre-release: + runs-on: ubuntu-latest + name: Publish NuGet packages + environment: AsyncAPI + strategy: + matrix: + package-name: [ "LEGO.AsyncAPI", "LEGO.AsyncAPI.Readers", "LEGO.AsyncAPI.Bindings" ] + steps: + - name: Checkout repository + uses: actions/checkout@v1 + + - name: Setup .NET Core @ Latest + uses: actions/setup-dotnet@v1 + + - name: Build ${{ matrix.package-name }} project and pack NuGet package + run: dotnet pack src/${{ matrix.package-name }}/${{ matrix.package-name }}.csproj -c Release -o out-${{ matrix.package-name }} -p:PackageVersion=${{ github.event.inputs.package_version }} + + - name: Push generated package to NuGet + run: dotnet nuget push out-${{ matrix.package-name }}/*.nupkg -s https://api.nuget.org/v3/index.json --skip-duplicate -n --api-key ${{secrets.NUGET}} From f5becdf3d24a5138dcc54bfbe6a961b806f22c0f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 17 Jul 2026 13:11:46 +0200 Subject: [PATCH 18/18] fix: bump System.Text.Json from 8.0.4 to 8.0.5 (#200) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- src/LEGO.AsyncAPI/LEGO.AsyncAPI.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/LEGO.AsyncAPI/LEGO.AsyncAPI.csproj b/src/LEGO.AsyncAPI/LEGO.AsyncAPI.csproj index 99e69016..a321a003 100644 --- a/src/LEGO.AsyncAPI/LEGO.AsyncAPI.csproj +++ b/src/LEGO.AsyncAPI/LEGO.AsyncAPI.csproj @@ -19,7 +19,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + <_Parameter1>$(MSBuildProjectName).Tests