runner_core/packs/resolver/
fs.rs

1use std::path::{Path, PathBuf};
2
3use anyhow::{Context, Result, anyhow};
4use url::Url;
5
6use crate::path_safety::normalize_under_root;
7
8use super::{FetchResponse, PackResolver};
9
10#[derive(Debug)]
11pub struct FsResolver {
12    root: PathBuf,
13}
14
15impl FsResolver {
16    pub fn new(root: PathBuf) -> Self {
17        Self { root }
18    }
19
20    fn parse_path(&self, locator: &str) -> Result<PathBuf> {
21        if let Some(stripped) = locator.strip_prefix("fs://") {
22            if stripped.starts_with('/')
23                || stripped.starts_with("./")
24                || stripped.starts_with("../")
25            {
26                return Ok(PathBuf::from(stripped));
27            }
28            if cfg!(windows) && stripped.chars().nth(1) == Some(':') {
29                return Ok(PathBuf::from(stripped));
30            }
31            let file_url = format!("file://{stripped}");
32            let url = Url::parse(&file_url).context("failed to parse fs:// locator as file URL")?;
33            return url
34                .to_file_path()
35                .map_err(|_| anyhow!("fs locator {locator} cannot be represented as a path"));
36        }
37        Ok(PathBuf::from(locator))
38    }
39
40    fn normalize(&self, path: PathBuf) -> Result<PathBuf> {
41        if path.is_absolute() {
42            let parent = path
43                .parent()
44                .ok_or_else(|| anyhow!("fs locator missing parent: {}", path.display()))?;
45            let root = parent
46                .canonicalize()
47                .with_context(|| format!("failed to canonicalize {}", parent.display()))?;
48            let file = path
49                .file_name()
50                .ok_or_else(|| anyhow!("fs locator missing file name: {}", path.display()))?;
51            return normalize_under_root(&root, Path::new(file));
52        }
53        normalize_under_root(&self.root, &path)
54    }
55}
56
57impl PackResolver for FsResolver {
58    fn scheme(&self) -> &'static str {
59        "fs"
60    }
61
62    fn fetch(&self, locator: &str) -> Result<FetchResponse> {
63        let path = self.parse_path(locator)?;
64        let path = self.normalize(path)?;
65        if !path.exists() {
66            anyhow::bail!("fs resolver: {} does not exist", path.display());
67        }
68        Ok(FetchResponse::from_path(path))
69    }
70}