1use reqwest::{Client, Response};
2use serde::de::DeserializeOwned;
3
4pub mod batch_scrape;
5pub mod crawl;
6pub mod document;
7mod error;
8pub mod map;
9pub mod scrape;
10pub mod search;
11
12use error::FirecrawlAPIError;
13pub use error::FirecrawlError;
14
15#[derive(Clone, Debug)]
16pub struct FirecrawlApp {
17 api_key: Option<String>,
18 api_url: String,
19 client: Client,
20}
21
22pub(crate) const API_VERSION: &str = "v1";
23const CLOUD_API_URL: &str = "https://api.firecrawl.dev";
24
25impl FirecrawlApp {
26 pub fn new(api_key: impl AsRef<str>) -> Result<Self, FirecrawlError> {
27 FirecrawlApp::new_selfhosted(CLOUD_API_URL, Some(api_key))
28 }
29
30 pub fn new_selfhosted(
31 api_url: impl AsRef<str>,
32 api_key: Option<impl AsRef<str>>,
33 ) -> Result<Self, FirecrawlError> {
34 let url = api_url.as_ref().to_string();
35
36 if url == CLOUD_API_URL && api_key.is_none() {
37 return Err(FirecrawlError::APIError(
38 "Configuration".to_string(),
39 FirecrawlAPIError {
40 error: "API key is required for cloud service".to_string(),
41 details: None,
42 },
43 ));
44 }
45
46 Ok(FirecrawlApp {
47 api_key: api_key.map(|x| x.as_ref().to_string()),
48 api_url: url,
49 client: Client::new(),
50 })
51 }
52
53 fn prepare_headers(&self, idempotency_key: Option<&String>) -> reqwest::header::HeaderMap {
54 let mut headers = reqwest::header::HeaderMap::new();
55 headers.insert("Content-Type", "application/json".parse().unwrap());
56 if let Some(api_key) = self.api_key.as_ref() {
57 headers.insert(
58 "Authorization",
59 format!("Bearer {}", api_key).parse().unwrap(),
60 );
61 }
62 if let Some(key) = idempotency_key {
63 headers.insert("x-idempotency-key", key.parse().unwrap());
64 }
65 headers
66 }
67
68 async fn handle_response<T: DeserializeOwned>(
69 &self,
70 response: Response,
71 action: impl AsRef<str>,
72 ) -> Result<T, FirecrawlError> {
73 let status = response.status();
74
75 if !status.is_success() {
76 match response.json::<FirecrawlAPIError>().await {
78 Ok(api_error) => {
79 return Err(FirecrawlError::APIError(
80 action.as_ref().to_string(),
81 api_error,
82 ));
83 }
84 Err(_) => {
85 return Err(FirecrawlError::HttpRequestFailed(
86 action.as_ref().to_string(),
87 status.as_u16(),
88 status.as_str().to_string(),
89 ));
90 }
91 }
92 }
93
94 response.json::<T>().await.map_err(|e| {
96 if e.is_decode() {
97 FirecrawlError::ResponseParseErrorText(e)
98 } else {
99 FirecrawlError::HttpError(action.as_ref().to_string(), e)
100 }
101 })
102 }
103}