made_core/error.rs
1//! Typed domain errors.
2//!
3//! Pure domain errors only. Anything related to I/O, transport, or
4//! serialization belongs to the adapter layer.
5
6use thiserror::Error;
7
8/// All errors that the core domain can raise.
9///
10/// Variants are intentionally coarse-grained at the boundary: each
11/// variant names the invariant that was violated, not the primitive
12/// type involved.
13#[derive(Debug, Clone, PartialEq, Error)]
14pub enum DomainError {
15 #[error("lifecycle refused `{operation}` while ceremony is {phase:?}")]
16 LifecycleRefused {
17 operation: &'static str,
18 phase: crate::value_objects::CeremonyLifecyclePhase,
19 },
20 /// A required textual field was empty or whitespace-only.
21 #[error("field `{field}` must not be empty")]
22 EmptyField { field: &'static str },
23
24 /// A textual field exceeded its maximum allowed length.
25 #[error("field `{field}` exceeds maximum length: {actual} > {max}")]
26 FieldTooLong {
27 field: &'static str,
28 actual: usize,
29 max: usize,
30 },
31
32 /// A textual field contained characters outside the allowed set.
33 #[error("field `{field}` contains invalid characters")]
34 InvalidCharacters { field: &'static str },
35
36 /// A numeric value fell outside its allowed range.
37 #[error("value `{field}` out of range: {value} not in [{min}, {max}]")]
38 OutOfRange {
39 field: &'static str,
40 value: f64,
41 min: f64,
42 max: f64,
43 },
44
45 /// A numeric value that must be non-zero was zero.
46 #[error("value `{field}` must be non-zero")]
47 MustBeNonZero { field: &'static str },
48
49 /// A collection that must contain at least one element was empty.
50 #[error("collection `{field}` must contain at least one element")]
51 EmptyCollection { field: &'static str },
52
53 /// A state transition was attempted from an invalid state.
54 #[error("invalid state transition `{from}` -> `{to}`")]
55 InvalidTransition {
56 from: &'static str,
57 to: &'static str,
58 },
59
60 /// An aggregate rejected a command because its preconditions were
61 /// not met (e.g. registering an agent into a sealed council).
62 #[error("invariant violated: {reason}")]
63 InvariantViolated { reason: &'static str },
64
65 /// A lookup in a domain registry did not resolve.
66 #[error("not found: {what}")]
67 NotFound { what: &'static str },
68
69 /// A document the caller authored describes something that cannot
70 /// be built, and saying which part requires naming it.
71 ///
72 /// Distinct from the field-level complaints above: those name one
73 /// field that is wrong on its own, and this one names parts that
74 /// are each acceptable and do not fit together — a stage owned by
75 /// nobody at the table, a name that collides with a generated one.
76 /// The reason is owned rather than `&'static str` because the
77 /// elements at fault carry the caller's own names, and a defect
78 /// that cannot say which element it is about sends an author
79 /// looking through the whole document.
80 #[error("{reason}")]
81 InvalidDocument { reason: String },
82
83 /// A domain entity with the same identity already exists.
84 #[error("already exists: {what}")]
85 AlreadyExists { what: &'static str },
86
87 /// Somebody else changed this first.
88 ///
89 /// A distinct variant rather than an invariant violation, because
90 /// the two ask different things of a caller. An invariant that was
91 /// violated will be violated again by the same call; a conflict is
92 /// the expected outcome of two callers reaching the same thing at
93 /// once, and the answer is to reload and decide, not to give up.
94 ///
95 /// Collapsing them made a caller unable to tell "try again from
96 /// what is stored now" from "this can never work".
97 #[error("conflict: {what} was changed by someone else first")]
98 Conflict { what: &'static str },
99
100 /// No candidate satisfied the structured output contract.
101 #[error("no valid proposal satisfied output contract `{contract_id}`")]
102 NoValidProposal { contract_id: String },
103
104 /// A sealed ceremony event could not be read back as the type and
105 /// payload schema version its record names.
106 ///
107 /// Names both so an operator can tell which record and which reader
108 /// disagree: a version no reader exists for, a payload that is not
109 /// an event, or a payload tagged as a different type.
110 #[error("ceremony event `{event_type}` at schema version {version} cannot be read: {reason}")]
111 UnreadableCeremonyEvent {
112 event_type: &'static str,
113 version: u32,
114 reason: &'static str,
115 },
116}