diff --git a/.azure-pipelines/ci-build.yml b/.azure-pipelines/ci-build.yml index f381a4303..28d442b3a 100644 --- a/.azure-pipelines/ci-build.yml +++ b/.azure-pipelines/ci-build.yml @@ -210,6 +210,13 @@ extends: dependsOn: build jobs: - deployment: deploy_hidi + templateContext: + type: releaseJob + isProduction: true + inputs: + - input: pipelineArtifact + artifactName: Nugets + targetPath: '$(Pipeline.Workspace)' dependsOn: [] environment: nuget-org strategy: @@ -218,11 +225,6 @@ extends: pool: vmImage: ubuntu-latest steps: - - task: DownloadPipelineArtifact@2 - displayName: Download nupkg from artifacts - inputs: - artifact: Nugets - source: current - task: DownloadPipelineArtifact@2 displayName: Download hidi executable from artifacts inputs: @@ -264,6 +266,13 @@ extends: ]' - deployment: deploy_lib + templateContext: + type: releaseJob + isProduction: true + inputs: + - input: pipelineArtifact + artifactName: Nugets + targetPath: '$(Pipeline.Workspace)' dependsOn: [] environment: nuget-org strategy: @@ -272,11 +281,6 @@ extends: pool: vmImage: ubuntu-latest steps: - - task: DownloadPipelineArtifact@2 - displayName: Download nupkg from artifacts - inputs: - artifact: Nugets - source: current - powershell: | $fileNames = "$(Pipeline.Workspace)/Nugets/Microsoft.OpenApi.Hidi.*.nupkg", "$(Pipeline.Workspace)/Nugets/Microsoft.OpenApi.Readers.*.nupkg", "$(Pipeline.Workspace)/Nugets/Microsoft.OpenApi.Workbench.*.nupkg" foreach($fileName in $fileNames) { @@ -294,6 +298,13 @@ extends: publishFeedCredentials: 'OpenAPI Nuget Connection' - deployment: deploy_readers + templateContext: + type: releaseJob + isProduction: true + inputs: + - input: pipelineArtifact + artifactName: Nugets + targetPath: '$(Pipeline.Workspace)' dependsOn: deploy_lib environment: nuget-org strategy: @@ -302,11 +313,6 @@ extends: pool: vmImage: ubuntu-latest steps: - - task: DownloadPipelineArtifact@2 - displayName: Download nupkg from artifacts - inputs: - artifact: Nugets - source: current - task: 1ES.PublishNuget@1 displayName: 'NuGet push' inputs: diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index a61cbd408..5bb7a32bd 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1 +1 @@ -* @irvinesunday @darrelmiller @gavinbarron @millicentachieng @MaggieKimani1 @andrueastman +* @irvinesunday @darrelmiller @gavinbarron @millicentachieng @MaggieKimani1 @andrueastman @baywet diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index a2a2bb104..e01c89f0f 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -30,13 +30,13 @@ jobs: id: getversion - name: Push to GitHub Packages - Nightly if: ${{ github.ref == 'refs/heads/vnext' }} - uses: docker/build-push-action@v6.9.0 + uses: docker/build-push-action@v6.10.0 with: push: true tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:nightly - name: Push to GitHub Packages - Release if: ${{ github.ref == 'refs/heads/master' }} - uses: docker/build-push-action@v6.9.0 + uses: docker/build-push-action@v6.10.0 with: push: true tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest,${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.getversion.outputs.version }} diff --git a/.gitignore b/.gitignore index 940794e60..4caae17f4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,7 @@ ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. ## -## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore +## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore # User-specific files *.suo diff --git a/.vscode/launch.json b/.vscode/launch.json index 2fa4340b3..1ff544a39 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -4,7 +4,7 @@ { // Use IntelliSense to find out which attributes exist for C# debugging // Use hover for the description of the existing attributes - // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md + // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/main/debugger-launchjson.md "name": "Launch Hidi", "type": "coreclr", "request": "launch", @@ -22,7 +22,7 @@ { // Use IntelliSense to find out which attributes exist for C# debugging // Use hover for the description of the existing attributes - // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md + // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/main/debugger-launchjson.md "name": "Launch Workbench", "type": "coreclr", "request": "launch", diff --git a/README.md b/README.md index c804787c1..de069aeda 100644 --- a/README.md +++ b/README.md @@ -81,7 +81,7 @@ var httpClient = new HttpClient BaseAddress = new Uri("https://raw.githubusercontent.com/OAI/OpenAPI-Specification/") }; -var stream = await httpClient.GetStreamAsync("master/examples/v3.0/petstore.yaml"); +var stream = await httpClient.GetStreamAsync("main/examples/v3.0/petstore.yaml"); // Read V3 as YAML var openApiDocument = new OpenApiStreamReader().Read(stream, out var diagnostic); diff --git a/src/Microsoft.OpenApi.Hidi/Microsoft.OpenApi.Hidi.csproj b/src/Microsoft.OpenApi.Hidi/Microsoft.OpenApi.Hidi.csproj index 07f2e3e7d..a42c91879 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.4.16 + 2.0.0-preview3 OpenAPI.NET CLI tool for slicing OpenAPI documents true @@ -29,23 +29,23 @@ - - - + + + - + runtime; build; native; contentfiles; analyzers; buildtransitive all - + - + diff --git a/src/Microsoft.OpenApi.Hidi/OpenApiService.cs b/src/Microsoft.OpenApi.Hidi/OpenApiService.cs index 7dfb5d797..8a121f7ad 100644 --- a/src/Microsoft.OpenApi.Hidi/OpenApiService.cs +++ b/src/Microsoft.OpenApi.Hidi/OpenApiService.cs @@ -255,7 +255,7 @@ private static async Task GetOpenApiAsync(HidiOptions options, { stream = await GetStreamAsync(options.OpenApi, logger, cancellationToken).ConfigureAwait(false); var result = await ParseOpenApiAsync(options.OpenApi, options.InlineExternal, logger, stream, cancellationToken).ConfigureAwait(false); - document = result.OpenApiDocument; + document = result.Document; } else throw new InvalidOperationException("No input file path or URL provided"); @@ -358,7 +358,7 @@ private static MemoryStream ApplyFilterToCsdl(Stream csdlStream, string entitySe { var statsVisitor = new StatsVisitor(); var walker = new OpenApiWalker(statsVisitor); - walker.Walk(result.OpenApiDocument); + walker.Walk(result.Document); logger.LogTrace("Finished walking through the OpenApi document. Generating a statistics report.."); #pragma warning disable CA2254 @@ -377,7 +377,7 @@ private static MemoryStream ApplyFilterToCsdl(Stream csdlStream, string entitySe if (result is null) return null; - return result.OpenApiDiagnostic.Errors.Count == 0; + return result.Diagnostic.Errors.Count == 0; } private static async Task ParseOpenApiAsync(string openApiFile, bool inlineExternal, ILogger logger, Stream stream, CancellationToken cancellationToken = default) @@ -439,7 +439,7 @@ public static OpenApiDocument FixReferences(OpenApiDocument document, string for var sb = new StringBuilder(); document.SerializeAsV3(new OpenApiYamlWriter(new StringWriter(sb))); - var doc = OpenApiDocument.Parse(sb.ToString(), format).OpenApiDocument; + var doc = OpenApiDocument.Parse(sb.ToString(), format).Document; return doc; } @@ -649,7 +649,7 @@ private static string GetInputPathExtension(string? openapi = null, string? csdl private static void LogErrors(ILogger logger, ReadResult result) { - var context = result.OpenApiDiagnostic; + var context = result.Diagnostic; if (context.Errors.Count != 0) { using (logger.BeginScope("Detected errors")) diff --git a/src/Microsoft.OpenApi.Hidi/StatsVisitor.cs b/src/Microsoft.OpenApi.Hidi/StatsVisitor.cs index b6af07778..d1f6f7f64 100644 --- a/src/Microsoft.OpenApi.Hidi/StatsVisitor.cs +++ b/src/Microsoft.OpenApi.Hidi/StatsVisitor.cs @@ -61,7 +61,7 @@ public override void Visit(OpenApiOperation operation) public int LinkCount { get; set; } - public override void Visit(OpenApiLink operation) + public override void Visit(OpenApiLink link) { LinkCount++; } diff --git a/src/Microsoft.OpenApi.Readers/Microsoft.OpenApi.Readers.csproj b/src/Microsoft.OpenApi.Readers/Microsoft.OpenApi.Readers.csproj index cdd32b997..05e3e52e6 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 - 2.0.0-preview2 + 2.0.0-preview3 OpenAPI.NET Readers for JSON and YAML documents true @@ -25,13 +25,15 @@ - + runtime; build; native; contentfiles; analyzers; buildtransitive all - + + + diff --git a/src/Microsoft.OpenApi.Readers/OpenApiYamlReader.cs b/src/Microsoft.OpenApi.Readers/OpenApiYamlReader.cs index cff6dd1da..d1db7926a 100644 --- a/src/Microsoft.OpenApi.Readers/OpenApiYamlReader.cs +++ b/src/Microsoft.OpenApi.Readers/OpenApiYamlReader.cs @@ -37,8 +37,8 @@ public async Task ReadAsync(TextReader input, diagnostic.Errors.Add(new($"#line={ex.LineNumber}", ex.Message)); return new() { - OpenApiDocument = null, - OpenApiDiagnostic = diagnostic + Document = null, + Diagnostic = diagnostic }; } @@ -77,7 +77,7 @@ static JsonNode LoadJsonNodesFromYamlDocument(TextReader input) { var yamlStream = new YamlStream(); yamlStream.Load(input); - var yamlDocument = yamlStream.Documents.First(); + var yamlDocument = yamlStream.Documents[0]; return yamlDocument.ToJsonNode(); } diff --git a/src/Microsoft.OpenApi.Workbench/MainModel.cs b/src/Microsoft.OpenApi.Workbench/MainModel.cs index d518645a5..1cd4f24ac 100644 --- a/src/Microsoft.OpenApi.Workbench/MainModel.cs +++ b/src/Microsoft.OpenApi.Workbench/MainModel.cs @@ -49,7 +49,7 @@ public class MainModel : INotifyPropertyChanged /// private OpenApiSpecVersion _version = OpenApiSpecVersion.OpenApi3_0; - private HttpClient _httpClient = new(); + private static readonly HttpClient _httpClient = new(); public string Input { @@ -166,31 +166,31 @@ public OpenApiSpecVersion Version public bool IsYaml { get => Format == OpenApiFormat.Yaml; - set => Format = OpenApiFormat.Yaml; + set => Format = value ? OpenApiFormat.Yaml : Format; } public bool IsJson { get => Format == OpenApiFormat.Json; - set => Format = OpenApiFormat.Json; + set => Format = value ? OpenApiFormat.Json : Format; } public bool IsV2_0 { get => Version == OpenApiSpecVersion.OpenApi2_0; - set => Version = OpenApiSpecVersion.OpenApi2_0; + set => Version = value ? OpenApiSpecVersion.OpenApi2_0 : Version; } public bool IsV3_0 { get => Version == OpenApiSpecVersion.OpenApi3_0; - set => Version = OpenApiSpecVersion.OpenApi3_0; + set => Version = value ? OpenApiSpecVersion.OpenApi3_0 : Version; } public bool IsV3_1 { get => Version == OpenApiSpecVersion.OpenApi3_1; - set => Version = OpenApiSpecVersion.OpenApi3_1; + set => Version = value ? OpenApiSpecVersion.OpenApi3_1 : Version; } /// @@ -219,14 +219,8 @@ internal async Task ParseDocumentAsync() { if (!string.IsNullOrWhiteSpace(_inputFile)) { - if (_inputFile.StartsWith("http")) - { - stream = await _httpClient.GetStreamAsync(_inputFile); - } - else - { - stream = new FileStream(_inputFile, FileMode.Open); - } + stream = _inputFile.StartsWith("http") ? await _httpClient.GetStreamAsync(_inputFile) + : new FileStream(_inputFile, FileMode.Open); } else { @@ -245,22 +239,15 @@ internal async Task ParseDocumentAsync() ReferenceResolution = ResolveExternal ? ReferenceResolutionSetting.ResolveAllReferences : ReferenceResolutionSetting.ResolveLocalReferences, RuleSet = ValidationRuleSet.GetDefaultRuleSet() }; - if (ResolveExternal) + if (ResolveExternal && !string.IsNullOrWhiteSpace(_inputFile)) { - if (_inputFile.StartsWith("http")) - { - settings.BaseUrl = new(_inputFile); - } - else - { - settings.BaseUrl = new("file://" + Path.GetDirectoryName(_inputFile) + "/"); - } + settings.BaseUrl = _inputFile.StartsWith("http") ? new(_inputFile) + : new("file://" + Path.GetDirectoryName(_inputFile) + "/"); } - var format = OpenApiModelFactory.GetFormat(_inputFile); - var readResult = await OpenApiDocument.LoadAsync(stream, format); - var document = readResult.OpenApiDocument; - var context = readResult.OpenApiDiagnostic; + var readResult = await OpenApiDocument.LoadAsync(stream, Format.GetDisplayName()); + var document = readResult.Document; + var context = readResult.Diagnostic; stopwatch.Stop(); ParseTime = $"{stopwatch.ElapsedMilliseconds} ms"; @@ -306,7 +293,6 @@ internal async Task ParseDocumentAsync() stream.Close(); await stream.DisposeAsync(); } - } } @@ -332,7 +318,7 @@ private string WriteContents(OpenApiDocument document) return new StreamReader(outputStream).ReadToEnd(); } - private MemoryStream CreateStream(string text) + private static MemoryStream CreateStream(string text) { var stream = new MemoryStream(); var writer = new StreamWriter(stream); diff --git a/src/Microsoft.OpenApi.Workbench/Microsoft.OpenApi.Workbench.csproj b/src/Microsoft.OpenApi.Workbench/Microsoft.OpenApi.Workbench.csproj index 3ea08878d..ab6c09b54 100644 --- a/src/Microsoft.OpenApi.Workbench/Microsoft.OpenApi.Workbench.csproj +++ b/src/Microsoft.OpenApi.Workbench/Microsoft.OpenApi.Workbench.csproj @@ -6,15 +6,16 @@ true true true + NU1903 - + runtime; build; native; contentfiles; analyzers; buildtransitive all - + - + diff --git a/src/Microsoft.OpenApi.Workbench/StatsVisitor.cs b/src/Microsoft.OpenApi.Workbench/StatsVisitor.cs index ed662d302..fafbc8188 100644 --- a/src/Microsoft.OpenApi.Workbench/StatsVisitor.cs +++ b/src/Microsoft.OpenApi.Workbench/StatsVisitor.cs @@ -61,7 +61,7 @@ public override void Visit(OpenApiOperation operation) public int LinkCount { get; set; } - public override void Visit(OpenApiLink operation) + public override void Visit(OpenApiLink link) { LinkCount++; } diff --git a/src/Microsoft.OpenApi/Extensions/OpenApiTypeMapper.cs b/src/Microsoft.OpenApi/Extensions/OpenApiTypeMapper.cs index e6dadd44d..e47eff496 100644 --- a/src/Microsoft.OpenApi/Extensions/OpenApiTypeMapper.cs +++ b/src/Microsoft.OpenApi/Extensions/OpenApiTypeMapper.cs @@ -13,12 +13,27 @@ namespace Microsoft.OpenApi.Extensions /// public static class OpenApiTypeMapper { +#nullable enable /// /// Maps a JsonSchema data type to an identifier. /// /// /// - public static string ToIdentifier(this JsonSchemaType? schemaType) + public static string? ToIdentifier(this JsonSchemaType? schemaType) + { + if (schemaType is null) + { + return null; + } + return schemaType.Value.ToIdentifier(); + } + + /// + /// Maps a JsonSchema data type to an identifier. + /// + /// + /// + public static string? ToIdentifier(this JsonSchemaType schemaType) { return schemaType switch { @@ -32,6 +47,7 @@ public static string ToIdentifier(this JsonSchemaType? schemaType) _ => null, }; } +#nullable restore /// /// Converts a schema type's identifier into the enum equivalent diff --git a/src/Microsoft.OpenApi/Microsoft.OpenApi.csproj b/src/Microsoft.OpenApi/Microsoft.OpenApi.csproj index b549decd7..5c4e18a29 100644 --- a/src/Microsoft.OpenApi/Microsoft.OpenApi.csproj +++ b/src/Microsoft.OpenApi/Microsoft.OpenApi.csproj @@ -3,7 +3,7 @@ netstandard2.0 Latest true - 2.0.0-preview2 + 2.0.0-preview3 .NET models with JSON and YAML writers for OpenAPI specification true @@ -22,7 +22,9 @@ true - + + + @@ -45,7 +47,7 @@ - + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/src/Microsoft.OpenApi/Models/OpenApiConstants.cs b/src/Microsoft.OpenApi/Models/OpenApiConstants.cs index c629f78be..1c016f4c4 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiConstants.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiConstants.cs @@ -50,6 +50,11 @@ public static class OpenApiConstants /// public const string Title = "title"; + /// + /// Field: Const + /// + public const string Const = "const"; + /// /// Field: Type /// @@ -475,6 +480,11 @@ public static class OpenApiConstants /// public const string Properties = "properties"; + /// + /// Field: UnrecognizedKeywords + /// + public const string UnrecognizedKeywords = "unrecognizedKeywords"; + /// /// Field: Pattern Properties /// diff --git a/src/Microsoft.OpenApi/Models/OpenApiDocument.cs b/src/Microsoft.OpenApi/Models/OpenApiDocument.cs index 0261fcff9..bf8458f2c 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiDocument.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiDocument.cs @@ -21,7 +21,7 @@ namespace Microsoft.OpenApi.Models { /// - /// Describes an OpenAPI object (OpenAPI document). See: https://swagger.io/specification + /// Describes an OpenAPI object (OpenAPI document). See: https://spec.openapis.org /// public class OpenApiDocument : IOpenApiSerializable, IOpenApiExtensible, IOpenApiAnnotatable { diff --git a/src/Microsoft.OpenApi/Models/OpenApiExtensibleDictionary.cs b/src/Microsoft.OpenApi/Models/OpenApiExtensibleDictionary.cs index be2e56a73..86fe7ea73 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiExtensibleDictionary.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiExtensibleDictionary.cs @@ -20,24 +20,23 @@ public abstract class OpenApiExtensibleDictionary : Dictionary, /// /// Parameterless constructor /// - protected OpenApiExtensibleDictionary() { } - + protected OpenApiExtensibleDictionary():this(null) { } /// /// Initializes a copy of class. /// /// The generic dictionary. /// The dictionary of . protected OpenApiExtensibleDictionary( - Dictionary dictionary = null, - IDictionary extensions = null) : base(dictionary) + Dictionary dictionary, + IDictionary extensions = null) : base(dictionary is null ? [] : dictionary) { - Extensions = extensions != null ? new Dictionary(extensions) : null; + Extensions = extensions != null ? new Dictionary(extensions) : []; } /// /// This object MAY be extended with Specification Extensions. /// - public IDictionary Extensions { get; set; } = new Dictionary(); + public IDictionary Extensions { get; set; } /// diff --git a/src/Microsoft.OpenApi/Models/OpenApiSchema.cs b/src/Microsoft.OpenApi/Models/OpenApiSchema.cs index 59b7e2025..c9e5441a9 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiSchema.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiSchema.cs @@ -15,12 +15,8 @@ namespace Microsoft.OpenApi.Models /// /// The Schema Object allows the definition of input and output data types. /// - public class OpenApiSchema : IOpenApiAnnotatable, IOpenApiExtensible, IOpenApiReferenceable, IOpenApiSerializable + public class OpenApiSchema : IOpenApiAnnotatable, IOpenApiExtensible, IOpenApiReferenceable { - private JsonNode _example; - private JsonNode _default; - private IList _examples; - /// /// Follow JSON Schema definition. Short text providing information about the data. /// @@ -83,6 +79,11 @@ public class OpenApiSchema : IOpenApiAnnotatable, IOpenApiExtensible, IOpenApiRe /// public virtual JsonSchemaType? Type { get; set; } + /// + /// Follow JSON Schema definition: https://json-schema.org/draft/2020-12/json-schema-validation + /// + public virtual string Const { get; set; } + /// /// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00 /// While relying on JSON Schema's defined formats, @@ -143,11 +144,7 @@ public class OpenApiSchema : IOpenApiAnnotatable, IOpenApiExtensible, IOpenApiRe /// Unlike JSON Schema, the value MUST conform to the defined type for the Schema Object defined at the same level. /// For example, if type is string, then default can be "foo" but cannot be 1. /// - public virtual JsonNode Default - { - get => _default; - set => _default = value; - } + public virtual JsonNode Default { get; set; } /// /// Relevant only for Schema "properties" definitions. Declares the property as "read only". @@ -268,22 +265,14 @@ public virtual JsonNode Default /// To represent examples that cannot be naturally represented in JSON or YAML, /// a string value can be used to contain the example with escaping where necessary. /// - public virtual JsonNode Example - { - get => _example; - set => _example = value; - } + public virtual JsonNode Example { get; set; } /// /// A free-form property to include examples of an instance for this schema. /// To represent examples that cannot be naturally represented in JSON or YAML, /// a list of values can be used to contain the examples with escaping where necessary. /// - public virtual IList Examples - { - get => _examples; - set => _examples = value; - } + public virtual IList Examples { get; set; } /// /// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00 @@ -322,6 +311,11 @@ public virtual IList Examples /// public virtual IDictionary Extensions { get; set; } = new Dictionary(); + /// + /// This object stores any unrecognized keywords found in the schema. + /// + public virtual IDictionary UnrecognizedKeywords { get; set; } = new Dictionary(); + /// /// Indicates object is a placeholder reference to an actual object and does not contain valid data. /// @@ -347,6 +341,7 @@ public OpenApiSchema(OpenApiSchema schema) { Title = schema?.Title ?? Title; Id = schema?.Id ?? Id; + Const = schema?.Const ?? Const; Schema = schema?.Schema ?? Schema; Comment = schema?.Comment ?? Comment; Vocabulary = schema?.Vocabulary != null ? new Dictionary(schema.Vocabulary) : null; @@ -367,7 +362,7 @@ public OpenApiSchema(OpenApiSchema schema) MinLength = schema?.MinLength ?? MinLength; Pattern = schema?.Pattern ?? Pattern; MultipleOf = schema?.MultipleOf ?? MultipleOf; - _default = schema?.Default != null ? JsonNodeCloneHelper.Clone(schema?.Default) : null; + Default = schema?.Default != null ? JsonNodeCloneHelper.Clone(schema?.Default) : null; ReadOnly = schema?.ReadOnly ?? ReadOnly; WriteOnly = schema?.WriteOnly ?? WriteOnly; AllOf = schema?.AllOf != null ? new List(schema.AllOf) : null; @@ -386,8 +381,8 @@ public OpenApiSchema(OpenApiSchema schema) AdditionalPropertiesAllowed = schema?.AdditionalPropertiesAllowed ?? AdditionalPropertiesAllowed; AdditionalProperties = schema?.AdditionalProperties != null ? new(schema?.AdditionalProperties) : null; Discriminator = schema?.Discriminator != null ? new(schema?.Discriminator) : null; - _example = schema?.Example != null ? JsonNodeCloneHelper.Clone(schema?.Example) : null; - _examples = schema?.Examples != null ? new List(schema.Examples) : null; + Example = schema?.Example != null ? JsonNodeCloneHelper.Clone(schema?.Example) : null; + Examples = schema?.Examples != null ? new List(schema.Examples) : null; Enum = schema?.Enum != null ? new List(schema.Enum) : null; Nullable = schema?.Nullable ?? Nullable; ExternalDocs = schema?.ExternalDocs != null ? new(schema?.ExternalDocs) : null; @@ -397,6 +392,7 @@ public OpenApiSchema(OpenApiSchema schema) UnresolvedReference = schema?.UnresolvedReference ?? UnresolvedReference; Reference = schema?.Reference != null ? new(schema?.Reference) : null; Annotations = schema?.Annotations != null ? new Dictionary(schema?.Annotations) : null; + UnrecognizedKeywords = schema?.UnrecognizedKeywords != null ? new Dictionary(schema?.UnrecognizedKeywords) : null; } /// @@ -424,7 +420,7 @@ public void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion version, if (version == OpenApiSpecVersion.OpenApi3_1) { - WriteV31Properties(writer); + WriteJsonSchemaKeywords(writer); } // title @@ -476,10 +472,7 @@ public void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion version, writer.WriteOptionalCollection(OpenApiConstants.Enum, Enum, (nodeWriter, s) => nodeWriter.WriteAny(s)); // type - if (Type is not null) - { - SerializeTypeProperty(Type, writer, version); - } + SerializeTypeProperty(Type, writer, version); // allOf writer.WriteOptionalCollection(OpenApiConstants.AllOf, AllOf, callback); @@ -551,6 +544,12 @@ public void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion version, // extensions writer.WriteExtensions(Extensions, version); + // Unrecognized keywords + if (UnrecognizedKeywords.Any()) + { + writer.WriteOptionalMap(OpenApiConstants.UnrecognizedKeywords, UnrecognizedKeywords, (w,s) => w.WriteAny(s)); + } + writer.WriteEndObject(); } @@ -561,11 +560,12 @@ public virtual void SerializeAsV2(IOpenApiWriter writer) SerializeAsV2(writer: writer, parentRequiredProperties: new HashSet(), propertyName: null); } - internal void WriteV31Properties(IOpenApiWriter writer) + internal void WriteJsonSchemaKeywords(IOpenApiWriter writer) { writer.WriteProperty(OpenApiConstants.Id, Id); writer.WriteProperty(OpenApiConstants.DollarSchema, Schema); writer.WriteProperty(OpenApiConstants.Comment, Comment); + writer.WriteProperty(OpenApiConstants.Const, Const); writer.WriteOptionalMap(OpenApiConstants.Vocabulary, Vocabulary, (w, s) => w.WriteValue(s)); writer.WriteOptionalMap(OpenApiConstants.Defs, Definitions, (w, s) => s.SerializeAsV31(w)); writer.WriteProperty(OpenApiConstants.DynamicRef, DynamicRef); @@ -573,7 +573,7 @@ internal void WriteV31Properties(IOpenApiWriter writer) writer.WriteProperty(OpenApiConstants.V31ExclusiveMaximum, V31ExclusiveMaximum); writer.WriteProperty(OpenApiConstants.V31ExclusiveMinimum, V31ExclusiveMinimum); writer.WriteProperty(OpenApiConstants.UnevaluatedProperties, UnevaluatedProperties, false); - writer.WriteOptionalCollection(OpenApiConstants.Examples, _examples, (nodeWriter, s) => nodeWriter.WriteAny(s)); + writer.WriteOptionalCollection(OpenApiConstants.Examples, Examples, (nodeWriter, s) => nodeWriter.WriteAny(s)); writer.WriteOptionalMap(OpenApiConstants.PatternProperties, PatternProperties, (w, s) => s.SerializeAsV31(w)); } @@ -660,10 +660,7 @@ internal void SerializeAsV2( writer.WriteStartObject(); // type - if (Type is not null) - { - SerializeTypeProperty(Type, writer, OpenApiSpecVersion.OpenApi2_0); - } + SerializeTypeProperty(Type, writer, OpenApiSpecVersion.OpenApi2_0); // description writer.WriteProperty(OpenApiConstants.Description, Description); @@ -794,8 +791,11 @@ internal void SerializeAsV2( private void SerializeTypeProperty(JsonSchemaType? type, IOpenApiWriter writer, OpenApiSpecVersion version) { - var flagsCount = CountEnumSetFlags(type); - if (flagsCount is 1) + if (type is null) + { + return; + } + if (!HasMultipleTypes(type.Value)) { // check whether nullable is true for upcasting purposes if (version is OpenApiSpecVersion.OpenApi3_1 && (Nullable || Extensions.ContainsKey(OpenApiConstants.NullableExtension))) @@ -804,73 +804,51 @@ private void SerializeTypeProperty(JsonSchemaType? type, IOpenApiWriter writer, } else { - writer.WriteProperty(OpenApiConstants.Type, type.ToIdentifier()); + writer.WriteProperty(OpenApiConstants.Type, type.Value.ToIdentifier()); } } - else if(flagsCount > 1) + else { // type if (version is OpenApiSpecVersion.OpenApi2_0 || version is OpenApiSpecVersion.OpenApi3_0) { - DowncastTypeArrayToV2OrV3(type, writer, version, flagsCount); + DowncastTypeArrayToV2OrV3(type.Value, writer, version); } else { - if (type is not null) - { - var list = new List(); - foreach (JsonSchemaType flag in System.Enum.GetValues(typeof(JsonSchemaType))) - { - if (type.Value.HasFlag(flag)) - { - list.Add(flag); - } - } - - writer.WriteOptionalCollection(OpenApiConstants.Type, list, (w, s) => w.WriteValue(s.ToIdentifier())); - } + var list = (from JsonSchemaType flag in jsonSchemaTypeValues + where type.Value.HasFlag(flag) + select flag).ToList(); + writer.WriteOptionalCollection(OpenApiConstants.Type, list, (w, s) => w.WriteValue(s.ToIdentifier())); } } } - private static int CountEnumSetFlags(JsonSchemaType? schemaType) + private static bool IsPowerOfTwo(int x) { - int count = 0; - - if (schemaType != null) - { - // Check each flag in the enum - foreach (JsonSchemaType value in System.Enum.GetValues(typeof(JsonSchemaType))) - { - // Check if the flag is set - if (schemaType.Value.HasFlag(value)) - { - count++; - } - } - } + return x != 0 && (x & (x - 1)) == 0; + } - return count; + private static bool HasMultipleTypes(JsonSchemaType schemaType) + { + var schemaTypeNumeric = (int)schemaType; + return !IsPowerOfTwo(schemaTypeNumeric) && // Boolean, Integer, Number, String, Array, Object + schemaTypeNumeric != (int)JsonSchemaType.Null; } private void UpCastSchemaTypeToV31(JsonSchemaType? type, IOpenApiWriter writer) { // create a new array and insert the type and "null" as values Type = type | JsonSchemaType.Null; - var list = new List(); - foreach (JsonSchemaType? flag in System.Enum.GetValues(typeof(JsonSchemaType))) - { - // Check if the flag is set in 'type' using a bitwise AND operation - if (Type.Value.HasFlag(flag)) - { - list.Add(flag.ToIdentifier()); - } - } - + var list = (from JsonSchemaType? flag in jsonSchemaTypeValues// Check if the flag is set in 'type' using a bitwise AND operation + where Type.Value.HasFlag(flag) + select flag.ToIdentifier()).ToList(); writer.WriteOptionalCollection(OpenApiConstants.Type, list, (w, s) => w.WriteValue(s)); } - private void DowncastTypeArrayToV2OrV3(JsonSchemaType? schemaType, IOpenApiWriter writer, OpenApiSpecVersion version, int flagsCount) + private static readonly Array jsonSchemaTypeValues = System.Enum.GetValues(typeof(JsonSchemaType)); + + private void DowncastTypeArrayToV2OrV3(JsonSchemaType schemaType, IOpenApiWriter writer, OpenApiSpecVersion version) { /* If the array has one non-null value, emit Type as string * If the array has one null value, emit x-nullable as true @@ -882,23 +860,12 @@ private void DowncastTypeArrayToV2OrV3(JsonSchemaType? schemaType, IOpenApiWrite ? OpenApiConstants.NullableExtension : OpenApiConstants.Nullable; - if (flagsCount is 1) + if (!HasMultipleTypes(schemaType ^ JsonSchemaType.Null) && (schemaType & JsonSchemaType.Null) == JsonSchemaType.Null) // checks for two values and one is null { - if (schemaType is JsonSchemaType.Null) - { - writer.WriteProperty(nullableProp, true); - } - else - { - writer.WriteProperty(OpenApiConstants.Type, schemaType.ToIdentifier()); - } - } - else if (flagsCount is 2 && (schemaType & JsonSchemaType.Null) == JsonSchemaType.Null) // checks for two values and one is null - { - foreach (JsonSchemaType? flag in System.Enum.GetValues(typeof(JsonSchemaType))) + foreach (JsonSchemaType? flag in jsonSchemaTypeValues) { // Skip if the flag is not set or if it's the Null flag - if (schemaType.Value.HasFlag(flag) && flag != JsonSchemaType.Null) + if (schemaType.HasFlag(flag) && flag != JsonSchemaType.Null) { // Write the non-null flag value to the writer writer.WriteProperty(OpenApiConstants.Type, flag.ToIdentifier()); @@ -909,6 +876,17 @@ private void DowncastTypeArrayToV2OrV3(JsonSchemaType? schemaType, IOpenApiWrite writer.WriteProperty(nullableProp, true); } } + else if (!HasMultipleTypes(schemaType)) + { + if (schemaType is JsonSchemaType.Null) + { + writer.WriteProperty(nullableProp, true); + } + else + { + writer.WriteProperty(OpenApiConstants.Type, schemaType.ToIdentifier()); + } + } } } } diff --git a/src/Microsoft.OpenApi/Models/OpenApiTag.cs b/src/Microsoft.OpenApi/Models/OpenApiTag.cs index 6f79e0999..8e9321fe8 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiTag.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiTag.cs @@ -55,56 +55,31 @@ public OpenApiTag(OpenApiTag tag) { Name = tag?.Name ?? Name; Description = tag?.Description ?? Description; - ExternalDocs = tag?.ExternalDocs != null ? new(tag?.ExternalDocs) : null; + ExternalDocs = tag?.ExternalDocs != null ? new(tag.ExternalDocs) : null; Extensions = tag?.Extensions != null ? new Dictionary(tag.Extensions) : null; UnresolvedReference = tag?.UnresolvedReference ?? UnresolvedReference; - Reference = tag?.Reference != null ? new(tag?.Reference) : null; + Reference = tag?.Reference != null ? new(tag.Reference) : null; } /// /// Serialize to Open Api v3.1 /// - public virtual void SerializeAsV31(IOpenApiWriter writer) + public virtual void SerializeAsV31(IOpenApiWriter writer) { - SerializeInternal(writer, (writer, element) => element.SerializeAsV31(writer)); - } - - /// - /// Serialize to Open Api v3.0 - /// - public virtual void SerializeAsV3(IOpenApiWriter writer) - { - SerializeInternal(writer, (writer, element) => element.SerializeAsV3(writer)); - } - - /// - /// Serialize to Open Api v3.0 - /// - private void SerializeInternal(IOpenApiWriter writer, Action callback) - { - Utils.CheckArgumentNull(writer); - writer.WriteValue(Name); - } - - /// - /// Serialize to OpenAPI V3 document without using reference. - /// - public virtual void SerializeAsV31WithoutReference(IOpenApiWriter writer) - { - SerializeInternalWithoutReference(writer, OpenApiSpecVersion.OpenApi3_1, + SerializeInternal(writer, OpenApiSpecVersion.OpenApi3_1, (writer, element) => element.SerializeAsV31(writer)); } /// - /// Serialize to OpenAPI V3 document without using reference. + /// Serialize to Open Api v3.0 /// - public virtual void SerializeAsV3WithoutReference(IOpenApiWriter writer) + public virtual void SerializeAsV3(IOpenApiWriter writer) { - SerializeInternalWithoutReference(writer, OpenApiSpecVersion.OpenApi3_0, + SerializeInternal(writer, OpenApiSpecVersion.OpenApi3_0, (writer, element) => element.SerializeAsV3(writer)); } - internal virtual void SerializeInternalWithoutReference(IOpenApiWriter writer, OpenApiSpecVersion version, + internal virtual void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion version, Action callback) { writer.WriteStartObject(); @@ -128,15 +103,6 @@ internal virtual void SerializeInternalWithoutReference(IOpenApiWriter writer, O /// Serialize to Open Api v2.0 /// public virtual void SerializeAsV2(IOpenApiWriter writer) - { - Utils.CheckArgumentNull(writer); - writer.WriteValue(Name); - } - - /// - /// Serialize to OpenAPI V2 document without using reference. - /// - public void SerializeAsV2WithoutReference(IOpenApiWriter writer) { writer.WriteStartObject(); diff --git a/src/Microsoft.OpenApi/Models/References/OpenApiSchemaReference.cs b/src/Microsoft.OpenApi/Models/References/OpenApiSchemaReference.cs index a7b55e109..011e0b930 100644 --- a/src/Microsoft.OpenApi/Models/References/OpenApiSchemaReference.cs +++ b/src/Microsoft.OpenApi/Models/References/OpenApiSchemaReference.cs @@ -17,6 +17,9 @@ public class OpenApiSchemaReference : OpenApiSchema internal OpenApiSchema _target; private readonly OpenApiReference _reference; private string _description; + private JsonNode _default; + private JsonNode _example; + private IList _examples; private OpenApiSchema Target { @@ -90,6 +93,8 @@ internal OpenApiSchemaReference(OpenApiSchema target, string referenceId) /// public override JsonSchemaType? Type { get => Target.Type; set => Target.Type = value; } /// + public override string Const { get => Target.Const; set => Target.Const = value; } + /// public override string Format { get => Target.Format; set => Target.Format = value; } /// public override string Description @@ -114,7 +119,11 @@ public override string Description /// public override decimal? MultipleOf { get => Target.MultipleOf; set => Target.MultipleOf = value; } /// - public override JsonNode Default { get => Target.Default; set => Target.Default = value; } + public override JsonNode Default + { + get => _default ??= Target.Default; + set => _default = value; + } /// public override bool ReadOnly { get => Target.ReadOnly; set => Target.ReadOnly = value; } /// @@ -152,9 +161,17 @@ public override string Description /// public override OpenApiDiscriminator Discriminator { get => Target.Discriminator; set => Target.Discriminator = value; } /// - public override JsonNode Example { get => Target.Example; set => Target.Example = value; } + public override JsonNode Example + { + get => _example ??= Target.Example; + set => _example = value; + } /// - public override IList Examples { get => Target.Examples; set => Target.Examples = value; } + public override IList Examples + { + get => _examples ??= Target.Examples; + set => Target.Examples = value; + } /// public override IList Enum { get => Target.Enum; set => Target.Enum = value; } /// diff --git a/src/Microsoft.OpenApi/Reader/OpenApiJsonReader.cs b/src/Microsoft.OpenApi/Reader/OpenApiJsonReader.cs index 27aad722e..35f4fa6f6 100644 --- a/src/Microsoft.OpenApi/Reader/OpenApiJsonReader.cs +++ b/src/Microsoft.OpenApi/Reader/OpenApiJsonReader.cs @@ -49,8 +49,8 @@ public async Task ReadAsync(TextReader input, diagnostic.Errors.Add(new OpenApiError($"#line={ex.LineNumber}", $"Please provide the correct format, {ex.Message}")); return new ReadResult { - OpenApiDocument = null, - OpenApiDiagnostic = diagnostic + Document = null, + Diagnostic = diagnostic }; } @@ -118,8 +118,8 @@ public async Task ReadAsync(JsonNode jsonNode, return new() { - OpenApiDocument = document, - OpenApiDiagnostic = diagnostic + Document = document, + Diagnostic = diagnostic }; } diff --git a/src/Microsoft.OpenApi/Reader/ParsingContext.cs b/src/Microsoft.OpenApi/Reader/ParsingContext.cs index aae60da9d..7a8b07244 100644 --- a/src/Microsoft.OpenApi/Reader/ParsingContext.cs +++ b/src/Microsoft.OpenApi/Reader/ParsingContext.cs @@ -271,9 +271,9 @@ public void PopLoop(string loopid) private void ValidateRequiredFields(OpenApiDocument doc, string version) { - if ((version.is2_0() || version.is3_0()) && (doc.Paths == null || !doc.Paths.Any())) + if ((version.is2_0() || version.is3_0()) && (doc.Paths == null)) { - // paths is a required field in OpenAPI 3.0 but optional in 3.1 + // paths is a required field in OpenAPI 2.0 and 3.0 but optional in 3.1 RootNode.Context.Diagnostic.Errors.Add(new OpenApiError("", $"Paths is a REQUIRED field at {RootNode.Context.GetLocation()}")); } } diff --git a/src/Microsoft.OpenApi/Reader/ReadResult.cs b/src/Microsoft.OpenApi/Reader/ReadResult.cs index 77a18ff78..a0013b249 100644 --- a/src/Microsoft.OpenApi/Reader/ReadResult.cs +++ b/src/Microsoft.OpenApi/Reader/ReadResult.cs @@ -13,10 +13,10 @@ public class ReadResult /// /// The parsed OpenApiDocument. Null will be returned if the document could not be parsed. /// - public OpenApiDocument OpenApiDocument { set; get; } + public OpenApiDocument Document { get; set; } /// /// OpenApiDiagnostic contains the Errors reported while parsing /// - public OpenApiDiagnostic OpenApiDiagnostic { set; get; } + public OpenApiDiagnostic Diagnostic { get; set; } } } diff --git a/src/Microsoft.OpenApi/Reader/Services/OpenApiWorkspaceLoader.cs b/src/Microsoft.OpenApi/Reader/Services/OpenApiWorkspaceLoader.cs index a3462da70..06231e75c 100644 --- a/src/Microsoft.OpenApi/Reader/Services/OpenApiWorkspaceLoader.cs +++ b/src/Microsoft.OpenApi/Reader/Services/OpenApiWorkspaceLoader.cs @@ -48,13 +48,13 @@ internal async Task LoadAsync(OpenApiReference reference, var input = await _loader.LoadAsync(new(item.ExternalResource, UriKind.RelativeOrAbsolute)); var result = await OpenApiDocument.LoadAsync(input, format, _readerSettings, cancellationToken); // Merge diagnostics - if (result.OpenApiDiagnostic != null) + if (result.Diagnostic != null) { - diagnostic.AppendDiagnostic(result.OpenApiDiagnostic, item.ExternalResource); + diagnostic.AppendDiagnostic(result.Diagnostic, item.ExternalResource); } - if (result.OpenApiDocument != null) + if (result.Document != null) { - var loadDiagnostic = await LoadAsync(item, result.OpenApiDocument, format, diagnostic, cancellationToken); + var loadDiagnostic = await LoadAsync(item, result.Document, format, diagnostic, cancellationToken); diagnostic = loadDiagnostic; } } diff --git a/src/Microsoft.OpenApi/Reader/V31/OpenApiSchemaDeserializer.cs b/src/Microsoft.OpenApi/Reader/V31/OpenApiSchemaDeserializer.cs index 5dc76b7fb..ad943dce4 100644 --- a/src/Microsoft.OpenApi/Reader/V31/OpenApiSchemaDeserializer.cs +++ b/src/Microsoft.OpenApi/Reader/V31/OpenApiSchemaDeserializer.cs @@ -7,6 +7,8 @@ using Microsoft.OpenApi.Reader.ParseNodes; using System.Collections.Generic; using System.Globalization; +using System.Linq; +using System.Text.Json.Nodes; namespace Microsoft.OpenApi.Reader.V31 { @@ -131,6 +133,10 @@ internal static partial class OpenApiV31Deserializer } } }, + { + "const", + (o, n, _) => o.Const = n.GetScalarValue() + }, { "allOf", (o, n, t) => o.AllOf = n.CreateList(LoadSchema, t) @@ -250,7 +256,17 @@ public static OpenApiSchema LoadSchema(ParseNode node, OpenApiDocument hostDocum foreach (var propertyNode in mapNode) { - propertyNode.ParseField(schema, _openApiSchemaFixedFields, _openApiSchemaPatternFields); + bool isRecognized = _openApiSchemaFixedFields.ContainsKey(propertyNode.Name) || + _openApiSchemaPatternFields.Any(p => p.Key(propertyNode.Name)); + + if (isRecognized) + { + propertyNode.ParseField(schema, _openApiSchemaFixedFields, _openApiSchemaPatternFields); + } + else + { + schema.UnrecognizedKeywords[propertyNode.Name] = propertyNode.JsonNode; + } } if (schema.Extensions.ContainsKey(OpenApiConstants.NullableExtension)) diff --git a/src/Microsoft.OpenApi/Services/OpenApiReferenceError.cs b/src/Microsoft.OpenApi/Services/OpenApiReferenceError.cs index d9a76368d..6dfd066ff 100644 --- a/src/Microsoft.OpenApi/Services/OpenApiReferenceError.cs +++ b/src/Microsoft.OpenApi/Services/OpenApiReferenceError.cs @@ -11,7 +11,10 @@ namespace Microsoft.OpenApi.Services /// public class OpenApiReferenceError : OpenApiError { - private OpenApiReference _reference; + /// + /// The reference that caused the error. + /// + public readonly OpenApiReference Reference; /// /// Initializes the class using the message and pointer from the given exception. /// @@ -26,7 +29,7 @@ public OpenApiReferenceError(OpenApiException exception) : base(exception.Pointe /// public OpenApiReferenceError(OpenApiReference reference, string message) : base("", message) { - _reference = reference; + Reference = reference; } } } diff --git a/src/Microsoft.OpenApi/Validations/OpenApiValidator.cs b/src/Microsoft.OpenApi/Validations/OpenApiValidator.cs index 6908e58bf..7281ef258 100644 --- a/src/Microsoft.OpenApi/Validations/OpenApiValidator.cs +++ b/src/Microsoft.OpenApi/Validations/OpenApiValidator.cs @@ -67,219 +67,108 @@ public void AddWarning(OpenApiValidatorWarning warning) _warnings.Add(warning); } - /// - /// Execute validation rules against an - /// - /// The object to be validated - public override void Visit(OpenApiDocument item) => Validate(item); + /// + public override void Visit(OpenApiDocument doc) => Validate(doc); - /// - /// Execute validation rules against an - /// - /// The object to be validated - public override void Visit(OpenApiInfo item) => Validate(item); + /// + public override void Visit(OpenApiInfo info) => Validate(info); - /// - /// Execute validation rules against an - /// - /// The object to be validated - public override void Visit(OpenApiContact item) => Validate(item); + /// + public override void Visit(OpenApiContact contact) => Validate(contact); - /// - /// Execute validation rules against an - /// - /// The object to be validated - public override void Visit(OpenApiComponents item) => Validate(item); + /// + public override void Visit(OpenApiComponents components) => Validate(components); - /// - /// Execute validation rules against an - /// - /// The object to be validated - public override void Visit(OpenApiHeader item) => Validate(item); + /// + public override void Visit(OpenApiHeader header) => Validate(header); - /// - /// Execute validation rules against an - /// - /// The object to be validated - public override void Visit(OpenApiResponse item) => Validate(item); + /// + public override void Visit(OpenApiResponse response) => Validate(response); - /// - /// Execute validation rules against an - /// - /// The object to be validated - public override void Visit(OpenApiMediaType item) => Validate(item); + /// + public override void Visit(OpenApiMediaType mediaType) => Validate(mediaType); - /// - /// Execute validation rules against an - /// - /// The object to be validated - public override void Visit(OpenApiResponses item) => Validate(item); + /// + public override void Visit(OpenApiResponses response) => Validate(response); - /// - /// Execute validation rules against an - /// - /// The object to be validated - public override void Visit(OpenApiExternalDocs item) => Validate(item); + /// + public override void Visit(OpenApiExternalDocs externalDocs) => Validate(externalDocs); - /// - /// Execute validation rules against an - /// - /// The object to be validated - public override void Visit(OpenApiLicense item) => Validate(item); + /// + public override void Visit(OpenApiLicense license) => Validate(license); - /// - /// Execute validation rules against an - /// - /// The object to be validated - public override void Visit(OpenApiOAuthFlow item) => Validate(item); + /// + public override void Visit(OpenApiOAuthFlow openApiOAuthFlow) => Validate(openApiOAuthFlow); - /// - /// Execute validation rules against an - /// - /// The object to be validated - public override void Visit(OpenApiTag item) => Validate(item); + /// + public override void Visit(OpenApiTag tag) => Validate(tag); - /// - /// Execute validation rules against an - /// - /// The object to be validated - public override void Visit(OpenApiParameter item) => Validate(item); + /// + public override void Visit(OpenApiParameter parameter) => Validate(parameter); - /// - /// Execute validation rules against an - /// - /// The object to be validated - public override void Visit(OpenApiSchema item) => Validate(item); + /// + public override void Visit(OpenApiSchema schema) => Validate(schema); - /// - /// Execute validation rules against an - /// - /// The object to be validated - public override void Visit(OpenApiServer item) => Validate(item); + /// + public override void Visit(OpenApiServer server) => Validate(server); - /// - /// Execute validation rules against an - /// - /// The object to be validated - public override void Visit(OpenApiEncoding item) => Validate(item); + /// + public override void Visit(OpenApiEncoding encoding) => Validate(encoding); - /// - /// Execute validation rules against an - /// - /// The object to be validated - public override void Visit(OpenApiCallback item) => Validate(item); + /// + public override void Visit(OpenApiCallback callback) => Validate(callback); - /// - /// Execute validation rules against an - /// - /// The object to be validated - public override void Visit(IOpenApiExtensible item) => Validate(item); + /// + public override void Visit(IOpenApiExtensible openApiExtensible) => Validate(openApiExtensible); - /// - /// Execute validation rules against an - /// - /// The object to be validated - public override void Visit(IOpenApiExtension item) => Validate(item, item.GetType()); + /// + public override void Visit(IOpenApiExtension openApiExtension) => Validate(openApiExtension, openApiExtension.GetType()); - /// - /// Execute validation rules against a list of - /// - /// The object to be validated - public override void Visit(IList items) => Validate(items, items.GetType()); + /// + public override void Visit(IList example) => Validate(example, example.GetType()); - /// - /// Execute validation rules against a - /// - /// The object to be validated - public override void Visit(OpenApiPathItem item) => Validate(item); + /// + public override void Visit(OpenApiPathItem pathItem) => Validate(pathItem); - /// - /// Execute validation rules against a - /// - /// The object to be validated - public override void Visit(OpenApiServerVariable item) => Validate(item); + /// + public override void Visit(OpenApiServerVariable serverVariable) => Validate(serverVariable); - /// - /// Execute validation rules against a - /// - /// The object to be validated - public override void Visit(OpenApiSecurityScheme item) => Validate(item); + /// + public override void Visit(OpenApiSecurityScheme securityScheme) => Validate(securityScheme); - /// - /// Execute validation rules against a - /// - /// The object to be validated - public override void Visit(OpenApiSecurityRequirement item) => Validate(item); + /// + public override void Visit(OpenApiSecurityRequirement securityRequirement) => Validate(securityRequirement); - /// - /// Execute validation rules against a - /// - /// The object to be validated - public override void Visit(OpenApiRequestBody item) => Validate(item); + /// + public override void Visit(OpenApiRequestBody requestBody) => Validate(requestBody); - /// - /// Execute validation rules against a - /// - /// The object to be validated - public override void Visit(OpenApiPaths item) => Validate(item); + /// + public override void Visit(OpenApiPaths paths) => Validate(paths); - /// - /// Execute validation rules against a - /// - /// The object to be validated - public override void Visit(OpenApiLink item) => Validate(item); + /// + public override void Visit(OpenApiLink link) => Validate(link); - /// - /// Execute validation rules against a - /// - /// The object to be validated - public override void Visit(OpenApiExample item) => Validate(item); + /// + public override void Visit(OpenApiExample example) => Validate(example); - /// - /// Execute validation rules against a - /// - /// The object to be validated - public override void Visit(OpenApiOperation item) => Validate(item); - /// - /// Execute validation rules against a - /// - /// The object to be validated - public override void Visit(IDictionary item) => Validate(item, item.GetType()); - /// - /// Execute validation rules against a - /// - /// The object to be validated - public override void Visit(IDictionary item) => Validate(item, item.GetType()); - /// - /// Execute validation rules against a - /// - /// The object to be validated - public override void Visit(IDictionary item) => Validate(item, item.GetType()); - /// - /// Execute validation rules against a - /// - /// The object to be validated - public override void Visit(IDictionary item) => Validate(item, item.GetType()); - /// - /// Execute validation rules against a - /// - /// The object to be validated - public override void Visit(IDictionary item) => Validate(item, item.GetType()); - /// - /// Execute validation rules against a - /// - /// The object to be validated - public override void Visit(IDictionary item) => Validate(item, item.GetType()); - /// - /// Execute validation rules against a - /// - /// The object to be validated - public override void Visit(IDictionary item) => Validate(item, item.GetType()); - /// - /// Execute validation rules against a - /// - /// The object to be validated - public override void Visit(IDictionary item) => Validate(item, item.GetType()); + /// + public override void Visit(OpenApiOperation operation) => Validate(operation); + /// + public override void Visit(IDictionary operations) => Validate(operations, operations.GetType()); + /// + public override void Visit(IDictionary headers) => Validate(headers, headers.GetType()); + /// + public override void Visit(IDictionary callbacks) => Validate(callbacks, callbacks.GetType()); + /// + public override void Visit(IDictionary content) => Validate(content, content.GetType()); + /// + public override void Visit(IDictionary examples) => Validate(examples, examples.GetType()); + /// + public override void Visit(IDictionary links) => Validate(links, links.GetType()); + /// + public override void Visit(IDictionary serverVariables) => Validate(serverVariables, serverVariables.GetType()); + /// + public override void Visit(IDictionary encodings) => Validate(encodings, encodings.GetType()); private void Validate(T item) { diff --git a/src/Microsoft.OpenApi/Validations/Rules/OpenApiComponentsRules.cs b/src/Microsoft.OpenApi/Validations/Rules/OpenApiComponentsRules.cs index 93eba5c71..bd14f93ed 100644 --- a/src/Microsoft.OpenApi/Validations/Rules/OpenApiComponentsRules.cs +++ b/src/Microsoft.OpenApi/Validations/Rules/OpenApiComponentsRules.cs @@ -17,7 +17,7 @@ public static class OpenApiComponentsRules /// /// The key regex. /// - public static Regex KeyRegex = new(@"^[a-zA-Z0-9\.\-_]+$"); + internal static readonly Regex KeyRegex = new(@"^[a-zA-Z0-9\.\-_]+$"); /// /// All the fixed fields declared above are objects diff --git a/src/Microsoft.OpenApi/Validations/Rules/OpenApiExtensionRules.cs b/src/Microsoft.OpenApi/Validations/Rules/OpenApiExtensionRules.cs index 890be82d7..3509d797f 100644 --- a/src/Microsoft.OpenApi/Validations/Rules/OpenApiExtensionRules.cs +++ b/src/Microsoft.OpenApi/Validations/Rules/OpenApiExtensionRules.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. using System; +using System.Linq; using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Properties; @@ -21,13 +22,10 @@ public static class OpenApiExtensibleRules (context, item) => { context.Enter("extensions"); - foreach (var extensible in item.Extensions) + foreach (var extensible in item.Extensions.Keys.Where(static x => !x.StartsWith("x-", StringComparison.OrdinalIgnoreCase))) { - if (!extensible.Key.StartsWith("x-")) - { - context.CreateError(nameof(ExtensionNameMustStartWithXDash), - String.Format(SRResource.Validation_ExtensionNameMustBeginWithXDash, extensible.Key, context.PathString)); - } + context.CreateError(nameof(ExtensionNameMustStartWithXDash), + string.Format(SRResource.Validation_ExtensionNameMustBeginWithXDash, extensible, context.PathString)); } context.Exit(); }); diff --git a/src/Microsoft.OpenApi/Writers/OpenApiWriterExtensions.cs b/src/Microsoft.OpenApi/Writers/OpenApiWriterExtensions.cs index a1a74e815..8c49a2960 100644 --- a/src/Microsoft.OpenApi/Writers/OpenApiWriterExtensions.cs +++ b/src/Microsoft.OpenApi/Writers/OpenApiWriterExtensions.cs @@ -5,6 +5,7 @@ using System.Collections; using System.Collections.Generic; using System.Linq; +using System.Text.Json.Nodes; using Microsoft.OpenApi.Interfaces; namespace Microsoft.OpenApi.Writers @@ -253,6 +254,25 @@ public static void WriteRequiredMap( writer.WriteMapInternal(name, elements, action); } + /// + /// Write the optional Open API element map (string to string mapping). + /// + /// The Open API writer. + /// The property name. + /// The map values. + /// The map element writer action. + public static void WriteOptionalMap( + this IOpenApiWriter writer, + string name, + IDictionary elements, + Action action) + { + if (elements != null && elements.Any()) + { + writer.WriteMapInternal(name, elements, action); + } + } + /// /// Write the optional Open API element map (string to string mapping). /// 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 397831833..a0cc5337f 100644 --- a/test/Microsoft.OpenApi.Hidi.Tests/Microsoft.OpenApi.Hidi.Tests.csproj +++ b/test/Microsoft.OpenApi.Hidi.Tests/Microsoft.OpenApi.Hidi.Tests.csproj @@ -12,11 +12,10 @@ - + - - + diff --git a/test/Microsoft.OpenApi.Hidi.Tests/Services/OpenApiFilterServiceTests.cs b/test/Microsoft.OpenApi.Hidi.Tests/Services/OpenApiFilterServiceTests.cs index 3bd9efd2a..01c4c59fa 100644 --- a/test/Microsoft.OpenApi.Hidi.Tests/Services/OpenApiFilterServiceTests.cs +++ b/test/Microsoft.OpenApi.Hidi.Tests/Services/OpenApiFilterServiceTests.cs @@ -232,7 +232,7 @@ public void CopiesOverAllReferencedComponentsToTheSubsetDocumentCorrectly() // Act using var stream = File.OpenRead(filePath); - var doc = OpenApiDocument.Load(stream, "yaml").OpenApiDocument; + var doc = OpenApiDocument.Load(stream, "yaml").Document; var predicate = OpenApiFilterService.CreatePredicate(operationIds: operationIds); var subsetOpenApiDocument = OpenApiFilterService.CreateFilteredDocument(doc, predicate); 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 91660802a..5a2e85fed 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/Microsoft.OpenApi.Readers.Tests.csproj +++ b/test/Microsoft.OpenApi.Readers.Tests/Microsoft.OpenApi.Readers.Tests.csproj @@ -17,14 +17,13 @@ - - - + + - + - + diff --git a/test/Microsoft.OpenApi.Readers.Tests/OpenApiReaderTests/OpenApiDiagnosticTests.cs b/test/Microsoft.OpenApi.Readers.Tests/OpenApiReaderTests/OpenApiDiagnosticTests.cs index 5ecd58071..988b42a7c 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/OpenApiReaderTests/OpenApiDiagnosticTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/OpenApiReaderTests/OpenApiDiagnosticTests.cs @@ -27,8 +27,8 @@ public void DetectedSpecificationVersionShouldBeV2_0() { var actual = OpenApiDocument.Load("V2Tests/Samples/basic.v2.yaml"); - actual.OpenApiDiagnostic.Should().NotBeNull(); - actual.OpenApiDiagnostic.SpecificationVersion.Should().Be(OpenApiSpecVersion.OpenApi2_0); + actual.Diagnostic.Should().NotBeNull(); + actual.Diagnostic.SpecificationVersion.Should().Be(OpenApiSpecVersion.OpenApi2_0); } [Fact] @@ -36,8 +36,8 @@ public void DetectedSpecificationVersionShouldBeV3_0() { var actual = OpenApiDocument.Load("V3Tests/Samples/OpenApiDocument/minimalDocument.yaml"); - actual.OpenApiDiagnostic.Should().NotBeNull(); - actual.OpenApiDiagnostic.SpecificationVersion.Should().Be(OpenApiSpecVersion.OpenApi3_0); + actual.Diagnostic.Should().NotBeNull(); + actual.Diagnostic.SpecificationVersion.Should().Be(OpenApiSpecVersion.OpenApi3_0); } [Fact] @@ -55,11 +55,8 @@ public async Task DiagnosticReportMergedForExternalReferenceAsync() result = await OpenApiDocument.LoadAsync("OpenApiReaderTests/Samples/OpenApiDiagnosticReportMerged/TodoMain.yaml", settings); Assert.NotNull(result); - Assert.NotNull(result.OpenApiDocument.Workspace); - result.OpenApiDiagnostic.Errors.Should().BeEquivalentTo(new List - { - new OpenApiError("", "[File: ./TodoReference.yaml] Paths is a REQUIRED field at #/") - }); + Assert.NotNull(result.Document.Workspace); + result.Diagnostic.Errors.Should().BeEmpty(); } } diff --git a/test/Microsoft.OpenApi.Readers.Tests/OpenApiReaderTests/OpenApiStreamReaderTests.cs b/test/Microsoft.OpenApi.Readers.Tests/OpenApiReaderTests/OpenApiStreamReaderTests.cs index 82a410946..da5ed0226 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/OpenApiReaderTests/OpenApiStreamReaderTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/OpenApiReaderTests/OpenApiStreamReaderTests.cs @@ -65,7 +65,7 @@ public async Task StreamShouldReadWhenInitializedAsync() // Read V3 as YAML var result = OpenApiDocument.Load(stream, "yaml"); - Assert.NotNull(result.OpenApiDocument); + Assert.NotNull(result.Document); } } } diff --git a/test/Microsoft.OpenApi.Readers.Tests/OpenApiWorkspaceTests/OpenApiWorkspaceStreamTests.cs b/test/Microsoft.OpenApi.Readers.Tests/OpenApiWorkspaceTests/OpenApiWorkspaceStreamTests.cs index 4aca9b54e..2b079ffb8 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/OpenApiWorkspaceTests/OpenApiWorkspaceStreamTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/OpenApiWorkspaceTests/OpenApiWorkspaceStreamTests.cs @@ -46,7 +46,7 @@ public async Task LoadingDocumentWithResolveAllReferencesShouldLoadDocumentIntoW var result = await OpenApiDocument.LoadAsync(stream, OpenApiConstants.Yaml, settings: settings); - Assert.NotNull(result.OpenApiDocument.Workspace); + Assert.NotNull(result.Document.Workspace); } [Fact] @@ -63,14 +63,14 @@ public async Task LoadDocumentWithExternalReferenceShouldLoadBothDocumentsIntoWo ReadResult result; result = await OpenApiDocument.LoadAsync("V3Tests/Samples/OpenApiWorkspace/TodoMain.yaml", settings); - var externalDocBaseUri = result.OpenApiDocument.Workspace.GetDocumentId("./TodoComponents.yaml"); + var externalDocBaseUri = result.Document.Workspace.GetDocumentId("./TodoComponents.yaml"); var schemasPath = "/components/schemas/"; var parametersPath = "/components/parameters/"; Assert.NotNull(externalDocBaseUri); - Assert.True(result.OpenApiDocument.Workspace.Contains(externalDocBaseUri + schemasPath + "todo")); - Assert.True(result.OpenApiDocument.Workspace.Contains(externalDocBaseUri + schemasPath + "entity")); - Assert.True(result.OpenApiDocument.Workspace.Contains(externalDocBaseUri + parametersPath + "filter")); + Assert.True(result.Document.Workspace.Contains(externalDocBaseUri + schemasPath + "todo")); + Assert.True(result.Document.Workspace.Contains(externalDocBaseUri + schemasPath + "entity")); + Assert.True(result.Document.Workspace.Contains(externalDocBaseUri + parametersPath + "filter")); } } diff --git a/test/Microsoft.OpenApi.Readers.Tests/ParseNodeTests.cs b/test/Microsoft.OpenApi.Readers.Tests/ParseNodeTests.cs index 3f7c669b0..0e5eae1c8 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/ParseNodeTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/ParseNodeTests.cs @@ -33,9 +33,8 @@ public void BrokenSimpleList() var result = OpenApiDocument.Parse(input, "yaml"); - result.OpenApiDiagnostic.Errors.Should().BeEquivalentTo(new List() { - new OpenApiError(new OpenApiReaderException("Expected a value.")), - new OpenApiError("", "Paths is a REQUIRED field at #/") + result.Diagnostic.Errors.Should().BeEquivalentTo(new List() { + new OpenApiError(new OpenApiReaderException("Expected a value.")) }); } @@ -60,7 +59,7 @@ public void BadSchema() var res= OpenApiDocument.Parse(input, "yaml"); - res.OpenApiDiagnostic.Errors.Should().BeEquivalentTo(new List + res.Diagnostic.Errors.Should().BeEquivalentTo(new List { new(new OpenApiReaderException("schema must be a map/object") { Pointer = "#/paths/~1foo/get/responses/200/content/application~1json/schema" diff --git a/test/Microsoft.OpenApi.Readers.Tests/ReferenceService/TryLoadReferenceV2Tests.cs b/test/Microsoft.OpenApi.Readers.Tests/ReferenceService/TryLoadReferenceV2Tests.cs index d6fb3b8ba..815f2cfbe 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/ReferenceService/TryLoadReferenceV2Tests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/ReferenceService/TryLoadReferenceV2Tests.cs @@ -26,7 +26,7 @@ public void LoadParameterReference() { // Arrange var result = OpenApiDocument.Load(Path.Combine(SampleFolderPath, "multipleReferences.v2.yaml")); - var reference = new OpenApiParameterReference("skipParam", result.OpenApiDocument); + var reference = new OpenApiParameterReference("skipParam", result.Document); // Assert reference.Should().BeEquivalentTo( @@ -51,7 +51,7 @@ public void LoadSecuritySchemeReference() { var result = OpenApiDocument.Load(Path.Combine(SampleFolderPath, "multipleReferences.v2.yaml")); - var reference = new OpenApiSecuritySchemeReference("api_key_sample", result.OpenApiDocument); + var reference = new OpenApiSecuritySchemeReference("api_key_sample", result.Document); // Assert reference.Should().BeEquivalentTo( @@ -69,7 +69,7 @@ public void LoadResponseReference() { var result = OpenApiDocument.Load(Path.Combine(SampleFolderPath, "multipleReferences.v2.yaml")); - var reference = new OpenApiResponseReference("NotFound", result.OpenApiDocument); + var reference = new OpenApiResponseReference("NotFound", result.Document); // Assert reference.Should().BeEquivalentTo( @@ -88,7 +88,7 @@ public void LoadResponseReference() public void LoadResponseAndSchemaReference() { var result = OpenApiDocument.Load(Path.Combine(SampleFolderPath, "multipleReferences.v2.yaml")); - var reference = new OpenApiResponseReference("GeneralError", result.OpenApiDocument); + var reference = new OpenApiResponseReference("GeneralError", result.Document); // Assert reference.Should().BeEquivalentTo( @@ -118,7 +118,7 @@ public void LoadResponseAndSchemaReference() { Type = ReferenceType.Schema, Id = "SampleObject2", - HostDocument = result.OpenApiDocument + HostDocument = result.Document } } } diff --git a/test/Microsoft.OpenApi.Readers.Tests/TestCustomExtension.cs b/test/Microsoft.OpenApi.Readers.Tests/TestCustomExtension.cs index 9d7727aae..9e7d19c7f 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/TestCustomExtension.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/TestCustomExtension.cs @@ -42,7 +42,7 @@ public void ParseCustomExtension() var diag = new OpenApiDiagnostic(); var actual = OpenApiDocument.Parse(description, "yaml", settings: settings); - var fooExtension = actual.OpenApiDocument.Info.Extensions["x-foo"] as FooExtension; + var fooExtension = actual.Document.Info.Extensions["x-foo"] as FooExtension; fooExtension.Should().NotBeNull(); fooExtension.Bar.Should().Be("hey"); diff --git a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/ComparisonTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/ComparisonTests.cs index b3e30c672..83e14118c 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/ComparisonTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/ComparisonTests.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.IO; @@ -26,10 +26,10 @@ public void EquivalentV2AndV3DocumentsShouldProduceEquivalentObjects(string file var result1 = OpenApiDocument.Load(Path.Combine(SampleFolderPath, $"{fileName}.v2.yaml")); var result2 = OpenApiDocument.Load(Path.Combine(SampleFolderPath, $"{fileName}.v3.yaml")); - result2.OpenApiDocument.Should().BeEquivalentTo(result1.OpenApiDocument, + result2.Document.Should().BeEquivalentTo(result1.Document, options => options.Excluding(x => x.Workspace).Excluding(y => y.BaseUri)); - result1.OpenApiDiagnostic.Errors.Should().BeEquivalentTo(result2.OpenApiDiagnostic.Errors); + result1.Diagnostic.Errors.Should().BeEquivalentTo(result2.Diagnostic.Errors); } } } diff --git a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiDocumentTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiDocumentTests.cs index 596269644..7cf03661c 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiDocumentTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiDocumentTests.cs @@ -57,7 +57,7 @@ public void ParseDocumentWithDifferentCultureShouldSucceed(string culture) """, "yaml"); - result.OpenApiDocument.Should().BeEquivalentTo( + result.Document.Should().BeEquivalentTo( new OpenApiDocument { Info = new() @@ -96,16 +96,7 @@ public void ParseDocumentWithDifferentCultureShouldSucceed(string culture) .Excluding((IMemberInfo memberInfo) => memberInfo.Path.EndsWith("Parent")) .Excluding((IMemberInfo memberInfo) => - memberInfo.Path.EndsWith("Root"))); - - result.OpenApiDiagnostic.Should().BeEquivalentTo( - new OpenApiDiagnostic { - SpecificationVersion = OpenApiSpecVersion.OpenApi2_0, - Errors = new List() - { - new OpenApiError("", "Paths is a REQUIRED field at #/") - } - }); + memberInfo.Path.EndsWith("Root")));; } [Fact] @@ -154,16 +145,16 @@ public void ShouldParseProducesInAnyOrder() Schema = new() { Type = JsonSchemaType.Array, - Items = new OpenApiSchemaReference("Item", result.OpenApiDocument) + Items = new OpenApiSchemaReference("Item", result.Document) } }; var errorMediaType = new OpenApiMediaType { - Schema = new OpenApiSchemaReference("Error", result.OpenApiDocument) + Schema = new OpenApiSchemaReference("Error", result.Document) }; - result.OpenApiDocument.Should().BeEquivalentTo(new OpenApiDocument + result.Document.Should().BeEquivalentTo(new OpenApiDocument { Info = new() { @@ -273,16 +264,16 @@ public void ShouldAssignSchemaToAllResponses() using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "multipleProduces.json")); var result = OpenApiDocument.Load(stream, OpenApiConstants.Json); - Assert.Equal(OpenApiSpecVersion.OpenApi2_0, result.OpenApiDiagnostic.SpecificationVersion); + Assert.Equal(OpenApiSpecVersion.OpenApi2_0, result.Diagnostic.SpecificationVersion); var successSchema = new OpenApiSchema { Type = JsonSchemaType.Array, - Items = new OpenApiSchemaReference("Item", result.OpenApiDocument) + Items = new OpenApiSchemaReference("Item", result.Document) }; - var errorSchema = new OpenApiSchemaReference("Error", result.OpenApiDocument); + var errorSchema = new OpenApiSchemaReference("Error", result.Document); - var responses = result.OpenApiDocument.Paths["/items"].Operations[OperationType.Get].Responses; + var responses = result.Document.Paths["/items"].Operations[OperationType.Get].Responses; foreach (var response in responses) { var targetSchema = response.Key == "200" ? successSchema : errorSchema; @@ -301,7 +292,7 @@ public void ShouldAssignSchemaToAllResponses() public void ShouldAllowComponentsThatJustContainAReference() { // Act - var actual = OpenApiDocument.Load(Path.Combine(SampleFolderPath, "ComponentRootReference.json")).OpenApiDocument; + var actual = OpenApiDocument.Load(Path.Combine(SampleFolderPath, "ComponentRootReference.json")).Document; var schema1 = actual.Components.Schemas["AllPets"]; Assert.False(schema1.UnresolvedReference); var schema2 = actual.ResolveReferenceTo(schema1.Reference); @@ -321,7 +312,7 @@ public void ParseDocumentWithDefaultContentTypeSettingShouldSucceed() }; var actual = OpenApiDocument.Load(Path.Combine(SampleFolderPath, "docWithEmptyProduces.yaml"), settings); - var mediaType = actual.OpenApiDocument.Paths["/example"].Operations[OperationType.Get].Responses["200"].Content; + var mediaType = actual.Document.Paths["/example"].Operations[OperationType.Get].Responses["200"].Content; Assert.Contains("application/json", mediaType); } @@ -330,7 +321,6 @@ public void testContentType() { var contentType = "application/json; charset = utf-8"; var res = contentType.Split(";".ToCharArray(), StringSplitOptions.RemoveEmptyEntries).First(); - var expected = res.Split('/').LastOrDefault(); Assert.Equal("application/json", res); } } diff --git a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiServerTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiServerTests.cs index 2e5779adb..e0c076ee3 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiServerTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiServerTests.cs @@ -27,7 +27,7 @@ public void NoServer() var result = OpenApiDocument.Parse(input, "yaml"); - Assert.Empty(result.OpenApiDocument.Servers); + Assert.Empty(result.Document.Servers); } [Fact] @@ -45,7 +45,7 @@ public void JustSchemeNoDefault() """; var result = OpenApiDocument.Parse(input, "yaml"); - Assert.Empty(result.OpenApiDocument.Servers); + Assert.Empty(result.Document.Servers); } [Fact] @@ -62,8 +62,8 @@ public void JustHostNoDefault() """; var result = OpenApiDocument.Parse(input, "yaml"); - var server = result.OpenApiDocument.Servers.First(); - Assert.Single(result.OpenApiDocument.Servers); + var server = result.Document.Servers.First(); + Assert.Single(result.Document.Servers); Assert.Equal("//www.foo.com", server.Url); } @@ -87,8 +87,8 @@ public void NoBasePath() }; var result = OpenApiDocument.Parse(input, "yaml", settings); - var server = result.OpenApiDocument.Servers.First(); - Assert.Single(result.OpenApiDocument.Servers); + var server = result.Document.Servers.First(); + Assert.Single(result.Document.Servers); Assert.Equal("http://www.foo.com", server.Url); } @@ -106,8 +106,8 @@ public void JustBasePathNoDefault() """; var result = OpenApiDocument.Parse(input, "yaml"); - var server = result.OpenApiDocument.Servers.First(); - Assert.Single(result.OpenApiDocument.Servers); + var server = result.Document.Servers.First(); + Assert.Single(result.Document.Servers); Assert.Equal("/baz", server.Url); } @@ -131,8 +131,8 @@ public void JustSchemeWithCustomHost() var result = OpenApiDocument.Parse(input, "yaml", settings); - var server = result.OpenApiDocument.Servers.First(); - Assert.Single(result.OpenApiDocument.Servers); + var server = result.Document.Servers.First(); + Assert.Single(result.Document.Servers); Assert.Equal("http://bing.com/foo", server.Url); } @@ -156,8 +156,8 @@ public void JustSchemeWithCustomHostWithEmptyPath() var result = OpenApiDocument.Parse(input, "yaml", settings); - var server = result.OpenApiDocument.Servers.First(); - Assert.Single(result.OpenApiDocument.Servers); + var server = result.Document.Servers.First(); + Assert.Single(result.Document.Servers); Assert.Equal("http://bing.com", server.Url); } @@ -180,8 +180,8 @@ public void JustBasePathWithCustomHost() var result = OpenApiDocument.Parse(input, "yaml", settings); - var server = result.OpenApiDocument.Servers.First(); - Assert.Single(result.OpenApiDocument.Servers); + var server = result.Document.Servers.First(); + Assert.Single(result.Document.Servers); Assert.Equal("https://bing.com/api", server.Url); } @@ -204,8 +204,8 @@ public void JustHostWithCustomHost() var result = OpenApiDocument.Parse(input, "yaml", settings); - var server = result.OpenApiDocument.Servers.First(); - Assert.Single(result.OpenApiDocument.Servers); + var server = result.Document.Servers.First(); + Assert.Single(result.Document.Servers); Assert.Equal("https://www.example.com", server.Url); } @@ -228,8 +228,8 @@ public void JustHostWithCustomHostWithApi() }; var result = OpenApiDocument.Parse(input, "yaml", settings); - var server = result.OpenApiDocument.Servers.First(); - Assert.Single(result.OpenApiDocument.Servers); + var server = result.Document.Servers.First(); + Assert.Single(result.Document.Servers); Assert.Equal("https://prod.bing.com", server.Url); } @@ -254,10 +254,10 @@ public void MultipleServers() }; var result = OpenApiDocument.Parse(input, "yaml", settings); - var server = result.OpenApiDocument.Servers.First(); - Assert.Equal(2, result.OpenApiDocument.Servers.Count); + var server = result.Document.Servers.First(); + Assert.Equal(2, result.Document.Servers.Count); Assert.Equal("http://dev.bing.com/api", server.Url); - Assert.Equal("https://dev.bing.com/api", result.OpenApiDocument.Servers.Last().Url); + Assert.Equal("https://dev.bing.com/api", result.Document.Servers.Last().Url); } [Fact] @@ -280,8 +280,8 @@ public void LocalHostWithCustomHost() var result = OpenApiDocument.Parse(input, "yaml", settings); - var server = result.OpenApiDocument.Servers.First(); - Assert.Single(result.OpenApiDocument.Servers); + var server = result.Document.Servers.First(); + Assert.Single(result.Document.Servers); Assert.Equal("https://localhost:23232", server.Url); } @@ -304,14 +304,13 @@ public void InvalidHostShouldYieldError() }; var result = OpenApiDocument.Parse(input, "yaml", settings); - result.OpenApiDocument.Servers.Count.Should().Be(0); - result.OpenApiDiagnostic.Should().BeEquivalentTo( + result.Document.Servers.Count.Should().Be(0); + result.Diagnostic.Should().BeEquivalentTo( new OpenApiDiagnostic { Errors = { - new OpenApiError("#/", "Invalid host"), - new OpenApiError("", "Paths is a REQUIRED field at #/") + new OpenApiError("#/", "Invalid host") }, SpecificationVersion = OpenApiSpecVersion.OpenApi2_0 }); diff --git a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiDocumentTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiDocumentTests.cs index 638d69667..de5471a44 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiDocumentTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiDocumentTests.cs @@ -30,9 +30,9 @@ public void ParseDocumentWithWebhooksShouldSucceed() { // Arrange and Act var actual = OpenApiDocument.Load(Path.Combine(SampleFolderPath, "documentWithWebhooks.yaml")); - var petSchema = new OpenApiSchemaReference("petSchema", actual.OpenApiDocument); + var petSchema = new OpenApiSchemaReference("petSchema", actual.Document); - var newPetSchema = new OpenApiSchemaReference("newPetSchema", actual.OpenApiDocument); + var newPetSchema = new OpenApiSchemaReference("newPetSchema", actual.Document); var components = new OpenApiComponents { @@ -200,8 +200,8 @@ public void ParseDocumentWithWebhooksShouldSucceed() }; // Assert - actual.OpenApiDiagnostic.Should().BeEquivalentTo(new OpenApiDiagnostic() { SpecificationVersion = OpenApiSpecVersion.OpenApi3_1 }); - actual.OpenApiDocument.Should().BeEquivalentTo(expected, options => options.Excluding(x => x.Workspace).Excluding(y => y.BaseUri)); + actual.Diagnostic.Should().BeEquivalentTo(new OpenApiDiagnostic() { SpecificationVersion = OpenApiSpecVersion.OpenApi3_1 }); + actual.Document.Should().BeEquivalentTo(expected, options => options.Excluding(x => x.Workspace).Excluding(y => y.BaseUri)); } [Fact] @@ -267,9 +267,9 @@ public void ParseDocumentsWithReusablePathItemInWebhooksSucceeds() }; // Create a clone of the schema to avoid modifying things in components. - var petSchema = new OpenApiSchemaReference("petSchema", actual.OpenApiDocument); + var petSchema = new OpenApiSchemaReference("petSchema", actual.Document); - var newPetSchema = new OpenApiSchemaReference("newPetSchema", actual.OpenApiDocument); + var newPetSchema = new OpenApiSchemaReference("newPetSchema", actual.Document); components.PathItems = new Dictionary { @@ -387,17 +387,12 @@ public void ParseDocumentsWithReusablePathItemInWebhooksSucceeds() }; // Assert - actual.OpenApiDocument.Should().BeEquivalentTo(expected, options => options + actual.Document.Should().BeEquivalentTo(expected, options => options .Excluding(x => x.Webhooks["pets"].Reference) .Excluding(x => x.Workspace) .Excluding(y => y.BaseUri)); - actual.OpenApiDiagnostic.Should().BeEquivalentTo( - new OpenApiDiagnostic() { SpecificationVersion = OpenApiSpecVersion.OpenApi3_1 }); - - var outputWriter = new StringWriter(CultureInfo.InvariantCulture); - var writer = new OpenApiJsonWriter(outputWriter, new() { InlineLocalReferences = true }); - actual.OpenApiDocument.SerializeAsV31(writer); - var serialized = outputWriter.ToString(); + actual.Diagnostic.Should().BeEquivalentTo( + new OpenApiDiagnostic() { SpecificationVersion = OpenApiSpecVersion.OpenApi3_1 }); } [Fact] @@ -409,7 +404,7 @@ public void ParseDocumentWithExampleInSchemaShouldSucceed() // Act var actual = OpenApiDocument.Load(Path.Combine(SampleFolderPath, "docWithExample.yaml")); - actual.OpenApiDocument.SerializeAsV31(writer); + actual.Document.SerializeAsV31(writer); // Assert Assert.NotNull(actual); @@ -420,7 +415,7 @@ public void ParseDocumentWithPatternPropertiesInSchemaWorks() { // Arrange and Act var result = OpenApiDocument.Load(Path.Combine(SampleFolderPath, "docWithPatternPropertiesInSchema.yaml")); - var actualSchema = result.OpenApiDocument.Paths["/example"].Operations[OperationType.Get].Responses["200"].Content["application/json"].Schema; + var actualSchema = result.Document.Paths["/example"].Operations[OperationType.Get].Responses["200"].Content["application/json"].Schema; var expectedSchema = new OpenApiSchema { @@ -450,7 +445,7 @@ public void ParseDocumentWithPatternPropertiesInSchemaWorks() }; // Serialization - var mediaType = result.OpenApiDocument.Paths["/example"].Operations[OperationType.Get].Responses["200"].Content["application/json"]; + var mediaType = result.Document.Paths["/example"].Operations[OperationType.Get].Responses["200"].Content["application/json"]; var expectedMediaType = @"schema: patternProperties: @@ -478,9 +473,9 @@ public void ParseDocumentWithReferenceByIdGetsResolved() // Arrange and Act var result = OpenApiDocument.Load(Path.Combine(SampleFolderPath, "docWithReferenceById.yaml")); - var responseSchema = result.OpenApiDocument.Paths["/resource"].Operations[OperationType.Get].Responses["200"].Content["application/json"].Schema; - var requestBodySchema = result.OpenApiDocument.Paths["/resource"].Operations[OperationType.Post].RequestBody.Content["application/json"].Schema; - var parameterSchema = result.OpenApiDocument.Paths["/resource"].Operations[OperationType.Get].Parameters[0].Schema; + var responseSchema = result.Document.Paths["/resource"].Operations[OperationType.Get].Responses["200"].Content["application/json"].Schema; + var requestBodySchema = result.Document.Paths["/resource"].Operations[OperationType.Post].RequestBody.Content["application/json"].Schema; + var parameterSchema = result.Document.Paths["/resource"].Operations[OperationType.Get].Parameters[0].Schema; // Assert Assert.Equal(JsonSchemaType.Object, responseSchema.Type); @@ -502,10 +497,10 @@ public async Task ExternalDocumentDereferenceToOpenApiDocumentUsingJsonPointerWo // Act var result = await OpenApiDocument.LoadAsync(Path.Combine(SampleFolderPath, "externalRefByJsonPointer.yaml"), settings); - var responseSchema = result.OpenApiDocument.Paths["/resource"].Operations[OperationType.Get].Responses["200"].Content["application/json"].Schema; + var responseSchema = result.Document.Paths["/resource"].Operations[OperationType.Get].Responses["200"].Content["application/json"].Schema; // Assert - result.OpenApiDocument.Workspace.Contains("./externalResource.yaml"); + result.Document.Workspace.Contains("./externalResource.yaml"); responseSchema.Properties.Count.Should().Be(2); // reference has been resolved } @@ -523,10 +518,10 @@ public async Task ParseExternalDocumentDereferenceToOpenApiDocumentByIdWorks() // Act var result = await OpenApiDocument.LoadAsync(Path.Combine(SampleFolderPath, "externalRefById.yaml"), settings); - var doc2 = OpenApiDocument.Load(Path.Combine(SampleFolderPath, "externalResource.yaml")).OpenApiDocument; + var doc2 = (await OpenApiDocument.LoadAsync(Path.Combine(SampleFolderPath, "externalResource.yaml"))).Document; - var requestBodySchema = result.OpenApiDocument.Paths["/resource"].Operations[OperationType.Get].Parameters.First().Schema; - result.OpenApiDocument.Workspace.RegisterComponents(doc2); + var requestBodySchema = result.Document.Paths["/resource"].Operations[OperationType.Get].Parameters[0].Schema; + result.Document.Workspace.RegisterComponents(doc2); // Assert requestBodySchema.Properties.Count.Should().Be(2); // reference has been resolved @@ -536,10 +531,10 @@ public async Task ParseExternalDocumentDereferenceToOpenApiDocumentByIdWorks() public async Task ParseDocumentWith31PropertiesWorks() { var path = Path.Combine(SampleFolderPath, "documentWith31Properties.yaml"); - var doc = OpenApiDocument.Load(path).OpenApiDocument; + var doc = (await OpenApiDocument.LoadAsync(path)).Document; var outputStringWriter = new StringWriter(); doc.SerializeAsV31(new OpenApiYamlWriter(outputStringWriter)); - outputStringWriter.Flush(); + await outputStringWriter.FlushAsync(); var actual = outputStringWriter.GetStringBuilder().ToString(); // Assert diff --git a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiSchemaTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiSchemaTests.cs index 967bb0f3e..4a6fdac50 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiSchemaTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiSchemaTests.cs @@ -452,5 +452,64 @@ public void SerializeSchemaWithJsonSchemaKeywordsWorks() schema.Vocabulary.Keys.Count.Should().Be(5); schemaString.MakeLineBreaksEnvironmentNeutral().Should().Be(expected.MakeLineBreaksEnvironmentNeutral()); } + + [Fact] + public void ParseSchemaWithConstWorks() + { + var expected = @"{ + ""$schema"": ""https://json-schema.org/draft/2020-12/schema"", + ""required"": [ + ""status"" + ], + ""type"": ""object"", + ""properties"": { + ""status"": { + ""const"": ""active"", + ""type"": ""string"" + }, + ""user"": { + ""required"": [ + ""role"" + ], + ""type"": ""object"", + ""properties"": { + ""role"": { + ""const"": ""admin"", + ""type"": ""string"" + } + } + } + } +}"; + + var path = Path.Combine(SampleFolderPath, "schemaWithConst.json"); + + // Act + var schema = OpenApiModelFactory.Load(path, OpenApiSpecVersion.OpenApi3_1, out _); + schema.Properties["status"].Const.Should().Be("active"); + schema.Properties["user"].Properties["role"].Const.Should().Be("admin"); + + // serialization + var writer = new StringWriter(); + schema.SerializeAsV31(new OpenApiJsonWriter(writer)); + var schemaString = writer.ToString(); + schemaString.MakeLineBreaksEnvironmentNeutral().Should().Be(expected.MakeLineBreaksEnvironmentNeutral()); + } + + [Fact] + public void ParseSchemaWithUnrecognizedKeywordsWorks() + { + var input = @"{ + ""type"": ""string"", + ""format"": ""date-time"", + ""customKeyword"": ""customValue"", + ""anotherKeyword"": 42, + ""x-test"": ""test"" +} +"; + var schema = OpenApiModelFactory.Parse(input, OpenApiSpecVersion.OpenApi3_1, out _, "json"); + schema.UnrecognizedKeywords.Should().HaveCount(2); + } + } } diff --git a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiSchema/schemaWithConst.json b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiSchema/schemaWithConst.json new file mode 100644 index 000000000..ec0a0c794 --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiSchema/schemaWithConst.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "status": { + "type": "string", + "const": "active" + }, + "user": { + "type": "object", + "properties": { + "role": { + "type": "string", + "const": "admin" + } + }, + "required": [ "role" ] + } + }, + "required": [ "status" ] +} diff --git a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiSchema/schemaWithJsonSchemaKeywords.yaml b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiSchema/schemaWithJsonSchemaKeywords.yaml index 3d88cffcd..8c4fb1c1b 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiSchema/schemaWithJsonSchemaKeywords.yaml +++ b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiSchema/schemaWithJsonSchemaKeywords.yaml @@ -28,14 +28,3 @@ required: - name $dynamicAnchor: "addressDef" -definitions: - address: - $dynamicAnchor: "addressDef" - type: "object" - properties: - street: - type: "string" - city: - type: "string" - postalCode: - type: "string" diff --git a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiCallbackTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiCallbackTests.cs index cab621c14..0cf804f80 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiCallbackTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiCallbackTests.cs @@ -72,12 +72,12 @@ public void ParseCallbackWithReferenceShouldSucceed() var result = OpenApiModelFactory.Load(stream, OpenApiConstants.Yaml); // Assert - var path = result.OpenApiDocument.Paths.First().Value; + var path = result.Document.Paths.First().Value; var subscribeOperation = path.Operations[OperationType.Post]; var callback = subscribeOperation.Callbacks["simpleHook"]; - result.OpenApiDiagnostic.Should().BeEquivalentTo( + result.Diagnostic.Should().BeEquivalentTo( new OpenApiDiagnostic() { SpecificationVersion = OpenApiSpecVersion.OpenApi3_0 }); callback.Should().BeEquivalentTo( @@ -116,7 +116,7 @@ public void ParseCallbackWithReferenceShouldSucceed() { Type = ReferenceType.Callback, Id = "simpleHook", - HostDocument = result.OpenApiDocument + HostDocument = result.Document } }); } @@ -128,10 +128,10 @@ public void ParseMultipleCallbacksWithReferenceShouldSucceed() var result = OpenApiModelFactory.Load(Path.Combine(SampleFolderPath, "multipleCallbacksWithReference.yaml")); // Assert - var path = result.OpenApiDocument.Paths.First().Value; + var path = result.Document.Paths.First().Value; var subscribeOperation = path.Operations[OperationType.Post]; - result.OpenApiDiagnostic.Should().BeEquivalentTo( + result.Diagnostic.Should().BeEquivalentTo( new OpenApiDiagnostic() { SpecificationVersion = OpenApiSpecVersion.OpenApi3_0 }); var callback1 = subscribeOperation.Callbacks["simpleHook"]; @@ -172,7 +172,7 @@ public void ParseMultipleCallbacksWithReferenceShouldSucceed() { Type = ReferenceType.Callback, Id = "simpleHook", - HostDocument = result.OpenApiDocument + HostDocument = result.Document } }); diff --git a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiDocumentTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiDocumentTests.cs index 1382d0248..461df1642 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiDocumentTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiDocumentTests.cs @@ -87,7 +87,7 @@ public void ParseDocumentFromInlineStringShouldSucceed() paths: {}", OpenApiConstants.Yaml); - result.OpenApiDocument.Should().BeEquivalentTo( + result.Document.Should().BeEquivalentTo( new OpenApiDocument { Info = new OpenApiInfo @@ -98,14 +98,10 @@ public void ParseDocumentFromInlineStringShouldSucceed() Paths = new OpenApiPaths() }, options => options.Excluding(x => x.Workspace).Excluding(y => y.BaseUri)); - result.OpenApiDiagnostic.Should().BeEquivalentTo( + result.Diagnostic.Should().BeEquivalentTo( new OpenApiDiagnostic() { - SpecificationVersion = OpenApiSpecVersion.OpenApi3_0, - Errors = new List() - { - new OpenApiError("", "Paths is a REQUIRED field at #/") - } + SpecificationVersion = OpenApiSpecVersion.OpenApi3_0 }); } @@ -115,17 +111,8 @@ public void ParseBasicDocumentWithMultipleServersShouldSucceed() var path = System.IO.Path.Combine(SampleFolderPath, "basicDocumentWithMultipleServers.yaml"); var result = OpenApiDocument.Load(path); - result.OpenApiDiagnostic.Should().BeEquivalentTo( - new OpenApiDiagnostic() - { - SpecificationVersion = OpenApiSpecVersion.OpenApi3_0, - Errors = new List() - { - new OpenApiError("", "Paths is a REQUIRED field at #/") - } - }); - - result.OpenApiDocument.Should().BeEquivalentTo( + result.Diagnostic.Errors.Should().BeEmpty(); + result.Document.Should().BeEquivalentTo( new OpenApiDocument { Info = new OpenApiInfo @@ -155,7 +142,7 @@ public void ParseBrokenMinimalDocumentShouldYieldExpectedDiagnostic() using var stream = Resources.GetStream(System.IO.Path.Combine(SampleFolderPath, "brokenMinimalDocument.yaml")); var result = OpenApiDocument.Load(stream, OpenApiConstants.Yaml); - result.OpenApiDocument.Should().BeEquivalentTo( + result.Document.Should().BeEquivalentTo( new OpenApiDocument { Info = new OpenApiInfo @@ -165,12 +152,11 @@ public void ParseBrokenMinimalDocumentShouldYieldExpectedDiagnostic() Paths = new OpenApiPaths() }, options => options.Excluding(x => x.Workspace).Excluding(y => y.BaseUri)); - result.OpenApiDiagnostic.Should().BeEquivalentTo( + result.Diagnostic.Should().BeEquivalentTo( new OpenApiDiagnostic { Errors = { - new OpenApiError("", "Paths is a REQUIRED field at #/"), new OpenApiValidatorError(nameof(OpenApiInfoRules.InfoRequiredFields),"#/info/title", "The field 'title' in 'info' object is REQUIRED.") }, SpecificationVersion = OpenApiSpecVersion.OpenApi3_0 @@ -182,7 +168,7 @@ public void ParseMinimalDocumentShouldSucceed() { var result = OpenApiDocument.Load(System.IO.Path.Combine(SampleFolderPath, "minimalDocument.yaml")); - result.OpenApiDocument.Should().BeEquivalentTo( + result.Document.Should().BeEquivalentTo( new OpenApiDocument { Info = new OpenApiInfo @@ -193,14 +179,10 @@ public void ParseMinimalDocumentShouldSucceed() Paths = new OpenApiPaths() }, options => options.Excluding(x => x.Workspace).Excluding(y => y.BaseUri)); - result.OpenApiDiagnostic.Should().BeEquivalentTo( + result.Diagnostic.Should().BeEquivalentTo( new OpenApiDiagnostic() { - SpecificationVersion = OpenApiSpecVersion.OpenApi3_0, - Errors = new List() - { - new OpenApiError("", "Paths is a REQUIRED field at #/") - } + SpecificationVersion = OpenApiSpecVersion.OpenApi3_0 }); } @@ -287,10 +269,10 @@ public void ParseStandardPetStoreDocumentShouldSucceed() } }; - var petSchema = new OpenApiSchemaReference("pet1", actual.OpenApiDocument); - var newPetSchema = new OpenApiSchemaReference("newPet", actual.OpenApiDocument); + var petSchema = new OpenApiSchemaReference("pet1", actual.Document); + var newPetSchema = new OpenApiSchemaReference("newPet", actual.Document); - var errorModelSchema = new OpenApiSchemaReference("errorModel", actual.OpenApiDocument); + var errorModelSchema = new OpenApiSchemaReference("errorModel", actual.Document); var expectedDoc = new OpenApiDocument { @@ -584,9 +566,9 @@ public void ParseStandardPetStoreDocumentShouldSucceed() Components = components }; - actual.OpenApiDocument.Should().BeEquivalentTo(expectedDoc, options => options.Excluding(x => x.Workspace).Excluding(y => y.BaseUri)); + actual.Document.Should().BeEquivalentTo(expectedDoc, options => options.Excluding(x => x.Workspace).Excluding(y => y.BaseUri)); - actual.OpenApiDiagnostic.Should().BeEquivalentTo( + actual.Diagnostic.Should().BeEquivalentTo( new OpenApiDiagnostic() { SpecificationVersion = OpenApiSpecVersion.OpenApi3_0 }); } @@ -693,7 +675,7 @@ public void ParseModifiedPetStoreDocumentWithTagAndSecurityShouldSucceed() { Id = "pet1", Type = ReferenceType.Schema, - HostDocument = actual.OpenApiDocument + HostDocument = actual.Document }; var newPetSchema = Clone(components.Schemas["newPet"]); @@ -702,7 +684,7 @@ public void ParseModifiedPetStoreDocumentWithTagAndSecurityShouldSucceed() { Id = "newPet", Type = ReferenceType.Schema, - HostDocument = actual.OpenApiDocument + HostDocument = actual.Document }; var errorModelSchema = Clone(components.Schemas["errorModel"]); @@ -711,7 +693,7 @@ public void ParseModifiedPetStoreDocumentWithTagAndSecurityShouldSucceed() { Id = "errorModel", Type = ReferenceType.Schema, - HostDocument = actual.OpenApiDocument + HostDocument = actual.Document }; var tag1 = new OpenApiTag @@ -1087,7 +1069,7 @@ public void ParseModifiedPetStoreDocumentWithTagAndSecurityShouldSucceed() } }; - actual.OpenApiDocument.Should().BeEquivalentTo(expected, options => options + actual.Document.Should().BeEquivalentTo(expected, options => options .Excluding(x => x.HashCode) .Excluding(m => m.Tags[0].Reference) .Excluding(x => x.Paths["/pets"].Operations[OperationType.Get].Tags[0].Reference) @@ -1098,7 +1080,7 @@ public void ParseModifiedPetStoreDocumentWithTagAndSecurityShouldSucceed() .Excluding(x => x.Workspace) .Excluding(y => y.BaseUri)); - actual.OpenApiDiagnostic.Should().BeEquivalentTo( + actual.Diagnostic.Should().BeEquivalentTo( new OpenApiDiagnostic() { SpecificationVersion = OpenApiSpecVersion.OpenApi3_0 }); } @@ -1109,7 +1091,7 @@ public void ParsePetStoreExpandedShouldSucceed() // TODO: Create the object in memory and compare with the one read from YAML file. - actual.OpenApiDiagnostic.Should().BeEquivalentTo( + actual.Diagnostic.Should().BeEquivalentTo( new OpenApiDiagnostic() { SpecificationVersion = OpenApiSpecVersion.OpenApi3_0 }); } @@ -1118,9 +1100,9 @@ public void GlobalSecurityRequirementShouldReferenceSecurityScheme() { var result = OpenApiDocument.Load(System.IO.Path.Combine(SampleFolderPath, "securedApi.yaml")); - var securityRequirement = result.OpenApiDocument.SecurityRequirements.First(); + var securityRequirement = result.Document.SecurityRequirements[0]; - securityRequirement.Keys.First().Should().BeEquivalentTo(result.OpenApiDocument.Components.SecuritySchemes.First().Value, + securityRequirement.Keys.First().Should().BeEquivalentTo(result.Document.Components.SecuritySchemes.First().Value, options => options.Excluding(x => x.Reference)); } @@ -1129,7 +1111,7 @@ public void HeaderParameterShouldAllowExample() { var result = OpenApiDocument.Load(System.IO.Path.Combine(SampleFolderPath, "apiWithFullHeaderComponent.yaml")); - var exampleHeader = result.OpenApiDocument.Components?.Headers?["example-header"]; + var exampleHeader = result.Document.Components?.Headers?["example-header"]; Assert.NotNull(exampleHeader); exampleHeader.Should().BeEquivalentTo( new OpenApiHeader() @@ -1151,7 +1133,7 @@ public void HeaderParameterShouldAllowExample() .Excluding(e => e.Example.Parent) .Excluding(x => x.Reference)); - var examplesHeader = result.OpenApiDocument.Components?.Headers?["examples-header"]; + var examplesHeader = result.Document.Components?.Headers?["examples-header"]; Assert.NotNull(examplesHeader); examplesHeader.Should().BeEquivalentTo( new OpenApiHeader() @@ -1196,7 +1178,7 @@ public void ParseDocumentWithReferencedSecuritySchemeWorks() }; var result = OpenApiDocument.Load(System.IO.Path.Combine(SampleFolderPath, "docWithSecuritySchemeReference.yaml"), settings); - var securityScheme = result.OpenApiDocument.Components.SecuritySchemes["OAuth2"]; + var securityScheme = result.Document.Components.SecuritySchemes["OAuth2"]; // Assert Assert.False(securityScheme.UnresolvedReference); @@ -1216,9 +1198,9 @@ public void ParseDocumentWithJsonSchemaReferencesWorks() }; var result = OpenApiDocument.Load(stream, OpenApiConstants.Yaml, settings); - var actualSchema = result.OpenApiDocument.Paths["/users/{userId}"].Operations[OperationType.Get].Responses["200"].Content["application/json"].Schema; + var actualSchema = result.Document.Paths["/users/{userId}"].Operations[OperationType.Get].Responses["200"].Content["application/json"].Schema; - var expectedSchema = new OpenApiSchemaReference("User", result.OpenApiDocument); + var expectedSchema = new OpenApiSchemaReference("User", result.Document); // Assert actualSchema.Should().BeEquivalentTo(expectedSchema); } @@ -1234,7 +1216,7 @@ public void ValidateExampleShouldNotHaveDataTypeMismatch() }); // Assert - var warnings = result.OpenApiDiagnostic.Warnings; + var warnings = result.Diagnostic.Warnings; Assert.False(warnings.Any()); } @@ -1330,11 +1312,10 @@ public void ParseDocWithRefsUsingProxyReferencesSucceeds() using var stream = Resources.GetStream(System.IO.Path.Combine(SampleFolderPath, "minifiedPetStore.yaml")); // Act - var doc = OpenApiDocument.Load(stream, "yaml").OpenApiDocument; - var actualParam = doc.Paths["/pets"].Operations[OperationType.Get].Parameters.First(); + var doc = OpenApiDocument.Load(stream, "yaml").Document; + var actualParam = doc.Paths["/pets"].Operations[OperationType.Get].Parameters[0]; var outputDoc = doc.SerializeAsYaml(OpenApiSpecVersion.OpenApi3_0).MakeLineBreaksEnvironmentNeutral(); - var output = actualParam.SerializeAsYaml(OpenApiSpecVersion.OpenApi3_0); - var expectedParam = expected.Paths["/pets"].Operations[OperationType.Get].Parameters.First(); + var expectedParam = expected.Paths["/pets"].Operations[OperationType.Get].Parameters[0]; // Assert actualParam.Should().BeEquivalentTo(expectedParam, options => options @@ -1385,17 +1366,13 @@ public void ParseBasicDocumentWithServerVariableShouldSucceed() Paths = new() }; - result.OpenApiDiagnostic.Should().BeEquivalentTo( + result.Diagnostic.Should().BeEquivalentTo( new OpenApiDiagnostic { - SpecificationVersion = OpenApiSpecVersion.OpenApi3_0, - Errors = new List() - { - new OpenApiError("", "Paths is a REQUIRED field at #/") - } + SpecificationVersion = OpenApiSpecVersion.OpenApi3_0 }); - result.OpenApiDocument.Should().BeEquivalentTo(expected, options => options.Excluding(x => x.BaseUri)); + result.Document.Should().BeEquivalentTo(expected, options => options.Excluding(x => x.BaseUri)); } [Fact] @@ -1415,7 +1392,14 @@ public void ParseBasicDocumentWithServerVariableAndNoDefaultShouldFail() paths: {} """, "yaml"); - result.OpenApiDiagnostic.Errors.Should().NotBeEmpty(); + result.Diagnostic.Errors.Should().NotBeEmpty(); + } + + [Fact] + public void ParseDocumentWithEmptyPathsSucceeds() + { + var result = OpenApiDocument.Load(System.IO.Path.Combine(SampleFolderPath, "docWithEmptyPaths.yaml")); + result.Diagnostic.Errors.Should().BeEmpty(); } } } diff --git a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiExampleTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiExampleTests.cs index 84f028f6b..18007d112 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiExampleTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiExampleTests.cs @@ -82,7 +82,7 @@ public void ParseAdvancedExampleShouldSucceed() public void ParseExampleForcedStringSucceed() { var result= OpenApiDocument.Load(Path.Combine(SampleFolderPath, "explicitString.yaml")); - result.OpenApiDiagnostic.Errors.Should().BeEmpty(); + result.Diagnostic.Errors.Should().BeEmpty(); } } } diff --git a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiOperationTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiOperationTests.cs index 9ba96bbda..4c8af2aeb 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiOperationTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiOperationTests.cs @@ -25,9 +25,9 @@ public void OperationWithSecurityRequirementShouldReferenceSecurityScheme() { var result = OpenApiDocument.Load(Path.Combine(SampleFolderPath, "securedOperation.yaml")); - var securityScheme = result.OpenApiDocument.Paths["/"].Operations[OperationType.Get].Security.First().Keys.First(); + var securityScheme = result.Document.Paths["/"].Operations[OperationType.Get].Security.First().Keys.First(); - securityScheme.Should().BeEquivalentTo(result.OpenApiDocument.Components.SecuritySchemes.First().Value, + securityScheme.Should().BeEquivalentTo(result.Document.Components.SecuritySchemes.First().Value, options => options.Excluding(x => x.Reference)); } diff --git a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiResponseTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiResponseTests.cs index 09a1d00a1..f14b89514 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiResponseTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiResponseTests.cs @@ -25,9 +25,9 @@ public void ResponseWithReferencedHeaderShouldReferenceComponent() { var result = OpenApiDocument.Load(Path.Combine(SampleFolderPath, "responseWithHeaderReference.yaml")); - var response = result.OpenApiDocument.Components.Responses["Test"]; + var response = result.Document.Components.Responses["Test"]; var expected = response.Headers.First().Value; - var actual = result.OpenApiDocument.Components.Headers.First().Value; + var actual = result.Document.Components.Headers.First().Value; actual.Description.Should().BeEquivalentTo(expected.Description); } diff --git a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiSchemaTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiSchemaTests.cs index 81cb4376b..d751c89d0 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiSchemaTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiSchemaTests.cs @@ -236,16 +236,12 @@ public void ParseBasicSchemaWithReferenceShouldSucceed() var result = OpenApiDocument.Load(Path.Combine(SampleFolderPath, "basicSchemaWithReference.yaml")); // Assert - var components = result.OpenApiDocument.Components; + var components = result.Document.Components; - result.OpenApiDiagnostic.Should().BeEquivalentTo( + result.Diagnostic.Should().BeEquivalentTo( new OpenApiDiagnostic() { - SpecificationVersion = OpenApiSpecVersion.OpenApi3_0, - Errors = new List() - { - new OpenApiError("", "Paths is a REQUIRED field at #/") - } + SpecificationVersion = OpenApiSpecVersion.OpenApi3_0 }); var expectedComponents = new OpenApiComponents @@ -278,7 +274,7 @@ public void ParseBasicSchemaWithReferenceShouldSucceed() { AllOf = { - new OpenApiSchemaReference("ErrorModel", result.OpenApiDocument), + new OpenApiSchemaReference("ErrorModel", result.Document), new OpenApiSchema { Type = JsonSchemaType.Object, @@ -338,7 +334,7 @@ public void ParseAdvancedSchemaWithReferenceShouldSucceed() Description = "A representation of a cat", AllOf = { - new OpenApiSchemaReference("Pet", result.OpenApiDocument), + new OpenApiSchemaReference("Pet", result.Document), new OpenApiSchema { Type = JsonSchemaType.Object, @@ -366,7 +362,7 @@ public void ParseAdvancedSchemaWithReferenceShouldSucceed() Description = "A representation of a dog", AllOf = { - new OpenApiSchemaReference("Pet", result.OpenApiDocument), + new OpenApiSchemaReference("Pet", result.Document), new OpenApiSchema { Type = JsonSchemaType.Object, @@ -389,7 +385,7 @@ public void ParseAdvancedSchemaWithReferenceShouldSucceed() }; // We serialize so that we can get rid of the schema BaseUri properties which show up as diffs - var actual = result.OpenApiDocument.Components.SerializeAsYaml(OpenApiSpecVersion.OpenApi3_0); + var actual = result.Document.Components.SerializeAsYaml(OpenApiSpecVersion.OpenApi3_0); var expected = expectedComponents.SerializeAsYaml(OpenApiSpecVersion.OpenApi3_0); // Assert diff --git a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/Samples/OpenApiDocument/docWithEmptyPaths.yaml b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/Samples/OpenApiDocument/docWithEmptyPaths.yaml new file mode 100644 index 000000000..a325ad743 --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/Samples/OpenApiDocument/docWithEmptyPaths.yaml @@ -0,0 +1,5 @@ +openapi: 3.0.0 +info: + title: Sample API + version: 1.0.0 +paths: {} \ 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 fe422d3e7..88977a0b9 100644 --- a/test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj +++ b/test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj @@ -10,16 +10,15 @@ - - + + - - + - + - + diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.cs index 884ffa68c..8d569015c 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.cs @@ -992,7 +992,7 @@ public OpenApiDocumentTests() { ["my-extension"] = new OpenApiAny(4) } - }, + }, Extensions = new Dictionary { ["my-extension"] = new OpenApiAny(4), @@ -1707,7 +1707,7 @@ private static OpenApiDocument ParseInputFile(string filePath) // Read in the input yaml file using FileStream stream = File.OpenRead(filePath); var format = OpenApiModelFactory.GetFormat(filePath); - var openApiDoc = OpenApiDocument.Load(stream, format).OpenApiDocument; + var openApiDoc = OpenApiDocument.Load(stream, format).Document; return openApiDoc; } @@ -2013,7 +2013,7 @@ public void SerializeV31DocumentWithRefsInWebhooksWorks() items: type: object"; - var doc = OpenApiDocument.Load("Models/Samples/docWithReusableWebhooks.yaml").OpenApiDocument; + var doc = OpenApiDocument.Load("Models/Samples/docWithReusableWebhooks.yaml").Document; var stringWriter = new StringWriter(); var writer = new OpenApiYamlWriter(stringWriter, new OpenApiWriterSettings { InlineLocalReferences = true }); @@ -2067,10 +2067,64 @@ public void SerializeDocWithDollarIdInDollarRefSucceeds() radius: type: number "; - var doc = OpenApiDocument.Load("Models/Samples/docWithDollarId.yaml").OpenApiDocument; + var doc = OpenApiDocument.Load("Models/Samples/docWithDollarId.yaml").Document; var actual = doc.SerializeAsYaml(OpenApiSpecVersion.OpenApi3_1); actual.MakeLineBreaksEnvironmentNeutral().Should().BeEquivalentTo(expected.MakeLineBreaksEnvironmentNeutral()); } + + [Fact] + public void SerializeDocumentTagsWithMultipleExtensionsWorks() + { + var expected = @"{ + ""openapi"": ""3.0.4"", + ""info"": { + ""title"": ""Test"", + ""version"": ""1.0.0"" + }, + ""paths"": { }, + ""tags"": [ + { + ""name"": ""tag1"", + ""x-tag1"": ""tag1"" + }, + { + ""name"": ""tag2"", + ""x-tag2"": ""tag2"" + } + ] +}"; + var doc = new OpenApiDocument + { + Info = new OpenApiInfo + { + Title = "Test", + Version = "1.0.0" + }, + Paths = new OpenApiPaths(), + Tags = new List + { + new OpenApiTag + { + Name = "tag1", + Extensions = new Dictionary + { + ["x-tag1"] = new OpenApiAny("tag1") + } + }, + new OpenApiTag + { + Name = "tag2", + Extensions = new Dictionary + { + ["x-tag2"] = new OpenApiAny("tag2") + } + } + } + }; + + var actual = doc.SerializeAsJson(OpenApiSpecVersion.OpenApi3_0); + actual.MakeLineBreaksEnvironmentNeutral().Should().BeEquivalentTo(expected.MakeLineBreaksEnvironmentNeutral()); + } } } diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiOperationTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiOperationTests.cs index a65bf24c5..5f6b5f4e7 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiOperationTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiOperationTests.cs @@ -89,11 +89,6 @@ public class OpenApiOperationTests { Tags = new List { - new() - { - Name = "tagName1", - Description = "tagDescription1", - }, new OpenApiTagReference("tagId1", null) }, Summary = "summary1", @@ -360,7 +355,6 @@ public void SerializeAdvancedOperationWithTagAndSecurityAsV3JsonWorks() """ { "tags": [ - "tagName1", "tagId1" ], "summary": "summary1", @@ -669,7 +663,6 @@ public void SerializeAdvancedOperationWithTagAndSecurityAsV2JsonWorks() """ { "tags": [ - "tagName1", "tagId1" ], "summary": "summary1", diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.cs index 408173e6e..75ea5ca47 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.cs @@ -22,7 +22,7 @@ namespace Microsoft.OpenApi.Tests.Models [Collection("DefaultSettings")] public class OpenApiSchemaTests { - public static OpenApiSchema BasicSchema = new(); + private static readonly OpenApiSchema BasicSchema = new(); public static readonly OpenApiSchema AdvancedSchemaNumber = new() { @@ -602,15 +602,42 @@ public void OpenApiWalkerVisitsOpenApiSchemaNot() // Assert visitor.Titles.Count.Should().Be(2); } - } - internal class SchemaVisitor : OpenApiVisitorBase - { - public List Titles = new(); + [Fact] + public void SerializeSchemaWithUnrecognizedPropertiesWorks() + { + // Arrange + var schema = new OpenApiSchema + { + UnrecognizedKeywords = new Dictionary() + { + ["customKeyWord"] = "bar", + ["anotherKeyword"] = 42 + } + }; - public override void Visit(OpenApiSchema schema) + var expected = @"{ + ""unrecognizedKeywords"": { + ""customKeyWord"": ""bar"", + ""anotherKeyword"": 42 + } +}"; + + // Act + var actual = schema.SerializeAsJson(OpenApiSpecVersion.OpenApi3_1); + + // Assert + actual.MakeLineBreaksEnvironmentNeutral().Should().Be(expected.MakeLineBreaksEnvironmentNeutral()); + } + + internal class SchemaVisitor : OpenApiVisitorBase { - Titles.Add(schema.Title); + public List Titles = new(); + + public override void Visit(OpenApiSchema schema) + { + Titles.Add(schema.Title); + } } } } diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiTagTests.SerializeAdvancedTagAsV2JsonWorksAsync_produceTerseOutput=False.verified.txt b/test/Microsoft.OpenApi.Tests/Models/OpenApiTagTests.SerializeAdvancedTagAsV2JsonWorksAsync_produceTerseOutput=False.verified.txt index d3d287dca..2afa516e0 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiTagTests.SerializeAdvancedTagAsV2JsonWorksAsync_produceTerseOutput=False.verified.txt +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiTagTests.SerializeAdvancedTagAsV2JsonWorksAsync_produceTerseOutput=False.verified.txt @@ -1 +1,9 @@ -"pet" \ No newline at end of file +{ + "name": "pet", + "description": "Pets operations", + "externalDocs": { + "description": "Find more info here", + "url": "https://example.com" + }, + "x-tag-extension": null +} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiTagTests.SerializeAdvancedTagAsV2JsonWorksAsync_produceTerseOutput=True.verified.txt b/test/Microsoft.OpenApi.Tests/Models/OpenApiTagTests.SerializeAdvancedTagAsV2JsonWorksAsync_produceTerseOutput=True.verified.txt index d3d287dca..f0a901938 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiTagTests.SerializeAdvancedTagAsV2JsonWorksAsync_produceTerseOutput=True.verified.txt +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiTagTests.SerializeAdvancedTagAsV2JsonWorksAsync_produceTerseOutput=True.verified.txt @@ -1 +1 @@ -"pet" \ No newline at end of file +{"name":"pet","description":"Pets operations","externalDocs":{"description":"Find more info here","url":"https://example.com"},"x-tag-extension":null} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiTagTests.SerializeAdvancedTagAsV3JsonWorksAsync_produceTerseOutput=False.verified.txt b/test/Microsoft.OpenApi.Tests/Models/OpenApiTagTests.SerializeAdvancedTagAsV3JsonWorksAsync_produceTerseOutput=False.verified.txt index d3d287dca..2afa516e0 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiTagTests.SerializeAdvancedTagAsV3JsonWorksAsync_produceTerseOutput=False.verified.txt +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiTagTests.SerializeAdvancedTagAsV3JsonWorksAsync_produceTerseOutput=False.verified.txt @@ -1 +1,9 @@ -"pet" \ No newline at end of file +{ + "name": "pet", + "description": "Pets operations", + "externalDocs": { + "description": "Find more info here", + "url": "https://example.com" + }, + "x-tag-extension": null +} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiTagTests.SerializeAdvancedTagAsV3JsonWorksAsync_produceTerseOutput=True.verified.txt b/test/Microsoft.OpenApi.Tests/Models/OpenApiTagTests.SerializeAdvancedTagAsV3JsonWorksAsync_produceTerseOutput=True.verified.txt index d3d287dca..f0a901938 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiTagTests.SerializeAdvancedTagAsV3JsonWorksAsync_produceTerseOutput=True.verified.txt +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiTagTests.SerializeAdvancedTagAsV3JsonWorksAsync_produceTerseOutput=True.verified.txt @@ -1 +1 @@ -"pet" \ No newline at end of file +{"name":"pet","description":"Pets operations","externalDocs":{"description":"Find more info here","url":"https://example.com"},"x-tag-extension":null} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiTagTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiTagTests.cs index c02f7598c..24c186b0b 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiTagTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiTagTests.cs @@ -8,6 +8,7 @@ using FluentAssertions; using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Models.References; using Microsoft.OpenApi.Writers; using VerifyXunit; using Xunit; @@ -17,9 +18,9 @@ namespace Microsoft.OpenApi.Tests.Models [Collection("DefaultSettings")] public class OpenApiTagTests { - public static OpenApiTag BasicTag = new(); + public static readonly OpenApiTag BasicTag = new(); - public static OpenApiTag AdvancedTag = new() + public static readonly OpenApiTag AdvancedTag = new() { Name = "pet", Description = "Pets operations", @@ -30,21 +31,7 @@ public class OpenApiTagTests } }; - public static OpenApiTag ReferencedTag = new() - { - Name = "pet", - Description = "Pets operations", - ExternalDocs = OpenApiExternalDocsTests.AdvanceExDocs, - Extensions = new Dictionary - { - {"x-tag-extension", null} - }, - Reference = new() - { - Type = ReferenceType.Tag, - Id = "pet" - } - }; + public static OpenApiTag ReferencedTag = new OpenApiTagReference("pet", null); [Theory] [InlineData(true)] @@ -56,7 +43,7 @@ public async Task SerializeBasicTagAsV3JsonWithoutReferenceWorksAsync(bool produ var writer = new OpenApiJsonWriter(outputStringWriter, new() { Terse = produceTerseOutput }); // Act - BasicTag.SerializeAsV3WithoutReference(writer); + BasicTag.SerializeAsV3(writer); writer.Flush(); // Assert @@ -73,7 +60,7 @@ public async Task SerializeBasicTagAsV2JsonWithoutReferenceWorksAsync(bool produ var writer = new OpenApiJsonWriter(outputStringWriter, new() { Terse = produceTerseOutput }); // Act - BasicTag.SerializeAsV2WithoutReference(writer); + BasicTag.SerializeAsV2(writer); writer.Flush(); // Assert @@ -89,7 +76,7 @@ public void SerializeBasicTagAsV3YamlWithoutReferenceWorks() var expected = "{ }"; // Act - BasicTag.SerializeAsV3WithoutReference(writer); + BasicTag.SerializeAsV3(writer); var actual = outputStringWriter.GetStringBuilder().ToString(); // Assert @@ -107,7 +94,7 @@ public void SerializeBasicTagAsV2YamlWithoutReferenceWorks() var expected = "{ }"; // Act - BasicTag.SerializeAsV2WithoutReference(writer); + BasicTag.SerializeAsV2(writer); writer.Flush(); var actual = outputStringWriter.GetStringBuilder().ToString(); @@ -117,40 +104,6 @@ public void SerializeBasicTagAsV2YamlWithoutReferenceWorks() actual.Should().Be(expected); } - [Theory] - [InlineData(true)] - [InlineData(false)] - public async Task SerializeAdvancedTagAsV3JsonWithoutReferenceWorksAsync(bool produceTerseOutput) - { - // Arrange - var outputStringWriter = new StringWriter(CultureInfo.InvariantCulture); - var writer = new OpenApiJsonWriter(outputStringWriter, new() { Terse = produceTerseOutput }); - - // Act - AdvancedTag.SerializeAsV3WithoutReference(writer); - writer.Flush(); - - // Assert - await Verifier.Verify(outputStringWriter).UseParameters(produceTerseOutput); - } - - [Theory] - [InlineData(true)] - [InlineData(false)] - public async Task SerializeAdvancedTagAsV2JsonWithoutReferenceWorksAsync(bool produceTerseOutput) - { - // Arrange - var outputStringWriter = new StringWriter(CultureInfo.InvariantCulture); - var writer = new OpenApiJsonWriter(outputStringWriter, new() { Terse = produceTerseOutput }); - - // Act - AdvancedTag.SerializeAsV2WithoutReference(writer); - writer.Flush(); - - // Assert - await Verifier.Verify(outputStringWriter).UseParameters(produceTerseOutput); - } - [Fact] public void SerializeAdvancedTagAsV3YamlWithoutReferenceWorks() { @@ -168,7 +121,7 @@ public void SerializeAdvancedTagAsV3YamlWithoutReferenceWorks() """; // Act - AdvancedTag.SerializeAsV3WithoutReference(writer); + AdvancedTag.SerializeAsV3(writer); writer.Flush(); var actual = outputStringWriter.GetStringBuilder().ToString(); @@ -195,7 +148,7 @@ public void SerializeAdvancedTagAsV2YamlWithoutReferenceWorks() """; // Act - AdvancedTag.SerializeAsV2WithoutReference(writer); + AdvancedTag.SerializeAsV2(writer); writer.Flush(); var actual = outputStringWriter.GetStringBuilder().ToString(); @@ -246,7 +199,12 @@ public void SerializeAdvancedTagAsV3YamlWorks() var outputStringWriter = new StringWriter(CultureInfo.InvariantCulture); var writer = new OpenApiYamlWriter(outputStringWriter); - var expected = @" pet"; + var expected = @"name: pet +description: Pets operations +externalDocs: + description: Find more info here + url: https://example.com +x-tag-extension:"; // Act AdvancedTag.SerializeAsV3(writer); @@ -266,7 +224,12 @@ public void SerializeAdvancedTagAsV2YamlWorks() var outputStringWriter = new StringWriter(CultureInfo.InvariantCulture); var writer = new OpenApiYamlWriter(outputStringWriter); - var expected = @" pet"; + var expected = @"name: pet +description: Pets operations +externalDocs: + description: Find more info here + url: https://example.com +x-tag-extension:"; // Act AdvancedTag.SerializeAsV2(writer); diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiCallbackReferenceTests.cs b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiCallbackReferenceTests.cs index 3a16f4d2a..8942e692c 100644 --- a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiCallbackReferenceTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiCallbackReferenceTests.cs @@ -134,8 +134,8 @@ public class OpenApiCallbackReferenceTests public OpenApiCallbackReferenceTests() { OpenApiReaderRegistry.RegisterReader(OpenApiConstants.Yaml, new OpenApiYamlReader()); - OpenApiDocument openApiDoc = OpenApiDocument.Parse(OpenApi, OpenApiConstants.Yaml).OpenApiDocument; - OpenApiDocument openApiDoc_2 = OpenApiDocument.Parse(OpenApi_2, OpenApiConstants.Yaml).OpenApiDocument; + OpenApiDocument openApiDoc = OpenApiDocument.Parse(OpenApi, OpenApiConstants.Yaml).Document; + OpenApiDocument openApiDoc_2 = OpenApiDocument.Parse(OpenApi_2, OpenApiConstants.Yaml).Document; openApiDoc.Workspace.AddDocumentId("https://myserver.com/beta", openApiDoc_2.BaseUri); openApiDoc.Workspace.RegisterComponents(openApiDoc_2); _externalCallbackReference = new("callbackEvent", openApiDoc, "https://myserver.com/beta"); diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiExampleReferenceTests.cs b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiExampleReferenceTests.cs index 4ea8cdef9..a3342ade6 100644 --- a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiExampleReferenceTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiExampleReferenceTests.cs @@ -113,8 +113,8 @@ public class OpenApiExampleReferenceTests public OpenApiExampleReferenceTests() { OpenApiReaderRegistry.RegisterReader(OpenApiConstants.Yaml, new OpenApiYamlReader()); - _openApiDoc = OpenApiDocument.Parse(OpenApi, OpenApiConstants.Yaml).OpenApiDocument; - _openApiDoc_2 = OpenApiDocument.Parse(OpenApi_2, OpenApiConstants.Yaml).OpenApiDocument; + _openApiDoc = OpenApiDocument.Parse(OpenApi, OpenApiConstants.Yaml).Document; + _openApiDoc_2 = OpenApiDocument.Parse(OpenApi_2, OpenApiConstants.Yaml).Document; _openApiDoc.Workspace.AddDocumentId("https://myserver.com/beta", _openApiDoc_2.BaseUri); _openApiDoc.Workspace.RegisterComponents(_openApiDoc_2); diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiHeaderReferenceTests.cs b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiHeaderReferenceTests.cs index cfdf4ab1c..c979e1eb0 100644 --- a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiHeaderReferenceTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiHeaderReferenceTests.cs @@ -82,8 +82,8 @@ public class OpenApiHeaderReferenceTests public OpenApiHeaderReferenceTests() { OpenApiReaderRegistry.RegisterReader(OpenApiConstants.Yaml, new OpenApiYamlReader()); - _openApiDoc = OpenApiDocument.Parse(OpenApi, OpenApiConstants.Yaml).OpenApiDocument; - _openApiDoc_2 = OpenApiDocument.Parse(OpenApi_2, OpenApiConstants.Yaml).OpenApiDocument; + _openApiDoc = OpenApiDocument.Parse(OpenApi, OpenApiConstants.Yaml).Document; + _openApiDoc_2 = OpenApiDocument.Parse(OpenApi_2, OpenApiConstants.Yaml).Document; _openApiDoc.Workspace.AddDocumentId("https://myserver.com/beta", _openApiDoc_2.BaseUri); _openApiDoc.Workspace.RegisterComponents(_openApiDoc_2); diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiLinkReferenceTests.cs b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiLinkReferenceTests.cs index 87d2db06e..3587a83d9 100644 --- a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiLinkReferenceTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiLinkReferenceTests.cs @@ -125,8 +125,8 @@ public class OpenApiLinkReferenceTests public OpenApiLinkReferenceTests() { OpenApiReaderRegistry.RegisterReader(OpenApiConstants.Yaml, new OpenApiYamlReader()); - _openApiDoc = OpenApiDocument.Parse(OpenApi, OpenApiConstants.Yaml).OpenApiDocument; - _openApiDoc_2 = OpenApiDocument.Parse(OpenApi_2, OpenApiConstants.Yaml).OpenApiDocument; + _openApiDoc = OpenApiDocument.Parse(OpenApi, OpenApiConstants.Yaml).Document; + _openApiDoc_2 = OpenApiDocument.Parse(OpenApi_2, OpenApiConstants.Yaml).Document; _openApiDoc.Workspace.AddDocumentId("https://myserver.com/beta", _openApiDoc_2.BaseUri); _openApiDoc.Workspace.RegisterComponents(_openApiDoc_2); diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiParameterReferenceTests.cs b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiParameterReferenceTests.cs index c00db94f5..8745da455 100644 --- a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiParameterReferenceTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiParameterReferenceTests.cs @@ -83,8 +83,8 @@ public class OpenApiParameterReferenceTests public OpenApiParameterReferenceTests() { OpenApiReaderRegistry.RegisterReader(OpenApiConstants.Yaml, new OpenApiYamlReader()); - _openApiDoc = OpenApiDocument.Parse(OpenApi, OpenApiConstants.Yaml).OpenApiDocument; - _openApiDoc_2 = OpenApiDocument.Parse(OpenApi_2, OpenApiConstants.Yaml).OpenApiDocument; + _openApiDoc = OpenApiDocument.Parse(OpenApi, OpenApiConstants.Yaml).Document; + _openApiDoc_2 = OpenApiDocument.Parse(OpenApi_2, OpenApiConstants.Yaml).Document; _openApiDoc.Workspace.AddDocumentId("https://myserver.com/beta", _openApiDoc_2.BaseUri); _openApiDoc.Workspace.RegisterComponents(_openApiDoc_2); diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiPathItemReferenceTests.cs b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiPathItemReferenceTests.cs index a2d9b525d..c23d564d5 100644 --- a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiPathItemReferenceTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiPathItemReferenceTests.cs @@ -80,8 +80,8 @@ public class OpenApiPathItemReferenceTests public OpenApiPathItemReferenceTests() { OpenApiReaderRegistry.RegisterReader(OpenApiConstants.Yaml, new OpenApiYamlReader()); - _openApiDoc = OpenApiDocument.Parse(OpenApi, OpenApiConstants.Yaml).OpenApiDocument; - _openApiDoc_2 = OpenApiDocument.Parse(OpenApi_2, OpenApiConstants.Yaml).OpenApiDocument; + _openApiDoc = OpenApiDocument.Parse(OpenApi, OpenApiConstants.Yaml).Document; + _openApiDoc_2 = OpenApiDocument.Parse(OpenApi_2, OpenApiConstants.Yaml).Document; _openApiDoc.Workspace.AddDocumentId("https://myserver.com/beta", _openApiDoc_2.BaseUri); _openApiDoc.Workspace.RegisterComponents(_openApiDoc_2); _openApiDoc_2.Workspace.RegisterComponents(_openApiDoc_2); diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiRequestBodyReferenceTests.cs b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiRequestBodyReferenceTests.cs index 54521e83c..7bd9ab35b 100644 --- a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiRequestBodyReferenceTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiRequestBodyReferenceTests.cs @@ -88,8 +88,8 @@ public class OpenApiRequestBodyReferenceTests public OpenApiRequestBodyReferenceTests() { OpenApiReaderRegistry.RegisterReader(OpenApiConstants.Yaml, new OpenApiYamlReader()); - _openApiDoc = OpenApiDocument.Parse(OpenApi, OpenApiConstants.Yaml).OpenApiDocument; - _openApiDoc_2 = OpenApiDocument.Parse(OpenApi_2, OpenApiConstants.Yaml).OpenApiDocument; + _openApiDoc = OpenApiDocument.Parse(OpenApi, OpenApiConstants.Yaml).Document; + _openApiDoc_2 = OpenApiDocument.Parse(OpenApi_2, OpenApiConstants.Yaml).Document; _openApiDoc.Workspace.AddDocumentId("https://myserver.com/beta", _openApiDoc_2.BaseUri); _openApiDoc.Workspace.RegisterComponents(_openApiDoc_2); diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiResponseReferenceTest.cs b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiResponseReferenceTest.cs index 4b6b25564..361006b64 100644 --- a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiResponseReferenceTest.cs +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiResponseReferenceTest.cs @@ -71,8 +71,8 @@ public class OpenApiResponseReferenceTest public OpenApiResponseReferenceTest() { OpenApiReaderRegistry.RegisterReader(OpenApiConstants.Yaml, new OpenApiYamlReader()); - _openApiDoc = OpenApiDocument.Parse(OpenApi, OpenApiConstants.Yaml).OpenApiDocument; - _openApiDoc_2 = OpenApiDocument.Parse(OpenApi_2, OpenApiConstants.Yaml).OpenApiDocument; + _openApiDoc = OpenApiDocument.Parse(OpenApi, OpenApiConstants.Yaml).Document; + _openApiDoc_2 = OpenApiDocument.Parse(OpenApi_2, OpenApiConstants.Yaml).Document; _openApiDoc.Workspace.AddDocumentId("https://myserver.com/beta", _openApiDoc_2.BaseUri); _openApiDoc.Workspace.RegisterComponents(_openApiDoc_2); diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiSecuritySchemeReferenceTests.cs b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiSecuritySchemeReferenceTests.cs index 7fcd7dfd8..af9ab3c23 100644 --- a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiSecuritySchemeReferenceTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiSecuritySchemeReferenceTests.cs @@ -45,7 +45,7 @@ public OpenApiSecuritySchemeReferenceTests() { OpenApiReaderRegistry.RegisterReader(OpenApiConstants.Yaml, new OpenApiYamlReader()); var result = OpenApiDocument.Parse(OpenApi, "yaml"); - _openApiSecuritySchemeReference = new("mySecurityScheme", result.OpenApiDocument); + _openApiSecuritySchemeReference = new("mySecurityScheme", result.Document); } [Fact] diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiTagReferenceTest.cs b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiTagReferenceTest.cs index 82f1b27a2..8ec0e1373 100644 --- a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiTagReferenceTest.cs +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiTagReferenceTest.cs @@ -64,7 +64,7 @@ public OpenApiTagReferenceTest() { OpenApiReaderRegistry.RegisterReader(OpenApiConstants.Yaml, new OpenApiYamlReader()); var result = OpenApiDocument.Parse(OpenApi, "yaml"); - _openApiTagReference = new("user", result.OpenApiDocument) + _openApiTagReference = new("user", result.Document) { Description = "Users operations" }; diff --git a/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt b/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt index ef18b4cfb..83c6898c0 100644 --- a/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt +++ b/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt @@ -192,7 +192,8 @@ namespace Microsoft.OpenApi.Extensions { public static System.Type MapOpenApiPrimitiveTypeToSimpleType(this Microsoft.OpenApi.Models.OpenApiSchema schema) { } public static Microsoft.OpenApi.Models.OpenApiSchema MapTypeToOpenApiPrimitiveType(this System.Type type) { } - public static string ToIdentifier(this Microsoft.OpenApi.Models.JsonSchemaType? schemaType) { } + public static string? ToIdentifier(this Microsoft.OpenApi.Models.JsonSchemaType schemaType) { } + public static string? ToIdentifier(this Microsoft.OpenApi.Models.JsonSchemaType? schemaType) { } public static Microsoft.OpenApi.Models.JsonSchemaType ToJsonSchemaType(this string identifier) { } } public static class StringExtensions @@ -397,6 +398,7 @@ namespace Microsoft.OpenApi.Models public const string Comment = "$comment"; public const string Components = "components"; public const string ComponentsSegment = "/components/"; + public const string Const = "const"; public const string Consumes = "consumes"; public const string Contact = "contact"; public const string Content = "content"; @@ -510,6 +512,7 @@ namespace Microsoft.OpenApi.Models public const string Type = "type"; public const string UnevaluatedProperties = "unevaluatedProperties"; public const string UniqueItems = "uniqueItems"; + public const string UnrecognizedKeywords = "unrecognizedKeywords"; public const string Url = "url"; public const string V2ReferenceUri = "https://registry/definitions/"; public const string V31ExclusiveMaximum = "exclusiveMaximum"; @@ -626,7 +629,7 @@ namespace Microsoft.OpenApi.Models where T : Microsoft.OpenApi.Interfaces.IOpenApiSerializable { protected OpenApiExtensibleDictionary() { } - protected OpenApiExtensibleDictionary(System.Collections.Generic.Dictionary dictionary = null, System.Collections.Generic.IDictionary extensions = null) { } + protected OpenApiExtensibleDictionary(System.Collections.Generic.Dictionary dictionary, System.Collections.Generic.IDictionary extensions = null) { } public System.Collections.Generic.IDictionary Extensions { get; set; } public void SerializeAsV2(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } public void SerializeAsV3(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } @@ -881,6 +884,7 @@ namespace Microsoft.OpenApi.Models public virtual System.Collections.Generic.IList AllOf { get; set; } public virtual System.Collections.Generic.IList AnyOf { get; set; } public virtual string Comment { get; set; } + public virtual string Const { get; set; } public virtual System.Text.Json.Nodes.JsonNode Default { get; set; } public virtual System.Collections.Generic.IDictionary Definitions { get; set; } public virtual bool Deprecated { get; set; } @@ -922,6 +926,7 @@ namespace Microsoft.OpenApi.Models public virtual bool UnEvaluatedProperties { get; set; } public virtual bool UnevaluatedProperties { get; set; } public virtual bool? UniqueItems { get; set; } + public virtual System.Collections.Generic.IDictionary UnrecognizedKeywords { get; set; } public virtual bool UnresolvedReference { get; set; } public virtual decimal? V31ExclusiveMaximum { get; set; } public virtual decimal? V31ExclusiveMinimum { get; set; } @@ -994,11 +999,8 @@ namespace Microsoft.OpenApi.Models public virtual Microsoft.OpenApi.Models.OpenApiExternalDocs ExternalDocs { get; set; } public virtual string Name { get; set; } public virtual void SerializeAsV2(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } - public void SerializeAsV2WithoutReference(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } public virtual void SerializeAsV3(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } public virtual void SerializeAsV31(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } - public virtual void SerializeAsV31WithoutReference(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } - public virtual void SerializeAsV3WithoutReference(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } } public class OpenApiXml : Microsoft.OpenApi.Interfaces.IOpenApiElement, Microsoft.OpenApi.Interfaces.IOpenApiExtensible, Microsoft.OpenApi.Interfaces.IOpenApiSerializable { @@ -1221,6 +1223,7 @@ namespace Microsoft.OpenApi.Models.References public override System.Collections.Generic.IList AllOf { get; set; } public override System.Collections.Generic.IList AnyOf { get; set; } public override string Comment { get; set; } + public override string Const { get; set; } public override System.Text.Json.Nodes.JsonNode Default { get; set; } public override System.Collections.Generic.IDictionary Definitions { get; set; } public override bool Deprecated { get; set; } @@ -1384,8 +1387,8 @@ namespace Microsoft.OpenApi.Reader public class ReadResult { public ReadResult() { } - public Microsoft.OpenApi.Reader.OpenApiDiagnostic OpenApiDiagnostic { get; set; } - public Microsoft.OpenApi.Models.OpenApiDocument OpenApiDocument { get; set; } + public Microsoft.OpenApi.Reader.OpenApiDiagnostic Diagnostic { get; set; } + public Microsoft.OpenApi.Models.OpenApiDocument Document { get; set; } } public enum ReferenceResolutionSetting { @@ -1449,6 +1452,7 @@ namespace Microsoft.OpenApi.Services } public class OpenApiReferenceError : Microsoft.OpenApi.Models.OpenApiError { + public readonly Microsoft.OpenApi.Models.OpenApiReference Reference; public OpenApiReferenceError(Microsoft.OpenApi.Exceptions.OpenApiException exception) { } public OpenApiReferenceError(Microsoft.OpenApi.Models.OpenApiReference reference, string message) { } } @@ -1574,43 +1578,43 @@ namespace Microsoft.OpenApi.Validations public System.Collections.Generic.IEnumerable Warnings { get; } public void AddError(Microsoft.OpenApi.Validations.OpenApiValidatorError error) { } public void AddWarning(Microsoft.OpenApi.Validations.OpenApiValidatorWarning warning) { } - public override void Visit(Microsoft.OpenApi.Interfaces.IOpenApiExtensible item) { } - public override void Visit(Microsoft.OpenApi.Interfaces.IOpenApiExtension item) { } - public override void Visit(Microsoft.OpenApi.Models.OpenApiCallback item) { } - public override void Visit(Microsoft.OpenApi.Models.OpenApiComponents item) { } - public override void Visit(Microsoft.OpenApi.Models.OpenApiContact item) { } - public override void Visit(Microsoft.OpenApi.Models.OpenApiDocument item) { } - public override void Visit(Microsoft.OpenApi.Models.OpenApiEncoding item) { } - public override void Visit(Microsoft.OpenApi.Models.OpenApiExample item) { } - public override void Visit(Microsoft.OpenApi.Models.OpenApiExternalDocs item) { } - public override void Visit(Microsoft.OpenApi.Models.OpenApiHeader item) { } - public override void Visit(Microsoft.OpenApi.Models.OpenApiInfo item) { } - public override void Visit(Microsoft.OpenApi.Models.OpenApiLicense item) { } - public override void Visit(Microsoft.OpenApi.Models.OpenApiLink item) { } - public override void Visit(Microsoft.OpenApi.Models.OpenApiMediaType item) { } - public override void Visit(Microsoft.OpenApi.Models.OpenApiOAuthFlow item) { } - public override void Visit(Microsoft.OpenApi.Models.OpenApiOperation item) { } - public override void Visit(Microsoft.OpenApi.Models.OpenApiParameter item) { } - public override void Visit(Microsoft.OpenApi.Models.OpenApiPathItem item) { } - public override void Visit(Microsoft.OpenApi.Models.OpenApiPaths item) { } - public override void Visit(Microsoft.OpenApi.Models.OpenApiRequestBody item) { } - public override void Visit(Microsoft.OpenApi.Models.OpenApiResponse item) { } - public override void Visit(Microsoft.OpenApi.Models.OpenApiResponses item) { } - public override void Visit(Microsoft.OpenApi.Models.OpenApiSchema item) { } - public override void Visit(Microsoft.OpenApi.Models.OpenApiSecurityRequirement item) { } - public override void Visit(Microsoft.OpenApi.Models.OpenApiSecurityScheme item) { } - public override void Visit(Microsoft.OpenApi.Models.OpenApiServer item) { } - public override void Visit(Microsoft.OpenApi.Models.OpenApiServerVariable item) { } - public override void Visit(Microsoft.OpenApi.Models.OpenApiTag item) { } - public override void Visit(System.Collections.Generic.IDictionary item) { } - public override void Visit(System.Collections.Generic.IDictionary item) { } - public override void Visit(System.Collections.Generic.IDictionary item) { } - public override void Visit(System.Collections.Generic.IDictionary item) { } - public override void Visit(System.Collections.Generic.IDictionary item) { } - public override void Visit(System.Collections.Generic.IDictionary item) { } - public override void Visit(System.Collections.Generic.IDictionary item) { } - public override void Visit(System.Collections.Generic.IDictionary item) { } - public override void Visit(System.Collections.Generic.IList items) { } + public override void Visit(Microsoft.OpenApi.Interfaces.IOpenApiExtensible openApiExtensible) { } + public override void Visit(Microsoft.OpenApi.Interfaces.IOpenApiExtension openApiExtension) { } + public override void Visit(Microsoft.OpenApi.Models.OpenApiCallback callback) { } + public override void Visit(Microsoft.OpenApi.Models.OpenApiComponents components) { } + public override void Visit(Microsoft.OpenApi.Models.OpenApiContact contact) { } + public override void Visit(Microsoft.OpenApi.Models.OpenApiDocument doc) { } + public override void Visit(Microsoft.OpenApi.Models.OpenApiEncoding encoding) { } + public override void Visit(Microsoft.OpenApi.Models.OpenApiExample example) { } + public override void Visit(Microsoft.OpenApi.Models.OpenApiExternalDocs externalDocs) { } + public override void Visit(Microsoft.OpenApi.Models.OpenApiHeader header) { } + public override void Visit(Microsoft.OpenApi.Models.OpenApiInfo info) { } + public override void Visit(Microsoft.OpenApi.Models.OpenApiLicense license) { } + public override void Visit(Microsoft.OpenApi.Models.OpenApiLink link) { } + public override void Visit(Microsoft.OpenApi.Models.OpenApiMediaType mediaType) { } + public override void Visit(Microsoft.OpenApi.Models.OpenApiOAuthFlow openApiOAuthFlow) { } + public override void Visit(Microsoft.OpenApi.Models.OpenApiOperation operation) { } + public override void Visit(Microsoft.OpenApi.Models.OpenApiParameter parameter) { } + public override void Visit(Microsoft.OpenApi.Models.OpenApiPathItem pathItem) { } + public override void Visit(Microsoft.OpenApi.Models.OpenApiPaths paths) { } + public override void Visit(Microsoft.OpenApi.Models.OpenApiRequestBody requestBody) { } + public override void Visit(Microsoft.OpenApi.Models.OpenApiResponse response) { } + public override void Visit(Microsoft.OpenApi.Models.OpenApiResponses response) { } + public override void Visit(Microsoft.OpenApi.Models.OpenApiSchema schema) { } + public override void Visit(Microsoft.OpenApi.Models.OpenApiSecurityRequirement securityRequirement) { } + public override void Visit(Microsoft.OpenApi.Models.OpenApiSecurityScheme securityScheme) { } + public override void Visit(Microsoft.OpenApi.Models.OpenApiServer server) { } + public override void Visit(Microsoft.OpenApi.Models.OpenApiServerVariable serverVariable) { } + public override void Visit(Microsoft.OpenApi.Models.OpenApiTag tag) { } + public override void Visit(System.Collections.Generic.IDictionary operations) { } + public override void Visit(System.Collections.Generic.IDictionary callbacks) { } + public override void Visit(System.Collections.Generic.IDictionary encodings) { } + public override void Visit(System.Collections.Generic.IDictionary examples) { } + public override void Visit(System.Collections.Generic.IDictionary headers) { } + public override void Visit(System.Collections.Generic.IDictionary links) { } + public override void Visit(System.Collections.Generic.IDictionary content) { } + public override void Visit(System.Collections.Generic.IDictionary serverVariables) { } + public override void Visit(System.Collections.Generic.IList example) { } } public class OpenApiValidatorError : Microsoft.OpenApi.Models.OpenApiError { @@ -1667,7 +1671,6 @@ namespace Microsoft.OpenApi.Validations.Rules [Microsoft.OpenApi.Validations.Rules.OpenApiRule] public static class OpenApiComponentsRules { - public static System.Text.RegularExpressions.Regex KeyRegex; public static Microsoft.OpenApi.Validations.ValidationRule KeyMustBeRegularExpression { get; } } [Microsoft.OpenApi.Validations.Rules.OpenApiRule] @@ -1855,6 +1858,7 @@ namespace Microsoft.OpenApi.Writers public static void WriteOptionalCollection(this Microsoft.OpenApi.Writers.IOpenApiWriter writer, string name, System.Collections.Generic.IEnumerable elements, System.Action action) { } public static void WriteOptionalMap(this Microsoft.OpenApi.Writers.IOpenApiWriter writer, string name, System.Collections.Generic.IDictionary elements, System.Action action) { } public static void WriteOptionalMap(this Microsoft.OpenApi.Writers.IOpenApiWriter writer, string name, System.Collections.Generic.IDictionary elements, System.Action action) { } + public static void WriteOptionalMap(this Microsoft.OpenApi.Writers.IOpenApiWriter writer, string name, System.Collections.Generic.IDictionary elements, System.Action action) { } public static void WriteOptionalMap(this Microsoft.OpenApi.Writers.IOpenApiWriter writer, string name, System.Collections.Generic.IDictionary elements, System.Action action) where T : Microsoft.OpenApi.Interfaces.IOpenApiElement { } public static void WriteOptionalMap(this Microsoft.OpenApi.Writers.IOpenApiWriter writer, string name, System.Collections.Generic.IDictionary elements, System.Action action) diff --git a/test/Microsoft.OpenApi.Tests/Writers/OpenApiJsonWriterTests.cs b/test/Microsoft.OpenApi.Tests/Writers/OpenApiJsonWriterTests.cs index a967c43a0..30247333f 100644 --- a/test/Microsoft.OpenApi.Tests/Writers/OpenApiJsonWriterTests.cs +++ b/test/Microsoft.OpenApi.Tests/Writers/OpenApiJsonWriterTests.cs @@ -8,12 +8,14 @@ using System.IO; using System.Linq; using System.Text; +using System.Text.Encodings.Web; +using System.Text.Json; using System.Text.Json.Nodes; +using System.Text.Json.Serialization; using FluentAssertions; using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Writers; -using Newtonsoft.Json; using Xunit; namespace Microsoft.OpenApi.Tests.Writers @@ -62,9 +64,9 @@ public void WriteStringListAsJsonShouldMatchExpected(string[] stringValues, bool writer.WriteEndArray(); writer.Flush(); - var parsedObject = JsonConvert.DeserializeObject(outputString.GetStringBuilder().ToString()); + var parsedObject = JsonSerializer.Deserialize>(outputString.GetStringBuilder().ToString()); var expectedObject = - JsonConvert.DeserializeObject(JsonConvert.SerializeObject(new List(stringValues))); + JsonSerializer.Deserialize>(JsonSerializer.Serialize(new List(stringValues))); // Assert parsedObject.Should().BeEquivalentTo(expectedObject); @@ -222,17 +224,17 @@ private void WriteValueRecursive(OpenApiJsonWriter writer, object value) public void WriteMapAsJsonShouldMatchExpected(IDictionary inputMap, bool produceTerseOutput) { // Arrange - var outputString = new StringWriter(CultureInfo.InvariantCulture); + using var outputString = new StringWriter(CultureInfo.InvariantCulture); var writer = new OpenApiJsonWriter(outputString, new() { Terse = produceTerseOutput }); // Act WriteValueRecursive(writer, inputMap); - var parsedObject = JsonConvert.DeserializeObject(outputString.GetStringBuilder().ToString()); - var expectedObject = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(inputMap)); + using var parsedObject = JsonDocument.Parse(outputString.GetStringBuilder().ToString()); + using var expectedObject = JsonDocument.Parse(JsonSerializer.Serialize(inputMap, _jsonSerializerOptions.Value)); // Assert - parsedObject.Should().BeEquivalentTo(expectedObject); + Assert.True(JsonElement.DeepEquals(parsedObject.RootElement, expectedObject.RootElement)); } public static IEnumerable WriteDateTimeAsJsonTestCases() @@ -248,6 +250,57 @@ from shouldBeTerse in shouldProduceTerseOutputValues select new object[] { input, shouldBeTerse }; } + public class CustomDateTimeOffsetConverter : JsonConverter + { + public CustomDateTimeOffsetConverter(string format) + { + ArgumentException.ThrowIfNullOrEmpty(format); + Format = format; + } + + public string Format { get; } + + public override DateTimeOffset Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return DateTime.ParseExact(reader.GetString(), Format, CultureInfo.InvariantCulture); + } + + public override void Write(Utf8JsonWriter writer, DateTimeOffset value, JsonSerializerOptions options) + { + writer.WriteStringValue(value.ToString(Format)); + } + } + public class CustomDateTimeConverter : JsonConverter + { + public CustomDateTimeConverter(string format) + { + ArgumentException.ThrowIfNullOrEmpty(format); + Format = format; + } + + public string Format { get; } + + public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return DateTime.ParseExact(reader.GetString(), Format, CultureInfo.InvariantCulture); + } + + public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options) + { + writer.WriteStringValue(value.ToString(Format)); + } + } + private static readonly Lazy _jsonSerializerOptions = new(() => + { + var options = new JsonSerializerOptions + { + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping + }; + options.Converters.Add(new CustomDateTimeOffsetConverter("yyyy-MM-ddTHH:mm:ss.fffffffK")); + options.Converters.Add(new CustomDateTimeConverter("yyyy-MM-ddTHH:mm:ss.fffffffK")); + return options; + }); + [Theory] [MemberData(nameof(WriteDateTimeAsJsonTestCases))] public void WriteDateTimeAsJsonShouldMatchExpected(DateTimeOffset dateTimeOffset, bool produceTerseOutput) @@ -260,10 +313,7 @@ public void WriteDateTimeAsJsonShouldMatchExpected(DateTimeOffset dateTimeOffset writer.WriteValue(dateTimeOffset); var writtenString = outputString.GetStringBuilder().ToString(); - var expectedString = JsonConvert.SerializeObject(dateTimeOffset, new JsonSerializerSettings - { - DateFormatString = "yyyy-MM-ddTHH:mm:ss.fffffffK", - }); + var expectedString = JsonSerializer.Serialize(dateTimeOffset, _jsonSerializerOptions.Value); // Assert writtenString.Should().Be(expectedString); diff --git a/test/Microsoft.OpenApi.Trimming.Tests/Microsoft.OpenApi.Trimming.Tests.csproj b/test/Microsoft.OpenApi.Trimming.Tests/Microsoft.OpenApi.Trimming.Tests.csproj index 3e6daf74c..08f51d715 100644 --- a/test/Microsoft.OpenApi.Trimming.Tests/Microsoft.OpenApi.Trimming.Tests.csproj +++ b/test/Microsoft.OpenApi.Trimming.Tests/Microsoft.OpenApi.Trimming.Tests.csproj @@ -7,6 +7,7 @@ true false true + NU1903 false