diff --git a/src/Microsoft.OpenApi.Readers/Exceptions/OpenApiReaderException.cs b/src/Microsoft.OpenApi.Readers/Exceptions/OpenApiReaderException.cs index 03f80c93e..e90137ad3 100644 --- a/src/Microsoft.OpenApi.Readers/Exceptions/OpenApiReaderException.cs +++ b/src/Microsoft.OpenApi.Readers/Exceptions/OpenApiReaderException.cs @@ -24,6 +24,15 @@ public OpenApiReaderException() { } /// Plain text error message for this exception. public OpenApiReaderException(string message) : base(message) { } + /// + /// Initializes the class with a custom message. + /// + /// Plain text error message for this exception. + /// Context of current parsing process. + public OpenApiReaderException(string message, ParsingContext context) : base(message) { + Pointer = context.GetLocation(); + } + /// /// Initializes the class with a message and line, column location of error. /// @@ -42,5 +51,6 @@ public OpenApiReaderException(string message, YamlNode node) : base(message) /// Plain text error message for this exception. /// Inner exception that caused this exception to be thrown. public OpenApiReaderException(string message, Exception innerException) : base(message, innerException) { } + } } diff --git a/src/Microsoft.OpenApi.Readers/ParseNodes/ListNode.cs b/src/Microsoft.OpenApi.Readers/ParseNodes/ListNode.cs index e1149cc5a..d11ff4c04 100644 --- a/src/Microsoft.OpenApi.Readers/ParseNodes/ListNode.cs +++ b/src/Microsoft.OpenApi.Readers/ParseNodes/ListNode.cs @@ -6,9 +6,7 @@ using System.Collections.Generic; using System.Linq; using Microsoft.OpenApi.Any; -using Microsoft.OpenApi.Exceptions; -using Microsoft.OpenApi.Interfaces; -using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Readers.Exceptions; using SharpYaml.Serialization; namespace Microsoft.OpenApi.Readers.ParseNodes @@ -27,8 +25,8 @@ public override List CreateList(Func map) { if (_nodeList == null) { - throw new OpenApiException( - $"Expected list at line {_nodeList.Start.Line} while parsing {typeof(T).Name}"); + throw new OpenApiReaderException( + $"Expected list at line {_nodeList.Start.Line} while parsing {typeof(T).Name}", _nodeList); } return _nodeList.Select(n => map(new MapNode(Context, n as YamlMappingNode))) @@ -47,8 +45,8 @@ public override List CreateSimpleList(Func map) { if (_nodeList == null) { - throw new OpenApiException( - $"Expected list at line {_nodeList.Start.Line} while parsing {typeof(T).Name}"); + throw new OpenApiReaderException( + $"Expected list at line {_nodeList.Start.Line} while parsing {typeof(T).Name}", _nodeList); } return _nodeList.Select(n => map(new ValueNode(Context, n))).ToList(); diff --git a/src/Microsoft.OpenApi.Readers/ParseNodes/MapNode.cs b/src/Microsoft.OpenApi.Readers/ParseNodes/MapNode.cs index 26fc81076..f1a9c2cf3 100644 --- a/src/Microsoft.OpenApi.Readers/ParseNodes/MapNode.cs +++ b/src/Microsoft.OpenApi.Readers/ParseNodes/MapNode.cs @@ -6,7 +6,6 @@ using System.Collections.Generic; using System.Linq; using Microsoft.OpenApi.Any; -using Microsoft.OpenApi.Exceptions; using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Readers.Exceptions; @@ -33,7 +32,7 @@ public MapNode(ParsingContext context, YamlNode node) : base( { if (!(node is YamlMappingNode mapNode)) { - throw new OpenApiReaderException("Expected map.", node); + throw new OpenApiReaderException("Expected map.", Context); } this._node = mapNode; @@ -48,10 +47,10 @@ public PropertyNode this[string key] { get { - YamlNode node = null; + YamlNode node; if (this._node.Children.TryGetValue(new YamlScalarNode(key), out node)) { - return new PropertyNode(Context, key, this._node.Children[new YamlScalarNode(key)]); + return new PropertyNode(Context, key, node); } return null; @@ -63,16 +62,30 @@ 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}"); + throw new OpenApiReaderException($"Expected map while parsing {typeof(T).Name}", Context); } var nodes = yamlMap.Select( - n => new - { - key = n.Key.GetScalarValue(), - value = n.Value as YamlMappingNode == null - ? default(T) - : map(new MapNode(Context, n.Value as YamlMappingNode)) + n => { + + var key = n.Key.GetScalarValue(); + T value; + try + { + Context.StartObject(key); + value = n.Value as YamlMappingNode == null + ? default(T) + : map(new MapNode(Context, n.Value as YamlMappingNode)); + } + finally + { + Context.EndObject(); + } + return new + { + key = key, + value = value + }; }); return nodes.ToDictionary(k => k.key, v => v.value); @@ -85,7 +98,7 @@ public override Dictionary CreateMapWithReference( var yamlMap = _node; if (yamlMap == null) { - throw new OpenApiException($"Expected map at line {yamlMap.Start.Line} while parsing {typeof(T).Name}"); + throw new OpenApiReaderException($"Expected map while parsing {typeof(T).Name}", Context); } var nodes = yamlMap.Select( @@ -119,8 +132,8 @@ 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}"); + { + throw new OpenApiReaderException($"Expected map while parsing {typeof(T).Name}", Context); } var nodes = yamlMap.Select( @@ -175,7 +188,7 @@ 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()}"); + throw new OpenApiReaderException($"Expected scalar at line {_node.Start.Line} for key {key.GetScalarValue()}", Context); } return scalarNode.Value; diff --git a/src/Microsoft.OpenApi.Readers/ParseNodes/ParseNode.cs b/src/Microsoft.OpenApi.Readers/ParseNodes/ParseNode.cs index 25f0eabc1..295b02bf3 100644 --- a/src/Microsoft.OpenApi.Readers/ParseNodes/ParseNode.cs +++ b/src/Microsoft.OpenApi.Readers/ParseNodes/ParseNode.cs @@ -26,7 +26,7 @@ public MapNode CheckMapNode(string nodeName) { if (!(this is MapNode mapNode)) { - throw new OpenApiReaderException($"{nodeName} must be a map/object"); + throw new OpenApiReaderException($"{nodeName} must be a map/object", Context); } return mapNode; @@ -50,12 +50,12 @@ public static ParseNode Create(ParsingContext context, YamlNode node) public virtual List CreateList(Func map) { - throw new OpenApiReaderException("Cannot create list from this type of node."); + throw new OpenApiReaderException("Cannot create list from this type of node.", Context); } public virtual Dictionary CreateMap(Func map) { - throw new OpenApiReaderException("Cannot create map from this type of node."); + throw new OpenApiReaderException("Cannot create map from this type of node.", Context); } public virtual Dictionary CreateMapWithReference( @@ -63,38 +63,37 @@ public virtual Dictionary CreateMapWithReference( Func map) where T : class, IOpenApiReferenceable { - throw new OpenApiReaderException("Cannot create map from this reference."); + throw new OpenApiReaderException("Cannot create map from this reference.", Context); } public virtual List CreateSimpleList(Func map) { - throw new OpenApiReaderException("Cannot create simple list from this type of node."); + throw new OpenApiReaderException("Cannot create simple list from this type of node.", Context); } public virtual Dictionary CreateSimpleMap(Func map) { - throw new OpenApiReaderException("Cannot create simple map from this type of node."); + throw new OpenApiReaderException("Cannot create simple map from this type of node.", Context); } public virtual IOpenApiAny CreateAny() { - throw new OpenApiReaderException("Cannot create an Any object this type of node."); + throw new OpenApiReaderException("Cannot create an Any object this type of node.", Context); } public virtual string GetRaw() { - throw new OpenApiReaderException("Cannot get raw value from this type of node."); + throw new OpenApiReaderException("Cannot get raw value from this type of node.", Context); } public virtual string GetScalarValue() { - throw new OpenApiReaderException("Cannot create a scalar value from this type of node."); + throw new OpenApiReaderException("Cannot create a scalar value from this type of node.", Context); } public virtual List CreateListOfAny() { - throw new OpenApiReaderException("Cannot create a list from this type of node."); + throw new OpenApiReaderException("Cannot create a list from this type of node.", Context); } - } } diff --git a/src/Microsoft.OpenApi.Readers/ParsingContext.cs b/src/Microsoft.OpenApi.Readers/ParsingContext.cs index 9c7277136..b7cfb6acb 100644 --- a/src/Microsoft.OpenApi.Readers/ParsingContext.cs +++ b/src/Microsoft.OpenApi.Readers/ParsingContext.cs @@ -141,7 +141,7 @@ public void EndObject() /// public string GetLocation() { - return "#/" + string.Join("/", _currentLocation.Reverse().ToArray()); + return "#/" + string.Join("/", _currentLocation.Reverse().Select(s=> s.Replace("~","~0").Replace("/","~1")).ToArray()); } /// diff --git a/src/Microsoft.OpenApi/Exceptions/OpenApiException.cs b/src/Microsoft.OpenApi/Exceptions/OpenApiException.cs index 410e0dd78..4d1ca4f62 100644 --- a/src/Microsoft.OpenApi/Exceptions/OpenApiException.cs +++ b/src/Microsoft.OpenApi/Exceptions/OpenApiException.cs @@ -39,8 +39,14 @@ public OpenApiException(string message, Exception innerException) } /// - /// The reference pointer. + /// The reference pointer. This is a fragment identifier used to point to where the error occurred in the document. + /// If the document has been parsed as JSON/YAML then the identifier will be a + /// JSON Pointer as per https://tools.ietf.org/html/rfc6901 + /// If the document fails to parse as JSON/YAML then the fragment will be based on + /// a text/plain pointer as defined in https://tools.ietf.org/html/rfc5147 + /// Currently only line= is provided because using char= causes tests to break due to CR/LF & LF differences /// public string Pointer { get; set; } + } } diff --git a/test/Microsoft.OpenApi.Readers.Tests/ParseNodeTests.cs b/test/Microsoft.OpenApi.Readers.Tests/ParseNodeTests.cs index 04a400b40..677232ac4 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/ParseNodeTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/ParseNodeTests.cs @@ -31,5 +31,34 @@ public void BrokenSimpleList() }) }); } + + [Fact] + public void BadSchema() + { + var input = @"openapi: 3.0.0 +info: + title: foo + version: bar +paths: + '/foo': + get: + responses: + 200: + description: ok + content: + application/json: + schema: asdasd +"; + + var reader = new OpenApiStringReader(); + reader.Read(input, out var diagnostic); + + diagnostic.Errors.Should().BeEquivalentTo(new List() { + new OpenApiError(new OpenApiReaderException("schema must be a map/object") { + Pointer = "#/paths/~1foo/get/responses/200/content/application~1json/schema" + }) + }); + } } } +