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_f64(arg_from_str(value, err, field, "int"))
283                                    .expect("valid f64"),
284                            ),
285                            JsonType::Uint => Value::Number(
286                                json::Number::from_f64(arg_from_str(value, err, field, "uint"))
287                                    .expect("valid f64"),
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 '{}' with error: {}.",
458                path, err
459            ),
460            ApplicationSecretError::FormatError(ref path) => writeln!(
461                f,
462                "'installed' field is unset in secret file at '{}'.",
463                path
464            ),
465        }
466    }
467}
468
469#[derive(Debug)]
470pub enum ConfigurationError {
471    DirectoryCreationFailed((String, io::Error)),
472    DirectoryUnset,
473    HomeExpansionFailed(String),
474    Secret(ApplicationSecretError),
475    Io((String, io::Error)),
476}
477
478impl fmt::Display for ConfigurationError {
479    fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
480        match *self {
481            ConfigurationError::DirectoryCreationFailed((ref dir, ref err)) => writeln!(
482                f,
483                "Directory '{}' could not be created with error: {}.",
484                dir, err
485            ),
486            ConfigurationError::DirectoryUnset => writeln!(f, "--config-dir was unset or empty."),
487            ConfigurationError::HomeExpansionFailed(ref dir) => writeln!(
488                f,
489                "Couldn't find HOME directory of current user, failed to expand '{}'.",
490                dir
491            ),
492            ConfigurationError::Secret(ref err) => writeln!(f, "Secret -> {}", err),
493            ConfigurationError::Io((ref path, ref err)) => writeln!(
494                f,
495                "IO operation failed on path '{}' with error: {}.",
496                path, err
497            ),
498        }
499    }
500}
501
502#[derive(Debug)]
503pub enum InputError {
504    Io((String, io::Error)),
505    Mime(String),
506}
507
508impl fmt::Display for InputError {
509    fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
510        match *self {
511            InputError::Io((ref file_path, ref io_err)) => writeln!(
512                f,
513                "Failed to open '{}' for reading with error: {}.",
514                file_path, io_err
515            ),
516            InputError::Mime(ref mime) => writeln!(f, "'{}' is not a known mime-type.", mime),
517        }
518    }
519}
520
521#[derive(Debug)]
522pub enum FieldError {
523    PopOnEmpty(String),
524    TrailingFieldSep(String),
525    Unknown(String, Option<String>, Option<String>),
526    Duplicate(String),
527    Empty,
528}
529
530impl fmt::Display for FieldError {
531    fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
532        match *self {
533            FieldError::PopOnEmpty(ref field) => {
534                writeln!(f, "'{}': Cannot move up on empty field cursor.", field)
535            }
536            FieldError::TrailingFieldSep(ref field) => writeln!(
537                f,
538                "'{}': Single field separator may not be last character.",
539                field
540            ),
541            FieldError::Unknown(ref field, ref suggestion, ref value) => {
542                let suffix = match *suggestion {
543                    Some(ref s) => {
544                        let kv = match *value {
545                            Some(ref v) => format!("{}={}", s, v),
546                            None => s.clone(),
547                        };
548                        format!(" Did you mean '{}' ?", kv)
549                    }
550                    None => String::new(),
551                };
552                writeln!(f, "Field '{}' does not exist.{}", field, suffix)
553            }
554            FieldError::Duplicate(ref cursor) => {
555                writeln!(f, "Value at '{}' was already set", cursor)
556            }
557            FieldError::Empty => writeln!(f, "Field names must not be empty."),
558        }
559    }
560}
561
562#[derive(Debug)]
563pub enum CLIError {
564    Configuration(ConfigurationError),
565    ParseError(String, String, String, String),
566    UnknownParameter(String, Vec<&'static str>),
567    InvalidUploadProtocol(String, Vec<String>),
568    InvalidKeyValueSyntax(String, bool),
569    Input(InputError),
570    Field(FieldError),
571    MissingCommandError,
572    MissingMethodError(String),
573}
574
575impl fmt::Display for CLIError {
576    fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
577        match *self {
578            CLIError::Configuration(ref err) => write!(f, "Configuration -> {}", err),
579            CLIError::Input(ref err) => write!(f, "Input -> {}", err),
580            CLIError::Field(ref err) => write!(f, "Field -> {}", err),
581            CLIError::InvalidUploadProtocol(ref proto_name, ref valid_names) => writeln!(
582                f,
583                "'{}' is not a valid upload protocol. Choose from one of {}.",
584                proto_name,
585                valid_names.join(", ")
586            ),
587            CLIError::ParseError(ref arg_name, ref type_name, ref value, ref err_desc) => writeln!(
588                f,
589                "Failed to parse argument '{}' with value '{}' as {} with error: {}.",
590                arg_name, value, type_name, err_desc
591            ),
592            CLIError::UnknownParameter(ref param_name, ref possible_values) => {
593                let suffix = match did_you_mean(param_name, possible_values) {
594                    Some(v) => format!(" Did you mean '{}' ?", v),
595                    None => String::new(),
596                };
597                writeln!(f, "Parameter '{}' is unknown.{}", param_name, suffix)
598            }
599            CLIError::InvalidKeyValueSyntax(ref kv, is_hashmap) => {
600                let hashmap_info = if is_hashmap { "hashmap " } else { "" };
601                writeln!(
602                    f,
603                    "'{}' does not match {}pattern <key>=<value>.",
604                    kv, hashmap_info
605                )
606            }
607            CLIError::MissingCommandError => writeln!(f, "Please specify the main sub-command."),
608            CLIError::MissingMethodError(ref cmd) => writeln!(
609                f,
610                "Please specify the method to call on the '{}' command.",
611                cmd
612            ),
613        }
614    }
615}
616
617#[derive(Debug)]
618pub struct InvalidOptionsError {
619    pub issues: Vec<CLIError>,
620    pub exit_code: i32,
621}
622
623impl Default for InvalidOptionsError {
624    fn default() -> Self {
625        InvalidOptionsError {
626            issues: Vec::new(),
627            exit_code: 1,
628        }
629    }
630}
631
632impl fmt::Display for InvalidOptionsError {
633    fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
634        for issue in &self.issues {
635            issue.fmt(f)?;
636        }
637        Ok(())
638    }
639}
640
641impl InvalidOptionsError {
642    pub fn single(err: CLIError, exit_code: i32) -> InvalidOptionsError {
643        InvalidOptionsError {
644            issues: vec![err],
645            exit_code,
646        }
647    }
648
649    pub fn new() -> InvalidOptionsError {
650        Default::default()
651    }
652}
653
654pub fn assure_config_dir_exists(dir: &str) -> Result<String, CLIError> {
655    let trdir = dir.trim();
656    if trdir.is_empty() {
657        return Err(CLIError::Configuration(ConfigurationError::DirectoryUnset));
658    }
659
660    let expanded_config_dir = if trdir.as_bytes()[0] == b'~' {
661        match env::var("HOME")
662            .ok()
663            .or_else(|| env::var("UserProfile").ok())
664        {
665            None => {
666                return Err(CLIError::Configuration(
667                    ConfigurationError::HomeExpansionFailed(trdir.to_string()),
668                ))
669            }
670            Some(mut user) => {
671                user.push_str(&trdir[1..]);
672                user
673            }
674        }
675    } else {
676        trdir.to_string()
677    };
678
679    if let Err(err) = fs::create_dir(&expanded_config_dir) {
680        if err.kind() != io::ErrorKind::AlreadyExists {
681            return Err(CLIError::Configuration(
682                ConfigurationError::DirectoryCreationFailed((expanded_config_dir, err)),
683            ));
684        }
685    }
686
687    Ok(expanded_config_dir)
688}
689
690pub fn application_secret_from_directory(
691    dir: &str,
692    secret_basename: &str,
693    json_console_secret: &str,
694) -> Result<ApplicationSecret, CLIError> {
695    let secret_path = Path::new(dir).join(secret_basename);
696    let secret_str = || secret_path.as_path().to_str().unwrap().to_string();
697    let secret_io_error = |io_err: io::Error| {
698        Err(CLIError::Configuration(ConfigurationError::Io((
699            secret_str(),
700            io_err,
701        ))))
702    };
703
704    for _ in 0..2 {
705        match fs::File::open(&secret_path) {
706            Err(mut err) => {
707                if err.kind() == io::ErrorKind::NotFound {
708                    // Write our built-in one - user may adjust the written file at will
709
710                    err = match fs::OpenOptions::new()
711                        .create(true)
712                        .write(true)
713                        .truncate(true)
714                        .open(&secret_path)
715                    {
716                        Err(cfe) => cfe,
717                        Ok(mut f) => {
718                            // Assure we convert 'ugly' json string into pretty one
719                            let console_secret: ConsoleApplicationSecret =
720                                json::from_str(json_console_secret).unwrap();
721                            match json::to_writer_pretty(&mut f, &console_secret) {
722                                Err(serde_err) => {
723                                    panic!("Unexpected serde error: {:#?}", serde_err)
724                                }
725                                Ok(_) => continue,
726                            }
727                        }
728                    };
729                    // fall through to IO error handling
730                }
731                return secret_io_error(err);
732            }
733            Ok(f) => match json::de::from_reader::<_, ConsoleApplicationSecret>(f) {
734                Err(json_err) => {
735                    return Err(CLIError::Configuration(ConfigurationError::Secret(
736                        ApplicationSecretError::DecoderError((secret_str(), json_err)),
737                    )))
738                }
739                Ok(console_secret) => match console_secret.installed {
740                    Some(secret) => return Ok(secret),
741                    None => {
742                        return Err(CLIError::Configuration(ConfigurationError::Secret(
743                            ApplicationSecretError::FormatError(secret_str()),
744                        )))
745                    }
746                },
747            },
748        }
749    }
750    unreachable!();
751}
752
753#[cfg(test)]
754mod tests {
755    use super::*;
756
757    use std::default::Default;
758
759    #[test]
760    fn cursor() {
761        let mut c: FieldCursor = Default::default();
762
763        assert_eq!(c.to_string(), "");
764        assert_eq!(c.num_fields(), 0);
765        assert!(c.set("").is_err());
766        assert!(c.set(".").is_ok());
767        assert!(c.set("..").is_err());
768        assert_eq!(c.num_fields(), 0);
769
770        assert!(c.set("foo").is_ok());
771        assert_eq!(c.to_string(), "foo");
772        assert_eq!(c.num_fields(), 1);
773        assert!(c.set("..").is_ok());
774        assert_eq!(c.num_fields(), 0);
775        assert_eq!(c.to_string(), "");
776
777        assert!(c.set("foo.").is_err());
778
779        assert!(c.set("foo.bar").is_ok());
780        assert_eq!(c.num_fields(), 2);
781        assert_eq!(c.to_string(), "foo.bar");
782        assert!(c.set("sub.level").is_ok());
783        assert_eq!(c.num_fields(), 4);
784        assert_eq!(c.to_string(), "foo.bar.sub.level");
785        assert!(c.set("...other").is_ok());
786        assert_eq!(c.to_string(), "foo.bar.other");
787        assert_eq!(c.num_fields(), 3);
788        assert!(c.set(".one.two.three...beer").is_ok());
789        assert_eq!(c.num_fields(), 2);
790        assert_eq!(c.to_string(), "one.beer");
791        assert!(c.set("one.two.three...").is_ok());
792        assert_eq!(c.num_fields(), 3);
793        assert_eq!(c.to_string(), "one.beer.one");
794    }
795}