resolved_pathbuf/
lib.rs

1use std::path::PathBuf;
2
3use derive_more::{AsRef, Deref};
4use resolve_path::PathResolveExt;
5use serde::{Deserialize, Deserializer, Serialize, Serializer};
6
7mod std_impl;
8
9/// This type is a wrapper around a normal [`PathBuf`] that is resolved upon construction. This
10/// happens through the [`resolve_path`] crate.
11///
12/// Additionally, this wrapper preserves the original path as well, and uses the original when
13/// serialized using [`serde`]. This makes this wrapper a safe tool when dealing with user provided
14/// path. For example either through command line arguments or configuration values.
15///
16/// Preserving the original allows us to avoid modifying the user configuration when serializing
17/// the configuration back to the disk.
18#[derive(Deref, AsRef)]
19pub struct ResolvedPathBuf {
20    #[as_ref]
21    #[deref]
22    resolved: PathBuf,
23    /// If the resolved path is the same as the original we don't store a copy
24    /// of the original. This can happen if the original path is already an
25    /// absolute path.
26    original: Option<PathBuf>,
27}
28
29impl TryFrom<PathBuf> for ResolvedPathBuf {
30    type Error = std::io::Error;
31
32    fn try_from(original: PathBuf) -> Result<Self, Self::Error> {
33        let resolved = original.try_resolve()?.to_path_buf();
34        let original = (resolved != original).then_some(original);
35        Ok(Self { resolved, original })
36    }
37}
38
39impl TryFrom<&str> for ResolvedPathBuf {
40    type Error = std::io::Error;
41
42    fn try_from(path: &str) -> Result<Self, Self::Error> {
43        let original: PathBuf = path.into();
44        let resolved = original.try_resolve()?.to_path_buf();
45        let original = (resolved != original).then_some(original);
46        Ok(Self { resolved, original })
47    }
48}
49
50impl Serialize for ResolvedPathBuf {
51    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
52    where
53        S: Serializer,
54    {
55        self.original
56            .as_ref()
57            .unwrap_or(&self.resolved)
58            .serialize(serializer)
59    }
60}
61
62impl<'de> Deserialize<'de> for ResolvedPathBuf {
63    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
64    where
65        D: Deserializer<'de>,
66    {
67        let original: PathBuf = PathBuf::deserialize(deserializer)?;
68        ResolvedPathBuf::try_from(original)
69            .map_err(|e| serde::de::Error::custom(format!("Failed to resolve path: {e}")))
70    }
71}
72
73#[cfg(test)]
74mod tests {
75    use super::*;
76
77    #[test]
78    fn test_as_ref() {
79        let p = ResolvedPathBuf::try_from(PathBuf::from(r"~/x")).unwrap();
80        let x: &PathBuf = p.as_ref();
81        assert_eq!(&p.resolved, x);
82        assert!(x.is_absolute());
83    }
84
85    #[test]
86    fn test_deref() {
87        let p = ResolvedPathBuf::try_from(PathBuf::from(r"~/x")).unwrap();
88        let x: &PathBuf = &p;
89        assert_eq!(&p.resolved, x);
90        assert!(x.is_absolute());
91    }
92
93    #[test]
94    fn serde_serialize() {
95        let o = PathBuf::from(r"~/x");
96        let r = ResolvedPathBuf::try_from(o.clone()).unwrap();
97
98        let o_json = serde_json::to_vec(&o).unwrap();
99        let r_json = serde_json::to_vec(&r).unwrap();
100
101        assert_eq!(o_json, r_json);
102    }
103
104    #[test]
105    fn serde_deserialize() {
106        let o = PathBuf::from(r"~/x");
107        let o_json = serde_json::to_vec(&o).unwrap();
108        let r: ResolvedPathBuf = serde_json::from_slice(&o_json).unwrap();
109        assert_eq!(r.original, Some(o));
110        assert!(r.is_absolute());
111    }
112}