1use crate::{
2 error::{CdpError, Result},
3 types::{CdpRequest, CdpResponse, CdpValue},
4};
5use serde::de::DeserializeOwned;
6use serde::Serialize;
7use std::sync::atomic::{AtomicU64, Ordering};
8
9#[allow(async_fn_in_trait)]
13pub trait CdpTransport: Send + Sync {
14 async fn send(&self, request: CdpRequest) -> Result<CdpResponse>;
16}
17
18pub trait Command {
20 type Params: Serialize;
22 type Output;
24 const METHOD: &'static str;
26
27 fn decode(response: CdpResponse) -> Result<Self::Output>;
29}
30
31pub struct CdpClient<T> {
33 transport: T,
34 next_id: AtomicU64,
35}
36
37impl<T> CdpClient<T> {
38 pub const fn new(transport: T) -> Self {
40 Self {
41 transport,
42 next_id: AtomicU64::new(1),
43 }
44 }
45
46 pub const fn transport(&self) -> &T {
48 &self.transport
49 }
50}
51
52impl<T> CdpClient<T>
53where
54 T: CdpTransport,
55{
56 pub async fn execute<C>(&self, params: &C::Params) -> Result<C::Output>
58 where
59 C: Command,
60 {
61 let request = CdpRequest {
62 id: self.next_id.fetch_add(1, Ordering::Relaxed),
63 method: C::METHOD.to_owned(),
64 params: serde_json::to_value(params)?,
65 session_id: None,
66 };
67 let response = self.transport.send(request).await?;
68 C::decode(response)
69 }
70
71 pub async fn execute_in_session<C>(
73 &self,
74 session_id: impl Into<String>,
75 params: &C::Params,
76 ) -> Result<C::Output>
77 where
78 C: Command,
79 {
80 let request = CdpRequest {
81 id: self.next_id.fetch_add(1, Ordering::Relaxed),
82 method: C::METHOD.to_owned(),
83 params: serde_json::to_value(params)?,
84 session_id: Some(session_id.into()),
85 };
86 let response = self.transport.send(request).await?;
87 C::decode(response)
88 }
89
90 pub async fn call_raw(
92 &self,
93 method: impl Into<String>,
94 params: impl Serialize,
95 session_id: Option<String>,
96 ) -> Result<CdpValue> {
97 let method = method.into();
98 let request = CdpRequest {
99 id: self.next_id.fetch_add(1, Ordering::Relaxed),
100 method: method.clone(),
101 params: serde_json::to_value(params)?,
102 session_id,
103 };
104 let response = self.transport.send(request).await?;
105 match (response.error, response.result) {
106 (Some(error), _) => Err(CdpError::Command {
107 method,
108 message: error.message,
109 }),
110 (None, Some(result)) => Ok(result),
111 (None, None) => Err(CdpError::InvalidResponse(
112 "missing result payload".to_owned(),
113 )),
114 }
115 }
116}
117
118fn decode_json<T>(method: &str, response: CdpResponse) -> Result<T>
119where
120 T: DeserializeOwned,
121{
122 if let Some(error) = response.error {
123 return Err(CdpError::Command {
124 method: method.to_owned(),
125 message: error.message,
126 });
127 }
128 let result = response
129 .result
130 .ok_or_else(|| CdpError::InvalidResponse("missing result payload".to_owned()))?;
131 Ok(serde_json::from_value(result)?)
132}
133
134impl Command for crate::BrowserGetVersionParams {
135 type Params = Self;
136 type Output = crate::BrowserVersion;
137 const METHOD: &'static str = "Browser.getVersion";
138
139 fn decode(response: CdpResponse) -> Result<Self::Output> {
140 decode_json(Self::METHOD, response)
141 }
142}
143
144impl Command for crate::TargetCreateTargetParams {
145 type Params = Self;
146 type Output = crate::TargetCreateTargetResult;
147 const METHOD: &'static str = "Target.createTarget";
148
149 fn decode(response: CdpResponse) -> Result<Self::Output> {
150 decode_json(Self::METHOD, response)
151 }
152}
153
154impl Command for crate::TargetAttachToTargetParams {
155 type Params = Self;
156 type Output = crate::TargetAttachToTargetResult;
157 const METHOD: &'static str = "Target.attachToTarget";
158
159 fn decode(response: CdpResponse) -> Result<Self::Output> {
160 decode_json(Self::METHOD, response)
161 }
162}
163
164impl Command for crate::TargetSetAutoAttachParams {
165 type Params = Self;
166 type Output = ();
167 const METHOD: &'static str = "Target.setAutoAttach";
168
169 fn decode(response: CdpResponse) -> Result<Self::Output> {
170 if let Some(error) = response.error {
171 return Err(CdpError::Command {
172 method: Self::METHOD.to_owned(),
173 message: error.message,
174 });
175 }
176 Ok(())
177 }
178}
179
180impl Command for crate::TargetSetDiscoverTargetsParams {
181 type Params = Self;
182 type Output = ();
183 const METHOD: &'static str = "Target.setDiscoverTargets";
184
185 fn decode(response: CdpResponse) -> Result<Self::Output> {
186 if let Some(error) = response.error {
187 return Err(CdpError::Command {
188 method: Self::METHOD.to_owned(),
189 message: error.message,
190 });
191 }
192 Ok(())
193 }
194}
195
196impl Command for crate::PageEnableParams {
197 type Params = Self;
198 type Output = ();
199 const METHOD: &'static str = "Page.enable";
200
201 fn decode(response: CdpResponse) -> Result<Self::Output> {
202 if let Some(error) = response.error {
203 return Err(CdpError::Command {
204 method: Self::METHOD.to_owned(),
205 message: error.message,
206 });
207 }
208 Ok(())
209 }
210}
211
212impl Command for crate::PageSetLifecycleEventsEnabledParams {
213 type Params = Self;
214 type Output = ();
215 const METHOD: &'static str = "Page.setLifecycleEventsEnabled";
216
217 fn decode(response: CdpResponse) -> Result<Self::Output> {
218 if let Some(error) = response.error {
219 return Err(CdpError::Command {
220 method: Self::METHOD.to_owned(),
221 message: error.message,
222 });
223 }
224 Ok(())
225 }
226}
227
228impl Command for crate::RuntimeEnableParams {
229 type Params = Self;
230 type Output = ();
231 const METHOD: &'static str = "Runtime.enable";
232
233 fn decode(response: CdpResponse) -> Result<Self::Output> {
234 if let Some(error) = response.error {
235 return Err(CdpError::Command {
236 method: Self::METHOD.to_owned(),
237 message: error.message,
238 });
239 }
240 Ok(())
241 }
242}
243
244impl Command for crate::NetworkEnableParams {
245 type Params = Self;
246 type Output = ();
247 const METHOD: &'static str = "Network.enable";
248
249 fn decode(response: CdpResponse) -> Result<Self::Output> {
250 if let Some(error) = response.error {
251 return Err(CdpError::Command {
252 method: Self::METHOD.to_owned(),
253 message: error.message,
254 });
255 }
256 Ok(())
257 }
258}
259
260impl Command for crate::PageNavigateParams {
261 type Params = Self;
262 type Output = crate::PageNavigateResult;
263 const METHOD: &'static str = "Page.navigate";
264
265 fn decode(response: CdpResponse) -> Result<Self::Output> {
266 decode_json(Self::METHOD, response)
267 }
268}
269
270impl Command for crate::PageGetFrameTreeParams {
271 type Params = Self;
272 type Output = crate::PageGetFrameTreeResult;
273 const METHOD: &'static str = "Page.getFrameTree";
274
275 fn decode(response: CdpResponse) -> Result<Self::Output> {
276 decode_json(Self::METHOD, response)
277 }
278}
279
280impl Command for crate::PageCreateIsolatedWorldParams {
281 type Params = Self;
282 type Output = crate::PageCreateIsolatedWorldResult;
283 const METHOD: &'static str = "Page.createIsolatedWorld";
284
285 fn decode(response: CdpResponse) -> Result<Self::Output> {
286 decode_json(Self::METHOD, response)
287 }
288}
289
290impl Command for crate::RuntimeEvaluateParams {
291 type Params = Self;
292 type Output = crate::RuntimeEvaluateResult;
293 const METHOD: &'static str = "Runtime.evaluate";
294
295 fn decode(response: CdpResponse) -> Result<Self::Output> {
296 decode_json(Self::METHOD, response)
297 }
298}
299
300impl Command for crate::RuntimeCallFunctionOnParams {
301 type Params = Self;
302 type Output = crate::RuntimeCallFunctionOnResult;
303 const METHOD: &'static str = "Runtime.callFunctionOn";
304
305 fn decode(response: CdpResponse) -> Result<Self::Output> {
306 decode_json(Self::METHOD, response)
307 }
308}
309
310impl Command for crate::RuntimeReleaseObjectParams {
311 type Params = Self;
312 type Output = ();
313 const METHOD: &'static str = "Runtime.releaseObject";
314
315 fn decode(response: CdpResponse) -> Result<Self::Output> {
316 if let Some(error) = response.error {
317 return Err(CdpError::Command {
318 method: Self::METHOD.to_owned(),
319 message: error.message,
320 });
321 }
322 Ok(())
323 }
324}
325
326impl Command for crate::PageCaptureScreenshotParams {
327 type Params = Self;
328 type Output = crate::PageCaptureScreenshotResult;
329 const METHOD: &'static str = "Page.captureScreenshot";
330
331 fn decode(response: CdpResponse) -> Result<Self::Output> {
332 decode_json(Self::METHOD, response)
333 }
334}
335
336impl Command for crate::InputDispatchKeyEventParams {
337 type Params = Self;
338 type Output = ();
339 const METHOD: &'static str = "Input.dispatchKeyEvent";
340
341 fn decode(response: CdpResponse) -> Result<Self::Output> {
342 if let Some(error) = response.error {
343 return Err(CdpError::Command {
344 method: Self::METHOD.to_owned(),
345 message: error.message,
346 });
347 }
348 Ok(())
349 }
350}
351
352impl Command for crate::InputInsertTextParams {
353 type Params = Self;
354 type Output = ();
355 const METHOD: &'static str = "Input.insertText";
356
357 fn decode(response: CdpResponse) -> Result<Self::Output> {
358 if let Some(error) = response.error {
359 return Err(CdpError::Command {
360 method: Self::METHOD.to_owned(),
361 message: error.message,
362 });
363 }
364 Ok(())
365 }
366}
367
368impl Command for crate::FetchEnableParams {
369 type Params = Self;
370 type Output = ();
371 const METHOD: &'static str = "Fetch.enable";
372
373 fn decode(response: CdpResponse) -> Result<Self::Output> {
374 if let Some(error) = response.error {
375 return Err(CdpError::Command {
376 method: Self::METHOD.to_owned(),
377 message: error.message,
378 });
379 }
380 Ok(())
381 }
382}
383
384impl Command for crate::FetchContinueRequestParams {
385 type Params = Self;
386 type Output = ();
387 const METHOD: &'static str = "Fetch.continueRequest";
388
389 fn decode(response: CdpResponse) -> Result<Self::Output> {
390 if let Some(error) = response.error {
391 return Err(CdpError::Command {
392 method: Self::METHOD.to_owned(),
393 message: error.message,
394 });
395 }
396 Ok(())
397 }
398}
399
400impl Command for crate::FetchFulfillRequestParams {
401 type Params = Self;
402 type Output = ();
403 const METHOD: &'static str = "Fetch.fulfillRequest";
404
405 fn decode(response: CdpResponse) -> Result<Self::Output> {
406 if let Some(error) = response.error {
407 return Err(CdpError::Command {
408 method: Self::METHOD.to_owned(),
409 message: error.message,
410 });
411 }
412 Ok(())
413 }
414}
415
416impl Command for crate::FetchGetResponseBodyParams {
417 type Params = Self;
418 type Output = crate::FetchGetResponseBodyResult;
419 const METHOD: &'static str = "Fetch.getResponseBody";
420
421 fn decode(response: CdpResponse) -> Result<Self::Output> {
422 decode_json(Self::METHOD, response)
423 }
424}
425
426impl Command for crate::FetchFailRequestParams {
427 type Params = Self;
428 type Output = ();
429 const METHOD: &'static str = "Fetch.failRequest";
430
431 fn decode(response: CdpResponse) -> Result<Self::Output> {
432 if let Some(error) = response.error {
433 return Err(CdpError::Command {
434 method: Self::METHOD.to_owned(),
435 message: error.message,
436 });
437 }
438 Ok(())
439 }
440}