Skip to content
Open
Show file tree
Hide file tree
Changes from 3 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
14 changes: 14 additions & 0 deletions .changelog/1762113734.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
applies_to:
- server
authors:
- drganjoo
references:
- smithy-rs#3362
breaking: false
new_feature: true
bug_fix: false
---
Add http-1x codegen support for server SDK generation

Enables generation of server SDKs compatible with either [email protected]/[email protected] or [email protected]/[email protected] based on the http-1x codegen flag.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

need more detail in the changelog message--e.g. changelog needs to say what the actual flag is, how to enable it etc.

Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ class EventStreamSymbolProvider(
private val runtimeConfig: RuntimeConfig,
base: RustSymbolProvider,
private val target: CodegenTarget,
private val smithyHttpDependency: CargoDependency = CargoDependency.smithyHttp(runtimeConfig),
) : WrappingSymbolProvider(base) {
override fun toSymbol(shape: Shape): Symbol {
val initial = super.toSymbol(shape)
Expand All @@ -46,7 +47,7 @@ class EventStreamSymbolProvider(
val unionShape = model.expectShape(shape.target).asUnionShape().get()
val error =
if (target == CodegenTarget.SERVER && unionShape.eventStreamErrors().isEmpty()) {
RuntimeType.smithyHttp(runtimeConfig).resolve("event_stream::MessageStreamError").toSymbol()
smithyHttpDependency.toType().resolve("event_stream::MessageStreamError").toSymbol()
} else {
symbolForEventStreamError(unionShape)
}
Expand All @@ -57,10 +58,10 @@ class EventStreamSymbolProvider(
(shape.isOutputEventStream(model) && target == CodegenTarget.SERVER)
val outer =
when (isSender) {
true -> RuntimeType.eventStreamSender(runtimeConfig).toSymbol().rustType()
true -> smithyHttpDependency.toType().resolve("event_stream::EventStreamSender").toSymbol().rustType()
else -> {
if (target == CodegenTarget.SERVER) {
RuntimeType.eventStreamReceiver(runtimeConfig).toSymbol().rustType()
smithyHttpDependency.toType().resolve("event_stream::Receiver").toSymbol().rustType()
} else {
RuntimeType.eventReceiver(runtimeConfig).toSymbol().rustType()
}
Expand All @@ -71,7 +72,7 @@ class EventStreamSymbolProvider(
.name(rustType.name)
.rustType(rustType)
.addReference(initial)
.addDependency(CargoDependency.smithyHttp(runtimeConfig).withFeature("event-stream"))
.addDependency(smithyHttpDependency.withFeature("event-stream"))
.addReference(error)
.build()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import software.amazon.smithy.rust.codegen.core.util.hasEventStreamOperations
import software.amazon.smithy.rust.codegen.core.util.hasStreamingMember

/** Returns true if the model has normal streaming operations (excluding event streams) */
private fun hasStreamingOperations(
fun hasStreamingOperations(
model: Model,
serviceShape: ServiceShape,
): Boolean {
Expand All @@ -49,10 +49,10 @@ private fun structUnionMembersMatchPredicate(
}

/** Returns true if the model uses any blob shapes */
private fun hasBlobs(model: Model): Boolean = structUnionMembersMatchPredicate(model, Shape::isBlobShape)
fun hasBlobs(model: Model): Boolean = structUnionMembersMatchPredicate(model, Shape::isBlobShape)

/** Returns true if the model uses any timestamp shapes */
private fun hasDateTimes(model: Model): Boolean = structUnionMembersMatchPredicate(model, Shape::isTimestampShape)
fun hasDateTimes(model: Model): Boolean = structUnionMembersMatchPredicate(model, Shape::isTimestampShape)

/** Adds re-export statements for Smithy primitives */
fun pubUseSmithyPrimitives(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import software.amazon.smithy.model.shapes.UnionShape
import software.amazon.smithy.model.traits.EnumTrait
import software.amazon.smithy.model.traits.MediaTypeTrait
import software.amazon.smithy.model.traits.TimestampFormatTrait
import software.amazon.smithy.rust.codegen.core.rustlang.CargoDependency
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.Writable
Expand Down Expand Up @@ -124,12 +125,14 @@ class HttpBindingGenerator(
private val symbolProvider: SymbolProvider,
private val operationShape: OperationShape,
private val customizations: List<HttpBindingCustomization> = listOf(),
private val httpRuntimeType: RuntimeType = RuntimeType.Http1x,
private val smithyHttpRuntimeType: RuntimeType = RuntimeType.smithyHttp(codegenContext.runtimeConfig),
) {
private val runtimeConfig = codegenContext.runtimeConfig
private val codegenTarget = codegenContext.target
private val model = codegenContext.model
private val index = HttpBindingIndex.of(model)
private val headerUtil = RuntimeType.smithyHttp(runtimeConfig).resolve("header")
private val headerUtil = smithyHttpRuntimeType.resolve("header")
private val defaultTimestampFormat = TimestampFormatTrait.Format.EPOCH_SECONDS
private val protocolFunctions = ProtocolFunctions(codegenContext)
private val serializerUtil = SerializerUtil(model, symbolProvider)
Expand Down Expand Up @@ -268,6 +271,7 @@ class HttpBindingGenerator(
codegenContext,
operationShape,
targetShape,
smithyHttpRuntimeType.dependency!! as CargoDependency,
).render()
rustTemplate(
"""
Expand All @@ -285,7 +289,7 @@ class HttpBindingGenerator(
rustTemplate(
"#{EventReceiver}::new(#{Receiver}::new(unmarshaller, body))",
"EventReceiver" to RuntimeType.eventReceiver(runtimeConfig),
"Receiver" to RuntimeType.eventStreamReceiver(runtimeConfig),
"Receiver" to smithyHttpRuntimeType.resolve("event_stream::Receiver"),
)
}
},
Expand Down Expand Up @@ -535,8 +539,8 @@ class HttpBindingGenerator(
val codegenScope =
arrayOf(
"BuildError" to runtimeConfig.operationBuildError(),
HttpMessageType.REQUEST.name to RuntimeType.HttpRequestBuilder1x,
HttpMessageType.RESPONSE.name to RuntimeType.HttpResponseBuilder1x,
HttpMessageType.REQUEST.name to httpRuntimeType.resolve("request::Builder"),
HttpMessageType.RESPONSE.name to httpRuntimeType.resolve("response::Builder"),
"Shape" to shapeSymbol,
)
rustBlockTemplate(
Expand Down Expand Up @@ -712,7 +716,7 @@ class HttpBindingGenerator(
builder = builder.header("$headerName", header_value);

""",
"HeaderValue" to RuntimeType.Http1x.resolve("HeaderValue"),
"HeaderValue" to httpRuntimeType.resolve("HeaderValue"),
"invalid_field_error" to renderErrorMessage("header_value"),
)
}
Expand Down Expand Up @@ -767,8 +771,8 @@ class HttpBindingGenerator(
}

""",
"HeaderValue" to RuntimeType.Http1x.resolve("HeaderValue"),
"HeaderName" to RuntimeType.Http1x.resolve("HeaderName"),
"HeaderValue" to httpRuntimeType.resolve("HeaderValue"),
"HeaderName" to httpRuntimeType.resolve("HeaderName"),
"invalid_header_name" to
OperationBuildError(runtimeConfig).invalidField(memberName) {
rust("""format!("`{k}` cannot be used as a header name: {err}")""")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ class HttpBoundProtocolPayloadGenerator(
private val protocol: Protocol,
private val httpMessageType: HttpMessageType = HttpMessageType.REQUEST,
private val renderEventStreamBody: (RustWriter, EventStreamBodyParams) -> Unit,
private val smithyHttpType: RuntimeType = RuntimeType.smithyHttp(codegenContext.runtimeConfig),
) : ProtocolPayloadGenerator {
private val symbolProvider = codegenContext.symbolProvider
private val model = codegenContext.model
Expand All @@ -69,7 +70,7 @@ class HttpBoundProtocolPayloadGenerator(
"hyper" to CargoDependency.HyperWithStream.toType(),
"SdkBody" to RuntimeType.sdkBody(runtimeConfig),
"BuildError" to runtimeConfig.operationBuildError(),
"SmithyHttp" to RuntimeType.smithyHttp(runtimeConfig),
"SmithyHttp" to smithyHttpType,
"NoOpSigner" to smithyEventStream.resolve("frame::NoOpSigner"),
*RuntimeType.preludeScope,
)
Expand Down Expand Up @@ -264,6 +265,7 @@ class HttpBoundProtocolPayloadGenerator(
unionShape,
serializerGenerator,
payloadContentType,
smithyHttpType,
).render()

val eventStreamMarshallerGenerator =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import software.amazon.smithy.model.shapes.TimestampShape
import software.amazon.smithy.model.shapes.UnionShape
import software.amazon.smithy.model.traits.EventHeaderTrait
import software.amazon.smithy.model.traits.EventPayloadTrait
import software.amazon.smithy.rust.codegen.core.rustlang.CargoDependency
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.rustlang.conditionalBlock
Expand Down Expand Up @@ -52,6 +53,7 @@ class EventStreamUnmarshallerGenerator(
codegenContext: CodegenContext,
private val operationShape: OperationShape,
private val unionShape: UnionShape,
private val smithyHttpDependency: CargoDependency = CargoDependency.smithyHttp(codegenContext.runtimeConfig),
) {
private val model = codegenContext.model
private val builderInstantiator = codegenContext.builderInstantiator()
Expand All @@ -61,7 +63,7 @@ class EventStreamUnmarshallerGenerator(
private val unionSymbol = symbolProvider.toSymbol(unionShape)
private val errorSymbol =
if (codegenTarget == CodegenTarget.SERVER && unionShape.eventStreamErrors().isEmpty()) {
RuntimeType.smithyHttp(runtimeConfig).resolve("event_stream::MessageStreamError").toSymbol()
smithyHttpDependency.toType().resolve("event_stream::MessageStreamError").toSymbol()
} else {
symbolProvider.symbolForEventStreamError(unionShape)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,14 @@ class EventStreamErrorMarshallerGenerator(
private val unionShape: UnionShape,
private val serializerGenerator: StructuredDataSerializerGenerator,
payloadContentType: String,
private val smithyHttpType: RuntimeType = RuntimeType.smithyHttp(runtimeConfig),
) : EventStreamMarshallerGenerator(model, target, runtimeConfig, symbolProvider, unionShape, serializerGenerator, payloadContentType) {
private val smithyEventStream = RuntimeType.smithyEventStream(runtimeConfig)
private val smithyTypes = RuntimeType.smithyTypes(runtimeConfig)

private val operationErrorSymbol =
if (target == CodegenTarget.SERVER && unionShape.eventStreamErrors().isEmpty()) {
RuntimeType.smithyHttp(runtimeConfig).resolve("event_stream::MessageStreamError").toSymbol()
smithyHttpType.resolve("event_stream::MessageStreamError").toSymbol()
} else {
symbolProvider.symbolForEventStreamError(unionShape)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,16 @@ class ServerAdditionalSettings private constructor(settings: List<AdditionalSett
return this
}

fun withHttp1x(enabled: Boolean = true): Builder {
settings.add(Http1x(enabled))
return this
}

fun ignoreUnsupportedConstraints(enabled: Boolean = true): Builder {
settings.add(IgnoreUnsupportedConstraints(enabled))
return this
}

override fun build(): ServerAdditionalSettings = ServerAdditionalSettings(settings)
}

Expand All @@ -139,6 +149,20 @@ class ServerAdditionalSettings private constructor(settings: List<AdditionalSett
.build()
}

private data class Http1x(val enabled: Boolean) : AdditionalSettings() {
override fun toObjectNode(): ObjectNode =
ObjectNode.builder()
.withMember("http-1x", enabled)
.build()
}

private data class IgnoreUnsupportedConstraints(val enabled: Boolean) : AdditionalSettings() {
override fun toObjectNode(): ObjectNode =
ObjectNode.builder()
.withMember("ignoreUnsupportedConstraints", enabled)
.build()
}

companion object {
fun builder() = Builder()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,28 @@ fun String.runCommand(
workdir: Path? = null,
environment: Map<String, String> = mapOf(),
timeout: Long = 3600,
redirect: ProcessBuilder.Redirect = ProcessBuilder.Redirect.PIPE,
redirect: ProcessBuilder.Redirect? = null,
): String {
val logger = Logger.getLogger("RunCommand")
logger.fine("Invoking comment $this in `$workdir` with env $environment")
val start = System.currentTimeMillis()

// Use INHERIT (realtime output) only when debug is enabled via system property or environment variable
val isDebugMode =
System.getProperty("smithy.debug") == "true" ||
System.getenv("SMITHY_DEBUG") == "true"
val actualRedirect =
redirect ?: if (isDebugMode) {
ProcessBuilder.Redirect.INHERIT
} else {
ProcessBuilder.Redirect.PIPE
}

val parts = this.split("\\s".toRegex())
val builder =
ProcessBuilder(*parts.toTypedArray())
.redirectOutput(redirect)
.redirectError(redirect)
.redirectOutput(actualRedirect)
.redirectError(actualRedirect)
.letIf(workdir != null) {
it.directory(workdir?.toFile())
}
Expand All @@ -35,9 +47,17 @@ fun String.runCommand(
try {
val proc = builder.start()
proc.waitFor(timeout, TimeUnit.SECONDS)
val stdErr = proc.errorStream.bufferedReader().readText()
val stdOut = proc.inputStream.bufferedReader().readText()
val output = "$stdErr\n$stdOut"

// When using INHERIT, streams are not available to read from
val output =
if (actualRedirect == ProcessBuilder.Redirect.PIPE) {
val stdErr = proc.errorStream.bufferedReader().readText()
val stdOut = proc.inputStream.bufferedReader().readText()
"$stdErr\n$stdOut"
} else {
""
}

return when (proc.exitValue()) {
0 -> output
else -> throw CommandError("Command Error\n$output")
Expand Down
Loading
Loading