use std::io::Write;
use inflector::Inflector;
use regex::{Captures, Regex};
use hyper::status::StatusCode;
use botocore::{Member, Operation, Service, Shape};
use super::{GenerateProtocol, error_type_name, FileWriter, IoResult};
pub struct RestJsonGenerator;
impl GenerateProtocol for RestJsonGenerator {
fn generate_methods(&self, writer: &mut FileWriter, service: &Service) -> IoResult {
for (operation_name, operation) in service.operations.iter() {
let input_type = operation.input_shape();
let output_type = operation.output_shape_or("()");
let input_shape = &service.shapes[input_type];
let member_uri_strings = generate_shape_member_uri_strings(input_shape);
let load_payload = input_shape.members
.as_ref()
.unwrap()
.iter()
.any(|(_, member)| member.location.is_none());
let member_param_strings = generate_shape_member_param_strings(input_shape);
writeln!(writer,"
{documentation}
{method_signature} -> Result<{output_type}, {error_type}> {{
{encode_input}
{request_uri_formatter}
let mut request = SignedRequest::new(\"{http_method}\", \"{endpoint_prefix}\", self.region, &request_uri);
request.set_content_type(\"application/x-amz-json-1.1\".to_owned());
{modify_endpoint_prefix}
{load_payload}
{load_params}
request.sign(&try!(self.credentials_provider.credentials()));
let result = try!(self.dispatcher.dispatch(&request));
let mut body = result.body;
// `serde-json` serializes field-less structs as \"null\", but AWS returns
// \"{{}}\" for a field-less response, so we must check for this result
// and convert it if necessary.
if body == b\"{{}}\" {{
body = b\"null\".to_vec();
}}
debug!(\"Response body: {{:?}}\", body);
debug!(\"Response status: {{}}\", result.status);
match result.status {{
{status_code} => {{
{ok_response}
}}
_ => Err({error_type}::from_body(String::from_utf8_lossy(&body).as_ref())),
}}
}}
",
documentation = generate_documentation(operation).unwrap_or("".to_owned()),
method_signature = generate_method_signature(operation, input_shape),
endpoint_prefix = service.signing_name(),
modify_endpoint_prefix = generate_endpoint_modification(service).unwrap_or("".to_owned()),
http_method = operation.http.method,
error_type = error_type_name(operation_name),
status_code = http_code_to_status_code(operation.http.response_code),
ok_response = generate_ok_response(operation, output_type),
output_type = output_type,
request_uri_formatter = generate_uri_formatter(
&generate_snake_case_uri(&operation.http.request_uri),
&member_uri_strings
),
load_payload = generate_payload_loading_string(load_payload),
load_params = generate_params_loading_string(&member_param_strings),
encode_input = generate_encoding_string(load_payload),
)?
}
Ok(())
}
fn generate_prelude(&self, writer: &mut FileWriter, _: &Service) -> IoResult {
writeln!(writer,
"use param::{{Params, ServiceParams}};
use signature::SignedRequest;
use serde_json;
use serde_json::from_str;
use serde_json::Value as SerdeJsonValue;")
}
fn generate_struct_attributes(&self,
serialized: bool,
deserialized: bool)
-> String {
let mut derived = vec!["Default", "Debug", "Clone"];
if serialized {
derived.push("Serialize");
}
if deserialized {
derived.push("Deserialize")
}
format!("#[derive({})]", derived.join(","))
}
fn timestamp_type(&self) -> &'static str {
"f64"
}
}
trait CodegenString {
fn enum_as_string(&self) -> String;
}
impl CodegenString for StatusCode {
fn enum_as_string(&self) -> String {
format!("StatusCode::{:?}", self)
}
}
fn http_code_to_status_code(code: Option<i32>) -> String {
match code {
Some(actual_code) => StatusCode::from_u16(actual_code as u16).enum_as_string(),
None => "StatusCode::Ok".to_string(),
}
}
fn generate_endpoint_modification(service: &Service) -> Option<String> {
if service.signing_name() == service.metadata.endpoint_prefix {
None
} else {
Some(format!("request.set_endpoint_prefix(\"{}\".to_string());",
service.metadata.endpoint_prefix))
}
}
fn generate_method_signature(operation: &Operation, shape: &Shape) -> String {
if shape.members.is_some() && !shape.members.as_ref().unwrap().is_empty() {
format!("pub fn {method_name}(&self, input: &{input_type})",
method_name = operation.name.to_snake_case(),
input_type = operation.input_shape())
} else {
format!("pub fn {method_name}(&self)",
method_name = operation.name.to_snake_case())
}
}
fn generate_encoding_string(load_payload: bool) -> String {
if load_payload {
"let encoded = serde_json::to_string(input).unwrap();".to_owned()
} else {
"".to_owned()
}
}
fn generate_payload_loading_string(load_payload: bool) -> String {
if load_payload {
"request.set_payload(Some(encoded.into_bytes()));".to_owned()
} else {
"".to_owned()
}
}
fn generate_snake_case_uri(request_uri: &str) -> String {
lazy_static! {
static ref URI_ARGS_REGEX: Regex = Regex::new(r"\{([\w\d]+)\}").unwrap();
}
URI_ARGS_REGEX.replace_all(request_uri, |caps: &Captures| {
format!("{{{}}}",
caps.get(1).map(|c| Inflector::to_snake_case(c.as_str())).unwrap())
})
.to_string()
}
fn generate_params_loading_string(param_strings: &[String]) -> String {
match param_strings.len() {
0 => "".to_owned(),
_ => {
format!("let mut params = Params::new();
{param_strings}
request.set_params(params);",
param_strings = param_strings.join("\n"))
}
}
}
fn generate_shape_member_param_strings(shape: &Shape) -> Vec<String> {
shape.members
.as_ref()
.unwrap()
.iter()
.filter_map(|(member_name, member)| if !member.deprecated() {
generate_param_load_string(member_name, member, shape)
} else {
None
})
.collect::<Vec<String>>()
}
fn generate_param_load_string(member_name: &str, member: &Member, shape: &Shape) -> Option<String> {
match member.location {
Some(ref x) if x == "querystring" => {
if shape.required(member_name) {
Some(format!("params.put(\"{member_name}\", &input.{field_name}.to_string());",
member_name = member_name,
field_name = member_name.to_snake_case()))
} else {
Some(format!(
"match input.{field_name} {{
Some(ref x) => params.put(\"{member_name}\", &x.to_string()),
None => {{}},
}}",
member_name = member_name,
field_name = member_name.to_snake_case(),
))
}
}
Some(_) | None => None,
}
}
fn generate_uri_formatter(request_uri: &str, uri_strings: &[String]) -> String {
match uri_strings.len() {
0 => {
format!(
"let request_uri = \"{request_uri}\";",
request_uri = request_uri,
)
}
_ => {
format!("let request_uri = format!(\"{request_uri}\", {uri_strings});",
request_uri = request_uri,
uri_strings = uri_strings.join(", "))
}
}
}
fn generate_shape_member_uri_strings(shape: &Shape) -> Vec<String> {
shape.members
.as_ref()
.unwrap()
.iter()
.filter_map(|(member_name, member)| {
generate_member_format_string(&member_name.to_snake_case(), member)
})
.collect::<Vec<String>>()
}
fn generate_member_format_string(member_name: &str, member: &Member) -> Option<String> {
match member.location {
Some(ref x) if x == "uri" => {
match member.location_name {
Some(ref loc_name) => {
Some(format!(
"{member_name} = input.{field_name}",
field_name = member_name,
member_name = loc_name.to_snake_case(),
))
}
None => {
Some(format!(
"{member_name} = input.{field_name}",
field_name = member_name,
member_name = member_name,
))
}
}
}
Some(_) | None => None,
}
}
fn generate_documentation(operation: &Operation) -> Option<String> {
operation.documentation
.as_ref()
.map(|docs| format!("#[doc=\"{}\"]", docs.replace("\"", "\\\"")))
}
fn generate_ok_response(operation: &Operation, output_type: &str) -> String {
if operation.output.is_some() {
format!("Ok(serde_json::from_str::<{}>(String::from_utf8_lossy(&body).as_ref()).unwrap())",
output_type)
} else {
"Ok(())".to_owned()
}
}