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;
5mod foreign_dependencies;
6pub mod framing;
7pub mod hybrid_codec;
8pub mod import_authority;
9pub mod mint_root_association;
10pub mod native_witness;
11pub mod passkey_mint_grant;
12pub mod password_owner;
13pub mod provider_v2;
14pub mod request_proof;
15pub mod signing;
16pub mod timeline_upload;
17mod transport;
18pub mod treadle;
19pub mod v2;
20pub mod witness_trust;
21pub mod writer_authority;
22
23/// Fixed native HYBRID binding/closure fixture, shared with TypeScript.
24pub const NATIVE_HOST_WITNESS_V1_FIXTURE_JSON: &str =
25    include_str!("../tests/fixtures/native-host-witness-v1.json");
26
27/// The single fixed cross-language HYBRID import wire/signature/history fixture.
28pub const IMPORT_AUTHORITY_HOST_WITNESS_V1_FIXTURE_JSON: &str =
29    include_str!("../tests/fixtures/import-authority-host-witness-v1.json");
30
31pub use failure::{
32    ACCOUNT_BILLING_LOCK_POLICY_ID, account_billing_lock_error_detail,
33    account_billing_lock_from_error_detail, decode_account_billing_lock_error_detail,
34    encode_account_billing_lock_error_detail,
35};
36pub use transport::{
37    ALL_METHODS, HOSTED_ALPN_V1, MethodDescriptor, MethodRoute, PROVIDER_ALPN_V1,
38    RequestMetadataError, RoutedCall, StreamingShape, human_verification_challenge,
39    human_verification_error_detail, method_descriptor,
40};
41
42include!(concat!(
43    env!("OUT_DIR"),
44    "/heddle_api_attachment_authorization.rs"
45));
46
47/// Cross-product hosted-call framing and typed-failure fixture.
48pub const HOSTED_CALL_V1_FIXTURE_JSON: &str = include_str!("../tests/fixtures/hosted-call-v1.json");
49/// Cross-product canonical unary-signing fixture.
50pub const UNARY_SIGNING_V1_FIXTURE_JSON: &str =
51    include_str!("../tests/fixtures/unary-signing-v1.json");
52/// Cross-product canonical GrantEnvelope v2 payload and rejection fixtures.
53pub const GRANT_ENVELOPE_V2_FIXTURE_JSON: &str =
54    include_str!("../tests/fixtures/grant-envelope-v2.json");
55/// Cross-product endpoint-descriptor and relay-admission signing fixture.
56pub const TRANSPORT_BOOTSTRAP_V1_FIXTURE_JSON: &str =
57    include_str!("../tests/fixtures/transport-bootstrap-v1.json");
58
59/// Generic helper default. V2 RPC-specific defaults may differ.
60pub const DEFAULT_PAGE_SIZE: u32 = 50;
61/// Generic helper cap. V2 RPC-specific bounds (such as ListPaths) take precedence.
62pub const MAX_PAGE_SIZE: u32 = 200;
63
64/// Applies generic paging defaults; use each v2 RPC's own bounds when specified.
65pub const fn normalize_page_size(requested: u32) -> u32 {
66    if requested == 0 {
67        DEFAULT_PAGE_SIZE
68    } else if requested > MAX_PAGE_SIZE {
69        MAX_PAGE_SIZE
70    } else {
71        requested
72    }
73}
74
75/// Heddle API protobuf packages.
76pub mod heddle {
77    /// Neutral public API contract.
78    pub mod api {
79        /// Shared foundational types used by every versioned API package.
80        pub mod common {
81            include!(concat!(env!("OUT_DIR"), "/heddle.api.common.rs"));
82        }
83        /// Frozen Thread-oriented contract; endpoint support is negotiated.
84        pub mod v1alpha2 {
85            include!(concat!(env!("OUT_DIR"), "/heddle.api.v1alpha2.rs"));
86        }
87    }
88}
89
90impl heddle::api::common::ErrorReason {
91    /// Returns whether callers may retry without first correcting the request.
92    pub fn retryable(&self) -> bool {
93        matches!(
94            self,
95            Self::RateLimited | Self::QuotaExceeded | Self::Transient
96        )
97    }
98}
99
100/// Compiled protobuf descriptor set for reflection and contract inspection.
101#[cfg(feature = "reflection")]
102pub const FILE_DESCRIPTOR_SET: &[u8] =
103    include_bytes!(concat!(env!("OUT_DIR"), "/heddle_api_descriptor.bin"));
104
105/// Errors returned while constructing fixed-width API identifiers.
106#[derive(Debug, Clone, PartialEq, Eq)]
107pub struct InvalidIdentifierLength {
108    kind: &'static str,
109    expected: usize,
110    actual: usize,
111}
112
113impl std::fmt::Display for InvalidIdentifierLength {
114    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
115        write!(
116            formatter,
117            "{} requires exactly {} bytes, got {}",
118            self.kind, self.expected, self.actual
119        )
120    }
121}
122
123impl std::error::Error for InvalidIdentifierLength {}
124
125impl heddle::api::common::StateId {
126    /// Constructs a physical state identifier from exactly 32 bytes.
127    pub fn from_bytes(value: impl AsRef<[u8]>) -> Result<Self, InvalidIdentifierLength> {
128        fixed_width("StateId", 32, value.as_ref()).map(|value| Self { value })
129    }
130}
131
132impl heddle::api::common::ChangeId {
133    /// Constructs a rewrite-stable change identifier from exactly 16 bytes.
134    pub fn from_bytes(value: impl AsRef<[u8]>) -> Result<Self, InvalidIdentifierLength> {
135        fixed_width("ChangeId", 16, value.as_ref()).map(|value| Self { value })
136    }
137}
138
139impl heddle::api::common::OperationId {
140    /// Constructs a durable operation identifier from exactly 16 bytes.
141    pub fn from_bytes(value: impl AsRef<[u8]>) -> Result<Self, InvalidIdentifierLength> {
142        fixed_width("OperationId", 16, value.as_ref()).map(|value| Self { value })
143    }
144}
145
146impl heddle::api::common::OperationBatchId {
147    /// Constructs a durable operation-batch identifier from exactly 16 bytes.
148    pub fn from_bytes(value: impl AsRef<[u8]>) -> Result<Self, InvalidIdentifierLength> {
149        fixed_width("OperationBatchId", 16, value.as_ref()).map(|value| Self { value })
150    }
151}
152
153impl heddle::api::common::GitObjectId {
154    /// Constructs and validates a Git object identifier for its hash algorithm.
155    pub fn from_digest(
156        algorithm: heddle::api::common::GitObjectAlgorithm,
157        digest: impl AsRef<[u8]>,
158    ) -> Result<Self, InvalidIdentifierLength> {
159        let expected = match algorithm {
160            heddle::api::common::GitObjectAlgorithm::Sha1 => 20,
161            heddle::api::common::GitObjectAlgorithm::Sha256 => 32,
162            heddle::api::common::GitObjectAlgorithm::Unspecified => 0,
163        };
164        fixed_width("GitObjectId", expected, digest.as_ref()).map(|digest| Self {
165            algorithm: algorithm as i32,
166            digest,
167        })
168    }
169}
170
171fn fixed_width(
172    kind: &'static str,
173    expected: usize,
174    value: &[u8],
175) -> Result<Vec<u8>, InvalidIdentifierLength> {
176    if value.len() != expected {
177        return Err(InvalidIdentifierLength {
178            kind,
179            expected,
180            actual: value.len(),
181        });
182    }
183    Ok(value.to_vec())
184}
185
186#[cfg(test)]
187mod tests {
188    use super::heddle::api::common::{
189        ChangeId, ErrorReason, GitObjectAlgorithm, GitObjectId, OperationBatchId, OperationId,
190        StateId,
191    };
192
193    #[test]
194    fn fixed_width_identifiers_reject_ambiguous_bytes() {
195        assert!(StateId::from_bytes([0; 32]).is_ok());
196        assert!(StateId::from_bytes([0; 31]).is_err());
197        assert!(ChangeId::from_bytes([0; 16]).is_ok());
198        assert!(ChangeId::from_bytes([0; 17]).is_err());
199        assert!(OperationId::from_bytes([0; 16]).is_ok());
200        assert!(OperationId::from_bytes([0; 15]).is_err());
201        assert!(OperationBatchId::from_bytes([0; 16]).is_ok());
202        assert!(OperationBatchId::from_bytes([0; 17]).is_err());
203        assert!(GitObjectId::from_digest(GitObjectAlgorithm::Sha1, [0; 20]).is_ok());
204        assert!(GitObjectId::from_digest(GitObjectAlgorithm::Sha256, [0; 20]).is_err());
205    }
206
207    #[test]
208    fn error_reason_retryability_is_derived_from_the_taxonomy() {
209        assert!(ErrorReason::RateLimited.retryable());
210        assert!(ErrorReason::QuotaExceeded.retryable());
211        assert!(ErrorReason::Transient.retryable());
212        assert!(!ErrorReason::CursorInvalid.retryable());
213        assert!(!ErrorReason::Internal.retryable());
214    }
215}