diff --git a/build.cmd b/build.cmd index d0d68b5d7..0657b8e1e 100644 --- a/build.cmd +++ b/build.cmd @@ -1,7 +1,7 @@ @echo off if "%~1"=="" goto :error -SET %VERSION%=%~1 +SET VERSION=%~1 Echo Building Microsoft.OpenApi diff --git a/src/Directory.Build.props b/src/Directory.Build.props new file mode 100644 index 000000000..adb086cc1 --- /dev/null +++ b/src/Directory.Build.props @@ -0,0 +1,8 @@ + + + $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb + + + + + \ No newline at end of file diff --git a/src/Microsoft.OpenApi.Readers/Exceptions/OpenApiReaderException.cs b/src/Microsoft.OpenApi.Readers/Exceptions/OpenApiReaderException.cs index 25aac4978..ee829d6b3 100644 --- a/src/Microsoft.OpenApi.Readers/Exceptions/OpenApiReaderException.cs +++ b/src/Microsoft.OpenApi.Readers/Exceptions/OpenApiReaderException.cs @@ -2,6 +2,8 @@ // Licensed under the MIT license. using System; +using Microsoft.OpenApi.Exceptions; +using SharpYaml.Serialization; namespace Microsoft.OpenApi.Readers.Exceptions { @@ -9,7 +11,7 @@ namespace Microsoft.OpenApi.Readers.Exceptions /// Defines an exception indicating OpenAPI Reader encountered an issue while reading. /// [Serializable] - public class OpenApiReaderException : Exception + public class OpenApiReaderException : OpenApiException { /// /// Initializes the class. @@ -22,6 +24,18 @@ public OpenApiReaderException() { } /// Plain text error message for this exception. public OpenApiReaderException(string message) : base(message) { } + /// + /// Initializes the class with a message and line, column location of error. + /// + /// Plain text error message for this exception. + /// Parsing node where error occured + public OpenApiReaderException(string message, YamlNode node) : base(message) + { + // This only includes line because using a char range causes tests to break due to CR/LF & LF differences + // See https://tools.ietf.org/html/rfc5147 for syntax + Pointer = $"#line={node.Start.Line}"; + } + /// /// Initializes the class with a custom message and inner exception. /// diff --git a/src/Microsoft.OpenApi.Readers/Exceptions/OpenApiUnsupportedSpecVersionException.cs b/src/Microsoft.OpenApi.Readers/Exceptions/OpenApiUnsupportedSpecVersionException.cs index 84faf9d4e..199020784 100644 --- a/src/Microsoft.OpenApi.Readers/Exceptions/OpenApiUnsupportedSpecVersionException.cs +++ b/src/Microsoft.OpenApi.Readers/Exceptions/OpenApiUnsupportedSpecVersionException.cs @@ -10,9 +10,9 @@ namespace Microsoft.OpenApi.Readers.Exceptions /// Defines an exception indicating OpenAPI Reader encountered an unsupported specification version while reading. /// [Serializable] - public class OpenApiUnsupportedSpecVersionException : OpenApiReaderException + public class OpenApiUnsupportedSpecVersionException : Exception { - const string messagePattern = "OpenAPI specification version {0} is not supported."; + const string messagePattern = "OpenAPI specification version '{0}' is not supported."; /// /// Initializes the class with a specification version. @@ -21,11 +21,6 @@ public class OpenApiUnsupportedSpecVersionException : OpenApiReaderException public OpenApiUnsupportedSpecVersionException(string specificationVersion) : base(string.Format(CultureInfo.InvariantCulture, messagePattern, specificationVersion)) { - if (string.IsNullOrWhiteSpace(specificationVersion)) - { - throw new ArgumentException("Value cannot be null or white space.", nameof(specificationVersion)); - } - this.SpecificationVersion = specificationVersion; } @@ -38,11 +33,6 @@ public OpenApiUnsupportedSpecVersionException(string specificationVersion) public OpenApiUnsupportedSpecVersionException(string specificationVersion, Exception innerException) : base(string.Format(CultureInfo.InvariantCulture, messagePattern, specificationVersion), innerException) { - if (string.IsNullOrWhiteSpace(specificationVersion)) - { - throw new ArgumentException("Value cannot be null or white space.", nameof(specificationVersion)); - } - this.SpecificationVersion = specificationVersion; } diff --git a/src/Microsoft.OpenApi.Readers/Interface/IOpenApiVersionService.cs b/src/Microsoft.OpenApi.Readers/Interface/IOpenApiVersionService.cs index 80ac9d28f..32dd420f4 100644 --- a/src/Microsoft.OpenApi.Readers/Interface/IOpenApiVersionService.cs +++ b/src/Microsoft.OpenApi.Readers/Interface/IOpenApiVersionService.cs @@ -23,9 +23,12 @@ internal interface IOpenApiVersionService OpenApiReference ConvertToOpenApiReference(string reference, ReferenceType? type); /// - /// Function that converts a MapNode into a Tag object in a version specific way + /// Loads an OpenAPI Element from a document fragment /// - Func TagLoader { get; } + /// Type of element to load + /// document fragment node + /// Instance of OpenAPIElement + T LoadElement(ParseNode node) where T : IOpenApiElement; /// /// Converts a generic RootNode instance into a strongly typed OpenApiDocument diff --git a/src/Microsoft.OpenApi.Readers/Microsoft.OpenApi.Readers.csproj b/src/Microsoft.OpenApi.Readers/Microsoft.OpenApi.Readers.csproj index 26091ae22..6fa86c0d7 100644 --- a/src/Microsoft.OpenApi.Readers/Microsoft.OpenApi.Readers.csproj +++ b/src/Microsoft.OpenApi.Readers/Microsoft.OpenApi.Readers.csproj @@ -10,7 +10,7 @@ Microsoft Microsoft.OpenApi.Readers Microsoft.OpenApi.Readers - 1.0.1 + 1.1.0 OpenAPI.NET Readers for JSON and YAML documents © Microsoft Corporation. All rights reserved. OpenAPI .NET diff --git a/src/Microsoft.OpenApi.Readers/OpenApiReaderError.cs b/src/Microsoft.OpenApi.Readers/OpenApiReaderError.cs deleted file mode 100644 index b06a01f31..000000000 --- a/src/Microsoft.OpenApi.Readers/OpenApiReaderError.cs +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Microsoft.OpenApi.Exceptions; -using Microsoft.OpenApi.Models; -using SharpYaml; - -namespace Microsoft.OpenApi.Readers -{ - /// - /// Error detected during the reading of some input and converting to an OpenApiDocument - /// - public class OpenApiReaderError : OpenApiError - { - - /// - /// Creates error object from thrown exception - /// - /// - public OpenApiReaderError(OpenApiException exception) : base(exception) - { - - } - - /// - /// Create error object from YAML SyntaxErrorException - /// - /// - public OpenApiReaderError(SyntaxErrorException exception) : base(String.Empty, exception.Message) - { - - } - } -} diff --git a/src/Microsoft.OpenApi.Readers/OpenApiReaderSettings.cs b/src/Microsoft.OpenApi.Readers/OpenApiReaderSettings.cs index 544fce2fd..1b1c2f367 100644 --- a/src/Microsoft.OpenApi.Readers/OpenApiReaderSettings.cs +++ b/src/Microsoft.OpenApi.Readers/OpenApiReaderSettings.cs @@ -42,12 +42,16 @@ public class OpenApiReaderSettings /// /// Dictionary of parsers for converting extensions into strongly typed classes /// - public Dictionary> ExtensionParsers { get; set; } = new Dictionary>(); + public Dictionary> ExtensionParsers { get; set; } = new Dictionary>(); /// /// Rules to use for validating OpenAPI specification. If none are provided a default set of rules are applied. /// public ValidationRuleSet RuleSet { get; set; } = ValidationRuleSet.GetDefaultRuleSet(); + /// + /// URL where relative references should be resolved from if the description does not contain Server definitions + /// + public Uri BaseUrl { get; set; } } } diff --git a/src/Microsoft.OpenApi.Readers/OpenApiStreamReader.cs b/src/Microsoft.OpenApi.Readers/OpenApiStreamReader.cs index 3aa1057fc..1e0c08695 100644 --- a/src/Microsoft.OpenApi.Readers/OpenApiStreamReader.cs +++ b/src/Microsoft.OpenApi.Readers/OpenApiStreamReader.cs @@ -6,6 +6,7 @@ using System.Linq; using Microsoft.OpenApi.Exceptions; using Microsoft.OpenApi.Extensions; +using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Readers.Interface; using Microsoft.OpenApi.Readers.Services; @@ -48,15 +49,16 @@ public OpenApiDocument Read(Stream input, out OpenApiDiagnostic diagnostic) { yamlDocument = LoadYamlDocument(input); } - catch (SyntaxErrorException ex) + catch (YamlException ex) { - diagnostic.Errors.Add(new OpenApiReaderError(ex)); + diagnostic.Errors.Add(new OpenApiError($"#char={ex.Start.Line}", ex.Message)); return new OpenApiDocument(); } context = new ParsingContext { - ExtensionParsers = _settings.ExtensionParsers + ExtensionParsers = _settings.ExtensionParsers, + BaseUrl = _settings.BaseUrl }; OpenApiDocument document = null; @@ -102,6 +104,60 @@ public OpenApiDocument Read(Stream input, out OpenApiDiagnostic diagnostic) return document; } + /// + /// Reads the stream input and parses the fragment of an OpenAPI description into an Open API Element. + /// + /// Stream containing OpenAPI description to parse. + /// Version of the OpenAPI specification that the fragment conforms to. + /// Returns diagnostic object containing errors detected during parsing + /// Instance of newly created OpenApiDocument + public T ReadFragment(Stream input, OpenApiSpecVersion version, out OpenApiDiagnostic diagnostic) where T : IOpenApiElement + { + ParsingContext context; + YamlDocument yamlDocument; + diagnostic = new OpenApiDiagnostic(); + + // Parse the YAML/JSON + try + { + yamlDocument = LoadYamlDocument(input); + } + catch (YamlException ex) + { + diagnostic.Errors.Add(new OpenApiError($"#line={ex.Start.Line}", ex.Message)); + return default(T); + } + + context = new ParsingContext + { + ExtensionParsers = _settings.ExtensionParsers + }; + + IOpenApiElement element = null; + + try + { + // Parse the OpenAPI element + element = context.ParseFragment(yamlDocument, version, diagnostic); + } + catch (OpenApiException ex) + { + diagnostic.Errors.Add(new OpenApiError(ex)); + } + + // Validate the element + if (_settings.RuleSet != null && _settings.RuleSet.Rules.Count > 0) + { + var errors = element.Validate(_settings.RuleSet); + foreach (var item in errors) + { + diagnostic.Errors.Add(item); + } + } + + return (T)element; + } + /// /// Helper method to turn streams into YamlDocument /// diff --git a/src/Microsoft.OpenApi.Readers/OpenApiStringReader.cs b/src/Microsoft.OpenApi.Readers/OpenApiStringReader.cs index 8530ca467..82b3a3ce7 100644 --- a/src/Microsoft.OpenApi.Readers/OpenApiStringReader.cs +++ b/src/Microsoft.OpenApi.Readers/OpenApiStringReader.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. using System.IO; +using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Readers.Interface; @@ -20,7 +21,7 @@ public class OpenApiStringReader : IOpenApiReader /// public OpenApiStringReader(OpenApiReaderSettings settings = null) { - _settings = settings ?? new OpenApiReaderSettings(); + _settings = settings ?? new OpenApiReaderSettings(); } /// @@ -38,5 +39,21 @@ public OpenApiDocument Read(string input, out OpenApiDiagnostic diagnostic) return new OpenApiStreamReader(_settings).Read(memoryStream, out diagnostic); } } + + /// + /// Reads the string input and parses it into an Open API element. + /// + public T ReadFragment(string input, OpenApiSpecVersion version, out OpenApiDiagnostic diagnostic) where T : IOpenApiElement + { + using (var memoryStream = new MemoryStream()) + { + var writer = new StreamWriter(memoryStream); + writer.Write(input); + writer.Flush(); + memoryStream.Position = 0; + + return new OpenApiStreamReader(_settings).ReadFragment(memoryStream, version, out diagnostic); + } + } } } \ No newline at end of file diff --git a/src/Microsoft.OpenApi.Readers/ParseNodes/ListNode.cs b/src/Microsoft.OpenApi.Readers/ParseNodes/ListNode.cs index f7630c617..ee3553ad4 100644 --- a/src/Microsoft.OpenApi.Readers/ParseNodes/ListNode.cs +++ b/src/Microsoft.OpenApi.Readers/ParseNodes/ListNode.cs @@ -52,7 +52,7 @@ public override List CreateSimpleList(Func map) $"Expected list at line {_nodeList.Start.Line} while parsing {typeof(T).Name}"); } - return _nodeList.Select(n => map(new ValueNode(Context, Diagnostic, (YamlScalarNode)n))).ToList(); + return _nodeList.Select(n => map(new ValueNode(Context, Diagnostic, n))).ToList(); } public IEnumerator GetEnumerator() diff --git a/src/Microsoft.OpenApi.Readers/ParseNodes/MapNode.cs b/src/Microsoft.OpenApi.Readers/ParseNodes/MapNode.cs index ef18cf11a..95aa4ff62 100644 --- a/src/Microsoft.OpenApi.Readers/ParseNodes/MapNode.cs +++ b/src/Microsoft.OpenApi.Readers/ParseNodes/MapNode.cs @@ -9,6 +9,7 @@ using Microsoft.OpenApi.Exceptions; using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Readers.Exceptions; using SharpYaml.Schemas; using SharpYaml.Serialization; @@ -27,16 +28,16 @@ public MapNode(ParsingContext context, OpenApiDiagnostic diagnostic, string yaml { } - public MapNode(ParsingContext context, OpenApiDiagnostic diagnostic, YamlMappingNode node) : base( + public MapNode(ParsingContext context, OpenApiDiagnostic diagnostic, YamlNode node) : base( context, diagnostic) { - if (node == null) + if (!(node is YamlMappingNode mapNode)) { - throw new OpenApiException("Expected map"); + throw new OpenApiReaderException("Expected map.", node); } - this._node = node; + this._node = mapNode; _nodes = this._node.Children .Select(kvp => new PropertyNode(Context, Diagnostic, kvp.Key.GetScalarValue(), kvp.Value)) diff --git a/src/Microsoft.OpenApi.Readers/ParseNodes/ParseNode.cs b/src/Microsoft.OpenApi.Readers/ParseNodes/ParseNode.cs index a80176317..abeee3d26 100644 --- a/src/Microsoft.OpenApi.Readers/ParseNodes/ParseNode.cs +++ b/src/Microsoft.OpenApi.Readers/ParseNodes/ParseNode.cs @@ -8,6 +8,7 @@ using Microsoft.OpenApi.Exceptions; using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Readers.Exceptions; using SharpYaml.Serialization; namespace Microsoft.OpenApi.Readers.ParseNodes @@ -26,10 +27,9 @@ protected ParseNode(ParsingContext parsingContext, OpenApiDiagnostic diagnostic) public MapNode CheckMapNode(string nodeName) { - var mapNode = this as MapNode; - if (mapNode == null) + if (!(this is MapNode mapNode)) { - throw new OpenApiException($"{nodeName} must be a map/object"); + throw new OpenApiReaderException($"{nodeName} must be a map/object"); } return mapNode; @@ -37,15 +37,13 @@ public MapNode CheckMapNode(string nodeName) public static ParseNode Create(ParsingContext context, OpenApiDiagnostic diagnostic, YamlNode node) { - var listNode = node as YamlSequenceNode; - if (listNode != null) + if (node is YamlSequenceNode listNode) { return new ListNode(context, diagnostic, listNode); } - var mapNode = node as YamlMappingNode; - if (mapNode != null) + if (node is YamlMappingNode mapNode) { return new MapNode(context, diagnostic, mapNode); } @@ -55,12 +53,12 @@ public static ParseNode Create(ParsingContext context, OpenApiDiagnostic diagnos public virtual List CreateList(Func map) { - throw new OpenApiException("Cannot create list from this type of node."); + throw new OpenApiReaderException("Cannot create list from this type of node."); } public virtual Dictionary CreateMap(Func map) { - throw new OpenApiException("Cannot create map from this type of node."); + throw new OpenApiReaderException("Cannot create map from this type of node."); } public virtual Dictionary CreateMapWithReference( @@ -68,37 +66,37 @@ public virtual Dictionary CreateMapWithReference( Func map) where T : class, IOpenApiReferenceable { - throw new OpenApiException("Cannot create map from this reference."); + throw new OpenApiReaderException("Cannot create map from this reference."); } public virtual List CreateSimpleList(Func map) { - throw new OpenApiException("Cannot create simple list from this type of node."); + throw new OpenApiReaderException("Cannot create simple list from this type of node."); } public virtual Dictionary CreateSimpleMap(Func map) { - throw new OpenApiException("Cannot create simple map from this type of node."); + throw new OpenApiReaderException("Cannot create simple map from this type of node."); } public virtual IOpenApiAny CreateAny() { - throw new OpenApiException("Cannot create an Any object this type of node."); + throw new OpenApiReaderException("Cannot create an Any object this type of node."); } public virtual string GetRaw() { - throw new OpenApiException("Cannot get raw value from this type of node."); + throw new OpenApiReaderException("Cannot get raw value from this type of node."); } public virtual string GetScalarValue() { - throw new OpenApiException("Cannot create a scalar value from this type of node."); + throw new OpenApiReaderException("Cannot create a scalar value from this type of node."); } public virtual List CreateListOfAny() { - throw new OpenApiException("Cannot create a list from this type of node."); + throw new OpenApiReaderException("Cannot create a list from this type of node."); } } diff --git a/src/Microsoft.OpenApi.Readers/ParseNodes/PropertyNode.cs b/src/Microsoft.OpenApi.Readers/ParseNodes/PropertyNode.cs index 1f4c9adcb..39b9370f8 100644 --- a/src/Microsoft.OpenApi.Readers/ParseNodes/PropertyNode.cs +++ b/src/Microsoft.OpenApi.Readers/ParseNodes/PropertyNode.cs @@ -7,6 +7,7 @@ using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Exceptions; using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Readers.Exceptions; using SharpYaml.Serialization; namespace Microsoft.OpenApi.Readers.ParseNodes @@ -40,10 +41,14 @@ public void ParseField( Context.StartObject(Name); fixedFieldMap(parentInstance, Value); } + catch (OpenApiReaderException ex) + { + Diagnostic.Errors.Add(new OpenApiError(ex)); + } catch (OpenApiException ex) { ex.Pointer = Context.GetLocation(); - Diagnostic.Errors.Add(new OpenApiReaderError(ex)); + Diagnostic.Errors.Add(new OpenApiError(ex)); } finally { @@ -60,10 +65,14 @@ public void ParseField( Context.StartObject(Name); map(parentInstance, Name, Value); } + catch (OpenApiReaderException ex) + { + Diagnostic.Errors.Add(new OpenApiError(ex)); + } catch (OpenApiException ex) { ex.Pointer = Context.GetLocation(); - Diagnostic.Errors.Add(new OpenApiReaderError(ex)); + Diagnostic.Errors.Add(new OpenApiError(ex)); } finally { diff --git a/src/Microsoft.OpenApi.Readers/ParseNodes/ValueNode.cs b/src/Microsoft.OpenApi.Readers/ParseNodes/ValueNode.cs index 44d23d856..f17b49f22 100644 --- a/src/Microsoft.OpenApi.Readers/ParseNodes/ValueNode.cs +++ b/src/Microsoft.OpenApi.Readers/ParseNodes/ValueNode.cs @@ -4,6 +4,7 @@ using System; using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Exceptions; +using Microsoft.OpenApi.Readers.Exceptions; using SharpYaml.Serialization; namespace Microsoft.OpenApi.Readers.ParseNodes @@ -12,23 +13,20 @@ internal class ValueNode : ParseNode { private readonly YamlScalarNode _node; - public ValueNode(ParsingContext context, OpenApiDiagnostic diagnostic, YamlScalarNode scalarNode) : base( + public ValueNode(ParsingContext context, OpenApiDiagnostic diagnostic, YamlNode node) : base( context, diagnostic) { + if (!(node is YamlScalarNode scalarNode)) + { + throw new OpenApiReaderException("Expected a value.", node); + } _node = scalarNode; } public override string GetScalarValue() { - var scalarNode = _node; - - if (scalarNode == null) - { - throw new OpenApiException($"Expected scalar at line {_node.Start.Line}"); - } - - return scalarNode.Value; + return _node.Value; } /// diff --git a/src/Microsoft.OpenApi.Readers/ParsingContext.cs b/src/Microsoft.OpenApi.Readers/ParsingContext.cs index e58ebde04..f693bfddc 100644 --- a/src/Microsoft.OpenApi.Readers/ParsingContext.cs +++ b/src/Microsoft.OpenApi.Readers/ParsingContext.cs @@ -25,15 +25,16 @@ public class ParsingContext private readonly Dictionary _tempStorage = new Dictionary(); private IOpenApiVersionService _versionService; private readonly Dictionary> _loopStacks = new Dictionary>(); - internal Dictionary> ExtensionParsers { get; set; } = new Dictionary>(); + internal Dictionary> ExtensionParsers { get; set; } = new Dictionary>(); internal RootNode RootNode { get; set; } internal List Tags { get; private set; } = new List(); + internal Uri BaseUrl { get; set; } /// /// Initiates the parsing process. Not thread safe and should only be called once on a parsing context /// - /// - /// + /// Yaml document to parse. + /// Diagnostic object which will return diagnostic results of the operation. /// An OpenApiDocument populated based on the passed yamlDocument internal OpenApiDocument Parse(YamlDocument yamlDocument, OpenApiDiagnostic diagnostic) { @@ -47,13 +48,13 @@ internal OpenApiDocument Parse(YamlDocument yamlDocument, OpenApiDiagnostic diag { case string version when version == "2.0": VersionService = new OpenApiV2VersionService(); - doc = this.VersionService.LoadDocument(this.RootNode); + doc = VersionService.LoadDocument(RootNode); diagnostic.SpecificationVersion = OpenApiSpecVersion.OpenApi2_0; break; case string version when version.StartsWith("3.0"): - this.VersionService = new OpenApiV3VersionService(); - doc = this.VersionService.LoadDocument(this.RootNode); + VersionService = new OpenApiV3VersionService(); + doc = VersionService.LoadDocument(RootNode); diagnostic.SpecificationVersion = OpenApiSpecVersion.OpenApi3_0; break; @@ -64,6 +65,34 @@ internal OpenApiDocument Parse(YamlDocument yamlDocument, OpenApiDiagnostic diag return doc; } + /// + /// Initiates the parsing process of a fragment. Not thread safe and should only be called once on a parsing context + /// + /// + /// OpenAPI version of the fragment + /// Diagnostic object which will return diagnostic results of the operation. + /// An OpenApiDocument populated based on the passed yamlDocument + internal T ParseFragment(YamlDocument yamlDocument, OpenApiSpecVersion version, OpenApiDiagnostic diagnostic) where T: IOpenApiElement + { + var node = ParseNode.Create(this, diagnostic, yamlDocument.RootNode); + + T element = default(T); + + switch (version) + { + case OpenApiSpecVersion.OpenApi2_0: + VersionService = new OpenApiV2VersionService(); + element = this.VersionService.LoadElement(node); + break; + + case OpenApiSpecVersion.OpenApi3_0: + this.VersionService = new OpenApiV3VersionService(); + element = this.VersionService.LoadElement(node); + break; + } + + return element; + } /// /// Gets the version of the Open API document. @@ -82,20 +111,6 @@ private static string GetVersion(RootNode rootNode) return versionNode?.GetScalarValue(); } - private void ComputeTags(List tags, Func loadTag) - { - // Precompute the tags array so that each tag reference does not require a new deserialization. - var tagListPointer = new JsonPointer("#/tags"); - - var tagListNode = RootNode.Find(tagListPointer); - - if (tagListNode != null && tagListNode is ListNode) - { - var tagListNodeAsListNode = (ListNode)tagListNode; - tags.AddRange(tagListNodeAsListNode.CreateList(loadTag)); - } - } - /// /// Service providing all Version specific conversion functions /// @@ -108,7 +123,6 @@ internal IOpenApiVersionService VersionService set { _versionService = value; - ComputeTags(Tags, VersionService.TagLoader); } } diff --git a/src/Microsoft.OpenApi.Readers/V2/OpenApiContactDeserializer.cs b/src/Microsoft.OpenApi.Readers/V2/OpenApiContactDeserializer.cs index 3bcddffc0..2469c887f 100644 --- a/src/Microsoft.OpenApi.Readers/V2/OpenApiContactDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V2/OpenApiContactDeserializer.cs @@ -38,7 +38,7 @@ internal static partial class OpenApiV2Deserializer private static PatternFieldMap _contactPatternFields = new PatternFieldMap { - {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, n.CreateAny())} + {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, LoadExtension(p, n))} }; public static OpenApiContact LoadContact(ParseNode node) diff --git a/src/Microsoft.OpenApi.Readers/V2/OpenApiDocumentDeserializer.cs b/src/Microsoft.OpenApi.Readers/V2/OpenApiDocumentDeserializer.cs index 559f325ce..525a3f52f 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.Linq; using Microsoft.OpenApi.Extensions; +using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Readers.ParseNodes; using Microsoft.OpenApi.Services; @@ -116,24 +117,96 @@ internal static partial class OpenApiV2Deserializer private static 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, n.CreateAny())} + {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, LoadExtension(p, n))} }; - private static void MakeServers(IList servers, ParsingContext context) + private static void MakeServers(IList servers, ParsingContext context, Uri defaultUrl) { var host = context.GetFromTempStorage("host"); var basePath = context.GetFromTempStorage("basePath"); var schemes = context.GetFromTempStorage>("schemes"); - if (schemes != null) + // If nothing is provided, don't create a server + if (host == null && basePath == null && schemes == null) + { + return; + } + + // Fill in missing information based on the defaultUrl + if (defaultUrl != null) + { + host = host ?? defaultUrl.GetComponents(UriComponents.NormalizedHost, UriFormat.SafeUnescaped); + basePath = basePath ?? defaultUrl.GetComponents(UriComponents.Path, UriFormat.SafeUnescaped); + schemes = schemes ?? new List { defaultUrl.GetComponents(UriComponents.Scheme, UriFormat.SafeUnescaped) }; + } + else if (String.IsNullOrEmpty(host) && String.IsNullOrEmpty(basePath)) + { + return; // Can't make a server object out of just a Scheme + } + + // Create the Server objects + if (schemes != null && schemes.Count > 0) { foreach (var scheme in schemes) { - var server = new OpenApiServer(); - server.Url = scheme + "://" + (host ?? "example.org/") + (basePath ?? "/"); + var server = new OpenApiServer + { + Url = BuildUrl(scheme, host, basePath) + }; + servers.Add(server); } } + else + { + var server = new OpenApiServer + { + Url = BuildUrl(null, host, basePath) + }; + + servers.Add(server); + } + + foreach (var server in servers) + { + // Server Urls are always appended to Paths and Paths must start with / + // so removing the slash prevents a double slash. + if (server.Url.EndsWith("/")) + { + server.Url = server.Url.Substring(0, server.Url.Length - 1); + } + } + } + + private static string BuildUrl(string scheme, string host, string basePath) + { + if (String.IsNullOrEmpty(scheme) && !String.IsNullOrEmpty(host)) + { + host = "//" + host; // The double slash prefix creates a relative url where the scheme is defined by the BaseUrl + } + + int? port = null; + + if (!String.IsNullOrEmpty(host) && host.Contains(":")) + { + var pieces = host.Split(':'); + host = pieces.First(); + port = int.Parse(pieces.Last()); + } + + var uriBuilder = new UriBuilder() + { + Scheme = scheme, + Host = host, + Path = basePath + }; + + if (port != null) + { + uriBuilder.Port = port.Value; + } + + return uriBuilder.ToString(); } public static OpenApiDocument LoadOpenApi(RootNode rootNode) @@ -151,7 +224,7 @@ public static OpenApiDocument LoadOpenApi(RootNode rootNode) openApidoc.Servers = new List(); } - MakeServers(openApidoc.Servers, openApiNode.Context); + MakeServers(openApidoc.Servers, openApiNode.Context, rootNode.Context.BaseUrl); FixRequestBodyReferences(openApidoc); return openApidoc; @@ -167,7 +240,6 @@ private static void FixRequestBodyReferences(OpenApiDocument doc) var walker = new OpenApiWalker(fixer); walker.Walk(doc); } - } } @@ -197,5 +269,7 @@ public override void Visit(OpenApiOperation operation) }; } } + + } } \ No newline at end of file diff --git a/src/Microsoft.OpenApi.Readers/V2/OpenApiHeaderDeserializer.cs b/src/Microsoft.OpenApi.Readers/V2/OpenApiHeaderDeserializer.cs index 3d8f2e2f6..f23746a0a 100644 --- a/src/Microsoft.OpenApi.Readers/V2/OpenApiHeaderDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V2/OpenApiHeaderDeserializer.cs @@ -130,7 +130,7 @@ internal static partial class OpenApiV2Deserializer private static readonly PatternFieldMap _headerPatternFields = new PatternFieldMap { - {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, n.CreateAny())} + {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, LoadExtension(p, n))} }; public static OpenApiHeader LoadHeader(ParseNode node) diff --git a/src/Microsoft.OpenApi.Readers/V2/OpenApiInfoDeserializer.cs b/src/Microsoft.OpenApi.Readers/V2/OpenApiInfoDeserializer.cs index c546254cb..b2a3083f7 100644 --- a/src/Microsoft.OpenApi.Readers/V2/OpenApiInfoDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V2/OpenApiInfoDeserializer.cs @@ -57,7 +57,7 @@ internal static partial class OpenApiV2Deserializer private static PatternFieldMap _infoPatternFields = new PatternFieldMap { - {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, n.CreateAny())} + {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, LoadExtension(p, n))} }; public static OpenApiInfo LoadInfo(ParseNode node) diff --git a/src/Microsoft.OpenApi.Readers/V2/OpenApiLicenseDeserializer.cs b/src/Microsoft.OpenApi.Readers/V2/OpenApiLicenseDeserializer.cs index 963c572a5..567d37de1 100644 --- a/src/Microsoft.OpenApi.Readers/V2/OpenApiLicenseDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V2/OpenApiLicenseDeserializer.cs @@ -32,7 +32,7 @@ internal static partial class OpenApiV2Deserializer private static PatternFieldMap _licensePatternFields = new PatternFieldMap { - {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, n.CreateAny())} + {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, LoadExtension(p, n))} }; public static OpenApiLicense LoadLicense(ParseNode node) diff --git a/src/Microsoft.OpenApi.Readers/V2/OpenApiOperationDeserializer.cs b/src/Microsoft.OpenApi.Readers/V2/OpenApiOperationDeserializer.cs index 838c4e892..9b8507105 100644 --- a/src/Microsoft.OpenApi.Readers/V2/OpenApiOperationDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V2/OpenApiOperationDeserializer.cs @@ -90,7 +90,7 @@ internal static partial class OpenApiV2Deserializer private static readonly PatternFieldMap _operationPatternFields = new PatternFieldMap { - {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, n.CreateAny())} + {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, LoadExtension(p, n))} }; private static readonly FixedFieldMap _responsesFixedFields = @@ -100,7 +100,7 @@ internal static partial class OpenApiV2Deserializer new PatternFieldMap { {s => !s.StartsWith("x-"), (o, p, n) => o.Add(p, LoadResponse(n))}, - {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, n.CreateAny())} + {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, LoadExtension(p, n))} }; internal static OpenApiOperation LoadOperation(ParseNode node) @@ -131,7 +131,12 @@ internal static OpenApiOperation LoadOperation(ParseNode node) operation.RequestBody = CreateFormBody(node.Context, formParameters); } } - + + foreach (var response in operation.Responses.Values) + { + ProcessProduces(response, node.Context); + } + return operation; } diff --git a/src/Microsoft.OpenApi.Readers/V2/OpenApiParameterDeserializer.cs b/src/Microsoft.OpenApi.Readers/V2/OpenApiParameterDeserializer.cs index b5f459a34..12b033f73 100644 --- a/src/Microsoft.OpenApi.Readers/V2/OpenApiParameterDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V2/OpenApiParameterDeserializer.cs @@ -146,7 +146,7 @@ internal static partial class OpenApiV2Deserializer private static readonly PatternFieldMap _parameterPatternFields = new PatternFieldMap { - {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, n.CreateAny())} + {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, LoadExtension(p, n))} }; private static void LoadStyle(OpenApiParameter p, string v) diff --git a/src/Microsoft.OpenApi.Readers/V2/OpenApiPathItemDeserializer.cs b/src/Microsoft.OpenApi.Readers/V2/OpenApiPathItemDeserializer.cs index 19c806cbf..5b3a782e9 100644 --- a/src/Microsoft.OpenApi.Readers/V2/OpenApiPathItemDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V2/OpenApiPathItemDeserializer.cs @@ -39,7 +39,7 @@ internal static partial class OpenApiV2Deserializer private static readonly PatternFieldMap _pathItemPatternFields = new PatternFieldMap { - {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, n.CreateAny())}, + {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, LoadExtension(p, n))}, }; public static OpenApiPathItem LoadPathItem(ParseNode node) diff --git a/src/Microsoft.OpenApi.Readers/V2/OpenApiPathsDeserializer.cs b/src/Microsoft.OpenApi.Readers/V2/OpenApiPathsDeserializer.cs index d11843e8d..5b8a1a24a 100644 --- a/src/Microsoft.OpenApi.Readers/V2/OpenApiPathsDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V2/OpenApiPathsDeserializer.cs @@ -18,7 +18,7 @@ internal static partial class OpenApiV2Deserializer private static 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, n.CreateAny())} + {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, LoadExtension(p, n))} }; public static OpenApiPaths LoadPaths(ParseNode node) diff --git a/src/Microsoft.OpenApi.Readers/V2/OpenApiResponseDeserializer.cs b/src/Microsoft.OpenApi.Readers/V2/OpenApiResponseDeserializer.cs index 7fc5170ec..e6fd39f89 100644 --- a/src/Microsoft.OpenApi.Readers/V2/OpenApiResponseDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V2/OpenApiResponseDeserializer.cs @@ -45,7 +45,7 @@ internal static partial class OpenApiV2Deserializer private static readonly PatternFieldMap _responsePatternFields = new PatternFieldMap { - {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, n.CreateAny())} + {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, LoadExtension(p, n))} }; private static void ProcessProduces(OpenApiResponse response, ParsingContext context) @@ -53,15 +53,22 @@ private static void ProcessProduces(OpenApiResponse response, ParsingContext con var produces = context.GetFromTempStorage>(TempStorageKeys.OperationProduces) ?? context.GetFromTempStorage>(TempStorageKeys.GlobalProduces) ?? new List(); - response.Content = new Dictionary(); + if (response.Content == null) + { + response.Content = new Dictionary(); + } + foreach (var produce in produces) { - var mediaType = new OpenApiMediaType + if (!response.Content.ContainsKey(produce)) { - Schema = context.GetFromTempStorage(TempStorageKeys.ResponseSchema) - }; + var mediaType = new OpenApiMediaType + { + Schema = context.GetFromTempStorage(TempStorageKeys.ResponseSchema) + }; - response.Content.Add(produce, mediaType); + response.Content.Add(produce, mediaType); + } } } diff --git a/src/Microsoft.OpenApi.Readers/V2/OpenApiSchemaDeserializer.cs b/src/Microsoft.OpenApi.Readers/V2/OpenApiSchemaDeserializer.cs index 79f878463..4d08dd29d 100644 --- a/src/Microsoft.OpenApi.Readers/V2/OpenApiSchemaDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V2/OpenApiSchemaDeserializer.cs @@ -206,7 +206,7 @@ internal static partial class OpenApiV2Deserializer private static readonly PatternFieldMap _schemaPatternFields = new PatternFieldMap { - {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, n.CreateAny())} + {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, LoadExtension(p, n))} }; public static OpenApiSchema LoadSchema(ParseNode node) diff --git a/src/Microsoft.OpenApi.Readers/V2/OpenApiSecuritySchemeDeserializer.cs b/src/Microsoft.OpenApi.Readers/V2/OpenApiSecuritySchemeDeserializer.cs index 5b88f33a8..117d1f3c4 100644 --- a/src/Microsoft.OpenApi.Readers/V2/OpenApiSecuritySchemeDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V2/OpenApiSecuritySchemeDeserializer.cs @@ -77,7 +77,7 @@ internal static partial class OpenApiV2Deserializer private static readonly PatternFieldMap _securitySchemePatternFields = new PatternFieldMap { - {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, n.CreateAny())} + {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, LoadExtension(p, n))} }; public static OpenApiSecurityScheme LoadSecurityScheme(ParseNode node) diff --git a/src/Microsoft.OpenApi.Readers/V2/OpenApiTagDeserializer.cs b/src/Microsoft.OpenApi.Readers/V2/OpenApiTagDeserializer.cs index 636a06796..380999475 100644 --- a/src/Microsoft.OpenApi.Readers/V2/OpenApiTagDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V2/OpenApiTagDeserializer.cs @@ -37,7 +37,7 @@ internal static partial class OpenApiV2Deserializer private static readonly PatternFieldMap _tagPatternFields = new PatternFieldMap { - {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, n.CreateAny())} + {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, LoadExtension(p, n))} }; public static OpenApiTag LoadTag(ParseNode n) diff --git a/src/Microsoft.OpenApi.Readers/V2/OpenApiV2Deserializer.cs b/src/Microsoft.OpenApi.Readers/V2/OpenApiV2Deserializer.cs index 31d9f96ee..4ebf2f693 100644 --- a/src/Microsoft.OpenApi.Readers/V2/OpenApiV2Deserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V2/OpenApiV2Deserializer.cs @@ -3,6 +3,8 @@ using System.Collections.Generic; using System.Linq; +using Microsoft.OpenApi.Any; +using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Readers.ParseNodes; namespace Microsoft.OpenApi.Readers.V2 @@ -32,6 +34,22 @@ private static void ParseMap( } } + public static IOpenApiAny LoadAny(ParseNode node) + { + return node.CreateAny(); + } + + private static IOpenApiExtension LoadExtension(string name, ParseNode node) + { + if (node.Context.ExtensionParsers.TryGetValue(name, out var parser)) + { + return parser(node.CreateAny(), OpenApiSpecVersion.OpenApi2_0); + } + else + { + return node.CreateAny(); + } + } private static string LoadString(ParseNode node) { diff --git a/src/Microsoft.OpenApi.Readers/V2/OpenApiV2VersionService.cs b/src/Microsoft.OpenApi.Readers/V2/OpenApiV2VersionService.cs index 3d3acff5b..e9561b367 100644 --- a/src/Microsoft.OpenApi.Readers/V2/OpenApiV2VersionService.cs +++ b/src/Microsoft.OpenApi.Readers/V2/OpenApiV2VersionService.cs @@ -2,9 +2,12 @@ // Licensed under the MIT license. using System; +using System.Collections.Generic; +using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Exceptions; using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Readers.Exceptions; using Microsoft.OpenApi.Readers.Interface; using Microsoft.OpenApi.Readers.ParseNodes; using Microsoft.OpenApi.Readers.Properties; @@ -17,10 +20,25 @@ namespace Microsoft.OpenApi.Readers.V2 /// internal class OpenApiV2VersionService : IOpenApiVersionService { - /// - /// Return a function that converts a MapNode into a V2 OpenApiTag - /// - public Func TagLoader => OpenApiV2Deserializer.LoadTag; + private IDictionary> _loaders = new Dictionary> + { + [typeof(IOpenApiAny)] = OpenApiV2Deserializer.LoadAny, + [typeof(OpenApiExternalDocs)] = OpenApiV2Deserializer.LoadExternalDocs, + [typeof(OpenApiHeader)] = OpenApiV2Deserializer.LoadHeader, + [typeof(OpenApiInfo)] = OpenApiV2Deserializer.LoadInfo, + [typeof(OpenApiLicense)] = OpenApiV2Deserializer.LoadLicense, + [typeof(OpenApiOperation)] = OpenApiV2Deserializer.LoadOperation, + [typeof(OpenApiParameter)] = OpenApiV2Deserializer.LoadParameter, + [typeof(OpenApiPathItem)] = OpenApiV2Deserializer.LoadPathItem, + [typeof(OpenApiPaths)] = OpenApiV2Deserializer.LoadPaths, + [typeof(OpenApiResponse)] = OpenApiV2Deserializer.LoadResponse, + [typeof(OpenApiResponses)] = OpenApiV2Deserializer.LoadResponses, + [typeof(OpenApiSchema)] = OpenApiV2Deserializer.LoadSchema, + [typeof(OpenApiSecurityRequirement)] = OpenApiV2Deserializer.LoadSecurityRequirement, + [typeof(OpenApiSecurityScheme)] = OpenApiV2Deserializer.LoadSecurityScheme, + [typeof(OpenApiTag)] = OpenApiV2Deserializer.LoadTag, + [typeof(OpenApiXml)] = OpenApiV2Deserializer.LoadXml + }; private static OpenApiReference ParseLocalReference(string localReference) { @@ -73,7 +91,7 @@ private static ReferenceType ParseReferenceType(string referenceTypeName) return ReferenceType.SecurityScheme; default: - throw new ArgumentException(); + throw new OpenApiReaderException($"Unknown reference type '{referenceTypeName}'"); } } @@ -155,5 +173,10 @@ public OpenApiDocument LoadDocument(RootNode rootNode) { return OpenApiV2Deserializer.LoadOpenApi(rootNode); } + + public T LoadElement(ParseNode node) where T : IOpenApiElement + { + return (T)_loaders[typeof(T)](node); + } } } \ No newline at end of file diff --git a/src/Microsoft.OpenApi.Readers/V2/OpenApiXmlDeserializer.cs b/src/Microsoft.OpenApi.Readers/V2/OpenApiXmlDeserializer.cs index 75c7d4658..6e2273839 100644 --- a/src/Microsoft.OpenApi.Readers/V2/OpenApiXmlDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V2/OpenApiXmlDeserializer.cs @@ -2,8 +2,10 @@ // Licensed under the MIT license. using System; +using Microsoft.OpenApi.Exceptions; using Microsoft.OpenApi.Extensions; using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Readers.Exceptions; using Microsoft.OpenApi.Readers.ParseNodes; namespace Microsoft.OpenApi.Readers.V2 @@ -25,7 +27,14 @@ internal static partial class OpenApiV2Deserializer { "namespace", (o, n) => { - o.Namespace = new Uri(n.GetScalarValue(), UriKind.Absolute); + if (Uri.IsWellFormedUriString(n.GetScalarValue(), UriKind.Absolute)) + { + o.Namespace = new Uri(n.GetScalarValue(), UriKind.Absolute); + } + else + { + throw new OpenApiReaderException($"Xml Namespace requires absolute URL. '{n.GetScalarValue()}' is not valid."); + } } }, { @@ -51,7 +60,7 @@ internal static partial class OpenApiV2Deserializer private static readonly PatternFieldMap _xmlPatternFields = new PatternFieldMap { - {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, n.CreateAny())} + {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, LoadExtension(p,n))} }; public static OpenApiXml LoadXml(ParseNode node) diff --git a/src/Microsoft.OpenApi.Readers/V3/OpenApiCallbackDeserializer.cs b/src/Microsoft.OpenApi.Readers/V3/OpenApiCallbackDeserializer.cs index ddc9d3d78..697740b6f 100644 --- a/src/Microsoft.OpenApi.Readers/V3/OpenApiCallbackDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V3/OpenApiCallbackDeserializer.cs @@ -21,7 +21,7 @@ internal static partial class OpenApiV3Deserializer new PatternFieldMap { {s => s.StartsWith("$"), (o, p, n) => o.AddPathItem(RuntimeExpression.Build(p), LoadPathItem(n))}, - {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, n.CreateAny())}, + {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, LoadExtension(p,n))}, }; public static OpenApiCallback LoadCallback(ParseNode node) diff --git a/src/Microsoft.OpenApi.Readers/V3/OpenApiComponentsDeserializer.cs b/src/Microsoft.OpenApi.Readers/V3/OpenApiComponentsDeserializer.cs index 018618efd..febeda730 100644 --- a/src/Microsoft.OpenApi.Readers/V3/OpenApiComponentsDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V3/OpenApiComponentsDeserializer.cs @@ -34,7 +34,7 @@ internal static partial class OpenApiV3Deserializer private static PatternFieldMap _componentsPatternFields = new PatternFieldMap { - {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, n.CreateAny())} + {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, LoadExtension(p, n))} }; public static OpenApiComponents LoadComponents(ParseNode node) diff --git a/src/Microsoft.OpenApi.Readers/V3/OpenApiContactDeserializer.cs b/src/Microsoft.OpenApi.Readers/V3/OpenApiContactDeserializer.cs index 2d873461a..154ed23c0 100644 --- a/src/Microsoft.OpenApi.Readers/V3/OpenApiContactDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V3/OpenApiContactDeserializer.cs @@ -38,7 +38,7 @@ internal static partial class OpenApiV3Deserializer private static PatternFieldMap _contactPatternFields = new PatternFieldMap { - {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, n.CreateAny())} + {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, LoadExtension(p,n))} }; public static OpenApiContact LoadContact(ParseNode node) diff --git a/src/Microsoft.OpenApi.Readers/V3/OpenApiDocumentDeserializer.cs b/src/Microsoft.OpenApi.Readers/V3/OpenApiDocumentDeserializer.cs index 220164c13..2221584f1 100644 --- a/src/Microsoft.OpenApi.Readers/V3/OpenApiDocumentDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V3/OpenApiDocumentDeserializer.cs @@ -44,7 +44,7 @@ internal static partial class OpenApiV3Deserializer private static 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, n.CreateAny())} + {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, LoadExtension(p, n))} }; public static OpenApiDocument LoadOpenApi(RootNode rootNode) @@ -57,17 +57,5 @@ public static OpenApiDocument LoadOpenApi(RootNode rootNode) return openApidoc; } - - - public static IOpenApiExtension LoadExtension(string name, ParseNode node) - { - if (node.Context.ExtensionParsers.TryGetValue(name, out var parser)) { - return parser(node.CreateAny()); - } - else - { - return node.CreateAny(); - } - } } } \ No newline at end of file diff --git a/src/Microsoft.OpenApi.Readers/V3/OpenApiEncodingDeserializer.cs b/src/Microsoft.OpenApi.Readers/V3/OpenApiEncodingDeserializer.cs index 279b3c0d4..18f1154c3 100644 --- a/src/Microsoft.OpenApi.Readers/V3/OpenApiEncodingDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V3/OpenApiEncodingDeserializer.cs @@ -59,7 +59,7 @@ internal static partial class OpenApiV3Deserializer private static readonly PatternFieldMap _encodingPatternFields = new PatternFieldMap { - {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, n.CreateAny())} + {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, LoadExtension(p,n))} }; public static OpenApiEncoding LoadEncoding(ParseNode node) diff --git a/src/Microsoft.OpenApi.Readers/V3/OpenApiExampleDeserializer.cs b/src/Microsoft.OpenApi.Readers/V3/OpenApiExampleDeserializer.cs index e204f034d..36f591a9a 100644 --- a/src/Microsoft.OpenApi.Readers/V3/OpenApiExampleDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V3/OpenApiExampleDeserializer.cs @@ -45,7 +45,7 @@ internal static partial class OpenApiV3Deserializer private static readonly PatternFieldMap _examplePatternFields = new PatternFieldMap { - {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, n.CreateAny())} + {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, LoadExtension(p,n))} }; public static OpenApiExample LoadExample(ParseNode node) diff --git a/src/Microsoft.OpenApi.Readers/V3/OpenApiHeaderDeserializer.cs b/src/Microsoft.OpenApi.Readers/V3/OpenApiHeaderDeserializer.cs index 868330a8f..95ffb78b0 100644 --- a/src/Microsoft.OpenApi.Readers/V3/OpenApiHeaderDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V3/OpenApiHeaderDeserializer.cs @@ -55,7 +55,7 @@ internal static partial class OpenApiV3Deserializer private static readonly PatternFieldMap _headerPatternFields = new PatternFieldMap { - {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, n.CreateAny())} + {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, LoadExtension(p,n))} }; public static OpenApiHeader LoadHeader(ParseNode node) diff --git a/src/Microsoft.OpenApi.Readers/V3/OpenApiInfoDeserializer.cs b/src/Microsoft.OpenApi.Readers/V3/OpenApiInfoDeserializer.cs index 4a65f261d..17a97c117 100644 --- a/src/Microsoft.OpenApi.Readers/V3/OpenApiInfoDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V3/OpenApiInfoDeserializer.cs @@ -57,7 +57,7 @@ internal static partial class OpenApiV3Deserializer public static PatternFieldMap InfoPatternFields = new PatternFieldMap { - {s => s.StartsWith("x-"), (o, k, n) => o.Extensions.Add(k,LoadExtension(k, n))} + {s => s.StartsWith("x-"), (o, k, n) => o.AddExtension(k,LoadExtension(k, n))} }; public static OpenApiInfo LoadInfo(ParseNode node) diff --git a/src/Microsoft.OpenApi.Readers/V3/OpenApiLicenseDeserializer.cs b/src/Microsoft.OpenApi.Readers/V3/OpenApiLicenseDeserializer.cs index 35e11c243..6bd352f53 100644 --- a/src/Microsoft.OpenApi.Readers/V3/OpenApiLicenseDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V3/OpenApiLicenseDeserializer.cs @@ -32,7 +32,7 @@ internal static partial class OpenApiV3Deserializer private static PatternFieldMap _licensePatternFields = new PatternFieldMap { - {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, n.CreateAny())} + {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, LoadExtension(p,n))} }; internal static OpenApiLicense LoadLicense(ParseNode node) diff --git a/src/Microsoft.OpenApi.Readers/V3/OpenApiLinkDeserializer.cs b/src/Microsoft.OpenApi.Readers/V3/OpenApiLinkDeserializer.cs index c2a205089..06ec8de3e 100644 --- a/src/Microsoft.OpenApi.Readers/V3/OpenApiLinkDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V3/OpenApiLinkDeserializer.cs @@ -50,7 +50,7 @@ internal static partial class OpenApiV3Deserializer private static readonly PatternFieldMap _linkPatternFields = new PatternFieldMap { - {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, n.CreateAny())}, + {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, LoadExtension(p,n))}, }; public static OpenApiLink LoadLink(ParseNode node) diff --git a/src/Microsoft.OpenApi.Readers/V3/OpenApiMediaTypeDeserializer.cs b/src/Microsoft.OpenApi.Readers/V3/OpenApiMediaTypeDeserializer.cs index 983d67848..1cf671777 100644 --- a/src/Microsoft.OpenApi.Readers/V3/OpenApiMediaTypeDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V3/OpenApiMediaTypeDeserializer.cs @@ -18,35 +18,40 @@ internal static partial class OpenApiV3Deserializer new FixedFieldMap { { - "schema", (o, n) => + OpenApiConstants.Schema, (o, n) => { o.Schema = LoadSchema(n); } }, { - "examples", (o, n) => + OpenApiConstants.Examples, (o, n) => { o.Examples = n.CreateMap(LoadExample); } }, { - "example", (o, n) => + OpenApiConstants.Example, (o, n) => { o.Example = n.CreateAny(); } }, - //Encoding + { + 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, n.CreateAny())} + {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, LoadExtension(p,n))} }; public static OpenApiMediaType LoadMediaType(ParseNode node) { - var mapNode = node.CheckMapNode("content"); + var mapNode = node.CheckMapNode(OpenApiConstants.Content); if (!mapNode.Any()) { diff --git a/src/Microsoft.OpenApi.Readers/V3/OpenApiOAuthFlowDeserializer.cs b/src/Microsoft.OpenApi.Readers/V3/OpenApiOAuthFlowDeserializer.cs index 96cd9af8d..adc814f33 100644 --- a/src/Microsoft.OpenApi.Readers/V3/OpenApiOAuthFlowDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V3/OpenApiOAuthFlowDeserializer.cs @@ -41,7 +41,7 @@ internal static partial class OpenApiV3Deserializer private static readonly PatternFieldMap _oAuthFlowPatternFields = new PatternFieldMap { - {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, n.CreateAny())} + {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, LoadExtension(p,n))} }; public static OpenApiOAuthFlow LoadOAuthFlow(ParseNode node) diff --git a/src/Microsoft.OpenApi.Readers/V3/OpenApiOAuthFlowsDeserializer.cs b/src/Microsoft.OpenApi.Readers/V3/OpenApiOAuthFlowsDeserializer.cs index 5d88a9175..022ed35fd 100644 --- a/src/Microsoft.OpenApi.Readers/V3/OpenApiOAuthFlowsDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V3/OpenApiOAuthFlowsDeserializer.cs @@ -25,7 +25,7 @@ internal static partial class OpenApiV3Deserializer private static readonly PatternFieldMap _oAuthFlowsPatternFields = new PatternFieldMap { - {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, n.CreateAny())} + {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, LoadExtension(p,n))} }; public static OpenApiOAuthFlows LoadOAuthFlows(ParseNode node) diff --git a/src/Microsoft.OpenApi.Readers/V3/OpenApiOperationDeserializer.cs b/src/Microsoft.OpenApi.Readers/V3/OpenApiOperationDeserializer.cs index 579063278..3ab39c828 100644 --- a/src/Microsoft.OpenApi.Readers/V3/OpenApiOperationDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V3/OpenApiOperationDeserializer.cs @@ -95,7 +95,7 @@ internal static partial class OpenApiV3Deserializer private static readonly PatternFieldMap _operationPatternFields = new PatternFieldMap { - {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, n.CreateAny())}, + {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, LoadExtension(p,n))}, }; internal static OpenApiOperation LoadOperation(ParseNode node) diff --git a/src/Microsoft.OpenApi.Readers/V3/OpenApiParameterDeserializer.cs b/src/Microsoft.OpenApi.Readers/V3/OpenApiParameterDeserializer.cs index e713d00b7..89f9b4366 100644 --- a/src/Microsoft.OpenApi.Readers/V3/OpenApiParameterDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V3/OpenApiParameterDeserializer.cs @@ -101,7 +101,7 @@ internal static partial class OpenApiV3Deserializer private static readonly PatternFieldMap _parameterPatternFields = new PatternFieldMap { - {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, n.CreateAny())} + {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, LoadExtension(p,n))} }; public static OpenApiParameter LoadParameter(ParseNode node) diff --git a/src/Microsoft.OpenApi.Readers/V3/OpenApiPathItemDeserializer.cs b/src/Microsoft.OpenApi.Readers/V3/OpenApiPathItemDeserializer.cs index ca6c0d73e..ff08ce186 100644 --- a/src/Microsoft.OpenApi.Readers/V3/OpenApiPathItemDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V3/OpenApiPathItemDeserializer.cs @@ -43,7 +43,7 @@ internal static partial class OpenApiV3Deserializer private static readonly PatternFieldMap _pathItemPatternFields = new PatternFieldMap { - {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, n.CreateAny())} + {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, LoadExtension(p,n))} }; public static OpenApiPathItem LoadPathItem(ParseNode node) diff --git a/src/Microsoft.OpenApi.Readers/V3/OpenApiPathsDeserializer.cs b/src/Microsoft.OpenApi.Readers/V3/OpenApiPathsDeserializer.cs index 207c5228b..2020628d0 100644 --- a/src/Microsoft.OpenApi.Readers/V3/OpenApiPathsDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V3/OpenApiPathsDeserializer.cs @@ -18,7 +18,7 @@ internal static partial class OpenApiV3Deserializer private static 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, n.CreateAny())} + {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, LoadExtension(p,n))} }; public static OpenApiPaths LoadPaths(ParseNode node) diff --git a/src/Microsoft.OpenApi.Readers/V3/OpenApiRequestBodyDeserializer.cs b/src/Microsoft.OpenApi.Readers/V3/OpenApiRequestBodyDeserializer.cs index 15b53473e..efd54b101 100644 --- a/src/Microsoft.OpenApi.Readers/V3/OpenApiRequestBodyDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V3/OpenApiRequestBodyDeserializer.cs @@ -39,7 +39,7 @@ internal static partial class OpenApiV3Deserializer private static readonly PatternFieldMap _requestBodyPatternFields = new PatternFieldMap { - {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, n.CreateAny())} + {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, LoadExtension(p,n))} }; public static OpenApiRequestBody LoadRequestBody(ParseNode node) diff --git a/src/Microsoft.OpenApi.Readers/V3/OpenApiResponseDeserializer.cs b/src/Microsoft.OpenApi.Readers/V3/OpenApiResponseDeserializer.cs index e5c483a77..30ea4a52e 100644 --- a/src/Microsoft.OpenApi.Readers/V3/OpenApiResponseDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V3/OpenApiResponseDeserializer.cs @@ -45,7 +45,7 @@ internal static partial class OpenApiV3Deserializer private static readonly PatternFieldMap _responsePatternFields = new PatternFieldMap { - {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, n.CreateAny())} + {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, LoadExtension(p,n))} }; public static OpenApiResponse LoadResponse(ParseNode node) diff --git a/src/Microsoft.OpenApi.Readers/V3/OpenApiResponsesDeserializer.cs b/src/Microsoft.OpenApi.Readers/V3/OpenApiResponsesDeserializer.cs index 9ee33015e..580d3ff67 100644 --- a/src/Microsoft.OpenApi.Readers/V3/OpenApiResponsesDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V3/OpenApiResponsesDeserializer.cs @@ -18,7 +18,7 @@ internal static partial class OpenApiV3Deserializer public static 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, n.CreateAny())} + {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, LoadExtension(p,n))} }; public static OpenApiResponses LoadResponses(ParseNode node) diff --git a/src/Microsoft.OpenApi.Readers/V3/OpenApiSchemaDeserializer.cs b/src/Microsoft.OpenApi.Readers/V3/OpenApiSchemaDeserializer.cs index da67820a3..a86ebcb50 100644 --- a/src/Microsoft.OpenApi.Readers/V3/OpenApiSchemaDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V3/OpenApiSchemaDeserializer.cs @@ -239,7 +239,7 @@ internal static partial class OpenApiV3Deserializer private static readonly PatternFieldMap _schemaPatternFields = new PatternFieldMap { - {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, n.CreateAny())} + {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, LoadExtension(p,n))} }; public static OpenApiSchema LoadSchema(ParseNode node) diff --git a/src/Microsoft.OpenApi.Readers/V3/OpenApiSecuritySchemeDeserializer.cs b/src/Microsoft.OpenApi.Readers/V3/OpenApiSecuritySchemeDeserializer.cs index 6779750d1..8657faceb 100644 --- a/src/Microsoft.OpenApi.Readers/V3/OpenApiSecuritySchemeDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V3/OpenApiSecuritySchemeDeserializer.cs @@ -70,7 +70,7 @@ internal static partial class OpenApiV3Deserializer private static readonly PatternFieldMap _securitySchemePatternFields = new PatternFieldMap { - {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, n.CreateAny())} + {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, LoadExtension(p,n))} }; public static OpenApiSecurityScheme LoadSecurityScheme(ParseNode node) diff --git a/src/Microsoft.OpenApi.Readers/V3/OpenApiServerDeserializer.cs b/src/Microsoft.OpenApi.Readers/V3/OpenApiServerDeserializer.cs index a1cc85b36..39842c974 100644 --- a/src/Microsoft.OpenApi.Readers/V3/OpenApiServerDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V3/OpenApiServerDeserializer.cs @@ -37,7 +37,7 @@ internal static partial class OpenApiV3Deserializer private static readonly PatternFieldMap _serverPatternFields = new PatternFieldMap { - {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, n.CreateAny())} + {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, LoadExtension(p,n))} }; public static OpenApiServer LoadServer(ParseNode node) diff --git a/src/Microsoft.OpenApi.Readers/V3/OpenApiServerVariableDeserializer.cs b/src/Microsoft.OpenApi.Readers/V3/OpenApiServerVariableDeserializer.cs index d8fb35ef1..9fc955a78 100644 --- a/src/Microsoft.OpenApi.Readers/V3/OpenApiServerVariableDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V3/OpenApiServerVariableDeserializer.cs @@ -39,7 +39,7 @@ internal static partial class OpenApiV3Deserializer private static readonly PatternFieldMap _serverVariablePatternFields = new PatternFieldMap { - {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, n.CreateAny())} + {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, LoadExtension(p,n))} }; public static OpenApiServerVariable LoadServerVariable(ParseNode node) diff --git a/src/Microsoft.OpenApi.Readers/V3/OpenApiTagDeserializer.cs b/src/Microsoft.OpenApi.Readers/V3/OpenApiTagDeserializer.cs index 8e8c329e3..1f3a36afb 100644 --- a/src/Microsoft.OpenApi.Readers/V3/OpenApiTagDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V3/OpenApiTagDeserializer.cs @@ -37,7 +37,7 @@ internal static partial class OpenApiV3Deserializer private static readonly PatternFieldMap _tagPatternFields = new PatternFieldMap { - {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, n.CreateAny())} + {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, LoadExtension(p,n))} }; public static OpenApiTag LoadTag(ParseNode n) diff --git a/src/Microsoft.OpenApi.Readers/V3/OpenApiV3Deserializer.cs b/src/Microsoft.OpenApi.Readers/V3/OpenApiV3Deserializer.cs index cde82d53d..cdf175090 100644 --- a/src/Microsoft.OpenApi.Readers/V3/OpenApiV3Deserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V3/OpenApiV3Deserializer.cs @@ -3,7 +3,9 @@ using System.Collections.Generic; using System.Linq; +using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Expressions; +using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Readers.ParseNodes; @@ -57,7 +59,22 @@ private static RuntimeExpressionAnyWrapper LoadRuntimeExpressionAnyWrapper(Parse }; } + public static IOpenApiAny LoadAny(ParseNode node) + { + return node.CreateAny(); + } + private static IOpenApiExtension LoadExtension(string name, ParseNode node) + { + if (node.Context.ExtensionParsers.TryGetValue(name, out var parser)) + { + return parser(node.CreateAny(), OpenApiSpecVersion.OpenApi3_0); + } + else + { + return node.CreateAny(); + } + } private static string LoadString(ParseNode node) { diff --git a/src/Microsoft.OpenApi.Readers/V3/OpenApiV3VersionService.cs b/src/Microsoft.OpenApi.Readers/V3/OpenApiV3VersionService.cs index 905123961..891822a21 100644 --- a/src/Microsoft.OpenApi.Readers/V3/OpenApiV3VersionService.cs +++ b/src/Microsoft.OpenApi.Readers/V3/OpenApiV3VersionService.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Exceptions; using Microsoft.OpenApi.Extensions; using Microsoft.OpenApi.Interfaces; @@ -18,11 +19,36 @@ namespace Microsoft.OpenApi.Readers.V3 /// internal class OpenApiV3VersionService : IOpenApiVersionService { - /// - /// Return a function that converts a MapNode into a V3 OpenApiTag - /// - public Func TagLoader => OpenApiV3Deserializer.LoadTag; - + private IDictionary> _loaders = new Dictionary> { + [typeof(IOpenApiAny)] = OpenApiV3Deserializer.LoadAny, + [typeof(OpenApiCallback)] = OpenApiV3Deserializer.LoadCallback, + [typeof(OpenApiComponents)] = OpenApiV3Deserializer.LoadComponents, + [typeof(OpenApiEncoding)] = OpenApiV3Deserializer.LoadEncoding, + [typeof(OpenApiExample)] = OpenApiV3Deserializer.LoadExample, + [typeof(OpenApiExternalDocs)] = OpenApiV3Deserializer.LoadExternalDocs, + [typeof(OpenApiHeader)] = OpenApiV3Deserializer.LoadHeader, + [typeof(OpenApiInfo)] = OpenApiV3Deserializer.LoadInfo, + [typeof(OpenApiLicense)] = OpenApiV3Deserializer.LoadLicense, + [typeof(OpenApiLink)] = OpenApiV3Deserializer.LoadLink, + [typeof(OpenApiMediaType)] = OpenApiV3Deserializer.LoadMediaType, + [typeof(OpenApiOAuthFlow)] = OpenApiV3Deserializer.LoadOAuthFlow, + [typeof(OpenApiOAuthFlows)] = OpenApiV3Deserializer.LoadOAuthFlows, + [typeof(OpenApiOperation)] = OpenApiV3Deserializer.LoadOperation, + [typeof(OpenApiParameter)] = OpenApiV3Deserializer.LoadParameter, + [typeof(OpenApiPathItem)] = OpenApiV3Deserializer.LoadPathItem, + [typeof(OpenApiPaths)] = OpenApiV3Deserializer.LoadPaths, + [typeof(OpenApiRequestBody)] = OpenApiV3Deserializer.LoadRequestBody, + [typeof(OpenApiResponse)] = OpenApiV3Deserializer.LoadResponse, + [typeof(OpenApiResponses)] = OpenApiV3Deserializer.LoadResponses, + [typeof(OpenApiSchema)] = OpenApiV3Deserializer.LoadSchema, + [typeof(OpenApiSecurityRequirement)] = OpenApiV3Deserializer.LoadSecurityRequirement, + [typeof(OpenApiSecurityScheme)] = OpenApiV3Deserializer.LoadSecurityScheme, + [typeof(OpenApiServer)] = OpenApiV3Deserializer.LoadServer, + [typeof(OpenApiServerVariable)] = OpenApiV3Deserializer.LoadServerVariable, + [typeof(OpenApiTag)] = OpenApiV3Deserializer.LoadTag, + [typeof(OpenApiXml)] = OpenApiV3Deserializer.LoadXml + }; + /// /// Parse the string to a object. /// @@ -80,6 +106,11 @@ public OpenApiDocument LoadDocument(RootNode rootNode) return OpenApiV3Deserializer.LoadOpenApi(rootNode); } + public T LoadElement(ParseNode node) where T : IOpenApiElement + { + return (T)_loaders[typeof(T)](node); + } + private OpenApiReference ParseLocalReference(string localReference) { if (string.IsNullOrWhiteSpace(localReference)) diff --git a/src/Microsoft.OpenApi.Readers/V3/OpenApiXmlDeserializer.cs b/src/Microsoft.OpenApi.Readers/V3/OpenApiXmlDeserializer.cs index 0ce7ea2eb..0b6196c65 100644 --- a/src/Microsoft.OpenApi.Readers/V3/OpenApiXmlDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V3/OpenApiXmlDeserializer.cs @@ -51,7 +51,7 @@ internal static partial class OpenApiV3Deserializer private static readonly PatternFieldMap _xmlPatternFields = new PatternFieldMap { - {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, n.CreateAny())} + {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, LoadExtension(p,n))} }; public static OpenApiXml LoadXml(ParseNode node) diff --git a/src/Microsoft.OpenApi/Any/OpenApiArray.cs b/src/Microsoft.OpenApi/Any/OpenApiArray.cs index 73c7a721e..abd4ae099 100644 --- a/src/Microsoft.OpenApi/Any/OpenApiArray.cs +++ b/src/Microsoft.OpenApi/Any/OpenApiArray.cs @@ -20,7 +20,8 @@ public class OpenApiArray : List, IOpenApiAny /// Write out contents of OpenApiArray to passed writer /// /// Instance of JSON or YAML writer. - public void Write(IOpenApiWriter writer) + /// Version of the OpenAPI specification that that will be output. + public void Write(IOpenApiWriter writer, OpenApiSpecVersion specVersion) { writer.WriteStartArray(); diff --git a/src/Microsoft.OpenApi/Any/OpenApiNull.cs b/src/Microsoft.OpenApi/Any/OpenApiNull.cs index 5ff43acfa..de6d4ff79 100644 --- a/src/Microsoft.OpenApi/Any/OpenApiNull.cs +++ b/src/Microsoft.OpenApi/Any/OpenApiNull.cs @@ -19,7 +19,8 @@ public class OpenApiNull : IOpenApiAny /// Write out null representation /// /// - public void Write(IOpenApiWriter writer) + /// Version of the OpenAPI specification that that will be output. + public void Write(IOpenApiWriter writer, OpenApiSpecVersion specVersion) { writer.WriteAny(this); } diff --git a/src/Microsoft.OpenApi/Any/OpenApiObject.cs b/src/Microsoft.OpenApi/Any/OpenApiObject.cs index 0f1aee397..795f9dbe6 100644 --- a/src/Microsoft.OpenApi/Any/OpenApiObject.cs +++ b/src/Microsoft.OpenApi/Any/OpenApiObject.cs @@ -20,7 +20,8 @@ public class OpenApiObject : Dictionary, IOpenApiAny /// Serialize OpenApiObject to writer /// /// - public void Write(IOpenApiWriter writer) + /// Version of the OpenAPI specification that that will be output. + public void Write(IOpenApiWriter writer, OpenApiSpecVersion specVersion) { writer.WriteStartObject(); diff --git a/src/Microsoft.OpenApi/Any/OpenApiPrimitive.cs b/src/Microsoft.OpenApi/Any/OpenApiPrimitive.cs index dd6be1b95..60cbbb9e2 100644 --- a/src/Microsoft.OpenApi/Any/OpenApiPrimitive.cs +++ b/src/Microsoft.OpenApi/Any/OpenApiPrimitive.cs @@ -41,7 +41,8 @@ public OpenApiPrimitive(T value) /// Write out content of primitive element /// /// - public void Write(IOpenApiWriter writer) + /// + public void Write(IOpenApiWriter writer, OpenApiSpecVersion specVersion) { switch (this.PrimitiveType) { diff --git a/src/Microsoft.OpenApi/Expressions/CompositeExpression.cs b/src/Microsoft.OpenApi/Expressions/CompositeExpression.cs new file mode 100644 index 000000000..4a08761db --- /dev/null +++ b/src/Microsoft.OpenApi/Expressions/CompositeExpression.cs @@ -0,0 +1,48 @@ +// 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.Text.RegularExpressions; +using System.Threading.Tasks; + +namespace Microsoft.OpenApi.Expressions +{ + /// + /// String literal with embedded expressions + /// + public class CompositeExpression : RuntimeExpression + { + private readonly string template; + private Regex expressionPattern = new Regex("{(?[^}]+)"); + /// + /// Expressions embedded into string literal + /// + public List ContainedExpressions = new List(); + + /// + /// Create a composite expression from a string literal with an embedded expression + /// + /// + public CompositeExpression(string expression) + { + template = expression; + + // Extract subexpressions and convert to RuntimeExpressions + var matches = expressionPattern.Matches(expression); + + foreach (var item in matches.Cast()) + { + var value = item.Groups["exp"].Captures.Cast().First().Value; + ContainedExpressions.Add(RuntimeExpression.Build(value)); + } + } + + /// + /// Return original string literal with embedded expression + /// + public override string Expression => template; + } +} diff --git a/src/Microsoft.OpenApi/Expressions/RuntimeExpression.cs b/src/Microsoft.OpenApi/Expressions/RuntimeExpression.cs index a28b95fc5..e039ad478 100644 --- a/src/Microsoft.OpenApi/Expressions/RuntimeExpression.cs +++ b/src/Microsoft.OpenApi/Expressions/RuntimeExpression.cs @@ -34,6 +34,11 @@ public static RuntimeExpression Build(string expression) throw Error.ArgumentNullOrWhiteSpace(nameof(expression)); } + if (expression.Contains("{$")) + { + return new CompositeExpression(expression); + } + if (!expression.StartsWith(Prefix)) { throw new OpenApiException(string.Format(SRResource.RuntimeExpressionMustBeginWithDollar, expression)); diff --git a/src/Microsoft.OpenApi/Extensions/OpenApiExtensibleExtensions.cs b/src/Microsoft.OpenApi/Extensions/OpenApiExtensibleExtensions.cs index 6f39041a2..b41802794 100644 --- a/src/Microsoft.OpenApi/Extensions/OpenApiExtensibleExtensions.cs +++ b/src/Microsoft.OpenApi/Extensions/OpenApiExtensibleExtensions.cs @@ -21,7 +21,7 @@ public static class OpenApiExtensibleExtensions /// The extensible Open API element. /// The extension name. /// The extension value. - public static void AddExtension(this T element, string name, IOpenApiAny any) + public static void AddExtension(this T element, string name, IOpenApiExtension any) where T : IOpenApiExtensible { if (element == null) @@ -41,6 +41,5 @@ public static void AddExtension(this T element, string name, IOpenApiAny any) element.Extensions[name] = any ?? throw Error.ArgumentNull(nameof(any)); } - } } \ No newline at end of file diff --git a/src/Microsoft.OpenApi/Extensions/OpenApiSerializableExtensions.cs b/src/Microsoft.OpenApi/Extensions/OpenApiSerializableExtensions.cs index a072753a4..2cd1eb18e 100644 --- a/src/Microsoft.OpenApi/Extensions/OpenApiSerializableExtensions.cs +++ b/src/Microsoft.OpenApi/Extensions/OpenApiSerializableExtensions.cs @@ -83,7 +83,7 @@ public static void Serialize( /// the /// The Open API element. /// The output writer. - /// The Open API specification version. + /// Version of the specification the output should conform to public static void Serialize(this T element, IOpenApiWriter writer, OpenApiSpecVersion specVersion) where T : IOpenApiSerializable { diff --git a/src/Microsoft.OpenApi/Interfaces/IOpenApiExtension.cs b/src/Microsoft.OpenApi/Interfaces/IOpenApiExtension.cs index e9e92fb5d..a9ea04a39 100644 --- a/src/Microsoft.OpenApi/Interfaces/IOpenApiExtension.cs +++ b/src/Microsoft.OpenApi/Interfaces/IOpenApiExtension.cs @@ -14,6 +14,7 @@ public interface IOpenApiExtension /// Write out contents of custom extension /// /// - void Write(IOpenApiWriter writer); + /// Version of the OpenAPI specification that that will be output. + void Write(IOpenApiWriter writer, OpenApiSpecVersion specVersion); } } diff --git a/src/Microsoft.OpenApi/Microsoft.OpenApi.csproj b/src/Microsoft.OpenApi/Microsoft.OpenApi.csproj index 291eb72cc..390fb7a19 100644 --- a/src/Microsoft.OpenApi/Microsoft.OpenApi.csproj +++ b/src/Microsoft.OpenApi/Microsoft.OpenApi.csproj @@ -10,7 +10,7 @@ Microsoft Microsoft.OpenApi Microsoft.OpenApi - 1.0.1 + 1.1.0 .NET models with JSON and YAML writers for OpenAPI specification © Microsoft Corporation. All rights reserved. OpenAPI .NET diff --git a/src/Microsoft.OpenApi/Models/OpenApiCallback.cs b/src/Microsoft.OpenApi/Models/OpenApiCallback.cs index 221881798..fc6db76dc 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiCallback.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiCallback.cs @@ -94,7 +94,7 @@ public void SerializeAsV3WithoutReference(IOpenApiWriter writer) } // extensions - writer.WriteExtensions(Extensions); + writer.WriteExtensions(Extensions, OpenApiSpecVersion.OpenApi3_0); writer.WriteEndObject(); } diff --git a/src/Microsoft.OpenApi/Models/OpenApiComponents.cs b/src/Microsoft.OpenApi/Models/OpenApiComponents.cs index 5bc87c1dd..6adab4b13 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiComponents.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiComponents.cs @@ -244,7 +244,7 @@ public void SerializeAsV3(IOpenApiWriter writer) }); // extensions - writer.WriteExtensions(Extensions); + writer.WriteExtensions(Extensions, OpenApiSpecVersion.OpenApi3_0); writer.WriteEndObject(); } diff --git a/src/Microsoft.OpenApi/Models/OpenApiContact.cs b/src/Microsoft.OpenApi/Models/OpenApiContact.cs index 52f56dc29..ecba3d3c4 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiContact.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiContact.cs @@ -40,7 +40,7 @@ public class OpenApiContact : IOpenApiSerializable, IOpenApiExtensible /// public void SerializeAsV3(IOpenApiWriter writer) { - WriteInternal(writer); + WriteInternal(writer, OpenApiSpecVersion.OpenApi3_0); } /// @@ -48,10 +48,10 @@ public void SerializeAsV3(IOpenApiWriter writer) /// public void SerializeAsV2(IOpenApiWriter writer) { - WriteInternal(writer); + WriteInternal(writer, OpenApiSpecVersion.OpenApi2_0); } - private void WriteInternal(IOpenApiWriter writer) + private void WriteInternal(IOpenApiWriter writer, OpenApiSpecVersion specVersion) { if (writer == null) { @@ -70,7 +70,7 @@ private void WriteInternal(IOpenApiWriter writer) writer.WriteProperty(OpenApiConstants.Email, Email); // extensions - writer.WriteExtensions(Extensions); + writer.WriteExtensions(Extensions, specVersion); writer.WriteEndObject(); } diff --git a/src/Microsoft.OpenApi/Models/OpenApiDocument.cs b/src/Microsoft.OpenApi/Models/OpenApiDocument.cs index 60db434f5..78d532f22 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiDocument.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiDocument.cs @@ -29,7 +29,7 @@ public class OpenApiDocument : IOpenApiSerializable, IOpenApiExtensible /// /// REQUIRED. The available paths and operations for the API. /// - public OpenApiPaths Paths { get; set; } = new OpenApiPaths(); + public OpenApiPaths Paths { get; set; } /// /// An element to hold various schemas for the specification. @@ -97,7 +97,7 @@ public void SerializeAsV3(IOpenApiWriter writer) writer.WriteOptionalObject(OpenApiConstants.ExternalDocs, ExternalDocs, (w, e) => e.SerializeAsV3(w)); // extensions - writer.WriteExtensions(Extensions); + writer.WriteExtensions(Extensions, OpenApiSpecVersion.OpenApi3_0); writer.WriteEndObject(); } @@ -214,7 +214,7 @@ public void SerializeAsV2(IOpenApiWriter writer) writer.WriteOptionalObject(OpenApiConstants.ExternalDocs, ExternalDocs, (w, e) => e.SerializeAsV2(w)); // extensions - writer.WriteExtensions(Extensions); + writer.WriteExtensions(Extensions, OpenApiSpecVersion.OpenApi2_0); writer.WriteEndObject(); } @@ -240,7 +240,10 @@ private static void WriteHostInfoV2(IOpenApiWriter writer, IList firstServerUrl.GetComponents(UriComponents.Host | UriComponents.Port, UriFormat.SafeUnescaped)); // basePath - writer.WriteProperty(OpenApiConstants.BasePath, firstServerUrl.AbsolutePath); + if (firstServerUrl.AbsolutePath != "/") + { + writer.WriteProperty(OpenApiConstants.BasePath, firstServerUrl.AbsolutePath); + } // Consider all schemes of the URLs in the server list that have the same // host, port, and base path as the first server. @@ -302,6 +305,10 @@ public IOpenApiReferenceable ResolveReference(OpenApiReference reference) return null; } + if (this.Components == null) { + throw new OpenApiException(string.Format(Properties.SRResource.InvalidReferenceId, reference.Id)); + } + try { switch (reference.Type) diff --git a/src/Microsoft.OpenApi/Models/OpenApiEncoding.cs b/src/Microsoft.OpenApi/Models/OpenApiEncoding.cs index ccb36ef77..74fb943b6 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiEncoding.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiEncoding.cs @@ -81,7 +81,7 @@ public void SerializeAsV3(IOpenApiWriter writer) writer.WriteProperty(OpenApiConstants.AllowReserved, AllowReserved, false); // extensions - writer.WriteExtensions(Extensions); + writer.WriteExtensions(Extensions, OpenApiSpecVersion.OpenApi3_0); writer.WriteEndObject(); } diff --git a/src/Microsoft.OpenApi/Models/OpenApiError.cs b/src/Microsoft.OpenApi/Models/OpenApiError.cs index 2b6d85635..dbd34845c 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiError.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiError.cs @@ -41,7 +41,7 @@ public OpenApiError(string pointer, string message) /// public override string ToString() { - return Message + (!string.IsNullOrEmpty(Pointer) ? " at " + Pointer : ""); + return Message + (!string.IsNullOrEmpty(Pointer) ? " [" + Pointer + "]" : "" ); } } } \ No newline at end of file diff --git a/src/Microsoft.OpenApi/Models/OpenApiExample.cs b/src/Microsoft.OpenApi/Models/OpenApiExample.cs index ae6cfa000..0ebce156b 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiExample.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiExample.cs @@ -93,7 +93,7 @@ public void SerializeAsV3WithoutReference(IOpenApiWriter writer) writer.WriteProperty(OpenApiConstants.ExternalValue, ExternalValue); // extensions - writer.WriteExtensions(Extensions); + writer.WriteExtensions(Extensions, OpenApiSpecVersion.OpenApi3_0); writer.WriteEndObject(); } diff --git a/src/Microsoft.OpenApi/Models/OpenApiExtensibleDictionary.cs b/src/Microsoft.OpenApi/Models/OpenApiExtensibleDictionary.cs index 643c4bb14..d3fc5117c 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiExtensibleDictionary.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiExtensibleDictionary.cs @@ -39,7 +39,7 @@ public void SerializeAsV3(IOpenApiWriter writer) writer.WriteRequiredObject(item.Key, item.Value, (w, p) => p.SerializeAsV3(w)); } - writer.WriteExtensions(Extensions); + writer.WriteExtensions(Extensions, OpenApiSpecVersion.OpenApi3_0); writer.WriteEndObject(); } @@ -61,7 +61,7 @@ public void SerializeAsV2(IOpenApiWriter writer) writer.WriteRequiredObject(item.Key, item.Value, (w, p) => p.SerializeAsV2(w)); } - writer.WriteExtensions(Extensions); + writer.WriteExtensions(Extensions, OpenApiSpecVersion.OpenApi2_0); writer.WriteEndObject(); } diff --git a/src/Microsoft.OpenApi/Models/OpenApiExternalDocs.cs b/src/Microsoft.OpenApi/Models/OpenApiExternalDocs.cs index 58e283fbf..9485eea55 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiExternalDocs.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiExternalDocs.cs @@ -34,7 +34,7 @@ public class OpenApiExternalDocs : IOpenApiSerializable, IOpenApiExtensible /// public void SerializeAsV3(IOpenApiWriter writer) { - WriteInternal(writer); + WriteInternal(writer, OpenApiSpecVersion.OpenApi3_0); } /// @@ -42,10 +42,10 @@ public void SerializeAsV3(IOpenApiWriter writer) /// public void SerializeAsV2(IOpenApiWriter writer) { - WriteInternal(writer); + WriteInternal(writer, OpenApiSpecVersion.OpenApi2_0); } - private void WriteInternal(IOpenApiWriter writer) + private void WriteInternal(IOpenApiWriter writer, OpenApiSpecVersion specVersion) { if (writer == null) { @@ -61,7 +61,7 @@ private void WriteInternal(IOpenApiWriter writer) writer.WriteProperty(OpenApiConstants.Url, Url?.OriginalString); // extensions - writer.WriteExtensions(Extensions); + writer.WriteExtensions(Extensions, specVersion); writer.WriteEndObject(); } diff --git a/src/Microsoft.OpenApi/Models/OpenApiHeader.cs b/src/Microsoft.OpenApi/Models/OpenApiHeader.cs index c04135efa..c5fa288ae 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiHeader.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiHeader.cs @@ -146,7 +146,7 @@ public void SerializeAsV3WithoutReference(IOpenApiWriter writer) writer.WriteOptionalMap(OpenApiConstants.Content, Content, (w, c) => c.SerializeAsV3(w)); // extensions - writer.WriteExtensions(Extensions); + writer.WriteExtensions(Extensions, OpenApiSpecVersion.OpenApi3_0); writer.WriteEndObject(); } @@ -205,7 +205,7 @@ public void SerializeAsV2WithoutReference(IOpenApiWriter writer) writer.WriteOptionalObject(OpenApiConstants.Example, Example, (w, s) => w.WriteAny(s)); // extensions - writer.WriteExtensions(Extensions); + writer.WriteExtensions(Extensions, OpenApiSpecVersion.OpenApi2_0); writer.WriteEndObject(); } diff --git a/src/Microsoft.OpenApi/Models/OpenApiInfo.cs b/src/Microsoft.OpenApi/Models/OpenApiInfo.cs index 126b60d46..03020cf56 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiInfo.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiInfo.cs @@ -80,7 +80,7 @@ public void SerializeAsV3(IOpenApiWriter writer) writer.WriteProperty(OpenApiConstants.Version, Version); // specification extensions - writer.WriteExtensions(Extensions); + writer.WriteExtensions(Extensions, OpenApiSpecVersion.OpenApi3_0); writer.WriteEndObject(); } @@ -116,7 +116,7 @@ public void SerializeAsV2(IOpenApiWriter writer) writer.WriteProperty(OpenApiConstants.Version, Version); // specification extensions - writer.WriteExtensions(Extensions); + writer.WriteExtensions(Extensions, OpenApiSpecVersion.OpenApi2_0); writer.WriteEndObject(); } diff --git a/src/Microsoft.OpenApi/Models/OpenApiLicense.cs b/src/Microsoft.OpenApi/Models/OpenApiLicense.cs index 5c147f42e..dcd2223b5 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiLicense.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiLicense.cs @@ -34,7 +34,7 @@ public class OpenApiLicense : IOpenApiSerializable, IOpenApiExtensible /// public void SerializeAsV3(IOpenApiWriter writer) { - WriteInternal(writer); + WriteInternal(writer, OpenApiSpecVersion.OpenApi3_0); } /// @@ -42,10 +42,10 @@ public void SerializeAsV3(IOpenApiWriter writer) /// public void SerializeAsV2(IOpenApiWriter writer) { - WriteInternal(writer); + WriteInternal(writer, OpenApiSpecVersion.OpenApi2_0); } - private void WriteInternal(IOpenApiWriter writer) + private void WriteInternal(IOpenApiWriter writer, OpenApiSpecVersion specVersion) { if (writer == null) { @@ -61,7 +61,7 @@ private void WriteInternal(IOpenApiWriter writer) writer.WriteProperty(OpenApiConstants.Url, Url?.OriginalString); // specification extensions - writer.WriteExtensions(Extensions); + writer.WriteExtensions(Extensions, specVersion); writer.WriteEndObject(); } diff --git a/src/Microsoft.OpenApi/Models/OpenApiMediaType.cs b/src/Microsoft.OpenApi/Models/OpenApiMediaType.cs index 6f6e05106..93098f836 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiMediaType.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiMediaType.cs @@ -68,7 +68,7 @@ public void SerializeAsV3(IOpenApiWriter writer) writer.WriteOptionalMap(OpenApiConstants.Encoding, Encoding, (w, e) => e.SerializeAsV3(w)); // extensions - writer.WriteExtensions(Extensions); + writer.WriteExtensions(Extensions, OpenApiSpecVersion.OpenApi3_0); writer.WriteEndObject(); } diff --git a/src/Microsoft.OpenApi/Models/OpenApiOAuthFlow.cs b/src/Microsoft.OpenApi/Models/OpenApiOAuthFlow.cs index 0c4072330..ee16dc7ba 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiOAuthFlow.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiOAuthFlow.cs @@ -66,7 +66,7 @@ public void SerializeAsV3(IOpenApiWriter writer) writer.WriteRequiredMap(OpenApiConstants.Scopes, Scopes, (w, s) => w.WriteValue(s)); // extensions - writer.WriteExtensions(Extensions); + writer.WriteExtensions(Extensions, OpenApiSpecVersion.OpenApi3_0); writer.WriteEndObject(); } diff --git a/src/Microsoft.OpenApi/Models/OpenApiOAuthFlows.cs b/src/Microsoft.OpenApi/Models/OpenApiOAuthFlows.cs index 97cff9b34..91e17368b 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiOAuthFlows.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiOAuthFlows.cs @@ -69,7 +69,7 @@ public void SerializeAsV3(IOpenApiWriter writer) (w, o) => o.SerializeAsV3(w)); // extensions - writer.WriteExtensions(Extensions); + writer.WriteExtensions(Extensions, OpenApiSpecVersion.OpenApi3_0); writer.WriteEndObject(); } diff --git a/src/Microsoft.OpenApi/Models/OpenApiOperation.cs b/src/Microsoft.OpenApi/Models/OpenApiOperation.cs index 5d3a16e33..d90637945 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiOperation.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiOperation.cs @@ -161,7 +161,7 @@ public void SerializeAsV3(IOpenApiWriter writer) writer.WriteOptionalCollection(OpenApiConstants.Servers, Servers, (w, s) => s.SerializeAsV3(w)); // specification extensions - writer.WriteExtensions(Extensions); + writer.WriteExtensions(Extensions, OpenApiSpecVersion.OpenApi3_0); writer.WriteEndObject(); } @@ -247,7 +247,7 @@ public void SerializeAsV2(IOpenApiWriter writer) // V2 spec actually allows the body to have custom name. // Our library does not support this at the moment. Name = "body", - Schema = content?.Schema, + Schema = content?.Schema ?? new OpenApiSchema(), Required = RequestBody.Required }; @@ -309,7 +309,7 @@ public void SerializeAsV2(IOpenApiWriter writer) writer.WriteOptionalCollection(OpenApiConstants.Security, Security, (w, s) => s.SerializeAsV2(w)); // specification extensions - writer.WriteExtensions(Extensions); + writer.WriteExtensions(Extensions, OpenApiSpecVersion.OpenApi2_0); writer.WriteEndObject(); } diff --git a/src/Microsoft.OpenApi/Models/OpenApiParameter.cs b/src/Microsoft.OpenApi/Models/OpenApiParameter.cs index 31d4f13e8..b717f0ec5 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiParameter.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiParameter.cs @@ -195,7 +195,7 @@ public void SerializeAsV3WithoutReference(IOpenApiWriter writer) writer.WriteOptionalMap(OpenApiConstants.Content, Content, (w, c) => c.SerializeAsV3(w)); // extensions - writer.WriteExtensions(Extensions); + writer.WriteExtensions(Extensions, OpenApiSpecVersion.OpenApi3_0); writer.WriteEndObject(); } @@ -307,7 +307,7 @@ public void SerializeAsV2WithoutReference(IOpenApiWriter writer) // extensions - writer.WriteExtensions(Extensions); + writer.WriteExtensions(Extensions, OpenApiSpecVersion.OpenApi2_0); writer.WriteEndObject(); } diff --git a/src/Microsoft.OpenApi/Models/OpenApiPathItem.cs b/src/Microsoft.OpenApi/Models/OpenApiPathItem.cs index 4da8363d0..aecabdefb 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiPathItem.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiPathItem.cs @@ -90,7 +90,7 @@ public void SerializeAsV3(IOpenApiWriter writer) writer.WriteOptionalCollection(OpenApiConstants.Parameters, Parameters, (w, p) => p.SerializeAsV3(w)); // specification extensions - writer.WriteExtensions(Extensions); + writer.WriteExtensions(Extensions, OpenApiSpecVersion.OpenApi3_0); writer.WriteEndObject(); } @@ -131,7 +131,7 @@ public void SerializeAsV2(IOpenApiWriter writer) Description); // specification extensions - writer.WriteExtensions(Extensions); + writer.WriteExtensions(Extensions, OpenApiSpecVersion.OpenApi2_0); writer.WriteEndObject(); } diff --git a/src/Microsoft.OpenApi/Models/OpenApiRequestBody.cs b/src/Microsoft.OpenApi/Models/OpenApiRequestBody.cs index 80f9a0a7f..9b2842e8b 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiRequestBody.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiRequestBody.cs @@ -81,7 +81,7 @@ public void SerializeAsV3WithoutReference(IOpenApiWriter writer) writer.WriteProperty(OpenApiConstants.Required, Required, false); // extensions - writer.WriteExtensions(Extensions); + writer.WriteExtensions(Extensions, OpenApiSpecVersion.OpenApi3_0); writer.WriteEndObject(); } diff --git a/src/Microsoft.OpenApi/Models/OpenApiResponse.cs b/src/Microsoft.OpenApi/Models/OpenApiResponse.cs index 1b9064345..4a4c5491e 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiResponse.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiResponse.cs @@ -91,7 +91,7 @@ public void SerializeAsV3WithoutReference(IOpenApiWriter writer) writer.WriteOptionalMap(OpenApiConstants.Links, Links, (w, l) => l.SerializeAsV3(w)); // extension - writer.WriteExtensions(Extensions); + writer.WriteExtensions(Extensions, OpenApiSpecVersion.OpenApi3_0); writer.WriteEndObject(); } @@ -136,12 +136,20 @@ public void SerializeAsV2WithoutReference(IOpenApiWriter writer) (w, s) => s.SerializeAsV2(w)); // examples - if (mediatype.Value.Example != null) + if (Content.Values.Any(m => m.Example != null)) { writer.WritePropertyName(OpenApiConstants.Examples); writer.WriteStartObject(); - writer.WritePropertyName(mediatype.Key); - writer.WriteAny(mediatype.Value.Example); + + foreach (var mediaTypePair in Content) + { + if (mediaTypePair.Value.Example != null) + { + writer.WritePropertyName(mediaTypePair.Key); + writer.WriteAny(mediaTypePair.Value.Example); + } + } + writer.WriteEndObject(); } } @@ -151,7 +159,7 @@ public void SerializeAsV2WithoutReference(IOpenApiWriter writer) writer.WriteOptionalMap(OpenApiConstants.Headers, Headers, (w, h) => h.SerializeAsV2(w)); // extension - writer.WriteExtensions(Extensions); + writer.WriteExtensions(Extensions, OpenApiSpecVersion.OpenApi2_0); writer.WriteEndObject(); } diff --git a/src/Microsoft.OpenApi/Models/OpenApiSchema.cs b/src/Microsoft.OpenApi/Models/OpenApiSchema.cs index 45a7874df..008f4ae21 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiSchema.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiSchema.cs @@ -383,7 +383,7 @@ public void SerializeAsV3WithoutReference(IOpenApiWriter writer) writer.WriteProperty(OpenApiConstants.Deprecated, Deprecated, false); // extensions - writer.WriteExtensions(Extensions); + writer.WriteExtensions(Extensions, OpenApiSpecVersion.OpenApi3_0); writer.WriteEndObject(); } @@ -516,7 +516,7 @@ internal void WriteAsItemsProperties(IOpenApiWriter writer) writer.WriteProperty(OpenApiConstants.MultipleOf, MultipleOf); // extensions - writer.WriteExtensions(Extensions); + writer.WriteExtensions(Extensions, OpenApiSpecVersion.OpenApi2_0); } internal void WriteAsSchemaProperties( @@ -626,7 +626,7 @@ internal void WriteAsSchemaProperties( writer.WriteOptionalObject(OpenApiConstants.Example, Example, (w, e) => w.WriteAny(e)); // extensions - writer.WriteExtensions(Extensions); + writer.WriteExtensions(Extensions, OpenApiSpecVersion.OpenApi2_0); } } } \ No newline at end of file diff --git a/src/Microsoft.OpenApi/Models/OpenApiSecurityScheme.cs b/src/Microsoft.OpenApi/Models/OpenApiSecurityScheme.cs index 79d87d976..f77893592 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiSecurityScheme.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiSecurityScheme.cs @@ -134,7 +134,7 @@ public void SerializeAsV3WithoutReference(IOpenApiWriter writer) } // extensions - writer.WriteExtensions(Extensions); + writer.WriteExtensions(Extensions, OpenApiSpecVersion.OpenApi3_0); writer.WriteEndObject(); } @@ -212,7 +212,7 @@ public void SerializeAsV2WithoutReference(IOpenApiWriter writer) writer.WriteProperty(OpenApiConstants.Description, Description); // extensions - writer.WriteExtensions(Extensions); + writer.WriteExtensions(Extensions, OpenApiSpecVersion.OpenApi2_0); writer.WriteEndObject(); } diff --git a/src/Microsoft.OpenApi/Models/OpenApiServer.cs b/src/Microsoft.OpenApi/Models/OpenApiServer.cs index a29d17f06..72cf492d5 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiServer.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiServer.cs @@ -58,7 +58,7 @@ public void SerializeAsV3(IOpenApiWriter writer) writer.WriteOptionalMap(OpenApiConstants.Variables, Variables, (w, v) => v.SerializeAsV3(w)); // specification extensions - writer.WriteExtensions(Extensions); + writer.WriteExtensions(Extensions, OpenApiSpecVersion.OpenApi3_0); writer.WriteEndObject(); } diff --git a/src/Microsoft.OpenApi/Models/OpenApiServerVariable.cs b/src/Microsoft.OpenApi/Models/OpenApiServerVariable.cs index 695f09965..3a8c462c5 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiServerVariable.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiServerVariable.cs @@ -56,7 +56,7 @@ public void SerializeAsV3(IOpenApiWriter writer) writer.WriteOptionalCollection(OpenApiConstants.Enum, Enum, (w, s) => w.WriteValue(s)); // specification extensions - writer.WriteExtensions(Extensions); + writer.WriteExtensions(Extensions, OpenApiSpecVersion.OpenApi3_0); writer.WriteEndObject(); } diff --git a/src/Microsoft.OpenApi/Models/OpenApiTag.cs b/src/Microsoft.OpenApi/Models/OpenApiTag.cs index dc3462191..10e4bf0d1 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiTag.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiTag.cs @@ -79,7 +79,7 @@ public void SerializeAsV3WithoutReference(IOpenApiWriter writer) writer.WriteOptionalObject(OpenApiConstants.ExternalDocs, ExternalDocs, (w, e) => e.SerializeAsV3(w)); // extensions. - writer.WriteExtensions(Extensions); + writer.WriteExtensions(Extensions, OpenApiSpecVersion.OpenApi3_0); writer.WriteEndObject(); } @@ -120,7 +120,7 @@ public void SerializeAsV2WithoutReference(IOpenApiWriter writer) writer.WriteOptionalObject(OpenApiConstants.ExternalDocs, ExternalDocs, (w, e) => e.SerializeAsV2(w)); // extensions - writer.WriteExtensions(Extensions); + writer.WriteExtensions(Extensions, OpenApiSpecVersion.OpenApi2_0); writer.WriteEndObject(); } diff --git a/src/Microsoft.OpenApi/Models/OpenApiXml.cs b/src/Microsoft.OpenApi/Models/OpenApiXml.cs index 3d4ae7bf9..24d084eb9 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiXml.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiXml.cs @@ -51,7 +51,7 @@ public class OpenApiXml : IOpenApiSerializable, IOpenApiExtensible /// public void SerializeAsV3(IOpenApiWriter writer) { - Write(writer); + Write(writer, OpenApiSpecVersion.OpenApi3_0); } /// @@ -59,10 +59,10 @@ public void SerializeAsV3(IOpenApiWriter writer) /// public void SerializeAsV2(IOpenApiWriter writer) { - Write(writer); + Write(writer, OpenApiSpecVersion.OpenApi2_0); } - private void Write(IOpenApiWriter writer) + private void Write(IOpenApiWriter writer, OpenApiSpecVersion specVersion) { if (writer == null) { @@ -87,7 +87,7 @@ private void Write(IOpenApiWriter writer) writer.WriteProperty(OpenApiConstants.Wrapped, Wrapped, false); // extensions - writer.WriteExtensions(Extensions); + writer.WriteExtensions(Extensions, specVersion); writer.WriteEndObject(); } diff --git a/src/Microsoft.OpenApi/OpenApiSerializerSettings.cs b/src/Microsoft.OpenApi/OpenApiSerializerSettings.cs deleted file mode 100644 index 1357af105..000000000 --- a/src/Microsoft.OpenApi/OpenApiSerializerSettings.cs +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. - -namespace Microsoft.OpenApi -{ - /// - /// Configuration settings for Open API writers. - /// - public sealed class OpenApiSerializerSettings - { - /// - /// Open Api specification version - /// - public OpenApiSpecVersion SpecVersion { get; set; } = OpenApiSpecVersion.OpenApi3_0; - - /// - /// Open Api document format. - /// - public OpenApiFormat Format { get; set; } = OpenApiFormat.Json; - } -} \ No newline at end of file diff --git a/src/Microsoft.OpenApi/Services/OpenApiVisitorBase.cs b/src/Microsoft.OpenApi/Services/OpenApiVisitorBase.cs index 3bf99abab..9ed912e4a 100644 --- a/src/Microsoft.OpenApi/Services/OpenApiVisitorBase.cs +++ b/src/Microsoft.OpenApi/Services/OpenApiVisitorBase.cs @@ -15,7 +15,12 @@ namespace Microsoft.OpenApi.Services public abstract class OpenApiVisitorBase { private readonly Stack _path = new Stack(); - + + /// + /// Properties available to identify context of where an object is within OpenAPI Document + /// + public CurrentKeys CurrentKeys { get; } = new CurrentKeys(); + /// /// Allow Rule to indicate validation error occured at a deeper context level. /// @@ -44,8 +49,6 @@ public string PathString } } - - /// /// Visits /// diff --git a/src/Microsoft.OpenApi/Services/OpenApiWalker.cs b/src/Microsoft.OpenApi/Services/OpenApiWalker.cs index 0e120037d..565d7a34b 100644 --- a/src/Microsoft.OpenApi/Services/OpenApiWalker.cs +++ b/src/Microsoft.OpenApi/Services/OpenApiWalker.cs @@ -18,6 +18,7 @@ public class OpenApiWalker private readonly OpenApiVisitorBase _visitor; private readonly Stack _schemaLoop = new Stack(); private readonly Stack _pathItemLoop = new Stack(); + private bool _inComponents = false; /// @@ -53,6 +54,7 @@ public void Walk(OpenApiDocument doc) Walk(OpenApiConstants.ExternalDocs, () => Walk(doc.ExternalDocs)); Walk(OpenApiConstants.Tags, () => Walk(doc.Tags)); Walk(doc as IOpenApiExtensible); + } /// @@ -75,7 +77,6 @@ internal void Walk(IList tags) Walk(i.ToString(), () => Walk(tags[i])); } } - } /// @@ -219,7 +220,9 @@ internal void Walk(OpenApiPaths paths) { foreach (var pathItem in paths) { + _visitor.CurrentKeys.Path = pathItem.Key; Walk(pathItem.Key, () => Walk(pathItem.Value));// JSON Pointer uses ~1 as an escape character for / + _visitor.CurrentKeys.Path = null; } } } @@ -280,7 +283,9 @@ internal void Walk(IOpenApiExtensible openApiExtensible) { foreach (var item in openApiExtensible.Extensions) { + _visitor.CurrentKeys.Extension = item.Key; Walk(item.Key, () => Walk(item.Value)); + _visitor.CurrentKeys.Extension = null; } } } @@ -340,8 +345,10 @@ internal void Walk(OpenApiCallback callback) { foreach (var item in callback.PathItems) { + _visitor.CurrentKeys.Callback = item.Key.ToString(); var pathItem = item.Value; Walk(item.Key.ToString(), () => Walk(pathItem)); + _visitor.CurrentKeys.Callback = null; } } } @@ -392,7 +399,9 @@ internal void Walk(IDictionary serverVariables) { foreach (var variable in serverVariables) { + _visitor.CurrentKeys.ServerVariable = variable.Key; Walk(variable.Key, () => Walk(variable.Value)); + _visitor.CurrentKeys.ServerVariable = null; } } } @@ -457,7 +466,9 @@ internal void Walk(IDictionary operations) { foreach (var operation in operations) { + _visitor.CurrentKeys.Operation = operation.Key; Walk(operation.Key.GetDisplayName(), () => Walk(operation.Value)); + _visitor.CurrentKeys.Operation = null; } } } @@ -561,7 +572,9 @@ internal void Walk(OpenApiResponses responses) { foreach (var response in responses) { + _visitor.CurrentKeys.Response = response.Key; Walk(response.Key, () => Walk(response.Value)); + _visitor.CurrentKeys.Response = null; } } Walk(responses as IOpenApiExtensible); @@ -622,7 +635,9 @@ internal void Walk(IDictionary headers) { foreach (var header in headers) { + _visitor.CurrentKeys.Header = header.Key; Walk(header.Key, () => Walk(header.Value)); + _visitor.CurrentKeys.Header = null; } } } @@ -640,9 +655,11 @@ internal void Walk(IDictionary callbacks) _visitor.Visit(callbacks); if (callbacks != null) { - foreach (var header in callbacks) + foreach (var callback in callbacks) { - Walk(header.Key, () => Walk(header.Value)); + _visitor.CurrentKeys.Callback = callback.Key; + Walk(callback.Key, () => Walk(callback.Value)); + _visitor.CurrentKeys.Callback = null; } } } @@ -662,7 +679,9 @@ internal void Walk(IDictionary content) { foreach (var mediaType in content) { + _visitor.CurrentKeys.Content = mediaType.Key; Walk(mediaType.Key, () => Walk(mediaType.Value)); + _visitor.CurrentKeys.Content = null; } } } @@ -701,7 +720,9 @@ internal void Walk(IDictionary encodings) { foreach (var item in encodings) { + _visitor.CurrentKeys.Encoding = item.Key; Walk(item.Key, () => Walk(item.Value)); + _visitor.CurrentKeys.Encoding = null; } } } @@ -787,7 +808,9 @@ internal void Walk(IDictionary examples) { foreach (var example in examples) { + _visitor.CurrentKeys.Example = example.Key; Walk(example.Key, () => Walk(example.Value)); + _visitor.CurrentKeys.Example = null; } } } @@ -904,7 +927,9 @@ internal void Walk(IDictionary links) { foreach (var item in links) { + _visitor.CurrentKeys.Link = item.Key; Walk(item.Key, () => Walk(item.Value)); + _visitor.CurrentKeys.Link = null; } } } @@ -1045,4 +1070,65 @@ private void ExitComponents() _inComponents = false; } } + + /// + /// Object containing contextual information based on where the walker is currently referencing in an OpenApiDocument + /// + public class CurrentKeys + { + /// + /// Current Path key + /// + public string Path { get; set; } + + /// + /// Current Operation Type + /// + public OperationType? Operation { get; set; } + + /// + /// Current Response Status Code + /// + public string Response { get; set; } + + /// + /// Current Content Media Type + /// + public string Content { get; set; } + + /// + /// Current Callback Key + /// + public string Callback { get; set; } + + /// + /// Current Link Key + /// + public string Link { get; set; } + + /// + /// Current Header Key + /// + public string Header { get; internal set; } + + /// + /// Current Encoding Key + /// + public string Encoding { get; internal set; } + + /// + /// Current Example Key + /// + public string Example { get; internal set; } + + /// + /// Current Extension Key + /// + public string Extension { get; internal set; } + + /// + /// Current ServerVariable + /// + public string ServerVariable { get; internal set; } + } } \ No newline at end of file diff --git a/src/Microsoft.OpenApi/Writers/OpenApiJsonWriter.cs b/src/Microsoft.OpenApi/Writers/OpenApiJsonWriter.cs index 1a7276d39..19e5b16ab 100644 --- a/src/Microsoft.OpenApi/Writers/OpenApiJsonWriter.cs +++ b/src/Microsoft.OpenApi/Writers/OpenApiJsonWriter.cs @@ -14,18 +14,7 @@ public class OpenApiJsonWriter : OpenApiWriterBase /// Initializes a new instance of the class. /// /// The text writer. - public OpenApiJsonWriter(TextWriter textWriter) - : this(textWriter, new OpenApiSerializerSettings()) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The text writer. - /// The writer settings. - public OpenApiJsonWriter(TextWriter textWriter, OpenApiSerializerSettings settings) - : base(textWriter, settings) + public OpenApiJsonWriter(TextWriter textWriter) : base(textWriter) { } diff --git a/src/Microsoft.OpenApi/Writers/OpenApiWriterAnyExtensions.cs b/src/Microsoft.OpenApi/Writers/OpenApiWriterAnyExtensions.cs index f5551cea5..0b39abc51 100644 --- a/src/Microsoft.OpenApi/Writers/OpenApiWriterAnyExtensions.cs +++ b/src/Microsoft.OpenApi/Writers/OpenApiWriterAnyExtensions.cs @@ -19,7 +19,8 @@ public static class OpenApiWriterAnyExtensions /// /// The Open API writer. /// The specification extensions. - public static void WriteExtensions(this IOpenApiWriter writer, IDictionary extensions) + /// Version of the OpenAPI specification that that will be output. + public static void WriteExtensions(this IOpenApiWriter writer, IDictionary extensions, OpenApiSpecVersion specVersion) { if (writer == null) { @@ -31,7 +32,7 @@ public static void WriteExtensions(this IOpenApiWriter writer, IDictionary private int _indentLevel; - /// - /// Settings controlling the format and the version of the serialization. - /// - private OpenApiSerializerSettings _settings; - /// /// Initializes a new instance of the class. /// /// The text writer. - /// The writer settings. - public OpenApiWriterBase(TextWriter textWriter, OpenApiSerializerSettings settings) + public OpenApiWriterBase(TextWriter textWriter) { Writer = textWriter; Writer.NewLine = "\n"; Scopes = new Stack(); - this._settings = settings; } /// diff --git a/src/Microsoft.OpenApi/Writers/OpenApiYamlWriter.cs b/src/Microsoft.OpenApi/Writers/OpenApiYamlWriter.cs index d6a259514..d213e6154 100644 --- a/src/Microsoft.OpenApi/Writers/OpenApiYamlWriter.cs +++ b/src/Microsoft.OpenApi/Writers/OpenApiYamlWriter.cs @@ -14,20 +14,10 @@ public class OpenApiYamlWriter : OpenApiWriterBase /// Initializes a new instance of the class. /// /// The text writer. - public OpenApiYamlWriter(TextWriter textWriter) - : this(textWriter, new OpenApiSerializerSettings()) + public OpenApiYamlWriter(TextWriter textWriter) : base(textWriter) { } - /// - /// Initializes a new instance of the class. - /// - /// The text writer. - /// The writer settings. - public OpenApiYamlWriter(TextWriter textWriter, OpenApiSerializerSettings settings) - : base(textWriter, settings) - { - } /// /// Base Indentation Level. diff --git a/src/Microsoft.OpenApi/Writers/SpecialCharacterStringExtensions.cs b/src/Microsoft.OpenApi/Writers/SpecialCharacterStringExtensions.cs index c9fc3d144..d63b42e32 100644 --- a/src/Microsoft.OpenApi/Writers/SpecialCharacterStringExtensions.cs +++ b/src/Microsoft.OpenApi/Writers/SpecialCharacterStringExtensions.cs @@ -196,6 +196,11 @@ internal static string GetYamlCompatibleString(this string input) /// internal static string GetJsonCompatibleString(this string value) { + if (value == null) + { + return "null"; + } + // Show the control characters as strings // http://json.org/ diff --git a/test/Microsoft.OpenApi.Readers.Tests/ParseNodeTests.cs b/test/Microsoft.OpenApi.Readers.Tests/ParseNodeTests.cs new file mode 100644 index 000000000..a6a8e124c --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/ParseNodeTests.cs @@ -0,0 +1,35 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System.Collections.Generic; +using FluentAssertions; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Readers; +using Microsoft.OpenApi.Readers.Exceptions; +using Xunit; + +namespace Microsoft.OpenApi.Tests +{ + public class ParseNodeTests + { + [Fact] + public void BrokenSimpleList() + { + var input = @"swagger: 2.0 +info: + title: hey + version: 1.0.0 +schemes: [ { ""hello"" }] +paths: { }"; + + var reader = new OpenApiStringReader(); + reader.Read(input, out var diagnostic); + + diagnostic.Errors.ShouldBeEquivalentTo(new List() { + new OpenApiError(new OpenApiReaderException("Expected a value.") { + Pointer = "#line=4" + }) + }); + } + } +} diff --git a/test/Microsoft.OpenApi.Readers.Tests/TestCustomExtension.cs b/test/Microsoft.OpenApi.Readers.Tests/TestCustomExtension.cs index a7eddb672..e3dbfa19b 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/TestCustomExtension.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/TestCustomExtension.cs @@ -26,7 +26,7 @@ public void ParseCustomExtension() "; var settings = new OpenApiReaderSettings() { - ExtensionParsers = { { "x-foo", (a) => { + ExtensionParsers = { { "x-foo", (a,v) => { var fooNode = (OpenApiObject)a; return new FooExtension() { Bar = (fooNode["bar"] as OpenApiString)?.Value, @@ -54,7 +54,7 @@ internal class FooExtension : IOpenApiExtension, IOpenApiElement public string Bar { get; set; } - public void Write(IOpenApiWriter writer) + public void Write(IOpenApiWriter writer, OpenApiSpecVersion specVersion) { writer.WriteStartObject(); writer.WriteProperty("baz", Baz); diff --git a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiDocumentTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiDocumentTests.cs new file mode 100644 index 000000000..6850628bd --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiDocumentTests.cs @@ -0,0 +1,70 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System.Collections.Generic; +using FluentAssertions; +using Microsoft.OpenApi.Exceptions; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Readers.Exceptions; +using Xunit; + +namespace Microsoft.OpenApi.Readers.Tests.V2Tests +{ + public class OpenApiDocumentTests + { + [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.ShouldBeEquivalentTo(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.ShouldBeEquivalentTo(new List { + new OpenApiError( new OpenApiException("Invalid Reference identifier 'doesnotexist'.")) }); + doc.Should().NotBeNull(); + } + } +} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiServerTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiServerTests.cs new file mode 100644 index 000000000..141f30053 --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiServerTests.cs @@ -0,0 +1,261 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Xunit; + +namespace Microsoft.OpenApi.Readers.Tests.V2Tests +{ + public class OpenApiServerTests + { + [Fact] + public void NoServer() + { + var input = @" +swagger: 2.0 +info: + title: test + version: 1.0.0 +paths: {} +"; + var reader = new OpenApiStringReader(new OpenApiReaderSettings() { + }); + + var doc = reader.Read(input, out var diagnostic); + + Assert.Empty(doc.Servers); + } + + [Fact] + public void JustSchemeNoDefault() + { + var input = @" +swagger: 2.0 +info: + title: test + version: 1.0.0 +schemes: + - http +paths: {} +"; + var reader = new OpenApiStringReader(new OpenApiReaderSettings() + { + }); + + var doc = reader.Read(input, out var diagnostic); + + Assert.Equal(0, doc.Servers.Count); + } + + [Fact] + public void JustHostNoDefault() + { + var input = @" +swagger: 2.0 +info: + title: test + version: 1.0.0 +host: www.foo.com +paths: {} +"; + var reader = new OpenApiStringReader(new OpenApiReaderSettings() + { + }); + + var doc = reader.Read(input, out var diagnostic); + + var server = doc.Servers.First(); + Assert.Equal(1, doc.Servers.Count); + Assert.Equal("//www.foo.com", server.Url); + } + + [Fact] + public void JustBasePathNoDefault() + { + var input = @" +swagger: 2.0 +info: + title: test + version: 1.0.0 +basePath: /baz +paths: {} +"; + var reader = new OpenApiStringReader(new OpenApiReaderSettings() + { + }); + + var doc = reader.Read(input, out var diagnostic); + + var server = doc.Servers.First(); + Assert.Equal(1, doc.Servers.Count); + Assert.Equal("/baz", server.Url); + } + + [Fact] + public void JustSchemeWithCustomHost() + { + var input = @" +swagger: 2.0 +info: + title: test + version: 1.0.0 +schemes: + - http +paths: {} +"; + var reader = new OpenApiStringReader(new OpenApiReaderSettings() + { + BaseUrl = new Uri("https://bing.com/foo") + }); + + var doc = reader.Read(input, out var diagnostic); + + var server = doc.Servers.First(); + Assert.Equal(1, doc.Servers.Count); + Assert.Equal("http://bing.com/foo", server.Url); + } + + [Fact] + public void JustSchemeWithCustomHostWithEmptyPath() + { + var input = @" +swagger: 2.0 +info: + title: test + version: 1.0.0 +schemes: + - http +paths: {} +"; + var reader = new OpenApiStringReader(new OpenApiReaderSettings() + { + BaseUrl = new Uri("https://bing.com") + }); + + var doc = reader.Read(input, out var diagnostic); + + var server = doc.Servers.First(); + Assert.Equal(1, doc.Servers.Count); + Assert.Equal("http://bing.com", server.Url); + } + + [Fact] + public void JustBasePathWithCustomHost() + { + var input = @" +swagger: 2.0 +info: + title: test + version: 1.0.0 +basePath: /api +paths: {} +"; + var reader = new OpenApiStringReader(new OpenApiReaderSettings() + { + BaseUrl = new Uri("https://bing.com") + }); + + var doc = reader.Read(input, out var diagnostic); + + var server = doc.Servers.First(); + Assert.Equal(1, doc.Servers.Count); + Assert.Equal("https://bing.com/api", server.Url); + } + + [Fact] + public void JustHostWithCustomHost() + { + var input = @" +swagger: 2.0 +info: + title: test + version: 1.0.0 +host: www.example.com +paths: {} +"; + var reader = new OpenApiStringReader(new OpenApiReaderSettings() + { + BaseUrl = new Uri("https://bing.com") + }); + + var doc = reader.Read(input, out var diagnostic); + + var server = doc.Servers.First(); + Assert.Equal(1, doc.Servers.Count); + Assert.Equal("https://www.example.com", server.Url); + } + + [Fact] + public void JustHostWithCustomHostWithApi() + { + var input = @" +swagger: 2.0 +info: + title: test + version: 1.0.0 +host: prod.bing.com +paths: {} +"; + var reader = new OpenApiStringReader(new OpenApiReaderSettings() + { + BaseUrl = new Uri("https://dev.bing.com/api") + }); + + var doc = reader.Read(input, out var diagnostic); + + var server = doc.Servers.First(); + Assert.Equal(1, doc.Servers.Count); + Assert.Equal("https://prod.bing.com/api", server.Url); + } + + [Fact] + public void MultipleServers() + { + var input = @" +swagger: 2.0 +info: + title: test + version: 1.0.0 +schemes: + - http + - https +paths: {} +"; + var reader = new OpenApiStringReader(new OpenApiReaderSettings() + { + BaseUrl = new Uri("https://dev.bing.com/api") + }); + + var doc = reader.Read(input, out var diagnostic); + + var server = doc.Servers.First(); + Assert.Equal(2, doc.Servers.Count); + Assert.Equal("http://dev.bing.com/api", server.Url); + Assert.Equal("https://dev.bing.com/api", doc.Servers.Last().Url); + } + + [Fact] + public void LocalHostWithCustomHost() + { + var input = @" +swagger: 2.0 +info: + title: test + version: 1.0.0 +host: localhost:23232 +paths: {} +"; + var reader = new OpenApiStringReader(new OpenApiReaderSettings() + { + BaseUrl = new Uri("https://bing.com") + }); + + var doc = reader.Read(input, out var diagnostic); + + var server = doc.Servers.First(); + Assert.Equal(1, doc.Servers.Count); + Assert.Equal("https://localhost:23232", server.Url); + } + } +} diff --git a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiDocumentTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiDocumentTests.cs index b8ed90a87..8117e1c57 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiDocumentTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiDocumentTests.cs @@ -46,7 +46,8 @@ public void ParseDocumentFromInlineStringShouldSucceed() { Title = "Simple Document", Version = "0.9.1" - } + }, + Paths = new OpenApiPaths() }); context.ShouldBeEquivalentTo( @@ -83,7 +84,8 @@ public void ParseBasicDocumentWithMultipleServersShouldSucceed() Url = new Uri("https://www.example.org/api").ToString(), Description = "The https endpoint" } - } + }, + Paths = new OpenApiPaths() }); } } @@ -101,7 +103,8 @@ public void ParseBrokenMinimalDocumentShouldYieldExpectedDiagnostic() Info = new OpenApiInfo { Version = "0.9" - } + }, + Paths = new OpenApiPaths() }); diagnostic.ShouldBeEquivalentTo( @@ -130,7 +133,8 @@ public void ParseMinimalDocumentShouldSucceed() { Title = "Simple Document", Version = "0.9.1" - } + }, + Paths = new OpenApiPaths() }); diagnostic.ShouldBeEquivalentTo( diff --git a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiSchemaTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiSchemaTests.cs index 6dffd33f6..7fdc2145d 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiSchemaTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiSchemaTests.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. +using System.Collections.Generic; using System.IO; using System.Linq; using FluentAssertions; @@ -47,6 +48,109 @@ public void ParsePrimitiveSchemaShouldSucceed() } } + [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.ShouldBeEquivalentTo(new OpenApiDiagnostic()); + + schema.ShouldBeEquivalentTo( + 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.ShouldBeEquivalentTo(new OpenApiDiagnostic()); + + schema.ShouldBeEquivalentTo( + new OpenApiSchema + { + Type = "integer", + Format = "int64", + Default = new OpenApiInteger(88) + }); + } + + [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.ShouldBeEquivalentTo(new OpenApiDiagnostic()); + + openApiAny.ShouldBeEquivalentTo( + new OpenApiObject + { + ["foo"] = new OpenApiString("bar"), + ["baz"] = new OpenApiArray() { + new OpenApiInteger(1), + new OpenApiInteger(2) + } + }); + } + + [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.ShouldBeEquivalentTo(new OpenApiDiagnostic()); + + openApiAny.ShouldBeEquivalentTo( + new OpenApiArray + { + new OpenApiString("foo"), + new OpenApiString("baz") + }); + } + [Fact] public void ParseSimpleSchemaShouldSucceed() { @@ -97,6 +201,44 @@ public void ParseSimpleSchemaShouldSucceed() } } + [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.ShouldBeEquivalentTo(new OpenApiDiagnostic()); + + openApiAny.ShouldBeEquivalentTo( + 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() { diff --git a/test/Microsoft.OpenApi.SmokeTests/ApiGurus.cs b/test/Microsoft.OpenApi.SmokeTests/ApiGurus.cs index 1dbb24f16..7c8418115 100644 --- a/test/Microsoft.OpenApi.SmokeTests/ApiGurus.cs +++ b/test/Microsoft.OpenApi.SmokeTests/ApiGurus.cs @@ -27,6 +27,7 @@ public ApisGuruTests(ITestOutputHelper output) static ApisGuruTests() { + System.Net.ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12; _httpClient = new HttpClient(new HttpClientHandler() { AutomaticDecompression = DecompressionMethods.GZip @@ -69,7 +70,7 @@ JToken GetProp(JToken obj, string prop) } } - [Theory(DisplayName = "APIs.guru")] + // Disable as some APIs are currently invalid [Theory(DisplayName = "APIs.guru")] [MemberData(nameof(GetSchemas))] public async Task EnsureThatICouldParse(string url) { diff --git a/test/Microsoft.OpenApi.Tests/Expressions/RuntimeExpressionTests.cs b/test/Microsoft.OpenApi.Tests/Expressions/RuntimeExpressionTests.cs index c583213bc..d5ac2fb96 100644 --- a/test/Microsoft.OpenApi.Tests/Expressions/RuntimeExpressionTests.cs +++ b/test/Microsoft.OpenApi.Tests/Expressions/RuntimeExpressionTests.cs @@ -5,6 +5,7 @@ using Microsoft.OpenApi.Expressions; using Microsoft.OpenApi.Properties; using System; +using System.Linq; using Xunit; namespace Microsoft.OpenApi.Tests.Writers @@ -171,5 +172,83 @@ public void CompareRuntimeExpressionWorks(string expression) Assert.NotSame(runtimeExpression1, runtimeExpression2); Assert.Equal(runtimeExpression1, runtimeExpression2); } + + + [Fact] + public void CompositeRuntimeExpressionContainsExpression() + { + // Arrange + string expression = "This is a composite expression {$url} yay"; + + // Act + var runtimeExpression = RuntimeExpression.Build(expression); + + // Assert + Assert.NotNull(runtimeExpression); + var response = Assert.IsType(runtimeExpression); + Assert.Equal(expression, response.Expression); + + var compositeExpression = runtimeExpression as CompositeExpression; + Assert.Single(compositeExpression.ContainedExpressions); + + } + + [Fact] + public void CompositeRuntimeExpressionContainsMultipleExpressions() + { + // Arrange + string expression = "This is a composite expression {$url} yay and {$request.header.foo}"; + + // Act + var runtimeExpression = RuntimeExpression.Build(expression); + + // Assert + Assert.NotNull(runtimeExpression); + var response = Assert.IsType(runtimeExpression); + Assert.Equal(expression, response.Expression); + + var compositeExpression = runtimeExpression as CompositeExpression; + Assert.Equal(2,compositeExpression.ContainedExpressions.Count); + + Assert.IsType(compositeExpression.ContainedExpressions.First()); + Assert.IsType(compositeExpression.ContainedExpressions.Last()); + } + + + + [Fact] + public void CompositeRuntimeExpressionForWebHook() + { + // Arrange + string expression = "http://notificationServer.com?transactionId={$request.body#/id}&email={$request.body#/email}"; + + // Act + var runtimeExpression = RuntimeExpression.Build(expression); + + // Assert + Assert.NotNull(runtimeExpression); + var response = Assert.IsType(runtimeExpression); + Assert.Equal(expression, response.Expression); + + var compositeExpression = runtimeExpression as CompositeExpression; + Assert.Equal(2, compositeExpression.ContainedExpressions.Count); + + Assert.IsType(compositeExpression.ContainedExpressions.First()); + Assert.IsType(compositeExpression.ContainedExpressions.Last()); + } + + [Theory] + [InlineData("This is a composite expression yay and {} and {$sddsd}")] + [InlineData("This is a composite expression {url} yay and {} and {$url}")] + public void CompositeRuntimeExpressionContainsInvalidExpressions(string expression) + { + // Arrange + + // Act + Action test = () => RuntimeExpression.Build(expression); + + // Assert + Assert.Throws(test); + } } } diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.cs index 6d24d71a8..ec7e24063 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.cs @@ -2496,5 +2496,60 @@ public void SerializeSimpleDocumentWithTopLevelSelfReferencingWithOtherPropertie expected = expected.MakeLineBreaksEnvironmentNeutral(); actual.Should().Be(expected); } + + [Fact] + public void SerializeDocumentWithReferenceButNoComponents() + { + // Arrange + var document = new OpenApiDocument() + { + Info = new OpenApiInfo + { + Title = "Test", + Version = "1.0.0" + }, + Paths = new OpenApiPaths + { + ["/"] = new OpenApiPathItem + { + Operations = new Dictionary + { + [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 + } + } + } + } + } + } + } + } + } + } + }; + + + var reference = document.Paths["/"].Operations[OperationType.Get].Responses["200"].Content["application/json"].Schema.Reference; + + // Act + var actual = document.Serialize(OpenApiSpecVersion.OpenApi2_0, OpenApiFormat.Json); + + // Assert + Assert.NotEmpty(actual); + } } } \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Services/OpenApiValidatorTests.cs b/test/Microsoft.OpenApi.Tests/Services/OpenApiValidatorTests.cs index 8cec27ad4..fba3965cf 100644 --- a/test/Microsoft.OpenApi.Tests/Services/OpenApiValidatorTests.cs +++ b/test/Microsoft.OpenApi.Tests/Services/OpenApiValidatorTests.cs @@ -35,6 +35,7 @@ public void ResponseMustHaveADescription() Title = "foo", Version = "1.2.2" }; + openApiDocument.Paths = new OpenApiPaths(); openApiDocument.Paths.Add( "/test", new OpenApiPathItem @@ -66,13 +67,14 @@ public void ResponseMustHaveADescription() [Fact] public void ServersShouldBeReferencedByIndex() { - var openApiDocument = new OpenApiDocument(); - openApiDocument.Info = new OpenApiInfo() + var openApiDocument = new OpenApiDocument { - Title = "foo", - Version = "1.2.2" - }; - openApiDocument.Servers = new List { + Info = new OpenApiInfo() + { + Title = "foo", + Version = "1.2.2" + }, + Servers = new List { new OpenApiServer { Url = "http://example.org" @@ -80,9 +82,11 @@ public void ServersShouldBeReferencedByIndex() new OpenApiServer { - } + }, + }, + Paths = new OpenApiPaths() }; - + var validator = new OpenApiValidator(ValidationRuleSet.GetDefaultRuleSet()); var walker = new OpenApiWalker(validator); walker.Walk(openApiDocument); @@ -111,11 +115,14 @@ public void ValidateCustomExtension() } })); - var openApiDocument = new OpenApiDocument(); - openApiDocument.Info = new OpenApiInfo() + var openApiDocument = new OpenApiDocument { - Title = "foo", - Version = "1.2.2" + Info = new OpenApiInfo() + { + Title = "foo", + Version = "1.2.2" + }, + Paths = new OpenApiPaths() }; var fooExtension = new FooExtension() @@ -145,7 +152,7 @@ internal class FooExtension : IOpenApiExtension, IOpenApiElement public string Bar { get; set; } - public void Write(IOpenApiWriter writer) + public void Write(IOpenApiWriter writer, OpenApiSpecVersion specVersion) { writer.WriteStartObject(); writer.WriteProperty("baz", Baz); diff --git a/test/Microsoft.OpenApi.Tests/Walkers/WalkerLocationTests.cs b/test/Microsoft.OpenApi.Tests/Walkers/WalkerLocationTests.cs index 933a25274..9606f0029 100644 --- a/test/Microsoft.OpenApi.Tests/Walkers/WalkerLocationTests.cs +++ b/test/Microsoft.OpenApi.Tests/Walkers/WalkerLocationTests.cs @@ -23,7 +23,6 @@ public void LocateTopLevelObjects() locator.Locations.ShouldBeEquivalentTo(new List { "#/servers", - "#/paths", "#/tags" }); } @@ -37,6 +36,7 @@ public void LocateTopLevelArrayItems() new OpenApiServer(), new OpenApiServer() }, + Paths = new OpenApiPaths(), Tags = new List() { new OpenApiTag() @@ -61,6 +61,7 @@ public void LocateTopLevelArrayItems() public void LocatePathOperationContentSchema() { var doc = new OpenApiDocument(); + doc.Paths = new OpenApiPaths(); doc.Paths.Add("/test", new OpenApiPathItem() { Operations = new Dictionary() @@ -106,6 +107,8 @@ public void LocatePathOperationContentSchema() "#/paths/~1test/get/responses/200/content/application~1json/schema", }); + + locator.Keys.ShouldAllBeEquivalentTo(new List { "/test","Get","200", "application/json" }); } [Fact] @@ -124,6 +127,7 @@ public void WalkDOMWithCycles() var doc = new OpenApiDocument() { + Paths = new OpenApiPaths(), Components = new OpenApiComponents() { Schemas = new Dictionary @@ -151,6 +155,8 @@ public void WalkDOMWithCycles() internal class LocatorVisitor : OpenApiVisitorBase { public List Locations = new List(); + public List Keys = new List(); + public override void Visit(OpenApiInfo info) { Locations.Add(this.PathString); @@ -173,6 +179,7 @@ public override void Visit(OpenApiPaths paths) public override void Visit(OpenApiPathItem pathItem) { + Keys.Add(CurrentKeys.Path); Locations.Add(this.PathString); } @@ -183,10 +190,12 @@ public override void Visit(OpenApiResponses responses) public override void Visit(OpenApiOperation operation) { + Keys.Add(CurrentKeys.Operation.ToString()); Locations.Add(this.PathString); } public override void Visit(OpenApiResponse response) { + Keys.Add(CurrentKeys.Response); Locations.Add(this.PathString); } @@ -197,6 +206,7 @@ public override void Visit(IDictionary content) public override void Visit(OpenApiMediaType mediaType) { + Keys.Add(CurrentKeys.Content); Locations.Add(this.PathString); }