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 /// A required textual field was empty or whitespace-only.
16 #[error("field `{field}` must not be empty")]
17 EmptyField { field: &'static str },
18
19 /// A textual field exceeded its maximum allowed length.
20 #[error("field `{field}` exceeds maximum length: {actual} > {max}")]
21 FieldTooLong {
22 field: &'static str,
23 actual: usize,
24 max: usize,
25 },
26
27 /// A textual field contained characters outside the allowed set.
28 #[error("field `{field}` contains invalid characters")]
29 InvalidCharacters { field: &'static str },
30
31 /// A numeric value fell outside its allowed range.
32 #[error("value `{field}` out of range: {value} not in [{min}, {max}]")]
33 OutOfRange {
34 field: &'static str,
35 value: f64,
36 min: f64,
37 max: f64,
38 },
39
40 /// A numeric value that must be non-zero was zero.
41 #[error("value `{field}` must be non-zero")]
42 MustBeNonZero { field: &'static str },
43
44 /// A collection that must contain at least one element was empty.
45 #[error("collection `{field}` must contain at least one element")]
46 EmptyCollection { field: &'static str },
47
48 /// A state transition was attempted from an invalid state.
49 #[error("invalid state transition `{from}` -> `{to}`")]
50 InvalidTransition {
51 from: &'static str,
52 to: &'static str,
53 },
54
55 /// An aggregate rejected a command because its preconditions were
56 /// not met (e.g. registering an agent into a sealed council).
57 #[error("invariant violated: {reason}")]
58 InvariantViolated { reason: &'static str },
59
60 /// A lookup in a domain registry did not resolve.
61 #[error("not found: {what}")]
62 NotFound { what: &'static str },
63
64 /// A domain entity with the same identity already exists.
65 #[error("already exists: {what}")]
66 AlreadyExists { what: &'static str },
67
68 /// Somebody else changed this first.
69 ///
70 /// A distinct variant rather than an invariant violation, because
71 /// the two ask different things of a caller. An invariant that was
72 /// violated will be violated again by the same call; a conflict is
73 /// the expected outcome of two callers reaching the same thing at
74 /// once, and the answer is to reload and decide, not to give up.
75 ///
76 /// Collapsing them made a caller unable to tell "try again from
77 /// what is stored now" from "this can never work".
78 #[error("conflict: {what} was changed by someone else first")]
79 Conflict { what: &'static str },
80
81 /// No candidate satisfied the structured output contract.
82 #[error("no valid proposal satisfied output contract `{contract_id}`")]
83 NoValidProposal { contract_id: String },
84}