Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ namespace Microsoft.OpenApi;
/// Defines the base properties for the response object.
/// This interface is provided for type assertions but should not be implemented by package consumers beyond automatic mocking.
/// </summary>
public interface IOpenApiResponse : IOpenApiDescribedElement, IOpenApiReadOnlyExtensible, IShallowCopyable<IOpenApiResponse>, IOpenApiReferenceable
public interface IOpenApiResponse : IOpenApiDescribedElement, IOpenApiReadOnlyExtensible, IShallowCopyable<IOpenApiResponse>, IOpenApiReferenceable, IOpenApiSummarizedElement
{
/// <summary>
/// Maps a header name to its definition.
Expand Down
16 changes: 16 additions & 0 deletions src/Microsoft.OpenApi/Models/OpenApiResponse.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ namespace Microsoft.OpenApi
/// </summary>
public class OpenApiResponse : IOpenApiExtensible, IOpenApiResponse
{
/// <inheritdoc/>
public string? Summary { get; set; }

/// <inheritdoc/>
public string? Description { get; set; }

Expand All @@ -38,6 +41,7 @@ public OpenApiResponse() { }
internal OpenApiResponse(IOpenApiResponse response)
{
Utils.CheckArgumentNull(response);
Summary = response.Summary ?? Summary;
Description = response.Description ?? Description;
Headers = response.Headers != null ? new Dictionary<string, IOpenApiHeader>(response.Headers) : null;
Content = response.Content != null ? new Dictionary<string, OpenApiMediaType>(response.Content) : null;
Expand Down Expand Up @@ -76,6 +80,12 @@ private void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion version

writer.WriteStartObject();

// summary - only for v3.2+
if (version >= OpenApiSpecVersion.OpenApi3_2)
{
writer.WriteProperty(OpenApiConstants.Summary, Summary);
}

// description
writer.WriteRequiredProperty(OpenApiConstants.Description, Description);

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

// summary as extension for v3.1 and earlier
if (version < OpenApiSpecVersion.OpenApi3_2 && !string.IsNullOrEmpty(Summary))
{
writer.WriteProperty(OpenApiConstants.ExtensionFieldNamePrefix + "oai-" + OpenApiConstants.Summary, Summary);
}

// extension
writer.WriteExtensions(Extensions, version);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ namespace Microsoft.OpenApi
/// <summary>
/// Response Object Reference.
/// </summary>
public class OpenApiResponseReference : BaseOpenApiReferenceHolder<OpenApiResponse, IOpenApiResponse, OpenApiReferenceWithDescription>, IOpenApiResponse
public class OpenApiResponseReference : BaseOpenApiReferenceHolder<OpenApiResponse, IOpenApiResponse, OpenApiReferenceWithDescriptionAndSummary>, IOpenApiResponse
{
/// <summary>
/// Constructor initializing the reference object.
Expand All @@ -32,6 +32,13 @@ private OpenApiResponseReference(OpenApiResponseReference openApiResponseReferen

}

/// <inheritdoc/>
public string? Summary
{
get => string.IsNullOrEmpty(Reference.Summary) ? Target?.Summary : Reference.Summary;
set => Reference.Summary = value;
}

/// <inheritdoc/>
public string? Description
{
Expand Down Expand Up @@ -63,9 +70,9 @@ public IOpenApiResponse CreateShallowCopy()
return new OpenApiResponseReference(this);
}
/// <inheritdoc/>
protected override OpenApiReferenceWithDescription CopyReference(OpenApiReferenceWithDescription sourceReference)
protected override OpenApiReferenceWithDescriptionAndSummary CopyReference(OpenApiReferenceWithDescriptionAndSummary sourceReference)
{
return new OpenApiReferenceWithDescription(sourceReference);
return new OpenApiReferenceWithDescriptionAndSummary(sourceReference);
}
}
}
12 changes: 11 additions & 1 deletion src/Microsoft.OpenApi/Reader/V3/OpenApiResponseDeserializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,17 @@ internal static partial class OpenApiV3Deserializer
private static readonly PatternFieldMap<OpenApiResponse> _responsePatternFields =
new()
{
{s => s.StartsWith(OpenApiConstants.ExtensionFieldNamePrefix, StringComparison.OrdinalIgnoreCase), (o, p, n, _) => o.AddExtension(p, LoadExtension(p,n))}
{s => s.StartsWith(OpenApiConstants.ExtensionFieldNamePrefix, StringComparison.OrdinalIgnoreCase), (o, p, n, _) =>
{
if (p.Equals("x-oai-summary", StringComparison.OrdinalIgnoreCase))
{
o.Summary = n.GetScalarValue();
}
else
{
o.AddExtension(p, LoadExtension(p,n));
}
}}
};

public static IOpenApiResponse LoadResponse(ParseNode node, OpenApiDocument hostDocument)
Expand Down
12 changes: 11 additions & 1 deletion src/Microsoft.OpenApi/Reader/V31/OpenApiResponseDeserializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,17 @@ internal static partial class OpenApiV31Deserializer
private static readonly PatternFieldMap<OpenApiResponse> _responsePatternFields =
new()
{
{s => s.StartsWith(OpenApiConstants.ExtensionFieldNamePrefix, StringComparison.OrdinalIgnoreCase), (o, p, n, _) => o.AddExtension(p, LoadExtension(p,n))}
{s => s.StartsWith(OpenApiConstants.ExtensionFieldNamePrefix, StringComparison.OrdinalIgnoreCase), (o, p, n, _) =>
{
if (p.Equals("x-oai-summary", StringComparison.OrdinalIgnoreCase))
{
o.Summary = n.GetScalarValue();
}
else
{
o.AddExtension(p, LoadExtension(p,n));
}
}}
};

public static IOpenApiResponse LoadResponse(ParseNode node, OpenApiDocument hostDocument)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ internal static partial class OpenApiV32Deserializer
{
private static readonly FixedFieldMap<OpenApiResponse> _responseFixedFields = new()
{
{
"summary", (o, n, _) =>
{
o.Summary = n.GetScalarValue();
}
},
{
"description", (o, n, _) =>
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ public async Task LoadResponseReference()
Assert.Equivalent(
new OpenApiResponse
{
Summary = null,
Description = "Entity not found.",
Content = new Dictionary<string, OpenApiMediaType>()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,29 @@ public async Task ResponseWithReferencedHeaderShouldReferenceComponent()

Assert.Equal(expected.Description, actual.Description);
}

[Fact]
public async Task ResponseWithSummaryV32ShouldDeserializeCorrectly()
{
var result = await OpenApiDocument.LoadAsync(Path.Combine(SampleFolderPath, "responseWithSummary.yaml"), SettingsFixture.ReaderSettings);

var response = result.Document.Components.Responses["SuccessResponse"] as OpenApiResponse;

Assert.NotNull(response);
Assert.Equal("Successful response", response.Summary);
Assert.Equal("A successful response with summary", response.Description);
}

[Fact]
public async Task ResponseWithSummaryExtensionV31ShouldDeserializeCorrectly()
{
var result = await OpenApiDocument.LoadAsync(Path.Combine(SampleFolderPath, "responseWithSummaryExtension.yaml"), SettingsFixture.ReaderSettings);

var response = result.Document.Components.Responses["SuccessResponse"] as OpenApiResponse;

Assert.NotNull(response);
Assert.Equal("Successful response", response.Summary);
Assert.Equal("A successful response with summary extension", response.Description);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
openapi: 3.2.0
info:
title: Test API
version: 1.0.0
components:
responses:
SuccessResponse:
summary: Successful response
description: A successful response with summary
content:
application/json:
schema:
type: object
properties:
message:
type: string
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
openapi: 3.1.0
info:
title: Test API
version: 1.0.0
components:
responses:
SuccessResponse:
description: A successful response with summary extension
x-oai-summary: Successful response
content:
application/json:
schema:
type: object
properties:
message:
type: string
97 changes: 97 additions & 0 deletions test/Microsoft.OpenApi.Tests/Models/OpenApiResponseTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Text.Json.Nodes;
using System.Threading.Tasks;
using VerifyXunit;
using Xunit;
Expand Down Expand Up @@ -166,6 +167,22 @@ public class OpenApiResponseTests
}
};

private static OpenApiResponse ResponseWithSummary => new OpenApiResponse
{
Summary = "Successful response",
Description = "A detailed description of a successful response",
Content = new Dictionary<string, OpenApiMediaType>
{
["application/json"] = new OpenApiMediaType
{
Schema = new OpenApiSchema()
{
Type = JsonSchemaType.Object
}
}
}
};

[Theory]
[InlineData(OpenApiSpecVersion.OpenApi3_0, OpenApiConstants.Json)]
[InlineData(OpenApiSpecVersion.OpenApi2_0, OpenApiConstants.Json)]
Expand Down Expand Up @@ -399,5 +416,85 @@ public async Task SerializeReferencedResponseAsV2JsonWithoutReferenceWorksAsync(
// Assert
await Verifier.Verify(outputStringWriter).UseParameters(produceTerseOutput);
}

[Fact]
public async Task SerializeResponseWithSummaryAsV32Works()
{
// Arrange
var expected = @"{
""summary"": ""Successful response"",
""description"": ""A detailed description of a successful response"",
""content"": {
""application/json"": {
""schema"": {
""type"": ""object""
}
}
}
}";

// Act
var actual = await ResponseWithSummary.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_2);

// Assert
Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(actual)));
}

[Fact]
public async Task SerializeResponseWithSummaryAsV31Works()
{
// Arrange
var expected = @"{
""description"": ""A detailed description of a successful response"",
""content"": {
""application/json"": {
""schema"": {
""type"": ""object""
}
}
},
""x-oai-summary"": ""Successful response""
}";

// Act
var actual = await ResponseWithSummary.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_1);

