Skip to content

Commit

Permalink
feat: improve AsyncApiAny api surface.
Browse files Browse the repository at this point in the history
## About the PR
Fixes some issues with working with AsyncApiAny, figuring out the correct types etc.
Added new GetValue type methods for extracting expected values.
These use `system.text.json` to deserialize to `T` from the `JsonNode` type.

Added a static FromExtension method, to remove redundant type casting.
So instead of 
```csharp
if (TryGetValue(key, out IAsyncApiExtension extension))
{
  var myType = (extension as AsyncApiAny).GetValue<MyType>();
}
```
You do
```csharp
if (TryGetValue(key, out IAsyncApiExtension extension))
{
  var myType = AsyncApiAny.FromExtensionOrDefault<MyType>(extension);
}
```

Added new constructor allowing for much simpler `AsyncApiAny` initialization, utlizing `system.json.text` to figure out the JsonNode type.

### Changelog
- Added: `GetValue<T>()`
- Added: `GetValueOrDefault<T>()`
- Added: `TryGetValue<T>()`
- Added: static `FromExtensionOrDefault<T>(IAsyncApiExtension extension)`
- Added: new constructor to allow for easier object creation.
- Obsoleted: `AsyncApiArray`
- Obsoleted: `AsyncApiObject`
  • Loading branch information
VisualBean committed Feb 26, 2024
1 parent b58f042 commit 5ebfbf8
Show file tree
Hide file tree
Showing 8 changed files with 209 additions and 14 deletions.
21 changes: 21 additions & 0 deletions src/LEGO.AsyncAPI/Extensions/AsyncApiExtensibleExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace LEGO.AsyncAPI.Extensions
{
using System.Collections.Generic;
using LEGO.AsyncAPI.Exceptions;
using LEGO.AsyncAPI.Models;
using LEGO.AsyncAPI.Models.Interfaces;
Expand Down Expand Up @@ -38,5 +39,25 @@ public static void AddExtension<T>(this T element, string name, IAsyncApiExtensi

element.Extensions[name] = any ?? throw Error.ArgumentNull(nameof(any));
}

/// <summary>
/// Tries the get value or default.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="dictionary">The dictionary.</param>
/// <param name="key">The key.</param>
/// <param name="value">The value.</param>
/// <returns></returns>
public static bool TryGetValueOrDefault<T>(this IDictionary<string, IAsyncApiExtension> dictionary, string key, out T value)
{
if (dictionary.TryGetValue(key, out var extension))
{
value = AsyncApiAny.FromExtensionOrDefault<T>(extension);
return true;
}

value = default(T);
return false;
}
}
}
2 changes: 2 additions & 0 deletions src/LEGO.AsyncAPI/Models/Any/AsyncAPIArray.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@

