Repository navigation
fix: say which value failed to parse and why references could not be resolved #54
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: vnext
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,6 +2,7 @@ namespace ByteBard.AsyncAPI.Readers.ParseNodes | |
| { | ||
| using System; | ||
| using System.Globalization; | ||
| using System.Linq; | ||
| using System.Text.Json.Nodes; | ||
| using ByteBard.AsyncAPI.Exceptions; | ||
| using ByteBard.AsyncAPI.Models; | ||
|
|
@@ -53,7 +54,7 @@ public override int GetIntegerValue() | |
| return value; | ||
| } | ||
|
|
||
| throw new AsyncApiReaderException("Value could not parse to integer."); | ||
| throw new AsyncApiReaderException(this.DescribeInvalidInteger("integer", int.MinValue, int.MaxValue), this.Context); | ||
| } | ||
|
|
||
| public override int? GetIntegerValueOrDefault(int? defaultValue = null) | ||
|
|
@@ -73,7 +74,7 @@ public override long GetLongValue() | |
| return value; | ||
| } | ||
|
|
||
| throw new AsyncApiReaderException("Value could not parse to long."); | ||
| throw new AsyncApiReaderException(this.DescribeInvalidInteger("long", long.MinValue, long.MaxValue), this.Context); | ||
| } | ||
|
|
||
| public override long? GetLongValueOrDefault(long? defaultValue = null) | ||
|
|
@@ -93,7 +94,7 @@ public override bool GetBooleanValue() | |
| return value; | ||
| } | ||
|
|
||
| throw new AsyncApiReaderException("Value could not parse to bool."); | ||
| throw new AsyncApiReaderException($"Value '{this.GetScalarValue()}' could not parse to bool. Expected true or false.", this.Context); | ||
| } | ||
|
|
||
| public override bool? GetBooleanValueOrDefault(bool? defaultValue = null) | ||
|
|
@@ -111,5 +112,19 @@ public override AsyncApiAny CreateAny() | |
| var value = this.GetScalarValue(); | ||
| return new AsyncApiAny(this.node); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Describes why the scalar is not a valid integer of the given type, telling an out-of-range number apart from a value that is not a whole number. | ||
| /// </summary> | ||
| private string DescribeInvalidInteger(string typeName, long min, long max) | ||
| { | ||
| var value = this.GetScalarValue(); | ||
| var digits = value?.Trim().TrimStart('+', '-'); | ||
| var isWholeNumber = !string.IsNullOrEmpty(digits) && digits.All(c => c >= '0' && c <= '9'); | ||
|
Comment on lines
+122
to
+123
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win Remove at most one leading sign. If the value is 🤖 Prompt for AI Agents |
||
|
|
||
| return isWholeNumber | ||
| ? $"Value '{value}' could not parse to {typeName}. It must be between {min} and {max}." | ||
| : $"Value '{value}' could not parse to {typeName}. Expected a whole number."; | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,68 @@ | ||
| namespace ByteBard.AsyncAPI.Tests | ||
| { | ||
| using System.Text.Json.Nodes; | ||
| using ByteBard.AsyncAPI.Models; | ||
| using ByteBard.AsyncAPI.Readers; | ||
| using ByteBard.AsyncAPI.Readers.ParseNodes; | ||
| using FluentAssertions; | ||
| using NUnit.Framework; | ||
|
|
||
| internal class ReaderErrorDetails_Should : TestBase | ||
| { | ||
| [TestCase("abc", "Value 'abc' could not parse to integer. Expected a whole number.")] | ||
| [TestCase("1.5", "Value '1.5' could not parse to integer. Expected a whole number.")] | ||
| [TestCase("99999999999", "Value '99999999999' could not parse to integer. It must be between -2147483648 and 2147483647.")] | ||
| public void SchemaIntegerField_ReportsTheValueAndWhyItIsInvalid(string value, string expectedMessage) | ||
| { | ||
| var yaml = $"type: string\nmaxLength: '{value}'"; | ||
|
|
||
| new AsyncApiStringReader().ReadFragment<AsyncApiJsonSchema>(yaml, AsyncApiVersion.AsyncApi3_0, out var diagnostics); | ||
|
|
||
| var error = diagnostics.Errors.Should().ContainSingle().Subject; | ||
| error.Message.Should().Be(expectedMessage); | ||
| error.Pointer.Should().Be("#/maxLength"); | ||
| } | ||
|
|
||
| [Test] | ||
| public void MissingLocalReference_IsReportedOnce() | ||
| { | ||
| var yaml = | ||
| """ | ||
| asyncapi: 3.0.0 | ||
| info: | ||
| title: test | ||
| version: 1.0.0 | ||
| channels: | ||
| users: | ||
| messages: | ||
| created: | ||
| payload: | ||
| $ref: '#/components/schemas/Missing' | ||
| """; | ||
|
|
||
| new AsyncApiStringReader().Read(yaml, out var diagnostics); | ||
|
|
||
| diagnostics.Errors.Should().ContainSingle() | ||
| .Which.Message.Should().Be("Could not resolve reference fragment."); | ||
| } | ||
|
|
||
| [TestCase("/info/title/nested")] | ||
| [TestCase("/tags/5")] | ||
| [TestCase("/tags/name")] | ||
| [TestCase("/missing")] | ||
| public void Find_ReturnsNull_WhenThePointerDoesNotLeadToANode(string pointer) | ||
| { | ||
| var json = JsonNode.Parse("""{ "info": { "title": "test" }, "tags": [ { "name": "a" } ] }"""); | ||
|
|
||
| new JsonPointer(pointer).Find(json).Should().BeNull(); | ||
| } | ||
|
|
||
| [Test] | ||
| public void Find_FollowsObjectsAndArrays() | ||
| { | ||
| var json = JsonNode.Parse("""{ "tags": [ { "name": "a" } ] }"""); | ||
|
|
||
| new JsonPointer("/tags/0/name").Find(json).GetValue<string>().Should().Be("a"); | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Use invariant culture when parsing the array index token.
int.TryParse(token, out var index)uses the current culture. A culture-specific negative sign or digit format can change which tokens parse as an index. RFC 6901 array indices are ASCII digits only. Theindex < 0check also lets a token such as-0or+1resolve to an element. UseNumberStyles.NoneandCultureInfo.InvariantCultureso the parser accepts only unsigned ASCII digits.Proposed fix
📝 Committable suggestion
🤖 Prompt for AI Agents