diff --git a/.github/workflows/release-internal.yml b/.github/workflows/release-internal.yml
index 971be8e7..1231b9e6 100644
--- a/.github/workflows/release-internal.yml
+++ b/.github/workflows/release-internal.yml
@@ -1,13 +1,5 @@
name: Publish beta NuGet package
on:
- push:
- branches: [ main ]
- paths:
- - 'src/LEGO.AsyncAPI/**'
- - 'src/LEGO.AsyncAPI.Readers/**'
- - 'src/LEGO.AsyncAPI.Bindings/**'
- - ".github/workflows/release-internal.yml"
- - '!**/*.md'
workflow_dispatch:
jobs:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 90780c52..505c9b09 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,10 @@
+## [5.2.1](https://github.com/LEGO/AsyncAPI.NET/compare/v5.2.0...v5.2.1) (2024-06-12)
+
+
+### Bug Fixes
+
+* inline channel parameters should not deserialize as references ([#172](https://github.com/LEGO/AsyncAPI.NET/issues/172)) ([7fd3af0](https://github.com/LEGO/AsyncAPI.NET/commit/7fd3af0e6669d18e805e0bab9cafac819ea64c1d))
+
# [5.2.0](https://github.com/LEGO/AsyncAPI.NET/compare/v5.1.1...v5.2.0) (2024-03-30)
diff --git a/Common.Build.props b/Common.Build.props
index 78aa4707..ba84a0f8 100644
--- a/Common.Build.props
+++ b/Common.Build.props
@@ -9,5 +9,12 @@
README.md
https://github.com/LEGO/AsyncAPI.NET
asyncapi .net openapi documentation
+ logo.png
+ LICENSE.txt
+
+
+
+
+
\ No newline at end of file
diff --git a/LICENSE b/LICENSE.txt
similarity index 100%
rename from LICENSE
rename to LICENSE.txt
diff --git a/README.md b/README.md
index fe9189f5..fa46b5c5 100644
--- a/README.md
+++ b/README.md
@@ -15,11 +15,11 @@ The AsyncAPI.NET SDK contains a useful object model for the AsyncAPI specificati
Install the NuGet packages:
### AsyncAPI.NET
[](https://www.nuget.org/packages/AsyncAPI.NET/)
-[](https://www.nuget.org/packages/AsyncAPI.NET/)
+[](https://www.nuget.org/packages/AsyncAPI.NET/)
### AsyncAPI.Readers
[](https://www.nuget.org/packages/AsyncAPI.NET.Readers/)
-[](https://www.nuget.org/packages/AsyncAPI.NET.Readers/)
+[](https://www.nuget.org/packages/AsyncAPI.NET.Readers/)
### AsyncAPI.Bindings
[](https://www.nuget.org/packages/AsyncAPI.NET.Bindings/)
@@ -78,6 +78,48 @@ var stream = await httpClient.GetStreamAsync("master/examples/streetlights-kafka
var asyncApiDocument = new AsyncApiStreamReader().Read(stream, out var diagnostic);
```
+#### Reading External $ref
+
+You can read externally referenced AsyncAPI documents by setting the `ReferenceResolution` property of the `AsyncApiReaderSettings` object to `ReferenceResolutionSetting.ResolveAllReferences` and providing an implementation for the `IAsyncApiExternalReferenceReader` interface. This interface contains a single method to which the built AsyncAPI.NET reader library will pass the location content contained in a `$ref` property (usually some form of path) and interface will return the content which is retrieved from wherever the `$ref` points to as a `string`. The AsyncAPI.NET reader will then automatically infer the `T` type of the content and recursively parse the external content into an AsyncAPI document as a child of the original document that contained the `$ref`. This means that you can have externally referenced documents that themselves contain external references.
+
+This interface allows users to load the content of their external reference however and from whereever is required. A new instance of the implementor of `IAsyncApiExternalReferenceReader` should be registered with the `ExternalReferenceReader` property of the `AsyncApiReaderSettings` when creating the reader from which the `GetExternalResource` method will be called when resolving external references.
+
+Below is a very simple example of implementation for `IAsyncApiExternalReferenceReader` that simply loads a file and returns it as a string found at the reference endpoint.
+```csharp
+using System.IO;
+
+public class AsyncApiExternalFileSystemReader : IAsyncApiExternalReferenceReader
+{
+ public string GetExternalResource(string reference)
+ {
+ return File.ReadAllText(reference);
+ }
+}
+```
+
+This can then be configured in the reader as follows:
+```csharp
+var settings = new AsyncApiReaderSettings
+{
+ ReferenceResolution = ReferenceResolutionSetting.ResolveAllReferences,
+ ExternalReferenceReader = new AsyncApiExternalFileSystemReader(),
+};
+var reader = new AsyncApiStringReader(settings);
+```
+
+This would function for a AsyncAPI document with following reference to load the content of `message.yaml` as a `AsyncApiMessage` object inline with the document object.
+```yaml
+asyncapi: 2.3.0
+info:
+ title: test
+ version: 1.0.0
+channels:
+ workspace:
+ publish:
+ message:
+ $ref: "../../../message.yaml"
+```
+
### Bindings
To add support for reading bindings, simply add the bindings you wish to support, to the `Bindings` collection of `AsyncApiReaderSettings`.
There is a nifty helper to add different types of bindings, or like in the example `All` of them.
diff --git a/media/logo.png b/media/logo.png
new file mode 100644
index 00000000..1a25d72c
Binary files /dev/null and b/media/logo.png differ
diff --git a/src/LEGO.AsyncAPI.Bindings/AMQP/AMQPOperationBinding.cs b/src/LEGO.AsyncAPI.Bindings/AMQP/AMQPOperationBinding.cs
index 86dc74ef..3a2e6a35 100644
--- a/src/LEGO.AsyncAPI.Bindings/AMQP/AMQPOperationBinding.cs
+++ b/src/LEGO.AsyncAPI.Bindings/AMQP/AMQPOperationBinding.cs
@@ -5,7 +5,6 @@ namespace LEGO.AsyncAPI.Bindings.AMQP
using System;
using System.Collections.Generic;
using LEGO.AsyncAPI.Models;
- using LEGO.AsyncAPI.Readers;
using LEGO.AsyncAPI.Readers.ParseNodes;
using LEGO.AsyncAPI.Writers;
diff --git a/src/LEGO.AsyncAPI.Bindings/BindingsCollection.cs b/src/LEGO.AsyncAPI.Bindings/BindingsCollection.cs
index 4fd5560f..83faf695 100644
--- a/src/LEGO.AsyncAPI.Bindings/BindingsCollection.cs
+++ b/src/LEGO.AsyncAPI.Bindings/BindingsCollection.cs
@@ -45,7 +45,7 @@ public static TCollection Add(
return destination;
}
- public static IEnumerable> All => new List>
+ public static ICollection> All => new List>
{
Pulsar,
Kafka,
@@ -57,18 +57,18 @@ public static TCollection Add(
MQTT,
};
- public static IEnumerable> Http => new List>
+ public static ICollection> Http => new List>
{
new HttpOperationBinding(),
new HttpMessageBinding(),
};
- public static IEnumerable> Websockets => new List>
+ public static ICollection> Websockets => new List>
{
new WebSocketsChannelBinding(),
};
- public static IEnumerable> Kafka => new List>
+ public static ICollection> Kafka => new List>
{
new KafkaServerBinding(),
new KafkaChannelBinding(),
@@ -76,32 +76,32 @@ public static TCollection Add(
new KafkaMessageBinding(),
};
- public static IEnumerable> Pulsar => new List>
+ public static ICollection> Pulsar => new List>
{
new PulsarServerBinding(),
new PulsarChannelBinding(),
};
- public static IEnumerable> Sqs => new List>
+ public static ICollection> Sqs => new List>
{
new SqsChannelBinding(),
new SqsOperationBinding(),
};
- public static IEnumerable> Sns => new List>
+ public static ICollection> Sns => new List>
{
new SnsChannelBinding(),
new SnsOperationBinding(),
};
- public static IEnumerable> AMQP => new List>
+ public static ICollection> AMQP => new List>
{
new AMQPChannelBinding(),
new AMQPOperationBinding(),
new AMQPMessageBinding(),
};
- public static IEnumerable> MQTT => new List>
+ public static ICollection> MQTT => new List>
{
new MQTTServerBinding(),
new MQTTOperationBinding(),
diff --git a/src/LEGO.AsyncAPI.Bindings/Http/HttpMessageBinding.cs b/src/LEGO.AsyncAPI.Bindings/Http/HttpMessageBinding.cs
index 7a5731ef..cbf65bc4 100644
--- a/src/LEGO.AsyncAPI.Bindings/Http/HttpMessageBinding.cs
+++ b/src/LEGO.AsyncAPI.Bindings/Http/HttpMessageBinding.cs
@@ -42,7 +42,7 @@ public override void SerializeProperties(IAsyncApiWriter writer)
protected override FixedFieldMap FixedFieldMap => new()
{
{ "bindingVersion", (a, n) => { a.BindingVersion = n.GetScalarValue(); } },
- { "headers", (a, n) => { a.Headers = JsonSchemaDeserializer.LoadSchema(n); } },
+ { "headers", (a, n) => { a.Headers = AsyncApiSchemaDeserializer.LoadSchema(n); } },
};
}
}
diff --git a/src/LEGO.AsyncAPI.Bindings/Http/HttpOperationBinding.cs b/src/LEGO.AsyncAPI.Bindings/Http/HttpOperationBinding.cs
index f70858c2..7b88d31b 100644
--- a/src/LEGO.AsyncAPI.Bindings/Http/HttpOperationBinding.cs
+++ b/src/LEGO.AsyncAPI.Bindings/Http/HttpOperationBinding.cs
@@ -63,7 +63,7 @@ public override void SerializeProperties(IAsyncApiWriter writer)
{ "bindingVersion", (a, n) => { a.BindingVersion = n.GetScalarValue(); } },
{ "type", (a, n) => { a.Type = n.GetScalarValue().GetEnumFromDisplayName(); } },
{ "method", (a, n) => { a.Method = n.GetScalarValue(); } },
- { "query", (a, n) => { a.Query = JsonSchemaDeserializer.LoadSchema(n); } },
+ { "query", (a, n) => { a.Query = AsyncApiSchemaDeserializer.LoadSchema(n); } },
};
public override string BindingKey => "http";
diff --git a/src/LEGO.AsyncAPI.Bindings/Kafka/KafkaMessageBinding.cs b/src/LEGO.AsyncAPI.Bindings/Kafka/KafkaMessageBinding.cs
index 2f665560..2e57b50a 100644
--- a/src/LEGO.AsyncAPI.Bindings/Kafka/KafkaMessageBinding.cs
+++ b/src/LEGO.AsyncAPI.Bindings/Kafka/KafkaMessageBinding.cs
@@ -67,7 +67,7 @@ public override void SerializeProperties(IAsyncApiWriter writer)
protected override FixedFieldMap FixedFieldMap => new()
{
{ "bindingVersion", (a, n) => { a.BindingVersion = n.GetScalarValue(); } },
- { "key", (a, n) => { a.Key = JsonSchemaDeserializer.LoadSchema(n); } },
+ { "key", (a, n) => { a.Key = AsyncApiSchemaDeserializer.LoadSchema(n); } },
{ "schemaIdLocation", (a, n) => { a.SchemaIdLocation = n.GetScalarValue(); } },
{ "schemaIdPayloadEncoding", (a, n) => { a.SchemaIdPayloadEncoding = n.GetScalarValue(); } },
{ "schemaLookupStrategy", (a, n) => { a.SchemaLookupStrategy = n.GetScalarValue(); } },
diff --git a/src/LEGO.AsyncAPI.Bindings/Kafka/KafkaOperationBinding.cs b/src/LEGO.AsyncAPI.Bindings/Kafka/KafkaOperationBinding.cs
index 53db7ae0..b6c6e046 100644
--- a/src/LEGO.AsyncAPI.Bindings/Kafka/KafkaOperationBinding.cs
+++ b/src/LEGO.AsyncAPI.Bindings/Kafka/KafkaOperationBinding.cs
@@ -28,8 +28,8 @@ public class KafkaOperationBinding : OperationBinding
protected override FixedFieldMap FixedFieldMap => new()
{
{ "bindingVersion", (a, n) => { a.BindingVersion = n.GetScalarValue(); } },
- { "groupId", (a, n) => { a.GroupId = JsonSchemaDeserializer.LoadSchema(n); } },
- { "clientId", (a, n) => { a.ClientId = JsonSchemaDeserializer.LoadSchema(n); } },
+ { "groupId", (a, n) => { a.GroupId = AsyncApiSchemaDeserializer.LoadSchema(n); } },
+ { "clientId", (a, n) => { a.ClientId = AsyncApiSchemaDeserializer.LoadSchema(n); } },
};
///
diff --git a/src/LEGO.AsyncAPI.Bindings/LEGO.AsyncAPI.Bindings.csproj b/src/LEGO.AsyncAPI.Bindings/LEGO.AsyncAPI.Bindings.csproj
index dc76c2ba..9c0b025e 100644
--- a/src/LEGO.AsyncAPI.Bindings/LEGO.AsyncAPI.Bindings.csproj
+++ b/src/LEGO.AsyncAPI.Bindings/LEGO.AsyncAPI.Bindings.csproj
@@ -20,12 +20,7 @@
runtime; build; native; contentfiles; analyzers; buildtransitive
-
-
- True
- \
-
-
+
diff --git a/src/LEGO.AsyncAPI.Bindings/MQTT/LastWill.cs b/src/LEGO.AsyncAPI.Bindings/MQTT/LastWill.cs
index 23118bfa..955ad90f 100644
--- a/src/LEGO.AsyncAPI.Bindings/MQTT/LastWill.cs
+++ b/src/LEGO.AsyncAPI.Bindings/MQTT/LastWill.cs
@@ -2,9 +2,9 @@
namespace LEGO.AsyncAPI.Bindings.MQTT
{
+ using System;
using LEGO.AsyncAPI.Models.Interfaces;
using LEGO.AsyncAPI.Writers;
- using System;
public class LastWill : IAsyncApiElement
{
diff --git a/src/LEGO.AsyncAPI.Bindings/MQTT/MQTTMessageBinding.cs b/src/LEGO.AsyncAPI.Bindings/MQTT/MQTTMessageBinding.cs
index b48e5ae9..597cc4c7 100644
--- a/src/LEGO.AsyncAPI.Bindings/MQTT/MQTTMessageBinding.cs
+++ b/src/LEGO.AsyncAPI.Bindings/MQTT/MQTTMessageBinding.cs
@@ -57,7 +57,7 @@ public override void SerializeProperties(IAsyncApiWriter writer)
protected override FixedFieldMap FixedFieldMap => new()
{
{ "payloadFormatIndicator", (a, n) => { a.PayloadFormatIndicator = n.GetIntegerValueOrDefault(); } },
- { "correlationData", (a, n) => { a.CorrelationData = JsonSchemaDeserializer.LoadSchema(n); } },
+ { "correlationData", (a, n) => { a.CorrelationData = AsyncApiSchemaDeserializer.LoadSchema(n); } },
{ "contentType", (a, n) => { a.ContentType = n.GetScalarValue(); } },
{ "responseTopic", (a, n) => { a.ResponseTopic = n.GetScalarValue(); } },
};
diff --git a/src/LEGO.AsyncAPI.Bindings/MQTT/MQTTOperationBinding.cs b/src/LEGO.AsyncAPI.Bindings/MQTT/MQTTOperationBinding.cs
index d3155ecd..f58eede9 100644
--- a/src/LEGO.AsyncAPI.Bindings/MQTT/MQTTOperationBinding.cs
+++ b/src/LEGO.AsyncAPI.Bindings/MQTT/MQTTOperationBinding.cs
@@ -3,9 +3,6 @@
namespace LEGO.AsyncAPI.Bindings.MQTT
{
using System;
- using System.Collections.Generic;
- using LEGO.AsyncAPI.Models;
- using LEGO.AsyncAPI.Readers;
using LEGO.AsyncAPI.Readers.ParseNodes;
using LEGO.AsyncAPI.Writers;
diff --git a/src/LEGO.AsyncAPI.Bindings/Sns/Ordering.cs b/src/LEGO.AsyncAPI.Bindings/Sns/Ordering.cs
index 23f69f52..d44a0c9e 100644
--- a/src/LEGO.AsyncAPI.Bindings/Sns/Ordering.cs
+++ b/src/LEGO.AsyncAPI.Bindings/Sns/Ordering.cs
@@ -11,7 +11,7 @@ namespace LEGO.AsyncAPI.Bindings.Sns
public class Ordering : IAsyncApiExtensible
{
///
- /// What type of SNS Topic is this?
+ /// What type of SNS Topic is this?.
///
public OrderingType Type { get; set; }
diff --git a/src/LEGO.AsyncAPI.Bindings/Sns/Statement.cs b/src/LEGO.AsyncAPI.Bindings/Sns/Statement.cs
index 7f3771f0..c92eaf94 100644
--- a/src/LEGO.AsyncAPI.Bindings/Sns/Statement.cs
+++ b/src/LEGO.AsyncAPI.Bindings/Sns/Statement.cs
@@ -19,7 +19,7 @@ public class Statement : IAsyncApiExtensible
public StringOrStringList 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; }
diff --git a/src/LEGO.AsyncAPI.Bindings/Sqs/Queue.cs b/src/LEGO.AsyncAPI.Bindings/Sqs/Queue.cs
index 33166af5..eb3d256a 100644
--- a/src/LEGO.AsyncAPI.Bindings/Sqs/Queue.cs
+++ b/src/LEGO.AsyncAPI.Bindings/Sqs/Queue.cs
@@ -16,7 +16,7 @@ public class Queue : IAsyncApiExtensible
public string Name { get; set; }
///
- /// Is this a FIFO queue?
+ /// Is this a FIFO queue?.
///
public bool FifoQueue { get; set; }
@@ -56,7 +56,7 @@ public class Queue : IAsyncApiExtensible
public RedrivePolicy RedrivePolicy { get; set; }
///
- /// The security policy for the SQS Queue
+ /// The security policy for the SQS Queue.
///
public Policy Policy { get; set; }
diff --git a/src/LEGO.AsyncAPI.Bindings/Sqs/RedrivePolicy.cs b/src/LEGO.AsyncAPI.Bindings/Sqs/RedrivePolicy.cs
index 923d668c..538b1edf 100644
--- a/src/LEGO.AsyncAPI.Bindings/Sqs/RedrivePolicy.cs
+++ b/src/LEGO.AsyncAPI.Bindings/Sqs/RedrivePolicy.cs
@@ -3,9 +3,9 @@
namespace LEGO.AsyncAPI.Bindings.Sqs
{
using System;
+ using System.Collections.Generic;
using LEGO.AsyncAPI.Models.Interfaces;
using LEGO.AsyncAPI.Writers;
- using System.Collections.Generic;
public class RedrivePolicy : IAsyncApiExtensible
{
diff --git a/src/LEGO.AsyncAPI.Bindings/Sqs/SqsOperationBinding.cs b/src/LEGO.AsyncAPI.Bindings/Sqs/SqsOperationBinding.cs
index d8eb43dd..98110209 100644
--- a/src/LEGO.AsyncAPI.Bindings/Sqs/SqsOperationBinding.cs
+++ b/src/LEGO.AsyncAPI.Bindings/Sqs/SqsOperationBinding.cs
@@ -10,7 +10,7 @@ namespace LEGO.AsyncAPI.Bindings.Sqs
public class SqsOperationBinding : OperationBinding
{
///
- /// Queue objects that are either the endpoint for an SNS Operation Binding Object, or the deadLetterQueue of the SQS Operation Binding Object
+ /// Queue objects that are either the endpoint for an SNS Operation Binding Object, or the deadLetterQueue of the SQS Operation Binding Object.
///
public List Queues { get; set; }
diff --git a/src/LEGO.AsyncAPI.Bindings/Sqs/Statement.cs b/src/LEGO.AsyncAPI.Bindings/Sqs/Statement.cs
index 9518d2d4..0bed331f 100644
--- a/src/LEGO.AsyncAPI.Bindings/Sqs/Statement.cs
+++ b/src/LEGO.AsyncAPI.Bindings/Sqs/Statement.cs
@@ -19,7 +19,7 @@ public class Statement : IAsyncApiExtensible
public StringOrStringList 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; }
diff --git a/src/LEGO.AsyncAPI.Bindings/WebSockets/WebSocketsChannelBinding.cs b/src/LEGO.AsyncAPI.Bindings/WebSockets/WebSocketsChannelBinding.cs
index c87393bb..8827bea4 100644
--- a/src/LEGO.AsyncAPI.Bindings/WebSockets/WebSocketsChannelBinding.cs
+++ b/src/LEGO.AsyncAPI.Bindings/WebSockets/WebSocketsChannelBinding.cs
@@ -31,8 +31,8 @@ public class WebSocketsChannelBinding : ChannelBinding
{
{ "bindingVersion", (a, n) => { a.BindingVersion = n.GetScalarValue(); } },
{ "method", (a, n) => { a.Method = n.GetScalarValue(); } },
- { "query", (a, n) => { a.Query = JsonSchemaDeserializer.LoadSchema(n); } },
- { "headers", (a, n) => { a.Headers = JsonSchemaDeserializer.LoadSchema(n); } },
+ { "query", (a, n) => { a.Query = AsyncApiSchemaDeserializer.LoadSchema(n); } },
+ { "headers", (a, n) => { a.Headers = AsyncApiSchemaDeserializer.LoadSchema(n); } },
};
public override void SerializeProperties(IAsyncApiWriter writer)
diff --git a/src/LEGO.AsyncAPI.Readers/AsyncApiExternalReferenceResolver.cs b/src/LEGO.AsyncAPI.Readers/AsyncApiExternalReferenceResolver.cs
new file mode 100644
index 00000000..2c111773
--- /dev/null
+++ b/src/LEGO.AsyncAPI.Readers/AsyncApiExternalReferenceResolver.cs
@@ -0,0 +1,247 @@
+using LEGO.AsyncAPI.Readers.Exceptions;
+using LEGO.AsyncAPI.Services;
+
+namespace LEGO.AsyncAPI.Readers
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using LEGO.AsyncAPI.Models;
+ using LEGO.AsyncAPI.Models.Interfaces;
+
+ ///
+ /// This class is used to walk an AsyncApiDocument and convert unresolved references to references to populated objects.
+ ///
+ internal class AsyncApiExternalReferenceResolver : AsyncApiVisitorBase
+ {
+ private AsyncApiDocument currentDocument;
+ private List errors = new List();
+ private AsyncApiReaderSettings readerSettings;
+
+ public AsyncApiExternalReferenceResolver(
+ AsyncApiDocument currentDocument,
+ AsyncApiReaderSettings readerSettings)
+ {
+ this.currentDocument = currentDocument;
+ this.readerSettings = readerSettings;
+ }
+
+ public IEnumerable Errors
+ {
+ get
+ {
+ return this.errors;
+ }
+ }
+
+ public override void Visit(IAsyncApiReferenceable referenceable)
+ {
+ if (referenceable.Reference != null)
+ {
+ referenceable.Reference.HostDocument = this.currentDocument;
+ }
+ }
+
+ public override void Visit(AsyncApiComponents components)
+ {
+ this.ResolveMap(components.Parameters);
+ this.ResolveMap(components.Channels);
+ this.ResolveMap(components.Schemas);
+ this.ResolveMap(components.Servers);
+ this.ResolveMap(components.CorrelationIds);
+ this.ResolveMap(components.MessageTraits);
+ this.ResolveMap(components.OperationTraits);
+ this.ResolveMap(components.SecuritySchemes);
+ this.ResolveMap(components.ChannelBindings);
+ this.ResolveMap(components.MessageBindings);
+ this.ResolveMap(components.OperationBindings);
+ this.ResolveMap(components.ServerBindings);
+ this.ResolveMap(components.Messages);
+ }
+
+ public override void Visit(AsyncApiDocument doc)
+ {
+ this.ResolveMap(doc.Servers);
+ this.ResolveMap(doc.Channels);
+ }
+
+ public override void Visit(AsyncApiChannel channel)
+ {
+ this.ResolveMap(channel.Parameters);
+ this.ResolveObject(channel.Bindings, r => channel.Bindings = r);
+ }
+
+ public override void Visit(AsyncApiMessageTrait trait)
+ {
+ this.ResolveObject(trait.CorrelationId, r => trait.CorrelationId = r);
+ this.ResolveObject(trait.Headers, r => trait.Headers = r);
+ }
+
+ ///
+ /// Resolve all references used in an operation.
+ ///
+ public override void Visit(AsyncApiOperation operation)
+ {
+ this.ResolveList(operation.Message);
+ this.ResolveList(operation.Traits);
+ this.ResolveObject(operation.Bindings, r => operation.Bindings = r);
+ }
+
+ public override void Visit(AsyncApiMessage message)
+ {
+ this.ResolveObject(message.Headers, r => message.Headers = r);
+ if (message.Payload is AsyncApiJsonSchemaPayload)
+ {
+ this.ResolveObject(message.Payload as AsyncApiJsonSchemaPayload, r => message.Payload = r);
+ }
+ this.ResolveList(message.Traits);
+ this.ResolveObject(message.CorrelationId, r => message.CorrelationId = r);
+ this.ResolveObject(message.Bindings, r => message.Bindings = r);
+ }
+
+ public override void Visit(AsyncApiServer server)
+ {
+ this.ResolveObject(server.Bindings, r => server.Bindings = r);
+ }
+
+ ///
+ /// Resolve all references to SecuritySchemes.
+ ///
+ public override void Visit(AsyncApiSecurityRequirement securityRequirement)
+ {
+ foreach (var scheme in securityRequirement.Keys.ToList())
+ {
+ this.ResolveObject(scheme, (resolvedScheme) =>
+ {
+ if (resolvedScheme != null)
+ {
+ // If scheme was unresolved
+ // copy Scopes and remove old unresolved scheme
+ var scopes = securityRequirement[scheme];
+ securityRequirement.Remove(scheme);
+ securityRequirement.Add(resolvedScheme, scopes);
+ }
+ });
+ }
+ }
+
+ ///
+ /// Resolve all references to parameters.
+ ///
+ public override void Visit(IList parameters)
+ {
+ this.ResolveList(parameters);
+ }
+
+ ///
+ /// Resolve all references used in a parameter.
+ ///
+ public override void Visit(AsyncApiParameter parameter)
+ {
+ this.ResolveObject(parameter.Schema, r => parameter.Schema = r);
+ }
+
+ ///
+ /// Resolve all references used in a schema.
+ ///
+ public override void Visit(AsyncApiSchema schema)
+ {
+ this.ResolveObject(schema.Items, r => schema.Items = r);
+ this.ResolveList(schema.OneOf);
+ this.ResolveList(schema.AllOf);
+ this.ResolveList(schema.AnyOf);
+ this.ResolveObject(schema.Contains, r => schema.Contains = r);
+ this.ResolveObject(schema.Else, r => schema.Else = r);
+ this.ResolveObject(schema.If, r => schema.If = r);
+ this.ResolveObject(schema.Items, r => schema.Items = r);
+ this.ResolveObject(schema.Not, r => schema.Not = r);
+ this.ResolveObject(schema.Then, r => schema.Then = r);
+ this.ResolveObject(schema.PropertyNames, r => schema.PropertyNames = r);
+ this.ResolveObject(schema.AdditionalProperties, r => schema.AdditionalProperties = r);
+ this.ResolveMap(schema.Properties);
+ }
+
+ private void ResolveObject(T entity, Action assign)
+ where T : class, IAsyncApiReferenceable, new()
+ {
+ if (entity == null)
+ {
+ return;
+ }
+
+ if (this.IsUnresolvedReference(entity))
+ {
+ assign(this.ResolveReference(entity.Reference));
+ }
+ }
+
+ private void ResolveList(IList list)
+ where T : class, IAsyncApiReferenceable, new()
+ {
+ if (list == null)
+ {
+ return;
+ }
+
+ for (int i = 0; i < list.Count; i++)
+ {
+ var entity = list[i];
+ if (this.IsUnresolvedReference(entity))
+ {
+ list[i] = this.ResolveReference(entity.Reference);
+ }
+ }
+ }
+
+ private void ResolveMap(IDictionary map)
+ where T : class, IAsyncApiReferenceable, new()
+ {
+ if (map == null)
+ {
+ return;
+ }
+
+ foreach (var key in map.Keys.ToList())
+ {
+ var entity = map[key];
+ if (this.IsUnresolvedReference(entity))
+ {
+ map[key] = this.ResolveReference(entity.Reference);
+ }
+ }
+ }
+
+ private T ResolveReference(AsyncApiReference reference)
+ where T : class, IAsyncApiReferenceable, new()
+ {
+ if (reference.IsExternal)
+ {
+ if (this.readerSettings.ExternalReferenceReader is null)
+ {
+ throw new AsyncApiReaderException(
+ "External reference configured in AsyncApi document but no implementation provided for ExternalReferenceReader.");
+ }
+
+ // read external content
+ var externalContent = this.readerSettings.ExternalReferenceReader.Load(reference.Reference);
+
+ // read external object content
+ var reader = new AsyncApiStringReader(this.readerSettings);
+ var externalAsyncApiContent = reader.ReadFragment(externalContent, AsyncApiVersion.AsyncApi2_0, out var diagnostic);
+ foreach (var error in diagnostic.Errors)
+ {
+ this.errors.Add(error);
+ }
+
+ return externalAsyncApiContent;
+ }
+
+ return null;
+ }
+
+ private bool IsUnresolvedReference(IAsyncApiReferenceable possibleReference)
+ {
+ return (possibleReference != null && possibleReference.UnresolvedReference);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/LEGO.AsyncAPI.Readers/AsyncApiJsonDocumentReader.cs b/src/LEGO.AsyncAPI.Readers/AsyncApiJsonDocumentReader.cs
index 3a973654..6b340651 100644
--- a/src/LEGO.AsyncAPI.Readers/AsyncApiJsonDocumentReader.cs
+++ b/src/LEGO.AsyncAPI.Readers/AsyncApiJsonDocumentReader.cs
@@ -12,6 +12,7 @@ namespace LEGO.AsyncAPI.Readers
using LEGO.AsyncAPI.Models;
using LEGO.AsyncAPI.Models.Interfaces;
using LEGO.AsyncAPI.Readers.Interface;
+ using LEGO.AsyncAPI.Services;
using LEGO.AsyncAPI.Validations;
///
@@ -165,23 +166,48 @@ public T ReadFragment(JsonNode input, AsyncApiVersion version, out AsyncApiDi
private void ResolveReferences(AsyncApiDiagnostic diagnostic, AsyncApiDocument document)
{
- var errors = new List();
-
- // Resolve References if requested
switch (this.settings.ReferenceResolution)
{
- case ReferenceResolutionSetting.ResolveReferences:
- errors.AddRange(document.ResolveReferences());
+ case ReferenceResolutionSetting.ResolveAllReferences:
+ this.ResolveAllReferences(diagnostic, document);
+ break;
+ case ReferenceResolutionSetting.ResolveInternalReferences:
+ this.ResolveInternalReferences(diagnostic, document);
break;
-
case ReferenceResolutionSetting.DoNotResolveReferences:
break;
}
+ }
+
+ private void ResolveAllReferences(AsyncApiDiagnostic diagnostic, AsyncApiDocument document)
+ {
+ this.ResolveInternalReferences(diagnostic, document);
+ this.ResolveExternalReferences(diagnostic, document);
+ }
+
+ private void ResolveInternalReferences(AsyncApiDiagnostic diagnostic, AsyncApiDocument document)
+ {
+ var errors = new List();
+
+ var reader = new AsyncApiStringReader(this.settings);
+ errors.AddRange(document.ResolveReferences());
foreach (var item in errors)
{
diagnostic.Errors.Add(item);
}
}
+
+ private void ResolveExternalReferences(AsyncApiDiagnostic diagnostic, AsyncApiDocument document)
+ {
+ var resolver = new AsyncApiExternalReferenceResolver(document, this.settings);
+ var walker = new AsyncApiWalker(resolver);
+ walker.Walk(document);
+
+ foreach (var error in resolver.Errors)
+ {
+ diagnostic.Errors.Add(error);
+ }
+ }
}
}
\ No newline at end of file
diff --git a/src/LEGO.AsyncAPI.Readers/AsyncApiReaderSettings.cs b/src/LEGO.AsyncAPI.Readers/AsyncApiReaderSettings.cs
index 6ca2e129..341a743c 100644
--- a/src/LEGO.AsyncAPI.Readers/AsyncApiReaderSettings.cs
+++ b/src/LEGO.AsyncAPI.Readers/AsyncApiReaderSettings.cs
@@ -18,9 +18,14 @@ public enum ReferenceResolutionSetting
DoNotResolveReferences,
///
- /// Resolve internal component references and inline them.
+ /// Resolve all references and inline them.
///
- ResolveReferences,
+ ResolveInternalReferences,
+
+ ///
+ /// Resolve internal component references and inline them while leaving external references as placeholder objects with an AsyncApiReference instance and UnresolvedReference set to true.
+ ///
+ ResolveAllReferences,
}
///
@@ -32,7 +37,13 @@ public class AsyncApiReaderSettings : AsyncApiSettings
/// Indicates how references in the source document should be handled.
///
public ReferenceResolutionSetting ReferenceResolution { get; set; } =
- ReferenceResolutionSetting.ResolveReferences;
+ ReferenceResolutionSetting.ResolveInternalReferences;
+
+ ///
+ /// Indicates what should happen when unmapped members are encountered during deserialization.
+ /// Error and Warning will add an error or warning to the diagnostics object.
+ ///
+ public UnmappedMemberHandling UnmappedMemberHandling { get; set; } = UnmappedMemberHandling.Error;
///
/// Dictionary of parsers for converting extensions into strongly typed classes.
@@ -42,7 +53,7 @@ public Dictionary>
{ get; set; } =
new Dictionary>();
- public IEnumerable>
+ public ICollection>
Bindings
{ get; set; } =
new List>();
@@ -57,5 +68,10 @@ public IEnumerable>
/// from an object.
///
public bool LeaveStreamOpen { get; set; }
+
+ ///
+ /// External reference reader implementation provided by users for reading external resources.
+ ///
+ public IAsyncApiExternalReferenceReader ExternalReferenceReader { get; set; }
}
}
\ No newline at end of file
diff --git a/src/LEGO.AsyncAPI.Readers/AsyncApiTextReader.cs b/src/LEGO.AsyncAPI.Readers/AsyncApiTextReader.cs
index eeece6a7..8d1eafc6 100644
--- a/src/LEGO.AsyncAPI.Readers/AsyncApiTextReader.cs
+++ b/src/LEGO.AsyncAPI.Readers/AsyncApiTextReader.cs
@@ -4,7 +4,6 @@ namespace LEGO.AsyncAPI.Readers
{
using System.IO;
using System.Linq;
- using System.Runtime.CompilerServices;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Threading;
diff --git a/src/LEGO.AsyncAPI.Readers/Exceptions/AsyncApiUnsupportedSpecVersionException.cs b/src/LEGO.AsyncAPI.Readers/Exceptions/AsyncApiUnsupportedSpecVersionException.cs
index d68da4bf..590b3d73 100644
--- a/src/LEGO.AsyncAPI.Readers/Exceptions/AsyncApiUnsupportedSpecVersionException.cs
+++ b/src/LEGO.AsyncAPI.Readers/Exceptions/AsyncApiUnsupportedSpecVersionException.cs
@@ -29,7 +29,7 @@ public AsyncApiUnsupportedSpecVersionException(string specificationVersion)
/// inner exception.
///
/// Version that caused this exception to be thrown.
- /// The setting used for reading and writing
+ /// The setting used for reading and writing.
/// Inner exception that caused this exception to be thrown.
public AsyncApiUnsupportedSpecVersionException(string specificationVersion, Exception innerException)
: base(string.Format(CultureInfo.InvariantCulture, MessagePattern, specificationVersion), innerException)
diff --git a/src/LEGO.AsyncAPI.Readers/Interface/IAsyncApiExternalReferenceReader.cs b/src/LEGO.AsyncAPI.Readers/Interface/IAsyncApiExternalReferenceReader.cs
new file mode 100644
index 00000000..6eef5608
--- /dev/null
+++ b/src/LEGO.AsyncAPI.Readers/Interface/IAsyncApiExternalReferenceReader.cs
@@ -0,0 +1,14 @@
+namespace LEGO.AsyncAPI.Readers;
+
+///
+/// Interface that provides method for reading external references.å.
+///
+public interface IAsyncApiExternalReferenceReader
+{
+ ///
+ /// Method that returns the AsyncAPI content that the external reference from the $ref points to.
+ ///
+ /// The content address of the $ref.
+ /// The content of the reference as a string.
+ public string Load(string reference);
+}
\ No newline at end of file
diff --git a/src/LEGO.AsyncAPI.Readers/LEGO.AsyncAPI.Readers.csproj b/src/LEGO.AsyncAPI.Readers/LEGO.AsyncAPI.Readers.csproj
index c47530c0..53071ca0 100644
--- a/src/LEGO.AsyncAPI.Readers/LEGO.AsyncAPI.Readers.csproj
+++ b/src/LEGO.AsyncAPI.Readers/LEGO.AsyncAPI.Readers.csproj
@@ -27,12 +27,6 @@
-
-
- True
- \
-
-
diff --git a/src/LEGO.AsyncAPI.Readers/ParseNodes/MapNode.cs b/src/LEGO.AsyncAPI.Readers/ParseNodes/MapNode.cs
index b087c875..63bea6fc 100644
--- a/src/LEGO.AsyncAPI.Readers/ParseNodes/MapNode.cs
+++ b/src/LEGO.AsyncAPI.Readers/ParseNodes/MapNode.cs
@@ -214,6 +214,14 @@ public override AsyncApiAny CreateAny()
return new AsyncApiAny(this.node);
}
+ public void ParseFields(ref T parentInstance, IDictionary> fixedFields, IDictionary, Action> patternFields)
+ {
+ foreach (var propertyNode in this)
+ {
+ propertyNode.ParseField(parentInstance, fixedFields, patternFields);
+ }
+ }
+
private string ToScalarValue(JsonNode node)
{
var scalarNode = node is JsonValue value ? value : throw new AsyncApiException($"Expected scalar value");
diff --git a/src/LEGO.AsyncAPI.Readers/ParseNodes/PropertyNode.cs b/src/LEGO.AsyncAPI.Readers/ParseNodes/PropertyNode.cs
index e0359659..2313f30d 100644
--- a/src/LEGO.AsyncAPI.Readers/ParseNodes/PropertyNode.cs
+++ b/src/LEGO.AsyncAPI.Readers/ParseNodes/PropertyNode.cs
@@ -29,8 +29,7 @@ public void ParseField(
IDictionary> fixedFields,
IDictionary, Action> patternFields)
{
- var found = fixedFields.TryGetValue(this.Name, out var fixedFieldMap);
-
+ var _ = fixedFields.TryGetValue(this.Name, out var fixedFieldMap);
if (fixedFieldMap != null)
{
try
@@ -78,8 +77,12 @@ public void ParseField(
}
else
{
- this.Context.Diagnostic.Errors.Add(
- new AsyncApiError("", $"{this.Name} is not a valid property at {this.Context.GetLocation()}"));
+ switch (this.Context.Settings.UnmappedMemberHandling)
+ {
+ case UnmappedMemberHandling.Error:
+ this.Context.Diagnostic.Errors.Add(new AsyncApiError(string.Empty, $"{this.Name} is not a valid property at {this.Context.GetLocation()}"));
+ break;
+ }
}
}
}
diff --git a/src/LEGO.AsyncAPI.Readers/ParseNodes/ValueNode.cs b/src/LEGO.AsyncAPI.Readers/ParseNodes/ValueNode.cs
index 201ab6f7..76add0b2 100644
--- a/src/LEGO.AsyncAPI.Readers/ParseNodes/ValueNode.cs
+++ b/src/LEGO.AsyncAPI.Readers/ParseNodes/ValueNode.cs
@@ -2,11 +2,11 @@
namespace LEGO.AsyncAPI.Readers.ParseNodes
{
+ using System;
+ using System.Text.Json.Nodes;
using LEGO.AsyncAPI.Exceptions;
using LEGO.AsyncAPI.Models;
using LEGO.AsyncAPI.Readers.Exceptions;
- using System;
- using System.Text.Json.Nodes;
public class ValueNode : ParseNode
{
diff --git a/src/LEGO.AsyncAPI.Readers/UnmappedMemberHandling.cs b/src/LEGO.AsyncAPI.Readers/UnmappedMemberHandling.cs
new file mode 100644
index 00000000..b58c34c4
--- /dev/null
+++ b/src/LEGO.AsyncAPI.Readers/UnmappedMemberHandling.cs
@@ -0,0 +1,20 @@
+// Copyright (c) The LEGO Group. All rights reserved.
+
+namespace LEGO.AsyncAPI.Readers
+{
+ ///
+ /// Unmapped member handling.
+ ///
+ public enum UnmappedMemberHandling
+ {
+ ///
+ /// Add error to diagnostics for unmapped members.
+ ///
+ Error,
+
+ ///
+ /// Ignore unmapped members.
+ ///
+ Ignore,
+ }
+}
diff --git a/src/LEGO.AsyncAPI.Readers/V2/AsyncApiAvroSchemaDeserializer.cs b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiAvroSchemaDeserializer.cs
new file mode 100644
index 00000000..5015cef2
--- /dev/null
+++ b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiAvroSchemaDeserializer.cs
@@ -0,0 +1,191 @@
+// Copyright (c) The LEGO Group. All rights reserved.
+
+namespace LEGO.AsyncAPI.Readers
+{
+ using System;
+ using LEGO.AsyncAPI.Exceptions;
+ using LEGO.AsyncAPI.Models;
+ using LEGO.AsyncAPI.Readers.Exceptions;
+ using LEGO.AsyncAPI.Readers.ParseNodes;
+ using LEGO.AsyncAPI.Writers;
+
+ public class AsyncApiAvroSchemaDeserializer
+ {
+ private static readonly FixedFieldMap FieldFixedFields = new()
+ {
+ { "name", (a, n) => a.Name = n.GetScalarValue() },
+ { "type", (a, n) => a.Type = LoadSchema(n) },
+ { "doc", (a, n) => a.Doc = n.GetScalarValue() },
+ { "default", (a, n) => a.Default = n.CreateAny() },
+ { "aliases", (a, n) => a.Aliases = n.CreateSimpleList(n2 => n2.GetScalarValue()) },
+ { "order", (a, n) => a.Order = n.GetScalarValue().GetEnumFromDisplayName() },
+ };
+
+ private static readonly FixedFieldMap RecordFixedFields = new()
+ {
+ { "type", (a, n) => { } },
+ { "name", (a, n) => a.Name = n.GetScalarValue() },
+ { "doc", (a, n) => a.Doc = n.GetScalarValue() },
+ { "namespace", (a, n) => a.Namespace = n.GetScalarValue() },
+ { "aliases", (a, n) => a.Aliases = n.CreateSimpleList(n2 => n2.GetScalarValue()) },
+ { "fields", (a, n) => a.Fields = n.CreateList(LoadField) },
+ };
+
+ private static readonly FixedFieldMap EnumFixedFields = new()
+ {
+ { "type", (a, n) => { } },
+ { "name", (a, n) => a.Name = n.GetScalarValue() },
+ { "doc", (a, n) => a.Doc = n.GetScalarValue() },
+ { "namespace", (a, n) => a.Namespace = n.GetScalarValue() },
+ { "aliases", (a, n) => a.Aliases = n.CreateSimpleList(n2 => n2.GetScalarValue()) },
+ { "symbols", (a, n) => a.Symbols = n.CreateSimpleList(n2 => n2.GetScalarValue()) },
+ { "default", (a, n) => a.Default = n.GetScalarValue() },
+ };
+
+ private static readonly FixedFieldMap FixedFixedFields = new()
+ {
+ { "type", (a, n) => { } },
+ { "name", (a, n) => a.Name = n.GetScalarValue() },
+ { "namespace", (a, n) => a.Namespace = n.GetScalarValue() },
+ { "aliases", (a, n) => a.Aliases = n.CreateSimpleList(n2 => n2.GetScalarValue()) },
+ { "size", (a, n) => a.Size = int.Parse(n.GetScalarValue(), n.Context.Settings.CultureInfo) },
+ };
+
+ private static readonly FixedFieldMap ArrayFixedFields = new()
+ {
+ { "type", (a, n) => { } },
+ { "items", (a, n) => a.Items = LoadSchema(n) },
+ };
+
+ private static readonly FixedFieldMap MapFixedFields = new()
+ {
+ { "type", (a, n) => { } },
+ { "values", (a, n) => a.Values = n.GetScalarValue().GetEnumFromDisplayName() },
+ };
+
+ private static readonly FixedFieldMap UnionFixedFields = new()
+ {
+ { "types", (a, n) => a.Types = n.CreateList(LoadSchema) },
+ };
+
+ private static readonly PatternFieldMap RecordMetadataPatternFields =
+ new()
+ {
+ { s => s.StartsWith(string.Empty), (a, p, n) => a.Metadata[p] = n.CreateAny() },
+ };
+
+ private static readonly PatternFieldMap FieldMetadataPatternFields =
+ new()
+ {
+ { s => s.StartsWith(string.Empty), (a, p, n) => a.Metadata[p] = n.CreateAny() },
+ };
+
+ private static readonly PatternFieldMap EnumMetadataPatternFields =
+ new()
+ {
+ { s => s.StartsWith(string.Empty), (a, p, n) => a.Metadata[p] = n.CreateAny() },
+ };
+
+ private static readonly PatternFieldMap FixedMetadataPatternFields =
+ new()
+ {
+ { s => s.StartsWith(string.Empty), (a, p, n) => a.Metadata[p] = n.CreateAny() },
+ };
+
+ private static readonly PatternFieldMap ArrayMetadataPatternFields =
+ new()
+ {
+ { s => s.StartsWith(string.Empty), (a, p, n) => a.Metadata[p] = n.CreateAny() },
+ };
+
+ private static readonly PatternFieldMap MapMetadataPatternFields =
+ new()
+ {
+ { s => s.StartsWith(string.Empty), (a, p, n) => a.Metadata[p] = n.CreateAny() },
+ };
+
+ private static readonly PatternFieldMap UnionMetadataPatternFields =
+ new()
+ {
+ { s => s.StartsWith(string.Empty), (a, p, n) => a.Metadata[p] = n.CreateAny() },
+ };
+
+ public static AvroSchema LoadSchema(ParseNode node)
+ {
+ if (node is ValueNode valueNode)
+ {
+ return new AvroPrimitive(valueNode.GetScalarValue().GetEnumFromDisplayName());
+ }
+
+ if (node is ListNode)
+ {
+ var union = new AvroUnion();
+ foreach (var item in node as ListNode)
+ {
+ union.Types.Add(LoadSchema(item));
+ }
+
+ return union;
+ }
+
+ if (node is MapNode mapNode)
+ {
+ var pointer = mapNode.GetReferencePointer();
+
+ if (pointer != null)
+ {
+ return new AvroRecord
+ {
+ UnresolvedReference = true,
+ Reference = node.Context.VersionService.ConvertToAsyncApiReference(pointer, ReferenceType.Schema),
+ };
+ }
+
+ var type = mapNode["type"]?.Value.GetScalarValue();
+
+ switch (type)
+ {
+ case "record":
+ var record = new AvroRecord();
+ mapNode.ParseFields(ref record, RecordFixedFields, RecordMetadataPatternFields);
+ return record;
+ case "enum":
+ var @enum = new AvroEnum();
+ mapNode.ParseFields(ref @enum, EnumFixedFields, EnumMetadataPatternFields);
+ return @enum;
+ case "fixed":
+ var @fixed = new AvroFixed();
+ mapNode.ParseFields(ref @fixed, FixedFixedFields, FixedMetadataPatternFields);
+ return @fixed;
+ case "array":
+ var array = new AvroArray();
+ mapNode.ParseFields(ref array, ArrayFixedFields, ArrayMetadataPatternFields);
+ return array;
+ case "map":
+ var map = new AvroMap();
+ mapNode.ParseFields(ref map, MapFixedFields, MapMetadataPatternFields);
+ return map;
+ case "union":
+ var union = new AvroUnion();
+ mapNode.ParseFields(ref union, UnionFixedFields, UnionMetadataPatternFields);
+ return union;
+ default:
+ throw new AsyncApiException($"Unsupported type: {type}");
+ }
+ }
+
+ throw new AsyncApiReaderException("Invalid node type");
+ }
+
+ private static AvroField LoadField(ParseNode node)
+ {
+ var mapNode = node.CheckMapNode("field");
+ var field = new AvroField();
+
+ mapNode.ParseFields(ref field, FieldFixedFields, FieldMetadataPatternFields);
+
+ return field;
+
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/LEGO.AsyncAPI.Readers/V2/AsyncApiChannelDeserializer.cs b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiChannelDeserializer.cs
index cc74e0c0..1d6acae6 100644
--- a/src/LEGO.AsyncAPI.Readers/V2/AsyncApiChannelDeserializer.cs
+++ b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiChannelDeserializer.cs
@@ -14,7 +14,7 @@ internal static partial class AsyncApiV2Deserializer
{ "servers", (a, n) => { a.Servers = n.CreateSimpleList(s => s.GetScalarValue()); } },
{ "subscribe", (a, n) => { a.Subscribe = LoadOperation(n); } },
{ "publish", (a, n) => { a.Publish = LoadOperation(n); } },
- { "parameters", (a, n) => { a.Parameters = n.CreateMapWithReference(ReferenceType.Parameter, LoadParameter); } },
+ { "parameters", (a, n) => { a.Parameters = n.CreateMap(LoadParameter); } },
{ "bindings", (a, n) => { a.Bindings = LoadChannelBindings(n); } },
};
diff --git a/src/LEGO.AsyncAPI.Readers/V2/AsyncApiComponentsDeserializer.cs b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiComponentsDeserializer.cs
index 3b63db28..df13308a 100644
--- a/src/LEGO.AsyncAPI.Readers/V2/AsyncApiComponentsDeserializer.cs
+++ b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiComponentsDeserializer.cs
@@ -10,7 +10,7 @@ internal static partial class AsyncApiV2Deserializer
{
private static FixedFieldMap componentsFixedFields = new()
{
- { "schemas", (a, n) => a.Schemas = n.CreateMapWithReference(ReferenceType.Schema, JsonSchemaDeserializer.LoadSchema) },
+ { "schemas", (a, n) => a.Schemas = n.CreateMapWithReference(ReferenceType.Schema, AsyncApiSchemaDeserializer.LoadSchema) },
{ "servers", (a, n) => a.Servers = n.CreateMapWithReference(ReferenceType.Server, LoadServer) },
{ "channels", (a, n) => a.Channels = n.CreateMapWithReference(ReferenceType.Channel, LoadChannel) },
{ "messages", (a, n) => a.Messages = n.CreateMapWithReference(ReferenceType.Message, LoadMessage) },
diff --git a/src/LEGO.AsyncAPI.Readers/V2/AsyncApiMessageDeserializer.cs b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiMessageDeserializer.cs
index 4c16bf22..2670d88a 100644
--- a/src/LEGO.AsyncAPI.Readers/V2/AsyncApiMessageDeserializer.cs
+++ b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiMessageDeserializer.cs
@@ -2,12 +2,13 @@
namespace LEGO.AsyncAPI.Readers
{
+ using System.Collections.Generic;
+ using System.Linq;
using LEGO.AsyncAPI.Exceptions;
using LEGO.AsyncAPI.Extensions;
using LEGO.AsyncAPI.Models;
+ using LEGO.AsyncAPI.Models.Interfaces;
using LEGO.AsyncAPI.Readers.ParseNodes;
- using System.Collections.Generic;
- using System.Linq;
///
/// Class containing logic to deserialize AsyncApi document into
@@ -21,10 +22,10 @@ internal static partial class AsyncApiV2Deserializer
"messageId", (a, n) => { a.MessageId = n.GetScalarValue(); }
},
{
- "headers", (a, n) => { a.Headers = JsonSchemaDeserializer.LoadSchema(n); }
+ "headers", (a, n) => { a.Headers = AsyncApiSchemaDeserializer.LoadSchema(n); }
},
{
- "payload", (a, n) => { a.Payload = JsonSchemaDeserializer.LoadSchema(n); }
+ "payload", (a, n) => { a.Payload = null; /* resolved after the initial run */ }
},
{
"correlationId", (a, n) => { a.CorrelationId = LoadCorrelationId(n); }
@@ -64,7 +65,39 @@ internal static partial class AsyncApiV2Deserializer
},
};
- static readonly IEnumerable SupportedSchemaFormats = new List
+ public static IAsyncApiMessagePayload LoadJsonSchemaPayload(ParseNode n)
+ {
+ return LoadPayload(n, null);
+ }
+
+ public static IAsyncApiMessagePayload LoadAvroPayload(ParseNode n)
+ {
+ return LoadPayload(n, "application/vnd.apache.avro");
+ }
+
+ private static IAsyncApiMessagePayload LoadPayload(ParseNode n, string format)
+ {
+
+ if (n == null)
+ {
+ return null;
+ }
+
+ switch (format)
+ {
+ case null:
+ case "":
+ case var _ when SupportedJsonSchemaFormats.Where(s => format.StartsWith(s)).Any():
+ return new AsyncApiJsonSchemaPayload(AsyncApiSchemaDeserializer.LoadSchema(n));
+ case var _ when SupportedAvroSchemaFormats.Where(s => format.StartsWith(s)).Any():
+ return new AsyncApiAvroSchemaPayload(AsyncApiAvroSchemaDeserializer.LoadSchema(n));
+ default:
+ var supportedFormats = SupportedJsonSchemaFormats.Concat(SupportedAvroSchemaFormats);
+ throw new AsyncApiException($"'Could not deserialize Payload. Supported formats are {string.Join(", ", supportedFormats)}");
+ }
+ }
+
+ static readonly IEnumerable SupportedJsonSchemaFormats = new List
{
"application/vnd.aai.asyncapi+json",
"application/vnd.aai.asyncapi+yaml",
@@ -73,11 +106,21 @@ internal static partial class AsyncApiV2Deserializer
"application/schema+yaml;version=draft-07",
};
+ static readonly IEnumerable SupportedAvroSchemaFormats = new List
+ {
+ "application/vnd.apache.avro",
+ "application/vnd.apache.avro+json",
+ "application/vnd.apache.avro+yaml",
+ "application/vnd.apache.avro+json;version=1.9.0",
+ "application/vnd.apache.avro+yaml;version=1.9.0",
+ };
+
private static string LoadSchemaFormat(string schemaFormat)
{
- if (!SupportedSchemaFormats.Where(s => schemaFormat.StartsWith(s)).Any())
+ var supportedFormats = SupportedJsonSchemaFormats.Concat(SupportedAvroSchemaFormats);
+ if (!supportedFormats.Where(s => schemaFormat.StartsWith(s)).Any())
{
- throw new AsyncApiException($"'{schemaFormat}' is not a supported format. Supported formats are {string.Join(", ", SupportedSchemaFormats)}");
+ throw new AsyncApiException($"'{schemaFormat}' is not a supported format. Supported formats are {string.Join(", ", supportedFormats)}");
}
return schemaFormat;
@@ -100,6 +143,7 @@ public static AsyncApiMessage LoadMessage(ParseNode node)
var message = new AsyncApiMessage();
ParseMap(mapNode, message, messageFixedFields, messagePatternFields);
+ message.Payload = LoadPayload(mapNode["payload"]?.Value, message.SchemaFormat);
return message;
}
diff --git a/src/LEGO.AsyncAPI.Readers/V2/AsyncApiMessageTraitDeserializer.cs b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiMessageTraitDeserializer.cs
index eca8af64..0d229e92 100644
--- a/src/LEGO.AsyncAPI.Readers/V2/AsyncApiMessageTraitDeserializer.cs
+++ b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiMessageTraitDeserializer.cs
@@ -11,7 +11,7 @@ internal static partial class AsyncApiV2Deserializer
private static FixedFieldMap messageTraitFixedFields = new()
{
{ "messageId", (a, n) => { a.MessageId = n.GetScalarValue(); } },
- { "headers", (a, n) => { a.Headers = JsonSchemaDeserializer.LoadSchema(n); } },
+ { "headers", (a, n) => { a.Headers = AsyncApiSchemaDeserializer.LoadSchema(n); } },
{ "correlationId", (a, n) => { a.CorrelationId = LoadCorrelationId(n); } },
{ "schemaFormat", (a, n) => { a.SchemaFormat = n.GetScalarValue(); } },
{ "contentType", (a, n) => { a.ContentType = n.GetScalarValue(); } },
diff --git a/src/LEGO.AsyncAPI.Readers/V2/AsyncApiParameterDeserializer.cs b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiParameterDeserializer.cs
index bff810f1..c77f48f4 100644
--- a/src/LEGO.AsyncAPI.Readers/V2/AsyncApiParameterDeserializer.cs
+++ b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiParameterDeserializer.cs
@@ -11,7 +11,7 @@ internal static partial class AsyncApiV2Deserializer
private static FixedFieldMap parameterFixedFields = new()
{
{ "description", (a, n) => { a.Description = n.GetScalarValue(); } },
- { "schema", (a, n) => { a.Schema = JsonSchemaDeserializer.LoadSchema(n); } },
+ { "schema", (a, n) => { a.Schema = AsyncApiSchemaDeserializer.LoadSchema(n); } },
{ "location", (a, n) => { a.Location = n.GetScalarValue(); } },
};
diff --git a/src/LEGO.AsyncAPI.Readers/V2/AsyncApiSchemaDeserializer.cs b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiSchemaDeserializer.cs
index b99fdef8..b0c10de7 100644
--- a/src/LEGO.AsyncAPI.Readers/V2/AsyncApiSchemaDeserializer.cs
+++ b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiSchemaDeserializer.cs
@@ -9,7 +9,7 @@ namespace LEGO.AsyncAPI.Readers
using LEGO.AsyncAPI.Readers.ParseNodes;
using LEGO.AsyncAPI.Writers;
- public class JsonSchemaDeserializer
+ public class AsyncApiSchemaDeserializer
{
private static readonly FixedFieldMap schemaFixedFields = new()
{
@@ -215,28 +215,6 @@ public class JsonSchemaDeserializer
{ s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, AsyncApiV2Deserializer.LoadExtension(p, n)) },
};
- private static readonly AnyFieldMap schemaAnyFields = new()
- {
- {
- AsyncApiConstants.Default,
- new AnyFieldMapParameter(
- s => s.Default,
- (s, v) => s.Default = v,
- s => s)
- },
- };
-
- private static readonly AnyListFieldMap schemaAnyListFields = new()
- {
- {
- AsyncApiConstants.Enum,
- new AnyListFieldMapParameter(
- s => s.Enum,
- (s, v) => s.Enum = v,
- s => s)
- },
- };
-
public static AsyncApiSchema LoadSchema(ParseNode node)
{
var mapNode = node.CheckMapNode(AsyncApiConstants.Schema);
@@ -259,9 +237,6 @@ public static AsyncApiSchema LoadSchema(ParseNode node)
propertyNode.ParseField(schema, schemaFixedFields, schemaPatternFields);
}
- AsyncApiV2Deserializer.ProcessAnyFields(mapNode, schema, schemaAnyFields);
- AsyncApiV2Deserializer.ProcessAnyListFields(mapNode, schema, schemaAnyListFields);
-
return schema;
}
}
diff --git a/src/LEGO.AsyncAPI.Readers/V2/AsyncApiV2VersionService.cs b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiV2VersionService.cs
index 10edd3ba..4acc3395 100644
--- a/src/LEGO.AsyncAPI.Readers/V2/AsyncApiV2VersionService.cs
+++ b/src/LEGO.AsyncAPI.Readers/V2/AsyncApiV2VersionService.cs
@@ -35,7 +35,10 @@ public AsyncApiV2VersionService(AsyncApiDiagnostic diagnostic)
[typeof(AsyncApiOAuthFlows)] = AsyncApiV2Deserializer.LoadOAuthFlows,
[typeof(AsyncApiOperation)] = AsyncApiV2Deserializer.LoadOperation,
[typeof(AsyncApiParameter)] = AsyncApiV2Deserializer.LoadParameter,
- [typeof(AsyncApiSchema)] = JsonSchemaDeserializer.LoadSchema,
+ [typeof(AsyncApiSchema)] = AsyncApiSchemaDeserializer.LoadSchema,
+ [typeof(AvroSchema)] = AsyncApiAvroSchemaDeserializer.LoadSchema,
+ [typeof(AsyncApiJsonSchemaPayload)] = AsyncApiV2Deserializer.LoadJsonSchemaPayload,
+ [typeof(AsyncApiAvroSchemaPayload)] = AsyncApiV2Deserializer.LoadAvroPayload,
[typeof(AsyncApiSecurityRequirement)] = AsyncApiV2Deserializer.LoadSecurityRequirement,
[typeof(AsyncApiSecurityScheme)] = AsyncApiV2Deserializer.LoadSecurityScheme,
[typeof(AsyncApiServer)] = AsyncApiV2Deserializer.LoadServer,
diff --git a/src/LEGO.AsyncAPI/AsyncApiVersion.cs b/src/LEGO.AsyncAPI/AsyncApiVersion.cs
index c5be2cc8..dbac63de 100644
--- a/src/LEGO.AsyncAPI/AsyncApiVersion.cs
+++ b/src/LEGO.AsyncAPI/AsyncApiVersion.cs
@@ -5,7 +5,7 @@ namespace LEGO.AsyncAPI
public enum AsyncApiVersion
{
///
- /// Represents AsyncAPI V2 spec
+ /// Represents AsyncAPI V2 spec.
///
AsyncApi2_0,
}
diff --git a/src/LEGO.AsyncAPI/LEGO.AsyncAPI.csproj b/src/LEGO.AsyncAPI/LEGO.AsyncAPI.csproj
index 3bfe1c9c..560c0f98 100644
--- a/src/LEGO.AsyncAPI/LEGO.AsyncAPI.csproj
+++ b/src/LEGO.AsyncAPI/LEGO.AsyncAPI.csproj
@@ -26,10 +26,6 @@
-
-
-
-
diff --git a/src/LEGO.AsyncAPI/Models/Any/AsyncApiAny.cs b/src/LEGO.AsyncAPI/Models/Any/AsyncApiAny.cs
index 5c5f2b31..45b764b3 100644
--- a/src/LEGO.AsyncAPI/Models/Any/AsyncApiAny.cs
+++ b/src/LEGO.AsyncAPI/Models/Any/AsyncApiAny.cs
@@ -2,7 +2,6 @@
namespace LEGO.AsyncAPI.Models
{
- using System.Collections.Generic;
using System.Text.Json;
using System.Text.Json.Nodes;
using LEGO.AsyncAPI.Models.Interfaces;
diff --git a/src/LEGO.AsyncAPI/Models/AsyncApiAvroSchemaPayload.cs b/src/LEGO.AsyncAPI/Models/AsyncApiAvroSchemaPayload.cs
new file mode 100644
index 00000000..596115ba
--- /dev/null
+++ b/src/LEGO.AsyncAPI/Models/AsyncApiAvroSchemaPayload.cs
@@ -0,0 +1,53 @@
+// Copyright (c) The LEGO Group. All rights reserved.
+
+namespace LEGO.AsyncAPI.Models
+{
+ using LEGO.AsyncAPI.Models.Interfaces;
+ using LEGO.AsyncAPI.Writers;
+
+ public class AsyncApiAvroSchemaPayload : IAsyncApiMessagePayload
+ {
+ public AvroSchema Schema { get; set; }
+
+ public AsyncApiAvroSchemaPayload(AvroSchema schema)
+ {
+ this.Schema = schema;
+ }
+
+ public AsyncApiAvroSchemaPayload()
+ {
+ }
+
+ public bool TryGetAs(out T schema)
+ where T : AvroSchema
+ {
+ schema = this.Schema as T;
+ return schema != null;
+ }
+
+ public bool UnresolvedReference { get => this.Schema.UnresolvedReference; set => this.Schema.UnresolvedReference = value; }
+
+ public AsyncApiReference Reference { get => this.Schema.Reference; set => this.Schema.Reference = value; }
+
+ public void SerializeV2(IAsyncApiWriter writer)
+ {
+ var settings = writer.GetSettings();
+
+ if (this.Reference != null)
+ {
+ if (!settings.ShouldInlineReference(this.Reference))
+ {
+ this.Reference.SerializeV2(writer);
+ return;
+ }
+ }
+
+ this.SerializeV2WithoutReference(writer);
+ }
+
+ public void SerializeV2WithoutReference(IAsyncApiWriter writer)
+ {
+ this.Schema.SerializeV2(writer);
+ }
+ }
+}
diff --git a/src/LEGO.AsyncAPI/Models/AsyncApiMessage.cs b/src/LEGO.AsyncAPI/Models/AsyncApiMessage.cs
index 2c627541..b4848bf6 100644
--- a/src/LEGO.AsyncAPI/Models/AsyncApiMessage.cs
+++ b/src/LEGO.AsyncAPI/Models/AsyncApiMessage.cs
@@ -25,7 +25,7 @@ public class AsyncApiMessage : IAsyncApiExtensible, IAsyncApiReferenceable, IAsy
///
/// definition of the message payload. It can be of any type but defaults to Schema object. It must match the schema format, including encoding type - e.g Avro should be inlined as either a YAML or JSON object NOT a string to be parsed as YAML or JSON.
///
- public AsyncApiSchema Payload { get; set; }
+ public IAsyncApiMessagePayload Payload { get; set; }
///
/// definition of the correlation ID used for message tracing or matching.
diff --git a/src/LEGO.AsyncAPI/Models/AsyncApiReference.cs b/src/LEGO.AsyncAPI/Models/AsyncApiReference.cs
index 4f9660d1..9475310e 100644
--- a/src/LEGO.AsyncAPI/Models/AsyncApiReference.cs
+++ b/src/LEGO.AsyncAPI/Models/AsyncApiReference.cs
@@ -15,7 +15,7 @@ public class AsyncApiReference : IAsyncApiSerializable
/// External resource in the reference.
/// It maybe:
/// 1. a absolute/relative file path, for example: ../commons/pet.json
- /// 2. a Url, for example: http://localhost/pet.json
+ /// 2. a Url, for example: http://localhost/pet.json.
///
public string ExternalResource { get; set; }
@@ -35,7 +35,7 @@ public class AsyncApiReference : IAsyncApiSerializable
public AsyncApiDocument HostDocument { get; set; } = null;
///
- /// Gets a flag indicating whether a file is a valid OpenAPI document or a fragment
+ /// Gets a flag indicating whether a file is a valid OpenAPI document or a fragment.
///
public bool IsFragment { get; set; } = false;
@@ -71,7 +71,7 @@ public string Reference
}
///
- /// Serialize to Async Api v2.4.
+ /// Serialize to Async Api.
///
public void SerializeV2(IAsyncApiWriter writer)
{
diff --git a/src/LEGO.AsyncAPI/Models/AsyncApiSchemaPayload.cs b/src/LEGO.AsyncAPI/Models/AsyncApiSchemaPayload.cs
new file mode 100644
index 00000000..19db07b2
--- /dev/null
+++ b/src/LEGO.AsyncAPI/Models/AsyncApiSchemaPayload.cs
@@ -0,0 +1,127 @@
+// Copyright (c) The LEGO Group. All rights reserved.
+
+namespace LEGO.AsyncAPI.Models
+{
+ using System.Collections.Generic;
+ using LEGO.AsyncAPI.Models.Interfaces;
+ using LEGO.AsyncAPI.Writers;
+
+ public class AsyncApiJsonSchemaPayload : IAsyncApiMessagePayload
+ {
+ private readonly AsyncApiSchema schema;
+
+ public AsyncApiJsonSchemaPayload()
+ {
+ this.schema = new AsyncApiSchema();
+ }
+
+ public AsyncApiJsonSchemaPayload(AsyncApiSchema schema)
+ {
+ this.schema = schema;
+ }
+
+ public string Title { get => this.schema.Title; set => this.schema.Title = value; }
+
+ public SchemaType? Type { get => this.schema.Type; set => this.schema.Type = value; }
+
+ public string Format { get => this.schema.Format; set => this.schema.Format = value; }
+
+ public string Description { get => this.schema.Description; set => this.schema.Description = value; }
+
+ public double? Maximum { get => this.schema.Maximum; set => this.schema.Maximum = value; }
+
+ public bool? ExclusiveMaximum { get => this.schema.ExclusiveMaximum; set => this.schema.ExclusiveMaximum = value; }
+
+ public double? Minimum { get => this.schema.Minimum; set => this.schema.Minimum = value; }
+
+ public bool? ExclusiveMinimum { get => this.schema.ExclusiveMinimum; set => this.schema.ExclusiveMinimum = value; }
+
+ public int? MaxLength { get => this.schema.MaxLength; set => this.schema.MaxLength = value; }
+
+ public int? MinLength { get => this.schema.MinLength; set => this.schema.MinLength = value; }
+
+ public string Pattern { get => this.schema.Pattern; set => this.schema.Pattern = value; }
+
+ public double? MultipleOf { get => this.schema.MultipleOf; set => this.schema.MultipleOf = value; }
+
+ public AsyncApiAny Default { get => this.schema.Default; set => this.schema.Default = value; }
+
+ public bool ReadOnly { get => this.schema.ReadOnly; set => this.schema.ReadOnly = value; }
+
+ public bool WriteOnly { get => this.schema.WriteOnly; set => this.schema.WriteOnly = value; }
+
+ public IList AllOf { get => this.schema.AllOf; set => this.schema.AllOf = value; }
+
+ public IList OneOf { get => this.schema.OneOf; set => this.schema.OneOf = value; }
+
+ public IList AnyOf { get => this.schema.AnyOf; set => this.schema.AnyOf = value; }
+
+ public AsyncApiSchema Not { get => this.schema.Not; set => this.schema.Not = value; }
+
+ public AsyncApiSchema Contains { get => this.schema.Contains; set => this.schema.Contains = value; }
+
+ public AsyncApiSchema If { get => this.schema.If; set => this.schema.If = value; }
+
+ public AsyncApiSchema Then { get => this.schema.Then; set => this.schema.Then = value; }
+
+ public AsyncApiSchema Else { get => this.schema.Else; set => this.schema.Else = value; }
+
+ public ISet Required { get => this.schema.Required; set => this.schema.Required = value; }
+
+ public AsyncApiSchema Items { get => this.schema.Items; set => this.schema.Items = value; }
+
+ public AsyncApiSchema AdditionalItems { get => this.schema.AdditionalItems; set => this.schema.AdditionalItems = value; }
+
+ public int? MaxItems { get => this.schema.MaxItems; set => this.schema.MaxItems = value; }
+
+ public int? MinItems { get => this.schema.MinItems; set => this.schema.MinItems = value; }
+
+ public bool? UniqueItems { get => this.schema.UniqueItems; set => this.schema.UniqueItems = value; }
+
+ public IDictionary Properties { get => this.schema.Properties; set => this.schema.Properties = value; }
+
+ public int? MaxProperties { get => this.schema.MaxProperties; set => this.schema.MaxProperties = value; }
+
+ public int? MinProperties { get => this.schema.MinProperties; set => this.schema.MinProperties = value; }
+
+ public IDictionary PatternProperties { get => this.schema.PatternProperties; set => this.schema.PatternProperties = value; }
+
+ public AsyncApiSchema PropertyNames { get => this.schema.PropertyNames; set => this.schema.PropertyNames = value; }
+
+ public string Discriminator { get => this.schema.Discriminator; set => this.schema.Discriminator = value; }
+
+ public IList Enum { get => this.schema.Enum; set => this.schema.Enum = value; }
+
+ public IList Examples { get => this.schema.Examples; set => this.schema.Examples = value; }
+
+ public AsyncApiAny Const { get => this.schema.Const; set => this.schema.Const = value; }
+
+ public bool Nullable { get => this.schema.Nullable; set => this.schema.Nullable = value; }
+
+ public AsyncApiExternalDocumentation ExternalDocs { get => this.schema.ExternalDocs; set => this.schema.ExternalDocs = value; }
+
+ public bool Deprecated { get => this.schema.Deprecated; set => this.schema.Deprecated = value; }
+
+ public bool UnresolvedReference { get => this.schema.UnresolvedReference; set => this.schema.UnresolvedReference = value; }
+
+ public AsyncApiReference Reference { get => this.schema.Reference; set => this.schema.Reference = value; }
+
+ public IDictionary Extensions { get => this.schema.Extensions; set => this.schema.Extensions = value; }
+
+ public AsyncApiSchema AdditionalProperties { get => this.schema.AdditionalProperties; set => this.schema.AdditionalProperties = value; }
+
+ public static implicit operator AsyncApiSchema(AsyncApiJsonSchemaPayload payload) => payload.schema;
+
+ public static implicit operator AsyncApiJsonSchemaPayload(AsyncApiSchema schema) => new AsyncApiJsonSchemaPayload(schema);
+
+ public void SerializeV2(IAsyncApiWriter writer)
+ {
+ this.schema.SerializeV2(writer);
+ }
+
+ public void SerializeV2WithoutReference(IAsyncApiWriter writer)
+ {
+ this.schema.SerializeV2WithoutReference(writer);
+ }
+ }
+}
diff --git a/src/LEGO.AsyncAPI/Models/AsyncApiSerializableExtensions.cs b/src/LEGO.AsyncAPI/Models/AsyncApiSerializableExtensions.cs
index 1d1bd35a..91167e19 100644
--- a/src/LEGO.AsyncAPI/Models/AsyncApiSerializableExtensions.cs
+++ b/src/LEGO.AsyncAPI/Models/AsyncApiSerializableExtensions.cs
@@ -33,7 +33,7 @@ public static void SerializeAsJson(this T element, Stream stream, AsyncApiVer
/// The AsyncApi element.
/// The output stream.
/// The AsyncApi specification version.
- /// The settings used for writing
+ /// The settings used for writing.
public static void SerializeAsJson(this T element, Stream stream, AsyncApiVersion specificationVersion, AsyncApiWriterSettings settings)
where T : IAsyncApiSerializable
{
@@ -60,7 +60,7 @@ public static void SerializeAsYaml(this T element, Stream stream, AsyncApiVer
/// The AsyncApi element.
/// The output stream.
/// The AsyncApi specification version.
- /// The settings used for writing
+ /// The settings used for writing.
public static void SerializeAsYaml(this T element, Stream stream, AsyncApiVersion specificationVersion, AsyncApiWriterSettings settings)
where T : IAsyncApiSerializable
{
diff --git a/src/LEGO.AsyncAPI/Models/Avro/AvroArray.cs b/src/LEGO.AsyncAPI/Models/Avro/AvroArray.cs
new file mode 100644
index 00000000..0958fa3d
--- /dev/null
+++ b/src/LEGO.AsyncAPI/Models/Avro/AvroArray.cs
@@ -0,0 +1,47 @@
+// Copyright (c) The LEGO Group. All rights reserved.
+
+namespace LEGO.AsyncAPI.Models
+{
+ using System.Collections.Generic;
+ using System.Linq;
+ using LEGO.AsyncAPI.Writers;
+
+ public class AvroArray : AvroSchema
+ {
+ public override string Type { get; } = "array";
+
+ ///
+ /// The schema of the array's items.
+ ///
+ public AvroSchema Items { get; set; }
+
+ ///
+ /// A map of properties not in the schema, but added as additional metadata.
+ ///
+ public override IDictionary Metadata { get; set; } = new Dictionary();
+
+ public override void SerializeV2WithoutReference(IAsyncApiWriter writer)
+ {
+ writer.WriteStartObject();
+ writer.WriteOptionalProperty("type", this.Type);
+ writer.WriteRequiredObject("items", this.Items, (w, f) => f.SerializeV2(w));
+ if (this.Metadata.Any())
+ {
+ foreach (var item in this.Metadata)
+ {
+ writer.WritePropertyName(item.Key);
+ if (item.Value == null)
+ {
+ writer.WriteNull();
+ }
+ else
+ {
+ writer.WriteAny(item.Value);
+ }
+ }
+ }
+
+ writer.WriteEndObject();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/LEGO.AsyncAPI/Models/Avro/AvroEnum.cs b/src/LEGO.AsyncAPI/Models/Avro/AvroEnum.cs
new file mode 100644
index 00000000..dbfbb627
--- /dev/null
+++ b/src/LEGO.AsyncAPI/Models/Avro/AvroEnum.cs
@@ -0,0 +1,77 @@
+// Copyright (c) The LEGO Group. All rights reserved.
+
+namespace LEGO.AsyncAPI.Models
+{
+ using System.Collections.Generic;
+ using System.Linq;
+ using LEGO.AsyncAPI.Writers;
+
+ public class AvroEnum : AvroSchema
+ {
+ public override string Type { get; } = "enum";
+
+ ///
+ /// The name of the schema. Required for named types. See Avro Names.
+ ///
+ public string Name { get; set; }
+
+ ///
+ /// The namespace of the schema. Useful for named types to avoid name conflicts.
+ ///
+ public string Namespace { get; set; }
+
+ ///
+ /// Documentation for the schema.
+ ///
+ public string Doc { get; set; }
+
+ ///
+ /// Alternate names for this enum.
+ ///
+ public IList Aliases { get; set; } = new List();
+
+ ///
+ /// Listing symbols. All symbols in an enum must be unique.
+ ///
+ public IList Symbols { get; set; } = new List();
+
+ ///
+ /// A default value for this enumeration.
+ ///
+ public string Default { get; set; }
+
+ ///
+ /// A map of properties not in the schema, but added as additional metadata.
+ ///
+ public override IDictionary Metadata { get; set; } = new Dictionary();
+
+ public override void SerializeV2WithoutReference(IAsyncApiWriter writer)
+ {
+ writer.WriteStartObject();
+ writer.WriteOptionalProperty("type", this.Type);
+ writer.WriteRequiredProperty("name", this.Name);
+ writer.WriteOptionalProperty("namespace", this.Namespace);
+ writer.WriteOptionalCollection("aliases", this.Aliases, (w, s) => w.WriteValue(s));
+ writer.WriteOptionalProperty("doc", this.Doc);
+ writer.WriteRequiredCollection("symbols", this.Symbols, (w, s) => w.WriteValue(s));
+ writer.WriteOptionalProperty("default", this.Default);
+ if (this.Metadata.Any())
+ {
+ foreach (var item in this.Metadata)
+ {
+ writer.WritePropertyName(item.Key);
+ if (item.Value == null)
+ {
+ writer.WriteNull();
+ }
+ else
+ {
+ writer.WriteAny(item.Value);
+ }
+ }
+ }
+
+ writer.WriteEndObject();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/LEGO.AsyncAPI/Models/Avro/AvroField.cs b/src/LEGO.AsyncAPI/Models/Avro/AvroField.cs
new file mode 100644
index 00000000..6e8f9ed8
--- /dev/null
+++ b/src/LEGO.AsyncAPI/Models/Avro/AvroField.cs
@@ -0,0 +1,96 @@
+// Copyright (c) The LEGO Group. All rights reserved.
+
+namespace LEGO.AsyncAPI.Models
+{
+ using System.Collections.Generic;
+ using System.Linq;
+ using LEGO.AsyncAPI.Attributes;
+ using LEGO.AsyncAPI.Models.Interfaces;
+ using LEGO.AsyncAPI.Writers;
+
+ public enum AvroFieldOrder
+ {
+ None = 0,
+
+ [Display("ascending")]
+ Ascending,
+
+ [Display("descending")]
+ Descending,
+
+ [Display("ignore")]
+ Ignore,
+ }
+ ///
+ /// Represents a field within an Avro record schema.
+ ///
+ public class AvroField : IAsyncApiSerializable
+ {
+ ///
+ /// The name of the field.
+ ///
+ public string Name { get; set; }
+
+ ///
+ /// The type of the field. Can be a primitive type, a complex type, or a union.
+ ///
+ public AvroSchema Type { get; set; }
+
+ ///
+ /// The documentation for the field.
+ ///
+ public string Doc { get; set; }
+
+ ///
+ /// The default value for the field.
+ ///
+ public AsyncApiAny Default { get; set; }
+
+ ///
+ /// The order of the field, can be 'ascending', 'descending', or 'ignore'.
+ ///
+ public AvroFieldOrder Order { get; set; }
+
+ ///
+ /// Alternate names for this record (optional).
+ ///
+ public IList Aliases { get; set; } = new List();
+
+ ///
+ /// A map of properties not in the schema, but added as additional metadata.
+ ///
+ public IDictionary Metadata { get; set; } = new Dictionary();
+
+ public void SerializeV2(IAsyncApiWriter writer)
+ {
+ writer.WriteStartObject();
+ writer.WriteOptionalProperty("name", this.Name);
+ writer.WriteOptionalObject("type", this.Type, (w, s) => s.SerializeV2(w));
+ writer.WriteOptionalProperty("doc", this.Doc);
+ writer.WriteOptionalObject("default", this.Default, (w, s) => w.WriteAny(s));
+ if (this.Order != AvroFieldOrder.None)
+ {
+ writer.WriteOptionalProperty("order", this.Order.GetDisplayName());
+ }
+
+ writer.WriteOptionalCollection("aliases", this.Aliases, (w, s) => w.WriteValue(s));
+ if (this.Metadata.Any())
+ {
+ foreach (var item in this.Metadata)
+ {
+ writer.WritePropertyName(item.Key);
+ if (item.Value == null)
+ {
+ writer.WriteNull();
+ }
+ else
+ {
+ writer.WriteAny(item.Value);
+ }
+ }
+ }
+
+ writer.WriteEndObject();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/LEGO.AsyncAPI/Models/Avro/AvroFixed.cs b/src/LEGO.AsyncAPI/Models/Avro/AvroFixed.cs
new file mode 100644
index 00000000..241e8c1e
--- /dev/null
+++ b/src/LEGO.AsyncAPI/Models/Avro/AvroFixed.cs
@@ -0,0 +1,66 @@
+// Copyright (c) The LEGO Group. All rights reserved.
+
+namespace LEGO.AsyncAPI.Models
+{
+ using System.Collections.Generic;
+ using System.Linq;
+ using LEGO.AsyncAPI.Writers;
+
+ public class AvroFixed : AvroSchema
+ {
+ public override string Type { get; } = "fixed";
+
+ ///
+ /// The name of the schema. Required for named types. See Avro Names.
+ ///
+ public string Name { get; set; }
+
+ ///
+ /// The namespace of the schema. Useful for named types to avoid name conflicts.
+ ///
+ public string Namespace { get; set; }
+
+
+ ///
+ /// Alternate names for this record.
+ ///
+ public IList Aliases { get; set; } = new List();
+
+ ///
+ /// Number of bytes per value.
+ ///
+ public int Size { get; set; }
+
+ ///
+ /// A map of properties not in the schema, but added as additional metadata.
+ ///
+ public override IDictionary Metadata { get; set; } = new Dictionary();
+
+ public override void SerializeV2WithoutReference(IAsyncApiWriter writer)
+ {
+ writer.WriteStartObject();
+ writer.WriteOptionalProperty("type", this.Type);
+ writer.WriteRequiredProperty("name", this.Name);
+ writer.WriteOptionalProperty("namespace", this.Namespace);
+ writer.WriteOptionalCollection("aliases", this.Aliases, (w, s) => w.WriteValue(s));
+ writer.WriteRequiredProperty("size", this.Size);
+ if (this.Metadata.Any())
+ {
+ foreach (var item in this.Metadata)
+ {
+ writer.WritePropertyName(item.Key);
+ if (item.Value == null)
+ {
+ writer.WriteNull();
+ }
+ else
+ {
+ writer.WriteAny(item.Value);
+ }
+ }
+ }
+
+ writer.WriteEndObject();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/LEGO.AsyncAPI/Models/Avro/AvroMap.cs b/src/LEGO.AsyncAPI/Models/Avro/AvroMap.cs
new file mode 100644
index 00000000..c66d476b
--- /dev/null
+++ b/src/LEGO.AsyncAPI/Models/Avro/AvroMap.cs
@@ -0,0 +1,45 @@
+// Copyright (c) The LEGO Group. All rights reserved.
+
+namespace LEGO.AsyncAPI.Models
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using LEGO.AsyncAPI.Writers;
+
+ public class AvroMap : AvroSchema
+ {
+ public override string Type { get; } = "map";
+
+ public AvroPrimitiveType Values { get; set; }
+
+ ///
+ /// A map of properties not in the schema, but added as additional metadata.
+ ///
+ public override IDictionary Metadata { get; set; } = new Dictionary();
+
+ public override void SerializeV2WithoutReference(IAsyncApiWriter writer)
+ {
+ writer.WriteStartObject();
+ writer.WriteOptionalProperty("type", this.Type);
+ writer.WriteRequiredProperty("values", this.Values.GetDisplayName());
+ if (this.Metadata.Any())
+ {
+ foreach (var item in this.Metadata)
+ {
+ writer.WritePropertyName(item.Key);
+ if (item.Value == null)
+ {
+ writer.WriteNull();
+ }
+ else
+ {
+ writer.WriteAny(item.Value);
+ }
+ }
+ }
+
+ writer.WriteEndObject();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/LEGO.AsyncAPI/Models/Avro/AvroPrimitive.cs b/src/LEGO.AsyncAPI/Models/Avro/AvroPrimitive.cs
new file mode 100644
index 00000000..d7526344
--- /dev/null
+++ b/src/LEGO.AsyncAPI/Models/Avro/AvroPrimitive.cs
@@ -0,0 +1,47 @@
+// Copyright (c) The LEGO Group. All rights reserved.
+
+namespace LEGO.AsyncAPI.Models
+{
+ using System.Collections.Generic;
+ using System.Linq;
+ using LEGO.AsyncAPI.Writers;
+
+ public class AvroPrimitive : AvroSchema
+ {
+ public override string Type { get; }
+
+ ///
+ /// A map of properties not in the schema, but added as additional metadata.
+ ///
+ public override IDictionary Metadata { get; set; } = new Dictionary();
+
+ public AvroPrimitive(AvroPrimitiveType type)
+ {
+ this.Type = type.GetDisplayName();
+ }
+
+ public AvroPrimitive()
+ {
+ }
+
+ public override void SerializeV2WithoutReference(IAsyncApiWriter writer)
+ {
+ writer.WriteValue(this.Type);
+ if (this.Metadata.Any())
+ {
+ foreach (var item in this.Metadata)
+ {
+ writer.WritePropertyName(item.Key);
+ if (item.Value == null)
+ {
+ writer.WriteNull();
+ }
+ else
+ {
+ writer.WriteAny(item.Value);
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/LEGO.AsyncAPI/Models/Avro/AvroPrimitiveType.cs b/src/LEGO.AsyncAPI/Models/Avro/AvroPrimitiveType.cs
new file mode 100644
index 00000000..b76e7554
--- /dev/null
+++ b/src/LEGO.AsyncAPI/Models/Avro/AvroPrimitiveType.cs
@@ -0,0 +1,33 @@
+// Copyright (c) The LEGO Group. All rights reserved.
+
+namespace LEGO.AsyncAPI.Models
+{
+ using LEGO.AsyncAPI.Attributes;
+
+ public enum AvroPrimitiveType
+ {
+ [Display("null")]
+ Null,
+
+ [Display("boolean")]
+ Boolean,
+
+ [Display("int")]
+ Int,
+
+ [Display("long")]
+ Long,
+
+ [Display("float")]
+ Float,
+
+ [Display("double")]
+ Double,
+
+ [Display("bytes")]
+ Bytes,
+
+ [Display("string")]
+ String,
+ }
+}
\ No newline at end of file
diff --git a/src/LEGO.AsyncAPI/Models/Avro/AvroRecord.cs b/src/LEGO.AsyncAPI/Models/Avro/AvroRecord.cs
new file mode 100644
index 00000000..04642f5c
--- /dev/null
+++ b/src/LEGO.AsyncAPI/Models/Avro/AvroRecord.cs
@@ -0,0 +1,71 @@
+// Copyright (c) The LEGO Group. All rights reserved.
+
+namespace LEGO.AsyncAPI.Models
+{
+ using System.Collections.Generic;
+ using System.Linq;
+ using LEGO.AsyncAPI.Writers;
+
+ public class AvroRecord : AvroSchema
+ {
+ public override string Type { get; } = "record";
+
+ ///
+ /// The name of the schema. Required for named types. See Avro Names.
+ ///
+ public string Name { get; set; }
+
+ ///
+ /// The namespace of the schema. Useful for named types to avoid name conflicts.
+ ///
+ public string Namespace { get; set; }
+
+ ///
+ /// Documentation for the schema.
+ ///
+ public string Doc { get; set; }
+
+ ///
+ /// Alternate names for this record.
+ ///
+ public IList Aliases { get; set; } = new List();
+
+ ///
+ /// A list of fields contained within this record.
+ ///
+ public IList Fields { get; set; } = new List();
+
+ ///
+ /// A map of properties not in the schema, but added as additional metadata.
+ ///
+ public override IDictionary Metadata { get; set; } = new Dictionary();
+
+ public override void SerializeV2WithoutReference(IAsyncApiWriter writer)
+ {
+ writer.WriteStartObject();
+ writer.WriteOptionalProperty("type", this.Type);
+ writer.WriteRequiredProperty("name", this.Name);
+ writer.WriteOptionalProperty("namespace", this.Namespace);
+ writer.WriteOptionalProperty("doc", this.Doc);
+ writer.WriteOptionalCollection("aliases", this.Aliases, (w, s) => w.WriteValue(s));
+ writer.WriteRequiredCollection("fields", this.Fields, (w, s) => s.SerializeV2(w));
+ if (this.Metadata.Any())
+ {
+ foreach (var item in this.Metadata)
+ {
+ writer.WritePropertyName(item.Key);
+ if (item.Value == null)
+ {
+ writer.WriteNull();
+ }
+ else
+ {
+ writer.WriteAny(item.Value);
+ }
+ }
+ }
+
+ writer.WriteEndObject();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/LEGO.AsyncAPI/Models/Avro/AvroSchema.cs b/src/LEGO.AsyncAPI/Models/Avro/AvroSchema.cs
new file mode 100644
index 00000000..fce1df2d
--- /dev/null
+++ b/src/LEGO.AsyncAPI/Models/Avro/AvroSchema.cs
@@ -0,0 +1,46 @@
+// Copyright (c) The LEGO Group. All rights reserved.
+
+namespace LEGO.AsyncAPI.Models
+{
+ using System;
+ using System.Collections.Generic;
+ using LEGO.AsyncAPI.Models.Interfaces;
+ using LEGO.AsyncAPI.Writers;
+
+ public abstract class AvroSchema : IAsyncApiSerializable, IAsyncApiReferenceable
+ {
+ public abstract string Type { get; }
+
+ ///
+ /// A map of properties not in the schema, but added as additional metadata.
+ ///
+ public abstract IDictionary Metadata { get; set; }
+
+ public bool UnresolvedReference { get; set; }
+
+ public AsyncApiReference Reference { get; set; }
+
+ public static implicit operator AvroSchema(AvroPrimitiveType type)
+ {
+ return new AvroPrimitive(type);
+ }
+
+ public void SerializeV2(IAsyncApiWriter writer)
+ {
+ if (writer is null)
+ {
+ throw new ArgumentNullException(nameof(writer));
+ }
+
+ if (this.Reference != null && !writer.GetSettings().ShouldInlineReference(this.Reference))
+ {
+ this.Reference.SerializeV2(writer);
+ return;
+ }
+
+ this.SerializeV2WithoutReference(writer);
+ }
+
+ public abstract void SerializeV2WithoutReference(IAsyncApiWriter writer);
+ }
+}
\ No newline at end of file
diff --git a/src/LEGO.AsyncAPI/Models/Avro/AvroUnion.cs b/src/LEGO.AsyncAPI/Models/Avro/AvroUnion.cs
new file mode 100644
index 00000000..5762935d
--- /dev/null
+++ b/src/LEGO.AsyncAPI/Models/Avro/AvroUnion.cs
@@ -0,0 +1,50 @@
+// Copyright (c) The LEGO Group. All rights reserved.
+
+namespace LEGO.AsyncAPI.Models
+{
+ using System.Collections.Generic;
+ using System.Linq;
+ using LEGO.AsyncAPI.Writers;
+
+ public class AvroUnion : AvroSchema
+ {
+ public override string Type { get; } = "map";
+
+ ///
+ /// The types in this union.
+ ///
+ public IList Types { get; set; } = new List();
+
+ ///
+ /// A map of properties not in the schema, but added as additional metadata.
+ ///
+ public override IDictionary Metadata { get; set; } = new Dictionary();
+
+ public override void SerializeV2WithoutReference(IAsyncApiWriter writer)
+ {
+ writer.WriteStartArray();
+ foreach (var type in this.Types)
+ {
+ type.SerializeV2(writer);
+ }
+
+ if (this.Metadata.Any())
+ {
+ foreach (var item in this.Metadata)
+ {
+ writer.WritePropertyName(item.Key);
+ if (item.Value == null)
+ {
+ writer.WriteNull();
+ }
+ else
+ {
+ writer.WriteAny(item.Value);
+ }
+ }
+ }
+
+ writer.WriteEndArray();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/LEGO.AsyncAPI/Models/Interfaces/IAsyncApiPayload.cs b/src/LEGO.AsyncAPI/Models/Interfaces/IAsyncApiPayload.cs
new file mode 100644
index 00000000..76112af9
--- /dev/null
+++ b/src/LEGO.AsyncAPI/Models/Interfaces/IAsyncApiPayload.cs
@@ -0,0 +1,8 @@
+// Copyright (c) The LEGO Group. All rights reserved.
+
+namespace LEGO.AsyncAPI.Models.Interfaces
+{
+ public interface IAsyncApiMessagePayload : IAsyncApiSerializable, IAsyncApiReferenceable
+ {
+ }
+}
diff --git a/src/LEGO.AsyncAPI/Models/ParameterLocation.cs b/src/LEGO.AsyncAPI/Models/ParameterLocation.cs
index 54feb896..bcda720d 100644
--- a/src/LEGO.AsyncAPI/Models/ParameterLocation.cs
+++ b/src/LEGO.AsyncAPI/Models/ParameterLocation.cs
@@ -7,12 +7,12 @@ namespace LEGO.AsyncAPI.Models
public enum ParameterLocation
{
///
- /// The user
+ /// The user.
///
[Display("user")] User,
///
- /// The password
+ /// The password.
///
[Display("password")] Password,
diff --git a/src/LEGO.AsyncAPI/Models/ReferenceType.cs b/src/LEGO.AsyncAPI/Models/ReferenceType.cs
index 8903dd73..cc7dcaf5 100644
--- a/src/LEGO.AsyncAPI/Models/ReferenceType.cs
+++ b/src/LEGO.AsyncAPI/Models/ReferenceType.cs
@@ -84,7 +84,7 @@ public enum ReferenceType
[Display("headers")] Header,
///
- /// The server variable
+ /// The server variable.
///
[Display("serverVariable")] ServerVariable,
}
diff --git a/src/LEGO.AsyncAPI/Services/AsyncApiReferenceResolver.cs b/src/LEGO.AsyncAPI/Services/AsyncApiReferenceResolver.cs
index da6195c6..af1176c3 100644
--- a/src/LEGO.AsyncAPI/Services/AsyncApiReferenceResolver.cs
+++ b/src/LEGO.AsyncAPI/Services/AsyncApiReferenceResolver.cs
@@ -1,5 +1,4 @@
// Copyright (c) The LEGO Group. All rights reserved.
-
namespace LEGO.AsyncAPI.Services
{
using System;
@@ -17,7 +16,8 @@ internal class AsyncApiReferenceResolver : AsyncApiVisitorBase
private AsyncApiDocument currentDocument;
private List errors = new List();
- public AsyncApiReferenceResolver(AsyncApiDocument currentDocument)
+ public AsyncApiReferenceResolver(
+ AsyncApiDocument currentDocument)
{
this.currentDocument = currentDocument;
}
@@ -86,7 +86,13 @@ public override void Visit(AsyncApiOperation operation)
public override void Visit(AsyncApiMessage message)
{
this.ResolveObject(message.Headers, r => message.Headers = r);
- this.ResolveObject(message.Payload, r => message.Payload = r);
+
+ // #ToFix Resolve references correctly
+ if (message.Payload is AsyncApiJsonSchemaPayload)
+ {
+ this.ResolveObject(message.Payload as AsyncApiJsonSchemaPayload, r => message.Payload = r);
+ }
+
this.ResolveList(message.Traits);
this.ResolveObject(message.CorrelationId, r => message.CorrelationId = r);
this.ResolveObject(message.Bindings, r => message.Bindings = r);
@@ -207,6 +213,7 @@ private void ResolveMap(IDictionary map)
private T ResolveReference(AsyncApiReference reference)
where T : class, IAsyncApiReferenceable, new()
{
+ // external references are resolved by the AsyncApiExternalReferenceResolver
if (reference.IsExternal)
{
return new()
@@ -218,7 +225,13 @@ private T ResolveReference(AsyncApiReference reference)
try
{
- return this.currentDocument.ResolveReference(reference) as T;
+ var resolvedReference = this.currentDocument.ResolveReference(reference) as T;
+ if (resolvedReference == null)
+ {
+ throw new AsyncApiException($"Cannot resolve reference '{reference.Reference}' to '{typeof(T).Name}'.");
+ }
+
+ return resolvedReference;
}
catch (AsyncApiException ex)
{
diff --git a/src/LEGO.AsyncAPI/Services/AsyncApiWalker.cs b/src/LEGO.AsyncAPI/Services/AsyncApiWalker.cs
index fc5c5186..a2108647 100644
--- a/src/LEGO.AsyncAPI/Services/AsyncApiWalker.cs
+++ b/src/LEGO.AsyncAPI/Services/AsyncApiWalker.cs
@@ -501,7 +501,12 @@ internal void Walk(AsyncApiMessage message, bool isComponent = false)
if (message != null)
{
this.Walk(AsyncApiConstants.Headers, () => this.Walk(message.Headers));
- this.Walk(AsyncApiConstants.Payload, () => this.Walk(message.Payload));
+ if (message.Payload is AsyncApiJsonSchemaPayload payload)
+ {
+ this.Walk(AsyncApiConstants.Payload, () => this.Walk((AsyncApiSchema)payload));
+ }
+
+ // #ToFix Add walking for avro.
this.Walk(AsyncApiConstants.CorrelationId, () => this.Walk(message.CorrelationId));
this.Walk(AsyncApiConstants.Tags, () => this.Walk(message.Tags));
this.Walk(AsyncApiConstants.Examples, () => this.Walk(message.Examples));
diff --git a/src/LEGO.AsyncAPI/Validation/Rules/AsyncApiOAuthFlowRules.cs b/src/LEGO.AsyncAPI/Validation/Rules/AsyncApiOAuthFlowRules.cs
index 457a03ec..66090074 100644
--- a/src/LEGO.AsyncAPI/Validation/Rules/AsyncApiOAuthFlowRules.cs
+++ b/src/LEGO.AsyncAPI/Validation/Rules/AsyncApiOAuthFlowRules.cs
@@ -2,9 +2,9 @@
namespace LEGO.AsyncAPI.Validation.Rules
{
+ using System.Linq;
using LEGO.AsyncAPI.Models;
using LEGO.AsyncAPI.Validations;
- using System.Linq;
[AsyncApiRule]
public static class AsyncApiOAuthFlowRules
diff --git a/src/LEGO.AsyncAPI/Writers/AsyncApiWriterException.cs b/src/LEGO.AsyncAPI/Writers/AsyncApiWriterException.cs
index 9d89bba3..17229818 100644
--- a/src/LEGO.AsyncAPI/Writers/AsyncApiWriterException.cs
+++ b/src/LEGO.AsyncAPI/Writers/AsyncApiWriterException.cs
@@ -2,8 +2,8 @@
namespace LEGO.AsyncAPI.Writers
{
- using LEGO.AsyncAPI.Exceptions;
using System;
+ using LEGO.AsyncAPI.Exceptions;
public class AsyncApiWriterException : AsyncApiException
{
diff --git a/src/LEGO.AsyncAPI/Writers/AsyncApiWriterExtensions.cs b/src/LEGO.AsyncAPI/Writers/AsyncApiWriterExtensions.cs
index 67c4737a..c909e168 100644
--- a/src/LEGO.AsyncAPI/Writers/AsyncApiWriterExtensions.cs
+++ b/src/LEGO.AsyncAPI/Writers/AsyncApiWriterExtensions.cs
@@ -204,6 +204,22 @@ public static void WriteOptionalCollection(
}
}
+ ///
+ /// Write the required of collection string.
+ ///
+ /// The AsyncApi writer.
+ /// The property name.
+ /// The collection values.
+ /// The collection element writer action.
+ public static void WriteRequiredCollection(
+ this IAsyncApiWriter writer,
+ string name,
+ IEnumerable elements,
+ Action action)
+ {
+ writer.WriteCollectionInternal(name, elements, action);
+ }
+
///
/// Write the optional AsyncApi object/element collection.
///
diff --git a/src/LEGO.AsyncAPI/Writers/AsyncApiYamlWriter.cs b/src/LEGO.AsyncAPI/Writers/AsyncApiYamlWriter.cs
index d0fab76e..f7b32dd9 100644
--- a/src/LEGO.AsyncAPI/Writers/AsyncApiYamlWriter.cs
+++ b/src/LEGO.AsyncAPI/Writers/AsyncApiYamlWriter.cs
@@ -42,7 +42,7 @@ public AsyncApiYamlWriter(TextWriter textWriter)
/// Initializes a new instance of the class.
///
/// The text writer.
- /// The settings used to read and write yaml
+ /// The settings used to read and write yaml.
public AsyncApiYamlWriter(TextWriter textWriter, AsyncApiWriterSettings settings)
: base(textWriter, settings)
{
diff --git a/src/LEGO.AsyncAPI/Writers/SpecialCharacterStringExtensions.cs b/src/LEGO.AsyncAPI/Writers/SpecialCharacterStringExtensions.cs
index f2f6fbe2..9bce6aa2 100644
--- a/src/LEGO.AsyncAPI/Writers/SpecialCharacterStringExtensions.cs
+++ b/src/LEGO.AsyncAPI/Writers/SpecialCharacterStringExtensions.cs
@@ -2,11 +2,6 @@
namespace LEGO.AsyncAPI.Writers
{
- using System;
- using System.Globalization;
- using System.Linq;
- using System.Text.RegularExpressions;
-
public static class SpecialCharacterStringExtensions
{
///
diff --git a/test/LEGO.AsyncAPI.Tests/AsyncApiDocumentBuilder.cs b/test/LEGO.AsyncAPI.Tests/AsyncApiDocumentBuilder.cs
index 3f2ee429..f800a1f7 100644
--- a/test/LEGO.AsyncAPI.Tests/AsyncApiDocumentBuilder.cs
+++ b/test/LEGO.AsyncAPI.Tests/AsyncApiDocumentBuilder.cs
@@ -2,9 +2,9 @@
namespace LEGO.AsyncAPI.Tests
{
+ using System;
using LEGO.AsyncAPI.Models;
using LEGO.AsyncAPI.Models.Interfaces;
- using System;
internal class AsyncApiDocumentBuilder
{
diff --git a/test/LEGO.AsyncAPI.Tests/AsyncApiDocumentV2Tests.cs b/test/LEGO.AsyncAPI.Tests/AsyncApiDocumentV2Tests.cs
index 63e818b4..4cbf1bd4 100644
--- a/test/LEGO.AsyncAPI.Tests/AsyncApiDocumentV2Tests.cs
+++ b/test/LEGO.AsyncAPI.Tests/AsyncApiDocumentV2Tests.cs
@@ -4,7 +4,6 @@ namespace LEGO.AsyncAPI.Tests
{
using System;
using System.Collections.Generic;
- using System.Globalization;
using System.IO;
using System.Linq;
using FluentAssertions;
@@ -493,7 +492,7 @@ public void AsyncApiDocument_WithStreetLightsExample_SerializesAndDeserializes()
},
},
},
- Payload = new AsyncApiSchema()
+ Payload = new AsyncApiJsonSchemaPayload
{
Reference = new AsyncApiReference()
{
@@ -518,7 +517,7 @@ public void AsyncApiDocument_WithStreetLightsExample_SerializesAndDeserializes()
},
},
},
- Payload = new AsyncApiSchema()
+ Payload = new AsyncApiJsonSchemaPayload()
{
Reference = new AsyncApiReference()
{
@@ -543,7 +542,7 @@ public void AsyncApiDocument_WithStreetLightsExample_SerializesAndDeserializes()
},
},
},
- Payload = new AsyncApiSchema()
+ Payload = new AsyncApiJsonSchemaPayload()
{
Reference = new AsyncApiReference()
{
diff --git a/test/LEGO.AsyncAPI.Tests/AsyncApiReaderTests.cs b/test/LEGO.AsyncAPI.Tests/AsyncApiReaderTests.cs
index bf19a944..036fae56 100644
--- a/test/LEGO.AsyncAPI.Tests/AsyncApiReaderTests.cs
+++ b/test/LEGO.AsyncAPI.Tests/AsyncApiReaderTests.cs
@@ -6,6 +6,7 @@ namespace LEGO.AsyncAPI.Tests
using System.Collections.Generic;
using System.Linq;
using System.Text.Json.Nodes;
+ using FluentAssertions;
using LEGO.AsyncAPI.Exceptions;
using LEGO.AsyncAPI.Models;
using LEGO.AsyncAPI.Models.Interfaces;
@@ -31,6 +32,7 @@ public void Read_WithExtensionParser_Parses()
info:
title: test
version: 1.0.0
+ test: 1234
contact:
name: API Support
url: https://www.example.com/support
@@ -58,6 +60,7 @@ public void Read_WithExtensionParser_Parses()
{
{ extensionName, valueExtensionParser },
},
+ UnmappedMemberHandling = UnmappedMemberHandling.Ignore,
};
var reader = new AsyncApiStringReader(settings);
@@ -65,6 +68,96 @@ public void Read_WithExtensionParser_Parses()
Assert.AreEqual((doc.Channels["workspace"].Extensions[extensionName] as AsyncApiAny).GetValue(), 1234);
}
+ [Test]
+ public void Read_WithUnmappedMemberHandlingError_AddsError()
+ {
+ var extensionName = "x-someValue";
+ var yaml = $"""
+ asyncapi: 2.3.0
+ info:
+ title: test
+ version: 1.0.0
+ test: 1234
+ contact:
+ name: API Support
+ url: https://www.example.com/support
+ email: support@example.com
+ channels:
+ workspace:
+ {extensionName}: onetwothreefour
+ """;
+ Func valueExtensionParser = (any) =>
+ {
+ if (any.TryGetValue(out var value))
+ {
+ if (value == "onetwothreefour")
+ {
+ return new AsyncApiAny(1234);
+ }
+ }
+
+ return new AsyncApiAny("No value provided");
+ };
+
+ var settings = new AsyncApiReaderSettings
+ {
+ ExtensionParsers = new Dictionary>
+ {
+ { extensionName, valueExtensionParser },
+ },
+ UnmappedMemberHandling = UnmappedMemberHandling.Error,
+ };
+
+ var reader = new AsyncApiStringReader(settings);
+ var doc = reader.Read(yaml, out var diagnostic);
+ diagnostic.Errors.Should().HaveCount(1);
+ }
+
+ [Test]
+ public void Read_WithUnmappedMemberHandlingIgnore_NoErrors()
+ {
+ var extensionName = "x-someValue";
+ var yaml = $"""
+ asyncapi: 2.3.0
+ info:
+ title: test
+ version: 1.0.0
+ test: 1234
+ contact:
+ name: API Support
+ url: https://www.example.com/support
+ email: support@example.com
+ channels:
+ workspace:
+ {extensionName}: onetwothreefour
+ """;
+ Func valueExtensionParser = (any) =>
+ {
+ if (any.TryGetValue(out var value))
+ {
+ if (value == "onetwothreefour")
+ {
+ return new AsyncApiAny(1234);
+ }
+ }
+
+ return new AsyncApiAny("No value provided");
+ };
+
+ var settings = new AsyncApiReaderSettings
+ {
+ ExtensionParsers = new Dictionary>
+ {
+ { extensionName, valueExtensionParser },
+ },
+ UnmappedMemberHandling = UnmappedMemberHandling.Ignore,
+ };
+
+ var reader = new AsyncApiStringReader(settings);
+ var doc = reader.Read(yaml, out var diagnostic);
+ diagnostic.Errors.Should().HaveCount(0);
+ }
+
[Test]
public void Read_WithThrowingExtensionParser_AddsToDiagnostics()
{
@@ -336,6 +429,32 @@ public void Read_WithBasicPlusSecuritySchemeDeserializes()
Assert.AreEqual("Provide your username and password for SASL/SCRAM authentication", scheme.Value.Description);
}
+ [Test]
+ public void Read_WithWrongReference_AddsError()
+ {
+ var yaml =
+ """
+ asyncapi: 2.3.0
+ info:
+ title: test
+ version: 1.0.0
+ channels:
+ workspace:
+ publish:
+ message:
+ $ref: '#/components/securitySchemes/saslScram'
+ components:
+ securitySchemes:
+ saslScram:
+ type: scramSha256
+ description: Provide your username and password for SASL/SCRAM authentication
+ """;
+ var reader = new AsyncApiStringReader();
+ var doc = reader.Read(yaml, out var diagnostic);
+ diagnostic.Errors.Should().NotBeEmpty();
+ doc.Channels.Values.First().Publish.Message.First().Should().BeNull();
+ }
+
[Test]
public void Read_WithBasicPlusOAuthFlowDeserializes()
{
diff --git a/test/LEGO.AsyncAPI.Tests/Bindings/StringOrStringList_Should.cs b/test/LEGO.AsyncAPI.Tests/Bindings/StringOrStringList_Should.cs
index c437f15c..1a7c8681 100644
--- a/test/LEGO.AsyncAPI.Tests/Bindings/StringOrStringList_Should.cs
+++ b/test/LEGO.AsyncAPI.Tests/Bindings/StringOrStringList_Should.cs
@@ -4,7 +4,6 @@ namespace LEGO.AsyncAPI.Tests.Bindings
{
using System;
using System.Collections.Generic;
- using System.Linq;
using FluentAssertions;
using LEGO.AsyncAPI.Bindings;
using LEGO.AsyncAPI.Models;
diff --git a/test/LEGO.AsyncAPI.Tests/FluentAssertionExtensions.cs b/test/LEGO.AsyncAPI.Tests/FluentAssertionExtensions.cs
index 31fec68a..a9a771ab 100644
--- a/test/LEGO.AsyncAPI.Tests/FluentAssertionExtensions.cs
+++ b/test/LEGO.AsyncAPI.Tests/FluentAssertionExtensions.cs
@@ -3,7 +3,6 @@
namespace LEGO.AsyncAPI.Tests
{
using System;
- using System.IO;
using FluentAssertions;
using FluentAssertions.Primitives;
using NUnit.Framework;
diff --git a/test/LEGO.AsyncAPI.Tests/Models/AsyncApiAnyTests.cs b/test/LEGO.AsyncAPI.Tests/Models/AsyncApiAnyTests.cs
index 6c93e483..1dfdc59a 100644
--- a/test/LEGO.AsyncAPI.Tests/Models/AsyncApiAnyTests.cs
+++ b/test/LEGO.AsyncAPI.Tests/Models/AsyncApiAnyTests.cs
@@ -1,10 +1,9 @@
// Copyright (c) The LEGO Group. All rights reserved.
-using LEGO.AsyncAPI.Models;
-using NUnit.Framework;
-using System;
using System.Collections.Generic;
using System.Linq;
+using LEGO.AsyncAPI.Models;
+using NUnit.Framework;
namespace LEGO.AsyncAPI.Tests
{
diff --git a/test/LEGO.AsyncAPI.Tests/Models/AsyncApiChannel_Should.cs b/test/LEGO.AsyncAPI.Tests/Models/AsyncApiChannel_Should.cs
index ef24e07b..b19cd383 100644
--- a/test/LEGO.AsyncAPI.Tests/Models/AsyncApiChannel_Should.cs
+++ b/test/LEGO.AsyncAPI.Tests/Models/AsyncApiChannel_Should.cs
@@ -3,15 +3,36 @@
namespace LEGO.AsyncAPI.Tests.Models
{
using System.Collections.Generic;
+ using System.Linq;
using FluentAssertions;
using LEGO.AsyncAPI.Bindings.Kafka;
using LEGO.AsyncAPI.Bindings.WebSockets;
using LEGO.AsyncAPI.Models;
using LEGO.AsyncAPI.Models.Interfaces;
+ using LEGO.AsyncAPI.Readers;
using NUnit.Framework;
- internal class AsyncApiChannel_Should : TestBase
+ public class AsyncApiChannel_Should : TestBase
{
+ [Test]
+ public void AsyncApiChannel_WithInlineParameter_DoesNotCreateReference()
+ {
+ var input =
+ """
+ parameters:
+ id:
+ description: ids
+ schema:
+ type: string
+ enum:
+ - 08735ae0-6a1a-4578-8b4a-35aa26d15993
+ - 97845c62-329c-4d87-ad24-4f611b909a10
+ """;
+
+ var channel = new AsyncApiStringReader().ReadFragment(input, AsyncApiVersion.AsyncApi2_0, out var _ );
+ channel.Parameters.First().Value.Reference.Should().BeNull();
+ }
+
[Test]
public void AsyncApiChannel_WithWebSocketsBinding_Serializes()
{
diff --git a/test/LEGO.AsyncAPI.Tests/Models/AsyncApiMessage_Should.cs b/test/LEGO.AsyncAPI.Tests/Models/AsyncApiMessage_Should.cs
index a37f1503..538664a8 100644
--- a/test/LEGO.AsyncAPI.Tests/Models/AsyncApiMessage_Should.cs
+++ b/test/LEGO.AsyncAPI.Tests/Models/AsyncApiMessage_Should.cs
@@ -41,7 +41,7 @@ public void AsyncApiMessage_WithNoType_DeserializesToDefault()
// Assert
diagnostic.Errors.Should().BeEmpty();
- message.Payload.Properties.First().Value.Enum.Should().HaveCount(2);
+ message.Payload.As().Properties.First().Value.Enum.Should().HaveCount(2);
}
[Test]
@@ -78,7 +78,7 @@ public void AsyncApiMessage_WithUnsupportedSchemaFormat_DeserializesWithError()
type:
- 'null'
- string
- schemaFormat: application/vnd.apache.avro;version=1.9.0
+ schemaFormat: whatever
""";
// Act
@@ -86,7 +86,7 @@ public void AsyncApiMessage_WithUnsupportedSchemaFormat_DeserializesWithError()
// Assert
diagnostic.Errors.Should().HaveCount(1);
- diagnostic.Errors.First().Message.Should().StartWith("'application/vnd.apache.avro;version=1.9.0' is not a supported format");
+ diagnostic.Errors.First().Message.Should().StartWith("'whatever' is not a supported format");
}
[Test]
@@ -104,7 +104,7 @@ public void AsyncApiMessage_WithNoSchemaFormat_DoesNotSerializeSchemaFormat()
""";
var message = new AsyncApiMessage();
- message.Payload = new AsyncApiSchema()
+ message.Payload = new AsyncApiJsonSchemaPayload()
{
Properties = new Dictionary()
{
@@ -120,7 +120,6 @@ public void AsyncApiMessage_WithNoSchemaFormat_DoesNotSerializeSchemaFormat()
// Act
var actual = message.SerializeAsYaml(AsyncApiVersion.AsyncApi2_0);
-
var deserializedMessage = new AsyncApiStringReader().ReadFragment(expected, AsyncApiVersion.AsyncApi2_0, out _);
// Assert
@@ -130,7 +129,7 @@ public void AsyncApiMessage_WithNoSchemaFormat_DoesNotSerializeSchemaFormat()
}
[Test]
- public void AsyncApiMessage_WithSchemaFormat_Serializes()
+ public void AsyncApiMessage_WithJsonSchemaFormat_Serializes()
{
// Arrange
var expected =
@@ -146,7 +145,7 @@ public void AsyncApiMessage_WithSchemaFormat_Serializes()
var message = new AsyncApiMessage();
message.SchemaFormat = "application/vnd.aai.asyncapi+json;version=2.6.0";
- message.Payload = new AsyncApiSchema()
+ message.Payload = new AsyncApiJsonSchemaPayload()
{
Properties = new Dictionary()
{
@@ -169,6 +168,78 @@ public void AsyncApiMessage_WithSchemaFormat_Serializes()
message.Should().BeEquivalentTo(deserializedMessage);
}
+ [Test]
+ public void AsyncApiMessage_WithAvroSchemaFormat_Serializes()
+ {
+ // Arrange
+ var expected =
+ """
+ payload:
+ type: record
+ name: User
+ namespace: com.example
+ fields:
+ - name: username
+ type: string
+ doc: The username of the user.
+ default: guest
+ order: ascending
+ schemaFormat: application/vnd.apache.avro
+ """;
+
+ var message = new AsyncApiMessage();
+ message.SchemaFormat = "application/vnd.apache.avro";
+ message.Payload = new AsyncApiAvroSchemaPayload()
+ {
+ Schema = new AvroRecord()
+ {
+ Name = "User",
+ Namespace = "com.example",
+ Fields = new List
+ {
+ new AvroField()
+ {
+ Name = "username",
+ Type = AvroPrimitiveType.String,
+ Doc = "The username of the user.",
+ Default = new AsyncApiAny("guest"),
+ Order = AvroFieldOrder.Ascending,
+ },
+ },
+ },
+ };
+
+ // Act
+ var actual = message.SerializeAsYaml(AsyncApiVersion.AsyncApi2_0);
+ var deserializedMessage = new AsyncApiStringReader().ReadFragment(expected, AsyncApiVersion.AsyncApi2_0, out _);
+
+ // Assert
+ actual.Should()
+ .BePlatformAgnosticEquivalentTo(expected);
+ message.Should().BeEquivalentTo(deserializedMessage);
+ }
+
+ [Test]
+ public void AsyncApiMessage_WithAvroAsReference_Deserializes()
+ {
+ // Arrange
+ var input =
+ """
+ schemaFormat: 'application/vnd.apache.avro+yaml;version=1.9.0'
+ payload:
+ $ref: 'path/to/user-create.avsc/#UserCreate'
+ """;
+
+ // Act
+ var deserializedMessage = new AsyncApiStringReader().ReadFragment(input, AsyncApiVersion.AsyncApi2_0, out _);
+
+ // Assert
+ deserializedMessage.Payload.Reference.Should().NotBeNull();
+ deserializedMessage.Payload.Reference.IsExternal.Should().BeTrue();
+ deserializedMessage.Payload.Reference.IsFragment.Should().BeTrue();
+
+ }
+
[Test]
public void AsyncApiMessage_WithFilledObject_Serializes()
{
@@ -257,7 +328,7 @@ public void AsyncApiMessage_WithFilledObject_Serializes()
}),
},
},
- Payload = new AsyncApiSchema()
+ Payload = new AsyncApiJsonSchemaPayload()
{
Properties = new Dictionary
{
diff --git a/test/LEGO.AsyncAPI.Tests/Models/AsyncApiOperation_Should.cs b/test/LEGO.AsyncAPI.Tests/Models/AsyncApiOperation_Should.cs
index 5597b211..847da7be 100644
--- a/test/LEGO.AsyncAPI.Tests/Models/AsyncApiOperation_Should.cs
+++ b/test/LEGO.AsyncAPI.Tests/Models/AsyncApiOperation_Should.cs
@@ -3,7 +3,6 @@
namespace LEGO.AsyncAPI.Tests.Models
{
using System;
- using System.Globalization;
using System.IO;
using FluentAssertions;
using LEGO.AsyncAPI.Bindings.Http;
diff --git a/test/LEGO.AsyncAPI.Tests/Models/AsyncApiReference_Should.cs b/test/LEGO.AsyncAPI.Tests/Models/AsyncApiReference_Should.cs
index c4491e40..41972784 100644
--- a/test/LEGO.AsyncAPI.Tests/Models/AsyncApiReference_Should.cs
+++ b/test/LEGO.AsyncAPI.Tests/Models/AsyncApiReference_Should.cs
@@ -2,12 +2,11 @@
namespace LEGO.AsyncAPI.Tests
{
+ using System.Linq;
using FluentAssertions;
- using FluentAssertions.Primitives;
using LEGO.AsyncAPI.Models;
using LEGO.AsyncAPI.Readers;
using NUnit.Framework;
- using System.Linq;
public class AsyncApiReference_Should : TestBase
{
@@ -26,9 +25,10 @@ public void AsyncApiReference_WithExternalFragmentUriReference_AllowReference()
// Assert
diagnostic.Errors.Should().BeEmpty();
- deserialized.Payload.UnresolvedReference.Should().BeTrue();
+ var payload = deserialized.Payload.As();
+ payload.UnresolvedReference.Should().BeTrue();
- var reference = deserialized.Payload.Reference;
+ var reference = payload.Reference;
reference.ExternalResource.Should().Be("http://example.com/some-resource");
reference.Id.Should().Be("/path/to/external/fragment");
reference.IsFragment.Should().BeTrue();
@@ -54,9 +54,10 @@ public void AsyncApiReference_WithFragmentReference_AllowReference()
// Assert
diagnostic.Errors.Should().BeEmpty();
- deserialized.Payload.UnresolvedReference.Should().BeTrue();
+ var payload = deserialized.Payload.As();
+ payload.UnresolvedReference.Should().BeTrue();
- var reference = deserialized.Payload.Reference;
+ var reference = payload.Reference;
reference.Type.Should().Be(ReferenceType.Schema);
reference.ExternalResource.Should().Be("/fragments/myFragment");
reference.Id.Should().BeNull();
@@ -82,7 +83,8 @@ public void AsyncApiReference_WithInternalComponentReference_AllowReference()
// Assert
diagnostic.Errors.Should().BeEmpty();
- var reference = deserialized.Payload.Reference;
+ var payload = deserialized.Payload.As();
+ var reference = payload.Reference;
reference.ExternalResource.Should().BeNull();
reference.Type.Should().Be(ReferenceType.Schema);
reference.Id.Should().Be("test");
@@ -109,7 +111,8 @@ public void AsyncApiReference_WithExternalFragmentReference_AllowReference()
// Assert
diagnostic.Errors.Should().BeEmpty();
- var reference = deserialized.Payload.Reference;
+ var payload = deserialized.Payload.As();
+ var reference = payload.Reference;
reference.ExternalResource.Should().Be("./myjsonfile.json");
reference.Id.Should().Be("/fragment");
reference.IsFragment.Should().BeTrue();
@@ -135,7 +138,8 @@ public void AsyncApiReference_WithExternalComponentReference_AllowReference()
// Assert
diagnostic.Errors.Should().BeEmpty();
- var reference = deserialized.Payload.Reference;
+ var payload = deserialized.Payload.As();
+ var reference = payload.Reference;
reference.ExternalResource.Should().Be("./someotherdocument.json");
reference.Type.Should().Be(ReferenceType.Schema);
reference.Id.Should().Be("test");
@@ -167,7 +171,7 @@ public void AsyncApiDocument_WithInternalComponentReference_ResolvesReference()
var settings = new AsyncApiReaderSettings()
{
- ReferenceResolution = ReferenceResolutionSetting.ResolveReferences,
+ ReferenceResolution = ReferenceResolutionSetting.ResolveInternalReferences,
};
var reader = new AsyncApiStringReader(settings);
@@ -187,7 +191,7 @@ public void AsyncApiDocument_WithInternalComponentReference_ResolvesReference()
}
[Test]
- public void AsyncApiDocument_WithExternalReference_DoesNotResolve()
+ public void AsyncApiDocument_WithNoConfiguredExternalReferenceReader_ThrowsError()
{
// Arrange
var actual = """
@@ -202,7 +206,38 @@ public void AsyncApiDocument_WithExternalReference_DoesNotResolve()
var settings = new AsyncApiReaderSettings()
{
- ReferenceResolution = ReferenceResolutionSetting.ResolveReferences,
+ ReferenceResolution = ReferenceResolutionSetting.ResolveAllReferences,
+ };
+ var reader = new AsyncApiStringReader(settings);
+
+ // Act
+ reader.Read(actual, out var diagnostic);
+
+ // Assert
+ diagnostic.Errors.Count.Should().Be(1);
+ var error = diagnostic.Errors.First();
+ error.Message.Should()
+ .Be(
+ "External reference configured in AsyncApi document but no implementation provided for ExternalReferenceReader.");
+ }
+
+ [Test]
+ public void AsyncApiDocument_WithExternalReferenceOnlySetToResolveInternalReferences_DoesNotResolve()
+ {
+ // Arrange
+ var actual = """
+ asyncapi: 2.6.0
+ info:
+ title: My AsyncAPI Document
+ version: 1.0.0
+ channels:
+ myChannel:
+ $ref: http://example.com/channel.json
+ """;
+
+ var settings = new AsyncApiReaderSettings()
+ {
+ ReferenceResolution = ReferenceResolutionSetting.ResolveInternalReferences,
};
var reader = new AsyncApiStringReader(settings);
@@ -237,7 +272,8 @@ public void AsyncApiReference_WithExternalReference_AllowsReferenceDoesNotResolv
// Assert
diagnostic.Errors.Should().BeEmpty();
- var reference = deserialized.Payload.Reference;
+ var payload = deserialized.Payload.As();
+ var reference = payload.Reference;
reference.ExternalResource.Should().Be("http://example.com/json.json");
reference.Id.Should().BeNull();
reference.IsExternal.Should().BeTrue();
@@ -250,5 +286,82 @@ public void AsyncApiReference_WithExternalReference_AllowsReferenceDoesNotResolv
.Should()
.BePlatformAgnosticEquivalentTo(actual);
}
+
+ [Test]
+ public void AsyncApiReference_WithExternalResourcesInterface_DeserializesCorrectly()
+ {
+ var yaml = """
+ asyncapi: 2.3.0
+ info:
+ title: test
+ version: 1.0.0
+ channels:
+ workspace:
+ publish:
+ message:
+ $ref: "./some/path/to/external/message.yaml"
+ """;
+ var settings = new AsyncApiReaderSettings
+ {
+ ReferenceResolution = ReferenceResolutionSetting.ResolveAllReferences,
+ ExternalReferenceReader = new MockExternalReferenceReader(),
+ };
+ var reader = new AsyncApiStringReader(settings);
+ var doc = reader.Read(yaml, out var diagnostic);
+ var message = doc.Channels["workspace"].Publish.Message.First();
+ message.Name.Should().Be("Test");
+ var payload = message.Payload.As();
+ payload.Properties.Count.Should().Be(3);
+ }
+ }
+
+ public class MockExternalReferenceReader : IAsyncApiExternalReferenceReader
+ {
+ public string Load(string reference)
+ {
+ if (reference == "./some/path/to/external/message.yaml")
+ {
+ return """
+ name: Test
+ title: Test message
+ summary: Test.
+ schemaFormat: application/schema+yaml;version=draft-07
+ contentType: application/cloudevents+json
+ payload:
+ $ref: "./some/path/to/schema.yaml"
+ """;
+ }
+
+ return """
+ type: object
+ properties:
+ orderId:
+ description: The ID of the order.
+ type: string
+ format: uuid
+ name:
+ description: Name of order.
+ type: string
+ orderDetails:
+ description: User details.
+ type: object
+ properties:
+ userId:
+ description: User Id.
+ type: string
+ format: uuid
+ userName:
+ description: User name.
+ type: string
+ required:
+ - orderId
+ example:
+ orderId: 8f9189f8-653b-4849-a1ec-c838c030bd67
+ handler: SomeName
+ orderDetails:
+ userId: Admin
+ userName: Admin
+ """;
+ }
}
}
\ No newline at end of file
diff --git a/test/LEGO.AsyncAPI.Tests/Models/AsyncApiSchema_Should.cs b/test/LEGO.AsyncAPI.Tests/Models/AsyncApiSchema_Should.cs
index 830a44bc..53fc0350 100644
--- a/test/LEGO.AsyncAPI.Tests/Models/AsyncApiSchema_Should.cs
+++ b/test/LEGO.AsyncAPI.Tests/Models/AsyncApiSchema_Should.cs
@@ -4,7 +4,6 @@ namespace LEGO.AsyncAPI.Tests.Models
{
using System;
using System.Collections.Generic;
- using System.Globalization;
using System.IO;
using FluentAssertions;
using LEGO.AsyncAPI.Models;
@@ -421,7 +420,7 @@ public void Serialize_WithInliningOptions_ShouldInlineAccordingly(bool shouldInl
{
new AsyncApiMessage
{
- Payload = new AsyncApiSchema
+ Payload = new AsyncApiJsonSchemaPayload
{
Type = SchemaType.Object,
Required = new HashSet { "testB" },
@@ -510,6 +509,28 @@ public void Serialize_WithAnyOf_DoesNotWriteIf()
Assert.True(!yaml.Contains("if:"));
}
+ [Test]
+ public void Deserialize_BasicExample()
+ {
+ var input =
+ """
+ title: title1
+ type: integer
+ maximum: 42
+ minimum: 10
+ exclusiveMinimum: true
+ multipleOf: 3
+ default: 15
+ nullable: true
+ externalDocs:
+ url: http://example.com/externalDocs
+ """;
+
+ var schema = new AsyncApiStringReader().ReadFragment(input, AsyncApiVersion.AsyncApi2_0, out var diag);
+
+ diag.Errors.Should().BeEmpty();
+ schema.Should().BeEquivalentTo(AdvancedSchemaNumber);
+ }
///
/// Regression test.
/// Bug: Serializing properties multiple times - specifically Schema.Not was serialized into Not and Else.
diff --git a/test/LEGO.AsyncAPI.Tests/Models/AvroSchema_Should.cs b/test/LEGO.AsyncAPI.Tests/Models/AvroSchema_Should.cs
new file mode 100644
index 00000000..2b5b5b0e
--- /dev/null
+++ b/test/LEGO.AsyncAPI.Tests/Models/AvroSchema_Should.cs
@@ -0,0 +1,388 @@
+// Copyright (c) The LEGO Group. All rights reserved.
+
+namespace LEGO.AsyncAPI.Tests.Models
+{
+ using System.Collections.Generic;
+ using FluentAssertions;
+ using LEGO.AsyncAPI.Models;
+ using LEGO.AsyncAPI.Readers;
+ using NUnit.Framework;
+
+ public class AvroSchema_Should
+ {
+ [Test]
+ public void Deserialize_WithMetadata_CreatesMetadata()
+ {
+ var input =
+ """
+ {
+ "type": "record",
+ "name": "SomeEvent",
+ "namespace": "my.namspace.for.event",
+ "fields": [
+ {
+ "name": "countryCode",
+ "type": "string",
+ "doc": "Country of the partner, (e.g. DE)"
+ },
+ {
+ "name": "occurredOn",
+ "type": "string",
+ "doc": "Timestamp of when action occurred."
+ },
+ {
+ "name": "partnerId",
+ "type": "string",
+ "doc": "Id of the partner"
+ },
+ {
+ "name": "platformSource",
+ "type": "string",
+ "doc": "Platform source"
+ }
+ ],
+ "example": {
+ "occurredOn": "2023-11-03T09:56.582+00:00",
+ "partnerId": "1",
+ "platformSource": "Brecht",
+ "countryCode": "DE"
+ }
+ }
+ """;
+ var model = new AsyncApiStringReader().ReadFragment(input, AsyncApiVersion.AsyncApi2_0, out var diag);
+ model.Metadata.Should().HaveCount(1);
+ var reserialized = model.SerializeAsJson(AsyncApiVersion.AsyncApi2_0);
+
+ // Assert
+ input.Should()
+ .BePlatformAgnosticEquivalentTo(reserialized);
+
+ }
+
+ [Test]
+ public void SerializeV2_SerializesCorrectly()
+ {
+ // Arrange
+ var expected = """
+ type: record
+ name: User
+ namespace: com.example
+ fields:
+ - name: username
+ type: string
+ doc: The username of the user.
+ default: guest
+ order: ascending
+ - name: status
+ type:
+ type: enum
+ name: Status
+ symbols:
+ - ACTIVE
+ - INACTIVE
+ - BANNED
+ doc: The status of the user.
+ - name: emails
+ type:
+ type: array
+ items: string
+ doc: A list of email addresses.
+ - name: metadata
+ type:
+ type: map
+ values: string
+ doc: Metadata associated with the user.
+ - name: address
+ type:
+ type: record
+ name: Address
+ fields:
+ - name: street
+ type: string
+ - name: city
+ type: string
+ - name: zipcode
+ type: string
+ doc: The address of the user.
+ - name: profilePicture
+ type:
+ type: fixed
+ name: ProfilePicture
+ size: 256
+ doc: A fixed-size profile picture.
+ - name: contact
+ type:
+ - 'null'
+ - type: record
+ name: PhoneNumber
+ fields:
+ - name: countryCode
+ type: int
+ - name: number
+ type: string
+ doc: 'The contact information of the user, which can be either null or a phone number.'
+ """;
+
+ var schema = new AvroRecord
+ {
+ Name = "User",
+ Namespace = "com.example",
+ Fields = new List
+ {
+ new AvroField
+ {
+ Name = "username",
+ Type = AvroPrimitiveType.String,
+ Doc = "The username of the user.",
+ Default = new AsyncApiAny("guest"),
+ Order = AvroFieldOrder.Ascending,
+ },
+ new AvroField
+ {
+ Name = "status",
+ Type = new AvroEnum
+ {
+ Name = "Status",
+ Symbols = new List { "ACTIVE", "INACTIVE", "BANNED" },
+ },
+ Doc = "The status of the user.",
+ },
+ new AvroField
+ {
+ Name = "emails",
+ Type = new AvroArray
+ {
+ Items = AvroPrimitiveType.String,
+ },
+ Doc = "A list of email addresses.",
+ },
+ new AvroField
+ {
+ Name = "metadata",
+ Type = new AvroMap
+ {
+ Values = AvroPrimitiveType.String,
+ },
+ Doc = "Metadata associated with the user.",
+ },
+ new AvroField
+ {
+ Name = "address",
+ Type = new AvroRecord
+ {
+ Name = "Address",
+ Fields = new List
+ {
+ new AvroField { Name = "street", Type = AvroPrimitiveType.String },
+ new AvroField { Name = "city", Type = AvroPrimitiveType.String },
+ new AvroField { Name = "zipcode", Type = AvroPrimitiveType.String },
+ },
+ },
+ Doc = "The address of the user.",
+ },
+ new AvroField
+ {
+ Name = "profilePicture",
+ Type = new AvroFixed
+ {
+ Name = "ProfilePicture",
+ Size = 256,
+ },
+ Doc = "A fixed-size profile picture.",
+ },
+ new AvroField
+ {
+ Name = "contact",
+ Type = new AvroUnion
+ {
+ Types = new List
+ {
+ AvroPrimitiveType.Null,
+ new AvroRecord
+ {
+ Name = "PhoneNumber",
+ Fields = new List
+ {
+ new AvroField { Name = "countryCode", Type = AvroPrimitiveType.Int },
+ new AvroField { Name = "number", Type = AvroPrimitiveType.String },
+ },
+ },
+ },
+ },
+ Doc = "The contact information of the user, which can be either null or a phone number.",
+ },
+ },
+ };
+
+ // Act
+ var actual = schema.SerializeAsYaml(AsyncApiVersion.AsyncApi2_0);
+
+ // Assert
+ actual.Should()
+ .BePlatformAgnosticEquivalentTo(expected);
+ }
+
+ [Test]
+ public void ReadFragment_DeserializesCorrectly()
+ {
+ // Arrange
+ var input = """
+ type: record
+ name: User
+ namespace: com.example
+ fields:
+ - name: username
+ type: string
+ doc: The username of the user.
+ default: guest
+ order: ascending
+ - name: status
+ type:
+ type: enum
+ name: Status
+ symbols:
+ - ACTIVE
+ - INACTIVE
+ - BANNED
+ doc: The status of the user.
+ - name: emails
+ type:
+ type: array
+ items: string
+ doc: A list of email addresses.
+ - name: metadata
+ type:
+ type: map
+ values: string
+ doc: Metadata associated with the user.
+ - name: address
+ type:
+ type: record
+ name: Address
+ fields:
+ - name: street
+ type: string
+ - name: city
+ type: string
+ - name: zipcode
+ type: string
+ doc: The address of the user.
+ - name: profilePicture
+ type:
+ type: fixed
+ name: ProfilePicture
+ size: 256
+ doc: A fixed-size profile picture.
+ - name: contact
+ type:
+ - 'null'
+ - type: record
+ name: PhoneNumber
+ fields:
+ - name: countryCode
+ type: int
+ - name: number
+ type: string
+ doc: 'The contact information of the user, which can be either null or a phone number.'
+ """;
+
+ var expected = new AvroRecord
+ {
+ Name = "User",
+ Namespace = "com.example",
+ Fields = new List
+ {
+ new AvroField
+ {
+ Name = "username",
+ Type = AvroPrimitiveType.String,
+ Doc = "The username of the user.",
+ Default = new AsyncApiAny("guest"),
+ Order = AvroFieldOrder.Ascending,
+ },
+ new AvroField
+ {
+ Name = "status",
+ Type = new AvroEnum
+ {
+ Name = "Status",
+ Symbols = new List { "ACTIVE", "INACTIVE", "BANNED" },
+ },
+ Doc = "The status of the user.",
+ },
+ new AvroField
+ {
+ Name = "emails",
+ Type = new AvroArray
+ {
+ Items = AvroPrimitiveType.String,
+ },
+ Doc = "A list of email addresses.",
+ },
+ new AvroField
+ {
+ Name = "metadata",
+ Type = new AvroMap
+ {
+ Values = AvroPrimitiveType.String,
+ },
+ Doc = "Metadata associated with the user.",
+ },
+ new AvroField
+ {
+ Name = "address",
+ Type = new AvroRecord
+ {
+ Name = "Address",
+ Fields = new List
+ {
+ new AvroField { Name = "street", Type = AvroPrimitiveType.String },
+ new AvroField { Name = "city", Type = AvroPrimitiveType.String },
+ new AvroField { Name = "zipcode", Type = AvroPrimitiveType.String },
+ },
+ },
+ Doc = "The address of the user.",
+ },
+ new AvroField
+ {
+ Name = "profilePicture",
+ Type = new AvroFixed
+ {
+ Name = "ProfilePicture",
+ Size = 256,
+ },
+ Doc = "A fixed-size profile picture.",
+ },
+ new AvroField
+ {
+ Name = "contact",
+ Type = new AvroUnion
+ {
+ Types = new List
+ {
+ AvroPrimitiveType.Null,
+ new AvroRecord
+ {
+ Name = "PhoneNumber",
+ Fields = new List
+ {
+ new AvroField { Name = "countryCode", Type = AvroPrimitiveType.Int },
+ new AvroField { Name = "number", Type = AvroPrimitiveType.String },
+ },
+ },
+ },
+ },
+ Doc = "The contact information of the user, which can be either null or a phone number.",
+ },
+ },
+ };
+
+ // Act
+ var actual = new AsyncApiStringReader().ReadFragment(input, AsyncApiVersion.AsyncApi2_0, out var diagnostic);
+
+ // Assert
+ actual.Should()
+ .BeEquivalentTo(expected);
+ }
+ }
+}
diff --git a/test/LEGO.AsyncAPI.Tests/Serialization/AsyncApiYamlWriterTests.cs b/test/LEGO.AsyncAPI.Tests/Serialization/AsyncApiYamlWriterTests.cs
index c364d345..69ae67e3 100644
--- a/test/LEGO.AsyncAPI.Tests/Serialization/AsyncApiYamlWriterTests.cs
+++ b/test/LEGO.AsyncAPI.Tests/Serialization/AsyncApiYamlWriterTests.cs
@@ -2,10 +2,9 @@
namespace LEGO.AsyncAPI.Tests.Writers
{
+ using System.IO;
using LEGO.AsyncAPI.Writers;
using NUnit.Framework;
- using System;
- using System.IO;
internal class AsyncApiYamlWriterTests : TestBase
{
diff --git a/test/LEGO.AsyncAPI.Tests/TestBase.cs b/test/LEGO.AsyncAPI.Tests/TestBase.cs
index 17309617..50e313ed 100644
--- a/test/LEGO.AsyncAPI.Tests/TestBase.cs
+++ b/test/LEGO.AsyncAPI.Tests/TestBase.cs
@@ -43,9 +43,9 @@ public void Log(string message)
/// Attempts to find the first file that matches the name of the active unit test
/// and returns it as an expected type.
///
- /// The type to return
+ /// The type to return.
/// The name of the resource file with an optional extension.
- /// The result
+ /// The result.
protected T GetTestData([CallerMemberName] string resourceName = "")
{
string searchPattern = string.IsNullOrWhiteSpace(Path.GetExtension(resourceName))