google_clis_common/
lib.rs

1use clap::arg_enum;
2use mime::Mime;
3use serde_json as json;
4use serde_json::value::Value;
5use yup_oauth2::{ApplicationSecret, ConsoleApplicationSecret};
6
7use std::env;
8use std::fmt;
9use std::fs;
10use std::io;
11use std::io::{stdout, Write};
12use std::path::Path;
13use std::str::FromStr;
14
15use std::default::Default;
16use std::fmt::Display;
17
18const FIELD_SEP: char = '.';
19
20pub enum ComplexType {
21    Pod,
22    Vec,
23    Map,
24}
25
26// Null,
27// Bool(bool),
28// I64(i64),
29// U64(u64),
30// F64(f64),
31// String(String),
32
33pub enum JsonType {
34    Boolean,
35    Int,
36    Uint,
37    Float,
38    String,
39}
40
41pub struct JsonTypeInfo {
42    pub jtype: JsonType,
43    pub ctype: ComplexType,
44}
45
46// Based on @erickt user comment. Thanks for the idea !
47// Remove all keys whose values are null from given value (changed in place)
48pub fn remove_json_null_values(value: &mut Value) {
49    match *value {
50        Value::Object(ref mut map) => {
51            let mut for_removal = Vec::new();
52
53            for (key, value) in map.iter_mut() {
54                if value.is_null() {
55                    for_removal.push(key.clone());
56                } else {
57                    remove_json_null_values(value);
58                }
59            }
60
61            for key in &for_removal {
62                map.remove(key);
63            }
64        }
65        json::value::Value::Array(ref mut arr) => {
66            let mut i = 0;
67            while i < arr.len() {
68                if arr[i].is_null() {
69                    arr.remove(i);
70                } else {
71                    remove_json_null_values(&mut arr[i]);
72                    i += 1;
73                }
74            }
75        }
76        _ => {}
77    }
78}
79
80fn did_you_mean<'a>(v: &str, possible_values: &[&'a str]) -> Option<&'a str> {
81    let mut candidate: Option<(f64, &str)> = None;
82    for pv in possible_values {
83        let confidence = strsim::jaro_winkler(v, pv);
84        if confidence > 0.8 && (candidate.is_none() || (candidate.as_ref().unwrap().0 < confidence))
85        {
86            candidate = Some((confidence, pv));
87        }
88    }
89    match candidate {
90        None => None,
91        Some((_, candidate)) => Some(candidate),
92    }
93}
94
95pub enum CallType {
96    Upload(UploadProtocol),
97    Standard,
98}
99
100arg_enum! {
101    pub enum UploadProtocol {
102        Simple,
103        // Resumable // This seems to be lost during the async conversion
104    }
105}
106
107impl AsRef<str> for UploadProtocol {
108    fn as_ref(&self) -> &str {
109        match *self {
110            UploadProtocol::Simple => "simple",
111            // UploadProtocol::Resumable => "resumable",
112        }
113    }
114}
115
116impl AsRef<str> for CallType {
117    fn as_ref(&self) -> &str {
118        match *self {
119            CallType::Upload(ref proto) => proto.as_ref(),
120            CallType::Standard => "standard-request",
121        }
122    }
123}
124
125#[derive(Clone, Default)]
126pub struct FieldCursor(Vec<String>);
127
128impl Display for FieldCursor {
129    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
130        write!(f, "{}", self.0.join("."))
131    }
132}
133
134impl From<&'static str> for FieldCursor {
135    fn from(value: &'static str) -> FieldCursor {
136        let mut res = FieldCursor::default();
137        res.set(value).unwrap();
138        res
139    }
140}
141
142fn assure_entry<'a>(m: &'a mut json::Map<String, Value>, k: &str) -> &'a mut Value {
143    if m.contains_key(k) {
144        return m.get_mut(k).expect("value to exist");
145    }
146    m.insert(k.to_owned(), Value::Object(Default::default()));
147    m.get_mut(k).expect("value to exist")
148}
149
150impl FieldCursor {
151    pub fn set(&mut self, value: &str) -> Result<(), CLIError> {
152        if value.is_empty() {
153            return Err(CLIError::Field(FieldError::Empty));
154        }
155
156        let mut first_is_field_sep = false;
157        let mut char_count: usize = 0;
158        let mut last_c = FIELD_SEP;
159        let mut num_conscutive_field_seps = 0;
160
161        let mut field = String::new();
162        let mut fields = self.0.clone();
163
164        let push_field = |fs: &mut Vec<String>, f: &mut String| {
165            if !f.is_empty() {
166                fs.push(f.clone());
167                f.truncate(0);
168            }
169        };
170
171        for (cid, c) in value.chars().enumerate() {
172            char_count += 1;
173
174            if c == FIELD_SEP {
175                if cid == 0 {
176                    first_is_field_sep = true;
177                }
178                num_conscutive_field_seps += 1;
179                if cid > 0 && last_c == FIELD_SEP {
180                    if fields.pop().is_none() {
181                        return Err(CLIError::Field(FieldError::PopOnEmpty(value.to_string())));
182                    }
183                } else {
184                    push_field(&mut fields, &mut field);
185                }
186            } else {
187                num_conscutive_field_seps = 0;
188                if cid == 1 && first_is_field_sep {
189                    fields.truncate(0);
190                }
191                field.push(c);
192            }
193
194            last_c = c;
195        }
196
197        push_field(&mut fields, &mut field);
198
199        if char_count == 1 && first_is_field_sep {
200            fields.truncate(0);
201        }
202        if char_count > 1 && num_conscutive_field_seps == 1 {
203            return Err(CLIError::Field(FieldError::TrailingFieldSep(
204                value.to_string(),
205            )));
206        }
207
208        self.0 = fields;
209        Ok(())
210    }
211
212    pub fn did_you_mean(value: &str, possible_values: &[&str]) -> Option<String> {
213        if value.is_empty() {
214            return None;
215        }
216
217        let mut last_c = FIELD_SEP;
218
219        let mut field = String::new();
220        let mut output = String::new();
221
222        let push_field = |fs: &mut String, f: &mut String| {
223            if !f.is_empty() {
224                fs.push_str(match did_you_mean(f, possible_values) {
225                    Some(candidate) => candidate,
226                    None => f,
227                });
228                f.truncate(0);
229            }
230        };
231
232        for c in value.chars() {
233            if c == FIELD_SEP {
234                if last_c != FIELD_SEP {
235                    push_field(&mut output, &mut field);
236                }
237                output.push(c);
238            } else {
239                field.push(c);
240            }
241
242            last_c = c;
243        }
244
245        push_field(&mut output, &mut field);
246
247        if output == value {
248            None
249        } else {
250            Some(output)
251        }
252    }
253
254    pub fn set_json_value(
255        &self,
256        mut object: &mut Value,
257        value: &str,
258        type_info: JsonTypeInfo,
259        err: &mut InvalidOptionsError,
260        orig_cursor: &FieldCursor,
261    ) {
262        assert!(!self.0.is_empty());
263
264        for field in &self.0[..self.0.len() - 1] {
265            let tmp = object;
266            object = match *tmp {
267                Value::Object(ref mut mapping) => assure_entry(mapping, field),
268                _ => panic!("We don't expect non-object Values here ..."),
269            };
270        }
271
272        match *object {
273            Value::Object(ref mut mapping) => {
274                let field = &self.0[self.0.len() - 1];
275                let to_jval =
276                    |value: &str, jtype: JsonType, err: &mut InvalidOptionsError| -> Value {
277                        match jtype {
278                            JsonType::Boolean => {
279                                Value::Bool(arg_from_str(value, err, field, "boolean"))
280                            }
281                            JsonType::Int => Value::Number(
282                                json::Number::from_i128(arg_from_str(value, err, field, "int"))
283                                    .expect("valid i128"),
284                            ),
285                            JsonType::Uint => Value::Number(
286                                json::Number::from_u128(arg_from_str(value, err, field, "uint"))
287                                    .expect("valid u128"),
288                            ),
289                            JsonType::Float => Value::Number(
290                                json::Number::from_f64(arg_from_str(value, err, field, "float"))
291                                    .expect("valid f64"),
292                            ),
293                            JsonType::String => Value::String(value.to_owned()),
294                        }
295                    };
296
297                match type_info.ctype {
298                    ComplexType::Pod => {
299                        if mapping
300                            .insert(field.to_owned(), to_jval(value, type_info.jtype, err))
301                            .is_some()
302                        {
303                            err.issues.push(CLIError::Field(FieldError::Duplicate(
304                                orig_cursor.to_string(),
305                            )));
306                        }
307                    }
308                    ComplexType::Vec => match *assure_entry(mapping, field) {
309                        Value::Array(ref mut values) => {
310                            values.push(to_jval(value, type_info.jtype, err))
311                        }
312                        _ => unreachable!(),
313                    },
314                    ComplexType::Map => {
315                        let (key, value) = parse_kv_arg(value, err, true);
316                        let jval = to_jval(value.unwrap_or(""), type_info.jtype, err);
317
318                        match *assure_entry(mapping, field) {
319                            Value::Object(ref mut value_map) => {
320                                if value_map.insert(key.to_owned(), jval).is_some() {
321                                    err.issues.push(CLIError::Field(FieldError::Duplicate(
322                                        orig_cursor.to_string(),
323                                    )));
324                                }
325                            }
326                            _ => unreachable!(),
327                        }
328                    }
329                }
330            }
331            _ => unreachable!(),
332        }
333    }
334
335    pub fn num_fields(&self) -> usize {
336        self.0.len()
337    }
338}
339
340pub fn parse_kv_arg<'a>(
341    kv: &'a str,
342    err: &mut InvalidOptionsError,
343    for_hashmap: bool,
344) -> (&'a str, Option<&'a str>) {
345    let mut add_err = || {
346        err.issues
347            .push(CLIError::InvalidKeyValueSyntax(kv.to_string(), for_hashmap))
348    };
349    match kv.find('=') {
350        None => {
351            add_err();
352            (kv, None)
353        }
354        Some(pos) => {
355            let key = &kv[..pos];
356            if kv.len() <= pos + 1 {
357                add_err();
358                return (key, Some(""));
359            }
360            (key, Some(&kv[pos + 1..]))
361        }
362    }
363}
364
365pub fn calltype_from_str(
366    name: &str,
367    valid_protocols: Vec<String>,
368    err: &mut InvalidOptionsError,
369) -> CallType {
370    CallType::Upload(match UploadProtocol::from_str(name) {
371        Ok(up) => up,
372        Err(_msg) => {
373            err.issues.push(CLIError::InvalidUploadProtocol(
374                name.to_string(),
375                valid_protocols,
376            ));
377            UploadProtocol::Simple
378        }
379    })
380}
381
382pub fn input_file_from_opts(file_path: &str, err: &mut InvalidOptionsError) -> Option<fs::File> {
383    match fs::File::open(file_path) {
384        Ok(f) => Some(f),
385        Err(io_err) => {
386            err.issues.push(CLIError::Input(InputError::Io((
387                file_path.to_string(),
388                io_err,
389            ))));
390            None
391        }
392    }
393}
394
395pub fn input_mime_from_opts(mime: &str, err: &mut InvalidOptionsError) -> Option<Mime> {
396    match mime.parse() {
397        Ok(m) => Some(m),
398        Err(_) => {
399            err.issues
400                .push(CLIError::Input(InputError::Mime(mime.to_string())));
401            None
402        }
403    }
404}
405
406pub fn writer_from_opts(arg: Option<&str>) -> Result<Box<dyn Write>, io::Error> {
407    let f = arg.unwrap_or("-");
408    match f {
409        "-" => Ok(Box::new(stdout())),
410        _ => match fs::OpenOptions::new()
411            .create(true)
412            .truncate(true)
413            .write(true)
414            .open(f)
415        {
416            Ok(f) => Ok(Box::new(f)),
417            Err(io_err) => Err(io_err),
418        },
419    }
420}
421
422pub fn arg_from_str<'a, T>(
423    arg: &str,
424    err: &mut InvalidOptionsError,
425    arg_name: &'a str,
426    arg_type: &'a str,
427) -> T
428where
429    T: FromStr + Default,
430    <T as FromStr>::Err: fmt::Display,
431{
432    match FromStr::from_str(arg) {
433        Err(perr) => {
434            err.issues.push(CLIError::ParseError(
435                arg_name.to_owned(),
436                arg_type.to_owned(),
437                arg.to_string(),
438                format!("{perr}"),
439            ));
440            Default::default()
441        }
442        Ok(v) => v,
443    }
444}
445
446#[derive(Debug)]
447pub enum ApplicationSecretError {
448    DecoderError((String, json::Error)),
449    FormatError(String),
450}
451
452impl fmt::Display for ApplicationSecretError {
453    fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
454        match *self {
455            ApplicationSecretError::DecoderError((ref path, ref err)) => writeln!(
456                f,
457                "Could not decode file at '{path}' with error: {err}."
458            ),
459            ApplicationSecretError::FormatError(ref path) => writeln!(
460                f,
461                "'installed' field is unset in secret file at '{path}'."
462            ),
463        }
464    }
465}
466
467#[derive(Debug)]
468pub enum ConfigurationError {
469    DirectoryCreationFailed((String, io::Error)),
470    DirectoryUnset,
471    HomeExpansionFailed(String),
472    Secret(ApplicationSecretError),
473    Io((String, io::Error)),
474}
475
476impl fmt::Display for ConfigurationError {
477    fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
478        match *self {
479            ConfigurationError::DirectoryCreationFailed((ref dir, ref err)) => writeln!(
480                f,
481                "Directory '{dir}' could not be created with error: {err}."
482            ),
483            ConfigurationError::DirectoryUnset => writeln!(f, "--config-dir was unset or empty."),
484            ConfigurationError::HomeExpansionFailed(ref dir) => writeln!(
485                f,
486                "Couldn't find HOME directory of current user, failed to expand '{dir}'."
487            ),
488            ConfigurationError::Secret(ref err) => writeln!(f, "Secret -> {err}"),
489            ConfigurationError::Io((ref path, ref err)) => writeln!(
490                f,
491                "IO operation failed on path '{path}' with error: {err}."
492            ),
493        }
494    }
495}
496
497#[derive(Debug)]
498pub enum InputError {
499    Io((String, io::Error)),
500    Mime(String),
501}
502
503impl fmt::Display for InputError {
504    fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
505        match *self {
506            InputError::Io((ref file_path, ref io_err)) => writeln!(
507                f,
508                "Failed to open '{file_path}' for reading with error: {io_err}."
509            ),
510            InputError::Mime(ref mime) => writeln!(f, "'{mime}' is not a known mime-type."),
511        }
512    }
513}
514
515#[derive(Debug)]
516pub enum FieldError {
517    PopOnEmpty(String),
518    TrailingFieldSep(String),
519    Unknown(String, Option<String>, Option<String>),
520    Duplicate(String),
521    Empty,
522}
523
524impl fmt::Display for FieldError {
525    fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
526        match *self {
527            FieldError::PopOnEmpty(ref field) => {
528                writeln!(f, "'{field}': Cannot move up on empty field cursor.")
529            }
530            FieldError::TrailingFieldSep(ref field) => writeln!(
531                f,
532                "'{field}': Single field separator may not be last character."
533            ),
534            FieldError::Unknown(ref field, ref suggestion, ref value) => {
535                let suffix = match *suggestion {
536                    Some(ref s) => {
537                        let kv = match *value {
538                            Some(ref v) => format!("{s}={v}"),
539                            None => s.clone(),
540                        };
541                        format!(" Did you mean '{kv}' ?")
542                    }
543                    None => String::new(),
544                };
545                writeln!(f, "Field '{field}' does not exist.{suffix}")
546            }
547            FieldError::Duplicate(ref cursor) => {
548                writeln!(f, "Value at '{cursor}' was already set")
549            }
550            FieldError::Empty => writeln!(f, "Field names must not be empty."),
551        }
552    }
553}
554
555#[derive(Debug)]
556pub enum CLIError {
557    Configuration(ConfigurationError),
558    ParseError(String, String, String, String),
559    UnknownParameter(String, Vec<&'static str>),
560    InvalidUploadProtocol(String, Vec<String>),
561    InvalidKeyValueSyntax(String, bool),
562    Input(InputError),
563    Field(FieldError),
564    MissingCommandError,
565    MissingMethodError(String),
566}
567
568impl fmt::Display for CLIError {
569    fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
570        match *self {
571            CLIError::Configuration(ref err) => write!(f, "Configuration -> {err}"),
572            CLIError::Input(ref err) => write!(f, "Input -> {err}"),
573            CLIError::Field(ref err) => write!(f, "Field -> {err}"),
574            CLIError::InvalidUploadProtocol(ref proto_name, ref valid_names) => writeln!(
575                f,
576                "'{}' is not a valid upload protocol. Choose from one of {}.",
577                proto_name,
578                valid_names.join(", ")
579            ),
580            CLIError::ParseError(ref arg_name, ref type_name, ref value, ref err_desc) => writeln!(
581                f,
582                "Failed to parse argument '{arg_name}' with value '{value}' as {type_name} with error: {err_desc}."
583            ),
584            CLIError::UnknownParameter(ref param_name, ref possible_values) => {
585                let suffix = match did_you_mean(param_name, possible_values) {
586                    Some(v) => format!(" Did you mean '{v}' ?"),
587                    None => String::new(),
588                };
589                writeln!(f, "Parameter '{param_name}' is unknown.{suffix}")
590            }
591            CLIError::InvalidKeyValueSyntax(ref kv, is_hashmap) => {
592                let hashmap_info = if is_hashmap { "hashmap " } else { "" };
593                writeln!(
594                    f,
595                    "'{kv}' does not match {hashmap_info}pattern <key>=<value>."
596                )
597            }
598            CLIError::MissingCommandError => writeln!(f, "Please specify the main sub-command."),
599            CLIError::MissingMethodError(ref cmd) => writeln!(
600                f,
601                "Please specify the method to call on the '{cmd}' command."
602            ),
603        }
604    }
605}
606
607#[derive(Debug)]
608pub struct InvalidOptionsError {
609    pub issues: Vec<CLIError>,
610    pub exit_code: i32,
611}
612
613impl Default for InvalidOptionsError {
614    fn default() -> Self {
615        InvalidOptionsError {
616            issues: Vec::new(),
617            exit_code: 1,
618        }
619    }
620}
621
622impl fmt::Display for InvalidOptionsError {
623    fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
624        for issue in &self.issues {
625            issue.fmt(f)?;
626        }
627        Ok(())
628    }
629}
630
631impl InvalidOptionsError {
632    pub fn single(err: CLIError, exit_code: i32) -> InvalidOptionsError {
633        InvalidOptionsError {
634            issues: vec![err],
635            exit_code,
636        }
637    }
638
639    pub fn new() -> InvalidOptionsError {
640        Default::default()
641    }
642}
643
644pub fn assure_config_dir_exists(dir: &str) -> Result<String, CLIError> {
645    let trdir = dir.trim();
646    if trdir.is_empty() {
647        return Err(CLIError::Configuration(ConfigurationError::DirectoryUnset));
648    }
649
650    let expanded_config_dir = if trdir.as_bytes()[0] == b'~' {
651        match env::var("HOME")
652            .ok()
653            .or_else(|| env::var("UserProfile").ok())
654        {
655            None => {
656                return Err(CLIError::Configuration(
657                    ConfigurationError::HomeExpansionFailed(trdir.to_string()),
658                ))
659            }
660            Some(mut user) => {
661                user.push_str(&trdir[1..]);
662                user
663            }
664        }
665    } else {
666        trdir.to_string()
667    };
668
669    if let Err(err) = fs::create_dir(&expanded_config_dir) {
670        if err.kind() != io::ErrorKind::AlreadyExists {
671            return Err(CLIError::Configuration(
672                ConfigurationError::DirectoryCreationFailed((expanded_config_dir, err)),
673            ));
674        }
675    }
676
677    Ok(expanded_config_dir)
678}
679
680pub fn application_secret_from_directory(
681    dir: &str,
682    secret_basename: &str,
683    json_console_secret: &str,
684) -> Result<ApplicationSecret, CLIError> {
685    let secret_path = Path::new(dir).join(secret_basename);
686    let secret_str = || secret_path.as_path().to_str().unwrap().to_string();
687    let secret_io_error = |io_err: io::Error| {
688        Err(CLIError::Configuration(ConfigurationError::Io((
689            secret_str(),
690            io_err,
691        ))))
692    };
693
694    for _ in 0..2 {
695        match fs::File::open(&secret_path) {
696            Err(mut err) => {
697                if err.kind() == io::ErrorKind::NotFound {
698                    // Write our built-in one - user may adjust the written file at will
699
700                    err = match fs::OpenOptions::new()
701                        .create(true)
702                        .write(true)
703                        .truncate(true)
704                        .open(&secret_path)
705                    {
706                        Err(cfe) => cfe,
707                        Ok(mut f) => {
708                            // Assure we convert 'ugly' json string into pretty one
709                            let console_secret: ConsoleApplicationSecret =
710                                json::from_str(json_console_secret).unwrap();
711                            match json::to_writer_pretty(&mut f, &console_secret) {
712                                Err(serde_err) => {
713                                    panic!("Unexpected serde error: {serde_err:#?}")
714                                }
715                                Ok(_) => continue,
716                            }
717                        }
718                    };
719                    // fall through to IO error handling
720                }
721                return secret_io_error(err);
722            }
723            Ok(f) => match json::de::from_reader::<_, ConsoleApplicationSecret>(f) {
724                Err(json_err) => {
725                    return Err(CLIError::Configuration(ConfigurationError::Secret(
726                        ApplicationSecretError::DecoderError((secret_str(), json_err)),
727                    )))
728                }
729                Ok(console_secret) => match console_secret.installed {
730                    Some(secret) => return Ok(secret),
731                    None => {
732                        return Err(CLIError::Configuration(ConfigurationError::Secret(
733                            ApplicationSecretError::FormatError(secret_str()),
734                        )))
735                    }
736                },
737            },
738        }
739    }
740    unreachable!();
741}
742
743#[cfg(test)]
744mod tests {
745    use super::*;
746
747    use std::default::Default;
748
749    #[test]
750    fn cursor() {
751        let mut c: FieldCursor = Default::default();
752
753        assert_eq!(c.to_string(), "");
754        assert_eq!(c.num_fields(), 0);
755        assert!(c.set("").is_err());
756        assert!(c.set(".").is_ok());
757        assert!(c.set("..").is_err());
758        assert_eq!(c.num_fields(), 0);
759
760        assert!(c.set("foo").is_ok());
761        assert_eq!(c.to_string(), "foo");
762        assert_eq!(c.num_fields(), 1);
763        assert!(c.set("..").is_ok());
764        assert_eq!(c.num_fields(), 0);
765        assert_eq!(c.to_string(), "");
766
767        assert!(c.set("foo.").is_err());
768
769        assert!(c.set("foo.bar").is_ok());
770        assert_eq!(c.num_fields(), 2);
771        assert_eq!(c.to_string(), "foo.bar");
772        assert!(c.set("sub.level").is_ok());
773        assert_eq!(c.num_fields(), 4);
774        assert_eq!(c.to_string(), "foo.bar.sub.level");
775        assert!(c.set("...other").is_ok());
776        assert_eq!(c.to_string(), "foo.bar.other");
777        assert_eq!(c.num_fields(), 3);
778        assert!(c.set(".one.two.three...beer").is_ok());
779        assert_eq!(c.num_fields(), 2);
780        assert_eq!(c.to_string(), "one.beer");
781        assert!(c.set("one.two.three...").is_ok());
782        assert_eq!(c.num_fields(), 3);
783        assert_eq!(c.to_string(), "one.beer.one");
784    }
785}