mirror of https://github.com/smithy-lang/smithy-rs
Smithy async docs (#1278)
* Add lints to aws-smithy-async * Write some event stream docs, and enable lints * Codegen docs * Fix one missing doc * Compile fixes * remove accidentally added code * Update codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/SymbolVisitor.kt * Update response headers docs
This commit is contained in:
parent
e78d40f0d5
commit
6bbc776d67
|
@ -44,6 +44,45 @@
|
|||
//! let client = aws_sdk_dynamodb::Client::new(&config);
|
||||
//! # }
|
||||
//! ```
|
||||
//!
|
||||
//! Override configuration after construction of `SdkConfig`:
|
||||
//!
|
||||
//! ```no_run
|
||||
//! # use aws_types::SdkConfig;
|
||||
//! # mod aws_sdk_dynamodb {
|
||||
//! # pub mod config {
|
||||
//! # pub struct Builder;
|
||||
//! # impl Builder {
|
||||
//! # pub fn credentials_provider(
|
||||
//! # self,
|
||||
//! # credentials_provider: impl aws_types::credentials::ProvideCredentials + 'static) -> Self { self }
|
||||
//! # pub fn build(self) -> Builder { self }
|
||||
//! # }
|
||||
//! # impl From<&aws_types::SdkConfig> for Builder {
|
||||
//! # fn from(_: &aws_types::SdkConfig) -> Self {
|
||||
//! # todo!()
|
||||
//! # }
|
||||
//! # }
|
||||
//! # }
|
||||
//! # pub struct Client;
|
||||
//! # impl Client {
|
||||
//! # pub fn from_conf(conf: config::Builder) -> Self { Client }
|
||||
//! # pub fn new(config: &aws_types::SdkConfig) -> Self { Client }
|
||||
//! # }
|
||||
//! # }
|
||||
//! # async fn docs() {
|
||||
//! # use aws_config::meta::region::RegionProviderChain;
|
||||
//! # fn custom_provider(base: &SdkConfig) -> impl aws_types::credentials::ProvideCredentials {
|
||||
//! # base.credentials_provider().unwrap().clone()
|
||||
//! # }
|
||||
//! let sdk_config = aws_config::load_from_env().await;
|
||||
//! let custom_credentials_provider = custom_provider(&sdk_config);
|
||||
//! let dynamo_config = aws_sdk_dynamodb::config::Builder::from(&sdk_config)
|
||||
//! .credentials_provider(custom_credentials_provider)
|
||||
//! .build();
|
||||
//! let client = aws_sdk_dynamodb::Client::from_conf(dynamo_config);
|
||||
//! # }
|
||||
//! ```
|
||||
|
||||
#[allow(dead_code)]
|
||||
const PKG_VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
|
|
|
@ -225,11 +225,11 @@ private class AwsFluentClientDocs(codegenContext: CodegenContext) : FluentClient
|
|||
/// ```rust,no_run
|
||||
/// use #{aws_config}::RetryConfig;
|
||||
/// ## async fn docs() {
|
||||
/// let shared_config = #{aws_config}::load_from_env().await;
|
||||
/// let config = $crateName::config::Builder::from(&shared_config)
|
||||
/// .retry_config(RetryConfig::disabled())
|
||||
/// .build();
|
||||
/// let client = $crateName::Client::from_conf(config);
|
||||
/// let shared_config = #{aws_config}::load_from_env().await;
|
||||
/// let config = $crateName::config::Builder::from(&shared_config)
|
||||
/// .retry_config(RetryConfig::disabled())
|
||||
/// .build();
|
||||
/// let client = $crateName::Client::from_conf(config);
|
||||
/// ## }
|
||||
""",
|
||||
*codegenScope
|
||||
|
|
|
@ -9,6 +9,9 @@ import software.amazon.smithy.model.Model
|
|||
import software.amazon.smithy.model.shapes.ServiceShape
|
||||
import software.amazon.smithy.model.shapes.ShapeId
|
||||
|
||||
/**
|
||||
* Code generation mode: In some situations, codegen has different behavior for client vs. server (eg. required fields)
|
||||
*/
|
||||
sealed class CodegenMode {
|
||||
object Server : CodegenMode()
|
||||
object Client : CodegenMode()
|
||||
|
|
|
@ -9,6 +9,13 @@ import software.amazon.smithy.model.node.Node
|
|||
import software.amazon.smithy.model.shapes.ShapeId
|
||||
import software.amazon.smithy.model.traits.Trait
|
||||
|
||||
/**
|
||||
* Trait indicating that this shape should be represented with `Box<T>` when converted into Rust
|
||||
*
|
||||
* This is used to handle recursive shapes. See RecursiveShapeBoxer.
|
||||
*
|
||||
* This trait is synthetic, applied during code generation, and never used in actual models.
|
||||
*/
|
||||
class RustBoxTrait : Trait {
|
||||
val ID = ShapeId.from("software.amazon.smithy.rust.codegen.smithy.rust.synthetic#box")
|
||||
override fun toNode(): Node = Node.objectNode()
|
||||
|
|
|
@ -48,6 +48,14 @@ class StreamingShapeSymbolProvider(private val base: RustSymbolProvider, private
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* SymbolProvider to drop the clone and PartialEq bounds in streaming shapes
|
||||
*
|
||||
* Streaming shapes cannot be cloned and equality cannot be checked without reading the body. Because of this, these shapes
|
||||
* do not implement `Clone` or `PartialEq`.
|
||||
*
|
||||
* Note that since streaming members can only be used on the root shape, this can only impact input and output shapes.
|
||||
*/
|
||||
class StreamingShapeMetadataProvider(private val base: RustSymbolProvider, private val model: Model) : SymbolMetadataProvider(base) {
|
||||
override fun memberMeta(memberShape: MemberShape): RustMetadata {
|
||||
return base.toSymbol(memberShape).expectRustMetadata()
|
||||
|
|
|
@ -48,6 +48,7 @@ import software.amazon.smithy.rust.codegen.util.toPascalCase
|
|||
import software.amazon.smithy.rust.codegen.util.toSnakeCase
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
/** Map from Smithy Shapes to Rust Types */
|
||||
val SimpleShapes: Map<KClass<out Shape>, RustType> = mapOf(
|
||||
BooleanShape::class to RustType.Bool,
|
||||
FloatShape::class to RustType.Float(32),
|
||||
|
@ -74,6 +75,11 @@ val DefaultConfig =
|
|||
codegenConfig = CodegenConfig()
|
||||
)
|
||||
|
||||
/**
|
||||
* Container type for the file a symbol should be written to
|
||||
*
|
||||
* Downstream code uses symbol location to determine which file to use acquiring a writer
|
||||
*/
|
||||
data class SymbolLocation(val namespace: String) {
|
||||
val filename = "$namespace.rs"
|
||||
}
|
||||
|
@ -85,6 +91,11 @@ val Serializers = SymbolLocation("serializer")
|
|||
val Inputs = SymbolLocation("input")
|
||||
val Outputs = SymbolLocation("output")
|
||||
|
||||
/**
|
||||
* Make the Rust type of a symbol optional (hold `Option<T>`)
|
||||
*
|
||||
* This is idempotent and will have no change if the type is already optional.
|
||||
*/
|
||||
fun Symbol.makeOptional(): Symbol {
|
||||
return if (isOptional()) {
|
||||
this
|
||||
|
@ -98,6 +109,7 @@ fun Symbol.makeOptional(): Symbol {
|
|||
}
|
||||
}
|
||||
|
||||
/** Map the RustType of a symbol with [f] */
|
||||
fun Symbol.mapRustType(f: (RustType) -> RustType): Symbol {
|
||||
val newType = f(this.rustType())
|
||||
return Symbol.builder().rustType(newType)
|
||||
|
@ -106,17 +118,7 @@ fun Symbol.mapRustType(f: (RustType) -> RustType): Symbol {
|
|||
.build()
|
||||
}
|
||||
|
||||
fun Symbol.makeRustBoxed(): Symbol {
|
||||
val symbol = this
|
||||
val rustType = RustType.Box(symbol.rustType())
|
||||
return with(Symbol.builder()) {
|
||||
rustType(rustType)
|
||||
addReference(symbol)
|
||||
name(rustType.name)
|
||||
build()
|
||||
}
|
||||
}
|
||||
|
||||
/** Set the symbolLocation for this symbol builder */
|
||||
fun Symbol.Builder.locatedIn(symbolLocation: SymbolLocation): Symbol.Builder {
|
||||
val currentRustType = this.build().rustType()
|
||||
check(currentRustType is RustType.Opaque) { "Only Opaque can have their namespace updated" }
|
||||
|
@ -126,8 +128,20 @@ fun Symbol.Builder.locatedIn(symbolLocation: SymbolLocation): Symbol.Builder {
|
|||
.rustType(newRustType)
|
||||
}
|
||||
|
||||
/**
|
||||
* Track both the past and current name of a symbol
|
||||
*
|
||||
* When a symbol name conflicts with another name, we need to rename it. This tracks both names enabling us to generate helpful
|
||||
* docs that cover both cases.
|
||||
*
|
||||
* Note that this is only used for enum shapes an enum variant does not have it's own symbol. For structures, the [Symbol.renamedFrom]
|
||||
* field will be set.
|
||||
*/
|
||||
data class MaybeRenamed(val name: String, val renamedFrom: String?)
|
||||
|
||||
/**
|
||||
* SymbolProvider interface that carries both the inner configuration and a function to produce an enum variant name.
|
||||
*/
|
||||
interface RustSymbolProvider : SymbolProvider {
|
||||
fun config(): SymbolVisitorConfig
|
||||
fun toEnumVariantName(definition: EnumDefinition): MaybeRenamed?
|
||||
|
@ -142,6 +156,13 @@ fun SymbolProvider.wrapOptional(member: MemberShape, value: String): String = va
|
|||
*/
|
||||
fun SymbolProvider.toOptional(member: MemberShape, value: String): String = value.letIf(!toSymbol(member).isOptional()) { "Some($value)" }
|
||||
|
||||
/**
|
||||
* Base converter from `Shape` to `Symbol`. Shapes are the direct contents of the `Smithy` model. `Symbols` carry information
|
||||
* about Rust types, namespaces, dependencies, metadata as well as other information required to render a symbol.
|
||||
*
|
||||
* This is composed with other symbol visitors to handle behavior like Streaming shapes and determining the correct
|
||||
* derives for a given shape.
|
||||
*/
|
||||
class SymbolVisitor(
|
||||
private val model: Model,
|
||||
private val serviceShape: ServiceShape?,
|
||||
|
@ -155,6 +176,10 @@ class SymbolVisitor(
|
|||
return shape.accept(this)
|
||||
}
|
||||
|
||||
/**
|
||||
* Services can rename their contained shapes. See https://awslabs.github.io/smithy/1.0/spec/core/model.html#service
|
||||
* specifically, `rename`
|
||||
*/
|
||||
private fun Shape.contextName(): String {
|
||||
return if (serviceShape != null) {
|
||||
id.getName(serviceShape)
|
||||
|
@ -163,6 +188,12 @@ class SymbolVisitor(
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the name of a given `enum` variant. Note that this refers to `enum` in the Smithy context
|
||||
* where enum is a trait that can be applied to [StringShape] and not in the Rust context of an algebraic data type.
|
||||
*
|
||||
* Because enum variants are not member shape, a separate handler is required.
|
||||
*/
|
||||
override fun toEnumVariantName(definition: EnumDefinition): MaybeRenamed? {
|
||||
val baseName = definition.name.orNull()?.toPascalCase() ?: return null
|
||||
return MaybeRenamed(baseName, null)
|
||||
|
@ -187,6 +218,9 @@ class SymbolVisitor(
|
|||
symbol
|
||||
}
|
||||
|
||||
/**
|
||||
* Produce `Box<T>` when the shape has the `RustBoxTrait`
|
||||
*/
|
||||
private fun handleRustBoxing(symbol: Symbol, shape: Shape): Symbol {
|
||||
return if (shape.hasTrait<RustBoxTrait>()) {
|
||||
val rustType = RustType.Box(symbol.rustType())
|
||||
|
@ -336,6 +370,9 @@ fun Symbol.Builder.setDefault(default: Default): Symbol.Builder {
|
|||
return this.putProperty(SYMBOL_DEFAULT, default)
|
||||
}
|
||||
|
||||
/**
|
||||
* Type representing the default value for a given type. (eg. for Strings, this is `""`)
|
||||
*/
|
||||
sealed class Default {
|
||||
/**
|
||||
* This symbol has no default value. If the symbol is not optional, this will be an error during builder construction
|
||||
|
@ -361,12 +398,15 @@ fun Symbol.isOptional(): Boolean = when (this.rustType()) {
|
|||
else -> false
|
||||
}
|
||||
|
||||
fun Symbol.isBoxed(): Boolean = rustType().stripOuter<RustType.Option>() is RustType.Box
|
||||
fun Symbol.isRustBoxed(): Boolean = rustType().stripOuter<RustType.Option>() is RustType.Box
|
||||
|
||||
// Symbols should _always_ be created with a Rust type & shape attached
|
||||
fun Symbol.rustType(): RustType = this.getProperty(RUST_TYPE_KEY, RustType::class.java).get()
|
||||
fun Symbol.shape(): Shape = this.expectProperty(SHAPE_KEY, Shape::class.java)
|
||||
|
||||
/**
|
||||
* Utility function similar to `let` that conditionally applies [f] only if [cond] is true.
|
||||
*/
|
||||
fun <T> T.letIf(cond: Boolean, f: (T) -> T): T {
|
||||
return if (cond) {
|
||||
f(this)
|
||||
|
|
|
@ -19,6 +19,9 @@ import software.amazon.smithy.rust.codegen.util.hasTrait
|
|||
import software.amazon.smithy.rust.codegen.util.inputShape
|
||||
import software.amazon.smithy.rust.codegen.util.outputShape
|
||||
|
||||
/**
|
||||
* KnowledgeIndex to determine the name for a given shape based on the XmlName trait and the shape's id.
|
||||
*/
|
||||
class XmlNameIndex(private val model: Model) : KnowledgeIndex {
|
||||
companion object {
|
||||
fun of(model: Model): XmlNameIndex {
|
||||
|
|
|
@ -41,7 +41,7 @@ import software.amazon.smithy.rust.codegen.smithy.generators.UnionGenerator
|
|||
import software.amazon.smithy.rust.codegen.smithy.generators.builderSymbol
|
||||
import software.amazon.smithy.rust.codegen.smithy.generators.renderUnknownVariant
|
||||
import software.amazon.smithy.rust.codegen.smithy.generators.setterName
|
||||
import software.amazon.smithy.rust.codegen.smithy.isBoxed
|
||||
import software.amazon.smithy.rust.codegen.smithy.isRustBoxed
|
||||
import software.amazon.smithy.rust.codegen.smithy.protocols.HttpBindingResolver
|
||||
import software.amazon.smithy.rust.codegen.smithy.protocols.HttpLocation
|
||||
import software.amazon.smithy.rust.codegen.smithy.protocols.deserializeFunctionName
|
||||
|
@ -230,7 +230,7 @@ class JsonParserGenerator(
|
|||
else -> PANIC("unexpected shape: $target")
|
||||
}
|
||||
val symbol = symbolProvider.toSymbol(memberShape)
|
||||
if (symbol.isBoxed()) {
|
||||
if (symbol.isRustBoxed()) {
|
||||
rust(".map(Box::new)")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -44,8 +44,8 @@ import software.amazon.smithy.rust.codegen.smithy.generators.UnionGenerator
|
|||
import software.amazon.smithy.rust.codegen.smithy.generators.builderSymbol
|
||||
import software.amazon.smithy.rust.codegen.smithy.generators.renderUnknownVariant
|
||||
import software.amazon.smithy.rust.codegen.smithy.generators.setterName
|
||||
import software.amazon.smithy.rust.codegen.smithy.isBoxed
|
||||
import software.amazon.smithy.rust.codegen.smithy.isOptional
|
||||
import software.amazon.smithy.rust.codegen.smithy.isRustBoxed
|
||||
import software.amazon.smithy.rust.codegen.smithy.protocols.XmlMemberIndex
|
||||
import software.amazon.smithy.rust.codegen.smithy.protocols.XmlNameIndex
|
||||
import software.amazon.smithy.rust.codegen.smithy.protocols.deserializeFunctionName
|
||||
|
@ -356,7 +356,7 @@ class XmlBindingTraitParserGenerator(
|
|||
val target = model.expectShape(memberShape.target)
|
||||
val symbol = symbolProvider.toSymbol(memberShape)
|
||||
conditionalBlock("Some(", ")", forceOptional || symbol.isOptional()) {
|
||||
conditionalBlock("Box::new(", ")", symbol.isBoxed()) {
|
||||
conditionalBlock("Box::new(", ")", symbol.isRustBoxed()) {
|
||||
when (target) {
|
||||
is StringShape, is BooleanShape, is NumberShape, is TimestampShape, is BlobShape ->
|
||||
parsePrimitiveInner(memberShape) {
|
||||
|
|
|
@ -11,6 +11,9 @@ import software.amazon.smithy.model.traits.AnnotationTrait
|
|||
|
||||
/**
|
||||
* Indicates that a shape is a synthetic input (see `OperationNormalizer.kt`)
|
||||
*
|
||||
* All operations are normalized to have an input, even when they are defined without on. This is done for backwards compatibility
|
||||
* and to produce a consistent API.
|
||||
*/
|
||||
class SyntheticInputTrait(
|
||||
val operation: ShapeId,
|
||||
|
|
|
@ -11,6 +11,9 @@ import software.amazon.smithy.model.traits.AnnotationTrait
|
|||
|
||||
/**
|
||||
* Indicates that a shape is a synthetic input (see `OperationNormalizer.kt`)
|
||||
*
|
||||
* All operations are normalized to have an input, even when they are defined without on. This is done for backwards compatibility
|
||||
* and to produce a consistent API.
|
||||
*/
|
||||
class SyntheticOutputTrait constructor(val operation: ShapeId, val originalId: ShapeId?) :
|
||||
AnnotationTrait(ID, Node.objectNode()) {
|
||||
|
|
|
@ -17,8 +17,24 @@ import java.util.logging.Logger
|
|||
|
||||
fun StructureShape.errorMessageMember(): MemberShape? = this.getMember("message").or { this.getMember("Message") }.orNull()
|
||||
|
||||
/**
|
||||
* Ensure that all errors have error messages
|
||||
*
|
||||
* Not all errors are modeled with an error message field. However, in many cases, the server can still send an error.
|
||||
* If an error, specifically, a structure shape with the error trait does not have a member `message` or `Message`,
|
||||
* this transformer will add a `message` member targetting a string.
|
||||
*
|
||||
* This ensures that we always generate a modeled error message field enabling end users to easily extract the error
|
||||
* message when present.
|
||||
*
|
||||
* Currently, this is run on all models, however, we may restrict this to AWS SDK code generation in the future.
|
||||
*/
|
||||
object AddErrorMessage {
|
||||
private val logger = Logger.getLogger("AddErrorMessage")
|
||||
|
||||
/**
|
||||
* Ensure that all errors have error messages
|
||||
*/
|
||||
fun transform(model: Model): Model {
|
||||
return ModelTransformer.create().mapShapes(model) { shape ->
|
||||
val addMessageField = shape.hasTrait<ErrorTrait>() && shape is StructureShape && shape.errorMessageMember() == null
|
||||
|
|
|
@ -37,7 +37,7 @@ import java.nio.file.Files.createTempDirectory
|
|||
import java.nio.file.Path
|
||||
|
||||
/**
|
||||
* Waiting for Kotlin to stabilize their temp directory stuff
|
||||
* Waiting for Kotlin to stabilize their temp directory functionality
|
||||
*/
|
||||
private fun tempDir(directory: File? = null): File {
|
||||
return if (directory != null) {
|
||||
|
@ -167,6 +167,11 @@ fun RustWriter.unitTest(
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WriterDelegator used for test purposes
|
||||
*
|
||||
* This exposes both the base directory and a list of [generatedFiles] for test purposes
|
||||
*/
|
||||
class TestWriterDelegator(private val fileManifest: FileManifest, symbolProvider: RustSymbolProvider) :
|
||||
RustCrate(fileManifest, symbolProvider, DefaultPublicModules) {
|
||||
val baseDir: Path = fileManifest.baseDir
|
||||
|
|
|
@ -13,7 +13,11 @@ import software.amazon.smithy.rust.codegen.smithy.generators.config.ConfigCustom
|
|||
import software.amazon.smithy.rust.codegen.smithy.generators.config.ServiceConfig
|
||||
import software.amazon.smithy.rust.codegen.smithy.generators.config.ServiceConfigGenerator
|
||||
|
||||
fun stubCustomization(name: String): ConfigCustomization {
|
||||
/**
|
||||
* Test helper to produce a valid config customization to test that a [ConfigCustomization] can be used in conjunction
|
||||
* with other [ConfigCustomization]s.
|
||||
*/
|
||||
fun stubConfigCustomization(name: String): ConfigCustomization {
|
||||
return object : ConfigCustomization() {
|
||||
override fun section(section: ServiceConfig): Writable = writable {
|
||||
when (section) {
|
||||
|
@ -56,7 +60,7 @@ fun validateConfigCustomizations(
|
|||
}
|
||||
|
||||
fun stubConfigProject(customization: ConfigCustomization, project: TestWriterDelegator): TestWriterDelegator {
|
||||
val customizations = listOf(stubCustomization("a")) + customization + stubCustomization("b")
|
||||
val customizations = listOf(stubConfigCustomization("a")) + customization + stubConfigCustomization("b")
|
||||
val generator = ServiceConfigGenerator(customizations = customizations.toList())
|
||||
project.withModule(RustModule.Config) {
|
||||
generator.render(it)
|
||||
|
|
|
@ -2,6 +2,9 @@
|
|||
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0.
|
||||
*/
|
||||
|
||||
//! Utility to drive a stream with an async function and a channel.
|
||||
|
||||
use crate::future::rendezvous;
|
||||
use futures_util::StreamExt;
|
||||
use pin_project_lite::pin_project;
|
||||
|
@ -45,6 +48,9 @@ pin_project! {
|
|||
}
|
||||
|
||||
impl<Item, F> FnStream<Item, F> {
|
||||
/// Creates a new function based stream driven by `generator`.
|
||||
///
|
||||
/// For examples, see the documentation for [`FnStream`]
|
||||
pub fn new<T>(generator: T) -> Self
|
||||
where
|
||||
T: FnOnce(rendezvous::Sender<Item>) -> F,
|
||||
|
@ -80,6 +86,7 @@ where
|
|||
/// When flattening paginated results, it's most convenient to produce an iterator where the `Result`
|
||||
/// is present in each item. This provides `items()` which can wrap an stream of `Result<Page, Err>`
|
||||
/// and produce a stream of `Result<Item, Err>`.
|
||||
#[derive(Debug)]
|
||||
pub struct TryFlatMap<I>(I);
|
||||
|
||||
impl<I> TryFlatMap<I> {
|
||||
|
@ -88,6 +95,7 @@ impl<I> TryFlatMap<I> {
|
|||
Self(i)
|
||||
}
|
||||
|
||||
/// Produce a new [`Stream`] by mapping this stream with `map` then flattening the result
|
||||
pub fn flat_map<M, Item, Iter, Page, Err>(self, map: M) -> impl Stream<Item = Result<Item, Err>>
|
||||
where
|
||||
I: Stream<Item = Result<Page, Err>>,
|
||||
|
|
|
@ -11,10 +11,11 @@ use std::task::{Context, Poll};
|
|||
|
||||
/// Future that never completes.
|
||||
#[non_exhaustive]
|
||||
#[derive(Default)]
|
||||
#[derive(Default, Debug)]
|
||||
pub struct Never;
|
||||
|
||||
impl Never {
|
||||
/// Create a new `Never` future that never resolves
|
||||
pub fn new() -> Never {
|
||||
Default::default()
|
||||
}
|
||||
|
|
|
@ -55,8 +55,10 @@ use std::task::{Context, Poll};
|
|||
|
||||
use pin_project_lite::pin_project;
|
||||
|
||||
/// Boxed future type alias
|
||||
pub type BoxFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
|
||||
|
||||
#[derive(Debug)]
|
||||
/// Zero sized type for using NowOrLater when no future variant exists.
|
||||
pub enum OnlyReady {}
|
||||
|
||||
|
@ -107,12 +109,14 @@ where
|
|||
}
|
||||
|
||||
impl<T, F> NowOrLater<T, F> {
|
||||
/// Creates a future that will resolve when `future` resolves
|
||||
pub fn new(future: F) -> Self {
|
||||
Self {
|
||||
inner: Inner::Later { future },
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a future that immediately resolves to `value`
|
||||
pub fn ready(value: T) -> NowOrLater<T, F> {
|
||||
let value = Some(value);
|
||||
Self {
|
||||
|
|
|
@ -38,6 +38,7 @@ pub fn channel<T>() -> (Sender<T>, Receiver<T>) {
|
|||
)
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
/// Sender-half of a channel
|
||||
pub struct Sender<T> {
|
||||
semaphore: Arc<Semaphore>,
|
||||
|
@ -64,6 +65,7 @@ impl<T> Sender<T> {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
/// Receiver half of the rendezvous channel
|
||||
pub struct Receiver<T> {
|
||||
semaphore: Arc<Semaphore>,
|
||||
|
|
|
@ -42,6 +42,7 @@ use std::future::Future;
|
|||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
/// Error returned when [`Timeout`] times out
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||
pub struct TimedOutError;
|
||||
|
||||
|
@ -54,6 +55,7 @@ impl fmt::Display for TimedOutError {
|
|||
}
|
||||
|
||||
pin_project! {
|
||||
/// Timeout Future
|
||||
#[non_exhaustive]
|
||||
#[must_use = "futures do nothing unless you `.await` or poll them"]
|
||||
#[derive(Debug)]
|
||||
|
@ -66,6 +68,9 @@ pin_project! {
|
|||
}
|
||||
|
||||
impl<T, S> Timeout<T, S> {
|
||||
/// Create a new future that will race `value` and `sleep`.
|
||||
///
|
||||
/// If `sleep` resolves first, a timeout error is returned. Otherwise, the value is returned.
|
||||
pub fn new(value: T, sleep: S) -> Timeout<T, S> {
|
||||
Timeout { value, sleep }
|
||||
}
|
||||
|
|
|
@ -3,6 +3,13 @@
|
|||
* SPDX-License-Identifier: Apache-2.0.
|
||||
*/
|
||||
|
||||
#![warn(
|
||||
missing_debug_implementations,
|
||||
missing_docs,
|
||||
rustdoc::all,
|
||||
rust_2018_idioms
|
||||
)]
|
||||
|
||||
//! Future utilities and runtime-agnostic abstractions for smithy-rs.
|
||||
//!
|
||||
//! Async runtime specific code is abstracted behind async traits, and implementations are
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
//! Provides an [`AsyncSleep`] trait that returns a future that sleeps for a given duration,
|
||||
//! and implementations of `AsyncSleep` for different async runtimes.
|
||||
|
||||
use std::fmt::{Debug, Formatter};
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
use std::sync::Arc;
|
||||
|
@ -45,6 +46,7 @@ pub fn default_async_sleep() -> Option<Arc<dyn AsyncSleep>> {
|
|||
}
|
||||
|
||||
#[cfg(not(feature = "rt-tokio"))]
|
||||
/// Returns a default sleep implementation based on the features enabled
|
||||
pub fn default_async_sleep() -> Option<Arc<dyn AsyncSleep>> {
|
||||
None
|
||||
}
|
||||
|
@ -53,7 +55,16 @@ pub fn default_async_sleep() -> Option<Arc<dyn AsyncSleep>> {
|
|||
#[non_exhaustive]
|
||||
pub struct Sleep(Pin<Box<dyn Future<Output = ()> + Send + 'static>>);
|
||||
|
||||
impl Debug for Sleep {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "Sleep")
|
||||
}
|
||||
}
|
||||
|
||||
impl Sleep {
|
||||
/// Create a new [`Sleep`] future
|
||||
///
|
||||
/// The provided future will be Boxed.
|
||||
pub fn new(future: impl Future<Output = ()> + Send + 'static) -> Sleep {
|
||||
Sleep(Box::pin(future))
|
||||
}
|
||||
|
@ -75,6 +86,7 @@ pub struct TokioSleep;
|
|||
|
||||
#[cfg(feature = "rt-tokio")]
|
||||
impl TokioSleep {
|
||||
/// Create a new [`AsyncSleep`] implementation using the Tokio hashed wheel sleep implementation
|
||||
pub fn new() -> TokioSleep {
|
||||
Default::default()
|
||||
}
|
||||
|
|
|
@ -3,6 +3,13 @@
|
|||
* SPDX-License-Identifier: Apache-2.0.
|
||||
*/
|
||||
|
||||
#![warn(
|
||||
missing_debug_implementations,
|
||||
/*missing_docs,
|
||||
rustdoc::all,*/
|
||||
rust_2018_idioms
|
||||
)]
|
||||
|
||||
//! AWS Event Stream frame serialization/deserialization implementation.
|
||||
|
||||
mod buf;
|
||||
|
|
|
@ -10,6 +10,9 @@ use aws_smithy_types::{Blob, DateTime};
|
|||
|
||||
macro_rules! expect_shape_fn {
|
||||
(fn $fn_name:ident[$val_typ:ident] -> $result_typ:ident { $val_name:ident -> $val_expr:expr }) => {
|
||||
#[doc = "Expects that `header` is a `"]
|
||||
#[doc = stringify!($result_typ)]
|
||||
#[doc = "`."]
|
||||
pub fn $fn_name(header: &Header) -> Result<$result_typ, Error> {
|
||||
match header.value() {
|
||||
HeaderValue::$val_typ($val_name) => Ok($val_expr),
|
||||
|
@ -32,14 +35,32 @@ expect_shape_fn!(fn expect_byte_array[ByteArray] -> Blob { bytes -> Blob::new(by
|
|||
expect_shape_fn!(fn expect_string[String] -> String { value -> value.as_str().into() });
|
||||
expect_shape_fn!(fn expect_timestamp[Timestamp] -> DateTime { value -> *value });
|
||||
|
||||
/// Structured header data from a [`Message`]
|
||||
#[derive(Debug)]
|
||||
pub struct ResponseHeaders<'a> {
|
||||
/// Content Type of the message
|
||||
///
|
||||
/// This can be a number of things depending on the protocol. For example, if the protocol is
|
||||
/// AwsJson1, then this could be `application/json`, or `application/xml` for RestXml.
|
||||
///
|
||||
/// It will be `application/octet-stream` if there is a Blob payload shape, and `text/plain` if
|
||||
/// there is a String payload shape.
|
||||
pub content_type: Option<&'a StrBytes>,
|
||||
|
||||
/// Message Type field
|
||||
///
|
||||
/// This field is used to distinguish between events where the value is `event` and errors where
|
||||
/// the value is `exception`
|
||||
pub message_type: &'a StrBytes,
|
||||
|
||||
/// Smithy Type field
|
||||
///
|
||||
/// This field is used to determine which of the possible union variants that this message represents
|
||||
pub smithy_type: &'a StrBytes,
|
||||
}
|
||||
|
||||
impl<'a> ResponseHeaders<'a> {
|
||||
/// Content-Type for this message
|
||||
pub fn content_type(&self) -> Option<&str> {
|
||||
self.content_type.map(|ct| ct.as_str())
|
||||
}
|
||||
|
@ -63,7 +84,11 @@ fn expect_header_str_value<'a>(
|
|||
}
|
||||
}
|
||||
|
||||
pub fn parse_response_headers(message: &Message) -> Result<ResponseHeaders, Error> {
|
||||
/// Parse headers from [`Message`]
|
||||
///
|
||||
/// `:content-type`, `:message-type`, `:event-type`, and `:exception-type` headers will be parsed.
|
||||
/// If any headers are invalid or missing, an error will be returned.
|
||||
pub fn parse_response_headers(message: &Message) -> Result<ResponseHeaders<'_>, Error> {
|
||||
let (mut content_type, mut message_type, mut event_type, mut exception_type) =
|
||||
(None, None, None, None);
|
||||
for header in message.headers() {
|
||||
|
|
Loading…
Reference in New Issue