hackerone_api/
transport.rs1use std::io::Read;
10use std::time::Duration;
11
12use serde::Serialize;
13
14use crate::error::{Error, Result};
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18pub enum Method {
19 Get,
21 Post,
23 Put,
25 Patch,
27 Delete,
29}
30
31impl Method {
32 pub fn as_str(self) -> &'static str {
34 match self {
35 Method::Get => "GET",
36 Method::Post => "POST",
37 Method::Put => "PUT",
38 Method::Patch => "PATCH",
39 Method::Delete => "DELETE",
40 }
41 }
42}
43
44#[derive(Debug, Clone)]
46pub struct Request {
47 pub method: Method,
49 pub url: String,
51 pub headers: Vec<(String, String)>,
53 pub body: Option<Vec<u8>>,
55}
56
57impl Request {
58 pub fn new(method: Method, url: impl Into<String>) -> Self {
60 Self {
61 method,
62 url: url.into(),
63 headers: Vec::new(),
64 body: None,
65 }
66 }
67
68 pub fn header(mut self, name: impl Into<String>, value: impl Into<String>) -> Self {
70 self.headers.push((name.into(), value.into()));
71 self
72 }
73
74 pub fn body_json<T: Serialize>(mut self, value: &T) -> Result<Self> {
76 let bytes = serde_json::to_vec(value)
77 .map_err(|e| Error::Invalid(format!("serialize request body: {e}")))?;
78 self.body = Some(bytes);
79 Ok(self)
80 }
81
82 pub fn header_value(&self, name: &str) -> Option<&str> {
84 self.headers
85 .iter()
86 .find(|(k, _)| k.eq_ignore_ascii_case(name))
87 .map(|(_, v)| v.as_str())
88 }
89}
90
91#[derive(Debug, Clone)]
93pub struct Response {
94 pub status: u16,
96 pub body: Vec<u8>,
98}
99
100impl Response {
101 pub fn new(status: u16, body: impl Into<Vec<u8>>) -> Self {
103 Self {
104 status,
105 body: body.into(),
106 }
107 }
108
109 pub fn text(&self) -> String {
111 String::from_utf8_lossy(&self.body).into_owned()
112 }
113
114 pub fn json(&self) -> Result<serde_json::Value> {
116 if self.body.is_empty() {
117 return Ok(serde_json::Value::Null);
118 }
119 serde_json::from_slice(&self.body)
120 .map_err(|e| Error::Decode(format!("invalid JSON response: {e}")))
121 }
122}
123
124pub trait Transport: Send + Sync {
126 fn send(&self, request: &Request) -> Result<Response>;
128}
129
130pub struct UreqTransport {
132 agent: ureq::Agent,
133}
134
135impl UreqTransport {
136 pub fn new() -> Self {
138 Self::with_timeout(Duration::from_secs(30))
139 }
140
141 pub fn with_timeout(timeout: Duration) -> Self {
143 Self {
144 agent: ureq::AgentBuilder::new().timeout(timeout).build(),
145 }
146 }
147}
148
149impl Default for UreqTransport {
150 fn default() -> Self {
151 Self::new()
152 }
153}
154
155impl Transport for UreqTransport {
156 fn send(&self, request: &Request) -> Result<Response> {
157 let mut req = self.agent.request(request.method.as_str(), &request.url);
158 for (name, value) in &request.headers {
159 req = req.set(name, value);
160 }
161
162 let result = match &request.body {
163 Some(bytes) => req.send_bytes(bytes),
164 None => req.call(),
165 };
166
167 match result {
168 Ok(resp) => {
169 let status = resp.status();
170 let mut body = Vec::new();
171 resp.into_reader()
172 .read_to_end(&mut body)
173 .map_err(|e| Error::Transport(e.to_string()))?;
174 Ok(Response { status, body })
175 }
176 Err(ureq::Error::Status(code, resp)) => {
178 let mut body = Vec::new();
179 let _ = resp.into_reader().read_to_end(&mut body);
180 Ok(Response { status: code, body })
181 }
182 Err(ureq::Error::Transport(t)) => Err(Error::Transport(t.to_string())),
183 }
184 }
185}