mirror of
https://github.com/OMGeeky/google-apis-rs.git
synced 2026-02-23 15:49:49 +01:00
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:
13
src/mako/api/README.md.mako
Normal file
13
src/mako/api/README.md.mako
Normal 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
149
src/mako/api/lib.rs.mako
Normal 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
369
src/mako/api/lib/lib.mako
Normal 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>
|
||||
864
src/mako/api/lib/mbuild.mako
Normal file
864
src/mako/api/lib/mbuild.mako
Normal 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>
|
||||
120
src/mako/api/lib/rbuild.mako
Normal file
120
src/mako/api/lib/rbuild.mako
Normal 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>
|
||||
170
src/mako/api/lib/schema.mako
Normal file
170
src/mako/api/lib/schema.mako
Normal 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>
|
||||
Reference in New Issue
Block a user