diff --git a/src/Microsoft.OpenApi.Hidi/Extensions/OpenApiExtensibleExtensions.cs b/src/Microsoft.OpenApi.Hidi/Extensions/OpenApiExtensibleExtensions.cs index faf03c3f0..ee57125dd 100644 --- a/src/Microsoft.OpenApi.Hidi/Extensions/OpenApiExtensibleExtensions.cs +++ b/src/Microsoft.OpenApi.Hidi/Extensions/OpenApiExtensibleExtensions.cs @@ -14,9 +14,9 @@ internal static class OpenApiExtensibleExtensions /// A value matching the provided extensionKey. Return null when extensionKey is not found. internal static string GetExtension(this IDictionary extensions, string extensionKey) { - if (extensions.TryGetValue(extensionKey, out var value) && value is OpenApiString castValue) + if (extensions.TryGetValue(extensionKey, out var value) && value is OpenApiAny castValue) { - return castValue.Value; + return castValue.Node.GetValue(); } return string.Empty; } diff --git a/src/Microsoft.OpenApi.Hidi/Formatters/PowerShellFormatter.cs b/src/Microsoft.OpenApi.Hidi/Formatters/PowerShellFormatter.cs index 96d3cc17d..c2bbc97d0 100644 --- a/src/Microsoft.OpenApi.Hidi/Formatters/PowerShellFormatter.cs +++ b/src/Microsoft.OpenApi.Hidi/Formatters/PowerShellFormatter.cs @@ -52,7 +52,8 @@ public override void Visit(OpenApiSchema schema) public override void Visit(OpenApiPathItem pathItem) { - if (pathItem.Operations.TryGetValue(OperationType.Put, out var value)) + if (pathItem.Operations.TryGetValue(OperationType.Put, out var value) && + value.OperationId != null) { var operationId = value.OperationId; pathItem.Operations[OperationType.Put].OperationId = ResolvePutOperationId(operationId); @@ -67,14 +68,14 @@ public override void Visit(OpenApiOperation operation) throw new ArgumentException($"OperationId is required {PathString}", nameof(operation)); var operationId = operation.OperationId; - var operationTypeExtension = operation.Extensions.GetExtension("x-ms-docs-operation-type"); + var operationTypeExtension = operation.Extensions?.GetExtension("x-ms-docs-operation-type"); if (operationTypeExtension.IsEquals("function")) - operation.Parameters = ResolveFunctionParameters(operation.Parameters); + operation.Parameters = ResolveFunctionParameters(operation.Parameters ?? new List()); // Order matters. Resolve operationId. operationId = RemoveHashSuffix(operationId); if (operationTypeExtension.IsEquals("action") || operationTypeExtension.IsEquals("function")) - operationId = RemoveKeyTypeSegment(operationId, operation.Parameters); + operationId = RemoveKeyTypeSegment(operationId, operation.Parameters ?? new List()); operationId = SingularizeAndDeduplicateOperationId(operationId.SplitByChar('.')); operationId = ResolveODataCastOperationId(operationId); operationId = ResolveByRefOperationId(operationId); @@ -165,10 +166,10 @@ private static IList ResolveFunctionParameters(IList ResolveFunctionParameters(IList + Exe diff --git a/src/Microsoft.OpenApi.Hidi/OpenApiService.cs b/src/Microsoft.OpenApi.Hidi/OpenApiService.cs index d98508a13..c981639e9 100644 --- a/src/Microsoft.OpenApi.Hidi/OpenApiService.cs +++ b/src/Microsoft.OpenApi.Hidi/OpenApiService.cs @@ -29,6 +29,7 @@ using Microsoft.OpenApi.Hidi.Utilities; using Microsoft.OpenApi.Models; using Microsoft.OpenApi.OData; +using Microsoft.OpenApi.Reader; using Microsoft.OpenApi.Readers; using Microsoft.OpenApi.Services; using Microsoft.OpenApi.Writers; @@ -38,6 +39,12 @@ namespace Microsoft.OpenApi.Hidi { internal static class OpenApiService { + static OpenApiService() + { + OpenApiReaderRegistry.RegisterReader(OpenApiConstants.Yaml, new OpenApiYamlReader()); + OpenApiReaderRegistry.RegisterReader(OpenApiConstants.Yml, new OpenApiYamlReader()); + } + /// /// Implementation of the transform command /// @@ -52,7 +59,12 @@ public static async Task TransformOpenApiDocumentAsync(HidiOptions options, ILog { if (options.Output == null) { - var inputExtension = GetInputPathExtension(options.OpenApi, options.Csdl); +#pragma warning disable CA1308 // Normalize strings to uppercase + var extension = options.OpenApiFormat?.GetDisplayName().ToLowerInvariant(); + var inputExtension = !string.IsNullOrEmpty(extension) ? string.Concat(".", extension) + : GetInputPathExtension(options.OpenApi, options.Csdl); + +#pragma warning restore CA1308 // Normalize strings to uppercase options.Output = new($"./output{inputExtension}"); }; @@ -67,7 +79,7 @@ public static async Task TransformOpenApiDocumentAsync(HidiOptions options, ILog // Default to yaml and OpenApiVersion 3 during csdl to OpenApi conversion var openApiFormat = options.OpenApiFormat ?? (!string.IsNullOrEmpty(options.OpenApi) ? GetOpenApiFormat(options.OpenApi, logger) : OpenApiFormat.Yaml); - var openApiVersion = options.Version != null ? TryParseOpenApiSpecVersion(options.Version) : OpenApiSpecVersion.OpenApi3_0; + var openApiVersion = options.Version != null ? TryParseOpenApiSpecVersion(options.Version) : OpenApiSpecVersion.OpenApi3_1; // If ApiManifest is provided, set the referenced OpenAPI document var apiDependency = await FindApiDependencyAsync(options.FilterOptions.FilterByApiManifest, logger, cancellationToken).ConfigureAwait(false); @@ -85,7 +97,8 @@ public static async Task TransformOpenApiDocumentAsync(HidiOptions options, ILog } // Load OpenAPI document - var document = await GetOpenApiAsync(options, logger, options.MetadataVersion, cancellationToken).ConfigureAwait(false); + var format = OpenApiModelFactory.GetFormat(options.OpenApi); + var document = await GetOpenApiAsync(options, format, logger, options.MetadataVersion, cancellationToken).ConfigureAwait(false); if (options.FilterOptions != null) { @@ -212,7 +225,7 @@ private static void WriteOpenApi(HidiOptions options, OpenApiFormat openApiForma } // Get OpenAPI document either from OpenAPI or CSDL - private static async Task GetOpenApiAsync(HidiOptions options, ILogger logger, string? metadataVersion = null, CancellationToken cancellationToken = default) + private static async Task GetOpenApiAsync(HidiOptions options, string format, ILogger logger, string? metadataVersion = null, CancellationToken cancellationToken = default) { OpenApiDocument document; Stream stream; @@ -233,7 +246,7 @@ private static async Task GetOpenApiAsync(HidiOptions options, await stream.DisposeAsync().ConfigureAwait(false); } - document = await ConvertCsdlToOpenApiAsync(filteredStream ?? stream, metadataVersion, options.SettingsConfig, cancellationToken).ConfigureAwait(false); + document = await ConvertCsdlToOpenApiAsync(filteredStream ?? stream, format, metadataVersion, options.SettingsConfig, cancellationToken).ConfigureAwait(false); stopwatch.Stop(); logger.LogTrace("{Timestamp}ms: Generated OpenAPI with {Paths} paths.", stopwatch.ElapsedMilliseconds, document.Paths.Count); } @@ -375,14 +388,16 @@ private static async Task ParseOpenApiAsync(string openApiFile, bool { stopwatch.Start(); - result = await new OpenApiStreamReader(new() - { + var settings = new OpenApiReaderSettings + { LoadExternalRefs = inlineExternal, BaseUrl = openApiFile.StartsWith("http", StringComparison.OrdinalIgnoreCase) ? new(openApiFile) : new Uri("file://" + new FileInfo(openApiFile).DirectoryName + Path.DirectorySeparatorChar) - } - ).ReadAsync(stream, cancellationToken).ConfigureAwait(false); + }; + + var format = OpenApiModelFactory.GetFormat(openApiFile); + result = await OpenApiDocument.LoadAsync(stream, format, settings, cancellationToken).ConfigureAwait(false); logger.LogTrace("{Timestamp}ms: Completed parsing.", stopwatch.ElapsedMilliseconds); @@ -398,7 +413,7 @@ private static async Task ParseOpenApiAsync(string openApiFile, bool /// /// The CSDL stream. /// An OpenAPI document. - public static async Task ConvertCsdlToOpenApiAsync(Stream csdl, string? metadataVersion = null, IConfiguration? settings = null, CancellationToken token = default) + public static async Task ConvertCsdlToOpenApiAsync(Stream csdl, string format, string? metadataVersion = null, IConfiguration? settings = null, CancellationToken token = default) { using var reader = new StreamReader(csdl); var csdlText = await reader.ReadToEndAsync(token).ConfigureAwait(false); @@ -406,7 +421,7 @@ public static async Task ConvertCsdlToOpenApiAsync(Stream csdl, settings ??= SettingsUtilities.GetConfiguration(); var document = edmModel.ConvertToOpenApi(SettingsUtilities.GetOpenApiConvertSettings(settings, metadataVersion)); - document = FixReferences(document); + document = FixReferences(document, format); return document; } @@ -416,14 +431,15 @@ public static async Task ConvertCsdlToOpenApiAsync(Stream csdl, /// /// The converted OpenApiDocument. /// A valid OpenApiDocument instance. - public static OpenApiDocument FixReferences(OpenApiDocument document) + public static OpenApiDocument FixReferences(OpenApiDocument document, string format) { // This method is only needed because the output of ConvertToOpenApi isn't quite a valid OpenApiDocument instance. // So we write it out, and read it back in again to fix it up. var sb = new StringBuilder(); document.SerializeAsV3(new OpenApiYamlWriter(new StringWriter(sb))); - var doc = new OpenApiStringReader().Read(sb.ToString(), out _); + + var doc = OpenApiDocument.Parse(sb.ToString(), format).OpenApiDocument; return doc; } @@ -571,7 +587,8 @@ private static string GetInputPathExtension(string? openapi = null, string? csdl throw new ArgumentException("Please input a file path or URL"); } - var document = await GetOpenApiAsync(options, logger, null, cancellationToken).ConfigureAwait(false); + var format = OpenApiModelFactory.GetFormat(options.OpenApi); + var document = await GetOpenApiAsync(options, format, logger, null, cancellationToken).ConfigureAwait(false); using (logger.BeginScope("Creating diagram")) { @@ -732,7 +749,8 @@ internal static async Task PluginManifestAsync(HidiOptions options, ILogger logg } // Load OpenAPI document - var document = await GetOpenApiAsync(options, logger, options.MetadataVersion, cancellationToken).ConfigureAwait(false); + var format = OpenApiModelFactory.GetFormat(options.OpenApi); + var document = await GetOpenApiAsync(options, format, logger, options.MetadataVersion, cancellationToken).ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested(); @@ -750,7 +768,7 @@ internal static async Task PluginManifestAsync(HidiOptions options, ILogger logg // Write OpenAPI to Output folder options.Output = new(Path.Combine(options.OutputFolder, "openapi.json")); options.TerseOutput = true; - WriteOpenApi(options, OpenApiFormat.Json, OpenApiSpecVersion.OpenApi3_0, document, logger); + WriteOpenApi(options, OpenApiFormat.Json, OpenApiSpecVersion.OpenApi3_1, document, logger); // Create OpenAIPluginManifest from ApiDependency and OpenAPI document var manifest = new OpenAIPluginManifest diff --git a/src/Microsoft.OpenApi.Hidi/OpenApiSpecVersionHelper.cs b/src/Microsoft.OpenApi.Hidi/OpenApiSpecVersionHelper.cs index 234298481..222f7a8c6 100644 --- a/src/Microsoft.OpenApi.Hidi/OpenApiSpecVersionHelper.cs +++ b/src/Microsoft.OpenApi.Hidi/OpenApiSpecVersionHelper.cs @@ -2,7 +2,6 @@ // Licensed under the MIT license. using System; -using System.Linq; namespace Microsoft.OpenApi.Hidi { @@ -14,17 +13,30 @@ public static OpenApiSpecVersion TryParseOpenApiSpecVersion(string value) { throw new InvalidOperationException("Please provide a version"); } - var res = value.Split('.', StringSplitOptions.RemoveEmptyEntries).FirstOrDefault(); + // Split the version string by the dot + var versionSegments = value.Split('.', StringSplitOptions.RemoveEmptyEntries); - if (int.TryParse(res, out var result)) + if (!int.TryParse(versionSegments[0], out var majorVersion) + || !int.TryParse(versionSegments[1], out var minorVersion)) { - if (result is >= 2 and < 3) - { - return OpenApiSpecVersion.OpenApi2_0; - } + throw new InvalidOperationException("Invalid version format. Please provide a valid OpenAPI version (e.g., 2.0, 3.0, 3.1)."); } - return OpenApiSpecVersion.OpenApi3_0; // default + // Check for specific version matches + if (majorVersion == 2) + { + return OpenApiSpecVersion.OpenApi2_0; + } + else if (majorVersion == 3 && minorVersion == 0) + { + return OpenApiSpecVersion.OpenApi3_0; + } + else if (majorVersion == 3 && minorVersion == 1) + { + return OpenApiSpecVersion.OpenApi3_1; + } + + return OpenApiSpecVersion.OpenApi3_1; // default } } } diff --git a/src/Microsoft.OpenApi.Hidi/Program.cs b/src/Microsoft.OpenApi.Hidi/Program.cs index d5846dca9..ddbb96fe9 100644 --- a/src/Microsoft.OpenApi.Hidi/Program.cs +++ b/src/Microsoft.OpenApi.Hidi/Program.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.CommandLine; diff --git a/src/Microsoft.OpenApi.Readers/Interface/IOpenApiReader.cs b/src/Microsoft.OpenApi.Readers/Interface/IOpenApiReader.cs deleted file mode 100644 index 8991c9b59..000000000 --- a/src/Microsoft.OpenApi.Readers/Interface/IOpenApiReader.cs +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. - -using Microsoft.OpenApi.Models; - -namespace Microsoft.OpenApi.Readers.Interface -{ - /// - /// Interface for Open API readers. - /// - /// The type of input to read from. - /// The type of diagnostic for information from reading process. - public interface IOpenApiReader where TDiagnostic : IDiagnostic - { - /// - /// Reads the input and parses it into an Open API document. - /// - /// The input to read from. - /// The diagnostic entity containing information from the reading process. - /// The Open API document. - OpenApiDocument Read(TInput input, out TDiagnostic diagnostic); - } -} diff --git a/src/Microsoft.OpenApi.Readers/Microsoft.OpenApi.Readers.csproj b/src/Microsoft.OpenApi.Readers/Microsoft.OpenApi.Readers.csproj index ca868b9b6..68204d9c9 100644 --- a/src/Microsoft.OpenApi.Readers/Microsoft.OpenApi.Readers.csproj +++ b/src/Microsoft.OpenApi.Readers/Microsoft.OpenApi.Readers.csproj @@ -1,9 +1,9 @@ - + netstandard2.0 latest true - 1.6.22 + 2.0.0-preview1 OpenAPI.NET Readers for JSON and YAML documents true @@ -17,6 +17,12 @@ ..\Microsoft.OpenApi.snk + + + + + + @@ -25,6 +31,7 @@ + diff --git a/src/Microsoft.OpenApi.Readers/OpenApiStreamReader.cs b/src/Microsoft.OpenApi.Readers/OpenApiStreamReader.cs deleted file mode 100644 index c6c8add2f..000000000 --- a/src/Microsoft.OpenApi.Readers/OpenApiStreamReader.cs +++ /dev/null @@ -1,90 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. - -using System; -using System.IO; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.OpenApi.Interfaces; -using Microsoft.OpenApi.Models; -using Microsoft.OpenApi.Readers.Interface; - -namespace Microsoft.OpenApi.Readers -{ - /// - /// Service class for converting streams into OpenApiDocument instances - /// - public class OpenApiStreamReader : IOpenApiReader - { - private readonly OpenApiReaderSettings _settings; - - /// - /// Create stream reader with custom settings if desired. - /// - /// - public OpenApiStreamReader(OpenApiReaderSettings settings = null) - { - _settings = settings ?? new OpenApiReaderSettings(); - - if((_settings.ReferenceResolution == ReferenceResolutionSetting.ResolveAllReferences || _settings.LoadExternalRefs) - && _settings.BaseUrl == null) - { - throw new ArgumentException("BaseUrl must be provided to resolve external references."); - } - } - - /// - /// Reads the stream input and parses it into an Open API document. - /// - /// Stream containing OpenAPI description to parse. - /// Returns diagnostic object containing errors detected during parsing. - /// Instance of newly created OpenApiDocument. - public OpenApiDocument Read(Stream input, out OpenApiDiagnostic diagnostic) - { - using var reader = new StreamReader(input, Encoding.UTF8, true, 4096, _settings.LeaveStreamOpen); - return new OpenApiTextReaderReader(_settings).Read(reader, out diagnostic); - } - - /// - /// Reads the stream input and parses it into an Open API document. - /// - /// Stream containing OpenAPI description to parse. - /// Cancellation token. - /// Instance result containing newly created OpenApiDocument and diagnostics object from the process - public async Task ReadAsync(Stream input, CancellationToken cancellationToken = default) - { - MemoryStream bufferedStream; - int bufferSize = 4096; - if (input is MemoryStream stream) - { - bufferedStream = stream; - } - else - { - // Buffer stream so that OpenApiTextReaderReader can process it synchronously - // YamlDocument doesn't support async reading. - bufferedStream = new(); - bufferSize = 81920; - await input.CopyToAsync(bufferedStream, bufferSize, cancellationToken); - bufferedStream.Position = 0; - } - - using var reader = new StreamReader(bufferedStream, Encoding.UTF8, true, bufferSize, _settings.LeaveStreamOpen); - return await new OpenApiTextReaderReader(_settings).ReadAsync(reader, cancellationToken); - } - - /// - /// Reads the stream input and parses the fragment of an OpenAPI description into an Open API Element. - /// - /// Stream containing OpenAPI description to parse. - /// Version of the OpenAPI specification that the fragment conforms to. - /// Returns diagnostic object containing errors detected during parsing - /// Instance of newly created OpenApiDocument - public T ReadFragment(Stream input, OpenApiSpecVersion version, out OpenApiDiagnostic diagnostic) where T : IOpenApiReferenceable - { - using var reader = new StreamReader(input, Encoding.UTF8, true, 4096, _settings.LeaveStreamOpen); - return new OpenApiTextReaderReader(_settings).ReadFragment(reader, version, out diagnostic); - } - } -} diff --git a/src/Microsoft.OpenApi.Readers/OpenApiStringReader.cs b/src/Microsoft.OpenApi.Readers/OpenApiStringReader.cs deleted file mode 100644 index 1a694f255..000000000 --- a/src/Microsoft.OpenApi.Readers/OpenApiStringReader.cs +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. - -using System.IO; -using Microsoft.OpenApi.Interfaces; -using Microsoft.OpenApi.Models; -using Microsoft.OpenApi.Readers.Interface; - -namespace Microsoft.OpenApi.Readers -{ - /// - /// Service class for converting strings into OpenApiDocument instances - /// - public class OpenApiStringReader : IOpenApiReader - { - private readonly OpenApiReaderSettings _settings; - - /// - /// Constructor tha allows reader to use non-default settings - /// - /// - public OpenApiStringReader(OpenApiReaderSettings settings = null) - { - _settings = settings ?? new OpenApiReaderSettings(); - } - - /// - /// Reads the string input and parses it into an Open API document. - /// - public OpenApiDocument Read(string input, out OpenApiDiagnostic diagnostic) - { - using var reader = new StringReader(input); - return new OpenApiTextReaderReader(_settings).Read(reader, out diagnostic); - } - - /// - /// Reads the string input and parses it into an Open API element. - /// - public T ReadFragment(string input, OpenApiSpecVersion version, out OpenApiDiagnostic diagnostic) where T : IOpenApiElement - { - using var reader = new StringReader(input); - return new OpenApiTextReaderReader(_settings).ReadFragment(reader, version, out diagnostic); - } - } -} diff --git a/src/Microsoft.OpenApi.Readers/OpenApiTextReaderReader.cs b/src/Microsoft.OpenApi.Readers/OpenApiTextReaderReader.cs deleted file mode 100644 index dff20fc7f..000000000 --- a/src/Microsoft.OpenApi.Readers/OpenApiTextReaderReader.cs +++ /dev/null @@ -1,124 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. - -using System.IO; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.OpenApi.Interfaces; -using Microsoft.OpenApi.Models; -using Microsoft.OpenApi.Readers.Interface; -using SharpYaml; -using SharpYaml.Serialization; - -namespace Microsoft.OpenApi.Readers -{ - /// - /// Service class for converting contents of TextReader into OpenApiDocument instances - /// - public class OpenApiTextReaderReader : IOpenApiReader - { - private readonly OpenApiReaderSettings _settings; - - /// - /// Create stream reader with custom settings if desired. - /// - /// - public OpenApiTextReaderReader(OpenApiReaderSettings settings = null) - { - _settings = settings ?? new OpenApiReaderSettings(); - } - - /// - /// Reads the stream input and parses it into an Open API document. - /// - /// TextReader containing OpenAPI description to parse. - /// Returns diagnostic object containing errors detected during parsing - /// Instance of newly created OpenApiDocument - public OpenApiDocument Read(TextReader input, out OpenApiDiagnostic diagnostic) - { - YamlDocument yamlDocument; - - // Parse the YAML/JSON text in the TextReader into the YamlDocument - try - { - yamlDocument = LoadYamlDocument(input); - } - catch (YamlException ex) - { - diagnostic = new(); - diagnostic.Errors.Add(new($"#line={ex.Start.Line}", ex.Message)); - return new(); - } - - return new OpenApiYamlDocumentReader(this._settings).Read(yamlDocument, out diagnostic); - } - - /// - /// Reads the content of the TextReader. If there are references to external documents then they will be read asynchronously. - /// - /// TextReader containing OpenAPI description to parse. - /// Cancellation token. - /// A ReadResult instance that contains the resulting OpenApiDocument and a diagnostics instance. - public async Task ReadAsync(TextReader input, CancellationToken cancellationToken = default) - { - YamlDocument yamlDocument; - - // Parse the YAML/JSON text in the TextReader into the YamlDocument - try - { - yamlDocument = LoadYamlDocument(input); - } - catch (YamlException ex) - { - var diagnostic = new OpenApiDiagnostic(); - diagnostic.Errors.Add(new($"#line={ex.Start.Line}", ex.Message)); - return new() - { - OpenApiDocument = null, - OpenApiDiagnostic = diagnostic - }; - } - - return await new OpenApiYamlDocumentReader(this._settings).ReadAsync(yamlDocument, cancellationToken); - } - - /// - /// Reads the stream input and parses the fragment of an OpenAPI description into an Open API Element. - /// - /// TextReader containing OpenAPI description to parse. - /// Version of the OpenAPI specification that the fragment conforms to. - /// Returns diagnostic object containing errors detected during parsing - /// Instance of newly created OpenApiDocument - public T ReadFragment(TextReader input, OpenApiSpecVersion version, out OpenApiDiagnostic diagnostic) where T : IOpenApiElement - { - YamlDocument yamlDocument; - - // Parse the YAML/JSON - try - { - yamlDocument = LoadYamlDocument(input); - } - catch (YamlException ex) - { - diagnostic = new(); - diagnostic.Errors.Add(new($"#line={ex.Start.Line}", ex.Message)); - return default; - } - - return new OpenApiYamlDocumentReader(this._settings).ReadFragment(yamlDocument, version, out diagnostic); - } - - /// - /// Helper method to turn streams into YamlDocument - /// - /// Stream containing YAML formatted text - /// Instance of a YamlDocument - static YamlDocument LoadYamlDocument(TextReader input) - { - var yamlStream = new YamlStream(); - yamlStream.Load(input); - return yamlStream.Documents.First(); - } - } -} diff --git a/src/Microsoft.OpenApi.Readers/OpenApiYamlDocumentReader.cs b/src/Microsoft.OpenApi.Readers/OpenApiYamlDocumentReader.cs deleted file mode 100644 index af9ebcad1..000000000 --- a/src/Microsoft.OpenApi.Readers/OpenApiYamlDocumentReader.cs +++ /dev/null @@ -1,214 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.OpenApi.Exceptions; -using Microsoft.OpenApi.Extensions; -using Microsoft.OpenApi.Interfaces; -using Microsoft.OpenApi.Models; -using Microsoft.OpenApi.Readers.Interface; -using Microsoft.OpenApi.Readers.Services; -using Microsoft.OpenApi.Services; -using Microsoft.OpenApi.Validations; -using SharpYaml.Serialization; - -namespace Microsoft.OpenApi.Readers -{ - /// - /// Service class for converting contents of TextReader into OpenApiDocument instances - /// - internal class OpenApiYamlDocumentReader : IOpenApiReader - { - private readonly OpenApiReaderSettings _settings; - - /// - /// Create stream reader with custom settings if desired. - /// - /// - public OpenApiYamlDocumentReader(OpenApiReaderSettings settings = null) - { - _settings = settings ?? new OpenApiReaderSettings(); - } - - /// - /// Reads the stream input and parses it into an Open API document. - /// - /// TextReader containing OpenAPI description to parse. - /// Returns diagnostic object containing errors detected during parsing - /// Instance of newly created OpenApiDocument - public OpenApiDocument Read(YamlDocument input, out OpenApiDiagnostic diagnostic) - { - diagnostic = new(); - var context = new ParsingContext(diagnostic) - { - ExtensionParsers = _settings.ExtensionParsers, - BaseUrl = _settings.BaseUrl, - DefaultContentType = _settings.DefaultContentType - }; - - OpenApiDocument document = null; - try - { - // Parse the OpenAPI Document - document = context.Parse(input); - - if (_settings.LoadExternalRefs) - { - throw new InvalidOperationException("Cannot load external refs using the synchronous Read, use ReadAsync instead."); - } - - ResolveReferences(diagnostic, document); - } - catch (OpenApiException ex) - { - diagnostic.Errors.Add(new(ex)); - } - - // Validate the document - if (_settings.RuleSet != null && _settings.RuleSet.Rules.Count > 0) - { - var openApiErrors = document.Validate(_settings.RuleSet); - foreach (var item in openApiErrors.OfType()) - { - diagnostic.Errors.Add(item); - } - foreach (var item in openApiErrors.OfType()) - { - diagnostic.Warnings.Add(item); - } - } - - return document; - } - - public async Task ReadAsync(YamlDocument input, CancellationToken cancellationToken = default) - { - var diagnostic = new OpenApiDiagnostic(); - var context = new ParsingContext(diagnostic) - { - ExtensionParsers = _settings.ExtensionParsers, - BaseUrl = _settings.BaseUrl - }; - - OpenApiDocument document = null; - try - { - // Parse the OpenAPI Document - document = context.Parse(input); - - if (_settings.LoadExternalRefs) - { - var diagnosticExternalRefs = await LoadExternalRefsAsync(document, cancellationToken); - // Merge diagnostics of external reference - if (diagnosticExternalRefs != null) - { - diagnostic.Errors.AddRange(diagnosticExternalRefs.Errors); - diagnostic.Warnings.AddRange(diagnosticExternalRefs.Warnings); - } - } - - ResolveReferences(diagnostic, document); - } - catch (OpenApiException ex) - { - diagnostic.Errors.Add(new(ex)); - } - - // Validate the document - if (_settings.RuleSet != null && _settings.RuleSet.Rules.Count > 0) - { - var openApiErrors = document.Validate(_settings.RuleSet); - foreach (var item in openApiErrors.OfType()) - { - diagnostic.Errors.Add(item); - } - foreach (var item in openApiErrors.OfType()) - { - diagnostic.Warnings.Add(item); - } - } - - return new() - { - OpenApiDocument = document, - OpenApiDiagnostic = diagnostic - }; - } - - private Task LoadExternalRefsAsync(OpenApiDocument document, CancellationToken cancellationToken = default) - { - // Create workspace for all documents to live in. - var openApiWorkSpace = new OpenApiWorkspace(); - - // Load this root document into the workspace - var streamLoader = new DefaultStreamLoader(_settings.BaseUrl); - var workspaceLoader = new OpenApiWorkspaceLoader(openApiWorkSpace, _settings.CustomExternalLoader ?? streamLoader, _settings); - return workspaceLoader.LoadAsync(new() { ExternalResource = "/" }, document, null, cancellationToken); - } - - private void ResolveReferences(OpenApiDiagnostic diagnostic, OpenApiDocument document) - { - var errors = new List(); - - // Resolve References if requested - switch (_settings.ReferenceResolution) - { - case ReferenceResolutionSetting.ResolveAllReferences: - throw new ArgumentException("Resolving external references is not supported"); - case ReferenceResolutionSetting.ResolveLocalReferences: - errors.AddRange(document.ResolveReferences()); - break; - case ReferenceResolutionSetting.DoNotResolveReferences: - break; - } - - foreach (var item in errors) - { - diagnostic.Errors.Add(item); - } - } - - /// - /// Reads the stream input and parses the fragment of an OpenAPI description into an Open API Element. - /// - /// TextReader containing OpenAPI description to parse. - /// Version of the OpenAPI specification that the fragment conforms to. - /// Returns diagnostic object containing errors detected during parsing - /// Instance of newly created OpenApiDocument - public T ReadFragment(YamlDocument input, OpenApiSpecVersion version, out OpenApiDiagnostic diagnostic) where T : IOpenApiElement - { - diagnostic = new(); - var context = new ParsingContext(diagnostic) - { - ExtensionParsers = _settings.ExtensionParsers - }; - - IOpenApiElement element = null; - try - { - // Parse the OpenAPI element - element = context.ParseFragment(input, version); - } - catch (OpenApiException ex) - { - diagnostic.Errors.Add(new(ex)); - } - - // Validate the element - if (_settings.RuleSet != null && _settings.RuleSet.Rules.Count > 0) - { - var errors = element.Validate(_settings.RuleSet); - foreach (var item in errors) - { - diagnostic.Errors.Add(item); - } - } - - return (T)element; - } - } -} diff --git a/src/Microsoft.OpenApi.Readers/OpenApiYamlReader.cs b/src/Microsoft.OpenApi.Readers/OpenApiYamlReader.cs new file mode 100644 index 000000000..cff6dd1da --- /dev/null +++ b/src/Microsoft.OpenApi.Readers/OpenApiYamlReader.cs @@ -0,0 +1,96 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System.IO; +using System.Text.Json.Nodes; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.OpenApi.Interfaces; +using Microsoft.OpenApi.Reader; +using SharpYaml.Serialization; +using System.Linq; +using Microsoft.OpenApi.Models; + +namespace Microsoft.OpenApi.Readers +{ + /// + /// Reader for parsing YAML files into an OpenAPI document. + /// + public class OpenApiYamlReader : IOpenApiReader + { + /// + public async Task ReadAsync(TextReader input, + OpenApiReaderSettings settings = null, + CancellationToken cancellationToken = default) + { + JsonNode jsonNode; + + // Parse the YAML text in the TextReader into a sequence of JsonNodes + try + { + jsonNode = LoadJsonNodesFromYamlDocument(input); + } + catch (JsonException ex) + { + var diagnostic = new OpenApiDiagnostic(); + diagnostic.Errors.Add(new($"#line={ex.LineNumber}", ex.Message)); + return new() + { + OpenApiDocument = null, + OpenApiDiagnostic = diagnostic + }; + } + + return await ReadAsync(jsonNode, settings, cancellationToken: cancellationToken); + } + + /// + public T ReadFragment(TextReader input, + OpenApiSpecVersion version, + out OpenApiDiagnostic diagnostic, + OpenApiReaderSettings settings = null) where T : IOpenApiElement + { + JsonNode jsonNode; + + // Parse the YAML + try + { + jsonNode = LoadJsonNodesFromYamlDocument(input); + } + catch (JsonException ex) + { + diagnostic = new(); + diagnostic.Errors.Add(new($"#line={ex.LineNumber}", ex.Message)); + return default; + } + + return ReadFragment(jsonNode, version, out diagnostic); + } + + /// + /// Helper method to turn streams into a sequence of JsonNodes + /// + /// Stream containing YAML formatted text + /// Instance of a YamlDocument + static JsonNode LoadJsonNodesFromYamlDocument(TextReader input) + { + var yamlStream = new YamlStream(); + yamlStream.Load(input); + var yamlDocument = yamlStream.Documents.First(); + return yamlDocument.ToJsonNode(); + } + + /// + public async Task ReadAsync(JsonNode jsonNode, OpenApiReaderSettings settings, string format = null, CancellationToken cancellationToken = default) + { + return await OpenApiReaderRegistry.DefaultReader.ReadAsync(jsonNode, settings, OpenApiConstants.Yaml, cancellationToken); + } + + /// + public T ReadFragment(JsonNode input, OpenApiSpecVersion version, out OpenApiDiagnostic diagnostic, OpenApiReaderSettings settings = null) where T : IOpenApiElement + { + return OpenApiReaderRegistry.DefaultReader.ReadFragment(input, version, out diagnostic); + } + } +} diff --git a/src/Microsoft.OpenApi.Readers/ParseNodes/JsonPointerExtensions.cs b/src/Microsoft.OpenApi.Readers/ParseNodes/JsonPointerExtensions.cs deleted file mode 100644 index 412d6901b..000000000 --- a/src/Microsoft.OpenApi.Readers/ParseNodes/JsonPointerExtensions.cs +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. - -using System; -using SharpYaml.Serialization; - -namespace Microsoft.OpenApi.Readers.ParseNodes -{ - /// - /// Extensions for JSON pointers. - /// - public static class JsonPointerExtensions - { - /// - /// Finds the YAML node that corresponds to this JSON pointer based on the base YAML node. - /// - public static YamlNode Find(this JsonPointer currentPointer, YamlNode baseYamlNode) - { - if (currentPointer.Tokens.Length == 0) - { - return baseYamlNode; - } - - try - { - var pointer = baseYamlNode; - foreach (var token in currentPointer.Tokens) - { - if (pointer is YamlSequenceNode sequence) - { - pointer = sequence.Children[Convert.ToInt32(token)]; - } - else - { - if (pointer is YamlMappingNode map) - { - if (!map.Children.TryGetValue(new YamlScalarNode(token), out pointer)) - { - return null; - } - } - } - } - - return pointer; - } - catch (Exception) - { - return null; - } - } - } -} diff --git a/src/Microsoft.OpenApi.Readers/ParseNodes/MapNode.cs b/src/Microsoft.OpenApi.Readers/ParseNodes/MapNode.cs deleted file mode 100644 index 61f609817..000000000 --- a/src/Microsoft.OpenApi.Readers/ParseNodes/MapNode.cs +++ /dev/null @@ -1,227 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. - -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using Microsoft.OpenApi.Any; -using Microsoft.OpenApi.Interfaces; -using Microsoft.OpenApi.Models; -using Microsoft.OpenApi.Readers.Exceptions; -using SharpYaml.Schemas; -using SharpYaml.Serialization; - -namespace Microsoft.OpenApi.Readers.ParseNodes -{ - /// - /// Abstraction of a Map to isolate semantic parsing from details of - /// - internal class MapNode : ParseNode, IEnumerable - { - private readonly YamlMappingNode _node; - private readonly List _nodes; - - public MapNode(ParsingContext context, string yamlString) : - this(context, (YamlMappingNode)YamlHelper.ParseYamlString(yamlString)) - { - } - - public MapNode(ParsingContext context, YamlNode node) : base( - context) - { - if (node is not YamlMappingNode mapNode) - { - throw new OpenApiReaderException("Expected map.", Context); - } - - this._node = mapNode; - - _nodes = this._node.Children - .Select(kvp => new PropertyNode(Context, kvp.Key.GetScalarValue(), kvp.Value)) - .Cast() - .ToList(); - } - - public PropertyNode this[string key] - { - get - { - if (this._node.Children.TryGetValue(new YamlScalarNode(key), out var node)) - { - return new(Context, key, node); - } - - return null; - } - } - - public override Dictionary CreateMap(Func map) - { - var yamlMap = _node; - if (yamlMap == null) - { - throw new OpenApiReaderException($"Expected map while parsing {typeof(T).Name}", Context); - } - - var nodes = yamlMap.Select( - n => - { - var key = n.Key.GetScalarValue(); - T value; - try - { - Context.StartObject(key); - value = n.Value as YamlMappingNode == null - ? default - : map(new(Context, n.Value as YamlMappingNode)); - } - finally - { - Context.EndObject(); - } - return new - { - key = key, - value = value - }; - }); - - return nodes.ToDictionary(k => k.key, v => v.value); - } - - public override Dictionary CreateMapWithReference( - ReferenceType referenceType, - Func map) - { - var yamlMap = _node; - if (yamlMap == null) - { - throw new OpenApiReaderException($"Expected map while parsing {typeof(T).Name}", Context); - } - - var nodes = yamlMap.Select( - n => - { - var key = n.Key.GetScalarValue(); - (string key, T value) entry; - try - { - Context.StartObject(key); - entry = ( - key: key, - value: map(new(Context, (YamlMappingNode)n.Value)) - ); - if (entry.value == null) - { - return default; // Body Parameters shouldn't be converted to Parameters - } - // If the component isn't a reference to another component, then point it to itself. - if (entry.value.Reference == null) - { - entry.value.Reference = new() - { - Type = referenceType, - Id = entry.key - }; - } - } - finally - { - Context.EndObject(); - } - return entry; - } - ); - return nodes.Where(n => n != default).ToDictionary(k => k.key, v => v.value); - } - - public override Dictionary CreateSimpleMap(Func map) - { - var yamlMap = _node; - if (yamlMap == null) - { - throw new OpenApiReaderException($"Expected map while parsing {typeof(T).Name}", Context); - } - - var nodes = yamlMap.Select( - n => - { - var key = n.Key.GetScalarValue(); - try - { - Context.StartObject(key); - if (n.Value is not YamlScalarNode scalarNode) - { - throw new OpenApiReaderException($"Expected scalar while parsing {typeof(T).Name}", Context); - } - return (key, value: map(new(Context, (YamlScalarNode)n.Value))); - } finally { - Context.EndObject(); - } - }); - return nodes.ToDictionary(k => k.key, v => v.value); - } - - public IEnumerator GetEnumerator() - { - return _nodes.GetEnumerator(); - } - - IEnumerator IEnumerable.GetEnumerator() - { - return _nodes.GetEnumerator(); - } - - public override string GetRaw() - { - var x = new Serializer(new(new JsonSchema()) { EmitJsonComptible = true }); - return x.Serialize(_node); - } - - public T GetReferencedObject(ReferenceType referenceType, string referenceId) - where T : IOpenApiReferenceable, new() - { - return new() - { - UnresolvedReference = true, - Reference = Context.VersionService.ConvertToOpenApiReference(referenceId, referenceType) - }; - } - - public string GetReferencePointer() - { - if (!_node.Children.TryGetValue(new YamlScalarNode("$ref"), out var refNode)) - { - return null; - } - - return refNode.GetScalarValue(); - } - - public string GetScalarValue(ValueNode key) - { - if (_node.Children[new YamlScalarNode(key.GetScalarValue())] is not YamlScalarNode scalarNode) - { - throw new OpenApiReaderException($"Expected scalar at line {_node.Start.Line} for key {key.GetScalarValue()}", Context); - } - - return scalarNode.Value; - } - - /// - /// Create a - /// - /// The created Any object. - public override IOpenApiAny CreateAny() - { - var apiObject = new OpenApiObject(); - foreach (var node in this) - { - apiObject.Add(node.Name, node.Value.CreateAny()); - } - - return apiObject; - } - } -} diff --git a/src/Microsoft.OpenApi.Readers/ParseNodes/OpenApiAnyConverter.cs b/src/Microsoft.OpenApi.Readers/ParseNodes/OpenApiAnyConverter.cs deleted file mode 100644 index 2c0a45379..000000000 --- a/src/Microsoft.OpenApi.Readers/ParseNodes/OpenApiAnyConverter.cs +++ /dev/null @@ -1,270 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. - -using System; -using System.Globalization; -using System.Linq; -using System.Text; -using Microsoft.OpenApi.Any; -using Microsoft.OpenApi.Models; - -namespace Microsoft.OpenApi.Readers.ParseNodes -{ - internal static class OpenApiAnyConverter - { - /// - /// Converts the s in the given - /// into the appropriate type based on the given . - /// For those strings that the schema does not specify the type for, convert them into - /// the most specific type based on the value. - /// - public static IOpenApiAny GetSpecificOpenApiAny(IOpenApiAny openApiAny, OpenApiSchema schema = null) - { - if (openApiAny is OpenApiArray openApiArray) - { - var newArray = new OpenApiArray(); - foreach (var element in openApiArray) - { - newArray.Add(GetSpecificOpenApiAny(element, schema?.Items)); - } - - return newArray; - } - - if (openApiAny is OpenApiObject openApiObject) - { - var newObject = new OpenApiObject(); - - foreach (var key in openApiObject.Keys.ToList()) - { - if (schema?.Properties != null && schema.Properties.TryGetValue(key, out var property)) - { - newObject[key] = GetSpecificOpenApiAny(openApiObject[key], property); - } - else - { - newObject[key] = GetSpecificOpenApiAny(openApiObject[key], schema?.AdditionalProperties); - } - } - - return newObject; - } - - if (openApiAny is not OpenApiString apiString) - { - return openApiAny; - } - - var value = apiString.Value; - var type = schema?.Type; - var format = schema?.Format; - - if (apiString.IsExplicit()) - { - // More narrow type detection for explicit strings, only check types that are passed as strings - if (schema == null) - { - if (DateTimeOffset.TryParse(value, CultureInfo.InvariantCulture, DateTimeStyles.None, out var dateTimeValue)) - { - // if the time component is exactly midnight(00:00:00) meaning no time has elapsed, return a date-only value - return dateTimeValue.TimeOfDay == TimeSpan.Zero ? new OpenApiDate(dateTimeValue.Date) - : new OpenApiDateTime(dateTimeValue); - } - } - else if (type == "string") - { - if (format == "byte") - { - try - { - return new OpenApiByte(Convert.FromBase64String(value)); - } - catch (FormatException) - { } - } - - if (format == "binary") - { - try - { - return new OpenApiBinary(Encoding.UTF8.GetBytes(value)); - } - catch (EncoderFallbackException) - { } - } - - if (format == "date") - { - if (DateTimeOffset.TryParse(value, CultureInfo.InvariantCulture, DateTimeStyles.None, out var dateValue)) - { - return new OpenApiDate(dateValue.Date); - } - } - - if (format == "date-time") - { - if (DateTimeOffset.TryParse(value, CultureInfo.InvariantCulture, DateTimeStyles.None, out var dateTimeValue)) - { - return new OpenApiDateTime(dateTimeValue); - } - } - - if (format == "password") - { - return new OpenApiPassword(value); - } - } - - return apiString; - } - - if (value is null or "null") - { - return new OpenApiNull(); - } - - if (schema?.Type == null) - { - if (value == "true") - { - return new OpenApiBoolean(true); - } - - if (value == "false") - { - return new OpenApiBoolean(false); - } - - if (int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var intValue)) - { - return new OpenApiInteger(intValue); - } - - if (long.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var longValue)) - { - return new OpenApiLong(longValue); - } - - if (double.TryParse(value, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture, out var doubleValue)) - { - return new OpenApiDouble(doubleValue); - } - - if (DateTimeOffset.TryParse(value, CultureInfo.InvariantCulture, DateTimeStyles.None, out var dateTimeValue)) - { - return new OpenApiDateTime(dateTimeValue); - } - } - else - { - if (type is "integer" or "number" && format == "int32") - { - if (int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var intValue)) - { - return new OpenApiInteger(intValue); - } - } - - if (type is "integer" or "number" && format == "int64") - { - if (long.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var longValue)) - { - return new OpenApiLong(longValue); - } - } - - if (type == "integer") - { - if (int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var intValue)) - { - return new OpenApiInteger(intValue); - } - } - - if (type == "number" && format == "float") - { - if (float.TryParse(value, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture, out var floatValue)) - { - return new OpenApiFloat(floatValue); - } - } - - if (type == "number" && format == "double") - { - if (double.TryParse(value, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture, out var doubleValue)) - { - return new OpenApiDouble(doubleValue); - } - } - - if (type == "number") - { - if (double.TryParse(value, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture, out var doubleValue)) - { - return new OpenApiDouble(doubleValue); - } - } - - if (type == "string" && format == "byte") - { - try - { - return new OpenApiByte(Convert.FromBase64String(value)); - } - catch (FormatException) - { } - } - - // binary - if (type == "string" && format == "binary") - { - try - { - return new OpenApiBinary(Encoding.UTF8.GetBytes(value)); - } - catch (EncoderFallbackException) - { } - } - - if (type == "string" && format == "date") - { - if (DateTimeOffset.TryParse(value, CultureInfo.InvariantCulture, DateTimeStyles.None, out var dateValue)) - { - return new OpenApiDate(dateValue.Date); - } - } - - if (type == "string" && format == "date-time") - { - if (DateTimeOffset.TryParse(value, CultureInfo.InvariantCulture, DateTimeStyles.None, out var dateTimeValue)) - { - return new OpenApiDateTime(dateTimeValue); - } - } - - if (type == "string" && format == "password") - { - return new OpenApiPassword(value); - } - - if (type == "string") - { - return apiString; - } - - if (type == "boolean") - { - if (bool.TryParse(value, out var booleanValue)) - { - return new OpenApiBoolean(booleanValue); - } - } - } - - // If data conflicts with the given type, return a string. - // This converter is used in the parser, so it does not perform any validations, - // but the validator can be used to validate whether the data and given type conflicts. - return apiString; - } - } -} diff --git a/src/Microsoft.OpenApi.Readers/ParseNodes/RootNode.cs b/src/Microsoft.OpenApi.Readers/ParseNodes/RootNode.cs deleted file mode 100644 index f70b9ca99..000000000 --- a/src/Microsoft.OpenApi.Readers/ParseNodes/RootNode.cs +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. - -using SharpYaml.Serialization; - -namespace Microsoft.OpenApi.Readers.ParseNodes -{ - /// - /// Wrapper class around YamlDocument to isolate semantic parsing from details of Yaml DOM. - /// - internal class RootNode : ParseNode - { - private readonly YamlDocument _yamlDocument; - - public RootNode( - ParsingContext context, - YamlDocument yamlDocument) : base(context) - { - _yamlDocument = yamlDocument; - } - - public ParseNode Find(JsonPointer referencePointer) - { - var yamlNode = referencePointer.Find(_yamlDocument.RootNode); - if (yamlNode == null) - { - return null; - } - - return Create(Context, yamlNode); - } - - public MapNode GetMap() - { - return new(Context, (YamlMappingNode)_yamlDocument.RootNode); - } - } -} diff --git a/src/Microsoft.OpenApi.Readers/ParseNodes/ValueNode.cs b/src/Microsoft.OpenApi.Readers/ParseNodes/ValueNode.cs deleted file mode 100644 index 1aeccb8e7..000000000 --- a/src/Microsoft.OpenApi.Readers/ParseNodes/ValueNode.cs +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. - -using Microsoft.OpenApi.Any; -using Microsoft.OpenApi.Readers.Exceptions; -using SharpYaml; -using SharpYaml.Serialization; - -namespace Microsoft.OpenApi.Readers.ParseNodes -{ - internal class ValueNode : ParseNode - { - private readonly YamlScalarNode _node; - - public ValueNode(ParsingContext context, YamlNode node) : base( - context) - { - if (node is not YamlScalarNode scalarNode) - { - throw new OpenApiReaderException("Expected a value.", node); - } - _node = scalarNode; - } - - public override string GetScalarValue() - { - return _node.Value; - } - - /// - /// Create a - /// - /// The created Any object. - public override IOpenApiAny CreateAny() - { - var value = GetScalarValue(); - return new OpenApiString(value, this._node.Style is ScalarStyle.SingleQuoted or ScalarStyle.DoubleQuoted or ScalarStyle.Literal or ScalarStyle.Folded); - } - } -} diff --git a/src/Microsoft.OpenApi.Readers/Properties/SRResource.Designer.cs b/src/Microsoft.OpenApi.Readers/Properties/SRResource.Designer.cs index e8bbc567e..a35dab766 100644 --- a/src/Microsoft.OpenApi.Readers/Properties/SRResource.Designer.cs +++ b/src/Microsoft.OpenApi.Readers/Properties/SRResource.Designer.cs @@ -8,7 +8,7 @@ // //------------------------------------------------------------------------------ -namespace Microsoft.OpenApi.Readers.Properties { +namespace Microsoft.OpenApi.Reader.Properties { using System; @@ -19,7 +19,7 @@ namespace Microsoft.OpenApi.Readers.Properties { // class via a tool like ResGen or Visual Studio. // To add or remove a member, edit your .ResX file then rerun ResGen // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] internal class SRResource { diff --git a/src/Microsoft.OpenApi.Readers/V2/OpenApiHeaderDeserializer.cs b/src/Microsoft.OpenApi.Readers/V2/OpenApiHeaderDeserializer.cs deleted file mode 100644 index ff873f4a0..000000000 --- a/src/Microsoft.OpenApi.Readers/V2/OpenApiHeaderDeserializer.cs +++ /dev/null @@ -1,174 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. - -using System; -using System.Globalization; -using Microsoft.OpenApi.Extensions; -using Microsoft.OpenApi.Models; -using Microsoft.OpenApi.Readers.Exceptions; -using Microsoft.OpenApi.Readers.ParseNodes; - -namespace Microsoft.OpenApi.Readers.V2 -{ - /// - /// Class containing logic to deserialize Open API V2 document into - /// runtime Open API object model. - /// - internal static partial class OpenApiV2Deserializer - { - private static readonly FixedFieldMap _headerFixedFields = new() - { - { - "description", - (o, n) => o.Description = n.GetScalarValue() - }, - { - "type", - (o, n) => GetOrCreateSchema(o).Type = n.GetScalarValue() - }, - { - "format", - (o, n) => GetOrCreateSchema(o).Format = n.GetScalarValue() - }, - { - "items", - (o, n) => GetOrCreateSchema(o).Items = LoadSchema(n) - }, - { - "collectionFormat", - (o, n) => LoadStyle(o, n.GetScalarValue()) - }, - { - "default", - (o, n) => GetOrCreateSchema(o).Default = n.CreateAny() - }, - { - "maximum", - (o, n) => GetOrCreateSchema(o).Maximum = ParserHelper.ParseDecimalWithFallbackOnOverflow(n.GetScalarValue(), decimal.MaxValue) - }, - { - "exclusiveMaximum", - (o, n) => GetOrCreateSchema(o).ExclusiveMaximum = bool.Parse(n.GetScalarValue()) - }, - { - "minimum", - (o, n) => GetOrCreateSchema(o).Minimum = ParserHelper.ParseDecimalWithFallbackOnOverflow(n.GetScalarValue(), decimal.MinValue) - }, - { - "exclusiveMinimum", - (o, n) => GetOrCreateSchema(o).ExclusiveMinimum = bool.Parse(n.GetScalarValue()) - }, - { - "maxLength", - (o, n) => GetOrCreateSchema(o).MaxLength = int.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture) - }, - { - "minLength", - (o, n) => GetOrCreateSchema(o).MinLength = int.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture) - }, - { - "pattern", - (o, n) => GetOrCreateSchema(o).Pattern = n.GetScalarValue() - }, - { - "maxItems", - (o, n) => GetOrCreateSchema(o).MaxItems = int.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture) - }, - { - "minItems", - (o, n) => GetOrCreateSchema(o).MinItems = int.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture) - }, - { - "uniqueItems", - (o, n) => GetOrCreateSchema(o).UniqueItems = bool.Parse(n.GetScalarValue()) - }, - { - "multipleOf", - (o, n) => GetOrCreateSchema(o).MultipleOf = decimal.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture) - }, - { - "enum", - (o, n) => GetOrCreateSchema(o).Enum = n.CreateListOfAny() - } - }; - - private static readonly PatternFieldMap _headerPatternFields = new() - { - {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, LoadExtension(p, n))} - }; - - private static readonly AnyFieldMap _headerAnyFields = - new() - { - { - OpenApiConstants.Default, - new( - p => p.Schema?.Default, - (p, v) => - { - if(p.Schema == null) return; - p.Schema.Default = v; - }, - p => p.Schema) - } - }; - - private static readonly AnyListFieldMap _headerAnyListFields = - new() - { - { - OpenApiConstants.Enum, - new( - p => p.Schema?.Enum, - (p, v) => - { - if(p.Schema == null) return; - p.Schema.Enum = v; - }, - p => p.Schema) - }, - }; - - public static OpenApiHeader LoadHeader(ParseNode node) - { - var mapNode = node.CheckMapNode("header"); - var header = new OpenApiHeader(); - foreach (var property in mapNode) - { - property.ParseField(header, _headerFixedFields, _headerPatternFields); - } - - var schema = node.Context.GetFromTempStorage("schema"); - if (schema != null) - { - header.Schema = schema; - node.Context.SetTempStorage("schema", null); - } - - ProcessAnyFields(mapNode, header, _headerAnyFields); - ProcessAnyListFields(mapNode, header, _headerAnyListFields); - - return header; - } - - private static void LoadStyle(OpenApiHeader header, string style) - { - switch (style) - { - case "csv": - header.Style = ParameterStyle.Simple; - return; - case "ssv": - header.Style = ParameterStyle.SpaceDelimited; - return; - case "pipes": - header.Style = ParameterStyle.PipeDelimited; - return; - case "tsv": - throw new NotSupportedException(); - default: - throw new OpenApiReaderException("Unrecognized header style: " + style); - } - } - } -} diff --git a/src/Microsoft.OpenApi.Readers/V2/OpenApiSchemaDeserializer.cs b/src/Microsoft.OpenApi.Readers/V2/OpenApiSchemaDeserializer.cs deleted file mode 100644 index dd884d574..000000000 --- a/src/Microsoft.OpenApi.Readers/V2/OpenApiSchemaDeserializer.cs +++ /dev/null @@ -1,209 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. - -using System.Collections.Generic; -using System.Globalization; -using Microsoft.OpenApi.Extensions; -using Microsoft.OpenApi.Models; -using Microsoft.OpenApi.Readers.ParseNodes; - -namespace Microsoft.OpenApi.Readers.V2 -{ - /// - /// Class containing logic to deserialize Open API V2 document into - /// runtime Open API object model. - /// - internal static partial class OpenApiV2Deserializer - { - private static readonly FixedFieldMap _schemaFixedFields = new() - { - { - "title", - (o, n) => o.Title = n.GetScalarValue() - }, - { - "multipleOf", - (o, n) => o.MultipleOf = decimal.Parse(n.GetScalarValue(), NumberStyles.Float, CultureInfo.InvariantCulture) - }, - { - "maximum", - (o, n) => o.Maximum = ParserHelper.ParseDecimalWithFallbackOnOverflow(n.GetScalarValue(), decimal.MaxValue) - }, - { - "exclusiveMaximum", - (o, n) => o.ExclusiveMaximum = bool.Parse(n.GetScalarValue()) - }, - { - "minimum", - (o, n) => o.Minimum = ParserHelper.ParseDecimalWithFallbackOnOverflow(n.GetScalarValue(), decimal.MinValue) - }, - { - "exclusiveMinimum", - (o, n) => o.ExclusiveMinimum = bool.Parse(n.GetScalarValue()) - }, - { - "maxLength", - (o, n) => o.MaxLength = int.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture) - }, - { - "minLength", - (o, n) => o.MinLength = int.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture) - }, - { - "pattern", - (o, n) => o.Pattern = n.GetScalarValue() - }, - { - "maxItems", - (o, n) => o.MaxItems = int.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture) - }, - { - "minItems", - (o, n) => o.MinItems = int.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture) - }, - { - "uniqueItems", - (o, n) => o.UniqueItems = bool.Parse(n.GetScalarValue()) - }, - { - "maxProperties", - (o, n) => o.MaxProperties = int.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture) - }, - { - "minProperties", - (o, n) => o.MinProperties = int.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture) - }, - { - "required", - (o, n) => o.Required = new HashSet(n.CreateSimpleList(n2 => n2.GetScalarValue())) - }, - { - "enum", - (o, n) => o.Enum = n.CreateListOfAny() - }, - - { - "type", - (o, n) => o.Type = n.GetScalarValue() - }, - { - "allOf", - (o, n) => o.AllOf = n.CreateList(LoadSchema) - }, - { - "items", - (o, n) => o.Items = LoadSchema(n) - }, - { - "properties", - (o, n) => o.Properties = n.CreateMap(LoadSchema) - }, - { - "additionalProperties", (o, n) => - { - if (n is ValueNode) - { - o.AdditionalPropertiesAllowed = bool.Parse(n.GetScalarValue()); - } - else - { - o.AdditionalProperties = LoadSchema(n); - } - } - }, - { - "description", - (o, n) => o.Description = n.GetScalarValue() - }, - { - "format", - (o, n) => o.Format = n.GetScalarValue() - }, - { - "default", - (o, n) => o.Default = n.CreateAny() - }, - { - "discriminator", (o, n) => - { - o.Discriminator = new() - { - PropertyName = n.GetScalarValue() - }; - } - }, - { - "readOnly", - (o, n) => o.ReadOnly = bool.Parse(n.GetScalarValue()) - }, - { - "xml", - (o, n) => o.Xml = LoadXml(n) - }, - { - "externalDocs", - (o, n) => o.ExternalDocs = LoadExternalDocs(n) - }, - { - "example", - (o, n) => o.Example = n.CreateAny() - }, - }; - - private static readonly PatternFieldMap _schemaPatternFields = new() - { - {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, LoadExtension(p, n))} - }; - - private static readonly AnyFieldMap _schemaAnyFields = new() - { - { - OpenApiConstants.Default, - new( - s => s.Default, - (s, v) => s.Default = v, - s => s) - }, - { - OpenApiConstants.Example, - new( - s => s.Example, - (s, v) => s.Example = v, - s => s) } - }; - - private static readonly AnyListFieldMap _schemaAnyListFields = new() - { - { - OpenApiConstants.Enum, - new( - s => s.Enum, - (s, v) => s.Enum = v, - s => s) - } - }; - - public static OpenApiSchema LoadSchema(ParseNode node) - { - var mapNode = node.CheckMapNode("schema"); - - var pointer = mapNode.GetReferencePointer(); - if (pointer != null) - { - return mapNode.GetReferencedObject(ReferenceType.Schema, pointer); - } - - var schema = new OpenApiSchema(); - - foreach (var propertyNode in mapNode) - { - propertyNode.ParseField(schema, _schemaFixedFields, _schemaPatternFields); - } - - ProcessAnyFields(mapNode, schema, _schemaAnyFields); - ProcessAnyListFields(mapNode, schema, _schemaAnyListFields); - - return schema; - } - } -} diff --git a/src/Microsoft.OpenApi.Readers/V2/OpenApiV2Deserializer.cs b/src/Microsoft.OpenApi.Readers/V2/OpenApiV2Deserializer.cs deleted file mode 100644 index 790940759..000000000 --- a/src/Microsoft.OpenApi.Readers/V2/OpenApiV2Deserializer.cs +++ /dev/null @@ -1,133 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. - -using System.Collections.Generic; -using System.Linq; -using Microsoft.OpenApi.Any; -using Microsoft.OpenApi.Exceptions; -using Microsoft.OpenApi.Interfaces; -using Microsoft.OpenApi.Readers.ParseNodes; - -namespace Microsoft.OpenApi.Readers.V2 -{ - /// - /// Class containing logic to deserialize Open API V2 document into - /// runtime Open API object model. - /// - internal static partial class OpenApiV2Deserializer - { - private static void ParseMap( - MapNode mapNode, - T domainObject, - FixedFieldMap fixedFieldMap, - PatternFieldMap patternFieldMap, - List requiredFields = null) - { - if (mapNode == null) - { - return; - } - - var allFields = fixedFieldMap.Keys.Union(mapNode.Select(static x => x.Name)); - foreach (var propertyNode in allFields) - { - mapNode[propertyNode]?.ParseField(domainObject, fixedFieldMap, patternFieldMap); - requiredFields?.Remove(propertyNode); - } - } - - private static void ProcessAnyFields( - MapNode mapNode, - T domainObject, - AnyFieldMap anyFieldMap) - { - foreach (var anyFieldName in anyFieldMap.Keys.ToList()) - { - try - { - mapNode.Context.StartObject(anyFieldName); - - var convertedOpenApiAny = OpenApiAnyConverter.GetSpecificOpenApiAny( - anyFieldMap[anyFieldName].PropertyGetter(domainObject), - anyFieldMap[anyFieldName].SchemaGetter(domainObject)); - - anyFieldMap[anyFieldName].PropertySetter(domainObject, convertedOpenApiAny); - } - catch (OpenApiException exception) - { - exception.Pointer = mapNode.Context.GetLocation(); - mapNode.Context.Diagnostic.Errors.Add(new(exception)); - } - finally - { - mapNode.Context.EndObject(); - } - } - } - - private static void ProcessAnyListFields( - MapNode mapNode, - T domainObject, - AnyListFieldMap anyListFieldMap) - { - foreach (var anyListFieldName in anyListFieldMap.Keys.ToList()) - { - try - { - var newProperty = new List(); - - mapNode.Context.StartObject(anyListFieldName); - if (anyListFieldMap.TryGetValue(anyListFieldName, out var fieldName)) - { - var list = fieldName.PropertyGetter(domainObject); - if (list != null) - { - foreach (var propertyElement in list) - { - newProperty.Add( - OpenApiAnyConverter.GetSpecificOpenApiAny( - propertyElement, - anyListFieldMap[anyListFieldName].SchemaGetter(domainObject))); - } - } - } - - anyListFieldMap[anyListFieldName].PropertySetter(domainObject, newProperty); - } - catch (OpenApiException exception) - { - exception.Pointer = mapNode.Context.GetLocation(); - mapNode.Context.Diagnostic.Errors.Add(new(exception)); - } - finally - { - mapNode.Context.EndObject(); - } - } - } - - public static IOpenApiAny LoadAny(ParseNode node) - { - return OpenApiAnyConverter.GetSpecificOpenApiAny(node.CreateAny()); - } - - private static IOpenApiExtension LoadExtension(string name, ParseNode node) - { - if (node.Context.ExtensionParsers.TryGetValue(name, out var parser) && parser( - OpenApiAnyConverter.GetSpecificOpenApiAny(node.CreateAny()), - OpenApiSpecVersion.OpenApi2_0) is { } result) - { - return result; - } - else - { - return OpenApiAnyConverter.GetSpecificOpenApiAny(node.CreateAny()); - } - } - - private static string LoadString(ParseNode node) - { - return node.GetScalarValue(); - } - } -} diff --git a/src/Microsoft.OpenApi.Readers/V3/OpenApiComponentsDeserializer.cs b/src/Microsoft.OpenApi.Readers/V3/OpenApiComponentsDeserializer.cs deleted file mode 100644 index 62ed95fda..000000000 --- a/src/Microsoft.OpenApi.Readers/V3/OpenApiComponentsDeserializer.cs +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. - -using Microsoft.OpenApi.Extensions; -using Microsoft.OpenApi.Models; -using Microsoft.OpenApi.Readers.ParseNodes; - -namespace Microsoft.OpenApi.Readers.V3 -{ - /// - /// Class containing logic to deserialize Open API V3 document into - /// runtime Open API object model. - /// - internal static partial class OpenApiV3Deserializer - { - private static FixedFieldMap _componentsFixedFields = new() - { - { - "schemas", (o, n) => o.Schemas = n.CreateMapWithReference(ReferenceType.Schema, LoadSchema) - }, - {"responses", (o, n) => o.Responses = n.CreateMapWithReference(ReferenceType.Response, LoadResponse)}, - {"parameters", (o, n) => o.Parameters = n.CreateMapWithReference(ReferenceType.Parameter, LoadParameter)}, - {"examples", (o, n) => o.Examples = n.CreateMapWithReference(ReferenceType.Example, LoadExample)}, - {"requestBodies", (o, n) => o.RequestBodies = n.CreateMapWithReference(ReferenceType.RequestBody, LoadRequestBody)}, - {"headers", (o, n) => o.Headers = n.CreateMapWithReference(ReferenceType.Header, LoadHeader)}, - {"securitySchemes", (o, n) => o.SecuritySchemes = n.CreateMapWithReference(ReferenceType.SecurityScheme, LoadSecurityScheme)}, - {"links", (o, n) => o.Links = n.CreateMapWithReference(ReferenceType.Link, LoadLink)}, - {"callbacks", (o, n) => o.Callbacks = n.CreateMapWithReference(ReferenceType.Callback, LoadCallback)}, - }; - - private static PatternFieldMap _componentsPatternFields = - new() - { - {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, LoadExtension(p, n))} - }; - - public static OpenApiComponents LoadComponents(ParseNode node) - { - var mapNode = node.CheckMapNode("components"); - var components = new OpenApiComponents(); - - ParseMap(mapNode, components, _componentsFixedFields, _componentsPatternFields); - - return components; - } - } -} diff --git a/src/Microsoft.OpenApi.Readers/V3/OpenApiDocumentDeserializer.cs b/src/Microsoft.OpenApi.Readers/V3/OpenApiDocumentDeserializer.cs deleted file mode 100644 index edeca23ad..000000000 --- a/src/Microsoft.OpenApi.Readers/V3/OpenApiDocumentDeserializer.cs +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. - -using Microsoft.OpenApi.Extensions; -using Microsoft.OpenApi.Models; -using Microsoft.OpenApi.Readers.ParseNodes; - -namespace Microsoft.OpenApi.Readers.V3 -{ - /// - /// Class containing logic to deserialize Open API V3 document into - /// runtime Open API object model. - /// - internal static partial class OpenApiV3Deserializer - { - private static FixedFieldMap _openApiFixedFields = new() - { - { - "openapi", (_, _) => - { - } /* Version is valid field but we already parsed it */ - }, - {"info", (o, n) => o.Info = LoadInfo(n)}, - {"servers", (o, n) => o.Servers = n.CreateList(LoadServer)}, - {"paths", (o, n) => o.Paths = LoadPaths(n)}, - {"components", (o, n) => o.Components = LoadComponents(n)}, - {"tags", (o, n) => {o.Tags = n.CreateList(LoadTag); - foreach (var tag in o.Tags) - { - tag.Reference = new() - { - Id = tag.Name, - Type = ReferenceType.Tag - }; - } - } }, - {"externalDocs", (o, n) => o.ExternalDocs = LoadExternalDocs(n)}, - {"security", (o, n) => o.SecurityRequirements = n.CreateList(LoadSecurityRequirement)} - }; - - private static PatternFieldMap _openApiPatternFields = new() - { - // We have no semantics to verify X- nodes, therefore treat them as just values. - {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, LoadExtension(p, n))} - }; - - public static OpenApiDocument LoadOpenApi(RootNode rootNode) - { - var openApidoc = new OpenApiDocument(); - - var openApiNode = rootNode.GetMap(); - - ParseMap(openApiNode, openApidoc, _openApiFixedFields, _openApiPatternFields); - - return openApidoc; - } - } -} diff --git a/src/Microsoft.OpenApi.Readers/V3/OpenApiPathItemDeserializer.cs b/src/Microsoft.OpenApi.Readers/V3/OpenApiPathItemDeserializer.cs deleted file mode 100644 index f4ab2ad3b..000000000 --- a/src/Microsoft.OpenApi.Readers/V3/OpenApiPathItemDeserializer.cs +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. - -using Microsoft.OpenApi.Extensions; -using Microsoft.OpenApi.Models; -using Microsoft.OpenApi.Readers.ParseNodes; - -namespace Microsoft.OpenApi.Readers.V3 -{ - /// - /// Class containing logic to deserialize Open API V3 document into - /// runtime Open API object model. - /// - internal static partial class OpenApiV3Deserializer - { - private static readonly FixedFieldMap _pathItemFixedFields = new() - { - { - "$ref", (o,n) => { - o.Reference = new() { ExternalResource = n.GetScalarValue() }; - o.UnresolvedReference =true; - } - }, - { - "summary", - (o, n) => o.Summary = n.GetScalarValue() - }, - { - "description", - (o, n) => o.Description = n.GetScalarValue() - }, - {"get", (o, n) => o.AddOperation(OperationType.Get, LoadOperation(n))}, - {"put", (o, n) => o.AddOperation(OperationType.Put, LoadOperation(n))}, - {"post", (o, n) => o.AddOperation(OperationType.Post, LoadOperation(n))}, - {"delete", (o, n) => o.AddOperation(OperationType.Delete, LoadOperation(n))}, - {"options", (o, n) => o.AddOperation(OperationType.Options, LoadOperation(n))}, - {"head", (o, n) => o.AddOperation(OperationType.Head, LoadOperation(n))}, - {"patch", (o, n) => o.AddOperation(OperationType.Patch, LoadOperation(n))}, - {"trace", (o, n) => o.AddOperation(OperationType.Trace, LoadOperation(n))}, - {"servers", (o, n) => o.Servers = n.CreateList(LoadServer)}, - {"parameters", (o, n) => o.Parameters = n.CreateList(LoadParameter)} - }; - - private static readonly PatternFieldMap _pathItemPatternFields = - new() - { - {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, LoadExtension(p,n))} - }; - - public static OpenApiPathItem LoadPathItem(ParseNode node) - { - var mapNode = node.CheckMapNode("PathItem"); - - var pointer = mapNode.GetReferencePointer(); - if (pointer != null) - { - var refObject = mapNode.GetReferencedObject(ReferenceType.Path, pointer); - return refObject; - } - - var pathItem = new OpenApiPathItem(); - - ParseMap(mapNode, pathItem, _pathItemFixedFields, _pathItemPatternFields); - - return pathItem; - } - } -} diff --git a/src/Microsoft.OpenApi.Readers/V3/OpenApiSchemaDeserializer.cs b/src/Microsoft.OpenApi.Readers/V3/OpenApiSchemaDeserializer.cs deleted file mode 100644 index 1e386a33d..000000000 --- a/src/Microsoft.OpenApi.Readers/V3/OpenApiSchemaDeserializer.cs +++ /dev/null @@ -1,233 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. - -using Microsoft.OpenApi.Extensions; -using Microsoft.OpenApi.Models; -using Microsoft.OpenApi.Readers.ParseNodes; -using System.Collections.Generic; -using System.Globalization; - -namespace Microsoft.OpenApi.Readers.V3 -{ - /// - /// Class containing logic to deserialize Open API V3 document into - /// runtime Open API object model. - /// - internal static partial class OpenApiV3Deserializer - { - private static readonly FixedFieldMap _schemaFixedFields = new() - { - { - "title", - (o, n) => o.Title = n.GetScalarValue() - }, - { - "multipleOf", - (o, n) => o.MultipleOf = decimal.Parse(n.GetScalarValue(), NumberStyles.Float, CultureInfo.InvariantCulture) - }, - { - "maximum", - (o, n) => o.Maximum = ParserHelper.ParseDecimalWithFallbackOnOverflow(n.GetScalarValue(), decimal.MaxValue) - }, - { - "exclusiveMaximum", - (o, n) => o.ExclusiveMaximum = bool.Parse(n.GetScalarValue()) - }, - { - "minimum", - (o, n) => o.Minimum = ParserHelper.ParseDecimalWithFallbackOnOverflow(n.GetScalarValue(), decimal.MinValue) - }, - { - "exclusiveMinimum", - (o, n) => o.ExclusiveMinimum = bool.Parse(n.GetScalarValue()) - }, - { - "maxLength", - (o, n) => o.MaxLength = int.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture) - }, - { - "minLength", - (o, n) => o.MinLength = int.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture) - }, - { - "pattern", - (o, n) => o.Pattern = n.GetScalarValue() - }, - { - "maxItems", - (o, n) => o.MaxItems = int.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture) - }, - { - "minItems", - (o, n) => o.MinItems = int.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture) - }, - { - "uniqueItems", - (o, n) => o.UniqueItems = bool.Parse(n.GetScalarValue()) - }, - { - "maxProperties", - (o, n) => o.MaxProperties = int.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture) - }, - { - "minProperties", - (o, n) => o.MinProperties = int.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture) - }, - { - "required", - (o, n) => o.Required = new HashSet(n.CreateSimpleList(n2 => n2.GetScalarValue())) - }, - { - "enum", - (o, n) => o.Enum = n.CreateListOfAny() - }, - { - "type", - (o, n) => o.Type = n.GetScalarValue() - }, - { - "allOf", - (o, n) => o.AllOf = n.CreateList(LoadSchema) - }, - { - "oneOf", - (o, n) => o.OneOf = n.CreateList(LoadSchema) - }, - { - "anyOf", - (o, n) => o.AnyOf = n.CreateList(LoadSchema) - }, - { - "not", - (o, n) => o.Not = LoadSchema(n) - }, - { - "items", - (o, n) => o.Items = LoadSchema(n) - }, - { - "properties", - (o, n) => o.Properties = n.CreateMap(LoadSchema) - }, - { - "additionalProperties", (o, n) => - { - if (n is ValueNode) - { - o.AdditionalPropertiesAllowed = bool.Parse(n.GetScalarValue()); - } - else - { - o.AdditionalProperties = LoadSchema(n); - } - } - }, - { - "description", - (o, n) => o.Description = n.GetScalarValue() - }, - { - "format", - (o, n) => o.Format = n.GetScalarValue() - }, - { - "default", - (o, n) => o.Default = n.CreateAny() - }, - { - "nullable", - (o, n) => o.Nullable = bool.Parse(n.GetScalarValue()) - }, - { - "discriminator", - (o, n) => o.Discriminator = LoadDiscriminator(n) - }, - { - "readOnly", - (o, n) => o.ReadOnly = bool.Parse(n.GetScalarValue()) - }, - { - "writeOnly", - (o, n) => o.WriteOnly = bool.Parse(n.GetScalarValue()) - }, - { - "xml", - (o, n) => o.Xml = LoadXml(n) - }, - { - "externalDocs", - (o, n) => o.ExternalDocs = LoadExternalDocs(n) - }, - { - "example", - (o, n) => o.Example = n.CreateAny() - }, - { - "deprecated", - (o, n) => o.Deprecated = bool.Parse(n.GetScalarValue()) - }, - }; - - private static readonly PatternFieldMap _schemaPatternFields = new() - { - {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, LoadExtension(p,n))} - }; - - private static readonly AnyFieldMap _schemaAnyFields = new() - { - { - OpenApiConstants.Default, - new( - s => s.Default, - (s, v) => s.Default = v, - s => s) - }, - { - OpenApiConstants.Example, - new( - s => s.Example, - (s, v) => s.Example = v, - s => s) - } - }; - - private static readonly AnyListFieldMap _schemaAnyListFields = new() - { - { - OpenApiConstants.Enum, - new( - s => s.Enum, - (s, v) => s.Enum = v, - s => s) - } - }; - - public static OpenApiSchema LoadSchema(ParseNode node) - { - var mapNode = node.CheckMapNode(OpenApiConstants.Schema); - - var pointer = mapNode.GetReferencePointer(); - - if (pointer != null) - { - return new() - { - UnresolvedReference = true, - Reference = node.Context.VersionService.ConvertToOpenApiReference(pointer, ReferenceType.Schema) - }; - } - - var schema = new OpenApiSchema(); - - foreach (var propertyNode in mapNode) - { - propertyNode.ParseField(schema, _schemaFixedFields, _schemaPatternFields); - } - - ProcessAnyFields(mapNode, schema, _schemaAnyFields); - ProcessAnyListFields(mapNode, schema, _schemaAnyListFields); - - return schema; - } - } -} diff --git a/src/Microsoft.OpenApi.Readers/YamlConverter.cs b/src/Microsoft.OpenApi.Readers/YamlConverter.cs new file mode 100644 index 000000000..7d338ffa1 --- /dev/null +++ b/src/Microsoft.OpenApi.Readers/YamlConverter.cs @@ -0,0 +1,139 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text.Json.Nodes; +using SharpYaml; +using SharpYaml.Serialization; + +namespace Microsoft.OpenApi.Reader +{ + /// + /// Provides extensions to convert YAML models to JSON models. + /// + public static class YamlConverter + { + /// + /// Converts all of the documents in a YAML stream to s. + /// + /// The YAML stream. + /// A collection of nodes representing the YAML documents in the stream. + public static IEnumerable ToJsonNode(this YamlStream yaml) + { + return yaml.Documents.Select(x => x.ToJsonNode()); + } + + /// + /// Converts a single YAML document to a . + /// + /// The YAML document. + /// A `JsonNode` representative of the YAML document. + public static JsonNode ToJsonNode(this YamlDocument yaml) + { + return yaml.RootNode.ToJsonNode(); + } + + /// + /// Converts a single YAML node to a . + /// + /// The YAML node. + /// A `JsonNode` representative of the YAML node. + /// Thrown for YAML that is not compatible with JSON. + public static JsonNode ToJsonNode(this YamlNode yaml) + { + return yaml switch + { + YamlMappingNode map => map.ToJsonObject(), + YamlSequenceNode seq => seq.ToJsonArray(), + YamlScalarNode scalar => scalar.ToJsonValue(), + _ => throw new NotSupportedException("This yaml isn't convertible to JSON") + }; + } + + /// + /// Converts a single JSON node to a . + /// + /// + /// + /// + public static YamlNode ToYamlNode(this JsonNode json) + { + return json switch + { + JsonObject obj => obj.ToYamlMapping(), + JsonArray arr => arr.ToYamlSequence(), + JsonValue val => val.ToYamlScalar(), + _ => throw new NotSupportedException("This isn't a supported JsonNode") + }; + } + + /// + /// Converts a to a . + /// + /// + /// + public static JsonObject ToJsonObject(this YamlMappingNode yaml) + { + var node = new JsonObject(); + foreach (var keyValuePair in yaml) + { + var key = ((YamlScalarNode)keyValuePair.Key).Value!; + node[key] = keyValuePair.Value.ToJsonNode(); + } + + return node; + } + + private static YamlMappingNode ToYamlMapping(this JsonObject obj) + { + return new YamlMappingNode(obj.ToDictionary(x => (YamlNode)new YamlScalarNode(x.Key), x => x.Value!.ToYamlNode())); + } + + /// + /// Converts a to a . + /// + /// + /// + public static JsonArray ToJsonArray(this YamlSequenceNode yaml) + { + var node = new JsonArray(); + foreach (var value in yaml) + { + node.Add(value.ToJsonNode()); + } + + return node; + } + + private static YamlSequenceNode ToYamlSequence(this JsonArray arr) + { + return new YamlSequenceNode(arr.Select(x => x!.ToYamlNode())); + } + + private static JsonValue ToJsonValue(this YamlScalarNode yaml) + { + switch (yaml.Style) + { + case ScalarStyle.Plain: + return decimal.TryParse(yaml.Value, NumberStyles.Float, CultureInfo.InvariantCulture, out var d) + ? JsonValue.Create(d) + : bool.TryParse(yaml.Value, out var b) + ? JsonValue.Create(b) + : JsonValue.Create(yaml.Value)!; + case ScalarStyle.SingleQuoted: + case ScalarStyle.DoubleQuoted: + case ScalarStyle.Literal: + case ScalarStyle.Folded: + case ScalarStyle.Any: + return JsonValue.Create(yaml.Value)!; + default: + throw new ArgumentOutOfRangeException(); + } + } + + private static YamlScalarNode ToYamlScalar(this JsonValue val) + { + return new YamlScalarNode(val.ToJsonString()); + } + } +} diff --git a/src/Microsoft.OpenApi.Readers/YamlHelper.cs b/src/Microsoft.OpenApi.Readers/YamlHelper.cs deleted file mode 100644 index 7ce58d481..000000000 --- a/src/Microsoft.OpenApi.Readers/YamlHelper.cs +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. - -using System.IO; -using System.Linq; -using Microsoft.OpenApi.Exceptions; -using SharpYaml.Serialization; - -namespace Microsoft.OpenApi.Readers -{ - internal static class YamlHelper - { - public static string GetScalarValue(this YamlNode node) - { - if (node is not YamlScalarNode scalarNode) - { - throw new OpenApiException($"Expected scalar at line {node.Start.Line}"); - } - - return scalarNode.Value; - } - - public static YamlNode ParseYamlString(string yamlString) - { - var reader = new StringReader(yamlString); - var yamlStream = new YamlStream(); - yamlStream.Load(reader); - - var yamlDocument = yamlStream.Documents.First(); - return yamlDocument.RootNode; - } - } -} diff --git a/src/Microsoft.OpenApi.Workbench/MainModel.cs b/src/Microsoft.OpenApi.Workbench/MainModel.cs index 7068a61e8..d518645a5 100644 --- a/src/Microsoft.OpenApi.Workbench/MainModel.cs +++ b/src/Microsoft.OpenApi.Workbench/MainModel.cs @@ -10,6 +10,7 @@ using System.Threading.Tasks; using Microsoft.OpenApi.Extensions; using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Reader; using Microsoft.OpenApi.Readers; using Microsoft.OpenApi.Services; using Microsoft.OpenApi.Validations; @@ -158,6 +159,7 @@ public OpenApiSpecVersion Version _version = value; OnPropertyChanged(nameof(IsV2_0)); OnPropertyChanged(nameof(IsV3_0)); + OnPropertyChanged(nameof(IsV3_1)); } } @@ -185,6 +187,12 @@ public bool IsV3_0 set => Version = OpenApiSpecVersion.OpenApi3_0; } + public bool IsV3_1 + { + get => Version == OpenApiSpecVersion.OpenApi3_1; + set => Version = OpenApiSpecVersion.OpenApi3_1; + } + /// /// Handling method when the property with given name has changed. /// @@ -203,6 +211,9 @@ protected void OnPropertyChanged(string propertyName) /// internal async Task ParseDocumentAsync() { + OpenApiReaderRegistry.RegisterReader(OpenApiConstants.Yaml, new OpenApiYamlReader()); + OpenApiReaderRegistry.RegisterReader(OpenApiConstants.Yml, new OpenApiYamlReader()); + Stream stream = null; try { @@ -245,8 +256,9 @@ internal async Task ParseDocumentAsync() settings.BaseUrl = new("file://" + Path.GetDirectoryName(_inputFile) + "/"); } } - var readResult = await new OpenApiStreamReader(settings - ).ReadAsync(stream); + + var format = OpenApiModelFactory.GetFormat(_inputFile); + var readResult = await OpenApiDocument.LoadAsync(stream, format); var document = readResult.OpenApiDocument; var context = readResult.OpenApiDiagnostic; @@ -287,7 +299,8 @@ internal async Task ParseDocumentAsync() Output = string.Empty; Errors = "Failed to parse input: " + ex.Message; } - finally { + finally + { if (stream != null) { stream.Close(); diff --git a/src/Microsoft.OpenApi.Workbench/MainWindow.xaml b/src/Microsoft.OpenApi.Workbench/MainWindow.xaml index 41a4f2543..a3696f1e7 100644 --- a/src/Microsoft.OpenApi.Workbench/MainWindow.xaml +++ b/src/Microsoft.OpenApi.Workbench/MainWindow.xaml @@ -40,6 +40,7 @@ + diff --git a/src/Microsoft.OpenApi.Workbench/MainWindow.xaml.cs b/src/Microsoft.OpenApi.Workbench/MainWindow.xaml.cs index f9f1773c7..3c02c254d 100644 --- a/src/Microsoft.OpenApi.Workbench/MainWindow.xaml.cs +++ b/src/Microsoft.OpenApi.Workbench/MainWindow.xaml.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. using System; diff --git a/src/Microsoft.OpenApi/Any/AnyType.cs b/src/Microsoft.OpenApi/Any/AnyType.cs deleted file mode 100644 index 3ac617e8a..000000000 --- a/src/Microsoft.OpenApi/Any/AnyType.cs +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. - -namespace Microsoft.OpenApi.Any -{ - /// - /// Type of an - /// - public enum AnyType - { - /// - /// Primitive. - /// - Primitive, - - /// - /// Null. - /// - Null, - - /// - /// Array. - /// - Array, - - /// - /// Object. - /// - Object - } -} diff --git a/src/Microsoft.OpenApi/Any/IOpenApiAny.cs b/src/Microsoft.OpenApi/Any/IOpenApiAny.cs deleted file mode 100644 index ece675508..000000000 --- a/src/Microsoft.OpenApi/Any/IOpenApiAny.cs +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. - -using Microsoft.OpenApi.Interfaces; - -namespace Microsoft.OpenApi.Any -{ - /// - /// Base interface for all the types that represent Open API Any. - /// - public interface IOpenApiAny : IOpenApiElement, IOpenApiExtension - { - /// - /// Type of an . - /// - AnyType AnyType { get; } - } -} diff --git a/src/Microsoft.OpenApi/Any/IOpenApiPrimitive.cs b/src/Microsoft.OpenApi/Any/IOpenApiPrimitive.cs deleted file mode 100644 index 039782852..000000000 --- a/src/Microsoft.OpenApi/Any/IOpenApiPrimitive.cs +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. - -namespace Microsoft.OpenApi.Any -{ - /// - /// Primitive type. - /// - public enum PrimitiveType - { - /// - /// Integer - /// - Integer, - - /// - /// Long - /// - Long, - - /// - /// Float - /// - Float, - - /// - /// Double - /// - Double, - - /// - /// String - /// - String, - - /// - /// Byte - /// - Byte, - - /// - /// Binary - /// - Binary, - - /// - /// Boolean - /// - Boolean, - - /// - /// Date - /// - Date, - - /// - /// DateTime - /// - DateTime, - - /// - /// Password - /// - Password - } - - /// - /// Base interface for the Primitive type. - /// - public interface IOpenApiPrimitive : IOpenApiAny - { - /// - /// Primitive type. - /// - PrimitiveType PrimitiveType { get; } - } -} diff --git a/src/Microsoft.OpenApi/Any/OpenApiAny.cs b/src/Microsoft.OpenApi/Any/OpenApiAny.cs new file mode 100644 index 000000000..54bddf326 --- /dev/null +++ b/src/Microsoft.OpenApi/Any/OpenApiAny.cs @@ -0,0 +1,41 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System.Text.Json.Nodes; +using Microsoft.OpenApi.Interfaces; +using Microsoft.OpenApi.Writers; + +namespace Microsoft.OpenApi.Any +{ + /// + /// A wrapper class for JsonNode + /// + public class OpenApiAny : IOpenApiElement, IOpenApiExtension + { + private readonly JsonNode jsonNode; + + /// + /// Initializes the class. + /// + /// + public OpenApiAny(JsonNode jsonNode) + { + this.jsonNode = jsonNode; + } + + /// + /// Gets the underlying JsonNode. + /// + public JsonNode Node { get { return jsonNode; } } + + /// + /// Writes out the OpenApiAny type. + /// + /// + /// + public void Write(IOpenApiWriter writer, OpenApiSpecVersion specVersion) + { + writer.WriteAny(Node); + } + } +} diff --git a/src/Microsoft.OpenApi/Any/OpenApiAnyCloneHelper.cs b/src/Microsoft.OpenApi/Any/OpenApiAnyCloneHelper.cs deleted file mode 100644 index eaa1dac31..000000000 --- a/src/Microsoft.OpenApi/Any/OpenApiAnyCloneHelper.cs +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. - -using System; -using System.Diagnostics.CodeAnalysis; - -namespace Microsoft.OpenApi.Any -{ - /// - /// Contains logic for cloning objects through copy constructors. - /// - public class OpenApiAnyCloneHelper - { - /// - /// Clones an instance of object from the copy constructor - /// - /// The object instance. - /// A clone copy or the object itself. - [Obsolete("Use native AoT-friendly generic overload of CloneFromCopyConstructor instead.")] - [RequiresUnreferencedCode("CloneFromCopyConstructor is not trim-compatible. Recommended to use native AoT-friendly type-specific overloads of CloneFromCopyConstructor instead.")] - public static IOpenApiAny CloneFromCopyConstructor(IOpenApiAny obj) - { - if (obj != null) - { - var t = obj.GetType(); - foreach (var ci in t.GetConstructors()) - { - var pi = ci.GetParameters(); - if (pi.Length == 1 && pi[0].ParameterType == t) - { - return (IOpenApiAny)ci.Invoke(new object[] { obj }); - } - } - } - - return obj; - } - - /// - /// Clones an instance of object from the copy constructor - /// - /// The object instance. - /// A clone copy or the object itself. - public static T CloneFromCopyConstructor<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T>(T obj) where T : IOpenApiAny - { - if (obj != null) - { - foreach (var ci in typeof(T).GetConstructors()) - { - var pi = ci.GetParameters(); - if (pi.Length == 1 && pi[0].ParameterType == typeof(T)) - { - return (T)ci.Invoke([obj]); - } - } - } - - return obj; - } - } -} diff --git a/src/Microsoft.OpenApi/Any/OpenApiArray.cs b/src/Microsoft.OpenApi/Any/OpenApiArray.cs deleted file mode 100644 index 5a9af0fff..000000000 --- a/src/Microsoft.OpenApi/Any/OpenApiArray.cs +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. - -using Microsoft.OpenApi.Writers; -using System.Collections.Generic; - -namespace Microsoft.OpenApi.Any -{ - /// - /// Open API array. - /// - public class OpenApiArray : List, IOpenApiAny - { - /// - /// The type of - /// - public AnyType AnyType { get; } = AnyType.Array; - - /// - /// Parameterless constructor - /// - public OpenApiArray() { } - - /// - /// Initializes a copy of object - /// - public OpenApiArray(OpenApiArray array) - { - AnyType = array.AnyType; - foreach (var item in array) - { - Add(OpenApiAnyCloneHelper.CloneFromCopyConstructor(item)); - } - } - - /// - /// Write out contents of OpenApiArray to passed writer - /// - /// Instance of JSON or YAML writer. - /// Version of the OpenAPI specification that that will be output. - public void Write(IOpenApiWriter writer, OpenApiSpecVersion specVersion) - { - writer.WriteStartArray(); - - foreach (var item in this) - { - writer.WriteAny(item); - } - - writer.WriteEndArray(); - } - } -} diff --git a/src/Microsoft.OpenApi/Any/OpenApiBinary.cs b/src/Microsoft.OpenApi/Any/OpenApiBinary.cs deleted file mode 100644 index 23fdbe4d6..000000000 --- a/src/Microsoft.OpenApi/Any/OpenApiBinary.cs +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. - -namespace Microsoft.OpenApi.Any -{ - /// - /// Open API binary. - /// - public class OpenApiBinary : OpenApiPrimitive - { - /// - /// Initializes the class. - /// - /// - public OpenApiBinary(byte[] value) - : base(value) - { - } - - /// - /// Primitive type this object represents. - /// - public override PrimitiveType PrimitiveType => PrimitiveType.Binary; - } -} diff --git a/src/Microsoft.OpenApi/Any/OpenApiBoolean.cs b/src/Microsoft.OpenApi/Any/OpenApiBoolean.cs deleted file mode 100644 index aef1a42f8..000000000 --- a/src/Microsoft.OpenApi/Any/OpenApiBoolean.cs +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. - -namespace Microsoft.OpenApi.Any -{ - /// - /// Open API boolean. - /// - public class OpenApiBoolean : OpenApiPrimitive - { - /// - /// Initializes the class. - /// - /// - public OpenApiBoolean(bool value) - : base(value) - { - } - - /// - /// Primitive type this object represents. - /// - public override PrimitiveType PrimitiveType => PrimitiveType.Boolean; - } -} diff --git a/src/Microsoft.OpenApi/Any/OpenApiByte.cs b/src/Microsoft.OpenApi/Any/OpenApiByte.cs deleted file mode 100644 index f28a50175..000000000 --- a/src/Microsoft.OpenApi/Any/OpenApiByte.cs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. - -namespace Microsoft.OpenApi.Any -{ - /// - /// Open API Byte - /// - public class OpenApiByte : OpenApiPrimitive - { - /// - /// Initializes the class. - /// - public OpenApiByte(byte value) - : this(new[] { value }) - { - } - - /// - /// Initializes the class. - /// - public OpenApiByte(byte[] value) - : base(value) - { - } - - /// - /// Primitive type this object represents. - /// - public override PrimitiveType PrimitiveType => PrimitiveType.Byte; - } -} diff --git a/src/Microsoft.OpenApi/Any/OpenApiDate.cs b/src/Microsoft.OpenApi/Any/OpenApiDate.cs deleted file mode 100644 index 4552ddfc8..000000000 --- a/src/Microsoft.OpenApi/Any/OpenApiDate.cs +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. - -using System; - -namespace Microsoft.OpenApi.Any -{ - /// - /// Open API Date - /// - public class OpenApiDate : OpenApiPrimitive - { - /// - /// Initializes the class. - /// - public OpenApiDate(DateTime value) - : base(value) - { - } - - /// - /// Primitive type this object represents. - /// - public override PrimitiveType PrimitiveType => PrimitiveType.Date; - } -} diff --git a/src/Microsoft.OpenApi/Any/OpenApiDateTime.cs b/src/Microsoft.OpenApi/Any/OpenApiDateTime.cs deleted file mode 100644 index c812aa50c..000000000 --- a/src/Microsoft.OpenApi/Any/OpenApiDateTime.cs +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. - -using System; - -namespace Microsoft.OpenApi.Any -{ - /// - /// Open API Datetime - /// - public class OpenApiDateTime : OpenApiPrimitive - { - /// - /// Initializes the class. - /// - public OpenApiDateTime(DateTimeOffset value) - : base(value) - { - } - - /// - /// Primitive type this object represents. - /// - public override PrimitiveType PrimitiveType => PrimitiveType.DateTime; - } -} diff --git a/src/Microsoft.OpenApi/Any/OpenApiDouble.cs b/src/Microsoft.OpenApi/Any/OpenApiDouble.cs deleted file mode 100644 index 3a913873c..000000000 --- a/src/Microsoft.OpenApi/Any/OpenApiDouble.cs +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. - -namespace Microsoft.OpenApi.Any -{ - /// - /// Open API Double - /// - public class OpenApiDouble : OpenApiPrimitive - { - /// - /// Initializes the class. - /// - public OpenApiDouble(double value) - : base(value) - { - } - - /// - /// Primitive type this object represents. - /// - public override PrimitiveType PrimitiveType => PrimitiveType.Double; - } -} diff --git a/src/Microsoft.OpenApi/Any/OpenApiFloat.cs b/src/Microsoft.OpenApi/Any/OpenApiFloat.cs deleted file mode 100644 index 5e90a4106..000000000 --- a/src/Microsoft.OpenApi/Any/OpenApiFloat.cs +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. - -namespace Microsoft.OpenApi.Any -{ - /// - /// Open API Float - /// - public class OpenApiFloat : OpenApiPrimitive - { - /// - /// Initializes the class. - /// - public OpenApiFloat(float value) - : base(value) - { - } - - /// - /// Primitive type this object represents. - /// - public override PrimitiveType PrimitiveType => PrimitiveType.Float; - } -} diff --git a/src/Microsoft.OpenApi/Any/OpenApiInteger.cs b/src/Microsoft.OpenApi/Any/OpenApiInteger.cs deleted file mode 100644 index fa47b4f48..000000000 --- a/src/Microsoft.OpenApi/Any/OpenApiInteger.cs +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. - -namespace Microsoft.OpenApi.Any -{ - /// - /// Open API Integer - /// - public class OpenApiInteger : OpenApiPrimitive - { - /// - /// Initializes the class. - /// - public OpenApiInteger(int value) - : base(value) - { - } - - /// - /// Primitive type this object represents. - /// - public override PrimitiveType PrimitiveType => PrimitiveType.Integer; - } -} diff --git a/src/Microsoft.OpenApi/Any/OpenApiLong.cs b/src/Microsoft.OpenApi/Any/OpenApiLong.cs deleted file mode 100644 index f54b20084..000000000 --- a/src/Microsoft.OpenApi/Any/OpenApiLong.cs +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. - -namespace Microsoft.OpenApi.Any -{ - /// - /// Open API long. - /// - public class OpenApiLong : OpenApiPrimitive - { - /// - /// Initializes the class. - /// - public OpenApiLong(long value) - : base(value) - { - } - - /// - /// Primitive type this object represents. - /// - public override PrimitiveType PrimitiveType => PrimitiveType.Long; - } -} diff --git a/src/Microsoft.OpenApi/Any/OpenApiNull.cs b/src/Microsoft.OpenApi/Any/OpenApiNull.cs deleted file mode 100644 index b606d98e1..000000000 --- a/src/Microsoft.OpenApi/Any/OpenApiNull.cs +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. - -using Microsoft.OpenApi.Writers; - -namespace Microsoft.OpenApi.Any -{ - /// - /// Open API null. - /// - public class OpenApiNull : IOpenApiAny - { - /// - /// The type of - /// - public AnyType AnyType { get; } = AnyType.Null; - - /// - /// Parameterless constructor - /// - public OpenApiNull() { } - - /// - /// Initializes a copy of object - /// - public OpenApiNull(OpenApiNull openApiNull) - { - AnyType = openApiNull.AnyType; - } - - /// - /// Write out null representation - /// - /// - /// Version of the OpenAPI specification that that will be output. - public void Write(IOpenApiWriter writer, OpenApiSpecVersion specVersion) - { - writer.WriteAny(this); - } - } -} diff --git a/src/Microsoft.OpenApi/Any/OpenApiObject.cs b/src/Microsoft.OpenApi/Any/OpenApiObject.cs deleted file mode 100644 index 95783cc23..000000000 --- a/src/Microsoft.OpenApi/Any/OpenApiObject.cs +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. - -using System.Collections.Generic; -using Microsoft.OpenApi.Writers; - -namespace Microsoft.OpenApi.Any -{ - /// - /// Open API object. - /// - public class OpenApiObject : Dictionary, IOpenApiAny - { - /// - /// Type of . - /// - public AnyType AnyType { get; } = AnyType.Object; - - /// - /// Parameterless constructor - /// - public OpenApiObject() { } - - /// - /// Initializes a copy of object - /// - public OpenApiObject(OpenApiObject obj) - { - AnyType = obj.AnyType; - foreach (var key in obj.Keys) - { - this[key] = OpenApiAnyCloneHelper.CloneFromCopyConstructor(obj[key]); - } - } - - /// - /// Serialize OpenApiObject to writer - /// - /// - /// Version of the OpenAPI specification that that will be output. - public void Write(IOpenApiWriter writer, OpenApiSpecVersion specVersion) - { - writer.WriteStartObject(); - - foreach (var item in this) - { - writer.WritePropertyName(item.Key); - writer.WriteAny(item.Value); - } - - writer.WriteEndObject(); - } - } -} diff --git a/src/Microsoft.OpenApi/Any/OpenApiPassword.cs b/src/Microsoft.OpenApi/Any/OpenApiPassword.cs deleted file mode 100644 index f9e68bf66..000000000 --- a/src/Microsoft.OpenApi/Any/OpenApiPassword.cs +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. - -namespace Microsoft.OpenApi.Any -{ - /// - /// Open API password. - /// - public class OpenApiPassword : OpenApiPrimitive - { - /// - /// Initializes the class. - /// - public OpenApiPassword(string value) - : base(value) - { - } - - /// - /// The primitive type this object represents. - /// - public override PrimitiveType PrimitiveType => PrimitiveType.Password; - } -} diff --git a/src/Microsoft.OpenApi/Any/OpenApiPrimitive.cs b/src/Microsoft.OpenApi/Any/OpenApiPrimitive.cs deleted file mode 100644 index 3ac1b0082..000000000 --- a/src/Microsoft.OpenApi/Any/OpenApiPrimitive.cs +++ /dev/null @@ -1,155 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. - -using System; -using System.Globalization; -using System.Text; -using Microsoft.OpenApi.Exceptions; -using Microsoft.OpenApi.Properties; -using Microsoft.OpenApi.Writers; - -namespace Microsoft.OpenApi.Any -{ - /// - /// Open API primitive class. - /// - /// - public abstract class OpenApiPrimitive : IOpenApiPrimitive - { - /// - /// Initializes the class with the given value. - /// - /// - public OpenApiPrimitive(T value) - { - Value = value; - } - - /// - /// Initializes a copy of an object - /// - /// - public OpenApiPrimitive(OpenApiPrimitive openApiPrimitive) - { - Value = openApiPrimitive.Value; - } - - /// - /// The kind of . - /// - public AnyType AnyType => AnyType.Primitive; - - /// - /// The primitive class this object represents. - /// - public abstract PrimitiveType PrimitiveType { get; } - - /// - /// Value of this - /// - public T Value { get; } - - /// - /// Write out content of primitive element - /// - /// - /// - public void Write(IOpenApiWriter writer, OpenApiSpecVersion specVersion) - { - switch (this.PrimitiveType) - { - case PrimitiveType.Integer: - var intValue = (OpenApiInteger)(IOpenApiPrimitive)this; - writer.WriteValue(intValue.Value); - break; - - case PrimitiveType.Long: - var longValue = (OpenApiLong)(IOpenApiPrimitive)this; - writer.WriteValue(longValue.Value); - break; - - case PrimitiveType.Float: - var floatValue = (OpenApiFloat)(IOpenApiPrimitive)this; - writer.WriteValue(floatValue.Value); - break; - - case PrimitiveType.Double: - var doubleValue = (OpenApiDouble)(IOpenApiPrimitive)this; - var actualValue = doubleValue.Value; - if (actualValue.Equals(double.NaN) - || actualValue.Equals(double.NegativeInfinity) - || actualValue.Equals(double.PositiveInfinity)) - { - // Write out NaN, -Infinity, Infinity as strings - writer.WriteValue(actualValue.ToString(CultureInfo.InvariantCulture)); - break; - } - else - { - writer.WriteValue(actualValue); - } - break; - - case PrimitiveType.String: - var stringValue = (OpenApiString)(IOpenApiPrimitive)this; - if (stringValue.IsRawString()) - writer.WriteRaw(stringValue.Value); - else - writer.WriteValue(stringValue.Value); - break; - - case PrimitiveType.Byte: - var byteValue = (OpenApiByte)(IOpenApiPrimitive)this; - if (byteValue.Value == null) - { - writer.WriteNull(); - } - else - { - writer.WriteValue(Convert.ToBase64String(byteValue.Value)); - } - - break; - - case PrimitiveType.Binary: - var binaryValue = (OpenApiBinary)(IOpenApiPrimitive)this; - if (binaryValue.Value == null) - { - writer.WriteNull(); - } - else - { - writer.WriteValue(Encoding.UTF8.GetString(binaryValue.Value)); - } - - break; - - case PrimitiveType.Boolean: - var boolValue = (OpenApiBoolean)(IOpenApiPrimitive)this; - writer.WriteValue(boolValue.Value); - break; - - case PrimitiveType.Date: - var dateValue = (OpenApiDate)(IOpenApiPrimitive)this; - writer.WriteValue(dateValue.Value.ToString("o").Substring(0, 10)); - break; - - case PrimitiveType.DateTime: - var dateTimeValue = (OpenApiDateTime)(IOpenApiPrimitive)this; - writer.WriteValue(dateTimeValue.Value); - break; - - case PrimitiveType.Password: - var passwordValue = (OpenApiPassword)(IOpenApiPrimitive)this; - writer.WriteValue(passwordValue.Value); - break; - - default: - throw new OpenApiWriterException( - string.Format( - SRResource.PrimitiveTypeNotSupported, - this.PrimitiveType)); - } - } - } -} diff --git a/src/Microsoft.OpenApi/Any/OpenApiString.cs b/src/Microsoft.OpenApi/Any/OpenApiString.cs deleted file mode 100644 index fa1b799ee..000000000 --- a/src/Microsoft.OpenApi/Any/OpenApiString.cs +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. - -namespace Microsoft.OpenApi.Any -{ - /// - /// Open API string type. - /// - public class OpenApiString : OpenApiPrimitive - { - private bool isExplicit; - private bool isRawString; - - /// - /// Initializes the class. - /// - /// - public OpenApiString(string value) - : this(value, false) - { - } - - /// - /// Initializes the class. - /// - /// - /// Used to indicate if a string is quoted. - public OpenApiString(string value, bool isExplicit) - : base(value) - { - this.isExplicit = isExplicit; - } - - /// - /// Initializes the class. - /// - /// - /// Used to indicate if a string is quoted. - /// Used to indicate to the writer that the value should be written without encoding. - public OpenApiString(string value, bool isExplicit, bool isRawString) - : base(value) - { - this.isExplicit = isExplicit; - this.isRawString = isRawString; - } - - /// - /// The primitive class this object represents. - /// - public override PrimitiveType PrimitiveType => PrimitiveType.String; - - /// - /// True if string was specified explicitly by the means of double quotes, single quotes, or literal or folded style. - /// - public bool IsExplicit() - { - return this.isExplicit; - } - - /// - /// True if the writer should process the value as supplied without encoding. - /// - public bool IsRawString() - { - return this.isRawString; - } - } -} diff --git a/src/Microsoft.OpenApi.Readers/Exceptions/OpenApiReaderException.cs b/src/Microsoft.OpenApi/Exceptions/OpenApiReaderException.cs similarity index 87% rename from src/Microsoft.OpenApi.Readers/Exceptions/OpenApiReaderException.cs rename to src/Microsoft.OpenApi/Exceptions/OpenApiReaderException.cs index 53ae62d50..257b0e9a4 100644 --- a/src/Microsoft.OpenApi.Readers/Exceptions/OpenApiReaderException.cs +++ b/src/Microsoft.OpenApi/Exceptions/OpenApiReaderException.cs @@ -1,11 +1,11 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. using System; -using Microsoft.OpenApi.Exceptions; -using SharpYaml.Serialization; +using System.Text.Json.Nodes; +using Microsoft.OpenApi.Reader; -namespace Microsoft.OpenApi.Readers.Exceptions +namespace Microsoft.OpenApi.Exceptions { /// /// Defines an exception indicating OpenAPI Reader encountered an issue while reading. @@ -29,7 +29,8 @@ public OpenApiReaderException(string message) : base(message) { } /// /// Plain text error message for this exception. /// Context of current parsing process. - public OpenApiReaderException(string message, ParsingContext context) : base(message) { + public OpenApiReaderException(string message, ParsingContext context) : base(message) + { Pointer = context.GetLocation(); } @@ -38,11 +39,10 @@ public OpenApiReaderException(string message, ParsingContext context) : base(mes /// /// Plain text error message for this exception. /// Parsing node where error occured - public OpenApiReaderException(string message, YamlNode node) : base(message) + public OpenApiReaderException(string message, JsonNode node) : base(message) { // This only includes line because using a char range causes tests to break due to CR/LF & LF differences // See https://tools.ietf.org/html/rfc5147 for syntax - Pointer = $"#line={node.Start.Line}"; } /// diff --git a/src/Microsoft.OpenApi.Readers/Exceptions/OpenApiUnsupportedSpecVersionException.cs b/src/Microsoft.OpenApi/Exceptions/OpenApiUnsupportedSpecVersionException.cs similarity index 97% rename from src/Microsoft.OpenApi.Readers/Exceptions/OpenApiUnsupportedSpecVersionException.cs rename to src/Microsoft.OpenApi/Exceptions/OpenApiUnsupportedSpecVersionException.cs index 2d125c259..f9be8bd63 100644 --- a/src/Microsoft.OpenApi.Readers/Exceptions/OpenApiUnsupportedSpecVersionException.cs +++ b/src/Microsoft.OpenApi/Exceptions/OpenApiUnsupportedSpecVersionException.cs @@ -4,7 +4,7 @@ using System; using System.Globalization; -namespace Microsoft.OpenApi.Readers.Exceptions +namespace Microsoft.OpenApi.Exceptions { /// /// Defines an exception indicating OpenAPI Reader encountered an unsupported specification version while reading. diff --git a/src/Microsoft.OpenApi/Extensions/OpenApiElementExtensions.cs b/src/Microsoft.OpenApi/Extensions/OpenApiElementExtensions.cs index 38a53ecec..d0b0d9c35 100644 --- a/src/Microsoft.OpenApi/Extensions/OpenApiElementExtensions.cs +++ b/src/Microsoft.OpenApi/Extensions/OpenApiElementExtensions.cs @@ -24,6 +24,12 @@ public static class OpenApiElementExtensions public static IEnumerable Validate(this IOpenApiElement element, ValidationRuleSet ruleSet) { var validator = new OpenApiValidator(ruleSet); + + if (element is OpenApiDocument doc) + { + validator.HostDocument = doc; + } + var walker = new OpenApiWalker(validator); walker.Walk(element); return validator.Errors.Cast().Union(validator.Warnings); diff --git a/src/Microsoft.OpenApi/Extensions/OpenApiReferencableExtensions.cs b/src/Microsoft.OpenApi/Extensions/OpenApiReferencableExtensions.cs index 5fb1190eb..aca76f979 100644 --- a/src/Microsoft.OpenApi/Extensions/OpenApiReferencableExtensions.cs +++ b/src/Microsoft.OpenApi/Extensions/OpenApiReferencableExtensions.cs @@ -59,8 +59,6 @@ private static IOpenApiReferenceable ResolveReferenceOnHeaderElement( { switch (propertyName) { - case OpenApiConstants.Schema: - return headerElement.Schema; case OpenApiConstants.Examples when mapKey != null: return headerElement.Examples[mapKey]; default: @@ -76,8 +74,6 @@ private static IOpenApiReferenceable ResolveReferenceOnParameterElement( { switch (propertyName) { - case OpenApiConstants.Schema: - return parameterElement.Schema; case OpenApiConstants.Examples when mapKey != null: return parameterElement.Examples[mapKey]; default: diff --git a/src/Microsoft.OpenApi/Extensions/OpenApiSerializableExtensions.cs b/src/Microsoft.OpenApi/Extensions/OpenApiSerializableExtensions.cs index 845f6ce65..5d59a8de2 100755 --- a/src/Microsoft.OpenApi/Extensions/OpenApiSerializableExtensions.cs +++ b/src/Microsoft.OpenApi/Extensions/OpenApiSerializableExtensions.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.Globalization; @@ -106,6 +106,10 @@ public static void Serialize(this T element, IOpenApiWriter writer, OpenApiSp switch (specVersion) { + case OpenApiSpecVersion.OpenApi3_1: + element.SerializeAsV31(writer); + break; + case OpenApiSpecVersion.OpenApi3_0: element.SerializeAsV3(writer); break; diff --git a/src/Microsoft.OpenApi/Extensions/OpenApiTypeMapper.cs b/src/Microsoft.OpenApi/Extensions/OpenApiTypeMapper.cs index 6b7801541..e6dadd44d 100644 --- a/src/Microsoft.OpenApi/Extensions/OpenApiTypeMapper.cs +++ b/src/Microsoft.OpenApi/Extensions/OpenApiTypeMapper.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using Microsoft.OpenApi.Exceptions; using Microsoft.OpenApi.Models; namespace Microsoft.OpenApi.Extensions @@ -12,40 +13,81 @@ namespace Microsoft.OpenApi.Extensions /// public static class OpenApiTypeMapper { + /// + /// Maps a JsonSchema data type to an identifier. + /// + /// + /// + public static string ToIdentifier(this JsonSchemaType? schemaType) + { + return schemaType switch + { + JsonSchemaType.Null => "null", + JsonSchemaType.Boolean => "boolean", + JsonSchemaType.Integer => "integer", + JsonSchemaType.Number => "number", + JsonSchemaType.String => "string", + JsonSchemaType.Array => "array", + JsonSchemaType.Object => "object", + _ => null, + }; + } + + /// + /// Converts a schema type's identifier into the enum equivalent + /// + /// + /// + public static JsonSchemaType ToJsonSchemaType(this string identifier) + { + return identifier switch + { + "null" => JsonSchemaType.Null, + "boolean" => JsonSchemaType.Boolean, + "integer" or "int" => JsonSchemaType.Integer, + "number" or "double" or "float" or "decimal"=> JsonSchemaType.Number, + "string" => JsonSchemaType.String, + "array" => JsonSchemaType.Array, + "object" => JsonSchemaType.Object, + "file" => JsonSchemaType.String, // File is treated as string + _ => throw new OpenApiException(string.Format("Invalid schema type identifier: {0}", identifier)) + }; + } + private static readonly Dictionary> _simpleTypeToOpenApiSchema = new() { - [typeof(bool)] = () => new() { Type = "boolean" }, - [typeof(byte)] = () => new() { Type = "string", Format = "byte" }, - [typeof(int)] = () => new() { Type = "number", Format = "int32" }, - [typeof(uint)] = () => new() { Type = "number", Format = "int32" }, - [typeof(long)] = () => new() { Type = "number", Format = "int64" }, - [typeof(ulong)] = () => new() { Type = "number", Format = "int64" }, - [typeof(float)] = () => new() { Type = "number", Format = "float" }, - [typeof(double)] = () => new() { Type = "number", Format = "double" }, - [typeof(decimal)] = () => new() { Type = "number", Format = "double" }, - [typeof(DateTime)] = () => new() { Type = "string", Format = "date-time" }, - [typeof(DateTimeOffset)] = () => new() { Type = "string", Format = "date-time" }, - [typeof(Guid)] = () => new() { Type = "string", Format = "uuid" }, - [typeof(char)] = () => new() { Type = "string" }, + [typeof(bool)] = () => new() { Type = JsonSchemaType.Boolean }, + [typeof(byte)] = () => new() { Type = JsonSchemaType.String, Format = "byte" }, + [typeof(int)] = () => new() { Type = JsonSchemaType.Integer, Format = "int32" }, + [typeof(uint)] = () => new() { Type = JsonSchemaType.Integer, Format = "int32" }, + [typeof(long)] = () => new() { Type = JsonSchemaType.Integer, Format = "int64" }, + [typeof(ulong)] = () => new() { Type = JsonSchemaType.Integer, Format = "int64" }, + [typeof(float)] = () => new() { Type = JsonSchemaType.Number, Format = "float" }, + [typeof(double)] = () => new() { Type = JsonSchemaType.Number, Format = "double" }, + [typeof(decimal)] = () => new() { Type = JsonSchemaType.Number, Format = "double" }, + [typeof(DateTime)] = () => new() { Type = JsonSchemaType.String, Format = "date-time" }, + [typeof(DateTimeOffset)] = () => new() { Type = JsonSchemaType.String, Format = "date-time" }, + [typeof(Guid)] = () => new() { Type = JsonSchemaType.String, Format = "uuid" }, + [typeof(char)] = () => new() { Type = JsonSchemaType.String }, // Nullable types - [typeof(bool?)] = () => new() { Type = "boolean", Nullable = true }, - [typeof(byte?)] = () => new() { Type = "string", Format = "byte", Nullable = true }, - [typeof(int?)] = () => new() { Type = "number", Format = "int32", Nullable = true }, - [typeof(uint?)] = () => new() { Type = "number", Format = "int32", Nullable = true }, - [typeof(long?)] = () => new() { Type = "number", Format = "int64", Nullable = true }, - [typeof(ulong?)] = () => new() { Type = "number", Format = "int64", Nullable = true }, - [typeof(float?)] = () => new() { Type = "number", Format = "float", Nullable = true }, - [typeof(double?)] = () => new() { Type = "number", Format = "double", Nullable = true }, - [typeof(decimal?)] = () => new() { Type = "number", Format = "double", Nullable = true }, - [typeof(DateTime?)] = () => new() { Type = "string", Format = "date-time", Nullable = true }, - [typeof(DateTimeOffset?)] = () => new() { Type = "string", Format = "date-time", Nullable = true }, - [typeof(Guid?)] = () => new() { Type = "string", Format = "uuid", Nullable = true }, - [typeof(char?)] = () => new() { Type = "string", Nullable = true }, + [typeof(bool?)] = () => new() { Type = JsonSchemaType.Boolean, Nullable = true }, + [typeof(byte?)] = () => new() { Type = JsonSchemaType.String, Format = "byte", Nullable = true }, + [typeof(int?)] = () => new() { Type = JsonSchemaType.Integer, Format = "int32", Nullable = true }, + [typeof(uint?)] = () => new() { Type = JsonSchemaType.Integer, Format = "int32", Nullable = true }, + [typeof(long?)] = () => new() { Type = JsonSchemaType.Integer, Format = "int64", Nullable = true }, + [typeof(ulong?)] = () => new() { Type = JsonSchemaType.Integer, Format = "int64", Nullable = true }, + [typeof(float?)] = () => new() { Type = JsonSchemaType.Number, Format = "float", Nullable = true }, + [typeof(double?)] = () => new() { Type = JsonSchemaType.Number, Format = "double", Nullable = true }, + [typeof(decimal?)] = () => new() { Type = JsonSchemaType.Number, Format = "double", Nullable = true }, + [typeof(DateTime?)] = () => new() { Type = JsonSchemaType.String, Format = "date-time", Nullable = true }, + [typeof(DateTimeOffset?)] = () => new() { Type = JsonSchemaType.String, Format = "date-time", Nullable = true }, + [typeof(Guid?)] = () => new() { Type = JsonSchemaType.String, Format = "uuid", Nullable = true }, + [typeof(char?)] = () => new() { Type = JsonSchemaType.String, Nullable = true }, - [typeof(Uri)] = () => new() { Type = "string", Format = "uri"}, // Uri is treated as simple string - [typeof(string)] = () => new() { Type = "string" }, - [typeof(object)] = () => new() { Type = "object" } + [typeof(Uri)] = () => new() { Type = JsonSchemaType.String, Format = "uri" }, // Uri is treated as simple string + [typeof(string)] = () => new() { Type = JsonSchemaType.String }, + [typeof(object)] = () => new() { Type = JsonSchemaType.Object } }; /// @@ -79,11 +121,11 @@ public static OpenApiSchema MapTypeToOpenApiPrimitiveType(this Type type) return _simpleTypeToOpenApiSchema.TryGetValue(type, out var result) ? result() - : new() { Type = "string" }; + : new() { Type = JsonSchemaType.String }; } /// - /// Maps an OpenAPI data type and format to a simple type. + /// Maps an JsonSchema data type and format to a simple type. /// /// The OpenApi data type /// The simple type @@ -95,13 +137,13 @@ public static Type MapOpenApiPrimitiveTypeToSimpleType(this OpenApiSchema schema throw new ArgumentNullException(nameof(schema)); } - var type = (schema.Type?.ToLowerInvariant(), schema.Format?.ToLowerInvariant(), schema.Nullable) switch + var type = (schema.Type.ToIdentifier(), schema.Format?.ToLowerInvariant(), schema.Nullable) switch { ("boolean", null, false) => typeof(bool), // integer is technically not valid with format, but we must provide some compatibility ("integer" or "number", "int32", false) => typeof(int), ("integer" or "number", "int64", false) => typeof(long), - ("integer", null, false) => typeof(int), + ("integer", null, false) => typeof(long), ("number", "float", false) => typeof(float), ("number", "double", false) => typeof(double), ("number", "decimal", false) => typeof(decimal), @@ -116,7 +158,7 @@ public static Type MapOpenApiPrimitiveTypeToSimpleType(this OpenApiSchema schema ("string", "uri", false) => typeof(Uri), ("integer" or "number", "int32", true) => typeof(int?), ("integer" or "number", "int64", true) => typeof(long?), - ("integer", null, true) => typeof(int?), + ("integer", null, true) => typeof(long?), ("number", "float", true) => typeof(float?), ("number", "double", true) => typeof(double?), ("number", null, true) => typeof(double?), diff --git a/src/Microsoft.OpenApi/Helpers/JsonNodeCloneHelper.cs b/src/Microsoft.OpenApi/Helpers/JsonNodeCloneHelper.cs new file mode 100644 index 000000000..d6e9cb9df --- /dev/null +++ b/src/Microsoft.OpenApi/Helpers/JsonNodeCloneHelper.cs @@ -0,0 +1,40 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System.Text.Json; +using System.Text.Json.Nodes; +using System.Text.Json.Serialization; +using Microsoft.OpenApi.Any; + +namespace Microsoft.OpenApi.Helpers +{ + internal static class JsonNodeCloneHelper + { + private static readonly JsonSerializerOptions options = new() + { + ReferenceHandler = ReferenceHandler.IgnoreCycles + }; + + internal static JsonNode Clone(JsonNode value) + { + var jsonString = Serialize(value); + if (string.IsNullOrEmpty(jsonString)) + { + return null; + } + + var result = JsonSerializer.Deserialize(jsonString, options); + return result; + } + + private static string Serialize(object obj) + { + if (obj == null) + { + return null; + } + var result = JsonSerializer.Serialize(obj, options); + return result; + } + } +} diff --git a/src/Microsoft.OpenApi.Readers/Interface/IDiagnostic.cs b/src/Microsoft.OpenApi/Interfaces/IDiagnostic.cs similarity index 85% rename from src/Microsoft.OpenApi.Readers/Interface/IDiagnostic.cs rename to src/Microsoft.OpenApi/Interfaces/IDiagnostic.cs index 65511ce11..74376de02 100644 --- a/src/Microsoft.OpenApi.Readers/Interface/IDiagnostic.cs +++ b/src/Microsoft.OpenApi/Interfaces/IDiagnostic.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. -namespace Microsoft.OpenApi.Readers.Interface +namespace Microsoft.OpenApi.Interfaces { /// /// Interface for the entity containing diagnostic information from the reading process. diff --git a/src/Microsoft.OpenApi/Interfaces/IEffective.cs b/src/Microsoft.OpenApi/Interfaces/IEffective.cs deleted file mode 100644 index b3ac0a37b..000000000 --- a/src/Microsoft.OpenApi/Interfaces/IEffective.cs +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. - -using Microsoft.OpenApi.Models; - -namespace Microsoft.OpenApi.Interfaces -{ - /// - /// OpenApiElements that implement IEffective indicate that their description is not self-contained. - /// External elements affect the effective description. - /// - /// Currently this will only be used for accessing external references. - /// In the next major version, this will be the approach accessing all referenced elements. - /// This will enable us to support merging properties that are peers of the $ref - /// Type of OpenApi Element that is being referenced. - public interface IEffective where T : class,IOpenApiElement - { - /// - /// Returns a calculated and cloned version of the element. - /// - T GetEffective(OpenApiDocument document); - } -} diff --git a/src/Microsoft.OpenApi/Interfaces/IOpenApiReader.cs b/src/Microsoft.OpenApi/Interfaces/IOpenApiReader.cs new file mode 100644 index 000000000..5f8b1cb22 --- /dev/null +++ b/src/Microsoft.OpenApi/Interfaces/IOpenApiReader.cs @@ -0,0 +1,56 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System.IO; +using System.Text.Json.Nodes; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.OpenApi.Reader; + +namespace Microsoft.OpenApi.Interfaces +{ + /// + /// Interface for Open API readers. + /// + public interface IOpenApiReader + { + /// + /// Reads the TextReader input and parses it into an Open API document. + /// + /// The TextReader input. + /// The OpenApi reader settings. + /// Propagates notification that an operation should be cancelled. + /// + Task ReadAsync(TextReader input, OpenApiReaderSettings settings = null, CancellationToken cancellationToken = default); + + /// + /// Parses the JsonNode input into an Open API document. + /// + /// The JsonNode input. + /// The Reader settings to be used during parsing. + /// Propagates notifications that operations should be cancelled. + /// The OpenAPI format. + /// + Task ReadAsync(JsonNode jsonNode, OpenApiReaderSettings settings, string format = null, CancellationToken cancellationToken = default); + + /// + /// Reads the TextReader input and parses the fragment of an OpenAPI description into an Open API Element. + /// + /// TextReader containing OpenAPI description to parse. + /// Version of the OpenAPI specification that the fragment conforms to. + /// Returns diagnostic object containing errors detected during parsing. + /// The OpenApiReader settings. + /// Instance of newly created IOpenApiElement. + T ReadFragment(TextReader input, OpenApiSpecVersion version, out OpenApiDiagnostic diagnostic, OpenApiReaderSettings settings = null) where T : IOpenApiElement; + + /// + /// Reads the JsonNode input and parses the fragment of an OpenAPI description into an Open API Element. + /// + /// TextReader containing OpenAPI description to parse. + /// Version of the OpenAPI specification that the fragment conforms to. + /// Returns diagnostic object containing errors detected during parsing. + /// The OpenApiReader settings. + /// Instance of newly created IOpenApiElement. + T ReadFragment(JsonNode input, OpenApiSpecVersion version, out OpenApiDiagnostic diagnostic, OpenApiReaderSettings settings = null) where T : IOpenApiElement; + } +} diff --git a/src/Microsoft.OpenApi/Interfaces/IOpenApiReferenceable.cs b/src/Microsoft.OpenApi/Interfaces/IOpenApiReferenceable.cs index 39d345319..0920fb1ef 100644 --- a/src/Microsoft.OpenApi/Interfaces/IOpenApiReferenceable.cs +++ b/src/Microsoft.OpenApi/Interfaces/IOpenApiReferenceable.cs @@ -20,16 +20,5 @@ public interface IOpenApiReferenceable : IOpenApiSerializable /// Reference object. /// OpenApiReference Reference { get; set; } - - /// - /// Serialize to OpenAPI V3 document without using reference. - /// - void SerializeAsV3WithoutReference(IOpenApiWriter writer); - - /// - /// Serialize to OpenAPI V2 document without using reference. - /// - void SerializeAsV2WithoutReference(IOpenApiWriter writer); - } } diff --git a/src/Microsoft.OpenApi/Interfaces/IOpenApiSerializable.cs b/src/Microsoft.OpenApi/Interfaces/IOpenApiSerializable.cs index a3b22bd08..081545b14 100644 --- a/src/Microsoft.OpenApi/Interfaces/IOpenApiSerializable.cs +++ b/src/Microsoft.OpenApi/Interfaces/IOpenApiSerializable.cs @@ -10,6 +10,12 @@ namespace Microsoft.OpenApi.Interfaces /// public interface IOpenApiSerializable : IOpenApiElement { + /// + /// Serialize OpenAPI element into v3.1 + /// + /// + void SerializeAsV31(IOpenApiWriter writer); + /// /// Serialize Open API element to v3.0. /// diff --git a/src/Microsoft.OpenApi.Readers/Interface/IOpenApiVersionService.cs b/src/Microsoft.OpenApi/Interfaces/IOpenApiVersionService.cs similarity index 58% rename from src/Microsoft.OpenApi.Readers/Interface/IOpenApiVersionService.cs rename to src/Microsoft.OpenApi/Interfaces/IOpenApiVersionService.cs index d70766132..97d1d3c9b 100644 --- a/src/Microsoft.OpenApi.Readers/Interface/IOpenApiVersionService.cs +++ b/src/Microsoft.OpenApi/Interfaces/IOpenApiVersionService.cs @@ -1,11 +1,10 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. -using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Models; -using Microsoft.OpenApi.Readers.ParseNodes; +using Microsoft.OpenApi.Reader.ParseNodes; -namespace Microsoft.OpenApi.Readers.Interface +namespace Microsoft.OpenApi.Interfaces { /// /// Interface to a version specific parsing implementations. @@ -17,16 +16,19 @@ internal interface IOpenApiVersionService /// /// The reference string. /// The type of the reference. + /// The summary of the reference. + /// A reference description /// The object or null. - OpenApiReference ConvertToOpenApiReference(string reference, ReferenceType? type); + OpenApiReference ConvertToOpenApiReference(string reference, ReferenceType? type, string summary = null, string description = null); /// /// Loads an OpenAPI Element from a document fragment /// /// Type of element to load /// document fragment node + /// A host document instance. /// Instance of OpenAPIElement - T LoadElement(ParseNode node) where T : IOpenApiElement; + T LoadElement(ParseNode node, OpenApiDocument doc = null) where T : IOpenApiElement; /// /// Converts a generic RootNode instance into a strongly typed OpenApiDocument @@ -34,5 +36,13 @@ internal interface IOpenApiVersionService /// RootNode containing the information to be converted into an OpenAPI Document /// Instance of OpenApiDocument populated with data from rootNode OpenApiDocument LoadDocument(RootNode rootNode); + + /// + /// Gets the description and summary scalar values in a reference object for V3.1 support + /// + /// A YamlMappingNode. + /// The scalar value we're parsing. + /// The resulting node value. + string GetReferenceScalarValues(MapNode mapNode, string scalarValue); } } diff --git a/src/Microsoft.OpenApi.Readers/Interface/IStreamLoader.cs b/src/Microsoft.OpenApi/Interfaces/IStreamLoader.cs similarity index 95% rename from src/Microsoft.OpenApi.Readers/Interface/IStreamLoader.cs rename to src/Microsoft.OpenApi/Interfaces/IStreamLoader.cs index 1c5471238..c3edebe1b 100644 --- a/src/Microsoft.OpenApi.Readers/Interface/IStreamLoader.cs +++ b/src/Microsoft.OpenApi/Interfaces/IStreamLoader.cs @@ -7,7 +7,7 @@ using System.Threading.Tasks; using Microsoft.OpenApi.Models; -namespace Microsoft.OpenApi.Readers.Interface +namespace Microsoft.OpenApi.Interfaces { /// /// Interface for service that translates a URI into a stream that can be loaded by a Reader diff --git a/src/Microsoft.OpenApi/Microsoft.OpenApi.csproj b/src/Microsoft.OpenApi/Microsoft.OpenApi.csproj index eb0ee6b12..6ddac0ec9 100644 --- a/src/Microsoft.OpenApi/Microsoft.OpenApi.csproj +++ b/src/Microsoft.OpenApi/Microsoft.OpenApi.csproj @@ -3,7 +3,7 @@ netstandard2.0 Latest true - 1.6.22 + 2.0.0-preview1 .NET models with JSON and YAML writers for OpenAPI specification true @@ -17,6 +17,14 @@ ..\Microsoft.OpenApi.snk + + + true + + + + + True diff --git a/src/Microsoft.OpenApi/MicrosoftExtensions/OpenApiDeprecationExtension.cs b/src/Microsoft.OpenApi/MicrosoftExtensions/OpenApiDeprecationExtension.cs index 683082e2c..a5bae9fa9 100644 --- a/src/Microsoft.OpenApi/MicrosoftExtensions/OpenApiDeprecationExtension.cs +++ b/src/Microsoft.OpenApi/MicrosoftExtensions/OpenApiDeprecationExtension.cs @@ -8,6 +8,7 @@ using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Writers; +using System.Text.Json.Nodes; namespace Microsoft.OpenApi.MicrosoftExtensions; @@ -71,23 +72,23 @@ public void Write(IOpenApiWriter writer, OpenApiSpecVersion specVersion) } } /// - /// Parses the to . + /// Parses the to . /// /// The source object. /// The . /// When the source element is not an object - public static OpenApiDeprecationExtension Parse(IOpenApiAny source) + public static OpenApiDeprecationExtension Parse(JsonNode source) { - if (source is not OpenApiObject rawObject) return null; + if (source is not JsonObject rawObject) return null; var extension = new OpenApiDeprecationExtension(); - if (rawObject.TryGetValue(nameof(RemovalDate).ToFirstCharacterLowerCase(), out var removalDate) && removalDate is OpenApiDateTime removalDateValue) - extension.RemovalDate = removalDateValue.Value; - if (rawObject.TryGetValue(nameof(Date).ToFirstCharacterLowerCase(), out var date) && date is OpenApiDateTime dateValue) - extension.Date = dateValue.Value; - if (rawObject.TryGetValue(nameof(Version).ToFirstCharacterLowerCase(), out var version) && version is OpenApiString versionValue) - extension.Version = versionValue.Value; - if (rawObject.TryGetValue(nameof(Description).ToFirstCharacterLowerCase(), out var description) && description is OpenApiString descriptionValue) - extension.Description = descriptionValue.Value; + if (rawObject.TryGetPropertyValue(nameof(RemovalDate).ToFirstCharacterLowerCase(), out var removalDate) && removalDate is JsonNode removalDateValue) + extension.RemovalDate = removalDateValue.GetValue(); + if (rawObject.TryGetPropertyValue(nameof(Date).ToFirstCharacterLowerCase(), out var date) && date is JsonNode dateValue) + extension.Date = dateValue.GetValue(); + if (rawObject.TryGetPropertyValue(nameof(Version).ToFirstCharacterLowerCase(), out var version) && version is JsonNode versionValue) + extension.Version = versionValue.GetValue(); + if (rawObject.TryGetPropertyValue(nameof(Description).ToFirstCharacterLowerCase(), out var description) && description is JsonNode descriptionValue) + extension.Description = descriptionValue.GetValue(); return extension; } } diff --git a/src/Microsoft.OpenApi/MicrosoftExtensions/OpenApiEnumFlagsExtension.cs b/src/Microsoft.OpenApi/MicrosoftExtensions/OpenApiEnumFlagsExtension.cs index 946537478..9cbae6350 100644 --- a/src/Microsoft.OpenApi/MicrosoftExtensions/OpenApiEnumFlagsExtension.cs +++ b/src/Microsoft.OpenApi/MicrosoftExtensions/OpenApiEnumFlagsExtension.cs @@ -8,6 +8,7 @@ using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Writers; +using System.Text.Json.Nodes; namespace Microsoft.OpenApi.MicrosoftExtensions; @@ -38,18 +39,18 @@ public void Write(IOpenApiWriter writer, OpenApiSpecVersion specVersion) writer.WriteEndObject(); } /// - /// Parse the extension from the raw IOpenApiAny object. + /// Parse the extension from the raw OpenApiAny object. /// /// The source element to parse. /// The . /// When the source element is not an object - public static OpenApiEnumFlagsExtension Parse(IOpenApiAny source) + public static OpenApiEnumFlagsExtension Parse(JsonNode source) { - if (source is not OpenApiObject rawObject) return null; + if (source is not JsonObject rawObject) throw new ArgumentOutOfRangeException(nameof(source)); var extension = new OpenApiEnumFlagsExtension(); - if (rawObject.TryGetValue(nameof(IsFlags).ToFirstCharacterLowerCase(), out var flagsValue) && flagsValue is OpenApiBoolean isFlags) + if (rawObject.TryGetPropertyValue(nameof(IsFlags).ToFirstCharacterLowerCase(), out var flagsValue) && flagsValue is JsonNode isFlags) { - extension.IsFlags = isFlags.Value; + extension.IsFlags = isFlags.GetValue(); } return extension; } diff --git a/src/Microsoft.OpenApi/MicrosoftExtensions/OpenApiEnumValuesDescriptionExtension.cs b/src/Microsoft.OpenApi/MicrosoftExtensions/OpenApiEnumValuesDescriptionExtension.cs index 272f4b313..1235e68b0 100644 --- a/src/Microsoft.OpenApi/MicrosoftExtensions/OpenApiEnumValuesDescriptionExtension.cs +++ b/src/Microsoft.OpenApi/MicrosoftExtensions/OpenApiEnumValuesDescriptionExtension.cs @@ -10,6 +10,7 @@ using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Writers; +using System.Text.Json.Nodes; namespace Microsoft.OpenApi.MicrosoftExtensions; @@ -62,14 +63,14 @@ public void Write(IOpenApiWriter writer, OpenApiSpecVersion specVersion) /// The source element to parse. /// The . /// When the source element is not an object - public static OpenApiEnumValuesDescriptionExtension Parse(IOpenApiAny source) + public static OpenApiEnumValuesDescriptionExtension Parse(JsonNode source) { - if (source is not OpenApiObject rawObject) return null; + if (source is not JsonObject rawObject) return null; var extension = new OpenApiEnumValuesDescriptionExtension(); - if (rawObject.TryGetValue("values", out var values) && values is OpenApiArray valuesArray) + if (rawObject.TryGetPropertyValue("values", out var values) && values is JsonArray valuesArray) { extension.ValuesDescriptions.AddRange(valuesArray - .OfType() + .OfType() .Select(x => new EnumDescription(x))); } return extension; @@ -92,15 +93,15 @@ public EnumDescription() /// Constructor from a raw OpenApiObject /// /// The source object - public EnumDescription(OpenApiObject source) + public EnumDescription(JsonObject source) { if (source is null) throw new ArgumentNullException(nameof(source)); - if (source.TryGetValue(nameof(Value).ToFirstCharacterLowerCase(), out var rawValue) && rawValue is OpenApiString value) - Value = value.Value; - if (source.TryGetValue(nameof(Description).ToFirstCharacterLowerCase(), out var rawDescription) && rawDescription is OpenApiString description) - Description = description.Value; - if (source.TryGetValue(nameof(Name).ToFirstCharacterLowerCase(), out var rawName) && rawName is OpenApiString name) - Name = name.Value; + if (source.TryGetPropertyValue(nameof(Value).ToFirstCharacterLowerCase(), out var rawValue) && rawValue is JsonNode value) + Value = value.GetValue(); + if (source.TryGetPropertyValue(nameof(Description).ToFirstCharacterLowerCase(), out var rawDescription) && rawDescription is JsonNode description) + Description = description.GetValue(); + if (source.TryGetPropertyValue(nameof(Name).ToFirstCharacterLowerCase(), out var rawName) && rawName is JsonNode name) + Name = name.GetValue(); } /// /// The description for the enum symbol diff --git a/src/Microsoft.OpenApi/MicrosoftExtensions/OpenApiPagingExtension.cs b/src/Microsoft.OpenApi/MicrosoftExtensions/OpenApiPagingExtension.cs index 9b81e2561..f64eebf3f 100644 --- a/src/Microsoft.OpenApi/MicrosoftExtensions/OpenApiPagingExtension.cs +++ b/src/Microsoft.OpenApi/MicrosoftExtensions/OpenApiPagingExtension.cs @@ -8,6 +8,7 @@ using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Writers; +using System.Text.Json.Nodes; namespace Microsoft.OpenApi.MicrosoftExtensions; @@ -71,23 +72,23 @@ public void Write(IOpenApiWriter writer, OpenApiSpecVersion specVersion) /// The source element to parse. /// The . /// When the source element is not an object - public static OpenApiPagingExtension Parse(IOpenApiAny source) + public static OpenApiPagingExtension Parse(JsonNode source) { - if (source is not OpenApiObject rawObject) return null; + if (source is not JsonObject rawObject) return null; var extension = new OpenApiPagingExtension(); - if (rawObject.TryGetValue(nameof(NextLinkName).ToFirstCharacterLowerCase(), out var nextLinkName) && nextLinkName is OpenApiString nextLinkNameStr) + if (rawObject.TryGetPropertyValue(nameof(NextLinkName).ToFirstCharacterLowerCase(), out var nextLinkName) && nextLinkName is JsonNode nextLinkNameStr) { - extension.NextLinkName = nextLinkNameStr.Value; + extension.NextLinkName = nextLinkNameStr.GetValue(); } - if (rawObject.TryGetValue(nameof(OperationName).ToFirstCharacterLowerCase(), out var opName) && opName is OpenApiString opNameStr) + if (rawObject.TryGetPropertyValue(nameof(OperationName).ToFirstCharacterLowerCase(), out var opName) && opName is JsonNode opNameStr) { - extension.OperationName = opNameStr.Value; + extension.OperationName = opNameStr.GetValue(); } - if (rawObject.TryGetValue(nameof(ItemName).ToFirstCharacterLowerCase(), out var itemName) && itemName is OpenApiString itemNameStr) + if (rawObject.TryGetPropertyValue(nameof(ItemName).ToFirstCharacterLowerCase(), out var itemName) && itemName is JsonNode itemNameStr) { - extension.ItemName = itemNameStr.Value; + extension.ItemName = itemNameStr.GetValue(); } return extension; diff --git a/src/Microsoft.OpenApi/MicrosoftExtensions/OpenApiPrimaryErrorMessageExtension.cs b/src/Microsoft.OpenApi/MicrosoftExtensions/OpenApiPrimaryErrorMessageExtension.cs index 0250af758..ad47db39b 100644 --- a/src/Microsoft.OpenApi/MicrosoftExtensions/OpenApiPrimaryErrorMessageExtension.cs +++ b/src/Microsoft.OpenApi/MicrosoftExtensions/OpenApiPrimaryErrorMessageExtension.cs @@ -4,6 +4,7 @@ // ------------------------------------------------------------ using System; +using System.Text.Json.Nodes; using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Writers; @@ -33,16 +34,16 @@ public void Write(IOpenApiWriter writer, OpenApiSpecVersion specVersion) public bool IsPrimaryErrorMessage { get; set; } /// - /// Parses the to . + /// Parses the to . /// /// The source object. /// The . - public static OpenApiPrimaryErrorMessageExtension Parse(IOpenApiAny source) + public static OpenApiPrimaryErrorMessageExtension Parse(JsonNode source) { - if (source is not OpenApiBoolean rawObject) return null; + if (source is not JsonNode rawObject) return null; return new() { - IsPrimaryErrorMessage = rawObject.Value + IsPrimaryErrorMessage = rawObject.GetValue() }; } } diff --git a/src/Microsoft.OpenApi/MicrosoftExtensions/OpenApiReservedParameterExtension.cs b/src/Microsoft.OpenApi/MicrosoftExtensions/OpenApiReservedParameterExtension.cs index e45d9e7e9..2d3a8c117 100644 --- a/src/Microsoft.OpenApi/MicrosoftExtensions/OpenApiReservedParameterExtension.cs +++ b/src/Microsoft.OpenApi/MicrosoftExtensions/OpenApiReservedParameterExtension.cs @@ -4,6 +4,7 @@ // ------------------------------------------------------------ using System; +using System.Text.Json.Nodes; using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Writers; @@ -34,17 +35,17 @@ public bool? IsReserved get; set; } /// - /// Parses the to . + /// Parses the to . /// /// The source object. /// The . /// - public static OpenApiReservedParameterExtension Parse(IOpenApiAny source) + public static OpenApiReservedParameterExtension Parse(JsonNode source) { - if (source is not OpenApiBoolean rawBoolean) return null; + if (source is not JsonNode rawBoolean) return null; return new() { - IsReserved = rawBoolean.Value + IsReserved = rawBoolean.GetValue() }; } } diff --git a/src/Microsoft.OpenApi/Models/JsonSchemaType.cs b/src/Microsoft.OpenApi/Models/JsonSchemaType.cs new file mode 100644 index 000000000..6024aa21b --- /dev/null +++ b/src/Microsoft.OpenApi/Models/JsonSchemaType.cs @@ -0,0 +1,49 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System; + +namespace Microsoft.OpenApi.Models +{ + /// + /// Represents the type of a JSON schema. + /// + [Flags] + public enum JsonSchemaType + { + /// + /// Represents a null type. + /// + Null = 1, + + /// + /// Represents a boolean type. + /// + Boolean = 2, + + /// + /// Represents an integer type. + /// + Integer = 4, + + /// + /// Represents a number type. + /// + Number = 8, + + /// + /// Represents a string type. + /// + String = 16, + + /// + /// Represents an object type. + /// + Object = 32, + + /// + /// Represents an array type. + /// + Array = 64, + } +} diff --git a/src/Microsoft.OpenApi/Models/OpenApiCallback.cs b/src/Microsoft.OpenApi/Models/OpenApiCallback.cs index 98799c9c6..f538d90c0 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiCallback.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiCallback.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. +using System; using System.Collections.Generic; using Microsoft.OpenApi.Expressions; using Microsoft.OpenApi.Interfaces; @@ -11,18 +12,18 @@ namespace Microsoft.OpenApi.Models /// /// Callback Object: A map of possible out-of band callbacks related to the parent operation. /// - public class OpenApiCallback : IOpenApiReferenceable, IOpenApiExtensible, IEffective + public class OpenApiCallback : IOpenApiReferenceable, IOpenApiExtensible { /// /// A Path Item Object used to define a callback request and expected responses. /// - public Dictionary PathItems { get; set; } + public virtual Dictionary PathItems { get; set; } = new(); /// /// Indicates if object is populated with data or is just a reference to the data /// - public bool UnresolvedReference { get; set; } + public virtual bool UnresolvedReference { get; set; } /// /// Reference pointer. @@ -32,7 +33,7 @@ public class OpenApiCallback : IOpenApiReferenceable, IOpenApiExtensible, IEffec /// /// This object MAY be extended with Specification Extensions. /// - public IDictionary Extensions { get; set; } = new Dictionary(); + public virtual IDictionary Extensions { get; set; } = new Dictionary(); /// /// Parameter-less constructor @@ -60,71 +61,44 @@ public void AddPathItem(RuntimeExpression expression, OpenApiPathItem pathItem) Utils.CheckArgumentNull(expression); Utils.CheckArgumentNull(pathItem); - if (PathItems == null) - { - PathItems = new(); - } + PathItems ??= new(); PathItems.Add(expression, pathItem); } /// - /// Serialize to Open Api v3.0 + /// Serialize to Open Api v3.1 /// - public void SerializeAsV3(IOpenApiWriter writer) + /// + /// + public virtual void SerializeAsV31(IOpenApiWriter writer) { - Utils.CheckArgumentNull(writer); - - var target = this; - - if (Reference != null) - { - if (!writer.GetSettings().ShouldInlineReference(Reference)) - { - Reference.SerializeAsV3(writer); - return; - } - else - { - target = GetEffective(Reference.HostDocument); - } - } - target.SerializeAsV3WithoutReference(writer); + SerializeInternal(writer, OpenApiSpecVersion.OpenApi3_1, (writer, element) => element.SerializeAsV31(writer)); } /// - /// Returns an effective OpenApiCallback object based on the presence of a $ref + /// Serialize to Open Api v3.0 /// - /// The host OpenApiDocument that contains the reference. - /// OpenApiCallback - public OpenApiCallback GetEffective(OpenApiDocument doc) + public virtual void SerializeAsV3(IOpenApiWriter writer) { - if (this.Reference != null) - { - return doc.ResolveReferenceTo(this.Reference); - } - else - { - return this; - } + SerializeInternal(writer, OpenApiSpecVersion.OpenApi3_0, (writer, element) => element.SerializeAsV3(writer)); } - /// - /// Serialize to OpenAPI V3 document without using reference. - /// - - public void SerializeAsV3WithoutReference(IOpenApiWriter writer) + internal void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion version, + Action callback) { + Utils.CheckArgumentNull(writer); + writer.WriteStartObject(); // path items foreach (var item in PathItems) { - writer.WriteRequiredObject(item.Key.Expression, item.Value, (w, p) => p.SerializeAsV3(w)); + writer.WriteRequiredObject(item.Key.Expression, item.Value, callback); } // extensions - writer.WriteExtensions(Extensions, OpenApiSpecVersion.OpenApi3_0); + writer.WriteExtensions(Extensions, version); writer.WriteEndObject(); } @@ -136,14 +110,5 @@ public void SerializeAsV2(IOpenApiWriter writer) { // Callback object does not exist in V2. } - - /// - /// Serialize to OpenAPI V2 document without using reference. - /// - - public void SerializeAsV2WithoutReference(IOpenApiWriter writer) - { - // Callback object does not exist in V2. - } } } diff --git a/src/Microsoft.OpenApi/Models/OpenApiComponents.cs b/src/Microsoft.OpenApi/Models/OpenApiComponents.cs index 52e8cac14..f672b7dd1 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiComponents.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiComponents.cs @@ -1,11 +1,15 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. +using System; using System.Collections.Generic; using System.Linq; using Microsoft.OpenApi.Interfaces; +using Microsoft.OpenApi.Models.References; using Microsoft.OpenApi.Writers; +#nullable enable + namespace Microsoft.OpenApi.Models { /// @@ -16,55 +20,60 @@ public class OpenApiComponents : IOpenApiSerializable, IOpenApiExtensible /// /// An object to hold reusable Objects. /// - public IDictionary Schemas { get; set; } = new Dictionary(); + public IDictionary? Schemas { get; set; } = new Dictionary(); /// /// An object to hold reusable Objects. /// - public IDictionary Responses { get; set; } = new Dictionary(); + public virtual IDictionary? Responses { get; set; } = new Dictionary(); /// /// An object to hold reusable Objects. /// - public IDictionary Parameters { get; set; } = + public virtual IDictionary? Parameters { get; set; } = new Dictionary(); /// /// An object to hold reusable Objects. /// - public IDictionary Examples { get; set; } = new Dictionary(); + public virtual IDictionary? Examples { get; set; } = new Dictionary(); /// /// An object to hold reusable Objects. /// - public IDictionary RequestBodies { get; set; } = + public virtual IDictionary? RequestBodies { get; set; } = new Dictionary(); /// /// An object to hold reusable Objects. /// - public IDictionary Headers { get; set; } = new Dictionary(); + public virtual IDictionary? Headers { get; set; } = new Dictionary(); /// /// An object to hold reusable Objects. /// - public IDictionary SecuritySchemes { get; set; } = + public virtual IDictionary? SecuritySchemes { get; set; } = new Dictionary(); /// /// An object to hold reusable Objects. /// - public IDictionary Links { get; set; } = new Dictionary(); + public virtual IDictionary? Links { get; set; } = new Dictionary(); /// /// An object to hold reusable Objects. /// - public IDictionary Callbacks { get; set; } = new Dictionary(); + public virtual IDictionary? Callbacks { get; set; } = new Dictionary(); + + /// + /// An object to hold reusable Object. + /// + public virtual IDictionary? PathItems { get; set; } = new Dictionary(); /// /// This object MAY be extended with Specification Extensions. /// - public IDictionary Extensions { get; set; } = new Dictionary(); + public virtual IDictionary? Extensions { get; set; } = new Dictionary(); /// /// Parameter-less constructor @@ -74,7 +83,7 @@ public OpenApiComponents() { } /// /// Initializes a copy of an object /// - public OpenApiComponents(OpenApiComponents components) + public OpenApiComponents(OpenApiComponents? components) { Schemas = components?.Schemas != null ? new Dictionary(components.Schemas) : null; Responses = components?.Responses != null ? new Dictionary(components.Responses) : null; @@ -85,13 +94,15 @@ public OpenApiComponents(OpenApiComponents components) SecuritySchemes = components?.SecuritySchemes != null ? new Dictionary(components.SecuritySchemes) : null; Links = components?.Links != null ? new Dictionary(components.Links) : null; Callbacks = components?.Callbacks != null ? new Dictionary(components.Callbacks) : null; + PathItems = components?.PathItems != null ? new Dictionary(components.PathItems) : null; Extensions = components?.Extensions != null ? new Dictionary(components.Extensions) : null; } /// - /// Serialize to Open Api v3.0. + /// Serialize to Open API v3.1. /// - public void SerializeAsV3(IOpenApiWriter writer) + /// + public void SerializeAsV31(IOpenApiWriter writer) { Utils.CheckArgumentNull(writer); @@ -99,24 +110,59 @@ public void SerializeAsV3(IOpenApiWriter writer) // however if they have cycles, then we will need a component rendered if (writer.GetSettings().InlineLocalReferences) { - var loops = writer.GetSettings().LoopDetector.Loops; - writer.WriteStartObject(); - if (loops.TryGetValue(typeof(OpenApiSchema), out var schemas)) - { - var openApiSchemas = schemas.Cast().Distinct().ToList() - .ToDictionary(k => k.Reference.Id); + RenderComponents(writer, (writer, element) => element.SerializeAsV31(writer)); + return; + } + + writer.WriteStartObject(); - writer.WriteOptionalMap( - OpenApiConstants.Schemas, - Schemas, - (w, _, component) => component.SerializeAsV3WithoutReference(w)); + // pathItems - only present in v3.1 + writer.WriteOptionalMap( + OpenApiConstants.PathItems, + PathItems, + (w, key, component) => + { + if (component is OpenApiPathItemReference reference) + { + reference.SerializeAsV31(w); + } + else + { + component.SerializeAsV31(w); } - writer.WriteEndObject(); + }); + + SerializeInternal(writer, OpenApiSpecVersion.OpenApi3_1, (writer, element) => element.SerializeAsV31(writer), + (writer, referenceElement) => referenceElement.SerializeAsV31(writer)); + } + + /// + /// Serialize to v3.0 + /// + /// + public void SerializeAsV3(IOpenApiWriter writer) + { + Utils.CheckArgumentNull(writer); + + // If references have been inlined we don't need the to render the components section + // however if they have cycles, then we will need a component rendered + if (writer.GetSettings().InlineLocalReferences) + { + RenderComponents(writer, (writer, element) => element.SerializeAsV3(writer)); return; } writer.WriteStartObject(); + SerializeInternal(writer, OpenApiSpecVersion.OpenApi3_0, (writer, element) => element.SerializeAsV3(writer), + (writer, referenceElement) => referenceElement.SerializeAsV3(writer)); + } + /// + /// Serialize . + /// + private void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion version, + Action callback, Action action) + { // Serialize each referenceable object as full object without reference if the reference in the object points to itself. // If the reference exists but points to other objects, the object is serialized to just that reference. @@ -126,14 +172,13 @@ public void SerializeAsV3(IOpenApiWriter writer) Schemas, (w, key, component) => { - if (component.Reference is {Type: ReferenceType.Schema} && - component.Reference.Id == key) + if (component is OpenApiSchemaReference reference) { - component.SerializeAsV3WithoutReference(w); + action(w, reference); } else { - component.SerializeAsV3(w); + callback(w, component); } }); @@ -143,14 +188,13 @@ public void SerializeAsV3(IOpenApiWriter writer) Responses, (w, key, component) => { - if (component.Reference is {Type: ReferenceType.Response} && - component.Reference.Id == key) + if (component is OpenApiResponseReference reference) { - component.SerializeAsV3WithoutReference(w); + action(w, reference); } else { - component.SerializeAsV3(w); + callback(w, component); } }); @@ -160,14 +204,13 @@ public void SerializeAsV3(IOpenApiWriter writer) Parameters, (w, key, component) => { - if (component.Reference is {Type: ReferenceType.Parameter} && - component.Reference.Id == key) + if (component is OpenApiParameterReference reference) { - component.SerializeAsV3WithoutReference(w); + action(w, reference); } else { - component.SerializeAsV3(w); + callback(w, component); } }); @@ -177,14 +220,13 @@ public void SerializeAsV3(IOpenApiWriter writer) Examples, (w, key, component) => { - if (component.Reference is {Type: ReferenceType.Example} && - component.Reference.Id == key) + if (component is OpenApiExampleReference reference) { - component.SerializeAsV3WithoutReference(w); + action(w, reference); } else { - component.SerializeAsV3(w); + callback(w, component); } }); @@ -194,14 +236,13 @@ public void SerializeAsV3(IOpenApiWriter writer) RequestBodies, (w, key, component) => { - if (component.Reference is {Type: ReferenceType.RequestBody} && - component.Reference.Id == key) + if (component is OpenApiRequestBodyReference reference) { - component.SerializeAsV3WithoutReference(w); + action(w, reference); } else { - component.SerializeAsV3(w); + callback(w, component); } }); @@ -211,14 +252,13 @@ public void SerializeAsV3(IOpenApiWriter writer) Headers, (w, key, component) => { - if (component.Reference is {Type: ReferenceType.Header} && - component.Reference.Id == key) + if (component is OpenApiHeaderReference reference) { - component.SerializeAsV3WithoutReference(w); + action(w, reference); } else { - component.SerializeAsV3(w); + callback(w, component); } }); @@ -228,14 +268,13 @@ public void SerializeAsV3(IOpenApiWriter writer) SecuritySchemes, (w, key, component) => { - if (component.Reference is {Type: ReferenceType.SecurityScheme} && - component.Reference.Id == key) + if (component is OpenApiSecuritySchemeReference reference) { - component.SerializeAsV3WithoutReference(w); + action(w, reference); } else { - component.SerializeAsV3(w); + callback(w, component); } }); @@ -245,14 +284,13 @@ public void SerializeAsV3(IOpenApiWriter writer) Links, (w, key, component) => { - if (component.Reference is {Type: ReferenceType.Link} && - component.Reference.Id == key) + if (component is OpenApiLinkReference reference) { - component.SerializeAsV3WithoutReference(w); + action(w, reference); } else { - component.SerializeAsV3(w); + callback(w, component); } }); @@ -262,20 +300,29 @@ public void SerializeAsV3(IOpenApiWriter writer) Callbacks, (w, key, component) => { - if (component.Reference is {Type: ReferenceType.Callback} && - component.Reference.Id == key) + if (component is OpenApiCallbackReference reference) { - component.SerializeAsV3WithoutReference(w); + action(w, reference); } else { - component.SerializeAsV3(w); + callback(w, component); } }); // extensions - writer.WriteExtensions(Extensions, OpenApiSpecVersion.OpenApi3_0); + writer.WriteExtensions(Extensions, version); + writer.WriteEndObject(); + } + private void RenderComponents(IOpenApiWriter writer, Action callback) + { + var loops = writer.GetSettings().LoopDetector.Loops; + writer.WriteStartObject(); + if (loops.TryGetValue(typeof(OpenApiSchema), out List schemas)) + { + writer.WriteOptionalMap(OpenApiConstants.Schemas, Schemas, callback); + } writer.WriteEndObject(); } diff --git a/src/Microsoft.OpenApi/Models/OpenApiConstants.cs b/src/Microsoft.OpenApi/Models/OpenApiConstants.cs index 73de74228..c629f78be 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiConstants.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiConstants.cs @@ -15,11 +15,36 @@ public static class OpenApiConstants /// public const string OpenApi = "openapi"; + /// + /// Field: Json + /// + public const string Json = "json"; + + /// + /// Field: Yaml + /// + public const string Yaml = "yaml"; + + /// + /// Field: Yml + /// + public const string Yml = "yml"; + /// /// Field: Info /// public const string Info = "info"; + /// + /// Field: JsonSchemaDialect + /// + public const string JsonSchemaDialect = "jsonSchemaDialect"; + + /// + /// Field: Webhooks + /// + public const string Webhooks = "webhooks"; + /// /// Field: Title /// @@ -35,6 +60,66 @@ public static class OpenApiConstants /// public const string Format = "format"; + /// + /// Field: Schema + /// + public const string DollarSchema = "$schema"; + + /// + /// Field: Id + /// + public const string Id = "$id"; + + /// + /// Field: Comment + /// + public const string Comment = "$comment"; + + /// + /// Field: Vocabulary + /// + public const string Vocabulary = "$vocabulary"; + + /// + /// Field: DynamicRef + /// + public const string DynamicRef = "$dynamicRef"; + + /// + /// Field: DynamicAnchor + /// + public const string DynamicAnchor = "$dynamicAnchor"; + + /// + /// Field: RecursiveRef + /// + public const string RecursiveRef = "$recursiveRef"; + + /// + /// Field: RecursiveAnchor + /// + public const string RecursiveAnchor = "$recursiveAnchor"; + + /// + /// Field: Definitions + /// + public const string Defs = "$defs"; + + /// + /// Field: V31ExclusiveMaximum + /// + public const string V31ExclusiveMaximum = "exclusiveMaximum"; + + /// + /// Field: V31ExclusiveMinimum + /// + public const string V31ExclusiveMinimum = "exclusiveMinimum"; + + /// + /// Field: UnevaluatedProperties + /// + public const string UnevaluatedProperties = "unevaluatedProperties"; + /// /// Field: Version /// @@ -75,6 +160,11 @@ public static class OpenApiConstants /// public const string Components = "components"; + /// + /// Field: PathItems + /// + public const string PathItems = "pathItems"; + /// /// Field: Security /// @@ -120,6 +210,11 @@ public static class OpenApiConstants /// public const string Name = "name"; + /// + /// Field: Identifier + /// + public const string Identifier = "identifier"; + /// /// Field: Namespace /// @@ -380,6 +475,11 @@ public static class OpenApiConstants /// public const string Properties = "properties"; + /// + /// Field: Pattern Properties + /// + public const string PatternProperties = "patternProperties"; + /// /// Field: AdditionalProperties /// @@ -580,6 +680,36 @@ public static class OpenApiConstants /// public static readonly Uri defaultUrl = new("http://localhost/"); + /// + /// Field: V3 JsonSchema Reference Uri + /// + public const string V3ReferenceUri = "https://registry/components/schemas/"; + + /// + /// Field: V2 JsonSchema Reference Uri + /// + public const string V2ReferenceUri = "https://registry/definitions/"; + + /// + /// The default registry uri for OpenApi documents and workspaces + /// + public const string BaseRegistryUri = "https://openapi.net/"; + + /// + /// The components path segment in a $ref value. + /// + public const string ComponentsSegment = "/components/"; + + /// + /// Field: Null + /// + public const string Null = "null"; + + /// + /// Field: Nullable extension + /// + public const string NullableExtension = "x-nullable"; + #region V2.0 /// diff --git a/src/Microsoft.OpenApi/Models/OpenApiContact.cs b/src/Microsoft.OpenApi/Models/OpenApiContact.cs index 365b96807..15d67cc76 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiContact.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiContact.cs @@ -50,6 +50,15 @@ public OpenApiContact(OpenApiContact contact) Extensions = contact?.Extensions != null ? new Dictionary(contact.Extensions) : null; } + /// + /// Serialize to Open Api v3.1 + /// + /// + public void SerializeAsV31(IOpenApiWriter writer) + { + WriteInternal(writer, OpenApiSpecVersion.OpenApi3_1); + } + /// /// Serialize to Open Api v3.0 /// @@ -68,6 +77,8 @@ public void SerializeAsV2(IOpenApiWriter writer) private void WriteInternal(IOpenApiWriter writer, OpenApiSpecVersion specVersion) { + Utils.CheckArgumentNull(writer); + writer.WriteStartObject(); // name diff --git a/src/Microsoft.OpenApi/Models/OpenApiDiscriminator.cs b/src/Microsoft.OpenApi/Models/OpenApiDiscriminator.cs index bb98be623..342025f9f 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiDiscriminator.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiDiscriminator.cs @@ -10,7 +10,7 @@ namespace Microsoft.OpenApi.Models /// /// Discriminator object. /// - public class OpenApiDiscriminator : IOpenApiSerializable + public class OpenApiDiscriminator : IOpenApiSerializable, IOpenApiExtensible { /// /// REQUIRED. The name of the property in the payload that will hold the discriminator value. @@ -22,6 +22,11 @@ public class OpenApiDiscriminator : IOpenApiSerializable /// public IDictionary Mapping { get; set; } = new Dictionary(); + /// + /// This object MAY be extended with Specification Extensions. + /// + public IDictionary Extensions { get; set; } = new Dictionary(); + /// /// Parameter-less constructor /// @@ -34,12 +39,38 @@ public OpenApiDiscriminator(OpenApiDiscriminator discriminator) { PropertyName = discriminator?.PropertyName ?? PropertyName; Mapping = discriminator?.Mapping != null ? new Dictionary(discriminator.Mapping) : null; + Extensions = discriminator?.Extensions != null ? new Dictionary(discriminator.Extensions) : null; + } + + /// + /// Serialize to Open Api v3.1 + /// + /// + public void SerializeAsV31(IOpenApiWriter writer) + { + SerializeInternal(writer); + + // extensions + writer.WriteExtensions(Extensions, OpenApiSpecVersion.OpenApi3_1); + + writer.WriteEndObject(); } /// /// Serialize to Open Api v3.0 /// public void SerializeAsV3(IOpenApiWriter writer) + { + SerializeInternal(writer); + + writer.WriteEndObject(); + } + + /// + /// Serialize to Open Api v3.0 + /// + /// + private void SerializeInternal(IOpenApiWriter writer) { Utils.CheckArgumentNull(writer); @@ -50,8 +81,6 @@ public void SerializeAsV3(IOpenApiWriter writer) // mapping writer.WriteOptionalMap(OpenApiConstants.Mapping, Mapping, (w, s) => w.WriteValue(s)); - - writer.WriteEndObject(); } /// diff --git a/src/Microsoft.OpenApi/Models/OpenApiDocument.cs b/src/Microsoft.OpenApi/Models/OpenApiDocument.cs index 1a7035793..0baf31e68 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiDocument.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiDocument.cs @@ -7,12 +7,17 @@ using System.Linq; using System.Security.Cryptography; using System.Text; -using Microsoft.OpenApi.Exceptions; +using System.Threading; +using System.Threading.Tasks; using Microsoft.OpenApi.Extensions; using Microsoft.OpenApi.Interfaces; +using Microsoft.OpenApi.Models.References; +using Microsoft.OpenApi.Reader; using Microsoft.OpenApi.Services; using Microsoft.OpenApi.Writers; +#nullable enable + namespace Microsoft.OpenApi.Models { /// @@ -21,49 +26,62 @@ namespace Microsoft.OpenApi.Models public class OpenApiDocument : IOpenApiSerializable, IOpenApiExtensible, IOpenApiAnnotatable { /// - /// Related workspace containing OpenApiDocuments that are referenced in this document + /// Related workspace containing components that are referenced in a document /// - public OpenApiWorkspace Workspace { get; set; } + public OpenApiWorkspace? Workspace { get; set; } /// /// REQUIRED. Provides metadata about the API. The metadata MAY be used by tooling as required. /// public OpenApiInfo Info { get; set; } + /// + /// The default value for the $schema keyword within Schema Objects contained within this OAS document. This MUST be in the form of a URI. + /// + public string? JsonSchemaDialect { get; set; } + /// /// An array of Server Objects, which provide connectivity information to a target server. /// - public IList Servers { get; set; } = new List(); + public IList? Servers { get; set; } = new List(); /// /// REQUIRED. The available paths and operations for the API. /// public OpenApiPaths Paths { get; set; } + /// + /// The incoming webhooks that MAY be received as part of this API and that the API consumer MAY choose to implement. + /// A map of requests initiated other than by an API call, for example by an out of band registration. + /// The key name is a unique string to refer to each webhook, while the (optionally referenced) Path Item Object describes a request that may be initiated by the API provider and the expected responses + /// + public IDictionary? Webhooks { get; set; } = new Dictionary(); + /// /// An element to hold various schemas for the specification. /// - public OpenApiComponents Components { get; set; } + public OpenApiComponents? Components { get; set; } /// /// A declaration of which security mechanisms can be used across the API. /// - public IList SecurityRequirements { get; set; } = new List(); + public IList? SecurityRequirements { get; set; } = + new List(); /// /// A list of tags used by the specification with additional metadata. /// - public IList Tags { get; set; } = new List(); + public IList? Tags { get; set; } = new List(); /// /// Additional external documentation. /// - public OpenApiExternalDocs ExternalDocs { get; set; } + public OpenApiExternalDocs? ExternalDocs { get; set; } /// /// This object MAY be extended with Specification Extensions. /// - public IDictionary Extensions { get; set; } = new Dictionary(); + public IDictionary? Extensions { get; set; } = new Dictionary(); /// /// The unique hash code of the generated OpenAPI document @@ -71,28 +89,79 @@ public class OpenApiDocument : IOpenApiSerializable, IOpenApiExtensible, IOpenAp public string HashCode => GenerateHashValue(this); /// - public IDictionary Annotations { get; set; } + public IDictionary? Annotations { get; set; } /// - /// Parameter-less constructor + /// Implements IBaseDocument /// - public OpenApiDocument() {} + public Uri BaseUri { get; } + /// + /// Parameter-less constructor + /// + public OpenApiDocument() + { + Workspace = new OpenApiWorkspace(); + BaseUri = new(OpenApiConstants.BaseRegistryUri + Guid.NewGuid()); + Info = new OpenApiInfo(); + Paths = new OpenApiPaths(); + } + /// /// Initializes a copy of an an object /// - public OpenApiDocument(OpenApiDocument document) + public OpenApiDocument(OpenApiDocument? document) { Workspace = document?.Workspace != null ? new(document?.Workspace) : null; - Info = document?.Info != null ? new(document?.Info) : null; + Info = document?.Info != null ? new(document?.Info) : new OpenApiInfo(); + JsonSchemaDialect = document?.JsonSchemaDialect ?? JsonSchemaDialect; Servers = document?.Servers != null ? new List(document.Servers) : null; - Paths = document?.Paths != null ? new(document?.Paths) : null; + Paths = document?.Paths != null ? new(document?.Paths) : new OpenApiPaths(); + Webhooks = document?.Webhooks != null ? new Dictionary(document.Webhooks) : null; Components = document?.Components != null ? new(document?.Components) : null; SecurityRequirements = document?.SecurityRequirements != null ? new List(document.SecurityRequirements) : null; Tags = document?.Tags != null ? new List(document.Tags) : null; ExternalDocs = document?.ExternalDocs != null ? new(document?.ExternalDocs) : null; Extensions = document?.Extensions != null ? new Dictionary(document.Extensions) : null; Annotations = document?.Annotations != null ? new Dictionary(document.Annotations) : null; + BaseUri = document?.BaseUri != null ? document.BaseUri : new(OpenApiConstants.BaseRegistryUri + Guid.NewGuid()); + } + + /// + /// Serialize to Open API v3.1 document. + /// + /// + public void SerializeAsV31(IOpenApiWriter writer) + { + Utils.CheckArgumentNull(writer); + + writer.WriteStartObject(); + + // openApi; + writer.WriteProperty(OpenApiConstants.OpenApi, "3.1.0"); + + // jsonSchemaDialect + writer.WriteProperty(OpenApiConstants.JsonSchemaDialect, JsonSchemaDialect); + + SerializeInternal(writer, OpenApiSpecVersion.OpenApi3_1, (w, element) => element.SerializeAsV31(w)); + + // webhooks + writer.WriteOptionalMap( + OpenApiConstants.Webhooks, + Webhooks, + (w, key, component) => + { + if (component is OpenApiPathItemReference reference) + { + reference.SerializeAsV31(w); + } + else + { + component.SerializeAsV31(w); + } + }); + + writer.WriteEndObject(); } /// @@ -106,35 +175,45 @@ public void SerializeAsV3(IOpenApiWriter writer) // openapi writer.WriteProperty(OpenApiConstants.OpenApi, "3.0.1"); + SerializeInternal(writer, OpenApiSpecVersion.OpenApi3_0, (w, element) => element.SerializeAsV3(w)); + writer.WriteEndObject(); + } + /// + /// Serialize + /// + /// + /// + /// + private void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion version, + Action callback) + { // info - writer.WriteRequiredObject(OpenApiConstants.Info, Info, (w, i) => i.SerializeAsV3(w)); + writer.WriteRequiredObject(OpenApiConstants.Info, Info, callback); // servers - writer.WriteOptionalCollection(OpenApiConstants.Servers, Servers, (w, s) => s.SerializeAsV3(w)); + writer.WriteOptionalCollection(OpenApiConstants.Servers, Servers, callback); - // paths - writer.WriteRequiredObject(OpenApiConstants.Paths, Paths, (w, p) => p.SerializeAsV3(w)); + // paths + writer.WriteRequiredObject(OpenApiConstants.Paths, Paths, callback); // components - writer.WriteOptionalObject(OpenApiConstants.Components, Components, (w, c) => c.SerializeAsV3(w)); + writer.WriteOptionalObject(OpenApiConstants.Components, Components, callback); // security writer.WriteOptionalCollection( OpenApiConstants.Security, SecurityRequirements, - (w, s) => s.SerializeAsV3(w)); + callback); // tags - writer.WriteOptionalCollection(OpenApiConstants.Tags, Tags, (w, t) => t.SerializeAsV3WithoutReference(w)); + writer.WriteOptionalCollection(OpenApiConstants.Tags, Tags, (w, t) => callback(w, t)); // external docs - writer.WriteOptionalObject(OpenApiConstants.ExternalDocs, ExternalDocs, (w, e) => e.SerializeAsV3(w)); + writer.WriteOptionalObject(OpenApiConstants.ExternalDocs, ExternalDocs, callback); // extensions - writer.WriteExtensions(Extensions, OpenApiSpecVersion.OpenApi3_0); - - writer.WriteEndObject(); + writer.WriteExtensions(Extensions, version); } /// @@ -164,10 +243,10 @@ public void SerializeAsV2(IOpenApiWriter writer) { var loops = writer.GetSettings().LoopDetector.Loops; - if (loops.TryGetValue(typeof(OpenApiSchema), out var schemas)) + if (loops.TryGetValue(typeof(OpenApiSchema), out List schemas)) { var openApiSchemas = schemas.Cast().Distinct().ToList() - .ToDictionary(k => k.Reference.Id); + .ToDictionary(k => k.Reference.Id); foreach (var schema in openApiSchemas.Values.ToList()) { @@ -177,7 +256,7 @@ public void SerializeAsV2(IOpenApiWriter writer) writer.WriteOptionalMap( OpenApiConstants.Definitions, openApiSchemas, - (w, _, component) => component.SerializeAsV2WithoutReference(w)); + (w, _, component) => component.SerializeAsV2(w)); } } else @@ -190,95 +269,92 @@ public void SerializeAsV2(IOpenApiWriter writer) Components?.Schemas, (w, key, component) => { - if (component.Reference is {Type: ReferenceType.Schema} && - component.Reference.Id == key) + if (component is OpenApiSchemaReference reference) { - component.SerializeAsV2WithoutReference(w); + reference.SerializeAsV2(w); } else { component.SerializeAsV2(w); } }); - } - // parameters - var parameters = Components?.Parameters != null - ? new(Components.Parameters) - : new Dictionary(); - if (Components?.RequestBodies != null) - { - foreach (var requestBody in Components.RequestBodies.Where(b => !parameters.ContainsKey(b.Key))) - { - parameters.Add(requestBody.Key, requestBody.Value.ConvertToBodyParameter()); - } - } - writer.WriteOptionalMap( - OpenApiConstants.Parameters, - parameters, - (w, key, component) => - { - if (component.Reference is {Type: ReferenceType.Parameter} && - component.Reference.Id == key) - { - component.SerializeAsV2WithoutReference(w); - } - else - { - component.SerializeAsV2(w); - } - }); + // parameters + var parameters = Components?.Parameters != null + ? new Dictionary(Components.Parameters) + : new Dictionary(); - // responses - writer.WriteOptionalMap( - OpenApiConstants.Responses, - Components?.Responses, - (w, key, component) => + if (Components?.RequestBodies != null) { - if (component.Reference is {Type: ReferenceType.Response} && - component.Reference.Id == key) + foreach (var requestBody in Components.RequestBodies.Where(b => !parameters.ContainsKey(b.Key))) { - component.SerializeAsV2WithoutReference(w); + parameters.Add(requestBody.Key, requestBody.Value.ConvertToBodyParameter()); } - else + } + writer.WriteOptionalMap( + OpenApiConstants.Parameters, + parameters, + (w, key, component) => { - component.SerializeAsV2(w); - } - }); + if (component is OpenApiParameterReference reference) + { + reference.SerializeAsV2(w); + } + else + { + component.SerializeAsV2(w); + } + }); - // securityDefinitions - writer.WriteOptionalMap( - OpenApiConstants.SecurityDefinitions, - Components?.SecuritySchemes, - (w, key, component) => - { - if (component.Reference is {Type: ReferenceType.SecurityScheme} && - component.Reference.Id == key) + // responses + writer.WriteOptionalMap( + OpenApiConstants.Responses, + Components?.Responses, + (w, key, component) => { - component.SerializeAsV2WithoutReference(w); - } - else + if (component is OpenApiResponseReference reference) + { + reference.SerializeAsV2(w); + } + else + { + component.SerializeAsV2(w); + } + }); + + // securityDefinitions + writer.WriteOptionalMap( + OpenApiConstants.SecurityDefinitions, + Components?.SecuritySchemes, + (w, key, component) => { - component.SerializeAsV2(w); - } - }); + if (component is OpenApiSecuritySchemeReference reference) + { + reference.SerializeAsV2(w); + } + else + { + component.SerializeAsV2(w); + } + }); - // security - writer.WriteOptionalCollection( - OpenApiConstants.Security, - SecurityRequirements, - (w, s) => s.SerializeAsV2(w)); + // security + writer.WriteOptionalCollection( + OpenApiConstants.Security, + SecurityRequirements, + (w, s) => s.SerializeAsV2(w)); - // tags - writer.WriteOptionalCollection(OpenApiConstants.Tags, Tags, (w, t) => t.SerializeAsV2WithoutReference(w)); + // tags + writer.WriteOptionalCollection(OpenApiConstants.Tags, Tags, (w, t) => t.SerializeAsV2(w)); - // externalDocs - writer.WriteOptionalObject(OpenApiConstants.ExternalDocs, ExternalDocs, (w, e) => e.SerializeAsV2(w)); + // externalDocs + writer.WriteOptionalObject(OpenApiConstants.ExternalDocs, ExternalDocs, (w, e) => e.SerializeAsV2(w)); - // extensions - writer.WriteExtensions(Extensions, OpenApiSpecVersion.OpenApi2_0); + // extensions + writer.WriteExtensions(Extensions, OpenApiSpecVersion.OpenApi2_0); - writer.WriteEndObject(); + writer.WriteEndObject(); + } } private static string ParseServerUrl(OpenApiServer server) @@ -286,7 +362,7 @@ private static string ParseServerUrl(OpenApiServer server) return server.ReplaceServerUrlVariables(new Dictionary(0)); } - private static void WriteHostInfoV2(IOpenApiWriter writer, IList servers) + private static void WriteHostInfoV2(IOpenApiWriter writer, IList? servers) { if (servers == null || !servers.Any()) { @@ -313,7 +389,8 @@ private static void WriteHostInfoV2(IOpenApiWriter writer, IList { writer.WriteProperty(OpenApiConstants.BasePath, firstServerUrl.AbsolutePath); } - } else + } + else { var relativeUrl = firstServerUrl.OriginalString; if (relativeUrl.StartsWith("//")) @@ -353,24 +430,19 @@ private static void WriteHostInfoV2(IOpenApiWriter writer, IList } /// - /// Walk the OpenApiDocument and resolve unresolved references + /// Walks the OpenApiDocument and sets the host document for all IOpenApiReferenceable objects /// - /// - /// This method will be replaced by a LoadExternalReferences in the next major update to this library. - /// Resolving references at load time is going to go away. - /// - public IEnumerable ResolveReferences() + public void SetReferenceHostDocument() { - var resolver = new OpenApiReferenceResolver(this, false); + var resolver = new ReferenceHostDocumentSetter(this); var walker = new OpenApiWalker(resolver); walker.Walk(this); - return resolver.Errors; } /// /// Load the referenced object from a object /// - internal T ResolveReferenceTo(OpenApiReference reference) where T : class, IOpenApiReferenceable + internal T? ResolveReferenceTo(OpenApiReference reference) where T : class, IOpenApiReferenceable { if (reference.IsExternal) { @@ -382,14 +454,6 @@ internal T ResolveReferenceTo(OpenApiReference reference) where T : class, IO } } - /// - /// Load the referenced object from a object - /// - public IOpenApiReferenceable ResolveReference(OpenApiReference reference) - { - return ResolveReference(reference, false); - } - /// /// Takes in an OpenApi document instance and generates its hash value /// @@ -427,23 +491,13 @@ private static string ConvertByteArrayToString(byte[] hash) /// /// Load the referenced object from a object /// - internal IOpenApiReferenceable ResolveReference(OpenApiReference reference, bool useExternal) + internal IOpenApiReferenceable? ResolveReference(OpenApiReference? reference, bool useExternal) { if (reference == null) { return null; } - // Todo: Verify if we need to check to see if this external reference is actually targeted at this document. - if (useExternal) - { - if (this.Workspace == null) - { - throw new ArgumentException(Properties.SRResource.WorkspaceRequredForExternalReferenceResolution); - } - return this.Workspace.ResolveReference(reference); - } - if (!reference.Type.HasValue) { throw new ArgumentException(Properties.SRResource.LocalReferenceRequiresType); @@ -452,7 +506,7 @@ internal IOpenApiReferenceable ResolveReference(OpenApiReference reference, bool // Special case for Tag if (reference.Type == ReferenceType.Tag) { - foreach (var tag in this.Tags) + foreach (var tag in this.Tags ?? Enumerable.Empty()) { if (tag.Name == reference.Id) { @@ -464,61 +518,118 @@ internal IOpenApiReferenceable ResolveReference(OpenApiReference reference, bool return null; } - if (this.Components == null) + string uriLocation; + if (reference.Id.Contains("/")) // this means its a URL reference { - throw new OpenApiException(string.Format(Properties.SRResource.InvalidReferenceId, reference.Id)); + uriLocation = reference.Id; } - - try + else { - switch (reference.Type) - { - case ReferenceType.Schema: - return this.Components.Schemas[reference.Id]; + string relativePath = OpenApiConstants.ComponentsSegment + reference.Type.GetDisplayName() + "/" + reference.Id; - case ReferenceType.Response: - return this.Components.Responses[reference.Id]; - - case ReferenceType.Parameter: - return this.Components.Parameters[reference.Id]; + uriLocation = useExternal + ? Workspace?.GetDocumentId(reference.ExternalResource)?.OriginalString + relativePath + : BaseUri + relativePath; + } - case ReferenceType.Example: - return this.Components.Examples[reference.Id]; + return Workspace?.ResolveReference(uriLocation); + } - case ReferenceType.RequestBody: - return this.Components.RequestBodies[reference.Id]; + /// + /// Parses a local file path or Url into an Open API document. + /// + /// The path to the OpenAPI file. + /// + /// + public static ReadResult Load(string url, OpenApiReaderSettings? settings = null) + { + return OpenApiModelFactory.Load(url, settings); + } - case ReferenceType.Header: - return this.Components.Headers[reference.Id]; + /// + /// Reads the stream input and parses it into an Open API document. + /// + /// Stream containing OpenAPI description to parse. + /// The OpenAPI format to use during parsing. + /// The OpenApi reader settings. + /// + public static ReadResult Load(Stream stream, + string format, + OpenApiReaderSettings? settings = null) + { + return OpenApiModelFactory.Load(stream, format, settings); + } - case ReferenceType.SecurityScheme: - return this.Components.SecuritySchemes[reference.Id]; + /// + /// Reads the text reader content and parses it into an Open API document. + /// + /// TextReader containing OpenAPI description to parse. + /// The OpenAPI format to use during parsing. + /// The OpenApi reader settings. + /// + public static ReadResult Load(TextReader input, + string format, + OpenApiReaderSettings? settings = null) + { + return OpenApiModelFactory.Load(input, format, settings); + } - case ReferenceType.Link: - return this.Components.Links[reference.Id]; + /// + /// Parses a local file path or Url into an Open API document. + /// + /// The path to the OpenAPI file. + /// The OpenApi reader settings. + /// + public static async Task LoadAsync(string url, OpenApiReaderSettings? settings = null) + { + return await OpenApiModelFactory.LoadAsync(url, settings); + } - case ReferenceType.Callback: - return this.Components.Callbacks[reference.Id]; + /// + /// Reads the stream input and parses it into an Open API document. + /// + /// Stream containing OpenAPI description to parse. + /// The OpenAPI format to use during parsing. + /// The OpenApi reader settings. + /// Propagates information about operation cancelling. + /// + public static async Task LoadAsync(Stream stream, string format, OpenApiReaderSettings? settings = null, CancellationToken cancellationToken = default) + { + return await OpenApiModelFactory.LoadAsync(stream, format, settings, cancellationToken); + } - case ReferenceType.Path: - return this.Paths[reference.Id]; + /// + /// Reads the text reader content and parses it into an Open API document. + /// + /// TextReader containing OpenAPI description to parse. + /// The OpenAPI format to use during parsing. + /// The OpenApi reader settings. + /// + public static async Task LoadAsync(TextReader input, string format, OpenApiReaderSettings? settings = null) + { + return await OpenApiModelFactory.LoadAsync(input, format, settings); + } - default: - throw new OpenApiException(Properties.SRResource.InvalidReferenceType); - } - } - catch (KeyNotFoundException) - { - throw new OpenApiException(string.Format(Properties.SRResource.InvalidReferenceId, reference.Id)); - } + /// + /// Parses a string into a object. + /// + /// The string input. + /// + /// + /// + public static ReadResult Parse(string input, + string? format = null, + OpenApiReaderSettings? settings = null) + { + return OpenApiModelFactory.Parse(input, format, settings); } } internal class FindSchemaReferences : OpenApiVisitorBase { - private Dictionary Schemas; + private Dictionary Schemas = new(); - public static void ResolveSchemas(OpenApiComponents components, Dictionary schemas ) + public static void ResolveSchemas(OpenApiComponents? components, Dictionary schemas) { var visitor = new FindSchemaReferences(); visitor.Schemas = schemas; @@ -546,12 +657,9 @@ public override void Visit(IOpenApiReferenceable referenceable) public override void Visit(OpenApiSchema schema) { // This is needed to handle schemas used in Responses in components - if (schema.Reference != null) + if (schema.Reference != null && !Schemas.ContainsKey(schema.Reference.Id)) { - if (!Schemas.ContainsKey(schema.Reference.Id)) - { - Schemas.Add(schema.Reference.Id, schema); - } + Schemas.Add(schema.Reference.Id, schema); } base.Visit(schema); } diff --git a/src/Microsoft.OpenApi/Models/OpenApiEncoding.cs b/src/Microsoft.OpenApi/Models/OpenApiEncoding.cs index 77d601a22..9ab0e7468 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiEncoding.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiEncoding.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. +using System; using System.Collections.Generic; using Microsoft.OpenApi.Extensions; using Microsoft.OpenApi.Interfaces; @@ -55,7 +56,7 @@ public class OpenApiEncoding : IOpenApiSerializable, IOpenApiExtensible /// /// Parameter-less constructor /// - public OpenApiEncoding() {} + public OpenApiEncoding() { } /// /// Initializes a copy of an object @@ -71,9 +72,28 @@ public OpenApiEncoding(OpenApiEncoding encoding) } /// - /// Serialize to Open Api v3.0. + /// Serialize to Open Api v3.1 + /// + /// + public void SerializeAsV31(IOpenApiWriter writer) + { + SerializeInternal(writer, OpenApiSpecVersion.OpenApi3_1, (writer, element) => element.SerializeAsV31(writer)); + } + + /// + /// Serialize to Open Api v3.0 /// + /// public void SerializeAsV3(IOpenApiWriter writer) + { + SerializeInternal(writer, OpenApiSpecVersion.OpenApi3_0, (writer, element) => element.SerializeAsV3(writer)); + } + + /// + /// Serialize to Open Api v3.0. + /// + private void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion version, + Action callback) { Utils.CheckArgumentNull(writer); @@ -83,7 +103,7 @@ public void SerializeAsV3(IOpenApiWriter writer) writer.WriteProperty(OpenApiConstants.ContentType, ContentType); // headers - writer.WriteOptionalMap(OpenApiConstants.Headers, Headers, (w, h) => h.SerializeAsV3(w)); + writer.WriteOptionalMap(OpenApiConstants.Headers, Headers, callback); // style writer.WriteProperty(OpenApiConstants.Style, Style?.GetDisplayName()); @@ -95,7 +115,7 @@ public void SerializeAsV3(IOpenApiWriter writer) writer.WriteProperty(OpenApiConstants.AllowReserved, AllowReserved, false); // extensions - writer.WriteExtensions(Extensions, OpenApiSpecVersion.OpenApi3_0); + writer.WriteExtensions(Extensions, version); writer.WriteEndObject(); } diff --git a/src/Microsoft.OpenApi/Models/OpenApiExample.cs b/src/Microsoft.OpenApi/Models/OpenApiExample.cs index c78353127..ef8a64b7a 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiExample.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiExample.cs @@ -1,8 +1,11 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. +using System; using System.Collections.Generic; +using System.Text.Json.Nodes; using Microsoft.OpenApi.Any; +using Microsoft.OpenApi.Helpers; using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Writers; @@ -11,25 +14,25 @@ namespace Microsoft.OpenApi.Models /// /// Example Object. /// - public class OpenApiExample : IOpenApiReferenceable, IOpenApiExtensible, IEffective + public class OpenApiExample : IOpenApiReferenceable, IOpenApiExtensible { /// /// Short description for the example. /// - public string Summary { get; set; } + public virtual string Summary { get; set; } /// /// Long description for the example. /// CommonMark syntax MAY be used for rich text representation. /// - public string Description { get; set; } + public virtual string Description { get; set; } /// /// Embedded literal example. The value field and externalValue field are mutually /// exclusive. To represent examples of media types that cannot naturally represented /// in JSON or YAML, use a string value to contain the example, escaping where necessary. /// - public IOpenApiAny Value { get; set; } + public virtual JsonNode Value { get; set; } /// /// A URL that points to the literal example. @@ -37,27 +40,27 @@ public class OpenApiExample : IOpenApiReferenceable, IOpenApiExtensible, IEffect /// included in JSON or YAML documents. /// The value field and externalValue field are mutually exclusive. /// - public string ExternalValue { get; set; } + public virtual string ExternalValue { get; set; } /// /// This object MAY be extended with Specification Extensions. /// - public IDictionary Extensions { get; set; } = new Dictionary(); + public virtual IDictionary Extensions { get; set; } = new Dictionary(); /// /// Reference object. /// - public OpenApiReference Reference { get; set; } + public virtual OpenApiReference Reference { get; set; } /// /// Indicates object is a placeholder reference to an actual object and does not contain valid data. /// - public bool UnresolvedReference { get; set; } + public virtual bool UnresolvedReference { get; set; } = false; /// /// Parameter-less constructor /// - public OpenApiExample() {} + public OpenApiExample() { } /// /// Initializes a copy of object @@ -66,60 +69,29 @@ public OpenApiExample(OpenApiExample example) { Summary = example?.Summary ?? Summary; Description = example?.Description ?? Description; - Value = OpenApiAnyCloneHelper.CloneFromCopyConstructor(example?.Value); + Value = example?.Value != null ? JsonNodeCloneHelper.Clone(example.Value) : null; ExternalValue = example?.ExternalValue ?? ExternalValue; Extensions = example?.Extensions != null ? new Dictionary(example.Extensions) : null; - Reference = example?.Reference != null ? new(example?.Reference) : null; + Reference = example?.Reference != null ? new(example.Reference) : null; UnresolvedReference = example?.UnresolvedReference ?? UnresolvedReference; } /// - /// Serialize to Open Api v3.0 + /// Serialize to Open Api v3.1 /// - public void SerializeAsV3(IOpenApiWriter writer) - { - Utils.CheckArgumentNull(writer); - - var target = this; - - if (Reference != null) - { - if (!writer.GetSettings().ShouldInlineReference(Reference)) - { - Reference.SerializeAsV3(writer); - return; - } - else - { - target = GetEffective(Reference.HostDocument); - } - } - target.SerializeAsV3WithoutReference(writer); - } - - /// - /// Returns an effective OpenApiExample object based on the presence of a $ref - /// - /// The host OpenApiDocument that contains the reference. - /// OpenApiExample - public OpenApiExample GetEffective(OpenApiDocument doc) + /// + public virtual void SerializeAsV31(IOpenApiWriter writer) { - if (this.Reference != null) - { - return doc.ResolveReferenceTo(this.Reference); - } - else - { - return this; - } + SerializeInternal(writer, OpenApiSpecVersion.OpenApi3_1); } /// - /// Serialize to OpenAPI V3 document without using reference. + /// Serialize to Open Api v3.0 /// - public void SerializeAsV3WithoutReference(IOpenApiWriter writer) + /// + public virtual void SerializeAsV3(IOpenApiWriter writer) { - Serialize(writer, OpenApiSpecVersion.OpenApi3_0); + SerializeInternal(writer, OpenApiSpecVersion.OpenApi3_0); } /// @@ -127,8 +99,10 @@ public void SerializeAsV3WithoutReference(IOpenApiWriter writer) /// /// /// - public void Serialize(IOpenApiWriter writer, OpenApiSpecVersion version) + public void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion version) { + Utils.CheckArgumentNull(writer); + writer.WriteStartObject(); // summary @@ -152,17 +126,7 @@ public void Serialize(IOpenApiWriter writer, OpenApiSpecVersion version) /// /// Serialize to Open Api v2.0 /// - public void SerializeAsV2(IOpenApiWriter writer) - { - // Example object of this form does not exist in V2. - // V2 Example object requires knowledge of media type and exists only - // in Response object, so it will be serialized as a part of the Response object. - } - - /// - /// Serialize to OpenAPI V2 document without using reference. - /// - public void SerializeAsV2WithoutReference(IOpenApiWriter writer) + public virtual void SerializeAsV2(IOpenApiWriter writer) { // Example object of this form does not exist in V2. // V2 Example object requires knowledge of media type and exists only diff --git a/src/Microsoft.OpenApi/Models/OpenApiExtensibleDictionary.cs b/src/Microsoft.OpenApi/Models/OpenApiExtensibleDictionary.cs index 8b7e95c41..be2e56a73 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiExtensibleDictionary.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiExtensibleDictionary.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. +using System; using System.Collections.Generic; using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Writers; @@ -28,7 +29,7 @@ protected OpenApiExtensibleDictionary() { } /// The dictionary of . protected OpenApiExtensibleDictionary( Dictionary dictionary = null, - IDictionary extensions = null) : base (dictionary) + IDictionary extensions = null) : base(dictionary) { Extensions = extensions != null ? new Dictionary(extensions) : null; } @@ -38,10 +39,30 @@ protected OpenApiExtensibleDictionary( /// public IDictionary Extensions { get; set; } = new Dictionary(); + + /// + /// Serialize to Open Api v3.1 + /// + /// + public void SerializeAsV31(IOpenApiWriter writer) + { + SerializeInternal(writer, OpenApiSpecVersion.OpenApi3_1, (writer, element) => element.SerializeAsV31(writer)); + } + /// /// Serialize to Open Api v3.0 /// + /// public void SerializeAsV3(IOpenApiWriter writer) + { + SerializeInternal(writer, OpenApiSpecVersion.OpenApi3_0, (writer, element) => element.SerializeAsV3(writer)); + } + + /// + /// Serialize to Open Api v3.0 + /// + private void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion version, + Action callback) { Utils.CheckArgumentNull(writer); @@ -49,10 +70,10 @@ public void SerializeAsV3(IOpenApiWriter writer) foreach (var item in this) { - writer.WriteRequiredObject(item.Key, item.Value, (w, p) => p.SerializeAsV3(w)); + writer.WriteRequiredObject(item.Key, item.Value, callback); } - writer.WriteExtensions(Extensions, OpenApiSpecVersion.OpenApi3_0); + writer.WriteExtensions(Extensions, version); writer.WriteEndObject(); } diff --git a/src/Microsoft.OpenApi/Models/OpenApiExternalDocs.cs b/src/Microsoft.OpenApi/Models/OpenApiExternalDocs.cs index 5720570fa..cceace01d 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiExternalDocs.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiExternalDocs.cs @@ -43,6 +43,14 @@ public OpenApiExternalDocs(OpenApiExternalDocs externalDocs) Extensions = externalDocs?.Extensions != null ? new Dictionary(externalDocs.Extensions) : null; } + /// + /// Serialize to Open Api v3.1. + /// + public void SerializeAsV31(IOpenApiWriter writer) + { + WriteInternal(writer, OpenApiSpecVersion.OpenApi3_1); + } + /// /// Serialize to Open Api v3.0. /// @@ -61,6 +69,8 @@ public void SerializeAsV2(IOpenApiWriter writer) private void WriteInternal(IOpenApiWriter writer, OpenApiSpecVersion specVersion) { + Utils.CheckArgumentNull(writer);; + writer.WriteStartObject(); // description diff --git a/src/Microsoft.OpenApi/Models/OpenApiHeader.cs b/src/Microsoft.OpenApi/Models/OpenApiHeader.cs index 4093775a2..268878b1b 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiHeader.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiHeader.cs @@ -1,9 +1,11 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. +using System; using System.Collections.Generic; -using Microsoft.OpenApi.Any; +using System.Text.Json.Nodes; using Microsoft.OpenApi.Extensions; +using Microsoft.OpenApi.Helpers; using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Writers; @@ -13,12 +15,14 @@ namespace Microsoft.OpenApi.Models /// Header Object. /// The Header Object follows the structure of the Parameter Object. /// - public class OpenApiHeader : IOpenApiReferenceable, IOpenApiExtensible, IEffective + public class OpenApiHeader : IOpenApiReferenceable, IOpenApiExtensible { + private OpenApiSchema _schema; + /// /// Indicates if object is populated with data or is just a reference to the data /// - public bool UnresolvedReference { get; set; } + public virtual bool UnresolvedReference { get; set; } /// /// Reference pointer. @@ -28,68 +32,72 @@ public class OpenApiHeader : IOpenApiReferenceable, IOpenApiExtensible, IEffecti /// /// A brief description of the header. /// - public string Description { get; set; } + public virtual string Description { get; set; } /// /// Determines whether this header is mandatory. /// - public bool Required { get; set; } + public virtual bool Required { get; set; } /// /// Specifies that a header is deprecated and SHOULD be transitioned out of usage. /// - public bool Deprecated { get; set; } + public virtual bool Deprecated { get; set; } /// /// Sets the ability to pass empty-valued headers. /// - public bool AllowEmptyValue { get; set; } + public virtual bool AllowEmptyValue { get; set; } /// /// Describes how the header value will be serialized depending on the type of the header value. /// - public ParameterStyle? Style { get; set; } + public virtual ParameterStyle? Style { get; set; } /// /// When this is true, header values of type array or object generate separate parameters /// for each value of the array or key-value pair of the map. /// - public bool Explode { get; set; } + public virtual bool Explode { get; set; } /// /// Determines whether the header value SHOULD allow reserved characters, as defined by RFC3986. /// - public bool AllowReserved { get; set; } + public virtual bool AllowReserved { get; set; } /// - /// The schema defining the type used for the header. + /// The schema defining the type used for the request body. /// - public OpenApiSchema Schema { get; set; } + public virtual OpenApiSchema Schema + { + get => _schema; + set => _schema = value; + } /// /// Example of the media type. /// - public IOpenApiAny Example { get; set; } + public virtual JsonNode Example { get; set; } /// /// Examples of the media type. /// - public IDictionary Examples { get; set; } = new Dictionary(); + public virtual IDictionary Examples { get; set; } = new Dictionary(); /// /// A map containing the representations for the header. /// - public IDictionary Content { get; set; } = new Dictionary(); + public virtual IDictionary Content { get; set; } = new Dictionary(); /// /// This object MAY be extended with Specification Extensions. /// - public IDictionary Extensions { get; set; } = new Dictionary(); + public virtual IDictionary Extensions { get; set; } = new Dictionary(); /// /// Parameter-less constructor /// - public OpenApiHeader() {} + public OpenApiHeader() { } /// /// Initializes a copy of an object @@ -105,59 +113,34 @@ public OpenApiHeader(OpenApiHeader header) Style = header?.Style ?? Style; Explode = header?.Explode ?? Explode; AllowReserved = header?.AllowReserved ?? AllowReserved; - Schema = header?.Schema != null ? new(header?.Schema) : null; - Example = OpenApiAnyCloneHelper.CloneFromCopyConstructor(header?.Example); + _schema = header?.Schema != null ? new(header.Schema) : null; + Example = header?.Example != null ? JsonNodeCloneHelper.Clone(header.Example) : null; Examples = header?.Examples != null ? new Dictionary(header.Examples) : null; Content = header?.Content != null ? new Dictionary(header.Content) : null; Extensions = header?.Extensions != null ? new Dictionary(header.Extensions) : null; } /// - /// Serialize to Open Api v3.0 + /// Serialize to Open Api v3.1 /// - public void SerializeAsV3(IOpenApiWriter writer) + public virtual void SerializeAsV31(IOpenApiWriter writer) { - Utils.CheckArgumentNull(writer); - - var target = this; - - if (Reference != null) - { - if (!writer.GetSettings().ShouldInlineReference(Reference)) - { - Reference.SerializeAsV3(writer); - return; - } - else - { - target = GetEffective(Reference.HostDocument); - } - } - target.SerializeAsV3WithoutReference(writer); + SerializeInternal(writer, OpenApiSpecVersion.OpenApi3_0, (writer, element) => element.SerializeAsV31(writer)); } /// - /// Returns an effective OpenApiHeader object based on the presence of a $ref + /// Serialize to Open Api v3.0 /// - /// The host OpenApiDocument that contains the reference. - /// OpenApiHeader - public OpenApiHeader GetEffective(OpenApiDocument doc) + public virtual void SerializeAsV3(IOpenApiWriter writer) { - if (this.Reference != null) - { - return doc.ResolveReferenceTo(this.Reference); - } - else - { - return this; - } + SerializeInternal(writer, OpenApiSpecVersion.OpenApi3_0, (writer, element) => element.SerializeAsV3(writer)); } - /// - /// Serialize to OpenAPI V3 document without using reference. - /// - public void SerializeAsV3WithoutReference(IOpenApiWriter writer) + internal virtual void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion version, + Action callback) { + Utils.CheckArgumentNull(writer); + writer.WriteStartObject(); // description @@ -182,52 +165,30 @@ public void SerializeAsV3WithoutReference(IOpenApiWriter writer) writer.WriteProperty(OpenApiConstants.AllowReserved, AllowReserved, false); // schema - writer.WriteOptionalObject(OpenApiConstants.Schema, Schema, (w, s) => s.SerializeAsV3(w)); + writer.WriteOptionalObject(OpenApiConstants.Schema, Schema, callback); // example writer.WriteOptionalObject(OpenApiConstants.Example, Example, (w, s) => w.WriteAny(s)); // examples - writer.WriteOptionalMap(OpenApiConstants.Examples, Examples, (w, e) => e.SerializeAsV3(w)); + writer.WriteOptionalMap(OpenApiConstants.Examples, Examples, callback); // content - writer.WriteOptionalMap(OpenApiConstants.Content, Content, (w, c) => c.SerializeAsV3(w)); + writer.WriteOptionalMap(OpenApiConstants.Content, Content, callback); // extensions - writer.WriteExtensions(Extensions, OpenApiSpecVersion.OpenApi3_0); + writer.WriteExtensions(Extensions, version); writer.WriteEndObject(); } /// - /// Serialize to Open Api v2.0 + /// Serialize to OpenAPI V2 document without using reference. /// - public void SerializeAsV2(IOpenApiWriter writer) + public virtual void SerializeAsV2(IOpenApiWriter writer) { Utils.CheckArgumentNull(writer); - var target = this; - - if (Reference != null) - { - if (!writer.GetSettings().ShouldInlineReference(Reference)) - { - Reference.SerializeAsV2(writer); - return; - } - else - { - target = GetEffective(Reference.HostDocument); - } - } - target.SerializeAsV2WithoutReference(writer); - } - - /// - /// Serialize to OpenAPI V2 document without using reference. - /// - public void SerializeAsV2WithoutReference(IOpenApiWriter writer) - { writer.WriteStartObject(); // description @@ -252,7 +213,7 @@ public void SerializeAsV2WithoutReference(IOpenApiWriter writer) writer.WriteProperty(OpenApiConstants.AllowReserved, AllowReserved, false); // schema - Schema?.WriteAsItemsProperties(writer); + Schema.WriteAsItemsProperties(writer); // example writer.WriteOptionalObject(OpenApiConstants.Example, Example, (w, s) => w.WriteAny(s)); diff --git a/src/Microsoft.OpenApi/Models/OpenApiInfo.cs b/src/Microsoft.OpenApi/Models/OpenApiInfo.cs index 2066830d7..68e37ee20 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiInfo.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiInfo.cs @@ -18,6 +18,11 @@ public class OpenApiInfo : IOpenApiSerializable, IOpenApiExtensible /// public string Title { get; set; } + /// + /// A short summary of the API. + /// + public string Summary { get; set; } + /// /// A short description of the application. /// @@ -51,7 +56,7 @@ public class OpenApiInfo : IOpenApiSerializable, IOpenApiExtensible /// /// Parameter-less constructor /// - public OpenApiInfo() {} + public OpenApiInfo() { } /// /// Initializes a copy of an object @@ -59,6 +64,7 @@ public OpenApiInfo() {} public OpenApiInfo(OpenApiInfo info) { Title = info?.Title ?? Title; + Summary = info?.Summary ?? Summary; Description = info?.Description ?? Description; Version = info?.Version ?? Version; TermsOfService = info?.TermsOfService ?? TermsOfService; @@ -67,13 +73,34 @@ public OpenApiInfo(OpenApiInfo info) Extensions = info?.Extensions != null ? new Dictionary(info.Extensions) : null; } + /// + /// Serialize to Open Api v3.1 + /// + public void SerializeAsV31(IOpenApiWriter writer) + { + SerializeInternal(writer, OpenApiSpecVersion.OpenApi3_1, (writer, element) => element.SerializeAsV31(writer)); + + // summary - present in 3.1 + writer.WriteProperty(OpenApiConstants.Summary, Summary); + writer.WriteEndObject(); + } + /// /// Serialize to Open Api v3.0 /// public void SerializeAsV3(IOpenApiWriter writer) { - Utils.CheckArgumentNull(writer); + SerializeInternal(writer, OpenApiSpecVersion.OpenApi3_0, (writer, element) => element.SerializeAsV3(writer)); + writer.WriteEndObject(); + } + + /// + /// Serialize to Open Api v3.0 + /// + private void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion version, Action callback) + { + Utils.CheckArgumentNull(writer);; writer.WriteStartObject(); // title @@ -86,18 +113,16 @@ public void SerializeAsV3(IOpenApiWriter writer) writer.WriteProperty(OpenApiConstants.TermsOfService, TermsOfService?.OriginalString); // contact object - writer.WriteOptionalObject(OpenApiConstants.Contact, Contact, (w, c) => c.SerializeAsV3(w)); + writer.WriteOptionalObject(OpenApiConstants.Contact, Contact, callback); // license object - writer.WriteOptionalObject(OpenApiConstants.License, License, (w, l) => l.SerializeAsV3(w)); + writer.WriteOptionalObject(OpenApiConstants.License, License, callback); // version writer.WriteProperty(OpenApiConstants.Version, Version); // specification extensions - writer.WriteExtensions(Extensions, OpenApiSpecVersion.OpenApi3_0); - - writer.WriteEndObject(); + writer.WriteExtensions(Extensions, version); } /// @@ -105,7 +130,7 @@ public void SerializeAsV3(IOpenApiWriter writer) /// public void SerializeAsV2(IOpenApiWriter writer) { - Utils.CheckArgumentNull(writer); + Utils.CheckArgumentNull(writer);; writer.WriteStartObject(); diff --git a/src/Microsoft.OpenApi/Models/OpenApiLicense.cs b/src/Microsoft.OpenApi/Models/OpenApiLicense.cs index c629224f8..6a8d4bcf7 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiLicense.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiLicense.cs @@ -18,6 +18,11 @@ public class OpenApiLicense : IOpenApiSerializable, IOpenApiExtensible /// public string Name { get; set; } + /// + /// An SPDX license expression for the API. The identifier field is mutually exclusive of the url field. + /// + public string Identifier { get; set; } + /// /// The URL pointing to the contact information. MUST be in the format of a URL. /// @@ -39,16 +44,28 @@ public OpenApiLicense() { } public OpenApiLicense(OpenApiLicense license) { Name = license?.Name ?? Name; + Identifier = license?.Identifier ?? Identifier; Url = license?.Url != null ? new Uri(license.Url.OriginalString, UriKind.RelativeOrAbsolute) : null; Extensions = license?.Extensions != null ? new Dictionary(license.Extensions) : null; } + /// + /// Serialize to Open Api v3.1 + /// + public void SerializeAsV31(IOpenApiWriter writer) + { + WriteInternal(writer, OpenApiSpecVersion.OpenApi3_1); + writer.WriteProperty(OpenApiConstants.Identifier, Identifier); + writer.WriteEndObject(); + } + /// /// Serialize to Open Api v3.0 /// public void SerializeAsV3(IOpenApiWriter writer) { WriteInternal(writer, OpenApiSpecVersion.OpenApi3_0); + writer.WriteEndObject(); } /// @@ -57,10 +74,12 @@ public void SerializeAsV3(IOpenApiWriter writer) public void SerializeAsV2(IOpenApiWriter writer) { WriteInternal(writer, OpenApiSpecVersion.OpenApi2_0); + writer.WriteEndObject(); } private void WriteInternal(IOpenApiWriter writer, OpenApiSpecVersion specVersion) { + Utils.CheckArgumentNull(writer);; writer.WriteStartObject(); // name @@ -71,8 +90,6 @@ private void WriteInternal(IOpenApiWriter writer, OpenApiSpecVersion specVersion // specification extensions writer.WriteExtensions(Extensions, specVersion); - - writer.WriteEndObject(); } } } diff --git a/src/Microsoft.OpenApi/Models/OpenApiLink.cs b/src/Microsoft.OpenApi/Models/OpenApiLink.cs index b7bdeb3f8..715826c67 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiLink.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiLink.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. +using System; using System.Collections.Generic; using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Writers; @@ -10,50 +11,50 @@ namespace Microsoft.OpenApi.Models /// /// Link Object. /// - public class OpenApiLink : IOpenApiReferenceable, IOpenApiExtensible, IEffective + public class OpenApiLink : IOpenApiReferenceable, IOpenApiExtensible { /// /// A relative or absolute reference to an OAS operation. /// This field is mutually exclusive of the operationId field, and MUST point to an Operation Object. /// - public string OperationRef { get; set; } + public virtual string OperationRef { get; set; } /// /// The name of an existing, resolvable OAS operation, as defined with a unique operationId. /// This field is mutually exclusive of the operationRef field. /// - public string OperationId { get; set; } + public virtual string OperationId { get; set; } /// /// A map representing parameters to pass to an operation as specified with operationId or identified via operationRef. /// - public Dictionary Parameters { get; set; } = + public virtual Dictionary Parameters { get; set; } = new(); /// /// A literal value or {expression} to use as a request body when calling the target operation. /// - public RuntimeExpressionAnyWrapper RequestBody { get; set; } + public virtual RuntimeExpressionAnyWrapper RequestBody { get; set; } /// /// A description of the link. /// - public string Description { get; set; } + public virtual string Description { get; set; } /// /// A server object to be used by the target operation. /// - public OpenApiServer Server { get; set; } + public virtual OpenApiServer Server { get; set; } /// /// This object MAY be extended with Specification Extensions. /// - public IDictionary Extensions { get; set; } = new Dictionary(); + public virtual IDictionary Extensions { get; set; } = new Dictionary(); /// /// Indicates if object is populated with data or is just a reference to the data /// - public bool UnresolvedReference { get; set; } + public virtual bool UnresolvedReference { get; set; } /// /// Reference pointer. @@ -63,7 +64,7 @@ public class OpenApiLink : IOpenApiReferenceable, IOpenApiExtensible, IEffective /// /// Parameterless constructor /// - public OpenApiLink() {} + public OpenApiLink() { } /// /// Initializes a copy of an object @@ -82,51 +83,25 @@ public OpenApiLink(OpenApiLink link) } /// - /// Serialize to Open Api v3.0 + /// Serialize to Open Api v3.1 /// - public void SerializeAsV3(IOpenApiWriter writer) + public virtual void SerializeAsV31(IOpenApiWriter writer) { - Utils.CheckArgumentNull(writer); - - var target = this; - - if (Reference != null) - { - if (!writer.GetSettings().ShouldInlineReference(Reference)) - { - Reference.SerializeAsV3(writer); - return; - } - else - { - target = GetEffective(Reference.HostDocument); - } - } - target.SerializeAsV3WithoutReference(writer); + SerializeInternal(writer, (writer, element) => element.SerializeAsV31(writer)); } /// - /// Returns an effective OpenApiLink object based on the presence of a $ref + /// Serialize to Open Api v3.0 /// - /// The host OpenApiDocument that contains the reference. - /// OpenApiLink - public OpenApiLink GetEffective(OpenApiDocument doc) + public virtual void SerializeAsV3(IOpenApiWriter writer) { - if (this.Reference != null) - { - return doc.ResolveReferenceTo(this.Reference); - } - else - { - return this; - } + SerializeInternal(writer, (writer, element) => element.SerializeAsV3(writer)); } - /// - /// Serialize to OpenAPI V3 document without using reference. - /// - public void SerializeAsV3WithoutReference(IOpenApiWriter writer) + internal virtual void SerializeInternal(IOpenApiWriter writer, Action callback) { + Utils.CheckArgumentNull(writer); + writer.WriteStartObject(); // operationRef @@ -145,7 +120,7 @@ public void SerializeAsV3WithoutReference(IOpenApiWriter writer) writer.WriteProperty(OpenApiConstants.Description, Description); // server - writer.WriteOptionalObject(OpenApiConstants.Server, Server, (w, s) => s.SerializeAsV3(w)); + writer.WriteOptionalObject(OpenApiConstants.Server, Server, callback); // specification extensions writer.WriteExtensions(Extensions, OpenApiSpecVersion.OpenApi3_0); @@ -160,13 +135,5 @@ public void SerializeAsV2(IOpenApiWriter writer) { // Link object does not exist in V2. } - - /// - /// Serialize to OpenAPI V2 document without using reference. - /// - public void SerializeAsV2WithoutReference(IOpenApiWriter writer) - { - // Link object does not exist in V2. - } } } diff --git a/src/Microsoft.OpenApi/Models/OpenApiMediaType.cs b/src/Microsoft.OpenApi/Models/OpenApiMediaType.cs index 1ee1ce176..671a0dcfc 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiMediaType.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiMediaType.cs @@ -1,12 +1,16 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. +using System; using System.Collections.Generic; using System.Linq; -using Microsoft.OpenApi.Any; +using System.Text.Json.Nodes; +using Microsoft.OpenApi.Helpers; using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Writers; +#nullable enable + namespace Microsoft.OpenApi.Models { /// @@ -14,22 +18,28 @@ namespace Microsoft.OpenApi.Models /// public class OpenApiMediaType : IOpenApiSerializable, IOpenApiExtensible { + private OpenApiSchema? _schema; + /// /// The schema defining the type used for the request body. /// - public OpenApiSchema Schema { get; set; } + public virtual OpenApiSchema? Schema + { + get => _schema; + set => _schema = value; + } /// /// Example of the media type. /// The example object SHOULD be in the correct format as specified by the media type. /// - public IOpenApiAny Example { get; set; } + public JsonNode? Example { get; set; } /// /// Examples of the media type. /// Each example object SHOULD match the media type and specified schema if present. /// - public IDictionary Examples { get; set; } = new Dictionary(); + public IDictionary? Examples { get; set; } = new Dictionary(); /// /// A map between a property name and its encoding information. @@ -37,41 +47,58 @@ public class OpenApiMediaType : IOpenApiSerializable, IOpenApiExtensible /// The encoding object SHALL only apply to requestBody objects /// when the media type is multipart or application/x-www-form-urlencoded. /// - public IDictionary Encoding { get; set; } = new Dictionary(); + public IDictionary? Encoding { get; set; } = new Dictionary(); /// /// Serialize to Open Api v3.0. /// - public IDictionary Extensions { get; set; } = new Dictionary(); + public IDictionary? Extensions { get; set; } = new Dictionary(); /// /// Parameterless constructor /// - public OpenApiMediaType() {} + public OpenApiMediaType() { } /// /// Initializes a copy of an object /// - public OpenApiMediaType(OpenApiMediaType mediaType) + public OpenApiMediaType(OpenApiMediaType? mediaType) { - Schema = mediaType?.Schema != null ? new(mediaType?.Schema) : null; - Example = OpenApiAnyCloneHelper.CloneFromCopyConstructor(mediaType?.Example); + _schema = mediaType?.Schema != null ? new(mediaType.Schema) : null; + Example = mediaType?.Example != null ? JsonNodeCloneHelper.Clone(mediaType.Example) : null; Examples = mediaType?.Examples != null ? new Dictionary(mediaType.Examples) : null; Encoding = mediaType?.Encoding != null ? new Dictionary(mediaType.Encoding) : null; Extensions = mediaType?.Extensions != null ? new Dictionary(mediaType.Extensions) : null; } + /// + /// Serialize to Open Api v3.1. + /// + public void SerializeAsV31(IOpenApiWriter writer) + { + SerializeInternal(writer, OpenApiSpecVersion.OpenApi3_1, (w, element) => element.SerializeAsV31(w)); + } + /// /// Serialize to Open Api v3.0. /// public void SerializeAsV3(IOpenApiWriter writer) { - Utils.CheckArgumentNull(writer); + SerializeInternal(writer, OpenApiSpecVersion.OpenApi3_0, (w, element) => element.SerializeAsV3(w)); + } + + /// + /// Serialize to Open Api v3.0. + /// + private void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion version, + Action callback) + { + Utils.CheckArgumentNull(writer);; writer.WriteStartObject(); // schema - writer.WriteOptionalObject(OpenApiConstants.Schema, Schema, (w, s) => s.SerializeAsV3(w)); + writer.WriteOptionalObject(OpenApiConstants.Schema, Schema, callback); // example writer.WriteOptionalObject(OpenApiConstants.Example, Example, (w, e) => w.WriteAny(e)); @@ -83,10 +110,10 @@ public void SerializeAsV3(IOpenApiWriter writer) } // encoding - writer.WriteOptionalMap(OpenApiConstants.Encoding, Encoding, (w, e) => e.SerializeAsV3(w)); + writer.WriteOptionalMap(OpenApiConstants.Encoding, Encoding, callback); // extensions - writer.WriteExtensions(Extensions, OpenApiSpecVersion.OpenApi3_0); + writer.WriteExtensions(Extensions, version); writer.WriteEndObject(); } @@ -105,14 +132,14 @@ private static void SerializeExamples(IOpenApiWriter writer, IDictionary - example.Value is OpenApiArray arr && arr.Count == 0 + example.Value is JsonArray arr && arr.Count == 0 ); if (hasEmptyArray) { writer.WritePropertyName(OpenApiConstants.Examples); writer.WriteStartObject(); - foreach (var kvp in examples.Where(static kvp => kvp.Value.Value is OpenApiArray arr && arr.Count == 0)) + foreach (var kvp in examples.Where(static kvp => kvp.Value.Value is JsonArray arr && arr.Count == 0)) { writer.WritePropertyName(kvp.Key); writer.WriteStartObject(); diff --git a/src/Microsoft.OpenApi/Models/OpenApiOAuthFlow.cs b/src/Microsoft.OpenApi/Models/OpenApiOAuthFlow.cs index f7e330686..2385a4c55 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiOAuthFlow.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiOAuthFlow.cs @@ -57,12 +57,28 @@ public OpenApiOAuthFlow(OpenApiOAuthFlow oAuthFlow) Extensions = oAuthFlow?.Extensions != null ? new Dictionary(oAuthFlow.Extensions) : null; } + /// + /// Serialize to Open Api v3.1 + /// + public void SerializeAsV31(IOpenApiWriter writer) + { + SerializeInternal(writer, OpenApiSpecVersion.OpenApi3_1); + } + /// /// Serialize to Open Api v3.0 /// public void SerializeAsV3(IOpenApiWriter writer) { - Utils.CheckArgumentNull(writer); + SerializeInternal(writer, OpenApiSpecVersion.OpenApi3_0); + } + + /// + /// Serialize to Open Api v3.0 + /// + private void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion version) + { + Utils.CheckArgumentNull(writer);; writer.WriteStartObject(); @@ -79,7 +95,7 @@ public void SerializeAsV3(IOpenApiWriter writer) writer.WriteRequiredMap(OpenApiConstants.Scopes, Scopes, (w, s) => w.WriteValue(s)); // extensions - writer.WriteExtensions(Extensions, OpenApiSpecVersion.OpenApi3_0); + writer.WriteExtensions(Extensions, version); writer.WriteEndObject(); } diff --git a/src/Microsoft.OpenApi/Models/OpenApiOAuthFlows.cs b/src/Microsoft.OpenApi/Models/OpenApiOAuthFlows.cs index dc8cf7bc0..5211159a4 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiOAuthFlows.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiOAuthFlows.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. +using System; using System.Collections.Generic; using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Writers; @@ -40,7 +41,7 @@ public class OpenApiOAuthFlows : IOpenApiSerializable, IOpenApiExtensible /// /// Parameterless constructor /// - public OpenApiOAuthFlows() {} + public OpenApiOAuthFlows() { } /// /// Initializes a copy of an object @@ -55,35 +56,52 @@ public OpenApiOAuthFlows(OpenApiOAuthFlows oAuthFlows) Extensions = oAuthFlows?.Extensions != null ? new Dictionary(oAuthFlows.Extensions) : null; } + /// + /// Serialize to Open Api v3.1 + /// + public void SerializeAsV31(IOpenApiWriter writer) + { + SerializeInternal(writer, OpenApiSpecVersion.OpenApi3_1, (writer, element) => element.SerializeAsV31(writer)); + } + /// /// Serialize to Open Api v3.0 /// public void SerializeAsV3(IOpenApiWriter writer) { - Utils.CheckArgumentNull(writer); + SerializeInternal(writer, OpenApiSpecVersion.OpenApi3_0, (writer, element) => element.SerializeAsV3(writer)); + } + + /// + /// Serialize + /// + private void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion version, + Action callback) + { + Utils.CheckArgumentNull(writer);; writer.WriteStartObject(); // implicit - writer.WriteOptionalObject(OpenApiConstants.Implicit, Implicit, (w, o) => o.SerializeAsV3(w)); + writer.WriteOptionalObject(OpenApiConstants.Implicit, Implicit, callback); // password - writer.WriteOptionalObject(OpenApiConstants.Password, Password, (w, o) => o.SerializeAsV3(w)); + writer.WriteOptionalObject(OpenApiConstants.Password, Password, callback); // clientCredentials writer.WriteOptionalObject( OpenApiConstants.ClientCredentials, ClientCredentials, - (w, o) => o.SerializeAsV3(w)); + callback); // authorizationCode writer.WriteOptionalObject( OpenApiConstants.AuthorizationCode, AuthorizationCode, - (w, o) => o.SerializeAsV3(w)); + callback); // extensions - writer.WriteExtensions(Extensions, OpenApiSpecVersion.OpenApi3_0); + writer.WriteExtensions(Extensions, version); writer.WriteEndObject(); } diff --git a/src/Microsoft.OpenApi/Models/OpenApiOperation.cs b/src/Microsoft.OpenApi/Models/OpenApiOperation.cs index 69054740e..6e54cd894 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiOperation.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiOperation.cs @@ -5,8 +5,11 @@ using System.Collections.Generic; using System.Linq; using Microsoft.OpenApi.Interfaces; +using Microsoft.OpenApi.Models.References; using Microsoft.OpenApi.Writers; +#nullable enable + namespace Microsoft.OpenApi.Models { /// @@ -23,30 +26,30 @@ public class OpenApiOperation : IOpenApiSerializable, IOpenApiExtensible, IOpenA /// A list of tags for API documentation control. /// Tags can be used for logical grouping of operations by resources or any other qualifier. /// - public IList Tags { get; set; } = new List(); + public IList? Tags { get; set; } = new List(); /// /// A short summary of what the operation does. /// - public string Summary { get; set; } + public string? Summary { get; set; } /// /// A verbose explanation of the operation behavior. /// CommonMark syntax MAY be used for rich text representation. /// - public string Description { get; set; } + public string? Description { get; set; } /// /// Additional external documentation for this operation. /// - public OpenApiExternalDocs ExternalDocs { get; set; } + public OpenApiExternalDocs? ExternalDocs { get; set; } /// /// Unique string used to identify the operation. The id MUST be unique among all operations described in the API. /// Tools and libraries MAY use the operationId to uniquely identify an operation, therefore, /// it is RECOMMENDED to follow common programming naming conventions. /// - public string OperationId { get; set; } + public string? OperationId { get; set; } /// /// A list of parameters that are applicable for this operation. @@ -54,7 +57,7 @@ public class OpenApiOperation : IOpenApiSerializable, IOpenApiExtensible, IOpenA /// The list MUST NOT include duplicated parameters. A unique parameter is defined by a combination of a name and location. /// The list can use the Reference Object to link to parameters that are defined at the OpenAPI Object's components/parameters. /// - public IList Parameters { get; set; } = new List(); + public IList? Parameters { get; set; } = new List(); /// /// The request body applicable for this operation. @@ -62,12 +65,12 @@ public class OpenApiOperation : IOpenApiSerializable, IOpenApiExtensible, IOpenA /// has explicitly defined semantics for request bodies. /// In other cases where the HTTP spec is vague, requestBody SHALL be ignored by consumers. /// - public OpenApiRequestBody RequestBody { get; set; } + public OpenApiRequestBody? RequestBody { get; set; } /// /// REQUIRED. The list of possible responses as they are returned from executing this operation. /// - public OpenApiResponses Responses { get; set; } = new(); + public OpenApiResponses? Responses { get; set; } = new(); /// /// A map of possible out-of band callbacks related to the parent operation. @@ -77,7 +80,7 @@ public class OpenApiOperation : IOpenApiSerializable, IOpenApiExtensible, IOpenA /// The key value used to identify the callback object is an expression, evaluated at runtime, /// that identifies a URL to use for the callback operation. /// - public IDictionary Callbacks { get; set; } = new Dictionary(); + public IDictionary? Callbacks { get; set; } = new Dictionary(); /// /// Declares this operation to be deprecated. Consumers SHOULD refrain from usage of the declared operation. @@ -91,34 +94,34 @@ public class OpenApiOperation : IOpenApiSerializable, IOpenApiExtensible, IOpenA /// This definition overrides any declared top-level security. /// To remove a top-level security declaration, an empty array can be used. /// - public IList Security { get; set; } = new List(); + public IList? Security { get; set; } = new List(); /// /// An alternative server array to service this operation. /// If an alternative server object is specified at the Path Item Object or Root level, /// it will be overridden by this value. /// - public IList Servers { get; set; } = new List(); + public IList? Servers { get; set; } = new List(); /// /// This object MAY be extended with Specification Extensions. /// - public IDictionary Extensions { get; set; } = new Dictionary(); + public IDictionary? Extensions { get; set; } = new Dictionary(); /// - public IDictionary Annotations { get; set; } + public IDictionary? Annotations { get; set; } /// /// Parameterless constructor /// - public OpenApiOperation() {} + public OpenApiOperation() { } /// /// Initializes a copy of an object /// - public OpenApiOperation(OpenApiOperation operation) + public OpenApiOperation(OpenApiOperation? operation) { - Tags = operation?.Tags != null ? new List(operation?.Tags) : null; + Tags = operation?.Tags != null ? new List(operation.Tags) : null; Summary = operation?.Summary ?? Summary; Description = operation?.Description ?? Description; ExternalDocs = operation?.ExternalDocs != null ? new(operation?.ExternalDocs) : null; @@ -134,12 +137,28 @@ public OpenApiOperation(OpenApiOperation operation) Annotations = operation?.Annotations != null ? new Dictionary(operation.Annotations) : null; } + /// + /// Serialize to Open Api v3.1. + /// + public void SerializeAsV31(IOpenApiWriter writer) + { + SerializeInternal(writer, OpenApiSpecVersion.OpenApi3_1, (writer, element) => element.SerializeAsV31(writer)); + } + /// /// Serialize to Open Api v3.0. /// public void SerializeAsV3(IOpenApiWriter writer) { - Utils.CheckArgumentNull(writer); + SerializeInternal(writer, OpenApiSpecVersion.OpenApi3_0, (writer, element) => element.SerializeAsV3(writer)); + } + + /// + /// Serialize to Open Api v3.0. + /// + private void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion version, Action callback) + { + Utils.CheckArgumentNull(writer);; writer.WriteStartObject(); @@ -147,7 +166,7 @@ public void SerializeAsV3(IOpenApiWriter writer) writer.WriteOptionalCollection( OpenApiConstants.Tags, Tags, - (w, t) => t.SerializeAsV3(w)); + callback); // summary writer.WriteProperty(OpenApiConstants.Summary, Summary); @@ -156,34 +175,34 @@ public void SerializeAsV3(IOpenApiWriter writer) writer.WriteProperty(OpenApiConstants.Description, Description); // externalDocs - writer.WriteOptionalObject(OpenApiConstants.ExternalDocs, ExternalDocs, (w, e) => e.SerializeAsV3(w)); + writer.WriteOptionalObject(OpenApiConstants.ExternalDocs, ExternalDocs, callback); // operationId writer.WriteProperty(OpenApiConstants.OperationId, OperationId); // parameters - writer.WriteOptionalCollection(OpenApiConstants.Parameters, Parameters, (w, p) => p.SerializeAsV3(w)); + writer.WriteOptionalCollection(OpenApiConstants.Parameters, Parameters, callback); // requestBody - writer.WriteOptionalObject(OpenApiConstants.RequestBody, RequestBody, (w, r) => r.SerializeAsV3(w)); + writer.WriteOptionalObject(OpenApiConstants.RequestBody, RequestBody, callback); // responses - writer.WriteRequiredObject(OpenApiConstants.Responses, Responses, (w, r) => r.SerializeAsV3(w)); + writer.WriteRequiredObject(OpenApiConstants.Responses, Responses, callback); // callbacks - writer.WriteOptionalMap(OpenApiConstants.Callbacks, Callbacks, (w, c) => c.SerializeAsV3(w)); + writer.WriteOptionalMap(OpenApiConstants.Callbacks, Callbacks, callback); // deprecated writer.WriteProperty(OpenApiConstants.Deprecated, Deprecated, false); // security - writer.WriteOptionalCollection(OpenApiConstants.Security, Security, (w, s) => s.SerializeAsV3(w)); + writer.WriteOptionalCollection(OpenApiConstants.Security, Security, callback); // servers - writer.WriteOptionalCollection(OpenApiConstants.Servers, Servers, (w, s) => s.SerializeAsV3(w)); + writer.WriteOptionalCollection(OpenApiConstants.Servers, Servers, callback); // specification extensions - writer.WriteExtensions(Extensions, OpenApiSpecVersion.OpenApi3_0); + writer.WriteExtensions(Extensions, version); writer.WriteEndObject(); } @@ -193,7 +212,7 @@ public void SerializeAsV3(IOpenApiWriter writer) /// public void SerializeAsV2(IOpenApiWriter writer) { - Utils.CheckArgumentNull(writer); + Utils.CheckArgumentNull(writer);; writer.WriteStartObject(); @@ -244,18 +263,13 @@ public void SerializeAsV2(IOpenApiWriter writer) } else if (RequestBody.Reference != null) { + var hostDocument = RequestBody.Reference.HostDocument; parameters.Add( - new() - { - UnresolvedReference = true, - Reference = RequestBody.Reference - }); + new OpenApiParameterReference(RequestBody.Reference.Id, hostDocument)); - if (RequestBody.Reference.HostDocument != null) - { - var effectiveRequestBody = RequestBody.GetEffective(RequestBody.Reference.HostDocument); - if (effectiveRequestBody != null) - consumes = effectiveRequestBody.Content.Keys.Distinct().ToList(); + if (hostDocument != null) + { + consumes = RequestBody.Content.Keys.Distinct().ToList(); } } @@ -279,7 +293,7 @@ public void SerializeAsV2(IOpenApiWriter writer) .Concat( Responses .Where(static r => r.Value.Reference is {HostDocument: not null}) - .SelectMany(static r => r.Value.GetEffective(r.Value.Reference.HostDocument)?.Content?.Keys)) + .SelectMany(static r => r.Value.Content?.Keys)) .Distinct() .ToList(); diff --git a/src/Microsoft.OpenApi/Models/OpenApiParameter.cs b/src/Microsoft.OpenApi/Models/OpenApiParameter.cs index 1cfbd2c6c..f3eb6c76f 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiParameter.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiParameter.cs @@ -4,8 +4,10 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Text.Json.Nodes; using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Extensions; +using Microsoft.OpenApi.Helpers; using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Writers; @@ -14,18 +16,16 @@ namespace Microsoft.OpenApi.Models /// /// Parameter Object. /// - public class OpenApiParameter : IOpenApiReferenceable, IEffective, IOpenApiExtensible + public class OpenApiParameter : IOpenApiReferenceable, IOpenApiExtensible { private bool? _explode; - /// - /// The style of the parameter. - /// - public ParameterStyle? _style; + private ParameterStyle? _style; + private OpenApiSchema _schema; /// /// Indicates if object is populated with data or is just a reference to the data /// - public bool UnresolvedReference { get; set; } + public virtual bool UnresolvedReference { get; set; } /// /// Reference object. @@ -38,31 +38,31 @@ public class OpenApiParameter : IOpenApiReferenceable, IEffective - public string Name { get; set; } + public virtual string Name { get; set; } /// /// REQUIRED. The location of the parameter. /// Possible values are "query", "header", "path" or "cookie". /// - public ParameterLocation? In { get; set; } + public virtual ParameterLocation? In { get; set; } /// /// A brief description of the parameter. This could contain examples of use. /// CommonMark syntax MAY be used for rich text representation. /// - public string Description { get; set; } + public virtual string Description { get; set; } /// /// Determines whether this parameter is mandatory. /// If the parameter location is "path", this property is REQUIRED and its value MUST be true. /// Otherwise, the property MAY be included and its default value is false. /// - public bool Required { get; set; } + public virtual bool Required { get; set; } /// /// Specifies that a parameter is deprecated and SHOULD be transitioned out of usage. /// - public bool Deprecated { get; set; } + public virtual bool Deprecated { get; set; } = false; /// /// Sets the ability to pass empty-valued parameters. @@ -71,16 +71,16 @@ public class OpenApiParameter : IOpenApiReferenceable, IEffective - public bool AllowEmptyValue { get; set; } + public virtual bool AllowEmptyValue { get; set; } = false; /// /// Describes how the parameter value will be serialized depending on the type of the parameter value. /// Default values (based on value of in): for query - form; for path - simple; for header - simple; /// for cookie - form. /// - public ParameterStyle? Style + public virtual ParameterStyle? Style { - get => _style ?? SetDefaultStyleValue(); + get => _style ?? GetDefaultStyleValue(); set => _style = value; } @@ -91,7 +91,7 @@ public ParameterStyle? Style /// When style is form, the default value is true. /// For all other styles, the default value is false. /// - public bool Explode + public virtual bool Explode { get => _explode ?? Style == ParameterStyle.Form; set => _explode = value; @@ -103,12 +103,16 @@ public bool Explode /// This property only applies to parameters with an in value of query. /// The default value is false. /// - public bool AllowReserved { get; set; } + public virtual bool AllowReserved { get; set; } /// /// The schema defining the type used for the parameter. /// - public OpenApiSchema Schema { get; set; } + public virtual OpenApiSchema Schema + { + get => _schema; + set => _schema = value; + } /// /// Examples of the media type. Each example SHOULD contain a value @@ -117,7 +121,7 @@ public bool Explode /// Furthermore, if referencing a schema which contains an example, /// the examples value SHALL override the example provided by the schema. /// - public IDictionary Examples { get; set; } = new Dictionary(); + public virtual IDictionary Examples { get; set; } = new Dictionary(); /// /// Example of the media type. The example SHOULD match the specified schema and encoding properties @@ -127,7 +131,7 @@ public bool Explode /// To represent examples of media types that cannot naturally be represented in JSON or YAML, /// a string value can contain the example with escaping where necessary. /// - public IOpenApiAny Example { get; set; } + public virtual JsonNode Example { get; set; } /// /// A map containing the representations for the parameter. @@ -138,17 +142,17 @@ public bool Explode /// When example or examples are provided in conjunction with the schema object, /// the example MUST follow the prescribed serialization strategy for the parameter. /// - public IDictionary Content { get; set; } = new Dictionary(); + public virtual IDictionary Content { get; set; } = new Dictionary(); /// /// This object MAY be extended with Specification Extensions. /// - public IDictionary Extensions { get; set; } = new Dictionary(); + public virtual IDictionary Extensions { get; set; } = new Dictionary(); /// /// A parameterless constructor /// - public OpenApiParameter() {} + public OpenApiParameter() { } /// /// Initializes a clone instance of object @@ -164,9 +168,9 @@ public OpenApiParameter(OpenApiParameter parameter) Style = parameter?.Style ?? Style; Explode = parameter?.Explode ?? Explode; AllowReserved = parameter?.AllowReserved ?? AllowReserved; - Schema = parameter?.Schema != null ? new(parameter?.Schema) : null; + _schema = parameter?.Schema != null ? new(parameter.Schema) : null; Examples = parameter?.Examples != null ? new Dictionary(parameter.Examples) : null; - Example = OpenApiAnyCloneHelper.CloneFromCopyConstructor(parameter?.Example); + Example = parameter?.Example != null ? JsonNodeCloneHelper.Clone(parameter.Example) : null; Content = parameter?.Content != null ? new Dictionary(parameter.Content) : null; Extensions = parameter?.Extensions != null ? new Dictionary(parameter.Extensions) : null; AllowEmptyValue = parameter?.AllowEmptyValue ?? AllowEmptyValue; @@ -174,45 +178,26 @@ public OpenApiParameter(OpenApiParameter parameter) } /// - /// Serialize to Open Api v3.0 + /// Serialize to Open Api v3.1 /// - public void SerializeAsV3(IOpenApiWriter writer) + public virtual void SerializeAsV31(IOpenApiWriter writer) { - Utils.CheckArgumentNull(writer); - - var target = this; - - if (Reference != null) - { - if (!writer.GetSettings().ShouldInlineReference(Reference)) - { - Reference.SerializeAsV3(writer); - return; - } - else - { - target = this.GetEffective(Reference.HostDocument); - } - } - - target.SerializeAsV3WithoutReference(writer); + SerializeInternal(writer, OpenApiSpecVersion.OpenApi3_1, (writer, element) => element.SerializeAsV31(writer)); } /// - /// Returns an effective OpenApiParameter object based on the presence of a $ref + /// Serialize to Open Api v3.0 /// - /// The host OpenApiDocument that contains the reference. - /// OpenApiParameter - public OpenApiParameter GetEffective(OpenApiDocument doc) + public virtual void SerializeAsV3(IOpenApiWriter writer) { - return Reference != null ? doc.ResolveReferenceTo(Reference) : this; + SerializeInternal(writer, OpenApiSpecVersion.OpenApi3_0, (writer, element) => element.SerializeAsV3(writer)); } - /// - /// Serialize to OpenAPI V3 document without using reference. - /// - public void SerializeAsV3WithoutReference(IOpenApiWriter writer) + internal virtual void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion version, + Action callback) { + Utils.CheckArgumentNull(writer); + writer.WriteStartObject(); // name @@ -246,52 +231,30 @@ public void SerializeAsV3WithoutReference(IOpenApiWriter writer) writer.WriteProperty(OpenApiConstants.AllowReserved, AllowReserved, false); // schema - writer.WriteOptionalObject(OpenApiConstants.Schema, Schema, (w, s) => s.SerializeAsV3(w)); + writer.WriteOptionalObject(OpenApiConstants.Schema, Schema, callback); // example writer.WriteOptionalObject(OpenApiConstants.Example, Example, (w, s) => w.WriteAny(s)); // examples - writer.WriteOptionalMap(OpenApiConstants.Examples, Examples, (w, e) => e.SerializeAsV3(w)); + writer.WriteOptionalMap(OpenApiConstants.Examples, Examples, callback); // content - writer.WriteOptionalMap(OpenApiConstants.Content, Content, (w, c) => c.SerializeAsV3(w)); + writer.WriteOptionalMap(OpenApiConstants.Content, Content, callback); // extensions - writer.WriteExtensions(Extensions, OpenApiSpecVersion.OpenApi3_0); + writer.WriteExtensions(Extensions, version); writer.WriteEndObject(); } /// - /// Serialize to Open Api v2.0 + /// Serialize to OpenAPI V2 document without using reference. /// - public void SerializeAsV2(IOpenApiWriter writer) + public virtual void SerializeAsV2(IOpenApiWriter writer) { Utils.CheckArgumentNull(writer); - var target = this; - if (Reference != null) - { - if (!writer.GetSettings().ShouldInlineReference(Reference)) - { - Reference.SerializeAsV2(writer); - return; - } - else - { - target = this.GetEffective(Reference.HostDocument); - } - } - - target.SerializeAsV2WithoutReference(writer); - } - - /// - /// Serialize to OpenAPI V2 document without using reference. - /// - public void SerializeAsV2WithoutReference(IOpenApiWriter writer) - { writer.WriteStartObject(); // in @@ -329,8 +292,7 @@ public void SerializeAsV2WithoutReference(IOpenApiWriter writer) } // In V2 parameter's type can't be a reference to a custom object schema or can't be of type object // So in that case map the type as string. - else - if (Schema?.UnresolvedReference == true || "object".Equals(Schema?.Type, StringComparison.OrdinalIgnoreCase)) + else if (Schema?.UnresolvedReference == true || Schema?.Type == JsonSchemaType.Object) { writer.WriteProperty(OpenApiConstants.Type, "string"); } @@ -356,10 +318,10 @@ public void SerializeAsV2WithoutReference(IOpenApiWriter writer) if (Schema != null) { Schema.WriteAsItemsProperties(writer); - - if (Schema.Extensions != null) + var extensions = Schema.Extensions; + if (extensions != null) { - foreach (var key in Schema.Extensions.Keys) + foreach (var key in extensions.Keys) { // The extension will already have been serialized as part of the call to WriteAsItemsProperties above, // so remove it from the cloned collection so we don't write it again. @@ -371,7 +333,7 @@ public void SerializeAsV2WithoutReference(IOpenApiWriter writer) // allowEmptyValue writer.WriteProperty(OpenApiConstants.AllowEmptyValue, AllowEmptyValue, false); - if (this.In == ParameterLocation.Query && "array".Equals(Schema?.Type, StringComparison.OrdinalIgnoreCase)) + if (this.In == ParameterLocation.Query && Schema?.Type == JsonSchemaType.Array) { if (this.Style == ParameterStyle.Form && this.Explode == true) { @@ -397,7 +359,7 @@ public void SerializeAsV2WithoutReference(IOpenApiWriter writer) foreach (var example in Examples) { writer.WritePropertyName(example.Key); - example.Value.Serialize(writer, OpenApiSpecVersion.OpenApi2_0); + example.Value.SerializeInternal(writer, OpenApiSpecVersion.OpenApi2_0); } writer.WriteEndObject(); } @@ -408,7 +370,7 @@ public void SerializeAsV2WithoutReference(IOpenApiWriter writer) writer.WriteEndObject(); } - private ParameterStyle? SetDefaultStyleValue() + internal virtual ParameterStyle? GetDefaultStyleValue() { Style = In switch { diff --git a/src/Microsoft.OpenApi/Models/OpenApiPathItem.cs b/src/Microsoft.OpenApi/Models/OpenApiPathItem.cs index 6ea4b2d48..ea7d628ea 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiPathItem.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiPathItem.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. +using System; using System.Collections.Generic; using Microsoft.OpenApi.Extensions; using Microsoft.OpenApi.Interfaces; @@ -11,39 +12,39 @@ namespace Microsoft.OpenApi.Models /// /// Path Item Object: to describe the operations available on a single path. /// - public class OpenApiPathItem : IOpenApiExtensible, IOpenApiReferenceable, IEffective + public class OpenApiPathItem : IOpenApiExtensible, IOpenApiReferenceable { /// /// An optional, string summary, intended to apply to all operations in this path. /// - public string Summary { get; set; } + public virtual string Summary { get; set; } /// /// An optional, string description, intended to apply to all operations in this path. /// - public string Description { get; set; } + public virtual string Description { get; set; } /// /// Gets the definition of operations on this path. /// - public IDictionary Operations { get; set; } + public virtual IDictionary Operations { get; set; } = new Dictionary(); /// /// An alternative server array to service all operations in this path. /// - public IList Servers { get; set; } = new List(); + public virtual IList Servers { get; set; } = new List(); /// /// A list of parameters that are applicable for all the operations described under this path. /// These parameters can be overridden at the operation level, but cannot be removed there. /// - public IList Parameters { get; set; } = new List(); + public virtual IList Parameters { get; set; } = new List(); /// /// This object MAY be extended with Specification Extensions. /// - public IDictionary Extensions { get; set; } = new Dictionary(); + public virtual IDictionary Extensions { get; set; } = new Dictionary(); /// /// Indicates if object is populated with data or is just a reference to the data @@ -68,7 +69,7 @@ public void AddOperation(OperationType operationType, OpenApiOperation operation /// /// Parameterless constructor /// - public OpenApiPathItem() {} + public OpenApiPathItem() { } /// /// Initializes a clone of an object @@ -86,76 +87,29 @@ public OpenApiPathItem(OpenApiPathItem pathItem) } /// - /// Serialize to Open Api v3.0 + /// Serialize to Open Api v3.1 /// - public void SerializeAsV3(IOpenApiWriter writer) + public virtual void SerializeAsV31(IOpenApiWriter writer) { - Utils.CheckArgumentNull(writer); - var target = this; - - if (Reference != null) - { - if (!writer.GetSettings().ShouldInlineReference(Reference)) - { - Reference.SerializeAsV3(writer); - return; - } - else - { - target = GetEffective(Reference.HostDocument); - } - } - target.SerializeAsV3WithoutReference(writer); + SerializeInternal(writer, OpenApiSpecVersion.OpenApi3_1, (writer, element) => element.SerializeAsV31(writer)); } /// - /// Returns an effective OpenApiPathItem object based on the presence of a $ref + /// Serialize to Open Api v3.0 /// - /// The host OpenApiDocument that contains the reference. - /// OpenApiPathItem - public OpenApiPathItem GetEffective(OpenApiDocument doc) + public virtual void SerializeAsV3(IOpenApiWriter writer) { - if (this.Reference != null) - { - return doc.ResolveReferenceTo(this.Reference); - } - else - { - return this; - } + SerializeInternal(writer, OpenApiSpecVersion.OpenApi3_0, (writer, element) => element.SerializeAsV3(writer)); } /// - /// Serialize to Open Api v2.0 + /// Serialize inline PathItem in OpenAPI V2 /// + /// public void SerializeAsV2(IOpenApiWriter writer) { Utils.CheckArgumentNull(writer); - var target = this; - - if (Reference != null) - { - if (!writer.GetSettings().ShouldInlineReference(Reference)) - { - Reference.SerializeAsV2(writer); - return; - } - else - { - target = this.GetEffective(Reference.HostDocument); - } - } - - target.SerializeAsV2WithoutReference(writer); - } - - /// - /// Serialize inline PathItem in OpenAPI V2 - /// - /// - public void SerializeAsV2WithoutReference(IOpenApiWriter writer) - { writer.WriteStartObject(); // operations except "trace" @@ -187,12 +141,11 @@ public void SerializeAsV2WithoutReference(IOpenApiWriter writer) writer.WriteEndObject(); } - /// - /// Serialize inline PathItem in OpenAPI V3 - /// - /// - public void SerializeAsV3WithoutReference(IOpenApiWriter writer) + internal virtual void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion version, + Action callback) { + Utils.CheckArgumentNull(writer); + writer.WriteStartObject(); // summary @@ -207,17 +160,17 @@ public void SerializeAsV3WithoutReference(IOpenApiWriter writer) writer.WriteOptionalObject( operation.Key.GetDisplayName(), operation.Value, - (w, o) => o.SerializeAsV3(w)); + callback); } // servers - writer.WriteOptionalCollection(OpenApiConstants.Servers, Servers, (w, s) => s.SerializeAsV3(w)); + writer.WriteOptionalCollection(OpenApiConstants.Servers, Servers, callback); // parameters - writer.WriteOptionalCollection(OpenApiConstants.Parameters, Parameters, (w, p) => p.SerializeAsV3(w)); + writer.WriteOptionalCollection(OpenApiConstants.Parameters, Parameters, callback); // specification extensions - writer.WriteExtensions(Extensions, OpenApiSpecVersion.OpenApi3_0); + writer.WriteExtensions(Extensions, version); writer.WriteEndObject(); } diff --git a/src/Microsoft.OpenApi/Models/OpenApiPaths.cs b/src/Microsoft.OpenApi/Models/OpenApiPaths.cs index 014b37a1a..f3a89460a 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiPaths.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiPaths.cs @@ -11,7 +11,7 @@ public class OpenApiPaths : OpenApiExtensibleDictionary /// /// Parameterless constructor /// - public OpenApiPaths() {} + public OpenApiPaths() { } /// /// Initializes a copy of object diff --git a/src/Microsoft.OpenApi/Models/OpenApiReference.cs b/src/Microsoft.OpenApi/Models/OpenApiReference.cs index b554ef25f..8a1ae4a43 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiReference.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiReference.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; @@ -13,6 +13,19 @@ namespace Microsoft.OpenApi.Models /// public class OpenApiReference : IOpenApiSerializable { + /// + /// A short summary which by default SHOULD override that of the referenced component. + /// If the referenced object-type does not allow a summary field, then this field has no effect. + /// + public string Summary { get; set; } + + /// + /// A description which by default SHOULD override that of the referenced component. + /// CommonMark syntax MAY be used for rich text representation. + /// If the referenced object-type does not allow a description field, then this field has no effect. + /// + public string Description { get; set; } + /// /// External resource in the reference. /// It maybe: @@ -49,7 +62,7 @@ public class OpenApiReference : IOpenApiSerializable /// /// Gets a flag indicating whether a file is a valid OpenAPI document or a fragment /// - public bool IsFragrament = false; + public bool IsFragment = false; /// /// The OpenApiDocument that is hosting the OpenApiReference instance. This is used to enable dereferencing the reference. @@ -82,6 +95,10 @@ public string ReferenceV3 { return Id; } + if (Id.StartsWith("http")) + { + return Id; + } return "#/components/" + Type.Value.GetDisplayName() + "/" + Id; } @@ -121,23 +138,45 @@ public string ReferenceV2 /// /// Parameterless constructor /// - public OpenApiReference() {} + public OpenApiReference() { } /// /// Initializes a copy instance of the object /// public OpenApiReference(OpenApiReference reference) { + Summary = reference?.Summary; + Description = reference?.Description; ExternalResource = reference?.ExternalResource; Type = reference?.Type; Id = reference?.Id; HostDocument = new(reference?.HostDocument); } + /// + /// Serialize to Open Api v3.1. + /// + public void SerializeAsV31(IOpenApiWriter writer) + { + // summary and description are in 3.1 but not in 3.0 + writer.WriteProperty(OpenApiConstants.Summary, Summary); + writer.WriteProperty(OpenApiConstants.Description, Description); + + SerializeInternal(writer); + } + /// /// Serialize to Open Api v3.0. /// public void SerializeAsV3(IOpenApiWriter writer) + { + SerializeInternal(writer); + } + + /// + /// Serialize + /// + private void SerializeInternal(IOpenApiWriter writer) { Utils.CheckArgumentNull(writer); @@ -161,7 +200,7 @@ public void SerializeAsV3(IOpenApiWriter writer) /// public void SerializeAsV2(IOpenApiWriter writer) { - Utils.CheckArgumentNull(writer); + Utils.CheckArgumentNull(writer);; if (Type == ReferenceType.Tag) { @@ -189,11 +228,16 @@ private string GetExternalReferenceV3() { if (Id != null) { - if (IsFragrament) + if (IsFragment) { return ExternalResource + "#" + Id; } - + + if (Id.StartsWith("http")) + { + return Id; + } + if (Type.HasValue) { return ExternalResource + "#/components/" + Type.Value.GetDisplayName() + "/"+ Id; diff --git a/src/Microsoft.OpenApi/Models/OpenApiRequestBody.cs b/src/Microsoft.OpenApi/Models/OpenApiRequestBody.cs index 5e5edc576..037e7d92c 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiRequestBody.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiRequestBody.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Linq; using Microsoft.OpenApi.Any; +using Microsoft.OpenApi.Extensions; using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Writers; @@ -13,7 +14,7 @@ namespace Microsoft.OpenApi.Models /// /// Request Body Object /// - public class OpenApiRequestBody : IOpenApiReferenceable, IOpenApiExtensible, IEffective + public class OpenApiRequestBody : IOpenApiReferenceable, IOpenApiExtensible { /// /// Indicates if object is populated with data or is just a reference to the data @@ -29,23 +30,23 @@ public class OpenApiRequestBody : IOpenApiReferenceable, IOpenApiExtensible, IEf /// A brief description of the request body. This could contain examples of use. /// CommonMark syntax MAY be used for rich text representation. /// - public string Description { get; set; } + public virtual string Description { get; set; } /// /// Determines if the request body is required in the request. Defaults to false. /// - public bool Required { get; set; } + public virtual bool Required { get; set; } /// /// REQUIRED. The content of the request body. The key is a media type or media type range and the value describes it. /// For requests that match multiple keys, only the most specific key is applicable. e.g. text/plain overrides text/* /// - public IDictionary Content { get; set; } = new Dictionary(); + public virtual IDictionary Content { get; set; } = new Dictionary(); /// /// This object MAY be extended with Specification Extensions. /// - public IDictionary Extensions { get; set; } = new Dictionary(); + public virtual IDictionary Extensions { get; set; } = new Dictionary(); /// /// Parameter-less constructor @@ -66,57 +67,39 @@ public OpenApiRequestBody(OpenApiRequestBody requestBody) } /// - /// Serialize to Open Api v3.0 + /// Serialize to Open Api v3.1 /// - public void SerializeAsV3(IOpenApiWriter writer) + public virtual void SerializeAsV31(IOpenApiWriter writer) { - Utils.CheckArgumentNull(writer); - - var target = this; - - if (Reference != null) - { - if (!writer.GetSettings().ShouldInlineReference(Reference)) - { - Reference.SerializeAsV3(writer); - return; - } - else - { - target = GetEffective(Reference.HostDocument); - } - } - target.SerializeAsV3WithoutReference(writer); + SerializeInternal(writer, OpenApiSpecVersion.OpenApi3_1, (writer, element) => element.SerializeAsV31(writer)); } /// - /// Returns an effective OpenApiRequestBody object based on the presence of a $ref + /// Serialize to Open Api v3.0 /// - /// The host OpenApiDocument that contains the reference. - /// OpenApiRequestBody - public OpenApiRequestBody GetEffective(OpenApiDocument doc) + public virtual void SerializeAsV3(IOpenApiWriter writer) { - return Reference != null ? doc.ResolveReferenceTo(Reference) : this; + SerializeInternal(writer, OpenApiSpecVersion.OpenApi3_0, (writer, element) => element.SerializeAsV3(writer)); } - - /// - /// Serialize to OpenAPI V3 document without using reference. - /// - public void SerializeAsV3WithoutReference(IOpenApiWriter writer) + + internal virtual void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion version, + Action callback) { + Utils.CheckArgumentNull(writer); + writer.WriteStartObject(); // description writer.WriteProperty(OpenApiConstants.Description, Description); // content - writer.WriteRequiredMap(OpenApiConstants.Content, Content, (w, c) => c.SerializeAsV3(w)); + writer.WriteRequiredMap(OpenApiConstants.Content, Content, callback); // required writer.WriteProperty(OpenApiConstants.Required, Required, false); // extensions - writer.WriteExtensions(Extensions, OpenApiSpecVersion.OpenApi3_0); + writer.WriteExtensions(Extensions, version); writer.WriteEndObject(); } @@ -129,14 +112,6 @@ public void SerializeAsV2(IOpenApiWriter writer) // RequestBody object does not exist in V2. } - /// - /// Serialize to OpenAPI V2 document without using reference. - /// - public void SerializeAsV2WithoutReference(IOpenApiWriter writer) - { - // RequestBody object does not exist in V2. - } - internal OpenApiBodyParameter ConvertToBodyParameter() { var bodyParameter = new OpenApiBodyParameter @@ -152,7 +127,8 @@ internal OpenApiBodyParameter ConvertToBodyParameter() }; if (bodyParameter.Extensions.ContainsKey(OpenApiConstants.BodyName)) { - bodyParameter.Name = (Extensions[OpenApiConstants.BodyName] as OpenApiString)?.Value ?? "body"; + var bodyName = bodyParameter.Extensions[OpenApiConstants.BodyName] as OpenApiAny; + bodyParameter.Name = string.IsNullOrEmpty(bodyName?.Node.ToString()) ? "body" : bodyName?.Node.ToString(); bodyParameter.Extensions.Remove(OpenApiConstants.BodyName); } return bodyParameter; @@ -166,11 +142,11 @@ internal IEnumerable ConvertToFormDataParameters() foreach (var property in Content.First().Value.Schema.Properties) { var paramSchema = property.Value; - if ("string".Equals(paramSchema.Type, StringComparison.OrdinalIgnoreCase) + if ("string".Equals(paramSchema.Type.ToIdentifier(), StringComparison.OrdinalIgnoreCase) && ("binary".Equals(paramSchema.Format, StringComparison.OrdinalIgnoreCase) || "base64".Equals(paramSchema.Format, StringComparison.OrdinalIgnoreCase))) { - paramSchema.Type = "file"; + paramSchema.Type = "file".ToJsonSchemaType(); paramSchema.Format = null; } yield return new() diff --git a/src/Microsoft.OpenApi/Models/OpenApiResponse.cs b/src/Microsoft.OpenApi/Models/OpenApiResponse.cs index e300cd33d..2fab33fd5 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiResponse.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiResponse.cs @@ -1,6 +1,7 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. +using System; using System.Collections.Generic; using System.Linq; using Microsoft.OpenApi.Interfaces; @@ -11,35 +12,35 @@ namespace Microsoft.OpenApi.Models /// /// Response object. /// - public class OpenApiResponse : IOpenApiReferenceable, IOpenApiExtensible, IEffective + public class OpenApiResponse : IOpenApiReferenceable, IOpenApiExtensible { /// /// REQUIRED. A short description of the response. /// - public string Description { get; set; } + public virtual string Description { get; set; } /// /// Maps a header name to its definition. /// - public IDictionary Headers { get; set; } = new Dictionary(); + public virtual IDictionary Headers { get; set; } = new Dictionary(); /// /// A map containing descriptions of potential response payloads. /// The key is a media type or media type range and the value describes it. /// - public IDictionary Content { get; set; } = new Dictionary(); + public virtual IDictionary Content { get; set; } = new Dictionary(); /// /// A map of operations links that can be followed from the response. /// The key of the map is a short name for the link, /// following the naming constraints of the names for Component Objects. /// - public IDictionary Links { get; set; } = new Dictionary(); + public virtual IDictionary Links { get; set; } = new Dictionary(); /// /// This object MAY be extended with Specification Extensions. /// - public IDictionary Extensions { get; set; } = new Dictionary(); + public virtual IDictionary Extensions { get; set; } = new Dictionary(); /// /// Indicates if object is populated with data or is just a reference to the data @@ -54,7 +55,7 @@ public class OpenApiResponse : IOpenApiReferenceable, IOpenApiExtensible, IEffec /// /// Parameterless constructor /// - public OpenApiResponse() {} + public OpenApiResponse() { } /// /// Initializes a copy of object @@ -71,93 +72,53 @@ public OpenApiResponse(OpenApiResponse response) } /// - /// Serialize to Open Api v3.0. + /// Serialize to Open Api v3.1 /// - public void SerializeAsV3(IOpenApiWriter writer) + public virtual void SerializeAsV31(IOpenApiWriter writer) { - Utils.CheckArgumentNull(writer); - - var target = this; - - if (Reference != null) - { - if (!writer.GetSettings().ShouldInlineReference(Reference)) - { - Reference.SerializeAsV3(writer); - return; - } - else - { - target = GetEffective(Reference.HostDocument); - } - } - target.SerializeAsV3WithoutReference(writer); + SerializeInternal(writer, OpenApiSpecVersion.OpenApi3_1, (writer, element) => element.SerializeAsV31(writer)); } /// - /// Returns an effective OpenApiRequestBody object based on the presence of a $ref + /// Serialize to Open Api v3.0. /// - /// The host OpenApiDocument that contains the reference. - /// OpenApiResponse - public OpenApiResponse GetEffective(OpenApiDocument doc) + public virtual void SerializeAsV3(IOpenApiWriter writer) { - return Reference != null ? doc.ResolveReferenceTo(Reference) : this; + SerializeInternal(writer, OpenApiSpecVersion.OpenApi3_0, (writer, element) => element.SerializeAsV3(writer)); } - /// - /// Serialize to OpenAPI V3 document without using reference. - /// - public void SerializeAsV3WithoutReference(IOpenApiWriter writer) + internal virtual void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion version, + Action callback) { + Utils.CheckArgumentNull(writer); + writer.WriteStartObject(); // description writer.WriteRequiredProperty(OpenApiConstants.Description, Description); // headers - writer.WriteOptionalMap(OpenApiConstants.Headers, Headers, (w, h) => h.SerializeAsV3(w)); + writer.WriteOptionalMap(OpenApiConstants.Headers, Headers, callback); // content - writer.WriteOptionalMap(OpenApiConstants.Content, Content, (w, c) => c.SerializeAsV3(w)); + writer.WriteOptionalMap(OpenApiConstants.Content, Content, callback); // links - writer.WriteOptionalMap(OpenApiConstants.Links, Links, (w, l) => l.SerializeAsV3(w)); + writer.WriteOptionalMap(OpenApiConstants.Links, Links, callback); // extension - writer.WriteExtensions(Extensions, OpenApiSpecVersion.OpenApi3_0); + writer.WriteExtensions(Extensions, version); writer.WriteEndObject(); } /// - /// Serialize to Open Api v2.0. + /// Serialize to OpenAPI V2 document without using reference. /// - public void SerializeAsV2(IOpenApiWriter writer) + public virtual void SerializeAsV2(IOpenApiWriter writer) { Utils.CheckArgumentNull(writer); - var target = this; - - if (Reference != null) - { - if (!writer.GetSettings().ShouldInlineReference(Reference)) - { - Reference.SerializeAsV2(writer); - return; - } - else - { - target = GetEffective(Reference.HostDocument); - } - } - target.SerializeAsV2WithoutReference(writer); - } - - /// - /// Serialize to OpenAPI V2 document without using reference. - /// - public void SerializeAsV2WithoutReference(IOpenApiWriter writer) - { writer.WriteStartObject(); // description @@ -171,10 +132,7 @@ public void SerializeAsV2WithoutReference(IOpenApiWriter writer) if (mediatype.Value != null) { // schema - writer.WriteOptionalObject( - OpenApiConstants.Schema, - mediatype.Value.Schema, - (w, s) => s.SerializeAsV2(w)); + writer.WriteOptionalObject(OpenApiConstants.Schema, mediatype.Value.Schema, (w, s) => s.SerializeAsV2(w)); // examples if (Content.Values.Any(m => m.Example != null)) @@ -204,7 +162,7 @@ public void SerializeAsV2WithoutReference(IOpenApiWriter writer) .SelectMany(mediaTypePair => mediaTypePair.Value.Examples)) { writer.WritePropertyName(example.Key); - example.Value.Serialize(writer, OpenApiSpecVersion.OpenApi2_0); + example.Value.SerializeInternal(writer, OpenApiSpecVersion.OpenApi2_0); } writer.WriteEndObject(); diff --git a/src/Microsoft.OpenApi/Models/OpenApiResponses.cs b/src/Microsoft.OpenApi/Models/OpenApiResponses.cs index 483524044..8880c244f 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiResponses.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiResponses.cs @@ -17,6 +17,6 @@ public OpenApiResponses() { } /// Initializes a copy of object /// /// The - public OpenApiResponses(OpenApiResponses openApiResponses) : base(dictionary: openApiResponses) {} + public OpenApiResponses(OpenApiResponses openApiResponses) : base(dictionary: openApiResponses) { } } } diff --git a/src/Microsoft.OpenApi/Models/OpenApiSchema.cs b/src/Microsoft.OpenApi/Models/OpenApiSchema.cs index 957e0f946..59b7e2025 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiSchema.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiSchema.cs @@ -1,83 +1,141 @@ // Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. +// Licensed under the MIT license. +using System; using System.Collections.Generic; using System.Linq; -using Microsoft.OpenApi.Any; +using System.Text.Json.Nodes; +using Microsoft.OpenApi.Extensions; +using Microsoft.OpenApi.Helpers; using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Writers; namespace Microsoft.OpenApi.Models { /// - /// Schema Object. + /// The Schema Object allows the definition of input and output data types. /// - public class OpenApiSchema : IOpenApiReferenceable, IEffective, IOpenApiExtensible, IOpenApiAnnotatable + public class OpenApiSchema : IOpenApiAnnotatable, IOpenApiExtensible, IOpenApiReferenceable, IOpenApiSerializable { + private JsonNode _example; + private JsonNode _default; + private IList _examples; + /// /// Follow JSON Schema definition. Short text providing information about the data. /// - public string Title { get; set; } + public virtual string Title { get; set; } + + /// + /// $schema, a JSON Schema dialect identifier. Value must be a URI + /// + public virtual string Schema { get; set; } + + /// + /// $id - Identifies a schema resource with its canonical URI. + /// + public virtual string Id { get; set; } + + /// + /// $comment - reserves a location for comments from schema authors to readers or maintainers of the schema. + /// + public virtual string Comment { get; set; } + + /// + /// $vocabulary- used in meta-schemas to identify the vocabularies available for use in schemas described by that meta-schema. + /// + public virtual IDictionary Vocabulary { get; set; } + + /// + /// $dynamicRef - an applicator that allows for deferring the full resolution until runtime, at which point it is resolved each time it is encountered while evaluating an instance + /// + public virtual string DynamicRef { get; set; } + + /// + /// $dynamicAnchor - used to create plain name fragments that are not tied to any particular structural location for referencing purposes, which are taken into consideration for dynamic referencing. + /// + public virtual string DynamicAnchor { get; set; } + + /// + /// $defs - reserves a location for schema authors to inline re-usable JSON Schemas into a more general schema. + /// The keyword does not directly affect the validation result + /// + public virtual IDictionary Definitions { get; set; } + + /// + /// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00 + /// + public virtual decimal? V31ExclusiveMaximum { get; set; } /// /// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00 - /// Value MUST be a string. Multiple types via an array are not supported. /// - public string Type { get; set; } + public virtual decimal? V31ExclusiveMinimum { get; set; } + + /// + /// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00 + /// + public virtual bool UnEvaluatedProperties { get; set; } + + /// + /// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00 + /// Value MUST be a string in V2 and V3. + /// + public virtual JsonSchemaType? Type { 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, /// the OAS offers a few additional predefined formats. /// - public string Format { get; set; } + public virtual string Format { get; set; } /// /// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00 /// CommonMark syntax MAY be used for rich text representation. /// - public string Description { get; set; } + public virtual string Description { get; set; } /// /// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00 /// - public decimal? Maximum { get; set; } + public virtual decimal? Maximum { get; set; } /// /// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00 /// - public bool? ExclusiveMaximum { get; set; } + public virtual bool? ExclusiveMaximum { get; set; } /// /// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00 /// - public decimal? Minimum { get; set; } + public virtual decimal? Minimum { get; set; } /// /// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00 /// - public bool? ExclusiveMinimum { get; set; } + public virtual bool? ExclusiveMinimum { get; set; } /// /// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00 /// - public int? MaxLength { get; set; } + public virtual int? MaxLength { get; set; } /// /// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00 /// - public int? MinLength { get; set; } + public virtual int? MinLength { get; set; } /// /// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00 /// This string SHOULD be a valid regular expression, according to the ECMA 262 regular expression dialect /// - public string Pattern { get; set; } + public virtual string Pattern { get; set; } /// /// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00 /// - public decimal? MultipleOf { get; set; } + public virtual decimal? MultipleOf { get; set; } /// /// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00 @@ -85,7 +143,11 @@ public class OpenApiSchema : IOpenApiReferenceable, IEffective, I /// 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 IOpenApiAny Default { get; set; } + public virtual JsonNode Default + { + get => _default; + set => _default = value; + } /// /// Relevant only for Schema "properties" definitions. Declares the property as "read only". @@ -95,7 +157,7 @@ public class OpenApiSchema : IOpenApiReferenceable, IEffective, I /// A property MUST NOT be marked as both readOnly and writeOnly being true. /// Default value is false. /// - public bool ReadOnly { get; set; } + public virtual bool ReadOnly { get; set; } /// /// Relevant only for Schema "properties" definitions. Declares the property as "write only". @@ -105,141 +167,170 @@ public class OpenApiSchema : IOpenApiReferenceable, IEffective, I /// A property MUST NOT be marked as both readOnly and writeOnly being true. /// Default value is false. /// - public bool WriteOnly { get; set; } + public virtual bool WriteOnly { get; set; } /// /// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00 /// Inline or referenced schema MUST be of a Schema Object and not a standard JSON Schema. /// - public IList AllOf { get; set; } = new List(); + public virtual IList AllOf { get; set; } = new List(); /// /// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00 /// Inline or referenced schema MUST be of a Schema Object and not a standard JSON Schema. /// - public IList OneOf { get; set; } = new List(); + public virtual IList OneOf { get; set; } = new List(); /// /// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00 /// Inline or referenced schema MUST be of a Schema Object and not a standard JSON Schema. /// - public IList AnyOf { get; set; } = new List(); + public virtual IList AnyOf { get; set; } = new List(); /// /// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00 /// Inline or referenced schema MUST be of a Schema Object and not a standard JSON Schema. /// - public OpenApiSchema Not { get; set; } + public virtual OpenApiSchema Not { get; set; } /// /// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00 /// - public ISet Required { get; set; } = new HashSet(); + public virtual ISet Required { get; set; } = new HashSet(); /// /// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00 /// Value MUST be an object and not an array. Inline or referenced schema MUST be of a Schema Object /// and not a standard JSON Schema. items MUST be present if the type is array. /// - public OpenApiSchema Items { get; set; } + public virtual OpenApiSchema Items { get; set; } /// /// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00 /// - public int? MaxItems { get; set; } + public virtual int? MaxItems { get; set; } /// /// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00 /// - public int? MinItems { get; set; } + public virtual int? MinItems { get; set; } /// /// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00 /// - public bool? UniqueItems { get; set; } + public virtual bool? UniqueItems { get; set; } /// /// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00 /// Property definitions MUST be a Schema Object and not a standard JSON Schema (inline or referenced). /// - public IDictionary Properties { get; set; } = new Dictionary(); + public virtual IDictionary Properties { get; set; } = new Dictionary(); + + /// + /// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00 + /// PatternProperty definitions MUST be a Schema Object and not a standard JSON Schema (inline or referenced) + /// Each property name of this object SHOULD be a valid regular expression according to the ECMA 262 r + /// egular expression dialect. Each property value of this object MUST be an object, and each object MUST + /// be a valid Schema Object not a standard JSON Schema. + /// + public virtual IDictionary PatternProperties { get; set; } = new Dictionary(); /// /// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00 /// - public int? MaxProperties { get; set; } + public virtual int? MaxProperties { get; set; } /// /// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00 /// - public int? MinProperties { get; set; } + public virtual int? MinProperties { get; set; } /// /// Indicates if the schema can contain properties other than those defined by the properties map. /// - public bool AdditionalPropertiesAllowed { get; set; } = true; + public virtual bool AdditionalPropertiesAllowed { get; set; } = true; /// /// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00 /// Value can be boolean or object. Inline or referenced schema /// MUST be of a Schema Object and not a standard JSON Schema. /// - public OpenApiSchema AdditionalProperties { get; set; } + public virtual OpenApiSchema AdditionalProperties { get; set; } /// /// Adds support for polymorphism. The discriminator is an object name that is used to differentiate /// between other schemas which may satisfy the payload description. /// - public OpenApiDiscriminator Discriminator { get; set; } + public virtual OpenApiDiscriminator Discriminator { get; set; } /// /// A free-form property to include an example of an instance for this schema. /// 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 IOpenApiAny Example { get; set; } + public virtual JsonNode Example + { + get => _example; + set => _example = value; + } + + /// + /// 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; + } /// /// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00 /// - public IList Enum { get; set; } = new List(); + public virtual IList Enum { get; set; } = new List(); /// /// Allows sending a null value for the defined schema. Default value is false. /// - public bool Nullable { get; set; } + public virtual bool Nullable { get; set; } + + /// + /// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00 + /// + public virtual bool UnevaluatedProperties { get; set;} /// /// Additional external documentation for this schema. /// - public OpenApiExternalDocs ExternalDocs { get; set; } + public virtual OpenApiExternalDocs ExternalDocs { get; set; } /// /// Specifies that a schema is deprecated and SHOULD be transitioned out of usage. /// Default value is false. /// - public bool Deprecated { get; set; } + public virtual bool Deprecated { get; set; } /// /// This MAY be used only on properties schemas. It has no effect on root schemas. /// Adds additional metadata to describe the XML representation of this property. /// - public OpenApiXml Xml { get; set; } + public virtual OpenApiXml Xml { get; set; } /// /// This object MAY be extended with Specification Extensions. /// - public IDictionary Extensions { get; set; } = new Dictionary(); + public virtual IDictionary Extensions { get; set; } = new Dictionary(); /// /// Indicates object is a placeholder reference to an actual object and does not contain valid data. /// - public bool UnresolvedReference { get; set; } + public virtual bool UnresolvedReference { get; set; } /// /// Reference object. /// - public OpenApiReference Reference { get; set; } + public virtual OpenApiReference Reference { get; set; } /// public IDictionary Annotations { get; set; } @@ -247,7 +338,7 @@ public class OpenApiSchema : IOpenApiReferenceable, IEffective, I /// /// Parameterless constructor /// - public OpenApiSchema() {} + public OpenApiSchema() { } /// /// Initializes a copy of object @@ -255,6 +346,16 @@ public OpenApiSchema() {} public OpenApiSchema(OpenApiSchema schema) { Title = schema?.Title ?? Title; + Id = schema?.Id ?? Id; + Schema = schema?.Schema ?? Schema; + Comment = schema?.Comment ?? Comment; + Vocabulary = schema?.Vocabulary != null ? new Dictionary(schema.Vocabulary) : null; + DynamicAnchor = schema?.DynamicAnchor ?? DynamicAnchor; + DynamicRef = schema?.DynamicRef ?? DynamicRef; + Definitions = schema?.Definitions != null ? new Dictionary(schema.Definitions) : null; + UnevaluatedProperties = schema?.UnevaluatedProperties ?? UnevaluatedProperties; + V31ExclusiveMaximum = schema?.V31ExclusiveMaximum ?? V31ExclusiveMaximum; + V31ExclusiveMinimum = schema?.V31ExclusiveMinimum ?? V31ExclusiveMinimum; Type = schema?.Type ?? Type; Format = schema?.Format ?? Format; Description = schema?.Description ?? Description; @@ -266,7 +367,7 @@ public OpenApiSchema(OpenApiSchema schema) MinLength = schema?.MinLength ?? MinLength; Pattern = schema?.Pattern ?? Pattern; MultipleOf = schema?.MultipleOf ?? MultipleOf; - Default = OpenApiAnyCloneHelper.CloneFromCopyConstructor(schema?.Default); + _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; @@ -279,13 +380,15 @@ public OpenApiSchema(OpenApiSchema schema) MinItems = schema?.MinItems ?? MinItems; UniqueItems = schema?.UniqueItems ?? UniqueItems; Properties = schema?.Properties != null ? new Dictionary(schema.Properties) : null; + PatternProperties = schema?.PatternProperties != null ? new Dictionary(schema.PatternProperties) : null; MaxProperties = schema?.MaxProperties ?? MaxProperties; MinProperties = schema?.MinProperties ?? MinProperties; AdditionalPropertiesAllowed = schema?.AdditionalPropertiesAllowed ?? AdditionalPropertiesAllowed; AdditionalProperties = schema?.AdditionalProperties != null ? new(schema?.AdditionalProperties) : null; - Discriminator = schema?.Discriminator != null ? new(schema?.Discriminator) : null; - Example = OpenApiAnyCloneHelper.CloneFromCopyConstructor(schema?.Example); - Enum = schema?.Enum != null ? new List(schema.Enum) : 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; + Enum = schema?.Enum != null ? new List(schema.Enum) : null; Nullable = schema?.Nullable ?? Nullable; ExternalDocs = schema?.ExternalDocs != null ? new(schema?.ExternalDocs) : null; Deprecated = schema?.Deprecated ?? Deprecated; @@ -297,54 +400,33 @@ public OpenApiSchema(OpenApiSchema schema) } /// - /// Serialize to Open Api v3.0 + /// Serialize to Open Api v3.1 /// - public void SerializeAsV3(IOpenApiWriter writer) + public virtual void SerializeAsV31(IOpenApiWriter writer) { - Utils.CheckArgumentNull(writer); - - var settings = writer.GetSettings(); - var target = this; - - if (Reference != null) - { - if (!settings.ShouldInlineReference(Reference)) - { - Reference.SerializeAsV3(writer); - return; - } - else - { - if (Reference.IsExternal) // Temporary until v2 - { - target = this.GetEffective(Reference.HostDocument); - } - } - - // If Loop is detected then just Serialize as a reference. - if (!settings.LoopDetector.PushLoop(this)) - { - settings.LoopDetector.SaveLoop(this); - Reference.SerializeAsV3(writer); - return; - } - } - - target.SerializeAsV3WithoutReference(writer); - - if (Reference != null) - { - settings.LoopDetector.PopLoop(); - } + 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 void SerializeAsV3WithoutReference(IOpenApiWriter writer) + public virtual void SerializeAsV3(IOpenApiWriter writer) + { + SerializeInternal(writer, OpenApiSpecVersion.OpenApi3_0, (writer, element) => element.SerializeAsV3(writer)); + } + +/// + + public void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion version, + Action callback) { writer.WriteStartObject(); + if (version == OpenApiSpecVersion.OpenApi3_1) + { + WriteV31Properties(writer); + } + // title writer.WriteProperty(OpenApiConstants.Title, Title); @@ -394,25 +476,28 @@ public void SerializeAsV3WithoutReference(IOpenApiWriter writer) writer.WriteOptionalCollection(OpenApiConstants.Enum, Enum, (nodeWriter, s) => nodeWriter.WriteAny(s)); // type - writer.WriteProperty(OpenApiConstants.Type, Type); + if (Type is not null) + { + SerializeTypeProperty(Type, writer, version); + } // allOf - writer.WriteOptionalCollection(OpenApiConstants.AllOf, AllOf, (w, s) => s.SerializeAsV3(w)); + writer.WriteOptionalCollection(OpenApiConstants.AllOf, AllOf, callback); // anyOf - writer.WriteOptionalCollection(OpenApiConstants.AnyOf, AnyOf, (w, s) => s.SerializeAsV3(w)); + writer.WriteOptionalCollection(OpenApiConstants.AnyOf, AnyOf, callback); // oneOf - writer.WriteOptionalCollection(OpenApiConstants.OneOf, OneOf, (w, s) => s.SerializeAsV3(w)); + writer.WriteOptionalCollection(OpenApiConstants.OneOf, OneOf, callback); // not - writer.WriteOptionalObject(OpenApiConstants.Not, Not, (w, s) => s.SerializeAsV3(w)); + writer.WriteOptionalObject(OpenApiConstants.Not, Not, callback); // items - writer.WriteOptionalObject(OpenApiConstants.Items, Items, (w, s) => s.SerializeAsV3(w)); + writer.WriteOptionalObject(OpenApiConstants.Items, Items, callback); // properties - writer.WriteOptionalMap(OpenApiConstants.Properties, Properties, (w, s) => s.SerializeAsV3(w)); + writer.WriteOptionalMap(OpenApiConstants.Properties, Properties, callback); // additionalProperties if (AdditionalPropertiesAllowed) @@ -420,7 +505,7 @@ public void SerializeAsV3WithoutReference(IOpenApiWriter writer) writer.WriteOptionalObject( OpenApiConstants.AdditionalProperties, AdditionalProperties, - (w, s) => s.SerializeAsV3(w)); + callback); } else { @@ -437,10 +522,13 @@ public void SerializeAsV3WithoutReference(IOpenApiWriter writer) writer.WriteOptionalObject(OpenApiConstants.Default, Default, (w, d) => w.WriteAny(d)); // nullable - writer.WriteProperty(OpenApiConstants.Nullable, Nullable, false); + if (version is OpenApiSpecVersion.OpenApi3_0) + { + writer.WriteProperty(OpenApiConstants.Nullable, Nullable, false); + } // discriminator - writer.WriteOptionalObject(OpenApiConstants.Discriminator, Discriminator, (w, s) => s.SerializeAsV3(w)); + writer.WriteOptionalObject(OpenApiConstants.Discriminator, Discriminator, callback); // readOnly writer.WriteProperty(OpenApiConstants.ReadOnly, ReadOnly, false); @@ -452,7 +540,7 @@ public void SerializeAsV3WithoutReference(IOpenApiWriter writer) writer.WriteOptionalObject(OpenApiConstants.Xml, Xml, (w, s) => s.SerializeAsV2(w)); // externalDocs - writer.WriteOptionalObject(OpenApiConstants.ExternalDocs, ExternalDocs, (w, s) => s.SerializeAsV3(w)); + writer.WriteOptionalObject(OpenApiConstants.ExternalDocs, ExternalDocs, callback); // example writer.WriteOptionalObject(OpenApiConstants.Example, Example, (w, e) => w.WriteAny(e)); @@ -461,103 +549,38 @@ public void SerializeAsV3WithoutReference(IOpenApiWriter writer) writer.WriteProperty(OpenApiConstants.Deprecated, Deprecated, false); // extensions - writer.WriteExtensions(Extensions, OpenApiSpecVersion.OpenApi3_0); + writer.WriteExtensions(Extensions, version); writer.WriteEndObject(); } - /// - /// Serialize to Open Api v2.0 - /// - public void SerializeAsV2(IOpenApiWriter writer) - { - SerializeAsV2(writer: writer, parentRequiredProperties: new HashSet(), propertyName: null); - } +/// - /// - /// Serialize to OpenAPI V2 document without using reference. - /// - public void SerializeAsV2WithoutReference(IOpenApiWriter writer) - { - SerializeAsV2WithoutReference( - writer: writer, - parentRequiredProperties: new HashSet(), - propertyName: null); - } - - /// - /// Serialize to Open Api v2.0 and handles not marking the provided property - /// as readonly if its included in the provided list of required properties of parent schema. - /// - /// The open api writer. - /// The list of required properties in parent schema. - /// The property name that will be serialized. - internal void SerializeAsV2( - IOpenApiWriter writer, - ISet parentRequiredProperties, - string propertyName) + public virtual void SerializeAsV2(IOpenApiWriter writer) { - var settings = writer.GetSettings(); - var target = this; - - if (Reference != null) - { - if (!settings.ShouldInlineReference(Reference)) - { - Reference.SerializeAsV2(writer); - return; - } - else - { - if (Reference.IsExternal) // Temporary until v2 - { - target = this.GetEffective(Reference.HostDocument); - } - } - - // If Loop is detected then just Serialize as a reference. - if (!settings.LoopDetector.PushLoop(this)) - { - settings.LoopDetector.SaveLoop(this); - Reference.SerializeAsV2(writer); - return; - } - } - - if (parentRequiredProperties == null) - { - parentRequiredProperties = new HashSet(); - } - - target.SerializeAsV2WithoutReference(writer, parentRequiredProperties, propertyName); - - if (Reference != null) - { - settings.LoopDetector.PopLoop(); - } + SerializeAsV2(writer: writer, parentRequiredProperties: new HashSet(), propertyName: null); } - /// - /// Serialize to OpenAPI V2 document without using reference and handles not marking the provided property - /// as readonly if its included in the provided list of required properties of parent schema. - /// - /// The open api writer. - /// The list of required properties in parent schema. - /// The property name that will be serialized. - internal void SerializeAsV2WithoutReference( - IOpenApiWriter writer, - ISet parentRequiredProperties, - string propertyName) + internal void WriteV31Properties(IOpenApiWriter writer) { - writer.WriteStartObject(); - WriteAsSchemaProperties(writer, parentRequiredProperties, propertyName); - writer.WriteEndObject(); + writer.WriteProperty(OpenApiConstants.Id, Id); + writer.WriteProperty(OpenApiConstants.DollarSchema, Schema); + writer.WriteProperty(OpenApiConstants.Comment, Comment); + 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); + writer.WriteProperty(OpenApiConstants.DynamicAnchor, DynamicAnchor); + 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.WriteOptionalMap(OpenApiConstants.PatternProperties, PatternProperties, (w, s) => s.SerializeAsV31(w)); } internal void WriteAsItemsProperties(IOpenApiWriter writer) { // type - writer.WriteProperty(OpenApiConstants.Type, Type); + writer.WriteProperty(OpenApiConstants.Type, Type.ToIdentifier()); // format if (string.IsNullOrEmpty(Format)) @@ -620,11 +643,31 @@ internal void WriteAsItemsProperties(IOpenApiWriter writer) writer.WriteExtensions(Extensions, OpenApiSpecVersion.OpenApi2_0); } - internal void WriteAsSchemaProperties( + /// + /// Serialize to Open Api v2.0 and handles not marking the provided property + /// as readonly if its included in the provided list of required properties of parent schema. + /// + /// The open api writer. + /// The list of required properties in parent schema. + /// The property name that will be serialized. + internal void SerializeAsV2( IOpenApiWriter writer, ISet parentRequiredProperties, string propertyName) { + parentRequiredProperties ??= new HashSet(); + + writer.WriteStartObject(); + + // type + if (Type is not null) + { + SerializeTypeProperty(Type, writer, OpenApiSpecVersion.OpenApi2_0); + } + + // description + writer.WriteProperty(OpenApiConstants.Description, Description); + // format if (string.IsNullOrEmpty(Format)) { @@ -638,9 +681,6 @@ internal void WriteAsSchemaProperties( // title writer.WriteProperty(OpenApiConstants.Title, Title); - // description - writer.WriteProperty(OpenApiConstants.Description, Description); - // default writer.WriteOptionalObject(OpenApiConstants.Default, Default, (w, d) => w.WriteAny(d)); @@ -689,9 +729,6 @@ internal void WriteAsSchemaProperties( // enum writer.WriteOptionalCollection(OpenApiConstants.Enum, Enum, (w, s) => w.WriteAny(s)); - // type - writer.WriteProperty(OpenApiConstants.Type, Type); - // items writer.WriteOptionalObject(OpenApiConstants.Items, Items, (w, s) => s.SerializeAsV2(w)); @@ -751,21 +788,126 @@ internal void WriteAsSchemaProperties( // extensions writer.WriteExtensions(Extensions, OpenApiSpecVersion.OpenApi2_0); + + writer.WriteEndObject(); } - /// - /// Returns an effective OpenApiSchema object based on the presence of a $ref - /// - /// The host OpenApiDocument that contains the reference. - /// OpenApiSchema - public OpenApiSchema GetEffective(OpenApiDocument doc) + private void SerializeTypeProperty(JsonSchemaType? type, IOpenApiWriter writer, OpenApiSpecVersion version) + { + var flagsCount = CountEnumSetFlags(type); + if (flagsCount is 1) + { + // check whether nullable is true for upcasting purposes + if (version is OpenApiSpecVersion.OpenApi3_1 && (Nullable || Extensions.ContainsKey(OpenApiConstants.NullableExtension))) + { + UpCastSchemaTypeToV31(type, writer); + } + else + { + writer.WriteProperty(OpenApiConstants.Type, type.ToIdentifier()); + } + } + else if(flagsCount > 1) + { + // type + if (version is OpenApiSpecVersion.OpenApi2_0 || version is OpenApiSpecVersion.OpenApi3_0) + { + DowncastTypeArrayToV2OrV3(type, writer, version, flagsCount); + } + 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())); + } + } + } + } + + private static int CountEnumSetFlags(JsonSchemaType? schemaType) + { + 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 count; + } + + private void UpCastSchemaTypeToV31(JsonSchemaType? type, IOpenApiWriter writer) { - if (this.Reference != null) + // 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()); + } + } + + writer.WriteOptionalCollection(OpenApiConstants.Type, list, (w, s) => w.WriteValue(s)); + } + + private void DowncastTypeArrayToV2OrV3(JsonSchemaType? schemaType, IOpenApiWriter writer, OpenApiSpecVersion version, int flagsCount) + { + /* If the array has one non-null value, emit Type as string + * If the array has one null value, emit x-nullable as true + * If the array has two values, one null and one non-null, emit Type as string and x-nullable as true + * If the array has more than two values or two non-null values, do not emit type + * */ + + var nullableProp = version.Equals(OpenApiSpecVersion.OpenApi2_0) + ? OpenApiConstants.NullableExtension + : OpenApiConstants.Nullable; + + if (flagsCount is 1) { - return doc.ResolveReferenceTo(this.Reference); - } else + 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 { - return this; + foreach (JsonSchemaType? flag in System.Enum.GetValues(typeof(JsonSchemaType))) + { + // Skip if the flag is not set or if it's the Null flag + if (schemaType.Value.HasFlag(flag) && flag != JsonSchemaType.Null) + { + // Write the non-null flag value to the writer + writer.WriteProperty(OpenApiConstants.Type, flag.ToIdentifier()); + } + } + if (!Nullable) + { + writer.WriteProperty(nullableProp, true); + } } } } diff --git a/src/Microsoft.OpenApi/Models/OpenApiSecurityRequirement.cs b/src/Microsoft.OpenApi/Models/OpenApiSecurityRequirement.cs index 9fe4498e0..428d0649e 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiSecurityRequirement.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiSecurityRequirement.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. +using System; using System.Collections.Generic; using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Writers; @@ -28,12 +29,28 @@ public OpenApiSecurityRequirement() { } + /// + /// Serialize to Open Api v3.1 + /// + public void SerializeAsV31(IOpenApiWriter writer) + { + SerializeInternal(writer, (writer, element) => element.SerializeAsV31(writer)); + } + /// /// Serialize to Open Api v3.0 /// public void SerializeAsV3(IOpenApiWriter writer) { - Utils.CheckArgumentNull(writer); + SerializeInternal(writer, (writer, element) => element.SerializeAsV3(writer)); + } + + /// + /// Serialize + /// + private void SerializeInternal(IOpenApiWriter writer, Action callback) + { + Utils.CheckArgumentNull(writer);; writer.WriteStartObject(); @@ -70,7 +87,7 @@ public void SerializeAsV3(IOpenApiWriter writer) /// public void SerializeAsV2(IOpenApiWriter writer) { - Utils.CheckArgumentNull(writer); + Utils.CheckArgumentNull(writer);; writer.WriteStartObject(); diff --git a/src/Microsoft.OpenApi/Models/OpenApiSecurityScheme.cs b/src/Microsoft.OpenApi/Models/OpenApiSecurityScheme.cs index 7be47b5ca..33a07beda 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiSecurityScheme.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiSecurityScheme.cs @@ -17,50 +17,50 @@ public class OpenApiSecurityScheme : IOpenApiReferenceable, IOpenApiExtensible /// /// REQUIRED. The type of the security scheme. Valid values are "apiKey", "http", "oauth2", "openIdConnect". /// - public SecuritySchemeType Type { get; set; } + public virtual SecuritySchemeType Type { get; set; } /// /// A short description for security scheme. CommonMark syntax MAY be used for rich text representation. /// - public string Description { get; set; } + public virtual string Description { get; set; } /// /// REQUIRED. The name of the header, query or cookie parameter to be used. /// - public string Name { get; set; } + public virtual string Name { get; set; } /// /// REQUIRED. The location of the API key. Valid values are "query", "header" or "cookie". /// - public ParameterLocation In { get; set; } + public virtual ParameterLocation In { get; set; } /// /// REQUIRED. The name of the HTTP Authorization scheme to be used /// in the Authorization header as defined in RFC7235. /// - public string Scheme { get; set; } + public virtual string Scheme { get; set; } /// /// A hint to the client to identify how the bearer token is formatted. /// Bearer tokens are usually generated by an authorization server, /// so this information is primarily for documentation purposes. /// - public string BearerFormat { get; set; } + public virtual string BearerFormat { get; set; } /// /// REQUIRED. An object containing configuration information for the flow types supported. /// - public OpenApiOAuthFlows Flows { get; set; } + public virtual OpenApiOAuthFlows Flows { get; set; } /// /// REQUIRED. OpenId Connect URL to discover OAuth2 configuration values. /// - public Uri OpenIdConnectUrl { get; set; } + public virtual Uri OpenIdConnectUrl { get; set; } /// /// Specification Extensions. /// - public IDictionary Extensions { get; set; } = new Dictionary(); + public virtual IDictionary Extensions { get; set; } = new Dictionary(); /// /// Indicates if object is populated with data or is just a reference to the data @@ -96,26 +96,26 @@ public OpenApiSecurityScheme(OpenApiSecurityScheme securityScheme) } /// - /// Serialize to Open Api v3.0 + /// Serialize to Open Api v3.1 /// - public void SerializeAsV3(IOpenApiWriter writer) + public virtual void SerializeAsV31(IOpenApiWriter writer) { - Utils.CheckArgumentNull(writer); - - if (Reference != null) - { - Reference.SerializeAsV3(writer); - return; - } - - SerializeAsV3WithoutReference(writer); + 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 void SerializeAsV3WithoutReference(IOpenApiWriter writer) + public virtual void SerializeAsV3(IOpenApiWriter writer) + { + SerializeInternal(writer, OpenApiSpecVersion.OpenApi3_0, (writer, element) => element.SerializeAsV3(writer)); + } + + internal virtual void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion version, + Action callback) { + Utils.CheckArgumentNull(writer); + writer.WriteStartObject(); // type @@ -143,7 +143,7 @@ public void SerializeAsV3WithoutReference(IOpenApiWriter writer) case SecuritySchemeType.OAuth2: // This property apply to oauth2 type only. // flows - writer.WriteOptionalObject(OpenApiConstants.Flows, Flows, (w, o) => o.SerializeAsV3(w)); + writer.WriteOptionalObject(OpenApiConstants.Flows, Flows, callback); break; case SecuritySchemeType.OpenIdConnect: // This property apply to openIdConnect only. @@ -153,7 +153,7 @@ public void SerializeAsV3WithoutReference(IOpenApiWriter writer) } // extensions - writer.WriteExtensions(Extensions, OpenApiSpecVersion.OpenApi3_0); + writer.WriteExtensions(Extensions, version); writer.WriteEndObject(); } @@ -161,24 +161,10 @@ public void SerializeAsV3WithoutReference(IOpenApiWriter writer) /// /// Serialize to Open Api v2.0 /// - public void SerializeAsV2(IOpenApiWriter writer) + public virtual void SerializeAsV2(IOpenApiWriter writer) { Utils.CheckArgumentNull(writer); - if (Reference != null) - { - Reference.SerializeAsV2(writer); - return; - } - - SerializeAsV2WithoutReference(writer); - } - - /// - /// Serialize to OpenAPI V2 document without using reference. - /// - public void SerializeAsV2WithoutReference(IOpenApiWriter writer) - { if (Type == SecuritySchemeType.Http && Scheme != OpenApiConstants.Basic) { // Bail because V2 does not support non-basic HTTP scheme diff --git a/src/Microsoft.OpenApi/Models/OpenApiServer.cs b/src/Microsoft.OpenApi/Models/OpenApiServer.cs index 836303579..b580f7fbb 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiServer.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiServer.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. +using System; using System.Collections.Generic; using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Writers; @@ -38,7 +39,7 @@ public class OpenApiServer : IOpenApiSerializable, IOpenApiExtensible /// /// Parameterless constructor /// - public OpenApiServer() {} + public OpenApiServer() { } /// /// Initializes a copy of an object @@ -51,12 +52,29 @@ public OpenApiServer(OpenApiServer server) Extensions = server?.Extensions != null ? new Dictionary(server.Extensions) : null; } + /// + /// Serialize to Open Api v3.1 + /// + public void SerializeAsV31(IOpenApiWriter writer) + { + SerializeInternal(writer, OpenApiSpecVersion.OpenApi3_1, (writer, element) => element.SerializeAsV31(writer)); + } + /// /// Serialize to Open Api v3.0 /// public void SerializeAsV3(IOpenApiWriter writer) { - Utils.CheckArgumentNull(writer); + SerializeInternal(writer, OpenApiSpecVersion.OpenApi3_0, (writer, element) => element.SerializeAsV3(writer)); + } + + /// + /// Serialize to Open Api v3.0 + /// + private void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion version, + Action callback) + { + Utils.CheckArgumentNull(writer);; writer.WriteStartObject(); @@ -67,10 +85,10 @@ public void SerializeAsV3(IOpenApiWriter writer) writer.WriteProperty(OpenApiConstants.Description, Description); // variables - writer.WriteOptionalMap(OpenApiConstants.Variables, Variables, (w, v) => v.SerializeAsV3(w)); + writer.WriteOptionalMap(OpenApiConstants.Variables, Variables, callback); // specification extensions - writer.WriteExtensions(Extensions, OpenApiSpecVersion.OpenApi3_0); + writer.WriteExtensions(Extensions, version); writer.WriteEndObject(); } diff --git a/src/Microsoft.OpenApi/Models/OpenApiServerVariable.cs b/src/Microsoft.OpenApi/Models/OpenApiServerVariable.cs index 4ab8bdcaa..615b8dc37 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiServerVariable.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiServerVariable.cs @@ -39,7 +39,7 @@ public class OpenApiServerVariable : IOpenApiSerializable, IOpenApiExtensible /// /// Parameterless constructor /// - public OpenApiServerVariable() {} + public OpenApiServerVariable() { } /// /// Initializes a copy of an object @@ -52,12 +52,28 @@ public OpenApiServerVariable(OpenApiServerVariable serverVariable) Extensions = serverVariable?.Extensions != null ? new Dictionary(serverVariable?.Extensions) : serverVariable?.Extensions; } + /// + /// Serialize to Open Api v3.1 + /// + public void SerializeAsV31(IOpenApiWriter writer) + { + SerializeInternal(writer, OpenApiSpecVersion.OpenApi3_1); + } + /// /// Serialize to Open Api v3.0 /// public void SerializeAsV3(IOpenApiWriter writer) { - Utils.CheckArgumentNull(writer); + SerializeInternal(writer, OpenApiSpecVersion.OpenApi3_0); + } + + /// + /// Serialize to Open Api v3.0 + /// + private void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion version) + { + Utils.CheckArgumentNull(writer);; writer.WriteStartObject(); @@ -71,7 +87,7 @@ public void SerializeAsV3(IOpenApiWriter writer) writer.WriteOptionalCollection(OpenApiConstants.Enum, Enum, (w, s) => w.WriteValue(s)); // specification extensions - writer.WriteExtensions(Extensions, OpenApiSpecVersion.OpenApi3_0); + writer.WriteExtensions(Extensions, version); writer.WriteEndObject(); } diff --git a/src/Microsoft.OpenApi/Models/OpenApiTag.cs b/src/Microsoft.OpenApi/Models/OpenApiTag.cs index f47287182..6f79e0999 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiTag.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiTag.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. +using System; using System.Collections.Generic; using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Writers; @@ -15,22 +16,22 @@ public class OpenApiTag : IOpenApiReferenceable, IOpenApiExtensible /// /// The name of the tag. /// - public string Name { get; set; } + public virtual string Name { get; set; } /// /// A short description for the tag. /// - public string Description { get; set; } + public virtual string Description { get; set; } /// /// Additional external documentation for this tag. /// - public OpenApiExternalDocs ExternalDocs { get; set; } + public virtual OpenApiExternalDocs ExternalDocs { get; set; } /// /// This object MAY be extended with Specification Extensions. /// - public IDictionary Extensions { get; set; } = new Dictionary(); + public virtual IDictionary Extensions { get; set; } = new Dictionary(); /// /// Indicates if object is populated with data or is just a reference to the data @@ -45,7 +46,7 @@ public class OpenApiTag : IOpenApiReferenceable, IOpenApiExtensible /// /// Parameterless constructor /// - public OpenApiTag() {} + public OpenApiTag() { } /// /// Initializes a copy of an object @@ -61,25 +62,50 @@ public OpenApiTag(OpenApiTag tag) } /// - /// Serialize to Open Api v3.0 + /// Serialize to Open Api v3.1 /// - public void SerializeAsV3(IOpenApiWriter writer) + public virtual void SerializeAsV31(IOpenApiWriter writer) { - Utils.CheckArgumentNull(writer); + SerializeInternal(writer, (writer, element) => element.SerializeAsV31(writer)); + } - if (Reference != null) - { - Reference.SerializeAsV3(writer); - return; - } + /// + /// 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 void SerializeAsV3WithoutReference(IOpenApiWriter writer) + public virtual void SerializeAsV31WithoutReference(IOpenApiWriter writer) + { + SerializeInternalWithoutReference(writer, OpenApiSpecVersion.OpenApi3_1, + (writer, element) => element.SerializeAsV31(writer)); + } + + /// + /// Serialize to OpenAPI V3 document without using reference. + /// + public virtual void SerializeAsV3WithoutReference(IOpenApiWriter writer) + { + SerializeInternalWithoutReference(writer, OpenApiSpecVersion.OpenApi3_0, + (writer, element) => element.SerializeAsV3(writer)); + } + + internal virtual void SerializeInternalWithoutReference(IOpenApiWriter writer, OpenApiSpecVersion version, + Action callback) { writer.WriteStartObject(); @@ -90,10 +116,10 @@ public void SerializeAsV3WithoutReference(IOpenApiWriter writer) writer.WriteProperty(OpenApiConstants.Description, Description); // external docs - writer.WriteOptionalObject(OpenApiConstants.ExternalDocs, ExternalDocs, (w, e) => e.SerializeAsV3(w)); + writer.WriteOptionalObject(OpenApiConstants.ExternalDocs, ExternalDocs, callback); // extensions. - writer.WriteExtensions(Extensions, OpenApiSpecVersion.OpenApi3_0); + writer.WriteExtensions(Extensions, version); writer.WriteEndObject(); } @@ -101,16 +127,9 @@ public void SerializeAsV3WithoutReference(IOpenApiWriter writer) /// /// Serialize to Open Api v2.0 /// - public void SerializeAsV2(IOpenApiWriter writer) + public virtual void SerializeAsV2(IOpenApiWriter writer) { Utils.CheckArgumentNull(writer); - - if (Reference != null) - { - Reference.SerializeAsV2(writer); - return; - } - writer.WriteValue(Name); } diff --git a/src/Microsoft.OpenApi/Models/OpenApiXml.cs b/src/Microsoft.OpenApi/Models/OpenApiXml.cs index ff1d196d4..d0ee6a00b 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiXml.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiXml.cs @@ -48,7 +48,7 @@ public class OpenApiXml : IOpenApiSerializable, IOpenApiExtensible /// /// Parameterless constructor /// - public OpenApiXml() {} + public OpenApiXml() { } /// /// Initializes a copy of an object @@ -63,6 +63,14 @@ public OpenApiXml(OpenApiXml xml) Extensions = xml?.Extensions != null ? new Dictionary(xml.Extensions) : null; } + /// + /// Serialize to Open Api v3.0 + /// + public void SerializeAsV31(IOpenApiWriter writer) + { + Write(writer, OpenApiSpecVersion.OpenApi3_1); + } + /// /// Serialize to Open Api v3.0 /// @@ -81,6 +89,8 @@ public void SerializeAsV2(IOpenApiWriter writer) private void Write(IOpenApiWriter writer, OpenApiSpecVersion specVersion) { + Utils.CheckArgumentNull(writer);; + writer.WriteStartObject(); // name diff --git a/src/Microsoft.OpenApi/Models/ReferenceType.cs b/src/Microsoft.OpenApi/Models/ReferenceType.cs index acbb22a9a..25bbe20dd 100644 --- a/src/Microsoft.OpenApi/Models/ReferenceType.cs +++ b/src/Microsoft.OpenApi/Models/ReferenceType.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 Microsoft.OpenApi.Attributes; @@ -61,8 +61,8 @@ public enum ReferenceType [Display("tags")] Tag, /// - /// Paths item. + /// Path item. /// - [Display("paths")] Path + [Display("pathItems")] PathItem } } diff --git a/src/Microsoft.OpenApi/Models/References/OpenApiCallbackReference.cs b/src/Microsoft.OpenApi/Models/References/OpenApiCallbackReference.cs new file mode 100644 index 000000000..632aa485f --- /dev/null +++ b/src/Microsoft.OpenApi/Models/References/OpenApiCallbackReference.cs @@ -0,0 +1,107 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; +using Microsoft.OpenApi.Expressions; +using Microsoft.OpenApi.Interfaces; +using Microsoft.OpenApi.Writers; + +namespace Microsoft.OpenApi.Models.References +{ + /// + /// Callback Object Reference: A reference to a map of possible out-of band callbacks related to the parent operation. + /// + public class OpenApiCallbackReference : OpenApiCallback + { + internal OpenApiCallback _target; + private readonly OpenApiReference _reference; + + private OpenApiCallback Target + { + get + { + _target ??= Reference.HostDocument.ResolveReferenceTo(_reference); + return _target; + } + } + + /// + /// Constructor initializing the reference object. + /// + /// The reference Id. + /// The host OpenAPI document. + /// Optional: External resource in the reference. + /// It may be: + /// 1. an absolute/relative file path, for example: ../commons/pet.json + /// 2. a Url, for example: http://localhost/pet.json + /// + public OpenApiCallbackReference(string referenceId, OpenApiDocument hostDocument, string externalResource = null) + { + Utils.CheckArgumentNullOrEmpty(referenceId); + + _reference = new OpenApiReference() + { + Id = referenceId, + HostDocument = hostDocument, + Type = ReferenceType.Callback, + ExternalResource = externalResource + }; + + Reference = _reference; + } + + internal OpenApiCallbackReference(OpenApiCallback target, string referenceId) + { + _target = target; + + _reference = new OpenApiReference() + { + Id = referenceId, + Type = ReferenceType.Callback, + }; + } + + /// + public override Dictionary PathItems { get => Target.PathItems; set => Target.PathItems = value; } + + /// + public override IDictionary Extensions { get => Target.Extensions; set => Target.Extensions = value; } + + /// + public override void SerializeAsV3(IOpenApiWriter writer) + { + if (!writer.GetSettings().ShouldInlineReference(_reference)) + { + _reference.SerializeAsV3(writer); + return; + } + else + { + SerializeInternal(writer, (writer, element) => element.SerializeAsV3(writer)); + } + } + + /// + public override void SerializeAsV31(IOpenApiWriter writer) + { + if (!writer.GetSettings().ShouldInlineReference(_reference)) + { + _reference.SerializeAsV31(writer); + return; + } + else + { + SerializeInternal(writer, (writer, element) => element.SerializeAsV31(writer)); + } + } + + /// + private void SerializeInternal(IOpenApiWriter writer, + Action action) + { + Utils.CheckArgumentNull(writer); + action(writer, Target); + } + } +} diff --git a/src/Microsoft.OpenApi/Models/References/OpenApiExampleReference.cs b/src/Microsoft.OpenApi/Models/References/OpenApiExampleReference.cs new file mode 100644 index 000000000..310ff0a8e --- /dev/null +++ b/src/Microsoft.OpenApi/Models/References/OpenApiExampleReference.cs @@ -0,0 +1,129 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; +using System.Text.Json.Nodes; +using Microsoft.OpenApi.Interfaces; +using Microsoft.OpenApi.Writers; + +namespace Microsoft.OpenApi.Models.References +{ + /// + /// Example Object Reference. + /// + public class OpenApiExampleReference : OpenApiExample + { + internal OpenApiExample _target; + private readonly OpenApiReference _reference; + private string _summary; + private string _description; + + private OpenApiExample Target + { + get + { + _target ??= Reference.HostDocument.ResolveReferenceTo(_reference); + OpenApiExample resolved = new OpenApiExample(_target); + if (!string.IsNullOrEmpty(_description)) resolved.Description = _description; + if (!string.IsNullOrEmpty(_summary)) resolved.Summary = _summary; + return resolved; + } + } + + /// + /// Constructor initializing the reference object. + /// + /// The reference Id. + /// The host OpenAPI document. + /// Optional: External resource in the reference. + /// It may be: + /// 1. a absolute/relative file path, for example: ../commons/pet.json + /// 2. a Url, for example: http://localhost/pet.json + /// + public OpenApiExampleReference(string referenceId, OpenApiDocument hostDocument, string externalResource = null) + { + Utils.CheckArgumentNullOrEmpty(referenceId); + + _reference = new OpenApiReference() + { + Id = referenceId, + HostDocument = hostDocument, + Type = ReferenceType.Example, + ExternalResource = externalResource + }; + + Reference = _reference; + } + + internal OpenApiExampleReference(OpenApiExample target, string referenceId) + { + _target = target; + + _reference = new OpenApiReference() + { + Id = referenceId, + Type = ReferenceType.Example, + }; + } + + /// + public override string Description + { + get => string.IsNullOrEmpty(_description) ? Target.Description : _description; + set => _description = value; + } + + /// + public override string Summary + { + get => string.IsNullOrEmpty(_summary) ? Target.Summary : _summary; + set => _summary = value; + } + + /// + public override IDictionary Extensions { get => Target.Extensions; set => Target.Extensions = value; } + + /// + public override string ExternalValue { get => Target.ExternalValue; set => Target.ExternalValue = value; } + + /// + public override JsonNode Value { get => Target.Value; set => Target.Value = value; } + + /// + public override void SerializeAsV3(IOpenApiWriter writer) + { + if (!writer.GetSettings().ShouldInlineReference(_reference)) + { + _reference.SerializeAsV3(writer); + return; + } + else + { + SerializeInternal(writer, (writer, referenceElement) => referenceElement.SerializeAsV3(writer)); + } + } + + /// + public override void SerializeAsV31(IOpenApiWriter writer) + { + if (!writer.GetSettings().ShouldInlineReference(_reference)) + { + _reference.SerializeAsV31(writer); + return; + } + else + { + SerializeInternal(writer, (writer, referenceElement) => referenceElement.SerializeAsV31(writer)); + } + } + + /// + private void SerializeInternal(IOpenApiWriter writer, + Action action) + { + Utils.CheckArgumentNull(writer); + action(writer, Target); + } + } +} diff --git a/src/Microsoft.OpenApi/Models/References/OpenApiHeaderReference.cs b/src/Microsoft.OpenApi/Models/References/OpenApiHeaderReference.cs new file mode 100644 index 000000000..2ffb0c3de --- /dev/null +++ b/src/Microsoft.OpenApi/Models/References/OpenApiHeaderReference.cs @@ -0,0 +1,158 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; +using System.Text.Json.Nodes; +using Microsoft.OpenApi.Interfaces; +using Microsoft.OpenApi.Writers; + +namespace Microsoft.OpenApi.Models.References +{ + /// + /// Header Object Reference. + /// + public class OpenApiHeaderReference : OpenApiHeader + { + internal OpenApiHeader _target; + private readonly OpenApiReference _reference; + private string _description; + + private OpenApiHeader Target + { + get + { + _target ??= Reference.HostDocument.ResolveReferenceTo(_reference); + OpenApiHeader resolved = new OpenApiHeader(_target); + if (!string.IsNullOrEmpty(_description)) resolved.Description = _description; + return resolved; + } + } + + /// + /// Constructor initializing the reference object. + /// + /// The reference Id. + /// The host OpenAPI document. + /// Optional: External resource in the reference. + /// It may be: + /// 1. a absolute/relative file path, for example: ../commons/pet.json + /// 2. a Url, for example: http://localhost/pet.json + /// + public OpenApiHeaderReference(string referenceId, OpenApiDocument hostDocument, string externalResource = null) + { + Utils.CheckArgumentNullOrEmpty(referenceId); + + _reference = new OpenApiReference() + { + Id = referenceId, + HostDocument = hostDocument, + Type = ReferenceType.Header, + ExternalResource = externalResource + }; + + Reference = _reference; + } + + internal OpenApiHeaderReference(OpenApiHeader target, string referenceId) + { + _target = target; + + _reference = new OpenApiReference() + { + Id = referenceId, + Type = ReferenceType.Header, + }; + } + + /// + public override string Description + { + get => string.IsNullOrEmpty(_description) ? Target.Description : _description; + set => _description = value; + } + + /// + public override bool Required { get => Target.Required; set => Target.Required = value; } + + /// + public override bool Deprecated { get => Target.Deprecated; set => Target.Deprecated = value; } + + /// + public override bool AllowEmptyValue { get => Target.AllowEmptyValue; set => Target.AllowEmptyValue = value; } + + /// + public override OpenApiSchema Schema { get => Target.Schema; set => Target.Schema = value; } + + /// + public override ParameterStyle? Style { get => Target.Style; set => Target.Style = value; } + + /// + public override bool Explode { get => Target.Explode; set => Target.Explode = value; } + + /// + public override bool AllowReserved { get => Target.AllowReserved; set => Target.AllowReserved = value; } + + /// + public override JsonNode Example { get => Target.Example; set => Target.Example = value; } + + /// + public override IDictionary Examples { get => Target.Examples; set => Target.Examples = value; } + + /// + public override IDictionary Content { get => Target.Content; set => Target.Content = value; } + + /// + public override IDictionary Extensions { get => base.Extensions; set => base.Extensions = value; } + + /// + public override void SerializeAsV31(IOpenApiWriter writer) + { + if (!writer.GetSettings().ShouldInlineReference(_reference)) + { + _reference.SerializeAsV31(writer); + return; + } + else + { + SerializeInternal(writer, (writer, element) => element.SerializeAsV31(writer)); + } + } + + /// + public override void SerializeAsV3(IOpenApiWriter writer) + { + if (!writer.GetSettings().ShouldInlineReference(_reference)) + { + _reference.SerializeAsV3(writer); + return; + } + else + { + SerializeInternal(writer, (writer, element) => element.SerializeAsV3(writer)); + } + } + + /// + public override void SerializeAsV2(IOpenApiWriter writer) + { + if (!writer.GetSettings().ShouldInlineReference(_reference)) + { + _reference.SerializeAsV2(writer); + return; + } + else + { + SerializeInternal(writer, (writer, element) => element.SerializeAsV2(writer)); + } + } + + /// + private void SerializeInternal(IOpenApiWriter writer, + Action action) + { + Utils.CheckArgumentNull(writer); + action(writer, Target); + } + } +} diff --git a/src/Microsoft.OpenApi/Models/References/OpenApiLinkReference.cs b/src/Microsoft.OpenApi/Models/References/OpenApiLinkReference.cs new file mode 100644 index 000000000..a3c33503e --- /dev/null +++ b/src/Microsoft.OpenApi/Models/References/OpenApiLinkReference.cs @@ -0,0 +1,128 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; +using Microsoft.OpenApi.Interfaces; +using Microsoft.OpenApi.Writers; + +namespace Microsoft.OpenApi.Models.References +{ + /// + /// Link Object Reference. + /// + public class OpenApiLinkReference : OpenApiLink + { + internal OpenApiLink _target; + private readonly OpenApiReference _reference; + private string _description; + + private OpenApiLink Target + { + get + { + _target ??= Reference.HostDocument.ResolveReferenceTo(_reference); + OpenApiLink resolved = new OpenApiLink(_target); + if (!string.IsNullOrEmpty(_description)) resolved.Description = _description; + return resolved; + } + } + + /// + /// Constructor initializing the reference object. + /// + /// The reference Id. + /// The host OpenAPI document. + /// Optional: External resource in the reference. + /// It may be: + /// 1. a absolute/relative file path, for example: ../commons/pet.json + /// 2. a Url, for example: http://localhost/pet.json + /// + public OpenApiLinkReference(string referenceId, OpenApiDocument hostDocument, string externalResource = null) + { + Utils.CheckArgumentNullOrEmpty(referenceId); + + _reference = new OpenApiReference() + { + Id = referenceId, + HostDocument = hostDocument, + Type = ReferenceType.Link, + ExternalResource = externalResource + }; + + Reference = _reference; + } + + internal OpenApiLinkReference(OpenApiLink target, string referenceId) + { + _target = target; + + _reference = new OpenApiReference() + { + Id = referenceId, + Type = ReferenceType.Link, + }; + } + + /// + public override string OperationRef { get => Target.OperationRef; set => Target.OperationRef = value; } + + /// + public override string OperationId { get => Target.OperationId; set => Target.OperationId = value; } + + /// + public override OpenApiServer Server { get => Target.Server; set => Target.Server = value; } + + /// + public override string Description + { + get => string.IsNullOrEmpty(_description) ? Target.Description : _description; + set => _description = value; + } + + /// + public override Dictionary Parameters { get => Target.Parameters; set => Target.Parameters = value; } + + /// + public override RuntimeExpressionAnyWrapper RequestBody { get => Target.RequestBody; set => Target.RequestBody = value; } + + /// + public override IDictionary Extensions { get => base.Extensions; set => base.Extensions = value; } + + /// + public override void SerializeAsV3(IOpenApiWriter writer) + { + if (!writer.GetSettings().ShouldInlineReference(_reference)) + { + _reference.SerializeAsV3(writer); + return; + } + else + { + SerializeInternal(writer, (writer, element) => element.SerializeAsV3(writer)); + } + } + + /// + public override void SerializeAsV31(IOpenApiWriter writer) + { + if (!writer.GetSettings().ShouldInlineReference(_reference)) + { + _reference.SerializeAsV31(writer); + return; + } + else + { + SerializeInternal(writer, (writer, element) => element.SerializeAsV31(writer)); + } + } + + /// + private void SerializeInternal(IOpenApiWriter writer, + Action action) + { + Utils.CheckArgumentNull(writer);; + action(writer, Target); + } + } +} diff --git a/src/Microsoft.OpenApi/Models/References/OpenApiParameterReference.cs b/src/Microsoft.OpenApi/Models/References/OpenApiParameterReference.cs new file mode 100644 index 000000000..2c2a6c90d --- /dev/null +++ b/src/Microsoft.OpenApi/Models/References/OpenApiParameterReference.cs @@ -0,0 +1,174 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; +using System.Text.Json.Nodes; +using Microsoft.OpenApi.Interfaces; +using Microsoft.OpenApi.Writers; + +namespace Microsoft.OpenApi.Models.References +{ + /// + /// Parameter Object Reference. + /// + public class OpenApiParameterReference : OpenApiParameter + { + internal OpenApiParameter _target; + private readonly OpenApiReference _reference; + private string _description; + private bool? _explode; + private ParameterStyle? _style; + + private OpenApiParameter Target + { + get + { + _target ??= Reference.HostDocument.ResolveReferenceTo(_reference); + OpenApiParameter resolved = new OpenApiParameter(_target); + if (!string.IsNullOrEmpty(_description)) resolved.Description = _description; + return resolved; + } + } + + /// + /// Constructor initializing the reference object. + /// + /// The reference Id. + /// The host OpenAPI document. + /// Optional: External resource in the reference. + /// It may be: + /// 1. a absolute/relative file path, for example: ../commons/pet.json + /// 2. a Url, for example: http://localhost/pet.json + /// + public OpenApiParameterReference(string referenceId, OpenApiDocument hostDocument, string externalResource = null) + { + Utils.CheckArgumentNullOrEmpty(referenceId); + + _reference = new OpenApiReference() + { + Id = referenceId, + HostDocument = hostDocument, + Type = ReferenceType.Parameter, + ExternalResource = externalResource + }; + + Reference = _reference; + } + + internal OpenApiParameterReference(OpenApiParameter target, string referenceId) + { + _target = target; + + _reference = new OpenApiReference() + { + Id = referenceId, + Type = ReferenceType.Parameter, + }; + } + + /// + public override string Name { get => Target.Name; set => Target.Name = value; } + + /// + public override string Description + { + get => string.IsNullOrEmpty(_description) ? Target.Description : _description; + set => _description = value; + } + + /// + public override bool Required { get => Target.Required; set => Target.Required = value; } + + /// + public override bool Deprecated { get => Target.Deprecated; set => Target.Deprecated = value; } + + /// + public override bool AllowEmptyValue { get => Target.AllowEmptyValue; set => Target.AllowEmptyValue = value; } + + /// + public override bool AllowReserved { get => Target.AllowReserved; set => Target.AllowReserved = value; } + + /// + public override OpenApiSchema Schema { get => Target.Schema; set => Target.Schema = value; } + + /// + public override IDictionary Examples { get => Target.Examples; set => Target.Examples = value; } + + /// + public override JsonNode Example { get => Target.Example; set => Target.Example = value; } + + /// + public override ParameterLocation? In { get => Target.In; set => Target.In = value; } + + /// + public override ParameterStyle? Style + { + get => _style ?? GetDefaultStyleValue(); + set => _style = value; + } + + /// + public override bool Explode + { + get => _explode ?? Style == ParameterStyle.Form; + set => _explode = value; + } + + /// + public override IDictionary Content { get => Target.Content; set => Target.Content = value; } + + /// + public override IDictionary Extensions { get => Target.Extensions; set => Target.Extensions = value; } + + /// + public override void SerializeAsV3(IOpenApiWriter writer) + { + if (!writer.GetSettings().ShouldInlineReference(_reference)) + { + _reference.SerializeAsV3(writer); + return; + } + else + { + SerializeInternal(writer, (writer, element) => element.SerializeAsV3(writer)); + } + } + + /// + public override void SerializeAsV31(IOpenApiWriter writer) + { + if (!writer.GetSettings().ShouldInlineReference(_reference)) + { + _reference.SerializeAsV31(writer); + return; + } + else + { + SerializeInternal(writer, (writer, element) => element.SerializeAsV31(writer)); + } + } + + /// + public override void SerializeAsV2(IOpenApiWriter writer) + { + if (!writer.GetSettings().ShouldInlineReference(_reference)) + { + _reference.SerializeAsV2(writer); + return; + } + else + { + SerializeInternal(writer, (writer, element) => element.SerializeAsV2(writer)); + } + } + + /// + private void SerializeInternal(IOpenApiWriter writer, + Action action) + { + Utils.CheckArgumentNull(writer); + action(writer, Target); + } + } +} diff --git a/src/Microsoft.OpenApi/Models/References/OpenApiPathItemReference.cs b/src/Microsoft.OpenApi/Models/References/OpenApiPathItemReference.cs new file mode 100644 index 000000000..f757b7a07 --- /dev/null +++ b/src/Microsoft.OpenApi/Models/References/OpenApiPathItemReference.cs @@ -0,0 +1,117 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; +using Microsoft.OpenApi.Interfaces; +using Microsoft.OpenApi.Writers; + +namespace Microsoft.OpenApi.Models.References +{ + /// + /// Path Item Object Reference: to describe the operations available on a single path. + /// + public class OpenApiPathItemReference : OpenApiPathItem + { + internal OpenApiPathItem _target; + private readonly OpenApiReference _reference; + private string _description; + private string _summary; + + private OpenApiPathItem Target + { + get + { + _target ??= Reference.HostDocument.ResolveReferenceTo(_reference); + OpenApiPathItem resolved = new OpenApiPathItem(_target); + if (!string.IsNullOrEmpty(_description)) resolved.Description = _description; + if (!string.IsNullOrEmpty(_summary)) resolved.Summary = _summary; + return resolved; + } + } + + /// + /// Constructor initializing the reference object. + /// + /// The reference Id. + /// The host OpenAPI document. + /// Optional: External resource in the reference. + /// It may be: + /// 1. a absolute/relative file path, for example: ../commons/pet.json + /// 2. a Url, for example: http://localhost/pet.json + /// + public OpenApiPathItemReference(string referenceId, OpenApiDocument hostDocument, string externalResource = null) + { + Utils.CheckArgumentNullOrEmpty(referenceId); + + _reference = new OpenApiReference() + { + Id = referenceId, + HostDocument = hostDocument, + Type = ReferenceType.PathItem, + ExternalResource = externalResource + }; + + Reference = _reference; + } + + internal OpenApiPathItemReference(OpenApiPathItem target, string referenceId) + { + _target = target; + + _reference = new OpenApiReference() + { + Id = referenceId, + Type = ReferenceType.PathItem, + }; + } + + /// + public override string Summary + { + get => string.IsNullOrEmpty(_summary) ? Target.Summary : _summary; + set => _summary = value; + } + + /// + public override string Description + { + get => string.IsNullOrEmpty(_description) ? Target.Description : _description; + set => _description = value; + } + + /// + public override IDictionary Operations { get => Target.Operations; set => Target.Operations = value; } + + /// + public override IList Servers { get => Target.Servers; set => Target.Servers = value; } + + /// + public override IList Parameters { get => Target.Parameters; set => Target.Parameters = value; } + + /// + public override IDictionary Extensions { get => Target.Extensions; set => Target.Extensions = value; } + + /// + public override void SerializeAsV31(IOpenApiWriter writer) + { + if (!writer.GetSettings().ShouldInlineReference(_reference)) + { + _reference.SerializeAsV31(writer); + return; + } + else + { + SerializeInternal(writer, (writer, element) => element.SerializeAsV31(writer)); + } + } + + /// + private void SerializeInternal(IOpenApiWriter writer, + Action action) + { + Utils.CheckArgumentNull(writer);; + action(writer, Target); + } + } +} diff --git a/src/Microsoft.OpenApi/Models/References/OpenApiRequestBodyReference.cs b/src/Microsoft.OpenApi/Models/References/OpenApiRequestBodyReference.cs new file mode 100644 index 000000000..8e3a81ad8 --- /dev/null +++ b/src/Microsoft.OpenApi/Models/References/OpenApiRequestBodyReference.cs @@ -0,0 +1,119 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; +using Microsoft.OpenApi.Interfaces; +using Microsoft.OpenApi.Writers; + +namespace Microsoft.OpenApi.Models.References +{ + /// + /// Request Body Object Reference. + /// + public class OpenApiRequestBodyReference : OpenApiRequestBody + { + internal OpenApiRequestBody _target; + private readonly OpenApiReference _reference; + private string _description; + + private OpenApiRequestBody Target + { + get + { + _target ??= Reference.HostDocument.ResolveReferenceTo(_reference); + OpenApiRequestBody resolved = new OpenApiRequestBody(_target); + if (!string.IsNullOrEmpty(_description)) resolved.Description = _description; + return resolved; + } + } + + /// + /// Constructor initializing the reference object. + /// + /// The reference Id. + /// The host OpenAPI document. + /// Optional: External resource in the reference. + /// It may be: + /// 1. a absolute/relative file path, for example: ../commons/pet.json + /// 2. a Url, for example: http://localhost/pet.json + /// + public OpenApiRequestBodyReference(string referenceId, OpenApiDocument hostDocument, string externalResource = null) + { + Utils.CheckArgumentNullOrEmpty(referenceId); + + _reference = new OpenApiReference() + { + Id = referenceId, + HostDocument = hostDocument, + Type = ReferenceType.RequestBody, + ExternalResource = externalResource + }; + + Reference = _reference; + } + + internal OpenApiRequestBodyReference(OpenApiRequestBody target, string referenceId) + { + _target = target; + + _reference = new OpenApiReference() + { + Id = referenceId, + Type = ReferenceType.RequestBody, + }; + } + + /// + public override string Description + { + get => string.IsNullOrEmpty(_description) ? Target.Description : _description; + set => _description = value; + } + + /// + public override IDictionary Content { get => Target.Content; set => Target.Content = value; } + + /// + public override bool Required { get => Target.Required; set => Target.Required = value; } + + /// + public override IDictionary Extensions { get => Target.Extensions; set => Target.Extensions = value; } + + /// + public override void SerializeAsV3(IOpenApiWriter writer) + { + if (!writer.GetSettings().ShouldInlineReference(_reference)) + { + _reference.SerializeAsV3(writer); + return; + } + else + { + SerializeInternal(writer, (writer, element) => element.SerializeAsV3(writer)); + } + } + + /// + public override void SerializeAsV31(IOpenApiWriter writer) + { + if (!writer.GetSettings().ShouldInlineReference(_reference)) + { + _reference.SerializeAsV31(writer); + return; + } + else + { + SerializeInternal(writer, (writer, element) => element.SerializeAsV31(writer)); + } + } + + /// + private void SerializeInternal(IOpenApiWriter writer, + Action action) + { + Utils.CheckArgumentNull(writer); + action(writer, Target); + } + } +} diff --git a/src/Microsoft.OpenApi/Models/References/OpenApiResponseReference.cs b/src/Microsoft.OpenApi/Models/References/OpenApiResponseReference.cs new file mode 100644 index 000000000..c24652504 --- /dev/null +++ b/src/Microsoft.OpenApi/Models/References/OpenApiResponseReference.cs @@ -0,0 +1,138 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; +using Microsoft.OpenApi.Interfaces; +using Microsoft.OpenApi.Writers; + +namespace Microsoft.OpenApi.Models.References +{ + /// + /// Response Object Reference. + /// + public class OpenApiResponseReference : OpenApiResponse + { + internal OpenApiResponse _target; + private readonly OpenApiReference _reference; + private string _description; + + private OpenApiResponse Target + { + get + { + _target ??= Reference.HostDocument?.ResolveReferenceTo(_reference); + OpenApiResponse resolved = new OpenApiResponse(_target); + if (!string.IsNullOrEmpty(_description)) resolved.Description = _description; + return resolved; + } + } + + /// + /// Constructor initializing the reference object. + /// + /// The reference Id. + /// The host OpenAPI document. + /// Optional: External resource in the reference. + /// It may be: + /// 1. a absolute/relative file path, for example: ../commons/pet.json + /// 2. a Url, for example: http://localhost/pet.json + /// + public OpenApiResponseReference(string referenceId, OpenApiDocument hostDocument, string externalResource = null) + { + Utils.CheckArgumentNullOrEmpty(referenceId); + + _reference = new OpenApiReference() + { + Id = referenceId, + HostDocument = hostDocument, + Type = ReferenceType.Response, + ExternalResource = externalResource + }; + + Reference = _reference; + } + + internal OpenApiResponseReference(string referenceId, OpenApiResponse target) + { + _target ??= target; + + _reference = new OpenApiReference() + { + Id = referenceId, + Type = ReferenceType.Response, + }; + + Reference = _reference; + } + + /// + public override string Description + { + get => string.IsNullOrEmpty(_description) ? Target.Description : _description; + set => _description = value; + } + + /// + public override IDictionary Content { get => Target?.Content; set => Target.Content = value; } + + /// + public override IDictionary Headers { get => Target.Headers; set => Target.Headers = value; } + + /// + public override IDictionary Links { get => Target.Links; set => Target.Links = value; } + + /// + public override IDictionary Extensions { get => Target.Extensions; set => Target.Extensions = value; } + + /// + public override void SerializeAsV3(IOpenApiWriter writer) + { + if (!writer.GetSettings().ShouldInlineReference(_reference)) + { + _reference.SerializeAsV3(writer); + return; + } + else + { + SerializeInternal(writer, (writer, element) => element.SerializeAsV3(writer)); + } + } + + /// + public override void SerializeAsV31(IOpenApiWriter writer) + { + if (!writer.GetSettings().ShouldInlineReference(_reference)) + { + _reference.SerializeAsV31(writer); + return; + } + else + { + SerializeInternal(writer, (writer, element) => element.SerializeAsV31(writer)); + } + } + + /// + public override void SerializeAsV2(IOpenApiWriter writer) + { + if (!writer.GetSettings().ShouldInlineReference(_reference)) + { + _reference.SerializeAsV2(writer); + return; + } + else + { + SerializeInternal(writer, (writer, element) => element.SerializeAsV2(writer)); + } + } + + /// + private void SerializeInternal(IOpenApiWriter writer, + Action action) + { + Utils.CheckArgumentNull(writer); + action(writer, this); + } + } +} diff --git a/src/Microsoft.OpenApi/Models/References/OpenApiSchemaReference.cs b/src/Microsoft.OpenApi/Models/References/OpenApiSchemaReference.cs new file mode 100644 index 000000000..a7b55e109 --- /dev/null +++ b/src/Microsoft.OpenApi/Models/References/OpenApiSchemaReference.cs @@ -0,0 +1,235 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using Microsoft.OpenApi.Interfaces; +using Microsoft.OpenApi.Writers; +using System; +using System.Collections.Generic; +using System.Text.Json.Nodes; + +namespace Microsoft.OpenApi.Models.References +{ + /// + /// Schema reference object + /// + public class OpenApiSchemaReference : OpenApiSchema + { + internal OpenApiSchema _target; + private readonly OpenApiReference _reference; + private string _description; + + private OpenApiSchema Target + { + get + { + _target ??= Reference.HostDocument?.ResolveReferenceTo(_reference); + OpenApiSchema resolved = new OpenApiSchema(_target); + if (!string.IsNullOrEmpty(_description)) resolved.Description = _description; + return resolved; + } + } + + /// + /// Constructor initializing the reference object. + /// + /// The reference Id. + /// The host OpenAPI document. + /// Optional: External resource in the reference. + /// It may be: + /// 1. a absolute/relative file path, for example: ../commons/pet.json + /// 2. a Url, for example: http://localhost/pet.json + /// + public OpenApiSchemaReference(string referenceId, OpenApiDocument hostDocument, string externalResource = null) + { + Utils.CheckArgumentNullOrEmpty(referenceId); + + _reference = new OpenApiReference() + { + Id = referenceId, + HostDocument = hostDocument, + Type = ReferenceType.Schema, + ExternalResource = externalResource + }; + + Reference = _reference; + } + + internal OpenApiSchemaReference(OpenApiSchema target, string referenceId) + { + _target = target; + + _reference = new OpenApiReference() + { + Id = referenceId, + Type = ReferenceType.Schema, + }; + } + + /// + public override string Title { get => Target.Title; set => Target.Title = value; } + /// + public override string Schema { get => Target.Schema; set => Target.Schema = value; } + /// + public override string Id { get => Target.Id; set => Target.Id = value; } + /// + public override string Comment { get => Target.Comment; set => Target.Comment = value; } + /// + public override IDictionary Vocabulary { get => Target.Vocabulary; set => Target.Vocabulary = value; } + /// + public override string DynamicRef { get => Target.DynamicRef; set => Target.DynamicRef = value; } + /// + public override string DynamicAnchor { get => Target.DynamicAnchor; set => Target.DynamicAnchor = value; } + /// + public override IDictionary Definitions { get => Target.Definitions; set => Target.Definitions = value; } + /// + public override decimal? V31ExclusiveMaximum { get => Target.V31ExclusiveMaximum; set => Target.V31ExclusiveMaximum = value; } + /// + public override decimal? V31ExclusiveMinimum { get => Target.V31ExclusiveMinimum; set => Target.V31ExclusiveMinimum = value; } + /// + public override bool UnEvaluatedProperties { get => Target.UnEvaluatedProperties; set => Target.UnEvaluatedProperties = value; } + /// + public override JsonSchemaType? Type { get => Target.Type; set => Target.Type = value; } + /// + public override string Format { get => Target.Format; set => Target.Format = value; } + /// + public override string Description + { + get => string.IsNullOrEmpty(_description) ? Target.Description : _description; + set => _description = value; + } + /// + public override decimal? Maximum { get => Target.Maximum; set => Target.Maximum = value; } + /// + public override bool? ExclusiveMaximum { get => Target.ExclusiveMaximum; set => Target.ExclusiveMaximum = value; } + /// + public override decimal? Minimum { get => Target.Minimum; set => Target.Minimum = value; } + /// + public override bool? ExclusiveMinimum { get => Target.ExclusiveMinimum; set => Target.ExclusiveMinimum = value; } + /// + public override int? MaxLength { get => Target.MaxLength; set => Target.MaxLength = value; } + /// + public override int? MinLength { get => Target.MinLength; set => Target.MinLength = value; } + /// + public override string Pattern { get => Target.Pattern; set => Target.Pattern = value; } + /// + public override decimal? MultipleOf { get => Target.MultipleOf; set => Target.MultipleOf = value; } + /// + public override JsonNode Default { get => Target.Default; set => Target.Default = value; } + /// + public override bool ReadOnly { get => Target.ReadOnly; set => Target.ReadOnly = value; } + /// + public override bool WriteOnly { get => Target.WriteOnly; set => Target.WriteOnly = value; } + /// + public override IList AllOf { get => Target.AllOf; set => Target.AllOf = value; } + /// + public override IList OneOf { get => Target.OneOf; set => Target.OneOf = value; } + /// + public override IList AnyOf { get => Target.AnyOf; set => Target.AnyOf = value; } + /// + public override OpenApiSchema Not { get => Target.Not; set => Target.Not = value; } + /// + public override ISet Required { get => Target.Required; set => Target.Required = value; } + /// + public override OpenApiSchema Items { get => Target.Items; set => Target.Items = value; } + /// + public override int? MaxItems { get => Target.MaxItems; set => Target.MaxItems = value; } + /// + public override int? MinItems { get => Target.MinItems; set => Target.MinItems = value; } + /// + public override bool? UniqueItems { get => Target.UniqueItems; set => Target.UniqueItems = value; } + /// + public override IDictionary Properties { get => Target.Properties; set => Target.Properties = value; } + /// + public override IDictionary PatternProperties { get => Target.PatternProperties; set => Target.PatternProperties = value; } + /// + public override int? MaxProperties { get => Target.MaxProperties; set => Target.MaxProperties = value; } + /// + public override int? MinProperties { get => Target.MinProperties; set => Target.MinProperties = value; } + /// + public override bool AdditionalPropertiesAllowed { get => Target.AdditionalPropertiesAllowed; set => Target.AdditionalPropertiesAllowed = value; } + /// + public override OpenApiSchema AdditionalProperties { get => Target.AdditionalProperties; set => Target.AdditionalProperties = value; } + /// + public override OpenApiDiscriminator Discriminator { get => Target.Discriminator; set => Target.Discriminator = value; } + /// + public override JsonNode Example { get => Target.Example; set => Target.Example = value; } + /// + public override IList Examples { get => Target.Examples; set => Target.Examples = value; } + /// + public override IList Enum { get => Target.Enum; set => Target.Enum = value; } + /// + public override bool Nullable { get => Target.Nullable; set => Target.Nullable = value; } + /// + public override bool UnevaluatedProperties { get => Target.UnevaluatedProperties; set => Target.UnevaluatedProperties = value; } + /// + public override OpenApiExternalDocs ExternalDocs { get => Target.ExternalDocs; set => Target.ExternalDocs = value; } + /// + public override bool Deprecated { get => Target.Deprecated; set => Target.Deprecated = value; } + /// + public override OpenApiXml Xml { get => Target.Xml; set => Target.Xml = value; } + /// + public override IDictionary Extensions { get => Target.Extensions; set => Target.Extensions = value; } + + /// + public override void SerializeAsV31(IOpenApiWriter writer) + { + if (!writer.GetSettings().ShouldInlineReference(_reference)) + { + _reference.SerializeAsV31(writer); + return; + } + // If Loop is detected then just Serialize as a reference. + else if (!writer.GetSettings().LoopDetector.PushLoop(this)) + { + writer.GetSettings().LoopDetector.SaveLoop(this); + _reference.SerializeAsV31(writer); + return; + } + + SerializeInternal(writer, (writer, element) => element.SerializeAsV31(writer)); + writer.GetSettings().LoopDetector.PopLoop(); + } + + /// + public override void SerializeAsV3(IOpenApiWriter writer) + { + if (!writer.GetSettings().ShouldInlineReference(_reference)) + { + _reference.SerializeAsV3(writer); + return; + } + // If Loop is detected then just Serialize as a reference. + else if (!writer.GetSettings().LoopDetector.PushLoop(this)) + { + writer.GetSettings().LoopDetector.SaveLoop(this); + _reference.SerializeAsV3(writer); + return; + } + + SerializeInternal(writer, (writer, element) => element.SerializeAsV3(writer)); + writer.GetSettings().LoopDetector.PopLoop(); + } + + /// + public override void SerializeAsV2(IOpenApiWriter writer) + { + if (!writer.GetSettings().ShouldInlineReference(_reference)) + { + _reference.SerializeAsV2(writer); + return; + } + else + { + SerializeInternal(writer, (writer, element) => element.SerializeAsV2(writer)); + } + } + + /// + private void SerializeInternal(IOpenApiWriter writer, + Action action) + { + Utils.CheckArgumentNull(writer); + action(writer, Target); + } + } +} diff --git a/src/Microsoft.OpenApi/Models/References/OpenApiSecuritySchemeReference.cs b/src/Microsoft.OpenApi/Models/References/OpenApiSecuritySchemeReference.cs new file mode 100644 index 000000000..e635de6f9 --- /dev/null +++ b/src/Microsoft.OpenApi/Models/References/OpenApiSecuritySchemeReference.cs @@ -0,0 +1,144 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; +using Microsoft.OpenApi.Interfaces; +using Microsoft.OpenApi.Writers; + +namespace Microsoft.OpenApi.Models.References +{ + /// + /// Security Scheme Object Reference. + /// + public class OpenApiSecuritySchemeReference : OpenApiSecurityScheme + { + internal OpenApiSecurityScheme _target; + private readonly OpenApiReference _reference; + private string _description; + + private OpenApiSecurityScheme Target + { + get + { + _target ??= Reference.HostDocument.ResolveReferenceTo(_reference); + OpenApiSecurityScheme resolved = new OpenApiSecurityScheme(_target); + if (!string.IsNullOrEmpty(_description)) resolved.Description = _description; + return resolved; + } + } + + /// + /// Constructor initializing the reference object. + /// + /// The reference Id. + /// The host OpenAPI document. + /// The externally referenced file. + public OpenApiSecuritySchemeReference(string referenceId, OpenApiDocument hostDocument, string externalResource = null) + { + Utils.CheckArgumentNullOrEmpty(referenceId); + + _reference = new OpenApiReference() + { + Id = referenceId, + HostDocument = hostDocument, + Type = ReferenceType.SecurityScheme, + ExternalResource = externalResource + }; + + Reference = _reference; + } + + internal OpenApiSecuritySchemeReference(string referenceId, OpenApiSecurityScheme target) + { + _target = target; + + _reference = new OpenApiReference() + { + Id = referenceId, + Type = ReferenceType.SecurityScheme, + }; + } + + /// + public override string Description + { + get => string.IsNullOrEmpty(_description) ? Target.Description : _description; + set => _description = value; + } + + /// + public override string Name { get => Target.Name; set => Target.Name = value; } + + /// + public override ParameterLocation In { get => Target.In; set => Target.In = value; } + + /// + public override string Scheme { get => Target.Scheme; set => Target.Scheme = value; } + + /// + public override string BearerFormat { get => Target.BearerFormat; set => Target.BearerFormat = value; } + + /// + public override OpenApiOAuthFlows Flows { get => Target.Flows; set => Target.Flows = value; } + + /// + public override Uri OpenIdConnectUrl { get => Target.OpenIdConnectUrl; set => Target.OpenIdConnectUrl = value; } + + /// + public override IDictionary Extensions { get => Target.Extensions; set => Target.Extensions = value; } + + /// + public override SecuritySchemeType Type { get => Target.Type; set => Target.Type = value; } + + /// + public override void SerializeAsV3(IOpenApiWriter writer) + { + if (!writer.GetSettings().ShouldInlineReference(_reference)) + { + _reference.SerializeAsV3(writer); + return; + } + else + { + SerializeInternal(writer, (writer, element) => element.SerializeAsV3(writer)); + } + } + + /// + public override void SerializeAsV31(IOpenApiWriter writer) + { + if (!writer.GetSettings().ShouldInlineReference(_reference)) + { + _reference.SerializeAsV31(writer); + return; + } + else + { + SerializeInternal(writer, (writer, element) => element.SerializeAsV31(writer)); + } + } + + /// + public override void SerializeAsV2(IOpenApiWriter writer) + { + if (!writer.GetSettings().ShouldInlineReference(_reference)) + { + _reference.SerializeAsV2(writer); + return; + } + else + { + SerializeInternal(writer, (writer, element) => element.SerializeAsV2(writer)); + } + } + + /// + private void SerializeInternal(IOpenApiWriter writer, + Action action) + { + Utils.CheckArgumentNull(writer);; + action(writer, Target); + } + } +} diff --git a/src/Microsoft.OpenApi/Models/References/OpenApiTagReference.cs b/src/Microsoft.OpenApi/Models/References/OpenApiTagReference.cs new file mode 100644 index 000000000..664f784f3 --- /dev/null +++ b/src/Microsoft.OpenApi/Models/References/OpenApiTagReference.cs @@ -0,0 +1,126 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System.Collections.Generic; +using Microsoft.OpenApi.Interfaces; +using Microsoft.OpenApi.Writers; + +namespace Microsoft.OpenApi.Models.References +{ + /// + /// Tag Object Reference + /// + public class OpenApiTagReference : OpenApiTag + { + internal OpenApiTag _target; + private readonly OpenApiReference _reference; + private string _description; + + private OpenApiTag Target + { + get + { + _target ??= Reference.HostDocument?.ResolveReferenceTo(_reference); + _target ??= new OpenApiTag() { Name = _reference.Id }; + OpenApiTag resolved = new OpenApiTag(_target); + if (!string.IsNullOrEmpty(_description)) resolved.Description = _description; + return resolved; + } + } + + /// + /// Constructor initializing the reference object. + /// + /// The reference Id. + /// The host OpenAPI document. + public OpenApiTagReference(string referenceId, OpenApiDocument hostDocument) + { + Utils.CheckArgumentNullOrEmpty(referenceId); + + _reference = new OpenApiReference() + { + Id = referenceId, + HostDocument = hostDocument, + Type = ReferenceType.Tag + }; + + Reference = _reference; + } + + internal OpenApiTagReference(OpenApiTag target, string referenceId) + { + _target = target; + + _reference = new OpenApiReference() + { + Id = referenceId, + Type = ReferenceType.Tag, + }; + } + + /// + public override string Description + { + get => string.IsNullOrEmpty(_description) ? Target?.Description : _description; + set => _description = value; + } + + /// + public override OpenApiExternalDocs ExternalDocs { get => Target?.ExternalDocs; set => Target.ExternalDocs = value; } + + /// + public override IDictionary Extensions { get => Target?.Extensions; set => Target.Extensions = value; } + + /// + public override string Name { get => Target?.Name; set => Target.Name = value; } + + /// + public override void SerializeAsV3(IOpenApiWriter writer) + { + if (!writer.GetSettings().ShouldInlineReference(_reference)) + { + _reference.SerializeAsV3(writer); + return; + } + else + { + SerializeInternal(writer); + } + } + + /// + public override void SerializeAsV31(IOpenApiWriter writer) + { + if (!writer.GetSettings().ShouldInlineReference(_reference)) + { + _reference.SerializeAsV31(writer); + return; + } + else + { + SerializeInternal(writer); + } + } + + /// + public override void SerializeAsV2(IOpenApiWriter writer) + { + if (!writer.GetSettings().ShouldInlineReference(_reference)) + { + _reference.SerializeAsV2(writer); + return; + } + else + { + SerializeInternal(writer); + } + } + + /// + private void SerializeInternal(IOpenApiWriter writer) + { + Utils.CheckArgumentNull(writer);; + writer.WriteValue(Name); + } + } +} diff --git a/src/Microsoft.OpenApi/Models/RuntimeExpressionAnyWrapper.cs b/src/Microsoft.OpenApi/Models/RuntimeExpressionAnyWrapper.cs index 796c72c3e..dca24c3e5 100644 --- a/src/Microsoft.OpenApi/Models/RuntimeExpressionAnyWrapper.cs +++ b/src/Microsoft.OpenApi/Models/RuntimeExpressionAnyWrapper.cs @@ -1,39 +1,41 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. +using System.Text.Json.Nodes; using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Expressions; +using Microsoft.OpenApi.Helpers; using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Writers; namespace Microsoft.OpenApi.Models { /// - /// The wrapper either for or + /// The wrapper either for or /// public class RuntimeExpressionAnyWrapper : IOpenApiElement { - private IOpenApiAny _any; + private JsonNode _any; private RuntimeExpression _expression; /// /// Parameterless constructor /// - public RuntimeExpressionAnyWrapper() {} + public RuntimeExpressionAnyWrapper() { } /// /// Initializes a copy of an object /// public RuntimeExpressionAnyWrapper(RuntimeExpressionAnyWrapper runtimeExpressionAnyWrapper) { - Any = OpenApiAnyCloneHelper.CloneFromCopyConstructor(runtimeExpressionAnyWrapper?.Any); + Any = JsonNodeCloneHelper.Clone(runtimeExpressionAnyWrapper?.Any); Expression = runtimeExpressionAnyWrapper?.Expression; } /// - /// Gets/Sets the + /// Gets/Sets the /// - public IOpenApiAny Any + public JsonNode Any { get { diff --git a/src/Microsoft.OpenApi/OpenApiSpecVersion.cs b/src/Microsoft.OpenApi/OpenApiSpecVersion.cs index 48666ab79..6ee2abf36 100644 --- a/src/Microsoft.OpenApi/OpenApiSpecVersion.cs +++ b/src/Microsoft.OpenApi/OpenApiSpecVersion.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. namespace Microsoft.OpenApi @@ -15,7 +15,13 @@ public enum OpenApiSpecVersion /// /// Represents all patches of OpenAPI V3.0 spec (e.g. 3.0.0, 3.0.1) + /// + OpenApi3_0, + + /// + /// Represents OpenAPI V3.1 spec /// - OpenApi3_0 + OpenApi3_1 + } } diff --git a/src/Microsoft.OpenApi/Properties/SRResource.Designer.cs b/src/Microsoft.OpenApi/Properties/SRResource.Designer.cs index 511f300f7..86334cd1e 100644 --- a/src/Microsoft.OpenApi/Properties/SRResource.Designer.cs +++ b/src/Microsoft.OpenApi/Properties/SRResource.Designer.cs @@ -76,7 +76,18 @@ internal static string ArgumentNullOrWhiteSpace { return ResourceManager.GetString("ArgumentNullOrWhiteSpace", resourceCulture); } } - + + /// + /// Looks up a localized string similar to The argument '{0}' is null.. + /// + internal static string ArgumentNull + { + get + { + return ResourceManager.GetString("ArgumentNull", resourceCulture); + } + } + /// /// Looks up a localized string similar to http://localhost/. /// @@ -401,5 +412,27 @@ internal static string WorkspaceRequredForExternalReferenceResolution { return ResourceManager.GetString("WorkspaceRequredForExternalReferenceResolution", resourceCulture); } } + + /// + /// Looks up a localized string similar to The HostDocument is null.. + /// + internal static string HostDocumentIsNull + { + get + { + return ResourceManager.GetString("HostDocumentIsNull", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The identifier in the referenced element is null or empty .. + /// + internal static string ReferenceIdIsNullOrEmpty + { + get + { + return ResourceManager.GetString("ReferenceIdIsNullOrEmpty", resourceCulture); + } + } } } diff --git a/src/Microsoft.OpenApi/Reader/JsonNodeHelper.cs b/src/Microsoft.OpenApi/Reader/JsonNodeHelper.cs new file mode 100644 index 000000000..e8dee12d1 --- /dev/null +++ b/src/Microsoft.OpenApi/Reader/JsonNodeHelper.cs @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System; +using System.Globalization; +using System.Text.Json.Nodes; +using Microsoft.OpenApi.Exceptions; + +namespace Microsoft.OpenApi.Reader +{ + internal static class JsonNodeHelper + { + public static string GetScalarValue(this JsonNode node) + { + + var scalarNode = node is JsonValue value ? value : throw new OpenApiException($"Expected scalar value."); + + return Convert.ToString(scalarNode?.GetValue(), CultureInfo.InvariantCulture); + } + } +} diff --git a/src/Microsoft.OpenApi.Readers/OpenApiDiagnostic.cs b/src/Microsoft.OpenApi/Reader/OpenApiDiagnostic.cs similarity index 92% rename from src/Microsoft.OpenApi.Readers/OpenApiDiagnostic.cs rename to src/Microsoft.OpenApi/Reader/OpenApiDiagnostic.cs index 509358174..9f09bb457 100644 --- a/src/Microsoft.OpenApi.Readers/OpenApiDiagnostic.cs +++ b/src/Microsoft.OpenApi/Reader/OpenApiDiagnostic.cs @@ -2,10 +2,10 @@ // Licensed under the MIT license. using System.Collections.Generic; +using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Models; -using Microsoft.OpenApi.Readers.Interface; -namespace Microsoft.OpenApi.Readers +namespace Microsoft.OpenApi.Reader { /// /// Object containing all diagnostic information related to Open API parsing. @@ -53,7 +53,7 @@ public void AppendDiagnostic(OpenApiDiagnostic diagnosticToAdd, string fileNameT /// /// Extension class for IList to add the Method "AddRange" used above /// -internal static class IDiagnosticExtensions +public static class IDiagnosticExtensions { /// /// Extension method for IList so that another list can be added to the current list. @@ -61,7 +61,7 @@ internal static class IDiagnosticExtensions /// /// /// - internal static void AddRange(this ICollection collection, IEnumerable enumerable) + public static void AddRange(this ICollection collection, IEnumerable enumerable) { if (collection is null || enumerable is null) return; diff --git a/src/Microsoft.OpenApi/Reader/OpenApiJsonReader.cs b/src/Microsoft.OpenApi/Reader/OpenApiJsonReader.cs new file mode 100644 index 000000000..27aad722e --- /dev/null +++ b/src/Microsoft.OpenApi/Reader/OpenApiJsonReader.cs @@ -0,0 +1,204 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System.IO; +using System.Text.Json.Nodes; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Exceptions; +using Microsoft.OpenApi.Extensions; +using Microsoft.OpenApi.Validations; +using System.Linq; +using Microsoft.OpenApi.Services; +using Microsoft.OpenApi.Interfaces; +using Microsoft.OpenApi.Reader.Services; +using System.Collections.Generic; +using System; + +namespace Microsoft.OpenApi.Reader +{ + /// + /// A reader class for parsing JSON files into Open API documents. + /// + public class OpenApiJsonReader : IOpenApiReader + { + /// + /// Reads the stream input and parses it into an Open API document. + /// + /// TextReader containing OpenAPI description to parse. + /// The Reader settings to be used during parsing. + /// Propagates notifications that operations should be cancelled. + /// + public async Task ReadAsync(TextReader input, + OpenApiReaderSettings settings = null, + CancellationToken cancellationToken = default) + { + JsonNode jsonNode; + var diagnostic = new OpenApiDiagnostic(); + settings ??= new OpenApiReaderSettings(); + + // Parse the JSON text in the TextReader into JsonNodes + try + { + jsonNode = LoadJsonNodes(input); + } + catch (JsonException ex) + { + diagnostic.Errors.Add(new OpenApiError($"#line={ex.LineNumber}", $"Please provide the correct format, {ex.Message}")); + return new ReadResult + { + OpenApiDocument = null, + OpenApiDiagnostic = diagnostic + }; + } + + return await ReadAsync(jsonNode, settings, cancellationToken: cancellationToken); + } + + /// + /// Parses the JsonNode input into an Open API document. + /// + /// The JsonNode input. + /// The Reader settings to be used during parsing. + /// The OpenAPI format. + /// Propagates notifications that operations should be cancelled. + /// + public async Task ReadAsync(JsonNode jsonNode, + OpenApiReaderSettings settings, + string format = null, + CancellationToken cancellationToken = default) + { + var diagnostic = new OpenApiDiagnostic(); + var context = new ParsingContext(diagnostic) + { + ExtensionParsers = settings.ExtensionParsers, + BaseUrl = settings.BaseUrl, + DefaultContentType = settings.DefaultContentType + }; + + OpenApiDocument document = null; + try + { + // Parse the OpenAPI Document + document = context.Parse(jsonNode); + + if (settings.LoadExternalRefs) + { + var diagnosticExternalRefs = await LoadExternalRefsAsync(document, cancellationToken, settings, format); + // Merge diagnostics of external reference + if (diagnosticExternalRefs != null) + { + diagnostic.Errors.AddRange(diagnosticExternalRefs.Errors); + diagnostic.Warnings.AddRange(diagnosticExternalRefs.Warnings); + } + } + + document.SetReferenceHostDocument(); + } + catch (OpenApiException ex) + { + diagnostic.Errors.Add(new(ex)); + } + + // Validate the document + if (settings.RuleSet != null && settings.RuleSet.Rules.Any()) + { + var openApiErrors = document.Validate(settings.RuleSet); + foreach (var item in openApiErrors.OfType()) + { + diagnostic.Errors.Add(item); + } + foreach (var item in openApiErrors.OfType()) + { + diagnostic.Warnings.Add(item); + } + } + + return new() + { + OpenApiDocument = document, + OpenApiDiagnostic = diagnostic + }; + } + + /// + public T ReadFragment(TextReader input, + OpenApiSpecVersion version, + out OpenApiDiagnostic diagnostic, + OpenApiReaderSettings settings = null) where T : IOpenApiElement + { + JsonNode jsonNode; + + // Parse the JSON + try + { + jsonNode = LoadJsonNodes(input); + } + catch (JsonException ex) + { + diagnostic = new(); + diagnostic.Errors.Add(new($"#line={ex.LineNumber}", ex.Message)); + return default; + } + + return ReadFragment(jsonNode, version, out diagnostic); + } + + /// + public T ReadFragment(JsonNode input, + OpenApiSpecVersion version, + out OpenApiDiagnostic diagnostic, + OpenApiReaderSettings settings = null) where T : IOpenApiElement + { + diagnostic = new(); + settings ??= new OpenApiReaderSettings(); + var context = new ParsingContext(diagnostic) + { + ExtensionParsers = settings.ExtensionParsers + }; + + IOpenApiElement element = null; + try + { + // Parse the OpenAPI element + element = context.ParseFragment(input, version); + } + catch (OpenApiException ex) + { + diagnostic.Errors.Add(new(ex)); + } + + // Validate the element + if (settings.RuleSet != null && settings.RuleSet.Rules.Any()) + { + var errors = element.Validate(settings.RuleSet); + foreach (var item in errors) + { + diagnostic.Errors.Add(item); + } + } + + return (T)element; + } + + private JsonNode LoadJsonNodes(TextReader input) + { + var nodes = JsonNode.Parse(input.ReadToEnd()); + return nodes; + } + + private async Task LoadExternalRefsAsync(OpenApiDocument document, CancellationToken cancellationToken, OpenApiReaderSettings settings, string format = null) + { + // Create workspace for all documents to live in. + var baseUrl = settings.BaseUrl ?? new Uri(OpenApiConstants.BaseRegistryUri); + var openApiWorkSpace = new OpenApiWorkspace(baseUrl); + + // Load this root document into the workspace + var streamLoader = new DefaultStreamLoader(settings.BaseUrl); + var workspaceLoader = new OpenApiWorkspaceLoader(openApiWorkSpace, settings.CustomExternalLoader ?? streamLoader, settings); + return await workspaceLoader.LoadAsync(new OpenApiReference() { ExternalResource = "/" }, document, format ?? OpenApiConstants.Json, null, cancellationToken); + } + } +} diff --git a/src/Microsoft.OpenApi/Reader/OpenApiModelFactory.cs b/src/Microsoft.OpenApi/Reader/OpenApiModelFactory.cs new file mode 100644 index 000000000..ddabdc6be --- /dev/null +++ b/src/Microsoft.OpenApi/Reader/OpenApiModelFactory.cs @@ -0,0 +1,338 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Security; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.OpenApi.Interfaces; +using Microsoft.OpenApi.Models; + +namespace Microsoft.OpenApi.Reader +{ + /// + /// A factory class for loading OpenAPI models from various sources. + /// + public static class OpenApiModelFactory + { + private static readonly HttpClient _httpClient = new(); + + static OpenApiModelFactory() + { + OpenApiReaderRegistry.RegisterReader(OpenApiConstants.Json, new OpenApiJsonReader()); + } + + /// + /// Loads the input URL and parses it into an Open API document. + /// + /// The path to the OpenAPI file. + /// The OpenApi reader settings. + /// An OpenAPI document instance. + public static ReadResult Load(string url, OpenApiReaderSettings settings = null) + { +#pragma warning disable VSTHRD002 // Avoid problematic synchronous waits + return LoadAsync(url, settings).GetAwaiter().GetResult(); +#pragma warning restore VSTHRD002 // Avoid problematic synchronous waits + } + + /// + /// Loads the input stream and parses it into an Open API document. + /// + /// The input stream. + /// The OpenApi reader settings. + /// The OpenAPI format. + /// An OpenAPI document instance. + public static ReadResult Load(Stream stream, + string format, + OpenApiReaderSettings settings = null) + { + settings ??= new OpenApiReaderSettings(); + +#pragma warning disable VSTHRD002 // Avoid problematic synchronous waits + var result = LoadAsync(stream, format, settings).GetAwaiter().GetResult(); +#pragma warning restore VSTHRD002 // Avoid problematic synchronous waits + + if (!settings.LeaveStreamOpen) + { + stream.Dispose(); + } + + return result; + } + + /// + /// Loads the TextReader input and parses it into an Open API document. + /// + /// The TextReader input. + /// The OpenApi reader settings. + /// The Open API format + /// An OpenAPI document instance. + public static ReadResult Load(TextReader input, + string format, + OpenApiReaderSettings settings = null) + { +#pragma warning disable VSTHRD002 // Avoid problematic synchronous waits + var result = LoadAsync(input, format, settings).GetAwaiter().GetResult(); +#pragma warning restore VSTHRD002 // Avoid problematic synchronous waits + return result; + } + + /// + /// Loads the input URL and parses it into an Open API document. + /// + /// The path to the OpenAPI file + /// The OpenApi reader settings. + /// + public static async Task LoadAsync(string url, OpenApiReaderSettings settings = null) + { + var format = GetFormat(url); + var stream = await GetStreamAsync(url); + return await LoadAsync(stream, format, settings); + } + + /// + /// Loads the input stream and parses it into an Open API document. + /// + /// The input stream. + /// The OpenApi reader settings. + /// Propagates notification that operations should be cancelled. + /// The Open API format + /// + public static async Task LoadAsync(Stream input, string format, OpenApiReaderSettings settings = null, CancellationToken cancellationToken = default) + { + Utils.CheckArgumentNull(format, nameof(format)); + settings ??= new OpenApiReaderSettings(); + + Stream preparedStream; + + // Avoid buffering for JSON documents + if (input is MemoryStream || format.Equals(OpenApiConstants.Json, StringComparison.OrdinalIgnoreCase)) + { + preparedStream = input; + } + else + { + // Buffer stream for non-JSON formats (e.g., YAML) since they require synchronous reading + preparedStream = new MemoryStream(); + await input.CopyToAsync(preparedStream, 81920, cancellationToken); + preparedStream.Position = 0; + } + + // Use StreamReader to process the prepared stream (buffered for YAML, direct for JSON) + using var reader = new StreamReader(preparedStream, default, true, -1, settings.LeaveStreamOpen); + return await LoadAsync(reader, format, settings, cancellationToken); + } + + + /// + /// Loads the TextReader input and parses it into an Open API document. + /// + /// The TextReader input. + /// The Open API format + /// The OpenApi reader settings. + /// Propagates notification that operations should be cancelled. + /// + public static async Task LoadAsync(TextReader input, string format, OpenApiReaderSettings settings = null, CancellationToken cancellationToken = default) + { + Utils.CheckArgumentNull(format, nameof(format)); + var reader = OpenApiReaderRegistry.GetReader(format); + return await reader.ReadAsync(input, settings, cancellationToken); + } + + /// + /// Reads the input string and parses it into an Open API document. + /// + /// The input string. + /// The Open API format + /// The OpenApi reader settings. + /// An OpenAPI document instance. + public static ReadResult Parse(string input, + string format = null, + OpenApiReaderSettings settings = null) + { + format ??= OpenApiConstants.Json; + settings ??= new OpenApiReaderSettings(); + using var reader = new StringReader(input); + +#pragma warning disable VSTHRD002 // Avoid problematic synchronous waits + return ParseAsync(input, reader, format, settings).GetAwaiter().GetResult(); +#pragma warning restore VSTHRD002 // Avoid problematic synchronous waits + } + + /// + /// An Async method to prevent synchornously blocking the calling thread. + /// + /// + /// + /// + /// + /// + public static async Task ParseAsync(string input, + StringReader reader, + string format = null, + OpenApiReaderSettings settings = null) + { + return await LoadAsync(reader, format, settings); + } + + /// + /// Reads the input string and parses it into an Open API document. + /// + /// The input string. + /// + /// The diagnostic entity containing information from the reading process. + /// The Open API format + /// The OpenApi reader settings. + /// An OpenAPI document instance. + public static T Parse(string input, + OpenApiSpecVersion version, + out OpenApiDiagnostic diagnostic, + string format = null, + OpenApiReaderSettings settings = null) where T : IOpenApiElement + { + format ??= OpenApiConstants.Json; + settings ??= new OpenApiReaderSettings(); + using var reader = new StringReader(input); + return Load(reader, version, out diagnostic, format, settings); + } + + /// + /// Reads the stream input and parses the fragment of an OpenAPI description into an Open API Element. + /// + /// + /// The path to the OpenAPI file + /// Version of the OpenAPI specification that the fragment conforms to. + /// Returns diagnostic object containing errors detected during parsing. + /// The OpenApiReader settings. + /// Instance of newly created IOpenApiElement. + /// The OpenAPI element. + public static T Load(string url, OpenApiSpecVersion version, out OpenApiDiagnostic diagnostic, OpenApiReaderSettings settings = null) where T : IOpenApiElement + { + var format = GetFormat(url); + settings ??= new OpenApiReaderSettings(); + +#pragma warning disable VSTHRD002 // Avoid problematic synchronous waits + var stream = GetStreamAsync(url).GetAwaiter().GetResult(); +#pragma warning restore VSTHRD002 // Avoid problematic synchronous waits + + return Load(stream, version, format, out diagnostic, settings); + } + + /// + /// Reads the stream input and parses the fragment of an OpenAPI description into an Open API Element. + /// + /// + /// Stream containing OpenAPI description to parse. + /// Version of the OpenAPI specification that the fragment conforms to. + /// + /// Returns diagnostic object containing errors detected during parsing. + /// The OpenApiReader settings. + /// Instance of newly created IOpenApiElement. + /// The OpenAPI element. + public static T Load(Stream input, OpenApiSpecVersion version, string format, out OpenApiDiagnostic diagnostic, OpenApiReaderSettings settings = null) where T : IOpenApiElement + { + format ??= OpenApiConstants.Json; + using var reader = new StreamReader(input); + return Load(reader, version, out diagnostic, format, settings); + } + + /// + /// Reads the TextReader input and parses the fragment of an OpenAPI description into an Open API Element. + /// + /// + /// TextReader containing OpenAPI description to parse. + /// Version of the OpenAPI specification that the fragment conforms to. + /// The OpenAPI format. + /// Returns diagnostic object containing errors detected during parsing. + /// The OpenApiReader settings. + /// Instance of newly created IOpenApiElement. + /// The OpenAPI element. + public static T Load(TextReader input, OpenApiSpecVersion version, out OpenApiDiagnostic diagnostic, string format, OpenApiReaderSettings settings = null) where T : IOpenApiElement + { + format ??= OpenApiConstants.Json; + return OpenApiReaderRegistry.GetReader(format).ReadFragment(input, version, out diagnostic, settings); + } + + + private static string GetContentType(string url) + { + if (!string.IsNullOrEmpty(url)) + { +#pragma warning disable VSTHRD002 // Avoid problematic synchronous waits + var response = _httpClient.GetAsync(url).GetAwaiter().GetResult(); +#pragma warning restore VSTHRD002 // Avoid problematic synchronous waits + + var mediaType = response.Content.Headers.ContentType.MediaType; + return mediaType.Split(";".ToCharArray(), StringSplitOptions.RemoveEmptyEntries).First(); + } + + return null; + } + + /// + /// Infers the OpenAPI format from the input URL. + /// + /// The input URL. + /// The OpenAPI format. + public static string GetFormat(string url) + { + if (!string.IsNullOrEmpty(url)) + { + if (url.StartsWith("http", StringComparison.OrdinalIgnoreCase) || url.StartsWith("https", StringComparison.OrdinalIgnoreCase)) + { + // URL examples ---> https://example.com/path/to/file.json, https://example.com/path/to/file.yaml + var path = new Uri(url); + var urlSuffix = path.Segments[path.Segments.Length - 1].Split('.').LastOrDefault(); + + return !string.IsNullOrEmpty(urlSuffix) ? urlSuffix : GetContentType(url).Split('/').LastOrDefault(); + } + else + { + return Path.GetExtension(url).Split('.').LastOrDefault(); + } + } + return null; + } + + private static async Task GetStreamAsync(string url) + { + Stream stream; + if (url.StartsWith("http", StringComparison.OrdinalIgnoreCase) || url.StartsWith("https", StringComparison.OrdinalIgnoreCase)) + { + try + { + stream = await _httpClient.GetStreamAsync(new Uri(url)); + } + catch (HttpRequestException ex) + { + throw new InvalidOperationException($"Could not download the file at {url}", ex); + } + } + else + { + try + { + var fileInput = new FileInfo(url); + stream = fileInput.OpenRead(); + } + catch (Exception ex) when ( + ex is + FileNotFoundException or + PathTooLongException or + DirectoryNotFoundException or + IOException or + UnauthorizedAccessException or + SecurityException or + NotSupportedException) + { + throw new InvalidOperationException($"Could not open the file at {url}", ex); + } + } + + return stream; + } + } +} diff --git a/src/Microsoft.OpenApi/Reader/OpenApiReaderRegistry.cs b/src/Microsoft.OpenApi/Reader/OpenApiReaderRegistry.cs new file mode 100644 index 000000000..e1eea86a1 --- /dev/null +++ b/src/Microsoft.OpenApi/Reader/OpenApiReaderRegistry.cs @@ -0,0 +1,49 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using Microsoft.OpenApi.Interfaces; + +namespace Microsoft.OpenApi.Reader +{ + /// + /// Registry for managing different OpenAPI format providers. + /// + public static class OpenApiReaderRegistry + { + private static readonly ConcurrentDictionary _readers = new(StringComparer.OrdinalIgnoreCase); + + /// + /// Defines a default OpenAPI reader. + /// + public static readonly IOpenApiReader DefaultReader = new OpenApiJsonReader(); + + /// + /// Registers an IOpenApiReader for a given OpenAPI format. + /// + /// The OpenApi file format. + /// The reader instance. + public static void RegisterReader(string format, IOpenApiReader reader) + { + _readers.AddOrUpdate(format, reader, (_, _) => reader); + } + + /// + /// Retrieves an IOpenApiReader for a given OpenAPI format. + /// + /// + /// + /// + public static IOpenApiReader GetReader(string format) + { + if (_readers.TryGetValue(format, out var reader)) + { + return reader; + } + + throw new NotSupportedException($"Format '{format}' is not supported. Register your reader with the OpenApiReaderRegistry class."); + } + } +} diff --git a/src/Microsoft.OpenApi.Readers/OpenApiReaderSettings.cs b/src/Microsoft.OpenApi/Reader/OpenApiReaderSettings.cs similarity index 94% rename from src/Microsoft.OpenApi.Readers/OpenApiReaderSettings.cs rename to src/Microsoft.OpenApi/Reader/OpenApiReaderSettings.cs index 5d251802f..fa0040ff8 100644 --- a/src/Microsoft.OpenApi.Readers/OpenApiReaderSettings.cs +++ b/src/Microsoft.OpenApi/Reader/OpenApiReaderSettings.cs @@ -1,16 +1,16 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. +using System; +using System.Collections.Generic; +using System.IO; +using System.Text.Json.Nodes; using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.MicrosoftExtensions; -using Microsoft.OpenApi.Readers.Interface; using Microsoft.OpenApi.Validations; -using System; -using System.Collections.Generic; -using System.IO; -namespace Microsoft.OpenApi.Readers +namespace Microsoft.OpenApi.Reader { /// /// Indicates if and when the reader should convert unresolved references into resolved objects @@ -50,7 +50,7 @@ public class OpenApiReaderSettings /// /// Dictionary of parsers for converting extensions into strongly typed classes /// - public Dictionary> ExtensionParsers { get; set; } = new(); + public Dictionary> ExtensionParsers { get; set; } = new(); /// /// Rules to use for validating OpenAPI specification. If none are provided a default set of rules are applied. @@ -77,7 +77,7 @@ public class OpenApiReaderSettings /// /// Whether to leave the object open after reading - /// from an object. + /// from an OpenApiStreamReader object. /// public bool LeaveStreamOpen { get; set; } diff --git a/src/Microsoft.OpenApi/Reader/OpenApiVersionExtensionMethods.cs b/src/Microsoft.OpenApi/Reader/OpenApiVersionExtensionMethods.cs new file mode 100644 index 000000000..24f32ef5f --- /dev/null +++ b/src/Microsoft.OpenApi/Reader/OpenApiVersionExtensionMethods.cs @@ -0,0 +1,61 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System; + +namespace Microsoft.OpenApi.Reader +{ + /// + /// Generates custom extension methods for the version string type + /// + public static class OpenApiVersionExtensionMethods + { + /// + /// Extension method for Spec version 2.0 + /// + /// + /// + public static bool is2_0(this string version) + { + bool result = false; + if (version.Equals("2.0", StringComparison.OrdinalIgnoreCase)) + { + result = true; + } + + return result; + } + + /// + /// Extension method for Spec version 3.0 + /// + /// + /// + public static bool is3_0(this string version) + { + bool result = false; + if (version.StartsWith("3.0", StringComparison.OrdinalIgnoreCase)) + { + result = true; + } + + return result; + } + + /// + /// Extension method for Spec version 3.1 + /// + /// + /// + public static bool is3_1(this string version) + { + bool result = false; + if (version.StartsWith("3.1", StringComparison.OrdinalIgnoreCase)) + { + result = true; + } + + return result; + } + } +} diff --git a/src/Microsoft.OpenApi.Readers/ParseNodes/AnyFieldMap.cs b/src/Microsoft.OpenApi/Reader/ParseNodes/AnyFieldMap.cs similarity index 83% rename from src/Microsoft.OpenApi.Readers/ParseNodes/AnyFieldMap.cs rename to src/Microsoft.OpenApi/Reader/ParseNodes/AnyFieldMap.cs index 479417bdb..f1c76d315 100644 --- a/src/Microsoft.OpenApi.Readers/ParseNodes/AnyFieldMap.cs +++ b/src/Microsoft.OpenApi/Reader/ParseNodes/AnyFieldMap.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; -namespace Microsoft.OpenApi.Readers.ParseNodes +namespace Microsoft.OpenApi.Reader.ParseNodes { internal class AnyFieldMap : Dictionary> { diff --git a/src/Microsoft.OpenApi.Readers/ParseNodes/AnyFieldMapParameter.cs b/src/Microsoft.OpenApi/Reader/ParseNodes/AnyFieldMapParameter.cs similarity index 68% rename from src/Microsoft.OpenApi.Readers/ParseNodes/AnyFieldMapParameter.cs rename to src/Microsoft.OpenApi/Reader/ParseNodes/AnyFieldMapParameter.cs index 515a6e174..ad8394b58 100644 --- a/src/Microsoft.OpenApi.Readers/ParseNodes/AnyFieldMapParameter.cs +++ b/src/Microsoft.OpenApi/Reader/ParseNodes/AnyFieldMapParameter.cs @@ -2,10 +2,11 @@ // Licensed under the MIT license. using System; +using System.Text.Json.Nodes; using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Models; -namespace Microsoft.OpenApi.Readers.ParseNodes +namespace Microsoft.OpenApi.Reader.ParseNodes { internal class AnyFieldMapParameter { @@ -13,24 +14,24 @@ internal class AnyFieldMapParameter /// Constructor. /// public AnyFieldMapParameter( - Func propertyGetter, - Action propertySetter, - Func schemaGetter) + Func propertyGetter, + Action propertySetter, + Func SchemaGetter = null) { this.PropertyGetter = propertyGetter; this.PropertySetter = propertySetter; - this.SchemaGetter = schemaGetter; + this.SchemaGetter = SchemaGetter; } /// /// Function to retrieve the value of the property. /// - public Func PropertyGetter { get; } + public Func PropertyGetter { get; } /// /// Function to set the value of the property. /// - public Action PropertySetter { get; } + public Action PropertySetter { get; } /// /// Function to get the schema to apply to the property. diff --git a/src/Microsoft.OpenApi.Readers/ParseNodes/AnyListFieldMap.cs b/src/Microsoft.OpenApi/Reader/ParseNodes/AnyListFieldMap.cs similarity index 83% rename from src/Microsoft.OpenApi.Readers/ParseNodes/AnyListFieldMap.cs rename to src/Microsoft.OpenApi/Reader/ParseNodes/AnyListFieldMap.cs index ffd73f893..578d6b68e 100644 --- a/src/Microsoft.OpenApi.Readers/ParseNodes/AnyListFieldMap.cs +++ b/src/Microsoft.OpenApi/Reader/ParseNodes/AnyListFieldMap.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; -namespace Microsoft.OpenApi.Readers.ParseNodes +namespace Microsoft.OpenApi.Reader.ParseNodes { internal class AnyListFieldMap : Dictionary> { diff --git a/src/Microsoft.OpenApi.Readers/ParseNodes/AnyListFieldMapParameter.cs b/src/Microsoft.OpenApi/Reader/ParseNodes/AnyListFieldMapParameter.cs similarity index 66% rename from src/Microsoft.OpenApi.Readers/ParseNodes/AnyListFieldMapParameter.cs rename to src/Microsoft.OpenApi/Reader/ParseNodes/AnyListFieldMapParameter.cs index aed34cf60..fc87a548e 100644 --- a/src/Microsoft.OpenApi.Readers/ParseNodes/AnyListFieldMapParameter.cs +++ b/src/Microsoft.OpenApi/Reader/ParseNodes/AnyListFieldMapParameter.cs @@ -3,10 +3,10 @@ using System; using System.Collections.Generic; -using Microsoft.OpenApi.Any; +using System.Text.Json.Nodes; using Microsoft.OpenApi.Models; -namespace Microsoft.OpenApi.Readers.ParseNodes +namespace Microsoft.OpenApi.Reader.ParseNodes { internal class AnyListFieldMapParameter { @@ -14,24 +14,24 @@ internal class AnyListFieldMapParameter /// Constructor /// public AnyListFieldMapParameter( - Func> propertyGetter, - Action> propertySetter, - Func schemaGetter) + Func> propertyGetter, + Action> propertySetter, + Func SchemaGetter = null) { this.PropertyGetter = propertyGetter; this.PropertySetter = propertySetter; - this.SchemaGetter = schemaGetter; + this.SchemaGetter = SchemaGetter; } /// /// Function to retrieve the value of the property. /// - public Func> PropertyGetter { get; } + public Func> PropertyGetter { get; } /// /// Function to set the value of the property. /// - public Action> PropertySetter { get; } + public Action> PropertySetter { get; } /// /// Function to get the schema to apply to the property. diff --git a/src/Microsoft.OpenApi.Readers/ParseNodes/AnyMapFieldMap.cs b/src/Microsoft.OpenApi/Reader/ParseNodes/AnyMapFieldMap.cs similarity index 83% rename from src/Microsoft.OpenApi.Readers/ParseNodes/AnyMapFieldMap.cs rename to src/Microsoft.OpenApi/Reader/ParseNodes/AnyMapFieldMap.cs index 55dd3b96a..cc4128740 100644 --- a/src/Microsoft.OpenApi.Readers/ParseNodes/AnyMapFieldMap.cs +++ b/src/Microsoft.OpenApi/Reader/ParseNodes/AnyMapFieldMap.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; -namespace Microsoft.OpenApi.Readers.ParseNodes +namespace Microsoft.OpenApi.Reader.ParseNodes { internal class AnyMapFieldMap : Dictionary> { diff --git a/src/Microsoft.OpenApi.Readers/ParseNodes/AnyMapFieldMapParameter.cs b/src/Microsoft.OpenApi/Reader/ParseNodes/AnyMapFieldMapParameter.cs similarity index 82% rename from src/Microsoft.OpenApi.Readers/ParseNodes/AnyMapFieldMapParameter.cs rename to src/Microsoft.OpenApi/Reader/ParseNodes/AnyMapFieldMapParameter.cs index 7ea30bdd9..a4dc41b7f 100644 --- a/src/Microsoft.OpenApi.Readers/ParseNodes/AnyMapFieldMapParameter.cs +++ b/src/Microsoft.OpenApi/Reader/ParseNodes/AnyMapFieldMapParameter.cs @@ -3,10 +3,11 @@ using System; using System.Collections.Generic; +using System.Text.Json.Nodes; using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Models; -namespace Microsoft.OpenApi.Readers.ParseNodes +namespace Microsoft.OpenApi.Reader.ParseNodes { internal class AnyMapFieldMapParameter { @@ -15,8 +16,8 @@ internal class AnyMapFieldMapParameter /// public AnyMapFieldMapParameter( Func> propertyMapGetter, - Func propertyGetter, - Action propertySetter, + Func propertyGetter, + Action propertySetter, Func schemaGetter) { this.PropertyMapGetter = propertyMapGetter; @@ -33,12 +34,12 @@ public AnyMapFieldMapParameter( /// /// Function to retrieve the value of the property from an inner element. /// - public Func PropertyGetter { get; } + public Func PropertyGetter { get; } /// /// Function to set the value of the property. /// - public Action PropertySetter { get; } + public Action PropertySetter { get; } /// /// Function to get the schema to apply to the property. diff --git a/src/Microsoft.OpenApi.Readers/ParseNodes/FixedFieldMap.cs b/src/Microsoft.OpenApi/Reader/ParseNodes/FixedFieldMap.cs similarity index 67% rename from src/Microsoft.OpenApi.Readers/ParseNodes/FixedFieldMap.cs rename to src/Microsoft.OpenApi/Reader/ParseNodes/FixedFieldMap.cs index 4364cf1df..139f38c6a 100644 --- a/src/Microsoft.OpenApi.Readers/ParseNodes/FixedFieldMap.cs +++ b/src/Microsoft.OpenApi/Reader/ParseNodes/FixedFieldMap.cs @@ -3,10 +3,11 @@ using System; using System.Collections.Generic; +using Microsoft.OpenApi.Models; -namespace Microsoft.OpenApi.Readers.ParseNodes +namespace Microsoft.OpenApi.Reader.ParseNodes { - internal class FixedFieldMap : Dictionary> + internal class FixedFieldMap : Dictionary> { } } diff --git a/src/Microsoft.OpenApi/Reader/ParseNodes/JsonPointerExtensions.cs b/src/Microsoft.OpenApi/Reader/ParseNodes/JsonPointerExtensions.cs new file mode 100644 index 000000000..b349f2d5d --- /dev/null +++ b/src/Microsoft.OpenApi/Reader/ParseNodes/JsonPointerExtensions.cs @@ -0,0 +1,49 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System; +using System.Text.Json.Nodes; + +namespace Microsoft.OpenApi.Reader.ParseNodes +{ + /// + /// Extensions for JSON pointers. + /// + public static class JsonPointerExtensions + { + /// + /// Finds the JSON node that corresponds to this JSON pointer based on the base Json node. + /// + public static JsonNode Find(this JsonPointer currentPointer, JsonNode baseJsonNode) + { + if (currentPointer.Tokens.Length == 0) + { + return baseJsonNode; + } + + try + { + var pointer = baseJsonNode; + foreach (var token in currentPointer.Tokens) + { + var array = pointer as JsonArray; + + if (array != null && int.TryParse(token, out var tokenValue)) + { + pointer = array[tokenValue]; + } + else if (pointer is JsonObject map && !map.TryGetPropertyValue(token, out pointer)) + { + return null; + } + } + + return pointer; + } + catch (Exception) + { + return null; + } + } + } +} diff --git a/src/Microsoft.OpenApi.Readers/ParseNodes/ListNode.cs b/src/Microsoft.OpenApi/Reader/ParseNodes/ListNode.cs similarity index 54% rename from src/Microsoft.OpenApi.Readers/ParseNodes/ListNode.cs rename to src/Microsoft.OpenApi/Reader/ParseNodes/ListNode.cs index dbeadb5d1..6654344cd 100644 --- a/src/Microsoft.OpenApi.Readers/ParseNodes/ListNode.cs +++ b/src/Microsoft.OpenApi/Reader/ParseNodes/ListNode.cs @@ -5,49 +5,53 @@ using System.Collections; using System.Collections.Generic; using System.Linq; +using System.Text.Json.Nodes; using Microsoft.OpenApi.Any; -using Microsoft.OpenApi.Readers.Exceptions; -using SharpYaml.Serialization; +using Microsoft.OpenApi.Exceptions; +using Microsoft.OpenApi.Models; -namespace Microsoft.OpenApi.Readers.ParseNodes +namespace Microsoft.OpenApi.Reader.ParseNodes { internal class ListNode : ParseNode, IEnumerable { - private readonly YamlSequenceNode _nodeList; + private readonly JsonArray _nodeList; - public ListNode(ParsingContext context, YamlSequenceNode sequenceNode) : base( - context) + public ListNode(ParsingContext context, JsonArray jsonArray) : base( + context, jsonArray) { - _nodeList = sequenceNode; + _nodeList = jsonArray; } - public override List CreateList(Func map) + public override List CreateList(Func map, OpenApiDocument hostDocument = null) { if (_nodeList == null) { - throw new OpenApiReaderException($"Expected list while parsing {typeof(T).Name}"); + throw new OpenApiReaderException($"Expected list while parsing {typeof(T).Name}", _nodeList); } - return _nodeList.Select(n => map(new(Context, n as YamlMappingNode))) + return _nodeList?.Select(n => map(new MapNode(Context, n as JsonObject), hostDocument)) .Where(i => i != null) .ToList(); } - public override List CreateListOfAny() + public override List CreateListOfAny() { - return _nodeList.Select(n => Create(Context, n).CreateAny()) + + var list = _nodeList.Select(n => Create(Context, n).CreateAny()) .Where(i => i != null) .ToList(); + + return list; } - public override List CreateSimpleList(Func map) + public override List CreateSimpleList(Func map) { if (_nodeList == null) { - throw new OpenApiReaderException($"Expected list while parsing {typeof(T).Name}"); + throw new OpenApiReaderException($"Expected list while parsing {typeof(T).Name}", _nodeList); } - return _nodeList.Select(n => map(new(Context, n))).ToList(); + return _nodeList.Select(n => map(new(Context, n), null)).ToList(); } public IEnumerator GetEnumerator() @@ -61,18 +65,12 @@ IEnumerator IEnumerable.GetEnumerator() } /// - /// Create a + /// Create a /// /// The created Any object. - public override IOpenApiAny CreateAny() + public override JsonNode CreateAny() { - var array = new OpenApiArray(); - foreach (var node in this) - { - array.Add(node.CreateAny()); - } - - return array; + return _nodeList; } } } diff --git a/src/Microsoft.OpenApi/Reader/ParseNodes/MapNode.cs b/src/Microsoft.OpenApi/Reader/ParseNodes/MapNode.cs new file mode 100644 index 000000000..919f1d85c --- /dev/null +++ b/src/Microsoft.OpenApi/Reader/ParseNodes/MapNode.cs @@ -0,0 +1,179 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text.Json; +using System.Text.Json.Nodes; +using Microsoft.OpenApi.Any; +using Microsoft.OpenApi.Exceptions; +using Microsoft.OpenApi.Interfaces; +using Microsoft.OpenApi.Models; + +namespace Microsoft.OpenApi.Reader.ParseNodes +{ + /// + /// Abstraction of a Map to isolate semantic parsing from details of JSON DOM + /// + internal class MapNode : ParseNode, IEnumerable + { + private readonly JsonObject _node; + private readonly List _nodes; + + public MapNode(ParsingContext context, JsonNode node) : base( + context, node) + { + if (node is not JsonObject mapNode) + { + throw new OpenApiReaderException("Expected map.", Context); + } + + _node = mapNode; + _nodes = _node.Select(p => new PropertyNode(Context, p.Key, p.Value)).ToList(); + } + + public PropertyNode this[string key] + { + get + { + if (_node.TryGetPropertyValue(key, out var node)) + { + return new(Context, key, node); + } + + return null; + } + } + + public override Dictionary CreateMap(Func map, OpenApiDocument hostDocument = null) + { + var jsonMap = _node ?? throw new OpenApiReaderException($"Expected map while parsing {typeof(T).Name}", Context); + var nodes = jsonMap.Select( + n => + { + + var key = n.Key; + T value; + try + { + Context.StartObject(key); + value = n.Value is JsonObject jsonObject + ? map(new MapNode(Context, jsonObject), hostDocument) + : default; + } + finally + { + Context.EndObject(); + } + return new + { + key, + value + }; + }); + + return nodes.ToDictionary(k => k.key, v => v.value); + } + + public override Dictionary CreateSimpleMap(Func map) + { + var jsonMap = _node ?? throw new OpenApiReaderException($"Expected map while parsing {typeof(T).Name}", Context); + var nodes = jsonMap.Select( + n => + { + var key = n.Key; + try + { + Context.StartObject(key); + JsonValue valueNode = n.Value is JsonValue value ? value + : throw new OpenApiReaderException($"Expected scalar while parsing {typeof(T).Name}", Context); + + return (key, value: map(new ValueNode(Context, valueNode))); + } + finally + { + Context.EndObject(); + } + }); + + return nodes.ToDictionary(k => k.key, v => v.value); + } + + public IEnumerator GetEnumerator() + { + return _nodes.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return _nodes.GetEnumerator(); + } + + public override string GetRaw() + { + var x = JsonSerializer.Serialize(_node); + return x; + } + + public T GetReferencedObject(ReferenceType referenceType, string referenceId, string summary = null, string description = null) + where T : IOpenApiReferenceable, new() + { + return new() + { + UnresolvedReference = true, + Reference = Context.VersionService.ConvertToOpenApiReference(referenceId, referenceType, summary, description) + }; + } + + public string GetReferencePointer() + { + if (!_node.TryGetPropertyValue("$ref", out JsonNode refNode)) + { + return null; + } + + return refNode.GetScalarValue(); + } + + public string GetSummaryValue() + { + if (!_node.TryGetPropertyValue("summary", out JsonNode summaryNode)) + { + return null; + } + + return summaryNode.GetScalarValue(); + } + + public string GetDescriptionValue() + { + if (!_node.TryGetPropertyValue("description", out JsonNode descriptionNode)) + { + return null; + } + + return descriptionNode.GetScalarValue(); + } + + public string GetScalarValue(ValueNode key) + { + var scalarNode = _node[key.GetScalarValue()] is JsonValue jsonValue + ? jsonValue + : throw new OpenApiReaderException($"Expected scalar while parsing {key.GetScalarValue()}", Context); + + return Convert.ToString(scalarNode?.GetValue(), CultureInfo.InvariantCulture); + } + + /// + /// Create an + /// + /// The created Json object. + public override JsonNode CreateAny() + { + return _node; + } + } +} diff --git a/src/Microsoft.OpenApi.Readers/ParseNodes/ParseNode.cs b/src/Microsoft.OpenApi/Reader/ParseNodes/ParseNode.cs similarity index 68% rename from src/Microsoft.OpenApi.Readers/ParseNodes/ParseNode.cs rename to src/Microsoft.OpenApi/Reader/ParseNodes/ParseNode.cs index 028371c39..44d626f35 100644 --- a/src/Microsoft.OpenApi.Readers/ParseNodes/ParseNode.cs +++ b/src/Microsoft.OpenApi/Reader/ParseNodes/ParseNode.cs @@ -3,23 +3,25 @@ using System; using System.Collections.Generic; +using System.Text.Json.Nodes; using Microsoft.OpenApi.Any; -using Microsoft.OpenApi.Interfaces; +using Microsoft.OpenApi.Exceptions; using Microsoft.OpenApi.Models; -using Microsoft.OpenApi.Readers.Exceptions; -using SharpYaml.Serialization; -namespace Microsoft.OpenApi.Readers.ParseNodes +namespace Microsoft.OpenApi.Reader.ParseNodes { internal abstract class ParseNode { - protected ParseNode(ParsingContext parsingContext) + protected ParseNode(ParsingContext parsingContext, JsonNode jsonNode) { Context = parsingContext; + JsonNode = jsonNode; } public ParsingContext Context { get; } + public JsonNode JsonNode { get; } + public MapNode CheckMapNode(string nodeName) { if (this is not MapNode mapNode) @@ -30,40 +32,32 @@ public MapNode CheckMapNode(string nodeName) return mapNode; } - public static ParseNode Create(ParsingContext context, YamlNode node) + public static ParseNode Create(ParsingContext context, JsonNode node) { - if (node is YamlSequenceNode listNode) + if (node is JsonArray listNode) { return new ListNode(context, listNode); } - if (node is YamlMappingNode mapNode) + if (node is JsonObject mapNode) { return new MapNode(context, mapNode); } - return new ValueNode(context, node as YamlScalarNode); + return new ValueNode(context, node as JsonValue); } - public virtual List CreateList(Func map) + public virtual List CreateList(Func map, OpenApiDocument hostDocument = null) { throw new OpenApiReaderException("Cannot create list from this type of node.", Context); } - public virtual Dictionary CreateMap(Func map) + public virtual Dictionary CreateMap(Func map, OpenApiDocument hostDocument = null) { throw new OpenApiReaderException("Cannot create map from this type of node.", Context); } - public virtual Dictionary CreateMapWithReference( - ReferenceType referenceType, - Func map) - where T : class, IOpenApiReferenceable - { - throw new OpenApiReaderException("Cannot create map from this reference.", Context); - } - - public virtual List CreateSimpleList(Func map) + public virtual List CreateSimpleList(Func map) { throw new OpenApiReaderException("Cannot create simple list from this type of node.", Context); } @@ -73,7 +67,7 @@ public virtual Dictionary CreateSimpleMap(Func map) throw new OpenApiReaderException("Cannot create simple map from this type of node.", Context); } - public virtual IOpenApiAny CreateAny() + public virtual JsonNode CreateAny() { throw new OpenApiReaderException("Cannot create an Any object this type of node.", Context); } @@ -88,9 +82,9 @@ public virtual string GetScalarValue() throw new OpenApiReaderException("Cannot create a scalar value from this type of node.", Context); } - public virtual List CreateListOfAny() + public virtual List CreateListOfAny() { throw new OpenApiReaderException("Cannot create a list from this type of node.", Context); - } + } } } diff --git a/src/Microsoft.OpenApi.Readers/ParseNodes/ParserHelper.cs b/src/Microsoft.OpenApi/Reader/ParseNodes/ParserHelper.cs similarity index 96% rename from src/Microsoft.OpenApi.Readers/ParseNodes/ParserHelper.cs rename to src/Microsoft.OpenApi/Reader/ParseNodes/ParserHelper.cs index 9dd05ebdd..030572f68 100644 --- a/src/Microsoft.OpenApi.Readers/ParseNodes/ParserHelper.cs +++ b/src/Microsoft.OpenApi/Reader/ParseNodes/ParserHelper.cs @@ -4,7 +4,7 @@ using System; using System.Globalization; -namespace Microsoft.OpenApi.Readers.ParseNodes +namespace Microsoft.OpenApi.Reader.ParseNodes { /// /// Useful tools to parse data diff --git a/src/Microsoft.OpenApi.Readers/ParseNodes/PatternFieldMap.cs b/src/Microsoft.OpenApi/Reader/ParseNodes/PatternFieldMap.cs similarity index 63% rename from src/Microsoft.OpenApi.Readers/ParseNodes/PatternFieldMap.cs rename to src/Microsoft.OpenApi/Reader/ParseNodes/PatternFieldMap.cs index 8fb28bc5e..79caf3221 100644 --- a/src/Microsoft.OpenApi.Readers/ParseNodes/PatternFieldMap.cs +++ b/src/Microsoft.OpenApi/Reader/ParseNodes/PatternFieldMap.cs @@ -3,10 +3,11 @@ using System; using System.Collections.Generic; +using Microsoft.OpenApi.Models; -namespace Microsoft.OpenApi.Readers.ParseNodes +namespace Microsoft.OpenApi.Reader.ParseNodes { - internal class PatternFieldMap : Dictionary, Action> + internal class PatternFieldMap : Dictionary, Action> { } } diff --git a/src/Microsoft.OpenApi.Readers/ParseNodes/PropertyNode.cs b/src/Microsoft.OpenApi/Reader/ParseNodes/PropertyNode.cs similarity index 82% rename from src/Microsoft.OpenApi.Readers/ParseNodes/PropertyNode.cs rename to src/Microsoft.OpenApi/Reader/ParseNodes/PropertyNode.cs index 6a059c348..5f8031e87 100644 --- a/src/Microsoft.OpenApi.Readers/ParseNodes/PropertyNode.cs +++ b/src/Microsoft.OpenApi/Reader/ParseNodes/PropertyNode.cs @@ -4,17 +4,17 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Text.Json.Nodes; using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Exceptions; -using Microsoft.OpenApi.Readers.Exceptions; -using SharpYaml.Serialization; +using Microsoft.OpenApi.Models; -namespace Microsoft.OpenApi.Readers.ParseNodes +namespace Microsoft.OpenApi.Reader.ParseNodes { internal class PropertyNode : ParseNode { - public PropertyNode(ParsingContext context, string name, YamlNode node) : base( - context) + public PropertyNode(ParsingContext context, string name, JsonNode node) : base( + context, node) { Name = name; Value = Create(context, node); @@ -26,15 +26,16 @@ public PropertyNode(ParsingContext context, string name, YamlNode node) : base( public void ParseField( T parentInstance, - IDictionary> fixedFields, - IDictionary, Action> patternFields) + IDictionary> fixedFields, + IDictionary, Action> patternFields, + OpenApiDocument hostDocument = null) { if (fixedFields.TryGetValue(Name, out var fixedFieldMap)) { try { Context.StartObject(Name); - fixedFieldMap(parentInstance, Value); + fixedFieldMap(parentInstance, Value, hostDocument); } catch (OpenApiReaderException ex) { @@ -58,7 +59,7 @@ public void ParseField( try { Context.StartObject(Name); - map(parentInstance, Name, Value); + map(parentInstance, Name, Value, hostDocument); } catch (OpenApiReaderException ex) { @@ -82,7 +83,7 @@ public void ParseField( } } - public override IOpenApiAny CreateAny() + public override JsonNode CreateAny() { throw new NotImplementedException(); } diff --git a/src/Microsoft.OpenApi/Reader/ParseNodes/RootNode.cs b/src/Microsoft.OpenApi/Reader/ParseNodes/RootNode.cs new file mode 100644 index 000000000..b9e49b47d --- /dev/null +++ b/src/Microsoft.OpenApi/Reader/ParseNodes/RootNode.cs @@ -0,0 +1,37 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System.Text.Json.Nodes; + +namespace Microsoft.OpenApi.Reader.ParseNodes +{ + /// + /// Wrapper class around JsonDocument to isolate semantic parsing from details of Json DOM. + /// + internal class RootNode : ParseNode + { + private readonly JsonNode _jsonNode; + + public RootNode( + ParsingContext context, + JsonNode jsonNode) : base(context, jsonNode) + { + _jsonNode = jsonNode; + } + + public ParseNode Find(JsonPointer referencePointer) + { + if (referencePointer.Find(_jsonNode) is not JsonNode jsonNode) + { + return null; + } + + return Create(Context, jsonNode); + } + + public MapNode GetMap() + { + return new MapNode(Context, _jsonNode); + } + } +} diff --git a/src/Microsoft.OpenApi/Reader/ParseNodes/ValueNode.cs b/src/Microsoft.OpenApi/Reader/ParseNodes/ValueNode.cs new file mode 100644 index 000000000..ec9fefde5 --- /dev/null +++ b/src/Microsoft.OpenApi/Reader/ParseNodes/ValueNode.cs @@ -0,0 +1,40 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System; +using System.Globalization; +using System.Text.Json.Nodes; +using Microsoft.OpenApi.Any; +using Microsoft.OpenApi.Exceptions; + +namespace Microsoft.OpenApi.Reader.ParseNodes +{ + internal class ValueNode : ParseNode + { + private readonly JsonValue _node; + + public ValueNode(ParsingContext context, JsonNode node) : base( + context, node) + { + if (node is not JsonValue scalarNode) + { + throw new OpenApiReaderException("Expected a value.", node); + } + _node = scalarNode; + } + + public override string GetScalarValue() + { + return Convert.ToString(_node.GetValue(), CultureInfo.InvariantCulture); + } + + /// + /// Create a + /// + /// The created Any object. + public override JsonNode CreateAny() + { + return _node; + } + } +} diff --git a/src/Microsoft.OpenApi.Readers/ParsingContext.cs b/src/Microsoft.OpenApi/Reader/ParsingContext.cs similarity index 72% rename from src/Microsoft.OpenApi.Readers/ParsingContext.cs rename to src/Microsoft.OpenApi/Reader/ParsingContext.cs index 6227514b8..aae60da9d 100644 --- a/src/Microsoft.OpenApi.Readers/ParsingContext.cs +++ b/src/Microsoft.OpenApi/Reader/ParsingContext.cs @@ -4,17 +4,17 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Text.Json.Nodes; using Microsoft.OpenApi.Any; +using Microsoft.OpenApi.Exceptions; using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Models; -using Microsoft.OpenApi.Readers.Exceptions; -using Microsoft.OpenApi.Readers.Interface; -using Microsoft.OpenApi.Readers.ParseNodes; -using Microsoft.OpenApi.Readers.V2; -using Microsoft.OpenApi.Readers.V3; -using SharpYaml.Serialization; - -namespace Microsoft.OpenApi.Readers +using Microsoft.OpenApi.Reader.ParseNodes; +using Microsoft.OpenApi.Reader.V2; +using Microsoft.OpenApi.Reader.V3; +using Microsoft.OpenApi.Reader.V31; + +namespace Microsoft.OpenApi.Reader { /// /// The Parsing Context holds temporary state needed whilst parsing an OpenAPI Document @@ -25,11 +25,25 @@ public class ParsingContext private readonly Dictionary _tempStorage = new(); private readonly Dictionary> _scopedTempStorage = new(); private readonly Dictionary> _loopStacks = new(); - internal Dictionary> ExtensionParsers { get; set; } = new(); + + /// + /// Extension parsers + /// + public Dictionary> ExtensionParsers { get; set; } = + new(); + internal RootNode RootNode { get; set; } internal List Tags { get; private set; } = new(); - internal Uri BaseUrl { get; set; } - internal List DefaultContentType { get; set; } + + /// + /// The base url for the document + /// + public Uri BaseUrl { get; set; } + + /// + /// Default content type for a response object + /// + public List DefaultContentType { get; set; } /// /// Diagnostic object that returns metadata about the parsing process. @@ -48,11 +62,11 @@ public ParsingContext(OpenApiDiagnostic diagnostic) /// /// Initiates the parsing process. Not thread safe and should only be called once on a parsing context /// - /// Yaml document to parse. + /// Set of Json nodes to parse. /// An OpenApiDocument populated based on the passed yamlDocument - internal OpenApiDocument Parse(YamlDocument yamlDocument) + public OpenApiDocument Parse(JsonNode jsonNode) { - RootNode = new(this, yamlDocument); + RootNode = new RootNode(this, jsonNode); var inputVersion = GetVersion(RootNode); @@ -60,18 +74,25 @@ internal OpenApiDocument Parse(YamlDocument yamlDocument) switch (inputVersion) { - case string and "2.0": + case string version when version.is2_0(): VersionService = new OpenApiV2VersionService(Diagnostic); doc = VersionService.LoadDocument(RootNode); this.Diagnostic.SpecificationVersion = OpenApiSpecVersion.OpenApi2_0; + ValidateRequiredFields(doc, version); break; - case string version when version.StartsWith("3.0"): + case string version when version.is3_0(): VersionService = new OpenApiV3VersionService(Diagnostic); doc = VersionService.LoadDocument(RootNode); - this.Diagnostic.SpecificationVersion = OpenApiSpecVersion.OpenApi3_0; + this.Diagnostic.SpecificationVersion = version.is3_1() ? OpenApiSpecVersion.OpenApi3_1 : OpenApiSpecVersion.OpenApi3_0; + ValidateRequiredFields(doc, version); + break; + case string version when version.is3_1(): + VersionService = new OpenApiV31VersionService(Diagnostic); + doc = VersionService.LoadDocument(RootNode); + this.Diagnostic.SpecificationVersion = OpenApiSpecVersion.OpenApi3_1; + ValidateRequiredFields(doc, version); break; - default: throw new OpenApiUnsupportedSpecVersionException(inputVersion); } @@ -82,12 +103,12 @@ internal OpenApiDocument Parse(YamlDocument yamlDocument) /// /// Initiates the parsing process of a fragment. Not thread safe and should only be called once on a parsing context /// - /// + /// /// OpenAPI version of the fragment /// An OpenApiDocument populated based on the passed yamlDocument - internal T ParseFragment(YamlDocument yamlDocument, OpenApiSpecVersion version) where T : IOpenApiElement + public T ParseFragment(JsonNode jsonNode, OpenApiSpecVersion version) where T : IOpenApiElement { - var node = ParseNode.Create(this, yamlDocument.RootNode); + var node = ParseNode.Create(this, jsonNode); var element = default(T); @@ -102,6 +123,10 @@ internal T ParseFragment(YamlDocument yamlDocument, OpenApiSpecVersion versio this.VersionService = new OpenApiV3VersionService(Diagnostic); element = this.VersionService.LoadElement(node); break; + case OpenApiSpecVersion.OpenApi3_1: + this.VersionService = new OpenApiV31VersionService(Diagnostic); + element = this.VersionService.LoadElement(node); + break; } return element; @@ -116,12 +141,12 @@ private static string GetVersion(RootNode rootNode) if (versionNode != null) { - return versionNode.GetScalarValue(); + return versionNode.GetScalarValue().Replace("\"", string.Empty); } versionNode = rootNode.Find(new("/swagger")); - return versionNode?.GetScalarValue(); + return versionNode?.GetScalarValue().Replace("\"", string.Empty); } /// @@ -142,7 +167,7 @@ public void EndObject() /// public string GetLocation() { - return "#/" + string.Join("/", _currentLocation.Reverse().Select(s=> s.Replace("~","~0").Replace("/","~1")).ToArray()); + return "#/" + string.Join("/", _currentLocation.Reverse().Select(s => s.Replace("~", "~0").Replace("/", "~1")).ToArray()); } /// @@ -243,5 +268,14 @@ public void PopLoop(string loopid) _loopStacks[loopid].Pop(); } } + + private void ValidateRequiredFields(OpenApiDocument doc, string version) + { + if ((version.is2_0() || version.is3_0()) && (doc.Paths == null || !doc.Paths.Any())) + { + // paths is a required field in OpenAPI 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.Readers/ReadResult.cs b/src/Microsoft.OpenApi/Reader/ReadResult.cs similarity index 95% rename from src/Microsoft.OpenApi.Readers/ReadResult.cs rename to src/Microsoft.OpenApi/Reader/ReadResult.cs index 382c22d64..77a18ff78 100644 --- a/src/Microsoft.OpenApi.Readers/ReadResult.cs +++ b/src/Microsoft.OpenApi/Reader/ReadResult.cs @@ -3,7 +3,7 @@ using Microsoft.OpenApi.Models; -namespace Microsoft.OpenApi.Readers +namespace Microsoft.OpenApi.Reader { /// /// Container object used for returning the result of reading an OpenAPI description. diff --git a/src/Microsoft.OpenApi.Readers/Services/DefaultStreamLoader.cs b/src/Microsoft.OpenApi/Reader/Services/DefaultStreamLoader.cs similarity index 59% rename from src/Microsoft.OpenApi.Readers/Services/DefaultStreamLoader.cs rename to src/Microsoft.OpenApi/Reader/Services/DefaultStreamLoader.cs index 3c7fd8d8a..746ca0c96 100644 --- a/src/Microsoft.OpenApi.Readers/Services/DefaultStreamLoader.cs +++ b/src/Microsoft.OpenApi/Reader/Services/DefaultStreamLoader.cs @@ -6,22 +6,28 @@ using System.IO; using System.Net.Http; using System.Threading.Tasks; -using Microsoft.OpenApi.Readers.Interface; +using Microsoft.OpenApi.Interfaces; +using Microsoft.OpenApi.Models; -namespace Microsoft.OpenApi.Readers.Services +namespace Microsoft.OpenApi.Reader.Services { /// /// Implementation of IInputLoader that loads streams from URIs /// - internal class DefaultStreamLoader : IStreamLoader + public class DefaultStreamLoader : IStreamLoader { private readonly Uri baseUrl; private HttpClient _httpClient = new(); + /// + /// The default stream loader + /// + /// public DefaultStreamLoader(Uri baseUrl) { this.baseUrl = baseUrl; } +/// [Obsolete] [EditorBrowsable(EditorBrowsableState.Never)] @@ -32,9 +38,17 @@ public Stream Load(Uri uri) #pragma warning restore VSTHRD002 // Avoid problematic synchronous waits } + /// + /// Use Uri to locate data and convert into an input object. + /// + /// Identifier of some source of an OpenAPI Description + /// A data object that can be processed by a reader to generate an + /// public async Task LoadAsync(Uri uri) { - var absoluteUri = new Uri(baseUrl, uri); + Uri absoluteUri; + absoluteUri = baseUrl.AbsoluteUri.Equals(OpenApiConstants.BaseRegistryUri) ? new Uri(Directory.GetCurrentDirectory() + uri) + : new Uri(baseUrl, uri); switch (absoluteUri.Scheme) { diff --git a/src/Microsoft.OpenApi.Readers/Services/OpenApiRemoteReferenceCollector.cs b/src/Microsoft.OpenApi/Reader/Services/OpenApiRemoteReferenceCollector.cs similarity index 73% rename from src/Microsoft.OpenApi.Readers/Services/OpenApiRemoteReferenceCollector.cs rename to src/Microsoft.OpenApi/Reader/Services/OpenApiRemoteReferenceCollector.cs index cc2879bd5..bb66cf9b2 100644 --- a/src/Microsoft.OpenApi.Readers/Services/OpenApiRemoteReferenceCollector.cs +++ b/src/Microsoft.OpenApi/Reader/Services/OpenApiRemoteReferenceCollector.cs @@ -6,21 +6,22 @@ using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Services; -namespace Microsoft.OpenApi.Readers.Services +namespace Microsoft.OpenApi.Reader.Services { /// /// Builds a list of all remote references used in an OpenApi document /// internal class OpenApiRemoteReferenceCollector : OpenApiVisitorBase { - private Dictionary _references = new(); + private readonly Dictionary _references = new(); /// - /// List of external references collected from OpenApiDocument + /// List of all external references collected from OpenApiDocument /// public IEnumerable References { - get { + get + { return _references.Values; } } @@ -31,13 +32,13 @@ public IEnumerable References /// public override void Visit(IOpenApiReferenceable referenceable) { - AddReference(referenceable.Reference); + AddExternalReferences(referenceable.Reference); } /// - /// Collect external reference + /// Collect external references /// - private void AddReference(OpenApiReference reference) + private void AddExternalReferences(OpenApiReference reference) { if (reference is {IsExternal: true} && !_references.ContainsKey(reference.ExternalResource)) diff --git a/src/Microsoft.OpenApi.Readers/Services/OpenApiWorkspaceLoader.cs b/src/Microsoft.OpenApi/Reader/Services/OpenApiWorkspaceLoader.cs similarity index 63% rename from src/Microsoft.OpenApi.Readers/Services/OpenApiWorkspaceLoader.cs rename to src/Microsoft.OpenApi/Reader/Services/OpenApiWorkspaceLoader.cs index c2d1cfe3c..a3462da70 100644 --- a/src/Microsoft.OpenApi.Readers/Services/OpenApiWorkspaceLoader.cs +++ b/src/Microsoft.OpenApi/Reader/Services/OpenApiWorkspaceLoader.cs @@ -1,16 +1,16 @@ using System; using System.Threading; using System.Threading.Tasks; +using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Models; -using Microsoft.OpenApi.Readers.Interface; using Microsoft.OpenApi.Services; -namespace Microsoft.OpenApi.Readers.Services +namespace Microsoft.OpenApi.Reader.Services { internal class OpenApiWorkspaceLoader { - private OpenApiWorkspace _workspace; - private IStreamLoader _loader; + private readonly OpenApiWorkspace _workspace; + private readonly IStreamLoader _loader; private readonly OpenApiReaderSettings _readerSettings; public OpenApiWorkspaceLoader(OpenApiWorkspace workspace, IStreamLoader loader, OpenApiReaderSettings readerSettings) @@ -20,9 +20,15 @@ public OpenApiWorkspaceLoader(OpenApiWorkspace workspace, IStreamLoader loader, _readerSettings = readerSettings; } - internal async Task LoadAsync(OpenApiReference reference, OpenApiDocument document, OpenApiDiagnostic diagnostic = null, CancellationToken cancellationToken = default) + internal async Task LoadAsync(OpenApiReference reference, + OpenApiDocument document, + string format = null, + OpenApiDiagnostic diagnostic = null, + CancellationToken cancellationToken = default) { - _workspace.AddDocument(reference.ExternalResource, document); + _workspace.AddDocumentId(reference.ExternalResource, document.BaseUri); + var version = diagnostic?.SpecificationVersion ?? OpenApiSpecVersion.OpenApi3_0; + _workspace.RegisterComponents(document); document.Workspace = _workspace; // Collect remote references by walking document @@ -30,21 +36,17 @@ internal async Task LoadAsync(OpenApiReference reference, Ope var collectorWalker = new OpenApiWalker(referenceCollector); collectorWalker.Walk(document); - var reader = new OpenApiStreamReader(_readerSettings); - - if (diagnostic is null) - { - diagnostic = new(); - } + diagnostic ??= new() { SpecificationVersion = version }; // Walk references foreach (var item in referenceCollector.References) { + // If not already in workspace, load it and process references if (!_workspace.Contains(item.ExternalResource)) { var input = await _loader.LoadAsync(new(item.ExternalResource, UriKind.RelativeOrAbsolute)); - var result = await reader.ReadAsync(input, cancellationToken); + var result = await OpenApiDocument.LoadAsync(input, format, _readerSettings, cancellationToken); // Merge diagnostics if (result.OpenApiDiagnostic != null) { @@ -52,7 +54,7 @@ internal async Task LoadAsync(OpenApiReference reference, Ope } if (result.OpenApiDocument != null) { - var loadDiagnostic = await LoadAsync(item, result.OpenApiDocument, diagnostic, cancellationToken); + var loadDiagnostic = await LoadAsync(item, result.OpenApiDocument, format, diagnostic, cancellationToken); diagnostic = loadDiagnostic; } } diff --git a/src/Microsoft.OpenApi.Readers/V2/OpenApiContactDeserializer.cs b/src/Microsoft.OpenApi/Reader/V2/OpenApiContactDeserializer.cs similarity index 55% rename from src/Microsoft.OpenApi.Readers/V2/OpenApiContactDeserializer.cs rename to src/Microsoft.OpenApi/Reader/V2/OpenApiContactDeserializer.cs index c1cab19f0..2cb8dea9c 100644 --- a/src/Microsoft.OpenApi.Readers/V2/OpenApiContactDeserializer.cs +++ b/src/Microsoft.OpenApi/Reader/V2/OpenApiContactDeserializer.cs @@ -4,9 +4,9 @@ using System; using Microsoft.OpenApi.Extensions; using Microsoft.OpenApi.Models; -using Microsoft.OpenApi.Readers.ParseNodes; +using Microsoft.OpenApi.Reader.ParseNodes; -namespace Microsoft.OpenApi.Readers.V2 +namespace Microsoft.OpenApi.Reader.V2 { /// /// Class containing logic to deserialize Open API V2 document into @@ -14,28 +14,28 @@ namespace Microsoft.OpenApi.Readers.V2 /// internal static partial class OpenApiV2Deserializer { - private static FixedFieldMap _contactFixedFields = new() + private static readonly FixedFieldMap _contactFixedFields = new() { { "name", - (o, n) => o.Name = n.GetScalarValue() + (o, n, t) => o.Name = n.GetScalarValue() }, { "url", - (o, n) => o.Url = new(n.GetScalarValue(), UriKind.RelativeOrAbsolute) + (o, n, t) => o.Url = new(n.GetScalarValue(), UriKind.RelativeOrAbsolute) }, { "email", - (o, n) => o.Email = n.GetScalarValue() + (o, n, t) => o.Email = n.GetScalarValue() }, }; - private static PatternFieldMap _contactPatternFields = new() + private static readonly PatternFieldMap _contactPatternFields = new() { - {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, LoadExtension(p, n))} + {s => s.StartsWith("x-"), (o, p, n, _) => o.AddExtension(p, LoadExtension(p, n))} }; - public static OpenApiContact LoadContact(ParseNode node) + public static OpenApiContact LoadContact(ParseNode node, OpenApiDocument hostDocument = null) { var mapNode = node as MapNode; var contact = new OpenApiContact(); diff --git a/src/Microsoft.OpenApi.Readers/V2/OpenApiDocumentDeserializer.cs b/src/Microsoft.OpenApi/Reader/V2/OpenApiDocumentDeserializer.cs similarity index 74% rename from src/Microsoft.OpenApi.Readers/V2/OpenApiDocumentDeserializer.cs rename to src/Microsoft.OpenApi/Reader/V2/OpenApiDocumentDeserializer.cs index 07bdcb301..f33d98465 100644 --- a/src/Microsoft.OpenApi.Readers/V2/OpenApiDocumentDeserializer.cs +++ b/src/Microsoft.OpenApi/Reader/V2/OpenApiDocumentDeserializer.cs @@ -7,10 +7,10 @@ using System.Linq; using Microsoft.OpenApi.Extensions; using Microsoft.OpenApi.Models; -using Microsoft.OpenApi.Readers.ParseNodes; +using Microsoft.OpenApi.Reader.ParseNodes; using Microsoft.OpenApi.Services; -namespace Microsoft.OpenApi.Readers.V2 +namespace Microsoft.OpenApi.Reader.V2 { /// /// Class containing logic to deserialize Open API V2 document into @@ -18,26 +18,26 @@ namespace Microsoft.OpenApi.Readers.V2 /// internal static partial class OpenApiV2Deserializer { - private static FixedFieldMap _openApiFixedFields = new() + private static readonly FixedFieldMap _openApiFixedFields = new() { { - "swagger", (_, _) => {} + "swagger", (_, _, _) => {} /* Version is valid field but we already parsed it */ }, - {"info", (o, n) => o.Info = LoadInfo(n)}, - {"host", (_, n) => n.Context.SetTempStorage("host", n.GetScalarValue())}, - {"basePath", (_, n) => n.Context.SetTempStorage("basePath", n.GetScalarValue())}, + {"info", (o, n, _) => o.Info = LoadInfo(n, o)}, + {"host", (_, n, _) => n.Context.SetTempStorage("host", n.GetScalarValue())}, + {"basePath", (_, n, _) => n.Context.SetTempStorage("basePath", n.GetScalarValue())}, { - "schemes", (_, n) => n.Context.SetTempStorage( + "schemes", (_, n, _) => n.Context.SetTempStorage( "schemes", n.CreateSimpleList( - s => s.GetScalarValue())) + (s, p) => s.GetScalarValue())) }, { "consumes", - (_, n) => + (_, n, _) => { - var consumes = n.CreateSimpleList(s => s.GetScalarValue()); + var consumes = n.CreateSimpleList((s, p) => s.GetScalarValue()); if (consumes.Count > 0) { n.Context.SetTempStorage(TempStorageKeys.GlobalConsumes, consumes); @@ -45,91 +45,73 @@ internal static partial class OpenApiV2Deserializer } }, { - "produces", (_, n) => { - var produces = n.CreateSimpleList(s => s.GetScalarValue()); + "produces", (_, n, _) => { + var produces = n.CreateSimpleList((s, p) => s.GetScalarValue()); if (produces.Count > 0) { n.Context.SetTempStorage(TempStorageKeys.GlobalProduces, produces); } } }, - {"paths", (o, n) => o.Paths = LoadPaths(n)}, + {"paths", (o, n, _) => o.Paths = LoadPaths(n, o)}, { "definitions", - (o, n) => + (o, n, _) => { - if (o.Components == null) - { - o.Components = new(); - } - - o.Components.Schemas = n.CreateMapWithReference( - ReferenceType.Schema, - LoadSchema); + o.Components ??= new(); + o.Components.Schemas = n.CreateMap(LoadSchema, o); } }, { "parameters", - (o, n) => + (o, n, _) => { if (o.Components == null) { o.Components = new(); } - o.Components.Parameters = n.CreateMapWithReference( - ReferenceType.Parameter, - LoadParameter); + o.Components.Parameters = n.CreateMap(LoadParameter, o); - o.Components.RequestBodies = n.CreateMapWithReference(ReferenceType.RequestBody, p => + o.Components.RequestBodies = n.CreateMap((p, d) => { - var parameter = LoadParameter(p, loadRequestBody: true); - if (parameter != null) - { - return CreateRequestBody(n.Context, parameter); - } - - return null; + var parameter = LoadParameter(node: p, loadRequestBody: true, hostDocument: d); + return parameter != null ? CreateRequestBody(p.Context, parameter) : null; } ); } }, { - "responses", (o, n) => + "responses", (o, n, _) => { if (o.Components == null) { o.Components = new(); } - o.Components.Responses = n.CreateMapWithReference( - ReferenceType.Response, - LoadResponse); + o.Components.Responses = n.CreateMap(LoadResponse, o); } }, { - "securityDefinitions", (o, n) => + "securityDefinitions", (o, n, _) => { if (o.Components == null) { o.Components = new(); } - o.Components.SecuritySchemes = n.CreateMapWithReference( - ReferenceType.SecurityScheme, - LoadSecurityScheme - ); + o.Components.SecuritySchemes = n.CreateMap(LoadSecurityScheme, o); } }, - {"security", (o, n) => o.SecurityRequirements = n.CreateList(LoadSecurityRequirement)}, - {"tags", (o, n) => o.Tags = n.CreateList(LoadTag)}, - {"externalDocs", (o, n) => o.ExternalDocs = LoadExternalDocs(n)} + {"security", (o, n, _) => o.SecurityRequirements = n.CreateList(LoadSecurityRequirement, o)}, + {"tags", (o, n, _) => o.Tags = n.CreateList(LoadTag, o)}, + {"externalDocs", (o, n, _) => o.ExternalDocs = LoadExternalDocs(n, o)} }; - private static PatternFieldMap _openApiPatternFields = new() + private static readonly PatternFieldMap _openApiPatternFields = new() { // We have no semantics to verify X- nodes, therefore treat them as just values. - {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, LoadExtension(p, n))} + {s => s.StartsWith("x-"), (o, p, n, _) => o.AddExtension(p, LoadExtension(p, n))} }; private static void MakeServers(IList servers, ParsingContext context, RootNode rootNode) @@ -206,7 +188,7 @@ private static void MakeServers(IList servers, ParsingContext con private static string BuildUrl(string scheme, string host, string basePath) { - if (String.IsNullOrEmpty(scheme) && !String.IsNullOrEmpty(host)) + if (string.IsNullOrEmpty(scheme) && !string.IsNullOrEmpty(host)) { host = "//" + host; // The double slash prefix creates a relative url where the scheme is defined by the BaseUrl } @@ -241,34 +223,38 @@ private static string BuildUrl(string scheme, string host, string basePath) public static OpenApiDocument LoadOpenApi(RootNode rootNode) { - var openApidoc = new OpenApiDocument(); + var openApiDoc = new OpenApiDocument(); var openApiNode = rootNode.GetMap(); - ParseMap(openApiNode, openApidoc, _openApiFixedFields, _openApiPatternFields); + ParseMap(openApiNode, openApiDoc, _openApiFixedFields, _openApiPatternFields); - if (openApidoc.Paths != null) + if (openApiDoc.Paths != null) { ProcessResponsesMediaTypes( rootNode.GetMap(), - openApidoc.Paths.Values + openApiDoc.Paths.Values .SelectMany(path => path.Operations?.Values ?? Enumerable.Empty()) .SelectMany(operation => operation.Responses?.Values ?? Enumerable.Empty()), openApiNode.Context); } - ProcessResponsesMediaTypes(rootNode.GetMap(), openApidoc.Components?.Responses?.Values, openApiNode.Context); + ProcessResponsesMediaTypes(rootNode.GetMap(), openApiDoc.Components?.Responses?.Values, openApiNode.Context); // Post Process OpenApi Object - if (openApidoc.Servers == null) + if (openApiDoc.Servers == null) { - openApidoc.Servers = new List(); + openApiDoc.Servers = new List(); } - MakeServers(openApidoc.Servers, openApiNode.Context, rootNode); + MakeServers(openApiDoc.Servers, openApiNode.Context, rootNode); + + FixRequestBodyReferences(openApiDoc); + + // Register components + openApiDoc.Workspace.RegisterComponents(openApiDoc); - FixRequestBodyReferences(openApidoc); - return openApidoc; + return openApiDoc; } private static void ProcessResponsesMediaTypes(MapNode mapNode, IEnumerable responses, ParsingContext context) diff --git a/src/Microsoft.OpenApi.Readers/V2/OpenApiExternalDocsDeserializer.cs b/src/Microsoft.OpenApi/Reader/V2/OpenApiExternalDocsDeserializer.cs similarity index 75% rename from src/Microsoft.OpenApi.Readers/V2/OpenApiExternalDocsDeserializer.cs rename to src/Microsoft.OpenApi/Reader/V2/OpenApiExternalDocsDeserializer.cs index 5297a3a72..8e90fb4e7 100644 --- a/src/Microsoft.OpenApi.Readers/V2/OpenApiExternalDocsDeserializer.cs +++ b/src/Microsoft.OpenApi/Reader/V2/OpenApiExternalDocsDeserializer.cs @@ -4,9 +4,9 @@ using System; using Microsoft.OpenApi.Extensions; using Microsoft.OpenApi.Models; -using Microsoft.OpenApi.Readers.ParseNodes; +using Microsoft.OpenApi.Reader.ParseNodes; -namespace Microsoft.OpenApi.Readers.V2 +namespace Microsoft.OpenApi.Reader.V2 { /// /// Class containing logic to deserialize Open API V2 document into @@ -19,21 +19,21 @@ internal static partial class OpenApiV2Deserializer { { OpenApiConstants.Description, - (o, n) => o.Description = n.GetScalarValue() + (o, n, _) => o.Description = n.GetScalarValue() }, { OpenApiConstants.Url, - (o, n) => o.Url = new(n.GetScalarValue(), UriKind.RelativeOrAbsolute) + (o, n, _) => o.Url = new(n.GetScalarValue(), UriKind.RelativeOrAbsolute) }, }; private static readonly PatternFieldMap _externalDocsPatternFields = new() { - {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, LoadExtension(p, n))} + {s => s.StartsWith("x-"), (o, p, n, _) => o.AddExtension(p, LoadExtension(p, n))} }; - public static OpenApiExternalDocs LoadExternalDocs(ParseNode node) + public static OpenApiExternalDocs LoadExternalDocs(ParseNode node, OpenApiDocument hostDocument = null) { var mapNode = node.CheckMapNode("externalDocs"); diff --git a/src/Microsoft.OpenApi/Reader/V2/OpenApiHeaderDeserializer.cs b/src/Microsoft.OpenApi/Reader/V2/OpenApiHeaderDeserializer.cs new file mode 100644 index 000000000..b4ddf7300 --- /dev/null +++ b/src/Microsoft.OpenApi/Reader/V2/OpenApiHeaderDeserializer.cs @@ -0,0 +1,145 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System; +using System.Globalization; +using Microsoft.OpenApi.Extensions; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Exceptions; +using Microsoft.OpenApi.Reader.ParseNodes; + +namespace Microsoft.OpenApi.Reader.V2 +{ + /// + /// Class containing logic to deserialize Open API V2 document into + /// runtime Open API object model. + /// + internal static partial class OpenApiV2Deserializer + { + private static readonly FixedFieldMap _headerFixedFields = new() + { + { + "description", + (o, n, _) => o.Description = n.GetScalarValue() + }, + { + "type", + (o, n, _) => GetOrCreateSchema(o).Type = n.GetScalarValue().ToJsonSchemaType() + }, + { + "format", + (o, n, _) => GetOrCreateSchema(o).Format = n.GetScalarValue() + }, + { + "items", + (o, n, _) => GetOrCreateSchema(o).Items = LoadSchema(n) + }, + { + "collectionFormat", + (o, n, _) => LoadStyle(o, n.GetScalarValue()) + }, + { + "default", + (o, n, _) => GetOrCreateSchema(o).Default = n.CreateAny() + }, + { + "maximum", + (o, n, _) => GetOrCreateSchema(o).Maximum = ParserHelper.ParseDecimalWithFallbackOnOverflow(n.GetScalarValue(), decimal.MaxValue) + }, + { + "exclusiveMaximum", + (o, n, _) => GetOrCreateSchema(o).ExclusiveMaximum = bool.Parse(n.GetScalarValue()) + }, + { + "minimum", + (o, n, _) => GetOrCreateSchema(o).Minimum = ParserHelper.ParseDecimalWithFallbackOnOverflow(n.GetScalarValue(), decimal.MinValue) + }, + { + "exclusiveMinimum", + (o, n, _) => GetOrCreateSchema(o).ExclusiveMinimum = bool.Parse(n.GetScalarValue()) + }, + { + "maxLength", + (o, n, _) => GetOrCreateSchema(o).MaxLength = int.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture) + }, + { + "minLength", + (o, n, _) => GetOrCreateSchema(o).MinLength = int.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture) + }, + { + "pattern", + (o, n, _) => GetOrCreateSchema(o).Pattern = n.GetScalarValue() + }, + { + "maxItems", + (o, n, _) => GetOrCreateSchema(o).MaxItems = int.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture) + }, + { + "minItems", + (o, n, _) => GetOrCreateSchema(o).MinItems = int.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture) + }, + { + "uniqueItems", + (o, n, _) => GetOrCreateSchema(o).UniqueItems = bool.Parse(n.GetScalarValue()) + }, + { + "multipleOf", + (o, n, _) => GetOrCreateSchema(o).MultipleOf = decimal.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture) + }, + { + "enum", + (o, n, _) => GetOrCreateSchema(o).Enum = n.CreateListOfAny() + } + }; + + private static readonly PatternFieldMap _headerPatternFields = new() + { + {s => s.StartsWith("x-"), (o, p, n, _) => o.AddExtension(p, LoadExtension(p, n))} + }; + + private static OpenApiSchema GetOrCreateSchema(OpenApiHeader p) + { + return p.Schema ??= new(); + } + + public static OpenApiHeader LoadHeader(ParseNode node, OpenApiDocument hostDocument = null) + { + var mapNode = node.CheckMapNode("header"); + var header = new OpenApiHeader(); + + foreach (var property in mapNode) + { + property.ParseField(header, _headerFixedFields, _headerPatternFields); + } + + var schema = node.Context.GetFromTempStorage("schema"); + if (schema != null) + { + header.Schema = schema; + node.Context.SetTempStorage("schema", null); + } + + return header; + } + + private static void LoadStyle(OpenApiHeader header, string style) + { + switch (style) + { + case "csv": + header.Style = ParameterStyle.Simple; + return; + case "ssv": + header.Style = ParameterStyle.SpaceDelimited; + return; + case "pipes": + header.Style = ParameterStyle.PipeDelimited; + return; + case "tsv": + throw new NotSupportedException(); + default: + throw new OpenApiReaderException("Unrecognized header style: " + style); + } + } + } +} diff --git a/src/Microsoft.OpenApi.Readers/V2/OpenApiInfoDeserializer.cs b/src/Microsoft.OpenApi/Reader/V2/OpenApiInfoDeserializer.cs similarity index 54% rename from src/Microsoft.OpenApi.Readers/V2/OpenApiInfoDeserializer.cs rename to src/Microsoft.OpenApi/Reader/V2/OpenApiInfoDeserializer.cs index 479eaa5c8..90a8535b1 100644 --- a/src/Microsoft.OpenApi.Readers/V2/OpenApiInfoDeserializer.cs +++ b/src/Microsoft.OpenApi/Reader/V2/OpenApiInfoDeserializer.cs @@ -4,9 +4,9 @@ using System; using Microsoft.OpenApi.Extensions; using Microsoft.OpenApi.Models; -using Microsoft.OpenApi.Readers.ParseNodes; +using Microsoft.OpenApi.Reader.ParseNodes; -namespace Microsoft.OpenApi.Readers.V2 +namespace Microsoft.OpenApi.Reader.V2 { /// /// Class containing logic to deserialize Open API V2 document into @@ -14,40 +14,40 @@ namespace Microsoft.OpenApi.Readers.V2 /// internal static partial class OpenApiV2Deserializer { - private static FixedFieldMap _infoFixedFields = new() + private static readonly FixedFieldMap _infoFixedFields = new() { { "title", - (o, n) => o.Title = n.GetScalarValue() + (o, n, _) => o.Title = n.GetScalarValue() }, { "description", - (o, n) => o.Description = n.GetScalarValue() + (o, n, _) => o.Description = n.GetScalarValue() }, { "termsOfService", - (o, n) => o.TermsOfService = new(n.GetScalarValue(), UriKind.RelativeOrAbsolute) + (o, n, _) => o.TermsOfService = new(n.GetScalarValue(), UriKind.RelativeOrAbsolute) }, { "contact", - (o, n) => o.Contact = LoadContact(n) + (o, n, t) => o.Contact = LoadContact(n, t) }, { "license", - (o, n) => o.License = LoadLicense(n) + (o, n, t) => o.License = LoadLicense(n, t) }, { "version", - (o, n) => o.Version = n.GetScalarValue() + (o, n, _) => o.Version = n.GetScalarValue() } }; - private static PatternFieldMap _infoPatternFields = new() + private static readonly PatternFieldMap _infoPatternFields = new() { - {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, LoadExtension(p, n))} + {s => s.StartsWith("x-"), (o, p, n, _) => o.AddExtension(p, LoadExtension(p, n))} }; - public static OpenApiInfo LoadInfo(ParseNode node) + public static OpenApiInfo LoadInfo(ParseNode node, OpenApiDocument hostDocument = null) { var mapNode = node.CheckMapNode("Info"); diff --git a/src/Microsoft.OpenApi.Readers/V2/OpenApiLicenseDeserializer.cs b/src/Microsoft.OpenApi/Reader/V2/OpenApiLicenseDeserializer.cs similarity index 54% rename from src/Microsoft.OpenApi.Readers/V2/OpenApiLicenseDeserializer.cs rename to src/Microsoft.OpenApi/Reader/V2/OpenApiLicenseDeserializer.cs index db394087f..f1f7a7b93 100644 --- a/src/Microsoft.OpenApi.Readers/V2/OpenApiLicenseDeserializer.cs +++ b/src/Microsoft.OpenApi/Reader/V2/OpenApiLicenseDeserializer.cs @@ -4,9 +4,9 @@ using System; using Microsoft.OpenApi.Extensions; using Microsoft.OpenApi.Models; -using Microsoft.OpenApi.Readers.ParseNodes; +using Microsoft.OpenApi.Reader.ParseNodes; -namespace Microsoft.OpenApi.Readers.V2 +namespace Microsoft.OpenApi.Reader.V2 { /// /// Class containing logic to deserialize Open API V2 document into @@ -14,30 +14,30 @@ namespace Microsoft.OpenApi.Readers.V2 /// internal static partial class OpenApiV2Deserializer { - private static FixedFieldMap _licenseFixedFields = new() + private static readonly FixedFieldMap _licenseFixedFields = new() { { "name", - (o, n) => o.Name = n.GetScalarValue() + (o, n, _) => o.Name = n.GetScalarValue() }, { "url", - (o, n) => o.Url = new(n.GetScalarValue(), UriKind.RelativeOrAbsolute) + (o, n, _) => o.Url = new(n.GetScalarValue(), UriKind.RelativeOrAbsolute) }, }; - private static PatternFieldMap _licensePatternFields = new() + private static readonly PatternFieldMap _licensePatternFields = new() { - {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, LoadExtension(p, n))} + {s => s.StartsWith("x-"), (o, p, n, _) => o.AddExtension(p, LoadExtension(p, n))} }; - public static OpenApiLicense LoadLicense(ParseNode node) + public static OpenApiLicense LoadLicense(ParseNode node, OpenApiDocument hostDocument = null) { var mapNode = node.CheckMapNode("OpenApiLicense"); var license = new OpenApiLicense(); - ParseMap(mapNode, license, _licenseFixedFields, _licensePatternFields); + ParseMap(mapNode, license, _licenseFixedFields, _licensePatternFields, doc: hostDocument); return license; } diff --git a/src/Microsoft.OpenApi.Readers/V2/OpenApiOperationDeserializer.cs b/src/Microsoft.OpenApi/Reader/V2/OpenApiOperationDeserializer.cs similarity index 76% rename from src/Microsoft.OpenApi.Readers/V2/OpenApiOperationDeserializer.cs rename to src/Microsoft.OpenApi/Reader/V2/OpenApiOperationDeserializer.cs index fa91f913a..d65f7a16b 100644 --- a/src/Microsoft.OpenApi.Readers/V2/OpenApiOperationDeserializer.cs +++ b/src/Microsoft.OpenApi/Reader/V2/OpenApiOperationDeserializer.cs @@ -3,14 +3,13 @@ using System.Collections.Generic; using System.Linq; -using System.Security.Cryptography.X509Certificates; -using System.Xml.Linq; using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Extensions; using Microsoft.OpenApi.Models; -using Microsoft.OpenApi.Readers.ParseNodes; +using Microsoft.OpenApi.Reader.ParseNodes; +using Microsoft.OpenApi.Models.References; -namespace Microsoft.OpenApi.Readers.V2 +namespace Microsoft.OpenApi.Reader.V2 { /// /// Class containing logic to deserialize Open API V2 document into @@ -22,43 +21,43 @@ internal static partial class OpenApiV2Deserializer new() { { - "tags", (o, n) => o.Tags = n.CreateSimpleList( - valueNode => + "tags", (o, n, doc) => o.Tags = n.CreateSimpleList( + (valueNode, doc) => LoadTagByReference( valueNode.Context, - valueNode.GetScalarValue())) + valueNode.GetScalarValue(), doc)) }, { "summary", - (o, n) => o.Summary = n.GetScalarValue() + (o, n, _) => o.Summary = n.GetScalarValue() }, { "description", - (o, n) => o.Description = n.GetScalarValue() + (o, n, _) => o.Description = n.GetScalarValue() }, { "externalDocs", - (o, n) => o.ExternalDocs = LoadExternalDocs(n) + (o, n, t) => o.ExternalDocs = LoadExternalDocs(n, t) }, { "operationId", - (o, n) => o.OperationId = n.GetScalarValue() + (o, n, _) => o.OperationId = n.GetScalarValue() }, { "parameters", - (o, n) => o.Parameters = n.CreateList(LoadParameter) + (o, n, t) => o.Parameters = n.CreateList(LoadParameter, t) }, { - "consumes", (_, n) => { - var consumes = n.CreateSimpleList(s => s.GetScalarValue()); + "consumes", (_, n, _) => { + var consumes = n.CreateSimpleList((s, p) => s.GetScalarValue()); if (consumes.Count > 0) { n.Context.SetTempStorage(TempStorageKeys.OperationConsumes,consumes); } } }, { - "produces", (_, n) => { - var produces = n.CreateSimpleList(s => s.GetScalarValue()); + "produces", (_, n, _) => { + var produces = n.CreateSimpleList((s, p) => s.GetScalarValue()); if (produces.Count > 0) { n.Context.SetTempStorage(TempStorageKeys.OperationProduces, produces); } @@ -66,22 +65,22 @@ internal static partial class OpenApiV2Deserializer }, { "responses", - (o, n) => o.Responses = LoadResponses(n) + (o, n, t) => o.Responses = LoadResponses(n, t) }, { "deprecated", - (o, n) => o.Deprecated = bool.Parse(n.GetScalarValue()) + (o, n, _) => o.Deprecated = bool.Parse(n.GetScalarValue()) }, { "security", - (o, n) => o.Security = n.CreateList(LoadSecurityRequirement) + (o, n, t) => o.Security = n.CreateList(LoadSecurityRequirement, t) }, }; private static readonly PatternFieldMap _operationPatternFields = new() { - {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, LoadExtension(p, n))} + {s => s.StartsWith("x-"), (o, p, n, _) => o.AddExtension(p, LoadExtension(p, n))} }; private static readonly FixedFieldMap _responsesFixedFields = new(); @@ -89,11 +88,11 @@ internal static partial class OpenApiV2Deserializer private static readonly PatternFieldMap _responsesPatternFields = new() { - {s => !s.StartsWith("x-"), (o, p, n) => o.Add(p, LoadResponse(n))}, - {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, LoadExtension(p, n))} + {s => !s.StartsWith("x-"), (o, p, n, t) => o.Add(p, LoadResponse(n, t))}, + {s => s.StartsWith("x-"), (o, p, n, _) => o.AddExtension(p, LoadExtension(p, n))} }; - internal static OpenApiOperation LoadOperation(ParseNode node) + internal static OpenApiOperation LoadOperation(ParseNode node, OpenApiDocument hostDocument = null) { // Reset these temp storage parameters for each operation. node.Context.SetTempStorage(TempStorageKeys.BodyParameter, null); @@ -105,7 +104,7 @@ internal static OpenApiOperation LoadOperation(ParseNode node) var operation = new OpenApiOperation(); - ParseMap(mapNode, operation, _operationFixedFields, _operationPatternFields); + ParseMap(mapNode, operation, _operationFixedFields, _operationPatternFields, doc: hostDocument); // Build request body based on information determined while parsing OpenApiOperation var bodyParameter = node.Context.GetFromTempStorage(TempStorageKeys.BodyParameter); @@ -133,13 +132,13 @@ internal static OpenApiOperation LoadOperation(ParseNode node) return operation; } - public static OpenApiResponses LoadResponses(ParseNode node) + public static OpenApiResponses LoadResponses(ParseNode node, OpenApiDocument hostDocument = null) { var mapNode = node.CheckMapNode("Responses"); var domainObject = new OpenApiResponses(); - ParseMap(mapNode, domainObject, _responsesFixedFields, _responsesPatternFields); + ParseMap(mapNode, domainObject, _responsesFixedFields, _responsesPatternFields, doc:hostDocument); return domainObject; } @@ -173,8 +172,9 @@ private static OpenApiRequestBody CreateFormBody(ParsingContext context, List k, _ => mediaType) }; - foreach(var value in formBody.Content.Values.Where(static x => x.Schema is not null && x.Schema.Properties.Any() && string.IsNullOrEmpty(x.Schema.Type))) - value.Schema.Type = "object"; + + foreach (var value in formBody.Content.Values.Where(static x => x.Schema is not null && x.Schema.Properties.Any() && x.Schema.Type == null)) + value.Schema.Type = JsonSchemaType.Object; return formBody; } @@ -201,21 +201,15 @@ internal static OpenApiRequestBody CreateRequestBody( Extensions = bodyParameter.Extensions }; - requestBody.Extensions[OpenApiConstants.BodyName] = new OpenApiString(bodyParameter.Name); + requestBody.Extensions[OpenApiConstants.BodyName] = new OpenApiAny(bodyParameter.Name); return requestBody; } private static OpenApiTag LoadTagByReference( ParsingContext context, - string tagName) + string tagName, OpenApiDocument hostDocument = null) { - var tagObject = new OpenApiTag - { - UnresolvedReference = true, - Reference = new() { Id = tagName, Type = ReferenceType.Tag } - }; - - return tagObject; + return new OpenApiTagReference(tagName, hostDocument); } } } diff --git a/src/Microsoft.OpenApi.Readers/V2/OpenApiParameterDeserializer.cs b/src/Microsoft.OpenApi/Reader/V2/OpenApiParameterDeserializer.cs similarity index 64% rename from src/Microsoft.OpenApi.Readers/V2/OpenApiParameterDeserializer.cs rename to src/Microsoft.OpenApi/Reader/V2/OpenApiParameterDeserializer.cs index a7d9f93d4..149c00fd3 100644 --- a/src/Microsoft.OpenApi.Readers/V2/OpenApiParameterDeserializer.cs +++ b/src/Microsoft.OpenApi/Reader/V2/OpenApiParameterDeserializer.cs @@ -6,9 +6,10 @@ using System.Globalization; using Microsoft.OpenApi.Extensions; using Microsoft.OpenApi.Models; -using Microsoft.OpenApi.Readers.ParseNodes; +using Microsoft.OpenApi.Models.References; +using Microsoft.OpenApi.Reader.ParseNodes; -namespace Microsoft.OpenApi.Readers.V2 +namespace Microsoft.OpenApi.Reader.V2 { /// /// Class containing logic to deserialize Open API V2 document into @@ -21,7 +22,7 @@ internal static partial class OpenApiV2Deserializer { { "name", - (o, n) => o.Name = n.GetScalarValue() + (o, n, t) => o.Name = n.GetScalarValue() }, { "in", @@ -29,71 +30,71 @@ internal static partial class OpenApiV2Deserializer }, { "description", - (o, n) => o.Description = n.GetScalarValue() + (o, n, t) => o.Description = n.GetScalarValue() }, { "required", - (o, n) => o.Required = bool.Parse(n.GetScalarValue()) + (o, n, t) => o.Required = bool.Parse(n.GetScalarValue()) }, { "deprecated", - (o, n) => o.Deprecated = bool.Parse(n.GetScalarValue()) + (o, n, t) => o.Deprecated = bool.Parse(n.GetScalarValue()) }, { "allowEmptyValue", - (o, n) => o.AllowEmptyValue = bool.Parse(n.GetScalarValue()) + (o, n, t) => o.AllowEmptyValue = bool.Parse(n.GetScalarValue()) }, { "type", - (o, n) => GetOrCreateSchema(o).Type = n.GetScalarValue() + (o, n, t) => GetOrCreateSchema(o).Type = n.GetScalarValue().ToJsonSchemaType() }, { "items", - (o, n) => GetOrCreateSchema(o).Items = LoadSchema(n) + (o, n, t) => GetOrCreateSchema(o).Items = LoadSchema(n) }, { "collectionFormat", - (o, n) => LoadStyle(o, n.GetScalarValue()) + (o, n, t) => LoadStyle(o, n.GetScalarValue()) }, { "format", - (o, n) => GetOrCreateSchema(o).Format = n.GetScalarValue() + (o, n, t) => GetOrCreateSchema(o).Format = n.GetScalarValue() }, { "minimum", - (o, n) => GetOrCreateSchema(o).Minimum = ParserHelper.ParseDecimalWithFallbackOnOverflow(n.GetScalarValue(), decimal.MinValue) + (o, n, t) => GetOrCreateSchema(o).Minimum = ParserHelper.ParseDecimalWithFallbackOnOverflow(n.GetScalarValue(), decimal.MinValue) }, { "maximum", - (o, n) => GetOrCreateSchema(o).Maximum = ParserHelper.ParseDecimalWithFallbackOnOverflow(n.GetScalarValue(), decimal.MaxValue) + (o, n, t) => GetOrCreateSchema(o).Maximum = ParserHelper.ParseDecimalWithFallbackOnOverflow(n.GetScalarValue(), decimal.MaxValue) }, { "maxLength", - (o, n) => GetOrCreateSchema(o).MaxLength = int.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture) + (o, n, t) => GetOrCreateSchema(o).MaxLength = int.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture) }, { "minLength", - (o, n) => GetOrCreateSchema(o).MinLength = int.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture) + (o, n, t) => GetOrCreateSchema(o).MinLength = int.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture) }, { "readOnly", - (o, n) => GetOrCreateSchema(o).ReadOnly = bool.Parse(n.GetScalarValue()) + (o, n, t) => GetOrCreateSchema(o).ReadOnly = bool.Parse(n.GetScalarValue()) }, { "default", - (o, n) => GetOrCreateSchema(o).Default = n.CreateAny() + (o, n, t) => GetOrCreateSchema(o).Default = n.CreateAny() }, { "pattern", - (o, n) => GetOrCreateSchema(o).Pattern = n.GetScalarValue() + (o, n, t) => GetOrCreateSchema(o).Pattern = n.GetScalarValue() }, { "enum", - (o, n) => GetOrCreateSchema(o).Enum = n.CreateListOfAny() + (o, n, t) => GetOrCreateSchema(o).Enum = n.CreateListOfAny() }, { "schema", - (o, n) => o.Schema = LoadSchema(n) + (o, n, t) => o.Schema = LoadSchema(n, t) }, { "x-examples", @@ -105,41 +106,7 @@ internal static partial class OpenApiV2Deserializer new() { {s => s.StartsWith("x-") && !s.Equals(OpenApiConstants.ExamplesExtension, StringComparison.OrdinalIgnoreCase), - (o, p, n) => o.AddExtension(p, LoadExtension(p, n))} - }; - - private static readonly AnyFieldMap _parameterAnyFields = - new() - { - { - OpenApiConstants.Default, - new( - p => p.Schema?.Default, - (p, v) => { - if (p.Schema != null || v != null) - { - GetOrCreateSchema(p).Default = v; - } - }, - p => p.Schema) - } - }; - - private static readonly AnyListFieldMap _parameterAnyListFields = - new() - { - { - OpenApiConstants.Enum, - new( - p => p.Schema?.Enum, - (p, v) => { - if (p.Schema != null || v is {Count: > 0}) - { - GetOrCreateSchema(p).Enum = v; - } - }, - p => p.Schema) - }, + (o, p, n, _) => o.AddExtension(p, LoadExtension(p, n))} }; private static void LoadStyle(OpenApiParameter p, string v) @@ -171,7 +138,7 @@ private static void LoadStyle(OpenApiParameter p, string v) } } - private static void LoadParameterExamplesExtension(OpenApiParameter parameter, ParseNode node) + private static void LoadParameterExamplesExtension(OpenApiParameter parameter, ParseNode node, OpenApiDocument hostDocument = null) { var examples = LoadExamplesExtension(node); node.Context.SetTempStorage(TempStorageKeys.Examples, examples, parameter); @@ -179,25 +146,10 @@ private static void LoadParameterExamplesExtension(OpenApiParameter parameter, P private static OpenApiSchema GetOrCreateSchema(OpenApiParameter p) { - if (p.Schema == null) - { - p.Schema = new(); - } - - return p.Schema; - } - - private static OpenApiSchema GetOrCreateSchema(OpenApiHeader p) - { - if (p.Schema == null) - { - p.Schema = new(); - } - - return p.Schema; + return p.Schema ??= new(); } - private static void ProcessIn(OpenApiParameter o, ParseNode n) + private static void ProcessIn(OpenApiParameter o, ParseNode n, OpenApiDocument hostDocument = null) { var value = n.GetScalarValue(); switch (value) @@ -228,12 +180,12 @@ private static void ProcessIn(OpenApiParameter o, ParseNode n) } } - public static OpenApiParameter LoadParameter(ParseNode node) + public static OpenApiParameter LoadParameter(ParseNode node, OpenApiDocument hostDocument = null) { - return LoadParameter(node, false); + return LoadParameter(node, false, hostDocument); } - public static OpenApiParameter LoadParameter(ParseNode node, bool loadRequestBody) + public static OpenApiParameter LoadParameter(ParseNode node, bool loadRequestBody, OpenApiDocument hostDocument = null) { // Reset the local variables every time this method is called. node.Context.SetTempStorage(TempStorageKeys.ParameterIsBodyOrFormData, false); @@ -244,15 +196,13 @@ public static OpenApiParameter LoadParameter(ParseNode node, bool loadRequestBod if (pointer != null) { - return mapNode.GetReferencedObject(ReferenceType.Parameter, pointer); + var reference = GetReferenceIdAndExternalResource(pointer); + return new OpenApiParameterReference(reference.Item1, hostDocument, reference.Item2); } var parameter = new OpenApiParameter(); - ParseMap(mapNode, parameter, _parameterFixedFields, _parameterPatternFields); - - ProcessAnyFields(mapNode, parameter, _parameterAnyFields); - ProcessAnyListFields(mapNode, parameter, _parameterAnyListFields); + ParseMap(mapNode, parameter, _parameterFixedFields, _parameterPatternFields, doc: hostDocument); var schema = node.Context.GetFromTempStorage("schema"); if (schema != null) diff --git a/src/Microsoft.OpenApi.Readers/V2/OpenApiPathItemDeserializer.cs b/src/Microsoft.OpenApi/Reader/V2/OpenApiPathItemDeserializer.cs similarity index 73% rename from src/Microsoft.OpenApi.Readers/V2/OpenApiPathItemDeserializer.cs rename to src/Microsoft.OpenApi/Reader/V2/OpenApiPathItemDeserializer.cs index afafe425a..71fd2e736 100644 --- a/src/Microsoft.OpenApi.Readers/V2/OpenApiPathItemDeserializer.cs +++ b/src/Microsoft.OpenApi/Reader/V2/OpenApiPathItemDeserializer.cs @@ -5,9 +5,9 @@ using System.Linq; using Microsoft.OpenApi.Extensions; using Microsoft.OpenApi.Models; -using Microsoft.OpenApi.Readers.ParseNodes; +using Microsoft.OpenApi.Reader.ParseNodes; -namespace Microsoft.OpenApi.Readers.V2 +namespace Microsoft.OpenApi.Reader.V2 { /// /// Class containing logic to deserialize Open API V2 document into @@ -18,19 +18,19 @@ internal static partial class OpenApiV2Deserializer private static readonly FixedFieldMap _pathItemFixedFields = new() { { - "$ref", (o, n) => + "$ref", (o, n, t) => { o.Reference = new() { ExternalResource = n.GetScalarValue() }; o.UnresolvedReference =true; } }, - {"get", (o, n) => o.AddOperation(OperationType.Get, LoadOperation(n))}, - {"put", (o, n) => o.AddOperation(OperationType.Put, LoadOperation(n))}, - {"post", (o, n) => o.AddOperation(OperationType.Post, LoadOperation(n))}, - {"delete", (o, n) => o.AddOperation(OperationType.Delete, LoadOperation(n))}, - {"options", (o, n) => o.AddOperation(OperationType.Options, LoadOperation(n))}, - {"head", (o, n) => o.AddOperation(OperationType.Head, LoadOperation(n))}, - {"patch", (o, n) => o.AddOperation(OperationType.Patch, LoadOperation(n))}, + {"get", (o, n, t) => o.AddOperation(OperationType.Get, LoadOperation(n, t))}, + {"put", (o, n, t) => o.AddOperation(OperationType.Put, LoadOperation(n, t))}, + {"post", (o, n, t) => o.AddOperation(OperationType.Post, LoadOperation(n, t))}, + {"delete", (o, n, t) => o.AddOperation(OperationType.Delete, LoadOperation(n, t))}, + {"options", (o, n, t) => o.AddOperation(OperationType.Options, LoadOperation(n, t))}, + {"head", (o, n, t) => o.AddOperation(OperationType.Head, LoadOperation(n, t))}, + {"patch", (o, n, t) => o.AddOperation(OperationType.Patch, LoadOperation(n, t))}, { "parameters", LoadPathParameters @@ -40,21 +40,21 @@ internal static partial class OpenApiV2Deserializer private static readonly PatternFieldMap _pathItemPatternFields = new() { - {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, LoadExtension(p, n))}, + {s => s.StartsWith("x-"), (o, p, n, _) => o.AddExtension(p, LoadExtension(p, n))}, }; - public static OpenApiPathItem LoadPathItem(ParseNode node) + public static OpenApiPathItem LoadPathItem(ParseNode node, OpenApiDocument hostDocument = null) { var mapNode = node.CheckMapNode("PathItem"); var pathItem = new OpenApiPathItem(); - ParseMap(mapNode, pathItem, _pathItemFixedFields, _pathItemPatternFields); + ParseMap(mapNode, pathItem, _pathItemFixedFields, _pathItemPatternFields, doc: hostDocument); return pathItem; } - private static void LoadPathParameters(OpenApiPathItem pathItem, ParseNode node) + private static void LoadPathParameters(OpenApiPathItem pathItem, ParseNode node, OpenApiDocument hostDocument = null) { node.Context.SetTempStorage(TempStorageKeys.BodyParameter, null); node.Context.SetTempStorage(TempStorageKeys.FormParameters, null); @@ -66,7 +66,7 @@ private static void LoadPathParameters(OpenApiPathItem pathItem, ParseNode node) if (bodyParameter != null) { var requestBody = CreateRequestBody(node.Context, bodyParameter); - foreach(var opPair in pathItem.Operations.Where(x => x.Value.RequestBody is null)) + foreach (var opPair in pathItem.Operations.Where(x => x.Value.RequestBody is null)) { switch (opPair.Key) { diff --git a/src/Microsoft.OpenApi.Readers/V2/OpenApiPathsDeserializer.cs b/src/Microsoft.OpenApi/Reader/V2/OpenApiPathsDeserializer.cs similarity index 52% rename from src/Microsoft.OpenApi.Readers/V2/OpenApiPathsDeserializer.cs rename to src/Microsoft.OpenApi/Reader/V2/OpenApiPathsDeserializer.cs index 8e026b7f7..9e0c0f08b 100644 --- a/src/Microsoft.OpenApi.Readers/V2/OpenApiPathsDeserializer.cs +++ b/src/Microsoft.OpenApi/Reader/V2/OpenApiPathsDeserializer.cs @@ -3,9 +3,9 @@ using Microsoft.OpenApi.Extensions; using Microsoft.OpenApi.Models; -using Microsoft.OpenApi.Readers.ParseNodes; +using Microsoft.OpenApi.Reader.ParseNodes; -namespace Microsoft.OpenApi.Readers.V2 +namespace Microsoft.OpenApi.Reader.V2 { /// /// Class containing logic to deserialize Open API V2 document into @@ -13,21 +13,21 @@ namespace Microsoft.OpenApi.Readers.V2 /// internal static partial class OpenApiV2Deserializer { - private static FixedFieldMap _pathsFixedFields = new(); + private static readonly FixedFieldMap _pathsFixedFields = new(); - private static PatternFieldMap _pathsPatternFields = new() + private static readonly PatternFieldMap _pathsPatternFields = new() { - {s => s.StartsWith("/"), (o, k, n) => o.Add(k, LoadPathItem(n))}, - {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, LoadExtension(p, n))} + {s => s.StartsWith("/"), (o, k, n, t) => o.Add(k, LoadPathItem(n, t))}, + {s => s.StartsWith("x-"), (o, p, n, _) => o.AddExtension(p, LoadExtension(p, n))} }; - public static OpenApiPaths LoadPaths(ParseNode node) + public static OpenApiPaths LoadPaths(ParseNode node, OpenApiDocument hostDocument = null) { var mapNode = node.CheckMapNode("Paths"); var domainObject = new OpenApiPaths(); - ParseMap(mapNode, domainObject, _pathsFixedFields, _pathsPatternFields); + ParseMap(mapNode, domainObject, _pathsFixedFields, _pathsPatternFields, doc: hostDocument); return domainObject; } diff --git a/src/Microsoft.OpenApi.Readers/V2/OpenApiResponseDeserializer.cs b/src/Microsoft.OpenApi/Reader/V2/OpenApiResponseDeserializer.cs similarity index 87% rename from src/Microsoft.OpenApi.Readers/V2/OpenApiResponseDeserializer.cs rename to src/Microsoft.OpenApi/Reader/V2/OpenApiResponseDeserializer.cs index 03691d14b..8436a09cd 100644 --- a/src/Microsoft.OpenApi.Readers/V2/OpenApiResponseDeserializer.cs +++ b/src/Microsoft.OpenApi/Reader/V2/OpenApiResponseDeserializer.cs @@ -5,9 +5,10 @@ using System.Collections.Generic; using Microsoft.OpenApi.Extensions; using Microsoft.OpenApi.Models; -using Microsoft.OpenApi.Readers.ParseNodes; +using Microsoft.OpenApi.Models.References; +using Microsoft.OpenApi.Reader.ParseNodes; -namespace Microsoft.OpenApi.Readers.V2 +namespace Microsoft.OpenApi.Reader.V2 { /// /// Class containing logic to deserialize Open API V2 document into @@ -19,23 +20,21 @@ internal static partial class OpenApiV2Deserializer { { "description", - (o, n) => o.Description = n.GetScalarValue() + (o, n, _) => o.Description = n.GetScalarValue() }, { "headers", - (o, n) => o.Headers = n.CreateMap(LoadHeader) + (o, n, t) => o.Headers = n.CreateMap(LoadHeader, t) }, { - "examples", - LoadExamples + "examples", LoadExamples }, { - "x-examples", - LoadResponseExamplesExtension + "x-examples", LoadResponseExamplesExtension }, { "schema", - (o, n) => n.Context.SetTempStorage(TempStorageKeys.ResponseSchema, LoadSchema(n), o) + (o, n, t) => n.Context.SetTempStorage(TempStorageKeys.ResponseSchema, LoadSchema(n, t), o) }, }; @@ -43,7 +42,7 @@ internal static partial class OpenApiV2Deserializer new() { {s => s.StartsWith("x-") && !s.Equals(OpenApiConstants.ExamplesExtension, StringComparison.OrdinalIgnoreCase), - (o, p, n) => o.AddExtension(p, LoadExtension(p, n))} + (o, p, n, _) => o.AddExtension(p, LoadExtension(p, n))} }; private static readonly AnyFieldMap _mediaTypeAnyFields = @@ -105,7 +104,7 @@ private static void ProcessProduces(MapNode mapNode, OpenApiResponse response, P context.SetTempStorage(TempStorageKeys.ResponseProducesSet, true, response); } - private static void LoadResponseExamplesExtension(OpenApiResponse response, ParseNode node) + private static void LoadResponseExamplesExtension(OpenApiResponse response, ParseNode node, OpenApiDocument hostDocument = null) { var examples = LoadExamplesExtension(node); node.Context.SetTempStorage(TempStorageKeys.Examples, examples, response); @@ -132,7 +131,7 @@ private static Dictionary LoadExamplesExtension(ParseNod example.Description = valueNode.Value.GetScalarValue(); break; case "value": - example.Value = OpenApiAnyConverter.GetSpecificOpenApiAny(valueNode.Value.CreateAny()); + example.Value = valueNode.Value.CreateAny(); break; case "externalValue": example.ExternalValue = valueNode.Value.GetScalarValue(); @@ -146,7 +145,7 @@ private static Dictionary LoadExamplesExtension(ParseNod return examples; } - private static void LoadExamples(OpenApiResponse response, ParseNode node) + private static void LoadExamples(OpenApiResponse response, ParseNode node, OpenApiDocument hostDocument = null) { var mapNode = node.CheckMapNode("examples"); @@ -179,14 +178,15 @@ private static void LoadExample(OpenApiResponse response, string mediaType, Pars mediaTypeObject.Example = exampleNode; } - public static OpenApiResponse LoadResponse(ParseNode node) + public static OpenApiResponse LoadResponse(ParseNode node, OpenApiDocument hostDocument = null) { var mapNode = node.CheckMapNode("response"); var pointer = mapNode.GetReferencePointer(); if (pointer != null) { - return mapNode.GetReferencedObject(ReferenceType.Response, pointer); + var reference = GetReferenceIdAndExternalResource(pointer); + return new OpenApiResponseReference(reference.Item1, hostDocument, reference.Item2); } var response = new OpenApiResponse(); diff --git a/src/Microsoft.OpenApi/Reader/V2/OpenApiSchemaDeserializer.cs b/src/Microsoft.OpenApi/Reader/V2/OpenApiSchemaDeserializer.cs new file mode 100644 index 000000000..53208fd40 --- /dev/null +++ b/src/Microsoft.OpenApi/Reader/V2/OpenApiSchemaDeserializer.cs @@ -0,0 +1,180 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System.Collections.Generic; +using System.Globalization; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Extensions; +using Microsoft.OpenApi.Reader.ParseNodes; +using Microsoft.OpenApi.Models.References; + +namespace Microsoft.OpenApi.Reader.V2 +{ + /// + /// Class containing logic to deserialize Open API V2 document into + /// runtime Open API object model. + /// + internal static partial class OpenApiV2Deserializer + { + private static readonly FixedFieldMap _openApiSchemaFixedFields = new() + { + { + "title", + (o, n, _) => o.Title = n.GetScalarValue() + }, + { + "multipleOf", + (o, n, _) => o.MultipleOf = decimal.Parse(n.GetScalarValue(), NumberStyles.Float, CultureInfo.InvariantCulture) + }, + { + "maximum", + (o, n,_) => o.Maximum = ParserHelper.ParseDecimalWithFallbackOnOverflow(n.GetScalarValue(), decimal.MaxValue) + }, + { + "exclusiveMaximum", + (o, n, _) => o.ExclusiveMaximum = bool.Parse(n.GetScalarValue()) + }, + { + "minimum", + (o, n, _) => o.Minimum = ParserHelper.ParseDecimalWithFallbackOnOverflow(n.GetScalarValue(), decimal.MinValue) + }, + { + "exclusiveMinimum", + (o, n, _) => o.ExclusiveMinimum = bool.Parse(n.GetScalarValue()) + }, + { + "maxLength", + (o, n, _) => o.MaxLength = int.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture) + }, + { + "minLength", + (o, n, _) => o.MinLength = int.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture) + }, + { + "pattern", + (o, n, _) => o.Pattern = n.GetScalarValue() + }, + { + "maxItems", + (o, n, _) => o.MaxItems = int.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture) + }, + { + "minItems", + (o, n, _) => o.MinItems = int.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture) + }, + { + "uniqueItems", + (o, n, _) => o.UniqueItems = bool.Parse(n.GetScalarValue()) + }, + { + "maxProperties", + (o, n, _) => o.MaxProperties = int.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture) + }, + { + "minProperties", + (o, n, _) => o.MinProperties = int.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture) + }, + { + "required", + (o, n, _) => o.Required = new HashSet(n.CreateSimpleList((n2, p) => n2.GetScalarValue())) + }, + { + "enum", + (o, n, _) => o.Enum = n.CreateListOfAny() + }, + + { + "type", + (o, n, _) => o.Type = n.GetScalarValue().ToJsonSchemaType() + }, + { + "allOf", + (o, n, t) => o.AllOf = n.CreateList(LoadSchema, t) + }, + { + "items", + (o, n, _) => o.Items = LoadSchema(n) + }, + { + "properties", + (o, n, t) => o.Properties = n.CreateMap(LoadSchema, t) + }, + { + "additionalProperties", (o, n, _) => + { + if (n is ValueNode) + { + o.AdditionalPropertiesAllowed = bool.Parse(n.GetScalarValue()); + } + else + { + o.AdditionalProperties = LoadSchema(n); + } + } + }, + { + "description", + (o, n, _) => o.Description = n.GetScalarValue() + }, + { + "format", + (o, n, _) => o.Format = n.GetScalarValue() + }, + { + "default", + (o, n, _) => o.Default = n.CreateAny() + }, + { + "discriminator", (o, n, _) => + { + o.Discriminator = new() + { + PropertyName = n.GetScalarValue() + }; + } + }, + { + "readOnly", + (o, n, _) => o.ReadOnly = bool.Parse(n.GetScalarValue()) + }, + { + "xml", + (o, n, _) => o.Xml = LoadXml(n) + }, + { + "externalDocs", + (o, n, _) => o.ExternalDocs = LoadExternalDocs(n) + }, + { + "example", + (o, n, _) => o.Example = n.CreateAny() + }, + }; + + private static readonly PatternFieldMap _openApiSchemaPatternFields = new PatternFieldMap + { + {s => s.StartsWith("x-"), (o, p, n, _) => o.AddExtension(p, LoadExtension(p, n))} + }; + + public static OpenApiSchema LoadSchema(ParseNode node, OpenApiDocument hostDocument = null) + { + var mapNode = node.CheckMapNode("schema"); + + var pointer = mapNode.GetReferencePointer(); + if (pointer != null) + { + var reference = GetReferenceIdAndExternalResource(pointer); + return new OpenApiSchemaReference(reference.Item1, hostDocument, reference.Item2); + } + + var schema = new OpenApiSchema(); + + foreach (var propertyNode in mapNode) + { + propertyNode.ParseField(schema, _openApiSchemaFixedFields, _openApiSchemaPatternFields); + } + + return schema; + } + } +} diff --git a/src/Microsoft.OpenApi.Readers/V2/OpenApiSecurityRequirementDeserializer.cs b/src/Microsoft.OpenApi/Reader/V2/OpenApiSecurityRequirementDeserializer.cs similarity index 87% rename from src/Microsoft.OpenApi.Readers/V2/OpenApiSecurityRequirementDeserializer.cs rename to src/Microsoft.OpenApi/Reader/V2/OpenApiSecurityRequirementDeserializer.cs index b4e578aa1..5e430206c 100644 --- a/src/Microsoft.OpenApi.Readers/V2/OpenApiSecurityRequirementDeserializer.cs +++ b/src/Microsoft.OpenApi/Reader/V2/OpenApiSecurityRequirementDeserializer.cs @@ -2,9 +2,9 @@ // Licensed under the MIT license. using Microsoft.OpenApi.Models; -using Microsoft.OpenApi.Readers.ParseNodes; +using Microsoft.OpenApi.Reader.ParseNodes; -namespace Microsoft.OpenApi.Readers.V2 +namespace Microsoft.OpenApi.Reader.V2 { /// /// Class containing logic to deserialize Open API V2 document into @@ -12,7 +12,7 @@ namespace Microsoft.OpenApi.Readers.V2 /// internal static partial class OpenApiV2Deserializer { - public static OpenApiSecurityRequirement LoadSecurityRequirement(ParseNode node) + public static OpenApiSecurityRequirement LoadSecurityRequirement(ParseNode node, OpenApiDocument hostDocument = null) { var mapNode = node.CheckMapNode("security"); @@ -24,7 +24,7 @@ public static OpenApiSecurityRequirement LoadSecurityRequirement(ParseNode node) mapNode.Context, property.Name); - var scopes = property.Value.CreateSimpleList(n2 => n2.GetScalarValue()); + var scopes = property.Value.CreateSimpleList((n2, p) => n2.GetScalarValue()); if (scheme != null) { diff --git a/src/Microsoft.OpenApi.Readers/V2/OpenApiSecuritySchemeDeserializer.cs b/src/Microsoft.OpenApi/Reader/V2/OpenApiSecuritySchemeDeserializer.cs similarity index 79% rename from src/Microsoft.OpenApi.Readers/V2/OpenApiSecuritySchemeDeserializer.cs rename to src/Microsoft.OpenApi/Reader/V2/OpenApiSecuritySchemeDeserializer.cs index 87086690f..4e142b479 100644 --- a/src/Microsoft.OpenApi.Readers/V2/OpenApiSecuritySchemeDeserializer.cs +++ b/src/Microsoft.OpenApi/Reader/V2/OpenApiSecuritySchemeDeserializer.cs @@ -4,9 +4,9 @@ using System; using Microsoft.OpenApi.Extensions; using Microsoft.OpenApi.Models; -using Microsoft.OpenApi.Readers.ParseNodes; +using Microsoft.OpenApi.Reader.ParseNodes; -namespace Microsoft.OpenApi.Readers.V2 +namespace Microsoft.OpenApi.Reader.V2 { /// /// Class containing logic to deserialize Open API V2 document into @@ -23,7 +23,7 @@ internal static partial class OpenApiV2Deserializer { { "type", - (o, n) => + (o, n, _) => { var type = n.GetScalarValue(); switch (type) @@ -43,32 +43,32 @@ internal static partial class OpenApiV2Deserializer } } }, - {"description", (o, n) => o.Description = n.GetScalarValue()}, - {"name", (o, n) => o.Name = n.GetScalarValue()}, - {"in", (o, n) => o.In = n.GetScalarValue().GetEnumFromDisplayName()}, + {"description", (o, n, _) => o.Description = n.GetScalarValue()}, + {"name", (o, n, _) => o.Name = n.GetScalarValue()}, + {"in", (o, n, _) => o.In = n.GetScalarValue().GetEnumFromDisplayName()}, { - "flow", (_, n) => _flowValue = n.GetScalarValue() + "flow", (_, n, _) => _flowValue = n.GetScalarValue() }, { "authorizationUrl", - (_, n) => _flow.AuthorizationUrl = new(n.GetScalarValue(), UriKind.RelativeOrAbsolute) + (_, n, _) => _flow.AuthorizationUrl = new(n.GetScalarValue(), UriKind.RelativeOrAbsolute) }, { "tokenUrl", - (_, n) => _flow.TokenUrl = new(n.GetScalarValue(), UriKind.RelativeOrAbsolute) + (_, n, _) => _flow.TokenUrl = new(n.GetScalarValue(), UriKind.RelativeOrAbsolute) }, { - "scopes", (_, n) => _flow.Scopes = n.CreateSimpleMap(LoadString) + "scopes", (_, n, _) => _flow.Scopes = n.CreateSimpleMap(LoadString) } }; private static readonly PatternFieldMap _securitySchemePatternFields = new() { - {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, LoadExtension(p, n))} + {s => s.StartsWith("x-"), (o, p, n, _) => o.AddExtension(p, LoadExtension(p, n))} }; - public static OpenApiSecurityScheme LoadSecurityScheme(ParseNode node) + public static OpenApiSecurityScheme LoadSecurityScheme(ParseNode node, OpenApiDocument hostDocument = null) { // Reset the local variables every time this method is called. // TODO: Change _flow to a tempStorage variable to make the deserializer thread-safe. diff --git a/src/Microsoft.OpenApi.Readers/V2/OpenApiTagDeserializer.cs b/src/Microsoft.OpenApi/Reader/V2/OpenApiTagDeserializer.cs similarity index 71% rename from src/Microsoft.OpenApi.Readers/V2/OpenApiTagDeserializer.cs rename to src/Microsoft.OpenApi/Reader/V2/OpenApiTagDeserializer.cs index 388b4fdb5..47c3c6a40 100644 --- a/src/Microsoft.OpenApi.Readers/V2/OpenApiTagDeserializer.cs +++ b/src/Microsoft.OpenApi/Reader/V2/OpenApiTagDeserializer.cs @@ -3,9 +3,9 @@ using Microsoft.OpenApi.Extensions; using Microsoft.OpenApi.Models; -using Microsoft.OpenApi.Readers.ParseNodes; +using Microsoft.OpenApi.Reader.ParseNodes; -namespace Microsoft.OpenApi.Readers.V2 +namespace Microsoft.OpenApi.Reader.V2 { /// /// Class containing logic to deserialize Open API V2 document into @@ -17,24 +17,24 @@ internal static partial class OpenApiV2Deserializer { { OpenApiConstants.Name, - (o, n) => o.Name = n.GetScalarValue() + (o, n, _) => o.Name = n.GetScalarValue() }, { OpenApiConstants.Description, - (o, n) => o.Description = n.GetScalarValue() + (o, n, _) => o.Description = n.GetScalarValue() }, { OpenApiConstants.ExternalDocs, - (o, n) => o.ExternalDocs = LoadExternalDocs(n) + (o, n, t) => o.ExternalDocs = LoadExternalDocs(n, t) } }; private static readonly PatternFieldMap _tagPatternFields = new() { - {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, LoadExtension(p, n))} + {s => s.StartsWith("x-"), (o, p, n, _) => o.AddExtension(p, LoadExtension(p, n))} }; - public static OpenApiTag LoadTag(ParseNode n) + public static OpenApiTag LoadTag(ParseNode n, OpenApiDocument hostDocument = null) { var mapNode = n.CheckMapNode("tag"); diff --git a/src/Microsoft.OpenApi/Reader/V2/OpenApiV2Deserializer.cs b/src/Microsoft.OpenApi/Reader/V2/OpenApiV2Deserializer.cs new file mode 100644 index 000000000..0bafab857 --- /dev/null +++ b/src/Microsoft.OpenApi/Reader/V2/OpenApiV2Deserializer.cs @@ -0,0 +1,108 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System.Collections.Generic; +using System.Linq; +using System.Text.Json.Nodes; +using Microsoft.OpenApi.Any; +using Microsoft.OpenApi.Exceptions; +using Microsoft.OpenApi.Interfaces; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Reader.ParseNodes; + +namespace Microsoft.OpenApi.Reader.V2 +{ + /// + /// Class containing logic to deserialize Open API V2 document into + /// runtime Open API object model. + /// + internal static partial class OpenApiV2Deserializer + { + private static void ParseMap( + MapNode mapNode, + T domainObject, + FixedFieldMap fixedFieldMap, + PatternFieldMap patternFieldMap, + List requiredFields = null, + OpenApiDocument doc = null) + { + if (mapNode == null) + { + return; + } + + var allFields = fixedFieldMap.Keys.Union(mapNode.Select(static x => x.Name)); + foreach (var propertyNode in allFields) + { + mapNode[propertyNode]?.ParseField(domainObject, fixedFieldMap, patternFieldMap, doc); + requiredFields?.Remove(propertyNode); + } + } + + private static void ProcessAnyFields( + MapNode mapNode, + T domainObject, + AnyFieldMap anyFieldMap) + { + foreach (var anyFieldName in anyFieldMap.Keys.ToList()) + { + try + { + mapNode.Context.StartObject(anyFieldName); + var anyFieldValue = anyFieldMap[anyFieldName].PropertyGetter(domainObject); + + if (anyFieldValue == null) + { + anyFieldMap[anyFieldName].PropertySetter(domainObject, null); + } + else + { + anyFieldMap[anyFieldName].PropertySetter(domainObject, anyFieldValue); + } + } + catch (OpenApiException exception) + { + exception.Pointer = mapNode.Context.GetLocation(); + mapNode.Context.Diagnostic.Errors.Add(new(exception)); + } + finally + { + mapNode.Context.EndObject(); + } + } + } + + public static JsonNode LoadAny(ParseNode node, OpenApiDocument hostDocument = null) + { + return node.CreateAny(); + } + + private static IOpenApiExtension LoadExtension(string name, ParseNode node) + { + if (node.Context.ExtensionParsers.TryGetValue(name, out var parser)) + { + return parser(node.CreateAny(), OpenApiSpecVersion.OpenApi2_0); + } + else + { + return new OpenApiAny(node.CreateAny()); + } + } + + private static string LoadString(ParseNode node) + { + return node.GetScalarValue(); + } + + private static (string, string) GetReferenceIdAndExternalResource(string pointer) + { + var refSegments = pointer.Split('/'); + var refId = refSegments.Last(); + var isExternalResource = !refSegments.First().StartsWith("#"); + + string externalResource = isExternalResource ? $"{refSegments.First()}/{refSegments[1].TrimEnd('#')}" : null; + + return (refId, externalResource); + } + } +} diff --git a/src/Microsoft.OpenApi.Readers/V2/OpenApiV2VersionService.cs b/src/Microsoft.OpenApi/Reader/V2/OpenApiV2VersionService.cs similarity index 91% rename from src/Microsoft.OpenApi.Readers/V2/OpenApiV2VersionService.cs rename to src/Microsoft.OpenApi/Reader/V2/OpenApiV2VersionService.cs index 86a7e2393..c9e58b519 100644 --- a/src/Microsoft.OpenApi.Readers/V2/OpenApiV2VersionService.cs +++ b/src/Microsoft.OpenApi/Reader/V2/OpenApiV2VersionService.cs @@ -7,12 +7,11 @@ using Microsoft.OpenApi.Exceptions; using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Models; -using Microsoft.OpenApi.Readers.Exceptions; -using Microsoft.OpenApi.Readers.Interface; -using Microsoft.OpenApi.Readers.ParseNodes; -using Microsoft.OpenApi.Readers.Properties; +using Microsoft.OpenApi.Properties; +using Microsoft.OpenApi.Reader.ParseNodes; -namespace Microsoft.OpenApi.Readers.V2 + +namespace Microsoft.OpenApi.Reader.V2 { /// /// The version specific implementations for OpenAPI V2.0. @@ -30,9 +29,9 @@ public OpenApiV2VersionService(OpenApiDiagnostic diagnostic) Diagnostic = diagnostic; } - private Dictionary> _loaders = new() + private readonly Dictionary> _loaders = new() { - [typeof(IOpenApiAny)] = OpenApiV2Deserializer.LoadAny, + [typeof(OpenApiAny)] = OpenApiV2Deserializer.LoadAny, [typeof(OpenApiContact)] = OpenApiV2Deserializer.LoadContact, [typeof(OpenApiExternalDocs)] = OpenApiV2Deserializer.LoadExternalDocs, [typeof(OpenApiHeader)] = OpenApiV2Deserializer.LoadHeader, @@ -133,7 +132,7 @@ private static ReferenceType GetReferenceTypeV2FromName(string referenceType) /// /// Parse the string to a object. /// - public OpenApiReference ConvertToOpenApiReference(string reference, ReferenceType? type) + public OpenApiReference ConvertToOpenApiReference(string reference, ReferenceType? type, string summary = null, string description = null) { if (!string.IsNullOrWhiteSpace(reference)) { @@ -215,9 +214,15 @@ public OpenApiDocument LoadDocument(RootNode rootNode) return OpenApiV2Deserializer.LoadOpenApi(rootNode); } - public T LoadElement(ParseNode node) where T : IOpenApiElement + public T LoadElement(ParseNode node, OpenApiDocument doc) where T : IOpenApiElement + { + return (T)_loaders[typeof(T)](node, doc); + } + + /// + public string GetReferenceScalarValues(MapNode mapNode, string scalarValue) { - return (T)_loaders[typeof(T)](node); + throw new InvalidOperationException(); } } } diff --git a/src/Microsoft.OpenApi.Readers/V2/OpenApiXmlDeserializer.cs b/src/Microsoft.OpenApi/Reader/V2/OpenApiXmlDeserializer.cs similarity index 72% rename from src/Microsoft.OpenApi.Readers/V2/OpenApiXmlDeserializer.cs rename to src/Microsoft.OpenApi/Reader/V2/OpenApiXmlDeserializer.cs index d11a51d65..c630bd941 100644 --- a/src/Microsoft.OpenApi.Readers/V2/OpenApiXmlDeserializer.cs +++ b/src/Microsoft.OpenApi/Reader/V2/OpenApiXmlDeserializer.cs @@ -2,12 +2,12 @@ // Licensed under the MIT license. using System; +using Microsoft.OpenApi.Exceptions; using Microsoft.OpenApi.Extensions; using Microsoft.OpenApi.Models; -using Microsoft.OpenApi.Readers.Exceptions; -using Microsoft.OpenApi.Readers.ParseNodes; +using Microsoft.OpenApi.Reader.ParseNodes; -namespace Microsoft.OpenApi.Readers.V2 +namespace Microsoft.OpenApi.Reader.V2 { /// /// Class containing logic to deserialize Open API V3 document into @@ -19,10 +19,10 @@ internal static partial class OpenApiV2Deserializer { { "name", - (o, n) => o.Name = n.GetScalarValue() + (o, n, _) => o.Name = n.GetScalarValue() }, { - "namespace", (o, n) => + "namespace", (o, n, _) => { if (Uri.IsWellFormedUriString(n.GetScalarValue(), UriKind.Absolute)) { @@ -36,25 +36,25 @@ internal static partial class OpenApiV2Deserializer }, { "prefix", - (o, n) => o.Prefix = n.GetScalarValue() + (o, n, _) => o.Prefix = n.GetScalarValue() }, { "attribute", - (o, n) => o.Attribute = bool.Parse(n.GetScalarValue()) + (o, n, _) => o.Attribute = bool.Parse(n.GetScalarValue()) }, { "wrapped", - (o, n) => o.Wrapped = bool.Parse(n.GetScalarValue()) + (o, n, _) => o.Wrapped = bool.Parse(n.GetScalarValue()) }, }; private static readonly PatternFieldMap _xmlPatternFields = new() { - {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, LoadExtension(p,n))} + {s => s.StartsWith("x-"), (o, p, n, _) => o.AddExtension(p, LoadExtension(p,n))} }; - public static OpenApiXml LoadXml(ParseNode node) + public static OpenApiXml LoadXml(ParseNode node, OpenApiDocument hostDocument = null) { var mapNode = node.CheckMapNode("xml"); diff --git a/src/Microsoft.OpenApi.Readers/V2/TempStorageKeys.cs b/src/Microsoft.OpenApi/Reader/V2/TempStorageKeys.cs similarity index 95% rename from src/Microsoft.OpenApi.Readers/V2/TempStorageKeys.cs rename to src/Microsoft.OpenApi/Reader/V2/TempStorageKeys.cs index 176af8a1e..1d99b1336 100644 --- a/src/Microsoft.OpenApi.Readers/V2/TempStorageKeys.cs +++ b/src/Microsoft.OpenApi/Reader/V2/TempStorageKeys.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. -namespace Microsoft.OpenApi.Readers.V2 +namespace Microsoft.OpenApi.Reader.V2 { /// /// Strings to be used as keys for the temporary storage. diff --git a/src/Microsoft.OpenApi.Readers/V3/OpenApiCallbackDeserializer.cs b/src/Microsoft.OpenApi/Reader/V3/OpenApiCallbackDeserializer.cs similarity index 62% rename from src/Microsoft.OpenApi.Readers/V3/OpenApiCallbackDeserializer.cs rename to src/Microsoft.OpenApi/Reader/V3/OpenApiCallbackDeserializer.cs index fc41e7daa..faf50ebb1 100644 --- a/src/Microsoft.OpenApi.Readers/V3/OpenApiCallbackDeserializer.cs +++ b/src/Microsoft.OpenApi/Reader/V3/OpenApiCallbackDeserializer.cs @@ -1,12 +1,14 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. +using System.Linq; using Microsoft.OpenApi.Expressions; using Microsoft.OpenApi.Extensions; using Microsoft.OpenApi.Models; -using Microsoft.OpenApi.Readers.ParseNodes; +using Microsoft.OpenApi.Models.References; +using Microsoft.OpenApi.Reader.ParseNodes; -namespace Microsoft.OpenApi.Readers.V3 +namespace Microsoft.OpenApi.Reader.V3 { /// /// Class containing logic to deserialize Open API V3 document into @@ -19,23 +21,25 @@ internal static partial class OpenApiV3Deserializer private static readonly PatternFieldMap _callbackPatternFields = new() { - {s => !s.StartsWith("x-"), (o, p, n) => o.AddPathItem(RuntimeExpression.Build(p), LoadPathItem(n))}, - {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, LoadExtension(p,n))}, + {s => !s.StartsWith("x-"), (o, p, n, t) => o.AddPathItem(RuntimeExpression.Build(p), LoadPathItem(n, t))}, + {s => s.StartsWith("x-"), (o, p, n, _) => o.AddExtension(p, LoadExtension(p,n))}, }; - public static OpenApiCallback LoadCallback(ParseNode node) + public static OpenApiCallback LoadCallback(ParseNode node, OpenApiDocument hostDocument = null) { var mapNode = node.CheckMapNode("callback"); var pointer = mapNode.GetReferencePointer(); + if (pointer != null) { - return mapNode.GetReferencedObject(ReferenceType.Callback, pointer); + var reference = GetReferenceIdAndExternalResource(pointer); + return new OpenApiCallbackReference(reference.Item1, hostDocument, reference.Item2); } var domainObject = new OpenApiCallback(); - ParseMap(mapNode, domainObject, _callbackFixedFields, _callbackPatternFields); + ParseMap(mapNode, domainObject, _callbackFixedFields, _callbackPatternFields, hostDocument); return domainObject; } diff --git a/src/Microsoft.OpenApi/Reader/V3/OpenApiComponentsDeserializer.cs b/src/Microsoft.OpenApi/Reader/V3/OpenApiComponentsDeserializer.cs new file mode 100644 index 000000000..cc51187d2 --- /dev/null +++ b/src/Microsoft.OpenApi/Reader/V3/OpenApiComponentsDeserializer.cs @@ -0,0 +1,44 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using Microsoft.OpenApi.Extensions; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Reader.ParseNodes; + +namespace Microsoft.OpenApi.Reader.V3 +{ + /// + /// Class containing logic to deserialize Open API V3 document into + /// runtime Open API object model. + /// + internal static partial class OpenApiV3Deserializer + { + private static readonly FixedFieldMap _componentsFixedFields = new() + { + {"schemas", (o, n, t) => o.Schemas = n.CreateMap(LoadSchema, t)}, + {"responses", (o, n, t) => o.Responses = n.CreateMap(LoadResponse, t)}, + {"parameters", (o, n, t) => o.Parameters = n.CreateMap(LoadParameter, t)}, + {"examples", (o, n, t) => o.Examples = n.CreateMap(LoadExample, t)}, + {"requestBodies", (o, n, t) => o.RequestBodies = n.CreateMap(LoadRequestBody, t)}, + {"headers", (o, n, t) => o.Headers = n.CreateMap(LoadHeader, t)}, + {"securitySchemes", (o, n, t) => o.SecuritySchemes = n.CreateMap(LoadSecurityScheme, t)}, + {"links", (o, n, t) => o.Links = n.CreateMap(LoadLink, t)}, + {"callbacks", (o, n, t) => o.Callbacks = n.CreateMap(LoadCallback, t)} + }; + + private static readonly PatternFieldMap _componentsPatternFields = + new() + { + {s => s.StartsWith("x-"), (o, p, n, _) => o.AddExtension(p, LoadExtension(p, n))} + }; + + public static OpenApiComponents LoadComponents(ParseNode node, OpenApiDocument hostDocument = null) + { + var mapNode = node.CheckMapNode("components"); + var components = new OpenApiComponents(); + + ParseMap(mapNode, components, _componentsFixedFields, _componentsPatternFields, hostDocument); + return components; + } + } +} diff --git a/src/Microsoft.OpenApi.Readers/V3/OpenApiContactDeserializer.cs b/src/Microsoft.OpenApi/Reader/V3/OpenApiContactDeserializer.cs similarity index 53% rename from src/Microsoft.OpenApi.Readers/V3/OpenApiContactDeserializer.cs rename to src/Microsoft.OpenApi/Reader/V3/OpenApiContactDeserializer.cs index cd38440fa..e4d98de64 100644 --- a/src/Microsoft.OpenApi.Readers/V3/OpenApiContactDeserializer.cs +++ b/src/Microsoft.OpenApi/Reader/V3/OpenApiContactDeserializer.cs @@ -4,9 +4,9 @@ using System; using Microsoft.OpenApi.Extensions; using Microsoft.OpenApi.Models; -using Microsoft.OpenApi.Readers.ParseNodes; +using Microsoft.OpenApi.Reader.ParseNodes; -namespace Microsoft.OpenApi.Readers.V3 +namespace Microsoft.OpenApi.Reader.V3 { /// /// Class containing logic to deserialize Open API V3 document into @@ -14,33 +14,33 @@ namespace Microsoft.OpenApi.Readers.V3 /// internal static partial class OpenApiV3Deserializer { - private static FixedFieldMap _contactFixedFields = new() + private static readonly FixedFieldMap _contactFixedFields = new() { { "name", - (o, n) => o.Name = n.GetScalarValue() + (o, n, _) => o.Name = n.GetScalarValue() }, { "email", - (o, n) => o.Email = n.GetScalarValue() + (o, n, _) => o.Email = n.GetScalarValue() }, { "url", - (o, n) => o.Url = new(n.GetScalarValue(), UriKind.RelativeOrAbsolute) + (o, n, _) => o.Url = new(n.GetScalarValue(), UriKind.RelativeOrAbsolute) }, }; - private static PatternFieldMap _contactPatternFields = new() + private static readonly PatternFieldMap _contactPatternFields = new() { - {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, LoadExtension(p,n))} + {s => s.StartsWith("x-"), (o, p, n, _) => o.AddExtension(p, LoadExtension(p,n))} }; - public static OpenApiContact LoadContact(ParseNode node) + public static OpenApiContact LoadContact(ParseNode node, OpenApiDocument hostDocument = null) { var mapNode = node as MapNode; var contact = new OpenApiContact(); - ParseMap(mapNode, contact, _contactFixedFields, _contactPatternFields); + ParseMap(mapNode, contact, _contactFixedFields, _contactPatternFields, hostDocument); return contact; } diff --git a/src/Microsoft.OpenApi.Readers/V3/OpenApiDiscriminatorDeserializer.cs b/src/Microsoft.OpenApi/Reader/V3/OpenApiDiscriminatorDeserializer.cs similarity index 80% rename from src/Microsoft.OpenApi.Readers/V3/OpenApiDiscriminatorDeserializer.cs rename to src/Microsoft.OpenApi/Reader/V3/OpenApiDiscriminatorDeserializer.cs index 0c3df1536..c10532c2c 100644 --- a/src/Microsoft.OpenApi.Readers/V3/OpenApiDiscriminatorDeserializer.cs +++ b/src/Microsoft.OpenApi/Reader/V3/OpenApiDiscriminatorDeserializer.cs @@ -2,9 +2,9 @@ // Licensed under the MIT license. using Microsoft.OpenApi.Models; -using Microsoft.OpenApi.Readers.ParseNodes; +using Microsoft.OpenApi.Reader.ParseNodes; -namespace Microsoft.OpenApi.Readers.V3 +namespace Microsoft.OpenApi.Reader.V3 { /// /// Class containing logic to deserialize Open API V3 document into @@ -17,17 +17,17 @@ internal static partial class OpenApiV3Deserializer { { "propertyName", - (o, n) => o.PropertyName = n.GetScalarValue() + (o, n, _) => o.PropertyName = n.GetScalarValue() }, { "mapping", - (o, n) => o.Mapping = n.CreateSimpleMap(LoadString) + (o, n, _) => o.Mapping = n.CreateSimpleMap(LoadString) } }; private static readonly PatternFieldMap _discriminatorPatternFields = new(); - public static OpenApiDiscriminator LoadDiscriminator(ParseNode node) + public static OpenApiDiscriminator LoadDiscriminator(ParseNode node, OpenApiDocument hostDocument = null) { var mapNode = node.CheckMapNode("discriminator"); diff --git a/src/Microsoft.OpenApi/Reader/V3/OpenApiDocumentDeserializer.cs b/src/Microsoft.OpenApi/Reader/V3/OpenApiDocumentDeserializer.cs new file mode 100644 index 000000000..3fcdb9af7 --- /dev/null +++ b/src/Microsoft.OpenApi/Reader/V3/OpenApiDocumentDeserializer.cs @@ -0,0 +1,62 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using Microsoft.OpenApi.Extensions; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Reader.ParseNodes; +using Microsoft.OpenApi.Services; + +namespace Microsoft.OpenApi.Reader.V3 +{ + /// + /// Class containing logic to deserialize Open API V3 document into + /// runtime Open API object model. + /// + internal static partial class OpenApiV3Deserializer + { + + private static readonly FixedFieldMap _openApiFixedFields = new() + { + { + "openapi", (_, _, _) => + { + } /* Version is valid field but we already parsed it */ + }, + {"info", (o, n, _) => o.Info = LoadInfo(n, o)}, + {"servers", (o, n, _) => o.Servers = n.CreateList(LoadServer, o)}, + {"paths", (o, n, _) => o.Paths = LoadPaths(n, o)}, + {"components", (o, n, _) => o.Components = LoadComponents(n, o)}, + {"tags", (o, n, _) => {o.Tags = n.CreateList(LoadTag, o); + foreach (var tag in o.Tags) + { + tag.Reference = new() + { + Id = tag.Name, + Type = ReferenceType.Tag + }; + } + } }, + {"externalDocs", (o, n, _) => o.ExternalDocs = LoadExternalDocs(n, o)}, + {"security", (o, n, _) => o.SecurityRequirements = n.CreateList(LoadSecurityRequirement, o)} + }; + + private static readonly PatternFieldMap _openApiPatternFields = new PatternFieldMap + { + // We have no semantics to verify X- nodes, therefore treat them as just values. + {s => s.StartsWith("x-"), (o, p, n, _) => o.AddExtension(p, LoadExtension(p, n))} + }; + + public static OpenApiDocument LoadOpenApi(RootNode rootNode) + { + var openApiDoc = new OpenApiDocument(); + var openApiNode = rootNode.GetMap(); + + ParseMap(openApiNode, openApiDoc, _openApiFixedFields, _openApiPatternFields, openApiDoc); + + // Register components + openApiDoc.Workspace.RegisterComponents(openApiDoc); + + return openApiDoc; + } + } +} diff --git a/src/Microsoft.OpenApi.Readers/V3/OpenApiEncodingDeserializer.cs b/src/Microsoft.OpenApi/Reader/V3/OpenApiEncodingDeserializer.cs similarity index 68% rename from src/Microsoft.OpenApi.Readers/V3/OpenApiEncodingDeserializer.cs rename to src/Microsoft.OpenApi/Reader/V3/OpenApiEncodingDeserializer.cs index c627ea8f5..67cb19ecb 100644 --- a/src/Microsoft.OpenApi.Readers/V3/OpenApiEncodingDeserializer.cs +++ b/src/Microsoft.OpenApi/Reader/V3/OpenApiEncodingDeserializer.cs @@ -3,9 +3,9 @@ using Microsoft.OpenApi.Extensions; using Microsoft.OpenApi.Models; -using Microsoft.OpenApi.Readers.ParseNodes; +using Microsoft.OpenApi.Reader.ParseNodes; -namespace Microsoft.OpenApi.Readers.V3 +namespace Microsoft.OpenApi.Reader.V3 { /// /// Class containing logic to deserialize Open API V3 document into @@ -17,33 +17,33 @@ internal static partial class OpenApiV3Deserializer { { "contentType", - (o, n) => o.ContentType = n.GetScalarValue() + (o, n, _) => o.ContentType = n.GetScalarValue() }, { "headers", - (o, n) => o.Headers = n.CreateMap(LoadHeader) + (o, n, t) => o.Headers = n.CreateMap(LoadHeader, t) }, { "style", - (o, n) => o.Style = n.GetScalarValue().GetEnumFromDisplayName() + (o, n, _) => o.Style = n.GetScalarValue().GetEnumFromDisplayName() }, { "explode", - (o, n) => o.Explode = bool.Parse(n.GetScalarValue()) + (o, n, _) => o.Explode = bool.Parse(n.GetScalarValue()) }, { "allowedReserved", - (o, n) => o.AllowReserved = bool.Parse(n.GetScalarValue()) + (o, n, _) => o.AllowReserved = bool.Parse(n.GetScalarValue()) }, }; private static readonly PatternFieldMap _encodingPatternFields = new() { - {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, LoadExtension(p,n))} + {s => s.StartsWith("x-"), (o, p, n, _) => o.AddExtension(p, LoadExtension(p,n))} }; - public static OpenApiEncoding LoadEncoding(ParseNode node) + public static OpenApiEncoding LoadEncoding(ParseNode node, OpenApiDocument hostDocument = null) { var mapNode = node.CheckMapNode("encoding"); diff --git a/src/Microsoft.OpenApi.Readers/V3/OpenApiExampleDeserializer.cs b/src/Microsoft.OpenApi/Reader/V3/OpenApiExampleDeserializer.cs similarity index 63% rename from src/Microsoft.OpenApi.Readers/V3/OpenApiExampleDeserializer.cs rename to src/Microsoft.OpenApi/Reader/V3/OpenApiExampleDeserializer.cs index 0399ad84d..a73ee02b1 100644 --- a/src/Microsoft.OpenApi.Readers/V3/OpenApiExampleDeserializer.cs +++ b/src/Microsoft.OpenApi/Reader/V3/OpenApiExampleDeserializer.cs @@ -1,11 +1,13 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. +using System.Linq; using Microsoft.OpenApi.Extensions; using Microsoft.OpenApi.Models; -using Microsoft.OpenApi.Readers.ParseNodes; +using Microsoft.OpenApi.Models.References; +using Microsoft.OpenApi.Reader.ParseNodes; -namespace Microsoft.OpenApi.Readers.V3 +namespace Microsoft.OpenApi.Reader.V3 { /// /// Class containing logic to deserialize Open API V3 document into @@ -17,36 +19,37 @@ internal static partial class OpenApiV3Deserializer { { "summary", - (o, n) => o.Summary = n.GetScalarValue() + (o, n, _) => o.Summary = n.GetScalarValue() }, { "description", - (o, n) => o.Description = n.GetScalarValue() + (o, n, _) => o.Description = n.GetScalarValue() }, { "value", - (o, n) => o.Value = n.CreateAny() + (o, n, _) => o.Value = n.CreateAny() }, { "externalValue", - (o, n) => o.ExternalValue = n.GetScalarValue() + (o, n, _) => o.ExternalValue = n.GetScalarValue() }, }; private static readonly PatternFieldMap _examplePatternFields = new() { - {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, LoadExtension(p,n))} + {s => s.StartsWith("x-"), (o, p, n, _) => o.AddExtension(p, LoadExtension(p,n))} }; - public static OpenApiExample LoadExample(ParseNode node) + public static OpenApiExample LoadExample(ParseNode node, OpenApiDocument hostDocument = null) { var mapNode = node.CheckMapNode("example"); var pointer = mapNode.GetReferencePointer(); if (pointer != null) { - return mapNode.GetReferencedObject(ReferenceType.Example, pointer); + var reference = GetReferenceIdAndExternalResource(pointer); + return new OpenApiExampleReference(reference.Item1, hostDocument, reference.Item2); } var example = new OpenApiExample(); diff --git a/src/Microsoft.OpenApi.Readers/V3/OpenApiExternalDocsDeserializer.cs b/src/Microsoft.OpenApi/Reader/V3/OpenApiExternalDocsDeserializer.cs similarity index 66% rename from src/Microsoft.OpenApi.Readers/V3/OpenApiExternalDocsDeserializer.cs rename to src/Microsoft.OpenApi/Reader/V3/OpenApiExternalDocsDeserializer.cs index e516bcfbc..39712494c 100644 --- a/src/Microsoft.OpenApi.Readers/V3/OpenApiExternalDocsDeserializer.cs +++ b/src/Microsoft.OpenApi/Reader/V3/OpenApiExternalDocsDeserializer.cs @@ -4,9 +4,9 @@ using System; using Microsoft.OpenApi.Extensions; using Microsoft.OpenApi.Models; -using Microsoft.OpenApi.Readers.ParseNodes; +using Microsoft.OpenApi.Reader.ParseNodes; -namespace Microsoft.OpenApi.Readers.V3 +namespace Microsoft.OpenApi.Reader.V3 { /// /// Class containing logic to deserialize Open API V3 document into @@ -20,27 +20,27 @@ internal static partial class OpenApiV3Deserializer // $ref { "description", - (o, n) => o.Description = n.GetScalarValue() + (o, n, _) => o.Description = n.GetScalarValue() }, { "url", - (o, n) => o.Url = new(n.GetScalarValue(), UriKind.RelativeOrAbsolute) + (o, n, _) => o.Url = new(n.GetScalarValue(), UriKind.RelativeOrAbsolute) }, }; private static readonly PatternFieldMap _externalDocsPatternFields = new() { - {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, LoadExtension(p, n))} - }; + {s => s.StartsWith("x-"), (o, p, n, _) => o.AddExtension(p, LoadExtension(p, n))} + }; - public static OpenApiExternalDocs LoadExternalDocs(ParseNode node) + public static OpenApiExternalDocs LoadExternalDocs(ParseNode node, OpenApiDocument hostDocument = null) { var mapNode = node.CheckMapNode("externalDocs"); var externalDocs = new OpenApiExternalDocs(); - ParseMap(mapNode, externalDocs, _externalDocsFixedFields, _externalDocsPatternFields); + ParseMap(mapNode, externalDocs, _externalDocsFixedFields, _externalDocsPatternFields, hostDocument); return externalDocs; } diff --git a/src/Microsoft.OpenApi.Readers/V3/OpenApiHeaderDeserializer.cs b/src/Microsoft.OpenApi/Reader/V3/OpenApiHeaderDeserializer.cs similarity index 57% rename from src/Microsoft.OpenApi.Readers/V3/OpenApiHeaderDeserializer.cs rename to src/Microsoft.OpenApi/Reader/V3/OpenApiHeaderDeserializer.cs index cd74df4b4..bc09b9b10 100644 --- a/src/Microsoft.OpenApi.Readers/V3/OpenApiHeaderDeserializer.cs +++ b/src/Microsoft.OpenApi/Reader/V3/OpenApiHeaderDeserializer.cs @@ -1,11 +1,13 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. +using System.Linq; using Microsoft.OpenApi.Extensions; using Microsoft.OpenApi.Models; -using Microsoft.OpenApi.Readers.ParseNodes; +using Microsoft.OpenApi.Models.References; +using Microsoft.OpenApi.Reader.ParseNodes; -namespace Microsoft.OpenApi.Readers.V3 +namespace Microsoft.OpenApi.Reader.V3 { /// /// Class containing logic to deserialize Open API V3 document into @@ -17,59 +19,60 @@ internal static partial class OpenApiV3Deserializer { { "description", - (o, n) => o.Description = n.GetScalarValue() + (o, n, _) => o.Description = n.GetScalarValue() }, { "required", - (o, n) => o.Required = bool.Parse(n.GetScalarValue()) + (o, n, _) => o.Required = bool.Parse(n.GetScalarValue()) }, { "deprecated", - (o, n) => o.Deprecated = bool.Parse(n.GetScalarValue()) + (o, n, _) => o.Deprecated = bool.Parse(n.GetScalarValue()) }, { "allowEmptyValue", - (o, n) => o.AllowEmptyValue = bool.Parse(n.GetScalarValue()) + (o, n, _) => o.AllowEmptyValue = bool.Parse(n.GetScalarValue()) }, { "allowReserved", - (o, n) => o.AllowReserved = bool.Parse(n.GetScalarValue()) + (o, n, _) => o.AllowReserved = bool.Parse(n.GetScalarValue()) }, { "style", - (o, n) => o.Style = n.GetScalarValue().GetEnumFromDisplayName() + (o, n, _) => o.Style = n.GetScalarValue().GetEnumFromDisplayName() }, { "explode", - (o, n) => o.Explode = bool.Parse(n.GetScalarValue()) + (o, n, _) => o.Explode = bool.Parse(n.GetScalarValue()) }, { "schema", - (o, n) => o.Schema = LoadSchema(n) + (o, n, t) => o.Schema = LoadSchema(n, t) }, { "examples", - (o, n) => o.Examples = n.CreateMap(LoadExample) + (o, n, t) => o.Examples = n.CreateMap(LoadExample, t) }, { "example", - (o, n) => o.Example = n.CreateAny() + (o, n, _) => o.Example = n.CreateAny() }, }; private static readonly PatternFieldMap _headerPatternFields = new() { - {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, LoadExtension(p,n))} + {s => s.StartsWith("x-"), (o, p, n, _) => o.AddExtension(p, LoadExtension(p,n))} }; - public static OpenApiHeader LoadHeader(ParseNode node) + public static OpenApiHeader LoadHeader(ParseNode node, OpenApiDocument hostDocument = null) { var mapNode = node.CheckMapNode("header"); var pointer = mapNode.GetReferencePointer(); if (pointer != null) { - return mapNode.GetReferencedObject(ReferenceType.Header, pointer); + var reference = GetReferenceIdAndExternalResource(pointer); + return new OpenApiHeaderReference(reference.Item1, hostDocument, reference.Item2); } var header = new OpenApiHeader(); diff --git a/src/Microsoft.OpenApi.Readers/V3/OpenApiInfoDeserializer.cs b/src/Microsoft.OpenApi/Reader/V3/OpenApiInfoDeserializer.cs similarity index 53% rename from src/Microsoft.OpenApi.Readers/V3/OpenApiInfoDeserializer.cs rename to src/Microsoft.OpenApi/Reader/V3/OpenApiInfoDeserializer.cs index 042bb5a5e..dcbf5ba4b 100644 --- a/src/Microsoft.OpenApi.Readers/V3/OpenApiInfoDeserializer.cs +++ b/src/Microsoft.OpenApi/Reader/V3/OpenApiInfoDeserializer.cs @@ -2,12 +2,11 @@ // Licensed under the MIT license. using System; -using System.Collections.Generic; using Microsoft.OpenApi.Extensions; using Microsoft.OpenApi.Models; -using Microsoft.OpenApi.Readers.ParseNodes; +using Microsoft.OpenApi.Reader.ParseNodes; -namespace Microsoft.OpenApi.Readers.V3 +namespace Microsoft.OpenApi.Reader.V3 { /// /// Class containing logic to deserialize Open API V3 document into @@ -15,47 +14,44 @@ namespace Microsoft.OpenApi.Readers.V3 /// internal static partial class OpenApiV3Deserializer { - public static FixedFieldMap InfoFixedFields = new() + public static readonly FixedFieldMap InfoFixedFields = new() { { "title", - (o, n) => o.Title = n.GetScalarValue() + (o, n, _) => o.Title = n.GetScalarValue() }, { "version", - (o, n) => o.Version = n.GetScalarValue() + (o, n, _) => o.Version = n.GetScalarValue() }, { "description", - (o, n) => o.Description = n.GetScalarValue() + (o, n, _) => o.Description = n.GetScalarValue() }, { "termsOfService", - (o, n) => o.TermsOfService = new(n.GetScalarValue(), UriKind.RelativeOrAbsolute) + (o, n, _) => o.TermsOfService = new(n.GetScalarValue(), UriKind.RelativeOrAbsolute) }, { "contact", - (o, n) => o.Contact = LoadContact(n) + (o, n, t) => o.Contact = LoadContact(n, t) }, { "license", - (o, n) => o.License = LoadLicense(n) + (o, n, t) => o.License = LoadLicense(n, t) } }; - public static PatternFieldMap InfoPatternFields = new() + public static readonly PatternFieldMap InfoPatternFields = new() { - {s => s.StartsWith("x-"), (o, k, n) => o.AddExtension(k,LoadExtension(k, n))} + {s => s.StartsWith("x-"), (o, k, n, _) => o.AddExtension(k,LoadExtension(k, n))} }; - public static OpenApiInfo LoadInfo(ParseNode node) + public static OpenApiInfo LoadInfo(ParseNode node, OpenApiDocument hostDocument = null) { var mapNode = node.CheckMapNode("Info"); - var info = new OpenApiInfo(); - var required = new List { "title", "version" }; - - ParseMap(mapNode, info, InfoFixedFields, InfoPatternFields); + ParseMap(mapNode, info, InfoFixedFields, InfoPatternFields, hostDocument); return info; } diff --git a/src/Microsoft.OpenApi.Readers/V3/OpenApiLicenseDeserializer.cs b/src/Microsoft.OpenApi/Reader/V3/OpenApiLicenseDeserializer.cs similarity index 59% rename from src/Microsoft.OpenApi.Readers/V3/OpenApiLicenseDeserializer.cs rename to src/Microsoft.OpenApi/Reader/V3/OpenApiLicenseDeserializer.cs index bde763454..e9054a0dd 100644 --- a/src/Microsoft.OpenApi.Readers/V3/OpenApiLicenseDeserializer.cs +++ b/src/Microsoft.OpenApi/Reader/V3/OpenApiLicenseDeserializer.cs @@ -4,9 +4,9 @@ using System; using Microsoft.OpenApi.Extensions; using Microsoft.OpenApi.Models; -using Microsoft.OpenApi.Readers.ParseNodes; +using Microsoft.OpenApi.Reader.ParseNodes; -namespace Microsoft.OpenApi.Readers.V3 +namespace Microsoft.OpenApi.Reader.V3 { /// /// Class containing logic to deserialize Open API V3 document into @@ -14,30 +14,30 @@ namespace Microsoft.OpenApi.Readers.V3 /// internal static partial class OpenApiV3Deserializer { - private static FixedFieldMap _licenseFixedFields = new() + private static readonly FixedFieldMap _licenseFixedFields = new() { { "name", - (o, n) => o.Name = n.GetScalarValue() + (o, n, _) => o.Name = n.GetScalarValue() }, { "url", - (o, n) => o.Url = new(n.GetScalarValue(), UriKind.RelativeOrAbsolute) + (o, n, _) => o.Url = new(n.GetScalarValue(), UriKind.RelativeOrAbsolute) }, }; - private static PatternFieldMap _licensePatternFields = new() + private static readonly PatternFieldMap _licensePatternFields = new() { - {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, LoadExtension(p,n))} + {s => s.StartsWith("x-"), (o, p, n, _) => o.AddExtension(p, LoadExtension(p,n))} }; - internal static OpenApiLicense LoadLicense(ParseNode node) + internal static OpenApiLicense LoadLicense(ParseNode node, OpenApiDocument hostDocument = null) { var mapNode = node.CheckMapNode("License"); var license = new OpenApiLicense(); - ParseMap(mapNode, license, _licenseFixedFields, _licensePatternFields); + ParseMap(mapNode, license, _licenseFixedFields, _licensePatternFields, hostDocument); return license; } diff --git a/src/Microsoft.OpenApi.Readers/V3/OpenApiLinkDeserializer.cs b/src/Microsoft.OpenApi/Reader/V3/OpenApiLinkDeserializer.cs similarity index 56% rename from src/Microsoft.OpenApi.Readers/V3/OpenApiLinkDeserializer.cs rename to src/Microsoft.OpenApi/Reader/V3/OpenApiLinkDeserializer.cs index 462bb875e..a95b6ebf8 100644 --- a/src/Microsoft.OpenApi.Readers/V3/OpenApiLinkDeserializer.cs +++ b/src/Microsoft.OpenApi/Reader/V3/OpenApiLinkDeserializer.cs @@ -1,11 +1,13 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. +using System.Linq; using Microsoft.OpenApi.Extensions; using Microsoft.OpenApi.Models; -using Microsoft.OpenApi.Readers.ParseNodes; +using Microsoft.OpenApi.Models.References; +using Microsoft.OpenApi.Reader.ParseNodes; -namespace Microsoft.OpenApi.Readers.V3 +namespace Microsoft.OpenApi.Reader.V3 { /// /// Class containing logic to deserialize Open API V3 document into @@ -17,33 +19,33 @@ internal static partial class OpenApiV3Deserializer { { "operationRef", - (o, n) => o.OperationRef = n.GetScalarValue() + (o, n, _) => o.OperationRef = n.GetScalarValue() }, { "operationId", - (o, n) => o.OperationId = n.GetScalarValue() + (o, n, _) => o.OperationId = n.GetScalarValue() }, { "parameters", - (o, n) => o.Parameters = n.CreateSimpleMap(LoadRuntimeExpressionAnyWrapper) + (o, n, _) => o.Parameters = n.CreateSimpleMap(LoadRuntimeExpressionAnyWrapper) }, { "requestBody", - (o, n) => o.RequestBody = LoadRuntimeExpressionAnyWrapper(n) + (o, n, _) => o.RequestBody = LoadRuntimeExpressionAnyWrapper(n) }, { "description", - (o, n) => o.Description = n.GetScalarValue() + (o, n, _) => o.Description = n.GetScalarValue() }, - {"server", (o, n) => o.Server = LoadServer(n)} + {"server", (o, n, t) => o.Server = LoadServer(n, t)} }; private static readonly PatternFieldMap _linkPatternFields = new() { - {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, LoadExtension(p,n))}, + {s => s.StartsWith("x-"), (o, p, n, _) => o.AddExtension(p, LoadExtension(p,n))}, }; - public static OpenApiLink LoadLink(ParseNode node) + public static OpenApiLink LoadLink(ParseNode node, OpenApiDocument hostDocument = null) { var mapNode = node.CheckMapNode("link"); var link = new OpenApiLink(); @@ -51,10 +53,11 @@ public static OpenApiLink LoadLink(ParseNode node) var pointer = mapNode.GetReferencePointer(); if (pointer != null) { - return mapNode.GetReferencedObject(ReferenceType.Link, pointer); + var reference = GetReferenceIdAndExternalResource(pointer); + return new OpenApiLinkReference(reference.Item1, hostDocument, reference.Item2); } - ParseMap(mapNode, link, _linkFixedFields, _linkPatternFields); + ParseMap(mapNode, link, _linkFixedFields, _linkPatternFields, hostDocument); return link; } diff --git a/src/Microsoft.OpenApi.Readers/V3/OpenApiMediaTypeDeserializer.cs b/src/Microsoft.OpenApi/Reader/V3/OpenApiMediaTypeDeserializer.cs similarity index 80% rename from src/Microsoft.OpenApi.Readers/V3/OpenApiMediaTypeDeserializer.cs rename to src/Microsoft.OpenApi/Reader/V3/OpenApiMediaTypeDeserializer.cs index a2625f438..1c055293a 100644 --- a/src/Microsoft.OpenApi.Readers/V3/OpenApiMediaTypeDeserializer.cs +++ b/src/Microsoft.OpenApi/Reader/V3/OpenApiMediaTypeDeserializer.cs @@ -3,9 +3,9 @@ using Microsoft.OpenApi.Extensions; using Microsoft.OpenApi.Models; -using Microsoft.OpenApi.Readers.ParseNodes; +using Microsoft.OpenApi.Reader.ParseNodes; -namespace Microsoft.OpenApi.Readers.V3 +namespace Microsoft.OpenApi.Reader.V3 { /// /// Class containing logic to deserialize Open API V3 document into @@ -18,26 +18,26 @@ internal static partial class OpenApiV3Deserializer { { OpenApiConstants.Schema, - (o, n) => o.Schema = LoadSchema(n) + (o, n, t) => o.Schema = LoadSchema(n, t) }, { OpenApiConstants.Examples, - (o, n) => o.Examples = n.CreateMap(LoadExample) + (o, n, t) => o.Examples = n.CreateMap(LoadExample, t) }, { OpenApiConstants.Example, - (o, n) => o.Example = n.CreateAny() + (o, n, _) => o.Example = n.CreateAny() }, { OpenApiConstants.Encoding, - (o, n) => o.Encoding = n.CreateMap(LoadEncoding) + (o, n, t) => o.Encoding = n.CreateMap(LoadEncoding, t) }, }; private static readonly PatternFieldMap _mediaTypePatternFields = new() { - {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, LoadExtension(p,n))} + {s => s.StartsWith("x-"), (o, p, n, _) => o.AddExtension(p, LoadExtension(p,n))} }; private static readonly AnyFieldMap _mediaTypeAnyFields = new() @@ -64,13 +64,12 @@ internal static partial class OpenApiV3Deserializer } }; - public static OpenApiMediaType LoadMediaType(ParseNode node) + public static OpenApiMediaType LoadMediaType(ParseNode node, OpenApiDocument hostDocument = null) { var mapNode = node.CheckMapNode(OpenApiConstants.Content); var mediaType = new OpenApiMediaType(); - - ParseMap(mapNode, mediaType, _mediaTypeFixedFields, _mediaTypePatternFields); + ParseMap(mapNode, mediaType, _mediaTypeFixedFields, _mediaTypePatternFields, hostDocument); ProcessAnyFields(mapNode, mediaType, _mediaTypeAnyFields); ProcessAnyMapFields(mapNode, mediaType, _mediaTypeAnyMapOpenApiExampleFields); diff --git a/src/Microsoft.OpenApi.Readers/V3/OpenApiOAuthFlowDeserializer.cs b/src/Microsoft.OpenApi/Reader/V3/OpenApiOAuthFlowDeserializer.cs similarity index 68% rename from src/Microsoft.OpenApi.Readers/V3/OpenApiOAuthFlowDeserializer.cs rename to src/Microsoft.OpenApi/Reader/V3/OpenApiOAuthFlowDeserializer.cs index 77e19ccbc..8e8783efa 100644 --- a/src/Microsoft.OpenApi.Readers/V3/OpenApiOAuthFlowDeserializer.cs +++ b/src/Microsoft.OpenApi/Reader/V3/OpenApiOAuthFlowDeserializer.cs @@ -4,9 +4,9 @@ using System; using Microsoft.OpenApi.Extensions; using Microsoft.OpenApi.Models; -using Microsoft.OpenApi.Readers.ParseNodes; +using Microsoft.OpenApi.Reader.ParseNodes; -namespace Microsoft.OpenApi.Readers.V3 +namespace Microsoft.OpenApi.Reader.V3 { /// /// Class containing logic to deserialize Open API V3 document into @@ -19,26 +19,26 @@ internal static partial class OpenApiV3Deserializer { { "authorizationUrl", - (o, n) => o.AuthorizationUrl = new(n.GetScalarValue(), UriKind.RelativeOrAbsolute) + (o, n, _) => o.AuthorizationUrl = new(n.GetScalarValue(), UriKind.RelativeOrAbsolute) }, { "tokenUrl", - (o, n) => o.TokenUrl = new(n.GetScalarValue(), UriKind.RelativeOrAbsolute) + (o, n, _) => o.TokenUrl = new(n.GetScalarValue(), UriKind.RelativeOrAbsolute) }, { "refreshUrl", - (o, n) => o.RefreshUrl = new(n.GetScalarValue(), UriKind.RelativeOrAbsolute) + (o, n, _) => o.RefreshUrl = new(n.GetScalarValue(), UriKind.RelativeOrAbsolute) }, - {"scopes", (o, n) => o.Scopes = n.CreateSimpleMap(LoadString)} + {"scopes", (o, n, _) => o.Scopes = n.CreateSimpleMap(LoadString)} }; private static readonly PatternFieldMap _oAuthFlowPatternFields = new() { - {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, LoadExtension(p,n))} + {s => s.StartsWith("x-"), (o, p, n, _) => o.AddExtension(p, LoadExtension(p,n))} }; - public static OpenApiOAuthFlow LoadOAuthFlow(ParseNode node) + public static OpenApiOAuthFlow LoadOAuthFlow(ParseNode node, OpenApiDocument hostDocument = null) { var mapNode = node.CheckMapNode("OAuthFlow"); diff --git a/src/Microsoft.OpenApi.Readers/V3/OpenApiOAuthFlowsDeserializer.cs b/src/Microsoft.OpenApi/Reader/V3/OpenApiOAuthFlowsDeserializer.cs similarity index 65% rename from src/Microsoft.OpenApi.Readers/V3/OpenApiOAuthFlowsDeserializer.cs rename to src/Microsoft.OpenApi/Reader/V3/OpenApiOAuthFlowsDeserializer.cs index 5423323f8..2856be979 100644 --- a/src/Microsoft.OpenApi.Readers/V3/OpenApiOAuthFlowsDeserializer.cs +++ b/src/Microsoft.OpenApi/Reader/V3/OpenApiOAuthFlowsDeserializer.cs @@ -3,9 +3,9 @@ using Microsoft.OpenApi.Extensions; using Microsoft.OpenApi.Models; -using Microsoft.OpenApi.Readers.ParseNodes; +using Microsoft.OpenApi.Reader.ParseNodes; -namespace Microsoft.OpenApi.Readers.V3 +namespace Microsoft.OpenApi.Reader.V3 { /// /// Class containing logic to deserialize Open API V3 document into @@ -16,19 +16,19 @@ internal static partial class OpenApiV3Deserializer private static readonly FixedFieldMap _oAuthFlowsFixedFields = new() { - {"implicit", (o, n) => o.Implicit = LoadOAuthFlow(n)}, - {"password", (o, n) => o.Password = LoadOAuthFlow(n)}, - {"clientCredentials", (o, n) => o.ClientCredentials = LoadOAuthFlow(n)}, - {"authorizationCode", (o, n) => o.AuthorizationCode = LoadOAuthFlow(n)} + {"implicit", (o, n, t) => o.Implicit = LoadOAuthFlow(n, t)}, + {"password", (o, n, t) => o.Password = LoadOAuthFlow(n, t)}, + {"clientCredentials", (o, n, t) => o.ClientCredentials = LoadOAuthFlow(n, t)}, + {"authorizationCode", (o, n, t) => o.AuthorizationCode = LoadOAuthFlow(n, t)} }; private static readonly PatternFieldMap _oAuthFlowsPatternFields = new() { - {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, LoadExtension(p,n))} + {s => s.StartsWith("x-"), (o, p, n, _) => o.AddExtension(p, LoadExtension(p,n))} }; - public static OpenApiOAuthFlows LoadOAuthFlows(ParseNode node) + public static OpenApiOAuthFlows LoadOAuthFlows(ParseNode node, OpenApiDocument hostDocument = null) { var mapNode = node.CheckMapNode("OAuthFlows"); diff --git a/src/Microsoft.OpenApi.Readers/V3/OpenApiOperationDeserializer.cs b/src/Microsoft.OpenApi/Reader/V3/OpenApiOperationDeserializer.cs similarity index 57% rename from src/Microsoft.OpenApi.Readers/V3/OpenApiOperationDeserializer.cs rename to src/Microsoft.OpenApi/Reader/V3/OpenApiOperationDeserializer.cs index 471b3a207..33aadc141 100644 --- a/src/Microsoft.OpenApi.Readers/V3/OpenApiOperationDeserializer.cs +++ b/src/Microsoft.OpenApi/Reader/V3/OpenApiOperationDeserializer.cs @@ -3,9 +3,10 @@ using Microsoft.OpenApi.Extensions; using Microsoft.OpenApi.Models; -using Microsoft.OpenApi.Readers.ParseNodes; +using Microsoft.OpenApi.Models.References; +using Microsoft.OpenApi.Reader.ParseNodes; -namespace Microsoft.OpenApi.Readers.V3 +namespace Microsoft.OpenApi.Reader.V3 { /// /// Class containing logic to deserialize Open API V3 document into @@ -17,90 +18,80 @@ internal static partial class OpenApiV3Deserializer new() { { - "tags", (o, n) => o.Tags = n.CreateSimpleList( - valueNode => + "tags", (o, n, doc) => o.Tags = n.CreateSimpleList( + (valueNode, doc) => LoadTagByReference( valueNode.Context, - valueNode.GetScalarValue())) + valueNode.GetScalarValue(), doc)) }, { "summary", - (o, n) => o.Summary = n.GetScalarValue() + (o, n, _) => o.Summary = n.GetScalarValue() }, { "description", - (o, n) => o.Description = n.GetScalarValue() + (o, n, _) => o.Description = n.GetScalarValue() }, { "externalDocs", - (o, n) => o.ExternalDocs = LoadExternalDocs(n) + (o, n, _) => o.ExternalDocs = LoadExternalDocs(n) }, { "operationId", - (o, n) => o.OperationId = n.GetScalarValue() + (o, n, _) => o.OperationId = n.GetScalarValue() }, { "parameters", - (o, n) => o.Parameters = n.CreateList(LoadParameter) + (o, n, t) => o.Parameters = n.CreateList(LoadParameter, t) }, { "requestBody", - (o, n) => o.RequestBody = LoadRequestBody(n) + (o, n, t) => o.RequestBody = LoadRequestBody(n, t) }, { "responses", - (o, n) => o.Responses = LoadResponses(n) + (o, n, t) => o.Responses = LoadResponses(n, t) }, { "callbacks", - (o, n) => o.Callbacks = n.CreateMap(LoadCallback) + (o, n, t) => o.Callbacks = n.CreateMap(LoadCallback, t) }, { "deprecated", - (o, n) => o.Deprecated = bool.Parse(n.GetScalarValue()) + (o, n, _) => o.Deprecated = bool.Parse(n.GetScalarValue()) }, { "security", - (o, n) => o.Security = n.CreateList(LoadSecurityRequirement) + (o, n, t) => o.Security = n.CreateList(LoadSecurityRequirement, t) }, { "servers", - (o, n) => o.Servers = n.CreateList(LoadServer) + (o, n, t) => o.Servers = n.CreateList(LoadServer, t) }, }; private static readonly PatternFieldMap _operationPatternFields = new() { - {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, LoadExtension(p,n))}, + {s => s.StartsWith("x-"), (o, p, n, _) => o.AddExtension(p, LoadExtension(p,n))}, }; - internal static OpenApiOperation LoadOperation(ParseNode node) + internal static OpenApiOperation LoadOperation(ParseNode node, OpenApiDocument hostDocument = null) { var mapNode = node.CheckMapNode("Operation"); var operation = new OpenApiOperation(); - ParseMap(mapNode, operation, _operationFixedFields, _operationPatternFields); + ParseMap(mapNode, operation, _operationFixedFields, _operationPatternFields, hostDocument); return operation; } private static OpenApiTag LoadTagByReference( ParsingContext context, - string tagName) + string tagName, OpenApiDocument hostDocument) { - var tagObject = new OpenApiTag - { - UnresolvedReference = true, - Reference = new() - { - Type = ReferenceType.Tag, - Id = tagName - } - }; - - return tagObject; + return new OpenApiTagReference(tagName, hostDocument); } } } diff --git a/src/Microsoft.OpenApi.Readers/V3/OpenApiParameterDeserializer.cs b/src/Microsoft.OpenApi/Reader/V3/OpenApiParameterDeserializer.cs similarity index 65% rename from src/Microsoft.OpenApi.Readers/V3/OpenApiParameterDeserializer.cs rename to src/Microsoft.OpenApi/Reader/V3/OpenApiParameterDeserializer.cs index 954cc7109..0446c52b7 100644 --- a/src/Microsoft.OpenApi.Readers/V3/OpenApiParameterDeserializer.cs +++ b/src/Microsoft.OpenApi/Reader/V3/OpenApiParameterDeserializer.cs @@ -5,9 +5,10 @@ using System.Linq; using Microsoft.OpenApi.Extensions; using Microsoft.OpenApi.Models; -using Microsoft.OpenApi.Readers.ParseNodes; +using Microsoft.OpenApi.Models.References; +using Microsoft.OpenApi.Reader.ParseNodes; -namespace Microsoft.OpenApi.Readers.V3 +namespace Microsoft.OpenApi.Reader.V3 { /// /// Class containing logic to deserialize Open API V3 document into @@ -20,75 +21,68 @@ internal static partial class OpenApiV3Deserializer { { "name", - (o, n) => o.Name = n.GetScalarValue() + (o, n,_) => o.Name = n.GetScalarValue() }, { - "in", (o, n) => + "in", (o, n, _) => { var inString = n.GetScalarValue(); - if ( Enum.GetValues(typeof(ParameterLocation)).Cast() + o.In = Enum.GetValues(typeof(ParameterLocation)).Cast() .Select( e => e.GetDisplayName() ) - .Contains(inString) ) - { - o.In = n.GetScalarValue().GetEnumFromDisplayName(); - } - else - { - o.In = null; - } + .Contains(inString) ? n.GetScalarValue().GetEnumFromDisplayName() : null; } }, { "description", - (o, n) => o.Description = n.GetScalarValue() + (o, n, _) => o.Description = n.GetScalarValue() }, { "required", - (o, n) => o.Required = bool.Parse(n.GetScalarValue()) + (o, n, _) => o.Required = bool.Parse(n.GetScalarValue()) }, { "deprecated", - (o, n) => o.Deprecated = bool.Parse(n.GetScalarValue()) + (o, n, _) => o.Deprecated = bool.Parse(n.GetScalarValue()) }, { "allowEmptyValue", - (o, n) => o.AllowEmptyValue = bool.Parse(n.GetScalarValue()) + (o, n, _) => o.AllowEmptyValue = bool.Parse(n.GetScalarValue()) }, { "allowReserved", - (o, n) => o.AllowReserved = bool.Parse(n.GetScalarValue()) + (o, n, _) => o.AllowReserved = bool.Parse(n.GetScalarValue()) }, { "style", - (o, n) => o.Style = n.GetScalarValue().GetEnumFromDisplayName() + (o, n, _) => o.Style = n.GetScalarValue().GetEnumFromDisplayName() }, { "explode", - (o, n) => o.Explode = bool.Parse(n.GetScalarValue()) + (o, n, _) => o.Explode = bool.Parse(n.GetScalarValue()) }, { "schema", - (o, n) => o.Schema = LoadSchema(n) + (o, n, t) => o.Schema = LoadSchema(n, t) }, { "content", - (o, n) => o.Content = n.CreateMap(LoadMediaType) + (o, n, t) => o.Content = n.CreateMap(LoadMediaType, t) }, { "examples", - (o, n) => o.Examples = n.CreateMap(LoadExample) + (o, n, t) => o.Examples = n.CreateMap(LoadExample, t) }, { "example", - (o, n) => o.Example = n.CreateAny() + (o, n, _) => o.Example = n.CreateAny() }, }; private static readonly PatternFieldMap _parameterPatternFields = new() { - {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, LoadExtension(p,n))} + {s => s.StartsWith("x-"), (o, p, n, _) => o.AddExtension(p, LoadExtension(p,n))} }; private static readonly AnyFieldMap _parameterAnyFields = new() @@ -115,19 +109,20 @@ internal static partial class OpenApiV3Deserializer } }; - public static OpenApiParameter LoadParameter(ParseNode node) + public static OpenApiParameter LoadParameter(ParseNode node, OpenApiDocument hostDocument = null) { var mapNode = node.CheckMapNode("parameter"); var pointer = mapNode.GetReferencePointer(); if (pointer != null) { - return mapNode.GetReferencedObject(ReferenceType.Parameter, pointer); + var reference = GetReferenceIdAndExternalResource(pointer); + return new OpenApiParameterReference(reference.Item1, hostDocument, reference.Item2); } var parameter = new OpenApiParameter(); - ParseMap(mapNode, parameter, _parameterFixedFields, _parameterPatternFields); + ParseMap(mapNode, parameter, _parameterFixedFields, _parameterPatternFields, hostDocument); ProcessAnyFields(mapNode, parameter, _parameterAnyFields); ProcessAnyMapFields(mapNode, parameter, _parameterAnyMapOpenApiExampleFields); diff --git a/src/Microsoft.OpenApi/Reader/V3/OpenApiPathItemDeserializer.cs b/src/Microsoft.OpenApi/Reader/V3/OpenApiPathItemDeserializer.cs new file mode 100644 index 000000000..afcee89b5 --- /dev/null +++ b/src/Microsoft.OpenApi/Reader/V3/OpenApiPathItemDeserializer.cs @@ -0,0 +1,70 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System.Linq; +using Microsoft.OpenApi.Extensions; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Models.References; +using Microsoft.OpenApi.Reader.ParseNodes; + +namespace Microsoft.OpenApi.Reader.V3 +{ + /// + /// Class containing logic to deserialize Open API V3 document into + /// runtime Open API object model. + /// + internal static partial class OpenApiV3Deserializer + { + private static readonly FixedFieldMap _pathItemFixedFields = new() + { + { + "$ref", (o, n, _) => { + o.Reference = new() { ExternalResource = n.GetScalarValue() }; + o.UnresolvedReference =true; + } + }, + { + "summary", + (o, n, _) => o.Summary = n.GetScalarValue() + }, + { + "description", + (o, n, _) => o.Description = n.GetScalarValue() + }, + {"get", (o, n, t) => o.AddOperation(OperationType.Get, LoadOperation(n, t))}, + {"put", (o, n, t) => o.AddOperation(OperationType.Put, LoadOperation(n, t))}, + {"post", (o, n, t) => o.AddOperation(OperationType.Post, LoadOperation(n, t))}, + {"delete", (o, n, t) => o.AddOperation(OperationType.Delete, LoadOperation(n, t))}, + {"options", (o, n, t) => o.AddOperation(OperationType.Options, LoadOperation(n, t))}, + {"head", (o, n, t) => o.AddOperation(OperationType.Head, LoadOperation(n, t))}, + {"patch", (o, n, t) => o.AddOperation(OperationType.Patch, LoadOperation(n, t))}, + {"trace", (o, n, t) => o.AddOperation(OperationType.Trace, LoadOperation(n, t))}, + {"servers", (o, n, t) => o.Servers = n.CreateList(LoadServer, t)}, + {"parameters", (o, n, t) => o.Parameters = n.CreateList(LoadParameter, t)} + }; + + private static readonly PatternFieldMap _pathItemPatternFields = + new() + { + {s => s.StartsWith("x-"), (o, p, n, _) => o.AddExtension(p, LoadExtension(p,n))} + }; + + public static OpenApiPathItem LoadPathItem(ParseNode node, OpenApiDocument hostDocument = null) + { + var mapNode = node.CheckMapNode("PathItem"); + + var pointer = mapNode.GetReferencePointer(); + if (pointer != null) + { + var reference = GetReferenceIdAndExternalResource(pointer); + return new OpenApiPathItemReference(reference.Item1, hostDocument, reference.Item2); + } + + var pathItem = new OpenApiPathItem(); + + ParseMap(mapNode, pathItem, _pathItemFixedFields, _pathItemPatternFields, hostDocument); + + return pathItem; + } + } +} diff --git a/src/Microsoft.OpenApi.Readers/V3/OpenApiPathsDeserializer.cs b/src/Microsoft.OpenApi/Reader/V3/OpenApiPathsDeserializer.cs similarity index 52% rename from src/Microsoft.OpenApi.Readers/V3/OpenApiPathsDeserializer.cs rename to src/Microsoft.OpenApi/Reader/V3/OpenApiPathsDeserializer.cs index 8104cde7f..d4343973c 100644 --- a/src/Microsoft.OpenApi.Readers/V3/OpenApiPathsDeserializer.cs +++ b/src/Microsoft.OpenApi/Reader/V3/OpenApiPathsDeserializer.cs @@ -3,9 +3,9 @@ using Microsoft.OpenApi.Extensions; using Microsoft.OpenApi.Models; -using Microsoft.OpenApi.Readers.ParseNodes; +using Microsoft.OpenApi.Reader.ParseNodes; -namespace Microsoft.OpenApi.Readers.V3 +namespace Microsoft.OpenApi.Reader.V3 { /// /// Class containing logic to deserialize Open API V3 document into @@ -13,21 +13,21 @@ namespace Microsoft.OpenApi.Readers.V3 /// internal static partial class OpenApiV3Deserializer { - private static FixedFieldMap _pathsFixedFields = new(); + private static readonly FixedFieldMap _pathsFixedFields = new(); - private static PatternFieldMap _pathsPatternFields = new() + private static readonly PatternFieldMap _pathsPatternFields = new() { - {s => s.StartsWith("/"), (o, k, n) => o.Add(k, LoadPathItem(n))}, - {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, LoadExtension(p,n))} + {s => s.StartsWith("/"), (o, k, n, t) => o.Add(k, LoadPathItem(n, t))}, + {s => s.StartsWith("x-"), (o, p, n, _) => o.AddExtension(p, LoadExtension(p,n))} }; - public static OpenApiPaths LoadPaths(ParseNode node) + public static OpenApiPaths LoadPaths(ParseNode node, OpenApiDocument hostDocument = null) { var mapNode = node.CheckMapNode("Paths"); var domainObject = new OpenApiPaths(); - ParseMap(mapNode, domainObject, _pathsFixedFields, _pathsPatternFields); + ParseMap(mapNode, domainObject, _pathsFixedFields, _pathsPatternFields, hostDocument); return domainObject; } diff --git a/src/Microsoft.OpenApi.Readers/V3/OpenApiRequestBodyDeserializer.cs b/src/Microsoft.OpenApi/Reader/V3/OpenApiRequestBodyDeserializer.cs similarity index 67% rename from src/Microsoft.OpenApi.Readers/V3/OpenApiRequestBodyDeserializer.cs rename to src/Microsoft.OpenApi/Reader/V3/OpenApiRequestBodyDeserializer.cs index 751fd1ac5..435b576e1 100644 --- a/src/Microsoft.OpenApi.Readers/V3/OpenApiRequestBodyDeserializer.cs +++ b/src/Microsoft.OpenApi/Reader/V3/OpenApiRequestBodyDeserializer.cs @@ -1,11 +1,13 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. +using System.Linq; using Microsoft.OpenApi.Extensions; using Microsoft.OpenApi.Models; -using Microsoft.OpenApi.Readers.ParseNodes; +using Microsoft.OpenApi.Models.References; +using Microsoft.OpenApi.Reader.ParseNodes; -namespace Microsoft.OpenApi.Readers.V3 +namespace Microsoft.OpenApi.Reader.V3 { /// /// Class containing logic to deserialize Open API V3 document into @@ -18,32 +20,33 @@ internal static partial class OpenApiV3Deserializer { { "description", - (o, n) => o.Description = n.GetScalarValue() + (o, n, _) => o.Description = n.GetScalarValue() }, { "content", - (o, n) => o.Content = n.CreateMap(LoadMediaType) + (o, n, t) => o.Content = n.CreateMap(LoadMediaType, t) }, { "required", - (o, n) => o.Required = bool.Parse(n.GetScalarValue()) + (o, n, _) => o.Required = bool.Parse(n.GetScalarValue()) }, }; private static readonly PatternFieldMap _requestBodyPatternFields = new() { - {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, LoadExtension(p,n))} + {s => s.StartsWith("x-"), (o, p, n, _) => o.AddExtension(p, LoadExtension(p,n))} }; - public static OpenApiRequestBody LoadRequestBody(ParseNode node) + public static OpenApiRequestBody LoadRequestBody(ParseNode node, OpenApiDocument hostDocument= null) { var mapNode = node.CheckMapNode("requestBody"); var pointer = mapNode.GetReferencePointer(); if (pointer != null) { - return mapNode.GetReferencedObject(ReferenceType.RequestBody, pointer); + var reference = GetReferenceIdAndExternalResource(pointer); + return new OpenApiRequestBodyReference(reference.Item1, hostDocument, reference.Item2); } var requestBody = new OpenApiRequestBody(); diff --git a/src/Microsoft.OpenApi.Readers/V3/OpenApiResponseDeserializer.cs b/src/Microsoft.OpenApi/Reader/V3/OpenApiResponseDeserializer.cs similarity index 62% rename from src/Microsoft.OpenApi.Readers/V3/OpenApiResponseDeserializer.cs rename to src/Microsoft.OpenApi/Reader/V3/OpenApiResponseDeserializer.cs index 4fe97c7e1..e65a1aafe 100644 --- a/src/Microsoft.OpenApi.Readers/V3/OpenApiResponseDeserializer.cs +++ b/src/Microsoft.OpenApi/Reader/V3/OpenApiResponseDeserializer.cs @@ -1,12 +1,13 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. -using System.Collections.Generic; +using System.Linq; using Microsoft.OpenApi.Extensions; using Microsoft.OpenApi.Models; -using Microsoft.OpenApi.Readers.ParseNodes; +using Microsoft.OpenApi.Models.References; +using Microsoft.OpenApi.Reader.ParseNodes; -namespace Microsoft.OpenApi.Readers.V3 +namespace Microsoft.OpenApi.Reader.V3 { /// /// Class containing logic to deserialize Open API V3 document into @@ -18,41 +19,41 @@ internal static partial class OpenApiV3Deserializer { { "description", - (o, n) => o.Description = n.GetScalarValue() + (o, n, _) => o.Description = n.GetScalarValue() }, { "headers", - (o, n) => o.Headers = n.CreateMap(LoadHeader) + (o, n, t) => o.Headers = n.CreateMap(LoadHeader, t) }, { "content", - (o, n) => o.Content = n.CreateMap(LoadMediaType) + (o, n, t) => o.Content = n.CreateMap(LoadMediaType, t) }, { "links", - (o, n) => o.Links = n.CreateMap(LoadLink) + (o, n, t) => o.Links = n.CreateMap(LoadLink, t) } }; private static readonly PatternFieldMap _responsePatternFields = new() { - {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, LoadExtension(p,n))} + {s => s.StartsWith("x-"), (o, p, n, _) => o.AddExtension(p, LoadExtension(p,n))} }; - public static OpenApiResponse LoadResponse(ParseNode node) + public static OpenApiResponse LoadResponse(ParseNode node, OpenApiDocument hostDocument = null) { var mapNode = node.CheckMapNode("response"); var pointer = mapNode.GetReferencePointer(); if (pointer != null) { - return mapNode.GetReferencedObject(ReferenceType.Response, pointer); + var reference = GetReferenceIdAndExternalResource(pointer); + return new OpenApiResponseReference(reference.Item1, hostDocument, reference.Item2); } - var requiredFields = new List { "description" }; var response = new OpenApiResponse(); - ParseMap(mapNode, response, _responseFixedFields, _responsePatternFields); + ParseMap(mapNode, response, _responseFixedFields, _responsePatternFields, hostDocument); return response; } diff --git a/src/Microsoft.OpenApi.Readers/V3/OpenApiResponsesDeserializer.cs b/src/Microsoft.OpenApi/Reader/V3/OpenApiResponsesDeserializer.cs similarity index 57% rename from src/Microsoft.OpenApi.Readers/V3/OpenApiResponsesDeserializer.cs rename to src/Microsoft.OpenApi/Reader/V3/OpenApiResponsesDeserializer.cs index 437553160..817cdcbf6 100644 --- a/src/Microsoft.OpenApi.Readers/V3/OpenApiResponsesDeserializer.cs +++ b/src/Microsoft.OpenApi/Reader/V3/OpenApiResponsesDeserializer.cs @@ -3,9 +3,9 @@ using Microsoft.OpenApi.Extensions; using Microsoft.OpenApi.Models; -using Microsoft.OpenApi.Readers.ParseNodes; +using Microsoft.OpenApi.Reader.ParseNodes; -namespace Microsoft.OpenApi.Readers.V3 +namespace Microsoft.OpenApi.Reader.V3 { /// /// Class containing logic to deserialize Open API V3 document into @@ -13,21 +13,21 @@ namespace Microsoft.OpenApi.Readers.V3 /// internal static partial class OpenApiV3Deserializer { - public static FixedFieldMap ResponsesFixedFields = new(); + public static readonly FixedFieldMap ResponsesFixedFields = new(); - public static PatternFieldMap ResponsesPatternFields = new() + public static readonly PatternFieldMap ResponsesPatternFields = new() { - {s => !s.StartsWith("x-"), (o, p, n) => o.Add(p, LoadResponse(n))}, - {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, LoadExtension(p,n))} + {s => !s.StartsWith("x-"), (o, p, n, t) => o.Add(p, LoadResponse(n, t))}, + {s => s.StartsWith("x-"), (o, p, n, _) => o.AddExtension(p, LoadExtension(p,n))} }; - public static OpenApiResponses LoadResponses(ParseNode node) + public static OpenApiResponses LoadResponses(ParseNode node, OpenApiDocument hostDocument = null) { var mapNode = node.CheckMapNode("Responses"); var domainObject = new OpenApiResponses(); - ParseMap(mapNode, domainObject, ResponsesFixedFields, ResponsesPatternFields); + ParseMap(mapNode, domainObject, ResponsesFixedFields, ResponsesPatternFields, hostDocument); return domainObject; } diff --git a/src/Microsoft.OpenApi/Reader/V3/OpenApiSchemaDeserializer.cs b/src/Microsoft.OpenApi/Reader/V3/OpenApiSchemaDeserializer.cs new file mode 100644 index 000000000..f3c02a6c8 --- /dev/null +++ b/src/Microsoft.OpenApi/Reader/V3/OpenApiSchemaDeserializer.cs @@ -0,0 +1,199 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using Microsoft.OpenApi.Extensions; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Models.References; +using Microsoft.OpenApi.Reader.ParseNodes; +using System.Collections.Generic; +using System.Globalization; + +namespace Microsoft.OpenApi.Reader.V3 +{ + /// + /// Class containing logic to deserialize Open API V3 document into + /// runtime Open API object model. + /// + internal static partial class OpenApiV3Deserializer + { + private static readonly FixedFieldMap _openApiSchemaFixedFields = new() + { + { + "title", + (o, n, _) => o.Title = n.GetScalarValue() + }, + { + "multipleOf", + (o, n, _) => o.MultipleOf = decimal.Parse(n.GetScalarValue(), NumberStyles.Float, CultureInfo.InvariantCulture) + }, + { + "maximum", + (o, n, _) => o.Maximum = ParserHelper.ParseDecimalWithFallbackOnOverflow(n.GetScalarValue(), decimal.MaxValue) + }, + { + "exclusiveMaximum", + (o, n, _) => o.ExclusiveMaximum = bool.Parse(n.GetScalarValue()) + }, + { + "minimum", + (o, n, _) => o.Minimum = ParserHelper.ParseDecimalWithFallbackOnOverflow(n.GetScalarValue(), decimal.MinValue) + }, + { + "exclusiveMinimum", + (o, n, _) => o.ExclusiveMinimum = bool.Parse(n.GetScalarValue()) + }, + { + "maxLength", + (o, n, _) => o.MaxLength = int.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture) + }, + { + "minLength", + (o, n, _) => o.MinLength = int.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture) + }, + { + "pattern", + (o, n, _) => o.Pattern = n.GetScalarValue() + }, + { + "maxItems", + (o, n, _) => o.MaxItems = int.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture) + }, + { + "minItems", + (o, n, _) => o.MinItems = int.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture) + }, + { + "uniqueItems", + (o, n, _) => o.UniqueItems = bool.Parse(n.GetScalarValue()) + }, + { + "maxProperties", + (o, n, _) => o.MaxProperties = int.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture) + }, + { + "minProperties", + (o, n, _) => o.MinProperties = int.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture) + }, + { + "required", + (o, n, _) => o.Required = new HashSet(n.CreateSimpleList((n2, p) => n2.GetScalarValue())) + }, + { + "enum", + (o, n, _) => o.Enum = n.CreateListOfAny() + }, + { + "type", + (o, n, _) => o.Type = n.GetScalarValue().ToJsonSchemaType() + }, + { + "allOf", + (o, n, t) => o.AllOf = n.CreateList(LoadSchema, t) + }, + { + "oneOf", + (o, n, _) => o.OneOf = n.CreateList(LoadSchema) + }, + { + "anyOf", + (o, n, t) => o.AnyOf = n.CreateList(LoadSchema, t) + }, + { + "not", + (o, n, _) => o.Not = LoadSchema(n) + }, + { + "items", + (o, n, _) => o.Items = LoadSchema(n) + }, + { + "properties", + (o, n, t) => o.Properties = n.CreateMap(LoadSchema, t) + }, + { + "additionalProperties", (o, n, _) => + { + if (n is ValueNode) + { + o.AdditionalPropertiesAllowed = bool.Parse(n.GetScalarValue()); + } + else + { + o.AdditionalProperties = LoadSchema(n); + } + } + }, + { + "description", + (o, n, _) => o.Description = n.GetScalarValue() + }, + { + "format", + (o, n, _) => o.Format = n.GetScalarValue() + }, + { + "default", + (o, n, _) => o.Default = n.CreateAny() + }, + { + "nullable", + (o, n, _) => o.Nullable = bool.Parse(n.GetScalarValue()) + }, + { + "discriminator", + (o, n, _) => o.Discriminator = LoadDiscriminator(n) + }, + { + "readOnly", + (o, n, _) => o.ReadOnly = bool.Parse(n.GetScalarValue()) + }, + { + "writeOnly", + (o, n, _) => o.WriteOnly = bool.Parse(n.GetScalarValue()) + }, + { + "xml", + (o, n, _) => o.Xml = LoadXml(n) + }, + { + "externalDocs", + (o, n, _) => o.ExternalDocs = LoadExternalDocs(n) + }, + { + "example", + (o, n, _) => o.Example = n.CreateAny() + }, + { + "deprecated", + (o, n, _) => o.Deprecated = bool.Parse(n.GetScalarValue()) + }, + }; + + private static readonly PatternFieldMap _openApiSchemaPatternFields = new() + { + {s => s.StartsWith("x-"), (o, p, n, _) => o.AddExtension(p, LoadExtension(p,n))} + }; + + public static OpenApiSchema LoadSchema(ParseNode node, OpenApiDocument hostDocument = null) + { + var mapNode = node.CheckMapNode(OpenApiConstants.Schema); + + var pointer = mapNode.GetReferencePointer(); + + if (pointer != null) + { + var reference = GetReferenceIdAndExternalResource(pointer); + return new OpenApiSchemaReference(reference.Item1, hostDocument, reference.Item2); + } + + var schema = new OpenApiSchema(); + + foreach (var propertyNode in mapNode) + { + propertyNode.ParseField(schema, _openApiSchemaFixedFields, _openApiSchemaPatternFields); + } + + return schema; + } + } +} diff --git a/src/Microsoft.OpenApi.Readers/V3/OpenApiSecurityRequirementDeserializer.cs b/src/Microsoft.OpenApi/Reader/V3/OpenApiSecurityRequirementDeserializer.cs similarity index 69% rename from src/Microsoft.OpenApi.Readers/V3/OpenApiSecurityRequirementDeserializer.cs rename to src/Microsoft.OpenApi/Reader/V3/OpenApiSecurityRequirementDeserializer.cs index fec3ec401..e1d4ddc2f 100644 --- a/src/Microsoft.OpenApi.Readers/V3/OpenApiSecurityRequirementDeserializer.cs +++ b/src/Microsoft.OpenApi/Reader/V3/OpenApiSecurityRequirementDeserializer.cs @@ -2,9 +2,10 @@ // Licensed under the MIT license. using Microsoft.OpenApi.Models; -using Microsoft.OpenApi.Readers.ParseNodes; +using Microsoft.OpenApi.Models.References; +using Microsoft.OpenApi.Reader.ParseNodes; -namespace Microsoft.OpenApi.Readers.V3 +namespace Microsoft.OpenApi.Reader.V3 { /// /// Class containing logic to deserialize Open API V3 document into @@ -12,7 +13,7 @@ namespace Microsoft.OpenApi.Readers.V3 /// internal static partial class OpenApiV3Deserializer { - public static OpenApiSecurityRequirement LoadSecurityRequirement(ParseNode node) + public static OpenApiSecurityRequirement LoadSecurityRequirement(ParseNode node, OpenApiDocument hostDocument = null) { var mapNode = node.CheckMapNode("security"); @@ -20,11 +21,9 @@ public static OpenApiSecurityRequirement LoadSecurityRequirement(ParseNode node) foreach (var property in mapNode) { - var scheme = LoadSecuritySchemeByReference( - mapNode.Context, - property.Name); + var scheme = LoadSecuritySchemeByReference(mapNode.Context, property.Name); - var scopes = property.Value.CreateSimpleList(value => value.GetScalarValue()); + var scopes = property.Value.CreateSimpleList((value, p) => value.GetScalarValue()); if (scheme != null) { @@ -44,16 +43,7 @@ private static OpenApiSecurityScheme LoadSecuritySchemeByReference( ParsingContext context, string schemeName) { - var securitySchemeObject = new OpenApiSecurityScheme - { - UnresolvedReference = true, - Reference = new() - { - Id = schemeName, - Type = ReferenceType.SecurityScheme - } - }; - + var securitySchemeObject = new OpenApiSecuritySchemeReference(schemeName, hostDocument: null); return securitySchemeObject; } } diff --git a/src/Microsoft.OpenApi.Readers/V3/OpenApiSecuritySchemeDeserializer.cs b/src/Microsoft.OpenApi/Reader/V3/OpenApiSecuritySchemeDeserializer.cs similarity index 61% rename from src/Microsoft.OpenApi.Readers/V3/OpenApiSecuritySchemeDeserializer.cs rename to src/Microsoft.OpenApi/Reader/V3/OpenApiSecuritySchemeDeserializer.cs index c219d586f..4a794408a 100644 --- a/src/Microsoft.OpenApi.Readers/V3/OpenApiSecuritySchemeDeserializer.cs +++ b/src/Microsoft.OpenApi/Reader/V3/OpenApiSecuritySchemeDeserializer.cs @@ -2,11 +2,13 @@ // Licensed under the MIT license. using System; +using System.Linq; using Microsoft.OpenApi.Extensions; using Microsoft.OpenApi.Models; -using Microsoft.OpenApi.Readers.ParseNodes; +using Microsoft.OpenApi.Models.References; +using Microsoft.OpenApi.Reader.ParseNodes; -namespace Microsoft.OpenApi.Readers.V3 +namespace Microsoft.OpenApi.Reader.V3 { /// /// Class containing logic to deserialize Open API V3 document into @@ -19,52 +21,54 @@ internal static partial class OpenApiV3Deserializer { { "type", - (o, n) => o.Type = n.GetScalarValue().GetEnumFromDisplayName() + (o, n, _) => o.Type = n.GetScalarValue().GetEnumFromDisplayName() }, { "description", - (o, n) => o.Description = n.GetScalarValue() + (o, n, _) => o.Description = n.GetScalarValue() }, { "name", - (o, n) => o.Name = n.GetScalarValue() + (o, n, _) => o.Name = n.GetScalarValue() }, { "in", - (o, n) => o.In = n.GetScalarValue().GetEnumFromDisplayName() + (o, n, _) => o.In = n.GetScalarValue().GetEnumFromDisplayName() }, { "scheme", - (o, n) => o.Scheme = n.GetScalarValue() + (o, n, _) => o.Scheme = n.GetScalarValue() }, { "bearerFormat", - (o, n) => o.BearerFormat = n.GetScalarValue() + (o, n, _) => o.BearerFormat = n.GetScalarValue() }, { "openIdConnectUrl", - (o, n) => o.OpenIdConnectUrl = new(n.GetScalarValue(), UriKind.RelativeOrAbsolute) + (o, n, _) => o.OpenIdConnectUrl = new(n.GetScalarValue(), UriKind.RelativeOrAbsolute) }, { "flows", - (o, n) => o.Flows = LoadOAuthFlows(n) + (o, n, t) => o.Flows = LoadOAuthFlows(n, t) } }; private static readonly PatternFieldMap _securitySchemePatternFields = new() { - {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, LoadExtension(p,n))} + {s => s.StartsWith("x-"), (o, p, n, _) => o.AddExtension(p, LoadExtension(p,n))} }; - public static OpenApiSecurityScheme LoadSecurityScheme(ParseNode node) + public static OpenApiSecurityScheme LoadSecurityScheme(ParseNode node, OpenApiDocument hostDocument = null) { var mapNode = node.CheckMapNode("securityScheme"); var pointer = mapNode.GetReferencePointer(); if (pointer != null) { - return mapNode.GetReferencedObject(ReferenceType.SecurityScheme, pointer); + var reference = GetReferenceIdAndExternalResource(pointer); + return new OpenApiSecuritySchemeReference(reference.Item1, hostDocument, reference.Item2); } + var securityScheme = new OpenApiSecurityScheme(); foreach (var property in mapNode) { diff --git a/src/Microsoft.OpenApi.Readers/V3/OpenApiServerDeserializer.cs b/src/Microsoft.OpenApi/Reader/V3/OpenApiServerDeserializer.cs similarity index 66% rename from src/Microsoft.OpenApi.Readers/V3/OpenApiServerDeserializer.cs rename to src/Microsoft.OpenApi/Reader/V3/OpenApiServerDeserializer.cs index cfdb5d3ae..9f56f764c 100644 --- a/src/Microsoft.OpenApi.Readers/V3/OpenApiServerDeserializer.cs +++ b/src/Microsoft.OpenApi/Reader/V3/OpenApiServerDeserializer.cs @@ -3,9 +3,9 @@ using Microsoft.OpenApi.Extensions; using Microsoft.OpenApi.Models; -using Microsoft.OpenApi.Readers.ParseNodes; +using Microsoft.OpenApi.Reader.ParseNodes; -namespace Microsoft.OpenApi.Readers.V3 +namespace Microsoft.OpenApi.Reader.V3 { /// /// Class containing logic to deserialize Open API V3 document into @@ -17,30 +17,30 @@ internal static partial class OpenApiV3Deserializer { { "url", - (o, n) => o.Url = n.GetScalarValue() + (o, n, _) => o.Url = n.GetScalarValue() }, { "description", - (o, n) => o.Description = n.GetScalarValue() + (o, n, _) => o.Description = n.GetScalarValue() }, { "variables", - (o, n) => o.Variables = n.CreateMap(LoadServerVariable) + (o, n, t) => o.Variables = n.CreateMap(LoadServerVariable, t) } }; private static readonly PatternFieldMap _serverPatternFields = new() { - {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, LoadExtension(p,n))} + {s => s.StartsWith("x-"), (o, p, n, _) => o.AddExtension(p, LoadExtension(p,n))} }; - public static OpenApiServer LoadServer(ParseNode node) + public static OpenApiServer LoadServer(ParseNode node, OpenApiDocument hostDocument = null) { var mapNode = node.CheckMapNode("server"); var server = new OpenApiServer(); - ParseMap(mapNode, server, _serverFixedFields, _serverPatternFields); + ParseMap(mapNode, server, _serverFixedFields, _serverPatternFields, hostDocument); return server; } diff --git a/src/Microsoft.OpenApi.Readers/V3/OpenApiServerVariableDeserializer.cs b/src/Microsoft.OpenApi/Reader/V3/OpenApiServerVariableDeserializer.cs similarity index 70% rename from src/Microsoft.OpenApi.Readers/V3/OpenApiServerVariableDeserializer.cs rename to src/Microsoft.OpenApi/Reader/V3/OpenApiServerVariableDeserializer.cs index e65222dde..1bfa4fe04 100644 --- a/src/Microsoft.OpenApi.Readers/V3/OpenApiServerVariableDeserializer.cs +++ b/src/Microsoft.OpenApi/Reader/V3/OpenApiServerVariableDeserializer.cs @@ -3,9 +3,9 @@ using Microsoft.OpenApi.Extensions; using Microsoft.OpenApi.Models; -using Microsoft.OpenApi.Readers.ParseNodes; +using Microsoft.OpenApi.Reader.ParseNodes; -namespace Microsoft.OpenApi.Readers.V3 +namespace Microsoft.OpenApi.Reader.V3 { /// /// Class containing logic to deserialize Open API V3 document into @@ -18,31 +18,31 @@ internal static partial class OpenApiV3Deserializer { { "enum", - (o, n) => o.Enum = n.CreateSimpleList(s => s.GetScalarValue()) + (o, n, _) => o.Enum = n.CreateSimpleList((s, p) => s.GetScalarValue()) }, { "default", - (o, n) => o.Default = n.GetScalarValue() + (o, n, _) => o.Default = n.GetScalarValue() }, { "description", - (o, n) => o.Description = n.GetScalarValue() + (o, n, _) => o.Description = n.GetScalarValue() }, }; private static readonly PatternFieldMap _serverVariablePatternFields = new() { - {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, LoadExtension(p,n))} + {s => s.StartsWith("x-"), (o, p, n, _) => o.AddExtension(p, LoadExtension(p,n))} }; - public static OpenApiServerVariable LoadServerVariable(ParseNode node) + public static OpenApiServerVariable LoadServerVariable(ParseNode node, OpenApiDocument hostDocument = null) { var mapNode = node.CheckMapNode("serverVariable"); var serverVariable = new OpenApiServerVariable(); - ParseMap(mapNode, serverVariable, _serverVariableFixedFields, _serverVariablePatternFields); + ParseMap(mapNode, serverVariable, _serverVariableFixedFields, _serverVariablePatternFields, hostDocument); return serverVariable; } diff --git a/src/Microsoft.OpenApi.Readers/V3/OpenApiTagDeserializer.cs b/src/Microsoft.OpenApi/Reader/V3/OpenApiTagDeserializer.cs similarity index 71% rename from src/Microsoft.OpenApi.Readers/V3/OpenApiTagDeserializer.cs rename to src/Microsoft.OpenApi/Reader/V3/OpenApiTagDeserializer.cs index 441ab330e..218399cbb 100644 --- a/src/Microsoft.OpenApi.Readers/V3/OpenApiTagDeserializer.cs +++ b/src/Microsoft.OpenApi/Reader/V3/OpenApiTagDeserializer.cs @@ -3,9 +3,9 @@ using Microsoft.OpenApi.Extensions; using Microsoft.OpenApi.Models; -using Microsoft.OpenApi.Readers.ParseNodes; +using Microsoft.OpenApi.Reader.ParseNodes; -namespace Microsoft.OpenApi.Readers.V3 +namespace Microsoft.OpenApi.Reader.V3 { /// /// Class containing logic to deserialize Open API V3 document into @@ -17,24 +17,24 @@ internal static partial class OpenApiV3Deserializer { { OpenApiConstants.Name, - (o, n) => o.Name = n.GetScalarValue() + (o, n, _) => o.Name = n.GetScalarValue() }, { OpenApiConstants.Description, - (o, n) => o.Description = n.GetScalarValue() + (o, n, _) => o.Description = n.GetScalarValue() }, { OpenApiConstants.ExternalDocs, - (o, n) => o.ExternalDocs = LoadExternalDocs(n) + (o, n, t) => o.ExternalDocs = LoadExternalDocs(n, t) } }; private static readonly PatternFieldMap _tagPatternFields = new() { - {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, LoadExtension(p,n))} + {s => s.StartsWith("x-"), (o, p, n, _) => o.AddExtension(p, LoadExtension(p,n))} }; - public static OpenApiTag LoadTag(ParseNode n) + public static OpenApiTag LoadTag(ParseNode n, OpenApiDocument hostDocument = null) { var mapNode = n.CheckMapNode("tag"); diff --git a/src/Microsoft.OpenApi.Readers/V3/OpenApiV3Deserializer.cs b/src/Microsoft.OpenApi/Reader/V3/OpenApiV3Deserializer.cs similarity index 75% rename from src/Microsoft.OpenApi.Readers/V3/OpenApiV3Deserializer.cs rename to src/Microsoft.OpenApi/Reader/V3/OpenApiV3Deserializer.cs index 558864854..6fa8406bf 100644 --- a/src/Microsoft.OpenApi.Readers/V3/OpenApiV3Deserializer.cs +++ b/src/Microsoft.OpenApi/Reader/V3/OpenApiV3Deserializer.cs @@ -3,14 +3,15 @@ using System.Collections.Generic; using System.Linq; +using System.Text.Json.Nodes; using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Exceptions; using Microsoft.OpenApi.Expressions; using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Models; -using Microsoft.OpenApi.Readers.ParseNodes; +using Microsoft.OpenApi.Reader.ParseNodes; -namespace Microsoft.OpenApi.Readers.V3 +namespace Microsoft.OpenApi.Reader.V3 { /// /// Class containing logic to deserialize Open API V3 document into @@ -22,7 +23,8 @@ private static void ParseMap( MapNode mapNode, T domainObject, FixedFieldMap fixedFieldMap, - PatternFieldMap patternFieldMap) + PatternFieldMap patternFieldMap, + OpenApiDocument hostDocument = null) { if (mapNode == null) { @@ -31,7 +33,7 @@ private static void ParseMap( foreach (var propertyNode in mapNode) { - propertyNode.ParseField(domainObject, fixedFieldMap, patternFieldMap); + propertyNode.ParseField(domainObject, fixedFieldMap, patternFieldMap, hostDocument); } } @@ -46,11 +48,16 @@ private static void ProcessAnyFields( { mapNode.Context.StartObject(anyFieldName); - var convertedOpenApiAny = OpenApiAnyConverter.GetSpecificOpenApiAny( - anyFieldMap[anyFieldName].PropertyGetter(domainObject), - anyFieldMap[anyFieldName].SchemaGetter(domainObject)); + var any = anyFieldMap[anyFieldName].PropertyGetter(domainObject); - anyFieldMap[anyFieldName].PropertySetter(domainObject, convertedOpenApiAny); + if (any == null) + { + anyFieldMap[anyFieldName].PropertySetter(domainObject, null); + } + else + { + anyFieldMap[anyFieldName].PropertySetter(domainObject, any); + } } catch (OpenApiException exception) { @@ -73,16 +80,13 @@ private static void ProcessAnyListFields( { try { - var newProperty = new List(); + var newProperty = new List(); mapNode.Context.StartObject(anyListFieldName); foreach (var propertyElement in anyListFieldMap[anyListFieldName].PropertyGetter(domainObject)) { - newProperty.Add( - OpenApiAnyConverter.GetSpecificOpenApiAny( - propertyElement, - anyListFieldMap[anyListFieldName].SchemaGetter(domainObject))); + newProperty.Add(propertyElement); } anyListFieldMap[anyListFieldName].PropertySetter(domainObject, newProperty); @@ -108,8 +112,6 @@ private static void ProcessAnyMapFields( { try { - var newProperty = new List(); - mapNode.Context.StartObject(anyMapFieldName); foreach (var propertyMapElement in anyMapFieldMap[anyMapFieldName].PropertyMapGetter(domainObject)) @@ -120,11 +122,7 @@ private static void ProcessAnyMapFields( { var any = anyMapFieldMap[anyMapFieldName].PropertyGetter(propertyMapElement.Value); - var newAny = OpenApiAnyConverter.GetSpecificOpenApiAny( - any, - anyMapFieldMap[anyMapFieldName].SchemaGetter(domainObject)); - - anyMapFieldMap[anyMapFieldName].PropertySetter(propertyMapElement.Value, newAny); + anyMapFieldMap[anyMapFieldName].PropertySetter(propertyMapElement.Value, any); } } } @@ -160,26 +158,26 @@ private static RuntimeExpressionAnyWrapper LoadRuntimeExpressionAnyWrapper(Parse return new() { - Any = OpenApiAnyConverter.GetSpecificOpenApiAny(node.CreateAny()) + Any = node.CreateAny() + }; } - public static IOpenApiAny LoadAny(ParseNode node) + public static OpenApiAny LoadAny(ParseNode node, OpenApiDocument hostDocument = null) { - return OpenApiAnyConverter.GetSpecificOpenApiAny(node.CreateAny()); + return new OpenApiAny(node.CreateAny()); } private static IOpenApiExtension LoadExtension(string name, ParseNode node) { if (node.Context.ExtensionParsers.TryGetValue(name, out var parser) && parser( - OpenApiAnyConverter.GetSpecificOpenApiAny(node.CreateAny()), - OpenApiSpecVersion.OpenApi3_0) is { } result) + node.CreateAny(), OpenApiSpecVersion.OpenApi3_0) is { } result) { return result; } else { - return OpenApiAnyConverter.GetSpecificOpenApiAny(node.CreateAny()); + return new OpenApiAny(node.CreateAny()); } } @@ -187,5 +185,16 @@ private static string LoadString(ParseNode node) { return node.GetScalarValue(); } + + private static (string, string) GetReferenceIdAndExternalResource(string pointer) + { + var refSegments = pointer.Split('/'); + var refId = refSegments.Last(); + var isExternalResource = !refSegments.First().StartsWith("#"); + + string externalResource = isExternalResource ? $"{refSegments.First()}/{refSegments[1].TrimEnd('#')}" : null; + + return (refId, externalResource); + } } } diff --git a/src/Microsoft.OpenApi.Readers/V3/OpenApiV3VersionService.cs b/src/Microsoft.OpenApi/Reader/V3/OpenApiV3VersionService.cs similarity index 81% rename from src/Microsoft.OpenApi.Readers/V3/OpenApiV3VersionService.cs rename to src/Microsoft.OpenApi/Reader/V3/OpenApiV3VersionService.cs index 96cf563f2..c2ef954a5 100644 --- a/src/Microsoft.OpenApi.Readers/V3/OpenApiV3VersionService.cs +++ b/src/Microsoft.OpenApi/Reader/V3/OpenApiV3VersionService.cs @@ -3,16 +3,17 @@ using System; using System.Collections.Generic; +using System.Linq; +using System.Text.Json.Nodes; using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Exceptions; using Microsoft.OpenApi.Extensions; using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Models; -using Microsoft.OpenApi.Readers.Interface; -using Microsoft.OpenApi.Readers.ParseNodes; -using Microsoft.OpenApi.Readers.Properties; +using Microsoft.OpenApi.Properties; +using Microsoft.OpenApi.Reader.ParseNodes; -namespace Microsoft.OpenApi.Readers.V3 +namespace Microsoft.OpenApi.Reader.V3 { /// /// The version service for the Open API V3.0. @@ -32,12 +33,13 @@ public OpenApiV3VersionService(OpenApiDiagnostic diagnostic) Diagnostic = diagnostic; } - private Dictionary> _loaders = new() + private readonly Dictionary> _loaders = new() { - [typeof(IOpenApiAny)] = OpenApiV3Deserializer.LoadAny, + [typeof(OpenApiAny)] = OpenApiV3Deserializer.LoadAny, [typeof(OpenApiCallback)] = OpenApiV3Deserializer.LoadCallback, [typeof(OpenApiComponents)] = OpenApiV3Deserializer.LoadComponents, [typeof(OpenApiContact)] = OpenApiV3Deserializer.LoadContact, + [typeof(OpenApiDiscriminator)] = OpenApiV3Deserializer.LoadDiscriminator, [typeof(OpenApiEncoding)] = OpenApiV3Deserializer.LoadEncoding, [typeof(OpenApiExample)] = OpenApiV3Deserializer.LoadExample, [typeof(OpenApiExternalDocs)] = OpenApiV3Deserializer.LoadExternalDocs, @@ -69,9 +71,13 @@ public OpenApiV3VersionService(OpenApiDiagnostic diagnostic) /// /// The URL of the reference /// The type of object referenced based on the context of the reference + /// + /// public OpenApiReference ConvertToOpenApiReference( string reference, - ReferenceType? type) + ReferenceType? type, + string summary = null, + string description = null) { if (!string.IsNullOrWhiteSpace(reference)) { @@ -147,7 +153,7 @@ public OpenApiReference ConvertToOpenApiReference( } else { - openApiReference.IsFragrament = true; + openApiReference.IsFragment = true; } openApiReference.ExternalResource = segments[0]; @@ -166,9 +172,23 @@ public OpenApiDocument LoadDocument(RootNode rootNode) return OpenApiV3Deserializer.LoadOpenApi(rootNode); } - public T LoadElement(ParseNode node) where T : IOpenApiElement + public T LoadElement(ParseNode node, OpenApiDocument doc) where T : IOpenApiElement { - return (T)_loaders[typeof(T)](node); + return (T)_loaders[typeof(T)](node, doc); + } + + /// + public string GetReferenceScalarValues(MapNode mapNode, string scalarValue) + { + if (mapNode.Any(static x => !"$ref".Equals(x.Name, StringComparison.OrdinalIgnoreCase))) + { + var valueNode = mapNode.Where(x => x.Name.Equals(scalarValue)) + .Select(static x => x.Value).OfType().FirstOrDefault(); + + return valueNode.GetScalarValue(); + } + + return null; } private OpenApiReference ParseLocalReference(string localReference) @@ -185,7 +205,19 @@ private OpenApiReference ParseLocalReference(string localReference) if (segments[1] == "components") { var referenceType = segments[2].GetEnumFromDisplayName(); - return new() { Type = referenceType, Id = segments[3] }; + var refId = segments[3]; + if (segments[2] == "pathItems") + { + refId = "/" + segments[3]; + }; + + var parsedReference = new OpenApiReference + { + Type = referenceType, + Id = refId + }; + + return parsedReference; } } diff --git a/src/Microsoft.OpenApi.Readers/V3/OpenApiXmlDeserializer.cs b/src/Microsoft.OpenApi/Reader/V3/OpenApiXmlDeserializer.cs similarity index 66% rename from src/Microsoft.OpenApi.Readers/V3/OpenApiXmlDeserializer.cs rename to src/Microsoft.OpenApi/Reader/V3/OpenApiXmlDeserializer.cs index b88aaade9..b57b641c4 100644 --- a/src/Microsoft.OpenApi.Readers/V3/OpenApiXmlDeserializer.cs +++ b/src/Microsoft.OpenApi/Reader/V3/OpenApiXmlDeserializer.cs @@ -4,9 +4,9 @@ using System; using Microsoft.OpenApi.Extensions; using Microsoft.OpenApi.Models; -using Microsoft.OpenApi.Readers.ParseNodes; +using Microsoft.OpenApi.Reader.ParseNodes; -namespace Microsoft.OpenApi.Readers.V3 +namespace Microsoft.OpenApi.Reader.V3 { /// /// Class containing logic to deserialize Open API V3 document into @@ -18,33 +18,33 @@ internal static partial class OpenApiV3Deserializer { { "name", - (o, n) => o.Name = n.GetScalarValue() + (o, n, _) => o.Name = n.GetScalarValue() }, { "namespace", - (o, n) => o.Namespace = new(n.GetScalarValue(), UriKind.Absolute) + (o, n, _) => o.Namespace = new(n.GetScalarValue(), UriKind.Absolute) }, { "prefix", - (o, n) => o.Prefix = n.GetScalarValue() + (o, n, _) => o.Prefix = n.GetScalarValue() }, { "attribute", - (o, n) => o.Attribute = bool.Parse(n.GetScalarValue()) + (o, n, _) => o.Attribute = bool.Parse(n.GetScalarValue()) }, { "wrapped", - (o, n) => o.Wrapped = bool.Parse(n.GetScalarValue()) + (o, n, _) => o.Wrapped = bool.Parse(n.GetScalarValue()) }, }; private static readonly PatternFieldMap _xmlPatternFields = new() { - {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, LoadExtension(p,n))} + {s => s.StartsWith("x-"), (o, p, n, _) => o.AddExtension(p, LoadExtension(p,n))} }; - public static OpenApiXml LoadXml(ParseNode node) + public static OpenApiXml LoadXml(ParseNode node, OpenApiDocument hostDocument = null) { var mapNode = node.CheckMapNode("xml"); diff --git a/src/Microsoft.OpenApi/Reader/V31/OpenApiCallbackDeserializer.cs b/src/Microsoft.OpenApi/Reader/V31/OpenApiCallbackDeserializer.cs new file mode 100644 index 000000000..580ce1356 --- /dev/null +++ b/src/Microsoft.OpenApi/Reader/V31/OpenApiCallbackDeserializer.cs @@ -0,0 +1,44 @@ +using Microsoft.OpenApi.Expressions; +using System; +using Microsoft.OpenApi.Extensions; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Reader.ParseNodes; +using Microsoft.OpenApi.Models.References; +using System.Linq; + +namespace Microsoft.OpenApi.Reader.V31 +{ + /// + /// Class containing logic to deserialize Open API V3 document into + /// runtime Open API object model. + /// + internal static partial class OpenApiV31Deserializer + { + private static readonly FixedFieldMap _callbackFixedFields = + new(); + + private static readonly PatternFieldMap _callbackPatternFields = + new() + { + {s => !s.StartsWith("x-", StringComparison.OrdinalIgnoreCase), (o, p, n, t) => o.AddPathItem(RuntimeExpression.Build(p), LoadPathItem(n, t))}, + {s => s.StartsWith("x-", StringComparison.OrdinalIgnoreCase), (o, p, n, _) => o.AddExtension(p, LoadExtension(p,n))}, + }; + + public static OpenApiCallback LoadCallback(ParseNode node, OpenApiDocument hostDocument = null) + { + var mapNode = node.CheckMapNode("callback"); + + if (mapNode.GetReferencePointer() is {} pointer) + { + var reference = GetReferenceIdAndExternalResource(pointer); + return new OpenApiCallbackReference(reference.Item1, hostDocument, reference.Item2); + } + + var domainObject = new OpenApiCallback(); + + ParseMap(mapNode, domainObject, _callbackFixedFields, _callbackPatternFields, hostDocument); + + return domainObject; + } + } +} diff --git a/src/Microsoft.OpenApi/Reader/V31/OpenApiComponentsDeserializer.cs b/src/Microsoft.OpenApi/Reader/V31/OpenApiComponentsDeserializer.cs new file mode 100644 index 000000000..e70087d4b --- /dev/null +++ b/src/Microsoft.OpenApi/Reader/V31/OpenApiComponentsDeserializer.cs @@ -0,0 +1,47 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System; +using Microsoft.OpenApi.Extensions; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Reader.ParseNodes; + +namespace Microsoft.OpenApi.Reader.V31 +{ + /// + /// Class containing logic to deserialize Open API V31 document into + /// runtime Open API object model. + /// + internal static partial class OpenApiV31Deserializer + { + private static readonly FixedFieldMap _componentsFixedFields = new() + { + {"schemas", (o, n, t) => o.Schemas = n.CreateMap(LoadSchema, t)}, + {"responses", (o, n, t) => o.Responses = n.CreateMap(LoadResponse, t)}, + {"parameters", (o, n, t) => o.Parameters = n.CreateMap(LoadParameter, t)}, + {"examples", (o, n, t) => o.Examples = n.CreateMap(LoadExample, t)}, + {"requestBodies", (o, n, t) => o.RequestBodies = n.CreateMap(LoadRequestBody, t)}, + {"headers", (o, n, t) => o.Headers = n.CreateMap(LoadHeader, t)}, + {"securitySchemes", (o, n, t) => o.SecuritySchemes = n.CreateMap(LoadSecurityScheme, t)}, + {"links", (o, n, t) => o.Links = n.CreateMap(LoadLink, t)}, + {"callbacks", (o, n, t) => o.Callbacks = n.CreateMap(LoadCallback, t)}, + {"pathItems", (o, n, t) => o.PathItems = n.CreateMap(LoadPathItem, t)} + }; + + private static readonly PatternFieldMap _componentsPatternFields = + new() + { + {s => s.StartsWith("x-", StringComparison.OrdinalIgnoreCase), (o, p, n, _) => o.AddExtension(p, LoadExtension(p, n))} + }; + + public static OpenApiComponents LoadComponents(ParseNode node, OpenApiDocument hostDocument = null) + { + var mapNode = node.CheckMapNode("components"); + var components = new OpenApiComponents(); + + ParseMap(mapNode, components, _componentsFixedFields, _componentsPatternFields, hostDocument); + + return components; + } + } +} diff --git a/src/Microsoft.OpenApi/Reader/V31/OpenApiContactDeserializer.cs b/src/Microsoft.OpenApi/Reader/V31/OpenApiContactDeserializer.cs new file mode 100644 index 000000000..7434deeec --- /dev/null +++ b/src/Microsoft.OpenApi/Reader/V31/OpenApiContactDeserializer.cs @@ -0,0 +1,51 @@ +using System; +using Microsoft.OpenApi.Extensions; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Reader.ParseNodes; + +namespace Microsoft.OpenApi.Reader.V31 +{ + /// + /// Class containing logic to deserialize Open API V31 document into + /// runtime Open API object model. + /// + internal static partial class OpenApiV31Deserializer + { + private static readonly FixedFieldMap _contactFixedFields = new() + { + { + "name", (o, n, _) => + { + o.Name = n.GetScalarValue(); + } + }, + { + "email", (o, n, _) => + { + o.Email = n.GetScalarValue(); + } + }, + { + "url", (o, n, _) => + { + o.Url = new Uri(n.GetScalarValue(), UriKind.RelativeOrAbsolute); + } + }, + }; + + private static readonly PatternFieldMap _contactPatternFields = new() + { + {s => s.StartsWith("x-"), (o, p, n, _) => o.AddExtension(p, LoadExtension(p,n))} + }; + + public static OpenApiContact LoadContact(ParseNode node, OpenApiDocument hostDocument = null) + { + var mapNode = node as MapNode; + var contact = new OpenApiContact(); + + ParseMap(mapNode, contact, _contactFixedFields, _contactPatternFields, hostDocument); + + return contact; + } + } +} diff --git a/src/Microsoft.OpenApi/Reader/V31/OpenApiDiscriminatorDeserializer.cs b/src/Microsoft.OpenApi/Reader/V31/OpenApiDiscriminatorDeserializer.cs new file mode 100644 index 000000000..51122a9c8 --- /dev/null +++ b/src/Microsoft.OpenApi/Reader/V31/OpenApiDiscriminatorDeserializer.cs @@ -0,0 +1,49 @@ +using Microsoft.OpenApi.Extensions; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Reader.ParseNodes; + +namespace Microsoft.OpenApi.Reader.V31 +{ + /// + /// Class containing logic to deserialize Open API V31 document into + /// runtime Open API object model. + /// + internal static partial class OpenApiV31Deserializer + { + private static readonly FixedFieldMap _discriminatorFixedFields = + new() + { + { + "propertyName", (o, n, _) => + { + o.PropertyName = n.GetScalarValue(); + } + }, + { + "mapping", (o, n, _) => + { + o.Mapping = n.CreateSimpleMap(LoadString); + } + } + }; + + private static readonly PatternFieldMap _discriminatorPatternFields = + new() + { + {s => s.StartsWith("x-"), (o, p, n, _) => o.AddExtension(p, LoadExtension(p,n))} + }; + + public static OpenApiDiscriminator LoadDiscriminator(ParseNode node, OpenApiDocument hostDocument = null) + { + var mapNode = node.CheckMapNode("discriminator"); + + var discriminator = new OpenApiDiscriminator(); + foreach (var property in mapNode) + { + property.ParseField(discriminator, _discriminatorFixedFields, _discriminatorPatternFields); + } + + return discriminator; + } + } +} diff --git a/src/Microsoft.OpenApi/Reader/V31/OpenApiDocumentDeserializer.cs b/src/Microsoft.OpenApi/Reader/V31/OpenApiDocumentDeserializer.cs new file mode 100644 index 000000000..8137fb460 --- /dev/null +++ b/src/Microsoft.OpenApi/Reader/V31/OpenApiDocumentDeserializer.cs @@ -0,0 +1,61 @@ +using System; +using Microsoft.OpenApi.Extensions; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Reader.ParseNodes; +using Microsoft.OpenApi.Services; + +namespace Microsoft.OpenApi.Reader.V31 +{ + /// + /// Class containing logic to deserialize Open API V31 document into + /// runtime Open API object model. + /// + internal static partial class OpenApiV31Deserializer + { + private static readonly FixedFieldMap _openApiFixedFields = new() + { + { + "openapi", (o, n, _) => + { + } /* Version is valid field but we already parsed it */ + }, + {"info", (o, n, _) => o.Info = LoadInfo(n, o)}, + {"jsonSchemaDialect", (o, n, _) => o.JsonSchemaDialect = n.GetScalarValue() }, + {"servers", (o, n, _) => o.Servers = n.CreateList(LoadServer, o)}, + {"paths", (o, n, _) => o.Paths = LoadPaths(n, o)}, + {"webhooks", (o, n, _) => o.Webhooks = n.CreateMap(LoadPathItem, o)}, + {"components", (o, n, _) => o.Components = LoadComponents(n, o)}, + {"tags", (o, n, _) => {o.Tags = n.CreateList(LoadTag, o); + foreach (var tag in o.Tags) + { + tag.Reference = new OpenApiReference() + { + Id = tag.Name, + Type = ReferenceType.Tag + }; + } + } }, + {"externalDocs", (o, n, _) => o.ExternalDocs = LoadExternalDocs(n, o)}, + {"security", (o, n, _) => o.SecurityRequirements = n.CreateList(LoadSecurityRequirement, o)} + }; + + private static readonly PatternFieldMap _openApiPatternFields = new() + { + // We have no semantics to verify X- nodes, therefore treat them as just values. + {s => s.StartsWith("x-"), (o, p, n, _) => o.AddExtension(p, LoadExtension(p, n))} + }; + + public static OpenApiDocument LoadOpenApi(RootNode rootNode) + { + var openApiDoc = new OpenApiDocument(); + var openApiNode = rootNode.GetMap(); + + ParseMap(openApiNode, openApiDoc, _openApiFixedFields, _openApiPatternFields, openApiDoc); + + // Register components + openApiDoc.Workspace.RegisterComponents(openApiDoc); + + return openApiDoc; + } + } +} diff --git a/src/Microsoft.OpenApi/Reader/V31/OpenApiEncodingDeserializer.cs b/src/Microsoft.OpenApi/Reader/V31/OpenApiEncodingDeserializer.cs new file mode 100644 index 000000000..b54c5e75b --- /dev/null +++ b/src/Microsoft.OpenApi/Reader/V31/OpenApiEncodingDeserializer.cs @@ -0,0 +1,66 @@ +using Microsoft.OpenApi.Extensions; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Reader.ParseNodes; + +namespace Microsoft.OpenApi.Reader.V31 +{ + /// + /// Class containing logic to deserialize Open API V31 document into + /// runtime Open API object model. + /// + internal static partial class OpenApiV31Deserializer + { + private static readonly FixedFieldMap _encodingFixedFields = new() + { + { + "contentType", (o, n, _) => + { + o.ContentType = n.GetScalarValue(); + } + }, + { + "headers", (o, n, t) => + { + o.Headers = n.CreateMap(LoadHeader, t); + } + }, + { + "style", (o, n, _) => + { + o.Style = n.GetScalarValue().GetEnumFromDisplayName(); + } + }, + { + "explode", (o, n, _) => + { + o.Explode = bool.Parse(n.GetScalarValue()); + } + }, + { + "allowedReserved", (o, n, _) => + { + o.AllowReserved = bool.Parse(n.GetScalarValue()); + } + }, + }; + + private static readonly PatternFieldMap _encodingPatternFields = + new() + { + {s => s.StartsWith("x-"), (o, p, n, _) => o.AddExtension(p, LoadExtension(p,n))} + }; + + public static OpenApiEncoding LoadEncoding(ParseNode node, OpenApiDocument hostDocument = null) + { + var mapNode = node.CheckMapNode("encoding"); + + var encoding = new OpenApiEncoding(); + foreach (var property in mapNode) + { + property.ParseField(encoding, _encodingFixedFields, _encodingPatternFields); + } + + return encoding; + } + } +} diff --git a/src/Microsoft.OpenApi/Reader/V31/OpenApiExampleDeserializer.cs b/src/Microsoft.OpenApi/Reader/V31/OpenApiExampleDeserializer.cs new file mode 100644 index 000000000..0035360d5 --- /dev/null +++ b/src/Microsoft.OpenApi/Reader/V31/OpenApiExampleDeserializer.cs @@ -0,0 +1,70 @@ +using System.Linq; +using Microsoft.OpenApi.Extensions; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Models.References; +using Microsoft.OpenApi.Reader.ParseNodes; + +namespace Microsoft.OpenApi.Reader.V31 +{ + /// + /// Class containing logic to deserialize Open API V31 document into + /// runtime Open API object model. + /// + internal static partial class OpenApiV31Deserializer + { + private static readonly FixedFieldMap _exampleFixedFields = new() + { + { + "summary", (o, n, _) => + { + o.Summary = n.GetScalarValue(); + } + }, + { + "description", (o, n, _) => + { + o.Description = n.GetScalarValue(); + } + }, + { + "value", (o, n, _) => + { + o.Value = n.CreateAny(); + } + }, + { + "externalValue", (o, n, _) => + { + o.ExternalValue = n.GetScalarValue(); + } + }, + + }; + + private static readonly PatternFieldMap _examplePatternFields = + new() + { + {s => s.StartsWith("x-"), (o, p, n, _) => o.AddExtension(p, LoadExtension(p,n))} + }; + + public static OpenApiExample LoadExample(ParseNode node, OpenApiDocument hostDocument = null) + { + var mapNode = node.CheckMapNode("example"); + + var pointer = mapNode.GetReferencePointer(); + if (pointer != null) + { + var reference = GetReferenceIdAndExternalResource(pointer); + return new OpenApiExampleReference(reference.Item1, hostDocument, reference.Item2); + } + + var example = new OpenApiExample(); + foreach (var property in mapNode) + { + property.ParseField(example, _exampleFixedFields, _examplePatternFields); + } + + return example; + } + } +} diff --git a/src/Microsoft.OpenApi/Reader/V31/OpenApiExternalDocsDeserializer.cs b/src/Microsoft.OpenApi/Reader/V31/OpenApiExternalDocsDeserializer.cs new file mode 100644 index 000000000..f42288fcf --- /dev/null +++ b/src/Microsoft.OpenApi/Reader/V31/OpenApiExternalDocsDeserializer.cs @@ -0,0 +1,50 @@ +using System; +using Microsoft.OpenApi.Extensions; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Reader.ParseNodes; + +namespace Microsoft.OpenApi.Reader.V31 +{ + /// + /// Class containing logic to deserialize Open API V31 document into + /// runtime Open API object model. + /// + internal static partial class OpenApiV31Deserializer + { + private static readonly FixedFieldMap _externalDocsFixedFields = + new() + { + // $ref + { + "description", (o, n, _) => + { + o.Description = n.GetScalarValue(); + } + }, + { + "url", (o, n, _) => + { + o.Url = new Uri(n.GetScalarValue(), UriKind.RelativeOrAbsolute); + } + }, + }; + + private static readonly PatternFieldMap _externalDocsPatternFields = + new() + { + + {s => s.StartsWith("x-"), (o, p, n, _) => o.AddExtension(p, LoadExtension(p, n))} + }; + + public static OpenApiExternalDocs LoadExternalDocs(ParseNode node, OpenApiDocument hostDocument = null) + { + var mapNode = node.CheckMapNode("externalDocs"); + + var externalDocs = new OpenApiExternalDocs(); + + ParseMap(mapNode, externalDocs, _externalDocsFixedFields, _externalDocsPatternFields, hostDocument); + + return externalDocs; + } + } +} diff --git a/src/Microsoft.OpenApi/Reader/V31/OpenApiHeaderDeserializer.cs b/src/Microsoft.OpenApi/Reader/V31/OpenApiHeaderDeserializer.cs new file mode 100644 index 000000000..d3657db02 --- /dev/null +++ b/src/Microsoft.OpenApi/Reader/V31/OpenApiHeaderDeserializer.cs @@ -0,0 +1,104 @@ +using System.Linq; +using Microsoft.OpenApi.Extensions; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Models.References; +using Microsoft.OpenApi.Reader.ParseNodes; + +namespace Microsoft.OpenApi.Reader.V31 +{ + /// + /// Class containing logic to deserialize Open API V31 document into + /// runtime Open API object model. + /// + internal static partial class OpenApiV31Deserializer + { + private static readonly FixedFieldMap _headerFixedFields = new() + { + { + "description", (o, n, _) => + { + o.Description = n.GetScalarValue(); + } + }, + { + "required", (o, n, _) => + { + o.Required = bool.Parse(n.GetScalarValue()); + } + }, + { + "deprecated", (o, n, _) => + { + o.Deprecated = bool.Parse(n.GetScalarValue()); + } + }, + { + "allowEmptyValue", (o, n, _) => + { + o.AllowEmptyValue = bool.Parse(n.GetScalarValue()); + } + }, + { + "allowReserved", (o, n, _) => + { + o.AllowReserved = bool.Parse(n.GetScalarValue()); + } + }, + { + "style", (o, n, _) => + { + o.Style = n.GetScalarValue().GetEnumFromDisplayName(); + } + }, + { + "explode", (o, n, _) => + { + o.Explode = bool.Parse(n.GetScalarValue()); + } + }, + { + "schema", (o, n, t) => + { + o.Schema = LoadSchema(n, t); + } + }, + { + "examples", (o, n, t) => + { + o.Examples = n.CreateMap(LoadExample, t); + } + }, + { + "example", (o, n, _) => + { + o.Example = n.CreateAny(); + } + }, + }; + + private static readonly PatternFieldMap _headerPatternFields = new() + { + {s => s.StartsWith("x-"), (o, p, n, _) => o.AddExtension(p, LoadExtension(p,n))} + }; + + public static OpenApiHeader LoadHeader(ParseNode node, OpenApiDocument hostDocument = null) + { + var mapNode = node.CheckMapNode("header"); + + var pointer = mapNode.GetReferencePointer(); + if (pointer != null) + { + var reference = GetReferenceIdAndExternalResource(pointer); + return new OpenApiHeaderReference(reference.Item1, hostDocument, reference.Item2); + } + + var header = new OpenApiHeader(); + foreach (var property in mapNode) + { + property.ParseField(header, _headerFixedFields, _headerPatternFields); + } + + return header; + } + } +} diff --git a/src/Microsoft.OpenApi/Reader/V31/OpenApiInfoDeserializer.cs b/src/Microsoft.OpenApi/Reader/V31/OpenApiInfoDeserializer.cs new file mode 100644 index 000000000..6476e1acc --- /dev/null +++ b/src/Microsoft.OpenApi/Reader/V31/OpenApiInfoDeserializer.cs @@ -0,0 +1,74 @@ +using System; +using Microsoft.OpenApi.Extensions; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Reader.ParseNodes; + +namespace Microsoft.OpenApi.Reader.V31 +{ + /// + /// Class containing logic to deserialize Open API V31 document into + /// runtime Open API object model. + /// + internal static partial class OpenApiV31Deserializer + { + public static readonly FixedFieldMap InfoFixedFields = new() + { + { + "title", (o, n, _) => + { + o.Title = n.GetScalarValue(); + } + }, + { + "version", (o, n, _) => + { + o.Version = n.GetScalarValue(); + } + }, + { + "summary", (o, n, _) => + { + o.Summary = n.GetScalarValue(); + } + }, + { + "description", (o, n, _) => + { + o.Description = n.GetScalarValue(); + } + }, + { + "termsOfService", (o, n, _) => + { + o.TermsOfService = new Uri(n.GetScalarValue(), UriKind.RelativeOrAbsolute); + } + }, + { + "contact", (o, n, t) => + { + o.Contact = LoadContact(n, t); + } + }, + { + "license", (o, n, t) => + { + o.License = LoadLicense(n, t); + } + } + }; + + public static readonly PatternFieldMap InfoPatternFields = new() + { + {s => s.StartsWith("x-"), (o, k, n, _) => o.AddExtension(k,LoadExtension(k, n))} + }; + + public static OpenApiInfo LoadInfo(ParseNode node, OpenApiDocument hostDocument = null) + { + var mapNode = node.CheckMapNode("Info"); + var info = new OpenApiInfo(); + ParseMap(mapNode, info, InfoFixedFields, InfoPatternFields, hostDocument); + + return info; + } + } +} diff --git a/src/Microsoft.OpenApi/Reader/V31/OpenApiLicenseDeserializer.cs b/src/Microsoft.OpenApi/Reader/V31/OpenApiLicenseDeserializer.cs new file mode 100644 index 000000000..efddbc2b1 --- /dev/null +++ b/src/Microsoft.OpenApi/Reader/V31/OpenApiLicenseDeserializer.cs @@ -0,0 +1,52 @@ +using System; +using Microsoft.OpenApi.Extensions; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Reader.ParseNodes; + +namespace Microsoft.OpenApi.Reader.V31 +{ + /// + /// Class containing logic to deserialize Open API V31 document into + /// runtime Open API object model. + /// + internal static partial class OpenApiV31Deserializer + { + private static readonly FixedFieldMap _licenseFixedFields = new() + { + { + "name", (o, n, _) => + { + o.Name = n.GetScalarValue(); + } + }, + { + "identifier", (o, n, _) => + { + o.Identifier = n.GetScalarValue(); + } + }, + { + "url", (o, n, _) => + { + o.Url = new Uri(n.GetScalarValue(), UriKind.RelativeOrAbsolute); + } + }, + }; + + private static readonly PatternFieldMap _licensePatternFields = new() + { + {s => s.StartsWith("x-"), (o, p, n, _) => o.AddExtension(p, LoadExtension(p,n))} + }; + + internal static OpenApiLicense LoadLicense(ParseNode node, OpenApiDocument hostDocument = null) + { + var mapNode = node.CheckMapNode("License"); + + var license = new OpenApiLicense(); + + ParseMap(mapNode, license, _licenseFixedFields, _licensePatternFields, hostDocument); + + return license; + } + } +} diff --git a/src/Microsoft.OpenApi/Reader/V31/OpenApiLinkDeserializer.cs b/src/Microsoft.OpenApi/Reader/V31/OpenApiLinkDeserializer.cs new file mode 100644 index 000000000..aa1e26ea1 --- /dev/null +++ b/src/Microsoft.OpenApi/Reader/V31/OpenApiLinkDeserializer.cs @@ -0,0 +1,72 @@ +using System.Linq; +using Microsoft.OpenApi.Extensions; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Models.References; +using Microsoft.OpenApi.Reader.ParseNodes; + +namespace Microsoft.OpenApi.Reader.V31 +{ + /// + /// Class containing logic to deserialize Open API V31 document into + /// runtime Open API object model. + /// + internal static partial class OpenApiV31Deserializer + { + private static readonly FixedFieldMap _linkFixedFields = new() + { + { + "operationRef", (o, n, _) => + { + o.OperationRef = n.GetScalarValue(); + } + }, + { + "operationId", (o, n, _) => + { + o.OperationId = n.GetScalarValue(); + } + }, + { + "parameters", (o, n, _) => + { + o.Parameters = n.CreateSimpleMap(LoadRuntimeExpressionAnyWrapper); + } + }, + { + "requestBody", (o, n, _) => + { + o.RequestBody = LoadRuntimeExpressionAnyWrapper(n); + } + }, + { + "description", (o, n, _) => + { + o.Description = n.GetScalarValue(); + } + }, + {"server", (o, n, t) => o.Server = LoadServer(n, t)} + }; + + private static readonly PatternFieldMap _linkPatternFields = new() + { + {s => s.StartsWith("x-"), (o, p, n, _) => o.AddExtension(p, LoadExtension(p,n))}, + }; + + public static OpenApiLink LoadLink(ParseNode node, OpenApiDocument hostDocument = null) + { + var mapNode = node.CheckMapNode("link"); + var link = new OpenApiLink(); + + var pointer = mapNode.GetReferencePointer(); + if (pointer != null) + { + var reference = GetReferenceIdAndExternalResource(pointer); + return new OpenApiLinkReference(reference.Item1, hostDocument, reference.Item2); + } + + ParseMap(mapNode, link, _linkFixedFields, _linkPatternFields, hostDocument); + + return link; + } + } +} diff --git a/src/Microsoft.OpenApi/Reader/V31/OpenApiMediaTypeDeserializer.cs b/src/Microsoft.OpenApi/Reader/V31/OpenApiMediaTypeDeserializer.cs new file mode 100644 index 000000000..c0ce9b843 --- /dev/null +++ b/src/Microsoft.OpenApi/Reader/V31/OpenApiMediaTypeDeserializer.cs @@ -0,0 +1,87 @@ +using Microsoft.OpenApi.Extensions; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Reader.ParseNodes; + +namespace Microsoft.OpenApi.Reader.V31 +{ + /// + /// Class containing logic to deserialize Open API V3 document into + /// runtime Open API object model. + /// + internal static partial class OpenApiV31Deserializer + { + private static readonly FixedFieldMap _mediaTypeFixedFields = + new() + { + { + OpenApiConstants.Schema, (o, n, t) => + { + o.Schema = LoadSchema(n, t); + } + }, + { + OpenApiConstants.Examples, (o, n, t) => + { + o.Examples = n.CreateMap(LoadExample, t); + } + }, + { + OpenApiConstants.Example, (o, n, _) => + { + o.Example = n.CreateAny(); + } + }, + { + OpenApiConstants.Encoding, (o, n, t) => + { + o.Encoding = n.CreateMap(LoadEncoding, t); + } + }, + }; + + private static readonly PatternFieldMap _mediaTypePatternFields = + new() + { + {s => s.StartsWith("x-"), (o, p, n, _) => o.AddExtension(p, LoadExtension(p,n))} + }; + + private static readonly AnyFieldMap _mediaTypeAnyFields = new AnyFieldMap + { + { + OpenApiConstants.Example, + new AnyFieldMapParameter( + s => s.Example, + (s, v) => s.Example = v, + s => s.Schema) + } + }; + + + private static readonly AnyMapFieldMap _mediaTypeAnyMapOpenApiExampleFields = + new AnyMapFieldMap + { + { + OpenApiConstants.Examples, + new AnyMapFieldMapParameter( + m => m.Examples, + e => e.Value, + (e, v) => e.Value = v, + m => m.Schema) + } + }; + + public static OpenApiMediaType LoadMediaType(ParseNode node, OpenApiDocument hostDocument = null) + { + var mapNode = node.CheckMapNode(OpenApiConstants.Content); + + var mediaType = new OpenApiMediaType(); + + ParseMap(mapNode, mediaType, _mediaTypeFixedFields, _mediaTypePatternFields, hostDocument); + + ProcessAnyFields(mapNode, mediaType, _mediaTypeAnyFields); + ProcessAnyMapFields(mapNode, mediaType, _mediaTypeAnyMapOpenApiExampleFields); + + return mediaType; + } + } +} diff --git a/src/Microsoft.OpenApi/Reader/V31/OpenApiOAuthFlowDeserializer.cs b/src/Microsoft.OpenApi/Reader/V31/OpenApiOAuthFlowDeserializer.cs new file mode 100644 index 000000000..199cf14e7 --- /dev/null +++ b/src/Microsoft.OpenApi/Reader/V31/OpenApiOAuthFlowDeserializer.cs @@ -0,0 +1,57 @@ +using System; +using Microsoft.OpenApi.Extensions; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Reader.ParseNodes; + +namespace Microsoft.OpenApi.Reader.V31 +{ + /// + /// Class containing logic to deserialize Open API V31 document into + /// runtime Open API object model. + /// + internal static partial class OpenApiV31Deserializer + { + private static readonly FixedFieldMap _oAuthFlowFixedFileds = + new() + { + { + "authorizationUrl", (o, n, _) => + { + o.AuthorizationUrl = new Uri(n.GetScalarValue(), UriKind.RelativeOrAbsolute); + } + }, + { + "tokenUrl", (o, n, _) => + { + o.TokenUrl = new Uri(n.GetScalarValue(), UriKind.RelativeOrAbsolute); + } + }, + { + "refreshUrl", (o, n, _) => + { + o.RefreshUrl = new Uri(n.GetScalarValue(), UriKind.RelativeOrAbsolute); + } + }, + {"scopes", (o, n, _) => o.Scopes = n.CreateSimpleMap(LoadString)} + }; + + private static readonly PatternFieldMap _oAuthFlowPatternFields = + new() + { + {s => s.StartsWith("x-"), (o, p, n, _) => o.AddExtension(p, LoadExtension(p,n))} + }; + + public static OpenApiOAuthFlow LoadOAuthFlow(ParseNode node, OpenApiDocument hostDocument = null) + { + var mapNode = node.CheckMapNode("OAuthFlow"); + + var oauthFlow = new OpenApiOAuthFlow(); + foreach (var property in mapNode) + { + property.ParseField(oauthFlow, _oAuthFlowFixedFileds, _oAuthFlowPatternFields); + } + + return oauthFlow; + } + } +} diff --git a/src/Microsoft.OpenApi/Reader/V31/OpenApiOAuthFlowsDeserializer.cs b/src/Microsoft.OpenApi/Reader/V31/OpenApiOAuthFlowsDeserializer.cs new file mode 100644 index 000000000..28316ec9b --- /dev/null +++ b/src/Microsoft.OpenApi/Reader/V31/OpenApiOAuthFlowsDeserializer.cs @@ -0,0 +1,41 @@ +using Microsoft.OpenApi.Extensions; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Reader.ParseNodes; + +namespace Microsoft.OpenApi.Reader.V31 +{ + /// + /// Class containing logic to deserialize Open API V31 document into + /// runtime Open API object model. + /// + internal static partial class OpenApiV31Deserializer + { + private static readonly FixedFieldMap _oAuthFlowsFixedFileds = + new() + { + {"implicit", (o, n, t) => o.Implicit = LoadOAuthFlow(n, t)}, + {"password", (o, n, t) => o.Password = LoadOAuthFlow(n, t)}, + {"clientCredentials", (o, n, t) => o.ClientCredentials = LoadOAuthFlow(n, t)}, + {"authorizationCode", (o, n, t) => o.AuthorizationCode = LoadOAuthFlow(n, t)} + }; + + private static readonly PatternFieldMap _oAuthFlowsPatternFields = + new() + { + {s => s.StartsWith("x-"), (o, p, n, _) => o.AddExtension(p, LoadExtension(p,n))} + }; + + public static OpenApiOAuthFlows LoadOAuthFlows(ParseNode node, OpenApiDocument hostDocument = null) + { + var mapNode = node.CheckMapNode("OAuthFlows"); + + var oAuthFlows = new OpenApiOAuthFlows(); + foreach (var property in mapNode) + { + property.ParseField(oAuthFlows, _oAuthFlowsFixedFileds, _oAuthFlowsPatternFields); + } + + return oAuthFlows; + } + } +} diff --git a/src/Microsoft.OpenApi/Reader/V31/OpenApiOperationDeserializer.cs b/src/Microsoft.OpenApi/Reader/V31/OpenApiOperationDeserializer.cs new file mode 100644 index 000000000..fb143e4c6 --- /dev/null +++ b/src/Microsoft.OpenApi/Reader/V31/OpenApiOperationDeserializer.cs @@ -0,0 +1,113 @@ +using Microsoft.OpenApi.Extensions; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Models.References; +using Microsoft.OpenApi.Reader.ParseNodes; + +namespace Microsoft.OpenApi.Reader.V31 +{ + /// + /// Class containing logic to deserialize Open API V31 document into + /// runtime Open API object model. + /// + internal static partial class OpenApiV31Deserializer + { + private static readonly FixedFieldMap _operationFixedFields = + new() + { + { + "tags", (o, n, doc) => o.Tags = n.CreateSimpleList( + (valueNode, doc) => + LoadTagByReference(valueNode.GetScalarValue(), doc)) + }, + { + "summary", (o, n, _) => + { + o.Summary = n.GetScalarValue(); + } + }, + { + "description", (o, n, _) => + { + o.Description = n.GetScalarValue(); + } + }, + { + "externalDocs", (o, n, t) => + { + o.ExternalDocs = LoadExternalDocs(n, t); + } + }, + { + "operationId", (o, n, _) => + { + o.OperationId = n.GetScalarValue(); + } + }, + { + "parameters", (o, n, t) => + { + o.Parameters = n.CreateList(LoadParameter, t); + } + }, + { + "requestBody", (o, n, t) => + { + o.RequestBody = LoadRequestBody(n, t); + } + }, + { + "responses", (o, n, t) => + { + o.Responses = LoadResponses(n, t); + } + }, + { + "callbacks", (o, n, t) => + { + o.Callbacks = n.CreateMap(LoadCallback, t); + } + }, + { + "deprecated", (o, n, _) => + { + o.Deprecated = bool.Parse(n.GetScalarValue()); + } + }, + { + "security", (o, n, t) => + { + o.Security = n.CreateList(LoadSecurityRequirement, t); + } + }, + { + "servers", (o, n, t) => + { + o.Servers = n.CreateList(LoadServer, t); + } + }, + }; + + private static readonly PatternFieldMap _operationPatternFields = + new() + { + {s => s.StartsWith("x-"), (o, p, n, _) => o.AddExtension(p, LoadExtension(p,n))}, + }; + + internal static OpenApiOperation LoadOperation(ParseNode node, OpenApiDocument hostDocument = null) + { + var mapNode = node.CheckMapNode("Operation"); + + var operation = new OpenApiOperation(); + + ParseMap(mapNode, operation, _operationFixedFields, _operationPatternFields, hostDocument); + + return operation; + } + + private static OpenApiTag LoadTagByReference(string tagName, OpenApiDocument hostDocument = null) + { + var tagObject = new OpenApiTagReference(tagName, hostDocument); + return tagObject; + } + } +} diff --git a/src/Microsoft.OpenApi/Reader/V31/OpenApiParameterDeserializer.cs b/src/Microsoft.OpenApi/Reader/V31/OpenApiParameterDeserializer.cs new file mode 100644 index 000000000..e8f4e5a93 --- /dev/null +++ b/src/Microsoft.OpenApi/Reader/V31/OpenApiParameterDeserializer.cs @@ -0,0 +1,153 @@ +using System; +using System.Linq; +using Microsoft.OpenApi.Extensions; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Models.References; +using Microsoft.OpenApi.Reader.ParseNodes; + +namespace Microsoft.OpenApi.Reader.V31 +{ + /// + /// Class containing logic to deserialize Open API V31 document into + /// runtime Open API object model. + /// + internal static partial class OpenApiV31Deserializer + { + private static readonly FixedFieldMap _parameterFixedFields = + new() + { + { + "name", (o, n, _) => + { + o.Name = n.GetScalarValue(); + } + }, + { + "in", (o, n, _) => + { + var inString = n.GetScalarValue(); + o.In = Enum.GetValues(typeof(ParameterLocation)).Cast() + .Select( e => e.GetDisplayName() ) + .Contains(inString) ? n.GetScalarValue().GetEnumFromDisplayName() : null; + + } + }, + { + "description", (o, n, _) => + { + o.Description = n.GetScalarValue(); + } + }, + { + "required", (o, n, _) => + { + o.Required = bool.Parse(n.GetScalarValue()); + } + }, + { + "deprecated", (o, n, _) => + { + o.Deprecated = bool.Parse(n.GetScalarValue()); + } + }, + { + "allowEmptyValue", (o, n, _) => + { + o.AllowEmptyValue = bool.Parse(n.GetScalarValue()); + } + }, + { + "allowReserved", (o, n, _) => + { + o.AllowReserved = bool.Parse(n.GetScalarValue()); + } + }, + { + "style", (o, n, _) => + { + o.Style = n.GetScalarValue().GetEnumFromDisplayName(); + } + }, + { + "explode", (o, n, _) => + { + o.Explode = bool.Parse(n.GetScalarValue()); + } + }, + { + "schema", (o, n, t) => + { + o.Schema = LoadSchema(n, t); + } + }, + { + "content", (o, n, t) => + { + o.Content = n.CreateMap(LoadMediaType, t); + } + }, + { + "examples", (o, n, t) => + { + o.Examples = n.CreateMap(LoadExample, t); + } + }, + { + "example", (o, n, _) => + { + o.Example = n.CreateAny(); + } + }, + }; + + private static readonly PatternFieldMap _parameterPatternFields = + new() + { + {s => s.StartsWith("x-"), (o, p, n, _) => o.AddExtension(p, LoadExtension(p,n))} + }; + + private static readonly AnyFieldMap _parameterAnyFields = new AnyFieldMap + { + { + OpenApiConstants.Example, + new AnyFieldMapParameter( + s => s.Example, + (s, v) => s.Example = v, + s => s.Schema) + } + }; + + private static readonly AnyMapFieldMap _parameterAnyMapOpenApiExampleFields = + new AnyMapFieldMap + { + { + OpenApiConstants.Examples, + new AnyMapFieldMapParameter( + m => m.Examples, + e => e.Value, + (e, v) => e.Value = v, + m => m.Schema) + } + }; + + public static OpenApiParameter LoadParameter(ParseNode node, OpenApiDocument hostDocument = null) + { + var mapNode = node.CheckMapNode("parameter"); + + var pointer = mapNode.GetReferencePointer(); + if (pointer != null) + { + var reference = GetReferenceIdAndExternalResource(pointer); + return new OpenApiParameterReference(reference.Item1, hostDocument, reference.Item2); + } + + var parameter = new OpenApiParameter(); + + ParseMap(mapNode, parameter, _parameterFixedFields, _parameterPatternFields, hostDocument); + ProcessAnyFields(mapNode, parameter, _parameterAnyFields); + ProcessAnyMapFields(mapNode, parameter, _parameterAnyMapOpenApiExampleFields); + + return parameter; + } + } +} diff --git a/src/Microsoft.OpenApi/Reader/V31/OpenApiPathItemDeserializer.cs b/src/Microsoft.OpenApi/Reader/V31/OpenApiPathItemDeserializer.cs new file mode 100644 index 000000000..8797b03e6 --- /dev/null +++ b/src/Microsoft.OpenApi/Reader/V31/OpenApiPathItemDeserializer.cs @@ -0,0 +1,73 @@ +using System.Linq; +using Microsoft.OpenApi.Extensions; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Models.References; +using Microsoft.OpenApi.Reader.ParseNodes; + +namespace Microsoft.OpenApi.Reader.V31 +{ + /// + /// Class containing logic to deserialize Open API V31 document into + /// runtime Open API object model. + /// + internal static partial class OpenApiV31Deserializer + { + private static readonly FixedFieldMap _pathItemFixedFields = new() + { + + { + "$ref", (o,n, _) => { + o.Reference = new OpenApiReference() { ExternalResource = n.GetScalarValue() }; + o.UnresolvedReference =true; + } + }, + { + "summary", (o, n, _) => + { + o.Summary = n.GetScalarValue(); + } + }, + { + "description", (o, n, _) => + { + o.Description = n.GetScalarValue(); + } + }, + {"get", (o, n, t) => o.AddOperation(OperationType.Get, LoadOperation(n, t))}, + {"put", (o, n, t) => o.AddOperation(OperationType.Put, LoadOperation(n, t))}, + {"post", (o, n, t) => o.AddOperation(OperationType.Post, LoadOperation(n, t))}, + {"delete", (o, n, t) => o.AddOperation(OperationType.Delete, LoadOperation(n, t))}, + {"options", (o, n, t) => o.AddOperation(OperationType.Options, LoadOperation(n, t))}, + {"head", (o, n, t) => o.AddOperation(OperationType.Head, LoadOperation(n, t))}, + {"patch", (o, n, t) => o.AddOperation(OperationType.Patch, LoadOperation(n, t))}, + {"trace", (o, n, t) => o.AddOperation(OperationType.Trace, LoadOperation(n, t))}, + {"servers", (o, n, t) => o.Servers = n.CreateList(LoadServer, t)}, + {"parameters", (o, n, t) => o.Parameters = n.CreateList(LoadParameter, t)} + }; + + private static readonly PatternFieldMap _pathItemPatternFields = + new() + { + {s => s.StartsWith("x-"), (o, p, n, _) => o.AddExtension(p, LoadExtension(p,n))} + }; + + public static OpenApiPathItem LoadPathItem(ParseNode node, OpenApiDocument hostDocument = null) + { + var mapNode = node.CheckMapNode("PathItem"); + + var pointer = mapNode.GetReferencePointer(); + + if (pointer != null) + { + var reference = GetReferenceIdAndExternalResource(pointer); + return new OpenApiPathItemReference(reference.Item1, hostDocument, reference.Item2); + } + + var pathItem = new OpenApiPathItem(); + + ParseMap(mapNode, pathItem, _pathItemFixedFields, _pathItemPatternFields, hostDocument); + + return pathItem; + } + } +} diff --git a/src/Microsoft.OpenApi/Reader/V31/OpenApiPathsDeserializer.cs b/src/Microsoft.OpenApi/Reader/V31/OpenApiPathsDeserializer.cs new file mode 100644 index 000000000..e9fef44a8 --- /dev/null +++ b/src/Microsoft.OpenApi/Reader/V31/OpenApiPathsDeserializer.cs @@ -0,0 +1,32 @@ +using Microsoft.OpenApi.Extensions; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Reader.ParseNodes; + +namespace Microsoft.OpenApi.Reader.V31 +{ + /// + /// Class containing logic to deserialize Open API V31 document into + /// runtime Open API object model. + /// + internal static partial class OpenApiV31Deserializer + { + private static readonly FixedFieldMap _pathsFixedFields = new(); + + private static readonly PatternFieldMap _pathsPatternFields = new() + { + {s => s.StartsWith("/"), (o, k, n, t) => o.Add(k, LoadPathItem(n, t))}, + {s => s.StartsWith("x-"), (o, p, n, _) => o.AddExtension(p, LoadExtension(p,n))} + }; + + public static OpenApiPaths LoadPaths(ParseNode node, OpenApiDocument hostDocument = null) + { + var mapNode = node.CheckMapNode("Paths"); + + var domainObject = new OpenApiPaths(); + + ParseMap(mapNode, domainObject, _pathsFixedFields, _pathsPatternFields, hostDocument); + + return domainObject; + } + } +} diff --git a/src/Microsoft.OpenApi/Reader/V31/OpenApiRequestBodyDeserializer.cs b/src/Microsoft.OpenApi/Reader/V31/OpenApiRequestBodyDeserializer.cs new file mode 100644 index 000000000..7acea65c0 --- /dev/null +++ b/src/Microsoft.OpenApi/Reader/V31/OpenApiRequestBodyDeserializer.cs @@ -0,0 +1,64 @@ +using System.Linq; +using Microsoft.OpenApi.Extensions; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Models.References; +using Microsoft.OpenApi.Reader.ParseNodes; + +namespace Microsoft.OpenApi.Reader.V31 +{ + /// + /// Class containing logic to deserialize Open API V31 document into + /// runtime Open API object model. + /// + internal static partial class OpenApiV31Deserializer + { + private static readonly FixedFieldMap _requestBodyFixedFields = + new() + { + { + "description", (o, n, _) => + { + o.Description = n.GetScalarValue(); + } + }, + { + "content", (o, n, t) => + { + o.Content = n.CreateMap(LoadMediaType, t); + } + }, + { + "required", (o, n, _) => + { + o.Required = bool.Parse(n.GetScalarValue()); + } + }, + }; + + private static readonly PatternFieldMap _requestBodyPatternFields = + new() + { + {s => s.StartsWith("x-"), (o, p, n, _) => o.AddExtension(p, LoadExtension(p,n))} + }; + + public static OpenApiRequestBody LoadRequestBody(ParseNode node, OpenApiDocument hostDocument = null) + { + var mapNode = node.CheckMapNode("requestBody"); + + var pointer = mapNode.GetReferencePointer(); + if (pointer != null) + { + var reference = GetReferenceIdAndExternalResource(pointer); + return new OpenApiRequestBodyReference(reference.Item1, hostDocument, reference.Item2); + } + + var requestBody = new OpenApiRequestBody(); + foreach (var property in mapNode) + { + property.ParseField(requestBody, _requestBodyFixedFields, _requestBodyPatternFields); + } + + return requestBody; + } + } +} diff --git a/src/Microsoft.OpenApi/Reader/V31/OpenApiResponseDeserializer.cs b/src/Microsoft.OpenApi/Reader/V31/OpenApiResponseDeserializer.cs new file mode 100644 index 000000000..611574bf2 --- /dev/null +++ b/src/Microsoft.OpenApi/Reader/V31/OpenApiResponseDeserializer.cs @@ -0,0 +1,66 @@ +using System.Linq; +using Microsoft.OpenApi.Extensions; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Models.References; +using Microsoft.OpenApi.Reader.ParseNodes; + +namespace Microsoft.OpenApi.Reader.V31 +{ + /// + /// Class containing logic to deserialize Open API V3 document into + /// runtime Open API object model. + /// + internal static partial class OpenApiV31Deserializer + { + private static readonly FixedFieldMap _responseFixedFields = new() + { + { + "description", (o, n, _) => + { + o.Description = n.GetScalarValue(); + } + }, + { + "headers", (o, n, t) => + { + o.Headers = n.CreateMap(LoadHeader, t); + } + }, + { + "content", (o, n, t) => + { + o.Content = n.CreateMap(LoadMediaType, t); + } + }, + { + "links", (o, n, t) => + { + o.Links = n.CreateMap(LoadLink, t); + } + } + }; + + private static readonly PatternFieldMap _responsePatternFields = + new() + { + {s => s.StartsWith("x-"), (o, p, n, _) => o.AddExtension(p, LoadExtension(p,n))} + }; + + public static OpenApiResponse LoadResponse(ParseNode node, OpenApiDocument hostDocument = null) + { + var mapNode = node.CheckMapNode("response"); + + var pointer = mapNode.GetReferencePointer(); + if (pointer != null) + { + var reference = GetReferenceIdAndExternalResource(pointer); + return new OpenApiResponseReference(reference.Item1, hostDocument, reference.Item2); + } + + var response = new OpenApiResponse(); + ParseMap(mapNode, response, _responseFixedFields, _responsePatternFields, hostDocument); + + return response; + } + } +} diff --git a/src/Microsoft.OpenApi/Reader/V31/OpenApiResponsesDeserializer.cs b/src/Microsoft.OpenApi/Reader/V31/OpenApiResponsesDeserializer.cs new file mode 100644 index 000000000..42cb3b826 --- /dev/null +++ b/src/Microsoft.OpenApi/Reader/V31/OpenApiResponsesDeserializer.cs @@ -0,0 +1,35 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using Microsoft.OpenApi.Extensions; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Reader.ParseNodes; + +namespace Microsoft.OpenApi.Reader.V31 +{ + /// + /// Class containing logic to deserialize Open API V31 document into + /// runtime Open API object model. + /// + internal static partial class OpenApiV31Deserializer + { + public static readonly FixedFieldMap ResponsesFixedFields = new(); + + public static readonly PatternFieldMap ResponsesPatternFields = new() + { + {s => !s.StartsWith("x-"), (o, p, n, t) => o.Add(p, LoadResponse(n, t))}, + {s => s.StartsWith("x-"), (o, p, n, _) => o.AddExtension(p, LoadExtension(p,n))} + }; + + public static OpenApiResponses LoadResponses(ParseNode node, OpenApiDocument hostDocument = null) + { + var mapNode = node.CheckMapNode("Responses"); + + var domainObject = new OpenApiResponses(); + + ParseMap(mapNode, domainObject, ResponsesFixedFields, ResponsesPatternFields, hostDocument); + + return domainObject; + } + } +} diff --git a/src/Microsoft.OpenApi/Reader/V31/OpenApiSchemaDeserializer.cs b/src/Microsoft.OpenApi/Reader/V31/OpenApiSchemaDeserializer.cs new file mode 100644 index 000000000..5dc76b7fb --- /dev/null +++ b/src/Microsoft.OpenApi/Reader/V31/OpenApiSchemaDeserializer.cs @@ -0,0 +1,266 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using Microsoft.OpenApi.Extensions; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Models.References; +using Microsoft.OpenApi.Reader.ParseNodes; +using System.Collections.Generic; +using System.Globalization; + +namespace Microsoft.OpenApi.Reader.V31 +{ + internal static partial class OpenApiV31Deserializer + { + private static readonly FixedFieldMap _openApiSchemaFixedFields = new() + { + { + "title", + (o, n, _) => o.Title = n.GetScalarValue() + }, + { + "$schema", + (o, n, _) => o.Schema = n.GetScalarValue() + }, + { + "$id", + (o, n, _) => o.Id = n.GetScalarValue() + }, + { + "$comment", + (o, n, _) => o.Comment = n.GetScalarValue() + }, + { + "$vocabulary", + (o, n, _) => o.Vocabulary = n.CreateSimpleMap(LoadBool) + }, + { + "$dynamicRef", + (o, n, _) => o.DynamicRef = n.GetScalarValue() + }, + { + "$dynamicAnchor", + (o, n, _) => o.DynamicAnchor = n.GetScalarValue() + }, + { + "$defs", + (o, n, t) => o.Definitions = n.CreateMap(LoadSchema, t) + }, + { + "multipleOf", + (o, n, _) => o.MultipleOf = decimal.Parse(n.GetScalarValue(), NumberStyles.Float, CultureInfo.InvariantCulture) + }, + { + "maximum", + (o, n, _) => o.Maximum = ParserHelper.ParseDecimalWithFallbackOnOverflow(n.GetScalarValue(), decimal.MaxValue) + }, + { + "exclusiveMaximum", + (o, n, _) => o.V31ExclusiveMaximum = ParserHelper.ParseDecimalWithFallbackOnOverflow(n.GetScalarValue(), decimal.MaxValue) + }, + { + "minimum", + (o, n, _) => o.Minimum = ParserHelper.ParseDecimalWithFallbackOnOverflow(n.GetScalarValue(), decimal.MinValue) + }, + { + "exclusiveMinimum", + (o, n, _) => o.V31ExclusiveMinimum = ParserHelper.ParseDecimalWithFallbackOnOverflow(n.GetScalarValue(), decimal.MaxValue) + }, + { + "maxLength", + (o, n, _) => o.MaxLength = int.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture) + }, + { + "minLength", + (o, n, _) => o.MinLength = int.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture) + }, + { + "pattern", + (o, n, _) => o.Pattern = n.GetScalarValue() + }, + { + "maxItems", + (o, n, _) => o.MaxItems = int.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture) + }, + { + "minItems", + (o, n, _) => o.MinItems = int.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture) + }, + { + "uniqueItems", + (o, n, _) => o.UniqueItems = bool.Parse(n.GetScalarValue()) + }, + { + "unevaluatedProperties", + (o, n, _) => o.UnevaluatedProperties = bool.Parse(n.GetScalarValue()) + }, + { + "maxProperties", + (o, n, _) => o.MaxProperties = int.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture) + }, + { + "minProperties", + (o, n, _) => o.MinProperties = int.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture) + }, + { + "required", + (o, n, _) => o.Required = new HashSet(n.CreateSimpleList((n2, p) => n2.GetScalarValue())) + }, + { + "enum", + (o, n, _) => o.Enum = n.CreateListOfAny() + }, + { + "type", + (o, n, _) => + { + if (n is ValueNode) + { + o.Type = n.GetScalarValue().ToJsonSchemaType(); + } + else + { + var list = n.CreateSimpleList((n2, p) => n2.GetScalarValue()); + JsonSchemaType combinedType = 0; + foreach(var type in list) + { + var schemaType = type.ToJsonSchemaType(); + combinedType |= schemaType; + } + o.Type = combinedType; + } + } + }, + { + "allOf", + (o, n, t) => o.AllOf = n.CreateList(LoadSchema, t) + }, + { + "oneOf", + (o, n, t) => o.OneOf = n.CreateList(LoadSchema, t) + }, + { + "anyOf", + (o, n, t) => o.AnyOf = n.CreateList(LoadSchema, t) + }, + { + "not", + (o, n, _) => o.Not = LoadSchema(n) + }, + { + "items", + (o, n, _) => o.Items = LoadSchema(n) + }, + { + "properties", + (o, n, t) => o.Properties = n.CreateMap(LoadSchema, t) + }, + { + "patternProperties", + (o, n, t) => o.PatternProperties = n.CreateMap(LoadSchema, t) + }, + { + "additionalProperties", (o, n, _) => + { + if (n is ValueNode) + { + o.AdditionalPropertiesAllowed = bool.Parse(n.GetScalarValue()); + } + else + { + o.AdditionalProperties = LoadSchema(n); + } + } + }, + { + "description", + (o, n, _) => o.Description = n.GetScalarValue() + }, + { + "format", + (o, n, _) => o.Format = n.GetScalarValue() + }, + { + "default", + (o, n, _) => o.Default = n.CreateAny() + }, + { + "nullable", + (o, n, _) => + { + var nullable = bool.Parse(n.GetScalarValue()); + if (nullable) // if nullable, convert type into an array of type(s) and null + { + o.Type |= JsonSchemaType.Null; + } + } + }, + { + "discriminator", + (o, n, _) => o.Discriminator = LoadDiscriminator(n) + }, + { + "readOnly", + (o, n, _) => o.ReadOnly = bool.Parse(n.GetScalarValue()) + }, + { + "writeOnly", + (o, n, _) => o.WriteOnly = bool.Parse(n.GetScalarValue()) + }, + { + "xml", + (o, n, _) => o.Xml = LoadXml(n) + }, + { + "externalDocs", + (o, n, _) => o.ExternalDocs = LoadExternalDocs(n) + }, + { + "example", + (o, n, _) => o.Example = n.CreateAny() + }, + { + "examples", + (o, n, _) => o.Examples = n.CreateListOfAny() + }, + { + "deprecated", + (o, n, _) => o.Deprecated = bool.Parse(n.GetScalarValue()) + }, + }; + + private static readonly PatternFieldMap _openApiSchemaPatternFields = new() + { + {s => s.StartsWith("x-"), (o, p, n, _) => o.AddExtension(p, LoadExtension(p,n))} + }; + + public static OpenApiSchema LoadSchema(ParseNode node, OpenApiDocument hostDocument = null) + { + var mapNode = node.CheckMapNode(OpenApiConstants.Schema); + + var pointer = mapNode.GetReferencePointer(); + + if (pointer != null) + { + var reference = GetReferenceIdAndExternalResource(pointer); + return new OpenApiSchemaReference(reference.Item1, hostDocument, reference.Item2); + } + + var schema = new OpenApiSchema(); + + foreach (var propertyNode in mapNode) + { + propertyNode.ParseField(schema, _openApiSchemaFixedFields, _openApiSchemaPatternFields); + } + + if (schema.Extensions.ContainsKey(OpenApiConstants.NullableExtension)) + { + var type = schema.Type; + schema.Type = type | JsonSchemaType.Null; + schema.Extensions.Remove(OpenApiConstants.NullableExtension); + } + + return schema; + } + } +} diff --git a/src/Microsoft.OpenApi/Reader/V31/OpenApiSecurityRequirementDeserializer.cs b/src/Microsoft.OpenApi/Reader/V31/OpenApiSecurityRequirementDeserializer.cs new file mode 100644 index 000000000..94753dafa --- /dev/null +++ b/src/Microsoft.OpenApi/Reader/V31/OpenApiSecurityRequirementDeserializer.cs @@ -0,0 +1,48 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Models.References; +using Microsoft.OpenApi.Reader.ParseNodes; + +namespace Microsoft.OpenApi.Reader.V31 +{ + /// + /// Class containing logic to deserialize Open API V31 document into + /// runtime Open API object model. + /// + internal static partial class OpenApiV31Deserializer + { + public static OpenApiSecurityRequirement LoadSecurityRequirement(ParseNode node, OpenApiDocument hostDocument = null) + { + var mapNode = node.CheckMapNode("security"); + + var securityRequirement = new OpenApiSecurityRequirement(); + + foreach (var property in mapNode) + { + var scheme = LoadSecuritySchemeByReference(property.Name, hostDocument); + + var scopes = property.Value.CreateSimpleList((value, p) => value.GetScalarValue()); + + if (scheme != null) + { + securityRequirement.Add(scheme, scopes); + } + else + { + mapNode.Context.Diagnostic.Errors.Add( + new OpenApiError(node.Context.GetLocation(), $"Scheme {property.Name} is not found")); + } + } + + return securityRequirement; + } + + private static OpenApiSecurityScheme LoadSecuritySchemeByReference(string schemeName, OpenApiDocument hostDocument) + { + var securitySchemeObject = new OpenApiSecuritySchemeReference(schemeName, hostDocument); + return securitySchemeObject; + } + } +} diff --git a/src/Microsoft.OpenApi/Reader/V31/OpenApiSecuritySchemeDeserializer.cs b/src/Microsoft.OpenApi/Reader/V31/OpenApiSecuritySchemeDeserializer.cs new file mode 100644 index 000000000..7b5ff5cb8 --- /dev/null +++ b/src/Microsoft.OpenApi/Reader/V31/OpenApiSecuritySchemeDeserializer.cs @@ -0,0 +1,98 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System; +using System.Linq; +using Microsoft.OpenApi.Extensions; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Models.References; +using Microsoft.OpenApi.Reader.ParseNodes; + +namespace Microsoft.OpenApi.Reader.V31 +{ + /// + /// Class containing logic to deserialize Open API V31 document into + /// runtime Open API object model. + /// + internal static partial class OpenApiV31Deserializer + { + private static readonly FixedFieldMap _securitySchemeFixedFields = + new() + { + { + "type", (o, n, _) => + { + o.Type = n.GetScalarValue().GetEnumFromDisplayName(); + } + }, + { + "description", (o, n, _) => + { + o.Description = n.GetScalarValue(); + } + }, + { + "name", (o, n, _) => + { + o.Name = n.GetScalarValue(); + } + }, + { + "in", (o, n, _) => + { + o.In = n.GetScalarValue().GetEnumFromDisplayName(); + } + }, + { + "scheme", (o, n, _) => + { + o.Scheme = n.GetScalarValue(); + } + }, + { + "bearerFormat", (o, n, _) => + { + o.BearerFormat = n.GetScalarValue(); + } + }, + { + "openIdConnectUrl", (o, n, _) => + { + o.OpenIdConnectUrl = new Uri(n.GetScalarValue(), UriKind.RelativeOrAbsolute); + } + }, + { + "flows", (o, n, t) => + { + o.Flows = LoadOAuthFlows(n, t); + } + } + }; + + private static readonly PatternFieldMap _securitySchemePatternFields = + new() + { + {s => s.StartsWith("x-"), (o, p, n, _) => o.AddExtension(p, LoadExtension(p,n))} + }; + + public static OpenApiSecurityScheme LoadSecurityScheme(ParseNode node, OpenApiDocument hostDocument = null) + { + var mapNode = node.CheckMapNode("securityScheme"); + + var pointer = mapNode.GetReferencePointer(); + if (pointer != null) + { + var reference = GetReferenceIdAndExternalResource(pointer); + return new OpenApiSecuritySchemeReference(reference.Item1, hostDocument, reference.Item2); + } + + var securityScheme = new OpenApiSecurityScheme(); + foreach (var property in mapNode) + { + property.ParseField(securityScheme, _securitySchemeFixedFields, _securitySchemePatternFields); + } + + return securityScheme; + } + } +} diff --git a/src/Microsoft.OpenApi/Reader/V31/OpenApiServerDeserializer.cs b/src/Microsoft.OpenApi/Reader/V31/OpenApiServerDeserializer.cs new file mode 100644 index 000000000..efe25fedb --- /dev/null +++ b/src/Microsoft.OpenApi/Reader/V31/OpenApiServerDeserializer.cs @@ -0,0 +1,54 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using Microsoft.OpenApi.Extensions; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Reader.ParseNodes; + +namespace Microsoft.OpenApi.Reader.V31 +{ + /// + /// Class containing logic to deserialize Open API V31 document into + /// runtime Open API object model. + /// + internal static partial class OpenApiV31Deserializer + { + private static readonly FixedFieldMap _serverFixedFields = new() + { + { + "url", (o, n, _) => + { + o.Url = n.GetScalarValue(); + } + }, + { + "description", (o, n, _) => + { + o.Description = n.GetScalarValue(); + } + }, + { + "variables", (o, n, t) => + { + o.Variables = n.CreateMap(LoadServerVariable, t); + } + } + }; + + private static readonly PatternFieldMap _serverPatternFields = new() + { + {s => s.StartsWith("x-"), (o, p, n, _) => o.AddExtension(p, LoadExtension(p,n))} + }; + + public static OpenApiServer LoadServer(ParseNode node, OpenApiDocument hostDocument = null) + { + var mapNode = node.CheckMapNode("server"); + + var server = new OpenApiServer(); + + ParseMap(mapNode, server, _serverFixedFields, _serverPatternFields, hostDocument); + + return server; + } + } +} diff --git a/src/Microsoft.OpenApi/Reader/V31/OpenApiServerVariableDeserializer.cs b/src/Microsoft.OpenApi/Reader/V31/OpenApiServerVariableDeserializer.cs new file mode 100644 index 000000000..e5344554d --- /dev/null +++ b/src/Microsoft.OpenApi/Reader/V31/OpenApiServerVariableDeserializer.cs @@ -0,0 +1,56 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using Microsoft.OpenApi.Extensions; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Reader.ParseNodes; + +namespace Microsoft.OpenApi.Reader.V31 +{ + /// + /// Class containing logic to deserialize Open API V31 document into + /// runtime Open API object model. + /// + internal static partial class OpenApiV31Deserializer + { + private static readonly FixedFieldMap _serverVariableFixedFields = + new() + { + { + "enum", (o, n, _) => + { + o.Enum = n.CreateSimpleList((s, p) => s.GetScalarValue()); + } + }, + { + "default", (o, n, _) => + { + o.Default = n.GetScalarValue(); + } + }, + { + "description", (o, n, _) => + { + o.Description = n.GetScalarValue(); + } + }, + }; + + private static readonly PatternFieldMap _serverVariablePatternFields = + new() + { + {s => s.StartsWith("x-"), (o, p, n, _) => o.AddExtension(p, LoadExtension(p,n))} + }; + + public static OpenApiServerVariable LoadServerVariable(ParseNode node, OpenApiDocument hostDocument = null) + { + var mapNode = node.CheckMapNode("serverVariable"); + + var serverVariable = new OpenApiServerVariable(); + + ParseMap(mapNode, serverVariable, _serverVariableFixedFields, _serverVariablePatternFields, hostDocument); + + return serverVariable; + } + } +} diff --git a/src/Microsoft.OpenApi/Reader/V31/OpenApiTagDeserializer.cs b/src/Microsoft.OpenApi/Reader/V31/OpenApiTagDeserializer.cs new file mode 100644 index 000000000..a6dfe5f1f --- /dev/null +++ b/src/Microsoft.OpenApi/Reader/V31/OpenApiTagDeserializer.cs @@ -0,0 +1,57 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using Microsoft.OpenApi.Extensions; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Reader.ParseNodes; + +namespace Microsoft.OpenApi.Reader.V31 +{ + /// + /// Class containing logic to deserialize Open API V31 document into + /// runtime Open API object model. + /// + internal static partial class OpenApiV31Deserializer + { + private static readonly FixedFieldMap _tagFixedFields = new() + { + { + OpenApiConstants.Name, (o, n, _) => + { + o.Name = n.GetScalarValue(); + } + }, + { + OpenApiConstants.Description, (o, n, _) => + { + o.Description = n.GetScalarValue(); + } + }, + { + OpenApiConstants.ExternalDocs, (o, n, t) => + { + o.ExternalDocs = LoadExternalDocs(n, t); + } + } + }; + + private static readonly PatternFieldMap _tagPatternFields = new() + { + {s => s.StartsWith("x-"), (o, p, n, _) => o.AddExtension(p, LoadExtension(p,n))} + }; + + public static OpenApiTag LoadTag(ParseNode n, OpenApiDocument hostDocument = null) + { + var mapNode = n.CheckMapNode("tag"); + + var domainObject = new OpenApiTag(); + + foreach (var propertyNode in mapNode) + { + propertyNode.ParseField(domainObject, _tagFixedFields, _tagPatternFields); + } + + return domainObject; + } + } +} diff --git a/src/Microsoft.OpenApi/Reader/V31/OpenApiV31Deserializer.cs b/src/Microsoft.OpenApi/Reader/V31/OpenApiV31Deserializer.cs new file mode 100644 index 000000000..a037dc3c1 --- /dev/null +++ b/src/Microsoft.OpenApi/Reader/V31/OpenApiV31Deserializer.cs @@ -0,0 +1,174 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System.Collections.Generic; +using System.Linq; +using System.Text.Json.Nodes; +using Microsoft.OpenApi.Any; +using Microsoft.OpenApi.Exceptions; +using Microsoft.OpenApi.Expressions; +using Microsoft.OpenApi.Interfaces; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Reader.ParseNodes; + +namespace Microsoft.OpenApi.Reader.V31 +{ + /// + /// Class containing logic to deserialize Open API V31 document into + /// runtime Open API object model. + /// + internal static partial class OpenApiV31Deserializer + { + private static void ParseMap( + MapNode mapNode, + T domainObject, + FixedFieldMap fixedFieldMap, + PatternFieldMap patternFieldMap, + OpenApiDocument doc = null) + { + if (mapNode == null) + { + return; + } + + foreach (var propertyNode in mapNode) + { + propertyNode.ParseField(domainObject, fixedFieldMap, patternFieldMap, doc); + } + + } + + private static void ProcessAnyFields( + MapNode mapNode, + T domainObject, + AnyFieldMap anyFieldMap) + { + foreach (var anyFieldName in anyFieldMap.Keys.ToList()) + { + try + { + mapNode.Context.StartObject(anyFieldName); + + var any = anyFieldMap[anyFieldName].PropertyGetter(domainObject); + + if (any == null) + { + anyFieldMap[anyFieldName].PropertySetter(domainObject, null); + } + else + { + anyFieldMap[anyFieldName].PropertySetter(domainObject, any); + } + } + catch (OpenApiException exception) + { + exception.Pointer = mapNode.Context.GetLocation(); + mapNode.Context.Diagnostic.Errors.Add(new OpenApiError(exception)); + } + finally + { + mapNode.Context.EndObject(); + } + } + } + + private static void ProcessAnyMapFields( + MapNode mapNode, + T domainObject, + AnyMapFieldMap anyMapFieldMap) + { + foreach (var anyMapFieldName in anyMapFieldMap.Keys.ToList()) + { + try + { + mapNode.Context.StartObject(anyMapFieldName); + var propertyMapGetter = anyMapFieldMap[anyMapFieldName].PropertyMapGetter(domainObject); + if (propertyMapGetter != null) + { + foreach (var propertyMapElement in propertyMapGetter) + { + mapNode.Context.StartObject(propertyMapElement.Key); + + if (propertyMapElement.Value != null) + { + var any = anyMapFieldMap[anyMapFieldName].PropertyGetter(propertyMapElement.Value); + + anyMapFieldMap[anyMapFieldName].PropertySetter(propertyMapElement.Value, any); + } + } + } + } + catch (OpenApiException exception) + { + exception.Pointer = mapNode.Context.GetLocation(); + mapNode.Context.Diagnostic.Errors.Add(new OpenApiError(exception)); + } + finally + { + mapNode.Context.EndObject(); + } + } + } + + private static RuntimeExpressionAnyWrapper LoadRuntimeExpressionAnyWrapper(ParseNode node) + { + var value = node.GetScalarValue(); + + if (value != null && value.StartsWith("$")) + { + return new RuntimeExpressionAnyWrapper + { + Expression = RuntimeExpression.Build(value) + }; + } + + return new RuntimeExpressionAnyWrapper + { + Any = node.CreateAny() + }; + } + + public static JsonNode LoadAny(ParseNode node, OpenApiDocument hostDocument = null) + { + return node.CreateAny(); + } + + private static IOpenApiExtension LoadExtension(string name, ParseNode node) + { + return node.Context.ExtensionParsers.TryGetValue(name, out var parser) + ? parser(node.CreateAny(), OpenApiSpecVersion.OpenApi3_1) + : new OpenApiAny(node.CreateAny()); + } + + private static string LoadString(ParseNode node) + { + return node.GetScalarValue(); + } + + private static bool LoadBool(ParseNode node) + { + return bool.Parse(node.GetScalarValue()); + } + + private static (string, string) GetReferenceIdAndExternalResource(string pointer) + { + /* Check whether the reference pointer is a URL + * (id keyword allows you to supply a URL for the schema as a target for referencing) + * E.g. $ref: 'https://example.com/schemas/resource.json' + * or its a normal json pointer fragment syntax + * E.g. $ref: '#/components/schemas/pet' + */ + var refSegments = pointer.Split('/'); + string refId = !pointer.Contains('#') ? pointer : refSegments.Last(); + + var isExternalResource = !refSegments.First().StartsWith("#"); + string externalResource = null; + if (isExternalResource && pointer.Contains('#')) + { + externalResource = $"{refSegments.First()}/{refSegments[1].TrimEnd('#')}"; + } + + return (refId, externalResource); + } + } +} diff --git a/src/Microsoft.OpenApi/Reader/V31/OpenApiV31VersionService.cs b/src/Microsoft.OpenApi/Reader/V31/OpenApiV31VersionService.cs new file mode 100644 index 000000000..333ec53bb --- /dev/null +++ b/src/Microsoft.OpenApi/Reader/V31/OpenApiV31VersionService.cs @@ -0,0 +1,212 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.OpenApi.Any; +using Microsoft.OpenApi.Exceptions; +using Microsoft.OpenApi.Extensions; +using Microsoft.OpenApi.Interfaces; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Properties; +using Microsoft.OpenApi.Reader.ParseNodes; +using Microsoft.OpenApi.Reader.V3; + +namespace Microsoft.OpenApi.Reader.V31 +{ + /// + /// The version service for the Open API V3.1. + /// + internal class OpenApiV31VersionService : IOpenApiVersionService + { + public OpenApiDiagnostic Diagnostic { get; } + + /// + /// Create Parsing Context + /// + /// Provide instance for diagnotic object for collecting and accessing information about the parsing. + public OpenApiV31VersionService(OpenApiDiagnostic diagnostic) + { + Diagnostic = diagnostic; + } + + private readonly IDictionary> _loaders = new Dictionary> + { + [typeof(OpenApiAny)] = OpenApiV31Deserializer.LoadAny, + [typeof(OpenApiCallback)] = OpenApiV31Deserializer.LoadCallback, + [typeof(OpenApiComponents)] = OpenApiV31Deserializer.LoadComponents, + [typeof(OpenApiContact)] = OpenApiV31Deserializer.LoadContact, + [typeof(OpenApiDiscriminator)] = OpenApiV3Deserializer.LoadDiscriminator, + [typeof(OpenApiEncoding)] = OpenApiV31Deserializer.LoadEncoding, + [typeof(OpenApiExample)] = OpenApiV31Deserializer.LoadExample, + [typeof(OpenApiExternalDocs)] = OpenApiV31Deserializer.LoadExternalDocs, + [typeof(OpenApiHeader)] = OpenApiV31Deserializer.LoadHeader, + [typeof(OpenApiInfo)] = OpenApiV31Deserializer.LoadInfo, + [typeof(OpenApiLicense)] = OpenApiV31Deserializer.LoadLicense, + [typeof(OpenApiLink)] = OpenApiV31Deserializer.LoadLink, + [typeof(OpenApiMediaType)] = OpenApiV31Deserializer.LoadMediaType, + [typeof(OpenApiOAuthFlow)] = OpenApiV31Deserializer.LoadOAuthFlow, + [typeof(OpenApiOAuthFlows)] = OpenApiV31Deserializer.LoadOAuthFlows, + [typeof(OpenApiOperation)] = OpenApiV31Deserializer.LoadOperation, + [typeof(OpenApiParameter)] = OpenApiV31Deserializer.LoadParameter, + [typeof(OpenApiPathItem)] = OpenApiV31Deserializer.LoadPathItem, + [typeof(OpenApiPaths)] = OpenApiV31Deserializer.LoadPaths, + [typeof(OpenApiRequestBody)] = OpenApiV31Deserializer.LoadRequestBody, + [typeof(OpenApiResponse)] = OpenApiV31Deserializer.LoadResponse, + [typeof(OpenApiResponses)] = OpenApiV31Deserializer.LoadResponses, + [typeof(OpenApiSchema)] = OpenApiV31Deserializer.LoadSchema, + [typeof(OpenApiSecurityRequirement)] = OpenApiV31Deserializer.LoadSecurityRequirement, + [typeof(OpenApiSecurityScheme)] = OpenApiV31Deserializer.LoadSecurityScheme, + [typeof(OpenApiServer)] = OpenApiV31Deserializer.LoadServer, + [typeof(OpenApiServerVariable)] = OpenApiV31Deserializer.LoadServerVariable, + [typeof(OpenApiTag)] = OpenApiV31Deserializer.LoadTag, + [typeof(OpenApiXml)] = OpenApiV31Deserializer.LoadXml + }; + + /// + /// Parse the string to a object. + /// + /// The URL of the reference + /// The type of object refefenced based on the context of the reference + /// The summary of the reference + /// A reference description + public OpenApiReference ConvertToOpenApiReference( + string reference, + ReferenceType? type, + string summary = null, + string description = null) + { + if (!string.IsNullOrWhiteSpace(reference)) + { + var segments = reference.Split('#'); + if (segments.Length == 1) + { + if (type == ReferenceType.Tag || type == ReferenceType.SecurityScheme) + { + return new OpenApiReference + { + Summary = summary, + Description = description, + Type = type, + Id = reference + }; + } + + // Either this is an external reference as an entire file + // or a simple string-style reference for tag and security scheme. + return new OpenApiReference + { + Summary = summary, + Description = description, + Type = type, + ExternalResource = segments[0] + }; + } + else if (segments.Length == 2) + { + if (reference.StartsWith("#")) + { + // "$ref": "#/components/schemas/Pet" + try + { + return ParseLocalReference(segments[1], summary, description); + } + catch (OpenApiException ex) + { + Diagnostic.Errors.Add(new OpenApiError(ex)); + return null; + } + } + // Where fragments point into a non-OpenAPI document, the id will be the complete fragment identifier + string id = segments[1]; + // $ref: externalSource.yaml#/Pet + if (id.StartsWith("/components/")) + { + var localSegments = segments[1].Split('/'); + var referencedType = localSegments[2].GetEnumFromDisplayName(); + if (type == null) + { + type = referencedType; + } + else + { + if (type != referencedType) + { + throw new OpenApiException("Referenced type mismatch"); + } + } + id = localSegments[3]; + } + + return new OpenApiReference + { + Summary = summary, + Description = description, + ExternalResource = segments[0], + Type = type, + Id = id + }; + } + } + + throw new OpenApiException(string.Format(SRResource.ReferenceHasInvalidFormat, reference)); + } + + public OpenApiDocument LoadDocument(RootNode rootNode) + { + return OpenApiV31Deserializer.LoadOpenApi(rootNode); + } + + public T LoadElement(ParseNode node, OpenApiDocument doc) where T : IOpenApiElement + { + return (T)_loaders[typeof(T)](node, doc); + } + + /// + public string GetReferenceScalarValues(MapNode mapNode, string scalarValue) + { + if (mapNode.Any(static x => !"$ref".Equals(x.Name, StringComparison.OrdinalIgnoreCase))) + { + var valueNode = mapNode.Where(x => x.Name.Equals(scalarValue)) + .Select(static x => x.Value).OfType().FirstOrDefault(); + + return valueNode?.GetScalarValue(); + } + + return null; + } + + private OpenApiReference ParseLocalReference(string localReference, string summary = null, string description = null) + { + if (string.IsNullOrWhiteSpace(localReference)) + { + throw new ArgumentException(string.Format(SRResource.ArgumentNullOrWhiteSpace, nameof(localReference))); + } + + var segments = localReference.Split('/'); + + if (segments.Length == 4 && segments[1] == "components") // /components/{type}/pet + { + var referenceType = segments[2].GetEnumFromDisplayName(); + var refId = segments[3]; + if (segments[2] == "pathItems") + { + refId = "/" + segments[3]; + } + + var parsedReference = new OpenApiReference + { + Summary = summary, + Description = description, + Type = referenceType, + Id = refId + }; + + return parsedReference; + } + + throw new OpenApiException(string.Format(SRResource.ReferenceHasInvalidFormat, localReference)); + } + } +} diff --git a/src/Microsoft.OpenApi/Reader/V31/OpenApiXmlDeserializer.cs b/src/Microsoft.OpenApi/Reader/V31/OpenApiXmlDeserializer.cs new file mode 100644 index 000000000..4c7a17b85 --- /dev/null +++ b/src/Microsoft.OpenApi/Reader/V31/OpenApiXmlDeserializer.cs @@ -0,0 +1,70 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System; +using Microsoft.OpenApi.Extensions; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Reader.ParseNodes; + +namespace Microsoft.OpenApi.Reader.V31 +{ + /// + /// Class containing logic to deserialize Open API V31 document into + /// runtime Open API object model. + /// + internal static partial class OpenApiV31Deserializer + { + private static readonly FixedFieldMap _xmlFixedFields = new FixedFieldMap + { + { + "name", (o, n, _) => + { + o.Name = n.GetScalarValue(); + } + }, + { + "namespace", (o, n, _) => + { + o.Namespace = new Uri(n.GetScalarValue(), UriKind.Absolute); + } + }, + { + "prefix", (o, n, _) => + { + o.Prefix = n.GetScalarValue(); + } + }, + { + "attribute", (o, n, _) => + { + o.Attribute = bool.Parse(n.GetScalarValue()); + } + }, + { + "wrapped", (o, n, _) => + { + o.Wrapped = bool.Parse(n.GetScalarValue()); + } + }, + }; + + private static readonly PatternFieldMap _xmlPatternFields = + new PatternFieldMap + { + {s => s.StartsWith("x-"), (o, p, n, _) => o.AddExtension(p, LoadExtension(p,n))} + }; + + public static OpenApiXml LoadXml(ParseNode node, OpenApiDocument hostDocument = null) + { + var mapNode = node.CheckMapNode("xml"); + + var xml = new OpenApiXml(); + foreach (var property in mapNode) + { + property.ParseField(xml, _xmlFixedFields, _xmlPatternFields); + } + + return xml; + } + } +} diff --git a/src/Microsoft.OpenApi/Services/OpenApiFilterService.cs b/src/Microsoft.OpenApi/Services/OpenApiFilterService.cs index bd351ce0e..22916fd7c 100644 --- a/src/Microsoft.OpenApi/Services/OpenApiFilterService.cs +++ b/src/Microsoft.OpenApi/Services/OpenApiFilterService.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. using System; diff --git a/src/Microsoft.OpenApi/Services/OpenApiReferenceResolver.cs b/src/Microsoft.OpenApi/Services/OpenApiReferenceResolver.cs deleted file mode 100644 index 00c069f30..000000000 --- a/src/Microsoft.OpenApi/Services/OpenApiReferenceResolver.cs +++ /dev/null @@ -1,313 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. - -using System; -using System.Collections.Generic; -using System.Linq; -using Microsoft.OpenApi.Exceptions; -using Microsoft.OpenApi.Interfaces; -using Microsoft.OpenApi.Models; - -namespace Microsoft.OpenApi.Services -{ - /// - /// This class is used to walk an OpenApiDocument and convert unresolved references to references to populated objects - /// - public class OpenApiReferenceResolver : OpenApiVisitorBase - { - private OpenApiDocument _currentDocument; - private readonly bool _resolveRemoteReferences; - private List _errors = new(); - - /// - /// Initializes the class. - /// - public OpenApiReferenceResolver(OpenApiDocument currentDocument, bool resolveRemoteReferences = true) - { - _currentDocument = currentDocument; - _resolveRemoteReferences = resolveRemoteReferences; - } - - /// - /// List of errors related to the OpenApiDocument - /// - public IEnumerable Errors => _errors; - - /// - /// Resolves tags in OpenApiDocument - /// - /// - public override void Visit(OpenApiDocument doc) - { - if (doc.Tags != null) - { - ResolveTags(doc.Tags); - } - } - - /// - /// Visits the referenceable element in the host document - /// - /// The referenceable element in the doc. - public override void Visit(IOpenApiReferenceable referenceable) - { - if (referenceable.Reference != null) - { - referenceable.Reference.HostDocument = _currentDocument; - } - } - - /// - /// Resolves references in components - /// - /// - public override void Visit(OpenApiComponents components) - { - ResolveMap(components.Parameters); - ResolveMap(components.RequestBodies); - ResolveMap(components.Responses); - ResolveMap(components.Links); - ResolveMap(components.Callbacks); - ResolveMap(components.Examples); - ResolveMap(components.Schemas); - ResolveMap(components.SecuritySchemes); - ResolveMap(components.Headers); - } - - /// - /// Resolves all references used in callbacks - /// - /// - public override void Visit(IDictionary callbacks) - { - ResolveMap(callbacks); - } - - /// - /// Resolve all references used in an operation - /// - public override void Visit(OpenApiOperation operation) - { - ResolveObject(operation.RequestBody, r => operation.RequestBody = r); - ResolveList(operation.Parameters); - - if (operation.Tags != null) - { - ResolveTags(operation.Tags); - } - } - - /// - /// Resolve all references using in mediaType object - /// - /// - public override void Visit(OpenApiMediaType mediaType) - { - ResolveObject(mediaType.Schema, r => mediaType.Schema = r); - } - - /// - /// Resolve all references to examples - /// - /// - public override void Visit(IDictionary examples) - { - ResolveMap(examples); - } - - /// - /// Resolve all references to responses - /// - public override void Visit(OpenApiResponses responses) - { - ResolveMap(responses); - } - - /// - /// Resolve all references to headers - /// - /// - public override void Visit(IDictionary headers) - { - ResolveMap(headers); - } - - /// - /// Resolve all references to SecuritySchemes - /// - public override void Visit(OpenApiSecurityRequirement securityRequirement) - { - foreach (var scheme in securityRequirement.Keys.ToList()) - { - ResolveObject(scheme, (resolvedScheme) => - { - if (resolvedScheme != null) - { - // If scheme was unresolved - // copy Scopes and remove old unresolved scheme - var scopes = securityRequirement[scheme]; - securityRequirement.Remove(scheme); - securityRequirement.Add(resolvedScheme, scopes); - } - }); - } - } - - /// - /// Resolve all references to parameters - /// - public override void Visit(IList parameters) - { - ResolveList(parameters); - } - - /// - /// Resolve all references used in a parameter - /// - public override void Visit(OpenApiParameter parameter) - { - ResolveObject(parameter.Schema, r => parameter.Schema = r); - ResolveMap(parameter.Examples); - } - - /// - /// Resolve all references to links - /// - public override void Visit(IDictionary links) - { - ResolveMap(links); - } - - /// - /// Resolve all references used in a schema - /// - public override void Visit(OpenApiSchema schema) - { - ResolveObject(schema.Items, r => schema.Items = r); - ResolveList(schema.OneOf); - ResolveList(schema.AllOf); - ResolveList(schema.AnyOf); - ResolveMap(schema.Properties); - ResolveObject(schema.AdditionalProperties, r => schema.AdditionalProperties = r); - } - - /// - /// Replace references to tags with either tag objects declared in components, or inline tag object - /// - private void ResolveTags(IList tags) - { - for (var i = 0; i < tags.Count; i++) - { - var tag = tags[i]; - if (IsUnresolvedReference(tag)) - { - var resolvedTag = ResolveReference(tag.Reference); - - if (resolvedTag == null) - { - resolvedTag = new() - { - Name = tag.Reference.Id - }; - } - tags[i] = resolvedTag; - } - } - } - - private void ResolveObject(T entity, Action assign) where T : class, IOpenApiReferenceable, new() - { - if (entity == null) return; - - if (IsUnresolvedReference(entity)) - { - assign(ResolveReference(entity.Reference)); - } - } - - private void ResolveList(IList list) where T : class, IOpenApiReferenceable, new() - { - if (list == null) return; - - for (var i = 0; i < list.Count; i++) - { - var entity = list[i]; - if (IsUnresolvedReference(entity)) - { - list[i] = ResolveReference(entity.Reference); - } - } - } - - private void ResolveMap(IDictionary map) where T : class, IOpenApiReferenceable, new() - { - if (map == null) return; - - foreach (var key in map.Keys.ToList()) - { - var entity = map[key]; - if (IsUnresolvedReference(entity)) - { - map[key] = ResolveReference(entity.Reference); - } - } - } - - private T ResolveReference(OpenApiReference reference) where T : class, IOpenApiReferenceable, new() - { - if (string.IsNullOrEmpty(reference?.ExternalResource)) - { - try - { - var referencedObject = typeof(T).Name; - var referenceType = reference?.Type.ToString(); - if (referenceType is not null && !referencedObject.Contains(referenceType)) - { - throw new OpenApiException(string.Format(Properties.SRResource.InvalidReferenceType, referenceType)); - } - - return _currentDocument.ResolveReference(reference, false) as T; - } - catch (OpenApiException ex) - { - _errors.Add(new OpenApiReferenceError(ex)); - return null; - } - } - // The concept of merging references with their target at load time is going away in the next major version - // External references will not support this approach. - //else if (_resolveRemoteReferences == true) - //{ - // if (_currentDocument.Workspace == null) - // { - // _errors.Add(new OpenApiReferenceError(reference,"Cannot resolve external references for documents not in workspaces.")); - // // Leave as unresolved reference - // return new T() - // { - // UnresolvedReference = true, - // Reference = reference - // }; - // } - // var target = _currentDocument.Workspace.ResolveReference(reference); - - // // TODO: If it is a document fragment, then we should resolve it within the current context - - // return target as T; - //} - else - { - // Leave as unresolved reference - return new() - { - UnresolvedReference = true, - Reference = reference - }; - } - } - - private bool IsUnresolvedReference(IOpenApiReferenceable possibleReference) - { - return possibleReference is {UnresolvedReference: true}; - } - } -} diff --git a/src/Microsoft.OpenApi/Services/OpenApiUrlTreeNode.cs b/src/Microsoft.OpenApi/Services/OpenApiUrlTreeNode.cs index e70f129b9..ba5d4349d 100644 --- a/src/Microsoft.OpenApi/Services/OpenApiUrlTreeNode.cs +++ b/src/Microsoft.OpenApi/Services/OpenApiUrlTreeNode.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; @@ -348,12 +348,12 @@ internal MermaidNodeStyle(string color, MermaidNodeShape shape) /// /// The CSS color name of the diagram element /// - public string Color { get; } + public string Color { get; } /// /// The shape of the diagram element /// - public MermaidNodeShape Shape { get; } + public MermaidNodeShape Shape { get; } } /// diff --git a/src/Microsoft.OpenApi/Services/OpenApiVisitorBase.cs b/src/Microsoft.OpenApi/Services/OpenApiVisitorBase.cs index d0e662453..c731d4d8b 100644 --- a/src/Microsoft.OpenApi/Services/OpenApiVisitorBase.cs +++ b/src/Microsoft.OpenApi/Services/OpenApiVisitorBase.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Text.Json.Nodes; using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Models; @@ -50,6 +51,14 @@ public virtual void Visit(OpenApiDocument doc) { } + /// + /// Visits + /// + /// + public virtual void Visit(JsonNode node) + { + } + /// /// Visits /// @@ -92,6 +101,13 @@ public virtual void Visit(OpenApiPaths paths) { } + /// + /// Visits Webhooks> + /// + public virtual void Visit(IDictionary webhooks) + { + } + /// /// Visits /// @@ -249,7 +265,7 @@ public virtual void Visit(OpenApiTag tag) /// /// Visits /// - public virtual void Visit(OpenApiHeader tag) + public virtual void Visit(OpenApiHeader header) { } diff --git a/src/Microsoft.OpenApi/Services/OpenApiWalker.cs b/src/Microsoft.OpenApi/Services/OpenApiWalker.cs index 446663f6b..2dd882ce7 100644 --- a/src/Microsoft.OpenApi/Services/OpenApiWalker.cs +++ b/src/Microsoft.OpenApi/Services/OpenApiWalker.cs @@ -3,10 +3,12 @@ using System; using System.Collections.Generic; -using Microsoft.OpenApi.Models; -using Microsoft.OpenApi.Interfaces; +using System.Text.Json.Nodes; using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Extensions; +using Microsoft.OpenApi.Interfaces; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Models.References; namespace Microsoft.OpenApi.Services { @@ -15,6 +17,7 @@ namespace Microsoft.OpenApi.Services /// public class OpenApiWalker { + private OpenApiDocument _hostDocument; private readonly OpenApiVisitorBase _visitor; private readonly Stack _schemaLoop = new(); private readonly Stack _pathItemLoop = new(); @@ -38,6 +41,7 @@ public void Walk(OpenApiDocument doc) return; } + _hostDocument = doc; _schemaLoop.Clear(); _pathItemLoop.Clear(); @@ -46,6 +50,7 @@ public void Walk(OpenApiDocument doc) Walk(OpenApiConstants.Info, () => Walk(doc.Info)); Walk(OpenApiConstants.Servers, () => Walk(doc.Servers)); Walk(OpenApiConstants.Paths, () => Walk(doc.Paths)); + Walk(OpenApiConstants.Webhooks, () => Walk(doc.Webhooks)); Walk(OpenApiConstants.Components, () => Walk(doc.Components)); Walk(OpenApiConstants.Security, () => Walk(doc.SecurityRequirements)); Walk(OpenApiConstants.ExternalDocs, () => Walk(doc.ExternalDocs)); @@ -78,7 +83,7 @@ internal void Walk(IList tags) /// /// Visits and child objects /// - internal void Walk(OpenApiExternalDocs externalDocs) + internal void Walk(string externalDocs) { if (externalDocs == null) { @@ -89,22 +94,30 @@ internal void Walk(OpenApiExternalDocs externalDocs) } /// - /// Visits and child objects + /// Visits and child objects /// - internal void Walk(OpenApiComponents components) + internal void Walk(OpenApiExternalDocs externalDocs) { - if (components == null) + if (externalDocs == null) { return; } - _visitor.Visit(components); - + _visitor.Visit(externalDocs); + } +#nullable enable + /// + /// Visits and child objects + /// + internal void Walk(OpenApiComponents? components) + { if (components == null) { return; } + _visitor.Visit(components); + Walk(OpenApiConstants.Schemas, () => { if (components.Schemas != null) @@ -138,6 +151,17 @@ internal void Walk(OpenApiComponents components) } }); + Walk(OpenApiConstants.PathItems, () => + { + if (components.PathItems != null) + { + foreach (var path in components.PathItems) + { + Walk(path.Key, () => Walk(path.Value, isComponent: true)); + } + } + }); + Walk(OpenApiConstants.Parameters, () => { if (components.Parameters != null) @@ -207,6 +231,7 @@ internal void Walk(OpenApiComponents components) Walk(components as IOpenApiExtensible); } +#nullable restore /// /// Visits and child objects /// @@ -232,6 +257,30 @@ internal void Walk(OpenApiPaths paths) } + /// + /// Visits Webhooks and child objects + /// + internal void Walk(IDictionary webhooks) + { + if (webhooks == null) + { + return; + } + + _visitor.Visit(webhooks); + + // Visit Webhooks + if (webhooks != null) + { + foreach (var pathItem in webhooks) + { + _visitor.CurrentKeys.Path = pathItem.Key; + Walk(pathItem.Key, () => Walk(pathItem.Value));// JSON Pointer uses ~1 as an escape character for / + _visitor.CurrentKeys.Path = null; + } + }; + } + /// /// Visits list of and child objects /// @@ -340,11 +389,17 @@ internal void Walk(OpenApiContact contact) /// internal void Walk(OpenApiCallback callback, bool isComponent = false) { - if (callback == null || ProcessAsReference(callback, isComponent)) + if (callback == null) { return; } + if (callback is OpenApiCallbackReference) + { + Walk(callback as IOpenApiReferenceable); + return; + } + _visitor.Visit(callback); if (callback != null) @@ -364,8 +419,14 @@ internal void Walk(OpenApiCallback callback, bool isComponent = false) /// internal void Walk(OpenApiTag tag) { - if (tag == null || ProcessAsReference(tag)) + if (tag == null) + { + return; + } + + if (tag is OpenApiTagReference) { + Walk(tag as IOpenApiReferenceable); return; } @@ -429,13 +490,19 @@ internal void Walk(OpenApiServerVariable serverVariable) /// /// Visits and child objects /// - internal void Walk(OpenApiPathItem pathItem) + internal void Walk(OpenApiPathItem pathItem, bool isComponent = false) { if (pathItem == null) { return; } + if (pathItem is OpenApiPathItemReference) + { + Walk(pathItem as IOpenApiReferenceable); + return; + } + if (_pathItemLoop.Contains(pathItem)) { return; // Loop detected, this pathItem has already been walked. @@ -447,8 +514,7 @@ internal void Walk(OpenApiPathItem pathItem) _visitor.Visit(pathItem); - // The path may be a reference - if (pathItem != null && !ProcessAsReference(pathItem)) + if (pathItem != null) { Walk(OpenApiConstants.Parameters, () => Walk(pathItem.Parameters)); Walk(pathItem.Operations); @@ -456,7 +522,7 @@ internal void Walk(OpenApiPathItem pathItem) _visitor.Visit(pathItem as IOpenApiExtensible); _pathItemLoop.Pop(); - } + } /// /// Visits dictionary of @@ -549,11 +615,17 @@ internal void Walk(IList parameters) /// internal void Walk(OpenApiParameter parameter, bool isComponent = false) { - if (parameter == null || ProcessAsReference(parameter, isComponent)) + if (parameter == null) { return; } + if (parameter is OpenApiParameterReference) + { + Walk(parameter as IOpenApiReferenceable); + return; + } + _visitor.Visit(parameter); Walk(OpenApiConstants.Schema, () => Walk(parameter.Schema)); Walk(OpenApiConstants.Content, () => Walk(parameter.Content)); @@ -591,11 +663,17 @@ internal void Walk(OpenApiResponses responses) /// internal void Walk(OpenApiResponse response, bool isComponent = false) { - if (response == null || ProcessAsReference(response, isComponent)) + if (response == null) { return; } + if (response is OpenApiResponseReference) + { + Walk(response as IOpenApiReferenceable); + return; + } + _visitor.Visit(response); Walk(OpenApiConstants.Content, () => Walk(response.Content)); Walk(OpenApiConstants.Links, () => Walk(response.Links)); @@ -608,8 +686,14 @@ internal void Walk(OpenApiResponse response, bool isComponent = false) /// internal void Walk(OpenApiRequestBody requestBody, bool isComponent = false) { - if (requestBody == null || ProcessAsReference(requestBody, isComponent)) + if (requestBody == null) + { + return; + } + + if (requestBody is OpenApiRequestBodyReference) { + Walk(requestBody as IOpenApiReferenceable); return; } @@ -817,6 +901,7 @@ internal void Walk(OpenApiSchema schema, bool isComponent = false) _schemaLoop.Pop(); } + /// /// Visits dictionary of /// @@ -841,9 +926,9 @@ internal void Walk(IDictionary examples) } /// - /// Visits and child objects + /// Visits and child objects /// - internal void Walk(IOpenApiAny example) + internal void Walk(JsonNode example) { if (example == null) { @@ -858,11 +943,17 @@ internal void Walk(IOpenApiAny example) /// internal void Walk(OpenApiExample example, bool isComponent = false) { - if (example == null || ProcessAsReference(example, isComponent)) + if (example == null) { return; } + if (example is OpenApiExampleReference) + { + Walk(example as IOpenApiReferenceable); + return; + } + _visitor.Visit(example); Walk(example as IOpenApiExtensible); } @@ -964,8 +1055,14 @@ internal void Walk(IDictionary links) /// internal void Walk(OpenApiLink link, bool isComponent = false) { - if (link == null || ProcessAsReference(link, isComponent)) + if (link == null) + { + return; + } + + if (link is OpenApiLinkReference) { + Walk(link as IOpenApiReferenceable); return; } @@ -979,11 +1076,17 @@ internal void Walk(OpenApiLink link, bool isComponent = false) /// internal void Walk(OpenApiHeader header, bool isComponent = false) { - if (header == null || ProcessAsReference(header, isComponent)) + if (header == null) { return; } + if (header is OpenApiHeaderReference) + { + Walk(header as IOpenApiReferenceable); + return; + } + _visitor.Visit(header); Walk(OpenApiConstants.Content, () => Walk(header.Content)); Walk(OpenApiConstants.Example, () => Walk(header.Example)); @@ -1002,6 +1105,11 @@ internal void Walk(OpenApiSecurityRequirement securityRequirement) return; } + foreach(var securityScheme in securityRequirement.Keys) + { + Walk(securityScheme); + } + _visitor.Visit(securityRequirement); Walk(securityRequirement as IOpenApiExtensible); } @@ -1011,8 +1119,14 @@ internal void Walk(OpenApiSecurityRequirement securityRequirement) /// internal void Walk(OpenApiSecurityScheme securityScheme, bool isComponent = false) { - if (securityScheme == null || ProcessAsReference(securityScheme, isComponent)) + if (securityScheme == null) + { + return; + } + + if (securityScheme is OpenApiSecuritySchemeReference) { + Walk(securityScheme as IOpenApiReferenceable); return; } @@ -1091,7 +1205,7 @@ private void Walk(string context, Action walk) /// private bool ProcessAsReference(IOpenApiReferenceable referenceable, bool isComponent = false) { - var isReference = referenceable.Reference != null && + var isReference = referenceable.Reference != null && (!isComponent || referenceable.UnresolvedReference); if (isReference) { diff --git a/src/Microsoft.OpenApi/Services/OpenApiWorkspace.cs b/src/Microsoft.OpenApi/Services/OpenApiWorkspace.cs index c275458a5..b227b06e9 100644 --- a/src/Microsoft.OpenApi/Services/OpenApiWorkspace.cs +++ b/src/Microsoft.OpenApi/Services/OpenApiWorkspace.cs @@ -15,34 +15,15 @@ namespace Microsoft.OpenApi.Services /// public class OpenApiWorkspace { - private Dictionary _documents = new(); - private Dictionary _fragments = new(); - private Dictionary _artifacts = new(); - - /// - /// A list of OpenApiDocuments contained in the workspace - /// - public IEnumerable Documents { - get { - return _documents.Values; - } - } - - /// - /// A list of document fragments that are contained in the workspace - /// - public IEnumerable Fragments { get; } + private readonly Dictionary _documentsIdRegistry = new(); + private readonly Dictionary _artifactsRegistry = new(); + private readonly Dictionary _IOpenApiReferenceableRegistry = new(); /// /// The base location from where all relative references are resolved /// public Uri BaseUrl { get; } - - /// - /// A list of document fragments that are contained in the workspace - /// - public IEnumerable Artifacts { get; } - + /// /// Initialize workspace pointing to a base URL to allow resolving relative document locations. Use a file:// url to point to a folder /// @@ -57,87 +38,198 @@ public OpenApiWorkspace(Uri baseUrl) /// public OpenApiWorkspace() { - BaseUrl = new("file://" + Environment.CurrentDirectory + $"{Path.DirectorySeparatorChar}" ); + BaseUrl = new Uri(OpenApiConstants.BaseRegistryUri); } /// /// Initializes a copy of an object /// - public OpenApiWorkspace(OpenApiWorkspace workspace){} + public OpenApiWorkspace(OpenApiWorkspace workspace) { } /// - /// Verify if workspace contains a document based on its URL. + /// Returns the total count of all the components in the workspace registry /// - /// A relative or absolute URL of the file. Use file:// for folder locations. - /// Returns true if a matching document is found. - public bool Contains(string location) + /// + public int ComponentsCount() { - var key = ToLocationUrl(location); - return _documents.ContainsKey(key) || _fragments.ContainsKey(key) || _artifacts.ContainsKey(key); + return _IOpenApiReferenceableRegistry.Count + _artifactsRegistry.Count; } /// - /// Add an OpenApiDocument to the workspace. + /// Registers a document's components into the workspace /// - /// /// - public void AddDocument(string location, OpenApiDocument document) + public void RegisterComponents(OpenApiDocument document) { - document.Workspace = this; - _documents.Add(ToLocationUrl(location), document); + if (document?.Components == null) return; + + string baseUri = document.BaseUri + OpenApiConstants.ComponentsSegment; + string location; + + // Register Schema + foreach (var item in document.Components.Schemas) + { + location = item.Value.Id ?? baseUri + ReferenceType.Schema.GetDisplayName() + "/" + item.Key; + + RegisterComponent(location, item.Value); + } + + // Register Parameters + foreach (var item in document.Components.Parameters) + { + location = baseUri + ReferenceType.Parameter.GetDisplayName() + "/" + item.Key; + RegisterComponent(location, item.Value); + } + + // Register Responses + foreach (var item in document.Components.Responses) + { + location = baseUri + ReferenceType.Response.GetDisplayName() + "/" + item.Key; + RegisterComponent(location, item.Value); + } + + // Register RequestBodies + foreach (var item in document.Components.RequestBodies) + { + location = baseUri + ReferenceType.RequestBody.GetDisplayName() + "/" + item.Key; + RegisterComponent(location, item.Value); + } + + // Register Links + foreach (var item in document.Components.Links) + { + location = baseUri + ReferenceType.Link.GetDisplayName() + "/" + item.Key; + RegisterComponent(location, item.Value); + } + + // Register Callbacks + foreach (var item in document.Components.Callbacks) + { + location = baseUri + ReferenceType.Callback.GetDisplayName() + "/" + item.Key; + RegisterComponent(location, item.Value); + } + + // Register PathItems + foreach (var item in document.Components.PathItems) + { + location = baseUri + ReferenceType.PathItem.GetDisplayName() + "/" + item.Key; + RegisterComponent(location, item.Value); + } + + // Register Examples + foreach (var item in document.Components.Examples) + { + location = baseUri + ReferenceType.Example.GetDisplayName() + "/" + item.Key; + RegisterComponent(location, item.Value); + } + + // Register Headers + foreach (var item in document.Components.Headers) + { + location = baseUri + ReferenceType.Header.GetDisplayName() + "/" + item.Key; + RegisterComponent(location, item.Value); + } + + // Register SecuritySchemes + foreach (var item in document.Components.SecuritySchemes) + { + location = baseUri + ReferenceType.SecurityScheme.GetDisplayName() + "/" + item.Key; + RegisterComponent(location, item.Value); + } } + /// - /// Adds a fragment of an OpenApiDocument to the workspace. + /// Registers a component in the component registry. /// /// - /// - /// Not sure how this is going to work. Does the reference just point to the fragment as a whole, or do we need to - /// to be able to point into the fragment. Keeping it private until we figure it out. - /// - public void AddFragment(string location, IOpenApiReferenceable fragment) + /// + /// true if the component is successfully registered; otherwise false. + public bool RegisterComponent(string location, T component) { - _fragments.Add(ToLocationUrl(location), fragment); + var uri = ToLocationUrl(location); + if (component is IOpenApiReferenceable referenceable) + { + if (!_IOpenApiReferenceableRegistry.ContainsKey(uri)) + { + _IOpenApiReferenceableRegistry[uri] = referenceable; + return true; + } + } + else if (component is Stream stream) + { + if (!_artifactsRegistry.ContainsKey(uri)) + { + _artifactsRegistry[uri] = stream; + return true; + } + } + + return false; } /// - /// Add a stream based artifact to the workspace. Useful for images, examples, alternative schemas. + /// Adds a document id to the dictionaries of document locations and their ids. /// - /// - /// - public void AddArtifact(string location, Stream artifact) + /// + /// + public void AddDocumentId(string key, Uri value) { - _artifacts.Add(ToLocationUrl(location), artifact); + if (!_documentsIdRegistry.ContainsKey(key)) + { + _documentsIdRegistry[key] = value; + } } /// - /// Returns the target of an OpenApiReference from within the workspace. + /// Retrieves the document id given a key. /// - /// An instance of an OpenApiReference - /// - public IOpenApiReferenceable ResolveReference(OpenApiReference reference) + /// + /// The document id of the given key. + public Uri GetDocumentId(string key) { - if (_documents.TryGetValue(new(BaseUrl, reference.ExternalResource), out var doc)) - { - return doc.ResolveReference(reference, false); - } - else if (_fragments.TryGetValue(new(BaseUrl, reference.ExternalResource), out var fragment)) + if (_documentsIdRegistry.TryGetValue(key, out var id)) { - var jsonPointer = new JsonPointer($"/{reference.Id ?? string.Empty}"); - return fragment.ResolveReference(jsonPointer); + return id; } return null; } /// - /// + /// Verify if workspace contains a component based on its URL. /// + /// A relative or absolute URL of the file. Use file:// for folder locations. + /// Returns true if a matching document is found. + public bool Contains(string location) + { + var key = ToLocationUrl(location); + return _IOpenApiReferenceableRegistry.ContainsKey(key) || _artifactsRegistry.ContainsKey(key); + } + +#nullable enable + /// + /// Resolves a reference given a key. + /// + /// /// - /// - public Stream GetArtifact(string location) + /// The resolved reference. + public T? ResolveReference(string location) { - return _artifacts[ToLocationUrl(location)]; + if (string.IsNullOrEmpty(location)) return default; + + var uri = ToLocationUrl(location); + if (_IOpenApiReferenceableRegistry.TryGetValue(uri, out var referenceableValue)) + { + return (T)referenceableValue; + } + else if (_artifactsRegistry.TryGetValue(uri, out var artifact)) + { + return (T)(object)artifact; + } + + return default; } +#nullable restore private Uri ToLocationUrl(string location) { diff --git a/src/Microsoft.OpenApi/Services/OperationSearch.cs b/src/Microsoft.OpenApi/Services/OperationSearch.cs index 49cdac49c..8b1dbd1ee 100644 --- a/src/Microsoft.OpenApi/Services/OperationSearch.cs +++ b/src/Microsoft.OpenApi/Services/OperationSearch.cs @@ -25,7 +25,7 @@ public class OperationSearch : OpenApiVisitorBase /// The OperationSearch constructor. /// /// A predicate function. - public OperationSearch(Func predicate) + public OperationSearch(Func predicate) { _predicate = predicate ?? throw new ArgumentNullException(nameof(predicate)); } diff --git a/src/Microsoft.OpenApi/Services/ReferenceHostDocumentSetter.cs b/src/Microsoft.OpenApi/Services/ReferenceHostDocumentSetter.cs new file mode 100644 index 000000000..1d9bb8e8e --- /dev/null +++ b/src/Microsoft.OpenApi/Services/ReferenceHostDocumentSetter.cs @@ -0,0 +1,33 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using Microsoft.OpenApi.Interfaces; +using Microsoft.OpenApi.Models; + +namespace Microsoft.OpenApi.Services +{ + /// + /// This class is used to walk an OpenApiDocument and sets the host document of IOpenApiReferenceable objects + /// + internal class ReferenceHostDocumentSetter : OpenApiVisitorBase + { + private readonly OpenApiDocument _currentDocument; + + public ReferenceHostDocumentSetter(OpenApiDocument currentDocument) + { + _currentDocument = currentDocument; + } + + /// + /// Visits the referenceable element in the host document + /// + /// The referenceable element in the doc. + public override void Visit(IOpenApiReferenceable referenceable) + { + if (referenceable.Reference != null) + { + referenceable.Reference.HostDocument = _currentDocument; + } + } + } +} diff --git a/src/Microsoft.OpenApi/Validations/IValidationContext.cs b/src/Microsoft.OpenApi/Validations/IValidationContext.cs index 73b1fec06..36c26baa6 100644 --- a/src/Microsoft.OpenApi/Validations/IValidationContext.cs +++ b/src/Microsoft.OpenApi/Validations/IValidationContext.cs @@ -1,6 +1,8 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. +using Microsoft.OpenApi.Models; + namespace Microsoft.OpenApi.Validations { /// @@ -35,5 +37,10 @@ public interface IValidationContext /// Pointer to source of validation error in document /// string PathString { get; } + + /// + /// + /// + OpenApiDocument HostDocument { get; } } } diff --git a/src/Microsoft.OpenApi/Validations/OpenApiValidatiorWarning.cs b/src/Microsoft.OpenApi/Validations/OpenApiValidatiorWarning.cs index 1770c0a6f..9012b1c06 100644 --- a/src/Microsoft.OpenApi/Validations/OpenApiValidatiorWarning.cs +++ b/src/Microsoft.OpenApi/Validations/OpenApiValidatiorWarning.cs @@ -3,8 +3,8 @@ namespace Microsoft.OpenApi.Validations { /// - /// Warnings detected when validating an OpenAPI Element - /// + /// Warnings detected when validating an OpenAPI Element + /// public class OpenApiValidatorWarning : OpenApiError { /// diff --git a/src/Microsoft.OpenApi/Validations/OpenApiValidator.cs b/src/Microsoft.OpenApi/Validations/OpenApiValidator.cs index 8e9ed4fc4..6908e58bf 100644 --- a/src/Microsoft.OpenApi/Validations/OpenApiValidator.cs +++ b/src/Microsoft.OpenApi/Validations/OpenApiValidator.cs @@ -1,4 +1,5 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. + +// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. using System; @@ -22,9 +23,11 @@ public class OpenApiValidator : OpenApiVisitorBase, IValidationContext /// Create a visitor that will validate an OpenAPIDocument /// /// - public OpenApiValidator(ValidationRuleSet ruleSet) + /// + public OpenApiValidator(ValidationRuleSet ruleSet, OpenApiDocument hostDocument = null) { _ruleSet = ruleSet; + HostDocument = hostDocument; } /// @@ -37,6 +40,11 @@ public OpenApiValidator(ValidationRuleSet ruleSet) /// public IEnumerable Warnings { get => _warnings; } + /// + /// The host document used for validation. + /// + public OpenApiDocument HostDocument { get; set; } + /// /// Register an error with the validation context. /// @@ -292,7 +300,7 @@ private void Validate(object item, Type type) } // Validate unresolved references as references - if (item is IOpenApiReferenceable {UnresolvedReference: true}) + if (item is IOpenApiReferenceable { UnresolvedReference: true }) { type = typeof(IOpenApiReferenceable); } @@ -300,7 +308,7 @@ private void Validate(object item, Type type) var rules = _ruleSet.FindRules(type); foreach (var rule in rules) { - rule.Evaluate(this as IValidationContext, item); + rule.Evaluate(this, item); } } } diff --git a/src/Microsoft.OpenApi/Validations/Rules/OpenApiDocumentRules.cs b/src/Microsoft.OpenApi/Validations/Rules/OpenApiDocumentRules.cs index f38e2530f..c98c9a038 100644 --- a/src/Microsoft.OpenApi/Validations/Rules/OpenApiDocumentRules.cs +++ b/src/Microsoft.OpenApi/Validations/Rules/OpenApiDocumentRules.cs @@ -28,15 +28,6 @@ public static class OpenApiDocumentRules String.Format(SRResource.Validation_FieldIsRequired, "info", "document")); } context.Exit(); - - // paths - context.Enter("paths"); - if (item.Paths == null) - { - context.CreateError(nameof(OpenApiDocumentFieldIsMissing), - String.Format(SRResource.Validation_FieldIsRequired, "paths", "document")); - } - context.Exit(); }); } } diff --git a/src/Microsoft.OpenApi/Validations/Rules/OpenApiHeaderRules.cs b/src/Microsoft.OpenApi/Validations/Rules/OpenApiHeaderRules.cs deleted file mode 100644 index 194e426cc..000000000 --- a/src/Microsoft.OpenApi/Validations/Rules/OpenApiHeaderRules.cs +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. - -using Microsoft.OpenApi.Models; - -namespace Microsoft.OpenApi.Validations.Rules -{ - /// - /// The validation rules for . - /// - //Removed from Default Rules as this is not a MUST in OpenAPI - [OpenApiRule] - public static class OpenApiHeaderRules - { - /// - /// Validate the data matches with the given data type. - /// - public static ValidationRule HeaderMismatchedDataType => - new(nameof(HeaderMismatchedDataType), - (context, header) => - { - // example - context.Enter("example"); - - if (header.Example != null) - { - RuleHelpers.ValidateDataTypeMismatch(context, nameof(HeaderMismatchedDataType), header.Example, header.Schema); - } - - context.Exit(); - - // examples - context.Enter("examples"); - - if (header.Examples != null) - { - foreach (var key in header.Examples.Keys) - { - if (header.Examples[key] != null) - { - context.Enter(key); - context.Enter("value"); - RuleHelpers.ValidateDataTypeMismatch(context, nameof(HeaderMismatchedDataType), header.Examples[key]?.Value, header.Schema); - context.Exit(); - context.Exit(); - } - } - } - - context.Exit(); - }); - - // add more rule. - } -} diff --git a/src/Microsoft.OpenApi/Validations/Rules/OpenApiMediaTypeRules.cs b/src/Microsoft.OpenApi/Validations/Rules/OpenApiMediaTypeRules.cs deleted file mode 100644 index 7ac09cbbf..000000000 --- a/src/Microsoft.OpenApi/Validations/Rules/OpenApiMediaTypeRules.cs +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. - -using Microsoft.OpenApi.Models; - -namespace Microsoft.OpenApi.Validations.Rules -{ - /// - /// The validation rules for . - /// - /// - /// Removed this in v1.3 as a default rule as the OpenAPI specification does not require that example - /// values validate against the schema. Validating examples against the schema is particularly difficult - /// as it requires parsing of the example using the schema as a guide. This is not possible when the schema - /// is referenced. Even if we fix this issue, this rule should be treated as a warning, not an error - /// Future versions of the validator should make that distinction. - /// Future versions of the example parsers should not try an infer types. - /// Example validation should be done as a separate post reading step so all schemas can be fully available. - /// - [OpenApiRule] - public static class OpenApiMediaTypeRules - { - /// - /// Validate the data matches with the given data type. - /// - public static ValidationRule MediaTypeMismatchedDataType => - new(nameof(MediaTypeMismatchedDataType), - (context, mediaType) => - { - // example - context.Enter("example"); - - if (mediaType.Example != null) - { - RuleHelpers.ValidateDataTypeMismatch(context, nameof(MediaTypeMismatchedDataType), mediaType.Example, mediaType.Schema); - } - - context.Exit(); - - // enum - context.Enter("examples"); - - if (mediaType.Examples != null) - { - foreach (var key in mediaType.Examples.Keys) - { - if (mediaType.Examples[key] != null) - { - context.Enter(key); - context.Enter("value"); - RuleHelpers.ValidateDataTypeMismatch(context, nameof(MediaTypeMismatchedDataType), mediaType.Examples[key]?.Value, mediaType.Schema); - context.Exit(); - context.Exit(); - } - } - } - - context.Exit(); - }); - - // add more rule. - } -} diff --git a/src/Microsoft.OpenApi/Validations/Rules/OpenApiNonDefaultRules.cs b/src/Microsoft.OpenApi/Validations/Rules/OpenApiNonDefaultRules.cs new file mode 100644 index 000000000..f02be33ee --- /dev/null +++ b/src/Microsoft.OpenApi/Validations/Rules/OpenApiNonDefaultRules.cs @@ -0,0 +1,123 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System.Collections.Generic; +using System.Linq; +using System.Text.Json.Nodes; +using Microsoft.OpenApi.Models; + +namespace Microsoft.OpenApi.Validations.Rules +{ + /// + /// Defines a non-default set of rules for validating examples in header, media type and parameter objects against the schema + /// + public static class OpenApiNonDefaultRules + { + /// + /// Validate the data matches with the given data type. + /// + public static ValidationRule HeaderMismatchedDataType => + new(nameof(HeaderMismatchedDataType), + (context, header) => + { + ValidateMismatchedDataType(context, nameof(HeaderMismatchedDataType), header.Example, header.Examples, header.Schema); + }); + + /// + /// Validate the data matches with the given data type. + /// + public static ValidationRule MediaTypeMismatchedDataType => + new(nameof(MediaTypeMismatchedDataType), + (context, mediaType) => + { + ValidateMismatchedDataType(context, nameof(MediaTypeMismatchedDataType), mediaType.Example, mediaType.Examples, mediaType.Schema); + }); + + /// + /// Validate the data matches with the given data type. + /// + public static ValidationRule ParameterMismatchedDataType => + new(nameof(ParameterMismatchedDataType), + (context, parameter) => + { + ValidateMismatchedDataType(context, nameof(ParameterMismatchedDataType), parameter.Example, parameter.Examples, parameter.Schema); + }); + + /// + /// Validate the data matches with the given data type. + /// + public static ValidationRule SchemaMismatchedDataType => + new(nameof(SchemaMismatchedDataType), + (context, schema) => + { + // default + context.Enter("default"); + + if (schema.Default != null) + { + RuleHelpers.ValidateDataTypeMismatch(context, nameof(SchemaMismatchedDataType), schema.Default, schema); + } + + context.Exit(); + + // example + context.Enter("example"); + + if (schema.Example != null) + { + RuleHelpers.ValidateDataTypeMismatch(context, nameof(SchemaMismatchedDataType), schema.Example, schema); + } + + context.Exit(); + + // enum + context.Enter("enum"); + + if (schema.Enum != null) + { + for (var i = 0; i < schema.Enum.Count; i++) + { + context.Enter(i.ToString()); + RuleHelpers.ValidateDataTypeMismatch(context, nameof(SchemaMismatchedDataType), schema.Enum[i], schema); + context.Exit(); + } + } + + context.Exit(); + }); + + private static void ValidateMismatchedDataType(IValidationContext context, + string ruleName, + JsonNode example, + IDictionary examples, + OpenApiSchema schema) + { + // example + context.Enter("example"); + + if (example != null) + { + RuleHelpers.ValidateDataTypeMismatch(context, ruleName, example, schema); + } + + context.Exit(); + + // enum + context.Enter("examples"); + + if (examples != null) + { + foreach (var key in examples.Keys.Where(k => examples[k] != null)) + { + context.Enter(key); + context.Enter("value"); + RuleHelpers.ValidateDataTypeMismatch(context, ruleName, examples[key]?.Value, schema); + context.Exit(); + context.Exit(); + } + } + + context.Exit(); + } + } +} diff --git a/src/Microsoft.OpenApi/Validations/Rules/OpenApiParameterRules.cs b/src/Microsoft.OpenApi/Validations/Rules/OpenApiParameterRules.cs index a1a228134..812bc7f12 100644 --- a/src/Microsoft.OpenApi/Validations/Rules/OpenApiParameterRules.cs +++ b/src/Microsoft.OpenApi/Validations/Rules/OpenApiParameterRules.cs @@ -58,44 +58,6 @@ public static class OpenApiParameterRules context.Exit(); }); - /// - /// Validate the data matches with the given data type. - /// - public static ValidationRule ParameterMismatchedDataType => - new(nameof(ParameterMismatchedDataType), - (context, parameter) => - { - // example - context.Enter("example"); - - if (parameter.Example != null) - { - RuleHelpers.ValidateDataTypeMismatch(context, nameof(ParameterMismatchedDataType), parameter.Example, parameter.Schema); - } - - context.Exit(); - - // examples - context.Enter("examples"); - - if (parameter.Examples != null) - { - foreach (var key in parameter.Examples.Keys) - { - if (parameter.Examples[key] != null) - { - context.Enter(key); - context.Enter("value"); - RuleHelpers.ValidateDataTypeMismatch(context, nameof(ParameterMismatchedDataType), parameter.Examples[key]?.Value, parameter.Schema); - context.Exit(); - context.Exit(); - } - } - } - - context.Exit(); - }); - /// /// Validate that a path parameter should always appear in the path /// diff --git a/src/Microsoft.OpenApi/Validations/Rules/OpenApiSchemaRules.cs b/src/Microsoft.OpenApi/Validations/Rules/OpenApiSchemaRules.cs index 411f26fd0..054c79c6b 100644 --- a/src/Microsoft.OpenApi/Validations/Rules/OpenApiSchemaRules.cs +++ b/src/Microsoft.OpenApi/Validations/Rules/OpenApiSchemaRules.cs @@ -1,9 +1,9 @@ // Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. +// Licensed under the MIT license. +using System.Collections.Generic; using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Properties; -using System.Collections.Generic; namespace Microsoft.OpenApi.Validations.Rules { @@ -13,49 +13,6 @@ namespace Microsoft.OpenApi.Validations.Rules [OpenApiRule] public static class OpenApiSchemaRules { - /// - /// Validate the data matches with the given data type. - /// - public static ValidationRule SchemaMismatchedDataType => - new(nameof(SchemaMismatchedDataType), - (context, schema) => - { - // default - context.Enter("default"); - - if (schema.Default != null) - { - RuleHelpers.ValidateDataTypeMismatch(context, nameof(SchemaMismatchedDataType), schema.Default, schema); - } - - context.Exit(); - - // example - context.Enter("example"); - - if (schema.Example != null) - { - RuleHelpers.ValidateDataTypeMismatch(context, nameof(SchemaMismatchedDataType), schema.Example, schema); - } - - context.Exit(); - - // enum - context.Enter("enum"); - - if (schema.Enum != null) - { - for (var i = 0; i < schema.Enum.Count; i++) - { - context.Enter(i.ToString()); - RuleHelpers.ValidateDataTypeMismatch(context, nameof(SchemaMismatchedDataType), schema.Enum[i], schema); - context.Exit(); - } - } - - context.Exit(); - }); - /// /// Validates Schema Discriminator /// diff --git a/src/Microsoft.OpenApi/Validations/Rules/RuleHelpers.cs b/src/Microsoft.OpenApi/Validations/Rules/RuleHelpers.cs index 8052892da..4e05c44fd 100644 --- a/src/Microsoft.OpenApi/Validations/Rules/RuleHelpers.cs +++ b/src/Microsoft.OpenApi/Validations/Rules/RuleHelpers.cs @@ -1,8 +1,9 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. -using System; -using Microsoft.OpenApi.Any; +using System.Text.Json; +using System.Text.Json.Nodes; +using Microsoft.OpenApi.Extensions; using Microsoft.OpenApi.Models; namespace Microsoft.OpenApi.Validations.Rules @@ -18,7 +19,7 @@ internal static class RuleHelpers /// True if it's an email address. Otherwise False. public static bool IsEmailAddress(this string input) { - if (String.IsNullOrEmpty(input)) + if (string.IsNullOrEmpty(input)) { return false; } @@ -29,7 +30,7 @@ public static bool IsEmailAddress(this string input) return false; } - if (String.IsNullOrEmpty(splits[0]) || String.IsNullOrEmpty(splits[1])) + if (string.IsNullOrEmpty(splits[0]) || string.IsNullOrEmpty(splits[1])) { return false; } @@ -42,7 +43,7 @@ public static bool IsEmailAddress(this string input) public static void ValidateDataTypeMismatch( IValidationContext context, string ruleName, - IOpenApiAny value, + JsonNode value, OpenApiSchema schema) { if (schema == null) @@ -50,18 +51,18 @@ public static void ValidateDataTypeMismatch( return; } - var type = schema.Type; + // convert value to JsonElement and access the ValueKind property to determine the type. + var jsonElement = JsonDocument.Parse(JsonSerializer.Serialize(value)).RootElement; + + var type = schema.Type.ToIdentifier(); var format = schema.Format; var nullable = schema.Nullable; // Before checking the type, check first if the schema allows null. // If so and the data given is also null, this is allowed for any type. - if (nullable) + if (nullable && jsonElement.ValueKind is JsonValueKind.Null) { - if (value is OpenApiNull) - { - return; - } + return; } if (type == "object") @@ -69,13 +70,13 @@ public static void ValidateDataTypeMismatch( // It is not against the spec to have a string representing an object value. // To represent examples of media types that cannot naturally be represented in JSON or YAML, // a string value can contain the example with escaping where necessary - if (value is OpenApiString) + if (jsonElement.ValueKind is JsonValueKind.String) { return; } // If value is not a string and also not an object, there is a data mismatch. - if (value is not OpenApiObject anyObject) + if (value is not JsonObject anyObject) { context.CreateWarning( ruleName, @@ -83,8 +84,9 @@ public static void ValidateDataTypeMismatch( return; } - foreach (var key in anyObject.Keys) + foreach (var kvp in anyObject) { + var key = kvp.Key; context.Enter(key); if (schema.Properties != null && @@ -108,13 +110,13 @@ public static void ValidateDataTypeMismatch( // It is not against the spec to have a string representing an array value. // To represent examples of media types that cannot naturally be represented in JSON or YAML, // a string value can contain the example with escaping where necessary - if (value is OpenApiString) + if (jsonElement.ValueKind is JsonValueKind.String) { return; } // If value is not a string and also not an array, there is a data mismatch. - if (value is not OpenApiArray anyArray) + if (value is not JsonArray anyArray) { context.CreateWarning( ruleName, @@ -134,9 +136,9 @@ public static void ValidateDataTypeMismatch( return; } - if (type is "integer" or "number" && format == "int32") + if (type is "integer" or "number" && format is "int32") { - if (value is not OpenApiInteger) + if (jsonElement.ValueKind is not JsonValueKind.Number) { context.CreateWarning( ruleName, @@ -146,9 +148,9 @@ public static void ValidateDataTypeMismatch( return; } - if (type is "integer" or "number" && format == "int64") + if (type is "integer" or "number" && format is "int64") { - if (value is not OpenApiLong) + if (jsonElement.ValueKind is not JsonValueKind.Number) { context.CreateWarning( ruleName, @@ -158,9 +160,9 @@ public static void ValidateDataTypeMismatch( return; } - if (type is "integer" && value is not OpenApiInteger) + if (type is "integer") { - if (value is not OpenApiInteger) + if (jsonElement.ValueKind is not JsonValueKind.Number) { context.CreateWarning( ruleName, @@ -170,9 +172,9 @@ public static void ValidateDataTypeMismatch( return; } - if (type == "number" && format == "float") + if (type is "number" && format is "float") { - if (value is not OpenApiFloat) + if (jsonElement.ValueKind is not JsonValueKind.Number) { context.CreateWarning( ruleName, @@ -182,9 +184,9 @@ public static void ValidateDataTypeMismatch( return; } - if (type == "number" && format == "double") + if (type is "number" && format is "double") { - if (value is not OpenApiDouble) + if (jsonElement.ValueKind is not JsonValueKind.Number) { context.CreateWarning( ruleName, @@ -194,9 +196,9 @@ public static void ValidateDataTypeMismatch( return; } - if (type == "number") + if (type is "number") { - if (value is not OpenApiDouble) + if (jsonElement.ValueKind is not JsonValueKind.Number) { context.CreateWarning( ruleName, @@ -206,9 +208,9 @@ public static void ValidateDataTypeMismatch( return; } - if (type == "string" && format == "byte") + if (type is "string" && format is "byte") { - if (value is not OpenApiByte) + if (jsonElement.ValueKind is not JsonValueKind.String) { context.CreateWarning( ruleName, @@ -218,9 +220,9 @@ public static void ValidateDataTypeMismatch( return; } - if (type == "string" && format == "date") + if (type is "string" && format is "date") { - if (value is not OpenApiDate) + if (jsonElement.ValueKind is not JsonValueKind.String) { context.CreateWarning( ruleName, @@ -230,9 +232,9 @@ public static void ValidateDataTypeMismatch( return; } - if (type == "string" && format == "date-time") + if (type is "string" && format is "date-time") { - if (value is not OpenApiDateTime) + if (jsonElement.ValueKind is not JsonValueKind.String) { context.CreateWarning( ruleName, @@ -242,9 +244,9 @@ public static void ValidateDataTypeMismatch( return; } - if (type == "string" && format == "password") + if (type is "string" && format is "password") { - if (value is not OpenApiPassword) + if (jsonElement.ValueKind is not JsonValueKind.String) { context.CreateWarning( ruleName, @@ -254,9 +256,9 @@ public static void ValidateDataTypeMismatch( return; } - if (type == "string") + if (type is "string") { - if (value is not OpenApiString) + if (jsonElement.ValueKind is not JsonValueKind.String) { context.CreateWarning( ruleName, @@ -266,9 +268,9 @@ public static void ValidateDataTypeMismatch( return; } - if (type == "boolean") + if (type is "boolean") { - if (value is not OpenApiBoolean) + if (jsonElement.ValueKind is not JsonValueKind.True && jsonElement.ValueKind is not JsonValueKind.False) { context.CreateWarning( ruleName, diff --git a/src/Microsoft.OpenApi/Validations/ValidationExtensions.cs b/src/Microsoft.OpenApi/Validations/ValidationExtensions.cs index 3f01ef549..afad6903d 100644 --- a/src/Microsoft.OpenApi/Validations/ValidationExtensions.cs +++ b/src/Microsoft.OpenApi/Validations/ValidationExtensions.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. namespace Microsoft.OpenApi.Validations diff --git a/src/Microsoft.OpenApi/Validations/ValidationRule.cs b/src/Microsoft.OpenApi/Validations/ValidationRule.cs index 4f0f00383..bccb28be6 100644 --- a/src/Microsoft.OpenApi/Validations/ValidationRule.cs +++ b/src/Microsoft.OpenApi/Validations/ValidationRule.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; @@ -40,7 +40,7 @@ internal ValidationRule(string name) /// Class containing validation rule logic for . /// /// - public class ValidationRule : ValidationRule where T : IOpenApiElement + public class ValidationRule : ValidationRule { private readonly Action _validate; diff --git a/src/Microsoft.OpenApi/Validations/ValidationRuleSet.cs b/src/Microsoft.OpenApi/Validations/ValidationRuleSet.cs index 448683fd9..3e38d65b2 100644 --- a/src/Microsoft.OpenApi/Validations/ValidationRuleSet.cs +++ b/src/Microsoft.OpenApi/Validations/ValidationRuleSet.cs @@ -4,7 +4,6 @@ using System; using System.Linq; using System.Reflection; -using System.Collections; using System.Collections.Generic; using Microsoft.OpenApi.Exceptions; using Microsoft.OpenApi.Properties; @@ -15,14 +14,32 @@ namespace Microsoft.OpenApi.Validations /// /// The rule set of the validation. /// - public sealed class ValidationRuleSet : IEnumerable + public sealed class ValidationRuleSet { - private Dictionary> _rules = new(); + private Dictionary> _rulesDictionary = new(); private static ValidationRuleSet _defaultRuleSet; private List _emptyRules = new(); + + /// + /// Gets the rules in this rule set. + /// + public IList Rules => _rulesDictionary.Values.SelectMany(v => v).ToList(); + + /// + /// Gets the number of elements contained in this rule set. + /// + public int Count => _rulesDictionary.Count; + + /// + /// Initializes a new instance of the class. + /// + public ValidationRuleSet() + { + } + /// /// Retrieve the rules that are related to a specific type /// @@ -30,7 +47,7 @@ public sealed class ValidationRuleSet : IEnumerable /// Either the rules related to the type, or an empty list. public IList FindRules(Type type) { - _rules.TryGetValue(type, out var results); + _rulesDictionary.TryGetValue(type, out var results); return results ?? _emptyRules; } @@ -66,10 +83,22 @@ public static ValidationRuleSet GetEmptyRuleSet() } /// - /// Initializes a new instance of the class. + /// Add validation rules to the rule set. /// - public ValidationRuleSet() + /// The rule set to add validation rules to. + /// The validation rules to be added to the rules set. + /// Throws a null argument exception if the arguments are null. + public static void AddValidationRules(ValidationRuleSet ruleSet, IDictionary> rules) { + if (ruleSet == null || rules == null) + { + throw new OpenApiException(SRResource.ArgumentNull); + } + + foreach (var rule in rules) + { + ruleSet.Add(rule.Key, rule.Value); + } } /// @@ -85,7 +114,7 @@ public ValidationRuleSet(ValidationRuleSet ruleSet) foreach (var rule in ruleSet) { - Add(rule); + Add(rule.ElementType, rule); } } @@ -93,7 +122,7 @@ public ValidationRuleSet(ValidationRuleSet ruleSet) /// Initializes a new instance of the class. /// /// Rules to be contained in this ruleset. - public ValidationRuleSet(IEnumerable rules) + public ValidationRuleSet(IDictionary> rules) { if (rules == null) { @@ -102,36 +131,69 @@ public ValidationRuleSet(IEnumerable rules) foreach (var rule in rules) { - Add(rule); + Add(rule.Key, rule.Value); } } /// - /// Gets the rules in this rule set. + /// Add the new rule into the rule set. /// - public IList Rules + /// The key for the rule. + /// The list of rules. + public void Add(Type key, IList rules) { - get => _rules.Values.SelectMany(v => v).ToList(); + foreach (var rule in rules) + { + Add(key, rule); + } } /// - /// Add the new rule into the rule set. + /// Add a new rule into the rule set. /// + /// The key for the rule. /// The rule. - public void Add(ValidationRule rule) + /// Exception thrown when rule already exists. + public void Add(Type key, ValidationRule rule) { - if (!_rules.TryGetValue(rule.ElementType, out var item)) + if (!_rulesDictionary.ContainsKey(key)) { - _rules[rule.ElementType] = new List {rule}; - return; + _rulesDictionary[key] = new List(); } - if (item.Contains(rule)) + if (_rulesDictionary[key].Contains(rule)) { throw new OpenApiException(SRResource.Validation_RuleAddTwice); } - item.Add(rule); + _rulesDictionary[key].Add(rule); + } + + /// + /// Updates an existing rule with a new one. + /// + /// The key of the existing rule. + /// The new rule. + /// The old rule. + /// true, if the update was successful; otherwise false. + public bool Update(Type key, ValidationRule newRule, ValidationRule oldRule) + { + if (_rulesDictionary.TryGetValue(key, out var currentRules)) + { + currentRules.Add(newRule); + return currentRules.Remove(oldRule); + } + return false; + } + + /// + /// Removes a collection of rules. + /// + /// The key of the collection of rules to be removed. + /// true if the collection of rules with the provided key is removed; otherwise, false. + public bool Remove(Type key) + { + return _rulesDictionary.Remove(key); } /// @@ -140,22 +202,81 @@ public void Add(ValidationRule rule) /// Name of the rule. public void Remove(string ruleName) { - foreach (KeyValuePair> rule in _rules) + foreach (KeyValuePair> rule in _rulesDictionary) { - _rules[rule.Key] = rule.Value.Where(vr => !vr.Name.Equals(ruleName, StringComparison.Ordinal)).ToList(); + _rulesDictionary[rule.Key] = rule.Value.Where(vr => !vr.Name.Equals(ruleName, StringComparison.Ordinal)).ToList(); } // Remove types with no rule - _rules = _rules.Where(r => r.Value.Any()).ToDictionary(r => r.Key, r => r.Value); + _rulesDictionary = _rulesDictionary.Where(r => r.Value.Any()).ToDictionary(r => r.Key, r => r.Value); } /// - /// Remove a rule by element type. - /// - /// Type of the rule. - public void Remove(Type type) + /// Removes a rule by key. + /// + /// The key of the rule to be removed. + /// The rule to be removed. + /// true if the rule is successfully removed; otherwise, false. + public bool Remove(Type key, ValidationRule rule) + { + if (_rulesDictionary.TryGetValue(key, out IList validationRules)) + { + return validationRules.Remove(rule); + } + + return false; + } + + /// + /// Removes the first rule that matches the provided rule from the list of rules. + /// + /// The rule to be removed. + /// true if the rule is successfully removed; otherwise, false. + public bool Remove(ValidationRule rule) + { + return _rulesDictionary.Values.FirstOrDefault(x => x.Remove(rule)) is not null; + } + + /// + /// Clears all rules in this rule set. + /// + public void Clear() { - _rules.Remove(type); + _rulesDictionary.Clear(); + } + + /// + /// Determines whether the rule set contains an element with the specified key. + /// + /// The key to locate in the rule set. + /// true if the rule set contains an element with the key; otherwise, false. + public bool ContainsKey(Type key) + { + return _rulesDictionary.ContainsKey(key); + } + + /// + /// Determines whether the provided rule is contained in the specified key in the rule set. + /// + /// The key to locate. + /// The rule to locate. + /// + public bool Contains(Type key, ValidationRule rule) + { + return _rulesDictionary.TryGetValue(key, out IList validationRules) && validationRules.Contains(rule); + } + + /// + /// Gets the rules associated with the specified key. + /// + /// The key whose rules to get. + /// When this method returns, the rules associated with the specified key, if the + /// key is found; otherwise, an empty object. + /// This parameter is passed uninitialized. + /// true if the specified key has rules. + public bool TryGetValue(Type key, out IList rules) + { + return _rulesDictionary.TryGetValue(key, out rules); } /// @@ -164,7 +285,7 @@ public void Remove(Type type) /// The enumerator. public IEnumerator GetEnumerator() { - foreach (var ruleList in _rules.Values) + foreach (var ruleList in _rulesDictionary.Values) { foreach (var rule in ruleList) { @@ -173,15 +294,6 @@ public IEnumerator GetEnumerator() } } - /// - /// Get the enumerator. - /// - /// The enumerator. - IEnumerator IEnumerable.GetEnumerator() - { - return this.GetEnumerator(); - } - private static ValidationRuleSet BuildDefaultRuleSet() { var ruleSet = new ValidationRuleSet(); @@ -198,7 +310,7 @@ private static ValidationRuleSet BuildDefaultRuleSet() var propertyValue = property.GetValue(null); // static property if (propertyValue is ValidationRule rule) { - ruleSet.Add(rule); + ruleSet.Add(rule.ElementType, rule); } } @@ -215,17 +327,15 @@ internal static PropertyInfo[] GetValidationRuleTypes() ..typeof(OpenApiExternalDocsRules).GetProperties(BindingFlags.Static | BindingFlags.Public), ..typeof(OpenApiInfoRules).GetProperties(BindingFlags.Static | BindingFlags.Public), ..typeof(OpenApiLicenseRules).GetProperties(BindingFlags.Static | BindingFlags.Public), - ..typeof(OpenApiMediaTypeRules).GetProperties(BindingFlags.Static | BindingFlags.Public), ..typeof(OpenApiOAuthFlowRules).GetProperties(BindingFlags.Static | BindingFlags.Public), ..typeof(OpenApiServerRules).GetProperties(BindingFlags.Static | BindingFlags.Public), ..typeof(OpenApiResponseRules).GetProperties(BindingFlags.Static | BindingFlags.Public), ..typeof(OpenApiResponsesRules).GetProperties(BindingFlags.Static | BindingFlags.Public), ..typeof(OpenApiSchemaRules).GetProperties(BindingFlags.Static | BindingFlags.Public), - ..typeof(OpenApiHeaderRules).GetProperties(BindingFlags.Static | BindingFlags.Public), ..typeof(OpenApiTagRules).GetProperties(BindingFlags.Static | BindingFlags.Public), ..typeof(OpenApiPathsRules).GetProperties(BindingFlags.Static | BindingFlags.Public), - ..typeof(OpenApiParameterRules).GetProperties(BindingFlags.Static | BindingFlags.Public), - ]; + ..typeof(OpenApiParameterRules).GetProperties(BindingFlags.Static | BindingFlags.Public) + ]; } } } diff --git a/src/Microsoft.OpenApi/Writers/OpenApiJsonWriter.cs b/src/Microsoft.OpenApi/Writers/OpenApiJsonWriter.cs index ef8ef47b5..15b7b07f7 100644 --- a/src/Microsoft.OpenApi/Writers/OpenApiJsonWriter.cs +++ b/src/Microsoft.OpenApi/Writers/OpenApiJsonWriter.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; @@ -42,7 +42,7 @@ public OpenApiJsonWriter(TextWriter textWriter, OpenApiWriterSettings settings, /// /// Indicates whether or not the produced document will be written in a compact or pretty fashion. /// - private readonly bool _produceTerseOutput; + private readonly bool _produceTerseOutput = false; /// /// Base Indentation Level. @@ -251,6 +251,7 @@ public override void WriteIndentation() base.WriteIndentation(); } + /// /// Writes a line terminator to the text string or stream. /// diff --git a/src/Microsoft.OpenApi/Writers/OpenApiWriterAnyExtensions.cs b/src/Microsoft.OpenApi/Writers/OpenApiWriterAnyExtensions.cs index 6aa449901..b0ef0a174 100644 --- a/src/Microsoft.OpenApi/Writers/OpenApiWriterAnyExtensions.cs +++ b/src/Microsoft.OpenApi/Writers/OpenApiWriterAnyExtensions.cs @@ -2,13 +2,15 @@ // Licensed under the MIT license. using System.Collections.Generic; +using System.Text.Json; +using System.Text.Json.Nodes; using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Interfaces; namespace Microsoft.OpenApi.Writers { /// - /// Extensions methods for writing the + /// Extensions methods for writing the /// public static class OpenApiWriterAnyExtensions { @@ -27,51 +29,61 @@ public static void WriteExtensions(this IOpenApiWriter writer, IDictionary - /// Write the value. + /// Write the value. /// - /// The Open API Any type. /// The Open API writer. - /// The Any value - public static void WriteAny(this IOpenApiWriter writer, T any) where T : IOpenApiAny + /// The JsonNode value + public static void WriteAny(this IOpenApiWriter writer, JsonNode node) { - Utils.CheckArgumentNull(writer); + Utils.CheckArgumentNull(writer);; - if (any == null) + if (node == null) { writer.WriteNull(); return; } - switch (any.AnyType) + var element = JsonDocument.Parse(node.ToJsonString()).RootElement; + switch (element.ValueKind) { - case AnyType.Array: // Array - writer.WriteArray(any as OpenApiArray); + case JsonValueKind.Array: // Array + writer.WriteArray(node as JsonArray); break; - - case AnyType.Object: // Object - writer.WriteObject(any as OpenApiObject); + case JsonValueKind.Object: // Object + writer.WriteObject(node as JsonObject); break; - - case AnyType.Primitive: // Primitive - writer.WritePrimitive(any as IOpenApiPrimitive); + case JsonValueKind.String: // Primitive + writer.WritePrimitive(element); break; - - case AnyType.Null: // null + case JsonValueKind.Number: // Primitive + writer.WritePrimitive(element); + break; + case JsonValueKind.True or JsonValueKind.False: // Primitive + writer.WritePrimitive(element); + break; + case JsonValueKind.Null: // null writer.WriteNull(); break; - default: break; } } - private static void WriteArray(this IOpenApiWriter writer, OpenApiArray array) + private static void WriteArray(this IOpenApiWriter writer, JsonArray array) { writer.WriteStartArray(); @@ -83,7 +95,7 @@ private static void WriteArray(this IOpenApiWriter writer, OpenApiArray array) writer.WriteEndArray(); } - private static void WriteObject(this IOpenApiWriter writer, OpenApiObject entity) + private static void WriteObject(this IOpenApiWriter writer, JsonObject entity) { writer.WriteStartObject(); @@ -96,10 +108,53 @@ private static void WriteObject(this IOpenApiWriter writer, OpenApiObject entity writer.WriteEndObject(); } - private static void WritePrimitive(this IOpenApiWriter writer, IOpenApiPrimitive primitive) + private static void WritePrimitive(this IOpenApiWriter writer, JsonElement primitive) { - // The Spec version is meaning for the Any type, so it's ok to use the latest one. - primitive.Write(writer, OpenApiSpecVersion.OpenApi3_0); + if (writer == null) + { + Utils.CheckArgumentNull(writer); + } + + if (primitive.ValueKind == JsonValueKind.String) + { + // check whether string is actual string or date time object + if (primitive.TryGetDateTime(out var dateTime)) + { + writer.WriteValue(dateTime); + } + else if (primitive.TryGetDateTimeOffset(out var dateTimeOffset)) + { + writer.WriteValue(dateTimeOffset); + } + else + { + writer.WriteValue(primitive.GetString()); + } + } + + if (primitive.ValueKind == JsonValueKind.Number) + { + if (primitive.TryGetDecimal(out var decimalValue)) + { + writer.WriteValue(decimalValue); + } + else if (primitive.TryGetDouble(out var doubleValue)) + { + writer.WriteValue(doubleValue); + } + else if (primitive.TryGetInt64(out var longValue)) + { + writer.WriteValue(longValue); + } + else if (primitive.TryGetInt32(out var intValue)) + { + writer.WriteValue(intValue); + } + } + if (primitive.ValueKind is JsonValueKind.True or JsonValueKind.False) + { + writer.WriteValue(primitive.GetBoolean()); + } } } } diff --git a/src/Microsoft.OpenApi/Writers/OpenApiWriterBase.cs b/src/Microsoft.OpenApi/Writers/OpenApiWriterBase.cs index 5254288f3..99b148652 100644 --- a/src/Microsoft.OpenApi/Writers/OpenApiWriterBase.cs +++ b/src/Microsoft.OpenApi/Writers/OpenApiWriterBase.cs @@ -4,8 +4,14 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; +using System.Text.Json; +using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Exceptions; +using Microsoft.OpenApi.Extensions; +using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Properties; +using Microsoft.OpenApi.Services; namespace Microsoft.OpenApi.Writers { @@ -14,6 +20,7 @@ namespace Microsoft.OpenApi.Writers /// public abstract class OpenApiWriterBase : IOpenApiWriter { + /// /// Settings for controlling how the OpenAPI document will be written out. /// @@ -225,6 +232,10 @@ public virtual void WriteValue(object value) { WriteValue((int)value); } + else if (type == typeof(uint) || type == typeof(uint?)) + { + WriteValue((uint)value); + } else if (type == typeof(long) || type == typeof(long?)) { WriteValue((long)value); @@ -297,7 +308,7 @@ public virtual void WriteIndentation() Writer.Write(IndentationString); } } - + /// /// Get current scope. /// @@ -405,5 +416,28 @@ protected void VerifyCanWritePropertyName(string name) string.Format(SRResource.ObjectScopeNeededForPropertyNameWriting, name)); } } + + /// + public void WriteV2Examples(IOpenApiWriter writer, OpenApiExample example, OpenApiSpecVersion version) + { + writer.WriteStartObject(); + + // summary + writer.WriteProperty(OpenApiConstants.Summary, example.Summary); + + // description + writer.WriteProperty(OpenApiConstants.Description, example.Description); + + // value + writer.WriteOptionalObject(OpenApiConstants.Value, example.Value, (w, v) => w.WriteAny(v)); + + // externalValue + writer.WriteProperty(OpenApiConstants.ExternalValue, example.ExternalValue); + + // extensions + writer.WriteExtensions(example.Extensions, version); + + writer.WriteEndObject(); + } } } diff --git a/src/Microsoft.OpenApi/Writers/OpenApiWriterExtensions.cs b/src/Microsoft.OpenApi/Writers/OpenApiWriterExtensions.cs index 7b85dd680..a1a74e815 100644 --- a/src/Microsoft.OpenApi/Writers/OpenApiWriterExtensions.cs +++ b/src/Microsoft.OpenApi/Writers/OpenApiWriterExtensions.cs @@ -125,6 +125,7 @@ public static void WriteProperty(this IOpenApiWriter writer, string name, T v writer.WriteValue(value); } +#nullable enable /// /// Write the optional Open API object/element. /// @@ -136,9 +137,8 @@ public static void WriteProperty(this IOpenApiWriter writer, string name, T v public static void WriteOptionalObject( this IOpenApiWriter writer, string name, - T value, + T? value, Action action) - where T : IOpenApiElement { if (value != null) { @@ -162,9 +162,8 @@ public static void WriteOptionalObject( public static void WriteRequiredObject( this IOpenApiWriter writer, string name, - T value, + T? value, Action action) - where T : IOpenApiElement { Utils.CheckArgumentNull(action); @@ -179,6 +178,7 @@ public static void WriteRequiredObject( writer.WriteEndObject(); } } +#nullable restore /// /// Write the optional of collection string. @@ -212,7 +212,6 @@ public static void WriteOptionalCollection( string name, IEnumerable elements, Action action) - where T : IOpenApiElement { if (elements != null && elements.Any()) { @@ -238,6 +237,22 @@ public static void WriteRequiredCollection( writer.WriteCollectionInternal(name, elements, action); } + /// + /// Write the required 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 WriteRequiredMap( + this IOpenApiWriter writer, + string name, + IDictionary elements, + Action action) + { + writer.WriteMapInternal(name, elements, action); + } + /// /// Write the optional Open API element map (string to string mapping). /// @@ -258,19 +273,22 @@ public static void WriteOptionalMap( } /// - /// Write the required Open API element map (string to string mapping). + /// 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 WriteRequiredMap( + public static void WriteOptionalMap( this IOpenApiWriter writer, string name, - IDictionary elements, - Action action) + IDictionary elements, + Action action) { - writer.WriteMapInternal(name, elements, action); + if (elements != null && elements.Any()) + { + writer.WriteMapInternal(name, elements, action); + } } /// @@ -360,7 +378,7 @@ private static void WriteCollectionInternal( writer.WriteEndArray(); } - + private static void WriteMapInternal( this IOpenApiWriter writer, string name, diff --git a/src/Microsoft.OpenApi/Writers/OpenApiWriterSettings.cs b/src/Microsoft.OpenApi/Writers/OpenApiWriterSettings.cs index dda995b0f..c647436ea 100644 --- a/src/Microsoft.OpenApi/Writers/OpenApiWriterSettings.cs +++ b/src/Microsoft.OpenApi/Writers/OpenApiWriterSettings.cs @@ -1,4 +1,4 @@ - + using System; using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Services; @@ -38,11 +38,13 @@ public class OpenApiWriterSettings /// Indicates how references in the source document should be handled. /// [Obsolete("Use InlineLocalReference and InlineExternalReference settings instead")] - public ReferenceInlineSetting ReferenceInline { + public ReferenceInlineSetting ReferenceInline + { get { return referenceInline; } - set { + set + { referenceInline = value; - switch(referenceInline) + switch (referenceInline) { case ReferenceInlineSetting.DoNotInlineReferences: InlineLocalReferences = false; @@ -74,5 +76,10 @@ internal bool ShouldInlineReference(OpenApiReference reference) return (reference.IsLocal && InlineLocalReferences) || (reference.IsExternal && InlineExternalReferences); } + + internal bool ShouldInlineReference() + { + return InlineLocalReferences || InlineExternalReferences; + } } } diff --git a/src/Microsoft.OpenApi/Writers/OpenApiYamlWriter.cs b/src/Microsoft.OpenApi/Writers/OpenApiYamlWriter.cs index 9ffb0c10e..7f525d642 100644 --- a/src/Microsoft.OpenApi/Writers/OpenApiYamlWriter.cs +++ b/src/Microsoft.OpenApi/Writers/OpenApiYamlWriter.cs @@ -25,7 +25,7 @@ public OpenApiYamlWriter(TextWriter textWriter) : this(textWriter, null) /// public OpenApiYamlWriter(TextWriter textWriter, OpenApiWriterSettings settings) : base(textWriter, settings) { - + } /// @@ -169,7 +169,7 @@ public override void WritePropertyName(string name) /// The string value. public override void WriteValue(string value) { - if (!UseLiteralStyle || value.IndexOfAny(new [] { '\n', '\r' }) == -1) + if (!UseLiteralStyle || value.IndexOfAny(new[] { '\n', '\r' }) == -1) { WriteValueSeparator(); diff --git a/test/Microsoft.OpenApi.Hidi.Tests/Formatters/PowerShellFormatterTests.cs b/test/Microsoft.OpenApi.Hidi.Tests/Formatters/PowerShellFormatterTests.cs index a5bf74219..110cac88c 100644 --- a/test/Microsoft.OpenApi.Hidi.Tests/Formatters/PowerShellFormatterTests.cs +++ b/test/Microsoft.OpenApi.Hidi.Tests/Formatters/PowerShellFormatterTests.cs @@ -57,18 +57,21 @@ public void RemoveAnyOfAndOneOfFromSchema() var walker = new OpenApiWalker(powerShellFormatter); walker.Walk(openApiDocument); - var testSchema = openApiDocument.Components.Schemas["TestSchema"]; - var averageAudioDegradationProperty = testSchema.Properties["averageAudioDegradation"]; - var defaultPriceProperty = testSchema.Properties["defaultPrice"]; + var testSchema = openApiDocument.Components?.Schemas?["TestSchema"]; + var averageAudioDegradationProperty = testSchema?.Properties["averageAudioDegradation"]; + var defaultPriceProperty = testSchema?.Properties["defaultPrice"]; // Assert - Assert.Null(averageAudioDegradationProperty.AnyOf); - Assert.Equal("number", averageAudioDegradationProperty.Type); - Assert.Equal("float", averageAudioDegradationProperty.Format); - Assert.True(averageAudioDegradationProperty.Nullable); - Assert.Null(defaultPriceProperty.OneOf); - Assert.Equal("number", defaultPriceProperty.Type); - Assert.Equal("double", defaultPriceProperty.Format); + Assert.NotNull(openApiDocument.Components); + Assert.NotNull(openApiDocument.Components.Schemas); + Assert.NotNull(testSchema); + Assert.Null(averageAudioDegradationProperty?.AnyOf); + Assert.Equal(JsonSchemaType.Number, averageAudioDegradationProperty?.Type); + Assert.Equal("float", averageAudioDegradationProperty?.Format); + Assert.True(averageAudioDegradationProperty?.Nullable); + Assert.Null(defaultPriceProperty?.OneOf); + Assert.Equal(JsonSchemaType.Number, defaultPriceProperty?.Type); + Assert.Equal("double", defaultPriceProperty?.Format); Assert.NotNull(testSchema.AdditionalProperties); } @@ -83,12 +86,12 @@ public void ResolveFunctionParameters() var walker = new OpenApiWalker(powerShellFormatter); walker.Walk(openApiDocument); - var idsParameter = openApiDocument.Paths["/foo"].Operations[OperationType.Get].Parameters.Where(static p => p.Name == "ids").FirstOrDefault(); + var idsParameter = openApiDocument.Paths["/foo"].Operations[OperationType.Get].Parameters?.Where(static p => p.Name == "ids").FirstOrDefault(); // Assert Assert.Null(idsParameter?.Content); Assert.NotNull(idsParameter?.Schema); - Assert.Equal("array", idsParameter?.Schema.Type); + Assert.Equal(JsonSchemaType.Array, idsParameter?.Schema.Type); } private static OpenApiDocument GetSampleOpenApiDocument() @@ -120,10 +123,10 @@ private static OpenApiDocument GetSampleOpenApiDocument() { Schema = new() { - Type = "array", + Type = JsonSchemaType.Array, Items = new() { - Type = "string" + Type = JsonSchemaType.String } } } @@ -134,7 +137,7 @@ private static OpenApiDocument GetSampleOpenApiDocument() Extensions = new Dictionary { { - "x-ms-docs-operation-type", new OpenApiString("function") + "x-ms-docs-operation-type", new OpenApiAny("function") } } } @@ -149,7 +152,7 @@ private static OpenApiDocument GetSampleOpenApiDocument() { { "TestSchema", new OpenApiSchema { - Type = "object", + Type = JsonSchemaType.Object, Properties = new Dictionary { { @@ -157,8 +160,8 @@ private static OpenApiDocument GetSampleOpenApiDocument() { AnyOf = new List { - new() { Type = "number" }, - new() { Type = "string" } + new() { Type = JsonSchemaType.Number }, + new() { Type = JsonSchemaType.String } }, Format = "float", Nullable = true @@ -169,14 +172,14 @@ private static OpenApiDocument GetSampleOpenApiDocument() { OneOf = new List { - new() { Type = "number", Format = "double" }, - new() { Type = "string" } + new() { Type = JsonSchemaType.Number, Format = "double" }, + new() { Type = JsonSchemaType.String } } } } } } - } + } } } }; diff --git a/test/Microsoft.OpenApi.Hidi.Tests/Services/OpenApiFilterServiceTests.cs b/test/Microsoft.OpenApi.Hidi.Tests/Services/OpenApiFilterServiceTests.cs index ebb863461..3bd9efd2a 100644 --- a/test/Microsoft.OpenApi.Hidi.Tests/Services/OpenApiFilterServiceTests.cs +++ b/test/Microsoft.OpenApi.Hidi.Tests/Services/OpenApiFilterServiceTests.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 Microsoft.Extensions.Logging; @@ -131,7 +131,7 @@ public void CreateFilteredDocumentUsingPredicateFromRequestUrl() Required = true, Schema = new() { - Type = "string" + Type = JsonSchemaType.String } } } @@ -232,22 +232,24 @@ public void CopiesOverAllReferencedComponentsToTheSubsetDocumentCorrectly() // Act using var stream = File.OpenRead(filePath); - var doc = new OpenApiStreamReader().Read(stream, out var diagnostic); + var doc = OpenApiDocument.Load(stream, "yaml").OpenApiDocument; var predicate = OpenApiFilterService.CreatePredicate(operationIds: operationIds); var subsetOpenApiDocument = OpenApiFilterService.CreateFilteredDocument(doc, predicate); - var response = subsetOpenApiDocument.Paths["/items"].Operations[OperationType.Get].Responses["200"]; - var responseHeader = response.Headers["x-custom-header"]; - var mediaTypeExample = response.Content["application/json"].Examples.First().Value; - var targetHeaders = subsetOpenApiDocument.Components.Headers; - var targetExamples = subsetOpenApiDocument.Components.Examples; + var response = subsetOpenApiDocument.Paths["/items"].Operations[OperationType.Get]?.Responses?["200"]; + var responseHeader = response?.Headers["x-custom-header"]; + var mediaTypeExample = response?.Content["application/json"]?.Examples?.First().Value; + var targetHeaders = subsetOpenApiDocument.Components?.Headers; + var targetExamples = subsetOpenApiDocument.Components?.Examples; // Assert Assert.Same(doc.Servers, subsetOpenApiDocument.Servers); - Assert.False(responseHeader.UnresolvedReference); - Assert.False(mediaTypeExample.UnresolvedReference); + Assert.False(responseHeader?.UnresolvedReference); + Assert.False(mediaTypeExample?.UnresolvedReference); + Assert.NotNull(targetHeaders); Assert.Single(targetHeaders); + Assert.NotNull(targetExamples); Assert.Single(targetExamples); } diff --git a/test/Microsoft.OpenApi.Hidi.Tests/Services/OpenApiServiceTests.cs b/test/Microsoft.OpenApi.Hidi.Tests/Services/OpenApiServiceTests.cs index a7ab42c03..798b7532e 100644 --- a/test/Microsoft.OpenApi.Hidi.Tests/Services/OpenApiServiceTests.cs +++ b/test/Microsoft.OpenApi.Hidi.Tests/Services/OpenApiServiceTests.cs @@ -11,6 +11,8 @@ using Microsoft.OpenApi.Hidi.Utilities; using Microsoft.OpenApi.Models; using Microsoft.OpenApi.OData; +using Microsoft.OpenApi.Reader; +using Microsoft.OpenApi.Readers; using Microsoft.OpenApi.Services; using Xunit; @@ -24,45 +26,8 @@ public sealed class OpenApiServiceTests : IDisposable public OpenApiServiceTests() { _logger = new Logger(_loggerFactory); - } - - [Fact] - public async Task ReturnConvertedCSDLFileAsync() - { - // Arrange - var filePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "UtilityFiles", "Todo.xml"); - var fileInput = new FileInfo(filePath); - var csdlStream = fileInput.OpenRead(); - // Act - var openApiDoc = await OpenApiService.ConvertCsdlToOpenApiAsync(csdlStream); - var expectedPathCount = 5; - - // Assert - Assert.NotNull(openApiDoc); - Assert.NotEmpty(openApiDoc.Paths); - Assert.Equal(expectedPathCount, openApiDoc.Paths.Count); - } - - [Theory] - [InlineData("Todos.Todo.UpdateTodo", null, 1)] - [InlineData("Todos.Todo.ListTodo", null, 1)] - [InlineData(null, "Todos.Todo", 5)] - public async Task ReturnFilteredOpenApiDocBasedOnOperationIdsAndInputCsdlDocumentAsync(string? operationIds, string? tags, int expectedPathCount) - { - // Arrange - var filePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "UtilityFiles", "Todo.xml"); - var fileInput = new FileInfo(filePath); - var csdlStream = fileInput.OpenRead(); - - // Act - var openApiDoc = await OpenApiService.ConvertCsdlToOpenApiAsync(csdlStream); - var predicate = OpenApiFilterService.CreatePredicate(operationIds, tags); - var subsetOpenApiDocument = OpenApiFilterService.CreateFilteredDocument(openApiDoc, predicate); - - // Assert - Assert.NotNull(subsetOpenApiDocument); - Assert.NotEmpty(subsetOpenApiDocument.Paths); - Assert.Equal(expectedPathCount, subsetOpenApiDocument.Paths.Count); + OpenApiReaderRegistry.RegisterReader(OpenApiConstants.Yml, new OpenApiYamlReader()); + OpenApiReaderRegistry.RegisterReader(OpenApiConstants.Yaml, new OpenApiYamlReader()); } [Fact] @@ -196,23 +161,6 @@ public async Task ShowCommandGeneratesMermaidHtmlFileWithMermaidDiagramAsync() Assert.True(File.Exists(filePath)); } - [Fact] - public async Task ShowCommandGeneratesMermaidMarkdownFileFromCsdlWithMermaidDiagramAsync() - { - var options = new HidiOptions - { - Csdl = Path.Combine("UtilityFiles", "Todo.xml"), - CsdlFilter = "todos", - Output = new("sample.md") - }; - - // create a dummy ILogger instance for testing - await OpenApiService.ShowOpenApiDocumentAsync(options, _logger); - - var output = await File.ReadAllTextAsync(options.Output.FullName); - Assert.Contains("graph LR", output, StringComparison.Ordinal); - } - [Fact] public Task ThrowIfOpenApiUrlIsNotProvidedWhenValidatingAsync() { @@ -307,24 +255,6 @@ public async Task TransformCommandConvertsOpenApiWithDefaultOutputNameAsync() Assert.NotEmpty(output); } - [Fact] - public async Task TransformCommandConvertsCsdlWithDefaultOutputNameAsync() - { - var options = new HidiOptions - { - Csdl = Path.Combine("UtilityFiles", "Todo.xml"), - CleanOutput = true, - TerseOutput = false, - InlineLocal = false, - InlineExternal = false, - }; - // create a dummy ILogger instance for testing - await OpenApiService.TransformOpenApiDocumentAsync(options, _logger); - - var output = await File.ReadAllTextAsync("output.yml"); - Assert.NotEmpty(output); - } - [Fact] public async Task TransformCommandConvertsOpenApiWithDefaultOutputNameAndSwitchFormatAsync() { @@ -377,7 +307,7 @@ public async Task TransformToPowerShellCompliantOpenApiAsync() // create a dummy ILogger instance for testing await OpenApiService.TransformOpenApiDocumentAsync(options, _logger); - var output = await File.ReadAllTextAsync("output.yml"); + var output = await File.ReadAllTextAsync("output.yaml"); Assert.NotEmpty(output); } diff --git a/test/Microsoft.OpenApi.Hidi.Tests/UtilityFiles/OpenApiDocumentMock.cs b/test/Microsoft.OpenApi.Hidi.Tests/UtilityFiles/OpenApiDocumentMock.cs index 67b06f72a..91dd59919 100644 --- a/test/Microsoft.OpenApi.Hidi.Tests/UtilityFiles/OpenApiDocumentMock.cs +++ b/test/Microsoft.OpenApi.Hidi.Tests/UtilityFiles/OpenApiDocumentMock.cs @@ -85,7 +85,7 @@ public static OpenApiDocument CreateOpenApiDocument() Required = true, Schema = new() { - Type = "string" + Type = JsonSchemaType.String } } } @@ -104,7 +104,7 @@ public static OpenApiDocument CreateOpenApiDocument() { Schema = new() { - Type = "array" + Type = JsonSchemaType.Array } } } @@ -125,7 +125,7 @@ public static OpenApiDocument CreateOpenApiDocument() Required = true, Schema = new() { - Type = "string" + Type = JsonSchemaType.String } } } @@ -159,7 +159,7 @@ public static OpenApiDocument CreateOpenApiDocument() Required = true, Schema = new() { - Type = "string" + Type = JsonSchemaType.String } } } @@ -178,7 +178,7 @@ public static OpenApiDocument CreateOpenApiDocument() { Schema = new() { - Type = "array" + Type = JsonSchemaType.Array } } } @@ -198,7 +198,7 @@ public static OpenApiDocument CreateOpenApiDocument() Required = true, Schema = new() { - Type = "string" + Type = JsonSchemaType.String } } } @@ -236,14 +236,14 @@ public static OpenApiDocument CreateOpenApiDocument() Schema = new() { Title = "Collection of user", - Type = "object", + Type = JsonSchemaType.Object, Properties = new Dictionary { { "value", new OpenApiSchema { - Type = "array", + Type = JsonSchemaType.Array, Items = new() { Reference = new() @@ -368,7 +368,7 @@ public static OpenApiDocument CreateOpenApiDocument() Description = "Select properties to be returned", Schema = new() { - Type = "array" + Type = JsonSchemaType.Array } // missing explode parameter } @@ -432,7 +432,7 @@ public static OpenApiDocument CreateOpenApiDocument() Description = "key: id of administrativeUnit", Schema = new() { - Type = "string" + Type = JsonSchemaType.String } } } @@ -455,7 +455,7 @@ public static OpenApiDocument CreateOpenApiDocument() { new() { - Type = "string" + Type = JsonSchemaType.String } }, Nullable = true @@ -534,14 +534,14 @@ public static OpenApiDocument CreateOpenApiDocument() Schema = new() { Title = "Collection of hostSecurityProfile", - Type = "object", + Type = JsonSchemaType.Object, Properties = new Dictionary { { "value", new OpenApiSchema { - Type = "array", + Type = JsonSchemaType.Array, Items = new() { Reference = new() @@ -592,12 +592,12 @@ public static OpenApiDocument CreateOpenApiDocument() Required = true, Schema = new() { - Type = "string" + Type = JsonSchemaType.String }, Extensions = new Dictionary { { - "x-ms-docs-key-type", new OpenApiString("call") + "x-ms-docs-key-type", new OpenApiAny("call") } } } @@ -614,7 +614,7 @@ public static OpenApiDocument CreateOpenApiDocument() Extensions = new Dictionary { { - "x-ms-docs-operation-type", new OpenApiString("action") + "x-ms-docs-operation-type", new OpenApiAny("action") } } } @@ -647,12 +647,12 @@ public static OpenApiDocument CreateOpenApiDocument() Required = true, Schema = new() { - Type = "string" + Type = JsonSchemaType.String }, Extensions = new Dictionary { { - "x-ms-docs-key-type", new OpenApiString("group") + "x-ms-docs-key-type", new OpenApiAny("group") } } }, @@ -664,12 +664,12 @@ public static OpenApiDocument CreateOpenApiDocument() Required = true, Schema = new() { - Type = "string" + Type = JsonSchemaType.String }, Extensions = new Dictionary { { - "x-ms-docs-key-type", new OpenApiString("event") + "x-ms-docs-key-type", new OpenApiAny("event") } } } @@ -688,7 +688,7 @@ public static OpenApiDocument CreateOpenApiDocument() { Schema = new() { - Type = "array", + Type = JsonSchemaType.Array, Reference = new() { Type = ReferenceType.Schema, @@ -704,7 +704,7 @@ public static OpenApiDocument CreateOpenApiDocument() Extensions = new Dictionary { { - "x-ms-docs-operation-type", new OpenApiString("function") + "x-ms-docs-operation-type", new OpenApiAny("function") } } } @@ -740,13 +740,13 @@ public static OpenApiDocument CreateOpenApiDocument() "microsoft.graph.networkInterface", new OpenApiSchema { Title = "networkInterface", - Type = "object", + Type = JsonSchemaType.Object, Properties = new Dictionary { { "description", new OpenApiSchema { - Type = "string", + Type = JsonSchemaType.String, Description = "Description of the NIC (e.g. Ethernet adapter, Wireless LAN adapter Local Area Connection <#>, etc.).", Nullable = true } 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 ae5d9f577..aff884556 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/Microsoft.OpenApi.Readers.Tests.csproj +++ b/test/Microsoft.OpenApi.Readers.Tests/Microsoft.OpenApi.Readers.Tests.csproj @@ -1,4 +1,4 @@ - + net8.0 false @@ -6,11 +6,11 @@ ..\..\src\Microsoft.OpenApi.snk - - Never + + Always - - Never + + Always diff --git a/test/Microsoft.OpenApi.Readers.Tests/OpenApiReaderTests/OpenApiDiagnosticTests.cs b/test/Microsoft.OpenApi.Readers.Tests/OpenApiReaderTests/OpenApiDiagnosticTests.cs index 0977dbcd9..5ecd58071 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/OpenApiReaderTests/OpenApiDiagnosticTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/OpenApiReaderTests/OpenApiDiagnosticTests.cs @@ -8,56 +8,58 @@ using Microsoft.OpenApi.Exceptions; using Microsoft.OpenApi.Models; using Xunit; -using Microsoft.OpenApi.Readers.Interface; using System.IO; +using Microsoft.OpenApi.Interfaces; +using Microsoft.OpenApi.Reader; namespace Microsoft.OpenApi.Readers.Tests.OpenApiReaderTests { [Collection("DefaultSettings")] public class OpenApiDiagnosticTests { + public OpenApiDiagnosticTests() + { + OpenApiReaderRegistry.RegisterReader(OpenApiConstants.Yaml, new OpenApiYamlReader()); + } + [Fact] public void DetectedSpecificationVersionShouldBeV2_0() { - using var stream = Resources.GetStream("V2Tests/Samples/basic.v2.yaml"); - new OpenApiStreamReader().Read(stream, out var diagnostic); + var actual = OpenApiDocument.Load("V2Tests/Samples/basic.v2.yaml"); - diagnostic.Should().NotBeNull(); - diagnostic.SpecificationVersion.Should().Be(OpenApiSpecVersion.OpenApi2_0); + actual.OpenApiDiagnostic.Should().NotBeNull(); + actual.OpenApiDiagnostic.SpecificationVersion.Should().Be(OpenApiSpecVersion.OpenApi2_0); } [Fact] public void DetectedSpecificationVersionShouldBeV3_0() { - using var stream = Resources.GetStream("V3Tests/Samples/OpenApiDocument/minimalDocument.yaml"); - new OpenApiStreamReader().Read(stream, out var diagnostic); + var actual = OpenApiDocument.Load("V3Tests/Samples/OpenApiDocument/minimalDocument.yaml"); - diagnostic.Should().NotBeNull(); - diagnostic.SpecificationVersion.Should().Be(OpenApiSpecVersion.OpenApi3_0); + actual.OpenApiDiagnostic.Should().NotBeNull(); + actual.OpenApiDiagnostic.SpecificationVersion.Should().Be(OpenApiSpecVersion.OpenApi3_0); } [Fact] public async Task DiagnosticReportMergedForExternalReferenceAsync() { // Create a reader that will resolve all references - var reader = new OpenApiStreamReader(new() + var settings = new OpenApiReaderSettings { LoadExternalRefs = true, CustomExternalLoader = new ResourceLoader(), BaseUrl = new("fie://c:\\") - }); + }; ReadResult result; - using (var stream = Resources.GetStream("OpenApiReaderTests/Samples/OpenApiDiagnosticReportMerged/TodoMain.yaml")) - { - result = await reader.ReadAsync(stream); - } + result = await OpenApiDocument.LoadAsync("OpenApiReaderTests/Samples/OpenApiDiagnosticReportMerged/TodoMain.yaml", settings); Assert.NotNull(result); Assert.NotNull(result.OpenApiDocument.Workspace); - Assert.True(result.OpenApiDocument.Workspace.Contains("TodoReference.yaml")); - result.OpenApiDiagnostic.Errors.Should().BeEquivalentTo(new List { - new( new OpenApiException("[File: ./TodoReference.yaml] Invalid Reference identifier 'object-not-existing'.")) }); + result.OpenApiDiagnostic.Errors.Should().BeEquivalentTo(new List + { + new OpenApiError("", "[File: ./TodoReference.yaml] Paths is a REQUIRED field at #/") + }); } } diff --git a/test/Microsoft.OpenApi.Readers.Tests/OpenApiReaderTests/OpenApiStreamReaderTests.cs b/test/Microsoft.OpenApi.Readers.Tests/OpenApiReaderTests/OpenApiStreamReaderTests.cs index 1b2faa137..82a410946 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/OpenApiReaderTests/OpenApiStreamReaderTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/OpenApiReaderTests/OpenApiStreamReaderTests.cs @@ -5,6 +5,8 @@ using System.IO; using System.Net.Http; using System.Threading.Tasks; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Reader; using Xunit; namespace Microsoft.OpenApi.Readers.Tests.OpenApiReaderTests @@ -13,12 +15,17 @@ public class OpenApiStreamReaderTests { private const string SampleFolderPath = "V3Tests/Samples/OpenApiDocument/"; + public OpenApiStreamReaderTests() + { + OpenApiReaderRegistry.RegisterReader("yaml", new OpenApiYamlReader()); + } + [Fact] public void StreamShouldCloseIfLeaveStreamOpenSettingEqualsFalse() { using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "petStore.yaml")); - var reader = new OpenApiStreamReader(new() { LeaveStreamOpen = false }); - reader.Read(stream, out _); + var settings = new OpenApiReaderSettings { LeaveStreamOpen = false }; + _ = OpenApiDocument.Load(stream, "yaml", settings); Assert.False(stream.CanRead); } @@ -26,8 +33,8 @@ public void StreamShouldCloseIfLeaveStreamOpenSettingEqualsFalse() public void StreamShouldNotCloseIfLeaveStreamOpenSettingEqualsTrue() { using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "petStore.yaml")); - var reader = new OpenApiStreamReader(new() { LeaveStreamOpen = true}); - reader.Read(stream, out _); + var settings = new OpenApiReaderSettings { LeaveStreamOpen = true }; + _ = OpenApiDocument.Load(stream, "yaml", settings); Assert.True(stream.CanRead); } @@ -41,8 +48,7 @@ public async Task StreamShouldNotBeDisposedIfLeaveStreamOpenSettingIsTrueAsync() memoryStream.Position = 0; var stream = memoryStream; - var reader = new OpenApiStreamReader(new() { LeaveStreamOpen = true }); - _ = await reader.ReadAsync(stream); + var result = OpenApiDocument.Load(stream, "yaml", new OpenApiReaderSettings { LeaveStreamOpen = true }); stream.Seek(0, SeekOrigin.Begin); // does not throw an object disposed exception Assert.True(stream.CanRead); } @@ -55,11 +61,11 @@ public async Task StreamShouldReadWhenInitializedAsync() 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("20fe7a7b720a0e48e5842d002ac418b12a8201df/tests/v3.0/pass/petstore.yaml"); // Read V3 as YAML - var openApiDocument = new OpenApiStreamReader().Read(stream, out var diagnostic); - Assert.NotNull(openApiDocument); + var result = OpenApiDocument.Load(stream, "yaml"); + Assert.NotNull(result.OpenApiDocument); } } } diff --git a/test/Microsoft.OpenApi.Readers.Tests/OpenApiReaderTests/Samples/OpenApiDiagnosticReportMerged/TodoReference.yaml b/test/Microsoft.OpenApi.Readers.Tests/OpenApiReaderTests/Samples/OpenApiDiagnosticReportMerged/TodoReference.yaml index db3958149..98cd3d40a 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/OpenApiReaderTests/Samples/OpenApiDiagnosticReportMerged/TodoReference.yaml +++ b/test/Microsoft.OpenApi.Readers.Tests/OpenApiReaderTests/Samples/OpenApiDiagnosticReportMerged/TodoReference.yaml @@ -23,4 +23,4 @@ components: type: object properties: id: - type:string \ No newline at end of file + type: string \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Readers.Tests/OpenApiReaderTests/UnsupportedSpecVersionTests.cs b/test/Microsoft.OpenApi.Readers.Tests/OpenApiReaderTests/UnsupportedSpecVersionTests.cs index 6c906ec5b..0b044e78b 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/OpenApiReaderTests/UnsupportedSpecVersionTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/OpenApiReaderTests/UnsupportedSpecVersionTests.cs @@ -2,7 +2,8 @@ // Licensed under the MIT license. using FluentAssertions; -using Microsoft.OpenApi.Readers.Exceptions; +using Microsoft.OpenApi.Exceptions; +using Microsoft.OpenApi.Models; using Xunit; namespace Microsoft.OpenApi.Readers.Tests.OpenApiReaderTests @@ -13,10 +14,9 @@ public class UnsupportedSpecVersionTests [Fact] public void ThrowOpenApiUnsupportedSpecVersionException() { - using var stream = Resources.GetStream("OpenApiReaderTests/Samples/unsupported.v1.yaml"); try { - new OpenApiStreamReader().Read(stream, out var diagnostic); + _ = OpenApiDocument.Load("OpenApiReaderTests/Samples/unsupported.v1.yaml"); } catch (OpenApiUnsupportedSpecVersionException exception) { diff --git a/test/Microsoft.OpenApi.Readers.Tests/OpenApiWorkspaceTests/OpenApiWorkspaceStreamTests.cs b/test/Microsoft.OpenApi.Readers.Tests/OpenApiWorkspaceTests/OpenApiWorkspaceStreamTests.cs index d72594620..4aca9b54e 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/OpenApiWorkspaceTests/OpenApiWorkspaceStreamTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/OpenApiWorkspaceTests/OpenApiWorkspaceStreamTests.cs @@ -1,27 +1,34 @@ -using System; +using System; using System.IO; -using System.Linq; using System.Threading.Tasks; +using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Models; -using Microsoft.OpenApi.Readers.Interface; +using Microsoft.OpenApi.Reader; using Xunit; namespace Microsoft.OpenApi.Readers.Tests.OpenApiWorkspaceTests { public class OpenApiWorkspaceStreamTests { + private const string SampleFolderPath = "V3Tests/Samples/OpenApiWorkspace/"; + + public OpenApiWorkspaceStreamTests() + { + OpenApiReaderRegistry.RegisterReader(OpenApiConstants.Yaml, new OpenApiYamlReader()); + } + // Use OpenApiWorkspace to load a document and a referenced document [Fact] public async Task LoadingDocumentWithResolveAllReferencesShouldLoadDocumentIntoWorkspaceAsync() { // Create a reader that will resolve all references - var reader = new OpenApiStreamReader(new() + var settings = new OpenApiReaderSettings { LoadExternalRefs = true, CustomExternalLoader = new MockLoader(), BaseUrl = new("file://c:\\") - }); + }; // Todo: this should be ReadAsync var stream = new MemoryStream(); @@ -37,7 +44,7 @@ public async Task LoadingDocumentWithResolveAllReferencesShouldLoadDocumentIntoW await wr.FlushAsync(); stream.Position = 0; - var result = await reader.ReadAsync(stream); + var result = await OpenApiDocument.LoadAsync(stream, OpenApiConstants.Yaml, settings: settings); Assert.NotNull(result.OpenApiDocument.Workspace); } @@ -46,42 +53,25 @@ public async Task LoadingDocumentWithResolveAllReferencesShouldLoadDocumentIntoW public async Task LoadDocumentWithExternalReferenceShouldLoadBothDocumentsIntoWorkspaceAsync() { // Create a reader that will resolve all references - var reader = new OpenApiStreamReader(new() + var settings = new OpenApiReaderSettings { LoadExternalRefs = true, CustomExternalLoader = new ResourceLoader(), - BaseUrl = new("fie://c:\\") - }); + BaseUrl = new("file://c:\\"), + }; ReadResult result; - using (var stream = Resources.GetStream("V3Tests/Samples/OpenApiWorkspace/TodoMain.yaml")) - { - result = await reader.ReadAsync(stream); - } + result = await OpenApiDocument.LoadAsync("V3Tests/Samples/OpenApiWorkspace/TodoMain.yaml", settings); - Assert.NotNull(result.OpenApiDocument.Workspace); - Assert.True(result.OpenApiDocument.Workspace.Contains("TodoComponents.yaml")); - - var referencedSchema = result.OpenApiDocument - .Paths["/todos"] - .Operations[OperationType.Get] - .Responses["200"] - .Content["application/json"] - .Schema.GetEffective(result.OpenApiDocument); - Assert.Equal("object", referencedSchema.Type); - Assert.Equal("string", referencedSchema.Properties["subject"].Type); - Assert.False(referencedSchema.UnresolvedReference); + var externalDocBaseUri = result.OpenApiDocument.Workspace.GetDocumentId("./TodoComponents.yaml"); + var schemasPath = "/components/schemas/"; + var parametersPath = "/components/parameters/"; - var referencedParameter = result.OpenApiDocument - .Paths["/todos"] - .Operations[OperationType.Get] - .Parameters - .Select(p => p.GetEffective(result.OpenApiDocument)) - .FirstOrDefault(p => p.Name == "filter"); - - Assert.Equal("string", referencedParameter.Schema.Type); + 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")); } - } public class MockLoader : IStreamLoader diff --git a/test/Microsoft.OpenApi.Readers.Tests/ParseNodeTests.cs b/test/Microsoft.OpenApi.Readers.Tests/ParseNodeTests.cs index f1af6f933..3f7c669b0 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/ParseNodeTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/ParseNodeTests.cs @@ -3,15 +3,21 @@ using System.Collections.Generic; using FluentAssertions; +using Microsoft.OpenApi.Exceptions; using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Reader; using Microsoft.OpenApi.Readers; -using Microsoft.OpenApi.Readers.Exceptions; using Xunit; namespace Microsoft.OpenApi.Tests { public class ParseNodeTests { + public ParseNodeTests() + { + OpenApiReaderRegistry.RegisterReader("yaml", new OpenApiYamlReader()); + } + [Fact] public void BrokenSimpleList() { @@ -25,14 +31,11 @@ public void BrokenSimpleList() paths: { } """; - var reader = new OpenApiStringReader(); - reader.Read(input, out var diagnostic); + var result = OpenApiDocument.Parse(input, "yaml"); - diagnostic.Errors.Should().BeEquivalentTo(new List - { - new(new OpenApiReaderException("Expected a value.") { - Pointer = "#line=4" - }) + result.OpenApiDiagnostic.Errors.Should().BeEquivalentTo(new List() { + new OpenApiError(new OpenApiReaderException("Expected a value.")), + new OpenApiError("", "Paths is a REQUIRED field at #/") }); } @@ -55,10 +58,9 @@ public void BadSchema() schema: asdasd """; - var reader = new OpenApiStringReader(); - reader.Read(input, out var diagnostic); + var res= OpenApiDocument.Parse(input, "yaml"); - diagnostic.Errors.Should().BeEquivalentTo(new List + res.OpenApiDiagnostic.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/ParseNodes/OpenApiAnyConverterTests.cs b/test/Microsoft.OpenApi.Readers.Tests/ParseNodes/OpenApiAnyConverterTests.cs deleted file mode 100644 index 0f36d7d89..000000000 --- a/test/Microsoft.OpenApi.Readers.Tests/ParseNodes/OpenApiAnyConverterTests.cs +++ /dev/null @@ -1,519 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. - -using System; -using System.Globalization; -using System.IO; -using System.Linq; -using FluentAssertions; -using Microsoft.OpenApi.Any; -using Microsoft.OpenApi.Models; -using Microsoft.OpenApi.Readers.ParseNodes; -using SharpYaml.Serialization; -using Xunit; - -namespace Microsoft.OpenApi.Readers.Tests.ParseNodes -{ - [Collection("DefaultSettings")] - public class OpenApiAnyConverterTests - { - [Fact] - public void ParseObjectAsAnyShouldSucceed() - { - var input = - """ - aString: fooBar - aInteger: 10 - aDouble: 2.34 - aDateTime: 2017-01-01 - aDate: 2017-01-02 - """; - var yamlStream = new YamlStream(); - yamlStream.Load(new StringReader(input)); - var yamlNode = yamlStream.Documents.First().RootNode; - - var diagnostic = new OpenApiDiagnostic(); - var context = new ParsingContext(diagnostic); - - var node = new MapNode(context, (YamlMappingNode)yamlNode); - - var anyMap = node.CreateAny(); - - var schema = new OpenApiSchema - { - Type = "object", - Properties = - { - ["aString"] = new() - { - Type = "string" - }, - ["aInteger"] = new() - { - Type = "integer", - Format = "int32" - }, - ["aDouble"] = new() - { - Type = "number", - Format = "double" - }, - ["aDateTime"] = new() - { - Type = "string", - Format = "date-time" - }, - ["aDate"] = new() - { - Type = "string", - Format = "date" - } - } - }; - - anyMap = OpenApiAnyConverter.GetSpecificOpenApiAny(anyMap, schema); - - diagnostic.Errors.Should().BeEmpty(); - - anyMap.Should().BeEquivalentTo( - new OpenApiObject - { - ["aString"] = new OpenApiString("fooBar"), - ["aInteger"] = new OpenApiInteger(10), - ["aDouble"] = new OpenApiDouble(2.34), - ["aDateTime"] = new OpenApiDateTime(DateTimeOffset.Parse("2017-01-01", CultureInfo.InvariantCulture)), - ["aDate"] = new OpenApiDate(DateTimeOffset.Parse("2017-01-02", CultureInfo.InvariantCulture).Date), - }); - } - - [Fact] - public void ParseNestedObjectAsAnyShouldSucceed() - { - var input = - """ - aString: fooBar - aInteger: 10 - aArray: - - 1 - - 2 - - 3 - aNestedArray: - - aFloat: 1 - aPassword: 1234 - aArray: [abc, def] - aDictionary: - arbitraryProperty: 1 - arbitraryProperty2: 2 - - aFloat: 1.6 - aArray: [123] - aDictionary: - arbitraryProperty: 1 - arbitraryProperty3: 20 - aObject: - aDate: 2017-02-03 - aDouble: 2.34 - aDateTime: 2017-01-01 - """; - var yamlStream = new YamlStream(); - yamlStream.Load(new StringReader(input)); - var yamlNode = yamlStream.Documents.First().RootNode; - - var diagnostic = new OpenApiDiagnostic(); - var context = new ParsingContext(diagnostic); - - var node = new MapNode(context, (YamlMappingNode)yamlNode); - - var anyMap = node.CreateAny(); - - var schema = new OpenApiSchema - { - Type = "object", - Properties = - { - ["aString"] = new() - { - Type = "string" - }, - ["aInteger"] = new() - { - Type = "integer", - Format = "int32" - }, - ["aArray"] = new() - { - Type = "array", - Items = new() - { - Type = "integer", - Format = "int64" - } - }, - ["aNestedArray"] = new() - { - Type = "array", - Items = new() - { - Type = "object", - Properties = - { - ["aFloat"] = new() - { - Type = "number", - Format = "float" - }, - ["aPassword"] = new() - { - Type = "string", - Format = "password" - }, - ["aArray"] = new() - { - Type = "array", - Items = new() - { - Type = "string", - } - }, - ["aDictionary"] = new() - { - Type = "object", - AdditionalProperties = new() - { - Type = "integer", - Format = "int64" - } - } - } - } - }, - ["aObject"] = new() - { - Type = "array", - Properties = - { - ["aDate"] = new() - { - Type = "string", - Format = "date" - } - } - }, - ["aDouble"] = new() - { - Type = "number", - Format = "double" - }, - ["aDateTime"] = new() - { - Type = "string", - Format = "date-time" - } - } - }; - - anyMap = OpenApiAnyConverter.GetSpecificOpenApiAny(anyMap, schema); - - diagnostic.Errors.Should().BeEmpty(); - - anyMap.Should().BeEquivalentTo( - new OpenApiObject - { - ["aString"] = new OpenApiString("fooBar"), - ["aInteger"] = new OpenApiInteger(10), - ["aArray"] = new OpenApiArray - { - new OpenApiLong(1), - new OpenApiLong(2), - new OpenApiLong(3), - }, - ["aNestedArray"] = new OpenApiArray - { - new OpenApiObject - { - ["aFloat"] = new OpenApiFloat(1), - ["aPassword"] = new OpenApiPassword("1234"), - ["aArray"] = new OpenApiArray - { - new OpenApiString("abc"), - new OpenApiString("def") - }, - ["aDictionary"] = new OpenApiObject - { - ["arbitraryProperty"] = new OpenApiLong(1), - ["arbitraryProperty2"] = new OpenApiLong(2), - } - }, - new OpenApiObject - { - ["aFloat"] = new OpenApiFloat((float)1.6), - ["aArray"] = new OpenApiArray - { - new OpenApiString("123"), - }, - ["aDictionary"] = new OpenApiObject - { - ["arbitraryProperty"] = new OpenApiLong(1), - ["arbitraryProperty3"] = new OpenApiLong(20), - } - } - }, - ["aObject"] = new OpenApiObject - { - ["aDate"] = new OpenApiDate(DateTimeOffset.Parse("2017-02-03", CultureInfo.InvariantCulture).Date) - }, - ["aDouble"] = new OpenApiDouble(2.34), - ["aDateTime"] = new OpenApiDateTime(DateTimeOffset.Parse("2017-01-01", CultureInfo.InvariantCulture)) - }); - } - - [Fact] - public void ParseNestedObjectAsAnyWithPartialSchemaShouldSucceed() - { - var input = - """ - aString: fooBar - aInteger: 10 - aArray: - - 1 - - 2 - - 3 - aNestedArray: - - aFloat: 1 - aPassword: 1234 - aArray: [abc, def] - aDictionary: - arbitraryProperty: 1 - arbitraryProperty2: 2 - - aFloat: 1.6 - aArray: [123] - aDictionary: - arbitraryProperty: 1 - arbitraryProperty3: 20 - aObject: - aDate: 2017-02-03 - aDouble: 2.34 - aDateTime: 2017-01-01 - """; - var yamlStream = new YamlStream(); - yamlStream.Load(new StringReader(input)); - var yamlNode = yamlStream.Documents.First().RootNode; - - var diagnostic = new OpenApiDiagnostic(); - var context = new ParsingContext(diagnostic); - - var node = new MapNode(context, (YamlMappingNode)yamlNode); - - var anyMap = node.CreateAny(); - - var schema = new OpenApiSchema - { - Type = "object", - Properties = - { - ["aString"] = new() - { - Type = "string" - }, - ["aArray"] = new() - { - Type = "array", - Items = new() - { - Type = "integer" - } - }, - ["aNestedArray"] = new() - { - Type = "array", - Items = new() - { - Type = "object", - Properties = - { - ["aFloat"] = new() - { - }, - ["aPassword"] = new() - { - }, - ["aArray"] = new() - { - Type = "array", - Items = new() - { - Type = "string", - } - } - } - } - }, - ["aObject"] = new() - { - Type = "array", - Properties = - { - ["aDate"] = new() - { - Type = "string" - } - } - }, - ["aDouble"] = new() - { - }, - ["aDateTime"] = new() - { - } - } - }; - - anyMap = OpenApiAnyConverter.GetSpecificOpenApiAny(anyMap, schema); - - diagnostic.Errors.Should().BeEmpty(); - - anyMap.Should().BeEquivalentTo( - new OpenApiObject - { - ["aString"] = new OpenApiString("fooBar"), - ["aInteger"] = new OpenApiInteger(10), - ["aArray"] = new OpenApiArray - { - new OpenApiInteger(1), - new OpenApiInteger(2), - new OpenApiInteger(3), - }, - ["aNestedArray"] = new OpenApiArray - { - new OpenApiObject - { - ["aFloat"] = new OpenApiInteger(1), - ["aPassword"] = new OpenApiInteger(1234), - ["aArray"] = new OpenApiArray - { - new OpenApiString("abc"), - new OpenApiString("def") - }, - ["aDictionary"] = new OpenApiObject - { - ["arbitraryProperty"] = new OpenApiInteger(1), - ["arbitraryProperty2"] = new OpenApiInteger(2), - } - }, - new OpenApiObject - { - ["aFloat"] = new OpenApiDouble(1.6), - ["aArray"] = new OpenApiArray - { - new OpenApiString("123"), - }, - ["aDictionary"] = new OpenApiObject - { - ["arbitraryProperty"] = new OpenApiInteger(1), - ["arbitraryProperty3"] = new OpenApiInteger(20), - } - } - }, - ["aObject"] = new OpenApiObject - { - ["aDate"] = new OpenApiString("2017-02-03") - }, - ["aDouble"] = new OpenApiDouble(2.34), - ["aDateTime"] = new OpenApiDateTime(DateTimeOffset.Parse("2017-01-01", CultureInfo.InvariantCulture)) - }); - } - - [Fact] - public void ParseNestedObjectAsAnyWithoutUsingSchemaShouldSucceed() - { - var input = - """ - aString: fooBar - aInteger: 10 - aArray: - - 1 - - 2 - - 3 - aNestedArray: - - aFloat: 1 - aPassword: 1234 - aArray: [abc, def] - aDictionary: - arbitraryProperty: 1 - arbitraryProperty2: 2 - - aFloat: 1.6 - aArray: [123] - aDictionary: - arbitraryProperty: 1 - arbitraryProperty3: 20 - aObject: - aDate: 2017-02-03 - aDouble: 2.34 - aDateTime: 2017-01-01 - """; - var yamlStream = new YamlStream(); - yamlStream.Load(new StringReader(input)); - var yamlNode = yamlStream.Documents.First().RootNode; - - var diagnostic = new OpenApiDiagnostic(); - var context = new ParsingContext(diagnostic); - - var node = new MapNode(context, (YamlMappingNode)yamlNode); - - var anyMap = node.CreateAny(); - - anyMap = OpenApiAnyConverter.GetSpecificOpenApiAny(anyMap); - - diagnostic.Errors.Should().BeEmpty(); - - anyMap.Should().BeEquivalentTo( - new OpenApiObject - { - ["aString"] = new OpenApiString("fooBar"), - ["aInteger"] = new OpenApiInteger(10), - ["aArray"] = new OpenApiArray - { - new OpenApiInteger(1), - new OpenApiInteger(2), - new OpenApiInteger(3), - }, - ["aNestedArray"] = new OpenApiArray - { - new OpenApiObject - { - ["aFloat"] = new OpenApiInteger(1), - ["aPassword"] = new OpenApiInteger(1234), - ["aArray"] = new OpenApiArray - { - new OpenApiString("abc"), - new OpenApiString("def") - }, - ["aDictionary"] = new OpenApiObject - { - ["arbitraryProperty"] = new OpenApiInteger(1), - ["arbitraryProperty2"] = new OpenApiInteger(2), - } - }, - new OpenApiObject - { - ["aFloat"] = new OpenApiDouble(1.6), - ["aArray"] = new OpenApiArray - { - new OpenApiInteger(123), - }, - ["aDictionary"] = new OpenApiObject - { - ["arbitraryProperty"] = new OpenApiInteger(1), - ["arbitraryProperty3"] = new OpenApiInteger(20), - } - } - }, - ["aObject"] = new OpenApiObject - { - ["aDate"] = new OpenApiDateTime(DateTimeOffset.Parse("2017-02-03", CultureInfo.InvariantCulture)) - }, - ["aDouble"] = new OpenApiDouble(2.34), - ["aDateTime"] = new OpenApiDateTime(DateTimeOffset.Parse("2017-01-01", CultureInfo.InvariantCulture)) - }); - } - } -} diff --git a/test/Microsoft.OpenApi.Readers.Tests/ParseNodes/OpenApiAnyTests.cs b/test/Microsoft.OpenApi.Readers.Tests/ParseNodes/OpenApiAnyTests.cs deleted file mode 100644 index ed28fa1c2..000000000 --- a/test/Microsoft.OpenApi.Readers.Tests/ParseNodes/OpenApiAnyTests.cs +++ /dev/null @@ -1,127 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. - -using System.IO; -using System.Linq; -using FluentAssertions; -using Microsoft.OpenApi.Any; -using Microsoft.OpenApi.Readers.ParseNodes; -using SharpYaml.Serialization; -using Xunit; - -namespace Microsoft.OpenApi.Readers.Tests.V3Tests -{ - [Collection("DefaultSettings")] - public class OpenApiAnyTests - { - [Fact] - public void ParseMapAsAnyShouldSucceed() - { - var input = - """ - aString: fooBar - aInteger: 10 - aDouble: 2.34 - aDateTime: 2017-01-01 - """; - var yamlStream = new YamlStream(); - yamlStream.Load(new StringReader(input)); - var yamlNode = yamlStream.Documents.First().RootNode; - - var diagnostic = new OpenApiDiagnostic(); - var context = new ParsingContext(diagnostic); - - var node = new MapNode(context, (YamlMappingNode)yamlNode); - - var anyMap = node.CreateAny(); - - diagnostic.Errors.Should().BeEmpty(); - - anyMap.Should().BeEquivalentTo( - new OpenApiObject - { - ["aString"] = new OpenApiString("fooBar"), - ["aInteger"] = new OpenApiString("10"), - ["aDouble"] = new OpenApiString("2.34"), - ["aDateTime"] = new OpenApiString("2017-01-01") - }); - } - - [Fact] - public void ParseListAsAnyShouldSucceed() - { - var input = - """ - - fooBar - - 10 - - 2.34 - - 2017-01-01 - """; - var yamlStream = new YamlStream(); - yamlStream.Load(new StringReader(input)); - var yamlNode = yamlStream.Documents.First().RootNode; - - var diagnostic = new OpenApiDiagnostic(); - var context = new ParsingContext(diagnostic); - - var node = new ListNode(context, (YamlSequenceNode)yamlNode); - - var any = node.CreateAny(); - - diagnostic.Errors.Should().BeEmpty(); - - any.Should().BeEquivalentTo( - new OpenApiArray - { - new OpenApiString("fooBar"), - new OpenApiString("10"), - new OpenApiString("2.34"), - new OpenApiString("2017-01-01") - }); - } - - [Fact] - public void ParseScalarIntegerAsAnyShouldSucceed() - { - var input = "10"; - var yamlStream = new YamlStream(); - yamlStream.Load(new StringReader(input)); - var yamlNode = yamlStream.Documents.First().RootNode; - - var diagnostic = new OpenApiDiagnostic(); - var context = new ParsingContext(diagnostic); - - var node = new ValueNode(context, (YamlScalarNode)yamlNode); - - var any = node.CreateAny(); - - diagnostic.Errors.Should().BeEmpty(); - - any.Should().BeEquivalentTo( - new OpenApiString("10") - ); - } - - [Fact] - public void ParseScalarDateTimeAsAnyShouldSucceed() - { - var input = "2012-07-23T12:33:00"; - var yamlStream = new YamlStream(); - yamlStream.Load(new StringReader(input)); - var yamlNode = yamlStream.Documents.First().RootNode; - - var diagnostic = new OpenApiDiagnostic(); - var context = new ParsingContext(diagnostic); - - var node = new ValueNode(context, (YamlScalarNode)yamlNode); - - var any = node.CreateAny(); - - diagnostic.Errors.Should().BeEmpty(); - - any.Should().BeEquivalentTo( - new OpenApiString("2012-07-23T12:33:00") - ); - } - } -} diff --git a/test/Microsoft.OpenApi.Readers.Tests/ParseNodes/ParserHelperTests.cs b/test/Microsoft.OpenApi.Readers.Tests/ParseNodes/ParserHelperTests.cs index 1368e103d..4e3500d6b 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/ParseNodes/ParserHelperTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/ParseNodes/ParserHelperTests.cs @@ -2,7 +2,7 @@ // Licensed under the MIT license. using System.Globalization; -using Microsoft.OpenApi.Readers.ParseNodes; +using Microsoft.OpenApi.Reader.ParseNodes; using Xunit; namespace Microsoft.OpenApi.Readers.Tests.ParseNodes diff --git a/test/Microsoft.OpenApi.Readers.Tests/ReferenceService/ConvertToOpenApiReferenceV2Tests.cs b/test/Microsoft.OpenApi.Readers.Tests/ReferenceService/ConvertToOpenApiReferenceV2Tests.cs index 737f4310c..abdbfcb9c 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/ReferenceService/ConvertToOpenApiReferenceV2Tests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/ReferenceService/ConvertToOpenApiReferenceV2Tests.cs @@ -3,14 +3,15 @@ using FluentAssertions; using Microsoft.OpenApi.Models; -using Microsoft.OpenApi.Readers.V2; +using Microsoft.OpenApi.Reader; +using Microsoft.OpenApi.Reader.V2; using Xunit; namespace Microsoft.OpenApi.Readers.Tests { public class ConvertToOpenApiReferenceV2Tests { - public OpenApiDiagnostic Diagnostic{get;} + public OpenApiDiagnostic Diagnostic { get; } public ConvertToOpenApiReferenceV2Tests() { diff --git a/test/Microsoft.OpenApi.Readers.Tests/ReferenceService/ConvertToOpenApiReferenceV3Tests.cs b/test/Microsoft.OpenApi.Readers.Tests/ReferenceService/ConvertToOpenApiReferenceV3Tests.cs index 2f00de3c2..6f4d53acb 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/ReferenceService/ConvertToOpenApiReferenceV3Tests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/ReferenceService/ConvertToOpenApiReferenceV3Tests.cs @@ -3,7 +3,8 @@ using FluentAssertions; using Microsoft.OpenApi.Models; -using Microsoft.OpenApi.Readers.V3; +using Microsoft.OpenApi.Reader; +using Microsoft.OpenApi.Reader.V3; using Xunit; namespace Microsoft.OpenApi.Readers.Tests diff --git a/test/Microsoft.OpenApi.Readers.Tests/ReferenceService/TryLoadReferenceV2Tests.cs b/test/Microsoft.OpenApi.Readers.Tests/ReferenceService/TryLoadReferenceV2Tests.cs index b51f696e0..d6fb3b8ba 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/ReferenceService/TryLoadReferenceV2Tests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/ReferenceService/TryLoadReferenceV2Tests.cs @@ -5,6 +5,8 @@ using System.IO; using FluentAssertions; using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Models.References; +using Microsoft.OpenApi.Reader; using Xunit; namespace Microsoft.OpenApi.Readers.Tests.ReferenceService @@ -14,84 +16,20 @@ public class TryLoadReferenceV2Tests { private const string SampleFolderPath = "ReferenceService/Samples/"; - [Fact] - public void LoadSchemaReference() + public TryLoadReferenceV2Tests() { - // Arrange - OpenApiDocument document; - var diagnostic = new OpenApiDiagnostic(); - - using (var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "multipleReferences.v2.yaml"))) - { - document = new OpenApiStreamReader().Read(stream, out diagnostic); - } - - var reference = new OpenApiReference - { - Type = ReferenceType.Schema, - Id = "SampleObject" - }; - - // Act - var referencedObject = document.ResolveReferenceTo(reference); - - // Assert - referencedObject.Should().BeEquivalentTo( - new OpenApiSchema - { - Required = - { - "id", - "name" - }, - Properties = - { - ["id"] = new() - { - Type = "integer", - Format = "int64" - }, - ["name"] = new() - { - Type = "string" - }, - ["tag"] = new() - { - Type = "string" - } - }, - Reference = new() - { - Type = ReferenceType.Schema, - Id = "SampleObject" - } - } - ); + OpenApiReaderRegistry.RegisterReader("yaml", new OpenApiYamlReader()); } [Fact] public void LoadParameterReference() { // Arrange - OpenApiDocument document; - var diagnostic = new OpenApiDiagnostic(); - - using (var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "multipleReferences.v2.yaml"))) - { - document = new OpenApiStreamReader().Read(stream, out diagnostic); - } - - var reference = new OpenApiReference - { - Type = ReferenceType.Parameter, - Id = "skipParam" - }; - - // Act - var referencedObject = document.ResolveReferenceTo(reference); + var result = OpenApiDocument.Load(Path.Combine(SampleFolderPath, "multipleReferences.v2.yaml")); + var reference = new OpenApiParameterReference("skipParam", result.OpenApiDocument); // Assert - referencedObject.Should().BeEquivalentTo( + reference.Should().BeEquivalentTo( new OpenApiParameter { Name = "skip", @@ -100,117 +38,60 @@ public void LoadParameterReference() Required = true, Schema = new() { - Type = "integer", + Type = JsonSchemaType.Integer, Format = "int32" - }, - Reference = new() - { - Type = ReferenceType.Parameter, - Id = "skipParam" } - } + + }, options => options.Excluding(x => x.Reference) ); } [Fact] public void LoadSecuritySchemeReference() { - // Arrange - OpenApiDocument document; - var diagnostic = new OpenApiDiagnostic(); - - using (var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "multipleReferences.v2.yaml"))) - { - document = new OpenApiStreamReader().Read(stream, out diagnostic); - } + var result = OpenApiDocument.Load(Path.Combine(SampleFolderPath, "multipleReferences.v2.yaml")); - var reference = new OpenApiReference - { - Type = ReferenceType.SecurityScheme, - Id = "api_key_sample" - }; - - // Act - var referencedObject = document.ResolveReferenceTo(reference); + var reference = new OpenApiSecuritySchemeReference("api_key_sample", result.OpenApiDocument); // Assert - referencedObject.Should().BeEquivalentTo( + reference.Should().BeEquivalentTo( new OpenApiSecurityScheme { Type = SecuritySchemeType.ApiKey, Name = "api_key", - In = ParameterLocation.Header, - Reference = new() - { - Type = ReferenceType.SecurityScheme, - Id = "api_key_sample" - } - } + In = ParameterLocation.Header + }, options => options.Excluding(x => x.Reference) ); } [Fact] public void LoadResponseReference() { - // Arrange - OpenApiDocument document; - var diagnostic = new OpenApiDiagnostic(); + var result = OpenApiDocument.Load(Path.Combine(SampleFolderPath, "multipleReferences.v2.yaml")); - using (var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "multipleReferences.v2.yaml"))) - { - document = new OpenApiStreamReader().Read(stream, out diagnostic); - } - - var reference = new OpenApiReference - { - Type = ReferenceType.Response, - Id = "NotFound" - }; - - // Act - var referencedObject = document.ResolveReferenceTo(reference); + var reference = new OpenApiResponseReference("NotFound", result.OpenApiDocument); // Assert - referencedObject.Should().BeEquivalentTo( + reference.Should().BeEquivalentTo( new OpenApiResponse { Description = "Entity not found.", - Reference = new() - { - Type = ReferenceType.Response, - Id = "NotFound" - }, Content = new Dictionary { ["application/json"] = new() } - } + }, options => options.Excluding(x => x.Reference) ); } [Fact] public void LoadResponseAndSchemaReference() { - // Arrange - OpenApiDocument document; - var diagnostic = new OpenApiDiagnostic(); - - using (var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "multipleReferences.v2.yaml"))) - { - document = new OpenApiStreamReader().Read(stream, out diagnostic); - } - - var reference = new OpenApiReference - { - Type = ReferenceType.Response, - Id = "GeneralError" - }; - - // Act - var referencedObject = document.ResolveReferenceTo(reference); + var result = OpenApiDocument.Load(Path.Combine(SampleFolderPath, "multipleReferences.v2.yaml")); + var reference = new OpenApiResponseReference("GeneralError", result.OpenApiDocument); // Assert - referencedObject.Should().BeEquivalentTo( + reference.Should().BeEquivalentTo( new OpenApiResponse { Description = "General Error", @@ -225,11 +106,11 @@ public void LoadResponseAndSchemaReference() Properties = { ["name"] = new() { - Type = "string" + Type = JsonSchemaType.String }, ["tag"] = new() { - Type = "string" + Type = JsonSchemaType.String } }, @@ -237,7 +118,7 @@ public void LoadResponseAndSchemaReference() { Type = ReferenceType.Schema, Id = "SampleObject2", - HostDocument = document + HostDocument = result.OpenApiDocument } } } @@ -247,7 +128,7 @@ public void LoadResponseAndSchemaReference() Type = ReferenceType.Response, Id = "GeneralError" } - } + }, options => options.Excluding(x => x.Reference) ); } } diff --git a/test/Microsoft.OpenApi.Readers.Tests/TestCustomExtension.cs b/test/Microsoft.OpenApi.Readers.Tests/TestCustomExtension.cs index 561cde431..9d7727aae 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/TestCustomExtension.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/TestCustomExtension.cs @@ -1,9 +1,11 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. +using System.Text.Json.Nodes; using FluentAssertions; -using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Interfaces; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Reader; using Microsoft.OpenApi.Writers; using Xunit; @@ -27,22 +29,20 @@ public void ParseCustomExtension() """; var settings = new OpenApiReaderSettings { - ExtensionParsers = { { "x-foo", (a,_) => { - var fooNode = (OpenApiObject)a; - return new FooExtension - { - Bar = (fooNode["bar"] as OpenApiString)?.Value, - Baz = (fooNode["baz"] as OpenApiString)?.Value + ExtensionParsers = { { "x-foo", (a,v) => { + var fooNode = (JsonObject)a; + return new FooExtension() { + Bar = (fooNode["bar"].ToString()), + Baz = (fooNode["baz"].ToString()) }; } } } }; - var reader = new OpenApiStringReader(settings); - + OpenApiReaderRegistry.RegisterReader("yaml", new OpenApiYamlReader()); var diag = new OpenApiDiagnostic(); - var doc = reader.Read(description, out diag); + var actual = OpenApiDocument.Parse(description, "yaml", settings: settings); - var fooExtension = doc.Info.Extensions["x-foo"] as FooExtension; + var fooExtension = actual.OpenApiDocument.Info.Extensions["x-foo"] as FooExtension; fooExtension.Should().NotBeNull(); fooExtension.Bar.Should().Be("hey"); diff --git a/test/Microsoft.OpenApi.Readers.Tests/TestHelper.cs b/test/Microsoft.OpenApi.Readers.Tests/TestHelper.cs index fa8b939e2..f8b222c36 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/TestHelper.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/TestHelper.cs @@ -3,7 +3,8 @@ using System.IO; using System.Linq; -using Microsoft.OpenApi.Readers.ParseNodes; +using Microsoft.OpenApi.Reader; +using Microsoft.OpenApi.Reader.ParseNodes; using SharpYaml.Serialization; namespace Microsoft.OpenApi.Readers.Tests @@ -16,9 +17,10 @@ public static MapNode CreateYamlMapNode(Stream stream) yamlStream.Load(new StreamReader(stream)); var yamlNode = yamlStream.Documents.First().RootNode; - var context = new ParsingContext(new()); + var context = new ParsingContext(new OpenApiDiagnostic()); + var asJsonNode = yamlNode.ToJsonNode(); - return new(context, (YamlMappingNode)yamlNode); + return new MapNode(context, asJsonNode); } } } diff --git a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/ComparisonTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/ComparisonTests.cs index f0d417f84..b3e30c672 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/ComparisonTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/ComparisonTests.cs @@ -1,8 +1,10 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. using System.IO; using FluentAssertions; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Reader; using Xunit; namespace Microsoft.OpenApi.Readers.Tests.V2Tests @@ -16,16 +18,18 @@ public class ComparisonTests [InlineData("minimal")] [InlineData("basic")] //[InlineData("definitions")] //Currently broken due to V3 references not behaving the same as V2 - public void EquivalentV2AndV3DocumentsShouldProductEquivalentObjects(string fileName) + public void EquivalentV2AndV3DocumentsShouldProduceEquivalentObjects(string fileName) { + OpenApiReaderRegistry.RegisterReader(OpenApiConstants.Yaml, new OpenApiYamlReader()); using var streamV2 = Resources.GetStream(Path.Combine(SampleFolderPath, $"{fileName}.v2.yaml")); using var streamV3 = Resources.GetStream(Path.Combine(SampleFolderPath, $"{fileName}.v3.yaml")); - var openApiDocV2 = new OpenApiStreamReader().Read(streamV2, out var diagnosticV2); - var openApiDocV3 = new OpenApiStreamReader().Read(streamV3, out var diagnosticV3); + var result1 = OpenApiDocument.Load(Path.Combine(SampleFolderPath, $"{fileName}.v2.yaml")); + var result2 = OpenApiDocument.Load(Path.Combine(SampleFolderPath, $"{fileName}.v3.yaml")); - openApiDocV3.Should().BeEquivalentTo(openApiDocV2); + result2.OpenApiDocument.Should().BeEquivalentTo(result1.OpenApiDocument, + options => options.Excluding(x => x.Workspace).Excluding(y => y.BaseUri)); - diagnosticV2.Errors.Should().BeEquivalentTo(diagnosticV3.Errors); + result1.OpenApiDiagnostic.Errors.Should().BeEquivalentTo(result2.OpenApiDiagnostic.Errors); } } } diff --git a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiContactTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiContactTests.cs index a7d586bf2..413d3ee7b 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiContactTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiContactTests.cs @@ -3,6 +3,7 @@ using FluentAssertions; using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Reader; using Xunit; namespace Microsoft.OpenApi.Readers.Tests.V2Tests @@ -20,10 +21,9 @@ public void ParseStringContactFragmentShouldSucceed() "email": "support@swagger.io" } """; - var reader = new OpenApiStringReader(); // Act - var contact = reader.ReadFragment(input, OpenApiSpecVersion.OpenApi2_0, out var diagnostic); + var contact = OpenApiModelFactory.Parse(input, OpenApiSpecVersion.OpenApi2_0, out var diagnostic); // Assert diagnostic.Should().BeEquivalentTo(new OpenApiDiagnostic()); diff --git a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiDocumentTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiDocumentTests.cs index 3a1c86298..596269644 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiDocumentTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiDocumentTests.cs @@ -1,13 +1,17 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. +using System; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Threading; using FluentAssertions; +using FluentAssertions.Equivalency; using Microsoft.OpenApi.Any; -using Microsoft.OpenApi.Exceptions; using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Models.References; +using Microsoft.OpenApi.Reader; using Xunit; namespace Microsoft.OpenApi.Readers.Tests.V2Tests @@ -16,60 +20,9 @@ public class OpenApiDocumentTests { private const string SampleFolderPath = "V2Tests/Samples/"; - [Fact] - public void ShouldThrowWhenReferenceTypeIsInvalid() + public OpenApiDocumentTests() { - var input = - """ - swagger: 2.0 - info: - title: test - version: 1.0.0 - paths: - '/': - get: - responses: - '200': - description: ok - schema: - $ref: '#/defi888nition/does/notexist' - """; - - var reader = new OpenApiStringReader(); - var doc = reader.Read(input, out var diagnostic); - - diagnostic.Errors.Should().BeEquivalentTo(new List { - new( new OpenApiException("Unknown reference type 'defi888nition'")) }); - doc.Should().NotBeNull(); - } - - [Fact] - public void ShouldThrowWhenReferenceDoesNotExist() - { - var input = - """ - swagger: 2.0 - info: - title: test - version: 1.0.0 - paths: - '/': - get: - produces: ['application/json'] - responses: - '200': - description: ok - schema: - $ref: '#/definitions/doesnotexist' - """; - - var reader = new OpenApiStringReader(); - - var doc = reader.Read(input, out var diagnostic); - - diagnostic.Errors.Should().BeEquivalentTo(new List { - new( new OpenApiException("Invalid Reference identifier 'doesnotexist'.")) }); - doc.Should().NotBeNull(); + OpenApiReaderRegistry.RegisterReader("yaml", new OpenApiYamlReader()); } [Theory] @@ -83,8 +36,8 @@ public void ParseDocumentWithDifferentCultureShouldSucceed(string culture) Thread.CurrentThread.CurrentCulture = new(culture); Thread.CurrentThread.CurrentUICulture = new(culture); - var openApiDoc = new OpenApiStringReader().Read( - """ + var result = OpenApiDocument.Parse( + """ swagger: 2.0 info: title: Simple Document @@ -102,9 +55,9 @@ public void ParseDocumentWithDifferentCultureShouldSucceed(string culture) exclusiveMinimum: false paths: {} """, - out var context); + "yaml"); - openApiDoc.Should().BeEquivalentTo( + result.OpenApiDocument.Should().BeEquivalentTo( new OpenApiDocument { Info = new() @@ -113,7 +66,7 @@ public void ParseDocumentWithDifferentCultureShouldSucceed(string culture) Version = "0.9.1", Extensions = { - ["x-extension"] = new OpenApiDouble(2.335) + ["x-extension"] = new OpenApiAny(2.335) } }, Components = new() @@ -122,53 +75,51 @@ public void ParseDocumentWithDifferentCultureShouldSucceed(string culture) { ["sampleSchema"] = new() { - Type = "object", + Type = JsonSchemaType.Object, Properties = { ["sampleProperty"] = new() { - Type = "double", + Type = JsonSchemaType.Number, Minimum = (decimal)100.54, Maximum = (decimal)60000000.35, ExclusiveMaximum = true, ExclusiveMinimum = false } - }, - Reference = new() - { - Id = "sampleSchema", - Type = ReferenceType.Schema } } } }, Paths = new() - }); + }, options => options + .Excluding(x=> x.BaseUri) + .Excluding((IMemberInfo memberInfo) => + memberInfo.Path.EndsWith("Parent")) + .Excluding((IMemberInfo memberInfo) => + memberInfo.Path.EndsWith("Root"))); - context.Should().BeEquivalentTo( - new OpenApiDiagnostic { SpecificationVersion = OpenApiSpecVersion.OpenApi2_0 }); + result.OpenApiDiagnostic.Should().BeEquivalentTo( + new OpenApiDiagnostic { + SpecificationVersion = OpenApiSpecVersion.OpenApi2_0, + Errors = new List() + { + new OpenApiError("", "Paths is a REQUIRED field at #/") + } + }); } [Fact] public void ShouldParseProducesInAnyOrder() { - using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "twoResponses.json")); - var reader = new OpenApiStreamReader(); - var doc = reader.Read(stream, out var diagnostic); + var result = OpenApiDocument.Load(Path.Combine(SampleFolderPath, "twoResponses.json")); var okSchema = new OpenApiSchema { - Reference = new() - { - Type = ReferenceType.Schema, - Id = "Item", - HostDocument = doc - }, Properties = new Dictionary { { "id", new OpenApiSchema { - Type = "string", + Type = JsonSchemaType.String, Description = "Item identifier." } } @@ -177,28 +128,22 @@ public void ShouldParseProducesInAnyOrder() var errorSchema = new OpenApiSchema { - Reference = new() - { - Type = ReferenceType.Schema, - Id = "Error", - HostDocument = doc - }, Properties = new Dictionary { { "code", new OpenApiSchema { - Type = "integer", + Type = JsonSchemaType.Integer, Format = "int32" } }, { "message", new OpenApiSchema { - Type = "string" + Type = JsonSchemaType.String } }, { "fields", new OpenApiSchema { - Type = "string" + Type = JsonSchemaType.String } } } @@ -208,17 +153,17 @@ public void ShouldParseProducesInAnyOrder() { Schema = new() { - Type = "array", - Items = okSchema + Type = JsonSchemaType.Array, + Items = new OpenApiSchemaReference("Item", result.OpenApiDocument) } }; var errorMediaType = new OpenApiMediaType { - Schema = errorSchema + Schema = new OpenApiSchemaReference("Error", result.OpenApiDocument) }; - doc.Should().BeEquivalentTo(new OpenApiDocument + result.OpenApiDocument.Should().BeEquivalentTo(new OpenApiDocument { Info = new() { @@ -319,70 +264,25 @@ public void ShouldParseProducesInAnyOrder() ["Error"] = errorSchema } } - }); + }, options => options.Excluding(x => x.BaseUri)); } [Fact] public void ShouldAssignSchemaToAllResponses() { - OpenApiDocument document; - OpenApiDiagnostic diagnostic; - using (var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "multipleProduces.json"))) - { - document = new OpenApiStreamReader().Read(stream, out diagnostic); - } + using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "multipleProduces.json")); + var result = OpenApiDocument.Load(stream, OpenApiConstants.Json); - Assert.Equal(OpenApiSpecVersion.OpenApi2_0, diagnostic.SpecificationVersion); + Assert.Equal(OpenApiSpecVersion.OpenApi2_0, result.OpenApiDiagnostic.SpecificationVersion); var successSchema = new OpenApiSchema { - Type = "array", - Items = new() - { - Properties = { - { "id", new OpenApiSchema - { - Type = "string", - Description = "Item identifier." - } - } - }, - Reference = new() - { - Id = "Item", - Type = ReferenceType.Schema, - HostDocument = document - } - } - }; - var errorSchema = new OpenApiSchema - { - Properties = { - { "code", new OpenApiSchema - { - Type = "integer", - Format = "int32" - } - }, - { "message", new OpenApiSchema - { - Type = "string" - } - }, - { "fields", new OpenApiSchema - { - Type = "string" - } - } - }, - Reference = new() - { - Id = "Error", - Type = ReferenceType.Schema, - HostDocument = document - } + Type = JsonSchemaType.Array, + Items = new OpenApiSchemaReference("Item", result.OpenApiDocument) }; - var responses = document.Paths["/items"].Operations[OperationType.Get].Responses; + var errorSchema = new OpenApiSchemaReference("Error", result.OpenApiDocument); + + var responses = result.OpenApiDocument.Paths["/items"].Operations[OperationType.Get].Responses; foreach (var response in responses) { var targetSchema = response.Key == "200" ? successSchema : errorSchema; @@ -400,12 +300,11 @@ public void ShouldAssignSchemaToAllResponses() [Fact] public void ShouldAllowComponentsThatJustContainAReference() { - using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "ComponentRootReference.json")); - var reader = new OpenApiStreamReader(); - var doc = reader.Read(stream, out var diags); - var schema1 = doc.Components.Schemas["AllPets"]; + // Act + var actual = OpenApiDocument.Load(Path.Combine(SampleFolderPath, "ComponentRootReference.json")).OpenApiDocument; + var schema1 = actual.Components.Schemas["AllPets"]; Assert.False(schema1.UnresolvedReference); - var schema2 = doc.ResolveReferenceTo(schema1.Reference); + var schema2 = actual.ResolveReferenceTo(schema1.Reference); if (schema2.UnresolvedReference && schema1.Reference.Id == schema2.Reference.Id) { // detected a cycle - this code gets triggered @@ -416,11 +315,23 @@ public void ShouldAllowComponentsThatJustContainAReference() [Fact] public void ParseDocumentWithDefaultContentTypeSettingShouldSucceed() { - using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "docWithEmptyProduces.yaml")); - var doc = new OpenApiStreamReader(new() { DefaultContentType = new() { "application/json" } }) - .Read(stream, out var diags); - var mediaType = doc.Paths["/example"].Operations[OperationType.Get].Responses["200"].Content; + var settings = new OpenApiReaderSettings + { + DefaultContentType = ["application/json"] + }; + + var actual = OpenApiDocument.Load(Path.Combine(SampleFolderPath, "docWithEmptyProduces.yaml"), settings); + var mediaType = actual.OpenApiDocument.Paths["/example"].Operations[OperationType.Get].Responses["200"].Content; Assert.Contains("application/json", mediaType); } + + [Fact] + 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/OpenApiHeaderTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiHeaderTests.cs index 39dff183f..80948f93b 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiHeaderTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiHeaderTests.cs @@ -3,10 +3,11 @@ using System.IO; using FluentAssertions; +using FluentAssertions.Equivalency; using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Models; -using Microsoft.OpenApi.Readers.ParseNodes; -using Microsoft.OpenApi.Readers.V2; +using Microsoft.OpenApi.Reader.ParseNodes; +using Microsoft.OpenApi.Reader.V2; using Xunit; namespace Microsoft.OpenApi.Readers.Tests.V2Tests @@ -35,11 +36,14 @@ public void ParseHeaderWithDefaultShouldSucceed() { Schema = new() { - Type = "number", + Type = JsonSchemaType.Number, Format = "float", - Default = new OpenApiFloat(5) + Default = new OpenApiAny(5).Node } - }); + }, + options => options + .IgnoringCyclicReferences() + .Excluding(x => x.Schema.Default.Parent)); } [Fact] @@ -61,16 +65,18 @@ public void ParseHeaderWithEnumShouldSucceed() { Schema = new() { - Type = "number", + Type = JsonSchemaType.Number, Format = "float", Enum = { - new OpenApiFloat(7), - new OpenApiFloat(8), - new OpenApiFloat(9) + new OpenApiAny(7).Node, + new OpenApiAny(8).Node, + new OpenApiAny(9).Node } } - }); + }, options => options.IgnoringCyclicReferences() + .Excluding((IMemberInfo memberInfo) => + memberInfo.Path.EndsWith("Parent"))); } } } diff --git a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiOperationTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiOperationTests.cs index 86d22f8a5..4142e9fcd 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiOperationTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiOperationTests.cs @@ -1,18 +1,20 @@ // Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. +// Licensed under the MIT license. -using System; using System.Collections.Generic; using System.IO; using System.Text; +using System.Text.Json.Nodes; using FluentAssertions; using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Extensions; +using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Models; -using Microsoft.OpenApi.Readers.ParseNodes; -using Microsoft.OpenApi.Readers.V2; -using Microsoft.OpenApi.Readers.V3; +using Microsoft.OpenApi.Reader.ParseNodes; +using Microsoft.OpenApi.Reader.V2; +using Microsoft.OpenApi.Reader.V3; using Microsoft.OpenApi.Tests; +using Microsoft.OpenApi.Writers; using Xunit; namespace Microsoft.OpenApi.Readers.Tests.V2Tests @@ -22,14 +24,14 @@ public class OpenApiOperationTests { private const string SampleFolderPath = "V2Tests/Samples/OpenApiOperation/"; - private static readonly OpenApiOperation _basicOperation = new() + private static readonly OpenApiOperation _basicOperation = new OpenApiOperation { Summary = "Updates a pet in the store", Description = "", OperationId = "updatePet", Parameters = new List { - new() + new OpenApiParameter { Name = "petId", In = ParameterLocation.Path, @@ -37,33 +39,33 @@ public class OpenApiOperationTests Required = true, Schema = new() { - Type = "string" + Type = JsonSchemaType.String } } }, - Responses = new() + Responses = new OpenApiResponses { - ["200"] = new() + ["200"] = new OpenApiResponse { Description = "Pet updated.", Content = new Dictionary { - ["application/json"] = new(), - ["application/xml"] = new() + ["application/json"] = new OpenApiMediaType(), + ["application/xml"] = new OpenApiMediaType() } } } }; private static readonly OpenApiOperation _operationWithFormData = - new() + new OpenApiOperation { Summary = "Updates a pet in the store with form data", Description = "", OperationId = "updatePetWithForm", Parameters = new List { - new() + new OpenApiParameter { Name = "petId", In = ParameterLocation.Path, @@ -71,30 +73,30 @@ public class OpenApiOperationTests Required = true, Schema = new() { - Type = "string" + Type = JsonSchemaType.String } } }, - RequestBody = new() + RequestBody = new OpenApiRequestBody { Content = { - ["application/x-www-form-urlencoded"] = new() + ["application/x-www-form-urlencoded"] = new OpenApiMediaType { Schema = new() { - Type = "object", + Type = JsonSchemaType.Object, Properties = { ["name"] = new() { Description = "Updated name of the pet", - Type = "string" + Type = JsonSchemaType.String }, ["status"] = new() { Description = "Updated status of the pet", - Type = "string" + Type = JsonSchemaType.String } }, Required = new HashSet @@ -103,22 +105,22 @@ public class OpenApiOperationTests } } }, - ["multipart/form-data"] = new() + ["multipart/form-data"] = new OpenApiMediaType { - Schema = new() + Schema = new() { - Type = "object", + Type = JsonSchemaType.Object, Properties = { ["name"] = new() { Description = "Updated name of the pet", - Type = "string" + Type = JsonSchemaType.String }, ["status"] = new() { Description = "Updated status of the pet", - Type = "string" + Type = JsonSchemaType.String } }, Required = new HashSet @@ -129,38 +131,38 @@ public class OpenApiOperationTests } } }, - Responses = new() + Responses = new OpenApiResponses { - ["200"] = new() + ["200"] = new OpenApiResponse { Description = "Pet updated.", Content = new Dictionary { - ["application/json"] = new(), - ["application/xml"] = new() + ["application/json"] = new OpenApiMediaType(), + ["application/xml"] = new OpenApiMediaType() } }, - ["405"] = new() + ["405"] = new OpenApiResponse { Description = "Invalid input", Content = new Dictionary { - ["application/json"] = new(), - ["application/xml"] = new() + ["application/json"] = new OpenApiMediaType(), + ["application/xml"] = new OpenApiMediaType() } } } }; - private static readonly OpenApiOperation _operationWithBody = new() + private static readonly OpenApiOperation _operationWithBody = new OpenApiOperation { Summary = "Updates a pet in the store with request body", Description = "", OperationId = "updatePetWithBody", Parameters = new List { - new() + new OpenApiParameter { Name = "petId", In = ParameterLocation.Path, @@ -168,46 +170,46 @@ public class OpenApiOperationTests Required = true, Schema = new() { - Type = "string" + Type = JsonSchemaType.String } }, }, - RequestBody = new() + RequestBody = new OpenApiRequestBody { Description = "Pet to update with", Required = true, Content = { - ["application/json"] = new() + ["application/json"] = new OpenApiMediaType { Schema = new() { - Type = "object" + Type = JsonSchemaType.Object } } }, Extensions = { - [OpenApiConstants.BodyName] = new OpenApiString("petObject") + [OpenApiConstants.BodyName] = new OpenApiAny("petObject") } }, - Responses = new() + Responses = new OpenApiResponses { - ["200"] = new() + ["200"] = new OpenApiResponse { Description = "Pet updated.", Content = new Dictionary { - ["application/json"] = new(), - ["application/xml"] = new() + ["application/json"] = new OpenApiMediaType(), + ["application/xml"] = new OpenApiMediaType() } }, - ["405"] = new() + ["405"] = new OpenApiResponse { Description = "Invalid input", Content = new Dictionary { - ["application/json"] = new(), - ["application/xml"] = new() + ["application/json"] = new OpenApiMediaType(), + ["application/xml"] = new OpenApiMediaType() } } @@ -249,41 +251,6 @@ public void ParseBasicOperationTwiceShouldYieldSameObject() operation.Should().BeEquivalentTo(_basicOperation); } - [Fact] - public void ParseOperationWithFormDataShouldSucceed() - { - // Arrange - MapNode node; - using (var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "operationWithFormData.yaml"))) - { - node = TestHelper.CreateYamlMapNode(stream); - } - - // Act - var operation = OpenApiV2Deserializer.LoadOperation(node); - - // Assert - operation.Should().BeEquivalentTo(_operationWithFormData); - } - - [Fact] - public void ParseOperationWithFormDataTwiceShouldYieldSameObject() - { - // Arrange - MapNode node; - using (var stream = new MemoryStream( - Encoding.Default.GetBytes(_operationWithFormData.SerializeAsYaml(OpenApiSpecVersion.OpenApi2_0)))) - { - node = TestHelper.CreateYamlMapNode(stream); - } - - // Act - var operation = OpenApiV2Deserializer.LoadOperation(node); - - // Assert - operation.Should().BeEquivalentTo(_operationWithFormData); - } - [Fact] public void ParseOperationWithBodyShouldSucceed() { @@ -298,7 +265,7 @@ public void ParseOperationWithBodyShouldSucceed() var operation = OpenApiV2Deserializer.LoadOperation(node); // Assert - operation.Should().BeEquivalentTo(_operationWithBody); + operation.Should().BeEquivalentTo(_operationWithBody, options => options.IgnoringCyclicReferences()); } [Fact] @@ -316,7 +283,7 @@ public void ParseOperationWithBodyTwiceShouldYieldSameObject() var operation = OpenApiV2Deserializer.LoadOperation(node); // Assert - operation.Should().BeEquivalentTo(_operationWithBody); + operation.Should().BeEquivalentTo(_operationWithBody, options => options.IgnoringCyclicReferences()); } [Fact] @@ -334,41 +301,41 @@ public void ParseOperationWithResponseExamplesShouldSucceed() // Assert operation.Should().BeEquivalentTo( - new OpenApiOperation + new OpenApiOperation() { - Responses = new() + Responses = new OpenApiResponses() { - { "200", new() + { "200", new OpenApiResponse() { Description = "An array of float response", Content = { - ["application/json"] = new() + ["application/json"] = new OpenApiMediaType() { Schema = new() { - Type = "array", + Type = JsonSchemaType.Array, Items = new() { - Type = "number", + Type = JsonSchemaType.Number, Format = "float" } }, - Example = new OpenApiArray + Example = new JsonArray() { - new OpenApiFloat(5), - new OpenApiFloat(6), - new OpenApiFloat(7), + 5.0, + 6.0, + 7.0 } }, - ["application/xml"] = new() + ["application/xml"] = new OpenApiMediaType() { Schema = new() { - Type = "array", + Type = JsonSchemaType.Array, Items = new() { - Type = "number", + Type = JsonSchemaType.Number, Format = "float" } } @@ -376,8 +343,13 @@ public void ParseOperationWithResponseExamplesShouldSucceed() } }} } - } - ); + }, options => options.IgnoringCyclicReferences() + .Excluding(o => o.Responses["200"].Content["application/json"].Example[0].Parent) + .Excluding(o => o.Responses["200"].Content["application/json"].Example[0].Root) + .Excluding(o => o.Responses["200"].Content["application/json"].Example[1].Parent) + .Excluding(o => o.Responses["200"].Content["application/json"].Example[1].Root) + .Excluding(o => o.Responses["200"].Content["application/json"].Example[2].Parent) + .Excluding(o => o.Responses["200"].Content["application/json"].Example[2].Root)); } [Fact] @@ -390,36 +362,30 @@ public void ParseOperationWithEmptyProducesArraySetsResponseSchemaIfExists() // Act var operation = OpenApiV2Deserializer.LoadOperation(node); + var expected = @"{ + ""produces"": [ + ""application/octet-stream"" + ], + ""responses"": { + ""200"": { + ""description"": ""OK"", + ""schema"": { + ""type"": ""string"", + ""description"": ""The content of the file."", + ""format"": ""binary"", + ""x-ms-summary"": ""File Content"" + } + } + } +}"; + + var stringBuilder = new StringBuilder(); + var jsonWriter = new OpenApiJsonWriter(new StringWriter(stringBuilder)); + operation.SerializeAsV2(jsonWriter); // Assert - operation.Should().BeEquivalentTo( - new OpenApiOperation - { - Responses = new() - { - { "200", new() - { - Description = "OK", - Content = - { - ["application/octet-stream"] = new() - { - Schema = new() - { - Format = "binary", - Description = "The content of the file.", - Type = "string", - Extensions = - { - ["x-ms-summary"] = new OpenApiString("File Content") - } - } - } - } - }} - } - } - ); + var actual = stringBuilder.ToString(); + actual.MakeLineBreaksEnvironmentNeutral().Should().BeEquivalentTo(expected.MakeLineBreaksEnvironmentNeutral()); } [Fact] @@ -434,7 +400,7 @@ public void ParseOperationWithBodyAndEmptyConsumesSetsRequestBodySchemaIfExists( var operation = OpenApiV2Deserializer.LoadOperation(node); // Assert - operation.Should().BeEquivalentTo(_operationWithBody); + operation.Should().BeEquivalentTo(_operationWithBody, options => options.IgnoringCyclicReferences()); } [Fact] diff --git a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiParameterTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiParameterTests.cs index 9eb20df4f..0b4b1a77e 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiParameterTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiParameterTests.cs @@ -1,13 +1,13 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. -using System.Collections.Generic; using System.IO; using FluentAssertions; +using FluentAssertions.Equivalency; using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Models; -using Microsoft.OpenApi.Readers.ParseNodes; -using Microsoft.OpenApi.Readers.V2; +using Microsoft.OpenApi.Reader.ParseNodes; +using Microsoft.OpenApi.Reader.V2; using Xunit; namespace Microsoft.OpenApi.Readers.Tests.V2Tests @@ -59,7 +59,7 @@ public void ParsePathParameterShouldSucceed() Required = true, Schema = new() { - Type = "string" + Type = JsonSchemaType.String } }); } @@ -87,10 +87,10 @@ public void ParseQueryParameterShouldSucceed() Required = false, Schema = new() { - Type = "array", + Type = JsonSchemaType.Array, Items = new() { - Type = "string" + Type = JsonSchemaType.String } }, Style = ParameterStyle.Form, @@ -98,131 +98,6 @@ public void ParseQueryParameterShouldSucceed() }); } - [Fact] - public void ParseFormDataParameterShouldSucceed() - { - // Arrange - MapNode node; - using (var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "formDataParameter.yaml"))) - { - node = TestHelper.CreateYamlMapNode(stream); - } - - // Act - var parameter = OpenApiV2Deserializer.LoadParameter(node); - - // Assert - // Form data parameter is currently not translated via LoadParameter. - // This design may be revisited and this unit test may likely change. - parameter.Should().BeNull(); - } - - [Fact] - public void ParseHeaderParameterShouldSucceed() - { - // Arrange - MapNode node; - using (var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "headerParameter.yaml"))) - { - node = TestHelper.CreateYamlMapNode(stream); - } - - // Act - var parameter = OpenApiV2Deserializer.LoadParameter(node); - - // Assert - parameter.Should().BeEquivalentTo( - new OpenApiParameter - { - In = ParameterLocation.Header, - Name = "token", - Description = "token to be passed as a header", - Required = true, - Style = ParameterStyle.Simple, - - Schema = new() - { - Type = "array", - Items = new() - { - Type = "integer", - Format = "int64", - Enum = new List - { - new OpenApiLong(1), - new OpenApiLong(2), - new OpenApiLong(3), - new OpenApiLong(4), - } - }, - Default = new OpenApiArray - { - new OpenApiLong(1), - new OpenApiLong(2) - }, - Enum = new List - { - new OpenApiArray { new OpenApiLong(1), new OpenApiLong(2) }, - new OpenApiArray { new OpenApiLong(2), new OpenApiLong(3) }, - new OpenApiArray { new OpenApiLong(3), new OpenApiLong(4) } - } - } - }); - } - - [Fact] - public void ParseHeaderParameterWithIncorrectDataTypeShouldSucceed() - { - // Arrange - MapNode node; - using (var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "headerParameterWithIncorrectDataType.yaml"))) - { - node = TestHelper.CreateYamlMapNode(stream); - } - - // Act - var parameter = OpenApiV2Deserializer.LoadParameter(node); - - // Assert - parameter.Should().BeEquivalentTo( - new OpenApiParameter - { - In = ParameterLocation.Header, - Name = "token", - Description = "token to be passed as a header", - Required = true, - Style = ParameterStyle.Simple, - - Schema = new() - { - Type = "array", - Items = new() - { - Type = "string", - Format = "date-time", - Enum = new List - { - new OpenApiString("1"), - new OpenApiString("2"), - new OpenApiString("3"), - new OpenApiString("4"), - } - }, - Default = new OpenApiArray - { - new OpenApiString("1"), - new OpenApiString("2") - }, - Enum = new List - { - new OpenApiArray { new OpenApiString("1"), new OpenApiString("2") }, - new OpenApiArray { new OpenApiString("2"), new OpenApiString("3") }, - new OpenApiArray { new OpenApiString("3"), new OpenApiString("4") } - } - } - }); - } - [Fact] public void ParseParameterWithNullLocationShouldSucceed() { @@ -246,7 +121,7 @@ public void ParseParameterWithNullLocationShouldSucceed() Required = true, Schema = new() { - Type = "string" + Type = JsonSchemaType.String } }); } @@ -274,7 +149,7 @@ public void ParseParameterWithNoLocationShouldSucceed() Required = true, Schema = new() { - Type = "string" + Type = JsonSchemaType.String } }); } @@ -326,7 +201,7 @@ public void ParseParameterWithUnknownLocationShouldSucceed() Required = true, Schema = new() { - Type = "string" + Type = JsonSchemaType.String } }); } @@ -354,11 +229,11 @@ public void ParseParameterWithDefaultShouldSucceed() Required = true, Schema = new() { - Type = "number", + Type = JsonSchemaType.Number, Format = "float", - Default = new OpenApiFloat(5) + Default = new OpenApiAny(5).Node } - }); + }, options => options.IgnoringCyclicReferences().Excluding(x => x.Schema.Default.Parent)); } [Fact] @@ -373,27 +248,30 @@ public void ParseParameterWithEnumShouldSucceed() // Act var parameter = OpenApiV2Deserializer.LoadParameter(node); - - // Assert - parameter.Should().BeEquivalentTo( - new OpenApiParameter + var expected = new OpenApiParameter + { + In = ParameterLocation.Path, + Name = "username", + Description = "username to fetch", + Required = true, + Schema = new() { - In = ParameterLocation.Path, - Name = "username", - Description = "username to fetch", - Required = true, - Schema = new() - { - Type = "number", - Format = "float", - Enum = + Type = JsonSchemaType.Number, + Format = "float", + Enum = { - new OpenApiFloat(7), - new OpenApiFloat(8), - new OpenApiFloat(9) + new OpenApiAny(7).Node, + new OpenApiAny(8).Node, + new OpenApiAny(9).Node } - } - }); + } + }; + + // Assert + parameter.Should().BeEquivalentTo(expected, options => options + .IgnoringCyclicReferences() + .Excluding((IMemberInfo memberInfo) => + memberInfo.Path.EndsWith("Parent"))); } } } diff --git a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiPathItemTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiPathItemTests.cs index 8891cb1bf..47f3903fa 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiPathItemTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiPathItemTests.cs @@ -7,8 +7,8 @@ using System.Linq; using FluentAssertions; using Microsoft.OpenApi.Models; -using Microsoft.OpenApi.Readers.ParseNodes; -using Microsoft.OpenApi.Readers.V2; +using Microsoft.OpenApi.Reader.ParseNodes; +using Microsoft.OpenApi.Reader.V2; using Xunit; namespace Microsoft.OpenApi.Readers.Tests.V2Tests @@ -30,10 +30,10 @@ public class OpenApiPathItemTests Required = true, Schema = new() { - Type = "array", + Type = JsonSchemaType.Array, Items = new() { - Type = "string" + Type = JsonSchemaType.String } }, Style = ParameterStyle.Simple @@ -56,7 +56,7 @@ public class OpenApiPathItemTests Required = true, Schema = new() { - Type = "string" + Type = JsonSchemaType.String } } }, @@ -68,18 +68,18 @@ public class OpenApiPathItemTests { Schema = new() { - Type = "object", + Type = JsonSchemaType.Object, Properties = { ["name"] = new() { Description = "Updated name of the pet", - Type = "string" + Type = JsonSchemaType.String }, ["status"] = new() { Description = "Updated status of the pet", - Type = "string" + Type = JsonSchemaType.String } }, Required = new HashSet @@ -90,20 +90,20 @@ public class OpenApiPathItemTests }, ["multipart/form-data"] = new() { - Schema = new() + Schema = new() { - Type = "object", + Type = JsonSchemaType.Object, Properties = { ["name"] = new() { Description = "Updated name of the pet", - Type = "string" + Type = JsonSchemaType.String }, ["status"] = new() { Description = "Updated status of the pet", - Type = "string" + Type = JsonSchemaType.String } }, Required = new HashSet @@ -151,7 +151,7 @@ public class OpenApiPathItemTests Required = true, Schema = new() { - Type = "string" + Type = JsonSchemaType.String } }, new() @@ -162,7 +162,7 @@ public class OpenApiPathItemTests Required = true, Schema = new() { - Type = "string" + Type = JsonSchemaType.String } } }, @@ -174,23 +174,23 @@ public class OpenApiPathItemTests { Schema = new() { - Type = "object", + Type = JsonSchemaType.Object, Properties = { ["name"] = new() { Description = "Updated name of the pet", - Type = "string" + Type = JsonSchemaType.String }, ["status"] = new() { Description = "Updated status of the pet", - Type = "string" + Type = JsonSchemaType.String }, ["skill"] = new() { Description = "Updated skill of the pet", - Type = "string" + Type = JsonSchemaType.String } }, Required = new HashSet @@ -203,23 +203,23 @@ public class OpenApiPathItemTests { Schema = new() { - Type = "object", + Type = JsonSchemaType.Object, Properties = { ["name"] = new() { Description = "Updated name of the pet", - Type = "string" + Type = JsonSchemaType.String }, ["status"] = new() { Description = "Updated status of the pet", - Type = "string" + Type = JsonSchemaType.String }, ["skill"] = new() { Description = "Updated skill of the pet", - Type = "string" + Type = JsonSchemaType.String } }, Required = new HashSet diff --git a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiSchemaTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiSchemaTests.cs index 6a6a8a15c..aee5aab7e 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiSchemaTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiSchemaTests.cs @@ -3,11 +3,14 @@ using System.IO; using FluentAssertions; -using Microsoft.OpenApi.Any; -using Microsoft.OpenApi.Models; -using Microsoft.OpenApi.Readers.ParseNodes; -using Microsoft.OpenApi.Readers.V2; +using Microsoft.OpenApi.Reader.V2; using Xunit; +using Microsoft.OpenApi.Reader.ParseNodes; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Any; +using System.Text.Json.Nodes; +using System.Collections.Generic; +using FluentAssertions.Equivalency; namespace Microsoft.OpenApi.Readers.Tests.V2Tests { @@ -30,13 +33,12 @@ public void ParseSchemaWithDefaultShouldSucceed() var schema = OpenApiV2Deserializer.LoadSchema(node); // Assert - schema.Should().BeEquivalentTo( - new OpenApiSchema - { - Type = "number", - Format = "float", - Default = new OpenApiFloat(5) - }); + schema.Should().BeEquivalentTo(new OpenApiSchema + { + Type = JsonSchemaType.Number, + Format = "float", + Default = 5 + }, options => options.IgnoringCyclicReferences().Excluding(x => x.Default.Parent)); } [Fact] @@ -56,10 +58,10 @@ public void ParseSchemaWithExampleShouldSucceed() schema.Should().BeEquivalentTo( new OpenApiSchema { - Type = "number", + Type = JsonSchemaType.Number, Format = "float", - Example = new OpenApiFloat(5) - }); + Example = 5 + }, options => options.IgnoringCyclicReferences().Excluding(x => x.Example.Parent)); } [Fact] @@ -76,18 +78,22 @@ public void ParseSchemaWithEnumShouldSucceed() var schema = OpenApiV2Deserializer.LoadSchema(node); // Assert - schema.Should().BeEquivalentTo( - new OpenApiSchema + var expected = new OpenApiSchema + { + Type = JsonSchemaType.Number, + Format = "float", + Enum = new List { - Type = "number", - Format = "float", - Enum = - { - new OpenApiFloat(7), - new OpenApiFloat(8), - new OpenApiFloat(9) - } - }); + new OpenApiAny(7).Node, + new OpenApiAny(8).Node, + new OpenApiAny(9).Node + } + }; + + schema.Should().BeEquivalentTo(expected, options => + options.IgnoringCyclicReferences() + .Excluding((IMemberInfo memberInfo) => + memberInfo.Path.EndsWith("Parent"))); } } } diff --git a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiSecuritySchemeTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiSecuritySchemeTests.cs index 80539af75..82565facd 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiSecuritySchemeTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiSecuritySchemeTests.cs @@ -1,12 +1,14 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. +using System; using System.IO; using System.Linq; using FluentAssertions; using Microsoft.OpenApi.Models; -using Microsoft.OpenApi.Readers.ParseNodes; -using Microsoft.OpenApi.Readers.V2; +using Microsoft.OpenApi.Reader; +using Microsoft.OpenApi.Reader.ParseNodes; +using Microsoft.OpenApi.Reader.V2; using SharpYaml.Serialization; using Xunit; @@ -20,13 +22,15 @@ public class OpenApiSecuritySchemeTests [Fact] public void ParseHttpSecuritySchemeShouldSucceed() { + // Arrange using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "basicSecurityScheme.yaml")); var document = LoadYamlDocument(stream); var diagnostic = new OpenApiDiagnostic(); var context = new ParsingContext(diagnostic); - var node = new MapNode(context, (YamlMappingNode)document.RootNode); + var asJsonNode = document.RootNode.ToJsonNode(); + var node = new MapNode(context, asJsonNode); // Act var securityScheme = OpenApiV2Deserializer.LoadSecurityScheme(node); @@ -43,12 +47,15 @@ public void ParseHttpSecuritySchemeShouldSucceed() [Fact] public void ParseApiKeySecuritySchemeShouldSucceed() { + // Arrange using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "apiKeySecurityScheme.yaml")); var document = LoadYamlDocument(stream); var diagnostic = new OpenApiDiagnostic(); var context = new ParsingContext(diagnostic); - var node = new MapNode(context, (YamlMappingNode)document.RootNode); + var asJsonNode = document.RootNode.ToJsonNode(); + + var node = new MapNode(context, asJsonNode); // Act var securityScheme = OpenApiV2Deserializer.LoadSecurityScheme(node); @@ -71,10 +78,12 @@ public void ParseOAuth2ImplicitSecuritySchemeShouldSucceed() var diagnostic = new OpenApiDiagnostic(); var context = new ParsingContext(diagnostic); - var node = new MapNode(context, (YamlMappingNode)document.RootNode); + var asJsonNode = document.RootNode.ToJsonNode(); - // Act - var securityScheme = OpenApiV2Deserializer.LoadSecurityScheme(node); + var node = new MapNode(context, asJsonNode); + + // Act + var securityScheme = OpenApiV2Deserializer.LoadSecurityScheme(node); // Assert securityScheme.Should().BeEquivalentTo( @@ -99,12 +108,14 @@ public void ParseOAuth2ImplicitSecuritySchemeShouldSucceed() [Fact] public void ParseOAuth2PasswordSecuritySchemeShouldSucceed() { + // Arrange using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "oauth2PasswordSecurityScheme.yaml")); var document = LoadYamlDocument(stream); var diagnostic = new OpenApiDiagnostic(); var context = new ParsingContext(diagnostic); - var node = new MapNode(context, (YamlMappingNode)document.RootNode); + var asJsonNode = document.RootNode.ToJsonNode(); + var node = new MapNode(context, asJsonNode); // Act var securityScheme = OpenApiV2Deserializer.LoadSecurityScheme(node); @@ -114,15 +125,15 @@ public void ParseOAuth2PasswordSecuritySchemeShouldSucceed() new OpenApiSecurityScheme { Type = SecuritySchemeType.OAuth2, - Flows = new() + Flows = new OpenApiOAuthFlows { - Password = new() + Password = new OpenApiOAuthFlow { - AuthorizationUrl = new("http://swagger.io/api/oauth/dialog"), + AuthorizationUrl = new Uri("http://swagger.io/api/oauth/dialog"), Scopes = { - ["write:pets"] = "modify pets in your account", - ["read:pets"] = "read your pets" + ["write:pets"] = "modify pets in your account", + ["read:pets"] = "read your pets" } } } @@ -132,12 +143,14 @@ public void ParseOAuth2PasswordSecuritySchemeShouldSucceed() [Fact] public void ParseOAuth2ApplicationSecuritySchemeShouldSucceed() { + // Arrange using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "oauth2ApplicationSecurityScheme.yaml")); var document = LoadYamlDocument(stream); var diagnostic = new OpenApiDiagnostic(); var context = new ParsingContext(diagnostic); - var node = new MapNode(context, (YamlMappingNode)document.RootNode); + var asJsonNode = document.RootNode.ToJsonNode(); + var node = new MapNode(context, asJsonNode); // Act var securityScheme = OpenApiV2Deserializer.LoadSecurityScheme(node); @@ -147,15 +160,15 @@ public void ParseOAuth2ApplicationSecuritySchemeShouldSucceed() new OpenApiSecurityScheme { Type = SecuritySchemeType.OAuth2, - Flows = new() + Flows = new OpenApiOAuthFlows { - ClientCredentials = new() + ClientCredentials = new OpenApiOAuthFlow { - AuthorizationUrl = new("http://swagger.io/api/oauth/dialog"), + AuthorizationUrl = new Uri("http://swagger.io/api/oauth/dialog"), Scopes = { - ["write:pets"] = "modify pets in your account", - ["read:pets"] = "read your pets" + ["write:pets"] = "modify pets in your account", + ["read:pets"] = "read your pets" } } } @@ -165,13 +178,15 @@ public void ParseOAuth2ApplicationSecuritySchemeShouldSucceed() [Fact] public void ParseOAuth2AccessCodeSecuritySchemeShouldSucceed() { + // Arrange using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "oauth2AccessCodeSecurityScheme.yaml")); var document = LoadYamlDocument(stream); var diagnostic = new OpenApiDiagnostic(); var context = new ParsingContext(diagnostic); - var node = new MapNode(context, (YamlMappingNode)document.RootNode); + var asJsonNode = document.RootNode.ToJsonNode(); + var node = new MapNode(context, asJsonNode); // Act var securityScheme = OpenApiV2Deserializer.LoadSecurityScheme(node); @@ -181,15 +196,15 @@ public void ParseOAuth2AccessCodeSecuritySchemeShouldSucceed() new OpenApiSecurityScheme { Type = SecuritySchemeType.OAuth2, - Flows = new() + Flows = new OpenApiOAuthFlows { - AuthorizationCode = new() + AuthorizationCode = new OpenApiOAuthFlow { - AuthorizationUrl = new("http://swagger.io/api/oauth/dialog"), + AuthorizationUrl = new Uri("http://swagger.io/api/oauth/dialog"), Scopes = { - ["write:pets"] = "modify pets in your account", - ["read:pets"] = "read your pets" + ["write:pets"] = "modify pets in your account", + ["read:pets"] = "read your pets" } } } diff --git a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiServerTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiServerTests.cs index f254800b9..2e5779adb 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiServerTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiServerTests.cs @@ -1,12 +1,18 @@ -using FluentAssertions; +using System.Linq; +using FluentAssertions; using Microsoft.OpenApi.Models; -using System.Linq; +using Microsoft.OpenApi.Reader; using Xunit; namespace Microsoft.OpenApi.Readers.Tests.V2Tests { public class OpenApiServerTests { + public OpenApiServerTests() + { + OpenApiReaderRegistry.RegisterReader("yaml", new OpenApiYamlReader()); + } + [Fact] public void NoServer() { @@ -18,13 +24,10 @@ public void NoServer() version: 1.0.0 paths: {} """; - var reader = new OpenApiStringReader(new() - { - }); - var doc = reader.Read(input, out var diagnostic); + var result = OpenApiDocument.Parse(input, "yaml"); - Assert.Empty(doc.Servers); + Assert.Empty(result.OpenApiDocument.Servers); } [Fact] @@ -40,13 +43,9 @@ public void JustSchemeNoDefault() - http paths: {} """; - var reader = new OpenApiStringReader(new() - { - }); + var result = OpenApiDocument.Parse(input, "yaml"); - var doc = reader.Read(input, out var diagnostic); - - Assert.Empty(doc.Servers); + Assert.Empty(result.OpenApiDocument.Servers); } [Fact] @@ -61,14 +60,10 @@ public void JustHostNoDefault() host: www.foo.com paths: {} """; - var reader = new OpenApiStringReader(new() - { - }); + var result = OpenApiDocument.Parse(input, "yaml"); - var doc = reader.Read(input, out var _); - - var server = doc.Servers.First(); - Assert.Single(doc.Servers); + var server = result.OpenApiDocument.Servers.First(); + Assert.Single(result.OpenApiDocument.Servers); Assert.Equal("//www.foo.com", server.Url); } @@ -86,15 +81,14 @@ public void NoBasePath() - http paths: {} """; - var reader = new OpenApiStringReader(new() + var settings = new OpenApiReaderSettings { BaseUrl = new("https://www.foo.com/spec.yaml") - }); - - var doc = reader.Read(input, out var diagnostic); + }; - var server = doc.Servers.First(); - Assert.Single(doc.Servers); + var result = OpenApiDocument.Parse(input, "yaml", settings); + var server = result.OpenApiDocument.Servers.First(); + Assert.Single(result.OpenApiDocument.Servers); Assert.Equal("http://www.foo.com", server.Url); } @@ -110,14 +104,10 @@ public void JustBasePathNoDefault() basePath: /baz paths: {} """; - var reader = new OpenApiStringReader(new() - { - }); - - var doc = reader.Read(input, out var diagnostic); + var result = OpenApiDocument.Parse(input, "yaml"); - var server = doc.Servers.First(); - Assert.Single(doc.Servers); + var server = result.OpenApiDocument.Servers.First(); + Assert.Single(result.OpenApiDocument.Servers); Assert.Equal("/baz", server.Url); } @@ -134,15 +124,15 @@ public void JustSchemeWithCustomHost() - http paths: {} """; - var reader = new OpenApiStringReader(new() + var settings = new OpenApiReaderSettings { BaseUrl = new("https://bing.com/foo") - }); + }; - var doc = reader.Read(input, out var diagnostic); + var result = OpenApiDocument.Parse(input, "yaml", settings); - var server = doc.Servers.First(); - Assert.Single(doc.Servers); + var server = result.OpenApiDocument.Servers.First(); + Assert.Single(result.OpenApiDocument.Servers); Assert.Equal("http://bing.com/foo", server.Url); } @@ -159,15 +149,15 @@ public void JustSchemeWithCustomHostWithEmptyPath() - http paths: {} """; - var reader = new OpenApiStringReader(new() + var settings = new OpenApiReaderSettings { BaseUrl = new("https://bing.com") - }); + }; - var doc = reader.Read(input, out var diagnostic); + var result = OpenApiDocument.Parse(input, "yaml", settings); - var server = doc.Servers.First(); - Assert.Single(doc.Servers); + var server = result.OpenApiDocument.Servers.First(); + Assert.Single(result.OpenApiDocument.Servers); Assert.Equal("http://bing.com", server.Url); } @@ -183,15 +173,15 @@ public void JustBasePathWithCustomHost() basePath: /api paths: {} """; - var reader = new OpenApiStringReader(new() + var settings = new OpenApiReaderSettings { BaseUrl = new("https://bing.com") - }); + }; - var doc = reader.Read(input, out var diagnostic); + var result = OpenApiDocument.Parse(input, "yaml", settings); - var server = doc.Servers.First(); - Assert.Single(doc.Servers); + var server = result.OpenApiDocument.Servers.First(); + Assert.Single(result.OpenApiDocument.Servers); Assert.Equal("https://bing.com/api", server.Url); } @@ -207,15 +197,15 @@ public void JustHostWithCustomHost() host: www.example.com paths: {} """; - var reader = new OpenApiStringReader(new() + var settings = new OpenApiReaderSettings { BaseUrl = new("https://bing.com") - }); + }; - var doc = reader.Read(input, out var diagnostic); + var result = OpenApiDocument.Parse(input, "yaml", settings); - var server = doc.Servers.First(); - Assert.Single(doc.Servers); + var server = result.OpenApiDocument.Servers.First(); + Assert.Single(result.OpenApiDocument.Servers); Assert.Equal("https://www.example.com", server.Url); } @@ -231,15 +221,15 @@ public void JustHostWithCustomHostWithApi() host: prod.bing.com paths: {} """; - var reader = new OpenApiStringReader(new() + + var settings = new OpenApiReaderSettings { BaseUrl = new("https://dev.bing.com/api/description.yaml") - }); - - var doc = reader.Read(input, out var _); + }; - var server = doc.Servers.First(); - Assert.Single(doc.Servers); + var result = OpenApiDocument.Parse(input, "yaml", settings); + var server = result.OpenApiDocument.Servers.First(); + Assert.Single(result.OpenApiDocument.Servers); Assert.Equal("https://prod.bing.com", server.Url); } @@ -257,17 +247,17 @@ public void MultipleServers() - https paths: {} """; - var reader = new OpenApiStringReader(new() + + var settings = new OpenApiReaderSettings { BaseUrl = new("https://dev.bing.com/api") - }); - - var doc = reader.Read(input, out var diagnostic); + }; - var server = doc.Servers.First(); - Assert.Equal(2, doc.Servers.Count); + var result = OpenApiDocument.Parse(input, "yaml", settings); + var server = result.OpenApiDocument.Servers.First(); + Assert.Equal(2, result.OpenApiDocument.Servers.Count); Assert.Equal("http://dev.bing.com/api", server.Url); - Assert.Equal("https://dev.bing.com/api", doc.Servers.Last().Url); + Assert.Equal("https://dev.bing.com/api", result.OpenApiDocument.Servers.Last().Url); } [Fact] @@ -282,15 +272,16 @@ public void LocalHostWithCustomHost() host: localhost:23232 paths: {} """; - var reader = new OpenApiStringReader(new() + + var settings = new OpenApiReaderSettings { BaseUrl = new("https://bing.com") - }); + }; - var doc = reader.Read(input, out var diagnostic); + var result = OpenApiDocument.Parse(input, "yaml", settings); - var server = doc.Servers.First(); - Assert.Single(doc.Servers); + var server = result.OpenApiDocument.Servers.First(); + Assert.Single(result.OpenApiDocument.Servers); Assert.Equal("https://localhost:23232", server.Url); } @@ -306,19 +297,21 @@ public void InvalidHostShouldYieldError() host: http://test.microsoft.com paths: {} """; - var reader = new OpenApiStringReader(new() + + var settings = new OpenApiReaderSettings { BaseUrl = new("https://bing.com") - }); + }; - var doc = reader.Read(input, out var diagnostic); - doc.Servers.Count.Should().Be(0); - diagnostic.Should().BeEquivalentTo( + var result = OpenApiDocument.Parse(input, "yaml", settings); + result.OpenApiDocument.Servers.Count.Should().Be(0); + result.OpenApiDiagnostic.Should().BeEquivalentTo( new OpenApiDiagnostic { Errors = { - new OpenApiError("#/", "Invalid host") + new OpenApiError("#/", "Invalid host"), + new OpenApiError("", "Paths is a REQUIRED field at #/") }, SpecificationVersion = OpenApiSpecVersion.OpenApi2_0 }); diff --git a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/Samples/docWithEmptyProduces.yaml b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/Samples/docWithEmptyProduces.yaml index ba9213c08..d4f262ca4 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/Samples/docWithEmptyProduces.yaml +++ b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/Samples/docWithEmptyProduces.yaml @@ -13,8 +13,8 @@ paths: description: Successful response schema: format: binary, - description: The content of the file., - type: string, + description: The content of the file. + type: string x-ms-summary: File Content components: {} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiDocumentTests.ParseDocumentWith31PropertiesWorks.verified.txt b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiDocumentTests.ParseDocumentWith31PropertiesWorks.verified.txt new file mode 100644 index 000000000..6c2f850fe --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiDocumentTests.ParseDocumentWith31PropertiesWorks.verified.txt @@ -0,0 +1,110 @@ +openapi: '3.1.0' +jsonSchemaDialect: https://json-schema.org/draft/2020-12/schema +info: + title: Sample OpenAPI 3.1 API + description: A sample API demonstrating OpenAPI 3.1 features + license: + name: Apache 2.0 + url: https://www.apache.org/licenses/LICENSE-2.0.html + identifier: Apache-2.0 + version: 2.0.0 + summary: Sample OpenAPI 3.1 API with the latest features +servers: + - url: https://api.example.com/v2 + description: Main production server +paths: + /pets: + get: + tags: + - pets + summary: List all pets + operationId: listPets + parameters: + - name: limit + in: query + description: How many items to return at one time (max 100) + schema: + exclusiveMaximum: 100 + exclusiveMinimum: 1 + type: integer + responses: + '200': + description: A paged array of pets + content: + application/json: + schema: + $ref: https://example.com/schemas/pet.json + /sample: + get: + summary: Sample endpoint + responses: + '200': + description: Sample response + content: + application/json: + schema: + $id: https://example.com/schemas/person.schema.yaml + $schema: https://json-schema.org/draft/2020-12/schema + $comment: A schema defining a pet object with optional references to dynamic components. + $vocabulary: + https://json-schema.org/draft/2020-12/vocab/core: true + https://json-schema.org/draft/2020-12/vocab/applicator: true + https://json-schema.org/draft/2020-12/vocab/validation: true + https://json-schema.org/draft/2020-12/vocab/meta-data: false + https://json-schema.org/draft/2020-12/vocab/format-annotation: false + $dynamicAnchor: addressDef + title: Pet + required: + - name + type: object + properties: + name: + $comment: The pet's full name + type: string + address: + $comment: Reference to an address definition which can change dynamically + $dynamicRef: '#addressDef' + description: Schema for a pet object +components: + schemas: + Pet: + $id: https://example.com/schemas/pet.json + $comment: This schema represents a pet in the system. + $defs: + ExtraInfo: + type: string + required: + - id + - weight + type: object + properties: + id: + type: string + format: uuid + weight: + exclusiveMinimum: 0 + type: number + description: Weight of the pet in kilograms + attributes: + patternProperties: + '^attr_[A-Za-z]+$': + type: string + type: + - 'null' + - object + description: Dynamic attributes for the pet +security: + - api_key: [ ] +webhooks: + newPetAlert: + post: + summary: Notify about a new pet being added + requestBody: + content: + application/json: + schema: + type: string + required: true + responses: + '200': + description: Webhook processed successfully \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiDocumentTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiDocumentTests.cs new file mode 100644 index 000000000..638d69667 --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiDocumentTests.cs @@ -0,0 +1,549 @@ +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Threading.Tasks; +using FluentAssertions; +using Microsoft.OpenApi.Extensions; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Models.References; +using Microsoft.OpenApi.Reader; +using Microsoft.OpenApi.Tests; +using Microsoft.OpenApi.Writers; +using Microsoft.OpenApi.Services; +using Xunit; +using System.Linq; +using VerifyXunit; + +namespace Microsoft.OpenApi.Readers.Tests.V31Tests +{ + public class OpenApiDocumentTests + { + private const string SampleFolderPath = "V31Tests/Samples/OpenApiDocument/"; + + public OpenApiDocumentTests() + { + OpenApiReaderRegistry.RegisterReader(OpenApiConstants.Yaml, new OpenApiYamlReader()); + } + + [Fact] + public void ParseDocumentWithWebhooksShouldSucceed() + { + // Arrange and Act + var actual = OpenApiDocument.Load(Path.Combine(SampleFolderPath, "documentWithWebhooks.yaml")); + var petSchema = new OpenApiSchemaReference("petSchema", actual.OpenApiDocument); + + var newPetSchema = new OpenApiSchemaReference("newPetSchema", actual.OpenApiDocument); + + var components = new OpenApiComponents + { + Schemas = + { + ["petSchema"] = new() + { + Type = JsonSchemaType.Object, + Required = new HashSet + { + "id", + "name" + }, + Properties = new Dictionary + { + ["id"] = new() + { + Type = JsonSchemaType.Integer, + Format = "int64" + }, + ["name"] = new() + { + Type = JsonSchemaType.String + }, + ["tag"] = new() + { + Type = JsonSchemaType.String + }, + } + }, + ["newPetSchema"] = new() + { + Type = JsonSchemaType.Object, + Required = new HashSet + { + "name" + }, + Properties = new Dictionary + { + ["id"] = new() + { + Type = JsonSchemaType.Integer, + Format = "int64" + }, + ["name"] = new() + { + Type = JsonSchemaType.String + }, + ["tag"] = new() + { + Type = JsonSchemaType.String + }, + } + } + } + }; + + var expected = new OpenApiDocument + { + Info = new OpenApiInfo + { + Version = "1.0.0", + Title = "Webhook Example" + }, + Webhooks = new Dictionary + { + ["pets"] = new OpenApiPathItem + { + Operations = new Dictionary + { + [OperationType.Get] = new OpenApiOperation + { + Description = "Returns all pets from the system that the user has access to", + OperationId = "findPets", + Parameters = new List + { + new OpenApiParameter + { + Name = "tags", + In = ParameterLocation.Query, + Description = "tags to filter by", + Required = false, + Schema = new() + { + Type = JsonSchemaType.Array, + Items = new() + { + Type = JsonSchemaType.String + } + } + }, + new OpenApiParameter + { + Name = "limit", + In = ParameterLocation.Query, + Description = "maximum number of results to return", + Required = false, + Schema = new() + { + Type = JsonSchemaType.Integer, + Format = "int32" + } + } + }, + Responses = new OpenApiResponses + { + ["200"] = new OpenApiResponse + { + Description = "pet response", + Content = new Dictionary + { + ["application/json"] = new OpenApiMediaType + { + Schema = new() + { + Type = JsonSchemaType.Array, + Items = petSchema + } + }, + ["application/xml"] = new OpenApiMediaType + { + Schema = new() + { + Type = JsonSchemaType.Array, + Items = petSchema + } + } + } + } + } + }, + [OperationType.Post] = new OpenApiOperation + { + RequestBody = new OpenApiRequestBody + { + Description = "Information about a new pet in the system", + Required = true, + Content = new Dictionary + { + ["application/json"] = new OpenApiMediaType + { + Schema = newPetSchema + } + } + }, + Responses = new OpenApiResponses + { + ["200"] = new OpenApiResponse + { + Description = "Return a 200 status to indicate that the data was received successfully", + Content = new Dictionary + { + ["application/json"] = new OpenApiMediaType + { + Schema = petSchema + } + } + } + } + } + } + } + }, + Components = components + }; + + // 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)); + } + + [Fact] + public void ParseDocumentsWithReusablePathItemInWebhooksSucceeds() + { + // Arrange && Act + var actual = OpenApiDocument.Load("V31Tests/Samples/OpenApiDocument/documentWithReusablePaths.yaml"); + + var components = new OpenApiComponents + { + Schemas = new Dictionary + { + ["petSchema"] = new() + { + Type = JsonSchemaType.Object, + Required = new HashSet + { + "id", + "name" + }, + Properties = new Dictionary + { + ["id"] = new() + { + Type = JsonSchemaType.Integer, + Format = "int64" + }, + ["name"] = new() + { + Type = JsonSchemaType.String + }, + ["tag"] = new() + { + Type = JsonSchemaType.String + }, + } + }, + ["newPetSchema"] = new() + { + Type = JsonSchemaType.Object, + Required = new HashSet + { + "name" + }, + Properties = new Dictionary + { + ["id"] = new() + { + Type = JsonSchemaType.Integer, + Format = "int64" + }, + ["name"] = new() + { + Type = JsonSchemaType.String + }, + ["tag"] = new() + { + Type = JsonSchemaType.String + }, + } + } + } + }; + + // Create a clone of the schema to avoid modifying things in components. + var petSchema = new OpenApiSchemaReference("petSchema", actual.OpenApiDocument); + + var newPetSchema = new OpenApiSchemaReference("newPetSchema", actual.OpenApiDocument); + + components.PathItems = new Dictionary + { + ["pets"] = new OpenApiPathItem + { + Operations = new Dictionary + { + [OperationType.Get] = new OpenApiOperation + { + Description = "Returns all pets from the system that the user has access to", + OperationId = "findPets", + Parameters = new List + { + new OpenApiParameter + { + Name = "tags", + In = ParameterLocation.Query, + Description = "tags to filter by", + Required = false, + Schema = new() + { + Type = JsonSchemaType.Array, + Items = new() + { + Type = JsonSchemaType.String + } + } + }, + new OpenApiParameter + { + Name = "limit", + In = ParameterLocation.Query, + Description = "maximum number of results to return", + Required = false, + Schema = new() + { + Type = JsonSchemaType.Integer, + Format = "int32" + } + } + }, + Responses = new OpenApiResponses + { + ["200"] = new OpenApiResponse + { + Description = "pet response", + Content = new Dictionary + { + ["application/json"] = new OpenApiMediaType + { + Schema = new OpenApiSchema + { + Type = JsonSchemaType.Array, + Items = petSchema + } + }, + ["application/xml"] = new OpenApiMediaType + { + Schema = new OpenApiSchema + { + Type = JsonSchemaType.Array, + Items = petSchema + } + } + } + } + } + }, + [OperationType.Post] = new OpenApiOperation + { + RequestBody = new OpenApiRequestBody + { + Description = "Information about a new pet in the system", + Required = true, + Content = new Dictionary + { + ["application/json"] = new OpenApiMediaType + { + Schema = newPetSchema + } + } + }, + Responses = new OpenApiResponses + { + ["200"] = new OpenApiResponse + { + Description = "Return a 200 status to indicate that the data was received successfully", + Content = new Dictionary + { + ["application/json"] = new OpenApiMediaType + { + Schema = petSchema + }, + } + } + } + } + } + } + }; + + var expected = new OpenApiDocument + { + Info = new OpenApiInfo + { + Title = "Webhook Example", + Version = "1.0.0" + }, + JsonSchemaDialect = "http://json-schema.org/draft-07/schema#", + Webhooks = new Dictionary + { + ["pets"] = components.PathItems["pets"] + }, + Components = components + }; + + // Assert + actual.OpenApiDocument.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(); + } + + [Fact] + public void ParseDocumentWithExampleInSchemaShouldSucceed() + { + // Arrange + var outputStringWriter = new StringWriter(CultureInfo.InvariantCulture); + var writer = new OpenApiJsonWriter(outputStringWriter, new OpenApiJsonWriterSettings { Terse = false }); + + // Act + var actual = OpenApiDocument.Load(Path.Combine(SampleFolderPath, "docWithExample.yaml")); + actual.OpenApiDocument.SerializeAsV31(writer); + + // Assert + Assert.NotNull(actual); + } + + [Fact] + 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 expectedSchema = new OpenApiSchema + { + Type = JsonSchemaType.Object, + Properties = new Dictionary + { + ["prop1"] = new OpenApiSchema + { + Type = JsonSchemaType.String + }, + ["prop2"] = new OpenApiSchema + { + Type = JsonSchemaType.String + }, + ["prop3"] = new OpenApiSchema + { + Type = JsonSchemaType.String + } + }, + PatternProperties = new Dictionary + { + ["^x-.*$"] = new OpenApiSchema + { + Type = JsonSchemaType.String + } + } + }; + + // Serialization + var mediaType = result.OpenApiDocument.Paths["/example"].Operations[OperationType.Get].Responses["200"].Content["application/json"]; + + var expectedMediaType = @"schema: + patternProperties: + ^x-.*$: + type: string + type: object + properties: + prop1: + type: string + prop2: + type: string + prop3: + type: string"; + + var actualMediaType = mediaType.SerializeAsYaml(OpenApiSpecVersion.OpenApi3_1); + + // Assert + actualSchema.Should().BeEquivalentTo(expectedSchema); + actualMediaType.MakeLineBreaksEnvironmentNeutral().Should().BeEquivalentTo(expectedMediaType.MakeLineBreaksEnvironmentNeutral()); + } + + [Fact] + 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; + + // Assert + Assert.Equal(JsonSchemaType.Object, responseSchema.Type); + Assert.Equal(JsonSchemaType.Object, requestBodySchema.Type); + Assert.Equal(JsonSchemaType.String, parameterSchema.Type); + } + + [Fact] + public async Task ExternalDocumentDereferenceToOpenApiDocumentUsingJsonPointerWorks() + { + // Arrange + var path = Path.Combine(Directory.GetCurrentDirectory(), SampleFolderPath); + + var settings = new OpenApiReaderSettings + { + LoadExternalRefs = true, + BaseUrl = new(path), + }; + + // 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; + + // Assert + result.OpenApiDocument.Workspace.Contains("./externalResource.yaml"); + responseSchema.Properties.Count.Should().Be(2); // reference has been resolved + } + + [Fact] + public async Task ParseExternalDocumentDereferenceToOpenApiDocumentByIdWorks() + { + // Arrange + var path = Path.Combine(Directory.GetCurrentDirectory(), SampleFolderPath); + + var settings = new OpenApiReaderSettings + { + LoadExternalRefs = true, + BaseUrl = new(path), + }; + + // Act + var result = await OpenApiDocument.LoadAsync(Path.Combine(SampleFolderPath, "externalRefById.yaml"), settings); + var doc2 = OpenApiDocument.Load(Path.Combine(SampleFolderPath, "externalResource.yaml")).OpenApiDocument; + + var requestBodySchema = result.OpenApiDocument.Paths["/resource"].Operations[OperationType.Get].Parameters.First().Schema; + result.OpenApiDocument.Workspace.RegisterComponents(doc2); + + // Assert + requestBodySchema.Properties.Count.Should().Be(2); // reference has been resolved + } + + [Fact] + public async Task ParseDocumentWith31PropertiesWorks() + { + var path = Path.Combine(SampleFolderPath, "documentWith31Properties.yaml"); + var doc = OpenApiDocument.Load(path).OpenApiDocument; + var outputStringWriter = new StringWriter(); + doc.SerializeAsV31(new OpenApiYamlWriter(outputStringWriter)); + outputStringWriter.Flush(); + var actual = outputStringWriter.GetStringBuilder().ToString(); + + // Assert + await Verifier.Verify(actual); + } + } +} diff --git a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiInfoTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiInfoTests.cs new file mode 100644 index 000000000..8ecfcf7d5 --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiInfoTests.cs @@ -0,0 +1,58 @@ +using System; +using System.IO; +using System.Linq; +using FluentAssertions; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Reader; +using Microsoft.OpenApi.Reader.ParseNodes; +using Microsoft.OpenApi.Reader.V31; +using SharpYaml.Serialization; +using Xunit; + +namespace Microsoft.OpenApi.Readers.Tests.V31Tests +{ + public class OpenApiInfoTests + { + private const string SampleFolderPath = "V31Tests/Samples/OpenApiInfo/"; + + [Fact] + public void ParseBasicInfoShouldSucceed() + { + using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "basicInfo.yaml")); + var yamlStream = new YamlStream(); + yamlStream.Load(new StreamReader(stream)); + var yamlNode = yamlStream.Documents.First().RootNode; + + var diagnostic = new OpenApiDiagnostic(); + var context = new ParsingContext(diagnostic); + + var asJsonNode = yamlNode.ToJsonNode(); + var node = new MapNode(context, asJsonNode); + + // Act + var openApiInfo = OpenApiV31Deserializer.LoadInfo(node); + + // Assert + openApiInfo.Should().BeEquivalentTo( + new OpenApiInfo + { + Title = "Basic Info", + Summary = "Sample Summary", + Description = "Sample Description", + Version = "1.0.1", + TermsOfService = new Uri("http://swagger.io/terms/"), + Contact = new OpenApiContact + { + Email = "support@swagger.io", + Name = "API Support", + Url = new Uri("http://www.swagger.io/support") + }, + License = new OpenApiLicense + { + Name = "Apache 2.0", + Url = new Uri("http://www.apache.org/licenses/LICENSE-2.0.html") + } + }); + } + } +} diff --git a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiLicenseTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiLicenseTests.cs new file mode 100644 index 000000000..cb617064e --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiLicenseTests.cs @@ -0,0 +1,47 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System.IO; +using System.Linq; +using FluentAssertions; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Reader; +using Microsoft.OpenApi.Reader.ParseNodes; +using Microsoft.OpenApi.Reader.V31; +using SharpYaml.Serialization; +using Xunit; + +namespace Microsoft.OpenApi.Readers.Tests.V31Tests +{ + + public class OpenApiLicenseTests + { + private const string SampleFolderPath = "V31Tests/Samples/OpenApiLicense/"; + + [Fact] + public void ParseLicenseWithSpdxIdentifierShouldSucceed() + { + using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "licenseWithSpdxIdentifier.yaml")); + var yamlStream = new YamlStream(); + yamlStream.Load(new StreamReader(stream)); + var yamlNode = yamlStream.Documents.First().RootNode; + + var diagnostic = new OpenApiDiagnostic(); + var context = new ParsingContext(diagnostic); + + var asJsonNode = yamlNode.ToJsonNode(); + var node = new MapNode(context, asJsonNode); + + // Act + var license = OpenApiV31Deserializer.LoadLicense(node); + + // Assert + license.Should().BeEquivalentTo( + new OpenApiLicense + { + Name = "Apache 2.0", + Identifier = "Apache-2.0" + }); + } + } +} diff --git a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiSchemaTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiSchemaTests.cs new file mode 100644 index 000000000..967bb0f3e --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiSchemaTests.cs @@ -0,0 +1,456 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System.Collections.Generic; +using System.IO; +using System.Text.Json.Nodes; +using FluentAssertions; +using FluentAssertions.Equivalency; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Reader; +using Microsoft.OpenApi.Tests; +using Microsoft.OpenApi.Writers; +using Xunit; + +namespace Microsoft.OpenApi.Readers.Tests.V31Tests +{ + public class OpenApiSchemaTests + { + private const string SampleFolderPath = "V31Tests/Samples/OpenApiSchema/"; + + public OpenApiSchemaTests() + { + OpenApiReaderRegistry.RegisterReader("yaml", new OpenApiYamlReader()); + } + + [Fact] + public void ParseBasicV31SchemaShouldSucceed() + { + var expectedObject = new OpenApiSchema() + { + Id = "https://example.com/arrays.schema.json", + Schema = "https://json-schema.org/draft/2020-12/schema", + Description = "A representation of a person, company, organization, or place", + Type = JsonSchemaType.Object, + Properties = new Dictionary + { + ["fruits"] = new OpenApiSchema + { + Type = JsonSchemaType.Array, + Items = new OpenApiSchema + { + Type = JsonSchemaType.String + } + }, + ["vegetables"] = new OpenApiSchema + { + Type = JsonSchemaType.Array + } + }, + Definitions = new Dictionary + { + ["veggie"] = new OpenApiSchema + { + Type = JsonSchemaType.Object, + Required = new HashSet + { + "veggieName", + "veggieLike" + }, + Properties = new Dictionary + { + ["veggieName"] = new OpenApiSchema + { + Type = JsonSchemaType.String, + Description = "The name of the vegetable." + }, + ["veggieLike"] = new OpenApiSchema + { + Type = JsonSchemaType.Boolean, + Description = "Do I like this vegetable?" + } + } + } + } + }; + + // Act + var schema = OpenApiModelFactory.Load( + System.IO.Path.Combine(SampleFolderPath, "jsonSchema.json"), OpenApiSpecVersion.OpenApi3_1, out _); + + // Assert + schema.Should().BeEquivalentTo(expectedObject); + } + + [Fact] + public void ParseSchemaWithTypeArrayWorks() + { + // Arrange + var schema = @"{ + ""$id"": ""https://example.com/arrays.schema.json"", + ""$schema"": ""https://json-schema.org/draft/2020-12/schema"", + ""description"": ""A representation of a person, company, organization, or place"", + ""type"": [""object"", ""null""] +}"; + + var expected = new OpenApiSchema() + { + Id = "https://example.com/arrays.schema.json", + Schema = "https://json-schema.org/draft/2020-12/schema", + Description = "A representation of a person, company, organization, or place", + Type = JsonSchemaType.Object | JsonSchemaType.Null + }; + + // Act + var actual = OpenApiModelFactory.Parse(schema, OpenApiSpecVersion.OpenApi3_1, out _); + + // Assert + actual.Should().BeEquivalentTo(expected); + } + + [Fact] + public void TestSchemaCopyConstructorWithTypeArrayWorks() + { + /* Arrange + * Test schema's copy constructor for deep-cloning type array + */ + var schemaWithTypeArray = new OpenApiSchema() + { + Type = JsonSchemaType.Array | JsonSchemaType.Null, + Items = new OpenApiSchema + { + Type = JsonSchemaType.String + } + }; + + var simpleSchema = new OpenApiSchema() + { + Type = JsonSchemaType.String + }; + + // Act + var schemaWithArrayCopy = new OpenApiSchema(schemaWithTypeArray); + schemaWithArrayCopy.Type = JsonSchemaType.String; + + var simpleSchemaCopy = new OpenApiSchema(simpleSchema) + { + Type = JsonSchemaType.String | JsonSchemaType.Null + }; + + // Assert + schemaWithArrayCopy.Type.Should().NotBe(schemaWithTypeArray.Type); + schemaWithTypeArray.Type = JsonSchemaType.String | JsonSchemaType.Null; + + simpleSchemaCopy.Type.Should().NotBe(simpleSchema.Type); + simpleSchema.Type = JsonSchemaType.String; + } + + [Fact] + public void ParseV31SchemaShouldSucceed() + { + var path = Path.Combine(SampleFolderPath, "schema.yaml"); + + // Act + var schema = OpenApiModelFactory.Load(path, OpenApiSpecVersion.OpenApi3_1, out _); + var expectedSchema = new OpenApiSchema + { + Type = JsonSchemaType.Object, + Properties = new Dictionary + { + ["one"] = new() + { + Description = "type array", + Type = JsonSchemaType.Integer | JsonSchemaType.String + } + } + }; + + // Assert + schema.Should().BeEquivalentTo(expectedSchema); + } + + [Fact] + public void ParseAdvancedV31SchemaShouldSucceed() + { + // Arrange and Act + var path = Path.Combine(SampleFolderPath, "advancedSchema.yaml"); + var schema = OpenApiModelFactory.Load(path, OpenApiSpecVersion.OpenApi3_1, out _); + + var expectedSchema = new OpenApiSchema + { + Type = JsonSchemaType.Object, + Properties = new Dictionary + { + ["one"] = new() + { + Description = "type array", + Type = JsonSchemaType.Integer | JsonSchemaType.String + }, + ["two"] = new() + { + Description = "type 'null'", + Type = JsonSchemaType.Null + }, + ["three"] = new() + { + Description = "type array including 'null'", + Type = JsonSchemaType.String | JsonSchemaType.Null + }, + ["four"] = new() + { + Description = "array with no items", + Type = JsonSchemaType.Array + }, + ["five"] = new() + { + Description = "singular example", + Type = JsonSchemaType.String, + Examples = new List + { + "exampleValue" + } + }, + ["six"] = new() + { + Description = "exclusiveMinimum true", + V31ExclusiveMinimum = 10 + }, + ["seven"] = new() + { + Description = "exclusiveMinimum false", + Minimum = 10 + }, + ["eight"] = new() + { + Description = "exclusiveMaximum true", + V31ExclusiveMaximum = 20 + }, + ["nine"] = new() + { + Description = "exclusiveMaximum false", + Maximum = 20 + }, + ["ten"] = new() + { + Description = "nullable string", + Type = JsonSchemaType.String | JsonSchemaType.Null + }, + ["eleven"] = new() + { + Description = "x-nullable string", + Type = JsonSchemaType.String | JsonSchemaType.Null + }, + ["twelve"] = new() + { + Description = "file/binary" + } + } + }; + + // Assert + schema.Should().BeEquivalentTo(expectedSchema, options => options + .IgnoringCyclicReferences() + .Excluding((IMemberInfo memberInfo) => + memberInfo.Path.EndsWith("Parent"))); + } + + [Fact] + public void ParseSchemaWithExamplesShouldSucceed() + { + // Arrange + var input = @" +type: string +examples: + - fedora + - ubuntu +"; + // Act + var schema = OpenApiModelFactory.Parse(input, OpenApiSpecVersion.OpenApi3_1, out _, "yaml"); + + // Assert + schema.Examples.Should().HaveCount(2); + } + + [Fact] + public void CloningSchemaWithExamplesAndEnumsShouldSucceed() + { + // Arrange + var schema = new OpenApiSchema + { + Type = JsonSchemaType.Integer, + Default = 5, + Examples = [2, 3], + Enum = [1, 2, 3] + }; + + var clone = new OpenApiSchema(schema); + clone.Examples.Add(4); + clone.Enum.Add(4); + clone.Default = 6; + + // Assert + clone.Enum.Should().NotBeEquivalentTo(schema.Enum); + clone.Examples.Should().NotBeEquivalentTo(schema.Examples); + clone.Default.Should().NotBeEquivalentTo(schema.Default); + } + + [Fact] + public void SerializeV31SchemaWithMultipleTypesAsV3Works() + { + // Arrange + var expected = @"type: string +nullable: true"; + + var path = Path.Combine(SampleFolderPath, "schemaWithTypeArray.yaml"); + + // Act + var schema = OpenApiModelFactory.Load(path, OpenApiSpecVersion.OpenApi3_1, out _); + + var writer = new StringWriter(); + schema.SerializeAsV3(new OpenApiYamlWriter(writer)); + var schema1String = writer.ToString(); + + schema1String.MakeLineBreaksEnvironmentNeutral().Should().Be(expected.MakeLineBreaksEnvironmentNeutral()); + } + + [Fact] + public void SerializeV31SchemaWithMultipleTypesAsV2Works() + { + // Arrange + var expected = @"type: string +x-nullable: true"; + + var path = Path.Combine(SampleFolderPath, "schemaWithTypeArray.yaml"); + + // Act + var schema = OpenApiModelFactory.Load(path, OpenApiSpecVersion.OpenApi3_1, out _); + + var writer = new StringWriter(); + schema.SerializeAsV2(new OpenApiYamlWriter(writer)); + var schema1String = writer.ToString(); + + schema1String.MakeLineBreaksEnvironmentNeutral().Should().Be(expected.MakeLineBreaksEnvironmentNeutral()); + } + + [Fact] + public void SerializeV3SchemaWithNullableAsV31Works() + { + // Arrange + var expected = @"type: + - 'null' + - string"; + + var path = Path.Combine(SampleFolderPath, "schemaWithNullable.yaml"); + + // Act + var schema = OpenApiModelFactory.Load(path, OpenApiSpecVersion.OpenApi3_0, out _); + + var writer = new StringWriter(); + schema.SerializeAsV31(new OpenApiYamlWriter(writer)); + var schemaString = writer.ToString(); + + schemaString.MakeLineBreaksEnvironmentNeutral().Should().Be(expected.MakeLineBreaksEnvironmentNeutral()); + } + + [Fact] + public void SerializeV2SchemaWithNullableExtensionAsV31Works() + { + // Arrange + var expected = @"type: + - 'null' + - string +x-nullable: true"; + + var path = Path.Combine(SampleFolderPath, "schemaWithNullableExtension.yaml"); + + // Act + var schema = OpenApiModelFactory.Load(path, OpenApiSpecVersion.OpenApi2_0, out _); + + var writer = new StringWriter(); + schema.SerializeAsV31(new OpenApiYamlWriter(writer)); + var schemaString = writer.ToString(); + + schemaString.MakeLineBreaksEnvironmentNeutral().Should().Be(expected.MakeLineBreaksEnvironmentNeutral()); + } + + [Fact] + public void SerializeSchemaWithTypeArrayAndNullableDoesntEmitType() + { + var input = @"type: +- ""string"" +- ""int"" +nullable: true"; + + var expected = @"{ }"; + + var schema = OpenApiModelFactory.Parse(input, OpenApiSpecVersion.OpenApi3_1, out _, "yaml"); + + var writer = new StringWriter(); + schema.SerializeAsV2(new OpenApiYamlWriter(writer)); + var schemaString = writer.ToString(); + + schemaString.MakeLineBreaksEnvironmentNeutral().Should().Be(expected.MakeLineBreaksEnvironmentNeutral()); + } + + [Theory] + [InlineData("schemaWithNullable.yaml")] + [InlineData("schemaWithNullableExtension.yaml")] + public void LoadSchemaWithNullableExtensionAsV31Works(string filePath) + { + // Arrange + var path = Path.Combine(SampleFolderPath, filePath); + + // Act + var schema = OpenApiModelFactory.Load(path, OpenApiSpecVersion.OpenApi3_1, out _); + + // Assert + schema.Type.Should().Be(JsonSchemaType.String | JsonSchemaType.Null); + } + + [Fact] + public void SerializeSchemaWithJsonSchemaKeywordsWorks() + { + // Arrange + var expected = @"$id: https://example.com/schemas/person.schema.yaml +$schema: https://json-schema.org/draft/2020-12/schema +$comment: A schema defining a person object with optional references to dynamic components. +$vocabulary: + https://json-schema.org/draft/2020-12/vocab/core: true + https://json-schema.org/draft/2020-12/vocab/applicator: true + https://json-schema.org/draft/2020-12/vocab/validation: true + https://json-schema.org/draft/2020-12/vocab/meta-data: false + https://json-schema.org/draft/2020-12/vocab/format-annotation: false +$dynamicAnchor: addressDef +title: Person +required: + - name +type: object +properties: + name: + $comment: The person's full name + type: string + age: + $comment: Age must be a non-negative integer + minimum: 0 + type: integer + address: + $comment: Reference to an address definition which can change dynamically + $dynamicRef: '#addressDef' +description: Schema for a person object +"; + var path = Path.Combine(SampleFolderPath, "schemaWithJsonSchemaKeywords.yaml"); + + // Act + var schema = OpenApiModelFactory.Load(path, OpenApiSpecVersion.OpenApi3_1, out _); + + // serialization + var writer = new StringWriter(); + schema.SerializeAsV31(new OpenApiYamlWriter(writer)); + var schemaString = writer.ToString(); + + // Assert + schema.Vocabulary.Keys.Count.Should().Be(5); + schemaString.MakeLineBreaksEnvironmentNeutral().Should().Be(expected.MakeLineBreaksEnvironmentNeutral()); + } + } +} diff --git a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiDocument/docWithExample.yaml b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiDocument/docWithExample.yaml new file mode 100644 index 000000000..44ede6301 --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiDocument/docWithExample.yaml @@ -0,0 +1,95 @@ +openapi: 3.1.0 # The version of the OpenAPI Specification +info: # Metadata about the API + title: A simple OpenAPI 3.1 example + version: 1.0.0 + license: + name: Apache 2.0 + identifier: Apache-2.0 # The SPDX license identifier +paths: # The available paths and operations for the API + /echo: # A path for echoing messages using WebSockets + get: # An operation using the GET method + summary: Echo a message + description: Send a message to the server and receive the same message back + responses: + '101': + description: Switching Protocols + headers: + Upgrade: + schema: + type: string + enum: + - websocket + Connection: + schema: + type: string + enum: + - Upgrade + Sec-WebSocket-Accept: + schema: + type: string + content: {} # No content is returned for this response + servers: + - url: ws://example.com # The WebSocket server URL + /upload: # A path for uploading files using multipart/form-data + post: # An operation using the POST method + summary: Upload a file + description: Upload a file to the server and receive a confirmation message + requestBody: + required: true + content: + multipart/form-data: # The media type for sending multiple parts of data + schema: + type: object + properties: + file: # A property for the file data + type: string + format: binary + comment: # A property for the file comment + type: string + encoding: # The encoding for each part of data + file: + contentType: application/octet-stream # The media type for the file data + comment: + contentType: text/plain # The media type for the file comment + responses: + '200': + description: File uploaded successfully + content: + application/json: # The media type for the response body + schema: + type: object + properties: + message: # A property for the confirmation message + type: string + examples: + - The file was uploaded successfully +components: # Reusable components for the API + schemas: # JSON Schema definitions for the API + Pet: # A schema for a pet object + type: object + required: + - petType + properties: + petType: # A property for the pet type + type: string + discriminator: # The discriminator for resolving the concrete schema type + propertyName: petType + mapping: + cat: '#/components/schemas/Cat' + dog: '#/components/schemas/Dog' + Cat: # A schema for a cat object + allOf: + - $ref: '#/components/schemas/Pet' + - type: object + properties: + name: # A property for the cat name + type: string + default: "Fluffy" # The default value for the cat name + Dog: # A schema for a dog object + allOf: + - $ref: '#/components/schemas/Pet' + - type: object + properties: + bark: # A property for the dog bark + type: string + default: "Woof" # The default value for the dog bark diff --git a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiDocument/docWithPatternPropertiesInSchema.yaml b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiDocument/docWithPatternPropertiesInSchema.yaml new file mode 100644 index 000000000..4ea2407d7 --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiDocument/docWithPatternPropertiesInSchema.yaml @@ -0,0 +1,25 @@ +openapi: 3.1.0 +info: + title: Example API + version: 1.0.0 +paths: + /example: + get: + summary: Get example object + responses: + '200': + description: Successful operation + content: + application/json: + schema: + type: object + properties: + prop1: + type: string + prop2: + type: string + prop3: + type: string + patternProperties: + "^x-.*$": + type: string diff --git a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiDocument/docWithReferenceById.yaml b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiDocument/docWithReferenceById.yaml new file mode 100644 index 000000000..d6c0121e4 --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiDocument/docWithReferenceById.yaml @@ -0,0 +1,45 @@ +openapi: 3.1.0 +info: + title: ReferenceById + version: 1.0.0 +paths: + /resource: + get: + parameters: + - name: id + in: query + required: true + schema: + $ref: 'https://example.com/schemas/id.json' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: 'https://example.com/schemas/resource.json' + post: + requestBody: + required: true + content: + application/json: + schema: + $ref: 'https://example.com/schemas/resource.json' + responses: + '200': + description: OK +components: + schemas: + Resource: + $id: 'https://example.com/schemas/resource.json' + type: object + properties: + id: + type: string + name: + type: string + reference: + $ref: '#/components/schemas/Resource' + Id: + $id: 'https://example.com/schemas/id.json' + type: string \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiDocument/documentWith31Properties.yaml b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiDocument/documentWith31Properties.yaml new file mode 100644 index 000000000..41817174e --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiDocument/documentWith31Properties.yaml @@ -0,0 +1,122 @@ +openapi: 3.1.0 +info: + title: Sample OpenAPI 3.1 API + description: A sample API demonstrating OpenAPI 3.1 features + version: "2.0.0" + summary: Sample OpenAPI 3.1 API with the latest features # OpenAPI 3.1 feature + license: + name: Apache 2.0 + identifier: Apache-2.0 # SPDX license identifier, a new 3.1 feature to define an API's SPDX license expression + url: https://www.apache.org/licenses/LICENSE-2.0.html + +# JSON Schema 2020-12 feature +jsonSchemaDialect: "https://json-schema.org/draft/2020-12/schema" + +servers: + - url: https://api.example.com/v2 + description: Main production server + +# Example Webhooks (OpenAPI 3.1 feature) +webhooks: + newPetAlert: + post: + summary: Notify about a new pet being added + requestBody: + required: true + content: + application/json: + schema: + type: string + responses: + '200': + description: Webhook processed successfully +paths: + /pets: + get: + summary: List all pets + operationId: listPets + tags: + - pets + parameters: + - name: limit + in: query + description: How many items to return at one time (max 100) + required: false + schema: + type: integer + #exclusiveMinimum and exclusiveMaximum now represent distinct values + exclusiveMinimum: 1 + exclusiveMaximum: 100 + responses: + '200': + description: A paged array of pets + content: + application/json: + schema: + # 3.1 feature where we can reference schemas using their identifier + $ref: 'https://example.com/schemas/pet.json' + /sample: + get: + summary: Sample endpoint + responses: + '200': + description: Sample response + content: + application/json: + schema: + #JSON schema keywords + $schema: "https://json-schema.org/draft/2020-12/schema" + $id: "https://example.com/schemas/person.schema.yaml" + $comment: "A schema defining a pet object with optional references to dynamic components." + $vocabulary: + "https://json-schema.org/draft/2020-12/vocab/core": true + "https://json-schema.org/draft/2020-12/vocab/applicator": true + "https://json-schema.org/draft/2020-12/vocab/validation": true + "https://json-schema.org/draft/2020-12/vocab/meta-data": false + "https://json-schema.org/draft/2020-12/vocab/format-annotation": false + + title: "Pet" + description: "Schema for a pet object" + type: "object" + properties: + name: + type: "string" + $comment: "The pet's full name" + address: + $dynamicRef: "#addressDef" + $comment: "Reference to an address definition which can change dynamically" + required: + - name + $dynamicAnchor: "addressDef" +components: + schemas: + Pet: + $id: 'https://example.com/schemas/pet.json' + type: object + required: + - id + - weight + properties: + id: + type: string + format: uuid + weight: + type: number + exclusiveMinimum: 0 + description: Weight of the pet in kilograms + # Pattern properties and Type array feature from JSON Schema + attributes: + type: + - "object" + - "null" + description: Dynamic attributes for the pet + patternProperties: + "^attr_[A-Za-z]+$": + type: string + $comment: "This schema represents a pet in the system." # JSON Schema 2020-12 feature + $defs: # JSON Schema 2020-12 feature + ExtraInfo: + type: string + +security: + - api_key: [] \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiDocument/documentWithReusablePaths.yaml b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiDocument/documentWithReusablePaths.yaml new file mode 100644 index 000000000..28fa04b19 --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiDocument/documentWithReusablePaths.yaml @@ -0,0 +1,85 @@ +openapi : 3.1.0 +info: + title: Webhook Example + version: 1.0.0 +jsonSchemaDialect: "http://json-schema.org/draft-07/schema#" +webhooks: + pets: + "$ref": '#/components/pathItems/pets' +components: + schemas: + petSchema: + type: object + required: + - id + - name + properties: + id: + type: integer + format: int64 + name: + type: string + tag: + type: string + newPetSchema: + type: object + required: + - name + properties: + id: + type: integer + format: int64 + name: + type: string + tag: + type: string + pathItems: + pets: + get: + description: Returns all pets from the system that the user has access to + operationId: findPets + parameters: + - name: tags + in: query + description: tags to filter by + required: false + schema: + type: array + items: + type: string + - name: limit + in: query + description: maximum number of results to return + required: false + schema: + type: integer + format: int32 + responses: + '200': + description: pet response + content: + application/json: + schema: + type: array + items: + "$ref": '#/components/schemas/petSchema' + application/xml: + schema: + type: array + items: + "$ref": '#/components/schemas/petSchema' + post: + requestBody: + description: Information about a new pet in the system + required: true + content: + 'application/json': + schema: + "$ref": '#/components/schemas/newPetSchema' + responses: + "200": + description: Return a 200 status to indicate that the data was received successfully + content: + application/json: + schema: + $ref: '#/components/schemas/petSchema' \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiDocument/documentWithSummaryAndDescriptionInReference.yaml b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiDocument/documentWithSummaryAndDescriptionInReference.yaml new file mode 100644 index 000000000..bfa7ab627 --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiDocument/documentWithSummaryAndDescriptionInReference.yaml @@ -0,0 +1,43 @@ +openapi: '3.1.0' +info: + version: '1.0.0' + title: Swagger Petstore (Simple) +paths: + /pets: + get: + description: Returns all pets from the system that the user has access to + responses: + '200': + description: pet response + content: + application/json: + schema: + "$ref": '#/components/schemas/pet' +components: + headers: + X-Test: + description: Test + summary: An X-Test header + schema: + type: string + responses: + Test: + description: Test Response + headers: + X-Test: + $ref: '#/components/headers/X-Test' + schemas: + pet: + description: A referenced pet in a petstore + type: object + required: + - id + - name + properties: + id: + type: integer + format: int64 + name: + type: string + tag: + type: string \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiDocument/documentWithWebhooks.yaml b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiDocument/documentWithWebhooks.yaml new file mode 100644 index 000000000..aeadc3d69 --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiDocument/documentWithWebhooks.yaml @@ -0,0 +1,81 @@ +openapi: 3.1.0 +info: + title: Webhook Example + version: 1.0.0 +webhooks: + pets: + get: + description: Returns all pets from the system that the user has access to + operationId: findPets + parameters: + - name: tags + in: query + description: tags to filter by + required: false + schema: + type: array + items: + type: string + - name: limit + in: query + description: maximum number of results to return + required: false + schema: + type: integer + format: int32 + responses: + '200': + description: pet response + content: + application/json: + schema: + type: array + items: + "$ref": '#/components/schemas/petSchema' + application/xml: + schema: + type: array + items: + "$ref": '#/components/schemas/petSchema' + post: + requestBody: + description: Information about a new pet in the system + required: true + content: + 'application/json': + schema: + "$ref": '#/components/schemas/newPetSchema' + responses: + "200": + description: Return a 200 status to indicate that the data was received successfully + content: + application/json: + schema: + $ref: '#/components/schemas/petSchema' +components: + schemas: + petSchema: + type: object + required: + - id + - name + properties: + id: + type: integer + format: int64 + name: + type: string + tag: + type: string + newPetSchema: + type: object + required: + - name + properties: + id: + type: integer + format: int64 + name: + type: string + tag: + type: string \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiDocument/externalRefById.yaml b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiDocument/externalRefById.yaml new file mode 100644 index 000000000..bb3755180 --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiDocument/externalRefById.yaml @@ -0,0 +1,14 @@ +openapi: 3.1.0 +info: + title: ReferenceById + version: 1.0.0 +paths: + /resource: + get: + parameters: + - name: id + in: query + required: true + schema: + $ref: 'https://example.com/schemas/user.json' +components: {} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiDocument/externalRefByJsonPointer.yaml b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiDocument/externalRefByJsonPointer.yaml new file mode 100644 index 000000000..913b20e7c --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiDocument/externalRefByJsonPointer.yaml @@ -0,0 +1,15 @@ +openapi: 3.1.0 +info: + title: ReferenceById + version: 1.0.0 +paths: + /resource: + get: + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: './externalResource.yaml#/components/schemas/todo' +components: {} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiDocument/externalResource.yaml b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiDocument/externalResource.yaml new file mode 100644 index 000000000..78d6c0851 --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiDocument/externalResource.yaml @@ -0,0 +1,22 @@ +openapi: 3.1.0 +info: + title: ReferencedById + version: 1.0.0 +paths: {} +components: + schemas: + todo: + type: object + properties: + id: + type: string + name: + type: string + user: + $id: 'https://example.com/schemas/user.json' + type: object + properties: + id: + type: string + name: + type: string \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiInfo/basicInfo.yaml b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiInfo/basicInfo.yaml new file mode 100644 index 000000000..12eabe650 --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiInfo/basicInfo.yaml @@ -0,0 +1,16 @@ +{ + "title": "Basic Info", + "summary": "Sample Summary", + "description": "Sample Description", + "termsOfService": "http://swagger.io/terms/", + "contact": { + "name": "API Support", + "url": "http://www.swagger.io/support", + "email": "support@swagger.io" + }, + "license": { + "name": "Apache 2.0", + "url": "http://www.apache.org/licenses/LICENSE-2.0.html" + }, + "version": "1.0.1" +} diff --git a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiLicense/licenseWithSpdxIdentifier.yaml b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiLicense/licenseWithSpdxIdentifier.yaml new file mode 100644 index 000000000..623529f4d --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiLicense/licenseWithSpdxIdentifier.yaml @@ -0,0 +1,2 @@ +name: Apache 2.0 +identifier: Apache-2.0 diff --git a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiSchema/advancedSchema.yaml b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiSchema/advancedSchema.yaml new file mode 100644 index 000000000..16cd59816 --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiSchema/advancedSchema.yaml @@ -0,0 +1,47 @@ +type: object +properties: + one: + description: type array + type: + - integer + - string + two: + description: type 'null' + type: "null" + three: + description: type array including 'null' + type: + - string + - "null" + four: + description: array with no items + type: array + five: + description: singular example + type: string + examples: + - exampleValue + six: + description: exclusiveMinimum true + exclusiveMinimum: 10 + seven: + description: exclusiveMinimum false + minimum: 10 + eight: + description: exclusiveMaximum true + exclusiveMaximum: 20 + nine: + description: exclusiveMaximum false + maximum: 20 + ten: + description: nullable string + type: + - string + - "null" + eleven: + description: x-nullable string + type: + - string + - "null" + twelve: + description: file/binary diff --git a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiSchema/jsonSchema.json b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiSchema/jsonSchema.json new file mode 100644 index 000000000..84b1ea211 --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiSchema/jsonSchema.json @@ -0,0 +1,33 @@ +{ + "$id": "https://example.com/arrays.schema.json", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "description": "A representation of a person, company, organization, or place", + "type": "object", + "properties": { + "fruits": { + "type": "array", + "items": { + "type": "string" + } + }, + "vegetables": { + "type": "array" + } + }, + "$defs": { + "veggie": { + "type": "object", + "required": [ "veggieName", "veggieLike" ], + "properties": { + "veggieName": { + "type": "string", + "description": "The name of the vegetable." + }, + "veggieLike": { + "type": "boolean", + "description": "Do I like this vegetable?" + } + } + } + } +} diff --git a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiSchema/schema.yaml b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiSchema/schema.yaml new file mode 100644 index 000000000..0ac2b2473 --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiSchema/schema.yaml @@ -0,0 +1,7 @@ +type: object +properties: + one: + description: type array + type: + - integer + - string diff --git a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiSchema/schemaWithExamples.yaml b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiSchema/schemaWithExamples.yaml new file mode 100644 index 000000000..56bcb1e4c --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiSchema/schemaWithExamples.yaml @@ -0,0 +1,4 @@ +type: string +examples: + - fedora + - ubuntu \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiSchema/schemaWithJsonSchemaKeywords.yaml b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiSchema/schemaWithJsonSchemaKeywords.yaml new file mode 100644 index 000000000..3d88cffcd --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiSchema/schemaWithJsonSchemaKeywords.yaml @@ -0,0 +1,41 @@ +$schema: "https://json-schema.org/draft/2020-12/schema" +$id: "https://example.com/schemas/person.schema.yaml" +$comment: "A schema defining a person object with optional references to dynamic components." +$vocabulary: + "https://json-schema.org/draft/2020-12/vocab/core": true + "https://json-schema.org/draft/2020-12/vocab/applicator": true + "https://json-schema.org/draft/2020-12/vocab/validation": true + "https://json-schema.org/draft/2020-12/vocab/meta-data": false + "https://json-schema.org/draft/2020-12/vocab/format-annotation": false + +title: "Person" +description: "Schema for a person object" +type: "object" + +properties: + name: + type: "string" + $comment: "The person's full name" + age: + type: "integer" + minimum: 0 + $comment: "Age must be a non-negative integer" + address: + $dynamicRef: "#addressDef" + $comment: "Reference to an address definition which can change dynamically" + +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/V31Tests/Samples/OpenApiSchema/schemaWithNullable.yaml b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiSchema/schemaWithNullable.yaml new file mode 100644 index 000000000..913c768d3 --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiSchema/schemaWithNullable.yaml @@ -0,0 +1,2 @@ +type: string +nullable: true \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiSchema/schemaWithNullableExtension.yaml b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiSchema/schemaWithNullableExtension.yaml new file mode 100644 index 000000000..e9bfbd513 --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiSchema/schemaWithNullableExtension.yaml @@ -0,0 +1,2 @@ +type: string +x-nullable: true \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiSchema/schemaWithTypeArray.yaml b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiSchema/schemaWithTypeArray.yaml new file mode 100644 index 000000000..38ac212be --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiSchema/schemaWithTypeArray.yaml @@ -0,0 +1,3 @@ +type: +- "string" +- "null" \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiCallbackTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiCallbackTests.cs index 3bddf695c..cab621c14 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiCallbackTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiCallbackTests.cs @@ -1,14 +1,12 @@ // Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. +// Licensed under the MIT license. using System.IO; using System.Linq; using FluentAssertions; using Microsoft.OpenApi.Expressions; using Microsoft.OpenApi.Models; -using Microsoft.OpenApi.Readers.ParseNodes; -using Microsoft.OpenApi.Readers.V3; -using SharpYaml.Serialization; +using Microsoft.OpenApi.Reader; using Xunit; namespace Microsoft.OpenApi.Readers.Tests.V3Tests @@ -17,23 +15,16 @@ namespace Microsoft.OpenApi.Readers.Tests.V3Tests public class OpenApiCallbackTests { private const string SampleFolderPath = "V3Tests/Samples/OpenApiCallback/"; + public OpenApiCallbackTests() + { + OpenApiReaderRegistry.RegisterReader(OpenApiConstants.Yaml, new OpenApiYamlReader()); + } [Fact] public void ParseBasicCallbackShouldSucceed() { - using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "basicCallback.yaml")); - // Arrange - var yamlStream = new YamlStream(); - yamlStream.Load(new StreamReader(stream)); - var yamlNode = yamlStream.Documents.First().RootNode; - - var diagnostic = new OpenApiDiagnostic(); - var context = new ParsingContext(diagnostic); - - var node = new MapNode(context, (YamlMappingNode)yamlNode); - // Act - var callback = OpenApiV3Deserializer.LoadCallback(node); + var callback = OpenApiModelFactory.Load(Path.Combine(SampleFolderPath, "basicCallback.yaml"), OpenApiSpecVersion.OpenApi3_0, out var diagnostic); // Assert diagnostic.Should().BeEquivalentTo(new OpenApiDiagnostic()); @@ -43,29 +34,29 @@ public void ParseBasicCallbackShouldSucceed() { PathItems = { - [RuntimeExpression.Build("$request.body#/url")] - = new() + [RuntimeExpression.Build("$request.body#/url")] + = new OpenApiPathItem { Operations = { [OperationType.Post] = - new() + new OpenApiOperation + { + RequestBody = new OpenApiRequestBody { - RequestBody = new() + Content = { - Content = - { - ["application/json"] = null - } - }, - Responses = new() + ["application/json"] = null + } + }, + Responses = new OpenApiResponses + { + ["200"] = new OpenApiResponse { - ["200"] = new() - { - Description = "Success" - } + Description = "Success" } } + } } } } @@ -76,43 +67,43 @@ public void ParseBasicCallbackShouldSucceed() public void ParseCallbackWithReferenceShouldSucceed() { using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "callbackWithReference.yaml")); + // Act - var openApiDoc = new OpenApiStreamReader().Read(stream, out var diagnostic); + var result = OpenApiModelFactory.Load(stream, OpenApiConstants.Yaml); // Assert - var path = openApiDoc.Paths.First().Value; + var path = result.OpenApiDocument.Paths.First().Value; var subscribeOperation = path.Operations[OperationType.Post]; var callback = subscribeOperation.Callbacks["simpleHook"]; - diagnostic.Should().BeEquivalentTo( - new OpenApiDiagnostic { SpecificationVersion = OpenApiSpecVersion.OpenApi3_0 }); + result.OpenApiDiagnostic.Should().BeEquivalentTo( + new OpenApiDiagnostic() { SpecificationVersion = OpenApiSpecVersion.OpenApi3_0 }); callback.Should().BeEquivalentTo( new OpenApiCallback { PathItems = { - [RuntimeExpression.Build("$request.body#/url")]= new() - { + [RuntimeExpression.Build("$request.body#/url")]= new OpenApiPathItem { Operations = { - [OperationType.Post] = new() + [OperationType.Post] = new OpenApiOperation() { - RequestBody = new() + RequestBody = new OpenApiRequestBody { Content = { - ["application/json"] = new() + ["application/json"] = new OpenApiMediaType { Schema = new() { - Type = "object" + Type = JsonSchemaType.Object } } } }, Responses = { - ["200"]= new() + ["200"]= new OpenApiResponse { Description = "Success" } @@ -121,11 +112,11 @@ public void ParseCallbackWithReferenceShouldSucceed() } } }, - Reference = new() + Reference = new OpenApiReference { Type = ReferenceType.Callback, Id = "simpleHook", - HostDocument = openApiDoc + HostDocument = result.OpenApiDocument } }); } @@ -133,16 +124,15 @@ public void ParseCallbackWithReferenceShouldSucceed() [Fact] public void ParseMultipleCallbacksWithReferenceShouldSucceed() { - using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "multipleCallbacksWithReference.yaml")); // Act - var openApiDoc = new OpenApiStreamReader().Read(stream, out var diagnostic); + var result = OpenApiModelFactory.Load(Path.Combine(SampleFolderPath, "multipleCallbacksWithReference.yaml")); // Assert - var path = openApiDoc.Paths.First().Value; + var path = result.OpenApiDocument.Paths.First().Value; var subscribeOperation = path.Operations[OperationType.Post]; - diagnostic.Should().BeEquivalentTo( - new OpenApiDiagnostic { SpecificationVersion = OpenApiSpecVersion.OpenApi3_0 }); + result.OpenApiDiagnostic.Should().BeEquivalentTo( + new OpenApiDiagnostic() { SpecificationVersion = OpenApiSpecVersion.OpenApi3_0 }); var callback1 = subscribeOperation.Callbacks["simpleHook"]; @@ -151,26 +141,25 @@ public void ParseMultipleCallbacksWithReferenceShouldSucceed() { PathItems = { - [RuntimeExpression.Build("$request.body#/url")]= new() - { + [RuntimeExpression.Build("$request.body#/url")]= new OpenApiPathItem { Operations = { - [OperationType.Post] = new() + [OperationType.Post] = new OpenApiOperation() { - RequestBody = new() + RequestBody = new OpenApiRequestBody { Content = { - ["application/json"] = new() + ["application/json"] = new OpenApiMediaType { Schema = new() { - Type = "object" + Type = JsonSchemaType.Object } } } }, Responses = { - ["200"]= new() + ["200"]= new OpenApiResponse { Description = "Success" } @@ -179,11 +168,11 @@ public void ParseMultipleCallbacksWithReferenceShouldSucceed() } } }, - Reference = new() + Reference = new OpenApiReference { Type = ReferenceType.Callback, Id = "simpleHook", - HostDocument = openApiDoc + HostDocument = result.OpenApiDocument } }); @@ -193,27 +182,26 @@ public void ParseMultipleCallbacksWithReferenceShouldSucceed() { PathItems = { - [RuntimeExpression.Build("/simplePath")]= new() - { + [RuntimeExpression.Build("/simplePath")]= new OpenApiPathItem { Operations = { - [OperationType.Post] = new() + [OperationType.Post] = new OpenApiOperation() { - RequestBody = new() + RequestBody = new OpenApiRequestBody { Description = "Callback 2", Content = { - ["application/json"] = new() + ["application/json"] = new OpenApiMediaType { Schema = new() { - Type = "string" + Type = JsonSchemaType.String } } } }, Responses = { - ["400"]= new() + ["400"]= new OpenApiResponse { Description = "Callback Response" } @@ -230,34 +218,33 @@ public void ParseMultipleCallbacksWithReferenceShouldSucceed() { PathItems = { - [RuntimeExpression.Build(@"http://example.com?transactionId={$request.body#/id}&email={$request.body#/email}")] = new() - { + [RuntimeExpression.Build(@"http://example.com?transactionId={$request.body#/id}&email={$request.body#/email}")] = new OpenApiPathItem { Operations = { - [OperationType.Post] = new() + [OperationType.Post] = new OpenApiOperation() { - RequestBody = new() + RequestBody = new OpenApiRequestBody { Content = { - ["application/xml"] = new() + ["application/xml"] = new OpenApiMediaType { Schema = new() { - Type = "object" + Type = JsonSchemaType.Object } } } }, Responses = { - ["200"]= new() + ["200"]= new OpenApiResponse { Description = "Success" }, - ["401"]= new() + ["401"]= new OpenApiResponse { Description = "Unauthorized" }, - ["404"]= new() + ["404"]= new OpenApiResponse { Description = "Not Found" } diff --git a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiContactTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiContactTests.cs index 3f6b4a320..d6d0422c4 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiContactTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiContactTests.cs @@ -3,6 +3,7 @@ using FluentAssertions; using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Reader; using Xunit; namespace Microsoft.OpenApi.Readers.Tests.V3Tests @@ -20,10 +21,9 @@ public void ParseStringContactFragmentShouldSucceed() "email": "support@swagger.io" } """; - var reader = new OpenApiStringReader(); // Act - var contact = reader.ReadFragment(input, OpenApiSpecVersion.OpenApi3_0, out var diagnostic); + var contact = OpenApiModelFactory.Parse(input, OpenApiSpecVersion.OpenApi3_0, out var diagnostic, OpenApiConstants.Json); // Assert diagnostic.Should().BeEquivalentTo(new OpenApiDiagnostic()); diff --git a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiDiscriminatorTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiDiscriminatorTests.cs index 76129c67a..6556ade48 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiDiscriminatorTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiDiscriminatorTests.cs @@ -2,12 +2,9 @@ // Licensed under the MIT license. using System.IO; -using System.Linq; using FluentAssertions; using Microsoft.OpenApi.Models; -using Microsoft.OpenApi.Readers.ParseNodes; -using Microsoft.OpenApi.Readers.V3; -using SharpYaml.Serialization; +using Microsoft.OpenApi.Reader; using Xunit; namespace Microsoft.OpenApi.Readers.Tests.V3Tests @@ -17,21 +14,19 @@ public class OpenApiDiscriminatorTests { private const string SampleFolderPath = "V3Tests/Samples/OpenApiDiscriminator/"; + public OpenApiDiscriminatorTests() + { + OpenApiReaderRegistry.RegisterReader(OpenApiConstants.Yaml, new OpenApiYamlReader()); + } + [Fact] public void ParseBasicDiscriminatorShouldSucceed() { + // Arrange using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "basicDiscriminator.yaml")); - var yamlStream = new YamlStream(); - yamlStream.Load(new StreamReader(stream)); - var yamlNode = yamlStream.Documents.First().RootNode; - - var diagnostic = new OpenApiDiagnostic(); - var context = new ParsingContext(diagnostic); - - var node = new MapNode(context, (YamlMappingNode)yamlNode); // Act - var discriminator = OpenApiV3Deserializer.LoadDiscriminator(node); + var discriminator = OpenApiModelFactory.Load(stream, OpenApiSpecVersion.OpenApi3_0, OpenApiConstants.Yaml, out var diagnostic); // Assert discriminator.Should().BeEquivalentTo( @@ -40,8 +35,8 @@ public void ParseBasicDiscriminatorShouldSucceed() PropertyName = "pet_type", Mapping = { - ["puppy"] = "#/components/schemas/Dog", - ["kitten"] = "Cat" + ["puppy"] = "#/components/schemas/Dog", + ["kitten"] = "Cat" } }); } diff --git a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiDocumentTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiDocumentTests.cs index 7ce9c0964..712fea099 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiDocumentTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiDocumentTests.cs @@ -1,22 +1,22 @@ // Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. +// Licensed under the MIT license. using System; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; -using System.Threading; using FluentAssertions; -using Microsoft.OpenApi.Any; -using Microsoft.OpenApi.Exceptions; using Microsoft.OpenApi.Extensions; using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Models; -using Microsoft.OpenApi.Readers.Interface; +using Microsoft.OpenApi.Models.References; +using Microsoft.OpenApi.Reader; +using Microsoft.OpenApi.Tests; using Microsoft.OpenApi.Validations; using Microsoft.OpenApi.Validations.Rules; using Microsoft.OpenApi.Writers; +using SharpYaml.Model; using Xunit; namespace Microsoft.OpenApi.Readers.Tests.V3Tests @@ -26,157 +26,109 @@ public class OpenApiDocumentTests { private const string SampleFolderPath = "V3Tests/Samples/OpenApiDocument/"; + public OpenApiDocumentTests() + { + OpenApiReaderRegistry.RegisterReader(OpenApiConstants.Yaml, new OpenApiYamlReader()); + } + public T Clone(T element) where T : IOpenApiSerializable { - using var stream = new MemoryStream(); - IOpenApiWriter writer; - var streamWriter = new FormattingStreamWriter(stream, CultureInfo.InvariantCulture); - writer = new OpenApiJsonWriter(streamWriter, new() + using (var stream = new MemoryStream()) { - InlineLocalReferences = true - }); - element.SerializeAsV3(writer); - writer.Flush(); - stream.Position = 0; + IOpenApiWriter writer; + var streamWriter = new FormattingStreamWriter(stream, CultureInfo.InvariantCulture); + writer = new OpenApiJsonWriter(streamWriter, new OpenApiJsonWriterSettings() + { + InlineLocalReferences = true + }); + element.SerializeAsV3(writer); + writer.Flush(); + stream.Position = 0; - using var streamReader = new StreamReader(stream); - var result = streamReader.ReadToEnd(); - return new OpenApiStringReader().ReadFragment(result, OpenApiSpecVersion.OpenApi3_0, out var diagnostic4); + using (var streamReader = new StreamReader(stream)) + { + var result = streamReader.ReadToEnd(); + return OpenApiModelFactory.Parse(result, OpenApiSpecVersion.OpenApi3_0, out OpenApiDiagnostic diagnostic4); + } + } } public OpenApiSecurityScheme CloneSecurityScheme(OpenApiSecurityScheme element) { - using var stream = new MemoryStream(); - IOpenApiWriter writer; - var streamWriter = new FormattingStreamWriter(stream, CultureInfo.InvariantCulture); - writer = new OpenApiJsonWriter(streamWriter, new() + using (var stream = new MemoryStream()) { - InlineLocalReferences = true - }); - element.SerializeAsV3WithoutReference(writer); - writer.Flush(); - stream.Position = 0; + IOpenApiWriter writer; + var streamWriter = new FormattingStreamWriter(stream, CultureInfo.InvariantCulture); + writer = new OpenApiJsonWriter(streamWriter, new OpenApiJsonWriterSettings() + { + InlineLocalReferences = true + }); + element.SerializeAsV3(writer); + writer.Flush(); + stream.Position = 0; - using var streamReader = new StreamReader(stream); - var result = streamReader.ReadToEnd(); - return new OpenApiStringReader().ReadFragment(result, OpenApiSpecVersion.OpenApi3_0, out var diagnostic4); + using (var streamReader = new StreamReader(stream)) + { + var result = streamReader.ReadToEnd(); + return OpenApiModelFactory.Parse(result, OpenApiSpecVersion.OpenApi3_0, out OpenApiDiagnostic diagnostic4); + } + } } [Fact] public void ParseDocumentFromInlineStringShouldSucceed() { - var openApiDoc = new OpenApiStringReader().Read( - """ - - openapi : 3.0.0 - info: - title: Simple Document - version: 0.9.1 - paths: {} - """, - out var context); - - openApiDoc.Should().BeEquivalentTo( + var result = OpenApiDocument.Parse( + @" +openapi : 3.0.0 +info: + title: Simple Document + version: 0.9.1 +paths: {}", + OpenApiConstants.Yaml); + + result.OpenApiDocument.Should().BeEquivalentTo( new OpenApiDocument { - Info = new() + Info = new OpenApiInfo { Title = "Simple Document", Version = "0.9.1" }, - Paths = new() - }); - - context.Should().BeEquivalentTo( - new OpenApiDiagnostic { SpecificationVersion = OpenApiSpecVersion.OpenApi3_0 }); - } + Paths = new OpenApiPaths() + }, options => options.Excluding(x => x.Workspace).Excluding(y => y.BaseUri)); - [Theory] - [InlineData("en-US")] - [InlineData("hi-IN")] - // The equivalent of English 1,000.36 in French and Danish is 1.000,36 - [InlineData("fr-FR")] - [InlineData("da-DK")] - public void ParseDocumentWithDifferentCultureShouldSucceed(string culture) - { - Thread.CurrentThread.CurrentCulture = new(culture); - Thread.CurrentThread.CurrentUICulture = new(culture); - - var openApiDoc = new OpenApiStringReader().Read( - """ - openapi : 3.0.0 - info: - title: Simple Document - version: 0.9.1 - components: - schemas: - sampleSchema: - type: object - properties: - sampleProperty: - type: double - minimum: 100.54 - maximum: 60000000.35 - exclusiveMaximum: true - exclusiveMinimum: false - paths: {} - """, - out var context); - - openApiDoc.Should().BeEquivalentTo( - new OpenApiDocument + result.OpenApiDiagnostic.Should().BeEquivalentTo( + new OpenApiDiagnostic() { - Info = new() - { - Title = "Simple Document", - Version = "0.9.1" - }, - Components = new() - { - Schemas = + SpecificationVersion = OpenApiSpecVersion.OpenApi3_0, + Errors = new List() { - ["sampleSchema"] = new() - { - Type = "object", - Properties = - { - ["sampleProperty"] = new() - { - Type = "double", - Minimum = (decimal)100.54, - Maximum = (decimal)60000000.35, - ExclusiveMaximum = true, - ExclusiveMinimum = false - } - }, - Reference = new() - { - Id = "sampleSchema", - Type = ReferenceType.Schema - } - } + new OpenApiError("", "Paths is a REQUIRED field at #/") } - }, - Paths = new() }); - - context.Should().BeEquivalentTo( - new OpenApiDiagnostic { SpecificationVersion = OpenApiSpecVersion.OpenApi3_0 }); } [Fact] public void ParseBasicDocumentWithMultipleServersShouldSucceed() { - using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "basicDocumentWithMultipleServers.yaml")); - var openApiDoc = new OpenApiStreamReader().Read(stream, out var diagnostic); + var path = System.IO.Path.Combine(SampleFolderPath, "basicDocumentWithMultipleServers.yaml"); + var result = OpenApiDocument.Load(path); - diagnostic.Should().BeEquivalentTo( - new OpenApiDiagnostic { SpecificationVersion = OpenApiSpecVersion.OpenApi3_0 }); + result.OpenApiDiagnostic.Should().BeEquivalentTo( + new OpenApiDiagnostic() + { + SpecificationVersion = OpenApiSpecVersion.OpenApi3_0, + Errors = new List() + { + new OpenApiError("", "Paths is a REQUIRED field at #/") + } + }); - openApiDoc.Should().BeEquivalentTo( + result.OpenApiDocument.Should().BeEquivalentTo( new OpenApiDocument { - Info = new() + Info = new OpenApiInfo { Title = "The API", Version = "0.9.1", @@ -194,32 +146,32 @@ public void ParseBasicDocumentWithMultipleServersShouldSucceed() Description = "The https endpoint" } }, - Paths = new() - }); + Paths = new OpenApiPaths() + }, options => options.Excluding(x => x.Workspace).Excluding(y => y.BaseUri)); } - [Fact] public void ParseBrokenMinimalDocumentShouldYieldExpectedDiagnostic() { - using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "brokenMinimalDocument.yaml")); - var openApiDoc = new OpenApiStreamReader().Read(stream, out var diagnostic); + using var stream = Resources.GetStream(System.IO.Path.Combine(SampleFolderPath, "brokenMinimalDocument.yaml")); + var result = OpenApiDocument.Load(stream, OpenApiConstants.Yaml); - openApiDoc.Should().BeEquivalentTo( + result.OpenApiDocument.Should().BeEquivalentTo( new OpenApiDocument { - Info = new() + Info = new OpenApiInfo { Version = "0.9" }, - Paths = new() - }); + Paths = new OpenApiPaths() + }, options => options.Excluding(x => x.Workspace).Excluding(y => y.BaseUri)); - diagnostic.Should().BeEquivalentTo( + result.OpenApiDiagnostic.Should().BeEquivalentTo( new OpenApiDiagnostic { Errors = { - new OpenApiValidatorError(nameof(OpenApiInfoRules.InfoRequiredFields),"#/info/title", "The field 'title' in 'info' object is REQUIRED.") + 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 }); @@ -228,685 +180,624 @@ public void ParseBrokenMinimalDocumentShouldYieldExpectedDiagnostic() [Fact] public void ParseMinimalDocumentShouldSucceed() { - using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "minimalDocument.yaml")); - var openApiDoc = new OpenApiStreamReader().Read(stream, out var diagnostic); + var result = OpenApiDocument.Load(System.IO.Path.Combine(SampleFolderPath, "minimalDocument.yaml")); - openApiDoc.Should().BeEquivalentTo( + result.OpenApiDocument.Should().BeEquivalentTo( new OpenApiDocument { - Info = new() + Info = new OpenApiInfo { Title = "Simple Document", Version = "0.9.1" }, - Paths = new() - }); + Paths = new OpenApiPaths() + }, options => options.Excluding(x => x.Workspace).Excluding(y => y.BaseUri)); - diagnostic.Should().BeEquivalentTo( - new OpenApiDiagnostic { SpecificationVersion = OpenApiSpecVersion.OpenApi3_0 }); + result.OpenApiDiagnostic.Should().BeEquivalentTo( + new OpenApiDiagnostic() + { + SpecificationVersion = OpenApiSpecVersion.OpenApi3_0, + Errors = new List() + { + new OpenApiError("", "Paths is a REQUIRED field at #/") + } + }); } [Fact] public void ParseStandardPetStoreDocumentShouldSucceed() { - OpenApiDiagnostic context; - using (var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "petStore.yaml"))) - { - var actual = new OpenApiStreamReader().Read(stream, out context); + using var stream = Resources.GetStream(System.IO.Path.Combine(SampleFolderPath, "petStore.yaml")); + var actual = OpenApiDocument.Load(stream, OpenApiConstants.Yaml); - var components = new OpenApiComponents + var components = new OpenApiComponents + { + Schemas = new Dictionary { - Schemas = new Dictionary + ["pet1"] = new() { - ["pet"] = new() - { - Type = "object", - Required = new HashSet + Type = JsonSchemaType.Object, + Required = new HashSet { "id", "name" }, - Properties = new Dictionary + Properties = new Dictionary + { + ["id"] = new() { - ["id"] = new() - { - Type = "integer", - Format = "int64" - }, - ["name"] = new() - { - Type = "string" - }, - ["tag"] = new() - { - Type = "string" - }, + Type = JsonSchemaType.Integer, + Format = "int64" }, - Reference = new() + ["name"] = new() { - Type = ReferenceType.Schema, - Id = "pet", - HostDocument = actual - } - }, - ["newPet"] = new() - { - Type = "object", - Required = new HashSet + Type = JsonSchemaType.String + }, + ["tag"] = new() + { + Type = JsonSchemaType.String + }, + } + }, + ["newPet"] = new() + { + Type = JsonSchemaType.Object, + Required = new HashSet { "name" }, - Properties = new Dictionary + Properties = new Dictionary + { + ["id"] = new() { - ["id"] = new() - { - Type = "integer", - Format = "int64" - }, - ["name"] = new() - { - Type = "string" - }, - ["tag"] = new() - { - Type = "string" - }, + Type = JsonSchemaType.Integer, + Format = "int64" }, - Reference = new() + ["name"] = new() { - Type = ReferenceType.Schema, - Id = "newPet", - HostDocument = actual - } - }, - ["errorModel"] = new() - { - Type = "object", - Required = new HashSet + Type = JsonSchemaType.String + }, + ["tag"] = new() + { + Type = JsonSchemaType.String + }, + } + }, + ["errorModel"] = new() + { + Type = JsonSchemaType.Object, + Required = new HashSet { "code", "message" }, - Properties = new Dictionary + Properties = new Dictionary + { + ["code"] = new() { - ["code"] = new() - { - Type = "integer", - Format = "int32" - }, - ["message"] = new() - { - Type = "string" - } + Type = JsonSchemaType.Integer, + Format = "int32" }, - Reference = new() + ["message"] = new() { - Type = ReferenceType.Schema, - Id = "errorModel", - HostDocument = actual + Type = JsonSchemaType.String } - }, - } - }; - - // Create a clone of the schema to avoid modifying things in components. - var petSchema = Clone(components.Schemas["pet"]); - - petSchema.Reference = new() - { - Id = "pet", - Type = ReferenceType.Schema, - HostDocument = actual - }; - - var newPetSchema = Clone(components.Schemas["newPet"]); - - newPetSchema.Reference = new() - { - Id = "newPet", - Type = ReferenceType.Schema, - HostDocument = actual - }; + } + }, + } + }; - var errorModelSchema = Clone(components.Schemas["errorModel"]); + var petSchema = new OpenApiSchemaReference("pet1", actual.OpenApiDocument); + var newPetSchema = new OpenApiSchemaReference("newPet", actual.OpenApiDocument); - errorModelSchema.Reference = new() - { - Id = "errorModel", - Type = ReferenceType.Schema, - HostDocument = actual - }; + var errorModelSchema = new OpenApiSchemaReference("errorModel", actual.OpenApiDocument); - var expected = new OpenApiDocument + var expectedDoc = new OpenApiDocument + { + Info = new OpenApiInfo { - Info = new() + Version = "1.0.0", + Title = "Swagger Petstore (Simple)", + Description = + "A sample API that uses a petstore as an example to demonstrate features in the swagger-2.0 specification", + TermsOfService = new Uri("http://helloreverb.com/terms/"), + Contact = new OpenApiContact { - Version = "1.0.0", - Title = "Swagger Petstore (Simple)", - Description = - "A sample API that uses a petstore as an example to demonstrate features in the swagger-2.0 specification", - TermsOfService = new("http://helloreverb.com/terms/"), - Contact = new() - { - Name = "Swagger API team", - Email = "foo@example.com", - Url = new("http://swagger.io") - }, - License = new() - { - Name = "MIT", - Url = new("http://opensource.org/licenses/MIT") - } + Name = "Swagger API team", + Email = "foo@example.com", + Url = new Uri("http://swagger.io") }, - Servers = new List + License = new OpenApiLicense { - new() - { - Url = "http://petstore.swagger.io/api" - } - }, - Paths = new() + Name = "MIT", + Url = new Uri("http://opensource.org/licenses/MIT") + } + }, + Servers = new List + { + new OpenApiServer + { + Url = "http://petstore.swagger.io/api" + } + }, + Paths = new OpenApiPaths + { + ["/pets"] = new OpenApiPathItem { - ["/pets"] = new() + Operations = new Dictionary { - Operations = new Dictionary + [OperationType.Get] = new OpenApiOperation { - [OperationType.Get] = new() + Description = "Returns all pets from the system that the user has access to", + OperationId = "findPets", + Parameters = new List { - Description = "Returns all pets from the system that the user has access to", - OperationId = "findPets", - Parameters = new List + new OpenApiParameter { - new() + Name = "tags", + In = ParameterLocation.Query, + Description = "tags to filter by", + Required = false, + Schema = new() { - Name = "tags", - In = ParameterLocation.Query, - Description = "tags to filter by", - Required = false, - Schema = new() - { - Type = "array", - Items = new() - { - Type = "string" - } - } - }, - new() - { - Name = "limit", - In = ParameterLocation.Query, - Description = "maximum number of results to return", - Required = false, - Schema = new() + Type = JsonSchemaType.Array, + Items = new() { - Type = "integer", - Format = "int32" + Type = JsonSchemaType.String } } }, - Responses = new() + new OpenApiParameter + { + Name = "limit", + In = ParameterLocation.Query, + Description = "maximum number of results to return", + Required = false, + Schema = new() + { + Type = JsonSchemaType.Integer, + Format = "int32" + } + } + }, + Responses = new OpenApiResponses + { + ["200"] = new OpenApiResponse { - ["200"] = new() + Description = "pet response", + Content = new Dictionary { - Description = "pet response", - Content = new Dictionary + ["application/json"] = new OpenApiMediaType { - ["application/json"] = new() + Schema = new() { - Schema = new() - { - Type = "array", - Items = petSchema - } - }, - ["application/xml"] = new() + Type = JsonSchemaType.Array, + Items = petSchema + } + }, + ["application/xml"] = new OpenApiMediaType + { + Schema = new() { - Schema = new() - { - Type = "array", - Items = petSchema - } + Type = JsonSchemaType.Array, + Items = petSchema } } - }, - ["4XX"] = new() + } + }, + ["4XX"] = new OpenApiResponse + { + Description = "unexpected client error", + Content = new Dictionary { - Description = "unexpected client error", - Content = new Dictionary + ["text/html"] = new OpenApiMediaType { - ["text/html"] = new() - { - Schema = errorModelSchema - } + Schema = errorModelSchema } - }, - ["5XX"] = new() + } + }, + ["5XX"] = new OpenApiResponse + { + Description = "unexpected server error", + Content = new Dictionary { - Description = "unexpected server error", - Content = new Dictionary + ["text/html"] = new OpenApiMediaType { - ["text/html"] = new() - { - Schema = errorModelSchema - } + Schema = errorModelSchema } } } + } + }, + [OperationType.Post] = new OpenApiOperation + { + Description = "Creates a new pet in the store. Duplicates are allowed", + OperationId = "addPet", + RequestBody = new OpenApiRequestBody + { + Description = "Pet to add to the store", + Required = true, + Content = new Dictionary + { + ["application/json"] = new OpenApiMediaType + { + Schema = newPetSchema + } + } }, - [OperationType.Post] = new() + Responses = new OpenApiResponses { - Description = "Creates a new pet in the store. Duplicates are allowed", - OperationId = "addPet", - RequestBody = new() + ["200"] = new OpenApiResponse { - Description = "Pet to add to the store", - Required = true, + Description = "pet response", Content = new Dictionary { - ["application/json"] = new() + ["application/json"] = new OpenApiMediaType { - Schema = newPetSchema - } + Schema = petSchema + }, } }, - Responses = new() + ["4XX"] = new OpenApiResponse { - ["200"] = new() - { - Description = "pet response", - Content = new Dictionary - { - ["application/json"] = new() - { - Schema = petSchema - }, - } - }, - ["4XX"] = new() + Description = "unexpected client error", + Content = new Dictionary { - Description = "unexpected client error", - Content = new Dictionary + ["text/html"] = new OpenApiMediaType { - ["text/html"] = new() - { - Schema = errorModelSchema - } + Schema = errorModelSchema } - }, - ["5XX"] = new() + } + }, + ["5XX"] = new OpenApiResponse + { + Description = "unexpected server error", + Content = new Dictionary { - Description = "unexpected server error", - Content = new Dictionary + ["text/html"] = new OpenApiMediaType { - ["text/html"] = new() - { - Schema = errorModelSchema - } + Schema = errorModelSchema } } } } } - }, - ["/pets/{id}"] = new() + } + }, + ["/pets/{id}"] = new OpenApiPathItem + { + Operations = new Dictionary { - Operations = new Dictionary + [OperationType.Get] = new OpenApiOperation { - [OperationType.Get] = new() + Description = + "Returns a user based on a single ID, if the user does not have access to the pet", + OperationId = "findPetById", + Parameters = new List { - Description = - "Returns a user based on a single ID, if the user does not have access to the pet", - OperationId = "findPetById", - Parameters = new List + new OpenApiParameter { - new() + Name = "id", + In = ParameterLocation.Path, + Description = "ID of pet to fetch", + Required = true, + Schema = new() { - Name = "id", - In = ParameterLocation.Path, - Description = "ID of pet to fetch", - Required = true, - Schema = new() + Type = JsonSchemaType.Integer, + Format = "int64" + } + } + }, + Responses = new OpenApiResponses + { + ["200"] = new OpenApiResponse + { + Description = "pet response", + Content = new Dictionary + { + ["application/json"] = new OpenApiMediaType { - Type = "integer", - Format = "int64" + Schema = petSchema + }, + ["application/xml"] = new OpenApiMediaType + { + Schema = petSchema } } }, - Responses = new() + ["4XX"] = new OpenApiResponse { - ["200"] = new() + Description = "unexpected client error", + Content = new Dictionary { - Description = "pet response", - Content = new Dictionary + ["text/html"] = new OpenApiMediaType { - ["application/json"] = new() - { - Schema = petSchema - }, - ["application/xml"] = new() - { - Schema = petSchema - } + Schema = errorModelSchema } - }, - ["4XX"] = new() + } + }, + ["5XX"] = new OpenApiResponse + { + Description = "unexpected server error", + Content = new Dictionary { - Description = "unexpected client error", - Content = new Dictionary + ["text/html"] = new OpenApiMediaType { - ["text/html"] = new() - { - Schema = errorModelSchema - } + Schema = errorModelSchema } - }, - ["5XX"] = new() + } + } + } + }, + [OperationType.Delete] = new OpenApiOperation + { + Description = "deletes a single pet based on the ID supplied", + OperationId = "deletePet", + Parameters = new List + { + new OpenApiParameter + { + Name = "id", + In = ParameterLocation.Path, + Description = "ID of pet to delete", + Required = true, + Schema = new() { - Description = "unexpected server error", - Content = new Dictionary - { - ["text/html"] = new() - { - Schema = errorModelSchema - } - } + Type = JsonSchemaType.Integer, + Format = "int64" } } }, - [OperationType.Delete] = new() + Responses = new OpenApiResponses { - Description = "deletes a single pet based on the ID supplied", - OperationId = "deletePet", - Parameters = new List + ["204"] = new OpenApiResponse + { + Description = "pet deleted" + }, + ["4XX"] = new OpenApiResponse { - new() + Description = "unexpected client error", + Content = new Dictionary { - Name = "id", - In = ParameterLocation.Path, - Description = "ID of pet to delete", - Required = true, - Schema = new() + ["text/html"] = new OpenApiMediaType { - Type = "integer", - Format = "int64" + Schema = errorModelSchema } } }, - Responses = new() + ["5XX"] = new OpenApiResponse { - ["204"] = new() - { - Description = "pet deleted" - }, - ["4XX"] = new() - { - Description = "unexpected client error", - Content = new Dictionary - { - ["text/html"] = new() - { - Schema = errorModelSchema - } - } - }, - ["5XX"] = new() + Description = "unexpected server error", + Content = new Dictionary { - Description = "unexpected server error", - Content = new Dictionary + ["text/html"] = new OpenApiMediaType { - ["text/html"] = new() - { - Schema = errorModelSchema - } + Schema = errorModelSchema } } } } } } - }, - Components = components - }; + } + }, + Components = components + }; - actual.Should().BeEquivalentTo(expected); - } + actual.OpenApiDocument.Should().BeEquivalentTo(expectedDoc, options => options.Excluding(x => x.Workspace).Excluding(y => y.BaseUri)); - context.Should().BeEquivalentTo( - new OpenApiDiagnostic { SpecificationVersion = OpenApiSpecVersion.OpenApi3_0 }); + actual.OpenApiDiagnostic.Should().BeEquivalentTo( + new OpenApiDiagnostic() { SpecificationVersion = OpenApiSpecVersion.OpenApi3_0 }); } [Fact] public void ParseModifiedPetStoreDocumentWithTagAndSecurityShouldSucceed() { - OpenApiDiagnostic context; - using (var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "petStoreWithTagAndSecurity.yaml"))) - { - var actual = new OpenApiStreamReader().Read(stream, out context); + using var stream = Resources.GetStream(System.IO.Path.Combine(SampleFolderPath, "petStoreWithTagAndSecurity.yaml")); + var actual = OpenApiDocument.Load(stream, OpenApiConstants.Yaml); - var components = new OpenApiComponents + var components = new OpenApiComponents + { + Schemas = new Dictionary { - Schemas = new Dictionary + ["pet1"] = new() { - ["pet"] = new() - { - Type = "object", - Required = new HashSet + Type = JsonSchemaType.Object, + Required = new HashSet { "id", "name" }, - Properties = new Dictionary + Properties = new Dictionary + { + ["id"] = new() { - ["id"] = new() - { - Type = "integer", - Format = "int64" - }, - ["name"] = new() - { - Type = "string" - }, - ["tag"] = new() - { - Type = "string" - }, + Type = JsonSchemaType.Integer, + Format = "int64" }, - Reference = new() - { - Type = ReferenceType.Schema, - Id = "pet", - HostDocument = actual - } - }, - ["newPet"] = new() - { - Type = "object", - Required = new HashSet + ["name"] = new() { - "name" + Type = JsonSchemaType.String }, - Properties = new Dictionary + ["tag"] = new() { - ["id"] = new() - { - Type = "integer", - Format = "int64" - }, - ["name"] = new() - { - Type = "string" - }, - ["tag"] = new() - { - Type = "string" - }, + Type = JsonSchemaType.String }, - Reference = new() + } + }, + ["newPet"] = new() + { + Type = JsonSchemaType.Object, + Required = new HashSet { - Type = ReferenceType.Schema, - Id = "newPet", - HostDocument = actual - } - }, - ["errorModel"] = new() + "name" + }, + Properties = new Dictionary { - Type = "object", - Required = new HashSet + ["id"] = new() { - "code", - "message" + Type = JsonSchemaType.Integer, + Format = "int64" }, - Properties = new Dictionary + ["name"] = new() { - ["code"] = new() - { - Type = "integer", - Format = "int32" - }, - ["message"] = new() - { - Type = "string" - } + Type = JsonSchemaType.String }, - Reference = new() + ["tag"] = new() { - Type = ReferenceType.Schema, - Id = "errorModel" - } - }, + Type = JsonSchemaType.String + }, + } }, - SecuritySchemes = new Dictionary + ["errorModel"] = new() { - ["securitySchemeName1"] = new() - { - Type = SecuritySchemeType.ApiKey, - Name = "apiKeyName1", - In = ParameterLocation.Header, - Reference = new() + Type = JsonSchemaType.Object, + Required = new HashSet { - Id = "securitySchemeName1", - Type = ReferenceType.SecurityScheme, - HostDocument = actual - } - - }, - ["securitySchemeName2"] = new() + "code", + "message" + }, + Properties = new Dictionary { - Type = SecuritySchemeType.OpenIdConnect, - OpenIdConnectUrl = new("http://example.com"), - Reference = new() + ["code"] = new() + { + Type = JsonSchemaType.Integer, + Format = "int32" + }, + ["message"] = new() { - Id = "securitySchemeName2", - Type = ReferenceType.SecurityScheme, - HostDocument = actual + Type = JsonSchemaType.String } } + }, + }, + SecuritySchemes = new Dictionary + { + ["securitySchemeName1"] = new OpenApiSecurityScheme + { + Type = SecuritySchemeType.ApiKey, + Name = "apiKeyName1", + In = ParameterLocation.Header + }, + ["securitySchemeName2"] = new OpenApiSecurityScheme + { + Type = SecuritySchemeType.OpenIdConnect, + OpenIdConnectUrl = new Uri("http://example.com") } - }; + } + }; - // Create a clone of the schema to avoid modifying things in components. - var petSchema = Clone(components.Schemas["pet"]); - petSchema.Reference = new() - { - Id = "pet", - Type = ReferenceType.Schema - }; + // Create a clone of the schema to avoid modifying things in components. + var petSchema = Clone(components.Schemas["pet1"]); + petSchema.Reference = new() + { + Id = "pet1", + Type = ReferenceType.Schema, + HostDocument = actual.OpenApiDocument + }; - var newPetSchema = Clone(components.Schemas["newPet"]); + var newPetSchema = Clone(components.Schemas["newPet"]); - newPetSchema.Reference = new() - { - Id = "newPet", - Type = ReferenceType.Schema - }; + newPetSchema.Reference = new() + { + Id = "newPet", + Type = ReferenceType.Schema, + HostDocument = actual.OpenApiDocument + }; - var errorModelSchema = Clone(components.Schemas["errorModel"]); + var errorModelSchema = Clone(components.Schemas["errorModel"]); - errorModelSchema.Reference = new() - { - Id = "errorModel", - Type = ReferenceType.Schema - }; + errorModelSchema.Reference = new() + { + Id = "errorModel", + Type = ReferenceType.Schema, + HostDocument = actual.OpenApiDocument + }; - var tag1 = new OpenApiTag + var tag1 = new OpenApiTag + { + Name = "tagName1", + Description = "tagDescription1", + Reference = new OpenApiReference { - Name = "tagName1", - Description = "tagDescription1", - Reference = new() - { - Id = "tagName1", - Type = ReferenceType.Tag - } - }; + Id = "tagName1", + Type = ReferenceType.Tag + } + }; - var tag2 = new OpenApiTag + + var tag2 = new OpenApiTag + { + Name = "tagName2", + Reference = new OpenApiReference { - Name = "tagName2" - }; + Id = "tagName2", + Type = ReferenceType.Tag + } + }; - var securityScheme1 = CloneSecurityScheme(components.SecuritySchemes["securitySchemeName1"]); + var securityScheme1 = CloneSecurityScheme(components.SecuritySchemes["securitySchemeName1"]); - securityScheme1.Reference = new() - { - Id = "securitySchemeName1", - Type = ReferenceType.SecurityScheme - }; + securityScheme1.Reference = new OpenApiReference + { + Id = "securitySchemeName1", + Type = ReferenceType.SecurityScheme + }; - var securityScheme2 = CloneSecurityScheme(components.SecuritySchemes["securitySchemeName2"]); + var securityScheme2 = CloneSecurityScheme(components.SecuritySchemes["securitySchemeName2"]); - securityScheme2.Reference = new() - { - Id = "securitySchemeName2", - Type = ReferenceType.SecurityScheme - }; + securityScheme2.Reference = new OpenApiReference + { + Id = "securitySchemeName2", + Type = ReferenceType.SecurityScheme + }; - var expected = new OpenApiDocument + var expected = new OpenApiDocument + { + Info = new OpenApiInfo { - Info = new() + Version = "1.0.0", + Title = "Swagger Petstore (Simple)", + Description = + "A sample API that uses a petstore as an example to demonstrate features in the swagger-2.0 specification", + TermsOfService = new Uri("http://helloreverb.com/terms/"), + Contact = new OpenApiContact { - Version = "1.0.0", - Title = "Swagger Petstore (Simple)", - Description = - "A sample API that uses a petstore as an example to demonstrate features in the swagger-2.0 specification", - TermsOfService = new("http://helloreverb.com/terms/"), - Contact = new() - { - Name = "Swagger API team", - Email = "foo@example.com", - Url = new("http://swagger.io") - }, - License = new() - { - Name = "MIT", - Url = new("http://opensource.org/licenses/MIT") - } + Name = "Swagger API team", + Email = "foo@example.com", + Url = new Uri("http://swagger.io") }, - Servers = new List + License = new OpenApiLicense + { + Name = "MIT", + Url = new Uri("http://opensource.org/licenses/MIT") + } + }, + Servers = new List { - new() + new OpenApiServer { Url = "http://petstore.swagger.io/api" } }, - Paths = new() + Paths = new OpenApiPaths + { + ["/pets"] = new OpenApiPathItem { - ["/pets"] = new() + Operations = new Dictionary { - Operations = new Dictionary + [OperationType.Get] = new OpenApiOperation { - [OperationType.Get] = new() - { - Tags = new List + Tags = new List { tag1, tag2 }, - Description = "Returns all pets from the system that the user has access to", - OperationId = "findPets", - Parameters = new List + Description = "Returns all pets from the system that the user has access to", + OperationId = "findPets", + Parameters = new List { - new() + new OpenApiParameter { Name = "tags", In = ParameterLocation.Query, @@ -914,14 +805,14 @@ public void ParseModifiedPetStoreDocumentWithTagAndSecurityShouldSucceed() Required = false, Schema = new() { - Type = "array", + Type = JsonSchemaType.Array, Items = new() { - Type = "string" + Type = JsonSchemaType.String } } }, - new() + new OpenApiParameter { Name = "limit", In = ParameterLocation.Query, @@ -929,120 +820,120 @@ public void ParseModifiedPetStoreDocumentWithTagAndSecurityShouldSucceed() Required = false, Schema = new() { - Type = "integer", + Type = JsonSchemaType.Integer, Format = "int32" } } }, - Responses = new() + Responses = new OpenApiResponses + { + ["200"] = new OpenApiResponse { - ["200"] = new() + Description = "pet response", + Content = new Dictionary { - Description = "pet response", - Content = new Dictionary + ["application/json"] = new OpenApiMediaType { - ["application/json"] = new() + Schema = new() { - Schema = new() - { - Type = "array", - Items = petSchema - } - }, - ["application/xml"] = new() + Type = JsonSchemaType.Array, + Items = petSchema + } + }, + ["application/xml"] = new OpenApiMediaType + { + Schema = new() { - Schema = new() - { - Type = "array", - Items = petSchema - } + Type = JsonSchemaType.Array, + Items = petSchema } } - }, - ["4XX"] = new() + } + }, + ["4XX"] = new OpenApiResponse + { + Description = "unexpected client error", + Content = new Dictionary { - Description = "unexpected client error", - Content = new Dictionary + ["text/html"] = new OpenApiMediaType { - ["text/html"] = new() - { - Schema = errorModelSchema - } + Schema = errorModelSchema } - }, - ["5XX"] = new() + } + }, + ["5XX"] = new OpenApiResponse + { + Description = "unexpected server error", + Content = new Dictionary { - Description = "unexpected server error", - Content = new Dictionary + ["text/html"] = new OpenApiMediaType { - ["text/html"] = new() - { - Schema = errorModelSchema - } + Schema = errorModelSchema } } } - }, - [OperationType.Post] = new() - { - Tags = new List + } + }, + [OperationType.Post] = new OpenApiOperation + { + Tags = new List { tag1, tag2 }, - Description = "Creates a new pet in the store. Duplicates are allowed", - OperationId = "addPet", - RequestBody = new() + Description = "Creates a new pet in the store. Duplicates are allowed", + OperationId = "addPet", + RequestBody = new OpenApiRequestBody + { + Description = "Pet to add to the store", + Required = true, + Content = new Dictionary { - Description = "Pet to add to the store", - Required = true, + ["application/json"] = new OpenApiMediaType + { + Schema = newPetSchema + } + } + }, + Responses = new OpenApiResponses + { + ["200"] = new OpenApiResponse + { + Description = "pet response", Content = new Dictionary { - ["application/json"] = new() + ["application/json"] = new OpenApiMediaType { - Schema = newPetSchema - } + Schema = petSchema + }, } }, - Responses = new() + ["4XX"] = new OpenApiResponse { - ["200"] = new() - { - Description = "pet response", - Content = new Dictionary - { - ["application/json"] = new() - { - Schema = petSchema - }, - } - }, - ["4XX"] = new() + Description = "unexpected client error", + Content = new Dictionary { - Description = "unexpected client error", - Content = new Dictionary + ["text/html"] = new OpenApiMediaType { - ["text/html"] = new() - { - Schema = errorModelSchema - } + Schema = errorModelSchema } - }, - ["5XX"] = new() + } + }, + ["5XX"] = new OpenApiResponse + { + Description = "unexpected server error", + Content = new Dictionary { - Description = "unexpected server error", - Content = new Dictionary + ["text/html"] = new OpenApiMediaType { - ["text/html"] = new() - { - Schema = errorModelSchema - } + Schema = errorModelSchema } } - }, - Security = new List + } + }, + Security = new List { - new() + new OpenApiSecurityRequirement { [securityScheme1] = new List(), [securityScheme2] = new List @@ -1052,21 +943,21 @@ public void ParseModifiedPetStoreDocumentWithTagAndSecurityShouldSucceed() } } } - } } - }, - ["/pets/{id}"] = new() + } + }, + ["/pets/{id}"] = new OpenApiPathItem + { + Operations = new Dictionary { - Operations = new Dictionary + [OperationType.Get] = new OpenApiOperation { - [OperationType.Get] = new() - { - Description = - "Returns a user based on a single ID, if the user does not have access to the pet", - OperationId = "findPetById", - Parameters = new List + Description = + "Returns a user based on a single ID, if the user does not have access to the pet", + OperationId = "findPetById", + Parameters = new List { - new() + new OpenApiParameter { Name = "id", In = ParameterLocation.Path, @@ -1074,59 +965,59 @@ public void ParseModifiedPetStoreDocumentWithTagAndSecurityShouldSucceed() Required = true, Schema = new() { - Type = "integer", + Type = JsonSchemaType.Integer, Format = "int64" } } }, - Responses = new() + Responses = new OpenApiResponses + { + ["200"] = new OpenApiResponse { - ["200"] = new() + Description = "pet response", + Content = new Dictionary { - Description = "pet response", - Content = new Dictionary + ["application/json"] = new OpenApiMediaType { - ["application/json"] = new() - { - Schema = petSchema - }, - ["application/xml"] = new() - { - Schema = petSchema - } + Schema = petSchema + }, + ["application/xml"] = new OpenApiMediaType + { + Schema = petSchema } - }, - ["4XX"] = new() + } + }, + ["4XX"] = new OpenApiResponse + { + Description = "unexpected client error", + Content = new Dictionary { - Description = "unexpected client error", - Content = new Dictionary + ["text/html"] = new OpenApiMediaType { - ["text/html"] = new() - { - Schema = errorModelSchema - } + Schema = errorModelSchema } - }, - ["5XX"] = new() + } + }, + ["5XX"] = new OpenApiResponse + { + Description = "unexpected server error", + Content = new Dictionary { - Description = "unexpected server error", - Content = new Dictionary + ["text/html"] = new OpenApiMediaType { - ["text/html"] = new() - { - Schema = errorModelSchema - } + Schema = errorModelSchema } } } - }, - [OperationType.Delete] = new() - { - Description = "deletes a single pet based on the ID supplied", - OperationId = "deletePet", - Parameters = new List + } + }, + [OperationType.Delete] = new OpenApiOperation + { + Description = "deletes a single pet based on the ID supplied", + OperationId = "deletePet", + Parameters = new List { - new() + new OpenApiParameter { Name = "id", In = ParameterLocation.Path, @@ -1134,61 +1025,56 @@ public void ParseModifiedPetStoreDocumentWithTagAndSecurityShouldSucceed() Required = true, Schema = new() { - Type = "integer", + Type = JsonSchemaType.Integer, Format = "int64" } } }, - Responses = new() + Responses = new OpenApiResponses + { + ["204"] = new OpenApiResponse { - ["204"] = new() - { - Description = "pet deleted" - }, - ["4XX"] = new() + Description = "pet deleted" + }, + ["4XX"] = new OpenApiResponse + { + Description = "unexpected client error", + Content = new Dictionary { - Description = "unexpected client error", - Content = new Dictionary + ["text/html"] = new OpenApiMediaType { - ["text/html"] = new() - { - Schema = errorModelSchema - } + Schema = errorModelSchema } - }, - ["5XX"] = new() + } + }, + ["5XX"] = new OpenApiResponse + { + Description = "unexpected server error", + Content = new Dictionary { - Description = "unexpected server error", - Content = new Dictionary + ["text/html"] = new OpenApiMediaType { - ["text/html"] = new() - { - Schema = errorModelSchema - } + Schema = errorModelSchema } } } } } } - }, - Components = components, - Tags = new List + } + }, + Components = components, + Tags = new List { - new() + new OpenApiTag { Name = "tagName1", - Description = "tagDescription1", - Reference = new() - { - Id = "tagName1", - Type = ReferenceType.Tag - } + Description = "tagDescription1" } }, - SecurityRequirements = new List + SecurityRequirements = new List { - new() + new OpenApiSecurityRequirement { [securityScheme1] = new List(), [securityScheme2] = new List @@ -1199,52 +1085,54 @@ public void ParseModifiedPetStoreDocumentWithTagAndSecurityShouldSucceed() } } } - }; - - actual.Should().BeEquivalentTo(expected, options => options.Excluding(m => m.Name == "HostDocument")); - } - - context.Should().BeEquivalentTo( - new OpenApiDiagnostic { SpecificationVersion = OpenApiSpecVersion.OpenApi3_0 }); + }; + + actual.OpenApiDocument.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) + .Excluding(x => x.Paths["/pets"].Operations[OperationType.Get].Tags[0].Reference.HostDocument) + .Excluding(x => x.Paths["/pets"].Operations[OperationType.Post].Tags[0].Reference.HostDocument) + .Excluding(x => x.Paths["/pets"].Operations[OperationType.Get].Tags[1].Reference.HostDocument) + .Excluding(x => x.Paths["/pets"].Operations[OperationType.Post].Tags[1].Reference.HostDocument) + .Excluding(x => x.Workspace) + .Excluding(y => y.BaseUri)); + + actual.OpenApiDiagnostic.Should().BeEquivalentTo( + new OpenApiDiagnostic() { SpecificationVersion = OpenApiSpecVersion.OpenApi3_0 }); } [Fact] public void ParsePetStoreExpandedShouldSucceed() { - OpenApiDiagnostic context; + var actual = OpenApiDocument.Load(System.IO.Path.Combine(SampleFolderPath, "petStoreExpanded.yaml")); - using (var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "petStoreExpanded.yaml"))) - { - var actual = new OpenApiStreamReader().Read(stream, out context); + // TODO: Create the object in memory and compare with the one read from YAML file. - // TODO: Create the object in memory and compare with the one read from YAML file. - } - - context.Should().BeEquivalentTo( - new OpenApiDiagnostic { SpecificationVersion = OpenApiSpecVersion.OpenApi3_0 }); + actual.OpenApiDiagnostic.Should().BeEquivalentTo( + new OpenApiDiagnostic() { SpecificationVersion = OpenApiSpecVersion.OpenApi3_0 }); } [Fact] public void GlobalSecurityRequirementShouldReferenceSecurityScheme() { - using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "securedApi.yaml")); - var openApiDoc = new OpenApiStreamReader().Read(stream, out var diagnostic); + var result = OpenApiDocument.Load(System.IO.Path.Combine(SampleFolderPath, "securedApi.yaml")); - var securityRequirement = openApiDoc.SecurityRequirements.First(); + var securityRequirement = result.OpenApiDocument.SecurityRequirements.First(); - Assert.Same(securityRequirement.Keys.First(), openApiDoc.Components.SecuritySchemes.First().Value); + securityRequirement.Keys.First().Should().BeEquivalentTo(result.OpenApiDocument.Components.SecuritySchemes.First().Value, + options => options.Excluding(x => x.Reference)); } [Fact] public void HeaderParameterShouldAllowExample() { - using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "apiWithFullHeaderComponent.yaml")); - var openApiDoc = new OpenApiStreamReader().Read(stream, out var diagnostic); + var result = OpenApiDocument.Load(System.IO.Path.Combine(SampleFolderPath, "apiWithFullHeaderComponent.yaml")); - var exampleHeader = openApiDoc.Components?.Headers?["example-header"]; + var exampleHeader = result.OpenApiDocument.Components?.Headers?["example-header"]; Assert.NotNull(exampleHeader); exampleHeader.Should().BeEquivalentTo( - new OpenApiHeader + new OpenApiHeader() { Description = "Test header with example", Required = true, @@ -1253,23 +1141,20 @@ public void HeaderParameterShouldAllowExample() AllowReserved = true, Style = ParameterStyle.Simple, Explode = true, - Example = new OpenApiString("99391c7e-ad88-49ec-a2ad-99ddcb1f7721"), + Example = "99391c7e-ad88-49ec-a2ad-99ddcb1f7721", Schema = new() { - Type = "string", + Type = JsonSchemaType.String, Format = "uuid" }, - Reference = new() - { - Type = ReferenceType.Header, - Id = "example-header" - } - }); + }, options => options.IgnoringCyclicReferences() + .Excluding(e => e.Example.Parent) + .Excluding(x => x.Reference)); - var examplesHeader = openApiDoc.Components?.Headers?["examples-header"]; + var examplesHeader = result.OpenApiDocument.Components?.Headers?["examples-header"]; Assert.NotNull(examplesHeader); examplesHeader.Should().BeEquivalentTo( - new OpenApiHeader + new OpenApiHeader() { Description = "Test header with example", Required = true, @@ -1278,101 +1163,192 @@ public void HeaderParameterShouldAllowExample() AllowReserved = true, Style = ParameterStyle.Simple, Explode = true, - Examples = new Dictionary + Examples = new Dictionary() { - { "uuid1", new OpenApiExample - { - Value = new OpenApiString("99391c7e-ad88-49ec-a2ad-99ddcb1f7721") - } - }, - { "uuid2", new OpenApiExample - { - Value = new OpenApiString("99391c7e-ad88-49ec-a2ad-99ddcb1f7721") + { "uuid1", new OpenApiExample() + { + Value = "99391c7e-ad88-49ec-a2ad-99ddcb1f7721" + } + }, + { "uuid2", new OpenApiExample() + { + Value = "99391c7e-ad88-49ec-a2ad-99ddcb1f7721" + } } - } }, Schema = new() { - Type = "string", + Type = JsonSchemaType.String, Format = "uuid" }, - Reference = new() - { - Type = ReferenceType.Header, - Id = "examples-header" - } - }); + }, options => options.IgnoringCyclicReferences() + .Excluding(e => e.Examples["uuid1"].Value.Parent) + .Excluding(e => e.Examples["uuid2"].Value.Parent)); } [Fact] - public void DoesNotChangeExternalReferences() + public void ParseDocumentWithReferencedSecuritySchemeWorks() { - // Arrange - using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "documentWithExternalRefs.yaml")); - // Act - var doc = new OpenApiStreamReader( - new() { ReferenceResolution = ReferenceResolutionSetting.DoNotResolveReferences }) - .Read(stream, out var diagnostic); + var settings = new OpenApiReaderSettings + { + ReferenceResolution = ReferenceResolutionSetting.ResolveLocalReferences + }; - var externalRef = doc.Components.Schemas["Nested"].Properties["AnyOf"].AnyOf.First().Reference.ReferenceV3; - var externalRef2 = doc.Components.Schemas["Nested"].Properties["AnyOf"].AnyOf.Last().Reference.ReferenceV3; + var result = OpenApiDocument.Load(System.IO.Path.Combine(SampleFolderPath, "docWithSecuritySchemeReference.yaml"), settings); + var securityScheme = result.OpenApiDocument.Components.SecuritySchemes["OAuth2"]; // Assert - Assert.Equal("file:///C:/MySchemas.json#/definitions/ArrayObject", externalRef); - Assert.Equal("../foo/schemas.yaml#/components/schemas/Number", externalRef2); + Assert.False(securityScheme.UnresolvedReference); + Assert.NotNull(securityScheme.Flows); } [Fact] - public void ParseDocumentWithReferencedSecuritySchemeWorks() + public void ParseDocumentWithJsonSchemaReferencesWorks() { // Arrange - using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "docWithSecuritySchemeReference.yaml")); + using var stream = Resources.GetStream(System.IO.Path.Combine(SampleFolderPath, "docWithJsonSchema.yaml")); // Act - var doc = new OpenApiStreamReader(new() + var settings = new OpenApiReaderSettings { ReferenceResolution = ReferenceResolutionSetting.ResolveLocalReferences - }).Read(stream, out var diagnostic); + }; + var result = OpenApiDocument.Load(stream, OpenApiConstants.Yaml, settings); - var securityScheme = doc.Components.SecuritySchemes["OAuth2"]; + var actualSchema = result.OpenApiDocument.Paths["/users/{userId}"].Operations[OperationType.Get].Responses["200"].Content["application/json"].Schema; + var expectedSchema = new OpenApiSchemaReference("User", result.OpenApiDocument); // Assert - Assert.False(securityScheme.UnresolvedReference); - Assert.NotNull(securityScheme.Flows); + actualSchema.Should().BeEquivalentTo(expectedSchema); } [Fact] public void ValidateExampleShouldNotHaveDataTypeMismatch() { - // Arrange - using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "documentWithDateExampleInSchema.yaml")); - // Act - var doc = new OpenApiStreamReader(new() + var result = OpenApiDocument.Load(System.IO.Path.Combine(SampleFolderPath, "documentWithDateExampleInSchema.yaml"), new OpenApiReaderSettings { ReferenceResolution = ReferenceResolutionSetting.ResolveLocalReferences - }).Read(stream, out var diagnostic); + + }); // Assert - var warnings = diagnostic.Warnings; + var warnings = result.OpenApiDiagnostic.Warnings; Assert.False(warnings.Any()); } [Fact] - public void ParseDocumetWithWrongReferenceTypeShouldReturnADiagnosticError() + public void ParseDocWithRefsUsingProxyReferencesSucceeds() { - using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "docWithWrongRef.json")); - _ = new OpenApiStreamReader().Read(stream, out var diagnostic); + // Arrange + var expected = new OpenApiDocument + { + Info = new OpenApiInfo + { + Title = "Pet Store with Referenceable Parameter", + Version = "1.0.0" + }, + Paths = new OpenApiPaths + { + ["/pets"] = new OpenApiPathItem + { + Operations = new Dictionary + { + [OperationType.Get] = new OpenApiOperation + { + Summary = "Returns all pets", + Parameters = + [ + new OpenApiParameter + { + Name = "limit", + In = ParameterLocation.Query, + Description = "Limit the number of pets returned", + Required = false, + Schema = new() + { + Type = JsonSchemaType.Integer, + Format = "int32", + Default = 10 + }, + Reference = new OpenApiReference + { + Id = "LimitParameter", + Type = ReferenceType.Parameter + } + } + ], + Responses = new OpenApiResponses() + } + } + } + }, + Components = new OpenApiComponents + { + Parameters = new Dictionary + { + ["LimitParameter"] = new OpenApiParameter + { + Name = "limit", + In = ParameterLocation.Query, + Description = "Limit the number of pets returned", + Required = false, + Schema = new() + { + Type = JsonSchemaType.Integer, + Format = "int32", + Default = 10 + }, + } + } + } + }; + + var expectedSerializedDoc = @"openapi: 3.0.1 +info: + title: Pet Store with Referenceable Parameter + version: 1.0.0 +paths: + /pets: + get: + summary: Returns all pets + parameters: + - $ref: '#/components/parameters/LimitParameter' + responses: { } +components: + parameters: + LimitParameter: + name: limit + in: query + description: Limit the number of pets returned + schema: + type: integer + format: int32 + default: 10"; + + 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 outputDoc = doc.SerializeAsYaml(OpenApiSpecVersion.OpenApi3_0).MakeLineBreaksEnvironmentNeutral(); + var output = actualParam.SerializeAsYaml(OpenApiSpecVersion.OpenApi3_0); + var expectedParam = expected.Paths["/pets"].Operations[OperationType.Get].Parameters.First(); - diagnostic.Errors.Should().BeEquivalentTo(new List { - new( new OpenApiException("Invalid Reference Type 'Schema'.")) }); + // Assert + actualParam.Should().BeEquivalentTo(expectedParam, options => options + .Excluding(x => x.Reference.HostDocument) + .Excluding(x => x.Schema.Default.Parent) + .Excluding(x => x.Schema.Default.Options) + .IgnoringCyclicReferences()); + outputDoc.Should().BeEquivalentTo(expectedSerializedDoc.MakeLineBreaksEnvironmentNeutral()); } [Fact] public void ParseBasicDocumentWithServerVariableShouldSucceed() { - var openApiDoc = new OpenApiStringReader().Read(""" + var result = OpenApiDocument.Parse(""" openapi : 3.0.0 info: title: The API @@ -1385,20 +1361,16 @@ public void ParseBasicDocumentWithServerVariableShouldSucceed() default: v2 enum: [v1, v2] paths: {} - """, out var diagnostic); + """, "yaml"); - diagnostic.Should().BeEquivalentTo( - new OpenApiDiagnostic { SpecificationVersion = OpenApiSpecVersion.OpenApi3_0 }); - - openApiDoc.Should().BeEquivalentTo( - new OpenApiDocument + var expected = new OpenApiDocument + { + Info = new() { - Info = new() - { - Title = "The API", - Version = "0.9.1", - }, - Servers = + Title = "The API", + Version = "0.9.1", + }, + Servers = { new OpenApiServer { @@ -1410,14 +1382,26 @@ public void ParseBasicDocumentWithServerVariableShouldSucceed() } } }, - Paths = new() + Paths = new() + }; + + 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(expected, options => options.Excluding(x => x.BaseUri)); } [Fact] public void ParseBasicDocumentWithServerVariableAndNoDefaultShouldFail() { - var openApiDoc = new OpenApiStringReader().Read(""" + var result = OpenApiDocument.Parse(""" openapi : 3.0.0 info: title: The API @@ -1429,9 +1413,9 @@ public void ParseBasicDocumentWithServerVariableAndNoDefaultShouldFail() version: enum: [v1, v2] paths: {} - """, out var diagnostic); + """, "yaml"); - diagnostic.Errors.Should().NotBeEmpty(); + result.OpenApiDiagnostic.Errors.Should().NotBeEmpty(); } } } diff --git a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiEncodingTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiEncodingTests.cs index 2b9c23fe1..eaf802d8c 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiEncodingTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiEncodingTests.cs @@ -2,12 +2,9 @@ // Licensed under the MIT license. using System.IO; -using System.Linq; using FluentAssertions; using Microsoft.OpenApi.Models; -using Microsoft.OpenApi.Readers.ParseNodes; -using Microsoft.OpenApi.Readers.V3; -using SharpYaml.Serialization; +using Microsoft.OpenApi.Reader; using Xunit; namespace Microsoft.OpenApi.Readers.Tests.V3Tests @@ -17,21 +14,16 @@ public class OpenApiEncodingTests { private const string SampleFolderPath = "V3Tests/Samples/OpenApiEncoding/"; + public OpenApiEncodingTests() + { + OpenApiReaderRegistry.RegisterReader(OpenApiConstants.Yaml, new OpenApiYamlReader()); + } + [Fact] public void ParseBasicEncodingShouldSucceed() { - using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "basicEncoding.yaml")); - var yamlStream = new YamlStream(); - yamlStream.Load(new StreamReader(stream)); - var yamlNode = yamlStream.Documents.First().RootNode; - - var diagnostic = new OpenApiDiagnostic(); - var context = new ParsingContext(diagnostic); - - var node = new MapNode(context, (YamlMappingNode)yamlNode); - // Act - var encoding = OpenApiV3Deserializer.LoadEncoding(node); + var encoding = OpenApiModelFactory.Load(Path.Combine(SampleFolderPath, "basicEncoding.yaml"), OpenApiSpecVersion.OpenApi3_0, out _); // Assert encoding.Should().BeEquivalentTo( @@ -45,17 +37,9 @@ public void ParseBasicEncodingShouldSucceed() public void ParseAdvancedEncodingShouldSucceed() { using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "advancedEncoding.yaml")); - var yamlStream = new YamlStream(); - yamlStream.Load(new StreamReader(stream)); - var yamlNode = yamlStream.Documents.First().RootNode; - - var diagnostic = new OpenApiDiagnostic(); - var context = new ParsingContext(diagnostic); - - var node = new MapNode(context, (YamlMappingNode)yamlNode); // Act - var encoding = OpenApiV3Deserializer.LoadEncoding(node); + var encoding = OpenApiModelFactory.Load(stream, OpenApiSpecVersion.OpenApi3_0, OpenApiConstants.Yaml, out _); // Assert encoding.Should().BeEquivalentTo( @@ -69,8 +53,8 @@ public void ParseAdvancedEncodingShouldSucceed() { Description = "The number of allowed requests in the current period", Schema = new() - { - Type = "integer" + { + Type = JsonSchemaType.Integer } } } diff --git a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiExampleTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiExampleTests.cs index 8dd2b8022..84f028f6b 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiExampleTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiExampleTests.cs @@ -1,14 +1,12 @@ // Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. +// Licensed under the MIT license. using System.IO; -using System.Linq; +using System.Text.Json.Nodes; using FluentAssertions; using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Models; -using Microsoft.OpenApi.Readers.ParseNodes; -using Microsoft.OpenApi.Readers.V3; -using SharpYaml.Serialization; +using Microsoft.OpenApi.Reader; using Xunit; namespace Microsoft.OpenApi.Readers.Tests.V3Tests @@ -18,68 +16,73 @@ public class OpenApiExampleTests { private const string SampleFolderPath = "V3Tests/Samples/OpenApiExample/"; + public OpenApiExampleTests() + { + OpenApiReaderRegistry.RegisterReader("yaml", new OpenApiYamlReader()); + } + [Fact] public void ParseAdvancedExampleShouldSucceed() { - using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "advancedExample.yaml")); - var yamlStream = new YamlStream(); - yamlStream.Load(new StreamReader(stream)); - var yamlNode = yamlStream.Documents.First().RootNode; - - var diagnostic = new OpenApiDiagnostic(); - var context = new ParsingContext(diagnostic); - - var node = new MapNode(context, (YamlMappingNode)yamlNode); - - var example = OpenApiV3Deserializer.LoadExample(node); - - diagnostic.Errors.Should().BeEmpty(); - - example.Should().BeEquivalentTo( - new OpenApiExample + var example = OpenApiModelFactory.Load(Path.Combine(SampleFolderPath, "advancedExample.yaml"), OpenApiSpecVersion.OpenApi3_0, out var diagnostic); + var expected = new OpenApiExample + { + Value = new JsonObject { - Value = new OpenApiObject - { - ["versions"] = new OpenApiArray - { - new OpenApiObject + ["versions"] = new JsonArray { - ["status"] = new OpenApiString("Status1"), - ["id"] = new OpenApiString("v1"), - ["links"] = new OpenApiArray + new JsonObject { - new OpenApiObject + ["status"] = "Status1", + ["id"] = "v1", + ["links"] = new JsonArray { - ["href"] = new OpenApiString("http://example.com/1"), - ["rel"] = new OpenApiString("sampleRel1") + new JsonObject + { + ["href"] = "http://example.com/1", + ["rel"] = "sampleRel1" + } } - } - }, + }, - new OpenApiObject - { - ["status"] = new OpenApiString("Status2"), - ["id"] = new OpenApiString("v2"), - ["links"] = new OpenApiArray + new JsonObject { - new OpenApiObject + ["status"] = "Status2", + ["id"] = "v2", + ["links"] = new JsonArray { - ["href"] = new OpenApiString("http://example.com/2"), - ["rel"] = new OpenApiString("sampleRel2") + new JsonObject + { + ["href"] = "http://example.com/2", + ["rel"] = "sampleRel2" + } } } } - } - } - }); + } + }; + + var actualRoot = example.Value["versions"][0]["status"].Root; + var expectedRoot = expected.Value["versions"][0]["status"].Root; + + diagnostic.Errors.Should().BeEmpty(); + + example.Should().BeEquivalentTo(expected, options => options.IgnoringCyclicReferences() + .Excluding(e => e.Value["versions"][0]["status"].Root) + .Excluding(e => e.Value["versions"][0]["id"].Root) + .Excluding(e => e.Value["versions"][0]["links"][0]["href"].Root) + .Excluding(e => e.Value["versions"][0]["links"][0]["rel"].Root) + .Excluding(e => e.Value["versions"][1]["status"].Root) + .Excluding(e => e.Value["versions"][1]["id"].Root) + .Excluding(e => e.Value["versions"][1]["links"][0]["href"].Root) + .Excluding(e => e.Value["versions"][1]["links"][0]["rel"].Root)); } [Fact] public void ParseExampleForcedStringSucceed() { - using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "explicitString.yaml")); - new OpenApiStreamReader().Read(stream, out var diagnostic); - diagnostic.Errors.Should().BeEmpty(); + var result= OpenApiDocument.Load(Path.Combine(SampleFolderPath, "explicitString.yaml")); + result.OpenApiDiagnostic.Errors.Should().BeEmpty(); } } } diff --git a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiInfoTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiInfoTests.cs index c21789ccf..2fa75cf60 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiInfoTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiInfoTests.cs @@ -1,14 +1,13 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. +using System; using System.IO; -using System.Linq; +using System.Text.Json.Nodes; using FluentAssertions; using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Models; -using Microsoft.OpenApi.Readers.ParseNodes; -using Microsoft.OpenApi.Readers.V3; -using SharpYaml.Serialization; +using Microsoft.OpenApi.Reader; using Xunit; namespace Microsoft.OpenApi.Readers.Tests.V3Tests @@ -18,21 +17,16 @@ public class OpenApiInfoTests { private const string SampleFolderPath = "V3Tests/Samples/OpenApiInfo/"; + public OpenApiInfoTests() + { + OpenApiReaderRegistry.RegisterReader("yaml", new OpenApiYamlReader()); + } + [Fact] public void ParseAdvancedInfoShouldSucceed() { - using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "advancedInfo.yaml")); - var yamlStream = new YamlStream(); - yamlStream.Load(new StreamReader(stream)); - var yamlNode = yamlStream.Documents.First().RootNode; - - var diagnostic = new OpenApiDiagnostic(); - var context = new ParsingContext(diagnostic); - - var node = new MapNode(context, (YamlMappingNode)yamlNode); - // Act - var openApiInfo = OpenApiV3Deserializer.LoadInfo(node); + var openApiInfo = OpenApiModelFactory.Load(Path.Combine(SampleFolderPath, "advancedInfo.yaml"), OpenApiSpecVersion.OpenApi3_0, out var diagnostic); // Assert openApiInfo.Should().BeEquivalentTo( @@ -41,56 +35,55 @@ public void ParseAdvancedInfoShouldSucceed() Title = "Advanced Info", Description = "Sample Description", Version = "1.0.0", - TermsOfService = new("http://example.org/termsOfService"), - Contact = new() + TermsOfService = new Uri("http://example.org/termsOfService"), + Contact = new OpenApiContact { Email = "example@example.com", Extensions = { - ["x-twitter"] = new OpenApiString("@exampleTwitterHandler") + ["x-twitter"] = new OpenApiAny("@exampleTwitterHandler") }, Name = "John Doe", - Url = new("http://www.example.com/url1") + Url = new Uri("http://www.example.com/url1") }, - License = new() + License = new OpenApiLicense { - Extensions = { ["x-disclaimer"] = new OpenApiString("Sample Extension String Disclaimer") }, + Extensions = { ["x-disclaimer"] = new OpenApiAny("Sample Extension String Disclaimer") }, Name = "licenseName", - Url = new("http://www.example.com/url2") + Url = new Uri("http://www.example.com/url2") }, Extensions = { - ["x-something"] = new OpenApiString("Sample Extension String Something"), - ["x-contact"] = new OpenApiObject - { - ["name"] = new OpenApiString("John Doe"), - ["url"] = new OpenApiString("http://www.example.com/url3"), - ["email"] = new OpenApiString("example@example.com") - }, - ["x-list"] = new OpenApiArray - { - new OpenApiString("1"), - new OpenApiString("2") - } + ["x-something"] = new OpenApiAny("Sample Extension String Something"), + ["x-contact"] = new OpenApiAny(new JsonObject() + { + ["name"] = "John Doe", + ["url"] = "http://www.example.com/url3", + ["email"] = "example@example.com" + }), + ["x-list"] = new OpenApiAny (new JsonArray { "1", "2" }) } - }); + }, options => options.IgnoringCyclicReferences() + .Excluding(i => ((OpenApiAny)i.Contact.Extensions["x-twitter"]).Node.Parent) + .Excluding(i => ((OpenApiAny)i.License.Extensions["x-disclaimer"]).Node.Parent) + .Excluding(i => ((OpenApiAny)i.Extensions["x-something"]).Node.Parent) + .Excluding(i => ((OpenApiAny)i.Extensions["x-contact"]).Node["name"].Parent) + .Excluding(i => ((OpenApiAny)i.Extensions["x-contact"]).Node["name"].Root) + .Excluding(i => ((OpenApiAny)i.Extensions["x-contact"]).Node["url"].Parent) + .Excluding(i => ((OpenApiAny)i.Extensions["x-contact"]).Node["url"].Root) + .Excluding(i => ((OpenApiAny)i.Extensions["x-contact"]).Node["email"].Parent) + .Excluding(i => ((OpenApiAny)i.Extensions["x-contact"]).Node["email"].Root) + .Excluding(i => ((OpenApiAny)i.Extensions["x-list"]).Node[0].Parent) + .Excluding(i => ((OpenApiAny)i.Extensions["x-list"]).Node[0].Root) + .Excluding(i => ((OpenApiAny)i.Extensions["x-list"]).Node[1].Parent) + .Excluding(i => ((OpenApiAny)i.Extensions["x-list"]).Node[1].Root)); } [Fact] public void ParseBasicInfoShouldSucceed() { - using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "basicInfo.yaml")); - var yamlStream = new YamlStream(); - yamlStream.Load(new StreamReader(stream)); - var yamlNode = yamlStream.Documents.First().RootNode; - - var diagnostic = new OpenApiDiagnostic(); - var context = new ParsingContext(diagnostic); - - var node = new MapNode(context, (YamlMappingNode)yamlNode); - // Act - var openApiInfo = OpenApiV3Deserializer.LoadInfo(node); + var openApiInfo = OpenApiModelFactory.Load(Path.Combine(SampleFolderPath, "basicInfo.yaml"), OpenApiSpecVersion.OpenApi3_0, out _); // Assert openApiInfo.Should().BeEquivalentTo( @@ -99,17 +92,17 @@ public void ParseBasicInfoShouldSucceed() Title = "Basic Info", Description = "Sample Description", Version = "1.0.1", - TermsOfService = new("http://swagger.io/terms/"), - Contact = new() + TermsOfService = new Uri("http://swagger.io/terms/"), + Contact = new OpenApiContact { Email = "support@swagger.io", Name = "API Support", - Url = new("http://www.swagger.io/support") + Url = new Uri("http://www.swagger.io/support") }, - License = new() + License = new OpenApiLicense { Name = "Apache 2.0", - Url = new("http://www.apache.org/licenses/LICENSE-2.0.html") + Url = new Uri("http://www.apache.org/licenses/LICENSE-2.0.html") } }); } @@ -118,17 +111,9 @@ public void ParseBasicInfoShouldSucceed() public void ParseMinimalInfoShouldSucceed() { using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "minimalInfo.yaml")); - var yamlStream = new YamlStream(); - yamlStream.Load(new StreamReader(stream)); - var yamlNode = yamlStream.Documents.First().RootNode; - - var diagnostic = new OpenApiDiagnostic(); - var context = new ParsingContext(diagnostic); - - var node = new MapNode(context, (YamlMappingNode)yamlNode); // Act - var openApiInfo = OpenApiV3Deserializer.LoadInfo(node); + var openApiInfo = OpenApiModelFactory.Load(stream, OpenApiSpecVersion.OpenApi3_0, "yaml", out _); // Assert openApiInfo.Should().BeEquivalentTo( diff --git a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiMediaTypeTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiMediaTypeTests.cs index 266809561..26de35edb 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiMediaTypeTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiMediaTypeTests.cs @@ -3,11 +3,11 @@ using System.IO; using FluentAssertions; -using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Extensions; using Microsoft.OpenApi.Models; -using Microsoft.OpenApi.Readers.ParseNodes; -using Microsoft.OpenApi.Readers.V3; +using Microsoft.OpenApi.Reader; +using Microsoft.OpenApi.Reader.ParseNodes; +using Microsoft.OpenApi.Reader.V3; using Microsoft.OpenApi.Tests; using Xunit; @@ -18,44 +18,37 @@ public class OpenApiMediaTypeTests { private const string SampleFolderPath = "V3Tests/Samples/OpenApiMediaType/"; + public OpenApiMediaTypeTests() + { + OpenApiReaderRegistry.RegisterReader("yaml", new OpenApiYamlReader()); + } + [Fact] public void ParseMediaTypeWithExampleShouldSucceed() { - // Arrange - MapNode node; - using (var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "mediaTypeWithExample.yaml"))) - { - node = TestHelper.CreateYamlMapNode(stream); - } - // Act - var mediaType = OpenApiV3Deserializer.LoadMediaType(node); + var mediaType = OpenApiModelFactory.Load(Path.Combine(SampleFolderPath, "mediaTypeWithExample.yaml"), OpenApiSpecVersion.OpenApi3_0, out var diagnostic); // Assert mediaType.Should().BeEquivalentTo( new OpenApiMediaType { - Example = new OpenApiFloat(5), + Example = 5, Schema = new() { - Type = "number", + Type = JsonSchemaType.Number, Format = "float" } - }); + }, options => options.IgnoringCyclicReferences() + .Excluding(m => m.Example.Parent) + ); } [Fact] public void ParseMediaTypeWithExamplesShouldSucceed() { - // Arrange - MapNode node; - using (var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "mediaTypeWithExamples.yaml"))) - { - node = TestHelper.CreateYamlMapNode(stream); - } - // Act - var mediaType = OpenApiV3Deserializer.LoadMediaType(node); + var mediaType = OpenApiModelFactory.Load(Path.Combine(SampleFolderPath, "mediaTypeWithExamples.yaml"), OpenApiSpecVersion.OpenApi3_0, out var diagnostic); // Assert mediaType.Should().BeEquivalentTo( @@ -65,19 +58,21 @@ public void ParseMediaTypeWithExamplesShouldSucceed() { ["example1"] = new() { - Value = new OpenApiFloat(5), + Value = 5 }, ["example2"] = new() { - Value = new OpenApiFloat((float)7.5), + Value = 7.5 } }, Schema = new() { - Type = "number", + Type = JsonSchemaType.Number, Format = "float" } - }); + }, options => options.IgnoringCyclicReferences() + .Excluding(m => m.Examples["example1"].Value.Parent) + .Excluding(m => m.Examples["example2"].Value.Parent)); } [Fact] diff --git a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiOperationTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiOperationTests.cs index 665605e8d..9ba96bbda 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiOperationTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiOperationTests.cs @@ -5,8 +5,8 @@ using System.Linq; using FluentAssertions; using Microsoft.OpenApi.Models; -using Microsoft.OpenApi.Readers.ParseNodes; -using Microsoft.OpenApi.Readers.V3; +using Microsoft.OpenApi.Models.References; +using Microsoft.OpenApi.Reader; using Xunit; namespace Microsoft.OpenApi.Readers.Tests.V3Tests @@ -15,44 +15,32 @@ public class OpenApiOperationTests { private const string SampleFolderPath = "V3Tests/Samples/OpenApiOperation/"; + public OpenApiOperationTests() + { + OpenApiReaderRegistry.RegisterReader("yaml", new OpenApiYamlReader()); + } + [Fact] public void OperationWithSecurityRequirementShouldReferenceSecurityScheme() { - using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "securedOperation.yaml")); - var openApiDoc = new OpenApiStreamReader().Read(stream, out var diagnostic); + var result = OpenApiDocument.Load(Path.Combine(SampleFolderPath, "securedOperation.yaml")); - var securityRequirement = openApiDoc.Paths["/"].Operations[OperationType.Get].Security.First(); + var securityScheme = result.OpenApiDocument.Paths["/"].Operations[OperationType.Get].Security.First().Keys.First(); - Assert.Same(securityRequirement.Keys.First(), openApiDoc.Components.SecuritySchemes.First().Value); + securityScheme.Should().BeEquivalentTo(result.OpenApiDocument.Components.SecuritySchemes.First().Value, + options => options.Excluding(x => x.Reference)); } [Fact] public void ParseOperationWithParameterWithNoLocationShouldSucceed() { - // Arrange - MapNode node; - using (var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "operationWithParameterWithNoLocation.json"))) - { - node = TestHelper.CreateYamlMapNode(stream); - } - // Act - var operation = OpenApiV3Deserializer.LoadOperation(node); - - // Assert - operation.Should().BeEquivalentTo(new OpenApiOperation + var operation = OpenApiModelFactory.Load(Path.Combine(SampleFolderPath, "operationWithParameterWithNoLocation.json"), OpenApiSpecVersion.OpenApi3_0, out _); + var expectedOp = new OpenApiOperation { Tags = { - new OpenApiTag - { - UnresolvedReference = true, - Reference = new() - { - Id = "user", - Type = ReferenceType.Tag - } - } + new OpenApiTagReference("user", null) }, Summary = "Logs user into the system", Description = "", @@ -66,7 +54,7 @@ public void ParseOperationWithParameterWithNoLocationShouldSucceed() Required = true, Schema = new() { - Type = "string" + Type = JsonSchemaType.String } }, new OpenApiParameter @@ -77,11 +65,16 @@ public void ParseOperationWithParameterWithNoLocationShouldSucceed() Required = true, Schema = new() { - Type = "string" + Type = JsonSchemaType.String } } } - }); + }; + + // Assert + expectedOp.Should().BeEquivalentTo(operation, + options => options.Excluding(x => x.Tags[0].Reference.HostDocument) + .Excluding(x => x.Tags[0].Extensions)); } } } diff --git a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiParameterTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiParameterTests.cs index 1439cea2e..e0f6460aa 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiParameterTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiParameterTests.cs @@ -1,13 +1,16 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. +using System.Collections.Generic; +using System; using System.IO; using FluentAssertions; using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Models; -using Microsoft.OpenApi.Readers.ParseNodes; -using Microsoft.OpenApi.Readers.V3; +using Microsoft.OpenApi.Reader; using Xunit; +using Microsoft.OpenApi.Reader.V3; +using Microsoft.OpenApi.Services; namespace Microsoft.OpenApi.Readers.Tests.V3Tests { @@ -16,18 +19,19 @@ public class OpenApiParameterTests { private const string SampleFolderPath = "V3Tests/Samples/OpenApiParameter/"; + public OpenApiParameterTests() + { + OpenApiReaderRegistry.RegisterReader("yaml", new OpenApiYamlReader()); + } + [Fact] public void ParsePathParameterShouldSucceed() { // Arrange - MapNode node; - using (var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "pathParameter.yaml"))) - { - node = TestHelper.CreateYamlMapNode(stream); - } + using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "pathParameter.yaml")); // Act - var parameter = OpenApiV3Deserializer.LoadParameter(node); + var parameter = OpenApiModelFactory.Load(stream, OpenApiSpecVersion.OpenApi3_0, "yaml", out _); // Assert parameter.Should().BeEquivalentTo( @@ -39,7 +43,7 @@ public void ParsePathParameterShouldSucceed() Required = true, Schema = new() { - Type = "string" + Type = JsonSchemaType.String } }); } @@ -47,15 +51,8 @@ public void ParsePathParameterShouldSucceed() [Fact] public void ParseQueryParameterShouldSucceed() { - // Arrange - MapNode node; - using (var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "queryParameter.yaml"))) - { - node = TestHelper.CreateYamlMapNode(stream); - } - // Act - var parameter = OpenApiV3Deserializer.LoadParameter(node); + var parameter = OpenApiModelFactory.Load(Path.Combine(SampleFolderPath, "queryParameter.yaml"), OpenApiSpecVersion.OpenApi3_0, out _); // Assert parameter.Should().BeEquivalentTo( @@ -67,10 +64,10 @@ public void ParseQueryParameterShouldSucceed() Required = false, Schema = new() { - Type = "array", + Type = JsonSchemaType.Array, Items = new() { - Type = "string" + Type = JsonSchemaType.String } }, Style = ParameterStyle.Form, @@ -81,15 +78,8 @@ public void ParseQueryParameterShouldSucceed() [Fact] public void ParseQueryParameterWithObjectTypeShouldSucceed() { - // Arrange - MapNode node; - using (var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "queryParameterWithObjectType.yaml"))) - { - node = TestHelper.CreateYamlMapNode(stream); - } - // Act - var parameter = OpenApiV3Deserializer.LoadParameter(node); + var parameter = OpenApiModelFactory.Load(Path.Combine(SampleFolderPath, "queryParameterWithObjectType.yaml"), OpenApiSpecVersion.OpenApi3_0, out _); // Assert parameter.Should().BeEquivalentTo( @@ -99,10 +89,10 @@ public void ParseQueryParameterWithObjectTypeShouldSucceed() Name = "freeForm", Schema = new() { - Type = "object", + Type = JsonSchemaType.Object, AdditionalProperties = new() { - Type = "integer" + Type = JsonSchemaType.Integer } }, Style = ParameterStyle.Form @@ -113,14 +103,10 @@ public void ParseQueryParameterWithObjectTypeShouldSucceed() public void ParseQueryParameterWithObjectTypeAndContentShouldSucceed() { // Arrange - MapNode node; - using (var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "queryParameterWithObjectTypeAndContent.yaml"))) - { - node = TestHelper.CreateYamlMapNode(stream); - } + using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "queryParameterWithObjectTypeAndContent.yaml")); // Act - var parameter = OpenApiV3Deserializer.LoadParameter(node); + var parameter = OpenApiModelFactory.Load(stream, OpenApiSpecVersion.OpenApi3_0, "yaml", out _); // Assert parameter.Should().BeEquivalentTo( @@ -132,9 +118,9 @@ public void ParseQueryParameterWithObjectTypeAndContentShouldSucceed() { ["application/json"] = new() { - Schema = new() - { - Type = "object", + Schema = new() + { + Type = JsonSchemaType.Object, Required = { "lat", @@ -144,14 +130,14 @@ public void ParseQueryParameterWithObjectTypeAndContentShouldSucceed() { ["lat"] = new() { - Type = "number" + Type = JsonSchemaType.Number }, ["long"] = new() { - Type = "number" + Type = JsonSchemaType.Number } } - } + } } } }); @@ -160,15 +146,8 @@ public void ParseQueryParameterWithObjectTypeAndContentShouldSucceed() [Fact] public void ParseHeaderParameterShouldSucceed() { - // Arrange - MapNode node; - using (var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "headerParameter.yaml"))) - { - node = TestHelper.CreateYamlMapNode(stream); - } - // Act - var parameter = OpenApiV3Deserializer.LoadParameter(node); + var parameter = OpenApiModelFactory.Load(Path.Combine(SampleFolderPath, "headerParameter.yaml"), OpenApiSpecVersion.OpenApi3_0, out _); // Assert parameter.Should().BeEquivalentTo( @@ -182,10 +161,10 @@ public void ParseHeaderParameterShouldSucceed() Schema = new() { - Type = "array", + Type = JsonSchemaType.Array, Items = new() { - Type = "integer", + Type = JsonSchemaType.Integer, Format = "int64", } } @@ -195,15 +174,8 @@ public void ParseHeaderParameterShouldSucceed() [Fact] public void ParseParameterWithNullLocationShouldSucceed() { - // Arrange - MapNode node; - using (var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "parameterWithNullLocation.yaml"))) - { - node = TestHelper.CreateYamlMapNode(stream); - } - // Act - var parameter = OpenApiV3Deserializer.LoadParameter(node); + var parameter = OpenApiModelFactory.Load(Path.Combine(SampleFolderPath, "parameterWithNullLocation.yaml"), OpenApiSpecVersion.OpenApi3_0, out _); // Assert parameter.Should().BeEquivalentTo( @@ -215,7 +187,7 @@ public void ParseParameterWithNullLocationShouldSucceed() Required = true, Schema = new() { - Type = "string" + Type = JsonSchemaType.String } }); } @@ -224,14 +196,10 @@ public void ParseParameterWithNullLocationShouldSucceed() public void ParseParameterWithNoLocationShouldSucceed() { // Arrange - MapNode node; - using (var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "parameterWithNoLocation.yaml"))) - { - node = TestHelper.CreateYamlMapNode(stream); - } + using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "parameterWithNoLocation.yaml")); // Act - var parameter = OpenApiV3Deserializer.LoadParameter(node); + var parameter = OpenApiModelFactory.Load(stream, OpenApiSpecVersion.OpenApi3_0, "yaml", out _); // Assert parameter.Should().BeEquivalentTo( @@ -243,7 +211,7 @@ public void ParseParameterWithNoLocationShouldSucceed() Required = true, Schema = new() { - Type = "string" + Type = JsonSchemaType.String } }); } @@ -252,14 +220,10 @@ public void ParseParameterWithNoLocationShouldSucceed() public void ParseParameterWithUnknownLocationShouldSucceed() { // Arrange - MapNode node; - using (var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "parameterWithUnknownLocation.yaml"))) - { - node = TestHelper.CreateYamlMapNode(stream); - } + using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "parameterWithUnknownLocation.yaml")); // Act - var parameter = OpenApiV3Deserializer.LoadParameter(node); + var parameter = OpenApiModelFactory.Load(stream, OpenApiSpecVersion.OpenApi3_0, "yaml", out _); // Assert parameter.Should().BeEquivalentTo( @@ -271,7 +235,7 @@ public void ParseParameterWithUnknownLocationShouldSucceed() Required = true, Schema = new() { - Type = "string" + Type = JsonSchemaType.String } }); } @@ -279,15 +243,8 @@ public void ParseParameterWithUnknownLocationShouldSucceed() [Fact] public void ParseParameterWithExampleShouldSucceed() { - // Arrange - MapNode node; - using (var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "parameterWithExample.yaml"))) - { - node = TestHelper.CreateYamlMapNode(stream); - } - // Act - var parameter = OpenApiV3Deserializer.LoadParameter(node); + var parameter = OpenApiModelFactory.Load(Path.Combine(SampleFolderPath, "parameterWithExample.yaml"), OpenApiSpecVersion.OpenApi3_0, out _); // Assert parameter.Should().BeEquivalentTo( @@ -297,27 +254,20 @@ public void ParseParameterWithExampleShouldSucceed() Name = "username", Description = "username to fetch", Required = true, - Example = new OpenApiFloat(5), + Example = (float)5.0, Schema = new() { - Type = "number", + Type = JsonSchemaType.Number, Format = "float" } - }); + }, options => options.IgnoringCyclicReferences().Excluding(p => p.Example.Parent)); } [Fact] public void ParseParameterWithExamplesShouldSucceed() { - // Arrange - MapNode node; - using (var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "parameterWithExamples.yaml"))) - { - node = TestHelper.CreateYamlMapNode(stream); - } - // Act - var parameter = OpenApiV3Deserializer.LoadParameter(node); + var parameter = OpenApiModelFactory.Load(Path.Combine(SampleFolderPath, "parameterWithExamples.yaml"), OpenApiSpecVersion.OpenApi3_0, out _); // Assert parameter.Should().BeEquivalentTo( @@ -331,19 +281,105 @@ public void ParseParameterWithExamplesShouldSucceed() { ["example1"] = new() { - Value = new OpenApiFloat(5), + Value = 5.0 }, ["example2"] = new() { - Value = new OpenApiFloat((float)7.5), + Value = (float) 7.5 } }, Schema = new() { - Type = "number", + Type = JsonSchemaType.Number, Format = "float" } - }); + }, options => options.IgnoringCyclicReferences() + .Excluding(p => p.Examples["example1"].Value.Parent) + .Excluding(p => p.Examples["example2"].Value.Parent)); + } + + [Fact] + public void ParseParameterWithReferenceWorks() + { + // Arrange + var document = new OpenApiDocument + { + Info = new OpenApiInfo + { + Version = "1.0.0", + Title = "Swagger Petstore (Simple)" + }, + Servers = new List + { + new OpenApiServer + { + Url = "http://petstore.swagger.io/api" + } + }, + Paths = new OpenApiPaths + { + ["/pets"] = new OpenApiPathItem + { + Operations = new Dictionary + { + [OperationType.Get] = new OpenApiOperation + { + Description = "Returns all pets from the system that the user has access to", + OperationId = "findPets", + Parameters = new List + { + new() { + Reference = new OpenApiReference + { + Type = ReferenceType.Parameter, + Id = "tagsParameter" + } + } + }, + } + } + } + }, + Components = new OpenApiComponents + { + Parameters = new Dictionary() + { + ["tagsParameter"] = new OpenApiParameter + { + Name = "tags", + In = ParameterLocation.Query, + Description = "tags to filter by", + Required = false, + Schema = new() + { + Type = JsonSchemaType.Array, + Items = new OpenApiSchema + { + Type = JsonSchemaType.String + } + }, + Reference = new OpenApiReference + { + Type = ReferenceType.Parameter, + Id = "tagsParameter" + } + } + } + } + }; + + document.Workspace.RegisterComponents(document); + + using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "parameterWithRef.yaml")); + var node = TestHelper.CreateYamlMapNode(stream); + + var expected = document.Components.Parameters["tagsParameter"]; + + // Act + var param = OpenApiV3Deserializer.LoadParameter(node, document); + + // Assert + param.Should().BeEquivalentTo(expected, options => options.Excluding(p => p.Reference.HostDocument)); } } } diff --git a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiResponseTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiResponseTests.cs index 90ec9047b..09a1d00a1 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiResponseTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiResponseTests.cs @@ -3,6 +3,9 @@ using System.IO; using System.Linq; +using FluentAssertions; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Reader; using Xunit; namespace Microsoft.OpenApi.Readers.Tests.V3Tests @@ -12,15 +15,21 @@ public class OpenApiResponseTests { private const string SampleFolderPath = "V3Tests/Samples/OpenApiResponse/"; + public OpenApiResponseTests() + { + OpenApiReaderRegistry.RegisterReader("yaml", new OpenApiYamlReader()); + } + [Fact] public void ResponseWithReferencedHeaderShouldReferenceComponent() { - using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "responseWithHeaderReference.yaml")); - var openApiDoc = new OpenApiStreamReader().Read(stream, out var diagnostic); + var result = OpenApiDocument.Load(Path.Combine(SampleFolderPath, "responseWithHeaderReference.yaml")); - var response = openApiDoc.Components.Responses["Test"]; + var response = result.OpenApiDocument.Components.Responses["Test"]; + var expected = response.Headers.First().Value; + var actual = result.OpenApiDocument.Components.Headers.First().Value; - Assert.Same(response.Headers.First().Value, openApiDoc.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 109997bb6..81cb4376b 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiSchemaTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiSchemaTests.cs @@ -1,16 +1,21 @@ // Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. +// Licensed under the MIT license. using System.Collections.Generic; using System.IO; using System.Linq; +using System.Text.Json.Nodes; using FluentAssertions; using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Models; -using Microsoft.OpenApi.Readers.ParseNodes; -using Microsoft.OpenApi.Readers.V3; +using Microsoft.OpenApi.Extensions; using SharpYaml.Serialization; using Xunit; +using Microsoft.OpenApi.Reader; +using Microsoft.OpenApi.Reader.ParseNodes; +using Microsoft.OpenApi.Reader.V3; +using FluentAssertions.Equivalency; +using Microsoft.OpenApi.Models.References; namespace Microsoft.OpenApi.Readers.Tests.V3Tests { @@ -19,6 +24,11 @@ public class OpenApiSchemaTests { private const string SampleFolderPath = "V3Tests/Samples/OpenApiSchema/"; + public OpenApiSchemaTests() + { + OpenApiReaderRegistry.RegisterReader("yaml", new OpenApiYamlReader()); + } + [Fact] public void ParsePrimitiveSchemaShouldSucceed() { @@ -30,7 +40,8 @@ public void ParsePrimitiveSchemaShouldSucceed() var diagnostic = new OpenApiDiagnostic(); var context = new ParsingContext(diagnostic); - var node = new MapNode(context, (YamlMappingNode)yamlNode); + var asJsonNode = yamlNode.ToJsonNode(); + var node = new MapNode(context, asJsonNode); // Act var schema = OpenApiV3Deserializer.LoadSchema(node); @@ -41,178 +52,73 @@ public void ParsePrimitiveSchemaShouldSucceed() schema.Should().BeEquivalentTo( new OpenApiSchema { - Type = "string", + Type = JsonSchemaType.String, Format = "email" }); - } - - [Fact] - public void ParsePrimitiveSchemaFragmentShouldSucceed() - { - using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "primitiveSchema.yaml")); - var reader = new OpenApiStreamReader(); - - // Act - var schema = reader.ReadFragment(stream, OpenApiSpecVersion.OpenApi3_0, out var diagnostic); - - // Assert - diagnostic.Should().BeEquivalentTo(new OpenApiDiagnostic()); - - schema.Should().BeEquivalentTo( - new OpenApiSchema - { - Type = "string", - Format = "email" - }); - } - - [Fact] - public void ParsePrimitiveStringSchemaFragmentShouldSucceed() - { - var input = - """ - { - "type": "integer", - "format": "int64", - "default": 88 - } - """; - var reader = new OpenApiStringReader(); - - // Act - var schema = reader.ReadFragment(input, OpenApiSpecVersion.OpenApi3_0, out var diagnostic); - - // Assert - diagnostic.Should().BeEquivalentTo(new OpenApiDiagnostic()); - - schema.Should().BeEquivalentTo( - new OpenApiSchema - { - Type = "integer", - Format = "int64", - Default = new OpenApiLong(88) - }); - } + } [Fact] public void ParseExampleStringFragmentShouldSucceed() { - var input = - """ - { - "foo": "bar", - "baz": [ 1,2] - } - """; - var reader = new OpenApiStringReader(); + var input = @" +{ + ""foo"": ""bar"", + ""baz"": [ 1,2] +}"; + var diagnostic = new OpenApiDiagnostic(); // Act - var openApiAny = reader.ReadFragment(input, OpenApiSpecVersion.OpenApi3_0, out var diagnostic); + var openApiAny = OpenApiModelFactory.Parse(input, OpenApiSpecVersion.OpenApi3_0, out diagnostic); // Assert diagnostic.Should().BeEquivalentTo(new OpenApiDiagnostic()); - openApiAny.Should().BeEquivalentTo( - new OpenApiObject + openApiAny.Should().BeEquivalentTo(new OpenApiAny( + new JsonObject { - ["foo"] = new OpenApiString("bar"), - ["baz"] = new OpenApiArray - { - new OpenApiInteger(1), - new OpenApiInteger(2) - } - }); + ["foo"] = "bar", + ["baz"] = new JsonArray() { 1, 2 } + }), options => options.IgnoringCyclicReferences()); } [Fact] public void ParseEnumFragmentShouldSucceed() { - var input = - """ - [ - "foo", - "baz" - ] - """; - var reader = new OpenApiStringReader(); - - // Act - var openApiAny = reader.ReadFragment(input, OpenApiSpecVersion.OpenApi3_0, out var diagnostic); - - // Assert - diagnostic.Should().BeEquivalentTo(new OpenApiDiagnostic()); - - openApiAny.Should().BeEquivalentTo( - new OpenApiArray - { - new OpenApiString("foo"), - new OpenApiString("baz") - }); - } - - [Fact] - public void ParseSimpleSchemaShouldSucceed() - { - using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "simpleSchema.yaml")); - var yamlStream = new YamlStream(); - yamlStream.Load(new StreamReader(stream)); - var yamlNode = yamlStream.Documents.First().RootNode; - + var input = @" +[ + ""foo"", + ""baz"" +]"; var diagnostic = new OpenApiDiagnostic(); - var context = new ParsingContext(diagnostic); - - var node = new MapNode(context, (YamlMappingNode)yamlNode); // Act - var schema = OpenApiV3Deserializer.LoadSchema(node); + var openApiAny = OpenApiModelFactory.Parse(input, OpenApiSpecVersion.OpenApi3_0, out diagnostic); // Assert diagnostic.Should().BeEquivalentTo(new OpenApiDiagnostic()); - schema.Should().BeEquivalentTo( - new OpenApiSchema + openApiAny.Should().BeEquivalentTo(new OpenApiAny( + new JsonArray { - Type = "object", - Required = - { - "name" - }, - Properties = - { - ["name"] = new() - { - Type = "string" - }, - ["address"] = new() - { - Type = "string" - }, - ["age"] = new() - { - Type = "integer", - Format = "int32", - Minimum = 0 - } - }, - AdditionalPropertiesAllowed = false - }); + "foo", + "baz" + }), options => options.IgnoringCyclicReferences()); } [Fact] public void ParsePathFragmentShouldSucceed() { - var input = - """ - summary: externally referenced path item - get: - responses: - '200': - description: Ok - """; - var reader = new OpenApiStringReader(); + var input = @" +summary: externally referenced path item +get: + responses: + '200': + description: Ok +"; + var diagnostic = new OpenApiDiagnostic(); // Act - var openApiAny = reader.ReadFragment(input, OpenApiSpecVersion.OpenApi3_0, out var diagnostic); + var openApiAny = OpenApiModelFactory.Parse(input, OpenApiSpecVersion.OpenApi3_0, out diagnostic, "yaml"); // Assert diagnostic.Should().BeEquivalentTo(new OpenApiDiagnostic()); @@ -223,11 +129,11 @@ public void ParsePathFragmentShouldSucceed() Summary = "externally referenced path item", Operations = new Dictionary { - [OperationType.Get] = new() + [OperationType.Get] = new OpenApiOperation() { - Responses = new() + Responses = new OpenApiResponses { - ["200"] = new() + ["200"] = new OpenApiResponse { Description = "Ok" } @@ -240,31 +146,34 @@ public void ParsePathFragmentShouldSucceed() [Fact] public void ParseDictionarySchemaShouldSucceed() { - using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "dictionarySchema.yaml")); - var yamlStream = new YamlStream(); - yamlStream.Load(new StreamReader(stream)); - var yamlNode = yamlStream.Documents.First().RootNode; + using (var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "dictionarySchema.yaml"))) + { + var yamlStream = new YamlStream(); + yamlStream.Load(new StreamReader(stream)); + var yamlNode = yamlStream.Documents.First().RootNode; - var diagnostic = new OpenApiDiagnostic(); - var context = new ParsingContext(diagnostic); + var diagnostic = new OpenApiDiagnostic(); + var context = new ParsingContext(diagnostic); - var node = new MapNode(context, (YamlMappingNode)yamlNode); + var asJsonNode = yamlNode.ToJsonNode(); + var node = new MapNode(context, asJsonNode); - // Act - var schema = OpenApiV3Deserializer.LoadSchema(node); + // Act + var schema = OpenApiV3Deserializer.LoadSchema(node); - // Assert - diagnostic.Should().BeEquivalentTo(new OpenApiDiagnostic()); + // Assert + diagnostic.Should().BeEquivalentTo(new OpenApiDiagnostic()); - schema.Should().BeEquivalentTo( + schema.Should().BeEquivalentTo( new OpenApiSchema { - Type = "object", + Type = JsonSchemaType.Object, AdditionalProperties = new() { - Type = "string" + Type = JsonSchemaType.String } }); + } } [Fact] @@ -278,7 +187,8 @@ public void ParseBasicSchemaWithExampleShouldSucceed() var diagnostic = new OpenApiDiagnostic(); var context = new ParsingContext(diagnostic); - var node = new MapNode(context, (YamlMappingNode)yamlNode); + var asJsonNode = yamlNode.ToJsonNode(); + var node = new MapNode(context, asJsonNode); // Act var schema = OpenApiV3Deserializer.LoadSchema(node); @@ -287,360 +197,203 @@ public void ParseBasicSchemaWithExampleShouldSucceed() diagnostic.Should().BeEquivalentTo(new OpenApiDiagnostic()); schema.Should().BeEquivalentTo( - new OpenApiSchema + new OpenApiSchema + { + Type = JsonSchemaType.Object, + Properties = { - Type = "object", - Properties = - { ["id"] = new() { - Type = "integer", + Type = JsonSchemaType.Integer, Format = "int64" }, ["name"] = new() { - Type = "string" + Type = JsonSchemaType.String } - }, - Required = - { + }, + Required = + { "name" - }, - Example = new OpenApiObject - { - ["name"] = new OpenApiString("Puma"), - ["id"] = new OpenApiLong(1) - } - }); + }, + Example = new JsonObject + { + ["name"] = new OpenApiAny("Puma").Node, + ["id"] = new OpenApiAny(1).Node + } + }, options => options + .IgnoringCyclicReferences() + .Excluding((IMemberInfo memberInfo) => + memberInfo.Path.EndsWith("Parent")) + .Excluding((IMemberInfo memberInfo) => + memberInfo.Path.EndsWith("Root"))); } [Fact] public void ParseBasicSchemaWithReferenceShouldSucceed() { - using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "basicSchemaWithReference.yaml")); // Act - var openApiDoc = new OpenApiStreamReader().Read(stream, out var diagnostic); + var result = OpenApiDocument.Load(Path.Combine(SampleFolderPath, "basicSchemaWithReference.yaml")); // Assert - var components = openApiDoc.Components; + var components = result.OpenApiDocument.Components; - diagnostic.Should().BeEquivalentTo( - new OpenApiDiagnostic { SpecificationVersion = OpenApiSpecVersion.OpenApi3_0 }); + result.OpenApiDiagnostic.Should().BeEquivalentTo( + new OpenApiDiagnostic() + { + SpecificationVersion = OpenApiSpecVersion.OpenApi3_0, + Errors = new List() + { + new OpenApiError("", "Paths is a REQUIRED field at #/") + } + }); - components.Should().BeEquivalentTo( - new OpenApiComponents + var expectedComponents = new OpenApiComponents + { + Schemas = { - Schemas = + ["ErrorModel"] = new() { - ["ErrorModel"] = new() + Type = JsonSchemaType.Object, + Properties = { - Type = "object", - Properties = + ["code"] = new() { - ["code"] = new() - { - Type = "integer", - Minimum = 100, - Maximum = 600 - }, - ["message"] = new() - { - Type = "string" - } + Type = JsonSchemaType.Integer, + Minimum = 100, + Maximum = 600 }, - Reference = new() + ["message"] = new() { - Type = ReferenceType.Schema, - Id = "ErrorModel", - HostDocument = openApiDoc - }, - Required = - { - "message", - "code" + Type = JsonSchemaType.String } }, - ["ExtendedErrorModel"] = new() + Required = { - Reference = new() - { - Type = ReferenceType.Schema, - Id = "ExtendedErrorModel", - HostDocument = openApiDoc - }, - AllOf = + "message", + "code" + } + }, + ["ExtendedErrorModel"] = new() + { + AllOf = + { + new OpenApiSchemaReference("ErrorModel", result.OpenApiDocument), + new OpenApiSchema { - new OpenApiSchema - { - Reference = new() - { - Type = ReferenceType.Schema, - Id = "ErrorModel", - HostDocument = openApiDoc - }, - // Schema should be dereferenced in our model, so all the properties - // from the ErrorModel above should be propagated here. - Type = "object", - Properties = - { - ["code"] = new() - { - Type = "integer", - Minimum = 100, - Maximum = 600 - }, - ["message"] = new() - { - Type = "string" - } - }, - Required = - { - "message", - "code" - } - }, - new OpenApiSchema + Type = JsonSchemaType.Object, + Required = {"rootCause"}, + Properties = { - Type = "object", - Required = {"rootCause"}, - Properties = + ["rootCause"] = new() { - ["rootCause"] = new() - { - Type = "string" - } + Type = JsonSchemaType.String } } } } } - }, options => options.Excluding(m => m.Name == "HostDocument")); + } + }; + + components.Should().BeEquivalentTo(expectedComponents); } [Fact] public void ParseAdvancedSchemaWithReferenceShouldSucceed() { - using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "advancedSchemaWithReference.yaml")); // Act - var openApiDoc = new OpenApiStreamReader().Read(stream, out var diagnostic); - - // Assert - var components = openApiDoc.Components; - - diagnostic.Should().BeEquivalentTo( - new OpenApiDiagnostic { SpecificationVersion = OpenApiSpecVersion.OpenApi3_0 }); + var result = OpenApiDocument.Load(Path.Combine(SampleFolderPath, "advancedSchemaWithReference.yaml")); - components.Should().BeEquivalentTo( - new OpenApiComponents + var expectedComponents = new OpenApiComponents + { + Schemas = { - Schemas = + ["Pet"] = new() { - ["Pet"] = new() + Type = JsonSchemaType.Object, + Discriminator = new() { - Type = "object", - Discriminator = new() - { - PropertyName = "petType" - }, - Properties = - { - ["name"] = new() - { - Type = "string" - }, - ["petType"] = new() - { - Type = "string" - } - }, - Required = - { - "name", - "petType" - }, - Reference = new() - { - Id= "Pet", - Type = ReferenceType.Schema, - HostDocument = openApiDoc - } + PropertyName = "petType" }, - ["Cat"] = new() + Properties = { - Description = "A representation of a cat", - AllOf = + ["name"] = new() { - new OpenApiSchema - { - Reference = new() - { - Type = ReferenceType.Schema, - Id = "Pet", - HostDocument = openApiDoc - }, - // Schema should be dereferenced in our model, so all the properties - // from the Pet above should be propagated here. - Type = "object", - Discriminator = new() - { - PropertyName = "petType" - }, - Properties = - { - ["name"] = new() - { - Type = "string" - }, - ["petType"] = new() - { - Type = "string" - } - }, - Required = - { - "name", - "petType" - } - }, - new OpenApiSchema - { - Type = "object", - Required = {"huntingSkill"}, - Properties = - { - ["huntingSkill"] = new() - { - Type = "string", - Description = "The measured skill for hunting", - Enum = - { - new OpenApiString("clueless"), - new OpenApiString("lazy"), - new OpenApiString("adventurous"), - new OpenApiString("aggressive") - } - } - } - } + Type = JsonSchemaType.String }, - Reference = new() + ["petType"] = new() { - Id= "Cat", - Type = ReferenceType.Schema, - HostDocument = openApiDoc + Type = JsonSchemaType.String } }, - ["Dog"] = new() + Required = + { + "name", + "petType" + } + }, + ["Cat"] = new() + { + Description = "A representation of a cat", + AllOf = { - Description = "A representation of a dog", - AllOf = + new OpenApiSchemaReference("Pet", result.OpenApiDocument), + new OpenApiSchema { - new OpenApiSchema + Type = JsonSchemaType.Object, + Required = {"huntingSkill"}, + Properties = { - Reference = new() - { - Type = ReferenceType.Schema, - Id = "Pet", - HostDocument = openApiDoc - }, - // Schema should be dereferenced in our model, so all the properties - // from the Pet above should be propagated here. - Type = "object", - Discriminator = new() + ["huntingSkill"] = new() { - PropertyName = "petType" - }, - Properties = - { - ["name"] = new() - { - Type = "string" - }, - ["petType"] = new() + Type = JsonSchemaType.String, + Description = "The measured skill for hunting", + Enum = { - Type = "string" - } - }, - Required = - { - "name", - "petType" - } - }, - new OpenApiSchema - { - Type = "object", - Required = {"packSize"}, - Properties = - { - ["packSize"] = new() - { - Type = "integer", - Format = "int32", - Description = "the size of the pack the dog is from", - Default = new OpenApiInteger(0), - Minimum = 0 + "clueless", + "lazy", + "adventurous", + "aggressive" } } } - }, - Reference = new() - { - Id= "Dog", - Type = ReferenceType.Schema, - HostDocument = openApiDoc } } - } - }, options => options.Excluding(m => m.Name == "HostDocument")); - } - - [Fact] - public void ParseSelfReferencingSchemaShouldNotStackOverflow() - { - using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "selfReferencingSchema.yaml")); - // Act - var openApiDoc = new OpenApiStreamReader().Read(stream, out var diagnostic); - - // Assert - var components = openApiDoc.Components; - - diagnostic.Should().BeEquivalentTo( - new OpenApiDiagnostic { SpecificationVersion = OpenApiSpecVersion.OpenApi3_0 }); - - var schemaExtension = new OpenApiSchema - { - AllOf = { new OpenApiSchema + }, + ["Dog"] = new() { - Title = "schemaExtension", - Type = "object", - Properties = { - ["description"] = new() { Type = "string", Nullable = true}, - ["targetTypes"] = new() + Description = "A representation of a dog", + AllOf = + { + new OpenApiSchemaReference("Pet", result.OpenApiDocument), + new OpenApiSchema { - Type = "array", - Items = new() + Type = JsonSchemaType.Object, + Required = {"packSize"}, + Properties = { - Type = "string" + ["packSize"] = new() + { + Type = JsonSchemaType.Integer, + Format = "int32", + Description = "the size of the pack the dog is from", + Default = 0, + Minimum = 0 + } } - }, - ["status"] = new() { Type = "string"}, - ["owner"] = new() { Type = "string"}, - ["child"] = null + } } } - }, - Reference = new() - { - Type = ReferenceType.Schema, - Id = "microsoft.graph.schemaExtension" } }; - schemaExtension.AllOf[0].Properties["child"] = schemaExtension; + // 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 expected = expectedComponents.SerializeAsYaml(OpenApiSpecVersion.OpenApi3_0); - components.Schemas["microsoft.graph.schemaExtension"].Should().BeEquivalentTo(components.Schemas["microsoft.graph.schemaExtension"].AllOf[0].Properties["child"]); + // Assert + actual.Should().Be(expected); } } } diff --git a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiSecuritySchemeTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiSecuritySchemeTests.cs index 0e462c2eb..ef1aa0fdb 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiSecuritySchemeTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiSecuritySchemeTests.cs @@ -1,13 +1,11 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. +using System; using System.IO; -using System.Linq; using FluentAssertions; using Microsoft.OpenApi.Models; -using Microsoft.OpenApi.Readers.ParseNodes; -using Microsoft.OpenApi.Readers.V3; -using SharpYaml.Serialization; +using Microsoft.OpenApi.Reader; using Xunit; namespace Microsoft.OpenApi.Readers.Tests.V3Tests @@ -16,22 +14,16 @@ namespace Microsoft.OpenApi.Readers.Tests.V3Tests public class OpenApiSecuritySchemeTests { private const string SampleFolderPath = "V3Tests/Samples/OpenApiSecurityScheme/"; + public OpenApiSecuritySchemeTests() + { + OpenApiReaderRegistry.RegisterReader("yaml", new OpenApiYamlReader()); + } [Fact] public void ParseHttpSecuritySchemeShouldSucceed() { - using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "httpSecurityScheme.yaml")); - var yamlStream = new YamlStream(); - yamlStream.Load(new StreamReader(stream)); - var yamlNode = yamlStream.Documents.First().RootNode; - - var diagnostic = new OpenApiDiagnostic(); - var context = new ParsingContext(diagnostic); - - var node = new MapNode(context, (YamlMappingNode)yamlNode); - // Act - var securityScheme = OpenApiV3Deserializer.LoadSecurityScheme(node); + var securityScheme = OpenApiModelFactory.Load(Path.Combine(SampleFolderPath, "httpSecurityScheme.yaml"), OpenApiSpecVersion.OpenApi3_0, out _); // Assert securityScheme.Should().BeEquivalentTo( @@ -45,18 +37,8 @@ public void ParseHttpSecuritySchemeShouldSucceed() [Fact] public void ParseApiKeySecuritySchemeShouldSucceed() { - using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "apiKeySecurityScheme.yaml")); - var yamlStream = new YamlStream(); - yamlStream.Load(new StreamReader(stream)); - var yamlNode = yamlStream.Documents.First().RootNode; - - var diagnostic = new OpenApiDiagnostic(); - var context = new ParsingContext(diagnostic); - - var node = new MapNode(context, (YamlMappingNode)yamlNode); - // Act - var securityScheme = OpenApiV3Deserializer.LoadSecurityScheme(node); + var securityScheme = OpenApiModelFactory.Load(Path.Combine(SampleFolderPath, "apiKeySecurityScheme.yaml"), OpenApiSpecVersion.OpenApi3_0, out _); // Assert securityScheme.Should().BeEquivalentTo( @@ -71,18 +53,8 @@ public void ParseApiKeySecuritySchemeShouldSucceed() [Fact] public void ParseBearerSecuritySchemeShouldSucceed() { - using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "bearerSecurityScheme.yaml")); - var yamlStream = new YamlStream(); - yamlStream.Load(new StreamReader(stream)); - var yamlNode = yamlStream.Documents.First().RootNode; - - var diagnostic = new OpenApiDiagnostic(); - var context = new ParsingContext(diagnostic); - - var node = new MapNode(context, (YamlMappingNode)yamlNode); - // Act - var securityScheme = OpenApiV3Deserializer.LoadSecurityScheme(node); + var securityScheme = OpenApiModelFactory.Load(Path.Combine(SampleFolderPath, "bearerSecurityScheme.yaml"), OpenApiSpecVersion.OpenApi3_0, out _); // Assert securityScheme.Should().BeEquivalentTo( @@ -97,33 +69,23 @@ public void ParseBearerSecuritySchemeShouldSucceed() [Fact] public void ParseOAuth2SecuritySchemeShouldSucceed() { - using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "oauth2SecurityScheme.yaml")); - var yamlStream = new YamlStream(); - yamlStream.Load(new StreamReader(stream)); - var yamlNode = yamlStream.Documents.First().RootNode; - - var diagnostic = new OpenApiDiagnostic(); - var context = new ParsingContext(diagnostic); - - var node = new MapNode(context, (YamlMappingNode)yamlNode); - // Act - var securityScheme = OpenApiV3Deserializer.LoadSecurityScheme(node); + var securityScheme = OpenApiModelFactory.Load(Path.Combine(SampleFolderPath, "oauth2SecurityScheme.yaml"), OpenApiSpecVersion.OpenApi3_0, out _); // Assert securityScheme.Should().BeEquivalentTo( new OpenApiSecurityScheme { Type = SecuritySchemeType.OAuth2, - Flows = new() + Flows = new OpenApiOAuthFlows { - Implicit = new() + Implicit = new OpenApiOAuthFlow { - AuthorizationUrl = new("https://example.com/api/oauth/dialog"), + AuthorizationUrl = new Uri("https://example.com/api/oauth/dialog"), Scopes = { - ["write:pets"] = "modify pets in your account", - ["read:pets"] = "read your pets" + ["write:pets"] = "modify pets in your account", + ["read:pets"] = "read your pets" } } } @@ -133,18 +95,8 @@ public void ParseOAuth2SecuritySchemeShouldSucceed() [Fact] public void ParseOpenIdConnectSecuritySchemeShouldSucceed() { - using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "openIdConnectSecurityScheme.yaml")); - var yamlStream = new YamlStream(); - yamlStream.Load(new StreamReader(stream)); - var yamlNode = yamlStream.Documents.First().RootNode; - - var diagnostic = new OpenApiDiagnostic(); - var context = new ParsingContext(diagnostic); - - var node = new MapNode(context, (YamlMappingNode)yamlNode); - // Act - var securityScheme = OpenApiV3Deserializer.LoadSecurityScheme(node); + var securityScheme = OpenApiModelFactory.Load(Path.Combine(SampleFolderPath, "openIdConnectSecurityScheme.yaml"), OpenApiSpecVersion.OpenApi3_0, out _); // Assert securityScheme.Should().BeEquivalentTo( @@ -152,7 +104,7 @@ public void ParseOpenIdConnectSecuritySchemeShouldSucceed() { Type = SecuritySchemeType.OpenIdConnect, Description = "Sample Description", - OpenIdConnectUrl = new("http://www.example.com") + OpenIdConnectUrl = new Uri("http://www.example.com") }); } } diff --git a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiXmlTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiXmlTests.cs index 4f7de222b..c0d99793e 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiXmlTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiXmlTests.cs @@ -1,13 +1,11 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. +using System; using System.IO; -using System.Linq; using FluentAssertions; using Microsoft.OpenApi.Models; -using Microsoft.OpenApi.Readers.ParseNodes; -using Microsoft.OpenApi.Readers.V3; -using SharpYaml.Serialization; +using Microsoft.OpenApi.Reader; using Xunit; namespace Microsoft.OpenApi.Readers.Tests.V3Tests @@ -17,28 +15,23 @@ public class OpenApiXmlTests { private const string SampleFolderPath = "V3Tests/Samples/OpenApiXml/"; + public OpenApiXmlTests() + { + OpenApiReaderRegistry.RegisterReader("yaml", new OpenApiYamlReader()); + } + [Fact] public void ParseBasicXmlShouldSucceed() { - using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "basicXml.yaml")); - var yamlStream = new YamlStream(); - yamlStream.Load(new StreamReader(stream)); - var yamlNode = yamlStream.Documents.First().RootNode; - - var diagnostic = new OpenApiDiagnostic(); - var context = new ParsingContext(diagnostic); - - var node = new MapNode(context, (YamlMappingNode)yamlNode); - // Act - var xml = OpenApiV3Deserializer.LoadXml(node); + var xml = OpenApiModelFactory.Load(Resources.GetStream(Path.Combine(SampleFolderPath, "basicXml.yaml")), OpenApiSpecVersion.OpenApi3_0, "yaml", out _); // Assert xml.Should().BeEquivalentTo( new OpenApiXml { Name = "name1", - Namespace = new("http://example.com/schema/namespaceSample"), + Namespace = new Uri("http://example.com/schema/namespaceSample"), Prefix = "samplePrefix", Wrapped = true }); diff --git a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/Samples/OpenApiDocument/azureblob.yaml b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/Samples/OpenApiDocument/azureblob.yaml new file mode 100644 index 000000000..358a11502 --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/Samples/OpenApiDocument/azureblob.yaml @@ -0,0 +1,469 @@ +{ + "swagger": "2.0", + "info": { + "version": "1.0", + "title": "Azure Blob Storage", + "description": "Microsoft Azure Storage provides a massively scalable, durable, and highly available storage for data on the cloud, and serves as the data storage solution for modern applications. Connect to Blob Storage to perform various operations such as create, update, get and delete on blobs in your Azure Storage account.", + "x-ms-api-annotation": { + "status": "Production" + }, + "contact": { + "name": "Microsoft", + "url": "https://azure.microsoft.com/support/" + } + }, + "host": "localhost:23340", + "basePath": "/apim/azureblob", + "schemes": [ + "https" + ], + "paths": { + "/{connectionId}/datasets/default/GetFileContentByPath": { + "get": { + "tags": [ + "AzureBlobSingletonFileTransferFileData" + ], + "summary": "Get blob content using path", + "description": "This operation retrieves blob contents using path.", + "operationId": "GetFileContentByPath", + "consumes": [], + "produces": [], + "parameters": [ + { + "name": "path", + "in": "query", + "description": "Specify unique path to the blob.", + "required": true, + "x-ms-summary": "Blob path", + "x-ms-dynamic-values": { + "capability": "file-picker", + "parameters": { + "dataset": "AccountNameFromSettings", + "isFolder": false, + "fileFilter": [] + }, + "value-path": "Path" + }, + "x-ms-dynamic-tree": { + "settings": { + "canSelectParentNodes": false, + "canSelectLeafNodes": true + }, + "open": { + "operationId": "ListAllRootFolders_V4", + "itemValuePath": "Path", + "itemTitlePath": "DisplayName", + "itemIsParent": "(IsFolder eq true)", + "itemFullTitlePath": "Path", + "itemsPath": "value", + "parameters": { + "dataset": { + "value": "AccountNameFromSettings" + } + } + }, + "browse": { + "operationId": "ListFolder_V4", + "itemValuePath": "Path", + "itemTitlePath": "DisplayName", + "itemIsParent": "(IsFolder eq true)", + "itemFullTitlePath": "Path", + "itemsPath": "value", + "parameters": { + "dataset": { + "value": "AccountNameFromSettings" + }, + "id": { + "selectedItemValuePath": "Id" + } + } + } + }, + "type": "string" + }, + { + "name": "inferContentType", + "in": "query", + "description": "Infer content-type based on extension.", + "required": false, + "x-ms-summary": "Infer content type", + "x-ms-visibility": "advanced", + "type": "boolean", + "default": true + }, + { + "name": "queryParametersSingleEncoded", + "in": "query", + "required": false, + "x-ms-visibility": "internal", + "type": "boolean", + "default": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "format": "binary", + "description": "The content of the file.", + "type": "string", + "x-ms-summary": "File Content" + } + }, + "default": { + "description": "Operation Failed." + } + }, + "deprecated": true, + "x-ms-api-annotation": { + "status": "Production", + "family": "GetFileContentByPath", + "revision": 1 + } + } + } + }, + "definitions": { + "Object": { + "type": "object", + "properties": {} + }, + "BlobMetadata": { + "description": "Blob metadata", + "type": "object", + "properties": { + "Id": { + "description": "The unique id of the file or folder.", + "type": "string" + }, + "Name": { + "description": "The name of the file or folder.", + "type": "string" + }, + "DisplayName": { + "description": "The display name of the file or folder.", + "type": "string" + }, + "Path": { + "description": "The path of the file or folder.", + "type": "string" + }, + "LastModified": { + "format": "date-time", + "description": "The date and time the file or folder was last modified.", + "type": "string" + }, + "Size": { + "format": "int64", + "description": "The size of the file or folder.", + "type": "integer" + }, + "MediaType": { + "description": "The media type of the file or folder.", + "type": "string" + }, + "IsFolder": { + "description": "A boolean value (true, false) to indicate whether or not the blob is a folder.", + "type": "boolean" + }, + "ETag": { + "description": "The etag of the file or folder.", + "type": "string" + }, + "FileLocator": { + "description": "The filelocator of the file or folder.", + "type": "string" + }, + "LastModifiedBy": { + "format": "string", + "description": "The author of the last modification.", + "type": "string" + } + } + }, + "BlobMetadataResponse": { + "description": "Represents blob datasets metadata response", + "type": "object", + "properties": { + "Id": { + "description": "The unique id of the file or folder.", + "type": "string" + }, + "Name": { + "description": "The name of the file or folder.", + "type": "string" + }, + "DisplayName": { + "description": "The display name of the file or folder.", + "type": "string" + }, + "Path": { + "description": "The path of the file or folder.", + "type": "string" + }, + "LastModified": { + "format": "date-time", + "description": "The date and time the file or folder was last modified.", + "type": "string" + }, + "Size": { + "format": "int64", + "description": "The size of the file or folder.", + "type": "integer" + }, + "MediaType": { + "description": "The media type of the file or folder.", + "type": "string" + }, + "IsFolder": { + "description": "A boolean value (true, false) to indicate whether or not the blob is a folder.", + "type": "boolean" + }, + "ETag": { + "description": "The etag of the file or folder.", + "type": "string" + }, + "FileLocator": { + "description": "The filelocator of the file or folder.", + "type": "string" + } + } + }, + "BlobMetadataPage": { + "description": "Represents a page of blob metadata.", + "type": "object", + "properties": { + "value": { + "description": "Blob metadata collection.", + "type": "array", + "items": { + "$ref": "#/definitions/BlobMetadata" + }, + "readOnly": true + }, + "nextLink": { + "description": "An Url which can be used to retrieve the next page.", + "type": "string", + "x-ms-visibility": "advanced" + }, + "nextPageMarker": { + "description": "A marker which can be used to retrieve the next page.", + "type": "string", + "x-ms-summary": "Next page marker", + "x-ms-visibility": "advanced" + } + } + }, + "SharedAccessSignatureBlobPolicy": { + "description": "The set of parameters to generate a SAS link.", + "type": "object", + "properties": { + "GroupPolicyIdentifier": { + "description": "The string identifying a stored access policy. The Group policy parameters (e.g. Start time and End time) have precedence over input parameters mentioned in actions.", + "type": "string", + "x-ms-summary": "Group Policy Identifier", + "x-ms-visibility": "important", + "x-ms-dynamic-values": { + "operationId": "GetAccessPolicies", + "parameters": { + "path": { + "parameter": "path" + } + }, + "value-path": "GroupPolicyIdentifier" + } + }, + "Permissions": { + "description": "The permissions specified on the SAS (Values separated by comma).", + "default": "Read", + "enum": [ + "Read", + "Write", + "Add", + "Create", + "Delete", + "List", + "Read,Write", + "Read,Write,List", + "Read,Write,List,Delete" + ], + "type": "string", + "x-ms-summary": "Permissions", + "x-ms-visibility": "advanced" + }, + "StartTime": { + "format": "date-time", + "description": "The date and time at which the SAS becomes valid (example: '2017-11-01T15:30:00+00:00'). Default = now().", + "type": "string", + "x-ms-summary": "Start Time", + "x-ms-visibility": "advanced" + }, + "ExpiryTime": { + "format": "date-time", + "description": "The date and time after which the SAS is no longer valid (example: '2017-12-01T15:30:00+00:00'). Default = now() + 24h.", + "type": "string", + "x-ms-summary": "Expiry Time", + "x-ms-visibility": "advanced" + }, + "AccessProtocol": { + "description": "The allowed protocols (https only, or http and https). Null if you don't want to restrict protocol.", + "enum": [ + "HttpsOnly", + "HttpsOrHttp" + ], + "type": "string", + "x-ms-summary": "Shared Access Protocol", + "x-ms-visibility": "advanced" + }, + "IpAddressOrRange": { + "description": "The allowed IP address or IP address range. Null if you don't want to restrict based on IP address.", + "type": "string", + "x-ms-summary": "IP address or IP address range", + "x-ms-visibility": "advanced" + } + } + }, + "SharedAccessSignature": { + "description": "Shared access signature", + "type": "object", + "properties": { + "WebUrl": { + "format": "uri", + "description": "A URL to an object with access token.", + "type": "string", + "x-ms-summary": "Web Url" + } + } + }, + "StorageAccountList": { + "description": "List of storage account names", + "type": "object", + "properties": { + "value": { + "description": "List of storage account names", + "type": "array", + "items": { + "$ref": "#/definitions/StorageAccount" + } + } + } + }, + "StorageAccount": { + "description": "Storage account", + "type": "object", + "properties": { + "Name": { + "description": "The name of the storage account.", + "type": "string", + "x-ms-summary": "Storage Account name" + }, + "DisplayName": { + "description": "The display name of the storage account.", + "type": "string", + "x-ms-summary": "Storage Account display name" + } + } + }, + "DataSetsMetadata": { + "description": "Dataset metadata", + "type": "object", + "properties": { + "tabular": { + "$ref": "#/definitions/TabularDataSetsMetadata" + }, + "blob": { + "$ref": "#/definitions/BlobDataSetsMetadata" + } + } + }, + "TabularDataSetsMetadata": { + "description": "Tabular dataset metadata", + "type": "object", + "properties": { + "source": { + "description": "Dataset source", + "type": "string" + }, + "displayName": { + "description": "Dataset display name", + "type": "string" + }, + "urlEncoding": { + "description": "Dataset url encoding", + "type": "string" + }, + "tableDisplayName": { + "description": "Table display name", + "type": "string" + }, + "tablePluralName": { + "description": "Table plural display name", + "type": "string" + } + } + }, + "BlobDataSetsMetadata": { + "description": "Blob dataset metadata", + "type": "object", + "properties": { + "source": { + "description": "Blob dataset source", + "type": "string" + }, + "displayName": { + "description": "Blob dataset display name", + "type": "string" + }, + "urlEncoding": { + "description": "Blob dataset url encoding", + "type": "string" + } + } + } + }, + "x-ms-capabilities": { + "file-picker": { + "open": { + "operationId": "ListAllRootFolders_V4", + "parameters": { + "dataset": { + "parameter": "dataset" + } + } + }, + "browse": { + "operationId": "ListFolder_V4", + "parameters": { + "dataset": { + "parameter": "dataset" + }, + "id": { + "value-property": "Id" + } + } + }, + "value-collection": "value", + "value-title": "DisplayName", + "value-folder-property": "IsFolder", + "value-media-property": "MediaType" + }, + "testConnection": { + "operationId": "TestConnection", + "parameters": {} + } + }, + "x-ms-connector-metadata": [ + { + "propertyName": "Website", + "propertyValue": "https://azure.microsoft.com/services/storage/blobs/" + }, + { + "propertyName": "Privacy policy", + "propertyValue": "https://privacy.microsoft.com/" + }, + { + "propertyName": "Categories", + "propertyValue": "Productivity" + } + ] +} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/Samples/OpenApiDocument/docWithJsonSchema.yaml b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/Samples/OpenApiDocument/docWithJsonSchema.yaml new file mode 100644 index 000000000..984e5ce2b --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/Samples/OpenApiDocument/docWithJsonSchema.yaml @@ -0,0 +1,32 @@ +openapi: '3.0.1' +info: + title: Sample API with Schema Reference + version: 1.0.0 +paths: + /users/{userId}: + get: + summary: Get user by ID + parameters: + - name: userId + in: path + required: true + schema: + type: integer + responses: + '200': + description: Successful response + content: + application/json: + schema: + $ref: '#/components/schemas/User' +components: + schemas: + User: + type: object + properties: + id: + type: integer + username: + type: string + email: + type: string \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/Samples/OpenApiDocument/minifiedPetStore.yaml b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/Samples/OpenApiDocument/minifiedPetStore.yaml new file mode 100644 index 000000000..6ebfc23fc --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/Samples/OpenApiDocument/minifiedPetStore.yaml @@ -0,0 +1,22 @@ +openapi: 3.0.0 +info: + title: Pet Store with Referenceable Parameter + version: 1.0.0 +paths: + /pets: + get: + summary: Returns all pets + parameters: + - $ref: '#/components/parameters/LimitParameter' + responses: {} +components: + parameters: + LimitParameter: + name: limit + in: query + description: Limit the number of pets returned + required: false + schema: + type: integer + format: int32 + default: 10 diff --git a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/Samples/OpenApiDocument/petStore.yaml b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/Samples/OpenApiDocument/petStore.yaml index d0696cde5..6a9df318b 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/Samples/OpenApiDocument/petStore.yaml +++ b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/Samples/OpenApiDocument/petStore.yaml @@ -42,12 +42,12 @@ paths: schema: type: array items: - "$ref": '#/components/schemas/pet' + "$ref": '#/components/schemas/pet1' application/xml: schema: type: array items: - "$ref": '#/components/schemas/pet' + "$ref": '#/components/schemas/pet1' '4XX': description: unexpected client error @@ -77,7 +77,7 @@ paths: content: application/json: schema: - "$ref": '#/components/schemas/pet' + "$ref": '#/components/schemas/pet1' '4XX': description: unexpected client error content: @@ -108,10 +108,10 @@ paths: content: application/json: schema: - "$ref": '#/components/schemas/pet' + "$ref": '#/components/schemas/pet1' application/xml: schema: - "$ref": '#/components/schemas/pet' + "$ref": '#/components/schemas/pet1' '4XX': description: unexpected client error content: @@ -152,7 +152,7 @@ paths: "$ref": '#/components/schemas/errorModel' components: schemas: - pet: + pet1: type: object required: - id diff --git a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/Samples/OpenApiDocument/petStoreWithTagAndSecurity.yaml b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/Samples/OpenApiDocument/petStoreWithTagAndSecurity.yaml index ac0e3f1d2..528804491 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/Samples/OpenApiDocument/petStoreWithTagAndSecurity.yaml +++ b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/Samples/OpenApiDocument/petStoreWithTagAndSecurity.yaml @@ -45,12 +45,12 @@ paths: schema: type: array items: - "$ref": '#/components/schemas/pet' + "$ref": '#/components/schemas/pet1' application/xml: schema: type: array items: - "$ref": '#/components/schemas/pet' + "$ref": '#/components/schemas/pet1' '4XX': description: unexpected client error @@ -83,7 +83,7 @@ paths: content: application/json: schema: - "$ref": '#/components/schemas/pet' + "$ref": '#/components/schemas/pet1' '4XX': description: unexpected client error content: @@ -119,10 +119,10 @@ paths: content: application/json: schema: - "$ref": '#/components/schemas/pet' + "$ref": '#/components/schemas/pet1' application/xml: schema: - "$ref": '#/components/schemas/pet' + "$ref": '#/components/schemas/pet1' '4XX': description: unexpected client error content: @@ -163,7 +163,7 @@ paths: "$ref": '#/components/schemas/errorModel' components: schemas: - pet: + pet1: type: object required: - id diff --git a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/Samples/OpenApiParameter/parameterWithRef.yaml b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/Samples/OpenApiParameter/parameterWithRef.yaml new file mode 100644 index 000000000..63f38a94e --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/Samples/OpenApiParameter/parameterWithRef.yaml @@ -0,0 +1 @@ +"$ref": '#/components/parameters/tagsParameter' \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/Samples/OpenApiSchema/advancedSchemaWithReference.yaml b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/Samples/OpenApiSchema/advancedSchemaWithReference.yaml index 3624a32a3..3d9f0343b 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/Samples/OpenApiSchema/advancedSchemaWithReference.yaml +++ b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/Samples/OpenApiSchema/advancedSchemaWithReference.yaml @@ -1,5 +1,3 @@ -# https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#schemaObject -# Add required properties in the Open API document object to avoid errors openapi: 3.0.0 info: title: Simple Document @@ -19,7 +17,7 @@ components: required: - name - petType - Cat: ## "Cat" will be used as the discriminator value + Cat: description: A representation of a cat allOf: - $ref: '#/components/schemas/Pet' @@ -35,7 +33,7 @@ components: - aggressive required: - huntingSkill - Dog: ## "Dog" will be used as the discriminator value + Dog: description: A representation of a dog allOf: - $ref: '#/components/schemas/Pet' @@ -48,4 +46,4 @@ components: default: 0 minimum: 0 required: - - packSize \ No newline at end of file + - packSize diff --git a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/Samples/OpenApiWorkspace/TodoComponents.yaml b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/Samples/OpenApiWorkspace/TodoComponents.yaml index 8602c4f5a..f16b83884 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/Samples/OpenApiWorkspace/TodoComponents.yaml +++ b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/Samples/OpenApiWorkspace/TodoComponents.yaml @@ -22,4 +22,4 @@ components: type: object properties: id: - type:string \ No newline at end of file + type: string \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Attributes/DisplayAttributeTests.cs b/test/Microsoft.OpenApi.Tests/Attributes/DisplayAttributeTests.cs index f9bb8beff..182108260 100644 --- a/test/Microsoft.OpenApi.Tests/Attributes/DisplayAttributeTests.cs +++ b/test/Microsoft.OpenApi.Tests/Attributes/DisplayAttributeTests.cs @@ -31,12 +31,12 @@ public enum UserType public class DisplayAttributeTests { [Theory] - [InlineData(ApiLevel.Private,"private")] + [InlineData(ApiLevel.Private, "private")] [InlineData(ApiLevel.Public, "public")] [InlineData(ApiLevel.Corporate, "corporate")] public void GetDisplayNameExtensionShouldUseDisplayAttribute(ApiLevel apiLevel, string expected) { - Assert.Equal(expected, apiLevel.GetDisplayName()); + Assert.Equal(expected, apiLevel.GetDisplayName()); } [Theory] diff --git a/test/Microsoft.OpenApi.Tests/Expressions/RuntimeExpressionTests.cs b/test/Microsoft.OpenApi.Tests/Expressions/RuntimeExpressionTests.cs index a60e2698a..70c7dc90d 100644 --- a/test/Microsoft.OpenApi.Tests/Expressions/RuntimeExpressionTests.cs +++ b/test/Microsoft.OpenApi.Tests/Expressions/RuntimeExpressionTests.cs @@ -1,13 +1,13 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. +using System; +using System.Collections.Generic; +using System.Linq; using FluentAssertions; using Microsoft.OpenApi.Exceptions; using Microsoft.OpenApi.Expressions; using Microsoft.OpenApi.Properties; -using System; -using System.Collections.Generic; -using System.Linq; using Xunit; namespace Microsoft.OpenApi.Tests.Writers diff --git a/test/Microsoft.OpenApi.Tests/Extensions/OpenApiTypeMapperTests.cs b/test/Microsoft.OpenApi.Tests/Extensions/OpenApiTypeMapperTests.cs index 44a4bcdb4..deec23c4e 100644 --- a/test/Microsoft.OpenApi.Tests/Extensions/OpenApiTypeMapperTests.cs +++ b/test/Microsoft.OpenApi.Tests/Extensions/OpenApiTypeMapperTests.cs @@ -14,23 +14,48 @@ public class OpenApiTypeMapperTests { public static IEnumerable PrimitiveTypeData => new List { - new object[] { typeof(int), new OpenApiSchema { Type = "number", Format = "int32" } }, - new object[] { typeof(string), new OpenApiSchema { Type = "string" } }, - new object[] { typeof(double), new OpenApiSchema { Type = "number", Format = "double" } }, - new object[] { typeof(float?), new OpenApiSchema { Type = "number", Format = "float", Nullable = true } }, - new object[] { typeof(DateTimeOffset), new OpenApiSchema { Type = "string", Format = "date-time" } } + new object[] { typeof(int), new OpenApiSchema { Type = JsonSchemaType.Integer, Format = "int32" } }, + new object[] { typeof(decimal), new OpenApiSchema { Type = JsonSchemaType.Number, Format = "double" } }, + new object[] { typeof(decimal?), new OpenApiSchema { Type = JsonSchemaType.Number, Format = "double", Nullable = true } }, + new object[] { typeof(bool?), new OpenApiSchema { Type = JsonSchemaType.Boolean, Nullable = true } }, + new object[] { typeof(Guid), new OpenApiSchema { Type = JsonSchemaType.String, Format = "uuid" } }, + new object[] { typeof(Guid?), new OpenApiSchema { Type = JsonSchemaType.String, Format = "uuid", Nullable = true } }, + new object[] { typeof(uint), new OpenApiSchema { Type = JsonSchemaType.Integer, Format = "int32" } }, + new object[] { typeof(long), new OpenApiSchema { Type = JsonSchemaType.Integer, Format = "int64" } }, + new object[] { typeof(long?), new OpenApiSchema { Type = JsonSchemaType.Integer, Format = "int64", Nullable = true } }, + new object[] { typeof(ulong), new OpenApiSchema { Type = JsonSchemaType.Integer, Format = "int64" } }, + new object[] { typeof(string), new OpenApiSchema { Type = JsonSchemaType.String } }, + new object[] { typeof(double), new OpenApiSchema { Type = JsonSchemaType.Number, Format = "double" } }, + new object[] { typeof(float?), new OpenApiSchema { Type = JsonSchemaType.Number, Format = "float", Nullable = true } }, + new object[] { typeof(byte?), new OpenApiSchema { Type = JsonSchemaType.String, Format = "byte", Nullable = true } }, + new object[] { typeof(int?), new OpenApiSchema { Type = JsonSchemaType.Integer, Format = "int32", Nullable = true } }, + new object[] { typeof(uint?), new OpenApiSchema { Type = JsonSchemaType.Integer, Format = "int32", Nullable = true } }, + new object[] { typeof(DateTimeOffset?), new OpenApiSchema { Type = JsonSchemaType.String, Format = "date-time", Nullable = true } }, + new object[] { typeof(double?), new OpenApiSchema { Type = JsonSchemaType.Number, Format = "double", Nullable = true } }, + new object[] { typeof(char?), new OpenApiSchema { Type = JsonSchemaType.String, Nullable = true } }, + new object[] { typeof(DateTimeOffset), new OpenApiSchema { Type = JsonSchemaType.String, Format = "date-time" } } }; public static IEnumerable OpenApiDataTypes => new List { - new object[] { new OpenApiSchema { Type = "integer", Format = "int32"}, typeof(int) }, - new object[] { new OpenApiSchema { Type = "number", Format = "int32"}, typeof(int) }, - new object[] { new OpenApiSchema { Type = "integer", Format = null, Nullable = false}, typeof(int) }, - new object[] { new OpenApiSchema { Type = "integer", Format = null, Nullable = true}, typeof(int?) }, - new object[] { new OpenApiSchema { Type = "string" }, typeof(string) }, - new object[] { new OpenApiSchema { Type = "number", Format = "double" }, typeof(double) }, - new object[] { new OpenApiSchema { Type = "number", Format = "float", Nullable = true }, typeof(float?) }, - new object[] { new OpenApiSchema { Type = "string", Format = "date-time" }, typeof(DateTimeOffset) } + new object[] { new OpenApiSchema { Type = JsonSchemaType.Integer, Format = "int32", Nullable = false}, typeof(int) }, + new object[] { new OpenApiSchema { Type = JsonSchemaType.Integer, Format = "int32", Nullable = true}, typeof(int?) }, + new object[] { new OpenApiSchema { Type = JsonSchemaType.Integer, Format = "int64", Nullable = false}, typeof(long) }, + new object[] { new OpenApiSchema { Type = JsonSchemaType.Integer, Format = "int64", Nullable = true}, typeof(long?) }, + new object[] { new OpenApiSchema { Type = JsonSchemaType.Number, Format = "decimal"}, typeof(decimal) }, + new object[] { new OpenApiSchema { Type = JsonSchemaType.Integer, Format = null, Nullable = false}, typeof(long) }, + new object[] { new OpenApiSchema { Type = JsonSchemaType.Integer, Format = null, Nullable = true}, typeof(long?) }, + new object[] { new OpenApiSchema { Type = JsonSchemaType.Number, Format = null, Nullable = false}, typeof(double) }, + new object[] { new OpenApiSchema { Type = JsonSchemaType.Number, Format = null, Nullable = true}, typeof(double?) }, + new object[] { new OpenApiSchema { Type = JsonSchemaType.Number, Format = "decimal", Nullable = true}, typeof(decimal?) }, + new object[] { new OpenApiSchema { Type = JsonSchemaType.Number, Format = "double", Nullable = true}, typeof(double?) }, + new object[] { new OpenApiSchema { Type = JsonSchemaType.String, Format = "date-time", Nullable = true}, typeof(DateTimeOffset?) }, + new object[] { new OpenApiSchema { Type = JsonSchemaType.String, Format = "char", Nullable = true}, typeof(char?) }, + new object[] { new OpenApiSchema { Type = JsonSchemaType.String, Format = "uuid", Nullable = true}, typeof(Guid?) }, + new object[] { new OpenApiSchema { Type = JsonSchemaType.String }, typeof(string) }, + new object[] { new OpenApiSchema { Type = JsonSchemaType.Number, Format = "double" }, typeof(double) }, + new object[] { new OpenApiSchema { Type = JsonSchemaType.Number, Format = "float", Nullable = true }, typeof(float?) }, + new object[] { new OpenApiSchema { Type = JsonSchemaType.String, Format = "date-time" }, typeof(DateTimeOffset) } }; [Theory] diff --git a/test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj b/test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj index a02c52260..3bc829358 100644 --- a/test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj +++ b/test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj @@ -1,4 +1,4 @@ - + net8.0 false @@ -29,19 +29,27 @@ - Always + PreserveNewest - Always + PreserveNewest + + OpenApiCallbackReferenceTests.cs + + + + PreserveNewest + + + + PreserveNewest + + - - - - \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/MicrosoftExtensions/OpenApiDeprecationExtensionTests.cs b/test/Microsoft.OpenApi.Tests/MicrosoftExtensions/OpenApiDeprecationExtensionTests.cs index 8f0a77160..6849e5e9c 100644 --- a/test/Microsoft.OpenApi.Tests/MicrosoftExtensions/OpenApiDeprecationExtensionTests.cs +++ b/test/Microsoft.OpenApi.Tests/MicrosoftExtensions/OpenApiDeprecationExtensionTests.cs @@ -4,6 +4,7 @@ using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Writers; using Xunit; +using System.Text.Json.Nodes; namespace Microsoft.OpenApi.Tests.MicrosoftExtensions; @@ -72,12 +73,12 @@ public void WritesAllValues() [Fact] public void Parses() { - var oaiValue = new OpenApiObject + var oaiValue = new JsonObject { - { "date", new OpenApiDateTime(new(2023,05,04, 16, 0, 0, 0, 0, new(4, 0, 0)))}, - { "removalDate", new OpenApiDateTime(new(2023,05,04, 16, 0, 0, 0, 0, new(4, 0, 0)))}, - { "version", new OpenApiString("v1.0")}, - { "description", new OpenApiString("removing")} + { "date", new OpenApiAny(new DateTimeOffset(2023,05,04, 16, 0, 0, 0, 0, new(4, 0, 0))).Node}, + { "removalDate", new OpenApiAny(new DateTimeOffset(2023,05,04, 16, 0, 0, 0, 0, new(4, 0, 0))).Node}, + { "version", new OpenApiAny("v1.0").Node}, + { "description", new OpenApiAny("removing").Node} }; var value = OpenApiDeprecationExtension.Parse(oaiValue); Assert.NotNull(value); diff --git a/test/Microsoft.OpenApi.Tests/MicrosoftExtensions/OpenApiPagingExtensionsTests.cs b/test/Microsoft.OpenApi.Tests/MicrosoftExtensions/OpenApiPagingExtensionsTests.cs index 2eb362885..3d084908c 100644 --- a/test/Microsoft.OpenApi.Tests/MicrosoftExtensions/OpenApiPagingExtensionsTests.cs +++ b/test/Microsoft.OpenApi.Tests/MicrosoftExtensions/OpenApiPagingExtensionsTests.cs @@ -4,6 +4,7 @@ using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Writers; using Xunit; +using System.Text.Json.Nodes; namespace Microsoft.OpenApi.Tests.MicrosoftExtensions; @@ -74,11 +75,11 @@ public void WritesPagingInfo() public void ParsesPagingInfo() { // Arrange - var obj = new OpenApiObject + var obj = new JsonObject { - ["nextLinkName"] = new OpenApiString("@odata.nextLink"), - ["operationName"] = new OpenApiString("more"), - ["itemName"] = new OpenApiString("item"), + ["nextLinkName"] = new OpenApiAny("@odata.nextLink").Node, + ["operationName"] = new OpenApiAny("more").Node, + ["itemName"] = new OpenApiAny("item").Node, }; // Act diff --git a/test/Microsoft.OpenApi.Tests/MicrosoftExtensions/OpenApiPrimaryErrorMessageExtensionTests.cs b/test/Microsoft.OpenApi.Tests/MicrosoftExtensions/OpenApiPrimaryErrorMessageExtensionTests.cs index 9ea10df21..f7256f8e6 100644 --- a/test/Microsoft.OpenApi.Tests/MicrosoftExtensions/OpenApiPrimaryErrorMessageExtensionTests.cs +++ b/test/Microsoft.OpenApi.Tests/MicrosoftExtensions/OpenApiPrimaryErrorMessageExtensionTests.cs @@ -1,4 +1,4 @@ -// ------------------------------------------------------------ +// ------------------------------------------------------------ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. // ------------------------------------------------------------ @@ -47,7 +47,7 @@ public void WritesValue() public void ParsesValue() { // Arrange - var value = new OpenApiBoolean(true); + var value = true; // Act var extension = MicrosoftExtensions.OpenApiPrimaryErrorMessageExtension.Parse(value); diff --git a/test/Microsoft.OpenApi.Tests/MicrosoftExtensions/OpenApiReservedParameterExtensionTests.cs b/test/Microsoft.OpenApi.Tests/MicrosoftExtensions/OpenApiReservedParameterExtensionTests.cs index 0ebeea11a..4972f3230 100644 --- a/test/Microsoft.OpenApi.Tests/MicrosoftExtensions/OpenApiReservedParameterExtensionTests.cs +++ b/test/Microsoft.OpenApi.Tests/MicrosoftExtensions/OpenApiReservedParameterExtensionTests.cs @@ -4,6 +4,7 @@ using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Writers; using Xunit; +using System.Text.Json.Nodes; namespace Microsoft.OpenApi.Tests.MicrosoftExtensions; @@ -12,24 +13,12 @@ public class OpenApiReservedParameterExtensionTests [Fact] public void Parses() { - var oaiValue = new OpenApiBoolean(true); + var oaiValue = true; var value = OpenApiReservedParameterExtension.Parse(oaiValue); Assert.NotNull(value); Assert.True(value.IsReserved); } - [Fact] - public void DoesNotThrowExceptionIfValueIsNull() - { - var oaiValue = new OpenApiObject - { - ["foo"] = new OpenApiString("foo") - }; - - var value = OpenApiReservedParameterExtension.Parse(oaiValue); - Assert.Null(value); - } - [Fact] public void Serializes() { diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiCallbackTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiCallbackTests.cs index 9dd80c3da..ad2c9ffdb 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiCallbackTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiCallbackTests.cs @@ -6,6 +6,7 @@ using System.Threading.Tasks; using Microsoft.OpenApi.Expressions; using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Models.References; using Microsoft.OpenApi.Writers; using VerifyXunit; using Xunit; @@ -35,7 +36,7 @@ public class OpenApiCallbackTests { Schema = new() { - Type = "object" + Type = JsonSchemaType.Object } } } @@ -53,13 +54,10 @@ public class OpenApiCallbackTests } }; + public static OpenApiCallbackReference CallbackProxy = new(ReferencedCallback, "simpleHook"); + public static OpenApiCallback ReferencedCallback = new() { - Reference = new() - { - Type = ReferenceType.Callback, - Id = "simpleHook", - }, PathItems = { [RuntimeExpression.Build("$request.body#/url")] @@ -78,7 +76,7 @@ public class OpenApiCallbackTests { Schema = new() { - Type = "object" + Type = JsonSchemaType.Object } } } @@ -123,7 +121,7 @@ public async Task SerializeReferencedCallbackAsV3JsonWorksAsync(bool produceTers var writer = new OpenApiJsonWriter(outputStringWriter, new() { Terse = produceTerseOutput }); // Act - ReferencedCallback.SerializeAsV3(writer); + CallbackProxy.SerializeAsV3(writer); writer.Flush(); // Assert @@ -140,7 +138,7 @@ public async Task SerializeReferencedCallbackAsV3JsonWithoutReferenceWorksAsync( var writer = new OpenApiJsonWriter(outputStringWriter, new() { Terse = produceTerseOutput }); // Act - ReferencedCallback.SerializeAsV3WithoutReference(writer); + ReferencedCallback.SerializeAsV3(writer); writer.Flush(); // Assert diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiComponentsTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiComponentsTests.cs index b361d6c6c..a959edbf6 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiComponentsTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiComponentsTests.cs @@ -3,8 +3,10 @@ using System.Collections.Generic; using FluentAssertions; +using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Extensions; using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Models.References; using Xunit; namespace Microsoft.OpenApi.Tests.Models @@ -22,15 +24,15 @@ public class OpenApiComponentsTests { ["property2"] = new() { - Type = "integer" + Type = JsonSchemaType.Integer }, ["property3"] = new() { - Type = "string", + Type = JsonSchemaType.String, MaxLength = 15 } - }, - }, + } + } }, SecuritySchemes = new Dictionary { @@ -71,21 +73,9 @@ public class OpenApiComponentsTests { ["property2"] = new() { - Type = "integer" + Type = JsonSchemaType.Integer }, - ["property3"] = new() - { - Reference = new() - { - Type = ReferenceType.Schema, - Id = "schema2" - } - } - }, - Reference = new() - { - Type = ReferenceType.Schema, - Id = "schema1" + ["property3"] = new OpenApiSchemaReference("schema2", null) } }, ["schema2"] = new() @@ -94,7 +84,7 @@ public class OpenApiComponentsTests { ["property2"] = new() { - Type = "integer" + Type = JsonSchemaType.Integer } } }, @@ -146,20 +136,20 @@ public class OpenApiComponentsTests { ["schema1"] = new() { - Type = "string" + Type = JsonSchemaType.String }, ["schema2"] = null, ["schema3"] = null, ["schema4"] = new() { - Type = "string", + Type = JsonSchemaType.String, AllOf = new List { null, null, new() { - Type = "string" + Type = JsonSchemaType.String }, null, null @@ -172,25 +162,18 @@ public class OpenApiComponentsTests { Schemas = { - ["schema1"] = new() - { - Reference = new() - { - Type = ReferenceType.Schema, - Id = "schema2" - } - }, + ["schema1"] = new OpenApiSchemaReference("schema2", null), ["schema2"] = new() { - Type = "object", + Type = JsonSchemaType.Object, Properties = { ["property1"] = new() { - Type = "string" + Type = JsonSchemaType.String } } - }, + } } }; @@ -200,28 +183,23 @@ public class OpenApiComponentsTests { ["schema1"] = new() { - Type = "object", + Type = JsonSchemaType.Object, Properties = { ["property1"] = new() { - Type = "string" + Type = JsonSchemaType.String } - }, - Reference = new() - { - Type = ReferenceType.Schema, - Id = "schema1" } }, ["schema2"] = new() { - Type = "object", + Type = JsonSchemaType.Object, Properties = { ["property1"] = new() { - Type = "string" + Type = JsonSchemaType.String } } }, @@ -232,12 +210,64 @@ public class OpenApiComponentsTests { Schemas = { - ["schema1"] = new() + ["schema1"] = new OpenApiSchemaReference("schema1", null) + } + }; + + public static OpenApiComponents ComponentsWithPathItem = new OpenApiComponents + { + Schemas = new Dictionary() + { + ["schema1"] = new OpenApiSchema() { - Reference = new() + Properties = new Dictionary() { - Type = ReferenceType.Schema, - Id = "schema1" + ["property2"] = new OpenApiSchema() + { + Type = JsonSchemaType.Integer + }, + ["property3"] = new OpenApiSchemaReference("schema2", null) + } + }, + + ["schema2"] = new() + { + Properties = new Dictionary() + { + ["property2"] = new OpenApiSchema() + { + Type = JsonSchemaType.Integer + } + } + } + }, + PathItems = new Dictionary + { + ["/pets"] = new OpenApiPathItem + { + Operations = new Dictionary + { + [OperationType.Post] = new OpenApiOperation + { + RequestBody = new OpenApiRequestBody + { + Description = "Information about a new pet in the system", + Content = new Dictionary + { + ["application/json"] = new OpenApiMediaType + { + Schema = new OpenApiSchemaReference("schema1", null) + } + } + }, + Responses = new OpenApiResponses + { + ["200"] = new OpenApiResponse + { + Description = "Return a 200 status to indicate that the data was received successfully" + } + } + } } } } @@ -468,8 +498,7 @@ public void SerializeAdvancedComponentsWithReferenceAsYamlV3Works() public void SerializeBrokenComponentsAsJsonV3Works() { // Arrange - var expected = - """ + var expected = """ { "schemas": { "schema1": { @@ -535,6 +564,7 @@ public void SerializeBrokenComponentsAsYamlV3Works() [Fact] public void SerializeTopLevelReferencingComponentsAsYamlV3Works() { + // Arrange // Arrange var expected = """ @@ -558,17 +588,23 @@ public void SerializeTopLevelReferencingComponentsAsYamlV3Works() } [Fact] - public void SerializeTopLevelSelfReferencingComponentsAsYamlV3Works() + public void SerializeTopLevelSelfReferencingWithOtherPropertiesComponentsAsYamlV3Works() { // Arrange - var expected = - """ - schemas: - schema1: { } - """; + var expected = @"schemas: + schema1: + type: object + properties: + property1: + type: string + schema2: + type: object + properties: + property1: + type: string"; // Act - var actual = TopLevelSelfReferencingComponents.SerializeAsYaml(OpenApiSpecVersion.OpenApi3_0); + var actual = TopLevelSelfReferencingComponentsWithOtherProperties.SerializeAsYaml(OpenApiSpecVersion.OpenApi3_0); // Assert actual = actual.MakeLineBreaksEnvironmentNeutral(); @@ -577,26 +613,90 @@ public void SerializeTopLevelSelfReferencingComponentsAsYamlV3Works() } [Fact] - public void SerializeTopLevelSelfReferencingWithOtherPropertiesComponentsAsYamlV3Works() + public void SerializeComponentsWithPathItemsAsJsonWorks() { // Arrange - var expected = - """ - schemas: - schema1: - type: object - properties: - property1: - type: string - schema2: - type: object - properties: - property1: - type: string - """; + var expected = @"{ + ""pathItems"": { + ""/pets"": { + ""post"": { + ""requestBody"": { + ""description"": ""Information about a new pet in the system"", + ""content"": { + ""application/json"": { + ""schema"": { + ""$ref"": ""#/components/schemas/schema1"" + } + } + } + }, + ""responses"": { + ""200"": { + ""description"": ""Return a 200 status to indicate that the data was received successfully"" + } + } + } + } + }, + ""schemas"": { + ""schema1"": { + ""properties"": { + ""property2"": { + ""type"": ""integer"" + }, + ""property3"": { + ""$ref"": ""#/components/schemas/schema2"" + } + } + }, + ""schema2"": { + ""properties"": { + ""property2"": { + ""type"": ""integer"" + } + } + } + } +}"; + // Act + var actual = ComponentsWithPathItem.SerializeAsJson(OpenApiSpecVersion.OpenApi3_1); + + // Assert + actual = actual.MakeLineBreaksEnvironmentNeutral(); + expected = expected.MakeLineBreaksEnvironmentNeutral(); + actual.Should().Be(expected); + } + + [Fact] + public void SerializeComponentsWithPathItemsAsYamlWorks() + { + // Arrange + var expected = @"pathItems: + /pets: + post: + requestBody: + description: Information about a new pet in the system + content: + application/json: + schema: + $ref: '#/components/schemas/schema1' + responses: + '200': + description: Return a 200 status to indicate that the data was received successfully +schemas: + schema1: + properties: + property2: + type: integer + property3: + $ref: '#/components/schemas/schema2' + schema2: + properties: + property2: + type: integer"; // Act - var actual = TopLevelSelfReferencingComponentsWithOtherProperties.SerializeAsYaml(OpenApiSpecVersion.OpenApi3_0); + var actual = ComponentsWithPathItem.SerializeAsYaml(OpenApiSpecVersion.OpenApi3_1); // Assert actual = actual.MakeLineBreaksEnvironmentNeutral(); diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiContactTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiContactTests.cs index b33029261..06c00fec4 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiContactTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiContactTests.cs @@ -23,7 +23,7 @@ public class OpenApiContactTests Email = "support@example.com", Extensions = new Dictionary { - {"x-internal-id", new OpenApiInteger(42)} + {"x-internal-id", new OpenApiAny(42)} } }; diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.SerializeAdvancedDocumentAsV2JsonWorksAsync_produceTerseOutput=False.verified.txt b/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.SerializeAdvancedDocumentAsV2JsonWorksAsync_produceTerseOutput=False.verified.txt index bd0084a2b..09804c666 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.SerializeAdvancedDocumentAsV2JsonWorksAsync_produceTerseOutput=False.verified.txt +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.SerializeAdvancedDocumentAsV2JsonWorksAsync_produceTerseOutput=False.verified.txt @@ -55,15 +55,15 @@ "schema": { "type": "array", "items": { + "type": "object", "required": [ "id", "name" ], - "type": "object", "properties": { "id": { - "format": "int64", - "type": "integer" + "type": "integer", + "format": "int64" }, "name": { "type": "string" @@ -78,15 +78,15 @@ "4XX": { "description": "unexpected client error", "schema": { + "type": "object", "required": [ "code", "message" ], - "type": "object", "properties": { "code": { - "format": "int32", - "type": "integer" + "type": "integer", + "format": "int32" }, "message": { "type": "string" @@ -97,15 +97,15 @@ "5XX": { "description": "unexpected server error", "schema": { + "type": "object", "required": [ "code", "message" ], - "type": "object", "properties": { "code": { - "format": "int32", - "type": "integer" + "type": "integer", + "format": "int32" }, "message": { "type": "string" @@ -132,14 +132,14 @@ "description": "Pet to add to the store", "required": true, "schema": { + "type": "object", "required": [ "name" ], - "type": "object", "properties": { "id": { - "format": "int64", - "type": "integer" + "type": "integer", + "format": "int64" }, "name": { "type": "string" @@ -155,15 +155,15 @@ "200": { "description": "pet response", "schema": { + "type": "object", "required": [ "id", "name" ], - "type": "object", "properties": { "id": { - "format": "int64", - "type": "integer" + "type": "integer", + "format": "int64" }, "name": { "type": "string" @@ -177,15 +177,15 @@ "4XX": { "description": "unexpected client error", "schema": { + "type": "object", "required": [ "code", "message" ], - "type": "object", "properties": { "code": { - "format": "int32", - "type": "integer" + "type": "integer", + "format": "int32" }, "message": { "type": "string" @@ -196,15 +196,15 @@ "5XX": { "description": "unexpected server error", "schema": { + "type": "object", "required": [ "code", "message" ], - "type": "object", "properties": { "code": { - "format": "int32", - "type": "integer" + "type": "integer", + "format": "int32" }, "message": { "type": "string" @@ -238,15 +238,15 @@ "200": { "description": "pet response", "schema": { + "type": "object", "required": [ "id", "name" ], - "type": "object", "properties": { "id": { - "format": "int64", - "type": "integer" + "type": "integer", + "format": "int64" }, "name": { "type": "string" @@ -260,15 +260,15 @@ "4XX": { "description": "unexpected client error", "schema": { + "type": "object", "required": [ "code", "message" ], - "type": "object", "properties": { "code": { - "format": "int32", - "type": "integer" + "type": "integer", + "format": "int32" }, "message": { "type": "string" @@ -279,15 +279,15 @@ "5XX": { "description": "unexpected server error", "schema": { + "type": "object", "required": [ "code", "message" ], - "type": "object", "properties": { "code": { - "format": "int32", - "type": "integer" + "type": "integer", + "format": "int32" }, "message": { "type": "string" @@ -320,15 +320,15 @@ "4XX": { "description": "unexpected client error", "schema": { + "type": "object", "required": [ "code", "message" ], - "type": "object", "properties": { "code": { - "format": "int32", - "type": "integer" + "type": "integer", + "format": "int32" }, "message": { "type": "string" @@ -339,15 +339,15 @@ "5XX": { "description": "unexpected server error", "schema": { + "type": "object", "required": [ "code", "message" ], - "type": "object", "properties": { "code": { - "format": "int32", - "type": "integer" + "type": "integer", + "format": "int32" }, "message": { "type": "string" @@ -361,15 +361,15 @@ }, "definitions": { "pet": { + "type": "object", "required": [ "id", "name" ], - "type": "object", "properties": { "id": { - "format": "int64", - "type": "integer" + "type": "integer", + "format": "int64" }, "name": { "type": "string" @@ -380,14 +380,14 @@ } }, "newPet": { + "type": "object", "required": [ "name" ], - "type": "object", "properties": { "id": { - "format": "int64", - "type": "integer" + "type": "integer", + "format": "int64" }, "name": { "type": "string" @@ -398,15 +398,15 @@ } }, "errorModel": { + "type": "object", "required": [ "code", "message" ], - "type": "object", "properties": { "code": { - "format": "int32", - "type": "integer" + "type": "integer", + "format": "int32" }, "message": { "type": "string" diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.SerializeAdvancedDocumentAsV2JsonWorksAsync_produceTerseOutput=True.verified.txt b/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.SerializeAdvancedDocumentAsV2JsonWorksAsync_produceTerseOutput=True.verified.txt index b82c2f263..0cd09732e 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.SerializeAdvancedDocumentAsV2JsonWorksAsync_produceTerseOutput=True.verified.txt +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.SerializeAdvancedDocumentAsV2JsonWorksAsync_produceTerseOutput=True.verified.txt @@ -1 +1 @@ -{"swagger":"2.0","info":{"title":"Swagger Petstore (Simple)","description":"A sample API that uses a petstore as an example to demonstrate features in the swagger-2.0 specification","termsOfService":"http://helloreverb.com/terms/","contact":{"name":"Swagger API team","url":"http://swagger.io","email":"foo@example.com"},"license":{"name":"MIT","url":"http://opensource.org/licenses/MIT"},"version":"1.0.0"},"host":"petstore.swagger.io","basePath":"/api","schemes":["http"],"paths":{"/pets":{"get":{"description":"Returns all pets from the system that the user has access to","operationId":"findPets","produces":["application/json","application/xml","text/html"],"parameters":[{"in":"query","name":"tags","description":"tags to filter by","type":"array","items":{"type":"string"},"collectionFormat":"multi"},{"in":"query","name":"limit","description":"maximum number of results to return","type":"integer","format":"int32"}],"responses":{"200":{"description":"pet response","schema":{"type":"array","items":{"required":["id","name"],"type":"object","properties":{"id":{"format":"int64","type":"integer"},"name":{"type":"string"},"tag":{"type":"string"}}}}},"4XX":{"description":"unexpected client error","schema":{"required":["code","message"],"type":"object","properties":{"code":{"format":"int32","type":"integer"},"message":{"type":"string"}}}},"5XX":{"description":"unexpected server error","schema":{"required":["code","message"],"type":"object","properties":{"code":{"format":"int32","type":"integer"},"message":{"type":"string"}}}}}},"post":{"description":"Creates a new pet in the store. Duplicates are allowed","operationId":"addPet","consumes":["application/json"],"produces":["application/json","text/html"],"parameters":[{"in":"body","name":"body","description":"Pet to add to the store","required":true,"schema":{"required":["name"],"type":"object","properties":{"id":{"format":"int64","type":"integer"},"name":{"type":"string"},"tag":{"type":"string"}}}}],"responses":{"200":{"description":"pet response","schema":{"required":["id","name"],"type":"object","properties":{"id":{"format":"int64","type":"integer"},"name":{"type":"string"},"tag":{"type":"string"}}}},"4XX":{"description":"unexpected client error","schema":{"required":["code","message"],"type":"object","properties":{"code":{"format":"int32","type":"integer"},"message":{"type":"string"}}}},"5XX":{"description":"unexpected server error","schema":{"required":["code","message"],"type":"object","properties":{"code":{"format":"int32","type":"integer"},"message":{"type":"string"}}}}}}},"/pets/{id}":{"get":{"description":"Returns a user based on a single ID, if the user does not have access to the pet","operationId":"findPetById","produces":["application/json","application/xml","text/html"],"parameters":[{"in":"path","name":"id","description":"ID of pet to fetch","required":true,"type":"integer","format":"int64"}],"responses":{"200":{"description":"pet response","schema":{"required":["id","name"],"type":"object","properties":{"id":{"format":"int64","type":"integer"},"name":{"type":"string"},"tag":{"type":"string"}}}},"4XX":{"description":"unexpected client error","schema":{"required":["code","message"],"type":"object","properties":{"code":{"format":"int32","type":"integer"},"message":{"type":"string"}}}},"5XX":{"description":"unexpected server error","schema":{"required":["code","message"],"type":"object","properties":{"code":{"format":"int32","type":"integer"},"message":{"type":"string"}}}}}},"delete":{"description":"deletes a single pet based on the ID supplied","operationId":"deletePet","produces":["text/html"],"parameters":[{"in":"path","name":"id","description":"ID of pet to delete","required":true,"type":"integer","format":"int64"}],"responses":{"204":{"description":"pet deleted"},"4XX":{"description":"unexpected client error","schema":{"required":["code","message"],"type":"object","properties":{"code":{"format":"int32","type":"integer"},"message":{"type":"string"}}}},"5XX":{"description":"unexpected server error","schema":{"required":["code","message"],"type":"object","properties":{"code":{"format":"int32","type":"integer"},"message":{"type":"string"}}}}}}}},"definitions":{"pet":{"required":["id","name"],"type":"object","properties":{"id":{"format":"int64","type":"integer"},"name":{"type":"string"},"tag":{"type":"string"}}},"newPet":{"required":["name"],"type":"object","properties":{"id":{"format":"int64","type":"integer"},"name":{"type":"string"},"tag":{"type":"string"}}},"errorModel":{"required":["code","message"],"type":"object","properties":{"code":{"format":"int32","type":"integer"},"message":{"type":"string"}}}}} \ No newline at end of file +{"swagger":"2.0","info":{"title":"Swagger Petstore (Simple)","description":"A sample API that uses a petstore as an example to demonstrate features in the swagger-2.0 specification","termsOfService":"http://helloreverb.com/terms/","contact":{"name":"Swagger API team","url":"http://swagger.io","email":"foo@example.com"},"license":{"name":"MIT","url":"http://opensource.org/licenses/MIT"},"version":"1.0.0"},"host":"petstore.swagger.io","basePath":"/api","schemes":["http"],"paths":{"/pets":{"get":{"description":"Returns all pets from the system that the user has access to","operationId":"findPets","produces":["application/json","application/xml","text/html"],"parameters":[{"in":"query","name":"tags","description":"tags to filter by","type":"array","items":{"type":"string"},"collectionFormat":"multi"},{"in":"query","name":"limit","description":"maximum number of results to return","type":"integer","format":"int32"}],"responses":{"200":{"description":"pet response","schema":{"type":"array","items":{"type":"object","required":["id","name"],"properties":{"id":{"type":"integer","format":"int64"},"name":{"type":"string"},"tag":{"type":"string"}}}}},"4XX":{"description":"unexpected client error","schema":{"type":"object","required":["code","message"],"properties":{"code":{"type":"integer","format":"int32"},"message":{"type":"string"}}}},"5XX":{"description":"unexpected server error","schema":{"type":"object","required":["code","message"],"properties":{"code":{"type":"integer","format":"int32"},"message":{"type":"string"}}}}}},"post":{"description":"Creates a new pet in the store. Duplicates are allowed","operationId":"addPet","consumes":["application/json"],"produces":["application/json","text/html"],"parameters":[{"in":"body","name":"body","description":"Pet to add to the store","required":true,"schema":{"type":"object","required":["name"],"properties":{"id":{"type":"integer","format":"int64"},"name":{"type":"string"},"tag":{"type":"string"}}}}],"responses":{"200":{"description":"pet response","schema":{"type":"object","required":["id","name"],"properties":{"id":{"type":"integer","format":"int64"},"name":{"type":"string"},"tag":{"type":"string"}}}},"4XX":{"description":"unexpected client error","schema":{"type":"object","required":["code","message"],"properties":{"code":{"type":"integer","format":"int32"},"message":{"type":"string"}}}},"5XX":{"description":"unexpected server error","schema":{"type":"object","required":["code","message"],"properties":{"code":{"type":"integer","format":"int32"},"message":{"type":"string"}}}}}}},"/pets/{id}":{"get":{"description":"Returns a user based on a single ID, if the user does not have access to the pet","operationId":"findPetById","produces":["application/json","application/xml","text/html"],"parameters":[{"in":"path","name":"id","description":"ID of pet to fetch","required":true,"type":"integer","format":"int64"}],"responses":{"200":{"description":"pet response","schema":{"type":"object","required":["id","name"],"properties":{"id":{"type":"integer","format":"int64"},"name":{"type":"string"},"tag":{"type":"string"}}}},"4XX":{"description":"unexpected client error","schema":{"type":"object","required":["code","message"],"properties":{"code":{"type":"integer","format":"int32"},"message":{"type":"string"}}}},"5XX":{"description":"unexpected server error","schema":{"type":"object","required":["code","message"],"properties":{"code":{"type":"integer","format":"int32"},"message":{"type":"string"}}}}}},"delete":{"description":"deletes a single pet based on the ID supplied","operationId":"deletePet","produces":["text/html"],"parameters":[{"in":"path","name":"id","description":"ID of pet to delete","required":true,"type":"integer","format":"int64"}],"responses":{"204":{"description":"pet deleted"},"4XX":{"description":"unexpected client error","schema":{"type":"object","required":["code","message"],"properties":{"code":{"type":"integer","format":"int32"},"message":{"type":"string"}}}},"5XX":{"description":"unexpected server error","schema":{"type":"object","required":["code","message"],"properties":{"code":{"type":"integer","format":"int32"},"message":{"type":"string"}}}}}}}},"definitions":{"pet":{"type":"object","required":["id","name"],"properties":{"id":{"type":"integer","format":"int64"},"name":{"type":"string"},"tag":{"type":"string"}}},"newPet":{"type":"object","required":["name"],"properties":{"id":{"type":"integer","format":"int64"},"name":{"type":"string"},"tag":{"type":"string"}}},"errorModel":{"type":"object","required":["code","message"],"properties":{"code":{"type":"integer","format":"int32"},"message":{"type":"string"}}}}} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.SerializeAdvancedDocumentAsV2JsonWorks_produceTerseOutput=True.verified.txt b/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.SerializeAdvancedDocumentAsV2JsonWorks_produceTerseOutput=True.verified.txt new file mode 100644 index 000000000..0248156d9 --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.SerializeAdvancedDocumentAsV2JsonWorks_produceTerseOutput=True.verified.txt @@ -0,0 +1 @@ +{"swagger":"2.0","info":{"title":"Swagger Petstore (Simple)","description":"A sample API that uses a petstore as an example to demonstrate features in the swagger-2.0 specification","termsOfService":"http://helloreverb.com/terms/","contact":{"name":"Swagger API team","url":"http://swagger.io","email":"foo@example.com"},"license":{"name":"MIT","url":"http://opensource.org/licenses/MIT"},"version":"1.0.0"},"host":"petstore.swagger.io","basePath":"/api","schemes":["http"],"paths":{"/pets":{"get":{"description":"Returns all pets from the system that the user has access to","operationId":"findPets","produces":["application/json","application/xml","text/html"],"parameters":[{"in":"query","name":"tags","description":"tags to filter by","type":"array","items":{"type":"string"},"collectionFormat":"multi"},{"in":"query","name":"limit","description":"maximum number of results to return","type":"integer","format":"int32"}],"responses":{"200":{"description":"pet response","schema":{"type":"array","items":{"type":"object","required":["id","name"],"properties":{"id":{"type":"integer","format":"int64"},"name":{"type":"string"},"tag":{"type":"string"}}}}},"4XX":{"description":"unexpected client error","schema":{"type":"object","required":["code","message"],"properties":{"code":{"type":"integer","format":"int32"},"message":{"type":"string"}}}},"5XX":{"description":"unexpected server error","schema":{"type":"object","required":["code","message"],"properties":{"code":{"type":"integer","format":"int32"},"message":{"type":"string"}}}}}},"post":{"description":"Creates a new pet in the store. Duplicates are allowed","operationId":"addPet","consumes":["application/json"],"produces":["application/json","text/html"],"parameters":[{"in":"body","name":"body","description":"Pet to add to the store","required":true,"schema":{"type":"object","required":["name"],"properties":{"id":{"type":"integer","format":"int64"},"name":{"type":"string"},"tag":{"type":"string"}}}}],"responses":{"200":{"description":"pet response","schema":{"type":"object","required":["id","name"],"properties":{"id":{"type":"integer","format":"int64"},"name":{"type":"string"},"tag":{"type":"string"}}}},"4XX":{"description":"unexpected client error","schema":{"type":"object","required":["code","message"],"properties":{"code":{"type":"integer","format":"int32"},"message":{"type":"string"}}}},"5XX":{"description":"unexpected server error","schema":{"type":"object","required":["code","message"],"properties":{"code":{"type":"integer","format":"int32"},"message":{"type":"string"}}}}}}},"/pets/{id}":{"get":{"description":"Returns a user based on a single ID, if the user does not have access to the pet","operationId":"findPetById","produces":["application/json","application/xml","text/html"],"parameters":[{"in":"path","name":"id","description":"ID of pet to fetch","required":true,"type":"integer","format":"int64"}],"responses":{"200":{"description":"pet response","schema":{"type":"object","required":["id","name"],"properties":{"id":{"type":"integer","format":"int64"},"name":{"type":"string"},"tag":{"type":"string"}}}},"4XX":{"description":"unexpected client error","schema":{"type":"object","required":["code","message"],"properties":{"code":{"type":"integer","format":"int32"},"message":{"type":"string"}}}},"5XX":{"description":"unexpected server error","schema":{"type":"object","required":["code","message"],"properties":{"code":{"type":"integer","format":"int32"},"message":{"type":"string"}}}}}},"delete":{"description":"deletes a single pet based on the ID supplied","operationId":"deletePet","produces":["text/html"],"parameters":[{"in":"path","name":"id","description":"ID of pet to delete","required":true,"type":"integer","format":"int64"}],"responses":{"204":{"description":"pet deleted"},"4XX":{"description":"unexpected client error","schema":{"type":"object","required":["code","message"],"properties":{"code":{"type":"integer","format":"int32"},"message":{"type":"string"}}}},"5XX":{"description":"unexpected server error","schema":{"type":"object","required":["code","message"],"properties":{"code":{"type":"integer","format":"int32"},"message":{"type":"string"}}}}}}}},"definitions":{"pet":{"type":"object","required":["id","name"],"properties":{"id":{"type":"integer","format":"int64"},"name":{"type":"string"},"tag":{"type":"string"}}},"newPet":{"type":"object","required":["name"],"properties":{"id":{"type":"integer","format":"int64"},"name":{"type":"string"},"tag":{"type":"string"}}},"errorModel":{"type":"object","required":["code","message"],"properties":{"code":{"type":"integer","format":"int32"},"message":{"type":"string"}}}}} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.SerializeAdvancedDocumentWithReferenceAsV2JsonWorksAsync_produceTerseOutput=False.verified.txt b/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.SerializeAdvancedDocumentWithReferenceAsV2JsonWorksAsync_produceTerseOutput=False.verified.txt index d66a8f4c2..09804c666 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.SerializeAdvancedDocumentWithReferenceAsV2JsonWorksAsync_produceTerseOutput=False.verified.txt +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.SerializeAdvancedDocumentWithReferenceAsV2JsonWorksAsync_produceTerseOutput=False.verified.txt @@ -55,20 +55,62 @@ "schema": { "type": "array", "items": { - "$ref": "#/definitions/pet" + "type": "object", + "required": [ + "id", + "name" + ], + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + }, + "tag": { + "type": "string" + } + } } } }, "4XX": { "description": "unexpected client error", "schema": { - "$ref": "#/definitions/errorModel" + "type": "object", + "required": [ + "code", + "message" + ], + "properties": { + "code": { + "type": "integer", + "format": "int32" + }, + "message": { + "type": "string" + } + } } }, "5XX": { "description": "unexpected server error", "schema": { - "$ref": "#/definitions/errorModel" + "type": "object", + "required": [ + "code", + "message" + ], + "properties": { + "code": { + "type": "integer", + "format": "int32" + }, + "message": { + "type": "string" + } + } } } } @@ -90,7 +132,22 @@ "description": "Pet to add to the store", "required": true, "schema": { - "$ref": "#/definitions/newPet" + "type": "object", + "required": [ + "name" + ], + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + }, + "tag": { + "type": "string" + } + } } } ], @@ -98,19 +155,61 @@ "200": { "description": "pet response", "schema": { - "$ref": "#/definitions/pet" + "type": "object", + "required": [ + "id", + "name" + ], + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + }, + "tag": { + "type": "string" + } + } } }, "4XX": { "description": "unexpected client error", "schema": { - "$ref": "#/definitions/errorModel" + "type": "object", + "required": [ + "code", + "message" + ], + "properties": { + "code": { + "type": "integer", + "format": "int32" + }, + "message": { + "type": "string" + } + } } }, "5XX": { "description": "unexpected server error", "schema": { - "$ref": "#/definitions/errorModel" + "type": "object", + "required": [ + "code", + "message" + ], + "properties": { + "code": { + "type": "integer", + "format": "int32" + }, + "message": { + "type": "string" + } + } } } } @@ -139,19 +238,61 @@ "200": { "description": "pet response", "schema": { - "$ref": "#/definitions/pet" + "type": "object", + "required": [ + "id", + "name" + ], + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + }, + "tag": { + "type": "string" + } + } } }, "4XX": { "description": "unexpected client error", "schema": { - "$ref": "#/definitions/errorModel" + "type": "object", + "required": [ + "code", + "message" + ], + "properties": { + "code": { + "type": "integer", + "format": "int32" + }, + "message": { + "type": "string" + } + } } }, "5XX": { "description": "unexpected server error", "schema": { - "$ref": "#/definitions/errorModel" + "type": "object", + "required": [ + "code", + "message" + ], + "properties": { + "code": { + "type": "integer", + "format": "int32" + }, + "message": { + "type": "string" + } + } } } } @@ -179,13 +320,39 @@ "4XX": { "description": "unexpected client error", "schema": { - "$ref": "#/definitions/errorModel" + "type": "object", + "required": [ + "code", + "message" + ], + "properties": { + "code": { + "type": "integer", + "format": "int32" + }, + "message": { + "type": "string" + } + } } }, "5XX": { "description": "unexpected server error", "schema": { - "$ref": "#/definitions/errorModel" + "type": "object", + "required": [ + "code", + "message" + ], + "properties": { + "code": { + "type": "integer", + "format": "int32" + }, + "message": { + "type": "string" + } + } } } } @@ -194,15 +361,15 @@ }, "definitions": { "pet": { + "type": "object", "required": [ "id", "name" ], - "type": "object", "properties": { "id": { - "format": "int64", - "type": "integer" + "type": "integer", + "format": "int64" }, "name": { "type": "string" @@ -213,14 +380,14 @@ } }, "newPet": { + "type": "object", "required": [ "name" ], - "type": "object", "properties": { "id": { - "format": "int64", - "type": "integer" + "type": "integer", + "format": "int64" }, "name": { "type": "string" @@ -231,15 +398,15 @@ } }, "errorModel": { + "type": "object", "required": [ "code", "message" ], - "type": "object", "properties": { "code": { - "format": "int32", - "type": "integer" + "type": "integer", + "format": "int32" }, "message": { "type": "string" diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.SerializeAdvancedDocumentWithReferenceAsV2JsonWorksAsync_produceTerseOutput=True.verified.txt b/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.SerializeAdvancedDocumentWithReferenceAsV2JsonWorksAsync_produceTerseOutput=True.verified.txt index 38ff58647..0cd09732e 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.SerializeAdvancedDocumentWithReferenceAsV2JsonWorksAsync_produceTerseOutput=True.verified.txt +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.SerializeAdvancedDocumentWithReferenceAsV2JsonWorksAsync_produceTerseOutput=True.verified.txt @@ -1 +1 @@ -{"swagger":"2.0","info":{"title":"Swagger Petstore (Simple)","description":"A sample API that uses a petstore as an example to demonstrate features in the swagger-2.0 specification","termsOfService":"http://helloreverb.com/terms/","contact":{"name":"Swagger API team","url":"http://swagger.io","email":"foo@example.com"},"license":{"name":"MIT","url":"http://opensource.org/licenses/MIT"},"version":"1.0.0"},"host":"petstore.swagger.io","basePath":"/api","schemes":["http"],"paths":{"/pets":{"get":{"description":"Returns all pets from the system that the user has access to","operationId":"findPets","produces":["application/json","application/xml","text/html"],"parameters":[{"in":"query","name":"tags","description":"tags to filter by","type":"array","items":{"type":"string"},"collectionFormat":"multi"},{"in":"query","name":"limit","description":"maximum number of results to return","type":"integer","format":"int32"}],"responses":{"200":{"description":"pet response","schema":{"type":"array","items":{"$ref":"#/definitions/pet"}}},"4XX":{"description":"unexpected client error","schema":{"$ref":"#/definitions/errorModel"}},"5XX":{"description":"unexpected server error","schema":{"$ref":"#/definitions/errorModel"}}}},"post":{"description":"Creates a new pet in the store. Duplicates are allowed","operationId":"addPet","consumes":["application/json"],"produces":["application/json","text/html"],"parameters":[{"in":"body","name":"body","description":"Pet to add to the store","required":true,"schema":{"$ref":"#/definitions/newPet"}}],"responses":{"200":{"description":"pet response","schema":{"$ref":"#/definitions/pet"}},"4XX":{"description":"unexpected client error","schema":{"$ref":"#/definitions/errorModel"}},"5XX":{"description":"unexpected server error","schema":{"$ref":"#/definitions/errorModel"}}}}},"/pets/{id}":{"get":{"description":"Returns a user based on a single ID, if the user does not have access to the pet","operationId":"findPetById","produces":["application/json","application/xml","text/html"],"parameters":[{"in":"path","name":"id","description":"ID of pet to fetch","required":true,"type":"integer","format":"int64"}],"responses":{"200":{"description":"pet response","schema":{"$ref":"#/definitions/pet"}},"4XX":{"description":"unexpected client error","schema":{"$ref":"#/definitions/errorModel"}},"5XX":{"description":"unexpected server error","schema":{"$ref":"#/definitions/errorModel"}}}},"delete":{"description":"deletes a single pet based on the ID supplied","operationId":"deletePet","produces":["text/html"],"parameters":[{"in":"path","name":"id","description":"ID of pet to delete","required":true,"type":"integer","format":"int64"}],"responses":{"204":{"description":"pet deleted"},"4XX":{"description":"unexpected client error","schema":{"$ref":"#/definitions/errorModel"}},"5XX":{"description":"unexpected server error","schema":{"$ref":"#/definitions/errorModel"}}}}}},"definitions":{"pet":{"required":["id","name"],"type":"object","properties":{"id":{"format":"int64","type":"integer"},"name":{"type":"string"},"tag":{"type":"string"}}},"newPet":{"required":["name"],"type":"object","properties":{"id":{"format":"int64","type":"integer"},"name":{"type":"string"},"tag":{"type":"string"}}},"errorModel":{"required":["code","message"],"type":"object","properties":{"code":{"format":"int32","type":"integer"},"message":{"type":"string"}}}}} \ No newline at end of file +{"swagger":"2.0","info":{"title":"Swagger Petstore (Simple)","description":"A sample API that uses a petstore as an example to demonstrate features in the swagger-2.0 specification","termsOfService":"http://helloreverb.com/terms/","contact":{"name":"Swagger API team","url":"http://swagger.io","email":"foo@example.com"},"license":{"name":"MIT","url":"http://opensource.org/licenses/MIT"},"version":"1.0.0"},"host":"petstore.swagger.io","basePath":"/api","schemes":["http"],"paths":{"/pets":{"get":{"description":"Returns all pets from the system that the user has access to","operationId":"findPets","produces":["application/json","application/xml","text/html"],"parameters":[{"in":"query","name":"tags","description":"tags to filter by","type":"array","items":{"type":"string"},"collectionFormat":"multi"},{"in":"query","name":"limit","description":"maximum number of results to return","type":"integer","format":"int32"}],"responses":{"200":{"description":"pet response","schema":{"type":"array","items":{"type":"object","required":["id","name"],"properties":{"id":{"type":"integer","format":"int64"},"name":{"type":"string"},"tag":{"type":"string"}}}}},"4XX":{"description":"unexpected client error","schema":{"type":"object","required":["code","message"],"properties":{"code":{"type":"integer","format":"int32"},"message":{"type":"string"}}}},"5XX":{"description":"unexpected server error","schema":{"type":"object","required":["code","message"],"properties":{"code":{"type":"integer","format":"int32"},"message":{"type":"string"}}}}}},"post":{"description":"Creates a new pet in the store. Duplicates are allowed","operationId":"addPet","consumes":["application/json"],"produces":["application/json","text/html"],"parameters":[{"in":"body","name":"body","description":"Pet to add to the store","required":true,"schema":{"type":"object","required":["name"],"properties":{"id":{"type":"integer","format":"int64"},"name":{"type":"string"},"tag":{"type":"string"}}}}],"responses":{"200":{"description":"pet response","schema":{"type":"object","required":["id","name"],"properties":{"id":{"type":"integer","format":"int64"},"name":{"type":"string"},"tag":{"type":"string"}}}},"4XX":{"description":"unexpected client error","schema":{"type":"object","required":["code","message"],"properties":{"code":{"type":"integer","format":"int32"},"message":{"type":"string"}}}},"5XX":{"description":"unexpected server error","schema":{"type":"object","required":["code","message"],"properties":{"code":{"type":"integer","format":"int32"},"message":{"type":"string"}}}}}}},"/pets/{id}":{"get":{"description":"Returns a user based on a single ID, if the user does not have access to the pet","operationId":"findPetById","produces":["application/json","application/xml","text/html"],"parameters":[{"in":"path","name":"id","description":"ID of pet to fetch","required":true,"type":"integer","format":"int64"}],"responses":{"200":{"description":"pet response","schema":{"type":"object","required":["id","name"],"properties":{"id":{"type":"integer","format":"int64"},"name":{"type":"string"},"tag":{"type":"string"}}}},"4XX":{"description":"unexpected client error","schema":{"type":"object","required":["code","message"],"properties":{"code":{"type":"integer","format":"int32"},"message":{"type":"string"}}}},"5XX":{"description":"unexpected server error","schema":{"type":"object","required":["code","message"],"properties":{"code":{"type":"integer","format":"int32"},"message":{"type":"string"}}}}}},"delete":{"description":"deletes a single pet based on the ID supplied","operationId":"deletePet","produces":["text/html"],"parameters":[{"in":"path","name":"id","description":"ID of pet to delete","required":true,"type":"integer","format":"int64"}],"responses":{"204":{"description":"pet deleted"},"4XX":{"description":"unexpected client error","schema":{"type":"object","required":["code","message"],"properties":{"code":{"type":"integer","format":"int32"},"message":{"type":"string"}}}},"5XX":{"description":"unexpected server error","schema":{"type":"object","required":["code","message"],"properties":{"code":{"type":"integer","format":"int32"},"message":{"type":"string"}}}}}}}},"definitions":{"pet":{"type":"object","required":["id","name"],"properties":{"id":{"type":"integer","format":"int64"},"name":{"type":"string"},"tag":{"type":"string"}}},"newPet":{"type":"object","required":["name"],"properties":{"id":{"type":"integer","format":"int64"},"name":{"type":"string"},"tag":{"type":"string"}}},"errorModel":{"type":"object","required":["code","message"],"properties":{"code":{"type":"integer","format":"int32"},"message":{"type":"string"}}}}} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.SerializeAdvancedDocumentWithReferenceAsV2JsonWorks_produceTerseOutput=True.verified.txt b/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.SerializeAdvancedDocumentWithReferenceAsV2JsonWorks_produceTerseOutput=True.verified.txt new file mode 100644 index 000000000..0248156d9 --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.SerializeAdvancedDocumentWithReferenceAsV2JsonWorks_produceTerseOutput=True.verified.txt @@ -0,0 +1 @@ +{"swagger":"2.0","info":{"title":"Swagger Petstore (Simple)","description":"A sample API that uses a petstore as an example to demonstrate features in the swagger-2.0 specification","termsOfService":"http://helloreverb.com/terms/","contact":{"name":"Swagger API team","url":"http://swagger.io","email":"foo@example.com"},"license":{"name":"MIT","url":"http://opensource.org/licenses/MIT"},"version":"1.0.0"},"host":"petstore.swagger.io","basePath":"/api","schemes":["http"],"paths":{"/pets":{"get":{"description":"Returns all pets from the system that the user has access to","operationId":"findPets","produces":["application/json","application/xml","text/html"],"parameters":[{"in":"query","name":"tags","description":"tags to filter by","type":"array","items":{"type":"string"},"collectionFormat":"multi"},{"in":"query","name":"limit","description":"maximum number of results to return","type":"integer","format":"int32"}],"responses":{"200":{"description":"pet response","schema":{"type":"array","items":{"type":"object","required":["id","name"],"properties":{"id":{"type":"integer","format":"int64"},"name":{"type":"string"},"tag":{"type":"string"}}}}},"4XX":{"description":"unexpected client error","schema":{"type":"object","required":["code","message"],"properties":{"code":{"type":"integer","format":"int32"},"message":{"type":"string"}}}},"5XX":{"description":"unexpected server error","schema":{"type":"object","required":["code","message"],"properties":{"code":{"type":"integer","format":"int32"},"message":{"type":"string"}}}}}},"post":{"description":"Creates a new pet in the store. Duplicates are allowed","operationId":"addPet","consumes":["application/json"],"produces":["application/json","text/html"],"parameters":[{"in":"body","name":"body","description":"Pet to add to the store","required":true,"schema":{"type":"object","required":["name"],"properties":{"id":{"type":"integer","format":"int64"},"name":{"type":"string"},"tag":{"type":"string"}}}}],"responses":{"200":{"description":"pet response","schema":{"type":"object","required":["id","name"],"properties":{"id":{"type":"integer","format":"int64"},"name":{"type":"string"},"tag":{"type":"string"}}}},"4XX":{"description":"unexpected client error","schema":{"type":"object","required":["code","message"],"properties":{"code":{"type":"integer","format":"int32"},"message":{"type":"string"}}}},"5XX":{"description":"unexpected server error","schema":{"type":"object","required":["code","message"],"properties":{"code":{"type":"integer","format":"int32"},"message":{"type":"string"}}}}}}},"/pets/{id}":{"get":{"description":"Returns a user based on a single ID, if the user does not have access to the pet","operationId":"findPetById","produces":["application/json","application/xml","text/html"],"parameters":[{"in":"path","name":"id","description":"ID of pet to fetch","required":true,"type":"integer","format":"int64"}],"responses":{"200":{"description":"pet response","schema":{"type":"object","required":["id","name"],"properties":{"id":{"type":"integer","format":"int64"},"name":{"type":"string"},"tag":{"type":"string"}}}},"4XX":{"description":"unexpected client error","schema":{"type":"object","required":["code","message"],"properties":{"code":{"type":"integer","format":"int32"},"message":{"type":"string"}}}},"5XX":{"description":"unexpected server error","schema":{"type":"object","required":["code","message"],"properties":{"code":{"type":"integer","format":"int32"},"message":{"type":"string"}}}}}},"delete":{"description":"deletes a single pet based on the ID supplied","operationId":"deletePet","produces":["text/html"],"parameters":[{"in":"path","name":"id","description":"ID of pet to delete","required":true,"type":"integer","format":"int64"}],"responses":{"204":{"description":"pet deleted"},"4XX":{"description":"unexpected client error","schema":{"type":"object","required":["code","message"],"properties":{"code":{"type":"integer","format":"int32"},"message":{"type":"string"}}}},"5XX":{"description":"unexpected server error","schema":{"type":"object","required":["code","message"],"properties":{"code":{"type":"integer","format":"int32"},"message":{"type":"string"}}}}}}}},"definitions":{"pet":{"type":"object","required":["id","name"],"properties":{"id":{"type":"integer","format":"int64"},"name":{"type":"string"},"tag":{"type":"string"}}},"newPet":{"type":"object","required":["name"],"properties":{"id":{"type":"integer","format":"int64"},"name":{"type":"string"},"tag":{"type":"string"}}},"errorModel":{"type":"object","required":["code","message"],"properties":{"code":{"type":"integer","format":"int32"},"message":{"type":"string"}}}}} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.SerializeAdvancedDocumentWithReferenceAsV3JsonWorksAsync_produceTerseOutput=False.verified.txt b/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.SerializeAdvancedDocumentWithReferenceAsV3JsonWorksAsync_produceTerseOutput=False.verified.txt index eb23d0c24..49c47bc62 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.SerializeAdvancedDocumentWithReferenceAsV3JsonWorksAsync_produceTerseOutput=False.verified.txt +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.SerializeAdvancedDocumentWithReferenceAsV3JsonWorksAsync_produceTerseOutput=False.verified.txt @@ -56,7 +56,23 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/pet" + "required": [ + "id", + "name" + ], + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + }, + "tag": { + "type": "string" + } + } } } }, @@ -64,7 +80,23 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/pet" + "required": [ + "id", + "name" + ], + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + }, + "tag": { + "type": "string" + } + } } } } @@ -75,7 +107,20 @@ "content": { "text/html": { "schema": { - "$ref": "#/components/schemas/errorModel" + "required": [ + "code", + "message" + ], + "type": "object", + "properties": { + "code": { + "type": "integer", + "format": "int32" + }, + "message": { + "type": "string" + } + } } } } @@ -85,7 +130,20 @@ "content": { "text/html": { "schema": { - "$ref": "#/components/schemas/errorModel" + "required": [ + "code", + "message" + ], + "type": "object", + "properties": { + "code": { + "type": "integer", + "format": "int32" + }, + "message": { + "type": "string" + } + } } } } @@ -100,7 +158,22 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/newPet" + "required": [ + "name" + ], + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + }, + "tag": { + "type": "string" + } + } } } }, @@ -112,7 +185,23 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/pet" + "required": [ + "id", + "name" + ], + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + }, + "tag": { + "type": "string" + } + } } } } @@ -122,7 +211,20 @@ "content": { "text/html": { "schema": { - "$ref": "#/components/schemas/errorModel" + "required": [ + "code", + "message" + ], + "type": "object", + "properties": { + "code": { + "type": "integer", + "format": "int32" + }, + "message": { + "type": "string" + } + } } } } @@ -132,7 +234,20 @@ "content": { "text/html": { "schema": { - "$ref": "#/components/schemas/errorModel" + "required": [ + "code", + "message" + ], + "type": "object", + "properties": { + "code": { + "type": "integer", + "format": "int32" + }, + "message": { + "type": "string" + } + } } } } @@ -162,12 +277,44 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/pet" + "required": [ + "id", + "name" + ], + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + }, + "tag": { + "type": "string" + } + } } }, "application/xml": { "schema": { - "$ref": "#/components/schemas/pet" + "required": [ + "id", + "name" + ], + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + }, + "tag": { + "type": "string" + } + } } } } @@ -177,7 +324,20 @@ "content": { "text/html": { "schema": { - "$ref": "#/components/schemas/errorModel" + "required": [ + "code", + "message" + ], + "type": "object", + "properties": { + "code": { + "type": "integer", + "format": "int32" + }, + "message": { + "type": "string" + } + } } } } @@ -187,7 +347,20 @@ "content": { "text/html": { "schema": { - "$ref": "#/components/schemas/errorModel" + "required": [ + "code", + "message" + ], + "type": "object", + "properties": { + "code": { + "type": "integer", + "format": "int32" + }, + "message": { + "type": "string" + } + } } } } @@ -218,7 +391,20 @@ "content": { "text/html": { "schema": { - "$ref": "#/components/schemas/errorModel" + "required": [ + "code", + "message" + ], + "type": "object", + "properties": { + "code": { + "type": "integer", + "format": "int32" + }, + "message": { + "type": "string" + } + } } } } @@ -228,7 +414,20 @@ "content": { "text/html": { "schema": { - "$ref": "#/components/schemas/errorModel" + "required": [ + "code", + "message" + ], + "type": "object", + "properties": { + "code": { + "type": "integer", + "format": "int32" + }, + "message": { + "type": "string" + } + } } } } diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.SerializeAdvancedDocumentWithReferenceAsV3JsonWorksAsync_produceTerseOutput=True.verified.txt b/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.SerializeAdvancedDocumentWithReferenceAsV3JsonWorksAsync_produceTerseOutput=True.verified.txt index 01840772e..81beb028b 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.SerializeAdvancedDocumentWithReferenceAsV3JsonWorksAsync_produceTerseOutput=True.verified.txt +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.SerializeAdvancedDocumentWithReferenceAsV3JsonWorksAsync_produceTerseOutput=True.verified.txt @@ -1 +1 @@ -{"openapi":"3.0.1","info":{"title":"Swagger Petstore (Simple)","description":"A sample API that uses a petstore as an example to demonstrate features in the swagger-2.0 specification","termsOfService":"http://helloreverb.com/terms/","contact":{"name":"Swagger API team","url":"http://swagger.io","email":"foo@example.com"},"license":{"name":"MIT","url":"http://opensource.org/licenses/MIT"},"version":"1.0.0"},"servers":[{"url":"http://petstore.swagger.io/api"}],"paths":{"/pets":{"get":{"description":"Returns all pets from the system that the user has access to","operationId":"findPets","parameters":[{"name":"tags","in":"query","description":"tags to filter by","style":"form","schema":{"type":"array","items":{"type":"string"}}},{"name":"limit","in":"query","description":"maximum number of results to return","schema":{"type":"integer","format":"int32"}}],"responses":{"200":{"description":"pet response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/pet"}}},"application/xml":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/pet"}}}}},"4XX":{"description":"unexpected client error","content":{"text/html":{"schema":{"$ref":"#/components/schemas/errorModel"}}}},"5XX":{"description":"unexpected server error","content":{"text/html":{"schema":{"$ref":"#/components/schemas/errorModel"}}}}}},"post":{"description":"Creates a new pet in the store. Duplicates are allowed","operationId":"addPet","requestBody":{"description":"Pet to add to the store","content":{"application/json":{"schema":{"$ref":"#/components/schemas/newPet"}}},"required":true},"responses":{"200":{"description":"pet response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/pet"}}}},"4XX":{"description":"unexpected client error","content":{"text/html":{"schema":{"$ref":"#/components/schemas/errorModel"}}}},"5XX":{"description":"unexpected server error","content":{"text/html":{"schema":{"$ref":"#/components/schemas/errorModel"}}}}}}},"/pets/{id}":{"get":{"description":"Returns a user based on a single ID, if the user does not have access to the pet","operationId":"findPetById","parameters":[{"name":"id","in":"path","description":"ID of pet to fetch","required":true,"schema":{"type":"integer","format":"int64"}}],"responses":{"200":{"description":"pet response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/pet"}},"application/xml":{"schema":{"$ref":"#/components/schemas/pet"}}}},"4XX":{"description":"unexpected client error","content":{"text/html":{"schema":{"$ref":"#/components/schemas/errorModel"}}}},"5XX":{"description":"unexpected server error","content":{"text/html":{"schema":{"$ref":"#/components/schemas/errorModel"}}}}}},"delete":{"description":"deletes a single pet based on the ID supplied","operationId":"deletePet","parameters":[{"name":"id","in":"path","description":"ID of pet to delete","required":true,"schema":{"type":"integer","format":"int64"}}],"responses":{"204":{"description":"pet deleted"},"4XX":{"description":"unexpected client error","content":{"text/html":{"schema":{"$ref":"#/components/schemas/errorModel"}}}},"5XX":{"description":"unexpected server error","content":{"text/html":{"schema":{"$ref":"#/components/schemas/errorModel"}}}}}}}},"components":{"schemas":{"pet":{"required":["id","name"],"type":"object","properties":{"id":{"type":"integer","format":"int64"},"name":{"type":"string"},"tag":{"type":"string"}}},"newPet":{"required":["name"],"type":"object","properties":{"id":{"type":"integer","format":"int64"},"name":{"type":"string"},"tag":{"type":"string"}}},"errorModel":{"required":["code","message"],"type":"object","properties":{"code":{"type":"integer","format":"int32"},"message":{"type":"string"}}}}}} \ No newline at end of file +{"openapi":"3.0.1","info":{"title":"Swagger Petstore (Simple)","description":"A sample API that uses a petstore as an example to demonstrate features in the swagger-2.0 specification","termsOfService":"http://helloreverb.com/terms/","contact":{"name":"Swagger API team","url":"http://swagger.io","email":"foo@example.com"},"license":{"name":"MIT","url":"http://opensource.org/licenses/MIT"},"version":"1.0.0"},"servers":[{"url":"http://petstore.swagger.io/api"}],"paths":{"/pets":{"get":{"description":"Returns all pets from the system that the user has access to","operationId":"findPets","parameters":[{"name":"tags","in":"query","description":"tags to filter by","style":"form","schema":{"type":"array","items":{"type":"string"}}},{"name":"limit","in":"query","description":"maximum number of results to return","schema":{"type":"integer","format":"int32"}}],"responses":{"200":{"description":"pet response","content":{"application/json":{"schema":{"type":"array","items":{"required":["id","name"],"type":"object","properties":{"id":{"type":"integer","format":"int64"},"name":{"type":"string"},"tag":{"type":"string"}}}}},"application/xml":{"schema":{"type":"array","items":{"required":["id","name"],"type":"object","properties":{"id":{"type":"integer","format":"int64"},"name":{"type":"string"},"tag":{"type":"string"}}}}}}},"4XX":{"description":"unexpected client error","content":{"text/html":{"schema":{"required":["code","message"],"type":"object","properties":{"code":{"type":"integer","format":"int32"},"message":{"type":"string"}}}}}},"5XX":{"description":"unexpected server error","content":{"text/html":{"schema":{"required":["code","message"],"type":"object","properties":{"code":{"type":"integer","format":"int32"},"message":{"type":"string"}}}}}}}},"post":{"description":"Creates a new pet in the store. Duplicates are allowed","operationId":"addPet","requestBody":{"description":"Pet to add to the store","content":{"application/json":{"schema":{"required":["name"],"type":"object","properties":{"id":{"type":"integer","format":"int64"},"name":{"type":"string"},"tag":{"type":"string"}}}}},"required":true},"responses":{"200":{"description":"pet response","content":{"application/json":{"schema":{"required":["id","name"],"type":"object","properties":{"id":{"type":"integer","format":"int64"},"name":{"type":"string"},"tag":{"type":"string"}}}}}},"4XX":{"description":"unexpected client error","content":{"text/html":{"schema":{"required":["code","message"],"type":"object","properties":{"code":{"type":"integer","format":"int32"},"message":{"type":"string"}}}}}},"5XX":{"description":"unexpected server error","content":{"text/html":{"schema":{"required":["code","message"],"type":"object","properties":{"code":{"type":"integer","format":"int32"},"message":{"type":"string"}}}}}}}}},"/pets/{id}":{"get":{"description":"Returns a user based on a single ID, if the user does not have access to the pet","operationId":"findPetById","parameters":[{"name":"id","in":"path","description":"ID of pet to fetch","required":true,"schema":{"type":"integer","format":"int64"}}],"responses":{"200":{"description":"pet response","content":{"application/json":{"schema":{"required":["id","name"],"type":"object","properties":{"id":{"type":"integer","format":"int64"},"name":{"type":"string"},"tag":{"type":"string"}}}},"application/xml":{"schema":{"required":["id","name"],"type":"object","properties":{"id":{"type":"integer","format":"int64"},"name":{"type":"string"},"tag":{"type":"string"}}}}}},"4XX":{"description":"unexpected client error","content":{"text/html":{"schema":{"required":["code","message"],"type":"object","properties":{"code":{"type":"integer","format":"int32"},"message":{"type":"string"}}}}}},"5XX":{"description":"unexpected server error","content":{"text/html":{"schema":{"required":["code","message"],"type":"object","properties":{"code":{"type":"integer","format":"int32"},"message":{"type":"string"}}}}}}}},"delete":{"description":"deletes a single pet based on the ID supplied","operationId":"deletePet","parameters":[{"name":"id","in":"path","description":"ID of pet to delete","required":true,"schema":{"type":"integer","format":"int64"}}],"responses":{"204":{"description":"pet deleted"},"4XX":{"description":"unexpected client error","content":{"text/html":{"schema":{"required":["code","message"],"type":"object","properties":{"code":{"type":"integer","format":"int32"},"message":{"type":"string"}}}}}},"5XX":{"description":"unexpected server error","content":{"text/html":{"schema":{"required":["code","message"],"type":"object","properties":{"code":{"type":"integer","format":"int32"},"message":{"type":"string"}}}}}}}}}},"components":{"schemas":{"pet":{"required":["id","name"],"type":"object","properties":{"id":{"type":"integer","format":"int64"},"name":{"type":"string"},"tag":{"type":"string"}}},"newPet":{"required":["name"],"type":"object","properties":{"id":{"type":"integer","format":"int64"},"name":{"type":"string"},"tag":{"type":"string"}}},"errorModel":{"required":["code","message"],"type":"object","properties":{"code":{"type":"integer","format":"int32"},"message":{"type":"string"}}}}}} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.SerializeAdvancedDocumentWithReferenceAsV3JsonWorks_produceTerseOutput=True.verified.txt b/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.SerializeAdvancedDocumentWithReferenceAsV3JsonWorks_produceTerseOutput=True.verified.txt new file mode 100644 index 000000000..0bb1c9679 --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.SerializeAdvancedDocumentWithReferenceAsV3JsonWorks_produceTerseOutput=True.verified.txt @@ -0,0 +1 @@ +{"openapi":"3.0.1","info":{"title":"Swagger Petstore (Simple)","description":"A sample API that uses a petstore as an example to demonstrate features in the swagger-2.0 specification","termsOfService":"http://helloreverb.com/terms/","contact":{"name":"Swagger API team","url":"http://swagger.io","email":"foo@example.com"},"license":{"name":"MIT","url":"http://opensource.org/licenses/MIT"},"version":"1.0.0"},"servers":[{"url":"http://petstore.swagger.io/api"}],"paths":{"/pets":{"get":{"description":"Returns all pets from the system that the user has access to","operationId":"findPets","parameters":[{"name":"tags","in":"query","description":"tags to filter by","schema":{"type":"array","items":{"type":"string"}}},{"name":"limit","in":"query","description":"maximum number of results to return","schema":{"type":"integer","format":"int32"}}],"responses":{"200":{"description":"pet response","content":{"application/json":{"schema":{"type":"array","items":{"required":["id","name"],"type":"object","properties":{"id":{"type":"integer","format":"int64"},"name":{"type":"string"},"tag":{"type":"string"}}}}},"application/xml":{"schema":{"type":"array","items":{"required":["id","name"],"type":"object","properties":{"id":{"type":"integer","format":"int64"},"name":{"type":"string"},"tag":{"type":"string"}}}}}}},"4XX":{"description":"unexpected client error","content":{"text/html":{"schema":{"required":["code","message"],"type":"object","properties":{"code":{"type":"integer","format":"int32"},"message":{"type":"string"}}}}}},"5XX":{"description":"unexpected server error","content":{"text/html":{"schema":{"required":["code","message"],"type":"object","properties":{"code":{"type":"integer","format":"int32"},"message":{"type":"string"}}}}}}}},"post":{"description":"Creates a new pet in the store. Duplicates are allowed","operationId":"addPet","requestBody":{"description":"Pet to add to the store","content":{"application/json":{"schema":{"required":["name"],"type":"object","properties":{"id":{"type":"integer","format":"int64"},"name":{"type":"string"},"tag":{"type":"string"}}}}},"required":true},"responses":{"200":{"description":"pet response","content":{"application/json":{"schema":{"required":["id","name"],"type":"object","properties":{"id":{"type":"integer","format":"int64"},"name":{"type":"string"},"tag":{"type":"string"}}}}}},"4XX":{"description":"unexpected client error","content":{"text/html":{"schema":{"required":["code","message"],"type":"object","properties":{"code":{"type":"integer","format":"int32"},"message":{"type":"string"}}}}}},"5XX":{"description":"unexpected server error","content":{"text/html":{"schema":{"required":["code","message"],"type":"object","properties":{"code":{"type":"integer","format":"int32"},"message":{"type":"string"}}}}}}}}},"/pets/{id}":{"get":{"description":"Returns a user based on a single ID, if the user does not have access to the pet","operationId":"findPetById","parameters":[{"name":"id","in":"path","description":"ID of pet to fetch","required":true,"schema":{"type":"integer","format":"int64"}}],"responses":{"200":{"description":"pet response","content":{"application/json":{"schema":{"required":["id","name"],"type":"object","properties":{"id":{"type":"integer","format":"int64"},"name":{"type":"string"},"tag":{"type":"string"}}}},"application/xml":{"schema":{"required":["id","name"],"type":"object","properties":{"id":{"type":"integer","format":"int64"},"name":{"type":"string"},"tag":{"type":"string"}}}}}},"4XX":{"description":"unexpected client error","content":{"text/html":{"schema":{"required":["code","message"],"type":"object","properties":{"code":{"type":"integer","format":"int32"},"message":{"type":"string"}}}}}},"5XX":{"description":"unexpected server error","content":{"text/html":{"schema":{"required":["code","message"],"type":"object","properties":{"code":{"type":"integer","format":"int32"},"message":{"type":"string"}}}}}}}},"delete":{"description":"deletes a single pet based on the ID supplied","operationId":"deletePet","parameters":[{"name":"id","in":"path","description":"ID of pet to delete","required":true,"schema":{"type":"integer","format":"int64"}}],"responses":{"204":{"description":"pet deleted"},"4XX":{"description":"unexpected client error","content":{"text/html":{"schema":{"required":["code","message"],"type":"object","properties":{"code":{"type":"integer","format":"int32"},"message":{"type":"string"}}}}}},"5XX":{"description":"unexpected server error","content":{"text/html":{"schema":{"required":["code","message"],"type":"object","properties":{"code":{"type":"integer","format":"int32"},"message":{"type":"string"}}}}}}}}}},"components":{"schemas":{"pet":{"required":["id","name"],"type":"object","properties":{"id":{"type":"integer","format":"int64"},"name":{"type":"string"},"tag":{"type":"string"}}},"newPet":{"required":["name"],"type":"object","properties":{"id":{"type":"integer","format":"int64"},"name":{"type":"string"},"tag":{"type":"string"}}},"errorModel":{"required":["code","message"],"type":"object","properties":{"code":{"type":"integer","format":"int32"},"message":{"type":"string"}}}}}} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.SerializeAdvancedDocumentWithServerVariableAsV2JsonWorksAsync_produceTerseOutput=False.verified.txt b/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.SerializeAdvancedDocumentWithServerVariableAsV2JsonWorksAsync_produceTerseOutput=False.verified.txt index 1656b2bf7..ae6572f21 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.SerializeAdvancedDocumentWithServerVariableAsV2JsonWorksAsync_produceTerseOutput=False.verified.txt +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.SerializeAdvancedDocumentWithServerVariableAsV2JsonWorksAsync_produceTerseOutput=False.verified.txt @@ -55,15 +55,15 @@ "schema": { "type": "array", "items": { + "type": "object", "required": [ "id", "name" ], - "type": "object", "properties": { "id": { - "format": "int64", - "type": "integer" + "type": "integer", + "format": "int64" }, "name": { "type": "string" @@ -78,15 +78,15 @@ "4XX": { "description": "unexpected client error", "schema": { + "type": "object", "required": [ "code", "message" ], - "type": "object", "properties": { "code": { - "format": "int32", - "type": "integer" + "type": "integer", + "format": "int32" }, "message": { "type": "string" @@ -97,15 +97,15 @@ "5XX": { "description": "unexpected server error", "schema": { + "type": "object", "required": [ "code", "message" ], - "type": "object", "properties": { "code": { - "format": "int32", - "type": "integer" + "type": "integer", + "format": "int32" }, "message": { "type": "string" @@ -132,14 +132,14 @@ "description": "Pet to add to the store", "required": true, "schema": { + "type": "object", "required": [ "name" ], - "type": "object", "properties": { "id": { - "format": "int64", - "type": "integer" + "type": "integer", + "format": "int64" }, "name": { "type": "string" @@ -155,15 +155,15 @@ "200": { "description": "pet response", "schema": { + "type": "object", "required": [ "id", "name" ], - "type": "object", "properties": { "id": { - "format": "int64", - "type": "integer" + "type": "integer", + "format": "int64" }, "name": { "type": "string" @@ -177,15 +177,15 @@ "4XX": { "description": "unexpected client error", "schema": { + "type": "object", "required": [ "code", "message" ], - "type": "object", "properties": { "code": { - "format": "int32", - "type": "integer" + "type": "integer", + "format": "int32" }, "message": { "type": "string" @@ -196,15 +196,15 @@ "5XX": { "description": "unexpected server error", "schema": { + "type": "object", "required": [ "code", "message" ], - "type": "object", "properties": { "code": { - "format": "int32", - "type": "integer" + "type": "integer", + "format": "int32" }, "message": { "type": "string" @@ -238,15 +238,15 @@ "200": { "description": "pet response", "schema": { + "type": "object", "required": [ "id", "name" ], - "type": "object", "properties": { "id": { - "format": "int64", - "type": "integer" + "type": "integer", + "format": "int64" }, "name": { "type": "string" @@ -260,15 +260,15 @@ "4XX": { "description": "unexpected client error", "schema": { + "type": "object", "required": [ "code", "message" ], - "type": "object", "properties": { "code": { - "format": "int32", - "type": "integer" + "type": "integer", + "format": "int32" }, "message": { "type": "string" @@ -279,15 +279,15 @@ "5XX": { "description": "unexpected server error", "schema": { + "type": "object", "required": [ "code", "message" ], - "type": "object", "properties": { "code": { - "format": "int32", - "type": "integer" + "type": "integer", + "format": "int32" }, "message": { "type": "string" @@ -320,15 +320,15 @@ "4XX": { "description": "unexpected client error", "schema": { + "type": "object", "required": [ "code", "message" ], - "type": "object", "properties": { "code": { - "format": "int32", - "type": "integer" + "type": "integer", + "format": "int32" }, "message": { "type": "string" @@ -339,15 +339,15 @@ "5XX": { "description": "unexpected server error", "schema": { + "type": "object", "required": [ "code", "message" ], - "type": "object", "properties": { "code": { - "format": "int32", - "type": "integer" + "type": "integer", + "format": "int32" }, "message": { "type": "string" @@ -361,15 +361,15 @@ }, "definitions": { "pet": { + "type": "object", "required": [ "id", "name" ], - "type": "object", "properties": { "id": { - "format": "int64", - "type": "integer" + "type": "integer", + "format": "int64" }, "name": { "type": "string" @@ -380,14 +380,14 @@ } }, "newPet": { + "type": "object", "required": [ "name" ], - "type": "object", "properties": { "id": { - "format": "int64", - "type": "integer" + "type": "integer", + "format": "int64" }, "name": { "type": "string" @@ -398,15 +398,15 @@ } }, "errorModel": { + "type": "object", "required": [ "code", "message" ], - "type": "object", "properties": { "code": { - "format": "int32", - "type": "integer" + "type": "integer", + "format": "int32" }, "message": { "type": "string" diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.SerializeAdvancedDocumentWithServerVariableAsV2JsonWorksAsync_produceTerseOutput=True.verified.txt b/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.SerializeAdvancedDocumentWithServerVariableAsV2JsonWorksAsync_produceTerseOutput=True.verified.txt index 3670fba11..5ae9e05e5 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.SerializeAdvancedDocumentWithServerVariableAsV2JsonWorksAsync_produceTerseOutput=True.verified.txt +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.SerializeAdvancedDocumentWithServerVariableAsV2JsonWorksAsync_produceTerseOutput=True.verified.txt @@ -1 +1 @@ -{"swagger":"2.0","info":{"title":"Swagger Petstore (Simple)","description":"A sample API that uses a petstore as an example to demonstrate features in the swagger-2.0 specification","termsOfService":"http://helloreverb.com/terms/","contact":{"name":"Swagger API team","url":"http://swagger.io","email":"foo@example.com"},"license":{"name":"MIT","url":"http://opensource.org/licenses/MIT"},"version":"1.0.0"},"host":"your-resource-name.openai.azure.com","basePath":"/openai","schemes":["https"],"paths":{"/pets":{"get":{"description":"Returns all pets from the system that the user has access to","operationId":"findPets","produces":["application/json","application/xml","text/html"],"parameters":[{"in":"query","name":"tags","description":"tags to filter by","type":"array","items":{"type":"string"},"collectionFormat":"multi"},{"in":"query","name":"limit","description":"maximum number of results to return","type":"integer","format":"int32"}],"responses":{"200":{"description":"pet response","schema":{"type":"array","items":{"required":["id","name"],"type":"object","properties":{"id":{"format":"int64","type":"integer"},"name":{"type":"string"},"tag":{"type":"string"}}}}},"4XX":{"description":"unexpected client error","schema":{"required":["code","message"],"type":"object","properties":{"code":{"format":"int32","type":"integer"},"message":{"type":"string"}}}},"5XX":{"description":"unexpected server error","schema":{"required":["code","message"],"type":"object","properties":{"code":{"format":"int32","type":"integer"},"message":{"type":"string"}}}}}},"post":{"description":"Creates a new pet in the store. Duplicates are allowed","operationId":"addPet","consumes":["application/json"],"produces":["application/json","text/html"],"parameters":[{"in":"body","name":"body","description":"Pet to add to the store","required":true,"schema":{"required":["name"],"type":"object","properties":{"id":{"format":"int64","type":"integer"},"name":{"type":"string"},"tag":{"type":"string"}}}}],"responses":{"200":{"description":"pet response","schema":{"required":["id","name"],"type":"object","properties":{"id":{"format":"int64","type":"integer"},"name":{"type":"string"},"tag":{"type":"string"}}}},"4XX":{"description":"unexpected client error","schema":{"required":["code","message"],"type":"object","properties":{"code":{"format":"int32","type":"integer"},"message":{"type":"string"}}}},"5XX":{"description":"unexpected server error","schema":{"required":["code","message"],"type":"object","properties":{"code":{"format":"int32","type":"integer"},"message":{"type":"string"}}}}}}},"/pets/{id}":{"get":{"description":"Returns a user based on a single ID, if the user does not have access to the pet","operationId":"findPetById","produces":["application/json","application/xml","text/html"],"parameters":[{"in":"path","name":"id","description":"ID of pet to fetch","required":true,"type":"integer","format":"int64"}],"responses":{"200":{"description":"pet response","schema":{"required":["id","name"],"type":"object","properties":{"id":{"format":"int64","type":"integer"},"name":{"type":"string"},"tag":{"type":"string"}}}},"4XX":{"description":"unexpected client error","schema":{"required":["code","message"],"type":"object","properties":{"code":{"format":"int32","type":"integer"},"message":{"type":"string"}}}},"5XX":{"description":"unexpected server error","schema":{"required":["code","message"],"type":"object","properties":{"code":{"format":"int32","type":"integer"},"message":{"type":"string"}}}}}},"delete":{"description":"deletes a single pet based on the ID supplied","operationId":"deletePet","produces":["text/html"],"parameters":[{"in":"path","name":"id","description":"ID of pet to delete","required":true,"type":"integer","format":"int64"}],"responses":{"204":{"description":"pet deleted"},"4XX":{"description":"unexpected client error","schema":{"required":["code","message"],"type":"object","properties":{"code":{"format":"int32","type":"integer"},"message":{"type":"string"}}}},"5XX":{"description":"unexpected server error","schema":{"required":["code","message"],"type":"object","properties":{"code":{"format":"int32","type":"integer"},"message":{"type":"string"}}}}}}}},"definitions":{"pet":{"required":["id","name"],"type":"object","properties":{"id":{"format":"int64","type":"integer"},"name":{"type":"string"},"tag":{"type":"string"}}},"newPet":{"required":["name"],"type":"object","properties":{"id":{"format":"int64","type":"integer"},"name":{"type":"string"},"tag":{"type":"string"}}},"errorModel":{"required":["code","message"],"type":"object","properties":{"code":{"format":"int32","type":"integer"},"message":{"type":"string"}}}}} \ No newline at end of file +{"swagger":"2.0","info":{"title":"Swagger Petstore (Simple)","description":"A sample API that uses a petstore as an example to demonstrate features in the swagger-2.0 specification","termsOfService":"http://helloreverb.com/terms/","contact":{"name":"Swagger API team","url":"http://swagger.io","email":"foo@example.com"},"license":{"name":"MIT","url":"http://opensource.org/licenses/MIT"},"version":"1.0.0"},"host":"your-resource-name.openai.azure.com","basePath":"/openai","schemes":["https"],"paths":{"/pets":{"get":{"description":"Returns all pets from the system that the user has access to","operationId":"findPets","produces":["application/json","application/xml","text/html"],"parameters":[{"in":"query","name":"tags","description":"tags to filter by","type":"array","items":{"type":"string"},"collectionFormat":"multi"},{"in":"query","name":"limit","description":"maximum number of results to return","type":"integer","format":"int32"}],"responses":{"200":{"description":"pet response","schema":{"type":"array","items":{"type":"object","required":["id","name"],"properties":{"id":{"type":"integer","format":"int64"},"name":{"type":"string"},"tag":{"type":"string"}}}}},"4XX":{"description":"unexpected client error","schema":{"type":"object","required":["code","message"],"properties":{"code":{"type":"integer","format":"int32"},"message":{"type":"string"}}}},"5XX":{"description":"unexpected server error","schema":{"type":"object","required":["code","message"],"properties":{"code":{"type":"integer","format":"int32"},"message":{"type":"string"}}}}}},"post":{"description":"Creates a new pet in the store. Duplicates are allowed","operationId":"addPet","consumes":["application/json"],"produces":["application/json","text/html"],"parameters":[{"in":"body","name":"body","description":"Pet to add to the store","required":true,"schema":{"type":"object","required":["name"],"properties":{"id":{"type":"integer","format":"int64"},"name":{"type":"string"},"tag":{"type":"string"}}}}],"responses":{"200":{"description":"pet response","schema":{"type":"object","required":["id","name"],"properties":{"id":{"type":"integer","format":"int64"},"name":{"type":"string"},"tag":{"type":"string"}}}},"4XX":{"description":"unexpected client error","schema":{"type":"object","required":["code","message"],"properties":{"code":{"type":"integer","format":"int32"},"message":{"type":"string"}}}},"5XX":{"description":"unexpected server error","schema":{"type":"object","required":["code","message"],"properties":{"code":{"type":"integer","format":"int32"},"message":{"type":"string"}}}}}}},"/pets/{id}":{"get":{"description":"Returns a user based on a single ID, if the user does not have access to the pet","operationId":"findPetById","produces":["application/json","application/xml","text/html"],"parameters":[{"in":"path","name":"id","description":"ID of pet to fetch","required":true,"type":"integer","format":"int64"}],"responses":{"200":{"description":"pet response","schema":{"type":"object","required":["id","name"],"properties":{"id":{"type":"integer","format":"int64"},"name":{"type":"string"},"tag":{"type":"string"}}}},"4XX":{"description":"unexpected client error","schema":{"type":"object","required":["code","message"],"properties":{"code":{"type":"integer","format":"int32"},"message":{"type":"string"}}}},"5XX":{"description":"unexpected server error","schema":{"type":"object","required":["code","message"],"properties":{"code":{"type":"integer","format":"int32"},"message":{"type":"string"}}}}}},"delete":{"description":"deletes a single pet based on the ID supplied","operationId":"deletePet","produces":["text/html"],"parameters":[{"in":"path","name":"id","description":"ID of pet to delete","required":true,"type":"integer","format":"int64"}],"responses":{"204":{"description":"pet deleted"},"4XX":{"description":"unexpected client error","schema":{"type":"object","required":["code","message"],"properties":{"code":{"type":"integer","format":"int32"},"message":{"type":"string"}}}},"5XX":{"description":"unexpected server error","schema":{"type":"object","required":["code","message"],"properties":{"code":{"type":"integer","format":"int32"},"message":{"type":"string"}}}}}}}},"definitions":{"pet":{"type":"object","required":["id","name"],"properties":{"id":{"type":"integer","format":"int64"},"name":{"type":"string"},"tag":{"type":"string"}}},"newPet":{"type":"object","required":["name"],"properties":{"id":{"type":"integer","format":"int64"},"name":{"type":"string"},"tag":{"type":"string"}}},"errorModel":{"type":"object","required":["code","message"],"properties":{"code":{"type":"integer","format":"int32"},"message":{"type":"string"}}}}} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.SerializeDocumentWithWebhooksAsV3JsonWorks_produceTerseOutput=False.verified.txt b/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.SerializeDocumentWithWebhooksAsV3JsonWorks_produceTerseOutput=False.verified.txt new file mode 100644 index 000000000..4eebd3082 --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.SerializeDocumentWithWebhooksAsV3JsonWorks_produceTerseOutput=False.verified.txt @@ -0,0 +1,51 @@ +{ + "openapi": "3.1.0", + "info": { + "title": "Webhook Example", + "version": "1.0.0" + }, + "paths": { }, + "components": { + "schemas": { + "Pet": { + "required": [ + "id", + "name" + ], + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + }, + "tag": { + "type": "string" + } + } + } + } + }, + "webhooks": { + "newPet": { + "post": { + "requestBody": { + "description": "Information about a new pet in the system", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + } + } + }, + "responses": { + "200": { + "description": "Return a 200 status to indicate that the data was received successfully" + } + } + } + } + } +} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.SerializeDocumentWithWebhooksAsV3JsonWorks_produceTerseOutput=True.verified.txt b/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.SerializeDocumentWithWebhooksAsV3JsonWorks_produceTerseOutput=True.verified.txt new file mode 100644 index 000000000..d105617d2 --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.SerializeDocumentWithWebhooksAsV3JsonWorks_produceTerseOutput=True.verified.txt @@ -0,0 +1 @@ +{"openapi":"3.1.0","info":{"title":"Webhook Example","version":"1.0.0"},"paths":{},"components":{"schemas":{"Pet":{"required":["id","name"],"properties":{"id":{"type":"integer","format":"int64"},"name":{"type":"string"},"tag":{"type":"string"}}}}},"webhooks":{"newPet":{"post":{"requestBody":{"description":"Information about a new pet in the system","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Pet"}}}},"responses":{"200":{"description":"Return a 200 status to indicate that the data was received successfully"}}}}}} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.SerializeDuplicateExtensionsAsV2JsonWorksAsync_produceTerseOutput=False.verified.txt b/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.SerializeDuplicateExtensionsAsV2JsonWorksAsync_produceTerseOutput=False.verified.txt index e2a2dbbab..1ba9050ca 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.SerializeDuplicateExtensionsAsV2JsonWorksAsync_produceTerseOutput=False.verified.txt +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.SerializeDuplicateExtensionsAsV2JsonWorksAsync_produceTerseOutput=False.verified.txt @@ -41,15 +41,15 @@ "schema": { "type": "array", "items": { + "type": "object", "required": [ "id", "name" ], - "type": "object", "properties": { "id": { - "format": "int64", - "type": "integer" + "type": "integer", + "format": "int64" }, "name": { "type": "string" @@ -64,5 +64,60 @@ } } } + }, + "definitions": { + "pet": { + "type": "object", + "required": [ + "id", + "name" + ], + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + }, + "tag": { + "type": "string" + } + } + }, + "newPet": { + "type": "object", + "required": [ + "name" + ], + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + }, + "tag": { + "type": "string" + } + } + }, + "errorModel": { + "type": "object", + "required": [ + "code", + "message" + ], + "properties": { + "code": { + "type": "integer", + "format": "int32" + }, + "message": { + "type": "string" + } + } + } } } \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.SerializeDuplicateExtensionsAsV2JsonWorksAsync_produceTerseOutput=True.verified.txt b/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.SerializeDuplicateExtensionsAsV2JsonWorksAsync_produceTerseOutput=True.verified.txt index 49072fda2..b61ba4d5a 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.SerializeDuplicateExtensionsAsV2JsonWorksAsync_produceTerseOutput=True.verified.txt +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.SerializeDuplicateExtensionsAsV2JsonWorksAsync_produceTerseOutput=True.verified.txt @@ -1 +1 @@ -{"swagger":"2.0","info":{"title":"Swagger Petstore (Simple)","description":"A sample API that uses a petstore as an example to demonstrate features in the swagger-2.0 specification","version":"1.0.0"},"host":"petstore.swagger.io","basePath":"/api","schemes":["http"],"paths":{"/add/{operand1}/{operand2}":{"get":{"operationId":"addByOperand1AndByOperand2","produces":["application/json"],"parameters":[{"in":"path","name":"operand1","description":"The first operand","required":true,"type":"integer","my-extension":4},{"in":"path","name":"operand2","description":"The second operand","required":true,"type":"integer","my-extension":4}],"responses":{"200":{"description":"pet response","schema":{"type":"array","items":{"required":["id","name"],"type":"object","properties":{"id":{"format":"int64","type":"integer"},"name":{"type":"string"},"tag":{"type":"string"}}}}}}}}}} \ No newline at end of file +{"swagger":"2.0","info":{"title":"Swagger Petstore (Simple)","description":"A sample API that uses a petstore as an example to demonstrate features in the swagger-2.0 specification","version":"1.0.0"},"host":"petstore.swagger.io","basePath":"/api","schemes":["http"],"paths":{"/add/{operand1}/{operand2}":{"get":{"operationId":"addByOperand1AndByOperand2","produces":["application/json"],"parameters":[{"in":"path","name":"operand1","description":"The first operand","required":true,"type":"integer","my-extension":4},{"in":"path","name":"operand2","description":"The second operand","required":true,"type":"integer","my-extension":4}],"responses":{"200":{"description":"pet response","schema":{"type":"array","items":{"type":"object","required":["id","name"],"properties":{"id":{"type":"integer","format":"int64"},"name":{"type":"string"},"tag":{"type":"string"}}}}}}}}},"definitions":{"pet":{"type":"object","required":["id","name"],"properties":{"id":{"type":"integer","format":"int64"},"name":{"type":"string"},"tag":{"type":"string"}}},"newPet":{"type":"object","required":["name"],"properties":{"id":{"type":"integer","format":"int64"},"name":{"type":"string"},"tag":{"type":"string"}}},"errorModel":{"type":"object","required":["code","message"],"properties":{"code":{"type":"integer","format":"int32"},"message":{"type":"string"}}}}} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.SerializeDuplicateExtensionsAsV2JsonWorks_produceTerseOutput=True.verified.txt b/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.SerializeDuplicateExtensionsAsV2JsonWorks_produceTerseOutput=True.verified.txt new file mode 100644 index 000000000..d8e55a839 --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.SerializeDuplicateExtensionsAsV2JsonWorks_produceTerseOutput=True.verified.txt @@ -0,0 +1 @@ +{"swagger":"2.0","info":{"title":"Swagger Petstore (Simple)","description":"A sample API that uses a petstore as an example to demonstrate features in the swagger-2.0 specification","version":"1.0.0"},"host":"petstore.swagger.io","basePath":"/api","schemes":["http"],"paths":{"/add/{operand1}/{operand2}":{"get":{"operationId":"addByOperand1AndByOperand2","produces":["application/json"],"parameters":[{"in":"path","name":"operand1","description":"The first operand","required":true,"type":"integer","my-extension":4},{"in":"path","name":"operand2","description":"The second operand","required":true,"type":"integer","my-extension":4}],"responses":{"200":{"description":"pet response","schema":{"type":"array","items":{"type":"object","required":["id","name"],"properties":{"id":{"type":"integer","format":"int64"},"name":{"type":"string"},"tag":{"type":"string"}}}}}}}}}} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.SerializeDuplicateExtensionsAsV3JsonWorksAsync_produceTerseOutput=False.verified.txt b/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.SerializeDuplicateExtensionsAsV3JsonWorksAsync_produceTerseOutput=False.verified.txt index 26442924a..c4a235055 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.SerializeDuplicateExtensionsAsV3JsonWorksAsync_produceTerseOutput=False.verified.txt +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.SerializeDuplicateExtensionsAsV3JsonWorksAsync_produceTerseOutput=False.verified.txt @@ -71,5 +71,62 @@ } } } + }, + "components": { + "schemas": { + "pet": { + "required": [ + "id", + "name" + ], + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + }, + "tag": { + "type": "string" + } + } + }, + "newPet": { + "required": [ + "name" + ], + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + }, + "tag": { + "type": "string" + } + } + }, + "errorModel": { + "required": [ + "code", + "message" + ], + "type": "object", + "properties": { + "code": { + "type": "integer", + "format": "int32" + }, + "message": { + "type": "string" + } + } + } + } } } \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.SerializeDuplicateExtensionsAsV3JsonWorksAsync_produceTerseOutput=True.verified.txt b/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.SerializeDuplicateExtensionsAsV3JsonWorksAsync_produceTerseOutput=True.verified.txt index c5d124594..dc50aeb17 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.SerializeDuplicateExtensionsAsV3JsonWorksAsync_produceTerseOutput=True.verified.txt +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.SerializeDuplicateExtensionsAsV3JsonWorksAsync_produceTerseOutput=True.verified.txt @@ -1 +1 @@ -{"openapi":"3.0.1","info":{"title":"Swagger Petstore (Simple)","description":"A sample API that uses a petstore as an example to demonstrate features in the swagger-2.0 specification","version":"1.0.0"},"servers":[{"url":"http://petstore.swagger.io/api"}],"paths":{"/add/{operand1}/{operand2}":{"get":{"operationId":"addByOperand1AndByOperand2","parameters":[{"name":"operand1","in":"path","description":"The first operand","required":true,"schema":{"type":"integer","my-extension":4},"my-extension":4},{"name":"operand2","in":"path","description":"The second operand","required":true,"schema":{"type":"integer","my-extension":4},"my-extension":4}],"responses":{"200":{"description":"pet response","content":{"application/json":{"schema":{"type":"array","items":{"required":["id","name"],"type":"object","properties":{"id":{"type":"integer","format":"int64"},"name":{"type":"string"},"tag":{"type":"string"}}}}}}}}}}}} \ No newline at end of file +{"openapi":"3.0.1","info":{"title":"Swagger Petstore (Simple)","description":"A sample API that uses a petstore as an example to demonstrate features in the swagger-2.0 specification","version":"1.0.0"},"servers":[{"url":"http://petstore.swagger.io/api"}],"paths":{"/add/{operand1}/{operand2}":{"get":{"operationId":"addByOperand1AndByOperand2","parameters":[{"name":"operand1","in":"path","description":"The first operand","required":true,"schema":{"type":"integer","my-extension":4},"my-extension":4},{"name":"operand2","in":"path","description":"The second operand","required":true,"schema":{"type":"integer","my-extension":4},"my-extension":4}],"responses":{"200":{"description":"pet response","content":{"application/json":{"schema":{"type":"array","items":{"required":["id","name"],"type":"object","properties":{"id":{"type":"integer","format":"int64"},"name":{"type":"string"},"tag":{"type":"string"}}}}}}}}}}},"components":{"schemas":{"pet":{"required":["id","name"],"type":"object","properties":{"id":{"type":"integer","format":"int64"},"name":{"type":"string"},"tag":{"type":"string"}}},"newPet":{"required":["name"],"type":"object","properties":{"id":{"type":"integer","format":"int64"},"name":{"type":"string"},"tag":{"type":"string"}}},"errorModel":{"required":["code","message"],"type":"object","properties":{"code":{"type":"integer","format":"int32"},"message":{"type":"string"}}}}}} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.cs index aa9433dfe..b4ba37215 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.cs @@ -1,5 +1,5 @@ // Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. +// Licensed under the MIT license. using System; using System.Collections.Generic; @@ -7,40 +7,42 @@ using System.IO; using System.Threading.Tasks; using FluentAssertions; +using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Extensions; using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Models.References; +using Microsoft.OpenApi.Reader; using Microsoft.OpenApi.Readers; using Microsoft.OpenApi.Writers; +using Microsoft.VisualBasic; using VerifyXunit; using Xunit; +using Xunit.Abstractions; namespace Microsoft.OpenApi.Tests.Models { [Collection("DefaultSettings")] public class OpenApiDocumentTests { - public static OpenApiComponents TopLevelReferencingComponents = new() + public OpenApiDocumentTests() + { + OpenApiReaderRegistry.RegisterReader(OpenApiConstants.Yaml, new OpenApiYamlReader()); + } + + public static readonly OpenApiComponents TopLevelReferencingComponents = new OpenApiComponents() { Schemas = { - ["schema1"] = new() - { - Reference = new() - { - Type = ReferenceType.Schema, - Id = "schema2" - }, - Annotations = new Dictionary { { "x-foo", "bar" } } - }, + ["schema1"] = new OpenApiSchemaReference("schema2", null), ["schema2"] = new() { - Type = "object", + Type = JsonSchemaType.Object, Properties = { ["property1"] = new() { - Type = "string", + Type = JsonSchemaType.String, Annotations = new Dictionary { { "key1", "value" } } } } @@ -48,18 +50,18 @@ public class OpenApiDocumentTests } }; - public static OpenApiComponents TopLevelSelfReferencingComponentsWithOtherProperties = new() + public static readonly OpenApiComponents TopLevelSelfReferencingComponentsWithOtherProperties = new OpenApiComponents() { Schemas = { ["schema1"] = new() { - Type = "object", + Type = JsonSchemaType.Object, Properties = { ["property1"] = new() { - Type = "string", + Type = JsonSchemaType.String, Annotations = new Dictionary { { "key1", "value" } } } }, @@ -72,19 +74,20 @@ public class OpenApiDocumentTests }, ["schema2"] = new() { - Type = "object", + Type = JsonSchemaType.Object, Properties = { ["property1"] = new() { - Type = "string" + Type = JsonSchemaType.String } } }, } + }; - public static OpenApiComponents TopLevelSelfReferencingComponents = new() + public static readonly OpenApiComponents TopLevelSelfReferencingComponents = new OpenApiComponents() { Schemas = { @@ -99,9 +102,9 @@ public class OpenApiDocumentTests } }; - public static OpenApiDocument SimpleDocumentWithTopLevelReferencingComponents = new() + public static readonly OpenApiDocument SimpleDocumentWithTopLevelReferencingComponents = new OpenApiDocument() { - Info = new() + Info = new OpenApiInfo() { Version = "1.0.0" }, @@ -109,9 +112,9 @@ public class OpenApiDocumentTests Components = TopLevelReferencingComponents }; - public static OpenApiDocument SimpleDocumentWithTopLevelSelfReferencingComponentsWithOtherProperties = new() + public static readonly OpenApiDocument SimpleDocumentWithTopLevelSelfReferencingComponentsWithOtherProperties = new OpenApiDocument() { - Info = new() + Info = new OpenApiInfo() { Version = "1.0.0" }, @@ -119,9 +122,9 @@ public class OpenApiDocumentTests Components = TopLevelSelfReferencingComponentsWithOtherProperties }; - public static OpenApiDocument SimpleDocumentWithTopLevelSelfReferencingComponents = new() + public static readonly OpenApiDocument SimpleDocumentWithTopLevelSelfReferencingComponents = new OpenApiDocument() { - Info = new() + Info = new OpenApiInfo() { Version = "1.0.0" }, @@ -129,13 +132,13 @@ public class OpenApiDocumentTests Components = TopLevelSelfReferencingComponents }; - public static OpenApiComponents AdvancedComponentsWithReference = new() + public static readonly OpenApiComponents AdvancedComponentsWithReference = new OpenApiComponents { Schemas = new Dictionary { ["pet"] = new() { - Type = "object", + Type = JsonSchemaType.Object, Required = new HashSet { "id", @@ -145,27 +148,22 @@ public class OpenApiDocumentTests { ["id"] = new() { - Type = "integer", + Type = JsonSchemaType.Integer, Format = "int64" }, ["name"] = new() { - Type = "string" + Type = JsonSchemaType.String }, ["tag"] = new() { - Type = "string" + Type = JsonSchemaType.String }, - }, - Reference = new() - { - Id = "pet", - Type = ReferenceType.Schema } }, ["newPet"] = new() { - Type = "object", + Type = JsonSchemaType.Object, Required = new HashSet { "name" @@ -174,27 +172,22 @@ public class OpenApiDocumentTests { ["id"] = new() { - Type = "integer", + Type = JsonSchemaType.Integer, Format = "int64" }, ["name"] = new() { - Type = "string" + Type = JsonSchemaType.String }, ["tag"] = new() { - Type = "string" + Type = JsonSchemaType.String }, - }, - Reference = new() - { - Id = "newPet", - Type = ReferenceType.Schema } }, ["errorModel"] = new() { - Type = "object", + Type = JsonSchemaType.Object, Required = new HashSet { "code", @@ -204,18 +197,13 @@ public class OpenApiDocumentTests { ["code"] = new() { - Type = "integer", + Type = JsonSchemaType.Integer, Format = "int32" }, ["message"] = new() { - Type = "string" + Type = JsonSchemaType.String } - }, - Reference = new() - { - Id = "errorModel", - Type = ReferenceType.Schema } }, } @@ -228,47 +216,47 @@ public class OpenApiDocumentTests public static OpenApiSchema ErrorModelSchemaWithReference = AdvancedComponentsWithReference.Schemas["errorModel"]; - public static OpenApiDocument AdvancedDocumentWithReference = new() + public static readonly OpenApiDocument AdvancedDocumentWithReference = new OpenApiDocument { - Info = new() + Info = new OpenApiInfo { Version = "1.0.0", Title = "Swagger Petstore (Simple)", Description = "A sample API that uses a petstore as an example to demonstrate features in the swagger-2.0 specification", - TermsOfService = new("http://helloreverb.com/terms/"), - Contact = new() + TermsOfService = new Uri("http://helloreverb.com/terms/"), + Contact = new OpenApiContact { Name = "Swagger API team", Email = "foo@example.com", - Url = new("http://swagger.io") + Url = new Uri("http://swagger.io") }, - License = new() + License = new OpenApiLicense { Name = "MIT", - Url = new("http://opensource.org/licenses/MIT") + Url = new Uri("http://opensource.org/licenses/MIT") } }, Servers = new List { - new() + new OpenApiServer { Url = "http://petstore.swagger.io/api" } }, - Paths = new() + Paths = new OpenApiPaths { - ["/pets"] = new() + ["/pets"] = new OpenApiPathItem { Operations = new Dictionary { - [OperationType.Get] = new() + [OperationType.Get] = new OpenApiOperation { Description = "Returns all pets from the system that the user has access to", OperationId = "findPets", Parameters = new List { - new() + new OpenApiParameter { Name = "tags", In = ParameterLocation.Query, @@ -276,14 +264,14 @@ public class OpenApiDocumentTests Required = false, Schema = new() { - Type = "array", + Type = JsonSchemaType.Array, Items = new() { - Type = "string" + Type = JsonSchemaType.String } } }, - new() + new OpenApiParameter { Name = "limit", In = ParameterLocation.Query, @@ -291,53 +279,53 @@ public class OpenApiDocumentTests Required = false, Schema = new() { - Type = "integer", + Type = JsonSchemaType.Integer, Format = "int32" } } }, - Responses = new() + Responses = new OpenApiResponses { - ["200"] = new() + ["200"] = new OpenApiResponse { Description = "pet response", Content = new Dictionary { - ["application/json"] = new() + ["application/json"] = new OpenApiMediaType { Schema = new() { - Type = "array", + Type = JsonSchemaType.Array, Items = PetSchemaWithReference } }, - ["application/xml"] = new() + ["application/xml"] = new OpenApiMediaType { Schema = new() { - Type = "array", + Type = JsonSchemaType.Array, Items = PetSchemaWithReference } } } }, - ["4XX"] = new() + ["4XX"] = new OpenApiResponse { Description = "unexpected client error", Content = new Dictionary { - ["text/html"] = new() + ["text/html"] = new OpenApiMediaType { Schema = ErrorModelSchemaWithReference } } }, - ["5XX"] = new() + ["5XX"] = new OpenApiResponse { Description = "unexpected server error", Content = new Dictionary { - ["text/html"] = new() + ["text/html"] = new OpenApiMediaType { Schema = ErrorModelSchemaWithReference } @@ -345,52 +333,52 @@ public class OpenApiDocumentTests } } }, - [OperationType.Post] = new() + [OperationType.Post] = new OpenApiOperation { Description = "Creates a new pet in the store. Duplicates are allowed", OperationId = "addPet", - RequestBody = new() + RequestBody = new OpenApiRequestBody { Description = "Pet to add to the store", Required = true, Content = new Dictionary { - ["application/json"] = new() + ["application/json"] = new OpenApiMediaType { Schema = NewPetSchemaWithReference } } }, - Responses = new() + Responses = new OpenApiResponses { - ["200"] = new() + ["200"] = new OpenApiResponse { Description = "pet response", Content = new Dictionary { - ["application/json"] = new() + ["application/json"] = new OpenApiMediaType { Schema = PetSchemaWithReference }, } }, - ["4XX"] = new() + ["4XX"] = new OpenApiResponse { Description = "unexpected client error", Content = new Dictionary { - ["text/html"] = new() + ["text/html"] = new OpenApiMediaType { Schema = ErrorModelSchemaWithReference } } }, - ["5XX"] = new() + ["5XX"] = new OpenApiResponse { Description = "unexpected server error", Content = new Dictionary { - ["text/html"] = new() + ["text/html"] = new OpenApiMediaType { Schema = ErrorModelSchemaWithReference } @@ -400,18 +388,18 @@ public class OpenApiDocumentTests } } }, - ["/pets/{id}"] = new() + ["/pets/{id}"] = new OpenApiPathItem { Operations = new Dictionary { - [OperationType.Get] = new() + [OperationType.Get] = new OpenApiOperation { Description = "Returns a user based on a single ID, if the user does not have access to the pet", OperationId = "findPetById", Parameters = new List { - new() + new OpenApiParameter { Name = "id", In = ParameterLocation.Path, @@ -419,45 +407,45 @@ public class OpenApiDocumentTests Required = true, Schema = new() { - Type = "integer", + Type = JsonSchemaType.Integer, Format = "int64" } } }, - Responses = new() + Responses = new OpenApiResponses { - ["200"] = new() + ["200"] = new OpenApiResponse { Description = "pet response", Content = new Dictionary { - ["application/json"] = new() + ["application/json"] = new OpenApiMediaType { Schema = PetSchemaWithReference }, - ["application/xml"] = new() + ["application/xml"] = new OpenApiMediaType { Schema = PetSchemaWithReference } } }, - ["4XX"] = new() + ["4XX"] = new OpenApiResponse { Description = "unexpected client error", Content = new Dictionary { - ["text/html"] = new() + ["text/html"] = new OpenApiMediaType { Schema = ErrorModelSchemaWithReference } } }, - ["5XX"] = new() + ["5XX"] = new OpenApiResponse { Description = "unexpected server error", Content = new Dictionary { - ["text/html"] = new() + ["text/html"] = new OpenApiMediaType { Schema = ErrorModelSchemaWithReference } @@ -465,13 +453,13 @@ public class OpenApiDocumentTests } } }, - [OperationType.Delete] = new() + [OperationType.Delete] = new OpenApiOperation { Description = "deletes a single pet based on the ID supplied", OperationId = "deletePet", Parameters = new List { - new() + new OpenApiParameter { Name = "id", In = ParameterLocation.Path, @@ -479,34 +467,34 @@ public class OpenApiDocumentTests Required = true, Schema = new() { - Type = "integer", + Type = JsonSchemaType.Integer, Format = "int64" } } }, - Responses = new() + Responses = new OpenApiResponses { - ["204"] = new() + ["204"] = new OpenApiResponse { Description = "pet deleted" }, - ["4XX"] = new() + ["4XX"] = new OpenApiResponse { Description = "unexpected client error", Content = new Dictionary { - ["text/html"] = new() + ["text/html"] = new OpenApiMediaType { Schema = ErrorModelSchemaWithReference } } }, - ["5XX"] = new() + ["5XX"] = new OpenApiResponse { Description = "unexpected server error", Content = new Dictionary { - ["text/html"] = new() + ["text/html"] = new OpenApiMediaType { Schema = ErrorModelSchemaWithReference } @@ -521,13 +509,13 @@ public class OpenApiDocumentTests Components = AdvancedComponentsWithReference }; - public static OpenApiComponents AdvancedComponents = new() + public static readonly OpenApiComponents AdvancedComponents = new OpenApiComponents { Schemas = new Dictionary { ["pet"] = new() { - Type = "object", + Type = JsonSchemaType.Object, Required = new HashSet { "id", @@ -537,22 +525,22 @@ public class OpenApiDocumentTests { ["id"] = new() { - Type = "integer", + Type = JsonSchemaType.Integer, Format = "int64" }, ["name"] = new() { - Type = "string" + Type = JsonSchemaType.String }, ["tag"] = new() { - Type = "string" + Type = JsonSchemaType.String }, } }, ["newPet"] = new() { - Type = "object", + Type = JsonSchemaType.Object, Required = new HashSet { "name" @@ -561,22 +549,22 @@ public class OpenApiDocumentTests { ["id"] = new() { - Type = "integer", + Type = JsonSchemaType.Integer, Format = "int64" }, ["name"] = new() { - Type = "string" + Type = JsonSchemaType.String }, ["tag"] = new() { - Type = "string" + Type = JsonSchemaType.String }, } }, ["errorModel"] = new() { - Type = "object", + Type = JsonSchemaType.Object, Required = new HashSet { "code", @@ -586,65 +574,65 @@ public class OpenApiDocumentTests { ["code"] = new() { - Type = "integer", + Type = JsonSchemaType.Integer, Format = "int32" }, ["message"] = new() { - Type = "string" + Type = JsonSchemaType.String } } }, } }; - public static OpenApiSchema PetSchema = AdvancedComponents.Schemas["pet"]; + public static readonly OpenApiSchema PetSchema = AdvancedComponents.Schemas["pet"]; - public static OpenApiSchema NewPetSchema = AdvancedComponents.Schemas["newPet"]; + public static readonly OpenApiSchema NewPetSchema = AdvancedComponents.Schemas["newPet"]; - public static OpenApiSchema ErrorModelSchema = AdvancedComponents.Schemas["errorModel"]; + public static readonly OpenApiSchema ErrorModelSchema = AdvancedComponents.Schemas["errorModel"]; - public OpenApiDocument AdvancedDocument = new() + public OpenApiDocument AdvancedDocument = new OpenApiDocument { - Info = new() + Info = new OpenApiInfo { Version = "1.0.0", Title = "Swagger Petstore (Simple)", Description = "A sample API that uses a petstore as an example to demonstrate features in the swagger-2.0 specification", - TermsOfService = new("http://helloreverb.com/terms/"), - Contact = new() + TermsOfService = new Uri("http://helloreverb.com/terms/"), + Contact = new OpenApiContact { Name = "Swagger API team", Email = "foo@example.com", - Url = new("http://swagger.io") + Url = new Uri("http://swagger.io") }, - License = new() + License = new OpenApiLicense { Name = "MIT", - Url = new("http://opensource.org/licenses/MIT") + Url = new Uri("http://opensource.org/licenses/MIT") } }, Servers = new List { - new() + new OpenApiServer { Url = "http://petstore.swagger.io/api" } }, - Paths = new() + Paths = new OpenApiPaths { - ["/pets"] = new() + ["/pets"] = new OpenApiPathItem { Operations = new Dictionary { - [OperationType.Get] = new() + [OperationType.Get] = new OpenApiOperation { Description = "Returns all pets from the system that the user has access to", OperationId = "findPets", Parameters = new List { - new() + new OpenApiParameter { Name = "tags", In = ParameterLocation.Query, @@ -652,14 +640,14 @@ public class OpenApiDocumentTests Required = false, Schema = new() { - Type = "array", + Type = JsonSchemaType.Array, Items = new() { - Type = "string" + Type = JsonSchemaType.String } } }, - new() + new OpenApiParameter { Name = "limit", In = ParameterLocation.Query, @@ -667,53 +655,53 @@ public class OpenApiDocumentTests Required = false, Schema = new() { - Type = "integer", + Type = JsonSchemaType.Integer, Format = "int32" } } }, - Responses = new() + Responses = new OpenApiResponses { - ["200"] = new() + ["200"] = new OpenApiResponse { Description = "pet response", Content = new Dictionary { - ["application/json"] = new() + ["application/json"] = new OpenApiMediaType { Schema = new() { - Type = "array", + Type = JsonSchemaType.Array, Items = PetSchema } }, - ["application/xml"] = new() + ["application/xml"] = new OpenApiMediaType { Schema = new() { - Type = "array", + Type = JsonSchemaType.Array, Items = PetSchema } } } }, - ["4XX"] = new() + ["4XX"] = new OpenApiResponse { Description = "unexpected client error", Content = new Dictionary { - ["text/html"] = new() + ["text/html"] = new OpenApiMediaType { Schema = ErrorModelSchema } } }, - ["5XX"] = new() + ["5XX"] = new OpenApiResponse { Description = "unexpected server error", Content = new Dictionary { - ["text/html"] = new() + ["text/html"] = new OpenApiMediaType { Schema = ErrorModelSchema } @@ -721,52 +709,52 @@ public class OpenApiDocumentTests } } }, - [OperationType.Post] = new() + [OperationType.Post] = new OpenApiOperation { Description = "Creates a new pet in the store. Duplicates are allowed", OperationId = "addPet", - RequestBody = new() + RequestBody = new OpenApiRequestBody { Description = "Pet to add to the store", Required = true, Content = new Dictionary { - ["application/json"] = new() + ["application/json"] = new OpenApiMediaType { Schema = NewPetSchema } } }, - Responses = new() + Responses = new OpenApiResponses { - ["200"] = new() + ["200"] = new OpenApiResponse { Description = "pet response", Content = new Dictionary { - ["application/json"] = new() + ["application/json"] = new OpenApiMediaType { Schema = PetSchema }, } }, - ["4XX"] = new() + ["4XX"] = new OpenApiResponse { Description = "unexpected client error", Content = new Dictionary { - ["text/html"] = new() + ["text/html"] = new OpenApiMediaType { Schema = ErrorModelSchema } } }, - ["5XX"] = new() + ["5XX"] = new OpenApiResponse { Description = "unexpected server error", Content = new Dictionary { - ["text/html"] = new() + ["text/html"] = new OpenApiMediaType { Schema = ErrorModelSchema } @@ -776,18 +764,18 @@ public class OpenApiDocumentTests } } }, - ["/pets/{id}"] = new() + ["/pets/{id}"] = new OpenApiPathItem { Operations = new Dictionary { - [OperationType.Get] = new() + [OperationType.Get] = new OpenApiOperation { Description = "Returns a user based on a single ID, if the user does not have access to the pet", OperationId = "findPetById", Parameters = new List { - new() + new OpenApiParameter { Name = "id", In = ParameterLocation.Path, @@ -795,45 +783,45 @@ public class OpenApiDocumentTests Required = true, Schema = new() { - Type = "integer", + Type = JsonSchemaType.Integer, Format = "int64" } } }, - Responses = new() + Responses = new OpenApiResponses { - ["200"] = new() + ["200"] = new OpenApiResponse { Description = "pet response", Content = new Dictionary { - ["application/json"] = new() + ["application/json"] = new OpenApiMediaType { Schema = PetSchema }, - ["application/xml"] = new() + ["application/xml"] = new OpenApiMediaType { Schema = PetSchema } } }, - ["4XX"] = new() + ["4XX"] = new OpenApiResponse { Description = "unexpected client error", Content = new Dictionary { - ["text/html"] = new() + ["text/html"] = new OpenApiMediaType { Schema = ErrorModelSchema } } }, - ["5XX"] = new() + ["5XX"] = new OpenApiResponse { Description = "unexpected server error", Content = new Dictionary { - ["text/html"] = new() + ["text/html"] = new OpenApiMediaType { Schema = ErrorModelSchema } @@ -841,13 +829,13 @@ public class OpenApiDocumentTests } } }, - [OperationType.Delete] = new() + [OperationType.Delete] = new OpenApiOperation { Description = "deletes a single pet based on the ID supplied", OperationId = "deletePet", Parameters = new List { - new() + new OpenApiParameter { Name = "id", In = ParameterLocation.Path, @@ -855,34 +843,34 @@ public class OpenApiDocumentTests Required = true, Schema = new() { - Type = "integer", + Type = JsonSchemaType.Integer, Format = "int64" } } }, - Responses = new() + Responses = new OpenApiResponses { - ["204"] = new() + ["204"] = new OpenApiResponse { Description = "pet deleted" }, - ["4XX"] = new() + ["4XX"] = new OpenApiResponse { Description = "unexpected client error", Content = new Dictionary { - ["text/html"] = new() + ["text/html"] = new OpenApiMediaType { Schema = ErrorModelSchema } } }, - ["5XX"] = new() + ["5XX"] = new OpenApiResponse { Description = "unexpected server error", Content = new Dictionary { - ["text/html"] = new() + ["text/html"] = new OpenApiMediaType { Schema = ErrorModelSchema } @@ -897,9 +885,77 @@ public class OpenApiDocumentTests Components = AdvancedComponents }; - public static OpenApiDocument DuplicateExtensions = new() + public static readonly OpenApiDocument DocumentWithWebhooks = new OpenApiDocument() { - Info = new() + Info = new OpenApiInfo + { + Title = "Webhook Example", + Version = "1.0.0" + }, + Webhooks = new Dictionary + { + ["newPet"] = new OpenApiPathItem + { + Operations = new Dictionary + { + [OperationType.Post] = new OpenApiOperation + { + RequestBody = new OpenApiRequestBody + { + Description = "Information about a new pet in the system", + Content = new Dictionary + { + ["application/json"] = new OpenApiMediaType + { + Schema = new OpenApiSchemaReference("Pet", null) + } + } + }, + Responses = new OpenApiResponses + { + ["200"] = new OpenApiResponse + { + Description = "Return a 200 status to indicate that the data was received successfully" + } + } + } + } + } + }, + Components = new OpenApiComponents + { + Schemas = new Dictionary + { + ["Pet"] = new OpenApiSchema() + { + Required = new HashSet + { + "id", "name" + }, + Properties = new Dictionary + { + ["id"] = new() + { + Type = JsonSchemaType.Integer, + Format = "int64" + }, + ["name"] = new() + { + Type = JsonSchemaType.String + }, + ["tag"] = new() + { + Type = JsonSchemaType.String + }, + }, + } + } + } + }; + + public static readonly OpenApiDocument DuplicateExtensions = new OpenApiDocument + { + Info = new OpenApiInfo { Version = "1.0.0", Title = "Swagger Petstore (Simple)", @@ -907,23 +963,23 @@ public class OpenApiDocumentTests }, Servers = new List { - new() + new OpenApiServer { Url = "http://petstore.swagger.io/api" } }, - Paths = new() + Paths = new OpenApiPaths { - ["/add/{operand1}/{operand2}"] = new() + ["/add/{operand1}/{operand2}"] = new OpenApiPathItem { Operations = new Dictionary { - [OperationType.Get] = new() + [OperationType.Get] = new OpenApiOperation { OperationId = "addByOperand1AndByOperand2", Parameters = new List { - new() + new OpenApiParameter { Name = "operand1", In = ParameterLocation.Path, @@ -931,18 +987,18 @@ public class OpenApiDocumentTests Required = true, Schema = new() { - Type = "integer", + Type = JsonSchemaType.Integer, Extensions = new Dictionary { - ["my-extension"] = new Any.OpenApiInteger(4), + ["my-extension"] = new OpenApiAny(4) } - }, + }, Extensions = new Dictionary { - ["my-extension"] = new Any.OpenApiInteger(4), + ["my-extension"] = new OpenApiAny(4), } }, - new() + new OpenApiParameter { Name = "operand2", In = ParameterLocation.Path, @@ -950,30 +1006,30 @@ public class OpenApiDocumentTests Required = true, Schema = new() { - Type = "integer", + Type = JsonSchemaType.Integer, Extensions = new Dictionary { - ["my-extension"] = new Any.OpenApiInteger(4), + ["my-extension"] = new OpenApiAny(4) } }, Extensions = new Dictionary { - ["my-extension"] = new Any.OpenApiInteger(4), + ["my-extension"] = new OpenApiAny(4), } }, }, - Responses = new() + Responses = new OpenApiResponses { - ["200"] = new() + ["200"] = new OpenApiResponse { Description = "pet response", Content = new Dictionary { - ["application/json"] = new() + ["application/json"] = new OpenApiMediaType { Schema = new() { - Type = "array", + Type = JsonSchemaType.Array, Items = PetSchema } }, @@ -983,7 +1039,9 @@ public class OpenApiDocumentTests } } } - } + }, + Annotations = new Dictionary { { "key1", "value" } }, + Components = AdvancedComponents }; public OpenApiDocument AdvancedDocumentWithServerVariable = new() @@ -1041,10 +1099,10 @@ public class OpenApiDocumentTests Required = false, Schema = new() { - Type = "array", + Type = JsonSchemaType.Array, Items = new() { - Type = "string" + Type = JsonSchemaType.String } } }, @@ -1056,7 +1114,7 @@ public class OpenApiDocumentTests Required = false, Schema = new() { - Type = "integer", + Type = JsonSchemaType.Integer, Format = "int32" } } @@ -1072,7 +1130,7 @@ public class OpenApiDocumentTests { Schema = new() { - Type = "array", + Type = JsonSchemaType.Array, Items = PetSchema } }, @@ -1080,7 +1138,7 @@ public class OpenApiDocumentTests { Schema = new() { - Type = "array", + Type = JsonSchemaType.Array, Items = PetSchema } } @@ -1184,7 +1242,7 @@ public class OpenApiDocumentTests Required = true, Schema = new() { - Type = "integer", + Type = JsonSchemaType.Integer, Format = "int64" } } @@ -1244,7 +1302,7 @@ public class OpenApiDocumentTests Required = true, Schema = new() { - Type = "integer", + Type = JsonSchemaType.Integer, Format = "int64" } } @@ -1293,7 +1351,7 @@ public async Task SerializeAdvancedDocumentAsV3JsonWorksAsync(bool produceTerseO { // Arrange var outputStringWriter = new StringWriter(CultureInfo.InvariantCulture); - var writer = new OpenApiJsonWriter(outputStringWriter, new() { Terse = produceTerseOutput }); + var writer = new OpenApiJsonWriter(outputStringWriter, new OpenApiJsonWriterSettings { Terse = produceTerseOutput }); // Act AdvancedDocument.SerializeAsV3(writer); @@ -1310,7 +1368,7 @@ public async Task SerializeAdvancedDocumentWithReferenceAsV3JsonWorksAsync(bool { // Arrange var outputStringWriter = new StringWriter(CultureInfo.InvariantCulture); - var writer = new OpenApiJsonWriter(outputStringWriter, new() { Terse = produceTerseOutput }); + var writer = new OpenApiJsonWriter(outputStringWriter, new OpenApiJsonWriterSettings { Terse = produceTerseOutput }); // Act AdvancedDocumentWithReference.SerializeAsV3(writer); @@ -1344,7 +1402,7 @@ public async Task SerializeAdvancedDocumentAsV2JsonWorksAsync(bool produceTerseO { // Arrange var outputStringWriter = new StringWriter(CultureInfo.InvariantCulture); - var writer = new OpenApiJsonWriter(outputStringWriter, new() { Terse = produceTerseOutput }); + var writer = new OpenApiJsonWriter(outputStringWriter, new OpenApiJsonWriterSettings { Terse = produceTerseOutput }); // Act AdvancedDocument.SerializeAsV2(writer); @@ -1361,7 +1419,7 @@ public async Task SerializeDuplicateExtensionsAsV3JsonWorksAsync(bool produceTer { // Arrange var outputStringWriter = new StringWriter(CultureInfo.InvariantCulture); - var writer = new OpenApiJsonWriter(outputStringWriter, new() { Terse = produceTerseOutput }); + var writer = new OpenApiJsonWriter(outputStringWriter, new OpenApiJsonWriterSettings { Terse = produceTerseOutput }); // Act DuplicateExtensions.SerializeAsV3(writer); @@ -1378,7 +1436,7 @@ public async Task SerializeDuplicateExtensionsAsV2JsonWorksAsync(bool produceTer { // Arrange var outputStringWriter = new StringWriter(CultureInfo.InvariantCulture); - var writer = new OpenApiJsonWriter(outputStringWriter, new() { Terse = produceTerseOutput }); + var writer = new OpenApiJsonWriter(outputStringWriter, new OpenApiJsonWriterSettings { Terse = produceTerseOutput }); // Act DuplicateExtensions.SerializeAsV2(writer); @@ -1395,7 +1453,7 @@ public async Task SerializeAdvancedDocumentWithReferenceAsV2JsonWorksAsync(bool { // Arrange var outputStringWriter = new StringWriter(CultureInfo.InvariantCulture); - var writer = new OpenApiJsonWriter(outputStringWriter, new() { Terse = produceTerseOutput }); + var writer = new OpenApiJsonWriter(outputStringWriter, new OpenApiJsonWriterSettings { Terse = produceTerseOutput }); // Act AdvancedDocumentWithReference.SerializeAsV2(writer); @@ -1409,21 +1467,18 @@ public async Task SerializeAdvancedDocumentWithReferenceAsV2JsonWorksAsync(bool public void SerializeSimpleDocumentWithTopLevelReferencingComponentsAsYamlV2Works() { // Arrange - var expected = - """ - swagger: '2.0' - info: - version: 1.0.0 - paths: { } - definitions: - schema1: - $ref: '#/definitions/schema2' - schema2: - type: object - properties: - property1: - type: string - """; + var expected = @"swagger: '2.0' +info: + version: 1.0.0 +paths: { } +definitions: + schema1: + $ref: '#/definitions/schema2' + schema2: + type: object + properties: + property1: + type: string"; // Act var actual = SimpleDocumentWithTopLevelReferencingComponents.SerializeAsYaml(OpenApiSpecVersion.OpenApi2_0); @@ -1438,15 +1493,12 @@ public void SerializeSimpleDocumentWithTopLevelReferencingComponentsAsYamlV2Work public void SerializeSimpleDocumentWithTopLevelSelfReferencingComponentsAsYamlV3Works() { // Arrange - var expected = - """ - swagger: '2.0' - info: - version: 1.0.0 - paths: { } - definitions: - schema1: { } - """; + var expected = @"swagger: '2.0' +info: + version: 1.0.0 +paths: { } +definitions: + schema1: { }"; // Act var actual = SimpleDocumentWithTopLevelSelfReferencingComponents.SerializeAsYaml(OpenApiSpecVersion.OpenApi2_0); @@ -1461,24 +1513,21 @@ public void SerializeSimpleDocumentWithTopLevelSelfReferencingComponentsAsYamlV3 public void SerializeSimpleDocumentWithTopLevelSelfReferencingWithOtherPropertiesComponentsAsYamlV3Works() { // Arrange - var expected = - """ - swagger: '2.0' - info: - version: 1.0.0 - paths: { } - definitions: - schema1: - type: object - properties: - property1: - type: string - schema2: - type: object - properties: - property1: - type: string - """; + var expected = @"swagger: '2.0' +info: + version: 1.0.0 +paths: { } +definitions: + schema1: + type: object + properties: + property1: + type: string + schema2: + type: object + properties: + property1: + type: string"; // Act var actual = SimpleDocumentWithTopLevelSelfReferencingComponentsWithOtherProperties.SerializeAsYaml(OpenApiSpecVersion.OpenApi2_0); @@ -1493,28 +1542,28 @@ public void SerializeSimpleDocumentWithTopLevelSelfReferencingWithOtherPropertie public void SerializeDocumentWithReferenceButNoComponents() { // Arrange - var document = new OpenApiDocument + var document = new OpenApiDocument() { - Info = new() + Info = new OpenApiInfo { Title = "Test", Version = "1.0.0" }, - Paths = new() + Paths = new OpenApiPaths { - ["/"] = new() + ["/"] = new OpenApiPathItem { Operations = new Dictionary { - [OperationType.Get] = new() + [OperationType.Get] = new OpenApiOperation { - Responses = new() + Responses = new OpenApiResponses { - ["200"] = new() + ["200"] = new OpenApiResponse { - Content = new Dictionary + Content = new Dictionary() { - ["application/json"] = new() + ["application/json"] = new OpenApiMediaType { Schema = new() { @@ -1548,19 +1597,16 @@ public void SerializeRelativePathAsV2JsonWorks() { // Arrange var expected = - """ - swagger: '2.0' - info: - version: 1.0.0 - basePath: /server1 - paths: { } - """; - var doc = new OpenApiDocument + @"swagger: '2.0' +info: + version: 1.0.0 +basePath: /server1 +paths: { }"; + var doc = new OpenApiDocument() { - Info = new() { Version = "1.0.0" }, - Servers = new List - { - new() + Info = new OpenApiInfo() { Version = "1.0.0" }, + Servers = new List() { + new OpenApiServer() { Url = "/server1" } @@ -1581,20 +1627,17 @@ public void SerializeRelativePathWithHostAsV2JsonWorks() { // Arrange var expected = - """ - swagger: '2.0' - info: - version: 1.0.0 - host: //example.org - basePath: /server1 - paths: { } - """; - var doc = new OpenApiDocument + @"swagger: '2.0' +info: + version: 1.0.0 +host: //example.org +basePath: /server1 +paths: { }"; + var doc = new OpenApiDocument() { - Info = new() { Version = "1.0.0" }, - Servers = new List - { - new() + Info = new OpenApiInfo() { Version = "1.0.0" }, + Servers = new List() { + new OpenApiServer() { Url = "//example.org/server1" } @@ -1615,19 +1658,16 @@ public void SerializeRelativeRootPathWithHostAsV2JsonWorks() { // Arrange var expected = - """ - swagger: '2.0' - info: - version: 1.0.0 - host: //example.org - paths: { } - """; - var doc = new OpenApiDocument + @"swagger: '2.0' +info: + version: 1.0.0 +host: //example.org +paths: { }"; + var doc = new OpenApiDocument() { - Info = new() { Version = "1.0.0" }, - Servers = new List - { - new() + Info = new OpenApiInfo() { Version = "1.0.0" }, + Servers = new List() { + new OpenApiServer() { Url = "//example.org/" } @@ -1665,8 +1705,9 @@ And reading in similar documents(one has a whitespace) yields the same hash code private static OpenApiDocument ParseInputFile(string filePath) { // Read in the input yaml file - using var stream = File.OpenRead(filePath); - var openApiDoc = new OpenApiStreamReader().Read(stream, out var diagnostic); + using FileStream stream = File.OpenRead(filePath); + var format = OpenApiModelFactory.GetFormat(filePath); + var openApiDoc = OpenApiDocument.Load(stream, format).OpenApiDocument; return openApiDoc; } @@ -1675,42 +1716,39 @@ private static OpenApiDocument ParseInputFile(string filePath) public void SerializeV2DocumentWithNonArraySchemaTypeDoesNotWriteOutCollectionFormat() { // Arrange - var expected = - """ - swagger: '2.0' - info: { } - paths: - /foo: - get: - parameters: - - in: query - type: string - responses: { } - """; + var expected = @"swagger: '2.0' +info: { } +paths: + /foo: + get: + parameters: + - in: query + type: string + responses: { }"; var doc = new OpenApiDocument { - Info = new(), - Paths = new() + Info = new OpenApiInfo(), + Paths = new OpenApiPaths { - ["/foo"] = new() + ["/foo"] = new OpenApiPathItem { Operations = new Dictionary { - [OperationType.Get] = new() + [OperationType.Get] = new OpenApiOperation { Parameters = new List { - new() + new OpenApiParameter { In = ParameterLocation.Query, Schema = new() { - Type = "string" + Type = JsonSchemaType.String } } }, - Responses = new() + Responses = new OpenApiResponses() } } } @@ -1730,74 +1768,71 @@ public void SerializeV2DocumentWithNonArraySchemaTypeDoesNotWriteOutCollectionFo public void SerializeV2DocumentWithStyleAsNullDoesNotWriteOutStyleValue() { // Arrange - var expected = - """ - openapi: 3.0.1 - info: - title: magic style - version: 1.0.0 - paths: - /foo: - get: - parameters: - - name: id - in: query - schema: - type: object - additionalProperties: - type: integer - responses: - '200': - description: foo - content: - text/plain: - schema: - type: string - """; + var expected = @"openapi: 3.0.1 +info: + title: magic style + version: 1.0.0 +paths: + /foo: + get: + parameters: + - name: id + in: query + schema: + type: object + additionalProperties: + type: integer + responses: + '200': + description: foo + content: + text/plain: + schema: + type: string"; var doc = new OpenApiDocument { - Info = new() + Info = new OpenApiInfo { Title = "magic style", Version = "1.0.0" }, - Paths = new() + Paths = new OpenApiPaths { - ["/foo"] = new() + ["/foo"] = new OpenApiPathItem { Operations = new Dictionary { - [OperationType.Get] = new() + [OperationType.Get] = new OpenApiOperation { Parameters = new List { - new() + new OpenApiParameter { Name = "id", In = ParameterLocation.Query, Schema = new() { - Type = "object", + Type = JsonSchemaType.Object, AdditionalProperties = new() { - Type = "integer" + Type = JsonSchemaType.Integer } } } }, - Responses = new() + Responses = new OpenApiResponses { - ["200"] = new() + ["200"] = new OpenApiResponse { Description = "foo", Content = new Dictionary { - ["text/plain"] = new() + ["text/plain"] = new OpenApiMediaType { Schema = new() { - Type = "string" + Type = JsonSchemaType.String } } } @@ -1871,5 +1906,171 @@ public void SerializeExamplesDoesNotThrowNullReferenceException() OpenApiJsonWriter apiWriter = new OpenApiJsonWriter(new StringWriter()); doc.Invoking(d => d.SerializeAsV3(apiWriter)).Should().NotThrow(); } - } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task SerializeDocumentWithWebhooksAsV3JsonWorks(bool produceTerseOutput) + { + // Arrange + var outputStringWriter = new StringWriter(CultureInfo.InvariantCulture); + var writer = new OpenApiJsonWriter(outputStringWriter, new OpenApiJsonWriterSettings { Terse = produceTerseOutput }); + + // Act + DocumentWithWebhooks.SerializeAsV31(writer); + writer.Flush(); + var actual = outputStringWriter.GetStringBuilder().ToString(); + + // Assert + await Verifier.Verify(actual).UseParameters(produceTerseOutput); + } + + [Fact] + public void SerializeDocumentWithWebhooksAsV3YamlWorks() + { + // Arrange + var expected = @"openapi: '3.1.0' +info: + title: Webhook Example + version: 1.0.0 +paths: { } +components: + schemas: + Pet: + required: + - id + - name + properties: + id: + type: integer + format: int64 + name: + type: string + tag: + type: string +webhooks: + newPet: + post: + requestBody: + description: Information about a new pet in the system + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + responses: + '200': + description: Return a 200 status to indicate that the data was received successfully"; + + // Act + var actual = DocumentWithWebhooks.SerializeAsYaml(OpenApiSpecVersion.OpenApi3_1); + + // Assert + actual = actual.MakeLineBreaksEnvironmentNeutral(); + expected = expected.MakeLineBreaksEnvironmentNeutral(); + actual.Should().BeEquivalentTo(expected); + } + + [Fact] + public void SerializeDocumentWithRootJsonSchemaDialectPropertyWorks() + { + // Arrange + var doc = new OpenApiDocument + { + Info = new OpenApiInfo + { + Title = "JsonSchemaDialectTest", + Version = "1.0.0" + }, + JsonSchemaDialect = "http://json-schema.org/draft-07/schema#" + }; + + var expected = @"openapi: '3.1.0' +jsonSchemaDialect: http://json-schema.org/draft-07/schema# +info: + title: JsonSchemaDialectTest + version: 1.0.0 +paths: { }"; + + // Act + var actual = doc.SerializeAsYaml(OpenApiSpecVersion.OpenApi3_1); + + // Assert + actual.MakeLineBreaksEnvironmentNeutral().Should().BeEquivalentTo(expected.MakeLineBreaksEnvironmentNeutral()); + } + + [Fact] + public void SerializeV31DocumentWithRefsInWebhooksWorks() + { + var expected = @"description: Returns all pets from the system that the user has access to +operationId: findPets +responses: + '200': + description: pet response + content: + application/json: + schema: + type: array + items: + type: object"; + + var doc = OpenApiDocument.Load("Models/Samples/docWithReusableWebhooks.yaml").OpenApiDocument; + + var stringWriter = new StringWriter(); + var writer = new OpenApiYamlWriter(stringWriter, new OpenApiWriterSettings { InlineLocalReferences = true }); + var webhooks = doc.Webhooks["pets"].Operations; + + webhooks[OperationType.Get].SerializeAsV31(writer); + var actual = stringWriter.ToString(); + actual.MakeLineBreaksEnvironmentNeutral().Should().BeEquivalentTo(expected.MakeLineBreaksEnvironmentNeutral()); + } + + [Fact] + public void SerializeDocWithDollarIdInDollarRefSucceeds() + { + var expected = @"openapi: '3.1.0' +info: + title: Simple API + version: 1.0.0 +paths: + /box: + get: + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: https://foo.bar/Box + /circle: + get: + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: https://foo.bar/Circle +components: + schemas: + Box: + $id: https://foo.bar/Box + type: object + properties: + width: + type: number + height: + type: number + Circle: + $id: https://foo.bar/Circle + type: object + properties: + radius: + type: number +"; + var doc = OpenApiDocument.Load("Models/Samples/docWithDollarId.yaml").OpenApiDocument; + + var actual = doc.SerializeAsYaml(OpenApiSpecVersion.OpenApi3_1); + actual.MakeLineBreaksEnvironmentNeutral().Should().BeEquivalentTo(expected.MakeLineBreaksEnvironmentNeutral()); + } + } } diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiExampleTests.SerializeAdvancedExampleAsV3JsonWorksAsync_produceTerseOutput=False.verified.txt b/test/Microsoft.OpenApi.Tests/Models/OpenApiExampleTests.SerializeAdvancedExampleAsV3JsonWorksAsync_produceTerseOutput=False.verified.txt index 755152dc0..a9950245b 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiExampleTests.SerializeAdvancedExampleAsV3JsonWorksAsync_produceTerseOutput=False.verified.txt +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiExampleTests.SerializeAdvancedExampleAsV3JsonWorksAsync_produceTerseOutput=False.verified.txt @@ -8,7 +8,7 @@ { "href": "http://example.com/1", "rel": "sampleRel1", - "bytes": "AQID", + "bytes": "\"AQID\"", "binary": "Ñ😻😑♮Í☛oƞ♑😲☇éNjžŁ♻😟¥a´Ī♃ƠąøƩ" } ] diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiExampleTests.SerializeAdvancedExampleAsV3JsonWorksAsync_produceTerseOutput=True.verified.txt b/test/Microsoft.OpenApi.Tests/Models/OpenApiExampleTests.SerializeAdvancedExampleAsV3JsonWorksAsync_produceTerseOutput=True.verified.txt index 2fd0836a4..4dfb0ce93 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiExampleTests.SerializeAdvancedExampleAsV3JsonWorksAsync_produceTerseOutput=True.verified.txt +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiExampleTests.SerializeAdvancedExampleAsV3JsonWorksAsync_produceTerseOutput=True.verified.txt @@ -1 +1 @@ -{"value":{"versions":[{"status":"Status1","id":"v1","links":[{"href":"http://example.com/1","rel":"sampleRel1","bytes":"AQID","binary":"Ñ😻😑♮Í☛oƞ♑😲☇éNjžŁ♻😟¥a´Ī♃ƠąøƩ"}]},{"status":"Status2","id":"v2","links":[{"href":"http://example.com/2","rel":"sampleRel2"}]}]}} \ No newline at end of file +{"value":{"versions":[{"status":"Status1","id":"v1","links":[{"href":"http://example.com/1","rel":"sampleRel1","bytes":"\"AQID\"","binary":"Ñ😻😑♮Í☛oƞ♑😲☇éNjžŁ♻😟¥a´Ī♃ƠąøƩ"}]},{"status":"Status2","id":"v2","links":[{"href":"http://example.com/2","rel":"sampleRel2"}]}]}} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiExampleTests.SerializeAdvancedExampleAsV3JsonWorks_produceTerseOutput=True.verified.txt b/test/Microsoft.OpenApi.Tests/Models/OpenApiExampleTests.SerializeAdvancedExampleAsV3JsonWorks_produceTerseOutput=True.verified.txt new file mode 100644 index 000000000..ebafd4dcb --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiExampleTests.SerializeAdvancedExampleAsV3JsonWorks_produceTerseOutput=True.verified.txt @@ -0,0 +1 @@ +{"value":{"versions":[{"status":"Status1","id":"v1","links":[{"href":"http://example.com/1","rel":"sampleRel1","bytes":"\"AQID\"","binary":"Ñ😻😑♮Í☛oƞ♑😲☇éNjžŁ♻😟¥a´Ī♃ƠąøƩ"}]},{"status":"Status2","id":"v2","links":[{"href":"http://example.com/2","rel":"sampleRel2"}]}]}} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiExampleTests.SerializeReferencedExampleAsV3JsonWithoutReferenceWorksAsync_produceTerseOutput=False.verified.txt b/test/Microsoft.OpenApi.Tests/Models/OpenApiExampleTests.SerializeReferencedExampleAsV3JsonWithoutReferenceWorksAsync_produceTerseOutput=False.verified.txt index ba74d9125..6282fd236 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiExampleTests.SerializeReferencedExampleAsV3JsonWithoutReferenceWorksAsync_produceTerseOutput=False.verified.txt +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiExampleTests.SerializeReferencedExampleAsV3JsonWithoutReferenceWorksAsync_produceTerseOutput=False.verified.txt @@ -22,6 +22,6 @@ ] } ], - "aDate": "2022-12-12" + "aDate": "\"2022-12-12\"" } } \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiExampleTests.SerializeReferencedExampleAsV3JsonWithoutReferenceWorksAsync_produceTerseOutput=True.verified.txt b/test/Microsoft.OpenApi.Tests/Models/OpenApiExampleTests.SerializeReferencedExampleAsV3JsonWithoutReferenceWorksAsync_produceTerseOutput=True.verified.txt index bbc944fee..c319c88f1 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiExampleTests.SerializeReferencedExampleAsV3JsonWithoutReferenceWorksAsync_produceTerseOutput=True.verified.txt +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiExampleTests.SerializeReferencedExampleAsV3JsonWithoutReferenceWorksAsync_produceTerseOutput=True.verified.txt @@ -1 +1 @@ -{"value":{"versions":[{"status":"Status1","id":"v1","links":[{"href":"http://example.com/1","rel":"sampleRel1"}]},{"status":"Status2","id":"v2","links":[{"href":"http://example.com/2","rel":"sampleRel2"}]}],"aDate":"2022-12-12"}} \ No newline at end of file +{"value":{"versions":[{"status":"Status1","id":"v1","links":[{"href":"http://example.com/1","rel":"sampleRel1"}]},{"status":"Status2","id":"v2","links":[{"href":"http://example.com/2","rel":"sampleRel2"}]}],"aDate":"\"2022-12-12\""}} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiExampleTests.SerializeReferencedExampleAsV3JsonWithoutReferenceWorks_produceTerseOutput=True.verified.txt b/test/Microsoft.OpenApi.Tests/Models/OpenApiExampleTests.SerializeReferencedExampleAsV3JsonWithoutReferenceWorks_produceTerseOutput=True.verified.txt new file mode 100644 index 000000000..ed5847ee5 --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiExampleTests.SerializeReferencedExampleAsV3JsonWithoutReferenceWorks_produceTerseOutput=True.verified.txt @@ -0,0 +1 @@ +{"value":{"versions":[{"status":"Status1","id":"v1","links":[{"href":"http://example.com/1","rel":"sampleRel1"}]},{"status":"Status2","id":"v2","links":[{"href":"http://example.com/2","rel":"sampleRel2"}]}],"aDate":"\"2022-12-12\""}} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiExampleTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiExampleTests.cs index 8341ac4e9..023f4d78f 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiExampleTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiExampleTests.cs @@ -5,9 +5,12 @@ using System.Globalization; using System.IO; using System.Text; +using System.Text.Json; +using System.Text.Json.Nodes; using System.Threading.Tasks; using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Models.References; using Microsoft.OpenApi.Writers; using VerifyXunit; using Xunit; @@ -19,36 +22,35 @@ public class OpenApiExampleTests { public static OpenApiExample AdvancedExample = new() { - Value = new OpenApiObject + Value = new JsonObject { - ["versions"] = new OpenApiArray + ["versions"] = new JsonArray { - new OpenApiObject + new JsonObject { - ["status"] = new OpenApiString("Status1"), - ["id"] = new OpenApiString("v1"), - ["links"] = new OpenApiArray + ["status"] = "Status1", + ["id"] = "v1", + ["links"] = new JsonArray { - new OpenApiObject + new JsonObject { - ["href"] = new OpenApiString("http://example.com/1"), - ["rel"] = new OpenApiString("sampleRel1"), - ["bytes"] = new OpenApiByte(new byte[] { 1, 2, 3 }), - ["binary"] = new OpenApiBinary(Encoding.UTF8.GetBytes("Ñ😻😑♮Í☛oƞ♑😲☇éNjžŁ♻😟¥a´Ī♃ƠąøƩ")) + ["href"] = "http://example.com/1", + ["rel"] = "sampleRel1", + ["bytes"] = JsonSerializer.Serialize(new byte[] { 1, 2, 3 }), + ["binary"] = Encoding.UTF8.GetString(Encoding.UTF8.GetBytes("Ñ😻😑♮Í☛oƞ♑😲☇éNjžŁ♻😟¥a´Ī♃ƠąøƩ")) } } }, - - new OpenApiObject + new JsonObject { - ["status"] = new OpenApiString("Status2"), - ["id"] = new OpenApiString("v2"), - ["links"] = new OpenApiArray + ["status"] = "Status2", + ["id"] = "v2", + ["links"] = new JsonArray { - new OpenApiObject + new JsonObject { - ["href"] = new OpenApiString("http://example.com/2"), - ["rel"] = new OpenApiString("sampleRel2") + ["href"] = "http://example.com/2", + ["rel"] = "sampleRel2" } } } @@ -56,46 +58,42 @@ public class OpenApiExampleTests } }; + public static OpenApiExampleReference OpenApiExampleReference = new(ReferencedExample, "example1"); public static OpenApiExample ReferencedExample = new() { - Reference = new() - { - Type = ReferenceType.Example, - Id = "example1", - }, - Value = new OpenApiObject + Value = new JsonObject { - ["versions"] = new OpenApiArray + ["versions"] = new JsonArray { - new OpenApiObject + new JsonObject { - ["status"] = new OpenApiString("Status1"), - ["id"] = new OpenApiString("v1"), - ["links"] = new OpenApiArray + ["status"] = "Status1", + ["id"] = "v1", + ["links"] = new JsonArray { - new OpenApiObject + new JsonObject { - ["href"] = new OpenApiString("http://example.com/1"), - ["rel"] = new OpenApiString("sampleRel1") + ["href"] = "http://example.com/1", + ["rel"] = "sampleRel1" } } }, - new OpenApiObject + new JsonObject { - ["status"] = new OpenApiString("Status2"), - ["id"] = new OpenApiString("v2"), - ["links"] = new OpenApiArray + ["status"] = "Status2", + ["id"] = "v2", + ["links"] = new JsonArray { - new OpenApiObject + new JsonObject { - ["href"] = new OpenApiString("http://example.com/2"), - ["rel"] = new OpenApiString("sampleRel2") + ["href"] = "http://example.com/2", + ["rel"] = "sampleRel2" } } } }, - ["aDate"] = new OpenApiDate(DateTime.Parse("12/12/2022 00:00:00")) + ["aDate"] = JsonSerializer.Serialize(DateTime.Parse("12/12/2022 00:00:00").ToString("yyyy-MM-dd")) } }; @@ -126,7 +124,7 @@ public async Task SerializeReferencedExampleAsV3JsonWorksAsync(bool produceTerse var writer = new OpenApiJsonWriter(outputStringWriter, new() { Terse = produceTerseOutput }); // Act - ReferencedExample.SerializeAsV3(writer); + OpenApiExampleReference.SerializeAsV3(writer); writer.Flush(); // Assert @@ -143,7 +141,7 @@ public async Task SerializeReferencedExampleAsV3JsonWithoutReferenceWorksAsync(b var writer = new OpenApiJsonWriter(outputStringWriter, new() { Terse = produceTerseOutput }); // Act - ReferencedExample.SerializeAsV3WithoutReference(writer); + ReferencedExample.SerializeAsV3(writer); writer.Flush(); // Assert diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiHeaderTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiHeaderTests.cs index f8ebfa95a..72c4fdfaa 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiHeaderTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiHeaderTests.cs @@ -5,6 +5,7 @@ using System.IO; using System.Threading.Tasks; using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Models.References; using Microsoft.OpenApi.Writers; using VerifyXunit; using Xunit; @@ -19,22 +20,19 @@ public class OpenApiHeaderTests Description = "sampleHeader", Schema = new() { - Type = "integer", + Type = JsonSchemaType.Integer, Format = "int32" } }; + public static OpenApiHeaderReference OpenApiHeaderReference = new(ReferencedHeader, "example1"); + public static OpenApiHeader ReferencedHeader = new() { - Reference = new() - { - Type = ReferenceType.Header, - Id = "example1", - }, Description = "sampleHeader", Schema = new() { - Type = "integer", + Type = JsonSchemaType.Integer, Format = "int32" } }; @@ -66,7 +64,7 @@ public async Task SerializeReferencedHeaderAsV3JsonWorksAsync(bool produceTerseO var writer = new OpenApiJsonWriter(outputStringWriter, new() { Terse = produceTerseOutput }); // Act - ReferencedHeader.SerializeAsV3(writer); + OpenApiHeaderReference.SerializeAsV3(writer); writer.Flush(); // Assert @@ -83,9 +81,8 @@ public async Task SerializeReferencedHeaderAsV3JsonWithoutReferenceWorksAsync(bo var writer = new OpenApiJsonWriter(outputStringWriter, new() { Terse = produceTerseOutput }); // Act - ReferencedHeader.SerializeAsV3WithoutReference(writer); + ReferencedHeader.SerializeAsV3(writer); writer.Flush(); - var actual = outputStringWriter.GetStringBuilder().ToString(); // Assert await Verifier.Verify(outputStringWriter).UseParameters(produceTerseOutput); @@ -118,7 +115,7 @@ public async Task SerializeReferencedHeaderAsV2JsonWorksAsync(bool produceTerseO var writer = new OpenApiJsonWriter(outputStringWriter, new() { Terse = produceTerseOutput }); // Act - ReferencedHeader.SerializeAsV2(writer); + OpenApiHeaderReference.SerializeAsV2(writer); writer.Flush(); // Assert @@ -135,7 +132,7 @@ public async Task SerializeReferencedHeaderAsV2JsonWithoutReferenceWorksAsync(bo var writer = new OpenApiJsonWriter(outputStringWriter, new() { Terse = produceTerseOutput }); // Act - ReferencedHeader.SerializeAsV2WithoutReference(writer); + ReferencedHeader.SerializeAsV2(writer); writer.Flush(); // Assert diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiInfoTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiInfoTests.cs index aed69a7a8..980ce0565 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiInfoTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiInfoTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. using System.Collections.Generic; @@ -24,11 +24,19 @@ public class OpenApiInfoTests Version = "1.1.1", Extensions = new Dictionary { - {"x-updated", new OpenApiString("metadata")} + {"x-updated", new OpenApiAny("metadata")} } }; - public static OpenApiInfo BasicInfo = new() + public static OpenApiInfo InfoWithSummary = new() + { + Title = "Sample Pet Store App", + Summary = "This is a sample server for a pet store.", + Description = "This is a sample server for a pet store.", + Version = "1.1.1", + }; + + public static OpenApiInfo BasicInfo = new OpenApiInfo { Title = "Sample Pet Store App", Version = "1.0" @@ -204,5 +212,43 @@ public void InfoVersionShouldAcceptDateStyledAsVersions() expected = expected.MakeLineBreaksEnvironmentNeutral(); actual.Should().Be(expected); } + + [Fact] + public void SerializeInfoObjectWithSummaryAsV31YamlWorks() + { + // Arrange + var expected = @"title: Sample Pet Store App +description: This is a sample server for a pet store. +version: '1.1.1' +summary: This is a sample server for a pet store."; + + // Act + var actual = InfoWithSummary.SerializeAsYaml(OpenApiSpecVersion.OpenApi3_1); + + // Assert + actual = actual.MakeLineBreaksEnvironmentNeutral(); + expected = expected.MakeLineBreaksEnvironmentNeutral(); + Assert.Equal(expected, actual); + } + + [Fact] + public void SerializeInfoObjectWithSummaryAsV31JsonWorks() + { + // Arrange + var expected = @"{ + ""title"": ""Sample Pet Store App"", + ""description"": ""This is a sample server for a pet store."", + ""version"": ""1.1.1"", + ""summary"": ""This is a sample server for a pet store."" +}"; + + // Act + var actual = InfoWithSummary.SerializeAsJson(OpenApiSpecVersion.OpenApi3_1); + + // Assert + actual = actual.MakeLineBreaksEnvironmentNeutral(); + expected = expected.MakeLineBreaksEnvironmentNeutral(); + Assert.Equal(expected, actual); + } } } diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiLicenseTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiLicenseTests.cs index e5620427b..bff5efe2c 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiLicenseTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiLicenseTests.cs @@ -25,10 +25,16 @@ public class OpenApiLicenseTests Url = new("http://www.apache.org/licenses/LICENSE-2.0.html"), Extensions = new Dictionary { - {"x-copyright", new OpenApiString("Abc")} + {"x-copyright", new OpenApiAny("Abc")} } }; + public static OpenApiLicense LicenseWithIdentifier = new OpenApiLicense + { + Name = "Apache 2.0", + Identifier = "Apache-2.0" + }; + [Theory] [InlineData(OpenApiSpecVersion.OpenApi3_0)] [InlineData(OpenApiSpecVersion.OpenApi2_0)] @@ -128,5 +134,36 @@ public void ShouldCopyFromOriginalObjectWithoutMutating() Assert.NotEqual(AdvanceLicense.Name, licenseCopy.Name); Assert.NotEqual(AdvanceLicense.Url, licenseCopy.Url); } + + [Fact] + public void SerializeLicenseWithIdentifierAsJsonWorks() + { + // Arrange + var expected = + @"{ + ""name"": ""Apache 2.0"", + ""identifier"": ""Apache-2.0"" +}"; + + // Act + var actual = LicenseWithIdentifier.SerializeAsJson(OpenApiSpecVersion.OpenApi3_1); + + // Assert + Assert.Equal(expected.MakeLineBreaksEnvironmentNeutral(), actual.MakeLineBreaksEnvironmentNeutral()); + } + + [Fact] + public void SerializeLicenseWithIdentifierAsYamlWorks() + { + // Arrange + var expected = @"name: Apache 2.0 +identifier: Apache-2.0"; + + // Act + var actual = LicenseWithIdentifier.SerializeAsYaml(OpenApiSpecVersion.OpenApi3_1); + + // Assert + Assert.Equal(expected.MakeLineBreaksEnvironmentNeutral(), actual.MakeLineBreaksEnvironmentNeutral()); + } } } diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiLinkTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiLinkTests.cs index f1a67c330..194d909b1 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiLinkTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiLinkTests.cs @@ -3,10 +3,12 @@ using System.Globalization; using System.IO; +using System.Text.Json.Nodes; using System.Threading.Tasks; using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Expressions; using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Models.References; using Microsoft.OpenApi.Writers; using VerifyXunit; using Xunit; @@ -28,9 +30,9 @@ public class OpenApiLinkTests }, RequestBody = new() { - Any = new OpenApiObject + Any = new JsonObject { - ["property1"] = new OpenApiBoolean(true) + ["property1"] = true } }, Description = "description1", @@ -40,6 +42,7 @@ public class OpenApiLinkTests } }; + public static readonly OpenApiLinkReference LinkReference = new(ReferencedLink, "example1"); public static readonly OpenApiLink ReferencedLink = new() { Reference = new() @@ -57,9 +60,9 @@ public class OpenApiLinkTests }, RequestBody = new() { - Any = new OpenApiObject + Any = new JsonObject { - ["property1"] = new OpenApiBoolean(true) + ["property1"] = true } }, Description = "description1", @@ -96,7 +99,7 @@ public async Task SerializeReferencedLinkAsV3JsonWorksAsync(bool produceTerseOut var writer = new OpenApiJsonWriter(outputStringWriter, new() { Terse = produceTerseOutput }); // Act - ReferencedLink.SerializeAsV3(writer); + LinkReference.SerializeAsV3(writer); writer.Flush(); // Assert @@ -113,7 +116,7 @@ public async Task SerializeReferencedLinkAsV3JsonWithoutReferenceWorksAsync(bool var writer = new OpenApiJsonWriter(outputStringWriter, new() { Terse = produceTerseOutput }); // Act - ReferencedLink.SerializeAsV3WithoutReference(writer); + ReferencedLink.SerializeAsV3(writer); writer.Flush(); // Assert @@ -127,7 +130,7 @@ public void LinkExtensionsSerializationWorks() var link = new OpenApiLink() { Extensions = { - { "x-display", new OpenApiString("Abc") } + { "x-display", new OpenApiAny("Abc") } } }; diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiMediaTypeTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiMediaTypeTests.cs index 97addbb1e..d4eecf7ee 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiMediaTypeTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiMediaTypeTests.cs @@ -2,9 +2,11 @@ // Licensed under the MIT license. using System.Collections.Generic; +using System.Text.Json.Nodes; using FluentAssertions; using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Extensions; +using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Models; using Xunit; using Xunit.Abstractions; @@ -17,8 +19,8 @@ public class OpenApiMediaTypeTests public static OpenApiMediaType BasicMediaType = new(); public static OpenApiMediaType AdvanceMediaType = new() - { - Example = new OpenApiInteger(42), + { + Example = 42, Encoding = new Dictionary { {"testEncoding", OpenApiEncodingTests.AdvanceEncoding} @@ -27,34 +29,34 @@ public class OpenApiMediaTypeTests public static OpenApiMediaType MediaTypeWithObjectExample = new() { - Example = new OpenApiObject + Example = new JsonObject { - ["versions"] = new OpenApiArray + ["versions"] = new JsonArray { - new OpenApiObject + new JsonObject { - ["status"] = new OpenApiString("Status1"), - ["id"] = new OpenApiString("v1"), - ["links"] = new OpenApiArray + ["status"] = "Status1", + ["id"] = "v1", + ["links"] = new JsonArray { - new OpenApiObject + new JsonObject { - ["href"] = new OpenApiString("http://example.com/1"), - ["rel"] = new OpenApiString("sampleRel1") + ["href"] = "http://example.com/1", + ["rel"] = "sampleRel1" } } }, - new OpenApiObject + new JsonObject { - ["status"] = new OpenApiString("Status2"), - ["id"] = new OpenApiString("v2"), - ["links"] = new OpenApiArray + ["status"] = "Status2", + ["id"] = "v2", + ["links"] = new JsonArray { - new OpenApiObject + new JsonObject { - ["href"] = new OpenApiString("http://example.com/2"), - ["rel"] = new OpenApiString("sampleRel2") + ["href"] = "http://example.com/2", + ["rel"] = "sampleRel2" } } } @@ -68,7 +70,7 @@ public class OpenApiMediaTypeTests public static OpenApiMediaType MediaTypeWithXmlExample = new() { - Example = new OpenApiString("123"), + Example = "123", Encoding = new Dictionary { {"testEncoding", OpenApiEncodingTests.AdvanceEncoding} @@ -80,34 +82,34 @@ public class OpenApiMediaTypeTests Examples = { ["object1"] = new() { - Value = new OpenApiObject + Value = new JsonObject { - ["versions"] = new OpenApiArray + ["versions"] = new JsonArray { - new OpenApiObject + new JsonObject { - ["status"] = new OpenApiString("Status1"), - ["id"] = new OpenApiString("v1"), - ["links"] = new OpenApiArray + ["status"] = "Status1", + ["id"] = "v1", + ["links"] = new JsonArray { - new OpenApiObject + new JsonObject { - ["href"] = new OpenApiString("http://example.com/1"), - ["rel"] = new OpenApiString("sampleRel1") + ["href"] = "http://example.com/1", + ["rel"] = "sampleRel1" } } }, - new OpenApiObject + new JsonObject { - ["status"] = new OpenApiString("Status2"), - ["id"] = new OpenApiString("v2"), - ["links"] = new OpenApiArray + ["status"] = "Status2", + ["id"] = "v2", + ["links"] = new JsonArray { - new OpenApiObject + new JsonObject { - ["href"] = new OpenApiString("http://example.com/2"), - ["rel"] = new OpenApiString("sampleRel2") + ["href"] = "http://example.com/2", + ["rel"] = "sampleRel2" } } } @@ -425,5 +427,21 @@ public void SerializeMediaTypeWithObjectExamplesAsV3JsonWorks() expected = expected.MakeLineBreaksEnvironmentNeutral(); actual.Should().Be(expected); } + + [Fact] + public void MediaTypeCopyConstructorWorks() + { + var clone = new OpenApiMediaType(MediaTypeWithObjectExamples) + { + Example = 42, + Examples = new Dictionary(), + Encoding = new Dictionary(), + Extensions = new Dictionary() + }; + + // Assert + MediaTypeWithObjectExamples.Examples.Should().NotBeEquivalentTo(clone.Examples); + MediaTypeWithObjectExamples.Example.Should().Be(null); + } } } diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiOperationTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiOperationTests.cs index 52906e61c..a65bf24c5 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiOperationTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiOperationTests.cs @@ -5,6 +5,7 @@ using FluentAssertions; using Microsoft.OpenApi.Extensions; using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Models.References; using Xunit; namespace Microsoft.OpenApi.Tests.Models @@ -47,7 +48,7 @@ public class OpenApiOperationTests { Schema = new() { - Type = "number", + Type = JsonSchemaType.Number, Minimum = 5, Maximum = 10 } @@ -56,14 +57,7 @@ public class OpenApiOperationTests }, Responses = new() { - ["200"] = new() - { - Reference = new() - { - Id = "response1", - Type = ReferenceType.Response - } - }, + ["200"] = new OpenApiResponseReference("response1", hostDocument: null), ["400"] = new() { Content = new Dictionary @@ -72,7 +66,7 @@ public class OpenApiOperationTests { Schema = new() { - Type = "number", + Type = JsonSchemaType.Number, Minimum = 5, Maximum = 10 } @@ -100,14 +94,7 @@ public class OpenApiOperationTests Name = "tagName1", Description = "tagDescription1", }, - new() - { - Reference = new() - { - Id = "tagId1", - Type = ReferenceType.Tag - } - } + new OpenApiTagReference("tagId1", null) }, Summary = "summary1", Description = "operationDescription", @@ -140,7 +127,7 @@ public class OpenApiOperationTests { Schema = new() { - Type = "number", + Type = JsonSchemaType.Number, Minimum = 5, Maximum = 10 } @@ -149,14 +136,7 @@ public class OpenApiOperationTests }, Responses = new() { - ["200"] = new() - { - Reference = new() - { - Id = "response1", - Type = ReferenceType.Response - } - }, + ["200"] = new OpenApiResponseReference("response1", hostDocument: null), ["400"] = new() { Content = new Dictionary @@ -165,7 +145,7 @@ public class OpenApiOperationTests { Schema = new() { - Type = "number", + Type = JsonSchemaType.Number, Minimum = 5, Maximum = 10 } @@ -177,22 +157,8 @@ public class OpenApiOperationTests { new() { - [new() - { - Reference = new() - { - Id = "securitySchemeId1", - Type = ReferenceType.SecurityScheme - } - }] = new List(), - [new() - { - Reference = new() - { - Id = "securitySchemeId2", - Type = ReferenceType.SecurityScheme - } - }] = new List + [new OpenApiSecuritySchemeReference("securitySchemeId1", hostDocument: null)] = new List(), + [new OpenApiSecuritySchemeReference("securitySchemeId2", hostDocument: null)] = new List { "scopeName1", "scopeName2" @@ -225,7 +191,7 @@ public class OpenApiOperationTests Required = true, Schema = new() { - Type = "string" + Type = JsonSchemaType.String } } }, @@ -242,12 +208,12 @@ public class OpenApiOperationTests ["name"] = new() { Description = "Updated name of the pet", - Type = "string" + Type = JsonSchemaType.String }, ["status"] = new() { Description = "Updated status of the pet", - Type = "string" + Type = JsonSchemaType.String } }, Required = new HashSet @@ -265,12 +231,12 @@ public class OpenApiOperationTests ["name"] = new() { Description = "Updated name of the pet", - Type = "string" + Type = JsonSchemaType.String }, ["status"] = new() { Description = "Updated status of the pet", - Type = "string" + Type = JsonSchemaType.String } }, Required = new HashSet @@ -661,9 +627,9 @@ public void SerializeOperationWithBodyAsV2JsonWorks() "description": "description2", "required": true, "schema": { + "type": "number", "maximum": 10, - "minimum": 5, - "type": "number" + "minimum": 5 } } ], @@ -674,9 +640,9 @@ public void SerializeOperationWithBodyAsV2JsonWorks() "400": { "description": null, "schema": { + "type": "number", "maximum": 10, - "minimum": 5, - "type": "number" + "minimum": 5 } } }, @@ -734,9 +700,9 @@ public void SerializeAdvancedOperationWithTagAndSecurityAsV2JsonWorks() "description": "description2", "required": true, "schema": { + "type": "number", "maximum": 10, - "minimum": 5, - "type": "number" + "minimum": 5 } } ], @@ -747,9 +713,9 @@ public void SerializeAdvancedOperationWithTagAndSecurityAsV2JsonWorks() "400": { "description": null, "schema": { + "type": "number", "maximum": 10, - "minimum": 5, - "type": "number" + "minimum": 5 } } }, diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiParameterTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiParameterTests.cs index 2f02ff7dd..95596b787 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiParameterTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiParameterTests.cs @@ -4,11 +4,13 @@ using System.Collections.Generic; using System.Globalization; using System.IO; +using System.Text.Json.Nodes; using System.Threading.Tasks; using FluentAssertions; using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Extensions; using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Models.References; using Microsoft.OpenApi.Writers; using VerifyXunit; using Xunit; @@ -24,15 +26,11 @@ public class OpenApiParameterTests In = ParameterLocation.Path }; + public static OpenApiParameterReference OpenApiParameterReference = new(ReferencedParameter, "example1"); public static OpenApiParameter ReferencedParameter = new() { Name = "name1", - In = ParameterLocation.Path, - Reference = new() - { - Type = ReferenceType.Parameter, - Id = "example1" - } + In = ParameterLocation.Path }; public static OpenApiParameter AdvancedPathParameterWithSchema = new() @@ -42,7 +40,6 @@ public class OpenApiParameterTests Description = "description1", Required = true, Deprecated = false, - Style = ParameterStyle.Simple, Explode = true, Schema = new() @@ -51,8 +48,8 @@ public class OpenApiParameterTests Description = "description2", OneOf = new List { - new() { Type = "number", Format = "double" }, - new() { Type = "string" } + new() { Type = JsonSchemaType.Number, Format = "double" }, + new() { Type = JsonSchemaType.String } } }, Examples = new Dictionary @@ -74,13 +71,13 @@ public class OpenApiParameterTests Explode = false, Schema = new() { - Type = "array", + Type = JsonSchemaType.Array, Items = new() { - Enum = new List + Enum = { - new OpenApiString("value1"), - new OpenApiString("value2") + new OpenApiAny("value1").Node, + new OpenApiAny("value2").Node } } } @@ -95,19 +92,33 @@ public class OpenApiParameterTests Explode = true, Schema = new() { - Type = "array", + Type = JsonSchemaType.Array, Items = new() { - Enum = new List - { - new OpenApiString("value1"), - new OpenApiString("value2") - } + Enum = + [ + new OpenApiAny("value1").Node, + new OpenApiAny("value2").Node + ] + } + } + }; + + public static OpenApiParameter QueryParameterWithMissingStyle = new OpenApiParameter + { + Name = "id", + In = ParameterLocation.Query, + Schema = new() + { + Type = JsonSchemaType.Object, + AdditionalProperties = new OpenApiSchema + { + Type = JsonSchemaType.Integer } } }; - public static OpenApiParameter AdvancedHeaderParameterWithSchemaReference = new() + public static OpenApiParameter AdvancedHeaderParameterWithSchemaReference = new OpenApiParameter { Name = "name1", In = ParameterLocation.Header, @@ -148,7 +159,7 @@ public class OpenApiParameterTests Explode = true, Schema = new() { - Type = "object" + Type = JsonSchemaType.Object }, Examples = new Dictionary { @@ -186,6 +197,8 @@ public void WhenStyleIsFormTheDefaultValueOfExplodeShouldBeTrueOtherwiseFalse(Pa public void WhenStyleAndInIsNullTheDefaultValueOfStyleShouldBeSimple(ParameterLocation? inValue, ParameterStyle expectedStyle) { // Arrange + var outputStringWriter = new StringWriter(CultureInfo.InvariantCulture); + var writer = new OpenApiJsonWriter(outputStringWriter, new OpenApiJsonWriterSettings { Terse = false }); var parameter = new OpenApiParameter { Name = "name1", @@ -193,9 +206,30 @@ public void WhenStyleAndInIsNullTheDefaultValueOfStyleShouldBeSimple(ParameterLo }; // Act & Assert + parameter.SerializeAsV3(writer); + writer.Flush(); + parameter.Style.Should().Be(expectedStyle); } + [Fact] + public void SerializeQueryParameterWithMissingStyleSucceeds() + { + // Arrange + var expected = @"name: id +in: query +schema: + type: object + additionalProperties: + type: integer"; + + // Act + var actual = QueryParameterWithMissingStyle.SerializeAsYaml(OpenApiSpecVersion.OpenApi3_0); + + // Assert + actual.MakeLineBreaksEnvironmentNeutral().Should().Be(expected.MakeLineBreaksEnvironmentNeutral()); + } + [Fact] public void SerializeBasicParameterAsV3JsonWorks() { @@ -301,7 +335,7 @@ public async Task SerializeReferencedParameterAsV3JsonWorksAsync(bool produceTer var writer = new OpenApiJsonWriter(outputStringWriter, new() { Terse = produceTerseOutput }); // Act - ReferencedParameter.SerializeAsV3(writer); + OpenApiParameterReference.SerializeAsV3(writer); writer.Flush(); // Assert @@ -318,7 +352,7 @@ public async Task SerializeReferencedParameterAsV3JsonWithoutReferenceWorksAsync var writer = new OpenApiJsonWriter(outputStringWriter, new() { Terse = produceTerseOutput }); // Act - ReferencedParameter.SerializeAsV3WithoutReference(writer); + ReferencedParameter.SerializeAsV3(writer); writer.Flush(); // Assert @@ -335,7 +369,7 @@ public async Task SerializeReferencedParameterAsV2JsonWorksAsync(bool produceTer var writer = new OpenApiJsonWriter(outputStringWriter, new() { Terse = produceTerseOutput }); // Act - ReferencedParameter.SerializeAsV2(writer); + OpenApiParameterReference.SerializeAsV2(writer); writer.Flush(); // Assert @@ -352,24 +386,7 @@ public async Task SerializeReferencedParameterAsV2JsonWithoutReferenceWorksAsync var writer = new OpenApiJsonWriter(outputStringWriter, new() { Terse = produceTerseOutput }); // Act - ReferencedParameter.SerializeAsV2WithoutReference(writer); - writer.Flush(); - - // Assert - await Verifier.Verify(outputStringWriter).UseParameters(produceTerseOutput); - } - - [Theory] - [InlineData(true)] - [InlineData(false)] - public async Task SerializeParameterWithSchemaReferenceAsV2JsonWorksAsync(bool produceTerseOutput) - { - // Arrange - var outputStringWriter = new StringWriter(CultureInfo.InvariantCulture); - var writer = new OpenApiJsonWriter(outputStringWriter, new() { Terse = produceTerseOutput }); - - // Act - AdvancedHeaderParameterWithSchemaReference.SerializeAsV2(writer); + ReferencedParameter.SerializeAsV2(writer); writer.Flush(); // Assert @@ -403,7 +420,7 @@ public async Task SerializeParameterWithFormStyleAndExplodeFalseWorksAsync(bool var writer = new OpenApiJsonWriter(outputStringWriter, new() { Terse = produceTerseOutput }); // Act - ParameterWithFormStyleAndExplodeFalse.SerializeAsV3WithoutReference(writer); + ParameterWithFormStyleAndExplodeFalse.SerializeAsV3(writer); writer.Flush(); // Assert @@ -420,7 +437,7 @@ public async Task SerializeParameterWithFormStyleAndExplodeTrueWorksAsync(bool p var writer = new OpenApiJsonWriter(outputStringWriter, new() { Terse = produceTerseOutput }); // Act - ParameterWithFormStyleAndExplodeTrue.SerializeAsV3WithoutReference(writer); + ParameterWithFormStyleAndExplodeTrue.SerializeAsV3(writer); writer.Flush(); // Assert diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiReferenceTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiReferenceTests.cs index e5366d18e..1a8497e5a 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiReferenceTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiReferenceTests.cs @@ -173,7 +173,7 @@ public void SerializeExternalReferenceAsJsonV2Works() var reference = new OpenApiReference { ExternalResource = "main.json", - Type= ReferenceType.Schema, + Type = ReferenceType.Schema, Id = "Pets" }; @@ -216,7 +216,7 @@ public void SerializeExternalReferenceAsYamlV2Works() public void SerializeExternalReferenceAsJsonV3Works() { // Arrange - var reference = new OpenApiReference { ExternalResource = "main.json", Type = ReferenceType.Schema,Id = "Pets" }; + var reference = new OpenApiReference { ExternalResource = "main.json", Type = ReferenceType.Schema, Id = "Pets" }; var expected = """ @@ -238,7 +238,7 @@ public void SerializeExternalReferenceAsJsonV3Works() public void SerializeExternalReferenceAsYamlV3Works() { // Arrange - var reference = new OpenApiReference { ExternalResource = "main.json", Type = ReferenceType.Schema, Id = "Pets" }; + var reference = new OpenApiReference { ExternalResource = "main.json", Type = ReferenceType.Schema, Id = "Pets" }; var expected = @"$ref: main.json#/components/schemas/Pets"; // Act diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiRequestBodyTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiRequestBodyTests.cs index 519c26607..ebffa38fd 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiRequestBodyTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiRequestBodyTests.cs @@ -5,6 +5,7 @@ using System.IO; using System.Threading.Tasks; using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Models.References; using Microsoft.OpenApi.Writers; using VerifyXunit; using Xunit; @@ -24,19 +25,15 @@ public class OpenApiRequestBodyTests { Schema = new() { - Type = "string" + Type = JsonSchemaType.String } } } }; + public static OpenApiRequestBodyReference OpenApiRequestBodyReference = new(ReferencedRequestBody, "example1"); public static OpenApiRequestBody ReferencedRequestBody = new() { - Reference = new() - { - Type = ReferenceType.RequestBody, - Id = "example1", - }, Description = "description", Required = true, Content = @@ -45,7 +42,7 @@ public class OpenApiRequestBodyTests { Schema = new() { - Type = "string" + Type = JsonSchemaType.String } } } @@ -78,7 +75,7 @@ public async Task SerializeReferencedRequestBodyAsV3JsonWorksAsync(bool produceT var writer = new OpenApiJsonWriter(outputStringWriter, new() { Terse = produceTerseOutput }); // Act - ReferencedRequestBody.SerializeAsV3(writer); + OpenApiRequestBodyReference.SerializeAsV3(writer); writer.Flush(); // Assert @@ -95,7 +92,7 @@ public async Task SerializeReferencedRequestBodyAsV3JsonWithoutReferenceWorksAsy var writer = new OpenApiJsonWriter(outputStringWriter, new() { Terse = produceTerseOutput }); // Act - ReferencedRequestBody.SerializeAsV3WithoutReference(writer); + ReferencedRequestBody.SerializeAsV3(writer); writer.Flush(); // Assert diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiResponseTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiResponseTests.cs index fdd1207a6..f09d41da8 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiResponseTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiResponseTests.cs @@ -1,5 +1,5 @@ // Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. +// Licensed under the MIT license. using System.Collections.Generic; using System.Globalization; @@ -10,103 +10,177 @@ using Microsoft.OpenApi.Extensions; using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Models.References; using Microsoft.OpenApi.Writers; using VerifyXunit; using Xunit; +using Xunit.Abstractions; namespace Microsoft.OpenApi.Tests.Models { [Collection("DefaultSettings")] public class OpenApiResponseTests { - public static OpenApiResponse BasicResponse = new(); + public static OpenApiResponse BasicResponse = new OpenApiResponse(); - public static OpenApiResponse AdvancedResponse = new() + public static OpenApiResponse AdvancedV2Response = new OpenApiResponse { Description = "A complex object array response", Content = { - ["text/plain"] = new() + ["text/plain"] = new OpenApiMediaType { Schema = new() { - Type = "array", - Items = new() - { - Reference = new() {Type = ReferenceType.Schema, Id = "customType"} - } + Type = JsonSchemaType.Array, + Items = new OpenApiSchemaReference("customType", null) }, - Example = new OpenApiString("Blabla"), + Example = "Blabla", Extensions = new Dictionary { - ["myextension"] = new OpenApiString("myextensionvalue"), + ["myextension"] = new OpenApiAny("myextensionvalue"), + }, + } + }, + Headers = + { + ["X-Rate-Limit-Limit"] = new OpenApiHeader + { + Description = "The number of allowed requests in the current period", + Schema = new() + { + Type = JsonSchemaType.Integer + } + }, + ["X-Rate-Limit-Reset"] = new OpenApiHeader + { + Description = "The number of seconds left in the current period", + Schema = new() + { + Type = JsonSchemaType.Integer + } + }, + } + }; + public static OpenApiResponse AdvancedV3Response = new OpenApiResponse + { + Description = "A complex object array response", + Content = + { + ["text/plain"] = new OpenApiMediaType + { + Schema = new() + { + Type = JsonSchemaType.Array, + Items = new OpenApiSchemaReference("customType", null) + }, + Example = "Blabla", + Extensions = new Dictionary + { + ["myextension"] = new OpenApiAny("myextensionvalue"), }, } }, Headers = { - ["X-Rate-Limit-Limit"] = new() + ["X-Rate-Limit-Limit"] = new OpenApiHeader { Description = "The number of allowed requests in the current period", Schema = new() { - Type = "integer" + Type = JsonSchemaType.Integer } }, - ["X-Rate-Limit-Reset"] = new() + ["X-Rate-Limit-Reset"] = new OpenApiHeader { Description = "The number of seconds left in the current period", Schema = new() { - Type = "integer" + Type = JsonSchemaType.Integer } }, } }; - public static OpenApiResponse ReferencedResponse = new() + public static OpenApiResponseReference V2OpenApiResponseReference = new OpenApiResponseReference("example1", ReferencedV2Response); + public static OpenApiResponse ReferencedV2Response = new OpenApiResponse { - Reference = new() + Description = "A complex object array response", + Content = { - Type = ReferenceType.Response, - Id = "example1" + ["text/plain"] = new OpenApiMediaType + { + Schema = new() + { + Type = JsonSchemaType.Array, + Items = new OpenApiSchemaReference("customType", null) + } + } }, + Headers = + { + ["X-Rate-Limit-Limit"] = new OpenApiHeader + { + Description = "The number of allowed requests in the current period", + Schema = new() + { + Type = JsonSchemaType.Integer + } + }, + ["X-Rate-Limit-Reset"] = new OpenApiHeader + { + Description = "The number of seconds left in the current period", + Schema = new() + { + Type = JsonSchemaType.Integer + } + }, + } + }; + public static OpenApiResponseReference V3OpenApiResponseReference = new OpenApiResponseReference("example1", ReferencedV3Response); + + public static OpenApiResponse ReferencedV3Response = new OpenApiResponse + { Description = "A complex object array response", Content = { - ["text/plain"] = new() + ["text/plain"] = new OpenApiMediaType { - Schema = new() + Schema = new() { - Type = "array", - Items = new() - { - Reference = new() {Type = ReferenceType.Schema, Id = "customType"} - } + Type = JsonSchemaType.Array, + Items = new OpenApiSchemaReference("customType", null) } } }, Headers = { - ["X-Rate-Limit-Limit"] = new() + ["X-Rate-Limit-Limit"] = new OpenApiHeader { Description = "The number of allowed requests in the current period", Schema = new() { - Type = "integer" + Type = JsonSchemaType.Integer } }, - ["X-Rate-Limit-Reset"] = new() + ["X-Rate-Limit-Reset"] = new OpenApiHeader { Description = "The number of seconds left in the current period", Schema = new() { - Type = "integer" + Type = JsonSchemaType.Integer } }, } }; + private readonly ITestOutputHelper _output; + + public OpenApiResponseTests(ITestOutputHelper output) + { + _output = output; + } + [Theory] [InlineData(OpenApiSpecVersion.OpenApi3_0, OpenApiFormat.Json)] [InlineData(OpenApiSpecVersion.OpenApi2_0, OpenApiFormat.Json)] @@ -117,13 +191,9 @@ public void SerializeBasicResponseWorks( OpenApiFormat format) { // Arrange - var expected = format == OpenApiFormat.Json ? - """ - { - "description": null - } - """ : - @"description: "; + var expected = format == OpenApiFormat.Json ? @"{ + ""description"": null +}" : @"description: "; // Act var actual = BasicResponse.Serialize(version, format); @@ -138,40 +208,38 @@ public void SerializeBasicResponseWorks( public void SerializeAdvancedResponseAsV3JsonWorks() { // Arrange - var expected = """ - { - "description": "A complex object array response", - "headers": { - "X-Rate-Limit-Limit": { - "description": "The number of allowed requests in the current period", - "schema": { - "type": "integer" - } - }, - "X-Rate-Limit-Reset": { - "description": "The number of seconds left in the current period", - "schema": { - "type": "integer" - } - } - }, - "content": { - "text/plain": { - "schema": { - "type": "array", - "items": { - "$ref": "#/components/schemas/customType" - } - }, - "example": "Blabla", - "myextension": "myextensionvalue" - } - } - } - """; + var expected = @"{ + ""description"": ""A complex object array response"", + ""headers"": { + ""X-Rate-Limit-Limit"": { + ""description"": ""The number of allowed requests in the current period"", + ""schema"": { + ""type"": ""integer"" + } + }, + ""X-Rate-Limit-Reset"": { + ""description"": ""The number of seconds left in the current period"", + ""schema"": { + ""type"": ""integer"" + } + } + }, + ""content"": { + ""text/plain"": { + ""schema"": { + ""type"": ""array"", + ""items"": { + ""$ref"": ""#/components/schemas/customType"" + } + }, + ""example"": ""Blabla"", + ""myextension"": ""myextensionvalue"" + } + } +}"; // Act - var actual = AdvancedResponse.SerializeAsJson(OpenApiSpecVersion.OpenApi3_0); + var actual = AdvancedV3Response.SerializeAsJson(OpenApiSpecVersion.OpenApi3_0); // Assert actual = actual.MakeLineBreaksEnvironmentNeutral(); @@ -184,29 +252,27 @@ public void SerializeAdvancedResponseAsV3YamlWorks() { // Arrange var expected = - """ - description: A complex object array response - headers: - X-Rate-Limit-Limit: - description: The number of allowed requests in the current period - schema: - type: integer - X-Rate-Limit-Reset: - description: The number of seconds left in the current period - schema: - type: integer - content: - text/plain: - schema: - type: array - items: - $ref: '#/components/schemas/customType' - example: Blabla - myextension: myextensionvalue - """; + @"description: A complex object array response +headers: + X-Rate-Limit-Limit: + description: The number of allowed requests in the current period + schema: + type: integer + X-Rate-Limit-Reset: + description: The number of seconds left in the current period + schema: + type: integer +content: + text/plain: + schema: + type: array + items: + $ref: '#/components/schemas/customType' + example: Blabla + myextension: myextensionvalue"; // Act - var actual = AdvancedResponse.SerializeAsYaml(OpenApiSpecVersion.OpenApi3_0); + var actual = AdvancedV3Response.SerializeAsYaml(OpenApiSpecVersion.OpenApi3_0); // Assert actual = actual.MakeLineBreaksEnvironmentNeutral(); @@ -218,35 +284,32 @@ public void SerializeAdvancedResponseAsV3YamlWorks() public void SerializeAdvancedResponseAsV2JsonWorks() { // Arrange - var expected = - """ - { - "description": "A complex object array response", - "schema": { - "type": "array", - "items": { - "$ref": "#/definitions/customType" - } - }, - "examples": { - "text/plain": "Blabla" - }, - "myextension": "myextensionvalue", - "headers": { - "X-Rate-Limit-Limit": { - "description": "The number of allowed requests in the current period", - "type": "integer" - }, - "X-Rate-Limit-Reset": { - "description": "The number of seconds left in the current period", - "type": "integer" - } - } - } - """; + var expected = @"{ + ""description"": ""A complex object array response"", + ""schema"": { + ""type"": ""array"", + ""items"": { + ""$ref"": ""#/definitions/customType"" + } + }, + ""examples"": { + ""text/plain"": ""Blabla"" + }, + ""myextension"": ""myextensionvalue"", + ""headers"": { + ""X-Rate-Limit-Limit"": { + ""description"": ""The number of allowed requests in the current period"", + ""type"": ""integer"" + }, + ""X-Rate-Limit-Reset"": { + ""description"": ""The number of seconds left in the current period"", + ""type"": ""integer"" + } + } +}"; // Act - var actual = AdvancedResponse.SerializeAsJson(OpenApiSpecVersion.OpenApi2_0); + var actual = AdvancedV2Response.SerializeAsJson(OpenApiSpecVersion.OpenApi2_0); // Assert actual = actual.MakeLineBreaksEnvironmentNeutral(); @@ -259,26 +322,24 @@ public void SerializeAdvancedResponseAsV2YamlWorks() { // Arrange var expected = - """ - description: A complex object array response - schema: - type: array - items: - $ref: '#/definitions/customType' - examples: - text/plain: Blabla - myextension: myextensionvalue - headers: - X-Rate-Limit-Limit: - description: The number of allowed requests in the current period - type: integer - X-Rate-Limit-Reset: - description: The number of seconds left in the current period - type: integer - """; + @"description: A complex object array response +schema: + type: array + items: + $ref: '#/definitions/customType' +examples: + text/plain: Blabla +myextension: myextensionvalue +headers: + X-Rate-Limit-Limit: + description: The number of allowed requests in the current period + type: integer + X-Rate-Limit-Reset: + description: The number of seconds left in the current period + type: integer"; // Act - var actual = AdvancedResponse.SerializeAsYaml(OpenApiSpecVersion.OpenApi2_0); + var actual = AdvancedV2Response.SerializeAsYaml(OpenApiSpecVersion.OpenApi2_0); // Assert actual = actual.MakeLineBreaksEnvironmentNeutral(); @@ -293,10 +354,10 @@ public async Task SerializeReferencedResponseAsV3JsonWorksAsync(bool produceTers { // Arrange var outputStringWriter = new StringWriter(CultureInfo.InvariantCulture); - var writer = new OpenApiJsonWriter(outputStringWriter, new() { Terse = produceTerseOutput }); + var writer = new OpenApiJsonWriter(outputStringWriter, new OpenApiJsonWriterSettings { Terse = produceTerseOutput }); // Act - ReferencedResponse.SerializeAsV3(writer); + V3OpenApiResponseReference.SerializeAsV3(writer); writer.Flush(); // Assert @@ -310,10 +371,10 @@ public async Task SerializeReferencedResponseAsV3JsonWithoutReferenceWorksAsync( { // Arrange var outputStringWriter = new StringWriter(CultureInfo.InvariantCulture); - var writer = new OpenApiJsonWriter(outputStringWriter, new() { Terse = produceTerseOutput }); + var writer = new OpenApiJsonWriter(outputStringWriter, new OpenApiJsonWriterSettings { Terse = produceTerseOutput }); // Act - ReferencedResponse.SerializeAsV3WithoutReference(writer); + ReferencedV3Response.SerializeAsV3(writer); writer.Flush(); // Assert @@ -327,10 +388,10 @@ public async Task SerializeReferencedResponseAsV2JsonWorksAsync(bool produceTers { // Arrange var outputStringWriter = new StringWriter(CultureInfo.InvariantCulture); - var writer = new OpenApiJsonWriter(outputStringWriter, new() { Terse = produceTerseOutput }); + var writer = new OpenApiJsonWriter(outputStringWriter, new OpenApiJsonWriterSettings { Terse = produceTerseOutput }); // Act - ReferencedResponse.SerializeAsV2(writer); + V2OpenApiResponseReference.SerializeAsV2(writer); writer.Flush(); // Assert @@ -344,10 +405,10 @@ public async Task SerializeReferencedResponseAsV2JsonWithoutReferenceWorksAsync( { // Arrange var outputStringWriter = new StringWriter(CultureInfo.InvariantCulture); - var writer = new OpenApiJsonWriter(outputStringWriter, new() { Terse = produceTerseOutput }); + var writer = new OpenApiJsonWriter(outputStringWriter, new OpenApiJsonWriterSettings { Terse = produceTerseOutput }); // Act - ReferencedResponse.SerializeAsV2WithoutReference(writer); + ReferencedV2Response.SerializeAsV2(writer); writer.Flush(); // Assert diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.SerializeReferencedSchemaAsV3JsonWorksAsync_produceTerseOutput=False.verified.txt b/test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.SerializeReferencedSchemaAsV3JsonWorksAsync_produceTerseOutput=False.verified.txt index 2a9b08f98..b431f1607 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.SerializeReferencedSchemaAsV3JsonWorksAsync_produceTerseOutput=False.verified.txt +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.SerializeReferencedSchemaAsV3JsonWorksAsync_produceTerseOutput=False.verified.txt @@ -1,3 +1,13 @@ { - "$ref": "#/components/schemas/schemaObject1" + "title": "title1", + "multipleOf": 3, + "maximum": 42, + "minimum": 10, + "exclusiveMinimum": true, + "type": "integer", + "default": 15, + "nullable": true, + "externalDocs": { + "url": "http://example.com/externalDocs" + } } \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.SerializeReferencedSchemaAsV3JsonWorksAsync_produceTerseOutput=True.verified.txt b/test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.SerializeReferencedSchemaAsV3JsonWorksAsync_produceTerseOutput=True.verified.txt index ca0ce704f..d71a5f0a8 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.SerializeReferencedSchemaAsV3JsonWorksAsync_produceTerseOutput=True.verified.txt +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.SerializeReferencedSchemaAsV3JsonWorksAsync_produceTerseOutput=True.verified.txt @@ -1 +1 @@ -{"$ref":"#/components/schemas/schemaObject1"} \ No newline at end of file +{"title":"title1","multipleOf":3,"maximum":42,"minimum":10,"exclusiveMinimum":true,"type":"integer","default":15,"nullable":true,"externalDocs":{"url":"http://example.com/externalDocs"}} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.SerializeSchemaWRequiredPropertiesAsV2JsonWorksAsync_produceTerseOutput=False.verified.txt b/test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.SerializeSchemaWRequiredPropertiesAsV2JsonWorksAsync_produceTerseOutput=False.verified.txt index 9ab9fad6f..e9543ede7 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.SerializeSchemaWRequiredPropertiesAsV2JsonWorksAsync_produceTerseOutput=False.verified.txt +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.SerializeSchemaWRequiredPropertiesAsV2JsonWorksAsync_produceTerseOutput=False.verified.txt @@ -13,8 +13,8 @@ "type": "integer" }, "property3": { - "maxLength": 15, - "type": "string" + "type": "string", + "maxLength": 15 } } }, @@ -28,8 +28,8 @@ } }, "property7": { - "minLength": 2, - "type": "string" + "type": "string", + "minLength": 2 } }, "readOnly": true diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.SerializeSchemaWRequiredPropertiesAsV2JsonWorksAsync_produceTerseOutput=True.verified.txt b/test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.SerializeSchemaWRequiredPropertiesAsV2JsonWorksAsync_produceTerseOutput=True.verified.txt index b0b24d295..9ea88dee8 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.SerializeSchemaWRequiredPropertiesAsV2JsonWorksAsync_produceTerseOutput=True.verified.txt +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.SerializeSchemaWRequiredPropertiesAsV2JsonWorksAsync_produceTerseOutput=True.verified.txt @@ -1 +1 @@ -{"title":"title1","required":["property1"],"properties":{"property1":{"required":["property3"],"properties":{"property2":{"type":"integer"},"property3":{"maxLength":15,"type":"string"}}},"property4":{"properties":{"property5":{"properties":{"property6":{"type":"boolean"}}},"property7":{"minLength":2,"type":"string"}},"readOnly":true}},"externalDocs":{"url":"http://example.com/externalDocs"}} \ No newline at end of file +{"title":"title1","required":["property1"],"properties":{"property1":{"required":["property3"],"properties":{"property2":{"type":"integer"},"property3":{"type":"string","maxLength":15}}},"property4":{"properties":{"property5":{"properties":{"property6":{"type":"boolean"}}},"property7":{"type":"string","minLength":2}},"readOnly":true}},"externalDocs":{"url":"http://example.com/externalDocs"}} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.cs index 1906dbbc4..408173e6e 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.cs @@ -1,9 +1,11 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. +using System; using System.Collections.Generic; using System.Globalization; using System.IO; +using System.Text.Json.Nodes; using System.Threading.Tasks; using FluentAssertions; using Microsoft.OpenApi.Any; @@ -29,8 +31,8 @@ public class OpenApiSchemaTests Maximum = 42, ExclusiveMinimum = true, Minimum = 10, - Default = new OpenApiInteger(15), - Type = "integer", + Default = 15, + Type = JsonSchemaType.Integer, Nullable = true, ExternalDocs = new() @@ -51,11 +53,11 @@ public class OpenApiSchemaTests { ["property2"] = new() { - Type = "integer" + Type = JsonSchemaType.Integer }, ["property3"] = new() { - Type = "string", + Type = JsonSchemaType.String, MaxLength = 15 } }, @@ -70,13 +72,13 @@ public class OpenApiSchemaTests { ["property6"] = new() { - Type = "boolean" + Type = JsonSchemaType.Boolean } } }, ["property7"] = new() { - Type = "string", + Type = JsonSchemaType.String, MinLength = 2 } }, @@ -101,11 +103,11 @@ public class OpenApiSchemaTests { ["property1"] = new() { - Type = "integer" + Type = JsonSchemaType.Integer }, ["property2"] = new() { - Type = "string", + Type = JsonSchemaType.String, MaxLength = 15 } }, @@ -121,13 +123,13 @@ public class OpenApiSchemaTests { ["property4"] = new() { - Type = "boolean" + Type = JsonSchemaType.Boolean } } }, ["property5"] = new() { - Type = "string", + Type = JsonSchemaType.String, MinLength = 2 } }, @@ -148,19 +150,13 @@ public class OpenApiSchemaTests Maximum = 42, ExclusiveMinimum = true, Minimum = 10, - Default = new OpenApiInteger(15), - Type = "integer", + Default = 15, + Type = JsonSchemaType.Integer, Nullable = true, ExternalDocs = new() { Url = new("http://example.com/externalDocs") - }, - - Reference = new() - { - Type = ReferenceType.Schema, - Id = "schemaObject1" } }; @@ -177,11 +173,11 @@ public class OpenApiSchemaTests { ["property2"] = new() { - Type = "integer" + Type = JsonSchemaType.Integer }, ["property3"] = new() { - Type = "string", + Type = JsonSchemaType.String, MaxLength = 15, ReadOnly = true } @@ -198,13 +194,13 @@ public class OpenApiSchemaTests { ["property6"] = new() { - Type = "boolean" + Type = JsonSchemaType.Boolean } } }, ["property7"] = new() { - Type = "string", + Type = JsonSchemaType.String, MinLength = 2 } }, @@ -380,7 +376,7 @@ public async Task SerializeReferencedSchemaAsV3WithoutReferenceJsonWorksAsync(bo var writer = new OpenApiJsonWriter(outputStringWriter, new() { Terse = produceTerseOutput }); // Act - ReferencedSchema.SerializeAsV3WithoutReference(writer); + ReferencedSchema.SerializeAsV3(writer); writer.Flush(); // Assert @@ -431,10 +427,10 @@ public void SerializeAsV2ShouldSetFormatPropertyInParentSchemaIfPresentInChildre { new() { - Type = "number", + Type = JsonSchemaType.Number, Format = "decimal" }, - new() { Type = "string" }, + new() { Type = JsonSchemaType.String }, } }; @@ -454,15 +450,15 @@ public void SerializeAsV2ShouldSetFormatPropertyInParentSchemaIfPresentInChildre "format": "decimal", "allOf": [ { - "format": "decimal", - "type": "number" + "type": "number", + "format": "decimal" } ] } """.MakeLineBreaksEnvironmentNeutral(); // Assert - Assert.Equal(expectedV2Schema, v2Schema); + expectedV2Schema.Should().BeEquivalentTo(v2Schema); } [Fact] @@ -470,7 +466,7 @@ public void OpenApiSchemaCopyConstructorSucceeds() { var baseSchema = new OpenApiSchema { - Type = "string", + Type = JsonSchemaType.String, Format = "date" }; @@ -479,7 +475,7 @@ public void OpenApiSchemaCopyConstructorSucceeds() Nullable = true }; - Assert.Equal("string", actualSchema.Type); + Assert.Equal(JsonSchemaType.String, actualSchema.Type); Assert.Equal("date", actualSchema.Format); Assert.True(actualSchema.Nullable); } @@ -505,30 +501,27 @@ public void OpenApiSchemaCopyConstructorWithAnnotationsSucceeds() Assert.NotEqual(baseSchema.Annotations["key1"], actualSchema.Annotations["key1"]); } - public static TheoryData SchemaExamples() + public static TheoryData SchemaExamples() { return new() { - new OpenApiArray() { new OpenApiString("example") }, - new OpenApiBinary([0, 1, 2]), - new OpenApiBoolean(true), - new OpenApiByte(42), - new OpenApiDate(new(2024, 07, 19, 12, 34, 56)), - new OpenApiDateTime(new(2024, 07, 19, 12, 34, 56, new(01, 00, 00))), - new OpenApiDouble(42.37), - new OpenApiFloat(42.37f), - new OpenApiInteger(42), - new OpenApiLong(42), - new OpenApiNull(), - new OpenApiObject() { ["prop"] = new OpenApiString("example") }, - new OpenApiPassword("secret"), - new OpenApiString("example"), + new JsonArray() { "example" }, + new JsonArray { 0, 1, 2 }, // Represent OpenApiBinary as JsonArray of bytes + true, + JsonValue.Create((byte)42), + JsonValue.Create(new DateTime(2024, 07, 19, 12, 34, 56, DateTimeKind.Utc).ToString("o")), // DateTime object + 42.37, + 42.37f, + 42, + null, + JsonValue.Create("secret"), //Represent OpenApiPassword as string + "example", }; } [Theory] [MemberData(nameof(SchemaExamples))] - public void CloningSchemaExamplesWorks(IOpenApiAny example) + public void CloningSchemaExamplesWorks(JsonNode example) { // Arrange var schema = new OpenApiSchema @@ -538,10 +531,11 @@ public void CloningSchemaExamplesWorks(IOpenApiAny example) // Act && Assert var schemaCopy = new OpenApiSchema(schema); - Assert.NotNull(schemaCopy.Example); // Act && Assert - Assert.Equivalent(schema.Example, schemaCopy.Example); + schema.Example.Should().BeEquivalentTo(schemaCopy.Example, options => options + .IgnoringCyclicReferences() + .Excluding(x => x.Options)); } [Fact] @@ -552,7 +546,7 @@ public void CloningSchemaExtensionsWorks() { Extensions = { - { "x-myextension", new OpenApiInteger(42) } + { "x-myextension", new OpenApiAny(42) } } }; @@ -563,7 +557,7 @@ public void CloningSchemaExtensionsWorks() // Act && Assert schemaCopy.Extensions = new Dictionary { - { "x-myextension" , new OpenApiInteger(40) } + { "x-myextension" , new OpenApiAny(40) } }; Assert.NotEqual(schema.Extensions, schemaCopy.Extensions); } @@ -577,9 +571,9 @@ public void OpenApiWalkerVisitsOpenApiSchemaNot() Not = new OpenApiSchema() { Title = "Inner Schema", - Type = "string", + Type = JsonSchemaType.String, } - }; + }; var document = new OpenApiDocument() { diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiSecurityRequirementTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiSecurityRequirementTests.cs index 9543f6a1a..1e9e13323 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiSecurityRequirementTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiSecurityRequirementTests.cs @@ -11,6 +11,7 @@ using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Writers; using VerifyXunit; +using Microsoft.OpenApi.Models.References; using Xunit; namespace Microsoft.OpenApi.Tests.Models @@ -24,10 +25,7 @@ public class OpenApiSecurityRequirementTests new() { [ - new() - { - Reference = new() { Type = ReferenceType.SecurityScheme, Id = "scheme1" } - } + new OpenApiSecuritySchemeReference("scheme1", hostDocument: null) ] = new List { "scope1", @@ -35,20 +33,14 @@ public class OpenApiSecurityRequirementTests "scope3", }, [ - new() - { - Reference = new() { Type = ReferenceType.SecurityScheme, Id = "scheme2" } - } + new OpenApiSecuritySchemeReference("scheme2", hostDocument: null) ] = new List { "scope4", "scope5", }, [ - new() - { - Reference = new() { Type = ReferenceType.SecurityScheme, Id = "scheme3" } - } + new OpenApiSecuritySchemeReference("scheme3", hostDocument: null) ] = new List() }; @@ -56,10 +48,7 @@ public class OpenApiSecurityRequirementTests new() { [ - new() - { - Reference = new() { Type = ReferenceType.SecurityScheme, Id = "scheme1" } - } + new OpenApiSecuritySchemeReference("scheme1", hostDocument: null) ] = new List { "scope1", @@ -78,10 +67,7 @@ public class OpenApiSecurityRequirementTests "scope5", }, [ - new() - { - Reference = new() { Type = ReferenceType.SecurityScheme, Id = "scheme3" } - } + new OpenApiSecuritySchemeReference("scheme3", hostDocument: null) ] = new List() }; @@ -138,8 +124,7 @@ public void SerializeSecurityRequirementWithReferencedSecuritySchemeAsV3JsonWork """; // Act - var actual = - SecurityRequirementWithReferencedSecurityScheme.SerializeAsJson(OpenApiSpecVersion.OpenApi3_0); + var actual = SecurityRequirementWithReferencedSecurityScheme.SerializeAsJson(OpenApiSpecVersion.OpenApi3_0); // Assert actual = actual.MakeLineBreaksEnvironmentNeutral(); @@ -177,8 +162,7 @@ public void SerializeSecurityRequirementWithReferencedSecuritySchemeAsV2JsonWork } [Fact] - public void - SerializeSecurityRequirementWithUnreferencedSecuritySchemeAsV3JsonShouldSkipUnserializableKeyValuePair() + public void SerializeSecurityRequirementWithUnreferencedSecuritySchemeAsV3JsonShouldSkipUnserializableKeyValuePair() { // Arrange var expected = @@ -194,8 +178,7 @@ public void """; // Act - var actual = - SecurityRequirementWithUnreferencedSecurityScheme.SerializeAsJson(OpenApiSpecVersion.OpenApi3_0); + var actual = SecurityRequirementWithUnreferencedSecurityScheme.SerializeAsJson(OpenApiSpecVersion.OpenApi3_0); // Assert actual = actual.MakeLineBreaksEnvironmentNeutral(); @@ -204,8 +187,7 @@ public void } [Fact] - public void - SerializeSecurityRequirementWithUnreferencedSecuritySchemeAsV2JsonShouldSkipUnserializableKeyValuePair() + public void SerializeSecurityRequirementWithUnreferencedSecuritySchemeAsV2JsonShouldSkipUnserializableKeyValuePair() { // Arrange var expected = diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiSecuritySchemeTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiSecuritySchemeTests.cs index aec0489b1..58794373d 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiSecuritySchemeTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiSecuritySchemeTests.cs @@ -8,6 +8,7 @@ using FluentAssertions; using Microsoft.OpenApi.Extensions; using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Models.References; using Microsoft.OpenApi.Writers; using VerifyXunit; using Xunit; @@ -104,17 +105,13 @@ public class OpenApiSecuritySchemeTests OpenIdConnectUrl = new("https://example.com/openIdConnect") }; + public static OpenApiSecuritySchemeReference OpenApiSecuritySchemeReference = new(target: ReferencedSecurityScheme, referenceId: "sampleSecurityScheme"); public static OpenApiSecurityScheme ReferencedSecurityScheme = new() { Description = "description1", Type = SecuritySchemeType.OpenIdConnect, Scheme = OpenApiConstants.Bearer, - OpenIdConnectUrl = new("https://example.com/openIdConnect"), - Reference = new() - { - Type = ReferenceType.SecurityScheme, - Id = "sampleSecurityScheme" - } + OpenIdConnectUrl = new("https://example.com/openIdConnect") }; [Fact] @@ -314,7 +311,7 @@ public async Task SerializeReferencedSecuritySchemeAsV3JsonWorksAsync(bool produ var writer = new OpenApiJsonWriter(outputStringWriter, new() { Terse = produceTerseOutput }); // Act - ReferencedSecurityScheme.SerializeAsV3(writer); + OpenApiSecuritySchemeReference.SerializeAsV3(writer); writer.Flush(); // Assert @@ -331,7 +328,7 @@ public async Task SerializeReferencedSecuritySchemeAsV3JsonWithoutReferenceWorks var writer = new OpenApiJsonWriter(outputStringWriter, new() { Terse = produceTerseOutput }); // Act - ReferencedSecurityScheme.SerializeAsV3WithoutReference(writer); + ReferencedSecurityScheme.SerializeAsV3(writer); writer.Flush(); // Assert diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiTagTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiTagTests.cs index 875eb960c..c02f7598c 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiTagTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiTagTests.cs @@ -6,7 +6,6 @@ using System.IO; using System.Threading.Tasks; using FluentAssertions; -using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Writers; @@ -27,7 +26,7 @@ public class OpenApiTagTests ExternalDocs = OpenApiExternalDocsTests.AdvanceExDocs, Extensions = new Dictionary { - {"x-tag-extension", new OpenApiNull()} + {"x-tag-extension", null} } }; @@ -38,7 +37,7 @@ public class OpenApiTagTests ExternalDocs = OpenApiExternalDocsTests.AdvanceExDocs, Extensions = new Dictionary { - {"x-tag-extension", new OpenApiNull()} + {"x-tag-extension", null} }, Reference = new() { diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiXmlTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiXmlTests.cs index 92562ac18..3a75b2336 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiXmlTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiXmlTests.cs @@ -23,7 +23,7 @@ public class OpenApiXmlTests Attribute = true, Extensions = new Dictionary { - {"x-xml-extension", new OpenApiInteger(7)} + {"x-xml-extension", new OpenApiAny(7)} } }; diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiCallbackReferenceTests.SerializeCallbackReferenceAsV31JsonWorks_produceTerseOutput=False.verified.txt b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiCallbackReferenceTests.SerializeCallbackReferenceAsV31JsonWorks_produceTerseOutput=False.verified.txt new file mode 100644 index 000000000..3bb0efa15 --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiCallbackReferenceTests.SerializeCallbackReferenceAsV31JsonWorks_produceTerseOutput=False.verified.txt @@ -0,0 +1,30 @@ +{ + "{$request.body#/callbackUrl}": { + "post": { + "requestBody": { + "content": { + "application/json": { + "schema": { + "required": [ + "message" + ], + "type": "object", + "properties": { + "message": { + "type": "string", + "example": "Some event happened" + } + } + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "ok" + } + } + } + } +} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiCallbackReferenceTests.SerializeCallbackReferenceAsV31JsonWorks_produceTerseOutput=True.verified.txt b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiCallbackReferenceTests.SerializeCallbackReferenceAsV31JsonWorks_produceTerseOutput=True.verified.txt new file mode 100644 index 000000000..63215a889 --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiCallbackReferenceTests.SerializeCallbackReferenceAsV31JsonWorks_produceTerseOutput=True.verified.txt @@ -0,0 +1 @@ +{"{$request.body#/callbackUrl}":{"post":{"requestBody":{"content":{"application/json":{"schema":{"required":["message"],"type":"object","properties":{"message":{"type":"string","example":"Some event happened"}}}}},"required":true},"responses":{"200":{"description":"ok"}}}}} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiCallbackReferenceTests.SerializeCallbackReferenceAsV3JsonWorks_produceTerseOutput=False.verified.txt b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiCallbackReferenceTests.SerializeCallbackReferenceAsV3JsonWorks_produceTerseOutput=False.verified.txt new file mode 100644 index 000000000..3bb0efa15 --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiCallbackReferenceTests.SerializeCallbackReferenceAsV3JsonWorks_produceTerseOutput=False.verified.txt @@ -0,0 +1,30 @@ +{ + "{$request.body#/callbackUrl}": { + "post": { + "requestBody": { + "content": { + "application/json": { + "schema": { + "required": [ + "message" + ], + "type": "object", + "properties": { + "message": { + "type": "string", + "example": "Some event happened" + } + } + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "ok" + } + } + } + } +} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiCallbackReferenceTests.SerializeCallbackReferenceAsV3JsonWorks_produceTerseOutput=True.verified.txt b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiCallbackReferenceTests.SerializeCallbackReferenceAsV3JsonWorks_produceTerseOutput=True.verified.txt new file mode 100644 index 000000000..63215a889 --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiCallbackReferenceTests.SerializeCallbackReferenceAsV3JsonWorks_produceTerseOutput=True.verified.txt @@ -0,0 +1 @@ +{"{$request.body#/callbackUrl}":{"post":{"requestBody":{"content":{"application/json":{"schema":{"required":["message"],"type":"object","properties":{"message":{"type":"string","example":"Some event happened"}}}}},"required":true},"responses":{"200":{"description":"ok"}}}}} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiCallbackReferenceTests.SerializeReferencedCallbackAsV31JsonWorks_produceTerseOutput=False.verified.txt b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiCallbackReferenceTests.SerializeReferencedCallbackAsV31JsonWorks_produceTerseOutput=False.verified.txt new file mode 100644 index 000000000..3bb0efa15 --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiCallbackReferenceTests.SerializeReferencedCallbackAsV31JsonWorks_produceTerseOutput=False.verified.txt @@ -0,0 +1,30 @@ +{ + "{$request.body#/callbackUrl}": { + "post": { + "requestBody": { + "content": { + "application/json": { + "schema": { + "required": [ + "message" + ], + "type": "object", + "properties": { + "message": { + "type": "string", + "example": "Some event happened" + } + } + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "ok" + } + } + } + } +} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiCallbackReferenceTests.SerializeReferencedCallbackAsV31JsonWorks_produceTerseOutput=True.verified.txt b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiCallbackReferenceTests.SerializeReferencedCallbackAsV31JsonWorks_produceTerseOutput=True.verified.txt new file mode 100644 index 000000000..63215a889 --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiCallbackReferenceTests.SerializeReferencedCallbackAsV31JsonWorks_produceTerseOutput=True.verified.txt @@ -0,0 +1 @@ +{"{$request.body#/callbackUrl}":{"post":{"requestBody":{"content":{"application/json":{"schema":{"required":["message"],"type":"object","properties":{"message":{"type":"string","example":"Some event happened"}}}}},"required":true},"responses":{"200":{"description":"ok"}}}}} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiCallbackReferenceTests.SerializeReferencedCallbackAsV3JsonWorks_produceTerseOutput=False.verified.txt b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiCallbackReferenceTests.SerializeReferencedCallbackAsV3JsonWorks_produceTerseOutput=False.verified.txt new file mode 100644 index 000000000..3bb0efa15 --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiCallbackReferenceTests.SerializeReferencedCallbackAsV3JsonWorks_produceTerseOutput=False.verified.txt @@ -0,0 +1,30 @@ +{ + "{$request.body#/callbackUrl}": { + "post": { + "requestBody": { + "content": { + "application/json": { + "schema": { + "required": [ + "message" + ], + "type": "object", + "properties": { + "message": { + "type": "string", + "example": "Some event happened" + } + } + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "ok" + } + } + } + } +} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiCallbackReferenceTests.SerializeReferencedCallbackAsV3JsonWorks_produceTerseOutput=True.verified.txt b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiCallbackReferenceTests.SerializeReferencedCallbackAsV3JsonWorks_produceTerseOutput=True.verified.txt new file mode 100644 index 000000000..63215a889 --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiCallbackReferenceTests.SerializeReferencedCallbackAsV3JsonWorks_produceTerseOutput=True.verified.txt @@ -0,0 +1 @@ +{"{$request.body#/callbackUrl}":{"post":{"requestBody":{"content":{"application/json":{"schema":{"required":["message"],"type":"object","properties":{"message":{"type":"string","example":"Some event happened"}}}}},"required":true},"responses":{"200":{"description":"ok"}}}}} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiCallbackReferenceTests.cs b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiCallbackReferenceTests.cs new file mode 100644 index 000000000..3a16f4d2a --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiCallbackReferenceTests.cs @@ -0,0 +1,196 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System.Globalization; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Models.References; +using Microsoft.OpenApi.Reader; +using Microsoft.OpenApi.Readers; +using Microsoft.OpenApi.Services; +using Microsoft.OpenApi.Writers; +using VerifyXunit; +using Xunit; + +namespace Microsoft.OpenApi.Tests.Models.References +{ + [Collection("DefaultSettings")] + public class OpenApiCallbackReferenceTests + { + // OpenApi doc with external $ref + private const string OpenApi = @" +openapi: 3.0.0 +info: + title: Callback with ref Example + version: 1.0.0 +servers: + - url: https://myserver.com/v1.0 +paths: + /register: + post: + summary: Subscribe to a webhook + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + callbackUrl: # Callback URL + type: string + format: uri + example: https://myserver.com/send/callback/here + required: + - callbackUrl + responses: + '200': + description: subscription successfully created + content: + application/json: + schema: + type: object + description: subscription information + required: + - subscriptionId + properties: + subscriptionId: + description: unique identifier + type: string + example: 2531329f-fb09-4ef7-887e-84e648214436 + callbacks: + myEvent: + $ref: 'https://myserver.com/beta#/components/callbacks/callbackEvent'"; + + // OpenApi doc with local $ref + private const string OpenApi_2 = @" +openapi: 3.0.0 +info: + title: Callback with ref Example + version: 1.0.0 +servers: + - url: https://myserver.com/beta +paths: + /register: + post: + summary: Subscribe to a webhook + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + callbackUrl: # Callback URL + type: string + format: uri + example: https://myserver.com/send/callback/here + required: + - callbackUrl + responses: + '200': + description: subscription successfully created + content: + application/json: + schema: + type: object + description: subscription information + required: + - subscriptionId + properties: + subscriptionId: + description: unique identifier + type: string + example: 2531329f-fb09-4ef7-887e-84e648214436 + callbacks: + myEvent: + $ref: '#/components/callbacks/callbackEvent' + +components: + callbacks: + callbackEvent: + '{$request.body#/callbackUrl}': + post: + requestBody: # Contents of the callback message + required: true + content: + application/json: + schema: + type: object + properties: + message: + type: string + example: Some event happened + required: + - message + responses: + '200': + description: ok"; + + private readonly OpenApiCallbackReference _externalCallbackReference; + private readonly OpenApiCallbackReference _localCallbackReference; + + 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; + openApiDoc.Workspace.AddDocumentId("https://myserver.com/beta", openApiDoc_2.BaseUri); + openApiDoc.Workspace.RegisterComponents(openApiDoc_2); + _externalCallbackReference = new("callbackEvent", openApiDoc, "https://myserver.com/beta"); + _localCallbackReference = new("callbackEvent", openApiDoc_2); + } + + [Fact] + public void CallbackReferenceResolutionWorks() + { + // Assert + // External reference resolution works + Assert.NotEmpty(_externalCallbackReference.PathItems); + Assert.Single(_externalCallbackReference.PathItems); + Assert.Equal("{$request.body#/callbackUrl}", _externalCallbackReference.PathItems.First().Key.Expression); + Assert.Equal(OperationType.Post, _externalCallbackReference.PathItems.FirstOrDefault().Value.Operations.FirstOrDefault().Key);; + + // Local reference resolution works + Assert.NotEmpty(_localCallbackReference.PathItems); + Assert.Single(_localCallbackReference.PathItems); + Assert.Equal("{$request.body#/callbackUrl}", _localCallbackReference.PathItems.First().Key.Expression); + Assert.Equal(OperationType.Post, _localCallbackReference.PathItems.FirstOrDefault().Value.Operations.FirstOrDefault().Key); ; + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task SerializeCallbackReferenceAsV3JsonWorks(bool produceTerseOutput) + { + // Arrange + var outputStringWriter = new StringWriter(CultureInfo.InvariantCulture); + var writer = new OpenApiJsonWriter(outputStringWriter, new OpenApiJsonWriterSettings { Terse = produceTerseOutput, InlineExternalReferences = true }); + + // Act + _externalCallbackReference.SerializeAsV3(writer); + writer.Flush(); + + // Assert + await Verifier.Verify(outputStringWriter).UseParameters(produceTerseOutput); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task SerializeCallbackReferenceAsV31JsonWorks(bool produceTerseOutput) + { + // Arrange + var outputStringWriter = new StringWriter(CultureInfo.InvariantCulture); + var writer = new OpenApiJsonWriter(outputStringWriter, new OpenApiJsonWriterSettings { Terse = produceTerseOutput, InlineExternalReferences = true }); + + // Act + _externalCallbackReference.SerializeAsV31(writer); + writer.Flush(); + + // Assert + await Verifier.Verify(outputStringWriter).UseParameters(produceTerseOutput); + } + } +} diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiExampleReferenceTests.SerializeCallbackReferenceAsV31JsonWorks_produceTerseOutput=False.verified.txt b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiExampleReferenceTests.SerializeCallbackReferenceAsV31JsonWorks_produceTerseOutput=False.verified.txt new file mode 100644 index 000000000..f71202885 --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiExampleReferenceTests.SerializeCallbackReferenceAsV31JsonWorks_produceTerseOutput=False.verified.txt @@ -0,0 +1,10 @@ +{ + "summary": "Example of a user", + "description": "This is is an example of a user", + "value": [ + { + "id": "1", + "name": "John Doe" + } + ] +} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiExampleReferenceTests.SerializeCallbackReferenceAsV31JsonWorks_produceTerseOutput=True.verified.txt b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiExampleReferenceTests.SerializeCallbackReferenceAsV31JsonWorks_produceTerseOutput=True.verified.txt new file mode 100644 index 000000000..cddf257f8 --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiExampleReferenceTests.SerializeCallbackReferenceAsV31JsonWorks_produceTerseOutput=True.verified.txt @@ -0,0 +1 @@ +{"summary":"Example of a user","description":"This is is an example of a user","value":[{"id":"1","name":"John Doe"}]} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiExampleReferenceTests.SerializeCallbackReferenceAsV3JsonWorks_produceTerseOutput=False.verified.txt b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiExampleReferenceTests.SerializeCallbackReferenceAsV3JsonWorks_produceTerseOutput=False.verified.txt new file mode 100644 index 000000000..f71202885 --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiExampleReferenceTests.SerializeCallbackReferenceAsV3JsonWorks_produceTerseOutput=False.verified.txt @@ -0,0 +1,10 @@ +{ + "summary": "Example of a user", + "description": "This is is an example of a user", + "value": [ + { + "id": "1", + "name": "John Doe" + } + ] +} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiExampleReferenceTests.SerializeCallbackReferenceAsV3JsonWorks_produceTerseOutput=True.verified.txt b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiExampleReferenceTests.SerializeCallbackReferenceAsV3JsonWorks_produceTerseOutput=True.verified.txt new file mode 100644 index 000000000..cddf257f8 --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiExampleReferenceTests.SerializeCallbackReferenceAsV3JsonWorks_produceTerseOutput=True.verified.txt @@ -0,0 +1 @@ +{"summary":"Example of a user","description":"This is is an example of a user","value":[{"id":"1","name":"John Doe"}]} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiExampleReferenceTests.SerializeExampleReferenceAsV31JsonWorks_produceTerseOutput=False.verified.txt b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiExampleReferenceTests.SerializeExampleReferenceAsV31JsonWorks_produceTerseOutput=False.verified.txt new file mode 100644 index 000000000..d3d85c6b5 --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiExampleReferenceTests.SerializeExampleReferenceAsV31JsonWorks_produceTerseOutput=False.verified.txt @@ -0,0 +1,10 @@ +{ + "summary": "Example of a local user", + "description": "This is an example of a local user", + "value": [ + { + "id": 1, + "name": "John Doe" + } + ] +} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiExampleReferenceTests.SerializeExampleReferenceAsV31JsonWorks_produceTerseOutput=True.verified.txt b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiExampleReferenceTests.SerializeExampleReferenceAsV31JsonWorks_produceTerseOutput=True.verified.txt new file mode 100644 index 000000000..0c1962929 --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiExampleReferenceTests.SerializeExampleReferenceAsV31JsonWorks_produceTerseOutput=True.verified.txt @@ -0,0 +1 @@ +{"summary":"Example of a local user","description":"This is an example of a local user","value":[{"id":1,"name":"John Doe"}]} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiExampleReferenceTests.SerializeExampleReferenceAsV3JsonWorks_produceTerseOutput=False.verified.txt b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiExampleReferenceTests.SerializeExampleReferenceAsV3JsonWorks_produceTerseOutput=False.verified.txt new file mode 100644 index 000000000..d3d85c6b5 --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiExampleReferenceTests.SerializeExampleReferenceAsV3JsonWorks_produceTerseOutput=False.verified.txt @@ -0,0 +1,10 @@ +{ + "summary": "Example of a local user", + "description": "This is an example of a local user", + "value": [ + { + "id": 1, + "name": "John Doe" + } + ] +} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiExampleReferenceTests.SerializeExampleReferenceAsV3JsonWorks_produceTerseOutput=True.verified.txt b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiExampleReferenceTests.SerializeExampleReferenceAsV3JsonWorks_produceTerseOutput=True.verified.txt new file mode 100644 index 000000000..0c1962929 --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiExampleReferenceTests.SerializeExampleReferenceAsV3JsonWorks_produceTerseOutput=True.verified.txt @@ -0,0 +1 @@ +{"summary":"Example of a local user","description":"This is an example of a local user","value":[{"id":1,"name":"John Doe"}]} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiExampleReferenceTests.cs b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiExampleReferenceTests.cs new file mode 100644 index 000000000..4ea8cdef9 --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiExampleReferenceTests.cs @@ -0,0 +1,187 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System.Globalization; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Models.References; +using Microsoft.OpenApi.Reader; +using Microsoft.OpenApi.Readers; +using Microsoft.OpenApi.Writers; +using Microsoft.OpenApi.Services; +using VerifyXunit; +using Xunit; + +namespace Microsoft.OpenApi.Tests.Models.References +{ + [Collection("DefaultSettings")] + public class OpenApiExampleReferenceTests + { + // OpenApi doc with external $ref + private const string OpenApi = @" +openapi: 3.0.0 +info: + title: Sample API + version: 1.0.0 +servers: + - url: https://myserver.com/v1.0 +paths: + /users: + get: + summary: Get users + responses: + '200': + description: Successful operation + content: + application/json: + schema: + type: array + items: + $ref: 'https://myserver.com/beta#/components/schemas/User' + examples: + - $ref: 'https://myserver.com/beta#/components/examples/UserExample' +components: + callbacks: + callbackEvent: + '{$request.body#/callbackUrl}': + post: + requestBody: # Contents of the callback message + required: true + content: + application/json: + schema: + type: object + properties: + message: + type: string + example: Some event happened + required: + - message + responses: + '200': + description: ok""; +"; + + // OpenApi doc with local $ref + private const string OpenApi_2 = @" +openapi: 3.0.0 +info: + title: Sample API + version: 1.0.0 +servers: + - url: https://myserver.com/beta +paths: + /users: + get: + summary: Get users + responses: + '200': + description: Successful operation + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/User' + examples: + - $ref: '#/components/examples/UserExample' +components: + schemas: + User: + type: object + properties: + id: + type: integer + name: + type: string + examples: + UserExample: + summary: Example of a user + description: This is is an example of a user + value: + - id: 1 + name: John Doe +"; + + private readonly OpenApiExampleReference _localExampleReference; + private readonly OpenApiExampleReference _externalExampleReference; + private readonly OpenApiDocument _openApiDoc; + private readonly OpenApiDocument _openApiDoc_2; + + 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.Workspace.AddDocumentId("https://myserver.com/beta", _openApiDoc_2.BaseUri); + _openApiDoc.Workspace.RegisterComponents(_openApiDoc_2); + + _localExampleReference = new OpenApiExampleReference("UserExample", _openApiDoc_2) + { + Summary = "Example of a local user", + Description = "This is an example of a local user" + }; + + _externalExampleReference = new OpenApiExampleReference("UserExample", _openApiDoc, "https://myserver.com/beta") + { + Summary = "Example of an external user", + Description = "This is an example of an external user" + }; + } + + [Fact] + public void ExampleReferenceResolutionWorks() + { + // Assert + Assert.NotNull(_localExampleReference.Value); + Assert.Equal("[{\"id\":1,\"name\":\"John Doe\"}]", _localExampleReference.Value.ToJsonString()); + Assert.Equal("Example of a local user", _localExampleReference.Summary); + Assert.Equal("This is an example of a local user", _localExampleReference.Description); + + Assert.NotNull(_externalExampleReference.Value); + Assert.Equal("Example of an external user", _externalExampleReference.Summary); + Assert.Equal("This is an example of an external user", _externalExampleReference.Description); + + // The main description and summary values shouldn't change + Assert.Equal("Example of a user", _openApiDoc_2.Components.Examples.First().Value.Summary); + Assert.Equal("This is is an example of a user", + _openApiDoc_2.Components.Examples.FirstOrDefault().Value.Description); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task SerializeExampleReferenceAsV3JsonWorks(bool produceTerseOutput) + { + // Arrange + var outputStringWriter = new StringWriter(CultureInfo.InvariantCulture); + var writer = new OpenApiJsonWriter(outputStringWriter, new OpenApiJsonWriterSettings { Terse = produceTerseOutput, InlineLocalReferences = true }); + + // Act + _localExampleReference.SerializeAsV3(writer); + writer.Flush(); + + // Assert + await Verifier.Verify(outputStringWriter).UseParameters(produceTerseOutput); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task SerializeExampleReferenceAsV31JsonWorks(bool produceTerseOutput) + { + // Arrange + var outputStringWriter = new StringWriter(CultureInfo.InvariantCulture); + var writer = new OpenApiJsonWriter(outputStringWriter, new OpenApiJsonWriterSettings { Terse = produceTerseOutput, InlineLocalReferences = true }); + + // Act + _localExampleReference.SerializeAsV31(writer); + writer.Flush(); + + // Assert + await Verifier.Verify(outputStringWriter).UseParameters(produceTerseOutput); + } + } +} diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiHeaderReferenceTests.SerializeCallbackReferenceAsV31JsonWorks_produceTerseOutput=False.verified.txt b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiHeaderReferenceTests.SerializeCallbackReferenceAsV31JsonWorks_produceTerseOutput=False.verified.txt new file mode 100644 index 000000000..f43e25a40 --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiHeaderReferenceTests.SerializeCallbackReferenceAsV31JsonWorks_produceTerseOutput=False.verified.txt @@ -0,0 +1,6 @@ +{ + "description": "The URL of the newly created post", + "schema": { + "type": "string" + } +} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiHeaderReferenceTests.SerializeCallbackReferenceAsV31JsonWorks_produceTerseOutput=True.verified.txt b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiHeaderReferenceTests.SerializeCallbackReferenceAsV31JsonWorks_produceTerseOutput=True.verified.txt new file mode 100644 index 000000000..1b29be17d --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiHeaderReferenceTests.SerializeCallbackReferenceAsV31JsonWorks_produceTerseOutput=True.verified.txt @@ -0,0 +1 @@ +{"description":"The URL of the newly created post","schema":{"type":"string"}} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiHeaderReferenceTests.SerializeCallbackReferenceAsV3JsonWorks_produceTerseOutput=False.verified.txt b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiHeaderReferenceTests.SerializeCallbackReferenceAsV3JsonWorks_produceTerseOutput=False.verified.txt new file mode 100644 index 000000000..f43e25a40 --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiHeaderReferenceTests.SerializeCallbackReferenceAsV3JsonWorks_produceTerseOutput=False.verified.txt @@ -0,0 +1,6 @@ +{ + "description": "The URL of the newly created post", + "schema": { + "type": "string" + } +} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiHeaderReferenceTests.SerializeCallbackReferenceAsV3JsonWorks_produceTerseOutput=True.verified.txt b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiHeaderReferenceTests.SerializeCallbackReferenceAsV3JsonWorks_produceTerseOutput=True.verified.txt new file mode 100644 index 000000000..1b29be17d --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiHeaderReferenceTests.SerializeCallbackReferenceAsV3JsonWorks_produceTerseOutput=True.verified.txt @@ -0,0 +1 @@ +{"description":"The URL of the newly created post","schema":{"type":"string"}} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiHeaderReferenceTests.SerializeHeaderReferenceAsV2JsonWorksAsync_produceTerseOutput=False.verified.txt b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiHeaderReferenceTests.SerializeHeaderReferenceAsV2JsonWorksAsync_produceTerseOutput=False.verified.txt new file mode 100644 index 000000000..b957bd951 --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiHeaderReferenceTests.SerializeHeaderReferenceAsV2JsonWorksAsync_produceTerseOutput=False.verified.txt @@ -0,0 +1,4 @@ +{ + "description": "Location of the locally referenced post", + "type": "string" +} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiHeaderReferenceTests.SerializeHeaderReferenceAsV2JsonWorksAsync_produceTerseOutput=True.verified.txt b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiHeaderReferenceTests.SerializeHeaderReferenceAsV2JsonWorksAsync_produceTerseOutput=True.verified.txt new file mode 100644 index 000000000..17f59471d --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiHeaderReferenceTests.SerializeHeaderReferenceAsV2JsonWorksAsync_produceTerseOutput=True.verified.txt @@ -0,0 +1 @@ +{"description":"Location of the locally referenced post","type":"string"} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiHeaderReferenceTests.SerializeHeaderReferenceAsV31JsonWorks_produceTerseOutput=False.verified.txt b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiHeaderReferenceTests.SerializeHeaderReferenceAsV31JsonWorks_produceTerseOutput=False.verified.txt new file mode 100644 index 000000000..badfda7f7 --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiHeaderReferenceTests.SerializeHeaderReferenceAsV31JsonWorks_produceTerseOutput=False.verified.txt @@ -0,0 +1,6 @@ +{ + "description": "Location of the locally referenced post", + "schema": { + "type": "string" + } +} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiHeaderReferenceTests.SerializeHeaderReferenceAsV31JsonWorks_produceTerseOutput=True.verified.txt b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiHeaderReferenceTests.SerializeHeaderReferenceAsV31JsonWorks_produceTerseOutput=True.verified.txt new file mode 100644 index 000000000..cf7cf9e25 --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiHeaderReferenceTests.SerializeHeaderReferenceAsV31JsonWorks_produceTerseOutput=True.verified.txt @@ -0,0 +1 @@ +{"description":"Location of the locally referenced post","schema":{"type":"string"}} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiHeaderReferenceTests.SerializeHeaderReferenceAsV3JsonWorks_produceTerseOutput=False.verified.txt b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiHeaderReferenceTests.SerializeHeaderReferenceAsV3JsonWorks_produceTerseOutput=False.verified.txt new file mode 100644 index 000000000..badfda7f7 --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiHeaderReferenceTests.SerializeHeaderReferenceAsV3JsonWorks_produceTerseOutput=False.verified.txt @@ -0,0 +1,6 @@ +{ + "description": "Location of the locally referenced post", + "schema": { + "type": "string" + } +} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiHeaderReferenceTests.SerializeHeaderReferenceAsV3JsonWorks_produceTerseOutput=True.verified.txt b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiHeaderReferenceTests.SerializeHeaderReferenceAsV3JsonWorks_produceTerseOutput=True.verified.txt new file mode 100644 index 000000000..cf7cf9e25 --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiHeaderReferenceTests.SerializeHeaderReferenceAsV3JsonWorks_produceTerseOutput=True.verified.txt @@ -0,0 +1 @@ +{"description":"Location of the locally referenced post","schema":{"type":"string"}} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiHeaderReferenceTests.SerializeParameterReferenceAsV2JsonWorksAsync_produceTerseOutput=False.verified.txt b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiHeaderReferenceTests.SerializeParameterReferenceAsV2JsonWorksAsync_produceTerseOutput=False.verified.txt new file mode 100644 index 000000000..8b29b212e --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiHeaderReferenceTests.SerializeParameterReferenceAsV2JsonWorksAsync_produceTerseOutput=False.verified.txt @@ -0,0 +1,4 @@ +{ + "description": "Location of the locally created post", + "type": "string" +} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiHeaderReferenceTests.SerializeParameterReferenceAsV2JsonWorksAsync_produceTerseOutput=True.verified.txt b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiHeaderReferenceTests.SerializeParameterReferenceAsV2JsonWorksAsync_produceTerseOutput=True.verified.txt new file mode 100644 index 000000000..243908873 --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiHeaderReferenceTests.SerializeParameterReferenceAsV2JsonWorksAsync_produceTerseOutput=True.verified.txt @@ -0,0 +1 @@ +{"description":"Location of the locally created post","type":"string"} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiHeaderReferenceTests.cs b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiHeaderReferenceTests.cs new file mode 100644 index 000000000..cfdf4ab1c --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiHeaderReferenceTests.cs @@ -0,0 +1,163 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System.Globalization; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Models.References; +using Microsoft.OpenApi.Reader; +using Microsoft.OpenApi.Readers; +using Microsoft.OpenApi.Writers; +using Microsoft.OpenApi.Services; +using VerifyXunit; +using Xunit; + +namespace Microsoft.OpenApi.Tests.Models.References +{ + [Collection("DefaultSettings")] + public class OpenApiHeaderReferenceTests + { + // OpenApi doc with external $ref + private const string OpenApi= @" +openapi: 3.0.0 +info: + title: Sample API + version: 1.0.0 +servers: + - url: https://myserver.com/v1.0 +paths: + /users: + post: + summary: Create a post + responses: + '201': + description: Post created successfully + headers: + Location: + $ref: 'https://myserver.com/beta##/components/headers/LocationHeader' +components: + schemas: + User: + type: object + properties: + id: + type: integer + name: + type: string +"; + + // OpenApi doc with local $ref + private const string OpenApi_2 = @" +openapi: 3.0.0 +info: + title: Sample API + version: 1.0.0 +servers: + - url: https://myserver.com/beta +paths: + /users: + post: + summary: Create a post + responses: + '201': + description: Post created successfully + headers: + Location: + $ref: '#/components/headers/LocationHeader' +components: + headers: + LocationHeader: + description: The URL of the newly created post + schema: + type: string +"; + + private readonly OpenApiHeaderReference _localHeaderReference; + private readonly OpenApiHeaderReference _externalHeaderReference; + private readonly OpenApiDocument _openApiDoc; + private readonly OpenApiDocument _openApiDoc_2; + + 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.Workspace.AddDocumentId("https://myserver.com/beta", _openApiDoc_2.BaseUri); + _openApiDoc.Workspace.RegisterComponents(_openApiDoc_2); + + _localHeaderReference = new OpenApiHeaderReference("LocationHeader", _openApiDoc_2) + { + Description = "Location of the locally referenced post" + }; + + _externalHeaderReference = new OpenApiHeaderReference("LocationHeader", _openApiDoc, "https://myserver.com/beta") + { + Description = "Location of the externally referenced post" + }; + } + + [Fact] + public void HeaderReferenceResolutionWorks() + { + // Assert + Assert.Equal(JsonSchemaType.String, _externalHeaderReference.Schema.Type); + Assert.Equal("Location of the locally referenced post", _localHeaderReference.Description); + Assert.Equal("Location of the externally referenced post", _externalHeaderReference.Description); + Assert.Equal("The URL of the newly created post", + _openApiDoc_2.Components.Headers.First().Value.Description); // The main description value shouldn't change + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task SerializeHeaderReferenceAsV3JsonWorks(bool produceTerseOutput) + { + // Arrange + var outputStringWriter = new StringWriter(CultureInfo.InvariantCulture); + var writer = new OpenApiJsonWriter(outputStringWriter, new OpenApiJsonWriterSettings { Terse = produceTerseOutput, InlineLocalReferences = true }); + + // Act + _localHeaderReference.SerializeAsV3(writer); + writer.Flush(); + + // Assert + await Verifier.Verify(outputStringWriter).UseParameters(produceTerseOutput); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task SerializeHeaderReferenceAsV31JsonWorks(bool produceTerseOutput) + { + // Arrange + var outputStringWriter = new StringWriter(CultureInfo.InvariantCulture); + var writer = new OpenApiJsonWriter(outputStringWriter, new OpenApiJsonWriterSettings { Terse = produceTerseOutput, InlineLocalReferences = true }); + + // Act + _localHeaderReference.SerializeAsV31(writer); + writer.Flush(); + + // Assert + await Verifier.Verify(outputStringWriter).UseParameters(produceTerseOutput); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task SerializeHeaderReferenceAsV2JsonWorksAsync(bool produceTerseOutput) + { + // Arrange + var outputStringWriter = new StringWriter(CultureInfo.InvariantCulture); + var writer = new OpenApiJsonWriter(outputStringWriter, new OpenApiJsonWriterSettings { Terse = produceTerseOutput, InlineLocalReferences = true}); + + // Act + _localHeaderReference.SerializeAsV2(writer); + writer.Flush(); + + // Assert + await Verifier.Verify(outputStringWriter).UseParameters(produceTerseOutput); + } + } +} diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiLinkReferenceTests.SerializeCallbackReferenceAsV31JsonWorks_produceTerseOutput=False.verified.txt b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiLinkReferenceTests.SerializeCallbackReferenceAsV31JsonWorks_produceTerseOutput=False.verified.txt new file mode 100644 index 000000000..6fe727ea0 --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiLinkReferenceTests.SerializeCallbackReferenceAsV31JsonWorks_produceTerseOutput=False.verified.txt @@ -0,0 +1,7 @@ +{ + "operationId": "getUser", + "parameters": { + "userId": "$response.body#/id" + }, + "description": "The id value returned in the response can be used as the userId parameter in GET /users/{userId}" +} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiLinkReferenceTests.SerializeCallbackReferenceAsV31JsonWorks_produceTerseOutput=True.verified.txt b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiLinkReferenceTests.SerializeCallbackReferenceAsV31JsonWorks_produceTerseOutput=True.verified.txt new file mode 100644 index 000000000..e3df412e9 --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiLinkReferenceTests.SerializeCallbackReferenceAsV31JsonWorks_produceTerseOutput=True.verified.txt @@ -0,0 +1 @@ +{"operationId":"getUser","parameters":{"userId":"$response.body#/id"},"description":"The id value returned in the response can be used as the userId parameter in GET /users/{userId}"} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiLinkReferenceTests.SerializeCallbackReferenceAsV3JsonWorks_produceTerseOutput=False.verified.txt b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiLinkReferenceTests.SerializeCallbackReferenceAsV3JsonWorks_produceTerseOutput=False.verified.txt new file mode 100644 index 000000000..6fe727ea0 --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiLinkReferenceTests.SerializeCallbackReferenceAsV3JsonWorks_produceTerseOutput=False.verified.txt @@ -0,0 +1,7 @@ +{ + "operationId": "getUser", + "parameters": { + "userId": "$response.body#/id" + }, + "description": "The id value returned in the response can be used as the userId parameter in GET /users/{userId}" +} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiLinkReferenceTests.SerializeCallbackReferenceAsV3JsonWorks_produceTerseOutput=True.verified.txt b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiLinkReferenceTests.SerializeCallbackReferenceAsV3JsonWorks_produceTerseOutput=True.verified.txt new file mode 100644 index 000000000..e3df412e9 --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiLinkReferenceTests.SerializeCallbackReferenceAsV3JsonWorks_produceTerseOutput=True.verified.txt @@ -0,0 +1 @@ +{"operationId":"getUser","parameters":{"userId":"$response.body#/id"},"description":"The id value returned in the response can be used as the userId parameter in GET /users/{userId}"} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiLinkReferenceTests.SerializeLinkReferenceAsV31JsonWorks_produceTerseOutput=False.verified.txt b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiLinkReferenceTests.SerializeLinkReferenceAsV31JsonWorks_produceTerseOutput=False.verified.txt new file mode 100644 index 000000000..89319843f --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiLinkReferenceTests.SerializeLinkReferenceAsV31JsonWorks_produceTerseOutput=False.verified.txt @@ -0,0 +1,7 @@ +{ + "operationId": "getUser", + "parameters": { + "userId": "$response.body#/id" + }, + "description": "Use the id returned as the userId in `GET /users/{userId}`" +} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiLinkReferenceTests.SerializeLinkReferenceAsV31JsonWorks_produceTerseOutput=True.verified.txt b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiLinkReferenceTests.SerializeLinkReferenceAsV31JsonWorks_produceTerseOutput=True.verified.txt new file mode 100644 index 000000000..93208a391 --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiLinkReferenceTests.SerializeLinkReferenceAsV31JsonWorks_produceTerseOutput=True.verified.txt @@ -0,0 +1 @@ +{"operationId":"getUser","parameters":{"userId":"$response.body#/id"},"description":"Use the id returned as the userId in `GET /users/{userId}`"} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiLinkReferenceTests.SerializeLinkReferenceAsV3JsonWorks_produceTerseOutput=False.verified.txt b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiLinkReferenceTests.SerializeLinkReferenceAsV3JsonWorks_produceTerseOutput=False.verified.txt new file mode 100644 index 000000000..89319843f --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiLinkReferenceTests.SerializeLinkReferenceAsV3JsonWorks_produceTerseOutput=False.verified.txt @@ -0,0 +1,7 @@ +{ + "operationId": "getUser", + "parameters": { + "userId": "$response.body#/id" + }, + "description": "Use the id returned as the userId in `GET /users/{userId}`" +} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiLinkReferenceTests.SerializeLinkReferenceAsV3JsonWorks_produceTerseOutput=True.verified.txt b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiLinkReferenceTests.SerializeLinkReferenceAsV3JsonWorks_produceTerseOutput=True.verified.txt new file mode 100644 index 000000000..93208a391 --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiLinkReferenceTests.SerializeLinkReferenceAsV3JsonWorks_produceTerseOutput=True.verified.txt @@ -0,0 +1 @@ +{"operationId":"getUser","parameters":{"userId":"$response.body#/id"},"description":"Use the id returned as the userId in `GET /users/{userId}`"} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiLinkReferenceTests.cs b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiLinkReferenceTests.cs new file mode 100644 index 000000000..87d2db06e --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiLinkReferenceTests.cs @@ -0,0 +1,195 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System.Globalization; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Models.References; +using Microsoft.OpenApi.Reader; +using Microsoft.OpenApi.Readers; +using Microsoft.OpenApi.Writers; +using Microsoft.OpenApi.Services; +using VerifyXunit; +using Xunit; + +namespace Microsoft.OpenApi.Tests.Models.References +{ + [Collection("DefaultSettings")] + public class OpenApiLinkReferenceTests + { + // OpenApi doc with external $ref + private const string OpenApi = @" +openapi: 3.0.0 +info: + version: 0.0.0 + title: Links example +servers: + - url: https://myserver.com/v1.0 +paths: + /users: + post: + summary: Creates a user and returns the user ID + operationId: createUser + requestBody: + required: true + description: A JSON object that contains the user name and age. + content: + application/json: + schema: + $ref: '#/components/schemas/User' + responses: + '201': + description: Created + content: + application/json: + schema: + type: object + properties: + id: + type: integer + format: int64 + description: ID of the created user. + links: + GetUserByUserId: + $ref: 'https://myserver.com/beta#/components/links/GetUserByUserId' # <---- referencing the link here (externally) +components: + schemas: + User: + type: object + properties: + id: + type: integer + name: + type: string +"; + + // OpenApi doc with local $ref + private const string OpenApi_2 = @" +openapi: 3.0.0 +info: + version: 0.0.0 + title: Links example +servers: + - url: https://myserver.com/beta +paths: + /users: + post: + summary: Creates a user and returns the user ID + operationId: createUser + requestBody: + required: true + description: A JSON object that contains the user name and age. + content: + application/json: + schema: + $ref: '#/components/schemas/User' + responses: + '201': + description: Created + content: + application/json: + schema: + type: object + properties: + id: + type: integer + format: int64 + description: ID of the created user. + links: + GetUserByUserId: + $ref: '#/components/links/GetUserByUserId' # <---- referencing the link here +components: + links: + GetUserByUserId: + operationId: getUser + parameters: + userId: '$response.body#/id' + description: The id value returned in the response can be used as the userId parameter in GET /users/{userId} + schemas: + User: + type: object + properties: + id: + type: integer + name: + type: string +"; + + private readonly OpenApiLinkReference _localLinkReference; + private readonly OpenApiLinkReference _externalLinkReference; + private readonly OpenApiDocument _openApiDoc; + private readonly OpenApiDocument _openApiDoc_2; + + 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.Workspace.AddDocumentId("https://myserver.com/beta", _openApiDoc_2.BaseUri); + _openApiDoc.Workspace.RegisterComponents(_openApiDoc_2); + + _localLinkReference = new("GetUserByUserId", _openApiDoc_2) + { + Description = "Use the id returned as the userId in `GET /users/{userId}`" + }; + + _externalLinkReference = new("GetUserByUserId", _openApiDoc, "https://myserver.com/beta") + { + Description = "Externally referenced: Use the id returned as the userId in `GET /users/{userId}`" + }; + } + + [Fact] + public void LinkReferenceResolutionWorks() + { + // Assert + Assert.Equal("getUser", _localLinkReference.OperationId); + Assert.Equal("userId", _localLinkReference.Parameters.First().Key); + Assert.Equal("Use the id returned as the userId in `GET /users/{userId}`", _localLinkReference.Description); + + Assert.Equal("getUser", _externalLinkReference.OperationId); + Assert.Equal("userId", _localLinkReference.Parameters.First().Key); + Assert.Equal("Externally referenced: Use the id returned as the userId in `GET /users/{userId}`", _externalLinkReference.Description); + + // The main description and summary values shouldn't change + Assert.Equal("The id value returned in the response can be used as the userId parameter in GET /users/{userId}", + _openApiDoc_2.Components.Links.FirstOrDefault().Value.Description); // The main description value shouldn't change + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task SerializeLinkReferenceAsV3JsonWorks(bool produceTerseOutput) + { + // Arrange + var outputStringWriter = new StringWriter(CultureInfo.InvariantCulture); + var writer = new OpenApiJsonWriter(outputStringWriter, new OpenApiJsonWriterSettings { Terse = produceTerseOutput, InlineLocalReferences = true }); + + // Act + _localLinkReference.SerializeAsV3(writer); + writer.Flush(); + + // Assert + await Verifier.Verify(outputStringWriter).UseParameters(produceTerseOutput); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task SerializeLinkReferenceAsV31JsonWorks(bool produceTerseOutput) + { + // Arrange + var outputStringWriter = new StringWriter(CultureInfo.InvariantCulture); + var writer = new OpenApiJsonWriter(outputStringWriter, new OpenApiJsonWriterSettings { Terse = produceTerseOutput, InlineLocalReferences = true }); + + // Act + _localLinkReference.SerializeAsV31(writer); + writer.Flush(); + + // Assert + await Verifier.Verify(outputStringWriter).UseParameters(produceTerseOutput); + } + } +} diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiParameterReferenceTests.SerializeParameterReferenceAsV2JsonWorksAsync_produceTerseOutput=False.verified.txt b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiParameterReferenceTests.SerializeParameterReferenceAsV2JsonWorksAsync_produceTerseOutput=False.verified.txt new file mode 100644 index 000000000..2a64ba6d9 --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiParameterReferenceTests.SerializeParameterReferenceAsV2JsonWorksAsync_produceTerseOutput=False.verified.txt @@ -0,0 +1,8 @@ +{ + "in": "query", + "name": "limit", + "description": "Results to return", + "type": "integer", + "maximum": 100, + "minimum": 1 +} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiParameterReferenceTests.SerializeParameterReferenceAsV2JsonWorksAsync_produceTerseOutput=True.verified.txt b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiParameterReferenceTests.SerializeParameterReferenceAsV2JsonWorksAsync_produceTerseOutput=True.verified.txt new file mode 100644 index 000000000..8d3cb1803 --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiParameterReferenceTests.SerializeParameterReferenceAsV2JsonWorksAsync_produceTerseOutput=True.verified.txt @@ -0,0 +1 @@ +{"in":"query","name":"limit","description":"Results to return","type":"integer","maximum":100,"minimum":1} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiParameterReferenceTests.SerializeParameterReferenceAsV31JsonWorks_produceTerseOutput=False.verified.txt b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiParameterReferenceTests.SerializeParameterReferenceAsV31JsonWorks_produceTerseOutput=False.verified.txt new file mode 100644 index 000000000..237298009 --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiParameterReferenceTests.SerializeParameterReferenceAsV31JsonWorks_produceTerseOutput=False.verified.txt @@ -0,0 +1,11 @@ +{ + "name": "limit", + "in": "query", + "description": "Results to return", + "style": "form", + "schema": { + "maximum": 100, + "minimum": 1, + "type": "integer" + } +} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiParameterReferenceTests.SerializeParameterReferenceAsV31JsonWorks_produceTerseOutput=True.verified.txt b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiParameterReferenceTests.SerializeParameterReferenceAsV31JsonWorks_produceTerseOutput=True.verified.txt new file mode 100644 index 000000000..e8eac1b64 --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiParameterReferenceTests.SerializeParameterReferenceAsV31JsonWorks_produceTerseOutput=True.verified.txt @@ -0,0 +1 @@ +{"name":"limit","in":"query","description":"Results to return","style":"form","schema":{"maximum":100,"minimum":1,"type":"integer"}} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiParameterReferenceTests.SerializeParameterReferenceAsV3JsonWorks_produceTerseOutput=False.verified.txt b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiParameterReferenceTests.SerializeParameterReferenceAsV3JsonWorks_produceTerseOutput=False.verified.txt new file mode 100644 index 000000000..237298009 --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiParameterReferenceTests.SerializeParameterReferenceAsV3JsonWorks_produceTerseOutput=False.verified.txt @@ -0,0 +1,11 @@ +{ + "name": "limit", + "in": "query", + "description": "Results to return", + "style": "form", + "schema": { + "maximum": 100, + "minimum": 1, + "type": "integer" + } +} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiParameterReferenceTests.SerializeParameterReferenceAsV3JsonWorks_produceTerseOutput=True.verified.txt b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiParameterReferenceTests.SerializeParameterReferenceAsV3JsonWorks_produceTerseOutput=True.verified.txt new file mode 100644 index 000000000..e8eac1b64 --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiParameterReferenceTests.SerializeParameterReferenceAsV3JsonWorks_produceTerseOutput=True.verified.txt @@ -0,0 +1 @@ +{"name":"limit","in":"query","description":"Results to return","style":"form","schema":{"maximum":100,"minimum":1,"type":"integer"}} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiParameterReferenceTests.SerializeReferencedParameterAsV2JsonWorksAsync_produceTerseOutput=False.verified.txt b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiParameterReferenceTests.SerializeReferencedParameterAsV2JsonWorksAsync_produceTerseOutput=False.verified.txt new file mode 100644 index 000000000..2a64ba6d9 --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiParameterReferenceTests.SerializeReferencedParameterAsV2JsonWorksAsync_produceTerseOutput=False.verified.txt @@ -0,0 +1,8 @@ +{ + "in": "query", + "name": "limit", + "description": "Results to return", + "type": "integer", + "maximum": 100, + "minimum": 1 +} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiParameterReferenceTests.SerializeReferencedParameterAsV2JsonWorksAsync_produceTerseOutput=True.verified.txt b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiParameterReferenceTests.SerializeReferencedParameterAsV2JsonWorksAsync_produceTerseOutput=True.verified.txt new file mode 100644 index 000000000..8d3cb1803 --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiParameterReferenceTests.SerializeReferencedParameterAsV2JsonWorksAsync_produceTerseOutput=True.verified.txt @@ -0,0 +1 @@ +{"in":"query","name":"limit","description":"Results to return","type":"integer","maximum":100,"minimum":1} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiParameterReferenceTests.SerializeReferencedParameterAsV3JsonWorks_produceTerseOutput=False.verified.txt b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiParameterReferenceTests.SerializeReferencedParameterAsV3JsonWorks_produceTerseOutput=False.verified.txt new file mode 100644 index 000000000..cd30a5fc2 --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiParameterReferenceTests.SerializeReferencedParameterAsV3JsonWorks_produceTerseOutput=False.verified.txt @@ -0,0 +1,10 @@ +{ + "name": "limit", + "in": "query", + "description": "Results to return", + "schema": { + "maximum": 100, + "minimum": 1, + "type": "integer" + } +} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiParameterReferenceTests.SerializeReferencedParameterAsV3JsonWorks_produceTerseOutput=True.verified.txt b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiParameterReferenceTests.SerializeReferencedParameterAsV3JsonWorks_produceTerseOutput=True.verified.txt new file mode 100644 index 000000000..da4f04c14 --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiParameterReferenceTests.SerializeReferencedParameterAsV3JsonWorks_produceTerseOutput=True.verified.txt @@ -0,0 +1 @@ +{"name":"limit","in":"query","description":"Results to return","schema":{"maximum":100,"minimum":1,"type":"integer"}} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiParameterReferenceTests.cs b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiParameterReferenceTests.cs new file mode 100644 index 000000000..c00db94f5 --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiParameterReferenceTests.cs @@ -0,0 +1,165 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System.Globalization; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Models.References; +using Microsoft.OpenApi.Reader; +using Microsoft.OpenApi.Readers; +using Microsoft.OpenApi.Writers; +using Microsoft.OpenApi.Services; +using VerifyXunit; +using Xunit; + +namespace Microsoft.OpenApi.Tests.Models.References +{ + [Collection("DefaultSettings")] + public class OpenApiParameterReferenceTests + { + // OpenApi doc with external $ref + private const string OpenApi = @" +openapi: 3.0.0 +info: + title: Sample API + version: 1.0.0 +servers: + - url: https://myserver.com/v1.0 +paths: + /users: + get: + summary: Get users + parameters: + - $ref: 'https://myserver.com/beta#/components/parameters/limitParam' + responses: + 200: + description: Successful operation +components: + schemas: + User: + type: object + properties: + id: + type: integer + name: + type: string +"; + + // OpenApi doc with local $ref + private const string OpenApi_2 = @" +openapi: 3.0.0 +info: + title: Sample API + version: 1.0.0 +servers: + - url: https://myserver.com/beta +paths: + /users: + get: + summary: Get users + parameters: + - $ref: '#/components/parameters/limitParam' + responses: + 200: + description: Successful operation +components: + parameters: + limitParam: + name: limit + in: query + description: Number of results to return + schema: + type: integer + minimum: 1 + maximum: 100 +"; + private readonly OpenApiParameterReference _localParameterReference; + private readonly OpenApiParameterReference _externalParameterReference; + private readonly OpenApiDocument _openApiDoc; + private readonly OpenApiDocument _openApiDoc_2; + + 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.Workspace.AddDocumentId("https://myserver.com/beta", _openApiDoc_2.BaseUri); + _openApiDoc.Workspace.RegisterComponents(_openApiDoc_2); + + _localParameterReference = new("limitParam", _openApiDoc_2) + { + Description = "Results to return" + }; + + _externalParameterReference = new OpenApiParameterReference("limitParam", _openApiDoc, "https://myserver.com/beta") + { + Description = "Externally referenced: Results to return" + }; + } + + [Fact] + public void ParameterReferenceResolutionWorks() + { + // Assert + Assert.Equal("limit", _localParameterReference.Name); + Assert.Equal("Results to return", _localParameterReference.Description); + Assert.Equal("limit", _externalParameterReference.Name); + Assert.Equal("Externally referenced: Results to return", _externalParameterReference.Description); + Assert.Equal("Number of results to return", + _openApiDoc_2.Components.Parameters.First().Value.Description); // The main description value shouldn't change + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task SerializeParameterReferenceAsV3JsonWorks(bool produceTerseOutput) + { + // Arrange + var outputStringWriter = new StringWriter(CultureInfo.InvariantCulture); + var writer = new OpenApiJsonWriter(outputStringWriter, new OpenApiJsonWriterSettings { Terse = produceTerseOutput, InlineLocalReferences = true }); + + // Act + _localParameterReference.SerializeAsV3(writer); + writer.Flush(); + + // Assert + await Verifier.Verify(outputStringWriter).UseParameters(produceTerseOutput); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task SerializeParameterReferenceAsV31JsonWorks(bool produceTerseOutput) + { + // Arrange + var outputStringWriter = new StringWriter(CultureInfo.InvariantCulture); + var writer = new OpenApiJsonWriter(outputStringWriter, new OpenApiJsonWriterSettings { Terse = produceTerseOutput, InlineLocalReferences = true }); + + // Act + _localParameterReference.SerializeAsV31(writer); + writer.Flush(); + + // Assert + await Verifier.Verify(outputStringWriter).UseParameters(produceTerseOutput); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task SerializeParameterReferenceAsV2JsonWorksAsync(bool produceTerseOutput) + { + // Arrange + var outputStringWriter = new StringWriter(CultureInfo.InvariantCulture); + var writer = new OpenApiJsonWriter(outputStringWriter, new OpenApiJsonWriterSettings { Terse = produceTerseOutput , InlineLocalReferences = true }); + + // Act + _localParameterReference.SerializeAsV2(writer); + writer.Flush(); + + // Assert + await Verifier.Verify(outputStringWriter).UseParameters(produceTerseOutput); + } + } +} diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiPathItemReferenceTests.SerializeCallbackReferenceAsV31JsonWorks_produceTerseOutput=False.verified.txt b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiPathItemReferenceTests.SerializeCallbackReferenceAsV31JsonWorks_produceTerseOutput=False.verified.txt new file mode 100644 index 000000000..844f5ee81 --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiPathItemReferenceTests.SerializeCallbackReferenceAsV31JsonWorks_produceTerseOutput=False.verified.txt @@ -0,0 +1,28 @@ +{ + "summary": "User path item summary", + "description": "User path item description", + "get": { + "summary": "Get users", + "responses": { + "200": { + "description": "Successful operation" + } + } + }, + "post": { + "summary": "Create a user", + "responses": { + "201": { + "description": "User created successfully" + } + } + }, + "delete": { + "summary": "Delete a user", + "responses": { + "204": { + "description": "User deleted successfully" + } + } + } +} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiPathItemReferenceTests.SerializeCallbackReferenceAsV31JsonWorks_produceTerseOutput=True.verified.txt b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiPathItemReferenceTests.SerializeCallbackReferenceAsV31JsonWorks_produceTerseOutput=True.verified.txt new file mode 100644 index 000000000..f43044ef8 --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiPathItemReferenceTests.SerializeCallbackReferenceAsV31JsonWorks_produceTerseOutput=True.verified.txt @@ -0,0 +1 @@ +{"summary":"User path item summary","description":"User path item description","get":{"summary":"Get users","responses":{"200":{"description":"Successful operation"}}},"post":{"summary":"Create a user","responses":{"201":{"description":"User created successfully"}}},"delete":{"summary":"Delete a user","responses":{"204":{"description":"User deleted successfully"}}}} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiPathItemReferenceTests.SerializeCallbackReferenceAsV3JsonWorks_produceTerseOutput=False.verified.txt b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiPathItemReferenceTests.SerializeCallbackReferenceAsV3JsonWorks_produceTerseOutput=False.verified.txt new file mode 100644 index 000000000..844f5ee81 --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiPathItemReferenceTests.SerializeCallbackReferenceAsV3JsonWorks_produceTerseOutput=False.verified.txt @@ -0,0 +1,28 @@ +{ + "summary": "User path item summary", + "description": "User path item description", + "get": { + "summary": "Get users", + "responses": { + "200": { + "description": "Successful operation" + } + } + }, + "post": { + "summary": "Create a user", + "responses": { + "201": { + "description": "User created successfully" + } + } + }, + "delete": { + "summary": "Delete a user", + "responses": { + "204": { + "description": "User deleted successfully" + } + } + } +} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiPathItemReferenceTests.SerializeCallbackReferenceAsV3JsonWorks_produceTerseOutput=True.verified.txt b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiPathItemReferenceTests.SerializeCallbackReferenceAsV3JsonWorks_produceTerseOutput=True.verified.txt new file mode 100644 index 000000000..f43044ef8 --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiPathItemReferenceTests.SerializeCallbackReferenceAsV3JsonWorks_produceTerseOutput=True.verified.txt @@ -0,0 +1 @@ +{"summary":"User path item summary","description":"User path item description","get":{"summary":"Get users","responses":{"200":{"description":"Successful operation"}}},"post":{"summary":"Create a user","responses":{"201":{"description":"User created successfully"}}},"delete":{"summary":"Delete a user","responses":{"204":{"description":"User deleted successfully"}}}} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiPathItemReferenceTests.SerializeParameterReferenceAsV2JsonWorksAsync_produceTerseOutput=False.verified.txt b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiPathItemReferenceTests.SerializeParameterReferenceAsV2JsonWorksAsync_produceTerseOutput=False.verified.txt new file mode 100644 index 000000000..86685c051 --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiPathItemReferenceTests.SerializeParameterReferenceAsV2JsonWorksAsync_produceTerseOutput=False.verified.txt @@ -0,0 +1,28 @@ +{ + "get": { + "summary": "Get users", + "responses": { + "200": { + "description": "Successful operation" + } + } + }, + "post": { + "summary": "Create a user", + "responses": { + "201": { + "description": "User created successfully" + } + } + }, + "delete": { + "summary": "Delete a user", + "responses": { + "204": { + "description": "User deleted successfully" + } + } + }, + "x-summary": "Local reference: User path item summary", + "x-description": "Local reference: User path item description" +} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiPathItemReferenceTests.SerializeParameterReferenceAsV2JsonWorksAsync_produceTerseOutput=True.verified.txt b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiPathItemReferenceTests.SerializeParameterReferenceAsV2JsonWorksAsync_produceTerseOutput=True.verified.txt new file mode 100644 index 000000000..efa477cae --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiPathItemReferenceTests.SerializeParameterReferenceAsV2JsonWorksAsync_produceTerseOutput=True.verified.txt @@ -0,0 +1 @@ +{"get":{"summary":"Get users","responses":{"200":{"description":"Successful operation"}}},"post":{"summary":"Create a user","responses":{"201":{"description":"User created successfully"}}},"delete":{"summary":"Delete a user","responses":{"204":{"description":"User deleted successfully"}}},"x-summary":"Local reference: User path item summary","x-description":"Local reference: User path item description"} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiPathItemReferenceTests.SerializePathItemReferenceAsV2JsonWorksAsync_produceTerseOutput=False.verified.txt b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiPathItemReferenceTests.SerializePathItemReferenceAsV2JsonWorksAsync_produceTerseOutput=False.verified.txt new file mode 100644 index 000000000..86685c051 --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiPathItemReferenceTests.SerializePathItemReferenceAsV2JsonWorksAsync_produceTerseOutput=False.verified.txt @@ -0,0 +1,28 @@ +{ + "get": { + "summary": "Get users", + "responses": { + "200": { + "description": "Successful operation" + } + } + }, + "post": { + "summary": "Create a user", + "responses": { + "201": { + "description": "User created successfully" + } + } + }, + "delete": { + "summary": "Delete a user", + "responses": { + "204": { + "description": "User deleted successfully" + } + } + }, + "x-summary": "Local reference: User path item summary", + "x-description": "Local reference: User path item description" +} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiPathItemReferenceTests.SerializePathItemReferenceAsV2JsonWorksAsync_produceTerseOutput=True.verified.txt b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiPathItemReferenceTests.SerializePathItemReferenceAsV2JsonWorksAsync_produceTerseOutput=True.verified.txt new file mode 100644 index 000000000..efa477cae --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiPathItemReferenceTests.SerializePathItemReferenceAsV2JsonWorksAsync_produceTerseOutput=True.verified.txt @@ -0,0 +1 @@ +{"get":{"summary":"Get users","responses":{"200":{"description":"Successful operation"}}},"post":{"summary":"Create a user","responses":{"201":{"description":"User created successfully"}}},"delete":{"summary":"Delete a user","responses":{"204":{"description":"User deleted successfully"}}},"x-summary":"Local reference: User path item summary","x-description":"Local reference: User path item description"} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiPathItemReferenceTests.SerializePathItemReferenceAsV31JsonWorks_produceTerseOutput=False.verified.txt b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiPathItemReferenceTests.SerializePathItemReferenceAsV31JsonWorks_produceTerseOutput=False.verified.txt new file mode 100644 index 000000000..4aa3a9451 --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiPathItemReferenceTests.SerializePathItemReferenceAsV31JsonWorks_produceTerseOutput=False.verified.txt @@ -0,0 +1,28 @@ +{ + "summary": "Local reference: User path item summary", + "description": "Local reference: User path item description", + "get": { + "summary": "Get users", + "responses": { + "200": { + "description": "Successful operation" + } + } + }, + "post": { + "summary": "Create a user", + "responses": { + "201": { + "description": "User created successfully" + } + } + }, + "delete": { + "summary": "Delete a user", + "responses": { + "204": { + "description": "User deleted successfully" + } + } + } +} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiPathItemReferenceTests.SerializePathItemReferenceAsV31JsonWorks_produceTerseOutput=True.verified.txt b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiPathItemReferenceTests.SerializePathItemReferenceAsV31JsonWorks_produceTerseOutput=True.verified.txt new file mode 100644 index 000000000..1b04eaa44 --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiPathItemReferenceTests.SerializePathItemReferenceAsV31JsonWorks_produceTerseOutput=True.verified.txt @@ -0,0 +1 @@ +{"summary":"Local reference: User path item summary","description":"Local reference: User path item description","get":{"summary":"Get users","responses":{"200":{"description":"Successful operation"}}},"post":{"summary":"Create a user","responses":{"201":{"description":"User created successfully"}}},"delete":{"summary":"Delete a user","responses":{"204":{"description":"User deleted successfully"}}}} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiPathItemReferenceTests.SerializePathItemReferenceAsV3JsonWorks_produceTerseOutput=False.verified.txt b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiPathItemReferenceTests.SerializePathItemReferenceAsV3JsonWorks_produceTerseOutput=False.verified.txt new file mode 100644 index 000000000..844f5ee81 --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiPathItemReferenceTests.SerializePathItemReferenceAsV3JsonWorks_produceTerseOutput=False.verified.txt @@ -0,0 +1,28 @@ +{ + "summary": "User path item summary", + "description": "User path item description", + "get": { + "summary": "Get users", + "responses": { + "200": { + "description": "Successful operation" + } + } + }, + "post": { + "summary": "Create a user", + "responses": { + "201": { + "description": "User created successfully" + } + } + }, + "delete": { + "summary": "Delete a user", + "responses": { + "204": { + "description": "User deleted successfully" + } + } + } +} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiPathItemReferenceTests.SerializePathItemReferenceAsV3JsonWorks_produceTerseOutput=True.verified.txt b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiPathItemReferenceTests.SerializePathItemReferenceAsV3JsonWorks_produceTerseOutput=True.verified.txt new file mode 100644 index 000000000..f43044ef8 --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiPathItemReferenceTests.SerializePathItemReferenceAsV3JsonWorks_produceTerseOutput=True.verified.txt @@ -0,0 +1 @@ +{"summary":"User path item summary","description":"User path item description","get":{"summary":"Get users","responses":{"200":{"description":"Successful operation"}}},"post":{"summary":"Create a user","responses":{"201":{"description":"User created successfully"}}},"delete":{"summary":"Delete a user","responses":{"204":{"description":"User deleted successfully"}}}} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiPathItemReferenceTests.cs b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiPathItemReferenceTests.cs new file mode 100644 index 000000000..2d7354f78 --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiPathItemReferenceTests.cs @@ -0,0 +1,139 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System.Globalization; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Models.References; +using Microsoft.OpenApi.Reader; +using Microsoft.OpenApi.Readers; +using Microsoft.OpenApi.Writers; +using Microsoft.OpenApi.Services; +using VerifyXunit; +using Xunit; + +namespace Microsoft.OpenApi.Tests.Models.References +{ + [Collection("DefaultSettings")] + public class OpenApiPathItemReferenceTests + { + private const string OpenApi = @" +openapi: 3.1.0 +info: + title: Sample API + version: 1.0.0 +servers: + - url: https://myserver.com/v1.0 +paths: + /users: + $ref: 'https://myserver.com/beta#/components/pathItems/userPathItem' +components: + schemas: + User: + type: object + properties: + id: + type: integer + name: + type: string +"; + + private const string OpenApi_2 = @" +openapi: 3.1.0 +info: + title: Sample API + version: 1.0.0 +servers: + - url: https://myserver.com/beta +paths: + /users: + $ref: '#/components/pathItems/userPathItem' +components: + pathItems: + userPathItem: + description: User path item description + summary: User path item summary + get: + summary: Get users + responses: + 200: + description: Successful operation + post: + summary: Create a user + responses: + 201: + description: User created successfully + delete: + summary: Delete a user + responses: + 204: + description: User deleted successfully +"; + + private readonly OpenApiPathItemReference _localPathItemReference; + private readonly OpenApiPathItemReference _externalPathItemReference; + private readonly OpenApiDocument _openApiDoc; + private readonly OpenApiDocument _openApiDoc_2; + + 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.Workspace.AddDocumentId("https://myserver.com/beta", _openApiDoc_2.BaseUri); + _openApiDoc.Workspace.RegisterComponents(_openApiDoc_2); + _openApiDoc_2.Workspace.RegisterComponents(_openApiDoc_2); + + _localPathItemReference = new OpenApiPathItemReference("userPathItem", _openApiDoc_2) + { + Description = "Local reference: User path item description", + Summary = "Local reference: User path item summary" + }; + + _externalPathItemReference = new OpenApiPathItemReference("userPathItem", _openApiDoc, "https://myserver.com/beta") + { + Description = "External reference: User path item description", + Summary = "External reference: User path item summary" + }; + } + + [Fact] + public void PathItemReferenceResolutionWorks() + { + // Assert + Assert.Equal([OperationType.Get, OperationType.Post, OperationType.Delete], + _localPathItemReference.Operations.Select(o => o.Key)); + Assert.Equal(3, _localPathItemReference.Operations.Count); + Assert.Equal("Local reference: User path item description", _localPathItemReference.Description); + Assert.Equal("Local reference: User path item summary", _localPathItemReference.Summary); + + Assert.Equal([OperationType.Get, OperationType.Post, OperationType.Delete], + _externalPathItemReference.Operations.Select(o => o.Key)); + Assert.Equal("External reference: User path item description", _externalPathItemReference.Description); + Assert.Equal("External reference: User path item summary", _externalPathItemReference.Summary); + + // The main description and summary values shouldn't change + Assert.Equal("User path item description", _openApiDoc_2.Components.PathItems.First().Value.Description); + Assert.Equal("User path item summary", _openApiDoc_2.Components.PathItems.First().Value.Summary); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task SerializePathItemReferenceAsV31JsonWorks(bool produceTerseOutput) + { + // Arrange + var outputStringWriter = new StringWriter(CultureInfo.InvariantCulture); + var writer = new OpenApiJsonWriter(outputStringWriter, new OpenApiJsonWriterSettings { Terse = produceTerseOutput, InlineLocalReferences = true }); + + // Act + _localPathItemReference.SerializeAsV31(writer); + writer.Flush(); + + // Assert + await Verifier.Verify(outputStringWriter).UseParameters(produceTerseOutput); + } + } +} diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiRequestBodyReferenceTests.SerializeRequestBodyReferenceAsV31JsonWorks_produceTerseOutput=False.verified.txt b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiRequestBodyReferenceTests.SerializeRequestBodyReferenceAsV31JsonWorks_produceTerseOutput=False.verified.txt new file mode 100644 index 000000000..a9be81418 --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiRequestBodyReferenceTests.SerializeRequestBodyReferenceAsV31JsonWorks_produceTerseOutput=False.verified.txt @@ -0,0 +1,3 @@ +{ + "$ref": "#/components/requestBodies/UserRequest" +} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiRequestBodyReferenceTests.SerializeRequestBodyReferenceAsV31JsonWorks_produceTerseOutput=True.verified.txt b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiRequestBodyReferenceTests.SerializeRequestBodyReferenceAsV31JsonWorks_produceTerseOutput=True.verified.txt new file mode 100644 index 000000000..04f67afdd --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiRequestBodyReferenceTests.SerializeRequestBodyReferenceAsV31JsonWorks_produceTerseOutput=True.verified.txt @@ -0,0 +1 @@ +{"$ref":"#/components/requestBodies/UserRequest"} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiRequestBodyReferenceTests.SerializeRequestBodyReferenceAsV3JsonWorks_produceTerseOutput=False.verified.txt b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiRequestBodyReferenceTests.SerializeRequestBodyReferenceAsV3JsonWorks_produceTerseOutput=False.verified.txt new file mode 100644 index 000000000..a9be81418 --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiRequestBodyReferenceTests.SerializeRequestBodyReferenceAsV3JsonWorks_produceTerseOutput=False.verified.txt @@ -0,0 +1,3 @@ +{ + "$ref": "#/components/requestBodies/UserRequest" +} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiRequestBodyReferenceTests.SerializeRequestBodyReferenceAsV3JsonWorks_produceTerseOutput=True.verified.txt b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiRequestBodyReferenceTests.SerializeRequestBodyReferenceAsV3JsonWorks_produceTerseOutput=True.verified.txt new file mode 100644 index 000000000..04f67afdd --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiRequestBodyReferenceTests.SerializeRequestBodyReferenceAsV3JsonWorks_produceTerseOutput=True.verified.txt @@ -0,0 +1 @@ +{"$ref":"#/components/requestBodies/UserRequest"} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiRequestBodyReferenceTests.cs b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiRequestBodyReferenceTests.cs new file mode 100644 index 000000000..54521e83c --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiRequestBodyReferenceTests.cs @@ -0,0 +1,159 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System.Globalization; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Models.References; +using Microsoft.OpenApi.Reader; +using Microsoft.OpenApi.Readers; +using Microsoft.OpenApi.Writers; +using Microsoft.OpenApi.Services; +using VerifyXunit; +using Xunit; + +namespace Microsoft.OpenApi.Tests.Models.References +{ + [Collection("DefaultSettings")] + public class OpenApiRequestBodyReferenceTests + { + private readonly string OpenApi = @" +openapi: 3.0.0 +info: + title: Sample API + version: 1.0.0 +servers: + - url: https://myserver.com/v1.0 +paths: + /users: + post: + summary: Create a user + requestBody: + $ref: 'https://myserver.com/beta#/components/requestBodies/UserRequest' # <---- externally referencing the requestBody here + responses: + '201': + description: User created +components: + schemas: + User: + type: object + properties: + id: + type: integer + name: + type: string +"; + + private readonly string OpenApi_2 = @" +openapi: 3.0.0 +info: + title: Sample API + version: 1.0.0 +servers: + - url: https://myserver.com/beta +paths: + /users: + post: + summary: Create a user + requestBody: + $ref: '#/components/requestBodies/UserRequest' # <---- referencing the requestBody here + responses: + '201': + description: User created +components: + requestBodies: + UserRequest: + description: User creation request body + content: + application/json: + schema: + $ref: '#/components/schemas/UserSchema' + schemas: + UserSchema: + type: object + properties: + name: + type: string + email: + type: string +"; + + private readonly OpenApiRequestBodyReference _localRequestBodyReference; + private readonly OpenApiRequestBodyReference _externalRequestBodyReference; + private readonly OpenApiDocument _openApiDoc; + private readonly OpenApiDocument _openApiDoc_2; + + 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.Workspace.AddDocumentId("https://myserver.com/beta", _openApiDoc_2.BaseUri); + _openApiDoc.Workspace.RegisterComponents(_openApiDoc_2); + + _localRequestBodyReference = new("UserRequest", _openApiDoc_2) + { + Description = "User request body" + }; + + _externalRequestBodyReference = new("UserRequest", _openApiDoc, "https://myserver.com/beta") + { + Description = "External Reference: User request body" + }; + } + + [Fact] + public void RequestBodyReferenceResolutionWorks() + { + // Assert + var localContent = _localRequestBodyReference.Content.Values.FirstOrDefault(); + Assert.NotNull(localContent); + Assert.Equal("UserSchema", localContent.Schema.Reference.Id); + Assert.Equal("User request body", _localRequestBodyReference.Description); + Assert.Equal("application/json", _localRequestBodyReference.Content.First().Key); + + var externalContent = _externalRequestBodyReference.Content.Values.FirstOrDefault(); + Assert.NotNull(externalContent); + Assert.Equal("UserSchema", externalContent.Schema.Reference.Id); + + Assert.Equal("External Reference: User request body", _externalRequestBodyReference.Description); + Assert.Equal("User creation request body", _openApiDoc_2.Components.RequestBodies.First().Value.Description); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task SerializeRequestBodyReferenceAsV3JsonWorks(bool produceTerseOutput) + { + // Arrange + var outputStringWriter = new StringWriter(CultureInfo.InvariantCulture); + var writer = new OpenApiJsonWriter(outputStringWriter, new OpenApiJsonWriterSettings { Terse = produceTerseOutput}); + + // Act + _localRequestBodyReference.SerializeAsV3(writer); + writer.Flush(); + + // Assert + await Verifier.Verify(outputStringWriter).UseParameters(produceTerseOutput); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task SerializeRequestBodyReferenceAsV31JsonWorks(bool produceTerseOutput) + { + // Arrange + var outputStringWriter = new StringWriter(CultureInfo.InvariantCulture); + var writer = new OpenApiJsonWriter(outputStringWriter, new OpenApiJsonWriterSettings { Terse = produceTerseOutput }); + + // Act + _localRequestBodyReference.SerializeAsV31(writer); + writer.Flush(); + + // Assert + await Verifier.Verify(outputStringWriter).UseParameters(produceTerseOutput); + } + } +} diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiResponseReferenceTest.SerializeResponseReferenceAsV31JsonWorks_produceTerseOutput=False.verified.txt b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiResponseReferenceTest.SerializeResponseReferenceAsV31JsonWorks_produceTerseOutput=False.verified.txt new file mode 100644 index 000000000..3b61b5a39 --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiResponseReferenceTest.SerializeResponseReferenceAsV31JsonWorks_produceTerseOutput=False.verified.txt @@ -0,0 +1,3 @@ +{ + "$ref": "#/components/responses/OkResponse" +} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiResponseReferenceTest.SerializeResponseReferenceAsV31JsonWorks_produceTerseOutput=True.verified.txt b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiResponseReferenceTest.SerializeResponseReferenceAsV31JsonWorks_produceTerseOutput=True.verified.txt new file mode 100644 index 000000000..d4776f5df --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiResponseReferenceTest.SerializeResponseReferenceAsV31JsonWorks_produceTerseOutput=True.verified.txt @@ -0,0 +1 @@ +{"$ref":"#/components/responses/OkResponse"} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiResponseReferenceTest.SerializeResponseReferenceAsV3JsonWorks_produceTerseOutput=False.verified.txt b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiResponseReferenceTest.SerializeResponseReferenceAsV3JsonWorks_produceTerseOutput=False.verified.txt new file mode 100644 index 000000000..3b61b5a39 --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiResponseReferenceTest.SerializeResponseReferenceAsV3JsonWorks_produceTerseOutput=False.verified.txt @@ -0,0 +1,3 @@ +{ + "$ref": "#/components/responses/OkResponse" +} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiResponseReferenceTest.SerializeResponseReferenceAsV3JsonWorks_produceTerseOutput=True.verified.txt b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiResponseReferenceTest.SerializeResponseReferenceAsV3JsonWorks_produceTerseOutput=True.verified.txt new file mode 100644 index 000000000..d4776f5df --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiResponseReferenceTest.SerializeResponseReferenceAsV3JsonWorks_produceTerseOutput=True.verified.txt @@ -0,0 +1 @@ +{"$ref":"#/components/responses/OkResponse"} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiResponseReferenceTest.cs b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiResponseReferenceTest.cs new file mode 100644 index 000000000..4b6b25564 --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiResponseReferenceTest.cs @@ -0,0 +1,141 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System.Globalization; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Models.References; +using Microsoft.OpenApi.Reader; +using Microsoft.OpenApi.Readers; +using Microsoft.OpenApi.Writers; +using Microsoft.OpenApi.Services; +using VerifyXunit; +using Xunit; + +namespace Microsoft.OpenApi.Tests.Models.References +{ + [Collection("DefaultSettings")] + public class OpenApiResponseReferenceTest + { + private const string OpenApi = @" +openapi: 3.0.0 +info: + title: Sample API + version: 1.0.0 +servers: + - url: https://myserver.com/v1.0 +paths: + /ping: + get: + responses: + '200': + $ref: 'https://myserver.com/beta#/components/responses/OkResponse' +"; + + private const string OpenApi_2 = @" +openapi: 3.0.0 +info: + title: Sample API + version: 1.0.0 +servers: + - url: https://myserver.com/beta +paths: + /ping: + get: + responses: + '200': + $ref: '#/components/responses/OkResponse' +components: + responses: + OkResponse: + description: OK + content: + text/plain: + schema: + $ref: '#/components/schemas/Pong' + schemas: + Pong: + type: object + properties: + sound: + type: string +"; + + private readonly OpenApiResponseReference _localResponseReference; + private readonly OpenApiResponseReference _externalResponseReference; + private readonly OpenApiDocument _openApiDoc; + private readonly OpenApiDocument _openApiDoc_2; + + 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.Workspace.AddDocumentId("https://myserver.com/beta", _openApiDoc_2.BaseUri); + _openApiDoc.Workspace.RegisterComponents(_openApiDoc_2); + + _localResponseReference = new("OkResponse", _openApiDoc_2) + { + Description = "OK response" + }; + + _externalResponseReference = new("OkResponse", _openApiDoc, "https://myserver.com/beta") + { + Description = "External reference: OK response" + }; + } + + [Fact] + public void ResponseReferenceResolutionWorks() + { + // Assert + var localContent = _localResponseReference.Content.FirstOrDefault(); + Assert.Equal("text/plain", localContent.Key); + Assert.Equal("Pong", localContent.Value.Schema.Reference.Id); + Assert.Equal("OK response", _localResponseReference.Description); + + var externalContent = _externalResponseReference.Content.FirstOrDefault(); + Assert.Equal("text/plain", externalContent.Key); + Assert.Equal("Pong", externalContent.Value.Schema.Reference.Id); + Assert.Equal("External reference: OK response", _externalResponseReference.Description); + + Assert.Equal("OK", _openApiDoc_2.Components.Responses.First().Value.Description); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task SerializeResponseReferenceAsV3JsonWorks(bool produceTerseOutput) + { + // Arrange + var outputStringWriter = new StringWriter(CultureInfo.InvariantCulture); + var writer = new OpenApiJsonWriter(outputStringWriter, new OpenApiJsonWriterSettings { Terse = produceTerseOutput}); + + // Act + _localResponseReference.SerializeAsV3(writer); + writer.Flush(); + + // Assert + await Verifier.Verify(outputStringWriter).UseParameters(produceTerseOutput); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task SerializeResponseReferenceAsV31JsonWorks(bool produceTerseOutput) + { + // Arrange + var outputStringWriter = new StringWriter(CultureInfo.InvariantCulture); + var writer = new OpenApiJsonWriter(outputStringWriter, new OpenApiJsonWriterSettings { Terse = produceTerseOutput}); + + // Act + _localResponseReference.SerializeAsV31(writer); + writer.Flush(); + + // Assert + await Verifier.Verify(outputStringWriter).UseParameters(produceTerseOutput); + } + } +} diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiSecuritySchemeReferenceTests.SerializeSecuritySchemeReferenceAsV31JsonWorks_produceTerseOutput=False.verified.txt b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiSecuritySchemeReferenceTests.SerializeSecuritySchemeReferenceAsV31JsonWorks_produceTerseOutput=False.verified.txt new file mode 100644 index 000000000..073ce3d7b --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiSecuritySchemeReferenceTests.SerializeSecuritySchemeReferenceAsV31JsonWorks_produceTerseOutput=False.verified.txt @@ -0,0 +1,5 @@ +{ + "type": "apiKey", + "name": "X-API-Key", + "in": "header" +} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiSecuritySchemeReferenceTests.SerializeSecuritySchemeReferenceAsV31JsonWorks_produceTerseOutput=True.verified.txt b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiSecuritySchemeReferenceTests.SerializeSecuritySchemeReferenceAsV31JsonWorks_produceTerseOutput=True.verified.txt new file mode 100644 index 000000000..6d0080a96 --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiSecuritySchemeReferenceTests.SerializeSecuritySchemeReferenceAsV31JsonWorks_produceTerseOutput=True.verified.txt @@ -0,0 +1 @@ +{"type":"apiKey","name":"X-API-Key","in":"header"} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiSecuritySchemeReferenceTests.SerializeSecuritySchemeReferenceAsV3JsonWorks_produceTerseOutput=False.verified.txt b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiSecuritySchemeReferenceTests.SerializeSecuritySchemeReferenceAsV3JsonWorks_produceTerseOutput=False.verified.txt new file mode 100644 index 000000000..073ce3d7b --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiSecuritySchemeReferenceTests.SerializeSecuritySchemeReferenceAsV3JsonWorks_produceTerseOutput=False.verified.txt @@ -0,0 +1,5 @@ +{ + "type": "apiKey", + "name": "X-API-Key", + "in": "header" +} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiSecuritySchemeReferenceTests.SerializeSecuritySchemeReferenceAsV3JsonWorks_produceTerseOutput=True.verified.txt b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiSecuritySchemeReferenceTests.SerializeSecuritySchemeReferenceAsV3JsonWorks_produceTerseOutput=True.verified.txt new file mode 100644 index 000000000..6d0080a96 --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiSecuritySchemeReferenceTests.SerializeSecuritySchemeReferenceAsV3JsonWorks_produceTerseOutput=True.verified.txt @@ -0,0 +1 @@ +{"type":"apiKey","name":"X-API-Key","in":"header"} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiSecuritySchemeReferenceTests.cs b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiSecuritySchemeReferenceTests.cs new file mode 100644 index 000000000..7fcd7dfd8 --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiSecuritySchemeReferenceTests.cs @@ -0,0 +1,93 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System.Globalization; +using System.IO; +using System.Threading.Tasks; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Models.References; +using Microsoft.OpenApi.Reader; +using Microsoft.OpenApi.Readers; +using Microsoft.OpenApi.Writers; +using VerifyXunit; +using Xunit; + +namespace Microsoft.OpenApi.Tests.Models.References +{ + [Collection("DefaultSettings")] + public class OpenApiSecuritySchemeReferenceTests + { + private const string OpenApi = @" +openapi: 3.0.3 +info: + title: Sample API + version: 1.0.0 +servers: + - url: https://myserver.com/v1.0 +paths: + /users: + get: + summary: Retrieve users + security: + - mySecurityScheme: [] + +components: + securitySchemes: + mySecurityScheme: + type: apiKey + name: X-API-Key + in: header +"; + + readonly OpenApiSecuritySchemeReference _openApiSecuritySchemeReference; + + public OpenApiSecuritySchemeReferenceTests() + { + OpenApiReaderRegistry.RegisterReader(OpenApiConstants.Yaml, new OpenApiYamlReader()); + var result = OpenApiDocument.Parse(OpenApi, "yaml"); + _openApiSecuritySchemeReference = new("mySecurityScheme", result.OpenApiDocument); + } + + [Fact] + public void SecuritySchemeResolutionWorks() + { + // Assert + Assert.Equal("X-API-Key", _openApiSecuritySchemeReference.Name); + Assert.Equal(SecuritySchemeType.ApiKey, _openApiSecuritySchemeReference.Type); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task SerializeSecuritySchemeReferenceAsV3JsonWorks(bool produceTerseOutput) + { + // Arrange + var outputStringWriter = new StringWriter(CultureInfo.InvariantCulture); + var writer = new OpenApiJsonWriter(outputStringWriter, new OpenApiJsonWriterSettings { Terse = produceTerseOutput, InlineLocalReferences = true }); + + // Act + _openApiSecuritySchemeReference.SerializeAsV3(writer); + writer.Flush(); + + // Assert + await Verifier.Verify(outputStringWriter).UseParameters(produceTerseOutput); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task SerializeSecuritySchemeReferenceAsV31JsonWorks(bool produceTerseOutput) + { + // Arrange + var outputStringWriter = new StringWriter(CultureInfo.InvariantCulture); + var writer = new OpenApiJsonWriter(outputStringWriter, new OpenApiJsonWriterSettings { Terse = produceTerseOutput, InlineLocalReferences = true }); + + // Act + _openApiSecuritySchemeReference.SerializeAsV31(writer); + writer.Flush(); + + // Assert + await Verifier.Verify(outputStringWriter).UseParameters(produceTerseOutput); + } + } +} diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiTagReferenceTest.SerializeSecuritySchemeReferenceAsV31JsonWorks_produceTerseOutput=False.verified.txt b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiTagReferenceTest.SerializeSecuritySchemeReferenceAsV31JsonWorks_produceTerseOutput=False.verified.txt new file mode 100644 index 000000000..5f282702b --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiTagReferenceTest.SerializeSecuritySchemeReferenceAsV31JsonWorks_produceTerseOutput=False.verified.txt @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiTagReferenceTest.SerializeSecuritySchemeReferenceAsV31JsonWorks_produceTerseOutput=True.verified.txt b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiTagReferenceTest.SerializeSecuritySchemeReferenceAsV31JsonWorks_produceTerseOutput=True.verified.txt new file mode 100644 index 000000000..5f282702b --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiTagReferenceTest.SerializeSecuritySchemeReferenceAsV31JsonWorks_produceTerseOutput=True.verified.txt @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiTagReferenceTest.SerializeSecuritySchemeReferenceAsV3JsonWorks_produceTerseOutput=False.verified.txt b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiTagReferenceTest.SerializeSecuritySchemeReferenceAsV3JsonWorks_produceTerseOutput=False.verified.txt new file mode 100644 index 000000000..dd019c493 --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiTagReferenceTest.SerializeSecuritySchemeReferenceAsV3JsonWorks_produceTerseOutput=False.verified.txt @@ -0,0 +1 @@ +"user" \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiTagReferenceTest.SerializeSecuritySchemeReferenceAsV3JsonWorks_produceTerseOutput=True.verified.txt b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiTagReferenceTest.SerializeSecuritySchemeReferenceAsV3JsonWorks_produceTerseOutput=True.verified.txt new file mode 100644 index 000000000..5f282702b --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiTagReferenceTest.SerializeSecuritySchemeReferenceAsV3JsonWorks_produceTerseOutput=True.verified.txt @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiTagReferenceTest.SerializeTagReferenceAsV31JsonWorks_produceTerseOutput=False.verified.txt b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiTagReferenceTest.SerializeTagReferenceAsV31JsonWorks_produceTerseOutput=False.verified.txt new file mode 100644 index 000000000..dd019c493 --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiTagReferenceTest.SerializeTagReferenceAsV31JsonWorks_produceTerseOutput=False.verified.txt @@ -0,0 +1 @@ +"user" \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiTagReferenceTest.SerializeTagReferenceAsV31JsonWorks_produceTerseOutput=True.verified.txt b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiTagReferenceTest.SerializeTagReferenceAsV31JsonWorks_produceTerseOutput=True.verified.txt new file mode 100644 index 000000000..dd019c493 --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiTagReferenceTest.SerializeTagReferenceAsV31JsonWorks_produceTerseOutput=True.verified.txt @@ -0,0 +1 @@ +"user" \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiTagReferenceTest.SerializeTagReferenceAsV3JsonWorks_produceTerseOutput=False.verified.txt b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiTagReferenceTest.SerializeTagReferenceAsV3JsonWorks_produceTerseOutput=False.verified.txt new file mode 100644 index 000000000..dd019c493 --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiTagReferenceTest.SerializeTagReferenceAsV3JsonWorks_produceTerseOutput=False.verified.txt @@ -0,0 +1 @@ +"user" \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiTagReferenceTest.SerializeTagReferenceAsV3JsonWorks_produceTerseOutput=True.verified.txt b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiTagReferenceTest.SerializeTagReferenceAsV3JsonWorks_produceTerseOutput=True.verified.txt new file mode 100644 index 000000000..dd019c493 --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiTagReferenceTest.SerializeTagReferenceAsV3JsonWorks_produceTerseOutput=True.verified.txt @@ -0,0 +1 @@ +"user" \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiTagReferenceTest.cs b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiTagReferenceTest.cs new file mode 100644 index 000000000..82f1b27a2 --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiTagReferenceTest.cs @@ -0,0 +1,115 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System.Globalization; +using System.IO; +using System.Threading.Tasks; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Models.References; +using Microsoft.OpenApi.Reader; +using Microsoft.OpenApi.Readers; +using Microsoft.OpenApi.Writers; +using VerifyXunit; +using Xunit; + +namespace Microsoft.OpenApi.Tests.Models.References +{ + [Collection("DefaultSettings")] + public class OpenApiTagReferenceTest + { + private const string OpenApi = @"openapi: 3.0.3 +info: + title: Sample API + version: 1.0.0 + +paths: + /users/{userId}: + get: + summary: Returns a user by ID. + parameters: + - name: userId + in: path + required: true + description: The ID of the user to return. + schema: + type: integer + responses: + '200': + description: A user object. + content: + application/json: + schema: + $ref: '#/components/schemas/User' + '404': + description: The user was not found. + tags: + - $ref: '#/tags/user' +components: + schemas: + User: + type: object + properties: + id: + type: integer + name: + type: string +tags: + - name: user + description: Operations about users. +"; + + readonly OpenApiTagReference _openApiTagReference; + + public OpenApiTagReferenceTest() + { + OpenApiReaderRegistry.RegisterReader(OpenApiConstants.Yaml, new OpenApiYamlReader()); + var result = OpenApiDocument.Parse(OpenApi, "yaml"); + _openApiTagReference = new("user", result.OpenApiDocument) + { + Description = "Users operations" + }; + } + + [Fact] + public void TagReferenceResolutionWorks() + { + // Assert + Assert.Equal("user", _openApiTagReference.Name); + Assert.Equal("Users operations", _openApiTagReference.Description); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task SerializeTagReferenceAsV3JsonWorks(bool produceTerseOutput) + { + // Arrange + var outputStringWriter = new StringWriter(CultureInfo.InvariantCulture); + var writer = new OpenApiJsonWriter(outputStringWriter, new OpenApiJsonWriterSettings { Terse = produceTerseOutput }); + + // Act + _openApiTagReference.SerializeAsV3(writer); + writer.Flush(); + + // Assert + await Verifier.Verify(outputStringWriter).UseParameters(produceTerseOutput); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task SerializeTagReferenceAsV31JsonWorks(bool produceTerseOutput) + { + // Arrange + var outputStringWriter = new StringWriter(CultureInfo.InvariantCulture); + var writer = new OpenApiJsonWriter(outputStringWriter, new OpenApiJsonWriterSettings { Terse = produceTerseOutput }); + + // Act + _openApiTagReference.SerializeAsV31(writer); + writer.Flush(); + + // Assert + await Verifier.Verify(outputStringWriter).UseParameters(produceTerseOutput); + } + } +} diff --git a/test/Microsoft.OpenApi.Tests/Models/Samples/docWithDollarId.yaml b/test/Microsoft.OpenApi.Tests/Models/Samples/docWithDollarId.yaml new file mode 100644 index 000000000..e8916f895 --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/Models/Samples/docWithDollarId.yaml @@ -0,0 +1,39 @@ +openapi: 3.1.0 +info: + title: Simple API + version: 1.0.0 +paths: + /box: + get: + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: https://foo.bar/Box + /circle: + get: + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: https://foo.bar/Circle +components: + schemas: + Box: + $id: https://foo.bar/Box + type: object + properties: + width: + type: number + height: + type: number + Circle: + $id: https://foo.bar/Circle + type: object + properties: + radius: + type: number diff --git a/test/Microsoft.OpenApi.Tests/Models/Samples/docWithReusableWebhooks.yaml b/test/Microsoft.OpenApi.Tests/Models/Samples/docWithReusableWebhooks.yaml new file mode 100644 index 000000000..6d3af550e --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/Models/Samples/docWithReusableWebhooks.yaml @@ -0,0 +1,26 @@ +openapi : 3.1.0 +info: + title: Webhook Example + version: 1.0.0 +jsonSchemaDialect: "http://json-schema.org/draft-07/schema#" +webhooks: + pets: + $ref: '#/components/pathItems/pets' +components: + schemas: + petSchema: + type: object + pathItems: + pets: + get: + description: Returns all pets from the system that the user has access to + operationId: findPets + responses: + '200': + description: pet response + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/petSchema' \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt b/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt old mode 100755 new mode 100644 index 9483e5f6e..ef18b4cfb --- a/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt +++ b/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt @@ -2,135 +2,18 @@ [assembly: System.Runtime.CompilerServices.InternalsVisibleTo(@"Microsoft.OpenApi.Readers.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100957cb48387b2a5f54f5ce39255f18f26d32a39990db27cf48737afc6bc62759ba996b8a2bfb675d4e39f3d06ecb55a178b1b4031dcb2a767e29977d88cce864a0d16bfc1b3bebb0edf9fe285f10fffc0a85f93d664fa05af07faa3aad2e545182dbf787e3fd32b56aca95df1a3c4e75dec164a3f1a4c653d971b01ffc39eb3c4")] [assembly: System.Runtime.CompilerServices.InternalsVisibleTo(@"Microsoft.OpenApi.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100957cb48387b2a5f54f5ce39255f18f26d32a39990db27cf48737afc6bc62759ba996b8a2bfb675d4e39f3d06ecb55a178b1b4031dcb2a767e29977d88cce864a0d16bfc1b3bebb0edf9fe285f10fffc0a85f93d664fa05af07faa3aad2e545182dbf787e3fd32b56aca95df1a3c4e75dec164a3f1a4c653d971b01ffc39eb3c4")] [assembly: System.Runtime.Versioning.TargetFramework(".NETStandard,Version=v2.0", FrameworkDisplayName=".NET Standard 2.0")] +public static class IDiagnosticExtensions +{ + public static void AddRange(this System.Collections.Generic.ICollection collection, System.Collections.Generic.IEnumerable enumerable) { } +} namespace Microsoft.OpenApi.Any { - public enum AnyType - { - Primitive = 0, - Null = 1, - Array = 2, - Object = 3, - } - public interface IOpenApiAny : Microsoft.OpenApi.Interfaces.IOpenApiElement, Microsoft.OpenApi.Interfaces.IOpenApiExtension - { - Microsoft.OpenApi.Any.AnyType AnyType { get; } - } - public interface IOpenApiPrimitive : Microsoft.OpenApi.Any.IOpenApiAny, Microsoft.OpenApi.Interfaces.IOpenApiElement, Microsoft.OpenApi.Interfaces.IOpenApiExtension - { - Microsoft.OpenApi.Any.PrimitiveType PrimitiveType { get; } - } - public class OpenApiAnyCloneHelper + public class OpenApiAny : Microsoft.OpenApi.Interfaces.IOpenApiElement, Microsoft.OpenApi.Interfaces.IOpenApiExtension { - public OpenApiAnyCloneHelper() { } - [System.Obsolete("Use native AoT-friendly generic overload of CloneFromCopyConstructor instead.")] - public static Microsoft.OpenApi.Any.IOpenApiAny CloneFromCopyConstructor(Microsoft.OpenApi.Any.IOpenApiAny obj) { } - public static T CloneFromCopyConstructor(T obj) - where T : Microsoft.OpenApi.Any.IOpenApiAny { } - } - public class OpenApiArray : System.Collections.Generic.List, Microsoft.OpenApi.Any.IOpenApiAny, Microsoft.OpenApi.Interfaces.IOpenApiElement, Microsoft.OpenApi.Interfaces.IOpenApiExtension - { - public OpenApiArray() { } - public OpenApiArray(Microsoft.OpenApi.Any.OpenApiArray array) { } - public Microsoft.OpenApi.Any.AnyType AnyType { get; } + public OpenApiAny(System.Text.Json.Nodes.JsonNode jsonNode) { } + public System.Text.Json.Nodes.JsonNode Node { get; } public void Write(Microsoft.OpenApi.Writers.IOpenApiWriter writer, Microsoft.OpenApi.OpenApiSpecVersion specVersion) { } } - public class OpenApiBinary : Microsoft.OpenApi.Any.OpenApiPrimitive - { - public OpenApiBinary(byte[] value) { } - public override Microsoft.OpenApi.Any.PrimitiveType PrimitiveType { get; } - } - public class OpenApiBoolean : Microsoft.OpenApi.Any.OpenApiPrimitive - { - public OpenApiBoolean(bool value) { } - public override Microsoft.OpenApi.Any.PrimitiveType PrimitiveType { get; } - } - public class OpenApiByte : Microsoft.OpenApi.Any.OpenApiPrimitive - { - public OpenApiByte(byte value) { } - public OpenApiByte(byte[] value) { } - public override Microsoft.OpenApi.Any.PrimitiveType PrimitiveType { get; } - } - public class OpenApiDate : Microsoft.OpenApi.Any.OpenApiPrimitive - { - public OpenApiDate(System.DateTime value) { } - public override Microsoft.OpenApi.Any.PrimitiveType PrimitiveType { get; } - } - public class OpenApiDateTime : Microsoft.OpenApi.Any.OpenApiPrimitive - { - public OpenApiDateTime(System.DateTimeOffset value) { } - public override Microsoft.OpenApi.Any.PrimitiveType PrimitiveType { get; } - } - public class OpenApiDouble : Microsoft.OpenApi.Any.OpenApiPrimitive - { - public OpenApiDouble(double value) { } - public override Microsoft.OpenApi.Any.PrimitiveType PrimitiveType { get; } - } - public class OpenApiFloat : Microsoft.OpenApi.Any.OpenApiPrimitive - { - public OpenApiFloat(float value) { } - public override Microsoft.OpenApi.Any.PrimitiveType PrimitiveType { get; } - } - public class OpenApiInteger : Microsoft.OpenApi.Any.OpenApiPrimitive - { - public OpenApiInteger(int value) { } - public override Microsoft.OpenApi.Any.PrimitiveType PrimitiveType { get; } - } - public class OpenApiLong : Microsoft.OpenApi.Any.OpenApiPrimitive - { - public OpenApiLong(long value) { } - public override Microsoft.OpenApi.Any.PrimitiveType PrimitiveType { get; } - } - public class OpenApiNull : Microsoft.OpenApi.Any.IOpenApiAny, Microsoft.OpenApi.Interfaces.IOpenApiElement, Microsoft.OpenApi.Interfaces.IOpenApiExtension - { - public OpenApiNull() { } - public OpenApiNull(Microsoft.OpenApi.Any.OpenApiNull openApiNull) { } - public Microsoft.OpenApi.Any.AnyType AnyType { get; } - public void Write(Microsoft.OpenApi.Writers.IOpenApiWriter writer, Microsoft.OpenApi.OpenApiSpecVersion specVersion) { } - } - public class OpenApiObject : System.Collections.Generic.Dictionary, Microsoft.OpenApi.Any.IOpenApiAny, Microsoft.OpenApi.Interfaces.IOpenApiElement, Microsoft.OpenApi.Interfaces.IOpenApiExtension - { - public OpenApiObject() { } - public OpenApiObject(Microsoft.OpenApi.Any.OpenApiObject obj) { } - public Microsoft.OpenApi.Any.AnyType AnyType { get; } - public void Write(Microsoft.OpenApi.Writers.IOpenApiWriter writer, Microsoft.OpenApi.OpenApiSpecVersion specVersion) { } - } - public class OpenApiPassword : Microsoft.OpenApi.Any.OpenApiPrimitive - { - public OpenApiPassword(string value) { } - public override Microsoft.OpenApi.Any.PrimitiveType PrimitiveType { get; } - } - public abstract class OpenApiPrimitive : Microsoft.OpenApi.Any.IOpenApiAny, Microsoft.OpenApi.Any.IOpenApiPrimitive, Microsoft.OpenApi.Interfaces.IOpenApiElement, Microsoft.OpenApi.Interfaces.IOpenApiExtension - { - public OpenApiPrimitive(Microsoft.OpenApi.Any.OpenApiPrimitive openApiPrimitive) { } - public OpenApiPrimitive(T value) { } - public Microsoft.OpenApi.Any.AnyType AnyType { get; } - public abstract Microsoft.OpenApi.Any.PrimitiveType PrimitiveType { get; } - public T Value { get; } - public void Write(Microsoft.OpenApi.Writers.IOpenApiWriter writer, Microsoft.OpenApi.OpenApiSpecVersion specVersion) { } - } - public class OpenApiString : Microsoft.OpenApi.Any.OpenApiPrimitive - { - public OpenApiString(string value) { } - public OpenApiString(string value, bool isExplicit) { } - public OpenApiString(string value, bool isExplicit, bool isRawString) { } - public override Microsoft.OpenApi.Any.PrimitiveType PrimitiveType { get; } - public bool IsExplicit() { } - public bool IsRawString() { } - } - public enum PrimitiveType - { - Integer = 0, - Long = 1, - Float = 2, - Double = 3, - String = 4, - Byte = 5, - Binary = 6, - Boolean = 7, - Date = 8, - DateTime = 9, - Password = 10, - } } namespace Microsoft.OpenApi.Attributes { @@ -150,6 +33,22 @@ namespace Microsoft.OpenApi.Exceptions public OpenApiException(string message, System.Exception innerException) { } public string Pointer { get; set; } } + [System.Serializable] + public class OpenApiReaderException : Microsoft.OpenApi.Exceptions.OpenApiException + { + public OpenApiReaderException() { } + public OpenApiReaderException(string message) { } + public OpenApiReaderException(string message, Microsoft.OpenApi.Reader.ParsingContext context) { } + public OpenApiReaderException(string message, System.Exception innerException) { } + public OpenApiReaderException(string message, System.Text.Json.Nodes.JsonNode node) { } + } + [System.Serializable] + public class OpenApiUnsupportedSpecVersionException : System.Exception + { + public OpenApiUnsupportedSpecVersionException(string specificationVersion) { } + public OpenApiUnsupportedSpecVersionException(string specificationVersion, System.Exception innerException) { } + public string SpecificationVersion { get; } + } public class OpenApiWriterException : Microsoft.OpenApi.Exceptions.OpenApiException { public OpenApiWriterException() { } @@ -293,6 +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 Microsoft.OpenApi.Models.JsonSchemaType ToJsonSchemaType(this string identifier) { } } public static class StringExtensions { @@ -301,11 +202,7 @@ namespace Microsoft.OpenApi.Extensions } namespace Microsoft.OpenApi.Interfaces { - public interface IEffective - where T : class, Microsoft.OpenApi.Interfaces.IOpenApiElement - { - T GetEffective(Microsoft.OpenApi.Models.OpenApiDocument document); - } + public interface IDiagnostic { } public interface IOpenApiAnnotatable { System.Collections.Generic.IDictionary Annotations { get; set; } @@ -319,17 +216,31 @@ namespace Microsoft.OpenApi.Interfaces { void Write(Microsoft.OpenApi.Writers.IOpenApiWriter writer, Microsoft.OpenApi.OpenApiSpecVersion specVersion); } + public interface IOpenApiReader + { + System.Threading.Tasks.Task ReadAsync(System.IO.TextReader input, Microsoft.OpenApi.Reader.OpenApiReaderSettings settings = null, System.Threading.CancellationToken cancellationToken = default); + System.Threading.Tasks.Task ReadAsync(System.Text.Json.Nodes.JsonNode jsonNode, Microsoft.OpenApi.Reader.OpenApiReaderSettings settings, string format = null, System.Threading.CancellationToken cancellationToken = default); + T ReadFragment(System.IO.TextReader input, Microsoft.OpenApi.OpenApiSpecVersion version, out Microsoft.OpenApi.Reader.OpenApiDiagnostic diagnostic, Microsoft.OpenApi.Reader.OpenApiReaderSettings settings = null) + where T : Microsoft.OpenApi.Interfaces.IOpenApiElement; + T ReadFragment(System.Text.Json.Nodes.JsonNode input, Microsoft.OpenApi.OpenApiSpecVersion version, out Microsoft.OpenApi.Reader.OpenApiDiagnostic diagnostic, Microsoft.OpenApi.Reader.OpenApiReaderSettings settings = null) + where T : Microsoft.OpenApi.Interfaces.IOpenApiElement; + } public interface IOpenApiReferenceable : Microsoft.OpenApi.Interfaces.IOpenApiElement, Microsoft.OpenApi.Interfaces.IOpenApiSerializable { Microsoft.OpenApi.Models.OpenApiReference Reference { get; set; } bool UnresolvedReference { get; set; } - void SerializeAsV2WithoutReference(Microsoft.OpenApi.Writers.IOpenApiWriter writer); - void SerializeAsV3WithoutReference(Microsoft.OpenApi.Writers.IOpenApiWriter writer); } public interface IOpenApiSerializable : Microsoft.OpenApi.Interfaces.IOpenApiElement { void SerializeAsV2(Microsoft.OpenApi.Writers.IOpenApiWriter writer); void SerializeAsV3(Microsoft.OpenApi.Writers.IOpenApiWriter writer); + void SerializeAsV31(Microsoft.OpenApi.Writers.IOpenApiWriter writer); + } + public interface IStreamLoader + { + [System.Obsolete("Use the Async overload")] + System.IO.Stream Load(System.Uri uri); + System.Threading.Tasks.Task LoadAsync(System.Uri uri); } } namespace Microsoft.OpenApi @@ -350,6 +261,7 @@ namespace Microsoft.OpenApi { OpenApi2_0 = 0, OpenApi3_0 = 1, + OpenApi3_1 = 2, } } namespace Microsoft.OpenApi.MicrosoftExtensions @@ -357,7 +269,7 @@ namespace Microsoft.OpenApi.MicrosoftExtensions public class EnumDescription : Microsoft.OpenApi.Interfaces.IOpenApiElement { public EnumDescription() { } - public EnumDescription(Microsoft.OpenApi.Any.OpenApiObject source) { } + public EnumDescription(System.Text.Json.Nodes.JsonObject source) { } public string Description { get; set; } public string Name { get; set; } public string Value { get; set; } @@ -371,7 +283,7 @@ namespace Microsoft.OpenApi.MicrosoftExtensions public string Version { get; set; } public static string Name { get; } public void Write(Microsoft.OpenApi.Writers.IOpenApiWriter writer, Microsoft.OpenApi.OpenApiSpecVersion specVersion) { } - public static Microsoft.OpenApi.MicrosoftExtensions.OpenApiDeprecationExtension Parse(Microsoft.OpenApi.Any.IOpenApiAny source) { } + public static Microsoft.OpenApi.MicrosoftExtensions.OpenApiDeprecationExtension Parse(System.Text.Json.Nodes.JsonNode source) { } } public class OpenApiEnumFlagsExtension : Microsoft.OpenApi.Interfaces.IOpenApiExtension { @@ -379,7 +291,7 @@ namespace Microsoft.OpenApi.MicrosoftExtensions public bool IsFlags { get; set; } public static string Name { get; } public void Write(Microsoft.OpenApi.Writers.IOpenApiWriter writer, Microsoft.OpenApi.OpenApiSpecVersion specVersion) { } - public static Microsoft.OpenApi.MicrosoftExtensions.OpenApiEnumFlagsExtension Parse(Microsoft.OpenApi.Any.IOpenApiAny source) { } + public static Microsoft.OpenApi.MicrosoftExtensions.OpenApiEnumFlagsExtension Parse(System.Text.Json.Nodes.JsonNode source) { } } public class OpenApiEnumValuesDescriptionExtension : Microsoft.OpenApi.Interfaces.IOpenApiExtension { @@ -388,7 +300,7 @@ namespace Microsoft.OpenApi.MicrosoftExtensions public System.Collections.Generic.List ValuesDescriptions { get; set; } public static string Name { get; } public void Write(Microsoft.OpenApi.Writers.IOpenApiWriter writer, Microsoft.OpenApi.OpenApiSpecVersion specVersion) { } - public static Microsoft.OpenApi.MicrosoftExtensions.OpenApiEnumValuesDescriptionExtension Parse(Microsoft.OpenApi.Any.IOpenApiAny source) { } + public static Microsoft.OpenApi.MicrosoftExtensions.OpenApiEnumValuesDescriptionExtension Parse(System.Text.Json.Nodes.JsonNode source) { } } public class OpenApiPagingExtension : Microsoft.OpenApi.Interfaces.IOpenApiExtension { @@ -398,7 +310,7 @@ namespace Microsoft.OpenApi.MicrosoftExtensions public string OperationName { get; set; } public static string Name { get; } public void Write(Microsoft.OpenApi.Writers.IOpenApiWriter writer, Microsoft.OpenApi.OpenApiSpecVersion specVersion) { } - public static Microsoft.OpenApi.MicrosoftExtensions.OpenApiPagingExtension Parse(Microsoft.OpenApi.Any.IOpenApiAny source) { } + public static Microsoft.OpenApi.MicrosoftExtensions.OpenApiPagingExtension Parse(System.Text.Json.Nodes.JsonNode source) { } } public class OpenApiPrimaryErrorMessageExtension : Microsoft.OpenApi.Interfaces.IOpenApiExtension { @@ -406,7 +318,7 @@ namespace Microsoft.OpenApi.MicrosoftExtensions public bool IsPrimaryErrorMessage { get; set; } public static string Name { get; } public void Write(Microsoft.OpenApi.Writers.IOpenApiWriter writer, Microsoft.OpenApi.OpenApiSpecVersion specVersion) { } - public static Microsoft.OpenApi.MicrosoftExtensions.OpenApiPrimaryErrorMessageExtension Parse(Microsoft.OpenApi.Any.IOpenApiAny source) { } + public static Microsoft.OpenApi.MicrosoftExtensions.OpenApiPrimaryErrorMessageExtension Parse(System.Text.Json.Nodes.JsonNode source) { } } public class OpenApiReservedParameterExtension : Microsoft.OpenApi.Interfaces.IOpenApiExtension { @@ -414,42 +326,53 @@ namespace Microsoft.OpenApi.MicrosoftExtensions public bool? IsReserved { get; set; } public static string Name { get; } public void Write(Microsoft.OpenApi.Writers.IOpenApiWriter writer, Microsoft.OpenApi.OpenApiSpecVersion specVersion) { } - public static Microsoft.OpenApi.MicrosoftExtensions.OpenApiReservedParameterExtension Parse(Microsoft.OpenApi.Any.IOpenApiAny source) { } + public static Microsoft.OpenApi.MicrosoftExtensions.OpenApiReservedParameterExtension Parse(System.Text.Json.Nodes.JsonNode source) { } } } namespace Microsoft.OpenApi.Models { - public class OpenApiCallback : Microsoft.OpenApi.Interfaces.IEffective, Microsoft.OpenApi.Interfaces.IOpenApiElement, Microsoft.OpenApi.Interfaces.IOpenApiExtensible, Microsoft.OpenApi.Interfaces.IOpenApiReferenceable, Microsoft.OpenApi.Interfaces.IOpenApiSerializable + [System.Flags] + public enum JsonSchemaType + { + Null = 1, + Boolean = 2, + Integer = 4, + Number = 8, + String = 16, + Object = 32, + Array = 64, + } + public class OpenApiCallback : Microsoft.OpenApi.Interfaces.IOpenApiElement, Microsoft.OpenApi.Interfaces.IOpenApiExtensible, Microsoft.OpenApi.Interfaces.IOpenApiReferenceable, Microsoft.OpenApi.Interfaces.IOpenApiSerializable { public OpenApiCallback() { } public OpenApiCallback(Microsoft.OpenApi.Models.OpenApiCallback callback) { } - public System.Collections.Generic.IDictionary Extensions { get; set; } - public System.Collections.Generic.Dictionary PathItems { get; set; } public Microsoft.OpenApi.Models.OpenApiReference Reference { get; set; } - public bool UnresolvedReference { get; set; } + public virtual System.Collections.Generic.IDictionary Extensions { get; set; } + public virtual System.Collections.Generic.Dictionary PathItems { get; set; } + public virtual bool UnresolvedReference { get; set; } public void AddPathItem(Microsoft.OpenApi.Expressions.RuntimeExpression expression, Microsoft.OpenApi.Models.OpenApiPathItem pathItem) { } - public Microsoft.OpenApi.Models.OpenApiCallback GetEffective(Microsoft.OpenApi.Models.OpenApiDocument doc) { } public void SerializeAsV2(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } - public void SerializeAsV2WithoutReference(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } - public void SerializeAsV3(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } - public void SerializeAsV3WithoutReference(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } + public virtual void SerializeAsV3(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } + public virtual void SerializeAsV31(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } } public class OpenApiComponents : Microsoft.OpenApi.Interfaces.IOpenApiElement, Microsoft.OpenApi.Interfaces.IOpenApiExtensible, Microsoft.OpenApi.Interfaces.IOpenApiSerializable { public OpenApiComponents() { } - public OpenApiComponents(Microsoft.OpenApi.Models.OpenApiComponents components) { } - public System.Collections.Generic.IDictionary Callbacks { get; set; } - public System.Collections.Generic.IDictionary Examples { get; set; } - public System.Collections.Generic.IDictionary Extensions { get; set; } - public System.Collections.Generic.IDictionary Headers { get; set; } - public System.Collections.Generic.IDictionary Links { get; set; } - public System.Collections.Generic.IDictionary Parameters { get; set; } - public System.Collections.Generic.IDictionary RequestBodies { get; set; } - public System.Collections.Generic.IDictionary Responses { get; set; } - public System.Collections.Generic.IDictionary Schemas { get; set; } - public System.Collections.Generic.IDictionary SecuritySchemes { get; set; } + public OpenApiComponents(Microsoft.OpenApi.Models.OpenApiComponents? components) { } + public System.Collections.Generic.IDictionary? Schemas { get; set; } + public virtual System.Collections.Generic.IDictionary? Callbacks { get; set; } + public virtual System.Collections.Generic.IDictionary? Examples { get; set; } + public virtual System.Collections.Generic.IDictionary? Extensions { get; set; } + public virtual System.Collections.Generic.IDictionary? Headers { get; set; } + public virtual System.Collections.Generic.IDictionary? Links { get; set; } + public virtual System.Collections.Generic.IDictionary? Parameters { get; set; } + public virtual System.Collections.Generic.IDictionary? PathItems { get; set; } + public virtual System.Collections.Generic.IDictionary? RequestBodies { get; set; } + public virtual System.Collections.Generic.IDictionary? Responses { get; set; } + public virtual System.Collections.Generic.IDictionary? SecuritySchemes { get; set; } public void SerializeAsV2(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } public void SerializeAsV3(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } + public void SerializeAsV31(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } } public static class OpenApiConstants { @@ -464,13 +387,16 @@ namespace Microsoft.OpenApi.Models public const string AuthorizationCode = "authorizationCode"; public const string AuthorizationUrl = "authorizationUrl"; public const string BasePath = "basePath"; + public const string BaseRegistryUri = "https://openapi.net/"; public const string Basic = "basic"; public const string Bearer = "bearer"; public const string BearerFormat = "bearerFormat"; public const string BodyName = "x-bodyName"; public const string Callbacks = "callbacks"; public const string ClientCredentials = "clientCredentials"; + public const string Comment = "$comment"; public const string Components = "components"; + public const string ComponentsSegment = "/components/"; public const string Consumes = "consumes"; public const string Contact = "contact"; public const string Content = "content"; @@ -481,11 +407,15 @@ namespace Microsoft.OpenApi.Models public const string DefaultName = "Default Name"; public const string DefaultTitle = "Default Title"; public const string Definitions = "definitions"; + public const string Defs = "$defs"; public const string Delete = "delete"; public const string Deprecated = "deprecated"; public const string Description = "description"; public const string Discriminator = "discriminator"; public const string DollarRef = "$ref"; + public const string DollarSchema = "$schema"; + public const string DynamicAnchor = "$dynamicAnchor"; + public const string DynamicRef = "$dynamicRef"; public const string Email = "email"; public const string Encoding = "encoding"; public const string Enum = "enum"; @@ -505,10 +435,14 @@ namespace Microsoft.OpenApi.Models public const string Head = "head"; public const string Headers = "headers"; public const string Host = "host"; + public const string Id = "$id"; + public const string Identifier = "identifier"; public const string Implicit = "implicit"; public const string In = "in"; public const string Info = "info"; public const string Items = "items"; + public const string Json = "json"; + public const string JsonSchemaDialect = "jsonSchemaDialect"; public const string Jwt = "JWT"; public const string License = "license"; public const string Links = "links"; @@ -525,7 +459,9 @@ namespace Microsoft.OpenApi.Models public const string Name = "name"; public const string Namespace = "namespace"; public const string Not = "not"; + public const string Null = "null"; public const string Nullable = "nullable"; + public const string NullableExtension = "x-nullable"; public const string OneOf = "oneOf"; public const string OpenApi = "openapi"; public const string OpenIdConnectUrl = "openIdConnectUrl"; @@ -535,8 +471,10 @@ namespace Microsoft.OpenApi.Models public const string Parameters = "parameters"; public const string Password = "password"; public const string Patch = "patch"; + public const string PathItems = "pathItems"; public const string Paths = "paths"; public const string Pattern = "pattern"; + public const string PatternProperties = "patternProperties"; public const string Post = "post"; public const string Prefix = "prefix"; public const string Produces = "produces"; @@ -544,6 +482,8 @@ namespace Microsoft.OpenApi.Models public const string PropertyName = "propertyName"; public const string Put = "put"; public const string ReadOnly = "readOnly"; + public const string RecursiveAnchor = "$recursiveAnchor"; + public const string RecursiveRef = "$recursiveRef"; public const string RefreshUrl = "refreshUrl"; public const string RequestBodies = "requestBodies"; public const string RequestBody = "requestBody"; @@ -568,14 +508,23 @@ namespace Microsoft.OpenApi.Models public const string TokenUrl = "tokenUrl"; public const string Trace = "trace"; public const string Type = "type"; + public const string UnevaluatedProperties = "unevaluatedProperties"; public const string UniqueItems = "uniqueItems"; public const string Url = "url"; + public const string V2ReferenceUri = "https://registry/definitions/"; + public const string V31ExclusiveMaximum = "exclusiveMaximum"; + public const string V31ExclusiveMinimum = "exclusiveMinimum"; + public const string V3ReferenceUri = "https://registry/components/schemas/"; public const string Value = "value"; public const string Variables = "variables"; public const string Version = "version"; + public const string Vocabulary = "$vocabulary"; + public const string Webhooks = "webhooks"; public const string Wrapped = "wrapped"; public const string WriteOnly = "writeOnly"; public const string Xml = "xml"; + public const string Yaml = "yaml"; + public const string Yml = "yml"; public static readonly System.Uri defaultUrl; public static readonly System.Version version2_0; public static readonly System.Version version3_0_0; @@ -590,36 +539,49 @@ namespace Microsoft.OpenApi.Models public System.Uri Url { get; set; } public void SerializeAsV2(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } public void SerializeAsV3(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } + public void SerializeAsV31(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } } - public class OpenApiDiscriminator : Microsoft.OpenApi.Interfaces.IOpenApiElement, Microsoft.OpenApi.Interfaces.IOpenApiSerializable + public class OpenApiDiscriminator : Microsoft.OpenApi.Interfaces.IOpenApiElement, Microsoft.OpenApi.Interfaces.IOpenApiExtensible, Microsoft.OpenApi.Interfaces.IOpenApiSerializable { public OpenApiDiscriminator() { } public OpenApiDiscriminator(Microsoft.OpenApi.Models.OpenApiDiscriminator discriminator) { } + public System.Collections.Generic.IDictionary Extensions { get; set; } public System.Collections.Generic.IDictionary Mapping { get; set; } public string PropertyName { get; set; } public void SerializeAsV2(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } public void SerializeAsV3(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } + public void SerializeAsV31(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } } public class OpenApiDocument : Microsoft.OpenApi.Interfaces.IOpenApiAnnotatable, Microsoft.OpenApi.Interfaces.IOpenApiElement, Microsoft.OpenApi.Interfaces.IOpenApiExtensible, Microsoft.OpenApi.Interfaces.IOpenApiSerializable { public OpenApiDocument() { } - public OpenApiDocument(Microsoft.OpenApi.Models.OpenApiDocument document) { } - public System.Collections.Generic.IDictionary Annotations { get; set; } - public Microsoft.OpenApi.Models.OpenApiComponents Components { get; set; } - public System.Collections.Generic.IDictionary Extensions { get; set; } - public Microsoft.OpenApi.Models.OpenApiExternalDocs ExternalDocs { get; set; } + public OpenApiDocument(Microsoft.OpenApi.Models.OpenApiDocument? document) { } + public System.Collections.Generic.IDictionary? Annotations { get; set; } + public System.Uri BaseUri { get; } + public Microsoft.OpenApi.Models.OpenApiComponents? Components { get; set; } + public System.Collections.Generic.IDictionary? Extensions { get; set; } + public Microsoft.OpenApi.Models.OpenApiExternalDocs? ExternalDocs { get; set; } public string HashCode { get; } public Microsoft.OpenApi.Models.OpenApiInfo Info { get; set; } + public string? JsonSchemaDialect { get; set; } public Microsoft.OpenApi.Models.OpenApiPaths Paths { get; set; } - public System.Collections.Generic.IList SecurityRequirements { get; set; } - public System.Collections.Generic.IList Servers { get; set; } - public System.Collections.Generic.IList Tags { get; set; } - public Microsoft.OpenApi.Services.OpenApiWorkspace Workspace { get; set; } - public Microsoft.OpenApi.Interfaces.IOpenApiReferenceable ResolveReference(Microsoft.OpenApi.Models.OpenApiReference reference) { } - public System.Collections.Generic.IEnumerable ResolveReferences() { } + public System.Collections.Generic.IList? SecurityRequirements { get; set; } + public System.Collections.Generic.IList? Servers { get; set; } + public System.Collections.Generic.IList? Tags { get; set; } + public System.Collections.Generic.IDictionary? Webhooks { get; set; } + public Microsoft.OpenApi.Services.OpenApiWorkspace? Workspace { get; set; } public void SerializeAsV2(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } public void SerializeAsV3(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } + public void SerializeAsV31(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } + public void SetReferenceHostDocument() { } public static string GenerateHashValue(Microsoft.OpenApi.Models.OpenApiDocument doc) { } + public static Microsoft.OpenApi.Reader.ReadResult Load(string url, Microsoft.OpenApi.Reader.OpenApiReaderSettings? settings = null) { } + public static Microsoft.OpenApi.Reader.ReadResult Load(System.IO.Stream stream, string format, Microsoft.OpenApi.Reader.OpenApiReaderSettings? settings = null) { } + public static Microsoft.OpenApi.Reader.ReadResult Load(System.IO.TextReader input, string format, Microsoft.OpenApi.Reader.OpenApiReaderSettings? settings = null) { } + public static System.Threading.Tasks.Task LoadAsync(string url, Microsoft.OpenApi.Reader.OpenApiReaderSettings? settings = null) { } + public static System.Threading.Tasks.Task LoadAsync(System.IO.TextReader input, string format, Microsoft.OpenApi.Reader.OpenApiReaderSettings? settings = null) { } + public static System.Threading.Tasks.Task LoadAsync(System.IO.Stream stream, string format, Microsoft.OpenApi.Reader.OpenApiReaderSettings? settings = null, System.Threading.CancellationToken cancellationToken = default) { } + public static Microsoft.OpenApi.Reader.ReadResult Parse(string input, string? format = null, Microsoft.OpenApi.Reader.OpenApiReaderSettings? settings = null) { } } public class OpenApiEncoding : Microsoft.OpenApi.Interfaces.IOpenApiElement, Microsoft.OpenApi.Interfaces.IOpenApiExtensible, Microsoft.OpenApi.Interfaces.IOpenApiSerializable { @@ -633,6 +595,7 @@ namespace Microsoft.OpenApi.Models public Microsoft.OpenApi.Models.ParameterStyle? Style { get; set; } public void SerializeAsV2(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } public void SerializeAsV3(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } + public void SerializeAsV31(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } } public class OpenApiError { @@ -643,23 +606,21 @@ namespace Microsoft.OpenApi.Models public string Pointer { get; set; } public override string ToString() { } } - public class OpenApiExample : Microsoft.OpenApi.Interfaces.IEffective, Microsoft.OpenApi.Interfaces.IOpenApiElement, Microsoft.OpenApi.Interfaces.IOpenApiExtensible, Microsoft.OpenApi.Interfaces.IOpenApiReferenceable, Microsoft.OpenApi.Interfaces.IOpenApiSerializable + public class OpenApiExample : Microsoft.OpenApi.Interfaces.IOpenApiElement, Microsoft.OpenApi.Interfaces.IOpenApiExtensible, Microsoft.OpenApi.Interfaces.IOpenApiReferenceable, Microsoft.OpenApi.Interfaces.IOpenApiSerializable { public OpenApiExample() { } public OpenApiExample(Microsoft.OpenApi.Models.OpenApiExample example) { } - public string Description { get; set; } - public System.Collections.Generic.IDictionary Extensions { get; set; } - public string ExternalValue { get; set; } - public Microsoft.OpenApi.Models.OpenApiReference Reference { get; set; } - public string Summary { get; set; } - public bool UnresolvedReference { get; set; } - public Microsoft.OpenApi.Any.IOpenApiAny Value { get; set; } - public Microsoft.OpenApi.Models.OpenApiExample GetEffective(Microsoft.OpenApi.Models.OpenApiDocument doc) { } - public void Serialize(Microsoft.OpenApi.Writers.IOpenApiWriter writer, Microsoft.OpenApi.OpenApiSpecVersion version) { } - public void SerializeAsV2(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } - public void SerializeAsV2WithoutReference(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } - public void SerializeAsV3(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } - public void SerializeAsV3WithoutReference(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } + public virtual string Description { get; set; } + public virtual System.Collections.Generic.IDictionary Extensions { get; set; } + public virtual string ExternalValue { get; set; } + public virtual Microsoft.OpenApi.Models.OpenApiReference Reference { get; set; } + public virtual string Summary { get; set; } + public virtual bool UnresolvedReference { get; set; } + public virtual System.Text.Json.Nodes.JsonNode Value { get; set; } + public virtual void SerializeAsV2(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } + public virtual void SerializeAsV3(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } + public virtual void SerializeAsV31(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } + public void SerializeInternal(Microsoft.OpenApi.Writers.IOpenApiWriter writer, Microsoft.OpenApi.OpenApiSpecVersion version) { } } public abstract class OpenApiExtensibleDictionary : System.Collections.Generic.Dictionary, Microsoft.OpenApi.Interfaces.IOpenApiElement, Microsoft.OpenApi.Interfaces.IOpenApiExtensible, Microsoft.OpenApi.Interfaces.IOpenApiSerializable where T : Microsoft.OpenApi.Interfaces.IOpenApiSerializable @@ -669,6 +630,7 @@ namespace Microsoft.OpenApi.Models public System.Collections.Generic.IDictionary Extensions { get; set; } public void SerializeAsV2(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } public void SerializeAsV3(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } + public void SerializeAsV31(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } } public class OpenApiExternalDocs : Microsoft.OpenApi.Interfaces.IOpenApiElement, Microsoft.OpenApi.Interfaces.IOpenApiExtensible, Microsoft.OpenApi.Interfaces.IOpenApiSerializable { @@ -679,30 +641,29 @@ namespace Microsoft.OpenApi.Models public System.Uri Url { get; set; } public void SerializeAsV2(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } public void SerializeAsV3(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } + public void SerializeAsV31(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } } - public class OpenApiHeader : Microsoft.OpenApi.Interfaces.IEffective, Microsoft.OpenApi.Interfaces.IOpenApiElement, Microsoft.OpenApi.Interfaces.IOpenApiExtensible, Microsoft.OpenApi.Interfaces.IOpenApiReferenceable, Microsoft.OpenApi.Interfaces.IOpenApiSerializable + public class OpenApiHeader : Microsoft.OpenApi.Interfaces.IOpenApiElement, Microsoft.OpenApi.Interfaces.IOpenApiExtensible, Microsoft.OpenApi.Interfaces.IOpenApiReferenceable, Microsoft.OpenApi.Interfaces.IOpenApiSerializable { public OpenApiHeader() { } public OpenApiHeader(Microsoft.OpenApi.Models.OpenApiHeader header) { } - public bool AllowEmptyValue { get; set; } - public bool AllowReserved { get; set; } - public System.Collections.Generic.IDictionary Content { get; set; } - public bool Deprecated { get; set; } - public string Description { get; set; } - public Microsoft.OpenApi.Any.IOpenApiAny Example { get; set; } - public System.Collections.Generic.IDictionary Examples { get; set; } - public bool Explode { get; set; } - public System.Collections.Generic.IDictionary Extensions { get; set; } public Microsoft.OpenApi.Models.OpenApiReference Reference { get; set; } - public bool Required { get; set; } - public Microsoft.OpenApi.Models.OpenApiSchema Schema { get; set; } - public Microsoft.OpenApi.Models.ParameterStyle? Style { get; set; } - public bool UnresolvedReference { get; set; } - public Microsoft.OpenApi.Models.OpenApiHeader GetEffective(Microsoft.OpenApi.Models.OpenApiDocument doc) { } - public void SerializeAsV2(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } - public void SerializeAsV2WithoutReference(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } - public void SerializeAsV3(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } - public void SerializeAsV3WithoutReference(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } + public virtual bool AllowEmptyValue { get; set; } + public virtual bool AllowReserved { get; set; } + public virtual System.Collections.Generic.IDictionary Content { get; set; } + public virtual bool Deprecated { get; set; } + public virtual string Description { get; set; } + public virtual System.Text.Json.Nodes.JsonNode Example { get; set; } + public virtual System.Collections.Generic.IDictionary Examples { get; set; } + public virtual bool Explode { get; set; } + public virtual System.Collections.Generic.IDictionary Extensions { get; set; } + public virtual bool Required { get; set; } + public virtual Microsoft.OpenApi.Models.OpenApiSchema Schema { get; set; } + public virtual Microsoft.OpenApi.Models.ParameterStyle? Style { get; set; } + public virtual bool UnresolvedReference { get; set; } + public virtual void SerializeAsV2(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } + public virtual void SerializeAsV3(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } + public virtual void SerializeAsV31(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } } public class OpenApiInfo : Microsoft.OpenApi.Interfaces.IOpenApiElement, Microsoft.OpenApi.Interfaces.IOpenApiExtensible, Microsoft.OpenApi.Interfaces.IOpenApiSerializable { @@ -712,52 +673,55 @@ namespace Microsoft.OpenApi.Models public string Description { get; set; } public System.Collections.Generic.IDictionary Extensions { get; set; } public Microsoft.OpenApi.Models.OpenApiLicense License { get; set; } + public string Summary { get; set; } public System.Uri TermsOfService { get; set; } public string Title { get; set; } public string Version { get; set; } public void SerializeAsV2(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } public void SerializeAsV3(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } + public void SerializeAsV31(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } } public class OpenApiLicense : Microsoft.OpenApi.Interfaces.IOpenApiElement, Microsoft.OpenApi.Interfaces.IOpenApiExtensible, Microsoft.OpenApi.Interfaces.IOpenApiSerializable { public OpenApiLicense() { } public OpenApiLicense(Microsoft.OpenApi.Models.OpenApiLicense license) { } public System.Collections.Generic.IDictionary Extensions { get; set; } + public string Identifier { get; set; } public string Name { get; set; } public System.Uri Url { get; set; } public void SerializeAsV2(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } public void SerializeAsV3(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } + public void SerializeAsV31(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } } - public class OpenApiLink : Microsoft.OpenApi.Interfaces.IEffective, Microsoft.OpenApi.Interfaces.IOpenApiElement, Microsoft.OpenApi.Interfaces.IOpenApiExtensible, Microsoft.OpenApi.Interfaces.IOpenApiReferenceable, Microsoft.OpenApi.Interfaces.IOpenApiSerializable + public class OpenApiLink : Microsoft.OpenApi.Interfaces.IOpenApiElement, Microsoft.OpenApi.Interfaces.IOpenApiExtensible, Microsoft.OpenApi.Interfaces.IOpenApiReferenceable, Microsoft.OpenApi.Interfaces.IOpenApiSerializable { public OpenApiLink() { } public OpenApiLink(Microsoft.OpenApi.Models.OpenApiLink link) { } - public string Description { get; set; } - public System.Collections.Generic.IDictionary Extensions { get; set; } - public string OperationId { get; set; } - public string OperationRef { get; set; } - public System.Collections.Generic.Dictionary Parameters { get; set; } public Microsoft.OpenApi.Models.OpenApiReference Reference { get; set; } - public Microsoft.OpenApi.Models.RuntimeExpressionAnyWrapper RequestBody { get; set; } - public Microsoft.OpenApi.Models.OpenApiServer Server { get; set; } - public bool UnresolvedReference { get; set; } - public Microsoft.OpenApi.Models.OpenApiLink GetEffective(Microsoft.OpenApi.Models.OpenApiDocument doc) { } + public virtual string Description { get; set; } + public virtual System.Collections.Generic.IDictionary Extensions { get; set; } + public virtual string OperationId { get; set; } + public virtual string OperationRef { get; set; } + public virtual System.Collections.Generic.Dictionary Parameters { get; set; } + public virtual Microsoft.OpenApi.Models.RuntimeExpressionAnyWrapper RequestBody { get; set; } + public virtual Microsoft.OpenApi.Models.OpenApiServer Server { get; set; } + public virtual bool UnresolvedReference { get; set; } public void SerializeAsV2(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } - public void SerializeAsV2WithoutReference(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } - public void SerializeAsV3(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } - public void SerializeAsV3WithoutReference(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } + public virtual void SerializeAsV3(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } + public virtual void SerializeAsV31(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } } public class OpenApiMediaType : Microsoft.OpenApi.Interfaces.IOpenApiElement, Microsoft.OpenApi.Interfaces.IOpenApiExtensible, Microsoft.OpenApi.Interfaces.IOpenApiSerializable { public OpenApiMediaType() { } - public OpenApiMediaType(Microsoft.OpenApi.Models.OpenApiMediaType mediaType) { } - public System.Collections.Generic.IDictionary Encoding { get; set; } - public Microsoft.OpenApi.Any.IOpenApiAny Example { get; set; } - public System.Collections.Generic.IDictionary Examples { get; set; } - public System.Collections.Generic.IDictionary Extensions { get; set; } - public Microsoft.OpenApi.Models.OpenApiSchema Schema { get; set; } + public OpenApiMediaType(Microsoft.OpenApi.Models.OpenApiMediaType? mediaType) { } + public System.Collections.Generic.IDictionary? Encoding { get; set; } + public System.Text.Json.Nodes.JsonNode? Example { get; set; } + public System.Collections.Generic.IDictionary? Examples { get; set; } + public System.Collections.Generic.IDictionary? Extensions { get; set; } + public virtual Microsoft.OpenApi.Models.OpenApiSchema? Schema { get; set; } public void SerializeAsV2(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } public void SerializeAsV3(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } + public void SerializeAsV31(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } } public class OpenApiOAuthFlow : Microsoft.OpenApi.Interfaces.IOpenApiElement, Microsoft.OpenApi.Interfaces.IOpenApiExtensible, Microsoft.OpenApi.Interfaces.IOpenApiSerializable { @@ -770,6 +734,7 @@ namespace Microsoft.OpenApi.Models public System.Uri TokenUrl { get; set; } public void SerializeAsV2(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } public void SerializeAsV3(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } + public void SerializeAsV31(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } } public class OpenApiOAuthFlows : Microsoft.OpenApi.Interfaces.IOpenApiElement, Microsoft.OpenApi.Interfaces.IOpenApiExtensible, Microsoft.OpenApi.Interfaces.IOpenApiSerializable { @@ -782,74 +747,71 @@ namespace Microsoft.OpenApi.Models public Microsoft.OpenApi.Models.OpenApiOAuthFlow Password { get; set; } public void SerializeAsV2(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } public void SerializeAsV3(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } + public void SerializeAsV31(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } } public class OpenApiOperation : Microsoft.OpenApi.Interfaces.IOpenApiAnnotatable, Microsoft.OpenApi.Interfaces.IOpenApiElement, Microsoft.OpenApi.Interfaces.IOpenApiExtensible, Microsoft.OpenApi.Interfaces.IOpenApiSerializable { public const bool DeprecatedDefault = false; public OpenApiOperation() { } - public OpenApiOperation(Microsoft.OpenApi.Models.OpenApiOperation operation) { } - public System.Collections.Generic.IDictionary Annotations { get; set; } - public System.Collections.Generic.IDictionary Callbacks { get; set; } + public OpenApiOperation(Microsoft.OpenApi.Models.OpenApiOperation? operation) { } + public System.Collections.Generic.IDictionary? Annotations { get; set; } + public System.Collections.Generic.IDictionary? Callbacks { get; set; } public bool Deprecated { get; set; } - public string Description { get; set; } - public System.Collections.Generic.IDictionary Extensions { get; set; } - public Microsoft.OpenApi.Models.OpenApiExternalDocs ExternalDocs { get; set; } - public string OperationId { get; set; } - public System.Collections.Generic.IList Parameters { get; set; } - public Microsoft.OpenApi.Models.OpenApiRequestBody RequestBody { get; set; } - public Microsoft.OpenApi.Models.OpenApiResponses Responses { get; set; } - public System.Collections.Generic.IList Security { get; set; } - public System.Collections.Generic.IList Servers { get; set; } - public string Summary { get; set; } - public System.Collections.Generic.IList Tags { get; set; } + public string? Description { get; set; } + public System.Collections.Generic.IDictionary? Extensions { get; set; } + public Microsoft.OpenApi.Models.OpenApiExternalDocs? ExternalDocs { get; set; } + public string? OperationId { get; set; } + public System.Collections.Generic.IList? Parameters { get; set; } + public Microsoft.OpenApi.Models.OpenApiRequestBody? RequestBody { get; set; } + public Microsoft.OpenApi.Models.OpenApiResponses? Responses { get; set; } + public System.Collections.Generic.IList? Security { get; set; } + public System.Collections.Generic.IList? Servers { get; set; } + public string? Summary { get; set; } + public System.Collections.Generic.IList? Tags { get; set; } public void SerializeAsV2(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } public void SerializeAsV3(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } + public void SerializeAsV31(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } } - public class OpenApiParameter : Microsoft.OpenApi.Interfaces.IEffective, Microsoft.OpenApi.Interfaces.IOpenApiElement, Microsoft.OpenApi.Interfaces.IOpenApiExtensible, Microsoft.OpenApi.Interfaces.IOpenApiReferenceable, Microsoft.OpenApi.Interfaces.IOpenApiSerializable + public class OpenApiParameter : Microsoft.OpenApi.Interfaces.IOpenApiElement, Microsoft.OpenApi.Interfaces.IOpenApiExtensible, Microsoft.OpenApi.Interfaces.IOpenApiReferenceable, Microsoft.OpenApi.Interfaces.IOpenApiSerializable { - public Microsoft.OpenApi.Models.ParameterStyle? _style; public OpenApiParameter() { } public OpenApiParameter(Microsoft.OpenApi.Models.OpenApiParameter parameter) { } - public bool AllowEmptyValue { get; set; } - public bool AllowReserved { get; set; } - public System.Collections.Generic.IDictionary Content { get; set; } - public bool Deprecated { get; set; } - public string Description { get; set; } - public Microsoft.OpenApi.Any.IOpenApiAny Example { get; set; } - public System.Collections.Generic.IDictionary Examples { get; set; } - public bool Explode { get; set; } - public System.Collections.Generic.IDictionary Extensions { get; set; } - public Microsoft.OpenApi.Models.ParameterLocation? In { get; set; } - public string Name { get; set; } public Microsoft.OpenApi.Models.OpenApiReference Reference { get; set; } - public bool Required { get; set; } - public Microsoft.OpenApi.Models.OpenApiSchema Schema { get; set; } - public Microsoft.OpenApi.Models.ParameterStyle? Style { get; set; } - public bool UnresolvedReference { get; set; } - public Microsoft.OpenApi.Models.OpenApiParameter GetEffective(Microsoft.OpenApi.Models.OpenApiDocument doc) { } - public void SerializeAsV2(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } - public void SerializeAsV2WithoutReference(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } - public void SerializeAsV3(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } - public void SerializeAsV3WithoutReference(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } - } - public class OpenApiPathItem : Microsoft.OpenApi.Interfaces.IEffective, Microsoft.OpenApi.Interfaces.IOpenApiElement, Microsoft.OpenApi.Interfaces.IOpenApiExtensible, Microsoft.OpenApi.Interfaces.IOpenApiReferenceable, Microsoft.OpenApi.Interfaces.IOpenApiSerializable + public virtual bool AllowEmptyValue { get; set; } + public virtual bool AllowReserved { get; set; } + public virtual System.Collections.Generic.IDictionary Content { get; set; } + public virtual bool Deprecated { get; set; } + public virtual string Description { get; set; } + public virtual System.Text.Json.Nodes.JsonNode Example { get; set; } + public virtual System.Collections.Generic.IDictionary Examples { get; set; } + public virtual bool Explode { get; set; } + public virtual System.Collections.Generic.IDictionary Extensions { get; set; } + public virtual Microsoft.OpenApi.Models.ParameterLocation? In { get; set; } + public virtual string Name { get; set; } + public virtual bool Required { get; set; } + public virtual Microsoft.OpenApi.Models.OpenApiSchema Schema { get; set; } + public virtual Microsoft.OpenApi.Models.ParameterStyle? Style { get; set; } + public virtual bool UnresolvedReference { get; set; } + public virtual void SerializeAsV2(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } + public virtual void SerializeAsV3(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } + public virtual void SerializeAsV31(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } + } + public class OpenApiPathItem : Microsoft.OpenApi.Interfaces.IOpenApiElement, Microsoft.OpenApi.Interfaces.IOpenApiExtensible, Microsoft.OpenApi.Interfaces.IOpenApiReferenceable, Microsoft.OpenApi.Interfaces.IOpenApiSerializable { public OpenApiPathItem() { } public OpenApiPathItem(Microsoft.OpenApi.Models.OpenApiPathItem pathItem) { } - public string Description { get; set; } - public System.Collections.Generic.IDictionary Extensions { get; set; } - public System.Collections.Generic.IDictionary Operations { get; set; } - public System.Collections.Generic.IList Parameters { get; set; } public Microsoft.OpenApi.Models.OpenApiReference Reference { get; set; } - public System.Collections.Generic.IList Servers { get; set; } - public string Summary { get; set; } public bool UnresolvedReference { get; set; } + public virtual string Description { get; set; } + public virtual System.Collections.Generic.IDictionary Extensions { get; set; } + public virtual System.Collections.Generic.IDictionary Operations { get; set; } + public virtual System.Collections.Generic.IList Parameters { get; set; } + public virtual System.Collections.Generic.IList Servers { get; set; } + public virtual string Summary { get; set; } public void AddOperation(Microsoft.OpenApi.Models.OperationType operationType, Microsoft.OpenApi.Models.OpenApiOperation operation) { } - public Microsoft.OpenApi.Models.OpenApiPathItem GetEffective(Microsoft.OpenApi.Models.OpenApiDocument doc) { } public void SerializeAsV2(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } - public void SerializeAsV2WithoutReference(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } - public void SerializeAsV3(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } - public void SerializeAsV3WithoutReference(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } + public virtual void SerializeAsV3(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } + public virtual void SerializeAsV31(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } } public class OpenApiPaths : Microsoft.OpenApi.Models.OpenApiExtensibleDictionary { @@ -858,9 +820,10 @@ namespace Microsoft.OpenApi.Models } public class OpenApiReference : Microsoft.OpenApi.Interfaces.IOpenApiElement, Microsoft.OpenApi.Interfaces.IOpenApiSerializable { - public bool IsFragrament; + public bool IsFragment; public OpenApiReference() { } public OpenApiReference(Microsoft.OpenApi.Models.OpenApiReference reference) { } + public string Description { get; set; } public string ExternalResource { get; set; } public Microsoft.OpenApi.Models.OpenApiDocument HostDocument { get; set; } public string Id { get; set; } @@ -868,123 +831,133 @@ namespace Microsoft.OpenApi.Models public bool IsLocal { get; } public string ReferenceV2 { get; } public string ReferenceV3 { get; } + public string Summary { get; set; } public Microsoft.OpenApi.Models.ReferenceType? Type { get; set; } public void SerializeAsV2(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } public void SerializeAsV3(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } + public void SerializeAsV31(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } } - public class OpenApiRequestBody : Microsoft.OpenApi.Interfaces.IEffective, Microsoft.OpenApi.Interfaces.IOpenApiElement, Microsoft.OpenApi.Interfaces.IOpenApiExtensible, Microsoft.OpenApi.Interfaces.IOpenApiReferenceable, Microsoft.OpenApi.Interfaces.IOpenApiSerializable + public class OpenApiRequestBody : Microsoft.OpenApi.Interfaces.IOpenApiElement, Microsoft.OpenApi.Interfaces.IOpenApiExtensible, Microsoft.OpenApi.Interfaces.IOpenApiReferenceable, Microsoft.OpenApi.Interfaces.IOpenApiSerializable { public OpenApiRequestBody() { } public OpenApiRequestBody(Microsoft.OpenApi.Models.OpenApiRequestBody requestBody) { } - public System.Collections.Generic.IDictionary Content { get; set; } - public string Description { get; set; } - public System.Collections.Generic.IDictionary Extensions { get; set; } public Microsoft.OpenApi.Models.OpenApiReference Reference { get; set; } - public bool Required { get; set; } public bool UnresolvedReference { get; set; } - public Microsoft.OpenApi.Models.OpenApiRequestBody GetEffective(Microsoft.OpenApi.Models.OpenApiDocument doc) { } + public virtual System.Collections.Generic.IDictionary Content { get; set; } + public virtual string Description { get; set; } + public virtual System.Collections.Generic.IDictionary Extensions { get; set; } + public virtual bool Required { get; set; } public void SerializeAsV2(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } - public void SerializeAsV2WithoutReference(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } - public void SerializeAsV3(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } - public void SerializeAsV3WithoutReference(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } + public virtual void SerializeAsV3(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } + public virtual void SerializeAsV31(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } } - public class OpenApiResponse : Microsoft.OpenApi.Interfaces.IEffective, Microsoft.OpenApi.Interfaces.IOpenApiElement, Microsoft.OpenApi.Interfaces.IOpenApiExtensible, Microsoft.OpenApi.Interfaces.IOpenApiReferenceable, Microsoft.OpenApi.Interfaces.IOpenApiSerializable + public class OpenApiResponse : Microsoft.OpenApi.Interfaces.IOpenApiElement, Microsoft.OpenApi.Interfaces.IOpenApiExtensible, Microsoft.OpenApi.Interfaces.IOpenApiReferenceable, Microsoft.OpenApi.Interfaces.IOpenApiSerializable { public OpenApiResponse() { } public OpenApiResponse(Microsoft.OpenApi.Models.OpenApiResponse response) { } - public System.Collections.Generic.IDictionary Content { get; set; } - public string Description { get; set; } - public System.Collections.Generic.IDictionary Extensions { get; set; } - public System.Collections.Generic.IDictionary Headers { get; set; } - public System.Collections.Generic.IDictionary Links { get; set; } public Microsoft.OpenApi.Models.OpenApiReference Reference { get; set; } public bool UnresolvedReference { get; set; } - public Microsoft.OpenApi.Models.OpenApiResponse GetEffective(Microsoft.OpenApi.Models.OpenApiDocument doc) { } - public void SerializeAsV2(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } - public void SerializeAsV2WithoutReference(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } - public void SerializeAsV3(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } - public void SerializeAsV3WithoutReference(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } + public virtual System.Collections.Generic.IDictionary Content { get; set; } + public virtual string Description { get; set; } + public virtual System.Collections.Generic.IDictionary Extensions { get; set; } + public virtual System.Collections.Generic.IDictionary Headers { get; set; } + public virtual System.Collections.Generic.IDictionary Links { get; set; } + public virtual void SerializeAsV2(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } + public virtual void SerializeAsV3(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } + public virtual void SerializeAsV31(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } } public class OpenApiResponses : Microsoft.OpenApi.Models.OpenApiExtensibleDictionary { public OpenApiResponses() { } public OpenApiResponses(Microsoft.OpenApi.Models.OpenApiResponses openApiResponses) { } } - public class OpenApiSchema : Microsoft.OpenApi.Interfaces.IEffective, Microsoft.OpenApi.Interfaces.IOpenApiAnnotatable, Microsoft.OpenApi.Interfaces.IOpenApiElement, Microsoft.OpenApi.Interfaces.IOpenApiExtensible, Microsoft.OpenApi.Interfaces.IOpenApiReferenceable, Microsoft.OpenApi.Interfaces.IOpenApiSerializable + public class OpenApiSchema : Microsoft.OpenApi.Interfaces.IOpenApiAnnotatable, Microsoft.OpenApi.Interfaces.IOpenApiElement, Microsoft.OpenApi.Interfaces.IOpenApiExtensible, Microsoft.OpenApi.Interfaces.IOpenApiReferenceable, Microsoft.OpenApi.Interfaces.IOpenApiSerializable { public OpenApiSchema() { } public OpenApiSchema(Microsoft.OpenApi.Models.OpenApiSchema schema) { } - public Microsoft.OpenApi.Models.OpenApiSchema AdditionalProperties { get; set; } - public bool AdditionalPropertiesAllowed { get; set; } - public System.Collections.Generic.IList AllOf { get; set; } public System.Collections.Generic.IDictionary Annotations { get; set; } - public System.Collections.Generic.IList AnyOf { get; set; } - public Microsoft.OpenApi.Any.IOpenApiAny Default { get; set; } - public bool Deprecated { get; set; } - public string Description { get; set; } - public Microsoft.OpenApi.Models.OpenApiDiscriminator Discriminator { get; set; } - public System.Collections.Generic.IList Enum { get; set; } - public Microsoft.OpenApi.Any.IOpenApiAny Example { get; set; } - public bool? ExclusiveMaximum { get; set; } - public bool? ExclusiveMinimum { get; set; } - public System.Collections.Generic.IDictionary Extensions { get; set; } - public Microsoft.OpenApi.Models.OpenApiExternalDocs ExternalDocs { get; set; } - public string Format { get; set; } - public Microsoft.OpenApi.Models.OpenApiSchema Items { get; set; } - public int? MaxItems { get; set; } - public int? MaxLength { get; set; } - public int? MaxProperties { get; set; } - public decimal? Maximum { get; set; } - public int? MinItems { get; set; } - public int? MinLength { get; set; } - public int? MinProperties { get; set; } - public decimal? Minimum { get; set; } - public decimal? MultipleOf { get; set; } - public Microsoft.OpenApi.Models.OpenApiSchema Not { get; set; } - public bool Nullable { get; set; } - public System.Collections.Generic.IList OneOf { get; set; } - public string Pattern { get; set; } - public System.Collections.Generic.IDictionary Properties { get; set; } - public bool ReadOnly { get; set; } - public Microsoft.OpenApi.Models.OpenApiReference Reference { get; set; } - public System.Collections.Generic.ISet Required { get; set; } - public string Title { get; set; } - public string Type { get; set; } - public bool? UniqueItems { get; set; } - public bool UnresolvedReference { get; set; } - public bool WriteOnly { get; set; } - public Microsoft.OpenApi.Models.OpenApiXml Xml { get; set; } - public Microsoft.OpenApi.Models.OpenApiSchema GetEffective(Microsoft.OpenApi.Models.OpenApiDocument doc) { } - public void SerializeAsV2(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } - public void SerializeAsV2WithoutReference(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } - public void SerializeAsV3(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } - public void SerializeAsV3WithoutReference(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } + public virtual Microsoft.OpenApi.Models.OpenApiSchema AdditionalProperties { get; set; } + public virtual bool AdditionalPropertiesAllowed { get; set; } + 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 System.Text.Json.Nodes.JsonNode Default { get; set; } + public virtual System.Collections.Generic.IDictionary Definitions { get; set; } + public virtual bool Deprecated { get; set; } + public virtual string Description { get; set; } + public virtual Microsoft.OpenApi.Models.OpenApiDiscriminator Discriminator { get; set; } + public virtual string DynamicAnchor { get; set; } + public virtual string DynamicRef { get; set; } + public virtual System.Collections.Generic.IList Enum { get; set; } + public virtual System.Text.Json.Nodes.JsonNode Example { get; set; } + public virtual System.Collections.Generic.IList Examples { get; set; } + public virtual bool? ExclusiveMaximum { get; set; } + public virtual bool? ExclusiveMinimum { get; set; } + public virtual System.Collections.Generic.IDictionary Extensions { get; set; } + public virtual Microsoft.OpenApi.Models.OpenApiExternalDocs ExternalDocs { get; set; } + public virtual string Format { get; set; } + public virtual string Id { get; set; } + public virtual Microsoft.OpenApi.Models.OpenApiSchema Items { get; set; } + public virtual int? MaxItems { get; set; } + public virtual int? MaxLength { get; set; } + public virtual int? MaxProperties { get; set; } + public virtual decimal? Maximum { get; set; } + public virtual int? MinItems { get; set; } + public virtual int? MinLength { get; set; } + public virtual int? MinProperties { get; set; } + public virtual decimal? Minimum { get; set; } + public virtual decimal? MultipleOf { get; set; } + public virtual Microsoft.OpenApi.Models.OpenApiSchema Not { get; set; } + public virtual bool Nullable { get; set; } + public virtual System.Collections.Generic.IList OneOf { get; set; } + public virtual string Pattern { get; set; } + public virtual System.Collections.Generic.IDictionary PatternProperties { get; set; } + public virtual System.Collections.Generic.IDictionary Properties { get; set; } + public virtual bool ReadOnly { get; set; } + public virtual Microsoft.OpenApi.Models.OpenApiReference Reference { get; set; } + public virtual System.Collections.Generic.ISet Required { get; set; } + public virtual string Schema { get; set; } + public virtual string Title { get; set; } + public virtual Microsoft.OpenApi.Models.JsonSchemaType? Type { get; set; } + public virtual bool UnEvaluatedProperties { get; set; } + public virtual bool UnevaluatedProperties { get; set; } + public virtual bool? UniqueItems { get; set; } + public virtual bool UnresolvedReference { get; set; } + public virtual decimal? V31ExclusiveMaximum { get; set; } + public virtual decimal? V31ExclusiveMinimum { get; set; } + public virtual System.Collections.Generic.IDictionary Vocabulary { get; set; } + public virtual bool WriteOnly { get; set; } + public virtual Microsoft.OpenApi.Models.OpenApiXml Xml { get; set; } + public virtual void SerializeAsV2(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } + public virtual void SerializeAsV3(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } + public virtual void SerializeAsV31(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } + public void SerializeInternal(Microsoft.OpenApi.Writers.IOpenApiWriter writer, Microsoft.OpenApi.OpenApiSpecVersion version, System.Action callback) { } } public class OpenApiSecurityRequirement : System.Collections.Generic.Dictionary>, Microsoft.OpenApi.Interfaces.IOpenApiElement, Microsoft.OpenApi.Interfaces.IOpenApiSerializable { public OpenApiSecurityRequirement() { } public void SerializeAsV2(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } public void SerializeAsV3(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } + public void SerializeAsV31(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } } public class OpenApiSecurityScheme : Microsoft.OpenApi.Interfaces.IOpenApiElement, Microsoft.OpenApi.Interfaces.IOpenApiExtensible, Microsoft.OpenApi.Interfaces.IOpenApiReferenceable, Microsoft.OpenApi.Interfaces.IOpenApiSerializable { public OpenApiSecurityScheme() { } public OpenApiSecurityScheme(Microsoft.OpenApi.Models.OpenApiSecurityScheme securityScheme) { } - public string BearerFormat { get; set; } - public string Description { get; set; } - public System.Collections.Generic.IDictionary Extensions { get; set; } - public Microsoft.OpenApi.Models.OpenApiOAuthFlows Flows { get; set; } - public Microsoft.OpenApi.Models.ParameterLocation In { get; set; } - public string Name { get; set; } - public System.Uri OpenIdConnectUrl { get; set; } public Microsoft.OpenApi.Models.OpenApiReference Reference { get; set; } - public string Scheme { get; set; } - public Microsoft.OpenApi.Models.SecuritySchemeType Type { get; set; } public bool UnresolvedReference { get; set; } - public void SerializeAsV2(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } - public void SerializeAsV2WithoutReference(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } - public void SerializeAsV3(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } - public void SerializeAsV3WithoutReference(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } + public virtual string BearerFormat { get; set; } + public virtual string Description { get; set; } + public virtual System.Collections.Generic.IDictionary Extensions { get; set; } + public virtual Microsoft.OpenApi.Models.OpenApiOAuthFlows Flows { get; set; } + public virtual Microsoft.OpenApi.Models.ParameterLocation In { get; set; } + public virtual string Name { get; set; } + public virtual System.Uri OpenIdConnectUrl { get; set; } + public virtual string Scheme { get; set; } + public virtual Microsoft.OpenApi.Models.SecuritySchemeType Type { get; set; } + public virtual void SerializeAsV2(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } + public virtual void SerializeAsV3(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } + public virtual void SerializeAsV31(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } } public class OpenApiServer : Microsoft.OpenApi.Interfaces.IOpenApiElement, Microsoft.OpenApi.Interfaces.IOpenApiExtensible, Microsoft.OpenApi.Interfaces.IOpenApiSerializable { @@ -996,6 +969,7 @@ namespace Microsoft.OpenApi.Models public System.Collections.Generic.IDictionary Variables { get; set; } public void SerializeAsV2(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } public void SerializeAsV3(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } + public void SerializeAsV31(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } } public class OpenApiServerVariable : Microsoft.OpenApi.Interfaces.IOpenApiElement, Microsoft.OpenApi.Interfaces.IOpenApiExtensible, Microsoft.OpenApi.Interfaces.IOpenApiSerializable { @@ -1007,21 +981,24 @@ namespace Microsoft.OpenApi.Models public System.Collections.Generic.IDictionary Extensions { get; set; } public void SerializeAsV2(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } public void SerializeAsV3(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } + public void SerializeAsV31(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } } public class OpenApiTag : Microsoft.OpenApi.Interfaces.IOpenApiElement, Microsoft.OpenApi.Interfaces.IOpenApiExtensible, Microsoft.OpenApi.Interfaces.IOpenApiReferenceable, Microsoft.OpenApi.Interfaces.IOpenApiSerializable { public OpenApiTag() { } public OpenApiTag(Microsoft.OpenApi.Models.OpenApiTag tag) { } - public string Description { get; set; } - public System.Collections.Generic.IDictionary Extensions { get; set; } - public Microsoft.OpenApi.Models.OpenApiExternalDocs ExternalDocs { get; set; } - public string Name { get; set; } public Microsoft.OpenApi.Models.OpenApiReference Reference { get; set; } public bool UnresolvedReference { get; set; } - public void SerializeAsV2(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } + public virtual string Description { get; set; } + public virtual System.Collections.Generic.IDictionary Extensions { get; set; } + 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 void SerializeAsV3(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } - public void SerializeAsV3WithoutReference(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 { @@ -1035,6 +1012,7 @@ namespace Microsoft.OpenApi.Models public bool Wrapped { get; set; } public void SerializeAsV2(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } public void SerializeAsV3(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } + public void SerializeAsV31(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } } public enum OperationType { @@ -1105,14 +1083,14 @@ namespace Microsoft.OpenApi.Models Callback = 8, [Microsoft.OpenApi.Attributes.Display("tags")] Tag = 9, - [Microsoft.OpenApi.Attributes.Display("paths")] - Path = 10, + [Microsoft.OpenApi.Attributes.Display("pathItems")] + PathItem = 10, } public class RuntimeExpressionAnyWrapper : Microsoft.OpenApi.Interfaces.IOpenApiElement { public RuntimeExpressionAnyWrapper() { } public RuntimeExpressionAnyWrapper(Microsoft.OpenApi.Models.RuntimeExpressionAnyWrapper runtimeExpressionAnyWrapper) { } - public Microsoft.OpenApi.Any.IOpenApiAny Any { get; set; } + public System.Text.Json.Nodes.JsonNode Any { get; set; } public Microsoft.OpenApi.Expressions.RuntimeExpression Expression { get; set; } public void WriteValue(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } } @@ -1128,6 +1106,311 @@ namespace Microsoft.OpenApi.Models OpenIdConnect = 3, } } +namespace Microsoft.OpenApi.Models.References +{ + public class OpenApiCallbackReference : Microsoft.OpenApi.Models.OpenApiCallback + { + public OpenApiCallbackReference(string referenceId, Microsoft.OpenApi.Models.OpenApiDocument hostDocument, string externalResource = null) { } + public override System.Collections.Generic.IDictionary Extensions { get; set; } + public override System.Collections.Generic.Dictionary PathItems { get; set; } + public override void SerializeAsV3(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } + public override void SerializeAsV31(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } + } + public class OpenApiExampleReference : Microsoft.OpenApi.Models.OpenApiExample + { + public OpenApiExampleReference(string referenceId, Microsoft.OpenApi.Models.OpenApiDocument hostDocument, string externalResource = null) { } + public override string Description { get; set; } + public override System.Collections.Generic.IDictionary Extensions { get; set; } + public override string ExternalValue { get; set; } + public override string Summary { get; set; } + public override System.Text.Json.Nodes.JsonNode Value { get; set; } + public override void SerializeAsV3(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } + public override void SerializeAsV31(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } + } + public class OpenApiHeaderReference : Microsoft.OpenApi.Models.OpenApiHeader + { + public OpenApiHeaderReference(string referenceId, Microsoft.OpenApi.Models.OpenApiDocument hostDocument, string externalResource = null) { } + public override bool AllowEmptyValue { get; set; } + public override bool AllowReserved { get; set; } + public override System.Collections.Generic.IDictionary Content { get; set; } + public override bool Deprecated { get; set; } + public override string Description { get; set; } + public override System.Text.Json.Nodes.JsonNode Example { get; set; } + public override System.Collections.Generic.IDictionary Examples { get; set; } + public override bool Explode { get; set; } + public override System.Collections.Generic.IDictionary Extensions { get; set; } + public override bool Required { get; set; } + public override Microsoft.OpenApi.Models.OpenApiSchema Schema { get; set; } + public override Microsoft.OpenApi.Models.ParameterStyle? Style { get; set; } + public override void SerializeAsV2(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } + public override void SerializeAsV3(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } + public override void SerializeAsV31(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } + } + public class OpenApiLinkReference : Microsoft.OpenApi.Models.OpenApiLink + { + public OpenApiLinkReference(string referenceId, Microsoft.OpenApi.Models.OpenApiDocument hostDocument, string externalResource = null) { } + public override string Description { get; set; } + public override System.Collections.Generic.IDictionary Extensions { get; set; } + public override string OperationId { get; set; } + public override string OperationRef { get; set; } + public override System.Collections.Generic.Dictionary Parameters { get; set; } + public override Microsoft.OpenApi.Models.RuntimeExpressionAnyWrapper RequestBody { get; set; } + public override Microsoft.OpenApi.Models.OpenApiServer Server { get; set; } + public override void SerializeAsV3(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } + public override void SerializeAsV31(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } + } + public class OpenApiParameterReference : Microsoft.OpenApi.Models.OpenApiParameter + { + public OpenApiParameterReference(string referenceId, Microsoft.OpenApi.Models.OpenApiDocument hostDocument, string externalResource = null) { } + public override bool AllowEmptyValue { get; set; } + public override bool AllowReserved { get; set; } + public override System.Collections.Generic.IDictionary Content { get; set; } + public override bool Deprecated { get; set; } + public override string Description { get; set; } + public override System.Text.Json.Nodes.JsonNode Example { get; set; } + public override System.Collections.Generic.IDictionary Examples { get; set; } + public override bool Explode { get; set; } + public override System.Collections.Generic.IDictionary Extensions { get; set; } + public override Microsoft.OpenApi.Models.ParameterLocation? In { get; set; } + public override string Name { get; set; } + public override bool Required { get; set; } + public override Microsoft.OpenApi.Models.OpenApiSchema Schema { get; set; } + public override Microsoft.OpenApi.Models.ParameterStyle? Style { get; set; } + public override void SerializeAsV2(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } + public override void SerializeAsV3(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } + public override void SerializeAsV31(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } + } + public class OpenApiPathItemReference : Microsoft.OpenApi.Models.OpenApiPathItem + { + public OpenApiPathItemReference(string referenceId, Microsoft.OpenApi.Models.OpenApiDocument hostDocument, string externalResource = null) { } + public override string Description { get; set; } + public override System.Collections.Generic.IDictionary Extensions { get; set; } + public override System.Collections.Generic.IDictionary Operations { get; set; } + public override System.Collections.Generic.IList Parameters { get; set; } + public override System.Collections.Generic.IList Servers { get; set; } + public override string Summary { get; set; } + public override void SerializeAsV31(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } + } + public class OpenApiRequestBodyReference : Microsoft.OpenApi.Models.OpenApiRequestBody + { + public OpenApiRequestBodyReference(string referenceId, Microsoft.OpenApi.Models.OpenApiDocument hostDocument, string externalResource = null) { } + public override System.Collections.Generic.IDictionary Content { get; set; } + public override string Description { get; set; } + public override System.Collections.Generic.IDictionary Extensions { get; set; } + public override bool Required { get; set; } + public override void SerializeAsV3(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } + public override void SerializeAsV31(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } + } + public class OpenApiResponseReference : Microsoft.OpenApi.Models.OpenApiResponse + { + public OpenApiResponseReference(string referenceId, Microsoft.OpenApi.Models.OpenApiDocument hostDocument, string externalResource = null) { } + public override System.Collections.Generic.IDictionary Content { get; set; } + public override string Description { get; set; } + public override System.Collections.Generic.IDictionary Extensions { get; set; } + public override System.Collections.Generic.IDictionary Headers { get; set; } + public override System.Collections.Generic.IDictionary Links { get; set; } + public override void SerializeAsV2(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } + public override void SerializeAsV3(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } + public override void SerializeAsV31(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } + } + public class OpenApiSchemaReference : Microsoft.OpenApi.Models.OpenApiSchema + { + public OpenApiSchemaReference(string referenceId, Microsoft.OpenApi.Models.OpenApiDocument hostDocument, string externalResource = null) { } + public override Microsoft.OpenApi.Models.OpenApiSchema AdditionalProperties { get; set; } + public override bool AdditionalPropertiesAllowed { get; set; } + 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 System.Text.Json.Nodes.JsonNode Default { get; set; } + public override System.Collections.Generic.IDictionary Definitions { get; set; } + public override bool Deprecated { get; set; } + public override string Description { get; set; } + public override Microsoft.OpenApi.Models.OpenApiDiscriminator Discriminator { get; set; } + public override string DynamicAnchor { get; set; } + public override string DynamicRef { get; set; } + public override System.Collections.Generic.IList Enum { get; set; } + public override System.Text.Json.Nodes.JsonNode Example { get; set; } + public override System.Collections.Generic.IList Examples { get; set; } + public override bool? ExclusiveMaximum { get; set; } + public override bool? ExclusiveMinimum { get; set; } + public override System.Collections.Generic.IDictionary Extensions { get; set; } + public override Microsoft.OpenApi.Models.OpenApiExternalDocs ExternalDocs { get; set; } + public override string Format { get; set; } + public override string Id { get; set; } + public override Microsoft.OpenApi.Models.OpenApiSchema Items { get; set; } + public override int? MaxItems { get; set; } + public override int? MaxLength { get; set; } + public override int? MaxProperties { get; set; } + public override decimal? Maximum { get; set; } + public override int? MinItems { get; set; } + public override int? MinLength { get; set; } + public override int? MinProperties { get; set; } + public override decimal? Minimum { get; set; } + public override decimal? MultipleOf { get; set; } + public override Microsoft.OpenApi.Models.OpenApiSchema Not { get; set; } + public override bool Nullable { get; set; } + public override System.Collections.Generic.IList OneOf { get; set; } + public override string Pattern { get; set; } + public override System.Collections.Generic.IDictionary PatternProperties { get; set; } + public override System.Collections.Generic.IDictionary Properties { get; set; } + public override bool ReadOnly { get; set; } + public override System.Collections.Generic.ISet Required { get; set; } + public override string Schema { get; set; } + public override string Title { get; set; } + public override Microsoft.OpenApi.Models.JsonSchemaType? Type { get; set; } + public override bool UnEvaluatedProperties { get; set; } + public override bool UnevaluatedProperties { get; set; } + public override bool? UniqueItems { get; set; } + public override decimal? V31ExclusiveMaximum { get; set; } + public override decimal? V31ExclusiveMinimum { get; set; } + public override System.Collections.Generic.IDictionary Vocabulary { get; set; } + public override bool WriteOnly { get; set; } + public override Microsoft.OpenApi.Models.OpenApiXml Xml { get; set; } + public override void SerializeAsV2(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } + public override void SerializeAsV3(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } + public override void SerializeAsV31(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } + } + public class OpenApiSecuritySchemeReference : Microsoft.OpenApi.Models.OpenApiSecurityScheme + { + public OpenApiSecuritySchemeReference(string referenceId, Microsoft.OpenApi.Models.OpenApiDocument hostDocument, string externalResource = null) { } + public override string BearerFormat { get; set; } + public override string Description { get; set; } + public override System.Collections.Generic.IDictionary Extensions { get; set; } + public override Microsoft.OpenApi.Models.OpenApiOAuthFlows Flows { get; set; } + public override Microsoft.OpenApi.Models.ParameterLocation In { get; set; } + public override string Name { get; set; } + public override System.Uri OpenIdConnectUrl { get; set; } + public override string Scheme { get; set; } + public override Microsoft.OpenApi.Models.SecuritySchemeType Type { get; set; } + public override void SerializeAsV2(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } + public override void SerializeAsV3(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } + public override void SerializeAsV31(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } + } + public class OpenApiTagReference : Microsoft.OpenApi.Models.OpenApiTag + { + public OpenApiTagReference(string referenceId, Microsoft.OpenApi.Models.OpenApiDocument hostDocument) { } + public override string Description { get; set; } + public override System.Collections.Generic.IDictionary Extensions { get; set; } + public override Microsoft.OpenApi.Models.OpenApiExternalDocs ExternalDocs { get; set; } + public override string Name { get; set; } + public override void SerializeAsV2(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } + public override void SerializeAsV3(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } + public override void SerializeAsV31(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } + } +} +namespace Microsoft.OpenApi.Reader +{ + public class OpenApiDiagnostic : Microsoft.OpenApi.Interfaces.IDiagnostic + { + public OpenApiDiagnostic() { } + public System.Collections.Generic.IList Errors { get; set; } + public Microsoft.OpenApi.OpenApiSpecVersion SpecificationVersion { get; set; } + public System.Collections.Generic.IList Warnings { get; set; } + public void AppendDiagnostic(Microsoft.OpenApi.Reader.OpenApiDiagnostic diagnosticToAdd, string fileNameToAdd = null) { } + } + public class OpenApiJsonReader : Microsoft.OpenApi.Interfaces.IOpenApiReader + { + public OpenApiJsonReader() { } + public System.Threading.Tasks.Task ReadAsync(System.IO.TextReader input, Microsoft.OpenApi.Reader.OpenApiReaderSettings settings = null, System.Threading.CancellationToken cancellationToken = default) { } + public System.Threading.Tasks.Task ReadAsync(System.Text.Json.Nodes.JsonNode jsonNode, Microsoft.OpenApi.Reader.OpenApiReaderSettings settings, string format = null, System.Threading.CancellationToken cancellationToken = default) { } + public T ReadFragment(System.IO.TextReader input, Microsoft.OpenApi.OpenApiSpecVersion version, out Microsoft.OpenApi.Reader.OpenApiDiagnostic diagnostic, Microsoft.OpenApi.Reader.OpenApiReaderSettings settings = null) + where T : Microsoft.OpenApi.Interfaces.IOpenApiElement { } + public T ReadFragment(System.Text.Json.Nodes.JsonNode input, Microsoft.OpenApi.OpenApiSpecVersion version, out Microsoft.OpenApi.Reader.OpenApiDiagnostic diagnostic, Microsoft.OpenApi.Reader.OpenApiReaderSettings settings = null) + where T : Microsoft.OpenApi.Interfaces.IOpenApiElement { } + } + public static class OpenApiModelFactory + { + public static string GetFormat(string url) { } + public static Microsoft.OpenApi.Reader.ReadResult Load(string url, Microsoft.OpenApi.Reader.OpenApiReaderSettings settings = null) { } + public static Microsoft.OpenApi.Reader.ReadResult Load(System.IO.Stream stream, string format, Microsoft.OpenApi.Reader.OpenApiReaderSettings settings = null) { } + public static Microsoft.OpenApi.Reader.ReadResult Load(System.IO.TextReader input, string format, Microsoft.OpenApi.Reader.OpenApiReaderSettings settings = null) { } + public static T Load(string url, Microsoft.OpenApi.OpenApiSpecVersion version, out Microsoft.OpenApi.Reader.OpenApiDiagnostic diagnostic, Microsoft.OpenApi.Reader.OpenApiReaderSettings settings = null) + where T : Microsoft.OpenApi.Interfaces.IOpenApiElement { } + public static T Load(System.IO.Stream input, Microsoft.OpenApi.OpenApiSpecVersion version, string format, out Microsoft.OpenApi.Reader.OpenApiDiagnostic diagnostic, Microsoft.OpenApi.Reader.OpenApiReaderSettings settings = null) + where T : Microsoft.OpenApi.Interfaces.IOpenApiElement { } + public static T Load(System.IO.TextReader input, Microsoft.OpenApi.OpenApiSpecVersion version, out Microsoft.OpenApi.Reader.OpenApiDiagnostic diagnostic, string format, Microsoft.OpenApi.Reader.OpenApiReaderSettings settings = null) + where T : Microsoft.OpenApi.Interfaces.IOpenApiElement { } + public static System.Threading.Tasks.Task LoadAsync(string url, Microsoft.OpenApi.Reader.OpenApiReaderSettings settings = null) { } + public static System.Threading.Tasks.Task LoadAsync(System.IO.Stream input, string format, Microsoft.OpenApi.Reader.OpenApiReaderSettings settings = null, System.Threading.CancellationToken cancellationToken = default) { } + public static System.Threading.Tasks.Task LoadAsync(System.IO.TextReader input, string format, Microsoft.OpenApi.Reader.OpenApiReaderSettings settings = null, System.Threading.CancellationToken cancellationToken = default) { } + public static Microsoft.OpenApi.Reader.ReadResult Parse(string input, string format = null, Microsoft.OpenApi.Reader.OpenApiReaderSettings settings = null) { } + public static T Parse(string input, Microsoft.OpenApi.OpenApiSpecVersion version, out Microsoft.OpenApi.Reader.OpenApiDiagnostic diagnostic, string format = null, Microsoft.OpenApi.Reader.OpenApiReaderSettings settings = null) + where T : Microsoft.OpenApi.Interfaces.IOpenApiElement { } + public static System.Threading.Tasks.Task ParseAsync(string input, System.IO.StringReader reader, string format = null, Microsoft.OpenApi.Reader.OpenApiReaderSettings settings = null) { } + } + public static class OpenApiReaderRegistry + { + public static readonly Microsoft.OpenApi.Interfaces.IOpenApiReader DefaultReader; + public static Microsoft.OpenApi.Interfaces.IOpenApiReader GetReader(string format) { } + public static void RegisterReader(string format, Microsoft.OpenApi.Interfaces.IOpenApiReader reader) { } + } + public class OpenApiReaderSettings + { + public OpenApiReaderSettings() { } + public System.Uri BaseUrl { get; set; } + public Microsoft.OpenApi.Interfaces.IStreamLoader CustomExternalLoader { get; set; } + public System.Collections.Generic.List DefaultContentType { get; set; } + public System.Collections.Generic.Dictionary> ExtensionParsers { get; set; } + public bool LeaveStreamOpen { get; set; } + public bool LoadExternalRefs { get; set; } + public Microsoft.OpenApi.Reader.ReferenceResolutionSetting ReferenceResolution { get; set; } + public Microsoft.OpenApi.Validations.ValidationRuleSet RuleSet { get; set; } + public void AddMicrosoftExtensionParsers() { } + } + public static class OpenApiVersionExtensionMethods + { + public static bool is2_0(this string version) { } + public static bool is3_0(this string version) { } + public static bool is3_1(this string version) { } + } + public class ParsingContext + { + public ParsingContext(Microsoft.OpenApi.Reader.OpenApiDiagnostic diagnostic) { } + public System.Uri BaseUrl { get; set; } + public System.Collections.Generic.List DefaultContentType { get; set; } + public Microsoft.OpenApi.Reader.OpenApiDiagnostic Diagnostic { get; } + public System.Collections.Generic.Dictionary> ExtensionParsers { get; set; } + public void EndObject() { } + public T GetFromTempStorage(string key, object scope = null) { } + public string GetLocation() { } + public Microsoft.OpenApi.Models.OpenApiDocument Parse(System.Text.Json.Nodes.JsonNode jsonNode) { } + public T ParseFragment(System.Text.Json.Nodes.JsonNode jsonNode, Microsoft.OpenApi.OpenApiSpecVersion version) + where T : Microsoft.OpenApi.Interfaces.IOpenApiElement { } + public void PopLoop(string loopid) { } + public bool PushLoop(string loopId, string key) { } + public void SetTempStorage(string key, object value, object scope = null) { } + public void StartObject(string objectName) { } + } + public class ReadResult + { + public ReadResult() { } + public Microsoft.OpenApi.Reader.OpenApiDiagnostic OpenApiDiagnostic { get; set; } + public Microsoft.OpenApi.Models.OpenApiDocument OpenApiDocument { get; set; } + } + public enum ReferenceResolutionSetting + { + DoNotResolveReferences = 0, + ResolveLocalReferences = 1, + ResolveAllReferences = 2, + } +} +namespace Microsoft.OpenApi.Reader.ParseNodes +{ + public static class JsonPointerExtensions + { + public static System.Text.Json.Nodes.JsonNode Find(this Microsoft.OpenApi.JsonPointer currentPointer, System.Text.Json.Nodes.JsonNode baseJsonNode) { } + } +} +namespace Microsoft.OpenApi.Reader.Services +{ + public class DefaultStreamLoader : Microsoft.OpenApi.Interfaces.IStreamLoader + { + public DefaultStreamLoader(System.Uri baseUrl) { } + [System.Obsolete] + public System.IO.Stream Load(System.Uri uri) { } + public System.Threading.Tasks.Task LoadAsync(System.Uri uri) { } + } +} namespace Microsoft.OpenApi.Services { public class CurrentKeys @@ -1169,25 +1452,6 @@ namespace Microsoft.OpenApi.Services public OpenApiReferenceError(Microsoft.OpenApi.Exceptions.OpenApiException exception) { } public OpenApiReferenceError(Microsoft.OpenApi.Models.OpenApiReference reference, string message) { } } - public class OpenApiReferenceResolver : Microsoft.OpenApi.Services.OpenApiVisitorBase - { - public OpenApiReferenceResolver(Microsoft.OpenApi.Models.OpenApiDocument currentDocument, bool resolveRemoteReferences = true) { } - public System.Collections.Generic.IEnumerable Errors { get; } - public override void Visit(Microsoft.OpenApi.Interfaces.IOpenApiReferenceable referenceable) { } - public override void Visit(Microsoft.OpenApi.Models.OpenApiComponents components) { } - public override void Visit(Microsoft.OpenApi.Models.OpenApiDocument doc) { } - public override void Visit(Microsoft.OpenApi.Models.OpenApiMediaType mediaType) { } - 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.OpenApiResponses responses) { } - public override void Visit(Microsoft.OpenApi.Models.OpenApiSchema schema) { } - public override void Visit(Microsoft.OpenApi.Models.OpenApiSecurityRequirement securityRequirement) { } - public override void Visit(System.Collections.Generic.IDictionary callbacks) { } - 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.IList parameters) { } - } public class OpenApiUrlTreeNode { public static readonly System.Collections.Generic.IReadOnlyDictionary MermaidNodeStyles; @@ -1222,7 +1486,7 @@ namespace Microsoft.OpenApi.Services public virtual void Visit(Microsoft.OpenApi.Models.OpenApiEncoding encoding) { } public virtual void Visit(Microsoft.OpenApi.Models.OpenApiExample example) { } public virtual void Visit(Microsoft.OpenApi.Models.OpenApiExternalDocs externalDocs) { } - public virtual void Visit(Microsoft.OpenApi.Models.OpenApiHeader tag) { } + public virtual void Visit(Microsoft.OpenApi.Models.OpenApiHeader header) { } public virtual void Visit(Microsoft.OpenApi.Models.OpenApiInfo info) { } public virtual void Visit(Microsoft.OpenApi.Models.OpenApiLicense license) { } public virtual void Visit(Microsoft.OpenApi.Models.OpenApiLink link) { } @@ -1248,12 +1512,14 @@ namespace Microsoft.OpenApi.Services public virtual void Visit(System.Collections.Generic.IDictionary headers) { } public virtual void Visit(System.Collections.Generic.IDictionary links) { } public virtual void Visit(System.Collections.Generic.IDictionary content) { } + public virtual void Visit(System.Collections.Generic.IDictionary webhooks) { } public virtual void Visit(System.Collections.Generic.IDictionary serverVariables) { } public virtual void Visit(System.Collections.Generic.IList example) { } public virtual void Visit(System.Collections.Generic.IList parameters) { } public virtual void Visit(System.Collections.Generic.IList openApiSecurityRequirements) { } public virtual void Visit(System.Collections.Generic.IList servers) { } public virtual void Visit(System.Collections.Generic.IList openApiTags) { } + public virtual void Visit(System.Text.Json.Nodes.JsonNode node) { } } public class OpenApiWalker { @@ -1265,16 +1531,14 @@ namespace Microsoft.OpenApi.Services public OpenApiWorkspace() { } public OpenApiWorkspace(Microsoft.OpenApi.Services.OpenApiWorkspace workspace) { } public OpenApiWorkspace(System.Uri baseUrl) { } - public System.Collections.Generic.IEnumerable Artifacts { get; } public System.Uri BaseUrl { get; } - public System.Collections.Generic.IEnumerable Documents { get; } - public System.Collections.Generic.IEnumerable Fragments { get; } - public void AddArtifact(string location, System.IO.Stream artifact) { } - public void AddDocument(string location, Microsoft.OpenApi.Models.OpenApiDocument document) { } - public void AddFragment(string location, Microsoft.OpenApi.Interfaces.IOpenApiReferenceable fragment) { } + public void AddDocumentId(string key, System.Uri value) { } + public int ComponentsCount() { } public bool Contains(string location) { } - public System.IO.Stream GetArtifact(string location) { } - public Microsoft.OpenApi.Interfaces.IOpenApiReferenceable ResolveReference(Microsoft.OpenApi.Models.OpenApiReference reference) { } + public System.Uri GetDocumentId(string key) { } + public bool RegisterComponent(string location, T component) { } + public void RegisterComponents(Microsoft.OpenApi.Models.OpenApiDocument document) { } + public T? ResolveReference(string location) { } } public class OperationSearch : Microsoft.OpenApi.Services.OpenApiVisitorBase { @@ -1295,6 +1559,7 @@ namespace Microsoft.OpenApi.Validations { public interface IValidationContext { + Microsoft.OpenApi.Models.OpenApiDocument HostDocument { get; } string PathString { get; } void AddError(Microsoft.OpenApi.Validations.OpenApiValidatorError error); void AddWarning(Microsoft.OpenApi.Validations.OpenApiValidatorWarning warning); @@ -1303,8 +1568,9 @@ namespace Microsoft.OpenApi.Validations } public class OpenApiValidator : Microsoft.OpenApi.Services.OpenApiVisitorBase, Microsoft.OpenApi.Validations.IValidationContext { - public OpenApiValidator(Microsoft.OpenApi.Validations.ValidationRuleSet ruleSet) { } + public OpenApiValidator(Microsoft.OpenApi.Validations.ValidationRuleSet ruleSet, Microsoft.OpenApi.Models.OpenApiDocument hostDocument = null) { } public System.Collections.Generic.IEnumerable Errors { get; } + public Microsoft.OpenApi.Models.OpenApiDocument HostDocument { get; set; } public System.Collections.Generic.IEnumerable Warnings { get; } public void AddError(Microsoft.OpenApi.Validations.OpenApiValidatorError error) { } public void AddWarning(Microsoft.OpenApi.Validations.OpenApiValidatorWarning warning) { } @@ -1365,22 +1631,31 @@ namespace Microsoft.OpenApi.Validations { public string Name { get; } } - public sealed class ValidationRuleSet : System.Collections.Generic.IEnumerable, System.Collections.IEnumerable + public sealed class ValidationRuleSet { public ValidationRuleSet() { } public ValidationRuleSet(Microsoft.OpenApi.Validations.ValidationRuleSet ruleSet) { } - public ValidationRuleSet(System.Collections.Generic.IEnumerable rules) { } + public ValidationRuleSet(System.Collections.Generic.IDictionary> rules) { } + public int Count { get; } public System.Collections.Generic.IList Rules { get; } - public void Add(Microsoft.OpenApi.Validations.ValidationRule rule) { } + public void Add(System.Type key, Microsoft.OpenApi.Validations.ValidationRule rule) { } + public void Add(System.Type key, System.Collections.Generic.IList rules) { } + public void Clear() { } + public bool Contains(System.Type key, Microsoft.OpenApi.Validations.ValidationRule rule) { } + public bool ContainsKey(System.Type key) { } public System.Collections.Generic.IList FindRules(System.Type type) { } public System.Collections.Generic.IEnumerator GetEnumerator() { } + public bool Remove(Microsoft.OpenApi.Validations.ValidationRule rule) { } public void Remove(string ruleName) { } - public void Remove(System.Type type) { } + public bool Remove(System.Type key) { } + public bool Remove(System.Type key, Microsoft.OpenApi.Validations.ValidationRule rule) { } + public bool TryGetValue(System.Type key, out System.Collections.Generic.IList rules) { } + public bool Update(System.Type key, Microsoft.OpenApi.Validations.ValidationRule newRule, Microsoft.OpenApi.Validations.ValidationRule oldRule) { } + public static void AddValidationRules(Microsoft.OpenApi.Validations.ValidationRuleSet ruleSet, System.Collections.Generic.IDictionary> rules) { } public static Microsoft.OpenApi.Validations.ValidationRuleSet GetDefaultRuleSet() { } public static Microsoft.OpenApi.Validations.ValidationRuleSet GetEmptyRuleSet() { } } public class ValidationRule : Microsoft.OpenApi.Validations.ValidationRule - where T : Microsoft.OpenApi.Interfaces.IOpenApiElement { [System.Obsolete("Please use the other constructor and specify a name")] public ValidationRule(System.Action validate) { } @@ -1416,11 +1691,6 @@ namespace Microsoft.OpenApi.Validations.Rules public static Microsoft.OpenApi.Validations.ValidationRule UrlIsRequired { get; } } [Microsoft.OpenApi.Validations.Rules.OpenApiRule] - public static class OpenApiHeaderRules - { - public static Microsoft.OpenApi.Validations.ValidationRule HeaderMismatchedDataType { get; } - } - [Microsoft.OpenApi.Validations.Rules.OpenApiRule] public static class OpenApiInfoRules { public static Microsoft.OpenApi.Validations.ValidationRule InfoRequiredFields { get; } @@ -1430,10 +1700,12 @@ namespace Microsoft.OpenApi.Validations.Rules { public static Microsoft.OpenApi.Validations.ValidationRule LicenseRequiredFields { get; } } - [Microsoft.OpenApi.Validations.Rules.OpenApiRule] - public static class OpenApiMediaTypeRules + public static class OpenApiNonDefaultRules { + public static Microsoft.OpenApi.Validations.ValidationRule HeaderMismatchedDataType { get; } public static Microsoft.OpenApi.Validations.ValidationRule MediaTypeMismatchedDataType { get; } + public static Microsoft.OpenApi.Validations.ValidationRule ParameterMismatchedDataType { get; } + public static Microsoft.OpenApi.Validations.ValidationRule SchemaMismatchedDataType { get; } } [Microsoft.OpenApi.Validations.Rules.OpenApiRule] public static class OpenApiOAuthFlowRules @@ -1443,7 +1715,6 @@ namespace Microsoft.OpenApi.Validations.Rules [Microsoft.OpenApi.Validations.Rules.OpenApiRule] public static class OpenApiParameterRules { - public static Microsoft.OpenApi.Validations.ValidationRule ParameterMismatchedDataType { get; } public static Microsoft.OpenApi.Validations.ValidationRule ParameterRequiredFields { get; } public static Microsoft.OpenApi.Validations.ValidationRule PathParameterShouldBeInThePath { get; } public static Microsoft.OpenApi.Validations.ValidationRule RequiredMustBeTrueWhenInIsPath { get; } @@ -1473,7 +1744,6 @@ namespace Microsoft.OpenApi.Validations.Rules [Microsoft.OpenApi.Validations.Rules.OpenApiRule] public static class OpenApiSchemaRules { - public static Microsoft.OpenApi.Validations.ValidationRule SchemaMismatchedDataType { get; } public static Microsoft.OpenApi.Validations.ValidationRule ValidateSchemaDiscriminator { get; } public static bool TraverseSchemaElements(string discriminatorName, System.Collections.Generic.IList childSchema) { } public static bool ValidateChildSchemaAgainstDiscriminator(Microsoft.OpenApi.Models.OpenApiSchema schema, string discriminatorName) { } @@ -1536,8 +1806,7 @@ namespace Microsoft.OpenApi.Writers } public static class OpenApiWriterAnyExtensions { - public static void WriteAny(this Microsoft.OpenApi.Writers.IOpenApiWriter writer, T any) - where T : Microsoft.OpenApi.Any.IOpenApiAny { } + public static void WriteAny(this Microsoft.OpenApi.Writers.IOpenApiWriter writer, System.Text.Json.Nodes.JsonNode node) { } public static void WriteExtensions(this Microsoft.OpenApi.Writers.IOpenApiWriter writer, System.Collections.Generic.IDictionary extensions, Microsoft.OpenApi.OpenApiSpecVersion specVersion) { } } public abstract class OpenApiWriterBase : Microsoft.OpenApi.Writers.IOpenApiWriter @@ -1567,6 +1836,7 @@ namespace Microsoft.OpenApi.Writers public abstract void WriteRaw(string value); public abstract void WriteStartArray(); public abstract void WriteStartObject(); + public void WriteV2Examples(Microsoft.OpenApi.Writers.IOpenApiWriter writer, Microsoft.OpenApi.Models.OpenApiExample example, Microsoft.OpenApi.OpenApiSpecVersion version) { } public virtual void WriteValue(bool value) { } public virtual void WriteValue(System.DateTime value) { } public virtual void WriteValue(System.DateTimeOffset value) { } @@ -1582,15 +1852,14 @@ namespace Microsoft.OpenApi.Writers public static class OpenApiWriterExtensions { public static void WriteOptionalCollection(this Microsoft.OpenApi.Writers.IOpenApiWriter writer, string name, System.Collections.Generic.IEnumerable elements, System.Action action) { } - public static void WriteOptionalCollection(this Microsoft.OpenApi.Writers.IOpenApiWriter writer, string name, System.Collections.Generic.IEnumerable elements, System.Action action) - where T : Microsoft.OpenApi.Interfaces.IOpenApiElement { } + public static void 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) 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) where T : Microsoft.OpenApi.Interfaces.IOpenApiElement { } - public static void WriteOptionalObject(this Microsoft.OpenApi.Writers.IOpenApiWriter writer, string name, T value, System.Action action) - where T : Microsoft.OpenApi.Interfaces.IOpenApiElement { } + public static void WriteOptionalObject(this Microsoft.OpenApi.Writers.IOpenApiWriter writer, string name, T? value, System.Action action) { } public static void WriteProperty(this Microsoft.OpenApi.Writers.IOpenApiWriter writer, string name, string value) { } public static void WriteProperty(this Microsoft.OpenApi.Writers.IOpenApiWriter writer, string name, bool value, bool defaultValue = false) { } public static void WriteProperty(this Microsoft.OpenApi.Writers.IOpenApiWriter writer, string name, bool? value, bool defaultValue = false) { } @@ -1603,8 +1872,7 @@ namespace Microsoft.OpenApi.Writers public static void WriteRequiredMap(this Microsoft.OpenApi.Writers.IOpenApiWriter writer, string name, System.Collections.Generic.IDictionary elements, System.Action action) { } public static void WriteRequiredMap(this Microsoft.OpenApi.Writers.IOpenApiWriter writer, string name, System.Collections.Generic.IDictionary elements, System.Action action) where T : Microsoft.OpenApi.Interfaces.IOpenApiElement { } - public static void WriteRequiredObject(this Microsoft.OpenApi.Writers.IOpenApiWriter writer, string name, T value, System.Action action) - where T : Microsoft.OpenApi.Interfaces.IOpenApiElement { } + public static void WriteRequiredObject(this Microsoft.OpenApi.Writers.IOpenApiWriter writer, string name, T? value, System.Action action) { } public static void WriteRequiredProperty(this Microsoft.OpenApi.Writers.IOpenApiWriter writer, string name, string value) { } } public class OpenApiWriterSettings diff --git a/test/Microsoft.OpenApi.Tests/PublicApi/PublicApiTests.cs b/test/Microsoft.OpenApi.Tests/PublicApi/PublicApiTests.cs index deb8e30be..ebaad90e4 100755 --- a/test/Microsoft.OpenApi.Tests/PublicApi/PublicApiTests.cs +++ b/test/Microsoft.OpenApi.Tests/PublicApi/PublicApiTests.cs @@ -1,10 +1,10 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. using System.IO; +using PublicApiGenerator; using Xunit; using Xunit.Abstractions; -using PublicApiGenerator; namespace Microsoft.OpenApi.Tests.PublicApi { diff --git a/test/Microsoft.OpenApi.Tests/Services/OpenApiUrlTreeNodeTests.cs b/test/Microsoft.OpenApi.Tests/Services/OpenApiUrlTreeNodeTests.cs index 4da7a337f..09ef9b04d 100644 --- a/test/Microsoft.OpenApi.Tests/Services/OpenApiUrlTreeNodeTests.cs +++ b/test/Microsoft.OpenApi.Tests/Services/OpenApiUrlTreeNodeTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. using System; diff --git a/test/Microsoft.OpenApi.Tests/Services/OpenApiValidatorTests.cs b/test/Microsoft.OpenApi.Tests/Services/OpenApiValidatorTests.cs index d027f40b8..e5fcc346f 100644 --- a/test/Microsoft.OpenApi.Tests/Services/OpenApiValidatorTests.cs +++ b/test/Microsoft.OpenApi.Tests/Services/OpenApiValidatorTests.cs @@ -3,6 +3,8 @@ using System; using System.Collections.Generic; +using System.Text.Json; +using System.Text.Json.Nodes; using FluentAssertions; using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Interfaces; @@ -28,12 +30,13 @@ public void ResponseMustHaveADescription() Title = "foo", Version = "1.2.2" }; - openApiDocument.Paths = new(); - openApiDocument.Paths.Add( - "/test", - new() + openApiDocument.Paths = new() + { { - Operations = + "/test", + new() + { + Operations = { [OperationType.Get] = new() { @@ -43,7 +46,9 @@ public void ResponseMustHaveADescription() } } } - }); + } + } + }; var validator = new OpenApiValidator(ValidationRuleSet.GetDefaultRuleSet()); var walker = new OpenApiWalker(validator); @@ -96,11 +101,11 @@ public void ValidateCustomExtension() { var ruleset = ValidationRuleSet.GetDefaultRuleSet(); - ruleset.Add( - new ValidationRule("FooExtensionRule", + ruleset.Add(typeof(OpenApiAny), + new ValidationRule("FooExtensionRule", (context, item) => { - if (item.Bar == "hey") + if (item.Node["Bar"].ToString() == "hey") { context.AddError(new("FooExtensionRule", context.PathString, "Don't say hey")); } @@ -122,7 +127,9 @@ public void ValidateCustomExtension() Baz = "baz" }; - openApiDocument.Info.Extensions.Add("x-foo", fooExtension); + var extensionNode = JsonSerializer.Serialize(fooExtension); + var jsonNode = JsonNode.Parse(extensionNode); + openApiDocument.Info.Extensions.Add("x-foo", new OpenApiAny(jsonNode)); var validator = new OpenApiValidator(ruleset); var walker = new OpenApiWalker(validator); @@ -138,8 +145,8 @@ public void ValidateCustomExtension() [Fact] public void RemoveRuleByName_Invalid() { - Assert.Throws(() => new ValidationRule(null, (vc, oaa) => { })); - Assert.Throws(() => new ValidationRule(string.Empty, (vc, oaa) => { })); + Assert.Throws(() => new ValidationRule(null, (vc, oaa) => { })); + Assert.Throws(() => new ValidationRule(string.Empty, (vc, oaa) => { })); } [Fact] diff --git a/test/Microsoft.OpenApi.Tests/Validations/OpenApiExternalDocsValidationTests.cs b/test/Microsoft.OpenApi.Tests/Validations/OpenApiExternalDocsValidationTests.cs index 001e5da92..aebb11c0a 100644 --- a/test/Microsoft.OpenApi.Tests/Validations/OpenApiExternalDocsValidationTests.cs +++ b/test/Microsoft.OpenApi.Tests/Validations/OpenApiExternalDocsValidationTests.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; @@ -23,9 +23,9 @@ public void ValidateUrlIsRequiredInExternalDocs() // Assert - var result = !errors.Any(); + bool result = errors.Any(); - Assert.False(result); + Assert.True(result); Assert.NotNull(errors); var error = Assert.Single(errors); Assert.Equal(String.Format(SRResource.Validation_FieldIsRequired, "url", "External Documentation"), error.Message); diff --git a/test/Microsoft.OpenApi.Tests/Validations/OpenApiHeaderValidationTests.cs b/test/Microsoft.OpenApi.Tests/Validations/OpenApiHeaderValidationTests.cs index 240b8b833..25423ab1f 100644 --- a/test/Microsoft.OpenApi.Tests/Validations/OpenApiHeaderValidationTests.cs +++ b/test/Microsoft.OpenApi.Tests/Validations/OpenApiHeaderValidationTests.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; +using System.Text.Json.Nodes; using FluentAssertions; using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Models; @@ -22,15 +23,18 @@ public void ValidateExampleShouldNotHaveDataTypeMismatchForSimpleSchema() var header = new OpenApiHeader { Required = true, - Example = new OpenApiInteger(55), - Schema = new() - { - Type = "string", + Example = 55, + Schema = new OpenApiSchema + { + Type = JsonSchemaType.String } }; // Act - var validator = new OpenApiValidator(ValidationRuleSet.GetDefaultRuleSet()); + var defaultRuleSet = ValidationRuleSet.GetDefaultRuleSet(); + defaultRuleSet.Add(typeof(OpenApiHeader), OpenApiNonDefaultRules.HeaderMismatchedDataType); + var validator = new OpenApiValidator(defaultRuleSet); + var walker = new OpenApiWalker(validator); walker.Walk(header); @@ -40,14 +44,6 @@ public void ValidateExampleShouldNotHaveDataTypeMismatchForSimpleSchema() // Assert result.Should().BeFalse(); - warnings.Select(e => e.Message).Should().BeEquivalentTo(new[] - { - RuleHelpers.DataTypeMismatchedErrorMessage - }); - warnings.Select(e => e.Pointer).Should().BeEquivalentTo(new[] - { - "#/example", - }); } [Fact] @@ -59,46 +55,42 @@ public void ValidateExamplesShouldNotHaveDataTypeMismatchForSimpleSchema() var header = new OpenApiHeader { Required = true, - Schema = new() + Schema = new OpenApiSchema { - Type = "object", - AdditionalProperties = new() + Type = JsonSchemaType.Object, + AdditionalProperties = new OpenApiSchema { - Type = "integer", + Type = JsonSchemaType.Integer } }, Examples = + { + ["example0"] = new() { - ["example0"] = new() - { - Value = new OpenApiString("1"), - }, - ["example1"] = new() - { - Value = new OpenApiObject - { - ["x"] = new OpenApiInteger(2), - ["y"] = new OpenApiString("20"), - ["z"] = new OpenApiString("200") - } - }, - ["example2"] = new() + Value = "1", + }, + ["example1"] = new() + { + Value = new JsonObject() { - Value = - new OpenApiArray - { - new OpenApiInteger(3) - } - }, - ["example3"] = new() + ["x"] = 2, + ["y"] = "20", + ["z"] = "200" + } + }, + ["example2"] = new() + { + Value = new JsonArray(){3} + }, + ["example3"] = new() + { + Value = new JsonObject() { - Value = new OpenApiObject - { - ["x"] = new OpenApiInteger(4), - ["y"] = new OpenApiInteger(40), - } - }, - } + ["x"] = 4, + ["y"] = 40 + } + }, + } }; // Act @@ -110,21 +102,7 @@ public void ValidateExamplesShouldNotHaveDataTypeMismatchForSimpleSchema() var result = !warnings.Any(); // Assert - result.Should().BeFalse(); - warnings.Select(e => e.Message).Should().BeEquivalentTo(new[] - { - RuleHelpers.DataTypeMismatchedErrorMessage, - RuleHelpers.DataTypeMismatchedErrorMessage, - RuleHelpers.DataTypeMismatchedErrorMessage, - }); - warnings.Select(e => e.Pointer).Should().BeEquivalentTo(new[] - { - // #enum/0 is not an error since the spec allows - // representing an object using a string. - "#/examples/example1/value/y", - "#/examples/example1/value/z", - "#/examples/example2/value" - }); + result.Should().BeTrue(); } } } diff --git a/test/Microsoft.OpenApi.Tests/Validations/OpenApiMediaTypeValidationTests.cs b/test/Microsoft.OpenApi.Tests/Validations/OpenApiMediaTypeValidationTests.cs index 550b01022..51a8e1795 100644 --- a/test/Microsoft.OpenApi.Tests/Validations/OpenApiMediaTypeValidationTests.cs +++ b/test/Microsoft.OpenApi.Tests/Validations/OpenApiMediaTypeValidationTests.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; +using System.Text.Json.Nodes; using FluentAssertions; using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Models; @@ -21,10 +22,10 @@ public void ValidateExampleShouldNotHaveDataTypeMismatchForSimpleSchema() IEnumerable warnings; var mediaType = new OpenApiMediaType { - Example = new OpenApiInteger(55), + Example = 55, Schema = new() { - Type = "string", + Type = JsonSchemaType.String, } }; @@ -38,15 +39,7 @@ public void ValidateExampleShouldNotHaveDataTypeMismatchForSimpleSchema() var result = !warnings.Any(); // Assert - result.Should().BeFalse(); - warnings.Select(e => e.Message).Should().BeEquivalentTo(new[] - { - RuleHelpers.DataTypeMismatchedErrorMessage - }); - warnings.Select(e => e.Pointer).Should().BeEquivalentTo(new[] - { - "#/example", - }); + result.Should().BeTrue(); } [Fact] @@ -59,41 +52,37 @@ public void ValidateExamplesShouldNotHaveDataTypeMismatchForSimpleSchema() { Schema = new() { - Type = "object", + Type = JsonSchemaType.Object, AdditionalProperties = new() { - Type = "integer", + Type = JsonSchemaType.Integer, } }, Examples = { ["example0"] = new() { - Value = new OpenApiString("1"), + Value = "1", }, ["example1"] = new() { - Value = new OpenApiObject - { - ["x"] = new OpenApiInteger(2), - ["y"] = new OpenApiString("20"), - ["z"] = new OpenApiString("200") + Value = new JsonObject() + { + ["x"] = 2, + ["y"] = "20", + ["z"] = "200" } }, ["example2"] = new() { - Value = - new OpenApiArray - { - new OpenApiInteger(3) - } + Value = new JsonArray(){3} }, ["example3"] = new() { - Value = new OpenApiObject + Value = new JsonObject() { - ["x"] = new OpenApiInteger(4), - ["y"] = new OpenApiInteger(40), + ["x"] = 4, + ["y"] = 40 } }, } @@ -109,21 +98,7 @@ public void ValidateExamplesShouldNotHaveDataTypeMismatchForSimpleSchema() var result = !warnings.Any(); // Assert - result.Should().BeFalse(); - warnings.Select(e => e.Message).Should().BeEquivalentTo(new[] - { - RuleHelpers.DataTypeMismatchedErrorMessage, - RuleHelpers.DataTypeMismatchedErrorMessage, - RuleHelpers.DataTypeMismatchedErrorMessage, - }); - warnings.Select(e => e.Pointer).Should().BeEquivalentTo(new[] - { - // #enum/0 is not an error since the spec allows - // representing an object using a string. - "#/examples/example1/value/y", - "#/examples/example1/value/z", - "#/examples/example2/value" - }); + result.Should().BeTrue(); } } } diff --git a/test/Microsoft.OpenApi.Tests/Validations/OpenApiParameterValidationTests.cs b/test/Microsoft.OpenApi.Tests/Validations/OpenApiParameterValidationTests.cs index bc8f1a517..3f380c7f1 100644 --- a/test/Microsoft.OpenApi.Tests/Validations/OpenApiParameterValidationTests.cs +++ b/test/Microsoft.OpenApi.Tests/Validations/OpenApiParameterValidationTests.cs @@ -1,9 +1,9 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. -using System; using System.Collections.Generic; using System.Linq; +using System.Text.Json.Nodes; using FluentAssertions; using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Extensions; @@ -21,8 +21,8 @@ public class OpenApiParameterValidationTests public void ValidateFieldIsRequiredInParameter() { // Arrange - var nameError = String.Format(SRResource.Validation_FieldIsRequired, "name", "parameter"); - var inError = String.Format(SRResource.Validation_FieldIsRequired, "in", "parameter"); + var nameError = string.Format(SRResource.Validation_FieldIsRequired, "name", "parameter"); + var inError = string.Format(SRResource.Validation_FieldIsRequired, "in", "parameter"); var parameter = new OpenApiParameter(); // Act @@ -71,10 +71,10 @@ public void ValidateExampleShouldNotHaveDataTypeMismatchForSimpleSchema() Name = "parameter1", In = ParameterLocation.Path, Required = true, - Example = new OpenApiInteger(55), + Example = 55, Schema = new() { - Type = "string", + Type = JsonSchemaType.String, } }; @@ -88,15 +88,7 @@ public void ValidateExampleShouldNotHaveDataTypeMismatchForSimpleSchema() var result = !warnings.Any(); // Assert - result.Should().BeFalse(); - warnings.Select(e => e.Message).Should().BeEquivalentTo(new[] - { - RuleHelpers.DataTypeMismatchedErrorMessage - }); - warnings.Select(e => e.Pointer).Should().BeEquivalentTo(new[] - { - "#/{parameter1}/example", - }); + result.Should().BeTrue(); } [Fact] @@ -112,48 +104,47 @@ public void ValidateExamplesShouldNotHaveDataTypeMismatchForSimpleSchema() Required = true, Schema = new() { - Type = "object", + Type = JsonSchemaType.Object, AdditionalProperties = new() { - Type = "integer", + Type = JsonSchemaType.Integer, } }, Examples = { ["example0"] = new() { - Value = new OpenApiString("1"), + Value = "1", }, ["example1"] = new() { - Value = new OpenApiObject - { - ["x"] = new OpenApiInteger(2), - ["y"] = new OpenApiString("20"), - ["z"] = new OpenApiString("200") + Value = new JsonObject() + { + ["x"] = 2, + ["y"] = "20", + ["z"] = "200" } }, ["example2"] = new() { - Value = - new OpenApiArray - { - new OpenApiInteger(3) - } + Value = new JsonArray(){3} }, ["example3"] = new() { - Value = new OpenApiObject + Value = new JsonObject() { - ["x"] = new OpenApiInteger(4), - ["y"] = new OpenApiInteger(40), + ["x"] = 4, + ["y"] = 40 } }, } }; // Act - var validator = new OpenApiValidator(ValidationRuleSet.GetDefaultRuleSet()); + var defaultRuleSet = ValidationRuleSet.GetDefaultRuleSet(); + defaultRuleSet.Add(typeof(OpenApiParameter), OpenApiNonDefaultRules.ParameterMismatchedDataType); + + var validator = new OpenApiValidator(defaultRuleSet); validator.Enter("{parameter1}"); var walker = new OpenApiWalker(validator); walker.Walk(parameter); @@ -163,20 +154,6 @@ public void ValidateExamplesShouldNotHaveDataTypeMismatchForSimpleSchema() // Assert result.Should().BeFalse(); - warnings.Select(e => e.Message).Should().BeEquivalentTo(new[] - { - RuleHelpers.DataTypeMismatchedErrorMessage, - RuleHelpers.DataTypeMismatchedErrorMessage, - RuleHelpers.DataTypeMismatchedErrorMessage, - }); - warnings.Select(e => e.Pointer).Should().BeEquivalentTo(new[] - { - // #enum/0 is not an error since the spec allows - // representing an object using a string. - "#/{parameter1}/examples/example1/value/y", - "#/{parameter1}/examples/example1/value/z", - "#/{parameter1}/examples/example2/value" - }); } [Fact] @@ -192,7 +169,7 @@ public void PathParameterNotInThePathShouldReturnAnError() Required = true, Schema = new() { - Type = "string", + Type = JsonSchemaType.String, } }; @@ -230,7 +207,7 @@ public void PathParameterInThePathShouldBeOk() Required = true, Schema = new() { - Type = "string", + Type = JsonSchemaType.String, } }; diff --git a/test/Microsoft.OpenApi.Tests/Validations/OpenApiReferenceValidationTests.cs b/test/Microsoft.OpenApi.Tests/Validations/OpenApiReferenceValidationTests.cs index 11e5edc9e..b7597cb31 100644 --- a/test/Microsoft.OpenApi.Tests/Validations/OpenApiReferenceValidationTests.cs +++ b/test/Microsoft.OpenApi.Tests/Validations/OpenApiReferenceValidationTests.cs @@ -1,10 +1,10 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. +using System; using System.Collections.Generic; using System.Linq; using Microsoft.OpenApi.Extensions; -using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Validations; using Xunit; @@ -20,7 +20,7 @@ public void ReferencedSchemaShouldOnlyBeValidatedOnce() var sharedSchema = new OpenApiSchema { - Type = "string", + Type = JsonSchemaType.String, Reference = new() { Id = "test" @@ -31,9 +31,9 @@ public void ReferencedSchemaShouldOnlyBeValidatedOnce() var document = new OpenApiDocument(); document.Components = new() { - Schemas = new Dictionary + Schemas = new Dictionary() { - [sharedSchema.Reference.Id] = sharedSchema + ["test"] = sharedSchema } }; @@ -64,40 +64,18 @@ public void ReferencedSchemaShouldOnlyBeValidatedOnce() }; // Act - var errors = document.Validate(new() { new AlwaysFailRule() }); - - // Assert - Assert.True(errors.Count() == 1); - } - - [Fact] - public void UnresolvedReferenceSchemaShouldNotBeValidated() - { - // Arrange - var sharedSchema = new OpenApiSchema + var rules = new Dictionary>() { - Type = "string", - Reference = new() - { - Id = "test" - }, - UnresolvedReference = true - }; - - var document = new OpenApiDocument(); - document.Components = new() - { - Schemas = new Dictionary - { - [sharedSchema.Reference.Id] = sharedSchema + { typeof(OpenApiSchema), + new List() { new AlwaysFailRule() } } }; - // Act - var errors = document.Validate(new() { new AlwaysFailRule() }); + var errors = document.Validate(new ValidationRuleSet(rules)); + // Assert - Assert.True(errors.Count() == 0); + Assert.True(errors.Count() == 1); } [Fact] @@ -107,6 +85,7 @@ public void UnresolvedSchemaReferencedShouldNotBeValidated() var sharedSchema = new OpenApiSchema { + Type = JsonSchemaType.String, Reference = new() { Id = "test" @@ -143,14 +122,21 @@ public void UnresolvedSchemaReferencedShouldNotBeValidated() }; // Act - var errors = document.Validate(new() { new AlwaysFailRule() }); + var rules = new Dictionary>() + { + { typeof(OpenApiSchema), + new List() { new AlwaysFailRule() } + } + }; + + var errors = document.Validate(new ValidationRuleSet(rules)); // Assert - Assert.True(errors.Count() == 0); + Assert.True(!errors.Any()); } } - public class AlwaysFailRule : ValidationRule where T : IOpenApiElement + public class AlwaysFailRule : ValidationRule { public AlwaysFailRule() : base("AlwaysFailRule", (c, _) => c.CreateError("x", "y")) { diff --git a/test/Microsoft.OpenApi.Tests/Validations/OpenApiSchemaValidationTests.cs b/test/Microsoft.OpenApi.Tests/Validations/OpenApiSchemaValidationTests.cs index 7f3caa20a..d2aa19590 100644 --- a/test/Microsoft.OpenApi.Tests/Validations/OpenApiSchemaValidationTests.cs +++ b/test/Microsoft.OpenApi.Tests/Validations/OpenApiSchemaValidationTests.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Text.Json.Nodes; using FluentAssertions; using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Models; @@ -11,6 +12,7 @@ using Microsoft.OpenApi.Services; using Microsoft.OpenApi.Validations.Rules; using Xunit; +using Microsoft.OpenApi.Extensions; namespace Microsoft.OpenApi.Validations.Tests { @@ -24,8 +26,8 @@ public void ValidateDefaultShouldNotHaveDataTypeMismatchForSimpleSchema() IEnumerable warnings; var schema = new OpenApiSchema { - Default = new OpenApiInteger(55), - Type = "string", + Default = 55, + Type = JsonSchemaType.String, }; // Act @@ -37,15 +39,7 @@ public void ValidateDefaultShouldNotHaveDataTypeMismatchForSimpleSchema() var result = !warnings.Any(); // Assert - result.Should().BeFalse(); - warnings.Select(e => e.Message).Should().BeEquivalentTo(new[] - { - RuleHelpers.DataTypeMismatchedErrorMessage - }); - warnings.Select(e => e.Pointer).Should().BeEquivalentTo(new[] - { - "#/default", - }); + result.Should().BeTrue(); } [Fact] @@ -55,9 +49,9 @@ public void ValidateExampleAndDefaultShouldNotHaveDataTypeMismatchForSimpleSchem IEnumerable warnings; var schema = new OpenApiSchema { - Example = new OpenApiLong(55), - Default = new OpenApiPassword("1234"), - Type = "string", + Example = 55, + Default = "1234", + Type = JsonSchemaType.String, }; // Act @@ -66,20 +60,11 @@ public void ValidateExampleAndDefaultShouldNotHaveDataTypeMismatchForSimpleSchem walker.Walk(schema); warnings = validator.Warnings; - var result = !warnings.Any(); + bool result = !warnings.Any(); + var expectedWarnings = warnings.Select(e => e.Message).ToList(); // Assert - result.Should().BeFalse(); - warnings.Select(e => e.Message).Should().BeEquivalentTo(new[] - { - RuleHelpers.DataTypeMismatchedErrorMessage, - RuleHelpers.DataTypeMismatchedErrorMessage - }); - warnings.Select(e => e.Pointer).Should().BeEquivalentTo(new[] - { - "#/default", - "#/example", - }); + result.Should().BeTrue(); } [Fact] @@ -87,31 +72,28 @@ public void ValidateEnumShouldNotHaveDataTypeMismatchForSimpleSchema() { // Arrange IEnumerable warnings; - var schema = new OpenApiSchema + var schema = new OpenApiSchema() { - Enum = + Enum = { - new OpenApiString("1"), - new OpenApiObject + new OpenApiAny("1").Node, + new OpenApiAny(new JsonObject() { - ["x"] = new OpenApiInteger(2), - ["y"] = new OpenApiString("20"), - ["z"] = new OpenApiString("200") - }, - new OpenApiArray + ["x"] = 2, + ["y"] = "20", + ["z"] = "200" + }).Node, + new OpenApiAny(new JsonArray() { 3 }).Node, + new OpenApiAny(new JsonObject() { - new OpenApiInteger(3) - }, - new OpenApiObject - { - ["x"] = new OpenApiInteger(4), - ["y"] = new OpenApiInteger(40), - }, + ["x"] = 4, + ["y"] = 40, + }).Node }, - Type = "object", + Type = JsonSchemaType.Object, AdditionalProperties = new() { - Type = "integer", + Type = JsonSchemaType.Integer } }; @@ -124,21 +106,7 @@ public void ValidateEnumShouldNotHaveDataTypeMismatchForSimpleSchema() var result = !warnings.Any(); // Assert - result.Should().BeFalse(); - warnings.Select(e => e.Message).Should().BeEquivalentTo(new[] - { - RuleHelpers.DataTypeMismatchedErrorMessage, - RuleHelpers.DataTypeMismatchedErrorMessage, - RuleHelpers.DataTypeMismatchedErrorMessage, - }); - warnings.Select(e => e.Pointer).Should().BeEquivalentTo(new[] - { - // #enum/0 is not an error since the spec allows - // representing an object using a string. - "#/enum/1/y", - "#/enum/1/z", - "#/enum/2" - }); + result.Should().BeTrue(); } [Fact] @@ -148,89 +116,75 @@ public void ValidateDefaultShouldNotHaveDataTypeMismatchForComplexSchema() IEnumerable warnings; var schema = new OpenApiSchema { - Type = "object", + Type = JsonSchemaType.Object, Properties = { ["property1"] = new() { - Type = "array", + Type = JsonSchemaType.Array, Items = new() { - Type = "integer", + Type = JsonSchemaType.Integer, Format = "int64" } }, ["property2"] = new() { - Type = "array", + Type = JsonSchemaType.Array, Items = new() { - Type = "object", + Type = JsonSchemaType.Object, AdditionalProperties = new() { - Type = "boolean" + Type = JsonSchemaType.Boolean } } }, ["property3"] = new() { - Type = "string", + Type = JsonSchemaType.String, Format = "password" }, ["property4"] = new() { - Type = "string" + Type = JsonSchemaType.String } }, - Default = new OpenApiObject + Default = new JsonObject() { - ["property1"] = new OpenApiArray + ["property1"] = new JsonArray() { - new OpenApiInteger(12), - new OpenApiLong(13), - new OpenApiString("1"), + 12, + 13, + "1", }, - ["property2"] = new OpenApiArray + ["property2"] = new JsonArray() { - new OpenApiInteger(2), - new OpenApiObject + 2, + new JsonObject() { - ["x"] = new OpenApiBoolean(true), - ["y"] = new OpenApiBoolean(false), - ["z"] = new OpenApiString("1234"), + ["x"] = true, + ["y"] = false, + ["z"] = "1234", } }, - ["property3"] = new OpenApiPassword("123"), - ["property4"] = new OpenApiDateTime(DateTime.UtcNow) + ["property3"] = "123", + ["property4"] = DateTime.UtcNow } }; // Act - var validator = new OpenApiValidator(ValidationRuleSet.GetDefaultRuleSet()); + var defaultRuleSet = ValidationRuleSet.GetDefaultRuleSet(); + defaultRuleSet.Add(typeof(OpenApiSchema), OpenApiNonDefaultRules.SchemaMismatchedDataType); + var validator = new OpenApiValidator(defaultRuleSet); var walker = new OpenApiWalker(validator); walker.Walk(schema); warnings = validator.Warnings; - var result = !warnings.Any(); + bool result = !warnings.Any(); // Assert result.Should().BeFalse(); - warnings.Select(e => e.Message).Should().BeEquivalentTo(new[] - { - RuleHelpers.DataTypeMismatchedErrorMessage, - RuleHelpers.DataTypeMismatchedErrorMessage, - RuleHelpers.DataTypeMismatchedErrorMessage, - RuleHelpers.DataTypeMismatchedErrorMessage, - RuleHelpers.DataTypeMismatchedErrorMessage, - }); - warnings.Select(e => e.Pointer).Should().BeEquivalentTo(new[] - { - "#/default/property1/0", - "#/default/property1/2", - "#/default/property2/0", - "#/default/property2/1/z", - "#/default/property4", - }); } [Fact] @@ -244,7 +198,7 @@ public void ValidateSchemaRequiredFieldListMustContainThePropertySpecifiedInTheD "schema1", new OpenApiSchema { - Type = "object", + Type = JsonSchemaType.Object, Discriminator = new() { PropertyName = "property1" }, Reference = new() { Id = "schema1" } } @@ -263,7 +217,7 @@ public void ValidateSchemaRequiredFieldListMustContainThePropertySpecifiedInTheD result.Should().BeFalse(); errors.Should().BeEquivalentTo(new List { - new(nameof(OpenApiSchemaRules.ValidateSchemaDiscriminator),"#/schemas/schema1/discriminator", + new OpenApiValidatorError(nameof(OpenApiSchemaRules.ValidateSchemaDiscriminator),"#/schemas/schema1/discriminator", string.Format(SRResource.Validation_SchemaRequiredFieldListMustContainThePropertySpecifiedInTheDiscriminator, "schema1", "property1")) }); @@ -281,7 +235,7 @@ public void ValidateOneOfSchemaPropertyNameContainsPropertySpecifiedInTheDiscrim "Person", new OpenApiSchema { - Type = "array", + Type = JsonSchemaType.Array, Discriminator = new() { PropertyName = "type" @@ -296,7 +250,7 @@ public void ValidateOneOfSchemaPropertyNameContainsPropertySpecifiedInTheDiscrim "type", new OpenApiSchema { - Type = "array" + Type = JsonSchemaType.Array } } }, diff --git a/test/Microsoft.OpenApi.Tests/Validations/OpenApiTagValidationTests.cs b/test/Microsoft.OpenApi.Tests/Validations/OpenApiTagValidationTests.cs index b1362664f..2bdec4ba2 100644 --- a/test/Microsoft.OpenApi.Tests/Validations/OpenApiTagValidationTests.cs +++ b/test/Microsoft.OpenApi.Tests/Validations/OpenApiTagValidationTests.cs @@ -43,7 +43,7 @@ public void ValidateExtensionNameStartsWithXDashInTag() { Name = "tag" }; - tag.Extensions.Add("tagExt", new OpenApiString("value")); + tag.Extensions.Add("tagExt", new OpenApiAny("value")); // Act var validator = new OpenApiValidator(ValidationRuleSet.GetDefaultRuleSet()); diff --git a/test/Microsoft.OpenApi.Tests/Validations/ValidationRuleSetTests.cs b/test/Microsoft.OpenApi.Tests/Validations/ValidationRuleSetTests.cs index 3c302be13..6b4a920cf 100644 --- a/test/Microsoft.OpenApi.Tests/Validations/ValidationRuleSetTests.cs +++ b/test/Microsoft.OpenApi.Tests/Validations/ValidationRuleSetTests.cs @@ -1,7 +1,10 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. +using System; +using System.Collections.Generic; using System.Linq; +using Microsoft.OpenApi.Models; using System.Reflection; using Microsoft.OpenApi.Validations.Rules; using Xunit; @@ -10,35 +13,182 @@ namespace Microsoft.OpenApi.Validations.Tests { public class ValidationRuleSetTests { + private readonly ValidationRule _contactValidationRule = new ValidationRule(nameof(_contactValidationRule), + (context, item) => { }); + + private readonly ValidationRule _headerValidationRule = new ValidationRule(nameof(_headerValidationRule), + (context, item) => { }); + + private readonly ValidationRule _parameterValidationRule = new ValidationRule(nameof(_parameterValidationRule), + (context, item) => { }); + + private readonly IDictionary> _rulesDictionary; + + public ValidationRuleSetTests() + { + _rulesDictionary = new Dictionary>() + { + {typeof(OpenApiContact), new List { _contactValidationRule } }, + {typeof(OpenApiHeader), new List { _headerValidationRule } }, + {typeof(OpenApiParameter), new List { _parameterValidationRule } } + }; + } + + [Fact] + public void RuleSetConstructorsReturnsTheCorrectRules() + { + // Arrange & Act + var ruleSet_1 = ValidationRuleSet.GetDefaultRuleSet(); + var ruleSet_2 = new ValidationRuleSet(ValidationRuleSet.GetDefaultRuleSet()); + var ruleSet_3 = new ValidationRuleSet(_rulesDictionary); + var ruleSet_4 = new ValidationRuleSet(); + + // Assert + Assert.NotNull(ruleSet_1?.Rules); + Assert.NotNull(ruleSet_2?.Rules); + Assert.NotNull(ruleSet_3?.Rules); + Assert.NotNull(ruleSet_4); + + Assert.NotEmpty(ruleSet_1.Rules); + Assert.NotEmpty(ruleSet_2.Rules); + Assert.NotEmpty(ruleSet_3.Rules); + Assert.Empty(ruleSet_4.Rules); + + // Update the number if you add new default rule(s). + Assert.Equal(19, ruleSet_1.Rules.Count); + Assert.Equal(19, ruleSet_2.Rules.Count); + Assert.Equal(3, ruleSet_3.Rules.Count); + } + [Fact] - public void DefaultRuleSetReturnsTheCorrectRules() + public void RemoveValidatioRuleGivenTheValidationRuleWorks() { // Arrange - var ruleSet = new ValidationRuleSet(); + var ruleSet = new ValidationRuleSet(_rulesDictionary); + var responseValidationRule = new ValidationRule("ValidateResponses", (context, item) => { }); + + // Act and Assert + Assert.True(ruleSet.Remove(_contactValidationRule)); + Assert.False(ruleSet.Rules.Contains(_contactValidationRule)); + Assert.False(ruleSet.Remove(_contactValidationRule)); // rule already removed + } + + [Fact] + public void RemoveValidationRuleGivenTheKeyAndValidationRuleWorks() + { + // Arrange + var ruleSet = new ValidationRuleSet(_rulesDictionary); // Act + ruleSet.Remove(typeof(OpenApiContact), _contactValidationRule); + ruleSet.Remove("parameter"); // validation rule not in parameter key; shouldn't remove + ruleSet.Remove("foo"); // key does not exist; shouldn't remove + var rules = ruleSet.Rules; // Assert - Assert.NotNull(rules); - Assert.Empty(rules); + Assert.False(rules.Contains(_contactValidationRule)); + Assert.True(rules.Contains(_headerValidationRule)); + Assert.True(rules.Contains(_parameterValidationRule)); } [Fact] - public void DefaultRuleSetPropertyReturnsTheCorrectRules() + public void RemoveRulesGivenAKeyWorks() { - // Arrange & Act - var ruleSet = ValidationRuleSet.GetDefaultRuleSet(); - Assert.NotNull(ruleSet); // guard + // Arrange + var ruleSet = new ValidationRuleSet(_rulesDictionary); + var responseValidationRule = new ValidationRule("ValidateResponses", (context, item) => { }); + ruleSet.Add(typeof(OpenApiResponse), new List { responseValidationRule }); + Assert.True(ruleSet.ContainsKey(typeof(OpenApiResponse))); + Assert.True(ruleSet.Rules.Contains(responseValidationRule)); // guard - var rules = ruleSet.Rules; + // Act + ruleSet.Remove(typeof(OpenApiResponse)); // Assert - Assert.NotNull(rules); - Assert.NotEmpty(rules); + Assert.False(ruleSet.ContainsKey(typeof(OpenApiResponse))); + } - // Update the number if you add new default rule(s). - Assert.Equal(23, rules.Count); + [Fact] + public void AddNewValidationRuleWorks() + { + // Arrange + var ruleSet = new ValidationRuleSet(_rulesDictionary); + var responseValidationRule = new ValidationRule("ValidateResponses", (context, item) => { }); + var tagValidationRule = new ValidationRule("ValidateTags", (context, item) => { }); + var pathsValidationRule = new ValidationRule("ValidatePaths", (context, item) => { }); + + // Act + ruleSet.Add(typeof(OpenApiResponse), new List { responseValidationRule }); + ruleSet.Add(typeof(OpenApiTag), new List { tagValidationRule }); + var rulesDictionary = new Dictionary>() + { + {typeof(OpenApiPaths), new List { pathsValidationRule } } + }; + + ValidationRuleSet.AddValidationRules(ruleSet, rulesDictionary); + + // Assert + Assert.True(ruleSet.ContainsKey(typeof(OpenApiResponse))); + Assert.True(ruleSet.ContainsKey(typeof(OpenApiTag))); + Assert.True(ruleSet.ContainsKey(typeof(OpenApiPaths))); + Assert.True(ruleSet.Rules.Contains(responseValidationRule)); + Assert.True(ruleSet.Rules.Contains(tagValidationRule)); + Assert.True(ruleSet.Rules.Contains(pathsValidationRule)); + } + + [Fact] + public void UpdateValidationRuleWorks() + { + // Arrange + var ruleSet = new ValidationRuleSet(_rulesDictionary); + var responseValidationRule = new ValidationRule("ValidateResponses", (context, item) => { }); + ruleSet.Add(typeof(OpenApiResponse), new List { responseValidationRule }); + + // Act + var pathsValidationRule = new ValidationRule("ValidatePaths", (context, item) => { }); + ruleSet.Update(typeof(OpenApiResponse), pathsValidationRule, responseValidationRule); + + // Assert + Assert.True(ruleSet.Contains(typeof(OpenApiResponse), pathsValidationRule)); + Assert.False(ruleSet.Contains(typeof(OpenApiResponse), responseValidationRule)); + } + + [Fact] + public void TryGetValueWorks() + { + // Arrange + var ruleSet = new ValidationRuleSet(_rulesDictionary); + + // Act + ruleSet.TryGetValue(typeof(OpenApiContact), out var validationRules); + + // Assert + Assert.True(validationRules.Any()); + Assert.True(validationRules.Contains(_contactValidationRule)); + } + + [Fact] + public void ClearAllRulesWorks() + { + // Arrange + var ruleSet = new ValidationRuleSet(); + var tagValidationRule = new ValidationRule("ValidateTags", (context, item) => { }); + var pathsValidationRule = new ValidationRule("ValidatePaths", (context, item) => { }); + var rulesDictionary = new Dictionary>() + { + {typeof(OpenApiPaths), new List { pathsValidationRule } }, + {typeof(OpenApiTag), new List { tagValidationRule } } + }; + + ValidationRuleSet.AddValidationRules(ruleSet, rulesDictionary); + Assert.NotEmpty(ruleSet.Rules); + + // Act + ruleSet.Clear(); + + // Assert + Assert.Empty(ruleSet.Rules); } [Fact] diff --git a/test/Microsoft.OpenApi.Tests/Visitors/InheritanceTests.cs b/test/Microsoft.OpenApi.Tests/Visitors/InheritanceTests.cs index a4c73e4b1..e805d4673 100644 --- a/test/Microsoft.OpenApi.Tests/Visitors/InheritanceTests.cs +++ b/test/Microsoft.OpenApi.Tests/Visitors/InheritanceTests.cs @@ -17,7 +17,7 @@ public void ExpectedVirtualsInvolved() visitor = new TestVisitor(); - visitor.Enter(default); + visitor.Enter(default(string)); visitor.Visit(default(OpenApiDocument)); visitor.Visit(default(OpenApiInfo)); visitor.Visit(default(OpenApiContact)); @@ -66,7 +66,7 @@ public void ExpectedVirtualsInvolved() internal protected class TestVisitor : OpenApiVisitorBase { - public Stack CallStack { get; } = new(); + public Stack CallStack { get; } = new Stack(); private string EncodeCall([CallerMemberName] string name = "", [CallerLineNumber] int lineNumber = 0) { @@ -261,10 +261,10 @@ public override void Visit(OpenApiTag tag) base.Visit(tag); } - public override void Visit(OpenApiHeader tag) + public override void Visit(OpenApiHeader header) { EncodeCall(); - base.Visit(tag); + base.Visit(header); } public override void Visit(OpenApiOAuthFlow openApiOAuthFlow) diff --git a/test/Microsoft.OpenApi.Tests/Walkers/WalkerLocationTests.cs b/test/Microsoft.OpenApi.Tests/Walkers/WalkerLocationTests.cs index 83b7eb341..bec1f3602 100644 --- a/test/Microsoft.OpenApi.Tests/Walkers/WalkerLocationTests.cs +++ b/test/Microsoft.OpenApi.Tests/Walkers/WalkerLocationTests.cs @@ -6,6 +6,7 @@ using FluentAssertions; using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Models.References; using Microsoft.OpenApi.Services; using Xunit; @@ -23,7 +24,9 @@ public void LocateTopLevelObjects() walker.Walk(doc); locator.Locations.Should().BeEquivalentTo(new List { + "#/info", "#/servers", + "#/paths", "#/tags" }); } @@ -38,7 +41,6 @@ public void LocateTopLevelArrayItems() new(), new() }, - Paths = new(), Tags = new List { new() @@ -50,6 +52,7 @@ public void LocateTopLevelArrayItems() walker.Walk(doc); locator.Locations.Should().BeEquivalentTo(new List { + "#/info", "#/servers", "#/servers/0", "#/servers/1", @@ -62,10 +65,7 @@ public void LocateTopLevelArrayItems() [Fact] public void LocatePathOperationContentSchema() { - var doc = new OpenApiDocument - { - Paths = new() - }; + var doc = new OpenApiDocument(); doc.Paths.Add("/test", new() { Operations = new Dictionary @@ -80,9 +80,9 @@ public void LocatePathOperationContentSchema() { ["application/json"] = new() { - Schema = new() + Schema = new OpenApiSchema { - Type = "string" + Type = JsonSchemaType.String } } } @@ -97,6 +97,7 @@ public void LocatePathOperationContentSchema() walker.Walk(doc); locator.Locations.Should().BeEquivalentTo(new List { + "#/info", "#/servers", "#/paths", "#/paths/~1test", @@ -119,10 +120,10 @@ public void WalkDOMWithCycles() { var loopySchema = new OpenApiSchema { - Type = "object", + Type = JsonSchemaType.Object, Properties = new Dictionary { - ["name"] = new() { Type = "string" } + ["name"] = new() { Type = JsonSchemaType.String } } }; @@ -130,7 +131,6 @@ public void WalkDOMWithCycles() var doc = new OpenApiDocument { - Paths = new(), Components = new() { Schemas = new Dictionary @@ -145,6 +145,7 @@ public void WalkDOMWithCycles() walker.Walk(doc); locator.Locations.Should().BeEquivalentTo(new List { + "#/info", "#/servers", "#/paths", "#/components", @@ -160,15 +161,7 @@ public void WalkDOMWithCycles() [Fact] public void LocateReferences() { - var baseSchema = new OpenApiSchema - { - Reference = new() - { - Id = "base", - Type = ReferenceType.Schema - }, - UnresolvedReference = false - }; + var baseSchema = new OpenApiSchemaReference("base", null); var derivedSchema = new OpenApiSchema { @@ -180,8 +173,7 @@ public void LocateReferences() }, UnresolvedReference = false }; - - var testHeader = new OpenApiHeader + var testHeader = new OpenApiHeader() { Schema = derivedSchema, Reference = new() @@ -236,15 +228,7 @@ public void LocateReferences() }, SecuritySchemes = new Dictionary { - ["test-secScheme"] = new OpenApiSecurityScheme - { - Reference = new OpenApiReference - { - Id = "reference-to-scheme", - Type = ReferenceType.SecurityScheme - }, - UnresolvedReference = true - } + ["test-secScheme"] = new OpenApiSecuritySchemeReference("reference-to-scheme", null, null) } } }; @@ -255,7 +239,7 @@ public void LocateReferences() locator.Locations.Where(l => l.StartsWith("referenceAt:")).Should().BeEquivalentTo(new List { "referenceAt: #/paths/~1/get/responses/200/content/application~1json/schema", - "referenceAt: #/paths/~1/get/responses/200/headers/test-header", + "referenceAt: #/paths/~1/get/responses/200/headers/test-header/schema", "referenceAt: #/components/schemas/derived/anyOf/0", "referenceAt: #/components/securitySchemes/test-secScheme", "referenceAt: #/components/headers/test-header/schema" diff --git a/test/Microsoft.OpenApi.Tests/Workspaces/OpenApiReferencableTests.cs b/test/Microsoft.OpenApi.Tests/Workspaces/OpenApiReferencableTests.cs index 6e615bd26..e015da4f4 100644 --- a/test/Microsoft.OpenApi.Tests/Workspaces/OpenApiReferencableTests.cs +++ b/test/Microsoft.OpenApi.Tests/Workspaces/OpenApiReferencableTests.cs @@ -19,7 +19,7 @@ public class OpenApiReferencableTests private static readonly OpenApiLink _linkFragment = new(); private static readonly OpenApiHeader _headerFragment = new() { - Schema = new(), + Schema = new OpenApiSchema(), Examples = new Dictionary { { "example1", new OpenApiExample() } @@ -27,7 +27,7 @@ public class OpenApiReferencableTests }; private static readonly OpenApiParameter _parameterFragment = new() { - Schema = new(), + Schema = new OpenApiSchema(), Examples = new Dictionary { { "example1", new OpenApiExample() } @@ -45,9 +45,9 @@ public class OpenApiReferencableTests { "link1", new OpenApiLink() } } }; - private static readonly OpenApiSchema _schemaFragment = new(); - private static readonly OpenApiSecurityScheme _securitySchemeFragment = new(); - private static readonly OpenApiTag _tagFragment = new(); + private static readonly OpenApiSchema _schemaFragment = new OpenApiSchema(); + private static readonly OpenApiSecurityScheme _securitySchemeFragment = new OpenApiSecurityScheme(); + private static readonly OpenApiTag _tagFragment = new OpenApiTag(); public static IEnumerable ResolveReferenceCanResolveValidJsonPointersTestData => new List @@ -56,16 +56,13 @@ public class OpenApiReferencableTests new object[] { _exampleFragment, "/", _exampleFragment }, new object[] { _linkFragment, "/", _linkFragment }, new object[] { _headerFragment, "/", _headerFragment }, - new object[] { _headerFragment, "/schema", _headerFragment.Schema }, new object[] { _headerFragment, "/examples/example1", _headerFragment.Examples["example1"] }, new object[] { _parameterFragment, "/", _parameterFragment }, - new object[] { _parameterFragment, "/schema", _parameterFragment.Schema }, new object[] { _parameterFragment, "/examples/example1", _parameterFragment.Examples["example1"] }, new object[] { _requestBodyFragment, "/", _requestBodyFragment }, new object[] { _responseFragment, "/", _responseFragment }, new object[] { _responseFragment, "/headers/header1", _responseFragment.Headers["header1"] }, new object[] { _responseFragment, "/links/link1", _responseFragment.Links["link1"] }, - new object[] { _schemaFragment, "/", _schemaFragment}, new object[] { _securitySchemeFragment, "/", _securitySchemeFragment}, new object[] { _tagFragment, "/", _tagFragment} }; diff --git a/test/Microsoft.OpenApi.Tests/Workspaces/OpenApiWorkspaceTests.cs b/test/Microsoft.OpenApi.Tests/Workspaces/OpenApiWorkspaceTests.cs index 12798779d..f8bde4f85 100644 --- a/test/Microsoft.OpenApi.Tests/Workspaces/OpenApiWorkspaceTests.cs +++ b/test/Microsoft.OpenApi.Tests/Workspaces/OpenApiWorkspaceTests.cs @@ -3,7 +3,7 @@ using System; using System.Collections.Generic; -using System.Linq; +using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Services; using Xunit; @@ -13,153 +13,92 @@ namespace Microsoft.OpenApi.Tests public class OpenApiWorkspaceTests { [Fact] - public void OpenApiWorkspaceCanHoldMultipleDocuments() + public void OpenApiWorkspacesCanAddComponentsFromAnotherDocument() { - var workspace = new OpenApiWorkspace(); - - workspace.AddDocument("root", new()); - workspace.AddDocument("common", new()); - - Assert.Equal(2, workspace.Documents.Count()); - } - - [Fact] - public void OpenApiWorkspacesAllowDocumentsToReferenceEachOther() - { - var workspace = new OpenApiWorkspace(); - - workspace.AddDocument("root", new() + var doc = new OpenApiDocument() { - Paths = new() + Paths = new OpenApiPaths() { ["/"] = new() { - Operations = new Dictionary + Operations = new Dictionary() { - [OperationType.Get] = new() + [OperationType.Get] = new OpenApiOperation() { - Responses = new() + Responses = new OpenApiResponses() { ["200"] = new() { - Content = new Dictionary - { - ["application/json"] = new() - { - Schema = new() - { - Reference = new() - { - Id = "test", - Type = ReferenceType.Schema - } - } - } - } + Content = new Dictionary() + { + ["application/json"] = new OpenApiMediaType() + { + Schema = new() + { + Reference = new() + { + Id = "test", + Type = ReferenceType.Schema + } + } + } + } } } } } } } - }); - workspace.AddDocument("common", new() + }; + + var doc2 = new OpenApiDocument() { - Components = new() + Components = new OpenApiComponents() { Schemas = { ["test"] = new() { - Type = "string", + Type = JsonSchemaType.String, Description = "The referenced one" } } } - }); - Assert.Equal(2, workspace.Documents.Count()); + }; + + doc.Workspace.RegisterComponents(doc2); + + Assert.Equal(1, doc.Workspace.ComponentsCount()); } [Fact] public void OpenApiWorkspacesCanResolveExternalReferences() { + var refUri = new Uri("https://everything.json/common#/components/schemas/test"); var workspace = new OpenApiWorkspace(); - workspace.AddDocument("common", CreateCommonDocument()); - var schema = workspace.ResolveReference(new() {Id = "test", Type = ReferenceType.Schema, ExternalResource = "common"}) as OpenApiSchema; + var externalDoc = CreateCommonDocument(); + + workspace.RegisterComponent("https://everything.json/common#/components/schemas/test", externalDoc.Components.Schemas["test"]); + var schema = workspace.ResolveReference("https://everything.json/common#/components/schemas/test"); + Assert.NotNull(schema); Assert.Equal("The referenced one", schema.Description); } - [Fact] - public void OpenApiWorkspacesAllowDocumentsToReferenceEachOther_short() - { - var workspace = new OpenApiWorkspace(); - - var doc = new OpenApiDocument(); - doc.CreatePathItem("/", p => - { - p.Description = "Consumer"; - p.CreateOperation(OperationType.Get, op => - op.CreateResponse("200", re => - { - re.Description = "Success"; - re.CreateContent("application/json", - co => - co.Schema = new() - { - Reference = new() // Reference - { - Id = "test", Type = ReferenceType.Schema, ExternalResource = "common" - }, - UnresolvedReference = true - } - ); - }) - ); - }); - - workspace.AddDocument("root", doc); - workspace.AddDocument("common", CreateCommonDocument()); - var errors = doc.ResolveReferences(); - Assert.Empty(errors); - - var schema = doc.Paths["/"].Operations[OperationType.Get].Responses["200"].Content["application/json"].Schema; - var effectiveSchema = schema.GetEffective(doc); - Assert.False(effectiveSchema.UnresolvedReference); - } - - [Fact] - public void OpenApiWorkspacesShouldNormalizeDocumentLocations() - { - var workspace = new OpenApiWorkspace(); - workspace.AddDocument("hello", new()); - workspace.AddDocument("hi", new()); - - Assert.True(workspace.Contains("./hello")); - Assert.True(workspace.Contains("./foo/../hello")); - Assert.True(workspace.Contains("file://" + Environment.CurrentDirectory + "/./foo/../hello")); - - Assert.False(workspace.Contains("./goodbye")); - } - - // Enable Workspace to load from any reader, not just streams. - - // Test fragments - internal void OpenApiWorkspacesShouldLoadDocumentFragments() - { - Assert.True(false); - } - [Fact] public void OpenApiWorkspacesCanResolveReferencesToDocumentFragments() { // Arrange var workspace = new OpenApiWorkspace(); - var schemaFragment = new OpenApiSchema {Type = "string", Description = "Schema from a fragment"}; - workspace.AddFragment("fragment", schemaFragment); + var schemaFragment = new OpenApiSchema() + { + Type = JsonSchemaType.String, + Description = "Schema from a fragment" + }; + workspace.RegisterComponent("common#/components/schemas/test", schemaFragment); // Act - var schema = workspace.ResolveReference(new() {ExternalResource = "fragment"}) as OpenApiSchema; + var schema = workspace.ResolveReference("common#/components/schemas/test"); // Assert Assert.NotNull(schema); @@ -178,45 +117,46 @@ public void OpenApiWorkspacesCanResolveReferencesToDocumentFragmentsWithJsonPoin { "header1", new OpenApiHeader() } } }; - workspace.AddFragment("fragment", responseFragment); + + workspace.RegisterComponent("headers/header1", responseFragment); // Act - var resolvedElement = workspace.ResolveReference(new() - { - Id = "headers/header1", - ExternalResource = "fragment" - }); + var resolvedElement = workspace.ResolveReference("headers/header1"); // Assert - Assert.Same(responseFragment.Headers["header1"], resolvedElement); + Assert.Same(responseFragment.Headers["header1"], resolvedElement.Headers["header1"]); } // Test artifacts private static OpenApiDocument CreateCommonDocument() { - return new() + var doc = new OpenApiDocument() { Components = new() { - Schemas = { + Schemas = + { ["test"] = new() { - Type = "string", + Type = JsonSchemaType.String, Description = "The referenced one" } } } }; + + return doc; } } public static class OpenApiFactoryExtensions { + public static OpenApiDocument CreatePathItem(this OpenApiDocument document, string path, Action config) { var pathItem = new OpenApiPathItem(); config(pathItem); - document.Paths = new(); + document.Paths = new OpenApiPaths(); document.Paths.Add(path, pathItem); return document; } @@ -244,5 +184,6 @@ public static OpenApiResponse CreateContent(this OpenApiResponse parent, string parent.Content.Add(mediaType, child); return parent; } + } } diff --git a/test/Microsoft.OpenApi.Tests/Writers/OpenApiJsonWriterTests.cs b/test/Microsoft.OpenApi.Tests/Writers/OpenApiJsonWriterTests.cs index f108b950a..a967c43a0 100644 --- a/test/Microsoft.OpenApi.Tests/Writers/OpenApiJsonWriterTests.cs +++ b/test/Microsoft.OpenApi.Tests/Writers/OpenApiJsonWriterTests.cs @@ -8,6 +8,7 @@ using System.IO; using System.Linq; using System.Text; +using System.Text.Json.Nodes; using FluentAssertions; using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Models; @@ -20,15 +21,14 @@ namespace Microsoft.OpenApi.Tests.Writers [Collection("DefaultSettings")] public class OpenApiJsonWriterTests { - static bool[] shouldProduceTerseOutputValues = new[] { true, false }; + static bool[] shouldProduceTerseOutputValues = [true, false]; public static IEnumerable WriteStringListAsJsonShouldMatchExpectedTestCases() { return from input in new[] { - new[] - { + [ "string1", "string2", "string3", @@ -37,7 +37,7 @@ from input in new[] "string6", "string7", "string8" - }, + ], new[] {"string1", "string1", "string1", "string1"} } from shouldBeTerse in shouldProduceTerseOutputValues @@ -275,10 +275,11 @@ public void OpenApiJsonWriterOutputsValidJsonValueWhenSchemaHasNanOrInfinityValu // Arrange var schema = new OpenApiSchema { - Enum = new List { - new OpenApiDouble(double.NaN), - new OpenApiDouble(double.PositiveInfinity), - new OpenApiDouble(double.NegativeInfinity) + Enum = new List + { + new OpenApiAny("NaN").Node, + new OpenApiAny("Infinity").Node, + new OpenApiAny("-Infinity").Node } }; diff --git a/test/Microsoft.OpenApi.Tests/Writers/OpenApiWriterAnyExtensionsTests.cs b/test/Microsoft.OpenApi.Tests/Writers/OpenApiWriterAnyExtensionsTests.cs index 6bc2c711b..2d966e8a5 100644 --- a/test/Microsoft.OpenApi.Tests/Writers/OpenApiWriterAnyExtensionsTests.cs +++ b/test/Microsoft.OpenApi.Tests/Writers/OpenApiWriterAnyExtensionsTests.cs @@ -6,6 +6,8 @@ using System.Globalization; using System.IO; using System.Linq; +using System.Text.Json; +using System.Text.Json.Nodes; using System.Threading.Tasks; using FluentAssertions; using Microsoft.OpenApi.Any; @@ -26,9 +28,7 @@ public class OpenApiWriterAnyExtensionsTests public async Task WriteOpenApiNullAsJsonWorksAsync(bool produceTerseOutput) { // Arrange - var nullValue = new OpenApiNull(); - - var json = await WriteAsJsonAsync(nullValue, produceTerseOutput); + var json = await WriteAsJsonAsync(null, produceTerseOutput); // Assert json.Should().Be("null"); @@ -54,7 +54,7 @@ from shouldBeTerse in shouldProduceTerseOutputValues public async Task WriteOpenApiIntegerAsJsonWorksAsync(int input, bool produceTerseOutput) { // Arrange - var intValue = new OpenApiInteger(input); + var intValue = input; var json = await WriteAsJsonAsync(intValue, produceTerseOutput); @@ -82,7 +82,7 @@ from shouldBeTerse in shouldProduceTerseOutputValues public async Task WriteOpenApiLongAsJsonWorksAsync(long input, bool produceTerseOutput) { // Arrange - var longValue = new OpenApiLong(input); + var longValue = input; var json = await WriteAsJsonAsync(longValue, produceTerseOutput); @@ -110,7 +110,7 @@ from shouldBeTerse in shouldProduceTerseOutputValues public async Task WriteOpenApiFloatAsJsonWorksAsync(float input, bool produceTerseOutput) { // Arrange - var floatValue = new OpenApiFloat(input); + var floatValue = input; var json = await WriteAsJsonAsync(floatValue, produceTerseOutput); @@ -138,7 +138,7 @@ from shouldBeTerse in shouldProduceTerseOutputValues public async Task WriteOpenApiDoubleAsJsonWorksAsync(double input, bool produceTerseOutput) { // Arrange - var doubleValue = new OpenApiDouble(input); + var doubleValue = input; var json = await WriteAsJsonAsync(doubleValue, produceTerseOutput); @@ -168,7 +168,7 @@ public async Task WriteOpenApiDateTimeAsJsonWorksAsync(string inputString, bool { // Arrange var input = DateTimeOffset.Parse(inputString, CultureInfo.InvariantCulture); - var dateTimeValue = new OpenApiDateTime(input); + var dateTimeValue = input; var json = await WriteAsJsonAsync(dateTimeValue, produceTerseOutput); var expectedJson = "\"" + input.ToString("o") + "\""; @@ -190,7 +190,7 @@ from shouldBeTerse in shouldProduceTerseOutputValues public async Task WriteOpenApiBooleanAsJsonWorksAsync(bool input, bool produceTerseOutput) { // Arrange - var boolValue = new OpenApiBoolean(input); + var boolValue = input; var json = await WriteAsJsonAsync(boolValue, produceTerseOutput); @@ -204,15 +204,15 @@ public async Task WriteOpenApiBooleanAsJsonWorksAsync(bool input, bool produceTe public async Task WriteOpenApiObjectAsJsonWorksAsync(bool produceTerseOutput) { // Arrange - var openApiObject = new OpenApiObject + var openApiObject = new JsonObject { - {"stringProp", new OpenApiString("stringValue1")}, - {"objProp", new OpenApiObject()}, + {"stringProp", "stringValue1"}, + {"objProp", new JsonObject()}, { "arrayProp", - new OpenApiArray + new JsonArray { - new OpenApiBoolean(false) + false } } }; @@ -229,24 +229,24 @@ public async Task WriteOpenApiObjectAsJsonWorksAsync(bool produceTerseOutput) public async Task WriteOpenApiArrayAsJsonWorksAsync(bool produceTerseOutput) { // Arrange - var openApiObject = new OpenApiObject + var openApiObject = new JsonObject { - {"stringProp", new OpenApiString("stringValue1")}, - {"objProp", new OpenApiObject()}, + {"stringProp", "stringValue1"}, + {"objProp", new JsonObject()}, { "arrayProp", - new OpenApiArray + new JsonArray { - new OpenApiBoolean(false) + false } } }; - var array = new OpenApiArray + var array = new JsonArray { - new OpenApiBoolean(false), + false, openApiObject, - new OpenApiString("stringValue2") + "stringValue2" }; var actualJson = WriteAsJsonAsync(array, produceTerseOutput); @@ -255,7 +255,7 @@ public async Task WriteOpenApiArrayAsJsonWorksAsync(bool produceTerseOutput) await Verifier.Verify(actualJson).UseParameters(produceTerseOutput); } - private static async Task WriteAsJsonAsync(IOpenApiAny any, bool produceTerseOutput = false) + private static async Task WriteAsJsonAsync(JsonNode any, bool produceTerseOutput = false) { // Arrange (continued) using var stream = new MemoryStream(); @@ -270,13 +270,16 @@ private static async Task WriteAsJsonAsync(IOpenApiAny any, bool produce // Act using var sr = new StreamReader(stream); var value = await sr.ReadToEndAsync(); - - if (any.AnyType is AnyType.Primitive or AnyType.Null) + var element = JsonDocument.Parse(value).RootElement; + return element.ValueKind switch { - return value; - } - - return value.MakeLineBreaksEnvironmentNeutral(); + JsonValueKind.String => value, + JsonValueKind.Number => value, + JsonValueKind.Null => value, + JsonValueKind.False => value, + JsonValueKind.True => value, + _ => value.MakeLineBreaksEnvironmentNeutral(), + }; } } } diff --git a/test/Microsoft.OpenApi.Tests/Writers/OpenApiYamlWriterTests.cs b/test/Microsoft.OpenApi.Tests/Writers/OpenApiYamlWriterTests.cs index 74e6a7d27..de9cbda56 100644 --- a/test/Microsoft.OpenApi.Tests/Writers/OpenApiYamlWriterTests.cs +++ b/test/Microsoft.OpenApi.Tests/Writers/OpenApiYamlWriterTests.cs @@ -389,11 +389,13 @@ public void WriteInlineSchema() // Act doc.SerializeAsV3(writer); + var mediaType = doc.Paths["/"].Operations[OperationType.Get].Responses["200"].Content["application/json"]; var actual = outputString.GetStringBuilder().ToString(); // Assert actual = actual.MakeLineBreaksEnvironmentNeutral(); expected = expected.MakeLineBreaksEnvironmentNeutral(); + actual.Should().BeEquivalentTo(expected); Assert.Equal(expected, actual); } @@ -438,7 +440,7 @@ private static OpenApiDocument CreateDocWithSimpleSchemaToInline() // Arrange var thingSchema = new OpenApiSchema { - Type = "object", + Type = JsonSchemaType.Object, UnresolvedReference = false, Reference = new() { @@ -447,7 +449,7 @@ private static OpenApiDocument CreateDocWithSimpleSchemaToInline() } }; - var doc = new OpenApiDocument + var doc = new OpenApiDocument() { Info = new() { @@ -483,184 +485,8 @@ private static OpenApiDocument CreateDocWithSimpleSchemaToInline() ["thing"] = thingSchema} } }; - thingSchema.Reference.HostDocument = doc; return doc; } - - [Fact] - - public void WriteInlineRecursiveSchema() - { - // Arrange - var doc = CreateDocWithRecursiveSchemaReference(); - - var expected = - """ - openapi: 3.0.1 - info: - title: Demo - version: 1.0.0 - paths: - /: - get: - responses: - '200': - description: OK - content: - application/json: - schema: - type: object - properties: - children: - $ref: '#/components/schemas/thing' - related: - type: integer - components: - schemas: - thing: - type: object - properties: - children: - type: object - properties: - children: - $ref: '#/components/schemas/thing' - related: - type: integer - related: - type: integer - """; - // Component schemas that are there due to cycles are still inlined because the items they reference may not exist in the components because they don't have cycles. - - var outputString = new StringWriter(CultureInfo.InvariantCulture); - var writer = new OpenApiYamlWriter(outputString, new() { InlineLocalReferences = true }); - - // Act - doc.SerializeAsV3(writer); - var actual = outputString.GetStringBuilder().ToString(); - - // Assert - actual = actual.MakeLineBreaksEnvironmentNeutral(); - expected = expected.MakeLineBreaksEnvironmentNeutral(); - Assert.Equal(expected, actual); - } - - private static OpenApiDocument CreateDocWithRecursiveSchemaReference() - { - var thingSchema = new OpenApiSchema - { - Type = "object", - UnresolvedReference = false, - Reference = new() - { - Id = "thing", - Type = ReferenceType.Schema - } - }; - thingSchema.Properties["children"] = thingSchema; - - var relatedSchema = new OpenApiSchema - { - Type = "integer", - }; - - thingSchema.Properties["related"] = relatedSchema; - - var doc = new OpenApiDocument - { - Info = new() - { - Title = "Demo", - Version = "1.0.0" - }, - Paths = new() - { - ["/"] = new() - { - Operations = { - [OperationType.Get] = new() - { - Responses = { - ["200"] = new() - { - Description = "OK", - Content = { - ["application/json"] = new() - { - Schema = thingSchema - } - } - } - } - } - } - } - }, - Components = new() - { - Schemas = { - ["thing"] = thingSchema} - } - }; - thingSchema.Reference.HostDocument = doc; - return doc; - } - - [Fact] - public void WriteInlineRecursiveSchemav2() - { - // Arrange - var doc = CreateDocWithRecursiveSchemaReference(); - - var expected = - """ - swagger: '2.0' - info: - title: Demo - version: 1.0.0 - paths: - /: - get: - produces: - - application/json - responses: - '200': - description: OK - schema: - type: object - properties: - children: - $ref: '#/definitions/thing' - related: - type: integer - definitions: - thing: - type: object - properties: - children: - type: object - properties: - children: - $ref: '#/definitions/thing' - related: - type: integer - related: - type: integer - """; - // Component schemas that are there due to cycles are still inlined because the items they reference may not exist in the components because they don't have cycles. - - var outputString = new StringWriter(CultureInfo.InvariantCulture); - var writer = new OpenApiYamlWriter(outputString, new() { InlineLocalReferences = true }); - - // Act - doc.SerializeAsV2(writer); - var actual = outputString.GetStringBuilder().ToString(); - - // Assert - actual = actual.MakeLineBreaksEnvironmentNeutral(); - expected = expected.MakeLineBreaksEnvironmentNeutral(); - Assert.Equal(expected, actual); - } } }