diff --git a/docs/source/1.0/guides/converting-to-openapi.rst b/docs/source/1.0/guides/converting-to-openapi.rst index 5329caeaa2d..832f0afbcb5 100644 --- a/docs/source/1.0/guides/converting-to-openapi.rst +++ b/docs/source/1.0/guides/converting-to-openapi.rst @@ -512,6 +512,7 @@ useIntegerType (``boolean``) } } + ---------------------------------- JSON schema configuration settings ---------------------------------- @@ -663,6 +664,62 @@ disableFeatures (``[string]``) } +.. _generate-openapi-setting-supportNonNumericFloats: + +supportNonNumericFloats (``boolean``) + Set to true to add support for NaN, Infinity, and -Infinity in float + and double shapes. These values will be serialized as strings. The + JSON Schema document will be updated to refer to them as a "oneOf" of + number and string. + + By default, these non-numeric values are not supported. + + .. code-block:: json + + { + "version": "1.0", + "plugins": { + "openapi": { + "service": "smithy.example#Weather", + "supportNonNumericFloats": true + } + } + } + + When this is disabled (the default), references to floats/doubles will + look like this: + + .. code-block: json + + { + "floatMember": { + "type": "number" + } + } + + With this enabled, references to floats/doubles will look like this: + + .. code-block:: json + + { + "floatMember": { + "oneOf": [ + { + "type": "number" + }, + { + "type": "string", + "enum": [ + "NaN", + "Infinity", + "-Infinity" + ] + } + ] + } + } + + ---------------- Security schemes ---------------- diff --git a/docs/source/1.0/spec/aws/aws-ec2-query-protocol.rst b/docs/source/1.0/spec/aws/aws-ec2-query-protocol.rst index 9ddb695c5c8..809a40d0ab0 100644 --- a/docs/source/1.0/spec/aws/aws-ec2-query-protocol.rst +++ b/docs/source/1.0/spec/aws/aws-ec2-query-protocol.rst @@ -389,6 +389,10 @@ serialized in the response. +.. _ec2Query-non-numeric-float-serialization: +.. include:: non-numeric-floats.rst.template + + .. _ec2Query-compliance-tests: ------------------------- diff --git a/docs/source/1.0/spec/aws/aws-json.rst.template b/docs/source/1.0/spec/aws/aws-json.rst.template index 1fb6575c01f..57eb36bea69 100644 --- a/docs/source/1.0/spec/aws/aws-json.rst.template +++ b/docs/source/1.0/spec/aws/aws-json.rst.template @@ -90,9 +90,11 @@ to convert each shape type: * - ``long`` - JSON number * - ``float`` - - JSON number + - JSON number for numeric values. JSON strings for ``NaN``, ``Infinity``, + and ``-Infinity``. * - ``double`` - - JSON number + - JSON number for numeric values. JSON strings for ``NaN``, ``Infinity``, + and ``-Infinity`` * - ``bigDecimal`` - JSON number. Unfortunately, this protocol serializes bigDecimal shapes as a normal JSON number. Many JSON parsers will either @@ -145,6 +147,9 @@ still send the protocol's ``Content-Type`` header in this case. Clients MUST also accept an empty JSON object as the response body. +.. include:: non-numeric-floats.rst.template + + ----------------------------- Operation error serialization ----------------------------- diff --git a/docs/source/1.0/spec/aws/aws-query-protocol.rst b/docs/source/1.0/spec/aws/aws-query-protocol.rst index bfab6ce0ca4..ce21b428d55 100644 --- a/docs/source/1.0/spec/aws/aws-query-protocol.rst +++ b/docs/source/1.0/spec/aws/aws-query-protocol.rst @@ -441,6 +441,10 @@ is used to distinguish which specific error is serialized in the response. +.. _awsQuery-non-numeric-float-serialization: +.. include:: non-numeric-floats.rst.template + + .. _awsQuery-error-response-code: Error HTTP response code resolution diff --git a/docs/source/1.0/spec/aws/aws-restjson1-protocol.rst b/docs/source/1.0/spec/aws/aws-restjson1-protocol.rst index d90b7764a95..e78772b6e57 100644 --- a/docs/source/1.0/spec/aws/aws-restjson1-protocol.rst +++ b/docs/source/1.0/spec/aws/aws-restjson1-protocol.rst @@ -246,9 +246,11 @@ JSON shape serialization * - ``long`` - JSON number * - ``float`` - - JSON number + - JSON number for numeric values. JSON strings for ``NaN``, ``Infinity``, + and ``-Infinity`` * - ``double`` - - JSON number + - JSON number for numeric values. JSON strings for ``NaN``, ``Infinity``, + and ``-Infinity`` * - ``bigDecimal`` - JSON number. Unfortunately, this protocol serializes bigDecimal shapes as a normal JSON number. Many JSON parsers will either @@ -297,6 +299,10 @@ serialization formats and and behaviors described for each trait are supported as defined in the ``aws.protocols#restJson1`` protocol. +.. |quoted shape name| replace:: ``aws.protocols#restJson1`` +.. include:: non-numeric-floats.rst.template + + .. restJson1-errors: ----------------------------- diff --git a/docs/source/1.0/spec/aws/aws-restxml-protocol.rst b/docs/source/1.0/spec/aws/aws-restxml-protocol.rst index 7bb587e1238..29fd4a0ad08 100644 --- a/docs/source/1.0/spec/aws/aws-restxml-protocol.rst +++ b/docs/source/1.0/spec/aws/aws-restxml-protocol.rst @@ -249,6 +249,10 @@ serialization formats and and behaviors described for each trait are supported as defined in the ``aws.protocols#restXml`` protocol. +.. |quoted shape name| replace:: ``aws.protocols#restXml`` +.. include:: non-numeric-floats.rst.template + + .. _restXml-errors: ----------------------------- diff --git a/docs/source/1.0/spec/aws/non-numeric-floats.rst.template b/docs/source/1.0/spec/aws/non-numeric-floats.rst.template new file mode 100644 index 00000000000..b57d25c76d1 --- /dev/null +++ b/docs/source/1.0/spec/aws/non-numeric-floats.rst.template @@ -0,0 +1,21 @@ +------------------------------------------ +Non-numeric float and double serialization +------------------------------------------ + +Smithy floats and doubles are defined by IEEE-754, which includes special values +for "not a number" and both positive and negative infinity. Unless otherwise +specified, the |quoted shape name| protocol treats those special values as +strings with the following values: + +.. list-table:: + :header-rows: 1 + :widths: 50 50 + + * - Special Value + - String Value + * - Not a number + - ``NaN`` + * - Positive infinity + - ``Infinity`` + * - Negative infinity + - ``-Infinity`` diff --git a/docs/source/1.0/spec/core/model.rst b/docs/source/1.0/spec/core/model.rst index 5f6705c549b..9f1a0b6b1be 100644 --- a/docs/source/1.0/spec/core/model.rst +++ b/docs/source/1.0/spec/core/model.rst @@ -2334,11 +2334,13 @@ target from traits and how their values are defined in - number - The value MUST fall within the range of -2^63 to (2^63)-1. * - float - - number - - A normal JSON number. + - string | number + - The value MUST be either a normal JSON number or one of the following + string values: ``"NaN"``, ``"Infinity"``, ``"-Infinity"``. * - double - - number - - A normal JSON number. + - string | number + - The value MUST be either a normal JSON number or one of the following + string values: ``"NaN"``, ``"Infinity"``, ``"-Infinity"``. * - bigDecimal - string | number - bigDecimal values can be serialized as strings to avoid rounding diff --git a/smithy-aws-apigateway-openapi/src/test/java/software/amazon/smithy/aws/apigateway/openapi/NonNumericFloatsTest.java b/smithy-aws-apigateway-openapi/src/test/java/software/amazon/smithy/aws/apigateway/openapi/NonNumericFloatsTest.java new file mode 100644 index 00000000000..d930461b1d7 --- /dev/null +++ b/smithy-aws-apigateway-openapi/src/test/java/software/amazon/smithy/aws/apigateway/openapi/NonNumericFloatsTest.java @@ -0,0 +1,29 @@ +package software.amazon.smithy.aws.apigateway.openapi; + +import org.junit.jupiter.api.Test; +import software.amazon.smithy.model.Model; +import software.amazon.smithy.model.node.Node; +import software.amazon.smithy.model.node.ObjectNode; +import software.amazon.smithy.model.shapes.ShapeId; +import software.amazon.smithy.openapi.OpenApiConfig; +import software.amazon.smithy.openapi.fromsmithy.OpenApiConverter; +import software.amazon.smithy.utils.IoUtils; + +public class NonNumericFloatsTest { + @Test + public void handlesNonNumericFloats() { + Model model = Model.assembler(getClass().getClassLoader()) + .discoverModels(getClass().getClassLoader()) + .addImport(getClass().getResource("non-numeric-floats.json")) + .assemble() + .unwrap(); + OpenApiConfig config = new OpenApiConfig(); + config.setService(ShapeId.from("example.smithy#MyService")); + config.setSupportNonNumericFloats(true); + ObjectNode result = OpenApiConverter.create().config(config).convertToNode(model); + Node expectedNode = Node.parse(IoUtils.toUtf8String( + getClass().getResourceAsStream("non-numeric-floats.openapi.json"))); + + Node.assertEquals(result, expectedNode); + } +} diff --git a/smithy-aws-apigateway-openapi/src/test/resources/software/amazon/smithy/aws/apigateway/openapi/non-numeric-floats.json b/smithy-aws-apigateway-openapi/src/test/resources/software/amazon/smithy/aws/apigateway/openapi/non-numeric-floats.json new file mode 100644 index 00000000000..e154f204842 --- /dev/null +++ b/smithy-aws-apigateway-openapi/src/test/resources/software/amazon/smithy/aws/apigateway/openapi/non-numeric-floats.json @@ -0,0 +1,40 @@ +{ + "smithy": "1.0", + "shapes": { + "example.smithy#MyService": { + "type": "service", + "version": "2006-03-01", + "operations": [ + { + "target": "example.smithy#MyOperation" + } + ], + "traits": { + "aws.protocols#restJson1": {} + } + }, + "example.smithy#MyOperation": { + "type": "operation", + "input": { + "target": "example.smithy#MyOperationInput" + }, + "traits": { + "smithy.api#http": { + "uri": "/foo", + "method": "POST" + } + } + }, + "example.smithy#MyOperationInput": { + "type": "structure", + "members": { + "floatMember": { + "target": "smithy.api#Float" + }, + "doubleMember": { + "target": "smithy.api#Double" + } + } + } + } +} diff --git a/smithy-aws-apigateway-openapi/src/test/resources/software/amazon/smithy/aws/apigateway/openapi/non-numeric-floats.openapi.json b/smithy-aws-apigateway-openapi/src/test/resources/software/amazon/smithy/aws/apigateway/openapi/non-numeric-floats.openapi.json new file mode 100644 index 00000000000..157831cf3e5 --- /dev/null +++ b/smithy-aws-apigateway-openapi/src/test/resources/software/amazon/smithy/aws/apigateway/openapi/non-numeric-floats.openapi.json @@ -0,0 +1,71 @@ +{ + "openapi": "3.0.2", + "info": { + "title": "MyService", + "version": "2006-03-01" + }, + "paths": { + "/foo": { + "post": { + "operationId": "MyOperation", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MyOperationRequestContent" + } + } + } + }, + "responses": { + "200": { + "description": "MyOperation response" + } + } + } + } + }, + "components": { + "schemas": { + "MyOperationRequestContent": { + "type": "object", + "properties": { + "floatMember": { + "oneOf": [ + { + "type": "number", + "format": "float" + }, + { + "type": "string", + "enum": [ + "NaN", + "Infinity", + "-Infinity" + ] + } + ], + "nullable": true + }, + "doubleMember": { + "oneOf": [ + { + "type": "number", + "format": "double" + }, + { + "type": "string", + "enum": [ + "NaN", + "Infinity", + "-Infinity" + ] + } + ], + "nullable": true + } + } + } + } + } +} diff --git a/smithy-aws-protocol-tests/model/awsJson1_0/json-structs.smithy b/smithy-aws-protocol-tests/model/awsJson1_0/json-structs.smithy new file mode 100644 index 00000000000..75ee5885c18 --- /dev/null +++ b/smithy-aws-protocol-tests/model/awsJson1_0/json-structs.smithy @@ -0,0 +1,146 @@ +// This file defines test cases that serialize structures. + +$version: "1.0" + +namespace aws.protocoltests.json10 + +use aws.protocols#awsJson1_0 +use smithy.test#httpRequestTests +use smithy.test#httpResponseTests + +apply SimpleScalarProperties @httpRequestTests([ + { + id: "AwsJson10SupportsNaNFloatInputs", + documentation: "Supports handling NaN float values.", + protocol: awsJson1_0, + method: "POST", + uri: "/", + body: """ + { + "floatValue": "NaN", + "doubleValue": "NaN" + }""", + bodyMediaType: "application/json", + headers: { + "Content-Type": "application/x-amz-json-1.0", + "X-Amz-Target": "JsonRpc10.SimpleScalarProperties", + }, + params: { + floatValue: "NaN", + doubleValue: "NaN", + } + }, + { + id: "AwsJson10SupportsInfinityFloatInputs", + documentation: "Supports handling Infinity float values.", + protocol: awsJson1_0, + method: "POST", + uri: "/", + body: """ + { + "floatValue": "Infinity", + "doubleValue": "Infinity" + }""", + bodyMediaType: "application/json", + headers: { + "Content-Type": "application/x-amz-json-1.0", + "X-Amz-Target": "JsonRpc10.SimpleScalarProperties", + }, + params: { + floatValue: "Infinity", + doubleValue: "Infinity", + } + }, + { + id: "AwsJson10SupportsNegativeInfinityFloatInputs", + documentation: "Supports handling -Infinity float values.", + protocol: awsJson1_0, + method: "POST", + uri: "/", + body: """ + { + "floatValue": "-Infinity", + "doubleValue": "-Infinity" + }""", + bodyMediaType: "application/json", + headers: { + "Content-Type": "application/x-amz-json-1.0", + "X-Amz-Target": "JsonRpc10.SimpleScalarProperties", + }, + params: { + floatValue: "-Infinity", + doubleValue: "-Infinity", + } + }, +]) + +apply SimpleScalarProperties @httpResponseTests([ + { + id: "AwsJson10SupportsNaNFloatInputs", + documentation: "Supports handling NaN float values.", + protocol: awsJson1_0, + code: 200, + body: """ + { + "floatValue": "NaN", + "doubleValue": "NaN" + }""", + bodyMediaType: "application/json", + headers: { + "Content-Type": "application/x-amz-json-1.0", + }, + params: { + floatValue: "NaN", + doubleValue: "NaN", + } + }, + { + id: "AwsJson10SupportsInfinityFloatInputs", + documentation: "Supports handling Infinity float values.", + protocol: awsJson1_0, + code: 200, + body: """ + { + "floatValue": "Infinity", + "doubleValue": "Infinity" + }""", + bodyMediaType: "application/json", + headers: { + "Content-Type": "application/x-amz-json-1.0", + }, + params: { + floatValue: "Infinity", + doubleValue: "Infinity", + } + }, + { + id: "AwsJson10SupportsNegativeInfinityFloatInputs", + documentation: "Supports handling -Infinity float values.", + protocol: awsJson1_0, + code: 200, + body: """ + { + "floatValue": "-Infinity", + "doubleValue": "-Infinity" + }""", + bodyMediaType: "application/json", + headers: { + "Content-Type": "application/x-amz-json-1.0", + }, + params: { + floatValue: "-Infinity", + doubleValue: "-Infinity", + } + }, +]) + +// This example serializes simple scalar types in the top level JSON document. +operation SimpleScalarProperties { + input: SimpleScalarPropertiesInputOutput, + output: SimpleScalarPropertiesInputOutput +} + +structure SimpleScalarPropertiesInputOutput { + floatValue: Float, + doubleValue: Double, +} diff --git a/smithy-aws-protocol-tests/model/awsJson1_0/main.smithy b/smithy-aws-protocol-tests/model/awsJson1_0/main.smithy index 0edffc5dd40..e82b286d47b 100644 --- a/smithy-aws-protocol-tests/model/awsJson1_0/main.smithy +++ b/smithy-aws-protocol-tests/model/awsJson1_0/main.smithy @@ -16,6 +16,7 @@ service JsonRpc10 { NoInputAndNoOutput, NoInputAndOutput, EmptyInputAndEmptyOutput, + SimpleScalarProperties, // Errors GreetingWithErrors, diff --git a/smithy-aws-protocol-tests/model/awsJson1_1/json-structs.smithy b/smithy-aws-protocol-tests/model/awsJson1_1/json-structs.smithy new file mode 100644 index 00000000000..4497d21b98e --- /dev/null +++ b/smithy-aws-protocol-tests/model/awsJson1_1/json-structs.smithy @@ -0,0 +1,148 @@ +// This file defines test cases that serialize structures. Over time this +// will take over much of what is in kitchen-sink as it gets refactored +// to not put everything into such a small number of tests. + +$version: "1.0" + +namespace aws.protocoltests.json + +use aws.protocols#awsJson1_1 +use smithy.test#httpRequestTests +use smithy.test#httpResponseTests + +apply SimpleScalarProperties @httpRequestTests([ + { + id: "AwsJson11SupportsNaNFloatInputs", + documentation: "Supports handling NaN float values.", + protocol: awsJson1_1, + method: "POST", + uri: "/", + body: """ + { + "floatValue": "NaN", + "doubleValue": "NaN" + }""", + bodyMediaType: "application/json", + headers: { + "Content-Type": "application/x-amz-json-1.1", + "X-Amz-Target": "JsonRpc10.SimpleScalarProperties", + }, + params: { + floatValue: "NaN", + doubleValue: "NaN", + } + }, + { + id: "AwsJson11SupportsInfinityFloatInputs", + documentation: "Supports handling Infinity float values.", + protocol: awsJson1_1, + method: "POST", + uri: "/", + body: """ + { + "floatValue": "Infinity", + "doubleValue": "Infinity" + }""", + bodyMediaType: "application/json", + headers: { + "Content-Type": "application/x-amz-json-1.1", + "X-Amz-Target": "JsonRpc10.SimpleScalarProperties", + }, + params: { + floatValue: "Infinity", + doubleValue: "Infinity", + } + }, + { + id: "AwsJson11SupportsNegativeInfinityFloatInputs", + documentation: "Supports handling -Infinity float values.", + protocol: awsJson1_1, + method: "POST", + uri: "/", + body: """ + { + "floatValue": "-Infinity", + "doubleValue": "-Infinity" + }""", + bodyMediaType: "application/json", + headers: { + "Content-Type": "application/x-amz-json-1.1", + "X-Amz-Target": "JsonRpc10.SimpleScalarProperties", + }, + params: { + floatValue: "-Infinity", + doubleValue: "-Infinity", + } + }, +]) + +apply SimpleScalarProperties @httpResponseTests([ + { + id: "AwsJson11SupportsNaNFloatInputs", + documentation: "Supports handling NaN float values.", + protocol: awsJson1_1, + code: 200, + body: """ + { + "floatValue": "NaN", + "doubleValue": "NaN" + }""", + bodyMediaType: "application/json", + headers: { + "Content-Type": "application/x-amz-json-1.1", + }, + params: { + floatValue: "NaN", + doubleValue: "NaN", + } + }, + { + id: "AwsJson11SupportsInfinityFloatInputs", + documentation: "Supports handling Infinity float values.", + protocol: awsJson1_1, + code: 200, + body: """ + { + "floatValue": "Infinity", + "doubleValue": "Infinity" + }""", + bodyMediaType: "application/json", + headers: { + "Content-Type": "application/x-amz-json-1.1", + }, + params: { + floatValue: "Infinity", + doubleValue: "Infinity", + } + }, + { + id: "AwsJson11SupportsNegativeInfinityFloatInputs", + documentation: "Supports handling -Infinity float values.", + protocol: awsJson1_1, + code: 200, + body: """ + { + "floatValue": "-Infinity", + "doubleValue": "-Infinity" + }""", + bodyMediaType: "application/json", + headers: { + "Content-Type": "application/x-amz-json-1.1", + }, + params: { + floatValue: "-Infinity", + doubleValue: "-Infinity", + } + }, +]) + +// This example serializes simple scalar types in the top level JSON document. +operation SimpleScalarProperties { + input: SimpleScalarPropertiesInputOutput, + output: SimpleScalarPropertiesInputOutput +} + +structure SimpleScalarPropertiesInputOutput { + floatValue: Float, + doubleValue: Double, +} diff --git a/smithy-aws-protocol-tests/model/awsJson1_1/main.smithy b/smithy-aws-protocol-tests/model/awsJson1_1/main.smithy index 9273027b70d..1a7e9356b40 100644 --- a/smithy-aws-protocol-tests/model/awsJson1_1/main.smithy +++ b/smithy-aws-protocol-tests/model/awsJson1_1/main.smithy @@ -22,6 +22,7 @@ service JsonProtocol { operations: [ EmptyOperation, KitchenSinkOperation, + SimpleScalarProperties, OperationWithOptionalInputOutput, PutAndGetInlineDocuments, JsonEnums, diff --git a/smithy-aws-protocol-tests/model/awsQuery/input.smithy b/smithy-aws-protocol-tests/model/awsQuery/input.smithy index 8984770c2d6..f460c3ab030 100644 --- a/smithy-aws-protocol-tests/model/awsQuery/input.smithy +++ b/smithy-aws-protocol-tests/model/awsQuery/input.smithy @@ -142,7 +142,64 @@ apply SimpleInputParams @httpRequestTests([ params: { FooEnum: "Foo", } - } + }, + { + id: "AwsQuerySupportsNaNFloatInputs", + documentation: "Supports handling NaN float values.", + protocol: awsQuery, + method: "POST", + uri: "/", + body: "Action=SimpleInputParams&Version=2020-01-08&FloatValue=NaN&Boo=NaN", + bodyMediaType: "application/x-www-form-urlencoded", + headers: { + "Content-Type": "application/x-www-form-urlencoded" + }, + requireHeaders: [ + "Content-Length" + ], + params: { + FloatValue: "NaN", + Boo: "NaN", + } + }, + { + id: "AwsQuerySupportsInfinityFloatInputs", + documentation: "Supports handling Infinity float values.", + protocol: awsQuery, + method: "POST", + uri: "/", + body: "Action=SimpleInputParams&Version=2020-01-08&FloatValue=Infinity&Boo=Infinity", + bodyMediaType: "application/x-www-form-urlencoded", + headers: { + "Content-Type": "application/x-www-form-urlencoded" + }, + requireHeaders: [ + "Content-Length" + ], + params: { + FloatValue: "Infinity", + Boo: "Infinity", + } + }, + { + id: "AwsQuerySupportsNegativeInfinityFloatInputs", + documentation: "Supports handling -Infinity float values.", + protocol: awsQuery, + method: "POST", + uri: "/", + body: "Action=SimpleInputParams&Version=2020-01-08&FloatValue=-Infinity&Boo=-Infinity", + bodyMediaType: "application/x-www-form-urlencoded", + headers: { + "Content-Type": "application/x-www-form-urlencoded" + }, + requireHeaders: [ + "Content-Length" + ], + params: { + FloatValue: "-Infinity", + Boo: "-Infinity", + } + }, ]) structure SimpleInputParamsInput { @@ -150,6 +207,7 @@ structure SimpleInputParamsInput { Bar: String, Baz: Boolean, Bam: Integer, + FloatValue: Float, Boo: Double, Qux: Blob, FooEnum: FooEnum, diff --git a/smithy-aws-protocol-tests/model/awsQuery/xml-structs.smithy b/smithy-aws-protocol-tests/model/awsQuery/xml-structs.smithy index fdc77b142b9..1872283d73d 100644 --- a/smithy-aws-protocol-tests/model/awsQuery/xml-structs.smithy +++ b/smithy-aws-protocol-tests/model/awsQuery/xml-structs.smithy @@ -55,7 +55,73 @@ apply SimpleScalarXmlProperties @httpResponseTests([ floatValue: 5.5, doubleValue: 6.5, } - } + }, + { + id: "AwsQuerySupportsNaNFloatOutputs", + documentation: "Supports handling NaN float values.", + protocol: awsQuery, + code: 200, + body: """ + + + NaN + NaN + + + """, + bodyMediaType: "application/xml", + headers: { + "Content-Type": "text/xml" + }, + params: { + floatValue: "NaN", + doubleValue: "NaN", + } + }, + { + id: "AwsQuerySupportsInfinityFloatOutputs", + documentation: "Supports handling Infinity float values.", + protocol: awsQuery, + code: 200, + body: """ + + + Infinity + Infinity + + + """, + bodyMediaType: "application/xml", + headers: { + "Content-Type": "text/xml" + }, + params: { + floatValue: "Infinity", + doubleValue: "Infinity", + } + }, + { + id: "AwsQuerySupportsNegativeInfinityFloatOutputs", + documentation: "Supports handling -Infinity float values.", + protocol: awsQuery, + code: 200, + body: """ + + + -Infinity + -Infinity + + + """, + bodyMediaType: "application/xml", + headers: { + "Content-Type": "text/xml" + }, + params: { + floatValue: "-Infinity", + doubleValue: "-Infinity", + } + }, ]) structure SimpleScalarXmlPropertiesOutput { diff --git a/smithy-aws-protocol-tests/model/ec2Query/input.smithy b/smithy-aws-protocol-tests/model/ec2Query/input.smithy index c1563a19b3f..0bf39ef8765 100644 --- a/smithy-aws-protocol-tests/model/ec2Query/input.smithy +++ b/smithy-aws-protocol-tests/model/ec2Query/input.smithy @@ -198,6 +198,63 @@ apply SimpleInputParams @httpRequestTests([ UsesXmlName: "Hi", } }, + { + id: "Ec2QuerySupportsNaNFloatInputs", + documentation: "Supports handling NaN float values.", + protocol: ec2Query, + method: "POST", + uri: "/", + body: "Action=SimpleInputParams&Version=2020-01-08&FloatValue=NaN&Boo=NaN", + bodyMediaType: "application/x-www-form-urlencoded", + headers: { + "Content-Type": "application/x-www-form-urlencoded" + }, + requireHeaders: [ + "Content-Length" + ], + params: { + FloatValue: "NaN", + Boo: "NaN", + } + }, + { + id: "Ec2QuerySupportsInfinityFloatInputs", + documentation: "Supports handling Infinity float values.", + protocol: ec2Query, + method: "POST", + uri: "/", + body: "Action=SimpleInputParams&Version=2020-01-08&FloatValue=Infinity&Boo=Infinity", + bodyMediaType: "application/x-www-form-urlencoded", + headers: { + "Content-Type": "application/x-www-form-urlencoded" + }, + requireHeaders: [ + "Content-Length" + ], + params: { + FloatValue: "Infinity", + Boo: "Infinity", + } + }, + { + id: "Ec2QuerySupportsNegativeInfinityFloatInputs", + documentation: "Supports handling -Infinity float values.", + protocol: ec2Query, + method: "POST", + uri: "/", + body: "Action=SimpleInputParams&Version=2020-01-08&FloatValue=-Infinity&Boo=-Infinity", + bodyMediaType: "application/x-www-form-urlencoded", + headers: { + "Content-Type": "application/x-www-form-urlencoded" + }, + requireHeaders: [ + "Content-Length" + ], + params: { + FloatValue: "-Infinity", + Boo: "-Infinity", + } + }, ]) structure SimpleInputParamsInput { @@ -205,6 +262,7 @@ structure SimpleInputParamsInput { Bar: String, Baz: Boolean, Bam: Integer, + FloatValue: Float, Boo: Double, Qux: Blob, FooEnum: FooEnum, diff --git a/smithy-aws-protocol-tests/model/ec2Query/xml-structs.smithy b/smithy-aws-protocol-tests/model/ec2Query/xml-structs.smithy index 7b75bfcc977..e4e99e94ce9 100644 --- a/smithy-aws-protocol-tests/model/ec2Query/xml-structs.smithy +++ b/smithy-aws-protocol-tests/model/ec2Query/xml-structs.smithy @@ -55,7 +55,73 @@ apply SimpleScalarXmlProperties @httpResponseTests([ floatValue: 5.5, doubleValue: 6.5, } - } + }, + { + id: "Ec2QuerySupportsNaNFloatOutputs", + documentation: "Supports handling NaN float values.", + protocol: ec2Query, + code: 200, + body: """ + + + NaN + NaN + + + """, + bodyMediaType: "application/xml", + headers: { + "Content-Type": "text/xml;charset=UTF-8" + }, + params: { + floatValue: "NaN", + doubleValue: "NaN", + } + }, + { + id: "Ec2QuerySupportsInfinityFloatOutputs", + documentation: "Supports handling Infinity float values.", + protocol: ec2Query, + code: 200, + body: """ + + + Infinity + Infinity + + + """, + bodyMediaType: "application/xml", + headers: { + "Content-Type": "text/xml;charset=UTF-8" + }, + params: { + floatValue: "Infinity", + doubleValue: "Infinity", + } + }, + { + id: "Ec2QuerySupportsNegativeInfinityFloatOutputs", + documentation: "Supports handling -Infinity float values.", + protocol: ec2Query, + code: 200, + body: """ + + + -Infinity + -Infinity + + + """, + bodyMediaType: "application/xml", + headers: { + "Content-Type": "text/xml;charset=UTF-8" + }, + params: { + floatValue: "-Infinity", + doubleValue: "-Infinity", + } + }, ]) structure SimpleScalarXmlPropertiesOutput { diff --git a/smithy-aws-protocol-tests/model/restJson1/http-headers.smithy b/smithy-aws-protocol-tests/model/restJson1/http-headers.smithy index 1c5fa152a60..c9606fbc449 100644 --- a/smithy-aws-protocol-tests/model/restJson1/http-headers.smithy +++ b/smithy-aws-protocol-tests/model/restJson1/http-headers.smithy @@ -120,6 +120,54 @@ apply InputAndOutputWithHeaders @httpRequestTests([ headerEnumList: ["Foo", "Bar", "Baz"], } }, + { + id: "RestJsonSupportsNaNFloatHeaderInputs", + documentation: "Supports handling NaN float header values.", + protocol: restJson1, + method: "POST", + uri: "/InputAndOutputWithHeaders", + body: "", + headers: { + "X-Float": "NaN", + "X-Double": "NaN", + }, + params: { + headerFloat: "NaN", + headerDouble: "NaN", + } + }, + { + id: "RestJsonSupportsInfinityFloatHeaderInputs", + documentation: "Supports handling Infinity float header values.", + protocol: restJson1, + method: "POST", + uri: "/InputAndOutputWithHeaders", + body: "", + headers: { + "X-Float": "Infinity", + "X-Double": "Infinity", + }, + params: { + headerFloat: "Infinity", + headerDouble: "Infinity", + } + }, + { + id: "RestJsonSupportsNegativeInfinityFloatHeaderInputs", + documentation: "Supports handling -Infinity float header values.", + protocol: restJson1, + method: "POST", + uri: "/InputAndOutputWithHeaders", + body: "", + headers: { + "X-Float": "-Infinity", + "X-Double": "-Infinity", + }, + params: { + headerFloat: "-Infinity", + headerDouble: "-Infinity", + } + }, ]) apply InputAndOutputWithHeaders @httpResponseTests([ @@ -205,6 +253,48 @@ apply InputAndOutputWithHeaders @httpResponseTests([ headerEnumList: ["Foo", "Bar", "Baz"], } }, + { + id: "RestJsonSupportsNaNFloatHeaderOutputs", + documentation: "Supports handling NaN float header values.", + protocol: restJson1, + code: 200, + headers: { + "X-Float": "NaN", + "X-Double": "NaN", + }, + params: { + headerFloat: "NaN", + headerDouble: "NaN", + } + }, + { + id: "RestJsonSupportsInfinityFloatHeaderOutputs", + documentation: "Supports handling Infinity float header values.", + protocol: restJson1, + code: 200, + headers: { + "X-Float": "Infinity", + "X-Double": "Infinity", + }, + params: { + headerFloat: "Infinity", + headerDouble: "Infinity", + } + }, + { + id: "RestJsonSupportsNegativeInfinityFloatHeaderOutputs", + documentation: "Supports handling -Infinity float header values.", + protocol: restJson1, + code: 200, + headers: { + "X-Float": "-Infinity", + "X-Double": "-Infinity", + }, + params: { + headerFloat: "-Infinity", + headerDouble: "-Infinity", + } + }, ]) structure InputAndOutputWithHeadersIO { diff --git a/smithy-aws-protocol-tests/model/restJson1/http-labels.smithy b/smithy-aws-protocol-tests/model/restJson1/http-labels.smithy index c61b5b52001..6e6c3b93c71 100644 --- a/smithy-aws-protocol-tests/model/restJson1/http-labels.smithy +++ b/smithy-aws-protocol-tests/model/restJson1/http-labels.smithy @@ -195,3 +195,58 @@ structure HttpRequestWithGreedyLabelInPathInput { @required baz: String, } + +apply HttpRequestWithFloatLabels @httpRequestTests([ + { + id: "RestJsonSupportsNaNFloatLabels", + documentation: "Supports handling NaN float label values.", + protocol: restJson1, + method: "GET", + uri: "/FloatHttpLabels/NaN/NaN", + body: "", + params: { + float: "NaN", + double: "NaN", + } + }, + { + id: "RestJsonSupportsInfinityFloatLabels", + documentation: "Supports handling Infinity float label values.", + protocol: restJson1, + method: "GET", + uri: "/FloatHttpLabels/Infinity/Infinity", + body: "", + params: { + float: "Infinity", + double: "Infinity", + } + }, + { + id: "RestJsonSupportsNegativeInfinityFloatLabels", + documentation: "Supports handling -Infinity float label values.", + protocol: restJson1, + method: "GET", + uri: "/FloatHttpLabels/-Infinity/-Infinity", + body: "", + params: { + float: "-Infinity", + double: "-Infinity", + } + }, +]) + +@readonly +@http(method: "GET", uri: "/FloatHttpLabels/{float}/{double}") +operation HttpRequestWithFloatLabels { + input: HttpRequestWithFloatLabelsInput +} + +structure HttpRequestWithFloatLabelsInput { + @httpLabel + @required + float: Float, + + @httpLabel + @required + double: Double, +} diff --git a/smithy-aws-protocol-tests/model/restJson1/http-query.smithy b/smithy-aws-protocol-tests/model/restJson1/http-query.smithy index 98f84362121..17d5515c068 100644 --- a/smithy-aws-protocol-tests/model/restJson1/http-query.smithy +++ b/smithy-aws-protocol-tests/model/restJson1/http-query.smithy @@ -125,7 +125,55 @@ apply AllQueryStringTypes @httpRequestTests([ params: { queryString: "%:/?#[]@!$&'()*+,;=😹" } - } + }, + { + id: "RestJsonSupportsNaNFloatQueryValues", + documentation: "Supports handling NaN float query values.", + protocol: restJson1, + method: "GET", + uri: "/AllQueryStringTypesInput", + body: "", + queryParams: [ + "Float=NaN", + "Double=NaN", + ], + params: { + queryFloat: "NaN", + queryDouble: "NaN", + } + }, + { + id: "RestJsonSupportsInfinityFloatQueryValues", + documentation: "Supports handling Infinity float query values.", + protocol: restJson1, + method: "GET", + uri: "/AllQueryStringTypesInput", + body: "", + queryParams: [ + "Float=Infinity", + "Double=Infinity", + ], + params: { + queryFloat: "Infinity", + queryDouble: "Infinity", + } + }, + { + id: "RestJsonSupportsNegativeInfinityFloatQueryValues", + documentation: "Supports handling -Infinity float query values.", + protocol: restJson1, + method: "GET", + uri: "/AllQueryStringTypesInput", + body: "", + queryParams: [ + "Float=-Infinity", + "Double=-Infinity", + ], + params: { + queryFloat: "-Infinity", + queryDouble: "-Infinity", + } + }, ]) structure AllQueryStringTypesInput { diff --git a/smithy-aws-protocol-tests/model/restJson1/json-structs.smithy b/smithy-aws-protocol-tests/model/restJson1/json-structs.smithy index 518b125ef78..a4e5a37a02f 100644 --- a/smithy-aws-protocol-tests/model/restJson1/json-structs.smithy +++ b/smithy-aws-protocol-tests/model/restJson1/json-structs.smithy @@ -85,12 +85,73 @@ apply SimpleScalarProperties @httpRequestTests([ { "stringValue": null }""", + bodyMediaType: "application/json", headers: { "Content-Type": "application/json", }, params: {}, appliesTo: "server", }, + { + id: "RestJsonSupportsNaNFloatInputs", + documentation: "Supports handling NaN float values.", + protocol: restJson1, + method: "PUT", + uri: "/SimpleScalarProperties", + body: """ + { + "floatValue": "NaN", + "doubleValue": "NaN" + }""", + bodyMediaType: "application/json", + headers: { + "Content-Type": "application/json", + }, + params: { + floatValue: "NaN", + doubleValue: "NaN", + } + }, + { + id: "RestJsonSupportsInfinityFloatInputs", + documentation: "Supports handling Infinity float values.", + protocol: restJson1, + method: "PUT", + uri: "/SimpleScalarProperties", + body: """ + { + "floatValue": "Infinity", + "doubleValue": "Infinity" + }""", + bodyMediaType: "application/json", + headers: { + "Content-Type": "application/json", + }, + params: { + floatValue: "Infinity", + doubleValue: "Infinity", + } + }, + { + id: "RestJsonSupportsNegativeInfinityFloatInputs", + documentation: "Supports handling -Infinity float values.", + protocol: restJson1, + method: "PUT", + uri: "/SimpleScalarProperties", + body: """ + { + "floatValue": "-Infinity", + "doubleValue": "-Infinity" + }""", + bodyMediaType: "application/json", + headers: { + "Content-Type": "application/json", + }, + params: { + floatValue: "-Infinity", + doubleValue: "-Infinity", + } + }, ]) apply SimpleScalarProperties @httpResponseTests([ @@ -160,6 +221,63 @@ apply SimpleScalarProperties @httpResponseTests([ }, appliesTo: "server", }, + { + id: "RestJsonSupportsNaNFloatInputs", + documentation: "Supports handling NaN float values.", + protocol: restJson1, + code: 200, + body: """ + { + "floatValue": "NaN", + "doubleValue": "NaN" + }""", + bodyMediaType: "application/json", + headers: { + "Content-Type": "application/json", + }, + params: { + floatValue: "NaN", + doubleValue: "NaN", + } + }, + { + id: "RestJsonSupportsInfinityFloatInputs", + documentation: "Supports handling Infinity float values.", + protocol: restJson1, + code: 200, + body: """ + { + "floatValue": "Infinity", + "doubleValue": "Infinity" + }""", + bodyMediaType: "application/json", + headers: { + "Content-Type": "application/json", + }, + params: { + floatValue: "Infinity", + doubleValue: "Infinity", + } + }, + { + id: "RestJsonSupportsNegativeInfinityFloatInputs", + documentation: "Supports handling -Infinity float values.", + protocol: restJson1, + code: 200, + body: """ + { + "floatValue": "-Infinity", + "doubleValue": "-Infinity" + }""", + bodyMediaType: "application/json", + headers: { + "Content-Type": "application/json", + }, + params: { + floatValue: "-Infinity", + doubleValue: "-Infinity", + } + }, ]) structure SimpleScalarPropertiesInputOutput { diff --git a/smithy-aws-protocol-tests/model/restJson1/main.smithy b/smithy-aws-protocol-tests/model/restJson1/main.smithy index 939f5317007..ea5b6971439 100644 --- a/smithy-aws-protocol-tests/model/restJson1/main.smithy +++ b/smithy-aws-protocol-tests/model/restJson1/main.smithy @@ -33,6 +33,7 @@ service RestJson { HttpRequestWithLabels, HttpRequestWithLabelsAndTimestampFormat, HttpRequestWithGreedyLabelInPath, + HttpRequestWithFloatLabels, // @httpQuery and @httpQueryParams tests AllQueryStringTypes, diff --git a/smithy-aws-protocol-tests/model/restXml/document-structs.smithy b/smithy-aws-protocol-tests/model/restXml/document-structs.smithy index c273ec4bcfd..4026e86f6de 100644 --- a/smithy-aws-protocol-tests/model/restXml/document-structs.smithy +++ b/smithy-aws-protocol-tests/model/restXml/document-structs.smithy @@ -102,6 +102,69 @@ apply SimpleScalarProperties @httpRequestTests([ stringValue: "string with white space", } }, + { + id: "RestXmlSupportsNaNFloatInputs", + documentation: "Supports handling NaN float values.", + protocol: restXml, + method: "PUT", + uri: "/SimpleScalarProperties", + body: """ + + NaN + NaN + + """, + bodyMediaType: "application/xml", + headers: { + "Content-Type": "application/xml" + }, + params: { + floatValue: "NaN", + doubleValue: "NaN", + } + }, + { + id: "RestXmlSupportsInfinityFloatInputs", + documentation: "Supports handling Infinity float values.", + protocol: restXml, + method: "PUT", + uri: "/SimpleScalarProperties", + body: """ + + Infinity + Infinity + + """, + bodyMediaType: "application/xml", + headers: { + "Content-Type": "application/xml" + }, + params: { + floatValue: "Infinity", + doubleValue: "Infinity", + } + }, + { + id: "RestXmlSupportsNegativeInfinityFloatInputs", + documentation: "Supports handling -Infinity float values.", + protocol: restXml, + method: "PUT", + uri: "/SimpleScalarProperties", + body: """ + + -Infinity + -Infinity + + """, + bodyMediaType: "application/xml", + headers: { + "Content-Type": "application/xml" + }, + params: { + floatValue: "-Infinity", + doubleValue: "-Infinity", + } + }, ]) apply SimpleScalarProperties @httpResponseTests([ @@ -232,7 +295,67 @@ apply SimpleScalarProperties @httpResponseTests([ foo: "Foo", stringValue: "string with white space", } - } + }, + { + id: "RestXmlSupportsNaNFloatOutputs", + documentation: "Supports handling NaN float values.", + protocol: restXml, + code: 200, + body: """ + + NaN + NaN + + """, + bodyMediaType: "application/xml", + headers: { + "Content-Type": "application/xml" + }, + params: { + floatValue: "NaN", + doubleValue: "NaN", + } + }, + { + id: "RestXmlSupportsInfinityFloatOutputs", + documentation: "Supports handling Infinity float values.", + protocol: restXml, + code: 200, + body: """ + + Infinity + Infinity + + """, + bodyMediaType: "application/xml", + headers: { + "Content-Type": "application/xml" + }, + params: { + floatValue: "Infinity", + doubleValue: "Infinity", + } + }, + { + id: "RestXmlSupportsNegativeInfinityFloatOutputs", + documentation: "Supports handling -Infinity float values.", + protocol: restXml, + code: 200, + body: """ + + -Infinity + -Infinity + + """, + bodyMediaType: "application/xml", + headers: { + "Content-Type": "application/xml" + }, + params: { + floatValue: "-Infinity", + doubleValue: "-Infinity", + } + }, ]) structure SimpleScalarPropertiesInputOutput { diff --git a/smithy-aws-protocol-tests/model/restXml/http-headers.smithy b/smithy-aws-protocol-tests/model/restXml/http-headers.smithy index 02c649aa6d0..74fa27dadce 100644 --- a/smithy-aws-protocol-tests/model/restXml/http-headers.smithy +++ b/smithy-aws-protocol-tests/model/restXml/http-headers.smithy @@ -120,6 +120,54 @@ apply InputAndOutputWithHeaders @httpRequestTests([ headerEnumList: ["Foo", "Bar", "Baz"], } }, + { + id: "RestXmlSupportsNaNFloatHeaderInputs", + documentation: "Supports handling NaN float header values.", + protocol: restXml, + method: "POST", + uri: "/InputAndOutputWithHeaders", + body: "", + headers: { + "X-Float": "NaN", + "X-Double": "NaN", + }, + params: { + headerFloat: "NaN", + headerDouble: "NaN", + } + }, + { + id: "RestXmlSupportsInfinityFloatHeaderInputs", + documentation: "Supports handling Infinity float header values.", + protocol: restXml, + method: "POST", + uri: "/InputAndOutputWithHeaders", + body: "", + headers: { + "X-Float": "Infinity", + "X-Double": "Infinity", + }, + params: { + headerFloat: "Infinity", + headerDouble: "Infinity", + } + }, + { + id: "RestXmlSupportsNegativeInfinityFloatHeaderInputs", + documentation: "Supports handling -Infinity float header values.", + protocol: restXml, + method: "POST", + uri: "/InputAndOutputWithHeaders", + body: "", + headers: { + "X-Float": "-Infinity", + "X-Double": "-Infinity", + }, + params: { + headerFloat: "-Infinity", + headerDouble: "-Infinity", + } + }, ]) apply InputAndOutputWithHeaders @httpResponseTests([ @@ -210,6 +258,51 @@ apply InputAndOutputWithHeaders @httpResponseTests([ headerEnumList: ["Foo", "Bar", "Baz"], } }, + { + id: "RestXmlSupportsNaNFloatHeaderOutputs", + documentation: "Supports handling NaN float header values.", + protocol: restXml, + code: 200, + headers: { + "X-Float": "NaN", + "X-Double": "NaN", + }, + body: "", + params: { + headerFloat: "NaN", + headerDouble: "NaN", + } + }, + { + id: "RestXmlSupportsInfinityFloatHeaderOutputs", + documentation: "Supports handling Infinity float header values.", + protocol: restXml, + code: 200, + headers: { + "X-Float": "Infinity", + "X-Double": "Infinity", + }, + body: "", + params: { + headerFloat: "Infinity", + headerDouble: "Infinity", + } + }, + { + id: "RestXmlSupportsNegativeInfinityFloatHeaderOutputs", + documentation: "Supports handling -Infinity float header values.", + protocol: restXml, + code: 200, + headers: { + "X-Float": "-Infinity", + "X-Double": "-Infinity", + }, + body: "", + params: { + headerFloat: "-Infinity", + headerDouble: "-Infinity", + } + }, ]) structure InputAndOutputWithHeadersIO { diff --git a/smithy-aws-protocol-tests/model/restXml/http-labels.smithy b/smithy-aws-protocol-tests/model/restXml/http-labels.smithy index cdd23c9fcca..fa816a31a60 100644 --- a/smithy-aws-protocol-tests/model/restXml/http-labels.smithy +++ b/smithy-aws-protocol-tests/model/restXml/http-labels.smithy @@ -177,3 +177,58 @@ structure HttpRequestWithGreedyLabelInPathInput { @required baz: String, } + +apply HttpRequestWithFloatLabels @httpRequestTests([ + { + id: "RestXmlSupportsNaNFloatLabels", + documentation: "Supports handling NaN float label values.", + protocol: restXml, + method: "GET", + uri: "/FloatHttpLabels/NaN/NaN", + body: "", + params: { + float: "NaN", + double: "NaN", + } + }, + { + id: "RestXmlSupportsInfinityFloatLabels", + documentation: "Supports handling Infinity float label values.", + protocol: restXml, + method: "GET", + uri: "/FloatHttpLabels/Infinity/Infinity", + body: "", + params: { + float: "Infinity", + double: "Infinity", + } + }, + { + id: "RestXmlSupportsNegativeInfinityFloatLabels", + documentation: "Supports handling -Infinity float label values.", + protocol: restXml, + method: "GET", + uri: "/FloatHttpLabels/-Infinity/-Infinity", + body: "", + params: { + float: "-Infinity", + double: "-Infinity", + } + }, +]) + +@readonly +@http(method: "GET", uri: "/FloatHttpLabels/{float}/{double}") +operation HttpRequestWithFloatLabels { + input: HttpRequestWithFloatLabelsInput +} + +structure HttpRequestWithFloatLabelsInput { + @httpLabel + @required + float: Float, + + @httpLabel + @required + double: Double, +} diff --git a/smithy-aws-protocol-tests/model/restXml/http-query.smithy b/smithy-aws-protocol-tests/model/restXml/http-query.smithy index 99d20bca01d..d551a3a0a2a 100644 --- a/smithy-aws-protocol-tests/model/restXml/http-query.smithy +++ b/smithy-aws-protocol-tests/model/restXml/http-query.smithy @@ -110,7 +110,55 @@ apply AllQueryStringTypes @httpRequestTests([ "QueryParamsStringKeyB": "Bar", }, } - } + }, + { + id: "RestXmlSupportsNaNFloatQueryValues", + documentation: "Supports handling NaN float query values.", + protocol: restXml, + method: "GET", + uri: "/AllQueryStringTypesInput", + body: "", + queryParams: [ + "Float=NaN", + "Double=NaN", + ], + params: { + queryFloat: "NaN", + queryDouble: "NaN", + } + }, + { + id: "RestXmlSupportsInfinityFloatQueryValues", + documentation: "Supports handling Infinity float query values.", + protocol: restXml, + method: "GET", + uri: "/AllQueryStringTypesInput", + body: "", + queryParams: [ + "Float=Infinity", + "Double=Infinity", + ], + params: { + queryFloat: "Infinity", + queryDouble: "Infinity", + } + }, + { + id: "RestXmlSupportsNegativeInfinityFloatQueryValues", + documentation: "Supports handling -Infinity float query values.", + protocol: restXml, + method: "GET", + uri: "/AllQueryStringTypesInput", + body: "", + queryParams: [ + "Float=-Infinity", + "Double=-Infinity", + ], + params: { + queryFloat: "-Infinity", + queryDouble: "-Infinity", + } + }, ]) structure AllQueryStringTypesInput { diff --git a/smithy-aws-protocol-tests/model/restXml/main.smithy b/smithy-aws-protocol-tests/model/restXml/main.smithy index 8867ff1334e..9c2461df322 100644 --- a/smithy-aws-protocol-tests/model/restXml/main.smithy +++ b/smithy-aws-protocol-tests/model/restXml/main.smithy @@ -31,6 +31,7 @@ service RestXml { HttpRequestWithLabels, HttpRequestWithLabelsAndTimestampFormat, HttpRequestWithGreedyLabelInPath, + HttpRequestWithFloatLabels, // @httpQuery and @httpQueryParams tests AllQueryStringTypes, diff --git a/smithy-jsonschema/src/main/java/software/amazon/smithy/jsonschema/JsonSchemaConfig.java b/smithy-jsonschema/src/main/java/software/amazon/smithy/jsonschema/JsonSchemaConfig.java index 7200bc1ed1a..b1e6fb2612c 100644 --- a/smithy-jsonschema/src/main/java/software/amazon/smithy/jsonschema/JsonSchemaConfig.java +++ b/smithy-jsonschema/src/main/java/software/amazon/smithy/jsonschema/JsonSchemaConfig.java @@ -108,6 +108,7 @@ public String toString() { private final ConcurrentHashMap extensionCache = new ConcurrentHashMap<>(); private final NodeMapper nodeMapper = new NodeMapper(); private ShapeId service; + private boolean supportNonNumericFloats = false; public JsonSchemaConfig() { nodeMapper.setWhenMissingSetter(NodeMapper.WhenMissing.INGORE); @@ -347,4 +348,22 @@ public Optional detectJsonTimestampFormat(Shape shape) { } return Optional.empty(); } + + public boolean getSupportNonNumericFloats() { + return supportNonNumericFloats; + } + + /** + * Set to true to add support for NaN, Infinity, and -Infinity in float + * and double shapes. These values will be serialized as strings. The + * OpenAPI document will be updated to refer to them as a "oneOf" of number + * and string. + * + *

By default, non-numeric values are not supported. + * + * @param supportNonNumericFloats True if non-numeric float values should be supported. + */ + public void setSupportNonNumericFloats(boolean supportNonNumericFloats) { + this.supportNonNumericFloats = supportNonNumericFloats; + } } diff --git a/smithy-jsonschema/src/main/java/software/amazon/smithy/jsonschema/JsonSchemaShapeVisitor.java b/smithy-jsonschema/src/main/java/software/amazon/smithy/jsonschema/JsonSchemaShapeVisitor.java index 6c46addcc61..00bf8e5940d 100644 --- a/smithy-jsonschema/src/main/java/software/amazon/smithy/jsonschema/JsonSchemaShapeVisitor.java +++ b/smithy-jsonschema/src/main/java/software/amazon/smithy/jsonschema/JsonSchemaShapeVisitor.java @@ -18,8 +18,10 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; +import java.util.Set; import java.util.regex.Pattern; import software.amazon.smithy.model.Model; +import software.amazon.smithy.model.node.Node.NonNumericFloat; import software.amazon.smithy.model.shapes.BigDecimalShape; import software.amazon.smithy.model.shapes.BigIntegerShape; import software.amazon.smithy.model.shapes.BlobShape; @@ -53,6 +55,7 @@ import software.amazon.smithy.utils.ListUtils; final class JsonSchemaShapeVisitor extends ShapeVisitor.Default { + private static final Set NON_NUMERIC_FLOAT_VALUES = NonNumericFloat.stringRepresentations(); private final Model model; private final JsonSchemaConverter converter; @@ -150,12 +153,30 @@ public Schema longShape(LongShape shape) { @Override public Schema floatShape(FloatShape shape) { - return buildSchema(shape, createBuilder(shape, "number")); + return buildFloatSchema(shape); } @Override public Schema doubleShape(DoubleShape shape) { - return buildSchema(shape, createBuilder(shape, "number")); + return buildFloatSchema(shape); + } + + private Schema buildFloatSchema(Shape shape) { + Schema.Builder numberBuilder = createBuilder(shape, "number"); + if (!converter.getConfig().getSupportNonNumericFloats()) { + return buildSchema(shape, numberBuilder); + } + + Schema nonNumericValues = Schema.builder() + .type("string") + .enumValues(NON_NUMERIC_FLOAT_VALUES) + .build(); + + Schema.Builder nonNumericNumberBuilder = createBuilder(shape, "number") + .type(null) + .oneOf(ListUtils.of(numberBuilder.build(), nonNumericValues)); + + return buildSchema(shape, nonNumericNumberBuilder); } @Override diff --git a/smithy-jsonschema/src/test/java/software/amazon/smithy/jsonschema/SupportNonNumericFloatsTest.java b/smithy-jsonschema/src/test/java/software/amazon/smithy/jsonschema/SupportNonNumericFloatsTest.java new file mode 100644 index 00000000000..61b36a158e9 --- /dev/null +++ b/smithy-jsonschema/src/test/java/software/amazon/smithy/jsonschema/SupportNonNumericFloatsTest.java @@ -0,0 +1,34 @@ +package software.amazon.smithy.jsonschema; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.not; + +import org.junit.jupiter.api.Test; +import software.amazon.smithy.model.Model; +import software.amazon.smithy.model.node.Node; +import software.amazon.smithy.utils.IoUtils; + +public class SupportNonNumericFloatsTest { + @Test + public void addsNonNumericFloatSupport() { + Model model = Model.assembler() + .discoverModels(getClass().getClassLoader()) + .addImport(getClass().getResource("non-numeric-floats.json")) + .assemble() + .unwrap(); + + JsonSchemaConfig config = new JsonSchemaConfig(); + config.setSupportNonNumericFloats(true); + SchemaDocument result = JsonSchemaConverter.builder() + .config(config) + .model(model) + .build() + .convert(); + assertThat(result.getDefinitions().keySet(), not(empty())); + + Node expectedNode = Node.parse(IoUtils.toUtf8String( + getClass().getResourceAsStream("non-numeric-floats.jsonschema.json"))); + Node.assertEquals(result, expectedNode); + } +} diff --git a/smithy-jsonschema/src/test/resources/software/amazon/smithy/jsonschema/non-numeric-floats.json b/smithy-jsonschema/src/test/resources/software/amazon/smithy/jsonschema/non-numeric-floats.json new file mode 100644 index 00000000000..e83756bcc07 --- /dev/null +++ b/smithy-jsonschema/src/test/resources/software/amazon/smithy/jsonschema/non-numeric-floats.json @@ -0,0 +1,37 @@ +{ + "smithy": "1.0", + "shapes": { + "example.smithy#MyService": { + "type": "service", + "version": "2006-03-01", + "operations": [ + { + "target": "example.smithy#MyOperation" + } + ] + }, + "example.smithy#MyOperation": { + "type": "operation", + "input": { + "target": "example.smithy#MyOperationInput" + }, + "traits": { + "smithy.api#http": { + "uri": "/foo", + "method": "POST" + } + } + }, + "example.smithy#MyOperationInput": { + "type": "structure", + "members": { + "floatMember": { + "target": "smithy.api#Float" + }, + "doubleMember": { + "target": "smithy.api#Double" + } + } + } + } +} diff --git a/smithy-jsonschema/src/test/resources/software/amazon/smithy/jsonschema/non-numeric-floats.jsonschema.json b/smithy-jsonschema/src/test/resources/software/amazon/smithy/jsonschema/non-numeric-floats.jsonschema.json new file mode 100644 index 00000000000..9145a798812 --- /dev/null +++ b/smithy-jsonschema/src/test/resources/software/amazon/smithy/jsonschema/non-numeric-floats.jsonschema.json @@ -0,0 +1,39 @@ +{ + "definitions": { + "MyOperationInput": { + "type": "object", + "properties": { + "doubleMember": { + "oneOf": [ + { + "type": "number" + }, + { + "type": "string", + "enum": [ + "NaN", + "Infinity", + "-Infinity" + ] + } + ] + }, + "floatMember": { + "oneOf": [ + { + "type": "number" + }, + { + "type": "string", + "enum": [ + "NaN", + "Infinity", + "-Infinity" + ] + } + ] + } + } + } + } +} diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/node/Node.java b/smithy-model/src/main/java/software/amazon/smithy/model/node/Node.java index b38f1f06351..92e2d7f43e5 100644 --- a/smithy-model/src/main/java/software/amazon/smithy/model/node/Node.java +++ b/smithy-model/src/main/java/software/amazon/smithy/model/node/Node.java @@ -21,10 +21,12 @@ import java.util.Arrays; import java.util.Collection; import java.util.Comparator; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; +import java.util.Set; import java.util.TreeMap; import java.util.function.Supplier; import java.util.stream.Collectors; @@ -722,4 +724,56 @@ public Node arrayNode(ArrayNode node) { } }); } + + /** + * Non-numeric values for floats and doubles. + */ + public enum NonNumericFloat { + NAN("NaN"), + POSITIVE_INFINITY("Infinity"), + NEGATIVE_INFINITY("-Infinity"); + + private final String stringRepresentation; + + NonNumericFloat(String stringRepresentation) { + this.stringRepresentation = stringRepresentation; + } + + /** + * @return The string representation of this non-numeric float. + */ + public String getStringRepresentation() { + return stringRepresentation; + } + + /** + * @return All the possible string representations of non-numeric floats. + */ + public static Set stringRepresentations() { + Set values = new LinkedHashSet<>(); + for (NonNumericFloat value : NonNumericFloat.values()) { + values.add(value.getStringRepresentation()); + } + return values; + } + + /** + * Convert a string value into a NonNumericFloat. + * + * @param value A string representation of a non-numeric float value. + * @return A NonNumericFloat that represents the given string value or empty if there is no associated value. + */ + public static Optional fromStringRepresentation(String value) { + switch (value) { + case "NaN": + return Optional.of(NAN); + case "Infinity": + return Optional.of(POSITIVE_INFINITY); + case "-Infinity": + return Optional.of(NEGATIVE_INFINITY); + default: + return Optional.empty(); + } + } + } } diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/validation/NodeValidationVisitor.java b/smithy-model/src/main/java/software/amazon/smithy/model/validation/NodeValidationVisitor.java index 5883bda0b2b..c7d3dfa8f08 100644 --- a/smithy-model/src/main/java/software/amazon/smithy/model/validation/NodeValidationVisitor.java +++ b/smithy-model/src/main/java/software/amazon/smithy/model/validation/NodeValidationVisitor.java @@ -211,7 +211,7 @@ private List validateNaturalNumber(Shape shape, Long min, Long @Override public List floatShape(FloatShape shape) { - return value.isNumberNode() + return value.isNumberNode() || value.isStringNode() ? applyPlugins(shape) : invalidShape(shape, NodeType.NUMBER); } @@ -224,7 +224,7 @@ public List documentShape(DocumentShape shape) { @Override public List doubleShape(DoubleShape shape) { - return value.isNumberNode() + return value.isNumberNode() || value.isStringNode() ? applyPlugins(shape) : invalidShape(shape, NodeType.NUMBER); } diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/validation/node/NodeValidatorPlugin.java b/smithy-model/src/main/java/software/amazon/smithy/model/validation/node/NodeValidatorPlugin.java index f59c487cfc3..ffb5b7caf30 100644 --- a/smithy-model/src/main/java/software/amazon/smithy/model/validation/node/NodeValidatorPlugin.java +++ b/smithy-model/src/main/java/software/amazon/smithy/model/validation/node/NodeValidatorPlugin.java @@ -51,6 +51,7 @@ public interface NodeValidatorPlugin { */ static List getBuiltins() { return ListUtils.of( + new NonNumericFloatValuesPlugin(), new BlobLengthPlugin(), new CollectionLengthPlugin(), new IdRefPlugin(), diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/validation/node/NonNumericFloatValuesPlugin.java b/smithy-model/src/main/java/software/amazon/smithy/model/validation/node/NonNumericFloatValuesPlugin.java new file mode 100644 index 00000000000..94c26f2a7ee --- /dev/null +++ b/smithy-model/src/main/java/software/amazon/smithy/model/validation/node/NonNumericFloatValuesPlugin.java @@ -0,0 +1,44 @@ +/* + * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.smithy.model.validation.node; + +import java.util.Set; +import java.util.function.BiConsumer; +import software.amazon.smithy.model.FromSourceLocation; +import software.amazon.smithy.model.node.Node; +import software.amazon.smithy.model.node.Node.NonNumericFloat; +import software.amazon.smithy.model.shapes.Shape; + +/** + * Validates the specific set of non-numeric values allowed for floats and doubles. + */ +final class NonNumericFloatValuesPlugin implements NodeValidatorPlugin { + private static final Set NON_NUMERIC_FLOAT_VALUES = NonNumericFloat.stringRepresentations(); + + @Override + public void apply(Shape shape, Node value, Context context, BiConsumer emitter) { + if (!(shape.isFloatShape() || shape.isDoubleShape()) || !value.isStringNode()) { + return; + } + String nodeValue = value.expectStringNode().getValue(); + if (!NON_NUMERIC_FLOAT_VALUES.contains(nodeValue)) { + emitter.accept(value, String.format( + "Value for `%s` must either be numeric or one of the following strings: [\"%s\"], but was \"%s\"", + shape.getId(), String.join("\", \"", NON_NUMERIC_FLOAT_VALUES), nodeValue + )); + } + } +} diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/validation/node/RangeTraitPlugin.java b/smithy-model/src/main/java/software/amazon/smithy/model/validation/node/RangeTraitPlugin.java index dd3665938ed..a23796743cb 100644 --- a/smithy-model/src/main/java/software/amazon/smithy/model/validation/node/RangeTraitPlugin.java +++ b/smithy-model/src/main/java/software/amazon/smithy/model/validation/node/RangeTraitPlugin.java @@ -18,26 +18,61 @@ import java.math.BigDecimal; import java.util.function.BiConsumer; import software.amazon.smithy.model.FromSourceLocation; +import software.amazon.smithy.model.node.Node; +import software.amazon.smithy.model.node.Node.NonNumericFloat; import software.amazon.smithy.model.node.NumberNode; -import software.amazon.smithy.model.shapes.NumberShape; +import software.amazon.smithy.model.node.StringNode; import software.amazon.smithy.model.shapes.Shape; import software.amazon.smithy.model.traits.RangeTrait; /** * Validates the range trait on number shapes or members that target them. */ -final class RangeTraitPlugin extends MemberAndShapeTraitPlugin { +class RangeTraitPlugin implements NodeValidatorPlugin { - RangeTraitPlugin() { - super(NumberShape.class, NumberNode.class, RangeTrait.class); + @Override + public final void apply(Shape shape, Node value, Context context, BiConsumer emitter) { + if (shape.hasTrait(RangeTrait.class)) { + if (value.isNumberNode()) { + check(shape, shape.expectTrait(RangeTrait.class), value.expectNumberNode(), emitter); + } else if (value.isStringNode()) { + checkNonNumeric(shape, shape.expectTrait(RangeTrait.class), value.expectStringNode(), emitter); + } + } + } + + private void checkNonNumeric( + Shape shape, + RangeTrait trait, + StringNode node, + BiConsumer emitter + ) { + NonNumericFloat.fromStringRepresentation(node.getValue()).ifPresent(value -> { + if (value.equals(NonNumericFloat.NAN)) { + emitter.accept(node, String.format( + "Value provided for `%s` must be a number because the `smithy.api#range` trait is applied, " + + "but found \"%s\"", + shape.getId(), node.getValue())); + } + + if (trait.getMin().isPresent() && value.equals(NonNumericFloat.NEGATIVE_INFINITY)) { + emitter.accept(node, String.format( + "Value provided for `%s` must be greater than or equal to %s, but found \"%s\"", + shape.getId(), trait.getMin().get(), node.getValue())); + } + + if (trait.getMax().isPresent() && value.equals(NonNumericFloat.POSITIVE_INFINITY)) { + emitter.accept(node, String.format( + "Value provided for `%s` must be less than or equal to %s, but found \"%s\"", + shape.getId(), trait.getMax().get(), node.getValue())); + } + }); } - @Override protected void check( Shape shape, RangeTrait trait, NumberNode node, - Context context, BiConsumer emitter ) { Number number = node.getValue(); diff --git a/smithy-model/src/test/java/software/amazon/smithy/model/validation/NodeValidationVisitorTest.java b/smithy-model/src/test/java/software/amazon/smithy/model/validation/NodeValidationVisitorTest.java index f78e1527dc0..809250248b7 100644 --- a/smithy-model/src/test/java/software/amazon/smithy/model/validation/NodeValidationVisitorTest.java +++ b/smithy-model/src/test/java/software/amazon/smithy/model/validation/NodeValidationVisitorTest.java @@ -140,15 +140,29 @@ public static Collection data() { // float {"ns.foo#Float", "10", null}, + {"smithy.api#Float", "\"NaN\"", null}, + {"ns.foo#Float", "\"NaN\"", new String[] {"Value provided for `ns.foo#Float` must be a number because the `smithy.api#range` trait is applied, but found \"NaN\""}}, + {"smithy.api#Float", "\"Infinity\"", null}, + {"smithy.api#Float", "\"-Infinity\"", null}, + {"smithy.api#Float", "\"+Infinity\"", new String[] {"Value for `smithy.api#Float` must either be numeric or one of the following strings: [\"NaN\", \"Infinity\", \"-Infinity\"], but was \"+Infinity\""}}, {"ns.foo#Float", "true", new String[] {"Expected number value for float shape, `ns.foo#Float`; found boolean value, `true`"}}, {"ns.foo#Float", "21", new String[] {"Value provided for `ns.foo#Float` must be less than or equal to 20, but found 21"}}, + {"ns.foo#Float", "\"Infinity\"", new String[] {"Value provided for `ns.foo#Float` must be less than or equal to 20, but found \"Infinity\""}}, {"ns.foo#Float", "9", new String[] {"Value provided for `ns.foo#Float` must be greater than or equal to 10, but found 9"}}, + {"ns.foo#Float", "\"-Infinity\"", new String[] {"Value provided for `ns.foo#Float` must be greater than or equal to 10, but found \"-Infinity\""}}, // double {"ns.foo#Double", "10", null}, + {"smithy.api#Double", "\"NaN\"", null}, + {"ns.foo#Double", "\"NaN\"", new String[] {"Value provided for `ns.foo#Double` must be a number because the `smithy.api#range` trait is applied, but found \"NaN\""}}, + {"smithy.api#Double", "\"Infinity\"", null}, + {"smithy.api#Double", "\"-Infinity\"", null}, + {"smithy.api#Double", "\"+Infinity\"", new String[] {"Value for `smithy.api#Double` must either be numeric or one of the following strings: [\"NaN\", \"Infinity\", \"-Infinity\"], but was \"+Infinity\""}}, {"ns.foo#Double", "true", new String[] {"Expected number value for double shape, `ns.foo#Double`; found boolean value, `true`"}}, {"ns.foo#Double", "21", new String[] {"Value provided for `ns.foo#Double` must be less than or equal to 20, but found 21"}}, + {"ns.foo#Double", "\"Infinity\"", new String[] {"Value provided for `ns.foo#Double` must be less than or equal to 20, but found \"Infinity\""}}, {"ns.foo#Double", "9", new String[] {"Value provided for `ns.foo#Double` must be greater than or equal to 10, but found 9"}}, + {"ns.foo#Double", "\"-Infinity\"", new String[] {"Value provided for `ns.foo#Double` must be greater than or equal to 10, but found \"-Infinity\""}}, // bigInteger {"ns.foo#BigInteger", "10", null}, diff --git a/smithy-openapi/src/main/java/software/amazon/smithy/openapi/fromsmithy/OpenApiJsonSchemaMapper.java b/smithy-openapi/src/main/java/software/amazon/smithy/openapi/fromsmithy/OpenApiJsonSchemaMapper.java index ea171f99b89..e6924f0b877 100644 --- a/smithy-openapi/src/main/java/software/amazon/smithy/openapi/fromsmithy/OpenApiJsonSchemaMapper.java +++ b/smithy-openapi/src/main/java/software/amazon/smithy/openapi/fromsmithy/OpenApiJsonSchemaMapper.java @@ -81,9 +81,9 @@ public Schema.Builder updateSchema(Shape shape, Schema.Builder builder, JsonSche } else if (shape.isLongShape()) { builder.format("int64"); } else if (shape.isFloatShape()) { - builder.format("float"); + updateFloatFormat(builder, config, "float"); } else if (shape.isDoubleShape()) { - builder.format("double"); + updateFloatFormat(builder, config, "double"); } else if (shape.isBlobShape()) { if (config instanceof OpenApiConfig) { String blobFormat = ((OpenApiConfig) config).getDefaultBlobFormat(); @@ -106,6 +106,22 @@ public Schema.Builder updateSchema(Shape shape, Schema.Builder builder, JsonSche return builder; } + private void updateFloatFormat(Schema.Builder builder, JsonSchemaConfig config, String format) { + if (config.getSupportNonNumericFloats()) { + List newOneOf = new ArrayList<>(); + for (Schema schema : builder.build().getOneOf()) { + if (schema.getType().isPresent() && schema.getType().get().equals("number")) { + newOneOf.add(schema.toBuilder().format(format).build()); + } else { + newOneOf.add(schema); + } + } + builder.oneOf(newOneOf); + } else { + builder.format(format); + } + } + static Optional getResolvedExternalDocs(Shape shape, JsonSchemaConfig config) { Optional traitOptional = shape.getTrait(ExternalDocumentationTrait.class); diff --git a/smithy-openapi/src/test/java/software/amazon/smithy/openapi/fromsmithy/OpenApiJsonSchemaMapperTest.java b/smithy-openapi/src/test/java/software/amazon/smithy/openapi/fromsmithy/OpenApiJsonSchemaMapperTest.java index b9ac1caaff8..d9f7c800cd2 100644 --- a/smithy-openapi/src/test/java/software/amazon/smithy/openapi/fromsmithy/OpenApiJsonSchemaMapperTest.java +++ b/smithy-openapi/src/test/java/software/amazon/smithy/openapi/fromsmithy/OpenApiJsonSchemaMapperTest.java @@ -16,12 +16,15 @@ package software.amazon.smithy.openapi.fromsmithy; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.equalTo; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.Map; import org.junit.jupiter.api.Test; +import software.amazon.smithy.jsonschema.JsonSchemaConfig; import software.amazon.smithy.jsonschema.JsonSchemaConverter; import software.amazon.smithy.jsonschema.Schema; import software.amazon.smithy.jsonschema.SchemaDocument; @@ -195,6 +198,32 @@ public void supportsFloatFormat() { assertThat(document.getRootSchema().getFormat().get(), equalTo("float")); } + @Test + public void supportsNonNumericFloatFormat() { + FloatShape shape = FloatShape.builder().id("a.b#C").build(); + Model model = Model.builder().addShape(shape).build(); + JsonSchemaConfig config = new JsonSchemaConfig(); + config.setSupportNonNumericFloats(true); + SchemaDocument document = JsonSchemaConverter.builder() + .addMapper(new OpenApiJsonSchemaMapper()) + .config(config) + .model(model) + .build() + .convertShape(shape); + + Schema rootSchema = document.getRootSchema(); + assertFalse(rootSchema.getFormat().isPresent()); + Schema numericSchema = null; + for (Schema schema : rootSchema.getOneOf()) { + if (schema.getType().isPresent() && schema.getType().get().equals("number")) { + numericSchema = schema; + break; + } + } + assertNotNull(numericSchema); + assertThat(numericSchema.getFormat().get(), equalTo("float")); + } + @Test public void supportsDoubleFormat() { DoubleShape shape = DoubleShape.builder().id("a.b#C").build(); @@ -208,6 +237,32 @@ public void supportsDoubleFormat() { assertThat(document.getRootSchema().getFormat().get(), equalTo("double")); } + @Test + public void supportsNonNumericDoubleFormat() { + DoubleShape shape = DoubleShape.builder().id("a.b#C").build(); + Model model = Model.builder().addShape(shape).build(); + JsonSchemaConfig config = new JsonSchemaConfig(); + config.setSupportNonNumericFloats(true); + SchemaDocument document = JsonSchemaConverter.builder() + .addMapper(new OpenApiJsonSchemaMapper()) + .config(config) + .model(model) + .build() + .convertShape(shape); + + Schema rootSchema = document.getRootSchema(); + assertFalse(rootSchema.getFormat().isPresent()); + Schema numericSchema = null; + for (Schema schema : rootSchema.getOneOf()) { + if (schema.getType().isPresent() && schema.getType().get().equals("number")) { + numericSchema = schema; + break; + } + } + assertNotNull(numericSchema); + assertThat(numericSchema.getFormat().get(), equalTo("double")); + } + @Test public void blobFormatDefaultsToByte() { BlobShape shape = BlobShape.builder().id("a.b#C").build(); diff --git a/smithy-protocol-test-traits/src/test/resources/software/amazon/smithy/protocoltests/traits/errorfiles/vendor-params-validation.errors b/smithy-protocol-test-traits/src/test/resources/software/amazon/smithy/protocoltests/traits/errorfiles/vendor-params-validation.errors index b2648353a5e..13d98013c70 100644 --- a/smithy-protocol-test-traits/src/test/resources/software/amazon/smithy/protocoltests/traits/errorfiles/vendor-params-validation.errors +++ b/smithy-protocol-test-traits/src/test/resources/software/amazon/smithy/protocoltests/traits/errorfiles/vendor-params-validation.errors @@ -1,5 +1,5 @@ [WARNING] smithy.example#SayGoodbye: Protocol test case defined a `vendorParamsShape` but no `vendorParams` | HttpResponseTestsOutput [WARNING] smithy.example#SayGoodbye: smithy.test#httpResponseTests.1.vendorParams: Invalid structure member `additional` found for `smithy.example#emptyVendorParamsStructure` | HttpResponseTestsOutput -[ERROR] smithy.example#MyError: smithy.test#httpResponseTests.0.vendorParams.float: Expected number value for float shape, `smithy.api#Float`; found string value, `Hi` | HttpResponseTestsError +[ERROR] smithy.example#MyError: smithy.test#httpResponseTests.0.vendorParams.float: Value for `smithy.api#Float` must either be numeric or one of the following strings: ["NaN", "Infinity", "-Infinity"], but was "Hi" | HttpResponseTestsError [ERROR] smithy.example#SayHello: smithy.test#httpRequestTests.0.vendorParams: Missing required structure member `integer` for `smithy.example#simpleVendorParamsStructure` | HttpRequestTestsInput [ERROR] smithy.example#SayHello: smithy.test#httpRequestTests.1.vendorParams.boolean: Expected boolean value for boolean shape, `smithy.api#Boolean`; found string value, `Hi` | HttpRequestTestsInput