diff --git a/Solutions/Menes.Abstractions/Menes/Converters/BooleanConverter.cs b/Solutions/Menes.Abstractions/Menes/Converters/BooleanConverter.cs index 7842e9eab..78e571d30 100644 --- a/Solutions/Menes.Abstractions/Menes/Converters/BooleanConverter.cs +++ b/Solutions/Menes.Abstractions/Menes/Converters/BooleanConverter.cs @@ -50,7 +50,7 @@ public string ConvertTo(object instance, OpenApiSchema schema) { string result = JsonConvert.SerializeObject(instance, this.configuration.Formatting, this.configuration.SerializerSettings); - this.validator.ValidateAndThrow(result, schema); + this.validator.ValidateAndThrow(JToken.Parse(result), schema); return result; } diff --git a/Solutions/Menes.Abstractions/Menes/Converters/ByteArrayConverter.cs b/Solutions/Menes.Abstractions/Menes/Converters/ByteArrayConverter.cs index ae7f2280f..f3e65d0cf 100644 --- a/Solutions/Menes.Abstractions/Menes/Converters/ByteArrayConverter.cs +++ b/Solutions/Menes.Abstractions/Menes/Converters/ByteArrayConverter.cs @@ -46,7 +46,7 @@ public string ConvertTo(object instance, OpenApiSchema schema) this.validator.ValidateAndThrow(result, schema); - return result; + return $"\"{result}\""; } } } \ No newline at end of file diff --git a/Solutions/Menes.Abstractions/Menes/Converters/DateConverter.cs b/Solutions/Menes.Abstractions/Menes/Converters/DateConverter.cs index ba378ad71..42f5e4b84 100644 --- a/Solutions/Menes.Abstractions/Menes/Converters/DateConverter.cs +++ b/Solutions/Menes.Abstractions/Menes/Converters/DateConverter.cs @@ -5,9 +5,11 @@ namespace Menes.Converters { using System; + using Menes.Validation; + using Microsoft.OpenApi.Models; - using Newtonsoft.Json; + using Newtonsoft.Json.Linq; /// @@ -16,17 +18,14 @@ namespace Menes.Converters public class DateConverter : IOpenApiConverter { private readonly OpenApiSchemaValidator validator; - private readonly IOpenApiConfiguration configuration; /// /// Initializes a new instance of the class. /// /// The . - /// The OpenAPI host configuration. - public DateConverter(OpenApiSchemaValidator validator, IOpenApiConfiguration configuration) + public DateConverter(OpenApiSchemaValidator validator) { this.validator = validator; - this.configuration = configuration; } /// @@ -39,9 +38,9 @@ public bool CanConvert(OpenApiSchema schema, bool ignoreFormat = false) public object ConvertFrom(string content, OpenApiSchema schema) { JToken token = content; - var result = (DateTimeOffset)token; + var result = new DateTimeOffset(DateTime.SpecifyKind((DateTime)token, DateTimeKind.Utc)); - this.validator.ValidateAndThrow(result, schema); + this.validator.ValidateAndThrow(token, schema); return result; } @@ -49,9 +48,15 @@ public object ConvertFrom(string content, OpenApiSchema schema) /// public string ConvertTo(object instance, OpenApiSchema schema) { - string result = JsonConvert.SerializeObject(instance, this.configuration.Formatting, this.configuration.SerializerSettings); + string result = instance switch + { + DateTimeOffset dt => $"\"{dt:yyyy-MM-dd}\"", + DateTime dt => $"\"{dt:yyyy-MM-dd}\"", + DateOnly dt => $"\"{dt:yyyy-MM-dd}\"", + _ => throw new ArgumentException($"Unsupported source type {instance.GetType().FullName}"), + }; - this.validator.ValidateAndThrow(result, schema); + this.validator.ValidateAndThrow(JToken.Parse(result), schema); return result; } diff --git a/Solutions/Menes.Abstractions/Menes/Converters/DoubleConverter.cs b/Solutions/Menes.Abstractions/Menes/Converters/DoubleConverter.cs index f7a3f5ff1..49f59e6bd 100644 --- a/Solutions/Menes.Abstractions/Menes/Converters/DoubleConverter.cs +++ b/Solutions/Menes.Abstractions/Menes/Converters/DoubleConverter.cs @@ -49,7 +49,7 @@ public object ConvertFrom(string content, OpenApiSchema schema) public string ConvertTo(object instance, OpenApiSchema schema) { string result = JsonConvert.SerializeObject(instance, this.configuration.Formatting, this.configuration.SerializerSettings); - this.validator.ValidateAndThrow(result, schema); + this.validator.ValidateAndThrow(JToken.Parse(result), schema); return result; } diff --git a/Solutions/Menes.Abstractions/Menes/Converters/FloatConverter.cs b/Solutions/Menes.Abstractions/Menes/Converters/FloatConverter.cs index f124c88d3..ce3e044fb 100644 --- a/Solutions/Menes.Abstractions/Menes/Converters/FloatConverter.cs +++ b/Solutions/Menes.Abstractions/Menes/Converters/FloatConverter.cs @@ -51,7 +51,7 @@ public string ConvertTo(object instance, OpenApiSchema schema) { string result = JsonConvert.SerializeObject(instance, this.configuration.Formatting, this.configuration.SerializerSettings); - this.validator.ValidateAndThrow(result, schema); + this.validator.ValidateAndThrow(JToken.Parse(result), schema); return result; } diff --git a/Solutions/Menes.Abstractions/Menes/Converters/IOpenApiConverter.cs b/Solutions/Menes.Abstractions/Menes/Converters/IOpenApiConverter.cs index c9033b5da..21b35a80f 100644 --- a/Solutions/Menes.Abstractions/Menes/Converters/IOpenApiConverter.cs +++ b/Solutions/Menes.Abstractions/Menes/Converters/IOpenApiConverter.cs @@ -9,6 +9,42 @@ namespace Menes.Converters /// /// Implemented by types that can convert a value to/from an OpenAPI schema value. /// + /// + /// + /// This interface has a slight internal inconsistency: string value handling is different for + /// inputs () and outputs + /// (). String-typed inputs normally come from + /// the path, query, or sometimes a header or cookie, and in these cases, we don't wrap the + /// strings with double quotes. This is in contrast to JSON where you can always tell the + /// difference between a string value and some other value, e.g.: + /// + /// + /// { + /// "booleanValue": true, + /// "stringValue": "true" + /// + /// + /// If inputs were always in JSON form, you'd see a similar difference. For example, if an + /// input appears in a query string, http://example.com/?x=true would unambiguously + /// mean that the parameter x has the JSON boolean value true, whereas if we wanted a + /// string value, we'd use http://example.com/?x=%22true%22. But in practice, it's + /// unusual for web sites to use this convention. In practice, all query string parameters + /// have string values, and it's up to the web server whether it chooses to interpret them + /// as having a particular format. + /// + /// + /// For this reason, this interface expects incoming string values to be unquoted, because + /// in most cases they are. The one exception is if the entire request body is a single + /// string. If the request Content-Type is application/json then it must be + /// enclosing in double quotes. But for anything other than the body, those quotes will not + /// be present in the raw inputs. But outputs are always in JSON form, + /// returns quoted + /// strings. We could have insisted on consistency here, but that would have required almost + /// all calls to to create new strings + /// wrapping the existing input values in quotes. This would add noise to the code, and require + /// additional string allocations, so instead, we just live with asymmetry in this interface. + /// + /// public interface IOpenApiConverter { /// @@ -22,7 +58,10 @@ public interface IOpenApiConverter /// /// Convert from the specified content to an object of the required type. /// - /// The content to convert. + /// + /// The content to convert. If the input data is of type string, this will not be + /// enclosed in double quotes. + /// /// The schema of the content to convert. /// An instance of the converted object. object ConvertFrom(string content, OpenApiSchema schema); @@ -32,7 +71,11 @@ public interface IOpenApiConverter /// /// The instance to convert. /// The schema of the content to convert. - /// A string representation of the converted object for the output document. + /// + /// A JSON representation of the converted object for the output document. Unlike with + /// , if the data is a JSON string, + /// it will be enclosed in double quotes. + /// string ConvertTo(object instance, OpenApiSchema schema); } } \ No newline at end of file diff --git a/Solutions/Menes.Abstractions/Menes/Converters/Integer32Converter.cs b/Solutions/Menes.Abstractions/Menes/Converters/Integer32Converter.cs index 9db5a3b07..b685ba116 100644 --- a/Solutions/Menes.Abstractions/Menes/Converters/Integer32Converter.cs +++ b/Solutions/Menes.Abstractions/Menes/Converters/Integer32Converter.cs @@ -4,6 +4,8 @@ namespace Menes.Converters { + using System; + using Menes.Validation; using Microsoft.OpenApi.Models; using Newtonsoft.Json; @@ -39,7 +41,15 @@ public object ConvertFrom(string content, OpenApiSchema schema) { JToken token = content; - int result = (int)token; + int result; + try + { + result = (int)token; + } + catch (OverflowException) + { + throw new FormatException("Number was too large to parse as an Int32"); + } this.validator.ValidateAndThrow(result, schema); @@ -51,7 +61,7 @@ public string ConvertTo(object instance, OpenApiSchema schema) { string result = JsonConvert.SerializeObject(instance, this.configuration.Formatting, this.configuration.SerializerSettings); - this.validator.ValidateAndThrow(result, schema); + this.validator.ValidateAndThrow(JToken.Parse(result), schema); return result; } diff --git a/Solutions/Menes.Abstractions/Menes/Converters/Integer64Converter.cs b/Solutions/Menes.Abstractions/Menes/Converters/Integer64Converter.cs index 13c4ac730..89fde734e 100644 --- a/Solutions/Menes.Abstractions/Menes/Converters/Integer64Converter.cs +++ b/Solutions/Menes.Abstractions/Menes/Converters/Integer64Converter.cs @@ -4,6 +4,8 @@ namespace Menes.Converters { + using System; + using Menes.Validation; using Microsoft.OpenApi.Models; using Newtonsoft.Json; @@ -39,7 +41,16 @@ public object ConvertFrom(string content, OpenApiSchema schema) { JToken token = content; - long result = (long)token; + long result; + try + { + result = (long)token; + } + catch (OverflowException) + { + throw new FormatException("Number was too large to parse as an Int64"); + } + this.validator.ValidateAndThrow(result, schema); return result; @@ -50,7 +61,7 @@ public string ConvertTo(object instance, OpenApiSchema schema) { string result = JsonConvert.SerializeObject(instance, this.configuration.Formatting, this.configuration.SerializerSettings); - this.validator.ValidateAndThrow(result, schema); + this.validator.ValidateAndThrow(JToken.Parse(result), schema); return result; } diff --git a/Solutions/Menes.Abstractions/Menes/Converters/PasswordConverter.cs b/Solutions/Menes.Abstractions/Menes/Converters/PasswordConverter.cs index 65371763a..a858a0472 100644 --- a/Solutions/Menes.Abstractions/Menes/Converters/PasswordConverter.cs +++ b/Solutions/Menes.Abstractions/Menes/Converters/PasswordConverter.cs @@ -44,7 +44,7 @@ public string ConvertTo(object instance, OpenApiSchema schema) this.validator.ValidateAndThrow(result, schema); - return result; + return $"\"{result}\""; } } } \ No newline at end of file diff --git a/Solutions/Menes.Abstractions/Menes/Converters/StringConverter.cs b/Solutions/Menes.Abstractions/Menes/Converters/StringConverter.cs index d9f33feb5..e37c06211 100644 --- a/Solutions/Menes.Abstractions/Menes/Converters/StringConverter.cs +++ b/Solutions/Menes.Abstractions/Menes/Converters/StringConverter.cs @@ -53,7 +53,7 @@ public string ConvertTo(object instance, OpenApiSchema schema) this.validator.ValidateAndThrow(result, schema); - return result; + return '"' + result + '"'; } } } \ No newline at end of file diff --git a/Solutions/Menes.Abstractions/Menes/Converters/UriConverter.cs b/Solutions/Menes.Abstractions/Menes/Converters/UriConverter.cs index e0d24929a..98b237cf1 100644 --- a/Solutions/Menes.Abstractions/Menes/Converters/UriConverter.cs +++ b/Solutions/Menes.Abstractions/Menes/Converters/UriConverter.cs @@ -51,7 +51,7 @@ public string ConvertTo(object instance, OpenApiSchema schema) { string result = JsonConvert.SerializeObject(instance, this.configuration.Formatting, this.configuration.SerializerSettings); - this.validator.ValidateAndThrow(result, schema); + this.validator.ValidateAndThrow(JToken.Parse(result), schema); return result; } diff --git a/Solutions/Menes.Abstractions/Menes/Exceptions/OpenApiAccessControlPolicyEvaluationFailedException.cs b/Solutions/Menes.Abstractions/Menes/Exceptions/OpenApiAccessControlPolicyEvaluationFailedException.cs index aaebc09b2..2a3b43cef 100644 --- a/Solutions/Menes.Abstractions/Menes/Exceptions/OpenApiAccessControlPolicyEvaluationFailedException.cs +++ b/Solutions/Menes.Abstractions/Menes/Exceptions/OpenApiAccessControlPolicyEvaluationFailedException.cs @@ -6,7 +6,6 @@ namespace Menes.Exceptions { using System; using System.Linq; - using System.Runtime.Serialization; /// /// An exception that is thrown by an if it fails diff --git a/Solutions/Menes.Abstractions/Menes/Exceptions/OpenApiBadRequestException.cs b/Solutions/Menes.Abstractions/Menes/Exceptions/OpenApiBadRequestException.cs index 06c0d3014..f51f6aa8e 100644 --- a/Solutions/Menes.Abstractions/Menes/Exceptions/OpenApiBadRequestException.cs +++ b/Solutions/Menes.Abstractions/Menes/Exceptions/OpenApiBadRequestException.cs @@ -94,6 +94,15 @@ public OpenApiBadRequestException( } } + /// + /// Creates a from an . + /// + /// The . + public OpenApiBadRequestException(OpenApiInvalidFormatException formatException) + : this(formatException.Message, formatException.Explanation!, formatException.DetailsType, formatException.DetailsInstance) + { + } + /// /// Gets the detailed explanation of the problem, if present. /// diff --git a/Solutions/Menes.Abstractions/Menes/Exceptions/OpenApiInvalidFormatException.cs b/Solutions/Menes.Abstractions/Menes/Exceptions/OpenApiInvalidFormatException.cs new file mode 100644 index 000000000..8cd3c4df0 --- /dev/null +++ b/Solutions/Menes.Abstractions/Menes/Exceptions/OpenApiInvalidFormatException.cs @@ -0,0 +1,82 @@ +// +// Copyright (c) Endjin Limited. All rights reserved. +// + +namespace Menes.Exceptions +{ + using System; + + /// + /// An exception that indicates that data has been found not to be in the format required + /// by an OpenAPI specification. + /// + /// + /// + /// We share validation code across multiple contexts (validation of inputs, default values + /// in an Open API spec, and outputs) and the required externally visible behaviour depends on + /// the context in which the problem is detected. The common layer needs to be able to throw + /// an exception indicating only that a problem has been detected, and this then needs to be + /// turned into a more suitable exception by the higher layers of code. + /// + /// + public class OpenApiInvalidFormatException : Exception + { + /// + /// Creates an . + /// + /// + /// The value for this exception's property. + /// + public OpenApiInvalidFormatException(string message = "Bad request") + : base(message) + { + } + + /// + /// Creates an . + /// + /// + /// A short description of the error. This is used as the + /// property, and will also be returned in the Problem Details in the response body. + /// + /// + /// A longer description of the problem. This is returned in the Problem Details in the + /// response body. + /// + /// + /// An optional URI identifying the problem type. If specified, this is returned in the + /// Problem Details in the response body. + /// response body. + /// + /// + /// An optional URI identifying the problem instance. If specified, this is returned in the + /// Problem Details in the response body. + /// + public OpenApiInvalidFormatException( + string title, + string explanation, + string? detailsType = null, + string? detailsInstance = null) + : base(title) + { + this.Explanation = explanation; + this.DetailsType = detailsType; + this.DetailsInstance = detailsInstance; + } + + /// + /// Gets the detailed explanation of the problem, if present. + /// + public string? Explanation { get; } + + /// + /// Gets the URI identifying the problem type, if present. + /// + public string? DetailsType { get; } + + /// + /// Gets the URI identifying the problem instance, if present. + /// + public string? DetailsInstance { get; } + } +} \ No newline at end of file diff --git a/Solutions/Menes.Abstractions/Menes/Validation/OpenApiSchemaValidator.cs b/Solutions/Menes.Abstractions/Menes/Validation/OpenApiSchemaValidator.cs index 0e7d09efe..8b983e84c 100644 --- a/Solutions/Menes.Abstractions/Menes/Validation/OpenApiSchemaValidator.cs +++ b/Solutions/Menes.Abstractions/Menes/Validation/OpenApiSchemaValidator.cs @@ -19,6 +19,8 @@ namespace Menes.Validation using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Writers; + + using Newtonsoft.Json; using Newtonsoft.Json.Linq; /// Class to validate a JSON schema against a given . @@ -41,7 +43,7 @@ public OpenApiSchemaValidator(ILogger logger) this.logger = logger; } - /// Validates the given data, throwing an augmented with detailing the errors. + /// Validates the given data, throwing an augmented with detailing the errors. /// The data. /// The schema. public void ValidateAndThrow(string data, OpenApiSchema schema) @@ -55,7 +57,7 @@ public void ValidateAndThrow(string data, OpenApiSchema schema) if (errors.Count > 0) { - Exception exception = new OpenApiBadRequestException("Schema Validation Error", "The content does not conform to the required schema.") + Exception exception = new OpenApiInvalidFormatException("Schema Validation Error", "The content does not conform to the required schema.") .AddProblemDetailsExtension("validationErrors", errors); this.logger.LogError( @@ -66,7 +68,7 @@ public void ValidateAndThrow(string data, OpenApiSchema schema) } } - /// Validates the given data, throwing an augmented with detailing the errors. + /// Validates the given data, throwing an augmented with detailing the errors. /// The data. /// The schema. public void ValidateAndThrow(JToken? data, OpenApiSchema schema) @@ -82,7 +84,7 @@ public void ValidateAndThrow(JToken? data, OpenApiSchema schema) if (errors.Count > 0) { - Exception exception = new OpenApiBadRequestException("Schema Validation Error", "The content does not conform to the required schema.") + Exception exception = new OpenApiInvalidFormatException("Schema Validation Error", "The content does not conform to the required schema.") .AddProblemDetailsExtension("validationErrors", errors); throw exception; @@ -96,7 +98,15 @@ public void ValidateAndThrow(JToken? data, OpenApiSchema schema) private static ICollection Validate(string data, OpenApiSchema schema) { bool dataShouldBeJson = schema.Type == "object" || schema.Type == "array" || schema.Type == null; - JToken jsonObject = dataShouldBeJson ? JToken.Parse(data) : data; + JToken jsonObject; + try + { + jsonObject = dataShouldBeJson ? JToken.Parse(data) : data; + } + catch (JsonReaderException x) + { + throw new OpenApiInvalidFormatException($"Expected object or array. Failed to parse: {x.Message}"); + } return Validate(jsonObject, schema); } diff --git a/Solutions/Menes.Hosting.AspNetCore/Menes/Internal/HttpRequestParameterBuilder.cs b/Solutions/Menes.Hosting.AspNetCore/Menes/Internal/HttpRequestParameterBuilder.cs index f6788db0a..147ea8866 100644 --- a/Solutions/Menes.Hosting.AspNetCore/Menes/Internal/HttpRequestParameterBuilder.cs +++ b/Solutions/Menes.Hosting.AspNetCore/Menes/Internal/HttpRequestParameterBuilder.cs @@ -8,7 +8,6 @@ namespace Menes.Internal using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.IO; - using System.Linq; using System.Threading.Tasks; using Menes.Converters; using Menes.Exceptions; @@ -58,107 +57,118 @@ public async Task> BuildParametersAsync(HttpRequest this.logger.LogDebug("Building parameters for parameter [{parameter}]", parameter.Name); } - switch (parameter.In) + try { - case ParameterLocation.Cookie: - if (this.TryGetParameterFromCookie(request.Cookies, parameter, out object? cookieResult)) - { - if (this.logger.IsEnabled(LogLevel.Debug)) + switch (parameter.In) + { + case ParameterLocation.Cookie: + if (this.TryGetParameterFromCookie(request.Cookies, parameter, out object? cookieResult)) + { + if (this.logger.IsEnabled(LogLevel.Debug)) + { + this.logger.LogDebug( + "Added parameter from cookie for parameter [{parameter}]", + parameter.Name); + } + + parameters.Add(parameter.Name, cookieResult); + } + else if (parameter.Required) { - this.logger.LogDebug( - "Added parameter from cookie for parameter [{parameter}]", + this.logger.LogError( + "Failed to get the required cookie parameters from the cookie for the parameter [{parameter}]", parameter.Name); + throw new OpenApiBadRequestException( + "Missing cookie", + $"Failed to get the required cookie for the parameter {parameter.Name}"); } - parameters.Add(parameter.Name, cookieResult); - } - else if (parameter.Required) - { - this.logger.LogError( - "Failed to get the required cookie parameters from the cookie for the parameter [{parameter}]", - parameter.Name); - throw new OpenApiBadRequestException( - "Missing cookie", - $"Failed to get the required cookie for the parameter {parameter.Name}"); - } - - break; - case ParameterLocation.Header: - if (this.TryGetParameterFromHeader(request.Headers, parameter, out object? headerResult)) - { - if (this.logger.IsEnabled(LogLevel.Debug)) + break; + case ParameterLocation.Header: + if (this.TryGetParameterFromHeader(request.Headers, parameter, out object? headerResult)) + { + if (this.logger.IsEnabled(LogLevel.Debug)) + { + this.logger.LogDebug( + "Added parameter from headers for parameter [{parameter}]", + parameter.Name); + } + + parameters.Add(parameter.Name, headerResult); + } + else if (parameter.Required) { - this.logger.LogDebug( - "Added parameter from headers for parameter [{parameter}]", + this.logger.LogError( + "Failed to get the required header parameters from the header for the parameter [{parameter}]", parameter.Name); + throw new OpenApiBadRequestException( + "Missing header", + $"Failed to get the required header for the parameter {parameter.Name}"); } - parameters.Add(parameter.Name, headerResult); - } - else if (parameter.Required) - { - this.logger.LogError( - "Failed to get the required header parameters from the header for the parameter [{parameter}]", - parameter.Name); - throw new OpenApiBadRequestException( - "Missing header", - $"Failed to get the required header for the parameter {parameter.Name}"); - } - - break; - case ParameterLocation.Path: - if (this.TryGetParameterFromPath(templateParameterValues, parameter, out object? pathResult)) - { - if (this.logger.IsEnabled(LogLevel.Debug)) + break; + case ParameterLocation.Path: + if (this.TryGetParameterFromPath(templateParameterValues, parameter, out object? pathResult)) + { + if (this.logger.IsEnabled(LogLevel.Debug)) + { + this.logger.LogDebug( + "Added parameter from path for parameter [{parameter}]", + parameter.Name); + } + + parameters.Add(parameter.Name, pathResult); + } + else if (parameter.Required) { - this.logger.LogDebug( - "Added parameter from path for parameter [{parameter}]", + this.logger.LogError( + "Failed to get the required path parameters from the path for the parameter [{parameter}]", parameter.Name); + throw new OpenApiBadRequestException( + "Missing path parameter", + $"Failed to get the required path element for the parameter {parameter.Name}"); } - parameters.Add(parameter.Name, pathResult); - } - else if (parameter.Required) - { - this.logger.LogError( - "Failed to get the required path parameters from the path for the parameter [{parameter}]", - parameter.Name); - throw new OpenApiBadRequestException( - "Missing path parameter", - $"Failed to get the required path element for the parameter {parameter.Name}"); - } - - break; - case ParameterLocation.Query: - if (this.TryGetParameterFromQuery(request.Query, parameter, out object? queryResult)) - { - if (this.logger.IsEnabled(LogLevel.Debug)) + break; + case ParameterLocation.Query: + if (this.TryGetParameterFromQuery(request.Query, parameter, out object? queryResult)) + { + if (this.logger.IsEnabled(LogLevel.Debug)) + { + this.logger.LogDebug( + "Added parameter from query for parameter [{parameter}]", + parameter.Name); + } + + parameters.Add(parameter.Name, queryResult); + } + else if (parameter.Required) { - this.logger.LogDebug( - "Added parameter from query for parameter [{parameter}]", + this.logger.LogError( + "Failed to get the required query parameters from the query for the parameter [{parameter}]", parameter.Name); + throw new OpenApiBadRequestException( + "Missing query parameter", + $"Failed to get the required query string entry for the parameter {parameter.Name}"); } - parameters.Add(parameter.Name, queryResult); - } - else if (parameter.Required) - { + break; + default: this.logger.LogError( - "Failed to get the required query parameters from the query for the parameter [{parameter}]", + "Failed to get the required parameters for the parameter [{parameter}], case not found", parameter.Name); throw new OpenApiBadRequestException( - "Missing query parameter", - $"Failed to get the required query string entry for the parameter {parameter.Name}"); - } - - break; - default: - this.logger.LogError( - "Failed to get the required parameters for the parameter [{parameter}], case not found", - parameter.Name); - throw new OpenApiBadRequestException( - "Missing parameter", - $"Failed to get the required parameter {parameter.Name}"); + "Missing parameter", + $"Failed to get the required parameter {parameter.Name}"); + } + } + catch (OpenApiInvalidFormatException x) + { + throw new OpenApiBadRequestException(x); + } + catch (FormatException) + { + throw new OpenApiBadRequestException($"Parameter {parameter} did not have the required format"); } } @@ -189,24 +199,35 @@ public async Task> BuildParametersAsync(HttpRequest private object? TryGetDefaultValueFromSchema(OpenApiSchema schema, IOpenApiAny? defaultValue) { - return defaultValue switch - { - //// Convert to DateTimeOffset rather than use the DateTime supplied by default to be consistent with our converters. - OpenApiDate d when schema.Format == "date" => new DateTimeOffset(DateTime.SpecifyKind(d.Value, DateTimeKind.Utc)), - //// Convert to string rather than use the DateTimeOffset supplied by default to be consistent with our current (lack of) support for strings with format "date-time". - OpenApiDateTime dt when schema.Format == "date-time" => dt.Value.ToString("yyyy-MM-ddTHH:mm:ssK"), - OpenApiPassword p when schema.Format == "password" => p.Value, - OpenApiByte by when schema.Format == "byte" => by.Value, - OpenApiString s when schema.Type == "string" => this.ConvertValue(schema, s.Value), - OpenApiBoolean b when schema.Type == "boolean" => b.Value, - OpenApiLong l when schema.Format == "int64" => l.Value, - OpenApiInteger i when schema.Type == "integer" => i.Value, - OpenApiFloat f when schema.Format == "float" => f.Value, - OpenApiDouble db when schema.Type == "number" => db.Value, - OpenApiArray a when schema.Type == "array" => HandleArray(schema.Items, a), - OpenApiObject o when schema.Type == "object" => HandleObject(schema.Properties, o), - _ => throw new OpenApiSpecificationException("Default value for parameter not valid."), - }; + try + { + return defaultValue switch + { + //// Convert to DateTimeOffset rather than use the DateTime supplied by default to be consistent with our converters. + OpenApiDate d when schema.Format == "date" => new DateTimeOffset(DateTime.SpecifyKind(d.Value, DateTimeKind.Utc)), + //// Convert to string rather than use the DateTimeOffset supplied by default to be consistent with our current (lack of) support for strings with format "date-time". + OpenApiDateTime dt when schema.Format == "date-time" => dt.Value.ToString("yyyy-MM-ddTHH:mm:ssK"), + OpenApiPassword p when schema.Format == "password" => p.Value, + OpenApiByte by when schema.Format == "byte" => by.Value, + OpenApiString s when schema.Type == "string" => this.ConvertValue(schema, s.Value), + OpenApiBoolean b when schema.Type == "boolean" => b.Value, + OpenApiLong l when schema.Format == "int64" => l.Value, + OpenApiInteger i when schema.Type == "integer" => i.Value, + OpenApiFloat f when schema.Format == "float" => f.Value, + OpenApiDouble db when schema.Type == "number" => db.Value, + OpenApiArray a when schema.Type == "array" => HandleArray(schema.Items, a), + OpenApiObject o when schema.Type == "object" => HandleObject(schema.Properties, o), + _ => throw new OpenApiSpecificationException("Default value for parameter not valid."), + }; + } + catch (OpenApiInvalidFormatException x) + { + throw new OpenApiSpecificationException($"Default value for parameter not valid ({x.Message})."); + } + catch (FormatException) + { + throw new OpenApiSpecificationException("Default value for parameter not valid."); + } object HandleArray(OpenApiSchema schema, OpenApiArray array) { @@ -413,7 +434,31 @@ private async Task ConvertBodyAsync(OpenApiSchema schema, Stream body) value = await reader.ReadToEndAsync().ConfigureAwait(false); } - return this.ConvertValue(schema, value); + try + { + // The funky thing about IOpenApiConverter is that although in most cases + // the string we pass for conversion is in its JSON format, strings are the + // exception: those appear unquoted. That's because converters are also + // passed values from headers, query strings, paths, and cookies, where + // strings are not quoted. + // Since we know we're receiving actual JSON, it's our job to detect when + // it's a string, and if it is, strip off the quotes and process any escaped + // characters inside. + if (value.Length >= 2 && value[0] == '"' && value[^1] == '"') + { + value = JsonConvert.DeserializeObject(value)!; + } + + return this.ConvertValue(schema, value); + } + catch (OpenApiInvalidFormatException x) + { + throw new OpenApiBadRequestException(x); + } + catch (FormatException) + { + throw new OpenApiBadRequestException("Request body did not have the required format"); + } } private bool TryGetParameterFromHeader( diff --git a/Solutions/Menes.PetStore.Hosting.AspNetCore.DirectPipeline/packages.lock.json b/Solutions/Menes.PetStore.Hosting.AspNetCore.DirectPipeline/packages.lock.json index ddff2b293..02e4ee358 100644 --- a/Solutions/Menes.PetStore.Hosting.AspNetCore.DirectPipeline/packages.lock.json +++ b/Solutions/Menes.PetStore.Hosting.AspNetCore.DirectPipeline/packages.lock.json @@ -192,15 +192,15 @@ }, "Microsoft.OpenApi": { "type": "Transitive", - "resolved": "1.4.4", - "contentHash": "JzSCWm7KGtZ8TCxB5V0ZHBHAe3TJ9AQ+/v28Cq9kVbMIM+erjes7f1W7j4CYD+PiHgaPK7Xss6jAYkwqVjU5NQ==" + "resolved": "1.4.5", + "contentHash": "qsKbcFvY9/yUXiLTSsXd76RePfOHTbqMYBmHlFe0j97XZy1I7g8Rj81iw2SpA2pwQO7HgwGN+Ogd/xdKRKjJTg==" }, "Microsoft.OpenApi.Readers": { "type": "Transitive", - "resolved": "1.4.4", - "contentHash": "SWVnL20hz01q/DTLpaVlZ8sHweQZJbNNff/MfQYuucDJkOoU0ucqDtURDLs1ynwg79foTx5r1x2Ab5MTWqM2vg==", + "resolved": "1.4.5", + "contentHash": "s5LG9sD1twambOgAb/QMuag93oY8H9jm0XA+8IgKolSuTbxJDRjZBRkkVUjZz1d6YEDS3HJjHVd541a5uNk8VQ==", "dependencies": { - "Microsoft.OpenApi": "1.4.4", + "Microsoft.OpenApi": "1.4.5", "SharpYaml": "2.1.0" } }, @@ -1113,7 +1113,7 @@ "Microsoft.CSharp": "[4.7.0, )", "Microsoft.Extensions.Configuration.Abstractions": "[6.0.0, )", "Microsoft.Extensions.Logging": "[6.0.0, )", - "Microsoft.OpenApi.Readers": "[1.4.4, )", + "Microsoft.OpenApi.Readers": "[1.4.5, )", "System.Interactive": "[4.1.*, )", "System.Text.Encodings.Web": "[4.7.*, )", "Tavis.UriTemplates": "[1.1.1, )" diff --git a/Solutions/Menes.PetStore.Hosting.AzureFunctions/packages.lock.json b/Solutions/Menes.PetStore.Hosting.AzureFunctions/packages.lock.json index 6915b667c..5ca10e7cc 100644 --- a/Solutions/Menes.PetStore.Hosting.AzureFunctions/packages.lock.json +++ b/Solutions/Menes.PetStore.Hosting.AzureFunctions/packages.lock.json @@ -663,15 +663,15 @@ }, "Microsoft.OpenApi": { "type": "Transitive", - "resolved": "1.4.4", - "contentHash": "JzSCWm7KGtZ8TCxB5V0ZHBHAe3TJ9AQ+/v28Cq9kVbMIM+erjes7f1W7j4CYD+PiHgaPK7Xss6jAYkwqVjU5NQ==" + "resolved": "1.4.5", + "contentHash": "qsKbcFvY9/yUXiLTSsXd76RePfOHTbqMYBmHlFe0j97XZy1I7g8Rj81iw2SpA2pwQO7HgwGN+Ogd/xdKRKjJTg==" }, "Microsoft.OpenApi.Readers": { "type": "Transitive", - "resolved": "1.4.4", - "contentHash": "SWVnL20hz01q/DTLpaVlZ8sHweQZJbNNff/MfQYuucDJkOoU0ucqDtURDLs1ynwg79foTx5r1x2Ab5MTWqM2vg==", + "resolved": "1.4.5", + "contentHash": "s5LG9sD1twambOgAb/QMuag93oY8H9jm0XA+8IgKolSuTbxJDRjZBRkkVUjZz1d6YEDS3HJjHVd541a5uNk8VQ==", "dependencies": { - "Microsoft.OpenApi": "1.4.4", + "Microsoft.OpenApi": "1.4.5", "SharpYaml": "2.1.0" } }, @@ -1744,7 +1744,7 @@ "Microsoft.CSharp": "[4.7.0, )", "Microsoft.Extensions.Configuration.Abstractions": "[6.0.0, )", "Microsoft.Extensions.Logging": "[6.0.0, )", - "Microsoft.OpenApi.Readers": "[1.4.4, )", + "Microsoft.OpenApi.Readers": "[1.4.5, )", "System.Interactive": "[4.1.*, )", "System.Text.Encodings.Web": "[4.7.*, )", "Tavis.UriTemplates": "[1.1.1, )" diff --git a/Solutions/Menes.PetStore.Specs/packages.lock.json b/Solutions/Menes.PetStore.Specs/packages.lock.json index bf6f7a184..392fceace 100644 --- a/Solutions/Menes.PetStore.Specs/packages.lock.json +++ b/Solutions/Menes.PetStore.Specs/packages.lock.json @@ -4,15 +4,15 @@ "net6.0": { "Corvus.Testing.SpecFlow.NUnit": { "type": "Direct", - "requested": "[1.6.0, )", - "resolved": "1.6.0", - "contentHash": "V8v7+oRFK99skWq/TmzgiiS6KYQtu/gJXTXep8HKxt3u1rbU7BZFQfPCHiyH7KSAIn+4HEnxqlvCbXUSyRGCAA==", + "requested": "[2.0.0, )", + "resolved": "2.0.0", + "contentHash": "mutrleL9V2ftOzcrfBGSe8imboy+p4hxWO+ejNE6TWvBPBdjFlteST/vsHoQwMOVT1VUj4AfXtgSqk5SN+NH4A==", "dependencies": { - "Corvus.Testing.SpecFlow": "1.6.0", - "Microsoft.NET.Test.Sdk": "16.11.0", - "Moq": "4.18.2", + "Corvus.Testing.SpecFlow": "2.0.0", + "Microsoft.NET.Test.Sdk": "17.4.0", + "Moq": "4.18.3", "SpecFlow.NUnit.Runners": "3.9.74", - "coverlet.msbuild": "3.1.2" + "coverlet.msbuild": "3.2.0" } }, "Endjin.RecommendedPractices.GitHub": { @@ -96,12 +96,12 @@ }, "Corvus.Testing.SpecFlow": { "type": "Transitive", - "resolved": "1.6.0", - "contentHash": "0Qg8suJQ6TbvQK1ND51LK6kP0x1lZfqQVpbLf0CqKcIVLrJLmsb2Az+j635D6RJNTZ6jYcWxxtFEh314bERaMQ==", + "resolved": "2.0.0", + "contentHash": "dNCAIMrssh6jG7HO8FlTRzdNIHcE/X8B6Zlsdfwi9LbI0EX2IZGiFZosp+XhFQDYZ5ttAzJjO2ZWUqhnpglWWQ==", "dependencies": { - "Microsoft.Extensions.Configuration.Abstractions": "3.1.31", - "Microsoft.Extensions.DependencyInjection": "3.1.31", - "Microsoft.Extensions.DependencyInjection.Abstractions": "3.1.31", + "Microsoft.Extensions.Configuration.Abstractions": "6.0.0", + "Microsoft.Extensions.DependencyInjection": "6.0.1", + "Microsoft.Extensions.DependencyInjection.Abstractions": "6.0.0", "NUnit": "3.13.3", "SpecFlow": "3.9.74", "System.Management": "4.7.0" @@ -109,8 +109,8 @@ }, "coverlet.msbuild": { "type": "Transitive", - "resolved": "3.1.2", - "contentHash": "QhM0fnDtmIMImY7oxyQ/kh1VYtRxPyRVeLwRUGuUvI6Xp83pSYG9gerK8WgJj4TzUl7ISziADUGtIWKhtlbkbQ==" + "resolved": "3.2.0", + "contentHash": "lu/eJJpqJb4qy3BGPtDD/LI5RSOwXYYyRErTyaG0OTP69llzVK3FEe74hBQx0JtLUTLEVBfERV4uGYcE1Br2sg==" }, "Endjin.RecommendedPractices": { "type": "Transitive", @@ -630,8 +630,8 @@ }, "Microsoft.CodeCoverage": { "type": "Transitive", - "resolved": "16.11.0", - "contentHash": "wf6lpAeCqP0KFfbDVtfL50lr7jY1gq0+0oSphyksfLOEygMDXqnaxHK5LPFtMEhYSEtgXdNyXNnEddOqQQUdlQ==" + "resolved": "17.4.0", + "contentHash": "2oZbSVTC2nAvQ2DnbXLlXS+c25ZyZdWeNd+znWwAxwGaPh9dwQ5NBsYyqQB7sKmJKIUdkKGmN3rzFzjVC81Dtg==" }, "Microsoft.CSharp": { "type": "Transitive", @@ -738,8 +738,8 @@ }, "Microsoft.Extensions.DependencyInjection": { "type": "Transitive", - "resolved": "6.0.0", - "contentHash": "k6PWQMuoBDGGHOQTtyois2u4AwyVcIwL2LaSLlTZQm2CYcJ1pxbt6jfAnpWmzENA/wfrYRI/X9DTLoUkE4AsLw==", + "resolved": "6.0.1", + "contentHash": "vWXPg3HJQIpZkENn1KWq8SfbqVujVD7S7vIAyFXXqK5xkf1Vho+vG0bLBCHxU36lD1cLLtmGpfYf0B3MYFi9tQ==", "dependencies": { "Microsoft.Extensions.DependencyInjection.Abstractions": "6.0.0", "System.Runtime.CompilerServices.Unsafe": "6.0.0" @@ -938,11 +938,11 @@ }, "Microsoft.NET.Test.Sdk": { "type": "Transitive", - "resolved": "16.11.0", - "contentHash": "f4mbG1SUSkNWF5p7B3Y8ZxMsvKhxCmpZhdl+w6tMtLSUGE7Izi1syU6TkmKOvB2BV66pdbENConFAISOix4ohQ==", + "resolved": "17.4.0", + "contentHash": "VtNZQ83ntG2aEUjy1gq6B4HNdn96se6FmdY/03At8WiqDReGrApm6OB2fNiSHz9D6IIEtWtNZ2FSH0RJDVXl/w==", "dependencies": { - "Microsoft.CodeCoverage": "16.11.0", - "Microsoft.TestPlatform.TestHost": "16.11.0" + "Microsoft.CodeCoverage": "17.4.0", + "Microsoft.TestPlatform.TestHost": "17.4.0" } }, "Microsoft.NETCore.Platforms": { @@ -957,15 +957,15 @@ }, "Microsoft.OpenApi": { "type": "Transitive", - "resolved": "1.4.4", - "contentHash": "JzSCWm7KGtZ8TCxB5V0ZHBHAe3TJ9AQ+/v28Cq9kVbMIM+erjes7f1W7j4CYD+PiHgaPK7Xss6jAYkwqVjU5NQ==" + "resolved": "1.4.5", + "contentHash": "qsKbcFvY9/yUXiLTSsXd76RePfOHTbqMYBmHlFe0j97XZy1I7g8Rj81iw2SpA2pwQO7HgwGN+Ogd/xdKRKjJTg==" }, "Microsoft.OpenApi.Readers": { "type": "Transitive", - "resolved": "1.4.4", - "contentHash": "SWVnL20hz01q/DTLpaVlZ8sHweQZJbNNff/MfQYuucDJkOoU0ucqDtURDLs1ynwg79foTx5r1x2Ab5MTWqM2vg==", + "resolved": "1.4.5", + "contentHash": "s5LG9sD1twambOgAb/QMuag93oY8H9jm0XA+8IgKolSuTbxJDRjZBRkkVUjZz1d6YEDS3HJjHVd541a5uNk8VQ==", "dependencies": { - "Microsoft.OpenApi": "1.4.4", + "Microsoft.OpenApi": "1.4.5", "SharpYaml": "2.1.0" } }, @@ -985,20 +985,20 @@ }, "Microsoft.TestPlatform.ObjectModel": { "type": "Transitive", - "resolved": "16.11.0", - "contentHash": "EiknJx9N9Z30gs7R+HHhki7fA8EiiM3pwD1vkw3bFsBC8kdVq/O7mHf1hrg5aJp+ASO6BoOzQueD2ysfTOy/Bg==", + "resolved": "17.4.0", + "contentHash": "oWe7A0wrZhxagTOcaxJ9r0NXTbgkiBQQuCpCXxnP06NsGV/qOoaY2oaangAJbOUrwEx0eka1do400NwNCjfytw==", "dependencies": { - "NuGet.Frameworks": "5.0.0", + "NuGet.Frameworks": "5.11.0", "System.Reflection.Metadata": "1.6.0" } }, "Microsoft.TestPlatform.TestHost": { "type": "Transitive", - "resolved": "16.11.0", - "contentHash": "/Q+R0EcCJE8JaYCk+bGReicw/xrB0HhecrYrUcLbn95BnAlaTJrZhoLkUhvtKTAVtqX/AIKWXYtutiU/Q6QUgg==", + "resolved": "17.4.0", + "contentHash": "sUx48fu9wgQF1JxzXeSVtzb7KoKpJrdtIzsFamxET3ZYOKXj+Ej13HWZ0U2nuMVZtZVHBmE+KS3Vv5cIdTlycQ==", "dependencies": { - "Microsoft.TestPlatform.ObjectModel": "16.11.0", - "Newtonsoft.Json": "9.0.1" + "Microsoft.TestPlatform.ObjectModel": "17.4.0", + "Newtonsoft.Json": "13.0.1" } }, "Microsoft.Win32.Primitives": { @@ -1022,8 +1022,8 @@ }, "Moq": { "type": "Transitive", - "resolved": "4.18.2", - "contentHash": "SjxKYS5nX6prcaT8ZjbkONh3vnh0Rxru09+gQ1a07v4TM530Oe/jq3Q4dOZPfo1wq0LYmTgLOZKrqRfEx4auPw==", + "resolved": "4.18.3", + "contentHash": "nmV2lludVOFmVi+Vtq9twX1/SDiEVyYDURzxW39gUBqjyoXmdyNwJSeOfSCJoJTXDXBVfFNfEljB5UWGj/cKnQ==", "dependencies": { "Castle.Core": "5.1.0" } @@ -1065,8 +1065,8 @@ }, "NuGet.Frameworks": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "c5JVjuVAm4f7E9Vj+v09Z9s2ZsqFDjBpcsyS3M9xRo0bEdm/LVZSzLxxNvfvAwRiiE8nwe1h2G4OwiwlzFKXlA==" + "resolved": "5.11.0", + "contentHash": "eaiXkUjC4NPcquGWzAGMXjuxvLwc6XGKMptSyOGQeT0X70BUZObuybJFZLA0OfTdueLd3US23NBPTBb6iF3V1Q==" }, "NUnit": { "type": "Transitive", @@ -2264,7 +2264,7 @@ "Microsoft.CSharp": "[4.7.0, )", "Microsoft.Extensions.Configuration.Abstractions": "[6.0.0, )", "Microsoft.Extensions.Logging": "[6.0.0, )", - "Microsoft.OpenApi.Readers": "[1.4.4, )", + "Microsoft.OpenApi.Readers": "[1.4.5, )", "System.Interactive": "[4.1.*, )", "System.Text.Encodings.Web": "[4.7.*, )", "Tavis.UriTemplates": "[1.1.1, )" diff --git a/Solutions/Menes.Specs/Bindings/MenesContainerBindings.cs b/Solutions/Menes.Specs/Bindings/MenesContainerBindings.cs index a6723245f..d9617bb1b 100644 --- a/Solutions/Menes.Specs/Bindings/MenesContainerBindings.cs +++ b/Solutions/Menes.Specs/Bindings/MenesContainerBindings.cs @@ -4,6 +4,7 @@ namespace Marain.Claims.SpecFlow.Bindings { + using Corvus.ContentHandling; using Corvus.Monitoring.Instrumentation; using Corvus.Testing.SpecFlow; using Menes; @@ -30,7 +31,18 @@ public static void InitializeContainer(ScenarioContext scenarioContext) serviceCollection => { serviceCollection.AddLogging(); - serviceCollection.AddOpenApiActionResultHosting(null); + serviceCollection.AddOpenApiAspNetPipelineHosting( + null, + config => + { + config.DiscriminatedTypes.Add("registeredDiscriminatedType1", typeof(RegisteredDiscriminatedType1)); + config.DiscriminatedTypes.Add("registeredDiscriminatedType2", typeof(RegisteredDiscriminatedType2)); + }); + serviceCollection.AddContent(cf => + { + cf.RegisterTransientContent(); + cf.RegisterTransientContent(); + }); var instrumentationProvider = new FakeInstrumentationProvider(); serviceCollection.AddSingleton(instrumentationProvider); diff --git a/Solutions/Menes.Specs/Fakes/ObjectWithIdAndName.cs b/Solutions/Menes.Specs/Fakes/ObjectWithIdAndName.cs new file mode 100644 index 000000000..0dbb00212 --- /dev/null +++ b/Solutions/Menes.Specs/Fakes/ObjectWithIdAndName.cs @@ -0,0 +1,12 @@ +// +// Copyright (c) Endjin Limited. All rights reserved. +// + +namespace Menes.Specs.Fakes; + +/// +/// Type used to test serialization of responses from objects with simple properties. +/// +/// A unique id. +/// A name. +public record ObjectWithIdAndName(int Id, string Name); \ No newline at end of file diff --git a/Solutions/Menes.Specs/Fakes/RegisteredContentType1.cs b/Solutions/Menes.Specs/Fakes/RegisteredContentType1.cs new file mode 100644 index 000000000..43df276d3 --- /dev/null +++ b/Solutions/Menes.Specs/Fakes/RegisteredContentType1.cs @@ -0,0 +1,15 @@ +// +// Copyright (c) Endjin Limited. All rights reserved. +// + +namespace Menes.Specs.Fakes; + +#pragma warning disable RCS1102 // Make class static. + +/// +/// A class used to test object deserialization based on Corvus.ContentHandling. +/// +public class RegisteredContentType1 +{ + public const string RegisteredContentType = "registeredContentType1"; +} \ No newline at end of file diff --git a/Solutions/Menes.Specs/Fakes/RegisteredContentType2.cs b/Solutions/Menes.Specs/Fakes/RegisteredContentType2.cs new file mode 100644 index 000000000..d241c8542 --- /dev/null +++ b/Solutions/Menes.Specs/Fakes/RegisteredContentType2.cs @@ -0,0 +1,15 @@ +// +// Copyright (c) Endjin Limited. All rights reserved. +// + +namespace Menes.Specs.Fakes; + +#pragma warning disable RCS1102 // Make class static. + +/// +/// A class used to test object deserialization based on Corvus.ContentHandling. +/// +public class RegisteredContentType2 +{ + public const string RegisteredContentType = "registeredContentType2"; +} \ No newline at end of file diff --git a/Solutions/Menes.Specs/Fakes/RegisteredDiscriminatedType1.cs b/Solutions/Menes.Specs/Fakes/RegisteredDiscriminatedType1.cs new file mode 100644 index 000000000..037dc7dd9 --- /dev/null +++ b/Solutions/Menes.Specs/Fakes/RegisteredDiscriminatedType1.cs @@ -0,0 +1,12 @@ +// +// Copyright (c) Endjin Limited. All rights reserved. +// + +namespace Menes.Specs.Fakes; + +/// +/// A class used to test object deserialization based on . +/// +public class RegisteredDiscriminatedType1 +{ +} \ No newline at end of file diff --git a/Solutions/Menes.Specs/Fakes/RegisteredDiscriminatedType2.cs b/Solutions/Menes.Specs/Fakes/RegisteredDiscriminatedType2.cs new file mode 100644 index 000000000..746e7d022 --- /dev/null +++ b/Solutions/Menes.Specs/Fakes/RegisteredDiscriminatedType2.cs @@ -0,0 +1,12 @@ +// +// Copyright (c) Endjin Limited. All rights reserved. +// + +namespace Menes.Specs.Fakes; + +/// +/// A class used to test object deserialization based on . +/// +public class RegisteredDiscriminatedType2 +{ +} \ No newline at end of file diff --git a/Solutions/Menes.Specs/Features/JsonTypeConversion/ArrayInputParsing.feature b/Solutions/Menes.Specs/Features/JsonTypeConversion/ArrayInputParsing.feature new file mode 100644 index 000000000..dc952c30e --- /dev/null +++ b/Solutions/Menes.Specs/Features/JsonTypeConversion/ArrayInputParsing.feature @@ -0,0 +1,213 @@ +@perScenarioContainer + +Feature: Array Input Parsing + In order to implement a web API + As a developer + I want to be able to specify JSON array inputs within the OpenAPI specification and have corresponding input parameters or bodies deserialized and validated + +Scenario: Array body with properties of simple types + Given I have constructed the OpenAPI specification with a request body of type array, containing items of type 'integer' + When I try to parse the value '[1,2,3,4,5]' as the request body + Then the parameter body should be [1,2,3,4,5] of type System.String + +Scenario: Array parameter with items of simple type + Given I have constructed the OpenAPI specification with a parameter with name 'openApiArray', of type array, containing items of type 'integer' + When I try to parse the query value '[1,2,3,4,5]' as the parameter 'openApiArray' + Then the parameter openApiArray should be [1,2,3,4,5] of type System.String + +Scenario: Array parameter with properties of simple types via default + Given I have constructed the OpenAPI specification with a parameter with name 'openApiArray', of type array, containing items of type 'integer', and the default value for the parameter is '[1,2,3,4,5]' + When I try to parse the default value + Then the parameter openApiArray should be [1,2,3,4,5] of type System.String + + +Scenario: Array body with items of array type + Given I have constructed the OpenAPI specification with a request body of type array, containing items which are arrays themselves with item type 'integer' + When I try to parse the value '[[1],[2,3],[4,5,6]]' as the request body + Then the parameter body should be [[1],[2,3],[4,5,6]] of type System.String + +Scenario: Array parameter with items of array type + Given I have constructed the OpenAPI specification with a parameter with name 'openApiNestedArray', of type array, containing items which are arrays themselves with item type 'integer' + When I try to parse the query value '[[1],[2,3],[4,5,6]]' as the parameter 'openApiNestedArray' + Then the parameter openApiNestedArray should be [[1],[2,3],[4,5,6]] of type System.String + +Scenario: Array parameter with items of array type via default + Given I have constructed the OpenAPI specification with a parameter with name 'openApiNestedArray', of type array, containing items which are arrays themselves with item type 'integer', and the default value for the parameter is '[[1],[2,3],[4,5,6]]' + When I try to parse the default value + Then the parameter openApiNestedArray should be [[1],[2,3],[4,5,6]] of type System.String + + +Scenario: Array body with items of object type with properties of complex types + Given I have constructed the OpenAPI specification with a request body of type array, containing items which are arrays themselves with item type 'integer' + Given I have constructed the OpenAPI specification with a request body of type object, containing properties in the structure '{ "names": { "type": "array", "items": { "type": "string" } }, "details": {"type": "object", "properties": { "age": { "type": "integer" }, "hairColour": { "type": "string" } } } }' + When I try to parse the value '{"names":["Ed","Ian"],"details":{"age":24,"hairColour":"Brown"}}' as the request body + Then the parameter body should be {"names":["Ed","Ian"],"details":{"age":24,"hairColour":"Brown"}} of type System.String + +Scenario: Array parameter with items of object type with properties of complex types + Given I have constructed the OpenAPI specification with a parameter with name 'openApiArrayWithObjectItems', of type array, containing items which are objects which has the property structure '{ "id": { "type": "integer" }, "name": {"type": "string"} }', and the default value for the parameter is '[{"id": 123, "name": "Ed"}, {"id": 456, "name": "Ian"}]' + When I try to parse the default value + Then the parameter openApiArrayWithObjectItems should be [{"id":123,"name":"Ed"},{"id":456,"name":"Ian"}] of type System.String + +Scenario: Array parameter with items of object type with properties of complex types via default + Given I have constructed the OpenAPI specification with a parameter with name 'openApiObjectWithComplexProperties', of type object, containing properties in the structure '{ "names": { "type": "array", "items": { "type": "string" } }, "details": {"type": "object", "properties": { "age": { "type": "integer" }, "hairColour": { "type": "string" } } } }', and the default value for the parameter is '{"names":["Ed","Ian"],"details":{"age":24,"hairColour":"Brown"}}' + When I try to parse the default value + Then the parameter openApiObjectWithComplexProperties should be {"names":["Ed","Ian"],"details":{"age":24,"hairColour":"Brown"}} of type System.String + + +Scenario Outline: Incorrect body values with array simple types + Given I have constructed the OpenAPI specification with a request body of type array, containing items of type 'integer' + When I try to parse the value '' as the request body and expect an error + Then an 'OpenApiBadRequestException' should be thrown + + Examples: + | Value | + | {"id":123.0,"name":"Ed"} | + | true | + | null | + | 42 | + | 42.0 | + | "text" | + | [1,2,3.0] | + | ["1", "2"] | + | [true, false] | + +Scenario Outline: Incorrect body values with properties of complex types + Given I have constructed the OpenAPI specification with a request body of type array, containing items which are objects which has the property structure '{ "names": { "type": "array", "items": { "type": "string" } }, "details": {"type": "object", "properties": { "age": { "type": "integer" }, "hairColour": { "type": "string" } } } }' + When I try to parse the value '' as the request body and expect an error + Then an 'OpenApiBadRequestException' should be thrown + + Examples: + | Value | + | {"names":["Ed","Ian"],"details":{"age":24,"hairColour":"Brown"}} | + | [{"names":["Ed",42],"details":{"age":24,"hairColour":"Brown"}}] | + | [{"names":["Ed",true],"details":{"age":24,"hairColour":"Brown"}}] | + | [{"names":["Ed",null],"details":{"age":24,"hairColour":"Brown"}}] | + | [{"names":["Ed",{}],"details":{"age":24,"hairColour":"Brown"}}] | + | [{"names":["Ed","Ian"],"details":{"age":24.1,"hairColour":"Brown"}}] | + | [{"names":["Ed","Ian"],"details":{"age":"24","hairColour":"Brown"}}] | + | [{"names":["Ed","Ian"],"details":{"age":"Ian","hairColour":"Brown"}}] | + | [{"names":["Ed","Ian"],"details":{"age":true,"hairColour":"Brown"}}] | + | [{"names":["Ed","Ian"],"details":{"age":null,"hairColour":"Brown"}}] | + | [{"names":["Ed","Ian"],"details":{"age":{},"hairColour":"Brown"}}] | + | [{"names":["Ed","Ian"],"details":{"age":24,"hairColour":1}}] | + | [{"names":["Ed","Ian"],"details":{"age":24,"hairColour":1.1}}] | + | [{"names":["Ed","Ian"],"details":{"age":24,"hairColour":null}}] | + | [{"names":["Ed","Ian"],"details":{"age":24,"hairColour":true}}] | + | [{"names":["Ed","Ian"],"details":{"age":24,"hairColour":{}}}] | + | null | + | true | + | false | + | 42 | + | 42.0 | + | "text" | + +Scenario Outline: Incorrect parameter values + Given I have constructed the OpenAPI specification with a parameter with name 'openApiObject', of type object, containing properties in the structure '{ "id": { "type": "integer" }, "name": {"type": "string"} }' + When I try to parse the query value '' as the parameter 'openApiObject' and expect an error + Then an 'OpenApiBadRequestException' should be thrown + + Examples: + | Value | + | {"id":123.0,"name":"Ed"} | + | {"id":"123","name":"Ed"} | + | {"id":true,"name":"Ed"} | + | {"id":null,"name":"Ed"} | + | {"id":123,"name":true} | + | {"id":123,"name":null} | + | {"id":123,"name":456} | + | {"id":123,"name":456.7} | + | null | + | true | + | false | + | 42 | + | 42.0 | + | text | + +Scenario Outline: Incorrect parameter values with complex types + Given I have constructed the OpenAPI specification with a parameter with name 'openApiObjectWithComplexProperties', of type object, containing properties in the structure '{ "names": { "type": "array", "items": { "type": "string" } }, "details": {"type": "object", "properties": { "age": { "type": "integer" }, "hairColour": { "type": "string" } } } }' + When I try to parse the query value '' as the parameter 'openApiObjectWithComplexProperties' and expect an error + Then an 'OpenApiBadRequestException' should be thrown + + Examples: + | Value | + | {"names":["Ed",42],"details":{"age":24,"hairColour":"Brown"}} | + | {"names":["Ed",true],"details":{"age":24,"hairColour":"Brown"}} | + | {"names":["Ed",null],"details":{"age":24,"hairColour":"Brown"}} | + | {"names":["Ed",{}],"details":{"age":24,"hairColour":"Brown"}} | + | {"names":["Ed","Ian"],"details":{"age":24.1,"hairColour":"Brown"}} | + | {"names":["Ed","Ian"],"details":{"age":"24","hairColour":"Brown"}} | + | {"names":["Ed","Ian"],"details":{"age":"Ian","hairColour":"Brown"}} | + | {"names":["Ed","Ian"],"details":{"age":true,"hairColour":"Brown"}} | + | {"names":["Ed","Ian"],"details":{"age":null,"hairColour":"Brown"}} | + | {"names":["Ed","Ian"],"details":{"age":{},"hairColour":"Brown"}} | + | {"names":["Ed","Ian"],"details":{"age":24,"hairColour":1}} | + | {"names":["Ed","Ian"],"details":{"age":24,"hairColour":1.1}} | + | {"names":["Ed","Ian"],"details":{"age":24,"hairColour":null}} | + | {"names":["Ed","Ian"],"details":{"age":24,"hairColour":true}} | + | {"names":["Ed","Ian"],"details":{"age":24,"hairColour":{}}} | + | null | + | true | + | false | + | 42 | + | 42.0 | + | text | + +Scenario Outline: Incorrect parameter defaults + Given I have constructed the OpenAPI specification with a parameter with name 'openApiObject', of type object, containing properties in the structure '{ "id": { "type": "integer" }, "name": {"type": "string"} }', and the default value for the parameter is '' + When I try to parse the default value and expect an error + Then an 'OpenApiSpecificationException' should be thrown + + Examples: + | DefaultValue | + | {"id":123.0,"name":"Ed"} | + | {"id":"123","name":"Ed"} | + | {"id":true,"name":"Ed"} | + | {"id":null,"name":"Ed"} | + | {"id":123,"name":null} | + # You'd expect these next 3 to fail, but Microsoft.OpenApi knows that "name" is supposed to be a string, + # so it "helpfully" converts JSON values of type bool or number to strings in the schema's default + # values. It's not clear whether that's correct. We might need to revisit this when moving to + # Corvus.JsonSchema. + #| {"id":123,"name":true} | + #| {"id":123,"name":456} | + #| {"id":123,"name":456.7} | + | null | + | true | + | false | + | 42 | + | 42.0 | + | text | + + +Scenario Outline: Incorrect parameter defaults with complex types + Given I have constructed the OpenAPI specification with a parameter with name 'openApiObjectWithComplexProperties', of type object, containing properties in the structure '{ "names": { "type": "array", "items": { "type": "string" } }, "details": {"type": "object", "properties": { "age": { "type": "integer" }, "hairColour": { "type": "string" } } } }', and the default value for the parameter is '' + When I try to parse the default value and expect an error + Then an 'OpenApiSpecificationException' should be thrown + + Examples: + | DefaultValue | + | {"names":["Ed",null],"details":{"age":24,"hairColour":"Brown"}} | + | {"names":["Ed",{}],"details":{"age":24,"hairColour":"Brown"}} | + | {"names":["Ed","Ian"],"details":{"age":24.1,"hairColour":"Brown"}} | + | {"names":["Ed","Ian"],"details":{"age":"24","hairColour":"Brown"}} | + | {"names":["Ed","Ian"],"details":{"age":"Ian","hairColour":"Brown"}} | + | {"names":["Ed","Ian"],"details":{"age":true,"hairColour":"Brown"}} | + | {"names":["Ed","Ian"],"details":{"age":null,"hairColour":"Brown"}} | + | {"names":["Ed","Ian"],"details":{"age":{},"hairColour":"Brown"}} | + | {"names":["Ed","Ian"],"details":{"age":24,"hairColour":null}} | + | {"names":["Ed","Ian"],"details":{"age":24,"hairColour":{}}} | + # You'd expect these next 5 to fail, but Microsoft.OpenApi knows that "name" is supposed to be a string, + # so it "helpfully" converts JSON values of type bool or number to strings in the schema's default + # values. It's not clear whether that's correct. We might need to revisit this when moving to + # Corvus.JsonSchema. + #| {"names":["Ed",42],"details":{"age":24,"hairColour":"Brown"}} | + #| {"names":["Ed",true],"details":{"age":24,"hairColour":"Brown"}} | + #| {"names":["Ed","Ian"],"details":{"age":24,"hairColour":1}} | + #| {"names":["Ed","Ian"],"details":{"age":24,"hairColour":1.1}} | + #| {"names":["Ed","Ian"],"details":{"age":24,"hairColour":true}} | + | null | + | true | + | false | + | 42 | + | 42.0 | + | text | diff --git a/Solutions/Menes.Specs/Features/JsonTypeConversion/BooleanInputParsing.feature b/Solutions/Menes.Specs/Features/JsonTypeConversion/BooleanInputParsing.feature new file mode 100644 index 000000000..f2aa95a5b --- /dev/null +++ b/Solutions/Menes.Specs/Features/JsonTypeConversion/BooleanInputParsing.feature @@ -0,0 +1,181 @@ +@perScenarioContainer + +Feature: Boolean Input Parsing + In order to implement a web API + As a developer + I want to be able to specify boolean inputs within the OpenAPI specification and have corresponding input parameters or bodies deserialized and validated + +Scenario Outline: Body with valid values for simple types + Given I have constructed the OpenAPI specification with a request body of type 'boolean', and format '' + When I try to parse the value '' as the request body + Then the parameter body should be of type System.Boolean + + Examples: + | Value | ExpectedResult | + | true | true | + | false | false | + +Scenario Outline: Parameters with valid values for simple types + Given I have constructed the OpenAPI specification with a parameter with name 'openApiBoolean', type 'boolean', and format '' + When I try to parse the value '' as the parameter 'openApiBoolean' + Then the parameter openApiBoolean should be of type System.Boolean + + Examples: + | ParameterLocation | Value | ExpectedResult | + | path | true | true | + | path | false | false | + | query | true | true | + | query | false | false | + | header | true | true | + | header | false | false | + | cookie | true | true | + | cookie | false | false | + +Scenario Outline: Parameters with valid default values for simple types + Given I have constructed the OpenAPI specification with a parameter with name 'openApiBoolean', type 'boolean', format '' and default value '' + When I try to parse the default value + Then the parameter openApiBoolean should be of type System.Boolean + + Examples: + | ParameterLocation | DefaultValue | ExpectedResult | + | query | true | true | + | query | false | false | + | header | true | true | + | header | false | false | + | cookie | true | true | + | cookie | false | false | + +Scenario: Array body with items of boolean type + Given I have constructed the OpenAPI specification with a request body of type array, containing items of type 'boolean' + When I try to parse the value '[true,false,true,true,false]' as the request body + Then the parameter body should be [true,false,true,true,false] of type System.String + +Scenario: Array parameter with items of boolean type + Given I have constructed the OpenAPI specification with a parameter with name 'openApiArray', of type array, containing items of type 'boolean' + When I try to parse the query value '[true,false,true,true,false]' as the parameter 'openApiArray' + Then the parameter openApiArray should be [true,false,true,true,false] of type System.String + +Scenario: Array parameter with items of boolean type via default + Given I have constructed the OpenAPI specification with a parameter with name 'openApiArray', of type array, containing items of type 'boolean', and the default value for the parameter is '[true,false,true,true,false]' + When I try to parse the default value + Then the parameter openApiArray should be [true,false,true,true,false] of type System.String + + +Scenario: Array body with items of array type + Given I have constructed the OpenAPI specification with a request body of type array, containing items which are arrays themselves with item type 'boolean' + When I try to parse the value '[[true],[false,true],[true,false,true]]' as the request body + Then the parameter body should be [[true],[false,true],[true,false,true]] of type System.String + +Scenario: Array parameter with items of array type + Given I have constructed the OpenAPI specification with a parameter with name 'openApiNestedArray', of type array, containing items which are arrays themselves with item type 'boolean' + When I try to parse the query value '[[true],[false,true],[true,false,true]]' as the parameter 'openApiNestedArray' + Then the parameter openApiNestedArray should be [[true],[false,true],[true,false,true]] of type System.String + +Scenario: Array parameter with items of array type via default + Given I have constructed the OpenAPI specification with a parameter with name 'openApiNestedArray', of type array, containing items which are arrays themselves with item type 'boolean', and the default value for the parameter is '[[true],[false,true],[true,false,true]]' + When I try to parse the default value + Then the parameter openApiNestedArray should be [[true],[false,true],[true,false,true]] of type System.String + + +Scenario: Array body with items of object type + Given I have constructed the OpenAPI specification with a request body of type array, containing items which are objects which has the property structure '{ "v1": { "type": "boolean" }, "v2": {"type": "boolean"} }' + When I try to parse the value '[{"v1":true,"v2":false},{"v1":false,"v2":true}]' as the request body + Then the parameter body should be [{"v1":true,"v2":false},{"v1":false,"v2":true}] of type System.String + +Scenario: Array parameter with items of object type + Given I have constructed the OpenAPI specification with a parameter with name 'openApiArrayWithObjectItems', of type array, containing items which are objects which has the property structure '{ "v1": { "type": "boolean" }, "v2": {"type": "boolean"} }' + When I try to parse the query value '[{"v1":true,"v2":false},{"v1":false,"v2":true}]' as the parameter 'openApiArrayWithObjectItems' + Then the parameter openApiArrayWithObjectItems should be [{"v1":true,"v2":false},{"v1":false,"v2":true}] of type System.String + +Scenario: Array parameter with items of object type via default + Given I have constructed the OpenAPI specification with a parameter with name 'openApiArrayWithObjectItems', of type array, containing items which are objects which has the property structure '{ "v1": { "type": "boolean" }, "v2": {"type": "boolean"} }', and the default value for the parameter is '[{"v1":true,"v2":false},{"v1":false,"v2":true}]' + When I try to parse the default value + Then the parameter openApiArrayWithObjectItems should be [{"v1":true,"v2":false},{"v1":false,"v2":true}] of type System.String + + +Scenario: Object body with properties of simple types + Given I have constructed the OpenAPI specification with a request body of type object, containing properties in the structure '{ "v1": { "type": "boolean" }, "v2": {"type": "boolean"} }' + When I try to parse the value '{"v1":true,"v2":false}' as the request body + Then the parameter body should be {"v1":true,"v2":false} of type System.String + +Scenario: Object parameter with properties of simple types + Given I have constructed the OpenAPI specification with a parameter with name 'openApiObject', of type object, containing properties in the structure '{ "v1": { "type": "boolean" }, "v2": {"type": "boolean"} }' + When I try to parse the query value '{"v1":true,"v2":false}' as the parameter 'openApiObject' + Then the parameter openApiObject should be {"v1":true,"v2":false} of type System.String + +Scenario: Object parameter with properties of simple types via default + Given I have constructed the OpenAPI specification with a parameter with name 'openApiObject', of type object, containing properties in the structure '{ "v1": { "type": "boolean" }, "v2": {"type": "boolean"} }', and the default value for the parameter is '{"v1":true,"v2":false}' + When I try to parse the default value + Then the parameter openApiObject should be {"v1":true,"v2":false} of type System.String + + +Scenario: Object body with properties of complex types + Given I have constructed the OpenAPI specification with a request body of type object, containing properties in the structure '{ "values": { "type": "array", "items": { "type": "boolean" } }, "details": {"type": "object", "properties": { "v1": { "type": "boolean" }, "v2": { "type": "boolean" } } } }' + When I try to parse the value '{"values":[true,false],"details":{"v1":true,"v2":false}}' as the request body + Then the parameter body should be {"values":[true,false],"details":{"v1":true,"v2":false}} of type System.String + +Scenario: Object parameter with properties of complex types + Given I have constructed the OpenAPI specification with a parameter with name 'openApiObjectWithComplexProperties', of type object, containing properties in the structure '{ "values": { "type": "array", "items": { "type": "boolean" } }, "details": {"type": "object", "properties": { "v1": { "type": "boolean" }, "v2": { "type": "boolean" } } } }' + When I try to parse the query value '{"values":[true,false],"details":{"v1":true,"v2":false}}' as the parameter 'openApiObjectWithComplexProperties' + Then the parameter openApiObjectWithComplexProperties should be {"values":[true,false],"details":{"v1":true,"v2":false}} of type System.String + +Scenario: Object parameter with properties of complex types via default + Given I have constructed the OpenAPI specification with a parameter with name 'openApiObjectWithComplexProperties', of type object, containing properties in the structure '{ "values": { "type": "array", "items": { "type": "boolean" } }, "details": {"type": "object", "properties": { "v1": { "type": "boolean" }, "v2": { "type": "boolean" } } } }', and the default value for the parameter is '{"values":[true,false],"details":{"v1":true,"v2":false}}' + When I try to parse the default value + Then the parameter openApiObjectWithComplexProperties should be {"values":[true,false],"details":{"v1":true,"v2":false}} of type System.String + + +Scenario Outline: Incorrect body values + Given I have constructed the OpenAPI specification with a request body of type 'boolean', and format '' + When I try to parse the value '' as the request body and expect an error + Then an 'OpenApiBadRequestException' should be thrown + + Examples: + | Value | + | "This is certainly not a boolean" | + | thisisnotevenvalidjson | + | null | + | 42 | + | 9223372036854775807 | + | 9223372036854775808123123123 | + | "20170721T173228Z" | + +Scenario Outline: Incorrect parameter values + Given I have constructed the OpenAPI specification with a parameter with name 'openApiBoolean', type 'boolean', and format '' + When I try to parse the value '' as the parameter 'openApiBoolean' and expect an error + Then an 'OpenApiBadRequestException' should be thrown + + Examples: + | ParameterLocation | Value | + | query | This is certainly not a boolean | + | query | null | + | query | 42 | + | query | 9223372036854775807 | + | query | 9223372036854775808123123123 | + | query | 20170721T173228Z | + | header | This is certainly not a boolean | + | cookie | This+is+certainly+not+a+boolean | + | path | This+is+certainly+not+a+boolean | + +Scenario Outline: Incorrect parameter defaults + Given I have constructed the OpenAPI specification with a parameter with name 'openApiBoolean', type 'boolean', format '' and default value '' + When I try to parse the default value and expect an error + Then an 'OpenApiSpecificationException' should be thrown + + Examples: + # Not doing path, because it doesn't really make sense to provide a default - we're only going to match + # the path template if all parts are present. (E.g., if the path is /pets/{petId}, we don't expect /pets/ + # to match. + | ParameterLocation | DefaultValue | + | query | "This is certainly not a boolean" | + | query | thisisnotevenvalidjson | + | query | null | + | query | 42 | + | query | 9223372036854775807 | + | query | 9223372036854775808123123123 | + | query | "20170721T173228Z" | + # String values of "true" and "false" are incorrect. The JSON true and false values should be used. + | query | "true" | + | query | "false" | + | header | "This is certainly not a boolean" | + | cookie | "This is certainly not a boolean" | \ No newline at end of file diff --git a/Solutions/Menes.Specs/Features/JsonTypeConversion/BooleanOutputParsing.feature b/Solutions/Menes.Specs/Features/JsonTypeConversion/BooleanOutputParsing.feature new file mode 100644 index 000000000..4fc87775c --- /dev/null +++ b/Solutions/Menes.Specs/Features/JsonTypeConversion/BooleanOutputParsing.feature @@ -0,0 +1,17 @@ +@perScenarioContainer + +Feature: Boolean Output Parsing + In order to implement a web API + As a developer + I want to be able to specify boolean values as or in response bodies within the OpenAPI specification and have corresponding response bodies deserialized and validated + + +Scenario Outline: Body with valid values for simple types + Given I have constructed the OpenAPI specification with a response body of type 'boolean', and format '' + When I try to build a response body from the value '' of type 'System.Boolean' + Then the response body should be '' + + Examples: + | Value | ExpectedResult | + | true | true | + | false | false | diff --git a/Solutions/Menes.Specs/Features/JsonTypeConversion/ByteArrayInputParsing.feature b/Solutions/Menes.Specs/Features/JsonTypeConversion/ByteArrayInputParsing.feature new file mode 100644 index 000000000..59d6b6fe4 --- /dev/null +++ b/Solutions/Menes.Specs/Features/JsonTypeConversion/ByteArrayInputParsing.feature @@ -0,0 +1,96 @@ +@perScenarioContainer + +Feature: Byte Array Input Parsing + In order to implement a web API + As a developer + I want to be able to specify byte-formatted string parameters within the OpenAPI specification and have corresponding input parameters deserialized and validated + +Scenario Outline: Body with valid values for simple types + Given I have constructed the OpenAPI specification with a request body of type 'string', and format 'byte' + When I try to parse the value '' as the request body + Then the parameter body should be of type ByteArrayFromBase64String + + Examples: + | Value | ExpectedResult | + | "U3dhZ2dlciByb2Nrcw==" | U3dhZ2dlciByb2Nrcw== | + | "" | | + +Scenario Outline: Parameters with valid values for simple types + Given I have constructed the OpenAPI specification with a parameter with name 'openApiString', type 'string', and format 'byte' + When I try to parse the value '' as the parameter 'openApiString' + Then the parameter openApiString should be of type ByteArrayFromBase64String + + Examples: + | ParameterLocation | Value | ExpectedResult | + | path | U3dhZ2dlciByb2Nrcw== | U3dhZ2dlciByb2Nrcw== | + | query | U3dhZ2dlciByb2Nrcw== | U3dhZ2dlciByb2Nrcw== | + | cookie | U3dhZ2dlciByb2Nrcw== | U3dhZ2dlciByb2Nrcw== | + | header | U3dhZ2dlciByb2Nrcw== | U3dhZ2dlciByb2Nrcw== | + +Scenario Outline: Parameters with valid default values for simple types + Given I have constructed the OpenAPI specification with a parameter with name 'openApiString', type 'string', format 'byte' and default value '' + When I try to parse the default value + Then the parameter openApiString should be of type ByteArrayFromBase64String + + Examples: + | ParameterLocation | DefaultValue | ExpectedResult | + | query | "U3dhZ2dlciByb2Nrcw==" | U3dhZ2dlciByb2Nrcw== | + | query | | | + | cookie | "U3dhZ2dlciByb2Nrcw==" | U3dhZ2dlciByb2Nrcw== | + | header | "U3dhZ2dlciByb2Nrcw==" | U3dhZ2dlciByb2Nrcw== | + +Scenario Outline: Incorrect body values + Given I have constructed the OpenAPI specification with a request body of type 'string', and format 'byte' + When I try to parse the value '' as the request body and expect an error + Then an 'OpenApiBadRequestException' should be thrown + + Examples: + | Value | + | "This is certainly not base64" | + | "2017-07-21T17:32:28Z" | + | thisisnotevenvalidjson | + | false | + | 42 | + | 9223372036854775808 | + # Because our current code design doesn't enable validation to make a distinction between + # quoted and unquoted text in request bodies, these values that shouldn't be handled as + # valid (because they are non-string JSON values, and therefore cannot possibly be + # base64-formatted string values) are in fact accepted. (Don't be misled by the fact + # that they don't much look like base64. Lots of strings consisting of only alphanumeric + # values are acceptable as base64 if their length happens to be a multiple of 4.) + #| true | + #| null | + #| 9223372036854775808123123123 | + +Scenario Outline: Incorrect parameter values + Given I have constructed the OpenAPI specification with a parameter with name 'openApiString', type 'string', and format 'byte' + When I try to parse the value '' as the parameter 'openApiString' and expect an error + Then an 'OpenApiBadRequestException' should be thrown + + Examples: + | ParameterLocation | Value | + | query | This is certainly not base64 | + | query | false | + | query | 42 | + | query | 9223372036854775808 | + | query | 2017-07-21T17:32:28Z | + | header | This is certainly not a date | + | cookie | This%3Ais+certainly+not+a+date | + | path | This%3Ais+certainly+not+a+date | + +Scenario Outline: Incorrect parameter defaults + Given I have constructed the OpenAPI specification with a parameter with name 'openApiString', type 'string', format 'byte' and default value '' + When I try to parse the default value and expect an error + Then an 'OpenApiSpecificationException' should be thrown + + Examples: + | ParameterLocation | DefaultValue | + | query | "This:is certainly not base64" | + | query | thisisnotevenvalidjson | + | query | false | + | query | 42 | + | query | 9223372036854775808 | + | query | "2017-07-21T17:32:28Z" | + | query | "42" | + | header | "This is certainly not a date" | + | cookie | "This is certainly not a date" | \ No newline at end of file diff --git a/Solutions/Menes.Specs/Features/JsonTypeConversion/ByteArrayOutputParsing.feature b/Solutions/Menes.Specs/Features/JsonTypeConversion/ByteArrayOutputParsing.feature new file mode 100644 index 000000000..990d03217 --- /dev/null +++ b/Solutions/Menes.Specs/Features/JsonTypeConversion/ByteArrayOutputParsing.feature @@ -0,0 +1,17 @@ +@perScenarioContainer + +Feature: Byte Array Output Parsing + In order to implement a web API + As a developer + I want to be able to specify byte-formatted string values as or in response bodies within the OpenAPI specification and have corresponding response bodies deserialized and validated + + +Scenario Outline: Valid values for simple types + Given I have constructed the OpenAPI specification with a response body of type 'string', and format 'byte' + When I try to build a response body from the value '' of type 'ByteArrayFromBase64String' + Then the response body should be '' + + Examples: + | Value | ExpectedResult | + | U3dhZ2dlciByb2Nrcw== | "U3dhZ2dlciByb2Nrcw==" | + | | "" | diff --git a/Solutions/Menes.Specs/Features/JsonTypeConversion/DateInputParsing.feature b/Solutions/Menes.Specs/Features/JsonTypeConversion/DateInputParsing.feature new file mode 100644 index 000000000..477c569aa --- /dev/null +++ b/Solutions/Menes.Specs/Features/JsonTypeConversion/DateInputParsing.feature @@ -0,0 +1,101 @@ +@perScenarioContainer + +Feature: Date Input Parsing + In order to implement a web API + As a developer + I want to be able to specify date-formatted string parameters within the OpenAPI specification and have corresponding input parameters deserialized and validated + +Scenario Outline: Body with valid values for simple types + Given I have constructed the OpenAPI specification with a request body of type 'string', and format 'date' + When I try to parse the value '' as the request body + Then the parameter body should be of type System.DateTimeOffset + + Examples: + | Value | ExpectedResult | + | "2017-07-21" | 2017-07-21T00:00:00Z | + +Scenario Outline: Parameters with valid values for simple types + Given I have constructed the OpenAPI specification with a parameter with name 'openApiDate', type 'string', and format 'date' + When I try to parse the value '' as the parameter 'openApiDate' + Then the parameter openApiDate should be of type System.DateTimeOffset + + Examples: + | ParameterLocation | Value | ExpectedResult | + | path | 2017-07-21 | 2017-07-21T00:00:00Z | + | query | 2017-07-21 | 2017-07-21T00:00:00Z | + | cookie | 2017-07-21 | 2017-07-21T00:00:00Z | + | header | 2017-07-21 | 2017-07-21T00:00:00Z | + +Scenario Outline: Parameters with valid default values for simple types + Given I have constructed the OpenAPI specification with a parameter with name 'openApiDate', type 'string', format 'date' and default value '' + When I try to parse the default value + Then the parameter openApiDate should be of type System.DateTimeOffset + + Examples: + | ParameterLocation | DefaultValue | ExpectedResult | + | query | "2017-07-21" | 2017-07-21T00:00:00Z | + | cookie | "2017-07-21" | 2017-07-21T00:00:00Z | + | header | "2017-07-21" | 2017-07-21T00:00:00Z | + +Scenario Outline: Incorrect body values + Given I have constructed the OpenAPI specification with a request body of type 'string', and format 'date' + When I try to parse the value '' as the request body and expect an error + Then an 'OpenApiBadRequestException' should be thrown + + Examples: + | Value | + | "This is certainly not a date" | + | thisisnotevenvalidjson | + | true | + | false | + | null | + | 42 | + | 9223372036854775808 | + | 9223372036854775808123123123 | + | "20170721T173228Z" | + | "2017-07-21T17:32:28Z" | + +Scenario Outline: Incorrect parameter values + Given I have constructed the OpenAPI specification with a parameter with name 'openApiDate', type 'string', and format 'date' + When I try to parse the value '' as the parameter 'openApiDate' and expect an error + Then an 'OpenApiBadRequestException' should be thrown + + Examples: + | ParameterLocation | Value | + | query | This is certainly not a date | + | query | true | + | query | false | + | query | null | + | query | 42 | + | query | 9223372036854775808 | + | query | 9223372036854775808123123123 | + | query | 20170721T173228Z | + | query | 2017-07-21T17:32:28Z | + | header | This is certainly not a date | + | cookie | This+is+certainly+not+a+date | + | path | This+is+certainly+not+a+date | + +Scenario Outline: Incorrect parameter defaults + Given I have constructed the OpenAPI specification with a parameter with name 'openApiDate', type 'string', format 'date' and default value '' + When I try to parse the default value and expect an error + Then an 'OpenApiSpecificationException' should be thrown + + Examples: + # Not doing path, because it doesn't really make sense to provide a default - we're only going to match + # the path template if all parts are present. (E.g., if the path is /pets/{petId}, we don't expect /pets/ + # to match. + | ParameterLocation | DefaultValue | + | query | "This is certainly not a date" | + | query | thisisnotevenvalidjson | + | query | true | + | query | false | + | query | 42 | + | query | 9223372036854775808 | + | query | 9223372036854775808123123123 | + | query | "20170721T173228Z" | + # I'm not convinced this next one should work, but Microsoft.OpenApi parses it happily, and discards + # the time portion, leaving just the 2017-07-21 date. + #| query | "2017-07-21T17:32:28Z" | + | query | "42" | + | header | "This is certainly not a date" | + | cookie | "This is certainly not a date" | \ No newline at end of file diff --git a/Solutions/Menes.Specs/Features/JsonTypeConversion/DateOutputParsing.feature b/Solutions/Menes.Specs/Features/JsonTypeConversion/DateOutputParsing.feature new file mode 100644 index 000000000..f778a2027 --- /dev/null +++ b/Solutions/Menes.Specs/Features/JsonTypeConversion/DateOutputParsing.feature @@ -0,0 +1,16 @@ +@perScenarioContainer + +Feature: Date Output Parsing + In order to implement a web API + As a developer + I want to be able to specify date values as or in response bodies within the OpenAPI specification and have corresponding response bodies deserialized and validated + + +Scenario Outline: Valid values for simple types + Given I have constructed the OpenAPI specification with a response body of type 'string', and format 'date' + When I try to build a response body from the value '' of type 'System.DateTimeOffset' + Then the response body should be '' + + Examples: + | Value | ExpectedResult | + | 2017-07-21T00:00:00Z | "2017-07-21" | diff --git a/Solutions/Menes.Specs/Features/JsonTypeConversion/DateTimeInputParsing.feature b/Solutions/Menes.Specs/Features/JsonTypeConversion/DateTimeInputParsing.feature new file mode 100644 index 000000000..81936dbee --- /dev/null +++ b/Solutions/Menes.Specs/Features/JsonTypeConversion/DateTimeInputParsing.feature @@ -0,0 +1,106 @@ +@perScenarioContainer + +Feature: DateTime Input Parsing + In order to implement a web API + As a developer + I want to be able to specify date-time-formatted string parameters within the OpenAPI specification and have corresponding input parameters deserialized and validated + +Scenario Outline: Body with valid values for simple types + Given I have constructed the OpenAPI specification with a request body of type 'string', and format 'date-time' + When I try to parse the value '' as the request body + Then the parameter body should be of type System.String + + Examples: + | Value | ExpectedResult | + | "2017-07-21T17:32:28Z" | 2017-07-21T17:32:28Z | + | "2017-07-21T17:32:28" | 2017-07-21T17:32:28 | + | "2017-07-21" | 2017-07-21 | + | "20170721" | 20170721 | + | "2017-07" | 2017-07 | + | "2017" | 2017 | + +Scenario Outline: Parameters with valid values for simple types + Given I have constructed the OpenAPI specification with a parameter with name 'openApiDateTime', type 'string', and format 'date-time' + When I try to parse the value '' as the parameter 'openApiDateTime' + Then the parameter openApiDateTime should be of type System.String + + Examples: + | ParameterLocation | Value | ExpectedResult | + | path | 2017-07-21 | 2017-07-21 | + | query | 2017-07-21 | 2017-07-21 | + | cookie | 2017-07-21 | 2017-07-21 | + | header | 2017-07-21 | 2017-07-21 | + +Scenario Outline: Parameters with valid default values for simple types + Given I have constructed the OpenAPI specification with a parameter with name 'openApiDateTime', type 'string', format 'date-time' and default value '' + When I try to parse the default value + Then the parameter openApiDateTime should be of type System.String + + Examples: + | ParameterLocation | DefaultValue | ExpectedResult | + | query | "2017-07-21T17:32:28+00:00" | 2017-07-21T17:32:28+00:00 | + | cookie | "2017-07-21T17:32:28+00:00" | 2017-07-21T17:32:28+00:00 | + | header | "2017-07-21T17:32:28+00:00" | 2017-07-21T17:32:28+00:00 | + +Scenario Outline: Incorrect body values + Given I have constructed the OpenAPI specification with a request body of type 'string', and format 'date' + When I try to parse the value '' as the request body and expect an error + Then an 'OpenApiBadRequestException' should be thrown + + Examples: + | Value | + | "This is certainly not a date" | + | thisisnotevenvalidjson | + | true | + | false | + | null | + | 42 | + | 9223372036854775808 | + | 9223372036854775808123123123 | + | "20170721T173228Z" | + | "2017-07-21T17:32:28Z" | + +Scenario Outline: Incorrect parameter values + Given I have constructed the OpenAPI specification with a parameter with name 'openApiDateTime', type 'string', and format 'date' + When I try to parse the value '' as the parameter 'openApiDateTime' and expect an error + Then an 'OpenApiBadRequestException' should be thrown + + Examples: + | ParameterLocation | Value | + | query | This is certainly not a date | + | query | true | + | query | false | + | query | null | + | query | 42 | + | query | 9223372036854775808 | + | query | 9223372036854775808123123123 | + | query | 20170721T173228Z | + | query | 2017-07-21T17:32:28Z | + | header | This is certainly not a date | + | cookie | This+is+certainly+not+a+date | + | path | This+is+certainly+not+a+date | + +Scenario Outline: Incorrect parameter defaults + Given I have constructed the OpenAPI specification with a parameter with name 'openApiDateTime', type 'string', format 'date' and default value '' + When I try to parse the default value and expect an error + Then an 'OpenApiSpecificationException' should be thrown + + Examples: + # Not doing path, because it doesn't really make sense to provide a default - we're only going to match + # the path template if all parts are present. (E.g., if the path is /pets/{petId}, we don't expect /pets/ + # to match. + | ParameterLocation | DefaultValue | + | query | "This is certainly not a date" | + | query | thisisnotevenvalidjson | + | query | true | + | query | false | + | query | 42 | + | query | 9223372036854775808 | + | query | 9223372036854775808123123123 | + | query | "20170721T173228Z" | + # I'm not convinced this next one should work, but Microsoft.OpenApi parses it happily, and discards + # the time portion, leaving just the 2017-07-21 date. + #| query | "2017-07-21T17:32:28Z" | + | query | "42" | + | header | "This is certainly not a date" | + | cookie | "This is certainly not a date" | \ No newline at end of file diff --git a/Solutions/Menes.Specs/Features/JsonTypeConversion/DateTimeOutputParsing.feature b/Solutions/Menes.Specs/Features/JsonTypeConversion/DateTimeOutputParsing.feature new file mode 100644 index 000000000..845a2f6d0 --- /dev/null +++ b/Solutions/Menes.Specs/Features/JsonTypeConversion/DateTimeOutputParsing.feature @@ -0,0 +1,16 @@ +@perScenarioContainer + +Feature: DateTime Output Parsing + In order to implement a web API + As a developer + I want to be able to specify date-time values as or in response bodies within the OpenAPI specification and have corresponding response bodies deserialized and validated + + +Scenario Outline: Valid values for simple types + Given I have constructed the OpenAPI specification with a response body of type 'string', and format 'date-time' + When I try to build a response body from the value '' of type 'System.String' + Then the response body should be '' + + Examples: + | Value | ExpectedResult | + | 2017-07-21T00:00:00Z | "2017-07-21T00:00:00Z" | diff --git a/Solutions/Menes.Specs/Features/JsonTypeConversion/DoubleInputParsing.feature b/Solutions/Menes.Specs/Features/JsonTypeConversion/DoubleInputParsing.feature new file mode 100644 index 000000000..4a6efd9ae --- /dev/null +++ b/Solutions/Menes.Specs/Features/JsonTypeConversion/DoubleInputParsing.feature @@ -0,0 +1,177 @@ +@perScenarioContainer + +Feature: Double Input Parsing + In order to implement a web API + As a developer + I want to be able to specify double parameters within the OpenAPI specification and have corresponding input parameters deserialized and validated + +Scenario Outline: Body with valid values for simple types + Given I have constructed the OpenAPI specification with a request body of type 'number', and format 'double' + When I try to parse the value '' as the request body + Then the parameter body should be of type System.Double + + Examples: + | Value | ExpectedResult | + | 1234 | 1234 | + | 0 | 0 | + | -42 | -42 | + | 2147483647 | 2147483647 | + | -2147483648 | -2147483648 | + | 1234.5 | 1234.5 | + | -1234.567 | -1234.567 | + | -1234.5678987 | -1234.5678987 | + +Scenario Outline: Parameters with valid values for simple types + Given I have constructed the OpenAPI specification with a parameter with name 'openApiDouble', type 'number', and format 'double' + When I try to parse the value '' as the parameter 'openApiDouble' + Then the parameter openApiDouble should be of type System.Double + + Examples: + | ParameterLocation | Value | ExpectedResult | + | path | 1234 | 1234 | + | path | 0 | 0 | + | path | -42 | -42 | + | path | 2147483647 | 2147483647 | + | path | -2147483648 | -2147483648 | + | path | 1234.5 | 1234.5 | + | path | -1234.567 | -1234.567 | + | path | -1234.5678987 | -1234.5678987 | + | query | 1234 | 1234 | + | query | 0 | 0 | + | query | -42 | -42 | + | header | 1234 | 1234 | + | header | 0 | 0 | + | header | -42 | -42 | + | cookie | 1234 | 1234 | + | cookie | 0 | 0 | + | cookie | -42 | -42 | + +Scenario: Array body with items of integer type + Given I have constructed the OpenAPI specification with a request body of type array, containing items of type 'number' and format 'double' + When I try to parse the value '[1,2,3,1234.5,-1234.5678987]' as the request body + Then the parameter body should be [1,2,3,1234.5,-1234.5678987] of type System.String + +Scenario: Array parameter with items of integer type + Given I have constructed the OpenAPI specification with a parameter with name 'openApiArray', of type array, containing items of type 'number' and format 'double' + When I try to parse the query value '[1,-1234.5678987,3,4,5]' as the parameter 'openApiArray' + Then the parameter openApiArray should be [1,-1234.5678987,3,4,5] of type System.String + +Scenario: Array parameter with items of integer type via default + Given I have constructed the OpenAPI specification with a parameter with name 'openApiArray', of type array, containing items of type 'number' and format 'double', and the default value for the parameter is '[1.0,2.0,-1234.5678987,4.0,5.0]' + When I try to parse the default value + Then the parameter openApiArray should be [1.0,2.0,-1234.5678987,4.0,5.0] of type System.String + + +Scenario: Array body with items of array type + Given I have constructed the OpenAPI specification with a request body of type array, containing items which are arrays themselves with item type 'number' and format 'double' + When I try to parse the value '[[1],[2,3],[4,5,6]]' as the request body + Then the parameter body should be [[1],[2,3],[4,5,6]] of type System.String + +Scenario: Array parameter with items of array type + Given I have constructed the OpenAPI specification with a parameter with name 'openApiNestedArray', of type array, containing items which are arrays themselves with item type 'number' and format 'double' + When I try to parse the query value '[[1],[2,-1234.567],[4,5,6]]' as the parameter 'openApiNestedArray' + Then the parameter openApiNestedArray should be [[1],[2,-1234.567],[4,5,6]] of type System.String + +Scenario: Array parameter with items of array type via default + Given I have constructed the OpenAPI specification with a parameter with name 'openApiNestedArray', of type array, containing items which are arrays themselves with item type 'number' and format 'double', and the default value for the parameter is '[[1.0],[2.0,3.0],[4.0,5.0,6.0]]' + When I try to parse the default value + Then the parameter openApiNestedArray should be [[1.0],[2.0,3.0],[4.0,5.0,6.0]] of type System.String + + +Scenario: Array body with items of object type + Given I have constructed the OpenAPI specification with a request body of type array, containing items which are objects which has the property structure '{ "v1": {"type":"number","format": "double"}, "v2": {"type":"number","format": "double"} }' + When I try to parse the value '[{"v1":1,"v2":2},{"v1":-3,"v2":0}]' as the request body + Then the parameter body should be [{"v1":1,"v2":2},{"v1":-3,"v2":0}] of type System.String + +Scenario: Array parameter with items of object type + Given I have constructed the OpenAPI specification with a parameter with name 'openApiArrayWithObjectItems', of type array, containing items which are objects which has the property structure '{ "v1": { "type":"number","format": "double" }, "v2": {"type":"number","format": "double"} }' + When I try to parse the query value '[{"v1":1234.5678987,"v2":2},{"v1":-3,"v2":0}]' as the parameter 'openApiArrayWithObjectItems' + Then the parameter openApiArrayWithObjectItems should be [{"v1":1234.5678987,"v2":2},{"v1":-3,"v2":0}] of type System.String + +Scenario: Array parameter with items of object type via default + Given I have constructed the OpenAPI specification with a parameter with name 'openApiArrayWithObjectItems', of type array, containing items which are objects which has the property structure '{ "v1": { "type":"number","format": "double" }, "v2": {"type":"number","format": "double"} }', and the default value for the parameter is '[{"v1":1.0,"v2":1234.5678987},{"v1":-3.0,"v2":0.0}]' + When I try to parse the default value + Then the parameter openApiArrayWithObjectItems should be [{"v1":1.0,"v2":1234.5678987},{"v1":-3.0,"v2":0.0}] of type System.String + + +Scenario: Object body with properties of simple types + Given I have constructed the OpenAPI specification with a request body of type object, containing properties in the structure '{ "v1": { "type":"number","format": "double" }, "v2": {"type":"number","format": "double"} }' + When I try to parse the value '{"v1": -42, "v2": 1234.5678987}' as the request body + Then the parameter body should be {"v1": -42, "v2": 1234.5678987} of type System.String + +Scenario: Object parameter with properties of simple types + Given I have constructed the OpenAPI specification with a parameter with name 'openApiObject', of type object, containing properties in the structure '{ "v1": { "type":"number","format": "double" }, "v2": {"type":"number","format": "double"} }' + When I try to parse the query value '{"v1": -42, "v2": 23}' as the parameter 'openApiObject' + Then the parameter openApiObject should be {"v1": -42, "v2": 23} of type System.String + +Scenario: Object parameter with properties of simple types via default + Given I have constructed the OpenAPI specification with a parameter with name 'openApiObject', of type object, containing properties in the structure '{ "v1": { "type":"number","format": "double" }, "v2": {"type":"number","format": "double"} }', and the default value for the parameter is '{"v1":-42.0,"v2":23.0}' + When I try to parse the default value + Then the parameter openApiObject should be {"v1":-42.0,"v2":23.0} of type System.String + + +Scenario: Object body with properties of complex types + Given I have constructed the OpenAPI specification with a request body of type object, containing properties in the structure '{ "values": { "type": "array", "items": { "type":"number","format": "double" } }, "details": {"type": "object", "properties": { "v1": { "type":"number","format": "double" }, "v2": { "type":"number","format": "double" } } } }' + When I try to parse the value '{"values":[-10,22],"details":{"v1":0,"v2":42}}' as the request body + Then the parameter body should be {"values":[-10,22],"details":{"v1":0,"v2":42}} of type System.String + +Scenario: Object parameter with properties of complex types + Given I have constructed the OpenAPI specification with a parameter with name 'openApiObjectWithComplexProperties', of type object, containing properties in the structure '{ "values": { "type": "array", "items": { "type":"number","format": "double" } }, "details": {"type": "object", "properties": { "v1": { "type":"number","format": "double" }, "v2": { "type":"number","format": "double" } } } }' + When I try to parse the query value '{"values":[-10,22],"details":{"v1":0,"v2":42}}' as the parameter 'openApiObjectWithComplexProperties' + Then the parameter openApiObjectWithComplexProperties should be {"values":[-10,22],"details":{"v1":0,"v2":42}} of type System.String + +Scenario: Object parameter with properties of complex types via default + Given I have constructed the OpenAPI specification with a parameter with name 'openApiObjectWithComplexProperties', of type object, containing properties in the structure '{ "values": { "type": "array", "items": { "type":"number","format": "double" } }, "details": {"type": "object", "properties": { "v1": { "type":"number","format": "double" }, "v2": { "type":"number","format": "double" } } } }', and the default value for the parameter is '{"values":[-10.0,22.0],"details":{"v1":0.0,"v2":42.0}}' + When I try to parse the default value + Then the parameter openApiObjectWithComplexProperties should be {"values":[-10.0,22.0],"details":{"v1":0.0,"v2":42.0}} of type System.String + + +Scenario Outline: Incorrect body values + Given I have constructed the OpenAPI specification with a request body of type 'number', and format 'double' + When I try to parse the value '' as the request body and expect an error + Then an 'OpenApiBadRequestException' should be thrown + + Examples: + | Value | + | "This is certainly not a double" | + | thisisnotevenvalidjson | + | true | + | false | + | null | + | "20170721T173228Z" | + +Scenario Outline: Incorrect parameter values + Given I have constructed the OpenAPI specification with a parameter with name 'openApiInteger', type 'number', and format 'double' + When I try to parse the value '' as the parameter 'openApiInteger' and expect an error + Then an 'OpenApiBadRequestException' should be thrown + + Examples: + | ParameterLocation | Value | + | query | This is certainly not a double | + | query | true | + | query | false | + | query | null | + | query | 20170721T173228Z | + | header | This is certainly not a double | + | cookie | This+is+certainly+not+a+double | + | path | This+is+certainly+not+a+double | + +Scenario Outline: Incorrect parameter defaults + Given I have constructed the OpenAPI specification with a parameter with name 'openApiInteger', type 'number', format 'double' and default value '' + When I try to parse the default value and expect an error + Then an 'OpenApiSpecificationException' should be thrown + + Examples: + # Not doing path, because it doesn't really make sense to provide a default - we're only going to match + # the path template if all parts are present. (E.g., if the path is /pets/{petId}, we don't expect /pets/ + # to match. + | ParameterLocation | DefaultValue | + | query | "This is certainly not a double" | + | query | thisisnotevenvalidjson | + | query | true | + | query | false | + | query | "20170721T173228Z" | + # String-typed numbers are incorrect. JSON numeric values should be used. + | query | "42" | + | header | "This is certainly not a double" | + | cookie | "This is certainly not a double" | \ No newline at end of file diff --git a/Solutions/Menes.Specs/Features/JsonTypeConversion/DoubleOutputParsing.feature b/Solutions/Menes.Specs/Features/JsonTypeConversion/DoubleOutputParsing.feature new file mode 100644 index 000000000..fc9feb5fe --- /dev/null +++ b/Solutions/Menes.Specs/Features/JsonTypeConversion/DoubleOutputParsing.feature @@ -0,0 +1,21 @@ +@perScenarioContainer + +Feature: Double Output Parsing + In order to implement a web API + As a developer + I want to be able to specify double values as or in response bodies within the OpenAPI specification and have corresponding response bodies deserialized and validated + + +Scenario Outline: Valid values for simple types + Given I have constructed the OpenAPI specification with a response body of type 'number', and format 'double' + When I try to build a response body from the value '' of type 'System.Double' + Then the response body should be '' + + Examples: + | Value | ExpectedResult | + | 0 | 0.0 | + | 1234 | 1234.0 | + | -1234 | -1234.0 | + | 1234.5 | 1234.5 | + | -1234.567 | -1234.567 | + | -1234.5678987 | -1234.5678987 | \ No newline at end of file diff --git a/Solutions/Menes.Specs/Features/JsonTypeConversion/FloatInputParsing.feature b/Solutions/Menes.Specs/Features/JsonTypeConversion/FloatInputParsing.feature new file mode 100644 index 000000000..96bfb4b0f --- /dev/null +++ b/Solutions/Menes.Specs/Features/JsonTypeConversion/FloatInputParsing.feature @@ -0,0 +1,175 @@ +@perScenarioContainer + +Feature: Float Input Parsing + In order to implement a web API + As a developer + I want to be able to specify float parameters within the OpenAPI specification and have corresponding input parameters deserialized and validated + +Scenario Outline: Body with valid values for simple types + Given I have constructed the OpenAPI specification with a request body of type 'number', and format 'double' + When I try to parse the value '' as the request body + Then the parameter body should be of type System.Double + + Examples: + | Value | ExpectedResult | + | 1234 | 1234 | + | 0 | 0 | + | -42 | -42 | + | 2147483647 | 2147483647 | + | -2147483648 | -2147483648 | + | 1234.5 | 1234.5 | + | -1234.567 | -1234.567 | + +Scenario Outline: Parameters with valid values for simple types + Given I have constructed the OpenAPI specification with a parameter with name 'openApiFloat', type 'number', and format 'float' + When I try to parse the value '' as the parameter 'openApiFloat' + Then the parameter openApiFloat should be of type System.Single + + Examples: + | ParameterLocation | Value | ExpectedResult | + | path | 1234 | 1234 | + | path | 0 | 0 | + | path | -42 | -42 | + | path | 2147483647 | 2147483647 | + | path | -2147483648 | -2147483648 | + | path | 1234.5 | 1234.5 | + | path | -1234.567 | -1234.567 | + | query | 1234 | 1234 | + | query | 0 | 0 | + | query | -42 | -42 | + | header | 1234 | 1234 | + | header | 0 | 0 | + | header | -42 | -42 | + | cookie | 1234 | 1234 | + | cookie | 0 | 0 | + | cookie | -42 | -42 | + +Scenario: Array body with items of float type + Given I have constructed the OpenAPI specification with a request body of type array, containing items of type 'number' and format 'float' + When I try to parse the value '[1,2,3,1234.5,-1234.567]' as the request body + Then the parameter body should be [1,2,3,1234.5,-1234.567] of type System.String + +Scenario: Array parameter with items of float type + Given I have constructed the OpenAPI specification with a parameter with name 'openApiArray', of type array, containing items of type 'number' and format 'float' + When I try to parse the query value '[1,-1234.567,3,4,5]' as the parameter 'openApiArray' + Then the parameter openApiArray should be [1,-1234.567,3,4,5] of type System.String + +Scenario: Array parameter with items of float type via default + Given I have constructed the OpenAPI specification with a parameter with name 'openApiArray', of type array, containing items of type 'number' and format 'float', and the default value for the parameter is '[1.0,2.0,-1234.567,4.0,5.0]' + When I try to parse the default value + Then the parameter openApiArray should be [1.0,2.0,-1234.567,4.0,5.0] of type System.String + + +Scenario: Array body with items of array type + Given I have constructed the OpenAPI specification with a request body of type array, containing items which are arrays themselves with item type 'number' and format 'float' + When I try to parse the value '[[1],[2,3],[4,5,6]]' as the request body + Then the parameter body should be [[1],[2,3],[4,5,6]] of type System.String + +Scenario: Array parameter with items of array type + Given I have constructed the OpenAPI specification with a parameter with name 'openApiNestedArray', of type array, containing items which are arrays themselves with item type 'number' and format 'float' + When I try to parse the query value '[[1],[2,-1234.567],[4,5,6]]' as the parameter 'openApiNestedArray' + Then the parameter openApiNestedArray should be [[1],[2,-1234.567],[4,5,6]] of type System.String + +Scenario: Array parameter with items of array type via default + Given I have constructed the OpenAPI specification with a parameter with name 'openApiNestedArray', of type array, containing items which are arrays themselves with item type 'number' and format 'float', and the default value for the parameter is '[[1.0],[2.0,3.0],[4.0,5.0,6.0]]' + When I try to parse the default value + Then the parameter openApiNestedArray should be [[1.0],[2.0,3.0],[4.0,5.0,6.0]] of type System.String + + +Scenario: Array body with items of object type + Given I have constructed the OpenAPI specification with a request body of type array, containing items which are objects which has the property structure '{ "v1": { "type": "number", "format": "float" }, "v2": {"type": "number", "format": "float"} }' + When I try to parse the value '[{"v1":1,"v2":2},{"v1":-3,"v2":0}]' as the request body + Then the parameter body should be [{"v1":1,"v2":2},{"v1":-3,"v2":0}] of type System.String + +Scenario: Array parameter with items of object type + Given I have constructed the OpenAPI specification with a parameter with name 'openApiArrayWithObjectItems', of type array, containing items which are objects which has the property structure '{ "v1": { "type": "number", "format": "float" }, "v2": {"type": "number", "format": "float"} }' + When I try to parse the query value '[{"v1":1,"v2":2},{"v1":-3,"v2":0}]' as the parameter 'openApiArrayWithObjectItems' + Then the parameter openApiArrayWithObjectItems should be [{"v1":1,"v2":2},{"v1":-3,"v2":0}] of type System.String + +Scenario: Array parameter with items of object type via default + Given I have constructed the OpenAPI specification with a parameter with name 'openApiArrayWithObjectItems', of type array, containing items which are objects which has the property structure '{ "v1": { "type": "number", "format": "float" }, "v2": {"type": "number", "format": "float"} }', and the default value for the parameter is '[{"v1":1.0,"v2":2.0},{"v1":-3.0,"v2":0.0}]' + When I try to parse the default value + Then the parameter openApiArrayWithObjectItems should be [{"v1":1.0,"v2":2.0},{"v1":-3.0,"v2":0.0}] of type System.String + + +Scenario: Object body with properties of simple types + Given I have constructed the OpenAPI specification with a request body of type object, containing properties in the structure '{ "v1": { "type": "number", "format": "float" }, "v2": {"type": "number", "format": "float"} }' + When I try to parse the value '{"v1": -42, "v2": 23}' as the request body + Then the parameter body should be {"v1": -42, "v2": 23} of type System.String + +Scenario: Object parameter with properties of simple types + Given I have constructed the OpenAPI specification with a parameter with name 'openApiObject', of type object, containing properties in the structure '{ "v1": { "type": "number", "format": "float" }, "v2": {"type": "number", "format": "float"} }' + When I try to parse the query value '{"v1": -42, "v2": 23}' as the parameter 'openApiObject' + Then the parameter openApiObject should be {"v1": -42, "v2": 23} of type System.String + +Scenario: Object parameter with properties of simple types via default + Given I have constructed the OpenAPI specification with a parameter with name 'openApiObject', of type object, containing properties in the structure '{ "v1": { "type": "number", "format": "float" }, "v2": {"type": "number", "format": "float"} }', and the default value for the parameter is '{"v1":-42.0,"v2":23.0}' + When I try to parse the default value + Then the parameter openApiObject should be {"v1":-42.0,"v2":23.0} of type System.String + + +Scenario: Object body with properties of complex types + Given I have constructed the OpenAPI specification with a request body of type object, containing properties in the structure '{ "values": { "type": "array", "items": { "type": "number", "format": "float" } }, "details": {"type": "object", "properties": { "v1": { "type": "number", "format": "float" }, "v2": { "type": "number", "format": "float" } } } }' + When I try to parse the value '{"values":[-10,22],"details":{"v1":0,"v2":42}}' as the request body + Then the parameter body should be {"values":[-10,22],"details":{"v1":0,"v2":42}} of type System.String + +Scenario: Object parameter with properties of complex types + Given I have constructed the OpenAPI specification with a parameter with name 'openApiObjectWithComplexProperties', of type object, containing properties in the structure '{ "values": { "type": "array", "items": { "type": "number", "format": "float" } }, "details": {"type": "object", "properties": { "v1": { "type": "number", "format": "float" }, "v2": { "type": "number", "format": "float" } } } }' + When I try to parse the query value '{"values":[-10,22],"details":{"v1":0,"v2":42}}' as the parameter 'openApiObjectWithComplexProperties' + Then the parameter openApiObjectWithComplexProperties should be {"values":[-10,22],"details":{"v1":0,"v2":42}} of type System.String + +Scenario: Object parameter with properties of complex types via default + Given I have constructed the OpenAPI specification with a parameter with name 'openApiObjectWithComplexProperties', of type object, containing properties in the structure '{ "values": { "type": "array", "items": { "type": "number", "format": "float" } }, "details": {"type": "object", "properties": { "v1": { "type": "number", "format": "float" }, "v2": { "type": "number", "format": "float" } } } }', and the default value for the parameter is '{"values":[-10.0,22.0],"details":{"v1":0.0,"v2":42.0}}' + When I try to parse the default value + Then the parameter openApiObjectWithComplexProperties should be {"values":[-10.0,22.0],"details":{"v1":0.0,"v2":42.0}} of type System.String + + +Scenario Outline: Incorrect body values + Given I have constructed the OpenAPI specification with a request body of type 'number', and format 'float' + When I try to parse the value '' as the request body and expect an error + Then an 'OpenApiBadRequestException' should be thrown + + Examples: + | Value | + | "This is certainly not a float" | + | thisisnotevenvalidjson | + | true | + | false | + | null | + | "20170721T173228Z" | + +Scenario Outline: Incorrect parameter values + Given I have constructed the OpenAPI specification with a parameter with name 'openApiInteger', type 'number', and format 'float' + When I try to parse the value '' as the parameter 'openApiInteger' and expect an error + Then an 'OpenApiBadRequestException' should be thrown + + Examples: + | ParameterLocation | Value | + | query | This is certainly not a float | + | query | true | + | query | false | + | query | null | + | query | 20170721T173228Z | + | header | This is certainly not a float | + | cookie | This+is+certainly+not+a+float | + | path | This+is+certainly+not+a+float | + +Scenario Outline: Incorrect parameter defaults + Given I have constructed the OpenAPI specification with a parameter with name 'openApiInteger', type 'number', format 'float' and default value '' + When I try to parse the default value and expect an error + Then an 'OpenApiSpecificationException' should be thrown + + Examples: + # Not doing path, because it doesn't really make sense to provide a default - we're only going to match + # the path template if all parts are present. (E.g., if the path is /pets/{petId}, we don't expect /pets/ + # to match. + | ParameterLocation | DefaultValue | + | query | "This is certainly not a float" | + | query | thisisnotevenvalidjson | + | query | true | + | query | false | + | query | "20170721T173228Z" | + # String-typed numbers are incorrect. JSON numeric values should be used. + | query | "42" | + | header | "This is certainly not a float" | + | cookie | "This is certainly not a float" | \ No newline at end of file diff --git a/Solutions/Menes.Specs/Features/JsonTypeConversion/FloatOutputParsing.feature b/Solutions/Menes.Specs/Features/JsonTypeConversion/FloatOutputParsing.feature new file mode 100644 index 000000000..a0417f67d --- /dev/null +++ b/Solutions/Menes.Specs/Features/JsonTypeConversion/FloatOutputParsing.feature @@ -0,0 +1,20 @@ +@perScenarioContainer + +Feature: Float Output Parsing + In order to implement a web API + As a developer + I want to be able to specify float values as or in response bodies within the OpenAPI specification and have corresponding response bodies deserialized and validated + + +Scenario Outline: Valid values for simple types + Given I have constructed the OpenAPI specification with a response body of type 'number', and format 'float' + When I try to build a response body from the value '' of type 'System.Single' + Then the response body should be '' + + Examples: + | Value | ExpectedResult | + | 0 | 0.0 | + | 1234 | 1234.0 | + | -1234 | -1234.0 | + | 1234.5 | 1234.5 | + | -1234.567 | -1234.567 | \ No newline at end of file diff --git a/Solutions/Menes.Specs/Features/JsonTypeConversion/Integer32InputParsing.feature b/Solutions/Menes.Specs/Features/JsonTypeConversion/Integer32InputParsing.feature new file mode 100644 index 000000000..553d9bba5 --- /dev/null +++ b/Solutions/Menes.Specs/Features/JsonTypeConversion/Integer32InputParsing.feature @@ -0,0 +1,162 @@ +@perScenarioContainer + +Feature: Integer32 Input Parsing + In order to implement a web API + As a developer + I want to be able to specify 32-bit integer parameters within the OpenAPI specification and have corresponding input parameters deserialized and validated + +Scenario Outline: Parameters with valid values for simple types + Given I have constructed the OpenAPI specification with a parameter with name 'openApiInteger32', type 'integer', and format '' + When I try to parse the value '' as the parameter 'openApiInteger32' + Then the parameter openApiInteger32 should be of type System.Int32 + + Examples: + | ParameterLocation | Value | ExpectedResult | + | path | 1234 | 1234 | + | path | 0 | 0 | + | path | -42 | -42 | + | query | 1234 | 1234 | + | query | 0 | 0 | + | query | -42 | -42 | + | header | 1234 | 1234 | + | header | 0 | 0 | + | header | -42 | -42 | + | cookie | 1234 | 1234 | + | cookie | 0 | 0 | + | cookie | -42 | -42 | + +Scenario: Array body with items of integer type + Given I have constructed the OpenAPI specification with a request body of type array, containing items of type 'integer' + When I try to parse the value '[1,2,3,4,5]' as the request body + Then the parameter body should be [1,2,3,4,5] of type System.String + +Scenario: Array parameter with items of integer type + Given I have constructed the OpenAPI specification with a parameter with name 'openApiArray', of type array, containing items of type 'integer' + When I try to parse the query value '[1,2,3,4,5]' as the parameter 'openApiArray' + Then the parameter openApiArray should be [1,2,3,4,5] of type System.String + +Scenario: Array parameter with items of integer type via default + Given I have constructed the OpenAPI specification with a parameter with name 'openApiArray', of type array, containing items of type 'integer', and the default value for the parameter is '[1,2,3,4,5]' + When I try to parse the default value + Then the parameter openApiArray should be [1,2,3,4,5] of type System.String + + +Scenario: Array body with items of array type + Given I have constructed the OpenAPI specification with a request body of type array, containing items which are arrays themselves with item type 'integer' + When I try to parse the value '[[1],[2,3],[4,5,6]]' as the request body + Then the parameter body should be [[1],[2,3],[4,5,6]] of type System.String + +Scenario: Array parameter with items of array type + Given I have constructed the OpenAPI specification with a parameter with name 'openApiNestedArray', of type array, containing items which are arrays themselves with item type 'integer' + When I try to parse the query value '[[1],[2,3],[4,5,6]]' as the parameter 'openApiNestedArray' + Then the parameter openApiNestedArray should be [[1],[2,3],[4,5,6]] of type System.String + +Scenario: Array parameter with items of array type via default + Given I have constructed the OpenAPI specification with a parameter with name 'openApiNestedArray', of type array, containing items which are arrays themselves with item type 'integer', and the default value for the parameter is '[[1],[2,3],[4,5,6]]' + When I try to parse the default value + Then the parameter openApiNestedArray should be [[1],[2,3],[4,5,6]] of type System.String + + +Scenario: Array body with items of object type + Given I have constructed the OpenAPI specification with a request body of type array, containing items which are objects which has the property structure '{ "v1": { "type": "integer" }, "v2": {"type": "integer"} }' + When I try to parse the value '[{"v1":1,"v2":2},{"v1":-3,"v2":0}]' as the request body + Then the parameter body should be [{"v1":1,"v2":2},{"v1":-3,"v2":0}] of type System.String + +Scenario: Array parameter with items of object type + Given I have constructed the OpenAPI specification with a parameter with name 'openApiArrayWithObjectItems', of type array, containing items which are objects which has the property structure '{ "v1": { "type": "integer" }, "v2": {"type": "integer"} }' + When I try to parse the query value '[{"v1":1,"v2":2},{"v1":-3,"v2":0}]' as the parameter 'openApiArrayWithObjectItems' + Then the parameter openApiArrayWithObjectItems should be [{"v1":1,"v2":2},{"v1":-3,"v2":0}] of type System.String + +Scenario: Array parameter with items of object type via default + Given I have constructed the OpenAPI specification with a parameter with name 'openApiArrayWithObjectItems', of type array, containing items which are objects which has the property structure '{ "v1": { "type": "integer" }, "v2": {"type": "integer"} }', and the default value for the parameter is '[{"v1":1,"v2":2},{"v1":-3,"v2":0}]' + When I try to parse the default value + Then the parameter openApiArrayWithObjectItems should be [{"v1":1,"v2":2},{"v1":-3,"v2":0}] of type System.String + + +Scenario: Object body with properties of simple types + Given I have constructed the OpenAPI specification with a request body of type object, containing properties in the structure '{ "v1": { "type": "integer" }, "v2": {"type": "integer"} }' + When I try to parse the value '{"v1": -42, "v2": 23}' as the request body + Then the parameter body should be {"v1": -42, "v2": 23} of type System.String + +Scenario: Object parameter with properties of simple types + Given I have constructed the OpenAPI specification with a parameter with name 'openApiObject', of type object, containing properties in the structure '{ "v1": { "type": "integer" }, "v2": {"type": "integer"} }' + When I try to parse the query value '{"v1": -42, "v2": 23}' as the parameter 'openApiObject' + Then the parameter openApiObject should be {"v1": -42, "v2": 23} of type System.String + +Scenario: Object parameter with properties of simple types via default + Given I have constructed the OpenAPI specification with a parameter with name 'openApiObject', of type object, containing properties in the structure '{ "v1": { "type": "integer" }, "v2": {"type": "integer"} }', and the default value for the parameter is '{"v1":-42,"v2":23}' + When I try to parse the default value + Then the parameter openApiObject should be {"v1":-42,"v2":23} of type System.String + + +Scenario: Object body with properties of complex types + Given I have constructed the OpenAPI specification with a request body of type object, containing properties in the structure '{ "values": { "type": "array", "items": { "type": "integer" } }, "details": {"type": "object", "properties": { "v1": { "type": "integer" }, "v2": { "type": "integer" } } } }' + When I try to parse the value '{"values":[-10,22],"details":{"v1":0,"v2":42}}' as the request body + Then the parameter body should be {"values":[-10,22],"details":{"v1":0,"v2":42}} of type System.String + +Scenario: Object parameter with properties of complex types + Given I have constructed the OpenAPI specification with a parameter with name 'openApiObjectWithComplexProperties', of type object, containing properties in the structure '{ "values": { "type": "array", "items": { "type": "integer" } }, "details": {"type": "object", "properties": { "v1": { "type": "integer" }, "v2": { "type": "integer" } } } }' + When I try to parse the query value '{"values":[-10,22],"details":{"v1":0,"v2":42}}' as the parameter 'openApiObjectWithComplexProperties' + Then the parameter openApiObjectWithComplexProperties should be {"values":[-10,22],"details":{"v1":0,"v2":42}} of type System.String + +Scenario: Object parameter with properties of complex types via default + Given I have constructed the OpenAPI specification with a parameter with name 'openApiObjectWithComplexProperties', of type object, containing properties in the structure '{ "values": { "type": "array", "items": { "type": "integer" } }, "details": {"type": "object", "properties": { "v1": { "type": "integer" }, "v2": { "type": "integer" } } } }', and the default value for the parameter is '{"values":[-10,22],"details":{"v1":0,"v2":42}}' + When I try to parse the default value + Then the parameter openApiObjectWithComplexProperties should be {"values":[-10,22],"details":{"v1":0,"v2":42}} of type System.String + + +Scenario Outline: Incorrect body values + Given I have constructed the OpenAPI specification with a request body of type 'integer', and format '' + When I try to parse the value '' as the request body and expect an error + Then an 'OpenApiBadRequestException' should be thrown + + Examples: + | Value | + | "This is certainly not a integer" | + | thisisnotevenvalidjson | + | true | + | false | + | null | + | 9223372036854775807 | + | 9223372036854775808123123123 | + | "20170721T173228Z" | + +Scenario Outline: Incorrect parameter values + Given I have constructed the OpenAPI specification with a parameter with name 'openApiInteger', type 'integer', and format '' + When I try to parse the value '' as the parameter 'openApiInteger' and expect an error + Then an 'OpenApiBadRequestException' should be thrown + + Examples: + | ParameterLocation | Value | + | query | This is certainly not a integer | + | query | true | + | query | false | + | query | null | + | query | 9223372036854775807 | + | query | 9223372036854775808123123123 | + | query | 20170721T173228Z | + | header | This is certainly not a integer | + | cookie | This+is+certainly+not+a+integer | + | path | This+is+certainly+not+a+integer | + +Scenario Outline: Incorrect parameter defaults + Given I have constructed the OpenAPI specification with a parameter with name 'openApiInteger', type 'integer', format '' and default value '' + When I try to parse the default value and expect an error + Then an 'OpenApiSpecificationException' should be thrown + + Examples: + # Not doing path, because it doesn't really make sense to provide a default - we're only going to match + # the path template if all parts are present. (E.g., if the path is /pets/{petId}, we don't expect /pets/ + # to match. + | ParameterLocation | DefaultValue | + | query | "This is certainly not a integer" | + | query | thisisnotevenvalidjson | + | query | true | + | query | false | + | query | 9223372036854775807 | + | query | 9223372036854775808123123123 | + | query | "20170721T173228Z" | + # String-typed numbers are incorrect. JSON numeric values should be used. + | query | "42" | + | header | "This is certainly not a integer" | + | cookie | "This is certainly not a integer" | \ No newline at end of file diff --git a/Solutions/Menes.Specs/Features/JsonTypeConversion/Integer32OutputParsing.feature b/Solutions/Menes.Specs/Features/JsonTypeConversion/Integer32OutputParsing.feature new file mode 100644 index 000000000..1fdecca94 --- /dev/null +++ b/Solutions/Menes.Specs/Features/JsonTypeConversion/Integer32OutputParsing.feature @@ -0,0 +1,20 @@ +@perScenarioContainer + +Feature: Integer32 Output Parsing + In order to implement a web API + As a developer + I want to be able to specify integer32 values as or in response bodies within the OpenAPI specification and have corresponding response bodies deserialized and validated + + +Scenario Outline: Valid values for simple types + Given I have constructed the OpenAPI specification with a response body of type 'integer', and format '' + When I try to build a response body from the value '' of type 'System.Int32' + Then the response body should be '' + + Examples: + | Value | ExpectedResult | + | 0 | 0 | + | 1234 | 1234 | + | -1234 | -1234 | + | 2147483647 | 2147483647 | + | -2147483648 | -2147483648 | diff --git a/Solutions/Menes.Specs/Features/JsonTypeConversion/Integer64InputParsing.feature b/Solutions/Menes.Specs/Features/JsonTypeConversion/Integer64InputParsing.feature new file mode 100644 index 000000000..56e4ec076 --- /dev/null +++ b/Solutions/Menes.Specs/Features/JsonTypeConversion/Integer64InputParsing.feature @@ -0,0 +1,180 @@ +@perScenarioContainer + +Feature: Integer64 Input Parsing + In order to implement a web API + As a developer + I want to be able to specify 64-bit integer parameters within the OpenAPI specification and have corresponding input parameters deserialized and validated + +Scenario Outline: Body with valid values for simple types + Given I have constructed the OpenAPI specification with a request body of type 'integer', and format 'int64' + When I try to parse the value '' as the request body + Then the parameter body should be of type System.Int64 + + Examples: + | Value | ExpectedResult | + | 0 | 0 | + | 1234 | 1234 | + | 2147483647 | 2147483647 | + | -2147483648 | -2147483648 | + | 9223372036854775807 | 9223372036854775807 | + | -9223372036854775808 | -9223372036854775808 | + +Scenario Outline: Parameters with valid values for simple types + Given I have constructed the OpenAPI specification with a parameter with name 'openApiInteger64', type 'integer', and format 'int64' + When I try to parse the value '' as the parameter 'openApiInteger64' + Then the parameter openApiInteger64 should be of type System.Int64 + + Examples: + | ParameterLocation | Value | ExpectedResult | + | path | 1234 | 1234 | + | path | 0 | 0 | + | path | -42 | -42 | + | path | 2147483647 | 2147483647 | + | path | -2147483648 | -2147483648 | + | path | 9223372036854775807 | 9223372036854775807 | + | path | -9223372036854775808 | -9223372036854775808 | + | query | 1234 | 1234 | + | query | 0 | 0 | + | query | -42 | -42 | + | header | 1234 | 1234 | + | header | 0 | 0 | + | header | -42 | -42 | + | cookie | 1234 | 1234 | + | cookie | 0 | 0 | + | cookie | -42 | -42 | + +Scenario: Array body with items of integer type + Given I have constructed the OpenAPI specification with a request body of type array, containing items of type 'integer' and format 'int64' + When I try to parse the value '[1,2,3,9223372036854775807,-9223372036854775808]' as the request body + Then the parameter body should be [1,2,3,9223372036854775807,-9223372036854775808] of type System.String + +Scenario: Array parameter with items of integer type + Given I have constructed the OpenAPI specification with a parameter with name 'openApiArray', of type array, containing items of type 'integer' and format 'int64' + When I try to parse the query value '[1,-9223372036854775808,3,4,5]' as the parameter 'openApiArray' + Then the parameter openApiArray should be [1,-9223372036854775808,3,4,5] of type System.String + +Scenario: Array parameter with items of integer type via default + Given I have constructed the OpenAPI specification with a parameter with name 'openApiArray', of type array, containing items of type 'integer' and format 'int64', and the default value for the parameter is '[1,2,-9223372036854775808,4,5]' + When I try to parse the default value + Then the parameter openApiArray should be [1,2,-9223372036854775808,4,5] of type System.String + + +Scenario: Array body with items of array type + Given I have constructed the OpenAPI specification with a request body of type array, containing items which are arrays themselves with item type 'integer' and format 'int64' + When I try to parse the value '[[1],[2,3],[4,5,6]]' as the request body + Then the parameter body should be [[1],[2,3],[4,5,6]] of type System.String + +Scenario: Array parameter with items of array type + Given I have constructed the OpenAPI specification with a parameter with name 'openApiNestedArray', of type array, containing items which are arrays themselves with item type 'integer' and format 'int64' + When I try to parse the query value '[[1],[2,3],[4,5,6]]' as the parameter 'openApiNestedArray' + Then the parameter openApiNestedArray should be [[1],[2,3],[4,5,6]] of type System.String + +Scenario: Array parameter with items of array type via default + Given I have constructed the OpenAPI specification with a parameter with name 'openApiNestedArray', of type array, containing items which are arrays themselves with item type 'integer' and format 'int64', and the default value for the parameter is '[[1],[2,3],[4,5,6]]' + When I try to parse the default value + Then the parameter openApiNestedArray should be [[1],[2,3],[4,5,6]] of type System.String + + +Scenario: Array body with items of object type + Given I have constructed the OpenAPI specification with a request body of type array, containing items which are objects which has the property structure '{ "v1": {"type":"integer","format":"int64"}, "v2": {"type": "integer","format":"int64"} }' + When I try to parse the value '[{"v1":1,"v2":2},{"v1":-3,"v2":0}]' as the request body + Then the parameter body should be [{"v1":1,"v2":2},{"v1":-3,"v2":0}] of type System.String + +Scenario: Array parameter with items of object type + Given I have constructed the OpenAPI specification with a parameter with name 'openApiArrayWithObjectItems', of type array, containing items which are objects which has the property structure '{ "v1": {"type":"integer","format":"int64"}, "v2": {"type":"integer","format":"int64"} }' + When I try to parse the query value '[{"v1":1,"v2":2},{"v1":-3,"v2":0}]' as the parameter 'openApiArrayWithObjectItems' + Then the parameter openApiArrayWithObjectItems should be [{"v1":1,"v2":2},{"v1":-3,"v2":0}] of type System.String + +Scenario: Array parameter with items of object type via default + Given I have constructed the OpenAPI specification with a parameter with name 'openApiArrayWithObjectItems', of type array, containing items which are objects which has the property structure '{ "v1": {"type":"integer","format":"int64"}, "v2": {"type":"integer","format":"int64"} }', and the default value for the parameter is '[{"v1":1,"v2":2},{"v1":-3,"v2":0}]' + When I try to parse the default value + Then the parameter openApiArrayWithObjectItems should be [{"v1":1,"v2":2},{"v1":-3,"v2":0}] of type System.String + + +Scenario: Object body with properties of simple types + Given I have constructed the OpenAPI specification with a request body of type object, containing properties in the structure '{ "v1": {"type":"integer","format":"int64"}, "v2": {"type":"integer","format":"int64"} }' + When I try to parse the value '{"v1": -42, "v2": 23}' as the request body + Then the parameter body should be {"v1": -42, "v2": 23} of type System.String + +Scenario: Object parameter with properties of simple types + Given I have constructed the OpenAPI specification with a parameter with name 'openApiObject', of type object, containing properties in the structure '{ "v1": {"type":"integer","format":"int64"}, "v2": {"type":"integer","format":"int64"} }' + When I try to parse the query value '{"v1": -42, "v2": 23}' as the parameter 'openApiObject' + Then the parameter openApiObject should be {"v1": -42, "v2": 23} of type System.String + +Scenario: Object parameter with properties of simple types via default + Given I have constructed the OpenAPI specification with a parameter with name 'openApiObject', of type object, containing properties in the structure '{ "v1": {"type":"integer","format":"int64"}, "v2": {"type":"integer","format":"int64"} }', and the default value for the parameter is '{"v1":-42,"v2":23}' + When I try to parse the default value + Then the parameter openApiObject should be {"v1":-42,"v2":23} of type System.String + + +Scenario: Object body with properties of complex types + Given I have constructed the OpenAPI specification with a request body of type object, containing properties in the structure '{ "values": { "type": "array", "items": {"type":"integer","format":"int64"} }, "details": {"type": "object", "properties": { "v1": {"type":"integer","format":"int64"}, "v2": {"type":"integer","format":"int64"} } } }' + When I try to parse the value '{"values":[-10,22],"details":{"v1":0,"v2":42}}' as the request body + Then the parameter body should be {"values":[-10,22],"details":{"v1":0,"v2":42}} of type System.String + +Scenario: Object parameter with properties of complex types + Given I have constructed the OpenAPI specification with a parameter with name 'openApiObjectWithComplexProperties', of type object, containing properties in the structure '{ "values": { "type": "array", "items": {"type":"integer","format":"int64"} }, "details": {"type": "object", "properties": { "v1": {"type":"integer","format":"int64"}, "v2": {"type":"integer","format":"int64"} } } }' + When I try to parse the query value '{"values":[-10,22],"details":{"v1":0,"v2":42}}' as the parameter 'openApiObjectWithComplexProperties' + Then the parameter openApiObjectWithComplexProperties should be {"values":[-10,22],"details":{"v1":0,"v2":42}} of type System.String + +Scenario: Object parameter with properties of complex types via default + Given I have constructed the OpenAPI specification with a parameter with name 'openApiObjectWithComplexProperties', of type object, containing properties in the structure '{ "values": { "type": "array", "items": {"type":"integer","format":"int64"} }, "details": {"type": "object", "properties": { "v1": {"type":"integer","format":"int64"}, "v2": {"type":"integer","format":"int64"} } } }', and the default value for the parameter is '{"values":[-10,22],"details":{"v1":0,"v2":42}}' + When I try to parse the default value + Then the parameter openApiObjectWithComplexProperties should be {"values":[-10,22],"details":{"v1":0,"v2":42}} of type System.String + + +Scenario Outline: Incorrect body values + Given I have constructed the OpenAPI specification with a request body of type 'integer', and format 'int64' + When I try to parse the value '' as the request body and expect an error + Then an 'OpenApiBadRequestException' should be thrown + + Examples: + | Value | + | "This is certainly not a integer" | + | thisisnotevenvalidjson | + | true | + | false | + | null | + | 9223372036854775808 | + | 9223372036854775808123123123 | + | "20170721T173228Z" | + +Scenario Outline: Incorrect parameter values + Given I have constructed the OpenAPI specification with a parameter with name 'openApiInteger', type 'integer', and format 'int64' + When I try to parse the value '' as the parameter 'openApiInteger' and expect an error + Then an 'OpenApiBadRequestException' should be thrown + + Examples: + | ParameterLocation | Value | + | query | This is certainly not a integer | + | query | true | + | query | false | + | query | null | + | query | 9223372036854775808 | + | query | 9223372036854775808123123123 | + | query | 20170721T173228Z | + | header | This is certainly not a integer | + | cookie | This+is+certainly+not+a+integer | + | path | This+is+certainly+not+a+integer | + +Scenario Outline: Incorrect parameter defaults + Given I have constructed the OpenAPI specification with a parameter with name 'openApiInteger', type 'integer', format 'int64' and default value '' + When I try to parse the default value and expect an error + Then an 'OpenApiSpecificationException' should be thrown + + Examples: + # Not doing path, because it doesn't really make sense to provide a default - we're only going to match + # the path template if all parts are present. (E.g., if the path is /pets/{petId}, we don't expect /pets/ + # to match. + | ParameterLocation | DefaultValue | + | query | "This is certainly not a integer" | + | query | thisisnotevenvalidjson | + | query | true | + | query | false | + | query | 9223372036854775808 | + | query | 9223372036854775808123123123 | + | query | "20170721T173228Z" | + # String-typed numbers are incorrect. JSON numeric values should be used. + | query | "42" | + | header | "This is certainly not a integer" | + | cookie | "This is certainly not a integer" | \ No newline at end of file diff --git a/Solutions/Menes.Specs/Features/JsonTypeConversion/Integer64OutputParsing.feature b/Solutions/Menes.Specs/Features/JsonTypeConversion/Integer64OutputParsing.feature new file mode 100644 index 000000000..484823cb2 --- /dev/null +++ b/Solutions/Menes.Specs/Features/JsonTypeConversion/Integer64OutputParsing.feature @@ -0,0 +1,22 @@ +@perScenarioContainer + +Feature: Integer64 Output Parsing + In order to implement a web API + As a developer + I want to be able to specify integer64 values as or in response bodies within the OpenAPI specification and have corresponding response bodies deserialized and validated + + +Scenario Outline: Valid values for simple types + Given I have constructed the OpenAPI specification with a response body of type 'integer', and format 'int64' + When I try to build a response body from the value '' of type 'System.Int64' + Then the response body should be '' + + Examples: + | Value | ExpectedResult | + | 0 | 0 | + | 1234 | 1234 | + | -1234 | -1234 | + | 2147483647 | 2147483647 | + | -2147483648 | -2147483648 | + | 9223372036854775807 | 9223372036854775807 | + | -9223372036854775808 | -9223372036854775808 | diff --git a/Solutions/Menes.Specs/Features/JsonTypeConversion/ObjectInputParsing.feature b/Solutions/Menes.Specs/Features/JsonTypeConversion/ObjectInputParsing.feature new file mode 100644 index 000000000..4d74c78e0 --- /dev/null +++ b/Solutions/Menes.Specs/Features/JsonTypeConversion/ObjectInputParsing.feature @@ -0,0 +1,215 @@ +@perScenarioContainer + +Feature: Object Input Parsing + In order to implement a web API + As a developer + I want to be able to specify JSON object inputs within the OpenAPI specification and have corresponding input parameters or bodies deserialized and validated + +Scenario: Object body with properties of simple types + Given I have constructed the OpenAPI specification with a request body of type object, containing properties in the structure '{ "id": { "type": "integer" }, "name": {"type": "string"} }' + When I try to parse the value '{"id":123,"name":"Ed"}' as the request body + Then the parameter body should be {"id":123,"name":"Ed"} of type System.String + +Scenario: Object parameter with properties of simple types + Given I have constructed the OpenAPI specification with a parameter with name 'openApiObject', of type object, containing properties in the structure '{ "id": { "type": "integer" }, "name": {"type": "string"} }' + When I try to parse the query value '{"id":123,"name":"Ed"}' as the parameter 'openApiObject' + Then the parameter openApiObject should be {"id":123,"name":"Ed"} of type System.String + +Scenario: Object parameter with properties of simple types via default + Given I have constructed the OpenAPI specification with a parameter with name 'openApiObject', of type object, containing properties in the structure '{ "id": { "type": "integer" }, "name": {"type": "string"} }', and the default value for the parameter is '{"id":123,"name":"Ed"}' + When I try to parse the default value + Then the parameter openApiObject should be {"id":123,"name":"Ed"} of type System.String + + +Scenario Outline: Object body with property-based discriminator using configured discriminated types + Given I have constructed the OpenAPI specification with a request body of type object, containing properties in the structure '{ "objectType": { "type": "string" } }' with 'objectType' as the discriminator + When I try to parse the value '{"objectType":""}' as the request body + Then the parameter body should be of type '' + + Examples: + | Discriminator | ExpectedType | + | registeredDiscriminatedType1 | RegisteredDiscriminatedType1 | + | registeredDiscriminatedType2 | RegisteredDiscriminatedType2 | + +Scenario: Object body with property-based discriminator using registered content type + Given I have constructed the OpenAPI specification with a request body of type object, containing properties in the structure '{ "objectType": { "type": "string" } }' with 'objectType' as the discriminator + When I try to parse the value '{"objectType":""}' as the request body + Then the parameter body should be of type '' + + Examples: + | Discriminator | ExpectedType | + | registeredContentType1 | RegisteredContentType1 | + | registeredContentType2 | RegisteredContentType2 | + + +Scenario: Object body with properties of complex types + Given I have constructed the OpenAPI specification with a request body of type object, containing properties in the structure '{ "names": { "type": "array", "items": { "type": "string" } }, "details": {"type": "object", "properties": { "age": { "type": "integer" }, "hairColour": { "type": "string" } } } }' + When I try to parse the value '{"names":["Ed","Ian"],"details":{"age":24,"hairColour":"Brown"}}' as the request body + Then the parameter body should be {"names":["Ed","Ian"],"details":{"age":24,"hairColour":"Brown"}} of type System.String + +Scenario: Object parameter with properties of complex types + Given I have constructed the OpenAPI specification with a parameter with name 'openApiObjectWithComplexProperties', of type object, containing properties in the structure '{ "names": { "type": "array", "items": { "type": "string" } }, "details": {"type": "object", "properties": { "age": { "type": "integer" }, "hairColour": { "type": "string" } } } }' + When I try to parse the query value '{"names":["Ed","Ian"],"details":{"age":24,"hairColour":"Brown"}}' as the parameter 'openApiObjectWithComplexProperties' + Then the parameter openApiObjectWithComplexProperties should be {"names":["Ed","Ian"],"details":{"age":24,"hairColour":"Brown"}} of type System.String + +Scenario: Object parameter with properties of complex types via default + Given I have constructed the OpenAPI specification with a parameter with name 'openApiObjectWithComplexProperties', of type object, containing properties in the structure '{ "names": { "type": "array", "items": { "type": "string" } }, "details": {"type": "object", "properties": { "age": { "type": "integer" }, "hairColour": { "type": "string" } } } }', and the default value for the parameter is '{"names":["Ed","Ian"],"details":{"age":24,"hairColour":"Brown"}}' + When I try to parse the default value + Then the parameter openApiObjectWithComplexProperties should be {"names":["Ed","Ian"],"details":{"age":24,"hairColour":"Brown"}} of type System.String + + +Scenario Outline: Incorrect body values with properties of simple types + Given I have constructed the OpenAPI specification with a request body of type object, containing properties in the structure '{ "id": { "type": "integer" }, "name": {"type": "string"} }' + When I try to parse the value '' as the request body and expect an error + Then an 'OpenApiBadRequestException' should be thrown + + Examples: + | Value | + | {"id":123.0,"name":"Ed"} | + | {"id":"123","name":"Ed"} | + | {"id":true,"name":"Ed"} | + | {"id":null,"name":"Ed"} | + | {"id":123,"name":true} | + | {"id":123,"name":null} | + | {"id":123,"name":456} | + | {"id":123,"name":456.7} | + +Scenario Outline: Incorrect body values with properties of complex types + Given I have constructed the OpenAPI specification with a request body of type object, containing properties in the structure '{ "names": { "type": "array", "items": { "type": "string" } }, "details": {"type": "object", "properties": { "age": { "type": "integer" }, "hairColour": { "type": "string" } } } }' + When I try to parse the value '' as the request body and expect an error + Then an 'OpenApiBadRequestException' should be thrown + + Examples: + | Value | + | {"names":["Ed",42],"details":{"age":24,"hairColour":"Brown"}} | + | {"names":["Ed",true],"details":{"age":24,"hairColour":"Brown"}} | + | {"names":["Ed",null],"details":{"age":24,"hairColour":"Brown"}} | + | {"names":["Ed",{}],"details":{"age":24,"hairColour":"Brown"}} | + | {"names":["Ed","Ian"],"details":{"age":24.1,"hairColour":"Brown"}} | + | {"names":["Ed","Ian"],"details":{"age":"24","hairColour":"Brown"}} | + | {"names":["Ed","Ian"],"details":{"age":"Ian","hairColour":"Brown"}} | + | {"names":["Ed","Ian"],"details":{"age":true,"hairColour":"Brown"}} | + | {"names":["Ed","Ian"],"details":{"age":null,"hairColour":"Brown"}} | + | {"names":["Ed","Ian"],"details":{"age":{},"hairColour":"Brown"}} | + | {"names":["Ed","Ian"],"details":{"age":24,"hairColour":1}} | + | {"names":["Ed","Ian"],"details":{"age":24,"hairColour":1.1}} | + | {"names":["Ed","Ian"],"details":{"age":24,"hairColour":null}} | + | {"names":["Ed","Ian"],"details":{"age":24,"hairColour":true}} | + | {"names":["Ed","Ian"],"details":{"age":24,"hairColour":{}}} | + | null | + | true | + | false | + | 42 | + | 42.0 | + | "text" | + +Scenario Outline: Incorrect parameter values + Given I have constructed the OpenAPI specification with a parameter with name 'openApiObject', of type object, containing properties in the structure '{ "id": { "type": "integer" }, "name": {"type": "string"} }' + When I try to parse the query value '' as the parameter 'openApiObject' and expect an error + Then an 'OpenApiBadRequestException' should be thrown + + Examples: + | Value | + | {"id":123.0,"name":"Ed"} | + | {"id":"123","name":"Ed"} | + | {"id":true,"name":"Ed"} | + | {"id":null,"name":"Ed"} | + | {"id":123,"name":true} | + | {"id":123,"name":null} | + | {"id":123,"name":456} | + | {"id":123,"name":456.7} | + | null | + | true | + | false | + | 42 | + | 42.0 | + | text | + +Scenario Outline: Incorrect parameter values with complex types + Given I have constructed the OpenAPI specification with a parameter with name 'openApiObjectWithComplexProperties', of type object, containing properties in the structure '{ "names": { "type": "array", "items": { "type": "string" } }, "details": {"type": "object", "properties": { "age": { "type": "integer" }, "hairColour": { "type": "string" } } } }' + When I try to parse the query value '' as the parameter 'openApiObjectWithComplexProperties' and expect an error + Then an 'OpenApiBadRequestException' should be thrown + + Examples: + | Value | + | {"names":["Ed",42],"details":{"age":24,"hairColour":"Brown"}} | + | {"names":["Ed",true],"details":{"age":24,"hairColour":"Brown"}} | + | {"names":["Ed",null],"details":{"age":24,"hairColour":"Brown"}} | + | {"names":["Ed",{}],"details":{"age":24,"hairColour":"Brown"}} | + | {"names":["Ed","Ian"],"details":{"age":24.1,"hairColour":"Brown"}} | + | {"names":["Ed","Ian"],"details":{"age":"24","hairColour":"Brown"}} | + | {"names":["Ed","Ian"],"details":{"age":"Ian","hairColour":"Brown"}} | + | {"names":["Ed","Ian"],"details":{"age":true,"hairColour":"Brown"}} | + | {"names":["Ed","Ian"],"details":{"age":null,"hairColour":"Brown"}} | + | {"names":["Ed","Ian"],"details":{"age":{},"hairColour":"Brown"}} | + | {"names":["Ed","Ian"],"details":{"age":24,"hairColour":1}} | + | {"names":["Ed","Ian"],"details":{"age":24,"hairColour":1.1}} | + | {"names":["Ed","Ian"],"details":{"age":24,"hairColour":null}} | + | {"names":["Ed","Ian"],"details":{"age":24,"hairColour":true}} | + | {"names":["Ed","Ian"],"details":{"age":24,"hairColour":{}}} | + | null | + | true | + | false | + | 42 | + | 42.0 | + | text | + +Scenario Outline: Incorrect parameter defaults + Given I have constructed the OpenAPI specification with a parameter with name 'openApiObject', of type object, containing properties in the structure '{ "id": { "type": "integer" }, "name": {"type": "string"} }', and the default value for the parameter is '' + When I try to parse the default value and expect an error + Then an 'OpenApiSpecificationException' should be thrown + + Examples: + | DefaultValue | + | {"id":123.0,"name":"Ed"} | + | {"id":"123","name":"Ed"} | + | {"id":true,"name":"Ed"} | + | {"id":null,"name":"Ed"} | + | {"id":123,"name":null} | + # You'd expect these next 3 to fail, but Microsoft.OpenApi knows that "name" is supposed to be a string, + # so it "helpfully" converts JSON values of type bool or number to strings in the schema's default + # values. It's not clear whether that's correct. We might need to revisit this when moving to + # Corvus.JsonSchema. + #| {"id":123,"name":true} | + #| {"id":123,"name":456} | + #| {"id":123,"name":456.7} | + | null | + | true | + | false | + | 42 | + | 42.0 | + | text | + + +Scenario Outline: Incorrect parameter defaults with complex types + Given I have constructed the OpenAPI specification with a parameter with name 'openApiObjectWithComplexProperties', of type object, containing properties in the structure '{ "names": { "type": "array", "items": { "type": "string" } }, "details": {"type": "object", "properties": { "age": { "type": "integer" }, "hairColour": { "type": "string" } } } }', and the default value for the parameter is '' + When I try to parse the default value and expect an error + Then an 'OpenApiSpecificationException' should be thrown + + Examples: + | DefaultValue | + | {"names":["Ed",null],"details":{"age":24,"hairColour":"Brown"}} | + | {"names":["Ed",{}],"details":{"age":24,"hairColour":"Brown"}} | + | {"names":["Ed","Ian"],"details":{"age":24.1,"hairColour":"Brown"}} | + | {"names":["Ed","Ian"],"details":{"age":"24","hairColour":"Brown"}} | + | {"names":["Ed","Ian"],"details":{"age":"Ian","hairColour":"Brown"}} | + | {"names":["Ed","Ian"],"details":{"age":true,"hairColour":"Brown"}} | + | {"names":["Ed","Ian"],"details":{"age":null,"hairColour":"Brown"}} | + | {"names":["Ed","Ian"],"details":{"age":{},"hairColour":"Brown"}} | + | {"names":["Ed","Ian"],"details":{"age":24,"hairColour":null}} | + | {"names":["Ed","Ian"],"details":{"age":24,"hairColour":{}}} | + # You'd expect these next 5 to fail, but Microsoft.OpenApi knows that "name" is supposed to be a string, + # so it "helpfully" converts JSON values of type bool or number to strings in the schema's default + # values. It's not clear whether that's correct. We might need to revisit this when moving to + # Corvus.JsonSchema. + #| {"names":["Ed",42],"details":{"age":24,"hairColour":"Brown"}} | + #| {"names":["Ed",true],"details":{"age":24,"hairColour":"Brown"}} | + #| {"names":["Ed","Ian"],"details":{"age":24,"hairColour":1}} | + #| {"names":["Ed","Ian"],"details":{"age":24,"hairColour":1.1}} | + #| {"names":["Ed","Ian"],"details":{"age":24,"hairColour":true}} | + | null | + | true | + | false | + | 42 | + | 42.0 | + | text | diff --git a/Solutions/Menes.Specs/Features/JsonTypeConversion/ObjectOutputParsing.feature b/Solutions/Menes.Specs/Features/JsonTypeConversion/ObjectOutputParsing.feature new file mode 100644 index 000000000..e5d895ea0 --- /dev/null +++ b/Solutions/Menes.Specs/Features/JsonTypeConversion/ObjectOutputParsing.feature @@ -0,0 +1,12 @@ +@perScenarioContainer + +Feature: Oubject Output Parsing + In order to implement a web API + As a developer + I want to be able to specify objects as or in response bodies within the OpenAPI specification and have corresponding response bodies deserialized and validated + + +Scenario: Object with properties of simple types + Given I have constructed the OpenAPI specification with a response body of type object, containing properties in the structure '{ "id": { "type": "integer" }, "name": {"type": "string"} }' + When I try to build a response body from the value '{"id":123,"name":"Ed"}' of type 'ObjectWithIdAndName' + Then the response body should be '{"id":123,"name":"Ed"}' diff --git a/Solutions/Menes.Specs/Features/JsonTypeConversion/OpenApiDefaultParameterParsing.feature b/Solutions/Menes.Specs/Features/JsonTypeConversion/OpenApiDefaultParameterParsing.feature new file mode 100644 index 000000000..a0f7b44af --- /dev/null +++ b/Solutions/Menes.Specs/Features/JsonTypeConversion/OpenApiDefaultParameterParsing.feature @@ -0,0 +1,97 @@ +@perScenarioContainer + +Feature: OpenApiDefaultParameterParsing + In order to simplify setting values for parameters required for the underlying operation + As a developer + I want to be able to specify default values for those parameters within the OpenAPI specification and have those parameters validated and serialized for use downstream. + +Scenario Outline: Parameters with valid values for simple types + Given I have constructed the OpenAPI specification with a parameter with name '', type '', format '' and default value '' + When I try to parse the default value + Then the parameter should be of type + + Examples: + | ParameterLocation | ParameterName | Type | Format | DefaultValue | ExpectedResult | ExpectedResultType | + | query | openApiDate | string | date | 2017-07-21 | 2017-07-21T00:00:00Z | System.DateTimeOffset | + | query | openApiDateTime | string | date-time | "2017-07-21T17:32:28Z" | 2017-07-21T17:32:28+00:00 | System.String | + | query | openApiPassword | string | password | myVErySeCurePAsSworD123 | myVErySeCurePAsSworD123 | System.String | + | query | openApiByte | string | byte | U3dhZ2dlciByb2Nrcw== | U3dhZ2dlciByb2Nrcw== | ByteArrayFromBase64String | + | query | openApiString | string | uuid | 9b7d63fb-1689-4697-9571-00d10b873d78 | 9b7d63fb-1689-4697-9571-00d10b873d78 | System.Guid | + | query | openApiString | string | uri | "https://myuri.com" | https://myuri.com | System.Uri | + | query | openApiString | string | | "I said \"What is a 'PC'?\"" | I said "What is a 'PC'?" | System.String | + | query | openApiBoolean | boolean | | true | true | System.Boolean | + | query | openApiLong | integer | int64 | 9223372036854775807 | 9223372036854775807 | System.Int64 | + | query | openApiInteger | integer | | 1234 | 1234 | System.Int32 | + | query | openApiNumber | number | | 1234.5 | 1234.5 | System.Double | + | query | openApiFloat | number | float | 1234.5 | 1234.5 | System.Single | + | query | openApiDouble | number | double | 1234.5678 | 1234.5678 | System.Double | + | header | openApiDate | string | date | 2017-07-21 | 2017-07-21T00:00:00Z | System.DateTimeOffset | + | header | openApiDateTime | string | date-time | "2017-07-21T17:32:28Z" | 2017-07-21T17:32:28+00:00 | System.String | + | header | openApiPassword | string | password | myVErySeCurePAsSworD123 | myVErySeCurePAsSworD123 | System.String | + | header | openApiByte | string | byte | U3dhZ2dlciByb2Nrcw== | U3dhZ2dlciByb2Nrcw== | ByteArrayFromBase64String | + | header | openApiString | string | uuid | 9b7d63fb-1689-4697-9571-00d10b873d78 | 9b7d63fb-1689-4697-9571-00d10b873d78 | System.Guid | + | header | openApiString | string | uri | "https://myuri.com" | https://myuri.com | System.Uri | + | header | openApiString | string | | "I said \"What is a 'PC'?\"" | I said "What is a 'PC'?" | System.String | + | header | openApiBoolean | boolean | | true | true | System.Boolean | + | header | openApiLong | integer | int64 | 9223372036854775807 | 9223372036854775807 | System.Int64 | + | header | openApiInteger | integer | | 1234 | 1234 | System.Int32 | + | header | openApiNumber | number | | 1234.5 | 1234.5 | System.Double | + | header | openApiFloat | number | float | 1234.5 | 1234.5 | System.Single | + | header | openApiDouble | number | double | 1234.5678 | 1234.5678 | System.Double | + | cookie | openApiDate | string | date | 2017-07-21 | 2017-07-21T00:00:00Z | System.DateTimeOffset | + | cookie | openApiDateTime | string | date-time | "2017-07-21T17:32:28Z" | 2017-07-21T17:32:28+00:00 | System.String | + | cookie | openApiPassword | string | password | myVErySeCurePAsSworD123 | myVErySeCurePAsSworD123 | System.String | + | cookie | openApiByte | string | byte | U3dhZ2dlciByb2Nrcw== | U3dhZ2dlciByb2Nrcw== | ByteArrayFromBase64String | + | cookie | openApiString | string | uuid | 9b7d63fb-1689-4697-9571-00d10b873d78 | 9b7d63fb-1689-4697-9571-00d10b873d78 | System.Guid | + | cookie | openApiString | string | uri | "https://myuri.com" | https://myuri.com | System.Uri | + | cookie | openApiString | string | | "I said \"What the ♻😟¥a is a 'PC'?\"" | I said "What the ♻😟¥a is a 'PC'?" | System.String | + | cookie | openApiBoolean | boolean | | true | true | System.Boolean | + | cookie | openApiLong | integer | int64 | 9223372036854775807 | 9223372036854775807 | System.Int64 | + | cookie | openApiInteger | integer | | 1234 | 1234 | System.Int32 | + | cookie | openApiNumber | number | | 1234.5 | 1234.5 | System.Double | + | cookie | openApiFloat | number | float | 1234.5 | 1234.5 | System.Single | + | cookie | openApiDouble | number | double | 1234.5678 | 1234.5678 | System.Double | + + + +Scenario: Any parameter with null default value + Given I have constructed the OpenAPI specification with a query parameter with name openApiNull, type string, format null and a null default value + When I try to parse the default value and expect an error + Then an 'OpenApiSpecificationException' should be thrown + +Scenario Outline: Incorrect parameter values + Given I have constructed the OpenAPI specification with a parameter with name '', type '', and format '' + When I try to parse the value '' as the parameter '' and expect an error + Then an 'OpenApiBadRequestException' should be thrown + + Examples: + | ParameterLocation | ParameterName | Type | Format | Value | + | query | openApiDate | string | date | "This is certainly not a date" | + | header | openApiDateTime | string | date-time | "20170721T173228Z" | + | cookie | openApiLong | integer | int64 | 9223372036854775808123123123 | + +Scenario Outline: Incorrect parameter defaults + Given I have constructed the OpenAPI specification with a parameter with name '', type '', format '' and default value '' + When I try to parse the default value and expect an error + Then an 'OpenApiSpecificationException' should be thrown + + # 1st example: + # If we set Type to string and Format to date, the DateConverter will definitely claim responsibility. + # And if we then hand it a thing that's not a DateTimeOffset, it will fail when it tries to cast the JToken + # to a DateTimeOffset. That cast is invoking an explicit conversion, and that throws a FormatException if + # the text isn't a date. + # 2nd example: + # The very non-obvious aspect of this is that although 20170721T173228Z looks a lot like a valid date time, + # it's not one of the acceptable forms for the date-time format. The closest acceptable format would be + # 2017-07-21T17:32:28. I don't know what the rationale is for getting a different exception for that vs + # the first example. They're both just strings that aren't in the expected format, and the only reason I + # can see for getting different exception types is as an artifact of the implementation details - the + # incorrectly formatted DateTimeOffset is detected by Json.NET at the point where we convert a JToken to + # a DateTimeOffset, whereas the incorrectly formatted date-time is detected by the JSON Schema validation. + # Since all three of these are errors caused by an incorrectly formatted string as the default value in + # the OpenAPI spec. So I think it should be an OpenApiSpecificationException in all three cases. + Examples: + | ParameterLocation | ParameterName | Type | Format | DefaultValue | + | query | openApiDate | string | date | "This is certainly not a date" | + | header | openApiDateTime | string | date-time | "20170721T173228Z" | + | cookie | openApiLong | integer | int64 | 9223372036854775808123123123 | \ No newline at end of file diff --git a/Solutions/Menes.Specs/Features/JsonTypeConversion/PasswordInputParsing.feature b/Solutions/Menes.Specs/Features/JsonTypeConversion/PasswordInputParsing.feature new file mode 100644 index 000000000..eefaf09ba --- /dev/null +++ b/Solutions/Menes.Specs/Features/JsonTypeConversion/PasswordInputParsing.feature @@ -0,0 +1,96 @@ +@perScenarioContainer + +Feature: Password Input Parsing + In order to implement a web API + As a developer + I want to be able to specify password-formatted string parameters within the OpenAPI specification and have corresponding input parameters deserialized and validated + +Scenario Outline: Body with valid values for simple types + Given I have constructed the OpenAPI specification with a request body of type 'string', and format 'password' + When I try to parse the value '' as the request body + Then the parameter body should be of type System.String + + Examples: + | Value | ExpectedResult | + | "myVErySeCurePAsSworD123" | myVErySeCurePAsSworD123 | + +Scenario Outline: Parameters with valid values for simple types + Given I have constructed the OpenAPI specification with a parameter with name 'openApiPassword', type 'string', and format 'password' + When I try to parse the value '' as the parameter 'openApiPassword' + Then the parameter openApiPassword should be of type System.String + + Examples: + | ParameterLocation | Value | ExpectedResult | + | path | myVErySeCurePAsSworD123 | myVErySeCurePAsSworD123 | + | query | myVErySeCurePAsSworD123 | myVErySeCurePAsSworD123 | + | cookie | myVErySeCurePAsSworD123 | myVErySeCurePAsSworD123 | + | header | myVErySeCurePAsSworD123 | myVErySeCurePAsSworD123 | + +Scenario Outline: Parameters with valid default values for simple types + Given I have constructed the OpenAPI specification with a parameter with name 'openApiPassword', type 'string', format 'password' and default value '' + When I try to parse the default value + Then the parameter openApiPassword should be of type System.String + + Examples: + | ParameterLocation | DefaultValue | ExpectedResult | + | query | "myVErySeCurePAsSworD123" | myVErySeCurePAsSworD123 | + | cookie | "myVErySeCurePAsSworD123" | myVErySeCurePAsSworD123 | + | header | "myVErySeCurePAsSworD123" | myVErySeCurePAsSworD123 | + +#Scenario Outline: Incorrect body values +# Given I have constructed the OpenAPI specification with a request body of type 'string', and format 'password' +# When I try to parse the value '' as the request body and expect an error +# Then an 'OpenApiBadRequestException' should be thrown +# +# Examples: +# # In principle, values that aren't quoted strings (e.g., true, false, numbers, objects) +# # should all fail because the request body is JSON, and only a string can contain a +# # password. However, IOpenApiConverter requires incoming strings values to have the quotes +# # removed, meaning that it can't tell the difference between "true" (which is a valid +# # relative password) and true (which shouldn't be). When we move to Corvus.JsonSchema, we +# # may be able to fix this, but for, any input body will be accepted as a password. +# | Value | +# | thisisnotevenvalidjson | +# | true | +# | false | +# | null | +# | 42 | +# | 9223372036854775808 | +# | 9223372036854775808123123123 | + +# Note: most input parsing specs have an "Incorrect parameter values" scenario outline. +# But because all strings are acceptable as passwords, there are no invalid inputs. +# And unlike the "Incorrect body values" case, non-string JSON values aren't a factor +# because when it comes to parameters, strings are never quoted. Whereas our inability +# to distinguish between "true" and true in the body case is a side effect of our current +# code design, in this case it's actually an unavoidable feature: when we have, say, +# http://example.com/pet?name=true, the value of the name parameter is not necessarily +# the JSON boolean constant true - the example would look the same if the parameter is +# intended as a string. + + +#Scenario Outline: Incorrect parameter defaults +# Given I have constructed the OpenAPI specification with a parameter with name 'openApiPassword', type 'string', format 'password' and default value '' +# When I try to parse the default value and expect an error +# Then an 'OpenApiSpecificationException' should be thrown +# +# Examples: +# # I had been expecting to be able to produce a failure with these because +# # the value handling doesn't go through the problematic code path that makes +# # it impossible to detect these as wrong when they are in a request body. +# # In these cases, it's Microsoft.OpenApi that parses the default value (because +# # it goes into the OpenAPI spec). However, it turns out that if you state that +# # a parameter type is a string, Microsoft.OpenApi will coerce any non-string +# # default value to a string. So if you specify a default value as the JSON +# # true constant, Microsoft.OpenApi acts as though you'd written the string +# # value "true". It's not clear if this is required by the OpenAPI or JSON +# # schema specifications, or if it's just the library trying to be helpful. +# # If it's the latter, we might want to reinstate these cases once we move +# # to Corvus.JsonSchema. +# | ParameterLocation | DefaultValue | +# | query | thisisnotevenvalidjson | +# | query | true | +# | query | false | +# | query | 42 | +# | query | 9223372036854775808 | +# | query | 9223372036854775808123123123 | diff --git a/Solutions/Menes.Specs/Features/JsonTypeConversion/PasswordOutputParsing.feature b/Solutions/Menes.Specs/Features/JsonTypeConversion/PasswordOutputParsing.feature new file mode 100644 index 000000000..aa4a6185a --- /dev/null +++ b/Solutions/Menes.Specs/Features/JsonTypeConversion/PasswordOutputParsing.feature @@ -0,0 +1,16 @@ +@perScenarioContainer + +Feature: Password Output Parsing + In order to implement a web API + As a developer + I want to be able to specify password-formatted string values as or in response bodies within the OpenAPI specification and have corresponding response bodies deserialized and validated + + +Scenario Outline: Valid values for simple types + Given I have constructed the OpenAPI specification with a response body of type 'string', and format 'password' + When I try to build a response body from the value '' of type 'System.String' + Then the response body should be '' + + Examples: + | Value | ExpectedResult | + | myVErySeCurePAsSworD123 | "myVErySeCurePAsSworD123" | diff --git a/Solutions/Menes.Specs/Features/JsonTypeConversion/StringInputParsing.feature b/Solutions/Menes.Specs/Features/JsonTypeConversion/StringInputParsing.feature new file mode 100644 index 000000000..c46a8f544 --- /dev/null +++ b/Solutions/Menes.Specs/Features/JsonTypeConversion/StringInputParsing.feature @@ -0,0 +1,141 @@ +@perScenarioContainer + +Feature: String Input Parsing + In order to implement a web API + As a developer + I want to be able to specify string parameters with an unspecified format within the OpenAPI specification and have corresponding input parameters deserialized and validated + +Scenario Outline: Body with valid values for simple types + Given I have constructed the OpenAPI specification with a request body of type 'string', and format '' + When I try to parse the value '' as the request body + Then the parameter body should be of type System.String + + Examples: + | Value | ExpectedResult | + | "simpletext" | simpletext | + | "simple text with space" | simple text with space | + # SpecFlow doesn't pass backslashes through unaltered. The comments for the next few the actual strings + # show the intended strings we're actually testing for. + # "I said \"What is a 'PC'?\"" | I said "What is a 'PC'?" + | "I said \\"What is a 'PC'?\\"" | I said "What is a 'PC'?" | + # "I said \\\"What is a 'PC'?\\\"" | I said \"What is a 'PC'?\" + | "I said \\\\\\"What is a 'PC'?\\\\\\"" | I said \\"What is a 'PC'?\\" | + #| "I said \"What the ♻😟¥a is a 'PC'?\"" | I said "What the ♻😟¥a is a 'PC'?" + | "I said \\"What the ♻😟¥a is a 'PC'?\\"" | I said "What the ♻😟¥a is a 'PC'?" | + #| "I said \\\"What the ♻😟¥a is a 'PC'?\"" | I said \"What the ♻😟¥a is a 'PC'?" + | "I said \\\\\\"What the ♻😟¥a is a 'PC'?\\"" | I said \\"What the ♻😟¥a is a 'PC'?" | + | "42" | 42 | + | "0" | 0 | + | "-0.12" | -0.12 | + | "true" | true | + | "false" | false | + | "null" | null | + | "{\\"lookslike\\": \\"object\\", \\"isActually\\": \\"string\\"}" | {"lookslike": "object", "isActually": "string"} | + | "https://myuri.com" | https://myuri.com | + +Scenario Outline: Parameters with valid values for simple types + Given I have constructed the OpenAPI specification with a parameter with name 'openApiString', type 'string', and format '' + When I try to parse the value '' as the parameter 'openApiString' + Then the parameter openApiString should be of type System.String + + Examples: + | ParameterLocation | Value | ExpectedResult | + | query | simpletext | simpletext | + | query | simple text with space | simple text with space | + | query | I said "What is a 'PC'?" | I said "What is a 'PC'?" | + | query | I said \\"What is a 'PC'?\\" | I said \\"What is a 'PC'?\\" | + | query | 42 | 42 | + | query | 0 | 0 | + | query | -0.12 | -0.12 | + | query | true | true | + | query | false | false | + | query | null | null | + | query | {"lookslike": "object", "isActually": "string"} | {"lookslike": "object", "isActually": "string"} | + | path | simple+text+with+space | simple+text+with+space | + | cookie | simple+text+with+space | simple+text+with+space | + | header | simple+text+with+space | simple+text+with+space | + +Scenario Outline: Parameters with valid default values for simple types + Given I have constructed the OpenAPI specification with a parameter with name 'openApiString', type 'string', format '' and default value '' + When I try to parse the default value + Then the parameter openApiString should be of type System.String + + Examples: + | ParameterLocation | DefaultValue | ExpectedResult | + | query | "simpletext" | simpletext | + | query | "simple text with space" | simple text with space | + | query | "I said \\"What is a 'PC'?\\"" | I said "What is a 'PC'?" | + | query | "I said \\"What the ♻😟¥a is a 'PC'?\\"" | I said "What the ♻😟¥a is a 'PC'?" | + | query | "I said \\\\\\"What the ♻😟¥a is a 'PC'?\\"" | I said \\"What the ♻😟¥a is a 'PC'?" | + | query | "42" | 42 | + | query | "0" | 0 | + | query | "-0.12" | -0.12 | + | query | "true" | true | + | query | "false" | false | + | query | "null" | null | + | query | "{\\"lookslike\\": \\"object\\", \\"isActually\\": \\"string\\"}" | {"lookslike": "object", "isActually": "string"} | + | cookie | "simple+text+with+space" | simple+text+with+space | + | header | "simple+text+with+space" | simple+text+with+space | + +#Scenario Outline: Incorrect body values +# Given I have constructed the OpenAPI specification with a request body of type 'string', and format '' +# When I try to parse the value '' as the request body and expect an error +# Then an 'OpenApiBadRequestException' should be thrown +# +# Examples: +# # In principle, values that aren't quoted strings (e.g., true, false, numbers, objects) +# # should all fail because the request body is JSON, and only a quoted string is really a +# # string. However, IOpenApiConverter requires incoming strings values to have the quotes +# # removed, meaning that it can't tell the difference between "true" (which is a valid +# # relative URI) and true (which shouldn't be). When we move to Corvus.JsonSchema, we +# # may be able to fix this, but for, any input body will be accepted as a URI. +# | Value | +# | {"foo":42} | +# | true | +# | false | +# | null | +# | 0 | +# | 1.2 | + +# Note: most input parsing specs have an "Incorrect parameter values" scenario outline. +# But because strings are value strings, there are no invalid inputs. And unlike the +# "Incorrect body values" case, non-string JSON values aren't a factor because when it +# comes to parameters, strings are never quoted. Whereas our inability to distinguish +# between "true" and true in the body case is a side effect of our current code design, +# in this case it's actually an unavoidable feature: when we have, say, +# http://example.com/pet?name=true, the value of the name parameter is not necessarily the +# JSON boolean constant true - the example would look the same if the parameter is intended as a string. + +#Scenario Outline: Incorrect parameter defaults +# Given I have constructed the OpenAPI specification with a parameter with name 'openApiString', type 'string', format '' and default value '' +# When I try to parse the default value and expect an error +# Then an 'OpenApiSpecificationException' should be thrown +# +# Examples: +# # I had been expecting to be able to produce a failure with these because +# # the value handling doesn't go through the problematic code path that makes +# # it impossible to detect these as wrong when they are in a request body. +# # In these cases, it's Microsoft.OpenApi that parses the default value (because +# # it goes into the OpenAPI spec). However, it turns out that if you state that +# # a parameter type is a string, Microsoft.OpenApi will coerce any non-string +# # default value to a string. So if you specify a default value as the JSON +# # true constant, Microsoft.OpenApi acts as though you'd written the string +# # value "true". It's not clear if this is required by the OpenAPI or JSON +# # schema specifications, or if it's just the library trying to be helpful. +# # If it's the latter, we might want to reinstate these cases once we move +# # to Corvus.JsonSchema. +# | ParameterLocation | DefaultValue | +# | query | true | +# | query | false | +# | query | null | +# | query | 42 | +# | query | 9223372036854775808 | +# | query | 9223372036854775808123123123 | +# | query | {"foo":42} | +# | query | true | +# | query | false | +# | query | null | +# | query | 0 | +# | query | 1.2 | +# | header | true | +# | cookie | true | \ No newline at end of file diff --git a/Solutions/Menes.Specs/Features/JsonTypeConversion/UriInputParsing.feature b/Solutions/Menes.Specs/Features/JsonTypeConversion/UriInputParsing.feature new file mode 100644 index 000000000..3f3d09a2c --- /dev/null +++ b/Solutions/Menes.Specs/Features/JsonTypeConversion/UriInputParsing.feature @@ -0,0 +1,109 @@ +@perScenarioContainer + +Feature: Uri Input Parsing + In order to implement a web API + As a developer + I want to be able to specify URI-formatted string parameters within the OpenAPI specification and have corresponding input parameters deserialized and validated + +Scenario Outline: Body with valid values for simple types + Given I have constructed the OpenAPI specification with a request body of type 'string', and format 'uri' + When I try to parse the value '' as the request body + Then the parameter body should be of type System.Uri + + Examples: + # Note: relative URIs have always been accepted as inputs (although not as outputs). + # It's not clear that this is a correct interpretation of the specification, but it + # is a correct description of what Menes had been doing in the years before adding + # this spec, so it would be a breaking change to stop allowing them. + | Value | ExpectedResult | + | "https://myuri.com" | https://myuri.com | + | "relativeuri" | relativeuri | + +Scenario Outline: Parameters with valid values for simple types + Given I have constructed the OpenAPI specification with a parameter with name 'openApiString', type 'string', and format 'uri' + When I try to parse the value '' as the parameter 'openApiString' + Then the parameter openApiString should be of type System.Uri + + Examples: + | ParameterLocation | Value | ExpectedResult | + | path | https%3A%2F%2Fmyuri.com | https://myuri.com | + | query | https://myuri.com | https://myuri.com | + | cookie | https://myuri.com | https://myuri.com | + | header | https://myuri.com | https://myuri.com | + +Scenario Outline: Parameters with valid default values for simple types + Given I have constructed the OpenAPI specification with a parameter with name 'openApiString', type 'string', format 'uri' and default value '' + When I try to parse the default value + Then the parameter openApiString should be of type System.Uri + + Examples: + | ParameterLocation | DefaultValue | ExpectedResult | + | query | "https://myuri.com" | https://myuri.com | + | cookie | "https://myuri.com" | https://myuri.com | + | header | "https://myuri.com" | https://myuri.com | + +#Scenario Outline: Incorrect body values +# Given I have constructed the OpenAPI specification with a request body of type 'string', and format 'uri' +# When I try to parse the value '' as the request body and expect an error +# Then an 'OpenApiBadRequestException' should be thrown +# +# Examples: +# # Note that since relative URIs are acceptable, most strings are valid. In fact I've +# # been unable to find a string that .NET won't accept. +# # In principle, values that aren't quoted strings (e.g., true, false, numbers, objects) +# # should all fail because the request body is JSON, and only a string can contain a +# # URI. However, IOpenApiConverter requires incoming strings values to have the quotes +# # removed, meaning that it can't tell the difference between "true" (which is a valid +# # relative URI) and true (which shouldn't be). When we move to Corvus.JsonSchema, we +# # may be able to fix this, but for, any input body will be accepted as a URI. +# | Value | +# | {"foo":42} | +# | true | +# | false | +# | null | +# | 0 | +# | 1.2 | + +# Note: most input parsing specs have an "Incorrect parameter values" scenario outline. +# But because .NET seems to accept all strings as URIs (when relative URIs are allowed) +# there are no invalid inputs. And unlike the "Incorrect body values" case, non-string +# JSON values aren't a factor because when it comes to parameters, strings are never +# quoted. Whereas our inability to distinguish between "true" and true in the body case +# is a side effect of our current code design, in this case it's actually an unavoidable +# feature: when we have, say, http://example.com/pet?name=true, the value of the name +# parameter is not necessarily the JSON boolean constant true - the example would look +# the same if the parameter is intended as a string. + +#Scenario Outline: Incorrect parameter defaults +# Given I have constructed the OpenAPI specification with a parameter with name 'openApiString', type 'string', format 'uri' and default value '' +# When I try to parse the default value and expect an error +# Then an 'OpenApiSpecificationException' should be thrown +# +# Examples: +# # I had been expecting to be able to produce a failure with these because +# # the value handling doesn't go through the problematic code path that makes +# # it impossible to detect these as wrong when they are in a request body. +# # In these cases, it's Microsoft.OpenApi that parses the default value (because +# # it goes into the OpenAPI spec). However, it turns out that if you state that +# # a parameter type is a string, Microsoft.OpenApi will coerce any non-string +# # default value to a string. So if you specify a default value as the JSON +# # true constant, Microsoft.OpenApi acts as though you'd written the string +# # value "true". It's not clear if this is required by the OpenAPI or JSON +# # schema specifications, or if it's just the library trying to be helpful. +# # If it's the latter, we might want to reinstate these cases once we move +# # to Corvus.JsonSchema. +# | ParameterLocation | DefaultValue | +# | query | true | +# | query | false | +# | query | null | +# | query | 42 | +# | query | 9223372036854775808 | +# | query | 9223372036854775808123123123 | +# | query | {"foo":42} | +# | query | true | +# | query | false | +# | query | null | +# | query | 0 | +# | query | 1.2 | +# | header | true | +# | cookie | true | \ No newline at end of file diff --git a/Solutions/Menes.Specs/Features/JsonTypeConversion/UriOutputParsing.feature b/Solutions/Menes.Specs/Features/JsonTypeConversion/UriOutputParsing.feature new file mode 100644 index 000000000..c15825711 --- /dev/null +++ b/Solutions/Menes.Specs/Features/JsonTypeConversion/UriOutputParsing.feature @@ -0,0 +1,20 @@ +@perScenarioContainer + +Feature: Uri Output Parsing + In order to implement a web API + As a developer + I want to be able to specify uri-formatted string values as or in response bodies within the OpenAPI specification and have corresponding response bodies deserialized and validated + + +Scenario Outline: Valid values for simple types + Given I have constructed the OpenAPI specification with a response body of type 'string', and format 'uri' + When I try to build a response body from the value '' of type 'System.Uri' + Then the response body should be '' + + Examples: + | Value | ExpectedResult | + | https://myuri.com | "https://myuri.com" | + # Note: although Menes has always supported relative URIs as inputs, it does not allow them + # as outputs. It seems likely that one of these facts is a bug, but it's not currently clear + # which. We will most likely resolve this when we move over to Corvus.JsonSchema. + #| relativeuri | "relativeuri" | diff --git a/Solutions/Menes.Specs/Features/JsonTypeConversion/UuidInputParsing.feature b/Solutions/Menes.Specs/Features/JsonTypeConversion/UuidInputParsing.feature new file mode 100644 index 000000000..8b9daa739 --- /dev/null +++ b/Solutions/Menes.Specs/Features/JsonTypeConversion/UuidInputParsing.feature @@ -0,0 +1,99 @@ +@perScenarioContainer + +Feature: Uuid Input Parsing + In order to implement a web API + As a developer + I want to be able to specify uuid-formatted string parameters within the OpenAPI specification and have corresponding input parameters deserialized and validated + +Scenario Outline: Body with valid values for simple types + Given I have constructed the OpenAPI specification with a request body of type 'string', and format 'uuid' + When I try to parse the value '' as the request body + Then the parameter body should be of type System.Guid + + Examples: + | Value | ExpectedResult | + | "9b7d63fb-1689-4697-9571-00d10b873d78" | 9b7d63fb-1689-4697-9571-00d10b873d78 | + +Scenario Outline: Parameters with valid values for simple types + Given I have constructed the OpenAPI specification with a parameter with name 'openApiString', type 'string', and format 'uuid' + When I try to parse the value '' as the parameter 'openApiString' + Then the parameter openApiString should be of type System.Guid + + Examples: + | ParameterLocation | Value | ExpectedResult | + | path | 9b7d63fb-1689-4697-9571-00d10b873d78 | 9b7d63fb-1689-4697-9571-00d10b873d78 | + | query | 9b7d63fb-1689-4697-9571-00d10b873d78 | 9b7d63fb-1689-4697-9571-00d10b873d78 | + | cookie | 9b7d63fb-1689-4697-9571-00d10b873d78 | 9b7d63fb-1689-4697-9571-00d10b873d78 | + | header | 9b7d63fb-1689-4697-9571-00d10b873d78 | 9b7d63fb-1689-4697-9571-00d10b873d78 | + +Scenario Outline: Parameters with valid default values for simple types + Given I have constructed the OpenAPI specification with a parameter with name 'openApiString', type 'string', format 'uuid' and default value '' + When I try to parse the default value + Then the parameter openApiString should be of type System.Guid + + Examples: + | ParameterLocation | DefaultValue | ExpectedResult | + | query | "9b7d63fb-1689-4697-9571-00d10b873d78" | 9b7d63fb-1689-4697-9571-00d10b873d78 | + | cookie | "9b7d63fb-1689-4697-9571-00d10b873d78" | 9b7d63fb-1689-4697-9571-00d10b873d78 | + | header | "9b7d63fb-1689-4697-9571-00d10b873d78" | 9b7d63fb-1689-4697-9571-00d10b873d78 | + +Scenario Outline: Incorrect body values + Given I have constructed the OpenAPI specification with a request body of type 'string', and format 'uuid' + When I try to parse the value '' as the request body and expect an error + Then an 'OpenApiBadRequestException' should be thrown + + Examples: + | Value | + | "This is certainly not a guid" | + | thisisnotevenvalidjson | + | true | + | false | + | null | + | 42 | + | 9223372036854775808 | + | 9223372036854775808123123123 | + | "20170721T173228Z" | + | "2017-07-21T17:32:28Z" | + +Scenario Outline: Incorrect parameter values + Given I have constructed the OpenAPI specification with a parameter with name 'openApiString', type 'string', and format 'uuid' + When I try to parse the value '' as the parameter 'openApiString' and expect an error + Then an 'OpenApiBadRequestException' should be thrown + + Examples: + | ParameterLocation | Value | + | query | This is certainly not a guid | + | query | true | + | query | false | + | query | null | + | query | 42 | + | query | 9223372036854775808 | + | query | 9223372036854775808123123123 | + | query | 20170721T173228Z | + | query | 2017-07-21T17:32:28Z | + | header | This is certainly not a guid | + | cookie | This+is+certainly+not+a+guid | + | path | This+is+certainly+not+a+guid | + +Scenario Outline: Incorrect parameter defaults + Given I have constructed the OpenAPI specification with a parameter with name 'openApiString', type 'string', format 'uuid' and default value '' + When I try to parse the default value and expect an error + Then an 'OpenApiSpecificationException' should be thrown + + Examples: + # Not doing path, because it doesn't really make sense to provide a default - we're only going to match + # the path template if all parts are present. (E.g., if the path is /pets/{petId}, we don't expect /pets/ + # to match. + | ParameterLocation | DefaultValue | + | query | "This is certainly not a guid" | + | query | thisisnotevenvalidjson | + | query | true | + | query | false | + | query | 42 | + | query | 9223372036854775808 | + | query | 9223372036854775808123123123 | + | query | "20170721T173228Z" | + | query | "2017-07-21T17:32:28Z" | + | query | "42" | + | header | "This is certainly not a date" | + | cookie | "This is certainly not a date" | \ No newline at end of file diff --git a/Solutions/Menes.Specs/Features/JsonTypeConversion/UuidOutputParsing.feature b/Solutions/Menes.Specs/Features/JsonTypeConversion/UuidOutputParsing.feature new file mode 100644 index 000000000..6379d59af --- /dev/null +++ b/Solutions/Menes.Specs/Features/JsonTypeConversion/UuidOutputParsing.feature @@ -0,0 +1,16 @@ +@perScenarioContainer + +Feature: Uuid Output Parsing + In order to implement a web API + As a developer + I want to be able to specify uuid-formatted string values as or in response bodies within the OpenAPI specification and have corresponding response bodies deserialized and validated + + +Scenario Outline: Valid values for simple types + Given I have constructed the OpenAPI specification with a response body of type 'string', and format 'uuid' + When I try to build a response body from the value '' of type 'System.Uri' + Then the response body should be '' + + Examples: + | Value | ExpectedResult | + | 9b7d63fb-1689-4697-9571-00d10b873d78 | "9b7d63fb-1689-4697-9571-00d10b873d78" | diff --git a/Solutions/Menes.Specs/Features/OpenApiDefaultParameterParsing.feature b/Solutions/Menes.Specs/Features/OpenApiDefaultParameterParsing.feature deleted file mode 100644 index abbd6d7ca..000000000 --- a/Solutions/Menes.Specs/Features/OpenApiDefaultParameterParsing.feature +++ /dev/null @@ -1,94 +0,0 @@ -@perScenarioContainer - -Feature: OpenApiDefaultParameterParsing - In order to simplify setting values for parameters required for the underlying operation - As a developer - I want to be able to specify default values for those parameters within the OpenAPI specification and have those parameters validated and serialized for use downstream. - -Scenario Outline: Parameters with valid values for simple types - Given I have constructed the OpenAPI specification with a parameter with name , type , format and default value - When I try to parse the default value - Then the parameter should be of type - - Examples: - | ParameterLocation | ParameterName | Type | Format | DefaultValue | ExpectedResult | ExpectedResultType | - | query | openApiDate | string | date | 2017-07-21 | 2017-07-21T00:00:00Z | System.DateTimeOffset | - | query | openApiDateTime | string | date-time | "2017-07-21T17:32:28Z" | 2017-07-21T17:32:28+00:00 | System.String | - | query | openApiPassword | string | password | myVErySeCurePAsSworD123 | myVErySeCurePAsSworD123 | System.String | - | query | openApiByte | string | byte | U3dhZ2dlciByb2Nrcw== | U3dhZ2dlciByb2Nrcw== | ByteArrayFromBase64String | - | query | openApiString | string | uuid | 9b7d63fb-1689-4697-9571-00d10b873d78 | 9b7d63fb-1689-4697-9571-00d10b873d78 | System.Guid | - | query | openApiString | string | uri | "https://myuri.com" | https://myuri.com | System.Uri | - | query | openApiString | string | | "I said \"What is a 'PC'?\"" | I said "What is a 'PC'?" | System.String | - | query | openApiBoolean | boolean | | true | true | System.Boolean | - | query | openApiLong | integer | int64 | 9223372036854775807 | 9223372036854775807 | System.Int64 | - | query | openApiInteger | integer | | 1234 | 1234 | System.Int32 | - | query | openApiNumber | number | | 1234.5 | 1234.5 | System.Double | - | query | openApiFloat | number | float | 1234.5 | 1234.5 | System.Single | - | query | openApiDouble | number | double | 1234.5678 | 1234.5678 | System.Double | - | header | openApiDate | string | date | 2017-07-21 | 2017-07-21T00:00:00Z | System.DateTimeOffset | - | header | openApiDateTime | string | date-time | "2017-07-21T17:32:28Z" | 2017-07-21T17:32:28+00:00 | System.String | - | header | openApiPassword | string | password | myVErySeCurePAsSworD123 | myVErySeCurePAsSworD123 | System.String | - | header | openApiByte | string | byte | U3dhZ2dlciByb2Nrcw== | U3dhZ2dlciByb2Nrcw== | ByteArrayFromBase64String | - | header | openApiString | string | uuid | 9b7d63fb-1689-4697-9571-00d10b873d78 | 9b7d63fb-1689-4697-9571-00d10b873d78 | System.Guid | - | header | openApiString | string | uri | "https://myuri.com" | https://myuri.com | System.Uri | - | header | openApiString | string | | "I said \"What is a 'PC'?\"" | I said "What is a 'PC'?" | System.String | - | header | openApiBoolean | boolean | | true | true | System.Boolean | - | header | openApiLong | integer | int64 | 9223372036854775807 | 9223372036854775807 | System.Int64 | - | header | openApiInteger | integer | | 1234 | 1234 | System.Int32 | - | header | openApiNumber | number | | 1234.5 | 1234.5 | System.Double | - | header | openApiFloat | number | float | 1234.5 | 1234.5 | System.Single | - | header | openApiDouble | number | double | 1234.5678 | 1234.5678 | System.Double | - | cookie | openApiDate | string | date | 2017-07-21 | 2017-07-21T00:00:00Z | System.DateTimeOffset | - | cookie | openApiDateTime | string | date-time | "2017-07-21T17:32:28Z" | 2017-07-21T17:32:28+00:00 | System.String | - | cookie | openApiPassword | string | password | myVErySeCurePAsSworD123 | myVErySeCurePAsSworD123 | System.String | - | cookie | openApiByte | string | byte | U3dhZ2dlciByb2Nrcw== | U3dhZ2dlciByb2Nrcw== | ByteArrayFromBase64String | - | cookie | openApiString | string | uuid | 9b7d63fb-1689-4697-9571-00d10b873d78 | 9b7d63fb-1689-4697-9571-00d10b873d78 | System.Guid | - | cookie | openApiString | string | uri | "https://myuri.com" | https://myuri.com | System.Uri | - | cookie | openApiString | string | | "I said \"What the ♻😟¥a is a 'PC'?\"" | I said "What the ♻😟¥a is a 'PC'?" | System.String | - | cookie | openApiBoolean | boolean | | true | true | System.Boolean | - | cookie | openApiLong | integer | int64 | 9223372036854775807 | 9223372036854775807 | System.Int64 | - | cookie | openApiInteger | integer | | 1234 | 1234 | System.Int32 | - | cookie | openApiNumber | number | | 1234.5 | 1234.5 | System.Double | - | cookie | openApiFloat | number | float | 1234.5 | 1234.5 | System.Single | - | cookie | openApiDouble | number | double | 1234.5678 | 1234.5678 | System.Double | - -Scenario: Array parameter with items of simple type - Given I have constructed the OpenAPI specification with a parameter with name 'openApiArray', of type array, containing items of type 'integer', and the default value for the parameter is '[1,2,3,4,5]' - When I try to parse the default value - Then the parameter openApiArray should be [1,2,3,4,5] of type System.String - -Scenario: Array parameter with items of array type - Given I have constructed the OpenAPI specification with a parameter with name 'openApiNestedArray', of type array, containing items which are arrays themselves with item type 'integer', and the default value for the parameter is '[[1],[2,3],[4,5,6]]' - When I try to parse the default value - Then the parameter openApiNestedArray should be [[1],[2,3],[4,5,6]] of type System.String - -Scenario: Array parameter with items of object type - Given I have constructed the OpenAPI specification with a parameter with name 'openApiArrayWithObjectItems', of type array, containing items which are objects which has the property structure '{ "id": { "type": "integer" }, "name": {"type": "string"} }', and the default value for the parameter is '[{"id": 123, "name": "Ed"}, {"id": 456, "name": "Ian"}]' - When I try to parse the default value - Then the parameter openApiArrayWithObjectItems should be [{"id":123,"name":"Ed"},{"id":456,"name":"Ian"}] of type System.String - -Scenario: Object parameter with properties of simple types - Given I have constructed the OpenAPI specification with a parameter with name 'openApiObject', of type object, containing properties in the structure '{ "id": { "type": "integer" }, "name": {"type": "string"} }', and the default value for the parameter is '{"id":123, "name": "Ed"}' - When I try to parse the default value - Then the parameter openApiObject should be {"id":123,"name":"Ed"} of type System.String - -Scenario: Object parameter with properties of complex types - Given I have constructed the OpenAPI specification with a parameter with name 'openApiObjectWithComplexProperties', of type object, containing properties in the structure '{ "names": { "type": "array", "items": { "type": "string" } }, "details": {"type": "object", "properties": { "age": { "type": "integer" }, "hairColour": { "type": "string" } } } }', and the default value for the parameter is '{"names": ["Ed","Ian"] , "details": {"age": 24, "hairColour": "Brown"} }' - When I try to parse the default value - Then the parameter openApiObjectWithComplexProperties should be {"names":["Ed","Ian"],"details":{"age":24,"hairColour":"Brown"}} of type System.String - -Scenario: Any parameter with null default value - Given I have constructed the OpenAPI specification with a query parameter with name openApiNull, type string, format null and a null default value - When I try to parse the default value and expect an error - Then an 'OpenApiSpecificationException' should be thrown - -Scenario Outline: Incorrect parameter values - Given I have constructed the OpenAPI specification with a parameter with name , type , format and default value - When I try to parse the default value and expect an error - Then an '' should be thrown - - Examples: - | ParameterLocation | ParameterName | Type | Format | DefaultValue | ExceptionType | - | query | openApiDate | string | date | This is certainly not a date | FormatException | - | header | openApiDateTime | string | date-time | 20170721T173228Z | OpenApiBadRequestException | - | cookie | openApiLong | integer | int64 | 9223372036854775808123123123 | OpenApiSpecificationException | \ No newline at end of file diff --git a/Solutions/Menes.Specs/Steps/LinkCollectionExtensionsSteps.cs b/Solutions/Menes.Specs/Steps/LinkCollectionExtensionsSteps.cs index 03517eef0..4d9250e3f 100644 --- a/Solutions/Menes.Specs/Steps/LinkCollectionExtensionsSteps.cs +++ b/Solutions/Menes.Specs/Steps/LinkCollectionExtensionsSteps.cs @@ -4,10 +4,12 @@ namespace Menes.Specs.Steps { - using System; using Menes.Links; + using Microsoft.OpenApi.Models; + using Moq; + using TechTalk.SpecFlow; [Binding] diff --git a/Solutions/Menes.Specs/Steps/OpenApiDefaultParameterParsingSteps.cs b/Solutions/Menes.Specs/Steps/OpenApiDefaultParameterParsingSteps.cs deleted file mode 100644 index 309842ad5..000000000 --- a/Solutions/Menes.Specs/Steps/OpenApiDefaultParameterParsingSteps.cs +++ /dev/null @@ -1,169 +0,0 @@ -// -// Copyright (c) Endjin Limited. All rights reserved. -// - -namespace Menes.Specs.Steps -{ - using System; - using System.Collections.Generic; - using Corvus.Testing.SpecFlow; - using Menes.Internal; - using Microsoft.AspNetCore.Http; - using Microsoft.Extensions.DependencyInjection; - using Microsoft.Extensions.Logging; - using Microsoft.OpenApi.Models; - using Microsoft.OpenApi.Readers; - using NUnit.Framework; - using TechTalk.SpecFlow; - - [Binding] - public class OpenApiDefaultParameterParsingSteps - { - private readonly ScenarioContext scenarioContext; - private IDictionary? parameters; - private Exception? exception; - - public OpenApiDefaultParameterParsingSteps(ScenarioContext scenarioContext) - { - this.scenarioContext = scenarioContext; - } - - [Given("I have constructed the OpenAPI specification with a (.*) parameter with name (.*), type (.*), format (.*) and default value (.*)")] - public void GivenIConstructASimpleParameter( - string parameterLocation, - string parameterName, - string parameterType, - string parameterFormat, - string parameterDefaultValue) - { - //// Build OpenAPI spec object from scratch to mimic reality. Cutting corners by initializing an - //// OpenApiDocument directly removes the ability for the the parameter type to be inferred. Something - //// that this test is trying to cover. - - string openApiSpec = $"{{ \"openapi\": \"3.0.1\", \"info\": {{ \"title\": \"Swagger Petstore (Simple)\", \"version\": \"1.0.0\" }}, \"servers\": [ {{ \"url\": \"http://petstore.swagger.io/api\" }} ], \"paths\": {{ \"/pets\": {{ \"get\": {{ \"summary\": \"List all pets\", \"operationId\": \"listPets\", \"parameters\": [ {{ \"name\": \"{parameterName}\", \"in\": \"{parameterLocation}\", \"schema\": {{ \"type\": \"{parameterType}\", \"format\": \"{parameterFormat}\", \"default\": {parameterDefaultValue} }} }} ], \"responses\": {{ \"200\": {{ \"description\": \"OK\" }} }} }} }} }} }}"; - - this.InitializeDocumentProviderAndPathMatcher(openApiSpec); - } - - [Given("I have constructed the OpenAPI specification with a parameter with name '(.*)', of type array, containing items of type '(.*)', and the default value for the parameter is '(.*)'")] - public void GivenIConstructAnArrayParameterWithSimpleItems( - string parameterName, - string arrayItemType, - string parameterDefaultValue) - { - string openApiSpec = $"{{ \"openapi\": \"3.0.1\", \"info\": {{ \"title\": \"Swagger Petstore (Simple)\", \"version\": \"1.0.0\" }}, \"servers\": [ {{ \"url\": \"http://petstore.swagger.io/api\" }} ], \"paths\": {{ \"/pets\": {{ \"get\": {{ \"summary\": \"List all pets\", \"operationId\": \"listPets\", \"parameters\": [ {{ \"name\": \"{parameterName}\", \"in\": \"query\", \"schema\": {{ \"type\": \"array\", \"items\": {{ type: \"{arrayItemType}\" }}, \"default\": {parameterDefaultValue} }} }} ], \"responses\": {{ \"200\": {{ \"description\": \"OK\" }} }} }} }} }} }}"; - - this.InitializeDocumentProviderAndPathMatcher(openApiSpec); - } - - [Given("I have constructed the OpenAPI specification with a parameter with name '(.*)', of type array, containing items which are arrays themselves with item type '(.*)', and the default value for the parameter is '(.*)'")] - public void GivenIConstructAnArrayParameterWithArrayItems( - string parameterName, - string nestedArrayItemType, - string parameterDefaultValue) - { - string openApiSpec = $"{{ \"openapi\": \"3.0.1\", \"info\": {{ \"title\": \"Swagger Petstore (Simple)\", \"version\": \"1.0.0\" }}, \"servers\": [ {{ \"url\": \"http://petstore.swagger.io/api\" }} ], \"paths\": {{ \"/pets\": {{ \"get\": {{ \"summary\": \"List all pets\", \"operationId\": \"listPets\", \"parameters\": [ {{ \"name\": \"{parameterName}\", \"in\": \"query\", \"schema\": {{ \"type\": \"array\", \"items\": {{ \"type\": \"array\", \"items\": {{ \"type\": \"{nestedArrayItemType}\" }} }}, \"default\": {parameterDefaultValue} }} }} ], \"responses\": {{ \"200\": {{ \"description\": \"OK\" }} }} }} }} }} }}"; - - this.InitializeDocumentProviderAndPathMatcher(openApiSpec); - } - - [Given("I have constructed the OpenAPI specification with a parameter with name '(.*)', of type array, containing items which are objects which has the property structure '(.*)', and the default value for the parameter is '(.*)'")] - public void GivenIConstructAnArrayParameterWithObjectItems( - string parameterName, - string objectProperties, - string parameterDefaultValue) - { - string openApiSpec = $"{{ \"openapi\": \"3.0.1\", \"info\": {{ \"title\": \"Swagger Petstore (Simple)\", \"version\": \"1.0.0\" }}, \"servers\": [ {{ \"url\": \"http://petstore.swagger.io/api\" }} ], \"paths\": {{ \"/pets\": {{ \"get\": {{ \"summary\": \"List all pets\", \"operationId\": \"listPets\", \"parameters\": [ {{ \"name\": \"{parameterName}\", \"in\": \"query\", \"schema\": {{ \"type\": \"array\", \"items\": {{ \"type\": \"object\", \"properties\": {objectProperties} }}, \"default\": {parameterDefaultValue} }} }} ], \"responses\": {{ \"200\": {{ \"description\": \"OK\" }} }} }} }} }} }}"; - - this.InitializeDocumentProviderAndPathMatcher(openApiSpec); - } - - [Given("I have constructed the OpenAPI specification with a parameter with name '(.*)', of type object, containing properties in the structure '(.*)', and the default value for the parameter is '(.*)'")] - public void GivenIConstructAnObjectParameterWithSimpleProperties( - string parameterName, - string objectProperties, - string parameterDefaultValue) - { - string openApiSpec = $"{{ \"openapi\": \"3.0.1\", \"info\": {{ \"title\": \"Swagger Petstore (Simple)\", \"version\": \"1.0.0\" }}, \"servers\": [ {{ \"url\": \"http://petstore.swagger.io/api\" }} ], \"paths\": {{ \"/pets\": {{ \"get\": {{ \"summary\": \"List all pets\", \"operationId\": \"listPets\", \"parameters\": [ {{ \"name\": \"{parameterName}\", \"in\": \"query\", \"schema\": {{ \"type\": \"object\", \"properties\": {objectProperties}, \"default\": {parameterDefaultValue} }} }} ], \"responses\": {{ \"200\": {{ \"description\": \"OK\" }} }} }} }} }} }}"; - - this.InitializeDocumentProviderAndPathMatcher(openApiSpec); - } - - [Given("I have constructed the OpenAPI specification with a (.*) parameter with name (.*), type (.*), format (.*) and a null default value")] - public void GivenIConstructAParameterWithANullDefaultValue( - string parameterLocation, - string parameterName, - string parameterType, - string parameterFormat) - { - string openApiSpec = $"{{ \"openapi\": \"3.0.1\", \"info\": {{ \"title\": \"Swagger Petstore (Simple)\", \"version\": \"1.0.0\" }}, \"servers\": [ {{ \"url\": \"http://petstore.swagger.io/api\" }} ], \"paths\": {{ \"/pets\": {{ \"get\": {{ \"summary\": \"List all pets\", \"operationId\": \"listPets\", \"parameters\": [ {{ \"name\": \"{parameterName}\", \"in\": \"{parameterLocation}\", \"schema\": {{ \"type\": \"{parameterType}\", \"format\": \"{parameterFormat}\", \"default\": null, \"nullable\": true }} }} ], \"responses\": {{ \"200\": {{ \"description\": \"OK\" }} }} }} }} }} }}"; - - this.InitializeDocumentProviderAndPathMatcher(openApiSpec); - } - - [When("I try to parse the default value")] - public async System.Threading.Tasks.Task WhenITryToParseTheDefaultValueAsync() - { - IPathMatcher matcher = this.scenarioContext.Get(); - - IOpenApiParameterBuilder builder = ContainerBindings.GetServiceProvider(this.scenarioContext).GetRequiredService>(); - - matcher.FindOperationPathTemplate("/pets", "GET", out OpenApiOperationPathTemplate? operationPathTemplate); - - var context = new DefaultHttpContext(); - - this.parameters = await builder.BuildParametersAsync(context.Request, operationPathTemplate!).ConfigureAwait(false); - } - - [When("I try to parse the default value and expect an error")] - public async System.Threading.Tasks.Task WhenITryToParseTheDefaultValueAndExpectAnErrorAsync() - { - try - { - await this.WhenITryToParseTheDefaultValueAsync().ConfigureAwait(false); - } - catch (Exception ex) - { - this.exception = ex; - } - } - - [Then("the parameter (.*) should be (.*) of type (.*)")] - public void ThenTheSerializedResultShouldBe(string parameterName, string expectedResultAsString, string expectedType) - { - object expectedResult = expectedType switch - { - "ByteArrayFromBase64String" => Convert.FromBase64String(expectedResultAsString), - "System.DateTimeOffset" => DateTimeOffset.Parse(expectedResultAsString), - "System.Guid" => Guid.Parse(expectedResultAsString), - "System.Uri" => new Uri(expectedResultAsString), - _ => Convert.ChangeType(expectedResultAsString, Type.GetType(expectedType)!), - }; - - Assert.AreEqual(expectedResult, this.parameters![parameterName]); - Assert.AreEqual(expectedResult.GetType(), this.parameters![parameterName]!.GetType()); - } - - [Then("an '(.*)' should be thrown")] - public void ThenAnShouldBeThrown(string exceptionType) - { - Assert.IsNotNull(this.exception); - - Assert.AreEqual(exceptionType, this.exception!.GetType().Name); - } - - private void InitializeDocumentProviderAndPathMatcher(string openApiSpec) - { - OpenApiDocument document = new OpenApiStringReader().Read(openApiSpec, out OpenApiDiagnostic _); - - var documentProvider = new OpenApiDocumentProvider(new LoggerFactory().CreateLogger()); - documentProvider.Add(document); - - this.scenarioContext.Set(documentProvider); - - var matcher = new PathMatcher(documentProvider); - - this.scenarioContext.Set(matcher); - } - } -} \ No newline at end of file diff --git a/Solutions/Menes.Specs/Steps/OpenApiParameterParsingSteps.cs b/Solutions/Menes.Specs/Steps/OpenApiParameterParsingSteps.cs new file mode 100644 index 000000000..8775fc19e --- /dev/null +++ b/Solutions/Menes.Specs/Steps/OpenApiParameterParsingSteps.cs @@ -0,0 +1,1121 @@ +// +// Copyright (c) Endjin Limited. All rights reserved. +// + +namespace Menes.Specs.Steps +{ + using System; + using System.Collections; + using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; + using System.IO; + using System.Text; + using System.Threading.Tasks; + + using Corvus.Testing.SpecFlow; + + using Menes.Internal; + using Menes.Specs.Fakes; + + using Microsoft.AspNetCore.Http; + using Microsoft.Extensions.DependencyInjection; + using Microsoft.Extensions.Logging; + using Microsoft.OpenApi.Models; + using Microsoft.OpenApi.Readers; + + using Newtonsoft.Json; + + using NUnit.Framework; + + using TechTalk.SpecFlow; + + [Binding] + public class OpenApiParameterParsingSteps + { + private readonly ScenarioContext scenarioContext; + private IPathMatcher? matcher; + private IDictionary? parameters; + private Exception? exception; + private string? responseBody; + + public OpenApiParameterParsingSteps(ScenarioContext scenarioContext) + { + this.scenarioContext = scenarioContext; + } + + private IPathMatcher Matcher => this.matcher ?? throw new InvalidOperationException("Matcher not set - test must first create a fake OpenAPI spec"); + + [Given("I have constructed the OpenAPI specification with a request body of type '([^']*)', and format '([^']*)'")] + public void GivenIHaveConstructedTheOpenAPISpecificationWithARequestBodyOfTypeAndFormat( + string bodyType, string bodyFormat) + { + string openApiSpec = $$""" + { + "openapi": "3.0.1", + "info": { + "title": "Swagger Petstore (Simple)", + "version": "1.0.0" + }, + "servers": [ { "url": "http://petstore.swagger.io/api" } ], + "paths": { + "/pets": { + "post": { + "summary": "Create a pet", + "operationId": "createPets", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "{{bodyType}}", + "format": "{{bodyFormat}}" + } + } + } + }, + "responses": { "201": { "description": "Created" } } + } + } + } + } + """; + + this.InitializeDocumentProviderAndPathMatcher(openApiSpec); + } + + [Given("I have constructed the OpenAPI specification with a response body of type '([^']*)', and format '([^']*)'")] + public void GivenIHaveConstructedTheOpenAPISpecificationWithAResponseBodyOfTypeAndFormat( + string bodyType, string bodyFormat) + { + string openApiSpec = $$""" + { + "openapi": "3.0.1", + "info": { + "title": "Swagger Petstore (Simple)", + "version": "1.0.0" + }, + "servers": [ { "url": "http://petstore.swagger.io/api" } ], + "paths": { + "/pets": { + "get": { + "summary": "Get a pet", + "operationId": "getPet", + "responses": { + "201": { + "description": "A pet", + "content": { + "application/json": { + "schema": { + "type": "{{bodyType}}", + "format": "{{bodyFormat}}" + } + } + } + } + } + } + } + } + } + """; + + this.InitializeDocumentProviderAndPathMatcher(openApiSpec); + } + + [Given("I have constructed the OpenAPI specification with a response body of type object, containing properties in the structure '([^']*)'")] + public void GivenIHaveConstructedTheOpenAPISpecificationWithAResponseBodyOfTypeObjectContainingPropertiesInTheStructure( + string objectProperties) + { + string openApiSpec = $$""" + { + "openapi": "3.0.1", + "info": { + "title": "Swagger Petstore (Simple)", + "version": "1.0.0" + }, + "servers": [ { "url": "http://petstore.swagger.io/api" } ], + "paths": { + "/pets": { + "get": { + "summary": "Get a pet", + "operationId": "getPet", + "responses": { + "200": { + "description": "A pet", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": {{objectProperties}} + } + } + } + } + } + } + } + } + } + """; + + this.InitializeDocumentProviderAndPathMatcher(openApiSpec); + } + + [Given("I have constructed the OpenAPI specification with a ([^ ]*) parameter with name '([^']*)', type '([^']*)', and format '([^']*)'")] + public void GivenIConstructAnOpenApiSpecWithSimpleParameter( + string parameterLocation, + string parameterName, + string parameterType, + string parameterFormat) + { + //// Build OpenAPI spec object from scratch to mimic reality. Cutting corners by initializing an + //// OpenApiDocument directly removes the ability for the the parameter type to be inferred. Something + //// that this test is trying to cover. + + string path = parameterLocation == "path" ? $"/pets/{{{parameterName}}}" : "/pets"; + string openApiSpec = $$""" + { + "openapi": "3.0.1", + "info": { + "title": "Swagger Petstore (Simple)", + "version": "1.0.0" + }, + "servers": [ { "url": "http://petstore.swagger.io/api" } ], + "paths": { + "{{path}}": { + "get": { + "summary": "List all pets", + "operationId": "listPets", + "parameters": [ + { + "name": "{{parameterName}}", + "in": "{{parameterLocation}}", + "schema": { + "type": "{{parameterType}}", + "format": "{{parameterFormat}}", + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + } + } + } + """; + + this.InitializeDocumentProviderAndPathMatcher(openApiSpec); + } + + [Given("I have constructed the OpenAPI specification with a ([^ ]*) parameter with name '([^']*)', type '([^']*)', format '([^']*)' and default value '(.*)'")] + public void GivenIConstructAnOpenApiSpecWithSimpleParameterWithDefault( + string parameterLocation, + string parameterName, + string parameterType, + string parameterFormat, + string parameterDefaultValue) + { + //// Build OpenAPI spec object from scratch to mimic reality. Cutting corners by initializing an + //// OpenApiDocument directly removes the ability for the the parameter type to be inferred. Something + //// that this test is trying to cover. + + string openApiSpec = $"{{ \"openapi\": \"3.0.1\", \"info\": {{ \"title\": \"Swagger Petstore (Simple)\", \"version\": \"1.0.0\" }}, \"servers\": [ {{ \"url\": \"http://petstore.swagger.io/api\" }} ], \"paths\": {{ \"/pets\": {{ \"get\": {{ \"summary\": \"List all pets\", \"operationId\": \"listPets\", \"parameters\": [ {{ \"name\": \"{parameterName}\", \"in\": \"{parameterLocation}\", \"schema\": {{ \"type\": \"{parameterType}\", \"format\": \"{parameterFormat}\", \"default\": {parameterDefaultValue} }} }} ], \"responses\": {{ \"200\": {{ \"description\": \"OK\" }} }} }} }} }} }}"; + + this.InitializeDocumentProviderAndPathMatcher(openApiSpec); + } + + [Given("I have constructed the OpenAPI specification with a request body of type array, containing items of type '([^']*)'")] + public void GivenIHaveConstructedTheOpenAPISpecificationWithARequestBodyOfTypeArrayContainingItemsOfType(string arrayItemType) + { + string openApiSpec = $$""" + { + "openapi": "3.0.1", + "info": { + "title": "Swagger Petstore (Simple)", + "version": "1.0.0" + }, + "servers": [ { "url": "http://petstore.swagger.io/api" } ], + "paths": { + "/pets": { + "post": { + "summary": "Create a pet", + "operationId": "createPets", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + type: "{{arrayItemType}}" + } + } + } + } + }, + "responses": { "201": { "description": "Created" } } + } + } + } + } + """; + + this.InitializeDocumentProviderAndPathMatcher(openApiSpec); + } + + [Given("I have constructed the OpenAPI specification with a request body of type array, containing items of type '([^']*)' and format '([^']*)'")] + public void GivenIHaveConstructedTheOpenAPISpecificationWithARequestBodyOfTypeArrayContainingItemsOfTypeAndFormat( + string arrayItemType, string arrayItemFormat) + { + string openApiSpec = $$""" + { + "openapi": "3.0.1", + "info": { + "title": "Swagger Petstore (Simple)", + "version": "1.0.0" + }, + "servers": [ { "url": "http://petstore.swagger.io/api" } ], + "paths": { + "/pets": { + "post": { + "summary": "Create a pet", + "operationId": "createPets", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "{{arrayItemType}}", + "format": "{{arrayItemFormat}}" + } + } + } + } + }, + "responses": { "201": { "description": "Created" } } + } + } + } + } + """; + + this.InitializeDocumentProviderAndPathMatcher(openApiSpec); + } + + [Given("I have constructed the OpenAPI specification with a parameter with name '([^']*)', of type array, containing items of type '([^']*)'")] + public void GivenIConstructAnArrayParameterWithSimpleItems( + string parameterName, + string arrayItemType) + { + string openApiSpec = $$""" + { + "openapi": "3.0.1", + "info": { "title": "Swagger Petstore (Simple)", "version": "1.0.0" }, + "servers": [ { "url": "http://petstore.swagger.io/api" } ], + "paths": { + "/pets": { + "get": { + "summary": "List all pets", + "operationId": "listPets", + "parameters": [ + { + "name": "{{parameterName}}", + "in": "query", + "schema": { + "type": "array", + "items": { + type: "{{arrayItemType}}" + } + } + } + ], + "responses": { + "200": { "description": "OK" } + } + } + } + } + } + """; + + this.InitializeDocumentProviderAndPathMatcher(openApiSpec); + } + + [Given("I have constructed the OpenAPI specification with a parameter with name '([^']*)', of type array, containing items of type '([^']*)' and format '([^']*)'")] + public void GivenIConstructAnArrayParameterWithSimpleItemsWithFormat( + string parameterName, + string arrayItemType, + string arrayItemFormat) + { + string openApiSpec = $$""" + { + "openapi": "3.0.1", + "info": { "title": "Swagger Petstore (Simple)", "version": "1.0.0" }, + "servers": [ { "url": "http://petstore.swagger.io/api" } ], + "paths": { + "/pets": { + "get": { + "summary": "List all pets", + "operationId": "listPets", + "parameters": [ + { + "name": "{{parameterName}}", + "in": "query", + "schema": { + "type": "array", + "items": { + "type": "{{arrayItemType}}", + "format": "{{arrayItemFormat}}" + } + } + } + ], + "responses": { + "200": { "description": "OK" } + } + } + } + } + } + """; + + this.InitializeDocumentProviderAndPathMatcher(openApiSpec); + } + + [Given("I have constructed the OpenAPI specification with a parameter with name '([^']*)', of type array, containing items of type '([^']*)', and the default value for the parameter is '(.*)'")] + public void GivenIConstructAnArrayParameterWithSimpleItemsWithDefault( + string parameterName, + string arrayItemType, + string parameterDefaultValue) + { + string openApiSpec = $"{{ \"openapi\": \"3.0.1\", \"info\": {{ \"title\": \"Swagger Petstore (Simple)\", \"version\": \"1.0.0\" }}, \"servers\": [ {{ \"url\": \"http://petstore.swagger.io/api\" }} ], \"paths\": {{ \"/pets\": {{ \"get\": {{ \"summary\": \"List all pets\", \"operationId\": \"listPets\", \"parameters\": [ {{ \"name\": \"{parameterName}\", \"in\": \"query\", \"schema\": {{ \"type\": \"array\", \"items\": {{ type: \"{arrayItemType}\" }}, \"default\": {parameterDefaultValue} }} }} ], \"responses\": {{ \"200\": {{ \"description\": \"OK\" }} }} }} }} }} }}"; + + this.InitializeDocumentProviderAndPathMatcher(openApiSpec); + } + + [Given("I have constructed the OpenAPI specification with a parameter with name '([^']*)', of type array, containing items of type '([^']*)' and format '([^']*)', and the default value for the parameter is '(.*)'")] + public void GivenIConstructAnArrayParameterWithSimpleItemsWithFormatWithDefault( + string parameterName, + string arrayItemType, + string arrayItemFormat, + string parameterDefaultValue) + { + string openApiSpec = $"{{ \"openapi\": \"3.0.1\", \"info\": {{ \"title\": \"Swagger Petstore (Simple)\", \"version\": \"1.0.0\" }}, \"servers\": [ {{ \"url\": \"http://petstore.swagger.io/api\" }} ], \"paths\": {{ \"/pets\": {{ \"get\": {{ \"summary\": \"List all pets\", \"operationId\": \"listPets\", \"parameters\": [ {{ \"name\": \"{parameterName}\", \"in\": \"query\", \"schema\": {{ \"type\": \"array\", \"items\": {{ \"type\": \"{arrayItemType}\", \"format\": \"{arrayItemFormat}\" }}, \"default\": {parameterDefaultValue} }} }} ], \"responses\": {{ \"200\": {{ \"description\": \"OK\" }} }} }} }} }} }}"; + + this.InitializeDocumentProviderAndPathMatcher(openApiSpec); + } + + [Given("I have constructed the OpenAPI specification with a request body of type array, containing items which are arrays themselves with item type '([^']*)'")] + public void GivenIHaveConstructedTheOpenAPISpecificationWithARequestBodyOfTypeArrayContainingItemsWhichAreArraysThemselvesWithItemType( + string nestedArrayItemType) + { + string openApiSpec = $$""" + { + "openapi": "3.0.1", + "info": { + "title": "Swagger Petstore (Simple)", + "version": "1.0.0" + }, + "servers": [ { "url": "http://petstore.swagger.io/api" } ], + "paths": { + "/pets": { + "post": { + "summary": "Create a pet", + "operationId": "createPets", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "array", + "items": { + "type": "{{nestedArrayItemType}}" + } + } + } + } + } + }, + "responses": { "201": { "description": "Created" } } + } + } + } + } + """; + + this.InitializeDocumentProviderAndPathMatcher(openApiSpec); + } + + [Given("I have constructed the OpenAPI specification with a request body of type array, containing items which are arrays themselves with item type '([^']*)' and format '([^']*)'")] + public void GivenIHaveConstructedTheOpenAPISpecificationWithARequestBodyOfTypeArrayContainingItemsWhichAreArraysThemselvesWithItemTypeAndFormat( + string nestedArrayItemType, + string nestedArrayItemFormat) + { + string openApiSpec = $$""" + { + "openapi": "3.0.1", + "info": { + "title": "Swagger Petstore (Simple)", + "version": "1.0.0" + }, + "servers": [ { "url": "http://petstore.swagger.io/api" } ], + "paths": { + "/pets": { + "post": { + "summary": "Create a pet", + "operationId": "createPets", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "array", + "items": { + "type": "{{nestedArrayItemType}}", + "format": "{{nestedArrayItemFormat}}" + } + } + } + } + } + }, + "responses": { "201": { "description": "Created" } } + } + } + } + } + """; + + this.InitializeDocumentProviderAndPathMatcher(openApiSpec); + } + + [Given("I have constructed the OpenAPI specification with a parameter with name '([^']*)', of type array, containing items which are arrays themselves with item type '([^']*)'")] + public void GivenIConstructAnArrayParameterWithArrayItems( + string parameterName, + string nestedArrayItemType) + { + string openApiSpec = $$""" + { + "openapi": "3.0.1", + "info": { "title": "Swagger Petstore (Simple)", "version": "1.0.0" }, + "servers": [ { "url": "http://petstore.swagger.io/api" } ], + "paths": { + "/pets": { + "get": { + "summary": "List all pets", + "operationId": "listPets", + "parameters": [ + { + "name": "{{parameterName}}", + "in": "query", + "schema": { + "type": "array", + "items": { + "type": "array", + "items": { + "type": "{{nestedArrayItemType}}" + } + } + } + } + ], + "responses": { + "200": { "description": "OK" } + } + } + } + } + } + """; + + this.InitializeDocumentProviderAndPathMatcher(openApiSpec); + } + + [Given("I have constructed the OpenAPI specification with a parameter with name '([^']*)', of type array, containing items which are arrays themselves with item type '([^']*)' and format '([^']*)'")] + public void GivenIHaveConstructedTheOpenAPISpecificationWithAParameterWithNameOfTypeArrayContainingItemsWhichAreArraysThemselvesWithItemTypeAndFormat( + string parameterName, + string nestedArrayItemType, + string nestedArrayItemFormat) + { + string openApiSpec = $$""" + { + "openapi": "3.0.1", + "info": { "title": "Swagger Petstore (Simple)", "version": "1.0.0" }, + "servers": [ { "url": "http://petstore.swagger.io/api" } ], + "paths": { + "/pets": { + "get": { + "summary": "List all pets", + "operationId": "listPets", + "parameters": [ + { + "name": "{{parameterName}}", + "in": "query", + "schema": { + "type": "array", + "items": { + "type": "array", + "items": { + "type": "{{nestedArrayItemType}}", + "format": "{{nestedArrayItemFormat}}" + } + } + } + } + ], + "responses": { + "200": { "description": "OK" } + } + } + } + } + } + """; + + this.InitializeDocumentProviderAndPathMatcher(openApiSpec); + } + + [Given("I have constructed the OpenAPI specification with a parameter with name '([^']*)', of type array, containing items which are arrays themselves with item type '([^']*)', and the default value for the parameter is '([^']*)'")] + public void GivenIConstructAnArrayParameterWithArrayItemsWithDefault( + string parameterName, + string nestedArrayItemType, + string parameterDefaultValue) + { + string openApiSpec = $"{{ \"openapi\": \"3.0.1\", \"info\": {{ \"title\": \"Swagger Petstore (Simple)\", \"version\": \"1.0.0\" }}, \"servers\": [ {{ \"url\": \"http://petstore.swagger.io/api\" }} ], \"paths\": {{ \"/pets\": {{ \"get\": {{ \"summary\": \"List all pets\", \"operationId\": \"listPets\", \"parameters\": [ {{ \"name\": \"{parameterName}\", \"in\": \"query\", \"schema\": {{ \"type\": \"array\", \"items\": {{ \"type\": \"array\", \"items\": {{ \"type\": \"{nestedArrayItemType}\" }} }}, \"default\": {parameterDefaultValue} }} }} ], \"responses\": {{ \"200\": {{ \"description\": \"OK\" }} }} }} }} }} }}"; + + this.InitializeDocumentProviderAndPathMatcher(openApiSpec); + } + + [Given("I have constructed the OpenAPI specification with a parameter with name '([^']*)', of type array, containing items which are arrays themselves with item type '([^']*)' and format '([^']*)', and the default value for the parameter is '([^']*)'")] + public void GivenIConstructAnArrayParameterWithArrayItemsWithFormatWithDefault( + string parameterName, + string nestedArrayItemType, + string nestedArrayItemFormat, + string parameterDefaultValue) + { + string openApiSpec = $"{{ \"openapi\": \"3.0.1\", \"info\": {{ \"title\": \"Swagger Petstore (Simple)\", \"version\": \"1.0.0\" }}, \"servers\": [ {{ \"url\": \"http://petstore.swagger.io/api\" }} ], \"paths\": {{ \"/pets\": {{ \"get\": {{ \"summary\": \"List all pets\", \"operationId\": \"listPets\", \"parameters\": [ {{ \"name\": \"{parameterName}\", \"in\": \"query\", \"schema\": {{ \"type\": \"array\", \"items\": {{ \"type\": \"array\", \"items\": {{ \"type\": \"{nestedArrayItemType}\", \"format\": \"{nestedArrayItemFormat}\" }} }}, \"default\": {parameterDefaultValue} }} }} ], \"responses\": {{ \"200\": {{ \"description\": \"OK\" }} }} }} }} }} }}"; + + this.InitializeDocumentProviderAndPathMatcher(openApiSpec); + } + + [Given("I have constructed the OpenAPI specification with a request body of type array, containing items which are objects which has the property structure '([^']*)'")] + public void GivenIHaveConstructedTheOpenAPISpecificationWithARequestBodyOfTypeArrayContainingItemsWhichAreObjectsWhichHasThePropertyStructure( + string objectProperties) + { + string openApiSpec = $$""" + { + "openapi": "3.0.1", + "info": { + "title": "Swagger Petstore (Simple)", + "version": "1.0.0" + }, + "servers": [ { "url": "http://petstore.swagger.io/api" } ], + "paths": { + "/pets": { + "post": { + "summary": "Create a pet", + "operationId": "createPets", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": {{objectProperties}} + } + } + } + } + }, + "responses": { "201": { "description": "Created" } } + } + } + } + } + """; + + this.InitializeDocumentProviderAndPathMatcher(openApiSpec); + } + + [Given("I have constructed the OpenAPI specification with a parameter with name '([^']*)', of type array, containing items which are objects which has the property structure '([^']*)'")] + public void GivenIConstructAnArrayParameterWithObjectItems( + string parameterName, + string objectProperties) + { + string openApiSpec = $$""" + { + "openapi": "3.0.1", + "info": { "title": "Swagger Petstore (Simple)", "version": "1.0.0" }, + "servers": [ { "url": "http://petstore.swagger.io/api" } ], + "paths": { + "/pets": { + "get": { + "summary": "List all pets", + "operationId": "listPets", + "parameters": [ + { + "name": "{{parameterName}}", + "in": "query", + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": {{objectProperties}} + } + } + } + ], + "responses": { "200": { "description": "OK" } } + } + } + } + } + """; + + this.InitializeDocumentProviderAndPathMatcher(openApiSpec); + } + + [Given("I have constructed the OpenAPI specification with a parameter with name '(.*)', of type array, containing items which are objects which has the property structure '(.*)', and the default value for the parameter is '(.*)'")] + public void GivenIConstructAnArrayParameterWithObjectItemsWithDefault( + string parameterName, + string objectProperties, + string parameterDefaultValue) + { + string openApiSpec = $"{{ \"openapi\": \"3.0.1\", \"info\": {{ \"title\": \"Swagger Petstore (Simple)\", \"version\": \"1.0.0\" }}, \"servers\": [ {{ \"url\": \"http://petstore.swagger.io/api\" }} ], \"paths\": {{ \"/pets\": {{ \"get\": {{ \"summary\": \"List all pets\", \"operationId\": \"listPets\", \"parameters\": [ {{ \"name\": \"{parameterName}\", \"in\": \"query\", \"schema\": {{ \"type\": \"array\", \"items\": {{ \"type\": \"object\", \"properties\": {objectProperties} }}, \"default\": {parameterDefaultValue} }} }} ], \"responses\": {{ \"200\": {{ \"description\": \"OK\" }} }} }} }} }} }}"; + + this.InitializeDocumentProviderAndPathMatcher(openApiSpec); + } + + [Given("I have constructed the OpenAPI specification with a request body of type object, containing properties in the structure '([^']*)'")] + public void GivenIHaveConstructedTheOpenAPISpecificationWithARequestBodyOfTypeObjectContainingPropertiesInTheStructure( + string objectProperties) + { + string openApiSpec = $$""" + { + "openapi": "3.0.1", + "info": { + "title": "Swagger Petstore (Simple)", + "version": "1.0.0" + }, + "servers": [ { "url": "http://petstore.swagger.io/api" } ], + "paths": { + "/pets": { + "post": { + "summary": "Create a pet", + "operationId": "createPets", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": {{objectProperties}} + } + } + } + }, + "responses": { "201": { "description": "Created" } } + } + } + } + } + """; + + this.InitializeDocumentProviderAndPathMatcher(openApiSpec); + } + + [Given("I have constructed the OpenAPI specification with a request body of type object, containing properties in the structure '([^']*)' with '([^']*)' as the discriminator")] + public void GivenIHaveConstructedTheOpenAPISpecificationWithARequestBodyOfTypeObjectContainingPropertiesInTheStructureWithDiscriminator( + string objectProperties, + string discriminator) + { + string openApiSpec = $$""" + { + "openapi": "3.0.1", + "info": { + "title": "Swagger Petstore (Simple)", + "version": "1.0.0" + }, + "servers": [ { "url": "http://petstore.swagger.io/api" } ], + "paths": { + "/pets": { + "post": { + "summary": "Create a pet", + "operationId": "createPets", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": {{objectProperties}}, + "discriminator": { "propertyName": "{{discriminator}}" } + } + } + } + }, + "responses": { "201": { "description": "Created" } } + } + } + } + } + """; + + this.InitializeDocumentProviderAndPathMatcher(openApiSpec); + } + + [Given("I have constructed the OpenAPI specification with a parameter with name '([^']*)', of type object, containing properties in the structure '([^']*)'")] + public void GivenIConstructAnObjectParameterWithSimpleProperties( + string parameterName, + string objectProperties) + { + string openApiSpec = $$""" + { + "openapi": "3.0.1", + "info": { "title": "Swagger Petstore (Simple)", "version": "1.0.0" }, + "servers": [ { "url": "http://petstore.swagger.io/api" } ], + "paths": { + "/pets": { + "get": { + "summary": "List all pets", + "operationId": "listPets", + "parameters": [ + { + "name": "{{parameterName}}", + "in": "query", + "schema": { + "type": "object", + "properties": {{objectProperties}} + } + } + ], + "responses": { "200": { "description": "OK" } } + } + } + } + } + """; + + this.InitializeDocumentProviderAndPathMatcher(openApiSpec); + } + + [Given("I have constructed the OpenAPI specification with a parameter with name '(.*)', of type object, containing properties in the structure '(.*)', and the default value for the parameter is '(.*)'")] + public void GivenIConstructAnObjectParameterWithSimplePropertiesWithDefault( + string parameterName, + string objectProperties, + string parameterDefaultValue) + { + string openApiSpec = $"{{ \"openapi\": \"3.0.1\", \"info\": {{ \"title\": \"Swagger Petstore (Simple)\", \"version\": \"1.0.0\" }}, \"servers\": [ {{ \"url\": \"http://petstore.swagger.io/api\" }} ], \"paths\": {{ \"/pets\": {{ \"get\": {{ \"summary\": \"List all pets\", \"operationId\": \"listPets\", \"parameters\": [ {{ \"name\": \"{parameterName}\", \"in\": \"query\", \"schema\": {{ \"type\": \"object\", \"properties\": {objectProperties}, \"default\": {parameterDefaultValue} }} }} ], \"responses\": {{ \"200\": {{ \"description\": \"OK\" }} }} }} }} }} }}"; + + this.InitializeDocumentProviderAndPathMatcher(openApiSpec); + } + + [Given("I have constructed the OpenAPI specification with a (.*) parameter with name (.*), type (.*), format (.*) and a null default value")] + public void GivenIConstructAParameterWithANullDefaultValue( + string parameterLocation, + string parameterName, + string parameterType, + string parameterFormat) + { + string openApiSpec = $"{{ \"openapi\": \"3.0.1\", \"info\": {{ \"title\": \"Swagger Petstore (Simple)\", \"version\": \"1.0.0\" }}, \"servers\": [ {{ \"url\": \"http://petstore.swagger.io/api\" }} ], \"paths\": {{ \"/pets\": {{ \"get\": {{ \"summary\": \"List all pets\", \"operationId\": \"listPets\", \"parameters\": [ {{ \"name\": \"{parameterName}\", \"in\": \"{parameterLocation}\", \"schema\": {{ \"type\": \"{parameterType}\", \"format\": \"{parameterFormat}\", \"default\": null, \"nullable\": true }} }} ], \"responses\": {{ \"200\": {{ \"description\": \"OK\" }} }} }} }} }} }}"; + + this.InitializeDocumentProviderAndPathMatcher(openApiSpec); + } + + [When("I try to parse the default value")] + public async Task WhenITryToParseTheDefaultValueAsync() + { + IOpenApiParameterBuilder builder = ContainerBindings.GetServiceProvider(this.scenarioContext).GetRequiredService>(); + + this.Matcher.FindOperationPathTemplate("/pets", "GET", out OpenApiOperationPathTemplate? operationPathTemplate); + + var context = new DefaultHttpContext(); + + this.parameters = await builder.BuildParametersAsync(context.Request, operationPathTemplate!).ConfigureAwait(false); + } + + [When("I try to parse the default value and expect an error")] + public async Task WhenITryToParseTheDefaultValueAndExpectAnErrorAsync() + { + try + { + await this.WhenITryToParseTheDefaultValueAsync().ConfigureAwait(false); + } + catch (Exception ex) + { + this.exception = ex; + } + } + + [When("I try to parse the value '(.*)' as the request body")] + public async Task WhenITryToParseTheValueAsTheRequestBody(string body) + { + IOpenApiParameterBuilder builder = ContainerBindings.GetServiceProvider(this.scenarioContext).GetRequiredService>(); + + this.Matcher.FindOperationPathTemplate("/pets", "POST", out OpenApiOperationPathTemplate? operationPathTemplate); + + var context = new DefaultHttpContext(); + context.Request.Body = new MemoryStream(Encoding.UTF8.GetBytes(body)); + context.Request.Headers.ContentType = "application/json"; + + this.parameters = await builder.BuildParametersAsync(context.Request, operationPathTemplate!).ConfigureAwait(false); + } + + [When("I try to parse the value '(.*?)' as the request body and expect an error")] + public async Task WhenITryToParseTheValueAsTheRequestBodyAndExpectAnError(string body) + { + try + { + await this.WhenITryToParseTheValueAsTheRequestBody(body).ConfigureAwait(false); + } + catch (Exception ex) + { + this.exception = ex; + } + } + + [When("I try to parse the path value '(.*?)' as the parameter '([^']*)'")] + public async Task WhenITryToParseThePathValue(string value, string unusedParameterName) + { + _ = unusedParameterName; + + IOpenApiParameterBuilder builder = ContainerBindings.GetServiceProvider(this.scenarioContext).GetRequiredService>(); + + string path = $"/pets/{value}"; + this.Matcher.FindOperationPathTemplate(path, "GET", out OpenApiOperationPathTemplate? operationPathTemplate); + + DefaultHttpContext context = new(); + context.Request.Path = path; + + this.parameters = await builder.BuildParametersAsync(context.Request, operationPathTemplate!).ConfigureAwait(false); + } + + [When("I try to parse the path value '(.*?)' as the parameter '([^']*)' and expect an error")] + public async Task WhenITryToParseThePathValueAndExpectAnErrorAsync(string value, string unusedParameterName) + { + try + { + await this.WhenITryToParseThePathValue(value, unusedParameterName).ConfigureAwait(false); + } + catch (Exception ex) + { + this.exception = ex; + } + } + + [When("I try to parse the query value '(.*?)' as the parameter '([^']*)'")] + public async Task WhenITryToParseTheQueryValue( + string value, + string parameterName) + { + IOpenApiParameterBuilder builder = ContainerBindings.GetServiceProvider(this.scenarioContext).GetRequiredService>(); + + this.Matcher.FindOperationPathTemplate("/pets", "GET", out OpenApiOperationPathTemplate? operationPathTemplate); + + var context = new DefaultHttpContext(); + context.Request.Query = new QueryCollection(new Dictionary + { + { parameterName, value }, + }); + + this.parameters = await builder.BuildParametersAsync(context.Request, operationPathTemplate!).ConfigureAwait(false); + } + + [When("I try to parse the query value '([^']*)' as the parameter '([^']*)' and expect an error")] + public async Task WhenITryToParseTheQueryValueAndExpectAnErrorAsync( + string value, + string parameterName) + { + try + { + await this.WhenITryToParseTheQueryValue(value, parameterName).ConfigureAwait(false); + } + catch (Exception ex) + { + this.exception = ex; + } + } + + [When("I try to parse the header value '([^']*)' as the parameter '([^']*)'")] + public async Task WhenITryToParseTheHeaderValue( + string value, + string parameterName) + { + IOpenApiParameterBuilder builder = ContainerBindings.GetServiceProvider(this.scenarioContext).GetRequiredService>(); + + this.Matcher.FindOperationPathTemplate("/pets", "GET", out OpenApiOperationPathTemplate? operationPathTemplate); + + var context = new DefaultHttpContext(); + context.Request.Headers.Add(parameterName, value); + + this.parameters = await builder.BuildParametersAsync(context.Request, operationPathTemplate!).ConfigureAwait(false); + } + + [When("I try to parse the header value '([^']*)' as the parameter '([^']*)' and expect an error")] + public async Task WhenITryToParseTheHeaderValueAndExpectAnErrorAsync( + string value, + string parameterName) + { + try + { + await this.WhenITryToParseTheHeaderValue(value, parameterName).ConfigureAwait(false); + } + catch (Exception ex) + { + this.exception = ex; + } + } + + [When("I try to parse the cookie value '([^']*)' as the parameter '([^']*)'")] + public async Task WhenITryToParseTheCookieValue( + string value, + string parameterName) + { + IOpenApiParameterBuilder builder = ContainerBindings.GetServiceProvider(this.scenarioContext).GetRequiredService>(); + + this.Matcher.FindOperationPathTemplate("/pets", "GET", out OpenApiOperationPathTemplate? operationPathTemplate); + + var context = new DefaultHttpContext(); + context.Request.Cookies = new FakeCookieCollection() + { + { parameterName, value }, + }; + + this.parameters = await builder.BuildParametersAsync(context.Request, operationPathTemplate!).ConfigureAwait(false); + } + + [When("I try to parse the cookie value '([^']*)' as the parameter '([^']*)' and expect an error")] + public async Task WhenITryToParseTheCookieValueAndExpectAnErrorAsync( + string value, + string parameterName) + { + try + { + await this.WhenITryToParseTheCookieValue(value, parameterName).ConfigureAwait(false); + } + catch (Exception ex) + { + this.exception = ex; + } + } + + [When("I try to build a response body from the value '([^']*)' of type '([^']*)'")] + public void WhenITryToBuildAResponseBodyFromTheValueOfTypeSystem_Boolean( + string valueAsString, string valueType) + { + object value = GetResultFromStringAndType(valueAsString, valueType); + + IEnumerable> builders = ContainerBindings.GetServiceProvider(this.scenarioContext). + GetServices>(); + + this.Matcher.FindOperationPathTemplate("/pets", "GET", out OpenApiOperationPathTemplate? operationPathTemplate); + OpenApiOperation operation = operationPathTemplate!.Operation; + + IHttpResponseResult? result = null; + foreach (IResponseOutputBuilder builder in builders) + { + if (builder.CanBuildOutput(value, operation)) + { + result = builder.BuildOutput(value, operation); + break; + } + } + + var context = new DefaultHttpContext(); + context.Response.Body = new MemoryStream(); + result!.ExecuteResultAsync(context.Response); + context.Response.Body.Position = 0; + using StreamReader sr = new(context.Response.Body); + this.responseBody = sr.ReadToEnd(); + } + + [Then("the response body should be '([^']*)'")] + public void ThenTheResponseBodyShouldBeTrue(string expectedBody) + { + Assert.AreEqual(expectedBody, this.responseBody); + } + + [Then("the parameter (.*?) should be (.*?) of type (.*)")] + public void ThenTheParameterShouldBe(string parameterName, string expectedResultAsString, string expectedType) + { + object expectedResult = GetResultFromStringAndType(expectedResultAsString, expectedType); + + Assert.AreEqual(expectedResult, this.parameters![parameterName]); + Assert.AreEqual(expectedResult.GetType(), this.parameters![parameterName]!.GetType()); + } + + [Then("the parameter (.*?) should be of type '([^']*)'")] + public void ThenTheParameterBodyShouldBeOfType(string parameterName, string expectedType) + { + Assert.AreEqual(expectedType, this.parameters![parameterName].GetType().Name); + } + + [Then("an '(.*)' should be thrown")] + public void ThenAnShouldBeThrown(string exceptionType) + { + Assert.IsNotNull(this.exception); + + Assert.AreEqual(exceptionType, this.exception!.GetType().Name); + } + + private static object GetResultFromStringAndType(string expectedResultAsString, string expectedType) + { + return expectedType switch + { + "ByteArrayFromBase64String" => Convert.FromBase64String(expectedResultAsString), + "System.DateTimeOffset" => DateTimeOffset.Parse(expectedResultAsString), + "System.Guid" => Guid.Parse(expectedResultAsString), + "System.Uri" => new Uri(expectedResultAsString, UriKind.RelativeOrAbsolute), + "ObjectWithIdAndName" => JsonConvert.DeserializeObject(expectedResultAsString)!, + _ => Convert.ChangeType(expectedResultAsString, Type.GetType(expectedType)!), + }; + } + + private void InitializeDocumentProviderAndPathMatcher(string openApiSpec) + { + OpenApiDocument document = new OpenApiStringReader().Read(openApiSpec, out OpenApiDiagnostic _); + + var documentProvider = new OpenApiDocumentProvider(new LoggerFactory().CreateLogger()); + documentProvider.Add(document); + + this.scenarioContext.Set(documentProvider); + + this.matcher = new PathMatcher(documentProvider); + } + + private class FakeCookieCollection : IRequestCookieCollection + { + private readonly Dictionary cookies = new(); + + public int Count => this.cookies.Count; + + public ICollection Keys => this.cookies.Keys; + + public string? this[string key] => this.TryGetValue(key, out string? cookie) ? cookie : null; + + public void Add(string key, string value) + { + this.cookies.Add(key, value); + } + + public bool ContainsKey(string key) => this.cookies.ContainsKey(key); + + public IEnumerator> GetEnumerator() => this.cookies.GetEnumerator(); + + public bool TryGetValue(string key, [MaybeNullWhen(false)] out string? value) => this.cookies.TryGetValue(key, out value); + + IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); + } + } +} \ No newline at end of file diff --git a/Solutions/Menes.Specs/Steps/OpenApiValidationSteps.cs b/Solutions/Menes.Specs/Steps/OpenApiValidationSteps.cs index d4c55f83f..f10d90c05 100644 --- a/Solutions/Menes.Specs/Steps/OpenApiValidationSteps.cs +++ b/Solutions/Menes.Specs/Steps/OpenApiValidationSteps.cs @@ -59,7 +59,7 @@ public void WhenIValidateThePayloadAgainstTheSchema() validator.ValidateAndThrow(payload, schema); this.scenarioContext.Set(true, ResultKey); } - catch (OpenApiBadRequestException ex) + catch (OpenApiInvalidFormatException ex) { this.scenarioContext.Set(false, ResultKey); this.scenarioContext.Set(ex, "Exception"); diff --git a/Solutions/Menes.Specs/Steps/OpenApiWebLinkResolverSteps.cs b/Solutions/Menes.Specs/Steps/OpenApiWebLinkResolverSteps.cs index 277fe8cee..1af701c7b 100644 --- a/Solutions/Menes.Specs/Steps/OpenApiWebLinkResolverSteps.cs +++ b/Solutions/Menes.Specs/Steps/OpenApiWebLinkResolverSteps.cs @@ -8,15 +8,16 @@ namespace Menes.Specs.Steps using System.Collections.Generic; using System.Linq; using System.Reflection; + using Menes.Internal; using Menes.Links; + using Microsoft.Extensions.Logging; - using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Readers; - using Newtonsoft.Json; - using Newtonsoft.Json.Linq; + using NUnit.Framework; + using TechTalk.SpecFlow; using TechTalk.SpecFlow.Assist; diff --git a/Solutions/Menes.Specs/Steps/TestClasses/OperationInvokerTestContext.cs b/Solutions/Menes.Specs/Steps/TestClasses/OperationInvokerTestContext.cs index 57f67ce3e..d2bf5ec1c 100644 --- a/Solutions/Menes.Specs/Steps/TestClasses/OperationInvokerTestContext.cs +++ b/Solutions/Menes.Specs/Steps/TestClasses/OperationInvokerTestContext.cs @@ -7,10 +7,14 @@ namespace Menes.Specs.Steps.TestClasses using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; + using Idg.AsyncTest; + using Menes.Auditing; using Menes.Internal; + using Microsoft.Extensions.DependencyInjection; + using Moq; /// @@ -43,7 +47,6 @@ public static void AddServices(IServiceCollection services) { var context = new OperationInvokerTestContext(); services.AddSingleton(context); - services.AddSingleton(); services.AddSingleton(context.OperationLocator.Object); services.AddSingleton(new AccessChecker(context)); services.AddSingleton(context.ExceptionMapper.Object); diff --git a/Solutions/Menes.Specs/packages.lock.json b/Solutions/Menes.Specs/packages.lock.json index 04c207a40..69cefb4a1 100644 --- a/Solutions/Menes.Specs/packages.lock.json +++ b/Solutions/Menes.Specs/packages.lock.json @@ -4,15 +4,15 @@ "net6.0": { "Corvus.Testing.SpecFlow.NUnit": { "type": "Direct", - "requested": "[1.6.0, )", - "resolved": "1.6.0", - "contentHash": "V8v7+oRFK99skWq/TmzgiiS6KYQtu/gJXTXep8HKxt3u1rbU7BZFQfPCHiyH7KSAIn+4HEnxqlvCbXUSyRGCAA==", + "requested": "[2.0.0, )", + "resolved": "2.0.0", + "contentHash": "mutrleL9V2ftOzcrfBGSe8imboy+p4hxWO+ejNE6TWvBPBdjFlteST/vsHoQwMOVT1VUj4AfXtgSqk5SN+NH4A==", "dependencies": { - "Corvus.Testing.SpecFlow": "1.6.0", - "Microsoft.NET.Test.Sdk": "16.11.0", - "Moq": "4.18.2", + "Corvus.Testing.SpecFlow": "2.0.0", + "Microsoft.NET.Test.Sdk": "17.4.0", + "Moq": "4.18.3", "SpecFlow.NUnit.Runners": "3.9.74", - "coverlet.msbuild": "3.1.2" + "coverlet.msbuild": "3.2.0" } }, "Endjin.RecommendedPractices.GitHub": { @@ -105,12 +105,12 @@ }, "Corvus.Testing.SpecFlow": { "type": "Transitive", - "resolved": "1.6.0", - "contentHash": "0Qg8suJQ6TbvQK1ND51LK6kP0x1lZfqQVpbLf0CqKcIVLrJLmsb2Az+j635D6RJNTZ6jYcWxxtFEh314bERaMQ==", + "resolved": "2.0.0", + "contentHash": "dNCAIMrssh6jG7HO8FlTRzdNIHcE/X8B6Zlsdfwi9LbI0EX2IZGiFZosp+XhFQDYZ5ttAzJjO2ZWUqhnpglWWQ==", "dependencies": { - "Microsoft.Extensions.Configuration.Abstractions": "3.1.31", - "Microsoft.Extensions.DependencyInjection": "3.1.31", - "Microsoft.Extensions.DependencyInjection.Abstractions": "3.1.31", + "Microsoft.Extensions.Configuration.Abstractions": "6.0.0", + "Microsoft.Extensions.DependencyInjection": "6.0.1", + "Microsoft.Extensions.DependencyInjection.Abstractions": "6.0.0", "NUnit": "3.13.3", "SpecFlow": "3.9.74", "System.Management": "4.7.0" @@ -118,8 +118,8 @@ }, "coverlet.msbuild": { "type": "Transitive", - "resolved": "3.1.2", - "contentHash": "QhM0fnDtmIMImY7oxyQ/kh1VYtRxPyRVeLwRUGuUvI6Xp83pSYG9gerK8WgJj4TzUl7ISziADUGtIWKhtlbkbQ==" + "resolved": "3.2.0", + "contentHash": "lu/eJJpqJb4qy3BGPtDD/LI5RSOwXYYyRErTyaG0OTP69llzVK3FEe74hBQx0JtLUTLEVBfERV4uGYcE1Br2sg==" }, "Endjin.RecommendedPractices": { "type": "Transitive", @@ -168,8 +168,8 @@ }, "Microsoft.CodeCoverage": { "type": "Transitive", - "resolved": "16.11.0", - "contentHash": "wf6lpAeCqP0KFfbDVtfL50lr7jY1gq0+0oSphyksfLOEygMDXqnaxHK5LPFtMEhYSEtgXdNyXNnEddOqQQUdlQ==" + "resolved": "17.4.0", + "contentHash": "2oZbSVTC2nAvQ2DnbXLlXS+c25ZyZdWeNd+znWwAxwGaPh9dwQ5NBsYyqQB7sKmJKIUdkKGmN3rzFzjVC81Dtg==" }, "Microsoft.CSharp": { "type": "Transitive", @@ -216,8 +216,8 @@ }, "Microsoft.Extensions.DependencyInjection": { "type": "Transitive", - "resolved": "6.0.0", - "contentHash": "k6PWQMuoBDGGHOQTtyois2u4AwyVcIwL2LaSLlTZQm2CYcJ1pxbt6jfAnpWmzENA/wfrYRI/X9DTLoUkE4AsLw==", + "resolved": "6.0.1", + "contentHash": "vWXPg3HJQIpZkENn1KWq8SfbqVujVD7S7vIAyFXXqK5xkf1Vho+vG0bLBCHxU36lD1cLLtmGpfYf0B3MYFi9tQ==", "dependencies": { "Microsoft.Extensions.DependencyInjection.Abstractions": "6.0.0", "System.Runtime.CompilerServices.Unsafe": "6.0.0" @@ -276,11 +276,11 @@ }, "Microsoft.NET.Test.Sdk": { "type": "Transitive", - "resolved": "16.11.0", - "contentHash": "f4mbG1SUSkNWF5p7B3Y8ZxMsvKhxCmpZhdl+w6tMtLSUGE7Izi1syU6TkmKOvB2BV66pdbENConFAISOix4ohQ==", + "resolved": "17.4.0", + "contentHash": "VtNZQ83ntG2aEUjy1gq6B4HNdn96se6FmdY/03At8WiqDReGrApm6OB2fNiSHz9D6IIEtWtNZ2FSH0RJDVXl/w==", "dependencies": { - "Microsoft.CodeCoverage": "16.11.0", - "Microsoft.TestPlatform.TestHost": "16.11.0" + "Microsoft.CodeCoverage": "17.4.0", + "Microsoft.TestPlatform.TestHost": "17.4.0" } }, "Microsoft.NETCore.Platforms": { @@ -295,15 +295,15 @@ }, "Microsoft.OpenApi": { "type": "Transitive", - "resolved": "1.4.4", - "contentHash": "JzSCWm7KGtZ8TCxB5V0ZHBHAe3TJ9AQ+/v28Cq9kVbMIM+erjes7f1W7j4CYD+PiHgaPK7Xss6jAYkwqVjU5NQ==" + "resolved": "1.4.5", + "contentHash": "qsKbcFvY9/yUXiLTSsXd76RePfOHTbqMYBmHlFe0j97XZy1I7g8Rj81iw2SpA2pwQO7HgwGN+Ogd/xdKRKjJTg==" }, "Microsoft.OpenApi.Readers": { "type": "Transitive", - "resolved": "1.4.4", - "contentHash": "SWVnL20hz01q/DTLpaVlZ8sHweQZJbNNff/MfQYuucDJkOoU0ucqDtURDLs1ynwg79foTx5r1x2Ab5MTWqM2vg==", + "resolved": "1.4.5", + "contentHash": "s5LG9sD1twambOgAb/QMuag93oY8H9jm0XA+8IgKolSuTbxJDRjZBRkkVUjZz1d6YEDS3HJjHVd541a5uNk8VQ==", "dependencies": { - "Microsoft.OpenApi": "1.4.4", + "Microsoft.OpenApi": "1.4.5", "SharpYaml": "2.1.0" } }, @@ -323,20 +323,20 @@ }, "Microsoft.TestPlatform.ObjectModel": { "type": "Transitive", - "resolved": "16.11.0", - "contentHash": "EiknJx9N9Z30gs7R+HHhki7fA8EiiM3pwD1vkw3bFsBC8kdVq/O7mHf1hrg5aJp+ASO6BoOzQueD2ysfTOy/Bg==", + "resolved": "17.4.0", + "contentHash": "oWe7A0wrZhxagTOcaxJ9r0NXTbgkiBQQuCpCXxnP06NsGV/qOoaY2oaangAJbOUrwEx0eka1do400NwNCjfytw==", "dependencies": { - "NuGet.Frameworks": "5.0.0", + "NuGet.Frameworks": "5.11.0", "System.Reflection.Metadata": "1.6.0" } }, "Microsoft.TestPlatform.TestHost": { "type": "Transitive", - "resolved": "16.11.0", - "contentHash": "/Q+R0EcCJE8JaYCk+bGReicw/xrB0HhecrYrUcLbn95BnAlaTJrZhoLkUhvtKTAVtqX/AIKWXYtutiU/Q6QUgg==", + "resolved": "17.4.0", + "contentHash": "sUx48fu9wgQF1JxzXeSVtzb7KoKpJrdtIzsFamxET3ZYOKXj+Ej13HWZ0U2nuMVZtZVHBmE+KS3Vv5cIdTlycQ==", "dependencies": { - "Microsoft.TestPlatform.ObjectModel": "16.11.0", - "Newtonsoft.Json": "9.0.1" + "Microsoft.TestPlatform.ObjectModel": "17.4.0", + "Newtonsoft.Json": "13.0.1" } }, "Microsoft.Win32.Primitives": { @@ -360,8 +360,8 @@ }, "Moq": { "type": "Transitive", - "resolved": "4.18.2", - "contentHash": "SjxKYS5nX6prcaT8ZjbkONh3vnh0Rxru09+gQ1a07v4TM530Oe/jq3Q4dOZPfo1wq0LYmTgLOZKrqRfEx4auPw==", + "resolved": "4.18.3", + "contentHash": "nmV2lludVOFmVi+Vtq9twX1/SDiEVyYDURzxW39gUBqjyoXmdyNwJSeOfSCJoJTXDXBVfFNfEljB5UWGj/cKnQ==", "dependencies": { "Castle.Core": "5.1.0" } @@ -381,8 +381,8 @@ }, "NuGet.Frameworks": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "c5JVjuVAm4f7E9Vj+v09Z9s2ZsqFDjBpcsyS3M9xRo0bEdm/LVZSzLxxNvfvAwRiiE8nwe1h2G4OwiwlzFKXlA==" + "resolved": "5.11.0", + "contentHash": "eaiXkUjC4NPcquGWzAGMXjuxvLwc6XGKMptSyOGQeT0X70BUZObuybJFZLA0OfTdueLd3US23NBPTBb6iF3V1Q==" }, "NUnit": { "type": "Transitive", @@ -1523,7 +1523,7 @@ "Microsoft.CSharp": "[4.7.0, )", "Microsoft.Extensions.Configuration.Abstractions": "[6.0.0, )", "Microsoft.Extensions.Logging": "[6.0.0, )", - "Microsoft.OpenApi.Readers": "[1.4.4, )", + "Microsoft.OpenApi.Readers": "[1.4.5, )", "System.Interactive": "[4.1.*, )", "System.Text.Encodings.Web": "[4.7.*, )", "Tavis.UriTemplates": "[1.1.1, )" diff --git a/azure-pipelines.yml b/azure-pipelines.yml index d154a4715..4906a11c4 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -22,8 +22,7 @@ jobs: service_connection_nuget_org: $(Endjin_Service_Connection_NuGet_Org) service_connection_github: $(Endjin_Service_Connection_GitHub) solution_to_build: $(Endjin_Solution_To_Build) - netSdkVersion: '5.x' + netSdkVersion: '7.x' additionalNetSdkVersions: - - '3.x' - '6.x' compileTasksServiceConnection: endjin-acr-reader \ No newline at end of file