diff --git a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/AwsEndpointsStdLib.kt b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/AwsEndpointsStdLib.kt index a46e8037e7..3782117d8a 100644 --- a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/AwsEndpointsStdLib.kt +++ b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/AwsEndpointsStdLib.kt @@ -9,8 +9,8 @@ import software.amazon.smithy.model.node.Node import software.amazon.smithy.model.node.ObjectNode import software.amazon.smithy.rust.codegen.client.smithy.ClientCodegenContext import software.amazon.smithy.rust.codegen.client.smithy.customize.RustCodegenDecorator -import software.amazon.smithy.rust.codegen.client.smithy.endpoint.CustomRuntimeFunction import software.amazon.smithy.rust.codegen.client.smithy.endpoint.EndpointCustomization +import software.amazon.smithy.rust.codegen.client.smithy.endpoint.generators.CustomRuntimeFunction import software.amazon.smithy.rust.codegen.client.smithy.endpoint.rulesgen.awsStandardLib import software.amazon.smithy.rust.codegen.client.smithy.generators.protocol.ClientProtocolGenerator import software.amazon.smithy.rust.codegen.core.smithy.CodegenContext @@ -32,12 +32,12 @@ class AwsEndpointsStdLib() : RustCodegenDecorator ( javaClass.getResource("/default-partitions.json") - ?: throw IllegalStateException("Failed to find default-default-partitions.json in the JAR") + ?: throw IllegalStateException("Failed to find default-partitions.json in the JAR") ).readText() else -> path.readText() @@ -52,7 +52,7 @@ class AwsEndpointsStdLib() : RustCodegenDecorator { val sdkSettings = SdkSettings.from(codegenContext.settings) - return awsStandardLib(codegenContext.runtimeConfig, partitionsDotjson(sdkSettings)) + return awsStandardLib(codegenContext.runtimeConfig, partitionMetadata(sdkSettings)) } }, ) diff --git a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/RegionDecorator.kt b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/RegionDecorator.kt index 8b6954d290..41fd28c6cb 100644 --- a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/RegionDecorator.kt +++ b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/RegionDecorator.kt @@ -10,8 +10,8 @@ import software.amazon.smithy.rulesengine.language.syntax.parameters.Builtins import software.amazon.smithy.rulesengine.language.syntax.parameters.Parameter import software.amazon.smithy.rust.codegen.client.smithy.ClientCodegenContext import software.amazon.smithy.rust.codegen.client.smithy.customize.RustCodegenDecorator -import software.amazon.smithy.rust.codegen.client.smithy.endpoint.CustomRuntimeFunction import software.amazon.smithy.rust.codegen.client.smithy.endpoint.EndpointCustomization +import software.amazon.smithy.rust.codegen.client.smithy.endpoint.generators.CustomRuntimeFunction import software.amazon.smithy.rust.codegen.client.smithy.generators.config.ConfigCustomization import software.amazon.smithy.rust.codegen.client.smithy.generators.config.ServiceConfig import software.amazon.smithy.rust.codegen.client.smithy.generators.protocol.ClientProtocolGenerator diff --git a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/SdkSettings.kt b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/SdkSettings.kt index 35b4aaede1..ce1f0f015f 100644 --- a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/SdkSettings.kt +++ b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/SdkSettings.kt @@ -30,7 +30,7 @@ class SdkSettings private constructor(private val awsSdk: ObjectNode?) { awsSdk?.getStringMember("endpointsConfigPath")?.orNull()?.value?.let { Paths.get(it) } /** Path to the `default-partitions.json` configuration */ - val partitionsDotJson: Path? + val partitionsConfigPath: Path? get() = awsSdk?.getStringMember("partitionsConfigPath")?.orNull()?.value?.let { Paths.get(it) } diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/ClientContextParamDecorator.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/ClientContextParamDecorator.kt index f83fcaf427..95cd14be26 100644 --- a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/ClientContextParamDecorator.kt +++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/ClientContextParamDecorator.kt @@ -16,6 +16,7 @@ import software.amazon.smithy.rust.codegen.client.smithy.generators.config.Servi import software.amazon.smithy.rust.codegen.core.rustlang.RustReservedWords 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.docsOrFallback import software.amazon.smithy.rust.codegen.core.rustlang.rust import software.amazon.smithy.rust.codegen.core.rustlang.writable import software.amazon.smithy.rust.codegen.core.smithy.CodegenContext @@ -75,12 +76,22 @@ internal class ClientContextDecorator(ctx: CodegenContext) : ConfigCustomization } ServiceConfig.BuilderImpl -> writable { contextParams.forEach { param -> - param.docs?.also { docs(it) } + docsOrFallback(param.docs) rust( """ pub fn ${param.name}(mut self, ${param.name}: impl Into<#T>) -> Self { self.${param.name} = Some(${param.name}.into()); self + }""", + param.type, + ) + + docsOrFallback(param.docs) + rust( + """ + pub fn set_${param.name}(mut self, ${param.name}: Option<#T>) -> Self { + self.${param.name} = ${param.name}; + self } """, param.type, diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/EndpointConfigCustomization.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/EndpointConfigCustomization.kt index 457680dd9d..12a0fddc89 100644 --- a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/EndpointConfigCustomization.kt +++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/EndpointConfigCustomization.kt @@ -75,10 +75,10 @@ internal class EndpointConfigCustomization( /// impl endpoint::ResolveEndpoint for PrefixResolver { /// fn resolve_endpoint(&self, params: &EndpointParams) -> endpoint::Result { /// self.base_resolver - /// .resolve_endpoint(params) + /// .resolve_endpoint(params) /// .map(|ep|{ - /// let url = ep.url().to_string(); - /// ep.into_builder().url(format!("{}.{}", &self.prefix, url)).build() + /// let url = ep.url().to_string(); + /// ep.into_builder().url(format!("{}.{}", &self.prefix, url)).build() /// }) /// } /// } diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/EndpointRulesetIndex.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/EndpointRulesetIndex.kt index 1006836ca2..9027f47f12 100644 --- a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/EndpointRulesetIndex.kt +++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/EndpointRulesetIndex.kt @@ -10,6 +10,7 @@ import software.amazon.smithy.model.knowledge.KnowledgeIndex import software.amazon.smithy.model.shapes.ServiceShape import software.amazon.smithy.rulesengine.language.EndpointRuleSet import software.amazon.smithy.rulesengine.traits.EndpointRuleSetTrait +import software.amazon.smithy.rulesengine.traits.EndpointTestsTrait import software.amazon.smithy.rust.codegen.core.util.getTrait import java.util.concurrent.ConcurrentHashMap @@ -24,6 +25,8 @@ internal class EndpointRulesetIndex : KnowledgeIndex { serviceShape, ) { serviceShape.getTrait()?.ruleSet?.let { EndpointRuleSet.fromNode(it) }?.also { it.typecheck() } } + fun endpointTests(serviceShape: ServiceShape) = serviceShape.getTrait()?.testCases ?: emptyList() + companion object { fun of(model: Model): EndpointRulesetIndex { return model.getKnowledge(EndpointRulesetIndex::class.java) { EndpointRulesetIndex() } diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/EndpointTypesGenerator.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/EndpointTypesGenerator.kt index abef205095..f8f37879af 100644 --- a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/EndpointTypesGenerator.kt +++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/EndpointTypesGenerator.kt @@ -8,38 +8,51 @@ package software.amazon.smithy.rust.codegen.client.smithy.endpoint import software.amazon.smithy.rulesengine.language.EndpointRuleSet import software.amazon.smithy.rulesengine.language.syntax.parameters.Parameter import software.amazon.smithy.rulesengine.language.syntax.parameters.Parameters -import software.amazon.smithy.rulesengine.traits.EndpointTestsTrait +import software.amazon.smithy.rulesengine.traits.EndpointTestCase import software.amazon.smithy.rust.codegen.client.smithy.ClientCodegenContext import software.amazon.smithy.rust.codegen.client.smithy.endpoint.generators.EndpointParamsGenerator +import software.amazon.smithy.rust.codegen.client.smithy.endpoint.generators.EndpointResolverGenerator import software.amazon.smithy.rust.codegen.client.smithy.endpoint.generators.EndpointTestGenerator import software.amazon.smithy.rust.codegen.core.rustlang.Writable import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType -import software.amazon.smithy.rust.codegen.core.util.getTrait /** * Entrypoint for Endpoints 2.0 Code generation * * This exposes [RuntimeType]s for the individual components of endpoints 2.0 */ -class EndpointTypesGenerator(codegenContext: ClientCodegenContext, private val rules: EndpointRuleSet) { +class EndpointTypesGenerator( + codegenContext: ClientCodegenContext, + private val rules: EndpointRuleSet, + private val tests: List, +) { val params: Parameters = rules.parameters private val runtimeConfig = codegenContext.runtimeConfig private val customizations = codegenContext.rootDecorator.endpointCustomizations(codegenContext) private val stdlib = customizations .flatMap { it.customRuntimeFunctions(codegenContext) } - private val tests = codegenContext.serviceShape.getTrait()?.testCases ?: emptyList() companion object { fun fromContext(codegenContext: ClientCodegenContext): EndpointTypesGenerator? { val index = EndpointRulesetIndex.of(codegenContext.model) - val rules = index.endpointRulesForService(codegenContext.serviceShape) ?: return null - return EndpointTypesGenerator(codegenContext, rules) + val rulesOrNull = index.endpointRulesForService(codegenContext.serviceShape) + return rulesOrNull?.let { rules -> + EndpointTypesGenerator(codegenContext, rules, index.endpointTests(codegenContext.serviceShape)) + } } } fun paramsStruct(): RuntimeType = EndpointParamsGenerator(params).paramsStruct() fun defaultResolver(): RuntimeType = EndpointResolverGenerator(stdlib, runtimeConfig).defaultEndpointResolver(rules) - fun testGenerator(): Writable = EndpointTestGenerator(tests, paramsStruct(), defaultResolver(), params, runtimeConfig).generate() + fun testGenerator(): Writable = + EndpointTestGenerator(tests, paramsStruct(), defaultResolver(), params, runtimeConfig).generate() + + /** + * Load the builtIn value for [parameter] from the endpoint customizations. If the built-in comes from service config, + * [config] refers to `&crate::config::Config` + * + * Exactly one endpoint customization must provide the value for this builtIn or null is returned. + */ fun builtInFor(parameter: Parameter, config: String): Writable? { val defaultProviders = customizations .mapNotNull { it.builtInDefaultValue(parameter, config) } diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/EndpointsDecorator.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/EndpointsDecorator.kt index dc78f4e56a..46c3c46054 100644 --- a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/EndpointsDecorator.kt +++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/EndpointsDecorator.kt @@ -14,9 +14,11 @@ import software.amazon.smithy.rulesengine.language.syntax.parameters.Parameters import software.amazon.smithy.rulesengine.traits.ContextIndex import software.amazon.smithy.rust.codegen.client.smithy.ClientCodegenContext import software.amazon.smithy.rust.codegen.client.smithy.customize.RustCodegenDecorator +import software.amazon.smithy.rust.codegen.client.smithy.endpoint.generators.CustomRuntimeFunction import software.amazon.smithy.rust.codegen.client.smithy.endpoint.generators.EndpointParamsGenerator import software.amazon.smithy.rust.codegen.client.smithy.endpoint.generators.EndpointTests import software.amazon.smithy.rust.codegen.client.smithy.endpoint.generators.EndpointsModule +import software.amazon.smithy.rust.codegen.client.smithy.endpoint.rulesgen.SmithyEndpointsStdLib import software.amazon.smithy.rust.codegen.client.smithy.generators.config.ConfigCustomization import software.amazon.smithy.rust.codegen.client.smithy.generators.protocol.ClientProtocolGenerator import software.amazon.smithy.rust.codegen.core.rustlang.Writable @@ -45,10 +47,10 @@ interface EndpointCustomization { /** * Decorator that injects endpoints 2.0 resolvers throughout the entire client. * - * This _does not_ inject any standard library functions. For the standard library, ensure that - * [NativeSmithyEndpointsStdLib] is included as a decorator on the classpath. + * This decorator installs the core standard library functions. It DOES NOT inject the AWS specific functions which + * must be injected separately. * - * If the service _does not_ provide custom endpoint rules, this decorator is a no-op. + * If the service DOES NOT provide custom endpoint rules, this decorator is a no-op. */ class EndpointsDecorator : RustCodegenDecorator { override val name: String = "Endpoints" @@ -73,6 +75,16 @@ class EndpointsDecorator : RustCodegenDecorator { + return listOf( + object : EndpointCustomization { + override fun customRuntimeFunctions(codegenContext: ClientCodegenContext): List { + return SmithyEndpointsStdLib + } + }, + ) + } + override fun configCustomizations( codegenContext: ClientCodegenContext, baseCustomizations: List, diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/Util.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/Util.kt index 66e1033730..9419df2df9 100644 --- a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/Util.kt +++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/Util.kt @@ -11,6 +11,7 @@ import software.amazon.smithy.rulesengine.language.syntax.parameters.Parameter import software.amazon.smithy.rulesengine.language.syntax.parameters.ParameterType import software.amazon.smithy.rulesengine.traits.ContextParamTrait import software.amazon.smithy.rust.codegen.client.smithy.endpoint.generators.EndpointsStdLib +import software.amazon.smithy.rust.codegen.client.smithy.endpoint.generators.FunctionRegistry import software.amazon.smithy.rust.codegen.core.rustlang.CargoDependency import software.amazon.smithy.rust.codegen.core.rustlang.InlineDependency import software.amazon.smithy.rust.codegen.core.rustlang.RustDependency diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/generators/EndpointResolverGenerator.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/generators/EndpointResolverGenerator.kt index 231f01d1c7..0a87d9e81f 100644 --- a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/generators/EndpointResolverGenerator.kt +++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/generators/EndpointResolverGenerator.kt @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package software.amazon.smithy.rust.codegen.client.smithy.endpoint +package software.amazon.smithy.rust.codegen.client.smithy.endpoint.generators import software.amazon.smithy.rulesengine.language.Endpoint import software.amazon.smithy.rulesengine.language.EndpointRuleSet @@ -16,11 +16,13 @@ import software.amazon.smithy.rulesengine.language.syntax.fn.IsSet import software.amazon.smithy.rulesengine.language.syntax.rule.Condition import software.amazon.smithy.rulesengine.language.syntax.rule.Rule import software.amazon.smithy.rulesengine.language.visit.RuleValueVisitor -import software.amazon.smithy.rust.codegen.client.smithy.endpoint.generators.EndpointParamsGenerator -import software.amazon.smithy.rust.codegen.client.smithy.endpoint.generators.EndpointsImpl -import software.amazon.smithy.rust.codegen.client.smithy.endpoint.generators.EndpointsModule +import software.amazon.smithy.rust.codegen.client.smithy.endpoint.Context +import software.amazon.smithy.rust.codegen.client.smithy.endpoint.Types +import software.amazon.smithy.rust.codegen.client.smithy.endpoint.endpointsLib +import software.amazon.smithy.rust.codegen.client.smithy.endpoint.memberName import software.amazon.smithy.rust.codegen.client.smithy.endpoint.rulesgen.ExpressionGenerator import software.amazon.smithy.rust.codegen.client.smithy.endpoint.rulesgen.Ownership +import software.amazon.smithy.rust.codegen.client.smithy.endpoint.rustName import software.amazon.smithy.rust.codegen.core.rustlang.Attribute import software.amazon.smithy.rust.codegen.core.rustlang.RustWriter import software.amazon.smithy.rust.codegen.core.rustlang.Writable @@ -42,13 +44,16 @@ abstract class CustomRuntimeFunction { /** Initialize the struct field to a default value */ abstract fun structFieldInit(): Writable? - /** The argument slot of the runtime function. MUST NOT include `,` + /** The argument slot of the runtime function. MUST NOT end with `,` * e.g `partition_data: &PartitionData` * */ abstract fun additionalArgsSignature(): Writable? /** - * A writable that passes additional args from `self` into the function. Must match the order of additionalArgsSignature + * A writable that passes additional args from `self` into the function. + * + * - Must match the order of additionalArgsSignature + * - Must not end with `,` */ abstract fun additionalArgsInvocation(self: String): Writable? @@ -58,7 +63,14 @@ abstract class CustomRuntimeFunction { abstract fun structField(): Writable? /** - * Invoking the runtime function—(parens / args not needed) `$fn` + * Invoking the runtime function—(parens / args not needed): `$fn` + * + * e.g. `crate::endpoint_lib::uri_encode::uri_encode` + * + * The function signature must match the standard endpoints function signature: + * - arguments in the order matching the spec + * - additionalArgsInvocation (if needed) + * - &mut DiagnosticCollector */ abstract fun usage(): Writable } @@ -72,11 +84,17 @@ class FunctionRegistry(private val functions: List) { } /** - * Generate an endpoint resolver struct. The struct may contain extras resulting from the usage of functions e.g. partition function + * Generate an endpoint resolver struct. The struct may contain additional fields required by the usage of + * additional functions e.g. `aws.partition` requires a `PartitionResolver` to cache the parsed result of `partitions.json` + * and to facilitate loading additional partitions at runtime. + * + * Additionally, runtime functions will conditionally bring in: * 1. resolver configuration (e.g. a custom partitions.json) * 2. extra function arguments in the resolver * 3. the runtime type of the library function * + * These dependencies are only brought in when the rules actually use these functions. + * * ```rust * pub struct DefaultResolver { * partition: PartitionResolver @@ -103,9 +121,6 @@ class FunctionRegistry(private val functions: List) { internal class EndpointResolverGenerator(stdlib: List, runtimeConfig: RuntimeConfig) { private val registry: FunctionRegistry = FunctionRegistry(stdlib) - // first, make a custom RustWriter and generate the interior of the resolver into it. - // next, since we've now captured what runtime functions are required, generate the container - private val types = Types(runtimeConfig) private val codegenScope = arrayOf( "endpoint" to types.smithyHttpEndpointModule, @@ -123,8 +138,9 @@ internal class EndpointResolverGenerator(stdlib: List, ru /** * Generates the endpoint resolver struct * - * If the rules require a runtime function that has state (e.g., the partition resolver, the `[CustomRuntimeFunction.structField]` - * will insert the required fields into the resolver so that they can be used later. + * If the rules require a runtime function that has state (e.g., the partition resolver, + * the `[CustomRuntimeFunction.structField]`) will insert the required fields into the resolver so that they can + * be used later. */ fun defaultEndpointResolver(endpointRuleSet: EndpointRuleSet): RuntimeType { check(endpointRuleSet.rules.isNotEmpty()) { "EndpointRuleset must contain at least one rule." } @@ -177,7 +193,7 @@ internal class EndpointResolverGenerator(stdlib: List, ru rustTemplate( """ pub(super) fn resolve_endpoint($ParamsName: &#{Params}, $DiagnosticCollector: &mut #{DiagnosticCollector}, #{additional_args}) -> #{endpoint}::Result { - #{body:W} + #{body:W} } """, @@ -259,7 +275,7 @@ internal class EndpointResolverGenerator(stdlib: List, ru val next = generateRuleInternal(rule, rest) when { fn.type() is Type.Option || - // ReterminusCore bug: substring should return `Option`: https://github.com/awslabs/smithy/pull/1504/files + // TODO(https://github.com/awslabs/smithy/pull/1504): ReterminusCore bug: substring should return `Option`: (fn as Function).name == "substring" -> { Attribute.AllowUnused.render(this) rustTemplate( diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/rulesgen/ExpressionGenerator.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/rulesgen/ExpressionGenerator.kt index d73544a37a..63871613c8 100644 --- a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/rulesgen/ExpressionGenerator.kt +++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/rulesgen/ExpressionGenerator.kt @@ -14,7 +14,7 @@ import software.amazon.smithy.rulesengine.language.syntax.fn.FunctionDefinition import software.amazon.smithy.rulesengine.language.syntax.fn.GetAttr import software.amazon.smithy.rulesengine.language.visit.ExpressionVisitor import software.amazon.smithy.rust.codegen.client.smithy.endpoint.Context -import software.amazon.smithy.rust.codegen.client.smithy.endpoint.EndpointResolverGenerator +import software.amazon.smithy.rust.codegen.client.smithy.endpoint.generators.EndpointResolverGenerator import software.amazon.smithy.rust.codegen.client.smithy.endpoint.rustName import software.amazon.smithy.rust.codegen.core.rustlang.Writable import software.amazon.smithy.rust.codegen.core.rustlang.join @@ -95,7 +95,12 @@ class ExpressionGenerator( } override fun visitLibraryFunction(fn: FunctionDefinition, args: MutableList): Writable = writable { - val fnDefinition = context.functionRegistry.fnFor(fn.id) ?: PANIC("no runtime function for ${fn.id}") + val fnDefinition = context.functionRegistry.fnFor(fn.id) + ?: PANIC( + "no runtime function for ${fn.id} " + + "(hint: if this is a custom or aws-specific runtime function, ensure the relevant standard library has been loaded " + + "on the classpath)", + ) val expressionGenerator = ExpressionGenerator(Ownership.Borrowed, context) val argWritables = args.map { expressionGenerator.generate(it) } rustTemplate( diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/rulesgen/StdLib.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/rulesgen/StdLib.kt index a545870f2a..08401904db 100644 --- a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/rulesgen/StdLib.kt +++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/rulesgen/StdLib.kt @@ -6,55 +6,29 @@ package software.amazon.smithy.rust.codegen.client.smithy.endpoint.rulesgen import software.amazon.smithy.model.node.Node -import software.amazon.smithy.rust.codegen.client.smithy.ClientCodegenContext -import software.amazon.smithy.rust.codegen.client.smithy.customize.RustCodegenDecorator -import software.amazon.smithy.rust.codegen.client.smithy.endpoint.CustomRuntimeFunction -import software.amazon.smithy.rust.codegen.client.smithy.endpoint.EndpointCustomization import software.amazon.smithy.rust.codegen.client.smithy.endpoint.endpointsLib -import software.amazon.smithy.rust.codegen.client.smithy.generators.protocol.ClientProtocolGenerator +import software.amazon.smithy.rust.codegen.client.smithy.endpoint.generators.CustomRuntimeFunction import software.amazon.smithy.rust.codegen.core.rustlang.CargoDependency import software.amazon.smithy.rust.codegen.core.rustlang.Writable import software.amazon.smithy.rust.codegen.core.rustlang.rust import software.amazon.smithy.rust.codegen.core.rustlang.rustTemplate import software.amazon.smithy.rust.codegen.core.rustlang.toType 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.RuntimeConfig import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType import software.amazon.smithy.rust.codegen.core.util.dq /** - * StandardLibrary functions for native-smithy implementations - * - * Note: this does not include any `aws.*` functions + * Standard library functions available to all generated crates (e.g. not `aws.` specific / prefixed) */ -class NativeSmithyEndpointsStdLib : RustCodegenDecorator { - override val name: String = "NativeSmithyStandardLib" - override val order: Byte = 0 - - override fun supportsCodegenContext(clazz: Class): Boolean { - return clazz.isAssignableFrom(ClientCodegenContext::class.java) - } - - override fun endpointCustomizations(codegenContext: ClientCodegenContext): List { - return listOf( - object : EndpointCustomization { - override fun customRuntimeFunctions(codegenContext: ClientCodegenContext): List { - return NativeSmithyFunctions - } - }, - ) - } -} - -internal val NativeSmithyFunctions: List = listOf( - PureRuntimeFunction("substring", endpointsLib("substring").toType().member("substring")), - PureRuntimeFunction("isValidHostLabel", endpointsLib("host").toType().member("is_valid_host_label")), - PureRuntimeFunction( +internal val SmithyEndpointsStdLib: List = listOf( + SimpleRuntimeFunction("substring", endpointsLib("substring").toType().member("substring")), + SimpleRuntimeFunction("isValidHostLabel", endpointsLib("host").toType().member("is_valid_host_label")), + SimpleRuntimeFunction( "parseURL", endpointsLib("parse_url", CargoDependency.Http, CargoDependency.Url).toType().member("parse_url"), ), - PureRuntimeFunction( + SimpleRuntimeFunction( "uriEncode", endpointsLib("uri_encode", CargoDependency.PercentEncoding).toType().member("uri_encode"), ), @@ -66,8 +40,8 @@ internal val NativeSmithyFunctions: List = listOf( * This is defined in client-codegen to support running tests—it is not used when generating smithy-native services. */ fun awsStandardLib(runtimeConfig: RuntimeConfig, partitionsDotJson: Node) = listOf( - PureRuntimeFunction("aws.parseArn", endpointsLib("arn").toType().member("parse_arn")), - PureRuntimeFunction( + SimpleRuntimeFunction("aws.parseArn", endpointsLib("arn").toType().member("parse_arn")), + SimpleRuntimeFunction( "aws.isVirtualHostableS3Bucket", endpointsLib( "s3", @@ -126,9 +100,11 @@ class AwsPartitionResolver(runtimeConfig: RuntimeConfig, private val partitionsD } /** - * A runtime function that doesn't need any support structures and can be invoked directly + * A runtime function that doesn't need any support structures and can be invoked directly. + * + * Currently, this is every runtime function other than `aws.partition`. */ -private class PureRuntimeFunction(override val id: String, private val runtimeType: RuntimeType) : +private class SimpleRuntimeFunction(override val id: String, private val runtimeType: RuntimeType) : CustomRuntimeFunction() { override fun structFieldInit(): Writable? = null diff --git a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/endpoint/EndpointResolverGeneratorTest.kt b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/endpoint/EndpointResolverGeneratorTest.kt index ec746ce9e7..07df842577 100644 --- a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/endpoint/EndpointResolverGeneratorTest.kt +++ b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/endpoint/EndpointResolverGeneratorTest.kt @@ -15,10 +15,10 @@ import software.amazon.smithy.rulesengine.language.Endpoint import software.amazon.smithy.rulesengine.language.syntax.expr.Expression import software.amazon.smithy.rulesengine.language.syntax.expr.Literal import software.amazon.smithy.rulesengine.testutil.TestDiscovery -import software.amazon.smithy.rust.codegen.client.smithy.endpoint.EndpointResolverGenerator import software.amazon.smithy.rust.codegen.client.smithy.endpoint.generators.EndpointParamsGenerator +import software.amazon.smithy.rust.codegen.client.smithy.endpoint.generators.EndpointResolverGenerator import software.amazon.smithy.rust.codegen.client.smithy.endpoint.generators.EndpointTestGenerator -import software.amazon.smithy.rust.codegen.client.smithy.endpoint.rulesgen.NativeSmithyFunctions +import software.amazon.smithy.rust.codegen.client.smithy.endpoint.rulesgen.SmithyEndpointsStdLib import software.amazon.smithy.rust.codegen.client.smithy.endpoint.rulesgen.awsStandardLib import software.amazon.smithy.rust.codegen.core.rustlang.rustTemplate import software.amazon.smithy.rust.codegen.core.testutil.TestRuntimeConfig @@ -51,7 +51,7 @@ class EndpointResolverGeneratorTest { suite.ruleSet().typecheck() project.lib { val ruleset = EndpointResolverGenerator( - NativeSmithyFunctions + awsStandardLib(TestRuntimeConfig, partitionsJson), + SmithyEndpointsStdLib + awsStandardLib(TestRuntimeConfig, partitionsJson), TestRuntimeConfig, ).defaultEndpointResolver(suite.ruleSet()) val testGenerator = EndpointTestGenerator( @@ -76,7 +76,7 @@ class EndpointResolverGeneratorTest { suite.ruleSet().typecheck() project.lib { val ruleset = EndpointResolverGenerator( - NativeSmithyFunctions + awsStandardLib(TestRuntimeConfig, partitionsJson), + SmithyEndpointsStdLib + awsStandardLib(TestRuntimeConfig, partitionsJson), TestRuntimeConfig, ).defaultEndpointResolver(suite.ruleSet()) val testGenerator = EndpointTestGenerator( diff --git a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/endpoint/EndpointsDecoratorTest.kt b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/endpoint/EndpointsDecoratorTest.kt index 8a0bae4d79..be28bab93f 100644 --- a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/endpoint/EndpointsDecoratorTest.kt +++ b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/endpoint/EndpointsDecoratorTest.kt @@ -9,7 +9,6 @@ import io.kotest.assertions.throwables.shouldThrow import io.kotest.matchers.string.shouldContain import org.junit.jupiter.api.Test import software.amazon.smithy.rust.codegen.client.smithy.endpoint.EndpointsDecorator -import software.amazon.smithy.rust.codegen.client.smithy.endpoint.rulesgen.NativeSmithyEndpointsStdLib import software.amazon.smithy.rust.codegen.client.testutil.clientIntegrationTest import software.amazon.smithy.rust.codegen.core.rustlang.rust import software.amazon.smithy.rust.codegen.core.testutil.TokioTest @@ -95,7 +94,7 @@ class EndpointsDecoratorTest { fun `set an endpoint in the property bag`() { val testDir = clientIntegrationTest( model, - addtionalDecorators = listOf(EndpointsDecorator(), NativeSmithyEndpointsStdLib()), + addtionalDecorators = listOf(EndpointsDecorator()), command = { "cargo check".runWithWarnings(it) }, ) { clientCodegenContext, rustCrate -> rustCrate.integrationTest("endpoint_params_test") { diff --git a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/rulesgen/ExpressionGeneratorTest.kt b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/rulesgen/ExpressionGeneratorTest.kt index dbe21b3f59..bdcd52b020 100644 --- a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/rulesgen/ExpressionGeneratorTest.kt +++ b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/rulesgen/ExpressionGeneratorTest.kt @@ -14,8 +14,9 @@ import software.amazon.smithy.rulesengine.language.syntax.Identifier import software.amazon.smithy.rulesengine.language.syntax.expr.Expression import software.amazon.smithy.rulesengine.language.syntax.expr.Literal import software.amazon.smithy.rulesengine.language.syntax.expr.Template +import software.amazon.smithy.rulesengine.language.syntax.fn.LibraryFunction import software.amazon.smithy.rust.codegen.client.smithy.endpoint.Context -import software.amazon.smithy.rust.codegen.client.smithy.endpoint.FunctionRegistry +import software.amazon.smithy.rust.codegen.client.smithy.endpoint.generators.FunctionRegistry import software.amazon.smithy.rust.codegen.core.rustlang.CargoDependency import software.amazon.smithy.rust.codegen.core.rustlang.rust import software.amazon.smithy.rust.codegen.core.rustlang.rustTemplate @@ -25,9 +26,20 @@ import software.amazon.smithy.rust.codegen.core.testutil.compileAndTest import software.amazon.smithy.rust.codegen.core.testutil.unitTest internal class ExprGeneratorTest { + /** + * This works around a bug in smithy-endpoint-rules where the constructors on functions like `BooleanEquals` + * hit the wrong branch in the visitor (but when they get parsed, they hit the right branch). + */ fun Expression.shoop() = Expression.fromNode(this.toNode()) private val testContext = Context(FunctionRegistry(listOf()), TestRuntimeConfig) + @Test + fun `fail when smithy is fixed`() { + check(BooleanEquals.ofExpressions(Expression.of(true), Expression.of(true)) is LibraryFunction) { + "smithy has been fixed, shoop can be removed" + } + } + @Test fun generateExprs() { val boolEq = Expression.of(true).equal(true).shoop() diff --git a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/rustlang/CargoDependency.kt b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/rustlang/CargoDependency.kt index cbe0c06b4f..60a43b8362 100644 --- a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/rustlang/CargoDependency.kt +++ b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/rustlang/CargoDependency.kt @@ -187,7 +187,7 @@ data class CargoDependency( } companion object { - val OnceCell: CargoDependency = CargoDependency("once_cell", CratesIo("1")) + val OnceCell: CargoDependency = CargoDependency("once_cell", CratesIo("1.16")) val Url: CargoDependency = CargoDependency("url", CratesIo("2.3.1")) val Bytes: CargoDependency = CargoDependency("bytes", CratesIo("1.0.0")) val BytesUtils: CargoDependency = CargoDependency("bytes-utils", CratesIo("0.1.0")) diff --git a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/rustlang/RustWriter.kt b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/rustlang/RustWriter.kt index 98a087ccb2..9f3ed975e5 100644 --- a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/rustlang/RustWriter.kt +++ b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/rustlang/RustWriter.kt @@ -247,10 +247,18 @@ fun > T.documentShape( ): T { val docTrait = shape.getMemberTrait(model, DocumentationTrait::class.java).orNull() - when (docTrait?.value?.isNotBlank()) { + return docsOrFallback(docTrait?.value, autoSuppressMissingDocs, note) +} + +fun > T.docsOrFallback( + docs: String? = null, + autoSuppressMissingDocs: Boolean = true, + note: String? = null, +): T { + when (docs?.isNotBlank()) { // If docs are modeled, then place them on the code generated shape true -> { - this.docs(normalizeHtml(escape(docTrait.value))) + this.docs(normalizeHtml(escape(docs))) note?.also { // Add a blank line between the docs and the note to visually differentiate write("///") @@ -646,7 +654,8 @@ class RustWriter private constructor( is Function<*> -> { @Suppress("UNCHECKED_CAST") - val func = t as? Writable ?: throw CodegenException("Invalid function type (expected writable) ($t)") + val func = + t as? Writable ?: throw CodegenException("Invalid function type (expected writable) ($t)") val innerWriter = RustWriter(filename, namespace, printWarning = false) func(innerWriter) innerWriter.dependencies.forEach { addDependency(it) } diff --git a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/testutil/Rust.kt b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/testutil/Rust.kt index 2fb8cdbda5..17b71989f8 100644 --- a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/testutil/Rust.kt +++ b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/testutil/Rust.kt @@ -85,6 +85,11 @@ object TestWorkspace { version = "0.0.1" """.trimIndent(), ) + newProject.resolve("rust-toolchain.toml").writeText( + // help rust select the right version when we run cargo test + // TODO(cleanup): load this from the msrv property using a method like we do for runtime crate versions + "[toolchain]\nchannel = \"1.62.1\"\n", + ) // ensure there at least an empty lib.rs file to avoid broken crates newProject.resolve("src").mkdirs() newProject.resolve("src/lib.rs").writeText("") @@ -110,13 +115,19 @@ object TestWorkspace { PANIC("") } } - val writer = TestWriterDelegator( + return TestWriterDelegator( FileManifest.create(subprojectDir.toPath()), symbolProvider, CoreCodegenConfig(debugMode = debugMode), - ) - writer.lib { rust("// touch lib.rs") } - return writer + ).apply { + lib { + // If the test fails before the crate is finalized, we'll end up with a broken crate. + // Since all tests are generated into the same workspace (to avoid re-compilation) a broken crate + // breaks the workspace and all subsequent unit tests. By putting this comment in, we prevent + // that state from occurring. + rust("// touch lib.rs") + } + } } }