playwright_cdp/
api_request.rs1use std::collections::HashMap;
13use std::sync::Arc;
14use std::time::Duration;
15
16use crate::error::{Error, Result};
17use crate::options::APIRequestOptions;
18use crate::types::Headers;
19
20#[derive(Clone)]
25pub struct APIRequestContext {
26 client: reqwest::Client,
27 default_headers: Headers,
28}
29
30impl APIRequestContext {
31 pub fn new(default_headers: Headers) -> Self {
34 let client = reqwest::Client::builder()
35 .build()
36 .unwrap_or_else(|_| reqwest::Client::new());
37 Self {
38 client,
39 default_headers,
40 }
41 }
42
43 pub fn default_headers(&self) -> &Headers {
45 &self.default_headers
46 }
47
48 pub async fn get(&self, url: &str, options: Option<APIRequestOptions>) -> Result<APIResponse> {
50 self.send(reqwest::Method::GET, url, options).await
51 }
52
53 pub async fn post(&self, url: &str, options: Option<APIRequestOptions>) -> Result<APIResponse> {
55 self.send(reqwest::Method::POST, url, options).await
56 }
57
58 pub async fn put(&self, url: &str, options: Option<APIRequestOptions>) -> Result<APIResponse> {
60 self.send(reqwest::Method::PUT, url, options).await
61 }
62
63 pub async fn patch(&self, url: &str, options: Option<APIRequestOptions>) -> Result<APIResponse> {
65 self.send(reqwest::Method::PATCH, url, options).await
66 }
67
68 pub async fn delete(
70 &self,
71 url: &str,
72 options: Option<APIRequestOptions>,
73 ) -> Result<APIResponse> {
74 self.send(reqwest::Method::DELETE, url, options).await
75 }
76
77 pub async fn head(&self, url: &str, options: Option<APIRequestOptions>) -> Result<APIResponse> {
79 self.send(reqwest::Method::HEAD, url, options).await
80 }
81
82 async fn send(
85 &self,
86 method: reqwest::Method,
87 url: &str,
88 options: Option<APIRequestOptions>,
89 ) -> Result<APIResponse> {
90 let options = options.unwrap_or_default();
91 let mut builder = self.client.request(method, url);
92
93 for (k, v) in &self.default_headers {
95 builder = builder.header(k.as_str(), v.as_str());
96 }
97 if let Some(headers) = options.headers.as_ref() {
98 for (k, v) in headers {
99 builder = builder.header(k.as_str(), v.as_str());
100 }
101 }
102
103 if let Some(params) = options.params.as_ref() {
105 builder = builder.query(¶ms);
106 }
107
108 if let Some(data) = options.data.as_ref() {
110 builder = builder.json(data);
111 } else if let Some(form) = options.form.as_ref() {
112 builder = builder.form(form);
113 }
114
115 if let Some(timeout_ms) = options.timeout {
117 builder = builder.timeout(Duration::from_millis(timeout_ms.max(0.0) as u64));
118 }
119
120 let resp = builder
121 .send()
122 .await
123 .map_err(|e| Error::Http(format!("request failed: {e}")))?;
124
125 let url = resp.url().to_string();
126 let status = resp.status().as_u16();
127 let mut headers: Headers = HashMap::with_capacity(resp.headers().len());
129 for (name, value) in resp.headers().iter() {
130 let key = name.as_str().to_ascii_lowercase();
131 let val = match value.to_str() {
132 Ok(s) => s.to_string(),
133 Err(_) => {
134 String::from_utf8_lossy(value.as_bytes()).into_owned()
136 }
137 };
138 headers.insert(key, val);
139 }
140
141 let body = resp
142 .bytes()
143 .await
144 .map_err(|e| Error::Http(format!("failed to read body: {e}")))?;
145
146 Ok(APIResponse {
147 url,
148 status,
149 headers,
150 body: Arc::from(body.as_ref()),
151 })
152 }
153}
154
155#[derive(Debug, Clone)]
161pub struct APIResponse {
162 url: String,
163 status: u16,
164 headers: Headers,
165 body: Arc<[u8]>,
166}
167
168impl APIResponse {
169 pub fn url(&self) -> &str {
171 &self.url
172 }
173
174 pub fn status(&self) -> u16 {
176 self.status
177 }
178
179 pub fn ok(&self) -> bool {
181 (200..300).contains(&self.status)
182 }
183
184 pub fn headers(&self) -> &Headers {
186 &self.headers
187 }
188
189 pub async fn body(&self) -> Result<Vec<u8>> {
191 Ok(self.body.to_vec())
192 }
193
194 pub async fn text(&self) -> Result<String> {
196 Ok(String::from_utf8_lossy(&self.body).into_owned())
197 }
198
199 pub async fn json(&self) -> Result<serde_json::Value> {
201 serde_json::from_slice(&self.body).map_err(Into::into)
202 }
203}