Skip to content

Commit 9d07ebb

Browse files
committed
fix: enum parsing when encountering unknown values should not default to first member
1 parent 1394da7 commit 9d07ebb

17 files changed

+153
-77
lines changed

src/Microsoft.OpenApi/Extensions/StringExtensions.cs

+33-19
Original file line numberDiff line numberDiff line change
@@ -2,47 +2,61 @@
22
// Licensed under the MIT license.
33
using System;
44
using System.Collections.Concurrent;
5+
using System.Collections.Generic;
6+
using System.Collections.ObjectModel;
57
using System.Diagnostics.CodeAnalysis;
68
using System.Reflection;
79
using Microsoft.OpenApi.Attributes;
10+
using Microsoft.OpenApi.Models;
11+
using Microsoft.OpenApi.Reader;
812

913
namespace Microsoft.OpenApi.Extensions
1014
{
1115
/// <summary>
1216
/// String extension methods.
1317
/// </summary>
14-
public static class StringExtensions
18+
internal static class StringExtensions
1519
{
16-
private static readonly ConcurrentDictionary<Type, ConcurrentDictionary<string, object>> EnumDisplayCache = new();
20+
private static readonly ConcurrentDictionary<Type, ReadOnlyDictionary<string, object>> EnumDisplayCache = new();
1721

18-
/// <summary>
19-
/// Gets the enum value based on the given enum type and display name.
20-
/// </summary>
21-
/// <param name="displayName">The display name.</param>
22-
public static T GetEnumFromDisplayName<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] T>(this string displayName)
22+
internal static bool TryGetEnumFromDisplayName<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] T>(this string displayName, ParsingContext parsingContext, out T result) where T : Enum
23+
{
24+
if (TryGetEnumFromDisplayName(displayName, out result))
25+
{
26+
return true;
27+
}
28+
29+
parsingContext.Diagnostic.Errors.Add(new OpenApiError(parsingContext.GetLocation(), $"Enum value {displayName} is not recognized."));
30+
return false;
31+
32+
}
33+
internal static bool TryGetEnumFromDisplayName<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] T>(this string displayName, out T result) where T : Enum
2334
{
2435
var type = typeof(T);
25-
if (!type.IsEnum)
26-
return default;
2736

28-
var displayMap = EnumDisplayCache.GetOrAdd(type, _ => new ConcurrentDictionary<string, object>(StringComparer.OrdinalIgnoreCase));
37+
var displayMap = EnumDisplayCache.GetOrAdd(type, GetEnumValues<T>);
2938

3039
if (displayMap.TryGetValue(displayName, out var cachedValue))
31-
return (T)cachedValue;
32-
40+
{
41+
result = (T)cachedValue;
42+
return true;
43+
}
3344

34-
foreach (var field in type.GetFields(BindingFlags.Public | BindingFlags.Static))
45+
result = default;
46+
return false;
47+
}
48+
private static ReadOnlyDictionary<string, object> GetEnumValues<T>(Type enumType) where T : Enum
49+
{
50+
var result = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
51+
foreach (var field in enumType.GetFields(BindingFlags.Public | BindingFlags.Static))
3552
{
36-
var displayAttribute = field.GetCustomAttribute<DisplayAttribute>();
37-
if (displayAttribute != null && displayAttribute.Name.Equals(displayName, StringComparison.OrdinalIgnoreCase))
53+
if (field.GetCustomAttribute<DisplayAttribute>() is {} displayAttribute)
3854
{
3955
var enumValue = (T)field.GetValue(null);
40-
displayMap.TryAdd(displayName, enumValue);
41-
return enumValue;
56+
result.Add(displayAttribute.Name, enumValue);
4257
}
4358
}
44-
45-
return default;
59+
return new ReadOnlyDictionary<string, object>(result);
4660
}
4761
internal static string ToFirstCharacterLowerCase(this string input)
4862
=> string.IsNullOrEmpty(input) ? string.Empty : char.ToLowerInvariant(input[0]) + input.Substring(1);

src/Microsoft.OpenApi/Models/OpenApiSecurityScheme.cs

+5-5
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ public class OpenApiSecurityScheme : IOpenApiReferenceable, IOpenApiExtensible
1717
/// <summary>
1818
/// REQUIRED. The type of the security scheme. Valid values are "apiKey", "http", "oauth2", "openIdConnect".
1919
/// </summary>
20-
public virtual SecuritySchemeType Type { get; set; }
20+
public virtual SecuritySchemeType? Type { get; set; }
2121

