Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Compile regexes for @pattern strings early #2058

Merged
merged 29 commits into from
Jan 9, 2023
Merged
Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
b5f1341
Add docs for `compile_regex()`
jjant Nov 30, 2022
83f3203
Try to pass `@pattern` string shapes to service visitor
jjant Nov 30, 2022
22a0e03
Merge branch 'main' into jjant/init-regexes-early
jjant Dec 5, 2022
e3bc78c
Add `compile_rege` calls
jjant Dec 5, 2022
0edb00f
Remove unwanted changes
jjant Dec 5, 2022
7ab947c
Fix python code generator
jjant Dec 7, 2022
926edeb
Add hack for `@pattern` + `@enum` trait
jjant Dec 7, 2022
73b1cfd
Generate tests for regexes
jjant Dec 16, 2022
acdf8b7
Move some functionality to `TraitInfo`
jjant Dec 16, 2022
db05308
Add docs for `Symbol.testModule`
jjant Dec 16, 2022
be5dd0c
Improve docs for `Symbol.testModule`
jjant Dec 16, 2022
f1bacb7
Merge branch 'main' into jjant/init-regexes-early
jjant Dec 20, 2022
c93bb16
Merge branch 'main' into jjant/init-regexes-early
jjant Dec 21, 2022
34d7187
Update codegen-server/src/main/kotlin/software/amazon/smithy/rust/cod…
jjant Dec 21, 2022
8283a1a
Merge branch 'main' into jjant/init-regexes-early
jjant Jan 3, 2023
86bfa1b
Merge branch 'main' into jjant/init-regexes-early
jjant Jan 5, 2023
339b608
Refactor `regex_compiles` test rendering
jjant Jan 5, 2023
f03bf20
Refactor `Symbol.testModule` -> `SymbolProvider.testModuleForShape`
jjant Jan 5, 2023
6808573
Use `RustWriter.unitTest` instead of manually annotating `#[test]`s
jjant Jan 5, 2023
4213e88
Refactor `mapNotNull` -> `filter` + `map`
jjant Jan 5, 2023
39b6ed8
Only render doc comment when there's `@pattern` strings
jjant Jan 5, 2023
732730d
Rename `String.toRustName` -> `´String.unsafeToRustName`
jjant Jan 5, 2023
2873529
Add kotlin test for pattern with lookahead
jjant Jan 5, 2023
cfbdd4b
Merge branch 'main' into jjant/init-regexes-early
jjant Jan 5, 2023
37a9d2a
Merge branch 'main' into jjant/init-regexes-early
jjant Jan 6, 2023
9deedeb
Update `Attribute.Cfg` to new `Attribute.CfgTest`
jjant Jan 6, 2023
8475b14
Prepend `@pattern` docs
jjant Jan 9, 2023
9b21c59
Merge branch 'main' into jjant/init-regexes-early
jjant Jan 9, 2023
f9c1a98
Fix test name
jjant Jan 9, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -15,22 +15,21 @@ import software.amazon.smithy.rust.codegen.client.smithy.endpoint.generators.Fun
import software.amazon.smithy.rust.codegen.core.rustlang.InlineDependency
import software.amazon.smithy.rust.codegen.core.rustlang.RustDependency
import software.amazon.smithy.rust.codegen.core.rustlang.RustModule
import software.amazon.smithy.rust.codegen.core.rustlang.RustReservedWords
import software.amazon.smithy.rust.codegen.core.rustlang.RustType
import software.amazon.smithy.rust.codegen.core.smithy.RuntimeConfig
import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType
import software.amazon.smithy.rust.codegen.core.smithy.makeOptional
import software.amazon.smithy.rust.codegen.core.smithy.rustType
import software.amazon.smithy.rust.codegen.core.smithy.unsafeToRustName
import software.amazon.smithy.rust.codegen.core.util.letIf
import software.amazon.smithy.rust.codegen.core.util.toSnakeCase

data class Context(val functionRegistry: FunctionRegistry, val runtimeConfig: RuntimeConfig)

