From 4bc07e32ac33e36109e692e7735641c2acc8eb53 Mon Sep 17 00:00:00 2001 From: Roman Bulanenko Date: Wed, 22 Nov 2023 16:30:02 +0100 Subject: [PATCH 01/42] Handle number overflow during parsing of min/max values --- .../ParseNodes/ParserHelper.cs | 35 +++++++++++++++++++ .../V2/OpenApiHeaderDeserializer.cs | 6 ++-- .../V2/OpenApiParameterDeserializer.cs | 4 +-- .../V2/OpenApiSchemaDeserializer.cs | 4 +-- .../V3/OpenApiSchemaDeserializer.cs | 4 +-- .../ParseNodes/ParserHelperTests.cs | 24 +++++++++++++ 6 files changed, 68 insertions(+), 9 deletions(-) create mode 100644 src/Microsoft.OpenApi.Readers/ParseNodes/ParserHelper.cs create mode 100644 test/Microsoft.OpenApi.Readers.Tests/ParseNodes/ParserHelperTests.cs diff --git a/src/Microsoft.OpenApi.Readers/ParseNodes/ParserHelper.cs b/src/Microsoft.OpenApi.Readers/ParseNodes/ParserHelper.cs new file mode 100644 index 000000000..9dd05ebdd --- /dev/null +++ b/src/Microsoft.OpenApi.Readers/ParseNodes/ParserHelper.cs @@ -0,0 +1,35 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System; +using System.Globalization; + +namespace Microsoft.OpenApi.Readers.ParseNodes +{ + /// + /// Useful tools to parse data + /// + internal class ParserHelper + { + /// + /// Parses decimal in invariant culture. + /// If the decimal is too big or small, it returns the default value + /// + /// Note: sometimes developers put Double.MaxValue or Long.MaxValue as min/max values for numbers in json schema even if their numbers are not expected to be that big/small. + /// As we have already released the library with Decimal type for Max/Min, let's not introduce the breaking change and just fallback to Decimal.Max / Min. This should satisfy almost every scenario. + /// We can revisit this if somebody really needs to have double or long here. + /// + /// + public static decimal ParseDecimalWithFallbackOnOverflow(string value, decimal defaultValue) + { + try + { + return decimal.Parse(value, NumberStyles.Float, CultureInfo.InvariantCulture); + } + catch (OverflowException) + { + return defaultValue; + } + } + } +} diff --git a/src/Microsoft.OpenApi.Readers/V2/OpenApiHeaderDeserializer.cs b/src/Microsoft.OpenApi.Readers/V2/OpenApiHeaderDeserializer.cs index a6c99a2d9..ff873f4a0 100644 --- a/src/Microsoft.OpenApi.Readers/V2/OpenApiHeaderDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V2/OpenApiHeaderDeserializer.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; @@ -44,7 +44,7 @@ internal static partial class OpenApiV2Deserializer }, { "maximum", - (o, n) => GetOrCreateSchema(o).Maximum = decimal.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture) + (o, n) => GetOrCreateSchema(o).Maximum = ParserHelper.ParseDecimalWithFallbackOnOverflow(n.GetScalarValue(), decimal.MaxValue) }, { "exclusiveMaximum", @@ -52,7 +52,7 @@ internal static partial class OpenApiV2Deserializer }, { "minimum", - (o, n) => GetOrCreateSchema(o).Minimum = decimal.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture) + (o, n) => GetOrCreateSchema(o).Minimum = ParserHelper.ParseDecimalWithFallbackOnOverflow(n.GetScalarValue(), decimal.MinValue) }, { "exclusiveMinimum", diff --git a/src/Microsoft.OpenApi.Readers/V2/OpenApiParameterDeserializer.cs b/src/Microsoft.OpenApi.Readers/V2/OpenApiParameterDeserializer.cs index 0429d30ba..f74600e66 100644 --- a/src/Microsoft.OpenApi.Readers/V2/OpenApiParameterDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V2/OpenApiParameterDeserializer.cs @@ -61,11 +61,11 @@ internal static partial class OpenApiV2Deserializer }, { "minimum", - (o, n) => GetOrCreateSchema(o).Minimum = decimal.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture) + (o, n) => GetOrCreateSchema(o).Minimum = ParserHelper.ParseDecimalWithFallbackOnOverflow(n.GetScalarValue(), decimal.MinValue) }, { "maximum", - (o, n) => GetOrCreateSchema(o).Maximum = decimal.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture) + (o, n) => GetOrCreateSchema(o).Maximum = ParserHelper.ParseDecimalWithFallbackOnOverflow(n.GetScalarValue(), decimal.MaxValue) }, { "maxLength", diff --git a/src/Microsoft.OpenApi.Readers/V2/OpenApiSchemaDeserializer.cs b/src/Microsoft.OpenApi.Readers/V2/OpenApiSchemaDeserializer.cs index 5fd3ba5c8..dd884d574 100644 --- a/src/Microsoft.OpenApi.Readers/V2/OpenApiSchemaDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V2/OpenApiSchemaDeserializer.cs @@ -27,7 +27,7 @@ internal static partial class OpenApiV2Deserializer }, { "maximum", - (o, n) => o.Maximum = decimal.Parse(n.GetScalarValue(), NumberStyles.Float, CultureInfo.InvariantCulture) + (o, n) => o.Maximum = ParserHelper.ParseDecimalWithFallbackOnOverflow(n.GetScalarValue(), decimal.MaxValue) }, { "exclusiveMaximum", @@ -35,7 +35,7 @@ internal static partial class OpenApiV2Deserializer }, { "minimum", - (o, n) => o.Minimum = decimal.Parse(n.GetScalarValue(), NumberStyles.Float, CultureInfo.InvariantCulture) + (o, n) => o.Minimum = ParserHelper.ParseDecimalWithFallbackOnOverflow(n.GetScalarValue(), decimal.MinValue) }, { "exclusiveMinimum", diff --git a/src/Microsoft.OpenApi.Readers/V3/OpenApiSchemaDeserializer.cs b/src/Microsoft.OpenApi.Readers/V3/OpenApiSchemaDeserializer.cs index 52b79a1f9..1e386a33d 100644 --- a/src/Microsoft.OpenApi.Readers/V3/OpenApiSchemaDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V3/OpenApiSchemaDeserializer.cs @@ -27,7 +27,7 @@ internal static partial class OpenApiV3Deserializer }, { "maximum", - (o, n) => o.Maximum = decimal.Parse(n.GetScalarValue(), NumberStyles.Float, CultureInfo.InvariantCulture) + (o, n) => o.Maximum = ParserHelper.ParseDecimalWithFallbackOnOverflow(n.GetScalarValue(), decimal.MaxValue) }, { "exclusiveMaximum", @@ -35,7 +35,7 @@ internal static partial class OpenApiV3Deserializer }, { "minimum", - (o, n) => o.Minimum = decimal.Parse(n.GetScalarValue(), NumberStyles.Float, CultureInfo.InvariantCulture) + (o, n) => o.Minimum = ParserHelper.ParseDecimalWithFallbackOnOverflow(n.GetScalarValue(), decimal.MinValue) }, { "exclusiveMinimum", diff --git a/test/Microsoft.OpenApi.Readers.Tests/ParseNodes/ParserHelperTests.cs b/test/Microsoft.OpenApi.Readers.Tests/ParseNodes/ParserHelperTests.cs new file mode 100644 index 000000000..b343abb5c --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/ParseNodes/ParserHelperTests.cs @@ -0,0 +1,24 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using Microsoft.OpenApi.Readers.ParseNodes; +using Xunit; + +namespace Microsoft.OpenApi.Readers.Tests.ParseNodes +{ + [Collection("DefaultSettings")] + public class ParserHelperTests + { + [Fact] + public void ParseDecimalWithFallbackOnOverflow_ReturnsParsedValue() + { + Assert.Equal(23434, ParserHelper.ParseDecimalWithFallbackOnOverflow("23434", 10)); + } + + [Fact] + public void ParseDecimalWithFallbackOnOverflow_Overflows_ReturnsFallback() + { + Assert.Equal(10, ParserHelper.ParseDecimalWithFallbackOnOverflow(double.MaxValue.ToString(), 10)); + } + } +} From 47ef7754479275495973fac82552b2f83a6502cf Mon Sep 17 00:00:00 2001 From: Maggiekimani1 Date: Wed, 22 Nov 2023 19:43:33 +0300 Subject: [PATCH 02/42] Configure the settings file path to be relative to the current working directory --- .../Utilities/SettingsUtilities.cs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/Microsoft.OpenApi.Hidi/Utilities/SettingsUtilities.cs b/src/Microsoft.OpenApi.Hidi/Utilities/SettingsUtilities.cs index 6ec32f488..f6798287a 100644 --- a/src/Microsoft.OpenApi.Hidi/Utilities/SettingsUtilities.cs +++ b/src/Microsoft.OpenApi.Hidi/Utilities/SettingsUtilities.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. +using System.IO; using Microsoft.Extensions.Configuration; using Microsoft.OpenApi.OData; @@ -11,15 +12,19 @@ internal static class SettingsUtilities internal static IConfiguration GetConfiguration(string? settingsFile = null) { if (string.IsNullOrEmpty(settingsFile)) + { settingsFile = "appsettings.json"; + } + + var settingsFilePath = Path.Combine(Directory.GetCurrentDirectory(), settingsFile); IConfiguration config = new ConfigurationBuilder() - .AddJsonFile(settingsFile, true) - .Build(); + .AddJsonFile(settingsFilePath, true) + .Build(); return config; } - + internal static OpenApiConvertSettings GetOpenApiConvertSettings(IConfiguration config, string? metadataVersion) { if (config == null) { throw new System.ArgumentNullException(nameof(config)); } From 85dafb55abf6164d06efb24352a6297edcefd52b Mon Sep 17 00:00:00 2001 From: Maggiekimani1 Date: Wed, 22 Nov 2023 19:53:03 +0300 Subject: [PATCH 03/42] Upgrade lib versions --- src/Microsoft.OpenApi.Hidi/Microsoft.OpenApi.Hidi.csproj | 2 +- src/Microsoft.OpenApi.Readers/Microsoft.OpenApi.Readers.csproj | 2 +- src/Microsoft.OpenApi/Microsoft.OpenApi.csproj | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Microsoft.OpenApi.Hidi/Microsoft.OpenApi.Hidi.csproj b/src/Microsoft.OpenApi.Hidi/Microsoft.OpenApi.Hidi.csproj index 620043d64..c5f3e09d0 100644 --- a/src/Microsoft.OpenApi.Hidi/Microsoft.OpenApi.Hidi.csproj +++ b/src/Microsoft.OpenApi.Hidi/Microsoft.OpenApi.Hidi.csproj @@ -9,7 +9,7 @@ enable hidi ./../../artifacts - 1.3.5 + 1.3.6 OpenAPI.NET CLI tool for slicing OpenAPI documents true diff --git a/src/Microsoft.OpenApi.Readers/Microsoft.OpenApi.Readers.csproj b/src/Microsoft.OpenApi.Readers/Microsoft.OpenApi.Readers.csproj index dd422d6ef..d7d866263 100644 --- a/src/Microsoft.OpenApi.Readers/Microsoft.OpenApi.Readers.csproj +++ b/src/Microsoft.OpenApi.Readers/Microsoft.OpenApi.Readers.csproj @@ -3,7 +3,7 @@ netstandard2.0 latest true - 1.6.10 + 1.6.11 OpenAPI.NET Readers for JSON and YAML documents true diff --git a/src/Microsoft.OpenApi/Microsoft.OpenApi.csproj b/src/Microsoft.OpenApi/Microsoft.OpenApi.csproj index 047a39f77..d949685c6 100644 --- a/src/Microsoft.OpenApi/Microsoft.OpenApi.csproj +++ b/src/Microsoft.OpenApi/Microsoft.OpenApi.csproj @@ -3,7 +3,7 @@ netstandard2.0 Latest true - 1.6.10 + 1.6.11 .NET models with JSON and YAML writers for OpenAPI specification true From fecac2aec59945d55432e303c3be1539493bd893 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Nov 2023 21:39:56 +0000 Subject: [PATCH 04/42] Bump Verify.Xunit from 22.5.0 to 22.6.0 Bumps [Verify.Xunit](https://github.com/VerifyTests/Verify) from 22.5.0 to 22.6.0. - [Release notes](https://github.com/VerifyTests/Verify/releases) - [Commits](https://github.com/VerifyTests/Verify/commits/22.6.0) --- updated-dependencies: - dependency-name: Verify.Xunit dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj b/test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj index c234da25a..abaf54ce1 100644 --- a/test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj +++ b/test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj @@ -15,7 +15,7 @@ - + From b786bc075f08b8d80700c68ed2b1884e80c56f80 Mon Sep 17 00:00:00 2001 From: Maggie Kimani Date: Tue, 28 Nov 2023 17:45:56 +0300 Subject: [PATCH 05/42] Update bug_report.md --- .github/ISSUE_TEMPLATE/bug_report.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index c5b4cab90..a7d83e3dc 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -10,8 +10,8 @@ assignees: '' **Describe the bug** A clear and concise description of what the bug is. -**To Reproduce** -Steps to reproduce the current behavior: +**OpenApi File To Reproduce** +Add the OpenApi file you're using or a link to it as well as the steps to reproduce the current behavior. **Expected behavior** A clear and concise description of what you expected to happen. From 66b81674049371a7fff94b750a7d08fad1a75e27 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 28 Nov 2023 21:44:30 +0000 Subject: [PATCH 06/42] Bump Moq from 4.20.69 to 4.20.70 Bumps [Moq](https://github.com/moq/moq) from 4.20.69 to 4.20.70. - [Release notes](https://github.com/moq/moq/releases) - [Changelog](https://github.com/devlooped/moq/blob/main/CHANGELOG.md) - [Commits](https://github.com/moq/moq/compare/v4.20.69...v4.20.70) --- updated-dependencies: - dependency-name: Moq dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .../Microsoft.OpenApi.Hidi.Tests.csproj | 2 +- test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/Microsoft.OpenApi.Hidi.Tests/Microsoft.OpenApi.Hidi.Tests.csproj b/test/Microsoft.OpenApi.Hidi.Tests/Microsoft.OpenApi.Hidi.Tests.csproj index 8ea740315..4a5dd2ad2 100644 --- a/test/Microsoft.OpenApi.Hidi.Tests/Microsoft.OpenApi.Hidi.Tests.csproj +++ b/test/Microsoft.OpenApi.Hidi.Tests/Microsoft.OpenApi.Hidi.Tests.csproj @@ -13,7 +13,7 @@ - + diff --git a/test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj b/test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj index abaf54ce1..4e9ef5341 100644 --- a/test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj +++ b/test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj @@ -12,7 +12,7 @@ - + From cf2106a52f01d6ff278f99a7c72b935f0dd79b73 Mon Sep 17 00:00:00 2001 From: Maggiekimani1 Date: Wed, 29 Nov 2023 13:04:32 +0300 Subject: [PATCH 07/42] Update public API interface --- .../PublicApi/PublicApi.approved.txt | 39 ++++++++++++------- 1 file changed, 26 insertions(+), 13 deletions(-) diff --git a/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt b/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt index 70695eaa5..d99f6b9b0 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 @@ -1471,9 +1482,9 @@ namespace Microsoft.OpenApi.Writers void Flush(); void WriteEndArray(); void WriteEndObject(); - void WriteJsonSchema(Json.Schema.JsonSchema schema); - void WriteJsonSchemaReference(Microsoft.OpenApi.Writers.IOpenApiWriter writer, System.Uri reference); - void WriteJsonSchemaWithoutReference(Microsoft.OpenApi.Writers.IOpenApiWriter writer, Json.Schema.JsonSchema schema); + void WriteJsonSchema(Json.Schema.JsonSchema schema, Microsoft.OpenApi.OpenApiSpecVersion version); + void WriteJsonSchemaReference(Microsoft.OpenApi.Writers.IOpenApiWriter writer, System.Uri reference, Microsoft.OpenApi.OpenApiSpecVersion version); + void WriteJsonSchemaWithoutReference(Microsoft.OpenApi.Writers.IOpenApiWriter writer, Json.Schema.JsonSchema schema, Microsoft.OpenApi.OpenApiSpecVersion version); void WriteNull(); void WritePropertyName(string name); void WriteRaw(string value); @@ -1534,9 +1545,9 @@ namespace Microsoft.OpenApi.Writers public abstract void WriteEndArray(); public abstract void WriteEndObject(); public virtual void WriteIndentation() { } - public void WriteJsonSchema(Json.Schema.JsonSchema schema) { } - public void WriteJsonSchemaReference(Microsoft.OpenApi.Writers.IOpenApiWriter writer, System.Uri reference) { } - public void WriteJsonSchemaWithoutReference(Microsoft.OpenApi.Writers.IOpenApiWriter writer, Json.Schema.JsonSchema schema) { } + public void WriteJsonSchema(Json.Schema.JsonSchema schema, Microsoft.OpenApi.OpenApiSpecVersion version) { } + public void WriteJsonSchemaReference(Microsoft.OpenApi.Writers.IOpenApiWriter writer, System.Uri referenceUri, Microsoft.OpenApi.OpenApiSpecVersion version) { } + public void WriteJsonSchemaWithoutReference(Microsoft.OpenApi.Writers.IOpenApiWriter writer, Json.Schema.JsonSchema schema, Microsoft.OpenApi.OpenApiSpecVersion version) { } public abstract void WriteNull(); public abstract void WritePropertyName(string name); public abstract void WriteRaw(string value); @@ -1572,6 +1583,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 { } From 10c69a86304ae7e5c80de0642ee273ecd39abef2 Mon Sep 17 00:00:00 2001 From: Maggiekimani1 Date: Wed, 29 Nov 2023 13:10:37 +0300 Subject: [PATCH 08/42] Create Json schema mappings with references in components --- .../ParseNodes/MapNode.cs | 58 +++++++++++++++++++ .../ParseNodes/ParseNode.cs | 11 +++- .../V2/OpenApiDocumentDeserializer.cs | 3 +- .../V3/OpenApiComponentsDeserializer.cs | 2 +- 4 files changed, 70 insertions(+), 4 deletions(-) diff --git a/src/Microsoft.OpenApi.Readers/ParseNodes/MapNode.cs b/src/Microsoft.OpenApi.Readers/ParseNodes/MapNode.cs index f0cdea3fa..071e2c7f1 100644 --- a/src/Microsoft.OpenApi.Readers/ParseNodes/MapNode.cs +++ b/src/Microsoft.OpenApi.Readers/ParseNodes/MapNode.cs @@ -8,6 +8,7 @@ using System.Linq; using System.Text.Json; using System.Text.Json.Nodes; +using Json.Schema; using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Models; @@ -123,6 +124,63 @@ public override Dictionary CreateMapWithReference( return nodes.Where(n => n != default).ToDictionary(k => k.key, v => v.value); } + public override Dictionary CreateJsonSchemaMapWithReference( + ReferenceType referenceType, + Func map, + OpenApiSpecVersion version) + { + var jsonMap = _node ?? throw new OpenApiReaderException($"Expected map while parsing {typeof(JsonSchema).Name}", Context); + + var nodes = jsonMap.Select( + n => + { + var key = n.Key; + (string key, JsonSchema value) entry; + try + { + Context.StartObject(key); + entry = (key, + value: map(new MapNode(Context, (JsonObject)n.Value)) + ); + if (entry.value == null) + { + return default; // Body Parameters shouldn't be converted to Parameters + } + // If the component isn't a reference to another component, then point it to itself. + if (entry.value.GetRef() == null) + { + var builder = new JsonSchemaBuilder(); + + // construct the Ref and append it to the builder + var reference = version == OpenApiSpecVersion.OpenApi2_0 ? string.Concat("#/definitions/", entry.key) : + string.Concat("#/components/schemas/", entry.key); + + builder.Ref(reference); + + // Append all the keywords in original schema to our new schema using a builder instance + foreach (var keyword in entry.value.Keywords) + { + builder.Add(keyword); + } + entry.value = builder.Build(); + //entry.value.GetRef() = new OpenApiReference() + //{ + // Type = referenceType, + // Id = entry.key + //}; + + } + } + finally + { + Context.EndObject(); + } + return entry; + } + ); + return nodes.Where(n => n != default).ToDictionary(k => k.key, v => v.value); + } + public override Dictionary CreateSimpleMap(Func map) { var jsonMap = _node ?? throw new OpenApiReaderException($"Expected map while parsing {typeof(T).Name}", Context); diff --git a/src/Microsoft.OpenApi.Readers/ParseNodes/ParseNode.cs b/src/Microsoft.OpenApi.Readers/ParseNodes/ParseNode.cs index 8c9f992f3..bfdc7f3f0 100644 --- a/src/Microsoft.OpenApi.Readers/ParseNodes/ParseNode.cs +++ b/src/Microsoft.OpenApi.Readers/ParseNodes/ParseNode.cs @@ -1,9 +1,10 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. using System; using System.Collections.Generic; using System.Text.Json.Nodes; +using Json.Schema; using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Models; @@ -66,6 +67,14 @@ public virtual Dictionary CreateMapWithReference( throw new OpenApiReaderException("Cannot create map from this reference.", Context); } + public virtual Dictionary CreateJsonSchemaMapWithReference( + ReferenceType referenceType, + Func map, + OpenApiSpecVersion version) + { + throw new OpenApiReaderException("Cannot create map from this reference.", Context); + } + public virtual List CreateSimpleList(Func map) { throw new OpenApiReaderException("Cannot create simple list from this type of node.", Context); diff --git a/src/Microsoft.OpenApi.Readers/V2/OpenApiDocumentDeserializer.cs b/src/Microsoft.OpenApi.Readers/V2/OpenApiDocumentDeserializer.cs index 9430e5d84..86c14f393 100644 --- a/src/Microsoft.OpenApi.Readers/V2/OpenApiDocumentDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V2/OpenApiDocumentDeserializer.cs @@ -64,8 +64,7 @@ internal static partial class OpenApiV2Deserializer o.Components = new(); } - o.Components.Schemas = n.CreateMap(LoadSchema); - } + o.Components.Schemas = n.CreateJsonSchemaMapWithReference(ReferenceType.Schema, LoadSchema, OpenApiSpecVersion.OpenApi2_0); } }, { "parameters", diff --git a/src/Microsoft.OpenApi.Readers/V3/OpenApiComponentsDeserializer.cs b/src/Microsoft.OpenApi.Readers/V3/OpenApiComponentsDeserializer.cs index b6296064d..53790ac5f 100644 --- a/src/Microsoft.OpenApi.Readers/V3/OpenApiComponentsDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V3/OpenApiComponentsDeserializer.cs @@ -20,7 +20,7 @@ internal static partial class OpenApiV3Deserializer { private static readonly FixedFieldMap _componentsFixedFields = new() { - {"schemas", (o, n) => o.Schemas = n.CreateMap(LoadSchema)}, + {"schemas", (o, n) => o.Schemas = n.CreateJsonSchemaMapWithReference(ReferenceType.Schema, LoadSchema, OpenApiSpecVersion.OpenApi3_0)}, {"responses", (o, n) => o.Responses = n.CreateMapWithReference(ReferenceType.Response, LoadResponse)}, {"parameters", (o, n) => o.Parameters = n.CreateMapWithReference(ReferenceType.Parameter, LoadParameter)}, {"examples", (o, n) => o.Examples = n.CreateMapWithReference(ReferenceType.Example, LoadExample)}, From 1ac5b1bf361de7d8c5b2d31c9413bfa9a22fd446 Mon Sep 17 00:00:00 2001 From: Maggiekimani1 Date: Wed, 29 Nov 2023 13:12:29 +0300 Subject: [PATCH 09/42] Add spec version param for writing out $refs for different versions --- .../Helpers/SchemaSerializerHelper.cs | 9 ++-- .../Models/OpenApiComponents.cs | 12 ++--- .../Models/OpenApiDocument.cs | 6 +-- src/Microsoft.OpenApi/Models/OpenApiHeader.cs | 6 +-- .../Models/OpenApiMediaType.cs | 4 +- .../Models/OpenApiParameter.cs | 8 ++-- .../Models/OpenApiResponse.cs | 8 +--- .../Writers/IOpenApiWriter.cs | 9 ++-- .../Writers/OpenApiWriterBase.cs | 45 ++++++++++++------- 9 files changed, 60 insertions(+), 47 deletions(-) diff --git a/src/Microsoft.OpenApi/Helpers/SchemaSerializerHelper.cs b/src/Microsoft.OpenApi/Helpers/SchemaSerializerHelper.cs index ae0ffd52b..7165eb0e3 100644 --- a/src/Microsoft.OpenApi/Helpers/SchemaSerializerHelper.cs +++ b/src/Microsoft.OpenApi/Helpers/SchemaSerializerHelper.cs @@ -13,7 +13,10 @@ namespace Microsoft.OpenApi.Helpers { internal static class SchemaSerializerHelper { - internal static void WriteAsItemsProperties(JsonSchema schema, IOpenApiWriter writer, IDictionary extensions) + internal static void WriteAsItemsProperties(JsonSchema schema, + IOpenApiWriter writer, + IDictionary extensions, + OpenApiSpecVersion version) { if (writer == null) { @@ -39,7 +42,7 @@ internal static void WriteAsItemsProperties(JsonSchema schema, IOpenApiWriter wr // items writer.WriteOptionalObject(OpenApiConstants.Items, schema.GetItems(), - (w, s) => w.WriteJsonSchema(s)); + (w, s) => w.WriteJsonSchema(s, version)); // collectionFormat // We need information from style in parameter to populate this. @@ -96,7 +99,7 @@ internal static void WriteAsItemsProperties(JsonSchema schema, IOpenApiWriter wr // extensions writer.WriteExtensions(extensions, OpenApiSpecVersion.OpenApi2_0); } - + private static string RetrieveFormatFromNestedSchema(IReadOnlyCollection schema) { if (schema != null) diff --git a/src/Microsoft.OpenApi/Models/OpenApiComponents.cs b/src/Microsoft.OpenApi/Models/OpenApiComponents.cs index 2d96e3327..4af4248ab 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiComponents.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiComponents.cs @@ -109,7 +109,7 @@ public void SerializeAsV31(IOpenApiWriter writer) // however if they have cycles, then we will need a component rendered if (writer.GetSettings().InlineLocalReferences) { - RenderComponents(writer); + RenderComponents(writer, OpenApiSpecVersion.OpenApi3_1); return; } @@ -149,7 +149,7 @@ public void SerializeAsV3(IOpenApiWriter writer) // however if they have cycles, then we will need a component rendered if (writer.GetSettings().InlineLocalReferences) { - RenderComponents(writer); + RenderComponents(writer, OpenApiSpecVersion.OpenApi3_0); return; } @@ -177,11 +177,11 @@ private void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion version if (reference != null && reference.OriginalString.Split('/').Last().Equals(key)) { - w.WriteJsonSchemaWithoutReference(w, s); + w.WriteJsonSchemaWithoutReference(w, s, version); } else { - w.WriteJsonSchema(s); + w.WriteJsonSchema(s, version); } }); @@ -335,7 +335,7 @@ private void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion version writer.WriteEndObject(); } - private void RenderComponents(IOpenApiWriter writer) + private void RenderComponents(IOpenApiWriter writer, OpenApiSpecVersion version) { var loops = writer.GetSettings().LoopDetector.Loops; writer.WriteStartObject(); @@ -344,7 +344,7 @@ private void RenderComponents(IOpenApiWriter writer) writer.WriteOptionalMap( OpenApiConstants.Schemas, Schemas, - static (w, key, s) => { w.WriteJsonSchema(s); }); + (w, key, s) => { w.WriteJsonSchema(s, version); }); } writer.WriteEndObject(); } diff --git a/src/Microsoft.OpenApi/Models/OpenApiDocument.cs b/src/Microsoft.OpenApi/Models/OpenApiDocument.cs index c6e047ce0..f0c341f48 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiDocument.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiDocument.cs @@ -246,7 +246,7 @@ public void SerializeAsV2(IOpenApiWriter writer) writer.WriteOptionalMap( OpenApiConstants.Definitions, openApiSchemas, - (w, key, s) => w.WriteJsonSchema(s)); + (w, key, s) => w.WriteJsonSchema(s, OpenApiSpecVersion.OpenApi2_0)); } } else @@ -265,11 +265,11 @@ public void SerializeAsV2(IOpenApiWriter writer) if (reference != null && reference.OriginalString.Split('/').Last().Equals(key)) { - w.WriteJsonSchemaWithoutReference(w, s); + w.WriteJsonSchemaWithoutReference(w, s, OpenApiSpecVersion.OpenApi2_0); } else { - w.WriteJsonSchema(s); + w.WriteJsonSchema(s, OpenApiSpecVersion.OpenApi2_0); } }); } diff --git a/src/Microsoft.OpenApi/Models/OpenApiHeader.cs b/src/Microsoft.OpenApi/Models/OpenApiHeader.cs index c73d9433d..0b5c8dd92 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiHeader.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiHeader.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; @@ -225,7 +225,7 @@ internal virtual void SerializeInternalWithoutReference(IOpenApiWriter writer, O writer.WriteProperty(OpenApiConstants.AllowReserved, AllowReserved, false); // schema - writer.WriteOptionalObject(OpenApiConstants.Schema, Schema, (w, s) => writer.WriteJsonSchema(s)); + writer.WriteOptionalObject(OpenApiConstants.Schema, Schema, (w, s) => writer.WriteJsonSchema(s, version)); // example writer.WriteOptionalObject(OpenApiConstants.Example, Example, (w, s) => w.WriteAny(s)); @@ -295,7 +295,7 @@ public void SerializeAsV2WithoutReference(IOpenApiWriter writer) writer.WriteProperty(OpenApiConstants.AllowReserved, AllowReserved, false); // schema - SchemaSerializerHelper.WriteAsItemsProperties(Schema, writer, Extensions); + SchemaSerializerHelper.WriteAsItemsProperties(Schema, writer, Extensions, OpenApiSpecVersion.OpenApi2_0); // example writer.WriteOptionalObject(OpenApiConstants.Example, Example, (w, s) => w.WriteAny(s)); diff --git a/src/Microsoft.OpenApi/Models/OpenApiMediaType.cs b/src/Microsoft.OpenApi/Models/OpenApiMediaType.cs index e8aa58986..5d195e264 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiMediaType.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiMediaType.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; @@ -97,7 +97,7 @@ private void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion version writer.WriteStartObject(); // schema - writer.WriteOptionalObject(OpenApiConstants.Schema, Schema, (w, s) => writer.WriteJsonSchema(s)); + writer.WriteOptionalObject(OpenApiConstants.Schema, Schema, (w, s) => writer.WriteJsonSchema(s, version)); // example writer.WriteOptionalObject(OpenApiConstants.Example, Example, (w, e) => w.WriteAny(e)); diff --git a/src/Microsoft.OpenApi/Models/OpenApiParameter.cs b/src/Microsoft.OpenApi/Models/OpenApiParameter.cs index 4fe85f1c0..3afae77e1 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiParameter.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiParameter.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; @@ -291,7 +291,7 @@ internal virtual void SerializeInternalWithoutReference(IOpenApiWriter writer, O if (Schema != null) { writer.WritePropertyName(OpenApiConstants.Schema); - writer.WriteJsonSchema(Schema); + writer.WriteJsonSchema(Schema, version); } // example @@ -371,7 +371,7 @@ public void SerializeAsV2WithoutReference(IOpenApiWriter writer) // schema if (this is OpenApiBodyParameter) { - writer.WriteOptionalObject(OpenApiConstants.Schema, Schema, (w, s) => writer.WriteJsonSchema(s)); + writer.WriteOptionalObject(OpenApiConstants.Schema, Schema, (w, s) => writer.WriteJsonSchema(s, OpenApiSpecVersion.OpenApi2_0)); } // In V2 parameter's type can't be a reference to a custom object schema or can't be of type object // So in that case map the type as string. @@ -400,7 +400,7 @@ public void SerializeAsV2WithoutReference(IOpenApiWriter writer) // multipleOf if (Schema != null) { - SchemaSerializerHelper.WriteAsItemsProperties(Schema, writer, Extensions); + SchemaSerializerHelper.WriteAsItemsProperties(Schema, writer, Extensions, OpenApiSpecVersion.OpenApi2_0); var extensions = Schema.GetExtensions(); if (extensions != null) { diff --git a/src/Microsoft.OpenApi/Models/OpenApiResponse.cs b/src/Microsoft.OpenApi/Models/OpenApiResponse.cs index 447b2fb1d..9aa136a77 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiResponse.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiResponse.cs @@ -1,13 +1,9 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. using System; using System.Collections.Generic; using System.Linq; -using System.Text.Json; -using System.Text.Json.Nodes; -using Json.More; -using Microsoft.OpenApi.Helpers; using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Writers; @@ -215,7 +211,7 @@ public void SerializeAsV2WithoutReference(IOpenApiWriter writer) if (mediatype.Value != null) { // schema - writer.WriteOptionalObject(OpenApiConstants.Schema, mediatype.Value.Schema, (w, s) => writer.WriteJsonSchema(s)); + writer.WriteOptionalObject(OpenApiConstants.Schema, mediatype.Value.Schema, (w, s) => writer.WriteJsonSchema(s, OpenApiSpecVersion.OpenApi2_0)); // examples if (Content.Values.Any(m => m.Example != null)) diff --git a/src/Microsoft.OpenApi/Writers/IOpenApiWriter.cs b/src/Microsoft.OpenApi/Writers/IOpenApiWriter.cs index 0c97f580b..88d4ae686 100644 --- a/src/Microsoft.OpenApi/Writers/IOpenApiWriter.cs +++ b/src/Microsoft.OpenApi/Writers/IOpenApiWriter.cs @@ -76,14 +76,16 @@ public interface IOpenApiWriter /// Write the JsonSchema object /// /// - void WriteJsonSchema(JsonSchema schema); + /// + void WriteJsonSchema(JsonSchema schema, OpenApiSpecVersion version); /// /// Write the JsonSchema object /// /// The IOpenApiWriter object /// The JsonSchema object - void WriteJsonSchemaWithoutReference(IOpenApiWriter writer, JsonSchema schema); + /// + void WriteJsonSchemaWithoutReference(IOpenApiWriter writer, JsonSchema schema, OpenApiSpecVersion version); /// /// Flush the writer. @@ -95,6 +97,7 @@ public interface IOpenApiWriter /// /// /// - void WriteJsonSchemaReference(IOpenApiWriter writer, Uri reference); + /// + void WriteJsonSchemaReference(IOpenApiWriter writer, Uri reference, OpenApiSpecVersion version); } } diff --git a/src/Microsoft.OpenApi/Writers/OpenApiWriterBase.cs b/src/Microsoft.OpenApi/Writers/OpenApiWriterBase.cs index 335cba7af..e6b032793 100644 --- a/src/Microsoft.OpenApi/Writers/OpenApiWriterBase.cs +++ b/src/Microsoft.OpenApi/Writers/OpenApiWriterBase.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; @@ -422,19 +422,20 @@ protected void VerifyCanWritePropertyName(string name) /// Writes out a JsonSchema object /// /// - public void WriteJsonSchema(JsonSchema schema) + /// + public void WriteJsonSchema(JsonSchema schema, OpenApiSpecVersion version) { if (schema == null) { return; } - + var reference = schema.GetRef(); if (reference != null) { if (!Settings.ShouldInlineReference()) { - WriteJsonSchemaReference(this, reference); + WriteJsonSchemaReference(this, reference, version); return; } else @@ -446,13 +447,13 @@ public void WriteJsonSchema(JsonSchema schema) if (!Settings.LoopDetector.PushLoop(schema)) { Settings.LoopDetector.SaveLoop(schema); - WriteJsonSchemaReference(this, reference); + WriteJsonSchemaReference(this, reference, version); return; } } } - WriteJsonSchemaWithoutReference(this, schema); + WriteJsonSchemaWithoutReference(this, schema, version); if (reference != null) { @@ -461,7 +462,7 @@ public void WriteJsonSchema(JsonSchema schema) } /// - public void WriteJsonSchemaWithoutReference(IOpenApiWriter writer, JsonSchema schema) + public void WriteJsonSchemaWithoutReference(IOpenApiWriter writer, JsonSchema schema, OpenApiSpecVersion version) { writer.WriteStartObject(); @@ -517,23 +518,23 @@ public void WriteJsonSchemaWithoutReference(IOpenApiWriter writer, JsonSchema sc writer.WriteProperty(OpenApiConstants.Type, schema.GetJsonType()?.ToString().ToLowerInvariant()); // allOf - writer.WriteOptionalCollection(OpenApiConstants.AllOf, schema.GetAllOf(), (w, s) => w.WriteJsonSchema(s)); + writer.WriteOptionalCollection(OpenApiConstants.AllOf, schema.GetAllOf(), (w, s) => w.WriteJsonSchema(s, version)); // anyOf - writer.WriteOptionalCollection(OpenApiConstants.AnyOf, schema.GetAnyOf(), (w, s) => w.WriteJsonSchema(s)); + writer.WriteOptionalCollection(OpenApiConstants.AnyOf, schema.GetAnyOf(), (w, s) => w.WriteJsonSchema(s, version)); // oneOf - writer.WriteOptionalCollection(OpenApiConstants.OneOf, schema.GetOneOf(), (w, s) => w.WriteJsonSchema(s)); + writer.WriteOptionalCollection(OpenApiConstants.OneOf, schema.GetOneOf(), (w, s) => w.WriteJsonSchema(s, version)); // not - writer.WriteOptionalObject(OpenApiConstants.Not, schema.GetNot(), (w, s) => w.WriteJsonSchema(s)); + writer.WriteOptionalObject(OpenApiConstants.Not, schema.GetNot(), (w, s) => w.WriteJsonSchema(s, version)); // items - writer.WriteOptionalObject(OpenApiConstants.Items, schema.GetItems(), (w, s) => w.WriteJsonSchema(s)); + writer.WriteOptionalObject(OpenApiConstants.Items, schema.GetItems(), (w, s) => w.WriteJsonSchema(s, version)); // properties writer.WriteOptionalMap(OpenApiConstants.Properties, (IDictionary)schema.GetProperties(), - (w, key, s) => w.WriteJsonSchema(s)); + (w, key, s) => w.WriteJsonSchema(s, version)); // additionalProperties if (schema.GetAdditionalPropertiesAllowed() ?? false) @@ -541,7 +542,7 @@ public void WriteJsonSchemaWithoutReference(IOpenApiWriter writer, JsonSchema sc writer.WriteOptionalObject( OpenApiConstants.AdditionalProperties, schema.GetAdditionalProperties(), - (w, s) => w.WriteJsonSchema(s)); + (w, s) => w.WriteJsonSchema(s, version)); } else { @@ -588,10 +589,20 @@ public void WriteJsonSchemaWithoutReference(IOpenApiWriter writer, JsonSchema sc } /// - public void WriteJsonSchemaReference(IOpenApiWriter writer, Uri reference) + public void WriteJsonSchemaReference(IOpenApiWriter writer, Uri referenceUri, OpenApiSpecVersion version) { - this.WriteStartObject(); - this.WriteProperty(OpenApiConstants.DollarRef, reference.OriginalString); + var reference = String.Empty; + if (version.Equals(OpenApiSpecVersion.OpenApi2_0)) + { + reference = referenceUri.OriginalString.Replace("components/schemas", "definitions"); + } + else + { + reference = referenceUri.OriginalString; + } + + WriteStartObject(); + this.WriteProperty(OpenApiConstants.DollarRef, reference); WriteEndObject(); } } From 9ed4887b7af391a36cfb2537d599c96d6197534d Mon Sep 17 00:00:00 2001 From: Maggiekimani1 Date: Wed, 29 Nov 2023 13:13:55 +0300 Subject: [PATCH 10/42] Clean up code and tests --- .../V3/JsonSchemaDeserializer.cs | 2 -- .../Extensions/JsonSchemaBuilderExtensions.cs | 22 ----------------- .../Extensions/JsonSchemaExtensions.cs | 2 +- .../Services/OpenApiReferenceResolver.cs | 12 +++++----- .../Validations/ValidationRule.cs | 3 +-- .../Services/OpenApiServiceTests.cs | 18 -------------- .../TryLoadReferenceV2Tests.cs | 3 ++- .../V2Tests/OpenApiDocumentTests.cs | 11 ++++----- .../OpenApiDocument/docWithExample.yaml | 12 ---------- ...tWithSummaryAndDescriptionInReference.yaml | 5 ++-- .../V3Tests/JsonSchemaTests.cs | 5 ++++ .../V3Tests/OpenApiDocumentTests.cs | 13 +++++++--- .../OpenApiDocument/docWithJsonSchema.yaml | 2 +- ...orks_produceTerseOutput=False.verified.txt | 24 +++++++++---------- ...Works_produceTerseOutput=True.verified.txt | 2 +- ...orks_produceTerseOutput=False.verified.txt | 10 +------- ...Works_produceTerseOutput=True.verified.txt | 2 +- ...orks_produceTerseOutput=False.verified.txt | 10 +------- ...Works_produceTerseOutput=True.verified.txt | 2 +- .../OpenApiRequestBodyReferenceTests.cs | 1 + .../Validations/ValidationRuleSetTests.cs | 6 ++--- 21 files changed, 54 insertions(+), 113 deletions(-) diff --git a/src/Microsoft.OpenApi.Readers/V3/JsonSchemaDeserializer.cs b/src/Microsoft.OpenApi.Readers/V3/JsonSchemaDeserializer.cs index 4f5796155..2621d3729 100644 --- a/src/Microsoft.OpenApi.Readers/V3/JsonSchemaDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V3/JsonSchemaDeserializer.cs @@ -1,13 +1,11 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. -using System; using System.Collections.Generic; using System.Globalization; using System.Text.Json.Nodes; using Json.Schema; using Json.Schema.OpenApi; -using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Extensions; diff --git a/src/Microsoft.OpenApi/Extensions/JsonSchemaBuilderExtensions.cs b/src/Microsoft.OpenApi/Extensions/JsonSchemaBuilderExtensions.cs index 92738f66c..e8d3a95c0 100644 --- a/src/Microsoft.OpenApi/Extensions/JsonSchemaBuilderExtensions.cs +++ b/src/Microsoft.OpenApi/Extensions/JsonSchemaBuilderExtensions.cs @@ -15,8 +15,6 @@ namespace Microsoft.OpenApi.Extensions /// public static class JsonSchemaBuilderExtensions { - private static readonly Dictionary _keywords = new Dictionary(); - /// /// Custom extensions in the schema /// @@ -114,25 +112,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 +134,6 @@ public static JsonSchemaBuilder Remove(this JsonSchemaBuilder builder, string ke } } - //_keywords.Remove(keyword); return schemaBuilder; } } diff --git a/src/Microsoft.OpenApi/Extensions/JsonSchemaExtensions.cs b/src/Microsoft.OpenApi/Extensions/JsonSchemaExtensions.cs index 1e70021de..6c0545fc3 100644 --- a/src/Microsoft.OpenApi/Extensions/JsonSchemaExtensions.cs +++ b/src/Microsoft.OpenApi/Extensions/JsonSchemaExtensions.cs @@ -84,6 +84,6 @@ public static string GetSummary(this JsonSchema schema) public static IDictionary GetExtensions(this JsonSchema schema) { return schema.TryGetKeyword(ExtensionsKeyword.Name, out var k) ? k.Extensions! : null; - } + } } } diff --git a/src/Microsoft.OpenApi/Services/OpenApiReferenceResolver.cs b/src/Microsoft.OpenApi/Services/OpenApiReferenceResolver.cs index d9849dd44..5e1f86889 100644 --- a/src/Microsoft.OpenApi/Services/OpenApiReferenceResolver.cs +++ b/src/Microsoft.OpenApi/Services/OpenApiReferenceResolver.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; @@ -189,7 +189,7 @@ public override void Visit(IDictionary links) { ResolveMap(links); } - + /// /// Resolve all references used in a schem /// @@ -200,17 +200,17 @@ public override void Visit(ref JsonSchema schema) var description = schema.GetDescription(); var summary = schema.GetSummary(); - if (reference != null) + if (schema.Keywords.Count.Equals(1) && reference != null) { schema = ResolveJsonSchemaReference(reference, description, summary); } - + var builder = new JsonSchemaBuilder(); - foreach (var keyword in schema.Keywords) + foreach (var keyword in schema?.Keywords) { builder.Add(keyword); } - + ResolveJsonSchema(schema.GetItems(), r => builder.Items(r)); ResolveJsonSchemaList((IList)schema.GetOneOf(), r => builder.OneOf(r)); ResolveJsonSchemaList((IList)schema.GetAllOf(), r => builder.AllOf(r)); diff --git a/src/Microsoft.OpenApi/Validations/ValidationRule.cs b/src/Microsoft.OpenApi/Validations/ValidationRule.cs index c59e0fc74..aa866734a 100644 --- a/src/Microsoft.OpenApi/Validations/ValidationRule.cs +++ b/src/Microsoft.OpenApi/Validations/ValidationRule.cs @@ -1,8 +1,7 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. using System; -using System.Globalization; using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Properties; diff --git a/test/Microsoft.OpenApi.Hidi.Tests/Services/OpenApiServiceTests.cs b/test/Microsoft.OpenApi.Hidi.Tests/Services/OpenApiServiceTests.cs index 56063130f..b06e38d3f 100644 --- a/test/Microsoft.OpenApi.Hidi.Tests/Services/OpenApiServiceTests.cs +++ b/test/Microsoft.OpenApi.Hidi.Tests/Services/OpenApiServiceTests.cs @@ -186,24 +186,6 @@ public async Task TransformCommandConvertsOpenApiWithDefaultOutputName() Assert.NotEmpty(output); } - [Fact] - public async Task TransformCommandConvertsCsdlWithDefaultOutputName() - { - var options = new HidiOptions - { - Csdl = Path.Combine("UtilityFiles", "Todo.xml"), - CleanOutput = true, - TerseOutput = false, - InlineLocal = false, - InlineExternal = false, - }; - // create a dummy ILogger instance for testing - await OpenApiService.TransformOpenApiDocument(options, _logger); - - var output = await File.ReadAllTextAsync("output.yml"); - Assert.NotEmpty(output); - } - [Fact] public async Task TransformCommandConvertsOpenApiWithDefaultOutputNameAndSwitchFormat() { diff --git a/test/Microsoft.OpenApi.Readers.Tests/ReferenceService/TryLoadReferenceV2Tests.cs b/test/Microsoft.OpenApi.Readers.Tests/ReferenceService/TryLoadReferenceV2Tests.cs index dcf9c5d43..d9d4e0eb3 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/ReferenceService/TryLoadReferenceV2Tests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/ReferenceService/TryLoadReferenceV2Tests.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; @@ -159,6 +159,7 @@ public void LoadResponseAndSchemaReference() ["application/json"] = new() { Schema = new JsonSchemaBuilder() + .Ref("#/definitions/SampleObject2") .Description("Sample description") .Required("name") .Properties( diff --git a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiDocumentTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiDocumentTests.cs index 2abac53ff..692cd31fa 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiDocumentTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiDocumentTests.cs @@ -22,15 +22,12 @@ public void ShouldParseProducesInAnyOrder() var reader = new OpenApiStreamReader(); var doc = reader.Read(stream, out var diagnostic); - var successSchema = new JsonSchemaBuilder() - .Type(SchemaValueType.Array) - .Items(new JsonSchemaBuilder() - .Ref("#/definitions/Item")); - var okSchema = new JsonSchemaBuilder() + .Ref("#/definitions/Item") .Properties(("id", new JsonSchemaBuilder().Type(SchemaValueType.String).Description("Item identifier."))); var errorSchema = new JsonSchemaBuilder() + .Ref("#/definitions/Error") .Properties(("code", new JsonSchemaBuilder().Type(SchemaValueType.Integer).Format("int32")), ("message", new JsonSchemaBuilder().Type(SchemaValueType.String)), ("fields", new JsonSchemaBuilder().Type(SchemaValueType.String))); @@ -165,10 +162,12 @@ public void ShouldAssignSchemaToAllResponses() var successSchema = new JsonSchemaBuilder() .Type(SchemaValueType.Array) .Items(new JsonSchemaBuilder() + .Ref("#/definitions/Item") .Properties(("id", new JsonSchemaBuilder().Type(SchemaValueType.String).Description("Item identifier.")))) .Build(); var errorSchema = new JsonSchemaBuilder() + .Ref("#/definitions/Error") .Properties(("code", new JsonSchemaBuilder().Type(SchemaValueType.Integer).Format("int32")), ("message", new JsonSchemaBuilder().Type(SchemaValueType.String)), ("fields", new JsonSchemaBuilder().Type(SchemaValueType.String))) @@ -201,7 +200,7 @@ public void ShouldAllowComponentsThatJustContainAReference() JsonSchema schema = doc.Components.Schemas["AllPets"]; // Assert - if (schema.GetRef() != null) + if (schema.Keywords.Count.Equals(1) && schema.GetRef() != null) { // detected a cycle - this code gets triggered Assert.Fail("A cycle should not be detected"); diff --git a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiDocument/docWithExample.yaml b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiDocument/docWithExample.yaml index 51ffd38b3..4f667a537 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiDocument/docWithExample.yaml +++ b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiDocument/docWithExample.yaml @@ -64,18 +64,6 @@ paths: # The available paths and operations for the API example: File uploaded successfully components: # Reusable components for the API schemas: # JSON Schema definitions for the API - User: # A schema for a user object - $id: http://example.com/schemas/user # The identifier for the schema - type: object - properties: - name: # A property for the user name - type: string - default: "John Doe" # The default value for the user name - age: # A property for the user age - type: integer - minimum: 0 - default: 18 # The default value for the user age - unevaluatedProperties: false # No additional properties are allowed Pet: # A schema for a pet object type: object required: diff --git a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiDocument/documentWithSummaryAndDescriptionInReference.yaml b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiDocument/documentWithSummaryAndDescriptionInReference.yaml index 0d061203d..37a05f101 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiDocument/documentWithSummaryAndDescriptionInReference.yaml +++ b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiDocument/documentWithSummaryAndDescriptionInReference.yaml @@ -13,17 +13,16 @@ paths: application/json: schema: "$ref": '#/components/schemas/pet' - summary: A pet - description: A pet in a petstore components: headers: X-Test: description: Test + summary: An X-Test header schema: type: string responses: Test: - description: Test Repsonse + description: Test Response headers: X-Test: $ref: '#/components/headers/X-Test' diff --git a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/JsonSchemaTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/JsonSchemaTests.cs index 7d81a8601..56b25a300 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/JsonSchemaTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/JsonSchemaTests.cs @@ -230,14 +230,17 @@ public void ParseBasicSchemaWithReferenceShouldSucceed() Schemas = { ["ErrorModel"] = new JsonSchemaBuilder() + .Ref("#/components/schemas/ErrorModel") .Type(SchemaValueType.Object) .Required("message", "code") .Properties( ("message", new JsonSchemaBuilder().Type(SchemaValueType.String)), ("code", new JsonSchemaBuilder().Type(SchemaValueType.Integer).Minimum(100).Maximum(600))), ["ExtendedErrorModel"] = new JsonSchemaBuilder() + .Ref("#/components/schemas/ExtendedErrorModel") .AllOf( new JsonSchemaBuilder() + .Ref("#/components/schemas/ExtendedErrorModel") .Type(SchemaValueType.Object) .Properties( ("code", new JsonSchemaBuilder().Type(SchemaValueType.Integer).Minimum(100).Maximum(600)), @@ -280,6 +283,7 @@ public void ParseAdvancedSchemaWithReferenceShouldSucceed() .Description("A representation of a cat") .AllOf( new JsonSchemaBuilder() + .Ref("#/components/schemas/Pet1") .Type(SchemaValueType.Object) .Discriminator(new OpenApiDiscriminator { PropertyName = "petType" }) .Properties( @@ -306,6 +310,7 @@ public void ParseAdvancedSchemaWithReferenceShouldSucceed() .Description("A representation of a dog") .AllOf( new JsonSchemaBuilder() + .Ref("#/components/schemas/Pet1") .Type(SchemaValueType.Object) .Discriminator(new OpenApiDiscriminator { PropertyName = "petType" }) .Properties( diff --git a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiDocumentTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiDocumentTests.cs index 71b7a7d74..aec2e9101 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiDocumentTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiDocumentTests.cs @@ -217,13 +217,14 @@ public void ParseStandardPetStoreDocumentShouldSucceed() OpenApiDiagnostic context; using (var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "petStore.yaml"))) { - var actual = new OpenApiStreamReader().Read(stream, out context); + var doc = new OpenApiStreamReader().Read(stream, out context); var components = new OpenApiComponents { Schemas = new Dictionary { ["pet"] = new JsonSchemaBuilder() + .Ref("#/components/schemas/pet") .Type(SchemaValueType.Object) .Required("id", "name") .Properties( @@ -231,6 +232,7 @@ public void ParseStandardPetStoreDocumentShouldSucceed() ("name", new JsonSchemaBuilder().Type(SchemaValueType.String)), ("tag", new JsonSchemaBuilder().Type(SchemaValueType.String))), ["newPet"] = new JsonSchemaBuilder() + .Ref("#/components/schemas/newPet") .Type(SchemaValueType.Object) .Required("name") .Properties( @@ -238,6 +240,7 @@ public void ParseStandardPetStoreDocumentShouldSucceed() ("name", new JsonSchemaBuilder().Type(SchemaValueType.String)), ("tag", new JsonSchemaBuilder().Type(SchemaValueType.String))), ["errorModel"] = new JsonSchemaBuilder() + .Ref("#/components/schemas/errorModel") .Type(SchemaValueType.Object) .Required("code", "message") .Properties( @@ -252,7 +255,7 @@ public void ParseStandardPetStoreDocumentShouldSucceed() var errorModelSchema = components.Schemas["errorModel"]; - var expected = new OpenApiDocument + var expectedDoc = new OpenApiDocument { Info = new OpenApiInfo { @@ -519,7 +522,7 @@ public void ParseStandardPetStoreDocumentShouldSucceed() Components = components }; - actual.Should().BeEquivalentTo(expected); + doc.Should().BeEquivalentTo(expectedDoc); } context.Should().BeEquivalentTo( @@ -539,6 +542,7 @@ public void ParseModifiedPetStoreDocumentWithTagAndSecurityShouldSucceed() Schemas = new Dictionary { ["pet1"] = new JsonSchemaBuilder() + .Ref("#/components/schemas/pet1") .Type(SchemaValueType.Object) .Required("id", "name") .Properties( @@ -546,6 +550,7 @@ public void ParseModifiedPetStoreDocumentWithTagAndSecurityShouldSucceed() ("name", new JsonSchemaBuilder().Type(SchemaValueType.String)), ("tag", new JsonSchemaBuilder().Type(SchemaValueType.String))), ["newPet"] = new JsonSchemaBuilder() + .Ref("#/components/schemas/newPet") .Type(SchemaValueType.Object) .Required("name") .Properties( @@ -553,6 +558,7 @@ public void ParseModifiedPetStoreDocumentWithTagAndSecurityShouldSucceed() ("name", new JsonSchemaBuilder().Type(SchemaValueType.String)), ("tag", new JsonSchemaBuilder().Type(SchemaValueType.String))), ["errorModel"] = new JsonSchemaBuilder() + .Ref("#/components/schemas/errorModel") .Type(SchemaValueType.Object) .Required("code", "message") .Properties( @@ -1090,6 +1096,7 @@ public void ParseDocumentWithJsonSchemaReferencesWorks() var actualSchema = doc.Paths["/users/{userId}"].Operations[OperationType.Get].Responses["200"].Content["application/json"].Schema; var expectedSchema = new JsonSchemaBuilder() + .Ref("#/components/schemas/User") .Type(SchemaValueType.Object) .Properties( ("id", new JsonSchemaBuilder().Type(SchemaValueType.Integer)), diff --git a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/Samples/OpenApiDocument/docWithJsonSchema.yaml b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/Samples/OpenApiDocument/docWithJsonSchema.yaml index b26947dc4..984e5ce2b 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/Samples/OpenApiDocument/docWithJsonSchema.yaml +++ b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/Samples/OpenApiDocument/docWithJsonSchema.yaml @@ -1,4 +1,4 @@ -openapi: 3.1.0 +openapi: '3.0.1' info: title: Sample API with Schema Reference version: 1.0.0 diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.SerializeAdvancedDocumentWithReferenceAsV2JsonWorks_produceTerseOutput=False.verified.txt b/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.SerializeAdvancedDocumentWithReferenceAsV2JsonWorks_produceTerseOutput=False.verified.txt index 6f4d12e71..06e0f2ca9 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.SerializeAdvancedDocumentWithReferenceAsV2JsonWorks_produceTerseOutput=False.verified.txt +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.SerializeAdvancedDocumentWithReferenceAsV2JsonWorks_produceTerseOutput=False.verified.txt @@ -55,20 +55,20 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/pet" + "$ref": "#/definitions/pet" } } }, "4XX": { "description": "unexpected client error", "schema": { - "$ref": "#/components/schemas/errorModel" + "$ref": "#/definitions/errorModel" } }, "5XX": { "description": "unexpected server error", "schema": { - "$ref": "#/components/schemas/errorModel" + "$ref": "#/definitions/errorModel" } } } @@ -90,7 +90,7 @@ "description": "Pet to add to the store", "required": true, "schema": { - "$ref": "#/components/schemas/newPet" + "$ref": "#/definitions/newPet" } } ], @@ -98,19 +98,19 @@ "200": { "description": "pet response", "schema": { - "$ref": "#/components/schemas/pet" + "$ref": "#/definitions/pet" } }, "4XX": { "description": "unexpected client error", "schema": { - "$ref": "#/components/schemas/errorModel" + "$ref": "#/definitions/errorModel" } }, "5XX": { "description": "unexpected server error", "schema": { - "$ref": "#/components/schemas/errorModel" + "$ref": "#/definitions/errorModel" } } } @@ -139,19 +139,19 @@ "200": { "description": "pet response", "schema": { - "$ref": "#/components/schemas/pet" + "$ref": "#/definitions/pet" } }, "4XX": { "description": "unexpected client error", "schema": { - "$ref": "#/components/schemas/errorModel" + "$ref": "#/definitions/errorModel" } }, "5XX": { "description": "unexpected server error", "schema": { - "$ref": "#/components/schemas/errorModel" + "$ref": "#/definitions/errorModel" } } } @@ -179,13 +179,13 @@ "4XX": { "description": "unexpected client error", "schema": { - "$ref": "#/components/schemas/errorModel" + "$ref": "#/definitions/errorModel" } }, "5XX": { "description": "unexpected server error", "schema": { - "$ref": "#/components/schemas/errorModel" + "$ref": "#/definitions/errorModel" } } } diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.SerializeAdvancedDocumentWithReferenceAsV2JsonWorks_produceTerseOutput=True.verified.txt b/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.SerializeAdvancedDocumentWithReferenceAsV2JsonWorks_produceTerseOutput=True.verified.txt index ce5390739..ae1db5447 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.SerializeAdvancedDocumentWithReferenceAsV2JsonWorks_produceTerseOutput=True.verified.txt +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.SerializeAdvancedDocumentWithReferenceAsV2JsonWorks_produceTerseOutput=True.verified.txt @@ -1 +1 @@ -{"swagger":"2.0","info":{"title":"Swagger Petstore (Simple)","description":"A sample API that uses a petstore as an example to demonstrate features in the swagger-2.0 specification","termsOfService":"http://helloreverb.com/terms/","contact":{"name":"Swagger API team","url":"http://swagger.io","email":"foo@example.com"},"license":{"name":"MIT","url":"http://opensource.org/licenses/MIT"},"version":"1.0.0"},"host":"petstore.swagger.io","basePath":"/api","schemes":["http"],"paths":{"/pets":{"get":{"description":"Returns all pets from the system that the user has access to","operationId":"findPets","produces":["application/json","application/xml","text/html"],"parameters":[{"in":"query","name":"tags","description":"tags to filter by","type":"array","items":{"type":"string"},"collectionFormat":"multi"},{"in":"query","name":"limit","description":"maximum number of results to return","type":"integer","format":"int32"}],"responses":{"200":{"description":"pet response","schema":{"type":"array","items":{"$ref":"#/components/schemas/pet"}}},"4XX":{"description":"unexpected client error","schema":{"$ref":"#/components/schemas/errorModel"}},"5XX":{"description":"unexpected server error","schema":{"$ref":"#/components/schemas/errorModel"}}}},"post":{"description":"Creates a new pet in the store. Duplicates are allowed","operationId":"addPet","consumes":["application/json"],"produces":["application/json","text/html"],"parameters":[{"in":"body","name":"body","description":"Pet to add to the store","required":true,"schema":{"$ref":"#/components/schemas/newPet"}}],"responses":{"200":{"description":"pet response","schema":{"$ref":"#/components/schemas/pet"}},"4XX":{"description":"unexpected client error","schema":{"$ref":"#/components/schemas/errorModel"}},"5XX":{"description":"unexpected server error","schema":{"$ref":"#/components/schemas/errorModel"}}}}},"/pets/{id}":{"get":{"description":"Returns a user based on a single ID, if the user does not have access to the pet","operationId":"findPetById","produces":["application/json","application/xml","text/html"],"parameters":[{"in":"path","name":"id","description":"ID of pet to fetch","required":true,"type":"integer","format":"int64"}],"responses":{"200":{"description":"pet response","schema":{"$ref":"#/components/schemas/pet"}},"4XX":{"description":"unexpected client error","schema":{"$ref":"#/components/schemas/errorModel"}},"5XX":{"description":"unexpected server error","schema":{"$ref":"#/components/schemas/errorModel"}}}},"delete":{"description":"deletes a single pet based on the ID supplied","operationId":"deletePet","produces":["text/html"],"parameters":[{"in":"path","name":"id","description":"ID of pet to delete","required":true,"type":"integer","format":"int64"}],"responses":{"204":{"description":"pet deleted"},"4XX":{"description":"unexpected client error","schema":{"$ref":"#/components/schemas/errorModel"}},"5XX":{"description":"unexpected server error","schema":{"$ref":"#/components/schemas/errorModel"}}}}}},"definitions":{"pet":{"required":["id","name"],"type":"object","properties":{"id":{"type":"integer","format":"int64"},"name":{"type":"string"},"tag":{"type":"string"}}},"newPet":{"required":["name"],"type":"object","properties":{"id":{"type":"integer","format":"int64"},"name":{"type":"string"},"tag":{"type":"string"}}},"errorModel":{"required":["code","message"],"type":"object","properties":{"code":{"type":"integer","format":"int32"},"message":{"type":"string"}}}}} \ No newline at end of file +{"swagger":"2.0","info":{"title":"Swagger Petstore (Simple)","description":"A sample API that uses a petstore as an example to demonstrate features in the swagger-2.0 specification","termsOfService":"http://helloreverb.com/terms/","contact":{"name":"Swagger API team","url":"http://swagger.io","email":"foo@example.com"},"license":{"name":"MIT","url":"http://opensource.org/licenses/MIT"},"version":"1.0.0"},"host":"petstore.swagger.io","basePath":"/api","schemes":["http"],"paths":{"/pets":{"get":{"description":"Returns all pets from the system that the user has access to","operationId":"findPets","produces":["application/json","application/xml","text/html"],"parameters":[{"in":"query","name":"tags","description":"tags to filter by","type":"array","items":{"type":"string"},"collectionFormat":"multi"},{"in":"query","name":"limit","description":"maximum number of results to return","type":"integer","format":"int32"}],"responses":{"200":{"description":"pet response","schema":{"type":"array","items":{"$ref":"#/definitions/pet"}}},"4XX":{"description":"unexpected client error","schema":{"$ref":"#/definitions/errorModel"}},"5XX":{"description":"unexpected server error","schema":{"$ref":"#/definitions/errorModel"}}}},"post":{"description":"Creates a new pet in the store. Duplicates are allowed","operationId":"addPet","consumes":["application/json"],"produces":["application/json","text/html"],"parameters":[{"in":"body","name":"body","description":"Pet to add to the store","required":true,"schema":{"$ref":"#/definitions/newPet"}}],"responses":{"200":{"description":"pet response","schema":{"$ref":"#/definitions/pet"}},"4XX":{"description":"unexpected client error","schema":{"$ref":"#/definitions/errorModel"}},"5XX":{"description":"unexpected server error","schema":{"$ref":"#/definitions/errorModel"}}}}},"/pets/{id}":{"get":{"description":"Returns a user based on a single ID, if the user does not have access to the pet","operationId":"findPetById","produces":["application/json","application/xml","text/html"],"parameters":[{"in":"path","name":"id","description":"ID of pet to fetch","required":true,"type":"integer","format":"int64"}],"responses":{"200":{"description":"pet response","schema":{"$ref":"#/definitions/pet"}},"4XX":{"description":"unexpected client error","schema":{"$ref":"#/definitions/errorModel"}},"5XX":{"description":"unexpected server error","schema":{"$ref":"#/definitions/errorModel"}}}},"delete":{"description":"deletes a single pet based on the ID supplied","operationId":"deletePet","produces":["text/html"],"parameters":[{"in":"path","name":"id","description":"ID of pet to delete","required":true,"type":"integer","format":"int64"}],"responses":{"204":{"description":"pet deleted"},"4XX":{"description":"unexpected client error","schema":{"$ref":"#/definitions/errorModel"}},"5XX":{"description":"unexpected server error","schema":{"$ref":"#/definitions/errorModel"}}}}}},"definitions":{"pet":{"required":["id","name"],"type":"object","properties":{"id":{"type":"integer","format":"int64"},"name":{"type":"string"},"tag":{"type":"string"}}},"newPet":{"required":["name"],"type":"object","properties":{"id":{"type":"integer","format":"int64"},"name":{"type":"string"},"tag":{"type":"string"}}},"errorModel":{"required":["code","message"],"type":"object","properties":{"code":{"type":"integer","format":"int32"},"message":{"type":"string"}}}}} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiRequestBodyReferenceTests.SerializeRequestBodyReferenceAsV31JsonWorks_produceTerseOutput=False.verified.txt b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiRequestBodyReferenceTests.SerializeRequestBodyReferenceAsV31JsonWorks_produceTerseOutput=False.verified.txt index c4d9bef00..cdbbe00d1 100644 --- a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiRequestBodyReferenceTests.SerializeRequestBodyReferenceAsV31JsonWorks_produceTerseOutput=False.verified.txt +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiRequestBodyReferenceTests.SerializeRequestBodyReferenceAsV31JsonWorks_produceTerseOutput=False.verified.txt @@ -3,15 +3,7 @@ "content": { "application/json": { "schema": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "email": { - "type": "string" - } - } + "$ref": "#/components/schemas/UserSchema" } } } diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiRequestBodyReferenceTests.SerializeRequestBodyReferenceAsV31JsonWorks_produceTerseOutput=True.verified.txt b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiRequestBodyReferenceTests.SerializeRequestBodyReferenceAsV31JsonWorks_produceTerseOutput=True.verified.txt index 3d91acf86..e82312f67 100644 --- a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiRequestBodyReferenceTests.SerializeRequestBodyReferenceAsV31JsonWorks_produceTerseOutput=True.verified.txt +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiRequestBodyReferenceTests.SerializeRequestBodyReferenceAsV31JsonWorks_produceTerseOutput=True.verified.txt @@ -1 +1 @@ -{"description":"User creation request body","content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"email":{"type":"string"}}}}}} \ No newline at end of file +{"description":"User creation request body","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserSchema"}}}} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiRequestBodyReferenceTests.SerializeRequestBodyReferenceAsV3JsonWorks_produceTerseOutput=False.verified.txt b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiRequestBodyReferenceTests.SerializeRequestBodyReferenceAsV3JsonWorks_produceTerseOutput=False.verified.txt index c4d9bef00..cdbbe00d1 100644 --- a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiRequestBodyReferenceTests.SerializeRequestBodyReferenceAsV3JsonWorks_produceTerseOutput=False.verified.txt +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiRequestBodyReferenceTests.SerializeRequestBodyReferenceAsV3JsonWorks_produceTerseOutput=False.verified.txt @@ -3,15 +3,7 @@ "content": { "application/json": { "schema": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "email": { - "type": "string" - } - } + "$ref": "#/components/schemas/UserSchema" } } } diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiRequestBodyReferenceTests.SerializeRequestBodyReferenceAsV3JsonWorks_produceTerseOutput=True.verified.txt b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiRequestBodyReferenceTests.SerializeRequestBodyReferenceAsV3JsonWorks_produceTerseOutput=True.verified.txt index 3d91acf86..e82312f67 100644 --- a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiRequestBodyReferenceTests.SerializeRequestBodyReferenceAsV3JsonWorks_produceTerseOutput=True.verified.txt +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiRequestBodyReferenceTests.SerializeRequestBodyReferenceAsV3JsonWorks_produceTerseOutput=True.verified.txt @@ -1 +1 @@ -{"description":"User creation request body","content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"email":{"type":"string"}}}}}} \ No newline at end of file +{"description":"User creation request body","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserSchema"}}}} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiRequestBodyReferenceTests.cs b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiRequestBodyReferenceTests.cs index 53fa179ea..edfb81e09 100644 --- a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiRequestBodyReferenceTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiRequestBodyReferenceTests.cs @@ -101,6 +101,7 @@ public void RequestBodyReferenceResolutionWorks() { // Assert var expectedSchema = new JsonSchemaBuilder() + .Ref("#/components/schemas/UserSchema") .Type(SchemaValueType.Object) .Properties( ("name", new JsonSchemaBuilder().Type(SchemaValueType.String)), 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 6c88daa3ef0ec5a26b5edce5895c56a43f60cb1e Mon Sep 17 00:00:00 2001 From: Maggiekimani1 Date: Wed, 29 Nov 2023 14:50:16 +0300 Subject: [PATCH 11/42] Remove dependency on external lib that uses YamlDotNet to reduce ambiguity --- src/Microsoft.OpenApi.Readers/Microsoft.OpenApi.Readers.csproj | 3 +-- .../Microsoft.OpenApi.Readers.Tests.csproj | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Microsoft.OpenApi.Readers/Microsoft.OpenApi.Readers.csproj b/src/Microsoft.OpenApi.Readers/Microsoft.OpenApi.Readers.csproj index 632cbf599..a53910c84 100644 --- a/src/Microsoft.OpenApi.Readers/Microsoft.OpenApi.Readers.csproj +++ b/src/Microsoft.OpenApi.Readers/Microsoft.OpenApi.Readers.csproj @@ -1,4 +1,4 @@ - + netstandard2.0 latest @@ -21,7 +21,6 @@ - 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 3fefc302d..1268158aa 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/Microsoft.OpenApi.Readers.Tests.csproj +++ b/test/Microsoft.OpenApi.Readers.Tests/Microsoft.OpenApi.Readers.Tests.csproj @@ -25,7 +25,6 @@ - From 80e7d84a95fd7c02df6dbbb4804cf71375b7ea66 Mon Sep 17 00:00:00 2001 From: Maggiekimani1 Date: Wed, 29 Nov 2023 16:04:30 +0300 Subject: [PATCH 12/42] Throw an exception if referenced schema does not exist --- src/Microsoft.OpenApi/Services/OpenApiReferenceResolver.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Microsoft.OpenApi/Services/OpenApiReferenceResolver.cs b/src/Microsoft.OpenApi/Services/OpenApiReferenceResolver.cs index 5e1f86889..f541a3330 100644 --- a/src/Microsoft.OpenApi/Services/OpenApiReferenceResolver.cs +++ b/src/Microsoft.OpenApi/Services/OpenApiReferenceResolver.cs @@ -277,7 +277,8 @@ public JsonSchema ResolveJsonSchemaReference(Uri reference, string description = } else { - return null; + var referenceId = reference.OriginalString.Split('/').LastOrDefault(); + throw new OpenApiException(string.Format(Properties.SRResource.InvalidReferenceId, referenceId)); } } @@ -349,7 +350,7 @@ private void ResolveJsonSchemaList(IList list, Action Date: Wed, 29 Nov 2023 16:04:45 +0300 Subject: [PATCH 13/42] code cleanup --- src/Microsoft.OpenApi/Services/OpenApiWorkspace.cs | 3 +-- .../OpenApiReaderTests/OpenApiDiagnosticTests.cs | 7 +++++-- .../OpenApiDiagnosticReportMerged/TodoReference.yaml | 2 +- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/Microsoft.OpenApi/Services/OpenApiWorkspace.cs b/src/Microsoft.OpenApi/Services/OpenApiWorkspace.cs index 1abde0f89..63c1defaf 100644 --- a/src/Microsoft.OpenApi/Services/OpenApiWorkspace.cs +++ b/src/Microsoft.OpenApi/Services/OpenApiWorkspace.cs @@ -1,11 +1,10 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. using System; using System.Collections.Generic; using System.IO; using System.Linq; -using Json.More; using Json.Schema; using Microsoft.OpenApi.Extensions; using Microsoft.OpenApi.Interfaces; diff --git a/test/Microsoft.OpenApi.Readers.Tests/OpenApiReaderTests/OpenApiDiagnosticTests.cs b/test/Microsoft.OpenApi.Readers.Tests/OpenApiReaderTests/OpenApiDiagnosticTests.cs index 7f7c34b26..be476652e 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/OpenApiReaderTests/OpenApiDiagnosticTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/OpenApiReaderTests/OpenApiDiagnosticTests.cs @@ -56,8 +56,11 @@ public async Task DiagnosticReportMergedForExternalReference() Assert.NotNull(result); Assert.NotNull(result.OpenApiDocument.Workspace); Assert.True(result.OpenApiDocument.Workspace.Contains("TodoReference.yaml")); - result.OpenApiDiagnostic.Errors.Should().BeEquivalentTo(new List { - new( new OpenApiException("[File: ./TodoReference.yaml] Invalid Reference identifier 'object-not-existing'.")) }); + result.OpenApiDiagnostic.Errors.Should().BeEquivalentTo(new List + { + new OpenApiError("", "[File: ./TodoReference.yaml] Paths is a REQUIRED field at #/"), + new(new OpenApiException("[File: ./TodoReference.yaml] Invalid Reference identifier 'object-not-existing'.")) + }); } } diff --git a/test/Microsoft.OpenApi.Readers.Tests/OpenApiReaderTests/Samples/OpenApiDiagnosticReportMerged/TodoReference.yaml b/test/Microsoft.OpenApi.Readers.Tests/OpenApiReaderTests/Samples/OpenApiDiagnosticReportMerged/TodoReference.yaml index db3958149..98cd3d40a 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/OpenApiReaderTests/Samples/OpenApiDiagnosticReportMerged/TodoReference.yaml +++ b/test/Microsoft.OpenApi.Readers.Tests/OpenApiReaderTests/Samples/OpenApiDiagnosticReportMerged/TodoReference.yaml @@ -23,4 +23,4 @@ components: type: object properties: id: - type:string \ No newline at end of file + type: string \ No newline at end of file From bfacb4e3cab16c08a74d47ca759c0f6d58be354a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 30 Nov 2023 21:10:02 +0000 Subject: [PATCH 14/42] Bump Microsoft.OData.Edm from 7.18.0 to 7.19.0 Bumps Microsoft.OData.Edm from 7.18.0 to 7.19.0. --- updated-dependencies: - dependency-name: Microsoft.OData.Edm dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- src/Microsoft.OpenApi.Hidi/Microsoft.OpenApi.Hidi.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Microsoft.OpenApi.Hidi/Microsoft.OpenApi.Hidi.csproj b/src/Microsoft.OpenApi.Hidi/Microsoft.OpenApi.Hidi.csproj index c5f3e09d0..85a6bafbb 100644 --- a/src/Microsoft.OpenApi.Hidi/Microsoft.OpenApi.Hidi.csproj +++ b/src/Microsoft.OpenApi.Hidi/Microsoft.OpenApi.Hidi.csproj @@ -34,7 +34,7 @@ - + From 5b408268e292a193cecacb87f47f4319bae7f9fd Mon Sep 17 00:00:00 2001 From: Daniel Date: Mon, 4 Dec 2023 13:06:28 +0100 Subject: [PATCH 15/42] Fix unresolved references in OpenApiWalker --- src/Microsoft.OpenApi/Services/OpenApiWalker.cs | 3 ++- .../Walkers/WalkerLocationTests.cs | 13 +++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/Microsoft.OpenApi/Services/OpenApiWalker.cs b/src/Microsoft.OpenApi/Services/OpenApiWalker.cs index 7ce011f4b..446663f6b 100644 --- a/src/Microsoft.OpenApi/Services/OpenApiWalker.cs +++ b/src/Microsoft.OpenApi/Services/OpenApiWalker.cs @@ -1091,7 +1091,8 @@ private void Walk(string context, Action walk) /// private bool ProcessAsReference(IOpenApiReferenceable referenceable, bool isComponent = false) { - var isReference = referenceable.Reference != null && !isComponent; + var isReference = referenceable.Reference != null && + (!isComponent || referenceable.UnresolvedReference); if (isReference) { Walk(referenceable); diff --git a/test/Microsoft.OpenApi.Tests/Walkers/WalkerLocationTests.cs b/test/Microsoft.OpenApi.Tests/Walkers/WalkerLocationTests.cs index 09c808a1e..83b7eb341 100644 --- a/test/Microsoft.OpenApi.Tests/Walkers/WalkerLocationTests.cs +++ b/test/Microsoft.OpenApi.Tests/Walkers/WalkerLocationTests.cs @@ -233,6 +233,18 @@ public void LocateReferences() Headers = new Dictionary { ["test-header"] = testHeader + }, + SecuritySchemes = new Dictionary + { + ["test-secScheme"] = new OpenApiSecurityScheme + { + Reference = new OpenApiReference + { + Id = "reference-to-scheme", + Type = ReferenceType.SecurityScheme + }, + UnresolvedReference = true + } } } }; @@ -245,6 +257,7 @@ public void LocateReferences() "referenceAt: #/paths/~1/get/responses/200/content/application~1json/schema", "referenceAt: #/paths/~1/get/responses/200/headers/test-header", "referenceAt: #/components/schemas/derived/anyOf/0", + "referenceAt: #/components/securitySchemes/test-secScheme", "referenceAt: #/components/headers/test-header/schema" }); } From 8a4080177ed5942581b086adb971504f9a61f520 Mon Sep 17 00:00:00 2001 From: Daniel Date: Mon, 4 Dec 2023 13:10:59 +0100 Subject: [PATCH 16/42] Fix culture dependency in ParseDecimalWithFallbackOnOverflow_Overflows_ReturnsFallback test --- .../ParseNodes/ParserHelperTests.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/Microsoft.OpenApi.Readers.Tests/ParseNodes/ParserHelperTests.cs b/test/Microsoft.OpenApi.Readers.Tests/ParseNodes/ParserHelperTests.cs index b343abb5c..1368e103d 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/ParseNodes/ParserHelperTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/ParseNodes/ParserHelperTests.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. +using System.Globalization; using Microsoft.OpenApi.Readers.ParseNodes; using Xunit; @@ -18,7 +19,7 @@ public void ParseDecimalWithFallbackOnOverflow_ReturnsParsedValue() [Fact] public void ParseDecimalWithFallbackOnOverflow_Overflows_ReturnsFallback() { - Assert.Equal(10, ParserHelper.ParseDecimalWithFallbackOnOverflow(double.MaxValue.ToString(), 10)); + Assert.Equal(10, ParserHelper.ParseDecimalWithFallbackOnOverflow(double.MaxValue.ToString(CultureInfo.InvariantCulture), 10)); } } } From ffc87ca2fd24fa8d4ab8fa247d1578d530db7c18 Mon Sep 17 00:00:00 2001 From: Irvine Sunday Date: Mon, 4 Dec 2023 19:19:16 +0300 Subject: [PATCH 17/42] Use keyword examples instead of example --- .../Validations/Rules/JsonSchemaRules.cs | 14 ++++++++++---- src/Microsoft.OpenApi/Writers/OpenApiWriterBase.cs | 8 ++++---- .../Samples/OpenApiDocument/docWithExample.yaml | 3 ++- 3 files changed, 16 insertions(+), 9 deletions(-) diff --git a/src/Microsoft.OpenApi/Validations/Rules/JsonSchemaRules.cs b/src/Microsoft.OpenApi/Validations/Rules/JsonSchemaRules.cs index a8efc0289..2a5d71e09 100644 --- a/src/Microsoft.OpenApi/Validations/Rules/JsonSchemaRules.cs +++ b/src/Microsoft.OpenApi/Validations/Rules/JsonSchemaRules.cs @@ -34,12 +34,18 @@ public static class JsonSchemaRules context.Exit(); - // example - context.Enter("example"); + // examples + context.Enter("examples"); - if (jsonSchema.GetExample() != null) + if (jsonSchema.GetExamples() != null) { - RuleHelpers.ValidateDataTypeMismatch(context, nameof(SchemaMismatchedDataType), jsonSchema.GetExample(), jsonSchema); + for (int i = 0; i < jsonSchema.GetExamples().Count(); i++) + { + context.Enter(i.ToString()); + RuleHelpers.ValidateDataTypeMismatch(context, nameof(SchemaMismatchedDataType), jsonSchema.GetExamples().ElementAt(i), jsonSchema); + context.Exit(); + } + } context.Exit(); diff --git a/src/Microsoft.OpenApi/Writers/OpenApiWriterBase.cs b/src/Microsoft.OpenApi/Writers/OpenApiWriterBase.cs index 335cba7af..dec5a3651 100644 --- a/src/Microsoft.OpenApi/Writers/OpenApiWriterBase.cs +++ b/src/Microsoft.OpenApi/Writers/OpenApiWriterBase.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; @@ -574,9 +574,9 @@ public void WriteJsonSchemaWithoutReference(IOpenApiWriter writer, JsonSchema sc // externalDocs writer.WriteOptionalObject(OpenApiConstants.ExternalDocs, schema.GetExternalDocs(), (w, s) => JsonSerializer.Serialize(s)); - - // example - writer.WriteOptionalObject(OpenApiConstants.Example, schema.GetExample(), (w, e) => w.WriteAny(new OpenApiAny(e))); + + // examples + writer.WriteOptionalCollection(OpenApiConstants.Examples, schema.GetExamples(), (n, e) => n.WriteAny(new OpenApiAny(e))); // deprecated writer.WriteProperty(OpenApiConstants.Deprecated, schema.GetDeprecated(), false); diff --git a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiDocument/docWithExample.yaml b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiDocument/docWithExample.yaml index 51ffd38b3..0f40f5e61 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiDocument/docWithExample.yaml +++ b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiDocument/docWithExample.yaml @@ -61,7 +61,8 @@ paths: # The available paths and operations for the API properties: message: # A property for the confirmation message type: string - example: File uploaded successfully + examples: + - The file was uploaded successfully components: # Reusable components for the API schemas: # JSON Schema definitions for the API User: # A schema for a user object From 7aa2e323643e8398d8d081386489e702d9b61829 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Dec 2023 21:09:57 +0000 Subject: [PATCH 18/42] Bump PublicApiGenerator from 11.0.0 to 11.1.0 Bumps [PublicApiGenerator](https://github.com/PublicApiGenerator/PublicApiGenerator) from 11.0.0 to 11.1.0. - [Release notes](https://github.com/PublicApiGenerator/PublicApiGenerator/releases) - [Commits](https://github.com/PublicApiGenerator/PublicApiGenerator/compare/11.0.0...11.1.0) --- updated-dependencies: - dependency-name: PublicApiGenerator dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj b/test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj index 4e9ef5341..34da13b28 100644 --- a/test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj +++ b/test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj @@ -19,7 +19,7 @@ - + From 030d397434104aae3f7b15e4a3ef71507bc59e2d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Dec 2023 21:10:30 +0000 Subject: [PATCH 19/42] Bump Verify.Xunit from 22.6.0 to 22.7.1 Bumps [Verify.Xunit](https://github.com/VerifyTests/Verify) from 22.6.0 to 22.7.1. - [Release notes](https://github.com/VerifyTests/Verify/releases) - [Commits](https://github.com/VerifyTests/Verify/compare/22.6.0...22.7.1) --- updated-dependencies: - dependency-name: Verify.Xunit dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj b/test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj index 4e9ef5341..dd48c70af 100644 --- a/test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj +++ b/test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj @@ -15,7 +15,7 @@ - + From 7c20deed494108c54feca636e22fe7b9123e9c30 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Dec 2023 21:35:50 +0000 Subject: [PATCH 20/42] Bump actions/setup-java from 3 to 4 Bumps [actions/setup-java](https://github.com/actions/setup-java) from 3 to 4. - [Release notes](https://github.com/actions/setup-java/releases) - [Commits](https://github.com/actions/setup-java/compare/v3...v4) --- updated-dependencies: - dependency-name: actions/setup-java dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/sonarcloud.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml index c6b975ae5..bcf9cbaf1 100644 --- a/.github/workflows/sonarcloud.yml +++ b/.github/workflows/sonarcloud.yml @@ -30,7 +30,7 @@ jobs: runs-on: windows-latest steps: - name: Set up JDK 17 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: distribution: 'adopt' java-version: 17 From 252d08c48c2fc6708d6c3d039250fd70840c0c1f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Dec 2023 21:35:56 +0000 Subject: [PATCH 21/42] Bump actions/setup-dotnet from 3 to 4 Bumps [actions/setup-dotnet](https://github.com/actions/setup-dotnet) from 3 to 4. - [Release notes](https://github.com/actions/setup-dotnet/releases) - [Commits](https://github.com/actions/setup-dotnet/compare/v3...v4) --- updated-dependencies: - dependency-name: actions/setup-dotnet dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/ci-cd.yml | 2 +- .github/workflows/codeql-analysis.yml | 2 +- .github/workflows/sonarcloud.yml | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index 77dc8fd7b..95f001e1f 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -14,7 +14,7 @@ jobs: GITHUB_RUN_NUMBER: ${{ github.run_number }} steps: - name: Setup .NET - uses: actions/setup-dotnet@v3 + uses: actions/setup-dotnet@v4 with: dotnet-version: 7.0.x diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index f58bfa0c9..11c90f95b 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -17,7 +17,7 @@ jobs: uses: actions/checkout@v4 - name: Setup .NET - uses: actions/setup-dotnet@v3 + uses: actions/setup-dotnet@v4 with: dotnet-version: 7.0.x diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml index c6b975ae5..bb84522e9 100644 --- a/.github/workflows/sonarcloud.yml +++ b/.github/workflows/sonarcloud.yml @@ -35,11 +35,11 @@ jobs: distribution: 'adopt' java-version: 17 - name: Setup .NET 5 # At the moment the scanner requires dotnet 5 https://www.nuget.org/packages/dotnet-sonarscanner - uses: actions/setup-dotnet@v3 + uses: actions/setup-dotnet@v4 with: dotnet-version: 5.0.x - name: Setup .NET - uses: actions/setup-dotnet@v3 + uses: actions/setup-dotnet@v4 with: dotnet-version: 7.0.x - uses: actions/checkout@v4 From 3e159d24c9b9aa7f56e3fc95f811236b15756f4e Mon Sep 17 00:00:00 2001 From: Irvine Sunday Date: Tue, 5 Dec 2023 21:22:04 +0300 Subject: [PATCH 22/42] Serialize JsonSchema --- .../V31/JsonSchemaDeserializer.cs | 269 +++++++++++++++++- .../Extensions/JsonSchemaBuilderExtensions.cs | 5 +- .../Validations/Rules/JsonSchemaRules.cs | 10 + .../Writers/OpenApiWriterBase.cs | 5 +- .../Validations/ValidationRuleSetTests.cs | 6 +- 5 files changed, 281 insertions(+), 14 deletions(-) diff --git a/src/Microsoft.OpenApi.Readers/V31/JsonSchemaDeserializer.cs b/src/Microsoft.OpenApi.Readers/V31/JsonSchemaDeserializer.cs index b389860af..0247230fa 100644 --- a/src/Microsoft.OpenApi.Readers/V31/JsonSchemaDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V31/JsonSchemaDeserializer.cs @@ -1,9 +1,13 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. -using System.Text.Json; +using System.Collections.Generic; +using System.Globalization; +using System.Text.Json.Nodes; using Json.Schema; +using Json.Schema.OpenApi; using Microsoft.OpenApi.Extensions; +using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Readers.ParseNodes; using JsonSchema = Json.Schema.JsonSchema; @@ -15,20 +19,260 @@ namespace Microsoft.OpenApi.Readers.V31 /// runtime Open API object model. /// internal static partial class OpenApiV31Deserializer - { + { + private static readonly FixedFieldMap _schemaFixedFields = new() + { + { + "title", (o, n) => + { + o.Title(n.GetScalarValue()); + } + }, + { + "multipleOf", (o, n) => + { + o.MultipleOf(decimal.Parse(n.GetScalarValue(), NumberStyles.Float, CultureInfo.InvariantCulture)); + } + }, + { + "maximum", (o, n) => + { + o.Maximum(decimal.Parse(n.GetScalarValue(), NumberStyles.Float, CultureInfo.InvariantCulture)); + } + }, + { + "exclusiveMaximum", (o, n) => + { + o.ExclusiveMaximum(decimal.Parse(n.GetScalarValue(), NumberStyles.Float, CultureInfo.InvariantCulture)); + } + }, + { + "minimum", (o, n) => + { + o.Minimum(decimal.Parse(n.GetScalarValue(), NumberStyles.Float, CultureInfo.InvariantCulture)); + } + }, + { + "exclusiveMinimum", (o, n) => + { + o.ExclusiveMinimum(decimal.Parse(n.GetScalarValue(), NumberStyles.Float, CultureInfo.InvariantCulture)); + } + }, + { + "maxLength", (o, n) => + { + o.MaxLength(uint.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture)); + } + }, + { + "minLength", (o, n) => + { + o.MinLength(uint.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture)); + } + }, + { + "pattern", (o, n) => + { + o.Pattern(n.GetScalarValue()); + } + }, + { + "maxItems", (o, n) => + { + o.MaxItems(uint.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture)); + } + }, + { + "minItems", (o, n) => + { + o.MinItems(uint.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture)); + } + }, + { + "uniqueItems", (o, n) => + { + o.UniqueItems(bool.Parse(n.GetScalarValue())); + } + }, + { + "maxProperties", (o, n) => + { + o.MaxProperties(uint.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture)); + } + }, + { + "minProperties", (o, n) => + { + o.MinProperties(uint.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture)); + } + }, + { + "required", (o, n) => + { + o.Required(new HashSet(n.CreateSimpleList(n2 => n2.GetScalarValue()))); + } + }, + { + "enum", (o, n) => + { + o.Enum(n.CreateListOfAny()); + } + }, + { + "type", (o, n) => + { + if(n is ListNode) + { + o.Type(n.CreateSimpleList(s => SchemaTypeConverter.ConvertToSchemaValueType(s.GetScalarValue()))); + } + else + { + o.Type(SchemaTypeConverter.ConvertToSchemaValueType(n.GetScalarValue())); + } + } + }, + { + "allOf", (o, n) => + { + o.AllOf(n.CreateList(LoadSchema)); + } + }, + { + "oneOf", (o, n) => + { + o.OneOf(n.CreateList(LoadSchema)); + } + }, + { + "anyOf", (o, n) => + { + o.AnyOf(n.CreateList(LoadSchema)); + } + }, + { + "not", (o, n) => + { + o.Not(LoadSchema(n)); + } + }, + { + "items", (o, n) => + { + o.Items(LoadSchema(n)); + } + }, + { + "properties", (o, n) => + { + o.Properties(n.CreateMap(LoadSchema)); + } + }, + { + "additionalProperties", (o, n) => + { + if (n is ValueNode) + { + o.AdditionalPropertiesAllowed(bool.Parse(n.GetScalarValue())); + } + else + { + o.AdditionalProperties(LoadSchema(n)); + } + } + }, + { + "description", (o, n) => + { + o.Description(n.GetScalarValue()); + } + }, + { + "format", (o, n) => + { + o.Format(n.GetScalarValue()); + } + }, + { + "default", (o, n) => + { + o.Default(n.CreateAny().Node); + } + }, + { + "discriminator", (o, n) => + { + var discriminator = LoadDiscriminator(n); + o.Discriminator(discriminator); + } + }, + { + "readOnly", (o, n) => + { + o.ReadOnly(bool.Parse(n.GetScalarValue())); + } + }, + { + "writeOnly", (o, n) => + { + o.WriteOnly(bool.Parse(n.GetScalarValue())); + } + }, + { + "xml", (o, n) => + { + var xml = LoadXml(n); + o.Xml(xml.Namespace, xml.Name, xml.Prefix, xml.Attribute, xml.Wrapped, + (IReadOnlyDictionary)xml.Extensions); + } + }, + { + "externalDocs", (o, n) => + { + var externalDocs = LoadExternalDocs(n); + o.ExternalDocs(externalDocs.Url, externalDocs.Description, + (IReadOnlyDictionary)externalDocs.Extensions); + } + }, + { + "example", (o, n) => + { + if(n is ListNode) + { + o.Examples(n.CreateSimpleList(s => (JsonNode)s.GetScalarValue())); + } + else + { + o.Example(n.CreateAny().Node); + } + } + }, + { + "deprecated", (o, n) => + { + o.Deprecated(bool.Parse(n.GetScalarValue())); + } + }, + }; + + private static readonly PatternFieldMap _schemaPatternFields = new PatternFieldMap + { + {s => s.StartsWith("x-"), (o, p, n) => o.Extensions(LoadExtensions(p, LoadExtension(p, n)))} + }; + public static JsonSchema LoadSchema(ParseNode node) { var mapNode = node.CheckMapNode(OpenApiConstants.Schema); var builder = new JsonSchemaBuilder(); // check for a $ref and if present, add it to the builder as a Ref keyword - if (mapNode.GetReferencePointer() is {} pointer) + var pointer = mapNode.GetReferencePointer(); + if (pointer != null) { builder = builder.Ref(pointer); // Check for summary and description and append to builder var summary = mapNode.GetSummaryValue(); - var description = mapNode.GetDescriptionValue(); + var description = mapNode.GetDescriptionValue(); if (!string.IsNullOrEmpty(summary)) { builder.Summary(summary); @@ -40,10 +284,23 @@ public static JsonSchema LoadSchema(ParseNode node) return builder.Build(); } - else + + foreach (var propertyNode in mapNode) { - return node.JsonNode.Deserialize(); + propertyNode.ParseField(builder, _schemaFixedFields, _schemaPatternFields); } + + var schema = builder.Build(); + return schema; + } + + private static Dictionary LoadExtensions(string value, IOpenApiExtension extension) + { + var extensions = new Dictionary + { + { value, extension } + }; + return extensions; } } diff --git a/src/Microsoft.OpenApi/Extensions/JsonSchemaBuilderExtensions.cs b/src/Microsoft.OpenApi/Extensions/JsonSchemaBuilderExtensions.cs index 92738f66c..d062b3404 100644 --- a/src/Microsoft.OpenApi/Extensions/JsonSchemaBuilderExtensions.cs +++ b/src/Microsoft.OpenApi/Extensions/JsonSchemaBuilderExtensions.cs @@ -15,8 +15,6 @@ namespace Microsoft.OpenApi.Extensions /// public static class JsonSchemaBuilderExtensions { - private static readonly Dictionary _keywords = new Dictionary(); - /// /// Custom extensions in the schema /// @@ -154,8 +152,7 @@ public static JsonSchemaBuilder Remove(this JsonSchemaBuilder builder, string ke schemaBuilder.Add(item); } } - - //_keywords.Remove(keyword); + return schemaBuilder; } } diff --git a/src/Microsoft.OpenApi/Validations/Rules/JsonSchemaRules.cs b/src/Microsoft.OpenApi/Validations/Rules/JsonSchemaRules.cs index 2a5d71e09..74fdfefac 100644 --- a/src/Microsoft.OpenApi/Validations/Rules/JsonSchemaRules.cs +++ b/src/Microsoft.OpenApi/Validations/Rules/JsonSchemaRules.cs @@ -48,6 +48,16 @@ public static class JsonSchemaRules } + context.Exit(); + + // example + context.Enter("example"); + + if (jsonSchema.GetExample() != null) + { + RuleHelpers.ValidateDataTypeMismatch(context, nameof(SchemaMismatchedDataType), jsonSchema.GetExample(), jsonSchema); + } + context.Exit(); // enum diff --git a/src/Microsoft.OpenApi/Writers/OpenApiWriterBase.cs b/src/Microsoft.OpenApi/Writers/OpenApiWriterBase.cs index dec5a3651..305fd2489 100644 --- a/src/Microsoft.OpenApi/Writers/OpenApiWriterBase.cs +++ b/src/Microsoft.OpenApi/Writers/OpenApiWriterBase.cs @@ -574,7 +574,10 @@ public void WriteJsonSchemaWithoutReference(IOpenApiWriter writer, JsonSchema sc // externalDocs writer.WriteOptionalObject(OpenApiConstants.ExternalDocs, schema.GetExternalDocs(), (w, s) => JsonSerializer.Serialize(s)); - + + // example + writer.WriteOptionalObject(OpenApiConstants.Example, schema.GetExample(), (w, s) => w.WriteAny(new OpenApiAny(s))); + // examples writer.WriteOptionalCollection(OpenApiConstants.Examples, schema.GetExamples(), (n, e) => n.WriteAny(new OpenApiAny(e))); 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 ae5203516f46142fcf98a6dd8f122218ecf60abd Mon Sep 17 00:00:00 2001 From: Maggiekimani1 Date: Thu, 7 Dec 2023 16:32:18 +0300 Subject: [PATCH 23/42] Refactor code to resolve Oneof and AnyOf schemas --- .../Formatters/PowerShellFormatter.cs | 15 ++++++++++++--- .../Services/OpenApiWalker.cs | 18 ++++++++++++++---- 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/src/Microsoft.OpenApi.Hidi/Formatters/PowerShellFormatter.cs b/src/Microsoft.OpenApi.Hidi/Formatters/PowerShellFormatter.cs index b7fe664c1..aab3fb829 100644 --- a/src/Microsoft.OpenApi.Hidi/Formatters/PowerShellFormatter.cs +++ b/src/Microsoft.OpenApi.Hidi/Formatters/PowerShellFormatter.cs @@ -44,8 +44,8 @@ static PowerShellFormatter() // 6. Add AdditionalProperties to object schemas. public override void Visit(ref JsonSchema schema) - { - AddAdditionalPropertiesToSchema(schema); + { + AddAdditionalPropertiesToSchema(ref schema); schema = ResolveAnyOfSchema(ref schema); schema = ResolveOneOfSchema(ref schema); @@ -174,7 +174,7 @@ private static IList ResolveFunctionParameters(IList Walk(item.Value, isComponent: true)); + Walk(item.Key, () => components.Schemas[item.Key] = Walk(item.Value, isComponent: true)); } } }); @@ -498,8 +498,7 @@ internal void Walk(OpenApiPathItem pathItem, bool isComponent = false) _visitor.Visit(pathItem); - // The path may be a reference - if (pathItem != null && !ProcessAsReference(pathItem)) + if (pathItem != null) { Walk(OpenApiConstants.Parameters, () => Walk(pathItem.Parameters)); Walk(pathItem.Operations); @@ -850,9 +849,20 @@ internal JsonSchema Walk(JsonSchema schema, bool isComponent = false) { Walk("properties", () => { + var props = new Dictionary(); + var builder = new JsonSchemaBuilder(); + foreach(var keyword in schema.Keywords) + { + builder.Add(keyword); + } + foreach (var item in schema.GetProperties()) { - Walk(item.Key, () => Walk(item.Value)); + var key = item.Key; + JsonSchema newSchema = null; + Walk(key, () => newSchema = Walk(item.Value)); + props.Add(key, newSchema); + schema = builder.Properties(props); } }); } From 4ca2f877eb282560783ce7afc02cf836bb7120fc Mon Sep 17 00:00:00 2001 From: Maggiekimani1 Date: Thu, 7 Dec 2023 16:34:00 +0300 Subject: [PATCH 24/42] Clean up tests --- .../V31Tests/OpenApiDocumentTests.cs | 13 ++++------ .../documentWithReusablePaths.yaml | 4 ++-- .../OpenApiDocument/documentWithWebhooks.yaml | 4 ++-- .../V3Tests/JsonSchemaTests.cs | 2 +- .../Workspaces/OpenApiWorkspaceTests.cs | 24 +++++++++++++------ 5 files changed, 26 insertions(+), 21 deletions(-) diff --git a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiDocumentTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiDocumentTests.cs index 89100c4aa..388bbf231 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiDocumentTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiDocumentTests.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Globalization; using System.IO; using FluentAssertions; @@ -7,7 +7,6 @@ using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Writers; using Xunit; -using static System.Net.Mime.MediaTypeNames; namespace Microsoft.OpenApi.Readers.Tests.V31Tests { @@ -71,7 +70,7 @@ public void ParseDocumentWithWebhooksShouldSucceed() Schemas = { ["pet1"] = petSchema, - ["newPet"] = newPetSchema + ["newPet1"] = newPetSchema } }; @@ -199,7 +198,7 @@ public void ParseDocumentsWithReusablePathItemInWebhooksSucceeds() ("id", new JsonSchemaBuilder().Type(SchemaValueType.Integer).Format("int64")), ("name", new JsonSchemaBuilder().Type(SchemaValueType.String)), ("tag", new JsonSchemaBuilder().Type(SchemaValueType.String))), - ["newPet"] = new JsonSchemaBuilder() + ["newPetSchema"] = new JsonSchemaBuilder() .Type(SchemaValueType.Object) .Required("name") .Properties( @@ -211,7 +210,7 @@ public void ParseDocumentsWithReusablePathItemInWebhooksSucceeds() // Create a clone of the schema to avoid modifying things in components. var petSchema = components.Schemas["petSchema"]; - var newPetSchema = components.Schemas["newPet"]; + var newPetSchema = components.Schemas["newPetSchema"]; components.PathItems = new Dictionary { @@ -333,14 +332,10 @@ public void ParseDocumentWithDescriptionInDollarRefsShouldSucceed() // Act var actual = new OpenApiStreamReader().Read(stream, out var diagnostic); - var schema = actual.Paths["/pets"].Operations[OperationType.Get].Responses["200"].Content["application/json"].Schema; var header = actual.Components.Responses["Test"].Headers["X-Test"]; // Assert Assert.True(header.Description == "A referenced X-Test header"); /*response header #ref's description overrides the header's description*/ - Assert.Null(schema.GetRef()); - Assert.Equal(SchemaValueType.Object, schema.GetJsonType()); - Assert.Equal("A pet in a petstore", schema.GetDescription()); /*The reference object's description overrides that of the referenced component*/ } [Fact] diff --git a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiDocument/documentWithReusablePaths.yaml b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiDocument/documentWithReusablePaths.yaml index f9327910b..33cf7301e 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiDocument/documentWithReusablePaths.yaml +++ b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiDocument/documentWithReusablePaths.yaml @@ -21,7 +21,7 @@ components: type: string tag: type: string - newPet: + newPetSchema: type: object required: - name @@ -75,7 +75,7 @@ components: content: 'application/json': schema: - "$ref": '#/components/schemas/newPet' + "$ref": '#/components/schemas/newPetSchema' responses: "200": description: Return a 200 status to indicate that the data was received successfully diff --git a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiDocument/documentWithWebhooks.yaml b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiDocument/documentWithWebhooks.yaml index 11c389157..41253f148 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiDocument/documentWithWebhooks.yaml +++ b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiDocument/documentWithWebhooks.yaml @@ -44,7 +44,7 @@ webhooks: content: 'application/json': schema: - "$ref": '#/components/schemas/newPet' + "$ref": '#/components/schemas/newPet1' responses: "200": description: Return a 200 status to indicate that the data was received successfully @@ -67,7 +67,7 @@ components: type: string tag: type: string - newPet: + newPet1: type: object required: - name diff --git a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/JsonSchemaTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/JsonSchemaTests.cs index 56b25a300..6ab2da8e9 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/JsonSchemaTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/JsonSchemaTests.cs @@ -249,7 +249,7 @@ public void ParseBasicSchemaWithReferenceShouldSucceed() new JsonSchemaBuilder() .Type(SchemaValueType.Object) .Required("rootCause") - .Properties(("rootCause", new JsonSchemaBuilder().Type(SchemaValueType.String)))) + .Properties(("rootCause", new JsonSchemaBuilder().Type(SchemaValueType.String)))) } }, options => options.Excluding(m => m.Name == "HostDocument") diff --git a/test/Microsoft.OpenApi.Tests/Workspaces/OpenApiWorkspaceTests.cs b/test/Microsoft.OpenApi.Tests/Workspaces/OpenApiWorkspaceTests.cs index a67df9e92..57faaf72f 100644 --- a/test/Microsoft.OpenApi.Tests/Workspaces/OpenApiWorkspaceTests.cs +++ b/test/Microsoft.OpenApi.Tests/Workspaces/OpenApiWorkspaceTests.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; @@ -72,13 +72,14 @@ public void OpenApiWorkspacesAllowDocumentsToReferenceEachOther() [Fact] public void OpenApiWorkspacesCanResolveExternalReferences() { + var refUri = new Uri("https://everything.json/common#/components/schemas/test"); var workspace = new OpenApiWorkspace(); - var doc = CreateCommonDocument(); + var doc = CreateCommonDocument(refUri); var location = "common"; workspace.AddDocument(location, doc); - var schema = workspace.ResolveJsonSchemaReference(new Uri("https://everything.json/common#/components/schemas/test")); + var schema = workspace.ResolveJsonSchemaReference(refUri); Assert.NotNull(schema); Assert.Equal("The referenced one", schema.GetDescription()); @@ -90,6 +91,7 @@ public void OpenApiWorkspacesAllowDocumentsToReferenceEachOther_short() var workspace = new OpenApiWorkspace(); var doc = new OpenApiDocument(); + var reference = "#/components/schemas/test"; doc.CreatePathItem("/", p => { p.Description = "Consumer"; @@ -98,14 +100,15 @@ public void OpenApiWorkspacesAllowDocumentsToReferenceEachOther_short() { re.Description = "Success"; re.CreateContent("application/json", co => - co.Schema = new JsonSchemaBuilder().Ref("test").Build() + co.Schema = new JsonSchemaBuilder().Ref(reference).Build() ); }) ); }); + var refUri = new Uri("https://registry" + reference.Split('#').LastOrDefault()); workspace.AddDocument("root", doc); - workspace.AddDocument("common", CreateCommonDocument()); + workspace.AddDocument("common", CreateCommonDocument(refUri)); var errors = doc.ResolveReferences(); Assert.Empty(errors); @@ -178,9 +181,9 @@ public void OpenApiWorkspacesCanResolveReferencesToDocumentFragmentsWithJsonPoin } // Test artifacts - private static OpenApiDocument CreateCommonDocument() + private static OpenApiDocument CreateCommonDocument(Uri refUri) { - return new() + var doc = new OpenApiDocument() { Components = new() { @@ -189,6 +192,13 @@ private static OpenApiDocument CreateCommonDocument() } } }; + + foreach(var schema in doc.Components.Schemas) + { + SchemaRegistry.Global.Register(refUri, schema.Value); + } + + return doc; } } From 6fd73a0c813b04c4de22c9e268bce1210cfb6118 Mon Sep 17 00:00:00 2001 From: Tim Heuer Date: Thu, 7 Dec 2023 07:56:04 -0800 Subject: [PATCH 25/42] Removing irrelevant docs --- docs/CI-CD_DOCUMENTATION.md | 81 ---------------------- docs/images/Actions_workflow_dispatch.png | Bin 39746 -> 0 bytes 2 files changed, 81 deletions(-) delete mode 100644 docs/CI-CD_DOCUMENTATION.md delete mode 100644 docs/images/Actions_workflow_dispatch.png diff --git a/docs/CI-CD_DOCUMENTATION.md b/docs/CI-CD_DOCUMENTATION.md deleted file mode 100644 index 40053cf82..000000000 --- a/docs/CI-CD_DOCUMENTATION.md +++ /dev/null @@ -1,81 +0,0 @@ -# CI/CD documentation - -## 1. Run workflow manually - -1. Go to the project's GitHub repository and click on the **Actions** tab - -2. From the "Workflows" list on the left, click on "CI/CD Pipeline" - -3. On the right, next to the "This workflow has a workflow_dispatch event trigger" label, click on the "Run workflow" dropdown, make sure the default branch is selected (if not manually changed, should be main or master) in the "Use workflow from" dropdown and click the "Run workflow" button - -![Actions_workflow_dispatch](images/Actions_workflow_dispatch.png) - -NOTE: **screenshots are only exemplary** - -
- -## 2. Automated NuGet publishing - -To setup the automated publishing to NuGet: - -1. Go to the repo **Settings** tab -> **Secrets** - -2. Add a secret with the name `NUGET_API_KEY` and as value use an API key from NuGet.org that is assigned to the packages for this project - -NOTE: the automated NuGet publishing is execute **only** when a release is triggered by the ["Automated versioning" feature](#3-automated-versioning) - -
- -## 3. Automated versioning - -Automatically bumps up the GitHub tag in the repo and executes the CD job - -Note: **not every commit to your default branch creates a release** - -Follow these instructions for any commit (push or PR merge) to your default branch, you would like to execute the automated versioning. - -You would need one of three keywords at the start of your commit title. Each of the three keywords corresponds to a number in your release version i.e. v1.2.3. The release versioning uses the ["Conventional Commits" specification](https://www.conventionalcommits.org/en/v1.0.0/): - -- "fix: ..." - this keyword corresponds to the last number v1.2.**3**, also known as PATCH; -- "feat: ..." - this keyword corresponds to the middle number v1.**2**.3, also known as MINOR; -- "perf: ..." - this keyword corresponds to the first number v**1**.2.3, also known as MAJOR. In addition, to trigger a MAJOR release, you would need to write "BREAKING CHANGE: ..." in the description of the commit, with an empty line above it to indicate it is in the