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