From a5e675e7a958327938a31ec38ddebfaf58af9f42 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Mon, 2 Mar 2015 19:07:15 +0100 Subject: [PATCH] feat(schema): generating valid rust from schemas It's very nice, even though there is some more work to be done here. It's just the beginning ... . --- src/mako/lib.rs.mako | 20 ++++++++++- src/mako/lib/schema.mako | 25 +++++++++++++ src/mako/lib/util.py | 76 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 120 insertions(+), 1 deletion(-) create mode 100644 src/mako/lib/schema.mako diff --git a/src/mako/lib.rs.mako b/src/mako/lib.rs.mako index cf73e30c59..fefa2571ec 100644 --- a/src/mako/lib.rs.mako +++ b/src/mako/lib.rs.mako @@ -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 />\ +#![allow(non_snake_case)] + extern crate cmn; extern crate "rustc-serialize" as rustc_serialize; -extern crate "yup-oauth2" as oauth2; \ No newline at end of file +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 \ No newline at end of file diff --git a/src/mako/lib/schema.mako b/src/mako/lib/schema.mako new file mode 100644 index 0000000000..d24c2a1b73 --- /dev/null +++ b/src/mako/lib/schema.mako @@ -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)}\ + +#[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 name="doc(s)">\ +${s.get('description', 'There is no detailed description.')} + \ No newline at end of file diff --git a/src/mako/lib/util.py b/src/mako/lib/util.py index 94e4f331d0..926d55ef23 100644 --- a/src/mako/lib/util.py +++ b/src/mako/lib/util.py @@ -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) @@ -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" % (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 +