From 9142859612e00fd81a3644f587f6abb9a8d837a9 Mon Sep 17 00:00:00 2001 From: philippeitis <33013301+philippeitis@users.noreply.github.com> Date: Tue, 27 Sep 2022 21:48:31 -0700 Subject: [PATCH 1/6] Move client.rs to common dependency --- Cargo.toml | 35 -- google-apis-common/Cargo.toml | 3 +- google-apis-common/src/lib.rs | 84 +++ src/generator/templates/Cargo.toml.mako | 4 +- src/generator/templates/cli/main.rs.mako | 4 +- src/generator/templates/deps.mako | 7 +- src/rust/api/client.rs | 2 - src/rust/cli/client.rs | 746 ----------------------- src/rust/cli/mod.rs | 1 - src/rust/lib.rs | 153 ----- 10 files changed, 92 insertions(+), 947 deletions(-) delete mode 100644 Cargo.toml delete mode 100644 src/rust/api/client.rs delete mode 100644 src/rust/cli/client.rs delete mode 100644 src/rust/cli/mod.rs delete mode 100644 src/rust/lib.rs diff --git a/Cargo.toml b/Cargo.toml deleted file mode 100644 index e6102dc7aa..0000000000 --- a/Cargo.toml +++ /dev/null @@ -1,35 +0,0 @@ -# DO NOT PUBLISH -# This library is just to try out the code that should ultimately go into the code generator ! -[package] - -name = "client" -version = "0.0.1" -authors = ["Sebastian Thiel "] -description = "A library to facilitate interacting with your youtube account" -repository = "https://github.com/Byron/google-apis-rs" -license = "MIT/Apache-2.0" -keywords = ["youtube", "google", "protocol", "not-for-use"] -edition = "2018" -publish = false - -[lib] -# The common client code, used by all generated implementations -name = "client" -path = "src/rust/lib.rs" - -[dependencies] -google-apis-common = { version = "4.0", path = "google-apis-common" } -clap = "2" -http = "^0.2" -hyper = "0.14" -mime = "0.2" -rustc-serialize = "*" -yup-oauth2 = "^ 7.0" -serde = "1" -serde_json = "1" -serde_derive = "1" -strsim = "*" -tokio = "^ 1.0" -hyper-rustls = "^0.22" -itertools = "^ 0.10" -tower-service = "^0.3" diff --git a/google-apis-common/Cargo.toml b/google-apis-common/Cargo.toml index 8c444448ca..1502269297 100644 --- a/google-apis-common/Cargo.toml +++ b/google-apis-common/Cargo.toml @@ -26,4 +26,5 @@ http = "^0.2" tokio = "^1.0" tower-service = "^0.3.1" - +[dev-dependencies] +serde = { version = "^ 1.0", features = ["derive"] } diff --git a/google-apis-common/src/lib.rs b/google-apis-common/src/lib.rs index b593198145..788fd3c0a0 100644 --- a/google-apis-common/src/lib.rs +++ b/google-apis-common/src/lib.rs @@ -861,3 +861,87 @@ mod yup_oauth2_impl { } } } + +#[cfg(test)] +mod test_api { + use super::*; + use hyper; + use std::default::Default; + use std::io::Read; + use std::str::FromStr; + + use serde_json as json; + use serde::{Serialize, Deserialize}; + + const EXPECTED: &'static str = "\r\n--MDuXWGyeE33QFXGchb2VFWc4Z7945d\r\n\ +Content-Length: 50\r\n\ +Content-Type: application/json\r\n\ +\r\n\ +foo\r\n\ +--MDuXWGyeE33QFXGchb2VFWc4Z7945d\r\n\ +Content-Length: 25\r\n\ +Content-Type: application/plain\r\n\ +\r\n\ +bar\r\n\ +--MDuXWGyeE33QFXGchb2VFWc4Z7945d--"; + + const EXPECTED_LEN: usize = 223; + + #[test] + fn serde() { + #[derive(Default, Serialize, Deserialize)] + struct Foo { + opt: Option, + req: u32, + opt_vec: Option>, + vec: Vec, + } + + let f: Foo = Default::default(); + json::to_string(&f).unwrap(); // should work + + let j = "{\"opt\":null,\"req\":0,\"vec\":[]}"; + let f: Foo = json::from_str(j).unwrap(); + + // This fails, unless 'vec' is optional + // let j = "{\"opt\":null,\"req\":0}"; + // let f: Foo = json::from_str(j).unwrap(); + + #[derive(Default, Serialize, Deserialize)] + struct Bar { + #[serde(rename = "snooSnoo")] + snoo_snoo: String, + } + json::to_string(&::default()).unwrap(); + + let j = "{\"snooSnoo\":\"foo\"}"; + let b: Bar = json::from_str(&j).unwrap(); + assert_eq!(b.snoo_snoo, "foo"); + + // We can't have unknown fields with structs. + // #[derive(Default, Serialize, Deserialize)] + // struct BarOpt { + // #[serde(rename="snooSnoo")] + // snoo_snoo: Option + // } + // let j = "{\"snooSnoo\":\"foo\",\"foo\":\"bar\"}"; + // let b: BarOpt = json::from_str(&j).unwrap(); + } + + #[test] + fn byte_range_from_str() { + assert_eq!( + ::from_str("2-42"), + Ok(Chunk { first: 2, last: 42 }) + ) + } + + #[test] + fn dyn_delegate_is_send() { + fn with_send(x: impl Send) {} + + let mut dd = DefaultDelegate::default(); + let dlg: &mut dyn Delegate = &mut dd; + with_send(dlg); + } +} diff --git a/src/generator/templates/Cargo.toml.mako b/src/generator/templates/Cargo.toml.mako index 584e9755fe..f49e115c6c 100644 --- a/src/generator/templates/Cargo.toml.mako +++ b/src/generator/templates/Cargo.toml.mako @@ -34,7 +34,9 @@ mime = "^ 0.2.0" serde = { version = "^ 1.0", features = ["derive"] } serde_json = "^ 1.0" itertools = "^ 0.10" -% if 'is_executable' not in cargo: +% if cargo.get('is_executable', False): +google-clis-common = { path = "../../google-clis-common", version = "4.0" } +% else: google-apis-common = { path = "../../google-apis-common", version = "4.0" } % endif % for dep in cargo.get('dependencies', list()): diff --git a/src/generator/templates/cli/main.rs.mako b/src/generator/templates/cli/main.rs.mako index c7efb11d65..fcdc9d9108 100644 --- a/src/generator/templates/cli/main.rs.mako +++ b/src/generator/templates/cli/main.rs.mako @@ -14,8 +14,6 @@ #![allow(unused_variables, unused_imports, dead_code, unused_mut)] -extern crate tokio; - #[macro_use] extern crate clap; @@ -25,7 +23,7 @@ use clap::{App, SubCommand, Arg}; use ${to_extern_crate_name(library_to_crate_name(library_name(name, version), make.depends_on_suffix))}::{api, Error, oauth2}; -mod client; +use google_clis_common as client; ${engine.new(c)}\ diff --git a/src/generator/templates/deps.mako b/src/generator/templates/deps.mako index 7185757a94..d875a6724d 100644 --- a/src/generator/templates/deps.mako +++ b/src/generator/templates/deps.mako @@ -105,12 +105,9 @@ print('Could not open JSON file at {}'.format(api_json)) print(e) %>\ -${api_common}: $(RUST_SRC)/${make.id}/client.rs $(lastword $(MAKEFILE_LIST)) ${gen_root_stamp} - @ echo "// COPY OF '$<'" > $@ - @ echo "// DO NOT EDIT" >> $@ - @cat $< >> $@ +${api_common}: ${gen_root_stamp} -${gen_root_stamp}: $(MAKO_RENDER) ${' '.join(i[0] for i in sds)} ${api_json_inputs} $(MAKO_STANDARD_DEPENDENCIES) ${depends_on_target} +${gen_root_stamp}: $(MAKO_RENDER) ${' '.join(i[0] for i in sds)} ${api_json_inputs} $(MAKO_STANDARD_DEPENDENCIES) @echo Generating ${api_target} $(MAKO) -io ${' '.join("%s=%s" % (s, d) for s, d in sds)} ${post_processor_arg} --data-files ${api_json_inputs} @touch $@ diff --git a/src/rust/api/client.rs b/src/rust/api/client.rs deleted file mode 100644 index c7ad4dd216..0000000000 --- a/src/rust/api/client.rs +++ /dev/null @@ -1,2 +0,0 @@ -//! This file serves on purpose and is an artifact of the buildsystem. -//! Its content can now be found in the `google-apis-common` crate. \ No newline at end of file diff --git a/src/rust/cli/client.rs b/src/rust/cli/client.rs deleted file mode 100644 index 1bfb75ccea..0000000000 --- a/src/rust/cli/client.rs +++ /dev/null @@ -1,746 +0,0 @@ -use clap::{App, SubCommand}; -use mime::Mime; -use crate::oauth2::{ApplicationSecret, ConsoleApplicationSecret}; -use serde_json as json; -use serde_json::value::Value; - -use std::env; -use std::error::Error as StdError; -use std::fmt; -use std::fs; -use std::io; -use std::io::{stdout, Read, Write}; -use std::path::{Path, PathBuf}; -use std::str::FromStr; -use std::string::ToString; - -use std::default::Default; - -const FIELD_SEP: char = '.'; - -pub enum ComplexType { - Pod, - Vec, - Map, -} - -// Null, -// Bool(bool), -// I64(i64), -// U64(u64), -// F64(f64), -// String(String), - -pub enum JsonType { - Boolean, - Int, - Uint, - Float, - String, -} - -pub struct JsonTypeInfo { - pub jtype: JsonType, - pub ctype: ComplexType, -} - -// Based on @erickt user comment. Thanks for the idea ! -// Remove all keys whose values are null from given value (changed in place) -pub fn remove_json_null_values(value: &mut Value) { - match *value { - Value::Object(ref mut map) => { - let mut for_removal = Vec::new(); - - for (key, mut value) in map.iter_mut() { - if value.is_null() { - for_removal.push(key.clone()); - } else { - remove_json_null_values(&mut value); - } - } - - for key in &for_removal { - map.remove(key); - } - } - json::value::Value::Array(ref mut arr) => { - let mut i = 0; - while i < arr.len() { - if arr[i].is_null() { - arr.remove(i); - } else { - remove_json_null_values(&mut arr[i]); - i += 1; - } - } - } - _ => {} - } -} - -fn did_you_mean<'a>(v: &str, possible_values: &[&'a str]) -> Option<&'a str> { - let mut candidate: Option<(f64, &str)> = None; - for pv in possible_values { - let confidence = strsim::jaro_winkler(v, pv); - if confidence > 0.8 && (candidate.is_none() || (candidate.as_ref().unwrap().0 < confidence)) - { - candidate = Some((confidence, pv)); - } - } - match candidate { - None => None, - Some((_, candidate)) => Some(candidate), - } -} - -pub enum CallType { - Upload(UploadProtocol), - Standard, -} - -arg_enum! { - pub enum UploadProtocol { - Simple, - // Resumable // This seems to be lost during the async conversion - } -} - -impl AsRef for UploadProtocol { - fn as_ref(&self) -> &str { - match *self { - UploadProtocol::Simple => "simple", - // UploadProtocol::Resumable => "resumable", - } - } -} - -impl AsRef for CallType { - fn as_ref(&self) -> &str { - match *self { - CallType::Upload(ref proto) => proto.as_ref(), - CallType::Standard => "standard-request", - } - } -} - -#[derive(Clone, Default)] -pub struct FieldCursor(Vec); - -impl ToString for FieldCursor { - fn to_string(&self) -> String { - self.0.join(".") - } -} - -impl From<&'static str> for FieldCursor { - fn from(value: &'static str) -> FieldCursor { - let mut res = FieldCursor::default(); - res.set(value).unwrap(); - res - } -} - -fn assure_entry<'a>(m: &'a mut json::Map, k: &str) -> &'a mut Value { - if m.contains_key(k) { - return m.get_mut(k).expect("value to exist"); - } - m.insert(k.to_owned(), Value::Object(Default::default())); - m.get_mut(k).expect("value to exist") -} - -impl FieldCursor { - pub fn set(&mut self, value: &str) -> Result<(), CLIError> { - if value.is_empty() { - return Err(CLIError::Field(FieldError::Empty)); - } - - let mut first_is_field_sep = false; - let mut char_count: usize = 0; - let mut last_c = FIELD_SEP; - let mut num_conscutive_field_seps = 0; - - let mut field = String::new(); - let mut fields = self.0.clone(); - - let push_field = |fs: &mut Vec, f: &mut String| { - if !f.is_empty() { - fs.push(f.clone()); - f.truncate(0); - } - }; - - for (cid, c) in value.chars().enumerate() { - char_count += 1; - - if c == FIELD_SEP { - if cid == 0 { - first_is_field_sep = true; - } - num_conscutive_field_seps += 1; - if cid > 0 && last_c == FIELD_SEP { - if fields.pop().is_none() { - return Err(CLIError::Field(FieldError::PopOnEmpty(value.to_string()))); - } - } else { - push_field(&mut fields, &mut field); - } - } else { - num_conscutive_field_seps = 0; - if cid == 1 && first_is_field_sep { - fields.truncate(0); - } - field.push(c); - } - - last_c = c; - } - - push_field(&mut fields, &mut field); - - if char_count == 1 && first_is_field_sep { - fields.truncate(0); - } - if char_count > 1 && num_conscutive_field_seps == 1 { - return Err(CLIError::Field(FieldError::TrailingFieldSep( - value.to_string(), - ))); - } - - self.0 = fields; - Ok(()) - } - - pub fn did_you_mean(value: &str, possible_values: &[&str]) -> Option { - if value.is_empty() { - return None; - } - - let mut last_c = FIELD_SEP; - - let mut field = String::new(); - let mut output = String::new(); - - let push_field = |fs: &mut String, f: &mut String| { - if !f.is_empty() { - fs.push_str(match did_you_mean(&f, possible_values) { - Some(candidate) => candidate, - None => &f, - }); - f.truncate(0); - } - }; - - for (cid, c) in value.chars().enumerate() { - if c == FIELD_SEP { - if last_c != FIELD_SEP { - push_field(&mut output, &mut field); - } - output.push(c); - } else { - field.push(c); - } - - last_c = c; - } - - push_field(&mut output, &mut field); - - if output == value { - None - } else { - Some(output) - } - } - - pub fn set_json_value( - &self, - mut object: &mut Value, - value: &str, - type_info: JsonTypeInfo, - err: &mut InvalidOptionsError, - orig_cursor: &FieldCursor, - ) { - assert!(!self.0.is_empty()); - - for field in &self.0[..self.0.len() - 1] { - let tmp = object; - object = match *tmp { - Value::Object(ref mut mapping) => assure_entry(mapping, &field), - _ => panic!("We don't expect non-object Values here ..."), - }; - } - - match *object { - Value::Object(ref mut mapping) => { - let field = &self.0[self.0.len() - 1]; - let to_jval = - |value: &str, jtype: JsonType, err: &mut InvalidOptionsError| -> Value { - match jtype { - JsonType::Boolean => { - Value::Bool(arg_from_str(value, err, &field, "boolean")) - } - JsonType::Int => Value::Number( - json::Number::from_f64(arg_from_str(value, err, &field, "int")) - .expect("valid f64"), - ), - JsonType::Uint => Value::Number( - json::Number::from_f64(arg_from_str(value, err, &field, "uint")) - .expect("valid f64"), - ), - JsonType::Float => Value::Number( - json::Number::from_f64(arg_from_str(value, err, &field, "float")) - .expect("valid f64"), - ), - JsonType::String => Value::String(value.to_owned()), - } - }; - - match type_info.ctype { - ComplexType::Pod => { - if mapping - .insert(field.to_owned(), to_jval(value, type_info.jtype, err)) - .is_some() - { - err.issues.push(CLIError::Field(FieldError::Duplicate( - orig_cursor.to_string(), - ))); - } - } - ComplexType::Vec => match *assure_entry(mapping, field) { - Value::Array(ref mut values) => { - values.push(to_jval(value, type_info.jtype, err)) - } - _ => unreachable!(), - }, - ComplexType::Map => { - let (key, value) = parse_kv_arg(value, err, true); - let jval = to_jval(value.unwrap_or(""), type_info.jtype, err); - - match *assure_entry(mapping, &field) { - Value::Object(ref mut value_map) => { - if value_map.insert(key.to_owned(), jval).is_some() { - err.issues.push(CLIError::Field(FieldError::Duplicate( - orig_cursor.to_string(), - ))); - } - } - _ => unreachable!(), - } - } - } - } - _ => unreachable!(), - } - } - - pub fn num_fields(&self) -> usize { - self.0.len() - } -} - -pub fn parse_kv_arg<'a>( - kv: &'a str, - err: &mut InvalidOptionsError, - for_hashmap: bool, -) -> (&'a str, Option<&'a str>) { - let mut add_err = || { - err.issues - .push(CLIError::InvalidKeyValueSyntax(kv.to_string(), for_hashmap)) - }; - match kv.find('=') { - None => { - add_err(); - (kv, None) - } - Some(pos) => { - let key = &kv[..pos]; - if kv.len() <= pos + 1 { - add_err(); - return (key, Some("")); - } - (key, Some(&kv[pos + 1..])) - } - } -} - -pub fn calltype_from_str( - name: &str, - valid_protocols: Vec, - err: &mut InvalidOptionsError, -) -> CallType { - CallType::Upload(match UploadProtocol::from_str(name) { - Ok(up) => up, - Err(msg) => { - err.issues.push(CLIError::InvalidUploadProtocol( - name.to_string(), - valid_protocols, - )); - UploadProtocol::Simple - } - }) -} - -pub fn input_file_from_opts(file_path: &str, err: &mut InvalidOptionsError) -> Option { - match fs::File::open(file_path) { - Ok(f) => Some(f), - Err(io_err) => { - err.issues.push(CLIError::Input(InputError::Io(( - file_path.to_string(), - io_err, - )))); - None - } - } -} - -pub fn input_mime_from_opts(mime: &str, err: &mut InvalidOptionsError) -> Option { - match mime.parse() { - Ok(m) => Some(m), - Err(_) => { - err.issues - .push(CLIError::Input(InputError::Mime(mime.to_string()))); - None - } - } -} - -pub fn writer_from_opts(arg: Option<&str>) -> Result, io::Error> { - let f = arg.unwrap_or("-"); - match f { - "-" => Ok(Box::new(stdout())), - _ => match fs::OpenOptions::new() - .create(true) - .truncate(true) - .write(true) - .open(f) - { - Ok(f) => Ok(Box::new(f)), - Err(io_err) => Err(io_err), - }, - } -} - -pub fn arg_from_str<'a, T>( - arg: &str, - err: &mut InvalidOptionsError, - arg_name: &'a str, - arg_type: &'a str, -) -> T -where - T: FromStr + Default, - ::Err: fmt::Display, -{ - match FromStr::from_str(arg) { - Err(perr) => { - err.issues.push(CLIError::ParseError( - arg_name.to_owned(), - arg_type.to_owned(), - arg.to_string(), - format!("{}", perr), - )); - Default::default() - } - Ok(v) => v, - } -} - -#[derive(Debug)] -pub enum ApplicationSecretError { - DecoderError((String, json::Error)), - FormatError(String), -} - -impl fmt::Display for ApplicationSecretError { - fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { - match *self { - ApplicationSecretError::DecoderError((ref path, ref err)) => writeln!( - f, - "Could not decode file at '{}' with error: {}.", - path, err - ), - ApplicationSecretError::FormatError(ref path) => writeln!( - f, - "'installed' field is unset in secret file at '{}'.", - path - ), - } - } -} - -#[derive(Debug)] -pub enum ConfigurationError { - DirectoryCreationFailed((String, io::Error)), - DirectoryUnset, - HomeExpansionFailed(String), - Secret(ApplicationSecretError), - Io((String, io::Error)), -} - -impl fmt::Display for ConfigurationError { - fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { - match *self { - ConfigurationError::DirectoryCreationFailed((ref dir, ref err)) => writeln!( - f, - "Directory '{}' could not be created with error: {}.", - dir, err - ), - ConfigurationError::DirectoryUnset => writeln!(f, "--config-dir was unset or empty."), - ConfigurationError::HomeExpansionFailed(ref dir) => writeln!( - f, - "Couldn't find HOME directory of current user, failed to expand '{}'.", - dir - ), - ConfigurationError::Secret(ref err) => writeln!(f, "Secret -> {}", err), - ConfigurationError::Io((ref path, ref err)) => writeln!( - f, - "IO operation failed on path '{}' with error: {}.", - path, err - ), - } - } -} - -#[derive(Debug)] -pub enum InputError { - Io((String, io::Error)), - Mime(String), -} - -impl fmt::Display for InputError { - fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { - match *self { - InputError::Io((ref file_path, ref io_err)) => writeln!( - f, - "Failed to open '{}' for reading with error: {}.", - file_path, io_err - ), - InputError::Mime(ref mime) => writeln!(f, "'{}' is not a known mime-type.", mime), - } - } -} - -#[derive(Debug)] -pub enum FieldError { - PopOnEmpty(String), - TrailingFieldSep(String), - Unknown(String, Option, Option), - Duplicate(String), - Empty, -} - -impl fmt::Display for FieldError { - fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { - match *self { - FieldError::PopOnEmpty(ref field) => { - writeln!(f, "'{}': Cannot move up on empty field cursor.", field) - } - FieldError::TrailingFieldSep(ref field) => writeln!( - f, - "'{}': Single field separator may not be last character.", - field - ), - FieldError::Unknown(ref field, ref suggestion, ref value) => { - let suffix = match *suggestion { - Some(ref s) => { - let kv = match *value { - Some(ref v) => format!("{}={}", s, v), - None => s.clone(), - }; - format!(" Did you mean '{}' ?", kv) - } - None => String::new(), - }; - writeln!(f, "Field '{}' does not exist.{}", field, suffix) - } - FieldError::Duplicate(ref cursor) => { - writeln!(f, "Value at '{}' was already set", cursor) - } - FieldError::Empty => writeln!(f, "Field names must not be empty."), - } - } -} - -#[derive(Debug)] -pub enum CLIError { - Configuration(ConfigurationError), - ParseError(String, String, String, String), - UnknownParameter(String, Vec<&'static str>), - InvalidUploadProtocol(String, Vec), - InvalidKeyValueSyntax(String, bool), - Input(InputError), - Field(FieldError), - MissingCommandError, - MissingMethodError(String), -} - -impl fmt::Display for CLIError { - fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { - match *self { - CLIError::Configuration(ref err) => write!(f, "Configuration -> {}", err), - CLIError::Input(ref err) => write!(f, "Input -> {}", err), - CLIError::Field(ref err) => write!(f, "Field -> {}", err), - CLIError::InvalidUploadProtocol(ref proto_name, ref valid_names) => writeln!( - f, - "'{}' is not a valid upload protocol. Choose from one of {}.", - proto_name, - valid_names.join(", ") - ), - CLIError::ParseError(ref arg_name, ref type_name, ref value, ref err_desc) => writeln!( - f, - "Failed to parse argument '{}' with value '{}' as {} with error: {}.", - arg_name, value, type_name, err_desc - ), - CLIError::UnknownParameter(ref param_name, ref possible_values) => { - let suffix = match did_you_mean(param_name, &possible_values) { - Some(v) => format!(" Did you mean '{}' ?", v), - None => String::new(), - }; - writeln!(f, "Parameter '{}' is unknown.{}", param_name, suffix) - } - CLIError::InvalidKeyValueSyntax(ref kv, is_hashmap) => { - let hashmap_info = if is_hashmap { "hashmap " } else { "" }; - writeln!( - f, - "'{}' does not match {}pattern =.", - kv, hashmap_info - ) - } - CLIError::MissingCommandError => writeln!(f, "Please specify the main sub-command."), - CLIError::MissingMethodError(ref cmd) => writeln!( - f, - "Please specify the method to call on the '{}' command.", - cmd - ), - } - } -} - -#[derive(Debug)] -pub struct InvalidOptionsError { - pub issues: Vec, - pub exit_code: i32, -} - -impl fmt::Display for InvalidOptionsError { - fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { - for issue in &self.issues { - issue.fmt(f)?; - } - Ok(()) - } -} - -impl InvalidOptionsError { - pub fn single(err: CLIError, exit_code: i32) -> InvalidOptionsError { - InvalidOptionsError { - issues: vec![err], - exit_code, - } - } - - pub fn new() -> InvalidOptionsError { - InvalidOptionsError { - issues: Vec::new(), - exit_code: 1, - } - } -} - -pub fn assure_config_dir_exists(dir: &str) -> Result { - let trdir = dir.trim(); - if trdir.is_empty() { - return Err(CLIError::Configuration(ConfigurationError::DirectoryUnset)); - } - - let expanded_config_dir = if trdir.as_bytes()[0] == b'~' { - match env::var("HOME") - .ok() - .or_else(|| env::var("UserProfile").ok()) - { - None => { - return Err(CLIError::Configuration( - ConfigurationError::HomeExpansionFailed(trdir.to_string()), - )) - } - Some(mut user) => { - user.push_str(&trdir[1..]); - user - } - } - } else { - trdir.to_string() - }; - - if let Err(err) = fs::create_dir(&expanded_config_dir) { - if err.kind() != io::ErrorKind::AlreadyExists { - return Err(CLIError::Configuration( - ConfigurationError::DirectoryCreationFailed((expanded_config_dir, err)), - )); - } - } - - Ok(expanded_config_dir) -} - -pub fn application_secret_from_directory( - dir: &str, - secret_basename: &str, - json_console_secret: &str, -) -> Result { - let secret_path = Path::new(dir).join(secret_basename); - let secret_str = || secret_path.as_path().to_str().unwrap().to_string(); - let secret_io_error = |io_err: io::Error| { - Err(CLIError::Configuration(ConfigurationError::Io(( - secret_str(), - io_err, - )))) - }; - - for _ in 0..2 { - match fs::File::open(&secret_path) { - Err(mut err) => { - if err.kind() == io::ErrorKind::NotFound { - // Write our built-in one - user may adjust the written file at will - - err = match fs::OpenOptions::new() - .create(true) - .write(true) - .truncate(true) - .open(&secret_path) - { - Err(cfe) => cfe, - Ok(mut f) => { - // Assure we convert 'ugly' json string into pretty one - let console_secret: ConsoleApplicationSecret = - json::from_str(json_console_secret).unwrap(); - match json::to_writer_pretty(&mut f, &console_secret) { - Err(serde_err) => { - panic!("Unexpected serde error: {:#?}", serde_err) - } - Ok(_) => continue, - } - } - }; - // fall through to IO error handling - } - return secret_io_error(err); - } - Ok(f) => match json::de::from_reader::<_, ConsoleApplicationSecret>(f) { - Err(json_err) => { - return Err(CLIError::Configuration(ConfigurationError::Secret( - ApplicationSecretError::DecoderError((secret_str(), json_err)), - ))) - } - Ok(console_secret) => match console_secret.installed { - Some(secret) => return Ok(secret), - None => { - return Err(CLIError::Configuration(ConfigurationError::Secret( - ApplicationSecretError::FormatError(secret_str()), - ))) - } - }, - }, - } - } - unreachable!(); -} diff --git a/src/rust/cli/mod.rs b/src/rust/cli/mod.rs deleted file mode 100644 index b9babe5bc1..0000000000 --- a/src/rust/cli/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod client; diff --git a/src/rust/lib.rs b/src/rust/lib.rs deleted file mode 100644 index 185b0c27b7..0000000000 --- a/src/rust/lib.rs +++ /dev/null @@ -1,153 +0,0 @@ -#![allow( - dead_code, - deprecated, - unused_features, - unused_variables, - unused_imports -)] - -#[macro_use] -extern crate clap; - -#[macro_use] -extern crate hyper; -extern crate mime; -extern crate rustc_serialize; -extern crate serde; -extern crate serde_json; -extern crate yup_oauth2 as oauth2; -#[macro_use] -extern crate serde_derive; -extern crate strsim; - -// just pull it in the check if it compiles -mod cli; -use google_apis_common as api; - -/// This module is for testing only, its code is used in mako templates -#[cfg(test)] -mod test_api { - use super::api::*; - use hyper; - use std::default::Default; - use std::io::Read; - use std::str::FromStr; - - use serde_json as json; - - const EXPECTED: &'static str = "\r\n--MDuXWGyeE33QFXGchb2VFWc4Z7945d\r\n\ -Content-Length: 50\r\n\ -Content-Type: application/json\r\n\ -\r\n\ -foo\r\n\ ---MDuXWGyeE33QFXGchb2VFWc4Z7945d\r\n\ -Content-Length: 25\r\n\ -Content-Type: application/plain\r\n\ -\r\n\ -bar\r\n\ ---MDuXWGyeE33QFXGchb2VFWc4Z7945d--"; - - const EXPECTED_LEN: usize = 223; - - #[test] - fn serde() { - #[derive(Default, Serialize, Deserialize)] - struct Foo { - opt: Option, - req: u32, - opt_vec: Option>, - vec: Vec, - } - - let f: Foo = Default::default(); - json::to_string(&f).unwrap(); // should work - - let j = "{\"opt\":null,\"req\":0,\"vec\":[]}"; - let f: Foo = json::from_str(j).unwrap(); - - // This fails, unless 'vec' is optional - // let j = "{\"opt\":null,\"req\":0}"; - // let f: Foo = json::from_str(j).unwrap(); - - #[derive(Default, Serialize, Deserialize)] - struct Bar { - #[serde(rename = "snooSnoo")] - snoo_snoo: String, - } - json::to_string(&::default()).unwrap(); - - let j = "{\"snooSnoo\":\"foo\"}"; - let b: Bar = json::from_str(&j).unwrap(); - assert_eq!(b.snoo_snoo, "foo"); - - // We can't have unknown fields with structs. - // #[derive(Default, Serialize, Deserialize)] - // struct BarOpt { - // #[serde(rename="snooSnoo")] - // snoo_snoo: Option - // } - // let j = "{\"snooSnoo\":\"foo\",\"foo\":\"bar\"}"; - // let b: BarOpt = json::from_str(&j).unwrap(); - } - - #[test] - fn byte_range_from_str() { - assert_eq!( - ::from_str("2-42"), - Ok(Chunk { first: 2, last: 42 }) - ) - } - - #[test] - fn dyn_delegate_is_send() { - fn with_send(x: impl Send) {} - - let mut dd = DefaultDelegate::default(); - let dlg: &mut dyn Delegate = &mut dd; - with_send(dlg); - } -} - -#[cfg(test)] -mod test_cli { - use super::cli::client::*; - - use std::default::Default; - - #[test] - fn cursor() { - let mut c: FieldCursor = Default::default(); - - assert_eq!(c.to_string(), ""); - assert_eq!(c.num_fields(), 0); - assert!(c.set("").is_err()); - assert!(c.set(".").is_ok()); - assert!(c.set("..").is_err()); - assert_eq!(c.num_fields(), 0); - - assert!(c.set("foo").is_ok()); - assert_eq!(c.to_string(), "foo"); - assert_eq!(c.num_fields(), 1); - assert!(c.set("..").is_ok()); - assert_eq!(c.num_fields(), 0); - assert_eq!(c.to_string(), ""); - - assert!(c.set("foo.").is_err()); - - assert!(c.set("foo.bar").is_ok()); - assert_eq!(c.num_fields(), 2); - assert_eq!(c.to_string(), "foo.bar"); - assert!(c.set("sub.level").is_ok()); - assert_eq!(c.num_fields(), 4); - assert_eq!(c.to_string(), "foo.bar.sub.level"); - assert!(c.set("...other").is_ok()); - assert_eq!(c.to_string(), "foo.bar.other"); - assert_eq!(c.num_fields(), 3); - assert!(c.set(".one.two.three...beer").is_ok()); - assert_eq!(c.num_fields(), 2); - assert_eq!(c.to_string(), "one.beer"); - assert!(c.set("one.two.three...").is_ok()); - assert_eq!(c.num_fields(), 3); - assert_eq!(c.to_string(), "one.beer.one"); - } -} From a93ff473aba2ac953a4bc211320dda8a201f5cd5 Mon Sep 17 00:00:00 2001 From: philippeitis <33013301+philippeitis@users.noreply.github.com> Date: Tue, 27 Sep 2022 21:52:39 -0700 Subject: [PATCH 2/6] Use workspace Cargo.toml --- Cargo.toml | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 Cargo.toml diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000000..81dd13b78e --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,6 @@ +[workspace] + +members = [ + "google-apis-common", + "google-clis-common" +] \ No newline at end of file From 322ea698c365f1a713bc54d5aae9ffb92d9e646b Mon Sep 17 00:00:00 2001 From: philippeitis <33013301+philippeitis@users.noreply.github.com> Date: Tue, 27 Sep 2022 22:01:58 -0700 Subject: [PATCH 3/6] Add google-clis-common crate --- google-clis-common/Cargo.toml | 21 + google-clis-common/src/lib.rs | 790 ++++++++++++++++++++++++++++++++++ 2 files changed, 811 insertions(+) create mode 100644 google-clis-common/Cargo.toml create mode 100644 google-clis-common/src/lib.rs diff --git a/google-clis-common/Cargo.toml b/google-clis-common/Cargo.toml new file mode 100644 index 0000000000..db89a5625a --- /dev/null +++ b/google-clis-common/Cargo.toml @@ -0,0 +1,21 @@ +# DO NOT PUBLISH +# This library is just to try out the code that should ultimately go into the code generator ! +[package] +name = "google-clis-common" +version = "4.0.0" +authors = ["Sebastian Thiel "] +repository = "https://github.com/Byron/google-apis-rs" +homepage = "https://github.com/Byron/google-apis-rs/google-clis-common" +documentation = "https://docs.rs/google-clis-common" +description = "Shared functionality among the google-api crates." +license = "MIT" +keywords = ["google", "web", "api", "cli", "common"] +edition = "2021" + +[dependencies] +mime = "0.2" +yup-oauth2 = "^ 7.0" +serde = "1" +serde_json = "1" +strsim = "*" +clap = "2" diff --git a/google-clis-common/src/lib.rs b/google-clis-common/src/lib.rs new file mode 100644 index 0000000000..f1a74813ea --- /dev/null +++ b/google-clis-common/src/lib.rs @@ -0,0 +1,790 @@ +use mime::Mime; +use yup_oauth2::{ApplicationSecret, ConsoleApplicationSecret}; +use serde_json as json; +use serde_json::value::Value; +use clap::arg_enum; + +use std::env; +use std::fmt; +use std::fs; +use std::io; +use std::io::{stdout, Write}; +use std::path::Path; +use std::str::FromStr; +use std::string::ToString; + +use std::default::Default; + +const FIELD_SEP: char = '.'; + +pub enum ComplexType { + Pod, + Vec, + Map, +} + +// Null, +// Bool(bool), +// I64(i64), +// U64(u64), +// F64(f64), +// String(String), + +pub enum JsonType { + Boolean, + Int, + Uint, + Float, + String, +} + +pub struct JsonTypeInfo { + pub jtype: JsonType, + pub ctype: ComplexType, +} + +// Based on @erickt user comment. Thanks for the idea ! +// Remove all keys whose values are null from given value (changed in place) +pub fn remove_json_null_values(value: &mut Value) { + match *value { + Value::Object(ref mut map) => { + let mut for_removal = Vec::new(); + + for (key, mut value) in map.iter_mut() { + if value.is_null() { + for_removal.push(key.clone()); + } else { + remove_json_null_values(&mut value); + } + } + + for key in &for_removal { + map.remove(key); + } + } + json::value::Value::Array(ref mut arr) => { + let mut i = 0; + while i < arr.len() { + if arr[i].is_null() { + arr.remove(i); + } else { + remove_json_null_values(&mut arr[i]); + i += 1; + } + } + } + _ => {} + } +} + +fn did_you_mean<'a>(v: &str, possible_values: &[&'a str]) -> Option<&'a str> { + let mut candidate: Option<(f64, &str)> = None; + for pv in possible_values { + let confidence = strsim::jaro_winkler(v, pv); + if confidence > 0.8 && (candidate.is_none() || (candidate.as_ref().unwrap().0 < confidence)) + { + candidate = Some((confidence, pv)); + } + } + match candidate { + None => None, + Some((_, candidate)) => Some(candidate), + } +} + +pub enum CallType { + Upload(UploadProtocol), + Standard, +} + +arg_enum! { + pub enum UploadProtocol { + Simple, + // Resumable // This seems to be lost during the async conversion + } +} + +impl AsRef for UploadProtocol { + fn as_ref(&self) -> &str { + match *self { + UploadProtocol::Simple => "simple", + // UploadProtocol::Resumable => "resumable", + } + } +} + +impl AsRef for CallType { + fn as_ref(&self) -> &str { + match *self { + CallType::Upload(ref proto) => proto.as_ref(), + CallType::Standard => "standard-request", + } + } +} + +#[derive(Clone, Default)] +pub struct FieldCursor(Vec); + +impl ToString for FieldCursor { + fn to_string(&self) -> String { + self.0.join(".") + } +} + +impl From<&'static str> for FieldCursor { + fn from(value: &'static str) -> FieldCursor { + let mut res = FieldCursor::default(); + res.set(value).unwrap(); + res + } +} + +fn assure_entry<'a>(m: &'a mut json::Map, k: &str) -> &'a mut Value { + if m.contains_key(k) { + return m.get_mut(k).expect("value to exist"); + } + m.insert(k.to_owned(), Value::Object(Default::default())); + m.get_mut(k).expect("value to exist") +} + +impl FieldCursor { + pub fn set(&mut self, value: &str) -> Result<(), CLIError> { + if value.is_empty() { + return Err(CLIError::Field(FieldError::Empty)); + } + + let mut first_is_field_sep = false; + let mut char_count: usize = 0; + let mut last_c = FIELD_SEP; + let mut num_conscutive_field_seps = 0; + + let mut field = String::new(); + let mut fields = self.0.clone(); + + let push_field = |fs: &mut Vec, f: &mut String| { + if !f.is_empty() { + fs.push(f.clone()); + f.truncate(0); + } + }; + + for (cid, c) in value.chars().enumerate() { + char_count += 1; + + if c == FIELD_SEP { + if cid == 0 { + first_is_field_sep = true; + } + num_conscutive_field_seps += 1; + if cid > 0 && last_c == FIELD_SEP { + if fields.pop().is_none() { + return Err(CLIError::Field(FieldError::PopOnEmpty(value.to_string()))); + } + } else { + push_field(&mut fields, &mut field); + } + } else { + num_conscutive_field_seps = 0; + if cid == 1 && first_is_field_sep { + fields.truncate(0); + } + field.push(c); + } + + last_c = c; + } + + push_field(&mut fields, &mut field); + + if char_count == 1 && first_is_field_sep { + fields.truncate(0); + } + if char_count > 1 && num_conscutive_field_seps == 1 { + return Err(CLIError::Field(FieldError::TrailingFieldSep( + value.to_string(), + ))); + } + + self.0 = fields; + Ok(()) + } + + pub fn did_you_mean(value: &str, possible_values: &[&str]) -> Option { + if value.is_empty() { + return None; + } + + let mut last_c = FIELD_SEP; + + let mut field = String::new(); + let mut output = String::new(); + + let push_field = |fs: &mut String, f: &mut String| { + if !f.is_empty() { + fs.push_str(match did_you_mean(&f, possible_values) { + Some(candidate) => candidate, + None => &f, + }); + f.truncate(0); + } + }; + + for c in value.chars() { + if c == FIELD_SEP { + if last_c != FIELD_SEP { + push_field(&mut output, &mut field); + } + output.push(c); + } else { + field.push(c); + } + + last_c = c; + } + + push_field(&mut output, &mut field); + + if output == value { + None + } else { + Some(output) + } + } + + pub fn set_json_value( + &self, + mut object: &mut Value, + value: &str, + type_info: JsonTypeInfo, + err: &mut InvalidOptionsError, + orig_cursor: &FieldCursor, + ) { + assert!(!self.0.is_empty()); + + for field in &self.0[..self.0.len() - 1] { + let tmp = object; + object = match *tmp { + Value::Object(ref mut mapping) => assure_entry(mapping, &field), + _ => panic!("We don't expect non-object Values here ..."), + }; + } + + match *object { + Value::Object(ref mut mapping) => { + let field = &self.0[self.0.len() - 1]; + let to_jval = + |value: &str, jtype: JsonType, err: &mut InvalidOptionsError| -> Value { + match jtype { + JsonType::Boolean => { + Value::Bool(arg_from_str(value, err, &field, "boolean")) + } + JsonType::Int => Value::Number( + json::Number::from_f64(arg_from_str(value, err, &field, "int")) + .expect("valid f64"), + ), + JsonType::Uint => Value::Number( + json::Number::from_f64(arg_from_str(value, err, &field, "uint")) + .expect("valid f64"), + ), + JsonType::Float => Value::Number( + json::Number::from_f64(arg_from_str(value, err, &field, "float")) + .expect("valid f64"), + ), + JsonType::String => Value::String(value.to_owned()), + } + }; + + match type_info.ctype { + ComplexType::Pod => { + if mapping + .insert(field.to_owned(), to_jval(value, type_info.jtype, err)) + .is_some() + { + err.issues.push(CLIError::Field(FieldError::Duplicate( + orig_cursor.to_string(), + ))); + } + } + ComplexType::Vec => match *assure_entry(mapping, field) { + Value::Array(ref mut values) => { + values.push(to_jval(value, type_info.jtype, err)) + } + _ => unreachable!(), + }, + ComplexType::Map => { + let (key, value) = parse_kv_arg(value, err, true); + let jval = to_jval(value.unwrap_or(""), type_info.jtype, err); + + match *assure_entry(mapping, &field) { + Value::Object(ref mut value_map) => { + if value_map.insert(key.to_owned(), jval).is_some() { + err.issues.push(CLIError::Field(FieldError::Duplicate( + orig_cursor.to_string(), + ))); + } + } + _ => unreachable!(), + } + } + } + } + _ => unreachable!(), + } + } + + pub fn num_fields(&self) -> usize { + self.0.len() + } +} + +pub fn parse_kv_arg<'a>( + kv: &'a str, + err: &mut InvalidOptionsError, + for_hashmap: bool, +) -> (&'a str, Option<&'a str>) { + let mut add_err = || { + err.issues + .push(CLIError::InvalidKeyValueSyntax(kv.to_string(), for_hashmap)) + }; + match kv.find('=') { + None => { + add_err(); + (kv, None) + } + Some(pos) => { + let key = &kv[..pos]; + if kv.len() <= pos + 1 { + add_err(); + return (key, Some("")); + } + (key, Some(&kv[pos + 1..])) + } + } +} + +pub fn calltype_from_str( + name: &str, + valid_protocols: Vec, + err: &mut InvalidOptionsError, +) -> CallType { + CallType::Upload(match UploadProtocol::from_str(name) { + Ok(up) => up, + Err(_msg) => { + err.issues.push(CLIError::InvalidUploadProtocol( + name.to_string(), + valid_protocols, + )); + UploadProtocol::Simple + } + }) +} + +pub fn input_file_from_opts(file_path: &str, err: &mut InvalidOptionsError) -> Option { + match fs::File::open(file_path) { + Ok(f) => Some(f), + Err(io_err) => { + err.issues.push(CLIError::Input(InputError::Io(( + file_path.to_string(), + io_err, + )))); + None + } + } +} + +pub fn input_mime_from_opts(mime: &str, err: &mut InvalidOptionsError) -> Option { + match mime.parse() { + Ok(m) => Some(m), + Err(_) => { + err.issues + .push(CLIError::Input(InputError::Mime(mime.to_string()))); + None + } + } +} + +pub fn writer_from_opts(arg: Option<&str>) -> Result, io::Error> { + let f = arg.unwrap_or("-"); + match f { + "-" => Ok(Box::new(stdout())), + _ => match fs::OpenOptions::new() + .create(true) + .truncate(true) + .write(true) + .open(f) + { + Ok(f) => Ok(Box::new(f)), + Err(io_err) => Err(io_err), + }, + } +} + +pub fn arg_from_str<'a, T>( + arg: &str, + err: &mut InvalidOptionsError, + arg_name: &'a str, + arg_type: &'a str, +) -> T +where + T: FromStr + Default, + ::Err: fmt::Display, +{ + match FromStr::from_str(arg) { + Err(perr) => { + err.issues.push(CLIError::ParseError( + arg_name.to_owned(), + arg_type.to_owned(), + arg.to_string(), + format!("{}", perr), + )); + Default::default() + } + Ok(v) => v, + } +} + +#[derive(Debug)] +pub enum ApplicationSecretError { + DecoderError((String, json::Error)), + FormatError(String), +} + +impl fmt::Display for ApplicationSecretError { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + match *self { + ApplicationSecretError::DecoderError((ref path, ref err)) => writeln!( + f, + "Could not decode file at '{}' with error: {}.", + path, err + ), + ApplicationSecretError::FormatError(ref path) => writeln!( + f, + "'installed' field is unset in secret file at '{}'.", + path + ), + } + } +} + +#[derive(Debug)] +pub enum ConfigurationError { + DirectoryCreationFailed((String, io::Error)), + DirectoryUnset, + HomeExpansionFailed(String), + Secret(ApplicationSecretError), + Io((String, io::Error)), +} + +impl fmt::Display for ConfigurationError { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + match *self { + ConfigurationError::DirectoryCreationFailed((ref dir, ref err)) => writeln!( + f, + "Directory '{}' could not be created with error: {}.", + dir, err + ), + ConfigurationError::DirectoryUnset => writeln!(f, "--config-dir was unset or empty."), + ConfigurationError::HomeExpansionFailed(ref dir) => writeln!( + f, + "Couldn't find HOME directory of current user, failed to expand '{}'.", + dir + ), + ConfigurationError::Secret(ref err) => writeln!(f, "Secret -> {}", err), + ConfigurationError::Io((ref path, ref err)) => writeln!( + f, + "IO operation failed on path '{}' with error: {}.", + path, err + ), + } + } +} + +#[derive(Debug)] +pub enum InputError { + Io((String, io::Error)), + Mime(String), +} + +impl fmt::Display for InputError { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + match *self { + InputError::Io((ref file_path, ref io_err)) => writeln!( + f, + "Failed to open '{}' for reading with error: {}.", + file_path, io_err + ), + InputError::Mime(ref mime) => writeln!(f, "'{}' is not a known mime-type.", mime), + } + } +} + +#[derive(Debug)] +pub enum FieldError { + PopOnEmpty(String), + TrailingFieldSep(String), + Unknown(String, Option, Option), + Duplicate(String), + Empty, +} + +impl fmt::Display for FieldError { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + match *self { + FieldError::PopOnEmpty(ref field) => { + writeln!(f, "'{}': Cannot move up on empty field cursor.", field) + } + FieldError::TrailingFieldSep(ref field) => writeln!( + f, + "'{}': Single field separator may not be last character.", + field + ), + FieldError::Unknown(ref field, ref suggestion, ref value) => { + let suffix = match *suggestion { + Some(ref s) => { + let kv = match *value { + Some(ref v) => format!("{}={}", s, v), + None => s.clone(), + }; + format!(" Did you mean '{}' ?", kv) + } + None => String::new(), + }; + writeln!(f, "Field '{}' does not exist.{}", field, suffix) + } + FieldError::Duplicate(ref cursor) => { + writeln!(f, "Value at '{}' was already set", cursor) + } + FieldError::Empty => writeln!(f, "Field names must not be empty."), + } + } +} + +#[derive(Debug)] +pub enum CLIError { + Configuration(ConfigurationError), + ParseError(String, String, String, String), + UnknownParameter(String, Vec<&'static str>), + InvalidUploadProtocol(String, Vec), + InvalidKeyValueSyntax(String, bool), + Input(InputError), + Field(FieldError), + MissingCommandError, + MissingMethodError(String), +} + +impl fmt::Display for CLIError { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + match *self { + CLIError::Configuration(ref err) => write!(f, "Configuration -> {}", err), + CLIError::Input(ref err) => write!(f, "Input -> {}", err), + CLIError::Field(ref err) => write!(f, "Field -> {}", err), + CLIError::InvalidUploadProtocol(ref proto_name, ref valid_names) => writeln!( + f, + "'{}' is not a valid upload protocol. Choose from one of {}.", + proto_name, + valid_names.join(", ") + ), + CLIError::ParseError(ref arg_name, ref type_name, ref value, ref err_desc) => writeln!( + f, + "Failed to parse argument '{}' with value '{}' as {} with error: {}.", + arg_name, value, type_name, err_desc + ), + CLIError::UnknownParameter(ref param_name, ref possible_values) => { + let suffix = match did_you_mean(param_name, &possible_values) { + Some(v) => format!(" Did you mean '{}' ?", v), + None => String::new(), + }; + writeln!(f, "Parameter '{}' is unknown.{}", param_name, suffix) + } + CLIError::InvalidKeyValueSyntax(ref kv, is_hashmap) => { + let hashmap_info = if is_hashmap { "hashmap " } else { "" }; + writeln!( + f, + "'{}' does not match {}pattern =.", + kv, hashmap_info + ) + } + CLIError::MissingCommandError => writeln!(f, "Please specify the main sub-command."), + CLIError::MissingMethodError(ref cmd) => writeln!( + f, + "Please specify the method to call on the '{}' command.", + cmd + ), + } + } +} + +#[derive(Debug)] +pub struct InvalidOptionsError { + pub issues: Vec, + pub exit_code: i32, +} + +impl fmt::Display for InvalidOptionsError { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + for issue in &self.issues { + issue.fmt(f)?; + } + Ok(()) + } +} + +impl InvalidOptionsError { + pub fn single(err: CLIError, exit_code: i32) -> InvalidOptionsError { + InvalidOptionsError { + issues: vec![err], + exit_code, + } + } + + pub fn new() -> InvalidOptionsError { + InvalidOptionsError { + issues: Vec::new(), + exit_code: 1, + } + } +} + +pub fn assure_config_dir_exists(dir: &str) -> Result { + let trdir = dir.trim(); + if trdir.is_empty() { + return Err(CLIError::Configuration(ConfigurationError::DirectoryUnset)); + } + + let expanded_config_dir = if trdir.as_bytes()[0] == b'~' { + match env::var("HOME") + .ok() + .or_else(|| env::var("UserProfile").ok()) + { + None => { + return Err(CLIError::Configuration( + ConfigurationError::HomeExpansionFailed(trdir.to_string()), + )) + } + Some(mut user) => { + user.push_str(&trdir[1..]); + user + } + } + } else { + trdir.to_string() + }; + + if let Err(err) = fs::create_dir(&expanded_config_dir) { + if err.kind() != io::ErrorKind::AlreadyExists { + return Err(CLIError::Configuration( + ConfigurationError::DirectoryCreationFailed((expanded_config_dir, err)), + )); + } + } + + Ok(expanded_config_dir) +} + +pub fn application_secret_from_directory( + dir: &str, + secret_basename: &str, + json_console_secret: &str, +) -> Result { + let secret_path = Path::new(dir).join(secret_basename); + let secret_str = || secret_path.as_path().to_str().unwrap().to_string(); + let secret_io_error = |io_err: io::Error| { + Err(CLIError::Configuration(ConfigurationError::Io(( + secret_str(), + io_err, + )))) + }; + + for _ in 0..2 { + match fs::File::open(&secret_path) { + Err(mut err) => { + if err.kind() == io::ErrorKind::NotFound { + // Write our built-in one - user may adjust the written file at will + + err = match fs::OpenOptions::new() + .create(true) + .write(true) + .truncate(true) + .open(&secret_path) + { + Err(cfe) => cfe, + Ok(mut f) => { + // Assure we convert 'ugly' json string into pretty one + let console_secret: ConsoleApplicationSecret = + json::from_str(json_console_secret).unwrap(); + match json::to_writer_pretty(&mut f, &console_secret) { + Err(serde_err) => { + panic!("Unexpected serde error: {:#?}", serde_err) + } + Ok(_) => continue, + } + } + }; + // fall through to IO error handling + } + return secret_io_error(err); + } + Ok(f) => match json::de::from_reader::<_, ConsoleApplicationSecret>(f) { + Err(json_err) => { + return Err(CLIError::Configuration(ConfigurationError::Secret( + ApplicationSecretError::DecoderError((secret_str(), json_err)), + ))) + } + Ok(console_secret) => match console_secret.installed { + Some(secret) => return Ok(secret), + None => { + return Err(CLIError::Configuration(ConfigurationError::Secret( + ApplicationSecretError::FormatError(secret_str()), + ))) + } + }, + }, + } + } + unreachable!(); +} + + +#[cfg(test)] +mod test_cli { + use super::*; + + use std::default::Default; + + #[test] + fn cursor() { + let mut c: FieldCursor = Default::default(); + + assert_eq!(c.to_string(), ""); + assert_eq!(c.num_fields(), 0); + assert!(c.set("").is_err()); + assert!(c.set(".").is_ok()); + assert!(c.set("..").is_err()); + assert_eq!(c.num_fields(), 0); + + assert!(c.set("foo").is_ok()); + assert_eq!(c.to_string(), "foo"); + assert_eq!(c.num_fields(), 1); + assert!(c.set("..").is_ok()); + assert_eq!(c.num_fields(), 0); + assert_eq!(c.to_string(), ""); + + assert!(c.set("foo.").is_err()); + + assert!(c.set("foo.bar").is_ok()); + assert_eq!(c.num_fields(), 2); + assert_eq!(c.to_string(), "foo.bar"); + assert!(c.set("sub.level").is_ok()); + assert_eq!(c.num_fields(), 4); + assert_eq!(c.to_string(), "foo.bar.sub.level"); + assert!(c.set("...other").is_ok()); + assert_eq!(c.to_string(), "foo.bar.other"); + assert_eq!(c.num_fields(), 3); + assert!(c.set(".one.two.three...beer").is_ok()); + assert_eq!(c.num_fields(), 2); + assert_eq!(c.to_string(), "one.beer"); + assert!(c.set("one.two.three...").is_ok()); + assert_eq!(c.num_fields(), 3); + assert_eq!(c.to_string(), "one.beer.one"); + } +} From 7ef96f2a31cd16410e59158e6266a98df4950461 Mon Sep 17 00:00:00 2001 From: philippeitis <33013301+philippeitis@users.noreply.github.com> Date: Tue, 27 Sep 2022 22:06:07 -0700 Subject: [PATCH 4/6] Tweak workspace members --- Cargo.toml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 81dd13b78e..4ab4a7a429 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,5 +2,7 @@ members = [ "google-apis-common", - "google-clis-common" -] \ No newline at end of file + "google-clis-common", + "src/rust/preproc" +] +exclude = ["gen"] \ No newline at end of file From a1e6496ccdeaa8a0d799c53ed3b95fca5b7fc7c9 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Thu, 29 Sep 2022 09:02:05 +0800 Subject: [PATCH 5/6] run only tests that are actually used/implemented --- google-apis-common/Cargo.toml | 2 ++ google-clis-common/Cargo.toml | 5 ++++- src/rust/preproc/Cargo.toml | 6 ++++++ 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/google-apis-common/Cargo.toml b/google-apis-common/Cargo.toml index 1502269297..d804c70821 100644 --- a/google-apis-common/Cargo.toml +++ b/google-apis-common/Cargo.toml @@ -10,6 +10,8 @@ license = "MIT" keywords = ["google", "web", "api", "common"] edition = "2021" +[lib] +doctest = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/google-clis-common/Cargo.toml b/google-clis-common/Cargo.toml index db89a5625a..33b61d4338 100644 --- a/google-clis-common/Cargo.toml +++ b/google-clis-common/Cargo.toml @@ -7,11 +7,14 @@ authors = ["Sebastian Thiel "] repository = "https://github.com/Byron/google-apis-rs" homepage = "https://github.com/Byron/google-apis-rs/google-clis-common" documentation = "https://docs.rs/google-clis-common" -description = "Shared functionality among the google-api crates." +description = "Shared functionality among the google-api CLI crates." license = "MIT" keywords = ["google", "web", "api", "cli", "common"] edition = "2021" +[lib] +doctest = false + [dependencies] mime = "0.2" yup-oauth2 = "^ 7.0" diff --git a/src/rust/preproc/Cargo.toml b/src/rust/preproc/Cargo.toml index 9e1477e0f7..eebc9f668f 100644 --- a/src/rust/preproc/Cargo.toml +++ b/src/rust/preproc/Cargo.toml @@ -5,6 +5,12 @@ authors = ["Sebastian Thiel "] edition = "2018" publish = false +[[bin]] +name = "preproc" +path = "src/main.rs" +doctest = false +test = false + # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] From 3b4fae994e82ba48c4cc809a668516f44a367e5e Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Thu, 29 Sep 2022 09:06:56 +0800 Subject: [PATCH 6/6] fix compile warnings --- google-apis-common/src/lib.rs | 20 ++------------------ 1 file changed, 2 insertions(+), 18 deletions(-) diff --git a/google-apis-common/src/lib.rs b/google-apis-common/src/lib.rs index 788fd3c0a0..3540fc9b46 100644 --- a/google-apis-common/src/lib.rs +++ b/google-apis-common/src/lib.rs @@ -865,28 +865,12 @@ mod yup_oauth2_impl { #[cfg(test)] mod test_api { use super::*; - use hyper; use std::default::Default; - use std::io::Read; use std::str::FromStr; use serde_json as json; use serde::{Serialize, Deserialize}; - const EXPECTED: &'static str = "\r\n--MDuXWGyeE33QFXGchb2VFWc4Z7945d\r\n\ -Content-Length: 50\r\n\ -Content-Type: application/json\r\n\ -\r\n\ -foo\r\n\ ---MDuXWGyeE33QFXGchb2VFWc4Z7945d\r\n\ -Content-Length: 25\r\n\ -Content-Type: application/plain\r\n\ -\r\n\ -bar\r\n\ ---MDuXWGyeE33QFXGchb2VFWc4Z7945d--"; - - const EXPECTED_LEN: usize = 223; - #[test] fn serde() { #[derive(Default, Serialize, Deserialize)] @@ -901,7 +885,7 @@ bar\r\n\ json::to_string(&f).unwrap(); // should work let j = "{\"opt\":null,\"req\":0,\"vec\":[]}"; - let f: Foo = json::from_str(j).unwrap(); + let _f: Foo = json::from_str(j).unwrap(); // This fails, unless 'vec' is optional // let j = "{\"opt\":null,\"req\":0}"; @@ -938,7 +922,7 @@ bar\r\n\ #[test] fn dyn_delegate_is_send() { - fn with_send(x: impl Send) {} + fn with_send(_x: impl Send) {} let mut dd = DefaultDelegate::default(); let dlg: &mut dyn Delegate = &mut dd;