Skip to content

Commit eb6bcb1

Browse files
committed
- Fix the serialization and deserialization logic for V2 formData and body parameter.
- Unit tests verify that serializing, deserializing, and re-deserializing all work as expected.
1 parent b9ba6bb commit eb6bcb1

File tree

11 files changed

+694
-102
lines changed

11 files changed

+694
-102
lines changed

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

+28-15
Original file line numberDiff line numberDiff line change
@@ -92,13 +92,15 @@ internal static partial class OpenApiV2Deserializer
9292
{s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, n.CreateAny())}
9393
};
9494

95-
private static FixedFieldMap<OpenApiResponses> _responsesFixedFields = new FixedFieldMap<OpenApiResponses>();
95+
private static readonly FixedFieldMap<OpenApiResponses> _responsesFixedFields =
96+
new FixedFieldMap<OpenApiResponses>();
9697

97-
private static PatternFieldMap<OpenApiResponses> _responsesPatternFields = new PatternFieldMap<OpenApiResponses>
98-
{
99-
{s => !s.StartsWith("x-"), (o, p, n) => o.Add(p, LoadResponse(n))},
100-
{s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, n.CreateAny())}
101-
};
98+
private static readonly PatternFieldMap<OpenApiResponses> _responsesPatternFields =
99+
new PatternFieldMap<OpenApiResponses>
100+
{
101+
{s => !s.StartsWith("x-"), (o, p, n) => o.Add(p, LoadResponse(n))},
102+
{s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, n.CreateAny())}
103+
};
102104

103105
internal static OpenApiOperation LoadOperation(ParseNode node)
104106
{
@@ -119,7 +121,7 @@ internal static OpenApiOperation LoadOperation(ParseNode node)
119121
var formParameters = node.Context.GetFromTempStorage<List<OpenApiParameter>>("formParameters");
120122
if (formParameters != null)
121123
{
122-
operation.RequestBody = CreateFormBody(formParameters);
124+
operation.RequestBody = CreateFormBody(node.Context, formParameters);
123125
}
124126
}
125127

@@ -137,22 +139,33 @@ public static OpenApiResponses LoadResponses(ParseNode node)
137139
return domainObject;
138140
}
139141

