refactor(mako): put API relevant stuff into subdir

This is the first of many changes to come.
We try to leverage our ability to merge multiple data source into one
to abstract away what we are actually doing, and of course, to allow
sharing the majority of the code, were applicable.
This commit is contained in:
Sebastian Thiel
2015-03-23 16:13:02 +01:00
parent d1c5bf1e4a
commit 137ba8caf3
11 changed files with 45 additions and 23 deletions

View File

@@ -0,0 +1,13 @@
<%
from util import (markdown_comment, new_context)
c = new_context(schemas, resources, context.get('methods'))
%>\
<%namespace name="lib" file="lib/lib.mako"/>\
<%namespace name="util" file="lib/util.mako"/>\
<%block filter="markdown_comment">\
<%util:gen_info source="${self.uri}" />\
</%block>
The `${util.crate_name()}` library allows access to all features of the *Google ${util.canonical_name()}* service.
${lib.docs(c, rust_doc=False)}
<%lib:license />

149
src/mako/api/lib.rs.mako Normal file
View File

@@ -0,0 +1,149 @@
<%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}" />\
</%block>
<%block filter="rust_module_doc_comment">\
${lib.docs(c)}
</%block>
#![feature(core,io,thread_sleep)]
// 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)]
#![plugin(serde_macros)]
#[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 std::marker::PhantomData;
use serde::json;
use std::io;
use std::fs;
use std::thread::sleep;
pub use cmn::{MultiPartReader, ToParts, MethodInfo, Result, Error, CallBuilder, Hub, ReadSeek, Part, ResponseResult, RequestValue, NestedType, Delegate, DefaultDelegate, MethodsBuilder, Resource, JsonServerError};
// ##############
// 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)}\
</%block>
pub struct ${hub_type}${ht_params} {
client: RefCell<C>,
auth: RefCell<A>,
_user_agent: String,
_m: PhantomData<NC>
}
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(),
_m: PhantomData
}
}
% 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

369
src/mako/api/lib/lib.mako Normal file
View File

