Skip to main content

coven_format/
coordinates.rs

1//! Where a file is, as its record carries it (format.md D12): in storage at a
2//! fixed location, or only on one device.
3
4use crate::{error::Rule, file_reference::FileReference, Error};
5use coven_crypto::SecretText;
6use coven_foundation::id_source::DeviceId;
7
8/// A file's one place: never both and never neither.
9#[derive(Debug)]
10pub enum Coordinates {
11    /// Stored at `<store>/files/<device>/<id>`, readable with the key.
12    Cloud(FileReference),
13    /// Only on this device, which no other device can read it from.
14    DeviceOnly(DeviceId),
15}
16
17impl Coordinates {
18    /// Decode `file <device> <id> <key>` or `device <device>`, each in its
19    /// one canonical spelling.
20    #[expect(clippy::map_err_ignore, reason = "malformed coordinates are invalid as a whole")]
21    pub fn decode(text: &str) -> Result<Self, Error> {
22        if let Some(device) = text.strip_prefix("device ") {
23            let number = crate::path::canonical_decimal(device).ok_or_else(invalid)?;
24            return Ok(Self::DeviceOnly(DeviceId(number)));
25        }
26        FileReference::decode(text).map(Self::Cloud).map_err(|_| invalid())
27    }
28
29    /// Encode into secret text, so diagnostics never reveal a file key.
30    pub fn encode(&self) -> SecretText {
31        match self {
32            Self::Cloud(reference) => reference.encode(),
33            Self::DeviceOnly(device) => SecretText::new(format!("device {}", device.0)),
34        }
35    }
36}
37
38fn invalid() -> Error {
39    Error::Invalid { field: "file coordinates", rule: Rule::KeyEncoding }
40}
41
42#[cfg(test)]
43#[path = "coordinates_tests.rs"]
44mod tests;