140-
private static OpenApiRequestBody CreateFormBody(List<OpenApiParameter> formParameters)
142+
private static OpenApiRequestBody CreateFormBody(ParsingContext context, List<OpenApiParameter> formParameters)
141143
{
142144
var mediaType = new OpenApiMediaType
143145
{
144146
Schema = new OpenApiSchema
145147
{
146-
Properties = formParameters.ToDictionary(k => k.Name, v => v.Schema)
148+
Properties = formParameters.ToDictionary(
149+
k => k.Name,
150+
v =>
151+
{
152+
var schema = v.Schema;
153+
schema.Description = v.Description;
154+
return schema;
155+
}),
156+
Required = formParameters.Where(p => p.Required).Select(p => p.Name).ToList()
147157
}
148158
};
149159

160+
var consumes = context.GetFromTempStorage<List<string>>("operationconsumes") ??
161+
context.GetFromTempStorage<List<string>>("globalconsumes") ??
162+
new List<string> {"application/x-www-form-urlencoded"};
163+
150164
var formBody = new OpenApiRequestBody
151165
{
152-
Content = new Dictionary<string, OpenApiMediaType>
153-
{
154-
{"application/x-www-form-urlencoded", mediaType}
155-
}
166+
Content = consumes.ToDictionary(
167+
k => k,
168+
v => mediaType)
156169
};
157170

158171
return formBody;
@@ -162,8 +175,8 @@ private static OpenApiRequestBody CreateRequestBody(
162175
ParsingContext context,
163176
OpenApiParameter bodyParameter)
164177
{
165-
var consumes = context.GetFromTempStorage<List<string>>("operationproduces") ??
166-
context.GetFromTempStorage<List<string>>("globalproduces") ?? new List<string> {"application/json"};
178+
var consumes = context.GetFromTempStorage<List<string>>("operationconsumes") ??
179+
context.GetFromTempStorage<List<string>>("globalconsumes") ?? new List<string> {"application/json"};
167180

168181
var requestBody = new OpenApiRequestBody
169182
{

src/Microsoft.OpenApi/Models/OpenApiOperation.cs

+29-9
Original file line numberDiff line numberDiff line change
@@ -222,16 +222,36 @@ public void SerializeAsV2(IOpenApiWriter writer)
222222

223223
writer.WriteEndArray();
224224

225-
// Create a parameter as BodyParameter type and add to Parameters.
226-
// This type will be used to populate the In property as "body" when Parameters is serialized.
227-
var bodyParameter = new BodyParameter
225+
// This is form data. We need to split the request body into multiple parameters.
226+
if (consumes.Contains("application/x-www-form-urlencoded") ||
227+
consumes.Contains("multipart/form-data"))
228228
{
229-
Description = RequestBody.Description,
230-
Schema = RequestBody.Content.First().Value.Schema,
231-
Format = new List<string>(consumes)
232-
};
233-
234-
parameters.Add(bodyParameter);
229+
foreach (var property in RequestBody.Content.First().Value.Schema.Properties)
230+
{
231+
parameters.Add(
232+
new OpenApiFormDataParameter
233+
{
234+
Description = property.Value.Description,
235+
Name = property.Key,
236+
Schema = property.Value,
237+
Required = RequestBody.Content.First().Value.Schema.Required.Contains(property.Key)
238+
});
239+
}
240+
}
241+
else
242+
{
243+
var bodyParameter = new OpenApiBodyParameter
244+
{
245+
Description = RequestBody.Description,
246+
// V2 spec actually allows the body to have custom name.
247+
// Our library does not support this at the moment.
248+
Name = "body",
249+
Schema = RequestBody.Content.First().Value.Schema,
250+
Required = RequestBody.Required
251+
};
252+
253+
parameters.Add(bodyParameter);
254+
}
235255
}
236256

237257
if (Responses != null)

src/Microsoft.OpenApi/Models/OpenApiParameter.cs

+14-40
Original file line numberDiff line numberDiff line change
@@ -221,24 +221,23 @@ public void SerializeAsV2WithoutReference(IOpenApiWriter writer)
221221
{
222222
writer.WriteStartObject();
223223

224-
// name
225224
// in
226-
if (IsFormDataParameter())
225+
if (this is OpenApiFormDataParameter)
227226
{
228-
writer.WriteProperty(OpenApiConstants.Name, "formData");
229227
writer.WriteProperty(OpenApiConstants.In, "formData");
230228
}
231-
else if (IsBodyParameter())
229+
else if (this is OpenApiBodyParameter)
232230
{
233-
writer.WriteProperty(OpenApiConstants.Name, "body");
234231
writer.WriteProperty(OpenApiConstants.In, "body");
235232
}
236233
else
237234
{
238-
writer.WriteProperty(OpenApiConstants.Name, Name);
239235
writer.WriteProperty(OpenApiConstants.In, In.GetDisplayName());
240236
}
241237

238+
// name
239+
writer.WriteProperty(OpenApiConstants.Name, Name);
240+
242241
// description
243242
writer.WriteProperty(OpenApiConstants.Description, Description);
244243

@@ -249,7 +248,7 @@ public void SerializeAsV2WithoutReference(IOpenApiWriter writer)
249248
writer.WriteProperty(OpenApiConstants.Deprecated, Deprecated, false);
250249

251250
// schema
252-
if (IsBodyParameter())
251+
if (this is OpenApiBodyParameter)
253252
{
254253
writer.WriteOptionalObject(OpenApiConstants.Schema, Schema, (w, s) => s.SerializeAsV2(w));
255254
}
@@ -283,44 +282,19 @@ public void SerializeAsV2WithoutReference(IOpenApiWriter writer)
283282

284283
writer.WriteEndObject();
285284
}
286-
287-
private bool IsBodyParameter()
288-
{
289-
if (this is BodyParameter)
290-
{
291-
var parameter = (BodyParameter)this;
292-
293-
return !(
294-
parameter.Format.Contains("application/x-www-form-urlencoded") ||
295-
parameter.Format.Contains("multipart/form-data"));
296-
}
297-
298-
return false;
299-
}
300-
301-
private bool IsFormDataParameter()
302-
{
303-
if (this is BodyParameter)
304-
{
305-
var parameter = (BodyParameter)this;
306-
307-
return
308-
parameter.Format.Contains("application/x-www-form-urlencoded") ||
309-
parameter.Format.Contains("multipart/form-data");
310-
}
311-
312-
return false;
313-
}
314285
}
315286

316287
/// <summary>
317288
/// Body parameter class to propagate information needed for <see cref="OpenApiParameter.SerializeAsV2"/>
318289
/// </summary>
319-
internal class BodyParameter : OpenApiParameter
290+
internal class OpenApiBodyParameter : OpenApiParameter
291+
{
292+
}
293+
294+
/// <summary>
295+
/// Form parameter class to propagate information needed for <see cref="OpenApiParameter.SerializeAsV2"/>
296+
/// </summary>
297+
internal class OpenApiFormDataParameter : OpenApiParameter
320298
{
321-
/// <summary>
322-
/// Format of the parameter. This should be the same as the "consumes" property in Operation.
323-
/// </summary>
324-
public IList<string> Format { get; set; }
325299
}
326300
}

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

+9
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,15 @@
3131
<EmbeddedResource Include="V2Tests\Samples\minimal.v3.yaml">
3232
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
3333
</EmbeddedResource>
34+
<EmbeddedResource Include="V2Tests\Samples\OpenApiOperation\basicOperation.yaml">
35+
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
36+
</EmbeddedResource>
37+
<EmbeddedResource Include="V2Tests\Samples\OpenApiOperation\operationWithBody.yaml">
38+
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
39+
</EmbeddedResource>
40+
<EmbeddedResource Include="V2Tests\Samples\OpenApiOperation\operationWithFormData.yaml">
41+
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
42+
</EmbeddedResource>
3443
<EmbeddedResource Include="V2Tests\Samples\OpenApiParameter\bodyParameter.yaml">
3544
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
3645
</EmbeddedResource>

0 commit comments

Comments
 (0)