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