1pub mod signing;
4
5pub const DEFAULT_PAGE_SIZE: u32 = 50;
7pub const MAX_PAGE_SIZE: u32 = 200;
9
10pub 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
21pub mod heddle {
23 pub mod api {
25 pub mod v1alpha1 {
27 include!(concat!(env!("OUT_DIR"), "/heddle.api.v1alpha1.rs"));
28 }
29 }
30}
31
32#[cfg(feature = "reflection")]
34pub const FILE_DESCRIPTOR_SET: &[u8] =
35 include_bytes!(concat!(env!("OUT_DIR"), "/heddle_api_descriptor.bin"));
36
37#[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 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 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 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 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 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}