diff --git a/src/LEGO.AsyncAPI.Bindings/BindingsCollection.cs b/src/LEGO.AsyncAPI.Bindings/BindingsCollection.cs index 9c729597..e52392b6 100644 --- a/src/LEGO.AsyncAPI.Bindings/BindingsCollection.cs +++ b/src/LEGO.AsyncAPI.Bindings/BindingsCollection.cs @@ -1,7 +1,4 @@ // Copyright (c) The LEGO Group. All rights reserved. - -using LEGO.AsyncAPI.Bindings.Sqs; - namespace LEGO.AsyncAPI.Bindings { using System; @@ -9,6 +6,8 @@ namespace LEGO.AsyncAPI.Bindings using LEGO.AsyncAPI.Bindings.Http; using LEGO.AsyncAPI.Bindings.Kafka; using LEGO.AsyncAPI.Bindings.Pulsar; + using LEGO.AsyncAPI.Bindings.Sns; + using LEGO.AsyncAPI.Bindings.Sqs; using LEGO.AsyncAPI.Bindings.WebSockets; using LEGO.AsyncAPI.Models.Interfaces; using LEGO.AsyncAPI.Readers.Interface; @@ -43,6 +42,8 @@ public static TCollection Add( Kafka, Http, Websockets, + Sqs, + Sns, }; public static IEnumerable> Http => new List> @@ -75,5 +76,11 @@ public static TCollection Add( new SqsChannelBinding(), new SqsOperationBinding(), }; + + public static IEnumerable> Sns => new List> + { + new SnsChannelBinding(), + new SnsOperationBinding(), + }; } } diff --git a/src/LEGO.AsyncAPI.Bindings/Sns/Consumer.cs b/src/LEGO.AsyncAPI.Bindings/Sns/Consumer.cs new file mode 100644 index 00000000..46548977 --- /dev/null +++ b/src/LEGO.AsyncAPI.Bindings/Sns/Consumer.cs @@ -0,0 +1,80 @@ +namespace LEGO.AsyncAPI.Bindings.Sns +{ + using System; + using System.Collections.Generic; + using LEGO.AsyncAPI.Attributes; + using LEGO.AsyncAPI.Models.Interfaces; + using LEGO.AsyncAPI.Writers; + + public class Consumer : IAsyncApiExtensible + { + /// + /// The protocol that this endpoint will receive messages by. + /// + public Protocol Protocol { get; set; } + + /// + /// The endpoint messages are delivered to. + /// + public Identifier Endpoint { get; set; } + + /// + /// Only receive a subset of messages from the channel, determined by this policy. + /// + public FilterPolicy FilterPolicy { 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. + /// + public bool RawMessageDelivery { get; set; } + + /// + /// Prevent poison pill messages by moving un-processable messages to an SQS dead letter queue. + /// + public RedrivePolicy RedrivePolicy { get; set; } + + /// + /// Policy for retries to HTTP. The parameter is for that SNS Subscription and overrides any policy on the SNS Topic. + /// + public DeliveryPolicy DeliveryPolicy { get; set; } + + /// + /// The display name to use with an SNS subscription. + /// + public string DisplayName { 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.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.WriteRequiredProperty("rawMessageDelivery", this.RawMessageDelivery); + writer.WriteOptionalObject("redrivePolicy", this.RedrivePolicy, (w, p) => p.Serialize(w)); + writer.WriteOptionalObject("deliveryPolicy", this.DeliveryPolicy, (w, p) => p.Serialize(w)); + writer.WriteOptionalProperty("displayName", this.DisplayName); + writer.WriteExtensions(this.Extensions); + writer.WriteEndObject(); + } + } + + public enum Protocol + { + [Display("http")] Http, + [Display("https")] Https, + [Display("email")] Email, + [Display("email-json")] EmailJson, + [Display("sms")] Sms, + [Display("sqs")] Sqs, + [Display("application")] Application, + [Display("lambda")] Lambda, + [Display("firehose")] Firehose, + } +} \ No newline at end of file diff --git a/src/LEGO.AsyncAPI.Bindings/Sns/DeliveryPolicy.cs b/src/LEGO.AsyncAPI.Bindings/Sns/DeliveryPolicy.cs new file mode 100644 index 00000000..f9421ca9 --- /dev/null +++ b/src/LEGO.AsyncAPI.Bindings/Sns/DeliveryPolicy.cs @@ -0,0 +1,81 @@ +namespace LEGO.AsyncAPI.Bindings.Sns +{ + using System; + using System.Collections.Generic; + using LEGO.AsyncAPI.Attributes; + using LEGO.AsyncAPI.Models.Interfaces; + using LEGO.AsyncAPI.Writers; + + public class DeliveryPolicy : IAsyncApiExtensible + { + /// + /// The minimum delay for a retry in seconds. + /// + public int? MinDelayTarget { get; set; } + + /// + /// The maximum delay for a retry in seconds. + /// + public int? MaxDelayTarget { get; set; } + + /// + /// The total number of retries, including immediate, pre-backoff, backoff, and post-backoff retries. + /// + public int? NumRetries { get; set; } + + /// + /// The number of immediate retries (with no delay). + /// + public int? NumNoDelayRetries { get; set; } + + /// + /// The number of immediate retries (with delay). + /// + public int? NumMinDelayRetries { get; set; } + + /// + /// The number of post-backoff phase retries, with the maximum delay between retries. + /// + public int? NumMaxDelayRetries { get; set; } + + /// + /// The algorithm for backoff between retries. + /// + public BackoffFunction BackoffFunction { get; set; } + + /// + /// The maximum number of deliveries per second, per subscription. + /// + public int? MaxReceivesPerSecond { 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.WriteOptionalProperty("minDelayTarget", this.MinDelayTarget); + writer.WriteOptionalProperty("maxDelayTarget", this.MaxDelayTarget); + writer.WriteOptionalProperty("numRetries", this.NumRetries); + writer.WriteOptionalProperty("numNoDelayRetries", this.NumNoDelayRetries); + writer.WriteOptionalProperty("numMinDelayRetries", this.NumMinDelayRetries); + writer.WriteOptionalProperty("numMaxDelayRetries", this.NumMaxDelayRetries); + writer.WriteOptionalProperty("backoffFunction", this.BackoffFunction.GetDisplayName()); + writer.WriteOptionalProperty("maxReceivesPerSecond", this.MaxReceivesPerSecond); + writer.WriteExtensions(this.Extensions); + writer.WriteEndObject(); + } + } + + public enum BackoffFunction + { + [Display("arithmetic")] Arithmetic, + [Display("exponential")] Exponential, + [Display("geometric")] Geometric, + [Display("linear")] Linear, + } +} \ No newline at end of file diff --git a/src/LEGO.AsyncAPI.Bindings/Sns/FilterPolicy.cs b/src/LEGO.AsyncAPI.Bindings/Sns/FilterPolicy.cs new file mode 100644 index 00000000..47530cc0 --- /dev/null +++ b/src/LEGO.AsyncAPI.Bindings/Sns/FilterPolicy.cs @@ -0,0 +1,30 @@ +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/Identifier.cs b/src/LEGO.AsyncAPI.Bindings/Sns/Identifier.cs new file mode 100644 index 00000000..0e6466b0 --- /dev/null +++ b/src/LEGO.AsyncAPI.Bindings/Sns/Identifier.cs @@ -0,0 +1,39 @@ +namespace LEGO.AsyncAPI.Bindings.Sns +{ + using System; + using System.Collections.Generic; + using LEGO.AsyncAPI.Models.Interfaces; + using LEGO.AsyncAPI.Writers; + + public class Identifier : IAsyncApiExtensible + { + public string Url { get; set; } + + public string Email { get; set; } + + public string Phone { get; set; } + + public string Arn { get; set; } + + public string Name { 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.WriteOptionalProperty("url", this.Url); + writer.WriteOptionalProperty("email", this.Email); + writer.WriteOptionalProperty("phone", this.Phone); + writer.WriteOptionalProperty("arn", this.Arn); + writer.WriteOptionalProperty("name", this.Name); + writer.WriteExtensions(this.Extensions); + writer.WriteEndObject(); + } + } +} \ No newline at end of file diff --git a/src/LEGO.AsyncAPI.Bindings/Sns/Ordering.cs b/src/LEGO.AsyncAPI.Bindings/Sns/Ordering.cs new file mode 100644 index 00000000..4ed81eec --- /dev/null +++ b/src/LEGO.AsyncAPI.Bindings/Sns/Ordering.cs @@ -0,0 +1,45 @@ +namespace LEGO.AsyncAPI.Bindings.Sns +{ + using System; + using System.Collections.Generic; + using LEGO.AsyncAPI.Attributes; + using LEGO.AsyncAPI.Models.Interfaces; + using LEGO.AsyncAPI.Writers; + + public class Ordering : IAsyncApiExtensible + { + /// + /// What type of SNS Topic is this? + /// + public OrderingType Type { get; set; } + + /// + /// True to turn on de-duplication of messages for a channel. + /// + public bool ContentBasedDeduplication { 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.WriteRequiredProperty("type", this.Type.GetDisplayName()); + writer.WriteOptionalProperty("contentBasedDeduplication", this.ContentBasedDeduplication); + writer.WriteExtensions(this.Extensions); + writer.WriteEndObject(); + } + } + + public enum OrderingType + { + [Display("standard")] + Standard, + [Display("FIFO")] + Fifo, + } +} \ No newline at end of file diff --git a/src/LEGO.AsyncAPI.Bindings/Sns/Policy.cs b/src/LEGO.AsyncAPI.Bindings/Sns/Policy.cs new file mode 100644 index 00000000..685b2f8d --- /dev/null +++ b/src/LEGO.AsyncAPI.Bindings/Sns/Policy.cs @@ -0,0 +1,31 @@ +namespace LEGO.AsyncAPI.Bindings.Sns +{ + using System; + using System.Collections.Generic; + using LEGO.AsyncAPI.Models; + using LEGO.AsyncAPI.Models.Interfaces; + using LEGO.AsyncAPI.Writers; + + public class Policy : IAsyncApiExtensible + { + /// + /// An array of statement objects, each of which controls a permission for this topic. + /// + public List Statements { 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.WriteOptionalCollection("statements", this.Statements, (w, t) => t.Serialize(w)); + writer.WriteExtensions(this.Extensions); + writer.WriteEndObject(); + } + } +} \ No newline at end of file diff --git a/src/LEGO.AsyncAPI.Bindings/Sns/RedrivePolicy.cs b/src/LEGO.AsyncAPI.Bindings/Sns/RedrivePolicy.cs new file mode 100644 index 00000000..4e5e6340 --- /dev/null +++ b/src/LEGO.AsyncAPI.Bindings/Sns/RedrivePolicy.cs @@ -0,0 +1,36 @@ +namespace LEGO.AsyncAPI.Bindings.Sns +{ + using System; + using System.Collections.Generic; + using LEGO.AsyncAPI.Models.Interfaces; + using LEGO.AsyncAPI.Writers; + + public class RedrivePolicy : IAsyncApiExtensible + { + /// + /// Prevent poison pill messages by moving un-processable messages to an SQS dead letter queue. + /// + public Identifier DeadLetterQueue { get; set; } + + /// + /// The number of times a message is delivered to the source queue before being moved to the dead-letter queue. + /// + public int? MaxReceiveCount { 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("deadLetterQueue", this.DeadLetterQueue, (w, q) => q.Serialize(w)); + writer.WriteOptionalProperty("maxReceiveCount", this.MaxReceiveCount); + writer.WriteExtensions(this.Extensions); + writer.WriteEndObject(); + } + } +} \ No newline at end of file diff --git a/src/LEGO.AsyncAPI.Bindings/Sns/SnsChannelBinding.cs b/src/LEGO.AsyncAPI.Bindings/Sns/SnsChannelBinding.cs new file mode 100644 index 00000000..a0df69a9 --- /dev/null +++ b/src/LEGO.AsyncAPI.Bindings/Sns/SnsChannelBinding.cs @@ -0,0 +1,79 @@ +namespace LEGO.AsyncAPI.Bindings.Sns +{ + using System; + using System.Collections.Generic; + using LEGO.AsyncAPI.Bindings; + using LEGO.AsyncAPI.Readers.ParseNodes; + using LEGO.AsyncAPI.Writers; + + /// + /// Binding class for SNS channel settings. + /// + public class SnsChannelBinding : ChannelBinding + { + /// + /// The name of the topic. Can be different from the channel name to allow flexibility around AWS resource naming limitations. + /// + public string Name { get; set; } + + /// + /// By default, we assume an unordered SNS topic. This field allows configuration of a FIFO SNS Topic. + /// + public Ordering Ordering { get; set; } + + /// + /// The security policy for the SNS Topic. + /// + public Policy Policy { get; set; } + + /// + /// Key-value pairs that represent AWS tags on the topic. + /// + public Dictionary Tags { get; set; } + + public override string BindingKey => "sns"; + + protected override FixedFieldMap FixedFieldMap => new() + { + { "name", (a, n) => { a.Name = n.GetScalarValue(); } }, + { "type", (a, n) => { a.Ordering = n.ParseMapWithExtensions(this.orderingFixedFields); } }, + { "policy", (a, n) => { a.Policy = n.ParseMapWithExtensions(this.policyFixedFields); } }, + { "tags", (a, n) => { a.Tags = n.CreateSimpleMap(s => s.GetScalarValue()); } }, + }; + + private FixedFieldMap orderingFixedFields = new() + { + { "type", (a, n) => { a.Type = n.GetScalarValue().GetEnumFromDisplayName(); } }, + { "contentBasedDeduplication", (a, n) => { a.ContentBasedDeduplication = n.GetBooleanValue(); } }, + }; + + private FixedFieldMap policyFixedFields = new() + { + { "statements", (a, n) => { a.Statements = n.CreateList(s => s.ParseMapWithExtensions(statementFixedFields)); } }, + }; + + private static FixedFieldMap statementFixedFields = new() + { + { "effect", (a, n) => { a.Effect = n.GetScalarValue().GetEnumFromDisplayName(); } }, + { "principal", (a, n) => { a.Principal = StringOrStringList.Parse(n); } }, + { "action", (a, n) => { a.Action = StringOrStringList.Parse(n); } }, + }; + + /// + public override void SerializeProperties(IAsyncApiWriter writer) + { + if (writer is null) + { + throw new ArgumentNullException(nameof(writer)); + } + + writer.WriteStartObject(); + writer.WriteRequiredProperty("name", this.Name); + writer.WriteOptionalObject("ordering", this.Ordering, (w, t) => t.Serialize(w)); + writer.WriteOptionalObject("policy", this.Policy, (w, t) => t.Serialize(w)); + writer.WriteOptionalMap("tags", this.Tags, (w, t) => w.WriteValue(t)); + 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 new file mode 100644 index 00000000..d35a46f5 --- /dev/null +++ b/src/LEGO.AsyncAPI.Bindings/Sns/SnsOperationBinding.cs @@ -0,0 +1,96 @@ +namespace LEGO.AsyncAPI.Bindings.Sns +{ + using System; + using System.Collections.Generic; + using LEGO.AsyncAPI.Models.Any; + using LEGO.AsyncAPI.Readers.ParseNodes; + using LEGO.AsyncAPI.Writers; + + /// + /// This object contains information about the operation representation in SNS. + /// + public class SnsOperationBinding : OperationBinding + { + /// + /// Often we can assume that the SNS Topic is the channel name-we provide this field in case the you need to supply the ARN, or the Topic name is not the channel name in the AsyncAPI document. + /// + public Identifier Topic { get; set; } + + /// + /// The protocols that listen to this topic and their endpoints. + /// + public List Consumers { get; set; } + + /// + /// Policy for retries to HTTP. The field is the default for HTTP receivers of the SNS Topic which may be overridden by a specific consumer. + /// + public DeliveryPolicy DeliveryPolicy { get; set; } + + public override string BindingKey => "sns"; + + protected override FixedFieldMap FixedFieldMap => new() + { + { "topic", (a, n) => { a.Topic = n.ParseMapWithExtensions(this.identifierFixFields); } }, + { "consumers", (a, n) => { a.Consumers = n.CreateList(s => s.ParseMapWithExtensions(this.consumerFixedFields)); } }, + { "deliveryPolicy", (a, n) => { a.DeliveryPolicy = n.ParseMapWithExtensions(this.deliveryPolicyFixedFields); } }, + }; + + private FixedFieldMap identifierFixFields => new() + { + { "url", (a, n) => { a.Url = n.GetScalarValue(); } }, + { "email", (a, n) => { a.Email = n.GetScalarValue(); } }, + { "phone", (a, n) => { a.Phone = n.GetScalarValue(); } }, + { "arn", (a, n) => { a.Arn = n.GetScalarValue(); } }, + { "name", (a, n) => { a.Name = n.GetScalarValue(); } }, + }; + + private FixedFieldMap consumerFixedFields => new () + { + { "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); } }, + { "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); } }, + { "maxReceiveCount", (a, n) => { a.MaxReceiveCount = n.GetIntegerValue(); } }, + }; + + private FixedFieldMap deliveryPolicyFixedFields => new() + { + { "minDelayTarget", (a, n) => { a.MinDelayTarget = n.GetIntegerValue(); } }, + { "maxDelayTarget", (a, n) => { a.MaxDelayTarget = n.GetIntegerValue(); } }, + { "numRetries", (a, n) => { a.NumRetries = n.GetIntegerValue(); } }, + { "numNoDelayRetries", (a, n) => { a.NumNoDelayRetries = n.GetIntegerValue(); } }, + { "numMinDelayRetries", (a, n) => { a.NumMinDelayRetries = n.GetIntegerValue(); } }, + { "numMaxDelayRetries", (a, n) => { a.NumMaxDelayRetries = n.GetIntegerValue(); } }, + { "backoffFunction", (a, n) => { a.BackoffFunction = n.GetScalarValue().GetEnumFromDisplayName(); } }, + { "maxReceivesPerSecond", (a, n) => { a.MaxReceivesPerSecond = n.GetIntegerValue(); } }, + }; + + public override void SerializeProperties(IAsyncApiWriter writer) + { + if (writer is null) + { + throw new ArgumentNullException(nameof(writer)); + } + + writer.WriteStartObject(); + writer.WriteOptionalObject("topic", this.Topic, (w, t) => t.Serialize(w)); + writer.WriteOptionalCollection("consumers", this.Consumers, (w, c) => c.Serialize(w)); + writer.WriteOptionalObject("deliveryPolicy", this.DeliveryPolicy, (w, p) => p.Serialize(w)); + writer.WriteExtensions(this.Extensions); + writer.WriteEndObject(); + } + } +} \ No newline at end of file diff --git a/src/LEGO.AsyncAPI.Bindings/Sns/Statement.cs b/src/LEGO.AsyncAPI.Bindings/Sns/Statement.cs new file mode 100644 index 00000000..c21ecc80 --- /dev/null +++ b/src/LEGO.AsyncAPI.Bindings/Sns/Statement.cs @@ -0,0 +1,51 @@ +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 + { + + public Effect Effect { get; set; } + + /// + /// The AWS account or resource ARN that this statement applies to. + /// + // public StringOrStringList Principal { get; set; } + public StringOrStringList Principal { get; set; } + + /// + /// The SNS permission being allowed or denied e.g. sns:Publish + /// + public StringOrStringList Action { 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.WriteRequiredProperty("effect", this.Effect.GetDisplayName()); + writer.WriteRequiredObject("principal", this.Principal, (w, t) => t.Value.Write(w)); + writer.WriteRequiredObject("action", this.Action, (w, t) => t.Value.Write(w)); + writer.WriteExtensions(this.Extensions); + writer.WriteEndObject(); + } + } + + public enum Effect + { + [Display("Allow")] + Allow, + [Display("Deny")] + Deny, + } +} \ No newline at end of file diff --git a/test/LEGO.AsyncAPI.Tests/Bindings/Sns/SnsBindings_Should.cs b/test/LEGO.AsyncAPI.Tests/Bindings/Sns/SnsBindings_Should.cs new file mode 100644 index 00000000..7a2269bb --- /dev/null +++ b/test/LEGO.AsyncAPI.Tests/Bindings/Sns/SnsBindings_Should.cs @@ -0,0 +1,409 @@ +using System; +using LEGO.AsyncAPI.Models.Any; +using LEGO.AsyncAPI.Models.Interfaces; +using BindingsCollection = LEGO.AsyncAPI.Bindings.BindingsCollection; + +namespace LEGO.AsyncAPI.Tests.Bindings.Sns +{ + using NUnit.Framework; + using System.Collections.Generic; + using FluentAssertions; + using LEGO.AsyncAPI.Bindings; + using LEGO.AsyncAPI.Bindings.Sns; + using LEGO.AsyncAPI.Models; + using LEGO.AsyncAPI.Readers; + + internal class SnsBindings_Should + { + [Test] + public void SnsChannelBinding_WithFilledObject_SerializesAndDeserializes() + { + // Arrange + var expected = + @"bindings: + sns: + name: myTopic + ordering: + type: FIFO + contentBasedDeduplication: true + x-orderingExtension: + orderingXPropertyName: orderingXPropertyValue + policy: + statements: + - effect: Deny + principal: arn:aws:iam::123456789012:user/alex.wichmann + action: + - sns:Publish + - sns:Delete + - effect: Allow + principal: + - arn:aws:iam::123456789012:user/alex.wichmann + - arn:aws:iam::123456789012:user/dec.kolakowski + action: sns:Create + x-statementExtension: + statementXPropertyName: statementXPropertyValue + x-policyExtension: + policyXPropertyName: policyXPropertyValue + tags: + owner: AsyncAPI.NET + platform: AsyncAPIOrg + x-bindingExtension: + bindingXPropertyName: bindingXPropertyValue"; + + var channel = new AsyncApiChannel(); + channel.Bindings.Add(new SnsChannelBinding() + { + Name = "myTopic", + Ordering = new Ordering() + { + Type = OrderingType.Fifo, + ContentBasedDeduplication = true, + Extensions = new Dictionary() + { + { + "x-orderingExtension", + new AsyncApiObject() + { + { "orderingXPropertyName", new AsyncApiString("orderingXPropertyValue") }, + } + }, + }, + }, + Policy = new Policy() + { + Statements = new List() + { + new Statement() + { + Effect = Effect.Deny, + Principal = new StringOrStringList(new AsyncApiString("arn:aws:iam::123456789012:user/alex.wichmann")), + Action = new StringOrStringList(new AsyncApiArray() + { + new AsyncApiString("sns:Publish"), + new AsyncApiString("sns:Delete") + }), + }, + new Statement() + { + Effect = Effect.Allow, + Principal = new StringOrStringList(new AsyncApiArray() + { + new AsyncApiString("arn:aws:iam::123456789012:user/alex.wichmann"), + new AsyncApiString("arn:aws:iam::123456789012:user/dec.kolakowski") + }), + Action = new StringOrStringList(new AsyncApiString("sns:Create")), + Extensions = new Dictionary() + { + { + "x-statementExtension", + new AsyncApiObject() + { + { "statementXPropertyName", new AsyncApiString("statementXPropertyValue") }, + } + }, + }, + }, + }, + Extensions = new Dictionary() + { + { + "x-policyExtension", + new AsyncApiObject() + { + { "policyXPropertyName", new AsyncApiString("policyXPropertyValue") }, + } + }, + }, + }, + Tags = new Dictionary() + { + { "owner", "AsyncAPI.NET" }, + { "platform", "AsyncAPIOrg" }, + }, + Extensions = new Dictionary() + { + { + "x-bindingExtension", + new AsyncApiObject() + { + { "bindingXPropertyName", new AsyncApiString("bindingXPropertyValue") }, + } + }, + }, + }); + + // Act + var actual = channel.SerializeAsYaml(AsyncApiVersion.AsyncApi2_0); + + // Assert + actual = actual.MakeLineBreaksEnvironmentNeutral(); + expected = expected.MakeLineBreaksEnvironmentNeutral(); + var settings = new AsyncApiReaderSettings(); + settings.Bindings.Add(BindingsCollection.Sns); + var binding = new AsyncApiStringReader(settings).ReadFragment(actual, AsyncApiVersion.AsyncApi2_0, out _); + + + // Assert + Assert.AreEqual(actual, expected); + binding.Should().BeEquivalentTo(channel); + + } + + [Test] + public void SnsOperationBinding_WithFilledObject_SerializesAndDeserializes() + { + // Arrange + var expected = + @"bindings: + sns: + topic: + name: someTopic + x-identifierExtension: + identifierXPropertyName: identifierXPropertyValue + consumers: + - protocol: sqs + endpoint: + name: someQueue + 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 + rawMessageDelivery: false + redrivePolicy: + deadLetterQueue: + arn: arn:aws:SQS:eu-west-1:0000000:123456789 + x-identifierExtension: + identifierXPropertyName: identifierXPropertyValue + maxReceiveCount: 25 + x-redrivePolicyExtension: + redrivePolicyXPropertyName: redrivePolicyXPropertyValue + deliveryPolicy: + minDelayTarget: 10 + maxDelayTarget: 100 + numRetries: 5 + numNoDelayRetries: 2 + numMinDelayRetries: 3 + numMaxDelayRetries: 5 + backoffFunction: linear + maxReceivesPerSecond: 2 + x-deliveryPolicyExtension: + deliveryPolicyXPropertyName: deliveryPolicyXPropertyValue + x-consumerExtension: + consumerXPropertyName: consumerXPropertyValue + deliveryPolicy: + minDelayTarget: 10 + maxDelayTarget: 100 + numRetries: 5 + numNoDelayRetries: 2 + numMinDelayRetries: 3 + numMaxDelayRetries: 5 + backoffFunction: geometric + maxReceivesPerSecond: 10 + x-deliveryPolicyExtension: + deliveryPolicyXPropertyName: deliveryPolicyXPropertyValue + x-bindingExtension: + bindingXPropertyName: bindingXPropertyValue"; + + var operation = new AsyncApiOperation(); + operation.Bindings.Add(new SnsOperationBinding() + { + Topic = new Identifier() + { + Name = "someTopic", + Extensions = new Dictionary() + { + { + "x-identifierExtension", + new AsyncApiObject() + { + { "identifierXPropertyName", new AsyncApiString("identifierXPropertyValue") }, + } + }, + }, + }, + Consumers = new List() + { + new Consumer() + { + Protocol = Protocol.Sqs, + Endpoint = new Identifier() + { + Name = "someQueue", + Extensions = new Dictionary() + { + { + "x-identifierExtension", + new AsyncApiObject() + { + { "identifierXPropertyName", new AsyncApiString("identifierXPropertyValue") }, + } + }, + }, + }, + FilterPolicy = new FilterPolicy() + { + Attributes = new AsyncApiObject() + { + { "store", new AsyncApiArray() { new AsyncApiString("asyncapi_corp") } }, + { "contact", new AsyncApiString("dec.kolakowski") }, + { + "event", new AsyncApiArray() + { + new AsyncApiObject() + { + { "anything-but", new AsyncApiString("order_cancelled") }, + }, + } + }, + { + "order_key", new AsyncApiObject() + { + { "transient", new AsyncApiString("by_area") }, + } + }, + { + "customer_interests", new AsyncApiArray() + { + new AsyncApiString("rugby"), + new AsyncApiString("football"), + new AsyncApiString("baseball"), + } + }, + }, + Extensions = new Dictionary() + { + { + "x-filterPolicyExtension", + new AsyncApiObject() + { + { "filterPolicyXPropertyName", new AsyncApiString("filterPolicyXPropertyValue") }, + } + }, + }, + }, + RawMessageDelivery = false, + RedrivePolicy = new RedrivePolicy() + { + DeadLetterQueue = new Identifier() + { + Arn = "arn:aws:SQS:eu-west-1:0000000:123456789", + Extensions = new Dictionary() + { + { + "x-identifierExtension", + new AsyncApiObject() + { + { "identifierXPropertyName", new AsyncApiString("identifierXPropertyValue") }, + } + }, + }, + }, + MaxReceiveCount = 25, + Extensions = new Dictionary() + { + { + "x-redrivePolicyExtension", + new AsyncApiObject() + { + { "redrivePolicyXPropertyName", new AsyncApiString("redrivePolicyXPropertyValue") }, + } + }, + }, + }, + DeliveryPolicy = new DeliveryPolicy() + { + MinDelayTarget = 10, + MaxDelayTarget = 100, + NumRetries = 5, + NumNoDelayRetries = 2, + NumMinDelayRetries = 3, + NumMaxDelayRetries = 5, + BackoffFunction = BackoffFunction.Linear, + MaxReceivesPerSecond = 2, + Extensions = new Dictionary() + { + { + "x-deliveryPolicyExtension", + new AsyncApiObject() + { + { "deliveryPolicyXPropertyName", new AsyncApiString("deliveryPolicyXPropertyValue") }, + } + }, + }, + }, + Extensions = new Dictionary() + { + { + "x-consumerExtension", + new AsyncApiObject() + { + { "consumerXPropertyName", new AsyncApiString("consumerXPropertyValue") }, + } + }, + }, + }, + }, + DeliveryPolicy = new DeliveryPolicy() + { + MinDelayTarget = 10, + MaxDelayTarget = 100, + NumRetries = 5, + NumNoDelayRetries = 2, + NumMinDelayRetries = 3, + NumMaxDelayRetries = 5, + BackoffFunction = BackoffFunction.Geometric, + MaxReceivesPerSecond = 10, + Extensions = new Dictionary() + { + { + "x-deliveryPolicyExtension", + new AsyncApiObject() + { + { "deliveryPolicyXPropertyName", new AsyncApiString("deliveryPolicyXPropertyValue") }, + } + }, + }, + }, + Extensions = new Dictionary() + { + { + "x-bindingExtension", + new AsyncApiObject() + { + { "bindingXPropertyName", new AsyncApiString("bindingXPropertyValue") }, + } + }, + }, + }); + + // Act + var actual = operation.SerializeAsYaml(AsyncApiVersion.AsyncApi2_0); + + // Assert + actual = actual.MakeLineBreaksEnvironmentNeutral(); + expected = expected.MakeLineBreaksEnvironmentNeutral(); + var settings = new AsyncApiReaderSettings(); + settings.Bindings.Add(BindingsCollection.Sns); + var binding = new AsyncApiStringReader(settings).ReadFragment(actual, AsyncApiVersion.AsyncApi2_0, out _); + + + // Assert + Assert.AreEqual(actual, expected); + binding.Should().BeEquivalentTo(operation); + + } + } +} \ No newline at end of file diff --git a/test/LEGO.AsyncAPI.Tests/Bindings/Sqs/SqsBindings_should.cs b/test/LEGO.AsyncAPI.Tests/Bindings/Sqs/SqsBindings_should.cs index b2cd0eb2..34c361e1 100644 --- a/test/LEGO.AsyncAPI.Tests/Bindings/Sqs/SqsBindings_should.cs +++ b/test/LEGO.AsyncAPI.Tests/Bindings/Sqs/SqsBindings_should.cs @@ -190,8 +190,8 @@ public void SqsChannelBinding_WithFilledObject_SerializesAndDeserializes() Principal = new StringOrStringList(new AsyncApiString("arn:aws:iam::123456789012:user/alex.wichmann")), Action = new StringOrStringList(new AsyncApiArray() { - new AsyncApiString("sqs:*") - }) + new AsyncApiString("sqs:*"), + }), }, }, },