Skip to content
This repository was archived by the owner on Nov 11, 2025. It is now read-only.

Commit 855a4a8

Browse files
Copilotbaywet
andcommitted
Add Summary support to OpenApiResponse with serialization/deserialization
Co-authored-by: baywet <7905502+baywet@users.noreply.github.com>
1 parent d7cbf64 commit 855a4a8

8 files changed

Lines changed: 203 additions & 3 deletions

File tree

src/Microsoft.OpenApi/Models/OpenApiResponse.cs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,11 @@ namespace Microsoft.OpenApi
1010
/// <summary>
1111
/// Response object.
1212
/// </summary>
13-
public class OpenApiResponse : IOpenApiExtensible, IOpenApiResponse
13+
public class OpenApiResponse : IOpenApiExtensible, IOpenApiResponse, IOpenApiSummarizedElement
1414
{
15+
/// <inheritdoc/>
16+
public string? Summary { get; set; }
17+
1518
/// <inheritdoc/>
1619
public string? Description { get; set; }
1720

@@ -38,6 +41,7 @@ public OpenApiResponse() { }
3841
internal OpenApiResponse(IOpenApiResponse response)
3942
{
4043
Utils.CheckArgumentNull(response);
44+
Summary = (response as IOpenApiSummarizedElement)?.Summary ?? Summary;
4145
Description = response.Description ?? Description;
4246
Headers = response.Headers != null ? new Dictionary<string, IOpenApiHeader>(response.Headers) : null;
4347
Content = response.Content != null ? new Dictionary<string, OpenApiMediaType>(response.Content) : null;
@@ -76,6 +80,12 @@ private void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion version
7680

7781
writer.WriteStartObject();
7882

83+
// summary - only for v3.2+
84+
if (version >= OpenApiSpecVersion.OpenApi3_2)
85+
{
86+
writer.WriteProperty(OpenApiConstants.Summary, Summary);
87+
}
88+
7989
// description
8090
writer.WriteRequiredProperty(OpenApiConstants.Description, Description);
8191

@@ -88,6 +98,12 @@ private void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion version
8898
// links
8999
writer.WriteOptionalMap(OpenApiConstants.Links, Links, callback);
90100

101+
// summary as extension for v3.1 and earlier
102+
if (version < OpenApiSpecVersion.OpenApi3_2 && !string.IsNullOrEmpty(Summary))
103+
{
104+
writer.WriteProperty(OpenApiConstants.ExtensionFieldNamePrefix + "oai-" + OpenApiConstants.Summary, Summary);
105+
}
106+
91107
// extension
92108
writer.WriteExtensions(Extensions, version);
93109

src/Microsoft.OpenApi/Reader/V3/OpenApiResponseDeserializer.cs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,17 @@ internal static partial class OpenApiV3Deserializer
3434
private static readonly PatternFieldMap<OpenApiResponse> _responsePatternFields =
3535
new()
3636
{
37-
{s => s.StartsWith(OpenApiConstants.ExtensionFieldNamePrefix, StringComparison.OrdinalIgnoreCase), (o, p, n, _) => o.AddExtension(p, LoadExtension(p,n))}
37+
{s => s.StartsWith(OpenApiConstants.ExtensionFieldNamePrefix, StringComparison.OrdinalIgnoreCase), (o, p, n, _) =>
38+
{
39+
if (p.Equals("x-oai-summary", StringComparison.OrdinalIgnoreCase))
40+
{
41+
o.Summary = n.GetScalarValue();
42+
}
43+
else
44+
{
45+
o.AddExtension(p, LoadExtension(p,n));
46+
}
47+
}}
3848
};
3949

4050
public static IOpenApiResponse LoadResponse(ParseNode node, OpenApiDocument hostDocument)

src/Microsoft.OpenApi/Reader/V31/OpenApiResponseDeserializer.cs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,17 @@ internal static partial class OpenApiV31Deserializer
3939
private static readonly PatternFieldMap<OpenApiResponse> _responsePatternFields =
4040
new()
4141
{
42-
{s => s.StartsWith(OpenApiConstants.ExtensionFieldNamePrefix, StringComparison.OrdinalIgnoreCase), (o, p, n, _) => o.AddExtension(p, LoadExtension(p,n))}
42+
{s => s.StartsWith(OpenApiConstants.ExtensionFieldNamePrefix, StringComparison.OrdinalIgnoreCase), (o, p, n, _) =>
43+
{
44+
if (p.Equals("x-oai-summary", StringComparison.OrdinalIgnoreCase))
45+
{
46+
o.Summary = n.GetScalarValue();
47+
}
48+
else
49+
{
50+
o.AddExtension(p, LoadExtension(p,n));
51+
}
52+
}}
4353
};
4454

4555
public static IOpenApiResponse LoadResponse(ParseNode node, OpenApiDocument hostDocument)

src/Microsoft.OpenApi/Reader/V32/OpenApiResponseDeserializer.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,12 @@ internal static partial class OpenApiV32Deserializer
1010
{
1111
private static readonly FixedFieldMap<OpenApiResponse> _responseFixedFields = new()
1212
{
13+
{
14+
"summary", (o, n, _) =>
15+
{
16+
o.Summary = n.GetScalarValue();
17+
}
18+
},
1319
{
1420
"description", (o, n, _) =>
1521
{

test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiResponseTests.cs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,5 +24,29 @@ public async Task ResponseWithReferencedHeaderShouldReferenceComponent()
2424

2525
Assert.Equal(expected.Description, actual.Description);
2626
}
27+
28+
[Fact]
29+
public async Task ResponseWithSummaryV32ShouldDeserializeCorrectly()
30+
{
31+
var result = await OpenApiDocument.LoadAsync(Path.Combine(SampleFolderPath, "responseWithSummary.yaml"), SettingsFixture.ReaderSettings);
32+
33+
var response = result.Document.Components.Responses["SuccessResponse"] as OpenApiResponse;
34+
35+
Assert.NotNull(response);
36+
Assert.Equal("Successful response", response.Summary);
37+
Assert.Equal("A successful response with summary", response.Description);
38+
}
39+
40+
[Fact]
41+
public async Task ResponseWithSummaryExtensionV31ShouldDeserializeCorrectly()
42+
{
43+
var result = await OpenApiDocument.LoadAsync(Path.Combine(SampleFolderPath, "responseWithSummaryExtension.yaml"), SettingsFixture.ReaderSettings);
44+
45+
var response = result.Document.Components.Responses["SuccessResponse"] as OpenApiResponse;
46+
47+
Assert.NotNull(response);
48+
Assert.Equal("Successful response", response.Summary);
49+
Assert.Equal("A successful response with summary extension", response.Description);
50+
}
2751
}
2852
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
openapi: 3.2.0
2+
info:
3+
title: Test API
4+
version: 1.0.0
5+
components:
6+
responses:
7+
SuccessResponse:
8+
summary: Successful response
9+
description: A successful response with summary
10+
content:
11+
application/json:
12+
schema:
13+
type: object
14+
properties:
15+
message:
16+
type: string
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
openapi: 3.1.0
2+
info:
3+
title: Test API
4+
version: 1.0.0
5+
components:
6+
responses:
7+
SuccessResponse:
8+
description: A successful response with summary extension
9+
x-oai-summary: Successful response
10+
content:
11+
application/json:
12+
schema:
13+
type: object
14+
properties:
15+
message:
16+
type: string

test/Microsoft.OpenApi.Tests/Models/OpenApiResponseTests.cs

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,22 @@ public class OpenApiResponseTests
166166
}
167167
};
168168

169+
private static OpenApiResponse ResponseWithSummary => new OpenApiResponse
170+
{
171+
Summary = "Successful response",
172+
Description = "A detailed description of a successful response",
173+
Content = new Dictionary<string, OpenApiMediaType>
174+
{
175+
["application/json"] = new OpenApiMediaType
176+
{
177+
Schema = new OpenApiSchema()
178+
{
179+
Type = JsonSchemaType.Object
180+
}
181+
}
182+
}
183+
};
184+
169185
[Theory]
170186
[InlineData(OpenApiSpecVersion.OpenApi3_0, OpenApiConstants.Json)]
171187
[InlineData(OpenApiSpecVersion.OpenApi2_0, OpenApiConstants.Json)]
@@ -399,5 +415,91 @@ public async Task SerializeReferencedResponseAsV2JsonWithoutReferenceWorksAsync(
399415
// Assert
400416
await Verifier.Verify(outputStringWriter).UseParameters(produceTerseOutput);
401417
}
418+
419+
[Fact]
420+
public async Task SerializeResponseWithSummaryAsV32Works()
421+
{
422+
// Arrange
423+
var expected = @"{
424+
""summary"": ""Successful response"",
425+
""description"": ""A detailed description of a successful response"",
426+
""content"": {
427+
""application/json"": {
428+
""schema"": {
429+
""type"": ""object""
430+
}
431+
}
432+
}
433+
}";
434+
435+
// Act
436+
var actual = await ResponseWithSummary.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_2);
437+
438+
// Assert
439+
actual = actual.MakeLineBreaksEnvironmentNeutral();
440+
expected = expected.MakeLineBreaksEnvironmentNeutral();
441+
Assert.Equal(expected, actual);
442+
}
443+
444+
[Fact]
445+
public async Task SerializeResponseWithSummaryAsV31Works()
446+
{
447+
// Arrange
448+
var expected = @"{
449+
""description"": ""A detailed description of a successful response"",
450+
""content"": {
451+
""application/json"": {
452+
""schema"": {
453+
""type"": ""object""
454+
}
455+
}
456+
},
457+
""x-oai-summary"": ""Successful response""
458+
}";
459+
460+
// Act
461+
var actual = await ResponseWithSummary.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_1);
462+
463+
// Assert
464+
actual = actual.MakeLineBreaksEnvironmentNeutral();
465+
expected = expected.MakeLineBreaksEnvironmentNeutral();
466+
Assert.Equal(expected, actual);
467+
}
468+
469+
[Fact]
470+
public async Task SerializeResponseWithSummaryAsV3Works()
471+
{
472+
// Arrange
473+
var expected = @"{
474+
""description"": ""A detailed description of a successful response"",
475+
""content"": {
476+
""application/json"": {
477+
""schema"": {
478+
""type"": ""object""
479+
}
480+
}
481+
},
482+
""x-oai-summary"": ""Successful response""
483+
}";
484+
485+
// Act
486+
var actual = await ResponseWithSummary.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0);
487+
488+
// Assert
489+
actual = actual.MakeLineBreaksEnvironmentNeutral();
490+
expected = expected.MakeLineBreaksEnvironmentNeutral();
491+
Assert.Equal(expected, actual);
492+
}
493+
494+
[Fact]
495+
public void ResponseWithSummaryShouldImplementIOpenApiSummarizedElement()
496+
{
497+
// Arrange
498+
var response = new OpenApiResponse { Summary = "Test summary" };
499+
500+
// Act & Assert
501+
Assert.IsAssignableFrom<IOpenApiSummarizedElement>(response);
502+
Assert.Equal("Test summary", response.Summary);
503+
}
402504
}
403505
}

0 commit comments

Comments
 (0)