Skip to main content

buntrail_protocol/
path.rs

1use base64::{Engine as _, engine::general_purpose::STANDARD};
2use buntrail_core::contracts::{CoreError, ErrorCode, TargetPath};
3use serde::{Deserialize, Serialize};
4
5#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
6#[serde(rename_all = "snake_case")]
7pub enum PathKind {
8    Posix,
9    Windows,
10}
11
12/// A validated, lossless target filesystem path; display strings never grant authority.
13#[derive(Clone, Debug, PartialEq, Eq)]
14pub struct WirePath(TargetPath);
15
16#[derive(Serialize, Deserialize)]
17#[serde(deny_unknown_fields)]
18struct EncodedPath {
19    kind: PathKind,
20    base64: String,
21}
22
23impl WirePath {
24    pub fn from_core(path: &TargetPath) -> Result<Self, CoreError> {
25        Ok(Self(match path {
26            TargetPath::PosixBytes(bytes) => TargetPath::posix(bytes.clone())?,
27            TargetPath::WindowsWide(units) => TargetPath::windows(units.clone())?,
28        }))
29    }
30
31    pub fn core(&self) -> TargetPath {
32        self.0.clone()
33    }
34
35    pub fn kind(&self) -> PathKind {
36        match self.0 {
37            TargetPath::PosixBytes(_) => PathKind::Posix,
38            TargetPath::WindowsWide(_) => PathKind::Windows,
39        }
40    }
41}
42
43impl Serialize for WirePath {
44    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
45        let bytes = match &self.0 {
46            TargetPath::PosixBytes(bytes) => bytes.clone(),
47            TargetPath::WindowsWide(units) => units.iter().flat_map(|u| u.to_le_bytes()).collect(),
48        };
49        EncodedPath {
50            kind: self.kind(),
51            base64: STANDARD.encode(bytes),
52        }
53        .serialize(serializer)
54    }
55}
56
57impl<'de> Deserialize<'de> for WirePath {
58    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
59        use serde::de::Error;
60        let encoded = EncodedPath::deserialize(deserializer)?;
61        let bytes = STANDARD.decode(encoded.base64).map_err(D::Error::custom)?;
62        let path = match encoded.kind {
63            PathKind::Posix => TargetPath::posix(bytes),
64            PathKind::Windows if bytes.len() % 2 == 0 => TargetPath::windows(
65                bytes
66                    .as_chunks::<2>()
67                    .0
68                    .iter()
69                    .map(|u| u16::from_le_bytes([u[0], u[1]]))
70                    .collect(),
71            ),
72            PathKind::Windows => Err(CoreError::new(
73                ErrorCode::InvalidArgument,
74                "odd Windows path byte length",
75            )),
76        };
77        Ok(Self(path.map_err(|e| D::Error::custom(e.message))?))
78    }
79}