Skip to content

Commit d639dd5

Browse files
authored
Merge pull request #186 from Microsoft/PerthCharern/FixFormData
Fix formData/body parameter and consumes handling Fix the serialization and deserialization logic for V2 formData and body parameter. V2 operation deserialization now respects the consumes property. Unit tests verify that serializing, deserializing, and re-deserializing all work as expected.
2 parents b9ba6bb + eb6bcb1 commit d639dd5

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)