use std::str::FromStr;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use serde_json::Value;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "UPPERCASE")]
pub enum HttpMethod {
Get,
Post,
Put,
Delete,
Patch,
}
#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
#[error("unknown HTTP method: {0}")]
pub struct UnknownHttpMethod(pub String);
impl FromStr for HttpMethod {
type Err = UnknownHttpMethod;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"get" => Ok(Self::Get),
"post" => Ok(Self::Post),
"put" => Ok(Self::Put),
"delete" => Ok(Self::Delete),
"patch" => Ok(Self::Patch),
_ => Err(UnknownHttpMethod(s.to_string())),
}
}
}
impl std::fmt::Display for HttpMethod {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Get => write!(f, "GET"),
Self::Post => write!(f, "POST"),
Self::Put => write!(f, "PUT"),
Self::Delete => write!(f, "DELETE"),
Self::Patch => write!(f, "PATCH"),
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ApiRequest {
pub method: HttpMethod,
pub path: String,
pub query_params: Vec<(String, String)>,
pub body: Option<RequestBody>,
pub content_type: Option<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum RequestBody {
Json(Value),
Multipart(MultipartBody),
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct MultipartBody {
pub text_fields: Vec<(String, String)>,
pub binary_fields: Vec<BinaryField>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct BinaryField {
pub field_name: String,
pub data: Vec<u8>,
pub content_type: Option<String>,
}
impl RequestBody {
#[must_use]
pub const fn as_json(&self) -> Option<&Value> {
match self {
Self::Json(v) => Some(v),
Self::Multipart(_) => None,
}
}
}
#[derive(Clone, Debug)]
pub struct ApiResponse {
pub status: u16,
pub body: String,
}
#[cfg(test)]
#[allow(clippy::expect_used)]
mod tests {
use super::*;
#[test]
fn http_method_from_str_lowercase() {
assert_eq!(HttpMethod::from_str("get"), Ok(HttpMethod::Get));
assert_eq!(HttpMethod::from_str("post"), Ok(HttpMethod::Post));
assert_eq!(HttpMethod::from_str("put"), Ok(HttpMethod::Put));
assert_eq!(HttpMethod::from_str("delete"), Ok(HttpMethod::Delete));
assert_eq!(HttpMethod::from_str("patch"), Ok(HttpMethod::Patch));
}
#[test]
fn http_method_from_str_uppercase() {
assert_eq!(HttpMethod::from_str("GET"), Ok(HttpMethod::Get));
assert_eq!(HttpMethod::from_str("POST"), Ok(HttpMethod::Post));
}
#[test]
fn http_method_from_str_mixed_case() {
assert_eq!(HttpMethod::from_str("Get"), Ok(HttpMethod::Get));
assert_eq!(HttpMethod::from_str("PoSt"), Ok(HttpMethod::Post));
}
#[test]
fn http_method_from_str_unknown() {
assert!(HttpMethod::from_str("TRACE").is_err());
assert!(HttpMethod::from_str("").is_err());
}
#[test]
fn http_method_display() {
assert_eq!(HttpMethod::Get.to_string(), "GET");
assert_eq!(HttpMethod::Post.to_string(), "POST");
assert_eq!(HttpMethod::Put.to_string(), "PUT");
assert_eq!(HttpMethod::Delete.to_string(), "DELETE");
assert_eq!(HttpMethod::Patch.to_string(), "PATCH");
}
#[test]
fn http_method_serializes_uppercase() {
let json = serde_json::to_string(&HttpMethod::Get).expect("should serialize");
assert_eq!(json, "\"GET\"");
}
#[test]
fn http_method_deserializes_uppercase() {
let method: HttpMethod = serde_json::from_str("\"POST\"").expect("should deserialize");
assert_eq!(method, HttpMethod::Post);
}
#[test]
fn api_request_serializes() {
let req = ApiRequest {
method: HttpMethod::Get,
path: "/documents/abc123".to_string(),
query_params: vec![("limit".to_string(), "10".to_string())],
body: None,
content_type: None,
};
let json = serde_json::to_value(&req).expect("should serialize");
assert_eq!(json["method"], "GET");
assert_eq!(json["path"], "/documents/abc123");
}
#[test]
fn api_request_with_json_body_serializes() {
let req = ApiRequest {
method: HttpMethod::Post,
path: "/documents".to_string(),
query_params: vec![],
body: Some(RequestBody::Json(serde_json::json!({"name": "test"}))),
content_type: Some("application/json".to_string()),
};
let json = serde_json::to_value(&req).expect("should serialize");
assert_eq!(json["method"], "POST");
assert!(json["body"].is_object());
}
#[test]
fn request_body_as_json() {
let json_body = RequestBody::Json(serde_json::json!({"key": "value"}));
assert!(json_body.as_json().is_some());
let multipart_body = RequestBody::Multipart(MultipartBody {
text_fields: vec![],
binary_fields: vec![],
});
assert!(multipart_body.as_json().is_none());
}
}