Skip to content

Commit 82cc94d

Browse files
authored
Improve testing for converters (#316)
This revealed that the ConvertTo for these methods didn't seem to work at all. (It would invariably fail when trying to validate its converted result, because it creates a string, but validation for these types fails unless the input is of the corresponding JToken type.) Also, there were inconsistencies in how validation failures were being reported, which this fixes. Note that this now uses the .NET 7 SDK in build pipeline (but only for access to new language features - there's no change in target runtimes).
1 parent 6b2b63d commit 82cc94d

File tree

61 files changed

+4007
-491
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

61 files changed

+4007
-491
lines changed

Solutions/Menes.Abstractions/Menes/Converters/BooleanConverter.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ public string ConvertTo(object instance, OpenApiSchema schema)
5050
{
5151
string result = JsonConvert.SerializeObject(instance, this.configuration.Formatting, this.configuration.SerializerSettings);
5252

53-
this.validator.ValidateAndThrow(result, schema);
53+
this.validator.ValidateAndThrow(JToken.Parse(result), schema);
5454

5555
return result;
5656
}

Solutions/Menes.Abstractions/Menes/Converters/ByteArrayConverter.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ public string ConvertTo(object instance, OpenApiSchema schema)
4646

4747
this.validator.ValidateAndThrow(result, schema);
4848

49-
return result;
49+
return $"\"{result}\"";
5050
}
5151
}
5252
}

