Skip to content

Commit 3edac3e

Browse files
committed
feat: unmapped member handling during deserialization (#176)
1 parent d4fe243 commit 3edac3e

File tree

5 files changed

+128
-5
lines changed

5 files changed

+128
-5
lines changed

src/LEGO.AsyncAPI.Readers/AsyncApiReaderSettings.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,12 @@ public class AsyncApiReaderSettings : AsyncApiSettings
3939
public ReferenceResolutionSetting ReferenceResolution { get; set; } =
4040
ReferenceResolutionSetting.ResolveInternalReferences;
4141

42+
/// <summary>
43+
/// Indicates what should happen when unmapped members are encountered during deserialization.
44+
/// Error and Warning will add an error or warning to the diagnostics object.
45+
/// </summary>
46+
public UnmappedMemberHandling UnmappedMemberHandling { get; set; } = UnmappedMemberHandling.Error;
47+
4248
/// <summary>
4349
/// Dictionary of parsers for converting extensions into strongly typed classes.
4450
/// </summary>

src/LEGO.AsyncAPI.Readers/ParseNodes/PropertyNode.cs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,7 @@ public void ParseField<T>(
2929
IDictionary<string, Action<T, ParseNode>> fixedFields,
3030
IDictionary<Func<string, bool>, Action<T, string, ParseNode>> patternFields)
3131
{
32-
var found = fixedFields.TryGetValue(this.Name, out var fixedFieldMap);
33-
32+
var _ = fixedFields.TryGetValue(this.Name, out var fixedFieldMap);
3433
if (fixedFieldMap != null)
3534
{
3635
try
@@ -78,8 +77,12 @@ public void ParseField<T>(
7877
}
7978
else
8079
{
81-
this.Context.Diagnostic.Errors.Add(
82-
new AsyncApiError(string.Empty, $"{this.Name} is not a valid property at {this.Context.GetLocation()}"));
80+
switch (this.Context.Settings.UnmappedMemberHandling)
81+
{
82+
case UnmappedMemberHandling.Error:
83+
this.Context.Diagnostic.Errors.Add(new AsyncApiError(string.Empty, $"{this.Name} is not a valid property at {this.Context.GetLocation()}"));
84+
break;
85+
}
8386
}
8487
}
8588
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// Copyright (c) The LEGO Group. All rights reserved.
2+
3+
namespace LEGO.AsyncAPI.Readers
4+
{
5+
/// <summary>
6+
/// Unmapped member handling.
7+
/// </summary>
8+
public enum UnmappedMemberHandling
9+
{
10+
/// <summary>
11+
/// Add error to diagnostics for unmapped members.
12+
/// </summary>
13+
Error,
14+
15+
/// <summary>
16+
/// Ignore unmapped members.
17+
/// </summary>
18+
Ignore,
19+
}
20+
}

src/LEGO.AsyncAPI.Readers/V2/AsyncApiAvroSchemaDeserializer.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace LEGO.AsyncAPI.Readers
44
{
55
using System;
6+
using LEGO.AsyncAPI.Exceptions;
67
using LEGO.AsyncAPI.Models;
78
using LEGO.AsyncAPI.Readers.Exceptions;
89
using LEGO.AsyncAPI.Readers.ParseNodes;
@@ -169,7 +170,7 @@ public static AvroSchema LoadSchema(ParseNode node)
169170
mapNode.ParseFields(ref union, UnionFixedFields, UnionMetadataPatternFields);
170171
return union;
171172
default:
172-
throw new InvalidOperationException($"Unsupported type: {type}");
173+
throw new AsyncApiException($"Unsupported type: {type}");
173174
}
174175
}
175176

test/LEGO.AsyncAPI.Tests/AsyncApiReaderTests.cs

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ namespace LEGO.AsyncAPI.Tests
55
using System;
66
using System.Collections.Generic;
77
using System.Linq;
8+
using FluentAssertions;
89
using LEGO.AsyncAPI.Exceptions;
910
using LEGO.AsyncAPI.Models;
1011
using LEGO.AsyncAPI.Models.Interfaces;
@@ -30,6 +31,7 @@ public void Read_WithExtensionParser_Parses()
3031
info:
3132
title: test
3233
version: 1.0.0
34+
test: 1234
3335
contact:
3436
name: API Support
3537
url: https://www.example.com/support
@@ -57,13 +59,104 @@ public void Read_WithExtensionParser_Parses()
5759
{
5860
{ extensionName, valueExtensionParser },
5961
},
62+
UnmappedMemberHandling = UnmappedMemberHandling.Ignore,
6063
};
6164