/**
* Utility function to convert an [Identifier] into a valid Rust identifier (snake case)
*/
fun Identifier.rustName(): String {
return this.toString().stringToRustName()
return this.toString().unsafeToRustName()
}

/**
Expand All @@ -53,16 +52,14 @@ class Types(runtimeConfig: RuntimeConfig) {
val resolveEndpointError = smithyHttpEndpointModule.resolve("ResolveEndpointError")
}

private fun String.stringToRustName(): String = RustReservedWords.escapeIfNeeded(this.toSnakeCase())

jjant marked this conversation as resolved.
Show resolved Hide resolved
/**
* Returns the memberName() for a given [Parameter]
*/
fun Parameter.memberName(): String {
return name.rustName()
}

fun ContextParamTrait.memberName(): String = this.name.stringToRustName()
fun ContextParamTrait.memberName(): String = this.name.unsafeToRustName()

/**
* Returns the symbol for a given parameter. This enables [RustWriter] to generate the correct [RustType].
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,11 @@ sealed class RustModule {
documentation: String? = null,
inline: Boolean = false,
parent: RustModule = LibRs,
additionalAttributes: List<Attribute> = listOf(),
): LeafModule {
return LeafModule(
RustReservedWords.escapeIfNeeded(name),
RustMetadata(visibility = visibility),
RustMetadata(visibility = visibility, additionalAttributes = additionalAttributes),
documentation,
inline = inline,
parent = parent,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,11 @@ import software.amazon.smithy.model.shapes.UnionShape
import software.amazon.smithy.model.traits.EnumDefinition
import software.amazon.smithy.model.traits.EnumTrait
import software.amazon.smithy.model.traits.ErrorTrait
import software.amazon.smithy.rust.codegen.core.rustlang.Attribute
import software.amazon.smithy.rust.codegen.core.rustlang.RustModule
import software.amazon.smithy.rust.codegen.core.rustlang.RustReservedWords
import software.amazon.smithy.rust.codegen.core.rustlang.RustType
import software.amazon.smithy.rust.codegen.core.rustlang.Visibility
import software.amazon.smithy.rust.codegen.core.rustlang.stripOuter
import software.amazon.smithy.rust.codegen.core.smithy.traits.RustBoxTrait
import software.amazon.smithy.rust.codegen.core.smithy.traits.SyntheticInputTrait
Expand Down Expand Up @@ -393,6 +396,28 @@ fun Symbol.Builder.rustType(rustType: RustType): Symbol.Builder = this.putProper
fun Symbol.Builder.module(module: RustModule.LeafModule): Symbol.Builder = this.putProperty(RUST_MODULE_KEY, module)
fun Symbol.module(): RustModule.LeafModule = this.expectProperty(RUST_MODULE_KEY, RustModule.LeafModule::class.java)

/**
* Creates a test module for this symbol.
* For example if the symbol represents the name for the struct `struct MyStruct { ... }`,
* this function will create the following inline module:
* ```rust
* #[cfg(test)]
* mod test_my_struct { ... }
* ```
*/
fun SymbolProvider.testModuleForShape(shape: Shape): RustModule.LeafModule {
val symbol = toSymbol(shape)
val rustName = symbol.name.unsafeToRustName()

return RustModule.new(
name = "test_$rustName",
visibility = Visibility.PRIVATE,
inline = true,
parent = symbol.module(),
additionalAttributes = listOf(Attribute.CfgTest),
)
}

fun Symbol.Builder.renamedFrom(name: String): Symbol.Builder {
return this.putProperty(RENAMED_FROM_KEY, name)
}
Expand Down Expand Up @@ -435,3 +460,9 @@ fun Symbol.isRustBoxed(): Boolean = rustType().stripOuter<RustType.Option>() is
// Symbols should _always_ be created with a Rust type & shape attached
fun Symbol.rustType(): RustType = this.expectProperty(RUST_TYPE_KEY, RustType::class.java)
fun Symbol.shape(): Shape = this.expectProperty(SHAPE_KEY, Shape::class.java)

