use alloc::collections::BTreeMap;
use alloc::string::String;
use alloc::vec::Vec;
#[derive(Debug, Clone, PartialEq)]
pub enum SchemaType {
String,
Number,
Integer,
Float,
Boolean,
Null,
Array,
Object,
Any,
}
#[derive(Debug, Clone)]
pub struct PropertySchema {
pub schema_type: SchemaType,
pub required: bool,
pub description: 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<String>>,
pub properties: Option<BTreeMap<String, PropertySchema>>,
pub items: Option<Box<PropertySchema>>,
pub default: Option<String>,
}
impl PropertySchema {
pub fn new(schema_type: SchemaType) -> Self {
Self {
schema_type,
required: false,
description: None,
minimum: None,
maximum: None,
min_length: None,
max_length: None,
pattern: None,
enum_values: None,
properties: None,
items: None,
default: None,
}
}
pub fn required(mut self) -> Self {
self.required = true;
self
}
pub fn with_description(mut self, desc: impl Into<String>) -> Self {
self.description = Some(desc.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, len: usize) -> Self {
self.min_length = Some(len);
self
}
pub fn with_max_length(mut self, len: usize) -> Self {
self.max_length = Some(len);
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<String>) -> Self {
self.enum_values = Some(values);
self
}
pub fn with_properties(mut self, props: BTreeMap<String, PropertySchema>) -> Self {
self.properties = Some(props);
self
}
pub fn with_items(mut self, items: PropertySchema) -> Self {
self.items = Some(Box::new(items));
self
}
pub fn with_default(mut self, default: impl Into<String>) -> Self {
self.default = Some(default.into());
self
}
}
#[derive(Debug, Clone)]
pub struct ArraySchema {
pub items: PropertySchema,
pub min_items: Option<usize>,
pub max_items: Option<usize>,
pub unique_items: bool,
}
impl ArraySchema {
pub fn new(items: PropertySchema) -> Self {
Self {
items,
min_items: None,
max_items: None,
unique_items: false,
}
}
pub fn with_min_items(mut self, min: usize) -> Self {
self.min_items = Some(min);
self
}
pub fn with_max_items(mut self, max: usize) -> Self {
self.max_items = Some(max);
self
}
pub fn with_unique_items(mut self) -> Self {
self.unique_items = true;
self
}
}
#[derive(Debug, Clone)]
pub struct ObjectSchema {
pub properties: BTreeMap<String, PropertySchema>,
pub required: Vec<String>,
pub additional_properties: bool,
}
impl ObjectSchema {
pub fn new() -> Self {
Self {
properties: BTreeMap::new(),
required: Vec::new(),
additional_properties: true,
}
}
pub fn with_property(mut self, name: impl Into<String>, schema: PropertySchema) -> Self {
let name_str = name.into();
if schema.required {
self.required.push(name_str.clone());
}
self.properties.insert(name_str, schema);
self
}
pub fn no_additional_properties(mut self) -> Self {
self.additional_properties = false;
self
}
pub fn require(mut self, name: impl Into<String>) -> Self {
self.required.push(name.into());
self
}
}
impl Default for ObjectSchema {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct Schema {
pub root: PropertySchema,
pub title: Option<String>,
pub description: Option<String>,
}
impl Schema {
pub fn new(root: PropertySchema) -> Self {
Self {
root,
title: None,
description: None,
}
}
pub fn with_title(mut self, title: impl Into<String>) -> Self {
self.title = Some(title.into());
self
}
pub fn with_description(mut self, desc: impl Into<String>) -> Self {
self.description = Some(desc.into());
self
}
pub fn string() -> Self {
Self::new(PropertySchema::new(SchemaType::String))
}
pub fn number() -> Self {
Self::new(PropertySchema::new(SchemaType::Number))
}
pub fn integer() -> Self {
Self::new(PropertySchema::new(SchemaType::Integer))
}
pub fn boolean() -> Self {
Self::new(PropertySchema::new(SchemaType::Boolean))
}
pub fn array(items: PropertySchema) -> Self {
Self::new(PropertySchema::new(SchemaType::Array).with_items(items))
}
pub fn object(properties: BTreeMap<String, PropertySchema>) -> Self {
Self::new(PropertySchema::new(SchemaType::Object).with_properties(properties))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_property_schema_builder() {
let schema = PropertySchema::new(SchemaType::String)
.required()
.with_min_length(5)
.with_max_length(50)
.with_description("A test string");
assert_eq!(schema.schema_type, SchemaType::String);
assert!(schema.required);
assert_eq!(schema.min_length, Some(5));
assert_eq!(schema.max_length, Some(50));
assert_eq!(schema.description, Some("A test string".to_string()));
}
#[test]
fn test_array_schema() {
let array = ArraySchema::new(PropertySchema::new(SchemaType::Integer))
.with_min_items(1)
.with_max_items(10)
.with_unique_items();
assert_eq!(array.items.schema_type, SchemaType::Integer);
assert_eq!(array.min_items, Some(1));
assert_eq!(array.max_items, Some(10));
assert!(array.unique_items);
}
#[test]
fn test_object_schema() {
let obj = ObjectSchema::new()
.with_property("name", PropertySchema::new(SchemaType::String).required())
.with_property("age", PropertySchema::new(SchemaType::Integer))
.no_additional_properties();
assert_eq!(obj.properties.len(), 2);
assert_eq!(obj.required.len(), 1);
assert!(!obj.additional_properties);
}
#[test]
fn test_schema_builders() {
let string_schema = Schema::string();
assert!(matches!(string_schema.root.schema_type, SchemaType::String));
let number_schema = Schema::number();
assert!(matches!(number_schema.root.schema_type, SchemaType::Number));
let int_schema = Schema::integer();
assert!(matches!(int_schema.root.schema_type, SchemaType::Integer));
}
#[test]
fn test_schema_with_metadata() {
let schema = Schema::string()
.with_title("User Name")
.with_description("The name of the user");
assert_eq!(schema.title, Some("User Name".to_string()));
assert_eq!(schema.description, Some("The name of the user".to_string()));
}
}
#[cfg(test)]
mod additional_schema_tests {
use super::*;
#[test]
fn test_property_schema_default_values() {
let schema = PropertySchema::new(SchemaType::Boolean);
assert!(!schema.required);
assert!(schema.description.is_none());
assert!(schema.minimum.is_none());
assert!(schema.maximum.is_none());
assert!(schema.min_length.is_none());
assert!(schema.max_length.is_none());
assert!(schema.pattern.is_none());
assert!(schema.enum_values.is_none());
assert!(schema.properties.is_none());
assert!(schema.items.is_none());
assert!(schema.default.is_none());
}
#[test]
fn test_property_schema_with_enum() {
let schema = PropertySchema::new(SchemaType::String)
.with_enum(vec!["A".to_string(), "B".to_string()]);
assert_eq!(schema.enum_values, Some(vec!["A".to_string(), "B".to_string()]));
}
#[test]
fn test_property_schema_with_items() {
let item_schema = PropertySchema::new(SchemaType::Integer);
let schema = PropertySchema::new(SchemaType::Array).with_items(item_schema.clone());
assert!(schema.items.is_some());
assert_eq!(schema.items.as_ref().unwrap().schema_type, SchemaType::Integer);
}
#[test]
fn test_property_schema_with_properties() {
let mut props = BTreeMap::new();
props.insert("foo".to_string(), PropertySchema::new(SchemaType::String));
let schema = PropertySchema::new(SchemaType::Object).with_properties(props.clone());
assert!(schema.properties.is_some());
assert_eq!(schema.properties.as_ref().unwrap().len(), 1);
assert!(schema.properties.as_ref().unwrap().contains_key("foo"));
}
#[test]
fn test_array_schema_defaults() {
let schema = ArraySchema::new(PropertySchema::new(SchemaType::String));
assert!(schema.min_items.is_none());
assert!(schema.max_items.is_none());
assert!(!schema.unique_items);
}
#[test]
fn test_object_schema_defaults() {
let schema = ObjectSchema::new();
assert!(schema.properties.is_empty());
assert!(schema.required.is_empty());
assert!(schema.additional_properties);
}
#[test]
fn test_schema_title_and_description() {
let schema = Schema::new(PropertySchema::new(SchemaType::Null))
.with_title("Null type")
.with_description("A schema for null values");
assert_eq!(schema.title, Some("Null type".to_string()));
assert_eq!(schema.description, Some("A schema for null values".to_string()));
}
}