Skip to content

Commit 9063f4e

Browse files
committed
feat: improve AsyncApiAny api surface.
## 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`
1 parent b58f042 commit 9063f4e

File tree

9 files changed

+211
-16
lines changed

9 files changed

+211
-16
lines changed

Diff for: src/LEGO.AsyncAPI/Extensions/AsyncApiExtensibleExtensions.cs

+21
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace LEGO.AsyncAPI.Extensions
44
{
5+
using System.Collections.Generic;
56
using LEGO.AsyncAPI.Exceptions;
67
using LEGO.AsyncAPI.Models;
78
using LEGO.AsyncAPI.Models.Interfaces;
@@ -38,5 +39,25 @@ public static void AddExtension<T>(this T element, string name, IAsyncApiExtensi
3839

3940
element.Extensions[name] = any ?? throw Error.ArgumentNull(nameof(any));
4041
}
42+
43+
/// <summary>
44+
/// Tries the get value or default.
45+
/// </summary>
46+
/// <typeparam name="T"></typeparam>
47+
/// <param name="dictionary">The dictionary.</param>
48+
/// <param name="key">The key.</param>
49+
/// <param name="value">The value.</param>
50+
/// <returns></returns>
51+
public static bool TryGetValueOrDefault<T>(this IDictionary<string, IAsyncApiExtension> dictionary, string key, out T value)
52+
{
53+
if (dictionary.TryGetValue(key, out var extension))
54+
{
55+
value = AsyncApiAny.FromExtensionOrDefault<T>(extension);
56+
return true;
57+
}
58+
59+
value = default(T);
60+
return false;
61+
}
4162
}
4263
}

Diff for: src/LEGO.AsyncAPI/Models/Any/AsyncAPIArray.cs

+2
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@
22

