Skip to content

Commit

Permalink
feat(schema): generating valid rust from schemas
Browse files Browse the repository at this point in the history
It's very nice, even though there is some more work to be done here.
It's just the beginning ... .
  • Loading branch information
Byron committed Mar 2, 2015
1 parent 939e631 commit a5e675e
Show file tree
Hide file tree
Showing 3 changed files with 120 additions and 1 deletion.
20 changes: 19 additions & 1 deletion src/mako/lib.rs.mako
Original file line number Diff line number Diff line change
@@ -1,9 +1,27 @@
<% import util %>\
<%namespace name="lib" file="lib/lib.mako"/>\
<%namespace name="mutil" file="lib/util.mako"/>\
<%namespace name="schema" file="lib/schema.mako"/>\
<%block filter="util.rust_module_doc_comment">\
<%lib:docs />\
</%block>
#![allow(non_snake_case)]
extern crate cmn;
extern crate "rustc-serialize" as rustc_serialize;
extern crate "yup-oauth2" as oauth2;
extern crate "yup-oauth2" as oauth2;
use std::default::Default;
use std::collections::HashMap;
## SCHEMAS - normal ones
% for s in schemas.values():
${schema.new(s)}
% endfor
## SCHEMAS - nested
## some schemas are only used once and basically internal types.
## We have to find them and process them as normal types
% for s in util.iter_nested_types(schemas):
${schema.new(s)}
% endfor
25 changes: 25 additions & 0 deletions src/mako/lib/schema.mako
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<%! import util %>\
## Create new schema with everything.
## 's' contains the schema structure from json to build
<%def name="new(s)">\
<% assert s.type == "object" %>\
<%block filter="util.rust_doc_comment">\
${doc(s)}\
</%block>
#[derive(RustcEncodable, RustcDecodable, Default, Clone)]
pub struct ${s.id}\
% if 'properties' in s:
{
% for pn, p in s.properties.iteritems():
${p.get('description', 'no description provided') | util.rust_doc_comment}
pub ${util.mangle_ident(pn)}: ${util.to_rust_type(s.id, pn, p)},
% endfor
}
% else:
;
% endif
</%def>

<%def name="doc(s)">\
${s.get('description', 'There is no detailed description.')}
</%def>
76 changes: 76 additions & 0 deletions src/mako/lib/util.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,18 @@
import re
re_linestart = re.compile('^', flags=re.MULTILINE)

USE_FORMAT = 'use_format_field'
TYPE_MAP = {'boolean' : 'bool',
'integer' : USE_FORMAT,
'number' : USE_FORMAT,
'uint32' : 'u32',
'double' : 'f64',
'int32' : 'i32',
'array' : 'Vec',
'string' : 'String',
'object' : 'HashMap'}
TREF = '$ref'

# rust module doc comment filter
def rust_module_doc_comment(s):
return re_linestart.sub('//! ', s)
Expand Down Expand Up @@ -29,3 +41,67 @@ def estr(l):
# build a full library name (non-canonical)
def library_name(name, version):
return name + to_api_version(version)


def nested_type_name(sn, pn):
return sn + pn[:1].upper() + pn[1:]

# Make properties which are reserved keywords usable
def mangle_ident(n):
if n == 'type':
return n + '_'
return n

# map a json type to an rust type
# sn = schema name
# pn = property name
# t = type dict
def to_rust_type(sn, pn, t, allow_optionals=True):
def nested_type(nt):
if nt.get('items', None) is not None:
nt = nt.items
elif nt.get('additionalProperties'):
nt = nt.additionalProperties
else:
assert(is_nested_type(nt))
# It's a nested type - we take it literally like $ref, but generate a name for the type ourselves
# This of course assumes
return nested_type_name(sn, pn)
return to_rust_type(sn, pn, nt, allow_optionals=False)

# unconditionally handle $ref types, which should point to another schema.
if TREF in t:
tn = t[TREF]
if allow_optionals:
return "Option<%s>" % tn
return tn
try:
rust_type = TYPE_MAP[t.type]
if t.type == 'array':
rust_type = "%s<%s>" % (rust_type, nested_type(t))
elif t.type == 'object':
rust_type = "%s<String, %s>" % (rust_type, nested_type(t))
elif rust_type == USE_FORMAT:
rust_type = TYPE_MAP[t.format]
return rust_type
except KeyError as err:
raise AssertionError("%s: Property type '%s' unknown - add new type mapping: %s" % (str(err), t.type, str(t)))
except AttributeError as err:
raise AssertionError("%s: unknown dict layout: %s" % (str(err), t))

def is_nested_type(t):
return 'type' in t and t.type == 'object' and 'additionalProperties' not in t

# return an iterator yielding fake-schemas that identify a nested type
def iter_nested_types(schemas):
for s in schemas.values():
if 'properties' not in s:
continue
for pn, p in s.properties.iteritems():
if is_nested_type(p):
ns = p.copy()
ns.id = nested_type_name(s.id, pn)
yield ns
# end for ach property
# end for aech schma

0 comments on commit a5e675e

Please sign in to comment.