/**
* You should rarely need this function, rust names in general should be symbol-aware,
* this is "automatic" if you use things like [software.amazon.smithy.rust.codegen.core.rustlang.rustTemplate].
*/
fun String.unsafeToRustName(): String = RustReservedWords.escapeIfNeeded(this.toSnakeCase())
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ package software.amazon.smithy.rust.codegen.server.python.smithy.generators
import software.amazon.smithy.model.shapes.OperationShape
import software.amazon.smithy.rust.codegen.core.rustlang.RustModule
import software.amazon.smithy.rust.codegen.core.rustlang.RustWriter
import software.amazon.smithy.rust.codegen.core.smithy.CodegenContext
import software.amazon.smithy.rust.codegen.core.smithy.RustCrate
import software.amazon.smithy.rust.codegen.core.smithy.generators.protocol.ProtocolSupport
import software.amazon.smithy.rust.codegen.server.smithy.ServerCodegenContext
import software.amazon.smithy.rust.codegen.server.smithy.generators.ServerServiceGenerator
import software.amazon.smithy.rust.codegen.server.smithy.generators.protocol.ServerProtocol
import software.amazon.smithy.rust.codegen.server.smithy.generators.protocol.ServerProtocolGenerator
Expand All @@ -26,7 +26,7 @@ class PythonServerServiceGenerator(
protocolGenerator: ServerProtocolGenerator,
protocolSupport: ProtocolSupport,
protocol: ServerProtocol,
private val context: CodegenContext,
private val context: ServerCodegenContext,
) : ServerServiceGenerator(rustCrate, protocolGenerator, protocolSupport, protocol, context) {

override fun renderCombinedErrors(writer: RustWriter, operation: OperationShape) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
package software.amazon.smithy.rust.codegen.server.smithy.generators

import software.amazon.smithy.codegen.core.Symbol
import software.amazon.smithy.model.shapes.Shape
import software.amazon.smithy.model.shapes.StringShape
import software.amazon.smithy.model.traits.LengthTrait
import software.amazon.smithy.model.traits.PatternTrait
Expand All @@ -25,6 +26,8 @@ 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.smithy.module
import software.amazon.smithy.rust.codegen.core.smithy.testModuleForShape
import software.amazon.smithy.rust.codegen.core.testutil.unitTest
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
Expand Down Expand Up @@ -56,14 +59,14 @@ class ConstrainedStringGenerator(
PubCrateConstraintViolationSymbolProvider(this)
}
}
private val symbol = constrainedShapeSymbolProvider.toSymbol(shape)
private val constraintsInfo: List<TraitInfo> =
supportedStringConstraintTraits
.mapNotNull { shape.getTrait(it).orNull() }
.map(StringTraitInfo::fromTrait)
.map { StringTraitInfo.fromTrait(symbol, it) }
.map(StringTraitInfo::toTraitInfo)

fun render() {
val symbol = constrainedShapeSymbolProvider.toSymbol(shape)
val name = symbol.name
val inner = RustType.String.render()
val constraintViolation = constraintViolationSymbolProvider.toSymbol(shape)
Expand Down Expand Up @@ -145,6 +148,8 @@ class ConstrainedStringGenerator(
writer.withInlineModule(constraintViolation.module()) {
renderConstraintViolationEnum(this, shape, constraintViolation)
}

renderTests(shape)
}

private fun renderConstraintViolationEnum(writer: RustWriter, shape: StringShape, constraintViolation: Symbol) {
Expand Down Expand Up @@ -174,6 +179,22 @@ class ConstrainedStringGenerator(
)
}
}

