changeset 2:c7e2e5de7401

Make struct generation work
author Lewin Bormann <lbo@spheniscida.de>
date Sat, 17 Oct 2020 10:36:44 +0200
parents d967aac4e997
children 09ee80214be1
files .hgignore generate/drivev3.json generate/generate.py manual_demo/src/main.rs manual_demo/tokencache.json
diffstat 5 files changed, 186 insertions(+), 42 deletions(-) [+]
line wrap: on
line diff
--- a/.hgignore	Fri Oct 16 22:31:04 2020 +0200
+++ b/.hgignore	Sat Oct 17 10:36:44 2020 +0200
@@ -1,2 +1,3 @@
 .txt
+.json
 target/
--- a/generate/drivev3.json	Fri Oct 16 22:31:04 2020 +0200
+++ b/generate/drivev3.json	Sat Oct 17 10:36:44 2020 +0200
@@ -2299,6 +2299,7 @@
         },
         "teamDriveThemes": {
           "description": "Deprecated - use driveThemes instead.",
+          // [ { backgroundImageLink: ..., colorRgb: ..., ...} ]
           "items": {
             "properties": {
               "backgroundImageLink": {
--- a/generate/generate.py	Fri Oct 16 22:31:04 2020 +0200
+++ b/generate/generate.py	Sat Oct 17 10:36:44 2020 +0200
@@ -5,54 +5,109 @@
 import json
 import requests
 
+from os import path
+
 ResourceStructTmpl = '''
-pub struct {name} {{
-    {{#fields}}
-        {{name}}: {{typ}},
-    {{/fields}}
-}}
+#[derive(Serialize, Deserialize, Debug, Clone)]
+pub struct {{name}} {
+{{#fields}}
+    {{#comment}}
+    // {{comment}}
+    {{/comment}}
+    {{{attr}}}
+    {{name}}: {{{typ}}},
+{{/fields}}
+}
 '''
 
-def json_type_to_rust_field(prop):
-    if prop is None:
-        return ''
-    print(prop)
-    if 'type' in prop:
-        jt = prop['type']
-    else:
-        jt = 'object'
+def capitalize_first(name):
+    return name[0].upper() + name[1:]
+
+def snake_case(name):
+    def r(c):
+        if c.islower():
+            return c
+        return '_'+c.lower()
+    return ''.join([r(c) for c in name])
 
-    if jt == 'string':
-        if 'format' in prop:
-            if prop['format'] in ['int64', 'int32']:
-                return 'i64'
-            if prop['format'] == 'date-time':
-                return 'Time'
-            if prop['format'] in ['float', 'double']:
-                return 'float64'
-            if prop['format'] == 'byte':
-                return 'Vec<u8>'
-        return 'String'
-    if jt == 'boolean':
-        return 'bool'
-    if jt == 'array':
-        inner = prop['items']
-        inner_type = json_type_to_rust_field(inner)
-        return 'Vec<' + inner_type + '>'
-    if jt == 'object':
-        if 'additionalProperties' in prop:
-            inner = prop.get('additionalProperties', None)
-            inner_type = json_type_to_rust_field(inner)
-            return 'HashMap<String,'+inner_type+'>'
-        else:
-            for subpropname, subprop in prop.items():
-                pass
+def type_of_property(name, prop):
+    typ = ''
+    comment = ''
+    structs = []
+    try:
+        if '$ref' in prop:
+            return prop['$ref'], structs
+        if 'type' in prop and prop['type'] == 'object':
+            if 'properties' in prop:
+                typ = name
+                struct = {'name': name, 'fields': []}
+                for pn, pp in prop['properties'].items():
+                    subtyp, substructs = type_of_property(name+capitalize_first(pn), pp)
+                    if type(subtyp) is tuple:
+                        subtyp, comment = subtyp
+                    else:
+                        comment = None
+                    struct['fields'].append({'name': snake_case(pn), 'attr': '#[serde(rename = "{}")]'.format(pn),
+                        'typ': subtyp, 'comment': comment})
+                    structs.extend(substructs)
+                structs.append(struct)
+                return typ, structs
+            if 'additionalProperties' in prop:
+                field, substructs = type_of_property(name, prop['additionalProperties'])
+                structs.extend(substructs)
+                if type(field) is tuple:
+                    typ = field[0]
+                else:
+                    typ = field
+                print(typ)
+                return 'HashMap<String,' + typ + '>', structs
+        if prop['type'] == 'array':
+            typ, substructs = type_of_property(name, prop['items'])
+            if type(typ) is tuple:
+                typ = typ[0]
+            return 'Vec<' + typ + '>', structs+substructs
+        if prop['type'] == 'string':
+            if 'format' in prop:
+                if prop['format'] == 'int64':
+                    return ('String', 'i64'), structs
+                if prop['format'] == 'int32':
+                    return ('String', 'i32'), structs
+                if prop['format'] == 'double':
+                    return ('String', 'f64'), structs
+                if prop['format'] == 'float':
+                    return ('String', 'f32'), structs
+                if prop['format'] == 'date-time':
+                    return 'Time', structs
+            return 'String', structs
+        if prop['type'] == 'boolean':
+            return 'bool', structs
+        if prop['type'] in ('number', 'integer'):
+            if prop['format'] == 'float':
+                return 'f32', structs
+            if prop['format'] == 'double':
+                return 'f64', structs
+            if prop['format'] == 'int32':
+                return 'i32', structs
+            if prop['format'] == 'int64':
+                return 'i64', structs
+        raise Exception('unimplemented!', name, prop)
+    except KeyError as e:
+        print(name, prop)
+        print(e)
+        raise e
 
 def generate_structs(discdoc):
     schemas = discdoc['schemas']
+    structs = []
     for name, desc in schemas.items():
-        for propname, prop in desc['properties'].items():
-            print(propname, '=>', json_type_to_rust_field(prop))
+        typ, substructs = type_of_property(name, desc)
+        structs.extend(substructs)
+
+    modname = (discdoc['id']+'_types').replace(':', '_')
+    with open(path.join('gen', modname+'.rs'), 'w') as f:
+        f.write('use serde::{Deserialize, Serialize};')
+        for s in structs:
+            f.write(chevron.render(ResourceStructTmpl, s))
 
 
 def fetch_discovery_base(url, apis):
--- a/manual_demo/src/main.rs	Fri Oct 16 22:31:04 2020 +0200
+++ b/manual_demo/src/main.rs	Sat Oct 17 10:36:44 2020 +0200
@@ -4,10 +4,97 @@
 use std::string::String;
 use std::str::FromStr;
 
+use std::collections::HashMap;
+
 use hyper::Uri;
 use hyper_rustls::HttpsConnector;
 use serde_json::Value;
 
+use serde::{Deserialize, Serialize};
+
+#[derive(Serialize, Deserialize, Debug, Clone)]
+pub struct AboutDriveThemes {
+    #[serde(rename = "backgroundImageLink")]
+    background_image_link: String,
+    #[serde(rename = "colorRgb")]
+    color_rgb: String,
+    #[serde(rename = "id")]
+    id: String,
+}
+
+#[derive(Serialize, Deserialize, Debug, Clone)]
+pub struct AboutStorageQuota {
+    // i64
+    #[serde(rename = "limit")]
+    limit: String,
+    // i64
+    #[serde(rename = "usage")]
+    usage: String,
+    // i64
+    #[serde(rename = "usageInDrive")]
+    usage_in_drive: String,
+    // i64
+    #[serde(rename = "usageInDriveTrash")]
+    usage_in_drive_trash: String,
+}
+
+#[derive(Serialize, Deserialize, Debug, Clone)]
+pub struct AboutTeamDriveThemes {
+    #[serde(rename = "backgroundImageLink")]
+    background_image_link: String,
+    #[serde(rename = "colorRgb")]
+    color_rgb: String,
+    #[serde(rename = "id")]
+    id: String,
+}
+
+#[derive(Serialize, Deserialize, Debug, Clone)]
+pub struct About {
+    #[serde(rename = "appInstalled")]
+    app_installed: bool,
+    #[serde(rename = "canCreateDrives")]
+    can_create_drives: bool,
+    #[serde(rename = "canCreateTeamDrives")]
+    can_create_team_drives: bool,
+    #[serde(rename = "driveThemes")]
+    drive_themes: Vec<AboutDriveThemes>,
+    #[serde(rename = "exportFormats")]
+    export_formats: HashMap<String,Vec<String>>,
+    #[serde(rename = "folderColorPalette")]
+    folder_color_palette: Vec<String>,
+    #[serde(rename = "importFormats")]
+    import_formats: HashMap<String,Vec<String>>,
+    #[serde(rename = "kind")]
+    kind: String,
+    #[serde(rename = "maxImportSizes")]
+    max_import_sizes: HashMap<String,String>,
+    // i64
+    #[serde(rename = "maxUploadSize")]
+    max_upload_size: String,
+    #[serde(rename = "storageQuota")]
+    storage_quota: AboutStorageQuota,
+    #[serde(rename = "teamDriveThemes")]
+    team_drive_themes: Vec<AboutTeamDriveThemes>,
+    #[serde(rename = "user")]
+    user: User,
+}
+
+#[derive(Serialize, Deserialize, Debug, Clone)]
+pub struct User {
+    #[serde(rename = "displayName")]
+    display_name: String,
+    #[serde(rename = "emailAddress")]
+    email_address: String,
+    #[serde(rename = "kind")]
+    kind: String,
+    #[serde(rename = "me")]
+    me: bool,
+    #[serde(rename = "permissionId")]
+    permission_id: String,
+    #[serde(rename = "photoLink")]
+    photo_link: String,
+}
+
 type TlsConnr = HttpsConnector<hyper::client::HttpConnector>;
 type TlsClient = hyper::Client<TlsConnr, hyper::Body>;
 
@@ -27,7 +114,8 @@
     let body = resp.into_body();
     let body = hyper::body::to_bytes(body).await.unwrap();
     let dec = String::from_utf8(body.to_vec()).unwrap();
-    println!("{}", dec);
+    let about: About = serde_json::from_str(&dec).unwrap();
+    println!("{:?}", about);
 }
 
 #[tokio::main]
--- a/manual_demo/tokencache.json	Fri Oct 16 22:31:04 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1 +0,0 @@
-[{"scopes":["https://www.googleapis.com/auth/drive.file"],"token":{"access_token":"ya29.a0AfH6SMDxs3RY-1v6wPg8nTDYse51fTM6MjP1dxflS4fTH_HCfMdhm1V59wAvcPeC8xnPjPkTqyNg_r3rSodWu1fYlv2oby_v2cZZ1euk1-GFZ6lnwpLgjRxVF-WxW8aKs-vx3vuMao8gd-FvD8U0Io-QdVTsKL2eaIQ","refresh_token":"1//094GIedhCtiYACgYIARAAGAkSNwF-L9IrAsqDDGTQUboZfDDpJFcEPcwHedLVwpAnSapXphwVutQIFOPFrOc0fE43DJ-6qJ1hL0c","expires_at":"2020-10-16T20:05:45.667484422Z"}}]
\ No newline at end of file