gemini_client_api/
utils.rs

1use base64::{Engine, engine::general_purpose::STANDARD};
2use futures::future::join_all;
3pub use mime;
4use regex::Regex;
5use reqwest::Client;
6pub use reqwest::header::{HeaderMap, HeaderValue};
7use std::time::Duration;
8
9#[derive(Clone)]
10pub struct MatchedFiles {
11    pub index: usize,
12    pub length: usize,
13    pub mime_type: Option<String>,
14    pub base64: Option<String>,
15}
16/// # Panics
17/// `regex` must have a Regex with atleast 1 capture group with file URL as first capture group, else it PANICS
18/// # Arguments
19/// `guess_mime_type` is used to detect mimi_type of URL pointing to file system or web resource
20/// with no "Content-Type" header.
21pub async fn get_file_base64s(
22    markdown: impl AsRef<str>,
23    regex: Regex,
24    guess_mime_type: fn(url: &str) -> mime::Mime,
25    decide_download: fn(headers: &HeaderMap) -> bool,
26    timeout: Duration,
27) -> Vec<MatchedFiles> {
28    let client = Client::builder().timeout(timeout).build().unwrap();
29    let mut tasks = Vec::new();
30
31    for file in regex.captures_iter(markdown.as_ref()) {
32        let capture = file.get(0).unwrap();
33        let url = file[1].to_string();
34        tasks.push((async |capture: regex::Match<'_>, url: String| {
35            let (mime_type, base64) = if url.starts_with("https://") || url.starts_with("http://") {
36                let response = client.get(&url).send().await;
37                match response {
38                    Ok(response) if (decide_download)(response.headers()) => {
39                        let mime_type = response
40                            .headers()
41                            .get("Content-Type")
42                            .map(|mime| mime.to_str().ok())
43                            .flatten()
44                            .map(|str| str.to_string());
45
46                        let base64 = response
47                            .bytes()
48                            .await
49                            .ok()
50                            .map(|bytes| STANDARD.encode(bytes));
51                        let mime_type = match base64 {
52                            Some(_) => {
53                                mime_type.or_else(|| Some(guess_mime_type(&url).to_string()))
54                            }
55                            None => None,
56                        };
57                        (mime_type, base64)
58                    }
59                    _ => (None, None),
60                }
61            } else {
62                let base64 = tokio::fs::read(url.clone())
63                    .await
64                    .ok()
65                    .map(|bytes| STANDARD.encode(&bytes));
66                match base64 {
67                    Some(base64) => (Some(guess_mime_type(&url).to_string()), Some(base64)),
68                    None => (None, None),
69                }
70            };
71            MatchedFiles {
72                index: capture.start(),
73                length: capture.len(),
74                mime_type,
75                base64,
76            }
77        })(capture, url));
78    }
79    join_all(tasks).await
80}