Skip to main content

heddle_api/
lib.rs

1//! Generated Rust types and optional gRPC bindings for the Heddle API.
2
3pub mod signing;
4
5/// Page size used when callers omit or pass zero for a requested size.
6pub const DEFAULT_PAGE_SIZE: u32 = 50;
7/// Largest page the public API permits.
8pub const MAX_PAGE_SIZE: u32 = 200;
9
10/// Applies the contract-owned default and upper bound to a requested page.
11pub const fn normalize_page_size(requested: u32) -> u32 {
12    if requested == 0 {
13        DEFAULT_PAGE_SIZE
14    } else if requested > MAX_PAGE_SIZE {
15        MAX_PAGE_SIZE
16    } else {
17        requested
18    }
19}
20
21/// Heddle API protobuf packages.
22pub mod heddle {
23    /// Neutral public API contract.
24    pub mod api {
25        /// Breaking pre-1.0 API generation.
26        pub mod v1alpha1 {
27            include!(concat!(env!("OUT_DIR"), "/heddle.api.v1alpha1.rs"));
28        }
29    }
30}
31
32/// Compiled protobuf descriptor set for reflection and contract inspection.
33#[cfg(feature = "reflection")]
34pub const FILE_DESCRIPTOR_SET: &[u8] =
35    include_bytes!(concat!(env!("OUT_DIR"), "/heddle_api_descriptor.bin"));
36
37/// Errors returned while constructing fixed-width API identifiers.
38#[derive(Debug, Clone, PartialEq, Eq)]
39pub struct InvalidIdentifierLength {
40    kind: &'static str,
41    expected: usize,
42    actual: usize,
43}
44
45impl std::fmt::Display for InvalidIdentifierLength {
46    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
47        write!(
48            formatter,
49            "{} requires exactly {} bytes, got {}",
50            self.kind, self.expected, self.actual
51        )
52    }
53}
54
55impl std::error::Error for InvalidIdentifierLength {}
56
57impl heddle::api::v1alpha1::StateId {
58    /// Constructs a physical state identifier from exactly 32 bytes.
59    pub fn from_bytes(value: impl AsRef<[u8]>) -> Result<Self, InvalidIdentifierLength> {
60        fixed_width("StateId", 32, value.as_ref()).map(|value| Self { value })
61    }
62}
63
64impl heddle::api::v1alpha1::ChangeId {
65    /// Constructs a rewrite-stable change identifier from exactly 16 bytes.
66    pub fn from_bytes(value: impl AsRef<[u8]>) -> Result<Self, InvalidIdentifierLength> {
67        fixed_width("ChangeId", 16, value.as_ref()).map(|value| Self { value })
68    }
69}
70
71impl heddle::api::v1alpha1::OperationId {
72    /// Constructs a durable operation identifier from exactly 16 bytes.
73    pub fn from_bytes(value: impl AsRef<[u8]>) -> Result<Self, InvalidIdentifierLength> {
74        fixed_width("OperationId", 16, value.as_ref()).map(|value| Self { value })
75    }
76}
77
78impl heddle::api::v1alpha1::OperationBatchId {
79    /// Constructs a durable operation-batch identifier from exactly 16 bytes.
80    pub fn from_bytes(value: impl AsRef<[u8]>) -> Result<Self, InvalidIdentifierLength> {
81        fixed_width("OperationBatchId", 16, value.as_ref()).map(|value| Self { value })
82    }
83}
84
85impl heddle::api::v1alpha1::GitObjectId {
86    /// Constructs and validates a Git object identifier for its hash algorithm.
87    pub fn from_digest(
88        algorithm: heddle::api::v1alpha1::GitObjectAlgorithm,
89        digest: impl AsRef<[u8]>,
90    ) -> Result<Self, InvalidIdentifierLength> {
91        let expected = match algorithm {
92            heddle::api::v1alpha1::GitObjectAlgorithm::Sha1 => 20,
93            heddle::api::v1alpha1::GitObjectAlgorithm::Sha256 => 32,
94            heddle::api::v1alpha1::GitObjectAlgorithm::Unspecified => 0,
95        };
96        fixed_width("GitObjectId", expected, digest.as_ref()).map(|digest| Self {
97            algorithm: algorithm as i32,
98            digest,
99        })
100    }
101}
102
103fn fixed_width(
104    kind: &'static str,
105    expected: usize,
106    value: &[u8],
107) -> Result<Vec<u8>, InvalidIdentifierLength> {
108    if value.len() != expected {
109        return Err(InvalidIdentifierLength {
110            kind,
111            expected,
112            actual: value.len(),
113        });
114    }
115    Ok(value.to_vec())
116}
117
118#[cfg(test)]
119mod tests {
120    use super::heddle::api::v1alpha1::{
121        ChangeId, GitObjectAlgorithm, GitObjectId, OperationBatchId, OperationId, StateId,
122    };
123
124    #[test]
125    fn fixed_width_identifiers_reject_ambiguous_bytes() {
126        assert!(StateId::from_bytes([0; 32]).is_ok());
127        assert!(StateId::from_bytes([0; 31]).is_err());
128        assert!(ChangeId::from_bytes([0; 16]).is_ok());
129        assert!(ChangeId::from_bytes([0; 17]).is_err());
130        assert!(OperationId::from_bytes([0; 16]).is_ok());
131        assert!(OperationId::from_bytes([0; 15]).is_err());
132        assert!(OperationBatchId::from_bytes([0; 16]).is_ok());
133        assert!(OperationBatchId::from_bytes([0; 17]).is_err());
134        assert!(GitObjectId::from_digest(GitObjectAlgorithm::Sha1, [0; 20]).is_ok());
135        assert!(GitObjectId::from_digest(GitObjectAlgorithm::Sha256, [0; 20]).is_err());
136    }
137}