changeset 184:4168f61afd5e

Represent API enums as Rust Enums For #10.
author Lewin Bormann <lbo@spheniscida.de>
date Fri, 29 Jan 2021 10:29:31 +0100
parents 048c8e2d63e6
children 9e5b86630da1
files generate/generate.py generate/templates.py
diffstat 2 files changed, 62 insertions(+), 23 deletions(-) [+]
line wrap: on
line diff
--- a/generate/generate.py	Thu Jan 28 11:37:06 2021 +0100
+++ b/generate/generate.py	Fri Jan 29 10:29:31 2021 +0100
@@ -76,7 +76,7 @@
         schema: A JSON object from a discovery document representing a type.
 
     Returns:
-        (tuple, [dict])
+        (tuple, [dict], enums)
 
         where type is a tuple where the first element is a Rust type and the
         second element is a comment detailing the use of the field. The list of
@@ -87,12 +87,13 @@
     typ = ''
     comment = ''
     structs = []
+    enums = []
     try:
         if '$ref' in schema:
             # We just assume that there is already a type generated for the reference.
             if schema['$ref'] not in parents:
-                return optionalize(schema['$ref'], optional), structs
-            return optionalize('Box<' + schema['$ref'] + '>', optional), structs
+                return optionalize(schema['$ref'], optional), structs, enums
+            return optionalize('Box<' + schema['$ref'] + '>', optional), structs, enums
         if 'type' in schema and schema['type'] == 'object':
             # There are two types of objects: those with `properties` are translated into a Rust struct,
             # and those with `additionalProperties` into a HashMap<String, ...>.
@@ -103,10 +104,10 @@
                 typ = name
                 struct = {'name': name, 'description': schema.get('description', ''), 'fields': []}
                 for pn, pp in schema['properties'].items():
-                    subtyp, substructs = parse_schema_types(name + capitalize_first(pn),
-                                                            pp,
-                                                            optional=True,
-                                                            parents=parents + [name])
+                    subtyp, substructs, subenums = parse_schema_types(name + capitalize_first(pn),
+                            pp,
+                            optional=True,
+                            parents=parents + [name])
                     if type(subtyp) is tuple:
                         subtyp, comment = subtyp
                     else:
@@ -129,11 +130,12 @@
                         comment
                     })
                     structs.extend(substructs)
+                    enums.extend(subenums)
                 structs.append(struct)
-                return (optionalize(typ, optional), schema.get('description', '')), structs
+                return (optionalize(typ, optional), schema.get('description', '')), structs, enums
 
             if 'additionalProperties' in schema:
