Skip to main content

exochain_sdk/
error.rs

1// Copyright 2026 Exochain Foundation
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at:
6//
7//     https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14//
15// SPDX-License-Identifier: Apache-2.0
16
17//! SDK error types.
18//!
19//! All SDK operations that can fail return [`ExoResult<T>`], which is an alias
20//! for [`std::result::Result<T, ExoError>`]. Each variant narrows the error to
21//! a specific subsystem to aid programmatic handling — callers can
22//! `match` on the variant rather than parsing the error string.
23//!
24//! # Examples
25//!
26//! ```
27//! use exochain_sdk::consent::BailmentBuilder;
28//! use exochain_sdk::error::ExoError;
29//! use exo_core::Did;
30//!
31//! let a = Did::new("did:exo:a").expect("valid");
32//! let b = Did::new("did:exo:b").expect("valid");
33//! let err = BailmentBuilder::new(a, b).build().unwrap_err();
34//! assert!(matches!(err, ExoError::Consent(_)));
35//! ```
36
37use thiserror::Error;
38
39/// Errors that can be produced by the SDK.
40///
41/// Each variant corresponds to a subsystem of the SDK. Callers should prefer
42/// matching on the variant over parsing the display string, which is intended
43/// for human consumption only.
44#[derive(Debug, Error)]
45pub enum ExoError {
46    /// An identity-related operation failed.
47    ///
48    /// Returned when DID derivation, key handling, or identity reconstruction
49    /// encounters an unexpected condition. In practice the SDK derives DIDs
50    /// deterministically, so this variant is rarely observed — it is
51    /// reserved for future identity flows that could validate or reject
52    /// caller-supplied material.
53    #[error("identity error: {0}")]
54    Identity(String),
55
56    /// A consent/bailment-related operation failed.
57    ///
58    /// Returned by [`crate::consent::BailmentBuilder::build`] when required
59    /// fields are missing (no scope, no duration) or invalid (empty scope,
60    /// zero-hour duration).
61    #[error("consent error: {0}")]
62    Consent(String),
63
64    /// A governance-related operation failed.
65    ///
66    /// Returned by [`crate::governance::DecisionBuilder::build`] for an
67    /// empty title, and by [`crate::governance::Decision::cast_vote`] when
68    /// the same voter tries to cast a second vote.
69    #[error("governance error: {0}")]
70    Governance(String),
71
72    /// An authority-chain operation failed.
73    ///
74    /// Returned by [`crate::authority::AuthorityChainBuilder::build`] for any
75    /// topology violation: an empty chain, a break in the grantor/grantee
76    /// sequence between consecutive links, or a terminal mismatch.
77    #[error("authority error: {0}")]
78    Authority(String),
79
80    /// The kernel denied an action.
81    ///
82    /// Reserved for flows that want to surface a kernel denial as a `Result`
83    /// error rather than a `KernelVerdict::Denied` value. The SDK's
84    /// [`crate::kernel::ConstitutionalKernel::adjudicate`] returns a verdict
85    /// directly; this variant exists so higher-level wrappers can lift it
86    /// into the error channel if they prefer.
87    #[error("kernel denied: {0}")]
88    KernelDenied(String),
89
90    /// The kernel escalated an action for review.
91    ///
92    /// Counterpart to [`ExoError::KernelDenied`] for the escalation outcome.
93    #[error("kernel escalated: {0}")]
94    KernelEscalated(String),
95
96    /// A cryptographic operation failed.
97    ///
98    /// Reserved for future crypto flows that could fail (e.g. signature
99    /// parsing of untrusted input). The current BLAKE3 and Ed25519 wrappers
100    /// in [`crate::crypto`] are infallible.
101    #[error("crypto error: {0}")]
102    Crypto(String),
103
104    /// A provided DID string is not valid.
105    ///
106    /// Returned when the SDK derives a DID whose method-specific string
107    /// fails [`exo_core::Did`] validation. In practice BLAKE3-derived hex
108    /// always satisfies the rules, so this variant is effectively
109    /// unreachable; it is retained for completeness.
110    #[error("invalid DID: {0}")]
111    InvalidDid(String),
112
113    /// Serialization or deserialization failed.
114    ///
115    /// Reserved for higher-level SDK flows that marshal wire payloads. The
116    /// primitive `Serialize`/`Deserialize` implementations on SDK types use
117    /// `serde_json::Error` directly; this variant lets downstream wrappers
118    /// homogenise their error channel.
119    #[error("serialization error: {0}")]
120    Serialization(String),
121}
122
123/// Convenience alias for `Result<T, ExoError>`.
124///
125/// Every fallible SDK function returns this type.
126///
127/// # Examples
128///
129/// ```
130/// use exochain_sdk::error::{ExoError, ExoResult};
131///
132/// fn pretend_work(ok: bool) -> ExoResult<u32> {
133///     if ok { Ok(42) } else { Err(ExoError::Governance("nope".into())) }
134/// }
135/// assert_eq!(pretend_work(true).unwrap(), 42);
136/// ```
137pub type ExoResult<T> = std::result::Result<T, ExoError>;