seam_server/validation/
mod.rs1mod check;
4mod compile;
5mod walk;
6
7use serde_json::Value;
8use std::env;
9
10use check::ValidateCtx;
11pub use compile::compile_schema;
12use walk::validate_walk;
13
14#[derive(Debug, Clone, PartialEq, Eq)]
16pub enum ValidationMode {
17 Dev,
19 Always,
21 Never,
23}
24
25pub fn should_validate(mode: &ValidationMode) -> bool {
27 match mode {
28 ValidationMode::Never => false,
29 ValidationMode::Always => true,
30 ValidationMode::Dev => {
31 if let Ok(v) = env::var("SEAM_ENV") {
32 return v != "production";
33 }
34 if let Ok(v) = env::var("NODE_ENV") {
35 return v != "production";
36 }
37 true
38 }
39 }
40}
41
42#[derive(Debug, Clone, PartialEq, Eq)]
44pub enum JtdType {
45 Boolean,
46 String,
47 Timestamp,
48 Int8,
49 Int16,
50 Int32,
51 Uint8,
52 Uint16,
53 Uint32,
54 Float32,
55 Float64,
56}
57
58#[derive(Debug, Clone)]
60pub enum CompiledSchema {
61 Empty,
62 Type(JtdType),
63 Enum(Vec<String>),
64 Elements(Box<CompiledSchema>),
65 Values(Box<CompiledSchema>),
66 Properties {
67 required: Vec<(String, CompiledSchema)>,
68 optional: Vec<(String, CompiledSchema)>,
69 allow_extra: bool,
70 },
71 Discriminator {
72 tag: String,
73 mapping: Vec<(String, CompiledSchema)>,
74 },
75 Nullable(Box<CompiledSchema>),
76}
77
78#[derive(Debug, Clone)]
80pub struct ValidationDetail {
81 pub path: String,
82 pub expected: String,
83 pub actual: String,
84}
85
86impl ValidationDetail {
87 pub fn to_json(&self) -> Value {
88 serde_json::json!({
89 "path": self.path,
90 "expected": self.expected,
91 "actual": self.actual,
92 })
93 }
94}
95
96const MAX_ERRORS: usize = 10;
97const MAX_DEPTH: usize = 32;
98
99pub fn validate_input(schema: &Value, data: &Value) -> Result<(), (String, Vec<ValidationDetail>)> {
102 let compiled = compile_schema(schema).map_err(|e| (e, vec![]))?;
103 validate_compiled(&compiled, data)
104}
105
106pub fn validate_compiled(
109 schema: &CompiledSchema,
110 data: &Value,
111) -> Result<(), (String, Vec<ValidationDetail>)> {
112 let mut errors = Vec::new();
113 let mut ctx = ValidateCtx { errors: &mut errors, max_errors: MAX_ERRORS, max_depth: MAX_DEPTH };
114 validate_walk(schema, data, "", &mut ctx, 0, None);
115 if errors.is_empty() {
116 Ok(())
117 } else {
118 let count = errors.len();
119 let summary = if count == 1 {
120 "validation failed: 1 error".into()
121 } else {
122 format!("validation failed: {count} errors")
123 };
124 Err((summary, errors))
125 }
126}
127
128#[cfg(test)]
129mod tests;