1use domainstack::{Validate, ValidationError};
60use domainstack_envelope::IntoEnvelopeError;
61
62#[allow(clippy::result_large_err)]
63pub fn into_domain<T, Dto>(dto: Dto) -> Result<T, error_envelope::Error>
64where
65 T: TryFrom<Dto, Error = ValidationError>,
66{
67 T::try_from(dto).map_err(|e| e.into_envelope_error())
68}
69
70#[allow(clippy::result_large_err)]
71pub fn validate_dto<Dto>(dto: Dto) -> Result<Dto, error_envelope::Error>
72where
73 Dto: Validate,
74{
75 dto.validate()
76 .map(|_| dto)
77 .map_err(|e| e.into_envelope_error())
78}
79
80#[cfg(test)]
81mod tests {
82 use super::*;
83 use domainstack::prelude::*;
84 use domainstack::Validate;
85
86 #[derive(Debug, Clone, Validate)]
87 struct EmailDto {
88 #[validate(length(min = 5, max = 255))]
89 value: String,
90 }
91
92 #[derive(Debug, Clone)]
93 struct Email(#[allow(dead_code)] String);
94
95 impl Email {
96 #[allow(clippy::result_large_err)]
97 pub fn new(raw: String) -> Result<Self, ValidationError> {
98 let rule = rules::min_len(5).and(rules::max_len(255));
99 validate("email", raw.as_str(), &rule)?;
100 Ok(Self(raw))
101 }
102 }
103
104 impl TryFrom<EmailDto> for Email {
105 type Error = ValidationError;
106
107 fn try_from(dto: EmailDto) -> Result<Self, Self::Error> {
108 Email::new(dto.value)
109 }
110 }
111
112 #[test]
113 fn test_into_domain_valid() {
114 let dto = EmailDto {
115 value: "test@example.com".to_string(),
116 };
117
118 let result = into_domain::<Email, EmailDto>(dto);
119 assert!(result.is_ok());
120 }
121
122 #[test]
123 fn test_into_domain_invalid_too_short() {
124 let dto = EmailDto {
125 value: "abc".to_string(),
126 };
127
128 let result = into_domain::<Email, EmailDto>(dto);
129 assert!(result.is_err());
130
131 let err = result.unwrap_err();
132 assert_eq!(err.status, 400);
133 assert!(err.details.is_some());
134 }
135
136 #[test]
137 fn test_into_domain_invalid_too_long() {
138 let dto = EmailDto {
139 value: "a".repeat(300),
140 };
141
142 let result = into_domain::<Email, EmailDto>(dto);
143 assert!(result.is_err());
144
145 let err = result.unwrap_err();
146 assert_eq!(err.status, 400);
147 assert!(err.details.is_some());
148 }
149
150 #[test]
151 fn test_validate_dto_valid() {
152 let dto = EmailDto {
153 value: "test@example.com".to_string(),
154 };
155
156 let result = validate_dto(dto.clone());
157 assert!(result.is_ok());
158 assert_eq!(result.unwrap().value, dto.value);
159 }
160
161 #[test]
162 fn test_validate_dto_invalid() {
163 let dto = EmailDto {
164 value: "abc".to_string(),
165 };
166
167 let result = validate_dto(dto);
168 assert!(result.is_err());
169
170 let err = result.unwrap_err();
171 assert_eq!(err.status, 400);
172 assert!(err.details.is_some());
173 }
174
175 #[derive(Debug, Clone, Validate)]
176 struct UserDto {
177 #[validate(length(min = 2, max = 50))]
178 name: String,
179
180 #[validate(range(min = 18, max = 120))]
181 age: u8,
182 }
183
184 #[derive(Debug)]
185 #[allow(dead_code)]
186 struct User {
187 name: String,
188 age: u8,
189 }
190
191 impl TryFrom<UserDto> for User {
192 type Error = ValidationError;
193
194 fn try_from(dto: UserDto) -> Result<Self, Self::Error> {
195 let mut err = ValidationError::new();
196
197 let name_rule = rules::min_len(2).and(rules::max_len(50));
198 if let Err(e) = validate("name", dto.name.as_str(), &name_rule) {
199 err.extend(e);
200 }
201
202 let age_rule = rules::range(18, 120);
203 if let Err(e) = validate("age", &dto.age, &age_rule) {
204 err.extend(e);
205 }
206
207 if !err.is_empty() {
208 return Err(err);
209 }
210
211 Ok(Self {
212 name: dto.name,
213 age: dto.age,
214 })
215 }
216 }
217
218 #[test]
219 fn test_into_domain_user_valid() {
220 let dto = UserDto {
221 name: "Alice".to_string(),
222 age: 30,
223 };
224
225 let result = into_domain::<User, UserDto>(dto);
226 assert!(result.is_ok());
227 }
228
229 #[test]
230 fn test_into_domain_user_invalid_multiple_errors() {
231 let dto = UserDto {
232 name: "A".to_string(),
233 age: 200,
234 };
235
236 let result = into_domain::<User, UserDto>(dto);
237 assert!(result.is_err());
238
239 let err = result.unwrap_err();
240 assert_eq!(err.status, 400);
241
242 let details = err.details.as_ref().unwrap();
243 let fields = details.as_object().unwrap().get("fields").unwrap();
244 let fields_obj = fields.as_object().unwrap();
245
246 assert!(fields_obj.contains_key("name"));
247 assert!(fields_obj.contains_key("age"));
248 }
249
250 #[test]
251 fn test_validate_dto_user_valid() {
252 let dto = UserDto {
253 name: "Alice".to_string(),
254 age: 30,
255 };
256
257 let result = validate_dto(dto.clone());
258 assert!(result.is_ok());
259 let validated = result.unwrap();
260 assert_eq!(validated.name, dto.name);
261 assert_eq!(validated.age, dto.age);
262 }
263
264 #[test]
265 fn test_validate_dto_user_invalid() {
266 let dto = UserDto {
267 name: "A".to_string(),
268 age: 200,
269 };
270
271 let result = validate_dto(dto);
272 assert!(result.is_err());
273
274 let err = result.unwrap_err();
275 assert_eq!(err.status, 400);
276
277 let details = err.details.as_ref().unwrap();
278 let fields = details.as_object().unwrap().get("fields").unwrap();
279 let fields_obj = fields.as_object().unwrap();
280
281 assert!(fields_obj.contains_key("name"));
282 assert!(fields_obj.contains_key("age"));
283 }
284}