use std::io::Read;
use std::time::Duration;
use serde::Serialize;
use crate::error::{Error, Result};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Method {
Get,
Post,
Put,
Patch,
Delete,
}
impl Method {
pub fn as_str(self) -> &'static str {
match self {
Method::Get => "GET",
Method::Post => "POST",
Method::Put => "PUT",
Method::Patch => "PATCH",
Method::Delete => "DELETE",
}
}
}
#[derive(Debug, Clone)]
pub struct Request {
pub method: Method,
pub url: String,
pub headers: Vec<(String, String)>,
pub body: Option<Vec<u8>>,
}
impl Request {
pub fn new(method: Method, url: impl Into<String>) -> Self {
Self {
method,
url: url.into(),
headers: Vec::new(),
body: None,
}
}
pub fn header(mut self, name: impl Into<String>, value: impl Into<String>) -> Self {
self.headers.push((name.into(), value.into()));
self
}
pub fn body_json<T: Serialize>(mut self, value: &T) -> Result<Self> {
let bytes = serde_json::to_vec(value)
.map_err(|e| Error::Invalid(format!("serialize request body: {e}")))?;
self.body = Some(bytes);
Ok(self)
}
pub fn header_value(&self, name: &str) -> Option<&str> {
self.headers
.iter()
.find(|(k, _)| k.eq_ignore_ascii_case(name))
.map(|(_, v)| v.as_str())
}
}
#[derive(Debug, Clone)]
pub struct Response {
pub status: u16,
pub body: Vec<u8>,
}
impl Response {
pub fn new(status: u16, body: impl Into<Vec<u8>>) -> Self {
Self {
status,
body: body.into(),
}
}
pub fn text(&self) -> String {
String::from_utf8_lossy(&self.body).into_owned()
}
pub fn json(&self) -> Result<serde_json::Value> {
if self.body.is_empty() {
return Ok(serde_json::Value::Null);
}
serde_json::from_slice(&self.body)
.map_err(|e| Error::Decode(format!("invalid JSON response: {e}")))
}
}
pub trait Transport: Send + Sync {
fn send(&self, request: &Request) -> Result<Response>;
}
pub struct UreqTransport {
agent: ureq::Agent,
}
impl UreqTransport {
pub fn new() -> Self {
Self::with_timeout(Duration::from_secs(30))
}
pub fn with_timeout(timeout: Duration) -> Self {
Self {
agent: ureq::AgentBuilder::new().timeout(timeout).build(),
}
}
}
impl Default for UreqTransport {
fn default() -> Self {
Self::new()
}
}
impl Transport for UreqTransport {
fn send(&self, request: &Request) -> Result<Response> {
let mut req = self.agent.request(request.method.as_str(), &request.url);
for (name, value) in &request.headers {
req = req.set(name, value);
}
let result = match &request.body {
Some(bytes) => req.send_bytes(bytes),
None => req.call(),
};
match result {
Ok(resp) => {
let status = resp.status();
let mut body = Vec::new();
resp.into_reader()
.read_to_end(&mut body)
.map_err(|e| Error::Transport(e.to_string()))?;
Ok(Response { status, body })
}
Err(ureq::Error::Status(code, resp)) => {
let mut body = Vec::new();
let _ = resp.into_reader().read_to_end(&mut body);
Ok(Response { status: code, body })
}
Err(ureq::Error::Transport(t)) => Err(Error::Transport(t.to_string())),
}
}
}