fraiseql_core/validation/
error_responses.rs1use serde::{Deserialize, Serialize};
7
8use crate::error::{FraiseQLError, ValidationFieldError};
9
10#[derive(Debug, Clone, Serialize, Deserialize)]
12pub struct GraphQLValidationError {
13 pub message: String,
15
16 #[serde(skip_serializing_if = "Option::is_none")]
18 pub path: Option<Vec<String>>,
19
20 #[serde(skip_serializing_if = "Option::is_none")]
22 pub extensions: Option<ValidationErrorExtensions>,
23}
24
25#[derive(Debug, Clone, Serialize, Deserialize)]
27pub struct ValidationErrorExtensions {
28 pub code: String,
30
31 pub rule_type: String,
33
34 #[serde(skip_serializing_if = "Option::is_none")]
36 pub field_path: Option<String>,
37
38 #[serde(skip_serializing_if = "Option::is_none")]
40 pub context: Option<serde_json::Value>,
41}
42
43#[derive(Debug, Clone, Serialize, Deserialize)]
45pub struct GraphQLValidationResponse {
46 pub errors: Vec<GraphQLValidationError>,
48
49 pub error_count: usize,
51}
52
53impl GraphQLValidationResponse {
54 pub fn new() -> Self {
56 Self {
57 errors: Vec::new(),
58 error_count: 0,
59 }
60 }
61
62 pub fn add_field_error(
64 &mut self,
65 field_error: ValidationFieldError,
66 context: Option<serde_json::Value>,
67 ) {
68 let path = Self::parse_path(&field_error.field);
69 let extensions = ValidationErrorExtensions {
70 code: "VALIDATION_FAILED".to_string(),
71 rule_type: field_error.rule_type,
72 field_path: Some(field_error.field.clone()),
73 context,
74 };
75
76 self.errors.push(GraphQLValidationError {
77 message: format!("Validation failed: {}", field_error.message),
78 path: Some(path),
79 extensions: Some(extensions),
80 });
81
82 self.error_count += 1;
83 }
84
85 pub fn add_errors(&mut self, errors: Vec<ValidationFieldError>) {
87 for error in errors {
88 self.add_field_error(error, None);
89 }
90 }
91
92 pub fn from_error(error: &FraiseQLError) -> Option<Self> {
94 if let FraiseQLError::Validation { message, path } = error {
95 let mut response = Self::new();
96 response.errors.push(GraphQLValidationError {
97 message: message.clone(),
98 path: path.as_ref().map(|p| Self::parse_path(p)),
99 extensions: Some(ValidationErrorExtensions {
100 code: "VALIDATION_FAILED".to_string(),
101 rule_type: "unknown".to_string(),
102 field_path: path.clone(),
103 context: None,
104 }),
105 });
106 response.error_count = 1;
107 Some(response)
108 } else {
109 None
110 }
111 }
112
113 fn parse_path(path: &str) -> Vec<String> {
115 path.split('.').map(|s| s.to_string()).collect()
116 }
117
118 pub fn has_errors(&self) -> bool {
120 !self.errors.is_empty()
121 }
122
123 pub fn to_graphql_errors(&self) -> serde_json::Value {
125 serde_json::json!({
126 "errors": self.errors,
127 "error_count": self.error_count
128 })
129 }
130}
131
132impl Default for GraphQLValidationResponse {
133 fn default() -> Self {
134 Self::new()
135 }
136}
137
138#[cfg(test)]
139mod tests {
140 use super::*;
141
142 #[test]
143 fn test_create_empty_response() {
144 let response = GraphQLValidationResponse::new();
145 assert!(!response.has_errors());
146 assert_eq!(response.error_count, 0);
147 }
148
149 #[test]
150 fn test_add_single_error() {
151 let mut response = GraphQLValidationResponse::new();
152 let field_error = ValidationFieldError::new("email", "pattern", "Invalid email format");
153 response.add_field_error(field_error, None);
154
155 assert!(response.has_errors());
156 assert_eq!(response.error_count, 1);
157 assert_eq!(response.errors[0].extensions.as_ref().unwrap().rule_type, "pattern");
158 }
159
160 #[test]
161 fn test_add_multiple_errors() {
162 let mut response = GraphQLValidationResponse::new();
163 let errors = vec![
164 ValidationFieldError::new("email", "pattern", "Invalid email"),
165 ValidationFieldError::new("phone", "pattern", "Invalid phone"),
166 ];
167 response.add_errors(errors);
168
169 assert_eq!(response.error_count, 2);
170 }
171
172 #[test]
173 fn test_path_parsing() {
174 let path = GraphQLValidationResponse::parse_path("user.email");
175 assert_eq!(path, vec!["user".to_string(), "email".to_string()]);
176
177 let path = GraphQLValidationResponse::parse_path("address.zipcode");
178 assert_eq!(path, vec!["address".to_string(), "zipcode".to_string()]);
179 }
180
181 #[test]
182 fn test_json_serialization() {
183 let mut response = GraphQLValidationResponse::new();
184 let field_error = ValidationFieldError::new("field1", "rule1", "Error message");
185 response.add_field_error(field_error, Some(serde_json::json!({"detail": "extra"})));
186
187 let json = response.to_graphql_errors();
188 assert!(json["error_count"].is_number());
189 assert!(json["errors"].is_array());
190 }
191
192 #[test]
193 fn test_from_fraiseql_error() {
194 let error = FraiseQLError::Validation {
195 message: "Validation failed".to_string(),
196 path: Some("user.email".to_string()),
197 };
198
199 let response = GraphQLValidationResponse::from_error(&error);
200 assert!(response.is_some());
201 let response = response.unwrap();
202 assert_eq!(response.error_count, 1);
203 }
204
205 #[test]
206 fn test_context_inclusion() {
207 let mut response = GraphQLValidationResponse::new();
208 let field_error = ValidationFieldError::new("password", "length", "Too short");
209 let context = serde_json::json!({"minimum_length": 12, "provided_length": 8});
210 response.add_field_error(field_error, Some(context));
211
212 assert!(response.errors[0].extensions.as_ref().unwrap().context.is_some());
213 }
214}