From 60d84d168f35e9c9c3ea066a44ece205fe6715ea Mon Sep 17 00:00:00 2001 From: Russell Cohen Date: Wed, 18 Jan 2023 16:00:42 -0500 Subject: [PATCH 01/11] Upgrade to Smithy 1.27.1 --- .../amazon/smithy/rustsdk/endpoints/AwsEndpointDecorator.kt | 6 +----- .../smithy/endpoint/generators/EndpointResolverGenerator.kt | 5 +---- gradle.properties | 2 +- 3 files changed, 3 insertions(+), 10 deletions(-) diff --git a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/endpoints/AwsEndpointDecorator.kt b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/endpoints/AwsEndpointDecorator.kt index 41c8d29bdf..adffa8bb4f 100644 --- a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/endpoints/AwsEndpointDecorator.kt +++ b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/endpoints/AwsEndpointDecorator.kt @@ -60,11 +60,7 @@ class AwsEndpointDecorator : ClientCodegenDecorator { epRules.parameters.toList() .map { param -> param.letIf(param.builtIn == Builtins.REGION.builtIn) { parameter -> - val builder = parameter.toBuilder().required(true) - // TODO(https://github.com/awslabs/smithy-rs/issues/2187): undo this workaround - parameter.defaultValue.ifPresent { default -> builder.defaultValue(default) } - - builder.build() + parameter.toBuilder().required(true).build() } } .forEach(newParameters::addParameter) diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/generators/EndpointResolverGenerator.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/generators/EndpointResolverGenerator.kt index 5fade8768d..d85282f16e 100644 --- a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/generators/EndpointResolverGenerator.kt +++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/generators/EndpointResolverGenerator.kt @@ -10,7 +10,6 @@ import software.amazon.smithy.rulesengine.language.EndpointRuleSet import software.amazon.smithy.rulesengine.language.eval.Type import software.amazon.smithy.rulesengine.language.syntax.expr.Expression import software.amazon.smithy.rulesengine.language.syntax.expr.Reference -import software.amazon.smithy.rulesengine.language.syntax.fn.Function import software.amazon.smithy.rulesengine.language.syntax.fn.IsSet import software.amazon.smithy.rulesengine.language.syntax.rule.Condition import software.amazon.smithy.rulesengine.language.syntax.rule.Rule @@ -289,9 +288,7 @@ internal class EndpointResolverGenerator(stdlib: List, ru val target = generator.generate(fn) val next = generateRuleInternal(rule, rest) when { - fn.type() is Type.Option || - // TODO(https://github.com/awslabs/smithy/pull/1504): ReterminusCore bug: substring should return `Option`: - (fn as Function).name == "substring" -> { + fn.type() is Type.Option -> { Attribute.AllowUnusedVariables.render(this) rustTemplate( "if let Some($resultName) = #{target:W} { #{next:W} }", diff --git a/gradle.properties b/gradle.properties index a353e421af..910aedf3df 100644 --- a/gradle.properties +++ b/gradle.properties @@ -18,7 +18,7 @@ kotlin.code.style=official # codegen smithyGradlePluginVersion=0.6.0 -smithyVersion=1.26.2 +smithyVersion=1.27.1 # kotlin kotlinVersion=1.7.21 From 34fd0a24044f205580a1f4a833b9ffb147aed0f9 Mon Sep 17 00:00:00 2001 From: Russell Cohen Date: Wed, 18 Jan 2023 16:58:26 -0500 Subject: [PATCH 02/11] Fix bugs exposed by 1.27.1 --- codegen-core/common-test-models/misc.smithy | 31 ------------------- .../rust/codegen/core/smithy/SymbolVisitor.kt | 4 +++ .../codegen/core/smithy/protocols/AwsJson.kt | 9 ++++++ .../smithy/protocols/HttpBindingResolver.kt | 25 ++++++++++++--- .../aws-smithy-types/src/date_time/format.rs | 6 ---- 5 files changed, 33 insertions(+), 42 deletions(-) diff --git a/codegen-core/common-test-models/misc.smithy b/codegen-core/common-test-models/misc.smithy index 7185e5a3b9..f78bb6db7c 100644 --- a/codegen-core/common-test-models/misc.smithy +++ b/codegen-core/common-test-models/misc.smithy @@ -20,7 +20,6 @@ service MiscService { ResponseCodeRequiredOperation, ResponseCodeHttpFallbackOperation, ResponseCodeDefaultOperation, - AcceptHeaderStarService, ], } @@ -204,36 +203,6 @@ structure ResponseCodeRequiredOutput { responseCode: Integer, } -// TODO(https://github.com/awslabs/smithy/pull/1365): remove when these tests are in smithy -@http(method: "GET", uri: "/test-accept-header") -@httpRequestTests([ - { - id: "AcceptHeaderStarRequestTest", - protocol: "aws.protocols#restJson1", - uri: "/test-accept-header", - headers: { - "Accept": "application/*", - }, - params: {}, - body: "{}", - method: "GET", - appliesTo: "server", - }, - { - id: "AcceptHeaderStarStarRequestTest", - protocol: "aws.protocols#restJson1", - uri: "/test-accept-header", - headers: { - "Accept": "*/*", - }, - params: {}, - body: "{}", - method: "GET", - appliesTo: "server", - } -]) -operation AcceptHeaderStarService {} - @http(uri: "/required-header-collection-operation", method: "GET") operation RequiredHeaderCollectionOperation { input: RequiredHeaderCollectionOperationInputOutput, diff --git a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/SymbolVisitor.kt b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/SymbolVisitor.kt index fbab905dad..b2426c602c 100644 --- a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/SymbolVisitor.kt +++ b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/SymbolVisitor.kt @@ -19,6 +19,7 @@ import software.amazon.smithy.model.shapes.DocumentShape import software.amazon.smithy.model.shapes.DoubleShape import software.amazon.smithy.model.shapes.EnumShape import software.amazon.smithy.model.shapes.FloatShape +import software.amazon.smithy.model.shapes.IntEnumShape import software.amazon.smithy.model.shapes.IntegerShape import software.amazon.smithy.model.shapes.ListShape import software.amazon.smithy.model.shapes.LongShape @@ -59,6 +60,7 @@ val SimpleShapes: Map, RustType> = mapOf( ByteShape::class to RustType.Integer(8), ShortShape::class to RustType.Integer(16), IntegerShape::class to RustType.Integer(32), + IntEnumShape::class to RustType.Integer(32), LongShape::class to RustType.Integer(64), StringShape::class to RustType.String, ) @@ -178,6 +180,8 @@ open class SymbolVisitor( override fun longShape(shape: LongShape): Symbol = simpleShape(shape) override fun floatShape(shape: FloatShape): Symbol = simpleShape(shape) override fun doubleShape(shape: DoubleShape): Symbol = simpleShape(shape) + + override fun intEnumShape(shape: IntEnumShape): Symbol = simpleShape(shape) override fun stringShape(shape: StringShape): Symbol { return if (shape.hasTrait()) { val rustType = RustType.Opaque(shape.contextName(serviceShape).toPascalCase()) diff --git a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/protocols/AwsJson.kt b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/protocols/AwsJson.kt index 0476acdba5..9b5200b0c4 100644 --- a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/protocols/AwsJson.kt +++ b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/protocols/AwsJson.kt @@ -79,6 +79,15 @@ class AwsJsonHttpBindingResolver( override fun errorResponseBindings(errorShape: ToShapeId): List = bindings(errorShape) + override fun timestampFormat( + memberShape: MemberShape, + location: HttpLocation, + defaultTimestampFormat: TimestampFormatTrait.Format, + ): TimestampFormatTrait.Format { + return memberShape.getMemberTrait(model, TimestampFormatTrait::class.java).map { it.format } + .orElse(defaultTimestampFormat) + } + override fun requestContentType(operationShape: OperationShape): String = "application/x-amz-json-${awsJsonVersion.value}" diff --git a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/protocols/HttpBindingResolver.kt b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/protocols/HttpBindingResolver.kt index cf14e07e2e..8dfd489c88 100644 --- a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/protocols/HttpBindingResolver.kt +++ b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/protocols/HttpBindingResolver.kt @@ -14,7 +14,6 @@ import software.amazon.smithy.model.shapes.ToShapeId import software.amazon.smithy.model.traits.HttpTrait import software.amazon.smithy.model.traits.TimestampFormatTrait import software.amazon.smithy.rust.codegen.core.util.expectTrait -import software.amazon.smithy.rust.codegen.core.util.getTrait import software.amazon.smithy.rust.codegen.core.util.orNull typealias HttpLocation = HttpBinding.Location @@ -79,8 +78,7 @@ interface HttpBindingResolver { memberShape: MemberShape, location: HttpLocation, defaultTimestampFormat: TimestampFormatTrait.Format, - ): TimestampFormatTrait.Format = - memberShape.getTrait()?.format ?: defaultTimestampFormat + ): TimestampFormatTrait.Format /** * Determines the request content type for given [operationShape]. @@ -138,10 +136,18 @@ open class HttpTraitHttpBindingResolver( httpIndex.determineTimestampFormat(memberShape, location, defaultTimestampFormat) override fun requestContentType(operationShape: OperationShape): String? = - httpIndex.determineRequestContentType(operationShape, contentTypes.requestDocument, contentTypes.eventStreamContentType).orNull() + httpIndex.determineRequestContentType( + operationShape, + contentTypes.requestDocument, + contentTypes.eventStreamContentType, + ).orNull() override fun responseContentType(operationShape: OperationShape): String? = - httpIndex.determineResponseContentType(operationShape, contentTypes.responseDocument, contentTypes.eventStreamContentType).orNull() + httpIndex.determineResponseContentType( + operationShape, + contentTypes.responseDocument, + contentTypes.eventStreamContentType, + ).orNull() // Sort the members after extracting them from the map to have a consistent order private fun mappedBindings(bindings: Map): List = @@ -178,4 +184,13 @@ open class StaticHttpBindingResolver( override fun requestContentType(operationShape: OperationShape): String = requestContentType override fun responseContentType(operationShape: OperationShape): String = responseContentType + + override fun timestampFormat( + memberShape: MemberShape, + location: HttpLocation, + defaultTimestampFormat: TimestampFormatTrait.Format, + ): TimestampFormatTrait.Format { + return memberShape.getMemberTrait(model, TimestampFormatTrait::class.java).map { it.format } + .orElse(defaultTimestampFormat) + } } diff --git a/rust-runtime/aws-smithy-types/src/date_time/format.rs b/rust-runtime/aws-smithy-types/src/date_time/format.rs index 6108c7e28d..31480dc743 100644 --- a/rust-runtime/aws-smithy-types/src/date_time/format.rs +++ b/rust-runtime/aws-smithy-types/src/date_time/format.rs @@ -405,12 +405,6 @@ pub(crate) mod rfc3339 { // Timezones not supported: // Not OK: 1985-04-12T23:20:50-02:00 pub(crate) fn parse(s: &str) -> Result { - if !matches!(s.chars().last(), Some('Z')) { - return Err(DateTimeParseErrorKind::Invalid( - "Smithy does not support timezone offsets in RFC-3339 date times".into(), - ) - .into()); - } let date_time = OffsetDateTime::parse(s, &Rfc3339).map_err(|err| { DateTimeParseErrorKind::Invalid(format!("invalid RFC-3339 date-time: {}", err).into()) })?; From 16cff9b43bb955150986bd9de456327b0be4f42e Mon Sep 17 00:00:00 2001 From: Russell Cohen Date: Thu, 19 Jan 2023 11:03:43 -0500 Subject: [PATCH 03/11] Fix rules engine customization --- .../rustsdk/endpoints/AwsEndpointDecorator.kt | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/endpoints/AwsEndpointDecorator.kt b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/endpoints/AwsEndpointDecorator.kt index adffa8bb4f..e34fb37cfa 100644 --- a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/endpoints/AwsEndpointDecorator.kt +++ b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/endpoints/AwsEndpointDecorator.kt @@ -10,8 +10,8 @@ import software.amazon.smithy.model.Model import software.amazon.smithy.model.shapes.ServiceShape import software.amazon.smithy.model.shapes.ShapeId import software.amazon.smithy.model.transform.ModelTransformer -import software.amazon.smithy.rulesengine.language.EndpointRuleSet import software.amazon.smithy.rulesengine.language.syntax.parameters.Builtins +import software.amazon.smithy.rulesengine.language.syntax.parameters.Parameter import software.amazon.smithy.rulesengine.language.syntax.parameters.Parameters import software.amazon.smithy.rulesengine.traits.EndpointRuleSetTrait import software.amazon.smithy.rust.codegen.client.smithy.ClientCodegenContext @@ -55,20 +55,18 @@ class AwsEndpointDecorator : ClientCodegenDecorator { return ModelTransformer.create().mapTraits(model) { _, trait -> when (trait) { is EndpointRuleSetTrait -> { - val epRules = EndpointRuleSet.fromNode(trait.ruleSet) + val rules = trait.ruleSet.expectObjectNode() + val params = rules.expectObjectMember("parameters") val newParameters = Parameters.builder() - epRules.parameters.toList() - .map { param -> - param.letIf(param.builtIn == Builtins.REGION.builtIn) { parameter -> - parameter.toBuilder().required(true).build() - } + params.members.map { (key, value) -> + val param = Parameter.fromNode(key, value.expectObjectNode()) + param.letIf(param.builtIn == Builtins.REGION.builtIn) { parameter -> + parameter.toBuilder().required(true).build() } - .forEach(newParameters::addParameter) - - val newTrait = epRules.toBuilder().parameters( - newParameters.build(), - ).build() - EndpointRuleSetTrait.builder().ruleSet(newTrait.toNode()).build() + }.forEach(newParameters::addParameter) + EndpointRuleSetTrait.builder() + .ruleSet(rules.toBuilder().withMember("parameters", newParameters.build().toNode()).build()) + .build() } else -> trait @@ -244,7 +242,9 @@ class AwsEndpointDecorator : ClientCodegenDecorator { } ServiceConfig.ConfigStruct -> rust("endpoint_url: Option,") - else -> {} + ServiceConfig.ConfigStructAdditionalDocs -> emptySection + ServiceConfig.Extras -> emptySection + else -> emptySection } } } From 59db199f54efd66205319724d6efb27c3d3cbf8e Mon Sep 17 00:00:00 2001 From: Russell Cohen Date: Thu, 19 Jan 2023 11:15:53 -0500 Subject: [PATCH 04/11] update rfc3339 parsing test --- .../rustsdk/endpoints/AwsEndpointDecorator.kt | 56 ------------------- .../aws-smithy-types/src/date_time/format.rs | 9 +-- 2 files changed, 2 insertions(+), 63 deletions(-) diff --git a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/endpoints/AwsEndpointDecorator.kt b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/endpoints/AwsEndpointDecorator.kt index e34fb37cfa..4a5311cd43 100644 --- a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/endpoints/AwsEndpointDecorator.kt +++ b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/endpoints/AwsEndpointDecorator.kt @@ -21,7 +21,6 @@ import software.amazon.smithy.rust.codegen.client.smithy.endpoint.EndpointTypesG import software.amazon.smithy.rust.codegen.client.smithy.featureGatedConfigModule import software.amazon.smithy.rust.codegen.client.smithy.generators.config.ConfigCustomization import software.amazon.smithy.rust.codegen.client.smithy.generators.config.ServiceConfig -import software.amazon.smithy.rust.codegen.core.rustlang.Attribute import software.amazon.smithy.rust.codegen.core.rustlang.CargoDependency import software.amazon.smithy.rust.codegen.core.rustlang.Writable import software.amazon.smithy.rust.codegen.core.rustlang.rust @@ -193,61 +192,6 @@ class AwsEndpointDecorator : ClientCodegenDecorator { } } } - - class SdkEndpointCustomization( - codegenContext: CodegenContext, - ) : - ConfigCustomization() { - private val runtimeConfig = codegenContext.runtimeConfig - private val resolveAwsEndpoint = AwsRuntimeType.awsEndpoint(runtimeConfig).resolve("ResolveAwsEndpoint") - private val endpointShim = AwsRuntimeType.awsEndpoint(runtimeConfig).resolve("EndpointShim") - private val codegenScope = arrayOf( - "ResolveAwsEndpoint" to resolveAwsEndpoint, - "EndpointShim" to endpointShim, - "aws_types" to AwsRuntimeType.awsTypes(runtimeConfig), - ) - - override fun section(section: ServiceConfig): Writable = writable { - when (section) { - ServiceConfig.BuilderImpl -> rustTemplate( - """ - /// Sets the endpoint url used to communicate with this service - /// - /// Note: this is used in combination with other endpoint rules, e.g. an API that applies a host-label prefix - /// will be prefixed onto this URL. To fully override the endpoint resolver, use - /// [`Builder::endpoint_resolver`]. - pub fn endpoint_url(mut self, endpoint_url: impl Into) -> Self { - self.endpoint_url = Some(endpoint_url.into()); - self - } - - /// Sets the endpoint url used to communicate with this service - /// - /// Note: this is used in combination with other endpoint rules, e.g. an API that applies a host-label prefix - /// will be prefixed onto this URL. To fully override the endpoint resolver, use - /// [`Builder::endpoint_resolver`]. - pub fn set_endpoint_url(&mut self, endpoint_url: Option) -> &mut Self { - self.endpoint_url = endpoint_url; - self - } - """, - *codegenScope, - ) - - ServiceConfig.BuilderBuild -> rust("endpoint_url: self.endpoint_url,") - ServiceConfig.BuilderStruct -> rust("endpoint_url: Option,") - ServiceConfig.ConfigImpl -> { - Attribute.AllowDeadCode.render(this) - rust("pub(crate) fn endpoint_url(&self) -> Option<&str> { self.endpoint_url.as_deref() }") - } - - ServiceConfig.ConfigStruct -> rust("endpoint_url: Option,") - ServiceConfig.ConfigStructAdditionalDocs -> emptySection - ServiceConfig.Extras -> emptySection - else -> emptySection - } - } - } } fun ClientCodegenContext.isRegionalized() = getBuiltIn(Builtins.REGION) != null diff --git a/rust-runtime/aws-smithy-types/src/date_time/format.rs b/rust-runtime/aws-smithy-types/src/date_time/format.rs index 31480dc743..41371ed64e 100644 --- a/rust-runtime/aws-smithy-types/src/date_time/format.rs +++ b/rust-runtime/aws-smithy-types/src/date_time/format.rs @@ -661,13 +661,8 @@ mod tests { #[test] fn parse_rfc3339_timezone_forbidden() { - let dt = rfc3339::parse("1985-04-12T23:20:50-02:00"); - assert!(matches!( - dt.unwrap_err(), - DateTimeParseError { - kind: DateTimeParseErrorKind::Invalid(_) - } - )); + let dt = rfc3339::parse("1985-04-12T21:20:51-02:00"); + assert_eq!(dt.unwrap(), DateTime::from_secs_and_nanos(482196051, 0)); } #[test] From 48d5f1e01c9c8b1f7de5a1e471bd55745ff2afb1 Mon Sep 17 00:00:00 2001 From: Russell Cohen Date: Thu, 19 Jan 2023 12:35:51 -0500 Subject: [PATCH 05/11] refactor timestamp format & remove upstreamed test --- .../malformed-range-extras.smithy | 662 ------------------ .../codegen/core/smithy/protocols/AwsJson.kt | 9 - .../smithy/protocols/HttpBindingResolver.kt | 17 +- .../protocols/parse/JsonParserGenerator.kt | 2 +- .../serialize/JsonSerializerGenerator.kt | 2 +- .../XmlBindingTraitSerializerGenerator.kt | 19 +- codegen-server-test/build.gradle.kts | 6 - 7 files changed, 25 insertions(+), 692 deletions(-) delete mode 100644 codegen-core/common-test-models/malformed-range-extras.smithy diff --git a/codegen-core/common-test-models/malformed-range-extras.smithy b/codegen-core/common-test-models/malformed-range-extras.smithy deleted file mode 100644 index 8fd9d93c11..0000000000 --- a/codegen-core/common-test-models/malformed-range-extras.smithy +++ /dev/null @@ -1,662 +0,0 @@ -$version: "2.0" - -namespace aws.protocoltests.extras.restjson.validation - -use aws.api#service -use aws.protocols#restJson1 -use smithy.test#httpMalformedRequestTests -use smithy.framework#ValidationException - -/// A REST JSON service that sends JSON requests and responses with validation applied -@service(sdkId: "Rest Json Validation Protocol") -@restJson1 -service MalformedRangeValidation { - version: "2022-11-23", - operations: [ - MalformedRange, - MalformedRangeOverride, - ] -} - -@suppress(["UnstableTrait"]) -@http(uri: "/MalformedRange", method: "POST") -operation MalformedRange { - input: MalformedRangeInput, - errors: [ValidationException] -} - -@suppress(["UnstableTrait"]) -@http(uri: "/MalformedRangeOverride", method: "POST") -operation MalformedRangeOverride { - input: MalformedRangeOverrideInput, - errors: [ValidationException] -} - -apply MalformedRange @httpMalformedRequestTests([ - { - id: "RestJsonMalformedRangeShort", - documentation: """ - When a short member does not fit within range bounds, - the response should be a 400 ValidationException.""", - protocol: restJson1, - request: { - method: "POST", - uri: "/MalformedRange", - body: """ - { "short" : $value:L }""", - headers: { - "content-type": "application/json" - } - }, - response: { - code: 400, - headers: { - "x-amzn-errortype": "ValidationException" - }, - body: { - mediaType: "application/json", - assertion: { - contents: """ - { "message" : "1 validation error detected. Value $value:L at '/short' failed to satisfy constraint: Member must be between 2 and 8, inclusive", - "fieldList" : [{"message": "Value $value:L at '/short' failed to satisfy constraint: Member must be between 2 and 8, inclusive", "path": "/short"}]}""" - } - } - }, - testParameters: { - value: ["1", "9"] - } - }, - { - id: "RestJsonMalformedRangeMinShort", - documentation: """ - When a short member does not fit within range bounds, - the response should be a 400 ValidationException.""", - protocol: restJson1, - request: { - method: "POST", - uri: "/MalformedRange", - body: """ - { "minShort" : 1 }""", - headers: { - "content-type": "application/json" - } - }, - response: { - code: 400, - headers: { - "x-amzn-errortype": "ValidationException" - }, - body: { - mediaType: "application/json", - assertion: { - contents: """ - { "message" : "1 validation error detected. Value 1 at '/minShort' failed to satisfy constraint: Member must be greater than or equal to 2", - "fieldList" : [{"message": "Value 1 at '/minShort' failed to satisfy constraint: Member must be greater than or equal to 2", "path": "/minShort"}]}""" - } - } - } - }, - { - id: "RestJsonMalformedRangeMaxShort", - documentation: """ - When a short member does not fit within range bounds, - the response should be a 400 ValidationException.""", - protocol: restJson1, - request: { - method: "POST", - uri: "/MalformedRange", - body: """ - { "maxShort" : 9 }""", - headers: { - "content-type": "application/json" - } - }, - response: { - code: 400, - headers: { - "x-amzn-errortype": "ValidationException" - }, - body: { - mediaType: "application/json", - assertion: { - contents: """ - { "message" : "1 validation error detected. Value 9 at '/maxShort' failed to satisfy constraint: Member must be less than or equal to 8", - "fieldList" : [{"message": "Value 9 at '/maxShort' failed to satisfy constraint: Member must be less than or equal to 8", "path": "/maxShort"}]}""" - } - } - } - }, - { - id: "RestJsonMalformedRangeInteger", - documentation: """ - When a integer member does not fit within range bounds, - the response should be a 400 ValidationException.""", - protocol: restJson1, - request: { - method: "POST", - uri: "/MalformedRange", - body: """ - { "integer" : $value:L }""", - headers: { - "content-type": "application/json" - } - }, - response: { - code: 400, - headers: { - "x-amzn-errortype": "ValidationException" - }, - body: { - mediaType: "application/json", - assertion: { - contents: """ - { "message" : "1 validation error detected. Value $value:L at '/integer' failed to satisfy constraint: Member must be between 2 and 8, inclusive", - "fieldList" : [{"message": "Value $value:L at '/integer' failed to satisfy constraint: Member must be between 2 and 8, inclusive", "path": "/integer"}]}""" - } - } - }, - testParameters: { - value: ["1", "9"] - } - }, - { - id: "RestJsonMalformedRangeMinInteger", - documentation: """ - When a integer member does not fit within range bounds, - the response should be a 400 ValidationException.""", - protocol: restJson1, - request: { - method: "POST", - uri: "/MalformedRange", - body: """ - { "minInteger" : 1 }""", - headers: { - "content-type": "application/json" - } - }, - response: { - code: 400, - headers: { - "x-amzn-errortype": "ValidationException" - }, - body: { - mediaType: "application/json", - assertion: { - contents: """ - { "message" : "1 validation error detected. Value 1 at '/minInteger' failed to satisfy constraint: Member must be greater than or equal to 2", - "fieldList" : [{"message": "Value 1 at '/minInteger' failed to satisfy constraint: Member must be greater than or equal to 2", "path": "/minInteger"}]}""" - } - } - } - }, - { - id: "RestJsonMalformedRangeMaxInteger", - documentation: """ - When a integer member does not fit within range bounds, - the response should be a 400 ValidationException.""", - protocol: restJson1, - request: { - method: "POST", - uri: "/MalformedRange", - body: """ - { "maxInteger" : 9 }""", - headers: { - "content-type": "application/json" - } - }, - response: { - code: 400, - headers: { - "x-amzn-errortype": "ValidationException" - }, - body: { - mediaType: "application/json", - assertion: { - contents: """ - { "message" : "1 validation error detected. Value 9 at '/maxInteger' failed to satisfy constraint: Member must be less than or equal to 8", - "fieldList" : [{"message": "Value 9 at '/maxInteger' failed to satisfy constraint: Member must be less than or equal to 8", "path": "/maxInteger"}]}""" - } - } - } - }, - { - id: "RestJsonMalformedRangeLong", - documentation: """ - When a long member does not fit within range bounds, - the response should be a 400 ValidationException.""", - protocol: restJson1, - request: { - method: "POST", - uri: "/MalformedRange", - body: """ - { "long" : $value:L }""", - headers: { - "content-type": "application/json" - } - }, - response: { - code: 400, - headers: { - "x-amzn-errortype": "ValidationException" - }, - body: { - mediaType: "application/json", - assertion: { - contents: """ - { "message" : "1 validation error detected. Value $value:L at '/long' failed to satisfy constraint: Member must be between 2 and 8, inclusive", - "fieldList" : [{"message": "Value $value:L at '/long' failed to satisfy constraint: Member must be between 2 and 8, inclusive", "path": "/long"}]}""" - } - } - }, - testParameters: { - value: ["1", "9"] - } - }, - { - id: "RestJsonMalformedRangeMinLong", - documentation: """ - When a long member does not fit within range bounds, - the response should be a 400 ValidationException.""", - protocol: restJson1, - request: { - method: "POST", - uri: "/MalformedRange", - body: """ - { "minLong" : 1 }""", - headers: { - "content-type": "application/json" - } - }, - response: { - code: 400, - headers: { - "x-amzn-errortype": "ValidationException" - }, - body: { - mediaType: "application/json", - assertion: { - contents: """ - { "message" : "1 validation error detected. Value 1 at '/minLong' failed to satisfy constraint: Member must be greater than or equal to 2", - "fieldList" : [{"message": "Value 1 at '/minLong' failed to satisfy constraint: Member must be greater than or equal to 2", "path": "/minLong"}]}""" - } - } - } - }, - { - id: "RestJsonMalformedRangeMaxLong", - documentation: """ - When a long member does not fit within range bounds, - the response should be a 400 ValidationException.""", - protocol: restJson1, - request: { - method: "POST", - uri: "/MalformedRange", - body: """ - { "maxLong" : 9 }""", - headers: { - "content-type": "application/json" - } - }, - response: { - code: 400, - headers: { - "x-amzn-errortype": "ValidationException" - }, - body: { - mediaType: "application/json", - assertion: { - contents: """ - { "message" : "1 validation error detected. Value 9 at '/maxLong' failed to satisfy constraint: Member must be less than or equal to 8", - "fieldList" : [{"message": "Value 9 at '/maxLong' failed to satisfy constraint: Member must be less than or equal to 8", "path": "/maxLong"}]}""" - } - } - } - }, -]) - -// now repeat the above tests, but for the more specific constraints applied to the input member -apply MalformedRangeOverride @httpMalformedRequestTests([ - { - id: "RestJsonMalformedRangeShortOverride", - documentation: """ - When a short member does not fit within range bounds, - the response should be a 400 ValidationException.""", - protocol: restJson1, - request: { - method: "POST", - uri: "/MalformedRangeOverride", - body: """ - { "short" : $value:L }""", - headers: { - "content-type": "application/json" - } - }, - response: { - code: 400, - headers: { - "x-amzn-errortype": "ValidationException" - }, - body: { - mediaType: "application/json", - assertion: { - contents: """ - { "message" : "1 validation error detected. Value $value:L at '/short' failed to satisfy constraint: Member must be between 4 and 6, inclusive", - "fieldList" : [{"message": "Value $value:L at '/short' failed to satisfy constraint: Member must be between 4 and 6, inclusive", "path": "/short"}]}""" - } - } - }, - testParameters: { - value: ["3", "7"] - } - }, - { - id: "RestJsonMalformedRangeMinShortOverride", - documentation: """ - When a short member does not fit within range bounds, - the response should be a 400 ValidationException.""", - protocol: restJson1, - request: { - method: "POST", - uri: "/MalformedRangeOverride", - body: """ - { "minShort" : 3 }""", - headers: { - "content-type": "application/json" - } - }, - response: { - code: 400, - headers: { - "x-amzn-errortype": "ValidationException" - }, - body: { - mediaType: "application/json", - assertion: { - contents: """ - { "message" : "1 validation error detected. Value 3 at '/minShort' failed to satisfy constraint: Member must be greater than or equal to 4", - "fieldList" : [{"message": "Value 3 at '/minShort' failed to satisfy constraint: Member must be greater than or equal to 4", "path": "/minShort"}]}""" - } - } - } - }, - { - id: "RestJsonMalformedRangeMaxShortOverride", - documentation: """ - When a short member does not fit within range bounds, - the response should be a 400 ValidationException.""", - protocol: restJson1, - request: { - method: "POST", - uri: "/MalformedRangeOverride", - body: """ - { "maxShort" : 7 }""", - headers: { - "content-type": "application/json" - } - }, - response: { - code: 400, - headers: { - "x-amzn-errortype": "ValidationException" - }, - body: { - mediaType: "application/json", - assertion: { - contents: """ - { "message" : "1 validation error detected. Value 7 at '/maxShort' failed to satisfy constraint: Member must be less than or equal to 6", - "fieldList" : [{"message": "Value 7 at '/maxShort' failed to satisfy constraint: Member must be less than or equal to 6", "path": "/maxShort"}]}""" - } - } - } - }, - { - id: "RestJsonMalformedRangeIntegerOverride", - documentation: """ - When a integer member does not fit within range bounds, - the response should be a 400 ValidationException.""", - protocol: restJson1, - request: { - method: "POST", - uri: "/MalformedRangeOverride", - body: """ - { "integer" : $value:L }""", - headers: { - "content-type": "application/json" - } - }, - response: { - code: 400, - headers: { - "x-amzn-errortype": "ValidationException" - }, - body: { - mediaType: "application/json", - assertion: { - contents: """ - { "message" : "1 validation error detected. Value $value:L at '/integer' failed to satisfy constraint: Member must be between 4 and 6, inclusive", - "fieldList" : [{"message": "Value $value:L at '/integer' failed to satisfy constraint: Member must be between 4 and 6, inclusive", "path": "/integer"}]}""" - } - } - }, - testParameters: { - value: ["3", "7"] - } - }, - { - id: "RestJsonMalformedRangeMinIntegerOverride", - documentation: """ - When a integer member does not fit within range bounds, - the response should be a 400 ValidationException.""", - protocol: restJson1, - request: { - method: "POST", - uri: "/MalformedRangeOverride", - body: """ - { "minInteger" : 3 }""", - headers: { - "content-type": "application/json" - } - }, - response: { - code: 400, - headers: { - "x-amzn-errortype": "ValidationException" - }, - body: { - mediaType: "application/json", - assertion: { - contents: """ - { "message" : "1 validation error detected. Value 3 at '/minInteger' failed to satisfy constraint: Member must be greater than or equal to 4", - "fieldList" : [{"message": "Value 3 at '/minInteger' failed to satisfy constraint: Member must be greater than or equal to 4", "path": "/minInteger"}]}""" - } - } - } - }, - { - id: "RestJsonMalformedRangeMaxIntegerOverride", - documentation: """ - When a integer member does not fit within range bounds, - the response should be a 400 ValidationException.""", - protocol: restJson1, - request: { - method: "POST", - uri: "/MalformedRangeOverride", - body: """ - { "maxInteger" : 7 }""", - headers: { - "content-type": "application/json" - } - }, - response: { - code: 400, - headers: { - "x-amzn-errortype": "ValidationException" - }, - body: { - mediaType: "application/json", - assertion: { - contents: """ - { "message" : "1 validation error detected. Value 7 at '/maxInteger' failed to satisfy constraint: Member must be less than or equal to 6", - "fieldList" : [{"message": "Value 7 at '/maxInteger' failed to satisfy constraint: Member must be less than or equal to 6", "path": "/maxInteger"}]}""" - } - } - } - }, - { - id: "RestJsonMalformedRangeLongOverride", - documentation: """ - When a long member does not fit within range bounds, - the response should be a 400 ValidationException.""", - protocol: restJson1, - request: { - method: "POST", - uri: "/MalformedRangeOverride", - body: """ - { "long" : $value:L }""", - headers: { - "content-type": "application/json" - } - }, - response: { - code: 400, - headers: { - "x-amzn-errortype": "ValidationException" - }, - body: { - mediaType: "application/json", - assertion: { - contents: """ - { "message" : "1 validation error detected. Value $value:L at '/long' failed to satisfy constraint: Member must be between 4 and 6, inclusive", - "fieldList" : [{"message": "Value $value:L at '/long' failed to satisfy constraint: Member must be between 4 and 6, inclusive", "path": "/long"}]}""" - } - } - }, - testParameters: { - value: ["3", "7"] - } - }, - { - id: "RestJsonMalformedRangeMinLongOverride", - documentation: """ - When a long member does not fit within range bounds, - the response should be a 400 ValidationException.""", - protocol: restJson1, - request: { - method: "POST", - uri: "/MalformedRangeOverride", - body: """ - { "minLong" : 3 }""", - headers: { - "content-type": "application/json" - } - }, - response: { - code: 400, - headers: { - "x-amzn-errortype": "ValidationException" - }, - body: { - mediaType: "application/json", - assertion: { - contents: """ - { "message" : "1 validation error detected. Value 3 at '/minLong' failed to satisfy constraint: Member must be greater than or equal to 4", - "fieldList" : [{"message": "Value 3 at '/minLong' failed to satisfy constraint: Member must be greater than or equal to 4", "path": "/minLong"}]}""" - } - } - } - }, - { - id: "RestJsonMalformedRangeMaxLongOverride", - documentation: """ - When a long member does not fit within range bounds, - the response should be a 400 ValidationException.""", - protocol: restJson1, - request: { - method: "POST", - uri: "/MalformedRangeOverride", - body: """ - { "maxLong" : 7 }""", - headers: { - "content-type": "application/json" - } - }, - response: { - code: 400, - headers: { - "x-amzn-errortype": "ValidationException" - }, - body: { - mediaType: "application/json", - assertion: { - contents: """ - { "message" : "1 validation error detected. Value 7 at '/maxLong' failed to satisfy constraint: Member must be less than or equal to 6", - "fieldList" : [{"message": "Value 7 at '/maxLong' failed to satisfy constraint: Member must be less than or equal to 6", "path": "/maxLong"}]}""" - } - } - } - }, -]) - -structure MalformedRangeInput { - short: RangeShort, - minShort: MinShort, - maxShort: MaxShort, - - integer: RangeInteger, - minInteger: MinInteger, - maxInteger: MaxInteger, - - long: RangeLong, - minLong: MinLong, - maxLong: MaxLong, -} - -structure MalformedRangeOverrideInput { - @range(min: 4, max: 6) - short: RangeShort, - @range(min: 4) - minShort: MinShort, - @range(max: 6) - maxShort: MaxShort, - - @range(min: 4, max: 6) - integer: RangeInteger, - @range(min: 4) - minInteger: MinInteger, - @range(max: 6) - maxInteger: MaxInteger, - - @range(min: 4, max: 6) - long: RangeLong, - @range(min: 4) - minLong: MinLong, - @range(max: 6) - maxLong: MaxLong, -} - -@range(min: 2, max: 8) -short RangeShort - -@range(min: 2) -short MinShort - -@range(max: 8) -short MaxShort - -@range(min: 2, max: 8) -integer RangeInteger - -@range(min: 2) -integer MinInteger - -@range(max: 8) -integer MaxInteger - -@range(min: 2, max: 8) -long RangeLong - -@range(min: 2) -long MinLong - -@range(max: 8) -long MaxLong diff --git a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/protocols/AwsJson.kt b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/protocols/AwsJson.kt index 9b5200b0c4..0476acdba5 100644 --- a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/protocols/AwsJson.kt +++ b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/protocols/AwsJson.kt @@ -79,15 +79,6 @@ class AwsJsonHttpBindingResolver( override fun errorResponseBindings(errorShape: ToShapeId): List = bindings(errorShape) - override fun timestampFormat( - memberShape: MemberShape, - location: HttpLocation, - defaultTimestampFormat: TimestampFormatTrait.Format, - ): TimestampFormatTrait.Format { - return memberShape.getMemberTrait(model, TimestampFormatTrait::class.java).map { it.format } - .orElse(defaultTimestampFormat) - } - override fun requestContentType(operationShape: OperationShape): String = "application/x-amz-json-${awsJsonVersion.value}" diff --git a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/protocols/HttpBindingResolver.kt b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/protocols/HttpBindingResolver.kt index 8dfd489c88..dbfe3de610 100644 --- a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/protocols/HttpBindingResolver.kt +++ b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/protocols/HttpBindingResolver.kt @@ -73,12 +73,17 @@ interface HttpBindingResolver { /** * Determine the timestamp format based on the input parameters. + * + * By default, this uses the timestamp trait, either on the member or on the target. */ fun timestampFormat( memberShape: MemberShape, location: HttpLocation, defaultTimestampFormat: TimestampFormatTrait.Format, - ): TimestampFormatTrait.Format + model: Model, + ): TimestampFormatTrait.Format = + memberShape.getMemberTrait(model, TimestampFormatTrait::class.java).map { it.format } + .orElse(defaultTimestampFormat) /** * Determines the request content type for given [operationShape]. @@ -132,6 +137,7 @@ open class HttpTraitHttpBindingResolver( memberShape: MemberShape, location: HttpLocation, defaultTimestampFormat: TimestampFormatTrait.Format, + model: Model, ): TimestampFormatTrait.Format = httpIndex.determineTimestampFormat(memberShape, location, defaultTimestampFormat) @@ -184,13 +190,4 @@ open class StaticHttpBindingResolver( override fun requestContentType(operationShape: OperationShape): String = requestContentType override fun responseContentType(operationShape: OperationShape): String = responseContentType - - override fun timestampFormat( - memberShape: MemberShape, - location: HttpLocation, - defaultTimestampFormat: TimestampFormatTrait.Format, - ): TimestampFormatTrait.Format { - return memberShape.getMemberTrait(model, TimestampFormatTrait::class.java).map { it.format } - .orElse(defaultTimestampFormat) - } } diff --git a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/protocols/parse/JsonParserGenerator.kt b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/protocols/parse/JsonParserGenerator.kt index 5a750844dd..754437a6a0 100644 --- a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/protocols/parse/JsonParserGenerator.kt +++ b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/protocols/parse/JsonParserGenerator.kt @@ -345,7 +345,7 @@ class JsonParserGenerator( val timestampFormat = httpBindingResolver.timestampFormat( member, HttpLocation.DOCUMENT, - TimestampFormatTrait.Format.EPOCH_SECONDS, + TimestampFormatTrait.Format.EPOCH_SECONDS, model, ) val timestampFormatType = RuntimeType.timestampFormat(runtimeConfig, timestampFormat) rustTemplate( diff --git a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/protocols/serialize/JsonSerializerGenerator.kt b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/protocols/serialize/JsonSerializerGenerator.kt index 8908907de6..ceb9c6f1e6 100644 --- a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/protocols/serialize/JsonSerializerGenerator.kt +++ b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/protocols/serialize/JsonSerializerGenerator.kt @@ -397,7 +397,7 @@ class JsonSerializerGenerator( is TimestampShape -> { val timestampFormat = - httpBindingResolver.timestampFormat(context.shape, HttpLocation.DOCUMENT, EPOCH_SECONDS) + httpBindingResolver.timestampFormat(context.shape, HttpLocation.DOCUMENT, EPOCH_SECONDS, model) val timestampFormatType = RuntimeType.timestampFormat(runtimeConfig, timestampFormat) rust("$writer.date_time(${value.asRef()}, #T)?;", timestampFormatType) } diff --git a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/protocols/serialize/XmlBindingTraitSerializerGenerator.kt b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/protocols/serialize/XmlBindingTraitSerializerGenerator.kt index 4c2697f5c8..e24c3ed2ec 100644 --- a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/protocols/serialize/XmlBindingTraitSerializerGenerator.kt +++ b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/protocols/serialize/XmlBindingTraitSerializerGenerator.kt @@ -120,7 +120,9 @@ class XmlBindingTraitSerializerGenerator( """ let mut writer = #{XmlWriter}::new(&mut out); ##[allow(unused_mut)] - let mut root = writer.start_el(${operationXmlName.dq()})${inputShape.xmlNamespace(root = true).apply()}; + let mut root = writer.start_el(${operationXmlName.dq()})${ + inputShape.xmlNamespace(root = true).apply() + }; """, *codegenScope, ) @@ -164,6 +166,7 @@ class XmlBindingTraitSerializerGenerator( XmlMemberIndex.fromMembers(target.members().toList()), Ctx.Element("root", "input"), ) + is UnionShape -> serializeUnion(target, Ctx.Element("root", "input")) else -> throw IllegalStateException("xml payloadSerializer only supports structs and unions") } @@ -208,7 +211,9 @@ class XmlBindingTraitSerializerGenerator( """ let mut writer = #{XmlWriter}::new(&mut out); ##[allow(unused_mut)] - let mut root = writer.start_el(${operationXmlName.dq()})${outputShape.xmlNamespace(root = true).apply()}; + let mut root = writer.start_el(${operationXmlName.dq()})${ + outputShape.xmlNamespace(root = true).apply() + }; """, *codegenScope, ) @@ -291,23 +296,26 @@ class XmlBindingTraitSerializerGenerator( } rust("$dereferenced.as_str()") } + is BooleanShape, is NumberShape -> { rust( "#T::from(${autoDeref(input)}).encode()", RuntimeType.smithyTypes(runtimeConfig).resolve("primitive::Encoder"), ) } + is BlobShape -> rust("#T($input.as_ref()).as_ref()", RuntimeType.base64Encode(runtimeConfig)) is TimestampShape -> { val timestampFormat = httpBindingResolver.timestampFormat( member, HttpLocation.DOCUMENT, - TimestampFormatTrait.Format.DATE_TIME, + TimestampFormatTrait.Format.DATE_TIME, model, ) val timestampFormatType = RuntimeType.timestampFormat(runtimeConfig, timestampFormat) rust("$input.fmt(#T)?.as_ref()", timestampFormatType) } + else -> TODO(member.toString()) } } @@ -325,18 +333,21 @@ class XmlBindingTraitSerializerGenerator( serializeRawMember(memberShape, ctx.input) } } + is CollectionShape -> if (memberShape.hasTrait()) { serializeFlatList(memberShape, target, ctx) } else { rust("let mut inner_writer = ${ctx.scopeWriter}.start_el(${xmlName.dq()})$ns.finish();") serializeList(target, Ctx.Scope("inner_writer", ctx.input)) } + is MapShape -> if (memberShape.hasTrait()) { serializeMap(target, xmlIndex.memberName(memberShape), ctx) } else { rust("let mut inner_writer = ${ctx.scopeWriter}.start_el(${xmlName.dq()})$ns.finish();") serializeMap(target, "entry", Ctx.Scope("inner_writer", ctx.input)) } + is StructureShape -> { // We call serializeStructure only when target.members() is nonempty. // If it were empty, serializeStructure would generate the following code: @@ -363,10 +374,12 @@ class XmlBindingTraitSerializerGenerator( ) } } + is UnionShape -> { rust("let inner_writer = ${ctx.scopeWriter}.start_el(${xmlName.dq()})$ns;") serializeUnion(target, Ctx.Element("inner_writer", ctx.input)) } + else -> TODO(target.toString()) } } diff --git a/codegen-server-test/build.gradle.kts b/codegen-server-test/build.gradle.kts index d8ec164a40..693ed13c82 100644 --- a/codegen-server-test/build.gradle.kts +++ b/codegen-server-test/build.gradle.kts @@ -77,12 +77,6 @@ val allCodegenTests = "../codegen-core/common-test-models".let { commonModels -> "rest_json_validation", extraConfig = """, "codegen": { "ignoreUnsupportedConstraints": true } """, ), - CodegenTest( - "aws.protocoltests.extras.restjson.validation#MalformedRangeValidation", - "malformed_range_extras", - extraConfig = """, "codegen": { "ignoreUnsupportedConstraints": true } """, - imports = listOf("$commonModels/malformed-range-extras.smithy"), - ), CodegenTest("aws.protocoltests.json10#JsonRpc10", "json_rpc10"), CodegenTest( "aws.protocoltests.json#JsonProtocol", From 812e1bbf2a0d8f5b8476508e79340446ca00a53b Mon Sep 17 00:00:00 2001 From: Russell Cohen Date: Thu, 19 Jan 2023 12:46:54 -0500 Subject: [PATCH 06/11] Update changelog --- CHANGELOG.next.toml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CHANGELOG.next.toml b/CHANGELOG.next.toml index 33b3c84abe..c1e7395611 100644 --- a/CHANGELOG.next.toml +++ b/CHANGELOG.next.toml @@ -287,3 +287,15 @@ message = "The modules in generated client crates have been reorganized. See the references = ["smithy-rs#2448"] meta = { "breaking" = true, "tada" = false, "bug" = false, "target" = "client" } author = "jdisanti" + +[[smithy-rs]] +message = "Fix bug in timestamp format resolution. Prior to this fix, the timestamp format may have been incorrect if set on the target instead of on the member." +references = ["smithy-rs#2226"] +meta = { "breaking" = false, "tada" = false, "bug" = true, "target" = "all" } +author = "rcoh" + +[[smithy-rs]] +message = "Add support for offsets when parsing datetimes. RFC3339 date times now support offsets like `-0200`" +references = ["smithy-rs#2226"] +meta = { "breaking" = false, "tada" = false, "bug" = false, "target" = "client" } +author = "rcoh" From b919fca674d50912f5aa2d15a1c1df4ed5645742 Mon Sep 17 00:00:00 2001 From: Russell Cohen Date: Thu, 19 Jan 2023 12:55:06 -0500 Subject: [PATCH 07/11] rename test --- rust-runtime/aws-smithy-types/src/date_time/format.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust-runtime/aws-smithy-types/src/date_time/format.rs b/rust-runtime/aws-smithy-types/src/date_time/format.rs index 41371ed64e..9bed7a7db6 100644 --- a/rust-runtime/aws-smithy-types/src/date_time/format.rs +++ b/rust-runtime/aws-smithy-types/src/date_time/format.rs @@ -660,7 +660,7 @@ mod tests { } #[test] - fn parse_rfc3339_timezone_forbidden() { + fn parse_rfc3339_with_timezone() { let dt = rfc3339::parse("1985-04-12T21:20:51-02:00"); assert_eq!(dt.unwrap(), DateTime::from_secs_and_nanos(482196051, 0)); } From e96790b9907074e4293e5fbacd5caafe41864ace Mon Sep 17 00:00:00 2001 From: Russell Cohen Date: Fri, 20 Jan 2023 10:30:11 -0500 Subject: [PATCH 08/11] fix datetime parsing on server and clients --- .../http/RequestBindingGenerator.kt | 4 +- .../rust/codegen/core/smithy/RuntimeType.kt | 25 ++++++++-- .../generators/http/HttpBindingGenerator.kt | 4 +- .../protocols/parse/JsonParserGenerator.kt | 4 +- .../parse/XmlBindingTraitParserGenerator.kt | 3 +- .../serialize/JsonSerializerGenerator.kt | 10 +++- .../serialize/QuerySerializerGenerator.kt | 2 +- .../XmlBindingTraitSerializerGenerator.kt | 2 +- .../ServerHttpBoundProtocolGenerator.kt | 5 +- .../aws-smithy-json/src/deserialize/token.rs | 10 ++-- .../aws-smithy-types/src/date_time/format.rs | 49 ++++++++++++++++--- .../aws-smithy-types/src/date_time/mod.rs | 16 ++++-- 12 files changed, 103 insertions(+), 31 deletions(-) diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/http/RequestBindingGenerator.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/http/RequestBindingGenerator.kt index b426ee9063..1d3f38a884 100644 --- a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/http/RequestBindingGenerator.kt +++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/http/RequestBindingGenerator.kt @@ -273,7 +273,7 @@ class RequestBindingGenerator( target.isTimestampShape -> { val timestampFormat = index.determineTimestampFormat(member, HttpBinding.Location.QUERY, protocol.defaultTimestampFormat) - val timestampFormatType = RuntimeType.timestampFormat(runtimeConfig, timestampFormat) + val timestampFormatType = RuntimeType.serializeTimestampFormat(runtimeConfig, timestampFormat) val func = writer.format(RuntimeType.queryFormat(runtimeConfig, "fmt_timestamp")) "&$func($targetName, ${writer.format(timestampFormatType)})?" } @@ -314,7 +314,7 @@ class RequestBindingGenerator( target.isTimestampShape -> { val timestampFormat = index.determineTimestampFormat(member, HttpBinding.Location.LABEL, protocol.defaultTimestampFormat) - val timestampFormatType = RuntimeType.timestampFormat(runtimeConfig, timestampFormat) + val timestampFormatType = RuntimeType.serializeTimestampFormat(runtimeConfig, timestampFormat) val func = format(RuntimeType.labelFormat(runtimeConfig, "fmt_timestamp")) rust("let $outputVar = $func($input, ${format(timestampFormatType)})?;") } diff --git a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/RuntimeType.kt b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/RuntimeType.kt index 219cb33c03..66ae833bb4 100644 --- a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/RuntimeType.kt +++ b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/RuntimeType.kt @@ -300,9 +300,29 @@ data class RuntimeType(val path: String, val dependency: RustDependency? = null) fun sdkSuccess(runtimeConfig: RuntimeConfig): RuntimeType = smithyHttp(runtimeConfig).resolve("result::SdkSuccess") - fun timestampFormat(runtimeConfig: RuntimeConfig, format: TimestampFormatTrait.Format): RuntimeType { + fun parseTimestampFormat( + codegenTarget: CodegenTarget, + runtimeConfig: RuntimeConfig, + format: TimestampFormatTrait.Format, + ): RuntimeType { val timestampFormat = when (format) { TimestampFormatTrait.Format.EPOCH_SECONDS -> "EpochSeconds" + // clients allow offsets, servers do nt + TimestampFormatTrait.Format.DATE_TIME -> codegenTarget.ifClient { "DateTimeWithOffset" } ?: "DateTime" + TimestampFormatTrait.Format.HTTP_DATE -> "HttpDate" + TimestampFormatTrait.Format.UNKNOWN -> TODO() + } + + return smithyTypes(runtimeConfig).resolve("date_time::Format::$timestampFormat") + } + + fun serializeTimestampFormat( + runtimeConfig: RuntimeConfig, + format: TimestampFormatTrait.Format, + ): RuntimeType { + val timestampFormat = when (format) { + TimestampFormatTrait.Format.EPOCH_SECONDS -> "EpochSeconds" + // clients allow offsets, servers do not TimestampFormatTrait.Format.DATE_TIME -> "DateTime" TimestampFormatTrait.Format.HTTP_DATE -> "HttpDate" TimestampFormatTrait.Format.UNKNOWN -> TODO() @@ -314,8 +334,7 @@ data class RuntimeType(val path: String, val dependency: RustDependency? = null) fun captureRequest(runtimeConfig: RuntimeConfig) = CargoDependency.smithyClientTestUtil(runtimeConfig).toType().resolve("test_connection::capture_request") - fun forInlineDependency(inlineDependency: InlineDependency) = - RuntimeType("crate::${inlineDependency.name}", inlineDependency) + fun forInlineDependency(inlineDependency: InlineDependency) = RuntimeType("crate::${inlineDependency.name}", inlineDependency) fun forInlineFun(name: String, module: RustModule, func: Writable) = RuntimeType( "${module.fullyQualifiedPath()}::$name", diff --git a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/http/HttpBindingGenerator.kt b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/http/HttpBindingGenerator.kt index 6a919a7f59..189362e2cd 100644 --- a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/http/HttpBindingGenerator.kt +++ b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/http/HttpBindingGenerator.kt @@ -359,7 +359,7 @@ class HttpBindingGenerator( HttpBinding.Location.HEADER, defaultTimestampFormat, ) - val timestampFormatType = RuntimeType.timestampFormat(runtimeConfig, timestampFormat) + val timestampFormatType = RuntimeType.parseTimestampFormat(codegenTarget, runtimeConfig, timestampFormat) rust( "let $parsedValue: Vec<${coreType.render()}> = #T::many_dates(headers, #T)?;", headerUtil, @@ -737,7 +737,7 @@ class HttpBindingGenerator( } target.isTimestampShape -> { - val timestampFormatType = RuntimeType.timestampFormat(runtimeConfig, timestampFormat) + val timestampFormatType = RuntimeType.serializeTimestampFormat(runtimeConfig, timestampFormat) quoteValue("$targetName.fmt(${writer.format(timestampFormatType)})?") } diff --git a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/protocols/parse/JsonParserGenerator.kt b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/protocols/parse/JsonParserGenerator.kt index 754437a6a0..dd7255e47e 100644 --- a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/protocols/parse/JsonParserGenerator.kt +++ b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/protocols/parse/JsonParserGenerator.kt @@ -71,7 +71,7 @@ typealias JsonParserCustomization = NamedCustomization data class ReturnSymbolToParse(val symbol: Symbol, val isUnconstrained: Boolean) class JsonParserGenerator( - codegenContext: CodegenContext, + private val codegenContext: CodegenContext, private val httpBindingResolver: HttpBindingResolver, /** Function that maps a MemberShape into a JSON field name */ private val jsonName: (MemberShape) -> String, @@ -347,7 +347,7 @@ class JsonParserGenerator( member, HttpLocation.DOCUMENT, TimestampFormatTrait.Format.EPOCH_SECONDS, model, ) - val timestampFormatType = RuntimeType.timestampFormat(runtimeConfig, timestampFormat) + val timestampFormatType = RuntimeType.parseTimestampFormat(codegenTarget, runtimeConfig, timestampFormat) rustTemplate( "#{expect_timestamp_or_null}(tokens.next(), #{T})?#{ConvertFrom:W}", "T" to timestampFormatType, "ConvertFrom" to typeConversionGenerator.convertViaFrom(shape), *codegenScope, diff --git a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/protocols/parse/XmlBindingTraitParserGenerator.kt b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/protocols/parse/XmlBindingTraitParserGenerator.kt index 56449cb56b..d083d0e901 100644 --- a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/protocols/parse/XmlBindingTraitParserGenerator.kt +++ b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/protocols/parse/XmlBindingTraitParserGenerator.kt @@ -100,6 +100,7 @@ class XmlBindingTraitParserGenerator( private val scopedDecoder = smithyXml.resolve("decode::ScopedDecoder") private val runtimeConfig = codegenContext.runtimeConfig private val protocolFunctions = ProtocolFunctions(codegenContext) + private val codegenTarget = codegenContext.target // The symbols we want all the time private val codegenScope = arrayOf( @@ -628,7 +629,7 @@ class XmlBindingTraitParserGenerator( HttpBinding.Location.DOCUMENT, TimestampFormatTrait.Format.DATE_TIME, ) - val timestampFormatType = RuntimeType.timestampFormat(runtimeConfig, timestampFormat) + val timestampFormatType = RuntimeType.parseTimestampFormat(codegenTarget, runtimeConfig, timestampFormat) withBlock("#T::from_str(", ")", RuntimeType.dateTime(runtimeConfig)) { provider() rust(", #T", timestampFormatType) diff --git a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/protocols/serialize/JsonSerializerGenerator.kt b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/protocols/serialize/JsonSerializerGenerator.kt index ceb9c6f1e6..5ee20b5bd0 100644 --- a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/protocols/serialize/JsonSerializerGenerator.kt +++ b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/protocols/serialize/JsonSerializerGenerator.kt @@ -37,6 +37,7 @@ import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType import software.amazon.smithy.rust.codegen.core.smithy.RustSymbolProvider import software.amazon.smithy.rust.codegen.core.smithy.customize.NamedCustomization import software.amazon.smithy.rust.codegen.core.smithy.customize.Section +import software.amazon.smithy.rust.codegen.core.smithy.generators.TypeConversionGenerator import software.amazon.smithy.rust.codegen.core.smithy.generators.UnionGenerator import software.amazon.smithy.rust.codegen.core.smithy.generators.renderUnknownVariant import software.amazon.smithy.rust.codegen.core.smithy.generators.serializationError @@ -166,6 +167,7 @@ class JsonSerializerGenerator( private val symbolProvider = codegenContext.symbolProvider private val codegenTarget = codegenContext.target private val runtimeConfig = codegenContext.runtimeConfig + private val typeConversionGenerator = TypeConversionGenerator(model, symbolProvider, runtimeConfig) private val protocolFunctions = ProtocolFunctions(codegenContext) private val codegenScope = arrayOf( "String" to RuntimeType.String, @@ -398,8 +400,12 @@ class JsonSerializerGenerator( is TimestampShape -> { val timestampFormat = httpBindingResolver.timestampFormat(context.shape, HttpLocation.DOCUMENT, EPOCH_SECONDS, model) - val timestampFormatType = RuntimeType.timestampFormat(runtimeConfig, timestampFormat) - rust("$writer.date_time(${value.asRef()}, #T)?;", timestampFormatType) + val timestampFormatType = RuntimeType.serializeTimestampFormat(runtimeConfig, timestampFormat) + rustTemplate( + "$writer.date_time(${value.asRef()}#{ConvertInto:W}, #{FormatType})?;", + "FormatType" to timestampFormatType, + "ConvertInto" to typeConversionGenerator.convertViaInto(target), + ) } is CollectionShape -> jsonArrayWriter(context) { arrayName -> diff --git a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/protocols/serialize/QuerySerializerGenerator.kt b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/protocols/serialize/QuerySerializerGenerator.kt index df2eed8e24..974e112cde 100644 --- a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/protocols/serialize/QuerySerializerGenerator.kt +++ b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/protocols/serialize/QuerySerializerGenerator.kt @@ -235,7 +235,7 @@ abstract class QuerySerializerGenerator(codegenContext: CodegenContext) : Struct ) is TimestampShape -> { val timestampFormat = determineTimestampFormat(context.shape) - val timestampFormatType = RuntimeType.timestampFormat(runtimeConfig, timestampFormat) + val timestampFormatType = RuntimeType.serializeTimestampFormat(runtimeConfig, timestampFormat) rust("$writer.date_time(${value.name}, #T)?;", timestampFormatType) } is CollectionShape -> serializeCollection(context, Context(writer, context.valueExpression, target)) diff --git a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/protocols/serialize/XmlBindingTraitSerializerGenerator.kt b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/protocols/serialize/XmlBindingTraitSerializerGenerator.kt index e24c3ed2ec..7740a3978f 100644 --- a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/protocols/serialize/XmlBindingTraitSerializerGenerator.kt +++ b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/protocols/serialize/XmlBindingTraitSerializerGenerator.kt @@ -312,7 +312,7 @@ class XmlBindingTraitSerializerGenerator( HttpLocation.DOCUMENT, TimestampFormatTrait.Format.DATE_TIME, model, ) - val timestampFormatType = RuntimeType.timestampFormat(runtimeConfig, timestampFormat) + val timestampFormatType = RuntimeType.parseTimestampFormat(codegenTarget, runtimeConfig, timestampFormat) rust("$input.fmt(#T)?.as_ref()", timestampFormatType) } diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/protocols/ServerHttpBoundProtocolGenerator.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/protocols/ServerHttpBoundProtocolGenerator.kt index 9b6d3f24a8..16441ac0c2 100644 --- a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/protocols/ServerHttpBoundProtocolGenerator.kt +++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/protocols/ServerHttpBoundProtocolGenerator.kt @@ -40,6 +40,7 @@ import software.amazon.smithy.rust.codegen.core.rustlang.stripOuter import software.amazon.smithy.rust.codegen.core.rustlang.withBlock import software.amazon.smithy.rust.codegen.core.rustlang.withBlockTemplate import software.amazon.smithy.rust.codegen.core.rustlang.writable +import software.amazon.smithy.rust.codegen.core.smithy.CodegenTarget import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType import software.amazon.smithy.rust.codegen.core.smithy.customize.OperationCustomization import software.amazon.smithy.rust.codegen.core.smithy.generators.TypeConversionGenerator @@ -945,7 +946,7 @@ private class ServerHttpBoundProtocolTraitImplGenerator( it.location, protocol.defaultTimestampFormat, ) - val timestampFormatType = RuntimeType.timestampFormat(runtimeConfig, timestampFormat) + val timestampFormatType = RuntimeType.parseTimestampFormat(CodegenTarget.SERVER, runtimeConfig, timestampFormat) rustTemplate( """ let v = #{DateTime}::from_str(&v, #{format})?#{ConvertInto:W}; @@ -1099,7 +1100,7 @@ private class ServerHttpBoundProtocolTraitImplGenerator( binding.location, protocol.defaultTimestampFormat, ) - val timestampFormatType = RuntimeType.timestampFormat(runtimeConfig, timestampFormat) + val timestampFormatType = RuntimeType.parseTimestampFormat(CodegenTarget.SERVER, runtimeConfig, timestampFormat) if (percentDecoding) { rustTemplate( diff --git a/rust-runtime/aws-smithy-json/src/deserialize/token.rs b/rust-runtime/aws-smithy-json/src/deserialize/token.rs index 0bed404d8d..f42fdafbda 100644 --- a/rust-runtime/aws-smithy-json/src/deserialize/token.rs +++ b/rust-runtime/aws-smithy-json/src/deserialize/token.rs @@ -216,10 +216,12 @@ pub fn expect_timestamp_or_null( } }) .transpose()?, - Format::DateTime | Format::HttpDate => expect_string_or_null(token)? - .map(|v| DateTime::from_str(v.as_escaped_str(), timestamp_format)) - .transpose() - .map_err(|err| Error::custom_source("failed to parse timestamp", err))?, + Format::DateTime | Format::HttpDate | Format::DateTimeWithOffset => { + expect_string_or_null(token)? + .map(|v| DateTime::from_str(v.as_escaped_str(), timestamp_format)) + .transpose() + .map_err(|err| Error::custom_source("failed to parse timestamp", err))? + } }) } diff --git a/rust-runtime/aws-smithy-types/src/date_time/format.rs b/rust-runtime/aws-smithy-types/src/date_time/format.rs index 9bed7a7db6..a259371ce1 100644 --- a/rust-runtime/aws-smithy-types/src/date_time/format.rs +++ b/rust-runtime/aws-smithy-types/src/date_time/format.rs @@ -399,12 +399,28 @@ pub(crate) mod rfc3339 { use time::format_description::well_known::Rfc3339; use time::OffsetDateTime; + #[derive(Debug, PartialEq)] + pub(crate) enum AllowOffsets { + OffsetsAllowed, + OffsetsForbidden, + } + // OK: 1985-04-12T23:20:50.52Z // OK: 1985-04-12T23:20:50Z // // Timezones not supported: // Not OK: 1985-04-12T23:20:50-02:00 - pub(crate) fn parse(s: &str) -> Result { + pub(crate) fn parse( + s: &str, + allow_offsets: AllowOffsets, + ) -> Result { + if allow_offsets == AllowOffsets::OffsetsForbidden && !matches!(s.chars().last(), Some('Z')) + { + return Err(DateTimeParseErrorKind::Invalid( + "Smithy does not support timezone offsets in RFC-3339 date times".into(), + ) + .into()); + } let date_time = OffsetDateTime::parse(s, &Rfc3339).map_err(|err| { DateTimeParseErrorKind::Invalid(format!("invalid RFC-3339 date-time: {}", err).into()) })?; @@ -413,10 +429,13 @@ pub(crate) mod rfc3339 { } /// Read 1 RFC-3339 date from &str and return the remaining str - pub(crate) fn read(s: &str) -> Result<(DateTime, &str), DateTimeParseError> { + pub(crate) fn read( + s: &str, + allow_offests: AllowOffsets, + ) -> Result<(DateTime, &str), DateTimeParseError> { let delim = s.find('Z').map(|idx| idx + 1).unwrap_or_else(|| s.len()); let (head, rest) = s.split_at(delim); - Ok((parse(head)?, rest)) + Ok((parse(head, allow_offests)?, rest)) } /// Format a [DateTime] in the RFC-3339 date format @@ -485,6 +504,7 @@ pub(crate) mod rfc3339 { #[cfg(test)] mod tests { use super::*; + use crate::date_time::format::rfc3339::AllowOffsets; use crate::DateTime; use lazy_static::lazy_static; use proptest::prelude::*; @@ -628,7 +648,9 @@ mod tests { #[test] fn parse_date_time() { - parse_test(&TEST_CASES.parse_date_time, rfc3339::parse); + parse_test(&TEST_CASES.parse_date_time, |date| { + rfc3339::parse(date, AllowOffsets::OffsetsForbidden) + }); } #[test] @@ -649,8 +671,10 @@ mod tests { #[test] fn read_rfc3339_date_comma_split() { let date = "1985-04-12T23:20:50Z,1985-04-12T23:20:51Z"; - let (e1, date) = rfc3339::read(date).expect("should succeed"); - let (e2, date2) = rfc3339::read(&date[1..]).expect("should succeed"); + let (e1, date) = + rfc3339::read(date, AllowOffsets::OffsetsForbidden).expect("should succeed"); + let (e2, date2) = + rfc3339::read(&date[1..], AllowOffsets::OffsetsForbidden).expect("should succeed"); assert_eq!(date2, ""); assert_eq!(date, ",1985-04-12T23:20:51Z"); let expected = DateTime::from_secs_and_nanos(482196050, 0); @@ -661,10 +685,21 @@ mod tests { #[test] fn parse_rfc3339_with_timezone() { - let dt = rfc3339::parse("1985-04-12T21:20:51-02:00"); + let dt = rfc3339::parse("1985-04-12T21:20:51-02:00", AllowOffsets::OffsetsAllowed); assert_eq!(dt.unwrap(), DateTime::from_secs_and_nanos(482196051, 0)); } + #[test] + fn parse_rfc3339_timezone_forbidden() { + let dt = rfc3339::parse("1985-04-12T23:20:50-02:00", AllowOffsets::OffsetsForbidden); + assert!(matches!( + dt.unwrap_err(), + DateTimeParseError { + kind: DateTimeParseErrorKind::Invalid(_) + } + )); + } + #[test] fn http_date_out_of_range() { assert_eq!( diff --git a/rust-runtime/aws-smithy-types/src/date_time/mod.rs b/rust-runtime/aws-smithy-types/src/date_time/mod.rs index accb788046..0459619998 100644 --- a/rust-runtime/aws-smithy-types/src/date_time/mod.rs +++ b/rust-runtime/aws-smithy-types/src/date_time/mod.rs @@ -5,6 +5,7 @@ //! DateTime type for representing Smithy timestamps. +use crate::date_time::format::rfc3339::AllowOffsets; use crate::date_time::format::DateTimeParseErrorKind; use num_integer::div_mod_floor; use num_integer::Integer; @@ -155,7 +156,8 @@ impl DateTime { /// Parses a `DateTime` from a string using the given `format`. pub fn from_str(s: &str, format: Format) -> Result { match format { - Format::DateTime => format::rfc3339::parse(s), + Format::DateTime => format::rfc3339::parse(s, AllowOffsets::OffsetsForbidden), + Format::DateTimeWithOffset => format::rfc3339::parse(s, AllowOffsets::OffsetsAllowed), Format::HttpDate => format::http_date::parse(s), Format::EpochSeconds => format::epoch_seconds::parse(s), } @@ -207,7 +209,8 @@ impl DateTime { /// Enable parsing multiple dates from the same string pub fn read(s: &str, format: Format, delim: char) -> Result<(Self, &str), DateTimeParseError> { let (inst, next) = match format { - Format::DateTime => format::rfc3339::read(s)?, + Format::DateTime => format::rfc3339::read(s, AllowOffsets::OffsetsForbidden)?, + Format::DateTimeWithOffset => format::rfc3339::read(s, AllowOffsets::OffsetsAllowed)?, Format::HttpDate => format::http_date::read(s)?, Format::EpochSeconds => { let split_point = s.find(delim).unwrap_or(s.len()); @@ -229,7 +232,7 @@ impl DateTime { /// Returns an error if the given `DateTime` cannot be represented by the desired format. pub fn fmt(&self, format: Format) -> Result { match format { - Format::DateTime => format::rfc3339::format(self), + Format::DateTime | Format::DateTimeWithOffset => format::rfc3339::format(self), Format::EpochSeconds => Ok(format::epoch_seconds::format(self)), Format::HttpDate => format::http_date::format(self), } @@ -314,10 +317,15 @@ impl fmt::Display for ConversionError { /// Formats for representing a `DateTime` in the Smithy protocols. #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum Format { - /// RFC-3339 Date Time. + /// RFC-3339 Date Time. If the date time has an offset, an error will be returned DateTime, + + /// RFC-3339 Date Time. Offsets are supported + DateTimeWithOffset, + /// Date format used by the HTTP `Date` header, specified in RFC-7231. HttpDate, + /// Number of seconds since the Unix epoch formatted as a floating point. EpochSeconds, } From f59ad8d923b7dfd2eec4a985e61192bfd22969df Mon Sep 17 00:00:00 2001 From: Russell Cohen Date: Mon, 6 Feb 2023 10:16:35 -0500 Subject: [PATCH 09/11] fix up a few more failing tests --- .../generators/protocol/ServerProtocolTestGenerator.kt | 7 +++---- gradle.properties | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/protocol/ServerProtocolTestGenerator.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/protocol/ServerProtocolTestGenerator.kt index 06d5333562..2d26fc4bef 100644 --- a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/protocol/ServerProtocolTestGenerator.kt +++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/protocol/ServerProtocolTestGenerator.kt @@ -759,9 +759,6 @@ class ServerProtocolTestGenerator( private const val RestJsonValidation = "aws.protocoltests.restjson.validation#RestJsonValidation" private const val MalformedRangeValidation = "aws.protocoltests.extras.restjson.validation#MalformedRangeValidation" private val ExpectFail: Set = setOf( - // Pending merge from the Smithy team: see https://github.com/awslabs/smithy/pull/1477. - FailingTest(RestJson, "RestJsonWithPayloadExpectsImpliedContentType", TestType.MalformedRequest), - // Pending resolution from the Smithy team, see https://github.com/awslabs/smithy/issues/1068. FailingTest(RestJson, "RestJsonHttpWithHeadersButNoPayload", TestType.Request), @@ -838,6 +835,8 @@ class ServerProtocolTestGenerator( "queryTimestamp": 1, "queryTimestampList": [1, 2, 3], "queryEnum": "Foo", + "queryIntegerEnum": 1, + "queryIntegerEnumList": [1,2,3], "queryEnumList": ["Foo", "Baz", "Bar"], "queryParamsMapOfStringList": { "String": ["Hello there"], @@ -915,7 +914,7 @@ class ServerProtocolTestGenerator( // TODO(https://github.com/awslabs/smithy-rs/issues/1288): Contribute a PR to fix them upstream. private val BrokenRequestTests = mapOf( // TODO(https://github.com/awslabs/smithy/pull/1564) - Pair(RestJson, "RestJsonAllQueryStringTypes") to ::fixRestJsonAllQueryStringTypes, + // Pair(RestJson, "RestJsonAllQueryStringTypes") to ::fixRestJsonAllQueryStringTypes, // TODO(https://github.com/awslabs/smithy/pull/1562) Pair(RestJson, "RestJsonQueryStringEscaping") to ::fixRestJsonQueryStringEscaping, ) diff --git a/gradle.properties b/gradle.properties index 910aedf3df..fc748a383b 100644 --- a/gradle.properties +++ b/gradle.properties @@ -18,7 +18,7 @@ kotlin.code.style=official # codegen smithyGradlePluginVersion=0.6.0 -smithyVersion=1.27.1 +smithyVersion=1.27.2 # kotlin kotlinVersion=1.7.21 From 4b461cc59e42ce6c99f6c2051bc3500a4e3a8cd9 Mon Sep 17 00:00:00 2001 From: Russell Cohen Date: Tue, 14 Mar 2023 14:19:34 -0400 Subject: [PATCH 10/11] precommit lints --- .../amazon/smithy/rustsdk/endpoints/AwsEndpointDecorator.kt | 2 -- .../protocols/serialize/XmlBindingTraitSerializerGenerator.kt | 4 ++-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/endpoints/AwsEndpointDecorator.kt b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/endpoints/AwsEndpointDecorator.kt index 4a5311cd43..fc2e811bf4 100644 --- a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/endpoints/AwsEndpointDecorator.kt +++ b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/endpoints/AwsEndpointDecorator.kt @@ -22,11 +22,9 @@ import software.amazon.smithy.rust.codegen.client.smithy.featureGatedConfigModul import software.amazon.smithy.rust.codegen.client.smithy.generators.config.ConfigCustomization import software.amazon.smithy.rust.codegen.client.smithy.generators.config.ServiceConfig import software.amazon.smithy.rust.codegen.core.rustlang.CargoDependency -import software.amazon.smithy.rust.codegen.core.rustlang.Writable import software.amazon.smithy.rust.codegen.core.rustlang.rust import software.amazon.smithy.rust.codegen.core.rustlang.rustTemplate import software.amazon.smithy.rust.codegen.core.rustlang.writable -import software.amazon.smithy.rust.codegen.core.smithy.CodegenContext import software.amazon.smithy.rust.codegen.core.smithy.RustCrate import software.amazon.smithy.rust.codegen.core.smithy.customize.AdHocCustomization import software.amazon.smithy.rust.codegen.core.smithy.customize.adhocCustomization diff --git a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/protocols/serialize/XmlBindingTraitSerializerGenerator.kt b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/protocols/serialize/XmlBindingTraitSerializerGenerator.kt index 7740a3978f..678768cfa7 100644 --- a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/protocols/serialize/XmlBindingTraitSerializerGenerator.kt +++ b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/protocols/serialize/XmlBindingTraitSerializerGenerator.kt @@ -121,7 +121,7 @@ class XmlBindingTraitSerializerGenerator( let mut writer = #{XmlWriter}::new(&mut out); ##[allow(unused_mut)] let mut root = writer.start_el(${operationXmlName.dq()})${ - inputShape.xmlNamespace(root = true).apply() + inputShape.xmlNamespace(root = true).apply() }; """, *codegenScope, @@ -212,7 +212,7 @@ class XmlBindingTraitSerializerGenerator( let mut writer = #{XmlWriter}::new(&mut out); ##[allow(unused_mut)] let mut root = writer.start_el(${operationXmlName.dq()})${ - outputShape.xmlNamespace(root = true).apply() + outputShape.xmlNamespace(root = true).apply() }; """, *codegenScope, From 79a645c57418b79d24cefc60d90bbde11781d6eb Mon Sep 17 00:00:00 2001 From: Russell Cohen Date: Tue, 14 Mar 2023 15:28:53 -0400 Subject: [PATCH 11/11] Fix bad merge --- .../smithy/protocols/serialize/JsonSerializerGenerator.kt | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/protocols/serialize/JsonSerializerGenerator.kt b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/protocols/serialize/JsonSerializerGenerator.kt index 5ee20b5bd0..33b06973b0 100644 --- a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/protocols/serialize/JsonSerializerGenerator.kt +++ b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/protocols/serialize/JsonSerializerGenerator.kt @@ -37,7 +37,6 @@ import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType import software.amazon.smithy.rust.codegen.core.smithy.RustSymbolProvider import software.amazon.smithy.rust.codegen.core.smithy.customize.NamedCustomization import software.amazon.smithy.rust.codegen.core.smithy.customize.Section -import software.amazon.smithy.rust.codegen.core.smithy.generators.TypeConversionGenerator import software.amazon.smithy.rust.codegen.core.smithy.generators.UnionGenerator import software.amazon.smithy.rust.codegen.core.smithy.generators.renderUnknownVariant import software.amazon.smithy.rust.codegen.core.smithy.generators.serializationError @@ -167,7 +166,6 @@ class JsonSerializerGenerator( private val symbolProvider = codegenContext.symbolProvider private val codegenTarget = codegenContext.target private val runtimeConfig = codegenContext.runtimeConfig - private val typeConversionGenerator = TypeConversionGenerator(model, symbolProvider, runtimeConfig) private val protocolFunctions = ProtocolFunctions(codegenContext) private val codegenScope = arrayOf( "String" to RuntimeType.String, @@ -402,9 +400,8 @@ class JsonSerializerGenerator( httpBindingResolver.timestampFormat(context.shape, HttpLocation.DOCUMENT, EPOCH_SECONDS, model) val timestampFormatType = RuntimeType.serializeTimestampFormat(runtimeConfig, timestampFormat) rustTemplate( - "$writer.date_time(${value.asRef()}#{ConvertInto:W}, #{FormatType})?;", + "$writer.date_time(${value.asRef()}, #{FormatType})?;", "FormatType" to timestampFormatType, - "ConvertInto" to typeConversionGenerator.convertViaInto(target), ) }