Skip to content

[draft] Implement @cacheable for servers #4234

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

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
7 changes: 5 additions & 2 deletions codegen-core/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ val smithyVersion: String by project

dependencies {
implementation(kotlin("stdlib-jdk8"))
implementation(project(":codegen-traits"))
implementation("org.jsoup:jsoup:1.16.2")
api("software.amazon.smithy:smithy-codegen-core:$smithyVersion")
api("com.moandjiezana.toml:toml4j:0.7.2")
Expand Down Expand Up @@ -80,15 +81,17 @@ val generateBuildEnvironmentConstants = tasks.register("generateBuildEnvironment

// Generate the Kotlin file.
val generatedFile = file("$outputDir/BuildEnvironment.kt")
generatedFile.writeText("""
generatedFile.writeText(
"""
// This file is automatically generated. Do not modify manually.
package software.amazon.smithy.rust.codegen.core.generated

object BuildEnvironment {
const val MSRV: String = "$rustMsrv"
const val PROJECT_DIR: String = "$rootDir"
}
""".trimIndent())
""".trimIndent(),
)
}
}

Expand Down
71 changes: 71 additions & 0 deletions codegen-core/common-test-models/cacheable-test.smithy
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
$version: "2"

namespace example.cacheable

use smithy.rust#cacheable

/// A service that uses the CBOR protocol and has cacheable members
@protocols([{name: "smithy.rpcv2.cbor"}])
service CacheableService {
version: "2023-01-01",
operations: [GetUserData]
}

/// Get user data operation
operation GetUserData {
input: GetUserDataInput,
output: GetUserDataOutput
}

/// Input for GetUserData operation
structure GetUserDataInput {
/// User ID to retrieve data for
userId: String
}

/// Output for GetUserData operation
structure GetUserDataOutput {
/// User data that can be cached
@cacheable
userData: UserData,

/// Request ID for tracing
requestId: String
}

/// User data structure
structure UserData {
/// User name
name: String,

/// User email
email: String,

/// User preferences that can be cached
@cacheable
preferences: UserPreferences
}

/// User preferences structure
structure UserPreferences {
/// Theme preference
theme: String,

/// Language preference
language: String,

/// Notification settings
notifications: Boolean
}

/// A list of users that can be cached
structure UserList {
/// List of users
@cacheable
users: Users
}

/// List of user data
list Users {
member: UserData
}
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,12 @@ class InlineDependency(
"sdk_feature_tracker",
CargoDependency.smithyRuntime(runtimeConfig),
)

fun cacheable(): InlineDependency =
forInlineableRustFile(
"cacheable",
CargoDependency.Bytes,
)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

package software.amazon.smithy.rust.codegen.core.rustlang

import software.amazon.smithy.rust.codegen.core.rustlang.Attribute.Companion.deprecated
import software.amazon.smithy.rust.codegen.core.rustlang.Attribute.Companion.derive
import software.amazon.smithy.rust.codegen.core.rustlang.Attribute.Companion.serde
import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType
Expand Down Expand Up @@ -34,6 +35,8 @@ sealed class RustType {
val member: RustType
val namespace: kotlin.String?
val name: kotlin.String

fun map(f: (RustType) -> RustType): RustType
}

/*
Expand Down Expand Up @@ -107,11 +110,16 @@ sealed class RustType {

data class Slice(override val member: RustType) : RustType(), Container {
override val name: kotlin.String = ""

override fun map(f: (RustType) -> RustType): RustType = this.copy(f(member))
}

data class HashMap(val key: RustType, override val member: RustType) : RustType(), Container {
// validating that `key` is a string occurs in the constructor in SymbolVisitor
override val name = RuntimeType.HashMap.name

override fun map(f: (RustType) -> RustType): RustType = this.copy(member = f(member))

override val namespace = RuntimeType.HashMap.namespace

companion object {
Expand All @@ -124,6 +132,8 @@ sealed class RustType {
override val name = RuntimeType.Vec.name
override val namespace = RuntimeType.Vec.namespace

override fun map(f: (RustType) -> RustType): RustType = this.copy(f(member))

companion object {
// This is Vec intentionally. Note the following passage from the Smithy spec:
// Sets MUST be insertion ordered. Not all programming languages that support sets
Expand All @@ -136,6 +146,8 @@ sealed class RustType {
}

data class Reference(val lifetime: kotlin.String?, override val member: RustType) : RustType(), Container {
override fun map(f: (RustType) -> RustType): RustType = this.copy(member = f(member))

override val name = member.name
}

Expand All @@ -144,6 +156,8 @@ sealed class RustType {
override val name = runtimeType.name
override val namespace = runtimeType.namespace

override fun map(f: (RustType) -> RustType): RustType = this.copy(member = f(member))

/** Convert `Option<T>` to `Option<&T>` **/
fun referenced(lifetime: kotlin.String?): Option {
return Option(Reference(lifetime, this.member))
Expand All @@ -154,23 +168,31 @@ sealed class RustType {
private val runtimeType = RuntimeType.MaybeConstrained
override val name = runtimeType.name
override val namespace = runtimeType.namespace

override fun map(f: (RustType) -> RustType): RustType = this.copy(member = f(member))
}

data class Box(override val member: RustType) : RustType(), Container {
private val runtimeType = RuntimeType.Box
override val name = runtimeType.name
override val namespace = runtimeType.namespace

override fun map(f: (RustType) -> RustType): RustType = this.copy(member = f(member))
}

data class Dyn(override val member: RustType) : RustType(), Container {
override val name = "dyn"
override val namespace: kotlin.String? = null

override fun map(f: (RustType) -> RustType): RustType = this.copy(member = f(member))
}

data class Vec(override val member: RustType) : RustType(), Container {
private val runtimeType: RuntimeType = RuntimeType.Vec
override val name = runtimeType.name
override val namespace = runtimeType.namespace

override fun map(f: (RustType) -> RustType): RustType = this.copy(member = f(member))
}

data class Opaque(override val name: kotlin.String, override val namespace: kotlin.String? = null) : RustType()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import software.amazon.smithy.codegen.core.CodegenException
import software.amazon.smithy.codegen.core.Symbol
import software.amazon.smithy.codegen.core.SymbolDependencyContainer
import software.amazon.smithy.codegen.core.SymbolWriter
import software.amazon.smithy.codegen.core.SymbolWriter.Factory
import software.amazon.smithy.model.Model
import software.amazon.smithy.model.node.Node
import software.amazon.smithy.model.shapes.BooleanShape
Expand Down Expand Up @@ -844,6 +843,7 @@ class RustWriter private constructor(
}
}
}

