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(
235 Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize,
236)]
237pub enum ProxyType {
238 #[serde(rename = "residential")]
240 Residential,
241 #[serde(rename = "residential_fast")]
243 ResidentialFast,
244 #[serde(rename = "residential_static")]
246 ResidentialStatic,
247 #[serde(rename = "mobile")]
249 Mobile,
250 #[serde(rename = "isp", alias = "datacenter")]
252 #[default]
253 Isp,
254 #[serde(rename = "residential_premium")]
256 ResidentialPremium,
257 #[serde(rename = "residential_core")]
259 ResidentialCore,
260 #[serde(rename = "residential_plus")]
262 ResidentialPlus,
263}
264
265#[derive(Debug, Deserialize, Serialize, Clone)]
267#[serde(untagged)]
268pub enum ReturnFormatHandling {
269 Single(ReturnFormat),
271 Multi(std::collections::HashSet<ReturnFormat>),
273}
274
275impl Default for ReturnFormatHandling {
276 fn default() -> ReturnFormatHandling {
277 ReturnFormatHandling::Single(ReturnFormat::Raw)
278 }
279}
280
281#[derive(Debug, Default, Deserialize, Serialize, Clone)]
282pub struct EventTracker {
283 responses: Option<bool>,
285 requests: Option<bool>
287}
288
289#[derive(Debug, Default, Deserialize, Serialize, Clone)]
291pub struct RequestParams {
292 #[serde(default)]
293 pub url: Option<String>,
295 #[serde(default)]
296 pub request: Option<RequestType>,
298 #[serde(default)]
299 pub limit: Option<u32>,
301 #[serde(default)]
302 pub return_format: Option<ReturnFormatHandling>,
304 #[serde(default)]
305 pub tld: Option<bool>,
307 #[serde(default)]
308 pub depth: Option<u32>,
310 #[serde(default)]
311 pub cache: Option<bool>,
313 #[serde(default)]
314 pub scroll: Option<u32>,
316 #[serde(default)]
317 pub budget: Option<HashMap<String, u32>>,
319 #[serde(default)]
320 pub blacklist: Option<Vec<String>>,
322 #[serde(default)]
323 pub whitelist: Option<Vec<String>>,
325 #[serde(default)]
326 pub locale: Option<String>,
328 #[serde(default)]
329 pub cookies: Option<String>,
331 #[serde(default)]
332 pub stealth: Option<bool>,
334 #[serde(default)]
335 pub headers: Option<HashMap<String, String>>,
337 #[serde(default)]
338 pub anti_bot: Option<bool>,
340 #[serde(default)]
341 pub webhooks: Option<WebhookSettings>,
343 #[serde(default)]
344 pub metadata: Option<bool>,
346 #[serde(default)]
347 pub viewport: Option<Viewport>,
349 #[serde(default)]
350 pub encoding: Option<String>,
352 #[serde(default)]
353 pub subdomains: Option<bool>,
355 #[serde(default)]
356 pub user_agent: Option<String>,
358 #[serde(default)]
359 pub store_data: Option<bool>,
361 #[serde(default)]
362 pub gpt_config: Option<HashMap<String, String>>,
364 #[serde(default)]
365 pub fingerprint: Option<bool>,
367 #[serde(default)]
368 pub storageless: Option<bool>,
370 #[serde(default)]
371 pub readability: Option<bool>,
373 #[serde(default)]
374 pub proxy_enabled: Option<bool>,
376 #[serde(default)]
377 pub respect_robots: Option<bool>,
379 #[serde(default)]
380 pub root_selector: Option<String>,
382 #[serde(default)]
383 pub full_resources: Option<bool>,
385 #[serde(default)]
386 pub text: Option<String>,
388 #[serde(default)]
389 pub sitemap: Option<bool>,
391 #[serde(default)]
392 pub external_domains: Option<Vec<String>>,
394 #[serde(default)]
395 pub return_embeddings: Option<bool>,
397 #[serde(default)]
398 pub return_headers: Option<bool>,
400 #[serde(default)]
401 pub return_page_links: Option<bool>,
403 #[serde(default)]
404 pub return_cookies: Option<bool>,
406 #[serde(default)]
407 pub request_timeout: Option<u8>,
409 #[serde(default)]
410 pub run_in_background: Option<bool>,
412 #[serde(default)]
413 pub skip_config_checks: Option<bool>,
415 #[serde(default)]
416 pub css_extraction_map: Option<CSSExtractionMap>,
418 #[serde(default)]
419 pub chunking_alg: Option<ChunkingAlgDict>,
421 #[serde(default)]
422 pub disable_intercept: Option<bool>,
424 #[serde(default)]
425 pub wait_for: Option<WaitFor>,
427 #[serde(default)]
428 pub execution_scripts: Option<ExecutionScriptsMap>,
430 #[serde(default)]
431 pub automation_scripts: Option<WebAutomationMap>,
433 #[serde(default)]
434 pub redirect_policy: Option<RedirectPolicy>,
436 #[serde(default)]
437 pub event_tracker: Option<EventTracker>,
439 #[serde(default)]
440 pub crawl_timeout: Option<Timeout>,
442 #[serde(default)]
443 pub evaluate_on_new_document: Option<Box<String>>,
445 #[serde(default)]
446 pub lite_mode: Option<bool>,
450 pub proxy: Option<ProxyType>,
452 pub remote_proxy: Option<String>,
455}
456
457#[derive(Debug, Default, Deserialize, Serialize, Clone)]
459pub struct SearchRequestParams {
460 #[serde(default, flatten)]
462 pub base: RequestParams,
463 pub search: String,
465 pub search_limit: Option<u32>,
467 pub fetch_page_content: Option<bool>,
469 pub location: Option<String>,
471 pub country: Option<String>,
473 pub language: Option<String>,
475 pub num: Option<u32>,
477 pub page: Option<u32>,
479 #[serde(default)]
480 pub website_limit: Option<u32>,
482}
483
484#[derive(Debug, Default, Deserialize, Serialize, Clone)]
486pub struct TransformParams {
487 #[serde(default)]
488 pub return_format: Option<ReturnFormat>,
490 #[serde(default)]
491 pub readability: Option<bool>,
493 #[serde(default)]
494 pub clean: Option<bool>,
496 #[serde(default)]
497 pub clean_full: Option<bool>,
499 pub data: Vec<DataParam>,
501}
502
503#[derive(Serialize, Deserialize, Debug, Clone)]
504pub struct DataParam {
505 pub html: String,
507 pub url: Option<String>,
509}
510
511#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq, Eq)]
513#[serde(rename_all = "lowercase")]
514pub enum RequestType {
515 Http,
517 Chrome,
519 #[default]
520 SmartMode,
522}
523
524#[derive(Default, Debug, Deserialize, Serialize, Clone, PartialEq, Eq, Hash)]
526#[serde(rename_all = "lowercase")]
527pub enum ReturnFormat {
528 #[default]
529 Raw,
531 Markdown,
533 Commonmark,
535 Html2text,
537 Text,
539 Xml,
541 Bytes,
543}
544
545#[derive(Debug, Default)]
547pub struct Spider {
548 pub api_key: String,
550 pub client: Client,
552}
553
554impl Spider {
555 pub fn new(api_key: Option<String>) -> Result<Self, &'static str> {
565 let api_key = api_key.or_else(|| std::env::var("SPIDER_API_KEY").ok());
566
567 match api_key {
568 Some(key) => Ok(Self {
569 api_key: key,
570 client: Client::new(),
571 }),
572 None => Err("No API key provided"),
573 }
574 }
575
576 pub fn new_with_client(api_key: Option<String>, client: Client) -> Result<Self, &'static str> {
587 let api_key = api_key.or_else(|| std::env::var("SPIDER_API_KEY").ok());
588
589 match api_key {
590 Some(key) => Ok(Self {
591 api_key: key,
592 client,
593 }),
594 None => Err("No API key provided"),
595 }
596 }
597
598 async fn api_post_base(
611 &self,
612 endpoint: &str,
613 data: impl Serialize + Sized + std::fmt::Debug,
614 content_type: &str,
615 ) -> Result<Response, Error> {
616 let url: String = format!("{API_URL}/{}", endpoint);
617
618 self.client
619 .post(&url)
620 .header(
621 "User-Agent",
622 format!("Spider-Client/{}", env!("CARGO_PKG_VERSION")),
623 )
624 .header("Content-Type", content_type)
625 .header("Authorization", format!("Bearer {}", self.api_key))
626 .json(&data)
627 .send()
628 .await
629 }
630
631 async fn api_post(
644 &self,
645 endpoint: &str,
646 data: impl Serialize + std::fmt::Debug + Clone + Send + Sync,
647 content_type: &str,
648 ) -> Result<Response, Error> {
649 let fetch = || async {
650 self.api_post_base(endpoint, data.to_owned(), content_type)
651 .await
652 };
653
654 fetch
655 .retry(ExponentialBuilder::default().with_max_times(5))
656 .when(|err: &reqwest::Error| {
657 if let Some(status) = err.status() {
658 status.is_server_error()
659 } else {
660 err.is_timeout()
661 }
662 })
663 .await
664 }
665
666 async fn api_get_base<T: Serialize>(
676 &self,
677 endpoint: &str,
678 query_params: Option<&T>,
679 ) -> Result<serde_json::Value, reqwest::Error> {
680 let url = format!("{API_URL}/{}", endpoint);
681 let res = self
682 .client
683 .get(&url)
684 .query(&query_params)
685 .header(
686 "User-Agent",
687 format!("Spider-Client/{}", env!("CARGO_PKG_VERSION")),
688 )
689 .header("Content-Type", "application/json")
690 .header("Authorization", format!("Bearer {}", self.api_key))
691 .send()
692 .await?;
693 res.json().await
694 }
695
696 async fn api_get<T: Serialize>(
706 &self,
707 endpoint: &str,
708 query_params: Option<&T>,
709 ) -> Result<serde_json::Value, reqwest::Error> {
710 let fetch = || async { self.api_get_base(endpoint, query_params.to_owned()).await };
711
712 fetch
713 .retry(ExponentialBuilder::default().with_max_times(5))
714 .when(|err: &reqwest::Error| {
715 if let Some(status) = err.status() {
716 status.is_server_error()
717 } else {
718 err.is_timeout()
719 }
720 })
721 .await
722 }
723
724 async fn api_delete_base(
737 &self,
738 endpoint: &str,
739 params: Option<HashMap<String, serde_json::Value>>,
740 ) -> Result<Response, Error> {
741 let url = format!("{API_URL}/v1/{}", endpoint);
742 let request_builder = self
743 .client
744 .delete(&url)
745 .header(
746 "User-Agent",
747 format!("Spider-Client/{}", env!("CARGO_PKG_VERSION")),
748 )
749 .header("Content-Type", "application/json")
750 .header("Authorization", format!("Bearer {}", self.api_key));
751
752 let request_builder = if let Some(params) = params {
753 request_builder.json(¶ms)
754 } else {
755 request_builder
756 };
757
758 request_builder.send().await
759 }
760
761 async fn api_delete(
774 &self,
775 endpoint: &str,
776 params: Option<HashMap<String, serde_json::Value>>,
777 ) -> Result<Response, Error> {
778 let fetch = || async { self.api_delete_base(endpoint, params.to_owned()).await };
779
780 fetch
781 .retry(ExponentialBuilder::default().with_max_times(5))
782 .when(|err: &reqwest::Error| {
783 if let Some(status) = err.status() {
784 status.is_server_error()
785 } else {
786 err.is_timeout()
787 }
788 })
789 .await
790 }
791
792 pub async fn scrape_url(
805 &self,
806 url: &str,
807 params: Option<RequestParams>,
808 content_type: &str,
809 ) -> Result<serde_json::Value, reqwest::Error> {
810 let mut data = HashMap::new();
811
812 data.insert(
813 "url".to_string(),
814 serde_json::Value::String(url.to_string()),
815 );
816 data.insert("limit".to_string(), serde_json::Value::Number(1.into()));
817
818 if let Ok(params) = serde_json::to_value(params) {
819 if let Some(ref p) = params.as_object() {
820 data.extend(p.iter().map(|(k, v)| (k.to_string(), v.clone())));
821 }
822 }
823
824 let res = self.api_post("crawl", data, content_type).await?;
825 res.json().await
826 }
827
828 pub async fn crawl_url(
842 &self,
843 url: &str,
844 params: Option<RequestParams>,
845 stream: bool,
846 content_type: &str,
847 callback: Option<impl Fn(serde_json::Value) + Send>,
848 ) -> Result<serde_json::Value, reqwest::Error> {
849 use tokio_util::codec::{FramedRead, LinesCodec};
850
851 let mut data = HashMap::new();
852
853 if let Ok(params) = serde_json::to_value(params) {
854 if let Some(ref p) = params.as_object() {
855 data.extend(p.iter().map(|(k, v)| (k.to_string(), v.clone())));
856 }
857 }
858
859 data.insert("url".into(), serde_json::Value::String(url.to_string()));
860
861 let res = self.api_post("crawl", data, content_type).await?;
862
863 if stream {
864 if let Some(callback) = callback {
865 let stream = res.bytes_stream();
866
867 let stream_reader = tokio_util::io::StreamReader::new(
868 stream.map(|r| r.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))),
869 );
870
871 let mut lines = FramedRead::new(stream_reader, LinesCodec::new());
872
873 while let Some(line_result) = lines.next().await {
874 match line_result {
875 Ok(line) => {
876 match serde_json::from_str::<serde_json::Value>(&line) {
877 Ok(value) => {
878 callback(value);
879 }
880 Err(_e) => {
881 continue;
882 }
883 }
884 }
885 Err(_e) => {
886 return Ok(serde_json::Value::Null)
887 }
888 }
889 }
890
891 Ok(serde_json::Value::Null)
892 } else {
893 Ok(serde_json::Value::Null)
894 }
895 } else {
896 res.json().await
897 }
898 }
899
900 pub async fn links(
913 &self,
914 url: &str,
915 params: Option<RequestParams>,
916 _stream: bool,
917 content_type: &str,
918 ) -> Result<serde_json::Value, reqwest::Error> {
919 let mut data = HashMap::new();
920
921 if let Ok(params) = serde_json::to_value(params) {
922 if let Some(ref p) = params.as_object() {
923 data.extend(p.iter().map(|(k, v)| (k.to_string(), v.clone())));
924 }
925 }
926
927 data.insert("url".into(), serde_json::Value::String(url.to_string()));
928
929 let res = self.api_post("links", data, content_type).await?;
930 res.json().await
931 }
932
933 pub async fn screenshot(
946 &self,
947 url: &str,
948 params: Option<RequestParams>,
949 _stream: bool,
950 content_type: &str,
951 ) -> Result<serde_json::Value, reqwest::Error> {
952 let mut data = HashMap::new();
953
954 if let Ok(params) = serde_json::to_value(params) {
955 if let Some(ref p) = params.as_object() {
956 data.extend(p.iter().map(|(k, v)| (k.to_string(), v.clone())));
957 }
958 }
959
960 data.insert("url".into(), serde_json::Value::String(url.to_string()));
961
962 let res = self.api_post("screenshot", data, content_type).await?;
963 res.json().await
964 }
965
966 pub async fn search(
979 &self,
980 q: &str,
981 params: Option<SearchRequestParams>,
982 _stream: bool,
983 content_type: &str,
984 ) -> Result<serde_json::Value, reqwest::Error> {
985 let body = match params {
986 Some(mut params) => {
987 params.search = q.to_string();
988 params
989 }
990 _ => {
991 let mut params = SearchRequestParams::default();
992 params.search = q.to_string();
993 params
994 }
995 };
996
997 let res = self.api_post("search", body, content_type).await?;
998
999 res.json().await
1000 }
1001
1002 pub async fn transform(
1015 &self,
1016 data: Vec<HashMap<&str, &str>>,
1017 params: Option<TransformParams>,
1018 _stream: bool,
1019 content_type: &str,
1020 ) -> Result<serde_json::Value, reqwest::Error> {
1021 let mut payload = HashMap::new();
1022
1023 if let Ok(params) = serde_json::to_value(params) {
1024 if let Some(ref p) = params.as_object() {
1025 payload.extend(p.iter().map(|(k, v)| (k.to_string(), v.clone())));
1026 }
1027 }
1028
1029 if let Ok(d) = serde_json::to_value(data) {
1030 payload.insert("data".into(), d);
1031 }
1032
1033 let res = self.api_post("transform", payload, content_type).await?;
1034
1035 res.json().await
1036 }
1037
1038 pub async fn extract_contacts(
1051 &self,
1052 url: &str,
1053 params: Option<RequestParams>,
1054 _stream: bool,
1055 content_type: &str,
1056 ) -> Result<serde_json::Value, reqwest::Error> {
1057 let mut data = HashMap::new();
1058
1059 if let Ok(params) = serde_json::to_value(params) {
1060 if let Ok(params) = serde_json::to_value(params) {
1061 if let Some(ref p) = params.as_object() {
1062 data.extend(p.iter().map(|(k, v)| (k.to_string(), v.clone())));
1063 }
1064 }
1065 }
1066
1067 match serde_json::to_value(url) {
1068 Ok(u) => {
1069 data.insert("url".into(), u);
1070 }
1071 _ => (),
1072 }
1073
1074 let res = self
1075 .api_post("pipeline/extract-contacts", data, content_type)
1076 .await?;
1077 res.json().await
1078 }
1079
1080 pub async fn label(
1093 &self,
1094 url: &str,
1095 params: Option<RequestParams>,
1096 _stream: bool,
1097 content_type: &str,
1098 ) -> Result<serde_json::Value, reqwest::Error> {
1099 let mut data = HashMap::new();
1100
1101 if let Ok(params) = serde_json::to_value(params) {
1102 if let Ok(params) = serde_json::to_value(params) {
1103 if let Some(ref p) = params.as_object() {
1104 data.extend(p.iter().map(|(k, v)| (k.to_string(), v.clone())));
1105 }
1106 }
1107 }
1108
1109 data.insert("url".into(), serde_json::Value::String(url.to_string()));
1110
1111 let res = self.api_post("pipeline/label", data, content_type).await?;
1112 res.json().await
1113 }
1114
1115 pub async fn download(
1127 &self,
1128 url: Option<&str>,
1129 options: Option<HashMap<&str, i32>>,
1130 ) -> Result<reqwest::Response, reqwest::Error> {
1131 let mut params = HashMap::new();
1132
1133 if let Some(url) = url {
1134 params.insert("url".to_string(), url.to_string());
1135 }
1136
1137 if let Some(options) = options {
1138 for (key, value) in options {
1139 params.insert(key.to_string(), value.to_string());
1140 }
1141 }
1142
1143 let url = format!("{API_URL}/v1/data/download");
1144 let request = self
1145 .client
1146 .get(&url)
1147 .header(
1148 "User-Agent",
1149 format!("Spider-Client/{}", env!("CARGO_PKG_VERSION")),
1150 )
1151 .header("Content-Type", "application/octet-stream")
1152 .header("Authorization", format!("Bearer {}", self.api_key))
1153 .query(¶ms);
1154
1155 let res = request.send().await?;
1156
1157 Ok(res)
1158 }
1159
1160 pub async fn create_signed_url(
1172 &self,
1173 url: Option<&str>,
1174 options: Option<HashMap<&str, i32>>,
1175 ) -> Result<serde_json::Value, reqwest::Error> {
1176 let mut params = HashMap::new();
1177
1178 if let Some(options) = options {
1179 for (key, value) in options {
1180 params.insert(key.to_string(), value.to_string());
1181 }
1182 }
1183
1184 if let Some(url) = url {
1185 params.insert("url".to_string(), url.to_string());
1186 }
1187
1188 let url = format!("{API_URL}/v1/data/sign-url");
1189 let request = self
1190 .client
1191 .get(&url)
1192 .header(
1193 "User-Agent",
1194 format!("Spider-Client/{}", env!("CARGO_PKG_VERSION")),
1195 )
1196 .header("Authorization", format!("Bearer {}", self.api_key))
1197 .query(¶ms);
1198
1199 let res = request.send().await?;
1200
1201 res.json().await
1202 }
1203
1204 pub async fn get_crawl_state(
1216 &self,
1217 url: &str,
1218 params: Option<RequestParams>,
1219 content_type: &str,
1220 ) -> Result<serde_json::Value, reqwest::Error> {
1221 let mut payload = HashMap::new();
1222 payload.insert("url".into(), serde_json::Value::String(url.to_string()));
1223 payload.insert(
1224 "contentType".into(),
1225 serde_json::Value::String(content_type.to_string()),
1226 );
1227
1228 if let Ok(params) = serde_json::to_value(params) {
1229 if let Ok(params) = serde_json::to_value(params) {
1230 if let Some(ref p) = params.as_object() {
1231 payload.extend(p.iter().map(|(k, v)| (k.to_string(), v.clone())));
1232 }
1233 }
1234 }
1235
1236 let res = self
1237 .api_post("data/crawl_state", payload, content_type)
1238 .await?;
1239 res.json().await
1240 }
1241
1242 pub async fn get_credits(&self) -> Result<serde_json::Value, reqwest::Error> {
1244 self.api_get::<serde_json::Value>("data/credits", None)
1245 .await
1246 }
1247
1248 pub async fn data_post(
1250 &self,
1251 table: &str,
1252 data: Option<RequestParams>,
1253 ) -> Result<serde_json::Value, reqwest::Error> {
1254 let res = self
1255 .api_post(&format!("data/{}", table), data, "application/json")
1256 .await?;
1257 res.json().await
1258 }
1259
1260 pub async fn query(&self, params: &QueryRequest) -> Result<serde_json::Value, reqwest::Error> {
1262 let res = self
1263 .api_get::<QueryRequest>(&"data/query", Some(params))
1264 .await?;
1265
1266 Ok(res)
1267 }
1268
1269 pub async fn data_get(
1271 &self,
1272 table: &str,
1273 params: Option<RequestParams>,
1274 ) -> Result<serde_json::Value, reqwest::Error> {
1275 let mut payload = HashMap::new();
1276
1277 if let Some(params) = params {
1278 if let Ok(p) = serde_json::to_value(params) {
1279 if let Some(o) = p.as_object() {
1280 payload.extend(o.iter().map(|(k, v)| (k.as_str(), v.clone())));
1281 }
1282 }
1283 }
1284
1285 let res = self
1286 .api_get::<serde_json::Value>(&format!("data/{}", table), None)
1287 .await?;
1288 Ok(res)
1289 }
1290
1291 pub async fn data_delete(
1293 &self,
1294 table: &str,
1295 params: Option<RequestParams>,
1296 ) -> Result<serde_json::Value, reqwest::Error> {
1297 let mut payload = HashMap::new();
1298
1299 if let Ok(params) = serde_json::to_value(params) {
1300 if let Ok(params) = serde_json::to_value(params) {
1301 if let Some(ref p) = params.as_object() {
1302 payload.extend(p.iter().map(|(k, v)| (k.to_string(), v.clone())));
1303 }
1304 }
1305 }
1306
1307 let res = self
1308 .api_delete(&format!("data/{}", table), Some(payload))
1309 .await?;
1310 res.json().await
1311 }
1312}
1313
1314#[cfg(test)]
1315mod tests {
1316 use super::*;
1317 use dotenv::dotenv;
1318 use lazy_static::lazy_static;
1319 use reqwest::ClientBuilder;
1320
1321 lazy_static! {
1322 static ref SPIDER_CLIENT: Spider = {
1323 dotenv().ok();
1324 let client = ClientBuilder::new();
1325 let client = client.user_agent("SpiderBot").build().unwrap();
1326
1327 Spider::new_with_client(None, client).expect("client to build")
1328 };
1329 }
1330
1331 #[tokio::test]
1332 #[ignore]
1333 async fn test_scrape_url() {
1334 let response = SPIDER_CLIENT
1335 .scrape_url("https://example.com", None, "application/json")
1336 .await;
1337 assert!(response.is_ok());
1338 }
1339
1340 #[tokio::test]
1341 async fn test_crawl_url() {
1342 let response = SPIDER_CLIENT
1343 .crawl_url(
1344 "https://example.com",
1345 None,
1346 false,
1347 "application/json",
1348 None::<fn(serde_json::Value)>,
1349 )
1350 .await;
1351 assert!(response.is_ok());
1352 }
1353
1354 #[tokio::test]
1355 #[ignore]
1356 async fn test_links() {
1357 let response: Result<serde_json::Value, Error> = SPIDER_CLIENT
1358 .links("https://example.com", None, false, "application/json")
1359 .await;
1360 assert!(response.is_ok());
1361 }
1362
1363 #[tokio::test]
1364 #[ignore]
1365 async fn test_screenshot() {
1366 let mut params = RequestParams::default();
1367 params.limit = Some(1);
1368
1369 let response = SPIDER_CLIENT
1370 .screenshot(
1371 "https://example.com",
1372 Some(params),
1373 false,
1374 "application/json",
1375 )
1376 .await;
1377 assert!(response.is_ok());
1378 }
1379
1380 #[tokio::test]
1396 #[ignore]
1397 async fn test_transform() {
1398 let data = vec![HashMap::from([(
1399 "<html><body><h1>Transformation</h1></body></html>".into(),
1400 "".into(),
1401 )])];
1402 let response = SPIDER_CLIENT
1403 .transform(data, None, false, "application/json")
1404 .await;
1405 assert!(response.is_ok());
1406 }
1407
1408 #[tokio::test]
1409 #[ignore]
1410 async fn test_extract_contacts() {
1411 let response = SPIDER_CLIENT
1412 .extract_contacts("https://example.com", None, false, "application/json")
1413 .await;
1414 assert!(response.is_ok());
1415 }
1416
1417 #[tokio::test]
1418 #[ignore]
1419 async fn test_label() {
1420 let response = SPIDER_CLIENT
1421 .label("https://example.com", None, false, "application/json")
1422 .await;
1423 assert!(response.is_ok());
1424 }
1425
1426 #[tokio::test]
1427 async fn test_create_signed_url() {
1428 let response = SPIDER_CLIENT
1429 .create_signed_url(Some("example.com"), None)
1430 .await;
1431 assert!(response.is_ok());
1432 }
1433
1434 #[tokio::test]
1435 async fn test_get_crawl_state() {
1436 let response = SPIDER_CLIENT
1437 .get_crawl_state("https://example.com", None, "application/json")
1438 .await;
1439 assert!(response.is_ok());
1440 }
1441
1442 #[tokio::test]
1443 async fn test_query() {
1444 let mut query = QueryRequest::default();
1445
1446 query.domain = Some("spider.cloud".into());
1447
1448 let response = SPIDER_CLIENT.query(&query).await;
1449 assert!(response.is_ok());
1450 }
1451
1452 #[tokio::test]
1453 async fn test_get_credits() {
1454 let response = SPIDER_CLIENT.get_credits().await;
1455 assert!(response.is_ok());
1456 }
1457}