1use backon::ExponentialBuilder;
65use backon::Retryable;
66use reqwest::Client;
67use reqwest::{Error, Response};
68use serde::{Deserialize, Serialize};
69use std::collections::HashMap;
70use tokio_stream::StreamExt;
71
72#[derive(Debug, Deserialize, Serialize, Clone)]
74pub struct ChunkingAlgDict {
75 r#type: ChunkingType,
77 value: i32,
79}
80
81#[derive(Serialize, Deserialize, Debug, Clone)]
83pub struct Timeout {
84 pub secs: u64,
86 pub nanos: u32,
88}
89
90#[derive(Serialize, Deserialize, Debug, Clone)]
91pub struct IdleNetwork {
92 pub timeout: Timeout,
94}
95
96#[derive(Serialize, Deserialize, Debug, Clone)]
97#[serde(tag = "type", rename_all = "PascalCase")]
98pub enum WebAutomation {
99 Evaluate { code: String },
100 Click { selector: String },
101 Wait { duration: u64 },
102 WaitForNavigation,
103 WaitFor { selector: String },
104 WaitForAndClick { selector: String },
105 ScrollX { pixels: i32 },
106 ScrollY { pixels: i32 },
107 Fill { selector: String, value: String },
108 InfiniteScroll { times: u32 },
109}
110
111#[derive(Default, Serialize, Deserialize, Debug, Clone)]
112#[serde(tag = "type", rename_all = "PascalCase")]
113pub enum RedirectPolicy {
114 Loose,
115 #[default]
116 Strict,
117}
118
119pub type WebAutomationMap = std::collections::HashMap<String, Vec<WebAutomation>>;
120pub type ExecutionScriptsMap = std::collections::HashMap<String, String>;
121
122#[derive(Serialize, Deserialize, Debug, Clone)]
123pub struct Selector {
124 pub timeout: Timeout,
126 pub selector: String,
128}
129
130#[derive(Serialize, Deserialize, Debug, Clone)]
131pub struct Delay {
132 pub timeout: Timeout,
134}
135
136#[derive(Serialize, Deserialize, Debug, Clone)]
137pub struct WaitFor {
138 pub idle_network: Option<IdleNetwork>,
140 pub selector: Option<Selector>,
142 pub delay: Option<Delay>,
144 pub page_navigations: Option<bool>,
146}
147
148#[derive(Serialize, Deserialize, Debug, Clone, Default)]
150pub struct QueryRequest {
151 pub url: Option<String>,
153 pub domain: Option<String>,
155 pub pathname: Option<String>,
157}
158
159#[derive(Default, Debug, Deserialize, Serialize, Clone)]
161#[serde(rename_all = "lowercase")]
162pub enum ChunkingType {
163 #[default]
164 ByWords,
166 ByLines,
168 ByCharacterLength,
170 BySentence,
172}
173
174#[derive(Default, Debug, Deserialize, Serialize, Clone)]
175pub struct Viewport {
177 pub width: u32,
179 pub height: u32,
181 pub device_scale_factor: Option<f64>,
183 pub emulating_mobile: bool,
185 pub is_landscape: bool,
187 pub has_touch: bool,
189}
190
191const API_URL: &'static str = "https://api.spider.cloud";
193
194#[derive(Debug, Clone, Default, Deserialize, Serialize)]
196pub struct CSSSelector {
197 pub name: String,
199 pub selectors: Vec<String>,
201}
202
203pub type CSSExtractionMap = HashMap<String, Vec<CSSSelector>>;
205
206#[derive(Debug, Default, Deserialize, Serialize, Clone)]
208pub struct WebhookSettings {
209 destination: String,
211 on_credits_depleted: bool,
213 on_credits_half_depleted: bool,
215 on_website_status: bool,
217 on_find: bool,
219 on_find_metadata: bool,
221}
222
223#[derive(Debug, Deserialize, Serialize, Clone)]
225#[serde(untagged)]
226pub enum ReturnFormatHandling {
227 Single(ReturnFormat),
229 Multi(std::collections::HashSet<ReturnFormat>),
231}
232
233impl Default for ReturnFormatHandling {
234 fn default() -> ReturnFormatHandling {
235 ReturnFormatHandling::Single(ReturnFormat::Raw)
236 }
237}
238
239#[derive(Debug, Default, Deserialize, Serialize, Clone)]
241pub struct RequestParams {
242 #[serde(default)]
243 pub url: Option<String>,
245 #[serde(default)]
246 pub request: Option<RequestType>,
248 #[serde(default)]
249 pub limit: Option<u32>,
251 #[serde(default)]
252 pub return_format: Option<ReturnFormatHandling>,
254 #[serde(default)]
255 pub tld: Option<bool>,
257 #[serde(default)]
258 pub depth: Option<u32>,
260 #[serde(default)]
261 pub cache: Option<bool>,
263 #[serde(default)]
264 pub scroll: Option<u32>,
266 #[serde(default)]
267 pub budget: Option<HashMap<String, u32>>,
269 #[serde(default)]
270 pub blacklist: Option<Vec<String>>,
272 #[serde(default)]
273 pub whitelist: Option<Vec<String>>,
275 #[serde(default)]
276 pub locale: Option<String>,
278 #[serde(default)]
279 pub cookies: Option<String>,
281 #[serde(default)]
282 pub stealth: Option<bool>,
284 #[serde(default)]
285 pub headers: Option<HashMap<String, String>>,
287 #[serde(default)]
288 pub anti_bot: Option<bool>,
290 #[serde(default)]
291 pub webhooks: Option<WebhookSettings>,
293 #[serde(default)]
294 pub metadata: Option<bool>,
296 #[serde(default)]
297 pub viewport: Option<Viewport>,
299 #[serde(default)]
300 pub encoding: Option<String>,
302 #[serde(default)]
303 pub subdomains: Option<bool>,
305 #[serde(default)]
306 pub user_agent: Option<String>,
308 #[serde(default)]
309 pub store_data: Option<bool>,
311 #[serde(default)]
312 pub gpt_config: Option<HashMap<String, String>>,
314 #[serde(default)]
315 pub fingerprint: Option<bool>,
317 #[serde(default)]
318 pub storageless: Option<bool>,
320 #[serde(default)]
321 pub readability: Option<bool>,
323 #[serde(default)]
324 pub proxy_enabled: Option<bool>,
326 #[serde(default)]
327 pub respect_robots: Option<bool>,
329 #[serde(default)]
330 pub root_selector: Option<String>,
332 #[serde(default)]
333 pub full_resources: Option<bool>,
335 #[serde(default)]
336 pub text: Option<String>,
338 #[serde(default)]
339 pub sitemap: Option<bool>,
341 #[serde(default)]
342 pub external_domains: Option<Vec<String>>,
344 #[serde(default)]
345 pub return_embeddings: Option<bool>,
347 #[serde(default)]
348 pub return_headers: Option<bool>,
350 #[serde(default)]
351 pub return_page_links: Option<bool>,
353 #[serde(default)]
354 pub return_cookies: Option<bool>,
356 #[serde(default)]
357 pub request_timeout: Option<u8>,
359 #[serde(default)]
360 pub run_in_background: Option<bool>,
362 #[serde(default)]
363 pub skip_config_checks: Option<bool>,
365 #[serde(default)]
366 pub css_extraction_map: Option<CSSExtractionMap>,
368 #[serde(default)]
369 pub chunking_alg: Option<ChunkingAlgDict>,
371 pub disable_intercept: Option<bool>,
373 pub wait_for: Option<WaitFor>,
375 pub execution_scripts: Option<ExecutionScriptsMap>,
377 pub automation_scripts: Option<WebAutomationMap>,
379 pub redirect_policy: Option<RedirectPolicy>,
381}
382
383#[derive(Debug, Default, Deserialize, Serialize, Clone)]
385pub struct SearchRequestParams {
386 #[serde(default, flatten)]
388 pub base: RequestParams,
389 pub search: String,
391 pub search_limit: Option<u32>,
393 pub fetch_page_content: Option<bool>,
395 pub location: Option<String>,
397 pub country: Option<String>,
399 pub language: Option<String>,
401 pub num: Option<u32>,
403 pub page: Option<u32>,
405 #[serde(default)]
406 pub website_limit: Option<u32>,
408}
409
410#[derive(Debug, Default, Deserialize, Serialize, Clone)]
412pub struct TransformParams {
413 #[serde(default)]
414 pub return_format: Option<ReturnFormat>,
416 #[serde(default)]
417 pub readability: Option<bool>,
419 #[serde(default)]
420 pub clean: Option<bool>,
422 #[serde(default)]
423 pub clean_full: Option<bool>,
425 pub data: Vec<DataParam>,
427}
428
429#[derive(Serialize, Deserialize, Debug, Clone)]
430pub struct DataParam {
431 pub html: String,
433 pub url: Option<String>,
435}
436
437#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq, Eq)]
439#[serde(rename_all = "lowercase")]
440pub enum RequestType {
441 Http,
443 Chrome,
445 #[default]
446 SmartMode,
448}
449
450#[derive(Default, Debug, Deserialize, Serialize, Clone, PartialEq, Eq, Hash)]
452#[serde(rename_all = "lowercase")]
453pub enum ReturnFormat {
454 #[default]
455 Raw,
457 Markdown,
459 Commonmark,
461 Html2text,
463 Text,
465 Xml,
467 Bytes,
469}
470
471#[derive(Debug, Default)]
473pub struct Spider {
474 pub api_key: String,
476 pub client: Client,
478}
479
480impl Spider {
481 pub fn new(api_key: Option<String>) -> Result<Self, &'static str> {
491 let api_key = api_key.or_else(|| std::env::var("SPIDER_API_KEY").ok());
492
493 match api_key {
494 Some(key) => Ok(Self {
495 api_key: key,
496 client: Client::new(),
497 }),
498 None => Err("No API key provided"),
499 }
500 }
501
502 pub fn new_with_client(api_key: Option<String>, client: Client) -> Result<Self, &'static str> {
513 let api_key = api_key.or_else(|| std::env::var("SPIDER_API_KEY").ok());
514
515 match api_key {
516 Some(key) => Ok(Self {
517 api_key: key,
518 client,
519 }),
520 None => Err("No API key provided"),
521 }
522 }
523
524 async fn api_post_base(
537 &self,
538 endpoint: &str,
539 data: impl Serialize + Sized + std::fmt::Debug,
540 content_type: &str,
541 ) -> Result<Response, Error> {
542 let url: String = format!("{API_URL}/{}", endpoint);
543
544 self.client
545 .post(&url)
546 .header(
547 "User-Agent",
548 format!("Spider-Client/{}", env!("CARGO_PKG_VERSION")),
549 )
550 .header("Content-Type", content_type)
551 .header("Authorization", format!("Bearer {}", self.api_key))
552 .json(&data)
553 .send()
554 .await
555 }
556
557 async fn api_post(
570 &self,
571 endpoint: &str,
572 data: impl Serialize + std::fmt::Debug + Clone + Send + Sync,
573 content_type: &str,
574 ) -> Result<Response, Error> {
575 let fetch = || async {
576 self.api_post_base(endpoint, data.to_owned(), content_type)
577 .await
578 };
579
580 fetch
581 .retry(ExponentialBuilder::default().with_max_times(5))
582 .when(|err: &reqwest::Error| {
583 if let Some(status) = err.status() {
584 status.is_server_error()
585 } else {
586 err.is_timeout()
587 }
588 })
589 .await
590 }
591
592 async fn api_get_base<T: Serialize>(
602 &self,
603 endpoint: &str,
604 query_params: Option<&T>,
605 ) -> Result<serde_json::Value, reqwest::Error> {
606 let url = format!("{API_URL}/{}", endpoint);
607 let res = self
608 .client
609 .get(&url)
610 .query(&query_params)
611 .header(
612 "User-Agent",
613 format!("Spider-Client/{}", env!("CARGO_PKG_VERSION")),
614 )
615 .header("Content-Type", "application/json")
616 .header("Authorization", format!("Bearer {}", self.api_key))
617 .send()
618 .await?;
619 res.json().await
620 }
621
622 async fn api_get<T: Serialize>(
632 &self,
633 endpoint: &str,
634 query_params: Option<&T>,
635 ) -> Result<serde_json::Value, reqwest::Error> {
636 let fetch = || async { self.api_get_base(endpoint, query_params.to_owned()).await };
637
638 fetch
639 .retry(ExponentialBuilder::default().with_max_times(5))
640 .when(|err: &reqwest::Error| {
641 if let Some(status) = err.status() {
642 status.is_server_error()
643 } else {
644 err.is_timeout()
645 }
646 })
647 .await
648 }
649
650 async fn api_delete_base(
663 &self,
664 endpoint: &str,
665 params: Option<HashMap<String, serde_json::Value>>,
666 ) -> Result<Response, Error> {
667 let url = format!("{API_URL}/v1/{}", endpoint);
668 let request_builder = self
669 .client
670 .delete(&url)
671 .header(
672 "User-Agent",
673 format!("Spider-Client/{}", env!("CARGO_PKG_VERSION")),
674 )
675 .header("Content-Type", "application/json")
676 .header("Authorization", format!("Bearer {}", self.api_key));
677
678 let request_builder = if let Some(params) = params {
679 request_builder.json(¶ms)
680 } else {
681 request_builder
682 };
683
684 request_builder.send().await
685 }
686
687 async fn api_delete(
700 &self,
701 endpoint: &str,
702 params: Option<HashMap<String, serde_json::Value>>,
703 ) -> Result<Response, Error> {
704 let fetch = || async { self.api_delete_base(endpoint, params.to_owned()).await };
705
706 fetch
707 .retry(ExponentialBuilder::default().with_max_times(5))
708 .when(|err: &reqwest::Error| {
709 if let Some(status) = err.status() {
710 status.is_server_error()
711 } else {
712 err.is_timeout()
713 }
714 })
715 .await
716 }
717
718 pub async fn scrape_url(
731 &self,
732 url: &str,
733 params: Option<RequestParams>,
734 content_type: &str,
735 ) -> Result<serde_json::Value, reqwest::Error> {
736 let mut data = HashMap::new();
737
738 data.insert(
739 "url".to_string(),
740 serde_json::Value::String(url.to_string()),
741 );
742 data.insert("limit".to_string(), serde_json::Value::Number(1.into()));
743
744 if let Ok(params) = serde_json::to_value(params) {
745 if let Some(ref p) = params.as_object() {
746 data.extend(p.iter().map(|(k, v)| (k.to_string(), v.clone())));
747 }
748 }
749
750 let res = self.api_post("crawl", data, content_type).await?;
751 res.json().await
752 }
753
754 pub async fn crawl_url(
768 &self,
769 url: &str,
770 params: Option<RequestParams>,
771 stream: bool,
772 content_type: &str,
773 callback: Option<impl Fn(serde_json::Value) + Send>,
774 ) -> Result<serde_json::Value, reqwest::Error> {
775 let mut data = HashMap::new();
776
777 if let Ok(params) = serde_json::to_value(params) {
778 if let Some(ref p) = params.as_object() {
779 data.extend(p.iter().map(|(k, v)| (k.to_string(), v.clone())));
780 }
781 }
782
783 data.insert("url".into(), serde_json::Value::String(url.to_string()));
784
785 let res = self.api_post("crawl", data, content_type).await?;
786
787 if stream {
788 if let Some(callback) = callback {
789 let stream = res.bytes_stream();
790 tokio::pin!(stream);
791
792 while let Some(item) = stream.next().await {
793 match item {
794 Ok(chunk) => match serde_json::from_slice(&chunk) {
795 Ok(json_obj) => {
796 callback(json_obj);
797 }
798 _ => (),
799 },
800 Err(e) => {
801 eprintln!("Error in streaming response: {}", e);
802 }
803 }
804 }
805 Ok(serde_json::Value::Null)
806 } else {
807 Ok(serde_json::Value::Null)
808 }
809 } else {
810 res.json().await
811 }
812 }
813
814 pub async fn links(
827 &self,
828 url: &str,
829 params: Option<RequestParams>,
830 _stream: bool,
831 content_type: &str,
832 ) -> Result<serde_json::Value, reqwest::Error> {
833 let mut data = HashMap::new();
834
835 if let Ok(params) = serde_json::to_value(params) {
836 if let Some(ref p) = params.as_object() {
837 data.extend(p.iter().map(|(k, v)| (k.to_string(), v.clone())));
838 }
839 }
840
841 data.insert("url".into(), serde_json::Value::String(url.to_string()));
842
843 let res = self.api_post("links", data, content_type).await?;
844 res.json().await
845 }
846
847 pub async fn screenshot(
860 &self,
861 url: &str,
862 params: Option<RequestParams>,
863 _stream: bool,
864 content_type: &str,
865 ) -> Result<serde_json::Value, reqwest::Error> {
866 let mut data = HashMap::new();
867
868 if let Ok(params) = serde_json::to_value(params) {
869 if let Some(ref p) = params.as_object() {
870 data.extend(p.iter().map(|(k, v)| (k.to_string(), v.clone())));
871 }
872 }
873
874 data.insert("url".into(), serde_json::Value::String(url.to_string()));
875
876 let res = self.api_post("screenshot", data, content_type).await?;
877 res.json().await
878 }
879
880 pub async fn search(
893 &self,
894 q: &str,
895 params: Option<SearchRequestParams>,
896 _stream: bool,
897 content_type: &str,
898 ) -> Result<serde_json::Value, reqwest::Error> {
899 let body = match params {
900 Some(mut params) => {
901 params.search = q.to_string();
902 params
903 }
904 _ => {
905 let mut params = SearchRequestParams::default();
906 params.search = q.to_string();
907 params
908 }
909 };
910
911 let res = self.api_post("search", body, content_type).await?;
912
913 res.json().await
914 }
915
916 pub async fn transform(
929 &self,
930 data: Vec<HashMap<&str, &str>>,
931 params: Option<TransformParams>,
932 _stream: bool,
933 content_type: &str,
934 ) -> Result<serde_json::Value, reqwest::Error> {
935 let mut payload = HashMap::new();
936
937 if let Ok(params) = serde_json::to_value(params) {
938 if let Some(ref p) = params.as_object() {
939 payload.extend(p.iter().map(|(k, v)| (k.to_string(), v.clone())));
940 }
941 }
942
943 if let Ok(d) = serde_json::to_value(data) {
944 payload.insert("data".into(), d);
945 }
946
947 let res = self.api_post("transform", payload, content_type).await?;
948
949 res.json().await
950 }
951
952 pub async fn extract_contacts(
965 &self,
966 url: &str,
967 params: Option<RequestParams>,
968 _stream: bool,
969 content_type: &str,
970 ) -> Result<serde_json::Value, reqwest::Error> {
971 let mut data = HashMap::new();
972
973 if let Ok(params) = serde_json::to_value(params) {
974 if let Ok(params) = serde_json::to_value(params) {
975 if let Some(ref p) = params.as_object() {
976 data.extend(p.iter().map(|(k, v)| (k.to_string(), v.clone())));
977 }
978 }
979 }
980
981 match serde_json::to_value(url) {
982 Ok(u) => {
983 data.insert("url".into(), u);
984 }
985 _ => (),
986 }
987
988 let res = self
989 .api_post("pipeline/extract-contacts", data, content_type)
990 .await?;
991 res.json().await
992 }
993
994 pub async fn label(
1007 &self,
1008 url: &str,
1009 params: Option<RequestParams>,
1010 _stream: bool,
1011 content_type: &str,
1012 ) -> Result<serde_json::Value, reqwest::Error> {
1013 let mut data = HashMap::new();
1014
1015 if let Ok(params) = serde_json::to_value(params) {
1016 if let Ok(params) = serde_json::to_value(params) {
1017 if let Some(ref p) = params.as_object() {
1018 data.extend(p.iter().map(|(k, v)| (k.to_string(), v.clone())));
1019 }
1020 }
1021 }
1022
1023 data.insert("url".into(), serde_json::Value::String(url.to_string()));
1024
1025 let res = self.api_post("pipeline/label", data, content_type).await?;
1026 res.json().await
1027 }
1028
1029 pub async fn download(
1041 &self,
1042 url: Option<&str>,
1043 options: Option<HashMap<&str, i32>>,
1044 ) -> Result<reqwest::Response, reqwest::Error> {
1045 let mut params = HashMap::new();
1046
1047 if let Some(url) = url {
1048 params.insert("url".to_string(), url.to_string());
1049 }
1050
1051 if let Some(options) = options {
1052 for (key, value) in options {
1053 params.insert(key.to_string(), value.to_string());
1054 }
1055 }
1056
1057 let url = format!("{API_URL}/v1/data/download");
1058 let request = self
1059 .client
1060 .get(&url)
1061 .header(
1062 "User-Agent",
1063 format!("Spider-Client/{}", env!("CARGO_PKG_VERSION")),
1064 )
1065 .header("Content-Type", "application/octet-stream")
1066 .header("Authorization", format!("Bearer {}", self.api_key))
1067 .query(¶ms);
1068
1069 let res = request.send().await?;
1070
1071 Ok(res)
1072 }
1073
1074 pub async fn create_signed_url(
1086 &self,
1087 url: Option<&str>,
1088 options: Option<HashMap<&str, i32>>,
1089 ) -> Result<serde_json::Value, reqwest::Error> {
1090 let mut params = HashMap::new();
1091
1092 if let Some(options) = options {
1093 for (key, value) in options {
1094 params.insert(key.to_string(), value.to_string());
1095 }
1096 }
1097
1098 if let Some(url) = url {
1099 params.insert("url".to_string(), url.to_string());
1100 }
1101
1102 let url = format!("{API_URL}/v1/data/sign-url");
1103 let request = self
1104 .client
1105 .get(&url)
1106 .header(
1107 "User-Agent",
1108 format!("Spider-Client/{}", env!("CARGO_PKG_VERSION")),
1109 )
1110 .header("Authorization", format!("Bearer {}", self.api_key))
1111 .query(¶ms);
1112
1113 let res = request.send().await?;
1114
1115 res.json().await
1116 }
1117
1118 pub async fn get_crawl_state(
1130 &self,
1131 url: &str,
1132 params: Option<RequestParams>,
1133 content_type: &str,
1134 ) -> Result<serde_json::Value, reqwest::Error> {
1135 let mut payload = HashMap::new();
1136 payload.insert("url".into(), serde_json::Value::String(url.to_string()));
1137 payload.insert(
1138 "contentType".into(),
1139 serde_json::Value::String(content_type.to_string()),
1140 );
1141
1142 if let Ok(params) = serde_json::to_value(params) {
1143 if let Ok(params) = serde_json::to_value(params) {
1144 if let Some(ref p) = params.as_object() {
1145 payload.extend(p.iter().map(|(k, v)| (k.to_string(), v.clone())));
1146 }
1147 }
1148 }
1149
1150 let res = self
1151 .api_post("data/crawl_state", payload, content_type)
1152 .await?;
1153 res.json().await
1154 }
1155
1156 pub async fn get_credits(&self) -> Result<serde_json::Value, reqwest::Error> {
1158 self.api_get::<serde_json::Value>("data/credits", None)
1159 .await
1160 }
1161
1162 pub async fn data_post(
1164 &self,
1165 table: &str,
1166 data: Option<RequestParams>,
1167 ) -> Result<serde_json::Value, reqwest::Error> {
1168 let res = self
1169 .api_post(&format!("data/{}", table), data, "application/json")
1170 .await?;
1171 res.json().await
1172 }
1173
1174 pub async fn query(&self, params: &QueryRequest) -> Result<serde_json::Value, reqwest::Error> {
1176 let res = self
1177 .api_get::<QueryRequest>(&"data/query", Some(params))
1178 .await?;
1179
1180 Ok(res)
1181 }
1182
1183 pub async fn data_get(
1185 &self,
1186 table: &str,
1187 params: Option<RequestParams>,
1188 ) -> Result<serde_json::Value, reqwest::Error> {
1189 let mut payload = HashMap::new();
1190
1191 if let Some(params) = params {
1192 if let Ok(p) = serde_json::to_value(params) {
1193 if let Some(o) = p.as_object() {
1194 payload.extend(o.iter().map(|(k, v)| (k.as_str(), v.clone())));
1195 }
1196 }
1197 }
1198
1199 let res = self
1200 .api_get::<serde_json::Value>(&format!("data/{}", table), None)
1201 .await?;
1202 Ok(res)
1203 }
1204
1205 pub async fn data_delete(
1207 &self,
1208 table: &str,
1209 params: Option<RequestParams>,
1210 ) -> Result<serde_json::Value, reqwest::Error> {
1211 let mut payload = HashMap::new();
1212
1213 if let Ok(params) = serde_json::to_value(params) {
1214 if let Ok(params) = serde_json::to_value(params) {
1215 if let Some(ref p) = params.as_object() {
1216 payload.extend(p.iter().map(|(k, v)| (k.to_string(), v.clone())));
1217 }
1218 }
1219 }
1220
1221 let res = self
1222 .api_delete(&format!("data/{}", table), Some(payload))
1223 .await?;
1224 res.json().await
1225 }
1226}
1227
1228#[cfg(test)]
1229mod tests {
1230 use super::*;
1231 use dotenv::dotenv;
1232 use lazy_static::lazy_static;
1233 use reqwest::ClientBuilder;
1234
1235 lazy_static! {
1236 static ref SPIDER_CLIENT: Spider = {
1237 dotenv().ok();
1238 let client = ClientBuilder::new();
1239 let client = client.user_agent("SpiderBot").build().unwrap();
1240
1241 Spider::new_with_client(None, client).expect("client to build")
1242 };
1243 }
1244
1245 #[tokio::test]
1246 #[ignore]
1247 async fn test_scrape_url() {
1248 let response = SPIDER_CLIENT
1249 .scrape_url("https://example.com", None, "application/json")
1250 .await;
1251 assert!(response.is_ok());
1252 }
1253
1254 #[tokio::test]
1255 async fn test_crawl_url() {
1256 let response = SPIDER_CLIENT
1257 .crawl_url(
1258 "https://example.com",
1259 None,
1260 false,
1261 "application/json",
1262 None::<fn(serde_json::Value)>,
1263 )
1264 .await;
1265 assert!(response.is_ok());
1266 }
1267
1268 #[tokio::test]
1269 #[ignore]
1270 async fn test_links() {
1271 let response: Result<serde_json::Value, Error> = SPIDER_CLIENT
1272 .links("https://example.com", None, false, "application/json")
1273 .await;
1274 assert!(response.is_ok());
1275 }
1276
1277 #[tokio::test]
1278 #[ignore]
1279 async fn test_screenshot() {
1280 let mut params = RequestParams::default();
1281 params.limit = Some(1);
1282
1283 let response = SPIDER_CLIENT
1284 .screenshot(
1285 "https://example.com",
1286 Some(params),
1287 false,
1288 "application/json",
1289 )
1290 .await;
1291 assert!(response.is_ok());
1292 }
1293
1294 #[tokio::test]
1310 #[ignore]
1311 async fn test_transform() {
1312 let data = vec![HashMap::from([(
1313 "<html><body><h1>Transformation</h1></body></html>".into(),
1314 "".into(),
1315 )])];
1316 let response = SPIDER_CLIENT
1317 .transform(data, None, false, "application/json")
1318 .await;
1319 assert!(response.is_ok());
1320 }
1321
1322 #[tokio::test]
1323 #[ignore]
1324 async fn test_extract_contacts() {
1325 let response = SPIDER_CLIENT
1326 .extract_contacts("https://example.com", None, false, "application/json")
1327 .await;
1328 assert!(response.is_ok());
1329 }
1330
1331 #[tokio::test]
1332 #[ignore]
1333 async fn test_label() {
1334 let response = SPIDER_CLIENT
1335 .label("https://example.com", None, false, "application/json")
1336 .await;
1337 assert!(response.is_ok());
1338 }
1339
1340 #[tokio::test]
1341 async fn test_create_signed_url() {
1342 let response = SPIDER_CLIENT
1343 .create_signed_url(Some("example.com"), None)
1344 .await;
1345 assert!(response.is_ok());
1346 }
1347
1348 #[tokio::test]
1349 async fn test_get_crawl_state() {
1350 let response = SPIDER_CLIENT
1351 .get_crawl_state("https://example.com", None, "application/json")
1352 .await;
1353 assert!(response.is_ok());
1354 }
1355
1356 #[tokio::test]
1357 async fn test_query() {
1358 let mut query = QueryRequest::default();
1359
1360 query.domain = Some("spider.cloud".into());
1361
1362 let response = SPIDER_CLIENT.query(&query).await;
1363 assert!(response.is_ok());
1364 }
1365
1366 #[tokio::test]
1367 async fn test_get_credits() {
1368 let response = SPIDER_CLIENT.get_credits().await;
1369 assert!(response.is_ok());
1370 }
1371}