feat(types): prevent duplicate schema types

These could clash with types we import from Cmn. When that happens,
just a single list must be adjusted for a fix, see
`unique_type_name`

Fixes #26
This commit is contained in:
Sebastian Thiel
2015-03-19 19:09:40 +01:00
parent 508d14eafb
commit 3a15430339
5 changed files with 44 additions and 33 deletions

View File

@@ -36,7 +36,7 @@ extern crate "yup-oauth2" as oauth2;
extern crate mime; extern crate mime;
extern crate url; extern crate url;
pub mod cmn; mod cmn;
use std::collections::HashMap; use std::collections::HashMap;
use std::cell::RefCell; use std::cell::RefCell;
@@ -49,7 +49,7 @@ use std::io;
use std::fs; use std::fs;
use std::old_io::timer::sleep; use std::old_io::timer::sleep;
use cmn::{Hub, ReadSeek, Part, ResponseResult, RequestValue, NestedType, Delegate, DefaultDelegate}; pub use cmn::{MultiPartReader, MethodInfo, Result, MethodBuilder, Hub, ReadSeek, Part, ResponseResult, RequestValue, NestedType, Delegate, DefaultDelegate};
// ############## // ##############

View File

@@ -5,7 +5,7 @@
find_fattest_resource, build_all_params, pass_through, parts_from_params, find_fattest_resource, build_all_params, pass_through, parts_from_params,
REQUEST_MARKER_TRAIT, RESPONSE_MARKER_TRAIT, supports_scopes, to_api_version, REQUEST_MARKER_TRAIT, RESPONSE_MARKER_TRAIT, supports_scopes, to_api_version,
to_fqan, METHODS_RESOURCE, ADD_PARAM_MEDIA_EXAMPLE, PROTOCOL_TYPE_INFO, enclose_in, to_fqan, METHODS_RESOURCE, ADD_PARAM_MEDIA_EXAMPLE, PROTOCOL_TYPE_INFO, enclose_in,
upload_action_fn) %>\ upload_action_fn, unique_type_name) %>\
<%namespace name="util" file="util.mako"/>\ <%namespace name="util" file="util.mako"/>\
<%namespace name="mbuild" file="mbuild.mako"/>\ <%namespace name="mbuild" file="mbuild.mako"/>\
@@ -78,7 +78,7 @@ It seems there is nothing you can do here ... .
sn = singular(canonical_type_name(r)) sn = singular(canonical_type_name(r))
if sn in schemas: if sn in schemas:
md_resource = link(md_resource, 'struct.%s.html' % singular(canonical_type_name(r))) md_resource = link(md_resource, 'struct.%s.html' % unique_type_name(singular(canonical_type_name(r))))
%>\ %>\
* ${md_resource} (${put_and(md_methods)}) * ${md_resource} (${put_and(md_methods)})
% endfor ## each resource activity % endfor ## each resource activity

View File

