Skip to content

Commit f1864c6

Browse files
authored
Merge pull request #329 from Microsoft/perthcharern/AllowParsingNullForParameterLocation
Ensure parsing null in "in" in Parameter does not fail
2 parents e8f55b1 + 772c98e commit f1864c6

26 files changed

+738
-23
lines changed

src/Microsoft.OpenApi.Readers/V2/OpenApiDocumentDeserializer.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -73,11 +73,12 @@ internal static partial class OpenApiV2Deserializer
7373

7474
o.Components.RequestBodies = n.CreateMapWithReference(ReferenceType.RequestBody, p =>
7575
{
76-
var parameter = LoadParameter(p, evenBody: true);
77-
if (parameter.In == null)
76+
var parameter = LoadParameter(p, loadRequestBody: true);
77+
if (parameter != null)
7878
{
79-
return CreateRequestBody(n.Context,parameter);
79+
return CreateRequestBody(n.Context, parameter);
8080
}
81+
8182
return null;
8283
}
8384
);

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

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
using System;
55
using System.Collections.Generic;
66
using System.Globalization;
7-
using Microsoft.OpenApi.Any;
87
using Microsoft.OpenApi.Extensions;
98
using Microsoft.OpenApi.Models;
109
using Microsoft.OpenApi.Readers.ParseNodes;
@@ -17,7 +16,7 @@ namespace Microsoft.OpenApi.Readers.V2
1716
/// </summary>
1817
internal static partial class OpenApiV2Deserializer
1918
{
20-
private static ParameterLocation? _in;
19+
private static bool _isBodyOrFormData;
2120

2221
private static readonly FixedFieldMap<OpenApiParameter> _parameterFixedFields =
2322
new FixedFieldMap<OpenApiParameter>
@@ -205,9 +204,11 @@ private static void ProcessIn(OpenApiParameter o, ParseNode n)
205204
switch (value)
206205
{
207206
case "body":
207+
_isBodyOrFormData = true;
208208
n.Context.SetTempStorage(TempStorageKeys.BodyParameter, o);
209209
break;
210210
case "formData":
211+
_isBodyOrFormData = true;
211212
var formParameters = n.Context.GetFromTempStorage<List<OpenApiParameter>>("formParameters");
212213
if (formParameters == null)
213214
{
@@ -217,9 +218,13 @@ private static void ProcessIn(OpenApiParameter o, ParseNode n)
217218

218219
formParameters.Add(o);
219220
break;
221+
case "query":
222+
case "header":
223+
case "path":
224+
o.In = value.GetEnumFromDisplayName<ParameterLocation>();
225+
break;
220226
default:
221-
_in = value.GetEnumFromDisplayName<ParameterLocation>();
222-
o.In = _in;
227+
o.In = null;
223228
break;
224229
}
225230
}
@@ -229,10 +234,10 @@ public static OpenApiParameter LoadParameter(ParseNode node)
229234
return LoadParameter(node, false);
230235
}
231236

232-
public static OpenApiParameter LoadParameter(ParseNode node, bool evenBody)
237+
public static OpenApiParameter LoadParameter(ParseNode node, bool loadRequestBody)
233238
{
234239
// Reset the local variables every time this method is called.
235-
_in = null;
240+
_isBodyOrFormData = false;
236241

237242
var mapNode = node.CheckMapNode("parameter");
238243

@@ -254,9 +259,14 @@ public static OpenApiParameter LoadParameter(ParseNode node, bool evenBody)
254259
node.Context.SetTempStorage("schema", null);
255260
}
256261

257-
if (_in == null && !evenBody)
262+
if (_isBodyOrFormData && !loadRequestBody)
263+
{
264+
return null; // Don't include Form or Body parameters when normal parameters are loaded.
265+
}
266+
267+
if ( loadRequestBody && !_isBodyOrFormData )
258268
{
259-
return null; // Don't include Form or Body parameters in OpenApiOperation.Parameters list
269+
return null; // Don't include non-Body or non-Form parameters when request bodies are loaded.
260270
}
261271

262272
return parameter;

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

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// Copyright (c) Microsoft Corporation. All rights reserved.
22
// Licensed under the MIT license.
33

4-
using System.Collections.Generic;
4+
using System;
55
using System.Linq;
66
using Microsoft.OpenApi.Extensions;
77
using Microsoft.OpenApi.Models;
@@ -27,7 +27,18 @@ internal static partial class OpenApiV3Deserializer
2727
{
2828
"in", (o, n) =>
2929
{
30-
o.In = n.GetScalarValue().GetEnumFromDisplayName<ParameterLocation>();
30+
var inString = n.GetScalarValue();
31+
32+
if ( Enum.GetValues(typeof(ParameterLocation)).Cast<ParameterLocation>()
33+
.Select( e => e.GetDisplayName() )
34+
.Contains(inString) )
35+
{
36+
o.In = n.GetScalarValue().GetEnumFromDisplayName<ParameterLocation>();
37+
}
38+
else
39+
{
40+
o.In = null;
41+
}
3142
}
3243
},
3344
{
@@ -115,7 +126,6 @@ public static OpenApiParameter LoadParameter(ParseNode node)
115126
}
116127

