Skip to content

Commit

Permalink
Unknown enum variants removed from server
Browse files Browse the repository at this point in the history
The server must have the most up to date variants and the unknown enum
variant should not be used. Clients are generated with it because they
might not have the most recent model and the server might return
an unknown variant to them.

Closes #1187

Signed-off-by: Daniele Ahmed <[email protected]>
  • Loading branch information
Daniele Ahmed committed May 18, 2022
1 parent 154c5d1 commit bdd6560
Show file tree
Hide file tree
Showing 25 changed files with 480 additions and 61 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.next.toml
Original file line number Diff line number Diff line change
Expand Up @@ -75,3 +75,9 @@ message = "Update generated crates to Rust 2021"
references = ["smithy-rs#1332"]
meta = { "breaking" = false, "tada" = false, "bug" = false }
author = "82marbag"

[[smithy-rs]]
message = "Remove Unknown variant from enums in server"
references = ["smithy-rs#1187"]
meta = { "breaking" = true, "tada" = false, "bug" = false }
author = "82marbag"
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ class RustCodegenServerPlugin : SmithyBuildPlugin {
override fun execute(context: PluginContext) {
// Suppress extremely noisy logs about reserved words
Logger.getLogger(ReservedWordSymbolProvider::class.java.name).level = Level.OFF
// Discover [RustCodegenDecorators] on the classpath. [RustCodegenDectorator] return different types of
// Discover [RustCodegenDecorators] on the classpath. [RustCodegenDecorator] return different types of
// customization. A customization is a function of:
// - location (e.g. the mutate section of an operation)
// - context (e.g. the of the operation)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,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.transform.ModelTransformer
import software.amazon.smithy.rust.codegen.server.smithy.generators.ServerEnumGenerator
import software.amazon.smithy.rust.codegen.server.smithy.generators.ServerServiceGenerator
import software.amazon.smithy.rust.codegen.server.smithy.protocols.ServerProtocolLoader
import software.amazon.smithy.rust.codegen.smithy.CodegenContext
Expand All @@ -28,7 +29,6 @@ import software.amazon.smithy.rust.codegen.smithy.SymbolVisitorConfig
import software.amazon.smithy.rust.codegen.smithy.customize.RustCodegenDecorator
import software.amazon.smithy.rust.codegen.smithy.generators.BuilderGenerator
import software.amazon.smithy.rust.codegen.smithy.generators.CodegenTarget
import software.amazon.smithy.rust.codegen.smithy.generators.EnumGenerator
import software.amazon.smithy.rust.codegen.smithy.generators.StructureGenerator
import software.amazon.smithy.rust.codegen.smithy.generators.UnionGenerator
import software.amazon.smithy.rust.codegen.smithy.generators.implBlock
Expand Down Expand Up @@ -184,7 +184,7 @@ class ServerCodegenVisitor(context: PluginContext, private val codegenDecorator:
logger.info("[rust-server-codegen] Generating an enum $shape")
shape.getTrait<EnumTrait>()?.also { enum ->
rustCrate.useShapeWriter(shape) { writer ->
EnumGenerator(model, symbolProvider, writer, shape, enum).render()
ServerEnumGenerator(model, symbolProvider, writer, shape, enum, codegenContext.mode, codegenContext.runtimeConfig).render()
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*
* 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.model.Model
import software.amazon.smithy.model.shapes.StringShape
import software.amazon.smithy.model.traits.EnumTrait
import software.amazon.smithy.rust.codegen.rustlang.RustWriter
import software.amazon.smithy.rust.codegen.rustlang.rustBlock
import software.amazon.smithy.rust.codegen.server.smithy.ServerRuntimeType
import software.amazon.smithy.rust.codegen.smithy.CodegenMode
import software.amazon.smithy.rust.codegen.smithy.RuntimeConfig
import software.amazon.smithy.rust.codegen.smithy.RuntimeType
import software.amazon.smithy.rust.codegen.smithy.RustSymbolProvider
import software.amazon.smithy.rust.codegen.smithy.generators.EnumGenerator
import software.amazon.smithy.rust.codegen.util.dq

class ServerEnumGenerator(
model: Model,
symbolProvider: RustSymbolProvider,
private val writer: RustWriter,
shape: StringShape,
enumTrait: EnumTrait,
mode: CodegenMode,
private val runtimeConfig: RuntimeConfig,
) : EnumGenerator(model, symbolProvider, writer, shape, enumTrait, mode,) {
override fun renderFromForStr() {
val errorStruct = "${enumName}UnknownVariantError"
writer.write("##[derive(Debug, PartialEq, Eq, Hash)]")
writer.write("pub struct $errorStruct(String);")
writer.rustBlock("impl #T<&str> for $enumName", RuntimeType.TryFrom) {
write("type Error = $errorStruct;")
writer.rustBlock("fn try_from(s: &str) -> Result<Self, <$enumName as #T<&str>>::Error>", RuntimeType.TryFrom) {
writer.rustBlock("match s") {
sortedMembers.forEach { member ->
write("""${member.value.dq()} => Ok($enumName::${member.derivedName()}),""")
}
write("_ => Err($errorStruct(s.to_owned()))")
}
}
}
writer.rustBlock("impl #T<$errorStruct> for #T", RuntimeType.From, ServerRuntimeType.RequestRejection(runtimeConfig)) {
writer.rustBlock("fn from(e: $errorStruct) -> Self") {
write("Self::EnumVariantNotFound(Box::new(e))")
}
}
writer.rustBlock("impl From<$errorStruct> for #T", RuntimeType.jsonDeserialize(runtimeConfig)) {
writer.rustBlock("fn from(e: $errorStruct) -> Self") {
write("""Self::custom(format!("unknown variant {}", e))""")
}
}
writer.rustBlock("impl std::error::Error for $errorStruct") {}
writer.rustBlock("impl std::fmt::Display for $errorStruct") {
writer.rustBlock("fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result") {
write("self.0.fmt(f)")
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import software.amazon.smithy.rust.codegen.rustlang.withBlock
import software.amazon.smithy.rust.codegen.server.smithy.ServerCargoDependency
import software.amazon.smithy.rust.codegen.server.smithy.protocols.ServerHttpBoundProtocolGenerator
import software.amazon.smithy.rust.codegen.smithy.CodegenContext
import software.amazon.smithy.rust.codegen.smithy.CodegenMode
import software.amazon.smithy.rust.codegen.smithy.RuntimeType
import software.amazon.smithy.rust.codegen.smithy.generators.Instantiator
import software.amazon.smithy.rust.codegen.smithy.generators.protocol.ProtocolSupport
Expand Down Expand Up @@ -71,7 +72,7 @@ class ServerProtocolTestGenerator(
private val operationErrorName = "crate::error::${operationSymbol.name}Error"

private val instantiator = with(codegenContext) {
Instantiator(symbolProvider, model, runtimeConfig)
Instantiator(symbolProvider, model, runtimeConfig, CodegenMode.Server)
}

private val codegenScope = arrayOf(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import software.amazon.smithy.model.shapes.OperationShape
import software.amazon.smithy.model.shapes.Shape
import software.amazon.smithy.model.shapes.StringShape
import software.amazon.smithy.model.shapes.StructureShape
import software.amazon.smithy.model.traits.EnumTrait
import software.amazon.smithy.model.traits.ErrorTrait
import software.amazon.smithy.model.traits.HttpErrorTrait
import software.amazon.smithy.rust.codegen.rustlang.Attribute
Expand All @@ -40,6 +41,7 @@ import software.amazon.smithy.rust.codegen.server.smithy.generators.http.ServerR
import software.amazon.smithy.rust.codegen.server.smithy.generators.http.ServerResponseBindingGenerator
import software.amazon.smithy.rust.codegen.smithy.CodegenContext
import software.amazon.smithy.rust.codegen.smithy.RuntimeType
import software.amazon.smithy.rust.codegen.smithy.extractSymbolFromOption
import software.amazon.smithy.rust.codegen.smithy.generators.StructureGenerator
import software.amazon.smithy.rust.codegen.smithy.generators.builderSymbol
import software.amazon.smithy.rust.codegen.smithy.generators.error.errorSymbol
Expand All @@ -53,13 +55,15 @@ import software.amazon.smithy.rust.codegen.smithy.protocols.HttpBoundProtocolPay
import software.amazon.smithy.rust.codegen.smithy.protocols.HttpLocation
import software.amazon.smithy.rust.codegen.smithy.protocols.Protocol
import software.amazon.smithy.rust.codegen.smithy.protocols.parse.StructuredDataParserGenerator
import software.amazon.smithy.rust.codegen.smithy.rustType
import software.amazon.smithy.rust.codegen.smithy.toOptional
import software.amazon.smithy.rust.codegen.smithy.wrapOptional
import software.amazon.smithy.rust.codegen.util.dq
import software.amazon.smithy.rust.codegen.util.expectTrait
import software.amazon.smithy.rust.codegen.util.findStreamingMember
import software.amazon.smithy.rust.codegen.util.getTrait
import software.amazon.smithy.rust.codegen.util.hasStreamingMember
import software.amazon.smithy.rust.codegen.util.hasTrait
import software.amazon.smithy.rust.codegen.util.inputShape
import software.amazon.smithy.rust.codegen.util.isStreaming
import software.amazon.smithy.rust.codegen.util.outputShape
Expand Down Expand Up @@ -858,12 +862,22 @@ private class ServerHttpBoundProtocolTraitImplGenerator(
// `<_>::from()` is necessary to convert the `&str` into:
// * the Rust enum in case the `string` shape has the `enum` trait; or
// * `String` in case it doesn't.
rustTemplate(
"""
let v = <_>::from(#{PercentEncoding}::percent_decode_str(&v).decode_utf8()?.as_ref());
""".trimIndent(),
*codegenScope
)
if (memberShape.hasTrait<EnumTrait>()) {
rustTemplate(
"""
let v = <#{memberShape}>::try_from(#{PercentEncoding}::percent_decode_str(&v).decode_utf8()?.as_ref())?;
""",
*codegenScope,
"memberShape" to symbolProvider.toSymbol(memberShape),
)
} else {
rustTemplate(
"""
let v = <_>::from(#{PercentEncoding}::percent_decode_str(&v).decode_utf8()?.as_ref());
""".trimIndent(),
*codegenScope
)
}
}
memberShape.isTimestampShape -> {
val index = HttpBindingIndex.of(model)
Expand Down Expand Up @@ -984,6 +998,7 @@ private class ServerHttpBoundProtocolTraitImplGenerator(
private fun generateParsePercentEncodedStrAsStringFn(binding: HttpBindingDescriptor): RuntimeType {
val output = symbolProvider.toSymbol(binding.member)
val fnName = generateParseStrFnName(binding)
val type = output.extractSymbolFromOption()
return RuntimeType.forInlineFun(fnName, operationDeserModule) { writer ->
writer.rustBlockTemplate(
"pub fn $fnName(value: &str) -> std::result::Result<#{O}, #{RequestRejection}>",
Expand All @@ -993,12 +1008,28 @@ private class ServerHttpBoundProtocolTraitImplGenerator(
// `<_>::from()` is necessary to convert the `&str` into:
// * the Rust enum in case the `string` shape has the `enum` trait; or
// * `String` in case it doesn't.
rustTemplate(
when (type.rustType()) {
RustType.String ->
rustTemplate(
"""
let value = <#{T}>::from(#{PercentEncoding}::percent_decode_str(value).decode_utf8()?.as_ref());
""",
*codegenScope,
"T" to type,
)
else -> // RustType.Opaque, the Enum
rustTemplate(
"""
let value = <#{T}>::try_from(#{PercentEncoding}::percent_decode_str(value).decode_utf8()?.as_ref())?;
""",
*codegenScope,
"T" to type,
)
}
writer.write(
"""
let value = <_>::from(#{PercentEncoding}::percent_decode_str(value).decode_utf8()?.as_ref());
Ok(${symbolProvider.wrapOptional(binding.member, "value")})
""".trimIndent(),
*codegenScope,
"""
)
}
}
Expand Down
Loading

0 comments on commit bdd6560

Please sign in to comment.