docs(mbuild): method builder call example

With nearly fully randomized examples to show how it can be done.
It's quite nice to see actual calls, using everything required to get
a call. The only thing the user has to manage is to fill in actual
values.

But, it also shows that our builder pattern doesn't work yet due to ...
you guessed it ... lifetime issues :D
This commit is contained in:
Sebastian Thiel
2015-03-05 15:42:37 +01:00
parent f2dda421e6
commit bfa20a18c8
4 changed files with 2819 additions and 2445 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -36,6 +36,26 @@ use std::default::Default;
pub use cmn::{Hub, ResourceMethodsBuilder, MethodBuilder, Resource, Part, ResponseResult, RequestValue, NestedType};
// ##############
// UTILITIES ###
// ############
/// This macro is advertised in the documentation, which is why we deliver it as well
#[macro_export]
macro_rules! map(
{ $($key:expr => $value:expr),+ } => {
{
let mut m = ::std::collections::HashMap::new();
$(
m.insert($key, $value);
)+
m
}
};
);
// ########
// HUB ###
// ######

View File

@@ -1,10 +1,11 @@
<%!
from util import (put_and, rust_test_fn_invisible, rust_doc_test_norun, rust_doc_comment,
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, method_io, IO_REQUEST,
schema_to_required_property, rust_copy_value_s, is_required_property,
hide_rust_doc_test, build_all_params, REQUEST_VALUE_PROPERTY_NAME)
hide_rust_doc_test, build_all_params, REQUEST_VALUE_PROPERTY_NAME, organize_params,
indent_by, to_rust_type, rnd_arg_val_for_type)
%>\
<%namespace name="util" file="util.mako"/>\
<%namespace name="lib" file="lib.mako"/>\
@@ -14,12 +15,29 @@
###############################################################################################
<%def name="new(resource, method, c)">\
<%
hub_type_name = hub_type(canonicalName)
m = c.fqan_map[to_fqan(name, resource, method)]
# an identifier for a property. We prefix them to prevent clashes with the setters
ThisType = mb_type(resource, method) + "<'a, C, NC, A>"
hub_type_name = hub_type(canonicalName)
m = c.fqan_map[to_fqan(name, resource, method)]
# an identifier for a property. We prefix them to prevent clashes with the setters
ThisType = mb_type(resource, method) + "<'a, C, NC, A>"
params, request_value = build_all_params(schemas, c, m, IO_REQUEST, REQUEST_VALUE_PROPERTY_NAME)
params, request_value = build_all_params(schemas, c, m, IO_REQUEST, REQUEST_VALUE_PROPERTY_NAME)
required_props, optional_props, part_prop = organize_params(params, request_value)
# rvfrt = random value for rust type
rvfrt = lambda spn, sp, sn=None: rnd_arg_val_for_type(to_rust_type(sn, spn, sp, allow_optionals=False))
rb_name = 'request' # 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 v.endswith('"'):
v = '"%s"' % p.name
required_args.append(v)
# end for each required property
required_args = ', '.join(required_args)
%>\
% if 'description' in m:
${m.description | rust_doc_comment}
@@ -34,17 +52,37 @@ ${m.description | rust_doc_comment}
///
<%block filter="rust_doc_test_norun, rust_doc_comment">\
${capture(util.test_prelude) | hide_rust_doc_test}\
<%block filter="rust_test_fn_invisible">\
${capture(lib.test_hub, hub_type_name, comments=False) | hide_rust_doc_test}
// Usually you wouldn't bind this to a variable, but keep calling methods
// to setup your call.
## % for p
// let mb = hub.${resource}().${mangle_ident(method)}(...);
% if request_value:
// As the method needs a request, you would usually fill it with the desired information.
// What can actually be filled in depends on the actual call - the following is just a
// random selection of properties ! Values are random and not representative !
let mut ${rb_name}: ${request_value.id} = Default::default();
% for spn, sp in request_value.get('properties', dict()).iteritems():
## ${to_rust_type(request_value.id, spn, sp, allow_optionals=False)}
${rb_name}.${mangle_ident(spn)} = ${rvfrt(spn, sp)};
% if loop.index == 3:
// ... and so forth ...
<% break %>
% endif
% endfor
% endif
// Even though you wouldn't bind this to a variable, you can configure optional parameters
// by calling the respective setters.
// Values are random and not representative !
let mut mb = hub.${resource}().${mangle_ident(method)}(${required_args})\
% for p in optional_props:
<%block filter="indent_by(8)">\
.${mangle_ident(p.name)}(${rvfrt(p.name, p)})\
</%block>\
% endfor
;
// Finally, execute your call and process the result
// TODO: comment in once args are properly setup !
// mb.do()
mb.${api.terms.action}()
</%block>
</%block>
pub struct ${ThisType}
@@ -55,10 +93,10 @@ pub struct ${ThisType}
hub: &'a ${hub_type_name}<C, NC, A>,
## PROPERTIES ###############
% for p in params:
${property(p.name)}:\
% if is_required_property(p):
${property(p.name)}:\
% if is_required_property(p):
${activity_rust_type(p, allow_optionals=False)},
% else:
% else:
${activity_rust_type(p)},
% endif
% endfor
@@ -68,12 +106,12 @@ impl<'a, C, NC, A> MethodBuilder for ${ThisType} {}
impl<'a, C, NC, A> ${ThisType} {
/// Perform the operation you have build so far.
/// Can only be called once !
/// TODO: Build actual call
pub fn ${api.terms.action}(self) {
/// Perform the operation you have build so far.
/// Can only be called once !
/// TODO: Build actual call
pub fn ${api.terms.action}(self) {
}
}
## SETTERS ###############
% for p in params:
@@ -88,38 +126,38 @@ ${self._setter(resource, method, m, p, ThisType, c)}\
###############################################################################################
<%def name="_setter(resource, method, m, p, ThisType, c)">\
<%
InType = activity_input_type(p)
InType = activity_input_type(p)
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')
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):
new_value_copied = 'Some(%s)' % new_value_copied
value_name = 'new_value'
new_value_copied = rust_copy_value_s(value_name, InType, p)
if not is_required_property(p):
new_value_copied = 'Some(%s)' % new_value_copied
%>\
/// Sets the *${split_camelcase_s(p.name)}* ${get_word(p, 'location')}property to the given value.
///
% 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 'description' in p:
/// Sets the *${split_camelcase_s(p.name)}* ${get_word(p, 'location')}property to the given value.
///
% 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 'description' in p:
${p.description | rust_doc_comment, indent_all_but_first_by(1)}
% endif
pub fn ${mangle_ident(p.name)}(&mut self, ${value_name}: ${InType}) -> &mut ${ThisType} {
self.${property(p.name)} = ${new_value_copied};
return self;
}
% endif
pub fn ${mangle_ident(p.name)}(&mut self, ${value_name}: ${InType}) -> &mut ${ThisType} {
self.${property(p.name)} = ${new_value_copied};
return self;
}
</%def>

