Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/ByteBard.AsyncAPI.Readers/AsyncApiJsonDocumentReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -410,6 +410,11 @@ private IAsyncApiSerializable ResolveStreamReference(Stream stream, IAsyncApiRef
result = this.ReadFragment<AsyncApiAvroSchema>(json, diagnostic.SpecificationVersion, out fragmentDiagnostic);
}

if (reference is AsyncApiMultiFormatSchemaReference)
{
result = this.ReadFragment<AsyncApiMultiFormatSchema>(json, diagnostic.SpecificationVersion, out fragmentDiagnostic);
}

break;

case ReferenceType.Server:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ public AsyncApiV2VersionService(AsyncApiDiagnostic diagnostic)
[typeof(AsyncApiAvroSchema)] = AsyncApiAvroSchemaDeserializer.LoadSchema,
[typeof(AsyncApiJsonSchema)] = AsyncApiV2Deserializer.LoadJsonSchemaPayload,
[typeof(AsyncApiAvroSchema)] = AsyncApiV2Deserializer.LoadAvroPayload,
// 2.x has no Multi Format Schema Object. Only the local reference fallback asks for one, and it reads the document after the upgrade to 3.0.
[typeof(AsyncApiMultiFormatSchema)] = AsyncApiV3Deserializer.LoadMultiFormatSchema,
[typeof(AsyncApiSecurityScheme)] = AsyncApiV2Deserializer.LoadSecurityScheme,
[typeof(AsyncApiServer)] = AsyncApiV2Deserializer.LoadServer,
[typeof(AsyncApiServerVariable)] = AsyncApiV2Deserializer.LoadServerVariable,
Expand Down
191 changes: 190 additions & 1 deletion test/ByteBard.AsyncAPI.Tests/Models/AsyncApiReference_Should.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,19 @@ public class AsyncApiReference_Should : TestBase
}
""";

private const string ExternalPayloads =
"""
{
"schemas": {
"plain": { "type": "string" },
"multiFormat": {
"schemaFormat": "application/vnd.aai.asyncapi+json;version=3.0.0",
"schema": { "type": "integer" }
}
}
}
""";

private static readonly TestCaseData[] ExternalFragmentCases =
{
new TestCaseData("/components/schemas/plain", SchemaType.String).SetArgDisplayNames("nested path"),
Expand Down Expand Up @@ -138,6 +151,182 @@ private static AsyncApiDocument ReadWithExternalSchemas(string yaml, out AsyncAp
return new AsyncApiStringReader(settings).Read(yaml, out diagnostic);
}

[TestCase("payloads.json#/schemas/plain", SchemaType.String)]
[TestCase("payloads.json#/schemas/multiFormat", SchemaType.Integer)]
public void V3_ExternalPayloadReference_ResolvesPayload(string reference, SchemaType expectedType)
{
var yaml =
$"""
asyncapi: 3.0.0
info:
title: test
version: 1.0.0
channels:
workspace:
messages:
message:
payload:
$ref: '{reference}'
""";

var settings = new AsyncApiReaderSettings
{
ReferenceResolution = ReferenceResolutionSetting.ResolveAllReferences,
ExternalReferenceLoader = new MockStringLoader(ExternalPayloads),
};

var doc = new AsyncApiStringReader(settings).Read(yaml, out var diagnostic);

diagnostic.Errors.Should().BeEmpty();
var payload = doc.Channels["workspace"].Messages["message"].Payload;
payload.Should().BeOfType<AsyncApiMultiFormatSchemaReference>();
payload.As<AsyncApiMultiFormatSchemaReference>().UnresolvedReference.Should().BeFalse();
payload.Schema.As<AsyncApiJsonSchema>().Type.Should().Be(expectedType);
doc.SerializeAsYaml(AsyncApiVersion.AsyncApi3_0).Should().Contain($"$ref: {reference}");
}

[Test]
public void V3_ExternalPayloadReference_WithoutFragment_ResolvesPayload()
{
var yaml =
"""
asyncapi: 3.0.0
info:
title: test
version: 1.0.0
channels:
workspace:
messages:
message:
payload:
$ref: './payload.json'
""";

var settings = new AsyncApiReaderSettings
{
ReferenceResolution = ReferenceResolutionSetting.ResolveAllReferences,
ExternalReferenceLoader = new MockStringLoader("""{ "type": "boolean" }"""),
};

var doc = new AsyncApiStringReader(settings).Read(yaml, out var diagnostic);

diagnostic.Errors.Should().BeEmpty();
doc.Channels["workspace"].Messages["message"].Payload.Schema.As<AsyncApiJsonSchema>().Type.Should().Be(SchemaType.Boolean);
}

// A local payload reference that is not a registered component is resolved by falling back to the whole document.
[TestCase("2.6.0")]
[TestCase("3.0.0")]
public void LocalPayloadReference_NotInWorkspace_ResolvesPayload(string version)
{
var yaml = version.StartsWith("2")
? """
asyncapi: 2.6.0
info:
title: test
version: 1.0.0
channels:
workspace:
publish:
message:
payload:
$ref: '#/components/messages/shared/payload'
components:
messages:
shared:
payload:
type: string
"""
: """
asyncapi: 3.0.0
info:
title: test
version: 1.0.0
channels:
workspace:
messages:
message:
payload:
$ref: '#/components/messages/shared/payload'
components:
messages:
shared:
payload:
type: string
""";

var doc = new AsyncApiStringReader().Read(yaml, out var diagnostic);

diagnostic.Errors.Should().BeEmpty();
var payload = doc.Channels["workspace"].Messages.Values.First().Payload;
payload.Should().BeOfType<AsyncApiMultiFormatSchemaReference>();
payload.Schema.As<AsyncApiJsonSchema>().Type.Should().Be(SchemaType.String);
}

// A local Avro payload reference that is not a registered component is resolved by falling back to the whole document.
[TestCase("2.6.0")]
[TestCase("3.0.0")]
public void LocalAvroPayloadReference_NotInWorkspace_ResolvesPayload(string version)
{
var yaml = version.StartsWith("2")
? """
asyncapi: 2.6.0
info:
title: test
version: 1.0.0
channels:
workspace:
publish:
message:
schemaFormat: application/vnd.apache.avro;version=1.9.0
payload:
$ref: '#/components/messages/shared/payload'
components:
messages:
shared:
schemaFormat: application/vnd.apache.avro;version=1.9.0
payload:
type: record
name: User
fields:
- name: name
type: string
"""
: """
asyncapi: 3.0.0
info:
title: test
version: 1.0.0
channels:
workspace:
messages:
message:
payload:
$ref: '#/components/messages/shared/payload'
components:
messages:
shared:
payload:
schemaFormat: application/vnd.apache.avro;version=1.9.0
schema:
type: record
name: User
fields:
- name: name
type: string
""";

var doc = new AsyncApiStringReader().Read(yaml, out var diagnostic);

diagnostic.Errors.Should().BeEmpty();
var payload = doc.Channels["workspace"].Messages.Values.First().Payload;
payload.Should().BeOfType<AsyncApiMultiFormatSchemaReference>();
payload.SchemaFormat.Should().StartWith("application/vnd.apache.avro");
var record = payload.Schema.As<AsyncApiAvroSchema>().As<AvroRecord>();
record.Name.Should().Be("User");
record.Fields.Should().ContainSingle().Which.Name.Should().Be("name");
}

[Test]
public void V2_ReferencePointers()
{
Expand Down Expand Up @@ -815,4 +1004,4 @@ public Task<Stream> LoadAsync(Uri baseUri, Uri uri)
return Task.FromResult(this.Load(baseUri, uri));
}
}
}
}
Loading