1use reliakit_primitives::PrimitiveError;
30use reliakit_validate::{ValidateResult, ValidationError, Violation};
31
32use crate::primitives::JsonExtractErrorKind;
33use crate::JsonObject;
34
35pub struct JsonForm<'a> {
42 object: &'a JsonObject,
43 errors: ValidationError,
44}
45
46impl<'a> JsonForm<'a> {
47 pub fn new(object: &'a JsonObject) -> Self {
49 Self {
50 object,
51 errors: ValidationError::empty(),
52 }
53 }
54
55 pub fn str_field<T>(&mut self, field: &'static str) -> Option<T>
61 where
62 T: TryFrom<&'a str, Error = PrimitiveError>,
63 {
64 let object = self.object;
65 match object.get_str_as::<T>(field) {
66 Ok(value) => Some(value),
67 Err(error) => {
68 let message = match error.kind() {
69 JsonExtractErrorKind::Missing => "is required",
70 JsonExtractErrorKind::WrongType { .. } => "must be a string",
71 _ => "is invalid",
72 };
73 self.errors.push(Violation::with_field(field, message));
74 None
75 }
76 }
77 }
78
79 pub fn is_valid(&self) -> bool {
81 self.errors.is_empty()
82 }
83
84 pub fn errors(&self) -> &ValidationError {
86 &self.errors
87 }
88
89 pub fn finish(self) -> ValidateResult {
92 self.errors.finish()
93 }
94}
95
96#[cfg(all(test, feature = "validate"))]
97mod tests {
98 use super::JsonForm;
99 use crate::parse_str;
100 use reliakit_primitives::{Email, Hostname};
101
102 fn obj(input: &str) -> crate::JsonObject {
103 parse_str(input).unwrap().as_object().unwrap().clone()
104 }
105
106 #[test]
107 fn all_fields_valid_finishes_ok() {
108 let o = obj(r#"{ "email": "ops@example.com", "host": "api.example.com" }"#);
109 let mut form = JsonForm::new(&o);
110 let email: Option<Email> = form.str_field("email");
111 let host: Option<Hostname> = form.str_field("host");
112 assert_eq!(email.unwrap().as_str(), "ops@example.com");
113 assert_eq!(host.unwrap().as_str(), "api.example.com");
114 assert!(form.is_valid());
115 assert!(form.finish().is_ok());
116 }
117
118 #[test]
119 fn collects_every_failure_together() {
120 let o = obj(r#"{ "email": "nope", "host": 42 }"#);
121 let mut form = JsonForm::new(&o);
122 assert!(form.str_field::<Email>("email").is_none());
123 assert!(form.str_field::<Hostname>("host").is_none());
124 assert!(form.str_field::<Email>("missing").is_none());
125 assert!(!form.is_valid());
126
127 let errors = form.finish().unwrap_err();
128 let v = errors.violations();
129 assert_eq!(v.len(), 3);
130 assert_eq!(v[0].field, Some("email"));
131 assert_eq!(v[0].message, "is invalid");
132 assert_eq!(v[1].field, Some("host"));
133 assert_eq!(v[1].message, "must be a string");
134 assert_eq!(v[2].field, Some("missing"));
135 assert_eq!(v[2].message, "is required");
136 }
137
138 #[test]
139 fn errors_accessor_tracks_progress() {
140 let o = obj(r#"{ "email": "ops@example.com" }"#);
141 let mut form = JsonForm::new(&o);
142 let _: Option<Email> = form.str_field("email");
143 assert!(form.errors().is_empty());
144 let _: Option<Hostname> = form.str_field("host"); assert_eq!(form.errors().len(), 1);
146 }
147}