diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml index d7efd6213..b4541f08c 100644 --- a/.github/workflows/sonarcloud.yml +++ b/.github/workflows/sonarcloud.yml @@ -29,11 +29,11 @@ jobs: name: Build runs-on: windows-latest steps: - - name: Set up JDK 11 + - name: Set up JDK 17 uses: actions/setup-java@v3 with: distribution: 'adopt' - java-version: 11 + java-version: 17 - name: Setup .NET 5 # At the moment the scanner requires dotnet 5 https://www.nuget.org/packages/dotnet-sonarscanner uses: actions/setup-dotnet@v3 with: diff --git a/src/Microsoft.OpenApi.Hidi/OpenApiService.cs b/src/Microsoft.OpenApi.Hidi/OpenApiService.cs index 5d5ec95d4..aaf6fdd66 100644 --- a/src/Microsoft.OpenApi.Hidi/OpenApiService.cs +++ b/src/Microsoft.OpenApi.Hidi/OpenApiService.cs @@ -7,12 +7,17 @@ using System.IO; using System.Net; using System.Net.Http; +using System.Reflection; using System.Security; using System.Text; -using System.Threading.Tasks; using System.Text.Json; -using Microsoft.Extensions.Logging; +using System.Threading; +using System.Threading.Tasks; +using System.Xml; using System.Xml.Linq; +using System.Xml.Xsl; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; using Microsoft.OData.Edm.Csdl; using Microsoft.OpenApi.Extensions; using Microsoft.OpenApi.Models; @@ -21,11 +26,6 @@ using Microsoft.OpenApi.Services; using Microsoft.OpenApi.Writers; using static Microsoft.OpenApi.Hidi.OpenApiSpecVersionHelper; -using System.Threading; -using System.Xml.Xsl; -using System.Xml; -using System.Reflection; -using Microsoft.Extensions.Configuration; namespace Microsoft.OpenApi.Hidi { @@ -98,7 +98,7 @@ CancellationToken cancellationToken } } - private static void WriteOpenApi(FileInfo output, bool terseOutput, bool inlineLocal, bool inlineExternal, OpenApiFormat openApiFormat, OpenApiSpecVersion openApiVersion, OpenApiDocument document, ILogger logger) + private static void WriteOpenApi(FileInfo output, bool terseOutput, bool inlineLocal, bool inlineExternal, OpenApiFormat openApiFormat, OpenApiSpecVersion openApiVersion, OpenApiDocument document, ILogger logger) { using (logger.BeginScope("Output")) { @@ -135,7 +135,7 @@ private static async Task GetOpenApi(string openapi, string csd { OpenApiDocument document; Stream stream; - + if (!string.IsNullOrEmpty(csdl)) { var stopwatch = new Stopwatch(); @@ -168,7 +168,7 @@ private static async Task GetOpenApi(string openapi, string csd return document; } - private static async Task FilterOpenApiDocument(string filterbyoperationids, string filterbytags, string filterbycollection, OpenApiDocument document, ILogger logger, CancellationToken cancellationToken) + private static async Task FilterOpenApiDocument(string filterbyoperationids, string filterbytags, string filterbycollection, OpenApiDocument document, ILogger logger, CancellationToken cancellationToken) { using (logger.BeginScope("Filter")) { @@ -239,8 +239,8 @@ private static Stream ApplyFilterToCsdl(Stream csdlStream, string entitySetOrSin /// Implementation of the validate command /// public static async Task ValidateOpenApiDocument( - string openapi, - ILogger logger, + string openapi, + ILogger logger, CancellationToken cancellationToken) { if (string.IsNullOrEmpty(openapi)) @@ -285,7 +285,7 @@ private static async Task ParseOpenApi(string openApiFile, bool inli result = await new OpenApiStreamReader(new OpenApiReaderSettings { LoadExternalRefs = inlineExternal, - BaseUrl = openApiFile.StartsWith("http", StringComparison.OrdinalIgnoreCase) ? + BaseUrl = openApiFile.StartsWith("http", StringComparison.OrdinalIgnoreCase) ? new Uri(openApiFile) : new Uri("file://" + new FileInfo(openApiFile).DirectoryName + Path.DirectorySeparatorChar) } @@ -296,7 +296,7 @@ private static async Task ParseOpenApi(string openApiFile, bool inli LogErrors(logger, result); stopwatch.Stop(); } - + return result; } @@ -310,7 +310,7 @@ internal static IConfiguration GetConfiguration(string settingsFile) return config; } - + /// /// Converts CSDL to OpenAPI /// @@ -329,7 +329,7 @@ public static async Task ConvertCsdlToOpenApi(Stream csdl, stri { settings.SemVerVersion = metadataVersion; } - + config.GetSection("OpenApiConvertSettings").Bind(settings); OpenApiDocument document = edmModel.ConvertToOpenApi(settings); @@ -354,7 +354,7 @@ public static OpenApiDocument FixReferences(OpenApiDocument document) return doc; } - + /// /// Takes in a file stream, parses the stream into a JsonDocument and gets a list of paths and Http methods /// @@ -377,13 +377,13 @@ public static Dictionary> ParseJsonCollectionFile(Stream st private static Dictionary> EnumerateJsonDocument(JsonElement itemElement, Dictionary> paths) { var itemsArray = itemElement.GetProperty("item"); - + foreach (var item in itemsArray.EnumerateArray()) { - if(item.ValueKind == JsonValueKind.Object) + if (item.ValueKind == JsonValueKind.Object) { - if(item.TryGetProperty("request", out var request)) - { + if (item.TryGetProperty("request", out var request)) + { // Fetch list of methods and urls from collection, store them in a dictionary var path = request.GetProperty("url").GetProperty("raw").ToString(); var method = request.GetProperty("method").ToString(); @@ -395,11 +395,11 @@ private static Dictionary> EnumerateJsonDocument(JsonElemen { paths[path].Add(method); } - } - else - { + } + else + { EnumerateJsonDocument(item, paths); - } + } } else { @@ -508,11 +508,11 @@ internal static async Task ShowOpenApiDocument(string openapi, string cs if (output == null) { var tempPath = Path.GetTempPath() + "/hidi/"; - if(!File.Exists(tempPath)) + if (!File.Exists(tempPath)) { Directory.CreateDirectory(tempPath); - } - + } + var fileName = Path.GetRandomFileName(); output = new FileInfo(Path.Combine(tempPath, fileName + ".html")); @@ -528,7 +528,7 @@ internal static async Task ShowOpenApiDocument(string openapi, string cs process.StartInfo.FileName = output.FullName; process.StartInfo.UseShellExecute = true; process.Start(); - + return output.FullName; } else // Write diagram as Markdown document to output file @@ -540,7 +540,7 @@ internal static async Task ShowOpenApiDocument(string openapi, string cs } logger.LogTrace("Created markdown document with diagram "); return output.FullName; - } + } } } catch (TaskCanceledException) @@ -563,7 +563,7 @@ private static void LogErrors(ILogger logger, ReadResult result) { foreach (var error in context.Errors) { - logger.LogError($"Detected error during parsing: {error}",error.ToString()); + logger.LogError($"Detected error during parsing: {error}", error.ToString()); } } } @@ -581,7 +581,7 @@ internal static void WriteTreeDocumentAsMarkdown(string openapiUrl, OpenApiDocum // write a span for each mermaidcolorscheme foreach (var style in OpenApiUrlTreeNode.MermaidNodeStyles) { - writer.WriteLine($"{style.Key.Replace("_"," ")}"); + writer.WriteLine($"{style.Key.Replace("_", " ")}"); } writer.WriteLine(""); writer.WriteLine(); @@ -609,7 +609,7 @@ internal static void WriteTreeDocumentAsHtml(string sourceUrl, OpenApiDocument d writer.WriteLine("

" + document.Info.Title + "

"); writer.WriteLine(); writer.WriteLine($"

API Description: {sourceUrl}

"); - + writer.WriteLine(@"
"); // write a span for each mermaidcolorscheme foreach (var style in OpenApiUrlTreeNode.MermaidNodeStyles) @@ -622,8 +622,8 @@ internal static void WriteTreeDocumentAsHtml(string sourceUrl, OpenApiDocument d rootNode.WriteMermaid(writer); writer.WriteLine(""); - // Write script tag to include JS library for rendering markdown - writer.WriteLine(@""); - // Write script tag to include JS library for rendering mermaid - writer.WriteLine("("--format", "File format"); formatOption.AddAlias("-f"); - + var terseOutputOption = new Option("--terse-output", "Produce terse json output"); terseOutputOption.AddAlias("--to"); diff --git a/src/Microsoft.OpenApi.Hidi/StatsVisitor.cs b/src/Microsoft.OpenApi.Hidi/StatsVisitor.cs index b05b0de7c..5c995d8fa 100644 --- a/src/Microsoft.OpenApi.Hidi/StatsVisitor.cs +++ b/src/Microsoft.OpenApi.Hidi/StatsVisitor.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using Json.Schema; using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Services; @@ -19,7 +20,7 @@ public override void Visit(OpenApiParameter parameter) public int SchemaCount { get; set; } = 0; - public override void Visit(OpenApiSchema schema) + public override void Visit(ref JsonSchema schema) { SchemaCount++; } diff --git a/src/Microsoft.OpenApi.Readers/Exceptions/OpenApiReaderException.cs b/src/Microsoft.OpenApi.Readers/Exceptions/OpenApiReaderException.cs index 72942ae20..8021d83a2 100644 --- a/src/Microsoft.OpenApi.Readers/Exceptions/OpenApiReaderException.cs +++ b/src/Microsoft.OpenApi.Readers/Exceptions/OpenApiReaderException.cs @@ -4,7 +4,6 @@ using System; using System.Text.Json.Nodes; using Microsoft.OpenApi.Exceptions; -using SharpYaml.Serialization; namespace Microsoft.OpenApi.Readers.Exceptions { @@ -30,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(); } diff --git a/src/Microsoft.OpenApi.Readers/Interface/IOpenApiVersionService.cs b/src/Microsoft.OpenApi.Readers/Interface/IOpenApiVersionService.cs index 1be9541cd..4f1ed0915 100644 --- a/src/Microsoft.OpenApi.Readers/Interface/IOpenApiVersionService.cs +++ b/src/Microsoft.OpenApi.Readers/Interface/IOpenApiVersionService.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. -using System; using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Readers.ParseNodes; diff --git a/src/Microsoft.OpenApi.Readers/Microsoft.OpenApi.Readers.csproj b/src/Microsoft.OpenApi.Readers/Microsoft.OpenApi.Readers.csproj index 4fa45c5c8..07a88a91c 100644 --- a/src/Microsoft.OpenApi.Readers/Microsoft.OpenApi.Readers.csproj +++ b/src/Microsoft.OpenApi.Readers/Microsoft.OpenApi.Readers.csproj @@ -1,4 +1,4 @@ - + netstandard2.0 9.0 @@ -35,7 +35,10 @@ + + + diff --git a/src/Microsoft.OpenApi.Readers/OpenApiReaderSettings.cs b/src/Microsoft.OpenApi.Readers/OpenApiReaderSettings.cs index 9eaa5ae18..0ff4bc0ef 100644 --- a/src/Microsoft.OpenApi.Readers/OpenApiReaderSettings.cs +++ b/src/Microsoft.OpenApi.Readers/OpenApiReaderSettings.cs @@ -1,14 +1,13 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. +using System; +using System.Collections.Generic; +using System.IO; using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Readers.Interface; using Microsoft.OpenApi.Validations; -using System; -using System.Collections.Generic; -using System.IO; -using System.Text.Json.Nodes; namespace Microsoft.OpenApi.Readers { diff --git a/src/Microsoft.OpenApi.Readers/OpenApiStreamReader.cs b/src/Microsoft.OpenApi.Readers/OpenApiStreamReader.cs index 8922be4ce..34b7ab81b 100644 --- a/src/Microsoft.OpenApi.Readers/OpenApiStreamReader.cs +++ b/src/Microsoft.OpenApi.Readers/OpenApiStreamReader.cs @@ -26,7 +26,7 @@ public OpenApiStreamReader(OpenApiReaderSettings settings = null) { _settings = settings ?? new OpenApiReaderSettings(); - if((_settings.ReferenceResolution == ReferenceResolutionSetting.ResolveAllReferences || _settings.LoadExternalRefs) + if ((_settings.ReferenceResolution == ReferenceResolutionSetting.ResolveAllReferences || _settings.LoadExternalRefs) && _settings.BaseUrl == null) { throw new ArgumentException("BaseUrl must be provided to resolve external references."); diff --git a/src/Microsoft.OpenApi.Readers/OpenApiTextReaderReader.cs b/src/Microsoft.OpenApi.Readers/OpenApiTextReaderReader.cs index ac22be99a..d4dac4dbd 100644 --- a/src/Microsoft.OpenApi.Readers/OpenApiTextReaderReader.cs +++ b/src/Microsoft.OpenApi.Readers/OpenApiTextReaderReader.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. -using System.Collections; using System.IO; using System.Linq; using System.Text.Json; diff --git a/src/Microsoft.OpenApi.Readers/OpenApiVersionExtensionMethods.cs b/src/Microsoft.OpenApi.Readers/OpenApiVersionExtensionMethods.cs index add2af701..ce35b9900 100644 --- a/src/Microsoft.OpenApi.Readers/OpenApiVersionExtensionMethods.cs +++ b/src/Microsoft.OpenApi.Readers/OpenApiVersionExtensionMethods.cs @@ -22,7 +22,7 @@ public static bool is2_0(this string version) { result = true; } - + return result; } diff --git a/src/Microsoft.OpenApi.Readers/OpenApiYamlDocumentReader.cs b/src/Microsoft.OpenApi.Readers/OpenApiYamlDocumentReader.cs index 95482bfa6..846b3f62d 100644 --- a/src/Microsoft.OpenApi.Readers/OpenApiYamlDocumentReader.cs +++ b/src/Microsoft.OpenApi.Readers/OpenApiYamlDocumentReader.cs @@ -3,9 +3,7 @@ using System; using System.Collections.Generic; -using System.IO; using System.Linq; -using System.Text.Json; using System.Text.Json.Nodes; using System.Threading; using System.Threading.Tasks; @@ -17,7 +15,6 @@ using Microsoft.OpenApi.Readers.Services; using Microsoft.OpenApi.Services; using Microsoft.OpenApi.Validations; -using SharpYaml.Serialization; namespace Microsoft.OpenApi.Readers { @@ -71,7 +68,7 @@ public OpenApiDocument Read(JsonNode input, out OpenApiDiagnostic diagnostic) } // Validate the document - if (_settings.RuleSet != null && _settings.RuleSet.Rules.Count() > 0) + if (_settings.RuleSet != null && _settings.RuleSet.Rules.Any()) { var openApiErrors = document.Validate(_settings.RuleSet); foreach (var item in openApiErrors.OfType()) diff --git a/src/Microsoft.OpenApi.Readers/ParseNodes/AnyFieldMapParameter.cs b/src/Microsoft.OpenApi.Readers/ParseNodes/AnyFieldMapParameter.cs index a1a0db6a5..20d691d5d 100644 --- a/src/Microsoft.OpenApi.Readers/ParseNodes/AnyFieldMapParameter.cs +++ b/src/Microsoft.OpenApi.Readers/ParseNodes/AnyFieldMapParameter.cs @@ -2,9 +2,8 @@ // Licensed under the MIT license. using System; -using System.Text.Json.Nodes; +using Json.Schema; using Microsoft.OpenApi.Any; -using Microsoft.OpenApi.Models; namespace Microsoft.OpenApi.Readers.ParseNodes { @@ -16,11 +15,11 @@ internal class AnyFieldMapParameter public AnyFieldMapParameter( Func propertyGetter, Action propertySetter, - Func schemaGetter) + Func SchemaGetter = null) { this.PropertyGetter = propertyGetter; this.PropertySetter = propertySetter; - this.SchemaGetter = schemaGetter; + this.SchemaGetter = SchemaGetter; } /// @@ -36,6 +35,6 @@ public AnyFieldMapParameter( /// /// Function to get the schema to apply to the property. /// - public Func SchemaGetter { get; } + public Func SchemaGetter { get; } } } diff --git a/src/Microsoft.OpenApi.Readers/ParseNodes/AnyListFieldMapParameter.cs b/src/Microsoft.OpenApi.Readers/ParseNodes/AnyListFieldMapParameter.cs index 667ce16ee..e4745a03d 100644 --- a/src/Microsoft.OpenApi.Readers/ParseNodes/AnyListFieldMapParameter.cs +++ b/src/Microsoft.OpenApi.Readers/ParseNodes/AnyListFieldMapParameter.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; using System.Collections.Generic; -using Microsoft.OpenApi.Any; -using Microsoft.OpenApi.Models; +using System.Text.Json.Nodes; +using Json.Schema; namespace Microsoft.OpenApi.Readers.ParseNodes { @@ -14,28 +14,28 @@ 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. /// - public Func SchemaGetter { get; } + public Func SchemaGetter { get; } } } diff --git a/src/Microsoft.OpenApi.Readers/ParseNodes/AnyMapFieldMapParameter.cs b/src/Microsoft.OpenApi.Readers/ParseNodes/AnyMapFieldMapParameter.cs index f24e1b1ed..43bf87262 100644 --- a/src/Microsoft.OpenApi.Readers/ParseNodes/AnyMapFieldMapParameter.cs +++ b/src/Microsoft.OpenApi.Readers/ParseNodes/AnyMapFieldMapParameter.cs @@ -3,10 +3,8 @@ using System; using System.Collections.Generic; -using System.Text.Json.Nodes; +using Json.Schema; using Microsoft.OpenApi.Any; -using Microsoft.OpenApi.Interfaces; -using Microsoft.OpenApi.Models; namespace Microsoft.OpenApi.Readers.ParseNodes { @@ -19,7 +17,7 @@ public AnyMapFieldMapParameter( Func> propertyMapGetter, Func propertyGetter, Action propertySetter, - Func schemaGetter) + Func schemaGetter) { this.PropertyMapGetter = propertyMapGetter; this.PropertyGetter = propertyGetter; @@ -45,6 +43,6 @@ public AnyMapFieldMapParameter( /// /// Function to get the schema to apply to the property. /// - public Func SchemaGetter { get; } + public Func SchemaGetter { get; } } } diff --git a/src/Microsoft.OpenApi.Readers/ParseNodes/JsonPointerExtensions.cs b/src/Microsoft.OpenApi.Readers/ParseNodes/JsonPointerExtensions.cs index 747ba87c8..9e3981811 100644 --- a/src/Microsoft.OpenApi.Readers/ParseNodes/JsonPointerExtensions.cs +++ b/src/Microsoft.OpenApi.Readers/ParseNodes/JsonPointerExtensions.cs @@ -3,7 +3,6 @@ using System; using System.Text.Json.Nodes; -using SharpYaml.Serialization; namespace Microsoft.OpenApi.Readers.ParseNodes { @@ -33,7 +32,7 @@ public static JsonNode Find(this JsonPointer currentPointer, JsonNode baseJsonNo { pointer = array[tokenValue]; } - else if(pointer is JsonObject map && !map.TryGetPropertyValue(token, out pointer)) + else if (pointer is JsonObject map && !map.TryGetPropertyValue(token, out pointer)) { return null; } diff --git a/src/Microsoft.OpenApi.Readers/ParseNodes/ListNode.cs b/src/Microsoft.OpenApi.Readers/ParseNodes/ListNode.cs index 405d1e1c9..0daf15775 100644 --- a/src/Microsoft.OpenApi.Readers/ParseNodes/ListNode.cs +++ b/src/Microsoft.OpenApi.Readers/ParseNodes/ListNode.cs @@ -16,7 +16,7 @@ internal class ListNode : ParseNode, IEnumerable private readonly JsonArray _nodeList; public ListNode(ParsingContext context, JsonArray jsonArray) : base( - context) + context, jsonArray) { _nodeList = jsonArray; } @@ -33,13 +33,16 @@ public override List CreateList(Func map) .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().Node) .Where(i => i != null) .ToList(); + + return list; } - + public override List CreateSimpleList(Func map) { if (_nodeList == null) @@ -65,7 +68,7 @@ IEnumerator IEnumerable.GetEnumerator() /// /// The created Any object. public override OpenApiAny CreateAny() - { + { return new OpenApiAny(_nodeList); } } diff --git a/src/Microsoft.OpenApi.Readers/ParseNodes/MapNode.cs b/src/Microsoft.OpenApi.Readers/ParseNodes/MapNode.cs index 4b2380eda..b1186f297 100644 --- a/src/Microsoft.OpenApi.Readers/ParseNodes/MapNode.cs +++ b/src/Microsoft.OpenApi.Readers/ParseNodes/MapNode.cs @@ -28,7 +28,7 @@ public MapNode(ParsingContext context, string jsonString) : { } public MapNode(ParsingContext context, JsonNode node) : base( - context) + context, node) { if (node is not JsonObject mapNode) { @@ -56,8 +56,9 @@ public override Dictionary CreateMap(Func map) { var jsonMap = _node ?? throw new OpenApiReaderException($"Expected map while parsing {typeof(T).Name}", Context); var nodes = jsonMap.Select( - n => { - + n => + { + var key = n.Key; T value; try @@ -66,7 +67,7 @@ public override Dictionary CreateMap(Func map) value = n.Value is JsonObject jsonObject ? map(new MapNode(Context, jsonObject)) : default; - } + } finally { Context.EndObject(); @@ -83,9 +84,9 @@ public override Dictionary CreateMap(Func map) public override Dictionary CreateMapWithReference( ReferenceType referenceType, - Func map) + Func map) { - var jsonMap = _node ?? throw new OpenApiReaderException($"Expected map while parsing {typeof(T).Name}", Context); + var jsonMap = _node ?? throw new OpenApiReaderException($"Expected map while parsing {typeof(T).Name}", Context); var nodes = jsonMap.Select( n => @@ -111,7 +112,7 @@ public override Dictionary CreateMapWithReference( Id = entry.key }; } - } + } finally { Context.EndObject(); @@ -132,15 +133,17 @@ public override Dictionary CreateSimpleMap(Func map) try { Context.StartObject(key); - JsonValue valueNode = n.Value is JsonValue value ? value - : throw new OpenApiReaderException($"Expected scalar while parsing {typeof(T).Name}", Context); - + 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 { + } + finally + { Context.EndObject(); } }); - + return nodes.ToDictionary(k => k.key, v => v.value); } @@ -180,12 +183,32 @@ public string GetReferencePointer() 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); } @@ -194,7 +217,7 @@ public string GetScalarValue(ValueNode key) /// /// The created Json object. public override OpenApiAny CreateAny() - { + { return new OpenApiAny(_node); } } diff --git a/src/Microsoft.OpenApi.Readers/ParseNodes/ParseNode.cs b/src/Microsoft.OpenApi.Readers/ParseNodes/ParseNode.cs index ca69ac089..a2d7aa156 100644 --- a/src/Microsoft.OpenApi.Readers/ParseNodes/ParseNode.cs +++ b/src/Microsoft.OpenApi.Readers/ParseNodes/ParseNode.cs @@ -13,13 +13,16 @@ namespace Microsoft.OpenApi.Readers.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 MapNode mapNode)) @@ -72,7 +75,7 @@ public virtual Dictionary CreateSimpleMap(Func map) { throw new OpenApiReaderException("Cannot create simple map from this type of node.", Context); } - + public virtual OpenApiAny CreateAny() { throw new OpenApiReaderException("Cannot create an Any object this type of node.", Context); @@ -87,8 +90,8 @@ 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/PropertyNode.cs b/src/Microsoft.OpenApi.Readers/ParseNodes/PropertyNode.cs index 0d2323cc0..070913c17 100644 --- a/src/Microsoft.OpenApi.Readers/ParseNodes/PropertyNode.cs +++ b/src/Microsoft.OpenApi.Readers/ParseNodes/PropertyNode.cs @@ -15,7 +15,7 @@ namespace Microsoft.OpenApi.Readers.ParseNodes internal class PropertyNode : ParseNode { public PropertyNode(ParsingContext context, string name, JsonNode node) : base( - context) + context, node) { Name = name; Value = Create(context, node); diff --git a/src/Microsoft.OpenApi.Readers/ParseNodes/RootNode.cs b/src/Microsoft.OpenApi.Readers/ParseNodes/RootNode.cs index 2a6e12e7e..6a55f77fe 100644 --- a/src/Microsoft.OpenApi.Readers/ParseNodes/RootNode.cs +++ b/src/Microsoft.OpenApi.Readers/ParseNodes/RootNode.cs @@ -1,9 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. -using System.Text.Json; using System.Text.Json.Nodes; -using SharpYaml.Serialization; namespace Microsoft.OpenApi.Readers.ParseNodes { @@ -16,7 +14,7 @@ internal class RootNode : ParseNode public RootNode( ParsingContext context, - JsonNode jsonNode) : base(context) + JsonNode jsonNode) : base(context, jsonNode) { _jsonNode = jsonNode; } diff --git a/src/Microsoft.OpenApi.Readers/ParseNodes/ValueNode.cs b/src/Microsoft.OpenApi.Readers/ParseNodes/ValueNode.cs index 04d38162f..3c973a7ff 100644 --- a/src/Microsoft.OpenApi.Readers/ParseNodes/ValueNode.cs +++ b/src/Microsoft.OpenApi.Readers/ParseNodes/ValueNode.cs @@ -1,11 +1,11 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. -using System.Globalization; using System; +using System.Globalization; using System.Text.Json.Nodes; -using Microsoft.OpenApi.Readers.Exceptions; using Microsoft.OpenApi.Any; +using Microsoft.OpenApi.Readers.Exceptions; namespace Microsoft.OpenApi.Readers.ParseNodes { @@ -14,7 +14,7 @@ internal class ValueNode : ParseNode private readonly JsonValue _node; public ValueNode(ParsingContext context, JsonNode node) : base( - context) + context, node) { if (node is not JsonValue scalarNode) { diff --git a/src/Microsoft.OpenApi.Readers/ParsingContext.cs b/src/Microsoft.OpenApi.Readers/ParsingContext.cs index 7c664e712..a0930e248 100644 --- a/src/Microsoft.OpenApi.Readers/ParsingContext.cs +++ b/src/Microsoft.OpenApi.Readers/ParsingContext.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,7 @@ using Microsoft.OpenApi.Readers.ParseNodes; using Microsoft.OpenApi.Readers.V2; using Microsoft.OpenApi.Readers.V3; +using Microsoft.OpenApi.Readers.V31; namespace Microsoft.OpenApi.Readers { @@ -25,7 +26,7 @@ public class ParsingContext private readonly Dictionary _tempStorage = new Dictionary(); private readonly Dictionary> _scopedTempStorage = new Dictionary>(); private readonly Dictionary> _loopStacks = new Dictionary>(); - internal Dictionary> ExtensionParsers { get; set; } = + internal Dictionary> ExtensionParsers { get; set; } = new Dictionary>(); internal RootNode RootNode { get; set; } @@ -68,13 +69,18 @@ internal OpenApiDocument Parse(JsonNode jsonNode) ValidateRequiredFields(doc, version); break; - case string version when version.is3_0() || version.is3_1(): + case string version when version.is3_0(): VersionService = new OpenApiV3VersionService(Diagnostic); doc = VersionService.LoadDocument(RootNode); 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); } @@ -105,6 +111,10 @@ internal T ParseFragment(JsonNode jsonNode, OpenApiSpecVersion version) where 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; @@ -145,7 +155,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()); } /// diff --git a/src/Microsoft.OpenApi.Readers/ReadResult.cs b/src/Microsoft.OpenApi.Readers/ReadResult.cs index 7479d345f..80b31316a 100644 --- a/src/Microsoft.OpenApi.Readers/ReadResult.cs +++ b/src/Microsoft.OpenApi.Readers/ReadResult.cs @@ -1,11 +1,6 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using Microsoft.OpenApi.Models; namespace Microsoft.OpenApi.Readers diff --git a/src/Microsoft.OpenApi.Readers/SchemaTypeConverter.cs b/src/Microsoft.OpenApi.Readers/SchemaTypeConverter.cs new file mode 100644 index 000000000..cb61a183e --- /dev/null +++ b/src/Microsoft.OpenApi.Readers/SchemaTypeConverter.cs @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System; +using Json.Schema; + +namespace Microsoft.OpenApi.Readers +{ + internal static class SchemaTypeConverter + { + internal static SchemaValueType ConvertToSchemaValueType(string value) + { + return value.ToLowerInvariant() switch + { + "string" => SchemaValueType.String, + "number" or "double" => SchemaValueType.Number, + "integer" => SchemaValueType.Integer, + "boolean" => SchemaValueType.Boolean, + "array" => SchemaValueType.Array, + "object" => SchemaValueType.Object, + "null" => SchemaValueType.Null, + _ => throw new NotSupportedException(), + }; + } + } +} diff --git a/src/Microsoft.OpenApi.Readers/Services/OpenApiRemoteReferenceCollector.cs b/src/Microsoft.OpenApi.Readers/Services/OpenApiRemoteReferenceCollector.cs index 9a5ba9213..332d76df4 100644 --- a/src/Microsoft.OpenApi.Readers/Services/OpenApiRemoteReferenceCollector.cs +++ b/src/Microsoft.OpenApi.Readers/Services/OpenApiRemoteReferenceCollector.cs @@ -25,7 +25,8 @@ public OpenApiRemoteReferenceCollector(OpenApiDocument document) /// public IEnumerable References { - get { + get + { return _references.Values; } } @@ -54,6 +55,6 @@ private void AddReference(OpenApiReference reference) } } } - } + } } } diff --git a/src/Microsoft.OpenApi.Readers/Services/OpenApiWorkspaceLoader.cs b/src/Microsoft.OpenApi.Readers/Services/OpenApiWorkspaceLoader.cs index 32e2db128..1a527f32a 100644 --- a/src/Microsoft.OpenApi.Readers/Services/OpenApiWorkspaceLoader.cs +++ b/src/Microsoft.OpenApi.Readers/Services/OpenApiWorkspaceLoader.cs @@ -1,8 +1,4 @@ using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.OpenApi.Models; @@ -11,7 +7,7 @@ namespace Microsoft.OpenApi.Readers.Services { - internal class OpenApiWorkspaceLoader + internal class OpenApiWorkspaceLoader { private OpenApiWorkspace _workspace; private IStreamLoader _loader; diff --git a/src/Microsoft.OpenApi.Readers/V2/JsonSchemaDeserializer.cs b/src/Microsoft.OpenApi.Readers/V2/JsonSchemaDeserializer.cs new file mode 100644 index 000000000..e2fea6cc4 --- /dev/null +++ b/src/Microsoft.OpenApi.Readers/V2/JsonSchemaDeserializer.cs @@ -0,0 +1,258 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System.Collections.Generic; +using System.Globalization; +using System.Text.Json.Nodes; +using Json.Schema; +using Json.Schema.OpenApi; +using Microsoft.OpenApi.Interfaces; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Extensions; +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(decimal.Parse(n.GetScalarValue(), NumberStyles.Float, CultureInfo.InvariantCulture)); + } + }, + { + "exclusiveMaximum", (o, n) => + { + o.ExclusiveMaximum(bool.Parse(n.GetScalarValue())); + } + }, + { + "minimum", (o, n) => + { + o.Minimum(decimal.Parse(n.GetScalarValue(), NumberStyles.Float, CultureInfo.InvariantCulture)); + } + }, + { + "exclusiveMinimum", (o, n) => + { + o.ExclusiveMinimum(bool.Parse(n.GetScalarValue())); + } + }, + { + "maxLength", (o, n) => + { + o.MaxLength(uint.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture)); + } + }, + { + "minLength", (o, n) => + { + o.MinLength(uint.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture)); + } + }, + { + "pattern", (o, n) => + { + o.Pattern(n.GetScalarValue()); + } + }, + { + "maxItems", (o, n) => + { + o.MaxItems(uint.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture)); + } + }, + { + "minItems", (o, n) => + { + o.MinItems(uint.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture)); + } + }, + { + "uniqueItems", (o, n) => + { + o.UniqueItems(bool.Parse(n.GetScalarValue())); + } + }, + { + "maxProperties", (o, n) => + { + o.MaxProperties(uint.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture)); + } + }, + { + "minProperties", (o, n) => + { + o.MinProperties(uint.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture)); + } + }, + { + "required", (o, n) => + { + o.Required(new HashSet(n.CreateSimpleList(n2 => n2.GetScalarValue()))); + } + }, + { + "enum", (o, n) => + { + o.Enum(n.CreateListOfAny()); + } + }, + { + "type", (o, n) => + { + if(n is ListNode) + { + o.Type(n.CreateSimpleList(s => SchemaTypeConverter.ConvertToSchemaValueType(s.GetScalarValue()))); + } + else + { + o.Type(SchemaTypeConverter.ConvertToSchemaValueType(n.GetScalarValue())); + } + } + }, + { + "allOf", (o, n) => + { + o.AllOf(n.CreateList(LoadSchema)); + } + }, + { + "items", (o, n) => + { + o.Items(LoadSchema(n)); + } + }, + { + "properties", (o, n) => + { + o.Properties(n.CreateMap(LoadSchema)); + } + }, + { + "additionalProperties", (o, n) => + { + if (n is ValueNode) + { + o.AdditionalProperties(bool.Parse(n.GetScalarValue())); + } + else + { + o.AdditionalProperties(LoadSchema(n)); + } + } + }, + { + "description", (o, n) => + { + o.Description(n.GetScalarValue()); + } + }, + { + "format", (o, n) => + { + o.Format(n.GetScalarValue()); + } + }, + { + "default", (o, n) => + { + o.Default(n.CreateAny().Node); + } + }, + { + "discriminator", (o, n) => + { + var discriminator = new OpenApiDiscriminator + { + PropertyName = n.GetScalarValue() + }; + o.Discriminator(discriminator.PropertyName, (IReadOnlyDictionary)discriminator.Mapping, + (IReadOnlyDictionary)discriminator.Extensions); + } + }, + { + "readOnly", (o, n) => + { + o.ReadOnly(bool.Parse(n.GetScalarValue())); + } + }, + { + "xml", (o, n) => + { + var xml = LoadXml(n); + o.Xml(xml.Namespace, xml.Name, xml.Prefix, xml.Attribute, xml.Wrapped, + (IReadOnlyDictionary)xml.Extensions); + } + }, + { + "externalDocs", (o, n) => + { + var externalDocs = LoadExternalDocs(n); + o.ExternalDocs(externalDocs.Url, externalDocs.Description, + (IReadOnlyDictionary)externalDocs.Extensions); + } + }, + { + "example", (o, n) => + { + o.Example(n.CreateAny().Node); + } + }, + }; + + private static readonly PatternFieldMap _schemaPatternFields = new PatternFieldMap + { + {s => s.StartsWith("x-"), (o, p, n) => o.Extensions(LoadExtensions(p, LoadExtension(p, n)))} + }; + + public static JsonSchema LoadSchema(ParseNode node) + { + var mapNode = node.CheckMapNode(OpenApiConstants.Schema); + var schemaBuilder = new JsonSchemaBuilder(); + + // check for a $ref and if present, add it to the builder as a Ref keyword + var pointer = mapNode.GetReferencePointer(); + if (pointer != null) + { + return schemaBuilder.Ref(pointer); + } + + foreach (var propertyNode in mapNode) + { + propertyNode.ParseField(schemaBuilder, _schemaFixedFields, _schemaPatternFields); + } + + var schema = schemaBuilder.Build(); + return schema; + } + + private static Dictionary LoadExtensions(string value, IOpenApiExtension extension) + { + var extensions = new Dictionary + { + { value, extension } + }; + return extensions; + } + } +} diff --git a/src/Microsoft.OpenApi.Readers/V2/OpenApiContactDeserializer.cs b/src/Microsoft.OpenApi.Readers/V2/OpenApiContactDeserializer.cs index 99bc4451a..af3ce3dad 100644 --- a/src/Microsoft.OpenApi.Readers/V2/OpenApiContactDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V2/OpenApiContactDeserializer.cs @@ -14,7 +14,7 @@ namespace Microsoft.OpenApi.Readers.V2 /// internal static partial class OpenApiV2Deserializer { - private static FixedFieldMap _contactFixedFields = new FixedFieldMap + private static readonly FixedFieldMap _contactFixedFields = new FixedFieldMap { { "name", (o, n) => @@ -36,7 +36,7 @@ internal static partial class OpenApiV2Deserializer }, }; - private static PatternFieldMap _contactPatternFields = new PatternFieldMap + private static readonly PatternFieldMap _contactPatternFields = new PatternFieldMap { {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, LoadExtension(p, n))} }; diff --git a/src/Microsoft.OpenApi.Readers/V2/OpenApiDocumentDeserializer.cs b/src/Microsoft.OpenApi.Readers/V2/OpenApiDocumentDeserializer.cs index fa3aa7224..d90cf76a0 100644 --- a/src/Microsoft.OpenApi.Readers/V2/OpenApiDocumentDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V2/OpenApiDocumentDeserializer.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; +using Json.Schema; using Microsoft.OpenApi.Extensions; using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Readers.ParseNodes; @@ -18,7 +19,7 @@ namespace Microsoft.OpenApi.Readers.V2 /// internal static partial class OpenApiV2Deserializer { - private static FixedFieldMap _openApiFixedFields = new FixedFieldMap + private static readonly FixedFieldMap _openApiFixedFields = new FixedFieldMap { { "swagger", (o, n) => @@ -63,9 +64,7 @@ internal static partial class OpenApiV2Deserializer o.Components = new OpenApiComponents(); } - o.Components.Schemas = n.CreateMapWithReference( - ReferenceType.Schema, - LoadSchema); + o.Components.Schemas = n.CreateMap(LoadSchema); } }, { @@ -126,7 +125,7 @@ internal static partial class OpenApiV2Deserializer {"externalDocs", (o, n) => o.ExternalDocs = LoadExternalDocs(n)} }; - private static PatternFieldMap _openApiPatternFields = new PatternFieldMap + 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))} @@ -264,6 +263,8 @@ public static OpenApiDocument LoadOpenApi(RootNode rootNode) MakeServers(openApidoc.Servers, openApiNode.Context, rootNode); FixRequestBodyReferences(openApidoc); + RegisterComponentsSchemasInGlobalRegistry(openApidoc.Components?.Schemas); + return openApidoc; } @@ -310,6 +311,20 @@ private static bool IsHostValid(string host) var hostPart = host.Split(':').First(); return Uri.CheckHostName(hostPart) != UriHostNameType.Unknown; } + + private static void RegisterComponentsSchemasInGlobalRegistry(IDictionary schemas) + { + if (schemas == null) + { + return; + } + + foreach (var schema in schemas) + { + var refUri = new Uri(OpenApiConstants.V2ReferenceUri + schema.Key); + SchemaRegistry.Global.Register(refUri, schema.Value); + } + } } internal class RequestBodyReferenceFixer : OpenApiVisitorBase diff --git a/src/Microsoft.OpenApi.Readers/V2/OpenApiHeaderDeserializer.cs b/src/Microsoft.OpenApi.Readers/V2/OpenApiHeaderDeserializer.cs index 5c1edcc32..273554219 100644 --- a/src/Microsoft.OpenApi.Readers/V2/OpenApiHeaderDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V2/OpenApiHeaderDeserializer.cs @@ -3,7 +3,7 @@ using System; using System.Globalization; -using Microsoft.OpenApi.Any; +using Json.Schema; using Microsoft.OpenApi.Extensions; using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Readers.Exceptions; @@ -17,6 +17,7 @@ namespace Microsoft.OpenApi.Readers.V2 /// internal static partial class OpenApiV2Deserializer { + private static JsonSchemaBuilder _headerJsonSchemaBuilder; private static readonly FixedFieldMap _headerFixedFields = new FixedFieldMap { { @@ -28,19 +29,19 @@ internal static partial class OpenApiV2Deserializer { "type", (o, n) => { - GetOrCreateSchema(o).Type = n.GetScalarValue(); + o.Schema = GetOrCreateHeaderSchemaBuilder().Type(SchemaTypeConverter.ConvertToSchemaValueType(n.GetScalarValue())); } }, { "format", (o, n) => { - GetOrCreateSchema(o).Format = n.GetScalarValue(); + o.Schema = GetOrCreateHeaderSchemaBuilder().Format(n.GetScalarValue()); } }, { "items", (o, n) => { - GetOrCreateSchema(o).Items = LoadSchema(n); + o.Schema = GetOrCreateHeaderSchemaBuilder().Items(LoadSchema(n)); } }, { @@ -52,81 +53,81 @@ internal static partial class OpenApiV2Deserializer { "default", (o, n) => { - GetOrCreateSchema(o).Default = n.CreateAny(); + o.Schema = GetOrCreateHeaderSchemaBuilder().Default(n.CreateAny().Node); } }, { "maximum", (o, n) => { - GetOrCreateSchema(o).Maximum = decimal.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture); + o.Schema = GetOrCreateHeaderSchemaBuilder().Maximum(decimal.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture)); } }, { "exclusiveMaximum", (o, n) => { - GetOrCreateSchema(o).ExclusiveMaximum = bool.Parse(n.GetScalarValue()); + o.Schema = GetOrCreateHeaderSchemaBuilder().ExclusiveMaximum(decimal.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture)); } }, { "minimum", (o, n) => { - GetOrCreateSchema(o).Minimum = decimal.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture); + o.Schema = GetOrCreateHeaderSchemaBuilder().Minimum(decimal.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture)); } }, { "exclusiveMinimum", (o, n) => { - GetOrCreateSchema(o).ExclusiveMinimum = bool.Parse(n.GetScalarValue()); + o.Schema = GetOrCreateHeaderSchemaBuilder().ExclusiveMinimum(decimal.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture)); } }, { "maxLength", (o, n) => { - GetOrCreateSchema(o).MaxLength = int.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture); + o.Schema = GetOrCreateHeaderSchemaBuilder().MaxLength(uint.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture)); } }, { "minLength", (o, n) => { - GetOrCreateSchema(o).MinLength = int.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture); + o.Schema = GetOrCreateHeaderSchemaBuilder().MinLength(uint.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture)); } }, { "pattern", (o, n) => { - GetOrCreateSchema(o).Pattern = n.GetScalarValue(); + o.Schema = GetOrCreateHeaderSchemaBuilder().Pattern(n.GetScalarValue()); } }, { "maxItems", (o, n) => { - GetOrCreateSchema(o).MaxItems = int.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture); + o.Schema = GetOrCreateHeaderSchemaBuilder().MaxItems(uint.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture)); } }, { "minItems", (o, n) => { - GetOrCreateSchema(o).MinItems = int.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture); + o.Schema = GetOrCreateHeaderSchemaBuilder().MinItems(uint.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture)); } }, { "uniqueItems", (o, n) => { - GetOrCreateSchema(o).UniqueItems = bool.Parse(n.GetScalarValue()); + o.Schema = GetOrCreateHeaderSchemaBuilder().UniqueItems(bool.Parse(n.GetScalarValue())); } }, { "multipleOf", (o, n) => { - GetOrCreateSchema(o).MultipleOf = decimal.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture); + o.Schema = GetOrCreateHeaderSchemaBuilder().MultipleOf(decimal.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture)); } }, { "enum", (o, n) => { - GetOrCreateSchema(o).Enum = n.CreateListOfAny(); + o.Schema = GetOrCreateHeaderSchemaBuilder().Enum(n.CreateListOfAny()).Build(); } - } + } }; private static readonly PatternFieldMap _headerPatternFields = new PatternFieldMap @@ -134,56 +135,29 @@ internal static partial class OpenApiV2Deserializer {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, LoadExtension(p, n))} }; - private static readonly AnyFieldMap _headerAnyFields = - new AnyFieldMap - { - { - OpenApiConstants.Default, - new AnyFieldMapParameter( - p => p.Schema?.Default, - (p, v) => - { - if(p.Schema == null) return; - p.Schema.Default = v; - }, - p => p.Schema) - } - }; - - private static readonly AnyListFieldMap _headerAnyListFields = - new AnyListFieldMap - { - { - OpenApiConstants.Enum, - new AnyListFieldMapParameter( - p => p.Schema?.Enum, - (p, v) => - { - if(p.Schema == null) return; - p.Schema.Enum = v; - }, - p => p.Schema) - }, - }; + private static JsonSchemaBuilder GetOrCreateHeaderSchemaBuilder() + { + _headerJsonSchemaBuilder ??= new JsonSchemaBuilder(); + return _headerJsonSchemaBuilder; + } public static OpenApiHeader LoadHeader(ParseNode node) { var mapNode = node.CheckMapNode("header"); var header = new OpenApiHeader(); + _headerJsonSchemaBuilder = null; + foreach (var property in mapNode) { property.ParseField(header, _headerFixedFields, _headerPatternFields); } - var schema = node.Context.GetFromTempStorage("schema"); + 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; } diff --git a/src/Microsoft.OpenApi.Readers/V2/OpenApiInfoDeserializer.cs b/src/Microsoft.OpenApi.Readers/V2/OpenApiInfoDeserializer.cs index 5854672d3..1259c599c 100644 --- a/src/Microsoft.OpenApi.Readers/V2/OpenApiInfoDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V2/OpenApiInfoDeserializer.cs @@ -2,7 +2,6 @@ // Licensed under the MIT license. using System; -using System.Collections.Generic; using Microsoft.OpenApi.Extensions; using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Readers.ParseNodes; @@ -15,7 +14,7 @@ namespace Microsoft.OpenApi.Readers.V2 /// internal static partial class OpenApiV2Deserializer { - private static FixedFieldMap _infoFixedFields = new FixedFieldMap + private static readonly FixedFieldMap _infoFixedFields = new FixedFieldMap { { "title", (o, n) => @@ -55,7 +54,7 @@ internal static partial class OpenApiV2Deserializer } }; - private static PatternFieldMap _infoPatternFields = new PatternFieldMap + private static readonly PatternFieldMap _infoPatternFields = new PatternFieldMap { {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, LoadExtension(p, n))} }; diff --git a/src/Microsoft.OpenApi.Readers/V2/OpenApiLicenseDeserializer.cs b/src/Microsoft.OpenApi.Readers/V2/OpenApiLicenseDeserializer.cs index 4c4009f57..3cd437fb5 100644 --- a/src/Microsoft.OpenApi.Readers/V2/OpenApiLicenseDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V2/OpenApiLicenseDeserializer.cs @@ -14,7 +14,7 @@ namespace Microsoft.OpenApi.Readers.V2 /// internal static partial class OpenApiV2Deserializer { - private static FixedFieldMap _licenseFixedFields = new FixedFieldMap + private static readonly FixedFieldMap _licenseFixedFields = new FixedFieldMap { { "name", (o, n) => @@ -30,7 +30,7 @@ internal static partial class OpenApiV2Deserializer }, }; - private static PatternFieldMap _licensePatternFields = new PatternFieldMap + private static readonly PatternFieldMap _licensePatternFields = new PatternFieldMap { {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, LoadExtension(p, n))} }; diff --git a/src/Microsoft.OpenApi.Readers/V2/OpenApiOperationDeserializer.cs b/src/Microsoft.OpenApi.Readers/V2/OpenApiOperationDeserializer.cs index b663cb946..a97a004f0 100644 --- a/src/Microsoft.OpenApi.Readers/V2/OpenApiOperationDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V2/OpenApiOperationDeserializer.cs @@ -3,10 +3,9 @@ using System.Collections.Generic; using System.Linq; -using System.Text.Json.Nodes; +using Json.Schema; using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Extensions; -using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Readers.ParseNodes; @@ -165,19 +164,25 @@ private static OpenApiRequestBody CreateFormBody(ParsingContext context, List k.Name, v => { + var schemaBuilder = new JsonSchemaBuilder(); var schema = v.Schema; - schema.Description = v.Description; - schema.Extensions = v.Extensions; - return schema; - }), - Required = new HashSet(formParameters.Where(p => p.Required).Select(p => p.Name)) - } + + foreach (var keyword in schema.Keywords) + { + schemaBuilder.Add(keyword); + } + + schemaBuilder.Description(v.Description); + if (v.Extensions.Any()) + { + schemaBuilder.Extensions(v.Extensions); + } + return schemaBuilder.Build(); + })).Required(new HashSet(formParameters.Where(p => p.Required).Select(p => p.Name))).Build() }; var consumes = context.GetFromTempStorage>(TempStorageKeys.OperationConsumes) ?? @@ -218,7 +223,7 @@ internal static OpenApiRequestBody CreateRequestBody( requestBody.Extensions[OpenApiConstants.BodyName] = new OpenApiAny(bodyParameter.Name); return requestBody; } - + private static OpenApiTag LoadTagByReference( ParsingContext context, string tagName) diff --git a/src/Microsoft.OpenApi.Readers/V2/OpenApiParameterDeserializer.cs b/src/Microsoft.OpenApi.Readers/V2/OpenApiParameterDeserializer.cs index fc013e55d..2108f188e 100644 --- a/src/Microsoft.OpenApi.Readers/V2/OpenApiParameterDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V2/OpenApiParameterDeserializer.cs @@ -4,6 +4,8 @@ using System; using System.Collections.Generic; using System.Globalization; +using System.Linq; +using Json.Schema; using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Extensions; using Microsoft.OpenApi.Models; @@ -17,6 +19,7 @@ namespace Microsoft.OpenApi.Readers.V2 /// internal static partial class OpenApiV2Deserializer { + private static JsonSchemaBuilder _parameterJsonSchemaBuilder; private static readonly FixedFieldMap _parameterFixedFields = new FixedFieldMap { @@ -59,13 +62,13 @@ internal static partial class OpenApiV2Deserializer { "type", (o, n) => { - GetOrCreateSchema(o).Type = n.GetScalarValue(); + o.Schema = GetOrCreateParameterSchemaBuilder().Type(SchemaTypeConverter.ConvertToSchemaValueType(n.GetScalarValue())); } }, { "items", (o, n) => { - GetOrCreateSchema(o).Items = LoadSchema(n); + o.Schema = GetOrCreateParameterSchemaBuilder().Items(LoadSchema(n)); } }, { @@ -77,55 +80,55 @@ internal static partial class OpenApiV2Deserializer { "format", (o, n) => { - GetOrCreateSchema(o).Format = n.GetScalarValue(); + o.Schema = GetOrCreateParameterSchemaBuilder().Format(n.GetScalarValue()); } }, { "minimum", (o, n) => { - GetOrCreateSchema(o).Minimum = decimal.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture); + o.Schema = GetOrCreateParameterSchemaBuilder().Minimum(decimal.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture)); } }, { "maximum", (o, n) => { - GetOrCreateSchema(o).Maximum = decimal.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture); + o.Schema = GetOrCreateParameterSchemaBuilder().Maximum(decimal.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture)); } }, { "maxLength", (o, n) => { - GetOrCreateSchema(o).MaxLength = int.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture); + o.Schema = GetOrCreateParameterSchemaBuilder().MaxLength(uint.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture)); } }, { "minLength", (o, n) => { - GetOrCreateSchema(o).MinLength = int.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture); + o.Schema = GetOrCreateParameterSchemaBuilder().MinLength(uint.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture)); } }, { "readOnly", (o, n) => { - GetOrCreateSchema(o).ReadOnly = bool.Parse(n.GetScalarValue()); + o.Schema = GetOrCreateParameterSchemaBuilder().ReadOnly(bool.Parse(n.GetScalarValue())); } }, { "default", (o, n) => { - GetOrCreateSchema(o).Default = n.CreateAny(); + o.Schema = GetOrCreateParameterSchemaBuilder().Default(n.CreateAny().Node); } }, { "pattern", (o, n) => { - GetOrCreateSchema(o).Pattern = n.GetScalarValue(); + o.Schema = GetOrCreateParameterSchemaBuilder().Pattern(n.GetScalarValue()); } }, { "enum", (o, n) => { - GetOrCreateSchema(o).Enum = n.CreateListOfAny(); + o.Schema = GetOrCreateParameterSchemaBuilder().Enum(n.CreateListOfAny()).Build(); } }, { @@ -142,40 +145,6 @@ internal static partial class OpenApiV2Deserializer {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, LoadExtension(p, n))} }; - private static readonly AnyFieldMap _parameterAnyFields = - new AnyFieldMap - { - { - OpenApiConstants.Default, - new AnyFieldMapParameter( - 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 AnyListFieldMap - { - { - OpenApiConstants.Enum, - new AnyListFieldMapParameter( - p => p.Schema?.Enum, - (p, v) => { - if (p.Schema != null || v != null && v.Count > 0) - { - GetOrCreateSchema(p).Enum = v; - } - }, - p => p.Schema) - }, - }; - private static void LoadStyle(OpenApiParameter p, string v) { switch (v) @@ -204,25 +173,11 @@ private static void LoadStyle(OpenApiParameter p, string v) return; } } - - private static OpenApiSchema GetOrCreateSchema(OpenApiParameter p) - { - if (p.Schema == null) - { - p.Schema = new OpenApiSchema(); - } - - return p.Schema; - } - - private static OpenApiSchema GetOrCreateSchema(OpenApiHeader p) + + private static JsonSchemaBuilder GetOrCreateParameterSchemaBuilder() { - if (p.Schema == null) - { - p.Schema = new OpenApiSchema(); - } - - return p.Schema; + _parameterJsonSchemaBuilder ??= new JsonSchemaBuilder(); + return _parameterJsonSchemaBuilder; } private static void ProcessIn(OpenApiParameter o, ParseNode n) @@ -276,13 +231,11 @@ public static OpenApiParameter LoadParameter(ParseNode node, bool loadRequestBod } var parameter = new OpenApiParameter(); + _parameterJsonSchemaBuilder = null; ParseMap(mapNode, parameter, _parameterFixedFields, _parameterPatternFields); - ProcessAnyFields(mapNode, parameter, _parameterAnyFields); - ProcessAnyListFields(mapNode, parameter, _parameterAnyListFields); - - var schema = node.Context.GetFromTempStorage("schema"); + var schema = node.Context.GetFromTempStorage("schema"); if (schema != null) { parameter.Schema = schema; diff --git a/src/Microsoft.OpenApi.Readers/V2/OpenApiPathItemDeserializer.cs b/src/Microsoft.OpenApi.Readers/V2/OpenApiPathItemDeserializer.cs index d905ea42e..2e56dc2fb 100644 --- a/src/Microsoft.OpenApi.Readers/V2/OpenApiPathItemDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V2/OpenApiPathItemDeserializer.cs @@ -68,7 +68,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.Readers/V2/OpenApiPathsDeserializer.cs index 2aa5de979..f25116844 100644 --- a/src/Microsoft.OpenApi.Readers/V2/OpenApiPathsDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V2/OpenApiPathsDeserializer.cs @@ -13,9 +13,9 @@ namespace Microsoft.OpenApi.Readers.V2 /// internal static partial class OpenApiV2Deserializer { - private static FixedFieldMap _pathsFixedFields = new FixedFieldMap(); + private static readonly FixedFieldMap _pathsFixedFields = new FixedFieldMap(); - private static PatternFieldMap _pathsPatternFields = new PatternFieldMap + private static readonly PatternFieldMap _pathsPatternFields = new PatternFieldMap { {s => s.StartsWith("/"), (o, k, n) => o.Add(k, LoadPathItem(n))}, {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, LoadExtension(p, n))} diff --git a/src/Microsoft.OpenApi.Readers/V2/OpenApiResponseDeserializer.cs b/src/Microsoft.OpenApi.Readers/V2/OpenApiResponseDeserializer.cs index cfdbfa949..3491bc161 100644 --- a/src/Microsoft.OpenApi.Readers/V2/OpenApiResponseDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V2/OpenApiResponseDeserializer.cs @@ -2,7 +2,7 @@ // Licensed under the MIT license. using System.Collections.Generic; -using Microsoft.OpenApi.Any; +using Json.Schema; using Microsoft.OpenApi.Extensions; using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Readers.ParseNodes; @@ -79,7 +79,7 @@ private static void ProcessProduces(MapNode mapNode, OpenApiResponse response, P { foreach (var produce in produces) { - var schema = context.GetFromTempStorage(TempStorageKeys.ResponseSchema, response); + var schema = context.GetFromTempStorage(TempStorageKeys.ResponseSchema, response); if (response.Content.ContainsKey(produce) && response.Content[produce] != null) { @@ -132,7 +132,7 @@ private static void LoadExample(OpenApiResponse response, string mediaType, Pars { mediaTypeObject = new OpenApiMediaType { - Schema = node.Context.GetFromTempStorage(TempStorageKeys.ResponseSchema, response) + Schema = node.Context.GetFromTempStorage(TempStorageKeys.ResponseSchema, response) }; response.Content.Add(mediaType, mediaTypeObject); } diff --git a/src/Microsoft.OpenApi.Readers/V2/OpenApiSchemaDeserializer.cs b/src/Microsoft.OpenApi.Readers/V2/OpenApiSchemaDeserializer.cs deleted file mode 100644 index 0bdaeda3a..000000000 --- a/src/Microsoft.OpenApi.Readers/V2/OpenApiSchemaDeserializer.cs +++ /dev/null @@ -1,264 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. - -using System.Collections.Generic; -using System.Globalization; -using Microsoft.OpenApi.Any; -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 FixedFieldMap - { - { - "title", (o, n) => - { - o.Title = n.GetScalarValue(); - } - }, - { - "multipleOf", (o, n) => - { - o.MultipleOf = decimal.Parse(n.GetScalarValue(), NumberStyles.Float, CultureInfo.InvariantCulture); - } - }, - { - "maximum", (o, n) => - { - o.Maximum = decimal.Parse(n.GetScalarValue(), NumberStyles.Float, CultureInfo.InvariantCulture); - } - }, - { - "exclusiveMaximum", (o, n) => - { - o.ExclusiveMaximum = bool.Parse(n.GetScalarValue()); - } - }, - { - "minimum", (o, n) => - { - o.Minimum = decimal.Parse(n.GetScalarValue(), NumberStyles.Float, CultureInfo.InvariantCulture); - } - }, - { - "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 OpenApiDiscriminator - { - 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 PatternFieldMap - { - {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, LoadExtension(p, n))} - }; - - private static readonly AnyFieldMap _schemaAnyFields = new AnyFieldMap - { - { - OpenApiConstants.Default, - new AnyFieldMapParameter( - s => s.Default, - (s, v) => s.Default = v, - s => s) - }, - { - OpenApiConstants.Example, - new AnyFieldMapParameter( - s => s.Example, - (s, v) => s.Example = v, - s => s) } - }; - - private static readonly AnyListFieldMap _schemaAnyListFields = new AnyListFieldMap - { - { - OpenApiConstants.Enum, - new AnyListFieldMapParameter( - 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 index f2e2ae2e9..433556504 100644 --- a/src/Microsoft.OpenApi.Readers/V2/OpenApiV2Deserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V2/OpenApiV2Deserializer.cs @@ -48,8 +48,8 @@ private static void ProcessAnyFields( { mapNode.Context.StartObject(anyFieldName); var anyFieldValue = anyFieldMap[anyFieldName].PropertyGetter(domainObject); - - if(anyFieldValue == null) + + if (anyFieldValue == null) { anyFieldMap[anyFieldName].PropertySetter(domainObject, null); } @@ -79,7 +79,7 @@ private static void ProcessAnyListFields( { try { - var newProperty = new List(); + var newProperty = new List(); mapNode.Context.StartObject(anyListFieldName); @@ -140,7 +140,7 @@ private static void ProcessAnyMapFields( } } } - + public static OpenApiAny LoadAny(ParseNode node) { return node.CreateAny(); diff --git a/src/Microsoft.OpenApi.Readers/V2/OpenApiV2VersionService.cs b/src/Microsoft.OpenApi.Readers/V2/OpenApiV2VersionService.cs index 47763c716..f511544c0 100644 --- a/src/Microsoft.OpenApi.Readers/V2/OpenApiV2VersionService.cs +++ b/src/Microsoft.OpenApi.Readers/V2/OpenApiV2VersionService.cs @@ -3,7 +3,7 @@ using System; using System.Collections.Generic; -using System.Text.Json.Nodes; +using Json.Schema; using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Exceptions; using Microsoft.OpenApi.Interfaces; @@ -46,7 +46,7 @@ public OpenApiV2VersionService(OpenApiDiagnostic diagnostic) [typeof(OpenApiPaths)] = OpenApiV2Deserializer.LoadPaths, [typeof(OpenApiResponse)] = OpenApiV2Deserializer.LoadResponse, [typeof(OpenApiResponses)] = OpenApiV2Deserializer.LoadResponses, - [typeof(OpenApiSchema)] = OpenApiV2Deserializer.LoadSchema, + [typeof(JsonSchema)] = OpenApiV2Deserializer.LoadSchema, [typeof(OpenApiSecurityRequirement)] = OpenApiV2Deserializer.LoadSecurityRequirement, [typeof(OpenApiSecurityScheme)] = OpenApiV2Deserializer.LoadSecurityScheme, [typeof(OpenApiTag)] = OpenApiV2Deserializer.LoadTag, diff --git a/src/Microsoft.OpenApi.Readers/V2/OpenApiXmlDeserializer.cs b/src/Microsoft.OpenApi.Readers/V2/OpenApiXmlDeserializer.cs index ac7db2db6..9824bc477 100644 --- a/src/Microsoft.OpenApi.Readers/V2/OpenApiXmlDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V2/OpenApiXmlDeserializer.cs @@ -2,7 +2,6 @@ // Licensed under the MIT license. using System; -using Microsoft.OpenApi.Exceptions; using Microsoft.OpenApi.Extensions; using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Readers.Exceptions; diff --git a/src/Microsoft.OpenApi.Readers/V3/OpenApiSchemaDeserializer.cs b/src/Microsoft.OpenApi.Readers/V3/JsonSchemaDeserializer.cs similarity index 50% rename from src/Microsoft.OpenApi.Readers/V3/OpenApiSchemaDeserializer.cs rename to src/Microsoft.OpenApi.Readers/V3/JsonSchemaDeserializer.cs index 8f465e38e..4f5796155 100644 --- a/src/Microsoft.OpenApi.Readers/V3/OpenApiSchemaDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V3/JsonSchemaDeserializer.cs @@ -1,13 +1,18 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Text.Json.Nodes; +using Json.Schema; +using Json.Schema.OpenApi; using Microsoft.OpenApi.Any; -using Microsoft.OpenApi.Extensions; +using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Extensions; using Microsoft.OpenApi.Readers.ParseNodes; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; +using JsonSchema = Json.Schema.JsonSchema; namespace Microsoft.OpenApi.Readers.V3 { @@ -17,144 +22,151 @@ namespace Microsoft.OpenApi.Readers.V3 /// internal static partial class OpenApiV3Deserializer { - private static readonly FixedFieldMap _schemaFixedFields = new FixedFieldMap + private static readonly FixedFieldMap _schemaFixedFields = new() { { "title", (o, n) => { - o.Title = n.GetScalarValue(); + o.Title(n.GetScalarValue()); } }, { "multipleOf", (o, n) => { - o.MultipleOf = decimal.Parse(n.GetScalarValue(), NumberStyles.Float, CultureInfo.InvariantCulture); + o.MultipleOf(decimal.Parse(n.GetScalarValue(), NumberStyles.Float, CultureInfo.InvariantCulture)); } }, { "maximum", (o, n) => { - o.Maximum = decimal.Parse(n.GetScalarValue(), NumberStyles.Float, CultureInfo.InvariantCulture); + o.Maximum(decimal.Parse(n.GetScalarValue(), NumberStyles.Float, CultureInfo.InvariantCulture)); } }, { "exclusiveMaximum", (o, n) => { - o.ExclusiveMaximum = bool.Parse(n.GetScalarValue()); + o.ExclusiveMaximum(bool.Parse(n.GetScalarValue())); } }, { "minimum", (o, n) => { - o.Minimum = decimal.Parse(n.GetScalarValue(), NumberStyles.Float, CultureInfo.InvariantCulture); + o.Minimum(decimal.Parse(n.GetScalarValue(), NumberStyles.Float, CultureInfo.InvariantCulture)); } }, { "exclusiveMinimum", (o, n) => { - o.ExclusiveMinimum = bool.Parse(n.GetScalarValue()); + o.ExclusiveMinimum(bool.Parse(n.GetScalarValue())); } }, { "maxLength", (o, n) => { - o.MaxLength = int.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture); + o.MaxLength(uint.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture)); } }, { "minLength", (o, n) => { - o.MinLength = int.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture); + o.MinLength(uint.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture)); } }, { "pattern", (o, n) => { - o.Pattern = n.GetScalarValue(); + o.Pattern(n.GetScalarValue()); } }, { "maxItems", (o, n) => { - o.MaxItems = int.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture); + o.MaxItems(uint.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture)); } }, { "minItems", (o, n) => { - o.MinItems = int.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture); + o.MinItems(uint.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture)); } }, { "uniqueItems", (o, n) => { - o.UniqueItems = bool.Parse(n.GetScalarValue()); + o.UniqueItems(bool.Parse(n.GetScalarValue())); } }, { "maxProperties", (o, n) => { - o.MaxProperties = int.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture); + o.MaxProperties(uint.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture)); } }, { "minProperties", (o, n) => { - o.MinProperties = int.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture); + o.MinProperties(uint.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture)); } }, { "required", (o, n) => { - o.Required = new HashSet(n.CreateSimpleList(n2 => n2.GetScalarValue())); + o.Required(new HashSet(n.CreateSimpleList(n2 => n2.GetScalarValue()))); } }, { "enum", (o, n) => { - o.Enum = n.CreateListOfAny(); + o.Enum(n.CreateListOfAny()); } }, { "type", (o, n) => { - o.Type = n.GetScalarValue(); + if(n is ListNode) + { + o.Type(n.CreateSimpleList(s => SchemaTypeConverter.ConvertToSchemaValueType(s.GetScalarValue()))); + } + else + { + o.Type(SchemaTypeConverter.ConvertToSchemaValueType(n.GetScalarValue())); + } } }, { "allOf", (o, n) => { - o.AllOf = n.CreateList(LoadSchema); + o.AllOf(n.CreateList(LoadSchema)); } }, { "oneOf", (o, n) => { - o.OneOf = n.CreateList(LoadSchema); + o.OneOf(n.CreateList(LoadSchema)); } }, { "anyOf", (o, n) => { - o.AnyOf = n.CreateList(LoadSchema); + o.AnyOf(n.CreateList(LoadSchema)); } }, { "not", (o, n) => { - o.Not = LoadSchema(n); + o.Not(LoadSchema(n)); } }, { "items", (o, n) => { - o.Items = LoadSchema(n); + o.Items(LoadSchema(n)); } }, { "properties", (o, n) => { - o.Properties = n.CreateMap(LoadSchema); + o.Properties(n.CreateMap(LoadSchema)); } }, { @@ -162,145 +174,127 @@ internal static partial class OpenApiV3Deserializer { if (n is ValueNode) { - o.AdditionalPropertiesAllowed = bool.Parse(n.GetScalarValue()); + o.AdditionalPropertiesAllowed(bool.Parse(n.GetScalarValue())); } else { - o.AdditionalProperties = LoadSchema(n); + o.AdditionalProperties(LoadSchema(n)); } } }, { "description", (o, n) => { - o.Description = n.GetScalarValue(); + o.Description(n.GetScalarValue()); } }, { "format", (o, n) => { - o.Format = n.GetScalarValue(); + o.Format(n.GetScalarValue()); } }, { "default", (o, n) => { - o.Default = n.CreateAny(); + o.Default(n.CreateAny().Node); } }, - { "nullable", (o, n) => { - o.Nullable = bool.Parse(n.GetScalarValue()); + o.Nullable(bool.Parse(n.GetScalarValue())); } }, { "discriminator", (o, n) => { - o.Discriminator = LoadDiscriminator(n); + var discriminator = LoadDiscriminator(n); + o.Discriminator(discriminator); } }, { "readOnly", (o, n) => { - o.ReadOnly = bool.Parse(n.GetScalarValue()); + o.ReadOnly(bool.Parse(n.GetScalarValue())); } }, { "writeOnly", (o, n) => { - o.WriteOnly = bool.Parse(n.GetScalarValue()); + o.WriteOnly(bool.Parse(n.GetScalarValue())); } }, { "xml", (o, n) => { - o.Xml = LoadXml(n); + var xml = LoadXml(n); + o.Xml(xml.Namespace, xml.Name, xml.Prefix, xml.Attribute, xml.Wrapped, + (IReadOnlyDictionary)xml.Extensions); } }, { "externalDocs", (o, n) => { - o.ExternalDocs = LoadExternalDocs(n); + var externalDocs = LoadExternalDocs(n); + o.ExternalDocs(externalDocs.Url, externalDocs.Description, + (IReadOnlyDictionary)externalDocs.Extensions); } }, { "example", (o, n) => { - o.Example = n.CreateAny(); + if(n is ListNode) + { + o.Examples(n.CreateSimpleList(s => (JsonNode)s.GetScalarValue())); + } + else + { + o.Example(n.CreateAny().Node); + } } }, { "deprecated", (o, n) => { - o.Deprecated = bool.Parse(n.GetScalarValue()); + o.Deprecated(bool.Parse(n.GetScalarValue())); } }, }; - private static readonly PatternFieldMap _schemaPatternFields = new PatternFieldMap + private static readonly PatternFieldMap _schemaPatternFields = new PatternFieldMap { - {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, LoadExtension(p,n))} + {s => s.StartsWith("x-"), (o, p, n) => o.Extensions(LoadExtensions(p, LoadExtension(p, n)))} }; - private static readonly AnyFieldMap _schemaAnyFields = new AnyFieldMap - { - { - OpenApiConstants.Default, - new AnyFieldMapParameter( - s => s.Default, - (s, v) => s.Default = v, - s => s) - }, - { - OpenApiConstants.Example, - new AnyFieldMapParameter( - s => s.Example, - (s, v) => s.Example = v, - s => s) - } - }; - - private static readonly AnyListFieldMap _schemaAnyListFields = new AnyListFieldMap - { - { - OpenApiConstants.Enum, - new AnyListFieldMapParameter( - s => s.Enum, - (s, v) => s.Enum = v, - s => s) - } - }; - - public static OpenApiSchema LoadSchema(ParseNode node) + public static JsonSchema LoadSchema(ParseNode node) { var mapNode = node.CheckMapNode(OpenApiConstants.Schema); + var builder = new JsonSchemaBuilder(); + // check for a $ref and if present, add it to the builder as a Ref keyword var pointer = mapNode.GetReferencePointer(); if (pointer != null) - { - var description = node.Context.VersionService.GetReferenceScalarValues(mapNode, OpenApiConstants.Description); - var summary = node.Context.VersionService.GetReferenceScalarValues(mapNode, OpenApiConstants.Summary); - - return new OpenApiSchema - { - UnresolvedReference = true, - Reference = node.Context.VersionService.ConvertToOpenApiReference(pointer, ReferenceType.Schema, summary, description) - }; + { + return builder.Ref(pointer); } - - var schema = new OpenApiSchema(); foreach (var propertyNode in mapNode) { - propertyNode.ParseField(schema, _schemaFixedFields, _schemaPatternFields); + propertyNode.ParseField(builder, _schemaFixedFields, _schemaPatternFields); } - ProcessAnyFields(mapNode, schema, _schemaAnyFields); - ProcessAnyListFields(mapNode, schema, _schemaAnyListFields); - + var schema = builder.Build(); return schema; } + + private static Dictionary LoadExtensions(string value, IOpenApiExtension extension) + { + var extensions = new Dictionary + { + { value, extension } + }; + return extensions; + } } } diff --git a/src/Microsoft.OpenApi.Readers/V3/OpenApiComponentsDeserializer.cs b/src/Microsoft.OpenApi.Readers/V3/OpenApiComponentsDeserializer.cs index 3845e23c0..227ee2e84 100644 --- a/src/Microsoft.OpenApi.Readers/V3/OpenApiComponentsDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V3/OpenApiComponentsDeserializer.cs @@ -2,8 +2,11 @@ // Licensed under the MIT license. using System; +using System.Reflection; +using System.Text.Json; +using System.Text.Json.Nodes; +using Json.Schema; using Microsoft.OpenApi.Extensions; -using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Readers.ParseNodes; @@ -15,9 +18,9 @@ namespace Microsoft.OpenApi.Readers.V3 /// internal static partial class OpenApiV3Deserializer { - private static FixedFieldMap _componentsFixedFields = new FixedFieldMap + private static readonly FixedFieldMap _componentsFixedFields = new FixedFieldMap { - {"schemas", (o, n) => o.Schemas = n.CreateMapWithReference(ReferenceType.Schema, LoadSchema)}, + {"schemas", (o, n) => o.Schemas = n.CreateMap(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)}, @@ -29,7 +32,7 @@ internal static partial class OpenApiV3Deserializer {"pathItems", (o, n) => o.PathItems = n.CreateMapWithReference(ReferenceType.PathItem, LoadPathItem)} }; - private static PatternFieldMap _componentsPatternFields = + private static readonly PatternFieldMap _componentsPatternFields = new PatternFieldMap { {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, LoadExtension(p, n))} @@ -41,6 +44,12 @@ public static OpenApiComponents LoadComponents(ParseNode node) var components = new OpenApiComponents(); ParseMap(mapNode, components, _componentsFixedFields, _componentsPatternFields); + + foreach (var schema in components.Schemas) + { + var refUri = new Uri(OpenApiConstants.V3ReferenceUri + schema.Key); + SchemaRegistry.Global.Register(refUri, schema.Value); + } return components; } diff --git a/src/Microsoft.OpenApi.Readers/V3/OpenApiContactDeserializer.cs b/src/Microsoft.OpenApi.Readers/V3/OpenApiContactDeserializer.cs index 151a12354..e2893d628 100644 --- a/src/Microsoft.OpenApi.Readers/V3/OpenApiContactDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V3/OpenApiContactDeserializer.cs @@ -14,7 +14,7 @@ namespace Microsoft.OpenApi.Readers.V3 /// internal static partial class OpenApiV3Deserializer { - private static FixedFieldMap _contactFixedFields = new FixedFieldMap + private static readonly FixedFieldMap _contactFixedFields = new FixedFieldMap { { "name", (o, n) => @@ -36,7 +36,7 @@ internal static partial class OpenApiV3Deserializer }, }; - private static PatternFieldMap _contactPatternFields = new PatternFieldMap + private static readonly PatternFieldMap _contactPatternFields = new PatternFieldMap { {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, LoadExtension(p,n))} }; diff --git a/src/Microsoft.OpenApi.Readers/V3/OpenApiDocumentDeserializer.cs b/src/Microsoft.OpenApi.Readers/V3/OpenApiDocumentDeserializer.cs index 858f13f0d..2084d9644 100644 --- a/src/Microsoft.OpenApi.Readers/V3/OpenApiDocumentDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V3/OpenApiDocumentDeserializer.cs @@ -13,7 +13,7 @@ namespace Microsoft.OpenApi.Readers.V3 /// internal static partial class OpenApiV3Deserializer { - private static FixedFieldMap _openApiFixedFields = new FixedFieldMap + private static readonly FixedFieldMap _openApiFixedFields = new FixedFieldMap { { "openapi", (o, n) => @@ -21,10 +21,8 @@ internal static partial class OpenApiV3Deserializer } /* Version is valid field but we already parsed it */ }, {"info", (o, n) => o.Info = LoadInfo(n)}, - {"jsonSchemaDialect", (o, n) => o.JsonSchemaDialect = n.GetScalarValue() }, {"servers", (o, n) => o.Servers = n.CreateList(LoadServer)}, {"paths", (o, n) => o.Paths = LoadPaths(n)}, - {"webhooks", (o, n) => o.Webhooks = LoadPaths(n)}, {"components", (o, n) => o.Components = LoadComponents(n)}, {"tags", (o, n) => {o.Tags = n.CreateList(LoadTag); foreach (var tag in o.Tags) @@ -40,7 +38,7 @@ internal static partial class OpenApiV3Deserializer {"security", (o, n) => o.SecurityRequirements = n.CreateList(LoadSecurityRequirement)} }; - private static PatternFieldMap _openApiPatternFields = new PatternFieldMap + 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))} diff --git a/src/Microsoft.OpenApi.Readers/V3/OpenApiEncodingDeserializer.cs b/src/Microsoft.OpenApi.Readers/V3/OpenApiEncodingDeserializer.cs index fc2f990e7..d965a7a58 100644 --- a/src/Microsoft.OpenApi.Readers/V3/OpenApiEncodingDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V3/OpenApiEncodingDeserializer.cs @@ -1,7 +1,6 @@ // 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.Readers.ParseNodes; diff --git a/src/Microsoft.OpenApi.Readers/V3/OpenApiExampleDeserializer.cs b/src/Microsoft.OpenApi.Readers/V3/OpenApiExampleDeserializer.cs index 01103efde..1e114ad73 100644 --- a/src/Microsoft.OpenApi.Readers/V3/OpenApiExampleDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V3/OpenApiExampleDeserializer.cs @@ -1,8 +1,6 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. -using System.Linq; -using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Extensions; using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Readers.ParseNodes; @@ -57,10 +55,7 @@ public static OpenApiExample LoadExample(ParseNode node) var pointer = mapNode.GetReferencePointer(); if (pointer != null) { - var description = node.Context.VersionService.GetReferenceScalarValues(mapNode, OpenApiConstants.Description); - var summary = node.Context.VersionService.GetReferenceScalarValues(mapNode, OpenApiConstants.Summary); - - return mapNode.GetReferencedObject(ReferenceType.Example, pointer, summary, description); + return mapNode.GetReferencedObject(ReferenceType.Example, pointer); } var example = new OpenApiExample(); diff --git a/src/Microsoft.OpenApi.Readers/V3/OpenApiExternalDocsDeserializer.cs b/src/Microsoft.OpenApi.Readers/V3/OpenApiExternalDocsDeserializer.cs index 920b84192..6c6cf6e91 100644 --- a/src/Microsoft.OpenApi.Readers/V3/OpenApiExternalDocsDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V3/OpenApiExternalDocsDeserializer.cs @@ -32,13 +32,13 @@ internal static partial class OpenApiV3Deserializer }, }; - private static readonly PatternFieldMap _externalDocsPatternFields = - new PatternFieldMap { + private static readonly PatternFieldMap _externalDocsPatternFields = + new PatternFieldMap { {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) { var mapNode = node.CheckMapNode("externalDocs"); diff --git a/src/Microsoft.OpenApi.Readers/V3/OpenApiHeaderDeserializer.cs b/src/Microsoft.OpenApi.Readers/V3/OpenApiHeaderDeserializer.cs index 488908f55..1616d67f0 100644 --- a/src/Microsoft.OpenApi.Readers/V3/OpenApiHeaderDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V3/OpenApiHeaderDeserializer.cs @@ -1,8 +1,6 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. -using System.Linq; -using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Extensions; using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Readers.ParseNodes; @@ -91,10 +89,7 @@ public static OpenApiHeader LoadHeader(ParseNode node) var pointer = mapNode.GetReferencePointer(); if (pointer != null) { - var description = node.Context.VersionService.GetReferenceScalarValues(mapNode, OpenApiConstants.Description); - var summary = node.Context.VersionService.GetReferenceScalarValues(mapNode, OpenApiConstants.Summary); - - return mapNode.GetReferencedObject(ReferenceType.Header, pointer, summary, description); + return mapNode.GetReferencedObject(ReferenceType.Header, pointer); } var header = new OpenApiHeader(); diff --git a/src/Microsoft.OpenApi.Readers/V3/OpenApiInfoDeserializer.cs b/src/Microsoft.OpenApi.Readers/V3/OpenApiInfoDeserializer.cs index 073c3d95f..b8a14b9b6 100644 --- a/src/Microsoft.OpenApi.Readers/V3/OpenApiInfoDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V3/OpenApiInfoDeserializer.cs @@ -2,7 +2,6 @@ // Licensed under the MIT license. using System; -using System.Collections.Generic; using Microsoft.OpenApi.Extensions; using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Readers.ParseNodes; @@ -15,7 +14,7 @@ namespace Microsoft.OpenApi.Readers.V3 /// internal static partial class OpenApiV3Deserializer { - public static FixedFieldMap InfoFixedFields = new FixedFieldMap + public static readonly FixedFieldMap InfoFixedFields = new FixedFieldMap { { "title", (o, n) => @@ -29,12 +28,6 @@ internal static partial class OpenApiV3Deserializer o.Version = n.GetScalarValue(); } }, - { - "summary", (o, n) => - { - o.Summary = n.GetScalarValue(); - } - }, { "description", (o, n) => { @@ -61,7 +54,7 @@ internal static partial class OpenApiV3Deserializer } }; - public static PatternFieldMap InfoPatternFields = new PatternFieldMap + public static readonly PatternFieldMap InfoPatternFields = new PatternFieldMap { {s => s.StartsWith("x-"), (o, k, n) => o.AddExtension(k,LoadExtension(k, n))} }; diff --git a/src/Microsoft.OpenApi.Readers/V3/OpenApiLicenseDeserializer.cs b/src/Microsoft.OpenApi.Readers/V3/OpenApiLicenseDeserializer.cs index 604d1ccbb..e0149ba67 100644 --- a/src/Microsoft.OpenApi.Readers/V3/OpenApiLicenseDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V3/OpenApiLicenseDeserializer.cs @@ -14,7 +14,7 @@ namespace Microsoft.OpenApi.Readers.V3 /// internal static partial class OpenApiV3Deserializer { - private static FixedFieldMap _licenseFixedFields = new FixedFieldMap + private static readonly FixedFieldMap _licenseFixedFields = new FixedFieldMap { { "name", (o, n) => @@ -22,12 +22,6 @@ internal static partial class OpenApiV3Deserializer o.Name = n.GetScalarValue(); } }, - { - "identifier", (o, n) => - { - o.Identifier = n.GetScalarValue(); - } - }, { "url", (o, n) => { @@ -36,7 +30,7 @@ internal static partial class OpenApiV3Deserializer }, }; - private static PatternFieldMap _licensePatternFields = new PatternFieldMap + private static readonly PatternFieldMap _licensePatternFields = new PatternFieldMap { {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, LoadExtension(p,n))} }; diff --git a/src/Microsoft.OpenApi.Readers/V3/OpenApiLinkDeserializer.cs b/src/Microsoft.OpenApi.Readers/V3/OpenApiLinkDeserializer.cs index c5419b483..7bf4c650b 100644 --- a/src/Microsoft.OpenApi.Readers/V3/OpenApiLinkDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V3/OpenApiLinkDeserializer.cs @@ -1,7 +1,6 @@ // 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; @@ -62,10 +61,7 @@ public static OpenApiLink LoadLink(ParseNode node) var pointer = mapNode.GetReferencePointer(); if (pointer != null) { - var description = node.Context.VersionService.GetReferenceScalarValues(mapNode, OpenApiConstants.Description); - var summary = node.Context.VersionService.GetReferenceScalarValues(mapNode, OpenApiConstants.Summary); - - return mapNode.GetReferencedObject(ReferenceType.Link, pointer, summary, description); + return mapNode.GetReferencedObject(ReferenceType.Link, pointer); } ParseMap(mapNode, link, _linkFixedFields, _linkPatternFields); diff --git a/src/Microsoft.OpenApi.Readers/V3/OpenApiMediaTypeDeserializer.cs b/src/Microsoft.OpenApi.Readers/V3/OpenApiMediaTypeDeserializer.cs index c8bd3d240..b9d64863c 100644 --- a/src/Microsoft.OpenApi.Readers/V3/OpenApiMediaTypeDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V3/OpenApiMediaTypeDeserializer.cs @@ -1,10 +1,6 @@ // 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.Extensions; using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Readers.ParseNodes; @@ -63,7 +59,6 @@ internal static partial class OpenApiV3Deserializer } }; - private static readonly AnyMapFieldMap _mediaTypeAnyMapOpenApiExampleFields = new AnyMapFieldMap { @@ -82,7 +77,6 @@ public static OpenApiMediaType LoadMediaType(ParseNode node) var mapNode = node.CheckMapNode(OpenApiConstants.Content); var mediaType = new OpenApiMediaType(); - ParseMap(mapNode, mediaType, _mediaTypeFixedFields, _mediaTypePatternFields); ProcessAnyFields(mapNode, mediaType, _mediaTypeAnyFields); diff --git a/src/Microsoft.OpenApi.Readers/V3/OpenApiParameterDeserializer.cs b/src/Microsoft.OpenApi.Readers/V3/OpenApiParameterDeserializer.cs index 14ed27f24..9d65dfad1 100644 --- a/src/Microsoft.OpenApi.Readers/V3/OpenApiParameterDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V3/OpenApiParameterDeserializer.cs @@ -3,7 +3,6 @@ using System; using System.Linq; -using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Extensions; using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Readers.ParseNodes; @@ -30,16 +29,9 @@ internal static partial class OpenApiV3Deserializer { 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; } }, { @@ -147,10 +139,7 @@ public static OpenApiParameter LoadParameter(ParseNode node) var pointer = mapNode.GetReferencePointer(); if (pointer != null) { - var description = node.Context.VersionService.GetReferenceScalarValues(mapNode, OpenApiConstants.Description); - var summary = node.Context.VersionService.GetReferenceScalarValues(mapNode, OpenApiConstants.Summary); - - return mapNode.GetReferencedObject(ReferenceType.Parameter, pointer, summary, description); + return mapNode.GetReferencedObject(ReferenceType.Parameter, pointer); } var parameter = new OpenApiParameter(); diff --git a/src/Microsoft.OpenApi.Readers/V3/OpenApiPathItemDeserializer.cs b/src/Microsoft.OpenApi.Readers/V3/OpenApiPathItemDeserializer.cs index e29a4735c..458f29228 100644 --- a/src/Microsoft.OpenApi.Readers/V3/OpenApiPathItemDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V3/OpenApiPathItemDeserializer.cs @@ -1,7 +1,6 @@ // 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; @@ -16,12 +15,12 @@ internal static partial class OpenApiV3Deserializer { private static readonly FixedFieldMap _pathItemFixedFields = new FixedFieldMap { - + { "$ref", (o,n) => { o.Reference = new OpenApiReference() { ExternalResource = n.GetScalarValue() }; o.UnresolvedReference =true; - } + } }, { "summary", (o, n) => @@ -61,13 +60,10 @@ public static OpenApiPathItem LoadPathItem(ParseNode node) if (pointer != null) { - var description = node.Context.VersionService.GetReferenceScalarValues(mapNode, OpenApiConstants.Description); - var summary = node.Context.VersionService.GetReferenceScalarValues(mapNode, OpenApiConstants.Summary); - return new OpenApiPathItem() { UnresolvedReference = true, - Reference = node.Context.VersionService.ConvertToOpenApiReference(pointer, ReferenceType.PathItem, summary, description) + Reference = node.Context.VersionService.ConvertToOpenApiReference(pointer, ReferenceType.PathItem) }; } diff --git a/src/Microsoft.OpenApi.Readers/V3/OpenApiPathsDeserializer.cs b/src/Microsoft.OpenApi.Readers/V3/OpenApiPathsDeserializer.cs index fcfad096c..23435a172 100644 --- a/src/Microsoft.OpenApi.Readers/V3/OpenApiPathsDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V3/OpenApiPathsDeserializer.cs @@ -13,9 +13,9 @@ namespace Microsoft.OpenApi.Readers.V3 /// internal static partial class OpenApiV3Deserializer { - private static FixedFieldMap _pathsFixedFields = new FixedFieldMap(); + private static readonly FixedFieldMap _pathsFixedFields = new FixedFieldMap(); - private static PatternFieldMap _pathsPatternFields = new PatternFieldMap + private static readonly PatternFieldMap _pathsPatternFields = new PatternFieldMap { {s => s.StartsWith("/"), (o, k, n) => o.Add(k, LoadPathItem(n))}, {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, LoadExtension(p,n))} diff --git a/src/Microsoft.OpenApi.Readers/V3/OpenApiRequestBodyDeserializer.cs b/src/Microsoft.OpenApi.Readers/V3/OpenApiRequestBodyDeserializer.cs index 226183b00..a2633028e 100644 --- a/src/Microsoft.OpenApi.Readers/V3/OpenApiRequestBodyDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V3/OpenApiRequestBodyDeserializer.cs @@ -1,7 +1,6 @@ // 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; @@ -50,10 +49,7 @@ public static OpenApiRequestBody LoadRequestBody(ParseNode node) var pointer = mapNode.GetReferencePointer(); if (pointer != null) { - var description = node.Context.VersionService.GetReferenceScalarValues(mapNode, OpenApiConstants.Description); - var summary = node.Context.VersionService.GetReferenceScalarValues(mapNode, OpenApiConstants.Summary); - - return mapNode.GetReferencedObject(ReferenceType.RequestBody, pointer, summary, description); + return mapNode.GetReferencedObject(ReferenceType.RequestBody, pointer); } var requestBody = new OpenApiRequestBody(); diff --git a/src/Microsoft.OpenApi.Readers/V3/OpenApiResponseDeserializer.cs b/src/Microsoft.OpenApi.Readers/V3/OpenApiResponseDeserializer.cs index f795ae7fd..45b8a3efb 100644 --- a/src/Microsoft.OpenApi.Readers/V3/OpenApiResponseDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V3/OpenApiResponseDeserializer.cs @@ -1,8 +1,6 @@ // 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; @@ -56,11 +54,7 @@ public static OpenApiResponse LoadResponse(ParseNode node) var pointer = mapNode.GetReferencePointer(); if (pointer != null) { - - var description = node.Context.VersionService.GetReferenceScalarValues(mapNode, OpenApiConstants.Description); - var summary = node.Context.VersionService.GetReferenceScalarValues(mapNode, OpenApiConstants.Summary); - - return mapNode.GetReferencedObject(ReferenceType.Response, pointer, summary, description); + return mapNode.GetReferencedObject(ReferenceType.Response, pointer); } var response = new OpenApiResponse(); diff --git a/src/Microsoft.OpenApi.Readers/V3/OpenApiResponsesDeserializer.cs b/src/Microsoft.OpenApi.Readers/V3/OpenApiResponsesDeserializer.cs index 9fe4d075f..105e56c22 100644 --- a/src/Microsoft.OpenApi.Readers/V3/OpenApiResponsesDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V3/OpenApiResponsesDeserializer.cs @@ -13,9 +13,9 @@ namespace Microsoft.OpenApi.Readers.V3 /// internal static partial class OpenApiV3Deserializer { - public static FixedFieldMap ResponsesFixedFields = new FixedFieldMap(); + public static readonly FixedFieldMap ResponsesFixedFields = new FixedFieldMap(); - public static PatternFieldMap ResponsesPatternFields = new PatternFieldMap + public static readonly PatternFieldMap ResponsesPatternFields = new PatternFieldMap { {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))} diff --git a/src/Microsoft.OpenApi.Readers/V3/OpenApiSecurityRequirementDeserializer.cs b/src/Microsoft.OpenApi.Readers/V3/OpenApiSecurityRequirementDeserializer.cs index bbc442c79..078927fea 100644 --- a/src/Microsoft.OpenApi.Readers/V3/OpenApiSecurityRequirementDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V3/OpenApiSecurityRequirementDeserializer.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. -using System.Linq; using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Readers.ParseNodes; @@ -16,20 +15,12 @@ internal static partial class OpenApiV3Deserializer public static OpenApiSecurityRequirement LoadSecurityRequirement(ParseNode node) { var mapNode = node.CheckMapNode("security"); - string description = null; - string summary = null; - + var securityRequirement = new OpenApiSecurityRequirement(); foreach (var property in mapNode) { - if(property.Name.Equals("description") || property.Name.Equals("summary")) - { - description = node.Context.VersionService.GetReferenceScalarValues(mapNode, OpenApiConstants.Description); - summary = node.Context.VersionService.GetReferenceScalarValues(mapNode, OpenApiConstants.Summary); - } - - var scheme = LoadSecuritySchemeByReference(mapNode.Context, property.Name, summary, description); + var scheme = LoadSecuritySchemeByReference(mapNode.Context, property.Name); var scopes = property.Value.CreateSimpleList(value => value.GetScalarValue()); @@ -49,17 +40,13 @@ public static OpenApiSecurityRequirement LoadSecurityRequirement(ParseNode node) private static OpenApiSecurityScheme LoadSecuritySchemeByReference( ParsingContext context, - string schemeName, - string summary = null, - string description = null) + string schemeName) { var securitySchemeObject = new OpenApiSecurityScheme() { UnresolvedReference = true, Reference = new OpenApiReference() { - Summary = summary, - Description = description, Id = schemeName, Type = ReferenceType.SecurityScheme } diff --git a/src/Microsoft.OpenApi.Readers/V3/OpenApiV3Deserializer.cs b/src/Microsoft.OpenApi.Readers/V3/OpenApiV3Deserializer.cs index 1628518fa..90dd9557b 100644 --- a/src/Microsoft.OpenApi.Readers/V3/OpenApiV3Deserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V3/OpenApiV3Deserializer.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; +using System.Text.Json.Nodes; using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Exceptions; using Microsoft.OpenApi.Expressions; @@ -48,7 +49,7 @@ private static void ProcessAnyFields( mapNode.Context.StartObject(anyFieldName); var any = anyFieldMap[anyFieldName].PropertyGetter(domainObject); - + if (any == null) { anyFieldMap[anyFieldName].PropertySetter(domainObject, null); @@ -79,7 +80,7 @@ private static void ProcessAnyListFields( { try { - var newProperty = new List(); + var newProperty = new List(); mapNode.Context.StartObject(anyListFieldName); @@ -110,7 +111,7 @@ private static void ProcessAnyMapFields( foreach (var anyMapFieldName in anyMapFieldMap.Keys.ToList()) { try - { + { mapNode.Context.StartObject(anyMapFieldName); foreach (var propertyMapElement in anyMapFieldMap[anyMapFieldName].PropertyMapGetter(domainObject)) @@ -120,7 +121,7 @@ private static void ProcessAnyMapFields( if (propertyMapElement.Value != null) { var any = anyMapFieldMap[anyMapFieldName].PropertyGetter(propertyMapElement.Value); - + anyMapFieldMap[anyMapFieldName].PropertySetter(propertyMapElement.Value, any); } } @@ -166,7 +167,7 @@ public static OpenApiAny LoadAny(ParseNode node) { return node.CreateAny(); } - + private static IOpenApiExtension LoadExtension(string name, ParseNode node) { if (node.Context.ExtensionParsers.TryGetValue(name, out var parser)) diff --git a/src/Microsoft.OpenApi.Readers/V3/OpenApiV3VersionService.cs b/src/Microsoft.OpenApi.Readers/V3/OpenApiV3VersionService.cs index 13990f126..bd9e54985 100644 --- a/src/Microsoft.OpenApi.Readers/V3/OpenApiV3VersionService.cs +++ b/src/Microsoft.OpenApi.Readers/V3/OpenApiV3VersionService.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; using System.Collections.Generic; using System.Linq; -using System.Text.Json.Nodes; +using Json.Schema; using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Exceptions; using Microsoft.OpenApi.Extensions; @@ -55,7 +55,7 @@ public OpenApiV3VersionService(OpenApiDiagnostic diagnostic) [typeof(OpenApiRequestBody)] = OpenApiV3Deserializer.LoadRequestBody, [typeof(OpenApiResponse)] = OpenApiV3Deserializer.LoadResponse, [typeof(OpenApiResponses)] = OpenApiV3Deserializer.LoadResponses, - [typeof(OpenApiSchema)] = OpenApiV3Deserializer.LoadSchema, + [typeof(JsonSchema)] = OpenApiV3Deserializer.LoadSchema, [typeof(OpenApiSecurityRequirement)] = OpenApiV3Deserializer.LoadSecurityRequirement, [typeof(OpenApiSecurityScheme)] = OpenApiV3Deserializer.LoadSecurityScheme, [typeof(OpenApiServer)] = OpenApiV3Deserializer.LoadServer, @@ -86,8 +86,6 @@ public OpenApiReference ConvertToOpenApiReference( { return new OpenApiReference { - Summary = summary, - Description = description, Type = type, Id = reference }; @@ -97,8 +95,6 @@ public OpenApiReference ConvertToOpenApiReference( // or a simple string-style reference for tag and security scheme. return new OpenApiReference { - Summary = summary, - Description = description, Type = type, ExternalResource = segments[0] }; @@ -110,7 +106,7 @@ public OpenApiReference ConvertToOpenApiReference( // "$ref": "#/components/schemas/Pet" try { - return ParseLocalReference(segments[1], summary, description); + return ParseLocalReference(segments[1]); } catch (OpenApiException ex) { @@ -129,7 +125,7 @@ public OpenApiReference ConvertToOpenApiReference( if (type == null) { type = referencedType; - } + } else { if (type != referencedType) @@ -165,7 +161,6 @@ public T LoadElement(ParseNode node) where T : IOpenApiElement return (T)_loaders[typeof(T)](node); } - /// public string GetReferenceScalarValues(MapNode mapNode, string scalarValue) { @@ -180,7 +175,7 @@ public string GetReferenceScalarValues(MapNode mapNode, string scalarValue) return null; } - private OpenApiReference ParseLocalReference(string localReference, string summary = null, string description = null) + private OpenApiReference ParseLocalReference(string localReference) { if (string.IsNullOrWhiteSpace(localReference)) { @@ -202,12 +197,10 @@ private OpenApiReference ParseLocalReference(string localReference, string summa var parsedReference = new OpenApiReference { - Summary = summary, - Description = description, Type = referenceType, Id = refId }; - + return parsedReference; } } diff --git a/src/Microsoft.OpenApi.Readers/V31/JsonSchemaDeserializer.cs b/src/Microsoft.OpenApi.Readers/V31/JsonSchemaDeserializer.cs new file mode 100644 index 000000000..b389860af --- /dev/null +++ b/src/Microsoft.OpenApi.Readers/V31/JsonSchemaDeserializer.cs @@ -0,0 +1,50 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System.Text.Json; +using Json.Schema; +using Microsoft.OpenApi.Extensions; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Readers.ParseNodes; +using JsonSchema = Json.Schema.JsonSchema; + +namespace Microsoft.OpenApi.Readers.V31 +{ + /// + /// Class containing logic to deserialize Open API V31 document into + /// runtime Open API object model. + /// + internal static partial class OpenApiV31Deserializer + { + public static JsonSchema LoadSchema(ParseNode node) + { + var mapNode = node.CheckMapNode(OpenApiConstants.Schema); + var builder = new JsonSchemaBuilder(); + + // check for a $ref and if present, add it to the builder as a Ref keyword + if (mapNode.GetReferencePointer() is {} pointer) + { + builder = builder.Ref(pointer); + + // Check for summary and description and append to builder + var summary = mapNode.GetSummaryValue(); + var description = mapNode.GetDescriptionValue(); + if (!string.IsNullOrEmpty(summary)) + { + builder.Summary(summary); + } + if (!string.IsNullOrEmpty(description)) + { + builder.Description(description); + } + + return builder.Build(); + } + else + { + return node.JsonNode.Deserialize(); + } + } + } + +} diff --git a/src/Microsoft.OpenApi.Readers/V31/OpenApiCallbackDeserializer.cs b/src/Microsoft.OpenApi.Readers/V31/OpenApiCallbackDeserializer.cs new file mode 100644 index 000000000..0fdc676d2 --- /dev/null +++ b/src/Microsoft.OpenApi.Readers/V31/OpenApiCallbackDeserializer.cs @@ -0,0 +1,41 @@ +using Microsoft.OpenApi.Expressions; +using System; +using Microsoft.OpenApi.Extensions; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Readers.ParseNodes; + +namespace Microsoft.OpenApi.Readers.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 FixedFieldMap(); + + private static readonly PatternFieldMap _callbackPatternFields = + new PatternFieldMap + { + {s => !s.StartsWith("x-", StringComparison.OrdinalIgnoreCase), (o, p, n) => o.AddPathItem(RuntimeExpression.Build(p), LoadPathItem(n))}, + {s => s.StartsWith("x-", StringComparison.OrdinalIgnoreCase), (o, p, n) => o.AddExtension(p, LoadExtension(p,n))}, + }; + + public static OpenApiCallback LoadCallback(ParseNode node) + { + var mapNode = node.CheckMapNode("callback"); + + if (mapNode.GetReferencePointer() is {} pointer) + { + return mapNode.GetReferencedObject(ReferenceType.Callback, pointer); + } + + var domainObject = new OpenApiCallback(); + + ParseMap(mapNode, domainObject, _callbackFixedFields, _callbackPatternFields); + + return domainObject; + } + } +} diff --git a/src/Microsoft.OpenApi.Readers/V31/OpenApiComponentsDeserializer.cs b/src/Microsoft.OpenApi.Readers/V31/OpenApiComponentsDeserializer.cs new file mode 100644 index 000000000..c25422a85 --- /dev/null +++ b/src/Microsoft.OpenApi.Readers/V31/OpenApiComponentsDeserializer.cs @@ -0,0 +1,54 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System; +using Json.Schema; +using Microsoft.OpenApi.Extensions; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Readers.ParseNodes; + +namespace Microsoft.OpenApi.Readers.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 FixedFieldMap + { + {"schemas", (o, n) => o.Schemas = n.CreateMap(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)}, + {"pathItems", (o, n) => o.PathItems = n.CreateMapWithReference(ReferenceType.PathItem, LoadPathItem)} + }; + + private static readonly PatternFieldMap _componentsPatternFields = + new PatternFieldMap + { + {s => s.StartsWith("x-", StringComparison.OrdinalIgnoreCase), (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); + + foreach (var schema in components.Schemas) + { + var refUri = new Uri(OpenApiConstants.V3ReferenceUri + schema.Key); + SchemaRegistry.Global.Register(refUri, schema.Value); + } + + return components; + } + } +} diff --git a/src/Microsoft.OpenApi.Readers/V31/OpenApiContactDeserializer.cs b/src/Microsoft.OpenApi.Readers/V31/OpenApiContactDeserializer.cs new file mode 100644 index 000000000..da7106ded --- /dev/null +++ b/src/Microsoft.OpenApi.Readers/V31/OpenApiContactDeserializer.cs @@ -0,0 +1,51 @@ +using System; +using Microsoft.OpenApi.Extensions; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Readers.ParseNodes; + +namespace Microsoft.OpenApi.Readers.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 FixedFieldMap + { + { + "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 PatternFieldMap + { + {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, LoadExtension(p,n))} + }; + + public static OpenApiContact LoadContact(ParseNode node) + { + var mapNode = node as MapNode; + var contact = new OpenApiContact(); + + ParseMap(mapNode, contact, _contactFixedFields, _contactPatternFields); + + return contact; + } + } +} diff --git a/src/Microsoft.OpenApi.Readers/V31/OpenApiDiscriminatorDeserializer.cs b/src/Microsoft.OpenApi.Readers/V31/OpenApiDiscriminatorDeserializer.cs new file mode 100644 index 000000000..59379a9ea --- /dev/null +++ b/src/Microsoft.OpenApi.Readers/V31/OpenApiDiscriminatorDeserializer.cs @@ -0,0 +1,49 @@ +using Microsoft.OpenApi.Extensions; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Readers.ParseNodes; + +namespace Microsoft.OpenApi.Readers.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 FixedFieldMap + { + { + "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) + { + 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.Readers/V31/OpenApiDocumentDeserializer.cs b/src/Microsoft.OpenApi.Readers/V31/OpenApiDocumentDeserializer.cs new file mode 100644 index 000000000..e970dac4f --- /dev/null +++ b/src/Microsoft.OpenApi.Readers/V31/OpenApiDocumentDeserializer.cs @@ -0,0 +1,56 @@ +using Microsoft.OpenApi.Extensions; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Readers.ParseNodes; + +namespace Microsoft.OpenApi.Readers.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 FixedFieldMap + { + { + "openapi", (o, n) => + { + } /* Version is valid field but we already parsed it */ + }, + {"info", (o, n) => o.Info = LoadInfo(n)}, + {"jsonSchemaDialect", (o, n) => o.JsonSchemaDialect = n.GetScalarValue() }, + {"servers", (o, n) => o.Servers = n.CreateList(LoadServer)}, + {"paths", (o, n) => o.Paths = LoadPaths(n)}, + {"webhooks", (o, n) => o.Webhooks = 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 OpenApiReference() + { + Id = tag.Name, + Type = ReferenceType.Tag + }; + } + } }, + {"externalDocs", (o, n) => o.ExternalDocs = LoadExternalDocs(n)}, + {"security", (o, n) => o.SecurityRequirements = n.CreateList(LoadSecurityRequirement)} + }; + + 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); + + return openApidoc; + } + } +} diff --git a/src/Microsoft.OpenApi.Readers/V31/OpenApiEncodingDeserializer.cs b/src/Microsoft.OpenApi.Readers/V31/OpenApiEncodingDeserializer.cs new file mode 100644 index 000000000..25f672db2 --- /dev/null +++ b/src/Microsoft.OpenApi.Readers/V31/OpenApiEncodingDeserializer.cs @@ -0,0 +1,66 @@ +using Microsoft.OpenApi.Extensions; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Readers.ParseNodes; + +namespace Microsoft.OpenApi.Readers.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 FixedFieldMap + { + { + "contentType", (o, n) => + { + o.ContentType = n.GetScalarValue(); + } + }, + { + "headers", (o, n) => + { + o.Headers = n.CreateMap(LoadHeader); + } + }, + { + "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 PatternFieldMap + { + {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, LoadExtension(p,n))} + }; + + public static OpenApiEncoding LoadEncoding(ParseNode node) + { + 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.Readers/V31/OpenApiExampleDeserializer.cs b/src/Microsoft.OpenApi.Readers/V31/OpenApiExampleDeserializer.cs new file mode 100644 index 000000000..86d319b6b --- /dev/null +++ b/src/Microsoft.OpenApi.Readers/V31/OpenApiExampleDeserializer.cs @@ -0,0 +1,70 @@ +using Microsoft.OpenApi.Extensions; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Readers.ParseNodes; + +namespace Microsoft.OpenApi.Readers.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 FixedFieldMap + { + { + "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 PatternFieldMap + { + {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, LoadExtension(p,n))} + }; + + public static OpenApiExample LoadExample(ParseNode node) + { + var mapNode = node.CheckMapNode("example"); + + var pointer = mapNode.GetReferencePointer(); + if (pointer != null) + { + var description = node.Context.VersionService.GetReferenceScalarValues(mapNode, OpenApiConstants.Description); + var summary = node.Context.VersionService.GetReferenceScalarValues(mapNode, OpenApiConstants.Summary); + + return mapNode.GetReferencedObject(ReferenceType.Example, pointer, summary, description); + } + + var example = new OpenApiExample(); + foreach (var property in mapNode) + { + property.ParseField(example, _exampleFixedFields, _examplePatternFields); + } + + return example; + } + } +} diff --git a/src/Microsoft.OpenApi.Readers/V31/OpenApiExternalDocsDeserializer.cs b/src/Microsoft.OpenApi.Readers/V31/OpenApiExternalDocsDeserializer.cs new file mode 100644 index 000000000..3e73a1db2 --- /dev/null +++ b/src/Microsoft.OpenApi.Readers/V31/OpenApiExternalDocsDeserializer.cs @@ -0,0 +1,49 @@ +using System; +using Microsoft.OpenApi.Extensions; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Readers.ParseNodes; + +namespace Microsoft.OpenApi.Readers.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 FixedFieldMap + { + // $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 PatternFieldMap { + + {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, LoadExtension(p, n))} + }; + + public static OpenApiExternalDocs LoadExternalDocs(ParseNode node) + { + var mapNode = node.CheckMapNode("externalDocs"); + + var externalDocs = new OpenApiExternalDocs(); + + ParseMap(mapNode, externalDocs, _externalDocsFixedFields, _externalDocsPatternFields); + + return externalDocs; + } + } +} diff --git a/src/Microsoft.OpenApi.Readers/V31/OpenApiHeaderDeserializer.cs b/src/Microsoft.OpenApi.Readers/V31/OpenApiHeaderDeserializer.cs new file mode 100644 index 000000000..ad88a499e --- /dev/null +++ b/src/Microsoft.OpenApi.Readers/V31/OpenApiHeaderDeserializer.cs @@ -0,0 +1,104 @@ +using Microsoft.OpenApi.Extensions; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Readers.ParseNodes; + +namespace Microsoft.OpenApi.Readers.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 FixedFieldMap + { + { + "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) => + { + o.Schema = LoadSchema(n); + } + }, + { + "examples", (o, n) => + { + o.Examples = n.CreateMap(LoadExample); + } + }, + { + "example", (o, n) => + { + o.Example = n.CreateAny(); + } + }, + }; + + private static readonly PatternFieldMap _headerPatternFields = new PatternFieldMap + { + {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, LoadExtension(p,n))} + }; + + public static OpenApiHeader LoadHeader(ParseNode node) + { + var mapNode = node.CheckMapNode("header"); + + var pointer = mapNode.GetReferencePointer(); + if (pointer != null) + { + var description = node.Context.VersionService.GetReferenceScalarValues(mapNode, OpenApiConstants.Description); + var summary = node.Context.VersionService.GetReferenceScalarValues(mapNode, OpenApiConstants.Summary); + + return mapNode.GetReferencedObject(ReferenceType.Header, pointer, summary, description); + } + + var header = new OpenApiHeader(); + foreach (var property in mapNode) + { + property.ParseField(header, _headerFixedFields, _headerPatternFields); + } + + return header; + } + } +} diff --git a/src/Microsoft.OpenApi.Readers/V31/OpenApiInfoDeserializer.cs b/src/Microsoft.OpenApi.Readers/V31/OpenApiInfoDeserializer.cs new file mode 100644 index 000000000..bf2027e21 --- /dev/null +++ b/src/Microsoft.OpenApi.Readers/V31/OpenApiInfoDeserializer.cs @@ -0,0 +1,74 @@ +using System; +using Microsoft.OpenApi.Extensions; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Readers.ParseNodes; + +namespace Microsoft.OpenApi.Readers.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 FixedFieldMap + { + { + "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) => + { + o.Contact = LoadContact(n); + } + }, + { + "license", (o, n) => + { + o.License = LoadLicense(n); + } + } + }; + + public static readonly PatternFieldMap InfoPatternFields = new PatternFieldMap + { + {s => s.StartsWith("x-"), (o, k, n) => o.AddExtension(k,LoadExtension(k, n))} + }; + + public static OpenApiInfo LoadInfo(ParseNode node) + { + var mapNode = node.CheckMapNode("Info"); + var info = new OpenApiInfo(); + ParseMap(mapNode, info, InfoFixedFields, InfoPatternFields); + + return info; + } + } +} diff --git a/src/Microsoft.OpenApi.Readers/V31/OpenApiLicenseDeserializer.cs b/src/Microsoft.OpenApi.Readers/V31/OpenApiLicenseDeserializer.cs new file mode 100644 index 000000000..81e9d6647 --- /dev/null +++ b/src/Microsoft.OpenApi.Readers/V31/OpenApiLicenseDeserializer.cs @@ -0,0 +1,52 @@ +using System; +using Microsoft.OpenApi.Extensions; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Readers.ParseNodes; + +namespace Microsoft.OpenApi.Readers.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 FixedFieldMap + { + { + "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 PatternFieldMap + { + {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, LoadExtension(p,n))} + }; + + internal static OpenApiLicense LoadLicense(ParseNode node) + { + var mapNode = node.CheckMapNode("License"); + + var license = new OpenApiLicense(); + + ParseMap(mapNode, license, _licenseFixedFields, _licensePatternFields); + + return license; + } + } +} diff --git a/src/Microsoft.OpenApi.Readers/V31/OpenApiLinkDeserializer.cs b/src/Microsoft.OpenApi.Readers/V31/OpenApiLinkDeserializer.cs new file mode 100644 index 000000000..3070e12d8 --- /dev/null +++ b/src/Microsoft.OpenApi.Readers/V31/OpenApiLinkDeserializer.cs @@ -0,0 +1,72 @@ +using Microsoft.OpenApi.Extensions; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Readers.ParseNodes; + +namespace Microsoft.OpenApi.Readers.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 FixedFieldMap + { + { + "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) => o.Server = LoadServer(n)} + }; + + private static readonly PatternFieldMap _linkPatternFields = new PatternFieldMap + { + {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, LoadExtension(p,n))}, + }; + + public static OpenApiLink LoadLink(ParseNode node) + { + var mapNode = node.CheckMapNode("link"); + var link = new OpenApiLink(); + + var pointer = mapNode.GetReferencePointer(); + if (pointer != null) + { + var description = node.Context.VersionService.GetReferenceScalarValues(mapNode, OpenApiConstants.Description); + var summary = node.Context.VersionService.GetReferenceScalarValues(mapNode, OpenApiConstants.Summary); + + return mapNode.GetReferencedObject(ReferenceType.Link, pointer, summary, description); + } + + ParseMap(mapNode, link, _linkFixedFields, _linkPatternFields); + + return link; + } + } +} diff --git a/src/Microsoft.OpenApi.Readers/V31/OpenApiMediaTypeDeserializer.cs b/src/Microsoft.OpenApi.Readers/V31/OpenApiMediaTypeDeserializer.cs new file mode 100644 index 000000000..ea6e6acee --- /dev/null +++ b/src/Microsoft.OpenApi.Readers/V31/OpenApiMediaTypeDeserializer.cs @@ -0,0 +1,87 @@ +using Microsoft.OpenApi.Extensions; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Readers.ParseNodes; + +namespace Microsoft.OpenApi.Readers.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 FixedFieldMap + { + { + OpenApiConstants.Schema, (o, n) => + { + o.Schema = LoadSchema(n); + } + }, + { + OpenApiConstants.Examples, (o, n) => + { + o.Examples = n.CreateMap(LoadExample); + } + }, + { + OpenApiConstants.Example, (o, n) => + { + o.Example = n.CreateAny(); + } + }, + { + OpenApiConstants.Encoding, (o, n) => + { + o.Encoding = n.CreateMap(LoadEncoding); + } + }, + }; + + private static readonly PatternFieldMap _mediaTypePatternFields = + new PatternFieldMap + { + {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) + { + var mapNode = node.CheckMapNode(OpenApiConstants.Content); + + var mediaType = new OpenApiMediaType(); + + ParseMap(mapNode, mediaType, _mediaTypeFixedFields, _mediaTypePatternFields); + + ProcessAnyFields(mapNode, mediaType, _mediaTypeAnyFields); + ProcessAnyMapFields(mapNode, mediaType, _mediaTypeAnyMapOpenApiExampleFields); + + return mediaType; + } + } +} diff --git a/src/Microsoft.OpenApi.Readers/V31/OpenApiOAuthFlowDeserializer.cs b/src/Microsoft.OpenApi.Readers/V31/OpenApiOAuthFlowDeserializer.cs new file mode 100644 index 000000000..5d7ae176b --- /dev/null +++ b/src/Microsoft.OpenApi.Readers/V31/OpenApiOAuthFlowDeserializer.cs @@ -0,0 +1,57 @@ +using System; +using Microsoft.OpenApi.Extensions; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Readers.ParseNodes; + +namespace Microsoft.OpenApi.Readers.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 FixedFieldMap + { + { + "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 PatternFieldMap + { + {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, LoadExtension(p,n))} + }; + + public static OpenApiOAuthFlow LoadOAuthFlow(ParseNode node) + { + 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.Readers/V31/OpenApiOAuthFlowsDeserializer.cs b/src/Microsoft.OpenApi.Readers/V31/OpenApiOAuthFlowsDeserializer.cs new file mode 100644 index 000000000..0e61f7aea --- /dev/null +++ b/src/Microsoft.OpenApi.Readers/V31/OpenApiOAuthFlowsDeserializer.cs @@ -0,0 +1,41 @@ +using Microsoft.OpenApi.Extensions; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Readers.ParseNodes; + +namespace Microsoft.OpenApi.Readers.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 FixedFieldMap + { + {"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)} + }; + + private static readonly PatternFieldMap _oAuthFlowsPatternFields = + new PatternFieldMap + { + {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, LoadExtension(p,n))} + }; + + public static OpenApiOAuthFlows LoadOAuthFlows(ParseNode node) + { + 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.Readers/V31/OpenApiOperationDeserializer.cs b/src/Microsoft.OpenApi.Readers/V31/OpenApiOperationDeserializer.cs new file mode 100644 index 000000000..2e0f129c1 --- /dev/null +++ b/src/Microsoft.OpenApi.Readers/V31/OpenApiOperationDeserializer.cs @@ -0,0 +1,121 @@ +using Microsoft.OpenApi.Extensions; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Readers.ParseNodes; + +namespace Microsoft.OpenApi.Readers.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 FixedFieldMap + { + { + "tags", (o, n) => o.Tags = n.CreateSimpleList( + valueNode => + LoadTagByReference(valueNode.GetScalarValue())) + }, + { + "summary", (o, n) => + { + o.Summary = n.GetScalarValue(); + } + }, + { + "description", (o, n) => + { + o.Description = n.GetScalarValue(); + } + }, + { + "externalDocs", (o, n) => + { + o.ExternalDocs = LoadExternalDocs(n); + } + }, + { + "operationId", (o, n) => + { + o.OperationId = n.GetScalarValue(); + } + }, + { + "parameters", (o, n) => + { + o.Parameters = n.CreateList(LoadParameter); + } + }, + { + "requestBody", (o, n) => + { + o.RequestBody = LoadRequestBody(n); + } + }, + { + "responses", (o, n) => + { + o.Responses = LoadResponses(n); + } + }, + { + "callbacks", (o, n) => + { + o.Callbacks = n.CreateMap(LoadCallback); + } + }, + { + "deprecated", (o, n) => + { + o.Deprecated = bool.Parse(n.GetScalarValue()); + } + }, + { + "security", (o, n) => + { + o.Security = n.CreateList(LoadSecurityRequirement); + } + }, + { + "servers", (o, n) => + { + o.Servers = n.CreateList(LoadServer); + } + }, + }; + + private static readonly PatternFieldMap _operationPatternFields = + new PatternFieldMap + { + {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, LoadExtension(p,n))}, + }; + + internal static OpenApiOperation LoadOperation(ParseNode node) + { + var mapNode = node.CheckMapNode("Operation"); + + var operation = new OpenApiOperation(); + + ParseMap(mapNode, operation, _operationFixedFields, _operationPatternFields); + + return operation; + } + + private static OpenApiTag LoadTagByReference(string tagName) + { + var tagObject = new OpenApiTag() + { + UnresolvedReference = true, + Reference = new OpenApiReference() + { + Type = ReferenceType.Tag, + Id = tagName + } + }; + + return tagObject; + } + } +} diff --git a/src/Microsoft.OpenApi.Readers/V31/OpenApiParameterDeserializer.cs b/src/Microsoft.OpenApi.Readers/V31/OpenApiParameterDeserializer.cs new file mode 100644 index 000000000..e5a9deccb --- /dev/null +++ b/src/Microsoft.OpenApi.Readers/V31/OpenApiParameterDeserializer.cs @@ -0,0 +1,154 @@ +using System; +using System.Linq; +using Microsoft.OpenApi.Extensions; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Readers.ParseNodes; + +namespace Microsoft.OpenApi.Readers.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 FixedFieldMap + { + { + "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) => + { + o.Schema = LoadSchema(n); + } + }, + { + "content", (o, n) => + { + o.Content = n.CreateMap(LoadMediaType); + } + }, + { + "examples", (o, n) => + { + o.Examples = n.CreateMap(LoadExample); + } + }, + { + "example", (o, n) => + { + o.Example = n.CreateAny(); + } + }, + }; + + private static readonly PatternFieldMap _parameterPatternFields = + new PatternFieldMap + { + {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) + { + var mapNode = node.CheckMapNode("parameter"); + + var pointer = mapNode.GetReferencePointer(); + if (pointer != null) + { + var description = node.Context.VersionService.GetReferenceScalarValues(mapNode, OpenApiConstants.Description); + var summary = node.Context.VersionService.GetReferenceScalarValues(mapNode, OpenApiConstants.Summary); + + return mapNode.GetReferencedObject(ReferenceType.Parameter, pointer, summary, description); + } + + var parameter = new OpenApiParameter(); + + ParseMap(mapNode, parameter, _parameterFixedFields, _parameterPatternFields); + ProcessAnyFields(mapNode, parameter, _parameterAnyFields); + ProcessAnyMapFields(mapNode, parameter, _parameterAnyMapOpenApiExampleFields); + + return parameter; + } + } +} diff --git a/src/Microsoft.OpenApi.Readers/V31/OpenApiPathItemDeserializer.cs b/src/Microsoft.OpenApi.Readers/V31/OpenApiPathItemDeserializer.cs new file mode 100644 index 000000000..a9a916e07 --- /dev/null +++ b/src/Microsoft.OpenApi.Readers/V31/OpenApiPathItemDeserializer.cs @@ -0,0 +1,77 @@ +using Microsoft.OpenApi.Extensions; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Readers.ParseNodes; + +namespace Microsoft.OpenApi.Readers.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 FixedFieldMap + { + + { + "$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) => 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 PatternFieldMap + { + {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 description = node.Context.VersionService.GetReferenceScalarValues(mapNode, OpenApiConstants.Description); + var summary = node.Context.VersionService.GetReferenceScalarValues(mapNode, OpenApiConstants.Summary); + + return new OpenApiPathItem() + { + UnresolvedReference = true, + Reference = node.Context.VersionService.ConvertToOpenApiReference(pointer, ReferenceType.PathItem, summary, description) + }; + } + + var pathItem = new OpenApiPathItem(); + + ParseMap(mapNode, pathItem, _pathItemFixedFields, _pathItemPatternFields); + + return pathItem; + } + } +} diff --git a/src/Microsoft.OpenApi.Readers/V31/OpenApiPathsDeserializer.cs b/src/Microsoft.OpenApi.Readers/V31/OpenApiPathsDeserializer.cs new file mode 100644 index 000000000..3511c6195 --- /dev/null +++ b/src/Microsoft.OpenApi.Readers/V31/OpenApiPathsDeserializer.cs @@ -0,0 +1,32 @@ +using Microsoft.OpenApi.Extensions; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Readers.ParseNodes; + +namespace Microsoft.OpenApi.Readers.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 FixedFieldMap(); + + private static readonly PatternFieldMap _pathsPatternFields = new PatternFieldMap + { + {s => s.StartsWith("/"), (o, k, n) => o.Add(k, LoadPathItem(n))}, + {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, LoadExtension(p,n))} + }; + + public static OpenApiPaths LoadPaths(ParseNode node) + { + var mapNode = node.CheckMapNode("Paths"); + + var domainObject = new OpenApiPaths(); + + ParseMap(mapNode, domainObject, _pathsFixedFields, _pathsPatternFields); + + return domainObject; + } + } +} diff --git a/src/Microsoft.OpenApi.Readers/V31/OpenApiRequestBodyDeserializer.cs b/src/Microsoft.OpenApi.Readers/V31/OpenApiRequestBodyDeserializer.cs new file mode 100644 index 000000000..7ea14f8b9 --- /dev/null +++ b/src/Microsoft.OpenApi.Readers/V31/OpenApiRequestBodyDeserializer.cs @@ -0,0 +1,64 @@ +using Microsoft.OpenApi.Extensions; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Readers.ParseNodes; + +namespace Microsoft.OpenApi.Readers.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 FixedFieldMap + { + { + "description", (o, n) => + { + o.Description = n.GetScalarValue(); + } + }, + { + "content", (o, n) => + { + o.Content = n.CreateMap(LoadMediaType); + } + }, + { + "required", (o, n) => + { + o.Required = bool.Parse(n.GetScalarValue()); + } + }, + }; + + private static readonly PatternFieldMap _requestBodyPatternFields = + new PatternFieldMap + { + {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, LoadExtension(p,n))} + }; + + public static OpenApiRequestBody LoadRequestBody(ParseNode node) + { + var mapNode = node.CheckMapNode("requestBody"); + + var pointer = mapNode.GetReferencePointer(); + if (pointer != null) + { + var description = node.Context.VersionService.GetReferenceScalarValues(mapNode, OpenApiConstants.Description); + var summary = node.Context.VersionService.GetReferenceScalarValues(mapNode, OpenApiConstants.Summary); + + return mapNode.GetReferencedObject(ReferenceType.RequestBody, pointer, summary, description); + } + + var requestBody = new OpenApiRequestBody(); + foreach (var property in mapNode) + { + property.ParseField(requestBody, _requestBodyFixedFields, _requestBodyPatternFields); + } + + return requestBody; + } + } +} diff --git a/src/Microsoft.OpenApi.Readers/V31/OpenApiResponseDeserializer.cs b/src/Microsoft.OpenApi.Readers/V31/OpenApiResponseDeserializer.cs new file mode 100644 index 000000000..6e68bfb78 --- /dev/null +++ b/src/Microsoft.OpenApi.Readers/V31/OpenApiResponseDeserializer.cs @@ -0,0 +1,67 @@ +using Microsoft.OpenApi.Extensions; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Readers.ParseNodes; + +namespace Microsoft.OpenApi.Readers.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 FixedFieldMap + { + { + "description", (o, n) => + { + o.Description = n.GetScalarValue(); + } + }, + { + "headers", (o, n) => + { + o.Headers = n.CreateMap(LoadHeader); + } + }, + { + "content", (o, n) => + { + o.Content = n.CreateMap(LoadMediaType); + } + }, + { + "links", (o, n) => + { + o.Links = n.CreateMap(LoadLink); + } + } + }; + + private static readonly PatternFieldMap _responsePatternFields = + new PatternFieldMap + { + {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, LoadExtension(p,n))} + }; + + public static OpenApiResponse LoadResponse(ParseNode node) + { + var mapNode = node.CheckMapNode("response"); + + var pointer = mapNode.GetReferencePointer(); + if (pointer != null) + { + + var description = node.Context.VersionService.GetReferenceScalarValues(mapNode, OpenApiConstants.Description); + var summary = node.Context.VersionService.GetReferenceScalarValues(mapNode, OpenApiConstants.Summary); + + return mapNode.GetReferencedObject(ReferenceType.Response, pointer, summary, description); + } + + var response = new OpenApiResponse(); + ParseMap(mapNode, response, _responseFixedFields, _responsePatternFields); + + return response; + } + } +} diff --git a/src/Microsoft.OpenApi.Readers/V31/OpenApiResponsesDeserializer.cs b/src/Microsoft.OpenApi.Readers/V31/OpenApiResponsesDeserializer.cs new file mode 100644 index 000000000..bae682ce6 --- /dev/null +++ b/src/Microsoft.OpenApi.Readers/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.Readers.ParseNodes; + +namespace Microsoft.OpenApi.Readers.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 FixedFieldMap(); + + public static readonly PatternFieldMap ResponsesPatternFields = new PatternFieldMap + { + {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))} + }; + + public static OpenApiResponses LoadResponses(ParseNode node) + { + var mapNode = node.CheckMapNode("Responses"); + + var domainObject = new OpenApiResponses(); + + ParseMap(mapNode, domainObject, ResponsesFixedFields, ResponsesPatternFields); + + return domainObject; + } + } +} diff --git a/src/Microsoft.OpenApi.Readers/V31/OpenApiSecurityRequirementDeserializer.cs b/src/Microsoft.OpenApi.Readers/V31/OpenApiSecurityRequirementDeserializer.cs new file mode 100644 index 000000000..6f64fa076 --- /dev/null +++ b/src/Microsoft.OpenApi.Readers/V31/OpenApiSecurityRequirementDeserializer.cs @@ -0,0 +1,56 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Readers.ParseNodes; + +namespace Microsoft.OpenApi.Readers.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) + { + var mapNode = node.CheckMapNode("security"); + + var securityRequirement = new OpenApiSecurityRequirement(); + + foreach (var property in mapNode) + { + var scheme = LoadSecuritySchemeByReference(property.Name); + + var scopes = property.Value.CreateSimpleList(value => 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) + { + var securitySchemeObject = new OpenApiSecurityScheme() + { + UnresolvedReference = true, + Reference = new OpenApiReference() + { + Id = schemeName, + Type = ReferenceType.SecurityScheme + } + }; + + return securitySchemeObject; + } + } +} diff --git a/src/Microsoft.OpenApi.Readers/V31/OpenApiSecuritySchemeDeserializer.cs b/src/Microsoft.OpenApi.Readers/V31/OpenApiSecuritySchemeDeserializer.cs new file mode 100644 index 000000000..59cc59955 --- /dev/null +++ b/src/Microsoft.OpenApi.Readers/V31/OpenApiSecuritySchemeDeserializer.cs @@ -0,0 +1,89 @@ +// 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.Readers.ParseNodes; + +namespace Microsoft.OpenApi.Readers.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 FixedFieldMap + { + { + "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) => + { + o.Flows = LoadOAuthFlows(n); + } + } + }; + + private static readonly PatternFieldMap _securitySchemePatternFields = + new PatternFieldMap + { + {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, LoadExtension(p,n))} + }; + + public static OpenApiSecurityScheme LoadSecurityScheme(ParseNode node) + { + var mapNode = node.CheckMapNode("securityScheme"); + + var securityScheme = new OpenApiSecurityScheme(); + foreach (var property in mapNode) + { + property.ParseField(securityScheme, _securitySchemeFixedFields, _securitySchemePatternFields); + } + + return securityScheme; + } + } +} diff --git a/src/Microsoft.OpenApi.Readers/V31/OpenApiServerDeserializer.cs b/src/Microsoft.OpenApi.Readers/V31/OpenApiServerDeserializer.cs new file mode 100644 index 000000000..54e41e8ac --- /dev/null +++ b/src/Microsoft.OpenApi.Readers/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.Readers.ParseNodes; + +namespace Microsoft.OpenApi.Readers.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 FixedFieldMap + { + { + "url", (o, n) => + { + o.Url = n.GetScalarValue(); + } + }, + { + "description", (o, n) => + { + o.Description = n.GetScalarValue(); + } + }, + { + "variables", (o, n) => + { + o.Variables = n.CreateMap(LoadServerVariable); + } + } + }; + + private static readonly PatternFieldMap _serverPatternFields = new PatternFieldMap + { + {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, LoadExtension(p,n))} + }; + + public static OpenApiServer LoadServer(ParseNode node) + { + var mapNode = node.CheckMapNode("server"); + + var server = new OpenApiServer(); + + ParseMap(mapNode, server, _serverFixedFields, _serverPatternFields); + + return server; + } + } +} diff --git a/src/Microsoft.OpenApi.Readers/V31/OpenApiServerVariableDeserializer.cs b/src/Microsoft.OpenApi.Readers/V31/OpenApiServerVariableDeserializer.cs new file mode 100644 index 000000000..f10008a6d --- /dev/null +++ b/src/Microsoft.OpenApi.Readers/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.Readers.ParseNodes; + +namespace Microsoft.OpenApi.Readers.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 FixedFieldMap + { + { + "enum", (o, n) => + { + o.Enum = n.CreateSimpleList(s => s.GetScalarValue()); + } + }, + { + "default", (o, n) => + { + o.Default = n.GetScalarValue(); + } + }, + { + "description", (o, n) => + { + o.Description = n.GetScalarValue(); + } + }, + }; + + private static readonly PatternFieldMap _serverVariablePatternFields = + new PatternFieldMap + { + {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, LoadExtension(p,n))} + }; + + public static OpenApiServerVariable LoadServerVariable(ParseNode node) + { + var mapNode = node.CheckMapNode("serverVariable"); + + var serverVariable = new OpenApiServerVariable(); + + ParseMap(mapNode, serverVariable, _serverVariableFixedFields, _serverVariablePatternFields); + + return serverVariable; + } + } +} diff --git a/src/Microsoft.OpenApi.Readers/V31/OpenApiTagDeserializer.cs b/src/Microsoft.OpenApi.Readers/V31/OpenApiTagDeserializer.cs new file mode 100644 index 000000000..293e21e07 --- /dev/null +++ b/src/Microsoft.OpenApi.Readers/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.Readers.ParseNodes; + +namespace Microsoft.OpenApi.Readers.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 FixedFieldMap + { + { + OpenApiConstants.Name, (o, n) => + { + o.Name = n.GetScalarValue(); + } + }, + { + OpenApiConstants.Description, (o, n) => + { + o.Description = n.GetScalarValue(); + } + }, + { + OpenApiConstants.ExternalDocs, (o, n) => + { + o.ExternalDocs = LoadExternalDocs(n); + } + } + }; + + private static readonly PatternFieldMap _tagPatternFields = new PatternFieldMap + { + {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, LoadExtension(p,n))} + }; + + public static OpenApiTag LoadTag(ParseNode n) + { + 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.Readers/V31/OpenApiV31Deserializer.cs b/src/Microsoft.OpenApi.Readers/V31/OpenApiV31Deserializer.cs new file mode 100644 index 000000000..abdeac81c --- /dev/null +++ b/src/Microsoft.OpenApi.Readers/V31/OpenApiV31Deserializer.cs @@ -0,0 +1,147 @@ +// 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.Readers.ParseNodes; + +namespace Microsoft.OpenApi.Readers.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) + { + if (mapNode == null) + { + return; + } + + foreach (var propertyNode in mapNode) + { + propertyNode.ParseField(domainObject, fixedFieldMap, patternFieldMap); + } + + } + + 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 OpenApiAny LoadAny(ParseNode node) + { + 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) + : node.CreateAny(); + } + + private static string LoadString(ParseNode node) + { + return node.GetScalarValue(); + } + } +} diff --git a/src/Microsoft.OpenApi.Readers/V31/OpenApiV31VersionService.cs b/src/Microsoft.OpenApi.Readers/V31/OpenApiV31VersionService.cs new file mode 100644 index 000000000..18a0018d6 --- /dev/null +++ b/src/Microsoft.OpenApi.Readers/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 Json.Schema; +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; + +namespace Microsoft.OpenApi.Readers.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(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(JsonSchema)] = 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) where T : IOpenApiElement + { + return (T)_loaders[typeof(T)](node); + } + + /// + 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.Readers/V31/OpenApiXmlDeserializer.cs b/src/Microsoft.OpenApi.Readers/V31/OpenApiXmlDeserializer.cs new file mode 100644 index 000000000..b73af6347 --- /dev/null +++ b/src/Microsoft.OpenApi.Readers/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.Readers.ParseNodes; + +namespace Microsoft.OpenApi.Readers.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) + { + 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.Readers/YamlConverter.cs b/src/Microsoft.OpenApi.Readers/YamlConverter.cs index 595fb0eaa..cc1776d2b 100644 --- a/src/Microsoft.OpenApi.Readers/YamlConverter.cs +++ b/src/Microsoft.OpenApi.Readers/YamlConverter.cs @@ -1,10 +1,10 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using System.Text.Json.Nodes; -using SharpYaml.Serialization; using SharpYaml; -using System.Globalization; +using SharpYaml.Serialization; namespace Microsoft.OpenApi.Readers { diff --git a/src/Microsoft.OpenApi.Readers/YamlHelper.cs b/src/Microsoft.OpenApi.Readers/YamlHelper.cs index ea450da2f..bbd78ad47 100644 --- a/src/Microsoft.OpenApi.Readers/YamlHelper.cs +++ b/src/Microsoft.OpenApi.Readers/YamlHelper.cs @@ -1,13 +1,13 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. -using System.Globalization; using System; +using System.Globalization; using System.IO; using System.Linq; using System.Text.Json.Nodes; -using SharpYaml.Serialization; using Microsoft.OpenApi.Exceptions; +using SharpYaml.Serialization; namespace Microsoft.OpenApi.Readers { @@ -20,7 +20,7 @@ public static string GetScalarValue(this JsonNode node) return Convert.ToString(scalarNode?.GetValue(), CultureInfo.InvariantCulture); } - + public static JsonNode ParseJsonString(string yamlString) { var reader = new StringReader(yamlString); diff --git a/src/Microsoft.OpenApi.Workbench/MainModel.cs b/src/Microsoft.OpenApi.Workbench/MainModel.cs index 70074736b..a02540430 100644 --- a/src/Microsoft.OpenApi.Workbench/MainModel.cs +++ b/src/Microsoft.OpenApi.Workbench/MainModel.cs @@ -4,7 +4,6 @@ using System; using System.ComponentModel; using System.Diagnostics; -using System.Globalization; using System.IO; using System.Net.Http; using System.Text; @@ -40,7 +39,7 @@ public class MainModel : INotifyPropertyChanged private string _renderTime; - + /// /// Default format. /// @@ -215,7 +214,7 @@ internal async Task ParseDocument() if (_inputFile.StartsWith("http")) { stream = await _httpClient.GetStreamAsync(_inputFile); - } + } else { stream = new FileStream(_inputFile, FileMode.Open); @@ -292,7 +291,8 @@ internal async Task ParseDocument() Output = string.Empty; Errors = "Failed to parse input: " + ex.Message; } - finally { + finally + { if (stream != null) { stream.Close(); @@ -308,16 +308,17 @@ internal async Task ParseDocument() private string WriteContents(OpenApiDocument document) { var outputStream = new MemoryStream(); - + document.Serialize( outputStream, Version, Format, - new OpenApiWriterSettings() { + new OpenApiWriterSettings() + { InlineLocalReferences = InlineLocal, InlineExternalReferences = InlineExternal }); - + outputStream.Position = 0; return new StreamReader(outputStream).ReadToEnd(); diff --git a/src/Microsoft.OpenApi.Workbench/MainWindow.xaml.cs b/src/Microsoft.OpenApi.Workbench/MainWindow.xaml.cs index 08bbb177d..117fdfc4b 100644 --- a/src/Microsoft.OpenApi.Workbench/MainWindow.xaml.cs +++ b/src/Microsoft.OpenApi.Workbench/MainWindow.xaml.cs @@ -24,7 +24,8 @@ private async void Button_Click(object sender, RoutedEventArgs e) try { await _mainModel.ParseDocument(); - } catch (Exception ex) + } + catch (Exception ex) { _mainModel.Errors = ex.Message; } diff --git a/src/Microsoft.OpenApi.Workbench/StatsVisitor.cs b/src/Microsoft.OpenApi.Workbench/StatsVisitor.cs index 85faef630..3ea933bf9 100644 --- a/src/Microsoft.OpenApi.Workbench/StatsVisitor.cs +++ b/src/Microsoft.OpenApi.Workbench/StatsVisitor.cs @@ -3,9 +3,7 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using Json.Schema; using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Services; @@ -22,7 +20,7 @@ public override void Visit(OpenApiParameter parameter) public int SchemaCount { get; set; } = 0; - public override void Visit(OpenApiSchema schema) + public override void Visit(ref JsonSchema schema) { SchemaCount++; } diff --git a/src/Microsoft.OpenApi/Any/OpenApiAny.cs b/src/Microsoft.OpenApi/Any/OpenApiAny.cs index 937a31442..bee1239fb 100644 --- a/src/Microsoft.OpenApi/Any/OpenApiAny.cs +++ b/src/Microsoft.OpenApi/Any/OpenApiAny.cs @@ -1,9 +1,9 @@ // 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; -using System.Text.Json.Nodes; namespace Microsoft.OpenApi.Any { diff --git a/src/Microsoft.OpenApi/Extensions/JsonSchemaBuilderExtensions.cs b/src/Microsoft.OpenApi/Extensions/JsonSchemaBuilderExtensions.cs new file mode 100644 index 000000000..f7de83f5b --- /dev/null +++ b/src/Microsoft.OpenApi/Extensions/JsonSchemaBuilderExtensions.cs @@ -0,0 +1,323 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; +using Json.Schema; +using Microsoft.OpenApi.Interfaces; +using Microsoft.OpenApi.Models; + +namespace Microsoft.OpenApi.Extensions +{ + /// + /// Provides extension methods for JSON schema generation + /// + public static class JsonSchemaBuilderExtensions + { + /// + /// Custom extensions in the schema + /// + /// + /// + /// + public static JsonSchemaBuilder Extensions(this JsonSchemaBuilder builder, IDictionary extensions) + { + builder.Add(new ExtensionsKeyword(extensions)); + return builder; + } + + /// + /// The Schema summary + /// + /// + /// + /// + public static JsonSchemaBuilder Summary(this JsonSchemaBuilder builder, string summary) + { + builder.Add(new SummaryKeyword(summary)); + return builder; + } + + /// + /// Indicates if the schema can contain properties other than those defined by the properties map + /// + /// + /// + /// + public static JsonSchemaBuilder AdditionalPropertiesAllowed(this JsonSchemaBuilder builder, bool additionalPropertiesAllowed) + { + builder.Add(new AdditionalPropertiesAllowedKeyword(additionalPropertiesAllowed)); + return builder; + } + + /// + /// Allows sending a null value for the defined schema. Default value is false. + /// + /// + /// + /// + public static JsonSchemaBuilder Nullable(this JsonSchemaBuilder builder, bool value) + { + builder.Add(new NullableKeyword(value)); + return builder; + } + + /// + /// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00 + /// + /// + /// + /// + public static JsonSchemaBuilder ExclusiveMaximum(this JsonSchemaBuilder builder, bool value) + { + builder.Add(new Draft4ExclusiveMaximumKeyword(value)); + return builder; + } + + /// + /// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00 + /// + /// + /// + /// + public static JsonSchemaBuilder ExclusiveMinimum(this JsonSchemaBuilder builder, bool value) + { + builder.Add(new Draft4ExclusiveMinimumKeyword(value)); + return builder; + } + + /// + /// 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 static JsonSchemaBuilder Discriminator(this JsonSchemaBuilder builder, OpenApiDiscriminator discriminator) + { + builder.Add(new DiscriminatorKeyword(discriminator)); + return builder; + } + } + + /// + /// The Exclusive minimum keyword as defined in JSON schema Draft4 + /// + [SchemaKeyword(Name)] + public class Draft4ExclusiveMinimumKeyword : IJsonSchemaKeyword + { + /// + /// The schema keyword name + /// + public const string Name = "exclusiveMinimum"; + + /// + /// The ID. + /// + public bool MinValue { get; } + + internal Draft4ExclusiveMinimumKeyword(bool value) + { + MinValue = value; + } + + /// + /// Implementation of IJsonSchemaKeyword interface + /// + /// + /// + public void Evaluate(EvaluationContext context) + { + throw new NotImplementedException(); + } + } + + /// + /// The Exclusive maximum keyword as defined in JSON schema Draft4 + /// + [SchemaKeyword(Name)] + public class Draft4ExclusiveMaximumKeyword : IJsonSchemaKeyword + { + /// + /// The schema keyword name + /// + public const string Name = "exclusiveMaximum"; + + /// + /// The ID. + /// + public bool MaxValue { get; } + + internal Draft4ExclusiveMaximumKeyword(bool value) + { + MaxValue = value; + } + + /// + /// Implementation of IJsonSchemaKeyword interface + /// + /// + /// + public void Evaluate(EvaluationContext context) + { + throw new NotImplementedException(); + } + } + + /// + /// The nullable keyword + /// + [SchemaKeyword(Name)] + public class NullableKeyword : IJsonSchemaKeyword + { + /// + /// The schema keyword name + /// + public const string Name = "nullable"; + + /// + /// The ID. + /// + public bool Value { get; } + + /// + /// Creates a new . + /// + /// Whether the `minimum` value should be considered exclusive. + public NullableKeyword(bool value) + { + Value = value; + } + + /// + /// Implementation of IJsonSchemaKeyword interface + /// + /// + /// + public void Evaluate(EvaluationContext context) + { + throw new NotImplementedException(); + } + } + + /// + /// The extensions keyword + /// + [SchemaKeyword(Name)] + public class ExtensionsKeyword : IJsonSchemaKeyword + { + /// + /// The schema keyword name + /// + public const string Name = "extensions"; + + internal IDictionary Extensions { get; } + + internal ExtensionsKeyword(IDictionary extensions) + { + Extensions = extensions; + } + + /// + /// Implementation of IJsonSchemaKeyword interface + /// + /// + /// + public void Evaluate(EvaluationContext context) + { + throw new NotImplementedException(); + } + } + + /// + /// The summary keyword + /// + [SchemaKeyword(Name)] + public class SummaryKeyword : IJsonSchemaKeyword + { + /// + /// The schema keyword name + /// + public const string Name = "summary"; + + internal string Summary { get; } + + internal SummaryKeyword(string summary) + { + Summary = summary; + } + + /// + /// Implementation of IJsonSchemaKeyword interface + /// + /// + /// + public void Evaluate(EvaluationContext context) + { + throw new NotImplementedException(); + } + } + + /// + /// The AdditionalPropertiesAllowed Keyword + /// + [SchemaKeyword(Name)] + public class AdditionalPropertiesAllowedKeyword : IJsonSchemaKeyword + { + /// + /// The schema keyword name + /// + public const string Name = "additionalPropertiesAllowed"; + + internal bool AdditionalPropertiesAllowed { get; } + + internal AdditionalPropertiesAllowedKeyword(bool additionalPropertiesAllowed) + { + AdditionalPropertiesAllowed = additionalPropertiesAllowed; + } + + /// + /// Implementation of IJsonSchemaKeyword interface + /// + /// + /// + public void Evaluate(EvaluationContext context) + { + throw new NotImplementedException(); + } + } + + /// + /// The Discriminator Keyword + /// + [SchemaKeyword(Name)] + [SchemaSpecVersion(SpecVersion.Draft202012)] + public class DiscriminatorKeyword : OpenApiDiscriminator, IJsonSchemaKeyword + { + /// + /// The schema keyword name + /// + public const string Name = "discriminator"; + + /// + /// Parameter-less constructor + /// + public DiscriminatorKeyword() : base() { } + + /// + /// Initializes a copy of an instance + /// + internal DiscriminatorKeyword(OpenApiDiscriminator discriminator) : base(discriminator) { } + + /// + /// Implementation of IJsonSchemaKeyword interface + /// + /// + /// + public void Evaluate(EvaluationContext context) + { + throw new NotImplementedException(); + } + } + +} diff --git a/src/Microsoft.OpenApi/Extensions/JsonSchemaExtensions.cs b/src/Microsoft.OpenApi/Extensions/JsonSchemaExtensions.cs new file mode 100644 index 000000000..ff9466342 --- /dev/null +++ b/src/Microsoft.OpenApi/Extensions/JsonSchemaExtensions.cs @@ -0,0 +1,81 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Json.Schema; +using Json.Schema.OpenApi; +using Microsoft.OpenApi.Interfaces; + +namespace Microsoft.OpenApi.Extensions +{ + /// + /// Specifies Extension methods to be applied on a JSON schema instance + /// + public static class JsonSchemaExtensions + { + /// + /// Gets the `discriminator` keyword if it exists. + /// + public static DiscriminatorKeyword GetOpenApiDiscriminator(this JsonSchema schema) + { + return schema.TryGetKeyword(DiscriminatorKeyword.Name, out var k) ? k! : null; + } + + /// + /// Gets the `summary` keyword if it exists. + /// + public static string GetSummary(this JsonSchema schema) + { + return schema.TryGetKeyword(SummaryKeyword.Name, out var k) ? k.Summary! : null; + } + + /// + /// Gets the nullable value if it exists + /// + /// + /// + public static bool? GetNullable(this JsonSchema schema) + { + return schema.TryGetKeyword(NullableKeyword.Name, out var k) ? k.Value! : null; + } + + /// + /// Gets the additional properties value if it exists + /// + /// + /// + public static bool? GetAdditionalPropertiesAllowed(this JsonSchema schema) + { + return schema.TryGetKeyword(AdditionalPropertiesAllowedKeyword.Name, out var k) ? k.AdditionalPropertiesAllowed! : null; + } + + /// + /// Gets the exclusive maximum value if it exists + /// + /// + /// + public static bool? GetOpenApiExclusiveMaximum(this JsonSchema schema) + { + return schema.TryGetKeyword(Draft4ExclusiveMaximumKeyword.Name, out var k) ? k.MaxValue! : null; + } + + /// + /// Gets the exclusive minimum value if it exists + /// + /// + /// + public static bool? GetOpenApiExclusiveMinimum(this JsonSchema schema) + { + return schema.TryGetKeyword(Draft4ExclusiveMinimumKeyword.Name, out var k) ? k.MinValue! : null; + } + + /// + /// Gets the custom extensions if it exists + /// + /// + /// + public static IDictionary GetExtensions(this JsonSchema schema) + { + return schema.TryGetKeyword(ExtensionsKeyword.Name, out var k) ? k.Extensions! : null; + } + } +} diff --git a/src/Microsoft.OpenApi/Extensions/OpenAPIWriterExtensions.cs b/src/Microsoft.OpenApi/Extensions/OpenAPIWriterExtensions.cs index a32807ab6..3644bc6b0 100644 --- a/src/Microsoft.OpenApi/Extensions/OpenAPIWriterExtensions.cs +++ b/src/Microsoft.OpenApi/Extensions/OpenAPIWriterExtensions.cs @@ -1,9 +1,4 @@ using Microsoft.OpenApi.Writers; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Microsoft.OpenApi { @@ -14,7 +9,7 @@ internal static class OpenAPIWriterExtensions /// /// /// - internal static OpenApiWriterSettings GetSettings(this IOpenApiWriter openApiWriter) + internal static OpenApiWriterSettings GetSettings(this IOpenApiWriter openApiWriter) { if (openApiWriter is OpenApiWriterBase) { diff --git a/src/Microsoft.OpenApi/Extensions/OpenApiReferencableExtensions.cs b/src/Microsoft.OpenApi/Extensions/OpenApiReferencableExtensions.cs index 11fcd7e9e..837c9e9df 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 fa1938737..ee1c45646 100755 --- a/src/Microsoft.OpenApi/Extensions/OpenApiSerializableExtensions.cs +++ b/src/Microsoft.OpenApi/Extensions/OpenApiSerializableExtensions.cs @@ -74,7 +74,7 @@ public static void Serialize( this T element, Stream stream, OpenApiSpecVersion specVersion, - OpenApiFormat format, + OpenApiFormat format, OpenApiWriterSettings settings) where T : IOpenApiSerializable { @@ -120,7 +120,7 @@ public static void Serialize(this T element, IOpenApiWriter writer, OpenApiSp 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 970b3a976..8afa34a0c 100644 --- a/src/Microsoft.OpenApi/Extensions/OpenApiTypeMapper.cs +++ b/src/Microsoft.OpenApi/Extensions/OpenApiTypeMapper.cs @@ -3,7 +3,7 @@ using System; using System.Collections.Generic; -using Microsoft.OpenApi.Models; +using Json.Schema; namespace Microsoft.OpenApi.Extensions { @@ -12,40 +12,115 @@ namespace Microsoft.OpenApi.Extensions /// public static class OpenApiTypeMapper { - private static readonly Dictionary> _simpleTypeToOpenApiSchema = new() + private static readonly Dictionary> _simpleTypeToJsonSchema = new() { - [typeof(bool)] = () => new OpenApiSchema { Type = "boolean" }, - [typeof(byte)] = () => new OpenApiSchema { Type = "string", Format = "byte" }, - [typeof(int)] = () => new OpenApiSchema { Type = "integer", Format = "int32" }, - [typeof(uint)] = () => new OpenApiSchema { Type = "integer", Format = "int32" }, - [typeof(long)] = () => new OpenApiSchema { Type = "integer", Format = "int64" }, - [typeof(ulong)] = () => new OpenApiSchema { Type = "integer", Format = "int64" }, - [typeof(float)] = () => new OpenApiSchema { Type = "number", Format = "float" }, - [typeof(double)] = () => new OpenApiSchema { Type = "number", Format = "double" }, - [typeof(decimal)] = () => new OpenApiSchema { Type = "number", Format = "double" }, - [typeof(DateTime)] = () => new OpenApiSchema { Type = "string", Format = "date-time" }, - [typeof(DateTimeOffset)] = () => new OpenApiSchema { Type = "string", Format = "date-time" }, - [typeof(Guid)] = () => new OpenApiSchema { Type = "string", Format = "uuid" }, - [typeof(char)] = () => new OpenApiSchema { Type = "string" }, - + [typeof(bool)] = () => new JsonSchemaBuilder().Type(SchemaValueType.Boolean).Build(), + [typeof(byte)] = () => new JsonSchemaBuilder().Type(SchemaValueType.String).Format("byte").Build(), + [typeof(int)] = () => new JsonSchemaBuilder().Type(SchemaValueType.Integer).Format("int32").Build(), + [typeof(uint)] = () => new JsonSchemaBuilder().Type(SchemaValueType.Integer).Format("int32").Build(), + [typeof(long)] = () => new JsonSchemaBuilder().Type(SchemaValueType.Integer).Format("int64").Build(), + [typeof(ulong)] = () => new JsonSchemaBuilder().Type(SchemaValueType.Integer).Format("int64").Build(), + [typeof(float)] = () => new JsonSchemaBuilder().Type(SchemaValueType.Number).Format("float").Build(), + [typeof(double)] = () => new JsonSchemaBuilder().Type(SchemaValueType.Number).Format("double").Build(), + [typeof(decimal)] = () => new JsonSchemaBuilder().Type(SchemaValueType.Number).Format("double").Build(), + [typeof(DateTime)] = () => new JsonSchemaBuilder().Type(SchemaValueType.String).Format("date-time").Build(), + [typeof(DateTimeOffset)] = () => new JsonSchemaBuilder().Type(SchemaValueType.String).Format("date-time").Build(), + [typeof(Guid)] = () => new JsonSchemaBuilder().Type(SchemaValueType.String).Format("uuid").Build(), + [typeof(char)] = () => new JsonSchemaBuilder().Type(SchemaValueType.String).Format("string").Build(), + // Nullable types - [typeof(bool?)] = () => new OpenApiSchema { Type = "boolean", Nullable = true }, - [typeof(byte?)] = () => new OpenApiSchema { Type = "string", Format = "byte", Nullable = true }, - [typeof(int?)] = () => new OpenApiSchema { Type = "integer", Format = "int32", Nullable = true }, - [typeof(uint?)] = () => new OpenApiSchema { Type = "integer", Format = "int32", Nullable = true }, - [typeof(long?)] = () => new OpenApiSchema { Type = "integer", Format = "int64", Nullable = true }, - [typeof(ulong?)] = () => new OpenApiSchema { Type = "integer", Format = "int64", Nullable = true }, - [typeof(float?)] = () => new OpenApiSchema { Type = "number", Format = "float", Nullable = true }, - [typeof(double?)] = () => new OpenApiSchema { Type = "number", Format = "double", Nullable = true }, - [typeof(decimal?)] = () => new OpenApiSchema { Type = "number", Format = "double", Nullable = true }, - [typeof(DateTime?)] = () => new OpenApiSchema { Type = "string", Format = "date-time", Nullable = true }, - [typeof(DateTimeOffset?)] = () => new OpenApiSchema { Type = "string", Format = "date-time", Nullable = true }, - [typeof(Guid?)] = () => new OpenApiSchema { Type = "string", Format = "uuid", Nullable = true }, - [typeof(char?)] = () => new OpenApiSchema { Type = "string", Nullable = true }, - - [typeof(Uri)] = () => new OpenApiSchema { Type = "string", Format = "uri"}, // Uri is treated as simple string - [typeof(string)] = () => new OpenApiSchema { Type = "string" }, - [typeof(object)] = () => new OpenApiSchema { Type = "object" } + [typeof(bool?)] = () => new JsonSchemaBuilder() + .AnyOf( + new JsonSchemaBuilder().Type(SchemaValueType.Null).Build(), + new JsonSchemaBuilder().Type(SchemaValueType.Boolean).Build() + ).Build(), + + [typeof(byte?)] = () => new JsonSchemaBuilder() + .AnyOf( + new JsonSchemaBuilder().Type(SchemaValueType.Null).Build(), + new JsonSchemaBuilder().Type(SchemaValueType.String).Build() + ) + .Format("byte").Build(), + + [typeof(int?)] = () => new JsonSchemaBuilder() + .AnyOf( + new JsonSchemaBuilder().Type(SchemaValueType.Null).Build(), + new JsonSchemaBuilder().Type(SchemaValueType.Integer).Build() + ) + .Format("int32").Build(), + + [typeof(uint?)] = () => new JsonSchemaBuilder().AnyOf( + new JsonSchemaBuilder().Type(SchemaValueType.Null).Build(), + new JsonSchemaBuilder().Type(SchemaValueType.Integer).Build() + ) + .Format("int32").Build(), + + [typeof(long?)] = () => new JsonSchemaBuilder() + .AnyOf( + new JsonSchemaBuilder().Type(SchemaValueType.Null).Build(), + new JsonSchemaBuilder().Type(SchemaValueType.Integer).Build() + ) + .Format("int64").Build(), + + [typeof(ulong?)] = () => new JsonSchemaBuilder() + .AnyOf( + new JsonSchemaBuilder().Type(SchemaValueType.Null).Build(), + new JsonSchemaBuilder().Type(SchemaValueType.Integer).Build() + ) + .Format("int64").Build(), + + [typeof(float?)] = () => new JsonSchemaBuilder() + .AnyOf( + new JsonSchemaBuilder().Type(SchemaValueType.Null).Build(), + new JsonSchemaBuilder().Type(SchemaValueType.Integer).Build() + ) + .Format("float").Build(), + + [typeof(double?)] = () => new JsonSchemaBuilder() + .AnyOf( + new JsonSchemaBuilder().Type(SchemaValueType.Null).Build(), + new JsonSchemaBuilder().Type(SchemaValueType.Number).Build()) + .Format("double").Build(), + + [typeof(decimal?)] = () => new JsonSchemaBuilder() + .AnyOf( + new JsonSchemaBuilder().Type(SchemaValueType.Null).Build(), + new JsonSchemaBuilder().Type(SchemaValueType.Number).Build() + ) + .Format("double").Build(), + + [typeof(DateTime?)] = () => new JsonSchemaBuilder() + .AnyOf( + new JsonSchemaBuilder().Type(SchemaValueType.Null).Build(), + new JsonSchemaBuilder().Type(SchemaValueType.String).Build() + ) + .Format("date-time").Build(), + + [typeof(DateTimeOffset?)] = () => new JsonSchemaBuilder() + .AnyOf( + new JsonSchemaBuilder().Type(SchemaValueType.Null).Build(), + new JsonSchemaBuilder().Type(SchemaValueType.String).Build() + ) + .Format("date-time").Build(), + + [typeof(Guid?)] = () => new JsonSchemaBuilder() + .AnyOf( + new JsonSchemaBuilder().Type(SchemaValueType.Null).Build(), + new JsonSchemaBuilder().Type(SchemaValueType.String).Build() + ) + .Format("string").Build(), + + [typeof(char?)] = () => new JsonSchemaBuilder() + .AnyOf( + new JsonSchemaBuilder().Type(SchemaValueType.Null).Build(), + new JsonSchemaBuilder().Type(SchemaValueType.String).Build() + ) + .Format("string").Build(), + + [typeof(Uri)] = () => new JsonSchemaBuilder().Type(SchemaValueType.String).Format("uri").Build(), // Uri is treated as simple string + [typeof(string)] = () => new JsonSchemaBuilder().Type(SchemaValueType.String).Build(), + [typeof(object)] = () => new JsonSchemaBuilder().Type(SchemaValueType.Object).Build(), + }; /// @@ -70,61 +145,78 @@ public static class OpenApiTypeMapper /// password string password Used to hint UIs the input needs to be obscured. /// If the type is not recognized as "simple", System.String will be returned. /// - public static OpenApiSchema MapTypeToOpenApiPrimitiveType(this Type type) + public static JsonSchema MapTypeToJsonPrimitiveType(this Type type) { if (type == null) { throw new ArgumentNullException(nameof(type)); } - return _simpleTypeToOpenApiSchema.TryGetValue(type, out var result) + return _simpleTypeToJsonSchema.TryGetValue(type, out var result) ? result() - : new OpenApiSchema { Type = "string" }; + : new JsonSchemaBuilder().Type(SchemaValueType.String).Build(); } /// - /// 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 /// - public static Type MapOpenApiPrimitiveTypeToSimpleType(this OpenApiSchema schema) + public static Type MapJsonSchemaValueTypeToSimpleType(this JsonSchema schema) { if (schema == null) { throw new ArgumentNullException(nameof(schema)); } - var type = (schema.Type?.ToLowerInvariant(), schema.Format?.ToLowerInvariant(), schema.Nullable) switch + var type = schema.GetJsonType(); + var format = schema.GetFormat().Key; + var result = (type, format) switch { - ("boolean", null, false) => typeof(bool), - ("integer", "int32", false) => typeof(int), - ("integer", "int64", false) => typeof(long), - ("number", "float", false) => typeof(float), - ("number", "double", false) => typeof(double), - ("number", "decimal", false) => typeof(decimal), - ("string", "byte", false) => typeof(byte), - ("string", "date-time", false) => typeof(DateTimeOffset), - ("string", "uuid", false) => typeof(Guid), - ("string", "duration", false) => typeof(TimeSpan), - ("string", "char", false) => typeof(char), - ("string", null, false) => typeof(string), - ("object", null, false) => typeof(object), - ("string", "uri", false) => typeof(Uri), - ("integer", "int32", true) => typeof(int?), - ("integer", "int64", true) => typeof(long?), - ("number", "float", true) => typeof(float?), - ("number", "double", true) => typeof(double?), - ("number", "decimal", true) => typeof(decimal?), - ("string", "byte", true) => typeof(byte?), - ("string", "date-time", true) => typeof(DateTimeOffset?), - ("string", "uuid", true) => typeof(Guid?), - ("string", "char", true) => typeof(char?), - ("boolean", null, true) => typeof(bool?), + (SchemaValueType.Boolean, null) => typeof(bool), + (SchemaValueType.Integer, "int32") => typeof(int), + (SchemaValueType.Integer, "int64") => typeof(long), + (SchemaValueType.Number, "float") => typeof(float), + (SchemaValueType.Number, "double") => typeof(double), + (SchemaValueType.Number, "decimal") => typeof(decimal), + (SchemaValueType.String, "byte") => typeof(byte), + (SchemaValueType.String, "date-time") => typeof(DateTimeOffset), + (SchemaValueType.String, "uuid") => typeof(Guid), + (SchemaValueType.String, "duration") => typeof(TimeSpan), + (SchemaValueType.String, "char") => typeof(char), + (SchemaValueType.String, null) => typeof(string), + (SchemaValueType.Object, null) => typeof(object), + (SchemaValueType.String, "uri") => typeof(Uri), + (SchemaValueType.Integer or null, "int32") => typeof(int?), + (SchemaValueType.Integer or null, "int64") => typeof(long?), + (SchemaValueType.Number or null, "float") => typeof(float?), + (SchemaValueType.Number or null, "double") => typeof(double?), + (SchemaValueType.Number or null, "decimal") => typeof(decimal?), + (SchemaValueType.String or null, "byte") => typeof(byte?), + (SchemaValueType.String or null, "date-time") => typeof(DateTimeOffset?), + (SchemaValueType.String or null, "uuid") => typeof(Guid?), + (SchemaValueType.String or null, "char") => typeof(char?), + (SchemaValueType.Boolean or null, null) => typeof(bool?), _ => typeof(string), }; - - return type; + + return result; + } + + internal static string ConvertSchemaValueTypeToString(SchemaValueType value) + { + return value switch + { + SchemaValueType.String => "string", + SchemaValueType.Number => "number", + SchemaValueType.Integer => "integer", + SchemaValueType.Boolean => "boolean", + SchemaValueType.Array => "array", + SchemaValueType.Object => "object", + SchemaValueType.Null => "null", + _ => throw new NotSupportedException(), + }; } } } diff --git a/src/Microsoft.OpenApi/Helpers/JsonNodeCloneHelper.cs b/src/Microsoft.OpenApi/Helpers/JsonNodeCloneHelper.cs index 33d8fed9e..9385f8ceb 100644 --- a/src/Microsoft.OpenApi/Helpers/JsonNodeCloneHelper.cs +++ b/src/Microsoft.OpenApi/Helpers/JsonNodeCloneHelper.cs @@ -3,27 +3,40 @@ using System.Text.Json; using System.Text.Json.Serialization; +using Json.Schema; using Microsoft.OpenApi.Any; namespace Microsoft.OpenApi.Helpers { internal static class JsonNodeCloneHelper { + private static readonly JsonSerializerOptions options = new() + { + ReferenceHandler = ReferenceHandler.IgnoreCycles + }; + internal static OpenApiAny Clone(OpenApiAny value) { - if(value == null) + var jsonString = Serialize(value); + var result = JsonSerializer.Deserialize(jsonString, options); + + return result; + } + + internal static JsonSchema CloneJsonSchema(JsonSchema schema) + { + var jsonString = Serialize(schema); + var result = JsonSerializer.Deserialize(jsonString, options); + return result; + } + + private static string Serialize(object obj) + { + if (obj == null) { return null; } - - var options = new JsonSerializerOptions - { - ReferenceHandler = ReferenceHandler.IgnoreCycles - }; - - var jsonString = JsonSerializer.Serialize(value.Node, options); - var result = JsonSerializer.Deserialize(jsonString, options); - + var result = JsonSerializer.Serialize(obj, options); return result; } } diff --git a/src/Microsoft.OpenApi/Helpers/SchemaSerializerHelper.cs b/src/Microsoft.OpenApi/Helpers/SchemaSerializerHelper.cs new file mode 100644 index 000000000..656a49106 --- /dev/null +++ b/src/Microsoft.OpenApi/Helpers/SchemaSerializerHelper.cs @@ -0,0 +1,113 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System.Collections.Generic; +using System.Linq; +using Json.Schema; +using Microsoft.OpenApi.Extensions; +using Microsoft.OpenApi.Interfaces; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Writers; + +namespace Microsoft.OpenApi.Helpers +{ + internal static class SchemaSerializerHelper + { + internal static void WriteAsItemsProperties(JsonSchema schema, IOpenApiWriter writer, IDictionary extensions) + { + if (writer == null) + { + throw Error.ArgumentNull(nameof(writer)); + } + + // type + if (schema.GetJsonType() != null) + { + writer.WritePropertyName(OpenApiConstants.Type); + var type = schema.GetJsonType().Value; + writer.WriteValue(OpenApiTypeMapper.ConvertSchemaValueTypeToString(type)); + } + + // format + var format = schema.GetFormat()?.Key; + if (string.IsNullOrEmpty(format)) + { + format = RetrieveFormatFromNestedSchema(schema.GetAllOf()) ?? RetrieveFormatFromNestedSchema(schema.GetOneOf()) + ?? RetrieveFormatFromNestedSchema(schema.GetAnyOf()); + } + writer.WriteProperty(OpenApiConstants.Format, format); + + // items + writer.WriteOptionalObject(OpenApiConstants.Items, schema.GetItems(), + (w, s) => w.WriteJsonSchema(s)); + + // collectionFormat + // We need information from style in parameter to populate this. + // The best effort we can make is to pull this information from the first parameter + // that leverages this schema. However, that in itself may not be as simple + // as the schema directly under parameter might be referencing one in the Components, + // so we will need to do a full scan of the object before we can write the value for + // this property. This is not supported yet, so we will skip this property at the moment. + + // default + if (schema.GetDefault() != null) + { + writer.WritePropertyName(OpenApiConstants.Default); + writer.WriteValue(schema.GetDefault()); + } + + // maximum + writer.WriteProperty(OpenApiConstants.Maximum, schema.GetMaximum()); + + // exclusiveMaximum + writer.WriteProperty(OpenApiConstants.ExclusiveMaximum, schema.GetExclusiveMaximum()); + + // minimum + writer.WriteProperty(OpenApiConstants.Minimum, schema.GetMinimum()); + + // exclusiveMinimum + writer.WriteProperty(OpenApiConstants.ExclusiveMinimum, schema.GetExclusiveMinimum()); + + // maxLength + writer.WriteProperty(OpenApiConstants.MaxLength, schema.GetMaxLength()); + + // minLength + writer.WriteProperty(OpenApiConstants.MinLength, schema.GetMinLength()); + + // pattern + writer.WriteProperty(OpenApiConstants.Pattern, schema.GetPattern()?.ToString()); + + // maxItems + writer.WriteProperty(OpenApiConstants.MaxItems, schema.GetMaxItems()); + + // minItems + writer.WriteProperty(OpenApiConstants.MinItems, schema.GetMinItems()); + + // enum + if (schema.GetEnum() != null) + { + writer.WritePropertyName(OpenApiConstants.Enum); + writer.WriteValue(schema.GetEnum()); + } + + // multipleOf + writer.WriteProperty(OpenApiConstants.MultipleOf, schema.GetMultipleOf()); + + // extensions + writer.WriteExtensions(extensions, OpenApiSpecVersion.OpenApi2_0); + } + + private static string RetrieveFormatFromNestedSchema(IReadOnlyCollection schema) + { + if (schema != null) + { + return schema + .Where(item => !string.IsNullOrEmpty(item.GetFormat()?.Key)) + .Select(item => item.GetFormat().Key) + .FirstOrDefault(); + } + + return null; + } + } +} diff --git a/src/Microsoft.OpenApi/Interfaces/IEffective.cs b/src/Microsoft.OpenApi/Interfaces/IEffective.cs index b62ec12ab..0fd150686 100644 --- a/src/Microsoft.OpenApi/Interfaces/IEffective.cs +++ b/src/Microsoft.OpenApi/Interfaces/IEffective.cs @@ -13,7 +13,7 @@ namespace Microsoft.OpenApi.Interfaces /// 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 + public interface IEffective where T : class, IOpenApiElement { /// /// Returns a calculated and cloned version of the element. diff --git a/src/Microsoft.OpenApi/Interfaces/IOpenApiExtensible.cs b/src/Microsoft.OpenApi/Interfaces/IOpenApiExtensible.cs index 8e28d09d5..2969168c8 100644 --- a/src/Microsoft.OpenApi/Interfaces/IOpenApiExtensible.cs +++ b/src/Microsoft.OpenApi/Interfaces/IOpenApiExtensible.cs @@ -2,7 +2,6 @@ // Licensed under the MIT license. using System.Collections.Generic; -using System.Text.Json.Nodes; namespace Microsoft.OpenApi.Interfaces { diff --git a/src/Microsoft.OpenApi/Interfaces/IOpenApiReferenceable.cs b/src/Microsoft.OpenApi/Interfaces/IOpenApiReferenceable.cs index e4d1224ab..c451b3949 100644 --- a/src/Microsoft.OpenApi/Interfaces/IOpenApiReferenceable.cs +++ b/src/Microsoft.OpenApi/Interfaces/IOpenApiReferenceable.cs @@ -3,7 +3,6 @@ using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Writers; -using static Microsoft.OpenApi.Extensions.OpenApiSerializableExtensions; namespace Microsoft.OpenApi.Interfaces { @@ -22,12 +21,12 @@ public interface IOpenApiReferenceable : IOpenApiSerializable /// Reference object. /// OpenApiReference Reference { get; set; } - + /// /// Serialize to OpenAPI V31 document without using reference. /// void SerializeAsV31WithoutReference(IOpenApiWriter writer); - + /// /// Serialize to OpenAPI V3 document without using reference. /// diff --git a/src/Microsoft.OpenApi/Microsoft.OpenApi.csproj b/src/Microsoft.OpenApi/Microsoft.OpenApi.csproj index c0b19dd13..6425c7f83 100644 --- a/src/Microsoft.OpenApi/Microsoft.OpenApi.csproj +++ b/src/Microsoft.OpenApi/Microsoft.OpenApi.csproj @@ -1,4 +1,4 @@ - + netstandard2.0 Latest @@ -33,6 +33,10 @@ true + + + + diff --git a/src/Microsoft.OpenApi/Models/OpenApiCallback.cs b/src/Microsoft.OpenApi/Models/OpenApiCallback.cs index dce353849..5576ed4ad 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiCallback.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiCallback.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Text.Json.Nodes; using Microsoft.OpenApi.Expressions; using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Writers; @@ -76,7 +75,7 @@ public void AddPathItem(RuntimeExpression expression, OpenApiPathItem pathItem) PathItems.Add(expression, pathItem); } - + /// /// Serialize to Open Api v3.1 /// @@ -87,13 +86,13 @@ public virtual void SerializeAsV31(IOpenApiWriter writer) SerializeInternal(writer, (writer, element) => element.SerializeAsV31(writer), (writer, referenceElement) => referenceElement.SerializeAsV31WithoutReference(writer)); } - + /// /// Serialize to Open Api v3.0 /// public virtual void SerializeAsV3(IOpenApiWriter writer) { - SerializeInternal(writer, (writer, element) => element.SerializeAsV3(writer), + SerializeInternal(writer, (writer, element) => element.SerializeAsV3(writer), (writer, referenceElement) => referenceElement.SerializeAsV3WithoutReference(writer)); } @@ -103,7 +102,7 @@ public virtual void SerializeAsV3(IOpenApiWriter writer) /// /// /// - private void SerializeInternal(IOpenApiWriter writer, + private void SerializeInternal(IOpenApiWriter writer, Action callback, Action action) { @@ -149,7 +148,7 @@ public OpenApiCallback GetEffective(OpenApiDocument doc) /// public virtual void SerializeAsV31WithoutReference(IOpenApiWriter writer) { - SerializeInternalWithoutReference(writer, OpenApiSpecVersion.OpenApi3_1, + SerializeInternalWithoutReference(writer, OpenApiSpecVersion.OpenApi3_1, (writer, element) => element.SerializeAsV31(writer)); } @@ -158,11 +157,11 @@ public virtual void SerializeAsV31WithoutReference(IOpenApiWriter writer) /// public virtual void SerializeAsV3WithoutReference(IOpenApiWriter writer) { - SerializeInternalWithoutReference(writer, OpenApiSpecVersion.OpenApi3_0, + SerializeInternalWithoutReference(writer, OpenApiSpecVersion.OpenApi3_0, (writer, element) => element.SerializeAsV3(writer)); - } - - internal void SerializeInternalWithoutReference(IOpenApiWriter writer, OpenApiSpecVersion version, + } + + internal void SerializeInternalWithoutReference(IOpenApiWriter writer, OpenApiSpecVersion version, Action callback) { writer.WriteStartObject(); @@ -175,7 +174,7 @@ internal void SerializeInternalWithoutReference(IOpenApiWriter writer, OpenApiSp // extensions writer.WriteExtensions(Extensions, version); - + writer.WriteEndObject(); } diff --git a/src/Microsoft.OpenApi/Models/OpenApiComponents.cs b/src/Microsoft.OpenApi/Models/OpenApiComponents.cs index 7b56745cd..78781d66b 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiComponents.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiComponents.cs @@ -3,9 +3,12 @@ using System; using System.Collections.Generic; +using System.Linq; +using Json.Schema; using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Writers; + namespace Microsoft.OpenApi.Models { /// @@ -14,9 +17,9 @@ namespace Microsoft.OpenApi.Models public class OpenApiComponents : IOpenApiSerializable, IOpenApiExtensible { /// - /// An object to hold reusable Objects. + /// An object to hold reusable Objects. /// - public virtual IDictionary Schemas { get; set; } = new Dictionary(); + public IDictionary Schemas { get; set; } = new Dictionary(); /// /// An object to hold reusable Objects. @@ -81,7 +84,7 @@ public OpenApiComponents() { } /// public OpenApiComponents(OpenApiComponents components) { - Schemas = components?.Schemas != null ? new Dictionary(components.Schemas) : null; + Schemas = components?.Schemas != null ? new Dictionary(components.Schemas) : null; Responses = components?.Responses != null ? new Dictionary(components.Responses) : null; Parameters = components?.Parameters != null ? new Dictionary(components.Parameters) : null; Examples = components?.Examples != null ? new Dictionary(components.Examples) : null; @@ -111,7 +114,7 @@ public void SerializeAsV31(IOpenApiWriter writer) } writer.WriteStartObject(); - + // pathItems - only present in v3.1 writer.WriteOptionalMap( OpenApiConstants.PathItems, @@ -131,7 +134,7 @@ public void SerializeAsV31(IOpenApiWriter writer) }); SerializeInternal(writer, OpenApiSpecVersion.OpenApi3_1, (writer, element) => element.SerializeAsV31(writer), - (writer, referenceElement) => referenceElement.SerializeAsV31WithoutReference(writer)); + (writer, referenceElement) => referenceElement.SerializeAsV31WithoutReference(writer)); } /// @@ -154,11 +157,11 @@ public void SerializeAsV3(IOpenApiWriter writer) SerializeInternal(writer, OpenApiSpecVersion.OpenApi3_0, (writer, element) => element.SerializeAsV3(writer), (writer, referenceElement) => referenceElement.SerializeAsV3WithoutReference(writer)); } - + /// /// Serialize . /// - private void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion version, + 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. @@ -168,17 +171,17 @@ private void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion version writer.WriteOptionalMap( OpenApiConstants.Schemas, Schemas, - (w, key, component) => + (w, key, s) => { - if (component.Reference != null && - component.Reference.Type == ReferenceType.Schema && - string.Equals(component.Reference.Id, key, StringComparison.OrdinalIgnoreCase)) + var reference = s.GetRef(); + if (reference != null && + reference.OriginalString.Split('/').Last().Equals(key)) { - action(w, component); + w.WriteJsonSchemaWithoutReference(w, s); } else { - callback(w, component); + w.WriteJsonSchema(s); } }); @@ -243,9 +246,9 @@ private void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion version (w, key, component) => { if (component.Reference != null && - component.Reference.Type == ReferenceType.RequestBody && + component.Reference.Type == ReferenceType.RequestBody && string.Equals(component.Reference.Id, key, StringComparison.OrdinalIgnoreCase)) - + { action(w, component); } @@ -326,7 +329,7 @@ private void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion version callback(w, component); } }); - + // extensions writer.WriteExtensions(Extensions, version); writer.WriteEndObject(); @@ -336,19 +339,16 @@ private void RenderComponents(IOpenApiWriter writer) { var loops = writer.GetSettings().LoopDetector.Loops; writer.WriteStartObject(); - if (loops.TryGetValue(typeof(OpenApiSchema), out List schemas)) + if (loops.TryGetValue(typeof(JsonSchema), out List schemas)) { - writer.WriteOptionalMap( OpenApiConstants.Schemas, Schemas, - static (w, key, component) => { - component.SerializeAsV31WithoutReference(w); - }); + static (w, key, s) => { w.WriteJsonSchema(s); }); } writer.WriteEndObject(); } - + /// /// Serialize to Open Api v2.0. /// diff --git a/src/Microsoft.OpenApi/Models/OpenApiConstants.cs b/src/Microsoft.OpenApi/Models/OpenApiConstants.cs index 235240e33..fe74fa238 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiConstants.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiConstants.cs @@ -19,7 +19,7 @@ public static class OpenApiConstants /// Field: Info /// public const string Info = "info"; - + /// /// Field: JsonSchemaDialect /// @@ -29,7 +29,7 @@ public static class OpenApiConstants /// Field: Webhooks /// public const string Webhooks = "webhooks"; - + /// /// Field: Title /// @@ -89,7 +89,7 @@ public static class OpenApiConstants /// Field: PathItems /// public const string PathItems = "pathItems"; - + /// /// Field: Security /// @@ -595,6 +595,16 @@ public static class OpenApiConstants /// public static readonly Uri defaultUrl = new Uri("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/"; + #region V2.0 /// diff --git a/src/Microsoft.OpenApi/Models/OpenApiContact.cs b/src/Microsoft.OpenApi/Models/OpenApiContact.cs index 801fbb0c4..1f60e6c96 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiContact.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiContact.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Text.Json.Nodes; using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Writers; @@ -59,7 +58,7 @@ public void SerializeAsV31(IOpenApiWriter writer) { WriteInternal(writer, OpenApiSpecVersion.OpenApi3_1); } - + /// /// Serialize to Open Api v3.0 /// diff --git a/src/Microsoft.OpenApi/Models/OpenApiDiscriminator.cs b/src/Microsoft.OpenApi/Models/OpenApiDiscriminator.cs index 3a2434d10..604d31b67 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,6 +39,7 @@ 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; } /// @@ -43,6 +49,11 @@ public OpenApiDiscriminator(OpenApiDiscriminator discriminator) public void SerializeAsV31(IOpenApiWriter writer) { SerializeInternal(writer); + + // extensions + writer.WriteExtensions(Extensions, OpenApiSpecVersion.OpenApi3_1); + + writer.WriteEndObject(); } /// @@ -51,6 +62,8 @@ public void SerializeAsV31(IOpenApiWriter writer) public void SerializeAsV3(IOpenApiWriter writer) { SerializeInternal(writer); + + writer.WriteEndObject(); } /// diff --git a/src/Microsoft.OpenApi/Models/OpenApiDocument.cs b/src/Microsoft.OpenApi/Models/OpenApiDocument.cs index 6e3672941..c5463ef61 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiDocument.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiDocument.cs @@ -7,7 +7,8 @@ using System.Linq; using System.Security.Cryptography; using System.Text; -using System.Text.Json.Nodes; +using System.Text.Json; +using Json.Schema; using Microsoft.OpenApi.Exceptions; using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Services; @@ -18,7 +19,7 @@ namespace Microsoft.OpenApi.Models /// /// Describes an OpenAPI object (OpenAPI document). See: https://swagger.io/specification /// - public class OpenApiDocument : IOpenApiSerializable, IOpenApiExtensible + public class OpenApiDocument : IOpenApiSerializable, IOpenApiExtensible, IBaseDocument { /// /// Related workspace containing OpenApiDocuments that are referenced in this document @@ -83,10 +84,15 @@ public class OpenApiDocument : IOpenApiSerializable, IOpenApiExtensible /// public string HashCode => GenerateHashValue(this); + /// + /// Implements IBaseDocument + /// + public Uri BaseUri { get; } + /// /// Parameter-less constructor /// - public OpenApiDocument() {} + public OpenApiDocument() { } /// /// Initializes a copy of an an object @@ -104,7 +110,7 @@ public OpenApiDocument(OpenApiDocument document) 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; - } + } /// /// Serialize to Open API v3.1 document. @@ -115,16 +121,16 @@ public void SerializeAsV31(IOpenApiWriter writer) writer = writer ?? throw Error.ArgumentNull(nameof(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), (w, element) => element.SerializeAsV31WithoutReference(w)); - + // webhooks writer.WriteOptionalMap( OpenApiConstants.Webhooks, @@ -155,10 +161,10 @@ public void SerializeAsV3(IOpenApiWriter writer) writer = writer ?? throw Error.ArgumentNull(nameof(writer)); writer.WriteStartObject(); - + // openapi writer.WriteProperty(OpenApiConstants.OpenApi, "3.0.1"); - SerializeInternal(writer, OpenApiSpecVersion.OpenApi3_0, (w, element) => element.SerializeAsV3(w), + SerializeInternal(writer, OpenApiSpecVersion.OpenApi3_0, (w, element) => element.SerializeAsV3(w), (w, element) => element.SerializeAsV3WithoutReference(w)); writer.WriteEndObject(); } @@ -170,10 +176,10 @@ public void SerializeAsV3(IOpenApiWriter writer) /// /// /// - private void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion version, - Action callback, + private void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion version, + Action callback, Action action) - { + { // info writer.WriteRequiredObject(OpenApiConstants.Info, Info, callback); @@ -229,10 +235,10 @@ public void SerializeAsV2(IOpenApiWriter writer) { var loops = writer.GetSettings().LoopDetector.Loops; - if (loops.TryGetValue(typeof(OpenApiSchema), out List schemas)) + if (loops.TryGetValue(typeof(JsonSchema), out List schemas)) { - var openApiSchemas = schemas.Cast().Distinct().ToList() - .ToDictionary(k => k.Reference.Id); + var openApiSchemas = schemas.Cast().Distinct() + .ToDictionary(k => k.GetRef().ToString()); foreach (var schema in openApiSchemas.Values.ToList()) { @@ -242,10 +248,7 @@ public void SerializeAsV2(IOpenApiWriter writer) writer.WriteOptionalMap( OpenApiConstants.Definitions, openApiSchemas, - (w, key, component) => - { - component.SerializeAsV2WithoutReference(w); - }); + (w, key, s) => w.WriteJsonSchema(s)); } } else @@ -253,13 +256,45 @@ public void SerializeAsV2(IOpenApiWriter writer) // 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. // definitions + if (Components?.Schemas != null) + { + writer.WriteOptionalMap( + OpenApiConstants.Definitions, + Components?.Schemas, + (w, key, s) => + { + var reference = s.GetRef(); + if (reference != null && + reference.OriginalString.Split('/').Last().Equals(key)) + { + w.WriteJsonSchemaWithoutReference(w, s); + } + else + { + w.WriteJsonSchema(s); + } + }); + } + + // parameters + var parameters = Components?.Parameters != null + ? new Dictionary(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.Definitions, - Components?.Schemas, + OpenApiConstants.Parameters, + parameters, (w, key, component) => { if (component.Reference != null && - component.Reference.Type == ReferenceType.Schema && + component.Reference.Type == ReferenceType.Parameter && component.Reference.Id == key) { component.SerializeAsV2WithoutReference(w); @@ -269,88 +304,60 @@ public void SerializeAsV2(IOpenApiWriter writer) component.SerializeAsV2(w); } }); - } - // parameters - var parameters = Components?.Parameters != null - ? new Dictionary(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 != null && - component.Reference.Type == ReferenceType.Parameter && - component.Reference.Id == key) - { - component.SerializeAsV2WithoutReference(w); - } - else - { - component.SerializeAsV2(w); - } - }); - // responses - writer.WriteOptionalMap( - OpenApiConstants.Responses, - Components?.Responses, - (w, key, component) => - { - if (component.Reference != null && - component.Reference.Type == ReferenceType.Response && - component.Reference.Id == key) - { - component.SerializeAsV2WithoutReference(w); - } - else + // responses + writer.WriteOptionalMap( + OpenApiConstants.Responses, + Components?.Responses, + (w, key, component) => { - component.SerializeAsV2(w); - } - }); + if (component.Reference != null && + component.Reference.Type == ReferenceType.Response && + component.Reference.Id == key) + { + component.SerializeAsV2WithoutReference(w); + } + else + { + component.SerializeAsV2(w); + } + }); - // securityDefinitions - writer.WriteOptionalMap( - OpenApiConstants.SecurityDefinitions, - Components?.SecuritySchemes, - (w, key, component) => - { - if (component.Reference != null && - component.Reference.Type == ReferenceType.SecurityScheme && - component.Reference.Id == key) - { - component.SerializeAsV2WithoutReference(w); - } - else + // securityDefinitions + writer.WriteOptionalMap( + OpenApiConstants.SecurityDefinitions, + Components?.SecuritySchemes, + (w, key, component) => { - component.SerializeAsV2(w); - } - }); + if (component.Reference != null && + component.Reference.Type == ReferenceType.SecurityScheme && + component.Reference.Id == key) + { + component.SerializeAsV2WithoutReference(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.SerializeAsV2WithoutReference(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 void WriteHostInfoV2(IOpenApiWriter writer, IList servers) @@ -374,13 +381,14 @@ private static void WriteHostInfoV2(IOpenApiWriter writer, IList writer.WriteProperty( OpenApiConstants.Host, firstServerUrl.GetComponents(UriComponents.Host | UriComponents.Port, UriFormat.SafeUnescaped)); - + // basePath if (firstServerUrl.AbsolutePath != "/") { writer.WriteProperty(OpenApiConstants.BasePath, firstServerUrl.AbsolutePath); } - } else + } + else { var relativeUrl = firstServerUrl.OriginalString; if (relativeUrl.StartsWith("//")) @@ -509,7 +517,7 @@ internal IOpenApiReferenceable ResolveReference(OpenApiReference reference, bool throw new ArgumentException(Properties.SRResource.WorkspaceRequredForExternalReferenceResolution); } return this.Workspace.ResolveReference(reference); - } + } if (!reference.Type.HasValue) { @@ -540,51 +548,46 @@ internal IOpenApiReferenceable ResolveReference(OpenApiReference reference, bool { switch (reference.Type) { - case ReferenceType.Schema: - var resolvedSchema = this.Components.Schemas[reference.Id]; - resolvedSchema.Description = reference.Description != null ? reference.Description : resolvedSchema.Description; - return resolvedSchema; - case ReferenceType.PathItem: var resolvedPathItem = this.Components.PathItems[reference.Id]; - resolvedPathItem.Description = reference.Description != null ? reference.Description : resolvedPathItem.Description; - resolvedPathItem.Summary = reference.Summary != null ? reference.Summary : resolvedPathItem.Summary; + resolvedPathItem.Description = reference.Description ?? resolvedPathItem.Description; + resolvedPathItem.Summary = reference.Summary ?? resolvedPathItem.Summary; return resolvedPathItem; - + case ReferenceType.Response: var resolvedResponse = this.Components.Responses[reference.Id]; - resolvedResponse.Description = reference.Description != null ? reference.Description : resolvedResponse.Description; + resolvedResponse.Description = reference.Description ?? resolvedResponse.Description; return resolvedResponse; case ReferenceType.Parameter: var resolvedParameter = this.Components.Parameters[reference.Id]; - resolvedParameter.Description = reference.Description != null ? reference.Description : resolvedParameter.Description; + resolvedParameter.Description = reference.Description ?? resolvedParameter.Description; return resolvedParameter; case ReferenceType.Example: var resolvedExample = this.Components.Examples[reference.Id]; - resolvedExample.Summary = reference.Summary != null ? reference.Summary : resolvedExample.Summary; - resolvedExample.Description = reference.Description != null ? reference.Description : resolvedExample.Description; + resolvedExample.Summary = reference.Summary ?? resolvedExample.Summary; + resolvedExample.Description = reference.Description ?? resolvedExample.Description; return resolvedExample; case ReferenceType.RequestBody: var resolvedRequestBody = this.Components.RequestBodies[reference.Id]; - resolvedRequestBody.Description = reference.Description != null ? reference.Description : resolvedRequestBody.Description; + resolvedRequestBody.Description = reference.Description ?? resolvedRequestBody.Description; return resolvedRequestBody; - + case ReferenceType.Header: var resolvedHeader = this.Components.Headers[reference.Id]; - resolvedHeader.Description = reference.Description != null ? reference.Description : resolvedHeader.Description; + resolvedHeader.Description = reference.Description ?? resolvedHeader.Description; return resolvedHeader; - + case ReferenceType.SecurityScheme: var resolvedSecurityScheme = this.Components.SecuritySchemes[reference.Id]; - resolvedSecurityScheme.Description = reference.Description != null ? reference.Description : resolvedSecurityScheme.Description; + resolvedSecurityScheme.Description = reference.Description ?? resolvedSecurityScheme.Description; return resolvedSecurityScheme; - + case ReferenceType.Link: var resolvedLink = this.Components.Links[reference.Id]; - resolvedLink.Description = reference.Description != null ? reference.Description : resolvedLink.Description; + resolvedLink.Description = reference.Description ?? resolvedLink.Description; return resolvedLink; case ReferenceType.Callback: @@ -599,48 +602,23 @@ internal IOpenApiReferenceable ResolveReference(OpenApiReference reference, bool throw new OpenApiException(string.Format(Properties.SRResource.InvalidReferenceId, reference.Id)); } } + + public JsonSchema FindSubschema(Json.Pointer.JsonPointer pointer, EvaluationOptions options) + { + throw new NotImplementedException(); + } } internal class FindSchemaReferences : OpenApiVisitorBase { - private Dictionary Schemas; + private Dictionary Schemas; - public static void ResolveSchemas(OpenApiComponents components, Dictionary schemas ) + public static void ResolveSchemas(OpenApiComponents components, Dictionary schemas) { var visitor = new FindSchemaReferences(); visitor.Schemas = schemas; var walker = new OpenApiWalker(visitor); walker.Walk(components); } - - public override void Visit(IOpenApiReferenceable referenceable) - { - switch (referenceable) - { - case OpenApiSchema schema: - if (!Schemas.ContainsKey(schema.Reference.Id)) - { - Schemas.Add(schema.Reference.Id, schema); - } - break; - - default: - break; - } - base.Visit(referenceable); - } - - public override void Visit(OpenApiSchema schema) - { - // This is needed to handle schemas used in Responses in components - if (schema.Reference != null) - { - if (!Schemas.ContainsKey(schema.Reference.Id)) - { - 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 0dbe37aaa..be0a7a87c 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiEncoding.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiEncoding.cs @@ -56,7 +56,7 @@ public class OpenApiEncoding : IOpenApiSerializable, IOpenApiExtensible /// /// Parameter-less constructor /// - public OpenApiEncoding() {} + public OpenApiEncoding() { } /// /// Initializes a copy of an object @@ -70,7 +70,7 @@ public OpenApiEncoding(OpenApiEncoding encoding) AllowReserved = encoding?.AllowReserved ?? AllowReserved; Extensions = encoding?.Extensions != null ? new Dictionary(encoding.Extensions) : null; } - + /// /// Serialize to Open Api v3.1 /// @@ -79,7 +79,7 @@ public void SerializeAsV31(IOpenApiWriter writer) { SerializeInternal(writer, OpenApiSpecVersion.OpenApi3_1, (writer, element) => element.SerializeAsV31(writer)); } - + /// /// Serialize to Open Api v3.0 /// @@ -88,11 +88,11 @@ 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, + private void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion version, Action callback) { writer = writer ?? throw Error.ArgumentNull(nameof(writer)); diff --git a/src/Microsoft.OpenApi/Models/OpenApiExample.cs b/src/Microsoft.OpenApi/Models/OpenApiExample.cs index 06870b2ae..41938cfe8 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiExample.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiExample.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; @@ -59,7 +59,7 @@ public class OpenApiExample : IOpenApiSerializable, IOpenApiReferenceable, IOpen /// /// Parameter-less constructor /// - public OpenApiExample() {} + public OpenApiExample() { } /// /// Initializes a copy of object @@ -81,7 +81,7 @@ public OpenApiExample(OpenApiExample example) /// public virtual void SerializeAsV31(IOpenApiWriter writer) { - SerializeInternal(writer, (writer, element) => element.SerializeAsV31(writer), + SerializeInternal(writer, (writer, element) => element.SerializeAsV31(writer), (writer, element) => element.SerializeAsV31WithoutReference(writer)); } @@ -91,7 +91,7 @@ public virtual void SerializeAsV31(IOpenApiWriter writer) /// public virtual void SerializeAsV3(IOpenApiWriter writer) { - SerializeInternal(writer, (writer, element) => element.SerializeAsV3(writer), + SerializeInternal(writer, (writer, element) => element.SerializeAsV3(writer), (writer, element) => element.SerializeAsV3WithoutReference(writer)); } diff --git a/src/Microsoft.OpenApi/Models/OpenApiExtensibleDictionary.cs b/src/Microsoft.OpenApi/Models/OpenApiExtensibleDictionary.cs index 3539401e0..f9b3f5373 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiExtensibleDictionary.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiExtensibleDictionary.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Text.Json.Nodes; using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Writers; @@ -30,10 +29,10 @@ 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; - } + } /// /// This object MAY be extended with Specification Extensions. @@ -58,11 +57,11 @@ 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, + private void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion version, Action callback) { writer = writer ?? throw Error.ArgumentNull(nameof(writer)); diff --git a/src/Microsoft.OpenApi/Models/OpenApiExternalDocs.cs b/src/Microsoft.OpenApi/Models/OpenApiExternalDocs.cs index 66a248b31..b2541c7e9 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiExternalDocs.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiExternalDocs.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Text.Json.Nodes; using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Writers; @@ -43,7 +42,7 @@ public OpenApiExternalDocs(OpenApiExternalDocs externalDocs) Url = externalDocs?.Url != null ? new Uri(externalDocs.Url.OriginalString, UriKind.RelativeOrAbsolute) : null; Extensions = externalDocs?.Extensions != null ? new Dictionary(externalDocs.Extensions) : null; } - + /// /// Serialize to Open Api v3.1. /// @@ -51,7 +50,7 @@ public void SerializeAsV31(IOpenApiWriter writer) { WriteInternal(writer, OpenApiSpecVersion.OpenApi3_1); } - + /// /// Serialize to Open Api v3.0. /// @@ -59,7 +58,7 @@ public void SerializeAsV3(IOpenApiWriter writer) { WriteInternal(writer, OpenApiSpecVersion.OpenApi3_0); } - + /// /// Serialize to Open Api v2.0. /// diff --git a/src/Microsoft.OpenApi/Models/OpenApiHeader.cs b/src/Microsoft.OpenApi/Models/OpenApiHeader.cs index bd4a9ee44..06061a309 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiHeader.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiHeader.cs @@ -3,6 +3,8 @@ using System; using System.Collections.Generic; +using System.Text.Json; +using Json.Schema; using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Extensions; using Microsoft.OpenApi.Helpers; @@ -17,6 +19,8 @@ namespace Microsoft.OpenApi.Models /// public class OpenApiHeader : IOpenApiSerializable, IOpenApiReferenceable, IOpenApiExtensible, IEffective { + private JsonSchema _schema; + /// /// Indicates if object is populated with data or is just a reference to the data /// @@ -64,9 +68,13 @@ public class OpenApiHeader : IOpenApiSerializable, IOpenApiReferenceable, IOpenA 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 virtual OpenApiSchema Schema { get; set; } + public virtual JsonSchema Schema + { + get => _schema; + set => _schema = value; + } /// /// Example of the media type. @@ -91,7 +99,7 @@ public class OpenApiHeader : IOpenApiSerializable, IOpenApiReferenceable, IOpenA /// /// Parameter-less constructor /// - public OpenApiHeader() {} + public OpenApiHeader() { } /// /// Initializes a copy of an object @@ -107,30 +115,30 @@ public OpenApiHeader(OpenApiHeader header) Style = header?.Style ?? Style; Explode = header?.Explode ?? Explode; AllowReserved = header?.AllowReserved ?? AllowReserved; - Schema = header?.Schema != null ? new(header?.Schema) : null; + _schema = JsonNodeCloneHelper.CloneJsonSchema(header?.Schema); Example = JsonNodeCloneHelper.Clone(header?.Example); 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.1 /// public virtual void SerializeAsV31(IOpenApiWriter writer) { - SerializeInternal(writer, (writer, element) => element.SerializeAsV31(writer), + SerializeInternal(writer, (writer, element) => element.SerializeAsV31(writer), (writer, element) => element.SerializeAsV31WithoutReference(writer)); } - + /// /// Serialize to Open Api v3.0 /// public virtual void SerializeAsV3(IOpenApiWriter writer) { - SerializeInternal(writer, (writer, element) => element.SerializeAsV3(writer), + SerializeInternal(writer, (writer, element) => element.SerializeAsV3(writer), (writer, element) => element.SerializeAsV3WithoutReference(writer)); - } + } private void SerializeInternal(IOpenApiWriter writer, Action callback, Action action) @@ -151,7 +159,7 @@ private void SerializeInternal(IOpenApiWriter writer, Action public virtual void SerializeAsV31WithoutReference(IOpenApiWriter writer) { - SerializeInternalWithoutReference(writer, OpenApiSpecVersion.OpenApi3_1, + SerializeInternalWithoutReference(writer, OpenApiSpecVersion.OpenApi3_1, (writer, element) => element.SerializeAsV31(writer)); } @@ -186,7 +194,7 @@ public virtual void SerializeAsV31WithoutReference(IOpenApiWriter writer) /// public virtual void SerializeAsV3WithoutReference(IOpenApiWriter writer) { - SerializeInternalWithoutReference(writer, OpenApiSpecVersion.OpenApi3_0, + SerializeInternalWithoutReference(writer, OpenApiSpecVersion.OpenApi3_0, (writer, element) => element.SerializeAsV3(writer)); } @@ -217,7 +225,7 @@ internal virtual void SerializeInternalWithoutReference(IOpenApiWriter writer, O writer.WriteProperty(OpenApiConstants.AllowReserved, AllowReserved, false); // schema - writer.WriteOptionalObject(OpenApiConstants.Schema, Schema, callback); + writer.WriteOptionalObject(OpenApiConstants.Schema, Schema, (w, s) => writer.WriteJsonSchema(s)); // example writer.WriteOptionalObject(OpenApiConstants.Example, Example, (w, s) => w.WriteAny(s)); @@ -287,7 +295,7 @@ public void SerializeAsV2WithoutReference(IOpenApiWriter writer) writer.WriteProperty(OpenApiConstants.AllowReserved, AllowReserved, false); // schema - Schema?.WriteAsItemsProperties(writer); + SchemaSerializerHelper.WriteAsItemsProperties(Schema, writer, Extensions); // 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 1ed93275e..362c0cd04 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiInfo.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiInfo.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Text.Json.Nodes; using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Writers; @@ -23,12 +22,12 @@ public class OpenApiInfo : IOpenApiSerializable, IOpenApiExtensible /// A short summary of the API. /// public string Summary { get; set; } - + /// /// A short description of the application. /// public string Description { get; set; } - + /// /// REQUIRED. The version of the OpenAPI document. /// @@ -57,7 +56,7 @@ public class OpenApiInfo : IOpenApiSerializable, IOpenApiExtensible /// /// Parameter-less constructor /// - public OpenApiInfo() {} + public OpenApiInfo() { } /// /// Initializes a copy of an object @@ -73,29 +72,29 @@ public OpenApiInfo(OpenApiInfo info) License = info?.License != null ? new(info?.License) : null; 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) - { + { SerializeInternal(writer, OpenApiSpecVersion.OpenApi3_0, (writer, element) => element.SerializeAsV3(writer)); - + writer.WriteEndObject(); } - + /// /// Serialize to Open Api v3.0 /// @@ -106,7 +105,7 @@ private void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion version // title writer.WriteProperty(OpenApiConstants.Title, Title); - + // description writer.WriteProperty(OpenApiConstants.Description, Description); diff --git a/src/Microsoft.OpenApi/Models/OpenApiLicense.cs b/src/Microsoft.OpenApi/Models/OpenApiLicense.cs index 48fb2518a..75d9e81d9 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiLicense.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiLicense.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Text.Json.Nodes; using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Writers; @@ -49,7 +48,7 @@ public OpenApiLicense(OpenApiLicense license) 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 /// @@ -64,8 +63,8 @@ public void SerializeAsV31(IOpenApiWriter writer) /// Serialize to Open Api v3.0 /// public void SerializeAsV3(IOpenApiWriter writer) - { - WriteInternal(writer, OpenApiSpecVersion.OpenApi3_0); + { + WriteInternal(writer, OpenApiSpecVersion.OpenApi3_0); writer.WriteEndObject(); } @@ -82,7 +81,7 @@ private void WriteInternal(IOpenApiWriter writer, OpenApiSpecVersion specVersion { writer = writer ?? throw Error.ArgumentNull(nameof(writer)); writer.WriteStartObject(); - + // name writer.WriteProperty(OpenApiConstants.Name, Name); diff --git a/src/Microsoft.OpenApi/Models/OpenApiLink.cs b/src/Microsoft.OpenApi/Models/OpenApiLink.cs index e9435e3ee..badaff694 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiLink.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiLink.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; @@ -64,7 +64,7 @@ public class OpenApiLink : IOpenApiSerializable, IOpenApiReferenceable, IOpenApi /// /// Parameterless constructor /// - public OpenApiLink() {} + public OpenApiLink() { } /// /// Initializes a copy of an object @@ -90,7 +90,7 @@ public virtual void SerializeAsV31(IOpenApiWriter writer) SerializeInternal(writer, (writer, element) => element.SerializeAsV31(writer), (writer, element) => element.SerializeAsV31WithoutReference(writer)); } - + /// /// Serialize to Open Api v3.0 /// diff --git a/src/Microsoft.OpenApi/Models/OpenApiMediaType.cs b/src/Microsoft.OpenApi/Models/OpenApiMediaType.cs index 0c52d6af8..2d7172e88 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiMediaType.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiMediaType.cs @@ -3,6 +3,8 @@ using System; using System.Collections.Generic; +using System.Text.Json; +using Json.Schema; using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Helpers; using Microsoft.OpenApi.Interfaces; @@ -15,10 +17,16 @@ namespace Microsoft.OpenApi.Models /// public class OpenApiMediaType : IOpenApiSerializable, IOpenApiExtensible { + private JsonSchema _schema; + /// /// The schema defining the type used for the request body. /// - public OpenApiSchema Schema { get; set; } + public virtual JsonSchema Schema + { + get => _schema; + set => _schema = value; + } /// /// Example of the media type. @@ -55,7 +63,7 @@ public OpenApiMediaType() { } /// public OpenApiMediaType(OpenApiMediaType mediaType) { - Schema = mediaType?.Schema != null ? new(mediaType?.Schema) : null; + _schema = JsonNodeCloneHelper.CloneJsonSchema(mediaType?.Schema); Example = JsonNodeCloneHelper.Clone(mediaType?.Example); Examples = mediaType?.Examples != null ? new Dictionary(mediaType.Examples) : null; Encoding = mediaType?.Encoding != null ? new Dictionary(mediaType.Encoding) : null; @@ -76,20 +84,20 @@ public void SerializeAsV31(IOpenApiWriter writer) public void SerializeAsV3(IOpenApiWriter writer) { SerializeInternal(writer, OpenApiSpecVersion.OpenApi3_0, (w, element) => element.SerializeAsV3(w)); - } - + } + /// /// Serialize to Open Api v3.0. /// - private void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion version, + private void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion version, Action callback) { writer = writer ?? throw Error.ArgumentNull(nameof(writer)); - + writer.WriteStartObject(); - + // schema - writer.WriteOptionalObject(OpenApiConstants.Schema, Schema, callback); + writer.WriteOptionalObject(OpenApiConstants.Schema, Schema, (w, s) => writer.WriteJsonSchema(s)); // example writer.WriteOptionalObject(OpenApiConstants.Example, Example, (w, e) => w.WriteAny(e)); @@ -102,7 +110,7 @@ private void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion version // extensions writer.WriteExtensions(Extensions, version); - + writer.WriteEndObject(); } diff --git a/src/Microsoft.OpenApi/Models/OpenApiOAuthFlow.cs b/src/Microsoft.OpenApi/Models/OpenApiOAuthFlow.cs index 2ba2272aa..c1e3fc47f 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiOAuthFlow.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiOAuthFlow.cs @@ -64,7 +64,7 @@ public void SerializeAsV31(IOpenApiWriter writer) { SerializeInternal(writer, OpenApiSpecVersion.OpenApi3_1); } - + /// /// Serialize to Open Api v3.0 /// diff --git a/src/Microsoft.OpenApi/Models/OpenApiOAuthFlows.cs b/src/Microsoft.OpenApi/Models/OpenApiOAuthFlows.cs index 6266b5b2d..aff61e99a 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiOAuthFlows.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiOAuthFlows.cs @@ -41,7 +41,7 @@ public class OpenApiOAuthFlows : IOpenApiSerializable, IOpenApiExtensible /// /// Parameterless constructor /// - public OpenApiOAuthFlows() {} + public OpenApiOAuthFlows() { } /// /// Initializes a copy of an object @@ -63,7 +63,7 @@ public void SerializeAsV31(IOpenApiWriter writer) { SerializeInternal(writer, OpenApiSpecVersion.OpenApi3_1, (writer, element) => element.SerializeAsV31(writer)); } - + /// /// Serialize to Open Api v3.0 /// @@ -75,7 +75,7 @@ public void SerializeAsV3(IOpenApiWriter writer) /// /// Serialize /// - private void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion version, + private void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion version, Action callback) { writer = writer ?? throw Error.ArgumentNull(nameof(writer)); diff --git a/src/Microsoft.OpenApi/Models/OpenApiOperation.cs b/src/Microsoft.OpenApi/Models/OpenApiOperation.cs index 216ff30e2..38d58f5da 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiOperation.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiOperation.cs @@ -108,7 +108,7 @@ public class OpenApiOperation : IOpenApiSerializable, IOpenApiExtensible /// /// Parameterless constructor /// - public OpenApiOperation() {} + public OpenApiOperation() { } /// /// Initializes a copy of an object @@ -137,7 +137,7 @@ public void SerializeAsV31(IOpenApiWriter writer) { SerializeInternal(writer, OpenApiSpecVersion.OpenApi3_1, (writer, element) => element.SerializeAsV31(writer)); } - + /// /// Serialize to Open Api v3.0. /// @@ -195,8 +195,8 @@ private void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion version writer.WriteOptionalCollection(OpenApiConstants.Servers, Servers, callback); // specification extensions - writer.WriteExtensions(Extensions,version); - + writer.WriteExtensions(Extensions, version); + writer.WriteEndObject(); } diff --git a/src/Microsoft.OpenApi/Models/OpenApiParameter.cs b/src/Microsoft.OpenApi/Models/OpenApiParameter.cs index 4a9923cef..e68122e54 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiParameter.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiParameter.cs @@ -3,6 +3,8 @@ using System; using System.Collections.Generic; +using System.Text.Json; +using Json.Schema; using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Extensions; using Microsoft.OpenApi.Helpers; @@ -18,6 +20,7 @@ public class OpenApiParameter : IOpenApiSerializable, IOpenApiReferenceable, IEf { private bool? _explode; private ParameterStyle? _style; + private JsonSchema _schema; /// /// Indicates if object is populated with data or is just a reference to the data @@ -105,7 +108,11 @@ public virtual bool Explode /// /// The schema defining the type used for the parameter. /// - public virtual OpenApiSchema Schema { get; set; } + public virtual JsonSchema Schema + { + get => _schema; + set => _schema = value; + } /// /// Examples of the media type. Each example SHOULD contain a value @@ -145,7 +152,7 @@ public virtual bool Explode /// /// A parameterless constructor /// - public OpenApiParameter() {} + public OpenApiParameter() { } /// /// Initializes a clone instance of object @@ -161,7 +168,7 @@ 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 = JsonNodeCloneHelper.CloneJsonSchema(parameter?.Schema); Examples = parameter?.Examples != null ? new Dictionary(parameter.Examples) : null; Example = JsonNodeCloneHelper.Clone(parameter?.Example); Content = parameter?.Content != null ? new Dictionary(parameter.Content) : null; @@ -169,26 +176,26 @@ public OpenApiParameter(OpenApiParameter parameter) AllowEmptyValue = parameter?.AllowEmptyValue ?? AllowEmptyValue; Deprecated = parameter?.Deprecated ?? Deprecated; } - + /// /// Serialize to Open Api v3.1 /// public virtual void SerializeAsV31(IOpenApiWriter writer) { - SerializeInternal(writer, (writer, element) => element.SerializeAsV31(writer), + SerializeInternal(writer, (writer, element) => element.SerializeAsV31(writer), (writer, element) => element.SerializeAsV31WithoutReference(writer)); } - + /// /// Serialize to Open Api v3.0 /// public virtual void SerializeAsV3(IOpenApiWriter writer) { - SerializeInternal(writer, (writer, element) => element.SerializeAsV3(writer), + SerializeInternal(writer, (writer, element) => element.SerializeAsV3(writer), (writer, element) => element.SerializeAsV3WithoutReference(writer)); - } + } - private void SerializeInternal(IOpenApiWriter writer, Action callback, + private void SerializeInternal(IOpenApiWriter writer, Action callback, Action action) { writer = writer ?? throw Error.ArgumentNull(nameof(writer)); @@ -201,7 +208,7 @@ private void SerializeInternal(IOpenApiWriter writer, Action /// Serialize to OpenAPI V3 document without using reference. /// public virtual void SerializeAsV31WithoutReference(IOpenApiWriter writer) { - SerializeInternalWithoutReference(writer, OpenApiSpecVersion.OpenApi3_1, + 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, + SerializeInternalWithoutReference(writer, OpenApiSpecVersion.OpenApi3_0, (writer, element) => element.SerializeAsV3(writer)); } @@ -267,7 +274,7 @@ internal virtual void SerializeInternalWithoutReference(IOpenApiWriter writer, O // allowEmptyValue writer.WriteProperty(OpenApiConstants.AllowEmptyValue, AllowEmptyValue, false); - + // style if (_style.HasValue) { @@ -281,7 +288,11 @@ internal virtual void SerializeInternalWithoutReference(IOpenApiWriter writer, O writer.WriteProperty(OpenApiConstants.AllowReserved, AllowReserved, false); // schema - writer.WriteOptionalObject(OpenApiConstants.Schema, Schema, callback); + if (Schema != null) + { + writer.WritePropertyName(OpenApiConstants.Schema); + writer.WriteJsonSchema(Schema); + } // example writer.WriteOptionalObject(OpenApiConstants.Example, Example, (w, s) => w.WriteAny(s)); @@ -312,7 +323,7 @@ public void SerializeAsV2(IOpenApiWriter writer) { Reference.SerializeAsV2(writer); return; - } + } else { target = this.GetEffective(Reference.HostDocument); @@ -360,12 +371,11 @@ public void SerializeAsV2WithoutReference(IOpenApiWriter writer) // schema if (this is OpenApiBodyParameter) { - writer.WriteOptionalObject(OpenApiConstants.Schema, Schema, (w, s) => s.SerializeAsV2(w)); + writer.WriteOptionalObject(OpenApiConstants.Schema, Schema, (w, s) => writer.WriteJsonSchema(s)); } // 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 || Schema?.Type == "object") + else if (Schema?.GetJsonType() == SchemaValueType.Object) { writer.WriteProperty(OpenApiConstants.Type, "string"); } @@ -390,11 +400,11 @@ public void SerializeAsV2WithoutReference(IOpenApiWriter writer) // multipleOf if (Schema != null) { - Schema.WriteAsItemsProperties(writer); - - if (Schema.Extensions != null) + SchemaSerializerHelper.WriteAsItemsProperties(Schema, writer, Extensions); + var extensions = Schema.GetExtensions(); + 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. @@ -406,7 +416,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 && SchemaValueType.Array.Equals(Schema?.GetJsonType())) { if (this.Style == ParameterStyle.Form && this.Explode == true) { @@ -423,7 +433,6 @@ public void SerializeAsV2WithoutReference(IOpenApiWriter writer) } } - // extensions writer.WriteExtensions(extensionsClone, OpenApiSpecVersion.OpenApi2_0); @@ -440,10 +449,9 @@ public void SerializeAsV2WithoutReference(IOpenApiWriter writer) ParameterLocation.Cookie => (ParameterStyle?)ParameterStyle.Form, _ => (ParameterStyle?)ParameterStyle.Simple, }; - + return Style; } - } /// diff --git a/src/Microsoft.OpenApi/Models/OpenApiPathItem.cs b/src/Microsoft.OpenApi/Models/OpenApiPathItem.cs index bcc826ef4..d4bc61f3d 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiPathItem.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiPathItem.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Text.Json.Nodes; using Microsoft.OpenApi.Extensions; using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Writers; @@ -70,7 +69,7 @@ public void AddOperation(OperationType operationType, OpenApiOperation operation /// /// Parameterless constructor /// - public OpenApiPathItem() {} + public OpenApiPathItem() { } /// /// Initializes a clone of an object @@ -92,7 +91,7 @@ public OpenApiPathItem(OpenApiPathItem pathItem) /// public virtual void SerializeAsV31(IOpenApiWriter writer) { - SerializeInternal(writer, (writer, element) => element.SerializeAsV31(writer), + SerializeInternal(writer, (writer, element) => element.SerializeAsV31(writer), (writer, element) => element.SerializeAsV31WithoutReference(writer)); } @@ -101,10 +100,10 @@ public virtual void SerializeAsV31(IOpenApiWriter writer) /// public virtual void SerializeAsV3(IOpenApiWriter writer) { - SerializeInternal(writer, (writer, element) => element.SerializeAsV3(writer), + SerializeInternal(writer, (writer, element) => element.SerializeAsV3(writer), (writer, element) => element.SerializeAsV3WithoutReference(writer)); } - + /// /// Serialize to Open Api v3.0 /// @@ -120,7 +119,7 @@ private void SerializeInternal(IOpenApiWriter writer, Action /// Serialize inline PathItem in OpenAPI V31 /// @@ -225,7 +224,7 @@ public virtual void SerializeAsV31WithoutReference(IOpenApiWriter writer) public virtual void SerializeAsV3WithoutReference(IOpenApiWriter writer) { SerializeInternalWithoutReference(writer, OpenApiSpecVersion.OpenApi3_0, (writer, element) => element.SerializeAsV3(writer)); - + } internal virtual void SerializeInternalWithoutReference(IOpenApiWriter writer, OpenApiSpecVersion version, diff --git a/src/Microsoft.OpenApi/Models/OpenApiPaths.cs b/src/Microsoft.OpenApi/Models/OpenApiPaths.cs index 8aae74883..77b162007 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiPaths.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiPaths.cs @@ -11,12 +11,12 @@ public class OpenApiPaths : OpenApiExtensibleDictionary /// /// Parameterless constructor /// - public OpenApiPaths() {} + public OpenApiPaths() { } /// /// Initializes a copy of object /// /// The . - public OpenApiPaths(OpenApiPaths paths) : base(dictionary: paths) { } + public OpenApiPaths(OpenApiPaths paths) : base(dictionary: paths) { } } } diff --git a/src/Microsoft.OpenApi/Models/OpenApiReference.cs b/src/Microsoft.OpenApi/Models/OpenApiReference.cs index f589327c0..bb52702a1 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiReference.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiReference.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. -using System; using Microsoft.OpenApi.Extensions; using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Writers; @@ -134,7 +133,7 @@ public string ReferenceV2 /// /// Parameterless constructor /// - public OpenApiReference() {} + public OpenApiReference() { } /// /// Initializes a copy instance of the object @@ -157,7 +156,7 @@ 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); } @@ -165,7 +164,7 @@ public void SerializeAsV31(IOpenApiWriter writer) /// Serialize to Open Api v3.0. /// public void SerializeAsV3(IOpenApiWriter writer) - { + { SerializeInternal(writer); } @@ -194,7 +193,7 @@ private void SerializeInternal(IOpenApiWriter writer) // $ref writer.WriteProperty(OpenApiConstants.DollarRef, ReferenceV3); - + writer.WriteEndObject(); } @@ -235,8 +234,8 @@ private string GetExternalReferenceV3() { return ExternalResource + "#" + Id; } - - return ExternalResource + "#/components/" + Type.GetDisplayName() + "/"+ Id; + + return ExternalResource + "#/components/" + Type.GetDisplayName() + "/" + Id; } return ExternalResource; diff --git a/src/Microsoft.OpenApi/Models/OpenApiRequestBody.cs b/src/Microsoft.OpenApi/Models/OpenApiRequestBody.cs index 51c5b2465..283694408 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiRequestBody.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiRequestBody.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; using System.Collections.Generic; using System.Linq; -using System.Text.Json.Nodes; +using Json.Schema; using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Writers; @@ -74,17 +74,17 @@ public virtual void SerializeAsV31(IOpenApiWriter writer) SerializeInternal(writer, (writer, element) => element.SerializeAsV31(writer), (writer, element) => element.SerializeAsV31WithoutReference(writer)); } - + /// /// Serialize to Open Api v3.0 /// public virtual void SerializeAsV3(IOpenApiWriter writer) { - SerializeInternal(writer, (writer, element) => element.SerializeAsV3(writer), + SerializeInternal(writer, (writer, element) => element.SerializeAsV3(writer), (writer, element) => element.SerializeAsV3WithoutReference(writer)); - } + } - private void SerializeInternal(IOpenApiWriter writer, Action callback, + private void SerializeInternal(IOpenApiWriter writer, Action callback, Action action) { writer = writer ?? throw Error.ArgumentNull(nameof(writer)); @@ -137,7 +137,7 @@ public virtual void SerializeAsV31WithoutReference(IOpenApiWriter writer) /// public virtual void SerializeAsV3WithoutReference(IOpenApiWriter writer) { - SerializeInternalWithoutReference(writer, OpenApiSpecVersion.OpenApi3_0, + SerializeInternalWithoutReference(writer, OpenApiSpecVersion.OpenApi3_0, (writer, element) => element.SerializeAsV3(writer)); } @@ -185,7 +185,7 @@ internal OpenApiBodyParameter ConvertToBodyParameter() // V2 spec actually allows the body to have custom name. // To allow round-tripping we use an extension to hold the name Name = "body", - Schema = Content.Values.FirstOrDefault()?.Schema ?? new OpenApiSchema(), + Schema = Content.Values.FirstOrDefault()?.Schema ?? new JsonSchemaBuilder().Build(), Required = Required, Extensions = Extensions.ToDictionary(static k => k.Key, static v => v.Value) // Clone extensions so we can remove the x-bodyName extensions from the output V2 model. }; @@ -203,22 +203,23 @@ internal IEnumerable ConvertToFormDataParameters() if (Content == null || !Content.Any()) yield break; - foreach (var property in Content.First().Value.Schema.Properties) + foreach (var property in Content.First().Value.Schema.GetProperties()) { var paramSchema = property.Value; - if ("string".Equals(paramSchema.Type, StringComparison.OrdinalIgnoreCase) - && ("binary".Equals(paramSchema.Format, StringComparison.OrdinalIgnoreCase) - || "base64".Equals(paramSchema.Format, StringComparison.OrdinalIgnoreCase))) + if (paramSchema.GetType().Equals(SchemaValueType.String) + && ("binary".Equals(paramSchema.GetFormat().Key, StringComparison.OrdinalIgnoreCase) + || "base64".Equals(paramSchema.GetFormat().Key, StringComparison.OrdinalIgnoreCase))) { - paramSchema.Type = "file"; - paramSchema.Format = null; + // JsonSchema is immutable so these can't be set + //paramSchema.Type("file"); + //paramSchema.Format(null); } yield return new OpenApiFormDataParameter { - Description = property.Value.Description, + Description = property.Value.GetDescription(), Name = property.Key, Schema = property.Value, - Required = Content.First().Value.Schema.Required.Contains(property.Key) + Required = Content.First().Value.Schema.GetRequired()?.Contains(property.Key) ?? false }; } } diff --git a/src/Microsoft.OpenApi/Models/OpenApiResponse.cs b/src/Microsoft.OpenApi/Models/OpenApiResponse.cs index 32faca799..389bc8500 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiResponse.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiResponse.cs @@ -1,10 +1,13 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. using System; using System.Collections.Generic; using System.Linq; +using System.Text.Json; using System.Text.Json.Nodes; +using Json.More; +using Microsoft.OpenApi.Helpers; using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Writers; @@ -56,7 +59,7 @@ public class OpenApiResponse : IOpenApiSerializable, IOpenApiReferenceable, IOpe /// /// Parameterless constructor /// - public OpenApiResponse() {} + public OpenApiResponse() { } /// /// Initializes a copy of object @@ -77,20 +80,20 @@ public OpenApiResponse(OpenApiResponse response) /// public virtual void SerializeAsV31(IOpenApiWriter writer) { - SerializeInternal(writer, (writer, element) => element.SerializeAsV31(writer), + SerializeInternal(writer, (writer, element) => element.SerializeAsV31(writer), (writer, element) => element.SerializeAsV31WithoutReference(writer)); } - + /// /// Serialize to Open Api v3.0. /// public virtual void SerializeAsV3(IOpenApiWriter writer) { - SerializeInternal(writer, (writer, element) => element.SerializeAsV3(writer), + SerializeInternal(writer, (writer, element) => element.SerializeAsV3(writer), (writer, element) => element.SerializeAsV3WithoutReference(writer)); } - private void SerializeInternal(IOpenApiWriter writer, Action callback, + private void SerializeInternal(IOpenApiWriter writer, Action callback, Action action) { writer = writer ?? throw Error.ArgumentNull(nameof(writer)); @@ -128,13 +131,13 @@ public OpenApiResponse GetEffective(OpenApiDocument doc) return this; } } - + /// /// Serialize to OpenAPI V3 document without using reference. /// public virtual void SerializeAsV31WithoutReference(IOpenApiWriter writer) { - SerializeInternalWithoutReference(writer, OpenApiSpecVersion.OpenApi3_1, + SerializeInternalWithoutReference(writer, OpenApiSpecVersion.OpenApi3_1, (writer, element) => element.SerializeAsV31(writer)); } @@ -143,7 +146,7 @@ public virtual void SerializeAsV31WithoutReference(IOpenApiWriter writer) /// public virtual void SerializeAsV3WithoutReference(IOpenApiWriter writer) { - SerializeInternalWithoutReference(writer, OpenApiSpecVersion.OpenApi3_0, + SerializeInternalWithoutReference(writer, OpenApiSpecVersion.OpenApi3_0, (writer, element) => element.SerializeAsV3(writer)); } @@ -212,10 +215,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) => writer.WriteJsonSchema(s)); // examples if (Content.Values.Any(m => m.Example != null)) diff --git a/src/Microsoft.OpenApi/Models/OpenApiResponses.cs b/src/Microsoft.OpenApi/Models/OpenApiResponses.cs index aa7a8c984..0d2876778 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 deleted file mode 100644 index 8793e8658..000000000 --- a/src/Microsoft.OpenApi/Models/OpenApiSchema.cs +++ /dev/null @@ -1,816 +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.Any; -using Microsoft.OpenApi.Helpers; -using Microsoft.OpenApi.Interfaces; -using Microsoft.OpenApi.Writers; - -namespace Microsoft.OpenApi.Models -{ - /// - /// Schema Object. - /// - public class OpenApiSchema : IOpenApiSerializable, IOpenApiReferenceable, IEffective, IOpenApiExtensible - { - /// - /// Follow JSON Schema definition. Short text providing information about the data. - /// - public string Title { 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; } - - /// - /// 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; } - - /// - /// 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; } - - /// - /// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00 - /// - public decimal? Maximum { get; set; } - - /// - /// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00 - /// - public bool? ExclusiveMaximum { get; set; } - - /// - /// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00 - /// - public decimal? Minimum { get; set; } - - /// - /// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00 - /// - public bool? ExclusiveMinimum { get; set; } - - /// - /// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00 - /// - public int? MaxLength { get; set; } - - /// - /// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00 - /// - public 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; } - - /// - /// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00 - /// - public decimal? MultipleOf { get; set; } - - /// - /// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00 - /// The default value represents what would be assumed by the consumer of the input as the value of the schema if one is not provided. - /// 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 OpenApiAny Default { get; set; } - - /// - /// Relevant only for Schema "properties" definitions. Declares the property as "read only". - /// This means that it MAY be sent as part of a response but SHOULD NOT be sent as part of the request. - /// If the property is marked as readOnly being true and is in the required list, - /// the required will take effect on the response only. - /// A property MUST NOT be marked as both readOnly and writeOnly being true. - /// Default value is false. - /// - public bool ReadOnly { get; set; } - - /// - /// Relevant only for Schema "properties" definitions. Declares the property as "write only". - /// Therefore, it MAY be sent as part of a request but SHOULD NOT be sent as part of the response. - /// If the property is marked as writeOnly being true and is in the required list, - /// the required will take effect on the request only. - /// A property MUST NOT be marked as both readOnly and writeOnly being true. - /// Default value is false. - /// - public 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(); - - /// - /// 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(); - - /// - /// 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(); - - /// - /// 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; } - - /// - /// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00 - /// - public 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; } - - /// - /// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00 - /// - public int? MaxItems { get; set; } - - /// - /// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00 - /// - public int? MinItems { get; set; } - - /// - /// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00 - /// - public 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(); - - /// - /// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00 - /// - public int? MaxProperties { get; set; } - - /// - /// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00 - /// - public 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; - - /// - /// 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; } - - - /// - /// 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; } - - /// - /// 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 OpenApiAny Example { get; set; } - - /// - /// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00 - /// - public IList Enum { get; set; } = new List(); - - /// - /// Allows sending a null value for the defined schema. Default value is false. - /// - public bool Nullable { get; set; } - - /// - /// Additional external documentation for this schema. - /// - public 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; } - - /// - /// 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; } - - /// - /// This object MAY be extended with Specification Extensions. - /// - public 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; } - - /// - /// Reference object. - /// - public OpenApiReference Reference { get; set; } - - /// - /// Parameterless constructor - /// - public OpenApiSchema() {} - - /// - /// Initializes a copy of object - /// - public OpenApiSchema(OpenApiSchema schema) - { - Title = schema?.Title ?? Title; - Type = schema?.Type ?? Type; - Format = schema?.Format ?? Format; - Description = schema?.Description ?? Description; - Maximum = schema?.Maximum ?? Maximum; - ExclusiveMaximum = schema?.ExclusiveMaximum ?? ExclusiveMaximum; - Minimum = schema?.Minimum ?? Minimum; - ExclusiveMinimum = schema?.ExclusiveMinimum ?? ExclusiveMinimum; - MaxLength = schema?.MaxLength ?? MaxLength; - MinLength = schema?.MinLength ?? MinLength; - Pattern = schema?.Pattern ?? Pattern; - MultipleOf = schema?.MultipleOf ?? MultipleOf; - Default = JsonNodeCloneHelper.Clone(schema?.Default); - ReadOnly = schema?.ReadOnly ?? ReadOnly; - WriteOnly = schema?.WriteOnly ?? WriteOnly; - AllOf = schema?.AllOf != null ? new List(schema.AllOf) : null; - OneOf = schema?.OneOf != null ? new List(schema.OneOf) : null; - AnyOf = schema?.AnyOf != null ? new List(schema.AnyOf) : null; - Not = schema?.Not != null ? new(schema?.Not) : null; - Required = schema?.Required != null ? new HashSet(schema.Required) : null; - Items = schema?.Items != null ? new(schema?.Items) : null; - MaxItems = schema?.MaxItems ?? MaxItems; - MinItems = schema?.MinItems ?? MinItems; - UniqueItems = schema?.UniqueItems ?? UniqueItems; - Properties = schema?.Properties != null ? new Dictionary(schema.Properties) : 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 = JsonNodeCloneHelper.Clone(schema?.Example); - 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; - Xml = schema?.Xml != null ? new(schema?.Xml) : null; - Extensions = schema?.Xml != null ? new Dictionary(schema.Extensions) : null; - UnresolvedReference = schema?.UnresolvedReference ?? UnresolvedReference; - Reference = schema?.Reference != null ? new(schema?.Reference) : null; - } - - /// - /// Serialize to Open Api v3.1 - /// - public void SerializeAsV31(IOpenApiWriter writer) - { - SerializeInternal(writer, (writer, element) => element.SerializeAsV31(writer), - (writer, element) => element.SerializeAsV31WithoutReference(writer)); - } - - /// - /// Serialize to Open Api v3.0 - /// - public void SerializeAsV3(IOpenApiWriter writer) - { - SerializeInternal(writer, (writer, element) => element.SerializeAsV3(writer), - (writer, element) => element.SerializeAsV3WithoutReference(writer)); - } - - /// - /// Serialize to Open Api v3.0 - /// - private void SerializeInternal(IOpenApiWriter writer, Action callback, - Action action) - { - writer = writer ?? throw Error.ArgumentNull(nameof(writer)); - - var settings = writer.GetSettings(); - var target = this; - - if (Reference != null) - { - if (!settings.ShouldInlineReference(Reference)) - { - callback(writer, Reference); - 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); - callback(writer, Reference); - return; - } - } - action(writer, target); - - if (Reference != null) - { - settings.LoopDetector.PopLoop(); - } - } - - /// - /// Serialize to OpenAPI V31 document without using reference. - /// - public void SerializeAsV31WithoutReference(IOpenApiWriter writer) - { - SerializeInternalWithoutReference(writer, OpenApiSpecVersion.OpenApi3_1, (writer, element) => element.SerializeAsV31(writer)); - } - - /// - /// Serialize to OpenAPI V3 document without using reference. - /// - public void SerializeAsV3WithoutReference(IOpenApiWriter writer) - { - SerializeInternalWithoutReference(writer, OpenApiSpecVersion.OpenApi3_0, (writer, element) => element.SerializeAsV3(writer)); - } - - private void SerializeInternalWithoutReference(IOpenApiWriter writer, OpenApiSpecVersion version, - Action callback) - { - writer.WriteStartObject(); - - // title - writer.WriteProperty(OpenApiConstants.Title, Title); - - // multipleOf - writer.WriteProperty(OpenApiConstants.MultipleOf, MultipleOf); - - // maximum - writer.WriteProperty(OpenApiConstants.Maximum, Maximum); - - // exclusiveMaximum - writer.WriteProperty(OpenApiConstants.ExclusiveMaximum, ExclusiveMaximum); - - // minimum - writer.WriteProperty(OpenApiConstants.Minimum, Minimum); - - // exclusiveMinimum - writer.WriteProperty(OpenApiConstants.ExclusiveMinimum, ExclusiveMinimum); - - // maxLength - writer.WriteProperty(OpenApiConstants.MaxLength, MaxLength); - - // minLength - writer.WriteProperty(OpenApiConstants.MinLength, MinLength); - - // pattern - writer.WriteProperty(OpenApiConstants.Pattern, Pattern); - - // maxItems - writer.WriteProperty(OpenApiConstants.MaxItems, MaxItems); - - // minItems - writer.WriteProperty(OpenApiConstants.MinItems, MinItems); - - // uniqueItems - writer.WriteProperty(OpenApiConstants.UniqueItems, UniqueItems); - - // maxProperties - writer.WriteProperty(OpenApiConstants.MaxProperties, MaxProperties); - - // minProperties - writer.WriteProperty(OpenApiConstants.MinProperties, MinProperties); - - // required - writer.WriteOptionalCollection(OpenApiConstants.Required, Required, (w, s) => w.WriteValue(s)); - - // enum - writer.WriteOptionalCollection(OpenApiConstants.Enum, Enum, (nodeWriter, s) => nodeWriter.WriteAny(s)); - - // type - writer.WriteProperty(OpenApiConstants.Type, Type); - - // allOf - writer.WriteOptionalCollection(OpenApiConstants.AllOf, AllOf, callback); - - // anyOf - writer.WriteOptionalCollection(OpenApiConstants.AnyOf, AnyOf, callback); - - // oneOf - writer.WriteOptionalCollection(OpenApiConstants.OneOf, OneOf, callback); - - // not - writer.WriteOptionalObject(OpenApiConstants.Not, Not, callback); - - // items - writer.WriteOptionalObject(OpenApiConstants.Items, Items, callback); - - // properties - writer.WriteOptionalMap(OpenApiConstants.Properties, Properties, callback); - - // additionalProperties - if (AdditionalPropertiesAllowed) - { - writer.WriteOptionalObject( - OpenApiConstants.AdditionalProperties, - AdditionalProperties, - callback); - } - else - { - writer.WriteProperty(OpenApiConstants.AdditionalProperties, AdditionalPropertiesAllowed); - } - - // description - writer.WriteProperty(OpenApiConstants.Description, Description); - - // format - writer.WriteProperty(OpenApiConstants.Format, Format); - - // default - writer.WriteOptionalObject(OpenApiConstants.Default, Default, (w, d) => w.WriteAny(d)); - - // nullable - writer.WriteProperty(OpenApiConstants.Nullable, Nullable, false); - - // discriminator - writer.WriteOptionalObject(OpenApiConstants.Discriminator, Discriminator, callback); - - // readOnly - writer.WriteProperty(OpenApiConstants.ReadOnly, ReadOnly, false); - - // writeOnly - writer.WriteProperty(OpenApiConstants.WriteOnly, WriteOnly, false); - - // xml - writer.WriteOptionalObject(OpenApiConstants.Xml, Xml, (w, s) => s.SerializeAsV2(w)); - - // externalDocs - writer.WriteOptionalObject(OpenApiConstants.ExternalDocs, ExternalDocs, callback); - - // example - writer.WriteOptionalObject(OpenApiConstants.Example, Example, (w, e) => w.WriteAny(e)); - - // deprecated - writer.WriteProperty(OpenApiConstants.Deprecated, Deprecated, false); - - // extensions - 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) - { - writer = writer ?? throw Error.ArgumentNull(nameof(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(); - } - } - - /// - /// 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) - { - writer.WriteStartObject(); - WriteAsSchemaProperties(writer, parentRequiredProperties, propertyName); - writer.WriteEndObject(); - } - - internal void WriteAsItemsProperties(IOpenApiWriter writer) - { - if (writer == null) - { - throw Error.ArgumentNull(nameof(writer)); - } - - // type - writer.WriteProperty(OpenApiConstants.Type, Type); - - // format - if (string.IsNullOrEmpty(Format)) - { - Format = AllOf?.FirstOrDefault(static x => !string.IsNullOrEmpty(x.Format))?.Format ?? - AnyOf?.FirstOrDefault(static x => !string.IsNullOrEmpty(x.Format))?.Format ?? - OneOf?.FirstOrDefault(static x => !string.IsNullOrEmpty(x.Format))?.Format; - } - - writer.WriteProperty(OpenApiConstants.Format, Format); - - // items - writer.WriteOptionalObject(OpenApiConstants.Items, Items, (w, s) => s.SerializeAsV2(w)); - - // collectionFormat - // We need information from style in parameter to populate this. - // The best effort we can make is to pull this information from the first parameter - // that leverages this schema. However, that in itself may not be as simple - // as the schema directly under parameter might be referencing one in the Components, - // so we will need to do a full scan of the object before we can write the value for - // this property. This is not supported yet, so we will skip this property at the moment. - - // default - writer.WriteOptionalObject(OpenApiConstants.Default, Default, (w, d) => w.WriteAny(d)); - - // maximum - writer.WriteProperty(OpenApiConstants.Maximum, Maximum); - - // exclusiveMaximum - writer.WriteProperty(OpenApiConstants.ExclusiveMaximum, ExclusiveMaximum); - - // minimum - writer.WriteProperty(OpenApiConstants.Minimum, Minimum); - - // exclusiveMinimum - writer.WriteProperty(OpenApiConstants.ExclusiveMinimum, ExclusiveMinimum); - - // maxLength - writer.WriteProperty(OpenApiConstants.MaxLength, MaxLength); - - // minLength - writer.WriteProperty(OpenApiConstants.MinLength, MinLength); - - // pattern - writer.WriteProperty(OpenApiConstants.Pattern, Pattern); - - // maxItems - writer.WriteProperty(OpenApiConstants.MaxItems, MaxItems); - - // minItems - writer.WriteProperty(OpenApiConstants.MinItems, MinItems); - - // enum - writer.WriteOptionalCollection(OpenApiConstants.Enum, Enum, (w, s) => w.WriteAny(s)); - - // multipleOf - writer.WriteProperty(OpenApiConstants.MultipleOf, MultipleOf); - - // extensions - writer.WriteExtensions(Extensions, OpenApiSpecVersion.OpenApi2_0); - } - - internal void WriteAsSchemaProperties( - IOpenApiWriter writer, - ISet parentRequiredProperties, - string propertyName) - { - if (writer == null) - { - throw Error.ArgumentNull(nameof(writer)); - } - - // format - if (string.IsNullOrEmpty(Format)) - { - Format = AllOf?.FirstOrDefault(static x => !string.IsNullOrEmpty(x.Format))?.Format ?? - AnyOf?.FirstOrDefault(static x => !string.IsNullOrEmpty(x.Format))?.Format ?? - OneOf?.FirstOrDefault(static x => !string.IsNullOrEmpty(x.Format))?.Format; - } - - writer.WriteProperty(OpenApiConstants.Format, Format); - - // title - writer.WriteProperty(OpenApiConstants.Title, Title); - - // description - writer.WriteProperty(OpenApiConstants.Description, Description); - - // default - writer.WriteOptionalObject(OpenApiConstants.Default, Default, (w, d) => w.WriteAny(d)); - - // multipleOf - writer.WriteProperty(OpenApiConstants.MultipleOf, MultipleOf); - - // maximum - writer.WriteProperty(OpenApiConstants.Maximum, Maximum); - - // exclusiveMaximum - writer.WriteProperty(OpenApiConstants.ExclusiveMaximum, ExclusiveMaximum); - - // minimum - writer.WriteProperty(OpenApiConstants.Minimum, Minimum); - - // exclusiveMinimum - writer.WriteProperty(OpenApiConstants.ExclusiveMinimum, ExclusiveMinimum); - - // maxLength - writer.WriteProperty(OpenApiConstants.MaxLength, MaxLength); - - // minLength - writer.WriteProperty(OpenApiConstants.MinLength, MinLength); - - // pattern - writer.WriteProperty(OpenApiConstants.Pattern, Pattern); - - // maxItems - writer.WriteProperty(OpenApiConstants.MaxItems, MaxItems); - - // minItems - writer.WriteProperty(OpenApiConstants.MinItems, MinItems); - - // uniqueItems - writer.WriteProperty(OpenApiConstants.UniqueItems, UniqueItems); - - // maxProperties - writer.WriteProperty(OpenApiConstants.MaxProperties, MaxProperties); - - // minProperties - writer.WriteProperty(OpenApiConstants.MinProperties, MinProperties); - - // required - writer.WriteOptionalCollection(OpenApiConstants.Required, Required, (w, s) => w.WriteValue(s)); - - // 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)); - - // allOf - writer.WriteOptionalCollection(OpenApiConstants.AllOf, AllOf, (w, s) => s.SerializeAsV2(w)); - - // If there isn't already an allOf, and the schema contains a oneOf or anyOf write an allOf with the first - // schema in the list as an attempt to guess at a graceful downgrade situation. - if (AllOf == null || AllOf.Count == 0) - { - // anyOf (Not Supported in V2) - Write the first schema only as an allOf. - writer.WriteOptionalCollection(OpenApiConstants.AllOf, AnyOf?.Take(1), (w, s) => s.SerializeAsV2(w)); - - if (AnyOf == null || AnyOf.Count == 0) - { - // oneOf (Not Supported in V2) - Write the first schema only as an allOf. - writer.WriteOptionalCollection(OpenApiConstants.AllOf, OneOf?.Take(1), (w, s) => s.SerializeAsV2(w)); - } - } - - // properties - writer.WriteOptionalMap(OpenApiConstants.Properties, Properties, (w, key, s) => - s.SerializeAsV2(w, Required, key)); - - // additionalProperties - if (AdditionalPropertiesAllowed) - { - writer.WriteOptionalObject( - OpenApiConstants.AdditionalProperties, - AdditionalProperties, - (w, s) => s.SerializeAsV2(w)); - } - else - { - writer.WriteProperty(OpenApiConstants.AdditionalProperties, AdditionalPropertiesAllowed); - } - - // discriminator - writer.WriteProperty(OpenApiConstants.Discriminator, Discriminator?.PropertyName); - - // readOnly - // In V2 schema if a property is part of required properties of parent schema, - // it cannot be marked as readonly. - if (!parentRequiredProperties.Contains(propertyName)) - { - writer.WriteProperty(name: OpenApiConstants.ReadOnly, value: ReadOnly, defaultValue: false); - } - - // xml - writer.WriteOptionalObject(OpenApiConstants.Xml, Xml, (w, s) => s.SerializeAsV2(w)); - - // externalDocs - writer.WriteOptionalObject(OpenApiConstants.ExternalDocs, ExternalDocs, (w, s) => s.SerializeAsV2(w)); - - // example - writer.WriteOptionalObject(OpenApiConstants.Example, Example, (w, e) => w.WriteAny(e)); - - // extensions - writer.WriteExtensions(Extensions, OpenApiSpecVersion.OpenApi2_0); - } - - /// - /// 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) - { - if (this.Reference != null) - { - return doc.ResolveReferenceTo(this.Reference); - } else - { - return this; - } - } - } -} diff --git a/src/Microsoft.OpenApi/Models/OpenApiSecurityRequirement.cs b/src/Microsoft.OpenApi/Models/OpenApiSecurityRequirement.cs index a763f0954..d7e5bf0cf 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiSecurityRequirement.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiSecurityRequirement.cs @@ -36,7 +36,7 @@ public void SerializeAsV31(IOpenApiWriter writer) { SerializeInternal(writer, (writer, element) => element.SerializeAsV31(writer)); } - + /// /// Serialize to Open Api v3.0 /// diff --git a/src/Microsoft.OpenApi/Models/OpenApiSecurityScheme.cs b/src/Microsoft.OpenApi/Models/OpenApiSecurityScheme.cs index 07b3c6161..ea6e82b90 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiSecurityScheme.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiSecurityScheme.cs @@ -102,7 +102,7 @@ public virtual void SerializeAsV31(IOpenApiWriter writer) { SerializeInternal(writer, (writer, element) => element.SerializeAsV31(writer), SerializeAsV31WithoutReference); } - + /// /// Serialize to Open Api v3.0 /// @@ -110,7 +110,7 @@ public virtual void SerializeAsV3(IOpenApiWriter writer) { SerializeInternal(writer, (writer, element) => element.SerializeAsV3(writer), SerializeAsV3WithoutReference); } - + /// /// Serialize to Open Api v3.0 /// @@ -118,13 +118,13 @@ private void SerializeInternal(IOpenApiWriter writer, Action action) { writer = writer ?? throw Error.ArgumentNull(nameof(writer)); - - if (Reference != null) + + if (Reference != null) { callback(writer, Reference); return; } - + action(writer); } @@ -133,7 +133,7 @@ private void SerializeInternal(IOpenApiWriter writer, Action public virtual void SerializeAsV31WithoutReference(IOpenApiWriter writer) { - SerializeInternalWithoutReference(writer, OpenApiSpecVersion.OpenApi3_1, + SerializeInternalWithoutReference(writer, OpenApiSpecVersion.OpenApi3_1, (writer, element) => element.SerializeAsV31(writer)); } @@ -142,7 +142,7 @@ public virtual void SerializeAsV31WithoutReference(IOpenApiWriter writer) /// public virtual void SerializeAsV3WithoutReference(IOpenApiWriter writer) { - SerializeInternalWithoutReference(writer, OpenApiSpecVersion.OpenApi3_0, + SerializeInternalWithoutReference(writer, OpenApiSpecVersion.OpenApi3_0, (writer, element) => element.SerializeAsV3(writer)); } diff --git a/src/Microsoft.OpenApi/Models/OpenApiServer.cs b/src/Microsoft.OpenApi/Models/OpenApiServer.cs index 832c8b0dd..ba67bd3d6 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiServer.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiServer.cs @@ -67,11 +67,11 @@ 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, + private void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion version, Action callback) { writer = writer ?? throw Error.ArgumentNull(nameof(writer)); diff --git a/src/Microsoft.OpenApi/Models/OpenApiServerVariable.cs b/src/Microsoft.OpenApi/Models/OpenApiServerVariable.cs index 7194b6284..beab7e450 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiServerVariable.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiServerVariable.cs @@ -36,7 +36,7 @@ public class OpenApiServerVariable : IOpenApiSerializable, IOpenApiExtensible /// /// Parameterless constructor /// - public OpenApiServerVariable() {} + public OpenApiServerVariable() { } /// /// Initializes a copy of an object @@ -56,7 +56,7 @@ public void SerializeAsV31(IOpenApiWriter writer) { SerializeInternal(writer, OpenApiSpecVersion.OpenApi3_1); } - + /// /// Serialize to Open Api v3.0 /// @@ -64,7 +64,7 @@ public void SerializeAsV3(IOpenApiWriter writer) { SerializeInternal(writer, OpenApiSpecVersion.OpenApi3_0); } - + /// /// Serialize to Open Api v3.0 /// diff --git a/src/Microsoft.OpenApi/Models/OpenApiTag.cs b/src/Microsoft.OpenApi/Models/OpenApiTag.cs index bcc2c056d..40303a9c0 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiTag.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiTag.cs @@ -60,7 +60,7 @@ public OpenApiTag(OpenApiTag tag) UnresolvedReference = tag?.UnresolvedReference ?? UnresolvedReference; Reference = tag?.Reference != null ? new(tag?.Reference) : null; } - + /// /// Serialize to Open Api v3.1 /// @@ -68,7 +68,7 @@ public virtual void SerializeAsV31(IOpenApiWriter writer) { SerializeInternal(writer, (writer, element) => element.SerializeAsV31(writer)); } - + /// /// Serialize to Open Api v3.0 /// @@ -76,7 +76,7 @@ public virtual void SerializeAsV3(IOpenApiWriter writer) { SerializeInternal(writer, (writer, element) => element.SerializeAsV3(writer)); } - + /// /// Serialize to Open Api v3.0 /// @@ -98,16 +98,16 @@ private void SerializeInternal(IOpenApiWriter writer, Action public virtual void SerializeAsV31WithoutReference(IOpenApiWriter writer) { - SerializeInternalWithoutReference(writer, OpenApiSpecVersion.OpenApi3_1, + 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, + SerializeInternalWithoutReference(writer, OpenApiSpecVersion.OpenApi3_0, (writer, element) => element.SerializeAsV3(writer)); } diff --git a/src/Microsoft.OpenApi/Models/OpenApiXml.cs b/src/Microsoft.OpenApi/Models/OpenApiXml.cs index 91748c879..bb53ac968 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 diff --git a/src/Microsoft.OpenApi/Models/References/OpenApiHeaderReference.cs b/src/Microsoft.OpenApi/Models/References/OpenApiHeaderReference.cs index a7ec90fca..276a56002 100644 --- a/src/Microsoft.OpenApi/Models/References/OpenApiHeaderReference.cs +++ b/src/Microsoft.OpenApi/Models/References/OpenApiHeaderReference.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using Json.Schema; using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Properties; @@ -72,7 +73,7 @@ public override string Description public override bool AllowEmptyValue { get => Target.AllowEmptyValue; set => Target.AllowEmptyValue = value; } /// - public override OpenApiSchema Schema { get => Target.Schema; set => Target.Schema = value; } + public override JsonSchema Schema { get => Target.Schema; set => Target.Schema = value; } /// public override ParameterStyle? Style { get => Target.Style; set => Target.Style = value; } diff --git a/src/Microsoft.OpenApi/Models/References/OpenApiParameterReference.cs b/src/Microsoft.OpenApi/Models/References/OpenApiParameterReference.cs index 6a12b0451..784c8be17 100644 --- a/src/Microsoft.OpenApi/Models/References/OpenApiParameterReference.cs +++ b/src/Microsoft.OpenApi/Models/References/OpenApiParameterReference.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using Json.Schema; using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Properties; @@ -83,7 +84,7 @@ public override string Description public override bool AllowReserved { get => Target.AllowReserved; set => Target.AllowReserved = value; } /// - public override OpenApiSchema Schema { get => Target.Schema; set => Target.Schema = value; } + public override JsonSchema Schema { get => Target.Schema; set => Target.Schema = value; } /// public override IDictionary Examples { get => Target.Examples; set => Target.Examples = value; } diff --git a/src/Microsoft.OpenApi/Services/CopyReferences.cs b/src/Microsoft.OpenApi/Services/CopyReferences.cs index 24dcfee25..e8fea8afb 100644 --- a/src/Microsoft.OpenApi/Services/CopyReferences.cs +++ b/src/Microsoft.OpenApi/Services/CopyReferences.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. using System.Collections.Generic; +using Json.Schema; using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Models; @@ -25,12 +26,12 @@ public override void Visit(IOpenApiReferenceable referenceable) { switch (referenceable) { - case OpenApiSchema schema: + case JsonSchema schema: EnsureComponentsExists(); EnsureSchemasExists(); - if (!Components.Schemas.ContainsKey(schema.Reference.Id)) + if (!Components.Schemas.ContainsKey(schema.GetRef().OriginalString)) { - Components.Schemas.Add(schema.Reference.Id, schema); + Components.Schemas.Add(schema.GetRef().OriginalString, schema); } break; @@ -59,22 +60,22 @@ public override void Visit(IOpenApiReferenceable referenceable) } /// - /// Visits + /// Visits /// /// The OpenApiSchema to be visited. - public override void Visit(OpenApiSchema schema) + public override void Visit(ref JsonSchema schema) { // This is needed to handle schemas used in Responses in components - if (schema.Reference != null) + if (schema.GetRef() != null) { EnsureComponentsExists(); EnsureSchemasExists(); - if (!Components.Schemas.ContainsKey(schema.Reference.Id)) + if (!Components.Schemas.ContainsKey(schema.GetRef().OriginalString)) { - Components.Schemas.Add(schema.Reference.Id, schema); + Components.Schemas.Add(schema.GetRef().OriginalString, schema); } } - base.Visit(schema); + base.Visit(ref schema); } private void EnsureComponentsExists() @@ -89,7 +90,7 @@ private void EnsureSchemasExists() { if (_target.Components.Schemas == null) { - _target.Components.Schemas = new Dictionary(); + _target.Components.Schemas = new Dictionary(); } } diff --git a/src/Microsoft.OpenApi/Services/LoopDetector.cs b/src/Microsoft.OpenApi/Services/LoopDetector.cs index 249cab51d..1a796f60b 100644 --- a/src/Microsoft.OpenApi/Services/LoopDetector.cs +++ b/src/Microsoft.OpenApi/Services/LoopDetector.cs @@ -1,8 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Microsoft.OpenApi.Services { diff --git a/src/Microsoft.OpenApi/Services/OpenApiFilterService.cs b/src/Microsoft.OpenApi/Services/OpenApiFilterService.cs index 7b9df3d0e..0aa28eb1c 100644 --- a/src/Microsoft.OpenApi/Services/OpenApiFilterService.cs +++ b/src/Microsoft.OpenApi/Services/OpenApiFilterService.cs @@ -69,7 +69,7 @@ public static class OpenApiFilterService { var apiVersion = source.Info.Version; - var sources = new Dictionary {{ apiVersion, source}}; + var sources = new Dictionary { { apiVersion, source } }; var rootNode = CreateOpenApiUrlTreeNode(sources); // Iterate through urls dictionary and fetch operations for each url @@ -135,7 +135,7 @@ public static OpenApiDocument CreateFilteredDocument(OpenApiDocument source, Fun Extensions = source.Info.Extensions }, - Components = new OpenApiComponents {SecuritySchemes = source.Components.SecuritySchemes}, + Components = new OpenApiComponents { SecuritySchemes = source.Components.SecuritySchemes }, SecurityRequirements = source.SecurityRequirements, Servers = source.Servers }; @@ -199,7 +199,7 @@ public static OpenApiUrlTreeNode CreateOpenApiUrlTreeNode(Dictionary GetOpenApiOperations(OpenApiUrlTreeNode rootNode, string relativeUrl, string label) { if (relativeUrl.Equals("/", StringComparison.Ordinal) && rootNode.HasOperations(label)) @@ -342,7 +342,7 @@ private static string ExtractPath(string url, IList serverList) continue; } - var urlComponents = url.Split(new[]{ serverUrl }, StringSplitOptions.None); + var urlComponents = url.Split(new[] { serverUrl }, StringSplitOptions.None); queryPath = urlComponents[1]; } diff --git a/src/Microsoft.OpenApi/Services/OpenApiReferenceError.cs b/src/Microsoft.OpenApi/Services/OpenApiReferenceError.cs index 7e2ebdcac..d27a0a47a 100644 --- a/src/Microsoft.OpenApi/Services/OpenApiReferenceError.cs +++ b/src/Microsoft.OpenApi/Services/OpenApiReferenceError.cs @@ -1,11 +1,6 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using Microsoft.OpenApi.Exceptions; using Microsoft.OpenApi.Models; diff --git a/src/Microsoft.OpenApi/Services/OpenApiReferenceResolver.cs b/src/Microsoft.OpenApi/Services/OpenApiReferenceResolver.cs index 2262bfd6c..86bf11e00 100644 --- a/src/Microsoft.OpenApi/Services/OpenApiReferenceResolver.cs +++ b/src/Microsoft.OpenApi/Services/OpenApiReferenceResolver.cs @@ -4,7 +4,9 @@ using System; using System.Collections.Generic; using System.Linq; +using Json.Schema; using Microsoft.OpenApi.Exceptions; +using Microsoft.OpenApi.Extensions; using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Models; @@ -69,7 +71,7 @@ public override void Visit(OpenApiComponents components) ResolveMap(components.Links); ResolveMap(components.Callbacks); ResolveMap(components.Examples); - ResolveMap(components.Schemas); + components.Schemas = ResolveJsonSchemas(components.Schemas); ResolveMap(components.PathItems); ResolveMap(components.SecuritySchemes); ResolveMap(components.Headers); @@ -108,12 +110,12 @@ public override void Visit(OpenApiOperation operation) } /// - /// Resolve all references using in mediaType object + /// Resolve all references used in mediaType object /// /// public override void Visit(OpenApiMediaType mediaType) { - ResolveObject(mediaType.Schema, r => mediaType.Schema = r); + ResolveJsonSchema(mediaType.Schema, r => mediaType.Schema = r ?? mediaType.Schema); } /// @@ -176,7 +178,7 @@ public override void Visit(IList parameters) /// public override void Visit(OpenApiParameter parameter) { - ResolveObject(parameter.Schema, r => parameter.Schema = r); + ResolveJsonSchema(parameter.Schema, r => parameter.Schema = r); ResolveMap(parameter.Examples); } @@ -187,18 +189,96 @@ public override void Visit(IDictionary links) { ResolveMap(links); } + + /// + /// Resolve all references used in a schem + /// + /// + public override void Visit(ref JsonSchema schema) + { + var reference = schema.GetRef(); + var description = schema.GetDescription(); + var summary = schema.GetSummary(); + + if (reference != null) + { + schema = ResolveJsonSchemaReference(reference, description, summary); + } + + var builder = new JsonSchemaBuilder(); + foreach (var keyword in schema.Keywords) + { + builder.Add(keyword); + } + + ResolveJsonSchema(schema.GetItems(), r => builder.Items(r)); + ResolveJsonSchemaList((IList)schema.GetOneOf(), r => builder.OneOf(r)); + ResolveJsonSchemaList((IList)schema.GetAllOf(), r => builder.AllOf(r)); + ResolveJsonSchemaList((IList)schema.GetAnyOf(), r => builder.AnyOf(r)); + ResolveJsonSchemaMap((IDictionary)schema.GetProperties(), r => builder.Properties((IReadOnlyDictionary)r)); + ResolveJsonSchema(schema.GetAdditionalProperties(), r => builder.AdditionalProperties(r)); + + schema = builder.Build(); + } /// - /// Resolve all references used in a schema + /// Visits an IBaseDocument instance /// - public override void Visit(OpenApiSchema schema) + /// + public override void Visit(IBaseDocument document) { } + + private Dictionary ResolveJsonSchemas(IDictionary schemas) { - 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); + var resolvedSchemas = new Dictionary(); + foreach (var schema in schemas) + { + var schemaValue = schema.Value; + Visit(ref schemaValue); + resolvedSchemas[schema.Key] = schemaValue; + } + + return resolvedSchemas; + } + + /// + /// Resolves the target to a JSON schema reference by retrieval from Schema registry + /// + /// The JSON schema reference. + /// The schema's description. + /// The schema's summary. + /// + public JsonSchema ResolveJsonSchemaReference(Uri reference, string description = null, string summary = null) + { + var refUri = $"https://registry{reference.OriginalString.Split('#').LastOrDefault()}"; + var resolvedSchema = (JsonSchema)SchemaRegistry.Global.Get(new Uri(refUri)); + + if (resolvedSchema != null) + { + var resolvedSchemaBuilder = new JsonSchemaBuilder(); + + foreach (var keyword in resolvedSchema.Keywords) + { + resolvedSchemaBuilder.Add(keyword); + + // Replace the resolved schema's description with that of the schema reference + if (!string.IsNullOrEmpty(description)) + { + resolvedSchemaBuilder.Description(description); + } + + // Replace the resolved schema's summary with that of the schema reference + if (!string.IsNullOrEmpty(summary)) + { + resolvedSchemaBuilder.Summary(summary); + } + } + + return resolvedSchemaBuilder.Build(); + } + else + { + return null; + } } /// @@ -235,6 +315,19 @@ private void ResolveTags(IList tags) } } + private void ResolveJsonSchema(JsonSchema schema, Action assign) + { + if (schema == null) return; + var reference = schema.GetRef(); + var description = schema.GetDescription(); + var summary = schema.GetSummary(); + + if (reference != null) + { + assign(ResolveJsonSchemaReference(reference, description, summary)); + } + } + private void ResolveList(IList list) where T : class, IOpenApiReferenceable, new() { if (list == null) return; @@ -249,6 +342,23 @@ private void ResolveTags(IList tags) } } + private void ResolveJsonSchemaList(IList list, Action> assign) + { + if (list == null) return; + + for (int i = 0; i < list.Count; i++) + { + var entity = list[i]; + var reference = entity.GetRef(); + if (reference != null) + { + list[i] = ResolveJsonSchemaReference(reference); + } + } + + assign(list.ToList()); + } + private void ResolveMap(IDictionary map) where T : class, IOpenApiReferenceable, new() { if (map == null) return; @@ -263,6 +373,23 @@ private void ResolveTags(IList tags) } } + private void ResolveJsonSchemaMap(IDictionary map, Action> assign) + { + if (map == null) return; + + foreach (var key in map.Keys.ToList()) + { + var entity = map[key]; + var reference = entity.GetRef(); + if (reference != null) + { + map[key] = ResolveJsonSchemaReference(reference); + } + } + + assign(map.ToDictionary(e => e.Key, e => e.Value)); + } + private T ResolveReference(OpenApiReference reference) where T : class, IOpenApiReferenceable, new() { if (string.IsNullOrEmpty(reference.ExternalResource)) diff --git a/src/Microsoft.OpenApi/Services/OpenApiUrlTreeNode.cs b/src/Microsoft.OpenApi/Services/OpenApiUrlTreeNode.cs index 9f4ccb8be..b6f9cb118 100644 --- a/src/Microsoft.OpenApi/Services/OpenApiUrlTreeNode.cs +++ b/src/Microsoft.OpenApi/Services/OpenApiUrlTreeNode.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Collections.ObjectModel; using System.IO; using System.Linq; using Microsoft.OpenApi.Models; @@ -268,7 +267,7 @@ public void WriteMermaid(TextWriter writer) { "DELETE", new MermaidNodeStyle("Tomato", MermaidNodeShape.Rhombus) }, { "OTHER", new MermaidNodeStyle("White", MermaidNodeShape.SquareCornerRectangle) }, }; - + private static void ProcessNode(OpenApiUrlTreeNode node, TextWriter writer) { var path = string.IsNullOrEmpty(node.Path) ? "/" : SanitizeMermaidNode(node.Path); @@ -296,7 +295,7 @@ private static string GetMethods(OpenApiUrlTreeNode node) private static (string, string) GetShapeDelimiters(string methods) { - + if (MermaidNodeStyles.TryGetValue(methods, out var style)) { //switch on shape @@ -329,7 +328,7 @@ private static string SanitizeMermaidNode(string token) .Replace(".", "_") .Replace("(", "_") .Replace(")", "_") - .Replace(";", "_") + .Replace(";", "_") .Replace("-", "_") .Replace("graph", "gra_ph") // graph is a reserved word .Replace("default", "def_ault"); // default is a reserved word for classes @@ -354,12 +353,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 b5df0b4f8..087084a08 100644 --- a/src/Microsoft.OpenApi/Services/OpenApiVisitorBase.cs +++ b/src/Microsoft.OpenApi/Services/OpenApiVisitorBase.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Linq; using System.Text.Json.Nodes; +using Json.Schema; using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Models; @@ -114,7 +115,7 @@ public virtual void Visit(OpenApiPaths paths) public virtual void Visit(IDictionary webhooks) { } - + /// /// Visits /// @@ -237,9 +238,18 @@ public virtual void Visit(OpenApiExternalDocs externalDocs) } /// - /// Visits + /// Visits + /// + public virtual void Visit(ref JsonSchema schema) + { + } + + public virtual void Visit(IBaseDocument document) { } + + /// + /// Visits /// - public virtual void Visit(OpenApiSchema schema) + public virtual void Visit(IReadOnlyCollection schema) { } diff --git a/src/Microsoft.OpenApi/Services/OpenApiWalker.cs b/src/Microsoft.OpenApi/Services/OpenApiWalker.cs index df5b41c83..3cad3c78c 100644 --- a/src/Microsoft.OpenApi/Services/OpenApiWalker.cs +++ b/src/Microsoft.OpenApi/Services/OpenApiWalker.cs @@ -1,13 +1,14 @@ -// 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.Models; -using Microsoft.OpenApi.Interfaces; -using Microsoft.OpenApi.Extensions; -using System.Text.Json.Nodes; +using Json.Schema; +using Json.Schema.OpenApi; using Microsoft.OpenApi.Any; +using Microsoft.OpenApi.Extensions; +using Microsoft.OpenApi.Interfaces; +using Microsoft.OpenApi.Models; namespace Microsoft.OpenApi.Services { @@ -17,7 +18,7 @@ namespace Microsoft.OpenApi.Services public class OpenApiWalker { private readonly OpenApiVisitorBase _visitor; - private readonly Stack _schemaLoop = new Stack(); + private readonly Stack _schemaLoop = new Stack(); private readonly Stack _pathItemLoop = new Stack(); /// @@ -78,6 +79,19 @@ internal void Walk(IList tags) } } + /// + /// Visits and child objects + /// + internal void Walk(string externalDocs) + { + if (externalDocs == null) + { + return; + } + + _visitor.Visit(externalDocs); + } + /// /// Visits and child objects /// @@ -118,7 +132,7 @@ internal void Walk(OpenApiComponents components) } } }); - + Walk(OpenApiConstants.SecuritySchemes, () => { if (components.SecuritySchemes != null) @@ -129,7 +143,7 @@ internal void Walk(OpenApiComponents components) } } }); - + Walk(OpenApiConstants.Callbacks, () => { if (components.Callbacks != null) @@ -492,7 +506,7 @@ internal void Walk(OpenApiPathItem pathItem, bool isComponent = false) _visitor.Visit(pathItem as IOpenApiExtensible); _pathItemLoop.Pop(); - } + } /// /// Visits dictionary of @@ -741,7 +755,7 @@ internal void Walk(OpenApiMediaType mediaType) _visitor.Visit(mediaType); Walk(OpenApiConstants.Example, () => Walk(mediaType.Examples)); - Walk(OpenApiConstants.Schema, () => Walk(mediaType.Schema)); + Walk(OpenApiConstants.Schema, () => mediaType.Schema = Walk(mediaType.Schema)); Walk(OpenApiConstants.Encoding, () => Walk(mediaType.Encoding)); Walk(mediaType as IOpenApiExtensible); } @@ -789,67 +803,83 @@ internal void Walk(OpenApiEncoding encoding) } /// - /// Visits and child objects + /// Visits and child objects /// - internal void Walk(OpenApiSchema schema, bool isComponent = false) + internal JsonSchema Walk(JsonSchema schema, bool isComponent = false) { - if (schema == null || ProcessAsReference(schema, isComponent)) + if (schema == null + || ProcessSchemaAsReference(schema, isComponent)) { - return; + return schema; } if (_schemaLoop.Contains(schema)) { - return; // Loop detected, this schema has already been walked. + return schema; // Loop detected, this schema has already been walked. } else { _schemaLoop.Push(schema); } - _visitor.Visit(schema); + _visitor.Visit(ref schema); - if (schema.Items != null) + if (schema.GetItems() != null) { - Walk("items", () => Walk(schema.Items)); + Walk("items", () => Walk(schema.GetItems())); } - if (schema.AllOf != null) + if (schema.GetAllOf() != null) { - Walk("allOf", () => Walk(schema.AllOf)); + Walk("allOf", () => Walk(schema.GetAllOf())); } - if (schema.AnyOf != null) + if (schema.GetAnyOf() != null) { - Walk("anyOf", () => Walk(schema.AnyOf)); + Walk("anyOf", () => Walk(schema.GetAnyOf())); } - if (schema.OneOf != null) + if (schema.GetOneOf() != null) { - Walk("oneOf", () => Walk(schema.OneOf)); + Walk("oneOf", () => Walk(schema.GetOneOf())); } - if (schema.Properties != null) + if (schema.GetProperties() != null) { Walk("properties", () => { - foreach (var item in schema.Properties) + foreach (var item in schema.GetProperties()) { Walk(item.Key, () => Walk(item.Value)); } }); } - if (schema.AdditionalProperties != null) + if (schema.GetAdditionalProperties() != null) { - Walk("additionalProperties", () => Walk(schema.AdditionalProperties)); + Walk("additionalProperties", () => Walk(schema.GetAdditionalProperties())); } - Walk(OpenApiConstants.ExternalDocs, () => Walk(schema.ExternalDocs)); + Walk(OpenApiConstants.ExternalDocs, () => Walk(schema.GetExternalDocs())); Walk(schema as IOpenApiExtensible); _schemaLoop.Pop(); + return schema; + } + + internal void Walk(IReadOnlyCollection schemaCollection, bool isComponent = false) + { + if (schemaCollection is null) + { + return; + } + + _visitor.Visit(schemaCollection); + foreach (var schema in schemaCollection) + { + Walk(schema); + } } /// @@ -925,9 +955,9 @@ internal void Walk(IList examples) } /// - /// Visits a list of and child objects + /// Visits a list of and child objects /// - internal void Walk(IList schemas) + internal void Walk(IList schemas) { if (schemas == null) { @@ -1096,7 +1126,7 @@ internal void Walk(IOpenApiElement element) case OpenApiParameter e: Walk(e); break; case OpenApiRequestBody e: Walk(e); break; case OpenApiResponse e: Walk(e); break; - case OpenApiSchema e: Walk(e); break; + case JsonSchema e: Walk(e); break; case OpenApiSecurityRequirement e: Walk(e); break; case OpenApiSecurityScheme e: Walk(e); break; case OpenApiServer e: Walk(e); break; @@ -1132,6 +1162,18 @@ private bool ProcessAsReference(IOpenApiReferenceable referenceable, bool isComp } return isReference; } + + private bool ProcessSchemaAsReference(IBaseDocument baseDocument, bool isComponent = false) + { + var schema = baseDocument as JsonSchema; + var isReference = schema?.GetRef() != null && !isComponent; + if (isReference) + { + _visitor.Visit(baseDocument); + } + + return isReference; + } } /// diff --git a/src/Microsoft.OpenApi/Services/OpenApiWorkspace.cs b/src/Microsoft.OpenApi/Services/OpenApiWorkspace.cs index 7827a50c1..24924998e 100644 --- a/src/Microsoft.OpenApi/Services/OpenApiWorkspace.cs +++ b/src/Microsoft.OpenApi/Services/OpenApiWorkspace.cs @@ -5,8 +5,8 @@ using System.Collections.Generic; using System.IO; using System.Linq; -using System.Text; -using System.Threading.Tasks; +using Json.More; +using Json.Schema; using Microsoft.OpenApi.Extensions; using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Models; @@ -18,18 +18,21 @@ namespace Microsoft.OpenApi.Services /// public class OpenApiWorkspace { - private Dictionary _documents = new Dictionary(); - private Dictionary _fragments = new Dictionary(); - private Dictionary _artifacts = new Dictionary(); + private readonly Dictionary _documents = new(); + private readonly Dictionary _fragments = new(); + private readonly Dictionary _schemaFragments = new(); + private readonly Dictionary _artifacts = new(); /// /// A list of OpenApiDocuments contained in the workspace /// - public IEnumerable Documents { - get { + public IEnumerable Documents + { + get + { return _documents.Values; } - } + } /// /// A list of document fragments that are contained in the workspace @@ -60,13 +63,13 @@ public OpenApiWorkspace(Uri baseUrl) /// public OpenApiWorkspace() { - BaseUrl = new Uri("file://" + Environment.CurrentDirectory + "\\" ); + BaseUrl = new Uri("file://" + Environment.CurrentDirectory + "\\"); } /// /// Initializes a copy of an object /// - public OpenApiWorkspace(OpenApiWorkspace workspace){} + public OpenApiWorkspace(OpenApiWorkspace workspace) { } /// /// Verify if workspace contains a document based on its URL. @@ -84,7 +87,7 @@ public bool Contains(string location) /// /// /// - public void AddDocument(string location, OpenApiDocument document) + public void AddDocument(string location, OpenApiDocument document) { document.Workspace = this; _documents.Add(ToLocationUrl(location), document); @@ -103,6 +106,16 @@ public void AddFragment(string location, IOpenApiReferenceable fragment) _fragments.Add(ToLocationUrl(location), fragment); } + /// + /// Adds a schema fragment of an OpenApiDocument to the workspace. + /// + /// + /// + public void AddSchemaFragment(string location, JsonSchema fragment) + { + _schemaFragments.Add(ToLocationUrl(location), fragment); + } + /// /// Add a stream based artificat to the workspace. Useful for images, examples, alternative schemas. /// @@ -132,6 +145,41 @@ public IOpenApiReferenceable ResolveReference(OpenApiReference reference) return null; } + /// + /// Resolve the target of a JSON schema reference from within the workspace + /// + /// An instance of a JSON schema reference. + /// + public JsonSchema ResolveJsonSchemaReference(Uri reference) + { + var docs = _documents.Values; + if (docs.Any()) + { + var doc = docs.FirstOrDefault(); + if (doc != null) + { + foreach (var jsonSchema in doc.Components.Schemas) + { + var refUri = new Uri(OpenApiConstants.V3ReferenceUri + jsonSchema.Key); + SchemaRegistry.Global.Register(refUri, jsonSchema.Value); + } + + var resolver = new OpenApiReferenceResolver(doc); + return resolver.ResolveJsonSchemaReference(reference); + } + return null; + } + else + { + foreach (var jsonSchema in _schemaFragments) + { + SchemaRegistry.Global.Register(reference, jsonSchema.Value); + } + + return FetchSchemaFromRegistry(reference); + } + } + /// /// /// @@ -146,5 +194,11 @@ private Uri ToLocationUrl(string location) { return new Uri(BaseUrl, location); } + + private static JsonSchema FetchSchemaFromRegistry(Uri reference) + { + var resolvedSchema = (JsonSchema)SchemaRegistry.Global.Get(reference); + return resolvedSchema; + } } } diff --git a/src/Microsoft.OpenApi/Services/OperationSearch.cs b/src/Microsoft.OpenApi/Services/OperationSearch.cs index 90e88cc70..19775d877 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/Validations/OpenApiValidatiorWarning.cs b/src/Microsoft.OpenApi/Validations/OpenApiValidatiorWarning.cs index 77480584d..9012b1c06 100644 --- a/src/Microsoft.OpenApi/Validations/OpenApiValidatiorWarning.cs +++ b/src/Microsoft.OpenApi/Validations/OpenApiValidatiorWarning.cs @@ -1,14 +1,10 @@ -using System; -using System.Collections.Generic; -using System.Text; -using Microsoft.OpenApi.Exceptions; -using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Models; 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 64f901c53..9abbdf224 100644 --- a/src/Microsoft.OpenApi/Validations/OpenApiValidator.cs +++ b/src/Microsoft.OpenApi/Validations/OpenApiValidator.cs @@ -3,7 +3,7 @@ using System; using System.Collections.Generic; -using System.Linq; +using Json.Schema; using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Services; @@ -157,10 +157,10 @@ public void AddWarning(OpenApiValidatorWarning warning) public override void Visit(OpenApiParameter item) => Validate(item); /// - /// Execute validation rules against an + /// Execute validation rules against an /// /// The object to be validated - public override void Visit(OpenApiSchema item) => Validate(item); + public override void Visit(ref JsonSchema item) => Validate(item); /// /// Execute validation rules against an diff --git a/src/Microsoft.OpenApi/Validations/OpenApiValidatorError.cs b/src/Microsoft.OpenApi/Validations/OpenApiValidatorError.cs index c24d48fe3..95f60dedd 100644 --- a/src/Microsoft.OpenApi/Validations/OpenApiValidatorError.cs +++ b/src/Microsoft.OpenApi/Validations/OpenApiValidatorError.cs @@ -1,11 +1,6 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using Microsoft.OpenApi.Models; namespace Microsoft.OpenApi.Validations diff --git a/src/Microsoft.OpenApi/Validations/Rules/OpenApiSchemaRules.cs b/src/Microsoft.OpenApi/Validations/Rules/JsonSchemaRules.cs similarity index 62% rename from src/Microsoft.OpenApi/Validations/Rules/OpenApiSchemaRules.cs rename to src/Microsoft.OpenApi/Validations/Rules/JsonSchemaRules.cs index 1fb715ac2..a8efc0289 100644 --- a/src/Microsoft.OpenApi/Validations/Rules/OpenApiSchemaRules.cs +++ b/src/Microsoft.OpenApi/Validations/Rules/JsonSchemaRules.cs @@ -1,31 +1,35 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. -using Microsoft.OpenApi.Models; -using Microsoft.OpenApi.Properties; using System.Collections.Generic; +using System.Linq; +using Json.Schema; +using Json.Schema.OpenApi; +using Microsoft.OpenApi.Any; +using Microsoft.OpenApi.Extensions; +using Microsoft.OpenApi.Properties; namespace Microsoft.OpenApi.Validations.Rules { /// - /// The validation rules for . + /// The validation rules for . /// [OpenApiRule] - public static class OpenApiSchemaRules + public static class JsonSchemaRules { /// /// Validate the data matches with the given data type. /// - public static ValidationRule SchemaMismatchedDataType => - new ValidationRule( - (context, schema) => + public static ValidationRule SchemaMismatchedDataType => + new ValidationRule( + (context, jsonSchema) => { // default context.Enter("default"); - if (schema.Default != null) + if (jsonSchema.GetDefault() != null) { - RuleHelpers.ValidateDataTypeMismatch(context, nameof(SchemaMismatchedDataType), schema.Default.Node, schema); + RuleHelpers.ValidateDataTypeMismatch(context, nameof(SchemaMismatchedDataType), jsonSchema.GetDefault(), jsonSchema); } context.Exit(); @@ -33,9 +37,9 @@ public static class OpenApiSchemaRules // example context.Enter("example"); - if (schema.Example != null) + if (jsonSchema.GetExample() != null) { - RuleHelpers.ValidateDataTypeMismatch(context, nameof(SchemaMismatchedDataType), schema.Example.Node, schema); + RuleHelpers.ValidateDataTypeMismatch(context, nameof(SchemaMismatchedDataType), jsonSchema.GetExample(), jsonSchema); } context.Exit(); @@ -43,12 +47,12 @@ public static class OpenApiSchemaRules // enum context.Enter("enum"); - if (schema.Enum != null) + if (jsonSchema.GetEnum() != null) { - for (int i = 0; i < schema.Enum.Count; i++) + for (int i = 0; i < jsonSchema.GetEnum().Count; i++) { context.Enter(i.ToString()); - RuleHelpers.ValidateDataTypeMismatch(context, nameof(SchemaMismatchedDataType), schema.Enum[i].Node, schema); + RuleHelpers.ValidateDataTypeMismatch(context, nameof(SchemaMismatchedDataType), jsonSchema.GetEnum().ElementAt(i), jsonSchema); context.Exit(); } } @@ -59,22 +63,22 @@ public static class OpenApiSchemaRules /// /// Validates Schema Discriminator /// - public static ValidationRule ValidateSchemaDiscriminator => - new ValidationRule( - (context, schema) => + public static ValidationRule ValidateSchemaDiscriminator => + new ValidationRule( + (context, jsonSchema) => { // discriminator context.Enter("discriminator"); - if (schema.Reference != null && schema.Discriminator != null) + if (jsonSchema.GetRef() != null && jsonSchema.GetOpenApiDiscriminator() != null) { - var discriminatorName = schema.Discriminator?.PropertyName; + var discriminatorName = jsonSchema.GetOpenApiDiscriminator()?.PropertyName; - if (!ValidateChildSchemaAgainstDiscriminator(schema, discriminatorName)) + if (!ValidateChildSchemaAgainstDiscriminator(jsonSchema, discriminatorName)) { context.CreateError(nameof(ValidateSchemaDiscriminator), string.Format(SRResource.Validation_SchemaRequiredFieldListMustContainThePropertySpecifiedInTheDiscriminator, - schema.Reference.Id, discriminatorName)); + jsonSchema.GetRef(), discriminatorName)); } } @@ -87,22 +91,22 @@ public static class OpenApiSchemaRules /// The parent schema. /// 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 static bool ValidateChildSchemaAgainstDiscriminator(OpenApiSchema schema, string discriminatorName) + public static bool ValidateChildSchemaAgainstDiscriminator(JsonSchema schema, string discriminatorName) { - if (!schema.Required?.Contains(discriminatorName) ?? false) + if (!schema.GetRequired()?.Contains(discriminatorName) ?? true) { // recursively check nested schema.OneOf, schema.AnyOf or schema.AllOf and their required fields for the discriminator - if (schema.OneOf.Count != 0) + if (schema.GetOneOf()?.Count != 0 && TraverseSchemaElements(discriminatorName, schema.GetOneOf())) { - return TraverseSchemaElements(discriminatorName, schema.OneOf); + return true; } - if (schema.AnyOf.Count != 0) + if (schema.GetAnyOf()?.Count != 0 && TraverseSchemaElements(discriminatorName, schema.GetAnyOf())) { - return TraverseSchemaElements(discriminatorName, schema.AnyOf); + return true; } - if (schema.AllOf.Count != 0) + if (schema.GetAllOf()?.Count != 0 && TraverseSchemaElements(discriminatorName, schema.GetAllOf())) { - return TraverseSchemaElements(discriminatorName, schema.AllOf); + return true; } } else @@ -120,12 +124,15 @@ public static bool ValidateChildSchemaAgainstDiscriminator(OpenApiSchema schema, /// between other schemas which may satisfy the payload description. /// The child schema. /// - public static bool TraverseSchemaElements(string discriminatorName, IList childSchema) + public static bool TraverseSchemaElements(string discriminatorName, IReadOnlyCollection childSchema) { + if (!childSchema?.Any() ?? true) + return false; + foreach (var childItem in childSchema) { - if ((!childItem.Properties?.ContainsKey(discriminatorName) ?? false) && - (!childItem.Required?.Contains(discriminatorName) ?? false)) + if ((!childItem.GetProperties()?.ContainsKey(discriminatorName) ?? true) && + (!childItem.GetRequired()?.Contains(discriminatorName) ?? true)) { return ValidateChildSchemaAgainstDiscriminator(childItem, discriminatorName); } diff --git a/src/Microsoft.OpenApi/Validations/Rules/OpenApiExtensionRules.cs b/src/Microsoft.OpenApi/Validations/Rules/OpenApiExtensionRules.cs index c44983ffb..ef11e23e2 100644 --- a/src/Microsoft.OpenApi/Validations/Rules/OpenApiExtensionRules.cs +++ b/src/Microsoft.OpenApi/Validations/Rules/OpenApiExtensionRules.cs @@ -3,7 +3,6 @@ using System; using Microsoft.OpenApi.Interfaces; -using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Properties; namespace Microsoft.OpenApi.Validations.Rules diff --git a/src/Microsoft.OpenApi/Validations/Rules/OpenApiHeaderRules.cs b/src/Microsoft.OpenApi/Validations/Rules/OpenApiHeaderRules.cs index a7fdc3f1b..3cd6a2f23 100644 --- a/src/Microsoft.OpenApi/Validations/Rules/OpenApiHeaderRules.cs +++ b/src/Microsoft.OpenApi/Validations/Rules/OpenApiHeaderRules.cs @@ -24,7 +24,8 @@ public static class OpenApiHeaderRules if (header.Example != null) { - RuleHelpers.ValidateDataTypeMismatch(context, nameof(HeaderMismatchedDataType), header.Example.Node, header.Schema); + RuleHelpers.ValidateDataTypeMismatch(context, + nameof(HeaderMismatchedDataType), header.Example.Node, header.Schema); } context.Exit(); @@ -40,7 +41,8 @@ public static class OpenApiHeaderRules { context.Enter(key); context.Enter("value"); - RuleHelpers.ValidateDataTypeMismatch(context, nameof(HeaderMismatchedDataType), header.Examples[key]?.Value.Node, header.Schema); + RuleHelpers.ValidateDataTypeMismatch(context, + nameof(HeaderMismatchedDataType), header.Examples[key]?.Value.Node, header.Schema); context.Exit(); context.Exit(); } diff --git a/src/Microsoft.OpenApi/Validations/Rules/OpenApiParameterRules.cs b/src/Microsoft.OpenApi/Validations/Rules/OpenApiParameterRules.cs index ca4dfac66..8082f3a79 100644 --- a/src/Microsoft.OpenApi/Validations/Rules/OpenApiParameterRules.cs +++ b/src/Microsoft.OpenApi/Validations/Rules/OpenApiParameterRules.cs @@ -86,7 +86,8 @@ public static class OpenApiParameterRules { context.Enter(key); context.Enter("value"); - RuleHelpers.ValidateDataTypeMismatch(context, nameof(ParameterMismatchedDataType), parameter.Examples[key]?.Value.Node, parameter.Schema); + RuleHelpers.ValidateDataTypeMismatch(context, + nameof(ParameterMismatchedDataType), parameter.Examples[key]?.Value.Node, parameter.Schema); context.Exit(); context.Exit(); } @@ -103,7 +104,7 @@ public static class OpenApiParameterRules new ValidationRule( (context, parameter) => { - if (parameter.In == ParameterLocation.Path && + if (parameter.In == ParameterLocation.Path && !(context.PathString.Contains("{" + parameter.Name + "}") || context.PathString.Contains("#/components"))) { context.Enter("in"); diff --git a/src/Microsoft.OpenApi/Validations/Rules/RuleHelpers.cs b/src/Microsoft.OpenApi/Validations/Rules/RuleHelpers.cs index 6673252e7..59c114cb4 100644 --- a/src/Microsoft.OpenApi/Validations/Rules/RuleHelpers.cs +++ b/src/Microsoft.OpenApi/Validations/Rules/RuleHelpers.cs @@ -4,7 +4,8 @@ using System; using System.Text.Json; using System.Text.Json.Nodes; -using Microsoft.OpenApi.Models; +using Json.Schema; +using Microsoft.OpenApi.Extensions; namespace Microsoft.OpenApi.Validations.Rules { @@ -44,26 +45,25 @@ public static void ValidateDataTypeMismatch( IValidationContext context, string ruleName, JsonNode value, - OpenApiSchema schema) + JsonSchema schema) { if (schema == null) { return; } - - var type = schema.Type; - var format = schema.Format; - var nullable = schema.Nullable; + + var type = schema.GetJsonType().Value.GetDisplayName(); + var format = schema.GetFormat()?.Key; var jsonElement = JsonSerializer.Deserialize(value); // 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 && jsonElement.ValueKind is JsonValueKind.Null) + if (jsonElement.ValueKind is JsonValueKind.Null) { return; } - if (type == "object") + if ("object".Equals(type, StringComparison.OrdinalIgnoreCase)) { // 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, @@ -87,13 +87,13 @@ public static void ValidateDataTypeMismatch( foreach (var property in anyObject) { context.Enter(property.Key); - if (schema.Properties.TryGetValue(property.Key, out var propertyValue)) + if ((schema.GetProperties()?.TryGetValue(property.Key, out var propertyValue)) ?? false) { ValidateDataTypeMismatch(context, ruleName, anyObject[property.Key], propertyValue); } else { - ValidateDataTypeMismatch(context, ruleName, anyObject[property.Key], schema.AdditionalProperties); + ValidateDataTypeMismatch(context, ruleName, anyObject[property.Key], schema.GetAdditionalProperties()); } context.Exit(); @@ -103,7 +103,7 @@ public static void ValidateDataTypeMismatch( return; } - if (type == "array") + if ("array".Equals(type, StringComparison.OrdinalIgnoreCase)) { // 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, @@ -114,7 +114,7 @@ public static void ValidateDataTypeMismatch( } // If value is not a string and also not an array, there is a data mismatch. - if (!(value is JsonArray)) + if (value is not JsonArray) { context.CreateWarning( ruleName, @@ -128,7 +128,7 @@ public static void ValidateDataTypeMismatch( { context.Enter(i.ToString()); - ValidateDataTypeMismatch(context, ruleName, anyArray[i], schema.Items); + ValidateDataTypeMismatch(context, ruleName, anyArray[i], schema.GetItems()); context.Exit(); } @@ -136,7 +136,8 @@ public static void ValidateDataTypeMismatch( return; } - if (type == "integer" && format == "int32") + if ("integer".Equals(type, StringComparison.OrdinalIgnoreCase) && + "int32".Equals(format, StringComparison.OrdinalIgnoreCase)) { if (jsonElement.ValueKind is not JsonValueKind.Number) { @@ -148,7 +149,8 @@ public static void ValidateDataTypeMismatch( return; } - if (type == "integer" && format == "int64") + if ("integer".Equals(type, StringComparison.OrdinalIgnoreCase) && + "int64".Equals(format, StringComparison.OrdinalIgnoreCase)) { if (jsonElement.ValueKind is not JsonValueKind.Number) { @@ -160,7 +162,8 @@ public static void ValidateDataTypeMismatch( return; } - if (type == "integer" && jsonElement.ValueKind is not JsonValueKind.Number) + if ("integer".Equals(type, StringComparison.OrdinalIgnoreCase) && + jsonElement.ValueKind is not JsonValueKind.Number) { if (jsonElement.ValueKind is not JsonValueKind.Number) { @@ -172,7 +175,8 @@ public static void ValidateDataTypeMismatch( return; } - if (type == "number" && format == "float") + if ("number".Equals(type, StringComparison.OrdinalIgnoreCase) && + "float".Equals(format, StringComparison.OrdinalIgnoreCase)) { if (jsonElement.ValueKind is not JsonValueKind.Number) { @@ -184,7 +188,8 @@ public static void ValidateDataTypeMismatch( return; } - if (type == "number" && format == "double") + if ("number".Equals(type, StringComparison.OrdinalIgnoreCase) && + "double".Equals(format, StringComparison.OrdinalIgnoreCase)) { if (jsonElement.ValueKind is not JsonValueKind.Number) { @@ -196,7 +201,7 @@ public static void ValidateDataTypeMismatch( return; } - if (type == "number") + if ("number".Equals(type, StringComparison.OrdinalIgnoreCase)) { if (jsonElement.ValueKind is not JsonValueKind.Number) { @@ -208,7 +213,8 @@ public static void ValidateDataTypeMismatch( return; } - if (type == "string" && format == "byte") + if ("string".Equals(type, StringComparison.OrdinalIgnoreCase) && + "byte".Equals(format, StringComparison.OrdinalIgnoreCase)) { if (jsonElement.ValueKind is not JsonValueKind.String) { @@ -220,7 +226,8 @@ public static void ValidateDataTypeMismatch( return; } - if (type == "string" && format == "date") + if ("string".Equals(type, StringComparison.OrdinalIgnoreCase) && + "date".Equals(format, StringComparison.OrdinalIgnoreCase)) { if (jsonElement.ValueKind is not JsonValueKind.String) { @@ -232,7 +239,8 @@ public static void ValidateDataTypeMismatch( return; } - if (type == "string" && format == "date-time") + if ("string".Equals(type, StringComparison.OrdinalIgnoreCase) && + "date-time".Equals(format, StringComparison.OrdinalIgnoreCase)) { if (jsonElement.ValueKind is not JsonValueKind.String) { @@ -244,7 +252,8 @@ public static void ValidateDataTypeMismatch( return; } - if (type == "string" && format == "password") + if ("string".Equals(type, StringComparison.OrdinalIgnoreCase) && + "password".Equals(format, StringComparison.OrdinalIgnoreCase)) { if (jsonElement.ValueKind is not JsonValueKind.String) { @@ -256,7 +265,7 @@ public static void ValidateDataTypeMismatch( return; } - if (type == "string") + if ("string".Equals(type, StringComparison.OrdinalIgnoreCase)) { if (jsonElement.ValueKind is not JsonValueKind.String) { @@ -268,7 +277,7 @@ public static void ValidateDataTypeMismatch( return; } - if (type == "boolean") + if ("boolean".Equals(type, StringComparison.OrdinalIgnoreCase)) { if (jsonElement.ValueKind is not JsonValueKind.True and not JsonValueKind.False) { diff --git a/src/Microsoft.OpenApi/Validations/ValidationExtensions.cs b/src/Microsoft.OpenApi/Validations/ValidationExtensions.cs index 195df89cd..b951cc393 100644 --- a/src/Microsoft.OpenApi/Validations/ValidationExtensions.cs +++ b/src/Microsoft.OpenApi/Validations/ValidationExtensions.cs @@ -1,13 +1,6 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Microsoft.OpenApi.Models; - namespace Microsoft.OpenApi.Validations { /// diff --git a/src/Microsoft.OpenApi/Validations/ValidationRule.cs b/src/Microsoft.OpenApi/Validations/ValidationRule.cs index fdbf5c330..7a3d3325f 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; @@ -29,7 +29,7 @@ public abstract class ValidationRule /// 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 c34d4a451..f82d2462b 100644 --- a/src/Microsoft.OpenApi/Validations/ValidationRuleSet.cs +++ b/src/Microsoft.OpenApi/Validations/ValidationRuleSet.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/Writers/IOpenApiWriter.cs b/src/Microsoft.OpenApi/Writers/IOpenApiWriter.cs index 8fcbf10ed..673e349e4 100644 --- a/src/Microsoft.OpenApi/Writers/IOpenApiWriter.cs +++ b/src/Microsoft.OpenApi/Writers/IOpenApiWriter.cs @@ -1,6 +1,10 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. +using System; +using System.Collections.Generic; +using Json.Schema; + namespace Microsoft.OpenApi.Writers { /// @@ -68,9 +72,29 @@ public interface IOpenApiWriter /// void WriteValue(object value); + /// + /// Write the JsonSchema object + /// + /// + void WriteJsonSchema(JsonSchema schema); + + /// + /// Write the JsonSchema object + /// + /// The IOpenApiWriter object + /// The JsonSchema object + void WriteJsonSchemaWithoutReference(IOpenApiWriter writer, JsonSchema schema); + /// /// Flush the writer. /// void Flush(); + + /// + /// Writes a reference to a JsonSchema object. + /// + /// + /// + void WriteJsonSchemaReference(IOpenApiWriter writer, Uri reference); } } diff --git a/src/Microsoft.OpenApi/Writers/OpenApiJsonWriter.cs b/src/Microsoft.OpenApi/Writers/OpenApiJsonWriter.cs index 10049974b..1fd4f3ccb 100644 --- a/src/Microsoft.OpenApi/Writers/OpenApiJsonWriter.cs +++ b/src/Microsoft.OpenApi/Writers/OpenApiJsonWriter.cs @@ -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 bool _produceTerseOutput = false; + 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 6d9f2fb16..fa48c4c5c 100644 --- a/src/Microsoft.OpenApi/Writers/OpenApiWriterAnyExtensions.cs +++ b/src/Microsoft.OpenApi/Writers/OpenApiWriterAnyExtensions.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. -using System; using System.Collections.Generic; using System.Text.Json; using System.Text.Json.Nodes; @@ -54,7 +53,7 @@ public static void WriteExtensions(this IOpenApiWriter writer, IDictionary public abstract class OpenApiWriterBase : IOpenApiWriter { - + /// /// Settings for controlling how the OpenAPI document will be written out. /// @@ -49,7 +55,7 @@ public OpenApiWriterBase(TextWriter textWriter) : this(textWriter, null) /// /// /// - public OpenApiWriterBase(TextWriter textWriter, OpenApiWriterSettings settings) + public OpenApiWriterBase(TextWriter textWriter, OpenApiWriterSettings settings) { Writer = textWriter; Writer.NewLine = "\n"; @@ -227,6 +233,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); @@ -299,7 +309,7 @@ public virtual void WriteIndentation() Writer.Write(IndentationString); } } - + /// /// Get current scope. /// @@ -410,5 +420,192 @@ protected void VerifyCanWritePropertyName(string name) string.Format(SRResource.ObjectScopeNeededForPropertyNameWriting, name)); } } + + /// + /// Writes out a JsonSchema object + /// + /// + public void WriteJsonSchema(JsonSchema schema) + { + if (schema == null) + { + return; + } + + var reference = schema.GetRef(); + if (reference != null) + { + if (!Settings.ShouldInlineReference()) + { + WriteJsonSchemaReference(this, reference); + return; + } + else + { + if (Settings.InlineExternalReferences) + { + FindJsonSchemaRefs.ResolveJsonSchema(schema); + } + if (!Settings.LoopDetector.PushLoop(schema)) + { + Settings.LoopDetector.SaveLoop(schema); + WriteJsonSchemaReference(this, reference); + return; + } + } + } + + WriteJsonSchemaWithoutReference(this, schema); + + if (reference != null) + { + Settings.LoopDetector.PopLoop(); + } + } + + /// + public void WriteJsonSchemaWithoutReference(IOpenApiWriter writer, JsonSchema schema) + { + writer.WriteStartObject(); + + // title + writer.WriteProperty(OpenApiConstants.Title, schema.GetTitle()); + + // multipleOf + writer.WriteProperty(OpenApiConstants.MultipleOf, schema.GetMultipleOf()); + + // maximum + writer.WriteProperty(OpenApiConstants.Maximum, schema.GetMaximum()); + + // exclusiveMaximum + writer.WriteProperty(OpenApiConstants.ExclusiveMaximum, schema.GetOpenApiExclusiveMaximum()); + + // minimum + writer.WriteProperty(OpenApiConstants.Minimum, schema.GetMinimum()); + + // exclusiveMinimum + writer.WriteProperty(OpenApiConstants.ExclusiveMinimum, schema.GetOpenApiExclusiveMinimum()); + + // maxLength + writer.WriteProperty(OpenApiConstants.MaxLength, schema.GetMaxLength()); + + // minLength + writer.WriteProperty(OpenApiConstants.MinLength, schema.GetMinLength()); + + // pattern + writer.WriteProperty(OpenApiConstants.Pattern, schema.GetPattern()?.ToString()); + + // maxItems + writer.WriteProperty(OpenApiConstants.MaxItems, schema.GetMaxItems()); + + // minItems + writer.WriteProperty(OpenApiConstants.MinItems, schema.GetMinItems()); + + // uniqueItems + writer.WriteProperty(OpenApiConstants.UniqueItems, schema.GetUniqueItems()); + + // maxProperties + writer.WriteProperty(OpenApiConstants.MaxProperties, schema.GetMaxProperties()); + + // minProperties + writer.WriteProperty(OpenApiConstants.MinProperties, schema.GetMinProperties()); + + // required + writer.WriteOptionalCollection(OpenApiConstants.Required, schema.GetRequired(), (w, s) => w.WriteValue(s)); + + // enum + writer.WriteOptionalCollection(OpenApiConstants.Enum, schema.GetEnum(), (nodeWriter, s) => nodeWriter.WriteAny(new OpenApiAny(s))); + + // type + writer.WriteProperty(OpenApiConstants.Type, schema.GetJsonType()?.ToString().ToLowerInvariant()); + + // allOf + writer.WriteOptionalCollection(OpenApiConstants.AllOf, schema.GetAllOf(), (w, s) => w.WriteJsonSchema(s)); + + // anyOf + writer.WriteOptionalCollection(OpenApiConstants.AnyOf, schema.GetAnyOf(), (w, s) => w.WriteJsonSchema(s)); + + // oneOf + writer.WriteOptionalCollection(OpenApiConstants.OneOf, schema.GetOneOf(), (w, s) => w.WriteJsonSchema(s)); + + // not + writer.WriteOptionalObject(OpenApiConstants.Not, schema.GetNot(), (w, s) => w.WriteJsonSchema(s)); + + // items + writer.WriteOptionalObject(OpenApiConstants.Items, schema.GetItems(), (w, s) => w.WriteJsonSchema(s)); + + // properties + writer.WriteOptionalMap(OpenApiConstants.Properties, (IDictionary)schema.GetProperties(), + (w, key, s) => w.WriteJsonSchema(s)); + + // additionalProperties + if (schema.GetAdditionalPropertiesAllowed() ?? false) + { + writer.WriteOptionalObject( + OpenApiConstants.AdditionalProperties, + schema.GetAdditionalProperties(), + (w, s) => w.WriteJsonSchema(s)); + } + else + { + writer.WriteProperty(OpenApiConstants.AdditionalProperties, schema.GetAdditionalPropertiesAllowed()); + } + + // description + writer.WriteProperty(OpenApiConstants.Description, schema.GetDescription()); + + // format + writer.WriteProperty(OpenApiConstants.Format, schema.GetFormat()?.Key); + + // default + writer.WriteOptionalObject(OpenApiConstants.Default, schema.GetDefault(), (w, d) => w.WriteAny(new OpenApiAny(d))); + + // nullable + writer.WriteProperty(OpenApiConstants.Nullable, schema.GetNullable(), false); + + // discriminator + writer.WriteOptionalObject(OpenApiConstants.Discriminator, schema.GetOpenApiDiscriminator(), (w, d) => d.SerializeAsV3(w)); + + // readOnly + writer.WriteProperty(OpenApiConstants.ReadOnly, schema.GetReadOnly(), false); + + // writeOnly + writer.WriteProperty(OpenApiConstants.WriteOnly, schema.GetWriteOnly(), false); + + // xml + writer.WriteOptionalObject(OpenApiConstants.Xml, schema.GetXml(), (w, s) => JsonSerializer.Serialize(s)); + + // externalDocs + writer.WriteOptionalObject(OpenApiConstants.ExternalDocs, schema.GetExternalDocs(), (w, s) => JsonSerializer.Serialize(s)); + + // example + writer.WriteOptionalObject(OpenApiConstants.Example, schema.GetExample(), (w, e) => w.WriteAny(new OpenApiAny(e))); + + // deprecated + writer.WriteProperty(OpenApiConstants.Deprecated, schema.GetDeprecated(), false); + + // extensions + writer.WriteExtensions(schema.GetExtensions(), OpenApiSpecVersion.OpenApi3_0); + + writer.WriteEndObject(); + } + + /// + public void WriteJsonSchemaReference(IOpenApiWriter writer, Uri reference) + { + this.WriteStartObject(); + this.WriteProperty(OpenApiConstants.DollarRef, reference.OriginalString); + WriteEndObject(); + } + } + + internal class FindJsonSchemaRefs : OpenApiVisitorBase + { + public static void ResolveJsonSchema(JsonSchema schema) + { + var visitor = new FindJsonSchemaRefs(); + var walker = new OpenApiWalker(visitor); + walker.Walk(schema); + } } } diff --git a/src/Microsoft.OpenApi/Writers/OpenApiWriterExtensions.cs b/src/Microsoft.OpenApi/Writers/OpenApiWriterExtensions.cs index 537273cac..1ad2f224b 100644 --- a/src/Microsoft.OpenApi/Writers/OpenApiWriterExtensions.cs +++ b/src/Microsoft.OpenApi/Writers/OpenApiWriterExtensions.cs @@ -5,6 +5,7 @@ using System.Collections; using System.Collections.Generic; using System.Linq; +using Json.Schema; using Microsoft.OpenApi.Interfaces; namespace Microsoft.OpenApi.Writers @@ -138,7 +139,6 @@ public static void WriteOptionalObject( string name, T value, Action action) - where T : IOpenApiElement { if (value != null) { @@ -165,7 +165,6 @@ public static void WriteRequiredObject( string name, T value, Action action) - where T : IOpenApiElement { CheckArguments(writer, name, action); @@ -213,7 +212,6 @@ public static void WriteOptionalCollection( string name, IEnumerable elements, Action action) - where T : IOpenApiElement { if (elements != null && elements.Any()) { @@ -222,35 +220,34 @@ public static void WriteOptionalCollection( } /// - /// Write the required Open API object/element collection. + /// Write the required Open API element map (string to string mapping). /// - /// The Open API element type. /// The Open API writer. /// The property name. - /// The collection values. - /// The collection element writer action. - public static void WriteRequiredCollection( + /// The map values. + /// The map element writer action. + public static void WriteRequiredMap( this IOpenApiWriter writer, string name, - IEnumerable elements, - Action action) - where T : IOpenApiElement + IDictionary elements, + Action action) { - writer.WriteCollectionInternal(name, elements, action); + writer.WriteMapInternal(name, elements, action); } /// - /// Write the optional Open API element map (string to string mapping). + /// Write the optional Open API element map. /// + /// The Open API element type. /// The Open API writer. /// The property name. /// The map values. - /// The map element writer action. + /// The map element writer action with writer and value as input. public static void WriteOptionalMap( this IOpenApiWriter writer, string name, - IDictionary elements, - Action action) + IDictionary elements, + Action action) { if (elements != null && elements.Any()) { @@ -259,19 +256,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) { - writer.WriteMapInternal(name, elements, action); + if (elements != null && elements.Any()) + { + writer.WriteMapInternal(name, elements, action); + } } /// @@ -361,7 +361,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 cf00c1339..ee0c81b61 100644 --- a/src/Microsoft.OpenApi/Writers/OpenApiWriterSettings.cs +++ b/src/Microsoft.OpenApi/Writers/OpenApiWriterSettings.cs @@ -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 { - get { return referenceInline; } - set { + public ReferenceInlineSetting ReferenceInline + { + get { return referenceInline; } + set + { referenceInline = value; - switch(referenceInline) + switch (referenceInline) { case ReferenceInlineSetting.DoNotInlineReferences: InlineLocalReferences = false; @@ -75,5 +77,9 @@ internal bool ShouldInlineReference(OpenApiReference reference) || (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 47afdcc31..6ed8d0c86 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(); @@ -185,7 +185,7 @@ public override void WriteValue(string value) } Writer.Write("|"); - + WriteChompingIndicator(value); // Write indentation indicator when it starts with spaces @@ -193,7 +193,7 @@ public override void WriteValue(string value) { Writer.Write(IndentationString.Length); } - + Writer.WriteLine(); IncreaseIndentation(); @@ -207,7 +207,7 @@ public override void WriteValue(string value) firstLine = false; else Writer.WriteLine(); - + // Indentations for empty lines aren't needed. if (line.Length > 0) { diff --git a/test/Microsoft.OpenApi.Hidi.Tests/Services/OpenApiFilterServiceTests.cs b/test/Microsoft.OpenApi.Hidi.Tests/Services/OpenApiFilterServiceTests.cs index 176fb20d1..ec1722e76 100644 --- a/test/Microsoft.OpenApi.Hidi.Tests/Services/OpenApiFilterServiceTests.cs +++ b/test/Microsoft.OpenApi.Hidi.Tests/Services/OpenApiFilterServiceTests.cs @@ -1,8 +1,6 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. -using System; -using System.IO; using Microsoft.Extensions.Logging; using Microsoft.OpenApi.Hidi; using Microsoft.OpenApi.Models; diff --git a/test/Microsoft.OpenApi.Hidi.Tests/UtilityFiles/OpenApiDocumentMock.cs b/test/Microsoft.OpenApi.Hidi.Tests/UtilityFiles/OpenApiDocumentMock.cs index 27da46bfb..7ff07be0a 100644 --- a/test/Microsoft.OpenApi.Hidi.Tests/UtilityFiles/OpenApiDocumentMock.cs +++ b/test/Microsoft.OpenApi.Hidi.Tests/UtilityFiles/OpenApiDocumentMock.cs @@ -1,9 +1,8 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. -using System.Text.Json.Nodes; +using Json.Schema; using Microsoft.OpenApi.Any; -using Microsoft.OpenApi.Extensions; using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Models; @@ -85,10 +84,7 @@ public static OpenApiDocument CreateOpenApiDocument() Name = "period", In = ParameterLocation.Path, Required = true, - Schema = new OpenApiSchema() - { - Type = "string" - } + Schema = new JsonSchemaBuilder().Type(SchemaValueType.String) } } }, @@ -104,10 +100,7 @@ public static OpenApiDocument CreateOpenApiDocument() applicationJsonMediaType, new OpenApiMediaType { - Schema = new OpenApiSchema - { - Type = "array" - } + Schema = new JsonSchemaBuilder().Type(SchemaValueType.Array) } } } @@ -125,10 +118,7 @@ public static OpenApiDocument CreateOpenApiDocument() Name = "period", In = ParameterLocation.Path, Required = true, - Schema = new OpenApiSchema() - { - Type = "string" - } + Schema = new JsonSchemaBuilder().Type(SchemaValueType.String) } } } @@ -159,10 +149,7 @@ public static OpenApiDocument CreateOpenApiDocument() Name = "period", In = ParameterLocation.Path, Required = true, - Schema = new OpenApiSchema() - { - Type = "string" - } + Schema = new JsonSchemaBuilder().Type(SchemaValueType.String) } } }, @@ -178,10 +165,7 @@ public static OpenApiDocument CreateOpenApiDocument() applicationJsonMediaType, new OpenApiMediaType { - Schema = new OpenApiSchema - { - Type = "array" - } + Schema = new JsonSchemaBuilder().Type(SchemaValueType.Array) } } } @@ -198,12 +182,9 @@ public static OpenApiDocument CreateOpenApiDocument() Name = "period", In = ParameterLocation.Path, Required = true, - Schema = new OpenApiSchema() - { - Type = "string" - } + Schema = new JsonSchemaBuilder().Type(SchemaValueType.String) } - } + } }, ["/users"] = new OpenApiPathItem() { @@ -235,29 +216,17 @@ public static OpenApiDocument CreateOpenApiDocument() applicationJsonMediaType, new OpenApiMediaType { - Schema = new OpenApiSchema - { - Title = "Collection of user", - Type = "object", - Properties = new Dictionary - { - { - "value", - new OpenApiSchema - { - Type = "array", - Items = new OpenApiSchema - { - Reference = new OpenApiReference - { - Type = ReferenceType.Schema, - Id = "microsoft.graph.user" - } - } - } - } - } - } + Schema = new JsonSchemaBuilder() + .Title("Collection of user") + .Type(SchemaValueType.Object) + .Properties(("value", + new JsonSchemaBuilder() + .Type(SchemaValueType.Array) + .Items(new JsonSchemaBuilder() + .Ref("microsoft.graph.user") + .Build()) + .Build())) + .Build() } } } @@ -298,14 +267,7 @@ public static OpenApiDocument CreateOpenApiDocument() applicationJsonMediaType, new OpenApiMediaType { - Schema = new OpenApiSchema - { - Reference = new OpenApiReference - { - Type = ReferenceType.Schema, - Id = "microsoft.graph.user" - } - } + Schema = new JsonSchemaBuilder().Ref("microsoft.graph.user").Build() } } } @@ -368,10 +330,7 @@ public static OpenApiDocument CreateOpenApiDocument() In = ParameterLocation.Query, Required = true, Description = "Select properties to be returned", - Schema = new OpenApiSchema() - { - Type = "array" - } + Schema = new JsonSchemaBuilder().Type(SchemaValueType.Array).Build() // missing explode parameter } }, @@ -387,14 +346,7 @@ public static OpenApiDocument CreateOpenApiDocument() applicationJsonMediaType, new OpenApiMediaType { - Schema = new OpenApiSchema - { - Reference = new OpenApiReference - { - Type = ReferenceType.Schema, - Id = "microsoft.graph.message" - } - } + Schema = new JsonSchemaBuilder().Ref("microsoft.graph.message").Build() } } } @@ -432,10 +384,7 @@ public static OpenApiDocument CreateOpenApiDocument() In = ParameterLocation.Path, Required = true, Description = "key: id of administrativeUnit", - Schema = new OpenApiSchema() - { - Type = "string" - } + Schema = new JsonSchemaBuilder().Type(SchemaValueType.String).Build() } } }, @@ -451,17 +400,12 @@ public static OpenApiDocument CreateOpenApiDocument() applicationJsonMediaType, new OpenApiMediaType { - Schema = new OpenApiSchema - { - AnyOf = new List - { - new OpenApiSchema - { - Type = "string" - } - }, - Nullable = true - } + Schema = new JsonSchemaBuilder() + .AnyOf( + new JsonSchemaBuilder() + .Type(SchemaValueType.String) + .Build()) + .Build() } } } @@ -533,29 +477,14 @@ public static OpenApiDocument CreateOpenApiDocument() applicationJsonMediaType, new OpenApiMediaType { - Schema = new OpenApiSchema - { - Title = "Collection of hostSecurityProfile", - Type = "object", - Properties = new Dictionary - { - { - "value", - new OpenApiSchema - { - Type = "array", - Items = new OpenApiSchema - { - Reference = new OpenApiReference - { - Type = ReferenceType.Schema, - Id = "microsoft.graph.networkInterface" - } - } - } - } - } - } + Schema = new JsonSchemaBuilder() + .Title("Collection of hostSecurityProfile") + .Type(SchemaValueType.Object) + .Properties(("value1", + new JsonSchemaBuilder() + .Type(SchemaValueType.Array) + .Items(new JsonSchemaBuilder().Ref("microsoft.graph.networkInterface")))) + .Build() } } } @@ -592,10 +521,7 @@ public static OpenApiDocument CreateOpenApiDocument() In = ParameterLocation.Path, Description = "key: id of call", Required = true, - Schema = new OpenApiSchema() - { - Type = "string" - }, + Schema = new JsonSchemaBuilder().Type(SchemaValueType.String).Build(), Extensions = new Dictionary { { @@ -647,10 +573,7 @@ public static OpenApiDocument CreateOpenApiDocument() In = ParameterLocation.Path, Description = "key: id of group", Required = true, - Schema = new OpenApiSchema() - { - Type = "string" - }, + Schema = new JsonSchemaBuilder().Type(SchemaValueType.String).Build(), Extensions = new Dictionary { { "x-ms-docs-key-type", new OpenApiAny("group") } } }, new OpenApiParameter() @@ -659,10 +582,7 @@ public static OpenApiDocument CreateOpenApiDocument() In = ParameterLocation.Path, Description = "key: id of event", Required = true, - Schema = new OpenApiSchema() - { - Type = "string" - }, + Schema = new JsonSchemaBuilder().Type(SchemaValueType.String).Build(), Extensions = new Dictionary { { "x-ms-docs-key-type", new OpenApiAny("event") } } } }, @@ -678,15 +598,7 @@ public static OpenApiDocument CreateOpenApiDocument() applicationJsonMediaType, new OpenApiMediaType { - Schema = new OpenApiSchema - { - Type = "array", - Reference = new OpenApiReference - { - Type = ReferenceType.Schema, - Id = "microsoft.graph.event" - } - } + Schema = new JsonSchemaBuilder().Type(SchemaValueType.Array).Ref("microsoft.graph.event").Build() } } } @@ -726,25 +638,17 @@ public static OpenApiDocument CreateOpenApiDocument() }, Components = new OpenApiComponents { - Schemas = new Dictionary + Schemas = new Dictionary { { - "microsoft.graph.networkInterface", new OpenApiSchema - { - Title = "networkInterface", - Type = "object", - Properties = new Dictionary - { - { - "description", new OpenApiSchema - { - Type = "string", - Description = "Description of the NIC (e.g. Ethernet adapter, Wireless LAN adapter Local Area Connection <#>, etc.).", - Nullable = true - } - } - } - } + "microsoft.graph.networkInterface", new JsonSchemaBuilder() + .Title("networkInterface") + .Type(SchemaValueType.Object) + .Properties( + ("description", new JsonSchemaBuilder() + .Type(SchemaValueType.String) + .Description("Description of the NIC (e.g. Ethernet adapter, Wireless LAN adapter Local Area Connection <#>, etc.)."))) + .Build() } } } 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 cb54435f0..1e515051e 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 @@ - + net7.0 false @@ -125,10 +125,13 @@ Never - + Never - + + Never + + Never @@ -143,10 +146,13 @@ Never - + + Never + + Never - + Never @@ -179,7 +185,16 @@ Never - + + Never + + + Never + + + Never + + Never @@ -272,6 +287,10 @@ + + + + runtime; build; native; contentfiles; analyzers; buildtransitive all @@ -293,8 +312,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - - + @@ -339,6 +357,12 @@ Never + + Always + + + Always + PreserveNewest @@ -349,5 +373,9 @@ PreserveNewest + + + + \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Readers.Tests/OpenApiReaderTests/OpenApiStreamReaderTests.cs b/test/Microsoft.OpenApi.Readers.Tests/OpenApiReaderTests/OpenApiStreamReaderTests.cs index 7567e0b7d..d136f5b3e 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/OpenApiReaderTests/OpenApiStreamReaderTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/OpenApiReaderTests/OpenApiStreamReaderTests.cs @@ -26,7 +26,7 @@ public void StreamShouldNotCloseIfLeaveStreamOpenSettingEqualsTrue() { using (var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "petStore.yaml"))) { - var reader = new OpenApiStreamReader(new OpenApiReaderSettings { LeaveStreamOpen = true}); + var reader = new OpenApiStreamReader(new OpenApiReaderSettings { LeaveStreamOpen = true }); reader.Read(stream, out _); Assert.True(stream.CanRead); } diff --git a/test/Microsoft.OpenApi.Readers.Tests/OpenApiWorkspaceTests/OpenApiWorkspaceStreamTests.cs b/test/Microsoft.OpenApi.Readers.Tests/OpenApiWorkspaceTests/OpenApiWorkspaceStreamTests.cs index e79a6539d..0efd2ea60 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/OpenApiWorkspaceTests/OpenApiWorkspaceStreamTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/OpenApiWorkspaceTests/OpenApiWorkspaceStreamTests.cs @@ -2,6 +2,7 @@ using System.IO; using System.Linq; using System.Threading.Tasks; +using Json.Schema; using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Readers.Interface; using Xunit; @@ -52,35 +53,35 @@ public async Task LoadDocumentWithExternalReferenceShouldLoadBothDocumentsIntoWo { LoadExternalRefs = true, CustomExternalLoader = new ResourceLoader(), - BaseUrl = new Uri("fie://c:\\") + BaseUrl = new Uri("file://c:\\") }); ReadResult result; - using (var stream = Resources.GetStream("V3Tests/Samples/OpenApiWorkspace/TodoMain.yaml")) - { - result = await reader.ReadAsync(stream); - } + using var stream = Resources.GetStream("V3Tests/Samples/OpenApiWorkspace/TodoMain.yaml"); + result = await reader.ReadAsync(stream); + 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); + .Paths["/todos"] + .Operations[OperationType.Get] + .Responses["200"] + .Content["application/json"] + .Schema; + + var x = referencedSchema.GetProperties().TryGetValue("subject", out var schema); + Assert.Equal(SchemaValueType.Object, referencedSchema.GetJsonType()); + Assert.Equal(SchemaValueType.String, schema.GetJsonType()); var referencedParameter = result.OpenApiDocument - .Paths["/todos"] - .Operations[OperationType.Get] - .Parameters.Select(p => p.GetEffective(result.OpenApiDocument)) - .Where(p => p.Name == "filter").FirstOrDefault(); + .Paths["/todos"] + .Operations[OperationType.Get] + .Parameters.Select(p => p.GetEffective(result.OpenApiDocument)) + .FirstOrDefault(p => p.Name == "filter"); - Assert.Equal("string", referencedParameter.Schema.Type); + Assert.Equal(SchemaValueType.String, referencedParameter.Schema.GetJsonType()); } } @@ -97,7 +98,7 @@ public Task LoadAsync(Uri uri) return null; } } - + public class ResourceLoader : IStreamLoader { diff --git a/test/Microsoft.OpenApi.Readers.Tests/ParseNodeTests.cs b/test/Microsoft.OpenApi.Readers.Tests/ParseNodeTests.cs index fade1ba2c..fab39ae02 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/ParseNodeTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/ParseNodeTests.cs @@ -26,8 +26,8 @@ public void BrokenSimpleList() reader.Read(input, out var diagnostic); diagnostic.Errors.Should().BeEquivalentTo(new List() { - new OpenApiError(new OpenApiReaderException("Expected a value.")), - new OpenApiError("", "Paths is a REQUIRED field at #/") + new OpenApiError(new OpenApiReaderException("Expected a value.")), + new OpenApiError("", "Paths is a REQUIRED field at #/") }); } diff --git a/test/Microsoft.OpenApi.Readers.Tests/ReferenceService/ConvertToOpenApiReferenceV2Tests.cs b/test/Microsoft.OpenApi.Readers.Tests/ReferenceService/ConvertToOpenApiReferenceV2Tests.cs index bd9600e4f..d3e0a93e6 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/ReferenceService/ConvertToOpenApiReferenceV2Tests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/ReferenceService/ConvertToOpenApiReferenceV2Tests.cs @@ -10,7 +10,7 @@ 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/TryLoadReferenceV2Tests.cs b/test/Microsoft.OpenApi.Readers.Tests/ReferenceService/TryLoadReferenceV2Tests.cs index a641b7d6f..afe76580b 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/ReferenceService/TryLoadReferenceV2Tests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/ReferenceService/TryLoadReferenceV2Tests.cs @@ -1,15 +1,11 @@ // 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 FluentAssertions; +using Json.Schema; using Microsoft.OpenApi.Models; -using Microsoft.OpenApi.Readers.ParseNodes; -using Microsoft.OpenApi.Readers.V2; -using SharpYaml.Serialization; using Xunit; namespace Microsoft.OpenApi.Readers.Tests.ReferenceService @@ -19,71 +15,15 @@ public class TryLoadReferenceV2Tests { private const string SampleFolderPath = "ReferenceService/Samples/"; - [Fact] - public void LoadSchemaReference() - { - // 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 OpenApiSchema - { - Type = "integer", - Format = "int64" - }, - ["name"] = new OpenApiSchema - { - Type = "string" - }, - ["tag"] = new OpenApiSchema - { - Type = "string" - } - }, - Reference = new OpenApiReference - { - Type = ReferenceType.Schema, - Id = "SampleObject" - } - } - ); - } - [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); + document = new OpenApiStreamReader().Read(stream, out var diagnostic); } var reference = new OpenApiReference @@ -103,11 +43,9 @@ public void LoadParameterReference() In = ParameterLocation.Query, Description = "number of items to skip", Required = true, - Schema = new OpenApiSchema - { - Type = "integer", - Format = "int32" - }, + Schema = new JsonSchemaBuilder() + .Type(SchemaValueType.Integer) + .Format("int32"), Reference = new OpenApiReference { Type = ReferenceType.Parameter, @@ -122,11 +60,10 @@ 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); + document = new OpenApiStreamReader().Read(stream, out var diagnostic); } var reference = new OpenApiReference @@ -159,11 +96,10 @@ public void LoadResponseReference() { // 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); + document = new OpenApiStreamReader().Read(stream, out var diagnostic); } var reference = new OpenApiReference @@ -198,11 +134,10 @@ 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); + document = new OpenApiStreamReader().Read(stream, out var diagnostic); } var reference = new OpenApiReference @@ -223,28 +158,12 @@ public void LoadResponseAndSchemaReference() { ["application/json"] = new OpenApiMediaType { - Schema = new OpenApiSchema - { - Description = "Sample description", - Required = new HashSet {"name" }, - Properties = { - ["name"] = new OpenApiSchema() - { - Type = "string" - }, - ["tag"] = new OpenApiSchema() - { - Type = "string" - } - }, - - Reference = new OpenApiReference - { - Type = ReferenceType.Schema, - Id = "SampleObject2", - HostDocument = document - } - } + Schema = new JsonSchemaBuilder() + .Description("Sample description") + .Required("name") + .Properties( + ("name", new JsonSchemaBuilder().Type(SchemaValueType.String)), + ("tag", new JsonSchemaBuilder().Type(SchemaValueType.String))) } }, Reference = new OpenApiReference diff --git a/test/Microsoft.OpenApi.Readers.Tests/Resources.cs b/test/Microsoft.OpenApi.Readers.Tests/Resources.cs index 4278a4a4b..895e1ed3f 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/Resources.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/Resources.cs @@ -29,7 +29,7 @@ public static string GetString(string fileName) public static Stream GetStream(string fileName) { string path = GetPath(fileName); - Stream stream = typeof(Resources).Assembly.GetManifestResourceStream(path); + Stream stream = typeof(Resources).Assembly.GetManifestResourceStream(path); if (stream == null) { diff --git a/test/Microsoft.OpenApi.Readers.Tests/TestCustomExtension.cs b/test/Microsoft.OpenApi.Readers.Tests/TestCustomExtension.cs index 9312720c1..e58c01180 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/TestCustomExtension.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/TestCustomExtension.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. -using System.Text.Json; using System.Text.Json.Nodes; using FluentAssertions; using Microsoft.OpenApi.Interfaces; diff --git a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiSchemaTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/JsonSchemaTests.cs similarity index 56% rename from test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiSchemaTests.cs rename to test/Microsoft.OpenApi.Readers.Tests/V2Tests/JsonSchemaTests.cs index 1c719f120..301932c14 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiSchemaTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/JsonSchemaTests.cs @@ -3,8 +3,8 @@ using System.IO; using FluentAssertions; -using Microsoft.OpenApi.Any; -using Microsoft.OpenApi.Models; +using Json.Schema; +using Json.Schema.OpenApi; using Microsoft.OpenApi.Readers.ParseNodes; using Microsoft.OpenApi.Readers.V2; using Xunit; @@ -12,7 +12,7 @@ namespace Microsoft.OpenApi.Readers.Tests.V2Tests { [Collection("DefaultSettings")] - public class OpenApiSchemaTests + public class JsonSchemaTests { private const string SampleFolderPath = "V2Tests/Samples/OpenApiSchema/"; @@ -30,14 +30,9 @@ public void ParseSchemaWithDefaultShouldSucceed() var schema = OpenApiV2Deserializer.LoadSchema(node); // Assert - schema.Should().BeEquivalentTo( - new OpenApiSchema - { - Type = "number", - Format = "float", - Default = new OpenApiAny(5) - }, options => options.IgnoringCyclicReferences() - .Excluding(schema => schema.Default.Node.Parent)); + schema.Should().BeEquivalentTo(new JsonSchemaBuilder() + .Type(SchemaValueType.Number).Format("float").Default(5).Build(), + options => options.IgnoringCyclicReferences()); } [Fact] @@ -55,13 +50,12 @@ public void ParseSchemaWithExampleShouldSucceed() // Assert schema.Should().BeEquivalentTo( - new OpenApiSchema - { - Type = "number", - Format = "float", - Example = new OpenApiAny(5) - }, options => options.IgnoringCyclicReferences() - .Excluding(schema => schema.Example.Node.Parent)); + new JsonSchemaBuilder() + .Type(SchemaValueType.Number) + .Format("float") + .Example(5) + .Build(), + options => options.IgnoringCyclicReferences()); } [Fact] @@ -78,21 +72,13 @@ public void ParseSchemaWithEnumShouldSucceed() var schema = OpenApiV2Deserializer.LoadSchema(node); // Assert - schema.Should().BeEquivalentTo( - new OpenApiSchema - { - Type = "number", - Format = "float", - Enum = - { - new OpenApiAny(7), - new OpenApiAny(8), - new OpenApiAny(9) - } - }, options => options.IgnoringCyclicReferences() - .Excluding(s => s.Enum[0].Node.Parent) - .Excluding(s => s.Enum[1].Node.Parent) - .Excluding(s => s.Enum[2].Node.Parent)); + var expected = new JsonSchemaBuilder() + .Type(SchemaValueType.Number) + .Format("float") + .Enum(7, 8, 9) + .Build(); + schema.Should().BeEquivalentTo(expected, + options => options.IgnoringCyclicReferences()); } } } diff --git a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiContactTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiContactTests.cs index 71489d39f..1a29081fe 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiContactTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiContactTests.cs @@ -1,9 +1,9 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. +using System; using FluentAssertions; using Microsoft.OpenApi.Models; -using System; using Xunit; namespace Microsoft.OpenApi.Readers.Tests.V2Tests diff --git a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiDocumentTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiDocumentTests.cs index 984c4cdcd..66ff8fabc 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiDocumentTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiDocumentTests.cs @@ -2,13 +2,10 @@ // Licensed under the MIT license. using System.Collections.Generic; -using System.Globalization; using System.IO; -using System.Threading; using FluentAssertions; -using Microsoft.OpenApi.Any; +using Json.Schema; using Microsoft.OpenApi.Exceptions; -using Microsoft.OpenApi.Extensions; using Microsoft.OpenApi.Models; using Xunit; @@ -20,149 +17,6 @@ public class OpenApiDocumentTests { private const string SampleFolderPath = "V2Tests/Samples/"; - - - - [Fact] - public void ShouldThrowWhenReferenceTypeIsInvalid() - { - 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 OpenApiError( 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 OpenApiError( new OpenApiException("Invalid Reference identifier 'doesnotexist'.")) }); - doc.Should().NotBeNull(); - } - - [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 CultureInfo(culture); - Thread.CurrentThread.CurrentUICulture = new CultureInfo(culture); - - var openApiDoc = new OpenApiStringReader().Read( - @" -swagger: 2.0 -info: - title: Simple Document - version: 0.9.1 - x-extension: 2.335 -definitions: - sampleSchema: - type: object - properties: - sampleProperty: - type: double - minimum: 100.54 - maximum: 60000000.35 - exclusiveMaximum: true - exclusiveMinimum: false -paths: {}", - out var context); - - var extension = (OpenApiAny)openApiDoc.Info.Extensions["x-extension"]; - - openApiDoc.Should().BeEquivalentTo( - new OpenApiDocument - { - Info = new OpenApiInfo - { - Title = "Simple Document", - Version = "0.9.1", - Extensions = - { - ["x-extension"] = new OpenApiAny(2.335) - } - }, - Components = new OpenApiComponents() - { - Schemas = - { - ["sampleSchema"] = new OpenApiSchema() - { - Type = "object", - Properties = - { - ["sampleProperty"] = new OpenApiSchema() - { - Type = "double", - Minimum = (decimal)100.54, - Maximum = (decimal)60000000.35, - ExclusiveMaximum = true, - ExclusiveMinimum = false - } - }, - Reference = new OpenApiReference() - { - Id = "sampleSchema", - Type = ReferenceType.Schema - } - } - } - }, - Paths = new OpenApiPaths() - }, options => options.IgnoringCyclicReferences() - .Excluding(doc => ((OpenApiAny)doc.Info.Extensions["x-extension"]).Node.Parent)); - - context.Should().BeEquivalentTo( - new OpenApiDiagnostic() - { - SpecificationVersion = OpenApiSpecVersion.OpenApi2_0, - Errors = new List() - { - new OpenApiError("", "Paths is a REQUIRED field at #/") - } - }); - } - [Fact] public void ShouldParseProducesInAnyOrder() { @@ -171,81 +25,22 @@ public void ShouldParseProducesInAnyOrder() var reader = new OpenApiStreamReader(); var doc = reader.Read(stream, out var diagnostic); - var successSchema = new OpenApiSchema() - { - Type = "array", - Reference = new OpenApiReference - { - Type = ReferenceType.Schema, - Id = "Item", - HostDocument = doc - }, - Items = new OpenApiSchema() - { - Reference = new OpenApiReference() - { - Type = ReferenceType.Schema, - Id = "Item", - HostDocument = doc - } - } - }; + var successSchema = new JsonSchemaBuilder() + .Type(SchemaValueType.Array) + .Items(new JsonSchemaBuilder() + .Ref("#/definitions/Item")); - var okSchema = new OpenApiSchema() - { - Reference = new OpenApiReference - { - Type = ReferenceType.Schema, - Id = "Item", - HostDocument = doc - }, - Properties = new Dictionary() - { - { "id", new OpenApiSchema() - { - Type = "string", - Description = "Item identifier." - } - } - } - }; + var okSchema = new JsonSchemaBuilder() + .Properties(("id", new JsonSchemaBuilder().Type(SchemaValueType.String).Description("Item identifier."))); - var errorSchema = new OpenApiSchema() - { - Reference = new OpenApiReference - { - Type = ReferenceType.Schema, - Id = "Error", - HostDocument = doc - }, - Properties = new Dictionary() - { - { "code", new OpenApiSchema() - { - Type = "integer", - Format = "int32" - } - }, - { "message", new OpenApiSchema() - { - Type = "string" - } - }, - { "fields", new OpenApiSchema() - { - Type = "string" - } - } - } - }; + var errorSchema = new JsonSchemaBuilder() + .Properties(("code", new JsonSchemaBuilder().Type(SchemaValueType.Integer).Format("int32")), + ("message", new JsonSchemaBuilder().Type(SchemaValueType.String)), + ("fields", new JsonSchemaBuilder().Type(SchemaValueType.String))); var okMediaType = new OpenApiMediaType { - Schema = new OpenApiSchema - { - Type = "array", - Items = okSchema - } + Schema = new JsonSchemaBuilder().Type(SchemaValueType.Array).Items(okSchema) }; var errorMediaType = new OpenApiMediaType @@ -370,54 +165,18 @@ public void ShouldAssignSchemaToAllResponses() Assert.Equal(OpenApiSpecVersion.OpenApi2_0, diagnostic.SpecificationVersion); - var successSchema = new OpenApiSchema - { - Type = "array", - Items = new OpenApiSchema - { - Properties = { - { "id", new OpenApiSchema - { - Type = "string", - Description = "Item identifier." - } - } - }, - Reference = new OpenApiReference - { - 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 OpenApiReference - { - Id = "Error", - Type = ReferenceType.Schema, - HostDocument = document - } - }; + var successSchema = new JsonSchemaBuilder() + .Type(SchemaValueType.Array) + .Items(new JsonSchemaBuilder() + .Properties(("id", new JsonSchemaBuilder().Type(SchemaValueType.String).Description("Item identifier.")))) + .Build(); + + var errorSchema = new JsonSchemaBuilder() + .Properties(("code", new JsonSchemaBuilder().Type(SchemaValueType.Integer).Format("int32")), + ("message", new JsonSchemaBuilder().Type(SchemaValueType.String)), + ("fields", new JsonSchemaBuilder().Type(SchemaValueType.String))) + .Build(); + var responses = document.Paths["/items"].Operations[OperationType.Get].Responses; foreach (var response in responses) { @@ -437,18 +196,19 @@ public void ShouldAssignSchemaToAllResponses() [Fact] public void ShouldAllowComponentsThatJustContainAReference() { - using (var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "ComponentRootReference.json"))) + // Arrange + using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "ComponentRootReference.json")); + OpenApiStreamReader reader = new OpenApiStreamReader(); + + // Act + OpenApiDocument doc = reader.Read(stream, out OpenApiDiagnostic diags); + JsonSchema schema = doc.Components.Schemas["AllPets"]; + + // Assert + if (schema.GetRef() != null) { - OpenApiStreamReader reader = new OpenApiStreamReader(); - OpenApiDocument doc = reader.Read(stream, out OpenApiDiagnostic diags); - OpenApiSchema schema1 = doc.Components.Schemas["AllPets"]; - Assert.False(schema1.UnresolvedReference); - OpenApiSchema schema2 = doc.ResolveReferenceTo(schema1.Reference); - if (schema2.UnresolvedReference && schema1.Reference.Id == schema2.Reference.Id) - { - // detected a cycle - this code gets triggered - Assert.True(false, "A cycle should not be detected"); - } + // detected a cycle - this code gets triggered + Assert.Fail("A cycle should not be detected"); } } } diff --git a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiHeaderTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiHeaderTests.cs index 129dccfa5..9d6e80788 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiHeaderTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiHeaderTests.cs @@ -2,9 +2,8 @@ // Licensed under the MIT license. using System.IO; -using System.Linq; using FluentAssertions; -using Microsoft.OpenApi.Any; +using Json.Schema; using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Readers.ParseNodes; using Microsoft.OpenApi.Readers.V2; @@ -34,16 +33,13 @@ public void ParseHeaderWithDefaultShouldSucceed() header.Should().BeEquivalentTo( new OpenApiHeader { - Schema = new OpenApiSchema() - { - Type = "number", - Format = "float", - Default = new OpenApiAny(5) - } - }, + Schema = new JsonSchemaBuilder() + .Type(SchemaValueType.Number) + .Format("float") + .Default(5) + }, options => options - .IgnoringCyclicReferences() - .Excluding(header => header.Schema.Default.Node.Parent)); + .IgnoringCyclicReferences()); } [Fact] @@ -58,27 +54,16 @@ public void ParseHeaderWithEnumShouldSucceed() // Act var header = OpenApiV2Deserializer.LoadHeader(node); - var parent = header.Schema.Enum.Select(e => e.Node.Parent); - + // Assert header.Should().BeEquivalentTo( new OpenApiHeader { - Schema = new OpenApiSchema() - { - Type = "number", - Format = "float", - Enum = - { - new OpenApiAny(7), - new OpenApiAny(8), - new OpenApiAny(9) - } - } - }, options => options.IgnoringCyclicReferences() - .Excluding(header => header.Schema.Enum[0].Node.Parent) - .Excluding(header => header.Schema.Enum[1].Node.Parent) - .Excluding(header => header.Schema.Enum[2].Node.Parent)); + Schema = new JsonSchemaBuilder() + .Type(SchemaValueType.Number) + .Format("float") + .Enum(7, 8, 9) + }, options => options.IgnoringCyclicReferences()); } } } diff --git a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiOperationTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiOperationTests.cs index 43f8caaa5..384d103fb 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiOperationTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiOperationTests.cs @@ -6,13 +6,13 @@ using System.Text; using System.Text.Json.Nodes; using FluentAssertions; +using Json.Schema; using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Extensions; using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Readers.ParseNodes; using Microsoft.OpenApi.Readers.V2; using Xunit; -using static System.Net.Mime.MediaTypeNames; namespace Microsoft.OpenApi.Readers.Tests.V2Tests { @@ -34,10 +34,7 @@ public class OpenApiOperationTests In = ParameterLocation.Path, Description = "ID of pet that needs to be updated", Required = true, - Schema = new OpenApiSchema - { - Type = "string" - } + Schema = new JsonSchemaBuilder().Type(SchemaValueType.String) } }, Responses = new OpenApiResponses @@ -68,10 +65,8 @@ public class OpenApiOperationTests In = ParameterLocation.Path, Description = "ID of pet that needs to be updated", Required = true, - Schema = new OpenApiSchema - { - Type = "string" - } + Schema = new JsonSchemaBuilder() + .Type(SchemaValueType.String) } }, RequestBody = new OpenApiRequestBody @@ -80,49 +75,19 @@ public class OpenApiOperationTests { ["application/x-www-form-urlencoded"] = new OpenApiMediaType { - Schema = new OpenApiSchema - { - Properties = - { - ["name"] = new OpenApiSchema - { - Description = "Updated name of the pet", - Type = "string" - }, - ["status"] = new OpenApiSchema - { - Description = "Updated status of the pet", - Type = "string" - } - }, - Required = new HashSet - { - "name" - } - } + Schema = new JsonSchemaBuilder() + .Properties( + ("name", new JsonSchemaBuilder().Description("Updated name of the pet").Type(SchemaValueType.String)), + ("status", new JsonSchemaBuilder().Description("Updated status of the pet").Type(SchemaValueType.String))) + .Required("name") }, ["multipart/form-data"] = new OpenApiMediaType { - Schema = new OpenApiSchema - { - Properties = - { - ["name"] = new OpenApiSchema - { - Description = "Updated name of the pet", - Type = "string" - }, - ["status"] = new OpenApiSchema - { - Description = "Updated status of the pet", - Type = "string" - } - }, - Required = new HashSet - { - "name" - } - } + Schema = new JsonSchemaBuilder() + .Properties( + ("name", new JsonSchemaBuilder().Description("Updated name of the pet").Type(SchemaValueType.String)), + ("status", new JsonSchemaBuilder().Description("Updated status of the pet").Type(SchemaValueType.String))) + .Required("name") } } }, @@ -163,10 +128,7 @@ public class OpenApiOperationTests In = ParameterLocation.Path, Description = "ID of pet that needs to be updated", Required = true, - Schema = new OpenApiSchema - { - Type = "string" - } + Schema = new JsonSchemaBuilder().Type(SchemaValueType.String) }, }, RequestBody = new OpenApiRequestBody @@ -177,13 +139,10 @@ public class OpenApiOperationTests { ["application/json"] = new OpenApiMediaType { - Schema = new OpenApiSchema - { - Type = "object" - } + Schema = new JsonSchemaBuilder().Type(SchemaValueType.Object) } }, - Extensions = { + Extensions = { [OpenApiConstants.BodyName] = new OpenApiAny("petObject") } }, @@ -246,41 +205,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() { @@ -342,15 +266,9 @@ public void ParseOperationWithResponseExamplesShouldSucceed() { ["application/json"] = new OpenApiMediaType() { - Schema = new OpenApiSchema() - { - Type = "array", - Items = new OpenApiSchema() - { - Type = "number", - Format = "float" - } - }, + Schema = new JsonSchemaBuilder() + .Type(SchemaValueType.Array) + .Items(new JsonSchemaBuilder().Type(SchemaValueType.Number).Format("float")), Example = new OpenApiAny(new JsonArray() { 5.0, @@ -360,15 +278,9 @@ public void ParseOperationWithResponseExamplesShouldSucceed() }, ["application/xml"] = new OpenApiMediaType() { - Schema = new OpenApiSchema() - { - Type = "array", - Items = new OpenApiSchema() - { - Type = "number", - Format = "float" - } - } + Schema = new JsonSchemaBuilder() + .Type(SchemaValueType.Array) + .Items(new JsonSchemaBuilder().Type(SchemaValueType.Number).Format("float")) } } }} diff --git a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiParameterTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiParameterTests.cs index f34aa7c74..cb29e7876 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiParameterTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiParameterTests.cs @@ -1,11 +1,9 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// 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 Microsoft.OpenApi.Any; +using Json.Schema; using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Readers.ParseNodes; using Microsoft.OpenApi.Readers.V2; @@ -58,10 +56,8 @@ public void ParsePathParameterShouldSucceed() Name = "username", Description = "username to fetch", Required = true, - Schema = new OpenApiSchema - { - Type = "string" - } + Schema = new JsonSchemaBuilder() + .Type(SchemaValueType.String) }); } @@ -86,111 +82,14 @@ public void ParseQueryParameterShouldSucceed() Name = "id", Description = "ID of the object to fetch", Required = false, - Schema = new OpenApiSchema - { - Type = "array", - Items = new OpenApiSchema - { - Type = "string" - } - }, + Schema = new JsonSchemaBuilder() + .Type(SchemaValueType.Array) + .Items(new JsonSchemaBuilder().Type(SchemaValueType.String)), Style = ParameterStyle.Form, Explode = true }); } - [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 OpenApiSchema - { - Type = "array", - Items = new OpenApiSchema - { - Type = "integer", - Format = "int64", - Enum = new List - { - new OpenApiAny(1), - new OpenApiAny(2), - new OpenApiAny(3), - new OpenApiAny(4) - } - }, - Default = new OpenApiAny(new JsonArray() { - 1, - 2 - }), - Enum = new List - { - new OpenApiAny(new JsonArray() { 1, 2 }), - new OpenApiAny(new JsonArray() { 2, 3 }), - new OpenApiAny(new JsonArray() { 3, 4 }) - } - } - }, options => options.IgnoringCyclicReferences() - .Excluding(p => p.Schema.Default.Node[0].Root) - .Excluding(p => p.Schema.Default.Node[0].Parent) - .Excluding(p => p.Schema.Default.Node[1].Parent) - .Excluding(p => p.Schema.Default.Node[1].Root) - .Excluding(p => p.Schema.Items.Enum[0].Node.Parent) - .Excluding(p => p.Schema.Items.Enum[1].Node.Parent) - .Excluding(p => p.Schema.Items.Enum[2].Node.Parent) - .Excluding(p => p.Schema.Items.Enum[3].Node.Parent) - .Excluding(p => p.Schema.Enum[0].Node[0].Parent) - .Excluding(p => p.Schema.Enum[0].Node[0].Root) - .Excluding(p => p.Schema.Enum[0].Node[1].Parent) - .Excluding(p => p.Schema.Enum[0].Node[1].Root) - .Excluding(p => p.Schema.Enum[1].Node[0].Parent) - .Excluding(p => p.Schema.Enum[1].Node[0].Root) - .Excluding(p => p.Schema.Enum[1].Node[1].Parent) - .Excluding(p => p.Schema.Enum[1].Node[1].Root) - .Excluding(p => p.Schema.Enum[2].Node[0].Parent) - .Excluding(p => p.Schema.Enum[2].Node[0].Root) - .Excluding(p => p.Schema.Enum[2].Node[1].Parent) - .Excluding(p => p.Schema.Enum[2].Node[1].Root) - ); - } - [Fact] public void ParseParameterWithNullLocationShouldSucceed() { @@ -212,10 +111,7 @@ public void ParseParameterWithNullLocationShouldSucceed() Name = "username", Description = "username to fetch", Required = true, - Schema = new OpenApiSchema - { - Type = "string" - } + Schema = new JsonSchemaBuilder().Type(SchemaValueType.String) }); } @@ -240,10 +136,7 @@ public void ParseParameterWithNoLocationShouldSucceed() Name = "username", Description = "username to fetch", Required = true, - Schema = new OpenApiSchema - { - Type = "string" - } + Schema = new JsonSchemaBuilder().Type(SchemaValueType.String) }); } @@ -292,10 +185,7 @@ public void ParseParameterWithUnknownLocationShouldSucceed() Name = "username", Description = "username to fetch", Required = true, - Schema = new OpenApiSchema - { - Type = "string" - } + Schema = new JsonSchemaBuilder().Type(SchemaValueType.String) }); } @@ -320,14 +210,8 @@ public void ParseParameterWithDefaultShouldSucceed() Name = "username", Description = "username to fetch", Required = true, - Schema = new OpenApiSchema - { - Type = "number", - Format = "float", - Default = new OpenApiAny(5) - } - }, options => options.IgnoringCyclicReferences() - .Excluding(p => p.Schema.Default.Node.Parent)); + Schema = new JsonSchemaBuilder().Type(SchemaValueType.Number).Format("float").Default(5) + }, options => options.IgnoringCyclicReferences()); } [Fact] @@ -351,21 +235,8 @@ public void ParseParameterWithEnumShouldSucceed() Name = "username", Description = "username to fetch", Required = true, - Schema = new OpenApiSchema - { - Type = "number", - Format = "float", - Enum = - { - new OpenApiAny(7), - new OpenApiAny(8), - new OpenApiAny(9) - } - } - }, options => options.IgnoringCyclicReferences() - .Excluding(p => p.Schema.Enum[0].Node.Parent) - .Excluding(p => p.Schema.Enum[1].Node.Parent) - .Excluding(p => p.Schema.Enum[2].Node.Parent)); + Schema = new JsonSchemaBuilder().Type(SchemaValueType.Number).Format("float").Enum(7, 8, 9) + }, options => options.IgnoringCyclicReferences()); } } } diff --git a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiPathItemTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiPathItemTests.cs index a11497cdf..07bfab17d 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiPathItemTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiPathItemTests.cs @@ -4,9 +4,8 @@ using System.Collections.Generic; using System.IO; using System.Linq; -using System.Text; using FluentAssertions; -using Microsoft.OpenApi.Extensions; +using Json.Schema; using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Readers.ParseNodes; using Microsoft.OpenApi.Readers.V2; @@ -29,14 +28,7 @@ public class OpenApiPathItemTests In = ParameterLocation.Path, Description = "ID of pet to use", Required = true, - Schema = new OpenApiSchema() - { - Type = "array", - Items = new OpenApiSchema() - { - Type = "string" - } - }, + Schema = new JsonSchemaBuilder().Type(SchemaValueType.Array).Items(new JsonSchemaBuilder().Type(SchemaValueType.String)), Style = ParameterStyle.Simple } }, @@ -55,10 +47,7 @@ public class OpenApiPathItemTests In = ParameterLocation.Path, Description = "ID of pet that needs to be updated", Required = true, - Schema = new OpenApiSchema - { - Type = "string" - } + Schema = new JsonSchemaBuilder().Type(SchemaValueType.String) } }, RequestBody = new OpenApiRequestBody @@ -67,49 +56,19 @@ public class OpenApiPathItemTests { ["application/x-www-form-urlencoded"] = new OpenApiMediaType { - Schema = new OpenApiSchema - { - Properties = - { - ["name"] = new OpenApiSchema - { - Description = "Updated name of the pet", - Type = "string" - }, - ["status"] = new OpenApiSchema - { - Description = "Updated status of the pet", - Type = "string" - } - }, - Required = new HashSet - { - "name" - } - } + Schema = new JsonSchemaBuilder() + .Properties( + ("name", new JsonSchemaBuilder().Description("Updated name of the pet").Type(SchemaValueType.String)), + ("status", new JsonSchemaBuilder().Description("Updated status of the pet").Type(SchemaValueType.String))) + .Required("name") }, ["multipart/form-data"] = new OpenApiMediaType { - Schema = new OpenApiSchema - { - Properties = - { - ["name"] = new OpenApiSchema - { - Description = "Updated name of the pet", - Type = "string" - }, - ["status"] = new OpenApiSchema - { - Description = "Updated status of the pet", - Type = "string" - } - }, - Required = new HashSet - { - "name" - } - } + Schema = new JsonSchemaBuilder() + .Properties( + ("name", new JsonSchemaBuilder().Description("Updated name of the pet").Type(SchemaValueType.String)), + ("status", new JsonSchemaBuilder().Description("Updated status of the pet").Type(SchemaValueType.String))) + .Required("name") } } }, @@ -148,10 +107,7 @@ public class OpenApiPathItemTests In = ParameterLocation.Path, Description = "ID of pet that needs to be updated", Required = true, - Schema = new OpenApiSchema - { - Type = "string" - } + Schema = new JsonSchemaBuilder().Type(SchemaValueType.String) }, new OpenApiParameter { @@ -159,10 +115,7 @@ public class OpenApiPathItemTests In = ParameterLocation.Path, Description = "Name of pet that needs to be updated", Required = true, - Schema = new OpenApiSchema - { - Type = "string" - } + Schema = new JsonSchemaBuilder().Type(SchemaValueType.String) } }, RequestBody = new OpenApiRequestBody @@ -171,59 +124,21 @@ public class OpenApiPathItemTests { ["application/x-www-form-urlencoded"] = new OpenApiMediaType { - Schema = new OpenApiSchema - { - Properties = - { - ["name"] = new OpenApiSchema - { - Description = "Updated name of the pet", - Type = "string" - }, - ["status"] = new OpenApiSchema - { - Description = "Updated status of the pet", - Type = "string" - }, - ["skill"] = new OpenApiSchema - { - Description = "Updated skill of the pet", - Type = "string" - } - }, - Required = new HashSet - { - "name" - } - } + Schema = new JsonSchemaBuilder() + .Properties( + ("name", new JsonSchemaBuilder().Description("Updated name of the pet").Type(SchemaValueType.String)), + ("status", new JsonSchemaBuilder().Description("Updated status of the pet").Type(SchemaValueType.String)), + ("skill", new JsonSchemaBuilder().Description("Updated skill of the pet").Type(SchemaValueType.String))) + .Required("name") }, ["multipart/form-data"] = new OpenApiMediaType { - Schema = new OpenApiSchema - { - Properties = - { - ["name"] = new OpenApiSchema - { - Description = "Updated name of the pet", - Type = "string" - }, - ["status"] = new OpenApiSchema - { - Description = "Updated status of the pet", - Type = "string" - }, - ["skill"] = new OpenApiSchema - { - Description = "Updated skill of the pet", - Type = "string" - } - }, - Required = new HashSet - { - "name" - } - } + Schema = new JsonSchemaBuilder() + .Properties( + ("name", new JsonSchemaBuilder().Description("Updated name of the pet").Type(SchemaValueType.String)), + ("status", new JsonSchemaBuilder().Description("Updated status of the pet").Type(SchemaValueType.String)), + ("skill", new JsonSchemaBuilder().Description("Updated skill of the pet").Type(SchemaValueType.String))) + .Required("name") } } }, diff --git a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiServerTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiServerTests.cs index e7016a795..bf07205a8 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiServerTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiServerTests.cs @@ -1,11 +1,7 @@ -using FluentAssertions; -using Microsoft.OpenApi.Exceptions; -using Microsoft.OpenApi.Models; -using System; -using System.Collections.Generic; +using System; using System.Linq; -using System.Text; -using System.Threading.Tasks; +using FluentAssertions; +using Microsoft.OpenApi.Models; using Xunit; namespace Microsoft.OpenApi.Readers.Tests.V2Tests @@ -311,7 +307,7 @@ public void InvalidHostShouldYieldError() Errors = { new OpenApiError("#/", "Invalid host"), - new OpenApiError("", "Paths is a REQUIRED field at #/") + new OpenApiError("", "Paths is a REQUIRED field at #/") }, SpecificationVersion = OpenApiSpecVersion.OpenApi2_0 }); diff --git a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/JsonSchemaTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/JsonSchemaTests.cs new file mode 100644 index 000000000..23cb8c2d7 --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/JsonSchemaTests.cs @@ -0,0 +1,177 @@ +using System.IO; +using System.Linq; +using System.Text.Json; +using FluentAssertions; +using Json.Schema; +using Microsoft.OpenApi.Readers.ParseNodes; +using Microsoft.OpenApi.Readers.V31; +using SharpYaml.Serialization; +using Xunit; + +namespace Microsoft.OpenApi.Readers.Tests.V31Tests +{ + public class JsonSchemaTests + { + private const string SampleFolderPath = "V31Tests/Samples/OpenApiSchema/"; + + [Fact] + public void ParseV31SchemaShouldSucceed() + { + using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "schema.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 schema = OpenApiV31Deserializer.LoadSchema(node); + var jsonString = @"{ + ""type"": ""object"", + ""properties"": { + ""one"": { + ""description"": ""type array"", + ""type"": [ + ""integer"", + ""string"" + ] + } + } +}"; + var expectedSchema = JsonSerializer.Deserialize(jsonString); + + // Assert + Assert.Equal(schema, expectedSchema); + } + + [Fact] + public void ParseAdvancedV31SchemaShouldSucceed() + { + using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "advancedSchema.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 schema = OpenApiV31Deserializer.LoadSchema(node); + var jsonString = @"{ + ""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"" + } + } +}"; + var expectedSchema = JsonSerializer.Deserialize(jsonString); + + // Assert + schema.Should().BeEquivalentTo(expectedSchema); + } + + [Fact] + public void ParseStandardSchemaExampleSucceeds() + { + // Arrange + var builder = new JsonSchemaBuilder(); + var myschema = builder.Title("My Schema") + .Description("A schema for testing") + .Type(SchemaValueType.Object) + .Properties( + ("name", + new JsonSchemaBuilder() + .Type(SchemaValueType.String) + .Description("The name of the person")), + ("age", + new JsonSchemaBuilder() + .Type(SchemaValueType.Integer) + .Description("The age of the person"))) + .Build(); + + // Act + var title = myschema.Get().Value; + var description = myschema.Get().Value; + var nameProperty = myschema.Get().Properties["name"]; + + // Assert + Assert.Equal("My Schema", title); + Assert.Equal("A schema for testing", description); + } + } + + public static class SchemaExtensions + { + public static T Get(this JsonSchema schema) + { + return (T)schema.Keywords.FirstOrDefault(x => x is T); + } + } +} 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..89100c4aa --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiDocumentTests.cs @@ -0,0 +1,361 @@ +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using FluentAssertions; +using Json.Schema; +using Microsoft.OpenApi.Interfaces; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Writers; +using Xunit; +using static System.Net.Mime.MediaTypeNames; + +namespace Microsoft.OpenApi.Readers.Tests.V31Tests +{ + public class OpenApiDocumentTests + { + private const string SampleFolderPath = "V31Tests/Samples/OpenApiDocument/"; + + public static 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 OpenApiJsonWriterSettings() + { + InlineLocalReferences = true + }); + element.SerializeAsV31(writer); + writer.Flush(); + stream.Position = 0; + + using var streamReader = new StreamReader(stream); + var result = streamReader.ReadToEnd(); + return new OpenApiStringReader().ReadFragment(result, OpenApiSpecVersion.OpenApi3_1, out OpenApiDiagnostic diagnostic4); + } + + [Fact] + public void ParseDocumentWithWebhooksShouldSucceed() + { + // Arrange and Act + using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "documentWithWebhooks.yaml")); + var actual = new OpenApiStreamReader().Read(stream, out var diagnostic); + + var petSchema = new JsonSchemaBuilder() + .Type(SchemaValueType.Object) + .Required("id", "name") + .Properties( + ("id", new JsonSchemaBuilder() + .Type(SchemaValueType.Integer) + .Format("int64")), + ("name", new JsonSchemaBuilder() + .Type(SchemaValueType.String) + ), + ("tag", new JsonSchemaBuilder().Type(SchemaValueType.String)) + ); + + var newPetSchema = new JsonSchemaBuilder() + .Type(SchemaValueType.Object) + .Required("name") + .Properties( + ("id", new JsonSchemaBuilder() + .Type(SchemaValueType.Integer) + .Format("int64")), + ("name", new JsonSchemaBuilder() + .Type(SchemaValueType.String) + ), + ("tag", new JsonSchemaBuilder().Type(SchemaValueType.String)) + ); + + var components = new OpenApiComponents + { + Schemas = + { + ["pet1"] = petSchema, + ["newPet"] = newPetSchema + } + }; + + 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 JsonSchemaBuilder() + .Type(SchemaValueType.Array) + .Items(new JsonSchemaBuilder() + .Type(SchemaValueType.String) + ) + }, + new OpenApiParameter + { + Name = "limit", + In = ParameterLocation.Query, + Description = "maximum number of results to return", + Required = false, + Schema = new JsonSchemaBuilder() + .Type(SchemaValueType.Integer).Format("int32") + } + }, + Responses = new OpenApiResponses + { + ["200"] = new OpenApiResponse + { + Description = "pet response", + Content = new Dictionary + { + ["application/json"] = new OpenApiMediaType + { + Schema = new JsonSchemaBuilder() + .Type(SchemaValueType.Array) + .Items(petSchema) + + }, + ["application/xml"] = new OpenApiMediaType + { + Schema = new JsonSchemaBuilder() + .Type(SchemaValueType.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 + var schema = actual.Webhooks["/pets"].Operations[OperationType.Get].Responses["200"].Content["application/json"].Schema; + diagnostic.Should().BeEquivalentTo(new OpenApiDiagnostic() { SpecificationVersion = OpenApiSpecVersion.OpenApi3_1 }); + actual.Should().BeEquivalentTo(expected); + } + + [Fact] + public void ParseDocumentsWithReusablePathItemInWebhooksSucceeds() + { + // Arrange && Act + using var stream = Resources.GetStream("V31Tests/Samples/OpenApiDocument/documentWithReusablePaths.yaml"); + var actual = new OpenApiStreamReader().Read(stream, out var context); + + var components = new OpenApiComponents + { + Schemas = new Dictionary + { + ["petSchema"] = new JsonSchemaBuilder() + .Type(SchemaValueType.Object) + .Required("id", "name") + .Properties( + ("id", new JsonSchemaBuilder().Type(SchemaValueType.Integer).Format("int64")), + ("name", new JsonSchemaBuilder().Type(SchemaValueType.String)), + ("tag", new JsonSchemaBuilder().Type(SchemaValueType.String))), + ["newPet"] = new JsonSchemaBuilder() + .Type(SchemaValueType.Object) + .Required("name") + .Properties( + ("id", new JsonSchemaBuilder().Type(SchemaValueType.Integer).Format("int64")), + ("name", new JsonSchemaBuilder().Type(SchemaValueType.String)), + ("tag", new JsonSchemaBuilder().Type(SchemaValueType.String))) + } + }; + + // Create a clone of the schema to avoid modifying things in components. + var petSchema = components.Schemas["petSchema"]; + var newPetSchema = components.Schemas["newPet"]; + + 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 JsonSchemaBuilder() + .Type(SchemaValueType.Array) + .Items(new JsonSchemaBuilder().Type(SchemaValueType.String)) + }, + new OpenApiParameter + { + Name = "limit", + In = ParameterLocation.Query, + Description = "maximum number of results to return", + Required = false, + Schema = new JsonSchemaBuilder() + .Type(SchemaValueType.Integer).Format("int32") + } + }, + Responses = new OpenApiResponses + { + ["200"] = new OpenApiResponse + { + Description = "pet response", + Content = new Dictionary + { + ["application/json"] = new OpenApiMediaType + { + Schema = new JsonSchemaBuilder() + .Type(SchemaValueType.Array) + .Items(petSchema) + }, + ["application/xml"] = new OpenApiMediaType + { + Schema = new JsonSchemaBuilder() + .Type(SchemaValueType.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 + }, + } + } + } + } + }, + Reference = new OpenApiReference + { + Type = ReferenceType.PathItem, + Id = "/pets", + HostDocument = actual + } + } + }; + + var expected = new OpenApiDocument + { + Info = new OpenApiInfo + { + Title = "Webhook Example", + Version = "1.0.0" + }, + JsonSchemaDialect = "http://json-schema.org/draft-07/schema#", + Webhooks = components.PathItems, + Components = components + }; + + // Assert + actual.Should().BeEquivalentTo(expected); + context.Should().BeEquivalentTo( + new OpenApiDiagnostic() { SpecificationVersion = OpenApiSpecVersion.OpenApi3_1 }); + } + + [Fact] + public void ParseDocumentWithDescriptionInDollarRefsShouldSucceed() + { + // Arrange + using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "documentWithSummaryAndDescriptionInReference.yaml")); + + // Act + var actual = new OpenApiStreamReader().Read(stream, out var diagnostic); + var schema = actual.Paths["/pets"].Operations[OperationType.Get].Responses["200"].Content["application/json"].Schema; + var header = actual.Components.Responses["Test"].Headers["X-Test"]; + + // Assert + Assert.True(header.Description == "A referenced X-Test header"); /*response header #ref's description overrides the header's description*/ + Assert.Null(schema.GetRef()); + Assert.Equal(SchemaValueType.Object, schema.GetJsonType()); + Assert.Equal("A pet in a petstore", schema.GetDescription()); /*The reference object's description overrides that of the referenced component*/ + } + + [Fact] + public void ParseDocumentWithExampleInSchemaShouldSucceed() + { + // Arrange + using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "docWithExample.yaml")); + var outputStringWriter = new StringWriter(CultureInfo.InvariantCulture); + var writer = new OpenApiJsonWriter(outputStringWriter, new OpenApiJsonWriterSettings { Terse = false }); + // Act + var actual = new OpenApiStreamReader().Read(stream, out var diagnostic); + actual.SerializeAsV31(writer); + + // Assert + Assert.NotNull(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..6ca93a780 --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiInfoTests.cs @@ -0,0 +1,57 @@ +using System; +using System.IO; +using System.Linq; +using FluentAssertions; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Readers.ParseNodes; +using Microsoft.OpenApi.Readers.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/V3Tests/OpenApiLicenseTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiLicenseTests.cs similarity index 82% rename from test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiLicenseTests.cs rename to test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiLicenseTests.cs index 7d60c2766..4b1cbdbf1 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiLicenseTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiLicenseTests.cs @@ -1,21 +1,21 @@ // 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.Readers.ParseNodes; -using Microsoft.OpenApi.Readers.V3; +using Microsoft.OpenApi.Readers.V31; using SharpYaml.Serialization; -using System.IO; using Xunit; -using System.Linq; -using FluentAssertions; -namespace Microsoft.OpenApi.Readers.Tests.V3Tests +namespace Microsoft.OpenApi.Readers.Tests.V31Tests { public class OpenApiLicenseTests { - private const string SampleFolderPath = "V3Tests/Samples/OpenApiLicense/"; + private const string SampleFolderPath = "V31Tests/Samples/OpenApiLicense/"; [Fact] public void ParseLicenseWithSpdxIdentifierShouldSucceed() @@ -30,9 +30,9 @@ public void ParseLicenseWithSpdxIdentifierShouldSucceed() var asJsonNode = yamlNode.ToJsonNode(); var node = new MapNode(context, asJsonNode); - + // Act - var license = OpenApiV3Deserializer.LoadLicense(node); + var license = OpenApiV31Deserializer.LoadLicense(node); // Assert license.Should().BeEquivalentTo( 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..51ffd38b3 --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiDocument/docWithExample.yaml @@ -0,0 +1,106 @@ +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 + example: File uploaded successfully +components: # Reusable components for the API + schemas: # JSON Schema definitions for the API + User: # A schema for a user object + $id: http://example.com/schemas/user # The identifier for the schema + type: object + properties: + name: # A property for the user name + type: string + default: "John Doe" # The default value for the user name + age: # A property for the user age + type: integer + minimum: 0 + default: 18 # The default value for the user age + unevaluatedProperties: false # No additional properties are allowed + Pet: # A schema for a pet object + type: object + required: + - 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/V3Tests/Samples/OpenApiDocument/documentWithReusablePaths.yaml b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiDocument/documentWithReusablePaths.yaml similarity index 91% rename from test/Microsoft.OpenApi.Readers.Tests/V3Tests/Samples/OpenApiDocument/documentWithReusablePaths.yaml rename to test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiDocument/documentWithReusablePaths.yaml index de2f05420..f9327910b 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/Samples/OpenApiDocument/documentWithReusablePaths.yaml +++ b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiDocument/documentWithReusablePaths.yaml @@ -8,7 +8,7 @@ webhooks: "$ref": '#/components/pathItems/pets' components: schemas: - pet: + petSchema: type: object required: - id @@ -62,12 +62,12 @@ components: schema: type: array items: - "$ref": '#/components/schemas/pet' + "$ref": '#/components/schemas/petSchema' application/xml: schema: type: array items: - "$ref": '#/components/schemas/pet' + "$ref": '#/components/schemas/petSchema' post: requestBody: description: Information about a new pet in the system @@ -82,4 +82,4 @@ components: content: application/json: schema: - $ref: '#/components/schemas/pet' \ No newline at end of file + $ref: '#/components/schemas/petSchema' \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/Samples/OpenApiDocument/documentWithSummaryAndDescriptionInReference.yaml b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiDocument/documentWithSummaryAndDescriptionInReference.yaml similarity index 100% rename from test/Microsoft.OpenApi.Readers.Tests/V3Tests/Samples/OpenApiDocument/documentWithSummaryAndDescriptionInReference.yaml rename to test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiDocument/documentWithSummaryAndDescriptionInReference.yaml diff --git a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/Samples/OpenApiDocument/documentWithWebhooks.yaml b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiDocument/documentWithWebhooks.yaml similarity index 91% rename from test/Microsoft.OpenApi.Readers.Tests/V3Tests/Samples/OpenApiDocument/documentWithWebhooks.yaml rename to test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiDocument/documentWithWebhooks.yaml index 189835344..11c389157 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/Samples/OpenApiDocument/documentWithWebhooks.yaml +++ b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiDocument/documentWithWebhooks.yaml @@ -31,12 +31,12 @@ webhooks: 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' post: requestBody: description: Information about a new pet in the system @@ -51,10 +51,10 @@ webhooks: content: application/json: schema: - $ref: '#/components/schemas/pet' + $ref: '#/components/schemas/pet1' components: schemas: - pet: + pet1: type: object required: - id 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/V3Tests/Samples/OpenApiLicense/licenseWithSpdxIdentifier.yaml b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiLicense/licenseWithSpdxIdentifier.yaml similarity index 100% rename from test/Microsoft.OpenApi.Readers.Tests/V3Tests/Samples/OpenApiLicense/licenseWithSpdxIdentifier.yaml rename to test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiLicense/licenseWithSpdxIdentifier.yaml 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/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/V3Tests/JsonSchemaTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/JsonSchemaTests.cs new file mode 100644 index 000000000..7d81a8601 --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/JsonSchemaTests.cs @@ -0,0 +1,344 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text.Json.Nodes; +using FluentAssertions; +using Json.Schema; +using Json.Schema.OpenApi; +using Microsoft.OpenApi.Any; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Extensions; +using Microsoft.OpenApi.Readers.ParseNodes; +using Microsoft.OpenApi.Readers.V3; +using SharpYaml.Serialization; +using Xunit; + +namespace Microsoft.OpenApi.Readers.Tests.V3Tests +{ + [Collection("DefaultSettings")] + public class JsonSchemaTests + { + private const string SampleFolderPath = "V3Tests/Samples/OpenApiSchema/"; + + [Fact] + public void ParsePrimitiveSchemaShouldSucceed() + { + using (var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "primitiveSchema.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 schema = OpenApiV3Deserializer.LoadSchema(node); + + // Assert + diagnostic.Should().BeEquivalentTo(new OpenApiDiagnostic()); + + schema.Should().BeEquivalentTo( + new JsonSchemaBuilder() + .Type(SchemaValueType.String) + .Format("email") + .Build()); + } + } + + [Fact] + public void ParseExampleStringFragmentShouldSucceed() + { + var input = @" +{ + ""foo"": ""bar"", + ""baz"": [ 1,2] +}"; + var reader = new OpenApiStringReader(); + var diagnostic = new OpenApiDiagnostic(); + + // Act + var openApiAny = reader.ReadFragment(input, OpenApiSpecVersion.OpenApi3_0, out diagnostic); + + // Assert + diagnostic.Should().BeEquivalentTo(new OpenApiDiagnostic()); + + openApiAny.Should().BeEquivalentTo(new OpenApiAny( + new JsonObject + { + ["foo"] = "bar", + ["baz"] = new JsonArray() { 1, 2 } + }), options => options.IgnoringCyclicReferences()); + } + + [Fact] + public void ParseEnumFragmentShouldSucceed() + { + var input = @" +[ + ""foo"", + ""baz"" +]"; + var reader = new OpenApiStringReader(); + var diagnostic = new OpenApiDiagnostic(); + + // Act + var openApiAny = reader.ReadFragment(input, OpenApiSpecVersion.OpenApi3_0, out diagnostic); + + // Assert + diagnostic.Should().BeEquivalentTo(new OpenApiDiagnostic()); + + openApiAny.Should().BeEquivalentTo(new OpenApiAny( + new JsonArray + { + "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 diagnostic = new OpenApiDiagnostic(); + + // Act + var openApiAny = reader.ReadFragment(input, OpenApiSpecVersion.OpenApi3_0, out diagnostic); + + // Assert + diagnostic.Should().BeEquivalentTo(new OpenApiDiagnostic()); + + openApiAny.Should().BeEquivalentTo( + new OpenApiPathItem + { + Summary = "externally referenced path item", + Operations = new Dictionary + { + [OperationType.Get] = new OpenApiOperation() + { + Responses = new OpenApiResponses + { + ["200"] = new OpenApiResponse + { + Description = "Ok" + } + } + } + } + }); + } + + [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; + + var diagnostic = new OpenApiDiagnostic(); + var context = new ParsingContext(diagnostic); + + var asJsonNode = yamlNode.ToJsonNode(); + var node = new MapNode(context, asJsonNode); + + // Act + var schema = OpenApiV3Deserializer.LoadSchema(node); + + // Assert + diagnostic.Should().BeEquivalentTo(new OpenApiDiagnostic()); + + schema.Should().BeEquivalentTo( + new JsonSchemaBuilder() + .Type(SchemaValueType.Object) + .AdditionalProperties(new JsonSchemaBuilder().Type(SchemaValueType.String)) + .Build()); + } + } + + [Fact] + public void ParseBasicSchemaWithExampleShouldSucceed() + { + using (var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "basicSchemaWithExample.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 schema = OpenApiV3Deserializer.LoadSchema(node); + + // Assert + diagnostic.Should().BeEquivalentTo(new OpenApiDiagnostic()); + + schema.Should().BeEquivalentTo( + new JsonSchemaBuilder() + .Type(SchemaValueType.Object) + .Properties( + ("id", new JsonSchemaBuilder().Type(SchemaValueType.Integer).Format("int64")), + ("name", new JsonSchemaBuilder().Type(SchemaValueType.String))) + .Required("name") + .Example(new JsonObject { ["name"] = "Puma", ["id"] = 1 }) + .Build(), + options => options.IgnoringCyclicReferences()); + } + } + + [Fact] + public void ParseBasicSchemaWithReferenceShouldSucceed() + { + using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "basicSchemaWithReference.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, + Errors = new List() + { + new OpenApiError("", "Paths is a REQUIRED field at #/") + } + }); + + components.Should().BeEquivalentTo( + new OpenApiComponents + { + Schemas = + { + ["ErrorModel"] = new JsonSchemaBuilder() + .Type(SchemaValueType.Object) + .Required("message", "code") + .Properties( + ("message", new JsonSchemaBuilder().Type(SchemaValueType.String)), + ("code", new JsonSchemaBuilder().Type(SchemaValueType.Integer).Minimum(100).Maximum(600))), + ["ExtendedErrorModel"] = new JsonSchemaBuilder() + .AllOf( + new JsonSchemaBuilder() + .Type(SchemaValueType.Object) + .Properties( + ("code", new JsonSchemaBuilder().Type(SchemaValueType.Integer).Minimum(100).Maximum(600)), + ("message", new JsonSchemaBuilder().Type(SchemaValueType.String))) + .Required("message", "code"), + new JsonSchemaBuilder() + .Type(SchemaValueType.Object) + .Required("rootCause") + .Properties(("rootCause", new JsonSchemaBuilder().Type(SchemaValueType.String)))) + } + }, + options => options.Excluding(m => m.Name == "HostDocument") + .IgnoringCyclicReferences()); + } + + [Fact] + public void ParseAdvancedSchemaWithReferenceShouldSucceed() + { + using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "advancedSchemaWithReference.yaml")); + // Act + var openApiDoc = new OpenApiStreamReader().Read(stream, out var diagnostic); + + var expectedComponents = new OpenApiComponents + { + Schemas = + { + ["Pet1"] = new JsonSchemaBuilder() + .Type(SchemaValueType.Object) + .Discriminator(new OpenApiDiscriminator { PropertyName = "petType" }) + .Properties( + ("name", new JsonSchemaBuilder() + .Type(SchemaValueType.String) + ), + ("petType", new JsonSchemaBuilder() + .Type(SchemaValueType.String) + ) + ) + .Required("name", "petType"), + ["Cat"] = new JsonSchemaBuilder() + .Description("A representation of a cat") + .AllOf( + new JsonSchemaBuilder() + .Type(SchemaValueType.Object) + .Discriminator(new OpenApiDiscriminator { PropertyName = "petType" }) + .Properties( + ("name", new JsonSchemaBuilder() + .Type(SchemaValueType.String) + ), + ("petType", new JsonSchemaBuilder() + .Type(SchemaValueType.String) + ) + ) + .Required("name", "petType"), + new JsonSchemaBuilder() + .Type(SchemaValueType.Object) + .Required("huntingSkill") + .Properties( + ("huntingSkill", new JsonSchemaBuilder() + .Type(SchemaValueType.String) + .Description("The measured skill for hunting") + .Enum("clueless", "lazy", "adventurous", "aggressive") + ) + ) + ), + ["Dog"] = new JsonSchemaBuilder() + .Description("A representation of a dog") + .AllOf( + new JsonSchemaBuilder() + .Type(SchemaValueType.Object) + .Discriminator(new OpenApiDiscriminator { PropertyName = "petType" }) + .Properties( + ("name", new JsonSchemaBuilder() + .Type(SchemaValueType.String) + ), + ("petType", new JsonSchemaBuilder() + .Type(SchemaValueType.String) + ) + ) + .Required("name", "petType"), + new JsonSchemaBuilder() + .Type(SchemaValueType.Object) + .Required("packSize") + .Properties( + ("packSize", new JsonSchemaBuilder() + .Type(SchemaValueType.Integer) + .Format("int32") + .Description("the size of the pack the dog is from") + .Default(0) + .Minimum(0) + ) + ) + ) + } + }; + + // We serialize so that we can get rid of the schema BaseUri properties which show up as diffs + var actual = openApiDoc.Components.SerializeAsYaml(OpenApiSpecVersion.OpenApi3_0); + var expected = expectedComponents.SerializeAsYaml(OpenApiSpecVersion.OpenApi3_0); + + // Assert + actual.Should().Be(expected); + } + } +} diff --git a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiCallbackTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiCallbackTests.cs index b8e975ad0..540f620a3 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiCallbackTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiCallbackTests.cs @@ -4,6 +4,7 @@ using System.IO; using System.Linq; using FluentAssertions; +using Json.Schema; using Microsoft.OpenApi.Expressions; using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Readers.ParseNodes; @@ -23,7 +24,7 @@ public void ParseBasicCallbackShouldSucceed() { // Arrange using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "basicCallback.yaml")); - var yamlStream = new YamlStream(); + var yamlStream = new YamlStream(); yamlStream.Load(new StreamReader(stream)); var yamlNode = yamlStream.Documents.First().RootNode; @@ -107,10 +108,7 @@ public void ParseCallbackWithReferenceShouldSucceed() { ["application/json"] = new OpenApiMediaType { - Schema = new OpenApiSchema() - { - Type = "object" - } + Schema = new JsonSchemaBuilder().Type(SchemaValueType.Object) } } }, @@ -166,10 +164,7 @@ public void ParseMultipleCallbacksWithReferenceShouldSucceed() { ["application/json"] = new OpenApiMediaType { - Schema = new OpenApiSchema() - { - Type = "object" - } + Schema = new JsonSchemaBuilder().Type(SchemaValueType.Object) } } }, @@ -208,10 +203,7 @@ public void ParseMultipleCallbacksWithReferenceShouldSucceed() { ["application/json"] = new OpenApiMediaType { - Schema = new OpenApiSchema() - { - Type = "string" - } + Schema = new JsonSchemaBuilder().Type(SchemaValueType.String) } } }, @@ -243,10 +235,7 @@ public void ParseMultipleCallbacksWithReferenceShouldSucceed() { ["application/xml"] = new OpenApiMediaType { - Schema = new OpenApiSchema() - { - Type = "object" - } + Schema = new JsonSchemaBuilder().Type(SchemaValueType.Object) } } }, diff --git a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiContactTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiContactTests.cs index be78f942b..1cb948427 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiContactTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiContactTests.cs @@ -1,9 +1,9 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. +using System; using FluentAssertions; using Microsoft.OpenApi.Models; -using System; using Xunit; namespace Microsoft.OpenApi.Readers.Tests.V3Tests diff --git a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiDocumentTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiDocumentTests.cs index 254a37ef9..590a7b9b4 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiDocumentTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiDocumentTests.cs @@ -3,13 +3,11 @@ using System; using System.Collections.Generic; -using System.Diagnostics.Contracts; using System.Globalization; using System.IO; using System.Linq; -using System.Text; -using System.Threading; using FluentAssertions; +using Json.Schema; using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Models; @@ -103,86 +101,7 @@ public void ParseDocumentFromInlineStringShouldSucceed() context.Should().BeEquivalentTo( new OpenApiDiagnostic() - { - SpecificationVersion = OpenApiSpecVersion.OpenApi3_0, - Errors = new List() - { - new OpenApiError("", "Paths is a REQUIRED field at #/") - } - }); - } - - [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 CultureInfo(culture); - Thread.CurrentThread.CurrentUICulture = new CultureInfo(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 { - Info = new OpenApiInfo - { - Title = "Simple Document", - Version = "0.9.1" - }, - Components = new OpenApiComponents() - { - Schemas = - { - ["sampleSchema"] = new OpenApiSchema() - { - Type = "object", - Properties = - { - ["sampleProperty"] = new OpenApiSchema() - { - Type = "double", - Minimum = (decimal)100.54, - Maximum = (decimal)60000000.35, - ExclusiveMaximum = true, - ExclusiveMinimum = false - } - }, - Reference = new OpenApiReference() - { - Id = "sampleSchema", - Type = ReferenceType.Schema - } - } - } - }, - Paths = new OpenApiPaths() - }); - - context.Should().BeEquivalentTo( - new OpenApiDiagnostic() - { SpecificationVersion = OpenApiSpecVersion.OpenApi3_0, Errors = new List() { @@ -200,7 +119,7 @@ public void ParseBasicDocumentWithMultipleServersShouldSucceed() diagnostic.Should().BeEquivalentTo( new OpenApiDiagnostic() - { + { SpecificationVersion = OpenApiSpecVersion.OpenApi3_0, Errors = new List() { @@ -282,7 +201,7 @@ public void ParseMinimalDocumentShouldSucceed() diagnostic.Should().BeEquivalentTo( new OpenApiDiagnostic() - { + { SpecificationVersion = OpenApiSpecVersion.OpenApi3_0, Errors = new List() { @@ -302,126 +221,36 @@ public void ParseStandardPetStoreDocumentShouldSucceed() var components = new OpenApiComponents { - Schemas = new Dictionary + Schemas = new Dictionary { - ["pet"] = new OpenApiSchema - { - Type = "object", - Required = new HashSet - { - "id", - "name" - }, - Properties = new Dictionary - { - ["id"] = new OpenApiSchema - { - Type = "integer", - Format = "int64" - }, - ["name"] = new OpenApiSchema - { - Type = "string" - }, - ["tag"] = new OpenApiSchema - { - Type = "string" - }, - }, - Reference = new OpenApiReference - { - Type = ReferenceType.Schema, - Id = "pet", - HostDocument = actual - } - }, - ["newPet"] = new OpenApiSchema - { - Type = "object", - Required = new HashSet - { - "name" - }, - Properties = new Dictionary - { - ["id"] = new OpenApiSchema - { - Type = "integer", - Format = "int64" - }, - ["name"] = new OpenApiSchema - { - Type = "string" - }, - ["tag"] = new OpenApiSchema - { - Type = "string" - }, - }, - Reference = new OpenApiReference - { - Type = ReferenceType.Schema, - Id = "newPet", - HostDocument = actual - } - }, - ["errorModel"] = new OpenApiSchema - { - Type = "object", - Required = new HashSet - { - "code", - "message" - }, - Properties = new Dictionary - { - ["code"] = new OpenApiSchema - { - Type = "integer", - Format = "int32" - }, - ["message"] = new OpenApiSchema - { - Type = "string" - } - }, - Reference = new OpenApiReference - { - Type = ReferenceType.Schema, - Id = "errorModel", - HostDocument = actual - } - }, + ["pet"] = new JsonSchemaBuilder() + .Type(SchemaValueType.Object) + .Required("id", "name") + .Properties( + ("id", new JsonSchemaBuilder().Type(SchemaValueType.Integer).Format("int64")), + ("name", new JsonSchemaBuilder().Type(SchemaValueType.String)), + ("tag", new JsonSchemaBuilder().Type(SchemaValueType.String))), + ["newPet"] = new JsonSchemaBuilder() + .Type(SchemaValueType.Object) + .Required("name") + .Properties( + ("id", new JsonSchemaBuilder().Type(SchemaValueType.Integer).Format("int64")), + ("name", new JsonSchemaBuilder().Type(SchemaValueType.String)), + ("tag", new JsonSchemaBuilder().Type(SchemaValueType.String))), + ["errorModel"] = new JsonSchemaBuilder() + .Type(SchemaValueType.Object) + .Required("code", "message") + .Properties( + ("code", new JsonSchemaBuilder().Type(SchemaValueType.Integer).Format("int32")), + ("message", new JsonSchemaBuilder().Type(SchemaValueType.String))) } }; - // Create a clone of the schema to avoid modifying things in components. - var petSchema = Clone(components.Schemas["pet"]); + var petSchema = components.Schemas["pet"]; - petSchema.Reference = new OpenApiReference - { - Id = "pet", - Type = ReferenceType.Schema, - HostDocument = actual - }; + var newPetSchema = components.Schemas["newPet"]; - var newPetSchema = Clone(components.Schemas["newPet"]); - - newPetSchema.Reference = new OpenApiReference - { - Id = "newPet", - Type = ReferenceType.Schema, - HostDocument = actual - }; - - var errorModelSchema = Clone(components.Schemas["errorModel"]); - - errorModelSchema.Reference = new OpenApiReference - { - Id = "errorModel", - Type = ReferenceType.Schema, - HostDocument = actual - }; + var errorModelSchema = components.Schemas["errorModel"]; var expected = new OpenApiDocument { @@ -469,14 +298,9 @@ public void ParseStandardPetStoreDocumentShouldSucceed() In = ParameterLocation.Query, Description = "tags to filter by", Required = false, - Schema = new OpenApiSchema - { - Type = "array", - Items = new OpenApiSchema - { - Type = "string" - } - } + Schema = new JsonSchemaBuilder() + .Type(SchemaValueType.Array) + .Items(new JsonSchemaBuilder().Type(SchemaValueType.String)) }, new OpenApiParameter { @@ -484,11 +308,7 @@ public void ParseStandardPetStoreDocumentShouldSucceed() In = ParameterLocation.Query, Description = "maximum number of results to return", Required = false, - Schema = new OpenApiSchema - { - Type = "integer", - Format = "int32" - } + Schema = new JsonSchemaBuilder().Type(SchemaValueType.Integer).Format("int32").Build() } }, Responses = new OpenApiResponses @@ -500,19 +320,11 @@ public void ParseStandardPetStoreDocumentShouldSucceed() { ["application/json"] = new OpenApiMediaType { - Schema = new OpenApiSchema - { - Type = "array", - Items = petSchema - } + Schema = new JsonSchemaBuilder().Type(SchemaValueType.Array).Items(petSchema) }, ["application/xml"] = new OpenApiMediaType { - Schema = new OpenApiSchema - { - Type = "array", - Items = petSchema - } + Schema = new JsonSchemaBuilder().Type(SchemaValueType.Array).Items(petSchema) } } }, @@ -552,8 +364,7 @@ public void ParseStandardPetStoreDocumentShouldSucceed() { ["application/json"] = new OpenApiMediaType { - Schema = newPetSchema - } + Schema = newPetSchema } } }, Responses = new OpenApiResponses @@ -612,11 +423,7 @@ public void ParseStandardPetStoreDocumentShouldSucceed() In = ParameterLocation.Path, Description = "ID of pet to fetch", Required = true, - Schema = new OpenApiSchema - { - Type = "integer", - Format = "int64" - } + Schema = new JsonSchemaBuilder().Type(SchemaValueType.Integer).Format("int64") } }, Responses = new OpenApiResponses @@ -672,11 +479,7 @@ public void ParseStandardPetStoreDocumentShouldSucceed() In = ParameterLocation.Path, Description = "ID of pet to delete", Required = true, - Schema = new OpenApiSchema - { - Type = "integer", - Format = "int64" - } + Schema = new JsonSchemaBuilder().Type(SchemaValueType.Integer).Format("int64").Build() } }, Responses = new OpenApiResponses @@ -732,95 +535,28 @@ public void ParseModifiedPetStoreDocumentWithTagAndSecurityShouldSucceed() var components = new OpenApiComponents { - Schemas = new Dictionary + Schemas = new Dictionary { - ["pet"] = new OpenApiSchema - { - Type = "object", - Required = new HashSet - { - "id", - "name" - }, - Properties = new Dictionary - { - ["id"] = new OpenApiSchema - { - Type = "integer", - Format = "int64" - }, - ["name"] = new OpenApiSchema - { - Type = "string" - }, - ["tag"] = new OpenApiSchema - { - Type = "string" - }, - }, - Reference = new OpenApiReference - { - Type = ReferenceType.Schema, - Id = "pet", - HostDocument = actual - } - }, - ["newPet"] = new OpenApiSchema - { - Type = "object", - Required = new HashSet - { - "name" - }, - Properties = new Dictionary - { - ["id"] = new OpenApiSchema - { - Type = "integer", - Format = "int64" - }, - ["name"] = new OpenApiSchema - { - Type = "string" - }, - ["tag"] = new OpenApiSchema - { - Type = "string" - }, - }, - Reference = new OpenApiReference - { - Type = ReferenceType.Schema, - Id = "newPet", - HostDocument = actual - } - }, - ["errorModel"] = new OpenApiSchema - { - Type = "object", - Required = new HashSet - { - "code", - "message" - }, - Properties = new Dictionary - { - ["code"] = new OpenApiSchema - { - Type = "integer", - Format = "int32" - }, - ["message"] = new OpenApiSchema - { - Type = "string" - } - }, - Reference = new OpenApiReference - { - Type = ReferenceType.Schema, - Id = "errorModel" - } - }, + ["pet1"] = new JsonSchemaBuilder() + .Type(SchemaValueType.Object) + .Required("id", "name") + .Properties( + ("id", new JsonSchemaBuilder().Type(SchemaValueType.Integer).Format("int64")), + ("name", new JsonSchemaBuilder().Type(SchemaValueType.String)), + ("tag", new JsonSchemaBuilder().Type(SchemaValueType.String))), + ["newPet"] = new JsonSchemaBuilder() + .Type(SchemaValueType.Object) + .Required("name") + .Properties( + ("id", new JsonSchemaBuilder().Type(SchemaValueType.Integer).Format("int64")), + ("name", new JsonSchemaBuilder().Type(SchemaValueType.String)), + ("tag", new JsonSchemaBuilder().Type(SchemaValueType.String))), + ["errorModel"] = new JsonSchemaBuilder() + .Type(SchemaValueType.Object) + .Required("code", "message") + .Properties( + ("code", new JsonSchemaBuilder().Type(SchemaValueType.Integer).Format("int32")), + ("message", new JsonSchemaBuilder().Type(SchemaValueType.String))) }, SecuritySchemes = new Dictionary { @@ -851,29 +587,11 @@ public void ParseModifiedPetStoreDocumentWithTagAndSecurityShouldSucceed() } }; - // Create a clone of the schema to avoid modifying things in components. - var petSchema = Clone(components.Schemas["pet"]); - petSchema.Reference = new OpenApiReference - { - Id = "pet", - Type = ReferenceType.Schema - }; + var petSchema = components.Schemas["pet1"]; - var newPetSchema = Clone(components.Schemas["newPet"]); + var newPetSchema = components.Schemas["newPet"]; - newPetSchema.Reference = new OpenApiReference - { - Id = "newPet", - Type = ReferenceType.Schema - }; - - var errorModelSchema = Clone(components.Schemas["errorModel"]); - - errorModelSchema.Reference = new OpenApiReference - { - Id = "errorModel", - Type = ReferenceType.Schema - }; + var errorModelSchema = components.Schemas["errorModel"]; var tag1 = new OpenApiTag { @@ -959,14 +677,9 @@ public void ParseModifiedPetStoreDocumentWithTagAndSecurityShouldSucceed() In = ParameterLocation.Query, Description = "tags to filter by", Required = false, - Schema = new OpenApiSchema - { - Type = "array", - Items = new OpenApiSchema - { - Type = "string" - } - } + Schema = new JsonSchemaBuilder() + .Type(SchemaValueType.Array) + .Items(new JsonSchemaBuilder().Type(SchemaValueType.String)) }, new OpenApiParameter { @@ -974,11 +687,9 @@ public void ParseModifiedPetStoreDocumentWithTagAndSecurityShouldSucceed() In = ParameterLocation.Query, Description = "maximum number of results to return", Required = false, - Schema = new OpenApiSchema - { - Type = "integer", - Format = "int32" - } + Schema = new JsonSchemaBuilder() + .Type(SchemaValueType.Integer) + .Format("int32") } }, Responses = new OpenApiResponses @@ -990,19 +701,15 @@ public void ParseModifiedPetStoreDocumentWithTagAndSecurityShouldSucceed() { ["application/json"] = new OpenApiMediaType { - Schema = new OpenApiSchema - { - Type = "array", - Items = petSchema - } + Schema = new JsonSchemaBuilder() + .Type(SchemaValueType.Array) + .Items(petSchema) }, ["application/xml"] = new OpenApiMediaType { - Schema = new OpenApiSchema - { - Type = "array", - Items = petSchema - } + Schema = new JsonSchemaBuilder() + .Type(SchemaValueType.Array) + .Items(petSchema) } } }, @@ -1119,11 +826,9 @@ public void ParseModifiedPetStoreDocumentWithTagAndSecurityShouldSucceed() In = ParameterLocation.Path, Description = "ID of pet to fetch", Required = true, - Schema = new OpenApiSchema - { - Type = "integer", - Format = "int64" - } + Schema = new JsonSchemaBuilder() + .Type(SchemaValueType.Integer) + .Format("int64") } }, Responses = new OpenApiResponses @@ -1179,11 +884,9 @@ public void ParseModifiedPetStoreDocumentWithTagAndSecurityShouldSucceed() In = ParameterLocation.Path, Description = "ID of pet to delete", Required = true, - Schema = new OpenApiSchema - { - Type = "integer", - Format = "int64" - } + Schema = new JsonSchemaBuilder() + .Type(SchemaValueType.Integer) + .Format("int64") } }, Responses = new OpenApiResponses @@ -1283,54 +986,51 @@ public void GlobalSecurityRequirementShouldReferenceSecurityScheme() Assert.Same(securityRequirement.Keys.First(), openApiDoc.Components.SecuritySchemes.First().Value); } } - + [Fact] public void HeaderParameterShouldAllowExample() { - using (var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "apiWithFullHeaderComponent.yaml"))) - { - var openApiDoc = new OpenApiStreamReader().Read(stream, out var diagnostic); + using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "apiWithFullHeaderComponent.yaml")); + var openApiDoc = new OpenApiStreamReader().Read(stream, out var diagnostic); - var exampleHeader = openApiDoc.Components?.Headers?["example-header"]; - Assert.NotNull(exampleHeader); - exampleHeader.Should().BeEquivalentTo( - new OpenApiHeader() + var exampleHeader = openApiDoc.Components?.Headers?["example-header"]; + Assert.NotNull(exampleHeader); + exampleHeader.Should().BeEquivalentTo( + new OpenApiHeader() + { + Description = "Test header with example", + Required = true, + Deprecated = true, + AllowEmptyValue = true, + AllowReserved = true, + Style = ParameterStyle.Simple, + Explode = true, + Example = new OpenApiAny("99391c7e-ad88-49ec-a2ad-99ddcb1f7721"), + Schema = new JsonSchemaBuilder() + .Type(SchemaValueType.String) + .Format(Formats.Uuid), + Reference = new OpenApiReference() { - Description = "Test header with example", - Required = true, - Deprecated = true, - AllowEmptyValue = true, - AllowReserved = true, - Style = ParameterStyle.Simple, - Explode = true, - Example = new OpenApiAny("99391c7e-ad88-49ec-a2ad-99ddcb1f7721"), - Schema = new OpenApiSchema() - { - Type = "string", - Format = "uuid" - }, - Reference = new OpenApiReference() - { - Type = ReferenceType.Header, - Id = "example-header" - } - }, options => options.IgnoringCyclicReferences() - .Excluding(e => e.Example.Node.Parent)); + Type = ReferenceType.Header, + Id = "example-header" + } + }, options => options.IgnoringCyclicReferences() + .Excluding(e => e.Example.Node.Parent)); - var examplesHeader = openApiDoc.Components?.Headers?["examples-header"]; - Assert.NotNull(examplesHeader); - examplesHeader.Should().BeEquivalentTo( - new OpenApiHeader() + var examplesHeader = openApiDoc.Components?.Headers?["examples-header"]; + Assert.NotNull(examplesHeader); + examplesHeader.Should().BeEquivalentTo( + new OpenApiHeader() + { + Description = "Test header with example", + Required = true, + Deprecated = true, + AllowEmptyValue = true, + AllowReserved = true, + Style = ParameterStyle.Simple, + Explode = true, + Examples = new Dictionary() { - Description = "Test header with example", - Required = true, - Deprecated = true, - AllowEmptyValue = true, - AllowReserved = true, - Style = ParameterStyle.Simple, - Explode = true, - Examples = new Dictionary() - { { "uuid1", new OpenApiExample() { Value = new OpenApiAny("99391c7e-ad88-49ec-a2ad-99ddcb1f7721") @@ -1341,40 +1041,18 @@ public void HeaderParameterShouldAllowExample() Value = new OpenApiAny("99391c7e-ad88-49ec-a2ad-99ddcb1f7721") } } - }, - Schema = new OpenApiSchema() - { - Type = "string", - Format = "uuid" - }, - Reference = new OpenApiReference() - { - Type = ReferenceType.Header, - Id = "examples-header" - } - }, options => options.IgnoringCyclicReferences() - .Excluding(e => e.Examples["uuid1"].Value.Node.Parent) - .Excluding(e => e.Examples["uuid2"].Value.Node.Parent)); - } - } - - [Fact] - public void DoesNotChangeExternalReferences() - { - // Arrange - using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "documentWithExternalRefs.yaml")); - - // Act - var doc = new OpenApiStreamReader( - new OpenApiReaderSettings { ReferenceResolution = ReferenceResolutionSetting.DoNotResolveReferences }) - .Read(stream, out var diagnostic); - - var externalRef = doc.Components.Schemas["Nested"].Properties["AnyOf"].AnyOf.First().Reference.ReferenceV3; - var externalRef2 = doc.Components.Schemas["Nested"].Properties["AnyOf"].AnyOf.Last().Reference.ReferenceV3; - - // Assert - Assert.Equal("file:///C:/MySchemas.json#/definitions/ArrayObject", externalRef); - Assert.Equal("../foo/schemas.yaml#/components/schemas/Number", externalRef2); + }, + Schema = new JsonSchemaBuilder() + .Type(SchemaValueType.String) + .Format(Formats.Uuid), + Reference = new OpenApiReference() + { + Type = ReferenceType.Header, + Id = "examples-header" + } + }, options => options.IgnoringCyclicReferences() + .Excluding(e => e.Examples["uuid1"].Value.Node.Parent) + .Excluding(e => e.Examples["uuid2"].Value.Node.Parent)); } [Fact] @@ -1397,446 +1075,30 @@ public void ParseDocumentWithReferencedSecuritySchemeWorks() } [Fact] - public void ParseDocumentWithWebhooksShouldSucceed() - { - // Arrange and Act - using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "documentWithWebhooks.yaml")); - var actual = new OpenApiStreamReader().Read(stream, out var diagnostic); - - var components = new OpenApiComponents - { - Schemas = new Dictionary - { - ["pet"] = new OpenApiSchema - { - Type = "object", - Required = new HashSet - { - "id", - "name" - }, - Properties = new Dictionary - { - ["id"] = new OpenApiSchema - { - Type = "integer", - Format = "int64" - }, - ["name"] = new OpenApiSchema - { - Type = "string" - }, - ["tag"] = new OpenApiSchema - { - Type = "string" - }, - }, - Reference = new OpenApiReference - { - Type = ReferenceType.Schema, - Id = "pet", - HostDocument = actual - } - }, - ["newPet"] = new OpenApiSchema - { - Type = "object", - Required = new HashSet - { - "name" - }, - Properties = new Dictionary - { - ["id"] = new OpenApiSchema - { - Type = "integer", - Format = "int64" - }, - ["name"] = new OpenApiSchema - { - Type = "string" - }, - ["tag"] = new OpenApiSchema - { - Type = "string" - }, - }, - Reference = new OpenApiReference - { - Type = ReferenceType.Schema, - Id = "newPet", - HostDocument = actual - } - } - } - }; - - // Create a clone of the schema to avoid modifying things in components. - var petSchema = Clone(components.Schemas["pet"]); - - petSchema.Reference = new OpenApiReference - { - Id = "pet", - Type = ReferenceType.Schema, - HostDocument = actual - }; - - var newPetSchema = Clone(components.Schemas["newPet"]); - - newPetSchema.Reference = new OpenApiReference - { - Id = "newPet", - Type = ReferenceType.Schema, - HostDocument = actual - }; - - 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 OpenApiSchema - { - Type = "array", - Items = new OpenApiSchema - { - Type = "string" - } - } - }, - new OpenApiParameter - { - Name = "limit", - In = ParameterLocation.Query, - Description = "maximum number of results to return", - Required = false, - Schema = new OpenApiSchema - { - Type = "integer", - Format = "int32" - } - } - }, - Responses = new OpenApiResponses - { - ["200"] = new OpenApiResponse - { - Description = "pet response", - Content = new Dictionary - { - ["application/json"] = new OpenApiMediaType - { - Schema = new OpenApiSchema - { - Type = "array", - Items = petSchema - } - }, - ["application/xml"] = new OpenApiMediaType - { - Schema = new OpenApiSchema - { - Type = "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 - diagnostic.Should().BeEquivalentTo(new OpenApiDiagnostic() { SpecificationVersion = OpenApiSpecVersion.OpenApi3_1 }); - actual.Should().BeEquivalentTo(expected); - } - - [Fact] - public void ParseDocumentsWithReusablePathItemInWebhooksSucceeds() + public void ParseDocumentWithJsonSchemaReferencesWorks() { - // Arrange && Act - using var stream = Resources.GetStream("V3Tests/Samples/OpenApiDocument/documentWithReusablePaths.yaml"); - var actual = new OpenApiStreamReader().Read(stream, out var context); - - var components = new OpenApiComponents - { - Schemas = new Dictionary - { - ["pet"] = new OpenApiSchema - { - Type = "object", - Required = new HashSet - { - "id", - "name" - }, - Properties = new Dictionary - { - ["id"] = new OpenApiSchema - { - Type = "integer", - Format = "int64" - }, - ["name"] = new OpenApiSchema - { - Type = "string" - }, - ["tag"] = new OpenApiSchema - { - Type = "string" - }, - }, - Reference = new OpenApiReference - { - Type = ReferenceType.Schema, - Id = "pet", - HostDocument = actual - } - }, - ["newPet"] = new OpenApiSchema - { - Type = "object", - Required = new HashSet - { - "name" - }, - Properties = new Dictionary - { - ["id"] = new OpenApiSchema - { - Type = "integer", - Format = "int64" - }, - ["name"] = new OpenApiSchema - { - Type = "string" - }, - ["tag"] = new OpenApiSchema - { - Type = "string" - }, - }, - Reference = new OpenApiReference - { - Type = ReferenceType.Schema, - Id = "newPet", - HostDocument = actual - } - } - } - }; - - // Create a clone of the schema to avoid modifying things in components. - var petSchema = Clone(components.Schemas["pet"]); + // Arrange + using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "docWithJsonSchema.yaml")); - petSchema.Reference = new OpenApiReference + // Act + var doc = new OpenApiStreamReader(new OpenApiReaderSettings { - Id = "pet", - Type = ReferenceType.Schema, - HostDocument = actual - }; - - var newPetSchema = Clone(components.Schemas["newPet"]); + ReferenceResolution = ReferenceResolutionSetting.ResolveLocalReferences + }).Read(stream, out var diagnostic); - newPetSchema.Reference = new OpenApiReference - { - Id = "newPet", - Type = ReferenceType.Schema, - HostDocument = actual - }; - 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 OpenApiSchema - { - Type = "array", - Items = new OpenApiSchema - { - Type = "string" - } - } - }, - new OpenApiParameter - { - Name = "limit", - In = ParameterLocation.Query, - Description = "maximum number of results to return", - Required = false, - Schema = new OpenApiSchema - { - Type = "integer", - Format = "int32" - } - } - }, - Responses = new OpenApiResponses - { - ["200"] = new OpenApiResponse - { - Description = "pet response", - Content = new Dictionary - { - ["application/json"] = new OpenApiMediaType - { - Schema = new OpenApiSchema - { - Type = "array", - Items = petSchema - } - }, - ["application/xml"] = new OpenApiMediaType - { - Schema = new OpenApiSchema - { - Type = "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 - }, - } - } - } - } - }, - Reference = new OpenApiReference - { - Type = ReferenceType.PathItem, - Id = "/pets", - HostDocument = actual - } - } - }; + var actualSchema = doc.Paths["/users/{userId}"].Operations[OperationType.Get].Responses["200"].Content["application/json"].Schema; - var expected = new OpenApiDocument - { - Info = new OpenApiInfo - { - Title = "Webhook Example", - Version = "1.0.0" - }, - JsonSchemaDialect = "http://json-schema.org/draft-07/schema#", - Webhooks = components.PathItems, - Components = components - }; + var expectedSchema = new JsonSchemaBuilder() + .Type(SchemaValueType.Object) + .Properties( + ("id", new JsonSchemaBuilder().Type(SchemaValueType.Integer)), + ("username", new JsonSchemaBuilder().Type(SchemaValueType.String)), + ("email", new JsonSchemaBuilder().Type(SchemaValueType.String))) + .Build(); // Assert - actual.Should().BeEquivalentTo(expected); - context.Should().BeEquivalentTo( - new OpenApiDiagnostic() { SpecificationVersion = OpenApiSpecVersion.OpenApi3_1}); - + actualSchema.Should().BeEquivalentTo(expectedSchema); } - [Fact] - public void ParseDocumentWithDescriptionInDollarRefsShouldSucceed() - { - // Arrange - using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "documentWithSummaryAndDescriptionInReference.yaml")); - - // Act - var actual = new OpenApiStreamReader().Read(stream, out var diagnostic); - var schema = actual.Paths["/pets"].Operations[OperationType.Get].Responses["200"].Content["application/json"].Schema; - var header = actual.Components.Responses["Test"].Headers["X-Test"]; - - // Assert - Assert.True(header.Description == "A referenced X-Test header"); /*response header #ref's description overrides the header's description*/ - Assert.True(schema.UnresolvedReference == false && schema.Type == "object"); /*schema reference is resolved*/ - Assert.Equal("A pet in a petstore", schema.Description); /*The reference object's description overrides that of the referenced component*/ - } } } diff --git a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiEncodingTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiEncodingTests.cs index db711f530..a89ffa3d6 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiEncodingTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiEncodingTests.cs @@ -3,8 +3,8 @@ using System.IO; using System.Linq; -using System.Reflection.Metadata; using FluentAssertions; +using Json.Schema; using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Readers.ParseNodes; using Microsoft.OpenApi.Readers.V3; @@ -59,7 +59,7 @@ public void ParseAdvancedEncodingShouldSucceed() var asJsonNode = yamlNode.ToJsonNode(); var node = new MapNode(context, asJsonNode); - + // Act var encoding = OpenApiV3Deserializer.LoadEncoding(node); @@ -74,10 +74,7 @@ public void ParseAdvancedEncodingShouldSucceed() new OpenApiHeader { Description = "The number of allowed requests in the current period", - Schema = new OpenApiSchema - { - Type = "integer" - } + Schema = new JsonSchemaBuilder().Type(SchemaValueType.Integer) } } }); diff --git a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiExampleTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiExampleTests.cs index 934acbcbc..14f15666d 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiExampleTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiExampleTests.cs @@ -33,7 +33,7 @@ public void ParseAdvancedExampleShouldSucceed() var asJsonNode = yamlNode.ToJsonNode(); var node = new MapNode(context, asJsonNode); - + var example = OpenApiV3Deserializer.LoadExample(node); var expected = new OpenApiExample { @@ -74,7 +74,7 @@ public void ParseAdvancedExampleShouldSucceed() var actualRoot = example.Value.Node["versions"][0]["status"].Root; var expectedRoot = expected.Value.Node["versions"][0]["status"].Root; - + diagnostic.Errors.Should().BeEmpty(); example.Should().BeEquivalentTo(expected, options => options.IgnoringCyclicReferences() diff --git a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiInfoTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiInfoTests.cs index dcfdaaee5..e71f92b54 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiInfoTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiInfoTests.cs @@ -5,10 +5,8 @@ using System.IO; using System.Linq; using System.Text.Json.Nodes; -using System.Xml.Linq; using FluentAssertions; using Microsoft.OpenApi.Any; -using Microsoft.OpenApi.Extensions; using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Readers.ParseNodes; using Microsoft.OpenApi.Readers.V3; @@ -45,7 +43,6 @@ public void ParseAdvancedInfoShouldSucceed() new OpenApiInfo { Title = "Advanced Info", - Summary = "Sample Summary", Description = "Sample Description", Version = "1.0.0", TermsOfService = new Uri("http://example.org/termsOfService"), @@ -95,71 +92,66 @@ public void ParseAdvancedInfoShouldSucceed() [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 = OpenApiV3Deserializer.LoadInfo(node); - - // Assert - openApiInfo.Should().BeEquivalentTo( - new OpenApiInfo + 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 = OpenApiV3Deserializer.LoadInfo(node); + + // Assert + openApiInfo.Should().BeEquivalentTo( + new OpenApiInfo + { + Title = "Basic Info", + Description = "Sample Description", + Version = "1.0.1", + TermsOfService = new Uri("http://swagger.io/terms/"), + Contact = new OpenApiContact { - 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") - } - }); - } + 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") + } + }); } [Fact] 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 asJsonNode = yamlNode.ToJsonNode(); - var node = new MapNode(context, asJsonNode); - - // Act - var openApiInfo = OpenApiV3Deserializer.LoadInfo(node); - - // Assert - openApiInfo.Should().BeEquivalentTo( - new OpenApiInfo - { - Title = "Minimal Info", - Version = "1.0.1" - }); - } + 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 asJsonNode = yamlNode.ToJsonNode(); + var node = new MapNode(context, asJsonNode); + + // Act + var openApiInfo = OpenApiV3Deserializer.LoadInfo(node); + + // Assert + openApiInfo.Should().BeEquivalentTo( + new OpenApiInfo + { + Title = "Minimal Info", + Version = "1.0.1" + }); } } } diff --git a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiMediaTypeTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiMediaTypeTests.cs index ecb5c8eb4..df15b7f5e 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiMediaTypeTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiMediaTypeTests.cs @@ -3,6 +3,7 @@ using System.IO; using FluentAssertions; +using Json.Schema; using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Readers.ParseNodes; @@ -34,13 +35,10 @@ public void ParseMediaTypeWithExampleShouldSucceed() new OpenApiMediaType { Example = new OpenApiAny(5), - Schema = new OpenApiSchema - { - Type = "number", - Format = "float" - } + Schema = new JsonSchemaBuilder().Type(SchemaValueType.Number).Format("float") }, options => options.IgnoringCyclicReferences() - .Excluding(m => m.Example.Node.Parent)); + .Excluding(m => m.Example.Node.Parent) + ); } [Fact] @@ -71,11 +69,7 @@ public void ParseMediaTypeWithExamplesShouldSucceed() Value = new OpenApiAny(7.5) } }, - Schema = new OpenApiSchema - { - Type = "number", - Format = "float" - } + Schema = new JsonSchemaBuilder().Type(SchemaValueType.Number).Format("float") }, options => options.IgnoringCyclicReferences() .Excluding(m => m.Examples["example1"].Value.Node.Parent) .Excluding(m => m.Examples["example2"].Value.Node.Parent)); diff --git a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiOperationTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiOperationTests.cs index a74c64154..5ba80778a 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiOperationTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiOperationTests.cs @@ -4,6 +4,7 @@ using System.IO; using System.Linq; using FluentAssertions; +using Json.Schema; using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Readers.ParseNodes; using Microsoft.OpenApi.Readers.V3; @@ -66,10 +67,8 @@ public void ParseOperationWithParameterWithNoLocationShouldSucceed() Name = "username", Description = "The user name for login", Required = true, - Schema = new OpenApiSchema - { - Type = "string" - } + Schema = new JsonSchemaBuilder() + .Type(SchemaValueType.String) }, new OpenApiParameter { @@ -77,10 +76,8 @@ public void ParseOperationWithParameterWithNoLocationShouldSucceed() Description = "The password for login in clear text", In = ParameterLocation.Query, Required = true, - Schema = new OpenApiSchema - { - Type = "string" - } + Schema = new JsonSchemaBuilder() + .Type(SchemaValueType.String) } } }); diff --git a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiParameterTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiParameterTests.cs index a521fdda2..7fff35438 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiParameterTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiParameterTests.cs @@ -3,6 +3,7 @@ using System.IO; using FluentAssertions; +using Json.Schema; using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Readers.ParseNodes; @@ -37,10 +38,7 @@ public void ParsePathParameterShouldSucceed() Name = "username", Description = "username to fetch", Required = true, - Schema = new OpenApiSchema - { - Type = "string" - } + Schema = new JsonSchemaBuilder().Type(SchemaValueType.String) }); } @@ -65,14 +63,7 @@ public void ParseQueryParameterShouldSucceed() Name = "id", Description = "ID of the object to fetch", Required = false, - Schema = new OpenApiSchema - { - Type = "array", - Items = new OpenApiSchema - { - Type = "string" - } - }, + Schema = new JsonSchemaBuilder().Type(SchemaValueType.Array).Items(new JsonSchemaBuilder().Type(SchemaValueType.String)), Style = ParameterStyle.Form, Explode = true }); @@ -97,14 +88,9 @@ public void ParseQueryParameterWithObjectTypeShouldSucceed() { In = ParameterLocation.Query, Name = "freeForm", - Schema = new OpenApiSchema - { - Type = "object", - AdditionalProperties = new OpenApiSchema - { - Type = "integer" - } - }, + Schema = new JsonSchemaBuilder() + .Type(SchemaValueType.Object) + .AdditionalProperties(new JsonSchemaBuilder().Type(SchemaValueType.Integer)), Style = ParameterStyle.Form }); } @@ -132,26 +118,17 @@ public void ParseQueryParameterWithObjectTypeAndContentShouldSucceed() { ["application/json"] = new OpenApiMediaType { - Schema = new OpenApiSchema - { - Type = "object", - Required = - { - "lat", - "long" - }, - Properties = - { - ["lat"] = new OpenApiSchema - { - Type = "number" - }, - ["long"] = new OpenApiSchema - { - Type = "number" - } - } - } + Schema = new JsonSchemaBuilder() + .Type(SchemaValueType.Object) + .Required("lat", "long") + .Properties( + ("lat", new JsonSchemaBuilder() + .Type(SchemaValueType.Number) + ), + ("long", new JsonSchemaBuilder() + .Type(SchemaValueType.Number) + ) + ) } } }); @@ -180,15 +157,11 @@ public void ParseHeaderParameterShouldSucceed() Required = true, Style = ParameterStyle.Simple, - Schema = new OpenApiSchema - { - Type = "array", - Items = new OpenApiSchema - { - Type = "integer", - Format = "int64", - } - } + Schema = new JsonSchemaBuilder() + .Type(SchemaValueType.Array) + .Items(new JsonSchemaBuilder() + .Type(SchemaValueType.Integer) + .Format("int64")) }); } @@ -213,10 +186,8 @@ public void ParseParameterWithNullLocationShouldSucceed() Name = "username", Description = "username to fetch", Required = true, - Schema = new OpenApiSchema - { - Type = "string" - } + Schema = new JsonSchemaBuilder() + .Type(SchemaValueType.String) }); } @@ -241,10 +212,8 @@ public void ParseParameterWithNoLocationShouldSucceed() Name = "username", Description = "username to fetch", Required = true, - Schema = new OpenApiSchema - { - Type = "string" - } + Schema = new JsonSchemaBuilder() + .Type(SchemaValueType.String) }); } @@ -269,10 +238,8 @@ public void ParseParameterWithUnknownLocationShouldSucceed() Name = "username", Description = "username to fetch", Required = true, - Schema = new OpenApiSchema - { - Type = "string" - } + Schema = new JsonSchemaBuilder() + .Type(SchemaValueType.String) }); } @@ -298,14 +265,12 @@ public void ParseParameterWithExampleShouldSucceed() Description = "username to fetch", Required = true, Example = new OpenApiAny((float)5.0), - Schema = new OpenApiSchema - { - Type = "number", - Format = "float" - } + Schema = new JsonSchemaBuilder() + .Type(SchemaValueType.Number) + .Format("float") }, options => options.IgnoringCyclicReferences().Excluding(p => p.Example.Node.Parent)); } - + [Fact] public void ParseParameterWithExamplesShouldSucceed() { @@ -338,11 +303,9 @@ public void ParseParameterWithExamplesShouldSucceed() Value = new OpenApiAny((float)7.5) } }, - Schema = new OpenApiSchema - { - Type = "number", - Format = "float" - } + Schema = new JsonSchemaBuilder() + .Type(SchemaValueType.Number) + .Format("float") }, options => options.IgnoringCyclicReferences() .Excluding(p => p.Examples["example1"].Value.Node.Parent) .Excluding(p => p.Examples["example2"].Value.Node.Parent)); diff --git a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiSchemaTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiSchemaTests.cs deleted file mode 100644 index b5ae00671..000000000 --- a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiSchemaTests.cs +++ /dev/null @@ -1,685 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. - -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text.Json.Nodes; -using System.Xml.Linq; -using FluentAssertions; -using Microsoft.OpenApi.Any; -using Microsoft.OpenApi.Interfaces; -using Microsoft.OpenApi.Models; -using Microsoft.OpenApi.Readers.ParseNodes; -using Microsoft.OpenApi.Readers.V3; -using SharpYaml.Serialization; -using Xunit; - -namespace Microsoft.OpenApi.Readers.Tests.V3Tests -{ - [Collection("DefaultSettings")] - public class OpenApiSchemaTests - { - private const string SampleFolderPath = "V3Tests/Samples/OpenApiSchema/"; - - [Fact] - public void ParsePrimitiveSchemaShouldSucceed() - { - using (var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "primitiveSchema.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 schema = OpenApiV3Deserializer.LoadSchema(node); - - // Assert - diagnostic.Should().BeEquivalentTo(new OpenApiDiagnostic()); - - schema.Should().BeEquivalentTo( - new OpenApiSchema - { - Type = "string", - Format = "email" - }); - } - } - - [Fact] - public void ParsePrimitiveSchemaFragmentShouldSucceed() - { - using (var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "primitiveSchema.yaml"))) - { - var reader = new OpenApiStreamReader(); - var diagnostic = new OpenApiDiagnostic(); - - // Act - var schema = reader.ReadFragment(stream, OpenApiSpecVersion.OpenApi3_0, out 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(); - var diagnostic = new OpenApiDiagnostic(); - - // Act - var schema = reader.ReadFragment(input, OpenApiSpecVersion.OpenApi3_0, out diagnostic); - - // Assert - diagnostic.Should().BeEquivalentTo(new OpenApiDiagnostic()); - - schema.Should().BeEquivalentTo( - new OpenApiSchema - { - Type = "integer", - Format = "int64", - Default = new OpenApiAny(88) - }, options => options.IgnoringCyclicReferences() - .Excluding(s => s.Default.Node.Parent)); - } - - [Fact] - public void ParseExampleStringFragmentShouldSucceed() - { - var input = @" -{ - ""foo"": ""bar"", - ""baz"": [ 1,2] -}"; - var reader = new OpenApiStringReader(); - var diagnostic = new OpenApiDiagnostic(); - - // Act - var openApiAny = reader.ReadFragment(input, OpenApiSpecVersion.OpenApi3_0, out diagnostic); - - // Assert - diagnostic.Should().BeEquivalentTo(new OpenApiDiagnostic()); - - openApiAny.Should().BeEquivalentTo(new OpenApiAny( - new JsonObject - { - ["foo"] = "bar", - ["baz"] = new JsonArray() {1, 2} - }), options => options.IgnoringCyclicReferences()); - } - - [Fact] - public void ParseEnumFragmentShouldSucceed() - { - var input = @" -[ - ""foo"", - ""baz"" -]"; - var reader = new OpenApiStringReader(); - var diagnostic = new OpenApiDiagnostic(); - - // Act - var openApiAny = reader.ReadFragment(input, OpenApiSpecVersion.OpenApi3_0, out diagnostic); - - // Assert - diagnostic.Should().BeEquivalentTo(new OpenApiDiagnostic()); - - openApiAny.Should().BeEquivalentTo(new OpenApiAny( - new JsonArray - { - "foo", - "baz" - }), options => options.IgnoringCyclicReferences()); - } - - [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 diagnostic = new OpenApiDiagnostic(); - var context = new ParsingContext(diagnostic); - - var asJsonNode = yamlNode.ToJsonNode(); - var node = new MapNode(context, asJsonNode); - - // Act - var schema = OpenApiV3Deserializer.LoadSchema(node); - - // Assert - diagnostic.Should().BeEquivalentTo(new OpenApiDiagnostic()); - - schema.Should().BeEquivalentTo( - new OpenApiSchema - { - Type = "object", - Required = - { - "name" - }, - Properties = - { - ["name"] = new OpenApiSchema - { - Type = "string" - }, - ["address"] = new OpenApiSchema - { - Type = "string" - }, - ["age"] = new OpenApiSchema - { - Type = "integer", - Format = "int32", - Minimum = 0 - } - }, - AdditionalPropertiesAllowed = false - }); - } - } - - [Fact] - public void ParsePathFragmentShouldSucceed() - { - var input = @" -summary: externally referenced path item -get: - responses: - '200': - description: Ok -"; - var reader = new OpenApiStringReader(); - var diagnostic = new OpenApiDiagnostic(); - - // Act - var openApiAny = reader.ReadFragment(input, OpenApiSpecVersion.OpenApi3_0, out diagnostic); - - // Assert - diagnostic.Should().BeEquivalentTo(new OpenApiDiagnostic()); - - openApiAny.Should().BeEquivalentTo( - new OpenApiPathItem - { - Summary = "externally referenced path item", - Operations = new Dictionary - { - [OperationType.Get] = new OpenApiOperation() - { - Responses = new OpenApiResponses - { - ["200"] = new OpenApiResponse - { - Description = "Ok" - } - } - } - } - }); - } - - [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; - - var diagnostic = new OpenApiDiagnostic(); - var context = new ParsingContext(diagnostic); - - var asJsonNode = yamlNode.ToJsonNode(); - var node = new MapNode(context, asJsonNode); - - // Act - var schema = OpenApiV3Deserializer.LoadSchema(node); - - // Assert - diagnostic.Should().BeEquivalentTo(new OpenApiDiagnostic()); - - schema.Should().BeEquivalentTo( - new OpenApiSchema - { - Type = "object", - AdditionalProperties = new OpenApiSchema - { - Type = "string" - } - }); - } - } - - [Fact] - public void ParseBasicSchemaWithExampleShouldSucceed() - { - using (var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "basicSchemaWithExample.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 schema = OpenApiV3Deserializer.LoadSchema(node); - - // Assert - diagnostic.Should().BeEquivalentTo(new OpenApiDiagnostic()); - - schema.Should().BeEquivalentTo( - new OpenApiSchema - { - Type = "object", - Properties = - { - ["id"] = new OpenApiSchema - { - Type = "integer", - Format = "int64" - }, - ["name"] = new OpenApiSchema - { - Type = "string" - } - }, - Required = - { - "name" - }, - Example = new OpenApiAny(new JsonObject { ["name"] = "Puma", ["id"] = 1 }) - }, - options => options.IgnoringCyclicReferences() - .Excluding(s => s.Example.Node["name"].Parent) - .Excluding(s => s.Example.Node["name"].Root) - .Excluding(s => s.Example.Node["id"].Parent) - .Excluding(s => s.Example.Node["id"].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); - - // Assert - var components = openApiDoc.Components; - - diagnostic.Should().BeEquivalentTo( - new OpenApiDiagnostic() - { - SpecificationVersion = OpenApiSpecVersion.OpenApi3_0, - Errors = new List() - { - new OpenApiError("", "Paths is a REQUIRED field at #/") - } - }); - - components.Should().BeEquivalentTo( - new OpenApiComponents - { - Schemas = - { - ["ErrorModel"] = new OpenApiSchema - { - Type = "object", - Properties = - { - ["code"] = new OpenApiSchema - { - Type = "integer", - Minimum = 100, - Maximum = 600 - }, - ["message"] = new OpenApiSchema - { - Type = "string" - } - }, - Reference = new OpenApiReference - { - Type = ReferenceType.Schema, - Id = "ErrorModel", - HostDocument = openApiDoc - }, - Required = - { - "message", - "code" - } - }, - ["ExtendedErrorModel"] = new OpenApiSchema - { - Reference = new OpenApiReference - { - Type = ReferenceType.Schema, - Id = "ExtendedErrorModel", - HostDocument = openApiDoc - }, - AllOf = - { - new OpenApiSchema - { - Reference = new OpenApiReference - { - 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 OpenApiSchema - { - Type = "integer", - Minimum = 100, - Maximum = 600 - }, - ["message"] = new OpenApiSchema - { - Type = "string" - } - }, - Required = - { - "message", - "code" - } - }, - new OpenApiSchema - { - Type = "object", - Required = {"rootCause"}, - Properties = - { - ["rootCause"] = new OpenApiSchema - { - Type = "string" - } - } - } - } - } - } - }, options => options.Excluding(m => m.Name == "HostDocument") - .IgnoringCyclicReferences()); - } - - [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, - Errors = new List() - { - new OpenApiError("", "Paths is a REQUIRED field at #/") - } - }); - - components.Should().BeEquivalentTo( - new OpenApiComponents - { - Schemas = - { - ["Pet"] = new OpenApiSchema - { - Type = "object", - Discriminator = new OpenApiDiscriminator - { - PropertyName = "petType" - }, - Properties = - { - ["name"] = new OpenApiSchema - { - Type = "string" - }, - ["petType"] = new OpenApiSchema - { - Type = "string" - } - }, - Required = - { - "name", - "petType" - }, - Reference = new OpenApiReference() - { - Id= "Pet", - Type = ReferenceType.Schema, - HostDocument = openApiDoc - } - }, - ["Cat"] = new OpenApiSchema - { - Description = "A representation of a cat", - AllOf = - { - new OpenApiSchema - { - Reference = new OpenApiReference - { - 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 OpenApiDiscriminator - { - PropertyName = "petType" - }, - Properties = - { - ["name"] = new OpenApiSchema - { - Type = "string" - }, - ["petType"] = new OpenApiSchema - { - Type = "string" - } - }, - Required = - { - "name", - "petType" - } - }, - new OpenApiSchema - { - Type = "object", - Required = {"huntingSkill"}, - Properties = - { - ["huntingSkill"] = new OpenApiSchema - { - Type = "string", - Description = "The measured skill for hunting", - Enum = - { - new OpenApiAny("clueless"), - new OpenApiAny("lazy"), - new OpenApiAny("adventurous"), - new OpenApiAny("aggressive") - } - } - } - } - }, - Reference = new OpenApiReference() - { - Id= "Cat", - Type = ReferenceType.Schema, - HostDocument = openApiDoc - } - }, - ["Dog"] = new OpenApiSchema - { - Description = "A representation of a dog", - AllOf = - { - new OpenApiSchema - { - Reference = new OpenApiReference - { - 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 OpenApiDiscriminator - { - PropertyName = "petType" - }, - Properties = - { - ["name"] = new OpenApiSchema - { - Type = "string" - }, - ["petType"] = new OpenApiSchema - { - Type = "string" - } - }, - Required = - { - "name", - "petType" - } - }, - new OpenApiSchema - { - Type = "object", - Required = {"packSize"}, - Properties = - { - ["packSize"] = new OpenApiSchema - { - Type = "integer", - Format = "int32", - Description = "the size of the pack the dog is from", - Default = new OpenApiAny(0), - Minimum = 0 - } - } - } - }, - Reference = new OpenApiReference() - { - Id= "Dog", - Type = ReferenceType.Schema, - HostDocument = openApiDoc - } - } - } - }, options => options.Excluding(m => m.Name == "HostDocument").IgnoringCyclicReferences() - .Excluding(c => c.Schemas["Cat"].AllOf[1].Properties["huntingSkill"].Enum[0].Node.Parent) - .Excluding(c => c.Schemas["Cat"].AllOf[1].Properties["huntingSkill"].Enum[1].Node.Parent) - .Excluding(c => c.Schemas["Cat"].AllOf[1].Properties["huntingSkill"].Enum[2].Node.Parent) - .Excluding(c => c.Schemas["Cat"].AllOf[1].Properties["huntingSkill"].Enum[3].Node.Parent) - .Excluding(c => c.Schemas["Dog"].AllOf[1].Properties["packSize"].Default.Node.Parent)); - } - - - [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, - Errors = new List() - { - new OpenApiError("", "Paths is a REQUIRED field at #/") - } - }); - - var schemaExtension = new OpenApiSchema() - { - AllOf = { new OpenApiSchema() - { - Title = "schemaExtension", - Type = "object", - Properties = { - ["description"] = new OpenApiSchema() { Type = "string", Nullable = true}, - ["targetTypes"] = new OpenApiSchema() { - Type = "array", - Items = new OpenApiSchema() { - Type = "string" - } - }, - ["status"] = new OpenApiSchema() { Type = "string"}, - ["owner"] = new OpenApiSchema() { Type = "string"}, - ["child"] = null - } - } - }, - Reference = new OpenApiReference() - { - Type = ReferenceType.Schema, - Id = "microsoft.graph.schemaExtension" - } - }; - - schemaExtension.AllOf[0].Properties["child"] = schemaExtension; - - components.Schemas["microsoft.graph.schemaExtension"].Should().BeEquivalentTo(components.Schemas["microsoft.graph.schemaExtension"].AllOf[0].Properties["child"]); - } - } -} 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..b26947dc4 --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/Samples/OpenApiDocument/docWithJsonSchema.yaml @@ -0,0 +1,32 @@ +openapi: 3.1.0 +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/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/OpenApiInfo/advancedInfo.yaml b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/Samples/OpenApiInfo/advancedInfo.yaml index 1af4a41dd..51288c257 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/Samples/OpenApiInfo/advancedInfo.yaml +++ b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/Samples/OpenApiInfo/advancedInfo.yaml @@ -1,6 +1,5 @@ title: Advanced Info version: 1.0.0 -summary: Sample Summary description: Sample Description termsOfService: http://example.org/termsOfService contact: diff --git a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/Samples/OpenApiInfo/basicInfo.yaml b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/Samples/OpenApiInfo/basicInfo.yaml index 12eabe650..d48905424 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/Samples/OpenApiInfo/basicInfo.yaml +++ b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/Samples/OpenApiInfo/basicInfo.yaml @@ -1,6 +1,5 @@ { "title": "Basic Info", - "summary": "Sample Summary", "description": "Sample Description", "termsOfService": "http://swagger.io/terms/", "contact": { 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..170958591 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/Samples/OpenApiSchema/advancedSchemaWithReference.yaml +++ b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/Samples/OpenApiSchema/advancedSchemaWithReference.yaml @@ -1,4 +1,4 @@ -# https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#schemaObject +# 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: @@ -7,7 +7,9 @@ info: paths: { } components: schemas: - Pet: + ## Naming this schema Pet1 to disambiguate it from another schema `pet` contained in other test files. + ## SchemaRegistry.Global.Register() is global and can only register 1 schema with the same name. + Pet1: type: object discriminator: propertyName: petType @@ -22,7 +24,7 @@ components: Cat: ## "Cat" will be used as the discriminator value description: A representation of a cat allOf: - - $ref: '#/components/schemas/Pet' + - $ref: '#/components/schemas/Pet1' - type: object properties: huntingSkill: @@ -38,7 +40,7 @@ components: Dog: ## "Dog" will be used as the discriminator value description: A representation of a dog allOf: - - $ref: '#/components/schemas/Pet' + - $ref: '#/components/schemas/Pet1' - type: object properties: 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.SmokeTests/GraphTests.cs b/test/Microsoft.OpenApi.SmokeTests/GraphTests.cs index de3101e27..6875f2b16 100644 --- a/test/Microsoft.OpenApi.SmokeTests/GraphTests.cs +++ b/test/Microsoft.OpenApi.SmokeTests/GraphTests.cs @@ -1,13 +1,9 @@ -using Microsoft.OpenApi.Models; -using Microsoft.OpenApi.Readers; -using Microsoft.OpenApi.Services; -using System; -using System.Collections.Generic; -using System.Linq; +using System; using System.Net; using System.Net.Http; -using System.Text; -using System.Threading.Tasks; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Readers; +using Microsoft.OpenApi.Services; using Xunit; using Xunit.Abstractions; @@ -25,7 +21,8 @@ public GraphTests(ITestOutputHelper output) _output = output; System.Net.ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12; _httpClient = new HttpClient(new HttpClientHandler() - { AutomaticDecompression = DecompressionMethods.GZip + { + AutomaticDecompression = DecompressionMethods.GZip }); _httpClient.DefaultRequestHeaders.AcceptEncoding.Add(new System.Net.Http.Headers.StringWithQualityHeaderValue("gzip")); _httpClient.DefaultRequestHeaders.UserAgent.Add(new System.Net.Http.Headers.ProductInfoHeaderValue("OpenApi.Net.Tests", "1.0")); @@ -57,7 +54,7 @@ public GraphTests(ITestOutputHelper output) //[Fact(Skip="Run manually")] public void LoadOpen() { - var operations = new[] { "foo","bar" }; + var operations = new[] { "foo", "bar" }; var workspace = new OpenApiWorkspace(); workspace.AddDocument(graphOpenApiUrl, _graphOpenApi); var subset = new OpenApiDocument(); diff --git a/test/Microsoft.OpenApi.SmokeTests/WorkspaceTests.cs b/test/Microsoft.OpenApi.SmokeTests/WorkspaceTests.cs index 84f9d74ad..0d0056fe3 100644 --- a/test/Microsoft.OpenApi.SmokeTests/WorkspaceTests.cs +++ b/test/Microsoft.OpenApi.SmokeTests/WorkspaceTests.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Microsoft.OpenApi.SmokeTests +namespace Microsoft.OpenApi.SmokeTests { public class WorkspaceTests { diff --git a/test/Microsoft.OpenApi.Tests/Attributes/DisplayAttributeTests.cs b/test/Microsoft.OpenApi.Tests/Attributes/DisplayAttributeTests.cs index 26ec04556..f9e2423ad 100644 --- a/test/Microsoft.OpenApi.Tests/Attributes/DisplayAttributeTests.cs +++ b/test/Microsoft.OpenApi.Tests/Attributes/DisplayAttributeTests.cs @@ -17,12 +17,12 @@ public enum ApiLevel 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()); } } } diff --git a/test/Microsoft.OpenApi.Tests/Expressions/RuntimeExpressionTests.cs b/test/Microsoft.OpenApi.Tests/Expressions/RuntimeExpressionTests.cs index 5c91249d3..20e1eb668 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 c2b6d9597..4d61409e1 100644 --- a/test/Microsoft.OpenApi.Tests/Extensions/OpenApiTypeMapperTests.cs +++ b/test/Microsoft.OpenApi.Tests/Extensions/OpenApiTypeMapperTests.cs @@ -4,8 +4,8 @@ using System; using System.Collections.Generic; using FluentAssertions; +using Json.Schema; using Microsoft.OpenApi.Extensions; -using Microsoft.OpenApi.Models; using Xunit; namespace Microsoft.OpenApi.Tests.Extensions @@ -14,39 +14,40 @@ public class OpenApiTypeMapperTests { public static IEnumerable PrimitiveTypeData => new List { - new object[] { typeof(int), new OpenApiSchema { Type = "integer", 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 JsonSchemaBuilder().Type(SchemaValueType.Integer).Format("int32").Build() }, + new object[] { typeof(string), new JsonSchemaBuilder().Type(SchemaValueType.String).Build() }, + new object[] { typeof(double), new JsonSchemaBuilder().Type(SchemaValueType.Number).Format("double").Build() }, + new object[] { typeof(DateTimeOffset), new JsonSchemaBuilder().Type(SchemaValueType.String).Format("date-time").Build() } }; - public static IEnumerable OpenApiDataTypes => new List + public static IEnumerable JsonSchemaDataTypes => new List { - new object[] { new OpenApiSchema { Type = "integer", Format = "int32"}, 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 JsonSchemaBuilder().Type(SchemaValueType.Integer).Format("int32").Build(), typeof(int) }, + new object[] { new JsonSchemaBuilder().Type(SchemaValueType.Number).Format("double").Build(), typeof(double) }, + new object[] { new JsonSchemaBuilder().AnyOf( + new JsonSchemaBuilder().Type(SchemaValueType.Integer).Build(), + new JsonSchemaBuilder().Type(SchemaValueType.Integer).Build()) + .Format("float").Build(), typeof(float?) }, + new object[] { new JsonSchemaBuilder().Type(SchemaValueType.String).Format("date-time").Build(), typeof(DateTimeOffset) } }; - + [Theory] [MemberData(nameof(PrimitiveTypeData))] - public void MapTypeToOpenApiPrimitiveTypeShouldSucceed(Type type, OpenApiSchema expected) + public void MapTypeToJsonPrimitiveTypeShouldSucceed(Type type, JsonSchema expected) { // Arrange & Act - var actual = OpenApiTypeMapper.MapTypeToOpenApiPrimitiveType(type); + var actual = OpenApiTypeMapper.MapTypeToJsonPrimitiveType(type); // Assert actual.Should().BeEquivalentTo(expected); } [Theory] - [MemberData(nameof(OpenApiDataTypes))] - public void MapOpenApiSchemaTypeToSimpleTypeShouldSucceed(OpenApiSchema schema, Type expected) + [MemberData(nameof(JsonSchemaDataTypes))] + public void MapOpenApiSchemaTypeToSimpleTypeShouldSucceed(JsonSchema schema, Type expected) { // Arrange & Act - var actual = OpenApiTypeMapper.MapOpenApiPrimitiveTypeToSimpleType(schema); + var actual = OpenApiTypeMapper.MapJsonSchemaValueTypeToSimpleType(schema); // Assert actual.Should().Be(expected); diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiCallbackTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiCallbackTests.cs index 93b78e71b..370f57091 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiCallbackTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiCallbackTests.cs @@ -4,6 +4,7 @@ using System.Globalization; using System.IO; using System.Threading.Tasks; +using Json.Schema; using Microsoft.OpenApi.Expressions; using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Writers; @@ -35,10 +36,7 @@ public class OpenApiCallbackTests { ["application/json"] = new OpenApiMediaType { - Schema = new OpenApiSchema - { - Type = "object" - } + Schema = new JsonSchemaBuilder().Type(SchemaValueType.Object).Build() } } }, @@ -78,10 +76,7 @@ public class OpenApiCallbackTests { ["application/json"] = new OpenApiMediaType { - Schema = new OpenApiSchema - { - Type = "object" - } + Schema = new JsonSchemaBuilder().Type(SchemaValueType.Object).Build() } } }, diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiComponentsTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiComponentsTests.cs index 7c6365ce4..08efcfac1 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiComponentsTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiComponentsTests.cs @@ -4,6 +4,8 @@ using System; using System.Collections.Generic; using FluentAssertions; +using Json.Schema; +using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Extensions; using Microsoft.OpenApi.Models; using Xunit; @@ -16,23 +18,14 @@ public class OpenApiComponentsTests { public static OpenApiComponents AdvancedComponents = new OpenApiComponents { - Schemas = new Dictionary + Schemas = new Dictionary { - ["schema1"] = new OpenApiSchema - { - Properties = new Dictionary - { - ["property2"] = new OpenApiSchema - { - Type = "integer" - }, - ["property3"] = new OpenApiSchema - { - Type = "string", - MaxLength = 15 - } - }, - }, + ["schema1"] = new JsonSchemaBuilder() + .Properties( + ("property2", new JsonSchemaBuilder().Type(SchemaValueType.Integer).Build()), + ("property3", new JsonSchemaBuilder().Type(SchemaValueType.String).MaxLength(15).Build())) + .Build() + }, SecuritySchemes = new Dictionary { @@ -65,41 +58,15 @@ public class OpenApiComponentsTests public static OpenApiComponents AdvancedComponentsWithReference = new OpenApiComponents { - Schemas = new Dictionary + Schemas = new Dictionary { - ["schema1"] = new OpenApiSchema - { - Properties = new Dictionary - { - ["property2"] = new OpenApiSchema - { - Type = "integer" - }, - ["property3"] = new OpenApiSchema - { - Reference = new OpenApiReference - { - Type = ReferenceType.Schema, - Id = "schema2" - } - } - }, - Reference = new OpenApiReference - { - Type = ReferenceType.Schema, - Id = "schema1" - } - }, - ["schema2"] = new OpenApiSchema - { - Properties = new Dictionary - { - ["property2"] = new OpenApiSchema - { - Type = "integer" - } - } - }, + ["schema1"] = new JsonSchemaBuilder() + .Properties( + ("property2", new JsonSchemaBuilder().Type(SchemaValueType.Integer)), + ("property3", new JsonSchemaBuilder().Ref("#/components/schemas/schema2"))), + ["schema2"] = new JsonSchemaBuilder() + .Properties( + ("property2", new JsonSchemaBuilder().Type(SchemaValueType.Integer))) }, SecuritySchemes = new Dictionary { @@ -144,29 +111,13 @@ public class OpenApiComponentsTests public static OpenApiComponents BrokenComponents = new OpenApiComponents { - Schemas = new Dictionary + Schemas = new Dictionary { - ["schema1"] = new OpenApiSchema - { - Type = "string" - }, - ["schema2"] = null, - ["schema3"] = null, - ["schema4"] = new OpenApiSchema - { - Type = "string", - AllOf = new List - { - null, - null, - new OpenApiSchema - { - Type = "string" - }, - null, - null - } - } + ["schema1"] = new JsonSchemaBuilder().Type(SchemaValueType.String), + ["schema4"] = new JsonSchemaBuilder() + .Type(SchemaValueType.String) + .AllOf(new JsonSchemaBuilder().Type(SchemaValueType.String).Build()) + .Build() } }; @@ -174,25 +125,12 @@ public class OpenApiComponentsTests { Schemas = { - ["schema1"] = new OpenApiSchema - { - Reference = new OpenApiReference() - { - Type = ReferenceType.Schema, - Id = "schema2" - } - }, - ["schema2"] = new OpenApiSchema - { - Type = "object", - Properties = - { - ["property1"] = new OpenApiSchema() - { - Type = "string" - } - } - }, + ["schema1"] = new JsonSchemaBuilder() + .Ref("#/components/schemas/schema2").Build(), + ["schema2"] = new JsonSchemaBuilder() + .Type(SchemaValueType.Object) + .Properties(("property1", new JsonSchemaBuilder().Type(SchemaValueType.String))) + .Build() } }; @@ -200,33 +138,18 @@ public class OpenApiComponentsTests { Schemas = { - ["schema1"] = new OpenApiSchema - { - Type = "object", - Properties = - { - ["property1"] = new OpenApiSchema() - { - Type = "string" - } - }, - Reference = new OpenApiReference() - { - Type = ReferenceType.Schema, - Id = "schema1" - } - }, - ["schema2"] = new OpenApiSchema - { - Type = "object", - Properties = - { - ["property1"] = new OpenApiSchema() - { - Type = "string" - } - } - }, + ["schema1"] = new JsonSchemaBuilder() + .Type(SchemaValueType.Object) + .Properties( + ("property1", new JsonSchemaBuilder().Type(SchemaValueType.String))) + .Ref("#/components/schemas/schema1") + .Build(), + + ["schema2"] = new JsonSchemaBuilder() + .Type(SchemaValueType.Object) + .Properties( + ("property1", new JsonSchemaBuilder().Type(SchemaValueType.String))) + .Build() } }; @@ -234,54 +157,25 @@ public class OpenApiComponentsTests { Schemas = { - ["schema1"] = new OpenApiSchema - { - Reference = new OpenApiReference() - { - Type = ReferenceType.Schema, - Id = "schema1" - } - } + ["schema1"] = new JsonSchemaBuilder() + .Ref("schema1").Build() } }; public static OpenApiComponents ComponentsWithPathItem = new OpenApiComponents { - Schemas = new Dictionary + Schemas = new Dictionary { - ["schema1"] = new OpenApiSchema - { - Properties = new Dictionary - { - ["property2"] = new OpenApiSchema - { - Type = "integer" - }, - ["property3"] = new OpenApiSchema - { - Reference = new OpenApiReference - { - Type = ReferenceType.Schema, - Id = "schema2" - } - } - }, - Reference = new OpenApiReference - { - Type = ReferenceType.Schema, - Id = "schema1" - } - }, - ["schema2"] = new OpenApiSchema - { - Properties = new Dictionary - { - ["property2"] = new OpenApiSchema - { - Type = "integer" - } - } - }, + ["schema1"] = new JsonSchemaBuilder() + .Properties( + ("property2", new JsonSchemaBuilder().Type(SchemaValueType.Integer).Build()), + ("property3", new JsonSchemaBuilder().Ref("#/components/schemas/schema2").Build())) + .Build(), + + ["schema2"] = new JsonSchemaBuilder() + .Properties( + ("property2", new JsonSchemaBuilder().Type(SchemaValueType.Integer))) + .Build() }, PathItems = new Dictionary { @@ -298,14 +192,7 @@ public class OpenApiComponentsTests { ["application/json"] = new OpenApiMediaType { - Schema = new OpenApiSchema - { - Reference = new OpenApiReference - { - Id = "schema1", - Type = ReferenceType.Schema - } - } + Schema = new JsonSchemaBuilder().Ref("#/components/schemas/schema1") } } }, @@ -322,7 +209,7 @@ public class OpenApiComponentsTests } }; - + private readonly ITestOutputHelper _output; public OpenApiComponentsTests(ITestOutputHelper output) @@ -548,18 +435,12 @@ public void SerializeBrokenComponentsAsJsonV3Works() ""schema1"": { ""type"": ""string"" }, - ""schema2"": null, - ""schema3"": null, ""schema4"": { ""type"": ""string"", ""allOf"": [ - null, - null, { ""type"": ""string"" - }, - null, - null + } ] } } @@ -581,16 +462,10 @@ public void SerializeBrokenComponentsAsYamlV3Works() var expected = @"schemas: schema1: type: string - schema2: - schema3: schema4: type: string allOf: - - - - - - type: string - - - - "; + - type: string"; // Act var actual = BrokenComponents.SerializeAsYaml(OpenApiSpecVersion.OpenApi3_0); @@ -623,22 +498,6 @@ public void SerializeTopLevelReferencingComponentsAsYamlV3Works() actual.Should().Be(expected); } - [Fact] - public void SerializeTopLevelSelfReferencingComponentsAsYamlV3Works() - { - // Arrange - var expected = @"schemas: - schema1: { }"; - - // Act - var actual = TopLevelSelfReferencingComponents.SerializeAsYaml(OpenApiSpecVersion.OpenApi3_0); - - // Assert - actual = actual.MakeLineBreaksEnvironmentNeutral(); - expected = expected.MakeLineBreaksEnvironmentNeutral(); - actual.Should().Be(expected); - } - [Fact] public void SerializeTopLevelSelfReferencingWithOtherPropertiesComponentsAsYamlV3Works() { @@ -723,7 +582,7 @@ public void SerializeComponentsWithPathItemsAsJsonWorks() public void SerializeComponentsWithPathItemsAsYamlWorks() { // Arrange - var expected = @"pathItems: + var expected = @"pathItems: /pets: post: requestBody: diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiContactTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiContactTests.cs index ee5c7b0cb..f38ab14aa 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiContactTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiContactTests.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Text.Json.Nodes; using FluentAssertions; using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Extensions; @@ -28,7 +27,7 @@ public class OpenApiContactTests {"x-internal-id", new OpenApiAny(42)} } }; - + [Theory] [InlineData(OpenApiSpecVersion.OpenApi3_0, OpenApiFormat.Json, "{ }")] [InlineData(OpenApiSpecVersion.OpenApi2_0, OpenApiFormat.Json, "{ }")] diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.SerializeAdvancedDocumentAsV2JsonWorks_produceTerseOutput=False.verified.txt b/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.SerializeAdvancedDocumentAsV2JsonWorks_produceTerseOutput=False.verified.txt index a2e4fbd4c..245cca5ca 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.SerializeAdvancedDocumentAsV2JsonWorks_produceTerseOutput=False.verified.txt +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.SerializeAdvancedDocumentAsV2JsonWorks_produceTerseOutput=False.verified.txt @@ -62,8 +62,8 @@ "type": "object", "properties": { "id": { - "format": "int64", - "type": "integer" + "type": "integer", + "format": "int64" }, "name": { "type": "string" @@ -85,8 +85,8 @@ "type": "object", "properties": { "code": { - "format": "int32", - "type": "integer" + "type": "integer", + "format": "int32" }, "message": { "type": "string" @@ -104,8 +104,8 @@ "type": "object", "properties": { "code": { - "format": "int32", - "type": "integer" + "type": "integer", + "format": "int32" }, "message": { "type": "string" @@ -138,8 +138,8 @@ "type": "object", "properties": { "id": { - "format": "int64", - "type": "integer" + "type": "integer", + "format": "int64" }, "name": { "type": "string" @@ -162,8 +162,8 @@ "type": "object", "properties": { "id": { - "format": "int64", - "type": "integer" + "type": "integer", + "format": "int64" }, "name": { "type": "string" @@ -184,8 +184,8 @@ "type": "object", "properties": { "code": { - "format": "int32", - "type": "integer" + "type": "integer", + "format": "int32" }, "message": { "type": "string" @@ -203,8 +203,8 @@ "type": "object", "properties": { "code": { - "format": "int32", - "type": "integer" + "type": "integer", + "format": "int32" }, "message": { "type": "string" @@ -245,8 +245,8 @@ "type": "object", "properties": { "id": { - "format": "int64", - "type": "integer" + "type": "integer", + "format": "int64" }, "name": { "type": "string" @@ -267,8 +267,8 @@ "type": "object", "properties": { "code": { - "format": "int32", - "type": "integer" + "type": "integer", + "format": "int32" }, "message": { "type": "string" @@ -286,8 +286,8 @@ "type": "object", "properties": { "code": { - "format": "int32", - "type": "integer" + "type": "integer", + "format": "int32" }, "message": { "type": "string" @@ -327,8 +327,8 @@ "type": "object", "properties": { "code": { - "format": "int32", - "type": "integer" + "type": "integer", + "format": "int32" }, "message": { "type": "string" @@ -346,8 +346,8 @@ "type": "object", "properties": { "code": { - "format": "int32", - "type": "integer" + "type": "integer", + "format": "int32" }, "message": { "type": "string" @@ -368,8 +368,8 @@ "type": "object", "properties": { "id": { - "format": "int64", - "type": "integer" + "type": "integer", + "format": "int64" }, "name": { "type": "string" @@ -386,8 +386,8 @@ "type": "object", "properties": { "id": { - "format": "int64", - "type": "integer" + "type": "integer", + "format": "int64" }, "name": { "type": "string" @@ -405,8 +405,8 @@ "type": "object", "properties": { "code": { - "format": "int32", - "type": "integer" + "type": "integer", + "format": "int32" }, "message": { "type": "string" 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 index 081bcda08..8bf9f35bc 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.SerializeAdvancedDocumentAsV2JsonWorks_produceTerseOutput=True.verified.txt +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.SerializeAdvancedDocumentAsV2JsonWorks_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":{"required":["id","name"],"type":"object","properties":{"id":{"type":"integer","format":"int64"},"name":{"type":"string"},"tag":{"type":"string"}}}}},"4XX":{"description":"unexpected client error","schema":{"required":["code","message"],"type":"object","properties":{"code":{"type":"integer","format":"int32"},"message":{"type":"string"}}}},"5XX":{"description":"unexpected server error","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","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":{"type":"integer","format":"int64"},"name":{"type":"string"},"tag":{"type":"string"}}}}],"responses":{"200":{"description":"pet response","schema":{"required":["id","name"],"type":"object","properties":{"id":{"type":"integer","format":"int64"},"name":{"type":"string"},"tag":{"type":"string"}}}},"4XX":{"description":"unexpected client error","schema":{"required":["code","message"],"type":"object","properties":{"code":{"type":"integer","format":"int32"},"message":{"type":"string"}}}},"5XX":{"description":"unexpected server error","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","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":{"type":"integer","format":"int64"},"name":{"type":"string"},"tag":{"type":"string"}}}},"4XX":{"description":"unexpected client error","schema":{"required":["code","message"],"type":"object","properties":{"code":{"type":"integer","format":"int32"},"message":{"type":"string"}}}},"5XX":{"description":"unexpected server error","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","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":{"type":"integer","format":"int32"},"message":{"type":"string"}}}},"5XX":{"description":"unexpected server error","schema":{"required":["code","message"],"type":"object","properties":{"code":{"type":"integer","format":"int32"},"message":{"type":"string"}}}}}}}},"definitions":{"pet":{"required":["id","name"],"type":"object","properties":{"id":{"type":"integer","format":"int64"},"name":{"type":"string"},"tag":{"type":"string"}}},"newPet":{"required":["name"],"type":"object","properties":{"id":{"type":"integer","format":"int64"},"name":{"type":"string"},"tag":{"type":"string"}}},"errorModel":{"required":["code","message"],"type":"object","properties":{"code":{"type":"integer","format":"int32"},"message":{"type":"string"}}}}} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.SerializeAdvancedDocumentWithReferenceAsV2JsonWorks_produceTerseOutput=False.verified.txt b/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.SerializeAdvancedDocumentWithReferenceAsV2JsonWorks_produceTerseOutput=False.verified.txt index 443881617..6f4d12e71 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.SerializeAdvancedDocumentWithReferenceAsV2JsonWorks_produceTerseOutput=False.verified.txt +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.SerializeAdvancedDocumentWithReferenceAsV2JsonWorks_produceTerseOutput=False.verified.txt @@ -55,20 +55,20 @@ "schema": { "type": "array", "items": { - "$ref": "#/definitions/pet" + "$ref": "#/components/schemas/pet" } } }, "4XX": { "description": "unexpected client error", "schema": { - "$ref": "#/definitions/errorModel" + "$ref": "#/components/schemas/errorModel" } }, "5XX": { "description": "unexpected server error", "schema": { - "$ref": "#/definitions/errorModel" + "$ref": "#/components/schemas/errorModel" } } } @@ -90,7 +90,7 @@ "description": "Pet to add to the store", "required": true, "schema": { - "$ref": "#/definitions/newPet" + "$ref": "#/components/schemas/newPet" } } ], @@ -98,19 +98,19 @@ "200": { "description": "pet response", "schema": { - "$ref": "#/definitions/pet" + "$ref": "#/components/schemas/pet" } }, "4XX": { "description": "unexpected client error", "schema": { - "$ref": "#/definitions/errorModel" + "$ref": "#/components/schemas/errorModel" } }, "5XX": { "description": "unexpected server error", "schema": { - "$ref": "#/definitions/errorModel" + "$ref": "#/components/schemas/errorModel" } } } @@ -139,19 +139,19 @@ "200": { "description": "pet response", "schema": { - "$ref": "#/definitions/pet" + "$ref": "#/components/schemas/pet" } }, "4XX": { "description": "unexpected client error", "schema": { - "$ref": "#/definitions/errorModel" + "$ref": "#/components/schemas/errorModel" } }, "5XX": { "description": "unexpected server error", "schema": { - "$ref": "#/definitions/errorModel" + "$ref": "#/components/schemas/errorModel" } } } @@ -179,13 +179,13 @@ "4XX": { "description": "unexpected client error", "schema": { - "$ref": "#/definitions/errorModel" + "$ref": "#/components/schemas/errorModel" } }, "5XX": { "description": "unexpected server error", "schema": { - "$ref": "#/definitions/errorModel" + "$ref": "#/components/schemas/errorModel" } } } @@ -201,8 +201,8 @@ "type": "object", "properties": { "id": { - "format": "int64", - "type": "integer" + "type": "integer", + "format": "int64" }, "name": { "type": "string" @@ -219,8 +219,8 @@ "type": "object", "properties": { "id": { - "format": "int64", - "type": "integer" + "type": "integer", + "format": "int64" }, "name": { "type": "string" @@ -238,8 +238,8 @@ "type": "object", "properties": { "code": { - "format": "int32", - "type": "integer" + "type": "integer", + "format": "int32" }, "message": { "type": "string" diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.SerializeAdvancedDocumentWithReferenceAsV2JsonWorks_produceTerseOutput=True.verified.txt b/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.SerializeAdvancedDocumentWithReferenceAsV2JsonWorks_produceTerseOutput=True.verified.txt index 3818a4799..ce5390739 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.SerializeAdvancedDocumentWithReferenceAsV2JsonWorks_produceTerseOutput=True.verified.txt +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.SerializeAdvancedDocumentWithReferenceAsV2JsonWorks_produceTerseOutput=True.verified.txt @@ -1 +1 @@ -{"swagger":"2.0","info":{"title":"Swagger Petstore (Simple)","description":"A sample API that uses a petstore as an example to demonstrate features in the swagger-2.0 specification","termsOfService":"http://helloreverb.com/terms/","contact":{"name":"Swagger API team","url":"http://swagger.io","email":"foo@example.com"},"license":{"name":"MIT","url":"http://opensource.org/licenses/MIT"},"version":"1.0.0"},"host":"petstore.swagger.io","basePath":"/api","schemes":["http"],"paths":{"/pets":{"get":{"description":"Returns all pets from the system that the user has access to","operationId":"findPets","produces":["application/json","application/xml","text/html"],"parameters":[{"in":"query","name":"tags","description":"tags to filter by","type":"array","items":{"type":"string"},"collectionFormat":"multi"},{"in":"query","name":"limit","description":"maximum number of results to return","type":"integer","format":"int32"}],"responses":{"200":{"description":"pet response","schema":{"type":"array","items":{"$ref":"#/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":{"$ref":"#/components/schemas/pet"}}},"4XX":{"description":"unexpected client error","schema":{"$ref":"#/components/schemas/errorModel"}},"5XX":{"description":"unexpected server error","schema":{"$ref":"#/components/schemas/errorModel"}}}},"post":{"description":"Creates a new pet in the store. Duplicates are allowed","operationId":"addPet","consumes":["application/json"],"produces":["application/json","text/html"],"parameters":[{"in":"body","name":"body","description":"Pet to add to the store","required":true,"schema":{"$ref":"#/components/schemas/newPet"}}],"responses":{"200":{"description":"pet response","schema":{"$ref":"#/components/schemas/pet"}},"4XX":{"description":"unexpected client error","schema":{"$ref":"#/components/schemas/errorModel"}},"5XX":{"description":"unexpected server error","schema":{"$ref":"#/components/schemas/errorModel"}}}}},"/pets/{id}":{"get":{"description":"Returns a user based on a single ID, if the user does not have access to the pet","operationId":"findPetById","produces":["application/json","application/xml","text/html"],"parameters":[{"in":"path","name":"id","description":"ID of pet to fetch","required":true,"type":"integer","format":"int64"}],"responses":{"200":{"description":"pet response","schema":{"$ref":"#/components/schemas/pet"}},"4XX":{"description":"unexpected client error","schema":{"$ref":"#/components/schemas/errorModel"}},"5XX":{"description":"unexpected server error","schema":{"$ref":"#/components/schemas/errorModel"}}}},"delete":{"description":"deletes a single pet based on the ID supplied","operationId":"deletePet","produces":["text/html"],"parameters":[{"in":"path","name":"id","description":"ID of pet to delete","required":true,"type":"integer","format":"int64"}],"responses":{"204":{"description":"pet deleted"},"4XX":{"description":"unexpected client error","schema":{"$ref":"#/components/schemas/errorModel"}},"5XX":{"description":"unexpected server error","schema":{"$ref":"#/components/schemas/errorModel"}}}}}},"definitions":{"pet":{"required":["id","name"],"type":"object","properties":{"id":{"type":"integer","format":"int64"},"name":{"type":"string"},"tag":{"type":"string"}}},"newPet":{"required":["name"],"type":"object","properties":{"id":{"type":"integer","format":"int64"},"name":{"type":"string"},"tag":{"type":"string"}}},"errorModel":{"required":["code","message"],"type":"object","properties":{"code":{"type":"integer","format":"int32"},"message":{"type":"string"}}}}} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.SerializeDuplicateExtensionsAsV2JsonWorks_produceTerseOutput=False.verified.txt b/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.SerializeDuplicateExtensionsAsV2JsonWorks_produceTerseOutput=False.verified.txt index 671c21ec5..08622d6b1 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.SerializeDuplicateExtensionsAsV2JsonWorks_produceTerseOutput=False.verified.txt +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.SerializeDuplicateExtensionsAsV2JsonWorks_produceTerseOutput=False.verified.txt @@ -48,8 +48,8 @@ "type": "object", "properties": { "id": { - "format": "int64", - "type": "integer" + "type": "integer", + "format": "int64" }, "name": { "type": "string" 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 index 7dd31e201..8cecc96a4 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.SerializeDuplicateExtensionsAsV2JsonWorks_produceTerseOutput=True.verified.txt +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.SerializeDuplicateExtensionsAsV2JsonWorks_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":{"required":["id","name"],"type":"object","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.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.cs index 175e308e3..8fb02fce9 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.cs @@ -5,22 +5,19 @@ using System.Collections.Generic; using System.Globalization; using System.IO; -using System.Text.Json.Nodes; -using System.Threading; using System.Threading.Tasks; using FluentAssertions; +using Json.Schema; using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Extensions; using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Readers; -using Microsoft.OpenApi.Services; using Microsoft.OpenApi.Writers; using Microsoft.VisualBasic; using VerifyXunit; using Xunit; using Xunit.Abstractions; -using static System.Net.Mime.MediaTypeNames; namespace Microsoft.OpenApi.Tests.Models { @@ -28,82 +25,42 @@ namespace Microsoft.OpenApi.Tests.Models [UsesVerify] public class OpenApiDocumentTests { - public static OpenApiComponents TopLevelReferencingComponents = new OpenApiComponents() + public static readonly OpenApiComponents TopLevelReferencingComponents = new OpenApiComponents() { Schemas = { - ["schema1"] = new OpenApiSchema - { - Reference = new OpenApiReference() - { - Type = ReferenceType.Schema, - Id = "schema2" - } - }, - ["schema2"] = new OpenApiSchema - { - Type = "object", - Properties = - { - ["property1"] = new OpenApiSchema() - { - Type = "string" - } - } - }, + ["schema1"] = new JsonSchemaBuilder().Ref("#/definitions/schema2"), + ["schema2"] = new JsonSchemaBuilder() + .Type(SchemaValueType.Object) + .Properties(("property1", new JsonSchemaBuilder().Type(SchemaValueType.String).Build())) + .Build() } }; - public static OpenApiComponents TopLevelSelfReferencingComponentsWithOtherProperties = new OpenApiComponents() + public static readonly OpenApiComponents TopLevelSelfReferencingComponentsWithOtherProperties = new OpenApiComponents() { Schemas = { - ["schema1"] = new OpenApiSchema - { - Type = "object", - Properties = - { - ["property1"] = new OpenApiSchema() - { - Type = "string" - } - }, - Reference = new OpenApiReference() - { - Type = ReferenceType.Schema, - Id = "schema1" - } - }, - ["schema2"] = new OpenApiSchema - { - Type = "object", - Properties = - { - ["property1"] = new OpenApiSchema() - { - Type = "string" - } - } - }, + ["schema1"] = new JsonSchemaBuilder() + .Type(SchemaValueType.Object) + .Properties(("property1", new JsonSchemaBuilder().Type(SchemaValueType.String).Build())) + .Ref("#/definitions/schema1"), + ["schema2"] = new JsonSchemaBuilder() + .Type(SchemaValueType.Object) + .Properties(("property1", new JsonSchemaBuilder().Type(SchemaValueType.String).Build())) } + }; - public static OpenApiComponents TopLevelSelfReferencingComponents = new OpenApiComponents() + public static readonly OpenApiComponents TopLevelSelfReferencingComponents = new OpenApiComponents() { Schemas = { - ["schema1"] = new OpenApiSchema - { - Reference = new OpenApiReference() - { - Type = ReferenceType.Schema, - Id = "schema1" - } - } + ["schema1"] = new JsonSchemaBuilder().Ref("#/definitions/schemas/schema1") } }; - public static OpenApiDocument SimpleDocumentWithTopLevelReferencingComponents = new OpenApiDocument() + public static readonly OpenApiDocument SimpleDocumentWithTopLevelReferencingComponents = new OpenApiDocument() { Info = new OpenApiInfo() { @@ -112,7 +69,7 @@ public class OpenApiDocumentTests Components = TopLevelReferencingComponents }; - public static OpenApiDocument SimpleDocumentWithTopLevelSelfReferencingComponentsWithOtherProperties = new OpenApiDocument() + public static readonly OpenApiDocument SimpleDocumentWithTopLevelSelfReferencingComponentsWithOtherProperties = new OpenApiDocument() { Info = new OpenApiInfo() { @@ -121,7 +78,7 @@ public class OpenApiDocumentTests Components = TopLevelSelfReferencingComponentsWithOtherProperties }; - public static OpenApiDocument SimpleDocumentWithTopLevelSelfReferencingComponents = new OpenApiDocument() + public static readonly OpenApiDocument SimpleDocumentWithTopLevelSelfReferencingComponents = new OpenApiDocument() { Info = new OpenApiInfo() { @@ -130,106 +87,43 @@ public class OpenApiDocumentTests Components = TopLevelSelfReferencingComponents }; - public static OpenApiComponents AdvancedComponentsWithReference = new OpenApiComponents + public static readonly OpenApiComponents AdvancedComponentsWithReference = new OpenApiComponents { - Schemas = new Dictionary + Schemas = new Dictionary { - ["pet"] = new OpenApiSchema - { - Type = "object", - Required = new HashSet - { - "id", - "name" - }, - Properties = new Dictionary - { - ["id"] = new OpenApiSchema - { - Type = "integer", - Format = "int64" - }, - ["name"] = new OpenApiSchema - { - Type = "string" - }, - ["tag"] = new OpenApiSchema - { - Type = "string" - }, - }, - Reference = new OpenApiReference - { - Id = "pet", - Type = ReferenceType.Schema - } - }, - ["newPet"] = new OpenApiSchema - { - Type = "object", - Required = new HashSet - { - "name" - }, - Properties = new Dictionary - { - ["id"] = new OpenApiSchema - { - Type = "integer", - Format = "int64" - }, - ["name"] = new OpenApiSchema - { - Type = "string" - }, - ["tag"] = new OpenApiSchema - { - Type = "string" - }, - }, - Reference = new OpenApiReference - { - Id = "newPet", - Type = ReferenceType.Schema - } - }, - ["errorModel"] = new OpenApiSchema - { - Type = "object", - Required = new HashSet - { - "code", - "message" - }, - Properties = new Dictionary - { - ["code"] = new OpenApiSchema - { - Type = "integer", - Format = "int32" - }, - ["message"] = new OpenApiSchema - { - Type = "string" - } - }, - Reference = new OpenApiReference - { - Id = "errorModel", - Type = ReferenceType.Schema - } - }, + ["pet"] = new JsonSchemaBuilder() + .Type(SchemaValueType.Object) + .Required("id", "name") + .Properties(("id", new JsonSchemaBuilder().Type(SchemaValueType.Integer).Format("int64").Build()), + ("name", new JsonSchemaBuilder().Type(SchemaValueType.String).Build()), + ("tag", new JsonSchemaBuilder().Type(SchemaValueType.String).Build())) + .Ref("#/components/schemas/pet").Build(), + ["newPet"] = new JsonSchemaBuilder() + .Type(SchemaValueType.Object) + .Required("name") + .Properties( + ("id", new JsonSchemaBuilder().Type(SchemaValueType.Integer).Format("int64").Build()), + ("name", new JsonSchemaBuilder().Type(SchemaValueType.String).Build()), + ("tag", new JsonSchemaBuilder().Type(SchemaValueType.String).Build())) + .Ref("#/components/schemas/newPet").Build(), + ["errorModel"] = new JsonSchemaBuilder() + .Type(SchemaValueType.Object) + .Required("code", "message") + .Properties( + ("code", new JsonSchemaBuilder().Type(SchemaValueType.Integer).Format("int32").Build()), + ("message", new JsonSchemaBuilder().Type(SchemaValueType.String).Build())) + .Ref("#/components/schemas/errorModel").Build() } }; - public static OpenApiSchema PetSchemaWithReference = AdvancedComponentsWithReference.Schemas["pet"]; + public static readonly JsonSchema PetSchemaWithReference = AdvancedComponentsWithReference.Schemas["pet"]; - public static OpenApiSchema NewPetSchemaWithReference = AdvancedComponentsWithReference.Schemas["newPet"]; + public static readonly JsonSchema NewPetSchemaWithReference = AdvancedComponentsWithReference.Schemas["newPet"]; - public static OpenApiSchema ErrorModelSchemaWithReference = + public static readonly JsonSchema ErrorModelSchemaWithReference = AdvancedComponentsWithReference.Schemas["errorModel"]; - public static OpenApiDocument AdvancedDocumentWithReference = new OpenApiDocument + public static readonly OpenApiDocument AdvancedDocumentWithReference = new OpenApiDocument { Info = new OpenApiInfo { @@ -275,14 +169,9 @@ public class OpenApiDocumentTests In = ParameterLocation.Query, Description = "tags to filter by", Required = false, - Schema = new OpenApiSchema - { - Type = "array", - Items = new OpenApiSchema - { - Type = "string" - } - } + Schema = new JsonSchemaBuilder() + .Type(SchemaValueType.Array) + .Items(new JsonSchemaBuilder().Type(SchemaValueType.String)).Build() }, new OpenApiParameter { @@ -290,11 +179,9 @@ public class OpenApiDocumentTests In = ParameterLocation.Query, Description = "maximum number of results to return", Required = false, - Schema = new OpenApiSchema - { - Type = "integer", - Format = "int32" - } + Schema = new JsonSchemaBuilder() + .Type(SchemaValueType.Integer) + .Format("int32").Build() } }, Responses = new OpenApiResponses @@ -306,19 +193,15 @@ public class OpenApiDocumentTests { ["application/json"] = new OpenApiMediaType { - Schema = new OpenApiSchema - { - Type = "array", - Items = PetSchemaWithReference - } + Schema = new JsonSchemaBuilder() + .Type(SchemaValueType.Array) + .Items(PetSchemaWithReference).Build() }, ["application/xml"] = new OpenApiMediaType { - Schema = new OpenApiSchema - { - Type = "array", - Items = PetSchemaWithReference - } + Schema = new JsonSchemaBuilder() + .Type(SchemaValueType.Array) + .Items(PetSchemaWithReference).Build() } } }, @@ -418,11 +301,10 @@ public class OpenApiDocumentTests In = ParameterLocation.Path, Description = "ID of pet to fetch", Required = true, - Schema = new OpenApiSchema - { - Type = "integer", - Format = "int64" - } + Schema = new JsonSchemaBuilder() + .Type(SchemaValueType.Integer) + .Format("int64") + .Build() } }, Responses = new OpenApiResponses @@ -478,11 +360,10 @@ public class OpenApiDocumentTests In = ParameterLocation.Path, Description = "ID of pet to delete", Required = true, - Schema = new OpenApiSchema - { - Type = "integer", - Format = "int64" - } + Schema = new JsonSchemaBuilder() + .Type(SchemaValueType.Integer) + .Format("int64") + .Build() } }, Responses = new OpenApiResponses @@ -521,88 +402,37 @@ public class OpenApiDocumentTests Components = AdvancedComponentsWithReference }; - public static OpenApiComponents AdvancedComponents = new OpenApiComponents + public static readonly OpenApiComponents AdvancedComponents = new OpenApiComponents { - Schemas = new Dictionary + Schemas = new Dictionary { - ["pet"] = new OpenApiSchema - { - Type = "object", - Required = new HashSet - { - "id", - "name" - }, - Properties = new Dictionary - { - ["id"] = new OpenApiSchema - { - Type = "integer", - Format = "int64" - }, - ["name"] = new OpenApiSchema - { - Type = "string" - }, - ["tag"] = new OpenApiSchema - { - Type = "string" - }, - } - }, - ["newPet"] = new OpenApiSchema - { - Type = "object", - Required = new HashSet - { - "name" - }, - Properties = new Dictionary - { - ["id"] = new OpenApiSchema - { - Type = "integer", - Format = "int64" - }, - ["name"] = new OpenApiSchema - { - Type = "string" - }, - ["tag"] = new OpenApiSchema - { - Type = "string" - }, - } - }, - ["errorModel"] = new OpenApiSchema - { - Type = "object", - Required = new HashSet - { - "code", - "message" - }, - Properties = new Dictionary - { - ["code"] = new OpenApiSchema - { - Type = "integer", - Format = "int32" - }, - ["message"] = new OpenApiSchema - { - Type = "string" - } - } - }, + ["pet"] = new JsonSchemaBuilder() + .Type(SchemaValueType.Object) + .Required("id", "name") + .Properties(("id", new JsonSchemaBuilder().Type(SchemaValueType.Integer).Format("int64").Build()), + ("name", new JsonSchemaBuilder().Type(SchemaValueType.String).Build()), + ("tag", new JsonSchemaBuilder().Type(SchemaValueType.String).Build())), + ["newPet"] = new JsonSchemaBuilder() + .Type(SchemaValueType.Object) + .Required("name") + .Properties( + ("id", new JsonSchemaBuilder().Type(SchemaValueType.Integer).Format("int64").Build()), + ("name", new JsonSchemaBuilder().Type(SchemaValueType.String).Build()), + ("tag", new JsonSchemaBuilder().Type(SchemaValueType.String).Build())), + ["errorModel"] = new JsonSchemaBuilder() + .Type(SchemaValueType.Object) + .Required("code", "message") + .Properties( + ("code", new JsonSchemaBuilder().Type(SchemaValueType.Integer).Format("int32").Build()), + ("message", new JsonSchemaBuilder().Type(SchemaValueType.String).Build())) } }; - public static OpenApiSchema PetSchema = AdvancedComponents.Schemas["pet"]; + public static readonly JsonSchema PetSchema = AdvancedComponents.Schemas["pet"]; - public static OpenApiSchema NewPetSchema = AdvancedComponents.Schemas["newPet"]; + public static readonly JsonSchema NewPetSchema = AdvancedComponents.Schemas["newPet"]; - public static OpenApiSchema ErrorModelSchema = AdvancedComponents.Schemas["errorModel"]; + public static readonly JsonSchema ErrorModelSchema = AdvancedComponents.Schemas["errorModel"]; public OpenApiDocument AdvancedDocument = new OpenApiDocument { @@ -650,14 +480,12 @@ public class OpenApiDocumentTests In = ParameterLocation.Query, Description = "tags to filter by", Required = false, - Schema = new OpenApiSchema - { - Type = "array", - Items = new OpenApiSchema - { - Type = "string" - } - } + Schema = new JsonSchemaBuilder() + .Type(SchemaValueType.Array) + .Items(new JsonSchemaBuilder() + .Type(SchemaValueType.String) + .Build()) + .Build() }, new OpenApiParameter { @@ -665,11 +493,10 @@ public class OpenApiDocumentTests In = ParameterLocation.Query, Description = "maximum number of results to return", Required = false, - Schema = new OpenApiSchema - { - Type = "integer", - Format = "int32" - } + Schema = new JsonSchemaBuilder() + .Type(SchemaValueType.Integer) + .Format("int32") + .Build() } }, Responses = new OpenApiResponses @@ -681,19 +508,17 @@ public class OpenApiDocumentTests { ["application/json"] = new OpenApiMediaType { - Schema = new OpenApiSchema - { - Type = "array", - Items = PetSchema - } + Schema = new JsonSchemaBuilder() + .Type(SchemaValueType.Array) + .Items(PetSchema) + .Build() }, ["application/xml"] = new OpenApiMediaType { - Schema = new OpenApiSchema - { - Type = "array", - Items = PetSchema - } + Schema = new JsonSchemaBuilder() + .Type(SchemaValueType.Array) + .Items(PetSchema) + .Build() } } }, @@ -793,11 +618,10 @@ public class OpenApiDocumentTests In = ParameterLocation.Path, Description = "ID of pet to fetch", Required = true, - Schema = new OpenApiSchema - { - Type = "integer", - Format = "int64" - } + Schema = new JsonSchemaBuilder() + .Type(SchemaValueType.Integer) + .Format("int64") + .Build() } }, Responses = new OpenApiResponses @@ -853,11 +677,10 @@ public class OpenApiDocumentTests In = ParameterLocation.Path, Description = "ID of pet to delete", Required = true, - Schema = new OpenApiSchema - { - Type = "integer", - Format = "int64" - } + Schema = new JsonSchemaBuilder() + .Type(SchemaValueType.Integer) + .Format("int64") + .Build() } }, Responses = new OpenApiResponses @@ -896,7 +719,7 @@ public class OpenApiDocumentTests Components = AdvancedComponents }; - public static OpenApiDocument DocumentWithWebhooks = new OpenApiDocument() + public static readonly OpenApiDocument DocumentWithWebhooks = new OpenApiDocument() { Info = new OpenApiInfo { @@ -918,14 +741,9 @@ public class OpenApiDocumentTests { ["application/json"] = new OpenApiMediaType { - Schema = new OpenApiSchema - { - Reference = new OpenApiReference - { - Id = "Pet", - Type = ReferenceType.Schema - } - } + Schema = new JsonSchemaBuilder() + .Ref("#/components/schemas/Pet") + .Build() } } }, @@ -933,7 +751,7 @@ public class OpenApiDocumentTests { ["200"] = new OpenApiResponse { - Description = "Return a 200 status to indicate that the data was received successfully" + Description = "Return a 200 status to indicate that the data was received successfully" } } } @@ -942,33 +760,20 @@ public class OpenApiDocumentTests }, Components = new OpenApiComponents { - Schemas = new Dictionary + Schemas = new Dictionary { - ["Pet"] = new OpenApiSchema - { - Required = new HashSet { "id", "name" }, - Properties = new Dictionary - { - ["id"] = new OpenApiSchema - { - Type = "integer", - Format = "int64" - }, - ["name"] = new OpenApiSchema - { - Type = "string" - }, - ["tag"] = new OpenApiSchema - { - Type = "string" - } - } - } + ["Pet"] = new JsonSchemaBuilder() + .Required("id", "name") + .Properties( + ("id", new JsonSchemaBuilder().Type(SchemaValueType.Integer).Format("int64").Build()), + ("name", new JsonSchemaBuilder().Type(SchemaValueType.String).Build()), + ("tag", new JsonSchemaBuilder().Type(SchemaValueType.String).Build())) + .Build() } } }; - public static OpenApiDocument DuplicateExtensions = new OpenApiDocument + public static readonly OpenApiDocument DuplicateExtensions = new OpenApiDocument { Info = new OpenApiInfo { @@ -1000,14 +805,12 @@ public class OpenApiDocumentTests In = ParameterLocation.Path, Description = "The first operand", Required = true, - Schema = new OpenApiSchema - { - Type = "integer", - Extensions = new Dictionary + Schema = new JsonSchemaBuilder() + .Type(SchemaValueType.Integer) + .Extensions(new Dictionary { - ["my-extension"] = new OpenApiAny(4), - } - }, + ["my-extension"] = new OpenApiAny(4) + }), Extensions = new Dictionary { ["my-extension"] = new OpenApiAny(4), @@ -1019,14 +822,12 @@ public class OpenApiDocumentTests In = ParameterLocation.Path, Description = "The second operand", Required = true, - Schema = new OpenApiSchema - { - Type = "integer", - Extensions = new Dictionary - { - ["my-extension"] = new OpenApiAny(4), - } - }, + Schema = new JsonSchemaBuilder() + .Type(SchemaValueType.Integer) + .Extensions(new Dictionary + { + ["my-extension"] = new OpenApiAny(4) + }), Extensions = new Dictionary { ["my-extension"] = new OpenApiAny(4), @@ -1042,11 +843,10 @@ public class OpenApiDocumentTests { ["application/json"] = new OpenApiMediaType { - Schema = new OpenApiSchema - { - Type = "array", - Items = PetSchema - } + Schema = new JsonSchemaBuilder() + .Type(SchemaValueType.Array) + .Items(PetSchema) + .Build() }, } } @@ -1268,14 +1068,7 @@ public void SerializeDocumentWithReferenceButNoComponents() { ["application/json"] = new OpenApiMediaType { - Schema = new OpenApiSchema - { - Reference = new OpenApiReference - { - Id = "test", - Type = ReferenceType.Schema - } - } + Schema = new JsonSchemaBuilder().Ref("test") } } } @@ -1286,9 +1079,8 @@ public void SerializeDocumentWithReferenceButNoComponents() } }; - - var reference = document.Paths["/"].Operations[OperationType.Get].Responses["200"].Content["application/json"].Schema.Reference; - + var reference = document.Paths["/"].Operations[OperationType.Get].Responses["200"].Content["application/json"].Schema.GetRef(); + // Act var actual = document.Serialize(OpenApiSpecVersion.OpenApi2_0, OpenApiFormat.Json); @@ -1454,10 +1246,10 @@ public void SerializeV2DocumentWithNonArraySchemaTypeDoesNotWriteOutCollectionFo { Info = new OpenApiInfo(), Paths = new OpenApiPaths - { + { ["/foo"] = new OpenApiPathItem { - Operations = new Dictionary + Operations = new Dictionary { [OperationType.Get] = new OpenApiOperation { @@ -1466,10 +1258,7 @@ public void SerializeV2DocumentWithNonArraySchemaTypeDoesNotWriteOutCollectionFo new OpenApiParameter { In = ParameterLocation.Query, - Schema = new OpenApiSchema - { - Type = "string" - } + Schema = new JsonSchemaBuilder().Type(SchemaValueType.String).Build() } }, Responses = new OpenApiResponses() @@ -1478,7 +1267,7 @@ public void SerializeV2DocumentWithNonArraySchemaTypeDoesNotWriteOutCollectionFo } } }; - + // Act var actual = doc.SerializeAsYaml(OpenApiSpecVersion.OpenApi2_0); @@ -1487,7 +1276,7 @@ public void SerializeV2DocumentWithNonArraySchemaTypeDoesNotWriteOutCollectionFo expected = expected.MakeLineBreaksEnvironmentNeutral(); actual.Should().Be(expected); } - + [Fact] public void SerializeV2DocumentWithStyleAsNullDoesNotWriteOutStyleValue() { @@ -1535,14 +1324,11 @@ public void SerializeV2DocumentWithStyleAsNullDoesNotWriteOutStyleValue() { Name = "id", In = ParameterLocation.Query, - Schema = new OpenApiSchema - { - Type = "object", - AdditionalProperties = new OpenApiSchema - { - Type = "integer" - } - } + Schema = new JsonSchemaBuilder() + .Type(SchemaValueType.Object) + .AdditionalProperties(new JsonSchemaBuilder().Type(SchemaValueType.Integer).Build()) + .AdditionalPropertiesAllowed(true) + .Build() } }, Responses = new OpenApiResponses @@ -1554,10 +1340,8 @@ public void SerializeV2DocumentWithStyleAsNullDoesNotWriteOutStyleValue() { ["text/plain"] = new OpenApiMediaType { - Schema = new OpenApiSchema - { - Type = "string" - } + Schema = new JsonSchemaBuilder() + .Type(SchemaValueType.String) } } } @@ -1575,7 +1359,7 @@ public void SerializeV2DocumentWithStyleAsNullDoesNotWriteOutStyleValue() actual = actual.MakeLineBreaksEnvironmentNeutral(); expected = expected.MakeLineBreaksEnvironmentNeutral(); actual.Should().Be(expected); - } + } [Theory] [InlineData(true)] @@ -1592,7 +1376,7 @@ public async void SerializeDocumentWithWebhooksAsV3JsonWorks(bool produceTerseOu var actual = outputStringWriter.GetStringBuilder().ToString(); // Assert - await Verifier.Verify(actual).UseParameters(produceTerseOutput); + await Verifier.Verify(actual).UseParameters(produceTerseOutput); } [Fact] @@ -1630,14 +1414,14 @@ public void SerializeDocumentWithWebhooksAsV3YamlWorks() 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(); - Assert.Equal(expected, actual); + actual.Should().BeEquivalentTo(expected); } [Fact] diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiExampleTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiExampleTests.cs index a6619a936..0457ba601 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiExampleTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiExampleTests.cs @@ -125,7 +125,7 @@ public async Task SerializeAdvancedExampleAsV3JsonWorks(bool produceTerseOutput) // Assert await Verifier.Verify(outputStringWriter).UseParameters(produceTerseOutput); } - + [Theory] [InlineData(true)] [InlineData(false)] diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiHeaderTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiHeaderTests.cs index d45bd0038..362147430 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiHeaderTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiHeaderTests.cs @@ -4,6 +4,7 @@ using System.Globalization; using System.IO; using System.Threading.Tasks; +using Json.Schema; using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Writers; using VerifyXunit; @@ -19,11 +20,7 @@ public class OpenApiHeaderTests public static OpenApiHeader AdvancedHeader = new OpenApiHeader { Description = "sampleHeader", - Schema = new OpenApiSchema - { - Type = "integer", - Format = "int32" - } + Schema = new JsonSchemaBuilder().Type(SchemaValueType.Integer).Format("int32").Build() }; public static OpenApiHeader ReferencedHeader = new OpenApiHeader @@ -34,11 +31,7 @@ public class OpenApiHeaderTests Id = "example1", }, Description = "sampleHeader", - Schema = new OpenApiSchema - { - Type = "integer", - Format = "int32" - } + Schema = new JsonSchemaBuilder().Type(SchemaValueType.Integer).Format("int32").Build() }; private readonly ITestOutputHelper _output; diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiInfoTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiInfoTests.cs index b76105bde..56d64d226 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiInfoTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiInfoTests.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Text.Json.Nodes; using FluentAssertions; using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Extensions; @@ -233,7 +232,7 @@ public void SerializeInfoObjectWithSummaryAsV31JsonWorks() ""version"": ""1.1.1"", ""summary"": ""This is a sample server for a pet store."" }"; - + // Act var actual = InfoWithSummary.SerializeAsJson(OpenApiSpecVersion.OpenApi3_1); diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiLicenseTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiLicenseTests.cs index 8e30642c2..9a0bc7f82 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiLicenseTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiLicenseTests.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Text.Json.Nodes; using FluentAssertions; using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Extensions; @@ -147,7 +146,7 @@ public void SerializeLicenseWithIdentifierAsJsonWorks() // Assert Assert.Equal(expected.MakeLineBreaksEnvironmentNeutral(), actual.MakeLineBreaksEnvironmentNeutral()); } - + [Fact] public void SerializeLicenseWithIdentifierAsYamlWorks() { diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiOperationTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiOperationTests.cs index de56df52e..15b45dc30 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiOperationTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiOperationTests.cs @@ -1,12 +1,12 @@ -// 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 FluentAssertions; +using Json.Schema; using Microsoft.OpenApi.Extensions; using Microsoft.OpenApi.Models; -using NuGet.Frameworks; using Xunit; using Xunit.Abstractions; @@ -48,12 +48,7 @@ public class OpenApiOperationTests { ["application/json"] = new OpenApiMediaType { - Schema = new OpenApiSchema - { - Type = "number", - Minimum = 5, - Maximum = 10 - } + Schema = new JsonSchemaBuilder().Type(SchemaValueType.Number).Minimum(5).Maximum(10).Build() } } }, @@ -73,12 +68,7 @@ public class OpenApiOperationTests { ["application/json"] = new OpenApiMediaType { - Schema = new OpenApiSchema - { - Type = "number", - Minimum = 5, - Maximum = 10 - } + Schema = new JsonSchemaBuilder().Type(SchemaValueType.Number).Minimum(5).Maximum(10).Build() } } } @@ -140,12 +130,7 @@ public class OpenApiOperationTests { ["application/json"] = new OpenApiMediaType { - Schema = new OpenApiSchema - { - Type = "number", - Minimum = 5, - Maximum = 10 - } + Schema = new JsonSchemaBuilder().Type(SchemaValueType.Number).Minimum(5).Maximum(10).Build() } } }, @@ -165,12 +150,7 @@ public class OpenApiOperationTests { ["application/json"] = new OpenApiMediaType { - Schema = new OpenApiSchema - { - Type = "number", - Minimum = 5, - Maximum = 10 - } + Schema = new JsonSchemaBuilder().Type(SchemaValueType.Number).Minimum(5).Maximum(10).Build() } } } @@ -225,10 +205,7 @@ [new OpenApiSecurityScheme In = ParameterLocation.Path, Description = "ID of pet that needs to be updated", Required = true, - Schema = new OpenApiSchema() - { - Type = "string" - } + Schema = new JsonSchemaBuilder().Type(SchemaValueType.String).Build() } }, RequestBody = new OpenApiRequestBody() @@ -237,49 +214,21 @@ [new OpenApiSecurityScheme { ["application/x-www-form-urlencoded"] = new OpenApiMediaType() { - Schema = new OpenApiSchema() - { - Properties = - { - ["name"] = new OpenApiSchema() - { - Description = "Updated name of the pet", - Type = "string" - }, - ["status"] = new OpenApiSchema() - { - Description = "Updated status of the pet", - Type = "string" - } - }, - Required = new HashSet() - { - "name" - } - } + Schema = new JsonSchemaBuilder() + .Properties( + ("name", new JsonSchemaBuilder().Type(SchemaValueType.String).Description("Updated name of the pet")), + ("status", new JsonSchemaBuilder().Type(SchemaValueType.String).Description("Updated status of the pet"))) + .Required("name") + .Build() }, ["multipart/form-data"] = new OpenApiMediaType() { - Schema = new OpenApiSchema() - { - Properties = - { - ["name"] = new OpenApiSchema() - { - Description = "Updated name of the pet", - Type = "string" - }, - ["status"] = new OpenApiSchema() - { - Description = "Updated status of the pet", - Type = "string" - } - }, - Required = new HashSet() - { - "name" - } - } + Schema = new JsonSchemaBuilder() + .Properties( + ("name", new JsonSchemaBuilder().Type(SchemaValueType.String).Description("Updated name of the pet")), + ("status", new JsonSchemaBuilder().Type(SchemaValueType.String).Description("Updated status of the pet"))) + .Required("name") + .Build() } } }, diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiParameterTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiParameterTests.cs index 4e443c824..eac38d0aa 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiParameterTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiParameterTests.cs @@ -7,6 +7,7 @@ using System.Text.Json.Nodes; using System.Threading.Tasks; using FluentAssertions; +using Json.Schema; using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Extensions; using Microsoft.OpenApi.Models; @@ -45,19 +46,15 @@ public class OpenApiParameterTests Description = "description1", Required = true, Deprecated = false, - Style = ParameterStyle.Simple, Explode = true, - Schema = new OpenApiSchema - { - Title = "title2", - Description = "description2", - OneOf = new List - { - new OpenApiSchema { Type = "number", Format = "double" }, - new OpenApiSchema { Type = "string" } - } - }, + Schema = new JsonSchemaBuilder() + .Title("title2") + .Description("description2") + .OneOf(new JsonSchemaBuilder().Type(SchemaValueType.Number).Format("double").Build(), + new JsonSchemaBuilder().Type(SchemaValueType.String).Build()) + .Build(), + Examples = new Dictionary { ["test"] = new OpenApiExample @@ -75,19 +72,18 @@ public class OpenApiParameterTests Description = "description1", Style = ParameterStyle.Form, Explode = false, - Schema = new OpenApiSchema - { - Type = "array", - Items = new OpenApiSchema - { - Enum = new List + Schema = new JsonSchemaBuilder() + .Type(SchemaValueType.Array) + .Items( + new JsonSchemaBuilder() + .Enum(new List { - new OpenApiAny("value1"), - new OpenApiAny("value2") - } - } - } - + new OpenApiAny("value1").Node, + new OpenApiAny("value2").Node + }) + .Build()) + .Build() + }; public static OpenApiParameter ParameterWithFormStyleAndExplodeTrue = new OpenApiParameter @@ -97,34 +93,32 @@ public class OpenApiParameterTests Description = "description1", Style = ParameterStyle.Form, Explode = true, - Schema = new OpenApiSchema - { - Type = "array", - Items = new OpenApiSchema - { - Enum = new List + Schema = new JsonSchemaBuilder() + .Type(SchemaValueType.Array) + .Items( + new JsonSchemaBuilder() + .Enum(new List { - new OpenApiAny("value1"), - new OpenApiAny("value2") - } - } - } + new OpenApiAny("value1").Node, + new OpenApiAny("value2").Node + }) + .Build()) + .Build() }; - + public static OpenApiParameter QueryParameterWithMissingStyle = new OpenApiParameter - { + { Name = "id", In = ParameterLocation.Query, - Schema = new OpenApiSchema - { - Type = "object", - AdditionalProperties = new OpenApiSchema - { - Type = "integer" - } - } - }; + Schema = new JsonSchemaBuilder() + .Type(SchemaValueType.Object) + .AdditionalProperties( + new JsonSchemaBuilder() + .Type(SchemaValueType.Integer).Build()) + .AdditionalPropertiesAllowed(true) + .Build() + }; public static OpenApiParameter AdvancedHeaderParameterWithSchemaReference = new OpenApiParameter { @@ -136,15 +130,7 @@ public class OpenApiParameterTests Style = ParameterStyle.Simple, Explode = true, - Schema = new OpenApiSchema - { - Reference = new OpenApiReference - { - Type = ReferenceType.Schema, - Id = "schemaObject1" - }, - UnresolvedReference = true - }, + Schema = new JsonSchemaBuilder().Ref("schemaObject1").Build(), Examples = new Dictionary { ["test"] = new OpenApiExample @@ -165,10 +151,7 @@ public class OpenApiParameterTests Style = ParameterStyle.Simple, Explode = true, - Schema = new OpenApiSchema - { - Type = "object" - }, + Schema = new JsonSchemaBuilder().Type(SchemaValueType.Object), Examples = new Dictionary { ["test"] = new OpenApiExample @@ -201,7 +184,7 @@ public void WhenStyleIsFormTheDefaultValueOfExplodeShouldBeTrueOtherwiseFalse(Pa // Act & Assert parameter.Explode.Should().Be(expectedExplode); - } + } [Theory] [InlineData(ParameterLocation.Path, ParameterStyle.Simple)] @@ -223,7 +206,7 @@ public void WhenStyleAndInIsNullTheDefaultValueOfStyleShouldBeSimple(ParameterLo // Act & Assert parameter.SerializeAsV3(writer); writer.Flush(); - + parameter.Style.Should().Be(expectedStyle); } @@ -393,23 +376,6 @@ public async Task SerializeReferencedParameterAsV2JsonWithoutReferenceWorksAsync 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 OpenApiJsonWriterSettings { Terse = produceTerseOutput }); - - // Act - AdvancedHeaderParameterWithSchemaReference.SerializeAsV2(writer); - writer.Flush(); - - // Assert - await Verifier.Verify(outputStringWriter).UseParameters(produceTerseOutput); - } - [Theory] [InlineData(true)] [InlineData(false)] diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiReferenceTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiReferenceTests.cs index b9edd2a32..fc72c5e0b 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiReferenceTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiReferenceTests.cs @@ -168,7 +168,7 @@ public void SerializeExternalReferenceAsJsonV2Works() var reference = new OpenApiReference { ExternalResource = "main.json", - Type= ReferenceType.Schema, + Type = ReferenceType.Schema, Id = "Pets" }; @@ -208,7 +208,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 = @"{ ""$ref"": ""main.json#/components/schemas/Pets"" @@ -227,7 +227,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 78fcd0d07..c593435c9 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiRequestBodyTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiRequestBodyTests.cs @@ -4,6 +4,7 @@ using System.Globalization; using System.IO; using System.Threading.Tasks; +using Json.Schema; using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Writers; using VerifyXunit; @@ -24,10 +25,7 @@ public class OpenApiRequestBodyTests { ["application/json"] = new OpenApiMediaType { - Schema = new OpenApiSchema - { - Type = "string" - } + Schema = new JsonSchemaBuilder().Type(SchemaValueType.String).Build() } } }; @@ -45,10 +43,7 @@ public class OpenApiRequestBodyTests { ["application/json"] = new OpenApiMediaType { - Schema = new OpenApiSchema - { - Type = "string" - } + Schema = new JsonSchemaBuilder().Type(SchemaValueType.String).Build() } } }; diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiResponseTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiResponseTests.cs index 7c6d6013e..c9bd5d56f 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiResponseTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiResponseTests.cs @@ -4,9 +4,9 @@ using System.Collections.Generic; using System.Globalization; using System.IO; -using System.Text.Json.Nodes; using System.Threading.Tasks; using FluentAssertions; +using Json.Schema; using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Extensions; using Microsoft.OpenApi.Interfaces; @@ -24,21 +24,47 @@ public class OpenApiResponseTests { public static OpenApiResponse BasicResponse = new OpenApiResponse(); - public static OpenApiResponse AdvancedResponse = new OpenApiResponse + public static OpenApiResponse AdvancedV2Response = new OpenApiResponse { Description = "A complex object array response", Content = { ["text/plain"] = new OpenApiMediaType { - Schema = new OpenApiSchema + Schema = new JsonSchemaBuilder() + .Type(SchemaValueType.Array) + .Items(new JsonSchemaBuilder().Ref("#/definitions/customType")), + Example = new OpenApiAny("Blabla"), + Extensions = new Dictionary { - Type = "array", - Items = new OpenApiSchema - { - Reference = new OpenApiReference {Type = ReferenceType.Schema, Id = "customType"} - } + ["myextension"] = new OpenApiAny("myextensionvalue"), }, + } + }, + Headers = + { + ["X-Rate-Limit-Limit"] = new OpenApiHeader + { + Description = "The number of allowed requests in the current period", + Schema = new JsonSchemaBuilder().Type(SchemaValueType.Integer) + }, + ["X-Rate-Limit-Reset"] = new OpenApiHeader + { + Description = "The number of seconds left in the current period", + Schema = new JsonSchemaBuilder().Type(SchemaValueType.Integer) + }, + } + }; + public static OpenApiResponse AdvancedV3Response = new OpenApiResponse + { + Description = "A complex object array response", + Content = + { + ["text/plain"] = new OpenApiMediaType + { + Schema = new JsonSchemaBuilder() + .Type(SchemaValueType.Array) + .Items(new JsonSchemaBuilder().Ref("#/components/schemas/customType")), Example = new OpenApiAny("Blabla"), Extensions = new Dictionary { @@ -51,23 +77,16 @@ public class OpenApiResponseTests ["X-Rate-Limit-Limit"] = new OpenApiHeader { Description = "The number of allowed requests in the current period", - Schema = new OpenApiSchema - { - Type = "integer" - } + Schema = new JsonSchemaBuilder().Type(SchemaValueType.Integer) }, ["X-Rate-Limit-Reset"] = new OpenApiHeader { Description = "The number of seconds left in the current period", - Schema = new OpenApiSchema - { - Type = "integer" - } + Schema = new JsonSchemaBuilder().Type(SchemaValueType.Integer) }, } }; - - public static OpenApiResponse ReferencedResponse = new OpenApiResponse + public static OpenApiResponse ReferencedV2Response = new OpenApiResponse { Reference = new OpenApiReference { @@ -79,14 +98,9 @@ public class OpenApiResponseTests { ["text/plain"] = new OpenApiMediaType { - Schema = new OpenApiSchema - { - Type = "array", - Items = new OpenApiSchema - { - Reference = new OpenApiReference {Type = ReferenceType.Schema, Id = "customType"} - } - } + Schema = new JsonSchemaBuilder() + .Type(SchemaValueType.Array) + .Items(new JsonSchemaBuilder().Ref("#/definitions/customType")) } }, Headers = @@ -94,18 +108,43 @@ public class OpenApiResponseTests ["X-Rate-Limit-Limit"] = new OpenApiHeader { Description = "The number of allowed requests in the current period", - Schema = new OpenApiSchema - { - Type = "integer" - } + Schema = new JsonSchemaBuilder().Type(SchemaValueType.Integer) }, ["X-Rate-Limit-Reset"] = new OpenApiHeader { Description = "The number of seconds left in the current period", - Schema = new OpenApiSchema - { - Type = "integer" - } + Schema = new JsonSchemaBuilder().Type(SchemaValueType.Integer) + }, + } + }; + public static OpenApiResponse ReferencedV3Response = new OpenApiResponse + { + Reference = new OpenApiReference + { + Type = ReferenceType.Response, + Id = "example1" + }, + Description = "A complex object array response", + Content = + { + ["text/plain"] = new OpenApiMediaType + { + Schema = new JsonSchemaBuilder() + .Type(SchemaValueType.Array) + .Items(new JsonSchemaBuilder().Ref("#/components/schemas/customType")) + } + }, + Headers = + { + ["X-Rate-Limit-Limit"] = new OpenApiHeader + { + Description = "The number of allowed requests in the current period", + Schema = new JsonSchemaBuilder().Type(SchemaValueType.Integer) + }, + ["X-Rate-Limit-Reset"] = new OpenApiHeader + { + Description = "The number of seconds left in the current period", + Schema = new JsonSchemaBuilder().Type(SchemaValueType.Integer) }, } }; @@ -175,7 +214,7 @@ public void SerializeAdvancedResponseAsV3JsonWorks() }"; // Act - var actual = AdvancedResponse.SerializeAsJson(OpenApiSpecVersion.OpenApi3_0); + var actual = AdvancedV3Response.SerializeAsJson(OpenApiSpecVersion.OpenApi3_0); // Assert actual = actual.MakeLineBreaksEnvironmentNeutral(); @@ -208,7 +247,7 @@ public void SerializeAdvancedResponseAsV3YamlWorks() myextension: myextensionvalue"; // Act - var actual = AdvancedResponse.SerializeAsYaml(OpenApiSpecVersion.OpenApi3_0); + var actual = AdvancedV3Response.SerializeAsYaml(OpenApiSpecVersion.OpenApi3_0); // Assert actual = actual.MakeLineBreaksEnvironmentNeutral(); @@ -245,7 +284,7 @@ public void SerializeAdvancedResponseAsV2JsonWorks() }"; // Act - var actual = AdvancedResponse.SerializeAsJson(OpenApiSpecVersion.OpenApi2_0); + var actual = AdvancedV2Response.SerializeAsJson(OpenApiSpecVersion.OpenApi2_0); // Assert actual = actual.MakeLineBreaksEnvironmentNeutral(); @@ -275,7 +314,7 @@ public void SerializeAdvancedResponseAsV2YamlWorks() type: integer"; // Act - var actual = AdvancedResponse.SerializeAsYaml(OpenApiSpecVersion.OpenApi2_0); + var actual = AdvancedV2Response.SerializeAsYaml(OpenApiSpecVersion.OpenApi2_0); // Assert actual = actual.MakeLineBreaksEnvironmentNeutral(); @@ -293,7 +332,7 @@ public async Task SerializeReferencedResponseAsV3JsonWorksAsync(bool produceTers var writer = new OpenApiJsonWriter(outputStringWriter, new OpenApiJsonWriterSettings { Terse = produceTerseOutput }); // Act - ReferencedResponse.SerializeAsV3(writer); + ReferencedV3Response.SerializeAsV3(writer); writer.Flush(); // Assert @@ -310,7 +349,7 @@ public async Task SerializeReferencedResponseAsV3JsonWithoutReferenceWorksAsync( var writer = new OpenApiJsonWriter(outputStringWriter, new OpenApiJsonWriterSettings { Terse = produceTerseOutput }); // Act - ReferencedResponse.SerializeAsV3WithoutReference(writer); + ReferencedV3Response.SerializeAsV3WithoutReference(writer); writer.Flush(); // Assert @@ -327,7 +366,7 @@ public async Task SerializeReferencedResponseAsV2JsonWorksAsync(bool produceTers var writer = new OpenApiJsonWriter(outputStringWriter, new OpenApiJsonWriterSettings { Terse = produceTerseOutput }); // Act - ReferencedResponse.SerializeAsV2(writer); + ReferencedV2Response.SerializeAsV2(writer); writer.Flush(); // Assert @@ -344,7 +383,7 @@ public async Task SerializeReferencedResponseAsV2JsonWithoutReferenceWorksAsync( var writer = new OpenApiJsonWriter(outputStringWriter, new OpenApiJsonWriterSettings { Terse = produceTerseOutput }); // Act - ReferencedResponse.SerializeAsV2WithoutReference(writer); + ReferencedV2Response.SerializeAsV2WithoutReference(writer); writer.Flush(); // Assert diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.cs index e5959efd6..a31df76cb 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.cs @@ -1,483 +1,490 @@ -// 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.Threading.Tasks; -using FluentAssertions; -using Microsoft.OpenApi.Any; -using Microsoft.OpenApi.Extensions; -using Microsoft.OpenApi.Models; -using Microsoft.OpenApi.Writers; -using VerifyXunit; -using Xunit; -using Xunit.Abstractions; - -namespace Microsoft.OpenApi.Tests.Models -{ - [Collection("DefaultSettings")] - [UsesVerify] - public class OpenApiSchemaTests - { - public static OpenApiSchema BasicSchema = new OpenApiSchema(); - - public static OpenApiSchema AdvancedSchemaNumber = new OpenApiSchema - { - Title = "title1", - MultipleOf = 3, - Maximum = 42, - ExclusiveMinimum = true, - Minimum = 10, - Default = new OpenApiAny(15), - Type = "integer", - - Nullable = true, - ExternalDocs = new OpenApiExternalDocs - { - Url = new Uri("http://example.com/externalDocs") - } - }; - - public static OpenApiSchema AdvancedSchemaObject = new OpenApiSchema - { - Title = "title1", - Properties = new Dictionary - { - ["property1"] = new OpenApiSchema - { - Properties = new Dictionary - { - ["property2"] = new OpenApiSchema - { - Type = "integer" - }, - ["property3"] = new OpenApiSchema - { - Type = "string", - MaxLength = 15 - } - }, - }, - ["property4"] = new OpenApiSchema - { - Properties = new Dictionary - { - ["property5"] = new OpenApiSchema - { - Properties = new Dictionary - { - ["property6"] = new OpenApiSchema - { - Type = "boolean" - } - } - }, - ["property7"] = new OpenApiSchema - { - Type = "string", - MinLength = 2 - } - }, - }, - }, - Nullable = true, - ExternalDocs = new OpenApiExternalDocs - { - Url = new Uri("http://example.com/externalDocs") - } - }; - - public static OpenApiSchema AdvancedSchemaWithAllOf = new OpenApiSchema - { - Title = "title1", - AllOf = new List - { - new OpenApiSchema - { - Title = "title2", - Properties = new Dictionary - { - ["property1"] = new OpenApiSchema - { - Type = "integer" - }, - ["property2"] = new OpenApiSchema - { - Type = "string", - MaxLength = 15 - } - }, - }, - new OpenApiSchema - { - Title = "title3", - Properties = new Dictionary - { - ["property3"] = new OpenApiSchema - { - Properties = new Dictionary - { - ["property4"] = new OpenApiSchema - { - Type = "boolean" - } - } - }, - ["property5"] = new OpenApiSchema - { - Type = "string", - MinLength = 2 - } - }, - Nullable = true - }, - }, - Nullable = true, - ExternalDocs = new OpenApiExternalDocs - { - Url = new Uri("http://example.com/externalDocs") - } - }; - - public static OpenApiSchema ReferencedSchema = new OpenApiSchema - { - Title = "title1", - MultipleOf = 3, - Maximum = 42, - ExclusiveMinimum = true, - Minimum = 10, - Default = new OpenApiAny(15), - Type = "integer", - - Nullable = true, - ExternalDocs = new OpenApiExternalDocs - { - Url = new Uri("http://example.com/externalDocs") - }, - - Reference = new OpenApiReference - { - Type = ReferenceType.Schema, - Id = "schemaObject1" - } - }; - - public static OpenApiSchema AdvancedSchemaWithRequiredPropertiesObject = new OpenApiSchema - { - Title = "title1", - Required = new HashSet() { "property1" }, - Properties = new Dictionary - { - ["property1"] = new OpenApiSchema - { - Required = new HashSet() { "property3" }, - Properties = new Dictionary - { - ["property2"] = new OpenApiSchema - { - Type = "integer" - }, - ["property3"] = new OpenApiSchema - { - Type = "string", - MaxLength = 15, - ReadOnly = true - } - }, - ReadOnly = true, - }, - ["property4"] = new OpenApiSchema - { - Properties = new Dictionary - { - ["property5"] = new OpenApiSchema - { - Properties = new Dictionary - { - ["property6"] = new OpenApiSchema - { - Type = "boolean" - } - } - }, - ["property7"] = new OpenApiSchema - { - Type = "string", - MinLength = 2 - } - }, - ReadOnly = true, - }, - }, - Nullable = true, - ExternalDocs = new OpenApiExternalDocs - { - Url = new Uri("http://example.com/externalDocs") - } - }; - - private readonly ITestOutputHelper _output; - - public OpenApiSchemaTests(ITestOutputHelper output) - { - _output = output; - } - - [Fact] - public void SerializeBasicSchemaAsV3JsonWorks() - { - // Arrange - var expected = @"{ }"; - - // Act - var actual = BasicSchema.SerializeAsJson(OpenApiSpecVersion.OpenApi3_0); - - // Assert - actual = actual.MakeLineBreaksEnvironmentNeutral(); - expected = expected.MakeLineBreaksEnvironmentNeutral(); - actual.Should().Be(expected); - } - - [Fact] - public void SerializeAdvancedSchemaNumberAsV3JsonWorks() - { - // Arrange - var expected = @"{ - ""title"": ""title1"", - ""multipleOf"": 3, - ""maximum"": 42, - ""minimum"": 10, - ""exclusiveMinimum"": true, - ""type"": ""integer"", - ""default"": 15, - ""nullable"": true, - ""externalDocs"": { - ""url"": ""http://example.com/externalDocs"" - } -}"; - - // Act - var actual = AdvancedSchemaNumber.SerializeAsJson(OpenApiSpecVersion.OpenApi3_0); - - // Assert - actual = actual.MakeLineBreaksEnvironmentNeutral(); - expected = expected.MakeLineBreaksEnvironmentNeutral(); - actual.Should().Be(expected); - } - - [Fact] - public void SerializeAdvancedSchemaObjectAsV3JsonWorks() - { - // Arrange - var expected = @"{ - ""title"": ""title1"", - ""properties"": { - ""property1"": { - ""properties"": { - ""property2"": { - ""type"": ""integer"" - }, - ""property3"": { - ""maxLength"": 15, - ""type"": ""string"" - } - } - }, - ""property4"": { - ""properties"": { - ""property5"": { - ""properties"": { - ""property6"": { - ""type"": ""boolean"" - } - } - }, - ""property7"": { - ""minLength"": 2, - ""type"": ""string"" - } - } - } - }, - ""nullable"": true, - ""externalDocs"": { - ""url"": ""http://example.com/externalDocs"" - } -}"; - - // Act - var actual = AdvancedSchemaObject.SerializeAsJson(OpenApiSpecVersion.OpenApi3_0); - - // Assert - actual = actual.MakeLineBreaksEnvironmentNeutral(); - expected = expected.MakeLineBreaksEnvironmentNeutral(); - actual.Should().Be(expected); - } - - [Fact] - public void SerializeAdvancedSchemaWithAllOfAsV3JsonWorks() - { - // Arrange - var expected = @"{ - ""title"": ""title1"", - ""allOf"": [ - { - ""title"": ""title2"", - ""properties"": { - ""property1"": { - ""type"": ""integer"" - }, - ""property2"": { - ""maxLength"": 15, - ""type"": ""string"" - } - } - }, - { - ""title"": ""title3"", - ""properties"": { - ""property3"": { - ""properties"": { - ""property4"": { - ""type"": ""boolean"" - } - } - }, - ""property5"": { - ""minLength"": 2, - ""type"": ""string"" - } - }, - ""nullable"": true - } - ], - ""nullable"": true, - ""externalDocs"": { - ""url"": ""http://example.com/externalDocs"" - } -}"; - - // Act - var actual = AdvancedSchemaWithAllOf.SerializeAsJson(OpenApiSpecVersion.OpenApi3_0); - - // Assert - actual = actual.MakeLineBreaksEnvironmentNeutral(); - expected = expected.MakeLineBreaksEnvironmentNeutral(); - actual.Should().Be(expected); - } - - [Theory] - [InlineData(true)] - [InlineData(false)] - public async Task SerializeReferencedSchemaAsV3WithoutReferenceJsonWorksAsync(bool produceTerseOutput) - { - // Arrange - var outputStringWriter = new StringWriter(CultureInfo.InvariantCulture); - var writer = new OpenApiJsonWriter(outputStringWriter, new OpenApiJsonWriterSettings { Terse = produceTerseOutput }); - - - // Act - ReferencedSchema.SerializeAsV3WithoutReference(writer); - writer.Flush(); - - // Assert - await Verifier.Verify(outputStringWriter).UseParameters(produceTerseOutput); - } - - [Theory] - [InlineData(true)] - [InlineData(false)] - public async Task SerializeReferencedSchemaAsV3JsonWorksAsync(bool produceTerseOutput) - { - // Arrange - var outputStringWriter = new StringWriter(CultureInfo.InvariantCulture); - var writer = new OpenApiJsonWriter(outputStringWriter, new OpenApiJsonWriterSettings { Terse = produceTerseOutput }); - - // Act - ReferencedSchema.SerializeAsV3(writer); - writer.Flush(); - - // Assert - await Verifier.Verify(outputStringWriter).UseParameters(produceTerseOutput); - } - - [Theory] - [InlineData(true)] - [InlineData(false)] - public async Task SerializeSchemaWRequiredPropertiesAsV2JsonWorksAsync(bool produceTerseOutput) - { - // Arrange - var outputStringWriter = new StringWriter(CultureInfo.InvariantCulture); - var writer = new OpenApiJsonWriter(outputStringWriter, new OpenApiJsonWriterSettings { Terse = produceTerseOutput }); - - // Act - AdvancedSchemaWithRequiredPropertiesObject.SerializeAsV2(writer); - writer.Flush(); - - // Assert - await Verifier.Verify(outputStringWriter).UseParameters(produceTerseOutput); - } - - [Fact] - public void SerializeAsV2ShouldSetFormatPropertyInParentSchemaIfPresentInChildrenSchema() - { - // Arrange - var schema = new OpenApiSchema() - { - OneOf = new List - { - new OpenApiSchema - { - Type = "number", - Format = "decimal" - }, - new OpenApiSchema { Type = "string" }, - } - }; - - var outputStringWriter = new StringWriter(CultureInfo.InvariantCulture); - var openApiJsonWriter = new OpenApiJsonWriter(outputStringWriter, new OpenApiJsonWriterSettings { Terse = false }); - - // Act - // Serialize as V2 - schema.SerializeAsV2(openApiJsonWriter); - openApiJsonWriter.Flush(); - - var v2Schema = outputStringWriter.GetStringBuilder().ToString().MakeLineBreaksEnvironmentNeutral(); - - var expectedV2Schema = @"{ - ""format"": ""decimal"", - ""allOf"": [ - { - ""format"": ""decimal"", - ""type"": ""number"" - } - ] -}".MakeLineBreaksEnvironmentNeutral(); - - // Assert - Assert.Equal(expectedV2Schema, v2Schema); - } - - [Fact] - public void OpenApiSchemaCopyConstructorSucceeds() - { - var baseSchema = new OpenApiSchema() - { - Type = "string", - Format = "date" - }; - - var actualSchema = new OpenApiSchema(baseSchema) - { - Nullable = true - }; - - Assert.Equal("string", actualSchema.Type); - Assert.Equal("date", actualSchema.Format); - Assert.True(actualSchema.Nullable); - } - } -} +//// 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.Threading.Tasks; +//using FluentAssertions; +//using Json.Schema; +//using Json.Schema.OpenApi; +//using Microsoft.OpenApi.Any; +//using Microsoft.OpenApi.Extensions; +//using Microsoft.OpenApi.Models; +//using Microsoft.OpenApi.Writers; +//using VerifyXunit; +//using Xunit; +//using Xunit.Abstractions; + +//namespace Microsoft.OpenApi.Tests.Models +//{ +// [Collection("DefaultSettings")] +// [UsesVerify] +// public class OpenApiSchemaTests +// { +// public static JsonSchema BasicSchema = new JsonSchemaBuilder().Build(); + +// public static JsonSchema AdvancedSchemaNumber = new JsonSchemaBuilder() +// .Title("title1") +// .MultipleOf(3) +// .Maximum(42) +// .ExclusiveMinimum(10) +// .Default(new OpenApiAny(15).Node) +// .AnyOf(new JsonSchemaBuilder().Type(SchemaValueType.Null).Build(), new JsonSchemaBuilder().Type(SchemaValueType.Integer).Build()) +// .ExternalDocs(new Uri("http://example.com/externalDocs"), string.Empty, null).Build(); + +// public static JsonSchema AdvancedSchemaObject = new JsonSchemaBuilder() +// .Title("title1") +// .Properties( +// ("property1", new JsonSchemaBuilder() +// .Properties( +// ("property2", new JsonSchemaBuilder() +// .Type(SchemaValueType.Integer) +// .Build()), +// ("property3", new JsonSchemaBuilder() +// .Type(SchemaValueType.String) +// .MaxLength(15) +// .Build())) +// .Build())) +// .Build(); +// { +// Title = "title1", +// Properties = new Dictionary +// { +// ["property1"] = new OpenApiSchema +// { +// Properties = new Dictionary +// { +// ["property2"] = new OpenApiSchema +// { +// Type = "integer" +// }, +// ["property3"] = new OpenApiSchema +// { +// Type = "string", +// MaxLength = 15 +// } +// }, +// }, +// ["property4"] = new OpenApiSchema +// { +// Properties = new Dictionary +// { +// ["property5"] = new OpenApiSchema +// { +// Properties = new Dictionary +// { +// ["property6"] = new OpenApiSchema +// { +// Type = "boolean" +// } +// } +// }, +// ["property7"] = new OpenApiSchema +// { +// Type = "string", +// MinLength = 2 +// } +// }, +// }, +// }, +// Nullable = true, +// ExternalDocs = new OpenApiExternalDocs +// { +// Url = new Uri("http://example.com/externalDocs") +// } +// }; + +// public static OpenApiSchema AdvancedSchemaWithAllOf = new OpenApiSchema +// { +// Title = "title1", +// AllOf = new List +// { +// new OpenApiSchema +// { +// Title = "title2", +// Properties = new Dictionary +// { +// ["property1"] = new OpenApiSchema +// { +// Type = "integer" +// }, +// ["property2"] = new OpenApiSchema +// { +// Type = "string", +// MaxLength = 15 +// } +// }, +// }, +// new OpenApiSchema +// { +// Title = "title3", +// Properties = new Dictionary +// { +// ["property3"] = new OpenApiSchema +// { +// Properties = new Dictionary +// { +// ["property4"] = new OpenApiSchema +// { +// Type = "boolean" +// } +// } +// }, +// ["property5"] = new OpenApiSchema +// { +// Type = "string", +// MinLength = 2 +// } +// }, +// Nullable = true +// }, +// }, +// Nullable = true, +// ExternalDocs = new OpenApiExternalDocs +// { +// Url = new Uri("http://example.com/externalDocs") +// } +// }; + +// public static OpenApiSchema ReferencedSchema = new OpenApiSchema +// { +// Title = "title1", +// MultipleOf = 3, +// Maximum = 42, +// ExclusiveMinimum = true, +// Minimum = 10, +// Default = new OpenApiAny(15), +// Type = "integer", + +// Nullable = true, +// ExternalDocs = new OpenApiExternalDocs +// { +// Url = new Uri("http://example.com/externalDocs") +// }, + +// Reference = new OpenApiReference +// { +// Type = ReferenceType.Schema, +// Id = "schemaObject1" +// } +// }; + +// public static OpenApiSchema AdvancedSchemaWithRequiredPropertiesObject = new OpenApiSchema +// { +// Title = "title1", +// Required = new HashSet() { "property1" }, +// Properties = new Dictionary +// { +// ["property1"] = new OpenApiSchema +// { +// Required = new HashSet() { "property3" }, +// Properties = new Dictionary +// { +// ["property2"] = new OpenApiSchema +// { +// Type = "integer" +// }, +// ["property3"] = new OpenApiSchema +// { +// Type = "string", +// MaxLength = 15, +// ReadOnly = true +// } +// }, +// ReadOnly = true, +// }, +// ["property4"] = new OpenApiSchema +// { +// Properties = new Dictionary +// { +// ["property5"] = new OpenApiSchema +// { +// Properties = new Dictionary +// { +// ["property6"] = new OpenApiSchema +// { +// Type = "boolean" +// } +// } +// }, +// ["property7"] = new OpenApiSchema +// { +// Type = "string", +// MinLength = 2 +// } +// }, +// ReadOnly = true, +// }, +// }, +// Nullable = true, +// ExternalDocs = new OpenApiExternalDocs +// { +// Url = new Uri("http://example.com/externalDocs") +// } +// }; + +// private readonly ITestOutputHelper _output; + +// public OpenApiSchemaTests(ITestOutputHelper output) +// { +// _output = output; +// } + +// [Fact] +// public void SerializeBasicSchemaAsV3JsonWorks() +// { +// // Arrange +// var expected = @"{ }"; + +// // Act +// var actual = BasicSchema.SerializeAsJson(OpenApiSpecVersion.OpenApi3_0); + +// // Assert +// actual = actual.MakeLineBreaksEnvironmentNeutral(); +// expected = expected.MakeLineBreaksEnvironmentNeutral(); +// actual.Should().Be(expected); +// } + +// [Fact] +// public void SerializeAdvancedSchemaNumberAsV3JsonWorks() +// { +// // Arrange +// var expected = @"{ +// ""title"": ""title1"", +// ""multipleOf"": 3, +// ""maximum"": 42, +// ""minimum"": 10, +// ""exclusiveMinimum"": true, +// ""type"": ""integer"", +// ""default"": 15, +// ""nullable"": true, +// ""externalDocs"": { +// ""url"": ""http://example.com/externalDocs"" +// } +//}"; + +// // Act +// var actual = AdvancedSchemaNumber.SerializeAsJson(OpenApiSpecVersion.OpenApi3_0); + +// // Assert +// actual = actual.MakeLineBreaksEnvironmentNeutral(); +// expected = expected.MakeLineBreaksEnvironmentNeutral(); +// actual.Should().Be(expected); +// } + +// [Fact] +// public void SerializeAdvancedSchemaObjectAsV3JsonWorks() +// { +// // Arrange +// var expected = @"{ +// ""title"": ""title1"", +// ""properties"": { +// ""property1"": { +// ""properties"": { +// ""property2"": { +// ""type"": ""integer"" +// }, +// ""property3"": { +// ""maxLength"": 15, +// ""type"": ""string"" +// } +// } +// }, +// ""property4"": { +// ""properties"": { +// ""property5"": { +// ""properties"": { +// ""property6"": { +// ""type"": ""boolean"" +// } +// } +// }, +// ""property7"": { +// ""minLength"": 2, +// ""type"": ""string"" +// } +// } +// } +// }, +// ""nullable"": true, +// ""externalDocs"": { +// ""url"": ""http://example.com/externalDocs"" +// } +//}"; + +// // Act +// var actual = AdvancedSchemaObject.SerializeAsJson(OpenApiSpecVersion.OpenApi3_0); + +// // Assert +// actual = actual.MakeLineBreaksEnvironmentNeutral(); +// expected = expected.MakeLineBreaksEnvironmentNeutral(); +// actual.Should().Be(expected); +// } + +// [Fact] +// public void SerializeAdvancedSchemaWithAllOfAsV3JsonWorks() +// { +// // Arrange +// var expected = @"{ +// ""title"": ""title1"", +// ""allOf"": [ +// { +// ""title"": ""title2"", +// ""properties"": { +// ""property1"": { +// ""type"": ""integer"" +// }, +// ""property2"": { +// ""maxLength"": 15, +// ""type"": ""string"" +// } +// } +// }, +// { +// ""title"": ""title3"", +// ""properties"": { +// ""property3"": { +// ""properties"": { +// ""property4"": { +// ""type"": ""boolean"" +// } +// } +// }, +// ""property5"": { +// ""minLength"": 2, +// ""type"": ""string"" +// } +// }, +// ""nullable"": true +// } +// ], +// ""nullable"": true, +// ""externalDocs"": { +// ""url"": ""http://example.com/externalDocs"" +// } +//}"; + +// // Act +// var actual = AdvancedSchemaWithAllOf.SerializeAsJson(OpenApiSpecVersion.OpenApi3_0); + +// // Assert +// actual = actual.MakeLineBreaksEnvironmentNeutral(); +// expected = expected.MakeLineBreaksEnvironmentNeutral(); +// actual.Should().Be(expected); +// } + +// [Theory] +// [InlineData(true)] +// [InlineData(false)] +// public async Task SerializeReferencedSchemaAsV3WithoutReferenceJsonWorksAsync(bool produceTerseOutput) +// { +// // Arrange +// var outputStringWriter = new StringWriter(CultureInfo.InvariantCulture); +// var writer = new OpenApiJsonWriter(outputStringWriter, new OpenApiJsonWriterSettings { Terse = produceTerseOutput }); + + +// // Act +// ReferencedSchema.SerializeAsV3WithoutReference(writer); +// writer.Flush(); + +// // Assert +// await Verifier.Verify(outputStringWriter).UseParameters(produceTerseOutput); +// } + +// [Theory] +// [InlineData(true)] +// [InlineData(false)] +// public async Task SerializeReferencedSchemaAsV3JsonWorksAsync(bool produceTerseOutput) +// { +// // Arrange +// var outputStringWriter = new StringWriter(CultureInfo.InvariantCulture); +// var writer = new OpenApiJsonWriter(outputStringWriter, new OpenApiJsonWriterSettings { Terse = produceTerseOutput }); + +// // Act +// ReferencedSchema.SerializeAsV3(writer); +// writer.Flush(); + +// // Assert +// await Verifier.Verify(outputStringWriter).UseParameters(produceTerseOutput); +// } + +// [Theory] +// [InlineData(true)] +// [InlineData(false)] +// public async Task SerializeSchemaWRequiredPropertiesAsV2JsonWorksAsync(bool produceTerseOutput) +// { +// // Arrange +// var outputStringWriter = new StringWriter(CultureInfo.InvariantCulture); +// var writer = new OpenApiJsonWriter(outputStringWriter, new OpenApiJsonWriterSettings { Terse = produceTerseOutput }); + +// // Act +// AdvancedSchemaWithRequiredPropertiesObject.SerializeAsV2(writer); +// writer.Flush(); + +// // Assert +// await Verifier.Verify(outputStringWriter).UseParameters(produceTerseOutput); +// } + +// [Fact] +// public void SerializeAsV2ShouldSetFormatPropertyInParentSchemaIfPresentInChildrenSchema() +// { +// // Arrange +// var schema = new OpenApiSchema() +// { +// OneOf = new List +// { +// new OpenApiSchema +// { +// Type = "number", +// Format = "decimal" +// }, +// new OpenApiSchema { Type = "string" }, +// } +// }; + +// var outputStringWriter = new StringWriter(CultureInfo.InvariantCulture); +// var openApiJsonWriter = new OpenApiJsonWriter(outputStringWriter, new OpenApiJsonWriterSettings { Terse = false }); + +// // Act +// // Serialize as V2 +// schema.SerializeAsV2(openApiJsonWriter); +// openApiJsonWriter.Flush(); + +// var v2Schema = outputStringWriter.GetStringBuilder().ToString().MakeLineBreaksEnvironmentNeutral(); + +// var expectedV2Schema = @"{ +// ""format"": ""decimal"", +// ""allOf"": [ +// { +// ""format"": ""decimal"", +// ""type"": ""number"" +// } +// ] +//}".MakeLineBreaksEnvironmentNeutral(); + +// // Assert +// Assert.Equal(expectedV2Schema, v2Schema); +// } + +// [Fact] +// public void OpenApiSchemaCopyConstructorSucceeds() +// { +// var baseSchema = new OpenApiSchema() +// { +// Type = "string", +// Format = "date" +// }; + +// var actualSchema = new OpenApiSchema(baseSchema) +// { +// Nullable = true +// }; + +// Assert.Equal("string", actualSchema.Type); +// Assert.Equal("date", actualSchema.Format); +// Assert.True(actualSchema.Nullable); +// } +// } +//} diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiSecuritySchemeTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiSecuritySchemeTests.cs index 44a388d90..a2e1269b4 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiSecuritySchemeTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiSecuritySchemeTests.cs @@ -32,7 +32,7 @@ public class OpenApiSecuritySchemeTests { Description = "description1", Type = SecuritySchemeType.Http, - Scheme = OpenApiConstants.Basic + Scheme = OpenApiConstants.Basic }; diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiTagTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiTagTests.cs index a133a7fcb..f279462d9 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiTagTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiTagTests.cs @@ -4,7 +4,6 @@ using System.Collections.Generic; using System.Globalization; using System.IO; -using System.Text.Json.Nodes; using System.Threading.Tasks; using FluentAssertions; using Microsoft.OpenApi.Interfaces; @@ -47,7 +46,7 @@ public class OpenApiTagTests Id = "pet" } }; - + [Theory] [InlineData(true)] [InlineData(false)] diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiXmlTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiXmlTests.cs index 67f2f1788..b30c6a2d7 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiXmlTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiXmlTests.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Text.Json.Nodes; using FluentAssertions; using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Extensions; diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiRequestBodyReferenceTests.SerializeRequestBodyReferenceAsV31JsonWorks_produceTerseOutput=False.verified.txt b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiRequestBodyReferenceTests.SerializeRequestBodyReferenceAsV31JsonWorks_produceTerseOutput=False.verified.txt index cdbbe00d1..c4d9bef00 100644 --- a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiRequestBodyReferenceTests.SerializeRequestBodyReferenceAsV31JsonWorks_produceTerseOutput=False.verified.txt +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiRequestBodyReferenceTests.SerializeRequestBodyReferenceAsV31JsonWorks_produceTerseOutput=False.verified.txt @@ -3,7 +3,15 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/UserSchema" + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "email": { + "type": "string" + } + } } } } diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiRequestBodyReferenceTests.SerializeRequestBodyReferenceAsV31JsonWorks_produceTerseOutput=True.verified.txt b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiRequestBodyReferenceTests.SerializeRequestBodyReferenceAsV31JsonWorks_produceTerseOutput=True.verified.txt index e82312f67..3d91acf86 100644 --- a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiRequestBodyReferenceTests.SerializeRequestBodyReferenceAsV31JsonWorks_produceTerseOutput=True.verified.txt +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiRequestBodyReferenceTests.SerializeRequestBodyReferenceAsV31JsonWorks_produceTerseOutput=True.verified.txt @@ -1 +1 @@ -{"description":"User creation request body","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserSchema"}}}} \ No newline at end of file +{"description":"User creation request body","content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"email":{"type":"string"}}}}}} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiRequestBodyReferenceTests.SerializeRequestBodyReferenceAsV3JsonWorks_produceTerseOutput=False.verified.txt b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiRequestBodyReferenceTests.SerializeRequestBodyReferenceAsV3JsonWorks_produceTerseOutput=False.verified.txt index cdbbe00d1..c4d9bef00 100644 --- a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiRequestBodyReferenceTests.SerializeRequestBodyReferenceAsV3JsonWorks_produceTerseOutput=False.verified.txt +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiRequestBodyReferenceTests.SerializeRequestBodyReferenceAsV3JsonWorks_produceTerseOutput=False.verified.txt @@ -3,7 +3,15 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/UserSchema" + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "email": { + "type": "string" + } + } } } } diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiRequestBodyReferenceTests.SerializeRequestBodyReferenceAsV3JsonWorks_produceTerseOutput=True.verified.txt b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiRequestBodyReferenceTests.SerializeRequestBodyReferenceAsV3JsonWorks_produceTerseOutput=True.verified.txt index e82312f67..3d91acf86 100644 --- a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiRequestBodyReferenceTests.SerializeRequestBodyReferenceAsV3JsonWorks_produceTerseOutput=True.verified.txt +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiRequestBodyReferenceTests.SerializeRequestBodyReferenceAsV3JsonWorks_produceTerseOutput=True.verified.txt @@ -1 +1 @@ -{"description":"User creation request body","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserSchema"}}}} \ No newline at end of file +{"description":"User creation request body","content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"email":{"type":"string"}}}}}} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiRequestBodyReferenceTests.cs b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiRequestBodyReferenceTests.cs index f96345842..53fa179ea 100644 --- a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiRequestBodyReferenceTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiRequestBodyReferenceTests.cs @@ -5,6 +5,8 @@ using System.IO; using System.Linq; using System.Threading.Tasks; +using FluentAssertions; +using Json.Schema; using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Models.References; using Microsoft.OpenApi.Readers; @@ -18,8 +20,8 @@ namespace Microsoft.OpenApi.Tests.Models.References [UsesVerify] public class OpenApiRequestBodyReferenceTests { - private const string OpenApi = @" -openapi: 3.0.3 + private readonly string OpenApi = @" +openapi: 3.0.0 info: title: Sample API version: 1.0.0 @@ -53,8 +55,8 @@ public class OpenApiRequestBodyReferenceTests type: string "; - private const string OpenApi_2 = @" -openapi: 3.0.3 + private readonly string OpenApi_2 = @" +openapi: 3.0.0 info: title: Sample API version: 1.0.0 @@ -98,6 +100,15 @@ public OpenApiRequestBodyReferenceTests() public void RequestBodyReferenceResolutionWorks() { // Assert + var expectedSchema = new JsonSchemaBuilder() + .Type(SchemaValueType.Object) + .Properties( + ("name", new JsonSchemaBuilder().Type(SchemaValueType.String)), + ("email", new JsonSchemaBuilder().Type(SchemaValueType.String))) + .Build(); + var actualSchema = _localRequestBodyReference.Content["application/json"].Schema; + + actualSchema.Should().BeEquivalentTo(expectedSchema); Assert.Equal("User request body", _localRequestBodyReference.Description); Assert.Equal("application/json", _localRequestBodyReference.Content.First().Key); Assert.Equal("External Reference: User request body", _externalRequestBodyReference.Description); 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 index b7716dcb6..45fb2bb48 100644 --- 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 @@ -1,6 +1,10 @@ { "description": "OK response", "content": { - "text/plain": { } + "text/plain": { + "schema": { + "$ref": "#/components/schemas/Pong" + } + } } } \ 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 index 037f74d31..7477918b3 100644 --- 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 @@ -1 +1 @@ -{"description":"OK response","content":{"text/plain":{}}} \ No newline at end of file +{"description":"OK response","content":{"text/plain":{"schema":{"$ref":"#/components/schemas/Pong"}}}} \ 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 index b7716dcb6..45fb2bb48 100644 --- 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 @@ -1,6 +1,10 @@ { "description": "OK response", "content": { - "text/plain": { } + "text/plain": { + "schema": { + "$ref": "#/components/schemas/Pong" + } + } } } \ 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 index 037f74d31..7477918b3 100644 --- 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 @@ -1 +1 @@ -{"description":"OK response","content":{"text/plain":{}}} \ No newline at end of file +{"description":"OK response","content":{"text/plain":{"schema":{"$ref":"#/components/schemas/Pong"}}}} \ 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 index f3a654a50..681d29e83 100644 --- a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiResponseReferenceTest.cs +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiResponseReferenceTest.cs @@ -1,10 +1,12 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// 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 FluentAssertions; +using Json.Schema; using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Models.References; using Microsoft.OpenApi.Readers; @@ -19,7 +21,7 @@ namespace Microsoft.OpenApi.Tests.Models.References public class OpenApiResponseReferenceTest { private const string OpenApi = @" -openapi: 3.0.3 +openapi: 3.0.0 info: title: Sample API version: 1.0.0 @@ -42,7 +44,7 @@ public class OpenApiResponseReferenceTest "; private const string OpenApi_2 = @" -openapi: 3.0.3 +openapi: 3.0.0 info: title: Sample API version: 1.0.0 @@ -85,6 +87,7 @@ public void ResponseReferenceResolutionWorks() // Assert Assert.Equal("OK response", _localResponseReference.Description); Assert.Equal("text/plain", _localResponseReference.Content.First().Key); + Assert.NotNull(_localResponseReference.Content.First().Value.Schema.GetRef()); Assert.Equal("External reference: OK response", _externalResponseReference.Description); Assert.Equal("OK", _openApiDoc.Components.Responses.First().Value.Description); } diff --git a/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt b/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt index 74d46a503..a496589aa 100755 --- a/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt +++ b/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt @@ -126,12 +126,74 @@ namespace Microsoft.OpenApi.Expressions } namespace Microsoft.OpenApi.Extensions { + [Json.Schema.SchemaKeyword("additionalPropertiesAllowed")] + public class AdditionalPropertiesAllowedKeyword : Json.Schema.IJsonSchemaKeyword + { + public const string Name = "additionalPropertiesAllowed"; + public void Evaluate(Json.Schema.EvaluationContext context) { } + } + [Json.Schema.SchemaKeyword("discriminator")] + [Json.Schema.SchemaSpecVersion(Json.Schema.SpecVersion.Draft202012)] + public class DiscriminatorKeyword : Microsoft.OpenApi.Models.OpenApiDiscriminator, Json.Schema.IJsonSchemaKeyword + { + public const string Name = "discriminator"; + public DiscriminatorKeyword() { } + public void Evaluate(Json.Schema.EvaluationContext context) { } + } + [Json.Schema.SchemaKeyword("exclusiveMaximum")] + public class Draft4ExclusiveMaximumKeyword : Json.Schema.IJsonSchemaKeyword + { + public const string Name = "exclusiveMaximum"; + public bool MaxValue { get; } + public void Evaluate(Json.Schema.EvaluationContext context) { } + } + [Json.Schema.SchemaKeyword("exclusiveMinimum")] + public class Draft4ExclusiveMinimumKeyword : Json.Schema.IJsonSchemaKeyword + { + public const string Name = "exclusiveMinimum"; + public bool MinValue { get; } + public void Evaluate(Json.Schema.EvaluationContext context) { } + } public static class EnumExtensions { public static T GetAttributeOfType(this System.Enum enumValue) where T : System.Attribute { } public static string GetDisplayName(this System.Enum enumValue) { } } + [Json.Schema.SchemaKeyword("extensions")] + public class ExtensionsKeyword : Json.Schema.IJsonSchemaKeyword + { + public const string Name = "extensions"; + public void Evaluate(Json.Schema.EvaluationContext context) { } + } + public static class JsonSchemaBuilderExtensions + { + public static Json.Schema.JsonSchemaBuilder AdditionalPropertiesAllowed(this Json.Schema.JsonSchemaBuilder builder, bool additionalPropertiesAllowed) { } + public static Json.Schema.JsonSchemaBuilder Discriminator(this Json.Schema.JsonSchemaBuilder builder, Microsoft.OpenApi.Models.OpenApiDiscriminator discriminator) { } + public static Json.Schema.JsonSchemaBuilder ExclusiveMaximum(this Json.Schema.JsonSchemaBuilder builder, bool value) { } + public static Json.Schema.JsonSchemaBuilder ExclusiveMinimum(this Json.Schema.JsonSchemaBuilder builder, bool value) { } + public static Json.Schema.JsonSchemaBuilder Extensions(this Json.Schema.JsonSchemaBuilder builder, System.Collections.Generic.IDictionary extensions) { } + public static Json.Schema.JsonSchemaBuilder Nullable(this Json.Schema.JsonSchemaBuilder builder, bool value) { } + public static Json.Schema.JsonSchemaBuilder Summary(this Json.Schema.JsonSchemaBuilder builder, string summary) { } + } + public static class JsonSchemaExtensions + { + public static bool? GetAdditionalPropertiesAllowed(this Json.Schema.JsonSchema schema) { } + public static System.Collections.Generic.IDictionary GetExtensions(this Json.Schema.JsonSchema schema) { } + public static bool? GetNullable(this Json.Schema.JsonSchema schema) { } + public static Microsoft.OpenApi.Extensions.DiscriminatorKeyword GetOpenApiDiscriminator(this Json.Schema.JsonSchema schema) { } + public static bool? GetOpenApiExclusiveMaximum(this Json.Schema.JsonSchema schema) { } + public static bool? GetOpenApiExclusiveMinimum(this Json.Schema.JsonSchema schema) { } + public static string GetSummary(this Json.Schema.JsonSchema schema) { } + } + [Json.Schema.SchemaKeyword("nullable")] + public class NullableKeyword : Json.Schema.IJsonSchemaKeyword + { + public const string Name = "nullable"; + public NullableKeyword(bool value) { } + public bool Value { get; } + public void Evaluate(Json.Schema.EvaluationContext context) { } + } public static class OpenApiElementExtensions { public static System.Collections.Generic.IEnumerable Validate(this Microsoft.OpenApi.Interfaces.IOpenApiElement element, Microsoft.OpenApi.Validations.ValidationRuleSet ruleSet) { } @@ -166,13 +228,19 @@ namespace Microsoft.OpenApi.Extensions } public static class OpenApiTypeMapper { - public static System.Type MapOpenApiPrimitiveTypeToSimpleType(this Microsoft.OpenApi.Models.OpenApiSchema schema) { } - public static Microsoft.OpenApi.Models.OpenApiSchema MapTypeToOpenApiPrimitiveType(this System.Type type) { } + public static System.Type MapJsonSchemaValueTypeToSimpleType(this Json.Schema.JsonSchema schema) { } + public static Json.Schema.JsonSchema MapTypeToJsonPrimitiveType(this System.Type type) { } } public static class StringExtensions { public static T GetEnumFromDisplayName(this string displayName) { } } + [Json.Schema.SchemaKeyword("summary")] + public class SummaryKeyword : Json.Schema.IJsonSchemaKeyword + { + public const string Name = "summary"; + public void Evaluate(Json.Schema.EvaluationContext context) { } + } } namespace Microsoft.OpenApi.Interfaces { @@ -249,6 +317,7 @@ namespace Microsoft.OpenApi.Models { public OpenApiComponents() { } 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; } @@ -258,7 +327,6 @@ namespace Microsoft.OpenApi.Models 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 Schemas { 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) { } @@ -385,6 +453,8 @@ namespace Microsoft.OpenApi.Models public const string Type = "type"; public const string UniqueItems = "uniqueItems"; public const string Url = "url"; + public const string V2ReferenceUri = "https://registry/definitions/"; + public const string V3ReferenceUri = "https://registry/components/schemas/"; public const string Value = "value"; public const string Variables = "variables"; public const string Version = "version"; @@ -408,20 +478,22 @@ namespace Microsoft.OpenApi.Models 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.IOpenApiElement, Microsoft.OpenApi.Interfaces.IOpenApiExtensible, Microsoft.OpenApi.Interfaces.IOpenApiSerializable + public class OpenApiDocument : Json.Schema.IBaseDocument, Microsoft.OpenApi.Interfaces.IOpenApiElement, Microsoft.OpenApi.Interfaces.IOpenApiExtensible, Microsoft.OpenApi.Interfaces.IOpenApiSerializable { public OpenApiDocument() { } public OpenApiDocument(Microsoft.OpenApi.Models.OpenApiDocument document) { } + 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; } @@ -434,6 +506,7 @@ namespace Microsoft.OpenApi.Models 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 Json.Schema.JsonSchema FindSubschema(Json.Pointer.JsonPointer pointer, Json.Schema.EvaluationOptions options) { } public Microsoft.OpenApi.Interfaces.IOpenApiReferenceable ResolveReference(Microsoft.OpenApi.Models.OpenApiReference reference) { } public System.Collections.Generic.IEnumerable ResolveReferences() { } public void SerializeAsV2(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } @@ -519,7 +592,7 @@ namespace Microsoft.OpenApi.Models 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 Json.Schema.JsonSchema Schema { get; set; } public virtual Microsoft.OpenApi.Models.ParameterStyle? Style { get; set; } public virtual bool UnresolvedReference { get; set; } public Microsoft.OpenApi.Models.OpenApiHeader GetEffective(Microsoft.OpenApi.Models.OpenApiDocument doc) { } @@ -587,7 +660,7 @@ namespace Microsoft.OpenApi.Models public Microsoft.OpenApi.Any.OpenApiAny 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 virtual Json.Schema.JsonSchema 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) { } @@ -657,7 +730,7 @@ namespace Microsoft.OpenApi.Models 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 Json.Schema.JsonSchema Schema { get; set; } public virtual Microsoft.OpenApi.Models.ParameterStyle? Style { get; set; } public virtual bool UnresolvedReference { get; set; } public Microsoft.OpenApi.Models.OpenApiParameter GetEffective(Microsoft.OpenApi.Models.OpenApiDocument doc) { } @@ -755,57 +828,6 @@ namespace Microsoft.OpenApi.Models public OpenApiResponses() { } public OpenApiResponses(Microsoft.OpenApi.Models.OpenApiResponses openApiResponses) { } } - public class OpenApiSchema : Microsoft.OpenApi.Interfaces.IEffective, 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.IList AnyOf { get; set; } - public Microsoft.OpenApi.Any.OpenApiAny 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.OpenApiAny 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 SerializeAsV31(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } - public void SerializeAsV31WithoutReference(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } - public void SerializeAsV3WithoutReference(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } - } public class OpenApiSecurityRequirement : System.Collections.Generic.Dictionary>, Microsoft.OpenApi.Interfaces.IOpenApiElement, Microsoft.OpenApi.Interfaces.IOpenApiSerializable { public OpenApiSecurityRequirement() { } @@ -1040,6 +1062,9 @@ namespace Microsoft.OpenApi.Services { public OpenApiReferenceResolver(Microsoft.OpenApi.Models.OpenApiDocument currentDocument, bool resolveRemoteReferences = true) { } public System.Collections.Generic.IEnumerable Errors { get; } + public Json.Schema.JsonSchema ResolveJsonSchemaReference(System.Uri reference, string description = null, string summary = null) { } + public override void Visit(Json.Schema.IBaseDocument document) { } + public override void Visit(ref Json.Schema.JsonSchema schema) { } 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) { } @@ -1047,7 +1072,6 @@ namespace Microsoft.OpenApi.Services 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) { } @@ -1080,6 +1104,8 @@ namespace Microsoft.OpenApi.Services public string PathString { get; } public virtual void Enter(string segment) { } public virtual void Exit() { } + public virtual void Visit(Json.Schema.IBaseDocument document) { } + public virtual void Visit(ref Json.Schema.JsonSchema schema) { } public virtual void Visit(Microsoft.OpenApi.Interfaces.IOpenApiExtensible openApiExtensible) { } public virtual void Visit(Microsoft.OpenApi.Interfaces.IOpenApiExtension openApiExtension) { } public virtual void Visit(Microsoft.OpenApi.Interfaces.IOpenApiReferenceable referenceable) { } @@ -1103,7 +1129,6 @@ namespace Microsoft.OpenApi.Services public virtual void Visit(Microsoft.OpenApi.Models.OpenApiRequestBody requestBody) { } public virtual void Visit(Microsoft.OpenApi.Models.OpenApiResponse response) { } public virtual void Visit(Microsoft.OpenApi.Models.OpenApiResponses response) { } - public virtual void Visit(Microsoft.OpenApi.Models.OpenApiSchema schema) { } public virtual void Visit(Microsoft.OpenApi.Models.OpenApiSecurityRequirement securityRequirement) { } public virtual void Visit(Microsoft.OpenApi.Models.OpenApiSecurityScheme securityScheme) { } public virtual void Visit(Microsoft.OpenApi.Models.OpenApiServer server) { } @@ -1123,6 +1148,7 @@ namespace Microsoft.OpenApi.Services 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.Collections.Generic.IReadOnlyCollection schema) { } public virtual void Visit(System.Text.Json.Nodes.JsonNode node) { } } public class OpenApiWalker @@ -1142,8 +1168,10 @@ namespace Microsoft.OpenApi.Services 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 AddSchemaFragment(string location, Json.Schema.JsonSchema fragment) { } public bool Contains(string location) { } public System.IO.Stream GetArtifact(string location) { } + public Json.Schema.JsonSchema ResolveJsonSchemaReference(System.Uri reference) { } public Microsoft.OpenApi.Interfaces.IOpenApiReferenceable ResolveReference(Microsoft.OpenApi.Models.OpenApiReference reference) { } } public class OperationSearch : Microsoft.OpenApi.Services.OpenApiVisitorBase @@ -1178,6 +1206,7 @@ namespace Microsoft.OpenApi.Validations public System.Collections.Generic.IEnumerable Warnings { get; } public void AddError(Microsoft.OpenApi.Validations.OpenApiValidatorError error) { } public void AddWarning(Microsoft.OpenApi.Validations.OpenApiValidatorWarning warning) { } + public override void Visit(ref Json.Schema.JsonSchema item) { } public override void Visit(Microsoft.OpenApi.Interfaces.IOpenApiExtensible item) { } public override void Visit(Microsoft.OpenApi.Interfaces.IOpenApiExtension item) { } public override void Visit(Microsoft.OpenApi.Models.OpenApiCallback item) { } @@ -1200,7 +1229,6 @@ namespace Microsoft.OpenApi.Validations public override void Visit(Microsoft.OpenApi.Models.OpenApiRequestBody item) { } public override void Visit(Microsoft.OpenApi.Models.OpenApiResponse item) { } public override void Visit(Microsoft.OpenApi.Models.OpenApiResponses item) { } - public override void Visit(Microsoft.OpenApi.Models.OpenApiSchema item) { } public override void Visit(Microsoft.OpenApi.Models.OpenApiSecurityRequirement item) { } public override void Visit(Microsoft.OpenApi.Models.OpenApiSecurityScheme item) { } public override void Visit(Microsoft.OpenApi.Models.OpenApiServer item) { } @@ -1260,13 +1288,20 @@ namespace Microsoft.OpenApi.Validations public static Microsoft.OpenApi.Validations.ValidationRuleSet GetEmptyRuleSet() { } } public class ValidationRule : Microsoft.OpenApi.Validations.ValidationRule - where T : Microsoft.OpenApi.Interfaces.IOpenApiElement { public ValidationRule(System.Action validate) { } } } namespace Microsoft.OpenApi.Validations.Rules { + [Microsoft.OpenApi.Validations.Rules.OpenApiRule] + public static class JsonSchemaRules + { + 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.IReadOnlyCollection childSchema) { } + public static bool ValidateChildSchemaAgainstDiscriminator(Json.Schema.JsonSchema schema, string discriminatorName) { } + } [Microsoft.OpenApi.Validations.Rules.OpenApiRule] public static class OpenApiComponentsRules { @@ -1348,14 +1383,6 @@ namespace Microsoft.OpenApi.Validations.Rules public OpenApiRuleAttribute() { } } [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) { } - } - [Microsoft.OpenApi.Validations.Rules.OpenApiRule] public static class OpenApiServerRules { public static Microsoft.OpenApi.Validations.ValidationRule ServerRequiredFields { get; } @@ -1378,6 +1405,9 @@ namespace Microsoft.OpenApi.Writers void Flush(); void WriteEndArray(); void WriteEndObject(); + void WriteJsonSchema(Json.Schema.JsonSchema schema); + void WriteJsonSchemaReference(Microsoft.OpenApi.Writers.IOpenApiWriter writer, System.Uri reference); + void WriteJsonSchemaWithoutReference(Microsoft.OpenApi.Writers.IOpenApiWriter writer, Json.Schema.JsonSchema schema); void WriteNull(); void WritePropertyName(string name); void WriteRaw(string value); @@ -1438,6 +1468,9 @@ namespace Microsoft.OpenApi.Writers public abstract void WriteEndArray(); public abstract void WriteEndObject(); public virtual void WriteIndentation() { } + public void WriteJsonSchema(Json.Schema.JsonSchema schema) { } + public void WriteJsonSchemaReference(Microsoft.OpenApi.Writers.IOpenApiWriter writer, System.Uri reference) { } + public void WriteJsonSchemaWithoutReference(Microsoft.OpenApi.Writers.IOpenApiWriter writer, Json.Schema.JsonSchema schema) { } public abstract void WriteNull(); public abstract void WritePropertyName(string name); public abstract void WriteRaw(string value); @@ -1458,15 +1491,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) { } @@ -1474,13 +1506,10 @@ namespace Microsoft.OpenApi.Writers where T : struct { } public static void WriteProperty(this Microsoft.OpenApi.Writers.IOpenApiWriter writer, string name, T? value) where T : struct { } - public static void WriteRequiredCollection(this Microsoft.OpenApi.Writers.IOpenApiWriter writer, string name, System.Collections.Generic.IEnumerable elements, System.Action action) - where T : Microsoft.OpenApi.Interfaces.IOpenApiElement { } public static void WriteRequiredMap(this Microsoft.OpenApi.Writers.IOpenApiWriter writer, string name, System.Collections.Generic.IDictionary elements, System.Action action) { } public static void WriteRequiredMap(this Microsoft.OpenApi.Writers.IOpenApiWriter writer, string name, System.Collections.Generic.IDictionary elements, System.Action action) where T : Microsoft.OpenApi.Interfaces.IOpenApiElement { } - 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 418a526d0..29a984a9b 100755 --- a/test/Microsoft.OpenApi.Tests/PublicApi/PublicApiTests.cs +++ b/test/Microsoft.OpenApi.Tests/PublicApi/PublicApiTests.cs @@ -2,9 +2,9 @@ // Licensed under the MIT license. using System.IO; +using PublicApiGenerator; using Xunit; using Xunit.Abstractions; -using PublicApiGenerator; namespace Microsoft.OpenApi.Tests.PublicApi { @@ -26,7 +26,7 @@ public void ReviewPublicApiChanges() // It takes a human to read the change, determine if it is breaking and update the PublicApi.approved.txt with the new approved API surface // Arrange - var publicApi = typeof(OpenApiSpecVersion).Assembly.GeneratePublicApi(new ApiGeneratorOptions() { AllowNamespacePrefixes = new[] { "Microsoft.OpenApi" } } ); + var publicApi = typeof(OpenApiSpecVersion).Assembly.GeneratePublicApi(new ApiGeneratorOptions() { AllowNamespacePrefixes = new[] { "Microsoft.OpenApi" } }); // Act var approvedFilePath = Path.Combine("PublicApi", "PublicApi.approved.txt"); diff --git a/test/Microsoft.OpenApi.Tests/Services/OpenApiUrlTreeNodeTests.cs b/test/Microsoft.OpenApi.Tests/Services/OpenApiUrlTreeNodeTests.cs index d251c99c1..e1b204f6e 100644 --- a/test/Microsoft.OpenApi.Tests/Services/OpenApiUrlTreeNodeTests.cs +++ b/test/Microsoft.OpenApi.Tests/Services/OpenApiUrlTreeNodeTests.cs @@ -19,7 +19,8 @@ public class OpenApiUrlTreeNodeTests { Paths = new OpenApiPaths() { - ["/"] = new OpenApiPathItem() { + ["/"] = new OpenApiPathItem() + { Operations = new Dictionary() { [OperationType.Get] = new OpenApiOperation(), diff --git a/test/Microsoft.OpenApi.Tests/Validations/OpenApiComponentsValidationTests.cs b/test/Microsoft.OpenApi.Tests/Validations/OpenApiComponentsValidationTests.cs index d10eaf590..b9c230d92 100644 --- a/test/Microsoft.OpenApi.Tests/Validations/OpenApiComponentsValidationTests.cs +++ b/test/Microsoft.OpenApi.Tests/Validations/OpenApiComponentsValidationTests.cs @@ -7,7 +7,6 @@ using Microsoft.OpenApi.Extensions; using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Properties; -using Microsoft.OpenApi.Services; using Microsoft.OpenApi.Validations.Rules; using Xunit; diff --git a/test/Microsoft.OpenApi.Tests/Validations/OpenApiContactValidationTests.cs b/test/Microsoft.OpenApi.Tests/Validations/OpenApiContactValidationTests.cs index ec6bba7b5..157967037 100644 --- a/test/Microsoft.OpenApi.Tests/Validations/OpenApiContactValidationTests.cs +++ b/test/Microsoft.OpenApi.Tests/Validations/OpenApiContactValidationTests.cs @@ -2,12 +2,10 @@ // Licensed under the MIT license. using System; -using System.Collections.Generic; using System.Linq; using Microsoft.OpenApi.Extensions; using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Properties; -using Microsoft.OpenApi.Services; using Xunit; namespace Microsoft.OpenApi.Validations.Tests diff --git a/test/Microsoft.OpenApi.Tests/Validations/OpenApiExternalDocsValidationTests.cs b/test/Microsoft.OpenApi.Tests/Validations/OpenApiExternalDocsValidationTests.cs index fee728f76..484f82978 100644 --- a/test/Microsoft.OpenApi.Tests/Validations/OpenApiExternalDocsValidationTests.cs +++ b/test/Microsoft.OpenApi.Tests/Validations/OpenApiExternalDocsValidationTests.cs @@ -2,12 +2,10 @@ // Licensed under the MIT license. using System; -using System.Collections.Generic; using System.Linq; using Microsoft.OpenApi.Extensions; using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Properties; -using Microsoft.OpenApi.Services; using Xunit; namespace Microsoft.OpenApi.Validations.Tests @@ -25,9 +23,9 @@ public void ValidateUrlIsRequiredInExternalDocs() // Assert - bool result = !errors.Any(); + bool result = errors.Any(); - Assert.False(result); + Assert.True(result); Assert.NotNull(errors); OpenApiError 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 9a243ca16..56e07cc20 100644 --- a/test/Microsoft.OpenApi.Tests/Validations/OpenApiHeaderValidationTests.cs +++ b/test/Microsoft.OpenApi.Tests/Validations/OpenApiHeaderValidationTests.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Text.Json.Nodes; using FluentAssertions; +using Json.Schema; using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Services; @@ -24,10 +25,7 @@ public void ValidateExampleShouldNotHaveDataTypeMismatchForSimpleSchema() { Required = true, Example = new OpenApiAny(55), - Schema = new OpenApiSchema() - { - Type = "string", - } + Schema = new JsonSchemaBuilder().Type(SchemaValueType.String) }; // Act @@ -60,14 +58,13 @@ public void ValidateExamplesShouldNotHaveDataTypeMismatchForSimpleSchema() var header = new OpenApiHeader() { Required = true, - Schema = new OpenApiSchema() - { - Type = "object", - AdditionalProperties = new OpenApiSchema() - { - Type = "integer", - } - }, + Schema = new JsonSchemaBuilder() + .Type(SchemaValueType.Object) + .AdditionalProperties( + new JsonSchemaBuilder() + .Type(SchemaValueType.Integer) + .Build()) + .Build(), Examples = { ["example0"] = new OpenApiExample() diff --git a/test/Microsoft.OpenApi.Tests/Validations/OpenApiInfoValidationTests.cs b/test/Microsoft.OpenApi.Tests/Validations/OpenApiInfoValidationTests.cs index 1a58fff04..f3006d2cd 100644 --- a/test/Microsoft.OpenApi.Tests/Validations/OpenApiInfoValidationTests.cs +++ b/test/Microsoft.OpenApi.Tests/Validations/OpenApiInfoValidationTests.cs @@ -2,12 +2,10 @@ // Licensed under the MIT license. using System; -using System.Collections.Generic; using System.Linq; using Microsoft.OpenApi.Extensions; using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Properties; -using Microsoft.OpenApi.Services; using Xunit; namespace Microsoft.OpenApi.Validations.Tests diff --git a/test/Microsoft.OpenApi.Tests/Validations/OpenApiMediaTypeValidationTests.cs b/test/Microsoft.OpenApi.Tests/Validations/OpenApiMediaTypeValidationTests.cs index 6b518f643..a4d38c6eb 100644 --- a/test/Microsoft.OpenApi.Tests/Validations/OpenApiMediaTypeValidationTests.cs +++ b/test/Microsoft.OpenApi.Tests/Validations/OpenApiMediaTypeValidationTests.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Text.Json.Nodes; using FluentAssertions; +using Json.Schema; using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Services; @@ -23,10 +24,7 @@ public void ValidateExampleShouldNotHaveDataTypeMismatchForSimpleSchema() var mediaType = new OpenApiMediaType() { Example = new OpenApiAny(55), - Schema = new OpenApiSchema() - { - Type = "string", - } + Schema = new JsonSchemaBuilder().Type(SchemaValueType.String).Build(), }; // Act @@ -58,14 +56,11 @@ public void ValidateExamplesShouldNotHaveDataTypeMismatchForSimpleSchema() var mediaType = new OpenApiMediaType() { - Schema = new OpenApiSchema() - { - Type = "object", - AdditionalProperties = new OpenApiSchema() - { - Type = "integer", - } - }, + Schema = new JsonSchemaBuilder() + .Type(SchemaValueType.Object) + .AdditionalProperties(new JsonSchemaBuilder() + .Type(SchemaValueType.Integer).Build()) + .Build(), Examples = { ["example0"] = new OpenApiExample() diff --git a/test/Microsoft.OpenApi.Tests/Validations/OpenApiParameterValidationTests.cs b/test/Microsoft.OpenApi.Tests/Validations/OpenApiParameterValidationTests.cs index f43cbcdd0..bb748b655 100644 --- a/test/Microsoft.OpenApi.Tests/Validations/OpenApiParameterValidationTests.cs +++ b/test/Microsoft.OpenApi.Tests/Validations/OpenApiParameterValidationTests.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Text.Json.Nodes; using FluentAssertions; +using Json.Schema; using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Extensions; using Microsoft.OpenApi.Models; @@ -73,12 +74,9 @@ public void ValidateExampleShouldNotHaveDataTypeMismatchForSimpleSchema() In = ParameterLocation.Path, Required = true, Example = new OpenApiAny(55), - Schema = new OpenApiSchema() - { - Type = "string", - } + Schema = new JsonSchemaBuilder().Type(SchemaValueType.String).Build() }; - + // Act var validator = new OpenApiValidator(ValidationRuleSet.GetDefaultRuleSet()); validator.Enter("{parameter1}"); @@ -111,14 +109,13 @@ public void ValidateExamplesShouldNotHaveDataTypeMismatchForSimpleSchema() Name = "parameter1", In = ParameterLocation.Path, Required = true, - Schema = new OpenApiSchema() - { - Type = "object", - AdditionalProperties = new OpenApiSchema() - { - Type = "integer", - } - }, + Schema = new JsonSchemaBuilder() + .Type(SchemaValueType.Object) + .AdditionalProperties( + new JsonSchemaBuilder() + .Type(SchemaValueType.Integer) + .Build()) + .Build(), Examples = { ["example0"] = new OpenApiExample() @@ -136,15 +133,14 @@ public void ValidateExamplesShouldNotHaveDataTypeMismatchForSimpleSchema() }, ["example2"] = new OpenApiExample() { - Value = - new OpenApiAny(new JsonArray(){3}) + Value = new OpenApiAny(new JsonArray(){3}) }, ["example3"] = new OpenApiExample() { Value = new OpenApiAny(new JsonObject() { ["x"] = 4, - ["y"] =40 + ["y"] = 40 }) }, } @@ -188,10 +184,7 @@ public void PathParameterNotInThePathShouldReturnAnError() Name = "parameter1", In = ParameterLocation.Path, Required = true, - Schema = new OpenApiSchema() - { - Type = "string", - } + Schema = new JsonSchemaBuilder().Type(SchemaValueType.String) }; // Act @@ -226,10 +219,7 @@ public void PathParameterInThePathShouldBeOk() Name = "parameter1", In = ParameterLocation.Path, Required = true, - Schema = new OpenApiSchema() - { - Type = "string", - } + Schema = new JsonSchemaBuilder().Type(SchemaValueType.String) }; // Act diff --git a/test/Microsoft.OpenApi.Tests/Validations/OpenApiReferenceValidationTests.cs b/test/Microsoft.OpenApi.Tests/Validations/OpenApiReferenceValidationTests.cs index 43576475d..5e962a601 100644 --- a/test/Microsoft.OpenApi.Tests/Validations/OpenApiReferenceValidationTests.cs +++ b/test/Microsoft.OpenApi.Tests/Validations/OpenApiReferenceValidationTests.cs @@ -3,8 +3,8 @@ using System.Collections.Generic; using System.Linq; +using Json.Schema; using Microsoft.OpenApi.Extensions; -using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Validations; using Xunit; @@ -18,22 +18,14 @@ public void ReferencedSchemaShouldOnlyBeValidatedOnce() { // Arrange - var sharedSchema = new OpenApiSchema - { - Type = "string", - Reference = new OpenApiReference() - { - Id = "test" - }, - UnresolvedReference = false - }; + var sharedSchema = new JsonSchemaBuilder().Type(SchemaValueType.String).Ref("test"); OpenApiDocument document = new OpenApiDocument(); document.Components = new OpenApiComponents() { - Schemas = new Dictionary() + Schemas = new Dictionary() { - [sharedSchema.Reference.Id] = sharedSchema + ["test"] = sharedSchema } }; @@ -66,8 +58,8 @@ public void ReferencedSchemaShouldOnlyBeValidatedOnce() // Act var rules = new Dictionary>() { - { typeof(OpenApiSchema).Name, - new List() { new AlwaysFailRule() } + { typeof(JsonSchema).Name, + new List() { new AlwaysFailRule() } } }; @@ -78,56 +70,12 @@ public void ReferencedSchemaShouldOnlyBeValidatedOnce() Assert.True(errors.Count() == 1); } - [Fact] - public void UnresolvedReferenceSchemaShouldNotBeValidated() - { - // Arrange - var sharedSchema = new OpenApiSchema - { - Type = "string", - Reference = new OpenApiReference() - { - Id = "test" - }, - UnresolvedReference = true - }; - - OpenApiDocument document = new OpenApiDocument(); - document.Components = new OpenApiComponents() - { - Schemas = new Dictionary() - { - [sharedSchema.Reference.Id] = sharedSchema - } - }; - - // Act - var rules = new Dictionary>() - { - { typeof(AlwaysFailRule).Name, - new List() { new AlwaysFailRule() } - } - }; - - var errors = document.Validate(new ValidationRuleSet(rules)); - - // Assert - Assert.True(errors.Count() == 0); - } - [Fact] public void UnresolvedSchemaReferencedShouldNotBeValidated() { // Arrange - var sharedSchema = new OpenApiSchema - { - Reference = new OpenApiReference() - { - Id = "test" - }, - UnresolvedReference = true - }; + var sharedSchema = new JsonSchemaBuilder().Type(SchemaValueType.String).Ref("test").Build(); OpenApiDocument document = new OpenApiDocument(); @@ -160,19 +108,19 @@ public void UnresolvedSchemaReferencedShouldNotBeValidated() // Act var rules = new Dictionary>() { - { typeof(AlwaysFailRule).Name, - new List() { new AlwaysFailRule() } + { typeof(JsonSchema).Name, + 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((c, t) => c.CreateError("x", "y")) { diff --git a/test/Microsoft.OpenApi.Tests/Validations/OpenApiSchemaValidationTests.cs b/test/Microsoft.OpenApi.Tests/Validations/OpenApiSchemaValidationTests.cs index 4ec118333..3b5a3cbb6 100644 --- a/test/Microsoft.OpenApi.Tests/Validations/OpenApiSchemaValidationTests.cs +++ b/test/Microsoft.OpenApi.Tests/Validations/OpenApiSchemaValidationTests.cs @@ -6,12 +6,15 @@ using System.Linq; using System.Text.Json.Nodes; using FluentAssertions; +using Json.Schema; +using Json.Schema.OpenApi; using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Properties; using Microsoft.OpenApi.Services; using Microsoft.OpenApi.Validations.Rules; using Xunit; +using Microsoft.OpenApi.Extensions; namespace Microsoft.OpenApi.Validations.Tests { @@ -23,11 +26,7 @@ public void ValidateDefaultShouldNotHaveDataTypeMismatchForSimpleSchema() { // Arrange IEnumerable warnings; - var schema = new OpenApiSchema() - { - Default = new OpenApiAny(55), - Type = "string", - }; + var schema = new JsonSchemaBuilder().Default(new OpenApiAny(55).Node).Type(SchemaValueType.String); // Act var validator = new OpenApiValidator(ValidationRuleSet.GetDefaultRuleSet()); @@ -54,13 +53,12 @@ public void ValidateExampleAndDefaultShouldNotHaveDataTypeMismatchForSimpleSchem { // Arrange IEnumerable warnings; - var schema = new OpenApiSchema() - { - Example = new OpenApiAny(55), - Default = new OpenApiAny("1234"), - Type = "string", - }; - + var schema = new JsonSchemaBuilder() + .Default(new OpenApiAny("1234").Node) + .Type(SchemaValueType.String) + .Example(new OpenApiAny(55).Node) + .Build(); + // Act var validator = new OpenApiValidator(ValidationRuleSet.GetDefaultRuleSet()); var walker = new OpenApiWalker(validator); @@ -87,30 +85,24 @@ public void ValidateEnumShouldNotHaveDataTypeMismatchForSimpleSchema() { // Arrange IEnumerable warnings; - var schema = new OpenApiSchema() - { - Enum = - { - new OpenApiAny("1"), + var schema = new JsonSchemaBuilder() + .Enum( + new OpenApiAny("1").Node, new OpenApiAny(new JsonObject() { ["x"] = 2, ["y"] = "20", ["z"] = "200" - }), - new OpenApiAny (new JsonArray() { 3 }), + }).Node, + new OpenApiAny(new JsonArray() { 3 }).Node, new OpenApiAny(new JsonObject() { ["x"] = 4, ["y"] = 40, - }) - }, - Type = "object", - AdditionalProperties = new OpenApiSchema() - { - Type = "integer", - } - }; + }).Node) + .Type(SchemaValueType.Object) + .AdditionalProperties(new JsonSchemaBuilder().Type(SchemaValueType.Integer).Build()) + .Build(); // Act var validator = new OpenApiValidator(ValidationRuleSet.GetDefaultRuleSet()); @@ -143,43 +135,32 @@ public void ValidateDefaultShouldNotHaveDataTypeMismatchForComplexSchema() { // Arrange IEnumerable warnings; - var schema = new OpenApiSchema() - { - Type = "object", - Properties = - { - ["property1"] = new OpenApiSchema() - { - Type = "array", - Items = new OpenApiSchema() - { - Type = "integer", - Format = "int64" - } - }, - ["property2"] = new OpenApiSchema() - { - Type = "array", - Items = new OpenApiSchema() - { - Type = "object", - AdditionalProperties = new OpenApiSchema() - { - Type = "boolean" - } - } - }, - ["property3"] = new OpenApiSchema() - { - Type = "string", - Format = "password" - }, - ["property4"] = new OpenApiSchema() - { - Type = "string" - } - }, - Default = new OpenApiAny(new JsonObject() + var schema = new JsonSchemaBuilder() + .Type(SchemaValueType.Object) + .Properties( + ("property1", + new JsonSchemaBuilder() + .Type(SchemaValueType.Array) + .Items(new JsonSchemaBuilder() + .Type(SchemaValueType.Integer).Format("int64").Build()).Build()), + ("property2", + new JsonSchemaBuilder() + .Type(SchemaValueType.Array) + .Items(new JsonSchemaBuilder() + .Type(SchemaValueType.Object) + .AdditionalProperties(new JsonSchemaBuilder().Type(SchemaValueType.Boolean).Build()) + .Build()) + .Build()), + ("property3", + new JsonSchemaBuilder() + .Type(SchemaValueType.String) + .Format("password") + .Build()), + ("property4", + new JsonSchemaBuilder() + .Type(SchemaValueType.String) + .Build())) + .Default(new OpenApiAny(new JsonObject() { ["property1"] = new JsonArray() { @@ -199,8 +180,7 @@ public void ValidateDefaultShouldNotHaveDataTypeMismatchForComplexSchema() }, ["property3"] = "123", ["property4"] = DateTime.UtcNow - }) - }; + }).Node).Build(); // Act var validator = new OpenApiValidator(ValidationRuleSet.GetDefaultRuleSet()); @@ -208,10 +188,10 @@ public void ValidateDefaultShouldNotHaveDataTypeMismatchForComplexSchema() walker.Walk(schema); warnings = validator.Warnings; - bool result = !warnings.Any(); + bool result = warnings.Any(); // Assert - result.Should().BeFalse(); + result.Should().BeTrue(); warnings.Select(e => e.Message).Should().BeEquivalentTo(new[] { RuleHelpers.DataTypeMismatchedErrorMessage, @@ -235,12 +215,11 @@ public void ValidateSchemaRequiredFieldListMustContainThePropertySpecifiedInTheD Schemas = { { "schema1", - new OpenApiSchema - { - Type = "object", - Discriminator = new OpenApiDiscriminator { PropertyName = "property1" }, - Reference = new OpenApiReference { Id = "schema1" } - } + new JsonSchemaBuilder() + .Type(SchemaValueType.Object) + .Discriminator(new OpenApiDiscriminator() { PropertyName = "property1" }) + .Ref("schema1") + .Build() } } }; @@ -256,7 +235,7 @@ public void ValidateSchemaRequiredFieldListMustContainThePropertySpecifiedInTheD result.Should().BeFalse(); errors.Should().BeEquivalentTo(new List { - new OpenApiValidatorError(nameof(OpenApiSchemaRules.ValidateSchemaDiscriminator),"#/schemas/schema1/discriminator", + new OpenApiValidatorError(nameof(JsonSchemaRules.ValidateSchemaDiscriminator),"#/schemas/schema1/discriminator", string.Format(SRResource.Validation_SchemaRequiredFieldListMustContainThePropertySpecifiedInTheDiscriminator, "schema1", "property1")) }); @@ -268,41 +247,22 @@ public void ValidateOneOfSchemaPropertyNameContainsPropertySpecifiedInTheDiscrim // Arrange var components = new OpenApiComponents { - Schemas = + Schemas = { { "Person", - new OpenApiSchema - { - Type = "array", - Discriminator = new OpenApiDiscriminator - { - PropertyName = "type" - }, - OneOf = new List - { - new OpenApiSchema - { - Properties = - { - { - "type", - new OpenApiSchema - { - Type = "array" - } - } - }, - Reference = new OpenApiReference - { - Type = ReferenceType.Schema, - Id = "Person" - } - } - }, - Reference = new OpenApiReference { Id = "Person" } - } - } + new JsonSchemaBuilder() + .Type(SchemaValueType.Array) + .Discriminator(new OpenApiDiscriminator + { + PropertyName = "type" + }) + .OneOf(new JsonSchemaBuilder() + .Properties(("type", new JsonSchemaBuilder().Type(SchemaValueType.Array).Ref("Person").Build())) + .Build()) + .Ref("Person") + .Build() + } } }; diff --git a/test/Microsoft.OpenApi.Tests/Validations/OpenApiServerValidationTests.cs b/test/Microsoft.OpenApi.Tests/Validations/OpenApiServerValidationTests.cs index bbc4c7e10..b09b14f3b 100644 --- a/test/Microsoft.OpenApi.Tests/Validations/OpenApiServerValidationTests.cs +++ b/test/Microsoft.OpenApi.Tests/Validations/OpenApiServerValidationTests.cs @@ -6,7 +6,6 @@ using System.Linq; using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Properties; -using Microsoft.OpenApi.Services; using Xunit; namespace Microsoft.OpenApi.Validations.Tests diff --git a/test/Microsoft.OpenApi.Tests/Validations/OpenApiTagValidationTests.cs b/test/Microsoft.OpenApi.Tests/Validations/OpenApiTagValidationTests.cs index b3ee07257..2874a2b7a 100644 --- a/test/Microsoft.OpenApi.Tests/Validations/OpenApiTagValidationTests.cs +++ b/test/Microsoft.OpenApi.Tests/Validations/OpenApiTagValidationTests.cs @@ -5,7 +5,6 @@ using System.Collections.Generic; using System.Linq; using Microsoft.OpenApi.Any; -using Microsoft.OpenApi.Extensions; using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Properties; diff --git a/test/Microsoft.OpenApi.Tests/Visitors/InheritanceTests.cs b/test/Microsoft.OpenApi.Tests/Visitors/InheritanceTests.cs index 102100019..4e8b038b1 100644 --- a/test/Microsoft.OpenApi.Tests/Visitors/InheritanceTests.cs +++ b/test/Microsoft.OpenApi.Tests/Visitors/InheritanceTests.cs @@ -1,9 +1,7 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using System.Runtime.CompilerServices; -using System.Text; -using System.Threading.Tasks; +using Json.Schema; using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Services; @@ -11,336 +9,336 @@ namespace Microsoft.OpenApi.Tests.Visitors { - public class InheritanceTests - { - [Fact] - public void ExpectedVirtualsInvolved() - { - OpenApiVisitorBase visitor = null; - - visitor = new TestVisitor(); - - visitor.Enter(default(string)); - visitor.Visit(default(OpenApiDocument)); - visitor.Visit(default(OpenApiInfo)); - visitor.Visit(default(OpenApiContact)); - visitor.Visit(default(OpenApiLicense)); - visitor.Visit(default(IList)); - visitor.Visit(default(OpenApiServer)); - visitor.Visit(default(OpenApiPaths)); - visitor.Visit(default(OpenApiPathItem)); - visitor.Visit(default(OpenApiServerVariable)); - visitor.Visit(default(IDictionary)); - visitor.Visit(default(OpenApiOperation)); - visitor.Visit(default(IList)); - visitor.Visit(default(OpenApiParameter)); - visitor.Visit(default(OpenApiRequestBody)); - visitor.Visit(default(IDictionary)); - visitor.Visit(default(IDictionary)); - visitor.Visit(default(OpenApiResponse)); - visitor.Visit(default(OpenApiResponses)); - visitor.Visit(default(IDictionary)); - visitor.Visit(default(OpenApiMediaType)); - visitor.Visit(default(OpenApiEncoding)); - visitor.Visit(default(IDictionary)); - visitor.Visit(default(OpenApiComponents)); - visitor.Visit(default(OpenApiExternalDocs)); - visitor.Visit(default(OpenApiSchema)); - visitor.Visit(default(IDictionary)); - visitor.Visit(default(OpenApiLink)); - visitor.Visit(default(OpenApiCallback)); - visitor.Visit(default(OpenApiTag)); - visitor.Visit(default(OpenApiHeader)); - visitor.Visit(default(OpenApiOAuthFlow)); - visitor.Visit(default(OpenApiSecurityRequirement)); - visitor.Visit(default(OpenApiSecurityScheme)); - visitor.Visit(default(OpenApiExample)); - visitor.Visit(default(IList)); - visitor.Visit(default(IList)); - visitor.Visit(default(IOpenApiExtensible)); - visitor.Visit(default(IOpenApiExtension)); - visitor.Visit(default(IList)); - visitor.Visit(default(IDictionary)); - visitor.Visit(default(IDictionary)); - visitor.Visit(default(IOpenApiReferenceable)); - visitor.Exit(); - Assert.True(42 < ((TestVisitor)visitor).CallStack.Count()); - } - - internal protected class TestVisitor : OpenApiVisitorBase - { - public Stack CallStack { get; } = new Stack(); - - private string EncodeCall([CallerMemberName] string name="", [CallerLineNumber]int lineNumber = 0) - { - var encoding = $"{name}:{lineNumber}"; - CallStack.Push(encoding); - return encoding; - } - - public override void Enter(string segment) - { - EncodeCall(); - base.Enter(segment); - } - - public override void Exit() - { - EncodeCall(); - base.Exit(); - } - - public override void Visit(OpenApiDocument doc) - { - EncodeCall(); - base.Visit(doc); - } - - public override void Visit(OpenApiInfo info) - { - EncodeCall(); - base.Visit(info); - } - - public override void Visit(OpenApiContact contact) - { - EncodeCall(); - base.Visit(contact); - } - - public override void Visit(OpenApiLicense license) - { - EncodeCall(); - base.Visit(license); - } - - public override void Visit(IList servers) - { - EncodeCall(); - base.Visit(servers); - } - - public override void Visit(OpenApiServer server) - { - EncodeCall(); - base.Visit(server); - } - - public override void Visit(OpenApiPaths paths) - { - EncodeCall(); - base.Visit(paths); - } - - public override void Visit(OpenApiPathItem pathItem) - { - EncodeCall(); - base.Visit(pathItem); - } - - public override void Visit(OpenApiServerVariable serverVariable) - { - EncodeCall(); - base.Visit(serverVariable); - } - - public override void Visit(IDictionary operations) - { - EncodeCall(); - base.Visit(operations); - } - - public override void Visit(OpenApiOperation operation) - { - EncodeCall(); - base.Visit(operation); - } - - public override void Visit(IList parameters) - { - EncodeCall(); - base.Visit(parameters); - } - - public override void Visit(OpenApiParameter parameter) - { - EncodeCall(); - base.Visit(parameter); - } - - public override void Visit(OpenApiRequestBody requestBody) - { - EncodeCall(); - base.Visit(requestBody); - } - - public override void Visit(IDictionary headers) - { - EncodeCall(); - base.Visit(headers); - } - - public override void Visit(IDictionary callbacks) - { - EncodeCall(); - base.Visit(callbacks); - } - - public override void Visit(OpenApiResponse response) - { - EncodeCall(); - base.Visit(response); - } - - public override void Visit(OpenApiResponses response) - { - EncodeCall(); - base.Visit(response); - } - - public override void Visit(IDictionary content) - { - EncodeCall(); - base.Visit(content); - } - - public override void Visit(OpenApiMediaType mediaType) - { - EncodeCall(); - base.Visit(mediaType); - } - - public override void Visit(OpenApiEncoding encoding) - { - EncodeCall(); - base.Visit(encoding); - } - - public override void Visit(IDictionary examples) - { - EncodeCall(); - base.Visit(examples); - } - - public override void Visit(OpenApiComponents components) - { - EncodeCall(); - base.Visit(components); - } - - public override void Visit(OpenApiExternalDocs externalDocs) - { - EncodeCall(); - base.Visit(externalDocs); - } - - public override void Visit(OpenApiSchema schema) - { - EncodeCall(); - base.Visit(schema); - } - - public override void Visit(IDictionary links) - { - EncodeCall(); - base.Visit(links); - } - - public override void Visit(OpenApiLink link) - { - EncodeCall(); - base.Visit(link); - } - - public override void Visit(OpenApiCallback callback) - { - EncodeCall(); - base.Visit(callback); - } - - public override void Visit(OpenApiTag tag) - { - EncodeCall(); - base.Visit(tag); - } - - public override void Visit(OpenApiHeader tag) - { - EncodeCall(); - base.Visit(tag); - } - - public override void Visit(OpenApiOAuthFlow openApiOAuthFlow) - { - EncodeCall(); - base.Visit(openApiOAuthFlow); - } - - public override void Visit(OpenApiSecurityRequirement securityRequirement) - { - EncodeCall(); - base.Visit(securityRequirement); - } - - public override void Visit(OpenApiSecurityScheme securityScheme) - { - EncodeCall(); - base.Visit(securityScheme); - } - - public override void Visit(OpenApiExample example) - { - EncodeCall(); - base.Visit(example); - } - - public override void Visit(IList openApiTags) - { - EncodeCall(); - base.Visit(openApiTags); - } - - public override void Visit(IList openApiSecurityRequirements) - { - EncodeCall(); - base.Visit(openApiSecurityRequirements); - } - - public override void Visit(IOpenApiExtensible openApiExtensible) - { - EncodeCall(); - base.Visit(openApiExtensible); - } - - public override void Visit(IOpenApiExtension openApiExtension) - { - EncodeCall(); - base.Visit(openApiExtension); - } - - public override void Visit(IList example) - { - EncodeCall(); - base.Visit(example); - } - - public override void Visit(IDictionary serverVariables) - { - EncodeCall(); - base.Visit(serverVariables); - } - - public override void Visit(IDictionary encodings) - { - EncodeCall(); - base.Visit(encodings); - } - - public override void Visit(IOpenApiReferenceable referenceable) - { - EncodeCall(); - base.Visit(referenceable); - } - } - } + public class InheritanceTests + { + [Fact] + public void ExpectedVirtualsInvolved() + { + OpenApiVisitorBase visitor = null; + + visitor = new TestVisitor(); + + visitor.Enter(default(string)); + visitor.Visit(default(OpenApiDocument)); + visitor.Visit(default(OpenApiInfo)); + visitor.Visit(default(OpenApiContact)); + visitor.Visit(default(OpenApiLicense)); + visitor.Visit(default(IList)); + visitor.Visit(default(OpenApiServer)); + visitor.Visit(default(OpenApiPaths)); + visitor.Visit(default(OpenApiPathItem)); + visitor.Visit(default(OpenApiServerVariable)); + visitor.Visit(default(IDictionary)); + visitor.Visit(default(OpenApiOperation)); + visitor.Visit(default(IList)); + visitor.Visit(default(OpenApiParameter)); + visitor.Visit(default(OpenApiRequestBody)); + visitor.Visit(default(IDictionary)); + visitor.Visit(default(IDictionary)); + visitor.Visit(default(OpenApiResponse)); + visitor.Visit(default(OpenApiResponses)); + visitor.Visit(default(IDictionary)); + visitor.Visit(default(OpenApiMediaType)); + visitor.Visit(default(OpenApiEncoding)); + visitor.Visit(default(IDictionary)); + visitor.Visit(default(OpenApiComponents)); + visitor.Visit(default(OpenApiExternalDocs)); + // visitor.Visit(default(JsonSchema)); + visitor.Visit(default(IDictionary)); + visitor.Visit(default(OpenApiLink)); + visitor.Visit(default(OpenApiCallback)); + visitor.Visit(default(OpenApiTag)); + visitor.Visit(default(OpenApiHeader)); + visitor.Visit(default(OpenApiOAuthFlow)); + visitor.Visit(default(OpenApiSecurityRequirement)); + visitor.Visit(default(OpenApiSecurityScheme)); + visitor.Visit(default(OpenApiExample)); + visitor.Visit(default(IList)); + visitor.Visit(default(IList)); + visitor.Visit(default(IOpenApiExtensible)); + visitor.Visit(default(IOpenApiExtension)); + visitor.Visit(default(IList)); + visitor.Visit(default(IDictionary)); + visitor.Visit(default(IDictionary)); + visitor.Visit(default(IOpenApiReferenceable)); + visitor.Exit(); + Assert.True(42 < ((TestVisitor)visitor).CallStack.Count()); + } + + internal protected class TestVisitor : OpenApiVisitorBase + { + public Stack CallStack { get; } = new Stack(); + + private string EncodeCall([CallerMemberName] string name = "", [CallerLineNumber] int lineNumber = 0) + { + var encoding = $"{name}:{lineNumber}"; + CallStack.Push(encoding); + return encoding; + } + + public override void Enter(string segment) + { + EncodeCall(); + base.Enter(segment); + } + + public override void Exit() + { + EncodeCall(); + base.Exit(); + } + + public override void Visit(OpenApiDocument doc) + { + EncodeCall(); + base.Visit(doc); + } + + public override void Visit(OpenApiInfo info) + { + EncodeCall(); + base.Visit(info); + } + + public override void Visit(OpenApiContact contact) + { + EncodeCall(); + base.Visit(contact); + } + + public override void Visit(OpenApiLicense license) + { + EncodeCall(); + base.Visit(license); + } + + public override void Visit(IList servers) + { + EncodeCall(); + base.Visit(servers); + } + + public override void Visit(OpenApiServer server) + { + EncodeCall(); + base.Visit(server); + } + + public override void Visit(OpenApiPaths paths) + { + EncodeCall(); + base.Visit(paths); + } + + public override void Visit(OpenApiPathItem pathItem) + { + EncodeCall(); + base.Visit(pathItem); + } + + public override void Visit(OpenApiServerVariable serverVariable) + { + EncodeCall(); + base.Visit(serverVariable); + } + + public override void Visit(IDictionary operations) + { + EncodeCall(); + base.Visit(operations); + } + + public override void Visit(OpenApiOperation operation) + { + EncodeCall(); + base.Visit(operation); + } + + public override void Visit(IList parameters) + { + EncodeCall(); + base.Visit(parameters); + } + + public override void Visit(OpenApiParameter parameter) + { + EncodeCall(); + base.Visit(parameter); + } + + public override void Visit(OpenApiRequestBody requestBody) + { + EncodeCall(); + base.Visit(requestBody); + } + + public override void Visit(IDictionary headers) + { + EncodeCall(); + base.Visit(headers); + } + + public override void Visit(IDictionary callbacks) + { + EncodeCall(); + base.Visit(callbacks); + } + + public override void Visit(OpenApiResponse response) + { + EncodeCall(); + base.Visit(response); + } + + public override void Visit(OpenApiResponses response) + { + EncodeCall(); + base.Visit(response); + } + + public override void Visit(IDictionary content) + { + EncodeCall(); + base.Visit(content); + } + + public override void Visit(OpenApiMediaType mediaType) + { + EncodeCall(); + base.Visit(mediaType); + } + + public override void Visit(OpenApiEncoding encoding) + { + EncodeCall(); + base.Visit(encoding); + } + + public override void Visit(IDictionary examples) + { + EncodeCall(); + base.Visit(examples); + } + + public override void Visit(OpenApiComponents components) + { + EncodeCall(); + base.Visit(components); + } + + public override void Visit(OpenApiExternalDocs externalDocs) + { + EncodeCall(); + base.Visit(externalDocs); + } + + public override void Visit(ref JsonSchema schema) + { + EncodeCall(); + base.Visit(ref schema); + } + + public override void Visit(IDictionary links) + { + EncodeCall(); + base.Visit(links); + } + + public override void Visit(OpenApiLink link) + { + EncodeCall(); + base.Visit(link); + } + + public override void Visit(OpenApiCallback callback) + { + EncodeCall(); + base.Visit(callback); + } + + public override void Visit(OpenApiTag tag) + { + EncodeCall(); + base.Visit(tag); + } + + public override void Visit(OpenApiHeader tag) + { + EncodeCall(); + base.Visit(tag); + } + + public override void Visit(OpenApiOAuthFlow openApiOAuthFlow) + { + EncodeCall(); + base.Visit(openApiOAuthFlow); + } + + public override void Visit(OpenApiSecurityRequirement securityRequirement) + { + EncodeCall(); + base.Visit(securityRequirement); + } + + public override void Visit(OpenApiSecurityScheme securityScheme) + { + EncodeCall(); + base.Visit(securityScheme); + } + + public override void Visit(OpenApiExample example) + { + EncodeCall(); + base.Visit(example); + } + + public override void Visit(IList openApiTags) + { + EncodeCall(); + base.Visit(openApiTags); + } + + public override void Visit(IList openApiSecurityRequirements) + { + EncodeCall(); + base.Visit(openApiSecurityRequirements); + } + + public override void Visit(IOpenApiExtensible openApiExtensible) + { + EncodeCall(); + base.Visit(openApiExtensible); + } + + public override void Visit(IOpenApiExtension openApiExtension) + { + EncodeCall(); + base.Visit(openApiExtension); + } + + public override void Visit(IList example) + { + EncodeCall(); + base.Visit(example); + } + + public override void Visit(IDictionary serverVariables) + { + EncodeCall(); + base.Visit(serverVariables); + } + + public override void Visit(IDictionary encodings) + { + EncodeCall(); + base.Visit(encodings); + } + + public override void Visit(IOpenApiReferenceable referenceable) + { + EncodeCall(); + base.Visit(referenceable); + } + } + } } diff --git a/test/Microsoft.OpenApi.Tests/Walkers/WalkerLocationTests.cs b/test/Microsoft.OpenApi.Tests/Walkers/WalkerLocationTests.cs index fc947da20..8c4f2e4e0 100644 --- a/test/Microsoft.OpenApi.Tests/Walkers/WalkerLocationTests.cs +++ b/test/Microsoft.OpenApi.Tests/Walkers/WalkerLocationTests.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Linq; using FluentAssertions; +using Json.Schema; using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Services; @@ -80,10 +81,7 @@ public void LocatePathOperationContentSchema() { ["application/json"] = new OpenApiMediaType { - Schema = new OpenApiSchema - { - Type = "string" - } + Schema = new JsonSchemaBuilder().Type(SchemaValueType.String).Build() } } } @@ -117,23 +115,18 @@ public void LocatePathOperationContentSchema() [Fact] public void WalkDOMWithCycles() { - var loopySchema = new OpenApiSchema() - { - Type = "object", - Properties = new Dictionary() - { - ["name"] = new OpenApiSchema() { Type = "string" } - } - }; + var loopySchema = new JsonSchemaBuilder() + .Type(SchemaValueType.Object) + .Properties(("name", new JsonSchemaBuilder().Type(SchemaValueType.String))); - loopySchema.Properties.Add("parent", loopySchema); + loopySchema.Properties(("parent", loopySchema)); var doc = new OpenApiDocument() { Paths = new OpenApiPaths(), Components = new OpenApiComponents() { - Schemas = new Dictionary + Schemas = new Dictionary { ["loopy"] = loopySchema } @@ -149,7 +142,8 @@ public void WalkDOMWithCycles() "#/paths", "#/components", "#/components/schemas/loopy", - "#/components/schemas/loopy/properties/name", + "#/components/schemas/loopy/properties/parent", + "#/components/schemas/loopy/properties/parent/properties/name", "#/tags" }); } @@ -161,27 +155,9 @@ public void WalkDOMWithCycles() public void LocateReferences() { - var baseSchema = new OpenApiSchema() - { - Reference = new OpenApiReference() - { - Id = "base", - Type = ReferenceType.Schema - }, - UnresolvedReference = false - }; - - var derivedSchema = new OpenApiSchema - { - AnyOf = new List() { baseSchema }, - Reference = new OpenApiReference() - { - Id = "derived", - Type = ReferenceType.Schema - }, - UnresolvedReference = false - }; + var baseSchema = new JsonSchemaBuilder().Ref("base").Build(); + var derivedSchema = new JsonSchemaBuilder().AnyOf(baseSchema).Ref("derived").Build(); var testHeader = new OpenApiHeader() { Schema = derivedSchema, @@ -226,7 +202,7 @@ public void LocateReferences() }, Components = new OpenApiComponents() { - Schemas = new Dictionary() + Schemas = new Dictionary() { ["derived"] = derivedSchema, ["base"] = baseSchema, @@ -245,7 +221,9 @@ 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: #/components/schemas/derived/anyOf/0", + "referenceAt: #/components/schemas/derived", + "referenceAt: #/components/schemas/derived/anyOf", + "referenceAt: #/components/schemas/base", "referenceAt: #/components/headers/test-header/schema" }); } @@ -313,9 +291,15 @@ public override void Visit(OpenApiMediaType mediaType) Locations.Add(this.PathString); } - public override void Visit(OpenApiSchema schema) + public override void Visit(IBaseDocument document) { - Locations.Add(this.PathString); + var schema = document as JsonSchema; + VisitJsonSchema(schema); + } + + public override void Visit(ref JsonSchema schema) + { + VisitJsonSchema(schema); } public override void Visit(IList openApiTags) @@ -332,5 +316,17 @@ public override void Visit(OpenApiServer server) { Locations.Add(this.PathString); } + + private void VisitJsonSchema(JsonSchema schema) + { + if (schema.GetRef() != null) + { + Locations.Add("referenceAt: " + this.PathString); + } + else + { + Locations.Add(this.PathString); + } + } } } diff --git a/test/Microsoft.OpenApi.Tests/Workspaces/OpenApiReferencableTests.cs b/test/Microsoft.OpenApi.Tests/Workspaces/OpenApiReferencableTests.cs index 2bae02b1f..02d9d7d07 100644 --- a/test/Microsoft.OpenApi.Tests/Workspaces/OpenApiReferencableTests.cs +++ b/test/Microsoft.OpenApi.Tests/Workspaces/OpenApiReferencableTests.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using Json.Schema; using Microsoft.OpenApi.Exceptions; using Microsoft.OpenApi.Extensions; using Microsoft.OpenApi.Interfaces; @@ -20,7 +21,7 @@ public class OpenApiReferencableTests private static readonly OpenApiLink _linkFragment = new OpenApiLink(); private static readonly OpenApiHeader _headerFragment = new OpenApiHeader() { - Schema = new OpenApiSchema(), + Schema = new JsonSchemaBuilder().Build(), Examples = new Dictionary { { "example1", new OpenApiExample() } @@ -28,7 +29,7 @@ public class OpenApiReferencableTests }; private static readonly OpenApiParameter _parameterFragment = new OpenApiParameter { - Schema = new OpenApiSchema(), + Schema = new JsonSchemaBuilder().Build(), Examples = new Dictionary { { "example1", new OpenApiExample() } @@ -46,7 +47,7 @@ public class OpenApiReferencableTests { "link1", new OpenApiLink() } } }; - private static readonly OpenApiSchema _schemaFragment = new OpenApiSchema(); + private static readonly JsonSchema _schemaFragment = new JsonSchemaBuilder().Build(); private static readonly OpenApiSecurityScheme _securitySchemeFragment = new OpenApiSecurityScheme(); private static readonly OpenApiTag _tagFragment = new OpenApiTag(); @@ -57,16 +58,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 63045847b..0aad60a55 100644 --- a/test/Microsoft.OpenApi.Tests/Workspaces/OpenApiWorkspaceTests.cs +++ b/test/Microsoft.OpenApi.Tests/Workspaces/OpenApiWorkspaceTests.cs @@ -4,8 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Text; -using System.Threading.Tasks; +using Json.Schema; using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Services; using Xunit; @@ -31,32 +30,27 @@ public void OpenApiWorkspacesAllowDocumentsToReferenceEachOther() { var workspace = new OpenApiWorkspace(); - workspace.AddDocument("root", new OpenApiDocument() { + workspace.AddDocument("root", new OpenApiDocument() + { Paths = new OpenApiPaths() { ["/"] = new OpenApiPathItem() { - Operations = new Dictionary() + Operations = new Dictionary() { - [OperationType.Get] = new OpenApiOperation() { + [OperationType.Get] = new OpenApiOperation() + { Responses = new OpenApiResponses() { ["200"] = new OpenApiResponse() { - Content = new Dictionary() - { - ["application/json"] = new OpenApiMediaType() - { - Schema = new OpenApiSchema() - { - Reference = new OpenApiReference() - { - Id = "test", - Type = ReferenceType.Schema - } - } - } - } + Content = new Dictionary() + { + ["application/json"] = new OpenApiMediaType() + { + Schema = new JsonSchemaBuilder().Ref("test").Build() + } + } } } } @@ -64,14 +58,12 @@ public void OpenApiWorkspacesAllowDocumentsToReferenceEachOther() } } }); - workspace.AddDocument("common", new OpenApiDocument() { + workspace.AddDocument("common", new OpenApiDocument() + { Components = new OpenApiComponents() { Schemas = { - ["test"] = new OpenApiSchema() { - Type = "string", - Description = "The referenced one" - } + ["test"] = new JsonSchemaBuilder().Type(SchemaValueType.String).Description("The referenced one").Build() } } }); @@ -83,16 +75,15 @@ public void OpenApiWorkspacesAllowDocumentsToReferenceEachOther() public void OpenApiWorkspacesCanResolveExternalReferences() { var workspace = new OpenApiWorkspace(); - workspace.AddDocument("common", CreateCommonDocument()); - var schema = workspace.ResolveReference(new OpenApiReference() - { - Id = "test", - Type = ReferenceType.Schema, - ExternalResource ="common" - }) as OpenApiSchema; + var doc = CreateCommonDocument(); + var location = "common"; + + workspace.AddDocument(location, doc); + var schema = workspace.ResolveJsonSchemaReference(new Uri("https://everything.json/common#/components/schemas/test")); + Assert.NotNull(schema); - Assert.Equal("The referenced one", schema.Description); + Assert.Equal("The referenced one", schema.GetDescription()); } [Fact] @@ -109,16 +100,7 @@ public void OpenApiWorkspacesAllowDocumentsToReferenceEachOther_short() { re.Description = "Success"; re.CreateContent("application/json", co => - co.Schema = new OpenApiSchema() - { - Reference = new OpenApiReference() // Reference - { - Id = "test", - Type = ReferenceType.Schema, - ExternalResource = "common" - }, - UnresolvedReference = true - } + co.Schema = new JsonSchemaBuilder().Ref("test").Build() ); }) ); @@ -130,8 +112,8 @@ public void OpenApiWorkspacesAllowDocumentsToReferenceEachOther_short() 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); + //var effectiveSchema = schema.GetEffective(doc); + //Assert.False(effectiveSchema.UnresolvedReference); } [Fact] @@ -161,18 +143,15 @@ 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 JsonSchemaBuilder().Type(SchemaValueType.String).Description("Schema from a fragment").Build(); + workspace.AddSchemaFragment("fragment", schemaFragment); // Act - var schema = workspace.ResolveReference(new OpenApiReference() - { - ExternalResource = "fragment" - }) as OpenApiSchema; + var schema = workspace.ResolveJsonSchemaReference(new Uri("https://everything.json/common#/components/schemas/test")); // Assert Assert.NotNull(schema); - Assert.Equal("Schema from a fragment", schema.Description); + Assert.Equal("Schema from a fragment", schema.GetDescription()); } [Fact] @@ -210,50 +189,48 @@ private static OpenApiDocument CreateCommonDocument() Components = new OpenApiComponents() { Schemas = { - ["test"] = new OpenApiSchema() { - Type = "string", - Description = "The referenced one" - } + ["test"] = new JsonSchemaBuilder().Type(SchemaValueType.String).Description("The referenced one").Build() } } }; } } - public static class OpenApiFactoryExtensions { - - public static OpenApiDocument CreatePathItem(this OpenApiDocument document, string path, Action config) + public static class OpenApiFactoryExtensions { - var pathItem = new OpenApiPathItem(); - config(pathItem); - document.Paths = new OpenApiPaths(); - document.Paths.Add(path, pathItem); - return document; - } - public static OpenApiPathItem CreateOperation(this OpenApiPathItem parent, OperationType opType, Action config) - { - var child = new OpenApiOperation(); - config(child); - parent.Operations.Add(opType, child); - return parent; - } + public static OpenApiDocument CreatePathItem(this OpenApiDocument document, string path, Action config) + { + var pathItem = new OpenApiPathItem(); + config(pathItem); + document.Paths = new OpenApiPaths(); + document.Paths.Add(path, pathItem); + return document; + } - public static OpenApiOperation CreateResponse(this OpenApiOperation parent, string status, Action config) - { - var child = new OpenApiResponse(); - config(child); - parent.Responses.Add(status, child); - return parent; - } + public static OpenApiPathItem CreateOperation(this OpenApiPathItem parent, OperationType opType, Action config) + { + var child = new OpenApiOperation(); + config(child); + parent.Operations.Add(opType, child); + return parent; + } - public static OpenApiResponse CreateContent(this OpenApiResponse parent, string mediaType, Action config) - { - var child = new OpenApiMediaType(); - config(child); - parent.Content.Add(mediaType, child); - return parent; - } + public static OpenApiOperation CreateResponse(this OpenApiOperation parent, string status, Action config) + { + var child = new OpenApiResponse(); + config(child); + parent.Responses.Add(status, child); + return parent; + } -} + public static OpenApiResponse CreateContent(this OpenApiResponse parent, string mediaType, Action config) + { + var child = new OpenApiMediaType(); + config(child); + parent.Content.Add(mediaType, child); + return parent; + } + + } } diff --git a/test/Microsoft.OpenApi.Tests/Writers/OpenApiWriterAnyExtensionsTests.cs b/test/Microsoft.OpenApi.Tests/Writers/OpenApiWriterAnyExtensionsTests.cs index 01ab6e02d..558ed0574 100644 --- a/test/Microsoft.OpenApi.Tests/Writers/OpenApiWriterAnyExtensionsTests.cs +++ b/test/Microsoft.OpenApi.Tests/Writers/OpenApiWriterAnyExtensionsTests.cs @@ -152,7 +152,7 @@ public static IEnumerable StringifiedDateTimes get { return - from input in new [] { + from input in new[] { "2017-1-2", "1999-01-02T12:10:22", "1999-01-03", @@ -183,7 +183,7 @@ public static IEnumerable BooleanInputs get { return - from input in new [] { true, false } + from input in new[] { true, false } from shouldBeTerse in shouldProduceTerseOutputValues select new object[] { input, shouldBeTerse }; } diff --git a/test/Microsoft.OpenApi.Tests/Writers/OpenApiWriterSpecialCharacterTests.cs b/test/Microsoft.OpenApi.Tests/Writers/OpenApiWriterSpecialCharacterTests.cs index 6ac47d6c3..c091e9502 100644 --- a/test/Microsoft.OpenApi.Tests/Writers/OpenApiWriterSpecialCharacterTests.cs +++ b/test/Microsoft.OpenApi.Tests/Writers/OpenApiWriterSpecialCharacterTests.cs @@ -157,11 +157,11 @@ public void WriteStringWithNewlineCharactersInArrayAsYamlWorks(string input, str public void WriteStringAsYamlDoesNotDependOnSystemCulture(string input, string expected, string culture) { CultureInfo.CurrentCulture = CultureInfo.GetCultureInfo(culture); - + // Arrange var outputStringWriter = new StringWriter(CultureInfo.InvariantCulture); var writer = new OpenApiYamlWriter(outputStringWriter); - + // Act writer.WriteValue(input); var actual = outputStringWriter.GetStringBuilder().ToString(); diff --git a/test/Microsoft.OpenApi.Tests/Writers/OpenApiYamlWriterTests.cs b/test/Microsoft.OpenApi.Tests/Writers/OpenApiYamlWriterTests.cs index 1a15ea3b4..75ddce41e 100644 --- a/test/Microsoft.OpenApi.Tests/Writers/OpenApiYamlWriterTests.cs +++ b/test/Microsoft.OpenApi.Tests/Writers/OpenApiYamlWriterTests.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; @@ -7,6 +7,7 @@ using System.Globalization; using System.IO; using FluentAssertions; +using Json.Schema; using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Writers; using Xunit; @@ -373,7 +374,7 @@ public void WriteInlineSchema() components: { }"; var outputString = new StringWriter(CultureInfo.InvariantCulture); - var writer = new OpenApiYamlWriter(outputString, new OpenApiWriterSettings { InlineLocalReferences = true } ); + var writer = new OpenApiYamlWriter(outputString, new OpenApiWriterSettings { InlineLocalReferences = true }); // Act doc.SerializeAsV3(writer); @@ -382,6 +383,7 @@ public void WriteInlineSchema() // Assert actual = actual.MakeLineBreaksEnvironmentNeutral(); expected = expected.MakeLineBreaksEnvironmentNeutral(); + actual.Should().BeEquivalentTo(expected); Assert.Equal(expected, actual); } @@ -424,131 +426,8 @@ public void WriteInlineSchemaV2() private static OpenApiDocument CreateDocWithSimpleSchemaToInline() { // Arrange - var thingSchema = new OpenApiSchema() - { - Type = "object", - UnresolvedReference = false, - Reference = new OpenApiReference - { - Id = "thing", - Type = ReferenceType.Schema - } - }; - - var doc = new OpenApiDocument() - { - Info = new OpenApiInfo() - { - Title = "Demo", - Version = "1.0.0" - }, - Paths = new OpenApiPaths() - { - ["/"] = new OpenApiPathItem - { - Operations = { - [OperationType.Get] = new OpenApiOperation() { - Responses = { - ["200"] = new OpenApiResponse { - Description = "OK", - Content = { - ["application/json"] = new OpenApiMediaType() { - Schema = thingSchema - } - } - } - } - } - } - } - }, - Components = new OpenApiComponents - { - Schemas = { - ["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 OpenApiWriterSettings { 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 OpenApiReference - { - Id = "thing", - Type = ReferenceType.Schema - } - }; - thingSchema.Properties["children"] = thingSchema; - - var relatedSchema = new OpenApiSchema() - { - Type = "integer", - }; - - thingSchema.Properties["related"] = relatedSchema; + var thingSchema = new JsonSchemaBuilder().Type(SchemaValueType.Object).Ref("#/components/schemas/thing").Build(); var doc = new OpenApiDocument() { @@ -583,63 +462,8 @@ private static OpenApiDocument CreateDocWithRecursiveSchemaReference() ["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 OpenApiWriterSettings { InlineLocalReferences = true }); - - // Act - doc.SerializeAsV2(writer); - var actual = outputString.GetStringBuilder().ToString(); - - // Assert - actual = actual.MakeLineBreaksEnvironmentNeutral(); - expected = expected.MakeLineBreaksEnvironmentNeutral(); - Assert.Equal(expected, actual); + return doc; } - } }