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