wick_component_cli/
utils.rs

1use serde_json::Value;
2use wick_interface_types::{OperationSignature, Type};
3use wick_packet::Packet;
4
5use crate::Error;
6
7/// Parse CLI arguments into a [wick_packet::PacketStream]
8pub fn parse_args(args: &[String], sig: &OperationSignature) -> Result<Vec<Packet>, Error> {
9  let mut packets = Vec::new();
10  let mut iter = args.iter();
11  while let Some(next) = iter.next() {
12    if !next.starts_with("--") {
13      return Err(Error::InvalidArgument(next.clone()));
14    }
15    let next = next.trim_start_matches("--");
16    let (name, value) = split_arg(next);
17    let value = match value {
18      Some(value) => value,
19      None => {
20        let value = iter.next();
21        if value.is_none() {
22          return Err(Error::MissingArgumentValue(name.to_owned()));
23        }
24        value.unwrap()
25      }
26    };
27    let input = sig.inputs.iter().find(|i| i.name == name);
28    if input.is_none() {
29      return Err(Error::InvalidInput(name.to_owned()));
30    }
31    let input = input.unwrap();
32
33    let value = if value.starts_with('@') {
34      let path = value.trim_start_matches('@');
35
36      match input.ty() {
37        Type::String => Value::String(std::fs::read_to_string(path)?),
38        Type::Bytes => {
39          let bytes: wick_packet::Base64Bytes = std::fs::read(path)?.into();
40          serde_json::to_value(bytes).unwrap()
41        }
42        _ => encode(&std::fs::read_to_string(path)?),
43      }
44    } else {
45      match input.ty() {
46        // Datetime can be parsed from a string or a number but numbers need to be stringified.
47        // Strings must be explicit because a bare number will be parsed as a number.
48        Type::Datetime | Type::String => {
49          if is_valid(value) {
50            coerce_string(name, value, input.ty())?
51          } else {
52            // if it's not valid JSON then it's a bare string.
53            value.into()
54          }
55        }
56        // serde_json does an adequate job on the rest.
57        _ => encode(value),
58      }
59    };
60
61    // Note on above: complex objects with embedded Datetime/Strings
62    // may not be parsed correctly but that's an edge case we're ignoring for now.
63
64    let payload = Packet::encode(name, value);
65
66    packets.push(payload);
67  }
68
69  Ok(packets)
70}
71
72fn encode(value: &str) -> Value {
73  serde_json::from_str::<Value>(value).unwrap_or_else(|_| Value::String(value.to_owned()))
74}
75
76fn coerce_string(name: &str, value: &str, ty: &Type) -> Result<Value, Error> {
77  let val = serde_json::from_str::<Value>(value).unwrap_or_else(|_| Value::String(value.to_owned()));
78
79  Ok(match val {
80    serde_json::Value::Null => Value::String("null".to_owned()),
81    serde_json::Value::Bool(v) => Value::String(v.to_string()),
82    serde_json::Value::Number(v) => Value::String(v.to_string()),
83    serde_json::Value::String(v) => Value::String(v),
84    serde_json::Value::Array(_v) => return Err(Error::encoding(name, value, ty.clone())),
85    serde_json::Value::Object(_v) => return Err(Error::encoding(name, value, ty.clone())),
86  })
87}
88
89#[must_use]
90fn split_arg(arg: &str) -> (&str, Option<&str>) {
91  let mut parts = arg.split('=');
92  (parts.next().unwrap(), parts.next())
93}
94
95fn is_valid(string: &str) -> bool {
96  let parsed: Result<Value, _> = serde_json::from_str(string);
97  parsed.is_ok()
98}
99
100#[cfg(test)]
101mod tests {
102  use anyhow::Result;
103  use wick_interface_types::Field;
104  use wick_packet::DateTime;
105
106  use super::*;
107
108  fn to_vec(list: &[&str]) -> Vec<String> {
109    list.iter().map(|s| (*s).to_owned()).collect()
110  }
111
112  fn sig(fields: &[(&str, Type)]) -> OperationSignature {
113    OperationSignature::new(
114      "test".to_owned(),
115      fields
116        .iter()
117        .map(|(n, t)| Field::new((*n).to_owned(), t.clone()))
118        .collect(),
119      Default::default(),
120      Default::default(),
121    )
122  }
123
124  #[test_logger::test]
125  fn parse_separate_args() -> Result<()> {
126    let args = to_vec(&["--input-a", "value-a"]);
127    let packets = parse_args(&args, &sig(&[("input-a", Type::String)]))?;
128    assert_eq!(packets[0], Packet::encode("input-a", "value-a"));
129    Ok(())
130  }
131
132  #[test_logger::test]
133  fn parse_numbers() -> Result<()> {
134    let args = to_vec(&["--input-a", "123"]);
135    let packets = parse_args(&args, &sig(&[("input-a", Type::U64)]))?;
136    assert_eq!(packets[0], Packet::encode("input-a", 123));
137    assert_eq!(packets[0].clone().decode::<i32>().unwrap(), 123);
138    Ok(())
139  }
140
141  #[test_logger::test]
142  #[ignore = "This is broken and should be fixed."]
143  fn parse_date_millis() -> Result<()> {
144    let date = wick_packet::parse_date("2021-04-12T22:10:57+02:00")?;
145    let args = to_vec(&["--input-a", &date.timestamp_millis().to_string()]);
146    println!("args: {:?}", args);
147    let packets = parse_args(&args, &sig(&[("input-a", Type::Datetime)]))?;
148    assert_eq!(packets[0].clone().decode::<DateTime>().unwrap(), date);
149    Ok(())
150  }
151
152  #[test_logger::test]
153  fn parse_date_str() -> Result<()> {
154    let date = wick_packet::parse_date("2021-04-12T22:10:57+02:00")?;
155    let args = to_vec(&["--input-a", "2021-04-12T22:10:57+02:00"]);
156    println!("args: {:?}", args);
157    let packets = parse_args(&args, &sig(&[("input-a", Type::Datetime)]))?;
158    assert_eq!(packets[0].clone().decode::<DateTime>().unwrap(), date);
159    Ok(())
160  }
161
162  #[test_logger::test]
163  fn parse_combined_args() -> Result<()> {
164    let args = to_vec(&["--input-a=value-a"]);
165    let packets = parse_args(&args, &sig(&[("input-a", Type::String)]))?;
166    assert_eq!(packets[0], Packet::encode("input-a", "value-a"));
167    Ok(())
168  }
169
170  #[test_logger::test]
171  fn parse_mixed_args() -> Result<()> {
172    let args = to_vec(&["--input-a", "value-a", "--input-b=value-b"]);
173    let packets = parse_args(&args, &sig(&[("input-a", Type::String), ("input-b", Type::String)]))?;
174    assert_eq!(packets[0], Packet::encode("input-a", "value-a"));
175    assert_eq!(packets[1], Packet::encode("input-b", "value-b"));
176    Ok(())
177  }
178
179  #[test_logger::test]
180  fn parse_err_invalid() -> Result<()> {
181    let args = to_vec(&["input-a", "value-a", "--input-b=value-b"]);
182    let result = parse_args(&args, &sig(&[("input-a", Type::String), ("input-b", Type::String)]));
183    assert!(result.is_err());
184    Ok(())
185  }
186
187  #[test_logger::test]
188  fn parse_err_dangling() -> Result<()> {
189    let args = to_vec(&["--input-a", "value-a", "--input-b"]);
190    let result = parse_args(&args, &sig(&[("input-a", Type::String), ("input-b", Type::String)]));
191    assert!(result.is_err());
192    Ok(())
193  }
194
195  #[test_logger::test]
196  fn parse_arg_numeric() -> Result<()> {
197    let args = to_vec(&["--num", "2000"]);
198    let packets = parse_args(&args, &sig(&[("num", Type::U32)]))?;
199    assert_eq!(packets[0], Packet::encode("num", 2000));
200    Ok(())
201  }
202
203  #[test_logger::test]
204  fn test_is_valid() -> Result<()> {
205    let int = "1234567890";
206    assert!(is_valid(int));
207    let float = "12345.67890";
208    assert!(is_valid(float));
209    let obj = "{}";
210    assert!(is_valid(obj));
211    let array = "[]";
212    assert!(is_valid(array));
213    let naked_string = "hello world";
214    assert!(!is_valid(naked_string));
215    let string = "\"hello world\"";
216    assert!(is_valid(string));
217    Ok(())
218  }
219}