-                field, substructs = parse_schema_types(name,
+                field, substructs, subenums = parse_schema_types(name,
                                                        schema['additionalProperties'],
                                                        optional=False,
                                                        parents=parents + [name])
@@ -142,18 +144,19 @@
                     typ = field[0]
                 else:
                     typ = field
-                return (optionalize('HashMap<String,' + typ + '>', optional), schema.get('description', '')), structs
+                return (optionalize('HashMap<String,' + typ + '>', optional), schema.get('description', '')), structs, subenums
 
         if schema['type'] == 'array':
-            typ, substructs = parse_schema_types(name, schema['items'], optional=False, parents=parents + [name])
+            typ, substructs, subenums = parse_schema_types(name, schema['items'], optional=False, parents=parents + [name])
             if type(typ) is tuple:
                 typ = typ[0]
-            return (optionalize('Vec<' + typ + '>', optional), schema.get('description', '')), structs + substructs
+            return (optionalize('Vec<' + typ + '>', optional), schema.get('description', '')), structs + substructs, subenums
 
         if schema['type'] == 'string':
 
+            # Builds a line of a rust type
             def build(intt, typ='String'):
-                return (optionalize(typ, optional), intt + ': ' + schema.get('description', '')), structs
+                return (optionalize(typ, optional), intt + ': ' + schema.get('description', '')), structs, enums
 
             if 'format' in schema:
                 if schema['format'] == 'int64':
@@ -170,15 +173,31 @@
                     return build('f32')
                 if schema['format'] == 'date-time':
                     return build('DateTime', typ='DateTime<Utc>')
-            return (optionalize('String', optional), schema.get('description', '')), structs
+
+            if 'enum' in schema and name:
+                name_ = snake_to_camel(rust_identifier(name))
+                def sanitize_enum_value(v):
+                    if v[0].isnumeric():
+                        return '_'+v
+                    return snake_to_camel(v)
+
+                values = [{
+                    'line': sanitize_enum_value(ev),
+                    'desc': schema.get('enumDescriptions', ['']*(i))[i]}
+                    for (i, ev) in enumerate(schema.get('enum', []))]
+                templ_params = {'name': name_, 'values': values}
+                print('Emitted enum', name_, 'with', len(values), 'fields')
+                return (name_, schema.get('description', '')), structs, [templ_params]
+
+            return (optionalize('String', optional), schema.get('description', '')), structs, enums
 
         if schema['type'] == 'boolean':
-            return (optionalize('bool', optional), schema.get('description', '')), structs
+            return (optionalize('bool', optional), schema.get('description', '')), structs, enums
 
         if schema['type'] in ('number', 'integer'):
 
             def build(intt):
-                return (optionalize(intt, optional), schema.get('description', '')), structs
+                return (optionalize(intt, optional), schema.get('description', '')), structs, enums
 
             if schema['format'] == 'float':
                 return build('f32')
@@ -194,11 +213,11 @@
                 return build('u64')
 
         if schema['type'] == 'any':
-            return (optionalize('String', optional), 'ANY data: ' + schema.get('description', '')), structs
+            return (optionalize('String', optional), 'ANY data: ' + schema.get('description', '')), structs, enums
 
-        raise Exception('unimplemented schema type!', name, schema)
+        raise Exception('unimplemented schema type!', name)
     except KeyError as e:
-        print('KeyError while processing:', name, schema)
+        print('KeyError while processing:', name)
         raise e
 
 
@@ -230,7 +249,8 @@
             # Build struct dict for rendering.
             if 'parameters' in method:
                 for paramname, param in method['parameters'].items():
-                    (typ, desc), substructs = parse_schema_types('', param, optional=False, parents=[])
+                    print(paramname, param)
+                    (typ, desc), substructs, enums = parse_schema_types('', param, optional=False, parents=[])
                     field = {
                         'name': replace_keywords(rust_identifier(paramname)),
                         'original_name': paramname,
@@ -501,19 +521,22 @@
 
     # Generate schema types.
     structs = []
+    enums = []
     for name, desc in schemas.items():
-        typ, substructs = parse_schema_types(name, desc)
+        typ, substructs, subenums = parse_schema_types(name, desc)
         structs.extend(substructs)
+        enums.extend(subenums)
 
     # Generate global parameters struct and its Display impl.
     if 'parameters' in discdoc:
         schema = {'type': 'object', 'properties': discdoc['parameters']}
         name = replace_keywords(snake_to_camel(params_struct_name))
-        typ, substructs = parse_schema_types(name, schema)
+        typ, substructs, subenums = parse_schema_types(name, schema)
         for s in substructs:
             s['optional_fields'] = s['fields']
             parameter_types.append(chevron.render(SchemaDisplayTmpl, s))
         structs.extend(substructs)
+        enums.extend(subenums)
 
     # Assemble everything into a file.
     modname = (discdoc['id'] + '_types').replace(':', '_')
@@ -529,6 +552,9 @@
             if not s['name']:
                 print('WARN', s)
             f.write(chevron.render(SchemaStructTmpl, s))
+        for e in enums:
+            print('enum', e)
+            f.write(chevron.render(SchemaEnumTmpl, e))
         # Render *Params structs.
         for pt in parameter_types:
             f.write(pt)
@@ -636,7 +662,7 @@
                 continue
             generate_all(discdoc)
         except Exception as e:
-            print("Error while processing", discdoc)
+            print("Error while processing discovery doc")
             raise e
             continue
 
--- a/generate/templates.py	Thu Jan 28 11:37:06 2021 +0100
+++ b/generate/templates.py	Fri Jan 29 10:29:31 2021 +0100
@@ -45,6 +45,19 @@
 
 '''
 
+# An API enum.
+#
+# fields: {name, values: [{desc, line}]}
+SchemaEnumTmpl = '''
+#[derive(Debug, Clone, Copy)]
+pub enum {{{name}}} {
+    {{#values}}
+    /// {{{desc}}}
+    {{{line}}},
+    {{/values}}
+}
+'''
+
 # A struct for parameters or input/output API types.
 # Dict contents --
 # name