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

Add support for query literals (#257)

* Add support for query literals

* Add comments

* Refactor to avoid needing a Vec and skip some allocations

* Another rename, more docs
parent 3f4f44c3
Loading
Loading
Loading
Loading
+0 −5
Original line number Diff line number Diff line
@@ -355,11 +355,6 @@ class HttpProtocolTestGenerator(
        val AwsJson11 = "aws.protocoltests.json#JsonProtocol"
        val RestJson = "aws.protocoltests.restjson#RestJson"
        private val ExpectFail = setOf(
            // Query literals: https://github.com/awslabs/smithy-rs/issues/36
            FailingTest(RestJson, "RestJsonConstantQueryString", Action.Request),
            FailingTest(RestJson, "RestJsonConstantAndVariableQueryStringMissingOneValue", Action.Request),
            FailingTest(RestJson, "RestJsonConstantAndVariableQueryStringAllValues", Action.Request),

            // Misc:

            // https://github.com/awslabs/smithy-rs/issues/35
+32 −9
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@ import software.amazon.smithy.model.traits.HttpTrait
import software.amazon.smithy.model.traits.MediaTypeTrait
import software.amazon.smithy.model.traits.TimestampFormatTrait
import software.amazon.smithy.rust.codegen.rustlang.RustWriter
import software.amazon.smithy.rust.codegen.rustlang.rust
import software.amazon.smithy.rust.codegen.rustlang.rustBlock
import software.amazon.smithy.rust.codegen.smithy.RuntimeConfig
import software.amazon.smithy.rust.codegen.smithy.RuntimeType
@@ -171,17 +172,40 @@ class HttpTraitBindingGenerator(

    /**
     * When needed, generate a function to build a query string
     *
     * This function uses smithy_http::query::Query to append params to a query string:
     * ```rust
     *    fn uri_query(&self, mut output: &mut String) {
     *      let mut query = smithy_http::query::Query::new(&mut output);
     *      if let Some(inner_89) = &self.null_value {
     *          query.push_kv("Null", &smithy_http::query::fmt_string(&inner_89));
     *      }
     *      if let Some(inner_90) = &self.empty_string {
     *          query.push_kv("Empty", &smithy_http::query::fmt_string(&inner_90));
     *      }
     *    }
     *  ```
     */
    private fun uriQuery(writer: RustWriter): Boolean {
        // Don't bother generating the function if we aren't going to make a query string
        val queryParams = index.getRequestBindings(shape, HttpBinding.Location.QUERY)
        if (queryParams.isEmpty()) {
        val dynamicParams = index.getRequestBindings(shape, HttpBinding.Location.QUERY)
        val literalParams = httpTrait.uri.queryLiterals
        if (dynamicParams.isEmpty() && literalParams.isEmpty()) {
            return false
        }
        writer.rustBlock("fn uri_query(&self, output: &mut String)") {
            write("let mut params = Vec::new();")
        writer.rustBlock("fn uri_query(&self, mut output: &mut String)") {
            write("let mut query = #T::new(&mut output);", RuntimeType.QueryFormat(runtimeConfig, "Writer"))
            literalParams.forEach { (k, v) ->
                // When `v` is an empty string, no value should be set.
                // this generates a query string like `?k=v&xyz`
                if (v.isEmpty()) {
                    rust("query.push_v(${k.dq()});")
                } else {
                    rust("query.push_kv(${k.dq()}, ${v.dq()});")
                }
            }

            queryParams.forEach { param ->
            dynamicParams.forEach { param ->
                val memberShape = param.member
                val memberSymbol = symbolProvider.toSymbol(memberShape)
                val memberName = symbolProvider.toMemberName(memberShape)
@@ -189,19 +213,18 @@ class HttpTraitBindingGenerator(
                ifSet(outerTarget, memberSymbol, "&self.$memberName") { field ->
                    ListForEach(outerTarget, field) { innerField, targetId ->
                        val target = model.expectShape(targetId)
                        write(
                            "params.push((${param.locationName.dq()}, ${
                        rust(
                            "query.push_kv(${param.locationName.dq()}, &${
                            paramFmtFun(
                                target,
                                memberShape,
                                innerField
                            )
                            }));"
                            });"
                        )
                    }
                }
            }
            write("#T(params, output)", RuntimeType.QueryFormat(runtimeConfig, "write"))
        }
        return true
    }
+31 −8
Original line number Diff line number Diff line
@@ -77,14 +77,37 @@ fn url_encode(c: char, buff: &mut String) {
    }
}

pub fn write(inp: Vec<(&str, String)>, out: &mut String) {
    let mut prefix = '?';
    for (k, v) in inp {
        out.push(prefix);
        out.push_str(k);
        out.push('=');
        out.push_str(&v);
        prefix = '&';
/// Simple abstraction to enable appending params to a string as query params
///
/// ```rust
/// use smithy_http::query::Writer;
/// let mut s = String::from("www.example.com");
/// let mut q = Writer::new(&mut s);
/// q.push_kv("key", "value");
/// q.push_v("another_value");
/// assert_eq!(s, "www.example.com?key=value&another_value");
/// ```
pub struct Writer<'a> {
    out: &'a mut String,
    prefix: char,
}

impl<'a> Writer<'a> {
    pub fn new(out: &'a mut String) -> Self {
        Writer { out, prefix: '?' }
    }

    pub fn push_kv(&mut self, k: &str, v: &str) {
        self.out.push(self.prefix);
        self.out.push_str(k);
        self.out.push('=');
        self.out.push_str(v);
        self.prefix = '&';
    }

    pub fn push_v(&mut self, v: &str) {
        self.out.push(self.prefix);
        self.out.push_str(v);
    }
}