Skip to main content

heddle_api/
lib.rs

1//! Generated transport-neutral Rust types for the Heddle API.
2
3pub 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
13/// Cross-product hosted-call framing and typed-failure fixture.
14pub const HOSTED_CALL_V1_FIXTURE_JSON: &str = include_str!("../tests/fixtures/hosted-call-v1.json");
15/// Cross-product canonical unary-signing fixture.
16pub const UNARY_SIGNING_V1_FIXTURE_JSON: &str =
17    include_str!("../tests/fixtures/unary-signing-v1.json");
18/// Cross-product endpoint-descriptor and relay-admission signing fixture.
19pub const TRANSPORT_BOOTSTRAP_V1_FIXTURE_JSON: &str =
20    include_str!("../tests/fixtures/transport-bootstrap-v1.json");
21
22/// Page size used when callers omit or pass zero for a requested size.
23pub const DEFAULT_PAGE_SIZE: u32 = 50;
24/// Largest page the public API permits.
25pub const MAX_PAGE_SIZE: u32 = 200;
26
27/// Applies the contract-owned default and upper bound to a requested page.
28pub 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
38/// Heddle API protobuf packages.
39pub mod heddle {
40    /// Neutral public API contract.
41    pub mod api {
42        /// Breaking pre-1.0 API generation.
43        pub mod v1alpha1 {
44            include!(concat!(env!("OUT_DIR"), "/heddle.api.v1alpha1.rs"));
45        }
46    }
47}
48
49impl heddle::api::v1alpha1::ErrorReason {
50    /// Returns whether callers may retry without first correcting the request.
51    pub fn retryable(&self) -> bool {
52        matches!(
53            self,
54            Self::RateLimited | Self::QuotaExceeded | Self::Transient
55        )
56    }
57}
58
59/// Compiled protobuf descriptor set for reflection and contract inspection.
60#[cfg(feature = "reflection")]
61pub const FILE_DESCRIPTOR_SET: &[u8] =
62    include_bytes!(concat!(env!("OUT_DIR"), "/heddle_api_descriptor.bin"));
63
64/// Errors returned while constructing fixed-width API identifiers.
65#[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    /// Constructs a physical state identifier from exactly 32 bytes.
86    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    /// Constructs a rewrite-stable change identifier from exactly 16 bytes.
93    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    /// Constructs a durable operation identifier from exactly 16 bytes.
100    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    /// Constructs a durable operation-batch identifier from exactly 16 bytes.
107    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    /// Constructs and validates a Git object identifier for its hash algorithm.
114    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}