Skip to main content

siderite_validation/
pipeline.rs

1//! The complete validation pipeline: `prepare` → deserialize → `validate`.
2
3use crate::context::ValidationContext;
4use crate::error::{FieldError, LocationItem, ValidationError, ValidationResult};
5use crate::validate::Validate;
6use serde::de::DeserializeOwned;
7use serde_json::{Map, Value};
8
9/// Validate raw input and build a `T` (Pydantic `model_validate`).
10///
11/// Every problem found while preparing the input is reported at once. If
12/// preparation succeeds but deserialization still fails (a type whose
13/// `prepare` is less strict than its `Deserialize`), that single error is
14/// reported with its path.
15///
16/// # Errors
17/// Returns all collected [`ValidationError`]s.
18pub fn parse_value<T>(mut input: Value, mut ctx: ValidationContext) -> ValidationResult<T>
19where
20    T: DeserializeOwned + Validate,
21{
22    T::prepare(&mut input, &mut ctx);
23    if ctx.has_errors() {
24        return Err(ctx.take_errors());
25    }
26    let value: T = match serde_path_to_error::deserialize(input) {
27        Ok(value) => value,
28        Err(err) => {
29            let location = err.path().iter().filter_map(segment_location).collect();
30            let mut error = FieldError::new("invalid", err.inner().to_string());
31            error.location = location;
32            ctx.push(error);
33            return Err(ctx.take_errors());
34        }
35    };
36    value.validate(&mut ctx);
37    if ctx.has_errors() {
38        Err(ctx.take_errors())
39    } else {
40        Ok(value)
41    }
42}
43
44/// Parse JSON text through the pipeline.
45///
46/// # Errors
47/// A `json_invalid` error for malformed JSON, otherwise as [`parse_value`].
48pub fn parse_json<T>(text: &str, ctx: ValidationContext) -> ValidationResult<T>
49where
50    T: DeserializeOwned + Validate,
51{
52    match serde_json::from_str::<Value>(text) {
53        Ok(value) => parse_value(value, ctx),
54        Err(err) => Err(FieldError::new("json_invalid", err.to_string()).into()),
55    }
56}
57
58/// Run only the post-deserialization checks on an existing value.
59///
60/// # Errors
61/// Returns every violation found.
62pub fn validate<T: Validate + ?Sized>(value: &T) -> ValidationResult<()> {
63    let mut ctx = ValidationContext::new();
64    value.validate(&mut ctx);
65    ctx.take_errors().into_result()
66}
67
68fn segment_location(segment: &serde_path_to_error::Segment) -> Option<LocationItem> {
69    use serde_path_to_error::Segment;
70    match segment {
71        Segment::Seq { index } => Some(LocationItem::index(*index)),
72        Segment::Map { key } => Some(LocationItem::key(key.clone())),
73        Segment::Enum { variant } => Some(LocationItem::key(variant.clone())),
74        Segment::Unknown => None,
75    }
76}
77
78/// Convert text key/value pairs (query string, form) into a JSON object.
79/// Repeated keys become arrays, so `Vec` fields receive every value.
80pub fn text_pairs_to_value<I, K, V>(pairs: I) -> Value
81where
82    I: IntoIterator<Item = (K, V)>,
83    K: Into<String>,
84    V: Into<String>,
85{
86    let mut map = Map::new();
87    for (key, value) in pairs {
88        let value = Value::String(value.into());
89        match map.entry(key.into()) {
90            serde_json::map::Entry::Vacant(slot) => {
91                slot.insert(value);
92            }
93            serde_json::map::Entry::Occupied(mut slot) => match slot.get_mut() {
94                Value::Array(items) => items.push(value),
95                existing => {
96                    let first = existing.take();
97                    *existing = Value::Array(vec![first, value]);
98                }
99            },
100        }
101    }
102    Value::Object(map)
103}
104
105/// Convenience: turn a [`ValidationError`] into its JSON body.
106pub fn error_value(error: &ValidationError) -> Value {
107    serde_json::to_value(error).unwrap_or(Value::Null)
108}