Skip to content

Commit a7dee44

Browse files
bieluclaude
andcommitted
fix: report unregistered bindings as warnings and stop flagging binding extensions
Specification extensions (x-*) placed on a bindings object were reported as "<Kind>Binding x-... is not found" errors, and in V2 they were dropped as well. Binding keys without a registered parser were reported as errors even though the document is valid. The eight V2/V3 binding loaders now share BindingDeserializer.LoadBindings, which: - skips extension keys and reads them as extensions (V2 and V3) - adds a warning instead of an error when no parser is registered for a key - no longer adds a second "not found" error when a parser fails Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 70b3885 commit a7dee44

10 files changed

Lines changed: 226 additions & 327 deletions

‎src/ByteBard.AsyncAPI.Readers/BindingDeserializer.cs‎

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
namespace ByteBard.AsyncAPI.Readers
22
{
3+
using System.Collections.Generic;
4+
using System.Linq;
5+
using ByteBard.AsyncAPI.Exceptions;
36
using ByteBard.AsyncAPI.Extensions;
7+
using ByteBard.AsyncAPI.Models;
48
using ByteBard.AsyncAPI.Models.Interfaces;
9+
using ByteBard.AsyncAPI.Readers.Interface;
510
using ByteBard.AsyncAPI.Readers.ParseNodes;
611

712
public class BindingDeserializer
@@ -17,6 +22,49 @@ public static T LoadBinding<T>(string nodeName, ParseNode node, FixedFieldMap<T>
1722
return binding;
1823
}
1924

25+
internal static AsyncApiBindings<TBinding> LoadBindings<TBinding>(
26+
string bindingType,
27+
MapNode mapNode,
28+
IDictionary<string, IBindingParser<TBinding>> parsers,
29+
PatternFieldMap<AsyncApiBindings<TBinding>> patternFields)
30+
where TBinding : IBinding
31+
{
32+
var context = mapNode.Context;
33+
var bindings = new AsyncApiBindings<TBinding>();
34+
foreach (var property in mapNode)
35+
{
36+
if (patternFields.Keys.Any(isMatch => isMatch(property.Name)))
37+
{
38+
continue;
39+
}
40+
41+
if (!parsers.TryGetValue(property.Name, out var parser))
42+
{
43+
context.Diagnostic.Warnings.Add(
44+
new AsyncApiError(context.GetLocation(), $"{bindingType} '{property.Name}' was ignored because no binding parser is registered for it."));
45+
continue;
46+
}
47+
48+
try
49+
{
50+
var binding = parser.LoadBinding(property);
51+
if (binding != null)
52+
{
53+
bindings.Add(binding);
54+
}
55+
}
56+
catch (AsyncApiException ex)
57+
{
58+
ex.Pointer = context.GetLocation();
59+
context.Diagnostic.Errors.Add(new AsyncApiError(ex));
60+
}
61+
}
62+
63+
mapNode.ParseFields(bindings, patternFields);
64+
65+
return bindings;
66+
}
67+
2068
private static PatternFieldMap<T> BindingPatternExtensionFields<T>()
2169
where T : IBinding, new()
2270
{
Lines changed: 8 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,18 @@
11
namespace ByteBard.AsyncAPI.Readers
22
{
3-
using ByteBard.AsyncAPI.Exceptions;
3+
using ByteBard.AsyncAPI.Extensions;
44
using ByteBard.AsyncAPI.Models;
55
using ByteBard.AsyncAPI.Models.Interfaces;
66
using ByteBard.AsyncAPI.Readers.ParseNodes;
77

88
internal static partial class AsyncApiV2Deserializer
99
{
10+
private static readonly PatternFieldMap<AsyncApiBindings<IChannelBinding>> channelBindingPatternFields =
11+
new()
12+
{
13+
{ s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, LoadExtension(p, n)) },
14+
};
15+
1016
internal static AsyncApiBindings<IChannelBinding> LoadChannelBindings(ParseNode node)
1117
{
1218
var mapNode = node.CheckMapNode("channelBindings");
@@ -16,42 +22,7 @@ internal static AsyncApiBindings<IChannelBinding> LoadChannelBindings(ParseNode
1622
return new AsyncApiBindingsReference<IChannelBinding>(pointer);
1723
}
1824

19-
var channelBindings = new AsyncApiBindings<IChannelBinding>();
20-
foreach (var property in mapNode)
21-
{
22-
var channelBinding = LoadChannelBinding(property);
23-
24-
if (channelBinding != null)
25-
{
26-
channelBindings.Add(channelBinding);
27-
}
28-
else
29-
{
30-
mapNode.Context.Diagnostic.Errors.Add(
31-
new AsyncApiError(node.Context.GetLocation(), $"ChannelBinding {property.Name} is not found"));
32-
}
33-
}
34-
35-
return channelBindings;
36-
}
37-
38-
private static IChannelBinding LoadChannelBinding(ParseNode node)
39-
{
40-
var property = node as PropertyNode;
41-
try
42-
{
43-
if (node.Context.ChannelBindingParsers.TryGetValue(property.Name, out var parser))
44-
{
45-
return parser.LoadBinding(property);
46-
}
47-
}
48-
catch (AsyncApiException ex)
49-
{
50-
ex.Pointer = node.Context.GetLocation();
51-
node.Context.Diagnostic.Errors.Add(new AsyncApiError(ex));
52-
}
53-
54-
return null;
25+
return BindingDeserializer.LoadBindings("ChannelBinding", mapNode, node.Context.ChannelBindingParsers, channelBindingPatternFields);
5526
}
5627
}
5728
}
Lines changed: 8 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,18 @@
11
namespace ByteBard.AsyncAPI.Readers
22
{
3-
using ByteBard.AsyncAPI.Exceptions;
3+
using ByteBard.AsyncAPI.Extensions;
44
using ByteBard.AsyncAPI.Models;
55
using ByteBard.AsyncAPI.Models.Interfaces;
66
using ByteBard.AsyncAPI.Readers.ParseNodes;
77

88
internal static partial class AsyncApiV2Deserializer
99
{
10+
private static readonly PatternFieldMap<AsyncApiBindings<IMessageBinding>> messageBindingPatternFields =
11+
new()
12+
{
13+
{ s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, LoadExtension(p, n)) },
14+
};
15+
1016
internal static AsyncApiBindings<IMessageBinding> LoadMessageBindings(ParseNode node)
1117
{
1218
var mapNode = node.CheckMapNode("messageBindings");
@@ -16,42 +22,7 @@ internal static AsyncApiBindings<IMessageBinding> LoadMessageBindings(ParseNode
1622
return new AsyncApiBindingsReference<IMessageBinding>(pointer);
1723
}
1824

19-
var messageBindings = new AsyncApiBindings<IMessageBinding>();
20-
foreach (var property in mapNode)
21-
{
22-
var messageBinding = LoadMessageBinding(property);
23-
24-
if (messageBinding != null)
25-
{
26-
messageBindings.Add(messageBinding);
27-
}
28-
else
29-
{
30-
mapNode.Context.Diagnostic.Errors.Add(
31-
new AsyncApiError(node.Context.GetLocation(), $"MessageBinding {property.Name} is not found"));
32-
}
33-
}
34-
35-
return messageBindings;
36-
}
37-
38-
internal static IMessageBinding LoadMessageBinding(ParseNode node)
39-
{
40-
var property = node as PropertyNode;
41-
try
42-
{
43-
if (node.Context.MessageBindingParsers.TryGetValue(property.Name, out var parser))
44-
{
45-
return parser.LoadBinding(property);
46-
}
47-
}
48-
catch (AsyncApiException ex)
49-
{
50-
ex.Pointer = node.Context.GetLocation();
51-
node.Context.Diagnostic.Errors.Add(new AsyncApiError(ex));
52-
}
53-
54-
return null;
25+
return BindingDeserializer.LoadBindings("MessageBinding", mapNode, node.Context.MessageBindingParsers, messageBindingPatternFields);
5526
}
5627
}
5728
}
Lines changed: 8 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,18 @@
11
namespace ByteBard.AsyncAPI.Readers
22
{
3-
using ByteBard.AsyncAPI.Exceptions;
3+
using ByteBard.AsyncAPI.Extensions;
44
using ByteBard.AsyncAPI.Models;
55
using ByteBard.AsyncAPI.Models.Interfaces;
66
using ByteBard.AsyncAPI.Readers.ParseNodes;
77

88
internal static partial class AsyncApiV2Deserializer
99
{
10+
private static readonly PatternFieldMap<AsyncApiBindings<IOperationBinding>> operationBindingPatternFields =
11+
new()
12+
{
13+
{ s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, LoadExtension(p, n)) },
14+
};
15+
1016
internal static AsyncApiBindings<IOperationBinding> LoadOperationBindings(ParseNode node)
1117
{
1218
var mapNode = node.CheckMapNode("operationBindings");
@@ -16,42 +22,7 @@ internal static AsyncApiBindings<IOperationBinding> LoadOperationBindings(ParseN
1622
return new AsyncApiBindingsReference<IOperationBinding>(pointer);
1723
}
1824

19-
var operationBindings = new AsyncApiBindings<IOperationBinding>();
20-
foreach (var property in mapNode)
21-
{
22-
var operationBinding = LoadOperationBinding(property);
23-
24-
if (operationBinding != null)
25-
{
26-
operationBindings.Add(operationBinding);
27-
}
28-
else
29-
{
30-
mapNode.Context.Diagnostic.Errors.Add(
31-
new AsyncApiError(node.Context.GetLocation(), $"OperationBinding {property.Name} is not found"));
32-
}
33-
}
34-
35-
return operationBindings;
36-
}
37-
38-
internal static IOperationBinding LoadOperationBinding(ParseNode node)
39-
{
40-
var property = node as PropertyNode;
41-
try
42-
{
43-
if (node.Context.OperationBindingParsers.TryGetValue(property.Name, out var parser))
44-
{
45-
return parser.LoadBinding(property);
46-
}
47-
}
48-
catch (AsyncApiException ex)
49-
{
50-
ex.Pointer = node.Context.GetLocation();
51-
node.Context.Diagnostic.Errors.Add(new AsyncApiError(ex));
52-
}
53-
54-
return null;
25+
return BindingDeserializer.LoadBindings("OperationBinding", mapNode, node.Context.OperationBindingParsers, operationBindingPatternFields);
5526
}
5627
}
5728
}
Lines changed: 8 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,18 @@
11
namespace ByteBard.AsyncAPI.Readers
22
{
3-
using ByteBard.AsyncAPI.Exceptions;
3+
using ByteBard.AsyncAPI.Extensions;
44
using ByteBard.AsyncAPI.Models;
55
using ByteBard.AsyncAPI.Models.Interfaces;
66
using ByteBard.AsyncAPI.Readers.ParseNodes;
77

88
internal static partial class AsyncApiV2Deserializer
99
{
10+
private static readonly PatternFieldMap<AsyncApiBindings<IServerBinding>> serverBindingPatternFields =
11+
new()
12+
{
13+
{ s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, LoadExtension(p, n)) },
14+
};
15+
1016
internal static AsyncApiBindings<IServerBinding> LoadServerBindings(ParseNode node)
1117
{
1218
var mapNode = node.CheckMapNode("serverBindings");
@@ -16,42 +22,7 @@ internal static AsyncApiBindings<IServerBinding> LoadServerBindings(ParseNode no
1622
return new AsyncApiBindingsReference<IServerBinding>(pointer);
1723
}
1824

19-
var serverBindings = new AsyncApiBindings<IServerBinding>();
20-
foreach (var property in mapNode)
21-
{
22-
var serverBinding = LoadServerBinding(property);
23-
24-
if (serverBinding != null)
25-
{
26-
serverBindings.Add(serverBinding);
27-
}
28-
else
29-
{
30-
mapNode.Context.Diagnostic.Errors.Add(
31-
new AsyncApiError(node.Context.GetLocation(), $"ServerBinding {property.Name} is not found"));
32-
}
33-
}
34-
35-
return serverBindings;
36-
}
37-
38-
internal static IServerBinding LoadServerBinding(ParseNode node)
39-
{
40-
var property = node as PropertyNode;
41-
try
42-
{
43-
if (node.Context.ServerBindingParsers.TryGetValue(property.Name, out var parser))
44-
{
45-
return parser.LoadBinding(property);
46-
}
47-
}
48-
catch (AsyncApiException ex)
49-
{
50-
ex.Pointer = node.Context.GetLocation();
51-
node.Context.Diagnostic.Errors.Add(new AsyncApiError(ex));
52-
}
53-
54-
return null;
25+
return BindingDeserializer.LoadBindings("ServerBinding", mapNode, node.Context.ServerBindingParsers, serverBindingPatternFields);
5526
}
5627
}
5728
}
Lines changed: 7 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,18 @@
11
namespace ByteBard.AsyncAPI.Readers
22
{
3-
using ByteBard.AsyncAPI.Exceptions;
43
using ByteBard.AsyncAPI.Extensions;
54
using ByteBard.AsyncAPI.Models;
65
using ByteBard.AsyncAPI.Models.Interfaces;
76
using ByteBard.AsyncAPI.Readers.ParseNodes;
87

98
internal static partial class AsyncApiV3Deserializer
109
{
10+
private static readonly PatternFieldMap<AsyncApiBindings<IChannelBinding>> channelBindingPatternFields =
11+
new()
12+
{
13+
{ s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, LoadExtension(p, n)) },
14+
};
15+
1116
internal static AsyncApiBindings<IChannelBinding> LoadChannelBindings(ParseNode node)
1217
{
1318
var mapNode = node.CheckMapNode("channelBindings");
@@ -17,50 +22,7 @@ internal static AsyncApiBindings<IChannelBinding> LoadChannelBindings(ParseNode
1722
return new AsyncApiBindingsReference<IChannelBinding>(pointer);
1823
}
1924

20-
var channelBindings = new AsyncApiBindings<IChannelBinding>();
21-
foreach (var property in mapNode)
22-
{
23-
var channelBinding = LoadChannelBinding(property);
24-
25-
if (channelBinding != null)
26-
{
27-
channelBindings.Add(channelBinding);
28-
}
29-
else
30-
{
31-
mapNode.Context.Diagnostic.Errors.Add(
32-
new AsyncApiError(node.Context.GetLocation(), $"ChannelBinding {property.Name} is not found"));
33-
}
34-
}
35-
36-
mapNode.ParseFields(channelBindings, channelBindingPatternFields);
37-
38-
return channelBindings;
39-
}
40-
41-
private static readonly PatternFieldMap<AsyncApiBindings<IChannelBinding>> channelBindingPatternFields =
42-
new()
43-
{
44-
{ s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, LoadExtension(p, n)) },
45-
};
46-
47-
private static IChannelBinding LoadChannelBinding(ParseNode node)
48-
{
49-
var property = node as PropertyNode;
50-
try
51-
{
52-
if (node.Context.ChannelBindingParsers.TryGetValue(property.Name, out var parser))
53-
{
54-
return parser.LoadBinding(property);
55-
}
56-
}
57-
catch (AsyncApiException ex)
58-
{
59-
ex.Pointer = node.Context.GetLocation();
60-
node.Context.Diagnostic.Errors.Add(new AsyncApiError(ex));
61-
}
62-
63-
return null;
25+
return BindingDeserializer.LoadBindings("ChannelBinding", mapNode, node.Context.ChannelBindingParsers, channelBindingPatternFields);
6426
}
6527
}
6628
}

0 commit comments

Comments
 (0)