117128
var parameter = new OpenApiParameter();
118-
var required = new List<string> {"name", "in"};
119129

120130
ParseMap(mapNode, parameter, _parameterFixedFields, _parameterPatternFields);
121131

src/Microsoft.OpenApi/Models/OpenApiParameter.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ public void SerializeAsV3WithoutReference(IOpenApiWriter writer)
159159
writer.WriteProperty(OpenApiConstants.Name, Name);
160160

161161
// in
162-
writer.WriteProperty(OpenApiConstants.In, In.GetDisplayName());
162+
writer.WriteProperty(OpenApiConstants.In, In?.GetDisplayName());
163163

164164
// description
165165
writer.WriteProperty(OpenApiConstants.Description, Description);
@@ -237,7 +237,7 @@ public void SerializeAsV2WithoutReference(IOpenApiWriter writer)
237237
}
238238
else
239239
{
240-
writer.WriteProperty(OpenApiConstants.In, In.GetDisplayName());
240+
writer.WriteProperty(OpenApiConstants.In, In?.GetDisplayName());
241241
}
242242

243243
// name

src/Microsoft.OpenApi/Validations/OpenApiValidator.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,12 @@ public void AddError(OpenApiValidatorError error)
113113
/// <param name="item">The object to be validated</param>
114114
public override void Visit(OpenApiTag item) => Validate(item);
115115

116+
/// <summary>
117+
/// Execute validation rules against an <see cref="OpenApiParameter"/>
118+
/// </summary>
119+
/// <param name="item">The object to be validated</param>
120+
public override void Visit(OpenApiParameter item) => Validate(item);
121+
116122
/// <summary>
117123
/// Execute validation rules against an <see cref="OpenApiSchema"/>
118124
/// </summary>
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT license.
3+
4+
using System;
5+
using Microsoft.OpenApi.Models;
6+
using Microsoft.OpenApi.Properties;
7+
8+
namespace Microsoft.OpenApi.Validations.Rules
9+
{
10+
/// <summary>
11+
/// The validation rules for <see cref="OpenApiParameter"/>.
12+
/// </summary>
13+
[OpenApiRule]
14+
public static class OpenApiParameterRules
15+
{
16+
/// <summary>
17+
/// Validate the field is required.
18+
/// </summary>
19+
public static ValidationRule<OpenApiParameter> ParameterRequiredFields =>
20+
new ValidationRule<OpenApiParameter>(
21+
(context, item) =>
22+
{
23+
// name
24+
context.Enter("name");
25+
if (item.Name == null)
26+
{
27+
context.CreateError(nameof(ParameterRequiredFields),
28+
String.Format(SRResource.Validation_FieldIsRequired, "name", "parameter"));
29+
}
30+
context.Exit();
31+
32+
// in
33+
context.Enter("in");
34+
if (item.In == null)
35+
{
36+
context.CreateError(nameof(ParameterRequiredFields),
37+
String.Format(SRResource.Validation_FieldIsRequired, "in", "parameter"));
38+
}
39+
context.Exit();
40+
});
41+
42+
/// <summary>
43+
/// Validate the "required" field is true when "in" is path.
44+
/// </summary>
45+
public static ValidationRule<OpenApiParameter> RequiredMustBeTrueWhenInIsPath =>
46+
new ValidationRule<OpenApiParameter>(
47+
(context, item) =>
48+
{
49+
// required
50+
context.Enter("required");
51+
if ( item.In == ParameterLocation.Path && !item.Required )
52+
{
53+
context.CreateError(
54+
nameof(RequiredMustBeTrueWhenInIsPath),
55+
"\"required\" must be true when parameter location is \"path\"");
56+
}
57+
58+
context.Exit();
59+
});
60+
61+
// add more rule.
62+
}
63+
}

