From a2dd71451deaf49e2bc4bb8de68a4e4cc87ec8a9 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Tue, 12 May 2015 11:05:59 +0200 Subject: [PATCH 01/14] feat(config): basis for simplified value setting Previously we would set static structures manully, using complex cases and utility functions. Now we setup the foundation to allow setting a generic `json::value::Value` instead, which can later be deserialized into the target structure. Related to #111 --- src/mako/api/lib/schema.mako | 6 +-- src/mako/cli/lib/engine.mako | 99 ++++++++---------------------------- src/rust/cli/cmn.rs | 4 ++ 3 files changed, 28 insertions(+), 81 deletions(-) diff --git a/src/mako/api/lib/schema.mako b/src/mako/api/lib/schema.mako index 521740999e..4cb4ee33e9 100644 --- a/src/mako/api/lib/schema.mako +++ b/src/mako/api/lib/schema.mako @@ -55,15 +55,13 @@ ${struct}; markers = schema_markers(s, c, transitive=True) # We always need Serialization support, as others might want to serialize the response, even though we will # only deserialize it. - traits = ['Clone', 'Debug', 'Serialize'] + # And since we don't know what others want to do, we implement Deserialize as well by default ... + traits = ['Clone', 'Debug', 'Serialize', 'Deserialize'] # default only works for structs, and 'variant' will be an enum if 'variant' not in s: traits.insert(0, 'Default') - if RESPONSE_MARKER_TRAIT in markers: - traits.append('Deserialize') - nt_markers = schema_markers(s, c, transitive=False) allow_optionals = is_schema_with_optionals(nt_markers) diff --git a/src/mako/cli/lib/engine.mako b/src/mako/cli/lib/engine.mako index 0cb8429887..bf6e206720 100644 --- a/src/mako/cli/lib/engine.mako +++ b/src/mako/cli/lib/engine.mako @@ -344,14 +344,12 @@ if dry_run { <% allow_optionals_fn = lambda s: is_schema_with_optionals(schema_markers(s, c, transitive=False)) - def flatten_schema_fields(schema, res, init_fn_map, fields, cur=list(), init_call=None): + def flatten_schema_fields(schema, res, fields, cur=list()): if len(cur) == 0: - init_call = '' cur = list() opt_access = '.as_mut().unwrap()' allow_optionals = allow_optionals_fn(schema) - parent_init_call = init_call if not allow_optionals: opt_access = '' for fn, f in schema.fields.iteritems(): @@ -359,38 +357,20 @@ if dry_run { fields.add(fn) if isinstance(f, SchemaEntry): cur[-1][0] = mangle_ident(fn) - res.append((init_call, schema, f, list(cur))) + res.append((schema, f, list(cur))) else: - if allow_optionals: - init_fn_name = 'request_%s_init' % '_'.join(mangle_ident(t[1]) for t in cur) - init_call = init_fn_name + '(&mut request);' - struct_field = 'request.' + '.'.join('%s%s' % (mangle_ident(t[1]), opt_access) for t in cur[:-1]) - if len(cur) > 1: - struct_field += '.' - struct_field += mangle_ident(cur[-1][1]) - init_fn = "fn %s(request: &mut api::%s) {\n" % (init_fn_name, request_prop_type) - if parent_init_call: - pcall = parent_init_call[:parent_init_call.index('(')] + "(request)" - init_fn += " %s;\n" % pcall - init_fn += " if %s.is_none() {\n" % struct_field - init_fn += " %s = Some(Default::default());\n" % struct_field - init_fn += " }\n" - init_fn += "}\n" - - init_fn_map[init_fn_name] = init_fn - # end handle init - flatten_schema_fields(f, res, init_fn_map, fields, cur, init_call) + flatten_schema_fields(f, res, fields, cur) cur.pop() # endfor # end utility schema_fields = list() - init_fn_map = dict() fields = set() - flatten_schema_fields(request_cli_schema, schema_fields, init_fn_map, fields) + flatten_schema_fields(request_cli_schema, schema_fields, fields) %>\ -let mut ${request_prop_name} = api::${request_prop_type}::default(); let mut field_cursor = FieldCursor::default(); +let mut object = json::value::Value::Object(Default::default()); + for kvarg in ${opt_values(KEY_VALUE_ARG)} { let last_errc = err.issues.len(); let (key, value) = parse_kv_arg(&*kvarg, err, false); @@ -405,60 +385,25 @@ for kvarg in ${opt_values(KEY_VALUE_ARG)} { } continue; } - % for name in sorted(init_fn_map.keys()): -${init_fn_map[name] | indent_by(4)} - % endfor - match &temp_cursor.to_string()[..] { - % for init_call, schema, fe, f in schema_fields: + + let field_type = + match &temp_cursor.to_string()[..] { + % for schema, fe, f in schema_fields: <% - ptype = actual_json_type(f[-1][1], fe.actual_property.type) - value_unwrap = 'value.unwrap_or("%s")' % JSON_TYPE_VALUE_MAP[ptype] pname = FIELD_SEP.join(mangle_subcommand(t[1]) for t in f) - - allow_optionals = True - opt_prefix = 'Some(' - opt_suffix = ')' - struct_field = 'request.' + '.'.join(t[0] for t in f) - - opt_init = 'if ' + struct_field + '.is_none() {\n' - opt_init += ' ' + struct_field + ' = Some(Default::default());\n' - opt_init += '}\n' - - opt_access = '.as_mut().unwrap()' - if not allow_optionals_fn(schema): - opt_prefix = opt_suffix = opt_access = opt_init = '' + ptype = actual_json_type(f[-1][1], fe.actual_property.type) %>\ - "${pname}" => { - % if init_call: - ${init_call} - % endif - % if fe.container_type == CTYPE_POD: - ${struct_field} = ${opt_prefix}\ - % elif fe.container_type == CTYPE_ARRAY: - ${opt_init | indent_all_but_first_by(4)}\ - ${struct_field}${opt_access}.push(\ - % elif fe.container_type == CTYPE_MAP: - ${opt_init | indent_all_but_first_by(4)}\ -let (key, value) = parse_kv_arg(${value_unwrap}, err, true); - ${struct_field}${opt_access}.insert(key.to_string(), \ - % endif # container type handling - % if ptype != 'string': -arg_from_str(${value_unwrap}, err, "${pname}", "${ptype}")\ - % else: -${value_unwrap}.to_string()\ - % endif - % if fe.container_type == CTYPE_POD: -${opt_suffix}\ - % else: -)\ - % endif -; - }, - % endfor # each nested field - _ => { - let suggestion = FieldCursor::did_you_mean(key, &${field_vec(sorted(fields))}); - err.issues.push(CLIError::Field(FieldError::Unknown(temp_cursor.to_string(), suggestion, value.map(|v| v.to_string())))); - } + "${pname}" => Some("${ptype}"), + % endfor # each nested field + _ => { + let suggestion = FieldCursor::did_you_mean(key, &${field_vec(sorted(fields))}); + err.issues.push(CLIError::Field(FieldError::Unknown(temp_cursor.to_string(), suggestion, value.map(|v| v.to_string())))); + None + } + }; + if let Some(field_type) = field_type { + temp_cursor.set_json_value(&mut object, value.unwrap(), field_type); } } +let mut ${request_prop_name}: api::${request_prop_type} = json::value::from_value(object).unwrap(); \ No newline at end of file diff --git a/src/rust/cli/cmn.rs b/src/rust/cli/cmn.rs index b5493a1495..c7299f5f15 100644 --- a/src/rust/cli/cmn.rs +++ b/src/rust/cli/cmn.rs @@ -199,6 +199,10 @@ impl FieldCursor { } } + pub fn set_json_value(&self, object: &mut json::value::Value, value: &str, value_type: &str) { + + } + pub fn num_fields(&self) -> usize { self.0.len() } From f83dff672bc5a739f1a4b76333e25d40523fbe2c Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Tue, 12 May 2015 12:17:36 +0200 Subject: [PATCH 02/14] refactor(config): bring in all required field data Previously we only knew the type as string, now we have enums and additional type information, like whether or not it's a POD. However, borrow-checker doesn't like the current code, will need more work. --- src/mako/cli/lib/cli.py | 16 ++++++++++++ src/mako/cli/lib/engine.mako | 15 ++++++----- src/rust/cli/cmn.rs | 50 ++++++++++++++++++++++++++++++++++-- 3 files changed, 73 insertions(+), 8 deletions(-) diff --git a/src/mako/cli/lib/cli.py b/src/mako/cli/lib/cli.py index 1eb3253444..4b99468a57 100644 --- a/src/mako/cli/lib/cli.py +++ b/src/mako/cli/lib/cli.py @@ -59,6 +59,22 @@ JSON_TYPE_RND_MAP = {'boolean': lambda: str(bool(randint(0, 1))).lower(), 'int64' : lambda: randint(-101, -1), 'string': lambda: '%s' % choice(util.words).lower()} +JSON_TYPE_TO_ENUM_MAP = {'boolean' : 'Boolean', + 'integer' : 'Int', + 'number' : 'Float', + 'uint32' : 'Int', + 'double' : 'Float', + 'float' : 'Float', + 'int32' : 'Int', + 'any' : 'String', # TODO: Figure out how to handle it. It's 'interface' in Go ... + 'int64' : 'Int', + 'uint64' : 'Uint', + 'string' : 'String'} + +CTYPE_TO_ENUM_MAP = {CTYPE_POD: 'Pod', + CTYPE_ARRAY: 'Vec', + CTYPE_MAP: 'Map'} + JSON_TYPE_VALUE_MAP = {'boolean': 'false', 'integer' : '-0', 'uint32' : '0', diff --git a/src/mako/cli/lib/engine.mako b/src/mako/cli/lib/engine.mako index bf6e206720..936a835954 100644 --- a/src/mako/cli/lib/engine.mako +++ b/src/mako/cli/lib/engine.mako @@ -8,7 +8,8 @@ call_method_ident, POD_TYPES, opt_value, ident, JSON_TYPE_VALUE_MAP, KEY_VALUE_ARG, to_cli_schema, SchemaEntry, CTYPE_POD, actual_json_type, CTYPE_MAP, CTYPE_ARRAY, application_secret_path, DEBUG_FLAG, DEBUG_AUTH_FLAG, CONFIG_DIR_FLAG, req_value, MODE_ARG, - opt_values, SCOPE_ARG, CONFIG_DIR_ARG, DEFAULT_MIME, field_vec, comma_sep_fields) + opt_values, SCOPE_ARG, CONFIG_DIR_ARG, DEFAULT_MIME, field_vec, comma_sep_fields, JSON_TYPE_TO_ENUM_MAP, + CTYPE_TO_ENUM_MAP) v_arg = '<%s>' % VALUE_ARG SOPT = 'self.opt' @@ -32,7 +33,7 @@ %>\ use cmn::{InvalidOptionsError, CLIError, JsonTokenStorage, arg_from_str, writer_from_opts, parse_kv_arg, input_file_from_opts, input_mime_from_opts, FieldCursor, FieldError, CallType, UploadProtocol, - calltype_from_str, remove_json_null_values}; + calltype_from_str, remove_json_null_values, ComplexType, JsonType, JsonTypeInfo}; use std::default::Default; use std::str::FromStr; @@ -386,14 +387,16 @@ for kvarg in ${opt_values(KEY_VALUE_ARG)} { continue; } - let field_type = + let type_info = match &temp_cursor.to_string()[..] { % for schema, fe, f in schema_fields: <% pname = FIELD_SEP.join(mangle_subcommand(t[1]) for t in f) ptype = actual_json_type(f[-1][1], fe.actual_property.type) + jtype = 'JsonType::' + JSON_TYPE_TO_ENUM_MAP[ptype] + ctype = 'ComplexType::' + CTYPE_TO_ENUM_MAP[fe.container_type] %>\ - "${pname}" => Some("${ptype}"), + "${pname}" => Some(JsonTypeInfo { jtype: ${jtype}, ctype: ${ctype} }), % endfor # each nested field _ => { let suggestion = FieldCursor::did_you_mean(key, &${field_vec(sorted(fields))}); @@ -401,8 +404,8 @@ for kvarg in ${opt_values(KEY_VALUE_ARG)} { None } }; - if let Some(field_type) = field_type { - temp_cursor.set_json_value(&mut object, value.unwrap(), field_type); + if let Some(type_info) = type_info { + temp_cursor.set_json_value(&mut object, value.unwrap(), type_info); } } let mut ${request_prop_name}: api::${request_prop_type} = json::value::from_value(object).unwrap(); diff --git a/src/rust/cli/cmn.rs b/src/rust/cli/cmn.rs index c7299f5f15..c11a7f325c 100644 --- a/src/rust/cli/cmn.rs +++ b/src/rust/cli/cmn.rs @@ -17,6 +17,33 @@ use std::default::Default; const FIELD_SEP: char = '.'; + +pub enum ComplexType { + Pod, + Vec, + Map, +} + + // Null, + // Bool(bool), + // I64(i64), + // U64(u64), + // F64(f64), + // String(String), + +pub enum JsonType { + Boolean, + Int, + Uint, + Float, + String, +} + +pub struct JsonTypeInfo { + pub jtype: JsonType, + pub ctype: ComplexType, +} + // Based on @erickt user comment. Thanks for the idea ! // Remove all keys whose values are null from given value (changed in place) pub fn remove_json_null_values(value: &mut json::value::Value) { @@ -199,8 +226,27 @@ impl FieldCursor { } } - pub fn set_json_value(&self, object: &mut json::value::Value, value: &str, value_type: &str) { - + pub fn set_json_value(&self, object: &mut json::value::Value, value: &str, + type_info: JsonTypeInfo) { + assert!(self.0.len() > 0); + + for field in &self.0[..self.0.len()-1] { + object = match *object { + json::value::Value::Object(ref mut mapping) => { + mapping.entry(field.to_owned()).or_insert( + json::value::Value::Object(Default::default()) + ) + }, + _ => panic!("We don't expect non-object Values here ...") + } + } + + match *object { + json::value::Value::Object(ref mut mapping) => { + + } + _ => unreachable!() + } } pub fn num_fields(&self) -> usize { From e434563215b6b8cddc0aaa6a6c5ef48d6e7aedbb Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Tue, 12 May 2015 17:21:25 +0200 Subject: [PATCH 03/14] chore(cargo): compilation without local overrides Also checked in code for groupsmigration to allow others to test it simply by checking out the right commit. --- etc/api/type-api.yaml | 2 -- etc/api/type-cli.yaml | 8 +++++--- gen/groupsmigration1-cli/Cargo.toml | 24 ++++++++++++++++++++---- gen/groupsmigration1-cli/README.md | 9 +++++++++ gen/groupsmigration1-cli/src/main.rs | 8 +++++--- gen/groupsmigration1/Cargo.toml | 18 +++++++++++++++--- gen/groupsmigration1/src/cmn.rs | 14 ++++++++------ src/mako/Cargo.toml.mako | 18 ++++++++++++++++-- 8 files changed, 78 insertions(+), 23 deletions(-) diff --git a/etc/api/type-api.yaml b/etc/api/type-api.yaml index 6c0e57783a..4e681ee659 100644 --- a/etc/api/type-api.yaml +++ b/etc/api/type-api.yaml @@ -25,6 +25,4 @@ cargo: keywords: [protocol, web, api] dependencies: - url = "*" - - serde = ">= 0.3.0" - - serde_macros = "*" - json-tools = ">= 0.3.0" diff --git a/etc/api/type-cli.yaml b/etc/api/type-cli.yaml index 82f29a1772..43009c3078 100644 --- a/etc/api/type-cli.yaml +++ b/etc/api/type-cli.yaml @@ -27,7 +27,9 @@ cargo: is_executable: YES dependencies: - strsim = "*" - - yup-hyper-mock = "*" - - serde = ">= 0.3.0" - - serde_macros = "*" - clap = "*" + - | + [dependencies.yup-hyper-mock] + version = "*" + git = "https://github.com/Byron/yup-hyper-mock" + rev = "ee56de4dead136b3ca5a3eda6ca7057f9074e261" diff --git a/gen/groupsmigration1-cli/Cargo.toml b/gen/groupsmigration1-cli/Cargo.toml index 22449fa31a..feab0ae694 100644 --- a/gen/groupsmigration1-cli/Cargo.toml +++ b/gen/groupsmigration1-cli/Cargo.toml @@ -17,14 +17,30 @@ keywords = ["groupsmigration", "google", "cli"] name = "groupsmigration1" [dependencies] -hyper = ">= 0.4.0" mime = "*" -yup-oauth2 = "*" -strsim = "*" -yup-hyper-mock = "*" + serde = ">= 0.3.0" serde_macros = "*" +strsim = "*" clap = "*" +[dependencies.yup-hyper-mock] + version = "*" + git = "https://github.com/Byron/yup-hyper-mock" + rev = "ee56de4dead136b3ca5a3eda6ca7057f9074e261" + + +# Needed for latest fix in macros ! +[dependencies.hyper] +version = ">= 0.4.0" +git = "https://github.com/hyperium/hyper" +rev = "871f37a5605d433e5699ed2f16631001d86d7805" + +# to adapt to hyper changes ... +[dependencies.yup-oauth2] +version = "*" +git = "https://github.com/Byron/yup-oauth2" +rev = "94d5b7c2cac02ad67da8504504364b3081a9a866" + [dependencies.google-groupsmigration1] path = "../groupsmigration1" diff --git a/gen/groupsmigration1-cli/README.md b/gen/groupsmigration1-cli/README.md index 8ff231a100..850b911839 100644 --- a/gen/groupsmigration1-cli/README.md +++ b/gen/groupsmigration1-cli/README.md @@ -13,6 +13,15 @@ If data-structures are requested, these will be returned as pretty-printed JSON, Everything else about the *Groups Migration* API can be found at the [official documentation site](https://developers.google.com/google-apps/groups-migration/). +# Downloads + +You can download the pre-compiled 64bit binaries for the following platforms: + +* ![icon](http://megaicons.net/static/img/icons_sizes/6/140/16/ubuntu-icon.png) [ubuntu](http://dl.byronimo.de/google.rs/cli/0.2.0/ubuntu/groupsmigration1.tar.gz) +* ![icon](http://hydra-media.cursecdn.com/wow.gamepedia.com/a/a2/Apple-icon-16x16.png?version=25ddd67ac3dd3b634478e3978b76cb74) [osx](http://dl.byronimo.de/google.rs/cli/0.2.0/osx/groupsmigration1.tar.gz) + +Find the source code [on github](https://github.com/Byron/google-apis-rs/tree/master/gen/groupsmigration1-cli). + # Usage This documentation was generated from the *Groups Migration* API at revision *20140416*. The CLI is at version *0.2.0*. diff --git a/gen/groupsmigration1-cli/src/main.rs b/gen/groupsmigration1-cli/src/main.rs index bb01653c02..1d4f176b35 100644 --- a/gen/groupsmigration1-cli/src/main.rs +++ b/gen/groupsmigration1-cli/src/main.rs @@ -61,9 +61,11 @@ impl<'n, 'a> Engine<'n, 'a> { } } if !found { - err.issues.push(CLIError::UnknownParameter(key.to_string(), - Vec::new() + &self.gp + &[] - )); + err.issues.push(CLIError::UnknownParameter(key.to_string(), + {let mut v = Vec::new(); + v.extend(self.gp.iter().map(|v|*v)); + v.extend([].iter().map(|v|*v)); + v } )); } } } diff --git a/gen/groupsmigration1/Cargo.toml b/gen/groupsmigration1/Cargo.toml index 362f5964b1..1a41af482f 100644 --- a/gen/groupsmigration1/Cargo.toml +++ b/gen/groupsmigration1/Cargo.toml @@ -15,10 +15,22 @@ keywords = ["groupsmigration", "google", "protocol", "web", "api"] [dependencies] -hyper = ">= 0.4.0" mime = "*" -yup-oauth2 = "*" -url = "*" + serde = ">= 0.3.0" serde_macros = "*" +url = "*" json-tools = ">= 0.3.0" + +# Needed for latest fix in macros ! +[dependencies.hyper] +version = ">= 0.4.0" +git = "https://github.com/hyperium/hyper" +rev = "871f37a5605d433e5699ed2f16631001d86d7805" + +# to adapt to hyper changes ... +[dependencies.yup-oauth2] +version = "*" +git = "https://github.com/Byron/yup-oauth2" +rev = "94d5b7c2cac02ad67da8504504364b3081a9a866" + diff --git a/gen/groupsmigration1/src/cmn.rs b/gen/groupsmigration1/src/cmn.rs index 908bef39dc..e38e121c07 100644 --- a/gen/groupsmigration1/src/cmn.rs +++ b/gen/groupsmigration1/src/cmn.rs @@ -668,12 +668,14 @@ impl<'a, A> ResumableUploadHelper<'a, A> if self.delegate.cancel_chunk_upload(&range_header) { return None } - match self.client.post(self.url) - .header(range_header) - .header(ContentType(self.media_type.clone())) - .header(UserAgent(self.user_agent.to_string())) - .body(&mut section_reader) - .send() { + // workaround https://github.com/rust-lang/rust/issues/22252 + let res = self.client.post(self.url) + .header(range_header) + .header(ContentType(self.media_type.clone())) + .header(UserAgent(self.user_agent.to_string())) + .body(&mut section_reader) + .send(); + match res { Ok(mut res) => { if res.status == StatusCode::PermanentRedirect { continue diff --git a/src/mako/Cargo.toml.mako b/src/mako/Cargo.toml.mako index 9f48de8a47..7366eb6570 100644 --- a/src/mako/Cargo.toml.mako +++ b/src/mako/Cargo.toml.mako @@ -23,12 +23,26 @@ name = "${util.program_name()}" % endif [dependencies] -hyper = ">= 0.4.0" mime = "*" -yup-oauth2 = "*" + +serde = ">= 0.3.0" +serde_macros = "*" % for dep in cargo.get('dependencies', list()): ${dep} % endfor + +# Needed for latest fix in macros ! +[dependencies.hyper] +version = ">= 0.4.0" +git = "https://github.com/hyperium/hyper" +rev = "871f37a5605d433e5699ed2f16631001d86d7805" + +# to adapt to hyper changes ... +[dependencies.yup-oauth2] +version = "*" +git = "https://github.com/Byron/yup-oauth2" +rev = "94d5b7c2cac02ad67da8504504364b3081a9a866" + % if make.depends_on_suffix is not None: <% api_name = util.library_name() %>\ From 464394af22714fee650ca3e310336584666f921a Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Tue, 12 May 2015 19:48:37 +0200 Subject: [PATCH 04/14] refactor(config): handle recursive mut json values * recurively drill down a mutable, recursive enumeration, without borrow checker issues. The obvious solution doesn't work, but should. Stackoverflow ? * infrastructure to set actual value, with support for ararys, pods and hashmaps --- src/mako/cli/lib/engine.mako | 2 +- src/rust/cli/cmn.rs | 72 +++++++++++++++++++++++++++--------- 2 files changed, 55 insertions(+), 19 deletions(-) diff --git a/src/mako/cli/lib/engine.mako b/src/mako/cli/lib/engine.mako index 936a835954..d118a6f266 100644 --- a/src/mako/cli/lib/engine.mako +++ b/src/mako/cli/lib/engine.mako @@ -405,7 +405,7 @@ for kvarg in ${opt_values(KEY_VALUE_ARG)} { } }; if let Some(type_info) = type_info { - temp_cursor.set_json_value(&mut object, value.unwrap(), type_info); + temp_cursor.set_json_value(&mut object, value.unwrap(), type_info, err); } } let mut ${request_prop_name}: api::${request_prop_type} = json::value::from_value(object).unwrap(); diff --git a/src/rust/cli/cmn.rs b/src/rust/cli/cmn.rs index c11a7f325c..c583864e09 100644 --- a/src/rust/cli/cmn.rs +++ b/src/rust/cli/cmn.rs @@ -226,25 +226,61 @@ impl FieldCursor { } } - pub fn set_json_value(&self, object: &mut json::value::Value, value: &str, - type_info: JsonTypeInfo) { + pub fn set_json_value(&self, object: &mut json::value::Value, + value: &str, type_info: JsonTypeInfo, + err: &mut InvalidOptionsError) { assert!(self.0.len() > 0); - for field in &self.0[..self.0.len()-1] { - object = match *object { - json::value::Value::Object(ref mut mapping) => { - mapping.entry(field.to_owned()).or_insert( - json::value::Value::Object(Default::default()) - ) - }, - _ => panic!("We don't expect non-object Values here ...") + fn recursive_descent<'v>(keys: &[String], object: &'v mut json::value::Value) + -> &'v mut json::value::Value { + let next = + match *object { + json::value::Value::Object(ref mut mapping) => { + mapping.entry(keys[0].to_owned()).or_insert( + json::value::Value::Object(Default::default()) + ) + }, + _ => panic!("We don't expect non-object Values here ...") + }; + if keys.len() > 1 { + recursive_descent(&keys[1..], next) + } else { + next } } - match *object { + match *recursive_descent(&self.0, object) { json::value::Value::Object(ref mut mapping) => { + let field = &self.0[self.0.len()-1]; + let to_jval = + |jtype: JsonType, err: &mut InvalidOptionsError| + -> json::value::Value { + match jtype { + JsonType::Boolean => + json::value::Value::Bool(arg_from_str(value, err, &field, "boolean")), + JsonType::Int => + json::value::Value::I64(arg_from_str(value, err, &field, "int")), + JsonType::Uint => + json::value::Value::U64(arg_from_str(value, err, &field, "uint")), + JsonType::Float => + json::value::Value::F64(arg_from_str(value, err, &field, "float")), + JsonType::String => + json::value::Value::String(value.to_owned()), + } + }; - } + match type_info.ctype { + ComplexType::Pod => { + mapping.entry(field.to_owned()).or_insert(to_jval(type_info.jtype, err)); + }, + ComplexType::Vec => { + + }, + ComplexType::Map => { + let (key, value) = parse_kv_arg(value, err, true); + } + } + }, _ => unreachable!() } } @@ -316,15 +352,15 @@ pub fn writer_from_opts(arg: Option<&str>) -> Result, io::Error> { } -pub fn arg_from_str(arg: &str, err: &mut InvalidOptionsError, - arg_name: &'static str, - arg_type: &'static str) -> T +pub fn arg_from_str<'a, T>(arg: &str, err: &mut InvalidOptionsError, + arg_name: &'a str, + arg_type: &'a str) -> T where T: FromStr + Default, ::Err: fmt::Display { match FromStr::from_str(arg) { Err(perr) => { err.issues.push( - CLIError::ParseError(arg_name, arg_type, arg.to_string(), format!("{}", perr)) + CLIError::ParseError(arg_name.to_owned(), arg_type.to_owned(), arg.to_string(), format!("{}", perr)) ); Default::default() }, @@ -495,7 +531,7 @@ impl fmt::Display for FieldError { #[derive(Debug)] pub enum CLIError { Configuration(ConfigurationError), - ParseError(&'static str, &'static str, String, String), + ParseError(String, String, String, String), UnknownParameter(String, Vec<&'static str>), InvalidUploadProtocol(String, Vec), InvalidKeyValueSyntax(String, bool), @@ -513,7 +549,7 @@ impl fmt::Display for CLIError { CLIError::Field(ref err) => write!(f, "Field -> {}", err), CLIError::InvalidUploadProtocol(ref proto_name, ref valid_names) => writeln!(f, "'{}' is not a valid upload protocol. Choose from one of {}.", proto_name, valid_names.connect(", ")), - CLIError::ParseError(arg_name, type_name, ref value, ref err_desc) + CLIError::ParseError(ref arg_name, ref type_name, ref value, ref err_desc) => writeln!(f, "Failed to parse argument '{}' with value '{}' as {} with error: {}.", arg_name, value, type_name, err_desc), CLIError::UnknownParameter(ref param_name, ref possible_values) => { From d0b69af41390df40f5a11d44e08d1b67167a969a Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Wed, 13 May 2015 07:51:08 +0200 Subject: [PATCH 05/14] refactor(config):OK version of json value setter However, we don't set the correct field names yet, and are lacking a remapping of CLI field names to struct field names before any testing makes sense. --- src/rust/cli/cmn.rs | 66 +++++++++++++++++++++++++++------------------ 1 file changed, 40 insertions(+), 26 deletions(-) diff --git a/src/rust/cli/cmn.rs b/src/rust/cli/cmn.rs index c583864e09..79aad3f223 100644 --- a/src/rust/cli/cmn.rs +++ b/src/rust/cli/cmn.rs @@ -1,5 +1,6 @@ use oauth2::{ApplicationSecret, ConsoleApplicationSecret, TokenStorage, Token}; use serde::json; +use serde::json::value::Value; use mime::Mime; use clap::{App, SubCommand}; use strsim; @@ -46,9 +47,9 @@ pub struct JsonTypeInfo { // Based on @erickt user comment. Thanks for the idea ! // Remove all keys whose values are null from given value (changed in place) -pub fn remove_json_null_values(value: &mut json::value::Value) { +pub fn remove_json_null_values(value: &mut Value) { match *value { - json::value::Value::Object(ref mut map) => { + Value::Object(ref mut map) => { let mut for_removal = Vec::new(); for (key, mut value) in map.iter_mut() { @@ -226,58 +227,68 @@ impl FieldCursor { } } - pub fn set_json_value(&self, object: &mut json::value::Value, + pub fn set_json_value(&self, mut object: &mut Value, value: &str, type_info: JsonTypeInfo, err: &mut InvalidOptionsError) { assert!(self.0.len() > 0); - fn recursive_descent<'v>(keys: &[String], object: &'v mut json::value::Value) - -> &'v mut json::value::Value { - let next = - match *object { - json::value::Value::Object(ref mut mapping) => { - mapping.entry(keys[0].to_owned()).or_insert( - json::value::Value::Object(Default::default()) + for field in &self.0[..self.0.len()-1] { + let tmp = object; + object = + match *tmp { + Value::Object(ref mut mapping) => { + mapping.entry(field.to_owned()).or_insert( + Value::Object(Default::default()) ) }, _ => panic!("We don't expect non-object Values here ...") }; - if keys.len() > 1 { - recursive_descent(&keys[1..], next) - } else { - next - } } - match *recursive_descent(&self.0, object) { - json::value::Value::Object(ref mut mapping) => { + match *object { + Value::Object(ref mut mapping) => { let field = &self.0[self.0.len()-1]; let to_jval = - |jtype: JsonType, err: &mut InvalidOptionsError| - -> json::value::Value { + |value: &str, jtype: JsonType, err: &mut InvalidOptionsError| + -> Value { match jtype { JsonType::Boolean => - json::value::Value::Bool(arg_from_str(value, err, &field, "boolean")), + Value::Bool(arg_from_str(value, err, &field, "boolean")), JsonType::Int => - json::value::Value::I64(arg_from_str(value, err, &field, "int")), + Value::I64(arg_from_str(value, err, &field, "int")), JsonType::Uint => - json::value::Value::U64(arg_from_str(value, err, &field, "uint")), + Value::U64(arg_from_str(value, err, &field, "uint")), JsonType::Float => - json::value::Value::F64(arg_from_str(value, err, &field, "float")), + Value::F64(arg_from_str(value, err, &field, "float")), JsonType::String => - json::value::Value::String(value.to_owned()), + Value::String(value.to_owned()), } }; match type_info.ctype { ComplexType::Pod => { - mapping.entry(field.to_owned()).or_insert(to_jval(type_info.jtype, err)); + mapping.entry(field.to_owned()).or_insert(to_jval(value, type_info.jtype, err)); }, ComplexType::Vec => { - + match *mapping.entry(field.to_owned()) + .or_insert(Value::Array(Default::default())) { + Value::Array(ref mut values) => values.push(to_jval(value, type_info.jtype, err)), + _ => unreachable!() + } }, ComplexType::Map => { let (key, value) = parse_kv_arg(value, err, true); + let jval = to_jval(value.unwrap_or(""), type_info.jtype, err); + + match *mapping.entry(field.to_owned()) + .or_insert(Value::Object(Default::default())) { + Value::Object(ref mut map) => { + if map.insert(key.to_owned(), jval).is_some() { + err.issues.push(CLIError::Field(FieldError::Duplicate(self.to_string()))); + } + } + _ => unreachable!() + } } } }, @@ -495,6 +506,7 @@ pub enum FieldError { PopOnEmpty(String), TrailingFieldSep(String), Unknown(String, Option, Option), + Duplicate(String), Empty, } @@ -521,6 +533,8 @@ impl fmt::Display for FieldError { }; writeln!(f, "Field '{}' does not exist.{}", field, suffix) }, + FieldError::Duplicate(ref cursor) + => writeln!(f, "Value at '{}' was already set", cursor), FieldError::Empty => writeln!(f, "Field names must not be empty."), } From ca36dbc50595f116952a95395d802d5be313b614 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Wed, 13 May 2015 08:45:57 +0200 Subject: [PATCH 06/14] feat(config): improved structure setter code We save about 30% of CLI code just because we offload the work of settings structures into serde, building a generic `json::Value` to contain all the data, and then let serde do the deserialization for us. All we need for that is some information we let the generator provide and translate it into the runtime. Closes #111 --- src/mako/cli/lib/engine.mako | 7 ++++--- src/rust/cli/cmn.rs | 21 ++++++++++++++++----- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/src/mako/cli/lib/engine.mako b/src/mako/cli/lib/engine.mako index d118a6f266..661d3c63db 100644 --- a/src/mako/cli/lib/engine.mako +++ b/src/mako/cli/lib/engine.mako @@ -392,11 +392,12 @@ for kvarg in ${opt_values(KEY_VALUE_ARG)} { % for schema, fe, f in schema_fields: <% pname = FIELD_SEP.join(mangle_subcommand(t[1]) for t in f) + sname = FIELD_SEP.join(t[1] for t in f) ptype = actual_json_type(f[-1][1], fe.actual_property.type) jtype = 'JsonType::' + JSON_TYPE_TO_ENUM_MAP[ptype] ctype = 'ComplexType::' + CTYPE_TO_ENUM_MAP[fe.container_type] %>\ - "${pname}" => Some(JsonTypeInfo { jtype: ${jtype}, ctype: ${ctype} }), + "${pname}" => Some(("${sname}", JsonTypeInfo { jtype: ${jtype}, ctype: ${ctype} })), % endfor # each nested field _ => { let suggestion = FieldCursor::did_you_mean(key, &${field_vec(sorted(fields))}); @@ -404,8 +405,8 @@ for kvarg in ${opt_values(KEY_VALUE_ARG)} { None } }; - if let Some(type_info) = type_info { - temp_cursor.set_json_value(&mut object, value.unwrap(), type_info, err); + if let Some((field_cursor_str, type_info)) = type_info { + FieldCursor::from(field_cursor_str).set_json_value(&mut object, value.unwrap(), type_info, err, &temp_cursor); } } let mut ${request_prop_name}: api::${request_prop_type} = json::value::from_value(object).unwrap(); diff --git a/src/rust/cli/cmn.rs b/src/rust/cli/cmn.rs index 79aad3f223..d4d131f762 100644 --- a/src/rust/cli/cmn.rs +++ b/src/rust/cli/cmn.rs @@ -122,6 +122,14 @@ impl ToString for FieldCursor { } } +impl From<&'static str> for FieldCursor { + fn from(value: &'static str) -> FieldCursor { + let mut res = FieldCursor::default(); + res.set(value).unwrap(); + res + } +} + impl FieldCursor { pub fn set(&mut self, value: &str) -> Result<(), CLIError> { if value.len() == 0 { @@ -229,7 +237,8 @@ impl FieldCursor { pub fn set_json_value(&self, mut object: &mut Value, value: &str, type_info: JsonTypeInfo, - err: &mut InvalidOptionsError) { + err: &mut InvalidOptionsError, + orig_cursor: &FieldCursor) { assert!(self.0.len() > 0); for field in &self.0[..self.0.len()-1] { @@ -267,7 +276,9 @@ impl FieldCursor { match type_info.ctype { ComplexType::Pod => { - mapping.entry(field.to_owned()).or_insert(to_jval(value, type_info.jtype, err)); + if mapping.insert(field.to_owned(), to_jval(value, type_info.jtype, err)).is_some() { + err.issues.push(CLIError::Field(FieldError::Duplicate(orig_cursor.to_string()))); + } }, ComplexType::Vec => { match *mapping.entry(field.to_owned()) @@ -282,9 +293,9 @@ impl FieldCursor { match *mapping.entry(field.to_owned()) .or_insert(Value::Object(Default::default())) { - Value::Object(ref mut map) => { - if map.insert(key.to_owned(), jval).is_some() { - err.issues.push(CLIError::Field(FieldError::Duplicate(self.to_string()))); + Value::Object(ref mut value_map) => { + if value_map.insert(key.to_owned(), jval).is_some() { + err.issues.push(CLIError::Field(FieldError::Duplicate(orig_cursor.to_string()))); } } _ => unreachable!() From 2cc48072344715c428c204e98ab991c8133cf4c2 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Wed, 13 May 2015 10:11:00 +0200 Subject: [PATCH 07/14] fix(API): URL substitution handling Previously we would remove the wrong parameters when attempting to remove only those parameters that have been used in the URL substitution. The code we have now is more idiomatic and appears to be removing the correct parameters. Closes #114 [skip ci] --- src/mako/api/lib/mbuild.mako | 29 +++++++++++++---------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/src/mako/api/lib/mbuild.mako b/src/mako/api/lib/mbuild.mako index cd754549e6..78cb303f22 100644 --- a/src/mako/api/lib/mbuild.mako +++ b/src/mako/api/lib/mbuild.mako @@ -618,16 +618,16 @@ else { ## Hanlde URI Tempates % if replacements: for &(find_this, param_name) in [${', '.join('("%s", "%s")' % r for r in replacements)}].iter() { - <% - replace_init = ': Option<&str> = None' - replace_assign = 'Some(value)' - url_replace_arg = 'replace_with.expect("to find substitution value in params")' - if URL_ENCODE in special_cases: - replace_init = ' = String::new()' - replace_assign = 'value.to_string()' - url_replace_arg = '&replace_with' - # end handle url encoding - %>\ +<% + replace_init = ': Option<&str> = None' + replace_assign = 'Some(value)' + url_replace_arg = 'replace_with.expect("to find substitution value in params")' + if URL_ENCODE in special_cases: + replace_init = ' = String::new()' + replace_assign = 'value.to_string()' + url_replace_arg = '&replace_with' + # end handle url encoding +%>\ let mut replace_with${replace_init}; for &(name, ref value) in params.iter() { if name == param_name { @@ -645,12 +645,9 @@ else { ## Remove all used parameters { let mut indices_for_removal: Vec = Vec::with_capacity(${len(replacements)}); - for param_name in [${', '.join('"%s"' % r[1] for r in replacements)}].iter() { - for (index, &(ref name, _)) in params.iter().rev().enumerate() { - if name == param_name { - indices_for_removal.push(params.len() - index - 1); - break; - } + for param_name in [${', '.join(reversed(['"%s"' % r[1] for r in replacements]))}].iter() { + if let Some(index) = params.iter().position(|t| &t.0 == param_name) { + indices_for_removal.push(index); } } for &index in indices_for_removal.iter() { From b0a41c4e788fd95f9ace6823c2e52d18f33195c9 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Thu, 18 Jun 2015 22:51:17 +0200 Subject: [PATCH 08/14] fix(api): first big step towards syntex Even though there is a bug that caues {} to be used in stead of (), when exanding macros, which causes syntax errors that we have to workaround, it's not a real issue. What's happening additionally is missing hyper macros, which now have to be expanded manually. Shouldn't be a problem, pretty-printing when compiling is made for just that ;). No, it's sad that `include!()` works so badly, it makes using serde so difficult ... it's no fun i must say. Just for stable ... I am not sure if it is worth it." --- etc/api/type-api.yaml | 5 +- gen/discovery1-cli/Cargo.toml | 16 +- gen/discovery1-cli/src/cmn.rs | 127 +++- gen/discovery1-cli/src/main.rs | 21 +- gen/discovery1/Cargo.toml | 12 +- gen/discovery1/src/cmn.rs | 54 +- gen/discovery1/src/lib.rs | 1107 +------------------------------- src/mako/Cargo.toml.mako | 11 +- src/mako/api/build.rs.mako | 17 + src/mako/api/lib.rs.in.mako | 134 ++++ src/mako/api/lib.rs.mako | 129 +--- src/mako/api/lib/mbuild.mako | 15 +- src/mako/cli/lib/engine.mako | 2 +- src/rust/api/cmn.rs | 36 +- 14 files changed, 392 insertions(+), 1294 deletions(-) create mode 100644 src/mako/api/build.rs.mako create mode 100644 src/mako/api/lib.rs.in.mako diff --git a/etc/api/type-api.yaml b/etc/api/type-api.yaml index 4e681ee659..a452cae9b6 100644 --- a/etc/api/type-api.yaml +++ b/etc/api/type-api.yaml @@ -18,11 +18,14 @@ make: - source: README.md - source: ../LICENSE.md - source: ../Cargo.toml + - source: lib.rs.in + output_dir: src - source: lib.rs output_dir: src + - source: build.rs + output_dir: src cargo: build_version: "0.1.7" keywords: [protocol, web, api] dependencies: - url = "*" - - json-tools = ">= 0.3.0" diff --git a/gen/discovery1-cli/Cargo.toml b/gen/discovery1-cli/Cargo.toml index 421b04bb8f..f64a0c3756 100644 --- a/gen/discovery1-cli/Cargo.toml +++ b/gen/discovery1-cli/Cargo.toml @@ -17,14 +17,20 @@ keywords = ["discovery", "google", "cli"] name = "discovery1" [dependencies] -hyper = ">= 0.4.0" +hyper = ">= 0.5.0" mime = "*" -yup-oauth2 = "*" -strsim = "*" -yup-hyper-mock = "*" serde = ">= 0.3.0" serde_macros = "*" -clap = "*" +strsim = "*" +yup-hyper-mock = ">=1.0.0" +clap = ">= 0.9.1" + +# to adapt to hyper changes ... +[dependencies.yup-oauth2] +version = "*" +git = "https://github.com/Byron/yup-oauth2" +rev = "598f5ed496077e9edca36ff95e2ab352c5b22d0f" + [dependencies.google-discovery1] path = "../discovery1" diff --git a/gen/discovery1-cli/src/cmn.rs b/gen/discovery1-cli/src/cmn.rs index 3335e7b6bd..56051cab08 100644 --- a/gen/discovery1-cli/src/cmn.rs +++ b/gen/discovery1-cli/src/cmn.rs @@ -2,6 +2,7 @@ // DO NOT EDIT use oauth2::{ApplicationSecret, ConsoleApplicationSecret, TokenStorage, Token}; use serde::json; +use serde::json::value::Value; use mime::Mime; use clap::{App, SubCommand}; use strsim; @@ -19,11 +20,38 @@ use std::default::Default; const FIELD_SEP: char = '.'; + +pub enum ComplexType { + Pod, + Vec, + Map, +} + + // Null, + // Bool(bool), + // I64(i64), + // U64(u64), + // F64(f64), + // String(String), + +pub enum JsonType { + Boolean, + Int, + Uint, + Float, + String, +} + +pub struct JsonTypeInfo { + pub jtype: JsonType, + pub ctype: ComplexType, +} + // Based on @erickt user comment. Thanks for the idea ! // Remove all keys whose values are null from given value (changed in place) -pub fn remove_json_null_values(value: &mut json::value::Value) { +pub fn remove_json_null_values(value: &mut Value) { match *value { - json::value::Value::Object(ref mut map) => { + Value::Object(ref mut map) => { let mut for_removal = Vec::new(); for (key, mut value) in map.iter_mut() { @@ -96,6 +124,14 @@ impl ToString for FieldCursor { } } +impl From<&'static str> for FieldCursor { + fn from(value: &'static str) -> FieldCursor { + let mut res = FieldCursor::default(); + res.set(value).unwrap(); + res + } +} + impl FieldCursor { pub fn set(&mut self, value: &str) -> Result<(), CLIError> { if value.len() == 0 { @@ -201,6 +237,78 @@ impl FieldCursor { } } + pub fn set_json_value(&self, mut object: &mut Value, + value: &str, type_info: JsonTypeInfo, + err: &mut InvalidOptionsError, + orig_cursor: &FieldCursor) { + assert!(self.0.len() > 0); + + for field in &self.0[..self.0.len()-1] { + let tmp = object; + object = + match *tmp { + Value::Object(ref mut mapping) => { + mapping.entry(field.to_owned()).or_insert( + Value::Object(Default::default()) + ) + }, + _ => panic!("We don't expect non-object Values here ...") + }; + } + + match *object { + Value::Object(ref mut mapping) => { + let field = &self.0[self.0.len()-1]; + let to_jval = + |value: &str, jtype: JsonType, err: &mut InvalidOptionsError| + -> Value { + match jtype { + JsonType::Boolean => + Value::Bool(arg_from_str(value, err, &field, "boolean")), + JsonType::Int => + Value::I64(arg_from_str(value, err, &field, "int")), + JsonType::Uint => + Value::U64(arg_from_str(value, err, &field, "uint")), + JsonType::Float => + Value::F64(arg_from_str(value, err, &field, "float")), + JsonType::String => + Value::String(value.to_owned()), + } + }; + + match type_info.ctype { + ComplexType::Pod => { + if mapping.insert(field.to_owned(), to_jval(value, type_info.jtype, err)).is_some() { + err.issues.push(CLIError::Field(FieldError::Duplicate(orig_cursor.to_string()))); + } + }, + ComplexType::Vec => { + match *mapping.entry(field.to_owned()) + .or_insert(Value::Array(Default::default())) { + Value::Array(ref mut values) => values.push(to_jval(value, type_info.jtype, err)), + _ => unreachable!() + } + }, + ComplexType::Map => { + let (key, value) = parse_kv_arg(value, err, true); + let jval = to_jval(value.unwrap_or(""), type_info.jtype, err); + + match *mapping.entry(field.to_owned()) + .or_insert(Value::Object(Default::default())) { + Value::Object(ref mut value_map) => { + if value_map.insert(key.to_owned(), jval).is_some() { + err.issues.push(CLIError::Field(FieldError::Duplicate(orig_cursor.to_string()))); + } + } + _ => unreachable!() + } + } + } + }, + _ => unreachable!() + } + } + pub fn num_fields(&self) -> usize { self.0.len() } @@ -268,15 +376,15 @@ pub fn writer_from_opts(arg: Option<&str>) -> Result, io::Error> { } -pub fn arg_from_str(arg: &str, err: &mut InvalidOptionsError, - arg_name: &'static str, - arg_type: &'static str) -> T +pub fn arg_from_str<'a, T>(arg: &str, err: &mut InvalidOptionsError, + arg_name: &'a str, + arg_type: &'a str) -> T where T: FromStr + Default, ::Err: fmt::Display { match FromStr::from_str(arg) { Err(perr) => { err.issues.push( - CLIError::ParseError(arg_name, arg_type, arg.to_string(), format!("{}", perr)) + CLIError::ParseError(arg_name.to_owned(), arg_type.to_owned(), arg.to_string(), format!("{}", perr)) ); Default::default() }, @@ -411,6 +519,7 @@ pub enum FieldError { PopOnEmpty(String), TrailingFieldSep(String), Unknown(String, Option, Option), + Duplicate(String), Empty, } @@ -437,6 +546,8 @@ impl fmt::Display for FieldError { }; writeln!(f, "Field '{}' does not exist.{}", field, suffix) }, + FieldError::Duplicate(ref cursor) + => writeln!(f, "Value at '{}' was already set", cursor), FieldError::Empty => writeln!(f, "Field names must not be empty."), } @@ -447,7 +558,7 @@ impl fmt::Display for FieldError { #[derive(Debug)] pub enum CLIError { Configuration(ConfigurationError), - ParseError(&'static str, &'static str, String, String), + ParseError(String, String, String, String), UnknownParameter(String, Vec<&'static str>), InvalidUploadProtocol(String, Vec), InvalidKeyValueSyntax(String, bool), @@ -465,7 +576,7 @@ impl fmt::Display for CLIError { CLIError::Field(ref err) => write!(f, "Field -> {}", err), CLIError::InvalidUploadProtocol(ref proto_name, ref valid_names) => writeln!(f, "'{}' is not a valid upload protocol. Choose from one of {}.", proto_name, valid_names.connect(", ")), - CLIError::ParseError(arg_name, type_name, ref value, ref err_desc) + CLIError::ParseError(ref arg_name, ref type_name, ref value, ref err_desc) => writeln!(f, "Failed to parse argument '{}' with value '{}' as {} with error: {}.", arg_name, value, type_name, err_desc), CLIError::UnknownParameter(ref param_name, ref possible_values) => { diff --git a/gen/discovery1-cli/src/main.rs b/gen/discovery1-cli/src/main.rs index ce64a21fe3..a4ce87df76 100644 --- a/gen/discovery1-cli/src/main.rs +++ b/gen/discovery1-cli/src/main.rs @@ -22,7 +22,7 @@ mod cmn; use cmn::{InvalidOptionsError, CLIError, JsonTokenStorage, arg_from_str, writer_from_opts, parse_kv_arg, input_file_from_opts, input_mime_from_opts, FieldCursor, FieldError, CallType, UploadProtocol, - calltype_from_str, remove_json_null_values}; + calltype_from_str, remove_json_null_values, ComplexType, JsonType, JsonTypeInfo}; use std::default::Default; use std::str::FromStr; @@ -61,9 +61,11 @@ impl<'n, 'a> Engine<'n, 'a> { } } if !found { - err.issues.push(CLIError::UnknownParameter(key.to_string(), - Vec::new() + &self.gp + &[] - )); + err.issues.push(CLIError::UnknownParameter(key.to_string(), + {let mut v = Vec::new(); + v.extend(self.gp.iter().map(|v|*v)); + v.extend([].iter().map(|v|*v)); + v } )); } } } @@ -114,9 +116,11 @@ impl<'n, 'a> Engine<'n, 'a> { } } if !found { - err.issues.push(CLIError::UnknownParameter(key.to_string(), - Vec::new() + &self.gp + &["name", "preferred"] - )); + err.issues.push(CLIError::UnknownParameter(key.to_string(), + {let mut v = Vec::new(); + v.extend(self.gp.iter().map(|v|*v)); + v.extend(["name", "preferred"].iter().map(|v|*v)); + v } )); } } } @@ -332,7 +336,8 @@ fn main() { (_ , &Some(f)) => f, _ => unreachable!(), }; - let mut arg = Arg::with_name(arg_name_str); + let mut arg = Arg::with_name(arg_name_str) + .empty_values(false); if let &Some(short_flag) = flag { arg = arg.short(short_flag); } diff --git a/gen/discovery1/Cargo.toml b/gen/discovery1/Cargo.toml index 250ccd2b04..dad66fb739 100644 --- a/gen/discovery1/Cargo.toml +++ b/gen/discovery1/Cargo.toml @@ -12,13 +12,17 @@ homepage = "https://developers.google.com/discovery/" documentation = "http://byron.github.io/google-apis-rs/google_discovery1" license = "MIT" keywords = ["discovery", "google", "protocol", "web", "api"] +build = "src/build.rs" [dependencies] -hyper = ">= 0.4.0" +hyper = ">= 0.5.0" mime = "*" +serde = ">= 0.3.0" yup-oauth2 = "*" url = "*" -serde = ">= 0.3.0" -serde_macros = "*" -json-tools = ">= 0.3.0" + +[build-dependencies] +syntex = { version = "*" } +serde_codegen = { version = "*" } + diff --git a/gen/discovery1/src/cmn.rs b/gen/discovery1/src/cmn.rs index 908bef39dc..9146b800cb 100644 --- a/gen/discovery1/src/cmn.rs +++ b/gen/discovery1/src/cmn.rs @@ -290,7 +290,7 @@ impl Display for Error { writeln!(f, "The media size {} exceeds the maximum allowed upload size of {}" , resource_size, max_size), Error::MissingAPIKey => { - writeln!(f, "The application's API key was not found in the configuration").ok(); + (writeln!(f, "The application's API key was not found in the configuration")).ok(); writeln!(f, "It is used as there are no Scopes defined for this method.") }, Error::BadRequest(ref err) => { @@ -424,8 +424,8 @@ impl<'a> Read for MultiPartReader<'a> { (n, true, _) if n > 0 => { let (headers, reader) = self.raw_parts.remove(0); let mut c = Cursor::new(Vec::::new()); - write!(&mut c, "{}--{}{}{}{}", LINE_ENDING, BOUNDARY, LINE_ENDING, - headers, LINE_ENDING).unwrap(); + (write!(&mut c, "{}--{}{}{}{}", LINE_ENDING, BOUNDARY, LINE_ENDING, + headers, LINE_ENDING)).unwrap(); c.seek(SeekFrom::Start(0)).unwrap(); self.current_part = Some((c, reader)); } @@ -471,7 +471,7 @@ impl<'a> Read for MultiPartReader<'a> { } } -header!{ +header!( #[doc="The `X-Upload-Content-Type` header."] (XUploadContentType, "X-Upload-Content-Type") => [Mime] @@ -484,7 +484,7 @@ header!{ ))); } -} +); #[derive(Clone, PartialEq, Debug)] pub struct Chunk { @@ -494,7 +494,7 @@ pub struct Chunk { impl fmt::Display for Chunk { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { - write!(fmt, "{}-{}", self.first, self.last).ok(); + (write!(fmt, "{}-{}", self.first, self.last)).ok(); Ok(()) } } @@ -549,7 +549,7 @@ impl HeaderFormat for ContentRange { Some(ref c) => try!(c.fmt(fmt)), None => try!(fmt.write_str("*")) } - write!(fmt, "/{}", self.total_length).ok(); + (write!(fmt, "/{}", self.total_length)).ok(); Ok(()) } } @@ -668,12 +668,14 @@ impl<'a, A> ResumableUploadHelper<'a, A> if self.delegate.cancel_chunk_upload(&range_header) { return None } - match self.client.post(self.url) - .header(range_header) - .header(ContentType(self.media_type.clone())) - .header(UserAgent(self.user_agent.to_string())) - .body(&mut section_reader) - .send() { + // workaround https://github.com/rust-lang/rust/issues/22252 + let res = self.client.post(self.url) + .header(range_header) + .header(ContentType(self.media_type.clone())) + .header(UserAgent(self.user_agent.to_string())) + .body(&mut section_reader) + .send(); + match res { Ok(mut res) => { if res.status == StatusCode::PermanentRedirect { continue @@ -700,4 +702,28 @@ impl<'a, A> ResumableUploadHelper<'a, A> } } } -} \ No newline at end of file +} + +use serde::json::value::Value; +// Copy of src/rust/cli/cmn.rs +// TODO(ST): Allow sharing common code between program types +pub fn remove_json_null_values(value: &mut Value) { + match *value { + Value::Object(ref mut map) => { + let mut for_removal = Vec::new(); + + for (key, mut value) in map.iter_mut() { + if value.is_null() { + for_removal.push(key.clone()); + } else { + remove_json_null_values(&mut value); + } + } + + for key in &for_removal { + map.remove(key); + } + } + _ => {} + } +} diff --git a/gen/discovery1/src/lib.rs b/gen/discovery1/src/lib.rs index 72f0cb0da2..16c41fdd4a 100644 --- a/gen/discovery1/src/lib.rs +++ b/gen/discovery1/src/lib.rs @@ -167,1114 +167,11 @@ //! [google-go-api]: https://github.com/google/google-api-go-client //! //! + // Unused attributes happen thanks to defined, but unused structures // We don't warn about this, as depending on the API, some data structures or facilities are never used. // Instead of pre-determining this, we just disable the lint. It's manually tuned to not have any // unused imports in fully featured APIs. Same with unused_mut ... . #![allow(unused_imports, unused_mut, dead_code)] -// Required for serde annotations -#![feature(custom_derive, custom_attribute, plugin, slice_patterns)] -#![plugin(serde_macros)] - -#[macro_use] -extern crate hyper; -extern crate serde; -extern crate yup_oauth2 as oauth2; -extern crate mime; -extern crate url; -extern crate json_tools; - -mod cmn; - -use std::collections::HashMap; -use std::cell::RefCell; -use std::borrow::BorrowMut; -use std::default::Default; -use std::collections::BTreeMap; -use serde::json; -use std::io; -use std::fs; -use std::thread::sleep_ms; - -pub use cmn::{MultiPartReader, ToParts, MethodInfo, Result, Error, CallBuilder, Hub, ReadSeek, Part, ResponseResult, RequestValue, NestedType, Delegate, DefaultDelegate, MethodsBuilder, Resource, ErrorResponse}; - - -// ############## -// UTILITIES ### -// ############ - - - - -// ######## -// HUB ### -// ###### - -/// Central instance to access all Discovery related resource activities -/// -/// # Examples -/// -/// Instantiate a new hub -/// -/// ```test_harness,no_run -/// extern crate hyper; -/// extern crate yup_oauth2 as oauth2; -/// extern crate google_discovery1 as discovery1; -/// use discovery1::{Result, Error}; -/// # #[test] fn egal() { -/// use std::default::Default; -/// use oauth2::{Authenticator, DefaultAuthenticatorDelegate, ApplicationSecret, MemoryStorage}; -/// use discovery1::Discovery; -/// -/// // Get an ApplicationSecret instance by some means. It contains the `client_id` and -/// // `client_secret`, among other things. -/// let secret: ApplicationSecret = Default::default(); -/// // Instantiate the authenticator. It will choose a suitable authentication flow for you, -/// // unless you replace `None` with the desired Flow. -/// // Provide your own `AuthenticatorDelegate` to adjust the way it operates and get feedback about -/// // what's going on. You probably want to bring in your own `TokenStorage` to persist tokens and -/// // retrieve them from storage. -/// let auth = Authenticator::new(&secret, DefaultAuthenticatorDelegate, -/// hyper::Client::new(), -/// ::default(), None); -/// let mut hub = Discovery::new(hyper::Client::new(), auth); -/// // You can configure optional parameters by calling the respective setters at will, and -/// // execute the final call using `doit()`. -/// // Values shown here are possibly random and not representative ! -/// let result = hub.apis().get_rest("api", "version") -/// .doit(); -/// -/// match result { -/// Err(e) => match e { -/// // The Error enum provides details about what exactly happened. -/// // You can also just use its `Debug`, `Display` or `Error` traits -/// Error::HttpError(_) -/// |Error::MissingAPIKey -/// |Error::MissingToken(_) -/// |Error::Cancelled -/// |Error::UploadSizeLimitExceeded(_, _) -/// |Error::Failure(_) -/// |Error::BadRequest(_) -/// |Error::FieldClash(_) -/// |Error::JsonDecodeError(_, _) => println!("{}", e), -/// }, -/// Ok(res) => println!("Success: {:?}", res), -/// } -/// # } -/// ``` -pub struct Discovery { - client: RefCell, - auth: RefCell, - _user_agent: String, -} - -impl<'a, C, A> Hub for Discovery {} - -impl<'a, C, A> Discovery - where C: BorrowMut, A: oauth2::GetToken { - - pub fn new(client: C, authenticator: A) -> Discovery { - Discovery { - client: RefCell::new(client), - auth: RefCell::new(authenticator), - _user_agent: "google-api-rust-client/0.1.7".to_string(), - } - } - - pub fn apis(&'a self) -> ApiMethods<'a, C, A> { - ApiMethods { hub: &self } - } - - /// Set the user-agent header field to use in all requests to the server. - /// It defaults to `google-api-rust-client/0.1.7`. - /// - /// Returns the previously set user-agent. - pub fn user_agent(&mut self, agent_name: String) -> String { - let prev = self._user_agent.clone(); - self._user_agent = agent_name; - prev - } -} - - -// ############ -// SCHEMAS ### -// ########## -/// OAuth 2.0 authentication information. -/// -/// This type is not used in any activity, and only used as *part* of another schema. -/// -#[derive(Default, Clone, Debug, Serialize, Deserialize)] -pub struct RestDescriptionAuthOauth2 { - /// Available OAuth 2.0 scopes. - pub scopes: Option>, -} - -impl NestedType for RestDescriptionAuthOauth2 {} -impl Part for RestDescriptionAuthOauth2 {} - - -/// The schema for the response. -/// -/// This type is not used in any activity, and only used as *part* of another schema. -/// -#[derive(Default, Clone, Debug, Serialize, Deserialize)] -pub struct RestMethodResponse { - /// Schema ID for the response schema. - #[serde(rename="$ref")] - pub ref_: Option, -} - -impl NestedType for RestMethodResponse {} -impl Part for RestMethodResponse {} - - -/// In a variant data type, the value of one property is used to determine how to interpret the entire entity. Its value must exist in a map of descriminant values to schema names. -/// -/// This type is not used in any activity, and only used as *part* of another schema. -/// -#[derive(Default, Clone, Debug, Serialize, Deserialize)] -pub struct JsonSchemaVariant { - /// The map of discriminant value to schema to use for parsing.. - pub map: Option>, - /// The name of the type discriminant property. - pub discriminant: Option, -} - -impl NestedType for JsonSchemaVariant {} -impl Part for JsonSchemaVariant {} - - -/// Supported upload protocols. -/// -/// This type is not used in any activity, and only used as *part* of another schema. -/// -#[derive(Default, Clone, Debug, Serialize, Deserialize)] -pub struct RestMethodMediaUploadProtocols { - /// Supports uploading as a single HTTP request. - pub simple: Option, - /// Supports the Resumable Media Upload protocol. - pub resumable: Option, -} - -impl NestedType for RestMethodMediaUploadProtocols {} -impl Part for RestMethodMediaUploadProtocols {} - - -/// Supports the Resumable Media Upload protocol. -/// -/// This type is not used in any activity, and only used as *part* of another schema. -/// -#[derive(Default, Clone, Debug, Serialize, Deserialize)] -pub struct RestMethodMediaUploadProtocolsResumable { - /// The URI path to be used for upload. Should be used in conjunction with the basePath property at the api-level. - pub path: Option, - /// True if this endpoint supports uploading multipart media. - pub multipart: Option, -} - -impl NestedType for RestMethodMediaUploadProtocolsResumable {} -impl Part for RestMethodMediaUploadProtocolsResumable {} - - -/// Additional information about this property. -/// -/// This type is not used in any activity, and only used as *part* of another schema. -/// -#[derive(Default, Clone, Debug, Serialize, Deserialize)] -pub struct JsonSchemaAnnotations { - /// A list of methods for which this property is required on requests. - pub required: Option>, -} - -impl NestedType for JsonSchemaAnnotations {} -impl Part for JsonSchemaAnnotations {} - - -/// The map of discriminant value to schema to use for parsing.. -/// -/// This type is not used in any activity, and only used as *part* of another schema. -/// -#[derive(Default, Clone, Debug, Serialize, Deserialize)] -pub struct JsonSchemaVariantMap { - /// no description provided - pub type_value: Option, - /// no description provided - #[serde(rename="$ref")] - pub ref_: Option, -} - -impl NestedType for JsonSchemaVariantMap {} -impl Part for JsonSchemaVariantMap {} - - -/// Links to 16x16 and 32x32 icons representing the API. -/// -/// This type is not used in any activity, and only used as *part* of another schema. -/// -#[derive(Default, Clone, Debug, Serialize, Deserialize)] -pub struct RestDescriptionIcons { - /// The URL of the 32x32 icon. - pub x32: Option, - /// The URL of the 16x16 icon. - pub x16: Option, -} - -impl NestedType for RestDescriptionIcons {} -impl Part for RestDescriptionIcons {} - - -/// There is no detailed description. -/// -/// This type is not used in any activity, and only used as *part* of another schema. -/// -#[derive(Default, Clone, Debug, Serialize, Deserialize)] -pub struct RestMethod { - /// OAuth 2.0 scopes applicable to this method. - pub scopes: Option>, - /// Description of this method. - pub description: Option, - /// Details for all parameters in this method. - pub parameters: Option>, - /// Whether this method supports media uploads. - #[serde(rename="supportsMediaUpload")] - pub supports_media_upload: Option, - /// Whether this method requires an ETag to be specified. The ETag is sent as an HTTP If-Match or If-None-Match header. - #[serde(rename="etagRequired")] - pub etag_required: Option, - /// Media upload parameters. - #[serde(rename="mediaUpload")] - pub media_upload: Option, - /// The schema for the request. - pub request: Option, - /// Indicates that downloads from this method should use the download service URL (i.e. "/download"). Only applies if the method supports media download. - #[serde(rename="useMediaDownloadService")] - pub use_media_download_service: Option, - /// HTTP method used by this method. - #[serde(rename="httpMethod")] - pub http_method: Option, - /// Whether this method supports subscriptions. - #[serde(rename="supportsSubscription")] - pub supports_subscription: Option, - /// Ordered list of required parameters, serves as a hint to clients on how to structure their method signatures. The array is ordered such that the "most-significant" parameter appears first. - #[serde(rename="parameterOrder")] - pub parameter_order: Option>, - /// A unique ID for this method. This property can be used to match methods between different versions of Discovery. - pub id: Option, - /// The URI path of this REST method. Should be used in conjunction with the basePath property at the api-level. - pub path: Option, - /// The schema for the response. - pub response: Option, - /// Whether this method supports media downloads. - #[serde(rename="supportsMediaDownload")] - pub supports_media_download: Option, -} - -impl Part for RestMethod {} - - -/// There is no detailed description. -/// -/// # Activities -/// -/// This type is used in activities, which are methods you may call on this type or where this type is involved in. -/// The list links the activity name, along with information about where it is used (one of *request* and *response*). -/// -/// * [get rest apis](struct.ApiGetRestCall.html) (response) -/// -#[derive(Default, Clone, Debug, Serialize, Deserialize)] -pub struct RestDescription { - /// The protocol described by this document. - pub protocol: Option, - /// API-level methods for this API. - pub methods: Option>, - /// Labels for the status of this API, such as labs or deprecated. - pub labels: Option>, - /// The kind for this response. - pub kind: Option, - /// Indicates how the API name should be capitalized and split into various parts. Useful for generating pretty class names. - #[serde(rename="canonicalName")] - pub canonical_name: Option, - /// The name of the owner of this API. See ownerDomain. - #[serde(rename="ownerName")] - pub owner_name: Option, - /// A link to human readable documentation for the API. - #[serde(rename="documentationLink")] - pub documentation_link: Option, - /// Authentication information. - pub auth: Option, - /// The package of the owner of this API. See ownerDomain. - #[serde(rename="packagePath")] - pub package_path: Option, - /// The path for REST batch requests. - #[serde(rename="batchPath")] - pub batch_path: Option, - /// The ID of this API. - pub id: Option, - /// A list of supported features for this API. - pub features: Option>, - /// The domain of the owner of this API. Together with the ownerName and a packagePath values, this can be used to generate a library for this API which would have a unique fully qualified name. - #[serde(rename="ownerDomain")] - pub owner_domain: Option, - /// The root URL under which all API services live. - #[serde(rename="rootUrl")] - pub root_url: Option, - /// The name of this API. - pub name: Option, - /// Common parameters that apply across all apis. - pub parameters: Option>, - /// Links to 16x16 and 32x32 icons representing the API. - pub icons: Option, - /// The description of this API. - pub description: Option, - /// The title of this API. - pub title: Option, - /// [DEPRECATED] The base URL for REST requests. - #[serde(rename="baseUrl")] - pub base_url: Option, - /// The ETag for this response. - pub etag: Option, - /// The version of this API. - pub version: Option, - /// The base path for all REST requests. - #[serde(rename="servicePath")] - pub service_path: Option, - /// Indicate the version of the Discovery API used to generate this doc. - #[serde(rename="discoveryVersion")] - pub discovery_version: Option, - /// The schemas for this API. - pub schemas: Option>, - /// [DEPRECATED] The base path for REST requests. - #[serde(rename="basePath")] - pub base_path: Option, - /// The resources in this API. - pub resources: Option>, - /// The version of this API. - pub revision: Option, -} - -impl ResponseResult for RestDescription {} - - -/// Media upload parameters. -/// -/// This type is not used in any activity, and only used as *part* of another schema. -/// -#[derive(Default, Clone, Debug, Serialize, Deserialize)] -pub struct RestMethodMediaUpload { - /// Maximum size of a media upload, such as "1MB", "2GB" or "3TB". - #[serde(rename="maxSize")] - pub max_size: Option, - /// MIME Media Ranges for acceptable media uploads to this method. - pub accept: Option>, - /// Supported upload protocols. - pub protocols: Option, -} - -impl NestedType for RestMethodMediaUpload {} -impl Part for RestMethodMediaUpload {} - - -/// There is no detailed description. -/// -/// # Activities -/// -/// This type is used in activities, which are methods you may call on this type or where this type is involved in. -/// The list links the activity name, along with information about where it is used (one of *request* and *response*). -/// -/// * [list apis](struct.ApiListCall.html) (response) -/// -#[derive(Default, Clone, Debug, Serialize, Deserialize)] -pub struct DirectoryList { - /// The individual directory entries. One entry per api/version pair. - pub items: Option>, - /// Indicate the version of the Discovery API used to generate this doc. - #[serde(rename="discoveryVersion")] - pub discovery_version: Option, - /// The kind for this response. - pub kind: Option, -} - -impl ResponseResult for DirectoryList {} - - -/// There is no detailed description. -/// -/// This type is not used in any activity, and only used as *part* of another schema. -/// -#[derive(Default, Clone, Debug, Serialize, Deserialize)] -pub struct JsonSchema { - /// A description of this object. - pub description: Option, - /// An additional regular expression or key that helps constrain the value. For more details see: http://tools.ietf.org/html/draft-zyp-json-schema-03#section-5.23 - pub format: Option, - /// Values this parameter may take (if it is an enum). - #[serde(rename="enum")] - pub enum_: Option>, - /// In a variant data type, the value of one property is used to determine how to interpret the entire entity. Its value must exist in a map of descriminant values to schema names. - pub variant: Option, - /// The descriptions for the enums. Each position maps to the corresponding value in the "enum" array. - #[serde(rename="enumDescriptions")] - pub enum_descriptions: Option>, - /// The value is read-only, generated by the service. The value cannot be modified by the client. If the value is included in a POST, PUT, or PATCH request, it is ignored by the service. - #[serde(rename="readOnly")] - pub read_only: Option, - /// The minimum value of this parameter. - pub minimum: Option, - /// Whether this parameter may appear multiple times. - pub repeated: Option, - /// Unique identifier for this schema. - pub id: Option, - /// A reference to another schema. The value of this property is the "id" of another schema. - #[serde(rename="$ref")] - pub ref_: Option, - /// The default value of this property (if one exists). - pub default: Option, - /// If this is a schema for an array, this property is the schema for each element in the array. - pub items: Option>>, - /// Whether the parameter is required. - pub required: Option, - /// The maximum value of this parameter. - pub maximum: Option, - /// If this is a schema for an object, list the schema for each property of this object. - pub properties: Option>, - /// Whether this parameter goes in the query or the path for REST requests. - pub location: Option, - /// The regular expression this parameter must conform to. Uses Java 6 regex format: http://docs.oracle.com/javase/6/docs/api/java/util/regex/Pattern.html - pub pattern: Option, - /// If this is a schema for an object, this property is the schema for any additional properties with dynamic keys on this object. - #[serde(rename="additionalProperties")] - pub additional_properties: Option>>, - /// The value type for this schema. A list of values can be found here: http://tools.ietf.org/html/draft-zyp-json-schema-03#section-5.1 - #[serde(rename="type")] - pub type_: Option, - /// Additional information about this property. - pub annotations: Option, -} - -impl Part for JsonSchema {} - - -/// The individual directory entries. One entry per api/version pair. -/// -/// This type is not used in any activity, and only used as *part* of another schema. -/// -#[derive(Default, Clone, Debug, Serialize, Deserialize)] -pub struct DirectoryListItems { - /// The kind for this response. - pub kind: Option, - /// The URL for the discovery REST document. - #[serde(rename="discoveryRestUrl")] - pub discovery_rest_url: Option, - /// The description of this API. - pub description: Option, - /// Links to 16x16 and 32x32 icons representing the API. - pub icons: Option, - /// Labels for the status of this API, such as labs or deprecated. - pub labels: Option>, - /// True if this version is the preferred version to use. - pub preferred: Option, - /// A link to the discovery document. - #[serde(rename="discoveryLink")] - pub discovery_link: Option, - /// The version of the API. - pub version: Option, - /// The title of this API. - pub title: Option, - /// A link to human readable documentation for the API. - #[serde(rename="documentationLink")] - pub documentation_link: Option, - /// The id of this API. - pub id: Option, - /// The name of the API. - pub name: Option, -} - -impl NestedType for DirectoryListItems {} -impl Part for DirectoryListItems {} - - -/// Authentication information. -/// -/// This type is not used in any activity, and only used as *part* of another schema. -/// -#[derive(Default, Clone, Debug, Serialize, Deserialize)] -pub struct RestDescriptionAuth { - /// OAuth 2.0 authentication information. - pub oauth2: Option, -} - -impl NestedType for RestDescriptionAuth {} -impl Part for RestDescriptionAuth {} - - -/// The scope value. -/// -/// This type is not used in any activity, and only used as *part* of another schema. -/// -#[derive(Default, Clone, Debug, Serialize, Deserialize)] -pub struct RestDescriptionAuthOauth2Scopes { - /// Description of scope. - pub description: Option, -} - -impl NestedType for RestDescriptionAuthOauth2Scopes {} -impl Part for RestDescriptionAuthOauth2Scopes {} - - -/// Links to 16x16 and 32x32 icons representing the API. -/// -/// This type is not used in any activity, and only used as *part* of another schema. -/// -#[derive(Default, Clone, Debug, Serialize, Deserialize)] -pub struct DirectoryListItemsIcons { - /// The URL of the 32x32 icon. - pub x32: Option, - /// The URL of the 16x16 icon. - pub x16: Option, -} - -impl NestedType for DirectoryListItemsIcons {} -impl Part for DirectoryListItemsIcons {} - - -/// The schema for the request. -/// -/// This type is not used in any activity, and only used as *part* of another schema. -/// -#[derive(Default, Clone, Debug, Serialize, Deserialize)] -pub struct RestMethodRequest { - /// parameter name. - #[serde(rename="parameterName")] - pub parameter_name: Option, - /// Schema ID for the request schema. - #[serde(rename="$ref")] - pub ref_: Option, -} - -impl NestedType for RestMethodRequest {} -impl Part for RestMethodRequest {} - - -/// There is no detailed description. -/// -/// This type is not used in any activity, and only used as *part* of another schema. -/// -#[derive(Default, Clone, Debug, Serialize, Deserialize)] -pub struct RestResource { - /// Methods on this resource. - pub methods: Option>, - /// Sub-resources on this resource. - pub resources: Option>, -} - -impl Part for RestResource {} - - -/// Supports uploading as a single HTTP request. -/// -/// This type is not used in any activity, and only used as *part* of another schema. -/// -#[derive(Default, Clone, Debug, Serialize, Deserialize)] -pub struct RestMethodMediaUploadProtocolsSimple { - /// The URI path to be used for upload. Should be used in conjunction with the basePath property at the api-level. - pub path: Option, - /// True if this endpoint supports upload multipart media. - pub multipart: Option, -} - -impl NestedType for RestMethodMediaUploadProtocolsSimple {} -impl Part for RestMethodMediaUploadProtocolsSimple {} - - - -// ################### -// MethodBuilders ### -// ################# - -/// A builder providing access to all methods supported on *api* resources. -/// It is not used directly, but through the `Discovery` hub. -/// -/// # Example -/// -/// Instantiate a resource builder -/// -/// ```test_harness,no_run -/// extern crate hyper; -/// extern crate yup_oauth2 as oauth2; -/// extern crate google_discovery1 as discovery1; -/// -/// # #[test] fn egal() { -/// use std::default::Default; -/// use oauth2::{Authenticator, DefaultAuthenticatorDelegate, ApplicationSecret, MemoryStorage}; -/// use discovery1::Discovery; -/// -/// let secret: ApplicationSecret = Default::default(); -/// let auth = Authenticator::new(&secret, DefaultAuthenticatorDelegate, -/// hyper::Client::new(), -/// ::default(), None); -/// let mut hub = Discovery::new(hyper::Client::new(), auth); -/// // Usually you wouldn't bind this to a variable, but keep calling *CallBuilders* -/// // like `get_rest(...)` and `list(...)` -/// // to build up your call. -/// let rb = hub.apis(); -/// # } -/// ``` -pub struct ApiMethods<'a, C, A> - where C: 'a, A: 'a { - - hub: &'a Discovery, -} - -impl<'a, C, A> MethodsBuilder for ApiMethods<'a, C, A> {} - -impl<'a, C, A> ApiMethods<'a, C, A> { - - /// Create a builder to help you perform the following task: - /// - /// Retrieve the description of a particular version of an api. - /// - /// # Arguments - /// - /// * `api` - The name of the API. - /// * `version` - The version of the API. - pub fn get_rest(&self, api: &str, version: &str) -> ApiGetRestCall<'a, C, A> { - ApiGetRestCall { - hub: self.hub, - _api: api.to_string(), - _version: version.to_string(), - _delegate: Default::default(), - _additional_params: Default::default(), - } - } - - /// Create a builder to help you perform the following task: - /// - /// Retrieve the list of APIs supported at this endpoint. - pub fn list(&self) -> ApiListCall<'a, C, A> { - ApiListCall { - hub: self.hub, - _preferred: Default::default(), - _name: Default::default(), - _delegate: Default::default(), - _additional_params: Default::default(), - } - } -} - - - - - -// ################### -// CallBuilders ### -// ################# - -/// Retrieve the description of a particular version of an api. -/// -/// A builder for the *getRest* method supported by a *api* resource. -/// It is not used directly, but through a `ApiMethods` instance. -/// -/// # Example -/// -/// Instantiate a resource method builder -/// -/// ```test_harness,no_run -/// # extern crate hyper; -/// # extern crate yup_oauth2 as oauth2; -/// # extern crate google_discovery1 as discovery1; -/// # #[test] fn egal() { -/// # use std::default::Default; -/// # use oauth2::{Authenticator, DefaultAuthenticatorDelegate, ApplicationSecret, MemoryStorage}; -/// # use discovery1::Discovery; -/// -/// # let secret: ApplicationSecret = Default::default(); -/// # let auth = Authenticator::new(&secret, DefaultAuthenticatorDelegate, -/// # hyper::Client::new(), -/// # ::default(), None); -/// # let mut hub = Discovery::new(hyper::Client::new(), auth); -/// // You can configure optional parameters by calling the respective setters at will, and -/// // execute the final call using `doit()`. -/// // Values shown here are possibly random and not representative ! -/// let result = hub.apis().get_rest("api", "version") -/// .doit(); -/// # } -/// ``` -pub struct ApiGetRestCall<'a, C, A> - where C: 'a, A: 'a { - - hub: &'a Discovery, - _api: String, - _version: String, - _delegate: Option<&'a mut Delegate>, - _additional_params: HashMap, -} - -impl<'a, C, A> CallBuilder for ApiGetRestCall<'a, C, A> {} - -impl<'a, C, A> ApiGetRestCall<'a, C, A> where C: BorrowMut, A: oauth2::GetToken { - - - /// Perform the operation you have build so far. - pub fn doit(mut self) -> Result<(hyper::client::Response, RestDescription)> { - use std::io::{Read, Seek}; - use hyper::header::{ContentType, ContentLength, Authorization, UserAgent, Location}; - let mut dd = DefaultDelegate; - let mut dlg: &mut Delegate = match self._delegate { - Some(d) => d, - None => &mut dd - }; - dlg.begin(MethodInfo { id: "discovery.apis.getRest", - http_method: hyper::method::Method::Get }); - let mut params: Vec<(&str, String)> = Vec::with_capacity((4 + self._additional_params.len())); - params.push(("api", self._api.to_string())); - params.push(("version", self._version.to_string())); - for &field in ["alt", "api", "version"].iter() { - if self._additional_params.contains_key(field) { - dlg.finished(false); - return Err(Error::FieldClash(field)); - } - } - for (name, value) in self._additional_params.iter() { - params.push((&name, value.clone())); - } - - params.push(("alt", "json".to_string())); - - let mut url = "https://www.googleapis.com/discovery/v1/apis/{api}/{version}/rest".to_string(); - - for &(find_this, param_name) in [("{api}", "api"), ("{version}", "version")].iter() { - let mut replace_with: Option<&str> = None; - for &(name, ref value) in params.iter() { - if name == param_name { - replace_with = Some(value); - break; - } - } - url = url.replace(find_this, replace_with.expect("to find substitution value in params")); - } - { - let mut indices_for_removal: Vec = Vec::with_capacity(2); - for param_name in ["api", "version"].iter() { - for (index, &(ref name, _)) in params.iter().rev().enumerate() { - if name == param_name { - indices_for_removal.push(params.len() - index - 1); - break; - } - } - } - for &index in indices_for_removal.iter() { - params.remove(index); - } - } - - if params.len() > 0 { - url.push('?'); - url.push_str(&url::form_urlencoded::serialize(params)); - } - - - - loop { - let mut req_result = { - let mut client = &mut *self.hub.client.borrow_mut(); - let mut req = client.borrow_mut().request(hyper::method::Method::Get, &url) - .header(UserAgent(self.hub._user_agent.clone())); - - dlg.pre_request(); - req.send() - }; - - match req_result { - Err(err) => { - if let oauth2::Retry::After(d) = dlg.http_error(&err) { - sleep_ms(d.num_milliseconds() as u32); - continue; - } - dlg.finished(false); - return Err(Error::HttpError(err)) - } - Ok(mut res) => { - if !res.status.is_success() { - let mut json_err = String::new(); - res.read_to_string(&mut json_err).unwrap(); - if let oauth2::Retry::After(d) = dlg.http_failure(&res, - json::from_str(&json_err).ok(), - json::from_str(&json_err).ok()) { - sleep_ms(d.num_milliseconds() as u32); - continue; - } - dlg.finished(false); - return match json::from_str::(&json_err){ - Err(_) => Err(Error::Failure(res)), - Ok(serr) => Err(Error::BadRequest(serr)) - } - } - let result_value = { - let mut json_response = String::new(); - res.read_to_string(&mut json_response).unwrap(); - match json::from_str(&json_response) { - Ok(decoded) => (res, decoded), - Err(err) => { - dlg.response_json_decode_error(&json_response, &err); - return Err(Error::JsonDecodeError(json_response, err)); - } - } - }; - - dlg.finished(true); - return Ok(result_value) - } - } - } - } - - - /// The name of the API. - /// - /// Sets the *api* path property to the given value. - /// - /// Even though the property as already been set when instantiating this call, - /// we provide this method for API completeness. - pub fn api(mut self, new_value: &str) -> ApiGetRestCall<'a, C, A> { - self._api = new_value.to_string(); - self - } - /// The version of the API. - /// - /// Sets the *version* path property to the given value. - /// - /// Even though the property as already been set when instantiating this call, - /// we provide this method for API completeness. - pub fn version(mut self, new_value: &str) -> ApiGetRestCall<'a, C, A> { - self._version = new_value.to_string(); - self - } - /// The delegate implementation is consulted whenever there is an intermediate result, or if something goes wrong - /// while executing the actual API request. - /// - /// It should be used to handle progress information, and to implement a certain level of resilience. - /// - /// Sets the *delegate* property to the given value. - pub fn delegate(mut self, new_value: &'a mut Delegate) -> ApiGetRestCall<'a, C, A> { - self._delegate = Some(new_value); - self - } - - /// Set any additional parameter of the query string used in the request. - /// It should be used to set parameters which are not yet available through their own - /// setters. - /// - /// Please note that this method must not be used to set any of the known paramters - /// which have their own setter method. If done anyway, the request will fail. - /// - /// # Additional Parameters - /// - /// * *quotaUser* (query-string) - Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. Overrides userIp if both are provided. - /// * *oauth_token* (query-string) - OAuth 2.0 token for the current user. - /// * *key* (query-string) - API key. Your API key identifies your project and provides you with API access, quota, and reports. Required unless you provide an OAuth 2.0 token. - /// * *prettyPrint* (query-boolean) - Returns response with indentations and line breaks. - /// * *userIp* (query-string) - IP address of the site where the request originates. Use this if you want to enforce per-user limits. - /// * *fields* (query-string) - Selector specifying which fields to include in a partial response. - /// * *alt* (query-string) - Data format for the response. - pub fn param(mut self, name: T, value: T) -> ApiGetRestCall<'a, C, A> - where T: AsRef { - self._additional_params.insert(name.as_ref().to_string(), value.as_ref().to_string()); - self - } - -} - - -/// Retrieve the list of APIs supported at this endpoint. -/// -/// A builder for the *list* method supported by a *api* resource. -/// It is not used directly, but through a `ApiMethods` instance. -/// -/// # Example -/// -/// Instantiate a resource method builder -/// -/// ```test_harness,no_run -/// # extern crate hyper; -/// # extern crate yup_oauth2 as oauth2; -/// # extern crate google_discovery1 as discovery1; -/// # #[test] fn egal() { -/// # use std::default::Default; -/// # use oauth2::{Authenticator, DefaultAuthenticatorDelegate, ApplicationSecret, MemoryStorage}; -/// # use discovery1::Discovery; -/// -/// # let secret: ApplicationSecret = Default::default(); -/// # let auth = Authenticator::new(&secret, DefaultAuthenticatorDelegate, -/// # hyper::Client::new(), -/// # ::default(), None); -/// # let mut hub = Discovery::new(hyper::Client::new(), auth); -/// // You can configure optional parameters by calling the respective setters at will, and -/// // execute the final call using `doit()`. -/// // Values shown here are possibly random and not representative ! -/// let result = hub.apis().list() -/// .preferred(true) -/// .name("justo") -/// .doit(); -/// # } -/// ``` -pub struct ApiListCall<'a, C, A> - where C: 'a, A: 'a { - - hub: &'a Discovery, - _preferred: Option, - _name: Option, - _delegate: Option<&'a mut Delegate>, - _additional_params: HashMap, -} - -impl<'a, C, A> CallBuilder for ApiListCall<'a, C, A> {} - -impl<'a, C, A> ApiListCall<'a, C, A> where C: BorrowMut, A: oauth2::GetToken { - - - /// Perform the operation you have build so far. - pub fn doit(mut self) -> Result<(hyper::client::Response, DirectoryList)> { - use std::io::{Read, Seek}; - use hyper::header::{ContentType, ContentLength, Authorization, UserAgent, Location}; - let mut dd = DefaultDelegate; - let mut dlg: &mut Delegate = match self._delegate { - Some(d) => d, - None => &mut dd - }; - dlg.begin(MethodInfo { id: "discovery.apis.list", - http_method: hyper::method::Method::Get }); - let mut params: Vec<(&str, String)> = Vec::with_capacity((4 + self._additional_params.len())); - if let Some(value) = self._preferred { - params.push(("preferred", value.to_string())); - } - if let Some(value) = self._name { - params.push(("name", value.to_string())); - } - for &field in ["alt", "preferred", "name"].iter() { - if self._additional_params.contains_key(field) { - dlg.finished(false); - return Err(Error::FieldClash(field)); - } - } - for (name, value) in self._additional_params.iter() { - params.push((&name, value.clone())); - } - - params.push(("alt", "json".to_string())); - - let mut url = "https://www.googleapis.com/discovery/v1/apis".to_string(); - - - if params.len() > 0 { - url.push('?'); - url.push_str(&url::form_urlencoded::serialize(params)); - } - - - - loop { - let mut req_result = { - let mut client = &mut *self.hub.client.borrow_mut(); - let mut req = client.borrow_mut().request(hyper::method::Method::Get, &url) - .header(UserAgent(self.hub._user_agent.clone())); - - dlg.pre_request(); - req.send() - }; - - match req_result { - Err(err) => { - if let oauth2::Retry::After(d) = dlg.http_error(&err) { - sleep_ms(d.num_milliseconds() as u32); - continue; - } - dlg.finished(false); - return Err(Error::HttpError(err)) - } - Ok(mut res) => { - if !res.status.is_success() { - let mut json_err = String::new(); - res.read_to_string(&mut json_err).unwrap(); - if let oauth2::Retry::After(d) = dlg.http_failure(&res, - json::from_str(&json_err).ok(), - json::from_str(&json_err).ok()) { - sleep_ms(d.num_milliseconds() as u32); - continue; - } - dlg.finished(false); - return match json::from_str::(&json_err){ - Err(_) => Err(Error::Failure(res)), - Ok(serr) => Err(Error::BadRequest(serr)) - } - } - let result_value = { - let mut json_response = String::new(); - res.read_to_string(&mut json_response).unwrap(); - match json::from_str(&json_response) { - Ok(decoded) => (res, decoded), - Err(err) => { - dlg.response_json_decode_error(&json_response, &err); - return Err(Error::JsonDecodeError(json_response, err)); - } - } - }; - - dlg.finished(true); - return Ok(result_value) - } - } - } - } - - - /// Return only the preferred version of an API. - /// - /// Sets the *preferred* query property to the given value. - pub fn preferred(mut self, new_value: bool) -> ApiListCall<'a, C, A> { - self._preferred = Some(new_value); - self - } - /// Only include APIs with the given name. - /// - /// Sets the *name* query property to the given value. - pub fn name(mut self, new_value: &str) -> ApiListCall<'a, C, A> { - self._name = Some(new_value.to_string()); - self - } - /// The delegate implementation is consulted whenever there is an intermediate result, or if something goes wrong - /// while executing the actual API request. - /// - /// It should be used to handle progress information, and to implement a certain level of resilience. - /// - /// Sets the *delegate* property to the given value. - pub fn delegate(mut self, new_value: &'a mut Delegate) -> ApiListCall<'a, C, A> { - self._delegate = Some(new_value); - self - } - - /// Set any additional parameter of the query string used in the request. - /// It should be used to set parameters which are not yet available through their own - /// setters. - /// - /// Please note that this method must not be used to set any of the known paramters - /// which have their own setter method. If done anyway, the request will fail. - /// - /// # Additional Parameters - /// - /// * *quotaUser* (query-string) - Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. Overrides userIp if both are provided. - /// * *oauth_token* (query-string) - OAuth 2.0 token for the current user. - /// * *key* (query-string) - API key. Your API key identifies your project and provides you with API access, quota, and reports. Required unless you provide an OAuth 2.0 token. - /// * *prettyPrint* (query-boolean) - Returns response with indentations and line breaks. - /// * *userIp* (query-string) - IP address of the site where the request originates. Use this if you want to enforce per-user limits. - /// * *fields* (query-string) - Selector specifying which fields to include in a partial response. - /// * *alt* (query-string) - Data format for the response. - pub fn param(mut self, name: T, value: T) -> ApiListCall<'a, C, A> - where T: AsRef { - self._additional_params.insert(name.as_ref().to_string(), value.as_ref().to_string()); - self - } - -} - +include!(concat!(env!("OUT_DIR"), "/lib.rs")); \ No newline at end of file diff --git a/src/mako/Cargo.toml.mako b/src/mako/Cargo.toml.mako index 36da136bed..9eebc4adb4 100644 --- a/src/mako/Cargo.toml.mako +++ b/src/mako/Cargo.toml.mako @@ -16,6 +16,7 @@ homepage = "${documentationLink}" documentation = "${cargo.doc_base_url}/${to_extern_crate_name(util.crate_name())}" license = "${copyright.license_abbrev}" keywords = ["${name[:20]}", ${", ".join(estr(cargo.keywords))}] +build = "src/build.rs" % if cargo.get('is_executable', False): [[bin]] @@ -26,16 +27,14 @@ name = "${util.program_name()}" hyper = ">= 0.5.0" mime = "*" serde = ">= 0.3.0" -serde_macros = "*" +yup-oauth2 = "*" % for dep in cargo.get('dependencies', list()): ${dep} % endfor -# to adapt to hyper changes ... -[dependencies.yup-oauth2] -version = "*" -git = "https://github.com/Byron/yup-oauth2" -rev = "598f5ed496077e9edca36ff95e2ab352c5b22d0f" +[build-dependencies] +syntex = { version = "*" } +serde_codegen = { version = "*" } % if make.depends_on_suffix is not None: diff --git a/src/mako/api/build.rs.mako b/src/mako/api/build.rs.mako new file mode 100644 index 0000000000..03dd77aa65 --- /dev/null +++ b/src/mako/api/build.rs.mako @@ -0,0 +1,17 @@ +<%namespace name="util" file="../lib/util.mako"/>\ +extern crate syntex; +extern crate serde_codegen; + +use std::env; +use std::path::Path; + +pub fn main() { + let out_dir = env::var_os("OUT_DIR").unwrap(); + + let src = Path::new("src/lib.rs.in"); + let dst = Path::new(&out_dir).join("lib.rs"); + + let mut registry = syntex::Registry::new(); + serde_codegen::register(&mut registry); + registry.expand("${util.crate_name()}", &src, &dst).unwrap(); +} diff --git a/src/mako/api/lib.rs.in.mako b/src/mako/api/lib.rs.in.mako new file mode 100644 index 0000000000..22bc67aa8f --- /dev/null +++ b/src/mako/api/lib.rs.in.mako @@ -0,0 +1,134 @@ +<%namespace name="lib" file="lib/lib.mako"/>\ +<%namespace name="util" file="../lib/util.mako"/>\ +<%namespace name="rbuild" file="lib/rbuild.mako"/>\ +<%namespace name="mbuild" file="lib/mbuild.mako"/>\ +<%namespace name="schema" file="lib/schema.mako"/>\ +<% + from util import (new_context, rust_comment, rust_doc_comment, rust_module_doc_comment, + rb_type, hub_type, mangle_ident, hub_type_params_s, hub_type_bounds, + rb_type_params_s, find_fattest_resource, HUB_TYPE_PARAMETERS, METHODS_RESOURCE, + UNUSED_TYPE_MARKER, schema_markers) + + c = new_context(schemas, resources, context.get('methods')) + hub_type = hub_type(c.schemas, util.canonical_name()) + ht_params = hub_type_params_s() + + default_user_agent = "google-api-rust-client/" + cargo.build_version +%>\ +<%block filter="rust_comment">\ +<%util:gen_info source="${self.uri}" />\ + + +#[macro_use] +extern crate hyper; +extern crate serde; +extern crate yup_oauth2 as oauth2; +extern crate mime; +extern crate url; + +mod cmn; + +use std::collections::HashMap; +use std::cell::RefCell; +use std::borrow::BorrowMut; +use std::default::Default; +use std::collections::BTreeMap; +use serde::json; +use std::io; +use std::fs; +use std::thread::sleep_ms; + +pub use cmn::{MultiPartReader, ToParts, MethodInfo, Result, Error, CallBuilder, Hub, ReadSeek, Part, + ResponseResult, RequestValue, NestedType, Delegate, DefaultDelegate, MethodsBuilder, + Resource, ErrorResponse, remove_json_null_values}; + + +// ############## +// UTILITIES ### +// ############ + +${lib.scope_enum()} + + +// ######## +// HUB ### +// ###### + +/// Central instance to access all ${hub_type} related resource activities +/// +/// # Examples +/// +/// Instantiate a new hub +/// +<%block filter="rust_doc_comment">\ +${lib.hub_usage_example(c)}\ + +pub struct ${hub_type}${ht_params} { + client: RefCell, + auth: RefCell, + _user_agent: String, +} + +impl<'a, ${', '.join(HUB_TYPE_PARAMETERS)}> Hub for ${hub_type}${ht_params} {} + +impl<'a, ${', '.join(HUB_TYPE_PARAMETERS)}> ${hub_type}${ht_params} + where ${', '.join(hub_type_bounds())} { + + pub fn new(client: C, authenticator: A) -> ${hub_type}${ht_params} { + ${hub_type} { + client: RefCell::new(client), + auth: RefCell::new(authenticator), + _user_agent: "${default_user_agent}".to_string(), + } + } + + % for resource in sorted(c.rta_map.keys()): + pub fn ${mangle_ident(resource)}(&'a self) -> ${rb_type(resource)}${rb_type_params_s(resource, c)} { + ${rb_type(resource)} { hub: &self } + } + % endfor + + /// Set the user-agent header field to use in all requests to the server. + /// It defaults to `${default_user_agent}`. + /// + /// Returns the previously set user-agent. + pub fn user_agent(&mut self, agent_name: String) -> String { + let prev = self._user_agent.clone(); + self._user_agent = agent_name; + prev + } +} + + +% if c.schemas: +// ############ +// SCHEMAS ### +// ########## +% for s in c.schemas.values(): +% if UNUSED_TYPE_MARKER not in schema_markers(s, c, transitive=True): +${schema.new(s, c)} +% endif +% endfor +% endif + +// ################### +// MethodBuilders ### +// ################# + +% for resource in c.rta_map: +${rbuild.new(resource, c)} + + +% endfor + + +// ################### +// CallBuilders ### +// ################# + +% for resource, methods in c.rta_map.iteritems(): +% for method in methods: +${mbuild.new(resource, method, c)} + +% endfor ## method in methods +% endfor ## resource, methods \ No newline at end of file diff --git a/src/mako/api/lib.rs.mako b/src/mako/api/lib.rs.mako index b485218f39..b3466da3e6 100644 --- a/src/mako/api/lib.rs.mako +++ b/src/mako/api/lib.rs.mako @@ -1,19 +1,9 @@ <%namespace name="lib" file="lib/lib.mako"/>\ <%namespace name="util" file="../lib/util.mako"/>\ -<%namespace name="rbuild" file="lib/rbuild.mako"/>\ -<%namespace name="mbuild" file="lib/mbuild.mako"/>\ -<%namespace name="schema" file="lib/schema.mako"/>\ <% - from util import (new_context, rust_comment, rust_doc_comment, rust_module_doc_comment, - rb_type, hub_type, mangle_ident, hub_type_params_s, hub_type_bounds, - rb_type_params_s, find_fattest_resource, HUB_TYPE_PARAMETERS, METHODS_RESOURCE, - UNUSED_TYPE_MARKER, schema_markers) + from util import (new_context, rust_comment, rust_module_doc_comment) c = new_context(schemas, resources, context.get('methods')) - hub_type = hub_type(c.schemas, util.canonical_name()) - ht_params = hub_type_params_s() - - default_user_agent = "google-api-rust-client/" + cargo.build_version %>\ <%block filter="rust_comment">\ <%util:gen_info source="${self.uri}" />\ @@ -22,124 +12,11 @@ <%block filter="rust_module_doc_comment">\ ${lib.docs(c)} + // Unused attributes happen thanks to defined, but unused structures // We don't warn about this, as depending on the API, some data structures or facilities are never used. // Instead of pre-determining this, we just disable the lint. It's manually tuned to not have any // unused imports in fully featured APIs. Same with unused_mut ... . #![allow(unused_imports, unused_mut, dead_code)] -// Required for serde annotations -#![feature(custom_derive, custom_attribute, plugin, slice_patterns)] -#![plugin(serde_macros)] -#[macro_use] -extern crate hyper; -extern crate serde; -extern crate yup_oauth2 as oauth2; -extern crate mime; -extern crate url; -extern crate json_tools; - -mod cmn; - -use std::collections::HashMap; -use std::cell::RefCell; -use std::borrow::BorrowMut; -use std::default::Default; -use std::collections::BTreeMap; -use serde::json; -use std::io; -use std::fs; -use std::thread::sleep_ms; - -pub use cmn::{MultiPartReader, ToParts, MethodInfo, Result, Error, CallBuilder, Hub, ReadSeek, Part, ResponseResult, RequestValue, NestedType, Delegate, DefaultDelegate, MethodsBuilder, Resource, ErrorResponse}; - - -// ############## -// UTILITIES ### -// ############ - -${lib.scope_enum()} - - -// ######## -// HUB ### -// ###### - -/// Central instance to access all ${hub_type} related resource activities -/// -/// # Examples -/// -/// Instantiate a new hub -/// -<%block filter="rust_doc_comment">\ -${lib.hub_usage_example(c)}\ - -pub struct ${hub_type}${ht_params} { - client: RefCell, - auth: RefCell, - _user_agent: String, -} - -impl<'a, ${', '.join(HUB_TYPE_PARAMETERS)}> Hub for ${hub_type}${ht_params} {} - -impl<'a, ${', '.join(HUB_TYPE_PARAMETERS)}> ${hub_type}${ht_params} - where ${', '.join(hub_type_bounds())} { - - pub fn new(client: C, authenticator: A) -> ${hub_type}${ht_params} { - ${hub_type} { - client: RefCell::new(client), - auth: RefCell::new(authenticator), - _user_agent: "${default_user_agent}".to_string(), - } - } - - % for resource in sorted(c.rta_map.keys()): - pub fn ${mangle_ident(resource)}(&'a self) -> ${rb_type(resource)}${rb_type_params_s(resource, c)} { - ${rb_type(resource)} { hub: &self } - } - % endfor - - /// Set the user-agent header field to use in all requests to the server. - /// It defaults to `${default_user_agent}`. - /// - /// Returns the previously set user-agent. - pub fn user_agent(&mut self, agent_name: String) -> String { - let prev = self._user_agent.clone(); - self._user_agent = agent_name; - prev - } -} - - -% if c.schemas: -// ############ -// SCHEMAS ### -// ########## -% for s in c.schemas.values(): -% if UNUSED_TYPE_MARKER not in schema_markers(s, c, transitive=True): -${schema.new(s, c)} -% endif -% endfor -% endif - -// ################### -// MethodBuilders ### -// ################# - -% for resource in c.rta_map: -${rbuild.new(resource, c)} - - -% endfor - - -// ################### -// CallBuilders ### -// ################# - -% for resource, methods in c.rta_map.iteritems(): -% for method in methods: -${mbuild.new(resource, method, c)} - -% endfor ## method in methods -% endfor ## resource, methods \ No newline at end of file +include!(concat!(env!("OUT_DIR"), "/lib.rs")); \ No newline at end of file diff --git a/src/mako/api/lib/mbuild.mako b/src/mako/api/lib/mbuild.mako index 78cb303f22..a6b1860c8c 100644 --- a/src/mako/api/lib/mbuild.mako +++ b/src/mako/api/lib/mbuild.mako @@ -483,9 +483,6 @@ match result { % if URL_ENCODE in special_cases: use url::percent_encoding::{percent_encode, FORM_URLENCODED_ENCODE_SET}; % endif - % if request_value: - use json_tools::{TokenReader, Lexer, BufferType, TokenType, FilterTypedKeyValuePairs, IteratorExt}; - % endif use std::io::{Read, Seek}; use hyper::header::{ContentType, ContentLength, Authorization, UserAgent, Location}; let mut dd = DefaultDelegate; @@ -665,13 +662,11 @@ else { let mut json_mime_type = mime::Mime(mime::TopLevel::Application, mime::SubLevel::Json, Default::default()); let mut request_value_reader = { - let json_cache = json::to_string(&self.${property(REQUEST_VALUE_PROPERTY_NAME)}).unwrap(); - let mut mem_dst = io::Cursor::new(Vec::with_capacity(json_cache.len())); - io::copy(&mut Lexer::new(json_cache.bytes(), BufferType::Span) - .filter_key_value_by_type(TokenType::Null) - .reader(Some(&json_cache)), &mut mem_dst).unwrap(); - mem_dst.seek(io::SeekFrom::Start(0)).unwrap(); - mem_dst + let mut value = json::value::to_value(&self.${property(REQUEST_VALUE_PROPERTY_NAME)}); + remove_json_null_values(&mut value); + let mut dst = io::Cursor::new(Vec::with_capacity(128)); + json::to_writer(&mut dst, &value).unwrap(); + dst }; let request_size = request_value_reader.seek(io::SeekFrom::End(0)).unwrap(); request_value_reader.seek(io::SeekFrom::Start(0)).unwrap(); diff --git a/src/mako/cli/lib/engine.mako b/src/mako/cli/lib/engine.mako index 661d3c63db..2661125ee0 100644 --- a/src/mako/cli/lib/engine.mako +++ b/src/mako/cli/lib/engine.mako @@ -323,7 +323,7 @@ if dry_run { % if mc.response_schema: let mut value = json::value::to_value(&output_schema); remove_json_null_values(&mut value); - serde::json::to_writer_pretty(&mut ostream, &value).unwrap(); + json::to_writer_pretty(&mut ostream, &value).unwrap(); % endif % if track_download_flag: } else { diff --git a/src/rust/api/cmn.rs b/src/rust/api/cmn.rs index c6d6b7185c..8007ede8fb 100644 --- a/src/rust/api/cmn.rs +++ b/src/rust/api/cmn.rs @@ -288,7 +288,7 @@ impl Display for Error { writeln!(f, "The media size {} exceeds the maximum allowed upload size of {}" , resource_size, max_size), Error::MissingAPIKey => { - writeln!(f, "The application's API key was not found in the configuration").ok(); + (writeln!(f, "The application's API key was not found in the configuration")).ok(); writeln!(f, "It is used as there are no Scopes defined for this method.") }, Error::BadRequest(ref err) => { @@ -422,8 +422,8 @@ impl<'a> Read for MultiPartReader<'a> { (n, true, _) if n > 0 => { let (headers, reader) = self.raw_parts.remove(0); let mut c = Cursor::new(Vec::::new()); - write!(&mut c, "{}--{}{}{}{}", LINE_ENDING, BOUNDARY, LINE_ENDING, - headers, LINE_ENDING).unwrap(); + (write!(&mut c, "{}--{}{}{}{}", LINE_ENDING, BOUNDARY, LINE_ENDING, + headers, LINE_ENDING)).unwrap(); c.seek(SeekFrom::Start(0)).unwrap(); self.current_part = Some((c, reader)); } @@ -492,7 +492,7 @@ pub struct Chunk { impl fmt::Display for Chunk { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { - write!(fmt, "{}-{}", self.first, self.last).ok(); + (write!(fmt, "{}-{}", self.first, self.last)).ok(); Ok(()) } } @@ -547,7 +547,7 @@ impl HeaderFormat for ContentRange { Some(ref c) => try!(c.fmt(fmt)), None => try!(fmt.write_str("*")) } - write!(fmt, "/{}", self.total_length).ok(); + (write!(fmt, "/{}", self.total_length)).ok(); Ok(()) } } @@ -700,4 +700,28 @@ impl<'a, A> ResumableUploadHelper<'a, A> } } } -} \ No newline at end of file +} + +use serde::json::value::Value; +// Copy of src/rust/cli/cmn.rs +// TODO(ST): Allow sharing common code between program types +pub fn remove_json_null_values(value: &mut Value) { + match *value { + Value::Object(ref mut map) => { + let mut for_removal = Vec::new(); + + for (key, mut value) in map.iter_mut() { + if value.is_null() { + for_removal.push(key.clone()); + } else { + remove_json_null_values(&mut value); + } + } + + for key in &for_removal { + map.remove(key); + } + } + _ => {} + } +} From 5483e328320412c39798ba15f26d02b90dd7591d Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Fri, 19 Jun 2015 11:32:07 +0200 Subject: [PATCH 09/14] fix(api): expanded header implementation Now it compiles to the point where `Mime` appears as duplicate type, for some reason. --- src/mako/Cargo.toml.mako | 4 +-- src/mako/api/lib.rs.in.mako | 1 - src/rust/api/cmn.rs | 67 +++++++++++++++++++++++++++---------- 3 files changed, 52 insertions(+), 20 deletions(-) diff --git a/src/mako/Cargo.toml.mako b/src/mako/Cargo.toml.mako index 9eebc4adb4..f931121a69 100644 --- a/src/mako/Cargo.toml.mako +++ b/src/mako/Cargo.toml.mako @@ -24,9 +24,9 @@ name = "${util.program_name()}" % endif [dependencies] -hyper = ">= 0.5.0" +hyper = ">= 0.5.2" mime = "*" -serde = ">= 0.3.0" +serde = ">= 0.4.1" yup-oauth2 = "*" % for dep in cargo.get('dependencies', list()): ${dep} diff --git a/src/mako/api/lib.rs.in.mako b/src/mako/api/lib.rs.in.mako index 22bc67aa8f..b7d6d63f1e 100644 --- a/src/mako/api/lib.rs.in.mako +++ b/src/mako/api/lib.rs.in.mako @@ -19,7 +19,6 @@ <%util:gen_info source="${self.uri}" />\ -#[macro_use] extern crate hyper; extern crate serde; extern crate yup_oauth2 as oauth2; diff --git a/src/rust/api/cmn.rs b/src/rust/api/cmn.rs index 8007ede8fb..027c701c03 100644 --- a/src/rust/api/cmn.rs +++ b/src/rust/api/cmn.rs @@ -469,18 +469,51 @@ impl<'a> Read for MultiPartReader<'a> { } } -header!{ - #[doc="The `X-Upload-Content-Type` header."] - (XUploadContentType, "X-Upload-Content-Type") => [Mime] +// The following macro invocation needs to be expanded, as `include!` +// doens't support external macros +// header!{ +// #[doc="The `X-Upload-Content-Type` header."] +// (XUploadContentType, "X-Upload-Content-Type") => [Mime] - xupload_content_type { - test_header!( - test1, - vec![b"text/plain"], - Some(HeaderField( - vec![Mime(TopLevel::Text, SubLevel::Plain, Vec::new())] - ))); +// xupload_content_type { +// test_header!( +// test1, +// vec![b"text/plain"], +// Some(HeaderField( +// vec![Mime(TopLevel::Text, SubLevel::Plain, Vec::new())] +// ))); +// } +// } + +/// The `X-Upload-Content-Type` header. +/// +/// Generated via rustc --pretty expanded -Z unstable-options, and manually +/// processed to be more readable. +#[derive(PartialEq, Debug, Clone)] +pub struct XUploadContentType(pub Mime); + +impl ::std::ops::Deref for XUploadContentType { + type Target = Mime; + fn deref<'a>(&'a self) -> &'a Mime { &self.0 } +} +impl ::std::ops::DerefMut for XUploadContentType { + fn deref_mut<'a>(&'a mut self) -> &'a mut Mime { &mut self.0 } +} +impl Header for XUploadContentType { + fn header_name() -> &'static str { "X-Upload-Content-Type" } + fn parse_header(raw: &[Vec]) -> Option { + hyper::header::parsing::from_one_raw_str(raw).map(XUploadContentType) + } +} +impl HeaderFormat for XUploadContentType { + fn fmt_header(&self, f: &mut fmt::Formatter) -> fmt::Result { + Display::fmt(&**self, f) + } +} +impl Display for XUploadContentType { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Display::fmt(&**self, f) } } @@ -534,7 +567,7 @@ impl Header for ContentRange { } /// We are not parsable, as parsing is done by the `Range` header - fn parse_header(_: &[Vec]) -> Option { + fn parse_header(_: &[Vec]) -> Option { None } } @@ -560,9 +593,10 @@ impl Header for RangeResponseHeader { "Range" } - fn parse_header(raw: &[Vec]) -> Option { - if let [ref v] = raw { - if let Ok(s) = std::str::from_utf8(v) { + fn parse_header(raw: &[Vec]) -> Option { + if raw.len() > 0 { + let v = raw[0]; + if let Ok(s) = std::str::from_utf8(&v) { const PREFIX: &'static str = "bytes "; if s.starts_with(PREFIX) { if let Ok(c) = ::from_str(&s[PREFIX.len()..]) { @@ -702,12 +736,11 @@ impl<'a, A> ResumableUploadHelper<'a, A> } } -use serde::json::value::Value; // Copy of src/rust/cli/cmn.rs // TODO(ST): Allow sharing common code between program types -pub fn remove_json_null_values(value: &mut Value) { +pub fn remove_json_null_values(value: &mut serde::json::value::Value) { match *value { - Value::Object(ref mut map) => { + serde::json::value::Value::Object(ref mut map) => { let mut for_removal = Vec::new(); for (key, mut value) in map.iter_mut() { From 2ad8d887cda32214dc520af5a9621366f4522fdf Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Fri, 19 Jun 2015 11:41:29 +0200 Subject: [PATCH 10/14] fix(api): minor fixes * Mime crate must be used in the same version hyper uses * made attempted move a borrow --- src/mako/Cargo.toml.mako | 3 ++- src/rust/api/cmn.rs | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/mako/Cargo.toml.mako b/src/mako/Cargo.toml.mako index f931121a69..c53c33b035 100644 --- a/src/mako/Cargo.toml.mako +++ b/src/mako/Cargo.toml.mako @@ -25,7 +25,8 @@ name = "${util.program_name()}" [dependencies] hyper = ">= 0.5.2" -mime = "*" +## Must match the one hyper uses, otherwise there are duplicate similarly named `Mime` structs +mime = "0.0.11" serde = ">= 0.4.1" yup-oauth2 = "*" % for dep in cargo.get('dependencies', list()): diff --git a/src/rust/api/cmn.rs b/src/rust/api/cmn.rs index 027c701c03..1422e1f554 100644 --- a/src/rust/api/cmn.rs +++ b/src/rust/api/cmn.rs @@ -595,8 +595,8 @@ impl Header for RangeResponseHeader { fn parse_header(raw: &[Vec]) -> Option { if raw.len() > 0 { - let v = raw[0]; - if let Ok(s) = std::str::from_utf8(&v) { + let v = &raw[0]; + if let Ok(s) = std::str::from_utf8(v) { const PREFIX: &'static str = "bytes "; if s.starts_with(PREFIX) { if let Ok(c) = ::from_str(&s[PREFIX.len()..]) { From a9e0be6583fd92b9a171091b70e81bdba4ad4aa2 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Fri, 19 Jun 2015 12:55:00 +0200 Subject: [PATCH 11/14] fix(cli): work on stable CLI was slightly adjusted to not use unstable features. Fortunately, there is no serde magic happening, which allows us to keep it simple without using a build script. --- etc/api/type-api.yaml | 1 + src/mako/Cargo.toml.mako | 4 +++- src/mako/cli/main.rs.mako | 8 +++++--- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/etc/api/type-api.yaml b/etc/api/type-api.yaml index a452cae9b6..cad1135fdd 100644 --- a/etc/api/type-api.yaml +++ b/etc/api/type-api.yaml @@ -26,6 +26,7 @@ make: output_dir: src cargo: build_version: "0.1.7" + build_script: src/build.rs keywords: [protocol, web, api] dependencies: - url = "*" diff --git a/src/mako/Cargo.toml.mako b/src/mako/Cargo.toml.mako index c53c33b035..82203c3d5b 100644 --- a/src/mako/Cargo.toml.mako +++ b/src/mako/Cargo.toml.mako @@ -16,7 +16,9 @@ homepage = "${documentationLink}" documentation = "${cargo.doc_base_url}/${to_extern_crate_name(util.crate_name())}" license = "${copyright.license_abbrev}" keywords = ["${name[:20]}", ${", ".join(estr(cargo.keywords))}] -build = "src/build.rs" +% if cargo.get('build_script'): +build = "${cargo.build_script}" +% endif % if cargo.get('is_executable', False): [[bin]] diff --git a/src/mako/cli/main.rs.mako b/src/mako/cli/main.rs.mako index 35e1986fb4..3149e59ca4 100644 --- a/src/mako/cli/main.rs.mako +++ b/src/mako/cli/main.rs.mako @@ -12,7 +12,6 @@ <%block filter="rust_comment">\ <%util:gen_info source="${self.uri}" />\ -#![feature(plugin, exit_status)] #![allow(unused_variables, unused_imports, dead_code, unused_mut)] #[macro_use] @@ -34,18 +33,19 @@ mod cmn; ${engine.new(c)}\ fn main() { + let mut exit_status = 0i32; ${argparse.new(c) | indent_all_but_first_by(1)}\ let matches = app.get_matches(); let debug = matches.is_present("${DEBUG_FLAG}"); match Engine::new(matches) { Err(err) => { - env::set_exit_status(err.exit_code); + exit_status = err.exit_code; writeln!(io::stderr(), "{}", err).ok(); }, Ok(engine) => { if let Err(doit_err) = engine.doit() { - env::set_exit_status(1); + exit_status = 1; match doit_err { DoitError::IoError(path, err) => { writeln!(io::stderr(), "Failed to open output file '{}': {}", path, err).ok(); @@ -61,4 +61,6 @@ fn main() { } } } + + std::process::exit(exit_status); } \ No newline at end of file From 1f9dc06a57f45c8be216602661b68a0adce5beca Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Fri, 19 Jun 2015 12:56:34 +0200 Subject: [PATCH 12/14] chore(travis): explicitly use stable rust This would be the default, but I want to be sure everyone sees stable is what we need. --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index 293ce2e328..c209fc5973 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,6 @@ language: rust +rust: +- stable script: - make docs-all gen-all-cli cargo-api ARGS=test env: From 6e669ced2aca094b246c2c0eb805b362924112b1 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Fri, 19 Jun 2015 12:59:51 +0200 Subject: [PATCH 13/14] doc(README): update info about rust stable Yes, it's fully supported now. --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 3bea1b7749..efda4a32d0 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,6 @@ To find a library of your interest, you might want to proceed looking at the [AP * first-class documentation with cross-links and complete code-examples * support all features, including downloads and resumable uploads * safety and resilience are built-in, allowing you to create highly available tools on top of it. For example, you can trigger retries for all operations that may temporarily fail, e.g. due to network outage. -* *(soon)* Feature-complete command line tool to interact with each API. # Live-Development @@ -46,8 +45,9 @@ To generate the APIs yourself, you will need to meet the following prerequisites * As [*mako*][mako] is a python program, you will need python installed on your system to run it. Some other programs we call depend on python being present as well. * **an internet connection and wget** * Make will download all other prerequisites automatically into hidden directories within this repository, which requires it to make some downloads via wget. -* **Rust beta/nightly** - * Due to the reliance on unstable dependencies, a beta or nightly build of the Rust toolchain is required. [Multirust][multirust] is recommended to support multiple toolchains on a per-project basis. +* **Rust Stable** + * This project compiles on *stable* Rust only. You might consider using [Multirust][multirust] to control + the toolchain on a per-project basis ## Using Make From d6ddff240d1fefac28549efad78648d98e4ed9a4 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Fri, 19 Jun 2015 13:11:28 +0200 Subject: [PATCH 14/14] chore(version): api+cli increment CLI was incremented to 0.3.0, just to signal usage of the latest clap-rs as well as the update of the used API implememtation. In that moment we also got rid of the json-tools dependency - it required unstable features, and I was not willing to enforce making it stable just yet. --- etc/api/type-api.yaml | 2 +- etc/api/type-cli.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/etc/api/type-api.yaml b/etc/api/type-api.yaml index cad1135fdd..6f1847fcb6 100644 --- a/etc/api/type-api.yaml +++ b/etc/api/type-api.yaml @@ -25,7 +25,7 @@ make: - source: build.rs output_dir: src cargo: - build_version: "0.1.7" + build_version: "0.1.8" build_script: src/build.rs keywords: [protocol, web, api] dependencies: diff --git a/etc/api/type-cli.yaml b/etc/api/type-cli.yaml index 7232a6f01d..11c0f53e1a 100644 --- a/etc/api/type-cli.yaml +++ b/etc/api/type-cli.yaml @@ -22,7 +22,7 @@ make: - source: main.rs output_dir: src cargo: - build_version: "0.2.0" + build_version: "0.3.0" keywords: [cli] is_executable: YES dependencies: