Skip to main content

heddle_api/
lib.rs

1//! Generated transport-neutral Rust types for the Heddle API.
2
3pub mod descriptor_trust;
4pub mod framing;
5pub mod signing;
6mod transport;
7pub mod treadle;
8
9pub use transport::{
10    ALL_METHODS, HOSTED_ALPN_V1, MethodDescriptor, MethodRoute, PROVIDER_ALPN_V1,
11    RequestMetadataError, RoutedCall, StreamingShape, human_verification_challenge,
12    human_verification_error_detail, method_descriptor,
13};
14
15include!(concat!(
16    env!("OUT_DIR"),
17    "/heddle_api_attachment_authorization.rs"
18));
19
20/// Cross-product hosted-call framing and typed-failure fixture.
21pub const HOSTED_CALL_V1_FIXTURE_JSON: &str = include_str!("../tests/fixtures/hosted-call-v1.json");
22/// Cross-product canonical unary-signing fixture.
23pub const UNARY_SIGNING_V1_FIXTURE_JSON: &str =
24    include_str!("../tests/fixtures/unary-signing-v1.json");
25/// Cross-product canonical GrantEnvelope v2 payload and rejection fixtures.
26pub const GRANT_ENVELOPE_V2_FIXTURE_JSON: &str =
27    include_str!("../tests/fixtures/grant-envelope-v2.json");
28/// Cross-product endpoint-descriptor and relay-admission signing fixture.
29pub const TRANSPORT_BOOTSTRAP_V1_FIXTURE_JSON: &str =
30    include_str!("../tests/fixtures/transport-bootstrap-v1.json");
31
32/// Page size used when callers omit or pass zero for a requested size.
33pub const DEFAULT_PAGE_SIZE: u32 = 50;
34/// Largest page the public API permits.
35pub const MAX_PAGE_SIZE: u32 = 200;
36
37/// Applies the contract-owned default and upper bound to a requested page.
38pub const fn normalize_page_size(requested: u32) -> u32 {
39    if requested == 0 {
40        DEFAULT_PAGE_SIZE
41    } else if requested > MAX_PAGE_SIZE {
42        MAX_PAGE_SIZE
43    } else {
44        requested
45    }
46}
47
48/// Heddle API protobuf packages.
49pub mod heddle {
50    /// Neutral public API contract.
51    pub mod api {
52        /// Breaking pre-1.0 API generation.
53        pub mod v1alpha1 {
54            include!(concat!(env!("OUT_DIR"), "/heddle.api.v1alpha1.rs"));
55        }
56    }
57}
58
59impl heddle::api::v1alpha1::ErrorReason {
60    /// Returns whether callers may retry without first correcting the request.
61    pub fn retryable(&self) -> bool {
62        matches!(
63            self,
64            Self::RateLimited | Self::QuotaExceeded | Self::Transient
65        )
66    }
67}
68
69/// Compiled protobuf descriptor set for reflection and contract inspection.
70#[cfg(feature = "reflection")]
71pub const FILE_DESCRIPTOR_SET: &[u8] =
72    include_bytes!(concat!(env!("OUT_DIR"), "/heddle_api_descriptor.bin"));
73
74/// Errors returned while constructing fixed-width API identifiers.
75#[derive(Debug, Clone, PartialEq, Eq)]
76pub struct InvalidIdentifierLength {
77    kind: &'static str,
78    expected: usize,
79    actual: usize,
80}
81
82impl std::fmt::Display for InvalidIdentifierLength {
83    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
84        write!(
85            formatter,
86            "{} requires exactly {} bytes, got {}",
87            self.kind, self.expected, self.actual
88        )
89    }
90}
91
92impl std::error::Error for InvalidIdentifierLength {}
93
94impl heddle::api::v1alpha1::StateId {
95    /// Constructs a physical state identifier from exactly 32 bytes.
96    pub fn from_bytes(value: impl AsRef<[u8]>) -> Result<Self, InvalidIdentifierLength> {
97        fixed_width("StateId", 32, value.as_ref()).map(|value| Self { value })
98    }
99}
100
101impl heddle::api::v1alpha1::ChangeId {
102    /// Constructs a rewrite-stable change identifier from exactly 16 bytes.
103    pub fn from_bytes(value: impl AsRef<[u8]>) -> Result<Self, InvalidIdentifierLength> {
104        fixed_width("ChangeId", 16, value.as_ref()).map(|value| Self { value })
105    }
106}
107
108impl heddle::api::v1alpha1::OperationId {
109    /// Constructs a durable operation identifier from exactly 16 bytes.
110    pub fn from_bytes(value: impl AsRef<[u8]>) -> Result<Self, InvalidIdentifierLength> {
111        fixed_width("OperationId", 16, value.as_ref()).map(|value| Self { value })
112    }
113}
114
115impl heddle::api::v1alpha1::OperationBatchId {
116    /// Constructs a durable operation-batch identifier from exactly 16 bytes.
117    pub fn from_bytes(value: impl AsRef<[u8]>) -> Result<Self, InvalidIdentifierLength> {
118        fixed_width("OperationBatchId", 16, value.as_ref()).map(|value| Self { value })
119    }
120}
121
122impl heddle::api::v1alpha1::GitObjectId {
123    /// Constructs and validates a Git object identifier for its hash algorithm.
124    pub fn from_digest(
125        algorithm: heddle::api::v1alpha1::GitObjectAlgorithm,
126        digest: impl AsRef<[u8]>,
127    ) -> Result<Self, InvalidIdentifierLength> {
128        let expected = match algorithm {
129            heddle::api::v1alpha1::GitObjectAlgorithm::Sha1 => 20,
130            heddle::api::v1alpha1::GitObjectAlgorithm::Sha256 => 32,
131            heddle::api::v1alpha1::GitObjectAlgorithm::Unspecified => 0,
132        };
133        fixed_width("GitObjectId", expected, digest.as_ref()).map(|digest| Self {
134            algorithm: algorithm as i32,
135            digest,
136        })
137    }
138}
139
140fn fixed_width(
141    kind: &'static str,
142    expected: usize,
143    value: &[u8],
144) -> Result<Vec<u8>, InvalidIdentifierLength> {
145    if value.len() != expected {
146        return Err(InvalidIdentifierLength {
147            kind,
148            expected,
149            actual: value.len(),
150        });
151    }
152    Ok(value.to_vec())
153}
154
155#[cfg(test)]
156mod tests {
157    use super::heddle::api::v1alpha1::{
158        ChangeId, ErrorReason, GitObjectAlgorithm, GitObjectId, OperationBatchId, OperationId,
159        StateId,
160    };
161
162    #[test]
163    fn fixed_width_identifiers_reject_ambiguous_bytes() {
164        assert!(StateId::from_bytes([0; 32]).is_ok());
165        assert!(StateId::from_bytes([0; 31]).is_err());
166        assert!(ChangeId::from_bytes([0; 16]).is_ok());
167        assert!(ChangeId::from_bytes([0; 17]).is_err());
168        assert!(OperationId::from_bytes([0; 16]).is_ok());
169        assert!(OperationId::from_bytes([0; 15]).is_err());
170        assert!(OperationBatchId::from_bytes([0; 16]).is_ok());
171        assert!(OperationBatchId::from_bytes([0; 17]).is_err());
172        assert!(GitObjectId::from_digest(GitObjectAlgorithm::Sha1, [0; 20]).is_ok());
173        assert!(GitObjectId::from_digest(GitObjectAlgorithm::Sha256, [0; 20]).is_err());
174    }
175
176    #[test]
177    fn error_reason_retryability_is_derived_from_the_taxonomy() {
178        assert!(ErrorReason::RateLimited.retryable());
179        assert!(ErrorReason::QuotaExceeded.retryable());
180        assert!(ErrorReason::Transient.retryable());
181        assert!(!ErrorReason::CursorInvalid.retryable());
182        assert!(!ErrorReason::Internal.retryable());
183    }
184}