2024-07-07 14:40:52 +02:00
|
|
|
use reqwest::{Client, Response};
|
2024-09-19 22:22:57 +02:00
|
|
|
use serde::de::DeserializeOwned;
|
2024-07-07 14:40:52 +02:00
|
|
|
use serde_json::json;
|
|
|
|
use serde_json::Value;
|
|
|
|
|
2024-09-19 22:22:57 +02:00
|
|
|
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,
|
|
|
|
}
|
2024-09-19 22:22:57 +02:00
|
|
|
|
|
|
|
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
|
2024-09-19 22:22:57 +02:00
|
|
|
.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(),
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2024-09-19 22:22:57 +02:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2024-09-19 22:22:57 +02:00
|
|
|
async fn handle_response<'a, T: DeserializeOwned>(
|
2024-07-07 14:40:52 +02:00
|
|
|
&self,
|
|
|
|
response: Response,
|
2024-09-19 22:22:57 +02:00
|
|
|
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) {
|
2024-09-19 22:22:57 +02:00
|
|
|
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 {}: {}",
|
2024-09-19 22:22:57 +02:00
|
|
|
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 {}. {}",
|
2024-09-19 22:22:57 +02:00
|
|
|
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. {}",
|
2024-09-19 22:22:57 +02:00
|
|
|
action.as_ref(), error_message["error"]
|
2024-07-07 14:40:52 +02:00
|
|
|
),
|
|
|
|
409 => format!(
|
|
|
|
"Conflict: Failed to {} due to a conflict. {}",
|
2024-09-19 22:22:57 +02:00
|
|
|
action.as_ref(), error_message["error"]
|
2024-07-07 14:40:52 +02:00
|
|
|
),
|
|
|
|
500 => format!(
|
|
|
|
"Internal Server Error: Failed to {}. {}",
|
2024-09-19 22:22:57 +02:00
|
|
|
action.as_ref(), error_message["error"]
|
2024-07-07 14:40:52 +02:00
|
|
|
),
|
|
|
|
_ => format!(
|
|
|
|
"Unexpected error during {}: Status code {}. {}",
|
2024-09-19 22:22:57 +02:00
|
|
|
action.as_ref(), status_code, error_message["error"]
|
2024-07-07 14:40:52 +02:00
|
|
|
),
|
|
|
|
};
|
|
|
|
Err(FirecrawlError::HttpRequestFailed(message))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|