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