Skip to content

Commit 8b99269

Browse files
committed
- Ensure that null in "in" in parameter object is parsed to a null In in the DOM.
- Add validation rule to validate the required fields in Parameter.
1 parent e8f55b1 commit 8b99269

21 files changed

+566
-17
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: 17 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,12 @@ private static void ProcessIn(OpenApiParameter o, ParseNode n)
217218

218219
formParameters.Add(o);
219220
break;
221+
case "null":
222+
case null:
223+
o.In = null;
224+
break;
220225
default:
221-
_in = value.GetEnumFromDisplayName<ParameterLocation>();
222-
o.In = _in;
226+
o.In = value.GetEnumFromDisplayName<ParameterLocation>();
223227
break;
224228
}
225229
}
@@ -229,10 +233,10 @@ public static OpenApiParameter LoadParameter(ParseNode node)
229233
return LoadParameter(node, false);
230234
}
231235

232-
public static OpenApiParameter LoadParameter(ParseNode node, bool evenBody)
236+
public static OpenApiParameter LoadParameter(ParseNode node, bool loadRequestBody)
233237
{
234238
// Reset the local variables every time this method is called.
235-
_in = null;
239+
_isBodyOrFormData = false;
236240

237241
var mapNode = node.CheckMapNode("parameter");
238242

@@ -254,9 +258,14 @@ public static OpenApiParameter LoadParameter(ParseNode node, bool evenBody)
254258
node.Context.SetTempStorage("schema", null);
255259
}
256260

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

262271
return parameter;

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

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,15 @@ internal static partial class OpenApiV3Deserializer
2727
{
2828
"in", (o, n) =>
2929
{
30-
o.In = n.GetScalarValue().GetEnumFromDisplayName<ParameterLocation>();
30+
var inString = n.GetScalarValue();
31+
if ( inString == null || inString == "null" )
32+
{
33+
o.In = null;
34+
}
35+
else
36+
{
37+
o.In = n.GetScalarValue().GetEnumFromDisplayName<ParameterLocation>();
38+
}
3139
}
3240
},
3341
{

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: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,12 @@
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\parameterWithNullLocation.yaml">
53+
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
54+
</EmbeddedResource>
4955
<EmbeddedResource Include="V2Tests\Samples\OpenApiParameter\headerParameter.yaml">
5056
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
5157
</EmbeddedResource>
@@ -114,6 +120,27 @@
114120
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
115121
</EmbeddedResource>
116122
<EmbeddedResource Include="V3Tests\Samples\OpenApiOperation\securedOperation.yaml" />
123+
<EmbeddedResource Include="V3Tests\Samples\OpenApiParameter\headerParameter.yaml">
124+
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
125+
</EmbeddedResource>
126+
<EmbeddedResource Include="V3Tests\Samples\OpenApiParameter\parameterWithNoLocation.yaml">
127+
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
128+
</EmbeddedResource>
129+
<EmbeddedResource Include="V3Tests\Samples\OpenApiParameter\parameterWithNullLocation.yaml">
130+
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
131+
</EmbeddedResource>
132+
<EmbeddedResource Include="V3Tests\Samples\OpenApiParameter\pathParameter.yaml">
133+
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
134+
</EmbeddedResource>
135+
<EmbeddedResource Include="V3Tests\Samples\OpenApiParameter\queryParameterWithObjectTypeAndContent.yaml">
136+
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
137+
</EmbeddedResource>
138+
<EmbeddedResource Include="V3Tests\Samples\OpenApiParameter\queryParameterWithObjectType.yaml">
139+
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
140+
</EmbeddedResource>
141+
<EmbeddedResource Include="V3Tests\Samples\OpenApiParameter\queryParameter.yaml">
142+
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
143+
</EmbeddedResource>
117144
<EmbeddedResource Include="V3Tests\Samples\OpenApiSchema\advancedSchemaWithReference.yaml">
118145
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
119146
</EmbeddedResource>

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

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,5 +168,61 @@ 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+
}
171227
}
172228
}

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
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
name: username
2+
in: null
3+
description: username to fetch
4+
required: true
5+
type: string

0 commit comments

Comments
 (0)