namespace LEGO.AsyncAPI.Models
{
using System;
using System.Collections.ObjectModel;
using System.Text.Json.Nodes;
using LEGO.AsyncAPI.Models.Interfaces;
using LEGO.AsyncAPI.Writers;

[Obsolete("Please use AsyncApiAny instead")]
public class AsyncApiArray : Collection<AsyncApiAny>, IAsyncApiExtension, IAsyncApiElement
{

Check warning on line 13 in src/LEGO.AsyncAPI/Models/Any/AsyncAPIArray.cs

View workflow job for this annotation

GitHub Actions / Publish NuGet packages (LEGO.AsyncAPI.Bindings)


Expand Down
2 changes: 2 additions & 0 deletions src/LEGO.AsyncAPI/Models/Any/AsyncAPIObject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace LEGO.AsyncAPI.Models
{
using System;
using System.Collections.Generic;
using System.Text.Json.Nodes;
using LEGO.AsyncAPI.Models.Interfaces;
Expand All @@ -10,6 +11,7 @@ namespace LEGO.AsyncAPI.Models
/// <summary>
/// AsyncApi object.
/// </summary>
[Obsolete("Please use AsyncApiAny instead")]
public class AsyncApiObject : Dictionary<string, AsyncApiAny>, IAsyncApiExtension, IAsyncApiElement
{

Expand Down
109 changes: 107 additions & 2 deletions src/LEGO.AsyncAPI/Models/Any/AsyncApiAny.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

namespace LEGO.AsyncAPI.Models
{
using System.Collections.Generic;
using System.Text.Json;
using System.Text.Json.Nodes;
using LEGO.AsyncAPI.Models.Interfaces;
using LEGO.AsyncAPI.Writers;
Expand All @@ -13,28 +15,131 @@ namespace LEGO.AsyncAPI.Models
/// <seealso cref="LEGO.AsyncAPI.Models.Interfaces.IAsyncApiExtension" />
public class AsyncApiAny : IAsyncApiElement, IAsyncApiExtension
{
private JsonSerializerOptions options = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
};

private JsonNode node;

/// <summary>
/// Initializes a new instance of the <see cref="AsyncApiAny"/> class.
/// Initializes a new instance of the <see cref="AsyncApiAny" /> class.
/// </summary>
/// <param name="node">The node.</param>
public AsyncApiAny(JsonNode node)
{
this.node = node;
}

/// <summary>
/// Initializes a new instance of the <see cref="AsyncApiAny"/> class.
/// </summary>
/// <param name="obj">The object.</param>
public AsyncApiAny(object obj)
{
this.node = JsonNode.Parse(JsonSerializer.Serialize(obj, this.options));
}

/// <summary>
/// Initializes a new instance of the <see cref="AsyncApiAny"/> class.
/// </summary>
/// <param name="node">The node.</param>
public AsyncApiAny(JsonArray node)
{
this.node = node;
}

/// <summary>
/// Initializes a new instance of the <see cref="AsyncApiAny"/> class.
/// </summary>
/// <param name="node">The node.</param>
public AsyncApiAny(JsonObject node)
{
this.node = node;
}

/// <summary>
/// Converts to <see cref="{T}" /> from an Extension.
/// </summary>
/// <typeparam name="T">T.</typeparam>
/// <param name="extension">The extension.</param>
/// <returns><see cref="{T}"/>.</returns>
public static T FromExtensionOrDefault<T>(IAsyncApiExtension extension)
{
if (extension is AsyncApiAny any)
{
return any.GetValueOrDefault<T>();
}
else
{
return default(T);
}
}

/// <summary>
/// Gets the node.
/// </summary>
/// <returns><see cref="JsonNode"/>.</returns>
/// <value>
/// The node.
/// </value>
public JsonNode GetNode() => this.node;

/// <summary>
/// Gets the value.
/// </summary>
/// <typeparam name="T"><see cref="{T}" />.</typeparam>
/// <returns><see cref="{T}" />.</returns>
public T GetValue<T>()
{
return this.node.GetValue<T>();
if (this.node == null)
{
return default(T);
}

if (this.node is JsonValue)
{
return this.node.GetValue<T>();
}

return JsonSerializer.Deserialize<T>(this.node.ToJsonString());
}

/// <summary>
/// Gets the value or default.
/// </summary>
/// <typeparam name="T"><see cref="{T}" />.</typeparam>
/// <returns><see cref="{T}" /> or default.</returns>
public T GetValueOrDefault<T>()
{
try
{
return this.GetValue<T>();
}
catch (System.Exception)
{
return default(T);
}
}

/// <summary>
/// Tries the get value.
/// </summary>
/// <typeparam name="T"><see cref="{T}" />.</typeparam>
/// <param name="value">The value.</param>
/// <returns>true if the value could be converted, otherwise false.</returns>
public bool TryGetValue<T>(out T value)
{
try
{
value = this.GetValue<T>();
return true;
}
catch (System.Exception)
{
value = default(T);
return false;
}
}

/// <summary>
Expand Down
27 changes: 16 additions & 11 deletions test/LEGO.AsyncAPI.Tests/AsyncApiDocumentV2Tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,21 @@ namespace LEGO.AsyncAPI.Tests
using System.Globalization;
using System.IO;
using System.Linq;
using LEGO.AsyncAPI.Bindings.Pulsar;
using LEGO.AsyncAPI.Bindings;
using LEGO.AsyncAPI.Bindings.Http;
using LEGO.AsyncAPI.Bindings.Kafka;
using LEGO.AsyncAPI.Bindings.Pulsar;
using LEGO.AsyncAPI.Models;
using LEGO.AsyncAPI.Models.Interfaces;
using LEGO.AsyncAPI.Readers;
using LEGO.AsyncAPI.Writers;
using NUnit.Framework;

public class ExtensionClass
{
public string Key { get; set; }
public long OtherKey { get; set; }
}
public class AsyncApiDocumentV2Tests
{
[Test]
Expand Down Expand Up @@ -838,8 +843,6 @@ public void SerializeV2_WithFullSpec_Serializes()
string traitTitle = "traitTitle";
string schemaTitle = "schemaTitle";
string schemaDescription = "schemaDescription";
string anyKey = "key";
string anyOtherKey = "otherKey";
string anyStringValue = "value";
long anyLongValue = long.MaxValue;
string exampleSummary = "exampleSummary";
Expand All @@ -864,6 +867,8 @@ public void SerializeV2_WithFullSpec_Serializes()
string refreshUrl = "https://example.com/refresh";
string authorizationUrl = "https://example.com/authorization";
string requirementString = "requirementItem";


var document = new AsyncApiDocument()
{
Id = documentId,
Expand Down Expand Up @@ -1016,11 +1021,11 @@ public void SerializeV2_WithFullSpec_Serializes()
Description = schemaDescription,
Examples = new List<AsyncApiAny>
{
new AsyncApiObject
new AsyncApiAny(new ExtensionClass
{
{ anyKey, new AsyncApiAny(anyStringValue) },
{ anyOtherKey, new AsyncApiAny(anyLongValue) },
},
Key = anyStringValue,
OtherKey = anyLongValue,
}),
},
},
Examples = new List<AsyncApiMessageExample>
Expand All @@ -1029,11 +1034,11 @@ public void SerializeV2_WithFullSpec_Serializes()
{
Summary = exampleSummary,
Name = exampleName,
Payload = new AsyncApiObject
Payload =new AsyncApiAny(new ExtensionClass
{
{ anyKey, new AsyncApiAny(anyStringValue) },
{ anyOtherKey, new AsyncApiAny(anyLongValue) },
},
Key = anyStringValue,
OtherKey = anyLongValue,
}),
Extensions = new Dictionary<string, IAsyncApiExtension>
{
{ extensionKey, new AsyncApiAny(extensionString) },
Expand Down
7 changes: 7 additions & 0 deletions test/LEGO.AsyncAPI.Tests/Bindings/Sns/SnsBindings_Should.cs
Original file line number Diff line number Diff line change
Expand Up @@ -381,12 +381,19 @@ public void SnsOperationBinding_WithFilledObject_SerializesAndDeserializes()
var settings = new AsyncApiReaderSettings();
settings.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);
var val = AsyncApiAny.FromExtensionOrDefault<ExtensionClass>(any);

// Assert
Assert.AreEqual(actual, expected);

var expectedSnsBinding = (SnsOperationBinding)operation.Bindings.Values.First();
expectedSnsBinding.Should().BeEquivalentTo((SnsOperationBinding)binding.Bindings.Values.First(), options => options.IgnoringCyclicReferences());
}
class ExtensionClass
{
public string bindingXPropertyName { get; set; }
}
}
}
1 change: 0 additions & 1 deletion test/LEGO.AsyncAPI.Tests/LEGO.AsyncAPI.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="xunit" Version="2.4.1" />
</ItemGroup>

<ItemGroup>
Expand Down
54 changes: 54 additions & 0 deletions test/LEGO.AsyncAPI.Tests/Models/AsyncApiAnyTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
using LEGO.AsyncAPI.Models;
using NUnit.Framework;
using System;
using System.Collections.Generic;
using System.Linq;

namespace LEGO.AsyncAPI.Tests
{

public class AsyncApiAnyTests
{
[Test]
public void GetValue_ReturnsCorrectConversions()
{
// Arrange
// Act
var a = new AsyncApiAny("string");
var b = new AsyncApiAny(1);
var c = new AsyncApiAny(1.1);
var d = new AsyncApiAny(true);
var e = new AsyncApiAny(new MyType("test"));
var f = new AsyncApiAny(new List<string>() { "test", "test2"});
var g = new AsyncApiAny(new List<string>() { "test", "test2"}.AsEnumerable());
var h = new AsyncApiAny(new List<MyType>() { new MyType("test") });
var i = new AsyncApiAny(new Dictionary<string, int>() { { "t", 2 } });
var j = new AsyncApiAny(new Dictionary<string, MyType>() { { "t", new MyType("test") } });

// Assert
Assert.AreEqual("string", a.GetValue<string>());
Assert.AreEqual(1, b.GetValue<int>());
Assert.AreEqual(1.1, c.GetValue<double>());
Assert.AreEqual(true, d.GetValue<bool>());
Assert.NotNull(e.GetValue<MyType>());
Assert.IsNotEmpty(f.GetValue<List<string>>());
Assert.IsNotEmpty(f.GetValue<IEnumerable<string>>());
Assert.IsNotEmpty(g.GetValue<List<string>>());
Assert.IsNotEmpty(g.GetValue<IEnumerable<string>>());
Assert.IsNotEmpty(h.GetValue<List<MyType>>());
Assert.IsNotEmpty(h.GetValue<IEnumerable<MyType>>());
Assert.IsNotEmpty(i.GetValue<Dictionary<string, int>>());
Assert.IsNotEmpty(j.GetValue<Dictionary<string, MyType>>());
}

class MyType
{
public MyType(string value)
{
this.Value = value;
}

public string Value { get; set; }
}
}
}

0 comments on commit 5ebfbf8

Please sign in to comment.