use std::{fmt::Display, sync::LazyLock};
use clap::error::{Error, ErrorKind};
use eyre::eyre;
use regex::Regex;
use reqwest::header::{HeaderName, HeaderValue};
use serde_json::Value;
use crate::{cli::json_helper::{append_object, extend_object}, request::Payload};
static HEADER_REGEX: LazyLock<Regex> = LazyLock::new(|| Regex::new(r#"^([\w-]+):(.*)$"#).unwrap());
static QUERY_REGEX: LazyLock<Regex> = LazyLock::new(|| Regex::new(r#"^([^=:]+)==(.*)$"#).unwrap());
static PAIR_REGEX: LazyLock<Regex> = LazyLock::new(|| Regex::new(r#"^([^=:]+)=(.*)$"#).unwrap());
#[derive(Clone, Debug, PartialEq)]
pub enum Param {
Header {
name: HeaderName,
content: HeaderValue,
},
Raw(String),
Pair {
key: String,
value: String,
},
FileUpload(String),
Query {
key: String,
value: String,
},
}
impl std::str::FromStr for Param {
type Err = Error;
fn from_str(value: &str) -> Result<Self, Self::Err> {
if let Some(filename) = value.strip_prefix("@") {
Ok(Self::FileUpload(filename.to_string()))
} else if let Some(pair) = QUERY_REGEX.captures(value) {
let key = pair.get(1)
.ok_or(to_error(format!("invalid query {}", value)))?
.as_str();
let value = pair.get(2)
.ok_or(to_error(format!("invalid query {}", value)))?
.as_str();
Ok(Self::Query{ key: key.to_owned(), value: value.to_string() })
} else if let Some(pair) = PAIR_REGEX.captures(value) {
let key = pair.get(1)
.ok_or(to_error(format!("invalid attribute {}", value)))?
.as_str()
.to_string();
let value = pair.get(2)
.ok_or(to_error(format!("invalid attribute {}", value)))?
.as_str()
.to_string();
Ok(Self::Pair { key, value })
} else if let Some(pair) = HEADER_REGEX.captures(value) {
let key = pair.get(1)
.ok_or(to_error(format!("invalid header {}", value)))?
.as_str()
.trim();
let value = pair.get(2)
.ok_or(to_error(format!("invalid header {}", value)))?
.as_str()
.trim();
Ok(Self::Header {
name: HeaderName::from_str(key).map_err(to_error)?,
content: HeaderValue::from_str(value).map_err(to_error)?,
})
} else {
Ok(Self::Raw(value.to_string()))
}
}
}
impl TryFrom<Vec<Param>> for Payload {
type Error = eyre::Error;
fn try_from(value: Vec<Param>) -> Result<Self, Self::Error> {
for param in &value {
if let Param::FileUpload(filename) = param {
return Ok(Self::FileUpload(filename.to_owned()));
}
}
let attributes: Vec<(&str, &str)> = value.iter()
.filter_map(|param| match param {
Param::Pair { key, value } => Some((key.as_str(), value.as_str())),
_ => None,
})
.collect();
let raw_strings: Vec<&str> = value.iter()
.filter_map(|param| match param {
Param::Raw(content) => Some(content.as_str()),
_ => None,
})
.collect();
if attributes.is_empty() {
if raw_strings.is_empty() {
return Ok(Self::NoContent);
}
if raw_strings.len() == 1 {
return Ok(Self::Content(raw_strings.first().unwrap().to_string()));
}
}
let mut res: Value = serde_json::from_str("{}").unwrap();
if let Some(res) = res.as_object_mut() {
for &raw in &raw_strings {
if let Ok(json) = serde_json::from_str::<Value>(raw) {
if let Some(data) = json.as_object() {
extend_object(res, &data);
} else {
return Err(eyre!("{:?} is not an object", raw));
}
} else {
return Err(eyre!("{} is not an object", raw));
}
}
for &(key, value) in &attributes {
let value = serde_json::from_str::<Value>(value)
.unwrap_or_else(|_| Value::String(value.into()));
append_object(res, key, value);
}
}
Ok(Self::Content(serde_json::to_string(&res)?))
}
}
fn to_error(exc: impl Display) -> Error {
clap::Error::raw(ErrorKind::InvalidValue, exc)
}