1use base64::{engine::general_purpose, Engine as _};
5use http::Method;
6use serde::{Deserialize, Serialize, Serializer};
7use std::error::Error;
8use std::str::FromStr;
9
10#[serde_with::skip_serializing_none]
11#[derive(Serialize, Debug, Default)]
12#[serde(rename_all = "camelCase")]
13pub struct Request {
14 #[serde(with = "http_serde::uri")]
15 url: http::Uri,
16 http_response_body: Option<bool>,
17 #[serde(serialize_with = "method_opt")]
18 http_request_method: Option<http::Method>,
19 #[serde(flatten)]
20 http_request_body_type: Option<HttpRequestBodyType>,
21}
22
23fn method_opt<S: Serializer>(method_opt: &Option<http::Method>, ser: S) -> Result<S::Ok, S::Error> {
25 match method_opt {
26 Some(method) => http_serde::method::serialize(method, ser),
27 None => ser.serialize_none(),
29 }
30}
31
32#[derive(Serialize, Debug)]
33#[serde(rename_all = "camelCase")]
34pub enum HttpRequestBodyType {
35 HttpRequestBody(String), HttpRequestText(String),
37}
38
39#[allow(dead_code)]
40#[derive(Deserialize, Debug)]
41#[serde(rename_all = "camelCase")]
42#[serde(deny_unknown_fields)]
43pub struct Response {
44 #[serde(with = "http_serde::uri")]
45 pub url: http::Uri,
46 pub http_response_body: String,
47 #[serde(with = "http_serde::status_code")]
48 pub status_code: http::StatusCode,
49}
50
51pub struct RequestBuilder {
52 client: ZyteApi,
53 request: Request,
54}
55
56impl RequestBuilder {
57 pub fn new(client: ZyteApi, method: Method, url: http::Uri) -> RequestBuilder {
58 RequestBuilder {
59 client,
60 request: Request {
61 url,
62 http_response_body: Some(true),
63 http_request_method: Some(method),
64 ..Default::default()
65 },
66 }
67 }
68 pub fn body(mut self, body: &str) -> RequestBuilder {
69 self.request.http_request_body_type =
71 Some(HttpRequestBodyType::HttpRequestBody(body.to_owned()));
72 self
73 }
74 pub fn text(mut self, text: &str) -> RequestBuilder {
75 self.request.http_request_body_type =
76 Some(HttpRequestBodyType::HttpRequestText(text.to_owned()));
77 self
78 }
79 pub async fn send(self) -> Result<Response, Box<dyn Error>> {
80 let mut response = self
82 .client
83 .client
84 .post(&self.client.api_url)
85 .basic_auth(&self.client.api_key, Some(""))
86 .json(&self.request)
87 .send()
88 .await?
89 .json::<Response>()
90 .await?;
91
92 let b = &general_purpose::STANDARD.decode(response.http_response_body)?;
94
95 let b = String::from_utf8_lossy(b);
97
98 response.http_response_body = b.to_string(); Ok(response)
102 }
103}
104
105#[derive(Clone)]
106pub struct ZyteApi {
107 client: reqwest::Client,
108 api_key: String,
109 api_url: String,
110}
111impl ZyteApi {
112 pub fn new(api_key: &str) -> ZyteApi {
113 ZyteApi {
114 client: reqwest::Client::new(),
115 api_key: api_key.to_string(),
116 api_url: "https://api.zyte.com/v1/extract".to_string(),
117 }
118 }
119 pub async fn get(&self, url: &str) -> Result<Response, Box<dyn Error>> {
120 let url = http::Uri::from_str(url)?;
121 Ok(RequestBuilder::new(self.clone(), Method::GET, url)
122 .send()
123 .await?)
124 }
125 pub fn post(&self, url: &str) -> Result<RequestBuilder, Box<dyn Error>> {
126 let url = http::Uri::from_str(url)?;
127 Ok(RequestBuilder::new(self.clone(), Method::POST, url))
128 }
129}