snm_brightdata_client/
client.rs

1// src/client.rs - Enhanced version with improved config and error handling
2use crate::{config::BrightDataConfig, error::BrightDataError};
3use reqwest::Client;
4use serde_json::Value;
5
6pub struct BrightDataClient {
7    config: BrightDataConfig,
8    client: Client,
9}
10
11impl BrightDataClient {
12    pub fn new(config: BrightDataConfig) -> Result<Self, BrightDataError> {
13        // Validate configuration
14        config.validate().map_err(|e| BrightDataError::ConfigError(e.to_string()))?;
15        
16        let client = Client::builder()
17            .timeout(config.timeout)
18            .build()
19            .map_err(|e| BrightDataError::NetworkError(e.to_string()))?;
20
21        Ok(Self { config, client })
22    }
23
24    /// Direct BrightData API call for basic scraping
25    pub async fn get(&self, target_url: &str) -> Result<Value, BrightDataError> {
26        let payload = serde_json::json!({
27            "url": target_url,
28            "zone": self.config.web_unlocker_zone,
29            "format": "raw",
30        });
31
32        let res = self
33            .client
34            .post(&format!("{}/request", self.config.base_url))
35            .header("Authorization", format!("Bearer {}", self.config.token))
36            .json(&payload)
37            .send()
38            .await
39            .map_err(|e| BrightDataError::Request(e))?;
40
41        let status = res.status();
42        if !status.is_success() {
43            let error_text = res.text().await.unwrap_or_default();
44            return Err(BrightDataError::ApiError(format!(
45                "BrightData API error {}: {}",
46                status.as_u16(),
47                error_text
48            )));
49        }
50
51        let result = res.json::<Value>().await
52            .map_err(|e| BrightDataError::ParseError(e.to_string()))?;
53
54        Ok(result)
55    }
56
57    /// Search using BrightData SERP API
58    pub async fn search(&self, query: &str, engine: &str) -> Result<Value, BrightDataError> {
59        let search_url = self.build_search_url(engine, query);
60        
61        let payload = serde_json::json!({
62            "url": search_url,
63            "zone": self.config.serp_zone,
64            "format": "raw",
65            "data_format": "markdown"
66        });
67
68        let res = self
69            .client
70            .post(&format!("{}/request", self.config.base_url))
71            .header("Authorization", format!("Bearer {}", self.config.token))
72            .json(&payload)
73            .send()
74            .await
75            .map_err(|e| BrightDataError::Request(e))?;
76
77        let status = res.status();
78        if !status.is_success() {
79            let error_text = res.text().await.unwrap_or_default();
80            return Err(BrightDataError::ApiError(format!(
81                "BrightData SERP API error {}: {}",
82                status.as_u16(),
83                error_text
84            )));
85        }
86
87        let result = res.json::<Value>().await
88            .map_err(|e| BrightDataError::ParseError(e.to_string()))?;
89
90        Ok(result)
91    }
92
93    /// Take screenshot using BrightData Browser
94    pub async fn screenshot(&self, url: &str, width: u32, height: u32) -> Result<Value, BrightDataError> {
95        let payload = serde_json::json!({
96            "url": url,
97            "zone": self.config.browser_zone,
98            "format": "raw",
99            "data_format": "screenshot",
100            "viewport": {
101                "width": width,
102                "height": height
103            }
104        });
105
106        let res = self
107            .client
108            .post(&format!("{}/request", self.config.base_url))
109            .header("Authorization", format!("Bearer {}", self.config.token))
110            .json(&payload)
111            .send()
112            .await
113            .map_err(|e| BrightDataError::Request(e))?;
114
115        let status = res.status();
116        if !status.is_success() {
117            let error_text = res.text().await.unwrap_or_default();
118            return Err(BrightDataError::ApiError(format!(
119                "BrightData Browser API error {}: {}",
120                status.as_u16(),
121                error_text
122            )));
123        }
124
125        let result = res.json::<Value>().await
126            .map_err(|e| BrightDataError::ParseError(e.to_string()))?;
127
128        Ok(result)
129    }
130
131    fn build_search_url(&self, engine: &str, query: &str) -> String {
132        let encoded_query = urlencoding::encode(query);
133        match engine {
134            "bing" => format!("https://www.bing.com/search?q={}", encoded_query),
135            "yandex" => format!("https://yandex.com/search/?text={}", encoded_query),
136            "duckduckgo" => format!("https://duckduckgo.com/?q={}", encoded_query),
137            _ => format!("https://www.google.com/search?q={}", encoded_query),
138        }
139    }
140}