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
26pub 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
46pub 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 }
105}
106
107impl AsRef<str> for UploadProtocol {
108 fn as_ref(&self) -> &str {
109 match *self {
110 UploadProtocol::Simple => "simple",
111 }
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 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 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 }
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}