kodumaro_http_cli/cli/
param.rs

1use 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/*----------------------------------------------------------------------------*/
88#[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}