@@ -0,0 +1,369 @@
<%!
from util import (activity_split, put_and, md_italic, split_camelcase_s, canonical_type_name, hub_type,
rust_test_fn_invisible, rust_doc_test_norun, rust_doc_comment, markdown_rust_block,
unindent_first_by, mangle_ident, mb_type, singular, scope_url_to_variant,
PART_MARKER_TRAIT, RESOURCE_MARKER_TRAIT, CALL_BUILDER_MARKERT_TRAIT,
find_fattest_resource, build_all_params, pass_through, parts_from_params,
REQUEST_MARKER_TRAIT, RESPONSE_MARKER_TRAIT, supports_scopes, to_api_version,
to_fqan, METHODS_RESOURCE, ADD_PARAM_MEDIA_EXAMPLE, PROTOCOL_TYPE_INFO, enclose_in,
upload_action_fn, unique_type_name, schema_doc_format, METHODS_BUILDER_MARKER_TRAIT)
def pretty_name(name):
return ' '.join(split_camelcase_s(name).split('.'))
%>\
<%namespace name="util" file="util.mako"/>\
<%namespace name="mbuild" file="mbuild.mako"/>\
## If rust-doc is True, examples will be made to work for rust doc tests. Otherwise they are set
## for github markdown.
###############################################################################################
###############################################################################################
<%def name="docs(c, rust_doc=True)">\
<%
# fr == fattest resource, the fatter, the more important, right ?
fr = find_fattest_resource(c)
hub_url = 'struct.' + hub_type(c.schemas, util.canonical_name()) + '.html'
call_builder_url = 'trait.' + CALL_BUILDER_MARKERT_TRAIT + '.html'
delegate_url = 'trait.Delegate.html'
request_trait_url = 'trait.' + REQUEST_MARKER_TRAIT + '.html'
response_trait_url = 'trait.' + RESPONSE_MARKER_TRAIT + '.html'
part_trait_url = 'trait.' + PART_MARKER_TRAIT + '.html'
doc_base_url = cargo.doc_base_url + '/' + util.crate_name() + '/'
def link(name, url):
lf = '[%s](%s)'
if rust_doc:
return lf % (name, url)
for scheme in ('http', 'https'):
if url.startswith(scheme + '://'):
return lf % (name, url)
return lf % (name, doc_base_url + url)
api_version = to_api_version(version)
if api_version[0].isdigit():
api_version = 'v' + api_version
upload_methods, download_methods, subscription_methods = list(), list(), list()
for m in c.fqan_map.values():
for array, param in ((download_methods, 'supportsMediaDownload'),
(upload_methods, 'supportsMediaUpload'),
(subscription_methods, 'supportsSubscription')):
if m.get(param, False):
array.append(m)
# end for each method
header_methods = (('Upload', upload_methods), ('Download', download_methods), ('Subscription', subscription_methods))
%>\
This documentation was generated from *${util.canonical_name()}* crate version *${util.crate_version()}*, where *${revision}* is the exact revision of the *${id}* schema built by the [mako](http://www.makotemplates.org/) code generator *v${cargo.build_version}*.
% if documentationLink:
Everything else about the *${util.canonical_name()}* *${api_version}* API can be found at the
[official documentation site](${documentationLink}).
% endif
% if rust_doc:
The original source code is [on github](${util.github_source_root_url()}).
% endif
# Features
% if len(c.rta_map) > 0 + (METHODS_RESOURCE in c.rta_map):
Handle the following *Resources* with ease from the central ${link('hub', hub_url)} ...
% elif METHODS_RESOURCE in c.rta_map:
Use the following functionality with ease from the central ${link('hub', hub_url)} ...
% else:
It seems there is nothing you can do here ... .
% endif
% for r in sorted(c.rta_map.keys()):
% if r == METHODS_RESOURCE:
<% continue %>
% endif ## skip method resource
<%
md_methods = list()
for method in sorted(c.rta_map[r]):
md_methods.append(link('*%s*' % pretty_name(method),
'struct.%s.html' % mb_type(r, method)))
md_resource = pretty_name(r)
sn = singular(canonical_type_name(r))
if sn in schemas:
md_resource = link(md_resource, schema_doc_format(schemas[sn]) % unique_type_name(sn))
%>\
* ${md_resource}
* ${put_and(md_methods)}
% endfor ## each resource activity
% if METHODS_RESOURCE in c.rta_map:
% if len(c.rta_map) > 1:
Other activities are ...
% endif
% for method in sorted(c.rta_map[METHODS_RESOURCE]):
* ${link(pretty_name(method), 'struct.%s.html' % mb_type(METHODS_RESOURCE, method))}
% endfor
% endif
% for method_type, methods in header_methods:
% if methods:
${method_type} supported by ...
% for m in methods:
<%
_, resource, method = activity_split(m.id)
name_parts = [pretty_name(method)]
if resource != METHODS_RESOURCE:
name_parts.append(pretty_name(resource))
%>\
* ${link('*%s*' % ' '.join(name_parts), 'struct.%s.html' % mb_type(resource, method))}
% endfor ## for each method
% endif ## if methods
% endfor ## for each method type
% if rust_doc:
Not what you are looking for ? Find all other Google APIs in their Rust [documentation index](../index.html).
% endif
# Structure of this Library
The API is structured into the following primary items:
* **${link('Hub', hub_url)}**
* a central object to maintain state and allow accessing all *Activities*
* creates ${link('*Method Builders*', 'trait.' + METHODS_BUILDER_MARKER_TRAIT + '.html')} which in turn
allow access to individual ${link('*Call Builders*', call_builder_url)}
* **${link('Resources', 'trait.' + RESOURCE_MARKER_TRAIT + '.html')}**
* primary types that you can apply *Activities* to
* a collection of properties and *Parts*
* **${link('Parts', part_trait_url)}**
* a collection of properties
* never directly used in *Activities*
* **${link('Activities', call_builder_url)}**
* operations to apply to *Resources*
All *structures* are marked with applicable traits to further categorize them and ease browsing.
Generally speaking, you can invoke *Activities* like this:
```Rust,ignore
let r = hub.resource().activity(...).${api.terms.action}()
```
% if fr:
Or specifically ...
```ignore
% for an, a in c.sta_map[fr.id].iteritems():
<% category, resource, activity = activity_split(an) %>\
let r = hub.${mangle_ident(resource)}().${mangle_ident(activity)}(...).${api.terms.action}()
% endfor
```
% endif
The `resource()` and `activity(...)` calls create [builders][builder-pattern]. The second one dealing with `Activities`
supports various methods to configure the impending operation (not shown here). It is made such that all required arguments have to be
specified right away (i.e. `(...)`), whereas all optional ones can be [build up][builder-pattern] as desired.
The `${api.terms.action}()` method performs the actual communication with the server and returns the respective result.
# Usage
${'##'} Setting up your Project
To use this library, you would put the following lines into your `Cargo.toml` file:
```toml
[dependencies]
${util.crate_name()} = "*"
```
${'##'} A complete example
${self.hub_usage_example(c, rust_doc, fr=fr)}\
${'##'} Handling Errors
All errors produced by the system are provided either as ${link('Result', 'enum.Result.html')} enumeration as return value of
the ${api.terms.action}() methods, or handed as possibly intermediate results to either the
${link('Hub Delegate', delegate_url)}, or the ${link('Authenticator Delegate', urls.authenticator_delegate)}.
When delegates handle errors or intermediate values, they may have a chance to instruct the system to retry. This
makes the system potentially resilient to all kinds of errors.
${'##'} Uploads and Downloads
If a method supports downloads, the response body, which is part of the ${link('Result', 'enum.Result.html')}, should be
read by you to obtain the media.
If such a method also supports a ${link('Response Result', 'trait.ResponseResult.html')}, it will return that by default.
You can see it as meta-data for the actual media. To trigger a media download, you will have to set up the builder by making
this call: `${ADD_PARAM_MEDIA_EXAMPLE}`.
Methods supporting uploads can do so using up to ${len(PROTOCOL_TYPE_INFO)} different protocols:
${put_and(md_italic(PROTOCOL_TYPE_INFO.keys()))}. The distinctiveness of each is represented by customized
`${api.terms.action}(...)` methods, which are then named ${put_and(enclose_in('`', ("%s(...)" % upload_action_fn(api.terms.upload_action, v['suffix']) for v in PROTOCOL_TYPE_INFO.values())))} respectively.
${'##'} Customization and Callbacks
You may alter the way an `${api.terms.action}()` method is called by providing a ${link('delegate', delegate_url)} to the
${link('Method Builder', call_builder_url)} before making the final `${api.terms.action}()` call.
Respective methods will be called to provide progress information, as well as determine whether the system should
retry on failure.
The ${link('delegate trait', delegate_url)} is default-implemented, allowing you to customize it with minimal effort.
${'##'} Optional Parts in Server-Requests
All structures provided by this library are made to be ${link('enocodable', request_trait_url)} and
${link('decodable', response_trait_url)} via *json*. Optionals are used to indicate that partial requests are responses
are valid.
Most optionals are are considered ${link('Parts', part_trait_url)} which are identifiable by name, which will be sent to
the server to indicate either the set parts of the request or the desired parts in the response.
${'##'} Builder Arguments
Using ${link('method builders', call_builder_url)}, you are able to prepare an action call by repeatedly calling it's methods.
These will always take a single argument, for which the following statements are true.
* [PODs][wiki-pod] are handed by copy
* strings are passed as `&str`
* ${link('request values', request_trait_url)} are borrowed
Arguments will always be copied or cloned into the builder, to make them independent of their original life times.
[wiki-pod]: http://en.wikipedia.org/wiki/Plain_old_data_structure
[builder-pattern]: http://en.wikipedia.org/wiki/Builder_pattern
[google-go-api]: https://github.com/google/google-api-go-client
</%def>
## Sets up a hub ready for use. You must wrap it into a test function for it to work
## Needs test_prelude.
###############################################################################################
###############################################################################################
<%def name="test_hub(hub_type, comments=True)">\
use std::default::Default;
use oauth2::{Authenticator, DefaultAuthenticatorDelegate, ApplicationSecret, MemoryStorage};
use ${util.library_name()}::${hub_type};
% if comments:
// Get an ApplicationSecret instance by some means. It contains the `client_id` and
// `client_secret`, among other things.
% endif
let secret: ApplicationSecret = Default::default();
% if comments:
// 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.
% endif
let auth = Authenticator::new(&secret, DefaultAuthenticatorDelegate,
hyper::Client::new(),
<MemoryStorage as Default>::default(), None);
let mut hub = ${hub_type}::new(hyper::Client::new(), auth);\
</%def>
## You will still have to set the filter for your comment type - either nothing, or rust_doc_comment !
###############################################################################################
###############################################################################################
<%def name="hub_usage_example(c, rust_doc=True, fr=None)">\
<%
test_filter = rust_test_fn_invisible
main_filter = rust_doc_test_norun
if not rust_doc:
test_filter = pass_through
main_filter = markdown_rust_block
if fr is None:
fr = find_fattest_resource(c)
if fr is not None:
fqan = None
last_param_count = None
for fqan in c.sta_map[fr.id]:
category, aresource, amethod = activity_split(fqan)
# Cannot use fqan directly, as it might need remapping thanks to 'special case' resource.
# see METHODS_RESOURCE for more information
am = c.fqan_map[to_fqan(category, aresource, amethod)]
build_all_params(c, am)
aparams, arequest_value = build_all_params(c, am)
if last_param_count is None or len(aparams) > last_param_count:
m, resource, method, params, request_value = am, aresource or category, amethod, aparams, arequest_value
last_param_count = len(aparams)
# end for each fn to test
part_prop, parts = parts_from_params(params)
# end fill in values
%>\
% if fr:
${mbuild.usage(resource, method, m, params, request_value, parts, show_all=True, rust_doc=rust_doc, handle_result=True)}\
% else:
<%block filter="main_filter">\
${util.test_prelude()}\
<%block filter="test_filter">\
${self.test_hub(hub_type(c.schemas, util.canonical_name()))}
</%block>
</%block>
% endif
</%def>
###############################################################################################
###############################################################################################
<%def name="license()">\
# License
The **${util.library_name()}** library was generated by ${put_and(copyright.authors)}, and is placed
under the *${copyright.license_abbrev}* license.
You can read the full text at the repository's [license file][repo-license].
[repo-license]: ${cargo.repo_base_url + '/LICENSE.md'}
</%def>
## Builds the scope-enum for the API
## It's possible there is no scope enum if there is no auth information
###############################################################################################
###############################################################################################
<%def name="scope_enum()">\
% if not supports_scopes(auth):
<% return '' %>\
% endif
/// Identifies the an OAuth2 authorization scope.
/// A scope is needed when requesting an
/// [authorization token](https://developers.google.com/youtube/v3/guides/authentication).
#[derive(PartialEq, Eq, Hash)]
pub enum Scope {
% for url, scope in auth.oauth2.scopes.items():
${scope.description | rust_doc_comment}
${scope_url_to_variant(name, url, fully_qualified=False)},
% if not loop.last:
% endif
% endfor
}
impl Str for Scope {
fn as_slice(&self) -> &str {
match *self {
% for url in auth.oauth2.scopes.keys():
${scope_url_to_variant(name, url)} => "${url}",
% endfor
}
}
}
impl Default for Scope {
fn default() -> Scope {
<%
default_url = None
shortest_url = None
for url in auth.oauth2.scopes.keys():
if not default_url and 'readonly' in url:
default_url = url
if not shortest_url or len(shortest_url) > len(url):
shortest_url = url
# end for each url
default_url = default_url or shortest_url
%>\
${scope_url_to_variant(name, default_url)}
}
}
</%def>

View File

@@ -0,0 +1,864 @@
<%!
from util import (put_and, rust_test_fn_invisible, rust_doc_test_norun, rust_doc_comment,
rb_type, mb_type, singular, hub_type, to_fqan, indent_all_but_first_by,
method_params, activity_rust_type, mangle_ident, activity_input_type, get_word,
split_camelcase_s, property, is_pod_property, TREF, IO_REQUEST,
schema_to_required_property, rust_copy_value_s, is_required_property,
hide_rust_doc_test, build_all_params, REQUEST_VALUE_PROPERTY_NAME, organize_params,
indent_by, to_rust_type, rnd_arg_val_for_type, extract_parts, mb_type_params_s,
hub_type_params_s, method_media_params, enclose_in, mb_type_bounds, method_response,
CALL_BUILDER_MARKERT_TRAIT, pass_through, markdown_rust_block, parts_from_params,
DELEGATE_PROPERTY_NAME, struct_type_bounds_s, supports_scopes, scope_url_to_variant,
re_find_replacements, ADD_PARAM_FN, ADD_PARAM_MEDIA_EXAMPLE, upload_action_fn, METHODS_RESOURCE,
method_name_to_variant, unique_type_name, size_to_bytes)
def get_parts(part_prop):
if not part_prop:
return list()
return extract_parts(part_prop.get('description', ''))
def make_parts_desc(part_prop):
parts = get_parts(part_prop)
if not parts:
return None
part_desc = "**Settable Parts**\n\n"
part_desc += ''.join('* *%s*\n' % part for part in parts)
part_desc = part_desc[:-1]
return part_desc
def is_repeated_property(p):
return p.get('repeated', False)
def setter_fn_name(p):
fn_name = p.name
if is_repeated_property(p):
fn_name = 'add_' + fn_name
return fn_name
%>\
<%namespace name="util" file="util.mako"/>\
<%namespace name="lib" file="lib.mako"/>\
## Creates a method builder type
###############################################################################################
###############################################################################################
<%def name="new(resource, method, c)">\
<%
hub_type_name = hub_type(schemas,util.canonical_name())
m = c.fqan_map[to_fqan(c.rtc_map[resource], resource, method)]
response_schema = method_response(c, m)
# an identifier for a property. We prefix them to prevent clashes with the setters
mb_tparams = mb_type_params_s(m)
ThisType = mb_type(resource, method) + mb_tparams
params, request_value = build_all_params(c, m)
part_prop, parts = parts_from_params(params)
part_desc = make_parts_desc(part_prop)
parts = get_parts(part_prop)
%>\
% if 'description' in m:
${m.description | rust_doc_comment}
///
% endif
% if m.get('supportsMediaDownload', False):
/// This method supports **media download**. To enable it, adjust the builder like this:
/// `${ADD_PARAM_MEDIA_EXAMPLE}`.
% if response_schema:
/// Please note that due to missing multi-part support on the server side, you will only receive the media,
/// but not the `${unique_type_name(response_schema.id)}` structure that you would usually get. The latter will be a default value.
% endif
///
% endif ## supports media download
% if resource == METHODS_RESOURCE:
/// A builder for the *${method}* method.
% else:
/// A builder for the *${method}* method supported by a *${singular(resource)}* resource.
% endif
/// It is not used directly, but through a `${rb_type(resource)}` instance.
///
% if part_desc:
${part_desc | rust_doc_comment}
///
% if m.get('scopes'):
/// # Scopes
///
/// You will need authorization for \
% if len(m.scopes) > 1:
at least one of the following scopes to make a valid call, possibly depending on *parts*:
///
% for s in m.scopes:
/// * *${s}*
% endfor
% else:
the *${m.scopes[0]}* scope to make a valid call.
% endif
% endif
///
% endif
/// # Example
///
/// Instantiate a resource method builder
///
<%block filter="rust_doc_comment">\
${self.usage(resource, method, m, params, request_value, parts)}\
</%block>
pub struct ${ThisType}
where ${struct_type_bounds_s()} {
hub: &'a ${hub_type_name}${hub_type_params_s()},
## PROPERTIES ###############
% for p in params:
${property(p.name)}:\
% if is_required_property(p):
${activity_rust_type(schemas, p, allow_optionals=False)},
% else:
${activity_rust_type(schemas, p)},
% endif
% endfor
## A generic map for additinal parameters. Sometimes you can set some that are documented online only
${api.properties.params}: HashMap<String, String>,
% if supports_scopes(auth):
## We need the scopes sorted, to not unnecessarily query new tokens
${api.properties.scopes}: BTreeMap<String, ()>
% endif
}
impl${mb_tparams} ${CALL_BUILDER_MARKERT_TRAIT} for ${ThisType} {}
impl${mb_tparams} ${ThisType} where ${', '.join(mb_type_bounds())} {
${self._action_fn(c, resource, method, m, params, request_value, parts)}\
## SETTERS ###############
% for p in params:
${self._setter_fn(resource, method, m, p, part_prop, ThisType, c)}\
% endfor
/// 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.
% if parameters:
///
/// # Additional Parameters
///
% for opn, op in parameters.iteritems():
/// * *${opn}* (${op.location}-${op.type}) - ${op.description}
% endfor
% endif
pub fn ${ADD_PARAM_FN}<T>(mut self, name: T, value: T) -> ${ThisType}
where T: Str {
self.${api.properties.params}.insert(name.as_slice().to_string(), value.as_slice().to_string());
self
}
% if supports_scopes(auth):
/// Identifies the authorization scope for the method you are building.
///
/// Use this method to actively specify which scope should be used, instead of relying on the
/// automated algorithm which simply prefers read-only scopes over those who are not.
///
/// The `scope` will be added to a set of scopes. This is important as one can maintain access
/// tokens for more than one scope.
///
/// Usually there is more than one suitable scope to authorize an operation, some of which may
/// encompass more rights than others. For example, for listing resources, a *read-only* scope will be
/// sufficient, a read-write scope will do as well.
pub fn add_scope<T>(mut self, scope: T) -> ${ThisType}
where T: Str {
self.${api.properties.scopes}.insert(scope.as_slice().to_string(), ());
self
}
% endif
}
</%def>
## creates a setter for the call builder
###############################################################################################
###############################################################################################
<%def name="_setter_fn(resource, method, m, p, part_prop, ThisType, c)">\
<%
InType = activity_input_type(schemas, p)
if is_repeated_property(p):
p.repeated = False
InType = activity_input_type(schemas, p)
p.repeated = True
def show_part_info(m, p):
if p.name != 'part':
return False
if not (m.get('request') and m.get('response')):
return False
return m.request.get(TREF, 'first') == m.response.get(TREF, 'second')
value_name = 'new_value'
new_value_copied = rust_copy_value_s(value_name, InType, p)
if not is_required_property(p) and not is_repeated_property(p):
new_value_copied = 'Some(%s)' % new_value_copied
part_desc = None
if part_prop is not None and p.name in ('part', REQUEST_VALUE_PROPERTY_NAME):
part_desc = make_parts_desc(part_prop)
# end part description
%>\
% if is_repeated_property(p):
/// Append the given value to the *${split_camelcase_s(p.name)}* ${get_word(p, 'location')}property.
/// Each appended value will retain its original ordering and be '/'-separated in the URL's parameters.
% else:
/// Sets the *${split_camelcase_s(p.name)}* ${get_word(p, 'location')}property to the given value.
% endif
///
% if show_part_info(m, p):
/// Even though the *parts* list is automatically derived from *Resource* passed in
/// during instantiation and indicates which values you are passing, the response would contain the very same parts.
/// This may not always be desirable, as you can obtain (newly generated) parts you cannot pass in,
/// like statistics that are generated server side. Therefore you should use this method to specify
/// the parts you provide in addition to the ones you want in the response.
% elif is_required_property(p):
/// Even though the property as already been set when instantiating this call,
/// we provide this method for API completeness.
% endif
% if part_desc:
///
${part_desc | rust_doc_comment, indent_all_but_first_by(1)}
% endif
///
% if 'description' in p:
${p.description | rust_doc_comment, indent_all_but_first_by(1)}
% endif
pub fn ${mangle_ident(setter_fn_name(p))}(mut self, ${value_name}: ${InType}) -> ${ThisType} {
% if p.get('repeated', False):
self.${property(p.name)}.push(${new_value_copied});
% else:
self.${property(p.name)} = ${new_value_copied};
% endif
self
}
</%def>
## creates usage docs the method builder
## show_all: If True, we will show all comments and hide no prelude. It's good to build a complete,
## documented example for a given method.
###############################################################################################
###############################################################################################
<%def name="usage(resource, method, m, params, request_value, parts=None, show_all=False, rust_doc=True, handle_result=False)">\
<%
hub_type_name = hub_type(schemas, util.canonical_name())
required_props, optional_props, part_prop = organize_params(params, request_value)
is_string_value = lambda v: v.endswith('"')
# to rust value
def trv(spn, sp, sn=None):
prev = sp.get('repeated', False)
sp.repeated = False
res = to_rust_type(schemas, sn, spn, sp, allow_optionals=False)
sp.repeated = prev
return res
# rvfrt = random value for rust type
rvfrt = lambda spn, sp, sn=None: rnd_arg_val_for_type(trv(spn, sp, sn))
rb_name = 'req' # name of request binding
required_args = request_value and ['&' + rb_name] or []
for p in required_props:
# could also just skip the first element, but ... let's be safe
if request_value and request_value.id == p.get(TREF):
continue
v = rvfrt(p.name, p)
# we chose to replace random strings with their meaning, as indicated by the name !
if is_string_value(v):
v = '"%s"' % p.name
required_args.append(v)
# end for each required property
required_args = ', '.join(required_args)
media_params = method_media_params(m)
if media_params:
# index 0 == Simple (usually)
# index 1 == Resumable
# propose standard upload for smaller media. Also means we get to test different code-paths
index = -1
if media_params[-1].max_size < 100*1024*1024:
index = 0
action_name = upload_action_fn(api.terms.upload_action, media_params[index].type.suffix)
else:
action_name = api.terms.action
action_args = media_params and media_params[-1].type.example_value or ''
random_value_warning = "Values shown here are possibly random and not representative !"
hide_filter = show_all and pass_through or hide_rust_doc_test
test_block_filter = rust_doc and rust_doc_test_norun or markdown_rust_block
test_fn_filter = rust_doc and rust_test_fn_invisible or pass_through
if request_value:
request_value_type = unique_type_name(request_value.id)
%>\
<%block filter="test_block_filter">\
${capture(util.test_prelude) | hide_filter}\
% if request_value:
use ${util.library_name()}::${request_value_type};
% endif
% if handle_result:
use ${util.library_name()}::{Result, Error};
% endif
% if media_params:
use std::fs;
% endif
<%block filter="test_fn_filter">\
${capture(lib.test_hub, hub_type_name, comments=show_all) | hide_filter}
% if request_value:
// As the method needs a request, you would usually fill it with the desired information
// into the respective structure. Some of the parts shown here might not be applicable !
// ${random_value_warning}
let mut ${rb_name}: ${request_value_type} = Default::default();
% for spn, sp in request_value.get('properties', dict()).iteritems():
% if parts is not None and spn not in parts:
<% continue %>
% endif
<%
rtn = trv(spn, sp, request_value.id)
assignment = rnd_arg_val_for_type(rtn)
if is_string_value(assignment):
assignment = assignment + '.to_string()'
if assignment.endswith('default()'):
assignment = assignment[1:] # cut & - it's not ok in this case :)!
assignment += '; // is %s' % rtn
else:
assignment = 'Some(%s);' % assignment
%>\
${rb_name}.${mangle_ident(spn)} = ${assignment}
% endfor
% endif
// You can configure optional parameters by calling the respective setters at will, and
// execute the final call using `${action_name}(${action_args and '...' or ''})`.
% if optional_props:
// ${random_value_warning}
% endif
let result = hub.${mangle_ident(resource)}().${mangle_ident(method)}(${required_args})\
% for p in optional_props:
% if p.get('skip_example', False):
<% continue %>
% endif
<%block filter="indent_by(13)">\
.${mangle_ident(setter_fn_name(p))}(${rvfrt(p.name, p)})\
</%block>\
% endfor
${'.' + action_name | indent_by(13)}(${action_args});
% if handle_result:
match result {
Err(e) => match e {
Error::HttpError(err) => println!("HTTPERROR: {:?}", err),
Error::MissingAPIKey => println!("Auth: Missing API Key - used if there are no scopes"),
Error::MissingToken => println!("OAuth2: Missing Token"),
Error::Cancelled => println!("Operation canceled by user"),
Error::UploadSizeLimitExceeded(size, max_size) => println!("Upload size too big: {} of {}", size, max_size),
Error::Failure(_) => println!("General Failure (hyper::client::Response doesn't print)"),
Error::FieldClash(clashed_field) => println!("You added custom parameter which is part of builder: {:?}", clashed_field),
Error::JsonDecodeError(err) => println!("Couldn't understand server reply - maybe API needs update: {:?}", err),
},
Ok(_) => println!("Success (value doesn't print)"),
}
% endif
</%block>
</%block>\
</%def>
## create an entire 'api.terms.action' method
###############################################################################################
###############################################################################################
<%def name="_action_fn(c, resource, method, m, params, request_value, parts)">\
<%
import os.path
join_url = lambda b, e: b.strip('/') + e
media_params = method_media_params(m)
type_params = ''
where = ''
qualifier = 'pub '
add_args = ''
rtype = 'Result<hyper::client::Response>'
response_schema = method_response(c, m)
supports_download = m.get('supportsMediaDownload', False);
reserved_params = []
if response_schema:
if not supports_download:
reserved_params = ['alt']
rtype = 'Result<(hyper::client::Response, %s)>' % (unique_type_name(response_schema.id))
mtype_param = 'RS'
possible_urls = [m.path]
simple_media_param = None
resumable_media_param = None
if media_params:
type_params = '<%s>' % mtype_param
qualifier = ''
where = '\n\t\twhere ' + mtype_param + ': ReadSeek'
add_args = (', mut reader: %s, reader_mime_type: mime::Mime' % mtype_param) + ", protocol: &'static str"
for p in media_params:
if p.protocol == 'simple':
simple_media_param = p
elif p.protocol == 'resumable':
resumable_media_param = p
# end handle media params
action_fn = qualifier + 'fn ' + api.terms.action + type_params + ('(mut self%s)' % add_args) + ' -> ' + rtype + where
field_params = [p for p in params if p.get('is_query_param', True)]
paddfields = 'self.' + api.properties.params
delegate = 'self.' + property(DELEGATE_PROPERTY_NAME)
delegate_finish = 'dlg.finished'
auth_call = 'self.hub.auth.borrow_mut()'
if supports_scopes(auth):
all_scopes = sorted(auth.oauth2.scopes.keys())
default_scope = all_scopes[0]
if m.httpMethod in ('HEAD', 'GET', 'OPTIONS', 'TRACE'):
for scope in all_scopes:
if 'readonly' in scope:
default_scope = scope
break
# end for each scope
# end try to find read-only default scope
# end handle default scope
# s = '{foo}' -> ('{foo}', 'foo') -> (find_this, replace_with)
seen = set()
replacements = list()
all_required_param_name = set(p.name for p in params if is_required_property(p))
MULTI_SLASH = 'multi-slash-prefix'
URL_ENCODE = 'url-encode'
READER_SEEK = "let size = reader.seek(io::SeekFrom::End(0)).unwrap();\nreader.seek(io::SeekFrom::Start(0)).unwrap();\n"
if media_params:
max_size = media_params[0].max_size
if max_size > 0:
READER_SEEK += "if size > %i {\n\treturn Err(Error::UploadSizeLimitExceeded(size, %i))\n}" % (max_size, max_size)
special_cases = set()
for possible_url in possible_urls:
for s in re_find_replacements.findall(possible_url):
if s in seen: continue
seen.add(s)
sn = s[1:-1]
# NOTE: We only handle the cases that are actually used in the schemas. If this shouldn't
# be worth it anymore (i.e. too many cases), then we should use a uri-template library
# to handle this at runtime, possibly, or use a python uri-template library, to more easily
# handle the required cases. Whatever is less work, I guess.
if sn.startswith('/') and sn.endswith('*'):
sn = sn[1:-1]
special_cases.add(MULTI_SLASH)
elif sn.startswith('+'):
sn = sn[1:]
special_cases.add(URL_ENCODE)
assert sn in all_required_param_name, "Expected param '%s' to be in required parameter list for substitution" % sn
replacements.append((s, sn))
# end for each found substitution
# Assure we can substitue everything
for s, d in replacements:
possible_url = possible_url.replace(s, d)
assert '{' not in possible_url, "Failed to replace all fields in '%s', have to parse expressions" % possible_url
# end for each possible url
del seen
%>
/// Perform the operation you have build so far.
${action_fn} {
% if URL_ENCODE in special_cases:
use url::{percent_encode, FORM_URLENCODED_ENCODE_SET};
% endif
use std::io::{Read, Seek};
use hyper::header::{ContentType, ContentLength, Authorization, UserAgent, Location};
let mut dd = DefaultDelegate;
let mut dlg: &mut Delegate = match ${delegate} {
Some(d) => d,
None => &mut dd
};
dlg.begin(MethodInfo { id: "${m.id}",
http_method: ${method_name_to_variant(m.httpMethod)} });
let mut params: Vec<(&str, String)> = Vec::with_capacity((${len(params) + len(reserved_params)} + ${paddfields}.len()));
% for p in field_params:
<%
pname = 'self.' + property(p.name) # property identifier
%>\
## parts can also be derived from the request, but we do that only if it's not set
% if p.name == 'part' and request_value:
% if not is_required_property(p):
if ${pname}.is_none() {
${pname} = Some(self.${property(REQUEST_VALUE_PROPERTY_NAME)}.to_parts());
}
% else:
if ${pname}.len() == 0 {
${pname} = self.${property(REQUEST_VALUE_PROPERTY_NAME)}.to_parts();
}
% endif ## not is_required_property(p):
% endif ## p.name == 'part' and request_value:
% if p.get('repeated', False):
if ${pname}.len() > 0 {
let mut s = String::new();
for f in ${pname}.iter() {
s.push_str(&("/".to_string() + &f.to_string()));
}
params.push(("${p.name}", s));
}
% elif not is_required_property(p):
if let Some(value) = ${pname} {
params.push(("${p.name}", value.to_string()));
}
% else:
params.push(("${p.name}", ${pname}.to_string()));
% endif
% endfor
## Additional params - may not overlap with optional params
for &field in [${', '.join(enclose_in('"', reserved_params + [p.name for p in field_params]))}].iter() {
if ${paddfields}.contains_key(field) {
${delegate_finish}(false);
return Err(Error::FieldClash(field));
}
}
for (name, value) in ${paddfields}.iter() {
params.push((&name, value.clone()));
}
% if response_schema:
% if supports_download:
let (json_field_missing, enable_resource_parsing) = {
let mut enable = true;
let mut field_present = true;
for &(name, ref value) in params.iter() {
if name == "alt" {
field_present = false;
if value.as_slice() != "json" {
enable = false;
}
break;
}
}
(field_present, enable)
};
if json_field_missing {
params.push(("alt", "json".to_string()));
}
% else:
params.push(("alt", "json".to_string()));
% endif ## supportsMediaDownload
% endif ## response schema
% if media_params:
let mut url = \
% for mp in media_params:
% if loop.first:
if \
% else:
else if \
% endif
protocol == "${mp.protocol}" {
"${join_url(rootUrl, mp.path)}".to_string()
} \
% endfor
else {
unreachable!()
};
params.push(("uploadType", protocol.to_string()));
% else:
let mut url = "${baseUrl}${m.path}".to_string();
% endif
% if not supports_scopes(auth):
<%
assert 'key' in parameters, "Expected 'key' parameter if there are no scopes"
%>
let mut key = ${auth_call}.api_key();
if key.is_none() {
key = dlg.api_key();
}
match key {
Some(value) => params.push(("key", value)),
None => {
${delegate_finish}(false);
return Err(Error::MissingAPIKey)
}
}
% else:
if self.${api.properties.scopes}.len() == 0 {
self.${api.properties.scopes}.insert(${scope_url_to_variant(name, default_scope, fully_qualified=True)}.as_slice().to_string(), ());
}
% endif
## 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
%>\
let mut replace_with${replace_init};
for &(name, ref value) in params.iter() {
if name == param_name {
replace_with = ${replace_assign};
break;
}
}
% if URL_ENCODE in special_cases:
if find_this.as_bytes()[1] == '+' as u8 {
replace_with = percent_encode(replace_with.as_bytes(), FORM_URLENCODED_ENCODE_SET);
}
% endif
url = url.replace(find_this, ${url_replace_arg});
}
## Remove all used parameters
{
let mut indices_for_removal: Vec<usize> = 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 &index in indices_for_removal.iter() {
params.remove(index);
}
}
% endif
if params.len() > 0 {
url.push('?');
url.push_str(&url::form_urlencoded::serialize(params.iter().map(|t| (t.0, t.1.as_slice()))));
}
% if request_value:
let mut json_mime_type = mime::Mime(mime::TopLevel::Application, mime::SubLevel::Json, Default::default());
let mut request_value_reader = io::Cursor::new(json::to_vec(&self.${property(REQUEST_VALUE_PROPERTY_NAME)}));
let request_size = request_value_reader.seek(io::SeekFrom::End(0)).unwrap();
request_value_reader.seek(io::SeekFrom::Start(0)).unwrap();
% endif
% if resumable_media_param:
let mut should_ask_dlg_for_url = false;
let mut upload_url_from_server;
let mut upload_url: Option<String> = None;
% endif
loop {
% if supports_scopes(auth):
let mut token = ${auth_call}.token(self.${api.properties.scopes}.keys());
if token.is_none() {
token = dlg.token();
}
if token.is_none() {
${delegate_finish}(false);
return Err(Error::MissingToken)
}
let auth_header = Authorization(oauth2::Scheme { token_type: oauth2::TokenType::Bearer,
access_token: token.unwrap().access_token });
% endif
% if request_value:
request_value_reader.seek(io::SeekFrom::Start(0)).unwrap();
% endif
let mut req_result = {
% if resumable_media_param:
if should_ask_dlg_for_url && (upload_url = dlg.upload_url()) == () && upload_url.is_some() {
should_ask_dlg_for_url = false;
upload_url_from_server = false;
let mut response = hyper::client::Response::new(Box::new(cmn::DummyNetworkStream));
match response {
Ok(ref mut res) => {
res.status = hyper::status::StatusCode::Ok;
res.headers.set(Location(upload_url.as_ref().unwrap().clone()))
}
_ => unreachable!(),
}
response
} else {
% endif
<%block filter="indent_by(resumable_media_param and 4 or 0)">\
% if request_value and simple_media_param:
let mut mp_reader: MultiPartReader = Default::default();
let (mut body_reader, content_type) = match protocol {
"${simple_media_param.protocol}" => {
mp_reader.reserve_exact(2);
${READER_SEEK | indent_all_but_first_by(5)}
mp_reader.add_part(&mut request_value_reader, request_size, json_mime_type.clone())
.add_part(&mut reader, size, reader_mime_type.clone());
let mime_type = mp_reader.mime_type();
(&mut mp_reader as &mut io::Read, ContentType(mime_type))
},
_ => (&mut request_value_reader as &mut io::Read, ContentType(json_mime_type.clone())),
};
% endif
let mut client = &mut *self.hub.client.borrow_mut();
let mut req = client.borrow_mut().request(${method_name_to_variant(m.httpMethod)}, url.as_slice())
.header(UserAgent(self.hub._user_agent.clone()))\
% if supports_scopes(auth):
.header(auth_header.clone())\
% endif
% if request_value:
% if not simple_media_param:
.header(ContentType(json_mime_type.clone()))
.header(ContentLength(request_size as u64))
.body(&mut request_value_reader)\
% else:
.header(content_type)
.body(&mut body_reader)\
% endif ## not simple_media_param
% endif
;
% if simple_media_param and not request_value:
if protocol == "${simple_media_param.protocol}" {
${READER_SEEK | indent_all_but_first_by(4)}
req = req.header(ContentType(reader_mime_type.clone()))
.header(ContentLength(size))
.body(&mut reader);
}
% endif ## media upload handling
% if resumable_media_param:
upload_url_from_server = true;
if protocol == "${resumable_media_param.protocol}" {
req = req.header(cmn::XUploadContentType(reader_mime_type.clone()));
}
% endif
dlg.pre_request();
req.send()
</%block>\
% if resumable_media_param:
}
% endif
};
match req_result {
Err(err) => {
if let oauth2::Retry::After(d) = dlg.http_error(&err) {
sleep(d);
continue;
}
${delegate_finish}(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()) {
sleep(d);
continue;
}
${delegate_finish}(false);
return Err(Error::Failure(res))
}
% if resumable_media_param:
if protocol == "${resumable_media_param.protocol}" {
${READER_SEEK | indent_all_but_first_by(6)}
let mut client = &mut *self.hub.client.borrow_mut();
let upload_result = {
let url = &res.headers.get::<Location>().expect("Location header is part of protocol").0;
if upload_url_from_server {
dlg.store_upload_url(url);
}
cmn::ResumableUploadHelper {
client: &mut client.borrow_mut(),
delegate: dlg,
start_at: if upload_url_from_server { Some(0) } else { None },
auth: &mut *self.hub.auth.borrow_mut(),
user_agent: &self.hub._user_agent,
auth_header: auth_header.clone(),
url: url,
reader: &mut reader,
media_type: reader_mime_type.clone(),
content_length: size
}.upload()
};
match upload_result {
None => {
${delegate_finish}(false);
return Err(Error::Cancelled)
}
Some(Err(err)) => {
## Do not ask the delgate again, as it was asked by the helper !
${delegate_finish}(false);
return Err(Error::HttpError(err))
}
## Now the result contains the actual resource, if any ... it will be
## decoded next
Some(Ok(upload_result)) => {
res = upload_result;
if !res.status.is_success() {
## delegate was called in upload() already - don't tell him again
${delegate_finish}(false);
return Err(Error::Failure(res))
}
}
}
}
% endif
% if response_schema:
## If 'alt' is not json, we cannot attempt to decode the response
let result_value = \
% if supports_download:
if enable_resource_parsing \
% endif
{
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(err));
}
}
}\
% if supports_download:
else { (res, Default::default()) }\
% endif
;
% else:
let result_value = res;
% endif
${delegate_finish}(true);
return Ok(result_value)
}
}
}
}
% for p in media_params:
${p.description | rust_doc_comment, indent_all_but_first_by(1)}
///
% for item_name, item in p.info.iteritems():
/// * *${split_camelcase_s(item_name)}*: ${isinstance(item, (list, tuple)) and put_and(enclose_in("'", item)) or str(item)}
% endfor
pub fn ${upload_action_fn(api.terms.upload_action, p.type.suffix)}<${mtype_param}>(self, ${p.type.arg_name}: ${mtype_param}, mime_type: mime::Mime) -> ${rtype}
where ${mtype_param}: ReadSeek {
self.${api.terms.action}(${p.type.arg_name}, mime_type, "${p.protocol}")
}
% endfor
</%def>