private fun renderTests(shape: Shape) {
val testCases = TraitInfo.testCases(constraintsInfo)

if (testCases.isNotEmpty()) {
val testModule = constrainedShapeSymbolProvider.testModuleForShape(shape)
writer.withInlineModule(testModule) {
rustTemplate(
"""
#{TestCases:W}
""",
"TestCases" to testCases.join("\n"),
)
}
}
}
}
private data class Length(val lengthTrait: LengthTrait) : StringTraitInfo() {
override fun toTraitInfo(): TraitInfo = TraitInfo(
Expand Down Expand Up @@ -217,7 +238,7 @@ private data class Length(val lengthTrait: LengthTrait) : StringTraitInfo() {
}
}

private data class Pattern(val patternTrait: PatternTrait) : StringTraitInfo() {
private data class Pattern(val symbol: Symbol, val patternTrait: PatternTrait) : StringTraitInfo() {
override fun toTraitInfo(): TraitInfo {
val pattern = patternTrait.pattern

Expand All @@ -239,6 +260,16 @@ private data class Pattern(val patternTrait: PatternTrait) : StringTraitInfo() {
)
},
this::renderValidationFunction,
testCases = listOf {
unitTest("regex_compiles") {
rustTemplate(
"""
#{T}::compile_regex();
""",
"T" to symbol,
)
}
},
)
}

Expand All @@ -264,6 +295,8 @@ private data class Pattern(val patternTrait: PatternTrait) : StringTraitInfo() {
}
}

/// Attempts to compile the regex for this constrained type's `@pattern`.
/// This can fail if the specified regex is not supported by the `#{Regex}` crate.
pub fn compile_regex() -> &'static #{Regex}::Regex {
static REGEX: #{OnceCell}::sync::Lazy<#{Regex}::Regex> = #{OnceCell}::sync::Lazy::new(|| #{Regex}::Regex::new(r##"$pattern"##).expect(r##"$errorMessageForUnsupportedRegex"##));

