1use anyhow::{Result, bail};
9use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Error as DeError};
10use std::path::Path;
11
12pub fn validate_non_empty(value: &str, field_name: &str) -> Result<()> {
14 if value.trim().is_empty() {
15 bail!("{field_name} cannot be empty");
16 }
17 Ok(())
18}
19
20pub fn validate_non_empty_string(value: String, field_name: &str) -> Result<String> {
22 if value.trim().is_empty() {
23 bail!("{field_name} cannot be empty");
24 }
25 Ok(value)
26}
27
28pub fn validate_optional_non_empty(value: &Option<String>, field_name: &str) -> Result<()> {
30 if let Some(v) = value {
31 validate_non_empty(v, field_name)?;
32 }
33 Ok(())
34}
35
36pub fn validate_non_empty_collection<T>(collection: &[T], field_name: &str) -> Result<()> {
38 if collection.is_empty() {
39 bail!("{field_name} collection cannot be empty");
40 }
41 Ok(())
42}
43
44pub fn validate_all_non_empty(values: &[String], field_name: &str) -> Result<()> {
46 for (i, value) in values.iter().enumerate() {
47 if value.trim().is_empty() {
48 bail!("{field_name}[{i}] cannot be empty");
49 }
50 }
51 Ok(())
52}
53
54pub fn validate_path_exists(path: &Path, field_name: &str) -> Result<()> {
56 if !path.exists() {
57 bail!("{} path does not exist: {}", field_name, path.display());
58 }
59 Ok(())
60}
61
62pub fn validate_is_file(path: &Path, field_name: &str) -> Result<()> {
64 validate_path_exists(path, field_name)?;
65 if !path.is_file() {
66 bail!("{} is not a file: {}", field_name, path.display());
67 }
68 Ok(())
69}
70
71pub fn validate_is_directory(path: &Path, field_name: &str) -> Result<()> {
73 validate_path_exists(path, field_name)?;
74 if !path.is_dir() {
75 bail!("{} is not a directory: {}", field_name, path.display());
76 }
77 Ok(())
78}
79
80pub fn validate_url_format(url: &str, field_name: &str) -> Result<()> {
82 if !url.starts_with("http://") && !url.starts_with("https://") {
83 bail!("{field_name} must be a valid URL starting with http:// or https://");
84 }
85 Ok(())
86}
87
88pub fn validate_identifier(id: &str, field_name: &str) -> Result<()> {
90 if id.is_empty() {
91 bail!("{field_name} cannot be empty");
92 }
93 if !id.chars().all(|c| c.is_alphanumeric() || c == '_' || c == '-') {
94 bail!("{field_name} must be alphanumeric (can include _ or -)");
95 }
96 Ok(())
97}
98
99#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
115pub struct NonEmptyString(String);
116
117impl NonEmptyString {
118 pub fn as_str(&self) -> &str {
119 &self.0
120 }
121
122 pub fn into_inner(self) -> String {
123 self.0
124 }
125}
126
127impl std::ops::Deref for NonEmptyString {
128 type Target = str;
129 fn deref(&self) -> &Self::Target {
130 &self.0
131 }
132}
133
134impl std::borrow::Borrow<str> for NonEmptyString {
135 fn borrow(&self) -> &str {
136 &self.0
137 }
138}
139
140impl AsRef<str> for NonEmptyString {
141 fn as_ref(&self) -> &str {
142 &self.0
143 }
144}
145
146impl std::fmt::Display for NonEmptyString {
147 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
148 self.0.fmt(f)
149 }
150}
151
152impl TryFrom<String> for NonEmptyString {
153 type Error = String;
154
155 fn try_from(value: String) -> Result<Self, Self::Error> {
156 if value.trim().is_empty() {
157 Err("string must be non-empty".to_string())
158 } else {
159 Ok(Self(value))
160 }
161 }
162}
163
164impl TryFrom<&str> for NonEmptyString {
165 type Error = String;
166
167 fn try_from(value: &str) -> Result<Self, Self::Error> {
168 if value.trim().is_empty() {
169 Err("string must be non-empty".to_string())
170 } else {
171 Ok(Self(value.to_string()))
172 }
173 }
174}
175
176impl From<NonEmptyString> for String {
177 fn from(value: NonEmptyString) -> Self {
178 value.0
179 }
180}
181
182impl Serialize for NonEmptyString {
183 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
184 where
185 S: Serializer,
186 {
187 serializer.serialize_str(&self.0)
188 }
189}
190
191impl<'de> Deserialize<'de> for NonEmptyString {
192 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
193 where
194 D: Deserializer<'de>,
195 {
196 let raw = String::deserialize(deserializer)?;
197 if raw.trim().is_empty() {
198 return Err(D::Error::invalid_value(serde::de::Unexpected::Str(&raw), &"a non-empty string"));
199 }
200 Ok(Self(raw))
201 }
202}
203
204#[derive(Debug, Clone, PartialEq, Eq)]
206pub struct EmptyCollectionError;
207
208impl std::fmt::Display for EmptyCollectionError {
209 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
210 f.write_str("collection must contain at least one element")
211 }
212}
213
214impl std::error::Error for EmptyCollectionError {}
215
216#[derive(Debug, Clone, PartialEq, Eq)]
234pub struct NonEmptyVec<T> {
235 pub head: T,
237 pub tail: Vec<T>,
239}
240
241impl<T> NonEmptyVec<T> {
242 pub const fn new(head: T) -> Self {
244 Self { head, tail: Vec::new() }
245 }
246
247 pub const fn singleton(head: T) -> Self {
249 Self { head, tail: Vec::new() }
250 }
251
252 pub fn from_vec(vec: Vec<T>) -> Option<Self> {
257 let mut iter = vec.into_iter();
258 let head = iter.next()?;
259 Some(Self { head, tail: iter.collect() })
260 }
261
262 #[must_use]
264 pub const fn first(&self) -> &T {
265 &self.head
266 }
267
268 #[must_use]
270 pub fn last(&self) -> &T {
271 self.tail.last().unwrap_or(&self.head)
272 }
273
274 #[must_use]
276 pub fn len(&self) -> usize {
277 self.tail.len().saturating_add(1)
278 }
279
280 #[must_use]
282 pub const fn is_empty(&self) -> bool {
283 false
284 }
285
286 pub fn iter(&self) -> impl Iterator<Item = &T> {
288 std::iter::once(&self.head).chain(self.tail.iter())
289 }
290
291 pub fn push(&mut self, value: T) {
293 self.tail.push(value);
294 }
295
296 #[must_use]
298 pub fn into_vec(self) -> Vec<T> {
299 let mut vec = Vec::with_capacity(self.tail.len().saturating_add(1));
300 vec.push(self.head);
301 vec.extend(self.tail);
302 vec
303 }
304}
305
306impl<T> TryFrom<Vec<T>> for NonEmptyVec<T> {
307 type Error = EmptyCollectionError;
308
309 fn try_from(value: Vec<T>) -> std::result::Result<Self, Self::Error> {
310 Self::from_vec(value).ok_or(EmptyCollectionError)
311 }
312}
313
314impl<T> From<NonEmptyVec<T>> for Vec<T> {
315 fn from(value: NonEmptyVec<T>) -> Self {
316 value.into_vec()
317 }
318}
319
320impl<T: Serialize> Serialize for NonEmptyVec<T> {
321 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
322 where
323 S: Serializer,
324 {
325 serializer.collect_seq(self.iter())
326 }
327}
328
329impl<'de, T> Deserialize<'de> for NonEmptyVec<T>
330where
331 T: Deserialize<'de>,
332{
333 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
334 where
335 D: Deserializer<'de>,
336 {
337 let raw = Vec::<T>::deserialize(deserializer)?;
338 Self::from_vec(raw).ok_or_else(|| D::Error::invalid_length(0, &"a non-empty array"))
339 }
340}
341
342#[derive(Debug, Clone, Copy, PartialEq, Eq)]
358pub struct NonEmptySlice<'a, T> {
359 first: &'a T,
361 rest: &'a [T],
363}
364
365impl<'a, T> NonEmptySlice<'a, T> {
366 pub fn from_slice(slice: &'a [T]) -> Option<Self> {
370 let (first, rest) = slice.split_first()?;
371 Some(Self { first, rest })
372 }
373
374 #[must_use]
376 pub const fn first(&self) -> &'a T {
377 self.first
378 }
379
380 #[must_use]
382 pub fn last(&self) -> &'a T {
383 self.rest.last().unwrap_or(self.first)
384 }
385
386 #[must_use]
388 pub fn len(&self) -> usize {
389 self.rest.len().saturating_add(1)
390 }
391
392 #[must_use]
394 pub const fn is_empty(&self) -> bool {
395 false
396 }
397
398 #[must_use]
403 pub const fn rest(&self) -> &'a [T] {
404 self.rest
405 }
406
407 pub fn iter(&self) -> impl Iterator<Item = &'a T> {
409 std::iter::once(self.first).chain(self.rest.iter())
410 }
411}
412
413#[cfg(test)]
414mod tests {
415 use super::*;
416
417 #[test]
418 fn test_validate_non_empty() {
419 assert!(validate_non_empty("test", "field").is_ok());
420 assert!(validate_non_empty("", "field").is_err());
421 assert!(validate_non_empty(" ", "field").is_err());
422 }
423
424 #[test]
425 fn test_validate_all_non_empty() {
426 assert!(validate_all_non_empty(&["a".to_string(), "b".to_string()], "field").is_ok());
427 assert!(validate_all_non_empty(&["a".to_string(), "".to_string()], "field").is_err());
428 assert!(validate_all_non_empty(&[], "field").is_ok());
429 }
430
431 #[test]
432 fn non_empty_string_accepts_valid() {
433 let s = NonEmptyString::try_from("hello").unwrap();
434 assert_eq!(s.as_str(), "hello");
435 assert_eq!(s.len(), 5);
436 }
437
438 #[test]
439 fn non_empty_string_rejects_empty() {
440 assert!(NonEmptyString::try_from("").is_err());
441 assert!(NonEmptyString::try_from(" ").is_err());
442 assert!(NonEmptyString::try_from("\t\n").is_err());
443 }
444
445 #[test]
446 fn non_empty_string_from_owned() {
447 let s = NonEmptyString::try_from("test".to_string()).unwrap();
448 assert_eq!(s.into_inner(), "test");
449 }
450
451 #[test]
452 fn non_empty_string_deref() {
453 let s = NonEmptyString::try_from("hello").unwrap();
454 assert!(s.starts_with("hel"));
455 assert_eq!(&*s, "hello");
456 }
457
458 #[test]
459 fn non_empty_string_serde_roundtrip_and_rejection() {
460 let parsed = NonEmptyString::try_from("hello").unwrap();
461 let json = serde_json::to_string(&parsed).unwrap();
462 assert_eq!(json, "\"hello\"");
463 let back: NonEmptyString = serde_json::from_str(&json).unwrap();
464 assert_eq!(back, parsed);
465
466 assert!(serde_json::from_str::<NonEmptyString>("\"\"").is_err());
467 assert!(serde_json::from_str::<NonEmptyString>("\" \"").is_err());
468 let spaced: NonEmptyString = serde_json::from_str("\" hello \"").unwrap();
469 assert_eq!(spaced.as_str(), " hello ");
470 }
471
472 #[test]
473 fn non_empty_vec_preserves_order_asymmetric() {
474 let forward = NonEmptyVec::from_vec(vec!["a", "b"]).unwrap();
475 let backward = NonEmptyVec::from_vec(vec!["b", "a"]).unwrap();
476 assert_eq!(forward.first(), &"a");
477 assert_eq!(backward.first(), &"b");
478 assert_ne!(forward, backward);
479 assert_eq!(forward.into_vec(), vec!["a", "b"]);
480 assert_eq!(backward.into_vec(), vec!["b", "a"]);
481 }
482
483 #[test]
484 fn non_empty_vec_singleton_and_empty_boundary() {
485 assert!(NonEmptyVec::<String>::from_vec(Vec::new()).is_none());
486 let singleton = NonEmptyVec::singleton("only".to_string());
487 assert_eq!(singleton.first(), "only");
488 assert_eq!(singleton.last(), "only");
489 assert_eq!(singleton.len(), 1);
490 assert!(!singleton.is_empty());
491 assert_eq!(singleton.into_vec(), vec!["only".to_string()]);
492 }
493
494 #[test]
495 fn non_empty_vec_try_from_reports_typed_error() {
496 let parsed = NonEmptyVec::try_from(vec![1, 2, 3]).unwrap();
497 assert_eq!(parsed.first(), &1);
498 assert_eq!(parsed.last(), &3);
499 assert_eq!(parsed.len(), 3);
500 let err = NonEmptyVec::<i32>::try_from(Vec::new()).unwrap_err();
501 assert_eq!(err, EmptyCollectionError);
502 assert_eq!(err.to_string(), "collection must contain at least one element");
503 }
504
505 #[test]
506 fn non_empty_vec_iter_push_and_serde() {
507 let mut parsed = NonEmptyVec::new("x".to_string());
508 parsed.push("y".to_string());
509 let collected: Vec<&String> = parsed.iter().collect();
510 assert_eq!(collected, vec!["x", "y"]);
511 assert_eq!(parsed.len(), 2);
512
513 let json = serde_json::to_string(&parsed).unwrap();
514 assert_eq!(json, "[\"x\",\"y\"]");
515 let back: NonEmptyVec<String> = serde_json::from_str(&json).unwrap();
516 assert_eq!(back, parsed);
517 assert!(serde_json::from_str::<NonEmptyVec<String>>("[]").is_err());
518 }
519
520 #[test]
521 fn non_empty_slice_parses_once_then_first_is_infallible() {
522 let forward = vec!["a", "b"];
523 let backward = vec!["b", "a"];
524 let parsed_forward = NonEmptySlice::from_slice(&forward).unwrap();
525 let parsed_backward = NonEmptySlice::from_slice(&backward).unwrap();
526 assert_eq!(parsed_forward.first(), &"a");
527 assert_eq!(parsed_backward.first(), &"b");
528 assert_ne!(parsed_forward, parsed_backward);
529 assert_eq!(parsed_forward.len(), 2);
530 assert!(!parsed_forward.is_empty());
531
532 let empty: Vec<String> = Vec::new();
533 assert!(NonEmptySlice::from_slice(&empty).is_none());
534
535 let singleton = vec!["only"];
536 let parsed_single = NonEmptySlice::from_slice(&singleton).unwrap();
537 assert_eq!(parsed_single.first(), &"only");
538 assert_eq!(parsed_single.last(), &"only");
539 assert_eq!(parsed_single.rest(), &[] as &[&str]);
540 let collected: Vec<&&str> = parsed_forward.iter().collect();
541 assert_eq!(collected, vec![&"a", &"b"]);
542 assert_eq!(parsed_forward.rest(), &["b"] as &[&str]);
543 }
544}