imferno_core/storage/
mod.rs1pub mod fs;
8
9#[cfg(feature = "aws-s3")]
10pub mod s3;
11
12#[derive(Debug, Clone, PartialEq, Eq)]
17pub struct StorageUri {
18 pub scheme: Scheme,
19 pub path: String,
22 pub bucket: Option<String>,
24}
25
26#[derive(Debug, Clone, Copy, PartialEq, Eq)]
27pub enum Scheme {
28 File,
29 S3,
30}
31
32impl StorageUri {
33 pub fn parse(input: &str) -> Result<Self, StorageError> {
35 if !input.contains("://") {
37 return Ok(StorageUri {
38 scheme: Scheme::File,
39 path: input.to_string(),
40 bucket: None,
41 });
42 }
43
44 let parsed = url::Url::parse(input)
45 .map_err(|e| StorageError::InvalidUri(format!("{input}: {e}")))?;
46
47 match parsed.scheme() {
48 "file" => Ok(StorageUri {
49 scheme: Scheme::File,
50 path: parsed.path().to_string(),
51 bucket: None,
52 }),
53 "s3" => {
54 let bucket = parsed
55 .host_str()
56 .ok_or_else(|| {
57 StorageError::InvalidUri(format!("s3 URI missing bucket: {input}"))
58 })?
59 .to_string();
60 let path = parsed.path().trim_start_matches('/').to_string();
62 Ok(StorageUri {
63 scheme: Scheme::S3,
64 path,
65 bucket: Some(bucket),
66 })
67 }
68 other => Err(StorageError::UnsupportedScheme(other.to_string())),
69 }
70 }
71}
72
73pub trait Storage: Send + Sync {
84 fn list(&self, uri: &StorageUri) -> Result<Vec<Entry>, StorageError>;
89
90 fn read_to_string(&self, uri: &StorageUri) -> Result<String, StorageError>;
95}
96
97#[derive(Debug, Clone)]
104pub struct Entry {
105 pub uri: String,
106 pub size: u64,
107 pub is_file: bool,
108}
109
110#[derive(thiserror::Error, Debug)]
111pub enum StorageError {
112 #[error("invalid URI: {0}")]
113 InvalidUri(String),
114 #[error("unsupported scheme: {0}")]
115 UnsupportedScheme(String),
116 #[error("IO: {0}")]
117 Io(#[from] std::io::Error),
118 #[error("backend error: {0}")]
119 Backend(String),
120}
121
122#[cfg(test)]
123mod tests {
124 use super::*;
125
126 #[test]
127 fn parse_file_uri() {
128 let uri = StorageUri::parse("file:///tmp/imp").unwrap();
129 assert_eq!(uri.scheme, Scheme::File);
130 assert_eq!(uri.path, "/tmp/imp");
131 assert_eq!(uri.bucket, None);
132 }
133
134 #[test]
135 fn parse_s3_uri() {
136 let uri = StorageUri::parse("s3://my-bucket/path/to/imp/").unwrap();
137 assert_eq!(uri.scheme, Scheme::S3);
138 assert_eq!(uri.bucket, Some("my-bucket".to_string()));
139 assert_eq!(uri.path, "path/to/imp/");
140 }
141
142 #[test]
143 fn parse_bare_path_becomes_file_uri() {
144 let uri = StorageUri::parse("/tmp/imp").unwrap();
145 assert_eq!(uri.scheme, Scheme::File);
146 assert_eq!(uri.path, "/tmp/imp");
147 }
148
149 #[test]
150 fn parse_unsupported_scheme_errors() {
151 let err = StorageUri::parse("ftp://example.com/imp").unwrap_err();
152 assert!(matches!(err, StorageError::UnsupportedScheme(_)));
153 }
154}