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