Skip to content

Commit 7768801

Browse files
Merge pull request #1524 from microsoft/mk/fix-v2-examples-serialization
Fixes v2 examples serialization in a response object
2 parents 73aead3 + 63f5fca commit 7768801

17 files changed

+313
-50
lines changed

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

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

4+
using System;
45
using System.Collections.Generic;
56
using Json.Schema;
67
using Microsoft.OpenApi.Extensions;
@@ -29,6 +30,10 @@ internal static partial class OpenApiV2Deserializer
2930
"examples",
3031
LoadExamples
3132
},
33+
{
34+
"x-examples",
35+
LoadExamplesExtension
36+
},
3237
{
3338
"schema",
3439
(o, n) => n.Context.SetTempStorage(TempStorageKeys.ResponseSchema, LoadSchema(n), o)
@@ -38,7 +43,7 @@ internal static partial class OpenApiV2Deserializer
3843
private static readonly PatternFieldMap<OpenApiResponse> _responsePatternFields =
3944
new()
4045
{
41-
{s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, LoadExtension(p, n))}
46+
{s => s.StartsWith("x-") && !s.Equals(OpenApiConstants.ExamplesExtension), (o, p, n) => o.AddExtension(p, LoadExtension(p, n))}
4247
};
4348

4449
private static readonly AnyFieldMap<OpenApiMediaType> _mediaTypeAnyFields =
@@ -70,6 +75,8 @@ private static void ProcessProduces(MapNode mapNode, OpenApiResponse response, P
7075
?? context.DefaultContentType ?? new List<string> { "application/octet-stream" };
7176

7277
var schema = context.GetFromTempStorage<JsonSchema>(TempStorageKeys.ResponseSchema, response);
78+
var examples = context.GetFromTempStorage<Dictionary<string, OpenApiExample>>(TempStorageKeys.Examples, response)
79+
?? new Dictionary<string, OpenApiExample>();
7380

7481
foreach (var produce in produces)
7582
{
@@ -85,20 +92,58 @@ private static void ProcessProduces(MapNode mapNode, OpenApiResponse response, P
8592
{
8693
var mediaType = new OpenApiMediaType
8794
{
88-
Schema = schema
95+
Schema = schema,
96+
Examples = examples
8997
};
9098

9199
response.Content.Add(produce, mediaType);
92100
}
93101
}
94102

95103
context.SetTempStorage(TempStorageKeys.ResponseSchema, null, response);
104+
context.SetTempStorage(TempStorageKeys.Examples, null, response);
96105
context.SetTempStorage(TempStorageKeys.ResponseProducesSet, true, response);
97106
}
98107

108+
private static void LoadExamplesExtension(OpenApiResponse response, ParseNode node)
109+
{
110+
var mapNode = node.CheckMapNode(OpenApiConstants.ExamplesExtension);
111+
var examples = new Dictionary<string, OpenApiExample>();
112+
113+
foreach (var examplesNode in mapNode)
114+
{
115+
// Load the media type node as an OpenApiExample object
116+
var example = new OpenApiExample();
117+
var exampleNode = examplesNode.Value.CheckMapNode(examplesNode.Name);
118+
foreach (var valueNode in exampleNode)
119+
{
120+
switch (valueNode.Name.ToLowerInvariant())
121+
{
122+
case "summary":
123+
example.Summary = valueNode.Value.GetScalarValue();
124+
break;
125+
case "description":
126+
example.Description = valueNode.Value.GetScalarValue();
127+
break;
128+
case "value":
129+
example.Value = valueNode.Value.CreateAny();
130+
break;
131+
case "externalValue":
132+
example.ExternalValue = valueNode.Value.GetScalarValue();
133+
break;
134+
}
135+
}
136+
137+
examples.Add(examplesNode.Name, example);
138+
}
139+
140+
node.Context.SetTempStorage(TempStorageKeys.Examples, examples, response);
141+
}
142+
99143
private static void LoadExamples(OpenApiResponse response, ParseNode node)
100144
{
101145
var mapNode = node.CheckMapNode("examples");
146+
102147
foreach (var mediaTypeNode in mapNode)
103148
{
104149
LoadExample(response, mediaTypeNode.Name, mediaTypeNode.Value);
@@ -109,10 +154,7 @@ private static void LoadExample(OpenApiResponse response, string mediaType, Pars
109154
{
110155
var exampleNode = node.CreateAny();
111156

112-
if (response.Content == null)
113-
{
114-
response.Content = new Dictionary<string, OpenApiMediaType>();
115-
}
157+
response.Content ??= new Dictionary<string, OpenApiMediaType>();
116158

117159
OpenApiMediaType mediaTypeObject;
118160
if (response.Content.TryGetValue(mediaType, out var value))
@@ -142,6 +184,7 @@ public static OpenApiResponse LoadResponse(ParseNode node)
142184
}
143185

144186
var response = new OpenApiResponse();
187+
145188
foreach (var property in mapNode)
146189
{
147190
property.ParseField(response, _responseFixedFields, _responsePatternFields);

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,5 +17,6 @@ internal static class TempStorageKeys
1717
public const string GlobalConsumes = "globalConsumes";
1818
public const string GlobalProduces = "globalProduces";
1919
public const string ParameterIsBodyOrFormData = "parameterIsBodyOrFormData";
20+
public const string Examples = "examples";
2021
}
2122
}

src/Microsoft.OpenApi/Models/OpenApiConstants.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -585,6 +585,11 @@ public static class OpenApiConstants
585585
/// </summary>
586586
public const string BodyName = "x-bodyName";
587587

588+
/// <summary>
589+
/// Field: Examples Extension
590+
/// </summary>
591+
public const string ExamplesExtension = "x-examples";
592+
588593
/// <summary>
589594
/// Field: version3_0_0
590595
/// </summary>

src/Microsoft.OpenApi/Models/OpenApiDocument.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -613,7 +613,13 @@ internal IOpenApiReferenceable ResolveReference(OpenApiReference reference, bool
613613
}
614614
}
615615

616-
///
616+
/// <summary>
617+
///
618+
/// </summary>
619+
/// <param name="pointer"></param>
620+
/// <param name="options"></param>
621+
/// <returns></returns>
622+
/// <exception cref="NotImplementedException"></exception>
617623
public JsonSchema FindSubschema(Json.Pointer.JsonPointer pointer, EvaluationOptions options)
618624
{
619625
throw new NotImplementedException();

src/Microsoft.OpenApi/Models/OpenApiParameter.cs

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
using System;
55
using System.Collections.Generic;
6-
using System.Text.Json;
6+
using System.Linq;
77
using Json.Schema;
88
using Microsoft.OpenApi.Any;
99
using Microsoft.OpenApi.Extensions;
@@ -224,14 +224,7 @@ private void SerializeInternal(IOpenApiWriter writer, Action<IOpenApiWriter, IOp
224224
/// <returns>OpenApiParameter</returns>
225225
public OpenApiParameter GetEffective(OpenApiDocument doc)
226226
{
227-
if (Reference != null)
228-
{
229-
return doc.ResolveReferenceTo<OpenApiParameter>(Reference);
230-
}
231-
else
232-
{
233-
return this;
234-
}
227+
return Reference != null ? doc.ResolveReferenceTo<OpenApiParameter>(Reference) : this;
235228
}
236229

237230
/// <summary>
@@ -433,6 +426,20 @@ public void SerializeAsV2WithoutReference(IOpenApiWriter writer)
433426
}
434427
}
435428

429+
//examples
430+
if (Examples != null && Examples.Any())
431+
{
432+
writer.WritePropertyName(OpenApiConstants.ExamplesExtension);
433+
writer.WriteStartObject();
434+
435+
foreach (var example in Examples)
436+
{
437+
writer.WritePropertyName(example.Key);
438+
writer.WriteV2Examples(writer, example.Value, OpenApiSpecVersion.OpenApi2_0);
439+
}
440+
writer.WriteEndObject();
441+
}
442+
436443
// extensions
437444
writer.WriteExtensions(extensionsClone, OpenApiSpecVersion.OpenApi2_0);
438445

src/Microsoft.OpenApi/Models/OpenApiRequestBody.cs

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

44
using System;
@@ -113,14 +113,7 @@ private void SerializeInternal(IOpenApiWriter writer, Action<IOpenApiWriter, IOp
113113
/// <returns>OpenApiRequestBody</returns>
114114
public OpenApiRequestBody GetEffective(OpenApiDocument doc)
115115
{
116-
if (Reference != null)
117-
{
118-
return doc.ResolveReferenceTo<OpenApiRequestBody>(Reference);
119-
}
120-
else
121-
{
122-
return this;
123-
}
116+
return Reference != null ? doc.ResolveReferenceTo<OpenApiRequestBody>(Reference) : this;
124117
}
125118

126119
/// <summary>
@@ -186,12 +179,13 @@ internal OpenApiBodyParameter ConvertToBodyParameter()
186179
// To allow round-tripping we use an extension to hold the name
187180
Name = "body",
188181
Schema = Content.Values.FirstOrDefault()?.Schema ?? new JsonSchemaBuilder().Build(),
182+
Examples = Content.Values.FirstOrDefault()?.Examples,
189183
Required = Required,
190184
Extensions = Extensions.ToDictionary(static k => k.Key, static v => v.Value) // Clone extensions so we can remove the x-bodyName extensions from the output V2 model.
191185
};
192-
if (bodyParameter.Extensions.ContainsKey(OpenApiConstants.BodyName))
186+
if (bodyParameter.Extensions.TryGetValue(OpenApiConstants.BodyName, out var bodyParameterName))
193187
{
194-
var bodyName = bodyParameter.Extensions[OpenApiConstants.BodyName] as OpenApiAny;
188+
var bodyName = bodyParameterName as OpenApiAny;
195189
bodyParameter.Name = string.IsNullOrEmpty(bodyName?.Node.ToString()) ? "body" : bodyName?.Node.ToString();
196190
bodyParameter.Extensions.Remove(OpenApiConstants.BodyName);
197191
}
@@ -205,20 +199,12 @@ internal IEnumerable<OpenApiFormDataParameter> ConvertToFormDataParameters()
205199

206200
foreach (var property in Content.First().Value.Schema.GetProperties())
207201
{
208-
var paramSchema = property.Value;
209-
if (paramSchema.GetType().Equals(SchemaValueType.String)
210-
&& ("binary".Equals(paramSchema.GetFormat().Key, StringComparison.OrdinalIgnoreCase)
211-
|| "base64".Equals(paramSchema.GetFormat().Key, StringComparison.OrdinalIgnoreCase)))
212-
{
213-
// JsonSchema is immutable so these can't be set
214-
//paramSchema.Type("file");
215-
//paramSchema.Format(null);
216-
}
217202
yield return new()
218203
{
219204
Description = property.Value.GetDescription(),
220205
Name = property.Key,
221206
Schema = property.Value,
207+
Examples = Content.Values.FirstOrDefault()?.Examples,
222208
Required = Content.First().Value.Schema.GetRequired()?.Contains(property.Key) ?? false
223209
};
224210
}

src/Microsoft.OpenApi/Models/OpenApiResponse.cs

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -118,14 +118,7 @@ private void SerializeInternal(IOpenApiWriter writer, Action<IOpenApiWriter, IOp
118118
/// <returns>OpenApiResponse</returns>
119119
public OpenApiResponse GetEffective(OpenApiDocument doc)
120120
{
121-
if (Reference != null)
122-
{
123-
return doc.ResolveReferenceTo<OpenApiResponse>(Reference);
124-
}
125-
else
126-
{
127-
return this;
128-
}
121+
return Reference != null ? doc.ResolveReferenceTo<OpenApiResponse>(Reference) : this;
129122
}
130123

131124
/// <summary>
@@ -140,7 +133,7 @@ public virtual void SerializeAsV31WithoutReference(IOpenApiWriter writer)
140133
/// <summary>
141134
/// Serialize to OpenAPI V3 document without using reference.
142135
/// </summary>
143-
public virtual void SerializeAsV3WithoutReference(IOpenApiWriter writer)
136+
public virtual void SerializeAsV3WithoutReference(IOpenApiWriter writer)
144137
{
145138
SerializeInternalWithoutReference(writer, OpenApiSpecVersion.OpenApi3_0,
146139
(writer, element) => element.SerializeAsV3(writer));
@@ -231,6 +224,22 @@ public void SerializeAsV2WithoutReference(IOpenApiWriter writer)
231224
writer.WriteEndObject();
232225
}
233226

227+
if (Content.Values.Any(m => m.Examples != null && m.Examples.Any()))
228+
{
229+
writer.WritePropertyName(OpenApiConstants.ExamplesExtension);
230+
writer.WriteStartObject();
231+
232+
foreach (var example in Content
233+
.Where(mediaTypePair => mediaTypePair.Value.Examples != null && mediaTypePair.Value.Examples.Any())
234+
.SelectMany(mediaTypePair => mediaTypePair.Value.Examples))
235+
{
236+
writer.WritePropertyName(example.Key);
237+
writer.WriteV2Examples(writer, example.Value, OpenApiSpecVersion.OpenApi2_0);
238+
}
239+
240+
writer.WriteEndObject();
241+
}
242+
234243
writer.WriteExtensions(mediatype.Value.Extensions, OpenApiSpecVersion.OpenApi2_0);
235244

236245
foreach (var key in mediatype.Value.Extensions.Keys)

src/Microsoft.OpenApi/Writers/IOpenApiWriter.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System;
55
using System.Collections.Generic;
66
using Json.Schema;
7+
using Microsoft.OpenApi.Models;
78

89
namespace Microsoft.OpenApi.Writers
910
{
@@ -99,5 +100,13 @@ public interface IOpenApiWriter
99100
/// <param name="reference"></param>
100101
/// <param name="version"></param>
101102
void WriteJsonSchemaReference(IOpenApiWriter writer, Uri reference, OpenApiSpecVersion version);
103+
104+
/// <summary>
105+
/// Writes out existing examples in a mediatype object
106+
/// </summary>
107+
/// <param name="writer"></param>
108+
/// <param name="example"></param>
109+
/// <param name="version"></param>
110+
void WriteV2Examples(IOpenApiWriter writer, OpenApiExample example, OpenApiSpecVersion version);
102111
}
103112
}

src/Microsoft.OpenApi/Writers/OpenApiWriterBase.cs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -602,6 +602,29 @@ public void WriteJsonSchemaReference(IOpenApiWriter writer, Uri reference, OpenA
602602
this.WriteProperty(OpenApiConstants.DollarRef, referenceItem);
603603
WriteEndObject();
604604
}
605+
606+
/// <inheritdoc/>
607+
public void WriteV2Examples(IOpenApiWriter writer, OpenApiExample example, OpenApiSpecVersion version)
608+
{
609+
writer.WriteStartObject();
610+
611+
// summary
612+
writer.WriteProperty(OpenApiConstants.Summary, example.Summary);
613+
614+
// description
615+
writer.WriteProperty(OpenApiConstants.Description, example.Description);
616+
617+
// value
618+
writer.WriteOptionalObject(OpenApiConstants.Value, example.Value, (w, v) => w.WriteAny(v));
619+
620+
// externalValue
621+
writer.WriteProperty(OpenApiConstants.ExternalValue, example.ExternalValue);
622+
623+
// extensions
624+
writer.WriteExtensions(example.Extensions, version);
625+
626+
writer.WriteEndObject();
627+
}
605628
}
606629

607630
internal class FindJsonSchemaRefs : OpenApiVisitorBase

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
<ItemGroup>
3131
<ProjectReference Include="..\..\src\Microsoft.OpenApi\Microsoft.OpenApi.csproj" />
3232
<ProjectReference Include="..\..\src\Microsoft.OpenApi.Readers\Microsoft.OpenApi.Readers.csproj" />
33+
<ProjectReference Include="..\Microsoft.OpenApi.Tests\Microsoft.OpenApi.Tests.csproj" />
3334
</ItemGroup>
3435

3536
</Project>

0 commit comments

Comments
 (0)