diff --git a/src/Microsoft.OpenApi.Readers/Interface/ILog.cs b/src/Microsoft.OpenApi.Readers/Interface/IDiagnostic.cs similarity index 57% rename from src/Microsoft.OpenApi.Readers/Interface/ILog.cs rename to src/Microsoft.OpenApi.Readers/Interface/IDiagnostic.cs index dd9df7c4c..d01b8dbc9 100644 --- a/src/Microsoft.OpenApi.Readers/Interface/ILog.cs +++ b/src/Microsoft.OpenApi.Readers/Interface/IDiagnostic.cs @@ -3,19 +3,12 @@ // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. // ------------------------------------------------------------ -using System.Collections.Generic; - namespace Microsoft.OpenApi.Readers.Interface { /// - /// Interface for the log + /// Interface for the entity containing diagnostic information from the reading process. /// - /// Type of recorded errors - public interface ILog + public interface IDiagnostic { - /// - /// List of recorded errors. - /// - IList Errors { get; set; } } } \ No newline at end of file diff --git a/src/Microsoft.OpenApi.Readers/Interface/IOpenApiReader.cs b/src/Microsoft.OpenApi.Readers/Interface/IOpenApiReader.cs index d718c3e90..6f0274552 100644 --- a/src/Microsoft.OpenApi.Readers/Interface/IOpenApiReader.cs +++ b/src/Microsoft.OpenApi.Readers/Interface/IOpenApiReader.cs @@ -3,24 +3,21 @@ // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. // ------------------------------------------------------------ -using SharpYaml.Serialization.Logging; - namespace Microsoft.OpenApi.Readers.Interface { /// - /// Interface for open API readers. + /// Interface for Open API readers. /// /// The type of input to read from. - /// The type of log for information from reading process. - /// The type of the recorded error from the reading process. - public interface IOpenApiReader where TLog : ILog + /// The type of diagnostic for information from reading process. + public interface IOpenApiReader where TDiagnostic : IDiagnostic { /// /// Reads the input and parses it into an Open API document. /// /// The input to read from. - /// The log or context containing information from the reading process. + /// The diagnostic entity containing information from the reading process. /// The Open API document. - OpenApiDocument Read(TInput input, out TLog log); + OpenApiDocument Read(TInput input, out TDiagnostic diagnostic); } } \ No newline at end of file diff --git a/src/Microsoft.OpenApi.Readers/YamlReaders/ListNode.cs b/src/Microsoft.OpenApi.Readers/YamlReaders/ListNode.cs deleted file mode 100644 index 7e833d8bb..000000000 --- a/src/Microsoft.OpenApi.Readers/YamlReaders/ListNode.cs +++ /dev/null @@ -1,47 +0,0 @@ -using SharpYaml.Serialization; -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; - -namespace Microsoft.OpenApi.Readers.YamlReaders -{ - internal class ListNode : ParseNode, IEnumerable - { - YamlSequenceNode nodeList; - ParsingContext context; - public ListNode(ParsingContext ctx, YamlSequenceNode sequenceNode) : base(ctx) - { - this.context = ctx; - nodeList = sequenceNode; - } - - public override List CreateList(Func map) - { - var yamlSequence = nodeList as YamlSequenceNode; - if (yamlSequence == null) throw new OpenApiException($"Expected list at line {nodeList.Start.Line} while parsing {typeof(T).Name}"); - - return yamlSequence.Select(n => map(new MapNode(this.context,n as YamlMappingNode))).Where(i => i != null).ToList(); - } - - public override List CreateSimpleList(Func map) - { - var yamlSequence = this.nodeList as YamlSequenceNode; - if (yamlSequence == null) throw new OpenApiException($"Expected list at line {nodeList.Start.Line} while parsing {typeof(T).Name}"); - - return yamlSequence.Select(n => map(new ValueNode(this.Context,(YamlScalarNode)n))).ToList(); - } - - public IEnumerator GetEnumerator() - { - return nodeList.Select(n => YamlHelper.Create(this.Context,n)).ToList().GetEnumerator(); - } - - IEnumerator IEnumerable.GetEnumerator() - { - return this.GetEnumerator(); - } - } - - -} diff --git a/src/Microsoft.OpenApi.Readers/YamlReaders/MapNode.cs b/src/Microsoft.OpenApi.Readers/YamlReaders/MapNode.cs deleted file mode 100644 index 88737a140..000000000 --- a/src/Microsoft.OpenApi.Readers/YamlReaders/MapNode.cs +++ /dev/null @@ -1,139 +0,0 @@ -using SharpYaml.Schemas; -using SharpYaml.Serialization; -using System; -using System.Collections; -using System.Collections.Generic; -using System.IO; -using System.Linq; - -namespace Microsoft.OpenApi.Readers.YamlReaders -{ - /// - /// Abstraction of a Map to isolate semantic parsing from details of - /// - internal class MapNode : ParseNode, IEnumerable - { - YamlMappingNode node; - private List nodes; - - public static MapNode Create(string yaml) - { - - var parsingContent = new ParsingContext(); - return new MapNode(parsingContent, (YamlMappingNode)YamlHelper.ParseYaml(yaml)); - } - - public MapNode(ParsingContext ctx, YamlMappingNode node) : base(ctx) - { - if (node == null) throw new OpenApiException($"Expected map"); - this.node = node; - nodes = this.node.Children.Select(kvp => new PropertyNode(Context, kvp.Key.GetScalarValue(), kvp.Value)).Cast().ToList(); - } - - public IEnumerator GetEnumerator() - { - return this.nodes.GetEnumerator(); - } - - IEnumerator IEnumerable.GetEnumerator() - { - return this.nodes.GetEnumerator(); - } - - public PropertyNode this[string key] - { - get - { - YamlNode node = null; - if (this.node.Children.TryGetValue(new YamlScalarNode(key), out node)) - { - return new PropertyNode(this.Context, key, this.node.Children[new YamlScalarNode(key)]); - } else - { - return null; - } - } - } - - public override string GetRaw() - { - var x = new Serializer(new SerializerSettings(new JsonSchema()) { EmitJsonComptible = true } ); - return x.Serialize(this.node); - - } - - public string GetScalarValue(ValueNode key) - { - var scalarNode = this.node.Children[new YamlScalarNode(key.GetScalarValue())] as YamlScalarNode; - if (scalarNode == null) throw new OpenApiException($"Expected scalar at line {this.node.Start.Line} for key {key.GetScalarValue()}"); - - return scalarNode.Value; - } - - public T GetReferencedObject(string refPointer) where T : IOpenApiReference - { - return (T)this.Context.GetReferencedObject(refPointer); - } - public T CreateOrReferenceDomainObject(Func factory) where T: IOpenApiReference - { - T domainObject; - var refPointer = GetReferencePointer(); // What should the DOM of a reference look like? - // Duplicated object - poor perf/more memory/unsynchronized changes - // Intermediate object - require common base class/client code has to explicitly code for it. - // Delegating object - lot of setup work/maintenance/ require full interfaces - // **current favourite***Shared object - marker to indicate its a reference/serialization code must serialize as reference everywhere except components. - if (refPointer != null) - { - domainObject = (T)this.Context.GetReferencedObject(refPointer); - - } - else - { - domainObject = factory(); - } - - return domainObject; - } - - public override Dictionary CreateMap(Func map) - { - var yamlMap = this.node; - if (yamlMap == null) throw new OpenApiException($"Expected map at line {yamlMap.Start.Line} while parsing {typeof(T).Name}"); - var nodes = yamlMap.Select(n => new { key = n.Key.GetScalarValue(), value = map(new MapNode(this.Context,n.Value as YamlMappingNode)) }); - return nodes.ToDictionary(k => k.key, v => v.value); - } - - public override Dictionary CreateMapWithReference(string refpointerbase, Func map) - { - - var yamlMap = this.node; - if (yamlMap == null) throw new OpenApiException($"Expected map at line {yamlMap.Start.Line} while parsing {typeof(T).Name}"); - var nodes = yamlMap.Select(n => new { key = n.Key.GetScalarValue(), - value = this.GetReferencedObject(refpointerbase + n.Key.GetScalarValue()) - ?? map(new MapNode(this.Context, (YamlMappingNode)n.Value)) - }); - return nodes.ToDictionary(k => k.key, v => v.value); - } - - public override Dictionary CreateSimpleMap(Func map) - { - var yamlMap = this.node; - if (yamlMap == null) throw new OpenApiException($"Expected map at line {yamlMap.Start.Line} while parsing {typeof(T).Name}"); - var nodes = yamlMap.Select(n => new { key = n.Key.GetScalarValue(), value = map(new ValueNode(this.Context, (YamlScalarNode)n.Value)) }); - return nodes.ToDictionary(k => k.key, v => v.value); - } - - public string GetReferencePointer() - { - YamlNode refNode; - - if (!this.node.Children.TryGetValue(new YamlScalarNode("$ref"), out refNode)) - { - return null; - } - return refNode.GetScalarValue(); - } - } - - -} diff --git a/src/Microsoft.OpenApi.Readers/YamlReaders/OpenApiDiagnostic.cs b/src/Microsoft.OpenApi.Readers/YamlReaders/OpenApiDiagnostic.cs new file mode 100644 index 000000000..0c40644b3 --- /dev/null +++ b/src/Microsoft.OpenApi.Readers/YamlReaders/OpenApiDiagnostic.cs @@ -0,0 +1,15 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + +using System.Collections.Generic; +using Microsoft.OpenApi.Readers.Interface; + +namespace Microsoft.OpenApi.Readers.YamlReaders +{ + public class OpenApiDiagnostic : IDiagnostic + { + public IList Errors { get; set; } = new List(); + } +} \ No newline at end of file diff --git a/src/Microsoft.OpenApi.Readers/YamlReaders/OpenApiError.cs b/src/Microsoft.OpenApi.Readers/YamlReaders/OpenApiError.cs index 06433d45e..fb78ceeb3 100644 --- a/src/Microsoft.OpenApi.Readers/YamlReaders/OpenApiError.cs +++ b/src/Microsoft.OpenApi.Readers/YamlReaders/OpenApiError.cs @@ -1,17 +1,21 @@ -using Microsoft.OpenApi.Readers.Interface; +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ namespace Microsoft.OpenApi.Readers.YamlReaders { public class OpenApiError { - string pointer; - string message; + private readonly string message; + private readonly string pointer; - public OpenApiError(OpenApiException ex) + public OpenApiError(OpenApiException exception) { - this.message = ex.Message; - this.pointer = ex.Pointer; + message = exception.Message; + pointer = exception.Pointer; } + public OpenApiError(string pointer, string message) { this.pointer = pointer; @@ -20,7 +24,7 @@ public OpenApiError(string pointer, string message) public override string ToString() { - return this.message + (!string.IsNullOrEmpty(this.pointer) ? " at " + this.pointer : ""); + return message + (!string.IsNullOrEmpty(pointer) ? " at " + pointer : ""); } } -} +} \ No newline at end of file diff --git a/src/Microsoft.OpenApi.Readers/YamlReaders/OpenApiStreamReader.cs b/src/Microsoft.OpenApi.Readers/YamlReaders/OpenApiStreamReader.cs index 1488ac041..779902be9 100644 --- a/src/Microsoft.OpenApi.Readers/YamlReaders/OpenApiStreamReader.cs +++ b/src/Microsoft.OpenApi.Readers/YamlReaders/OpenApiStreamReader.cs @@ -6,6 +6,7 @@ using System.IO; using System.Linq; using Microsoft.OpenApi.Readers.Interface; +using Microsoft.OpenApi.Readers.YamlReaders.ParseNodes; using SharpYaml; using SharpYaml.Serialization; @@ -14,7 +15,7 @@ namespace Microsoft.OpenApi.Readers.YamlReaders /// /// Service class for converting streams into OpenApiDocument instances /// - public class OpenApiStreamReader : IOpenApiReader + public class OpenApiStreamReader : IOpenApiReader { /// /// Gets the version of the Open API document. @@ -22,46 +23,42 @@ public class OpenApiStreamReader : IOpenApiReader /// Reads the stream input and parses it into an Open API document. /// - public OpenApiDocument Read(Stream input, out ParsingContext context) + public OpenApiDocument Read(Stream input, out OpenApiDiagnostic diagnostic) { RootNode rootNode; - context = new ParsingContext(); - + var context = new ParsingContext(); + diagnostic = new OpenApiDiagnostic(); + try { using (var streamReader = new StreamReader(input)) { - var yamlStream = new YamlStream(); yamlStream.Load(streamReader); var yamlDocument = yamlStream.Documents.First(); - rootNode = new RootNode(context, yamlDocument); + rootNode = new RootNode(context, diagnostic, yamlDocument); } } catch (SyntaxErrorException ex) { - context.Errors.Add(new OpenApiError(string.Empty, ex.Message)); - context.OpenApiDocument = new OpenApiDocument(); // Could leave this null? + diagnostic.Errors.Add(new OpenApiError(string.Empty, ex.Message)); - return context.OpenApiDocument; + return new OpenApiDocument(); } var inputVersion = GetVersion(rootNode); @@ -70,25 +67,24 @@ public OpenApiDocument Read(Stream input, out ParsingContext context) { case "2.0": context.SetReferenceService( - new ReferenceService(rootNode) + new OpenApiReferenceService(rootNode) { - loadReference = OpenApiV2Builder.LoadReference, - parseReference = p => OpenApiV2Builder.ParseReference(p) + loadReference = OpenApiV2Deserializer.LoadReference, + parseReference = p => OpenApiV2Deserializer.ParseReference(p) }); - context.OpenApiDocument = OpenApiV2Builder.LoadOpenApi(rootNode); - break; + + return OpenApiV2Deserializer.LoadOpenApi(rootNode); + default: context.SetReferenceService( - new ReferenceService(rootNode) + new OpenApiReferenceService(rootNode) { - loadReference = OpenApiV3Builder.LoadReference, + loadReference = OpenApiV3Deserializer.LoadReference, parseReference = p => new OpenApiReference(p) }); - context.OpenApiDocument = OpenApiV3Builder.LoadOpenApi(rootNode); - break; - } - return context.OpenApiDocument; + return OpenApiV3Deserializer.LoadOpenApi(rootNode); + } } } } \ No newline at end of file diff --git a/src/Microsoft.OpenApi.Readers/YamlReaders/OpenApiStringReader.cs b/src/Microsoft.OpenApi.Readers/YamlReaders/OpenApiStringReader.cs index d5ff05905..a06625d2d 100644 --- a/src/Microsoft.OpenApi.Readers/YamlReaders/OpenApiStringReader.cs +++ b/src/Microsoft.OpenApi.Readers/YamlReaders/OpenApiStringReader.cs @@ -11,12 +11,12 @@ namespace Microsoft.OpenApi.Readers.YamlReaders /// /// Service class for converting strings into OpenApiDocument instances /// - public class OpenApiStringReader : IOpenApiReader + public class OpenApiStringReader : IOpenApiReader { /// /// Reads the string input and parses it into an Open API document. /// - public OpenApiDocument Read(string input, out ParsingContext context) + public OpenApiDocument Read(string input, out OpenApiDiagnostic diagnostic) { using (var memoryStream = new MemoryStream()) { @@ -25,7 +25,7 @@ public OpenApiDocument Read(string input, out ParsingContext context) writer.Flush(); memoryStream.Position = 0; - return new OpenApiStreamReader().Read(memoryStream, out context); + return new OpenApiStreamReader().Read(memoryStream, out diagnostic); } } } diff --git a/src/Microsoft.OpenApi.Readers/YamlReaders/OpenApiV2Builder.cs b/src/Microsoft.OpenApi.Readers/YamlReaders/OpenApiV2Deserializer.cs similarity index 95% rename from src/Microsoft.OpenApi.Readers/YamlReaders/OpenApiV2Builder.cs rename to src/Microsoft.OpenApi.Readers/YamlReaders/OpenApiV2Deserializer.cs index eeee13a6a..f852cae9c 100644 --- a/src/Microsoft.OpenApi.Readers/YamlReaders/OpenApiV2Builder.cs +++ b/src/Microsoft.OpenApi.Readers/YamlReaders/OpenApiV2Deserializer.cs @@ -1,12 +1,18 @@ using System; using System.Collections.Generic; using System.Linq; +using Microsoft.OpenApi.Readers.YamlReaders.ParseNodes; namespace Microsoft.OpenApi.Readers.YamlReaders { - internal static class OpenApiV2Builder + /// + /// Class containing logic to deserialize Open API V2 document into + /// runtime Open API object model. + /// + internal static class OpenApiV2Deserializer { #region OpenApiObject + public static FixedFieldMap OpenApiFixedFields = new FixedFieldMap { { "swagger", (o,n) => { /* Ignore it */} }, { "info", (o,n) => o.Info = LoadInfo(n) }, @@ -23,9 +29,7 @@ internal static class OpenApiV2Builder { "security", (o,n) => o.SecurityRequirements = n.CreateList(LoadSecurityRequirement)}, { "tags", (o,n) => o.Tags = n.CreateList(LoadTag)}, { "externalDocs", (o,n) => o.ExternalDocs = LoadExternalDocs(n) } - }; - - + }; private static void MakeServers(IList servers, ParsingContext context) { @@ -163,7 +167,7 @@ internal static OpenApiLicense LoadLicense(ParseNode node) public static PatternFieldMap PathsPatternFields = new PatternFieldMap { - { (s)=> s.StartsWith("/"), (o,k,n)=> o.Add(k, OpenApiV2Builder.LoadPathItem(n) ) }, + { (s)=> s.StartsWith("/"), (o,k,n)=> o.Add(k, OpenApiV2Deserializer.LoadPathItem(n) ) }, { (s)=> s.StartsWith("x-"), (o,k,n)=> o.Extensions.Add(k, new OpenApiString(n.GetScalarValue())) } }; @@ -184,7 +188,7 @@ public static OpenApiPaths LoadPaths(ParseNode node) private static FixedFieldMap PathItemFixedFields = new FixedFieldMap { { "$ref", (o,n) => { /* Not supported yet */} }, - { "parameters", (o,n) => { o.Parameters = n.CreateList(OpenApiV2Builder.LoadParameter); } }, + { "parameters", (o,n) => { o.Parameters = n.CreateList(OpenApiV2Deserializer.LoadParameter); } }, }; @@ -192,7 +196,7 @@ public static OpenApiPaths LoadPaths(ParseNode node) { { (s)=> s.StartsWith("x-"), (o,k,n)=> o.Extensions.Add(k, new OpenApiString(n.GetScalarValue())) }, { (s)=> "get,put,post,delete,patch,options,head,patch".Contains(s), - (o,k,n)=> o.AddOperation(OperationTypeExtensions.ParseOperationType(k), OpenApiV2Builder.LoadOperation(n) ) } + (o,k,n)=> o.AddOperation(OperationTypeExtensions.ParseOperationType(k), OpenApiV2Deserializer.LoadOperation(n) ) } }; @@ -213,14 +217,22 @@ public static OpenApiPathItem LoadPathItem(ParseNode node) private static FixedFieldMap OperationFixedFields = new FixedFieldMap { - { "tags", (o,n) => o.Tags = n.CreateSimpleList((v) => ReferenceService.LoadTagByReference(v.Context,v.GetScalarValue()))}, + { "tags", (o,n) => o.Tags = n.CreateSimpleList((valueNode) => + OpenApiReferenceService.LoadTagByReference( + valueNode.Context, + valueNode.Diagnostic, + valueNode.GetScalarValue()))}, { "summary", (o,n) => { o.Summary = n.GetScalarValue(); } }, { "description", (o,n) => { o.Description = n.GetScalarValue(); } }, { "externalDocs", (o,n) => { o.ExternalDocs = LoadExternalDocs(n); } }, { "operationId", (o,n) => { o.OperationId = n.GetScalarValue(); } }, { "parameters", (o,n) => { o.Parameters = n.CreateList(LoadParameter); } }, - { "consumes", (o,n) => n.Context.SetTempStorage("operationconsumes", n.CreateSimpleList((s) => s.GetScalarValue()))}, - { "produces", (o,n) => n.Context.SetTempStorage("operationproduces", n.CreateSimpleList((s) => s.GetScalarValue()))}, + { "consumes", (o,n) => n.Context.SetTempStorage( + "operationconsumes", + n.CreateSimpleList((s) => s.GetScalarValue()))}, + { "produces", (o,n) => n.Context.SetTempStorage( + "operationproduces", + n.CreateSimpleList((s) => s.GetScalarValue()))}, { "responses", (o,n) => { o.Responses = n.CreateMap(LoadResponse); } }, { "deprecated", (o,n) => { o.Deprecated = bool.Parse(n.GetScalarValue()); } }, { "security", (o,n) => { o.Security = n.CreateList(LoadSecurityRequirement); } }, @@ -765,20 +777,19 @@ public static OpenApiOAuthFlow LoadOAuthFlow(ParseNode node) #region SecurityRequirement public static OpenApiSecurityRequirement LoadSecurityRequirement(ParseNode node) { - var mapNode = node.CheckMapNode("security"); var obj = new OpenApiSecurityRequirement(); foreach (var property in mapNode) { - var scheme = ReferenceService.LoadSecuritySchemeByReference(mapNode.Context, property.Name); + var scheme = OpenApiReferenceService.LoadSecuritySchemeByReference(mapNode.Context, mapNode.Diagnostic, property.Name); if (scheme != null) { obj.Schemes.Add(scheme, property.Value.CreateSimpleList(n2 => n2.GetScalarValue())); } else { - node.Context.Errors.Add(new OpenApiError(node.Context.GetLocation(), $"Scheme {property.Name} is not found")); + node.Diagnostic.Errors.Add(new OpenApiError(node.Context.GetLocation(), $"Scheme {property.Name} is not found")); } } return obj; @@ -854,13 +865,13 @@ public static IOpenApiReference LoadReference(OpenApiReference reference, object switch (reference.ReferenceType) { case ReferenceType.Schema: - referencedObject = OpenApiV2Builder.LoadSchema(node); + referencedObject = LoadSchema(node); break; case ReferenceType.Parameter: - referencedObject = OpenApiV2Builder.LoadParameter(node); + referencedObject = LoadParameter(node); break; case ReferenceType.SecurityScheme: - referencedObject = OpenApiV2Builder.LoadSecurityScheme(node); + referencedObject = LoadSecurityScheme(node); break; case ReferenceType.Tags: ListNode list = (ListNode)node; @@ -868,7 +879,7 @@ public static IOpenApiReference LoadReference(OpenApiReference reference, object { foreach (var item in list) { - var tag = OpenApiV2Builder.LoadTag(item); + var tag = LoadTag(item); if (tag.Name == reference.TypeName) { @@ -896,15 +907,15 @@ private static void ParseMap(MapNode mapNode, T domainObject, FixedFieldMap(domainObject, fixedFieldMap, patternFieldMap); - if (requiredFields != null) requiredFields.Remove(propertyNode.Name); + requiredFields?.Remove(propertyNode.Name); } } - private static void ReportMissing(ParseNode node, List required) + private static void ReportMissing(ParseNode node, IList required) { foreach ( var error in required.Select(r => new OpenApiError("", $"{r} is a required property")).ToList() ) { - node.Context.Errors.Add(error); + node.Diagnostic.Errors.Add(error); } } diff --git a/src/Microsoft.OpenApi.Readers/YamlReaders/OpenApiV3Builder.cs b/src/Microsoft.OpenApi.Readers/YamlReaders/OpenApiV3Deserializer.cs similarity index 95% rename from src/Microsoft.OpenApi.Readers/YamlReaders/OpenApiV3Builder.cs rename to src/Microsoft.OpenApi.Readers/YamlReaders/OpenApiV3Deserializer.cs index 9dac8a643..d1793575c 100644 --- a/src/Microsoft.OpenApi.Readers/YamlReaders/OpenApiV3Builder.cs +++ b/src/Microsoft.OpenApi.Readers/YamlReaders/OpenApiV3Deserializer.cs @@ -1,10 +1,15 @@ using System; using System.Collections.Generic; using System.Linq; +using Microsoft.OpenApi.Readers.YamlReaders.ParseNodes; namespace Microsoft.OpenApi.Readers.YamlReaders { - internal static class OpenApiV3Builder + /// + /// Class containing logic to deserialize Open API V3 document into + /// runtime Open API object model. + /// + internal static class OpenApiV3Deserializer { #region OpenApiObject public static FixedFieldMap OpenApiFixedFields = new FixedFieldMap { @@ -270,7 +275,8 @@ public static OpenApiPathItem LoadPathItem(ParseNode node) private static FixedFieldMap OperationFixedFields = new FixedFieldMap { - { "tags", (o,n) => o.Tags = n.CreateSimpleList((v) => ReferenceService.LoadTagByReference(v.Context,v.GetScalarValue()))}, + { "tags", (o,n) => o.Tags = n.CreateSimpleList((valueNode) => + OpenApiReferenceService.LoadTagByReference(valueNode.Context, valueNode.Diagnostic, valueNode.GetScalarValue()))}, { "summary", (o,n) => { o.Summary = n.GetScalarValue(); } }, { "description", (o,n) => { o.Description = n.GetScalarValue(); } }, { "externalDocs", (o,n) => { o.ExternalDocs = LoadExternalDocs(n); } }, @@ -677,11 +683,6 @@ internal static OpenApiTag LoadTag(ParseNode n) { (s)=> s.StartsWith("x-"), (o,k,n)=> o.Extensions.Add(k, new OpenApiString(n.GetScalarValue())) } }; - public static OpenApiSchema LoadSchema(string schema) - { - return LoadSchema(MapNode.Create(schema)); - } - public static OpenApiSchema LoadSchema(ParseNode node) { @@ -807,7 +808,7 @@ public static OpenApiSecurityRequirement LoadSecurityRequirement(ParseNode node) foreach (var property in mapNode) { - var scheme = ReferenceService.LoadSecuritySchemeByReference(mapNode.Context, property.Name); + var scheme = OpenApiReferenceService.LoadSecuritySchemeByReference(mapNode.Context, mapNode.Diagnostic, property.Name); obj.Schemes.Add(scheme, property.Value.CreateSimpleList(n2 => n2.GetScalarValue())); } @@ -826,31 +827,36 @@ public static IOpenApiReference LoadReference(OpenApiReference pointer, object r switch (pointer.ReferenceType) { case ReferenceType.Schema: - referencedObject = OpenApiV3Builder.LoadSchema(node); + referencedObject = LoadSchema(node); break; - case ReferenceType.Parameter: - referencedObject = OpenApiV3Builder.LoadParameter(node); + case ReferenceType.Parameter: + referencedObject = LoadParameter(node); break; + case ReferenceType.Callback: - referencedObject = OpenApiV3Builder.LoadCallback(node); + referencedObject = LoadCallback(node); break; + case ReferenceType.SecurityScheme: - referencedObject = OpenApiV3Builder.LoadSecurityScheme(node); + referencedObject = LoadSecurityScheme(node); break; + case ReferenceType.Link: - referencedObject = OpenApiV3Builder.LoadLink(node); + referencedObject = LoadLink(node); break; + case ReferenceType.Example: - referencedObject = OpenApiV3Builder.LoadExample(node); + referencedObject = LoadExample(node); break; + case ReferenceType.Tags: ListNode list = (ListNode)node; if (list != null) { foreach (var item in list) { - var tag = OpenApiV3Builder.LoadTag(item); + var tag = OpenApiV3Deserializer.LoadTag(item); if (tag.Name == pointer.TypeName) { @@ -864,10 +870,11 @@ public static IOpenApiReference LoadReference(OpenApiReference pointer, object r } break; + default: throw new OpenApiException($"Unknown type of $ref {pointer.ReferenceType} at {pointer.ToString()}"); - } + return referencedObject; } @@ -878,8 +885,9 @@ private static void ParseMap(MapNode mapNode, T domainObject, FixedFieldMap(domainObject, fixedFieldMap, patternFieldMap); - if (requiredFields != null) requiredFields.Remove(propertyNode.Name); + requiredFields?.Remove(propertyNode.Name); } + ReportMissing(mapNode, requiredFields); } @@ -888,14 +896,17 @@ private static RuntimeExpression LoadRuntimeExpression(ParseNode node) var value = node.GetScalarValue(); return new RuntimeExpression(value); } - private static void ReportMissing(ParseNode node, List required) + + private static void ReportMissing(ParseNode node, IList required) { - if (required != null && required.Count > 0) + if (required == null || !required.Any() ) { - foreach ( var error in required.Select(r => new OpenApiError("", $"{r} is a required property of {node.Context.GetLocation()}")).ToList()) - { - node.Context.Errors.Add(error); - } + return; + } + + foreach (var error in required.Select(r => new OpenApiError("", $"{r} is a required property of {node.Context.GetLocation()}")).ToList()) + { + node.Diagnostic.Errors.Add(error); } } diff --git a/src/Microsoft.OpenApi.Readers/YamlReaders/ParseNodes/FixedFieldMap.cs b/src/Microsoft.OpenApi.Readers/YamlReaders/ParseNodes/FixedFieldMap.cs new file mode 100644 index 000000000..3e7fcaf78 --- /dev/null +++ b/src/Microsoft.OpenApi.Readers/YamlReaders/ParseNodes/FixedFieldMap.cs @@ -0,0 +1,14 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + +using System; +using System.Collections.Generic; + +namespace Microsoft.OpenApi.Readers.YamlReaders.ParseNodes +{ + internal class FixedFieldMap : Dictionary> + { + } +} \ No newline at end of file diff --git a/src/Microsoft.OpenApi.Readers/YamlReaders/ParseNodes/ListNode.cs b/src/Microsoft.OpenApi.Readers/YamlReaders/ParseNodes/ListNode.cs new file mode 100644 index 000000000..1a2f09f6d --- /dev/null +++ b/src/Microsoft.OpenApi.Readers/YamlReaders/ParseNodes/ListNode.cs @@ -0,0 +1,61 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using SharpYaml.Serialization; + +namespace Microsoft.OpenApi.Readers.YamlReaders.ParseNodes +{ + internal class ListNode : ParseNode, IEnumerable + { + private readonly YamlSequenceNode nodeList; + + public ListNode(ParsingContext context, OpenApiDiagnostic diagnostic, YamlSequenceNode sequenceNode) : base( + context, + diagnostic) + { + nodeList = sequenceNode; + } + + public override List CreateList(Func map) + { + var yamlSequence = nodeList; + if (yamlSequence == null) + { + throw new OpenApiException( + $"Expected list at line {nodeList.Start.Line} while parsing {typeof(T).Name}"); + } + + return yamlSequence.Select(n => map(new MapNode(Context, Diagnostic, n as YamlMappingNode))) + .Where(i => i != null) + .ToList(); + } + + public override List CreateSimpleList(Func map) + { + var yamlSequence = nodeList; + if (yamlSequence == null) + { + throw new OpenApiException( + $"Expected list at line {nodeList.Start.Line} while parsing {typeof(T).Name}"); + } + + return yamlSequence.Select(n => map(new ValueNode(Context, Diagnostic, (YamlScalarNode)n))).ToList(); + } + + public IEnumerator GetEnumerator() + { + return nodeList.Select(n => Create(Context, Diagnostic, n)).ToList().GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.OpenApi.Readers/YamlReaders/ParseNodes/MapNode.cs b/src/Microsoft.OpenApi.Readers/YamlReaders/ParseNodes/MapNode.cs new file mode 100644 index 000000000..49fbd8e5f --- /dev/null +++ b/src/Microsoft.OpenApi.Readers/YamlReaders/ParseNodes/MapNode.cs @@ -0,0 +1,173 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using SharpYaml.Schemas; +using SharpYaml.Serialization; + +namespace Microsoft.OpenApi.Readers.YamlReaders.ParseNodes +{ + /// + /// Abstraction of a Map to isolate semantic parsing from details of + /// + internal class MapNode : ParseNode, IEnumerable + { + private readonly YamlMappingNode node; + private readonly List nodes; + + public MapNode(ParsingContext context, OpenApiDiagnostic diagnostic, string yamlString) : + this(context, diagnostic, (YamlMappingNode)YamlHelper.ParseYamlString(yamlString)) + { + } + + public MapNode(ParsingContext context, OpenApiDiagnostic diagnostic, YamlMappingNode node) : base(context, diagnostic) + { + if (node == null) + { + throw new OpenApiException($"Expected map"); + } + + this.node = node; + + nodes = this.node.Children + .Select(kvp => new PropertyNode(Context, Diagnostic, kvp.Key.GetScalarValue(), kvp.Value)) + .Cast() + .ToList(); + } + + public PropertyNode this[string key] + { + get + { + YamlNode node = null; + if (this.node.Children.TryGetValue(new YamlScalarNode(key), out node)) + { + return new PropertyNode(Context, Diagnostic, key, this.node.Children[new YamlScalarNode(key)]); + } + + return null; + } + } + + public override Dictionary CreateMap(Func map) + { + var yamlMap = node; + if (yamlMap == null) + { + throw new OpenApiException($"Expected map at line {yamlMap.Start.Line} while parsing {typeof(T).Name}"); + } + + var nodes = yamlMap.Select( + n => new + { + key = n.Key.GetScalarValue(), + value = map(new MapNode(Context, Diagnostic, n.Value as YamlMappingNode)) + }); + return nodes.ToDictionary(k => k.key, v => v.value); + } + + public override Dictionary CreateMapWithReference(string refpointerbase, Func map) + { + var yamlMap = node; + if (yamlMap == null) + { + throw new OpenApiException($"Expected map at line {yamlMap.Start.Line} while parsing {typeof(T).Name}"); + } + + var nodes = yamlMap.Select( + n => new + { + key = n.Key.GetScalarValue(), + value = GetReferencedObject(refpointerbase + n.Key.GetScalarValue()) ?? + map(new MapNode(Context, Diagnostic, (YamlMappingNode)n.Value)) + }); + return nodes.ToDictionary(k => k.key, v => v.value); + } + + public T CreateOrReferenceDomainObject(Func factory) where T : IOpenApiReference + { + T domainObject; + var refPointer = GetReferencePointer(); // What should the DOM of a reference look like? + // Duplicated object - poor perf/more memory/unsynchronized changes + // Intermediate object - require common base class/client code has to explicitly code for it. + // Delegating object - lot of setup work/maintenance/ require full interfaces + // **current favourite***Shared object - marker to indicate its a reference/serialization code must serialize as reference everywhere except components. + if (refPointer != null) + { + domainObject = (T)Context.GetReferencedObject(Diagnostic, refPointer); + } + else + { + domainObject = factory(); + } + + return domainObject; + } + + public override Dictionary CreateSimpleMap(Func map) + { + var yamlMap = node; + if (yamlMap == null) + { + throw new OpenApiException($"Expected map at line {yamlMap.Start.Line} while parsing {typeof(T).Name}"); + } + + var nodes = yamlMap.Select( + n => new + { + key = n.Key.GetScalarValue(), + value = map(new ValueNode(Context, Diagnostic, (YamlScalarNode)n.Value)) + }); + return nodes.ToDictionary(k => k.key, v => v.value); + } + + public IEnumerator GetEnumerator() + { + return nodes.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return nodes.GetEnumerator(); + } + + public override string GetRaw() + { + var x = new Serializer(new SerializerSettings(new JsonSchema()) {EmitJsonComptible = true}); + return x.Serialize(node); + } + + public T GetReferencedObject(string refPointer) where T : IOpenApiReference + { + return (T)Context.GetReferencedObject(Diagnostic, refPointer); + } + + public string GetReferencePointer() + { + YamlNode refNode; + + if (!node.Children.TryGetValue(new YamlScalarNode("$ref"), out refNode)) + { + return null; + } + + return refNode.GetScalarValue(); + } + + public string GetScalarValue(ValueNode key) + { + var scalarNode = node.Children[new YamlScalarNode(key.GetScalarValue())] as YamlScalarNode; + if (scalarNode == null) + { + throw new OpenApiException($"Expected scalar at line {node.Start.Line} for key {key.GetScalarValue()}"); + } + + return scalarNode.Value; + } + } +} \ No newline at end of file diff --git a/src/Microsoft.OpenApi.Readers/YamlReaders/ParseNode.cs b/src/Microsoft.OpenApi.Readers/YamlReaders/ParseNodes/ParseNode.cs similarity index 52% rename from src/Microsoft.OpenApi.Readers/YamlReaders/ParseNode.cs rename to src/Microsoft.OpenApi.Readers/YamlReaders/ParseNodes/ParseNode.cs index 843f13acf..28a76538f 100644 --- a/src/Microsoft.OpenApi.Readers/YamlReaders/ParseNode.cs +++ b/src/Microsoft.OpenApi.Readers/YamlReaders/ParseNodes/ParseNode.cs @@ -1,27 +1,27 @@ -using System; +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + +using System; using System.Collections.Generic; -using System.IO; -using System.Linq; using System.Text.RegularExpressions; +using SharpYaml.Serialization; -namespace Microsoft.OpenApi.Readers.YamlReaders +namespace Microsoft.OpenApi.Readers.YamlReaders.ParseNodes { - internal class FixedFieldMap : Dictionary> - { - } - - internal class PatternFieldMap : Dictionary, Action> + internal abstract class ParseNode { - } - - internal abstract class ParseNode - { - public ParseNode(ParsingContext ctx) + protected ParseNode(ParsingContext parsingContext, OpenApiDiagnostic diagnostic) { - this.Context = ctx; + Context = parsingContext; + Diagnostic = diagnostic; } public ParsingContext Context { get; } + + public OpenApiDiagnostic Diagnostic { get; } + public string DomainType { get; internal set; } public MapNode CheckMapNode(string nodeName) @@ -29,54 +29,76 @@ public MapNode CheckMapNode(string nodeName) var mapNode = this as MapNode; if (mapNode == null) { - this.Context.Errors.Add(new OpenApiError("", $"{nodeName} must be a map/object at " + this.Context.GetLocation() )); + Diagnostic.Errors.Add( + new OpenApiError("", $"{nodeName} must be a map/object at " + Context.GetLocation())); } return mapNode; } - public virtual string GetRaw() + internal string CheckRegex(string value, Regex versionRegex, string defaultValue) { - throw new OpenApiException("Cannot get raw value"); + if (!versionRegex.IsMatch(value)) + { + Diagnostic.Errors.Add(new OpenApiError("", "Value does not match regex: " + versionRegex)); + return defaultValue; + } + + return value; } - - public virtual string GetScalarValue() + + public static ParseNode Create(ParsingContext context, OpenApiDiagnostic diagnostic, YamlNode node) { - throw new OpenApiException("Cannot get scalar value"); + var listNode = node as YamlSequenceNode; + + if (listNode != null) + { + return new ListNode(context, diagnostic, listNode); + } + + var mapNode = node as YamlMappingNode; + if (mapNode != null) + { + return new MapNode(context, diagnostic, mapNode); + } + + return new ValueNode(context, diagnostic, node as YamlScalarNode); + } + + public virtual List CreateList(Func map) + { + throw new OpenApiException("Cannot create list"); } public virtual Dictionary CreateMap(Func map) { throw new OpenApiException("Cannot create map"); } - public virtual Dictionary CreateMapWithReference(string refpointer, Func map) where T : class, IOpenApiReference + + public virtual Dictionary CreateMapWithReference(string refpointer, Func map) + where T : class, IOpenApiReference { throw new OpenApiException("Cannot create map from reference"); } - public virtual Dictionary CreateSimpleMap(Func map) + + public virtual List CreateSimpleList(Func map) { - throw new OpenApiException("Cannot create simple map"); + throw new OpenApiException("Cannot create simple list"); } - public virtual List CreateList(Func map) + public virtual Dictionary CreateSimpleMap(Func map) { - throw new OpenApiException("Cannot create list"); - + throw new OpenApiException("Cannot create simple map"); } - public virtual List CreateSimpleList(Func map) + + public virtual string GetRaw() { - throw new OpenApiException("Cannot create simple list"); + throw new OpenApiException("Cannot get raw value"); } - internal string CheckRegex(string value, Regex versionRegex, string defaultValue) + public virtual string GetScalarValue() { - if (!versionRegex.IsMatch(value)) - { - this.Context.Errors.Add(new OpenApiError("", "Value does not match regex: " + versionRegex.ToString())); - return defaultValue; - } - return value; + throw new OpenApiException("Cannot get scalar value"); } } - -} +} \ No newline at end of file diff --git a/src/Microsoft.OpenApi.Readers/YamlReaders/ParseNodes/PatternFieldMap.cs b/src/Microsoft.OpenApi.Readers/YamlReaders/ParseNodes/PatternFieldMap.cs new file mode 100644 index 000000000..1fd238f68 --- /dev/null +++ b/src/Microsoft.OpenApi.Readers/YamlReaders/ParseNodes/PatternFieldMap.cs @@ -0,0 +1,14 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + +using System; +using System.Collections.Generic; + +namespace Microsoft.OpenApi.Readers.YamlReaders.ParseNodes +{ + internal class PatternFieldMap : Dictionary, Action> + { + } +} \ No newline at end of file diff --git a/src/Microsoft.OpenApi.Readers/YamlReaders/ParseNodes/PropertyNode.cs b/src/Microsoft.OpenApi.Readers/YamlReaders/ParseNodes/PropertyNode.cs new file mode 100644 index 000000000..8489ba12b --- /dev/null +++ b/src/Microsoft.OpenApi.Readers/YamlReaders/ParseNodes/PropertyNode.cs @@ -0,0 +1,80 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + +using System; +using System.Collections.Generic; +using System.Linq; +using SharpYaml.Serialization; + +namespace Microsoft.OpenApi.Readers.YamlReaders.ParseNodes +{ + internal class PropertyNode : ParseNode + { + public PropertyNode(ParsingContext context, OpenApiDiagnostic diagnostic, string name, YamlNode node) : base( + context, + diagnostic) + { + Name = name; + Value = Create(context, diagnostic, node); + } + + public string Name { get; set; } + + public ParseNode Value { get; set; } + + public void ParseField( + T parentInstance, + IDictionary> fixedFields, + IDictionary, Action> patternFields) + { + Action fixedFieldMap; + var found = fixedFields.TryGetValue(Name, out fixedFieldMap); + + if (fixedFieldMap != null) + { + try + { + Context.StartObject(Name); + fixedFieldMap(parentInstance, Value); + } + catch (OpenApiException ex) + { + ex.Pointer = Context.GetLocation(); + Diagnostic.Errors.Add(new OpenApiError(ex)); + } + finally + { + Context.EndObject(); + } + } + else + { + var map = patternFields.Where(p => p.Key(Name)).Select(p => p.Value).FirstOrDefault(); + if (map != null) + { + try + { + Context.StartObject(Name); + map(parentInstance, Name, Value); + } + catch (OpenApiException ex) + { + ex.Pointer = Context.GetLocation(); + Diagnostic.Errors.Add(new OpenApiError(ex)); + } + finally + { + Context.EndObject(); + } + } + else + { + Diagnostic.Errors.Add( + new OpenApiError("", $"{Name} is not a valid property at {Context.GetLocation()}")); + } + } + } + } +} \ No newline at end of file diff --git a/src/Microsoft.OpenApi.Readers/YamlReaders/RootNode.cs b/src/Microsoft.OpenApi.Readers/YamlReaders/ParseNodes/RootNode.cs similarity index 65% rename from src/Microsoft.OpenApi.Readers/YamlReaders/RootNode.cs rename to src/Microsoft.OpenApi.Readers/YamlReaders/ParseNodes/RootNode.cs index ae8e142ad..fedf2483d 100644 --- a/src/Microsoft.OpenApi.Readers/YamlReaders/RootNode.cs +++ b/src/Microsoft.OpenApi.Readers/YamlReaders/ParseNodes/RootNode.cs @@ -1,41 +1,51 @@ -using SharpYaml.Serialization; +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + using System; +using SharpYaml.Serialization; -namespace Microsoft.OpenApi.Readers.YamlReaders +namespace Microsoft.OpenApi.Readers.YamlReaders.ParseNodes { /// /// Wrapper class around YamlDocument to isolate semantic parsing from details of Yaml DOM. /// internal class RootNode : ParseNode { - YamlDocument yamlDocument; - public RootNode(ParsingContext ctx, YamlDocument yamlDocument) : base(ctx) + private readonly YamlDocument yamlDocument; + + public RootNode(ParsingContext context, OpenApiDiagnostic diagnostic, YamlDocument yamlDocument) : base(context, diagnostic) { this.yamlDocument = yamlDocument; } - public MapNode GetMap() + public ParseNode Find(JsonPointer refPointer) { - return new MapNode(Context, (YamlMappingNode)yamlDocument.RootNode); + var yamlNode = refPointer.Find(yamlDocument.RootNode); + if (yamlNode == null) + { + return null; + } + + return Create(Context, Diagnostic, yamlNode); } - public ParseNode Find(JsonPointer refPointer) + public MapNode GetMap() { - var yamlNode = refPointer.Find(this.yamlDocument.RootNode); - if (yamlNode == null) return null; - return YamlHelper.Create(this.Context, yamlNode); + return new MapNode(Context, Diagnostic, (YamlMappingNode)yamlDocument.RootNode); } } public static class JsonPointerExtensions { - public static YamlNode Find(this JsonPointer currentpointer, YamlNode sample) { if (currentpointer.Tokens.Length == 0) { return sample; } + try { var pointer = sample; @@ -58,6 +68,7 @@ public static YamlNode Find(this JsonPointer currentpointer, YamlNode sample) } } } + return pointer; } catch (Exception ex) @@ -65,6 +76,5 @@ public static YamlNode Find(this JsonPointer currentpointer, YamlNode sample) throw new ArgumentException("Failed to dereference pointer", ex); } } - } -} +} \ No newline at end of file diff --git a/src/Microsoft.OpenApi.Readers/YamlReaders/ParseNodes/ValueNode.cs b/src/Microsoft.OpenApi.Readers/YamlReaders/ParseNodes/ValueNode.cs new file mode 100644 index 000000000..77d85c773 --- /dev/null +++ b/src/Microsoft.OpenApi.Readers/YamlReaders/ParseNodes/ValueNode.cs @@ -0,0 +1,31 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + +using SharpYaml.Serialization; + +namespace Microsoft.OpenApi.Readers.YamlReaders.ParseNodes +{ + internal class ValueNode : ParseNode + { + private readonly YamlScalarNode node; + + public ValueNode(ParsingContext context, OpenApiDiagnostic diagnostic, YamlScalarNode scalarNode) : base(context, diagnostic) + { + 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; + } + } +} \ No newline at end of file diff --git a/src/Microsoft.OpenApi.Readers/YamlReaders/ParsingContext.cs b/src/Microsoft.OpenApi.Readers/YamlReaders/ParsingContext.cs index 401491162..b5bd1a85b 100644 --- a/src/Microsoft.OpenApi.Readers/YamlReaders/ParsingContext.cs +++ b/src/Microsoft.OpenApi.Readers/YamlReaders/ParsingContext.cs @@ -5,11 +5,10 @@ using System.Collections.Generic; using System.Linq; -using Microsoft.OpenApi.Readers.Interface; namespace Microsoft.OpenApi.Readers.YamlReaders { - public class ParsingContext : ILog + public class ParsingContext { private readonly Stack currentLocation = new Stack(); @@ -20,11 +19,7 @@ public class ParsingContext : ILog private readonly Dictionary referenceStore = new Dictionary(); private readonly Dictionary tempStorage = new Dictionary(); - - public OpenApiDocument OpenApiDocument { get; set; } - - public IList Errors { get; set; } = new List(); - + public string Version { get; set; } public void EndObject() @@ -37,13 +32,13 @@ public string GetLocation() return "#/" + string.Join("/", currentLocation.Reverse().ToArray()); } - public IOpenApiReference GetReferencedObject(string pointer) + public IOpenApiReference GetReferencedObject(OpenApiDiagnostic diagnostic, string pointer) { var reference = referenceService.ParseReference(pointer); - return GetReferencedObject(reference); + return GetReferencedObject(diagnostic, reference); } - public IOpenApiReference GetReferencedObject(OpenApiReference reference) + public IOpenApiReference GetReferencedObject(OpenApiDiagnostic diagnostic, OpenApiReference reference) { IOpenApiReference returnValue = null; referenceStore.TryGetValue(reference.ToString(), out returnValue); @@ -58,6 +53,7 @@ public IOpenApiReference GetReferencedObject(OpenApiReference reference) previousPointers.Push(reference.ToString()); returnValue = referenceService.LoadReference(reference); previousPointers.Pop(); + if (returnValue != null) { returnValue.Pointer = reference; @@ -65,7 +61,7 @@ public IOpenApiReference GetReferencedObject(OpenApiReference reference) } else { - Errors.Add(new OpenApiError(GetLocation(), $"Cannot resolve $ref {reference}")); + diagnostic.Errors.Add(new OpenApiError(GetLocation(), $"Cannot resolve $ref {reference}")); } } diff --git a/src/Microsoft.OpenApi.Readers/YamlReaders/PropertyNode.cs b/src/Microsoft.OpenApi.Readers/YamlReaders/PropertyNode.cs deleted file mode 100644 index 1742afa30..000000000 --- a/src/Microsoft.OpenApi.Readers/YamlReaders/PropertyNode.cs +++ /dev/null @@ -1,77 +0,0 @@ -using SharpYaml.Serialization; -using System; -using System.Collections.Generic; -using System.Linq; - -namespace Microsoft.OpenApi.Readers.YamlReaders -{ - internal class PropertyNode : ParseNode - { - public PropertyNode(ParsingContext ctx, string name, YamlNode node) : base(ctx) - { - this.Name = name; - Value = YamlHelper.Create(ctx,node); - } - - - public string Name { get; set; } - public ParseNode Value { get; set; } - - - public void ParseField( - T parentInstance, - IDictionary> fixedFields, - IDictionary, Action> patternFields - ) - { - - Action fixedFieldMap; - var found = fixedFields.TryGetValue(this.Name, out fixedFieldMap); - - if (fixedFieldMap != null) - { - try - { - this.Context.StartObject(this.Name); - fixedFieldMap(parentInstance, this.Value); - } catch (OpenApiException ex) - { - ex.Pointer = this.Context.GetLocation(); - this.Context.Errors.Add(new OpenApiError(ex)); - } - finally - { - this.Context.EndObject(); - } - } - else - { - var map = patternFields.Where(p => p.Key(this.Name)).Select(p => p.Value).FirstOrDefault(); - if (map != null) - { - try - { - this.Context.StartObject(this.Name); - map(parentInstance, this.Name, this.Value); - } - catch (OpenApiException ex) - { - ex.Pointer = this.Context.GetLocation(); - this.Context.Errors.Add(new OpenApiError(ex)); - } - finally - { - this.Context.EndObject(); - } - } - else - { - this.Context.Errors.Add(new OpenApiError("", $"{this.Name} is not a valid property at {this.Context.GetLocation()}" )); - } - } - } - - } - - -} diff --git a/src/Microsoft.OpenApi.Readers/YamlReaders/ReferenceService.cs b/src/Microsoft.OpenApi.Readers/YamlReaders/ReferenceService.cs index 0bef85fc7..15b5196f9 100644 --- a/src/Microsoft.OpenApi.Readers/YamlReaders/ReferenceService.cs +++ b/src/Microsoft.OpenApi.Readers/YamlReaders/ReferenceService.cs @@ -3,17 +3,18 @@ namespace Microsoft.OpenApi.Readers.YamlReaders { - internal class ReferenceService : IOpenApiReferenceService + internal class OpenApiReferenceService : IOpenApiReferenceService { public Func loadReference { get; set; } public Func parseReference { get; set; } - private object rootNode; + private readonly object rootNode; - public ReferenceService(object rootNode) + public OpenApiReferenceService(object rootNode) { this.rootNode = rootNode; } + public IOpenApiReference LoadReference(OpenApiReference reference) { var referenceObject = this.loadReference(reference,this.rootNode); @@ -21,6 +22,7 @@ public IOpenApiReference LoadReference(OpenApiReference reference) { throw new OpenApiException($"Cannot locate $ref {reference.ToString()}"); } + return referenceObject; } @@ -29,28 +31,36 @@ public OpenApiReference ParseReference(string pointer) return this.parseReference(pointer); } - public static OpenApiSecurityScheme LoadSecuritySchemeByReference(ParsingContext context, string schemeName) + public static OpenApiSecurityScheme LoadSecuritySchemeByReference( + ParsingContext context, + OpenApiDiagnostic diagnostic, + string schemeName) { + var securitySchemeObject = (OpenApiSecurityScheme)context.GetReferencedObject( + diagnostic, + new OpenApiReference() + { + ReferenceType = ReferenceType.SecurityScheme, + TypeName = schemeName + }); - var schemeObject = (OpenApiSecurityScheme)context.GetReferencedObject(new OpenApiReference() - { - ReferenceType = ReferenceType.SecurityScheme, - TypeName = schemeName - }); - - return schemeObject; + return securitySchemeObject; } - - - public static OpenApiTag LoadTagByReference(ParsingContext context, string tagName) + + public static OpenApiTag LoadTagByReference( + ParsingContext context, + OpenApiDiagnostic diagnostic, + string tagName) { - - var tagObject = (OpenApiTag)context.GetReferencedObject($"#/tags/{tagName}"); + var tagObject = (OpenApiTag)context.GetReferencedObject( + diagnostic, + $"#/tags/{tagName}"); if (tagObject == null) { tagObject = new OpenApiTag() { Name = tagName }; } + return tagObject; } } diff --git a/src/Microsoft.OpenApi.Readers/YamlReaders/ValueNode.cs b/src/Microsoft.OpenApi.Readers/YamlReaders/ValueNode.cs deleted file mode 100644 index 31bf94fe8..000000000 --- a/src/Microsoft.OpenApi.Readers/YamlReaders/ValueNode.cs +++ /dev/null @@ -1,26 +0,0 @@ - -using SharpYaml.Serialization; - -namespace Microsoft.OpenApi.Readers.YamlReaders -{ - internal class ValueNode : ParseNode - { - YamlScalarNode node; - public ValueNode(ParsingContext ctx, YamlScalarNode scalarNode) : base(ctx) - { - this.node = scalarNode; - } - - public override string GetScalarValue() - { - - var scalarNode = this.node as YamlScalarNode; - if (scalarNode == null) throw new OpenApiException($"Expected scalar at line {node.Start.Line}"); - - return scalarNode.Value; - } - - } - - -} diff --git a/src/Microsoft.OpenApi.Readers/YamlReaders/YamlHelper.cs b/src/Microsoft.OpenApi.Readers/YamlReaders/YamlHelper.cs index 692b4a01d..0f94ddbf6 100644 --- a/src/Microsoft.OpenApi.Readers/YamlReaders/YamlHelper.cs +++ b/src/Microsoft.OpenApi.Readers/YamlReaders/YamlHelper.cs @@ -11,23 +11,6 @@ namespace Microsoft.OpenApi.Readers.YamlReaders { internal static class YamlHelper { - public static ParseNode Create(ParsingContext context, YamlNode node) - { - var listNode = node as YamlSequenceNode; - if (listNode != null) - { - return new ListNode(context, listNode); - } - - var mapNode = node as YamlMappingNode; - if (mapNode != null) - { - return new MapNode(context, mapNode); - } - - return new ValueNode(context, node as YamlScalarNode); - } - public static string GetScalarValue(this YamlNode node) { var scalarNode = node as YamlScalarNode; @@ -39,9 +22,9 @@ public static string GetScalarValue(this YamlNode node) return scalarNode.Value; } - public static YamlNode ParseYaml(string yaml) + public static YamlNode ParseYamlString(string yamlString) { - var reader = new StringReader(yaml); + var reader = new StringReader(yamlString); var yamlStream = new YamlStream(); yamlStream.Load(reader); var yamlDocument = yamlStream.Documents.First(); diff --git a/src/Microsoft.OpenApi.Workbench/MainModel.cs b/src/Microsoft.OpenApi.Workbench/MainModel.cs index c081c2f25..c64661efc 100644 --- a/src/Microsoft.OpenApi.Workbench/MainModel.cs +++ b/src/Microsoft.OpenApi.Workbench/MainModel.cs @@ -75,43 +75,40 @@ protected void OnPropertyChanged(string name) internal void Validate() { - try { + try + { - MemoryStream stream = CreateStream(input); + MemoryStream stream = CreateStream(input); + var stopwatch = new Stopwatch(); + stopwatch.Start(); - var stopwatch = new Stopwatch(); - stopwatch.Start(); - + var openApiDocument = new OpenApiStreamReader().Read(stream, out var context); + stopwatch.Stop(); + ParseTime = $"{stopwatch.ElapsedMilliseconds} ms"; - var openApiDoc = new OpenApiStreamReader().Read(stream, out var context); - stopwatch.Stop(); - ParseTime = $"{stopwatch.ElapsedMilliseconds} ms"; - - if (context.Errors.Count == 0) - { - Errors = "OK"; + if (context.Errors.Count == 0) + { + Errors = "OK"; - } - else - { - var errorReport = new StringBuilder(); - foreach (var error in context.Errors) + } + else { - errorReport.AppendLine(error.ToString()); + var errorReport = new StringBuilder(); + foreach (var error in context.Errors) + { + errorReport.AppendLine(error.ToString()); + } + Errors = errorReport.ToString(); } - Errors = errorReport.ToString(); - } - - stopwatch.Reset(); - stopwatch.Start(); - Output = WriteContents(context.OpenApiDocument); - stopwatch.Stop(); - RenderTime = $"{stopwatch.ElapsedMilliseconds} ms"; + stopwatch.Reset(); + stopwatch.Start(); + Output = WriteContents(openApiDocument); + stopwatch.Stop(); - - } + RenderTime = $"{stopwatch.ElapsedMilliseconds} ms"; + } catch (Exception ex) { Errors = "Failed to parse input: " + ex.Message; @@ -119,11 +116,8 @@ internal void Validate() // Verify output is valid JSON or YAML //var dummy = YamlHelper.ParseYaml(Output); - } - - - + private string WriteContents(OpenApiDocument doc) { Func writerFactory = s => (this.format == "Yaml" ? new OpenApiYamlWriter(new StreamWriter(s)) : (IOpenApiWriter)new OpenApiJsonWriter(new StreamWriter(s))); diff --git a/src/Microsoft.OpenApi/Abstracts/IOpenApiElement.cs b/src/Microsoft.OpenApi/Interfaces/IOpenApiElement.cs similarity index 100% rename from src/Microsoft.OpenApi/Abstracts/IOpenApiElement.cs rename to src/Microsoft.OpenApi/Interfaces/IOpenApiElement.cs diff --git a/src/Microsoft.OpenApi/Abstracts/IOpenApiExtension.cs b/src/Microsoft.OpenApi/Interfaces/IOpenApiExtension.cs similarity index 100% rename from src/Microsoft.OpenApi/Abstracts/IOpenApiExtension.cs rename to src/Microsoft.OpenApi/Interfaces/IOpenApiExtension.cs diff --git a/src/Microsoft.OpenApi/Abstracts/IOpenApiReference.cs b/src/Microsoft.OpenApi/Interfaces/IOpenApiReference.cs similarity index 100% rename from src/Microsoft.OpenApi/Abstracts/IOpenApiReference.cs rename to src/Microsoft.OpenApi/Interfaces/IOpenApiReference.cs diff --git a/src/Microsoft.OpenApi/Abstracts/IOpenApiReferenceService.cs b/src/Microsoft.OpenApi/Interfaces/IOpenApiReferenceService.cs similarity index 99% rename from src/Microsoft.OpenApi/Abstracts/IOpenApiReferenceService.cs rename to src/Microsoft.OpenApi/Interfaces/IOpenApiReferenceService.cs index a0baea3a6..5cec663f5 100644 --- a/src/Microsoft.OpenApi/Abstracts/IOpenApiReferenceService.cs +++ b/src/Microsoft.OpenApi/Interfaces/IOpenApiReferenceService.cs @@ -9,6 +9,7 @@ namespace Microsoft.OpenApi public interface IOpenApiReferenceService { IOpenApiReference LoadReference(OpenApiReference reference); + OpenApiReference ParseReference(string pointer); } } diff --git a/src/Microsoft.OpenApi/Sevices/OpenAPIDiff.cs b/src/Microsoft.OpenApi/Services/OpenAPIDiff.cs similarity index 100% rename from src/Microsoft.OpenApi/Sevices/OpenAPIDiff.cs rename to src/Microsoft.OpenApi/Services/OpenAPIDiff.cs diff --git a/src/Microsoft.OpenApi/Sevices/OpenApiValidator.cs b/src/Microsoft.OpenApi/Services/OpenApiValidator.cs similarity index 100% rename from src/Microsoft.OpenApi/Sevices/OpenApiValidator.cs rename to src/Microsoft.OpenApi/Services/OpenApiValidator.cs diff --git a/src/Microsoft.OpenApi/Sevices/OpenApiVisitorBase.cs b/src/Microsoft.OpenApi/Services/OpenApiVisitorBase.cs similarity index 100% rename from src/Microsoft.OpenApi/Sevices/OpenApiVisitorBase.cs rename to src/Microsoft.OpenApi/Services/OpenApiVisitorBase.cs diff --git a/src/Microsoft.OpenApi/Sevices/OpenApiWalker.cs b/src/Microsoft.OpenApi/Services/OpenApiWalker.cs similarity index 100% rename from src/Microsoft.OpenApi/Sevices/OpenApiWalker.cs rename to src/Microsoft.OpenApi/Services/OpenApiWalker.cs diff --git a/src/Microsoft.OpenApi/Writers/WriterExtensions.cs b/src/Microsoft.OpenApi/Writers/WriterExtensions.cs index 772078d35..d5466fe4f 100644 --- a/src/Microsoft.OpenApi/Writers/WriterExtensions.cs +++ b/src/Microsoft.OpenApi/Writers/WriterExtensions.cs @@ -19,99 +19,114 @@ public static void Save(this OpenApiDocument doc, Stream stream, IOpenApiStructu { openApiWriter = new OpenApiV3Writer(); } + openApiWriter.Write(stream, doc); } public static void WriteObject(this IOpenApiWriter writer, string propertyName, T entity, Action parser) { - if (entity != null) + if (entity == null) { - writer.WritePropertyName(propertyName); - parser(writer, entity); + return; } + writer.WritePropertyName(propertyName); + parser(writer, entity); } public static void WriteList(this IOpenApiWriter writer, string propertyName, IList list, Action parser) { - if (list != null && list.Any()) + if (list == null || !list.Any()) { - writer.WritePropertyName(propertyName); - writer.WriteStartArray(); - foreach (var item in list) - { - parser(writer,item); - } - writer.WriteEndArray(); + return; } + writer.WritePropertyName(propertyName); + writer.WriteStartArray(); + foreach (var item in list) + { + parser(writer,item); + } + + writer.WriteEndArray(); } public static void WriteMap(this IOpenApiWriter writer, string propertyName, IDictionary list, Action parser) { - if (list != null && list.Count() > 0) + if (list == null || !list.Any()) + { + return; + } + + writer.WritePropertyName(propertyName); + writer.WriteStartObject(); + foreach (var item in list) { - writer.WritePropertyName(propertyName); - writer.WriteStartObject(); - foreach (var item in list) + writer.WritePropertyName(item.Key); + if (item.Value != null) { - writer.WritePropertyName(item.Key); - if (item.Value != null) - { - parser(writer, item.Value); - } - else - { - writer.WriteNull(); - } + parser(writer, item.Value); + } + else + { + writer.WriteNull(); } - writer.WriteEndObject(); } - + writer.WriteEndObject(); } public static void WriteStringProperty(this IOpenApiWriter writer, string name, string value) { - if (!String.IsNullOrEmpty(value)) + if (string.IsNullOrEmpty(value)) { - writer.WritePropertyName(name); - writer.WriteValue(value); + return; } + + writer.WritePropertyName(name); + writer.WriteValue(value); } public static void WriteBoolProperty(this IOpenApiWriter writer, string name, bool value, bool? defaultValue = null) { - if (defaultValue == null || value != defaultValue) + if (defaultValue != null && value == defaultValue) { - writer.WritePropertyName(name); - writer.WriteValue(value); + return; } + + writer.WritePropertyName(name); + writer.WriteValue(value); } public static void WriteNumberProperty(this IOpenApiWriter writer, string name, decimal value, decimal? defaultValue = null) { - if (defaultValue == null || value != defaultValue) + if (defaultValue != null && value == defaultValue) { - writer.WritePropertyName(name); - writer.WriteValue(value); + return; } + + writer.WritePropertyName(name); + writer.WriteValue(value); } public static void WriteNumberProperty(this IOpenApiWriter writer, string name, int? value) { - if (value != null) + if (value == null) { - writer.WritePropertyName(name); - writer.WriteValue((int)value); + return; } + + writer.WritePropertyName(name); + writer.WriteValue((int)value); } public static void WriteNumberProperty(this IOpenApiWriter writer, string name, decimal? value) { - if (value != null) + if (value == null) { - writer.WritePropertyName(name); - writer.WriteValue((decimal)value); + return; } + + writer.WritePropertyName(name); + writer.WriteValue((decimal)value); } } diff --git a/test/Microsoft.OpenApi.Readers.Tests/BasicTests.cs b/test/Microsoft.OpenApi.Readers.Tests/BasicTests.cs index 3abb8befa..682b5e25f 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/BasicTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/BasicTests.cs @@ -1,74 +1,26 @@ -using Microsoft.OpenApi.Readers; -using SharpYaml.Serialization; -using System; -using System.Collections.Generic; +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + using System.IO; using System.Linq; using System.Text; -using System.Threading.Tasks; using Microsoft.OpenApi.Readers.YamlReaders; +using SharpYaml.Serialization; using Xunit; -using Microsoft.OpenApi.Readers.Interface; namespace Microsoft.OpenApi.Readers.Tests { public class BasicTests { - [Fact] - public void TestYamlBuilding() - { - var doc = new YamlDocument(new YamlMappingNode( - new YamlScalarNode("a"), new YamlScalarNode("apple"), - new YamlScalarNode("b"), new YamlScalarNode("banana"), - new YamlScalarNode("c"), new YamlScalarNode("cherry") - )); - - var s = new YamlStream(); - s.Documents.Add(doc); - var sb = new StringBuilder(); - var writer = new StringWriter(sb); - s.Save(writer); - var output = sb.ToString(); - } - - [Fact] - public void ParseSimplestOpenApiEver() - { - - var stream = this.GetType().Assembly.GetManifestResourceStream(typeof(BasicTests), "Samples.Simplest.yaml"); - - var openApiDoc = new OpenApiStreamReader().Read(stream, out var context); - - Assert.Equal("1.0.0", openApiDoc.Version); - Assert.Empty(openApiDoc.Paths); - Assert.Equal("The Api", openApiDoc.Info.Title); - Assert.Equal("0.9.1", openApiDoc.Info.Version.ToString()); - Assert.Empty(context.Errors); - } - - [Fact] - public void ParseBrokenSimplest() - { - - var stream = this.GetType().Assembly.GetManifestResourceStream(typeof(BasicTests),"Samples.BrokenSimplest.yaml"); - - var openApiDoc = new OpenApiStreamReader().Read(stream, out var context); - - Assert.Equal(2, context.Errors.Count); - Assert.NotNull(context.Errors.Where(s=> s.ToString() == "`openapi` property does not match the required format major.minor.patch at #/openapi").FirstOrDefault()); - Assert.NotNull(context.Errors.Where(s => s.ToString() == "title is a required property of #/info").FirstOrDefault()); - - } - [Fact] public void CheckOpenAPIVersion() { - - var stream = this.GetType().Assembly.GetManifestResourceStream(typeof(BasicTests), "Samples.petstore30.yaml"); + var stream = GetType().Assembly.GetManifestResourceStream(typeof(BasicTests), "Samples.petstore30.yaml"); var openApiDoc = new OpenApiStreamReader().Read(stream, out var context); Assert.Equal("3.0.0", openApiDoc.Version); - } [Fact] @@ -90,9 +42,59 @@ public void InlineExample() out var parsingContext); Assert.Equal("3.0.0", openApiDoc.Version); + } + + [Fact] + public void ParseBrokenSimplest() + { + var stream = GetType() + .Assembly.GetManifestResourceStream(typeof(BasicTests), "Samples.BrokenSimplest.yaml"); + + var openApiDoc = new OpenApiStreamReader().Read(stream, out var context); + + Assert.Equal(2, context.Errors.Count); + Assert.NotNull( + context.Errors.Where( + s => s.ToString() == + "`openapi` property does not match the required format major.minor.patch at #/openapi") + .FirstOrDefault()); + Assert.NotNull( + context.Errors.Where(s => s.ToString() == "title is a required property of #/info").FirstOrDefault()); + } + + [Fact] + public void ParseSimplestOpenApiEver() + { + var stream = GetType().Assembly.GetManifestResourceStream(typeof(BasicTests), "Samples.Simplest.yaml"); + + var openApiDoc = new OpenApiStreamReader().Read(stream, out var context); + Assert.Equal("1.0.0", openApiDoc.Version); + Assert.Empty(openApiDoc.Paths); + Assert.Equal("The Api", openApiDoc.Info.Title); + Assert.Equal("0.9.1", openApiDoc.Info.Version.ToString()); + Assert.Empty(context.Errors); } + [Fact] + public void TestYamlBuilding() + { + var doc = new YamlDocument( + new YamlMappingNode( + new YamlScalarNode("a"), + new YamlScalarNode("apple"), + new YamlScalarNode("b"), + new YamlScalarNode("banana"), + new YamlScalarNode("c"), + new YamlScalarNode("cherry") + )); + var s = new YamlStream(); + s.Documents.Add(doc); + var sb = new StringBuilder(); + var writer = new StringWriter(sb); + s.Save(writer); + var output = sb.ToString(); + } } -} +} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Readers.Tests/CallbackTests.cs b/test/Microsoft.OpenApi.Readers.Tests/CallbackTests.cs index 4e463f99f..170984636 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/CallbackTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/CallbackTests.cs @@ -1,41 +1,38 @@ -using System; -using System.Collections.Generic; +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Microsoft.OpenApi; -using Xunit; -using Microsoft.OpenApi.Readers; using Microsoft.OpenApi.Readers.YamlReaders; +using Xunit; namespace Microsoft.OpenApi.Readers.Tests { - public class CallbackTests { [Fact] public void LoadSimpleCallback() { - - var stream = this.GetType().Assembly.GetManifestResourceStream(typeof(CallbackTests),"Samples.CallbackSample.yaml"); + var stream = GetType() + .Assembly.GetManifestResourceStream(typeof(CallbackTests), "Samples.CallbackSample.yaml"); var openApiDoc = new OpenApiStreamReader().Read(stream, out var context); - OpenApiPathItem path = openApiDoc.Paths.First().Value; - OpenApiOperation subscribeOperation = path.Operations["post"]; + var path = openApiDoc.Paths.First().Value; + var subscribeOperation = path.Operations["post"]; var callback = subscribeOperation.Callbacks["mainHook"]; var pathItem = callback.PathItems.First().Value; var operation = pathItem.Operations["post"]; Assert.NotNull(operation); - } [Fact] public void LoadSimpleCallbackWithRefs() { - - var stream = this.GetType().Assembly.GetManifestResourceStream(typeof(CallbackTests), "Samples.CallbackSampleWithRef.yaml"); + var stream = GetType() + .Assembly.GetManifestResourceStream(typeof(CallbackTests), "Samples.CallbackSampleWithRef.yaml"); var openApiDoc = new OpenApiStreamReader().Read(stream, out var context); @@ -45,19 +42,17 @@ public void LoadSimpleCallbackWithRefs() var callbackPair = operation.Callbacks.First(); Assert.Equal("simplehook", callbackPair.Key); - OpenApiCallback callback = callbackPair.Value; + var callback = callbackPair.Value; var pathItemPair = callback.PathItems.First(); Assert.Equal("$request.body(/url)", pathItemPair.Key.Expression); - OpenApiPathItem pathItem = pathItemPair.Value; + var pathItem = pathItemPair.Value; var operationPair = pathItem.Operations.First(); - OpenApiOperation cboperation = operationPair.Value; + var cboperation = operationPair.Value; Assert.Equal("post", operationPair.Key); Assert.NotNull(callback); - } - } -} +} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Readers.Tests/DownGradeTests.cs b/test/Microsoft.OpenApi.Readers.Tests/DownGradeTests.cs index bbf6a3aba..f4be19a69 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/DownGradeTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/DownGradeTests.cs @@ -1,56 +1,62 @@ -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using System; +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + using System.Collections.Generic; using System.IO; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Microsoft.OpenApi; -using Xunit; -using Microsoft.OpenApi.Readers; using Microsoft.OpenApi.Readers.YamlReaders; using Microsoft.OpenApi.Writers; +using Newtonsoft.Json.Linq; +using Xunit; -namespace OpenApiTests +namespace Microsoft.OpenApi.Readers.Tests { - public class DownGradeTests + public class DowngradeTests { - public void SimpleTest() - { - var stream = this.GetType().Assembly.GetManifestResourceStream("OpenApiTests.Samples.Simplest.yaml"); - - var openApiDoc = new OpenApiStreamReader().Read(stream, out var context); - - var outputStream = new MemoryStream(); - openApiDoc.Save(outputStream, new OpenApiV2Writer()); - } - [Fact] public void EmptyTest() { var openApiDoc = new OpenApiDocument(); - JObject jObject = ExportV2ToJObject(openApiDoc); + var jObject = ExportV2ToJObject(openApiDoc); Assert.Equal("2.0", jObject["swagger"]); Assert.NotNull(jObject["info"]); - } + private static JObject ExportV2ToJObject(OpenApiDocument openApiDoc) + { + var outputStream = new MemoryStream(); + openApiDoc.Save(outputStream, new OpenApiV2Writer(s => new OpenApiJsonWriter(new StreamWriter(s)))); + outputStream.Position = 0; + var json = new StreamReader(outputStream).ReadToEnd(); + var jObject = JObject.Parse(json); + return jObject; + } [Fact] public void HostTest() { var openApiDoc = new OpenApiDocument(); - openApiDoc.Servers.Add(new OpenApiServer() { Url = "http://example.org/api" }); - openApiDoc.Servers.Add(new OpenApiServer() { Url = "https://example.org/api" }); + openApiDoc.Servers.Add(new OpenApiServer {Url = "http://example.org/api"}); + openApiDoc.Servers.Add(new OpenApiServer {Url = "https://example.org/api"}); - JObject jObject = ExportV2ToJObject(openApiDoc); + var jObject = ExportV2ToJObject(openApiDoc); Assert.Equal("example.org", (string)jObject["host"]); Assert.Equal("/api", (string)jObject["basePath"]); + } + [Fact] + public void SimpleTest() + { + var stream = GetType().Assembly.GetManifestResourceStream(typeof(DowngradeTests), "Samples.Simplest.yaml"); + + var openApiDoc = new OpenApiStreamReader().Read(stream, out var context); + + var outputStream = new MemoryStream(); + openApiDoc.Save(outputStream, new OpenApiV2Writer()); } [Fact] @@ -62,60 +68,68 @@ public void TestConsumes() { RequestBody = new OpenApiRequestBody { - Content = new Dictionary() { - { "application/vnd.collection+json", new OpenApiMediaType - { - } + Content = new Dictionary + { + { + "application/vnd.collection+json", new OpenApiMediaType() + } + } + }, + Responses = new Dictionary + { + { + "200", new OpenApiResponse + { + Description = "Success" } } - }, - Responses = new Dictionary { { "200", new OpenApiResponse() { - Description = "Success" - } } } + } }; pathItem.AddOperation(OperationType.Get, operation); openApiDoc.Paths.Add("/resource", pathItem); - JObject jObject = ExportV2ToJObject(openApiDoc); - - Assert.Equal("application/vnd.collection+json", (string)jObject["paths"]["/resource"]["get"]["consumes"][0]); + var jObject = ExportV2ToJObject(openApiDoc); + Assert.Equal( + "application/vnd.collection+json", + (string)jObject["paths"]["/resource"]["get"]["consumes"][0]); } [Fact] - public void TestRequestBody() + public void TestParameter() { var openApiDoc = new OpenApiDocument(); var pathItem = new OpenApiPathItem(); var operation = new OpenApiOperation { - RequestBody = new OpenApiRequestBody + Parameters = new List { - Content = new Dictionary() { - { "application/vnd.collection+json", new OpenApiMediaType - { - Schema = new OpenApiSchema - { - Type = "string", - MaxLength = 100 - } - } + new OpenApiParameter + { + Name = "param1", + In = ParameterLocation.query, + Schema = new OpenApiSchema + { + Type = "string" } } }, - Responses = new Dictionary { { "200", new OpenApiResponse() { - Description = "Success" - } } } + Responses = new Dictionary + { + { + "200", new OpenApiResponse + { + Description = "Success" + } + } + } }; - pathItem.AddOperation(OperationType.Post, operation); + pathItem.AddOperation(OperationType.Get, operation); openApiDoc.Paths.Add("/resource", pathItem); - JObject jObject = ExportV2ToJObject(openApiDoc); + var jObject = ExportV2ToJObject(openApiDoc); - var bodyparam = jObject["paths"]["/resource"]["post"]["parameters"][0]; - Assert.Equal("body", (string)bodyparam["in"]); - Assert.Equal("string", (string)bodyparam["schema"]["type"]); - Assert.Equal("100", (string)bodyparam["schema"]["maxLength"]); + Assert.Equal("string", (string)jObject["paths"]["/resource"]["get"]["parameters"][0]["type"]); } [Fact] @@ -125,69 +139,76 @@ public void TestProduces() var pathItem = new OpenApiPathItem(); var operation = new OpenApiOperation { - Responses = new Dictionary { { "200", new OpenApiResponse() { - Description = "Success", - Content = new Dictionary() { - { "application/vnd.collection+json", new OpenApiMediaType + Responses = new Dictionary + { + { + "200", new OpenApiResponse + { + Description = "Success", + Content = new Dictionary + { { - } - }, - { "text/plain", null } + "application/vnd.collection+json", new OpenApiMediaType() + }, + {"text/plain", null} + } + } } - - } } } + } }; pathItem.AddOperation(OperationType.Get, operation); openApiDoc.Paths.Add("/resource", pathItem); - JObject jObject = ExportV2ToJObject(openApiDoc); + var jObject = ExportV2ToJObject(openApiDoc); - Assert.Equal("application/vnd.collection+json", (string)jObject["paths"]["/resource"]["get"]["produces"][0]); + Assert.Equal( + "application/vnd.collection+json", + (string)jObject["paths"]["/resource"]["get"]["produces"][0]); Assert.Equal("text/plain", (string)jObject["paths"]["/resource"]["get"]["produces"][1]); - - } [Fact] - public void TestParameter() + public void TestRequestBody() { var openApiDoc = new OpenApiDocument(); var pathItem = new OpenApiPathItem(); var operation = new OpenApiOperation { - Parameters = new List { - new OpenApiParameter { - Name = "param1", - In = ParameterLocation.query, - Schema = new OpenApiSchema + RequestBody = new OpenApiRequestBody + { + Content = new Dictionary { - Type = "string" + { + "application/vnd.collection+json", new OpenApiMediaType + { + Schema = new OpenApiSchema + { + Type = "string", + MaxLength = 100 + } + } + } } - - } }, - Responses = new Dictionary { { "200", new OpenApiResponse() { - Description = "Success" - } } } + }, + Responses = new Dictionary + { + { + "200", new OpenApiResponse + { + Description = "Success" + } + } + } }; - pathItem.AddOperation(OperationType.Get, operation); + pathItem.AddOperation(OperationType.Post, operation); openApiDoc.Paths.Add("/resource", pathItem); - JObject jObject = ExportV2ToJObject(openApiDoc); - - Assert.Equal("string", (string)jObject["paths"]["/resource"]["get"]["parameters"][0]["type"]); - - } + var jObject = ExportV2ToJObject(openApiDoc); - private static JObject ExportV2ToJObject(OpenApiDocument openApiDoc) - { - var outputStream = new MemoryStream(); - openApiDoc.Save(outputStream, new OpenApiV2Writer((s) => new OpenApiJsonWriter(new StreamWriter(s)))); - outputStream.Position = 0; - var json = new StreamReader(outputStream).ReadToEnd(); - var jObject = JObject.Parse(json); - return jObject; + var bodyparam = jObject["paths"]["/resource"]["post"]["parameters"][0]; + Assert.Equal("body", (string)bodyparam["in"]); + Assert.Equal("string", (string)bodyparam["schema"]["type"]); + Assert.Equal("100", (string)bodyparam["schema"]["maxLength"]); } - - } -} +} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Readers.Tests/FixtureTests.cs b/test/Microsoft.OpenApi.Readers.Tests/FixtureTests.cs index 85b1a34c0..0a5d9bb41 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/FixtureTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/FixtureTests.cs @@ -1,79 +1,79 @@ -using Microsoft.OpenApi.Readers; -using Microsoft.OpenApi.Services; -using SharpYaml.Serialization; -using System; -using System.Collections.Generic; +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + using System.IO; using System.Linq; -using System.Text; -using System.Threading.Tasks; using Microsoft.OpenApi.Readers.YamlReaders; +using Microsoft.OpenApi.Readers.YamlReaders.ParseNodes; +using SharpYaml.Serialization; using Xunit; -namespace OpenApiTests +namespace Microsoft.OpenApi.Readers.Tests { public class FixtureTests { + private YamlNode LoadNode(string filePath) + { + using (var stream = new FileStream(filePath, FileMode.Open)) + { + var yamlStream = new YamlStream(); + yamlStream.Load(new StreamReader(stream)); + return yamlStream.Documents.First().RootNode; + } + } // Load json,yaml for both 2.0 and 3.0. Ensure resulting DOM is not empty and equivalent? // Load files from ../../fixtures/(v3.0|v2.0|v1.0 ???)/(json|yaml)/general/basicInfoObject.json [Fact] public void TestBasicInfoObject() { - var yamlNode = LoadNode("../../../../fixtures/v3.0/json/general/basicInfoObject.json"); - var ctx = new ParsingContext(); - var node = new MapNode(ctx, (YamlMappingNode)yamlNode); - var info = OpenApiV3Builder.LoadInfo(node); + var context = new ParsingContext(); + var diagnostic = new OpenApiDiagnostic(); + + var node = new MapNode(context, diagnostic, (YamlMappingNode)yamlNode); + var info = OpenApiV3Deserializer.LoadInfo(node); Assert.NotNull(info); Assert.Equal("Swagger Sample App", info.Title); Assert.Equal("1.0.1", info.Version.ToString()); Assert.Equal("support@swagger.io", info.Contact.Email); - Assert.Empty(ctx.Errors); + Assert.Empty(diagnostic.Errors); } [Fact] public void TestMinimalInfoObject() { - var yamlNode = LoadNode("../../../../fixtures/v3.0/json/general/minimalInfoObject.json"); - var ctx = new ParsingContext(); - var node = new MapNode(ctx, (YamlMappingNode)yamlNode); - var info = OpenApiV3Builder.LoadInfo(node); + var context = new ParsingContext(); + var diagnostic = new OpenApiDiagnostic(); + + var node = new MapNode(context, diagnostic, (YamlMappingNode)yamlNode); + var info = OpenApiV3Deserializer.LoadInfo(node); Assert.NotNull(info); Assert.Equal("Swagger Sample App", info.Title); Assert.Equal("1.0.1", info.Version.ToString()); - Assert.Empty(ctx.Errors); + Assert.Empty(diagnostic.Errors); } [Fact] public void TestNegativeInfoObject() { - var yamlNode = LoadNode("../../../../fixtures/v3.0/json/general/negative/negativeInfoObject.json"); - var ctx = new ParsingContext(); - var node = new MapNode(ctx, (YamlMappingNode)yamlNode); - var info = OpenApiV3Builder.LoadInfo(node); + var context = new ParsingContext(); + var diagnostic = new OpenApiDiagnostic(); - Assert.NotNull(info); - Assert.Equal(2, ctx.Errors.Count); - } + var node = new MapNode(context, diagnostic, (YamlMappingNode)yamlNode); + var info = OpenApiV3Deserializer.LoadInfo(node); - private YamlNode LoadNode(string filePath) - { - using (var stream = new FileStream(filePath, FileMode.Open)) - { - - var yamlStream = new YamlStream(); - yamlStream.Load(new StreamReader(stream)); - return yamlStream.Documents.First().RootNode; - } + Assert.NotNull(info); + Assert.Equal(2, diagnostic.Errors.Count); } } -} - +} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Readers.Tests/InfoTests.cs b/test/Microsoft.OpenApi.Readers.Tests/InfoTests.cs index 24b85002a..7ffb2d9fb 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/InfoTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/InfoTests.cs @@ -1,9 +1,10 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Xunit; -using Microsoft.OpenApi.Readers; +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + using Microsoft.OpenApi.Readers.YamlReaders; +using Xunit; namespace Microsoft.OpenApi.Readers.Tests { @@ -12,20 +13,22 @@ public class InfoTests [Fact] public void CheckPetStoreApiInfo() { - var stream = this.GetType().Assembly.GetManifestResourceStream(typeof(InfoTests), "Samples.petstore30.yaml"); + var stream = GetType().Assembly.GetManifestResourceStream(typeof(InfoTests), "Samples.petstore30.yaml"); var openApiDoc = new OpenApiStreamReader().Read(stream, out var context); var info = openApiDoc.Info; Assert.Equal("Swagger Petstore (Simple)", openApiDoc.Info.Title); - Assert.Equal("A sample API that uses a petstore as an example to demonstrate features in the swagger-2.0 specification", info.Description); + Assert.Equal( + "A sample API that uses a petstore as an example to demonstrate features in the swagger-2.0 specification", + info.Description); Assert.Equal("1.0.0", info.Version.ToString()); } - + [Fact] public void ParseCompleteHeaderOpenApi() { - var stream = this.GetType().Assembly.GetManifestResourceStream(typeof(InfoTests), "Samples.CompleteHeader.yaml"); + var stream = GetType().Assembly.GetManifestResourceStream(typeof(InfoTests), "Samples.CompleteHeader.yaml"); var openApiDoc = new OpenApiStreamReader().Read(stream, out var context); @@ -37,8 +40,7 @@ public void ParseCompleteHeaderOpenApi() Assert.Equal("This is an api", openApiDoc.Info.Description); Assert.Equal("http://example.org/Dowhatyouwant", openApiDoc.Info.TermsOfService); Assert.Equal("Darrel Miller", openApiDoc.Info.Contact.Name); - // Assert.Equal("@darrel_miller", openApiDoc.Info.Contact.Extensions["x-twitter"].GetValueNode().GetScalarValue()); + // Assert.Equal("@darrel_miller", openApiDoc.Info.Contact.Extensions["x-twitter"].GetValueNode().GetScalarValue()); } - } -} +} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Readers.Tests/JsonWriterTests.cs b/test/Microsoft.OpenApi.Readers.Tests/JsonWriterTests.cs index d1b24616c..11115121f 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/JsonWriterTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/JsonWriterTests.cs @@ -1,35 +1,17 @@ -using Newtonsoft.Json.Linq; -using System; -using System.Collections.Generic; +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + using System.IO; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Microsoft.OpenApi; -using Xunit; using Microsoft.OpenApi.Writers; +using Newtonsoft.Json.Linq; +using Xunit; -namespace OpenApiTests +namespace Microsoft.OpenApi.Readers.Tests { public class JsonWriterTests { - [Fact] - public void WriteMap() - { - var outputString = new StringWriter(); - var writer = new OpenApiJsonWriter(outputString); - writer.WriteStartObject(); - writer.WriteStringProperty("hello","world"); - writer.WriteStringProperty("good", "bye"); - writer.WriteEndObject(); - writer.Flush(); - - - var jObject = JObject.Parse(outputString.GetStringBuilder().ToString()); - - Assert.Equal("world", jObject["hello"]); - } - [Fact] public void WriteList() { @@ -47,27 +29,19 @@ public void WriteList() } [Fact] - public void WriteNestedMap() + public void WriteMap() { var outputString = new StringWriter(); var writer = new OpenApiJsonWriter(outputString); - writer.WriteStartObject(); - writer.WritePropertyName("intro"); - writer.WriteStartObject(); - writer.WriteStringProperty("hello", "world"); - writer.WriteEndObject(); - - writer.WritePropertyName("outro"); - writer.WriteStartObject(); - writer.WriteStringProperty("good", "bye"); - writer.WriteEndObject(); - - writer.WriteEndObject(); + writer.WriteStartObject(); + writer.WriteStringProperty("hello", "world"); + writer.WriteStringProperty("good", "bye"); + writer.WriteEndObject(); writer.Flush(); var jObject = JObject.Parse(outputString.GetStringBuilder().ToString()); - Assert.Equal("world", jObject["intro"]["hello"]); + Assert.Equal("world", jObject["hello"]); } [Fact] @@ -94,5 +68,28 @@ public void WriteNestedEmptyMap() Assert.Equal("bye", jObject["outro"]["good"]); } + [Fact] + public void WriteNestedMap() + { + var outputString = new StringWriter(); + var writer = new OpenApiJsonWriter(outputString); + writer.WriteStartObject(); + writer.WritePropertyName("intro"); + writer.WriteStartObject(); + writer.WriteStringProperty("hello", "world"); + writer.WriteEndObject(); + + writer.WritePropertyName("outro"); + writer.WriteStartObject(); + writer.WriteStringProperty("good", "bye"); + writer.WriteEndObject(); + + writer.WriteEndObject(); + writer.Flush(); + + var jObject = JObject.Parse(outputString.GetStringBuilder().ToString()); + + Assert.Equal("world", jObject["intro"]["hello"]); + } } -} +} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Readers.Tests/Microsoft.OpenApi.Readers.Tests.csproj b/test/Microsoft.OpenApi.Readers.Tests/Microsoft.OpenApi.Readers.Tests.csproj index e447c2449..50493bc79 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/Microsoft.OpenApi.Readers.Tests.csproj +++ b/test/Microsoft.OpenApi.Readers.Tests/Microsoft.OpenApi.Readers.Tests.csproj @@ -65,10 +65,10 @@ - + - + diff --git a/test/Microsoft.OpenApi.Readers.Tests/OAIExampleTests.cs b/test/Microsoft.OpenApi.Readers.Tests/OpenApiExampleTests.cs similarity index 68% rename from test/Microsoft.OpenApi.Readers.Tests/OAIExampleTests.cs rename to test/Microsoft.OpenApi.Readers.Tests/OpenApiExampleTests.cs index 479b3a19e..6c3fb2298 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/OAIExampleTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/OpenApiExampleTests.cs @@ -1,59 +1,61 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + +using System; using System.Net.Http; -using System.Text; using System.Threading.Tasks; -using Microsoft.OpenApi; -using Xunit; -using Microsoft.OpenApi.Readers; using Microsoft.OpenApi.Readers.YamlReaders; +using Xunit; -namespace OpenApiTests +namespace Microsoft.OpenApi.Readers.Tests { - public class OAIExampleTests + public class OpenApiExampleTests { - HttpClient client; - public OAIExampleTests() + private readonly HttpClient client; + + public OpenApiExampleTests() { client = new HttpClient(); - client.BaseAddress = new Uri("https://raw.githubusercontent.com/OAI/OpenAPI-Specification/OpenAPI.next/examples/v3.0/"); + client.BaseAddress = new Uri( + "https://raw.githubusercontent.com/OAI/OpenAPI-Specification/OpenAPI.next/examples/v3.0/"); } - [Fact] - public async Task SimplePetStore() + + [Fact(Skip = "Example is not updated yet")] + public async Task ApiWithExamples() { - var stream = await client.GetStreamAsync("petstore.yaml"); + var stream = await client.GetStreamAsync("api-with-examples.yaml"); var openApiDoc = new OpenApiStreamReader().Read(stream, out var context); Assert.Empty(context.Errors); } [Fact] - public async Task UberExample() + public async Task PetStoreExpandedExample() { - var stream = await client.GetStreamAsync("uber.yaml"); + var stream = await client.GetStreamAsync("petstore-expanded.yaml"); var openApiDoc = new OpenApiStreamReader().Read(stream, out var context); Assert.Empty(context.Errors); } [Fact] - public async Task PetStoreExpandedExample() + public async Task SimplePetStore() { - var stream = await client.GetStreamAsync("petstore-expanded.yaml"); + var stream = await client.GetStreamAsync("petstore.yaml"); var openApiDoc = new OpenApiStreamReader().Read(stream, out var context); Assert.Empty(context.Errors); } - [Fact(Skip = "Example is not updated yet")] - public async Task ApiWithExamples() + [Fact] + public async Task UberExample() { - var stream = await client.GetStreamAsync("api-with-examples.yaml"); + var stream = await client.GetStreamAsync("uber.yaml"); var openApiDoc = new OpenApiStreamReader().Read(stream, out var context); Assert.Empty(context.Errors); } } -} +} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Readers.Tests/OpenApiReferenceTests.cs b/test/Microsoft.OpenApi.Readers.Tests/OpenApiReferenceTests.cs index 58d2ac4ca..14a665f1a 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/OpenApiReferenceTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/OpenApiReferenceTests.cs @@ -1,27 +1,32 @@ - +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ using Microsoft.OpenApi.Readers.YamlReaders; +using Xunit; -namespace OpenApiTests +namespace Microsoft.OpenApi.Readers.Tests { - using System; - using System.Collections.Generic; - using System.Linq; - using System.Text; - using System.Threading.Tasks; - using Microsoft.OpenApi; - using Xunit; - using Microsoft.OpenApi.Readers; - public class OpenApiReferenceTests { + [Fact] + public void ParseExternalHeaderReference() + { + var reference = new OpenApiReference("externalschema.json#/components/headers/blah"); + + Assert.Equal(ReferenceType.Header, reference.ReferenceType); + Assert.Equal("externalschema.json", reference.ExternalFilePath); + Assert.Equal("blah", reference.TypeName); + } + [Fact] public void ParseLocalParameterReference() { var reference = new OpenApiReference("#/components/parameters/foobar"); Assert.Equal(ReferenceType.Parameter, reference.ReferenceType); - Assert.Equal(String.Empty, reference.ExternalFilePath); + Assert.Equal(string.Empty, reference.ExternalFilePath); Assert.Equal("foobar", reference.TypeName); } @@ -31,36 +36,24 @@ public void ParseLocalSchemaReference() var reference = new OpenApiReference("foobar"); Assert.Equal(ReferenceType.Schema, reference.ReferenceType); - Assert.Equal(String.Empty, reference.ExternalFilePath); + Assert.Equal(string.Empty, reference.ExternalFilePath); Assert.Equal("foobar", reference.TypeName); } [Fact] - public void ParseExternalHeaderReference() - { - var reference = new OpenApiReference("externalschema.json#/components/headers/blah"); - - Assert.Equal(ReferenceType.Header, reference.ReferenceType); - Assert.Equal("externalschema.json", reference.ExternalFilePath); - Assert.Equal("blah", reference.TypeName); - } - - [Fact] - public void TranslateV2Reference() + public void TranslateV2ExternalReference() { - - var reference = OpenApiV2Builder.ParseReference("#/definitions/blahblah"); + var reference = OpenApiV2Deserializer.ParseReference("swagger.json#/parameters/blahblah"); - Assert.Equal(ReferenceType.Schema, reference.ReferenceType); - Assert.Equal(string.Empty, reference.ExternalFilePath); + Assert.Equal(ReferenceType.Parameter, reference.ReferenceType); + Assert.Equal("swagger.json", reference.ExternalFilePath); Assert.Equal("blahblah", reference.TypeName); } [Fact] public void TranslateV2LocalReference() { - - var reference = OpenApiV2Builder.ParseReference("blahblah"); + var reference = OpenApiV2Deserializer.ParseReference("blahblah"); Assert.Equal(ReferenceType.Schema, reference.ReferenceType); Assert.Equal(string.Empty, reference.ExternalFilePath); @@ -68,14 +61,13 @@ public void TranslateV2LocalReference() } [Fact] - public void TranslateV2ExternalReference() + public void TranslateV2Reference() { + var reference = OpenApiV2Deserializer.ParseReference("#/definitions/blahblah"); - var reference = OpenApiV2Builder.ParseReference("swagger.json#/parameters/blahblah"); - - Assert.Equal(ReferenceType.Parameter, reference.ReferenceType); - Assert.Equal("swagger.json", reference.ExternalFilePath); + Assert.Equal(ReferenceType.Schema, reference.ReferenceType); + Assert.Equal(string.Empty, reference.ExternalFilePath); Assert.Equal("blahblah", reference.TypeName); } } -} +} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Readers.Tests/OperationTests.cs b/test/Microsoft.OpenApi.Readers.Tests/OperationTests.cs index 2bbb467e7..fd6623111 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/OperationTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/OperationTests.cs @@ -1,24 +1,23 @@ -using System; -using System.Collections.Generic; +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Microsoft.OpenApi; -using Xunit; -using Microsoft.OpenApi.Readers; using Microsoft.OpenApi.Readers.YamlReaders; +using Xunit; namespace Microsoft.OpenApi.Readers.Tests { public class OperationTests { - private OpenApiDocument _PetStoreDoc; - // private Operation _PostOperation; + private readonly OpenApiDocument _PetStoreDoc; + // private Operation _PostOperation; public OperationTests() { - - var stream = this.GetType().Assembly.GetManifestResourceStream(typeof(OperationTests), "Samples.petstore30.yaml"); + var stream = GetType() + .Assembly.GetManifestResourceStream(typeof(OperationTests), "Samples.petstore30.yaml"); _PetStoreDoc = new OpenApiStreamReader().Read(stream, out var context); //_PostOperation = _PetStoreDoc.Paths.PathMap.Where(pm=>pm.Key == "/pets").Value // .Operations.Where() @@ -37,7 +36,6 @@ public void CheckPetStoreFirstOperation() [Fact] public void CheckPetStoreFirstOperationParameters() { - var firstPath = _PetStoreDoc.Paths.First().Value; var firstOperation = firstPath.Operations.First().Value; var firstParameter = firstOperation.Parameters.First(); @@ -50,25 +48,28 @@ public void CheckPetStoreFirstOperationParameters() [Fact] public void CheckPetStoreFirstOperationRequest() { - var firstPath = _PetStoreDoc.Paths.First().Value; var firstOperation = firstPath.Operations.First().Value; var requestBody = firstOperation.RequestBody; - Assert.Null(firstOperation.RequestBody); } [Fact] - public void GetPostOperation() + public void DoesAPathExist() { + Assert.Equal(2, _PetStoreDoc.Paths.Count()); + Assert.NotNull(_PetStoreDoc.Paths["/pets"]); + } + [Fact] + public void GetPostOperation() + { var postOperation = _PetStoreDoc.Paths["/pets"].Operations["post"]; Assert.Equal("addPet", postOperation.OperationId); Assert.NotNull(postOperation); - } [Fact] @@ -77,24 +78,12 @@ public void GetResponses() var getOperation = _PetStoreDoc.Paths["/pets"].Operations["get"]; var responses = getOperation.Responses; - + Assert.Equal(2, responses["200"].Content.Values.Count()); var response = getOperation.Responses["200"]; var content = response.Content["application/json"]; Assert.NotNull(content.Schema); } - - - - - [Fact] - public void DoesAPathExist() - { - - Assert.Equal(2, _PetStoreDoc.Paths.Count()); - Assert.NotNull(_PetStoreDoc.Paths["/pets"]); - } - } -} +} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Readers.Tests/ParameterTests.cs b/test/Microsoft.OpenApi.Readers.Tests/ParameterTests.cs index 5d90e736b..e971aba30 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/ParameterTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/ParameterTests.cs @@ -1,70 +1,130 @@ -using Microsoft.OpenApi; +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Text; -using System.Threading.Tasks; using Xunit; -namespace OpenApiTests +namespace Microsoft.OpenApi.Readers.Tests { public class ParameterTests { + private const string _UriReservedSymbols = ":/?#[]@!$&'()*+,;="; + private const string _UriUnreservedSymbols = "-._~"; - [Theory, - InlineData("foo","foo")] - public void SerializeStrings(string value, string expected) + private static readonly char[] HexDigits = { - var parameter = new OpenApiParameter() { + '0', + '1', + '2', + '3', + '4', + '5', + '6', + '7', + '8', + '9', + 'A', + 'B', + 'C', + 'D', + 'E', + 'F' + }; - }; + private static string Encode(string p, bool allowReserved) + { + var result = new StringBuilder(); + foreach (var c in p) + { + if ((c >= 'A' && c <= 'z') //Alpha + || + (c >= '0' && c <= '9') // Digit + || + _UriUnreservedSymbols.IndexOf(c) != + -1 // Unreserved symbols - These should never be percent encoded + || + (allowReserved && _UriReservedSymbols.IndexOf(c) != -1) + ) // Reserved symbols - should be included if requested (+) + { + result.Append(c); + } + else + { + var bytes = Encoding.UTF8.GetBytes(new[] {c}); + foreach (var abyte in bytes) + { + result.Append(HexEscape(abyte)); + } + } + } - var actual = SerializeParameterValue(parameter,value); - Assert.Equal(expected, actual); + return result.ToString(); } - [Theory, - InlineData("://foo/bar?", "://foo/bar?"), - InlineData("foo bar", "foo%20bar")] - public void SerializeEscapedStrings(string value, string expected) + public static string HexEscape(byte i) { - var parameter = new OpenApiParameter() - { - AllowReserved = false - }; + var esc = new char[3]; + esc[0] = '%'; + esc[1] = HexDigits[((i & 240) >> 4)]; + esc[2] = HexDigits[(i & 15)]; + return new string(esc); + } - var actual = SerializeParameterValue(parameter, value); - Assert.Equal(expected, actual); + public static string HexEscape(char c) + { + var esc = new char[3]; + esc[0] = '%'; + esc[1] = HexDigits[((c & 240) >> 4)]; + esc[2] = HexDigits[(c & 15)]; + return new string(esc); + } + + private bool IsArray(object value) + { + return value is IEnumerable; } - [Theory, - InlineData("label","yo", ".yo"), - InlineData("matrix", "x", ";foo=x"), - InlineData("matrix", "", ";foo")] - public void SerializePrefixedStrings(string style,string value, string expected) + private bool IsMap(object value) { - var parameter = new OpenApiParameter() + return value is IDictionary; + } + + private bool IsSimple(object value) + { + return value is string; + } + + [Theory] + [InlineData("matrix", new[] {"a", "b"}, ";bar=a,b")] + [InlineData("exploded-matrix", new[] {"a", "b"}, ";bar=a;bar=b")] + [InlineData("label", new[] {"a", "b"}, ".a.b")] + [InlineData("exploded-label", new[] {"a", "b"}, ".a.b")] + public void SerializeArrays(string style, string[] value, string expected) + { + var parameter = new OpenApiParameter { - Name = "foo", + Name = "bar", Style = style }; - + var actual = SerializeParameterValue(parameter, value); Assert.Equal(expected, actual); } - [Theory, - InlineData("matrix",new[] { "a", "b" }, ";bar=a,b"), - InlineData("exploded-matrix", new[] { "a", "b" }, ";bar=a;bar=b"), - InlineData("label", new[] { "a", "b" }, ".a.b"), - InlineData("exploded-label", new[] { "a", "b" }, ".a.b")] - public void SerializeArrays(string style, string[] value, string expected) + [Theory] + [InlineData("://foo/bar?", "://foo/bar?")] + [InlineData("foo bar", "foo%20bar")] + public void SerializeEscapedStrings(string value, string expected) { - var parameter = new OpenApiParameter() + var parameter = new OpenApiParameter { - Name = "bar", - Style = style + AllowReserved = false }; var actual = SerializeParameterValue(parameter, value); @@ -74,17 +134,15 @@ public void SerializeArrays(string style, string[] value, string expected) // format="space-delimited" type="array" explode=false bar=a b // format="space-delimited" type="array" explode=true bar=a bar=b - - - [Theory, - InlineData("matrix", ";bar=a,1,b,2"), - InlineData("exploded-matrix", ";a=1;b=2"), - InlineData("label", ".a.1.b.2"), - InlineData("exploded-label", ".a=1.b=2")] + [Theory] + [InlineData("matrix", ";bar=a,1,b,2")] + [InlineData("exploded-matrix", ";a=1;b=2")] + [InlineData("label", ".a.1.b.2")] + [InlineData("exploded-label", ".a=1.b=2")] public void SerializeMaps(string style, string expected) { - var value = new Dictionary { { "a", "1" }, { "b", "2" } }; - var parameter = new OpenApiParameter() + var value = new Dictionary {{"a", "1"}, {"b", "2"}}; + var parameter = new OpenApiParameter { Name = "bar", Style = style @@ -93,44 +151,107 @@ public void SerializeMaps(string style, string expected) var actual = SerializeParameterValue(parameter, value); Assert.Equal(expected, actual); } + // Is it possible to write code based on the information in Parameter // to serialize the value like RFC6570 does! - private string SerializeParameterValue(OpenApiParameter parameter, object value ) + private string SerializeParameterValue(OpenApiParameter parameter, object value) { string output; - + switch (parameter.Style) { - case "matrix": // Matrix - output = SerializeValues(parameter.Name, false,parameter.AllowReserved,value, (n,v,m) => ";" + n + (string.IsNullOrEmpty(v) ? "" : "=") + v, ","); + case "matrix": // Matrix + output = SerializeValues( + parameter.Name, + false, + parameter.AllowReserved, + value, + (n, v, m) => ";" + n + (string.IsNullOrEmpty(v) ? "" : "=") + v, + ","); break; - case "exploded-matrix": // Matrix - output = SerializeValues(parameter.Name, true, parameter.AllowReserved, value, (n, v, m) => ";" + n + (string.IsNullOrEmpty(v) ? "" : "=") + v, ","); + case "exploded-matrix": // Matrix + output = SerializeValues( + parameter.Name, + true, + parameter.AllowReserved, + value, + (n, v, m) => ";" + n + (string.IsNullOrEmpty(v) ? "" : "=") + v, + ","); break; - case "label": // Label - output = SerializeValues(parameter.Name,false, parameter.AllowReserved, value, (n,v,m) => "." + ( m ? n + "=" : "") + v , "."); + case "label": // Label + output = SerializeValues( + parameter.Name, + false, + parameter.AllowReserved, + value, + (n, v, m) => "." + (m ? n + "=" : "") + v, + "."); break; - case "exploded-label": // Label - output = SerializeValues(parameter.Name, true, parameter.AllowReserved, value, (n, v, m) => "." + (m ? n + "=" : "") + v, "."); + case "exploded-label": // Label + output = SerializeValues( + parameter.Name, + true, + parameter.AllowReserved, + value, + (n, v, m) => "." + (m ? n + "=" : "") + v, + "."); break; default: // Simple - output = SerializeValues(parameter.Name, false, parameter.AllowReserved, value, (n, v,m) => v, ","); + output = SerializeValues( + parameter.Name, + false, + parameter.AllowReserved, + value, + (n, v, m) => v, + ","); break; - } + return output; } - private string SerializeValues(string name, bool explode, bool allowReserved, object value, Func renderValue, string seperator) + [Theory] + [InlineData("label", "yo", ".yo")] + [InlineData("matrix", "x", ";foo=x")] + [InlineData("matrix", "", ";foo")] + public void SerializePrefixedStrings(string style, string value, string expected) + { + var parameter = new OpenApiParameter + { + Name = "foo", + Style = style + }; + + var actual = SerializeParameterValue(parameter, value); + Assert.Equal(expected, actual); + } + + [Theory] + [InlineData("foo", "foo")] + public void SerializeStrings(string value, string expected) + { + var parameter = new OpenApiParameter(); + + var actual = SerializeParameterValue(parameter, value); + Assert.Equal(expected, actual); + } + + private string SerializeValues( + string name, + bool explode, + bool allowReserved, + object value, + Func renderValue, + string seperator) { string output = null; if (IsSimple(value)) { var stringValue = (string)value; - output = renderValue(name, stringValue,false); + output = renderValue(name, stringValue, false); } else if (IsMap(value)) { @@ -139,97 +260,36 @@ private string SerializeValues(string name, bool explode, bool allowReserved, ob { foreach (var item in mapValue) { - output += renderValue(item.Key, item.Value,true); + output += renderValue(item.Key, item.Value, true); } } else { - output = renderValue(name, string.Join(seperator, mapValue.Select(kvp => kvp.Key + (explode ? "=" : seperator) + kvp.Value).ToList()),false); + output = renderValue( + name, + string.Join( + seperator, + mapValue.Select(kvp => kvp.Key + (explode ? "=" : seperator) + kvp.Value).ToList()), + false); } } - else if (IsArray(value)) { + else if (IsArray(value)) + { var arrayValue = (string[])value; - if (explode) { - foreach(var itemValue in arrayValue) + if (explode) + { + foreach (var itemValue in arrayValue) { - output += renderValue(name, itemValue,false); + output += renderValue(name, itemValue, false); } } else { - output = renderValue(name, string.Join(seperator, arrayValue),false); + output = renderValue(name, string.Join(seperator, arrayValue), false); } } return Encode(output, !allowReserved); - - } - - private bool IsMap(object value) - { - return value is IDictionary; - } - - private bool IsArray(object value) - { - return value is IEnumerable; - } - - private bool IsSimple(object value) - { - return value is string; - } - - - private const string _UriReservedSymbols = ":/?#[]@!$&'()*+,;="; - private const string _UriUnreservedSymbols = "-._~"; - private static string Encode(string p, bool allowReserved) - { - - var result = new StringBuilder(); - foreach (char c in p) - { - if ((c >= 'A' && c <= 'z') //Alpha - || (c >= '0' && c <= '9') // Digit - || _UriUnreservedSymbols.IndexOf(c) != -1 // Unreserved symbols - These should never be percent encoded - || (allowReserved && _UriReservedSymbols.IndexOf(c) != -1)) // Reserved symbols - should be included if requested (+) - { - result.Append(c); - } - else - { - var bytes = Encoding.UTF8.GetBytes(new[] { c }); - foreach (var abyte in bytes) - { - result.Append(HexEscape(abyte)); - } - - } - } - - return result.ToString(); - - - } - - public static string HexEscape(byte i) - { - var esc = new char[3]; - esc[0] = '%'; - esc[1] = HexDigits[((i & 240) >> 4)]; - esc[2] = HexDigits[(i & 15)]; - return new string(esc); } - public static string HexEscape(char c) - { - var esc = new char[3]; - esc[0] = '%'; - esc[1] = HexDigits[(((int)c & 240) >> 4)]; - esc[2] = HexDigits[((int)c & 15)]; - return new string(esc); - } - private static readonly char[] HexDigits = new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; - - } -} +} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Readers.Tests/SchemaTests.cs b/test/Microsoft.OpenApi.Readers.Tests/SchemaTests.cs index f59e5d58b..3aa4c738f 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/SchemaTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/SchemaTests.cs @@ -1,31 +1,26 @@ -using SharpYaml.Serialization; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Microsoft.OpenApi; +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + using Microsoft.OpenApi.Readers.YamlReaders; +using Microsoft.OpenApi.Readers.YamlReaders.ParseNodes; using Xunit; - namespace Microsoft.OpenApi.Readers.Tests { public class SchemaTests { - [Fact] public void CheckPetStoreApiInfo() { - var stream = this.GetType().Assembly.GetManifestResourceStream(typeof(SchemaTests), "Samples.petstore30.yaml"); + var stream = GetType().Assembly.GetManifestResourceStream(typeof(SchemaTests), "Samples.petstore30.yaml"); var openApiDoc = new OpenApiStreamReader().Read(stream, out var context); var operation = openApiDoc.Paths["/pets"].Operations["get"]; var schema = operation.Responses["200"].Content["application/json"].Schema; Assert.NotNull(schema); - } [Fact] @@ -33,12 +28,15 @@ public void CreateSchemaFromInlineJsonSchema() { var jsonSchema = " { \"type\" : \"int\" } "; - var mapNode = MapNode.Create(jsonSchema); + var context = new ParsingContext(); + var diagnostic = new OpenApiDiagnostic(); + + var mapNode = new MapNode(context, diagnostic, jsonSchema); - var schema = OpenApiV3Builder.LoadSchema(mapNode); + var schema = OpenApiV3Deserializer.LoadSchema(mapNode); Assert.NotNull(schema); Assert.Equal("int", schema.Type); } } -} +} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Readers.Tests/YamlWriterTests.cs b/test/Microsoft.OpenApi.Readers.Tests/YamlWriterTests.cs index fcde38f13..c6ab44d5e 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/YamlWriterTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/YamlWriterTests.cs @@ -1,46 +1,42 @@ -using Microsoft.OpenApi; -using Microsoft.OpenApi.Writers; -using System; -using System.Collections.Generic; +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + using System.IO; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using Microsoft.OpenApi.Writers; using Xunit; -namespace OpenApiTests +namespace Microsoft.OpenApi.Readers.Tests { public class YamlWriterTests { [Fact] - public void WriteMap() + public void WriteList() { var outputString = new StringWriter(); var writer = new OpenApiYamlWriter(outputString); + writer.WriteStartArray(); writer.WriteStartObject(); writer.WriteEndObject(); - + writer.WriteEndArray(); //Assert.Equal(0, debug.StackState.Count); //Assert.Equal("", debug.Indent); } [Fact] - public void WriteList() + public void WriteMap() { var outputString = new StringWriter(); var writer = new OpenApiYamlWriter(outputString); - writer.WriteStartArray(); writer.WriteStartObject(); writer.WriteEndObject(); - writer.WriteEndArray(); //Assert.Equal(0, debug.StackState.Count); //Assert.Equal("", debug.Indent); } - - - } } +} \ No newline at end of file