Skip to main content

icechunk_types/
lib.rs

1pub mod error;
2
3use core::fmt;
4use std::fmt::{Debug, Display};
5
6use serde::{Deserialize, Serialize};
7use serde_with::{TryFromInto, serde_as};
8use thiserror::Error;
9use typed_path::Utf8UnixPathBuf;
10
11#[doc(hidden)]
12pub mod sealed {
13    pub trait Sealed {}
14}
15
16pub use error::{ICError, ICResultExt};
17
18pub const DEFAULT_BRANCH: &str = "main";
19
20#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone, Hash, PartialOrd, Ord)]
21pub struct ETag(pub String);
22
23/// A normalized Zarr path: absolute (starts with `/`) and no trailing slash.
24#[serde_as]
25#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Serialize, Deserialize)]
26pub struct Path(#[serde_as(as = "TryFromInto<String>")] Utf8UnixPathBuf);
27
28// The impl of Debug for Utf8UnixPathBuf is expensive and triggered often by tracing Spans
29// This implemnetation is much cheaper, and removes formatting from our samply profiles.
30impl Debug for Path {
31    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
32        Display::fmt(self, f)
33    }
34}
35
36impl Display for Path {
37    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
38        write!(f, "{}", self.0)
39    }
40}
41
42/// Errors when constructing a [`Path`].
43#[derive(Debug, Clone, Error, PartialEq, Eq)]
44#[non_exhaustive]
45pub enum PathError {
46    #[error("path must start with a `/` character")]
47    NotAbsolute,
48    #[error(r#"path must be cannonic, cannot include "." or "..""#)]
49    NotCanonic,
50}
51
52impl Path {
53    pub fn root() -> Path {
54        Path(Utf8UnixPathBuf::from("/".to_string()))
55    }
56
57    // Fast-path unvalidated constructor for use when reading from Snapshots
58    pub fn from_trusted(path: &str) -> Path {
59        Path(Utf8UnixPathBuf::from(path))
60    }
61
62    pub fn new(path: &str) -> Result<Path, PathError> {
63        let buf = Utf8UnixPathBuf::from(path);
64        if !buf.is_absolute() {
65            return Err(PathError::NotAbsolute);
66        }
67
68        if buf.normalize() != buf {
69            return Err(PathError::NotCanonic);
70        }
71        Ok(Path(buf))
72    }
73
74    pub fn starts_with(&self, other: &Path) -> bool {
75        self.0.starts_with(&other.0)
76    }
77
78    pub fn ancestors(&self) -> impl Iterator<Item = Path> + '_ {
79        self.0.ancestors().map(|p| Path(p.to_owned()))
80    }
81
82    pub fn name(&self) -> Option<&str> {
83        self.0.file_name()
84    }
85
86    pub fn buf(&self) -> &Utf8UnixPathBuf {
87        &self.0
88    }
89}
90
91impl TryFrom<&str> for Path {
92    type Error = PathError;
93
94    fn try_from(value: &str) -> Result<Self, Self::Error> {
95        Self::new(value)
96    }
97}
98
99impl TryFrom<&String> for Path {
100    type Error = PathError;
101
102    fn try_from(value: &String) -> Result<Self, Self::Error> {
103        value.as_str().try_into()
104    }
105}
106
107impl TryFrom<String> for Path {
108    type Error = PathError;
109
110    fn try_from(value: String) -> Result<Self, Self::Error> {
111        value.as_str().try_into()
112    }
113}
114
115/// Returns the user-agent string for icechunk HTTP requests.
116///
117/// Format: `icechunk-rust-<version>` (e.g., `icechunk-rust-2.0.0-alpha.4`).
118pub fn user_agent() -> &'static str {
119    concat!("icechunk-rust-", env!("CARGO_PKG_VERSION"))
120}