ddd_toolkit_core/domain/
vo.rs1use std::fmt::Debug;
2
3pub trait ValueObject: Clone + PartialEq + Debug + Send + Sync {}
20
21pub trait WrappedInner {
26 type Inner;
28}
29
30pub trait Wrapped:
80 ValueObject
81 + WrappedInner
82 + TryFrom<<Self as WrappedInner>::Inner, Error: std::error::Error + Send + Sync + 'static>
83{
84 fn as_inner(&self) -> &Self::Inner;
86 fn into_inner(self) -> Self::Inner;
88}
89
90#[cfg(test)]
91mod test {
92 use super::*;
93 use crate::domain::ValidationError;
94
95 #[derive(Clone, PartialEq, Debug)]
96 struct FooValue(String);
97
98 impl ValueObject for FooValue {}
99
100 impl WrappedInner for FooValue {
101 type Inner = String;
102 }
103
104 impl Wrapped for FooValue {
105 fn as_inner(&self) -> &Self::Inner {
106 &self.0
107 }
108
109 fn into_inner(self) -> Self::Inner {
110 self.0
111 }
112 }
113
114 impl TryFrom<String> for FooValue {
115 type Error = ValidationError;
116
117 fn try_from(value: String) -> Result<Self, Self::Error> {
118 if value.trim().is_empty() {
119 return Err(ValidationError::new("FooValue", "empty"));
120 }
121 Ok(Self(value))
122 }
123 }
124
125 #[test]
126 fn try_from_non_empty_string_succeeds() {
127 assert!(FooValue::try_from("value".to_string()).is_ok())
128 }
129
130 #[test]
131 fn as_inner_returns_reference_to_original_value() {
132 let foo = FooValue("value".to_string());
133 assert_eq!(foo.as_inner(), "value")
134 }
135
136 #[test]
137 fn into_inner_returns_owned_original_value() {
138 let foo = FooValue("value".to_string());
139 assert_eq!(foo.into_inner(), "value".to_string())
140 }
141
142 #[test]
143 fn round_trip_preserves_value() {
144 let foo = FooValue::try_from("value".to_string()).unwrap();
145 assert_eq!(foo.into_inner(), "value".to_string())
146 }
147
148 #[test]
149 fn clone_produces_equal_value() {
150 let foo = FooValue("value".to_string());
151 let bar = foo.clone();
152
153 assert_eq!(foo, bar);
154 assert_eq!(foo.into_inner(), bar.into_inner());
155 }
156
157 #[test]
158 fn eq_returns_true_for_same_inner_value() {
159 let foo = FooValue("foo".to_string());
160 let bar = FooValue("foo".to_string());
161
162 assert_eq!(foo, bar);
163 assert_eq!(foo.into_inner(), bar.into_inner());
164 }
165
166 #[test]
167 fn eq_returns_false_for_different_inner_value() {
168 let foo = FooValue("foo".to_string());
169 let bar = FooValue("bar".to_string());
170
171 assert_ne!(foo, bar);
172 assert_ne!(foo.into_inner(), bar.into_inner());
173 }
174
175 #[test]
176 fn try_from_empty_string_returns_err() {
177 assert!(FooValue::try_from("".to_string()).is_err());
178 assert!(FooValue::try_from(" ".to_string()).is_err());
179 }
180
181 #[test]
182 fn try_from_empty_string_error_has_expected_type_name() {
183 let err = FooValue::try_from("".to_string()).unwrap_err();
184 assert_eq!(err.type_name, "FooValue")
185 }
186
187 #[test]
188 fn try_from_empty_string_error_has_expected_reason() {
189 let err = FooValue::try_from("".to_string()).unwrap_err();
190 assert_eq!(err.reason, "empty")
191 }
192
193 #[test]
194 fn try_from_empty_string_error_display_message() {
195 let err = FooValue::try_from("".to_string()).unwrap_err();
196 assert_eq!(format!("{err}"), "invalid FooValue: empty")
197 }
198}