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