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