Solutions/Menes.Abstractions/Menes/Converters/DateConverter.cs

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@
55
namespace Menes.Converters
66
{
77
using System;
8+
89
using Menes.Validation;
10+
911
using Microsoft.OpenApi.Models;
10-
using Newtonsoft.Json;
12+
1113
using Newtonsoft.Json.Linq;
1214

1315
/// <summary>
@@ -16,17 +18,14 @@ namespace Menes.Converters
1618
public class DateConverter : IOpenApiConverter
1719
{
1820
private readonly OpenApiSchemaValidator validator;
19-
private readonly IOpenApiConfiguration configuration;
2021

2122
/// <summary>
2223
/// Initializes a new instance of the <see cref="DateConverter"/> class.
2324
/// </summary>
2425
/// <param name="validator">The <see cref="OpenApiSchemaValidator"/>.</param>
25-
/// <param name="configuration">The OpenAPI host configuration.</param>
26-
public DateConverter(OpenApiSchemaValidator validator, IOpenApiConfiguration configuration)
26+
public DateConverter(OpenApiSchemaValidator validator)
2727
{
2828
this.validator = validator;
29-
this.configuration = configuration;
3029
}
3130

3231
/// <inheritdoc/>
@@ -39,19 +38,25 @@ public bool CanConvert(OpenApiSchema schema, bool ignoreFormat = false)
3938
public object ConvertFrom(string content, OpenApiSchema schema)
4039
{
4140
JToken token = content;
42-
var result = (DateTimeOffset)token;
41+
var result = new DateTimeOffset(DateTime.SpecifyKind((DateTime)token, DateTimeKind.Utc));
4342

44-
this.validator.ValidateAndThrow(result, schema);
43+
this.validator.ValidateAndThrow(token, schema);
4544

4645
return result;
4746
}
4847

4948
/// <inheritdoc/>
5049
public string ConvertTo(object instance, OpenApiSchema schema)
5150
{
52-
string result = JsonConvert.SerializeObject(instance, this.configuration.Formatting, this.configuration.SerializerSettings);
51+
string result = instance switch
52+
{
53+
DateTimeOffset dt => $"\"{dt:yyyy-MM-dd}\"",
54+
DateTime dt => $"\"{dt:yyyy-MM-dd}\"",
55+
DateOnly dt => $"\"{dt:yyyy-MM-dd}\"",
56+
_ => throw new ArgumentException($"Unsupported source type {instance.GetType().FullName}"),
57+
};
5358

54-
this.validator.ValidateAndThrow(result, schema);
59+
this.validator.ValidateAndThrow(JToken.Parse(result), schema);
5560

5661
return result;
5762
}

Solutions/Menes.Abstractions/Menes/Converters/DoubleConverter.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ public object ConvertFrom(string content, OpenApiSchema schema)
4949
public string ConvertTo(object instance, OpenApiSchema schema)
5050
{
5151
string result = JsonConvert.SerializeObject(instance, this.configuration.Formatting, this.configuration.SerializerSettings);
52-
this.validator.ValidateAndThrow(result, schema);
52+
this.validator.ValidateAndThrow(JToken.Parse(result), schema);
5353

5454
return result;
5555
}

Solutions/Menes.Abstractions/Menes/Converters/FloatConverter.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ public string ConvertTo(object instance, OpenApiSchema schema)
5151
{
5252
string result = JsonConvert.SerializeObject(instance, this.configuration.Formatting, this.configuration.SerializerSettings);
5353

54-
this.validator.ValidateAndThrow(result, schema);
54+
this.validator.ValidateAndThrow(JToken.Parse(result), schema);
5555

5656
return result;
5757
}

Solutions/Menes.Abstractions/Menes/Converters/IOpenApiConverter.cs

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,42 @@ namespace Menes.Converters
99
/// <summary>
1010
/// Implemented by types that can convert a value to/from an OpenAPI schema value.
1111
/// </summary>
12+
/// <remarks>
13+
/// <para>
14+
/// This interface has a slight internal inconsistency: string value handling is different for
15+
/// inputs (<see cref="ConvertFrom(string, OpenApiSchema)"/>) and outputs
16+
/// (<see cref="ConvertTo(object, OpenApiSchema)"/>). String-typed inputs normally come from
17+
/// the path, query, or sometimes a header or cookie, and in these cases, we don't wrap the
18+
/// strings with double quotes. This is in contrast to JSON where you can always tell the
19+
/// difference between a string value and some other value, e.g.:
20+
/// </para>
21+
/// <code>
22+
/// {
23+
/// "booleanValue": true,
24+
/// "stringValue": "true"
25+
/// </code>
26+
/// <para>
27+
/// If inputs were always in JSON form, you'd see a similar difference. For example, if an
28+
/// input appears in a query string, <c>http://example.com/?x=true</c> would unambiguously
29+
/// mean that the parameter x has the JSON boolean value <c>true</c>, whereas if we wanted a
30+
/// string value, we'd use <c>http://example.com/?x=%22true%22</c>. But in practice, it's
31+
/// unusual for web sites to use this convention. In practice, all query string parameters
32+
/// have string values, and it's up to the web server whether it chooses to interpret them
33+
/// as having a particular format.
34+
/// </para>
35+
/// <para>
36+
/// For this reason, this interface expects incoming string values to be unquoted, because
37+
/// in most cases they are. The one exception is if the entire request body is a single
38+
/// string. If the request <c>Content-Type</c> is <c>application/json</c> then it must be
39+
/// enclosing in double quotes. But for anything other than the body, those quotes will not
40+
/// be present in the raw inputs. But outputs are always in JSON form,
41+
/// <see cref="ConvertTo(object, Microsoft.OpenApi.Models.OpenApiSchema)"/> returns quoted
42+
/// strings. We could have insisted on consistency here, but that would have required almost
43+
/// all calls to <see cref="ConvertFrom(string, OpenApiSchema)"/> to create new strings
44+
/// wrapping the existing input values in quotes. This would add noise to the code, and require
45+
/// additional string allocations, so instead, we just live with asymmetry in this interface.
46+
/// </para>
47+
/// </remarks>
1248
public interface IOpenApiConverter
1349
{
1450
/// <summary>
@@ -22,7 +58,10 @@ public interface IOpenApiConverter
2258
/// <summary>
2359
/// Convert from the specified content to an object of the required type.
2460
/// </summary>
25-
/// <param name="content">The content to convert.</param>
61+
/// <param name="content">
62+
/// The content to convert. If the input data is of type string, this will not be
63+
/// enclosed in double quotes.
64+
/// </param>
2665
/// <param name="schema">The schema of the content to convert.</param>
2766
/// <returns>An instance of the converted object.</returns>
2867
object ConvertFrom(string content, OpenApiSchema schema);
@@ -32,7 +71,11 @@ public interface IOpenApiConverter
3271
/// </summary>
3372
/// <param name="instance">The instance to convert.</param>
3473
/// <param name="schema">The schema of the content to convert.</param>
35-
/// <returns>A string representation of the converted object for the output document.</returns>
74+
/// <returns>
75+
/// A JSON representation of the converted object for the output document. Unlike with
76+
/// <see cref="ConvertFrom(string, OpenApiSchema)"/>, if the data is a JSON string,
77+
/// it will be enclosed in double quotes.
78+
/// </returns>
3679
string ConvertTo(object instance, OpenApiSchema schema);
3780
}
3881
}

Solutions/Menes.Abstractions/Menes/Converters/Integer32Converter.cs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
namespace Menes.Converters
66
{
7+
using System;
8+
79
using Menes.Validation;
810
using Microsoft.OpenApi.Models;
911
using Newtonsoft.Json;
@@ -39,7 +41,15 @@ public object ConvertFrom(string content, OpenApiSchema schema)
3941
{
4042
JToken token = content;
4143

42-
int result = (int)token;
44+
int result;
45+
try
46+
{
47+
result = (int)token;
48+
}
49+
catch (OverflowException)
50+
{
51+
throw new FormatException("Number was too large to parse as an Int32");
52+
}
4353

4454
this.validator.ValidateAndThrow(result, schema);
4555

@@ -51,7 +61,7 @@ public string ConvertTo(object instance, OpenApiSchema schema)
5161
{
5262
string result = JsonConvert.SerializeObject(instance, this.configuration.Formatting, this.configuration.SerializerSettings);
5363

54-
this.validator.ValidateAndThrow(result, schema);
64+
this.validator.ValidateAndThrow(JToken.Parse(result), schema);
5565

5666
return result;
5767
}

Solutions/Menes.Abstractions/Menes/Converters/Integer64Converter.cs

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
namespace Menes.Converters
66
{
7+
using System;
8+
79
using Menes.Validation;
810
using Microsoft.OpenApi.Models;
911
using Newtonsoft.Json;
@@ -39,7 +41,16 @@ public object ConvertFrom(string content, OpenApiSchema schema)
3941
{
4042
JToken token = content;
4143

42-
long result = (long)token;
44+
long result;
45+
try
46+
{
47+
result = (long)token;
48+
}
49+
catch (OverflowException)
50+
{
51+
throw new FormatException("Number was too large to parse as an Int64");
52+
}
53+
4354
this.validator.ValidateAndThrow(result, schema);
4455

4556
return result;
@@ -50,7 +61,7 @@ public string ConvertTo(object instance, OpenApiSchema schema)
5061
{
5162
string result = JsonConvert.SerializeObject(instance, this.configuration.Formatting, this.configuration.SerializerSettings);
5263

53-
this.validator.ValidateAndThrow(result, schema);
64+
this.validator.ValidateAndThrow(JToken.Parse(result), schema);
5465

5566
return result;
5667
}

Solutions/Menes.Abstractions/Menes/Converters/PasswordConverter.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ public string ConvertTo(object instance, OpenApiSchema schema)
4444

4545
this.validator.ValidateAndThrow(result, schema);
4646

47-
return result;
47+
return $"\"{result}\"";
4848
}
4949
}
5050
}

Solutions/Menes.Abstractions/Menes/Converters/StringConverter.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ public string ConvertTo(object instance, OpenApiSchema schema)
5353

5454
this.validator.ValidateAndThrow(result, schema);
5555

56-
return result;
56+
return '"' + result + '"';
5757
}
5858
}
5959
}

0 commit comments

Comments
 (0)