-
Notifications
You must be signed in to change notification settings - Fork 280
/
Copy pathjson.rs
123 lines (111 loc) · 4.06 KB
/
json.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
use pyo3::intern;
use pyo3::prelude::*;
use pyo3::types::PyDict;
use jiter::{FloatMode, JsonValue, PartialMode, PythonParse};
use crate::errors::{ErrorType, ErrorTypeDefaults, ValError, ValLineError, ValResult};
use crate::input::{EitherBytes, Input, InputType, ValidationMatch};
use crate::serializers::BytesMode;
use crate::tools::SchemaDict;
use super::config::ValBytesMode;
use super::{build_validator, BuildValidator, CombinedValidator, DefinitionsBuilder, ValidationState, Validator};
#[derive(Debug)]
pub struct JsonValidator {
validator: Option<Box<CombinedValidator>>,
name: String,
}
impl BuildValidator for JsonValidator {
const EXPECTED_TYPE: &'static str = "json";
fn build(
schema: &Bound<'_, PyDict>,
config: Option<&Bound<'_, PyDict>>,
definitions: &mut DefinitionsBuilder<CombinedValidator>,
) -> PyResult<CombinedValidator> {
let validator = match schema.get_as(intern!(schema.py(), "schema"))? {
Some(schema) => {
let validator = build_validator(&schema, config, definitions)?;
match validator {
CombinedValidator::Any(_) => None,
_ => Some(Box::new(validator)),
}
}
None => None,
};
let name = format!(
"{}[{}]",
Self::EXPECTED_TYPE,
validator.as_ref().map_or("any", |v| v.get_name())
);
Ok(Self { validator, name }.into())
}
}
impl_py_gc_traverse!(JsonValidator { validator });
impl Validator for JsonValidator {
fn validate<'py>(
&self,
py: Python<'py>,
input: &(impl Input<'py> + ?Sized),
state: &mut ValidationState<'_, 'py>,
) -> ValResult<PyObject> {
let v_match = validate_json_bytes(input)?;
let json_either_bytes = v_match.unpack(state);
let json_bytes = json_either_bytes.as_slice();
match self.validator {
Some(ref validator) => {
let json_value = JsonValue::parse_with_config(json_bytes, true, state.allow_partial)
.map_err(|e| map_json_err(input, e, json_bytes))?;
let mut json_state = state.rebind_extra(|e| {
e.input_type = InputType::Json;
});
validator.validate(py, &json_value, &mut json_state)
}
None => {
let parse_builder = PythonParse {
allow_inf_nan: true,
cache_mode: state.cache_str(),
partial_mode: if state.allow_partial {
PartialMode::TrailingStrings
} else {
PartialMode::Off
},
catch_duplicate_keys: false,
float_mode: FloatMode::Float,
};
let obj = parse_builder
.python_parse(py, json_bytes)
.map_err(|e| map_json_err(input, e, json_bytes))?;
Ok(obj.unbind())
}
}
}
fn get_name(&self) -> &str {
&self.name
}
}
pub fn validate_json_bytes<'a, 'py>(
input: &'a (impl Input<'py> + ?Sized),
) -> ValResult<ValidationMatch<EitherBytes<'a, 'py>>> {
match input.validate_bytes(false, ValBytesMode { ser: BytesMode::Utf8 }) {
Ok(v_match) => Ok(v_match),
Err(ValError::LineErrors(e)) => Err(ValError::LineErrors(
e.into_iter().map(map_bytes_error).collect::<Vec<_>>(),
)),
Err(e) => Err(e),
}
}
fn map_bytes_error(line_error: ValLineError) -> ValLineError {
match line_error.error_type {
ErrorType::BytesType { .. } => {
ValLineError::new_custom_input(ErrorTypeDefaults::JsonType, line_error.input_value)
}
_ => line_error,
}
}
pub fn map_json_err<'py>(input: &(impl Input<'py> + ?Sized), error: jiter::JsonError, json_bytes: &[u8]) -> ValError {
ValError::new(
ErrorType::JsonInvalid {
error: error.description(json_bytes),
context: None,
},
input,
)
}