2222
/// <summary>
2323
/// A short description for security scheme. CommonMark syntax MAY be used for rich text representation.
@@ -32,7 +32,7 @@ public class OpenApiSecurityScheme : IOpenApiReferenceable, IOpenApiExtensible
3232
/// <summary>
3333
/// REQUIRED. The location of the API key. Valid values are "query", "header" or "cookie".
3434
/// </summary>
35-
public virtual ParameterLocation In { get; set; }
35+
public virtual ParameterLocation? In { get; set; }
3636

3737
/// <summary>
3838
/// REQUIRED. The name of the HTTP Authorization scheme to be used
@@ -82,10 +82,10 @@ public OpenApiSecurityScheme() { }
8282
/// </summary>
8383
public OpenApiSecurityScheme(OpenApiSecurityScheme securityScheme)
8484
{
85-
Type = securityScheme?.Type ?? Type;
85+
Type = securityScheme?.Type;
8686
Description = securityScheme?.Description ?? Description;
8787
Name = securityScheme?.Name ?? Name;
88-
In = securityScheme?.In ?? In;
88+
In = securityScheme?.In;
8989
Scheme = securityScheme?.Scheme ?? Scheme;
9090
BearerFormat = securityScheme?.BearerFormat ?? BearerFormat;
9191
Flows = securityScheme?.Flows != null ? new(securityScheme?.Flows) : null;
@@ -111,7 +111,7 @@ public virtual void SerializeAsV3(IOpenApiWriter writer)
111111
SerializeInternal(writer, OpenApiSpecVersion.OpenApi3_0, (writer, element) => element.SerializeAsV3(writer));
112112
}
113113

114-
internal virtual void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion version,
114+
internal virtual void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion version,
115115
Action<IOpenApiWriter, IOpenApiSerializable> callback)
116116
{
117117
Utils.CheckArgumentNull(writer);

src/Microsoft.OpenApi/Models/References/OpenApiSecuritySchemeReference.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ public override string Description
7171
public override string Name { get => Target.Name; set => Target.Name = value; }
7272

7373
/// <inheritdoc/>
74-
public override ParameterLocation In { get => Target.In; set => Target.In = value; }
74+
public override ParameterLocation? In { get => Target.In; set => Target.In = value; }
7575

7676
/// <inheritdoc/>
7777
public override string Scheme { get => Target.Scheme; set => Target.Scheme = value; }
@@ -89,7 +89,7 @@ public override string Description
8989
public override IDictionary<string, IOpenApiExtension> Extensions { get => Target.Extensions; set => Target.Extensions = value; }
9090

9191
/// <inheritdoc/>
92-
public override SecuritySchemeType Type { get => Target.Type; set => Target.Type = value; }
92+
public override SecuritySchemeType? Type { get => Target.Type; set => Target.Type = value; }
9393

9494
/// <inheritdoc/>
9595
public override void SerializeAsV3(IOpenApiWriter writer)

src/Microsoft.OpenApi/Reader/V2/OpenApiParameterDeserializer.cs

+2-1
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,8 @@ private static void ProcessIn(OpenApiParameter o, ParseNode n, OpenApiDocument h
172172
case "query":
173173
case "header":
174174
case "path":
175-
o.In = value.GetEnumFromDisplayName<ParameterLocation>();
175+
value.TryGetEnumFromDisplayName<ParameterLocation>(out var _in);
176+
o.In = _in;
176177
break;
177178
default:
178179
o.In = null;

src/Microsoft.OpenApi/Reader/V2/OpenApiSecuritySchemeDeserializer.cs

+13-1
Original file line numberDiff line numberDiff line change
@@ -40,12 +40,24 @@ internal static partial class OpenApiV2Deserializer
4040
case "oauth2":
4141
o.Type = SecuritySchemeType.OAuth2;
4242
break;
43+
44+
default:
45+
n.Context.Diagnostic.Errors.Add(new OpenApiError(n.Context.GetLocation(), $"Security scheme type {type} is not recognized."));
46+
break;
4347
}
4448
}
4549
},
4650
{"description", (o, n, _) => o.Description = n.GetScalarValue()},
4751
{"name", (o, n, _) => o.Name = n.GetScalarValue()},
48-
{"in", (o, n, _) => o.In = n.GetScalarValue().GetEnumFromDisplayName<ParameterLocation>()},
52+
{"in", (o, n, _) =>
53+
{
54+
if (!n.GetScalarValue().TryGetEnumFromDisplayName<ParameterLocation>(n.Context, out var _in))
55+
{
56+
return;
57+
}
58+
o.In = _in;
59+
}
60+
},
4961
{
5062
"flow", (_, n, _) => _flowValue = n.GetScalarValue()
5163
},

