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