diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index 95f001e1f..bf1aa6a33 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -2,6 +2,9 @@ name: CI/CD Pipeline on: [push, pull_request, workflow_dispatch] +permissions: + contents: write + jobs: ci: name: Continuous Integration diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml index 5f12a604b..fbd2629e9 100644 --- a/.github/workflows/sonarcloud.yml +++ b/.github/workflows/sonarcloud.yml @@ -9,6 +9,11 @@ on: types: [opened, synchronize, reopened] paths-ignore: ['.vscode/**'] + +permissions: + contents: read + pull-requests: read + env: SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} @@ -46,14 +51,14 @@ jobs: with: fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis - name: Cache SonarCloud packages - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ~/.sonar/cache key: ${{ runner.os }}-sonar restore-keys: ${{ runner.os }}-sonar - name: Cache SonarCloud scanner id: cache-sonar-scanner - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ./.sonar/scanner key: ${{ runner.os }}-sonar-scanner diff --git a/src/Microsoft.OpenApi.Hidi/Microsoft.OpenApi.Hidi.csproj b/src/Microsoft.OpenApi.Hidi/Microsoft.OpenApi.Hidi.csproj index 84a64182b..70e04e54b 100644 --- a/src/Microsoft.OpenApi.Hidi/Microsoft.OpenApi.Hidi.csproj +++ b/src/Microsoft.OpenApi.Hidi/Microsoft.OpenApi.Hidi.csproj @@ -1,4 +1,4 @@ - + Exe @@ -9,7 +9,7 @@ enable hidi ./../../artifacts - 1.3.7 + 1.3.8 OpenAPI.NET CLI tool for slicing OpenAPI documents true @@ -35,7 +35,7 @@ - + diff --git a/src/Microsoft.OpenApi.Readers/Microsoft.OpenApi.Readers.csproj b/src/Microsoft.OpenApi.Readers/Microsoft.OpenApi.Readers.csproj index 160253eee..c36efb50f 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.12 + 1.6.13 OpenAPI.NET Readers for JSON and YAML documents true diff --git a/src/Microsoft.OpenApi.Readers/V2/OpenApiOperationDeserializer.cs b/src/Microsoft.OpenApi.Readers/V2/OpenApiOperationDeserializer.cs index f47ca1c6c..fa91f913a 100644 --- a/src/Microsoft.OpenApi.Readers/V2/OpenApiOperationDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V2/OpenApiOperationDeserializer.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Linq; using System.Security.Cryptography.X509Certificates; +using System.Xml.Linq; using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Extensions; using Microsoft.OpenApi.Models; @@ -194,7 +195,8 @@ internal static OpenApiRequestBody CreateRequestBody( k => k, _ => new OpenApiMediaType { - Schema = bodyParameter.Schema + Schema = bodyParameter.Schema, + Examples = bodyParameter.Examples }), Extensions = bodyParameter.Extensions }; diff --git a/src/Microsoft.OpenApi.Readers/V2/OpenApiParameterDeserializer.cs b/src/Microsoft.OpenApi.Readers/V2/OpenApiParameterDeserializer.cs index f74600e66..a7d9f93d4 100644 --- a/src/Microsoft.OpenApi.Readers/V2/OpenApiParameterDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V2/OpenApiParameterDeserializer.cs @@ -95,12 +95,17 @@ internal static partial class OpenApiV2Deserializer "schema", (o, n) => o.Schema = LoadSchema(n) }, + { + "x-examples", + LoadParameterExamplesExtension + }, }; private static readonly PatternFieldMap _parameterPatternFields = new() { - {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, LoadExtension(p, n))} + {s => s.StartsWith("x-") && !s.Equals(OpenApiConstants.ExamplesExtension, StringComparison.OrdinalIgnoreCase), + (o, p, n) => o.AddExtension(p, LoadExtension(p, n))} }; private static readonly AnyFieldMap _parameterAnyFields = @@ -166,6 +171,12 @@ private static void LoadStyle(OpenApiParameter p, string v) } } + private static void LoadParameterExamplesExtension(OpenApiParameter parameter, ParseNode node) + { + var examples = LoadExamplesExtension(node); + node.Context.SetTempStorage(TempStorageKeys.Examples, examples, parameter); + } + private static OpenApiSchema GetOrCreateSchema(OpenApiParameter p) { if (p.Schema == null) @@ -250,6 +261,14 @@ public static OpenApiParameter LoadParameter(ParseNode node, bool loadRequestBod node.Context.SetTempStorage("schema", null); } + // load examples from storage and add them to the parameter + var examples = node.Context.GetFromTempStorage>(TempStorageKeys.Examples, parameter); + if (examples != null) + { + parameter.Examples = examples; + node.Context.SetTempStorage("examples", null); + } + var isBodyOrFormData = (bool)node.Context.GetFromTempStorage(TempStorageKeys.ParameterIsBodyOrFormData); if (isBodyOrFormData && !loadRequestBody) { diff --git a/src/Microsoft.OpenApi.Readers/V2/OpenApiResponseDeserializer.cs b/src/Microsoft.OpenApi.Readers/V2/OpenApiResponseDeserializer.cs index 2fef353ea..03691d14b 100644 --- a/src/Microsoft.OpenApi.Readers/V2/OpenApiResponseDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V2/OpenApiResponseDeserializer.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. +using System; using System.Collections.Generic; using Microsoft.OpenApi.Extensions; using Microsoft.OpenApi.Models; @@ -28,6 +29,10 @@ internal static partial class OpenApiV2Deserializer "examples", LoadExamples }, + { + "x-examples", + LoadResponseExamplesExtension + }, { "schema", (o, n) => n.Context.SetTempStorage(TempStorageKeys.ResponseSchema, LoadSchema(n), o) @@ -37,7 +42,8 @@ internal static partial class OpenApiV2Deserializer private static readonly PatternFieldMap _responsePatternFields = new() { - {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, LoadExtension(p, n))} + {s => s.StartsWith("x-") && !s.Equals(OpenApiConstants.ExamplesExtension, StringComparison.OrdinalIgnoreCase), + (o, p, n) => o.AddExtension(p, LoadExtension(p, n))} }; private static readonly AnyFieldMap _mediaTypeAnyFields = @@ -69,6 +75,8 @@ private static void ProcessProduces(MapNode mapNode, OpenApiResponse response, P ?? context.DefaultContentType ?? new List { "application/octet-stream" }; var schema = context.GetFromTempStorage(TempStorageKeys.ResponseSchema, response); + var examples = context.GetFromTempStorage>(TempStorageKeys.Examples, response) + ?? new Dictionary(); foreach (var produce in produces) { @@ -84,7 +92,8 @@ private static void ProcessProduces(MapNode mapNode, OpenApiResponse response, P { var mediaType = new OpenApiMediaType { - Schema = schema + Schema = schema, + Examples = examples }; response.Content.Add(produce, mediaType); @@ -92,12 +101,55 @@ private static void ProcessProduces(MapNode mapNode, OpenApiResponse response, P } context.SetTempStorage(TempStorageKeys.ResponseSchema, null, response); + context.SetTempStorage(TempStorageKeys.Examples, null, response); context.SetTempStorage(TempStorageKeys.ResponseProducesSet, true, response); } + private static void LoadResponseExamplesExtension(OpenApiResponse response, ParseNode node) + { + var examples = LoadExamplesExtension(node); + node.Context.SetTempStorage(TempStorageKeys.Examples, examples, response); + } + + private static Dictionary LoadExamplesExtension(ParseNode node) + { + var mapNode = node.CheckMapNode(OpenApiConstants.ExamplesExtension); + var examples = new Dictionary(); + + foreach (var examplesNode in mapNode) + { + // Load the media type node as an OpenApiExample object + var example = new OpenApiExample(); + var exampleNode = examplesNode.Value.CheckMapNode(examplesNode.Name); + foreach (var valueNode in exampleNode) + { + switch (valueNode.Name.ToLowerInvariant()) + { + case "summary": + example.Summary = valueNode.Value.GetScalarValue(); + break; + case "description": + example.Description = valueNode.Value.GetScalarValue(); + break; + case "value": + example.Value = OpenApiAnyConverter.GetSpecificOpenApiAny(valueNode.Value.CreateAny()); + break; + case "externalValue": + example.ExternalValue = valueNode.Value.GetScalarValue(); + break; + } + } + + examples.Add(examplesNode.Name, example); + } + + return examples; + } + private static void LoadExamples(OpenApiResponse response, ParseNode node) { var mapNode = node.CheckMapNode("examples"); + foreach (var mediaTypeNode in mapNode) { LoadExample(response, mediaTypeNode.Name, mediaTypeNode.Value); @@ -108,10 +160,7 @@ private static void LoadExample(OpenApiResponse response, string mediaType, Pars { var exampleNode = node.CreateAny(); - if (response.Content == null) - { - response.Content = new Dictionary(); - } + response.Content ??= new Dictionary(); OpenApiMediaType mediaTypeObject; if (response.Content.TryGetValue(mediaType, out var value)) @@ -141,6 +190,7 @@ public static OpenApiResponse LoadResponse(ParseNode node) } var response = new OpenApiResponse(); + foreach (var property in mapNode) { property.ParseField(response, _responseFixedFields, _responsePatternFields); diff --git a/src/Microsoft.OpenApi.Readers/V2/TempStorageKeys.cs b/src/Microsoft.OpenApi.Readers/V2/TempStorageKeys.cs index c7b96f6ce..176af8a1e 100644 --- a/src/Microsoft.OpenApi.Readers/V2/TempStorageKeys.cs +++ b/src/Microsoft.OpenApi.Readers/V2/TempStorageKeys.cs @@ -17,5 +17,6 @@ internal static class TempStorageKeys public const string GlobalConsumes = "globalConsumes"; public const string GlobalProduces = "globalProduces"; public const string ParameterIsBodyOrFormData = "parameterIsBodyOrFormData"; + public const string Examples = "examples"; } } diff --git a/src/Microsoft.OpenApi/Microsoft.OpenApi.csproj b/src/Microsoft.OpenApi/Microsoft.OpenApi.csproj index b59136dd0..8f17fae72 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.12 + 1.6.13 .NET models with JSON and YAML writers for OpenAPI specification true diff --git a/src/Microsoft.OpenApi/Models/OpenApiConstants.cs b/src/Microsoft.OpenApi/Models/OpenApiConstants.cs index 40867d7e0..73de74228 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiConstants.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiConstants.cs @@ -565,6 +565,11 @@ public static class OpenApiConstants /// public const string BodyName = "x-bodyName"; + /// + /// Field: Examples Extension + /// + public const string ExamplesExtension = "x-examples"; + /// /// Field: version3_0_0 /// diff --git a/src/Microsoft.OpenApi/Models/OpenApiDocument.cs b/src/Microsoft.OpenApi/Models/OpenApiDocument.cs index 578c1e34e..8d4526a20 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiDocument.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiDocument.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; diff --git a/src/Microsoft.OpenApi/Models/OpenApiExample.cs b/src/Microsoft.OpenApi/Models/OpenApiExample.cs index d70bab01d..1b9d31022 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiExample.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiExample.cs @@ -118,6 +118,16 @@ public OpenApiExample GetEffective(OpenApiDocument doc) /// Serialize to OpenAPI V3 document without using reference. /// public void SerializeAsV3WithoutReference(IOpenApiWriter writer) + { + Serialize(writer, OpenApiSpecVersion.OpenApi3_0); + } + + /// + /// Writes out existing examples in a mediatype object + /// + /// + /// + public void Serialize(IOpenApiWriter writer, OpenApiSpecVersion version) { writer.WriteStartObject(); @@ -134,7 +144,7 @@ public void SerializeAsV3WithoutReference(IOpenApiWriter writer) writer.WriteProperty(OpenApiConstants.ExternalValue, ExternalValue); // extensions - writer.WriteExtensions(Extensions, OpenApiSpecVersion.OpenApi3_0); + writer.WriteExtensions(Extensions, version); writer.WriteEndObject(); } diff --git a/src/Microsoft.OpenApi/Models/OpenApiParameter.cs b/src/Microsoft.OpenApi/Models/OpenApiParameter.cs index 9d1651ad8..fc1eaf8cc 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiParameter.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiParameter.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Linq; using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Extensions; using Microsoft.OpenApi.Interfaces; @@ -204,14 +205,7 @@ public void SerializeAsV3(IOpenApiWriter writer) /// OpenApiParameter public OpenApiParameter GetEffective(OpenApiDocument doc) { - if (this.Reference != null) - { - return doc.ResolveReferenceTo(this.Reference); - } - else - { - return this; - } + return Reference != null ? doc.ResolveReferenceTo(Reference) : this; } /// @@ -394,6 +388,20 @@ public void SerializeAsV2WithoutReference(IOpenApiWriter writer) } } + //examples + if (Examples != null && Examples.Any()) + { + writer.WritePropertyName(OpenApiConstants.ExamplesExtension); + writer.WriteStartObject(); + + foreach (var example in Examples) + { + writer.WritePropertyName(example.Key); + example.Value.Serialize(writer, OpenApiSpecVersion.OpenApi2_0); + } + writer.WriteEndObject(); + } + // extensions writer.WriteExtensions(extensionsClone, OpenApiSpecVersion.OpenApi2_0); diff --git a/src/Microsoft.OpenApi/Models/OpenApiRequestBody.cs b/src/Microsoft.OpenApi/Models/OpenApiRequestBody.cs index ff1a70b92..5e5edc576 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiRequestBody.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiRequestBody.cs @@ -96,14 +96,7 @@ public void SerializeAsV3(IOpenApiWriter writer) /// OpenApiRequestBody public OpenApiRequestBody GetEffective(OpenApiDocument doc) { - if (this.Reference != null) - { - return doc.ResolveReferenceTo(this.Reference); - } - else - { - return this; - } + return Reference != null ? doc.ResolveReferenceTo(Reference) : this; } /// @@ -153,6 +146,7 @@ internal OpenApiBodyParameter ConvertToBodyParameter() // To allow round-tripping we use an extension to hold the name Name = "body", Schema = Content.Values.FirstOrDefault()?.Schema ?? new OpenApiSchema(), + Examples = Content.Values.FirstOrDefault()?.Examples, Required = Required, Extensions = Extensions.ToDictionary(static k => k.Key, static v => v.Value) // Clone extensions so we can remove the x-bodyName extensions from the output V2 model. }; @@ -184,7 +178,8 @@ internal IEnumerable ConvertToFormDataParameters() Description = property.Value.Description, Name = property.Key, Schema = property.Value, - Required = Content.First().Value.Schema.Required.Contains(property.Key) + Examples = Content.Values.FirstOrDefault()?.Examples, + Required = Content.First().Value.Schema.Required?.Contains(property.Key) ?? false }; } } diff --git a/src/Microsoft.OpenApi/Models/OpenApiResponse.cs b/src/Microsoft.OpenApi/Models/OpenApiResponse.cs index 320ecc484..e300cd33d 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiResponse.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiResponse.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. using System.Collections.Generic; @@ -101,14 +101,7 @@ public void SerializeAsV3(IOpenApiWriter writer) /// OpenApiResponse public OpenApiResponse GetEffective(OpenApiDocument doc) { - if (this.Reference != null) - { - return doc.ResolveReferenceTo(this.Reference); - } - else - { - return this; - } + return Reference != null ? doc.ResolveReferenceTo(Reference) : this; } /// @@ -201,6 +194,22 @@ public void SerializeAsV2WithoutReference(IOpenApiWriter writer) writer.WriteEndObject(); } + if (Content.Values.Any(m => m.Examples != null && m.Examples.Any())) + { + writer.WritePropertyName(OpenApiConstants.ExamplesExtension); + writer.WriteStartObject(); + + foreach (var example in Content + .Where(mediaTypePair => mediaTypePair.Value.Examples != null && mediaTypePair.Value.Examples.Any()) + .SelectMany(mediaTypePair => mediaTypePair.Value.Examples)) + { + writer.WritePropertyName(example.Key); + example.Value.Serialize(writer, OpenApiSpecVersion.OpenApi2_0); + } + + writer.WriteEndObject(); + } + writer.WriteExtensions(mediatype.Value.Extensions, OpenApiSpecVersion.OpenApi2_0); foreach (var key in mediatype.Value.Extensions.Keys) diff --git a/src/Microsoft.OpenApi/Validations/Rules/OpenApiComponentsRules.cs b/src/Microsoft.OpenApi/Validations/Rules/OpenApiComponentsRules.cs index f2f3a649c..93eba5c71 100644 --- a/src/Microsoft.OpenApi/Validations/Rules/OpenApiComponentsRules.cs +++ b/src/Microsoft.OpenApi/Validations/Rules/OpenApiComponentsRules.cs @@ -24,7 +24,7 @@ public static class OpenApiComponentsRules /// that MUST use keys that match the regular expression: ^[a-zA-Z0-9\.\-_]+$. /// public static ValidationRule KeyMustBeRegularExpression => - new( + new(nameof(KeyMustBeRegularExpression), (context, components) => { ValidateKeys(context, components.Schemas?.Keys, "schemas"); diff --git a/src/Microsoft.OpenApi/Validations/Rules/OpenApiContactRules.cs b/src/Microsoft.OpenApi/Validations/Rules/OpenApiContactRules.cs index cfa8d9927..e31dc1e07 100644 --- a/src/Microsoft.OpenApi/Validations/Rules/OpenApiContactRules.cs +++ b/src/Microsoft.OpenApi/Validations/Rules/OpenApiContactRules.cs @@ -17,7 +17,7 @@ public static class OpenApiContactRules /// Email field MUST be email address. /// public static ValidationRule EmailMustBeEmailFormat => - new( + new(nameof(EmailMustBeEmailFormat), (context, item) => { context.Enter("email"); diff --git a/src/Microsoft.OpenApi/Validations/Rules/OpenApiDocumentRules.cs b/src/Microsoft.OpenApi/Validations/Rules/OpenApiDocumentRules.cs index be0dc1538..f38e2530f 100644 --- a/src/Microsoft.OpenApi/Validations/Rules/OpenApiDocumentRules.cs +++ b/src/Microsoft.OpenApi/Validations/Rules/OpenApiDocumentRules.cs @@ -17,7 +17,7 @@ public static class OpenApiDocumentRules /// The Info field is required. /// public static ValidationRule OpenApiDocumentFieldIsMissing => - new( + new(nameof(OpenApiDocumentFieldIsMissing), (context, item) => { // info diff --git a/src/Microsoft.OpenApi/Validations/Rules/OpenApiExtensionRules.cs b/src/Microsoft.OpenApi/Validations/Rules/OpenApiExtensionRules.cs index 5d124e8de..890be82d7 100644 --- a/src/Microsoft.OpenApi/Validations/Rules/OpenApiExtensionRules.cs +++ b/src/Microsoft.OpenApi/Validations/Rules/OpenApiExtensionRules.cs @@ -17,7 +17,7 @@ public static class OpenApiExtensibleRules /// Extension name MUST start with "x-". /// public static ValidationRule ExtensionNameMustStartWithXDash => - new( + new(nameof(ExtensionNameMustStartWithXDash), (context, item) => { context.Enter("extensions"); diff --git a/src/Microsoft.OpenApi/Validations/Rules/OpenApiExternalDocsRules.cs b/src/Microsoft.OpenApi/Validations/Rules/OpenApiExternalDocsRules.cs index ff4fde4a2..1754dbee2 100644 --- a/src/Microsoft.OpenApi/Validations/Rules/OpenApiExternalDocsRules.cs +++ b/src/Microsoft.OpenApi/Validations/Rules/OpenApiExternalDocsRules.cs @@ -17,7 +17,7 @@ public static class OpenApiExternalDocsRules /// Validate the field is required. /// public static ValidationRule UrlIsRequired => - new( + new(nameof(UrlIsRequired), (context, item) => { // url diff --git a/src/Microsoft.OpenApi/Validations/Rules/OpenApiHeaderRules.cs b/src/Microsoft.OpenApi/Validations/Rules/OpenApiHeaderRules.cs index c446a7b56..194e426cc 100644 --- a/src/Microsoft.OpenApi/Validations/Rules/OpenApiHeaderRules.cs +++ b/src/Microsoft.OpenApi/Validations/Rules/OpenApiHeaderRules.cs @@ -16,7 +16,7 @@ public static class OpenApiHeaderRules /// Validate the data matches with the given data type. /// public static ValidationRule HeaderMismatchedDataType => - new( + new(nameof(HeaderMismatchedDataType), (context, header) => { // example diff --git a/src/Microsoft.OpenApi/Validations/Rules/OpenApiInfoRules.cs b/src/Microsoft.OpenApi/Validations/Rules/OpenApiInfoRules.cs index 88b534c02..337bf8687 100644 --- a/src/Microsoft.OpenApi/Validations/Rules/OpenApiInfoRules.cs +++ b/src/Microsoft.OpenApi/Validations/Rules/OpenApiInfoRules.cs @@ -17,7 +17,7 @@ public static class OpenApiInfoRules /// Validate the field is required. /// public static ValidationRule InfoRequiredFields => - new( + new(nameof(InfoRequiredFields), (context, item) => { // title diff --git a/src/Microsoft.OpenApi/Validations/Rules/OpenApiLicenseRules.cs b/src/Microsoft.OpenApi/Validations/Rules/OpenApiLicenseRules.cs index edbf19bf5..08f67d209 100644 --- a/src/Microsoft.OpenApi/Validations/Rules/OpenApiLicenseRules.cs +++ b/src/Microsoft.OpenApi/Validations/Rules/OpenApiLicenseRules.cs @@ -17,7 +17,7 @@ public static class OpenApiLicenseRules /// REQUIRED. /// public static ValidationRule LicenseRequiredFields => - new( + new(nameof(LicenseRequiredFields), (context, license) => { context.Enter("name"); diff --git a/src/Microsoft.OpenApi/Validations/Rules/OpenApiMediaTypeRules.cs b/src/Microsoft.OpenApi/Validations/Rules/OpenApiMediaTypeRules.cs index 6f7b65a6c..7ac09cbbf 100644 --- a/src/Microsoft.OpenApi/Validations/Rules/OpenApiMediaTypeRules.cs +++ b/src/Microsoft.OpenApi/Validations/Rules/OpenApiMediaTypeRules.cs @@ -24,7 +24,7 @@ public static class OpenApiMediaTypeRules /// Validate the data matches with the given data type. /// public static ValidationRule MediaTypeMismatchedDataType => - new( + new(nameof(MediaTypeMismatchedDataType), (context, mediaType) => { // example diff --git a/src/Microsoft.OpenApi/Validations/Rules/OpenApiOAuthFlowRules.cs b/src/Microsoft.OpenApi/Validations/Rules/OpenApiOAuthFlowRules.cs index de31d933d..c6db49d7c 100644 --- a/src/Microsoft.OpenApi/Validations/Rules/OpenApiOAuthFlowRules.cs +++ b/src/Microsoft.OpenApi/Validations/Rules/OpenApiOAuthFlowRules.cs @@ -17,7 +17,7 @@ public static class OpenApiOAuthFlowRules /// Validate the field is required. /// public static ValidationRule OAuthFlowRequiredFields => - new( + new(nameof(OAuthFlowRequiredFields), (context, flow) => { // authorizationUrl diff --git a/src/Microsoft.OpenApi/Validations/Rules/OpenApiParameterRules.cs b/src/Microsoft.OpenApi/Validations/Rules/OpenApiParameterRules.cs index 5c19ce1d9..a1a228134 100644 --- a/src/Microsoft.OpenApi/Validations/Rules/OpenApiParameterRules.cs +++ b/src/Microsoft.OpenApi/Validations/Rules/OpenApiParameterRules.cs @@ -17,7 +17,7 @@ public static class OpenApiParameterRules /// Validate the field is required. /// public static ValidationRule ParameterRequiredFields => - new( + new(nameof(ParameterRequiredFields), (context, item) => { // name @@ -43,7 +43,7 @@ public static class OpenApiParameterRules /// Validate the "required" field is true when "in" is path. /// public static ValidationRule RequiredMustBeTrueWhenInIsPath => - new( + new(nameof(RequiredMustBeTrueWhenInIsPath), (context, item) => { // required @@ -62,7 +62,7 @@ public static class OpenApiParameterRules /// Validate the data matches with the given data type. /// public static ValidationRule ParameterMismatchedDataType => - new( + new(nameof(ParameterMismatchedDataType), (context, parameter) => { // example @@ -100,7 +100,7 @@ public static class OpenApiParameterRules /// Validate that a path parameter should always appear in the path /// public static ValidationRule PathParameterShouldBeInThePath => - new( + new(nameof(PathParameterShouldBeInThePath), (context, parameter) => { if (parameter.In == ParameterLocation.Path && diff --git a/src/Microsoft.OpenApi/Validations/Rules/OpenApiPathsRules.cs b/src/Microsoft.OpenApi/Validations/Rules/OpenApiPathsRules.cs index d248edb3c..9c23f7220 100644 --- a/src/Microsoft.OpenApi/Validations/Rules/OpenApiPathsRules.cs +++ b/src/Microsoft.OpenApi/Validations/Rules/OpenApiPathsRules.cs @@ -18,7 +18,7 @@ public static class OpenApiPathsRules /// A relative path to an individual endpoint. The field name MUST begin with a slash. /// public static ValidationRule PathNameMustBeginWithSlash => - new( + new(nameof(PathNameMustBeginWithSlash), (context, item) => { foreach (var pathName in item.Keys) @@ -39,7 +39,7 @@ public static class OpenApiPathsRules /// A relative path to an individual endpoint. The field name MUST begin with a slash. /// public static ValidationRule PathMustBeUnique => - new ValidationRule( + new ValidationRule(nameof(PathMustBeUnique), (context, item) => { var hashSet = new HashSet(); diff --git a/src/Microsoft.OpenApi/Validations/Rules/OpenApiResponseRules.cs b/src/Microsoft.OpenApi/Validations/Rules/OpenApiResponseRules.cs index 0f725c90e..f30b49ea0 100644 --- a/src/Microsoft.OpenApi/Validations/Rules/OpenApiResponseRules.cs +++ b/src/Microsoft.OpenApi/Validations/Rules/OpenApiResponseRules.cs @@ -17,7 +17,7 @@ public static class OpenApiResponseRules /// Validate the field is required. /// public static ValidationRule ResponseRequiredFields => - new( + new(nameof(ResponseRequiredFields), (context, response) => { // description diff --git a/src/Microsoft.OpenApi/Validations/Rules/OpenApiResponsesRules.cs b/src/Microsoft.OpenApi/Validations/Rules/OpenApiResponsesRules.cs index 1afe9a388..a2b91dc31 100644 --- a/src/Microsoft.OpenApi/Validations/Rules/OpenApiResponsesRules.cs +++ b/src/Microsoft.OpenApi/Validations/Rules/OpenApiResponsesRules.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.Linq; @@ -17,7 +17,7 @@ public static class OpenApiResponsesRules /// An OpenAPI operation must contain at least one response /// public static ValidationRule ResponsesMustContainAtLeastOneResponse => - new( + new(nameof(ResponsesMustContainAtLeastOneResponse), (context, responses) => { if (!responses.Keys.Any()) @@ -31,7 +31,7 @@ public static class OpenApiResponsesRules /// The response key must either be "default" or an HTTP status code (1xx, 2xx, 3xx, 4xx, 5xx). /// public static ValidationRule ResponsesMustBeIdentifiedByDefaultOrStatusCode => - new( + new(nameof(ResponsesMustBeIdentifiedByDefaultOrStatusCode), (context, responses) => { foreach (var key in responses.Keys) diff --git a/src/Microsoft.OpenApi/Validations/Rules/OpenApiSchemaRules.cs b/src/Microsoft.OpenApi/Validations/Rules/OpenApiSchemaRules.cs index a936766c8..411f26fd0 100644 --- a/src/Microsoft.OpenApi/Validations/Rules/OpenApiSchemaRules.cs +++ b/src/Microsoft.OpenApi/Validations/Rules/OpenApiSchemaRules.cs @@ -17,7 +17,7 @@ public static class OpenApiSchemaRules /// Validate the data matches with the given data type. /// public static ValidationRule SchemaMismatchedDataType => - new( + new(nameof(SchemaMismatchedDataType), (context, schema) => { // default @@ -60,7 +60,7 @@ public static class OpenApiSchemaRules /// Validates Schema Discriminator /// public static ValidationRule ValidateSchemaDiscriminator => - new( + new(nameof(ValidateSchemaDiscriminator), (context, schema) => { // discriminator diff --git a/src/Microsoft.OpenApi/Validations/Rules/OpenApiServerRules.cs b/src/Microsoft.OpenApi/Validations/Rules/OpenApiServerRules.cs index 292fd1fd0..dd11a661d 100644 --- a/src/Microsoft.OpenApi/Validations/Rules/OpenApiServerRules.cs +++ b/src/Microsoft.OpenApi/Validations/Rules/OpenApiServerRules.cs @@ -17,7 +17,7 @@ public static class OpenApiServerRules /// Validate the field is required. /// public static ValidationRule ServerRequiredFields => - new( + new(nameof(ServerRequiredFields), (context, server) => { context.Enter("url"); diff --git a/src/Microsoft.OpenApi/Validations/Rules/OpenApiTagRules.cs b/src/Microsoft.OpenApi/Validations/Rules/OpenApiTagRules.cs index f28732e1e..cc006f971 100644 --- a/src/Microsoft.OpenApi/Validations/Rules/OpenApiTagRules.cs +++ b/src/Microsoft.OpenApi/Validations/Rules/OpenApiTagRules.cs @@ -17,7 +17,7 @@ public static class OpenApiTagRules /// Validate the field is required. /// public static ValidationRule TagRequiredFields => - new( + new(nameof(TagRequiredFields), (context, tag) => { context.Enter("name"); diff --git a/src/Microsoft.OpenApi/Validations/ValidationRule.cs b/src/Microsoft.OpenApi/Validations/ValidationRule.cs index 48907635d..4f0f00383 100644 --- a/src/Microsoft.OpenApi/Validations/ValidationRule.cs +++ b/src/Microsoft.OpenApi/Validations/ValidationRule.cs @@ -2,7 +2,7 @@ // Licensed under the MIT license. using System; -using System.Globalization; +using Microsoft.OpenApi.Exceptions; using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Properties; @@ -18,12 +18,22 @@ public abstract class ValidationRule /// internal abstract Type ElementType { get; } + /// + /// Validation rule Name. + /// + public string Name { get; } + /// /// Validate the object. /// /// The context. /// The object item. internal abstract void Evaluate(IValidationContext context, object item); + + internal ValidationRule(string name) + { + Name = !string.IsNullOrEmpty(name) ? name : throw new ArgumentNullException(nameof(name)); + } } /// @@ -33,14 +43,26 @@ public abstract class ValidationRule public class ValidationRule : ValidationRule where T : IOpenApiElement { private readonly Action _validate; + + /// + /// Initializes a new instance of the class. + /// + /// Action to perform the validation. + [Obsolete("Please use the other constructor and specify a name")] + public ValidationRule(Action validate) + : this (Guid.NewGuid().ToString("D"), validate) + { + } /// /// Initializes a new instance of the class. /// + /// Validation rule name. /// Action to perform the validation. - public ValidationRule(Action validate) + public ValidationRule(string name, Action validate) + : base(name) { - _validate = Utils.CheckArgumentNull(validate); + _validate = Utils.CheckArgumentNull(validate); } internal override Type ElementType diff --git a/src/Microsoft.OpenApi/Validations/ValidationRuleSet.cs b/src/Microsoft.OpenApi/Validations/ValidationRuleSet.cs index a5c6fee83..3dd916755 100644 --- a/src/Microsoft.OpenApi/Validations/ValidationRuleSet.cs +++ b/src/Microsoft.OpenApi/Validations/ValidationRuleSet.cs @@ -134,6 +134,30 @@ public void Add(ValidationRule rule) item.Add(rule); } + /// + /// Remove a rule by its name from all types it is used by. + /// + /// Name of the rule. + public void Remove(string ruleName) + { + foreach (KeyValuePair> rule in _rules) + { + _rules[rule.Key] = rule.Value.Where(vr => !vr.Name.Equals(ruleName, StringComparison.Ordinal)).ToList(); + } + + // Remove types with no rule + _rules = _rules.Where(r => r.Value.Any()).ToDictionary(r => r.Key, r => r.Value); + } + + /// + /// Remove a rule by element type. + /// + /// Type of the rule. + public void Remove(Type type) + { + _rules.Remove(type); + } + /// /// Get the enumerator. /// 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 7a89c0044..0bf7fcc3e 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/Microsoft.OpenApi.Readers.Tests.csproj +++ b/test/Microsoft.OpenApi.Readers.Tests/Microsoft.OpenApi.Readers.Tests.csproj @@ -28,6 +28,7 @@ + \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiOperationTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiOperationTests.cs index 326c16969..86d22f8a5 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiOperationTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiOperationTests.cs @@ -11,6 +11,8 @@ using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Readers.ParseNodes; using Microsoft.OpenApi.Readers.V2; +using Microsoft.OpenApi.Readers.V3; +using Microsoft.OpenApi.Tests; using Xunit; namespace Microsoft.OpenApi.Readers.Tests.V2Tests @@ -434,5 +436,208 @@ public void ParseOperationWithBodyAndEmptyConsumesSetsRequestBodySchemaIfExists( // Assert operation.Should().BeEquivalentTo(_operationWithBody); } + + [Fact] + public void ParseV2ResponseWithExamplesExtensionWorks() + { + // Arrange + MapNode node; + using (var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "opWithResponseExamplesExtension.yaml"))) + { + node = TestHelper.CreateYamlMapNode(stream); + } + + // Act + var operation = OpenApiV2Deserializer.LoadOperation(node); + var actual = operation.SerializeAsYaml(OpenApiSpecVersion.OpenApi3_0); + + // Assert + var expected = @"summary: Get all pets +responses: + '200': + description: Successful response + content: + application/json: + schema: + type: array + items: + type: object + properties: + name: + type: string + age: + type: integer + examples: + example1: + summary: Example - List of Pets + value: + - name: Buddy + age: 2 + - name: Whiskers + age: 1 + example2: + summary: Example - Playful Cat + value: + name: Whiskers + age: 1"; + + // Assert + actual = actual.MakeLineBreaksEnvironmentNeutral(); + expected = expected.MakeLineBreaksEnvironmentNeutral(); + actual.Should().Be(expected); + } + + [Fact] + public void LoadV3ExamplesInResponseAsExtensionsWorks() + { + // Arrange + MapNode node; + using (var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "v3OperationWithResponseExamples.yaml"))) + { + node = TestHelper.CreateYamlMapNode(stream); + } + + // Act + var operation = OpenApiV3Deserializer.LoadOperation(node); + var actual = operation.SerializeAsYaml(OpenApiSpecVersion.OpenApi2_0); + + // Assert + var expected = @"summary: Get all pets +produces: + - application/json +responses: + '200': + description: Successful response + schema: + type: array + items: + type: object + properties: + name: + type: string + age: + type: integer + x-examples: + example1: + summary: Example - List of Pets + value: + - name: Buddy + age: 2 + - name: Whiskers + age: 1 + example2: + summary: Example - Playful Cat + value: + name: Whiskers + age: 1"; + + // Assert + actual = actual.MakeLineBreaksEnvironmentNeutral(); + expected = expected.MakeLineBreaksEnvironmentNeutral(); + actual.Should().Be(expected); + } + + [Fact] + public void LoadV2OperationWithBodyParameterExamplesWorks() + { + // Arrange + MapNode node; + using (var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "opWithBodyParameterExamples.yaml"))) + { + node = TestHelper.CreateYamlMapNode(stream); + } + + // Act + var operation = OpenApiV2Deserializer.LoadOperation(node); + var actual = operation.SerializeAsYaml(OpenApiSpecVersion.OpenApi3_0); + + // Assert + var expected = @"summary: Get all pets +requestBody: + content: + application/json: + schema: + type: array + items: + type: object + properties: + name: + type: string + age: + type: integer + examples: + example1: + summary: Example - List of Pets + value: + - name: Buddy + age: 2 + - name: Whiskers + age: 1 + example2: + summary: Example - Playful Cat + value: + name: Whiskers + age: 1 + required: true + x-bodyName: body +responses: { }"; + + // Assert + actual = actual.MakeLineBreaksEnvironmentNeutral(); + expected = expected.MakeLineBreaksEnvironmentNeutral(); + actual.Should().Be(expected); + } + + [Fact] + public void LoadV3ExamplesInRequestBodyParameterAsExtensionsWorks() + { + // Arrange + MapNode node; + using (var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "v3OperationWithBodyParameterExamples.yaml"))) + { + node = TestHelper.CreateYamlMapNode(stream); + } + + // Act + var operation = OpenApiV3Deserializer.LoadOperation(node); + var actual = operation.SerializeAsYaml(OpenApiSpecVersion.OpenApi2_0); + + // Assert + var expected = @"summary: Get all pets +consumes: + - application/json +parameters: + - in: body + name: body + required: true + schema: + type: array + items: + type: object + properties: + name: + type: string + age: + type: integer + x-examples: + example1: + summary: Example - List of Pets + value: + - name: Buddy + age: 2 + - name: Whiskers + age: 1 + example2: + summary: Example - Playful Cat + value: + name: Whiskers + age: 1 +responses: { }"; + + // Assert + actual = actual.MakeLineBreaksEnvironmentNeutral(); + expected = expected.MakeLineBreaksEnvironmentNeutral(); + actual.Should().Be(expected); + } } } diff --git a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/Samples/OpenApiOperation/opWithBodyParameterExamples.yaml b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/Samples/OpenApiOperation/opWithBodyParameterExamples.yaml new file mode 100644 index 000000000..e2ffcc7df --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/Samples/OpenApiOperation/opWithBodyParameterExamples.yaml @@ -0,0 +1,29 @@ +summary: Get all pets +consumes: + - application/json +parameters: + - in: body + name: body + required: true + schema: + type: array + items: + type: object + properties: + name: + type: string + age: + type: integer + x-examples: + example1: + summary: Example - List of Pets + value: + - name: Buddy + age: 2 + - name: Whiskers + age: 1 + example2: + summary: Example - Playful Cat + value: + name: Whiskers + age: 1 \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/Samples/OpenApiOperation/opWithResponseExamplesExtension.yaml b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/Samples/OpenApiOperation/opWithResponseExamplesExtension.yaml new file mode 100644 index 000000000..5dcc89d97 --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/Samples/OpenApiOperation/opWithResponseExamplesExtension.yaml @@ -0,0 +1,28 @@ +summary: Get all pets +produces: +- application/json +responses: + '200': + description: Successful response + schema: + type: array + items: + type: object + properties: + name: + type: string + age: + type: integer + x-examples: + example1: + summary: Example - List of Pets + value: + - name: "Buddy" + age: 2 + - name: "Whiskers" + age: 1 + example2: + summary: Example - Playful Cat + value: + name: "Whiskers" + age: 1 \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/Samples/OpenApiOperation/v3OperationWithBodyParameterExamples.yaml b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/Samples/OpenApiOperation/v3OperationWithBodyParameterExamples.yaml new file mode 100644 index 000000000..a0358125a --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/Samples/OpenApiOperation/v3OperationWithBodyParameterExamples.yaml @@ -0,0 +1,27 @@ +summary: Get all pets +requestBody: + required: true + content: + application/json: + schema: + type: array + items: + type: object + properties: + name: + type: string + age: + type: integer + examples: + example1: + summary: Example - List of Pets + value: + - name: "Buddy" + age: 2 + - name: "Whiskers" + age: 1 + example2: + summary: Example - Playful Cat + value: + name: "Whiskers" + age: 1 \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/Samples/OpenApiOperation/v3OperationWithResponseExamples.yaml b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/Samples/OpenApiOperation/v3OperationWithResponseExamples.yaml new file mode 100644 index 000000000..c3b124685 --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/Samples/OpenApiOperation/v3OperationWithResponseExamples.yaml @@ -0,0 +1,28 @@ +summary: Get all pets +responses: + '200': + description: Successful response + content: + application/json: + schema: + type: array + items: + type: object + properties: + name: + type: string + age: + type: integer + examples: + example1: + summary: Example - List of Pets + value: + - name: "Buddy" + age: 2 + - name: "Whiskers" + age: 1 + example2: + summary: Example - Playful Cat + value: + name: "Whiskers" + age: 1 \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj b/test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj index 8fce973d7..6f68a8cee 100644 --- a/test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj +++ b/test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj @@ -15,7 +15,7 @@ - + diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiCallbackTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiCallbackTests.cs index 6b6a8d734..ad26b8288 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiCallbackTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiCallbackTests.cs @@ -13,7 +13,6 @@ namespace Microsoft.OpenApi.Tests.Models { [Collection("DefaultSettings")] - [UsesVerify] public class OpenApiCallbackTests { public static OpenApiCallback AdvancedCallback = new() diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.cs index ea0b98956..7a7f883f6 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.cs @@ -17,7 +17,6 @@ namespace Microsoft.OpenApi.Tests.Models { [Collection("DefaultSettings")] - [UsesVerify] public class OpenApiDocumentTests { public static OpenApiComponents TopLevelReferencingComponents = new() diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiExampleTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiExampleTests.cs index 14561debd..a847016d0 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiExampleTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiExampleTests.cs @@ -15,7 +15,6 @@ namespace Microsoft.OpenApi.Tests.Models { [Collection("DefaultSettings")] - [UsesVerify] public class OpenApiExampleTests { public static OpenApiExample AdvancedExample = new() diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiHeaderTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiHeaderTests.cs index b54c2457e..85316cf3b 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiHeaderTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiHeaderTests.cs @@ -12,7 +12,6 @@ namespace Microsoft.OpenApi.Tests.Models { [Collection("DefaultSettings")] - [UsesVerify] public class OpenApiHeaderTests { public static OpenApiHeader AdvancedHeader = new() diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiLinkTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiLinkTests.cs index 2f4a532a7..f1a67c330 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiLinkTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiLinkTests.cs @@ -14,7 +14,6 @@ namespace Microsoft.OpenApi.Tests.Models { [Collection("DefaultSettings")] - [UsesVerify] public class OpenApiLinkTests { public static readonly OpenApiLink AdvancedLink = new() diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiParameterTests.SerializeParameterWithSchemaReferenceAsV2JsonWorksAsync_produceTerseOutput=False.verified.txt b/test/Microsoft.OpenApi.Tests/Models/OpenApiParameterTests.SerializeParameterWithSchemaReferenceAsV2JsonWorksAsync_produceTerseOutput=False.verified.txt index 0542c58ce..744f8451c 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiParameterTests.SerializeParameterWithSchemaReferenceAsV2JsonWorksAsync_produceTerseOutput=False.verified.txt +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiParameterTests.SerializeParameterWithSchemaReferenceAsV2JsonWorksAsync_produceTerseOutput=False.verified.txt @@ -3,5 +3,11 @@ "name": "name1", "description": "description1", "required": true, - "type": "string" + "type": "string", + "x-examples": { + "test": { + "summary": "summary3", + "description": "description3" + } + } } \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiParameterTests.SerializeParameterWithSchemaReferenceAsV2JsonWorksAsync_produceTerseOutput=True.verified.txt b/test/Microsoft.OpenApi.Tests/Models/OpenApiParameterTests.SerializeParameterWithSchemaReferenceAsV2JsonWorksAsync_produceTerseOutput=True.verified.txt index b80b263d3..26b158865 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiParameterTests.SerializeParameterWithSchemaReferenceAsV2JsonWorksAsync_produceTerseOutput=True.verified.txt +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiParameterTests.SerializeParameterWithSchemaReferenceAsV2JsonWorksAsync_produceTerseOutput=True.verified.txt @@ -1 +1 @@ -{"in":"header","name":"name1","description":"description1","required":true,"type":"string"} \ No newline at end of file +{"in":"header","name":"name1","description":"description1","required":true,"type":"string","x-examples":{"test":{"summary":"summary3","description":"description3"}}} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiParameterTests.SerializeParameterWithSchemaTypeObjectAsV2JsonWorksAsync_produceTerseOutput=False.verified.txt b/test/Microsoft.OpenApi.Tests/Models/OpenApiParameterTests.SerializeParameterWithSchemaTypeObjectAsV2JsonWorksAsync_produceTerseOutput=False.verified.txt index 0542c58ce..744f8451c 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiParameterTests.SerializeParameterWithSchemaTypeObjectAsV2JsonWorksAsync_produceTerseOutput=False.verified.txt +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiParameterTests.SerializeParameterWithSchemaTypeObjectAsV2JsonWorksAsync_produceTerseOutput=False.verified.txt @@ -3,5 +3,11 @@ "name": "name1", "description": "description1", "required": true, - "type": "string" + "type": "string", + "x-examples": { + "test": { + "summary": "summary3", + "description": "description3" + } + } } \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiParameterTests.SerializeParameterWithSchemaTypeObjectAsV2JsonWorksAsync_produceTerseOutput=True.verified.txt b/test/Microsoft.OpenApi.Tests/Models/OpenApiParameterTests.SerializeParameterWithSchemaTypeObjectAsV2JsonWorksAsync_produceTerseOutput=True.verified.txt index b80b263d3..26b158865 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiParameterTests.SerializeParameterWithSchemaTypeObjectAsV2JsonWorksAsync_produceTerseOutput=True.verified.txt +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiParameterTests.SerializeParameterWithSchemaTypeObjectAsV2JsonWorksAsync_produceTerseOutput=True.verified.txt @@ -1 +1 @@ -{"in":"header","name":"name1","description":"description1","required":true,"type":"string"} \ No newline at end of file +{"in":"header","name":"name1","description":"description1","required":true,"type":"string","x-examples":{"test":{"summary":"summary3","description":"description3"}}} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiParameterTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiParameterTests.cs index a49f415e3..2f02ff7dd 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiParameterTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiParameterTests.cs @@ -16,7 +16,6 @@ namespace Microsoft.OpenApi.Tests.Models { [Collection("DefaultSettings")] - [UsesVerify] public class OpenApiParameterTests { public static OpenApiParameter BasicParameter = new() @@ -273,7 +272,13 @@ public void SerializeAdvancedParameterAsV2JsonWorks() "name": "name1", "description": "description1", "required": true, - "format": "double" + "format": "double", + "x-examples": { + "test": { + "summary": "summary3", + "description": "description3" + } + } } """; diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiRequestBodyTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiRequestBodyTests.cs index 08ef42fe7..519c26607 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiRequestBodyTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiRequestBodyTests.cs @@ -12,7 +12,6 @@ namespace Microsoft.OpenApi.Tests.Models { [Collection("DefaultSettings")] - [UsesVerify] public class OpenApiRequestBodyTests { public static OpenApiRequestBody AdvancedRequestBody = new() diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiResponseTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiResponseTests.cs index ea3a6ee29..fdd1207a6 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiResponseTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiResponseTests.cs @@ -17,7 +17,6 @@ namespace Microsoft.OpenApi.Tests.Models { [Collection("DefaultSettings")] - [UsesVerify] public class OpenApiResponseTests { public static OpenApiResponse BasicResponse = new(); diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.cs index c4a9ddd53..5d8320c62 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.cs @@ -20,7 +20,6 @@ namespace Microsoft.OpenApi.Tests.Models { [Collection("DefaultSettings")] - [UsesVerify] public class OpenApiSchemaTests { public static OpenApiSchema BasicSchema = new(); diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiSecuritySchemeTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiSecuritySchemeTests.cs index 5cc4a19cb..5df97e135 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiSecuritySchemeTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiSecuritySchemeTests.cs @@ -15,7 +15,6 @@ namespace Microsoft.OpenApi.Tests.Models { [Collection("DefaultSettings")] - [UsesVerify] public class OpenApiSecuritySchemeTests { public static OpenApiSecurityScheme ApiKeySecurityScheme = new() diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiTagTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiTagTests.cs index fa6690c94..875eb960c 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiTagTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiTagTests.cs @@ -16,7 +16,6 @@ namespace Microsoft.OpenApi.Tests.Models { [Collection("DefaultSettings")] - [UsesVerify] public class OpenApiTagTests { public static OpenApiTag BasicTag = new(); diff --git a/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt b/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt index a5309e46d..37f40bb11 100755 --- a/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt +++ b/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt @@ -480,6 +480,7 @@ namespace Microsoft.OpenApi.Models public const string Enum = "enum"; public const string Example = "example"; public const string Examples = "examples"; + public const string ExamplesExtension = "x-examples"; public const string ExclusiveMaximum = "exclusiveMaximum"; public const string ExclusiveMinimum = "exclusiveMinimum"; public const string Explode = "explode"; @@ -642,6 +643,7 @@ namespace Microsoft.OpenApi.Models public bool UnresolvedReference { get; set; } public Microsoft.OpenApi.Any.IOpenApiAny Value { get; set; } public Microsoft.OpenApi.Models.OpenApiExample GetEffective(Microsoft.OpenApi.Models.OpenApiDocument doc) { } + public void Serialize(Microsoft.OpenApi.Writers.IOpenApiWriter writer, Microsoft.OpenApi.OpenApiSpecVersion version) { } public void SerializeAsV2(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } public void SerializeAsV2WithoutReference(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } public void SerializeAsV3(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } @@ -1347,7 +1349,7 @@ namespace Microsoft.OpenApi.Validations } public abstract class ValidationRule { - protected ValidationRule() { } + public string Name { get; } } public sealed class ValidationRuleSet : System.Collections.Generic.IEnumerable, System.Collections.IEnumerable { @@ -1358,13 +1360,17 @@ namespace Microsoft.OpenApi.Validations public void Add(Microsoft.OpenApi.Validations.ValidationRule rule) { } public System.Collections.Generic.IList FindRules(System.Type type) { } public System.Collections.Generic.IEnumerator GetEnumerator() { } + public void Remove(string ruleName) { } + public void Remove(System.Type type) { } public static Microsoft.OpenApi.Validations.ValidationRuleSet GetDefaultRuleSet() { } public static Microsoft.OpenApi.Validations.ValidationRuleSet GetEmptyRuleSet() { } } public class ValidationRule : Microsoft.OpenApi.Validations.ValidationRule where T : Microsoft.OpenApi.Interfaces.IOpenApiElement { + [System.Obsolete("Please use the other constructor and specify a name")] public ValidationRule(System.Action validate) { } + public ValidationRule(string name, System.Action validate) { } } } namespace Microsoft.OpenApi.Validations.Rules diff --git a/test/Microsoft.OpenApi.Tests/Services/OpenApiUrlTreeNodeTests.cs b/test/Microsoft.OpenApi.Tests/Services/OpenApiUrlTreeNodeTests.cs index 94164c3cd..511c6c5bd 100644 --- a/test/Microsoft.OpenApi.Tests/Services/OpenApiUrlTreeNodeTests.cs +++ b/test/Microsoft.OpenApi.Tests/Services/OpenApiUrlTreeNodeTests.cs @@ -12,7 +12,6 @@ namespace Microsoft.OpenApi.Tests.Services { - [UsesVerify] public class OpenApiUrlTreeNodeTests { private OpenApiDocument OpenApiDocumentSample_1 => new() diff --git a/test/Microsoft.OpenApi.Tests/Services/OpenApiValidatorTests.cs b/test/Microsoft.OpenApi.Tests/Services/OpenApiValidatorTests.cs index 02eba9347..d027f40b8 100644 --- a/test/Microsoft.OpenApi.Tests/Services/OpenApiValidatorTests.cs +++ b/test/Microsoft.OpenApi.Tests/Services/OpenApiValidatorTests.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using FluentAssertions; +using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Properties; @@ -96,7 +97,7 @@ public void ValidateCustomExtension() var ruleset = ValidationRuleSet.GetDefaultRuleSet(); ruleset.Add( - new ValidationRule( + new ValidationRule("FooExtensionRule", (context, item) => { if (item.Bar == "hey") @@ -133,6 +134,44 @@ public void ValidateCustomExtension() new OpenApiValidatorError("FooExtensionRule", "#/info/x-foo", "Don't say hey") }); } + + [Fact] + public void RemoveRuleByName_Invalid() + { + Assert.Throws(() => new ValidationRule(null, (vc, oaa) => { })); + Assert.Throws(() => new ValidationRule(string.Empty, (vc, oaa) => { })); + } + + [Fact] + public void RemoveRuleByName() + { + var ruleset = ValidationRuleSet.GetDefaultRuleSet(); + int expected = ruleset.Rules.Count - 1; + ruleset.Remove("KeyMustBeRegularExpression"); + + Assert.Equal(expected, ruleset.Rules.Count); + + ruleset.Remove("KeyMustBeRegularExpression"); + ruleset.Remove("UnknownName"); + + Assert.Equal(expected, ruleset.Rules.Count); + } + + [Fact] + public void RemoveRuleByType() + { + var ruleset = ValidationRuleSet.GetDefaultRuleSet(); + int expected = ruleset.Rules.Count - 1; + + ruleset.Remove(typeof(OpenApiComponents)); + + Assert.Equal(expected, ruleset.Rules.Count); + + ruleset.Remove(typeof(OpenApiComponents)); + ruleset.Remove(typeof(int)); + + Assert.Equal(expected, ruleset.Rules.Count); + } } internal class FooExtension : IOpenApiExtension, IOpenApiElement diff --git a/test/Microsoft.OpenApi.Tests/Validations/OpenApiReferenceValidationTests.cs b/test/Microsoft.OpenApi.Tests/Validations/OpenApiReferenceValidationTests.cs index 4c252716f..11e5edc9e 100644 --- a/test/Microsoft.OpenApi.Tests/Validations/OpenApiReferenceValidationTests.cs +++ b/test/Microsoft.OpenApi.Tests/Validations/OpenApiReferenceValidationTests.cs @@ -152,7 +152,7 @@ public void UnresolvedSchemaReferencedShouldNotBeValidated() public class AlwaysFailRule : ValidationRule where T : IOpenApiElement { - public AlwaysFailRule() : base((c, _) => c.CreateError("x", "y")) + public AlwaysFailRule() : base("AlwaysFailRule", (c, _) => c.CreateError("x", "y")) { } } diff --git a/test/Microsoft.OpenApi.Tests/Writers/OpenApiWriterAnyExtensionsTests.cs b/test/Microsoft.OpenApi.Tests/Writers/OpenApiWriterAnyExtensionsTests.cs index 1e632572e..686e3a08a 100644 --- a/test/Microsoft.OpenApi.Tests/Writers/OpenApiWriterAnyExtensionsTests.cs +++ b/test/Microsoft.OpenApi.Tests/Writers/OpenApiWriterAnyExtensionsTests.cs @@ -16,7 +16,6 @@ namespace Microsoft.OpenApi.Tests.Writers { [Collection("DefaultSettings")] - [UsesVerify] public class OpenApiWriterAnyExtensionsTests { static bool[] shouldProduceTerseOutputValues = new[] { true, false };