Skip to content

Commit e220edb

Browse files
committed
feat(parameter): support parameter value parsing for openapi 3.0 and 3.1
1 parent bdbcbb7 commit e220edb

4 files changed

Lines changed: 85 additions & 53 deletions

File tree

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,11 @@ https://www.nuget.org/packages/WebApiGenerator.OpenAPI
3434
```
3535
<ItemGroup>
3636
<PackageReference Include="Corvus.Json.ExtendedTypes" Version="4.3.13" />
37-
<PackageReference Include="ParameterStyleParsers.OpenAPI" Version="1.1.0" />
37+
<PackageReference Include="ParameterStyleParsers.OpenAPI" Version="1.4.0" />
3838
</ItemGroup>
3939
```
4040
* Corvus.Json.ExtendedTypes >= 4.0.0
41-
* ParameterStyleParsers.OpenAPI >= 1.1.0
41+
* ParameterStyleParsers.OpenAPI >= 1.4.0
4242

4343
4. Compile the project.
4444

src/OpenAPI.WebApiGenerator/ApiGenerator.cs

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,9 @@ private static void GenerateCode(SourceProductionContext context, (
8383
var openApiReference = new OpenApiReference<OpenApiDocument>(openApi, openApiDocument, openApiUri);
8484
var openApiVisitor = OpenApiVisitor.V(openApiVersion, openApiReference);
8585

86-
var httpRequestExtensionsGenerator = new HttpRequestExtensionsGenerator(rootNamespace);
86+
var httpRequestExtensionsGenerator = new HttpRequestExtensionsGenerator(
87+
openApiVersion,
88+
rootNamespace);
8789
httpRequestExtensionsGenerator.GenerateHttpRequestExtensionsClass().AddTo(context);
8890

8991
var httpResponseExtensionsGenerator = new HttpResponseExtensionsGenerator(rootNamespace);
@@ -106,8 +108,10 @@ private static void GenerateCode(SourceProductionContext context, (
106108
{
107109
var schemaReference = openApiPathVisitor.GetSchemaReference(parameter);
108110
var typeDeclaration = schemaGenerator.Generate(schemaReference);
109-
pathParameterGenerators[$"{parameter.GetName()}_{parameter.GetLocation()}"] = new ParameterGenerator(typeDeclaration, parameter,
110-
httpRequestExtensionsGenerator);
111+
pathParameterGenerators[$"{parameter.GetName()}_{parameter.GetLocation()}"] =
112+
new ParameterGenerator(typeDeclaration,
113+
parameter,
114+
httpRequestExtensionsGenerator);
111115
}
112116

113117
foreach (var openApiOperation in path.Value.GetOperations())
@@ -124,8 +128,10 @@ private static void GenerateCode(SourceProductionContext context, (
124128
{
125129
var schemaReference = openApiOperationVisitor.GetSchemaReference(parameter);
126130
var typeDeclaration = schemaGenerator.Generate(schemaReference);
127-
operationParameterGenerators[$"{parameter.GetName()}_{parameter.GetLocation()}"] = new ParameterGenerator(typeDeclaration, parameter,
128-
httpRequestExtensionsGenerator);
131+
operationParameterGenerators[$"{parameter.GetName()}_{parameter.GetLocation()}"] =
132+
new ParameterGenerator(typeDeclaration,
133+
parameter,
134+
httpRequestExtensionsGenerator);
129135
}
130136

131137
var body = operation.RequestBody;

src/OpenAPI.WebApiGenerator/CodeGeneration/HttpRequestExtensionsGenerator.cs

Lines changed: 71 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,21 @@
1-
namespace OpenAPI.WebApiGenerator.CodeGeneration;
1+
using System;
2+
using Microsoft.OpenApi;
3+
4+
namespace OpenAPI.WebApiGenerator.CodeGeneration;
25

36
internal sealed class HttpRequestExtensionsGenerator(
7+
OpenApiSpecVersion openApiVersion,
48
string @namespace)
59
{
610
private const string HttpRequestExtensionsClassName = "HttpRequestExtensions";
11+
12+
private readonly string _openApiVersion = openApiVersion switch
13+
{
14+
OpenApiSpecVersion.OpenApi2_0 => "2.0",
15+
OpenApiSpecVersion.OpenApi3_0 => "3.0",
16+
OpenApiSpecVersion.OpenApi3_1 => "3.1",
17+
_ => throw new ArgumentOutOfRangeException(nameof(openApiVersion), openApiVersion, "Unknown OpenAPI version")
18+
};
719

820
internal string CreateBindParameterInvocation(
921
string requestVariableName,
@@ -14,6 +26,7 @@ internal string CreateBindParameterInvocation(
1426
$""""
1527
{@namespace}.{HttpRequestExtensionsClassName}.Bind<{bindingTypeName}>(
1628
{requestVariableName},
29+
"{_openApiVersion}",
1730
"""
1831
{parameterSpecificationAsJson}
1932
""")
@@ -37,37 +50,39 @@ internal SourceCode GenerateHttpRequestExtensionsClass() =>
3750
$$$""""
3851
#nullable enable
3952
using System.Collections.Concurrent;
53+
using System.Diagnostics.CodeAnalysis;
4054
using System.Text.Json;
4155
using Corvus.Json;
4256
using Microsoft.AspNetCore.Http;
4357
using Microsoft.Extensions.Primitives;
44-
using OpenAPI.ParameterStyleParsers.OpenApi20;
45-
using OpenAPI.ParameterStyleParsers.OpenApi20.ParameterParsers;
58+
using OpenAPI.ParameterStyleParsers;
4659
4760
namespace {{{@namespace}}};
4861
4962
internal static class {{{HttpRequestExtensionsClassName}}}
5063
{
51-
private static readonly ConcurrentDictionary<Parameter, ParameterValueParser> ParserCache = new();
64+
private static readonly ConcurrentDictionary<IParameter, IParameterValueParser> ParserCache = new();
65+
private static IParameterValueParser GetParser(IParameter parameter) => ParserCache.GetOrAdd(parameter, _ => parameter.CreateParameterValueParser());
5266
5367
/// <summary>
5468
/// Binds an http parameter to a json type
5569
/// </summary>
5670
/// <param name="request"></param>
57-
/// <param name="parameterSpecificationAsJson"></param>
58-
/// <typeparam name="T"></typeparam>
59-
/// <returns></returns>
71+
/// <param name="openApiVersion">OpenAPI Version of the specification</param>
72+
/// <param name="parameterSpecificationAsJson">OpenAPI parameter specification formatted as json</param>
73+
/// <typeparam name="T">The type to bind</typeparam>
74+
/// <returns>The bound instance</returns>
6075
/// <exception cref="BadHttpRequestException"></exception>
6176
internal static T Bind<T>(this HttpRequest request,
77+
string openApiVersion,
6278
string parameterSpecificationAsJson)
6379
where T : struct, IJsonValue<T>
6480
{
65-
var parameter = Parameter.FromOpenApi20ParameterSpecification(parameterSpecificationAsJson);
81+
var parameter = ParameterFactory.OpenApi(openApiVersion, parameterSpecificationAsJson);
6682
return parameter switch
6783
{
6884
_ when parameter.InBody => T.Parse(request.BodyReader.AsStream()),
69-
_ when TryGetValue(request, parameter, out var stringValue) =>
70-
Parse<T>(parameter, stringValue),
85+
_ when TryParse<T>(request, parameter, out var value) => value.Value,
7186
_ => T.Undefined
7287
};
7388
}
@@ -82,79 +97,90 @@ internal static async Task<T> BindBodyAsync<T>(this HttpRequest request,
8297
return T.FromJson(document.RootElement.Clone());
8398
}
8499
85-
86-
private static T Parse<T>(Parameter parameter, string? stringValue)
87-
where T : struct, IJsonValue<T>
88-
{
89-
var parser = ParserCache.GetOrAdd(parameter, ParameterValueParser.Create);
90-
if (!parser.TryParse(stringValue, out var instance, out var error))
91-
{
92-
throw new BadHttpRequestException(error);
93-
}
94-
95-
return instance == null ? T.Null : T.Parse(instance.ToJsonString());
96-
}
97-
98-
private static bool TryGetValue(this HttpRequest request, Parameter parameter, out string? stringValue) =>
100+
private static bool TryParse<T>(this HttpRequest request, IParameter parameter, [NotNullWhen(true)] out T? value)
101+
where T : struct, IJsonValue<T> =>
99102
parameter switch
100103
{
101-
_ when parameter.InHeader => TryGetHeaderValue(request.Headers, parameter, out stringValue),
102-
_ when parameter.InFormData => TryGetFormDataValue(request.Form, parameter, out stringValue),
103-
_ when parameter.InPath => TryGetPathValue(request.RouteValues, parameter, out stringValue),
104-
_ when parameter.InQuery => TryGetQueryValue(request.Query, parameter, out stringValue),
104+
_ when parameter.InHeader => TryParseHeader<T>(request.Headers, parameter, out value),
105+
_ when parameter.InFormData => TryParseForm<T>(request.Form, parameter, out value),
106+
_ when parameter.InPath => TryParsePath<T>(request.RouteValues, parameter, out value),
107+
_ when parameter.InQuery => TryParseQuery<T>(request.Query, parameter, out value),
105108
_ => throw new InvalidOperationException($"Parameter {parameter.Name} has an unknown location")
106109
};
107110
108-
private static bool TryGetQueryValue(IQueryCollection query, Parameter parameter, out string? stringValue)
111+
private static bool TryParseQuery<T>(IQueryCollection query, IParameter parameter, [NotNullWhen(true)] out T? value)
112+
where T : struct, IJsonValue<T>
109113
{
110-
stringValue = null;
114+
value = null;
111115
return query.TryGetValue(parameter.Name, out var values) &&
112-
TryGetValue(values, parameter, out stringValue);
116+
TryParse<T>(values, parameter, out value);
113117
}
114118
115-
private static bool TryGetPathValue(RouteValueDictionary requestPath, Parameter parameter, out string? stringValue)
119+
private static bool TryParsePath<T>(RouteValueDictionary requestPath, IParameter parameter, [NotNullWhen(true)] out T? value)
120+
where T : struct, IJsonValue<T>
116121
{
117-
if (!requestPath.TryGetValue(parameter.Name, out var value))
122+
if (!requestPath.TryGetValue(parameter.Name, out var objValue))
118123
{
119-
stringValue = null;
124+
value = default;
120125
return false;
121126
}
122127
123-
stringValue = value switch
128+
var stringValue = objValue switch
124129
{
125130
null => null,
126131
string strValue => strValue,
127132
_ => throw new InvalidOperationException(
128-
$"Route value of '{value}' with type '{value.GetType()}' is not supported")
133+
$"Route value of '{objValue}' with type '{objValue.GetType()}' is not supported")
129134
};
135+
136+
var parser = GetParser(parameter);
137+
value = Parse<T>(parser, stringValue);
130138
return true;
131139
}
132140
133-
private static bool TryGetFormDataValue(IFormCollection requestForm, Parameter parameter, out string? stringValue)
141+
private static bool TryParseForm<T>(IFormCollection requestForm, IParameter parameter, [NotNullWhen(true)] out T? value)
142+
where T : struct, IJsonValue<T>
134143
{
135-
stringValue = null;
136-
return requestForm.TryGetValue(parameter.Name, out var values) && TryGetValue(values, parameter, out stringValue);
144+
value = default;
145+
return requestForm.TryGetValue(parameter.Name, out var values) && TryParse<T>(values, parameter, out value);
137146
}
138147
139-
private static bool TryGetHeaderValue(IHeaderDictionary headers, Parameter parameter, out string? stringValue)
148+
private static bool TryParseHeader<T>(IHeaderDictionary headers, IParameter parameter, [NotNullWhen(true)] out T? value)
149+
where T : struct, IJsonValue<T>
140150
{
141-
stringValue = null;
151+
value = default;
142152
return headers.TryGetValue(parameter.Name, out var values) &&
143-
TryGetValue(values, parameter, out stringValue);
153+
TryParse<T>(values, parameter, out value);
144154
}
145155
146-
private static bool TryGetValue(StringValues values, Parameter parameter, out string? stringValue)
156+
private static bool TryParse<T>(StringValues values, IParameter parameter, [NotNullWhen(true)] out T? value)
157+
where T : struct, IJsonValue<T>
147158
{
148159
if (values.Count == 0)
149160
{
150-
stringValue = null;
161+
value = default;
151162
return false;
152163
}
153-
stringValue = parameter.ValueIncludesKey
164+
165+
var parser = GetParser(parameter);
166+
var stringValue = parser.ValueIncludesParameterName
154167
? string.Join('&', values.Select(value => $"{parameter.Name}=${value}"))
155168
: values.Single();
169+
170+
value = Parse<T>(parser, stringValue);
156171
return true;
157172
}
173+
174+
private static T Parse<T>(IParameterValueParser parser, string? value)
175+
where T : struct, IJsonValue<T>
176+
{
177+
if (!parser.TryParse(value, out var instance, out var error))
178+
{
179+
throw new BadHttpRequestException(error);
180+
}
181+
182+
return instance == null ? T.Null : T.Parse(instance.ToJsonString());
183+
}
158184
}
159185
#nullable restore
160186
"""");

tests/Example.Api/Example.Api.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
<ItemGroup>
1010
<PackageReference Include="Corvus.Json.ExtendedTypes" Version="4.4.2" />
11-
<PackageReference Include="ParameterStyleParsers.OpenAPI" Version="1.1.1" />
11+
<PackageReference Include="ParameterStyleParsers.OpenAPI" Version="1.4.0" />
1212
</ItemGroup>
1313

1414
<ItemGroup>

0 commit comments

Comments
 (0)