test/Microsoft.OpenApi.Readers.Tests/Microsoft.OpenApi.Readers.Tests.csproj

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,15 @@
4646
<EmbeddedResource Include="V2Tests\Samples\OpenApiParameter\formDataParameter.yaml">
4747
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
4848
</EmbeddedResource>
49+
<EmbeddedResource Include="V2Tests\Samples\OpenApiParameter\parameterWithNoLocation.yaml">
50+
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
51+
</EmbeddedResource>
52+
<EmbeddedResource Include="V2Tests\Samples\OpenApiParameter\parameterWithUnknownLocation.yaml">
53+
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
54+
</EmbeddedResource>
55+
<EmbeddedResource Include="V2Tests\Samples\OpenApiParameter\parameterWithNullLocation.yaml">
56+
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
57+
</EmbeddedResource>
4958
<EmbeddedResource Include="V2Tests\Samples\OpenApiParameter\headerParameter.yaml">
5059
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
5160
</EmbeddedResource>
@@ -113,7 +122,36 @@
113122
<EmbeddedResource Include="V3Tests\Samples\OpenApiInfo\minimalInfo.yaml">
114123
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
115124
</EmbeddedResource>
116-
<EmbeddedResource Include="V3Tests\Samples\OpenApiOperation\securedOperation.yaml" />
125+
<EmbeddedResource Include="V3Tests\Samples\OpenApiOperation\operationWithParameterWithNoLocation.json">
126+
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
127+
</EmbeddedResource>
128+
<EmbeddedResource Include="V3Tests\Samples\OpenApiOperation\securedOperation.yaml">
129+
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
130+
</EmbeddedResource>
131+
<EmbeddedResource Include="V3Tests\Samples\OpenApiParameter\headerParameter.yaml">
132+
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
133+
</EmbeddedResource>
134+
<EmbeddedResource Include="V3Tests\Samples\OpenApiParameter\parameterWithNoLocation.yaml">
135+
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
136+
</EmbeddedResource>
137+
<EmbeddedResource Include="V3Tests\Samples\OpenApiParameter\parameterWithUnknownLocation.yaml">
138+
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
139+
</EmbeddedResource>
140+
<EmbeddedResource Include="V3Tests\Samples\OpenApiParameter\parameterWithNullLocation.yaml">
141+
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
142+
</EmbeddedResource>
143+
<EmbeddedResource Include="V3Tests\Samples\OpenApiParameter\pathParameter.yaml">
144+
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
145+
</EmbeddedResource>
146+
<EmbeddedResource Include="V3Tests\Samples\OpenApiParameter\queryParameterWithObjectTypeAndContent.yaml">
147+
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
148+
</EmbeddedResource>
149+
<EmbeddedResource Include="V3Tests\Samples\OpenApiParameter\queryParameterWithObjectType.yaml">
150+
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
151+
</EmbeddedResource>
152+
<EmbeddedResource Include="V3Tests\Samples\OpenApiParameter\queryParameter.yaml">
153+
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
154+
</EmbeddedResource>
117155
<EmbeddedResource Include="V3Tests\Samples\OpenApiSchema\advancedSchemaWithReference.yaml">
118156
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
119157
</EmbeddedResource>

test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiParameterTests.cs

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,5 +168,89 @@ public void ParseHeaderParameterShouldSucceed()
168168
}
169169
});
170170
}
171+
172+
[Fact]
173+
public void ParseParameterWithNullLocationShouldSucceed()
174+
{
175+
// Arrange
176+
MapNode node;
177+
using ( var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "parameterWithNullLocation.yaml")) )
178+
{
179+
node = TestHelper.CreateYamlMapNode(stream);
180+
}
181+
182+
// Act
183+
var parameter = OpenApiV2Deserializer.LoadParameter(node);
184+
185+
// Assert
186+
parameter.ShouldBeEquivalentTo(
187+
new OpenApiParameter
188+
{
189+
In = null,
190+
Name = "username",
191+
Description = "username to fetch",
192+
Required = true,
193+
Schema = new OpenApiSchema
194+
{
195+
Type = "string"
196+
}
197+
});
198+
}
199+
200+
[Fact]
201+
public void ParseParameterWithNoLocationShouldSucceed()
202+
{
203+
// Arrange
204+
MapNode node;
205+
using ( var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "parameterWithNoLocation.yaml")) )
206+
{
207+
node = TestHelper.CreateYamlMapNode(stream);
208+
}
209+
210+
// Act
211+
var parameter = OpenApiV2Deserializer.LoadParameter(node);
212+
213+
// Assert
214+
parameter.ShouldBeEquivalentTo(
215+
new OpenApiParameter
216+
{
217+
In = null,
218+
Name = "username",
219+
Description = "username to fetch",
220+
Required = true,
221+
Schema = new OpenApiSchema
222+
{
223+
Type = "string"
224+
}
225+
});
226+
}
227+
228+
[Fact]
229+
public void ParseParameterWithUnknownLocationShouldSucceed()
230+
{
231+
// Arrange
232+
MapNode node;
233+
using ( var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "parameterWithUnknownLocation.yaml")) )
234+
{
235+
node = TestHelper.CreateYamlMapNode(stream);
236+
}
237+
238+
// Act
239+
var parameter = OpenApiV2Deserializer.LoadParameter(node);
240+
241+
// Assert
242+
parameter.ShouldBeEquivalentTo(
243+
new OpenApiParameter
244+
{
245+
In = null,
246+
Name = "username",
247+
Description = "username to fetch",
248+
Required = true,
249+
Schema = new OpenApiSchema
250+
{
251+
Type = "string"
252+
}
253+
});
254+
}
171255
}
172256
}

test/Microsoft.OpenApi.Readers.Tests/V2Tests/Samples/OpenApiParameter/headerParameter.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#parameterObject
1+
# modified from https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#parameterObject
22
name: token
33
in: header
44
description: token to be passed as a header
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
name: username
2+
description: username to fetch
3+
required: true
4+
type: string

0 commit comments

Comments
 (0)