6265
var reader = new AsyncApiStringReader(settings);
6366
var doc = reader.Read(yaml, out var diagnostic);
6467
Assert.AreEqual((doc.Channels["workspace"].Extensions[extensionName] as AsyncApiAny).GetValue<int>(), 1234);
6568
}
6669

70+
[Test]
71+
public void Read_WithUnmappedMemberHandlingError_AddsError()
72+
{
73+
var extensionName = "x-someValue";
74+
var yaml = $"""
75+
asyncapi: 2.3.0
76+
info:
77+
title: test
78+
version: 1.0.0
79+
test: 1234
80+
contact:
81+
name: API Support
82+
url: https://www.example.com/support
83+
email: support@example.com
84+
channels:
85+
workspace:
86+
{extensionName}: onetwothreefour
87+
""";
88+
Func<AsyncApiAny, IAsyncApiExtension> valueExtensionParser = (any) =>
89+
{
90+
if (any.TryGetValue<string>(out var value))
91+
{
92+
if (value == "onetwothreefour")
93+
{
94+
return new AsyncApiAny(1234);
95+
}
96+
}
97+
98+
return new AsyncApiAny("No value provided");
99+
};
100+
101+
var settings = new AsyncApiReaderSettings
102+
{
103+
ExtensionParsers = new Dictionary<string, Func<AsyncApiAny, IAsyncApiExtension>>
104+
{
105+
{ extensionName, valueExtensionParser },
106+
},
107+
UnmappedMemberHandling = UnmappedMemberHandling.Error,
108+
};
109+
110+
var reader = new AsyncApiStringReader(settings);
111+
var doc = reader.Read(yaml, out var diagnostic);
112+
diagnostic.Errors.Should().HaveCount(1);
113+
}
114+
115+
[Test]
116+
public void Read_WithUnmappedMemberHandlingIgnore_NoErrors()
117+
{
118+
var extensionName = "x-someValue";
119+
var yaml = $"""
120+
asyncapi: 2.3.0
121+
info:
122+
title: test
123+
version: 1.0.0
124+
test: 1234
125+
contact:
126+
name: API Support
127+
url: https://www.example.com/support
128+
email: support@example.com
129+
channels:
130+
workspace:
131+
{extensionName}: onetwothreefour
132+
""";
133+
Func<AsyncApiAny, IAsyncApiExtension> valueExtensionParser = (any) =>
134+
{
135+
if (any.TryGetValue<string>(out var value))
136+
{
137+
if (value == "onetwothreefour")
138+
{
139+
return new AsyncApiAny(1234);
140+
}
141+
}
142+
143+
return new AsyncApiAny("No value provided");
144+
};
145+
146+
var settings = new AsyncApiReaderSettings
147+
{
148+
ExtensionParsers = new Dictionary<string, Func<AsyncApiAny, IAsyncApiExtension>>
149+
{
150+
{ extensionName, valueExtensionParser },
151+
},
152+
UnmappedMemberHandling = UnmappedMemberHandling.Ignore,
153+
};
154+
155+
var reader = new AsyncApiStringReader(settings);
156+
var doc = reader.Read(yaml, out var diagnostic);
157+
diagnostic.Errors.Should().HaveCount(0);
158+
}
159+
67160
[Test]
68161
public void Read_WithThrowingExtensionParser_AddsToDiagnostics()
69162
{

0 commit comments

Comments
 (0)