Skip to content

Commit

Permalink
feat(traits): add marker traits to schema types
Browse files Browse the repository at this point in the history
Based on their involvement in activities.
It nearly works perfectly.
  • Loading branch information
Byron committed Mar 3, 2015
1 parent f4030f0 commit c1eeee0
Show file tree
Hide file tree
Showing 5 changed files with 83 additions and 13 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ API_DEPS = .api.deps
API_SHARED_INFO = etc/api/shared.yaml
API_JSON_FILES = $(shell find etc -type f -name '*-api.json')
MAKO_LIB_DIR = $(MAKO_SRC)/lib
MAKO_LIB_FILES = $(shell find $(MAKO_LIB_DIR) -type f -name '*.mako' -or -name '*.py')
MAKO_LIB_FILES = $(shell find $(MAKO_LIB_DIR) -type f -name '*.*')

help:
$(info using template engine: '$(TPL)')
Expand Down
2 changes: 2 additions & 0 deletions src/mako/lib.rs.mako
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ extern crate "yup-oauth2" as oauth2;

use std::collections::HashMap;

use cmn::{Resource, Part, ResponseResult, RequestResult, NestedType};

// ############
// SCHEMAS ###
// ##########
Expand Down
15 changes: 13 additions & 2 deletions src/mako/lib/schema.mako
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@
## Create new schema with everything.
## 's' contains the schema structure from json to build
<%def name="new(s, c)">\
<% assert s.type == "object" %>\
<%
assert s.type == "object"
markers = util.schema_markers(s, c)
%>\
<%block filter="util.rust_doc_comment">\
${doc(s, c)}\
</%block>
Expand All @@ -18,6 +21,10 @@ pub struct ${s.id}\
% else:
;
% endif

% for marker_trait in markers:
impl ${marker_trait} for ${s.id} {}
% endfor
</%def>

<%def name="doc(s, c)">\
Expand All @@ -26,7 +33,11 @@ ${s.get('description', 'There is no detailed description.')}
# Activities
${''.join("* %s\n" % a for a in c.sta_map[s.id].keys())}
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 ${util.put_and(util.IO_TYPES)}.
${''.join("* %s (%s)\n" % (util.activity_split(a)[1], iot and '|'.join(iot) or 'none')
for a, iot in c.sta_map[s.id].iteritems())}
% else:
This schema type is not used in any activity, and only used as *part* of another schema.
Expand Down
57 changes: 47 additions & 10 deletions src/mako/lib/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,14 @@
'string' : 'String',
'object' : 'HashMap'}
TREF = '$ref'
IO_RESPONSE = 'response'
IO_REQUEST = 'request'
IO_TYPES = (IO_REQUEST, IO_RESPONSE)
INS_METHOD = 'insert'
DEL_METHOD = 'delete'

NESTED_TYPE_MARKER = 'is_nested'

# ==============================================================================
## @name Filters
# ------------------------------------------------------------------------------
Expand Down Expand Up @@ -79,7 +84,7 @@ def nested_type(nt):
elif nt.get('additionalProperties'):
nt = nt.additionalProperties
else:
assert(is_nested_type(nt))
assert(is_nested_type_property(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)
Expand Down Expand Up @@ -112,22 +117,52 @@ def nested_type(nt):
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 True if this property is actually a nested type
def is_nested_type_property(t):
return 'type' in t and t.type == 'object' and 'properties' in t

# Return True if the schema is nested
def is_nested_type(s):
return NESTED_TYPE_MARKER in s

# 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):
if is_nested_type_property(p):
ns = p.copy()
ns.id = nested_type_name(s.id, pn)
ns[NESTED_TYPE_MARKER] = True
yield ns
# end for ach property
# end for aech schma

# Return sorted type names of all markers applicable to the given schema
def schema_markers(s, c):
res = set()

activities = c.sta_map.get(s.id, dict())
if len(activities) == 0:
res.add('Part')
else:
# it should have at least one activity that matches it's type to qualify for the Resource trait
for fqan, iot in activities.iteritems():
if activity_name_to_type_name(activity_split(fqan)[0]) == s.id:
res.add('Resource')
if IO_RESPONSE in iot:
res.add('ResponseResult')
if IO_REQUEST in iot:
res.add('RequestResult')
# end for each activity
# end handle activites

if is_nested_type(s):
res.add('NestedType')

return sorted(res)

## -- End Rust TypeSystem -- @}


Expand All @@ -148,7 +183,7 @@ def build_activity_mappings(activities):
for mn, m in a.methods.iteritems():
assert m.id not in fqan
fqan[m.id] = m
for in_out_type_name in ('request', 'response'):
for in_out_type_name in IO_TYPES:
t = m.get(in_out_type_name, None)
if t is None:
continue
Expand All @@ -163,12 +198,10 @@ def build_activity_mappings(activities):
# getrating: response is a 'SomethingResult', which is still related to activities name
# the latter is used to deduce the resource name
an, _ = activity_split(m.id)
# videos -> Video
an = an.capitalize()[:-1]
info = res.setdefault(an, dict())
tn = activity_name_to_type_name(an)
info = res.setdefault(tn, dict())
if m.id not in info:
io_info = info.setdefault(m.id, [])
io_info.append([])
info.setdefault(m.id, [])
# end handle other cases
# end for each method
# end for each activity
Expand All @@ -180,6 +213,10 @@ def activity_split(fqan):
assert len(t) == 3
return t[1:]

# videos -> Video
def activity_name_to_type_name(an):
return an.capitalize()[:-1]

## -- End Activity Utilities -- @}


Expand Down
20 changes: 20 additions & 0 deletions src/rust/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,26 @@ extern crate hyper;
extern crate "rustc-serialize" as rustc_serialize;
extern crate "yup-oauth2" as oauth2;

use std::marker::MarkerTrait;

/// Identifies types which can be inserted and deleted.
/// Types with this trait are most commonly used by clients of this API.
pub trait Resource: MarkerTrait {}

/// Identifies types which are used in API responses.
pub trait ResponseResult: MarkerTrait {}

/// Identifies types which are used in API requests.
pub trait RequestResult: MarkerTrait {}

/// Identifies types which are only used as part of other types, which
/// usually are carrying the `Resource` trait.
pub trait Part: MarkerTrait {}

/// Identifies types which are only used by other types internally.
/// They have no special meaning, this trait just marks them for completeness.
pub trait NestedType: MarkerTrait {}

/// This module is for testing only, its code is used in mako templates
#[cfg(test)]
mod dev;

0 comments on commit c1eeee0

Please sign in to comment.