From c45d0b010f7ac62038318b05797af2cb9873f849 Mon Sep 17 00:00:00 2001 From: Julian Antonielli Date: Wed, 16 Nov 2022 16:49:28 +0000 Subject: [PATCH 01/59] Refactor `ConstrainedStringGenerator` to extract length checking --- .../generators/ConstrainedStringGenerator.kt | 66 ++++++++++++------- 1 file changed, 41 insertions(+), 25 deletions(-) diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt index a63801cce4..44695daa17 100644 --- a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt +++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt @@ -5,6 +5,7 @@ package software.amazon.smithy.rust.codegen.server.smithy.generators +import software.amazon.smithy.codegen.core.Symbol import software.amazon.smithy.model.shapes.StringShape import software.amazon.smithy.model.traits.LengthTrait import software.amazon.smithy.rust.codegen.core.rustlang.Attribute @@ -50,6 +51,26 @@ class ConstrainedStringGenerator( } } + fun renderLengthValidation(lengthTrait: LengthTrait, constraintViolation: Symbol) { + val condition = if (lengthTrait.min.isPresent && lengthTrait.max.isPresent) { + "(${lengthTrait.min.get()}..=${lengthTrait.max.get()}).contains(&length)" + } else if (lengthTrait.min.isPresent) { + "${lengthTrait.min.get()} <= length" + } else { + "length <= ${lengthTrait.max.get()}" + } + writer.rust(""" + fn check_length(string: &str) -> Result<(), $constraintViolation> { + let length = string.chars().count(); + + if $condition { + Ok(()) + } else { + Err($constraintViolation::Length(length)) + } + } + """.trimIndent()) + } fun render() { val lengthTrait = shape.expectTrait() @@ -58,13 +79,6 @@ class ConstrainedStringGenerator( val inner = RustType.String.render() val constraintViolation = constraintViolationSymbolProvider.toSymbol(shape) - val condition = if (lengthTrait.min.isPresent && lengthTrait.max.isPresent) { - "(${lengthTrait.min.get()}..=${lengthTrait.max.get()}).contains(&length)" - } else if (lengthTrait.min.isPresent) { - "${lengthTrait.min.get()} <= length" - } else { - "length <= ${lengthTrait.max.get()}" - } val constrainedTypeVisibility = if (publicConstrainedTypes) { Visibility.PUBLIC @@ -85,55 +99,57 @@ class ConstrainedStringGenerator( if (constrainedTypeVisibility == Visibility.PUBCRATE) { Attribute.AllowUnused.render(writer) } - writer.rustTemplate( - """ - impl $name { + writer.rustBlockTemplate("impl $name") { + renderLengthValidation(lengthTrait, constraintViolation) + + rust(""" /// Extracts a string slice containing the entire underlying `String`. pub fn as_str(&self) -> &str { &self.0 } - + /// ${rustDocsInnerMethod(inner)} pub fn inner(&self) -> &$inner { &self.0 } - + /// ${rustDocsIntoInnerMethod(inner)} pub fn into_inner(self) -> $inner { self.0 } - } - + """.trimIndent()) + } + + + writer.rustTemplate( + """ impl #{ConstrainedTrait} for $name { type Unconstrained = $inner; } - + impl #{From}<$inner> for #{MaybeConstrained} { fn from(value: $inner) -> Self { Self::Unconstrained(value) } } - + impl #{Display} for $name { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { ${shape.redactIfNecessary(model, "self.0")}.fmt(f) } } - + impl #{TryFrom}<$inner> for $name { type Error = #{ConstraintViolation}; - + /// ${rustDocsTryFromMethod(name, inner)} fn try_from(value: $inner) -> Result { - let length = value.chars().count(); - if $condition { - Ok(Self(value)) - } else { - Err(#{ConstraintViolation}::Length(length)) - } + Self::check_length(&value)?; + + Ok(Self(value)) } } - + impl #{From}<$name> for $inner { fn from(value: $name) -> Self { value.into_inner() From bc29310cd325cc4c5f45313d12ec6f0afc92b9fb Mon Sep 17 00:00:00 2001 From: Julian Antonielli Date: Thu, 17 Nov 2022 10:13:11 +0000 Subject: [PATCH 02/59] Extract `TryFrom` rendering into its own function --- .../generators/ConstrainedStringGenerator.kt | 48 +++++++++++++------ 1 file changed, 33 insertions(+), 15 deletions(-) diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt index 44695daa17..04d4c0b990 100644 --- a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt +++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt @@ -23,6 +23,7 @@ import software.amazon.smithy.rust.codegen.core.rustlang.rustTemplate import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType import software.amazon.smithy.rust.codegen.core.smithy.makeMaybeConstrained 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.redactIfNecessary import software.amazon.smithy.rust.codegen.server.smithy.PubCrateConstraintViolationSymbolProvider import software.amazon.smithy.rust.codegen.server.smithy.ServerCodegenContext @@ -51,7 +52,7 @@ class ConstrainedStringGenerator( } } - fun renderLengthValidation(lengthTrait: LengthTrait, constraintViolation: Symbol) { + private fun renderLengthValidation(lengthTrait: LengthTrait, constraintViolation: Symbol) { val condition = if (lengthTrait.min.isPresent && lengthTrait.max.isPresent) { "(${lengthTrait.min.get()}..=${lengthTrait.max.get()}).contains(&length)" } else if (lengthTrait.min.isPresent) { @@ -71,9 +72,38 @@ class ConstrainedStringGenerator( } """.trimIndent()) } + + private fun renderTryFrom(inner: String, name: String, shape: StringShape, constraintViolation: Symbol) { + val lengthTrait = shape.getTrait() + + var lengthCheck = "" + if (lengthTrait != null) { + lengthCheck = "Self::check_length(&value);?" + + writer.rustBlock("impl $name") { + renderLengthValidation(lengthTrait, constraintViolation) + } + } + + writer.rustTemplate( + """ + impl #{TryFrom}<$inner> for $name { + type Error = #{ConstraintViolation}; + + /// ${rustDocsTryFromMethod(name, inner)} + fn try_from(value: $inner) -> Result { + $lengthCheck + Ok(Self(value)) + } + } + """.trimIndent(), + "ConstraintViolation" to constraintViolation, + "TryFrom" to RuntimeType.TryFrom, + ) + } + fun render() { val lengthTrait = shape.expectTrait() - val symbol = constrainedShapeSymbolProvider.toSymbol(shape) val name = symbol.name val inner = RustType.String.render() @@ -100,8 +130,6 @@ class ConstrainedStringGenerator( Attribute.AllowUnused.render(writer) } writer.rustBlockTemplate("impl $name") { - renderLengthValidation(lengthTrait, constraintViolation) - rust(""" /// Extracts a string slice containing the entire underlying `String`. pub fn as_str(&self) -> &str { @@ -120,6 +148,7 @@ class ConstrainedStringGenerator( """.trimIndent()) } + renderTryFrom(inner, name, shape, constraintViolation) writer.rustTemplate( """ @@ -139,16 +168,6 @@ class ConstrainedStringGenerator( } } - impl #{TryFrom}<$inner> for $name { - type Error = #{ConstraintViolation}; - - /// ${rustDocsTryFromMethod(name, inner)} - fn try_from(value: $inner) -> Result { - Self::check_length(&value)?; - - Ok(Self(value)) - } - } impl #{From}<$name> for $inner { fn from(value: $name) -> Self { @@ -161,7 +180,6 @@ class ConstrainedStringGenerator( "MaybeConstrained" to symbol.makeMaybeConstrained(), "Display" to RuntimeType.Display, "From" to RuntimeType.From, - "TryFrom" to RuntimeType.TryFrom, ) val constraintViolationModuleName = constraintViolation.namespace.split(constraintViolation.namespaceDelimiter).last() From 44495b9f54744f0c21ee9b2223964f88e60cb8fc Mon Sep 17 00:00:00 2001 From: Julian Antonielli Date: Thu, 17 Nov 2022 10:19:20 +0000 Subject: [PATCH 03/59] Extract `ConstraintViolation` definition to separate function --- .../generators/ConstrainedStringGenerator.kt | 56 ++++++++++--------- 1 file changed, 31 insertions(+), 25 deletions(-) diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt index 04d4c0b990..86ae3f5617 100644 --- a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt +++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt @@ -184,31 +184,37 @@ class ConstrainedStringGenerator( val constraintViolationModuleName = constraintViolation.namespace.split(constraintViolation.namespaceDelimiter).last() writer.withModule(RustModule(constraintViolationModuleName, RustMetadata(visibility = constrainedTypeVisibility))) { - rust( - """ - ##[derive(Debug, PartialEq)] - pub enum ${constraintViolation.name} { - Length(usize), - } - """, - ) - - if (shape.isReachableFromOperationInput()) { - rustBlock("impl ${constraintViolation.name}") { - rustBlockTemplate( - "pub(crate) fn as_validation_exception_field(self, path: #{String}) -> crate::model::ValidationExceptionField", - "String" to RuntimeType.String, - ) { - rustBlock("match self") { - rust( - """ - Self::Length(length) => crate::model::ValidationExceptionField { - message: format!("${lengthTrait.validationErrorMessage()}", length, &path), - path, - }, - """, - ) - } + renderConstraintViolationEnum(this, shape, constraintViolation) + } + } + + private fun renderConstraintViolationEnum(writer: RustWriter, shape: StringShape, constraintViolation: Symbol) { + val lengthTrait = shape.expectTrait() + + writer.rust( + """ + ##[derive(Debug, PartialEq)] + pub enum ${constraintViolation.name} { + Length(usize), + } + """, + ) + + if (shape.isReachableFromOperationInput()) { + writer.rustBlock("impl ${constraintViolation.name}") { + rustBlockTemplate( + "pub(crate) fn as_validation_exception_field(self, path: #{String}) -> crate::model::ValidationExceptionField", + "String" to RuntimeType.String, + ) { + rustBlock("match self") { + rust( + """ + Self::Length(length) => crate::model::ValidationExceptionField { + message: format!("${lengthTrait.validationErrorMessage()}", length, &path), + path, + }, + """, + ) } } } From e140694e8e5489f24d194bc8e4c788a19bc7ebc4 Mon Sep 17 00:00:00 2001 From: Julian Antonielli Date: Thu, 17 Nov 2022 11:17:51 +0000 Subject: [PATCH 04/59] Add pattern check mock for `@pattern` trait --- .../generators/ConstrainedStringGenerator.kt | 56 ++++++++++++++----- 1 file changed, 42 insertions(+), 14 deletions(-) diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt index 86ae3f5617..13355eebd6 100644 --- a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt +++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt @@ -8,6 +8,7 @@ package software.amazon.smithy.rust.codegen.server.smithy.generators import software.amazon.smithy.codegen.core.Symbol import software.amazon.smithy.model.shapes.StringShape import software.amazon.smithy.model.traits.LengthTrait +import software.amazon.smithy.model.traits.PatternTrait import software.amazon.smithy.rust.codegen.core.rustlang.Attribute import software.amazon.smithy.rust.codegen.core.rustlang.RustMetadata import software.amazon.smithy.rust.codegen.core.rustlang.RustModule @@ -22,7 +23,6 @@ import software.amazon.smithy.rust.codegen.core.rustlang.rustBlockTemplate import software.amazon.smithy.rust.codegen.core.rustlang.rustTemplate import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType import software.amazon.smithy.rust.codegen.core.smithy.makeMaybeConstrained -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.redactIfNecessary import software.amazon.smithy.rust.codegen.server.smithy.PubCrateConstraintViolationSymbolProvider @@ -52,7 +52,7 @@ class ConstrainedStringGenerator( } } - private fun renderLengthValidation(lengthTrait: LengthTrait, constraintViolation: Symbol) { + private fun renderLengthValidation(writer: RustWriter, lengthTrait: LengthTrait, constraintViolation: Symbol) { val condition = if (lengthTrait.min.isPresent && lengthTrait.max.isPresent) { "(${lengthTrait.min.get()}..=${lengthTrait.max.get()}).contains(&length)" } else if (lengthTrait.min.isPresent) { @@ -60,7 +60,8 @@ class ConstrainedStringGenerator( } else { "length <= ${lengthTrait.max.get()}" } - writer.rust(""" + writer.rust( + """ fn check_length(string: &str) -> Result<(), $constraintViolation> { let length = string.chars().count(); @@ -70,21 +71,41 @@ class ConstrainedStringGenerator( Err($constraintViolation::Length(length)) } } - """.trimIndent()) + """.trimIndent(), + ) } + private fun renderPatternValidation(writer: RustWriter, patternTrait: PatternTrait, constraintViolation: Symbol) { + val pattern = patternTrait.pattern.toString() + + writer.rust( + """ + fn check_pattern(_string: &str) -> Result<(), $constraintViolation> { + return Err($constraintViolation::Pattern("$pattern".to_owned())); + } + """.trimIndent(), + ) + } private fun renderTryFrom(inner: String, name: String, shape: StringShape, constraintViolation: Symbol) { val lengthTrait = shape.getTrait() + val patternTrait = shape.getTrait() var lengthCheck = "" if (lengthTrait != null) { - lengthCheck = "Self::check_length(&value);?" + lengthCheck = "Self::check_length(&value)?;" writer.rustBlock("impl $name") { - renderLengthValidation(lengthTrait, constraintViolation) + renderLengthValidation(writer, lengthTrait, constraintViolation) } } + var patternCheck = "" + if (patternTrait != null) { + patternCheck = "Self::check_pattern(&value)?;" + writer.rustBlock("impl $name") { + renderPatternValidation(writer, patternTrait, constraintViolation) + } + } writer.rustTemplate( """ impl #{TryFrom}<$inner> for $name { @@ -93,6 +114,7 @@ class ConstrainedStringGenerator( /// ${rustDocsTryFromMethod(name, inner)} fn try_from(value: $inner) -> Result { $lengthCheck + $patternCheck Ok(Self(value)) } } @@ -103,13 +125,11 @@ class ConstrainedStringGenerator( } fun render() { - val lengthTrait = shape.expectTrait() val symbol = constrainedShapeSymbolProvider.toSymbol(shape) val name = symbol.name val inner = RustType.String.render() val constraintViolation = constraintViolationSymbolProvider.toSymbol(shape) - val constrainedTypeVisibility = if (publicConstrainedTypes) { Visibility.PUBLIC } else { @@ -130,7 +150,8 @@ class ConstrainedStringGenerator( Attribute.AllowUnused.render(writer) } writer.rustBlockTemplate("impl $name") { - rust(""" + rust( + """ /// Extracts a string slice containing the entire underlying `String`. pub fn as_str(&self) -> &str { &self.0 @@ -145,7 +166,8 @@ class ConstrainedStringGenerator( pub fn into_inner(self) -> $inner { self.0 } - """.trimIndent()) + """.trimIndent(), + ) } renderTryFrom(inner, name, shape, constraintViolation) @@ -189,13 +211,15 @@ class ConstrainedStringGenerator( } private fun renderConstraintViolationEnum(writer: RustWriter, shape: StringShape, constraintViolation: Symbol) { - val lengthTrait = shape.expectTrait() + val lengthTrait = shape.getTrait() + val patternTrait = shape.getTrait() writer.rust( """ ##[derive(Debug, PartialEq)] pub enum ${constraintViolation.name} { - Length(usize), + ${if (lengthTrait != null) "Length(usize)," else ""} + ${if (patternTrait != null) "Pattern(String)," else ""} } """, ) @@ -209,10 +233,14 @@ class ConstrainedStringGenerator( rustBlock("match self") { rust( """ - Self::Length(length) => crate::model::ValidationExceptionField { + ${if (lengthTrait != null) """Self::Length(length) => crate::model::ValidationExceptionField { message: format!("${lengthTrait.validationErrorMessage()}", length, &path), path, - }, + }""" else ""}, + ${ if (patternTrait != null) """Self::Pattern(pattern) => crate::model::ValidationExceptionField { + message: format!("String at path {} failed to satisfy pattern {}", &path, pattern), + path + }""" else ""} """, ) } From ae21ce7099e49a2f0e55250d0f239d9fd9de6fe3 Mon Sep 17 00:00:00 2001 From: Julian Antonielli Date: Thu, 17 Nov 2022 11:54:23 +0000 Subject: [PATCH 05/59] Add `regex` to list of server dependencies --- .../smithy/rust/codegen/server/smithy/ServerCargoDependency.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/ServerCargoDependency.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/ServerCargoDependency.kt index 6058519abf..ef2e930dfd 100644 --- a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/ServerCargoDependency.kt +++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/ServerCargoDependency.kt @@ -25,6 +25,7 @@ object ServerCargoDependency { val PinProjectLite: CargoDependency = CargoDependency("pin-project-lite", CratesIo("0.2")) val Tower: CargoDependency = CargoDependency("tower", CratesIo("0.4")) val TokioDev: CargoDependency = CargoDependency("tokio", CratesIo("1.8.4"), scope = DependencyScope.Dev) + val Regex: CargoDependency = CargoDependency("regex", CratesIo("1.7.0")) fun SmithyHttpServer(runtimeConfig: RuntimeConfig) = runtimeConfig.runtimeCrate("http-server") } From 4c6384dad180b990425d2effb5af889198dfae29 Mon Sep 17 00:00:00 2001 From: Julian Antonielli Date: Thu, 17 Nov 2022 11:55:23 +0000 Subject: [PATCH 06/59] Use real regex check in `check_pattern` --- .../generators/ConstrainedStringGenerator.kt | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt index 13355eebd6..3edc811a42 100644 --- a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt +++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt @@ -15,6 +15,7 @@ import software.amazon.smithy.rust.codegen.core.rustlang.RustModule import software.amazon.smithy.rust.codegen.core.rustlang.RustType import software.amazon.smithy.rust.codegen.core.rustlang.RustWriter import software.amazon.smithy.rust.codegen.core.rustlang.Visibility +import software.amazon.smithy.rust.codegen.core.rustlang.asType import software.amazon.smithy.rust.codegen.core.rustlang.documentShape import software.amazon.smithy.rust.codegen.core.rustlang.render import software.amazon.smithy.rust.codegen.core.rustlang.rust @@ -26,6 +27,7 @@ import software.amazon.smithy.rust.codegen.core.smithy.makeMaybeConstrained import software.amazon.smithy.rust.codegen.core.util.getTrait import software.amazon.smithy.rust.codegen.core.util.redactIfNecessary import software.amazon.smithy.rust.codegen.server.smithy.PubCrateConstraintViolationSymbolProvider +import software.amazon.smithy.rust.codegen.server.smithy.ServerCargoDependency import software.amazon.smithy.rust.codegen.server.smithy.ServerCodegenContext import software.amazon.smithy.rust.codegen.server.smithy.traits.isReachableFromOperationInput import software.amazon.smithy.rust.codegen.server.smithy.validationErrorMessage @@ -78,12 +80,19 @@ class ConstrainedStringGenerator( private fun renderPatternValidation(writer: RustWriter, patternTrait: PatternTrait, constraintViolation: Symbol) { val pattern = patternTrait.pattern.toString() - writer.rust( + writer.rustTemplate( """ - fn check_pattern(_string: &str) -> Result<(), $constraintViolation> { - return Err($constraintViolation::Pattern("$pattern".to_owned())); + fn check_pattern(string: &str) -> Result<(), $constraintViolation> { + let regex = #{Regex}::Regex::new("$pattern").unwrap(); + + if regex.is_match(string) { + Ok(()) + } else { + Err($constraintViolation::Pattern("$pattern".to_owned())) + } } """.trimIndent(), + "Regex" to ServerCargoDependency.Regex.asType(), ) } private fun renderTryFrom(inner: String, name: String, shape: StringShape, constraintViolation: Symbol) { From c3e96598ef0ba425aefaec4c8ca31b12234f98f5 Mon Sep 17 00:00:00 2001 From: Julian Antonielli Date: Thu, 17 Nov 2022 12:14:06 +0000 Subject: [PATCH 07/59] Add `@pattern` to traits that make `string` shapes directly constrained --- .../amazon/smithy/rust/codegen/server/smithy/Constraints.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/Constraints.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/Constraints.kt index 82102be18a..c8f8684f18 100644 --- a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/Constraints.kt +++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/Constraints.kt @@ -66,7 +66,8 @@ fun Shape.isDirectlyConstrained(symbolProvider: SymbolProvider): Boolean = when this.members().map { symbolProvider.toSymbol(it) }.any { !it.isOptional() } } is MapShape -> this.hasTrait() - is StringShape -> this.hasTrait() || this.hasTrait() + is StringShape -> + this.hasTrait() || this.hasTrait() || this.hasTrait() else -> false } From b950c19b257f1870c22f9d8b4173483ebb2cf2b2 Mon Sep 17 00:00:00 2001 From: Julian Antonielli Date: Thu, 17 Nov 2022 12:15:35 +0000 Subject: [PATCH 08/59] Remove unsupported validation for `@pattern` on strings --- .../smithy/ValidateUnsupportedConstraints.kt | 41 ++++++------------- 1 file changed, 12 insertions(+), 29 deletions(-) diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/ValidateUnsupportedConstraints.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/ValidateUnsupportedConstraints.kt index d487689b20..cdc745a8e6 100644 --- a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/ValidateUnsupportedConstraints.kt +++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/ValidateUnsupportedConstraints.kt @@ -16,7 +16,6 @@ import software.amazon.smithy.model.shapes.ServiceShape import software.amazon.smithy.model.shapes.SetShape import software.amazon.smithy.model.shapes.Shape import software.amazon.smithy.model.shapes.ShapeId -import software.amazon.smithy.model.shapes.StringShape import software.amazon.smithy.model.shapes.UnionShape import software.amazon.smithy.model.traits.EnumTrait import software.amazon.smithy.model.traits.LengthTrait @@ -86,10 +85,6 @@ private sealed class UnsupportedConstraintMessageKind { level, buildMessageShapeHasUnsupportedConstraintTrait(shape, lengthTrait, constraintTraitsUberIssue), ) - is UnsupportedPatternTraitOnStringShape -> LogMessage( - level, - buildMessageShapeHasUnsupportedConstraintTrait(shape, patternTrait, constraintTraitsUberIssue), - ) is UnsupportedRangeTraitOnShape -> LogMessage( level, buildMessageShapeHasUnsupportedConstraintTrait(shape, rangeTrait, constraintTraitsUberIssue), @@ -102,7 +97,6 @@ private data class UnsupportedConstraintOnMemberShape(val shape: MemberShape, va private data class UnsupportedConstraintOnShapeReachableViaAnEventStream(val shape: Shape, val constraintTrait: Trait) : UnsupportedConstraintMessageKind() private data class UnsupportedLengthTraitOnStreamingBlobShape(val shape: BlobShape, val lengthTrait: LengthTrait, val streamingTrait: StreamingTrait) : UnsupportedConstraintMessageKind() private data class UnsupportedLengthTraitOnCollectionOrOnBlobShape(val shape: Shape, val lengthTrait: LengthTrait) : UnsupportedConstraintMessageKind() -private data class UnsupportedPatternTraitOnStringShape(val shape: Shape, val patternTrait: PatternTrait) : UnsupportedConstraintMessageKind() private data class UnsupportedRangeTraitOnShape(val shape: Shape, val rangeTrait: RangeTrait) : UnsupportedConstraintMessageKind() data class LogMessage(val level: Level, val message: String) @@ -141,20 +135,20 @@ fun validateOperationsWithConstrainedInputHaveValidationExceptionAttached(model: LogMessage( Level.SEVERE, """ - Operation ${it.shape.id} takes in input that is constrained - (https://awslabs.github.io/smithy/2.0/spec/constraint-traits.html), and as such can fail with a validation + Operation ${it.shape.id} takes in input that is constrained + (https://awslabs.github.io/smithy/2.0/spec/constraint-traits.html), and as such can fail with a validation exception. You must model this behavior in the operation shape in your model file. """.trimIndent().replace("\n", "") + """ - - ```smithy - use smithy.framework#ValidationException - - operation ${it.shape.id.name} { - ... - errors: [..., ValidationException] // <-- Add this. - } - ``` + + ```smithy + use smithy.framework#ValidationException + + operation ${it.shape.id.name} { + ... + errors: [..., ValidationException] // <-- Add this. + } + ``` """.trimIndent(), ) } @@ -209,17 +203,7 @@ fun validateUnsupportedConstraints(model: Model, service: ServiceShape, codegenC .map { UnsupportedLengthTraitOnCollectionOrOnBlobShape(it, it.expectTrait()) } .toSet() - // 5. Pattern trait on string shapes is used. It has not been implemented yet. - // TODO(https://github.com/awslabs/smithy-rs/issues/1401) - val unsupportedPatternTraitOnStringShapeSet = walker - .walkShapes(service) - .asSequence() - .filterIsInstance() - .filterMapShapesToTraits(setOf(PatternTrait::class.java)) - .map { (shape, patternTrait) -> UnsupportedPatternTraitOnStringShape(shape, patternTrait as PatternTrait) } - .toSet() - - // 6. Range trait on any shape is used. It has not been implemented yet. + // 5. Range trait on any shape is used. It has not been implemented yet. // TODO(https://github.com/awslabs/smithy-rs/issues/1401) val unsupportedRangeTraitOnShapeSet = walker .walkShapes(service) @@ -233,7 +217,6 @@ fun validateUnsupportedConstraints(model: Model, service: ServiceShape, codegenC unsupportedLengthTraitOnStreamingBlobShapeSet.map { it.intoLogMessage(codegenConfig.ignoreUnsupportedConstraints) } + unsupportedConstraintOnShapeReachableViaAnEventStreamSet.map { it.intoLogMessage(codegenConfig.ignoreUnsupportedConstraints) } + unsupportedLengthTraitOnCollectionOrOnBlobShapeSet.map { it.intoLogMessage(codegenConfig.ignoreUnsupportedConstraints) } + - unsupportedPatternTraitOnStringShapeSet.map { it.intoLogMessage(codegenConfig.ignoreUnsupportedConstraints) } + unsupportedRangeTraitOnShapeSet.map { it.intoLogMessage(codegenConfig.ignoreUnsupportedConstraints) } return ValidationResult(shouldAbort = messages.any { it.level == Level.SEVERE }, messages) From b9206bf18b6659b27b596acee6c825d06ea96716 Mon Sep 17 00:00:00 2001 From: Julian Antonielli Date: Thu, 17 Nov 2022 12:16:33 +0000 Subject: [PATCH 09/59] Fix test cases in `ConstraintsTest` --- .../codegen/server/smithy/ConstraintsTest.kt | 37 +++++++++---------- 1 file changed, 17 insertions(+), 20 deletions(-) diff --git a/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/ConstraintsTest.kt b/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/ConstraintsTest.kt index 80e2d93dae..dfd5e6c002 100644 --- a/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/ConstraintsTest.kt +++ b/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/ConstraintsTest.kt @@ -26,63 +26,63 @@ class ConstraintsTest { version: "123", operations: [TestOperation] } - + operation TestOperation { input: TestInputOutput, output: TestInputOutput, } - + structure TestInputOutput { map: MapA, - + recursive: RecursiveShape } - + structure RecursiveShape { shape: RecursiveShape, mapB: MapB } - + @length(min: 1, max: 69) map MapA { key: String, value: MapB } - + map MapB { key: String, value: StructureA } - + @uniqueItems list ListA { member: MyString } - + @pattern("\\w+") string MyString - + @length(min: 1, max: 69) string LengthString - + structure StructureA { @range(min: 1, max: 69) int: Integer, - + @required string: String } - + // This shape is not in the service closure. structure StructureB { @pattern("\\w+") patternString: String, - + @required requiredString: String, - + mapA: MapA, - + @length(min: 1, max: 5) mapAPrecedence: MapA } @@ -94,7 +94,6 @@ class ConstraintsTest { private val mapA = model.lookup("test#MapA") private val mapB = model.lookup("test#MapB") private val listA = model.lookup("test#ListA") - private val myString = model.lookup("test#MyString") private val lengthString = model.lookup("test#LengthString") private val structA = model.lookup("test#StructureA") private val structAInt = model.lookup("test#StructureA\$int") @@ -114,7 +113,7 @@ class ConstraintsTest { @Test fun `it should not detect unsupported constrained traits as constrained`() { - listOf(structAInt, structAString, myString).forAll { + listOf(structAInt, structAString).forAll { it.isDirectlyConstrained(symbolProvider) shouldBe false } } @@ -123,9 +122,7 @@ class ConstraintsTest { fun `it should evaluate reachability of constrained shapes`() { mapA.canReachConstrainedShape(model, symbolProvider) shouldBe true structAInt.canReachConstrainedShape(model, symbolProvider) shouldBe false - - // This should be true when we start supporting the `pattern` trait on string shapes. - listA.canReachConstrainedShape(model, symbolProvider) shouldBe false + listA.canReachConstrainedShape(model, symbolProvider) shouldBe true // All of these eventually reach `StructureA`, which is constrained because one of its members is `required`. testInputOutput.canReachConstrainedShape(model, symbolProvider) shouldBe true From 1f3d763032f192088dcfa231dc62db1e0c5a4d3b Mon Sep 17 00:00:00 2001 From: Julian Antonielli Date: Thu, 17 Nov 2022 12:17:35 +0000 Subject: [PATCH 10/59] Remove test disallowing `@pattern` on strings --- ...ateUnsupportedConstraintsAreNotUsedTest.kt | 51 ++++++------------- 1 file changed, 16 insertions(+), 35 deletions(-) diff --git a/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/ValidateUnsupportedConstraintsAreNotUsedTest.kt b/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/ValidateUnsupportedConstraintsAreNotUsedTest.kt index 0624358ba3..ae1a0e3697 100644 --- a/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/ValidateUnsupportedConstraintsAreNotUsedTest.kt +++ b/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/ValidateUnsupportedConstraintsAreNotUsedTest.kt @@ -22,12 +22,12 @@ internal class ValidateUnsupportedConstraintsAreNotUsedTest { private val baseModel = """ namespace test - + service TestService { version: "123", operations: [TestOperation] } - + operation TestOperation { input: TestInputOutput, output: TestInputOutput, @@ -44,7 +44,7 @@ internal class ValidateUnsupportedConstraintsAreNotUsedTest { val model = """ $baseModel - + structure TestInputOutput { @required requiredString: String @@ -62,7 +62,7 @@ internal class ValidateUnsupportedConstraintsAreNotUsedTest { val model = """ $baseModel - + structure TestInputOutput { @length(min: 1, max: 69) lengthString: String @@ -79,7 +79,7 @@ internal class ValidateUnsupportedConstraintsAreNotUsedTest { val model = """ $baseModel - + structure TestInputOutput { @required string: String @@ -93,12 +93,12 @@ internal class ValidateUnsupportedConstraintsAreNotUsedTest { private val constraintTraitOnStreamingBlobShapeModel = """ $baseModel - + structure TestInputOutput { @required streamingBlob: StreamingBlob } - + @streaming @length(min: 69) blob StreamingBlob @@ -123,20 +123,20 @@ internal class ValidateUnsupportedConstraintsAreNotUsedTest { val model = """ $baseModel - + structure TestInputOutput { eventStream: EventStream } - + @streaming union EventStream { message: Message } - + structure Message { lengthString: LengthString } - + @length(min: 1) string LengthString """.asSmithyModel() @@ -155,17 +155,17 @@ internal class ValidateUnsupportedConstraintsAreNotUsedTest { val model = """ $baseModel - + structure TestInputOutput { collection: LengthCollection, blob: LengthBlob } - + @length(min: 1) list LengthCollection { member: String } - + @length(min: 1) blob LengthBlob """.asSmithyModel() @@ -176,35 +176,16 @@ internal class ValidateUnsupportedConstraintsAreNotUsedTest { validationResult.messages.forSome { it.message shouldContain "The blob shape `test#LengthBlob` has the constraint trait `smithy.api#length` attached" } } - @Test - fun `it should detect when the pattern trait on string shapes is used`() { - val model = - """ - $baseModel - - structure TestInputOutput { - patternString: PatternString - } - - @pattern("^[A-Za-z]+$") - string PatternString - """.asSmithyModel() - val validationResult = validateModel(model) - - validationResult.messages shouldHaveSize 1 - validationResult.messages[0].message shouldContain "The string shape `test#PatternString` has the constraint trait `smithy.api#pattern` attached" - } - @Test fun `it should detect when the range trait is used`() { val model = """ $baseModel - + structure TestInputOutput { rangeInteger: RangeInteger } - + @range(min: 1) integer RangeInteger """.asSmithyModel() From 838485bfa71e288b70913c1670c85c98ade7a384 Mon Sep 17 00:00:00 2001 From: Julian Antonielli Date: Thu, 17 Nov 2022 12:18:29 +0000 Subject: [PATCH 11/59] Add first test case for `@pattern` on strings --- .../generators/ConstrainedStringGeneratorTest.kt | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGeneratorTest.kt b/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGeneratorTest.kt index 75db6303f7..13e1c43f09 100644 --- a/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGeneratorTest.kt +++ b/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGeneratorTest.kt @@ -44,11 +44,12 @@ class ConstrainedStringGeneratorTest { "👍👍👍", // These three emojis are three Unicode scalar values. "👍👍👍👍", ), + Triple("@pattern(\"^[a-z]+$\")", "valid", "123 invalid"), ).map { TestCase( """ namespace test - + ${it.first} string ConstrainedString """.asSmithyModel(), @@ -116,10 +117,10 @@ class ConstrainedStringGeneratorTest { fun `type should not be constructible without using a constructor`() { val model = """ namespace test - + @length(min: 1, max: 69) string ConstrainedString - """.asSmithyModel() + """.asSmithyModel() val constrainedStringShape = model.lookup("test#ConstrainedString") val codegenContext = serverTestCodegenContext(model) @@ -136,14 +137,14 @@ class ConstrainedStringGeneratorTest { fun `Display implementation`() { val model = """ namespace test - + @length(min: 1, max: 69) string ConstrainedString - + @sensitive @length(min: 1, max: 78) string SensitiveConstrainedString - """.asSmithyModel() + """.asSmithyModel() val constrainedStringShape = model.lookup("test#ConstrainedString") val sensitiveConstrainedStringShape = model.lookup("test#SensitiveConstrainedString") From 1aa257345df402e6cb2b0dd7af98e7506eb0d4c7 Mon Sep 17 00:00:00 2001 From: Julian Antonielli Date: Thu, 17 Nov 2022 12:22:31 +0000 Subject: [PATCH 12/59] Fix codegen in `ConstraintViolation::as_validation_exception_field` --- .../server/smithy/generators/ConstrainedStringGenerator.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt index 3edc811a42..a3e8781cd2 100644 --- a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt +++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt @@ -245,11 +245,11 @@ class ConstrainedStringGenerator( ${if (lengthTrait != null) """Self::Length(length) => crate::model::ValidationExceptionField { message: format!("${lengthTrait.validationErrorMessage()}", length, &path), path, - }""" else ""}, + },""" else ""} ${ if (patternTrait != null) """Self::Pattern(pattern) => crate::model::ValidationExceptionField { message: format!("String at path {} failed to satisfy pattern {}", &path, pattern), path - }""" else ""} + },""" else ""} """, ) } From d4de23465ddb6e3f5da1318f369ddad58bd3f5db Mon Sep 17 00:00:00 2001 From: Julian Antonielli Date: Thu, 17 Nov 2022 12:38:25 +0000 Subject: [PATCH 13/59] Use `OnceCell` to store `@pattern`'s regex --- .../server/smithy/generators/ConstrainedStringGenerator.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt index a3e8781cd2..27e0640bc8 100644 --- a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt +++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt @@ -83,7 +83,8 @@ class ConstrainedStringGenerator( writer.rustTemplate( """ fn check_pattern(string: &str) -> Result<(), $constraintViolation> { - let regex = #{Regex}::Regex::new("$pattern").unwrap(); + static REGEX : #{OnceCell}::sync::OnceCell<#{Regex}::Regex> = #{OnceCell}::sync::OnceCell::new(); + let regex = REGEX.get_or_init(|| #{Regex}::Regex::new("$pattern").unwrap()); if regex.is_match(string) { Ok(()) @@ -93,6 +94,7 @@ class ConstrainedStringGenerator( } """.trimIndent(), "Regex" to ServerCargoDependency.Regex.asType(), + "OnceCell" to ServerCargoDependency.OnceCell.asType(), ) } private fun renderTryFrom(inner: String, name: String, shape: StringShape, constraintViolation: Symbol) { From be244073b3959edcc1afcd802c447c035c6db8eb Mon Sep 17 00:00:00 2001 From: Julian Antonielli Date: Thu, 17 Nov 2022 14:42:54 +0000 Subject: [PATCH 14/59] Tidy up `ConstrainedStringGenerator` to work with many constraints --- .../server/smithy/ServerCodegenVisitor.kt | 6 +- .../generators/ConstrainedStringGenerator.kt | 221 ++++++++++-------- 2 files changed, 128 insertions(+), 99 deletions(-) diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/ServerCodegenVisitor.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/ServerCodegenVisitor.kt index c0a68a26b7..444de52392 100644 --- a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/ServerCodegenVisitor.kt +++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/ServerCodegenVisitor.kt @@ -22,6 +22,7 @@ import software.amazon.smithy.model.shapes.StructureShape import software.amazon.smithy.model.shapes.UnionShape import software.amazon.smithy.model.traits.EnumTrait import software.amazon.smithy.model.traits.LengthTrait +import software.amazon.smithy.model.traits.PatternTrait import software.amazon.smithy.model.transform.ModelTransformer import software.amazon.smithy.rust.codegen.client.smithy.customize.RustCodegenDecorator import software.amazon.smithy.rust.codegen.core.rustlang.RustModule @@ -41,6 +42,7 @@ import software.amazon.smithy.rust.codegen.core.smithy.transformers.EventStreamN import software.amazon.smithy.rust.codegen.core.smithy.transformers.OperationNormalizer import software.amazon.smithy.rust.codegen.core.smithy.transformers.RecursiveShapeBoxer import software.amazon.smithy.rust.codegen.core.util.CommandFailed +import software.amazon.smithy.rust.codegen.core.util.getTrait import software.amazon.smithy.rust.codegen.core.util.hasTrait import software.amazon.smithy.rust.codegen.core.util.runCommand import software.amazon.smithy.rust.codegen.server.smithy.generators.ConstrainedMapGenerator @@ -384,9 +386,11 @@ open class ServerCodegenVisitor( """.trimIndent().replace("\n", " "), ) } else if (!shape.hasTrait() && shape.isDirectlyConstrained(codegenContext.symbolProvider)) { + // TODO: Tidy up + val constraints = listOfNotNull(shape.getTrait(), shape.getTrait()) logger.info("[rust-server-codegen] Generating a constrained string $shape") rustCrate.withModule(ModelsModule) { - ConstrainedStringGenerator(codegenContext, this, shape).render() + ConstrainedStringGenerator(codegenContext, this, shape, constraints).render() } } } diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt index 27e0640bc8..e37a1e1f9a 100644 --- a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt +++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt @@ -7,6 +7,7 @@ package software.amazon.smithy.rust.codegen.server.smithy.generators import software.amazon.smithy.codegen.core.Symbol import software.amazon.smithy.model.shapes.StringShape +import software.amazon.smithy.model.traits.AbstractTrait import software.amazon.smithy.model.traits.LengthTrait import software.amazon.smithy.model.traits.PatternTrait import software.amazon.smithy.rust.codegen.core.rustlang.Attribute @@ -24,7 +25,6 @@ import software.amazon.smithy.rust.codegen.core.rustlang.rustBlockTemplate import software.amazon.smithy.rust.codegen.core.rustlang.rustTemplate import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType import software.amazon.smithy.rust.codegen.core.smithy.makeMaybeConstrained -import software.amazon.smithy.rust.codegen.core.util.getTrait import software.amazon.smithy.rust.codegen.core.util.redactIfNecessary import software.amazon.smithy.rust.codegen.server.smithy.PubCrateConstraintViolationSymbolProvider import software.amazon.smithy.rust.codegen.server.smithy.ServerCargoDependency @@ -32,6 +32,97 @@ import software.amazon.smithy.rust.codegen.server.smithy.ServerCodegenContext import software.amazon.smithy.rust.codegen.server.smithy.traits.isReachableFromOperationInput import software.amazon.smithy.rust.codegen.server.smithy.validationErrorMessage +private data class TraitInfo( + val tryFromCheck: (writer: RustWriter) -> Unit, + val renderConstraintViolationVariant: (writer: RustWriter) -> Unit, + val asValidationExceptionField: (writer: RustWriter) -> Unit, + val renderValidationFunctionDefinition: (writer: RustWriter) -> Unit, +) +private fun handleTrait(trait: AbstractTrait, constraintViolation: Symbol): TraitInfo? { + when (trait) { + is LengthTrait -> { + return TraitInfo( + { writer -> writer.rust("Self::check_length(&value)?;") }, + { writer -> writer.rust("Length(usize),") }, + { writer -> + writer.rust( + """ + Self::Length(length) => crate::model::ValidationExceptionField { + message: format!("${trait.validationErrorMessage()}", length, &path), + path, + }, + """.trimIndent(), + ) + }, + { writer -> renderLengthValidation(writer, trait, constraintViolation) }, + ) + } + is PatternTrait -> { + return TraitInfo( + { writer -> writer.rust("Self::check_pattern(&value)?;") }, + { writer -> writer.rust("Pattern(String),") }, + { writer -> + writer.rust( + """ + Self::Pattern(pattern) => crate::model::ValidationExceptionField { + message: format!("String at path {} failed to satisfy pattern {}", &path, pattern), + path + }, + """.trimIndent(), + ) + }, + { writer -> renderPatternValidation(writer, trait, constraintViolation) }, + ) + } + else -> { + return null + } + } +} + +private fun renderLengthValidation(writer: RustWriter, lengthTrait: LengthTrait, constraintViolation: Symbol) { + val condition = if (lengthTrait.min.isPresent && lengthTrait.max.isPresent) { + "(${lengthTrait.min.get()}..=${lengthTrait.max.get()}).contains(&length)" + } else if (lengthTrait.min.isPresent) { + "${lengthTrait.min.get()} <= length" + } else { + "length <= ${lengthTrait.max.get()}" + } + writer.rust( + """ + fn check_length(string: &str) -> Result<(), $constraintViolation> { + let length = string.chars().count(); + + if $condition { + Ok(()) + } else { + Err($constraintViolation::Length(length)) + } + } + """.trimIndent(), + ) +} +private fun renderPatternValidation(writer: RustWriter, patternTrait: PatternTrait, constraintViolation: Symbol) { + val pattern = patternTrait.pattern.toString() + + writer.rustTemplate( + """ + fn check_pattern(string: &str) -> Result<(), $constraintViolation> { + static REGEX : #{OnceCell}::sync::OnceCell<#{Regex}::Regex> = #{OnceCell}::sync::OnceCell::new(); + let regex = REGEX.get_or_init(|| #{Regex}::Regex::new("$pattern").unwrap()); + + if regex.is_match(string) { + Ok(()) + } else { + Err($constraintViolation::Pattern("$pattern".to_owned())) + } + } + """.trimIndent(), + "Regex" to ServerCargoDependency.Regex.asType(), + "OnceCell" to ServerCargoDependency.OnceCell.asType(), + ) +} + /** * [ConstrainedStringGenerator] generates a wrapper tuple newtype holding a constrained `String`. * This type can be built from unconstrained values, yielding a `ConstraintViolation` when the input does not satisfy @@ -41,6 +132,7 @@ class ConstrainedStringGenerator( val codegenContext: ServerCodegenContext, val writer: RustWriter, val shape: StringShape, + val constraints: List, ) { val model = codegenContext.model val constrainedShapeSymbolProvider = codegenContext.constrainedShapeSymbolProvider @@ -54,85 +146,27 @@ class ConstrainedStringGenerator( } } - private fun renderLengthValidation(writer: RustWriter, lengthTrait: LengthTrait, constraintViolation: Symbol) { - val condition = if (lengthTrait.min.isPresent && lengthTrait.max.isPresent) { - "(${lengthTrait.min.get()}..=${lengthTrait.max.get()}).contains(&length)" - } else if (lengthTrait.min.isPresent) { - "${lengthTrait.min.get()} <= length" - } else { - "length <= ${lengthTrait.max.get()}" - } - writer.rust( - """ - fn check_length(string: &str) -> Result<(), $constraintViolation> { - let length = string.chars().count(); - - if $condition { - Ok(()) - } else { - Err($constraintViolation::Length(length)) - } - } - """.trimIndent(), - ) - } - - private fun renderPatternValidation(writer: RustWriter, patternTrait: PatternTrait, constraintViolation: Symbol) { - val pattern = patternTrait.pattern.toString() - - writer.rustTemplate( - """ - fn check_pattern(string: &str) -> Result<(), $constraintViolation> { - static REGEX : #{OnceCell}::sync::OnceCell<#{Regex}::Regex> = #{OnceCell}::sync::OnceCell::new(); - let regex = REGEX.get_or_init(|| #{Regex}::Regex::new("$pattern").unwrap()); - - if regex.is_match(string) { - Ok(()) - } else { - Err($constraintViolation::Pattern("$pattern".to_owned())) - } - } - """.trimIndent(), - "Regex" to ServerCargoDependency.Regex.asType(), - "OnceCell" to ServerCargoDependency.OnceCell.asType(), - ) - } - private fun renderTryFrom(inner: String, name: String, shape: StringShape, constraintViolation: Symbol) { - val lengthTrait = shape.getTrait() - val patternTrait = shape.getTrait() - - var lengthCheck = "" - if (lengthTrait != null) { - lengthCheck = "Self::check_length(&value)?;" - - writer.rustBlock("impl $name") { - renderLengthValidation(writer, lengthTrait, constraintViolation) - } - } - - var patternCheck = "" - if (patternTrait != null) { - patternCheck = "Self::check_pattern(&value)?;" - writer.rustBlock("impl $name") { - renderPatternValidation(writer, patternTrait, constraintViolation) + private fun renderTryFrom(inner: String, name: String, constraintViolation: Symbol, traits: List) { + writer.rustBlock("impl $name") { + for (traitInfo in traits) { + traitInfo.renderValidationFunctionDefinition(writer) } } - writer.rustTemplate( - """ - impl #{TryFrom}<$inner> for $name { - type Error = #{ConstraintViolation}; + writer.rustBlockTemplate("impl #{TryFrom}<$inner> for $name", "TryFrom" to RuntimeType.TryFrom) { + rustTemplate("type Error = #{ConstraintViolation};", "ConstraintViolation" to constraintViolation) + rustBlock( + """ /// ${rustDocsTryFromMethod(name, inner)} - fn try_from(value: $inner) -> Result { - $lengthCheck - $patternCheck - Ok(Self(value)) + fn try_from(value: $inner) -> Result + """, + ) { + for (traitInfo in traits) { + traitInfo.tryFromCheck(writer) } + rust("Ok(Self(value))") } - """.trimIndent(), - "ConstraintViolation" to constraintViolation, - "TryFrom" to RuntimeType.TryFrom, - ) + } } fun render() { @@ -181,7 +215,9 @@ class ConstrainedStringGenerator( ) } - renderTryFrom(inner, name, shape, constraintViolation) + val traits: List = constraints.mapNotNull { handleTrait(it, constraintViolation) } + + renderTryFrom(inner, name, constraintViolation, traits) writer.rustTemplate( """ @@ -217,23 +253,21 @@ class ConstrainedStringGenerator( val constraintViolationModuleName = constraintViolation.namespace.split(constraintViolation.namespaceDelimiter).last() writer.withModule(RustModule(constraintViolationModuleName, RustMetadata(visibility = constrainedTypeVisibility))) { - renderConstraintViolationEnum(this, shape, constraintViolation) + renderConstraintViolationEnum(this, shape, constraintViolation, traits) } } - private fun renderConstraintViolationEnum(writer: RustWriter, shape: StringShape, constraintViolation: Symbol) { - val lengthTrait = shape.getTrait() - val patternTrait = shape.getTrait() - - writer.rust( + private fun renderConstraintViolationEnum(writer: RustWriter, shape: StringShape, constraintViolation: Symbol, constraintTraits: List) { + writer.rustBlock( """ ##[derive(Debug, PartialEq)] - pub enum ${constraintViolation.name} { - ${if (lengthTrait != null) "Length(usize)," else ""} - ${if (patternTrait != null) "Pattern(String)," else ""} + pub enum ${constraintViolation.name} + """.trimIndent(), + ) { + for (traitInfo in constraintTraits) { + traitInfo.renderConstraintViolationVariant(this) } - """, - ) + } if (shape.isReachableFromOperationInput()) { writer.rustBlock("impl ${constraintViolation.name}") { @@ -242,18 +276,9 @@ class ConstrainedStringGenerator( "String" to RuntimeType.String, ) { rustBlock("match self") { - rust( - """ - ${if (lengthTrait != null) """Self::Length(length) => crate::model::ValidationExceptionField { - message: format!("${lengthTrait.validationErrorMessage()}", length, &path), - path, - },""" else ""} - ${ if (patternTrait != null) """Self::Pattern(pattern) => crate::model::ValidationExceptionField { - message: format!("String at path {} failed to satisfy pattern {}", &path, pattern), - path - },""" else ""} - """, - ) + for (traitInfo in constraintTraits) { + traitInfo.asValidationExceptionField(this) + } } } } From 77637fd40c4e817554178159044f6d90cc3f75ef Mon Sep 17 00:00:00 2001 From: Julian Antonielli Date: Thu, 17 Nov 2022 14:52:46 +0000 Subject: [PATCH 15/59] Rename `handleTrait` -> `fromTrait` and make it a static method --- .../generators/ConstrainedStringGenerator.kt | 101 ++++++++++-------- 1 file changed, 54 insertions(+), 47 deletions(-) diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt index e37a1e1f9a..dfa0177fd9 100644 --- a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt +++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt @@ -32,50 +32,58 @@ import software.amazon.smithy.rust.codegen.server.smithy.ServerCodegenContext import software.amazon.smithy.rust.codegen.server.smithy.traits.isReachableFromOperationInput import software.amazon.smithy.rust.codegen.server.smithy.validationErrorMessage +/** + * Information needed to render a constraint trait as Rust code. + */ private data class TraitInfo( val tryFromCheck: (writer: RustWriter) -> Unit, val renderConstraintViolationVariant: (writer: RustWriter) -> Unit, val asValidationExceptionField: (writer: RustWriter) -> Unit, - val renderValidationFunctionDefinition: (writer: RustWriter) -> Unit, -) -private fun handleTrait(trait: AbstractTrait, constraintViolation: Symbol): TraitInfo? { - when (trait) { - is LengthTrait -> { - return TraitInfo( - { writer -> writer.rust("Self::check_length(&value)?;") }, - { writer -> writer.rust("Length(usize),") }, - { writer -> - writer.rust( - """ - Self::Length(length) => crate::model::ValidationExceptionField { - message: format!("${trait.validationErrorMessage()}", length, &path), - path, + val renderValidationFunctionDefinition: (writer: RustWriter, constraintViolation: Symbol) -> Unit, +) { + companion object { + fun fromTrait(trait: AbstractTrait): TraitInfo? { + when (trait) { + is LengthTrait -> { + return TraitInfo( + { writer -> writer.rust("Self::check_length(&value)?;") }, + { writer -> writer.rust("Length(usize),") }, + { writer -> + writer.rust( + """ + Self::Length(length) => crate::model::ValidationExceptionField { + message: format!("${trait.validationErrorMessage()}", length, &path), + path, + }, + """.trimIndent(), + ) }, - """.trimIndent(), + { writer, constraintViolation -> renderLengthValidation(writer, trait, constraintViolation) }, ) - }, - { writer -> renderLengthValidation(writer, trait, constraintViolation) }, - ) - } - is PatternTrait -> { - return TraitInfo( - { writer -> writer.rust("Self::check_pattern(&value)?;") }, - { writer -> writer.rust("Pattern(String),") }, - { writer -> - writer.rust( - """ - Self::Pattern(pattern) => crate::model::ValidationExceptionField { - message: format!("String at path {} failed to satisfy pattern {}", &path, pattern), - path + } + + is PatternTrait -> { + return TraitInfo( + { writer -> writer.rust("Self::check_pattern(&value)?;") }, + { writer -> writer.rust("Pattern(String),") }, + { writer -> + writer.rust( + """ + Self::Pattern(pattern) => crate::model::ValidationExceptionField { + message: format!("String at path {} failed to satisfy pattern {}", &path, pattern), + path + }, + """.trimIndent(), + ) }, - """.trimIndent(), + { writer, constraintViolation -> renderPatternValidation(writer, trait, constraintViolation) }, ) - }, - { writer -> renderPatternValidation(writer, trait, constraintViolation) }, - ) - } - else -> { - return null + } + + else -> { + return null + } + } } } } @@ -146,10 +154,10 @@ class ConstrainedStringGenerator( } } - private fun renderTryFrom(inner: String, name: String, constraintViolation: Symbol, traits: List) { + private fun renderTryFrom(inner: String, name: String, constraintViolation: Symbol, constraintsInfo: List) { writer.rustBlock("impl $name") { - for (traitInfo in traits) { - traitInfo.renderValidationFunctionDefinition(writer) + for (traitInfo in constraintsInfo) { + traitInfo.renderValidationFunctionDefinition(writer, constraintViolation) } } @@ -161,7 +169,7 @@ class ConstrainedStringGenerator( fn try_from(value: $inner) -> Result """, ) { - for (traitInfo in traits) { + for (traitInfo in constraintsInfo) { traitInfo.tryFromCheck(writer) } rust("Ok(Self(value))") @@ -174,6 +182,7 @@ class ConstrainedStringGenerator( val name = symbol.name val inner = RustType.String.render() val constraintViolation = constraintViolationSymbolProvider.toSymbol(shape) + val constraintsInfo: List = constraints.mapNotNull(TraitInfo::fromTrait) val constrainedTypeVisibility = if (publicConstrainedTypes) { Visibility.PUBLIC @@ -215,9 +224,7 @@ class ConstrainedStringGenerator( ) } - val traits: List = constraints.mapNotNull { handleTrait(it, constraintViolation) } - - renderTryFrom(inner, name, constraintViolation, traits) + renderTryFrom(inner, name, constraintViolation, constraintsInfo) writer.rustTemplate( """ @@ -253,18 +260,18 @@ class ConstrainedStringGenerator( val constraintViolationModuleName = constraintViolation.namespace.split(constraintViolation.namespaceDelimiter).last() writer.withModule(RustModule(constraintViolationModuleName, RustMetadata(visibility = constrainedTypeVisibility))) { - renderConstraintViolationEnum(this, shape, constraintViolation, traits) + renderConstraintViolationEnum(this, shape, constraintViolation, constraintsInfo) } } - private fun renderConstraintViolationEnum(writer: RustWriter, shape: StringShape, constraintViolation: Symbol, constraintTraits: List) { + private fun renderConstraintViolationEnum(writer: RustWriter, shape: StringShape, constraintViolation: Symbol, constraintsInfo: List) { writer.rustBlock( """ ##[derive(Debug, PartialEq)] pub enum ${constraintViolation.name} """.trimIndent(), ) { - for (traitInfo in constraintTraits) { + for (traitInfo in constraintsInfo) { traitInfo.renderConstraintViolationVariant(this) } } @@ -276,7 +283,7 @@ class ConstrainedStringGenerator( "String" to RuntimeType.String, ) { rustBlock("match self") { - for (traitInfo in constraintTraits) { + for (traitInfo in constraintsInfo) { traitInfo.asValidationExceptionField(this) } } From aae0b755ed34b6df36f6543a2c4e21e8230d8490 Mon Sep 17 00:00:00 2001 From: Julian Antonielli Date: Thu, 17 Nov 2022 14:55:29 +0000 Subject: [PATCH 16/59] Add docs for `ConstraintViolation` variants --- .../smithy/generators/ConstrainedStringGenerator.kt | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt index dfa0177fd9..5397dd5789 100644 --- a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt +++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt @@ -17,6 +17,7 @@ import software.amazon.smithy.rust.codegen.core.rustlang.RustType import software.amazon.smithy.rust.codegen.core.rustlang.RustWriter import software.amazon.smithy.rust.codegen.core.rustlang.Visibility import software.amazon.smithy.rust.codegen.core.rustlang.asType +import software.amazon.smithy.rust.codegen.core.rustlang.docs import software.amazon.smithy.rust.codegen.core.rustlang.documentShape import software.amazon.smithy.rust.codegen.core.rustlang.render import software.amazon.smithy.rust.codegen.core.rustlang.rust @@ -47,7 +48,10 @@ private data class TraitInfo( is LengthTrait -> { return TraitInfo( { writer -> writer.rust("Self::check_length(&value)?;") }, - { writer -> writer.rust("Length(usize),") }, + { writer -> + writer.docs("Error when a string doesn't satisfy its `@length` requirements.") + writer.rust("Length(usize),") + }, { writer -> writer.rust( """ @@ -65,7 +69,10 @@ private data class TraitInfo( is PatternTrait -> { return TraitInfo( { writer -> writer.rust("Self::check_pattern(&value)?;") }, - { writer -> writer.rust("Pattern(String),") }, + { writer -> + writer.docs("Error when a string doesn't satisfy its `@pattern`.") + writer.rust("Pattern(String),") + }, { writer -> writer.rust( """ From 116e9dea874e7c5fbb41c9e63edde132872f4d61 Mon Sep 17 00:00:00 2001 From: Julian Antonielli Date: Thu, 17 Nov 2022 14:58:41 +0000 Subject: [PATCH 17/59] Fix unescaped slashes in regexes --- .../server/smithy/generators/ConstrainedStringGenerator.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt index 5397dd5789..92e46e00aa 100644 --- a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt +++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt @@ -118,7 +118,9 @@ private fun renderLengthValidation(writer: RustWriter, lengthTrait: LengthTrait, ) } private fun renderPatternValidation(writer: RustWriter, patternTrait: PatternTrait, constraintViolation: Symbol) { - val pattern = patternTrait.pattern.toString() + // Escape `\`s to not end up with broken rust code in the presence of regexes with slashes. + // This turns `Regex::new("^[\S\s]+$")` into `Regex::new("^[\\S\\s]+$")`. + val pattern = patternTrait.pattern.toString().replace("\\", "\\\\") writer.rustTemplate( """ From 55df743b52d38940a724323617177dae3fe74ca1 Mon Sep 17 00:00:00 2001 From: Julian Antonielli Date: Thu, 17 Nov 2022 16:17:09 +0000 Subject: [PATCH 18/59] Quick hack to get tests compiling --- .../server/smithy/ServerCodegenVisitor.kt | 1 - .../ConstrainedStringGeneratorTest.kt | 20 +++++++++++-------- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/ServerCodegenVisitor.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/ServerCodegenVisitor.kt index 444de52392..2f87f6b1d9 100644 --- a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/ServerCodegenVisitor.kt +++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/ServerCodegenVisitor.kt @@ -386,7 +386,6 @@ open class ServerCodegenVisitor( """.trimIndent().replace("\n", " "), ) } else if (!shape.hasTrait() && shape.isDirectlyConstrained(codegenContext.symbolProvider)) { - // TODO: Tidy up val constraints = listOfNotNull(shape.getTrait(), shape.getTrait()) logger.info("[rust-server-codegen] Generating a constrained string $shape") rustCrate.withModule(ModelsModule) { diff --git a/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGeneratorTest.kt b/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGeneratorTest.kt index 13e1c43f09..2dc8925fc7 100644 --- a/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGeneratorTest.kt +++ b/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGeneratorTest.kt @@ -14,22 +14,25 @@ import org.junit.jupiter.params.provider.ArgumentsProvider import org.junit.jupiter.params.provider.ArgumentsSource import software.amazon.smithy.model.Model import software.amazon.smithy.model.shapes.StringShape +import software.amazon.smithy.model.traits.LengthTrait +import software.amazon.smithy.model.traits.PatternTrait import software.amazon.smithy.rust.codegen.core.rustlang.RustWriter import software.amazon.smithy.rust.codegen.core.smithy.ModelsModule import software.amazon.smithy.rust.codegen.core.testutil.TestWorkspace import software.amazon.smithy.rust.codegen.core.testutil.asSmithyModel import software.amazon.smithy.rust.codegen.core.testutil.compileAndTest import software.amazon.smithy.rust.codegen.core.testutil.unitTest +import software.amazon.smithy.rust.codegen.core.util.expectTrait import software.amazon.smithy.rust.codegen.core.util.lookup import software.amazon.smithy.rust.codegen.server.smithy.testutil.serverTestCodegenContext import java.util.stream.Stream class ConstrainedStringGeneratorTest { - + val a = PatternTrait("something") data class TestCase(val model: Model, val validString: String, val invalidString: String) class ConstrainedStringGeneratorTestProvider : ArgumentsProvider { - private val testCases = listOf( + private val lengthTestCases = listOf( // Min and max. Triple("@length(min: 11, max: 12)", "validString", "invalidString"), // Min equal to max. @@ -44,7 +47,8 @@ class ConstrainedStringGeneratorTest { "👍👍👍", // These three emojis are three Unicode scalar values. "👍👍👍👍", ), - Triple("@pattern(\"^[a-z]+$\")", "valid", "123 invalid"), + // Need to fix the setup to be able to add `@pattern` tests. + // Triple("@pattern(\"^[a-z]+$\")", "valid", "123 invalid"), ).map { TestCase( """ @@ -59,7 +63,7 @@ class ConstrainedStringGeneratorTest { } override fun provideArguments(context: ExtensionContext?): Stream = - testCases.map { Arguments.of(it) }.stream() + lengthTestCases.map { Arguments.of(it) }.stream() } @ParameterizedTest @@ -73,7 +77,7 @@ class ConstrainedStringGeneratorTest { val project = TestWorkspace.testProject(symbolProvider) project.withModule(ModelsModule) { - ConstrainedStringGenerator(codegenContext, this, constrainedStringShape).render() + ConstrainedStringGenerator(codegenContext, this, constrainedStringShape, listOf(constrainedStringShape.expectTrait())).render() unitTest( name = "try_from_success", @@ -127,7 +131,7 @@ class ConstrainedStringGeneratorTest { val writer = RustWriter.forModule(ModelsModule.name) - ConstrainedStringGenerator(codegenContext, writer, constrainedStringShape).render() + ConstrainedStringGenerator(codegenContext, writer, constrainedStringShape, listOf(constrainedStringShape.expectTrait())).render() // Check that the wrapped type is `pub(crate)`. writer.toString() shouldContain "pub struct ConstrainedString(pub(crate) std::string::String);" @@ -153,8 +157,8 @@ class ConstrainedStringGeneratorTest { val project = TestWorkspace.testProject(codegenContext.symbolProvider) project.withModule(ModelsModule) { - ConstrainedStringGenerator(codegenContext, this, constrainedStringShape).render() - ConstrainedStringGenerator(codegenContext, this, sensitiveConstrainedStringShape).render() + ConstrainedStringGenerator(codegenContext, this, constrainedStringShape, listOf(constrainedStringShape.expectTrait())).render() + ConstrainedStringGenerator(codegenContext, this, sensitiveConstrainedStringShape, listOf(constrainedStringShape.expectTrait())).render() unitTest( name = "non_sensitive_string_display_implementation", From 67863ca7ef12f95d354adaa8e8717285dc6e5e3e Mon Sep 17 00:00:00 2001 From: Julian Antonielli Date: Fri, 18 Nov 2022 09:46:45 +0000 Subject: [PATCH 19/59] Fix `sensitive_string_display_implementation` test --- .../server/smithy/generators/ConstrainedStringGeneratorTest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGeneratorTest.kt b/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGeneratorTest.kt index 2dc8925fc7..3c6b61717b 100644 --- a/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGeneratorTest.kt +++ b/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGeneratorTest.kt @@ -158,7 +158,7 @@ class ConstrainedStringGeneratorTest { project.withModule(ModelsModule) { ConstrainedStringGenerator(codegenContext, this, constrainedStringShape, listOf(constrainedStringShape.expectTrait())).render() - ConstrainedStringGenerator(codegenContext, this, sensitiveConstrainedStringShape, listOf(constrainedStringShape.expectTrait())).render() + ConstrainedStringGenerator(codegenContext, this, sensitiveConstrainedStringShape, listOf(sensitiveConstrainedStringShape.expectTrait())).render() unitTest( name = "non_sensitive_string_display_implementation", From 96ea2c72b023d0451f7d13e9940b2ff0270271de Mon Sep 17 00:00:00 2001 From: Julian Antonielli Date: Fri, 18 Nov 2022 10:05:39 +0000 Subject: [PATCH 20/59] Use new fn name: `asType` -> `toType` --- .../server/smithy/generators/ConstrainedStringGenerator.kt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt index 92e46e00aa..085612cf09 100644 --- a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt +++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt @@ -16,7 +16,6 @@ import software.amazon.smithy.rust.codegen.core.rustlang.RustModule import software.amazon.smithy.rust.codegen.core.rustlang.RustType import software.amazon.smithy.rust.codegen.core.rustlang.RustWriter import software.amazon.smithy.rust.codegen.core.rustlang.Visibility -import software.amazon.smithy.rust.codegen.core.rustlang.asType import software.amazon.smithy.rust.codegen.core.rustlang.docs import software.amazon.smithy.rust.codegen.core.rustlang.documentShape import software.amazon.smithy.rust.codegen.core.rustlang.render @@ -135,8 +134,8 @@ private fun renderPatternValidation(writer: RustWriter, patternTrait: PatternTra } } """.trimIndent(), - "Regex" to ServerCargoDependency.Regex.asType(), - "OnceCell" to ServerCargoDependency.OnceCell.asType(), + "Regex" to ServerCargoDependency.Regex.toType(), + "OnceCell" to ServerCargoDependency.OnceCell.toType(), ) } From 275e2a88368e19d4e51c4865bb1e74e4dbebe874 Mon Sep 17 00:00:00 2001 From: Julian Antonielli Date: Fri, 18 Nov 2022 10:20:26 +0000 Subject: [PATCH 21/59] Refactor `ConstrainedStringGenerator` to not pass in `constraints` --- .../server/smithy/ServerCodegenVisitor.kt | 5 +--- .../generators/ConstrainedStringGenerator.kt | 26 ++++++++++++++----- .../ConstrainedStringGeneratorTest.kt | 10 +++---- 3 files changed, 25 insertions(+), 16 deletions(-) diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/ServerCodegenVisitor.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/ServerCodegenVisitor.kt index dc947e8003..8a3c5649bc 100644 --- a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/ServerCodegenVisitor.kt +++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/ServerCodegenVisitor.kt @@ -22,7 +22,6 @@ import software.amazon.smithy.model.shapes.StructureShape import software.amazon.smithy.model.shapes.UnionShape import software.amazon.smithy.model.traits.EnumTrait import software.amazon.smithy.model.traits.LengthTrait -import software.amazon.smithy.model.traits.PatternTrait import software.amazon.smithy.model.transform.ModelTransformer import software.amazon.smithy.rust.codegen.client.smithy.customize.RustCodegenDecorator import software.amazon.smithy.rust.codegen.core.rustlang.RustModule @@ -42,7 +41,6 @@ import software.amazon.smithy.rust.codegen.core.smithy.transformers.EventStreamN import software.amazon.smithy.rust.codegen.core.smithy.transformers.OperationNormalizer import software.amazon.smithy.rust.codegen.core.smithy.transformers.RecursiveShapeBoxer import software.amazon.smithy.rust.codegen.core.util.CommandFailed -import software.amazon.smithy.rust.codegen.core.util.getTrait import software.amazon.smithy.rust.codegen.core.util.hasTrait import software.amazon.smithy.rust.codegen.core.util.runCommand import software.amazon.smithy.rust.codegen.server.smithy.generators.ConstrainedMapGenerator @@ -386,10 +384,9 @@ open class ServerCodegenVisitor( """.trimIndent().replace("\n", " "), ) } else if (!shape.hasTrait() && shape.isDirectlyConstrained(codegenContext.symbolProvider)) { - val constraints = listOfNotNull(shape.getTrait(), shape.getTrait()) logger.info("[rust-server-codegen] Generating a constrained string $shape") rustCrate.withModule(ModelsModule) { - ConstrainedStringGenerator(codegenContext, this, shape, constraints).render() + ConstrainedStringGenerator(codegenContext, this, shape).render() } } } diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt index 085612cf09..79b6e88a03 100644 --- a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt +++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt @@ -25,6 +25,7 @@ import software.amazon.smithy.rust.codegen.core.rustlang.rustBlockTemplate import software.amazon.smithy.rust.codegen.core.rustlang.rustTemplate import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType import software.amazon.smithy.rust.codegen.core.smithy.makeMaybeConstrained +import software.amazon.smithy.rust.codegen.core.util.orNull import software.amazon.smithy.rust.codegen.core.util.redactIfNecessary import software.amazon.smithy.rust.codegen.server.smithy.PubCrateConstraintViolationSymbolProvider import software.amazon.smithy.rust.codegen.server.smithy.ServerCargoDependency @@ -94,6 +95,10 @@ private data class TraitInfo( } } +/** + * Renders a `check_length` function to validate the string matches the + * required length indicated by the `@length` trait. + */ private fun renderLengthValidation(writer: RustWriter, lengthTrait: LengthTrait, constraintViolation: Symbol) { val condition = if (lengthTrait.min.isPresent && lengthTrait.max.isPresent) { "(${lengthTrait.min.get()}..=${lengthTrait.max.get()}).contains(&length)" @@ -116,6 +121,11 @@ private fun renderLengthValidation(writer: RustWriter, lengthTrait: LengthTrait, """.trimIndent(), ) } + +/** + * Renders a `check_pattern` function to validate the string matches the + * supplied regex in the `@pattern` trait. + */ private fun renderPatternValidation(writer: RustWriter, patternTrait: PatternTrait, constraintViolation: Symbol) { // Escape `\`s to not end up with broken rust code in the presence of regexes with slashes. // This turns `Regex::new("^[\S\s]+$")` into `Regex::new("^[\\S\\s]+$")`. @@ -139,6 +149,8 @@ private fun renderPatternValidation(writer: RustWriter, patternTrait: PatternTra ) } +private val supportedStringConstraintTraits = listOf(LengthTrait::class.java, PatternTrait::class.java) + /** * [ConstrainedStringGenerator] generates a wrapper tuple newtype holding a constrained `String`. * This type can be built from unconstrained values, yielding a `ConstraintViolation` when the input does not satisfy @@ -148,7 +160,6 @@ class ConstrainedStringGenerator( val codegenContext: ServerCodegenContext, val writer: RustWriter, val shape: StringShape, - val constraints: List, ) { val model = codegenContext.model val constrainedShapeSymbolProvider = codegenContext.constrainedShapeSymbolProvider @@ -161,8 +172,12 @@ class ConstrainedStringGenerator( PubCrateConstraintViolationSymbolProvider(this) } } + private val constraintsInfo: List = + supportedStringConstraintTraits + .mapNotNull { shape.getTrait(it).orNull() } + .mapNotNull(TraitInfo::fromTrait) - private fun renderTryFrom(inner: String, name: String, constraintViolation: Symbol, constraintsInfo: List) { + private fun renderTryFrom(inner: String, name: String, constraintViolation: Symbol) { writer.rustBlock("impl $name") { for (traitInfo in constraintsInfo) { traitInfo.renderValidationFunctionDefinition(writer, constraintViolation) @@ -190,7 +205,6 @@ class ConstrainedStringGenerator( val name = symbol.name val inner = RustType.String.render() val constraintViolation = constraintViolationSymbolProvider.toSymbol(shape) - val constraintsInfo: List = constraints.mapNotNull(TraitInfo::fromTrait) val constrainedTypeVisibility = if (publicConstrainedTypes) { Visibility.PUBLIC @@ -232,7 +246,7 @@ class ConstrainedStringGenerator( ) } - renderTryFrom(inner, name, constraintViolation, constraintsInfo) + renderTryFrom(inner, name, constraintViolation) writer.rustTemplate( """ @@ -268,11 +282,11 @@ class ConstrainedStringGenerator( val constraintViolationModuleName = constraintViolation.namespace.split(constraintViolation.namespaceDelimiter).last() writer.withModule(RustModule(constraintViolationModuleName, RustMetadata(visibility = constrainedTypeVisibility))) { - renderConstraintViolationEnum(this, shape, constraintViolation, constraintsInfo) + renderConstraintViolationEnum(this, shape, constraintViolation) } } - private fun renderConstraintViolationEnum(writer: RustWriter, shape: StringShape, constraintViolation: Symbol, constraintsInfo: List) { + private fun renderConstraintViolationEnum(writer: RustWriter, shape: StringShape, constraintViolation: Symbol) { writer.rustBlock( """ ##[derive(Debug, PartialEq)] diff --git a/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGeneratorTest.kt b/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGeneratorTest.kt index 3c6b61717b..409bdc4638 100644 --- a/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGeneratorTest.kt +++ b/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGeneratorTest.kt @@ -14,7 +14,6 @@ import org.junit.jupiter.params.provider.ArgumentsProvider import org.junit.jupiter.params.provider.ArgumentsSource import software.amazon.smithy.model.Model import software.amazon.smithy.model.shapes.StringShape -import software.amazon.smithy.model.traits.LengthTrait import software.amazon.smithy.model.traits.PatternTrait import software.amazon.smithy.rust.codegen.core.rustlang.RustWriter import software.amazon.smithy.rust.codegen.core.smithy.ModelsModule @@ -22,7 +21,6 @@ import software.amazon.smithy.rust.codegen.core.testutil.TestWorkspace import software.amazon.smithy.rust.codegen.core.testutil.asSmithyModel import software.amazon.smithy.rust.codegen.core.testutil.compileAndTest import software.amazon.smithy.rust.codegen.core.testutil.unitTest -import software.amazon.smithy.rust.codegen.core.util.expectTrait import software.amazon.smithy.rust.codegen.core.util.lookup import software.amazon.smithy.rust.codegen.server.smithy.testutil.serverTestCodegenContext import java.util.stream.Stream @@ -77,7 +75,7 @@ class ConstrainedStringGeneratorTest { val project = TestWorkspace.testProject(symbolProvider) project.withModule(ModelsModule) { - ConstrainedStringGenerator(codegenContext, this, constrainedStringShape, listOf(constrainedStringShape.expectTrait())).render() + ConstrainedStringGenerator(codegenContext, this, constrainedStringShape).render() unitTest( name = "try_from_success", @@ -131,7 +129,7 @@ class ConstrainedStringGeneratorTest { val writer = RustWriter.forModule(ModelsModule.name) - ConstrainedStringGenerator(codegenContext, writer, constrainedStringShape, listOf(constrainedStringShape.expectTrait())).render() + ConstrainedStringGenerator(codegenContext, writer, constrainedStringShape).render() // Check that the wrapped type is `pub(crate)`. writer.toString() shouldContain "pub struct ConstrainedString(pub(crate) std::string::String);" @@ -157,8 +155,8 @@ class ConstrainedStringGeneratorTest { val project = TestWorkspace.testProject(codegenContext.symbolProvider) project.withModule(ModelsModule) { - ConstrainedStringGenerator(codegenContext, this, constrainedStringShape, listOf(constrainedStringShape.expectTrait())).render() - ConstrainedStringGenerator(codegenContext, this, sensitiveConstrainedStringShape, listOf(sensitiveConstrainedStringShape.expectTrait())).render() + ConstrainedStringGenerator(codegenContext, this, constrainedStringShape).render() + ConstrainedStringGenerator(codegenContext, this, sensitiveConstrainedStringShape).render() unitTest( name = "non_sensitive_string_display_implementation", From 4ffd5503d6d8896faf0793a297c42b2af3cc712b Mon Sep 17 00:00:00 2001 From: Julian Antonielli Date: Fri, 18 Nov 2022 10:25:11 +0000 Subject: [PATCH 22/59] Add `@pattern` test --- .../server/smithy/generators/ConstrainedStringGeneratorTest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGeneratorTest.kt b/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGeneratorTest.kt index 409bdc4638..3ba301937c 100644 --- a/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGeneratorTest.kt +++ b/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGeneratorTest.kt @@ -46,7 +46,7 @@ class ConstrainedStringGeneratorTest { "👍👍👍👍", ), // Need to fix the setup to be able to add `@pattern` tests. - // Triple("@pattern(\"^[a-z]+$\")", "valid", "123 invalid"), + Triple("@pattern(\"^[a-z]+$\")", "valid", "123 invalid"), ).map { TestCase( """ From 0797f4dcd1e1b8e6911d8fcca4c29434d03d2855 Mon Sep 17 00:00:00 2001 From: Julian Antonielli Date: Fri, 18 Nov 2022 13:27:27 +0000 Subject: [PATCH 23/59] Use `Lazy` instead of `OnceCell` --- .../server/smithy/generators/ConstrainedStringGenerator.kt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt index 79b6e88a03..ec4e50f759 100644 --- a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt +++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt @@ -134,10 +134,9 @@ private fun renderPatternValidation(writer: RustWriter, patternTrait: PatternTra writer.rustTemplate( """ fn check_pattern(string: &str) -> Result<(), $constraintViolation> { - static REGEX : #{OnceCell}::sync::OnceCell<#{Regex}::Regex> = #{OnceCell}::sync::OnceCell::new(); - let regex = REGEX.get_or_init(|| #{Regex}::Regex::new("$pattern").unwrap()); + static REGEX : #{OnceCell}::sync::Lazy<#{Regex}::Regex> = #{OnceCell}::sync::Lazy::new(|| #{Regex}::Regex::new("$pattern").unwrap()); - if regex.is_match(string) { + if REGEX.is_match(string) { Ok(()) } else { Err($constraintViolation::Pattern("$pattern".to_owned())) From 22fd18f582fcfc3573f8f66cdfbb3ca89c8f7b9e Mon Sep 17 00:00:00 2001 From: Julian Antonielli Date: Fri, 18 Nov 2022 13:28:53 +0000 Subject: [PATCH 24/59] Store `&'static str` in `Pattern` error instead of `String` --- .../server/smithy/generators/ConstrainedStringGenerator.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt index ec4e50f759..ecc5ff7872 100644 --- a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt +++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt @@ -71,7 +71,7 @@ private data class TraitInfo( { writer -> writer.rust("Self::check_pattern(&value)?;") }, { writer -> writer.docs("Error when a string doesn't satisfy its `@pattern`.") - writer.rust("Pattern(String),") + writer.rust("Pattern(&'static str),") }, { writer -> writer.rust( @@ -139,7 +139,7 @@ private fun renderPatternValidation(writer: RustWriter, patternTrait: PatternTra if REGEX.is_match(string) { Ok(()) } else { - Err($constraintViolation::Pattern("$pattern".to_owned())) + Err($constraintViolation::Pattern("$pattern")) } } """.trimIndent(), From 65139bd067c282cc8bd42fd66e287da85871e838 Mon Sep 17 00:00:00 2001 From: Julian Antonielli Date: Fri, 18 Nov 2022 13:30:00 +0000 Subject: [PATCH 25/59] Move `TraitInfo` to the bottom --- .../generators/ConstrainedStringGenerator.kt | 230 +++++++++--------- 1 file changed, 115 insertions(+), 115 deletions(-) diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt index ecc5ff7872..48f551f60d 100644 --- a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt +++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt @@ -33,121 +33,6 @@ import software.amazon.smithy.rust.codegen.server.smithy.ServerCodegenContext import software.amazon.smithy.rust.codegen.server.smithy.traits.isReachableFromOperationInput import software.amazon.smithy.rust.codegen.server.smithy.validationErrorMessage -/** - * Information needed to render a constraint trait as Rust code. - */ -private data class TraitInfo( - val tryFromCheck: (writer: RustWriter) -> Unit, - val renderConstraintViolationVariant: (writer: RustWriter) -> Unit, - val asValidationExceptionField: (writer: RustWriter) -> Unit, - val renderValidationFunctionDefinition: (writer: RustWriter, constraintViolation: Symbol) -> Unit, -) { - companion object { - fun fromTrait(trait: AbstractTrait): TraitInfo? { - when (trait) { - is LengthTrait -> { - return TraitInfo( - { writer -> writer.rust("Self::check_length(&value)?;") }, - { writer -> - writer.docs("Error when a string doesn't satisfy its `@length` requirements.") - writer.rust("Length(usize),") - }, - { writer -> - writer.rust( - """ - Self::Length(length) => crate::model::ValidationExceptionField { - message: format!("${trait.validationErrorMessage()}", length, &path), - path, - }, - """.trimIndent(), - ) - }, - { writer, constraintViolation -> renderLengthValidation(writer, trait, constraintViolation) }, - ) - } - - is PatternTrait -> { - return TraitInfo( - { writer -> writer.rust("Self::check_pattern(&value)?;") }, - { writer -> - writer.docs("Error when a string doesn't satisfy its `@pattern`.") - writer.rust("Pattern(&'static str),") - }, - { writer -> - writer.rust( - """ - Self::Pattern(pattern) => crate::model::ValidationExceptionField { - message: format!("String at path {} failed to satisfy pattern {}", &path, pattern), - path - }, - """.trimIndent(), - ) - }, - { writer, constraintViolation -> renderPatternValidation(writer, trait, constraintViolation) }, - ) - } - - else -> { - return null - } - } - } - } -} - -/** - * Renders a `check_length` function to validate the string matches the - * required length indicated by the `@length` trait. - */ -private fun renderLengthValidation(writer: RustWriter, lengthTrait: LengthTrait, constraintViolation: Symbol) { - val condition = if (lengthTrait.min.isPresent && lengthTrait.max.isPresent) { - "(${lengthTrait.min.get()}..=${lengthTrait.max.get()}).contains(&length)" - } else if (lengthTrait.min.isPresent) { - "${lengthTrait.min.get()} <= length" - } else { - "length <= ${lengthTrait.max.get()}" - } - writer.rust( - """ - fn check_length(string: &str) -> Result<(), $constraintViolation> { - let length = string.chars().count(); - - if $condition { - Ok(()) - } else { - Err($constraintViolation::Length(length)) - } - } - """.trimIndent(), - ) -} - -/** - * Renders a `check_pattern` function to validate the string matches the - * supplied regex in the `@pattern` trait. - */ -private fun renderPatternValidation(writer: RustWriter, patternTrait: PatternTrait, constraintViolation: Symbol) { - // Escape `\`s to not end up with broken rust code in the presence of regexes with slashes. - // This turns `Regex::new("^[\S\s]+$")` into `Regex::new("^[\\S\\s]+$")`. - val pattern = patternTrait.pattern.toString().replace("\\", "\\\\") - - writer.rustTemplate( - """ - fn check_pattern(string: &str) -> Result<(), $constraintViolation> { - static REGEX : #{OnceCell}::sync::Lazy<#{Regex}::Regex> = #{OnceCell}::sync::Lazy::new(|| #{Regex}::Regex::new("$pattern").unwrap()); - - if REGEX.is_match(string) { - Ok(()) - } else { - Err($constraintViolation::Pattern("$pattern")) - } - } - """.trimIndent(), - "Regex" to ServerCargoDependency.Regex.toType(), - "OnceCell" to ServerCargoDependency.OnceCell.toType(), - ) -} - private val supportedStringConstraintTraits = listOf(LengthTrait::class.java, PatternTrait::class.java) /** @@ -313,3 +198,118 @@ class ConstrainedStringGenerator( } } } + +/** + * Information needed to render a constraint trait as Rust code. + */ +private data class TraitInfo( + val tryFromCheck: (writer: RustWriter) -> Unit, + val renderConstraintViolationVariant: (writer: RustWriter) -> Unit, + val asValidationExceptionField: (writer: RustWriter) -> Unit, + val renderValidationFunctionDefinition: (writer: RustWriter, constraintViolation: Symbol) -> Unit, +) { + companion object { + fun fromTrait(trait: AbstractTrait): TraitInfo? { + when (trait) { + is LengthTrait -> { + return TraitInfo( + { writer -> writer.rust("Self::check_length(&value)?;") }, + { writer -> + writer.docs("Error when a string doesn't satisfy its `@length` requirements.") + writer.rust("Length(usize),") + }, + { writer -> + writer.rust( + """ + Self::Length(length) => crate::model::ValidationExceptionField { + message: format!("${trait.validationErrorMessage()}", length, &path), + path, + }, + """.trimIndent(), + ) + }, + { writer, constraintViolation -> renderLengthValidation(writer, trait, constraintViolation) }, + ) + } + + is PatternTrait -> { + return TraitInfo( + { writer -> writer.rust("Self::check_pattern(&value)?;") }, + { writer -> + writer.docs("Error when a string doesn't satisfy its `@pattern`.") + writer.rust("Pattern(&'static str),") + }, + { writer -> + writer.rust( + """ + Self::Pattern(pattern) => crate::model::ValidationExceptionField { + message: format!("String at path {} failed to satisfy pattern {}", &path, pattern), + path + }, + """.trimIndent(), + ) + }, + { writer, constraintViolation -> renderPatternValidation(writer, trait, constraintViolation) }, + ) + } + + else -> { + return null + } + } + } + } +} + +/** + * Renders a `check_length` function to validate the string matches the + * required length indicated by the `@length` trait. + */ +private fun renderLengthValidation(writer: RustWriter, lengthTrait: LengthTrait, constraintViolation: Symbol) { + val condition = if (lengthTrait.min.isPresent && lengthTrait.max.isPresent) { + "(${lengthTrait.min.get()}..=${lengthTrait.max.get()}).contains(&length)" + } else if (lengthTrait.min.isPresent) { + "${lengthTrait.min.get()} <= length" + } else { + "length <= ${lengthTrait.max.get()}" + } + writer.rust( + """ + fn check_length(string: &str) -> Result<(), $constraintViolation> { + let length = string.chars().count(); + + if $condition { + Ok(()) + } else { + Err($constraintViolation::Length(length)) + } + } + """.trimIndent(), + ) +} + +/** + * Renders a `check_pattern` function to validate the string matches the + * supplied regex in the `@pattern` trait. + */ +private fun renderPatternValidation(writer: RustWriter, patternTrait: PatternTrait, constraintViolation: Symbol) { + // Escape `\`s to not end up with broken rust code in the presence of regexes with slashes. + // This turns `Regex::new("^[\S\s]+$")` into `Regex::new("^[\\S\\s]+$")`. + val pattern = patternTrait.pattern.toString().replace("\\", "\\\\") + + writer.rustTemplate( + """ + fn check_pattern(string: &str) -> Result<(), $constraintViolation> { + static REGEX : #{OnceCell}::sync::Lazy<#{Regex}::Regex> = #{OnceCell}::sync::Lazy::new(|| #{Regex}::Regex::new("$pattern").unwrap()); + + if REGEX.is_match(string) { + Ok(()) + } else { + Err($constraintViolation::Pattern("$pattern")) + } + } + """.trimIndent(), + "Regex" to ServerCargoDependency.Regex.toType(), + "OnceCell" to ServerCargoDependency.OnceCell.toType(), + ) +} From d26a87715092bae0fe9a433642d438b4fd0b307e Mon Sep 17 00:00:00 2001 From: Julian Antonielli Date: Fri, 18 Nov 2022 13:36:07 +0000 Subject: [PATCH 26/59] Extract branches in `TraitInfo.fromTrait` into separate functions --- .../generators/ConstrainedStringGenerator.kt | 84 ++++++++++--------- 1 file changed, 46 insertions(+), 38 deletions(-) diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt index 48f551f60d..025ac7bd31 100644 --- a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt +++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt @@ -210,54 +210,62 @@ private data class TraitInfo( ) { companion object { fun fromTrait(trait: AbstractTrait): TraitInfo? { - when (trait) { + return when (trait) { is LengthTrait -> { - return TraitInfo( - { writer -> writer.rust("Self::check_length(&value)?;") }, - { writer -> - writer.docs("Error when a string doesn't satisfy its `@length` requirements.") - writer.rust("Length(usize),") - }, - { writer -> - writer.rust( - """ - Self::Length(length) => crate::model::ValidationExceptionField { - message: format!("${trait.validationErrorMessage()}", length, &path), - path, - }, - """.trimIndent(), - ) - }, - { writer, constraintViolation -> renderLengthValidation(writer, trait, constraintViolation) }, - ) + this.fromLengthTrait(trait) } is PatternTrait -> { - return TraitInfo( - { writer -> writer.rust("Self::check_pattern(&value)?;") }, - { writer -> - writer.docs("Error when a string doesn't satisfy its `@pattern`.") - writer.rust("Pattern(&'static str),") - }, - { writer -> - writer.rust( - """ - Self::Pattern(pattern) => crate::model::ValidationExceptionField { - message: format!("String at path {} failed to satisfy pattern {}", &path, pattern), - path - }, - """.trimIndent(), - ) - }, - { writer, constraintViolation -> renderPatternValidation(writer, trait, constraintViolation) }, - ) + this.fromPatternTrait(trait) } else -> { - return null + null } } } + + private fun fromLengthTrait(lengthTrait: LengthTrait): TraitInfo { + return TraitInfo( + { writer -> writer.rust("Self::check_length(&value)?;") }, + { writer -> + writer.docs("Error when a string doesn't satisfy its `@length` requirements.") + writer.rust("Length(usize),") + }, + { writer -> + writer.rust( + """ + Self::Length(length) => crate::model::ValidationExceptionField { + message: format!("${lengthTrait.validationErrorMessage()}", length, &path), + path, + }, + """.trimIndent(), + ) + }, + { writer, constraintViolation -> renderLengthValidation(writer, lengthTrait, constraintViolation) }, + ) + } + + private fun fromPatternTrait(patternTrait: PatternTrait): TraitInfo { + return TraitInfo( + { writer -> writer.rust("Self::check_pattern(&value)?;") }, + { writer -> + writer.docs("Error when a string doesn't satisfy its `@pattern`.") + writer.rust("Pattern(&'static str),") + }, + { writer -> + writer.rust( + """ + Self::Pattern(pattern) => crate::model::ValidationExceptionField { + message: format!("String at path {} failed to satisfy pattern {}", &path, pattern), + path + }, + """.trimIndent(), + ) + }, + { writer, constraintViolation -> renderPatternValidation(writer, patternTrait, constraintViolation) }, + ) + } } } From 0d7bbee01c5814afffa14273f6115f0c179513b9 Mon Sep 17 00:00:00 2001 From: Julian Antonielli Date: Fri, 18 Nov 2022 13:48:17 +0000 Subject: [PATCH 27/59] Add `PatternTrait.validationErrorMessage` --- .../smithy/PatternTraitValidationErrorMessage.kt | 16 ++++++++++++++++ .../generators/ConstrainedStringGenerator.kt | 7 +++---- 2 files changed, 19 insertions(+), 4 deletions(-) create mode 100644 codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/PatternTraitValidationErrorMessage.kt diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/PatternTraitValidationErrorMessage.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/PatternTraitValidationErrorMessage.kt new file mode 100644 index 0000000000..bd345e7e8a --- /dev/null +++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/PatternTraitValidationErrorMessage.kt @@ -0,0 +1,16 @@ +package software.amazon.smithy.rust.codegen.server.smithy + +import software.amazon.smithy.model.traits.PatternTrait + +@Suppress("UnusedReceiverParameter") +fun PatternTrait.validationErrorMessage(): String { + return "Value at '{}' failed to satisfy constraint: Member must satisfy regex '{}'." +} + +/** + * Escape `\`s to not end up with broken rust code in the presence of regexes with slashes. + * This turns `Regex::new("^[\S\s]+$")` into `Regex::new("^[\\S\\s]+$")`. + */ +fun PatternTrait.escapedPattern(): String { + return this.pattern.toString().replace("\\", "\\\\") +} diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt index 025ac7bd31..ad13718098 100644 --- a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt +++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt @@ -30,6 +30,7 @@ import software.amazon.smithy.rust.codegen.core.util.redactIfNecessary import software.amazon.smithy.rust.codegen.server.smithy.PubCrateConstraintViolationSymbolProvider import software.amazon.smithy.rust.codegen.server.smithy.ServerCargoDependency import software.amazon.smithy.rust.codegen.server.smithy.ServerCodegenContext +import software.amazon.smithy.rust.codegen.server.smithy.escapedPattern import software.amazon.smithy.rust.codegen.server.smithy.traits.isReachableFromOperationInput import software.amazon.smithy.rust.codegen.server.smithy.validationErrorMessage @@ -257,7 +258,7 @@ private data class TraitInfo( writer.rust( """ Self::Pattern(pattern) => crate::model::ValidationExceptionField { - message: format!("String at path {} failed to satisfy pattern {}", &path, pattern), + message: format!("${patternTrait.validationErrorMessage()}", &path, pattern), path }, """.trimIndent(), @@ -301,9 +302,7 @@ private fun renderLengthValidation(writer: RustWriter, lengthTrait: LengthTrait, * supplied regex in the `@pattern` trait. */ private fun renderPatternValidation(writer: RustWriter, patternTrait: PatternTrait, constraintViolation: Symbol) { - // Escape `\`s to not end up with broken rust code in the presence of regexes with slashes. - // This turns `Regex::new("^[\S\s]+$")` into `Regex::new("^[\\S\\s]+$")`. - val pattern = patternTrait.pattern.toString().replace("\\", "\\\\") + val pattern = patternTrait.escapedPattern() writer.rustTemplate( """ From 85ddc4c23c968b62106d78c4e938c36491d26468 Mon Sep 17 00:00:00 2001 From: Julian Antonielli Date: Fri, 18 Nov 2022 13:52:35 +0000 Subject: [PATCH 28/59] Add more tests for `@pattern` --- .../server/smithy/PatternTraitValidationErrorMessage.kt | 5 +++++ .../smithy/generators/ConstrainedStringGeneratorTest.kt | 9 ++++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/PatternTraitValidationErrorMessage.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/PatternTraitValidationErrorMessage.kt index bd345e7e8a..6e4cba3fe9 100644 --- a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/PatternTraitValidationErrorMessage.kt +++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/PatternTraitValidationErrorMessage.kt @@ -1,3 +1,8 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + package software.amazon.smithy.rust.codegen.server.smithy import software.amazon.smithy.model.traits.PatternTrait diff --git a/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGeneratorTest.kt b/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGeneratorTest.kt index 3ba301937c..21b0bf5c5f 100644 --- a/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGeneratorTest.kt +++ b/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGeneratorTest.kt @@ -45,8 +45,15 @@ class ConstrainedStringGeneratorTest { "👍👍👍", // These three emojis are three Unicode scalar values. "👍👍👍👍", ), - // Need to fix the setup to be able to add `@pattern` tests. Triple("@pattern(\"^[a-z]+$\")", "valid", "123 invalid"), + Triple( + """ + @length(min: 3, max: 10) + @pattern("^a string$) + """.trimIndent(), + "a string", "an invalid string", + ), + Triple("@pattern(\"123\")", "some pattern 123 in the middle", "no pattern at all"), ).map { TestCase( """ From a6ad449846b92488f9ab5024643c24950f9f3983 Mon Sep 17 00:00:00 2001 From: Julian Antonielli Date: Fri, 18 Nov 2022 13:55:46 +0000 Subject: [PATCH 29/59] Add entry to `CHANGELOG.next.toml` --- CHANGELOG.next.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.next.toml b/CHANGELOG.next.toml index ccd59ef313..a628b7a30f 100644 --- a/CHANGELOG.next.toml +++ b/CHANGELOG.next.toml @@ -221,12 +221,13 @@ message = """ * The `length` trait on `string` shapes. * The `length` trait on `map` shapes. +* The `pattern` trait on `string` shapes. Upon receiving a request that violates the modeled constraints, the server SDK will reject it with a message indicating why. Unsupported (constraint trait, target shape) combinations will now fail at code generation time, whereas previously they were just ignored. This is a breaking change to raise awareness in service owners of their server SDKs behaving differently than what was modeled. To continue generating a server SDK with unsupported constraint traits, set `codegenConfig.ignoreUnsupportedConstraints` to `true` in your `smithy-build.json`. """ -references = ["smithy-rs#1199", "smithy-rs#1342", "smithy-rs#1401"] +references = ["smithy-rs#1199", "smithy-rs#1342", "smithy-rs#1401", "smithy-rs#1998"] meta = { "breaking" = true, "tada" = true, "bug" = false, "target" = "server" } author = "david-perez" From 8ade3a15ad6b548630599c6f43ad726d99c1078d Mon Sep 17 00:00:00 2001 From: Julian Antonielli Date: Fri, 18 Nov 2022 14:20:47 +0000 Subject: [PATCH 30/59] Fix a `@length` + `@pattern` test case --- .../smithy/generators/ConstrainedStringGeneratorTest.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGeneratorTest.kt b/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGeneratorTest.kt index 21b0bf5c5f..77427b2024 100644 --- a/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGeneratorTest.kt +++ b/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGeneratorTest.kt @@ -30,7 +30,7 @@ class ConstrainedStringGeneratorTest { data class TestCase(val model: Model, val validString: String, val invalidString: String) class ConstrainedStringGeneratorTestProvider : ArgumentsProvider { - private val lengthTestCases = listOf( + private val testCases = listOf( // Min and max. Triple("@length(min: 11, max: 12)", "validString", "invalidString"), // Min equal to max. @@ -49,7 +49,7 @@ class ConstrainedStringGeneratorTest { Triple( """ @length(min: 3, max: 10) - @pattern("^a string$) + @pattern("^a string$") """.trimIndent(), "a string", "an invalid string", ), @@ -68,7 +68,7 @@ class ConstrainedStringGeneratorTest { } override fun provideArguments(context: ExtensionContext?): Stream = - lengthTestCases.map { Arguments.of(it) }.stream() + testCases.map { Arguments.of(it) }.stream() } @ParameterizedTest From 961e3af17a92d2164ad6e337895c42fa9ec25ec2 Mon Sep 17 00:00:00 2001 From: Julian Antonielli Date: Mon, 21 Nov 2022 09:45:39 +0000 Subject: [PATCH 31/59] Remove unused binding in `ConstrainedStringGeneratorTest` --- .../smithy/generators/ConstrainedStringGeneratorTest.kt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGeneratorTest.kt b/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGeneratorTest.kt index 77427b2024..ddf9a53d07 100644 --- a/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGeneratorTest.kt +++ b/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGeneratorTest.kt @@ -14,7 +14,6 @@ import org.junit.jupiter.params.provider.ArgumentsProvider import org.junit.jupiter.params.provider.ArgumentsSource import software.amazon.smithy.model.Model import software.amazon.smithy.model.shapes.StringShape -import software.amazon.smithy.model.traits.PatternTrait import software.amazon.smithy.rust.codegen.core.rustlang.RustWriter import software.amazon.smithy.rust.codegen.core.smithy.ModelsModule import software.amazon.smithy.rust.codegen.core.testutil.TestWorkspace @@ -26,7 +25,6 @@ import software.amazon.smithy.rust.codegen.server.smithy.testutil.serverTestCode import java.util.stream.Stream class ConstrainedStringGeneratorTest { - val a = PatternTrait("something") data class TestCase(val model: Model, val validString: String, val invalidString: String) class ConstrainedStringGeneratorTestProvider : ArgumentsProvider { @@ -50,7 +48,7 @@ class ConstrainedStringGeneratorTest { """ @length(min: 3, max: 10) @pattern("^a string$") - """.trimIndent(), + """, "a string", "an invalid string", ), Triple("@pattern(\"123\")", "some pattern 123 in the middle", "no pattern at all"), From 8a1f73f18e7a748f6363ac5b36d06791b2dcae21 Mon Sep 17 00:00:00 2001 From: Julian Antonielli Date: Mon, 21 Nov 2022 09:46:02 +0000 Subject: [PATCH 32/59] Remove calls to `trimIndent` --- .../smithy/generators/ConstrainedStringGenerator.kt | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt index ad13718098..bdb20347a0 100644 --- a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt +++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt @@ -127,7 +127,7 @@ class ConstrainedStringGenerator( pub fn into_inner(self) -> $inner { self.0 } - """.trimIndent(), + """, ) } @@ -176,7 +176,7 @@ class ConstrainedStringGenerator( """ ##[derive(Debug, PartialEq)] pub enum ${constraintViolation.name} - """.trimIndent(), + """, ) { for (traitInfo in constraintsInfo) { traitInfo.renderConstraintViolationVariant(this) @@ -240,7 +240,7 @@ private data class TraitInfo( message: format!("${lengthTrait.validationErrorMessage()}", length, &path), path, }, - """.trimIndent(), + """, ) }, { writer, constraintViolation -> renderLengthValidation(writer, lengthTrait, constraintViolation) }, @@ -261,7 +261,7 @@ private data class TraitInfo( message: format!("${patternTrait.validationErrorMessage()}", &path, pattern), path }, - """.trimIndent(), + """, ) }, { writer, constraintViolation -> renderPatternValidation(writer, patternTrait, constraintViolation) }, @@ -293,7 +293,7 @@ private fun renderLengthValidation(writer: RustWriter, lengthTrait: LengthTrait, Err($constraintViolation::Length(length)) } } - """.trimIndent(), + """, ) } @@ -315,7 +315,7 @@ private fun renderPatternValidation(writer: RustWriter, patternTrait: PatternTra Err($constraintViolation::Pattern("$pattern")) } } - """.trimIndent(), + """, "Regex" to ServerCargoDependency.Regex.toType(), "OnceCell" to ServerCargoDependency.OnceCell.toType(), ) From 1f02303310b8b0f6864238227145c89aa748c460 Mon Sep 17 00:00:00 2001 From: Julian Antonielli Date: Mon, 21 Nov 2022 09:51:43 +0000 Subject: [PATCH 33/59] Use raw Rust strings instead of `escapedPattern` --- .../server/smithy/PatternTraitValidationErrorMessage.kt | 8 -------- .../smithy/generators/ConstrainedStringGenerator.kt | 7 +++---- 2 files changed, 3 insertions(+), 12 deletions(-) diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/PatternTraitValidationErrorMessage.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/PatternTraitValidationErrorMessage.kt index 6e4cba3fe9..bb6bcb8981 100644 --- a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/PatternTraitValidationErrorMessage.kt +++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/PatternTraitValidationErrorMessage.kt @@ -11,11 +11,3 @@ import software.amazon.smithy.model.traits.PatternTrait fun PatternTrait.validationErrorMessage(): String { return "Value at '{}' failed to satisfy constraint: Member must satisfy regex '{}'." } - -/** - * Escape `\`s to not end up with broken rust code in the presence of regexes with slashes. - * This turns `Regex::new("^[\S\s]+$")` into `Regex::new("^[\\S\\s]+$")`. - */ -fun PatternTrait.escapedPattern(): String { - return this.pattern.toString().replace("\\", "\\\\") -} diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt index bdb20347a0..ef9e0daa9a 100644 --- a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt +++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt @@ -30,7 +30,6 @@ import software.amazon.smithy.rust.codegen.core.util.redactIfNecessary import software.amazon.smithy.rust.codegen.server.smithy.PubCrateConstraintViolationSymbolProvider import software.amazon.smithy.rust.codegen.server.smithy.ServerCargoDependency import software.amazon.smithy.rust.codegen.server.smithy.ServerCodegenContext -import software.amazon.smithy.rust.codegen.server.smithy.escapedPattern import software.amazon.smithy.rust.codegen.server.smithy.traits.isReachableFromOperationInput import software.amazon.smithy.rust.codegen.server.smithy.validationErrorMessage @@ -302,17 +301,17 @@ private fun renderLengthValidation(writer: RustWriter, lengthTrait: LengthTrait, * supplied regex in the `@pattern` trait. */ private fun renderPatternValidation(writer: RustWriter, patternTrait: PatternTrait, constraintViolation: Symbol) { - val pattern = patternTrait.escapedPattern() + val pattern = patternTrait.pattern writer.rustTemplate( """ fn check_pattern(string: &str) -> Result<(), $constraintViolation> { - static REGEX : #{OnceCell}::sync::Lazy<#{Regex}::Regex> = #{OnceCell}::sync::Lazy::new(|| #{Regex}::Regex::new("$pattern").unwrap()); + static REGEX : #{OnceCell}::sync::Lazy<#{Regex}::Regex> = #{OnceCell}::sync::Lazy::new(|| #{Regex}::Regex::new(r#"$pattern"#).unwrap()); if REGEX.is_match(string) { Ok(()) } else { - Err($constraintViolation::Pattern("$pattern")) + Err($constraintViolation::Pattern(r#"$pattern"#)) } } """, From dcd2528fe938c7ddcc419793c57940030952b0d8 Mon Sep 17 00:00:00 2001 From: Julian Antonielli Date: Mon, 21 Nov 2022 09:55:06 +0000 Subject: [PATCH 34/59] Require `regex 1.5.5` instead of `1.7.0` --- .../smithy/rust/codegen/server/smithy/ServerCargoDependency.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/ServerCargoDependency.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/ServerCargoDependency.kt index ef2e930dfd..a5735b81d8 100644 --- a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/ServerCargoDependency.kt +++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/ServerCargoDependency.kt @@ -25,7 +25,7 @@ object ServerCargoDependency { val PinProjectLite: CargoDependency = CargoDependency("pin-project-lite", CratesIo("0.2")) val Tower: CargoDependency = CargoDependency("tower", CratesIo("0.4")) val TokioDev: CargoDependency = CargoDependency("tokio", CratesIo("1.8.4"), scope = DependencyScope.Dev) - val Regex: CargoDependency = CargoDependency("regex", CratesIo("1.7.0")) + val Regex: CargoDependency = CargoDependency("regex", CratesIo("1.5.5")) fun SmithyHttpServer(runtimeConfig: RuntimeConfig) = runtimeConfig.runtimeCrate("http-server") } From d90f44797264b33168d464d9da4bac9f0e5c3d47 Mon Sep 17 00:00:00 2001 From: Julian Antonielli Date: Mon, 21 Nov 2022 10:37:40 +0000 Subject: [PATCH 35/59] Use `Writable`s in `TraitInfo` --- .../generators/ConstrainedStringGenerator.kt | 48 ++++++++++--------- 1 file changed, 26 insertions(+), 22 deletions(-) diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt index ef9e0daa9a..7ccf55f683 100644 --- a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt +++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt @@ -16,6 +16,7 @@ import software.amazon.smithy.rust.codegen.core.rustlang.RustModule import software.amazon.smithy.rust.codegen.core.rustlang.RustType import software.amazon.smithy.rust.codegen.core.rustlang.RustWriter import software.amazon.smithy.rust.codegen.core.rustlang.Visibility +import software.amazon.smithy.rust.codegen.core.rustlang.Writable import software.amazon.smithy.rust.codegen.core.rustlang.docs import software.amazon.smithy.rust.codegen.core.rustlang.documentShape import software.amazon.smithy.rust.codegen.core.rustlang.render @@ -64,7 +65,7 @@ class ConstrainedStringGenerator( private fun renderTryFrom(inner: String, name: String, constraintViolation: Symbol) { writer.rustBlock("impl $name") { for (traitInfo in constraintsInfo) { - traitInfo.renderValidationFunctionDefinition(writer, constraintViolation) + rustTemplate("#ValidationFunction:W", "ValidationFunction" to traitInfo.validationFunctionDefinition(constraintViolation)) } } @@ -77,7 +78,7 @@ class ConstrainedStringGenerator( """, ) { for (traitInfo in constraintsInfo) { - traitInfo.tryFromCheck(writer) + rustTemplate("#TryFromCheck:W", "TryFromCheck" to traitInfo.tryFromCheck) } rust("Ok(Self(value))") } @@ -178,7 +179,10 @@ class ConstrainedStringGenerator( """, ) { for (traitInfo in constraintsInfo) { - traitInfo.renderConstraintViolationVariant(this) + rustTemplate( + "#ConstraintViolationVariant", + "ConstraintViolationVariant:W" to traitInfo.constraintViolationVariant, + ) } } @@ -190,7 +194,7 @@ class ConstrainedStringGenerator( ) { rustBlock("match self") { for (traitInfo in constraintsInfo) { - traitInfo.asValidationExceptionField(this) + rustTemplate("#ValidationExceptionField:W", "ValidationExceptionField" to traitInfo.asValidationExceptionField) } } } @@ -203,10 +207,10 @@ class ConstrainedStringGenerator( * Information needed to render a constraint trait as Rust code. */ private data class TraitInfo( - val tryFromCheck: (writer: RustWriter) -> Unit, - val renderConstraintViolationVariant: (writer: RustWriter) -> Unit, - val asValidationExceptionField: (writer: RustWriter) -> Unit, - val renderValidationFunctionDefinition: (writer: RustWriter, constraintViolation: Symbol) -> Unit, + val tryFromCheck: Writable, + val constraintViolationVariant: Writable, + val asValidationExceptionField: Writable, + val validationFunctionDefinition: (constraintViolation: Symbol) -> Writable, ) { companion object { fun fromTrait(trait: AbstractTrait): TraitInfo? { @@ -227,13 +231,13 @@ private data class TraitInfo( private fun fromLengthTrait(lengthTrait: LengthTrait): TraitInfo { return TraitInfo( - { writer -> writer.rust("Self::check_length(&value)?;") }, - { writer -> - writer.docs("Error when a string doesn't satisfy its `@length` requirements.") - writer.rust("Length(usize),") + { rust("Self::check_length(&value)?;") }, + { + docs("Error when a string doesn't satisfy its `@length` requirements.") + rust("Length(usize),") }, - { writer -> - writer.rust( + { + rust( """ Self::Length(length) => crate::model::ValidationExceptionField { message: format!("${lengthTrait.validationErrorMessage()}", length, &path), @@ -242,19 +246,19 @@ private data class TraitInfo( """, ) }, - { writer, constraintViolation -> renderLengthValidation(writer, lengthTrait, constraintViolation) }, + { constraintViolation -> { renderLengthValidation(this, lengthTrait, constraintViolation) } }, ) } private fun fromPatternTrait(patternTrait: PatternTrait): TraitInfo { return TraitInfo( - { writer -> writer.rust("Self::check_pattern(&value)?;") }, - { writer -> - writer.docs("Error when a string doesn't satisfy its `@pattern`.") - writer.rust("Pattern(&'static str),") + { rust("Self::check_pattern(&value)?;") }, + { + docs("Error when a string doesn't satisfy its `@pattern`.") + rust("Pattern(&'static str),") }, - { writer -> - writer.rust( + { + rust( """ Self::Pattern(pattern) => crate::model::ValidationExceptionField { message: format!("${patternTrait.validationErrorMessage()}", &path, pattern), @@ -263,7 +267,7 @@ private data class TraitInfo( """, ) }, - { writer, constraintViolation -> renderPatternValidation(writer, patternTrait, constraintViolation) }, + { constraintViolation -> { renderPatternValidation(this, patternTrait, constraintViolation) } }, ) } } From 78d65125698bcaa60382bfd893bd3618019f6b9c Mon Sep 17 00:00:00 2001 From: Julian Antonielli Date: Mon, 21 Nov 2022 11:04:10 +0000 Subject: [PATCH 36/59] Use single `rust` call for `impl $name` block --- .../smithy/generators/ConstrainedStringGenerator.kt | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt index 7ccf55f683..0cde3da6e0 100644 --- a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt +++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt @@ -110,9 +110,9 @@ class ConstrainedStringGenerator( if (constrainedTypeVisibility == Visibility.PUBCRATE) { Attribute.AllowUnused.render(writer) } - writer.rustBlockTemplate("impl $name") { - rust( - """ + writer.rust( + """ + impl $name { /// Extracts a string slice containing the entire underlying `String`. pub fn as_str(&self) -> &str { &self.0 @@ -127,9 +127,8 @@ class ConstrainedStringGenerator( pub fn into_inner(self) -> $inner { self.0 } - """, - ) - } + }""", + ) renderTryFrom(inner, name, constraintViolation) From 4234b6d795536acdc8cd8956d85c2da7250f297d Mon Sep 17 00:00:00 2001 From: Julian Antonielli Date: Mon, 21 Nov 2022 11:49:34 +0000 Subject: [PATCH 37/59] Move `supportedStringConstraintTraits` to `Constraints.kt` --- .../smithy/rust/codegen/server/smithy/Constraints.kt | 6 ++++-- .../server/smithy/generators/ConstrainedStringGenerator.kt | 7 +++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/Constraints.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/Constraints.kt index c8f8684f18..35211f01fd 100644 --- a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/Constraints.kt +++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/Constraints.kt @@ -21,6 +21,7 @@ import software.amazon.smithy.model.traits.LengthTrait import software.amazon.smithy.model.traits.PatternTrait import software.amazon.smithy.model.traits.RangeTrait import software.amazon.smithy.model.traits.RequiredTrait +import software.amazon.smithy.model.traits.Trait import software.amazon.smithy.model.traits.UniqueItemsTrait import software.amazon.smithy.rust.codegen.core.smithy.isOptional import software.amazon.smithy.rust.codegen.core.util.UNREACHABLE @@ -42,6 +43,8 @@ fun Shape.hasConstraintTrait() = hasTrait() || hasTrait() +val supportedStringConstraintTraits: List> = listOf(LengthTrait::class.java, PatternTrait::class.java) + /** * We say a shape is _directly_ constrained if: * @@ -66,8 +69,7 @@ fun Shape.isDirectlyConstrained(symbolProvider: SymbolProvider): Boolean = when this.members().map { symbolProvider.toSymbol(it) }.any { !it.isOptional() } } is MapShape -> this.hasTrait() - is StringShape -> - this.hasTrait() || this.hasTrait() || this.hasTrait() + is StringShape -> this.hasTrait() || supportedStringConstraintTraits.any { this.hasTrait(it) } else -> false } diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt index 0cde3da6e0..e5b242b4ad 100644 --- a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt +++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt @@ -7,9 +7,9 @@ package software.amazon.smithy.rust.codegen.server.smithy.generators import software.amazon.smithy.codegen.core.Symbol import software.amazon.smithy.model.shapes.StringShape -import software.amazon.smithy.model.traits.AbstractTrait import software.amazon.smithy.model.traits.LengthTrait import software.amazon.smithy.model.traits.PatternTrait +import software.amazon.smithy.model.traits.Trait import software.amazon.smithy.rust.codegen.core.rustlang.Attribute import software.amazon.smithy.rust.codegen.core.rustlang.RustMetadata import software.amazon.smithy.rust.codegen.core.rustlang.RustModule @@ -31,11 +31,10 @@ import software.amazon.smithy.rust.codegen.core.util.redactIfNecessary import software.amazon.smithy.rust.codegen.server.smithy.PubCrateConstraintViolationSymbolProvider import software.amazon.smithy.rust.codegen.server.smithy.ServerCargoDependency import software.amazon.smithy.rust.codegen.server.smithy.ServerCodegenContext +import software.amazon.smithy.rust.codegen.server.smithy.supportedStringConstraintTraits import software.amazon.smithy.rust.codegen.server.smithy.traits.isReachableFromOperationInput import software.amazon.smithy.rust.codegen.server.smithy.validationErrorMessage -private val supportedStringConstraintTraits = listOf(LengthTrait::class.java, PatternTrait::class.java) - /** * [ConstrainedStringGenerator] generates a wrapper tuple newtype holding a constrained `String`. * This type can be built from unconstrained values, yielding a `ConstraintViolation` when the input does not satisfy @@ -212,7 +211,7 @@ private data class TraitInfo( val validationFunctionDefinition: (constraintViolation: Symbol) -> Writable, ) { companion object { - fun fromTrait(trait: AbstractTrait): TraitInfo? { + fun fromTrait(trait: Trait): TraitInfo? { return when (trait) { is LengthTrait -> { this.fromLengthTrait(trait) From abaf910583675066ab69349b55a3034170a77a08 Mon Sep 17 00:00:00 2001 From: Julian Antonielli Date: Mon, 21 Nov 2022 12:03:38 +0000 Subject: [PATCH 38/59] Fix error message mentioning wrong `ignoreUnsupportedConstraintTraits` key --- .../codegen/server/smithy/ValidateUnsupportedConstraints.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/ValidateUnsupportedConstraints.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/ValidateUnsupportedConstraints.kt index cdc745a8e6..014e60ed5d 100644 --- a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/ValidateUnsupportedConstraints.kt +++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/ValidateUnsupportedConstraints.kt @@ -41,7 +41,7 @@ private sealed class UnsupportedConstraintMessageKind { This is not supported in the smithy-rs server SDK. ${ if (willSupport) "It will be supported in the future." else "" } See the tracking issue ($trackingIssue). - If you want to go ahead and generate the server SDK ignoring unsupported constraint traits, set the key `ignoreUnsupportedConstraintTraits` + If you want to go ahead and generate the server SDK ignoring unsupported constraint traits, set the key `ignoreUnsupportedConstraints` inside the `runtimeConfig.codegenConfig` JSON object in your `smithy-build.json` to `true`. """.trimIndent().replace("\n", " ") From 46b126b8c5480cbce1ceba987f29c0d7896a7fb6 Mon Sep 17 00:00:00 2001 From: Julian Antonielli Date: Mon, 21 Nov 2022 13:37:32 +0000 Subject: [PATCH 39/59] Fix usage of `:W` in `rustTemplate` and raw Rust strings --- .../generators/ConstrainedStringGenerator.kt | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt index e5b242b4ad..b97970d0fd 100644 --- a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt +++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt @@ -64,7 +64,7 @@ class ConstrainedStringGenerator( private fun renderTryFrom(inner: String, name: String, constraintViolation: Symbol) { writer.rustBlock("impl $name") { for (traitInfo in constraintsInfo) { - rustTemplate("#ValidationFunction:W", "ValidationFunction" to traitInfo.validationFunctionDefinition(constraintViolation)) + rustTemplate("#{ValidationFunction:W}", "ValidationFunction" to traitInfo.validationFunctionDefinition(constraintViolation)) } } @@ -77,7 +77,7 @@ class ConstrainedStringGenerator( """, ) { for (traitInfo in constraintsInfo) { - rustTemplate("#TryFromCheck:W", "TryFromCheck" to traitInfo.tryFromCheck) + rustTemplate("#{TryFromCheck:W}", "TryFromCheck" to traitInfo.tryFromCheck) } rust("Ok(Self(value))") } @@ -178,8 +178,8 @@ class ConstrainedStringGenerator( ) { for (traitInfo in constraintsInfo) { rustTemplate( - "#ConstraintViolationVariant", - "ConstraintViolationVariant:W" to traitInfo.constraintViolationVariant, + "#{ConstraintViolationVariant:W}", + "ConstraintViolationVariant" to traitInfo.constraintViolationVariant, ) } } @@ -192,7 +192,7 @@ class ConstrainedStringGenerator( ) { rustBlock("match self") { for (traitInfo in constraintsInfo) { - rustTemplate("#ValidationExceptionField:W", "ValidationExceptionField" to traitInfo.asValidationExceptionField) + rustTemplate("#{ValidationExceptionField:W}", "ValidationExceptionField" to traitInfo.asValidationExceptionField) } } } @@ -308,12 +308,12 @@ private fun renderPatternValidation(writer: RustWriter, patternTrait: PatternTra writer.rustTemplate( """ fn check_pattern(string: &str) -> Result<(), $constraintViolation> { - static REGEX : #{OnceCell}::sync::Lazy<#{Regex}::Regex> = #{OnceCell}::sync::Lazy::new(|| #{Regex}::Regex::new(r#"$pattern"#).unwrap()); + static REGEX : #{OnceCell}::sync::Lazy<#{Regex}::Regex> = #{OnceCell}::sync::Lazy::new(|| #{Regex}::Regex::new(r##"$pattern"##).unwrap()); if REGEX.is_match(string) { Ok(()) } else { - Err($constraintViolation::Pattern(r#"$pattern"#)) + Err($constraintViolation::Pattern(r##"$pattern"##)) } } """, From d5511680aa165d6f3422e0cf46fdd0c28b41e1ac Mon Sep 17 00:00:00 2001 From: Julian Antonielli Date: Mon, 21 Nov 2022 13:42:36 +0000 Subject: [PATCH 40/59] Use shorthand form for `PatternTrait.validationErrorMessage` --- .../server/smithy/PatternTraitValidationErrorMessage.kt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/PatternTraitValidationErrorMessage.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/PatternTraitValidationErrorMessage.kt index bb6bcb8981..9bace222dd 100644 --- a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/PatternTraitValidationErrorMessage.kt +++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/PatternTraitValidationErrorMessage.kt @@ -8,6 +8,5 @@ package software.amazon.smithy.rust.codegen.server.smithy import software.amazon.smithy.model.traits.PatternTrait @Suppress("UnusedReceiverParameter") -fun PatternTrait.validationErrorMessage(): String { - return "Value at '{}' failed to satisfy constraint: Member must satisfy regex '{}'." -} +fun PatternTrait.validationErrorMessage(): String = + "Value at '{}' failed to satisfy constraint: Member must satisfy regex '{}'." From 76e5984dea2fe32a689c7f3caa2779bcaa9648b2 Mon Sep 17 00:00:00 2001 From: Julian Antonielli Date: Mon, 21 Nov 2022 13:54:20 +0000 Subject: [PATCH 41/59] Fix protocol tests by adjusting the validation message --- .../PatternTraitValidationErrorMessage.kt | 2 +- .../generators/ConstrainedStringGenerator.kt | 19 +++++++++++-------- .../protocol/ServerProtocolTestGenerator.kt | 10 ---------- 3 files changed, 12 insertions(+), 19 deletions(-) diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/PatternTraitValidationErrorMessage.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/PatternTraitValidationErrorMessage.kt index 9bace222dd..8bb3cb648e 100644 --- a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/PatternTraitValidationErrorMessage.kt +++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/PatternTraitValidationErrorMessage.kt @@ -9,4 +9,4 @@ import software.amazon.smithy.model.traits.PatternTrait @Suppress("UnusedReceiverParameter") fun PatternTrait.validationErrorMessage(): String = - "Value at '{}' failed to satisfy constraint: Member must satisfy regex '{}'." + "Value {} at '{}' failed to satisfy constraint: Member must satisfy regular expression pattern: {}" diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt index b97970d0fd..9b8a8ec911 100644 --- a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt +++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt @@ -249,17 +249,20 @@ private data class TraitInfo( } private fun fromPatternTrait(patternTrait: PatternTrait): TraitInfo { + val pattern = patternTrait.pattern + return TraitInfo( - { rust("Self::check_pattern(&value)?;") }, + { rust("let value = Self::check_pattern(value)?;") }, { docs("Error when a string doesn't satisfy its `@pattern`.") - rust("Pattern(&'static str),") + docs("Contains the String that failed the pattern.") + rust("Pattern(String),") }, { rust( """ - Self::Pattern(pattern) => crate::model::ValidationExceptionField { - message: format!("${patternTrait.validationErrorMessage()}", &path, pattern), + Self::Pattern(string) => crate::model::ValidationExceptionField { + message: format!("${patternTrait.validationErrorMessage()}", &string, &path, r##"$pattern"##), path }, """, @@ -307,13 +310,13 @@ private fun renderPatternValidation(writer: RustWriter, patternTrait: PatternTra writer.rustTemplate( """ - fn check_pattern(string: &str) -> Result<(), $constraintViolation> { + fn check_pattern(string: String) -> Result { static REGEX : #{OnceCell}::sync::Lazy<#{Regex}::Regex> = #{OnceCell}::sync::Lazy::new(|| #{Regex}::Regex::new(r##"$pattern"##).unwrap()); - if REGEX.is_match(string) { - Ok(()) + if REGEX.is_match(&string) { + Ok(string) } else { - Err($constraintViolation::Pattern(r##"$pattern"##)) + Err($constraintViolation::Pattern(string)) } } """, 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 cf981edb73..2d61e8011f 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 @@ -969,17 +969,7 @@ class ServerProtocolTestGenerator( FailingTest(RestJsonValidation, "RestJsonMalformedPatternStringOverride_case1", TestType.MalformedRequest), FailingTest(RestJsonValidation, "RestJsonMalformedPatternUnionOverride_case0", TestType.MalformedRequest), FailingTest(RestJsonValidation, "RestJsonMalformedPatternUnionOverride_case1", TestType.MalformedRequest), - FailingTest(RestJsonValidation, "RestJsonMalformedPatternList_case0", TestType.MalformedRequest), - FailingTest(RestJsonValidation, "RestJsonMalformedPatternList_case1", TestType.MalformedRequest), - FailingTest(RestJsonValidation, "RestJsonMalformedPatternMapKey_case0", TestType.MalformedRequest), - FailingTest(RestJsonValidation, "RestJsonMalformedPatternMapKey_case1", TestType.MalformedRequest), - FailingTest(RestJsonValidation, "RestJsonMalformedPatternMapValue_case0", TestType.MalformedRequest), - FailingTest(RestJsonValidation, "RestJsonMalformedPatternMapValue_case1", TestType.MalformedRequest), FailingTest(RestJsonValidation, "RestJsonMalformedPatternReDOSString", TestType.MalformedRequest), - FailingTest(RestJsonValidation, "RestJsonMalformedPatternString_case0", TestType.MalformedRequest), - FailingTest(RestJsonValidation, "RestJsonMalformedPatternString_case1", TestType.MalformedRequest), - FailingTest(RestJsonValidation, "RestJsonMalformedPatternUnion_case0", TestType.MalformedRequest), - FailingTest(RestJsonValidation, "RestJsonMalformedPatternUnion_case1", TestType.MalformedRequest), FailingTest(RestJsonValidation, "RestJsonMalformedRangeByteOverride_case0", TestType.MalformedRequest), FailingTest(RestJsonValidation, "RestJsonMalformedRangeByteOverride_case1", TestType.MalformedRequest), FailingTest(RestJsonValidation, "RestJsonMalformedRangeFloatOverride_case0", TestType.MalformedRequest), From 227ddaf74aa23ef9c95eabcf3bcc4c5388ba3101 Mon Sep 17 00:00:00 2001 From: Julian Antonielli Date: Mon, 21 Nov 2022 15:48:13 +0000 Subject: [PATCH 42/59] Add uses of `@pattern` in `constraints.smithy` model --- .../common-test-models/constraints.smithy | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/codegen-core/common-test-models/constraints.smithy b/codegen-core/common-test-models/constraints.smithy index d43ea8b7b2..98486c989a 100644 --- a/codegen-core/common-test-models/constraints.smithy +++ b/codegen-core/common-test-models/constraints.smithy @@ -23,6 +23,10 @@ service ConstraintsService { QueryParamsTargetingMapOfListOfLengthStringOperation, QueryParamsTargetingMapOfSetOfLengthStringOperation, QueryParamsTargetingMapOfListOfEnumStringOperation, + + QueryParamsTargetingMapOfPatternStringOperation, + QueryParamsTargetingMapOfLengthPatternStringOperation, + HttpPrefixHeadersTargetingLengthMapOperation, // TODO(https://github.com/awslabs/smithy-rs/issues/1431) // HttpPrefixHeadersTargetingMapOfEnumStringOperation, @@ -97,6 +101,20 @@ operation QueryParamsTargetingMapOfListOfEnumStringOperation { errors: [ValidationException] } +@http(uri: "/query-params-targeting-map-of-pattern-string", method: "POST") +operation QueryParamsTargetingMapOfPatternStringOperation { + input: QueryParamsTargetingMapOfPatternStringOperationInputOutput, + output: QueryParamsTargetingMapOfPatternStringOperationInputOutput, + errors: [ValidationException] +} + +@http(uri: "/query-params-targeting-map-of-length-pattern-string", method: "POST") +operation QueryParamsTargetingMapOfLengthPatternStringOperation { + input: QueryParamsTargetingMapOfLengthPatternStringOperationInputOutput, + output: QueryParamsTargetingMapOfLengthPatternStringOperationInputOutput, + errors: [ValidationException], +} + @http(uri: "/http-prefix-headers-targeting-length-map-operation", method: "POST") operation HttpPrefixHeadersTargetingLengthMapOperation { input: HttpPrefixHeadersTargetingLengthMapOperationInputOutput, @@ -183,6 +201,16 @@ structure ConstrainedHttpBoundShapesOperationInputOutput { enumStringListQuery: ListOfEnumString, } +structure QueryParamsTargetingMapOfPatternStringOperationInputOutput { + @httpQueryParams + mapOfPatternString: MapOfPatternString +} + +structure QueryParamsTargetingMapOfLengthPatternStringOperationInputOutput { + @httpQueryParams + mapOfLengthPatternString: MapOfLengthPatternString, +} + structure HttpPrefixHeadersTargetingLengthMapOperationInputOutput { @httpPrefixHeaders("X-Prefix-Headers-LengthMap-") lengthMap: ConBMap, @@ -335,6 +363,13 @@ string MaxLengthString @length(min: 69, max: 69) string FixedLengthString +@pattern("[a-d]{5}") +string PatternString + +@pattern("[a-f0-5]*") +@length(min:5, max: 10) +string LengthPatternString + @mediaType("video/quicktime") @length(min: 1, max: 69) string MediaTypeLengthString @@ -428,6 +463,16 @@ set NestedSet { member: String } +map MapOfPatternString { + key: String, + value: PatternString, +} + +map MapOfLengthPatternString { + key: String, + value: LengthPatternString, +} + @length(min: 1, max: 69) map ConBMap { key: String, From e89850f584f84504ca73ed07e29e0d0aa5d8b096 Mon Sep 17 00:00:00 2001 From: Julian Antonielli Date: Mon, 21 Nov 2022 16:46:23 +0000 Subject: [PATCH 43/59] Fix broken `RestJsonMalformedPatternReDOSString` test --- .../protocol/ServerProtocolTestGenerator.kt | 36 +++++++++++++++++-- 1 file changed, 33 insertions(+), 3 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 2d61e8011f..ad62f1c8f7 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 @@ -18,6 +18,7 @@ import software.amazon.smithy.model.traits.ErrorTrait import software.amazon.smithy.protocoltests.traits.AppliesTo import software.amazon.smithy.protocoltests.traits.HttpMalformedRequestTestCase import software.amazon.smithy.protocoltests.traits.HttpMalformedRequestTestsTrait +import software.amazon.smithy.protocoltests.traits.HttpMalformedResponseBodyDefinition import software.amazon.smithy.protocoltests.traits.HttpMalformedResponseDefinition import software.amazon.smithy.protocoltests.traits.HttpRequestTestCase import software.amazon.smithy.protocoltests.traits.HttpRequestTestsTrait @@ -339,8 +340,13 @@ class ServerProtocolTestGenerator( } is TestCase.MalformedRequestTest -> { - // We haven't found any broken `HttpMalformedRequestTest`s yet. - it + val howToFixIt = BrokenMalformedRequestTests[Pair(codegenContext.serviceShape.id.toString(), it.id)] + if (howToFixIt == null) { + it + } else { + val fixed = howToFixIt(it.testCase) + TestCase.MalformedRequestTest(fixed) + } } } } @@ -969,7 +975,6 @@ class ServerProtocolTestGenerator( FailingTest(RestJsonValidation, "RestJsonMalformedPatternStringOverride_case1", TestType.MalformedRequest), FailingTest(RestJsonValidation, "RestJsonMalformedPatternUnionOverride_case0", TestType.MalformedRequest), FailingTest(RestJsonValidation, "RestJsonMalformedPatternUnionOverride_case1", TestType.MalformedRequest), - FailingTest(RestJsonValidation, "RestJsonMalformedPatternReDOSString", TestType.MalformedRequest), FailingTest(RestJsonValidation, "RestJsonMalformedRangeByteOverride_case0", TestType.MalformedRequest), FailingTest(RestJsonValidation, "RestJsonMalformedRangeByteOverride_case1", TestType.MalformedRequest), FailingTest(RestJsonValidation, "RestJsonMalformedRangeFloatOverride_case0", TestType.MalformedRequest), @@ -1166,6 +1171,25 @@ class ServerProtocolTestGenerator( private fun fixRestJsonComplexErrorWithNoMessage(testCase: HttpResponseTestCase): HttpResponseTestCase = testCase.toBuilder().putHeader("X-Amzn-Errortype", "aws.protocoltests.restjson#ComplexError").build() + // TODO(https://github.com/awslabs/smithy/issues/1506) + private fun fixRestJsonMalformedPatternReDOSString(testCase: HttpMalformedRequestTestCase): HttpMalformedRequestTestCase { + val brokenResponse = testCase.response + val brokenBody = brokenResponse.body.get() + val fixedBody = HttpMalformedResponseBodyDefinition.builder() + .mediaType(brokenBody.mediaType) + .contents( + """ + { "message" : "1 validation error detected. Value 000000000000000000000000000000000000000000000000000000000000000000000000000000000000! at '/evilString' failed to satisfy constraint: Member must satisfy regular expression pattern: ^([0-9]+)+${'$'}", + "fieldList" : [{"message": "Value 000000000000000000000000000000000000000000000000000000000000000000000000000000000000! at '/evilString' failed to satisfy constraint: Member must satisfy regular expression pattern: ^([0-9]+)+${'$'}", "path": "/evilString"}]} + """.trimIndent(), + ) + .build() + + return testCase.toBuilder() + .response(brokenResponse.toBuilder().body(fixedBody).build()) + .build() + } + // These are tests whose definitions in the `awslabs/smithy` repository are wrong. // This is because they have not been written from a server perspective, and as such the expected `params` field is incomplete. // TODO(https://github.com/awslabs/smithy-rs/issues/1288): Contribute a PR to fix them upstream. @@ -1248,5 +1272,11 @@ class ServerProtocolTestGenerator( Pair(RestJson, "RestJsonEmptyComplexErrorWithNoMessage") to ::fixRestJsonEmptyComplexErrorWithNoMessage, Pair(RestJson, "RestJsonComplexErrorWithNoMessage") to ::fixRestJsonComplexErrorWithNoMessage, ) + + private val BrokenMalformedRequestTests: Map, KFunction1> = + // TODO(https://github.com/awslabs/smithy/issues/1506) + mapOf( + Pair(RestJsonValidation, "RestJsonMalformedPatternReDOSString") to ::fixRestJsonMalformedPatternReDOSString, + ) } } From 995bf0f39043869372355b0538d640b1ce19733e Mon Sep 17 00:00:00 2001 From: Julian Antonielli Date: Mon, 21 Nov 2022 17:31:43 +0000 Subject: [PATCH 44/59] Move `TraitInfo` to its own module and separate string-specific details --- .../generators/ConstrainedStringGenerator.kt | 216 +++++++++--------- .../server/smithy/generators/TraitInfo.kt | 15 ++ 2 files changed, 122 insertions(+), 109 deletions(-) create mode 100644 codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/TraitInfo.kt diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt index 9b8a8ec911..218ac72164 100644 --- a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt +++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt @@ -59,7 +59,8 @@ class ConstrainedStringGenerator( private val constraintsInfo: List = supportedStringConstraintTraits .mapNotNull { shape.getTrait(it).orNull() } - .mapNotNull(TraitInfo::fromTrait) + .mapNotNull(StringTraitInfo::fromTrait) + .map(StringTraitInfo::toTraitInfo) private fun renderTryFrom(inner: String, name: String, constraintViolation: Symbol) { writer.rustBlock("impl $name") { @@ -200,127 +201,124 @@ class ConstrainedStringGenerator( } } } +private data class Length(val lengthTrait: LengthTrait) : StringTraitInfo() { + override fun toTraitInfo(): TraitInfo { + return TraitInfo( + { rust("Self::check_length(&value)?;") }, + { + docs("Error when a string doesn't satisfy its `@length` requirements.") + rust("Length(usize),") + }, + { + rust( + """ + Self::Length(length) => crate::model::ValidationExceptionField { + message: format!("${lengthTrait.validationErrorMessage()}", length, &path), + path, + }, + """, + ) + }, + this::renderValidationFunction, + ) + } -/** - * Information needed to render a constraint trait as Rust code. - */ -private data class TraitInfo( - val tryFromCheck: Writable, - val constraintViolationVariant: Writable, - val asValidationExceptionField: Writable, - val validationFunctionDefinition: (constraintViolation: Symbol) -> Writable, -) { - companion object { - fun fromTrait(trait: Trait): TraitInfo? { - return when (trait) { - is LengthTrait -> { - this.fromLengthTrait(trait) - } - - is PatternTrait -> { - this.fromPatternTrait(trait) - } - - else -> { - null - } + /** + * Renders a `check_length` function to validate the string matches the + * required length indicated by the `@length` trait. + */ + private fun renderValidationFunction(constraintViolation: Symbol): Writable { + return { + val condition = if (lengthTrait.min.isPresent && lengthTrait.max.isPresent) { + "(${lengthTrait.min.get()}..=${lengthTrait.max.get()}).contains(&length)" + } else if (lengthTrait.min.isPresent) { + "${lengthTrait.min.get()} <= length" + } else { + "length <= ${lengthTrait.max.get()}" } - } - private fun fromLengthTrait(lengthTrait: LengthTrait): TraitInfo { - return TraitInfo( - { rust("Self::check_length(&value)?;") }, - { - docs("Error when a string doesn't satisfy its `@length` requirements.") - rust("Length(usize),") - }, - { - rust( - """ - Self::Length(length) => crate::model::ValidationExceptionField { - message: format!("${lengthTrait.validationErrorMessage()}", length, &path), - path, - }, - """, - ) - }, - { constraintViolation -> { renderLengthValidation(this, lengthTrait, constraintViolation) } }, - ) - } - - private fun fromPatternTrait(patternTrait: PatternTrait): TraitInfo { - val pattern = patternTrait.pattern + rust( + """ + fn check_length(string: &str) -> Result<(), $constraintViolation> { + let length = string.chars().count(); - return TraitInfo( - { rust("let value = Self::check_pattern(value)?;") }, - { - docs("Error when a string doesn't satisfy its `@pattern`.") - docs("Contains the String that failed the pattern.") - rust("Pattern(String),") - }, - { - rust( - """ - Self::Pattern(string) => crate::model::ValidationExceptionField { - message: format!("${patternTrait.validationErrorMessage()}", &string, &path, r##"$pattern"##), - path - }, - """, - ) - }, - { constraintViolation -> { renderPatternValidation(this, patternTrait, constraintViolation) } }, + if $condition { + Ok(()) + } else { + Err($constraintViolation::Length(length)) + } + } + """, ) } } } -/** - * Renders a `check_length` function to validate the string matches the - * required length indicated by the `@length` trait. - */ -private fun renderLengthValidation(writer: RustWriter, lengthTrait: LengthTrait, constraintViolation: Symbol) { - val condition = if (lengthTrait.min.isPresent && lengthTrait.max.isPresent) { - "(${lengthTrait.min.get()}..=${lengthTrait.max.get()}).contains(&length)" - } else if (lengthTrait.min.isPresent) { - "${lengthTrait.min.get()} <= length" - } else { - "length <= ${lengthTrait.max.get()}" +private data class Pattern(val patternTrait: PatternTrait) : StringTraitInfo() { + override fun toTraitInfo(): TraitInfo { + val pattern = patternTrait.pattern + + return TraitInfo( + { rust("let value = Self::check_pattern(value)?;") }, + { + docs("Error when a string doesn't satisfy its `@pattern`.") + docs("Contains the String that failed the pattern.") + rust("Pattern(String),") + }, + { + rust( + """ + Self::Pattern(string) => crate::model::ValidationExceptionField { + message: format!("${patternTrait.validationErrorMessage()}", &string, &path, r##"$pattern"##), + path + }, + """, + ) + }, + this::renderValidationFunction, + ) } - writer.rust( - """ - fn check_length(string: &str) -> Result<(), $constraintViolation> { - let length = string.chars().count(); - if $condition { - Ok(()) - } else { - Err($constraintViolation::Length(length)) - } - } - """, - ) -} + /** + * Renders a `check_pattern` function to validate the string matches the + * supplied regex in the `@pattern` trait. + */ + private fun renderValidationFunction(constraintViolation: Symbol): Writable { + val pattern = patternTrait.pattern -/** - * Renders a `check_pattern` function to validate the string matches the - * supplied regex in the `@pattern` trait. - */ -private fun renderPatternValidation(writer: RustWriter, patternTrait: PatternTrait, constraintViolation: Symbol) { - val pattern = patternTrait.pattern + return { + rustTemplate( + """ + fn check_pattern(string: String) -> Result { + static REGEX : #{OnceCell}::sync::Lazy<#{Regex}::Regex> = #{OnceCell}::sync::Lazy::new(|| #{Regex}::Regex::new(r##"$pattern"##).unwrap()); - writer.rustTemplate( - """ - fn check_pattern(string: String) -> Result { - static REGEX : #{OnceCell}::sync::Lazy<#{Regex}::Regex> = #{OnceCell}::sync::Lazy::new(|| #{Regex}::Regex::new(r##"$pattern"##).unwrap()); + if REGEX.is_match(&string) { + Ok(string) + } else { + Err($constraintViolation::Pattern(string)) + } + } + """, + "Regex" to ServerCargoDependency.Regex.toType(), + "OnceCell" to ServerCargoDependency.OnceCell.toType(), + ) + } + } +} - if REGEX.is_match(&string) { - Ok(string) - } else { - Err($constraintViolation::Pattern(string)) +private sealed class StringTraitInfo { + companion object { + fun fromTrait(trait: Trait): StringTraitInfo? = + when (trait) { + is PatternTrait -> { + Pattern(trait) + } + is LengthTrait -> { + Length(trait) + } + else -> null } - } - """, - "Regex" to ServerCargoDependency.Regex.toType(), - "OnceCell" to ServerCargoDependency.OnceCell.toType(), - ) + } + + abstract fun toTraitInfo(): TraitInfo } diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/TraitInfo.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/TraitInfo.kt new file mode 100644 index 0000000000..3b22531d3c --- /dev/null +++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/TraitInfo.kt @@ -0,0 +1,15 @@ +package software.amazon.smithy.rust.codegen.server.smithy.generators + +import software.amazon.smithy.codegen.core.Symbol +import software.amazon.smithy.rust.codegen.core.rustlang.Writable +import software.amazon.smithy.rust.codegen.core.rustlang.rust + +/** + * Information needed to render a constraint trait as Rust code. + */ +data class TraitInfo( + val tryFromCheck: Writable, + val constraintViolationVariant: Writable, + val asValidationExceptionField: Writable, + val validationFunctionDefinition: (constraintViolation: Symbol) -> Writable, +) From 3e45b9e6e23f602ec6046a9eb5f93912a6f6ca35 Mon Sep 17 00:00:00 2001 From: Julian Antonielli Date: Tue, 22 Nov 2022 13:46:38 +0000 Subject: [PATCH 45/59] Update codegen-core/common-test-models/constraints.smithy Co-authored-by: david-perez --- codegen-core/common-test-models/constraints.smithy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codegen-core/common-test-models/constraints.smithy b/codegen-core/common-test-models/constraints.smithy index 4fd625ce73..ef2fbfac9c 100644 --- a/codegen-core/common-test-models/constraints.smithy +++ b/codegen-core/common-test-models/constraints.smithy @@ -378,7 +378,7 @@ string FixedLengthString string PatternString @pattern("[a-f0-5]*") -@length(min:5, max: 10) +@length(min: 5, max: 10) string LengthPatternString @mediaType("video/quicktime") From 309423f9331c9f5d772e276e81bf63741c42bf3f Mon Sep 17 00:00:00 2001 From: Julian Antonielli Date: Tue, 22 Nov 2022 13:48:20 +0000 Subject: [PATCH 46/59] Fix nits in `constraints.smithy` --- codegen-core/common-test-models/constraints.smithy | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/codegen-core/common-test-models/constraints.smithy b/codegen-core/common-test-models/constraints.smithy index 4fd625ce73..8870d4bdb0 100644 --- a/codegen-core/common-test-models/constraints.smithy +++ b/codegen-core/common-test-models/constraints.smithy @@ -101,7 +101,7 @@ operation QueryParamsTargetingMapOfListOfEnumStringOperation { errors: [ValidationException] } -@http(uri: "/query-params-targeting-map-of-pattern-string", method: "POST") +@http(uri: "/query-params-targeting-map-of-pattern-string-operation", method: "POST") operation QueryParamsTargetingMapOfPatternStringOperation { input: QueryParamsTargetingMapOfPatternStringOperationInputOutput, output: QueryParamsTargetingMapOfPatternStringOperationInputOutput, @@ -378,7 +378,7 @@ string FixedLengthString string PatternString @pattern("[a-f0-5]*") -@length(min:5, max: 10) +@length(min: 5, max: 10) string LengthPatternString @mediaType("video/quicktime") From 7a95fe2bac9533cc3d09f9d99bf9af4438d66b5d Mon Sep 17 00:00:00 2001 From: Julian Antonielli Date: Tue, 22 Nov 2022 13:48:42 +0000 Subject: [PATCH 47/59] Add license to `TraitInfo.kt` --- .../rust/codegen/server/smithy/generators/TraitInfo.kt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/TraitInfo.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/TraitInfo.kt index 3b22531d3c..3390382ba1 100644 --- a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/TraitInfo.kt +++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/TraitInfo.kt @@ -1,3 +1,8 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + package software.amazon.smithy.rust.codegen.server.smithy.generators import software.amazon.smithy.codegen.core.Symbol From bbb1b818a98813d0b1168157e382baec1e17f02d Mon Sep 17 00:00:00 2001 From: Julian Antonielli Date: Tue, 22 Nov 2022 13:53:19 +0000 Subject: [PATCH 48/59] Make `StringTraitInfo.fromTrait` not return a nullable --- .../server/smithy/generators/ConstrainedStringGenerator.kt | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt index 218ac72164..c9e5de6be4 100644 --- a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt +++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt @@ -26,6 +26,7 @@ import software.amazon.smithy.rust.codegen.core.rustlang.rustBlockTemplate import software.amazon.smithy.rust.codegen.core.rustlang.rustTemplate import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType import software.amazon.smithy.rust.codegen.core.smithy.makeMaybeConstrained +import software.amazon.smithy.rust.codegen.core.util.PANIC import software.amazon.smithy.rust.codegen.core.util.orNull import software.amazon.smithy.rust.codegen.core.util.redactIfNecessary import software.amazon.smithy.rust.codegen.server.smithy.PubCrateConstraintViolationSymbolProvider @@ -59,7 +60,7 @@ class ConstrainedStringGenerator( private val constraintsInfo: List = supportedStringConstraintTraits .mapNotNull { shape.getTrait(it).orNull() } - .mapNotNull(StringTraitInfo::fromTrait) + .map(StringTraitInfo::fromTrait) .map(StringTraitInfo::toTraitInfo) private fun renderTryFrom(inner: String, name: String, constraintViolation: Symbol) { @@ -308,7 +309,7 @@ private data class Pattern(val patternTrait: PatternTrait) : StringTraitInfo() { private sealed class StringTraitInfo { companion object { - fun fromTrait(trait: Trait): StringTraitInfo? = + fun fromTrait(trait: Trait): StringTraitInfo = when (trait) { is PatternTrait -> { Pattern(trait) @@ -316,7 +317,7 @@ private sealed class StringTraitInfo { is LengthTrait -> { Length(trait) } - else -> null + else -> PANIC("StringTraitInfo.fromTrait called with unsupported trait $trait") } } From fdcd2b76b682dd240d143bfcb2fcc47e75085357 Mon Sep 17 00:00:00 2001 From: Julian Antonielli Date: Tue, 22 Nov 2022 14:09:58 +0000 Subject: [PATCH 49/59] Remove overzealous formatting --- .../smithy/rust/codegen/server/smithy/ConstraintsTest.kt | 5 ----- 1 file changed, 5 deletions(-) diff --git a/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/ConstraintsTest.kt b/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/ConstraintsTest.kt index dfd5e6c002..76821a90dd 100644 --- a/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/ConstraintsTest.kt +++ b/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/ConstraintsTest.kt @@ -34,7 +34,6 @@ class ConstraintsTest { structure TestInputOutput { map: MapA, - recursive: RecursiveShape } @@ -68,7 +67,6 @@ class ConstraintsTest { structure StructureA { @range(min: 1, max: 69) int: Integer, - @required string: String } @@ -77,12 +75,9 @@ class ConstraintsTest { structure StructureB { @pattern("\\w+") patternString: String, - @required requiredString: String, - mapA: MapA, - @length(min: 1, max: 5) mapAPrecedence: MapA } From 4a06c7735a3ed023f3a71915de71112f58116b25 Mon Sep 17 00:00:00 2001 From: Julian Antonielli Date: Tue, 22 Nov 2022 14:11:08 +0000 Subject: [PATCH 50/59] Add some padding to json in `fixRestJsonMalformedPatternReDOSString` --- .../generators/protocol/ServerProtocolTestGenerator.kt | 6 ++++-- 1 file changed, 4 insertions(+), 2 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 ad62f1c8f7..01c5df10e5 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 @@ -1179,8 +1179,10 @@ class ServerProtocolTestGenerator( .mediaType(brokenBody.mediaType) .contents( """ - { "message" : "1 validation error detected. Value 000000000000000000000000000000000000000000000000000000000000000000000000000000000000! at '/evilString' failed to satisfy constraint: Member must satisfy regular expression pattern: ^([0-9]+)+${'$'}", - "fieldList" : [{"message": "Value 000000000000000000000000000000000000000000000000000000000000000000000000000000000000! at '/evilString' failed to satisfy constraint: Member must satisfy regular expression pattern: ^([0-9]+)+${'$'}", "path": "/evilString"}]} + { + "message" : "1 validation error detected. Value 000000000000000000000000000000000000000000000000000000000000000000000000000000000000! at '/evilString' failed to satisfy constraint: Member must satisfy regular expression pattern: ^([0-9]+)+${'$'}", + "fieldList" : [{"message": "Value 000000000000000000000000000000000000000000000000000000000000000000000000000000000000! at '/evilString' failed to satisfy constraint: Member must satisfy regular expression pattern: ^([0-9]+)+${'$'}", "path": "/evilString"}] + } """.trimIndent(), ) .build() From 750e49d62fe2cc5b0777772ecd2141a80661774b Mon Sep 17 00:00:00 2001 From: Julian Antonielli Date: Tue, 22 Nov 2022 14:49:59 +0000 Subject: [PATCH 51/59] Add `compile_regex` function for `@pattern` strings --- .../smithy/generators/ConstrainedStringGenerator.kt | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt index c9e5de6be4..fd4ae2e191 100644 --- a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt +++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt @@ -291,14 +291,20 @@ private data class Pattern(val patternTrait: PatternTrait) : StringTraitInfo() { rustTemplate( """ fn check_pattern(string: String) -> Result { - static REGEX : #{OnceCell}::sync::Lazy<#{Regex}::Regex> = #{OnceCell}::sync::Lazy::new(|| #{Regex}::Regex::new(r##"$pattern"##).unwrap()); + let regex = Self::compile_regex(); - if REGEX.is_match(&string) { + if regex.is_match(&string) { Ok(string) } else { Err($constraintViolation::Pattern(string)) } } + + pub fn compile_regex() -> &'static #{Regex}::Regex { + static REGEX: #{OnceCell}::sync::OnceCell<#{Regex}::Regex> = #{OnceCell}::sync::OnceCell::new(); + + REGEX.get_or_init(|| #{Regex}::Regex::new(r##"$pattern"##).unwrap()) + } """, "Regex" to ServerCargoDependency.Regex.toType(), "OnceCell" to ServerCargoDependency.OnceCell.toType(), From 11ee326a0b36cd5948776f29633d43ece8699041 Mon Sep 17 00:00:00 2001 From: Julian Antonielli Date: Tue, 22 Nov 2022 15:00:03 +0000 Subject: [PATCH 52/59] Add helpful error message when regexes fail to compile --- .../server/smithy/generators/ConstrainedStringGenerator.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt index fd4ae2e191..041b00803a 100644 --- a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt +++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt @@ -303,7 +303,10 @@ private data class Pattern(val patternTrait: PatternTrait) : StringTraitInfo() { pub fn compile_regex() -> &'static #{Regex}::Regex { static REGEX: #{OnceCell}::sync::OnceCell<#{Regex}::Regex> = #{OnceCell}::sync::OnceCell::new(); - REGEX.get_or_init(|| #{Regex}::Regex::new(r##"$pattern"##).unwrap()) + REGEX.get_or_init(|| + #{Regex}::Regex::new(r##"$pattern"##) + .unwrap_or_else(|_| panic!("The regular expression {} is not supported by the `regex` crate; feel free to file an issue under https://github.com/awslabs/smithy-rs/issues for support", r##"$pattern"##)) + ) } """, "Regex" to ServerCargoDependency.Regex.toType(), From cb4d4eef4da911b82c7182ee6b4415a130e4c580 Mon Sep 17 00:00:00 2001 From: Julian Antonielli Date: Tue, 22 Nov 2022 17:20:16 +0000 Subject: [PATCH 53/59] Add missing coverage in `constraints.smithy` --- .../common-test-models/constraints.smithy | 68 ++++++++++++++++++- 1 file changed, 67 insertions(+), 1 deletion(-) diff --git a/codegen-core/common-test-models/constraints.smithy b/codegen-core/common-test-models/constraints.smithy index 8870d4bdb0..8254bf5bbf 100644 --- a/codegen-core/common-test-models/constraints.smithy +++ b/codegen-core/common-test-models/constraints.smithy @@ -25,7 +25,9 @@ service ConstraintsService { QueryParamsTargetingMapOfListOfEnumStringOperation, QueryParamsTargetingMapOfPatternStringOperation, + QueryParamsTargetingMapOfListOfPatternStringOperation, QueryParamsTargetingMapOfLengthPatternStringOperation, + QueryParamsTargetingMapOfListOfLengthPatternStringOperation, HttpPrefixHeadersTargetingLengthMapOperation, // TODO(https://github.com/awslabs/smithy-rs/issues/1431) @@ -108,6 +110,13 @@ operation QueryParamsTargetingMapOfPatternStringOperation { errors: [ValidationException] } +@http(uri: "/query-params-targeting-map-of-list-of-pattern-string-operation", method: "POST") +operation QueryParamsTargetingMapOfListOfPatternStringOperation { + input: QueryParamsTargetingMapOfListOfPatternStringOperationInputOutput, + output: QueryParamsTargetingMapOfListOfPatternStringOperationInputOutput, + errors: [ValidationException] +} + @http(uri: "/query-params-targeting-map-of-length-pattern-string", method: "POST") operation QueryParamsTargetingMapOfLengthPatternStringOperation { input: QueryParamsTargetingMapOfLengthPatternStringOperationInputOutput, @@ -115,6 +124,13 @@ operation QueryParamsTargetingMapOfLengthPatternStringOperation { errors: [ValidationException], } +@http(uri: "/query-params-targeting-map-of-list-of-length-pattern-string-operation", method: "POST") +operation QueryParamsTargetingMapOfListOfLengthPatternStringOperation { + input: QueryParamsTargetingMapOfListOfLengthPatternStringOperationInputOutput, + output: QueryParamsTargetingMapOfListOfLengthPatternStringOperationInputOutput, + errors: [ValidationException] +} + @http(uri: "/http-prefix-headers-targeting-length-map-operation", method: "POST") operation HttpPrefixHeadersTargetingLengthMapOperation { input: HttpPrefixHeadersTargetingLengthMapOperationInputOutput, @@ -210,11 +226,21 @@ structure QueryParamsTargetingMapOfPatternStringOperationInputOutput { mapOfPatternString: MapOfPatternString } +structure QueryParamsTargetingMapOfListOfPatternStringOperationInputOutput { + @httpQueryParams + mapOfListOfPatternString: MapOfListOfPatternString +} + structure QueryParamsTargetingMapOfLengthPatternStringOperationInputOutput { @httpQueryParams mapOfLengthPatternString: MapOfLengthPatternString, } +structure QueryParamsTargetingMapOfListOfLengthPatternStringOperationInputOutput { + @httpQueryParams + mapOfLengthPatternString: MapOfListOfLengthPatternString, +} + structure HttpPrefixHeadersTargetingLengthMapOperationInputOutput { @httpPrefixHeaders("X-Prefix-Headers-LengthMap-") lengthMap: ConBMap, @@ -326,7 +352,21 @@ structure ConA { // setOfLengthString: SetOfLengthString, mapOfLengthString: MapOfLengthString, - nonStreamingBlob: NonStreamingBlob + nonStreamingBlob: NonStreamingBlob, + + patternString: PatternString, + mapOfPatternString: MapOfPatternString, + listOfPatternString: ListOfPatternString, + // TODO(https://github.com/awslabs/smithy-rs/issues/1401): a `set` shape is + // just a `list` shape with `uniqueItems`, which hasn't been implemented yet. + // setOfPatternString: SetOfPatternString, + + lengthLengthPatternString: LengthPatternString, + mapOfLengthPatternString: MapOfLengthPatternString, + listOfLengthPatternString: ListOfLengthPatternString + // TODO(https://github.com/awslabs/smithy-rs/issues/1401): a `set` shape is + // just a `list` shape with `uniqueItems`, which hasn't been implemented yet. + // setOfLengthPatternString: SetOfLengthPatternString, } map MapOfLengthString { @@ -349,6 +389,16 @@ map MapOfListOfEnumString { value: ListOfEnumString, } +map MapOfListOfPatternString { + key: PatternString, + value: ListOfPatternString +} + +map MapOfListOfLengthPatternString { + key: LengthPatternString, + value: ListOfLengthPatternString +} + map MapOfSetOfLengthString { key: LengthString, // TODO(https://github.com/awslabs/smithy-rs/issues/1401): a `set` shape is @@ -418,6 +468,14 @@ set SetOfLengthString { member: LengthString } +set SetOfPatternString { + member: PatternString +} + +set SetOfLengthPatternString { + member: LengthPatternString +} + list ListOfLengthString { member: LengthString } @@ -426,6 +484,14 @@ list ListOfEnumString { member: EnumString } +list ListOfPatternString { + member: PatternString +} + +list ListOfLengthPatternString { + member: LengthPatternString +} + structure ConB { @required nice: String, From be4a85a3c42f881329d30bde74376dd179325beb Mon Sep 17 00:00:00 2001 From: Julian Antonielli Date: Wed, 23 Nov 2022 10:02:41 +0000 Subject: [PATCH 54/59] Fix examples in `constraints.smithy` --- codegen-core/common-test-models/constraints.smithy | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/codegen-core/common-test-models/constraints.smithy b/codegen-core/common-test-models/constraints.smithy index 8254bf5bbf..0bdfe6d2a1 100644 --- a/codegen-core/common-test-models/constraints.smithy +++ b/codegen-core/common-test-models/constraints.smithy @@ -545,12 +545,12 @@ list NestedList { // } map MapOfPatternString { - key: String, + key: PatternString, value: PatternString, } map MapOfLengthPatternString { - key: String, + key: LengthPatternString, value: LengthPatternString, } From 105112a61a16aaa737d053a77b2ec73e4d857250 Mon Sep 17 00:00:00 2001 From: Julian Antonielli Date: Wed, 23 Nov 2022 10:21:20 +0000 Subject: [PATCH 55/59] Fix failing test --- .../amazon/smithy/rust/codegen/server/smithy/Constraints.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/Constraints.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/Constraints.kt index 35211f01fd..9acabf7532 100644 --- a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/Constraints.kt +++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/Constraints.kt @@ -94,7 +94,7 @@ fun MemberShape.targetCanReachConstrainedShape(model: Model, symbolProvider: Sym fun Shape.hasPublicConstrainedWrapperTupleType(model: Model, publicConstrainedTypes: Boolean): Boolean = when (this) { is MapShape -> publicConstrainedTypes && this.hasTrait() - is StringShape -> !this.hasTrait() && (publicConstrainedTypes && this.hasTrait()) + is StringShape -> !this.hasTrait() && (publicConstrainedTypes && supportedStringConstraintTraits.any(this::hasTrait)) is MemberShape -> model.expectShape(this.target).hasPublicConstrainedWrapperTupleType(model, publicConstrainedTypes) else -> false } From 0d2b6b629ac9df14a33a3734bf91c5258846755e Mon Sep 17 00:00:00 2001 From: Julian Antonielli Date: Wed, 23 Nov 2022 13:52:31 +0000 Subject: [PATCH 56/59] Combine writables in `ConstrainedStringGenerator` --- .../generators/ConstrainedStringGenerator.kt | 75 +++++++++---------- 1 file changed, 37 insertions(+), 38 deletions(-) diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt index 041b00803a..587b53628e 100644 --- a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt +++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt @@ -19,10 +19,9 @@ import software.amazon.smithy.rust.codegen.core.rustlang.Visibility import software.amazon.smithy.rust.codegen.core.rustlang.Writable import software.amazon.smithy.rust.codegen.core.rustlang.docs import software.amazon.smithy.rust.codegen.core.rustlang.documentShape +import software.amazon.smithy.rust.codegen.core.rustlang.join import software.amazon.smithy.rust.codegen.core.rustlang.render import software.amazon.smithy.rust.codegen.core.rustlang.rust -import software.amazon.smithy.rust.codegen.core.rustlang.rustBlock -import software.amazon.smithy.rust.codegen.core.rustlang.rustBlockTemplate import software.amazon.smithy.rust.codegen.core.rustlang.rustTemplate import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType import software.amazon.smithy.rust.codegen.core.smithy.makeMaybeConstrained @@ -64,26 +63,31 @@ class ConstrainedStringGenerator( .map(StringTraitInfo::toTraitInfo) private fun renderTryFrom(inner: String, name: String, constraintViolation: Symbol) { - writer.rustBlock("impl $name") { - for (traitInfo in constraintsInfo) { - rustTemplate("#{ValidationFunction:W}", "ValidationFunction" to traitInfo.validationFunctionDefinition(constraintViolation)) + writer.rustTemplate( + """ + impl $name { + #{ValidationFunctions:W} } - } + """, + "ValidationFunctions" to constraintsInfo.map { it.validationFunctionDefinition(constraintViolation) }.join("\n"), + ) + + writer.rustTemplate( + """ + impl #{TryFrom}<$inner> for $name { + type Error = #{ConstraintViolation}; - writer.rustBlockTemplate("impl #{TryFrom}<$inner> for $name", "TryFrom" to RuntimeType.TryFrom) { - rustTemplate("type Error = #{ConstraintViolation};", "ConstraintViolation" to constraintViolation) - rustBlock( - """ /// ${rustDocsTryFromMethod(name, inner)} - fn try_from(value: $inner) -> Result - """, - ) { - for (traitInfo in constraintsInfo) { - rustTemplate("#{TryFromCheck:W}", "TryFromCheck" to traitInfo.tryFromCheck) + fn try_from(value: $inner) -> Result { + #{TryFromChecks:W} + + Ok(Self(value)) } - rust("Ok(Self(value))") } - } + """, + "TryFrom" to RuntimeType.TryFrom, + "TryFromChecks" to constraintsInfo.map { it.tryFromCheck }.join("\n"), + ) } fun render() { @@ -172,33 +176,28 @@ class ConstrainedStringGenerator( } private fun renderConstraintViolationEnum(writer: RustWriter, shape: StringShape, constraintViolation: Symbol) { - writer.rustBlock( + writer.rustTemplate( """ ##[derive(Debug, PartialEq)] - pub enum ${constraintViolation.name} - """, - ) { - for (traitInfo in constraintsInfo) { - rustTemplate( - "#{ConstraintViolationVariant:W}", - "ConstraintViolationVariant" to traitInfo.constraintViolationVariant, - ) + pub enum ${constraintViolation.name} { + #{Variants:W} } - } + """, + "Variants" to constraintsInfo.map { it.constraintViolationVariant }.join(",\n"), + ) if (shape.isReachableFromOperationInput()) { - writer.rustBlock("impl ${constraintViolation.name}") { - rustBlockTemplate( - "pub(crate) fn as_validation_exception_field(self, path: #{String}) -> crate::model::ValidationExceptionField", - "String" to RuntimeType.String, - ) { - rustBlock("match self") { - for (traitInfo in constraintsInfo) { - rustTemplate("#{ValidationExceptionField:W}", "ValidationExceptionField" to traitInfo.asValidationExceptionField) - } - } + writer.rustTemplate( + """ + impl ${constraintViolation.name} { + pub(crate) fn as_validation_exception_field(self, path: #{String}) -> crate::model::ValidationExceptionField { + #{ValidationExceptionFields:W} + } } - } + """, + "String" to RuntimeType.String, + "ValidationExceptionFields" to constraintsInfo.map { it.asValidationExceptionField }.join("\n"), + ) } } } From 3848d34336a400b8f1bb0f49aa81324b4633ae73 Mon Sep 17 00:00:00 2001 From: Julian Antonielli Date: Wed, 23 Nov 2022 14:19:41 +0000 Subject: [PATCH 57/59] Fix uses of `Writable.join` --- .../smithy/generators/ConstrainedStringGenerator.kt | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt index 587b53628e..076c169c7a 100644 --- a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt +++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt @@ -86,6 +86,7 @@ class ConstrainedStringGenerator( } """, "TryFrom" to RuntimeType.TryFrom, + "ConstraintViolation" to constraintViolation, "TryFromChecks" to constraintsInfo.map { it.tryFromCheck }.join("\n"), ) } @@ -190,9 +191,11 @@ class ConstrainedStringGenerator( writer.rustTemplate( """ impl ${constraintViolation.name} { - pub(crate) fn as_validation_exception_field(self, path: #{String}) -> crate::model::ValidationExceptionField { - #{ValidationExceptionFields:W} - } + pub(crate) fn as_validation_exception_field(self, path: #{String}) -> crate::model::ValidationExceptionField { + match self { + #{ValidationExceptionFields:W} + } + } } """, "String" to RuntimeType.String, @@ -207,7 +210,7 @@ private data class Length(val lengthTrait: LengthTrait) : StringTraitInfo() { { rust("Self::check_length(&value)?;") }, { docs("Error when a string doesn't satisfy its `@length` requirements.") - rust("Length(usize),") + rust("Length(usize)") }, { rust( @@ -263,7 +266,7 @@ private data class Pattern(val patternTrait: PatternTrait) : StringTraitInfo() { { docs("Error when a string doesn't satisfy its `@pattern`.") docs("Contains the String that failed the pattern.") - rust("Pattern(String),") + rust("Pattern(String)") }, { rust( From fdf88b0de0cae3a4c33c6e4105dbddfb0a9b25dc Mon Sep 17 00:00:00 2001 From: Julian Antonielli Date: Wed, 23 Nov 2022 14:21:48 +0000 Subject: [PATCH 58/59] Use `expect` over `unwrap_or_else` --- .../server/smithy/generators/ConstrainedStringGenerator.kt | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt index 076c169c7a..3bf8fddaf9 100644 --- a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt +++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt @@ -288,6 +288,7 @@ private data class Pattern(val patternTrait: PatternTrait) : StringTraitInfo() { */ private fun renderValidationFunction(constraintViolation: Symbol): Writable { val pattern = patternTrait.pattern + val errorMessageForUnsupportedRegex = """The regular expression $pattern is not supported by the `regex` crate; feel free to file an issue under https://github.com/awslabs/smithy-rs/issues for support""" return { rustTemplate( @@ -305,10 +306,7 @@ private data class Pattern(val patternTrait: PatternTrait) : StringTraitInfo() { pub fn compile_regex() -> &'static #{Regex}::Regex { static REGEX: #{OnceCell}::sync::OnceCell<#{Regex}::Regex> = #{OnceCell}::sync::OnceCell::new(); - REGEX.get_or_init(|| - #{Regex}::Regex::new(r##"$pattern"##) - .unwrap_or_else(|_| panic!("The regular expression {} is not supported by the `regex` crate; feel free to file an issue under https://github.com/awslabs/smithy-rs/issues for support", r##"$pattern"##)) - ) + REGEX.get_or_init(|| #{Regex}::Regex::new(r##"$pattern"##).expect(r##"$errorMessageForUnsupportedRegex"##)) } """, "Regex" to ServerCargoDependency.Regex.toType(), From 53cf34c6b0e0fed2d0289f4659c49894d4f53c05 Mon Sep 17 00:00:00 2001 From: Julian Antonielli Date: Wed, 23 Nov 2022 14:28:09 +0000 Subject: [PATCH 59/59] Use `once_cell::sync::Lazy` over `once_cell::sync::OnceCell` --- .../server/smithy/generators/ConstrainedStringGenerator.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt index 3bf8fddaf9..c0ef3c043c 100644 --- a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt +++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt @@ -304,9 +304,9 @@ private data class Pattern(val patternTrait: PatternTrait) : StringTraitInfo() { } pub fn compile_regex() -> &'static #{Regex}::Regex { - static REGEX: #{OnceCell}::sync::OnceCell<#{Regex}::Regex> = #{OnceCell}::sync::OnceCell::new(); + static REGEX: #{OnceCell}::sync::Lazy<#{Regex}::Regex> = #{OnceCell}::sync::Lazy::new(|| #{Regex}::Regex::new(r##"$pattern"##).expect(r##"$errorMessageForUnsupportedRegex"##)); - REGEX.get_or_init(|| #{Regex}::Regex::new(r##"$pattern"##).expect(r##"$errorMessageForUnsupportedRegex"##)) + ®EX } """, "Regex" to ServerCargoDependency.Regex.toType(),