isonantic_rs/
lib.rs

1//! # ISONantic for Rust
2//!
3//! Type-safe validation and schema definitions for ISON format.
4//!
5//! ## Quick Start
6//!
7//! ```rust,no_run
8//! use isonantic_rs::prelude::*;
9//! use ison_rs::parse;
10//!
11//! // Define a schema
12//! let user_schema = table("users")
13//!     .field("id", int().required())
14//!     .field("name", string().min(1).max(100))
15//!     .field("email", string().email())
16//!     .field("active", boolean().default_value(true));
17//!
18//! // Parse ISON text
19//! let ison_text = r#"
20//! table.users
21//! id name email active
22//! 1 Alice alice@example.com true
23//! "#;
24//!
25//! // Parse and validate
26//! let doc = parse(ison_text).expect("Parse failed");
27//! let users = user_schema.validate(&doc).expect("Validation failed");
28//! ```
29
30use std::collections::HashMap;
31use std::fmt;
32
33pub mod schema;
34pub mod validators;
35
36pub use schema::*;
37pub use validators::*;
38
39/// Library version
40pub const VERSION: &str = "1.0.0";
41
42// =============================================================================
43// Error Types
44// =============================================================================
45
46/// Field validation error
47#[derive(Debug, Clone)]
48pub struct FieldError {
49    pub field: String,
50    pub message: String,
51    pub value: Option<String>,
52}
53
54impl fmt::Display for FieldError {
55    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
56        write!(f, "{}: {}", self.field, self.message)
57    }
58}
59
60/// Validation error containing one or more field errors
61#[derive(Debug, Clone)]
62pub struct ValidationError {
63    pub errors: Vec<FieldError>,
64}
65
66impl ValidationError {
67    pub fn new(errors: Vec<FieldError>) -> Self {
68        Self { errors }
69    }
70
71    pub fn single(field: impl Into<String>, message: impl Into<String>) -> Self {
72        Self {
73            errors: vec![FieldError {
74                field: field.into(),
75                message: message.into(),
76                value: None,
77            }],
78        }
79    }
80}
81
82impl fmt::Display for ValidationError {
83    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
84        write!(f, "Validation failed with {} error(s):", self.errors.len())?;
85        for error in &self.errors {
86            write!(f, "\n  - {}", error)?;
87        }
88        Ok(())
89    }
90}
91
92impl std::error::Error for ValidationError {}
93
94/// Result type for validation operations
95pub type Result<T> = std::result::Result<T, ValidationError>;
96
97// =============================================================================
98// Value Types
99// =============================================================================
100
101/// Validated ISON value
102#[derive(Debug, Clone, PartialEq)]
103pub enum ValidatedValue {
104    Null,
105    Bool(bool),
106    Int(i64),
107    Float(f64),
108    String(String),
109    Reference(ISONReference),
110    Array(Vec<ValidatedValue>),
111    Object(HashMap<String, ValidatedValue>),
112}
113
114impl ValidatedValue {
115    pub fn as_bool(&self) -> Option<bool> {
116        match self {
117            ValidatedValue::Bool(b) => Some(*b),
118            _ => None,
119        }
120    }
121
122    pub fn as_int(&self) -> Option<i64> {
123        match self {
124            ValidatedValue::Int(i) => Some(*i),
125            _ => None,
126        }
127    }
128
129    pub fn as_float(&self) -> Option<f64> {
130        match self {
131            ValidatedValue::Float(f) => Some(*f),
132            ValidatedValue::Int(i) => Some(*i as f64),
133            _ => None,
134        }
135    }
136
137    pub fn as_str(&self) -> Option<&str> {
138        match self {
139            ValidatedValue::String(s) => Some(s),
140            _ => None,
141        }
142    }
143
144    pub fn as_reference(&self) -> Option<&ISONReference> {
145        match self {
146            ValidatedValue::Reference(r) => Some(r),
147            _ => None,
148        }
149    }
150
151    pub fn is_null(&self) -> bool {
152        matches!(self, ValidatedValue::Null)
153    }
154}
155
156/// ISON reference
157#[derive(Debug, Clone, PartialEq)]
158pub struct ISONReference {
159    pub id: String,
160    pub ref_type: Option<String>,
161}
162
163impl ISONReference {
164    pub fn new(id: impl Into<String>) -> Self {
165        Self {
166            id: id.into(),
167            ref_type: None,
168        }
169    }
170
171    pub fn with_type(id: impl Into<String>, ref_type: impl Into<String>) -> Self {
172        Self {
173            id: id.into(),
174            ref_type: Some(ref_type.into()),
175        }
176    }
177
178    pub fn to_ison(&self) -> String {
179        match &self.ref_type {
180            Some(t) => format!(":{}:{}", t, self.id),
181            None => format!(":{}", self.id),
182        }
183    }
184}
185
186// =============================================================================
187// Validated Row/Table
188// =============================================================================
189
190/// A validated row of data
191#[derive(Debug, Clone)]
192pub struct ValidatedRow {
193    pub fields: HashMap<String, ValidatedValue>,
194}
195
196impl ValidatedRow {
197    pub fn new() -> Self {
198        Self {
199            fields: HashMap::new(),
200        }
201    }
202
203    pub fn get(&self, field: &str) -> Option<&ValidatedValue> {
204        self.fields.get(field)
205    }
206
207    pub fn get_string(&self, field: &str) -> Option<&str> {
208        self.fields.get(field).and_then(|v| v.as_str())
209    }
210
211    pub fn get_int(&self, field: &str) -> Option<i64> {
212        self.fields.get(field).and_then(|v| v.as_int())
213    }
214
215    pub fn get_bool(&self, field: &str) -> Option<bool> {
216        self.fields.get(field).and_then(|v| v.as_bool())
217    }
218}
219
220impl Default for ValidatedRow {
221    fn default() -> Self {
222        Self::new()
223    }
224}
225
226/// A validated table of rows
227#[derive(Debug, Clone)]
228pub struct ValidatedTable {
229    pub name: String,
230    pub rows: Vec<ValidatedRow>,
231}
232
233impl ValidatedTable {
234    pub fn new(name: impl Into<String>) -> Self {
235        Self {
236            name: name.into(),
237            rows: Vec::new(),
238        }
239    }
240
241    pub fn len(&self) -> usize {
242        self.rows.len()
243    }
244
245    pub fn is_empty(&self) -> bool {
246        self.rows.is_empty()
247    }
248
249    pub fn iter(&self) -> impl Iterator<Item = &ValidatedRow> {
250        self.rows.iter()
251    }
252}
253
254impl std::ops::Index<usize> for ValidatedTable {
255    type Output = ValidatedRow;
256
257    fn index(&self, index: usize) -> &Self::Output {
258        &self.rows[index]
259    }
260}
261
262// =============================================================================
263// Re-exports
264// =============================================================================
265
266pub mod prelude {
267    pub use crate::schema::*;
268    pub use crate::validators::*;
269    pub use crate::{
270        FieldError, ISONReference, Result, ValidatedRow, ValidatedTable,
271        ValidatedValue, ValidationError,
272    };
273}