else ->
rustBlock("") {
block(variable)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/

package software.amazon.smithy.rust.codegen.core.smithy

import software.amazon.smithy.codegen.core.Symbol
import software.amazon.smithy.model.shapes.ListShape
import software.amazon.smithy.model.shapes.MemberShape
import software.amazon.smithy.model.shapes.Shape
import software.amazon.smithy.rust.codegen.core.rustlang.RustType
import software.amazon.smithy.rust.codegen.traits.CacheableTrait

/**
* Wrapping symbol provider support adding Cacheable to members
*/
class CacheableSymbolProvider(private val base: RustSymbolProvider) : WrappingSymbolProvider(base) {
private val runtimeConfig = base.config.runtimeConfig

override fun toSymbol(shape: Shape): Symbol {
return when (shape) {
is ListShape ->
base.toSymbol(shape)
.mapRustType { ty -> (ty as RustType.Vec).copy(member = toSymbol(shape.member).rustType()) }

is MemberShape -> {
val initial = base.toSymbol(shape)
val targetShape = model.expectShape(shape.target)
val cacheableType = RuntimeType.Cacheable.toSymbol().rustType()

return if (shape.hasTrait(CacheableTrait::class.java)) {
val cacheable = RuntimeType.Cacheable
initial.mapRustType { initial ->
when (initial) {
is RustType.Option -> initial.map { RustType.Application(cacheableType, listOf(it)) }

else ->
RustType.Application(
type = cacheableType,
args = listOf(initial),
)
}
}.toBuilder().addReference(cacheable.toSymbol()).build()
} else {
val targetSymbol = toSymbol(targetShape)
initial.mapRustType { memberRustType ->
when (memberRustType) {
is RustType.Container -> memberRustType.map { targetSymbol.rustType() }
else -> memberRustType
}
}
}
}

else -> base.toSymbol(shape)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,8 @@ data class RuntimeType(val path: String, val dependency: RustDependency? = null)
val MaybeConstrained =
RuntimeType("crate::constrained::MaybeConstrained", InlineDependency.constrained())

val Cacheable = cacheable().resolve("Cacheable")

// smithy runtime types
fun smithyAsync(runtimeConfig: RuntimeConfig) = CargoDependency.smithyAsync(runtimeConfig).toType()

Expand Down Expand Up @@ -488,6 +490,7 @@ data class RuntimeType(val path: String, val dependency: RustDependency? = null)
// clients allow offsets, servers do nt
TimestampFormatTrait.Format.DATE_TIME ->
codegenTarget.ifClient { "DateTimeWithOffset" } ?: "DateTime"

TimestampFormatTrait.Format.HTTP_DATE -> "HttpDate"
TimestampFormatTrait.Format.UNKNOWN -> TODO()
}
Expand Down Expand Up @@ -545,5 +548,7 @@ data class RuntimeType(val path: String, val dependency: RustDependency? = null)

fun clientRequestCompression(runtimeConfig: RuntimeConfig) =
forInlineDependency(InlineDependency.clientRequestCompression(runtimeConfig))

fun cacheable() = forInlineDependency(InlineDependency.cacheable())
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,14 @@ fun Symbol.isOptional(): Boolean =
else -> false
}

fun Symbol.isCacheable(): Boolean {
val rustType = this.rustType().stripOuter<RustType.Option>()
if (rustType is RustType.Application) {
return rustType.type.name == "Cacheable"
}
return false
}

fun Symbol.isRustBoxed(): Boolean = rustType().stripOuter<RustType.Option>() is RustType.Box

private const val RUST_TYPE_KEY = "rusttype"
Expand Down
Loading
Loading