33
namespace LEGO.AsyncAPI.Models
44
{
5+
using System;
56
using System.Collections.ObjectModel;
67
using System.Text.Json.Nodes;
78
using LEGO.AsyncAPI.Models.Interfaces;
89
using LEGO.AsyncAPI.Writers;
910

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

Diff for: src/LEGO.AsyncAPI/Models/Any/AsyncAPIObject.cs

+2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace LEGO.AsyncAPI.Models
44
{
5+
using System;
56
using System.Collections.Generic;
67
using System.Text.Json.Nodes;
78
using LEGO.AsyncAPI.Models.Interfaces;
@@ -10,6 +11,7 @@ namespace LEGO.AsyncAPI.Models
1011
/// <summary>
1112
/// AsyncApi object.
1213
/// </summary>
14+
[Obsolete("Please use AsyncApiAny instead")]
1315
public class AsyncApiObject : Dictionary<string, AsyncApiAny>, IAsyncApiExtension, IAsyncApiElement
1416
{
1517

Diff for: src/LEGO.AsyncAPI/Models/Any/AsyncApiAny.cs

+107-2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
namespace LEGO.AsyncAPI.Models
44
{
5+
using System.Collections.Generic;
6+
using System.Text.Json;
57
using System.Text.Json.Nodes;
68
using LEGO.AsyncAPI.Models.Interfaces;
79
using LEGO.AsyncAPI.Writers;
@@ -13,28 +15,131 @@ namespace LEGO.AsyncAPI.Models
1315
/// <seealso cref="LEGO.AsyncAPI.Models.Interfaces.IAsyncApiExtension" />
1416
public class AsyncApiAny : IAsyncApiElement, IAsyncApiExtension
1517
{
18+
private JsonSerializerOptions options = new JsonSerializerOptions
19+
{
20+
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
21+
};
22+
1623
private JsonNode node;
1724

1825
/// <summary>
19-
/// Initializes a new instance of the <see cref="AsyncApiAny"/> class.
26+
/// Initializes a new instance of the <see cref="AsyncApiAny" /> class.
2027
/// </summary>
2128
/// <param name="node">The node.</param>
2229
public AsyncApiAny(JsonNode node)
2330
{
2431
this.node = node;
2532
}
2633

34+
/// <summary>
35+
/// Initializes a new instance of the <see cref="AsyncApiAny"/> class.
36+
/// </summary>
37+
/// <param name="obj">The object.</param>
38+
public AsyncApiAny(object obj)
39+
{
40+
this.node = JsonNode.Parse(JsonSerializer.Serialize(obj, this.options));
41+
}
42+
43+
/// <summary>
44+
/// Initializes a new instance of the <see cref="AsyncApiAny"/> class.
45+
/// </summary>
46+
/// <param name="node">The node.</param>
47+
public AsyncApiAny(JsonArray node)
48+
{
49+
this.node = node;
50+
}
51+
52+
/// <summary>
53+
/// Initializes a new instance of the <see cref="AsyncApiAny"/> class.
54+
/// </summary>
55+
/// <param name="node">The node.</param>
56+
public AsyncApiAny(JsonObject node)
57+
{
58+
this.node = node;
59+
}
60+
61+
/// <summary>
62+
/// Converts to <see cref="{T}" /> from an Extension.
63+
/// </summary>
64+
/// <typeparam name="T">T.</typeparam>
65+
/// <param name="extension">The extension.</param>
66+
/// <returns><see cref="{T}"/>.</returns>
67+
public static T FromExtensionOrDefault<T>(IAsyncApiExtension extension)
68+
{
69+
if (extension is AsyncApiAny any)
70+
{
71+
return any.GetValueOrDefault<T>();
72+
}
73+
else
74+
{
75+
return default(T);
76+
}
77+
}
78+
2779
/// <summary>
2880
/// Gets the node.
2981
/// </summary>
82+
/// <returns><see cref="JsonNode"/>.</returns>
3083
/// <value>
3184
/// The node.
3285
/// </value>
3386
public JsonNode GetNode() => this.node;
3487

88+
/// <summary>
89+
/// Gets the value.
90+
/// </summary>
91+
/// <typeparam name="T"><see cref="{T}" />.</typeparam>
92+
/// <returns><see cref="{T}" />.</returns>
3593
public T GetValue<T>()
3694
{
37-
return this.node.GetValue<T>();
95+
if (this.node == null)
96+
{
97+
return default(T);
98+
}
99+
100+
if (this.node is JsonValue)
101+
{
102+
return this.node.GetValue<T>();
103+
}
104+
105+
return JsonSerializer.Deserialize<T>(this.node.ToJsonString());
106+
}
107+
108+
/// <summary>
109+
/// Gets the value or default.
110+
/// </summary>
111+
/// <typeparam name="T"><see cref="{T}" />.</typeparam>
112+
/// <returns><see cref="{T}" /> or default.</returns>
113+
public T GetValueOrDefault<T>()
114+
{
115+
try
116+
{
117+
return this.GetValue<T>();
118+
}
119+
catch (System.Exception)
120+
{
121+
return default(T);
122+
}
123+
}
124+
125+
/// <summary>
126+
/// Tries the get value.
127+
/// </summary>
128+
/// <typeparam name="T"><see cref="{T}" />.</typeparam>
129+
/// <param name="value">The value.</param>
130+
/// <returns>true if the value could be converted, otherwise false.</returns>
131+
public bool TryGetValue<T>(out T value)
132+
{
133+
try
134+
{
135+
value = this.GetValue<T>();
136+
return true;
137+
}
138+
catch (System.Exception)
139+
{
140+
value = default(T);
141+
return false;
142+
}
38143
}
39144

40145
/// <summary>

Diff for: test/LEGO.AsyncAPI.Tests/AsyncApiDocumentV2Tests.cs

+16-11
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,21 @@ namespace LEGO.AsyncAPI.Tests
77
using System.Globalization;
88
using System.IO;
99
using System.Linq;
10-
using LEGO.AsyncAPI.Bindings.Pulsar;
1110
using LEGO.AsyncAPI.Bindings;
1211
using LEGO.AsyncAPI.Bindings.Http;
1312
using LEGO.AsyncAPI.Bindings.Kafka;
13+
using LEGO.AsyncAPI.Bindings.Pulsar;
1414
using LEGO.AsyncAPI.Models;
1515
using LEGO.AsyncAPI.Models.Interfaces;
1616
using LEGO.AsyncAPI.Readers;
1717
using LEGO.AsyncAPI.Writers;
1818
using NUnit.Framework;
1919

20+
public class ExtensionClass
21+
{
22+
public string Key { get; set; }
23+
public long OtherKey { get; set; }
24+
}
2025
public class AsyncApiDocumentV2Tests
2126
{
2227
[Test]
@@ -838,8 +843,6 @@ public void SerializeV2_WithFullSpec_Serializes()
838843
string traitTitle = "traitTitle";
839844
string schemaTitle = "schemaTitle";
840845
string schemaDescription = "schemaDescription";
841-
string anyKey = "key";
842-
string anyOtherKey = "otherKey";
843846
string anyStringValue = "value";
844847
long anyLongValue = long.MaxValue;
845848
string exampleSummary = "exampleSummary";
@@ -864,6 +867,8 @@ public void SerializeV2_WithFullSpec_Serializes()
864867
string refreshUrl = "https://example.com/refresh";
865868
string authorizationUrl = "https://example.com/authorization";
866869
string requirementString = "requirementItem";
870+
871+
867872
var document = new AsyncApiDocument()
868873
{
869874
Id = documentId,
@@ -1016,11 +1021,11 @@ public void SerializeV2_WithFullSpec_Serializes()
10161021
Description = schemaDescription,
10171022
Examples = new List<AsyncApiAny>
10181023
{
1019-
new AsyncApiObject
1024+
new AsyncApiAny(new ExtensionClass
10201025
{
1021-
{ anyKey, new AsyncApiAny(anyStringValue) },
1022-
{ anyOtherKey, new AsyncApiAny(anyLongValue) },
1023-
},
1026+
Key = anyStringValue,
1027+
OtherKey = anyLongValue,
1028+
}),
10241029
},
10251030
},
10261031
Examples = new List<AsyncApiMessageExample>
@@ -1029,11 +1034,11 @@ public void SerializeV2_WithFullSpec_Serializes()
10291034
{
10301035
Summary = exampleSummary,
10311036
Name = exampleName,
1032-
Payload = new AsyncApiObject
1037+
Payload =new AsyncApiAny(new ExtensionClass
10331038
{
1034-
{ anyKey, new AsyncApiAny(anyStringValue) },
1035-
{ anyOtherKey, new AsyncApiAny(anyLongValue) },
1036-
},
1039+
Key = anyStringValue,
1040+
OtherKey = anyLongValue,
1041+
}),
10371042
Extensions = new Dictionary<string, IAsyncApiExtension>
10381043
{
10391044
{ extensionKey, new AsyncApiAny(extensionString) },

Diff for: test/LEGO.AsyncAPI.Tests/AsyncApiReaderTests.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,9 @@ public void Read_WithExtensionParser_Parses()
4040
";
4141
Func<AsyncApiAny, IAsyncApiExtension> valueExtensionParser = (any) =>
4242
{
43-
if (any.GetNode() is JsonValue value)
43+
if (any.TryGetValue<string>(out var value))
4444
{
45-
if (value.GetScalarValue() == "onetwothreefour")
45+
if (value == "onetwothreefour")
4646
{
4747
return new AsyncApiAny(1234);
4848
}

Diff for: test/LEGO.AsyncAPI.Tests/Bindings/Sns/SnsBindings_Should.cs

+7
Original file line numberDiff line numberDiff line change
@@ -381,12 +381,19 @@ public void SnsOperationBinding_WithFilledObject_SerializesAndDeserializes()
381381
var settings = new AsyncApiReaderSettings();
382382
settings.Bindings = BindingsCollection.Sns;
383383
var binding = new AsyncApiStringReader(settings).ReadFragment<AsyncApiOperation>(actual, AsyncApiVersion.AsyncApi2_0, out _);
384+
var binding2 = new AsyncApiStringReader(settings).ReadFragment<AsyncApiOperation>(expected, AsyncApiVersion.AsyncApi2_0, out _);
385+
binding2.Bindings.First().Value.Extensions.TryGetValue("x-bindingExtension", out IAsyncApiExtension any);
386+
var val = AsyncApiAny.FromExtensionOrDefault<ExtensionClass>(any);
384387

385388
// Assert
386389
Assert.AreEqual(actual, expected);
387390

388391
var expectedSnsBinding = (SnsOperationBinding)operation.Bindings.Values.First();
389392
expectedSnsBinding.Should().BeEquivalentTo((SnsOperationBinding)binding.Bindings.Values.First(), options => options.IgnoringCyclicReferences());
390393
}
394+
class ExtensionClass
395+
{
396+
public string bindingXPropertyName { get; set; }
397+
}
391398
}
392399
}

Diff for: test/LEGO.AsyncAPI.Tests/LEGO.AsyncAPI.Tests.csproj

-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424
<PrivateAssets>all</PrivateAssets>
2525
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
2626
</PackageReference>
27-
<PackageReference Include="xunit" Version="2.4.1" />
2827
</ItemGroup>
2928

3029
<ItemGroup>

Diff for: test/LEGO.AsyncAPI.Tests/Models/AsyncApiAnyTests.cs

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
using LEGO.AsyncAPI.Models;
2+
using NUnit.Framework;
3+
using System;
4+
using System.Collections.Generic;
5+
using System.Linq;
6+
7+
namespace LEGO.AsyncAPI.Tests
8+
{
9+
10+
public class AsyncApiAnyTests
11+
{
12+
[Test]
13+
public void GetValue_ReturnsCorrectConversions()
14+
{
15+
// Arrange
16+
// Act
17+
var a = new AsyncApiAny("string");
18+
var b = new AsyncApiAny(1);
19+
var c = new AsyncApiAny(1.1);
20+
var d = new AsyncApiAny(true);
21+
var e = new AsyncApiAny(new MyType("test"));
22+
var f = new AsyncApiAny(new List<string>() { "test", "test2"});
23+
var g = new AsyncApiAny(new List<string>() { "test", "test2"}.AsEnumerable());
24+
var h = new AsyncApiAny(new List<MyType>() { new MyType("test") });
25+
var i = new AsyncApiAny(new Dictionary<string, int>() { { "t", 2 } });
26+
var j = new AsyncApiAny(new Dictionary<string, MyType>() { { "t", new MyType("test") } });
27+
28+
// Assert
29+
Assert.AreEqual("string", a.GetValue<string>());
30+
Assert.AreEqual(1, b.GetValue<int>());
31+
Assert.AreEqual(1.1, c.GetValue<double>());
32+
Assert.AreEqual(true, d.GetValue<bool>());
33+
Assert.NotNull(e.GetValue<MyType>());
34+
Assert.IsNotEmpty(f.GetValue<List<string>>());
35+
Assert.IsNotEmpty(f.GetValue<IEnumerable<string>>());
36+
Assert.IsNotEmpty(g.GetValue<List<string>>());
37+
Assert.IsNotEmpty(g.GetValue<IEnumerable<string>>());
38+
Assert.IsNotEmpty(h.GetValue<List<MyType>>());
39+
Assert.IsNotEmpty(h.GetValue<IEnumerable<MyType>>());
40+
Assert.IsNotEmpty(i.GetValue<Dictionary<string, int>>());
41+
Assert.IsNotEmpty(j.GetValue<Dictionary<string, MyType>>());
42+
}
43+
44+
class MyType
45+
{
46+
public MyType(string value)
47+
{
48+
this.Value = value;
49+
}
50+
51+
public string Value { get; set; }
52+
}
53+
}
54+
}

0 commit comments

Comments
 (0)