openbim_ifcx/layers/
fs.rs1use std::fmt;
2use std::io;
3use std::path::{Path, PathBuf};
4
5use super::LayerResolver;
6
7#[derive(Debug, Clone, Default)]
26pub struct FsResolver {
27 prefixes: Vec<(String, PathBuf)>,
28}
29
30impl FsResolver {
31 pub fn new() -> Self {
32 Self::default()
33 }
34
35 pub fn map_prefix(mut self, prefix: impl Into<String>, dir: impl Into<PathBuf>) -> Self {
39 self.prefixes.push((prefix.into(), dir.into()));
40 self
41 }
42
43 fn path(&self, uri: &str, importer: Option<&str>) -> Result<PathBuf, FsError> {
44 for (prefix, dir) in &self.prefixes {
45 if let Some(rest) = uri.strip_prefix(prefix.as_str()) {
46 return Ok(dir.join(rest.trim_start_matches('/')));
47 }
48 }
49 if uri.contains("://") {
50 return Err(FsError::UnsupportedUri(uri.to_owned()));
51 }
52 let path = Path::new(uri);
53 Ok(match importer.and_then(|key| Path::new(key).parent()) {
54 Some(dir) if path.is_relative() => dir.join(path),
55 _ => path.to_path_buf(),
56 })
57 }
58}
59
60impl LayerResolver for FsResolver {
61 type Error = FsError;
62
63 fn key(&mut self, uri: &str, importer: Option<&str>) -> Result<String, FsError> {
64 let path = self.path(uri, importer)?;
65 let path = std::fs::canonicalize(&path).unwrap_or(path);
67 path.into_os_string()
68 .into_string()
69 .map_err(|path| FsError::NonUtf8Path(path.into()))
70 }
71
72 fn load(&mut self, key: &str) -> Result<Option<Vec<u8>>, FsError> {
73 match std::fs::read(key) {
74 Ok(bytes) => Ok(Some(bytes)),
75 Err(e) if e.kind() == io::ErrorKind::NotFound => Ok(None),
76 Err(source) => Err(FsError::Io {
77 path: key.into(),
78 source,
79 }),
80 }
81 }
82}
83
84#[derive(Debug)]
86pub enum FsError {
87 UnsupportedUri(String),
89 NonUtf8Path(PathBuf),
91 Io {
92 path: PathBuf,
93 source: io::Error,
94 },
95}
96
97impl fmt::Display for FsError {
98 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
99 match self {
100 Self::UnsupportedUri(uri) => write!(f, "no local mapping for URI {uri:?}"),
101 Self::NonUtf8Path(path) => write!(f, "path {} is not valid UTF-8", path.display()),
102 Self::Io { path, source } => write!(f, "reading {}: {source}", path.display()),
103 }
104 }
105}
106
107impl std::error::Error for FsError {
108 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
109 match self {
110 Self::Io { source, .. } => Some(source),
111 Self::UnsupportedUri(_) | Self::NonUtf8Path(_) => None,
112 }
113 }
114}