aster_forge_cloud_files_core/
revision.rs

1//! Opaque metadata/content revisions and optional content integrity digests.
2
3use std::fmt;
4
5use crate::{CloudFilesCoreError, Result};
6
7macro_rules! opaque_revision {
8    ($name:ident, $field:literal, $docs:literal) => {
9        #[doc = $docs]
10        #[derive(Clone, PartialEq, Eq, Hash)]
11        pub struct $name(Vec<u8>);
12
13        impl $name {
14            /// Creates a non-empty opaque revision token.
15            /// # Errors
16            ///
17            /// Returns an error when validation fails or an underlying backend, store, or platform
18            /// operation fails.
19            pub fn new(value: impl Into<Vec<u8>>) -> Result<Self> {
20                let value = value.into();
21                if value.is_empty() {
22                    return Err(CloudFilesCoreError::empty($field));
23                }
24                Ok(Self(value))
25            }
26
27            /// Copies a non-empty opaque revision token from a byte slice.
28            /// # Errors
29            ///
30            /// Returns an error when validation fails or an underlying backend, store, or platform
31            /// operation fails.
32            pub fn from_slice(value: &[u8]) -> Result<Self> {
33                Self::new(value.to_vec())
34            }
35
36            /// Returns the opaque revision bytes.
37            pub fn as_bytes(&self) -> &[u8] {
38                &self.0
39            }
40
41            /// Consumes the revision and returns its opaque bytes.
42            pub fn into_bytes(self) -> Vec<u8> {
43                self.0
44            }
45        }
46
47        impl fmt::Debug for $name {
48            fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
49                formatter
50                    .debug_struct(stringify!($name))
51                    .field("byte_len", &self.0.len())
52                    .finish()
53            }
54        }
55    };
56}
57
58opaque_revision!(
59    MetadataRevision,
60    "metadata revision",
61    "Opaque equality/precondition token for metadata state."
62);
63opaque_revision!(
64    ContentRevision,
65    "content revision",
66    "Opaque equality/precondition token for content state."
67);
68
69/// Algorithm identifier attached to a real content integrity digest.
70#[derive(Debug, Clone, PartialEq, Eq, Hash)]
71pub struct ContentDigestAlgorithm(String);
72
73impl ContentDigestAlgorithm {
74    /// Creates a non-empty algorithm identifier without normalizing its spelling.
75    /// # Errors
76    ///
77    /// Returns an error when validation fails or an underlying backend, store, or platform
78    /// operation fails.
79    pub fn new(value: impl Into<String>) -> Result<Self> {
80        let value = value.into();
81        if value.is_empty() {
82            return Err(CloudFilesCoreError::empty("content digest algorithm"));
83        }
84        Ok(Self(value))
85    }
86
87    /// Returns the algorithm identifier.
88    #[must_use]
89    pub fn as_str(&self) -> &str {
90        &self.0
91    }
92
93    /// Consumes the identifier and returns its string value.
94    #[must_use]
95    pub fn into_string(self) -> String {
96        self.0
97    }
98}
99
100/// Optional algorithm-tagged content integrity digest.
101///
102/// A content revision or strong `ETag` is not automatically a digest. Callers construct this value
103/// only when the remote backend supplies bytes produced by the named digest algorithm.
104#[derive(Clone, PartialEq, Eq, Hash)]
105pub struct ContentDigest {
106    algorithm: ContentDigestAlgorithm,
107    value: Vec<u8>,
108}
109
110impl ContentDigest {
111    /// Creates a non-empty algorithm-tagged digest.
112    /// # Errors
113    ///
114    /// Returns an error when validation fails or an underlying backend, store, or platform
115    /// operation fails.
116    pub fn new(algorithm: ContentDigestAlgorithm, value: impl Into<Vec<u8>>) -> Result<Self> {
117        let value = value.into();
118        if value.is_empty() {
119            return Err(CloudFilesCoreError::empty("content digest value"));
120        }
121        Ok(Self { algorithm, value })
122    }
123
124    /// Returns the digest algorithm identifier.
125    #[must_use]
126    pub const fn algorithm(&self) -> &ContentDigestAlgorithm {
127        &self.algorithm
128    }
129
130    /// Returns the digest bytes.
131    #[must_use]
132    pub fn value(&self) -> &[u8] {
133        &self.value
134    }
135
136    /// Consumes the digest and returns its algorithm and bytes.
137    #[must_use]
138    pub fn into_parts(self) -> (ContentDigestAlgorithm, Vec<u8>) {
139        (self.algorithm, self.value)
140    }
141}
142
143impl fmt::Debug for ContentDigest {
144    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
145        formatter
146            .debug_struct("ContentDigest")
147            .field("algorithm", &self.algorithm)
148            .field("byte_len", &self.value.len())
149            .finish()
150    }
151}