changeset 8:755f08722795

Work on generating services
author Lewin Bormann <lbo@spheniscida.de>
date Sat, 17 Oct 2020 13:04:25 +0200
parents f098e2637387
children bcd017d6c863
files generate/generate.py manual_demo/Cargo.lock manual_demo/Cargo.toml manual_demo/src/main.rs
diffstat 4 files changed, 99 insertions(+), 3 deletions(-) [+]
line wrap: on
line diff
--- a/generate/generate.py	Sat Oct 17 12:06:44 2020 +0200
+++ b/generate/generate.py	Sat Oct 17 13:04:25 2020 +0200
@@ -3,6 +3,7 @@
 import argparse
 import chevron
 import json
+import re
 import requests
 
 from os import path
@@ -134,11 +135,60 @@
         print(e)
         raise e
 
+def resolve_parameters(string, paramsname='params', suffix='.unwrap()'):
+    """Returns a Rust syntax for formatting the given string with API
+    parameters, and a list of (snake-case) API parameters that are used. """
+    pat = re.compile('\{(\w+)\}')
+    params = re.findall(pat, string)
+    snakeparams = [snake_case(p) for p in params]
+    format_params = ','.join(['{}={}.{}{}'.format(p, paramsname, sp, suffix) for (p, sp) in zip(params, snakeparams)])
+    return 'format!("{}", {})'.format(string, format_params), snakeparams
+
+def generate_service(resource, methods, discdoc):
+    service = capitalize_first(resource)
+
+    parts = []
+    parts.append('pub struct {}Service {{'.format(service))
+    parts.append('  client: TlsClient,')
+    parts.append('  authenticator: Authenticator,')
+    parts.append('}')
+    parts.append('')
+
+    parts.append('impl {}Service {{'.format(service))
+
+    for methodname, method in methods['methods'].items():
+        params_name = service+capitalize_first(methodname)+'Params'
+        in_type = method['request']['$ref'] if 'request' in method else '()'
+        out_type = method['response']['$ref'] if 'response' in method else '()'
+        is_upload = 'mediaUpload' in method
+
+        if is_upload:
+            parts.append('  fn {}(&mut self, params: {}, req: {}) -> Result<{}> {{'.format(
+                snake_case(methodname), params_name, in_type, out_type))
+        else:
+            parts.append('  fn {}(&mut self, params: {}, req: {}, data: &hyper::body::Bytes) -> Result<{}> {{'.format(
+                snake_case(methodname), params_name, in_type, out_type))
+
+        formatted_path, required_params = resolve_parameters(method['path'])
+        for rp in required_params:
+            parts.append('    if params.{}.is_none() {{'.format(rp))
+            parts.append('      return Err(Error::new(ApiError::InputDataError("Parameter {} is missing!".to_string())));'.format(rp))
+            parts.append('    }')
+        parts.append('    let relpath = {};'.format(formatted_path))
+        parts.append('    unimplemented!()')
+        parts.append('  }')
+
+    parts.append('}')
+    parts.append('')
+
+    return '\n'.join(parts)
+
 
 def generate_structs(discdoc):
     schemas = discdoc['schemas']
     resources = discdoc['resources']
     structs = []
+    services = []
     for name, desc in schemas.items():
         typ, substructs = type_of_property(name, desc)
         structs.extend(substructs)
@@ -156,17 +206,42 @@
                     '{}{}Params'.format(capitalize_first(name), capitalize_first(methodname)), typ)
                 structs.extend(substructs)
 
+    for resource, methods in resources.items():
+        services.append(generate_service(resource, methods, discdoc))
+
     modname = (discdoc['id'] + '_types').replace(':', '_')
     with open(path.join('gen', modname + '.rs'), 'w') as f:
         f.writelines([
-            'use serde::{Deserialize, Serialize};\n', 'use chrono::{DateTime, Utc};\n',
-            'use std::collections::HashMap;\n'
+            'use serde::{Deserialize, Serialize};\n',
+            'use chrono::{DateTime, Utc};\n',
+            'use anyhow::{Error, Result};\n',
+            'use std::collections::HashMap;\n',
+            '''
+type TlsConnr = hyper_rustls::HttpsConnector<hyper::client::HttpConnector>;
+type TlsClient = hyper::Client<TlsConnr, hyper::Body>;
+type Authenticator = yup_oauth2::authenticator::Authenticator<TlsConnr>;
+
+#[derive(Debug, Clone)]
+pub enum ApiError {
+  InputDataError(String),
+}
+
+impl std::error::Error for ApiError {}
+impl std::fmt::Display for ApiError {
+  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+    std::fmt::Debug::fmt(self, f)
+  }
+}
+''',
         ])
         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))
+        for s in services:
+            f.write(s)
+            f.write('\n')
 
 
 def fetch_discovery_base(url, apis):
--- a/manual_demo/Cargo.lock	Sat Oct 17 12:06:44 2020 +0200
+++ b/manual_demo/Cargo.lock	Sat Oct 17 13:04:25 2020 +0200
@@ -1,6 +1,12 @@
 # This file is automatically @generated by Cargo.
 # It is not intended for manual editing.
 [[package]]
+name = "anyhow"
+version = "1.0.33"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1fd36ffbb1fb7c834eac128ea8d0e310c5aeb635548f9d58861e1308d46e71c"
+
+[[package]]
 name = "arc-swap"
 version = "0.4.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -399,6 +405,7 @@
 name = "manual_demo"
 version = "0.1.0"
 dependencies = [
+ "anyhow",
  "chrono",
  "hyper",
  "hyper-rustls",
--- a/manual_demo/Cargo.toml	Sat Oct 17 12:06:44 2020 +0200
+++ b/manual_demo/Cargo.toml	Sat Oct 17 13:04:25 2020 +0200
@@ -15,3 +15,4 @@
 serde_json = "~1.0"
 tokio = { version = "~0.2", features = ["full"] }
 yup-oauth2 = "~4"
+anyhow = "~1.0"
--- a/manual_demo/src/main.rs	Sat Oct 17 12:06:44 2020 +0200
+++ b/manual_demo/src/main.rs	Sat Oct 17 13:04:25 2020 +0200
@@ -23,6 +23,18 @@
     cl
 }
 
+struct FilesService {
+    client: TlsClient,
+    authenticator: Authenticator
+}
+
+impl FilesService {
+    fn create(&mut self, parameters: drive::FilesCreateParams, file: drive::File, data: &hyper::body::Bytes) -> hyper::Result<drive::File> {
+
+        unimplemented!()
+    }
+}
+
 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();
@@ -31,7 +43,8 @@
     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 bytes = hyper::body::Bytes::from(fs::read(&f).unwrap());
+    let body = hyper::Body::from(bytes);
     let req = hyper::Request::post(posturl.to_string()+&authtok).header("Content-Length", format!("{}", len))
         .body(body).unwrap();
     let resp = cl.request(req).await.unwrap();