99 lines
3.3 KiB
Rust
Raw Normal View History

2024-07-07 14:40:52 +02:00
use reqwest::{Client, Response};
use serde::de::DeserializeOwned;
2024-07-07 14:40:52 +02:00
use serde_json::json;
use serde_json::Value;
pub mod crawl;
pub mod document;
mod error;
pub mod scrape;
pub use error::FirecrawlError;
2024-07-07 14:40:52 +02:00
2024-07-12 14:07:23 +02:00
#[derive(Clone, Debug)]
2024-07-07 14:40:52 +02:00
pub struct FirecrawlApp {
api_key: String,
api_url: String,
client: Client,
}
pub(crate) const API_VERSION: &str = "/v1";
2024-07-07 14:40:52 +02:00
impl FirecrawlApp {
pub fn new(api_key: Option<String>, api_url: Option<String>) -> Result<Self, FirecrawlError> {
let api_key = api_key
.ok_or(FirecrawlError::APIKeyNotProvided)?;
let api_url = api_url
.unwrap_or_else(|| "https://api.firecrawl.dev".to_string());
2024-07-07 14:40:52 +02:00
Ok(FirecrawlApp {
api_key,
api_url,
client: Client::new(),
})
}
fn prepare_headers(&self, idempotency_key: Option<&String>) -> reqwest::header::HeaderMap {
2024-07-07 14:40:52 +02:00
let mut headers = reqwest::header::HeaderMap::new();
headers.insert("Content-Type", "application/json".parse().unwrap());
headers.insert(
"Authorization",
format!("Bearer {}", self.api_key).parse().unwrap(),
);
if let Some(key) = idempotency_key {
headers.insert("x-idempotency-key", key.parse().unwrap());
}
headers
}
async fn handle_response<'a, T: DeserializeOwned>(
2024-07-07 14:40:52 +02:00
&self,
response: Response,
action: impl AsRef<str>,
) -> Result<T, FirecrawlError> {
2024-07-07 14:40:52 +02:00
if response.status().is_success() {
let response_json: Value = response
.json()
.await
.map_err(|e| FirecrawlError::ResponseParseError(e.to_string()))?;
if response_json["success"].as_bool().unwrap_or(false) {
Ok(serde_json::from_value(response_json).map_err(|e| FirecrawlError::ResponseParseError(e.to_string()))?)
2024-07-07 14:40:52 +02:00
} else {
Err(FirecrawlError::HttpRequestFailed(format!(
"Failed to {}: {}",
action.as_ref(), response_json["error"]
2024-07-07 14:40:52 +02:00
)))
}
} else {
let status_code = response.status().as_u16();
let error_message = response
.json::<Value>()
.await
.unwrap_or_else(|_| json!({"error": "No additional error details provided."}));
let message = match status_code {
402 => format!(
"Payment Required: Failed to {}. {}",
action.as_ref(), error_message["error"]
2024-07-07 14:40:52 +02:00
),
408 => format!(
"Request Timeout: Failed to {} as the request timed out. {}",
action.as_ref(), error_message["error"]
2024-07-07 14:40:52 +02:00
),
409 => format!(
"Conflict: Failed to {} due to a conflict. {}",
action.as_ref(), error_message["error"]
2024-07-07 14:40:52 +02:00
),
500 => format!(
"Internal Server Error: Failed to {}. {}",
action.as_ref(), error_message["error"]
2024-07-07 14:40:52 +02:00
),
_ => format!(
"Unexpected error during {}: Status code {}. {}",
action.as_ref(), status_code, error_message["error"]
2024-07-07 14:40:52 +02:00
),
};
Err(FirecrawlError::HttpRequestFailed(message))
}
}
}