@@ -10,7 +10,7 @@
METHOD_BUILDER_MARKERT_TRAIT, pass_through, markdown_rust_block, parts_from_params, METHOD_BUILDER_MARKERT_TRAIT, pass_through, markdown_rust_block, parts_from_params,
DELEGATE_PROPERTY_NAME, struct_type_bounds_s, supports_scopes, scope_url_to_variant, 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, re_find_replacements, ADD_PARAM_FN, ADD_PARAM_MEDIA_EXAMPLE, upload_action_fn, METHODS_RESOURCE,
method_name_to_variant) method_name_to_variant, unique_type_name)
def get_parts(part_prop): def get_parts(part_prop):
if not part_prop: if not part_prop:
@@ -67,7 +67,7 @@ ${m.description | rust_doc_comment}
/// `${ADD_PARAM_MEDIA_EXAMPLE}`. /// `${ADD_PARAM_MEDIA_EXAMPLE}`.
% if response_schema: % if response_schema:
/// Please note that due to missing multi-part support on the server side, you will only receive the media, /// Please note that due to missing multi-part support on the server side, you will only receive the media,
/// but not the `${response_schema.id}` structure that you would usually get. The latter will be a default value. /// but not the `${unique_type_name(response_schema.id)}` structure that you would usually get. The latter will be a default value.
% endif % endif
/// ///
% endif ## supports media download % endif ## supports media download
@@ -125,7 +125,7 @@ pub struct ${ThisType}
% endif % endif
} }
impl${mb_tparams} cmn::${METHOD_BUILDER_MARKERT_TRAIT} for ${ThisType} {} impl${mb_tparams} ${METHOD_BUILDER_MARKERT_TRAIT} for ${ThisType} {}
impl${mb_tparams} ${ThisType} where ${', '.join(mb_type_bounds())} { impl${mb_tparams} ${ThisType} where ${', '.join(mb_type_bounds())} {
@@ -297,14 +297,17 @@ ${self._setter_fn(resource, method, m, p, part_prop, ThisType, c)}\
hide_filter = show_all and pass_through or hide_rust_doc_test 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_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 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">\ <%block filter="test_block_filter">\
${capture(util.test_prelude) | hide_filter}\ ${capture(util.test_prelude) | hide_filter}\
% if request_value: % if request_value:
use ${util.library_name()}::${request_value.id}; use ${util.library_name()}::${request_value_type};
% endif % endif
% if handle_result: % if handle_result:
use ${util.library_name()}::cmn::Result; use ${util.library_name()}::Result;
% endif % endif
% if media_params: % if media_params:
use std::fs; use std::fs;
@@ -315,7 +318,7 @@ ${capture(lib.test_hub, hub_type_name, comments=show_all) | hide_filter}
// As the method needs a request, you would usually fill it with the desired information // 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 ! // into the respective structure. Some of the parts shown here might not be applicable !
// ${random_value_warning} // ${random_value_warning}
let mut ${rb_name}: ${request_value.id} = Default::default(); let mut ${rb_name}: ${request_value_type} = Default::default();
% for spn, sp in request_value.get('properties', dict()).iteritems(): % for spn, sp in request_value.get('properties', dict()).iteritems():
% if parts is not None and spn not in parts: % if parts is not None and spn not in parts:
<% continue %> <% continue %>
@@ -331,7 +334,6 @@ let mut ${rb_name}: ${request_value.id} = Default::default();
else: else:
assignment = 'Some(%s);' % assignment assignment = 'Some(%s);' % assignment
%>\ %>\
## ${to_rust_type(request_value.id, spn, sp, allow_optionals=False)}
${rb_name}.${mangle_ident(spn)} = ${assignment} ${rb_name}.${mangle_ident(spn)} = ${assignment}
% endfor % endfor
@@ -382,7 +384,7 @@ match result {
where = '' where = ''
qualifier = 'pub ' qualifier = 'pub '
add_args = '' add_args = ''
rtype = 'cmn::Result<hyper::client::Response>' rtype = 'Result<hyper::client::Response>'
response_schema = method_response(c, m) response_schema = method_response(c, m)
supports_download = m.get('supportsMediaDownload', False); supports_download = m.get('supportsMediaDownload', False);
@@ -390,7 +392,7 @@ match result {
if response_schema: if response_schema:
if not supports_download: if not supports_download:
reserved_params = ['alt'] reserved_params = ['alt']
rtype = 'cmn::Result<(hyper::client::Response, %s)>' % (response_schema.id) rtype = 'Result<(hyper::client::Response, %s)>' % (unique_type_name(response_schema.id))
mtype_param = 'RS' mtype_param = 'RS'
mtype_where = 'ReadSeek' mtype_where = 'ReadSeek'
@@ -482,7 +484,7 @@ match result {
use std::io::{Read, Seek}; use std::io::{Read, Seek};
use hyper::header::{ContentType, ContentLength, Authorization, UserAgent}; use hyper::header::{ContentType, ContentLength, Authorization, UserAgent};
if let Some(ref mut d) = ${delegate} { if let Some(ref mut d) = ${delegate} {
d.begin(cmn::MethodInfo { id: "${m.id}", d.begin(MethodInfo { id: "${m.id}",
http_method: ${method_name_to_variant(m.httpMethod)} }); http_method: ${method_name_to_variant(m.httpMethod)} });
} }
let mut params: Vec<(&str, String)> = Vec::with_capacity((${len(params) + len(reserved_params)} + ${paddfields}.len())); let mut params: Vec<(&str, String)> = Vec::with_capacity((${len(params) + len(reserved_params)} + ${paddfields}.len()));
@@ -522,7 +524,7 @@ match result {
for &field in [${', '.join(enclose_in('"', reserved_params + [p.name for p in field_params]))}].iter() { for &field in [${', '.join(enclose_in('"', reserved_params + [p.name for p in field_params]))}].iter() {
if ${paddfields}.contains_key(field) { if ${paddfields}.contains_key(field) {
${delegate_finish} ${delegate_finish}
return cmn::Result::FieldClash(field); return Result::FieldClash(field);
} }
} }
for (name, value) in ${paddfields}.iter() { for (name, value) in ${paddfields}.iter() {
@@ -584,7 +586,7 @@ else {
Some(value) => params.push(("key", value)), Some(value) => params.push(("key", value)),
None => { None => {
${delegate_finish} ${delegate_finish}
return cmn::Result::MissingAPIKey return Result::MissingAPIKey
} }
} }
% else: % else:
@@ -658,7 +660,7 @@ else {
}} }}
if token.is_none() { if token.is_none() {
${delegate_finish} ${delegate_finish}
return cmn::Result::MissingToken return Result::MissingToken
} }
let auth_header = Authorization(token.unwrap().access_token); let auth_header = Authorization(token.unwrap().access_token);
% endif % endif
@@ -666,7 +668,7 @@ else {
request_value_reader.seek(io::SeekFrom::Start(0)).unwrap(); request_value_reader.seek(io::SeekFrom::Start(0)).unwrap();
% endif % endif
% if request_value and simple_media_param: % if request_value and simple_media_param:
let mut mp_reader: cmn::MultiPartReader = Default::default(); let mut mp_reader: MultiPartReader = Default::default();
let (mut body_reader, content_type) = match ${simple_media_param.type.arg_name}.as_mut() { let (mut body_reader, content_type) = match ${simple_media_param.type.arg_name}.as_mut() {
Some(&mut (ref mut reader, ref mime)) => { Some(&mut (ref mut reader, ref mime)) => {
mp_reader.reserve_exact(2); mp_reader.reserve_exact(2);
@@ -721,7 +723,7 @@ else {
} }
} }
${delegate_finish} ${delegate_finish}
return cmn::Result::HttpError(err) return Result::HttpError(err)
} }
Ok(mut res) => { Ok(mut res) => {
if !res.status.is_success() { if !res.status.is_success() {
@@ -735,7 +737,7 @@ else {
} }
} }
${delegate_finish} ${delegate_finish}
return cmn::Result::Failure(res) return Result::Failure(res)
} }
% if response_schema: % if response_schema:
## If 'alt' is not json, we cannot attempt to decode the response ## If 'alt' is not json, we cannot attempt to decode the response
@@ -756,7 +758,7 @@ if enable_resource_parsing \
let result_value = res; let result_value = res;
% endif % endif
${delegate_finish} ${delegate_finish}
return cmn::Result::Success(result_value) return Result::Success(result_value)
} }
} }
} }

