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