kodumaro_http_cli/cli/
param.rs1use std::{
2 fmt::{Display, Formatter},
3 str::FromStr,
4 sync::LazyLock,
5};
6
7use eyre::eyre;
8use regex::Regex;
9use reqwest::Url;
10use serde_json::{Map, Value};
11
12use super::util::parse_string;
13
14
15static FILE_PAYLOAD_REGEX: LazyLock<Regex> = LazyLock::new(|| Regex::from_str(r#"^@.+$"#).unwrap());
16static HEADER_REGEX: LazyLock<Regex> = LazyLock::new(|| Regex::from_str(r#"^([\w-]+):(.*)$"#).unwrap());
17static QUERY_REGEX: LazyLock<Regex> = LazyLock::new(|| Regex::from_str(r#"^([^=:]+)==(.*)$"#).unwrap());
18static PAYLOAD_REGEX: LazyLock<Regex> = LazyLock::new(|| Regex::from_str(r#"^([^=:]+)=(.*)$"#).unwrap());
19
20
21#[derive(Clone, Debug, PartialEq)]
22pub enum Param {
23 Header(String, String),
24 Payload(Value),
25 Query(String, String),
26}
27
28impl Display for Param {
29
30 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
31 match self {
32 Self::Header(name, value) => write!(f, "{}:{}", name, value),
33 Self::Payload(value) => write!(f, "{}", value),
34 Self::Query(key, value) => {
35 let mut buf = Url::parse("http://localhost/").unwrap();
36 buf.query_pairs_mut().append_pair(key, value);
37 write!(f, "{}", buf.query().unwrap())
38 }
39 }
40 }
41}
42
43impl FromStr for Param {
44
45 type Err = eyre::Error;
46
47 fn from_str(value: &str) -> Result<Self, Self::Err> {
48
49 if FILE_PAYLOAD_REGEX.is_match(value) {
50 let payload: Value = serde_json::from_str(&parse_string(value)?)?;
51 return Ok(Self::Payload(payload));
52 }
53
54 if let Some(pair) = QUERY_REGEX.captures(value) {
55 let key = pair.get(1).ok_or(eyre!("invalid query {}", value))?.as_str();
56 let value = pair.get(2).ok_or(eyre!("invalid query {}", value))?.as_str();
57 return Ok(Self::Query(key.to_owned(), parse_string(value)?));
58 }
59
60 if let Some(pair) = PAYLOAD_REGEX.captures(value) {
61 let key = pair.get(1).ok_or(eyre!("invalid attribute {}", value))?.as_str();
62 let value = pair.get(2).ok_or(eyre!("invalid attribute {}", value))?.as_str();
63 let value = parse_string(value)?;
64 let value: Value = string_to_value(&value);
65 let mut payload = Map::new();
66 payload.insert(key.to_owned(), value);
67 return Ok(Self::Payload(Value::Object(payload)))
68 }
69
70 if let Some(pair) = HEADER_REGEX.captures(value) {
71 let key = pair.get(1)
72 .ok_or(eyre!("invalid header {}", value))?
73 .as_str()
74 .trim();
75 let value = pair.get(2)
76 .ok_or(eyre!("invalid header {}", value))?
77 .as_str()
78 .trim();
79 return Ok(Self::Header(key.to_owned(), parse_string(value)?));
80 }
81
82 Err(eyre!("could not parse {}", value))
83 }
84}
85
86
87#[cfg(test)]
89mod tests {
90 use super::*;
91
92 #[test]
93 fn test_parse_header() {
94 let param = Param::from_str("Content-Type: application/json").unwrap();
95 assert_eq!(Param::Header("Content-Type".to_string(), "application/json".to_string()), param);
96 }
97
98 #[test]
99 fn test_parse_query() {
100 let param = Param::from_str("foo==bar").unwrap();
101 assert_eq!(Param::Query("foo".to_string(), "bar".to_string()), param);
102 }
103
104 #[test]
105 fn test_parse_payload() {
106 let param = Param::from_str("num=42").unwrap();
107 if let Param::Payload(Value::Object(param)) = param {
108 let num= param.get("num").unwrap();
109 assert!(num.is_i64());
110 assert_eq!(42, num.as_i64().unwrap());
111 } else {
112 panic!();
113 }
114 }
115
116 #[test]
117 fn test_invalid_param() {
118 let param = Param::from_str("invalid param");
119 assert!(param.is_err());
120 }
121}
122
123fn string_to_value(value: &str) -> Value {
124 if value.starts_with("str!") {
125 return Value::String(value[4..].to_string());
126 }
127
128 serde_json::from_str(&value).unwrap_or(Value::String(value.to_string()))
129}