View File

@@ -0,0 +1,120 @@
<%!
from util import (put_and, rust_test_fn_invisible, rust_doc_test_norun, rust_doc_comment,
rb_type, singular, hub_type, mangle_ident, mb_type, method_params, property,
to_fqan, indent_all_but_first_by,
activity_input_type, TREF, IO_REQUEST, schema_to_required_property,
rust_copy_value_s, is_required_property, organize_params, REQUEST_VALUE_PROPERTY_NAME,
build_all_params, rb_type_params_s, hub_type_params_s, mb_type_params_s, mb_additional_type_params,
struct_type_bounds_s, METHODS_RESOURCE, SPACES_PER_TAB, prefix_all_but_first_with,
METHODS_BUILDER_MARKER_TRAIT, remove_empty_lines)
%>\
<%namespace name="util" file="util.mako"/>\
<%namespace name="lib" file="lib.mako"/>\
## Creates a Resource builder type
###############################################################################################
###############################################################################################
<%def name="new(resource, c)">\
<%
hub_type_name = hub_type(schemas, util.canonical_name())
rb_params = rb_type_params_s(resource, c)
ThisType = rb_type(resource) + rb_params
%>\
% if resource == METHODS_RESOURCE:
/// A builder providing access to all free methods, which are not associated with a particular resource.
% else:
/// A builder providing access to all methods supported on *${singular(resource)}* resources.
% endif
/// It is not used directly, but through the `${hub_type_name}` hub.
///
/// # Example
///
/// Instantiate a resource builder
///
<%block filter="rust_doc_test_norun, rust_doc_comment">\
${util.test_prelude()}\
<%block filter="rust_test_fn_invisible">\
${lib.test_hub(hub_type_name, comments=False)}\
// Usually you wouldn't bind this to a variable, but keep calling *CallBuilders*
// like ${put_and(sorted('`%s(...)`' % mangle_ident(f) for f in c.rta_map[resource]))}
// to build up your call.
let rb = hub.${mangle_ident(resource)}();
</%block>
</%block>
pub struct ${ThisType}
where ${struct_type_bounds_s()} {
hub: &'a ${hub_type_name}${hub_type_params_s()},
}
impl${rb_params} ${METHODS_BUILDER_MARKER_TRAIT} for ${ThisType} {}
## Builder Creators Methods ####################
impl${rb_params} ${ThisType} {
% for a in c.rta_map[resource]:
<%
m = c.fqan_map[to_fqan(c.rtc_map[resource], resource, a)]
RType = mb_type(resource, a)
# skip part if we have a request resource. Only resources can have parts
# that we can easily deduce
params, request_value = build_all_params(c, m)
required_props, optional_props, part_prop = organize_params(params, request_value)
method_args = ''
if required_props:
method_args = ', ' + ', '.join('%s: %s' % (mangle_ident(p.name), activity_input_type(schemas, p)) for p in required_props)
mb_tparams = mb_type_params_s(m)
# we would could have information about data requirements for each property in it's dict.
# for now, we just hardcode it, and treat the entries as way to easily change param names
assert len(api.properties) == 2, "Hardcoded for now, thanks to scope requirements"
type_params = ''
if mb_additional_type_params(m):
type_params = '<%s>' % ', '.join(mb_additional_type_params(m))
%>\
% if 'description' in m:
/// Create a builder to help you perform the following task:
///
${m.description | rust_doc_comment, indent_all_but_first_by(1)}
% endif
% if required_props:
///
/// # Arguments
///
% for p in required_props:
<%
arg_prefix = "/// * `" + p.name + "` - "
%>\
${arg_prefix}${p.get('description', "No description provided.")
| remove_empty_lines, prefix_all_but_first_with(' ' * SPACES_PER_TAB + '///' + ' ' * (len(arg_prefix) - len('///')))}
% endfor
% endif
pub fn ${mangle_ident(a)}${type_params}(&self${method_args}) -> ${RType}${mb_tparams} {
${RType} {
hub: self.hub,
% for p in required_props:
${property(p.name)}: ${rust_copy_value_s(mangle_ident(p.name), activity_input_type(schemas, p), p)},
% endfor
## auto-generate parts from request resources
% if part_prop and request_value:
${property(part_prop.name)}: ${mangle_ident(REQUEST_VALUE_PROPERTY_NAME)}.to_parts(),
% endif
% for p in optional_props:
${property(p.name)}: Default::default(),
% endfor
% for prop_key, custom_name in api.properties.iteritems():
% if prop_key == 'scopes' and (not auth or not auth.oauth2):
<% continue %>\
% endif
${custom_name}: Default::default(),
% endfor
}
}
% endfor ## for each activity
}
</%def>

