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