src/Microsoft.OpenApi/Reader/V3/OpenApiEncodingDeserializer.cs

+8-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,14 @@ internal static partial class OpenApiV3Deserializer
2525
},
2626
{
2727
"style",
28-
(o, n, _) => o.Style = n.GetScalarValue().GetEnumFromDisplayName<ParameterStyle>()
28+
(o, n, _) =>
29+
{
30+
if(!n.GetScalarValue().TryGetEnumFromDisplayName<ParameterStyle>(n.Context, out var style))
31+
{
32+
return;
33+
}
34+
o.Style = style;
35+
}
2936
},
3037
{
3138
"explode",

src/Microsoft.OpenApi/Reader/V3/OpenApiHeaderDeserializer.cs

+8-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,14 @@ internal static partial class OpenApiV3Deserializer
3939
},
4040
{
4141
"style",
42-
(o, n, _) => o.Style = n.GetScalarValue().GetEnumFromDisplayName<ParameterStyle>()
42+
(o, n, _) =>
43+
{
44+
if(!n.GetScalarValue().TryGetEnumFromDisplayName<ParameterStyle>(n.Context, out var style))
45+
{
46+
return;
47+
}
48+
o.Style = style;
49+
}
4350
},
4451
{
4552
"explode",

src/Microsoft.OpenApi/Reader/V3/OpenApiParameterDeserializer.cs

+13-6
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,11 @@ internal static partial class OpenApiV3Deserializer
2626
{
2727
"in", (o, n, _) =>
2828
{
29-
var inString = n.GetScalarValue();
30-
31-
o.In = Enum.GetValues(typeof(ParameterLocation)).Cast<ParameterLocation>()
32-
.Select( e => e.GetDisplayName() )
33-
.Contains(inString) ? n.GetScalarValue().GetEnumFromDisplayName<ParameterLocation>() : null;
29+
if (!n.GetScalarValue().TryGetEnumFromDisplayName<ParameterLocation>(n.Context, out var _in))
30+
{
31+
return;
32+
}
33+
o.In = _in;
3434
}
3535
},
3636
{
@@ -55,7 +55,14 @@ internal static partial class OpenApiV3Deserializer
5555
},
5656
{
5757
"style",
58-
(o, n, _) => o.Style = n.GetScalarValue().GetEnumFromDisplayName<ParameterStyle>()
58+
(o, n, _) =>
59+
{
60+
if (!n.GetScalarValue().TryGetEnumFromDisplayName<ParameterStyle>(n.Context, out var style))
61+
{
62+
return;
63+
}
64+
o.Style = style;
65+
}
5966
},
6067
{
6168
"explode",

src/Microsoft.OpenApi/Reader/V3/OpenApiSecuritySchemeDeserializer.cs

+16-3
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
// Licensed under the MIT license.
33

44
using System;
5-
using System.Linq;
65
using Microsoft.OpenApi.Extensions;
76
using Microsoft.OpenApi.Models;
87
using Microsoft.OpenApi.Models.References;
@@ -21,7 +20,14 @@ internal static partial class OpenApiV3Deserializer
2120
{
2221
{
2322
"type",
24-
(o, n, _) => o.Type = n.GetScalarValue().GetEnumFromDisplayName<SecuritySchemeType>()
23+
(o, n, _) =>
24+
{
25+
if (!n.GetScalarValue().TryGetEnumFromDisplayName<SecuritySchemeType>(n.Context, out var type))
26+
{
27+
return;
28+
}
29+
o.Type = type;
30+
}
2531
},
2632
{
2733
"description",
@@ -33,7 +39,14 @@ internal static partial class OpenApiV3Deserializer
3339
},
3440
{
3541
"in",
36-
(o, n, _) => o.In = n.GetScalarValue().GetEnumFromDisplayName<ParameterLocation>()
42+
(o, n, _) =>
43+
{
44+
if(!n.GetScalarValue().TryGetEnumFromDisplayName<ParameterLocation>(n.Context, out var _in))
45+
{
46+
return;
47+
}
48+
o.In = _in;
49+
}
3750
},
3851
{
3952
"scheme",

src/Microsoft.OpenApi/Reader/V3/OpenApiV3VersionService.cs

+13-16
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ public OpenApiReference ConvertToOpenApiReference(
123123
if (id.StartsWith("/components/"))
124124
{
125125
var localSegments = segments[1].Split('/');
126-
var referencedType = localSegments[2].GetEnumFromDisplayName<ReferenceType>();
126+
localSegments[2].TryGetEnumFromDisplayName<ReferenceType>(out var referencedType);
127127
if (type == null)
128128
{
129129
type = referencedType;
@@ -200,25 +200,22 @@ private OpenApiReference ParseLocalReference(string localReference)
200200

201201
var segments = localReference.Split('/');
202202

203-
if (segments.Length == 4) // /components/{type}/pet
203+
if (segments.Length == 4 && segments[1] == "components") // /components/{type}/pet
204204
{
205-
if (segments[1] == "components")
205+
segments[2].TryGetEnumFromDisplayName<ReferenceType>(out var referenceType);
206+
var refId = segments[3];
207+
if (segments[2] == "pathItems")
206208
{
207-
var referenceType = segments[2].GetEnumFromDisplayName<ReferenceType>();
208-
var refId = segments[3];
209-
if (segments[2] == "pathItems")
210-
{
211-
refId = "/" + segments[3];
212-
};
209+
refId = "/" + segments[3];
210+
}
213211

214-
var parsedReference = new OpenApiReference
215-
{
216-
Type = referenceType,
217-
Id = refId
218-
};
212+
var parsedReference = new OpenApiReference
213+
{
214+
Type = referenceType,
215+
Id = refId
216+
};
219217

220-
return parsedReference;
221-
}
218+
return parsedReference;
222219
}
223220

224221
throw new OpenApiException(string.Format(SRResource.ReferenceHasInvalidFormat, localReference));

src/Microsoft.OpenApi/Reader/V31/OpenApiEncodingDeserializer.cs

+5-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,11 @@ internal static partial class OpenApiV31Deserializer
2727
{
2828
"style", (o, n, _) =>
2929
{
30-
o.Style = n.GetScalarValue().GetEnumFromDisplayName<ParameterStyle>();
30+
if(!n.GetScalarValue().TryGetEnumFromDisplayName<ParameterStyle>(n.Context, out var style))
31+
{
32+
return;
33+
}
34+
o.Style = style;
3135
}
3236
},
3337
{

src/Microsoft.OpenApi/Reader/V31/OpenApiHeaderDeserializer.cs

+5-1
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,11 @@ internal static partial class OpenApiV31Deserializer
4747
{
4848
"style", (o, n, _) =>
4949
{
50-
o.Style = n.GetScalarValue().GetEnumFromDisplayName<ParameterStyle>();
50+
if(!n.GetScalarValue().TryGetEnumFromDisplayName<ParameterStyle>(n.Context, out var style))
51+
{
52+
return;
53+
}
54+
o.Style = style;
5155
}
5256
},
5357
{

src/Microsoft.OpenApi/Reader/V31/OpenApiParameterDeserializer.cs

+10-6
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,11 @@ internal static partial class OpenApiV31Deserializer
2525
{
2626
"in", (o, n, _) =>
2727
{
28-
var inString = n.GetScalarValue();
29-
o.In = Enum.GetValues(typeof(ParameterLocation)).Cast<ParameterLocation>()
30-
.Select( e => e.GetDisplayName() )
31-
.Contains(inString) ? n.GetScalarValue().GetEnumFromDisplayName<ParameterLocation>() : null;
32-
28+
if (!n.GetScalarValue().TryGetEnumFromDisplayName<ParameterLocation>(n.Context, out var _in))
29+
{
30+
return;
31+
}
32+
o.In = _in;
3333
}
3434
},
3535
{
@@ -65,7 +65,11 @@ internal static partial class OpenApiV31Deserializer
6565
{
6666
"style", (o, n, _) =>
6767
{
68-
o.Style = n.GetScalarValue().GetEnumFromDisplayName<ParameterStyle>();
68+
if (!n.GetScalarValue().TryGetEnumFromDisplayName<ParameterStyle>(n.Context, out var style))
69+
{
70+
return;
71+
}
72+
o.Style = style;
6973
}
7074
},
7175
{

0 commit comments

Comments
 (0)