use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FieldSchema {
pub field_type: String,
pub description: Option<String>,
pub required: bool,
pub example: Option<serde_json::Value>,
pub format: Option<String>,
pub minimum: Option<f64>,
pub maximum: Option<f64>,
pub min_length: Option<usize>,
pub max_length: Option<usize>,
pub pattern: Option<String>,
pub enum_values: Option<Vec<serde_json::Value>>,
pub items: Option<Box<FieldSchema>>,
pub properties: Option<HashMap<String, FieldSchema>>,
}
impl FieldSchema {
pub fn string() -> Self {
Self {
field_type: "string".to_string(),
description: None,
required: false,
example: None,
format: None,
minimum: None,
maximum: None,
min_length: None,
max_length: None,
pattern: None,
enum_values: None,
items: None,
properties: None,
}
}
pub fn integer() -> Self {
Self {
field_type: "integer".to_string(),
description: None,
required: false,
example: None,
format: None,
minimum: None,
maximum: None,
min_length: None,
max_length: None,
pattern: None,
enum_values: None,
items: None,
properties: None,
}
}
pub fn number() -> Self {
Self {
field_type: "number".to_string(),
description: None,
required: false,
example: None,
format: None,
minimum: None,
maximum: None,
min_length: None,
max_length: None,
pattern: None,
enum_values: None,
items: None,
properties: None,
}
}
pub fn boolean() -> Self {
Self {
field_type: "boolean".to_string(),
description: None,
required: false,
example: None,
format: None,
minimum: None,
maximum: None,
min_length: None,
max_length: None,
pattern: None,
enum_values: None,
items: None,
properties: None,
}
}
pub fn array(items: FieldSchema) -> Self {
Self {
field_type: "array".to_string(),
description: None,
required: false,
example: None,
format: None,
minimum: None,
maximum: None,
min_length: None,
max_length: None,
pattern: None,
enum_values: None,
items: Some(Box::new(items)),
properties: None,
}
}
pub fn object() -> Self {
Self {
field_type: "object".to_string(),
description: None,
required: false,
example: None,
format: None,
minimum: None,
maximum: None,
min_length: None,
max_length: None,
pattern: None,
enum_values: None,
items: None,
properties: Some(HashMap::new()),
}
}
pub fn required(mut self) -> Self {
self.required = true;
self
}
pub fn with_description(mut self, description: impl Into<String>) -> Self {
self.description = Some(description.into());
self
}
pub fn with_example(mut self, example: serde_json::Value) -> Self {
self.example = Some(example);
self
}
pub fn with_format(mut self, format: impl Into<String>) -> Self {
self.format = Some(format.into());
self
}
pub fn with_minimum(mut self, min: f64) -> Self {
self.minimum = Some(min);
self
}
pub fn with_maximum(mut self, max: f64) -> Self {
self.maximum = Some(max);
self
}
pub fn with_min_length(mut self, min: usize) -> Self {
self.min_length = Some(min);
self
}
pub fn with_max_length(mut self, max: usize) -> Self {
self.max_length = Some(max);
self
}
pub fn with_pattern(mut self, pattern: impl Into<String>) -> Self {
self.pattern = Some(pattern.into());
self
}
pub fn with_enum(mut self, values: Vec<serde_json::Value>) -> Self {
self.enum_values = Some(values);
self
}
pub fn add_property(mut self, name: impl Into<String>, schema: FieldSchema) -> Self {
if let Some(ref mut props) = self.properties {
props.insert(name.into(), schema);
}
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RequestSchema {
pub content_type: String,
pub schema: ModelSchema,
pub examples: Option<HashMap<String, serde_json::Value>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ResponseSchema {
pub status_code: u16,
pub description: String,
pub content_type: String,
pub schema: ModelSchema,
pub examples: Option<HashMap<String, serde_json::Value>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ModelSchema {
pub name: String,
pub description: Option<String>,
pub fields: HashMap<String, FieldSchema>,
}
impl ModelSchema {
pub fn new(name: impl Into<String>) -> Self {
Self {
name: name.into(),
description: None,
fields: HashMap::new(),
}
}
pub fn with_description(mut self, description: impl Into<String>) -> Self {
self.description = Some(description.into());
self
}
pub fn add_field(mut self, name: impl Into<String>, schema: FieldSchema) -> Self {
self.fields.insert(name.into(), schema);
self
}
}
#[derive(Debug, Clone)]
pub struct ViewSetSchema {
pub name: String,
pub description: Option<String>,
pub model_schema: Option<ModelSchema>,
pub request_schemas: HashMap<String, RequestSchema>,
pub response_schemas: HashMap<String, Vec<ResponseSchema>>,
pub tags: Vec<String>,
}
impl ViewSetSchema {
pub fn new(name: impl Into<String>) -> Self {
Self {
name: name.into(),
description: None,
model_schema: None,
request_schemas: HashMap::new(),
response_schemas: HashMap::new(),
tags: Vec::new(),
}
}
pub fn with_description(mut self, description: impl Into<String>) -> Self {
self.description = Some(description.into());
self
}
pub fn with_model_schema(mut self, schema: ModelSchema) -> Self {
self.model_schema = Some(schema);
self
}
pub fn add_request_schema(mut self, action: impl Into<String>, schema: RequestSchema) -> Self {
self.request_schemas.insert(action.into(), schema);
self
}
pub fn add_response_schema(
mut self,
action: impl Into<String>,
schema: ResponseSchema,
) -> Self {
self.response_schemas
.entry(action.into())
.or_default()
.push(schema);
self
}
pub fn with_tags(mut self, tags: Vec<String>) -> Self {
self.tags = tags;
self
}
pub fn add_tag(mut self, tag: impl Into<String>) -> Self {
self.tags.push(tag.into());
self
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_field_schema_string() {
let schema = FieldSchema::string()
.with_description("Test field")
.with_format("email")
.required();
assert_eq!(schema.field_type, "string");
assert_eq!(schema.description, Some("Test field".to_string()));
assert_eq!(schema.format, Some("email".to_string()));
assert!(schema.required);
}
#[test]
fn test_field_schema_integer() {
let schema = FieldSchema::integer()
.with_minimum(0.0)
.with_maximum(100.0)
.required();
assert_eq!(schema.field_type, "integer");
assert_eq!(schema.minimum, Some(0.0));
assert_eq!(schema.maximum, Some(100.0));
assert!(schema.required);
}
#[test]
fn test_field_schema_array() {
let items = FieldSchema::string();
let schema = FieldSchema::array(items)
.with_min_length(1)
.with_max_length(10);
assert_eq!(schema.field_type, "array");
assert!(schema.items.is_some());
assert_eq!(schema.min_length, Some(1));
assert_eq!(schema.max_length, Some(10));
}
#[test]
fn test_field_schema_object() {
let schema = FieldSchema::object()
.add_property("id", FieldSchema::integer().required())
.add_property("name", FieldSchema::string().required());
assert_eq!(schema.field_type, "object");
assert!(schema.properties.is_some());
let props = schema.properties.unwrap();
assert_eq!(props.len(), 2);
assert!(props.contains_key("id"));
assert!(props.contains_key("name"));
}
#[test]
fn test_model_schema() {
let schema = ModelSchema::new("User")
.with_description("User model")
.add_field("id", FieldSchema::integer().required())
.add_field("name", FieldSchema::string().required())
.add_field("email", FieldSchema::string().with_format("email"));
assert_eq!(schema.name, "User");
assert_eq!(schema.description, Some("User model".to_string()));
assert_eq!(schema.fields.len(), 3);
assert!(schema.fields.get("id").unwrap().required);
assert!(schema.fields.get("name").unwrap().required);
assert!(!schema.fields.get("email").unwrap().required);
}
#[test]
fn test_viewset_schema() {
let model_schema = ModelSchema::new("User")
.add_field("id", FieldSchema::integer().required())
.add_field("name", FieldSchema::string().required());
let schema = ViewSetSchema::new("UserViewSet")
.with_description("User management")
.with_model_schema(model_schema)
.with_tags(vec!["users".to_string()])
.add_tag("auth".to_string());
assert_eq!(schema.name, "UserViewSet");
assert_eq!(schema.description, Some("User management".to_string()));
assert!(schema.model_schema.is_some());
assert_eq!(schema.tags.len(), 2);
}
#[test]
fn test_request_schema() {
let model_schema = ModelSchema::new("CreateUser")
.add_field("name", FieldSchema::string().required())
.add_field(
"email",
FieldSchema::string().with_format("email").required(),
);
let request = RequestSchema {
content_type: "application/json".to_string(),
schema: model_schema,
examples: None,
};
assert_eq!(request.content_type, "application/json");
assert_eq!(request.schema.name, "CreateUser");
assert_eq!(request.schema.fields.len(), 2);
}
#[test]
fn test_response_schema() {
let model_schema = ModelSchema::new("User")
.add_field("id", FieldSchema::integer().required())
.add_field("name", FieldSchema::string().required());
let response = ResponseSchema {
status_code: 200,
description: "Success".to_string(),
content_type: "application/json".to_string(),
schema: model_schema,
examples: None,
};
assert_eq!(response.status_code, 200);
assert_eq!(response.description, "Success");
assert_eq!(response.schema.name, "User");
}
#[test]
fn test_field_schema_enum() {
let schema = FieldSchema::string().with_enum(vec![
serde_json::json!("active"),
serde_json::json!("inactive"),
serde_json::json!("pending"),
]);
assert!(schema.enum_values.is_some());
assert_eq!(schema.enum_values.unwrap().len(), 3);
}
#[test]
fn test_field_schema_pattern() {
let schema = FieldSchema::string()
.with_pattern("^[a-zA-Z0-9]+$")
.with_min_length(3)
.with_max_length(20);
assert_eq!(schema.pattern, Some("^[a-zA-Z0-9]+$".to_string()));
assert_eq!(schema.min_length, Some(3));
assert_eq!(schema.max_length, Some(20));
}
}