1pub mod framing;
4pub mod signing;
5mod transport;
6pub mod treadle;
7
8pub use transport::{
9 ALL_METHODS, HOSTED_ALPN_V1, MethodDescriptor, MethodRoute, PROVIDER_ALPN_V1,
10 RequestMetadataError, RoutedCall, StreamingShape, human_verification_challenge,
11 human_verification_error_detail, method_descriptor,
12};
13
14include!(concat!(
15 env!("OUT_DIR"),
16 "/heddle_api_attachment_authorization.rs"
17));
18
19pub const HOSTED_CALL_V1_FIXTURE_JSON: &str = include_str!("../tests/fixtures/hosted-call-v1.json");
21pub const UNARY_SIGNING_V1_FIXTURE_JSON: &str =
23 include_str!("../tests/fixtures/unary-signing-v1.json");
24pub const GRANT_ENVELOPE_V2_FIXTURE_JSON: &str =
26 include_str!("../tests/fixtures/grant-envelope-v2.json");
27pub const TRANSPORT_BOOTSTRAP_V1_FIXTURE_JSON: &str =
29 include_str!("../tests/fixtures/transport-bootstrap-v1.json");
30
31pub const DEFAULT_PAGE_SIZE: u32 = 50;
33pub const MAX_PAGE_SIZE: u32 = 200;
35
36pub const fn normalize_page_size(requested: u32) -> u32 {
38 if requested == 0 {
39 DEFAULT_PAGE_SIZE
40 } else if requested > MAX_PAGE_SIZE {
41 MAX_PAGE_SIZE
42 } else {
43 requested
44 }
45}
46
47pub mod heddle {
49 pub mod api {
51 pub mod v1alpha1 {
53 include!(concat!(env!("OUT_DIR"), "/heddle.api.v1alpha1.rs"));
54 }
55 }
56}
57
58impl heddle::api::v1alpha1::ErrorReason {
59 pub fn retryable(&self) -> bool {
61 matches!(
62 self,
63 Self::RateLimited | Self::QuotaExceeded | Self::Transient
64 )
65 }
66}
67
68#[cfg(feature = "reflection")]
70pub const FILE_DESCRIPTOR_SET: &[u8] =
71 include_bytes!(concat!(env!("OUT_DIR"), "/heddle_api_descriptor.bin"));
72
73#[derive(Debug, Clone, PartialEq, Eq)]
75pub struct InvalidIdentifierLength {
76 kind: &'static str,
77 expected: usize,
78 actual: usize,
79}
80
81impl std::fmt::Display for InvalidIdentifierLength {
82 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
83 write!(
84 formatter,
85 "{} requires exactly {} bytes, got {}",
86 self.kind, self.expected, self.actual
87 )
88 }
89}
90
91impl std::error::Error for InvalidIdentifierLength {}
92
93impl heddle::api::v1alpha1::StateId {
94 pub fn from_bytes(value: impl AsRef<[u8]>) -> Result<Self, InvalidIdentifierLength> {
96 fixed_width("StateId", 32, value.as_ref()).map(|value| Self { value })
97 }
98}
99
100impl heddle::api::v1alpha1::ChangeId {
101 pub fn from_bytes(value: impl AsRef<[u8]>) -> Result<Self, InvalidIdentifierLength> {
103 fixed_width("ChangeId", 16, value.as_ref()).map(|value| Self { value })
104 }
105}
106
107impl heddle::api::v1alpha1::OperationId {
108 pub fn from_bytes(value: impl AsRef<[u8]>) -> Result<Self, InvalidIdentifierLength> {
110 fixed_width("OperationId", 16, value.as_ref()).map(|value| Self { value })
111 }
112}
113
114impl heddle::api::v1alpha1::OperationBatchId {
115 pub fn from_bytes(value: impl AsRef<[u8]>) -> Result<Self, InvalidIdentifierLength> {
117 fixed_width("OperationBatchId", 16, value.as_ref()).map(|value| Self { value })
118 }
119}
120
121impl heddle::api::v1alpha1::GitObjectId {
122 pub fn from_digest(
124 algorithm: heddle::api::v1alpha1::GitObjectAlgorithm,
125 digest: impl AsRef<[u8]>,
126 ) -> Result<Self, InvalidIdentifierLength> {
127 let expected = match algorithm {
128 heddle::api::v1alpha1::GitObjectAlgorithm::Sha1 => 20,
129 heddle::api::v1alpha1::GitObjectAlgorithm::Sha256 => 32,
130 heddle::api::v1alpha1::GitObjectAlgorithm::Unspecified => 0,
131 };
132 fixed_width("GitObjectId", expected, digest.as_ref()).map(|digest| Self {
133 algorithm: algorithm as i32,
134 digest,
135 })
136 }
137}
138
139fn fixed_width(
140 kind: &'static str,
141 expected: usize,
142 value: &[u8],
143) -> Result<Vec<u8>, InvalidIdentifierLength> {
144 if value.len() != expected {
145 return Err(InvalidIdentifierLength {
146 kind,
147 expected,
148 actual: value.len(),
149 });
150 }
151 Ok(value.to_vec())
152}
153
154#[cfg(test)]
155mod tests {
156 use super::heddle::api::v1alpha1::{
157 ChangeId, ErrorReason, GitObjectAlgorithm, GitObjectId, OperationBatchId, OperationId,
158 StateId,
159 };
160
161 #[test]
162 fn fixed_width_identifiers_reject_ambiguous_bytes() {
163 assert!(StateId::from_bytes([0; 32]).is_ok());
164 assert!(StateId::from_bytes([0; 31]).is_err());
165 assert!(ChangeId::from_bytes([0; 16]).is_ok());
166 assert!(ChangeId::from_bytes([0; 17]).is_err());
167 assert!(OperationId::from_bytes([0; 16]).is_ok());
168 assert!(OperationId::from_bytes([0; 15]).is_err());
169 assert!(OperationBatchId::from_bytes([0; 16]).is_ok());
170 assert!(OperationBatchId::from_bytes([0; 17]).is_err());
171 assert!(GitObjectId::from_digest(GitObjectAlgorithm::Sha1, [0; 20]).is_ok());
172 assert!(GitObjectId::from_digest(GitObjectAlgorithm::Sha256, [0; 20]).is_err());
173 }
174
175 #[test]
176 fn error_reason_retryability_is_derived_from_the_taxonomy() {
177 assert!(ErrorReason::RateLimited.retryable());
178 assert!(ErrorReason::QuotaExceeded.retryable());
179 assert!(ErrorReason::Transient.retryable());
180 assert!(!ErrorReason::CursorInvalid.retryable());
181 assert!(!ErrorReason::Internal.retryable());
182 }
183}