Mercurial > lbo > hg > async-google-apis
changeset 11:67e17a5c8c7d
Use better templates instead of piecing together code
author | Lewin Bormann <lbo@spheniscida.de> |
---|---|
date | Sat, 17 Oct 2020 17:23:54 +0200 |
parents | 4b89ea16b73e |
children | 2a5c8f471c11 |
files | generate/generate.py generate/templates.py manual_demo/src/main.rs |
diffstat | 3 files changed, 168 insertions(+), 126 deletions(-) [+] |
line wrap: on
line diff
--- a/generate/generate.py Sat Oct 17 16:23:29 2020 +0200 +++ b/generate/generate.py Sat Oct 17 17:23:54 2020 +0200 @@ -8,47 +8,7 @@ from os import path -# General imports and error type. -RustHeader = ''' -use serde::{Deserialize, Serialize}; -use chrono::{DateTime, Utc}; -use anyhow::{Error, Result}; -use std::collections::HashMap; - -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), - HTTPError(hyper::StatusCode), -} - -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) - } -} -''' - -# A struct for parameters or input/output API types. -ResourceStructTmpl = ''' -#[derive(Serialize, Deserialize, Debug, Clone, Default)] -pub struct {{name}} { -{{#fields}} - {{#comment}} - // {{comment}} - {{/comment}} - {{#attr}} - {{{attr}}} - {{/attr}} - pub {{name}}: {{{typ}}}, -{{/fields}} -} -''' - +from templates import * def optionalize(name, optional=True): return 'Option<{}>'.format(name) if optional else name @@ -219,27 +179,8 @@ service = capitalize_first(resource) parts = [] - parts.append(''' -pub struct {}Service {{ - client: TlsClient, - authenticator: Authenticator, - scopes: Vec<String>, -}} -'''.format(service)) - parts.append(''' -impl {service}Service {{ - /// Create a new {service}Service object. - pub fn new(client: TlsClient, auth: Authenticator) -> {service}Service {{ - {service}Service {{ client: client, authenticator: auth, scopes: vec![] }} - }} - - /// Explicitly select which scopes should be requested for authorization. Otherwise, - /// a possibly too large scope will be requested. - pub fn set_scopes<S: AsRef<str>, T: AsRef<[S]>>(&mut self, scopes: T) {{ - self.scopes = scopes.as_ref().into_iter().map(|s| s.as_ref().to_string()).collect(); - }} -'''.format(service=service)) + method_fragments = [] # Generate individual methods. for methodname, method in methods['methods'].items(): @@ -255,70 +196,23 @@ upload_path = '' http_method = method['httpMethod'] - # TODO: Incorporate parameters into query! - for is_upload in set([False, is_upload]): - # TODO: Support multipart upload properly - if is_upload: - parts.append( - ' pub async fn {}_upload(&mut self, params: &{}, data: hyper::body::Bytes) -> Result<{}> {{'. - format(snake_case(methodname), params_name, out_type)) - else: - parts.append(' pub async fn {}(&mut self, params: &{}, req: &{}) -> Result<{}> {{'.format( - snake_case(methodname), params_name, in_type, out_type)) - - # Check parameters and format API path. - formatted_path, required_params = resolve_parameters(method['path']) - parts.append(' let relpath = {};'.format('"' + upload_path.lstrip('/') + - '"' if is_upload else formatted_path)) - parts.append(' let path = "{}".to_string() + &relpath;'.format( - discdoc['rootUrl'] if is_upload else discdoc['baseUrl'])) - parts.append(' let tok = self.authenticator.token(&self.scopes).await?;') - - if is_upload: - parts.append( - ' let mut url_params = format!("?uploadType=media&oauth_token={token}&fields=*", token=tok.as_str());' - ) - else: - parts.append(' let mut url_params = format!("?oauth_token={token}&fields=*", token=tok.as_str());') - - for p, snakeparam in parameters.items(): - parts.append(''' - if let Some(ref val) = ¶ms.{snake} {{ - url_params.push_str(&format!("&{p}={{}}", val)); - }}'''.format(p=p, snake=snakeparam)) + formatted_path, required_params = resolve_parameters(method['path']) + data_normal = {'name': methodname, 'param_type': params_name, 'in_type': in_type, 'out_type': out_type, + 'base_path': discdoc['baseUrl'], 'rel_path_expr': formatted_path, + 'params': [{'param': p, 'snake_param': sp} for (p, sp) in parameters.items()], + 'http_method': http_method} + method_fragments.append(chevron.render(NormalMethodTmpl, data_normal)) - parts.append(''' - let full_uri = path+&url_params; - println!("To: {{}}", full_uri); - let reqb = hyper::Request::builder().uri(full_uri).method("{method}");'''.format(method=http_method)) - if is_upload: - parts.append(''' - let reqb = reqb.header("Content-Length", data.len()); - let body = hyper::Body::from(data);''') - else: - parts.append(''' let reqb = reqb.header("Content-Type", "application/json");''') - parts.append(''' println!("Request: {}", serde_json::to_string(req)?);''') - if in_type != '()': - parts.append(''' let body = hyper::Body::from(serde_json::to_string(req)?);''') - else: - parts.append(''' let body = hyper::Body::from("");''') + if is_upload: + data_upload = {'name': snake_case(methodname), 'param_type': params_name, 'in_type': in_type, 'out_type': out_type, + 'base_path': discdoc['rootUrl'], 'rel_path_expr': '"'+upload_path.lstrip('/')+'"', + 'params': [{'param': p, 'snake_param': sp} for (p, sp) in parameters.items()], + 'http_method': http_method} + method_fragments.append(chevron.render(UploadMethodTmpl, data_upload)) - parts.append(''' let req = reqb.body(body)?; - let resp = self.client.request(req).await?; - if !resp.status().is_success() { - return Err(anyhow::Error::new(ApiError::HTTPError(resp.status()))); - } - let resp_body = hyper::body::to_bytes(resp.into_body()).await?; - let bodystr = String::from_utf8(resp_body.to_vec())?; - println!("Response: {}", bodystr); - let decoded = serde_json::from_str(&bodystr)?; - Ok(decoded) - }''') - - parts.append('}') - parts.append('') - - return '\n'.join(parts) + return chevron.render(ServiceImplementationTmpl, + {'service': service, + 'methods': [{'text': t} for t in method_fragments]}) def generate_structs(discdoc): @@ -346,7 +240,6 @@ f.write(chevron.render(ResourceStructTmpl, s)) for s in services: f.write(s) - f.write('\n') def fetch_discovery_base(url, apis):
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/generate/templates.py Sat Oct 17 17:23:54 2020 +0200 @@ -0,0 +1,147 @@ + +# General imports and error type. +RustHeader = ''' +use serde::{Deserialize, Serialize}; +use chrono::{DateTime, Utc}; +use anyhow::{Error, Result}; +use std::collections::HashMap; + +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), + HTTPError(hyper::StatusCode), +} + +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) + } +} +''' + +# A struct for parameters or input/output API types. +ResourceStructTmpl = ''' +#[derive(Serialize, Deserialize, Debug, Clone, Default)] +pub struct {{{name}}} { +{{#fields}} + {{#comment}} + // {{{comment}}} + {{/comment}} + {{#attr}} + {{{attr}}} + {{/attr}} + pub {{{name}}}: {{{typ}}}, +{{/fields}} +} +''' + +# Dict contents -- +# service (e.g. Files) +# [methods] ([{'text': ...}]) +ServiceImplementationTmpl = ''' +pub struct {{{service}}}Service { + client: TlsClient, + authenticator: Authenticator, + scopes: Vec<String>, +} + +impl {{{service}}}Service { + /// Create a new {{service}}Service object. + pub fn new(client: TlsClient, auth: Authenticator) -> {{service}}Service { + {{{service}}}Service { client: client, authenticator: auth, scopes: vec![] } + } + + /// Explicitly select which scopes should be requested for authorization. Otherwise, + /// a possibly too large scope will be requested. + pub fn set_scopes<S: AsRef<str>, T: AsRef<[S]>>(&mut self, scopes: T) { + self.scopes = scopes.as_ref().into_iter().map(|s| s.as_ref().to_string()).collect(); + } + +{{#methods}} + {{{text}}} +{{/methods}} + +} +''' + +# Takes: +# name, param_type, in_type, out_type +# base_path, rel_path_expr +# params: [{param, snake_param}] +# http_method +NormalMethodTmpl = '''pub async fn {{{name}}}( + &mut self, params: &{{{param_type}}}{{{#in_type}}}, req: &{{{in_type}}}{{{/in_type}}}) -> Result<{{{out_type}}}> { + + let rel_path = {{{rel_path_expr}}}; + let path = "{{{base_path}}}".to_string() + &rel_path; + let tok = self.authenticator.token(&self.scopes).await?; + let mut url_params = format!("?oauth_token={token}&fields=*", token=tok.as_str()); + + {{#params}} + if let Some(ref val) = ¶ms.{{{snake_param}}} { + url_params.push_str(&format!("&{{{param}}}={}", val)); + } + {{/params}} + + let full_uri = path + &url_params; + let reqb = hyper::Request::builder() + .uri(full_uri) + .method("{{{http_method}}}") + .header("Content-Type", "application/json"); + let mut body_str = serde_json::to_string(req)?; + if body_str == "null" { + body_str.clear(); + } + let body = hyper::Body::from(body_str); + let req = reqb.body(body)?; + let resp = self.client.request(req).await?; + if !resp.status().is_success() { + return Err(anyhow::Error::new(ApiError::HTTPError(resp.status()))); + } + let resp_body = hyper::body::to_bytes(resp.into_body()).await?; + let bodystr = String::from_utf8(resp_body.to_vec())?; + let decoded = serde_json::from_str(&bodystr)?; + Ok(decoded) +} +''' + +# Takes: +# name, param_type, in_type, out_type +# base_path, rel_path_expr +# params: [{param, snake_param}] +# http_method +UploadMethodTmpl = '''pub async fn {{{name}}}_upload( + &mut self, params: &{{{param_type}}}, data: hyper::body::Bytes) -> Result<{{out_type}}> { + let rel_path = {{{rel_path_expr}}}; + let path = "{{{base_path}}}".to_string() + &rel_path; + let tok = self.authenticator.token(&self.scopes).await?; + let mut url_params = format!("?uploadType=media&oauth_token={token}&fields=*", token=tok.as_str()); + + {{#params}} + if let Some(ref val) = ¶ms.{{{snake_param}}} { + url_params.push_str(&format!("&{{{param}}}={}", val)); + } + {{/params}} + + let full_uri = path + &url_params; + let reqb = hyper::Request::builder() + .uri(full_uri) + .method("{{{http_method}}}") + .header("Content-Length", data.len()); + let body = hyper::Body::from(data); + let req = reqb.body(body)?; + let resp = self.client.request(req).await?; + if !resp.status().is_success() { + return Err(anyhow::Error::new(ApiError::HTTPError(resp.status()))); + } + let resp_body = hyper::body::to_bytes(resp.into_body()).await?; + let bodystr = String::from_utf8(resp_body.to_vec())?; + let decoded = serde_json::from_str(&bodystr)?; + Ok(decoded) +} +'''
--- a/manual_demo/src/main.rs Sat Oct 17 16:23:29 2020 +0200 +++ b/manual_demo/src/main.rs Sat Oct 17 17:23:54 2020 +0200 @@ -67,9 +67,11 @@ println!("{:?}", resp); + let file_id = resp.id.unwrap(); + let mut params = drive::FilesUpdateParams::default(); println!("{:?}", params); - params.file_id = resp.id.clone().unwrap(); + params.file_id = file_id.clone(); params.include_permissions_for_view = Some("published".to_string()); let mut file = drive::File::default(); file.name = Some("profilepic.jpg".to_string()); @@ -77,7 +79,7 @@ println!("{:?}", update_resp); let mut params = drive::FilesGetParams::default(); - params.file_id = file.id.clone().unwrap(); + params.file_id = file_id.clone(); println!("{:?}", cl.get(¶ms, &()).await.unwrap()); }