changeset 5:e0856ff450bb

Improve rust struct generation: Also generate parameters
author Lewin Bormann <lbo@spheniscida.de>
date Sat, 17 Oct 2020 12:04:51 +0200
parents 95fca1b07ef5
children 4b27e65aeccc
files generate/generate.py manual_demo/src/main.rs
diffstat 2 files changed, 63 insertions(+), 113 deletions(-) [+]
line wrap: on
line diff
--- a/generate/generate.py	Sat Oct 17 10:56:24 2020 +0200
+++ b/generate/generate.py	Sat Oct 17 12:04:51 2020 +0200
@@ -8,7 +8,7 @@
 from os import path
 
 ResourceStructTmpl = '''
-#[derive(Serialize, Deserialize, Debug, Clone)]
+#[derive(Serialize, Deserialize, Debug, Clone, Default)]
 pub struct {{name}} {
 {{#fields}}
     {{#comment}}
@@ -20,6 +20,9 @@
 }
 '''
 
+def optionalize(name, optional=True):
+    return 'Option<{}>'.format(name) if optional else name
+
 def replace_keywords(name):
     return {
             'type': ('typ', 'type'),
@@ -37,7 +40,7 @@
     return ''.join([r(c) for c in name])
 
 
-def type_of_property(name, prop):
+def type_of_property(name, prop, optional=True):
     """Translate a JSON schema type into Rust types.
 
     Arguments:
@@ -57,14 +60,14 @@
     structs = []
     try:
         if '$ref' in prop:
-            return prop['$ref'], structs
+            return optionalize(prop['$ref'], optional), 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)
+                        name + capitalize_first(pn), pp, optional=True)
                     if type(subtyp) is tuple:
                         subtyp, comment = subtyp
                     else:
@@ -88,59 +91,67 @@
                     })
                     structs.extend(substructs)
                 structs.append(struct)
-                return typ, structs
+                return (optionalize(typ, optional), prop.get('description', '')), structs
             if 'additionalProperties' in prop:
                 field, substructs = type_of_property(
-                    name, prop['additionalProperties'])
+                    name, prop['additionalProperties'], optional=False)
                 structs.extend(substructs)
                 if type(field) is tuple:
                     typ = field[0]
                 else:
                     typ = field
-                print(typ)
-                return 'HashMap<String,' + typ + '>', structs
+                return (optionalize('HashMap<String,'+typ+'>', optional), prop.get('description', '')), structs
         if prop['type'] == 'array':
-            typ, substructs = type_of_property(name, prop['items'])
+            typ, substructs = type_of_property(name, prop['items'], optional=False)
             if type(typ) is tuple:
                 typ = typ[0]
-            return 'Vec<' + typ + '>', structs + substructs
+            return (optionalize('Vec<'+typ+'>', optional), prop.get('description', '')), structs + substructs
         if prop['type'] == 'string':
             if 'format' in prop:
                 if prop['format'] == 'int64':
-                    return ('String', 'i64'), structs
+                    return (optionalize('String', optional), 'i64: ' + prop.get('description', '')), structs
                 if prop['format'] == 'int32':
-                    return ('String', 'i32'), structs
+                    return (optionalize('String', optional), 'i32: ' + prop.get('description', '')), structs
                 if prop['format'] == 'double':
-                    return ('String', 'f64'), structs
+                    return (optionalize('String', optional), 'f64: ' + prop.get('description', '')), structs
                 if prop['format'] == 'float':
-                    return ('String', 'f32'), structs
+                    return (optionalize('String', optional), 'f32: ' + prop.get('description', '')), structs
                 if prop['format'] == 'date-time':
-                    return 'DateTime<Utc>', structs
-            return 'String', structs
+                    return (optionalize('DateTime<Utc>', optional),  prop.get('description', '')), structs
+            return (optionalize('String', optional), prop.get('description', '')), structs
         if prop['type'] == 'boolean':
-            return 'bool', structs
+            return (optionalize('bool', optional), prop.get('description', '')), structs
         if prop['type'] in ('number', 'integer'):
             if prop['format'] == 'float':
-                return 'f32', structs
+                return (optionalize('f32', optional),  prop.get('description', '')), structs
             if prop['format'] == 'double':
-                return 'f64', structs
+                return (optionalize('f64', optional), prop.get('description', '')), structs
             if prop['format'] == 'int32':
-                return 'i32', structs
+                return (optionalize('i32', optional), prop.get('description', '')), structs
             if prop['format'] == 'int64':
-                return 'i64', structs
+                return (optionalize('i64', optional), prop.get('description', '')), structs
         raise Exception('unimplemented!', name, prop)
     except KeyError as e:
         print(name, prop)
         print(e)
         raise e
 
-
 def generate_structs(discdoc):
     schemas = discdoc['schemas']
+    resources = discdoc['resources']
     structs = []
     for name, desc in schemas.items():
         typ, substructs = type_of_property(name, desc)
         structs.extend(substructs)
+    for name, res in resources.items():
+        for methodname, method in res['methods'].items():
+            if 'parameters' not in method:
+                structs.append({'name': '{}{}Params'.format(capitalize_first(name) , capitalize_first(methodname)), 'fields': []})
+            else:
+                params = method['parameters']
+                typ = {'type': 'object', 'properties': params}
+                typ, substructs = type_of_property('{}{}Params'.format(capitalize_first(name), capitalize_first(methodname)), typ)
+                structs.extend(substructs)
 
     modname = (discdoc['id'] + '_types').replace(':', '_')
     with open(path.join('gen', modname + '.rs'), 'w') as f:
@@ -150,6 +161,9 @@
             'use std::collections::HashMap;\n'
             ])
         for s in structs:
+            for field in s['fields']:
+                if field.get('comment', None):
+                    field['comment'] = field['comment'].replace('\n', ' ')
             f.write(chevron.render(ResourceStructTmpl, s))
 
 
--- a/manual_demo/src/main.rs	Sat Oct 17 10:56:24 2020 +0200
+++ b/manual_demo/src/main.rs	Sat Oct 17 12:04:51 2020 +0200
@@ -7,100 +7,15 @@
 use yup_oauth2::InstalledFlowAuthenticator;
 use std::string::String;
 use std::str::FromStr;
-
-use std::collections::HashMap;
+use std::path::Path;
+use std::fs;
 
 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>;
+type Authenticator = yup_oauth2::authenticator::Authenticator<TlsConnr>;
 
 fn https_client() -> TlsClient {
     let conn = hyper_rustls::HttpsConnector::new();
@@ -108,7 +23,27 @@
     cl
 }
 
-async fn get_about(cl: &mut TlsClient, auth: &mut yup_oauth2::authenticator::Authenticator<TlsConnr>) {
+async fn upload_file(cl: &mut TlsClient, auth: &mut Authenticator, f: &Path) {
+    let posturl = "https://www.googleapis.com/upload/drive/v3/files?uploadType=media";
+    let tok = auth.token(&["https://www.googleapis.com/auth/drive.file"]).await.unwrap();
+    let authtok = format!("&oauth_token={}&fields=*", tok.as_str());
+
+    let file = fs::OpenOptions::new().read(true).open(f).unwrap();
+    let len = file.metadata().unwrap().len();
+
+    let body = hyper::Body::from(fs::read(&f).unwrap());
+    let req = hyper::Request::post(posturl.to_string()+&authtok).header("Content-Length", format!("{}", len))
+        .body(body).unwrap();
+    let resp = cl.request(req).await.unwrap();
+
+    let body = resp.into_body();
+    let body = hyper::body::to_bytes(body).await.unwrap();
+    let dec = String::from_utf8(body.to_vec()).unwrap();
+    let about: drive::File = serde_json::from_str(&dec).unwrap();
+    println!("{:?}", about);
+}
+
+async fn get_about(cl: &mut TlsClient, auth: &mut Authenticator) {
     let baseurl = "https://www.googleapis.com/drive/v3/";
     let path = "about";
     let tok = auth.token(&["https://www.googleapis.com/auth/drive.file"]).await.unwrap();
@@ -118,7 +53,7 @@
     let body = resp.into_body();
     let body = hyper::body::to_bytes(body).await.unwrap();
     let dec = String::from_utf8(body.to_vec()).unwrap();
-    let about: About = serde_json::from_str(&dec).unwrap();
+    let about: drive::About = serde_json::from_str(&dec).unwrap();
     println!("{:?}", about);
 }
 
@@ -138,7 +73,8 @@
 
     let mut cl = https_client();
 
-    get_about(&mut cl, &mut auth).await;
+    //get_about(&mut cl, &mut auth).await;
+    upload_file(&mut cl, &mut auth, Path::new("pp.jpg")).await;
 
     match auth.token(scopes).await {
         Ok(token) => println!("The token is {:?}", token),