View File

@@ -1,6 +1,9 @@
import re
from random import (randint, random, choice, seed)
import collections
seed(7337)
re_linestart = re.compile('^', flags=re.MULTILINE)
re_first_4_spaces = re.compile('^ {1,4}', flags=re.MULTILINE)
@@ -14,6 +17,14 @@ TYPE_MAP = {'boolean' : 'bool',
'array' : 'Vec',
'string' : 'String',
'object' : 'HashMap'}
_words = "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.".split(' ')
RUST_TYPE_RND_MAP = {'bool': lambda: str(bool(randint(0, 1))).lower(),
'u32' : lambda: randint(0, 100),
'f64' : lambda: random(),
'i32' : lambda: randint(-101, -1),
'String': lambda: '"%s"' % choice(_words),
}
TREF = '$ref'
IO_RESPONSE = 'response'
IO_REQUEST = 'request'
@@ -81,7 +92,13 @@ def indent_all_but_first_by(tabs):
# useful if you have defs embedded in an unindent block - they need to counteract.
# It's a bit itchy, but logical
def indent(s):
return re_linestart.sub(' ', s)
return re_linestart.sub(' ' * SPACES_PER_TAB, s)
# indent by given amount of spaces
def indent_by(n):
def indent_inner(s):
return re_linestart.sub(' ' * n, s)
return indent_inner
# return s, with trailing newline
def trailing_newline(s):
@@ -466,3 +483,10 @@ def get_word(d, n, e = ''):
def property(n):
return '_' + mangle_ident(n)
# given a rust type-name (no optional, as from to_rust_type), you will get a suitable random default value
# as string suitable to be passed as reference (or copy, where applicable)
def rnd_arg_val_for_type(tn):
try:
return str(RUST_TYPE_RND_MAP[tn]())
except KeyError:
return '&Default::default()'