Expand All @@ -279,10 +312,10 @@ private data class Pattern(val patternTrait: PatternTrait) : StringTraitInfo() {

private sealed class StringTraitInfo {
companion object {
fun fromTrait(trait: Trait): StringTraitInfo =
fun fromTrait(symbol: Symbol, trait: Trait) =
when (trait) {
is PatternTrait -> {
Pattern(trait)
Pattern(symbol, trait)
}
is LengthTrait -> {
Length(trait)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import software.amazon.smithy.rust.codegen.core.rustlang.Writable
import software.amazon.smithy.rust.codegen.core.rustlang.join
import software.amazon.smithy.rust.codegen.core.rustlang.rust
import software.amazon.smithy.rust.codegen.core.rustlang.rustTemplate
import software.amazon.smithy.rust.codegen.core.smithy.CodegenContext
import software.amazon.smithy.rust.codegen.core.smithy.ErrorsModule
import software.amazon.smithy.rust.codegen.core.smithy.InputsModule
import software.amazon.smithy.rust.codegen.core.smithy.OutputsModule
Expand All @@ -27,6 +26,7 @@ import software.amazon.smithy.rust.codegen.core.smithy.generators.protocol.Proto
import software.amazon.smithy.rust.codegen.core.util.toPascalCase
import software.amazon.smithy.rust.codegen.core.util.toSnakeCase
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.generators.protocol.ServerProtocol
import software.amazon.smithy.rust.codegen.server.smithy.generators.protocol.ServerProtocolGenerator
import software.amazon.smithy.rust.codegen.server.smithy.generators.protocol.ServerProtocolTestGenerator
Expand All @@ -42,7 +42,7 @@ open class ServerServiceGenerator(
private val protocolGenerator: ServerProtocolGenerator,
private val protocolSupport: ProtocolSupport,
val protocol: ServerProtocol,
private val codegenContext: CodegenContext,
private val codegenContext: ServerCodegenContext,
) {
private val index = TopDownIndex.of(codegenContext.model)
protected val operations = index.getContainedOperations(codegenContext.serviceShape).sortedBy { it.id }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@
package software.amazon.smithy.rust.codegen.server.smithy.generators

import software.amazon.smithy.model.knowledge.TopDownIndex
import software.amazon.smithy.model.neighbor.Walker
import software.amazon.smithy.model.shapes.OperationShape
import software.amazon.smithy.model.shapes.StringShape
import software.amazon.smithy.model.traits.PatternTrait
import software.amazon.smithy.rust.codegen.core.rustlang.RustReservedWords
import software.amazon.smithy.rust.codegen.core.rustlang.RustWriter
import software.amazon.smithy.rust.codegen.core.rustlang.Writable
Expand All @@ -15,18 +18,20 @@ import software.amazon.smithy.rust.codegen.core.rustlang.join
import software.amazon.smithy.rust.codegen.core.rustlang.rust
import software.amazon.smithy.rust.codegen.core.rustlang.rustTemplate
import software.amazon.smithy.rust.codegen.core.rustlang.writable
import software.amazon.smithy.rust.codegen.core.smithy.CodegenContext
import software.amazon.smithy.rust.codegen.core.smithy.ErrorsModule
import software.amazon.smithy.rust.codegen.core.smithy.InputsModule
import software.amazon.smithy.rust.codegen.core.smithy.OutputsModule
import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType
import software.amazon.smithy.rust.codegen.core.util.hasTrait
import software.amazon.smithy.rust.codegen.core.util.letIf
import software.amazon.smithy.rust.codegen.core.util.toPascalCase
import software.amazon.smithy.rust.codegen.core.util.toSnakeCase
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.generators.protocol.ServerProtocol

class ServerServiceGeneratorV2(
private val codegenContext: CodegenContext,
private val codegenContext: ServerCodegenContext,
private val protocol: ServerProtocol,
) {
private val runtimeConfig = codegenContext.runtimeConfig
Expand Down Expand Up @@ -199,6 +204,7 @@ class ServerServiceGeneratorV2(
)
}
}

rustTemplate(
"""
/// Constructs a [`$serviceName`] from the arguments provided to the builder.
Expand All @@ -219,6 +225,9 @@ class ServerServiceGeneratorV2(
});
}
let $expectMessageVariableName = "this should never panic since we are supposed to check beforehand that a handler has been registered for this operation; please file a bug report under https://github.com/awslabs/smithy-rs/issues";

#{PatternInitializations:W}

#{Router}::from_iter([#{RoutesArrayElements:W}])
};
Ok($serviceName {
Expand All @@ -230,9 +239,34 @@ class ServerServiceGeneratorV2(
"NullabilityChecks" to nullabilityChecks,
"RoutesArrayElements" to routesArrayElements,
"SmithyHttpServer" to smithyHttpServer,
"PatternInitializations" to patternInitializations(),
)
}

/**
* Renders `PatternString::compile_regex()` function calls for every
* `@pattern`-constrained string shape in the service closure.
*/
@Suppress("DEPRECATION")
private fun patternInitializations(): Writable {
val patterns = Walker(model).walkShapes(service)
.filter { shape -> shape is StringShape && shape.hasTrait<PatternTrait>() && !shape.hasTrait<software.amazon.smithy.model.traits.EnumTrait>() }
.map { shape -> codegenContext.constrainedShapeSymbolProvider.toSymbol(shape) }
.map { symbol ->
writable {
rustTemplate("#{Type}::compile_regex();", "Type" to symbol)
}
}

patterns.letIf(patterns.isNotEmpty()) {
val docs = listOf(writable { rust("// Eagerly initialize regexes for `@pattern` strings.") })

docs + patterns
}

return patterns.join("")
}

private fun buildUncheckedMethod(): Writable = writable {
val pairs = writable {
for (operationShape in operations) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,14 @@ data class TraitInfo(
val constraintViolationVariant: Writable,
val asValidationExceptionField: Writable,
val validationFunctionDefinition: (constraintViolation: Symbol, unconstrainedTypeName: String) -> Writable,
)
private val testCases: List<Writable> = listOf(),
) {
companion object {
fun testCases(constraintsInfo: List<TraitInfo>): List<Writable> {
return constraintsInfo.flatMap { it.testCases }
}
}
}

/**
* Render the implementation of `TryFrom` for a constrained type.
Expand Down
Loading