Skip to main content

blitz_script/
fetch.rs

1//! Synchronous fetching of external script sources (`<script src="...">`)
2
3use std::fmt;
4
5use url::Url;
6
7#[derive(Debug)]
8pub enum FetchError {
9    UnsupportedScheme(String),
10    Io(std::io::Error),
11    InvalidData(String),
12}
13
14impl fmt::Display for FetchError {
15    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
16        match self {
17            Self::UnsupportedScheme(scheme) => {
18                write!(f, "unsupported URL scheme for script: {scheme}")
19            }
20            Self::Io(error) => write!(f, "IO error fetching script: {error}"),
21            Self::InvalidData(msg) => write!(f, "invalid script data: {msg}"),
22        }
23    }
24}
25
26impl std::error::Error for FetchError {}
27
28/// Trait for synchronously fetching external script sources.
29///
30/// Scripts are fetched synchronously because the HTML spec requires classic
31/// scripts to execute in document order, blocking parsing.
32pub trait ScriptFetcher: 'static {
33    fn fetch(&self, url: &Url) -> Result<String, FetchError>;
34}
35
36/// The default [`ScriptFetcher`]: supports `file:` and `data:` URLs.
37pub struct DefaultScriptFetcher;
38
39impl ScriptFetcher for DefaultScriptFetcher {
40    fn fetch(&self, url: &Url) -> Result<String, FetchError> {
41        match url.scheme() {
42            "file" => {
43                let path = url
44                    .to_file_path()
45                    .map_err(|_| FetchError::InvalidData(format!("invalid file URL: {url}")))?;
46                std::fs::read_to_string(path).map_err(FetchError::Io)
47            }
48            "data" => {
49                let data_url = data_url::DataUrl::process(url.as_str())
50                    .map_err(|err| FetchError::InvalidData(format!("{err:?}")))?;
51                let (bytes, _) = data_url
52                    .decode_to_vec()
53                    .map_err(|err| FetchError::InvalidData(format!("{err:?}")))?;
54                String::from_utf8(bytes).map_err(|err| FetchError::InvalidData(format!("{err:?}")))
55            }
56            scheme => Err(FetchError::UnsupportedScheme(scheme.to_string())),
57        }
58    }
59}