View File

@@ -1,7 +1,7 @@
<%! <%!
from util import (schema_markers, rust_doc_comment, mangle_ident, to_rust_type, put_and, 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, 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) NESTED_TYPE_SUFFIX, RESPONSE_MARKER_TRAIT, split_camelcase_s, METHODS_RESOURCE, unique_type_name)
default_traits = ('RustcEncodable', 'Clone', 'Default') default_traits = ('RustcEncodable', 'Clone', 'Default')
%>\ %>\
@@ -9,7 +9,7 @@
################################################################################################################### ###################################################################################################################
################################################################################################################### ###################################################################################################################
<%def name="_new_object(s, properties, c)">\ <%def name="_new_object(s, properties, c)">\
<% struct = 'pub struct ' + s.id %>\ <% struct = 'pub struct ' + unique_type_name(s.id) %>\
% if properties: % if properties:
${struct} { ${struct} {
% for pn, p in properties.iteritems(): % for pn, p in properties.iteritems():
@@ -41,6 +41,8 @@ ${struct};
## waiting for Default: https://github.com/rust-lang/rustc-serialize/issues/71 ## waiting for Default: https://github.com/rust-lang/rustc-serialize/issues/71
if s.type == 'any': if s.type == 'any':
traits.remove('Default') traits.remove('Default')
s_type = unique_type_name(s.id)
%>\ %>\
<%block filter="rust_doc_comment">\ <%block filter="rust_doc_comment">\
${doc(s, c)}\ ${doc(s, c)}\
@@ -50,17 +52,17 @@ ${doc(s, c)}\
${_new_object(s, s.get('properties'), c)}\ ${_new_object(s, s.get('properties'), c)}\
% elif s.type == 'array': % elif s.type == 'array':
% if s.items.get('type') != 'object': % if s.items.get('type') != 'object':
pub struct ${s.id}(${to_rust_type(schemas, s.id, NESTED_TYPE_SUFFIX, s)}); pub struct ${s_type}(${to_rust_type(schemas, s.id, NESTED_TYPE_SUFFIX, s)});
% else: % else:
${_new_object(s, s.items.get('properties'), c)}\ ${_new_object(s, s.items.get('properties'), c)}\
% endif ## array item != 'object' % endif ## array item != 'object'
% elif s.type == 'any': % elif s.type == 'any':
## waiting for Default: https://github.com/rust-lang/rustc-serialize/issues/71 ## waiting for Default: https://github.com/rust-lang/rustc-serialize/issues/71
pub struct ${s.id}(rustc_serialize::json::Json); pub struct ${s_type}(rustc_serialize::json::Json);
impl Default for ${s.id} { impl Default for ${s_type} {
fn default() -> ${s.id} { fn default() -> ${s_type} {
${s.id}(rustc_serialize::json::Json::Null) ${s_type}(rustc_serialize::json::Json::Null)
} }
} }
% else: % else:
@@ -68,11 +70,11 @@ impl Default for ${s.id} {
% endif ## type == ? % endif ## type == ?
% for marker_trait in markers: % for marker_trait in markers:
impl ${marker_trait} for ${s.id} {} impl ${marker_trait} for ${s_type} {}
% endfor % endfor
% if REQUEST_MARKER_TRAIT in markers and 'properties' in s: % if REQUEST_MARKER_TRAIT in markers and 'properties' in s:
impl ${s.id} { impl ${s_type} {
/// Return a comma separated list of members that are currently set, i.e. for which `self.member.is_some()`. /// 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 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. /// the parts you want to see in the server response.

View File

@@ -35,6 +35,7 @@ RESERVED_WORDS = set(('abstract', 'alignof', 'as', 'become', 'box', 'break', 'co
'return', 'sizeof', 'static', 'self', 'struct', 'super', 'true', 'trait', 'type', 'typeof', 'return', 'sizeof', 'static', 'self', 'struct', 'super', 'true', 'trait', 'type', 'typeof',
'unsafe', 'unsized', 'use', 'virtual', 'where', 'while', 'yield')) 'unsafe', 'unsized', 'use', 'virtual', 'where', 'while', 'yield'))
RESERVED_TYPES = set(("Result", ))
_words = [w.strip(',') for w in "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(' ')] _words = [w.strip(',') for w in "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(), RUST_TYPE_RND_MAP = {'bool': lambda: str(bool(randint(0, 1))).lower(),
@@ -283,7 +284,7 @@ def _assure_unique_type_name(schemas, tn):
if tn in schemas: if tn in schemas:
tn += 'Nested' tn += 'Nested'
assert tn not in schemas assert tn not in schemas
return tn return unique_type_name(tn)
# map a json type to an rust type # map a json type to an rust type
# sn = schema name # sn = schema name
@@ -305,7 +306,7 @@ def to_rust_type(schemas, sn, pn, t, allow_optionals=True):
def wrap_type(tn): def wrap_type(tn):
if allow_optionals: if allow_optionals:
tn = "Option<%s>" % tn tn = "Option<%s>" % tn
return tn return unique_type_name(tn)
# unconditionally handle $ref types, which should point to another schema. # unconditionally handle $ref types, which should point to another schema.
if TREF in t: if TREF in t:
@@ -314,7 +315,7 @@ def to_rust_type(schemas, sn, pn, t, allow_optionals=True):
# usually is on on the first call, and off when recursion is involved. # usually is on on the first call, and off when recursion is involved.
tn = t[TREF] tn = t[TREF]
if allow_optionals and tn == sn: if allow_optionals and tn == sn:
tn = 'Box<%s>' % tn tn = 'Box<%s>' % unique_type_name(tn)
return wrap_type(tn) return wrap_type(tn)
try: try:
rust_type = TYPE_MAP[t.type] rust_type = TYPE_MAP[t.type]
@@ -799,6 +800,12 @@ def mb_type_params_s(m):
def mb_additional_type_params(m): def mb_additional_type_params(m):
return [] return []
# check type_name against a list of reserved types, and return a possibly rename type_name to prevent a clash
def unique_type_name(type_name):
if type_name in RESERVED_TYPES:
type_name += 'Type'
return type_name
# return type name for a method on the given resource # return type name for a method on the given resource
def mb_type(r, m): def mb_type(r, m):
return "%s%sMethodBuilder" % (singular(canonical_type_name(r)), dot_sep_to_canonical_type_name(m)) return "%s%sMethodBuilder" % (singular(canonical_type_name(r)), dot_sep_to_canonical_type_name(m))