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