Unverified Commit cd21b6d3 authored by Russell Cohen's avatar Russell Cohen Committed by GitHub
Browse files

Make Generated Builders Vec-Aware (#267)

* Make Generated Builders Vec-Aware

This updates the generated builders & fluent client to be vec-aware. The builder method with the same name as the field will append the field to the list, creating it if it doesn't exist. A `set_xyz` method is added which retains the original behavior.

* Fix test usages of builders

* Add Map builder

* Fix unit tests

* Add impl Into clause for builder helpers

* Update another test

* One more spot that needed to be updated

* Delete dead code
parent 336670b4
Loading
Loading
Loading
Loading
+52 −8
Original line number Diff line number Diff line
@@ -15,6 +15,7 @@ import software.amazon.smithy.rust.codegen.rustlang.RustModule
import software.amazon.smithy.rust.codegen.rustlang.RustType
import software.amazon.smithy.rust.codegen.rustlang.RustWriter
import software.amazon.smithy.rust.codegen.rustlang.asType
import software.amazon.smithy.rust.codegen.rustlang.contains
import software.amazon.smithy.rust.codegen.rustlang.documentShape
import software.amazon.smithy.rust.codegen.rustlang.render
import software.amazon.smithy.rust.codegen.rustlang.rust
@@ -29,6 +30,7 @@ import software.amazon.smithy.rust.codegen.smithy.generators.LibRsSection
import software.amazon.smithy.rust.codegen.smithy.generators.ProtocolConfig
import software.amazon.smithy.rust.codegen.smithy.generators.builderSymbol
import software.amazon.smithy.rust.codegen.smithy.generators.error.errorSymbol
import software.amazon.smithy.rust.codegen.smithy.generators.setterName
import software.amazon.smithy.rust.codegen.smithy.rustType
import software.amazon.smithy.rust.codegen.util.inputShape
import software.amazon.smithy.rust.codegen.util.outputShape
@@ -148,6 +150,10 @@ class FluentClientGenerator(protocolConfig: ProtocolConfig) {
                        val memberSymbol = symbolProvider.toSymbol(member)
                        val outerType = memberSymbol.rustType()
                        val coreType = outerType.stripOuter<RustType.Option>()
                        when (coreType) {
                            is RustType.Vec -> renderVecHelper(member, memberName, coreType)
                            is RustType.HashMap -> renderMapHelper(member, memberName, coreType)
                            else -> {
                                val signature = when (coreType) {
                                    is RustType.String,
                                    is RustType.Box -> "(mut self, inp: impl Into<${coreType.render(true)}>) -> Self"
@@ -160,7 +166,45 @@ class FluentClientGenerator(protocolConfig: ProtocolConfig) {
                                }
                            }
                        }
                        // pure setter
                        rustBlock("pub fn ${member.setterName()}(mut self, inp: ${outerType.render(true)}) -> Self") {
                            rust(
                                """
                                self.inner = self.inner.${member.setterName()}(inp);
                                self
                                """
                            )
                        }
                    }
                }
            }
        }
    }

    private fun RustWriter.renderMapHelper(member: MemberShape, memberName: String, coreType: RustType.HashMap) {
        documentShape(member, model)
        val k = coreType.key
        val v = coreType.member

        rustBlock("pub fn $memberName(mut self, k: impl Into<${k.render()}>, v: impl Into<${v.render()}>) -> Self") {
            rust(
                """
                self.inner = self.inner.$memberName(k, v);
                self
            """
            )
        }
    }

    private fun RustWriter.renderVecHelper(member: MemberShape, memberName: String, coreType: RustType.Vec) {
        documentShape(member, model)
        rustBlock("pub fn $memberName(mut self, inp: impl Into<${coreType.member.render(true)}>) -> Self") {
            rust(
                """
                self.inner = self.inner.$memberName(inp);
                self
            """
            )
        }
    }
}
+4 −4
Original line number Diff line number Diff line
@@ -17,14 +17,14 @@ async fn main() -> Result<(), dynamodb::Error> {
    let new_table = client
        .create_table()
        .table_name("test-table")
        .key_schema(vec![KeySchemaElement::builder()
        .key_schema(KeySchemaElement::builder()
            .attribute_name("k")
            .key_type(KeyType::Hash)
            .build()])
        .attribute_definitions(vec![AttributeDefinition::builder()
            .build())
        .attribute_definitions(AttributeDefinition::builder()
            .attribute_name("k")
            .attribute_type(ScalarAttributeType::S)
            .build()])
            .build())
        .provisioned_throughput(
            ProvisionedThroughput::builder()
                .write_capacity_units(10)
+12 −12
Original line number Diff line number Diff line
@@ -5,8 +5,8 @@

use aws_http::AwsErrorRetryPolicy;
use aws_hyper::{SdkError, SdkSuccess};
use dynamodb::error::DescribeTableError;
use dynamodb::client::fluent_builders::Query;
use dynamodb::error::DescribeTableError;
use dynamodb::input::DescribeTableInput;
use dynamodb::model::{
    AttributeDefinition, AttributeValue, KeySchemaElement, KeyType, ProvisionedThroughput,
@@ -71,7 +71,7 @@ async fn main() {
        client
            .put_item()
            .table_name(table_name)
            .item(parse_item(value))
            .set_item(Some(parse_item(value)))
            .send()
            .await
            .expect("failed to insert item");
@@ -110,26 +110,30 @@ fn create_table(
    client
        .create_table()
        .table_name(table_name)
        .key_schema(vec![
        .key_schema(
            KeySchemaElement::builder()
                .attribute_name("year")
                .key_type(KeyType::Hash)
                .build(),
        )
        .key_schema(
            KeySchemaElement::builder()
                .attribute_name("title")
                .key_type(KeyType::Range)
                .build(),
        ])
        .attribute_definitions(vec![
        )
        .attribute_definitions(
            AttributeDefinition::builder()
                .attribute_name("year")
                .attribute_type(ScalarAttributeType::N)
                .build(),
        )
        .attribute_definitions(
            AttributeDefinition::builder()
                .attribute_name("title")
                .attribute_type(ScalarAttributeType::S)
                .build(),
        ])
        )
        .provisioned_throughput(
            ProvisionedThroughput::builder()
                .read_capacity_units(10)
@@ -159,16 +163,12 @@ fn value_to_item(value: Value) -> AttributeValue {
}

fn movies_in_year(client: &dynamodb::Client, table_name: &str, year: u16) -> Query {
    let mut expr_attrib_names = HashMap::new();
    expr_attrib_names.insert("#yr".to_string(), "year".to_string());
    let mut expr_attrib_values = HashMap::new();
    expr_attrib_values.insert(":yyyy".to_string(), AttributeValue::N(year.to_string()));
    client
        .query()
        .table_name(table_name)
        .key_condition_expression("#yr = :yyyy")
        .expression_attribute_names(expr_attrib_names)
        .expression_attribute_values(expr_attrib_values)
        .expression_attribute_names("#yr", "year")
        .expression_attribute_values(":yyyy", AttributeValue::N(year.to_string()))
}

/// Hand-written waiter to retry every second until the table is out of `Creating` state
+27 −13
Original line number Diff line number Diff line
@@ -16,41 +16,45 @@ use dynamodb::model::{
};
use dynamodb::operation::{CreateTable, DescribeTable};
use dynamodb::output::DescribeTableOutput;
use dynamodb::{Config, Region, Credentials};
use dynamodb::{Config, Credentials, Region};
use http::header::{HeaderName, AUTHORIZATION};
use http::Uri;
use serde_json::Value;
use smithy_http::operation::Operation;
use smithy_http::body::SdkBody;
use smithy_http::operation::Operation;
use smithy_http::retry::ClassifyResponse;
use smithy_types::retry::RetryKind;
use std::collections::HashMap;
use std::time::Duration;
use tokio::time::Instant;
use http::header::{AUTHORIZATION, HeaderName};

fn create_table(table_name: &str) -> create_table_input::Builder {
    CreateTable::builder()
        .table_name(table_name)
        .key_schema(vec![
        .key_schema(
            KeySchemaElement::builder()
                .attribute_name("year")
                .key_type(KeyType::Hash)
                .build(),
        )
        .key_schema(
            KeySchemaElement::builder()
                .attribute_name("title")
                .key_type(KeyType::Range)
                .build(),
        ])
        .attribute_definitions(vec![
        )
        .attribute_definitions(
            AttributeDefinition::builder()
                .attribute_name("year")
                .attribute_type(ScalarAttributeType::N)
                .build(),
        )
        .attribute_definitions(
            AttributeDefinition::builder()
                .attribute_name("title")
                .attribute_type(ScalarAttributeType::S)
                .build(),
        ])
        )
        .provisioned_throughput(
            ProvisionedThroughput::builder()
                .read_capacity_units(10)
@@ -80,7 +84,7 @@ fn add_item(table_name: impl Into<String>, item: Value) -> put_item_input::Build

    PutItemInput::builder()
        .table_name(table_name)
        .item(attribute_value)
        .set_item(Some(attribute_value))
}

fn movies_in_year(table_name: &str, year: u16) -> query_input::Builder {
@@ -91,8 +95,8 @@ fn movies_in_year(table_name: &str, year: u16) -> query_input::Builder {
    QueryInput::builder()
        .table_name(table_name)
        .key_condition_expression("#yr = :yyyy")
        .expression_attribute_names(expr_attrib_names)
        .expression_attribute_values(expr_attrib_values)
        .set_expression_attribute_names(Some(expr_attrib_names))
        .set_expression_attribute_values(Some(expr_attrib_values))
}

/// Hand-written waiter to retry every second until the table is out of `Creating` state
@@ -150,7 +154,6 @@ fn wait_for_ready_table(
    operation.with_retry_policy(waiting_policy)
}


/// Validate that time has passed with a 5ms tolerance
///
/// This is to account for some non-determinism in the Tokio timer
@@ -216,8 +219,19 @@ async fn movies_it() {
        .await
        .expect("query should succeed");
    assert_eq!(films_2013.count, 2);
    let titles: Vec<AttributeValue> = films_2013.items.unwrap().into_iter().map(|mut row|row.remove("title").expect("row should have title")).collect();
    assert_eq!(titles, vec![AttributeValue::S("Rush".to_string()), AttributeValue::S("Turn It Down, Or Else!".to_string())]);
    let titles: Vec<AttributeValue> = films_2013
        .items
        .unwrap()
        .into_iter()
        .map(|mut row| row.remove("title").expect("row should have title"))
        .collect();
    assert_eq!(
        titles,
        vec![
            AttributeValue::S("Rush".to_string()),
            AttributeValue::S("Turn It Down, Or Else!".to_string())
        ]
    );

    for req in conn.requests().iter() {
        req.assert_matches(vec![AUTHORIZATION, HeaderName::from_static("x-amz-date")]);
+1 −1
Original line number Diff line number Diff line
@@ -101,7 +101,7 @@ sealed class RustType {
    data class Opaque(override val name: kotlin.String, override val namespace: kotlin.String? = null) : RustType()
}

fun RustType.render(fullyQualified: Boolean): String {
fun RustType.render(fullyQualified: Boolean = true): String {
    val namespace = if (fullyQualified) {
        this.namespace?.let { "$it::" } ?: ""
    } else ""
Loading