Skip to content

Commit

Permalink
feat: implement principal parse logic
Browse files Browse the repository at this point in the history
  • Loading branch information
adam.gloyne committed Jul 23, 2024
1 parent e8f7826 commit c20ad8d
Show file tree
Hide file tree
Showing 10 changed files with 186 additions and 35 deletions.
66 changes: 66 additions & 0 deletions src/LEGO.AsyncAPI.Bindings/Sns/Principal.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
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;
using LEGO.AsyncAPI.Models.Interfaces;
using LEGO.AsyncAPI.Readers.ParseNodes;

public class Principal : IAsyncApiElement
{
public Principal(AsyncApiAny value)
{
this.Value = value;
}

public AsyncApiAny Value { get; }

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 Principal(new AsyncApiAny(nodeValue));
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 parsedObject = new Dictionary<string, AsyncApiAny>()
{ { propertyNode.Name, StringOrStringList.Parse(propertyNode.Value).Value } };

return new Principal(new AsyncApiAny(parsedObject));
}

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);
}
}
2 changes: 1 addition & 1 deletion src/LEGO.AsyncAPI.Bindings/Sns/SnsChannelBinding.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ public class SnsChannelBinding : ChannelBinding<SnsChannelBinding>
private static FixedFieldMap<Statement> statementFixedFields = new()
{
{ "effect", (a, n) => { a.Effect = n.GetScalarValue().GetEnumFromDisplayName<Effect>(); } },
{ "principal", (a, n) => { a.Principal = n.CreateAny(); } },
{ "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(); } },
Expand Down
6 changes: 3 additions & 3 deletions src/LEGO.AsyncAPI.Bindings/Sns/Statement.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@ public class Statement : IAsyncApiExtensible
/// <summary>
/// The AWS account(s) or resource ARN(s) that this statement applies to.
/// </summary>
public AsyncApiAny Principal { get; set; }
public Principal Principal { get; set; }

/// <summary>
/// The SNS permission being allowed or denied e.g. sns:Publish
/// The SNS permission being allowed or denied e.g. sns:Publish.
/// </summary>
public StringOrStringList Action { get; set; }

Expand All @@ -46,7 +46,7 @@ public void Serialize(IAsyncApiWriter writer)

writer.WriteStartObject();
writer.WriteRequiredProperty("effect", this.Effect.GetDisplayName());
writer.WriteRequiredObject("principal", this.Principal, (w, t) => t.Write(w));
writer.WriteRequiredObject("principal", this.Principal, (w, t) => t.Value.Write(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));
Expand Down
66 changes: 66 additions & 0 deletions src/LEGO.AsyncAPI.Bindings/Sqs/Principal.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
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;
using LEGO.AsyncAPI.Models.Interfaces;
using LEGO.AsyncAPI.Readers.ParseNodes;

public class Principal : IAsyncApiElement
{
public Principal(AsyncApiAny value)
{
this.Value = value;
}

public AsyncApiAny Value { get; }

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 Principal(new AsyncApiAny(nodeValue));
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 parsedObject = new Dictionary<string, AsyncApiAny>()
{ { propertyNode.Name, StringOrStringList.Parse(propertyNode.Value).Value } };

return new Principal(new AsyncApiAny(parsedObject));
}

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);
}
}
2 changes: 1 addition & 1 deletion src/LEGO.AsyncAPI.Bindings/Sqs/SqsChannelBinding.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ public class SqsChannelBinding : ChannelBinding<SqsChannelBinding>
private static FixedFieldMap<Statement> statementFixedFields = new()
{
{ "effect", (a, n) => { a.Effect = n.GetScalarValue().GetEnumFromDisplayName<Effect>(); } },
{ "principal", (a, n) => { a.Principal = n.CreateAny(); } },
{ "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(); } },
Expand Down
2 changes: 1 addition & 1 deletion src/LEGO.AsyncAPI.Bindings/Sqs/SqsOperationBinding.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ public class SqsOperationBinding : OperationBinding<SqsOperationBinding>
private static FixedFieldMap<Statement> statementFixedFields = new()
{
{ "effect", (a, n) => { a.Effect = n.GetScalarValue().GetEnumFromDisplayName<Effect>(); } },
{ "principal", (a, n) => { a.Principal = n.CreateAny(); } },
{ "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(); } },
Expand Down
4 changes: 2 additions & 2 deletions src/LEGO.AsyncAPI.Bindings/Sqs/Statement.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public class Statement : IAsyncApiExtensible
/// <summary>
/// The AWS account(s) or resource ARN(s) that this statement applies to.
/// </summary>
public AsyncApiAny Principal { get; set; }
public Principal Principal { get; set; }

/// <summary>
/// The SNS permission being allowed or denied e.g. sns:Publish.
Expand Down Expand Up @@ -47,7 +47,7 @@ public void Serialize(IAsyncApiWriter writer)

writer.WriteStartObject();
writer.WriteRequiredProperty("effect", this.Effect.GetDisplayName());
writer.WriteRequiredObject("principal", this.Principal, (w, t) => t.Write(w));
writer.WriteRequiredObject("principal", this.Principal, (w, t) => t.Value.Write(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));
Expand Down
4 changes: 2 additions & 2 deletions src/LEGO.AsyncAPI.Bindings/StringOrStringList.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}
Expand Down
21 changes: 14 additions & 7 deletions test/LEGO.AsyncAPI.Tests/Bindings/Sns/SnsBindings_Should.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace LEGO.AsyncAPI.Tests.Bindings.Sns
{
using System;
using System.Collections.Generic;
using System.Linq;
using FluentAssertions;
Expand Down Expand Up @@ -86,7 +87,7 @@ public void SnsChannelBinding_WithFilledObject_SerializesAndDeserializes()
new Statement()
{
Effect = Effect.Deny,
Principal = new AsyncApiAny("*"),
Principal = new Principal(new AsyncApiAny("*")),
Action = new StringOrStringList(new AsyncApiAny(new List<string>()
{
"sns:Publish",
Expand All @@ -105,10 +106,10 @@ public void SnsChannelBinding_WithFilledObject_SerializesAndDeserializes()
new Statement()
{
Effect = Effect.Allow,
Principal = new AsyncApiAny(new Dictionary<string, List<string>>()
Principal = new Principal(new AsyncApiAny(new Dictionary<string, List<string>>()
{
{ "AWS", new List<string>() { "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<string, object>()
{
Expand Down Expand Up @@ -163,8 +164,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<AsyncApiChannel>(actual, AsyncApiVersion.AsyncApi2_0, out _);

// Assert
Expand Down Expand Up @@ -407,8 +411,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<AsyncApiOperation>(actual, AsyncApiVersion.AsyncApi2_0, out _);
var binding2 = new AsyncApiStringReader(settings).ReadFragment<AsyncApiOperation>(expected, AsyncApiVersion.AsyncApi2_0, out _);
binding2.Bindings.First().Value.Extensions.TryGetValue("x-bindingExtension", out IAsyncApiExtension any);
Expand Down
Loading

0 comments on commit c20ad8d

Please sign in to comment.