Skip to main content

heddle_api/
lib.rs

1//! Generated transport-neutral Rust types for the Heddle API.
2
3pub 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
24/// Cross-product hosted-call framing and typed-failure fixture.
25pub const HOSTED_CALL_V1_FIXTURE_JSON: &str = include_str!("../tests/fixtures/hosted-call-v1.json");
26/// Cross-product canonical unary-signing fixture.
27pub const UNARY_SIGNING_V1_FIXTURE_JSON: &str =
28    include_str!("../tests/fixtures/unary-signing-v1.json");
29/// Cross-product canonical GrantEnvelope v2 payload and rejection fixtures.
30pub const GRANT_ENVELOPE_V2_FIXTURE_JSON: &str =
31    include_str!("../tests/fixtures/grant-envelope-v2.json");
32/// Cross-product endpoint-descriptor and relay-admission signing fixture.
33pub const TRANSPORT_BOOTSTRAP_V1_FIXTURE_JSON: &str =
34    include_str!("../tests/fixtures/transport-bootstrap-v1.json");
35
36/// Page size used when callers omit or pass zero for a requested size.
37pub const DEFAULT_PAGE_SIZE: u32 = 50;
38/// Largest page the public API permits.
39pub const MAX_PAGE_SIZE: u32 = 200;
40
41/// Applies the contract-owned default and upper bound to a requested page.
42pub 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
52/// Heddle API protobuf packages.
53pub mod heddle {
54    /// Neutral public API contract.
55    pub mod api {
56        /// Shared foundational types used by every versioned API package.
57        pub mod common {
58            include!(concat!(env!("OUT_DIR"), "/heddle.api.common.rs"));
59        }
60        /// Frozen Thread-oriented contract; endpoint support is negotiated.
61        pub mod v1alpha2 {
62            include!(concat!(env!("OUT_DIR"), "/heddle.api.v1alpha2.rs"));
63        }
64    }
65}
66
67impl heddle::api::common::ErrorReason {
68    /// Returns whether callers may retry without first correcting the request.
69    pub fn retryable(&self) -> bool {
70        matches!(
71            self,
72            Self::RateLimited | Self::QuotaExceeded | Self::Transient
73        )
74    }
75}
76
77/// Compiled protobuf descriptor set for reflection and contract inspection.
78#[cfg(feature = "reflection")]
79pub const FILE_DESCRIPTOR_SET: &[u8] =
80    include_bytes!(concat!(env!("OUT_DIR"), "/heddle_api_descriptor.bin"));
81
82/// Errors returned while constructing fixed-width API identifiers.
83#[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    /// Constructs a physical state identifier from exactly 32 bytes.
104    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    /// Constructs a rewrite-stable change identifier from exactly 16 bytes.
111    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    /// Constructs a durable operation identifier from exactly 16 bytes.
118    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    /// Constructs a durable operation-batch identifier from exactly 16 bytes.
125    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    /// Constructs and validates a Git object identifier for its hash algorithm.
132    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}