From 4015f13c89c2140216d3b263a2293b39290cf0f9 Mon Sep 17 00:00:00 2001 From: Maggiekimani1 Date: Tue, 5 Dec 2023 16:19:31 +0300 Subject: [PATCH 01/18] Preserve examples in v2 files and write them out as extensions --- .../Models/OpenApiParameter.cs | 16 ++++++++++++- .../Models/OpenApiRequestBody.cs | 2 ++ .../Models/OpenApiResponse.cs | 23 +++++++++++++++++++ .../Writers/IOpenApiWriter.cs | 9 ++++++++ .../Writers/OpenApiWriterBase.cs | 23 +++++++++++++++++++ 5 files changed, 72 insertions(+), 1 deletion(-) diff --git a/src/Microsoft.OpenApi/Models/OpenApiParameter.cs b/src/Microsoft.OpenApi/Models/OpenApiParameter.cs index 4fe85f1c0..cf1ce9a66 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiParameter.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiParameter.cs @@ -3,7 +3,7 @@ using System; using System.Collections.Generic; -using System.Text.Json; +using System.Linq; using Json.Schema; using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Extensions; @@ -433,6 +433,20 @@ public void SerializeAsV2WithoutReference(IOpenApiWriter writer) } } + //examples + if (Examples != null && Examples.Any()) + { + writer.WritePropertyName("x-examples"); + writer.WriteStartObject(); + + foreach (var example in Examples) + { + writer.WritePropertyName(example.Key); + writer.WriteV2Examples(writer, example.Value, OpenApiSpecVersion.OpenApi2_0); + } + writer.WriteEndObject(); + } + // extensions writer.WriteExtensions(extensionsClone, OpenApiSpecVersion.OpenApi2_0); diff --git a/src/Microsoft.OpenApi/Models/OpenApiRequestBody.cs b/src/Microsoft.OpenApi/Models/OpenApiRequestBody.cs index b6ef5d28c..a18df4588 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiRequestBody.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiRequestBody.cs @@ -186,6 +186,7 @@ internal OpenApiBodyParameter ConvertToBodyParameter() // To allow round-tripping we use an extension to hold the name Name = "body", Schema = Content.Values.FirstOrDefault()?.Schema ?? new JsonSchemaBuilder().Build(), + Examples = Content.Values.FirstOrDefault()?.Examples, Required = Required, 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. }; @@ -219,6 +220,7 @@ internal IEnumerable ConvertToFormDataParameters() Description = property.Value.GetDescription(), Name = property.Key, Schema = property.Value, + Examples = Content.Values.FirstOrDefault()?.Examples, Required = Content.First().Value.Schema.GetRequired()?.Contains(property.Key) ?? false }; } diff --git a/src/Microsoft.OpenApi/Models/OpenApiResponse.cs b/src/Microsoft.OpenApi/Models/OpenApiResponse.cs index 447b2fb1d..b60445d1f 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiResponse.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiResponse.cs @@ -6,7 +6,9 @@ using System.Linq; using System.Text.Json; using System.Text.Json.Nodes; +using System.Xml.Linq; using Json.More; +using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Helpers; using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Writers; @@ -235,6 +237,27 @@ public void SerializeAsV2WithoutReference(IOpenApiWriter writer) writer.WriteEndObject(); } + if (Content.Values.Any(m => m.Examples != null && m.Examples.Any())) + { + writer.WritePropertyName("x-examples"); + writer.WriteStartObject(); + + foreach (var mediaTypePair in Content) + { + var examples = mediaTypePair.Value.Examples; + if (examples != null && examples.Any()) + { + foreach (var example in examples) + { + writer.WritePropertyName(example.Key); + writer.WriteV2Examples(writer, example.Value, OpenApiSpecVersion.OpenApi2_0); + } + } + } + + writer.WriteEndObject(); + } + writer.WriteExtensions(mediatype.Value.Extensions, OpenApiSpecVersion.OpenApi2_0); foreach (var key in mediatype.Value.Extensions.Keys) diff --git a/src/Microsoft.OpenApi/Writers/IOpenApiWriter.cs b/src/Microsoft.OpenApi/Writers/IOpenApiWriter.cs index 0c97f580b..d2565ca21 100644 --- a/src/Microsoft.OpenApi/Writers/IOpenApiWriter.cs +++ b/src/Microsoft.OpenApi/Writers/IOpenApiWriter.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using Json.Schema; +using Microsoft.OpenApi.Models; namespace Microsoft.OpenApi.Writers { @@ -96,5 +97,13 @@ public interface IOpenApiWriter /// /// void WriteJsonSchemaReference(IOpenApiWriter writer, Uri reference); + + /// + /// Writes out existing examples in a mediatype object + /// + /// + /// + /// + void WriteV2Examples(IOpenApiWriter writer, OpenApiExample example, OpenApiSpecVersion version); } } diff --git a/src/Microsoft.OpenApi/Writers/OpenApiWriterBase.cs b/src/Microsoft.OpenApi/Writers/OpenApiWriterBase.cs index 335cba7af..ef4042e04 100644 --- a/src/Microsoft.OpenApi/Writers/OpenApiWriterBase.cs +++ b/src/Microsoft.OpenApi/Writers/OpenApiWriterBase.cs @@ -594,6 +594,29 @@ public void WriteJsonSchemaReference(IOpenApiWriter writer, Uri reference) this.WriteProperty(OpenApiConstants.DollarRef, reference.OriginalString); WriteEndObject(); } + + /// + public void WriteV2Examples(IOpenApiWriter writer, OpenApiExample example, OpenApiSpecVersion version) + { + writer.WriteStartObject(); + + // summary + writer.WriteProperty(OpenApiConstants.Summary, example.Summary); + + // description + writer.WriteProperty(OpenApiConstants.Description, example.Description); + + // value + writer.WriteOptionalObject(OpenApiConstants.Value, example.Value, (w, v) => w.WriteAny(v)); + + // externalValue + writer.WriteProperty(OpenApiConstants.ExternalValue, example.ExternalValue); + + // extensions + writer.WriteExtensions(example.Extensions, version); + + writer.WriteEndObject(); + } } internal class FindJsonSchemaRefs : OpenApiVisitorBase From f5037576035db8296c1e1b46fe3e414990d3df45 Mon Sep 17 00:00:00 2001 From: Maggiekimani1 Date: Tue, 5 Dec 2023 16:25:57 +0300 Subject: [PATCH 02/18] Clean up code; update tests and public API --- .../Extensions/JsonSchemaBuilderExtensions.cs | 20 ------------- ...sync_produceTerseOutput=False.verified.txt | 8 ++++- ...Async_produceTerseOutput=True.verified.txt | 2 +- .../Models/OpenApiParameterTests.cs | 10 +++++-- .../PublicApi/PublicApi.approved.txt | 29 ++++++++++++++----- .../Validations/ValidationRuleSetTests.cs | 6 ++-- 6 files changed, 41 insertions(+), 34 deletions(-) diff --git a/src/Microsoft.OpenApi/Extensions/JsonSchemaBuilderExtensions.cs b/src/Microsoft.OpenApi/Extensions/JsonSchemaBuilderExtensions.cs index 92738f66c..09e73e532 100644 --- a/src/Microsoft.OpenApi/Extensions/JsonSchemaBuilderExtensions.cs +++ b/src/Microsoft.OpenApi/Extensions/JsonSchemaBuilderExtensions.cs @@ -114,25 +114,6 @@ public static JsonSchemaBuilder OpenApiExternalDocs(this JsonSchemaBuilder build return builder; } - /// - /// Removes a keyword from the builder instance - /// - /// - /// - /// - public static JsonSchemaBuilder RemoveKeyWord(this JsonSchemaBuilder builder, IJsonSchemaKeyword keyWord) - { - var schema = builder.Build(); - var newKeyWords = new List(); - newKeyWords = schema.Keywords.Where(x => !x.Equals(keyWord)).ToList(); - foreach (var item in newKeyWords) - { - builder.Add(item); - } - - return builder; - } - /// /// Removes a keyword /// @@ -155,7 +136,6 @@ public static JsonSchemaBuilder Remove(this JsonSchemaBuilder builder, string ke } } - //_keywords.Remove(keyword); return schemaBuilder; } } diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiParameterTests.SerializeParameterWithSchemaTypeObjectAsV2JsonWorksAsync_produceTerseOutput=False.verified.txt b/test/Microsoft.OpenApi.Tests/Models/OpenApiParameterTests.SerializeParameterWithSchemaTypeObjectAsV2JsonWorksAsync_produceTerseOutput=False.verified.txt index 0542c58ce..744f8451c 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiParameterTests.SerializeParameterWithSchemaTypeObjectAsV2JsonWorksAsync_produceTerseOutput=False.verified.txt +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiParameterTests.SerializeParameterWithSchemaTypeObjectAsV2JsonWorksAsync_produceTerseOutput=False.verified.txt @@ -3,5 +3,11 @@ "name": "name1", "description": "description1", "required": true, - "type": "string" + "type": "string", + "x-examples": { + "test": { + "summary": "summary3", + "description": "description3" + } + } } \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiParameterTests.SerializeParameterWithSchemaTypeObjectAsV2JsonWorksAsync_produceTerseOutput=True.verified.txt b/test/Microsoft.OpenApi.Tests/Models/OpenApiParameterTests.SerializeParameterWithSchemaTypeObjectAsV2JsonWorksAsync_produceTerseOutput=True.verified.txt index b80b263d3..26b158865 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiParameterTests.SerializeParameterWithSchemaTypeObjectAsV2JsonWorksAsync_produceTerseOutput=True.verified.txt +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiParameterTests.SerializeParameterWithSchemaTypeObjectAsV2JsonWorksAsync_produceTerseOutput=True.verified.txt @@ -1 +1 @@ -{"in":"header","name":"name1","description":"description1","required":true,"type":"string"} \ No newline at end of file +{"in":"header","name":"name1","description":"description1","required":true,"type":"string","x-examples":{"test":{"summary":"summary3","description":"description3"}}} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiParameterTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiParameterTests.cs index d846f7a99..633157b55 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiParameterTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiParameterTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. using System.Collections.Generic; @@ -296,7 +296,13 @@ public void SerializeAdvancedParameterAsV2JsonWorks() "name": "name1", "description": "description1", "required": true, - "format": "double" + "format": "double", + "x-examples": { + "test": { + "summary": "summary3", + "description": "description3" + } + } } """; diff --git a/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt b/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt index 70695eaa5..0f296b27c 100755 --- a/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt +++ b/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt @@ -166,6 +166,14 @@ namespace Microsoft.OpenApi.Extensions public const string Name = "extensions"; public void Evaluate(Json.Schema.EvaluationContext context) { } } + [Json.Schema.SchemaKeyword("externalDocs")] + public class ExternalDocsKeyword : Json.Schema.IJsonSchemaKeyword + { + public const string Name = "externalDocs"; + public ExternalDocsKeyword(Microsoft.OpenApi.Models.OpenApiExternalDocs value) { } + public Microsoft.OpenApi.Models.OpenApiExternalDocs Value { get; } + public void Evaluate(Json.Schema.EvaluationContext context) { } + } public static class JsonSchemaBuilderExtensions { public static Json.Schema.JsonSchemaBuilder AdditionalPropertiesAllowed(this Json.Schema.JsonSchemaBuilder builder, bool additionalPropertiesAllowed) { } @@ -174,6 +182,8 @@ namespace Microsoft.OpenApi.Extensions public static Json.Schema.JsonSchemaBuilder ExclusiveMinimum(this Json.Schema.JsonSchemaBuilder builder, bool value) { } public static Json.Schema.JsonSchemaBuilder Extensions(this Json.Schema.JsonSchemaBuilder builder, System.Collections.Generic.IDictionary extensions) { } public static Json.Schema.JsonSchemaBuilder Nullable(this Json.Schema.JsonSchemaBuilder builder, bool value) { } + public static Json.Schema.JsonSchemaBuilder OpenApiExternalDocs(this Json.Schema.JsonSchemaBuilder builder, Microsoft.OpenApi.Models.OpenApiExternalDocs externalDocs) { } + public static Json.Schema.JsonSchemaBuilder Remove(this Json.Schema.JsonSchemaBuilder builder, string keyword) { } public static Json.Schema.JsonSchemaBuilder Summary(this Json.Schema.JsonSchemaBuilder builder, string summary) { } } public static class JsonSchemaExtensions @@ -184,6 +194,7 @@ namespace Microsoft.OpenApi.Extensions public static Microsoft.OpenApi.Extensions.DiscriminatorKeyword GetOpenApiDiscriminator(this Json.Schema.JsonSchema schema) { } public static bool? GetOpenApiExclusiveMaximum(this Json.Schema.JsonSchema schema) { } public static bool? GetOpenApiExclusiveMinimum(this Json.Schema.JsonSchema schema) { } + public static Microsoft.OpenApi.Models.OpenApiExternalDocs GetOpenApiExternalDocs(this Json.Schema.JsonSchema schema) { } public static string GetSummary(this Json.Schema.JsonSchema schema) { } } [Json.Schema.SchemaKeyword("nullable")] @@ -299,7 +310,7 @@ namespace Microsoft.OpenApi.MicrosoftExtensions public class EnumDescription : Microsoft.OpenApi.Interfaces.IOpenApiElement { public EnumDescription() { } - public EnumDescription(Microsoft.OpenApi.Any.OpenApiObject source) { } + public EnumDescription(System.Text.Json.Nodes.JsonObject source) { } public string Description { get; set; } public string Name { get; set; } public string Value { get; set; } @@ -313,7 +324,7 @@ namespace Microsoft.OpenApi.MicrosoftExtensions public string Version { get; set; } public static string Name { get; } public void Write(Microsoft.OpenApi.Writers.IOpenApiWriter writer, Microsoft.OpenApi.OpenApiSpecVersion specVersion) { } - public static Microsoft.OpenApi.MicrosoftExtensions.OpenApiDeprecationExtension Parse(Microsoft.OpenApi.Any.IOpenApiAny source) { } + public static Microsoft.OpenApi.MicrosoftExtensions.OpenApiDeprecationExtension Parse(Microsoft.OpenApi.Any.OpenApiAny source) { } } public class OpenApiEnumFlagsExtension : Microsoft.OpenApi.Interfaces.IOpenApiExtension { @@ -321,7 +332,7 @@ namespace Microsoft.OpenApi.MicrosoftExtensions public bool IsFlags { get; set; } public static string Name { get; } public void Write(Microsoft.OpenApi.Writers.IOpenApiWriter writer, Microsoft.OpenApi.OpenApiSpecVersion specVersion) { } - public static Microsoft.OpenApi.MicrosoftExtensions.OpenApiEnumFlagsExtension Parse(Microsoft.OpenApi.Any.IOpenApiAny source) { } + public static Microsoft.OpenApi.MicrosoftExtensions.OpenApiEnumFlagsExtension Parse(Microsoft.OpenApi.Any.OpenApiAny source) { } } public class OpenApiEnumValuesDescriptionExtension : Microsoft.OpenApi.Interfaces.IOpenApiExtension { @@ -330,7 +341,7 @@ namespace Microsoft.OpenApi.MicrosoftExtensions public System.Collections.Generic.List ValuesDescriptions { get; set; } public static string Name { get; } public void Write(Microsoft.OpenApi.Writers.IOpenApiWriter writer, Microsoft.OpenApi.OpenApiSpecVersion specVersion) { } - public static Microsoft.OpenApi.MicrosoftExtensions.OpenApiEnumValuesDescriptionExtension Parse(Microsoft.OpenApi.Any.IOpenApiAny source) { } + public static Microsoft.OpenApi.MicrosoftExtensions.OpenApiEnumValuesDescriptionExtension Parse(Microsoft.OpenApi.Any.OpenApiAny source) { } } public class OpenApiPagingExtension : Microsoft.OpenApi.Interfaces.IOpenApiExtension { @@ -340,7 +351,7 @@ namespace Microsoft.OpenApi.MicrosoftExtensions public string OperationName { get; set; } public static string Name { get; } public void Write(Microsoft.OpenApi.Writers.IOpenApiWriter writer, Microsoft.OpenApi.OpenApiSpecVersion specVersion) { } - public static Microsoft.OpenApi.MicrosoftExtensions.OpenApiPagingExtension Parse(Microsoft.OpenApi.Any.IOpenApiAny source) { } + public static Microsoft.OpenApi.MicrosoftExtensions.OpenApiPagingExtension Parse(Microsoft.OpenApi.Any.OpenApiAny source) { } } public class OpenApiPrimaryErrorMessageExtension : Microsoft.OpenApi.Interfaces.IOpenApiExtension { @@ -348,7 +359,7 @@ namespace Microsoft.OpenApi.MicrosoftExtensions public bool IsPrimaryErrorMessage { get; set; } public static string Name { get; } public void Write(Microsoft.OpenApi.Writers.IOpenApiWriter writer, Microsoft.OpenApi.OpenApiSpecVersion specVersion) { } - public static Microsoft.OpenApi.MicrosoftExtensions.OpenApiPrimaryErrorMessageExtension Parse(Microsoft.OpenApi.Any.IOpenApiAny source) { } + public static Microsoft.OpenApi.MicrosoftExtensions.OpenApiPrimaryErrorMessageExtension Parse(Microsoft.OpenApi.Any.OpenApiAny source) { } } public class OpenApiReservedParameterExtension : Microsoft.OpenApi.Interfaces.IOpenApiExtension { @@ -356,7 +367,7 @@ namespace Microsoft.OpenApi.MicrosoftExtensions public bool? IsReserved { get; set; } public static string Name { get; } public void Write(Microsoft.OpenApi.Writers.IOpenApiWriter writer, Microsoft.OpenApi.OpenApiSpecVersion specVersion) { } - public static Microsoft.OpenApi.MicrosoftExtensions.OpenApiReservedParameterExtension Parse(Microsoft.OpenApi.Any.IOpenApiAny source) { } + public static Microsoft.OpenApi.MicrosoftExtensions.OpenApiReservedParameterExtension Parse(Microsoft.OpenApi.Any.OpenApiAny source) { } } } namespace Microsoft.OpenApi.Models @@ -1479,6 +1490,7 @@ namespace Microsoft.OpenApi.Writers void WriteRaw(string value); void WriteStartArray(); void WriteStartObject(); + void WriteV2Examples(Microsoft.OpenApi.Writers.IOpenApiWriter writer, Microsoft.OpenApi.Models.OpenApiExample example, Microsoft.OpenApi.OpenApiSpecVersion version); void WriteValue(bool value); void WriteValue(decimal value); void WriteValue(int value); @@ -1542,6 +1554,7 @@ namespace Microsoft.OpenApi.Writers public abstract void WriteRaw(string value); public abstract void WriteStartArray(); public abstract void WriteStartObject(); + public void WriteV2Examples(Microsoft.OpenApi.Writers.IOpenApiWriter writer, Microsoft.OpenApi.Models.OpenApiExample example, Microsoft.OpenApi.OpenApiSpecVersion version) { } public virtual void WriteValue(bool value) { } public virtual void WriteValue(System.DateTime value) { } public virtual void WriteValue(System.DateTimeOffset value) { } @@ -1572,6 +1585,8 @@ namespace Microsoft.OpenApi.Writers where T : struct { } public static void WriteProperty(this Microsoft.OpenApi.Writers.IOpenApiWriter writer, string name, T? value) where T : struct { } + public static void WriteRequiredCollection(this Microsoft.OpenApi.Writers.IOpenApiWriter writer, string name, System.Collections.Generic.IEnumerable elements, System.Action action) + where T : Microsoft.OpenApi.Interfaces.IOpenApiElement { } public static void WriteRequiredMap(this Microsoft.OpenApi.Writers.IOpenApiWriter writer, string name, System.Collections.Generic.IDictionary elements, System.Action action) { } public static void WriteRequiredMap(this Microsoft.OpenApi.Writers.IOpenApiWriter writer, string name, System.Collections.Generic.IDictionary elements, System.Action action) where T : Microsoft.OpenApi.Interfaces.IOpenApiElement { } diff --git a/test/Microsoft.OpenApi.Tests/Validations/ValidationRuleSetTests.cs b/test/Microsoft.OpenApi.Tests/Validations/ValidationRuleSetTests.cs index 14af8e042..55ae552d1 100644 --- a/test/Microsoft.OpenApi.Tests/Validations/ValidationRuleSetTests.cs +++ b/test/Microsoft.OpenApi.Tests/Validations/ValidationRuleSetTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. using System.Collections.Generic; @@ -52,8 +52,8 @@ public void RuleSetConstructorsReturnsTheCorrectRules() Assert.Empty(ruleSet_4.Rules); // Update the number if you add new default rule(s). - Assert.Equal(22, ruleSet_1.Rules.Count); - Assert.Equal(22, ruleSet_2.Rules.Count); + Assert.Equal(23, ruleSet_1.Rules.Count); + Assert.Equal(23, ruleSet_2.Rules.Count); Assert.Equal(3, ruleSet_3.Rules.Count); } From add658345974ae839245de4006317eba193b0c90 Mon Sep 17 00:00:00 2001 From: Maggiekimani1 Date: Wed, 10 Jan 2024 11:35:20 +0300 Subject: [PATCH 03/18] Add examples constant to temp storage keys --- src/Microsoft.OpenApi.Readers/V2/TempStorageKeys.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Microsoft.OpenApi.Readers/V2/TempStorageKeys.cs b/src/Microsoft.OpenApi.Readers/V2/TempStorageKeys.cs index c7b96f6ce..176af8a1e 100644 --- a/src/Microsoft.OpenApi.Readers/V2/TempStorageKeys.cs +++ b/src/Microsoft.OpenApi.Readers/V2/TempStorageKeys.cs @@ -17,5 +17,6 @@ internal static class TempStorageKeys public const string GlobalConsumes = "globalConsumes"; public const string GlobalProduces = "globalProduces"; public const string ParameterIsBodyOrFormData = "parameterIsBodyOrFormData"; + public const string Examples = "examples"; } } From 4b9e7765fb3c50fe9717643210a111331fb90475 Mon Sep 17 00:00:00 2001 From: Maggiekimani1 Date: Wed, 10 Jan 2024 11:37:21 +0300 Subject: [PATCH 04/18] Load "x-examples" as Examples; store in temp storage; retrieve and append it to the resulting MediaType object during deserialization --- .../V2/OpenApiResponseDeserializer.cs | 53 ++++++++++++++++--- 1 file changed, 47 insertions(+), 6 deletions(-) diff --git a/src/Microsoft.OpenApi.Readers/V2/OpenApiResponseDeserializer.cs b/src/Microsoft.OpenApi.Readers/V2/OpenApiResponseDeserializer.cs index f771a9974..dd7e17f94 100644 --- a/src/Microsoft.OpenApi.Readers/V2/OpenApiResponseDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V2/OpenApiResponseDeserializer.cs @@ -29,6 +29,10 @@ internal static partial class OpenApiV2Deserializer "examples", LoadExamples }, + { + "x-examples", + LoadExamplesExtension + }, { "schema", (o, n) => n.Context.SetTempStorage(TempStorageKeys.ResponseSchema, LoadSchema(n), o) @@ -38,7 +42,7 @@ internal static partial class OpenApiV2Deserializer private static readonly PatternFieldMap _responsePatternFields = new() { - {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, LoadExtension(p, n))} + {s => s.StartsWith("x-") && !s.Equals("x-examples"), (o, p, n) => o.AddExtension(p, LoadExtension(p, n))} }; private static readonly AnyFieldMap _mediaTypeAnyFields = @@ -70,6 +74,7 @@ private static void ProcessProduces(MapNode mapNode, OpenApiResponse response, P ?? context.DefaultContentType ?? new List { "application/octet-stream" }; var schema = context.GetFromTempStorage(TempStorageKeys.ResponseSchema, response); + var examples = context.GetFromTempStorage>(TempStorageKeys.Examples, response); foreach (var produce in produces) { @@ -85,7 +90,8 @@ private static void ProcessProduces(MapNode mapNode, OpenApiResponse response, P { var mediaType = new OpenApiMediaType { - Schema = schema + Schema = schema, + Examples = examples }; response.Content.Add(produce, mediaType); @@ -93,12 +99,49 @@ private static void ProcessProduces(MapNode mapNode, OpenApiResponse response, P } context.SetTempStorage(TempStorageKeys.ResponseSchema, null, response); + context.SetTempStorage(TempStorageKeys.Examples, null, response); context.SetTempStorage(TempStorageKeys.ResponseProducesSet, true, response); } + private static void LoadExamplesExtension(OpenApiResponse response, ParseNode node) + { + var mapNode = node.CheckMapNode("x-examples"); + var examples = new Dictionary(); + + foreach (var examplesNode in mapNode) + { + // Load the media type node as an OpenApiExample object + var example = new OpenApiExample(); + var exampleNode = examplesNode.Value.CheckMapNode(examplesNode.Name); + foreach (var valueNode in exampleNode) + { + switch (valueNode.Name) + { + case "summary": + example.Summary = valueNode.Value.GetScalarValue(); + break; + case "description": + example.Description = valueNode.Value.GetScalarValue(); + break; + case "value": + example.Value = valueNode.Value.CreateAny(); + break; + case "externalValue": + example.ExternalValue = valueNode.Value.GetScalarValue(); + break; + } + } + + examples.Add(examplesNode.Name, example); + } + + node.Context.SetTempStorage(TempStorageKeys.Examples, examples, response); + } + private static void LoadExamples(OpenApiResponse response, ParseNode node) { var mapNode = node.CheckMapNode("examples"); + foreach (var mediaTypeNode in mapNode) { LoadExample(response, mediaTypeNode.Name, mediaTypeNode.Value); @@ -109,10 +152,7 @@ private static void LoadExample(OpenApiResponse response, string mediaType, Pars { var exampleNode = node.CreateAny(); - if (response.Content == null) - { - response.Content = new Dictionary(); - } + response.Content ??= new Dictionary(); OpenApiMediaType mediaTypeObject; if (response.Content.TryGetValue(mediaType, out var value)) @@ -142,6 +182,7 @@ public static OpenApiResponse LoadResponse(ParseNode node) } var response = new OpenApiResponse(); + foreach (var property in mapNode) { property.ParseField(response, _responseFixedFields, _responsePatternFields); From 97571dd0aa8d764fad35c4757fb2377513b15156 Mon Sep 17 00:00:00 2001 From: Maggiekimani1 Date: Wed, 10 Jan 2024 11:38:29 +0300 Subject: [PATCH 05/18] Add missing method to the writer interface --- src/Microsoft.OpenApi/Writers/IOpenApiWriter.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/Microsoft.OpenApi/Writers/IOpenApiWriter.cs b/src/Microsoft.OpenApi/Writers/IOpenApiWriter.cs index 9da2b64e0..0a526a0ac 100644 --- a/src/Microsoft.OpenApi/Writers/IOpenApiWriter.cs +++ b/src/Microsoft.OpenApi/Writers/IOpenApiWriter.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. using System; @@ -100,5 +100,13 @@ public interface IOpenApiWriter /// /// void WriteJsonSchemaReference(IOpenApiWriter writer, Uri reference, OpenApiSpecVersion version); + + /// + /// Writes out existing examples in a mediatype object + /// + /// + /// + /// + void WriteV2Examples(IOpenApiWriter writer, OpenApiExample example, OpenApiSpecVersion version); } } From 9fb89b128aaded367a91ce5f91e697a0df6cce4b Mon Sep 17 00:00:00 2001 From: Maggiekimani1 Date: Wed, 10 Jan 2024 11:39:06 +0300 Subject: [PATCH 06/18] Remove whitespace --- src/Microsoft.OpenApi/Models/OpenApiResponse.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Microsoft.OpenApi/Models/OpenApiResponse.cs b/src/Microsoft.OpenApi/Models/OpenApiResponse.cs index e0daf154c..e3ce1fab3 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiResponse.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiResponse.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. using System; @@ -140,7 +140,7 @@ public virtual void SerializeAsV31WithoutReference(IOpenApiWriter writer) /// /// Serialize to OpenAPI V3 document without using reference. /// - public virtual void SerializeAsV3WithoutReference(IOpenApiWriter writer) + public virtual void SerializeAsV3WithoutReference(IOpenApiWriter writer) { SerializeInternalWithoutReference(writer, OpenApiSpecVersion.OpenApi3_0, (writer, element) => element.SerializeAsV3(writer)); From a681e8406780c3e9f48b5c7bf3faf5fc2999b3b0 Mon Sep 17 00:00:00 2001 From: Maggiekimani1 Date: Wed, 10 Jan 2024 12:00:17 +0300 Subject: [PATCH 07/18] Default to an empty collection if examples is null --- .../V2/OpenApiResponseDeserializer.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Microsoft.OpenApi.Readers/V2/OpenApiResponseDeserializer.cs b/src/Microsoft.OpenApi.Readers/V2/OpenApiResponseDeserializer.cs index dd7e17f94..811b6f8dd 100644 --- a/src/Microsoft.OpenApi.Readers/V2/OpenApiResponseDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V2/OpenApiResponseDeserializer.cs @@ -74,7 +74,8 @@ private static void ProcessProduces(MapNode mapNode, OpenApiResponse response, P ?? context.DefaultContentType ?? new List { "application/octet-stream" }; var schema = context.GetFromTempStorage(TempStorageKeys.ResponseSchema, response); - var examples = context.GetFromTempStorage>(TempStorageKeys.Examples, response); + var examples = context.GetFromTempStorage>(TempStorageKeys.Examples, response) + ?? new Dictionary(); foreach (var produce in produces) { From f4a9fa81f6310fafb57d61108b70aecb194e04e9 Mon Sep 17 00:00:00 2001 From: Maggiekimani1 Date: Wed, 10 Jan 2024 13:47:47 +0300 Subject: [PATCH 08/18] Add a reference to the OpenApi.Tests project to access the string extension method --- .../Microsoft.OpenApi.Readers.Tests.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/test/Microsoft.OpenApi.Readers.Tests/Microsoft.OpenApi.Readers.Tests.csproj b/test/Microsoft.OpenApi.Readers.Tests/Microsoft.OpenApi.Readers.Tests.csproj index 4cdae8853..d310d8af0 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/Microsoft.OpenApi.Readers.Tests.csproj +++ b/test/Microsoft.OpenApi.Readers.Tests/Microsoft.OpenApi.Readers.Tests.csproj @@ -30,6 +30,7 @@ + \ No newline at end of file From ab6995266fd3d28d2335a88f7bd6b992503909d1 Mon Sep 17 00:00:00 2001 From: Maggiekimani1 Date: Wed, 10 Jan 2024 13:48:56 +0300 Subject: [PATCH 09/18] Add test to validate that a V2 doc with x-examples gets mapped to MediaType Examples object when upcasting to V3 --- .../V2Tests/OpenApiOperationTests.cs | 52 +++++++++++++++++++ .../opWithResponseExamplesExtension.yaml | 28 ++++++++++ 2 files changed, 80 insertions(+) create mode 100644 test/Microsoft.OpenApi.Readers.Tests/V2Tests/Samples/OpenApiOperation/opWithResponseExamplesExtension.yaml diff --git a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiOperationTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiOperationTests.cs index 384d103fb..5589efd53 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiOperationTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiOperationTests.cs @@ -12,6 +12,7 @@ using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Readers.ParseNodes; using Microsoft.OpenApi.Readers.V2; +using Microsoft.OpenApi.Tests; using Xunit; namespace Microsoft.OpenApi.Readers.Tests.V2Tests @@ -293,5 +294,56 @@ public void ParseOperationWithResponseExamplesShouldSucceed() .Excluding(o => o.Responses["200"].Content["application/json"].Example.Node[2].Parent) .Excluding(o => o.Responses["200"].Content["application/json"].Example.Node[2].Root)); } + + [Fact] + public void ParseV2ResponseWithExamplesExtensionWorks() + { + // Arrange + MapNode node; + using (var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "opWithResponseExamplesExtension.yaml"))) + { + node = TestHelper.CreateYamlMapNode(stream); + } + + // Act + var operation = OpenApiV2Deserializer.LoadOperation(node); + var actual = operation.SerializeAsYaml(OpenApiSpecVersion.OpenApi3_0); + + // Assert + var expected = @"summary: Get all pets +responses: + '200': + description: Successful response + content: + application/json: + schema: + type: array + items: + type: object + properties: + name: + type: string + age: + type: integer + examples: + example1: + summary: Example - List of Pets + value: + - name: Buddy + age: 2 + - name: Whiskers + age: 1 + example2: + summary: Example - Playful Cat + value: + name: Whiskers + age: 1"; + + // Assert + actual = actual.MakeLineBreaksEnvironmentNeutral(); + expected = expected.MakeLineBreaksEnvironmentNeutral(); + actual.Should().Be(expected); + //Assert.Equal(expected, actual); + } } } diff --git a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/Samples/OpenApiOperation/opWithResponseExamplesExtension.yaml b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/Samples/OpenApiOperation/opWithResponseExamplesExtension.yaml new file mode 100644 index 000000000..5dcc89d97 --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/Samples/OpenApiOperation/opWithResponseExamplesExtension.yaml @@ -0,0 +1,28 @@ +summary: Get all pets +produces: +- application/json +responses: + '200': + description: Successful response + schema: + type: array + items: + type: object + properties: + name: + type: string + age: + type: integer + x-examples: + example1: + summary: Example - List of Pets + value: + - name: "Buddy" + age: 2 + - name: "Whiskers" + age: 1 + example2: + summary: Example - Playful Cat + value: + name: "Whiskers" + age: 1 \ No newline at end of file From 4e4b51562bb4d9b749ba536fa2e6af902348d1ee Mon Sep 17 00:00:00 2001 From: Maggiekimani1 Date: Thu, 11 Jan 2024 12:12:42 +0300 Subject: [PATCH 10/18] Add unit tests --- .../V2Tests/OpenApiOperationTests.cs | 52 ++++++++++++++++++- .../v3OperationWithResponseExamples.yaml | 28 ++++++++++ 2 files changed, 79 insertions(+), 1 deletion(-) create mode 100644 test/Microsoft.OpenApi.Readers.Tests/V2Tests/Samples/OpenApiOperation/v3OperationWithResponseExamples.yaml diff --git a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiOperationTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiOperationTests.cs index 5589efd53..d99f91525 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiOperationTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiOperationTests.cs @@ -12,6 +12,7 @@ using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Readers.ParseNodes; using Microsoft.OpenApi.Readers.V2; +using Microsoft.OpenApi.Readers.V3; using Microsoft.OpenApi.Tests; using Xunit; @@ -343,7 +344,56 @@ public void ParseV2ResponseWithExamplesExtensionWorks() actual = actual.MakeLineBreaksEnvironmentNeutral(); expected = expected.MakeLineBreaksEnvironmentNeutral(); actual.Should().Be(expected); - //Assert.Equal(expected, actual); + } + + [Fact] + public void LoadV3ExamplesInResponseAsExtensionsWorks() + { + // Arrange + MapNode node; + using (var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "v3OperationWithResponseExamples.yaml"))) + { + node = TestHelper.CreateYamlMapNode(stream); + } + + // Act + var operation = OpenApiV3Deserializer.LoadOperation(node); + var actual = operation.SerializeAsYaml(OpenApiSpecVersion.OpenApi2_0); + + // Assert + var expected = @"summary: Get all pets +produces: + - application/json +responses: + '200': + description: Successful response + schema: + type: array + items: + type: object + properties: + name: + type: string + age: + type: integer + x-examples: + example1: + summary: Example - List of Pets + value: + - name: Buddy + age: 2 + - name: Whiskers + age: 1 + example2: + summary: Example - Playful Cat + value: + name: Whiskers + age: 1"; + + // Assert + actual = actual.MakeLineBreaksEnvironmentNeutral(); + expected = expected.MakeLineBreaksEnvironmentNeutral(); + actual.Should().Be(expected); } } } diff --git a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/Samples/OpenApiOperation/v3OperationWithResponseExamples.yaml b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/Samples/OpenApiOperation/v3OperationWithResponseExamples.yaml new file mode 100644 index 000000000..c3b124685 --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/Samples/OpenApiOperation/v3OperationWithResponseExamples.yaml @@ -0,0 +1,28 @@ +summary: Get all pets +responses: + '200': + description: Successful response + content: + application/json: + schema: + type: array + items: + type: object + properties: + name: + type: string + age: + type: integer + examples: + example1: + summary: Example - List of Pets + value: + - name: "Buddy" + age: 2 + - name: "Whiskers" + age: 1 + example2: + summary: Example - Playful Cat + value: + name: "Whiskers" + age: 1 \ No newline at end of file From 3748ef13ddf19ea7cb2255148f962622f4d5c709 Mon Sep 17 00:00:00 2001 From: Maggiekimani1 Date: Thu, 11 Jan 2024 15:55:16 +0300 Subject: [PATCH 11/18] Fix CodeQL warnings --- .../Models/OpenApiDocument.cs | 8 ++++++- .../Models/OpenApiParameter.cs | 16 ++++++------- .../Models/OpenApiRequestBody.cs | 24 ++++--------------- .../Models/OpenApiResponse.cs | 9 +------ 4 files changed, 19 insertions(+), 38 deletions(-) diff --git a/src/Microsoft.OpenApi/Models/OpenApiDocument.cs b/src/Microsoft.OpenApi/Models/OpenApiDocument.cs index f0c341f48..5b754e0ad 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiDocument.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiDocument.cs @@ -613,7 +613,13 @@ internal IOpenApiReferenceable ResolveReference(OpenApiReference reference, bool } } - /// + /// + /// + /// + /// + /// + /// + /// public JsonSchema FindSubschema(Json.Pointer.JsonPointer pointer, EvaluationOptions options) { throw new NotImplementedException(); diff --git a/src/Microsoft.OpenApi/Models/OpenApiParameter.cs b/src/Microsoft.OpenApi/Models/OpenApiParameter.cs index 4dcbc4aa4..f2aff67a8 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiParameter.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiParameter.cs @@ -21,6 +21,7 @@ public class OpenApiParameter : IOpenApiReferenceable, IEffective /// Indicates if object is populated with data or is just a reference to the data @@ -131,7 +132,11 @@ public virtual JsonSchema Schema /// To represent examples of media types that cannot naturally be represented in JSON or YAML, /// a string value can contain the example with escaping where necessary. /// - public virtual OpenApiAny Example { get; set; } + public virtual OpenApiAny Example + { + get => _example; + set => _example = value; + } /// /// A map containing the representations for the parameter. @@ -224,14 +229,7 @@ private void SerializeInternal(IOpenApiWriter writer, ActionOpenApiParameter public OpenApiParameter GetEffective(OpenApiDocument doc) { - if (Reference != null) - { - return doc.ResolveReferenceTo(Reference); - } - else - { - return this; - } + return Reference != null ? doc.ResolveReferenceTo(Reference) : this; } /// diff --git a/src/Microsoft.OpenApi/Models/OpenApiRequestBody.cs b/src/Microsoft.OpenApi/Models/OpenApiRequestBody.cs index a18df4588..a2d0bd301 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiRequestBody.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiRequestBody.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. using System; @@ -113,14 +113,7 @@ private void SerializeInternal(IOpenApiWriter writer, ActionOpenApiRequestBody public OpenApiRequestBody GetEffective(OpenApiDocument doc) { - if (Reference != null) - { - return doc.ResolveReferenceTo(Reference); - } - else - { - return this; - } + return Reference != null ? doc.ResolveReferenceTo(Reference) : this; } /// @@ -190,9 +183,9 @@ internal OpenApiBodyParameter ConvertToBodyParameter() Required = Required, 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. }; - if (bodyParameter.Extensions.ContainsKey(OpenApiConstants.BodyName)) + if (bodyParameter.Extensions.TryGetValue(OpenApiConstants.BodyName, out var bodyParameterName)) { - var bodyName = bodyParameter.Extensions[OpenApiConstants.BodyName] as OpenApiAny; + var bodyName = bodyParameterName as OpenApiAny; bodyParameter.Name = string.IsNullOrEmpty(bodyName?.Node.ToString()) ? "body" : bodyName?.Node.ToString(); bodyParameter.Extensions.Remove(OpenApiConstants.BodyName); } @@ -206,15 +199,6 @@ internal IEnumerable ConvertToFormDataParameters() foreach (var property in Content.First().Value.Schema.GetProperties()) { - var paramSchema = property.Value; - if (paramSchema.GetType().Equals(SchemaValueType.String) - && ("binary".Equals(paramSchema.GetFormat().Key, StringComparison.OrdinalIgnoreCase) - || "base64".Equals(paramSchema.GetFormat().Key, StringComparison.OrdinalIgnoreCase))) - { - // JsonSchema is immutable so these can't be set - //paramSchema.Type("file"); - //paramSchema.Format(null); - } yield return new() { Description = property.Value.GetDescription(), diff --git a/src/Microsoft.OpenApi/Models/OpenApiResponse.cs b/src/Microsoft.OpenApi/Models/OpenApiResponse.cs index e3ce1fab3..3146f6fa5 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiResponse.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiResponse.cs @@ -118,14 +118,7 @@ private void SerializeInternal(IOpenApiWriter writer, ActionOpenApiResponse public OpenApiResponse GetEffective(OpenApiDocument doc) { - if (Reference != null) - { - return doc.ResolveReferenceTo(Reference); - } - else - { - return this; - } + return Reference != null ? doc.ResolveReferenceTo(Reference) : this; } /// From 819e857a9e33c6bfd8558d2ddfe34d331ebefafe Mon Sep 17 00:00:00 2001 From: Maggiekimani1 Date: Thu, 11 Jan 2024 16:12:55 +0300 Subject: [PATCH 12/18] Map the sequence explicitly using 'Select' --- src/Microsoft.OpenApi/Models/OpenApiResponse.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Microsoft.OpenApi/Models/OpenApiResponse.cs b/src/Microsoft.OpenApi/Models/OpenApiResponse.cs index 3146f6fa5..0050522d9 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiResponse.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiResponse.cs @@ -229,9 +229,8 @@ public void SerializeAsV2WithoutReference(IOpenApiWriter writer) writer.WritePropertyName("x-examples"); writer.WriteStartObject(); - foreach (var mediaTypePair in Content) + foreach (var examples in Content.Select(mediaTypePair => mediaTypePair.Value.Examples)) { - var examples = mediaTypePair.Value.Examples; if (examples != null && examples.Any()) { foreach (var example in examples) From 2f02d2f1277f980785a2ab276d45abdcc4859470 Mon Sep 17 00:00:00 2001 From: Maggiekimani1 Date: Thu, 11 Jan 2024 16:26:39 +0300 Subject: [PATCH 13/18] Filter using 'Where' --- src/Microsoft.OpenApi/Models/OpenApiResponse.cs | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/src/Microsoft.OpenApi/Models/OpenApiResponse.cs b/src/Microsoft.OpenApi/Models/OpenApiResponse.cs index 0050522d9..064dc4ce1 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiResponse.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiResponse.cs @@ -229,16 +229,12 @@ public void SerializeAsV2WithoutReference(IOpenApiWriter writer) writer.WritePropertyName("x-examples"); writer.WriteStartObject(); - foreach (var examples in Content.Select(mediaTypePair => mediaTypePair.Value.Examples)) + foreach (var example in Content + .Where(mediaTypePair => mediaTypePair.Value.Examples != null && mediaTypePair.Value.Examples.Any()) + .SelectMany(mediaTypePair => mediaTypePair.Value.Examples)) { - if (examples != null && examples.Any()) - { - foreach (var example in examples) - { - writer.WritePropertyName(example.Key); - writer.WriteV2Examples(writer, example.Value, OpenApiSpecVersion.OpenApi2_0); - } - } + writer.WritePropertyName(example.Key); + writer.WriteV2Examples(writer, example.Value, OpenApiSpecVersion.OpenApi2_0); } writer.WriteEndObject(); From 3b9cb1dc4cafc4f09d4944945226a81780c28496 Mon Sep 17 00:00:00 2001 From: Maggiekimani1 Date: Thu, 11 Jan 2024 16:43:23 +0300 Subject: [PATCH 14/18] Make the Examples property auto-implemented and remove the backing field --- src/Microsoft.OpenApi/Models/OpenApiParameter.cs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/Microsoft.OpenApi/Models/OpenApiParameter.cs b/src/Microsoft.OpenApi/Models/OpenApiParameter.cs index f2aff67a8..34c2ce288 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiParameter.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiParameter.cs @@ -21,7 +21,6 @@ public class OpenApiParameter : IOpenApiReferenceable, IEffective /// Indicates if object is populated with data or is just a reference to the data @@ -132,11 +131,7 @@ public virtual JsonSchema Schema /// To represent examples of media types that cannot naturally be represented in JSON or YAML, /// a string value can contain the example with escaping where necessary. /// - public virtual OpenApiAny Example - { - get => _example; - set => _example = value; - } + public virtual OpenApiAny Example { get; set; } /// /// A map containing the representations for the parameter. From 4537168ebef9b2e63adb44d58b2069c5ee802261 Mon Sep 17 00:00:00 2001 From: Maggie Kimani Date: Thu, 11 Jan 2024 17:34:44 +0300 Subject: [PATCH 15/18] Update src/Microsoft.OpenApi.Readers/V2/OpenApiResponseDeserializer.cs Co-authored-by: Vincent Biret --- src/Microsoft.OpenApi.Readers/V2/OpenApiResponseDeserializer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Microsoft.OpenApi.Readers/V2/OpenApiResponseDeserializer.cs b/src/Microsoft.OpenApi.Readers/V2/OpenApiResponseDeserializer.cs index 811b6f8dd..d8eef6ad8 100644 --- a/src/Microsoft.OpenApi.Readers/V2/OpenApiResponseDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V2/OpenApiResponseDeserializer.cs @@ -42,7 +42,7 @@ internal static partial class OpenApiV2Deserializer private static readonly PatternFieldMap _responsePatternFields = new() { - {s => s.StartsWith("x-") && !s.Equals("x-examples"), (o, p, n) => o.AddExtension(p, LoadExtension(p, n))} + {s => s.StartsWith("x-") && !s.Equals("x-examples", StringComparison.OrdinalIgnoreCase), (o, p, n) => o.AddExtension(p, LoadExtension(p, n))} }; private static readonly AnyFieldMap _mediaTypeAnyFields = From ad85b8711d7cd4eb1f1470d10862fad4211275af Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Thu, 11 Jan 2024 09:45:23 -0500 Subject: [PATCH 16/18] - adds missing using --- src/Microsoft.OpenApi.Readers/V2/OpenApiResponseDeserializer.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Microsoft.OpenApi.Readers/V2/OpenApiResponseDeserializer.cs b/src/Microsoft.OpenApi.Readers/V2/OpenApiResponseDeserializer.cs index d8eef6ad8..82c7c06bb 100644 --- a/src/Microsoft.OpenApi.Readers/V2/OpenApiResponseDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V2/OpenApiResponseDeserializer.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. +using System; using System.Collections.Generic; using Json.Schema; using Microsoft.OpenApi.Extensions; From 6d6ee783cb9e5839cac36811c0c492280487d766 Mon Sep 17 00:00:00 2001 From: Maggiekimani1 Date: Thu, 11 Jan 2024 17:45:55 +0300 Subject: [PATCH 17/18] Use constant to define the Example extensions property for reuse; add normalization --- .../V2/OpenApiResponseDeserializer.cs | 6 +++--- src/Microsoft.OpenApi/Models/OpenApiConstants.cs | 5 +++++ src/Microsoft.OpenApi/Models/OpenApiParameter.cs | 2 +- src/Microsoft.OpenApi/Models/OpenApiResponse.cs | 2 +- 4 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/Microsoft.OpenApi.Readers/V2/OpenApiResponseDeserializer.cs b/src/Microsoft.OpenApi.Readers/V2/OpenApiResponseDeserializer.cs index 811b6f8dd..eb86660d4 100644 --- a/src/Microsoft.OpenApi.Readers/V2/OpenApiResponseDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V2/OpenApiResponseDeserializer.cs @@ -42,7 +42,7 @@ internal static partial class OpenApiV2Deserializer private static readonly PatternFieldMap _responsePatternFields = new() { - {s => s.StartsWith("x-") && !s.Equals("x-examples"), (o, p, n) => o.AddExtension(p, LoadExtension(p, n))} + {s => s.StartsWith("x-") && !s.Equals(OpenApiConstants.ExamplesExtension), (o, p, n) => o.AddExtension(p, LoadExtension(p, n))} }; private static readonly AnyFieldMap _mediaTypeAnyFields = @@ -106,7 +106,7 @@ private static void ProcessProduces(MapNode mapNode, OpenApiResponse response, P private static void LoadExamplesExtension(OpenApiResponse response, ParseNode node) { - var mapNode = node.CheckMapNode("x-examples"); + var mapNode = node.CheckMapNode(OpenApiConstants.ExamplesExtension); var examples = new Dictionary(); foreach (var examplesNode in mapNode) @@ -116,7 +116,7 @@ private static void LoadExamplesExtension(OpenApiResponse response, ParseNode no var exampleNode = examplesNode.Value.CheckMapNode(examplesNode.Name); foreach (var valueNode in exampleNode) { - switch (valueNode.Name) + switch (valueNode.Name.ToLowerInvariant()) { case "summary": example.Summary = valueNode.Value.GetScalarValue(); diff --git a/src/Microsoft.OpenApi/Models/OpenApiConstants.cs b/src/Microsoft.OpenApi/Models/OpenApiConstants.cs index dca7d3fe8..25138d3c2 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiConstants.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiConstants.cs @@ -585,6 +585,11 @@ public static class OpenApiConstants /// public const string BodyName = "x-bodyName"; + /// + /// Field: Examples Extension + /// + public const string ExamplesExtension = "x-examples"; + /// /// Field: version3_0_0 /// diff --git a/src/Microsoft.OpenApi/Models/OpenApiParameter.cs b/src/Microsoft.OpenApi/Models/OpenApiParameter.cs index 34c2ce288..2aa115bf0 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiParameter.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiParameter.cs @@ -429,7 +429,7 @@ public void SerializeAsV2WithoutReference(IOpenApiWriter writer) //examples if (Examples != null && Examples.Any()) { - writer.WritePropertyName("x-examples"); + writer.WritePropertyName(OpenApiConstants.ExamplesExtension); writer.WriteStartObject(); foreach (var example in Examples) diff --git a/src/Microsoft.OpenApi/Models/OpenApiResponse.cs b/src/Microsoft.OpenApi/Models/OpenApiResponse.cs index 064dc4ce1..2f59a857d 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiResponse.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiResponse.cs @@ -226,7 +226,7 @@ public void SerializeAsV2WithoutReference(IOpenApiWriter writer) if (Content.Values.Any(m => m.Examples != null && m.Examples.Any())) { - writer.WritePropertyName("x-examples"); + writer.WritePropertyName(OpenApiConstants.ExamplesExtension); writer.WriteStartObject(); foreach (var example in Content From 63f5fcab5bda440e21d00cfbd1d54266fbdd6ac4 Mon Sep 17 00:00:00 2001 From: Maggiekimani1 Date: Thu, 11 Jan 2024 17:55:51 +0300 Subject: [PATCH 18/18] Update API interface --- test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt b/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt index 4c7cb2402..322a1a6d3 100755 --- a/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt +++ b/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt @@ -448,6 +448,7 @@ namespace Microsoft.OpenApi.Models public const string Enum = "enum"; public const string Example = "example"; public const string Examples = "examples"; + public const string ExamplesExtension = "x-examples"; public const string ExclusiveMaximum = "exclusiveMaximum"; public const string ExclusiveMinimum = "exclusiveMinimum"; public const string Explode = "explode";