1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
/// Core trait for all value objects in arvo.
///
/// A value object is an immutable, validated wrapper around a raw value.
/// It guarantees that once constructed, the inner value always satisfies
/// the domain rules defined in [`ValueObject::new`].
///
/// # Type parameters
///
/// - `Input` — the type accepted by [`new`](ValueObject::new).
/// For simple types this is the raw primitive (e.g. `String`).
/// For composite types this is a dedicated input struct.
/// - `Output` — the type returned by [`value`](ValueObject::value).
/// For simple types `Input` and `Output` are the same.
/// For composite types `Output` is the canonical representation
/// (e.g. an E.164 string for a phone number).
/// - `Error` — the error returned when validation fails.
///
/// # Simple type example
///
/// ```rust,ignore
/// use arvo::traits::ValueObject;
/// use arvo::errors::ValidationError;
///
/// pub type PercentageInput = f64;
/// pub type PercentageOutput = f64;
///
/// pub struct Percentage(f64);
///
/// impl ValueObject for Percentage {
/// type Input = PercentageInput;
/// type Output = PercentageOutput;
/// type Error = ValidationError;
///
/// fn new(value: f64) -> Result<Self, ValidationError> {
/// if !(0.0..=100.0).contains(&value) {
/// return Err(ValidationError::OutOfRange {
/// type_name: "Percentage",
/// min: "0".into(),
/// max: "100".into(),
/// actual: value.to_string(),
/// });
/// }
/// Ok(Self(value))
/// }
///
/// fn value(&self) -> &f64 { &self.0 }
/// fn into_inner(self) -> f64 { self.0 }
/// }
/// ```
///
/// # Composite type example
///
/// ```rust,ignore
/// use arvo::traits::ValueObject;
/// use arvo::errors::ValidationError;
///
/// pub struct PhoneNumberInput {
/// pub country_code: CountryCode,
/// pub number: String,
/// }
/// pub type PhoneNumberOutput = String; // canonical E.164: "+420123456789"
///
/// pub struct PhoneNumber {
/// input: PhoneNumberInput,
/// e164: String,
/// }
///
/// impl ValueObject for PhoneNumber {
/// type Input = PhoneNumberInput;
/// type Output = PhoneNumberOutput;
/// type Error = ValidationError;
///
/// fn new(value: PhoneNumberInput) -> Result<Self, ValidationError> { /* ... */ }
/// fn value(&self) -> &String { &self.e164 } // "+420123456789"
/// fn into_inner(self) -> PhoneNumberInput { self.input }
/// }
/// ```