View File

@@ -0,0 +1,170 @@
<%!
from util import (schema_markers, rust_doc_comment, mangle_ident, to_rust_type, put_and,
IO_TYPES, activity_split, enclose_in, REQUEST_MARKER_TRAIT, mb_type, indent_all_but_first_by,
NESTED_TYPE_SUFFIX, RESPONSE_MARKER_TRAIT, split_camelcase_s, METHODS_RESOURCE, unique_type_name,
PART_MARKER_TRAIT, canonical_type_name, TO_PARTS_MARKER, UNUSED_TYPE_MARKER)
default_traits = ('RustcEncodable', 'Clone', 'Default')
%>\
## Build a schema which must be an object
###################################################################################################################
###################################################################################################################
<%def name="_new_object(s, properties, c, allow_optionals)">\
<% struct = 'pub struct ' + unique_type_name(s.id) %>\
% if properties:
${struct} {
% for pn, p in properties.iteritems():
${p.get('description', 'no description provided') | rust_doc_comment, indent_all_but_first_by(1)}
% if pn != mangle_ident(pn):
#[serde(alias="${pn}")]
% endif
pub ${mangle_ident(pn)}: ${to_rust_type(schemas, s.id, pn, p, allow_optionals=allow_optionals)},
% endfor
}
% elif 'additionalProperties' in s:
${struct}(${to_rust_type(schemas, s.id, NESTED_TYPE_SUFFIX, s, allow_optionals=allow_optionals)});
% elif 'variant' in s:
<%
et = unique_type_name(s.id)
variant_type = lambda p: canonical_type_name(p.type_value)
%>\
pub enum ${et} {
% for p in s.variant.map:
${p.get('description', 'no description provided') | rust_doc_comment, indent_all_but_first_by(1)}
% if variant_type(p) != p.type_value:
#[serde(alias="${p.type_value}")]
% endif
${variant_type(p)}(${to_rust_type(schemas, s.id, None, p, allow_optionals=allow_optionals)}),
% endfor
}
impl Default for ${et} {
fn default() -> ${et} {
${et}::${variant_type(s.variant.map[0])}(Default::default())
}
}
% else: ## it's an empty struct, i.e. struct Foo;
${struct};
% endif ## 'properties' in s
</%def>
## Create new schema with everything.
## 's' contains the schema structure from json to build
###################################################################################################################
###################################################################################################################
<%def name="new(s, c)">\
<%
markers = schema_markers(s, c, transitive=True)
traits = ['Clone', 'Debug']
# default only works for structs, and 'variant' will be an enum
if 'variant' not in s:
traits.insert(0, 'Default')
allow_optionals = True
if REQUEST_MARKER_TRAIT in markers:
traits.append('Serialize')
if RESPONSE_MARKER_TRAIT in markers:
traits.append('Deserialize')
nt_markers = schema_markers(s, c, transitive=False)
if ( PART_MARKER_TRAIT in nt_markers
or RESPONSE_MARKER_TRAIT in nt_markers and REQUEST_MARKER_TRAIT not in nt_markers):
allow_optionals = False
# use optionals only when needed
# waiting for Default: https://github.com/rust-lang/rustc-serialize/issues/71
if s.type == 'any':
traits.remove('Default')
s_type = unique_type_name(s.id)
%>\
<%block filter="rust_doc_comment">\
${doc(s, c)}\
</%block>
#[derive(${', '.join(traits)})]
% if s.type == 'object':
${_new_object(s, s.get('properties'), c, allow_optionals)}\
% elif s.type == 'array':
% if s.items.get('type') != 'object':
pub struct ${s_type}(${to_rust_type(schemas, s.id, NESTED_TYPE_SUFFIX, s, allow_optionals=allow_optionals)});
% else:
${_new_object(s, s.items.get('properties'), c, allow_optionals)}\
% endif ## array item != 'object'
% elif s.type == 'any':
## waiting for Default: https://github.com/rust-lang/rustc-serialize/issues/71
pub struct ${s_type}(json::Value);
impl Default for ${s_type} {
fn default() -> ${s_type} {
${s_type}(json::Value::Null)
}
}
% else:
<% assert False, "Object not handled: %s" % str(s) %>\
% endif ## type == ?
% for marker_trait in nt_markers:
% if marker_trait not in (TO_PARTS_MARKER, UNUSED_TYPE_MARKER):
impl ${marker_trait} for ${s_type} {}
% endif
% endfor
% if TO_PARTS_MARKER in nt_markers and allow_optionals:
impl ${TO_PARTS_MARKER} for ${s_type} {
/// Return a comma separated list of members that are currently set, i.e. for which `self.member.is_some()`.
/// The produced string is suitable for use as a parts list that indicates the parts you are sending, and/or
/// the parts you want to see in the server response.
fn to_parts(&self) -> String {
let mut r = String::new();
% for pn, p in s.properties.iteritems():
<%
mn = 'self.' + mangle_ident(pn)
rt = to_rust_type(schemas, s.id, pn, p, allow_optionals=allow_optionals)
check = 'is_some()'
if rt.startswith('Vec') or rt.startswith('HashMap'):
check = 'len() > 0'
%>\
if ${mn}.${check} { r = r + "${pn},"; }
% endfor
## remove (possibly non-existing) trailing comma
r.pop();
r
}
}
% endif
</%def>
#########################################################################################################
#########################################################################################################
<%def name="doc(s, c)">\
${s.get('description', 'There is no detailed description.')}
% if s.id in c.sta_map:
# 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 ${put_and(enclose_in('*', IO_TYPES))}).
% for a, iot in c.sta_map[s.id].iteritems():
<%
category, name, method = activity_split(a)
name_suffix = ' ' + split_camelcase_s(name)
if name == METHODS_RESOURCE:
name_suffix = ''
struct_url = 'struct.' + mb_type(name, method) + '.html'
method_name = ' '.join(split_camelcase_s(method).split('.')) + name_suffix
value_type = '|'.join(iot) or 'none'
%>\
* [${method_name}](${struct_url}) (${value_type})
% endfor
% else:
This type is not used in any activity, and only used as *part* of another schema.
% endif
% if s.type != 'object':
## for some reason, it's not shown in rustdoc ...
The contained type is `${to_rust_type(schemas, s.id, s.id, s)}`.
%endif
</%def>