1use serde::{Deserialize, Serialize};
7use std::collections::HashMap;
8
9#[derive(Debug, Clone, Serialize, Deserialize)]
11pub struct FieldSchema {
12 pub field_type: String,
14 pub description: Option<String>,
16 pub required: bool,
18 pub example: Option<serde_json::Value>,
20 pub format: Option<String>,
22 pub minimum: Option<f64>,
24 pub maximum: Option<f64>,
26 pub min_length: Option<usize>,
28 pub max_length: Option<usize>,
30 pub pattern: Option<String>,
32 pub enum_values: Option<Vec<serde_json::Value>>,
34 pub items: Option<Box<FieldSchema>>,
36 pub properties: Option<HashMap<String, FieldSchema>>,
38}
39
40impl FieldSchema {
41 pub fn string() -> Self {
56 Self {
57 field_type: "string".to_string(),
58 description: None,
59 required: false,
60 example: None,
61 format: None,
62 minimum: None,
63 maximum: None,
64 min_length: None,
65 max_length: None,
66 pattern: None,
67 enum_values: None,
68 items: None,
69 properties: None,
70 }
71 }
72
73 pub fn integer() -> Self {
75 Self {
76 field_type: "integer".to_string(),
77 description: None,
78 required: false,
79 example: None,
80 format: None,
81 minimum: None,
82 maximum: None,
83 min_length: None,
84 max_length: None,
85 pattern: None,
86 enum_values: None,
87 items: None,
88 properties: None,
89 }
90 }
91
92 pub fn number() -> Self {
94 Self {
95 field_type: "number".to_string(),
96 description: None,
97 required: false,
98 example: None,
99 format: None,
100 minimum: None,
101 maximum: None,
102 min_length: None,
103 max_length: None,
104 pattern: None,
105 enum_values: None,
106 items: None,
107 properties: None,
108 }
109 }
110
111 pub fn boolean() -> Self {
113 Self {
114 field_type: "boolean".to_string(),
115 description: None,
116 required: false,
117 example: None,
118 format: None,
119 minimum: None,
120 maximum: None,
121 min_length: None,
122 max_length: None,
123 pattern: None,
124 enum_values: None,
125 items: None,
126 properties: None,
127 }
128 }
129
130 pub fn array(items: FieldSchema) -> Self {
132 Self {
133 field_type: "array".to_string(),
134 description: None,
135 required: false,
136 example: None,
137 format: None,
138 minimum: None,
139 maximum: None,
140 min_length: None,
141 max_length: None,
142 pattern: None,
143 enum_values: None,
144 items: Some(Box::new(items)),
145 properties: None,
146 }
147 }
148
149 pub fn object() -> Self {
151 Self {
152 field_type: "object".to_string(),
153 description: None,
154 required: false,
155 example: None,
156 format: None,
157 minimum: None,
158 maximum: None,
159 min_length: None,
160 max_length: None,
161 pattern: None,
162 enum_values: None,
163 items: None,
164 properties: Some(HashMap::new()),
165 }
166 }
167
168 pub fn required(mut self) -> Self {
170 self.required = true;
171 self
172 }
173
174 pub fn with_description(mut self, description: impl Into<String>) -> Self {
176 self.description = Some(description.into());
177 self
178 }
179
180 pub fn with_example(mut self, example: serde_json::Value) -> Self {
182 self.example = Some(example);
183 self
184 }
185
186 pub fn with_format(mut self, format: impl Into<String>) -> Self {
188 self.format = Some(format.into());
189 self
190 }
191
192 pub fn with_minimum(mut self, min: f64) -> Self {
194 self.minimum = Some(min);
195 self
196 }
197
198 pub fn with_maximum(mut self, max: f64) -> Self {
200 self.maximum = Some(max);
201 self
202 }
203
204 pub fn with_min_length(mut self, min: usize) -> Self {
206 self.min_length = Some(min);
207 self
208 }
209
210 pub fn with_max_length(mut self, max: usize) -> Self {
212 self.max_length = Some(max);
213 self
214 }
215
216 pub fn with_pattern(mut self, pattern: impl Into<String>) -> Self {
218 self.pattern = Some(pattern.into());
219 self
220 }
221
222 pub fn with_enum(mut self, values: Vec<serde_json::Value>) -> Self {
224 self.enum_values = Some(values);
225 self
226 }
227
228 pub fn add_property(mut self, name: impl Into<String>, schema: FieldSchema) -> Self {
230 if let Some(ref mut props) = self.properties {
231 props.insert(name.into(), schema);
232 }
233 self
234 }
235}
236
237#[derive(Debug, Clone, Serialize, Deserialize)]
239pub struct RequestSchema {
240 pub content_type: String,
242 pub schema: ModelSchema,
244 pub examples: Option<HashMap<String, serde_json::Value>>,
246}
247
248#[derive(Debug, Clone, Serialize, Deserialize)]
250pub struct ResponseSchema {
251 pub status_code: u16,
253 pub description: String,
255 pub content_type: String,
257 pub schema: ModelSchema,
259 pub examples: Option<HashMap<String, serde_json::Value>>,
261}
262
263#[derive(Debug, Clone, Serialize, Deserialize)]
265pub struct ModelSchema {
266 pub name: String,
268 pub description: Option<String>,
270 pub fields: HashMap<String, FieldSchema>,
272}
273
274impl ModelSchema {
275 pub fn new(name: impl Into<String>) -> Self {
290 Self {
291 name: name.into(),
292 description: None,
293 fields: HashMap::new(),
294 }
295 }
296
297 pub fn with_description(mut self, description: impl Into<String>) -> Self {
299 self.description = Some(description.into());
300 self
301 }
302
303 pub fn add_field(mut self, name: impl Into<String>, schema: FieldSchema) -> Self {
305 self.fields.insert(name.into(), schema);
306 self
307 }
308}
309
310#[derive(Debug, Clone)]
312pub struct ViewSetSchema {
313 pub name: String,
315 pub description: Option<String>,
317 pub model_schema: Option<ModelSchema>,
319 pub request_schemas: HashMap<String, RequestSchema>,
321 pub response_schemas: HashMap<String, Vec<ResponseSchema>>,
323 pub tags: Vec<String>,
325}
326
327impl ViewSetSchema {
328 pub fn new(name: impl Into<String>) -> Self {
342 Self {
343 name: name.into(),
344 description: None,
345 model_schema: None,
346 request_schemas: HashMap::new(),
347 response_schemas: HashMap::new(),
348 tags: Vec::new(),
349 }
350 }
351
352 pub fn with_description(mut self, description: impl Into<String>) -> Self {
354 self.description = Some(description.into());
355 self
356 }
357
358 pub fn with_model_schema(mut self, schema: ModelSchema) -> Self {
360 self.model_schema = Some(schema);
361 self
362 }
363
364 pub fn add_request_schema(mut self, action: impl Into<String>, schema: RequestSchema) -> Self {
366 self.request_schemas.insert(action.into(), schema);
367 self
368 }
369
370 pub fn add_response_schema(
372 mut self,
373 action: impl Into<String>,
374 schema: ResponseSchema,
375 ) -> Self {
376 self.response_schemas
377 .entry(action.into())
378 .or_default()
379 .push(schema);
380 self
381 }
382
383 pub fn with_tags(mut self, tags: Vec<String>) -> Self {
385 self.tags = tags;
386 self
387 }
388
389 pub fn add_tag(mut self, tag: impl Into<String>) -> Self {
391 self.tags.push(tag.into());
392 self
393 }
394}
395
396#[cfg(test)]
397mod tests {
398 use super::*;
399
400 #[test]
401 fn test_field_schema_string() {
402 let schema = FieldSchema::string()
403 .with_description("Test field")
404 .with_format("email")
405 .required();
406
407 assert_eq!(schema.field_type, "string");
408 assert_eq!(schema.description, Some("Test field".to_string()));
409 assert_eq!(schema.format, Some("email".to_string()));
410 assert!(schema.required);
411 }
412
413 #[test]
414 fn test_field_schema_integer() {
415 let schema = FieldSchema::integer()
416 .with_minimum(0.0)
417 .with_maximum(100.0)
418 .required();
419
420 assert_eq!(schema.field_type, "integer");
421 assert_eq!(schema.minimum, Some(0.0));
422 assert_eq!(schema.maximum, Some(100.0));
423 assert!(schema.required);
424 }
425
426 #[test]
427 fn test_field_schema_array() {
428 let items = FieldSchema::string();
429 let schema = FieldSchema::array(items)
430 .with_min_length(1)
431 .with_max_length(10);
432
433 assert_eq!(schema.field_type, "array");
434 assert!(schema.items.is_some());
435 assert_eq!(schema.min_length, Some(1));
436 assert_eq!(schema.max_length, Some(10));
437 }
438
439 #[test]
440 fn test_field_schema_object() {
441 let schema = FieldSchema::object()
442 .add_property("id", FieldSchema::integer().required())
443 .add_property("name", FieldSchema::string().required());
444
445 assert_eq!(schema.field_type, "object");
446 assert!(schema.properties.is_some());
447 let props = schema.properties.unwrap();
448 assert_eq!(props.len(), 2);
449 assert!(props.contains_key("id"));
450 assert!(props.contains_key("name"));
451 }
452
453 #[test]
454 fn test_model_schema() {
455 let schema = ModelSchema::new("User")
456 .with_description("User model")
457 .add_field("id", FieldSchema::integer().required())
458 .add_field("name", FieldSchema::string().required())
459 .add_field("email", FieldSchema::string().with_format("email"));
460
461 assert_eq!(schema.name, "User");
462 assert_eq!(schema.description, Some("User model".to_string()));
463 assert_eq!(schema.fields.len(), 3);
464 assert!(schema.fields.get("id").unwrap().required);
465 assert!(schema.fields.get("name").unwrap().required);
466 assert!(!schema.fields.get("email").unwrap().required);
467 }
468
469 #[test]
470 fn test_viewset_schema() {
471 let model_schema = ModelSchema::new("User")
472 .add_field("id", FieldSchema::integer().required())
473 .add_field("name", FieldSchema::string().required());
474
475 let schema = ViewSetSchema::new("UserViewSet")
476 .with_description("User management")
477 .with_model_schema(model_schema)
478 .with_tags(vec!["users".to_string()])
479 .add_tag("auth".to_string());
480
481 assert_eq!(schema.name, "UserViewSet");
482 assert_eq!(schema.description, Some("User management".to_string()));
483 assert!(schema.model_schema.is_some());
484 assert_eq!(schema.tags.len(), 2);
485 }
486
487 #[test]
488 fn test_request_schema() {
489 let model_schema = ModelSchema::new("CreateUser")
490 .add_field("name", FieldSchema::string().required())
491 .add_field(
492 "email",
493 FieldSchema::string().with_format("email").required(),
494 );
495
496 let request = RequestSchema {
497 content_type: "application/json".to_string(),
498 schema: model_schema,
499 examples: None,
500 };
501
502 assert_eq!(request.content_type, "application/json");
503 assert_eq!(request.schema.name, "CreateUser");
504 assert_eq!(request.schema.fields.len(), 2);
505 }
506
507 #[test]
508 fn test_response_schema() {
509 let model_schema = ModelSchema::new("User")
510 .add_field("id", FieldSchema::integer().required())
511 .add_field("name", FieldSchema::string().required());
512
513 let response = ResponseSchema {
514 status_code: 200,
515 description: "Success".to_string(),
516 content_type: "application/json".to_string(),
517 schema: model_schema,
518 examples: None,
519 };
520
521 assert_eq!(response.status_code, 200);
522 assert_eq!(response.description, "Success");
523 assert_eq!(response.schema.name, "User");
524 }
525
526 #[test]
527 fn test_field_schema_enum() {
528 let schema = FieldSchema::string().with_enum(vec![
529 serde_json::json!("active"),
530 serde_json::json!("inactive"),
531 serde_json::json!("pending"),
532 ]);
533
534 assert!(schema.enum_values.is_some());
535 assert_eq!(schema.enum_values.unwrap().len(), 3);
536 }
537
538 #[test]
539 fn test_field_schema_pattern() {
540 let schema = FieldSchema::string()
541 .with_pattern("^[a-zA-Z0-9]+$")
542 .with_min_length(3)
543 .with_max_length(20);
544
545 assert_eq!(schema.pattern, Some("^[a-zA-Z0-9]+$".to_string()));
546 assert_eq!(schema.min_length, Some(3));
547 assert_eq!(schema.max_length, Some(20));
548 }
549}