// Assert
Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(actual)));
}

[Fact]
public async Task SerializeResponseWithSummaryAsV3Works()
{
// Arrange
var expected = @"{
""description"": ""A detailed description of a successful response"",
""content"": {
""application/json"": {
""schema"": {
""type"": ""object""
}
}
},
""x-oai-summary"": ""Successful response""
}";

// Act
var actual = await ResponseWithSummary.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0);

// Assert
Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(actual)));
}

[Fact]
public void ResponseWithSummaryShouldImplementIOpenApiSummarizedElement()
{
// Arrange
var response = new OpenApiResponse { Summary = "Test summary" };

// Act & Assert
Assert.IsType<IOpenApiSummarizedElement>(response, exactMatch: false);
Assert.Equal("Test summary", response.Summary);
}
}
}
10 changes: 6 additions & 4 deletions test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ namespace Microsoft.OpenApi
Microsoft.OpenApi.IOpenApiParameter? ConvertToBodyParameter(Microsoft.OpenApi.IOpenApiWriter writer);
System.Collections.Generic.IEnumerable<Microsoft.OpenApi.IOpenApiParameter>? ConvertToFormDataParameters(Microsoft.OpenApi.IOpenApiWriter writer);
}
public interface IOpenApiResponse : Microsoft.OpenApi.IOpenApiDescribedElement, Microsoft.OpenApi.IOpenApiElement, Microsoft.OpenApi.IOpenApiReadOnlyExtensible, Microsoft.OpenApi.IOpenApiReferenceable, Microsoft.OpenApi.IOpenApiSerializable, Microsoft.OpenApi.IShallowCopyable<Microsoft.OpenApi.IOpenApiResponse>
public interface IOpenApiResponse : Microsoft.OpenApi.IOpenApiDescribedElement, Microsoft.OpenApi.IOpenApiElement, Microsoft.OpenApi.IOpenApiReadOnlyExtensible, Microsoft.OpenApi.IOpenApiReferenceable, Microsoft.OpenApi.IOpenApiSerializable, Microsoft.OpenApi.IOpenApiSummarizedElement, Microsoft.OpenApi.IShallowCopyable<Microsoft.OpenApi.IOpenApiResponse>
{
System.Collections.Generic.IDictionary<string, Microsoft.OpenApi.OpenApiMediaType>? Content { get; }
System.Collections.Generic.IDictionary<string, Microsoft.OpenApi.IOpenApiHeader>? Headers { get; }
Expand Down Expand Up @@ -1112,29 +1112,31 @@ namespace Microsoft.OpenApi
public Microsoft.OpenApi.IOpenApiRequestBody CreateShallowCopy() { }
public override void SerializeAsV2(Microsoft.OpenApi.IOpenApiWriter writer) { }
}
public class OpenApiResponse : Microsoft.OpenApi.IOpenApiDescribedElement, Microsoft.OpenApi.IOpenApiElement, Microsoft.OpenApi.IOpenApiExtensible, Microsoft.OpenApi.IOpenApiReadOnlyExtensible, Microsoft.OpenApi.IOpenApiReferenceable, Microsoft.OpenApi.IOpenApiResponse, Microsoft.OpenApi.IOpenApiSerializable, Microsoft.OpenApi.IShallowCopyable<Microsoft.OpenApi.IOpenApiResponse>
public class OpenApiResponse : Microsoft.OpenApi.IOpenApiDescribedElement, Microsoft.OpenApi.IOpenApiElement, Microsoft.OpenApi.IOpenApiExtensible, Microsoft.OpenApi.IOpenApiReadOnlyExtensible, Microsoft.OpenApi.IOpenApiReferenceable, Microsoft.OpenApi.IOpenApiResponse, Microsoft.OpenApi.IOpenApiSerializable, Microsoft.OpenApi.IOpenApiSummarizedElement, Microsoft.OpenApi.IShallowCopyable<Microsoft.OpenApi.IOpenApiResponse>
{
public OpenApiResponse() { }
public System.Collections.Generic.IDictionary<string, Microsoft.OpenApi.OpenApiMediaType>? Content { get; set; }
public string? Description { get; set; }
public System.Collections.Generic.IDictionary<string, Microsoft.OpenApi.IOpenApiExtension>? Extensions { get; set; }
public System.Collections.Generic.IDictionary<string, Microsoft.OpenApi.IOpenApiHeader>? Headers { get; set; }
public System.Collections.Generic.IDictionary<string, Microsoft.OpenApi.IOpenApiLink>? Links { get; set; }
public string? Summary { get; set; }
public Microsoft.OpenApi.IOpenApiResponse CreateShallowCopy() { }
public virtual void SerializeAsV2(Microsoft.OpenApi.IOpenApiWriter writer) { }
public virtual void SerializeAsV3(Microsoft.OpenApi.IOpenApiWriter writer) { }
public virtual void SerializeAsV31(Microsoft.OpenApi.IOpenApiWriter writer) { }
public virtual void SerializeAsV32(Microsoft.OpenApi.IOpenApiWriter writer) { }
}
public class OpenApiResponseReference : Microsoft.OpenApi.BaseOpenApiReferenceHolder<Microsoft.OpenApi.OpenApiResponse, Microsoft.OpenApi.IOpenApiResponse, Microsoft.OpenApi.OpenApiReferenceWithDescription>, Microsoft.OpenApi.IOpenApiDescribedElement, Microsoft.OpenApi.IOpenApiElement, Microsoft.OpenApi.IOpenApiReadOnlyExtensible, Microsoft.OpenApi.IOpenApiReferenceable, Microsoft.OpenApi.IOpenApiResponse, Microsoft.OpenApi.IOpenApiSerializable, Microsoft.OpenApi.IShallowCopyable<Microsoft.OpenApi.IOpenApiResponse>
public class OpenApiResponseReference : Microsoft.OpenApi.BaseOpenApiReferenceHolder<Microsoft.OpenApi.OpenApiResponse, Microsoft.OpenApi.IOpenApiResponse, Microsoft.OpenApi.OpenApiReferenceWithDescriptionAndSummary>, Microsoft.OpenApi.IOpenApiDescribedElement, Microsoft.OpenApi.IOpenApiElement, Microsoft.OpenApi.IOpenApiReadOnlyExtensible, Microsoft.OpenApi.IOpenApiReferenceable, Microsoft.OpenApi.IOpenApiResponse, Microsoft.OpenApi.IOpenApiSerializable, Microsoft.OpenApi.IOpenApiSummarizedElement, Microsoft.OpenApi.IShallowCopyable<Microsoft.OpenApi.IOpenApiResponse>
{
public OpenApiResponseReference(string referenceId, Microsoft.OpenApi.OpenApiDocument? hostDocument = null, string? externalResource = null) { }
public System.Collections.Generic.IDictionary<string, Microsoft.OpenApi.OpenApiMediaType>? Content { get; }
public string? Description { get; set; }
public System.Collections.Generic.IDictionary<string, Microsoft.OpenApi.IOpenApiExtension>? Extensions { get; }
public System.Collections.Generic.IDictionary<string, Microsoft.OpenApi.IOpenApiHeader>? Headers { get; }
public System.Collections.Generic.IDictionary<string, Microsoft.OpenApi.IOpenApiLink>? Links { get; }
protected override Microsoft.OpenApi.OpenApiReferenceWithDescription CopyReference(Microsoft.OpenApi.OpenApiReferenceWithDescription sourceReference) { }
public string? Summary { get; set; }
protected override Microsoft.OpenApi.OpenApiReferenceWithDescriptionAndSummary CopyReference(Microsoft.OpenApi.OpenApiReferenceWithDescriptionAndSummary sourceReference) { }
public override Microsoft.OpenApi.IOpenApiResponse CopyReferenceAsTargetElementWithOverrides(Microsoft.OpenApi.IOpenApiResponse source) { }
public Microsoft.OpenApi.IOpenApiResponse CreateShallowCopy() { }
}
Expand Down
Loading