rusoto_codegen 0.8.0

Code generation library for Rusoto.
Documentation
use std::io::Write;
use inflector::Inflector;
use botocore::{Operation, Service, Shape, ShapeType, Member};

use super::xml_payload_parser;
use super::{IoResult, FileWriter, GenerateProtocol, error_type_name, capitalize_first,
            generate_field_name};

pub struct QueryGenerator;

impl GenerateProtocol for QueryGenerator {
    fn generate_methods(&self, writer: &mut FileWriter, service: &Service) -> IoResult {
        for (operation_name, operation) in service.operations.iter() {
            writeln!(writer,
                     "
                {documentation}
                {method_signature} {{
                    let mut request = SignedRequest::new(\"{http_method}\", \"{endpoint_prefix}\", self.region, \"{request_uri}\");
                    let mut params = Params::new();

                    params.put(\"Action\", \"{operation_name}\");
                    params.put(\"Version\", \"{api_version}\");
                    {serialize_input}
                    request.set_params(params);

                    request.sign(&try!(self.credentials_provider.credentials()));
                    let response = try!(self.dispatcher.dispatch(&request));
                    match response.status {{
                        StatusCode::Ok => {{
                            {parse_payload}
                            Ok(result)
                        }}
                        _ => {{
                            Err({error_type}::from_body(String::from_utf8_lossy(&response.body).as_ref()))
                        }}
                    }}
                }}
                ",
                     api_version = &service.metadata.api_version,
                     documentation = generate_documentation(operation),
                     error_type = error_type_name(operation_name),
                     http_method = &operation.http.method,
                     endpoint_prefix = &service.metadata.endpoint_prefix,
                     parse_payload =
                         xml_payload_parser::generate_response_parser(service, operation, false),
                     method_signature = generate_method_signature(operation_name, operation),
                     operation_name = &operation.name,
                     request_uri = &operation.http.request_uri,
                     serialize_input = generate_method_input_serialization(operation))?;
        }
        Ok(())
    }

    fn generate_prelude(&self, writer: &mut FileWriter, _service: &Service) -> IoResult {
        writeln!(writer,
                 "use std::str::FromStr;
            use xml::EventReader;
            use xml::reader::ParserConfig;
            use param::{{Params, ServiceParams}};
            use signature::SignedRequest;
            use xml::reader::XmlEvent;
            use xmlutil::{{Next, Peek, XmlParseError, XmlResponse}};
            use xmlutil::{{characters, end_element, start_element, skip_tree, peek_at_name}};
            use xmlerror::*;

            enum DeserializerNext {{
                Close,
                Skip,
                Element(String),
        }}")
    }

    fn generate_struct_attributes(&self,
                                  _serialized: bool,
                                  deserialized: bool)
                                  -> String {
        xml_payload_parser::generate_struct_attributes(deserialized)
    }

    fn generate_serializer(&self, name: &str, shape: &Shape, service: &Service) -> Option<String> {
        if shape.is_primitive() {
            return None;
        }

        Some(format!("
            /// Serialize `{name}` contents to a `SignedRequest`.
            struct {name}Serializer;
            impl {name}Serializer {{
                {serializer_signature} {{
                    {serializer_body}
                }}
            }}
            ",
                     name = name,
                     serializer_signature = generate_serializer_signature(name, shape),
                     serializer_body = generate_serializer_body(service, shape)))
    }

    fn generate_deserializer(&self,
                             name: &str,
                             shape: &Shape,
                             service: &Service)
                             -> Option<String> {
        Some(xml_payload_parser::generate_deserializer(name, shape, service))
    }

    fn timestamp_type(&self) -> &'static str {
        "String"
    }
}

pub fn generate_method_input_serialization(operation: &Operation) -> String {
    if operation.input.is_some() {
        format!(
            "{input_type}Serializer::serialize(&mut params, \"\", &input);",
            input_type = operation.input.as_ref().unwrap().shape,
        )
    } else {
        String::new()
    }
}

fn generate_serializer_body(service: &Service, shape: &Shape) -> String {
    match shape.shape_type {
        ShapeType::List => generate_list_serializer(service, shape),
        ShapeType::Map => generate_map_serializer(service, shape),
        ShapeType::Structure => generate_struct_serializer(service, shape),
        _ => "".to_owned(),
    }
}

fn generate_serializer_signature(name: &str, shape: &Shape) -> String {
    if shape.shape_type == ShapeType::Structure && shape.members.as_ref().unwrap().is_empty() {
        format!("fn serialize(_params: &mut Params, name: &str, _obj: &{})",
                name)
    } else {
        format!("fn serialize(params: &mut Params, name: &str, obj: &{})",
                name)
    }
}

fn generate_list_serializer(service: &Service, shape: &Shape) -> String {

    let member_shape = service.shape_for_member(shape.member.as_ref().unwrap()).unwrap();
    let primitive = member_shape.is_primitive();

    let mut parts = Vec::new();

    let lmf = list_member_format(service);

    parts.push(format!("for (index, obj) in obj.iter().enumerate() {{
                    let key = format!(\"{list_member_format}\", name, index+1);",
                       list_member_format = lmf));

    if primitive {
        parts.push(format!("params.put(&key, {});",
                           serialize_primitive_expression(&member_shape.shape_type, "obj")));
    } else {
        parts.push(format!("{}Serializer::serialize(params, &key, obj);",
                           shape.member_type()));
    }

    parts.push("}".to_owned());
    parts.join("\n")
}

fn list_member_format(service: &Service) -> String {
    match &service.metadata.protocol[..] {
        "ec2" => "{}.{}".to_owned(),
        "query" => "{}.member.{}".to_owned(),
        _ => panic!("Unsupported protocol"),
    }
}

fn generate_map_serializer(service: &Service, shape: &Shape) -> String {
    let mut parts = Vec::new();

    // the key is always a string type
    parts.push(format!("for (index, (key, value)) in obj.iter().enumerate() {{
            let prefix = format!(\"{{}}.{{}}\", name, index+1);
            params.put(&format!(\"{{}}.{{}}\", prefix, \"{key_name}\"), &key);",
            key_name = key_name(service, shape),
        ));

    let value_shape = service.shape_for_value(shape.value.as_ref().unwrap()).unwrap();
    let primitive_value = value_shape.is_primitive();

    if primitive_value {
        parts.push(format!("params.put(&key, {});",
                           serialize_primitive_expression(&value_shape.shape_type, "value")));
    } else {
        parts.push(format!("{value_type}Serializer::serialize(
                    params,
                    &format!(\"{{}}.{{}}\", prefix, \"{value_name}\"),
                    value,
                );",
                           value_type = shape.value_type(),
                           value_name = value_name(service, shape)))
    }

    parts.push("}".to_owned());
    parts.join("\n")
}

fn key_name(service: &Service, shape: &Shape) -> String {
    let key_name = shape.key
        .as_ref()
        .expect("Key undefined")
        .location_name
        .as_ref()
        .map(String::as_ref)
        .unwrap_or_else(|| "key");
    capitalize_if_ec2(service, key_name)
}

fn value_name(service: &Service, shape: &Shape) -> String {
    let value_name = shape.value
        .as_ref()
        .expect("Value undefined")
        .location_name
        .as_ref()
        .map(String::as_ref)
        .unwrap_or_else(|| "value");
    capitalize_if_ec2(service, value_name)
}

fn member_location(service: &Service, member: &Member, default: &str) -> String {
    let member_location = member.location_name.clone().unwrap_or_else(|| default.to_owned());
    capitalize_if_ec2(service, &member_location)
}

fn capitalize_if_ec2(service: &Service, name: &str) -> String {
    match &service.metadata.protocol[..] {
        "ec2" => capitalize_first(name),
        _ => name.to_owned(),
    }
}

fn generate_struct_serializer(service: &Service, shape: &Shape) -> String {
    format!(
        "let mut prefix = name.to_string();
        if prefix != \"\" {{
            prefix.push_str(\".\");
        }}

        {struct_field_serializers}
        ",
        struct_field_serializers = generate_struct_field_serializers(service, shape),
    )
}

fn generate_struct_field_serializers(service: &Service, shape: &Shape) -> String {
    shape.members
        .as_ref()
        .unwrap()
        .iter()
        .map(|(member_name, member)| {
            let primitive = service.shape_for_member(member).unwrap().is_primitive();

            if shape.required(member_name) {
                if primitive {
                    required_primitive_field_serializer(service, member_name, member)
                } else {
                    required_complex_field_serializer(service, member_name, member)
                }
            } else if primitive {
                optional_primitive_field_serializer(service, member_name, member)
            } else {
                optional_complex_field_serializer(service, member_name, member)
            }
        })
        .collect::<Vec<String>>()
        .join("\n")
}

fn optional_primitive_field_serializer(service: &Service,
                                       member_name: &str,
                                       member: &Member)
                                       -> String {
    let member_shape = service.shape_for_member(member).unwrap();
    let expression = serialize_primitive_expression(&member_shape.shape_type, "field_value");

    format!("if let Some(ref field_value) = obj.{field_name} {{
                params.put(&format!(\"{{}}{{}}\", prefix, \"{tag_name}\"), {expression});
            }}",
            field_name = generate_field_name(member_name),
            expression = expression,
            tag_name = member_location(service, member, member_name))
}

fn required_primitive_field_serializer(service: &Service,
                                       member_name: &str,
                                       member: &Member)
                                       -> String {
    let member_shape = service.shape_for_member(member).unwrap();
    let expression = serialize_primitive_expression(&member_shape.shape_type,
                                                    &format!("obj.{}",
                                                             generate_field_name(member_name)));

    format!("params.put(&format!(\"{{}}{{}}\", prefix, \"{tag_name}\"), {expression});",
            expression = expression,
            tag_name = member_location(service, member, member_name))
}

fn serialize_primitive_expression(shape_type: &ShapeType, var_name: &str) -> String {
    match *shape_type {
        ShapeType::String | ShapeType::Timestamp => format!("&{}", var_name),
        ShapeType::Integer | ShapeType::Double | ShapeType::Long | ShapeType::Boolean => {
            format!("&{}.to_string()", var_name)
        }
        ShapeType::Blob => format!("::std::str::from_utf8(&{}).unwrap()", var_name),
        shape_type => panic!("Unknown primitive shape type: {:?}", shape_type),
    }
}

fn required_complex_field_serializer(service: &Service,
                                     member_name: &str,
                                     member: &Member)
                                     -> String {
    format!("{member_shape}Serializer::serialize(
                params,
                &format!(\"{{}}{{}}\", prefix, \"{tag_name}\"),
                &obj.{field_name},
            );",
            field_name = generate_field_name(member_name),
            member_shape = member.shape,
            tag_name = member_location(service, member, member_name))
}

fn optional_complex_field_serializer(service: &Service,
                                     member_name: &str,
                                     member: &Member)
                                     -> String {
    format!("if let Some(ref field_value) = obj.{field_name} {{
                {member_shape_name}Serializer::serialize(
                    params,
                    &format!(\"{{}}{{}}\", prefix, \"{tag_name}\"),
                    field_value,
                );
            }}",
            field_name = generate_field_name(member_name),
            member_shape_name = member.shape,
            tag_name = member_location(service, member, member_name))
}

fn generate_documentation(operation: &Operation) -> String {
    match operation.documentation {
        Some(ref docs) => {
            format!("#[doc=\"{}\"]",
                    docs.replace("\\", "\\\\").replace("\"", "\\\""))
        }
        None => "".to_owned(),
    }
}

fn generate_method_signature(operation_name: &str, operation: &Operation) -> String {
    if operation.input.is_some() {
        format!(
            "pub fn {operation_name}(&self, input: &{input_type}) -> Result<{output_type}, {error_type}>",
            input_type = operation.input.as_ref().unwrap().shape,
            operation_name = operation.name.to_snake_case(),
            output_type = &operation.output_shape_or("()"),
            error_type = error_type_name(operation_name),
        )
    } else {
        format!(
            "pub fn {operation_name}(&self) -> Result<{output_type}, {error_type}>",
            operation_name = operation.name.to_snake_case(),
            output_type = &operation.output_shape_or("()"),
            error_type = error_type_name(operation_name),
        )
    }
}