1use rskit_errors::{AppError, AppResult, ErrorCode};
4use serde_json::Value;
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub struct ValidationLimits {
9 pub max_depth: usize,
11 pub max_nodes: usize,
13 pub max_string_bytes: usize,
15 pub max_key_bytes: usize,
17 pub max_total_string_bytes: usize,
19}
20
21impl Default for ValidationLimits {
22 fn default() -> Self {
23 Self {
24 max_depth: 128,
25 max_nodes: 100_000,
26 max_string_bytes: 1_048_576,
27 max_key_bytes: 16_384,
28 max_total_string_bytes: 16_777_216,
29 }
30 }
31}
32
33impl ValidationLimits {
34 #[must_use]
36 pub const fn new(max_depth: usize, max_nodes: usize) -> Self {
37 Self {
38 max_depth,
39 max_nodes,
40 max_string_bytes: 1_048_576,
41 max_key_bytes: 16_384,
42 max_total_string_bytes: 16_777_216,
43 }
44 }
45
46 #[must_use]
48 pub const fn with_max_string_bytes(mut self, max_string_bytes: usize) -> Self {
49 self.max_string_bytes = max_string_bytes;
50 self
51 }
52
53 #[must_use]
55 pub const fn with_max_key_bytes(mut self, max_key_bytes: usize) -> Self {
56 self.max_key_bytes = max_key_bytes;
57 self
58 }
59
60 #[must_use]
62 pub const fn with_max_total_string_bytes(mut self, max_total_string_bytes: usize) -> Self {
63 self.max_total_string_bytes = max_total_string_bytes;
64 self
65 }
66}
67
68pub(crate) fn check_json_limits(
69 name: &str,
70 value: &Value,
71 limits: ValidationLimits,
72) -> AppResult<()> {
73 let mut node_count = 0usize;
74 let mut total_string_bytes = 0usize;
75 let mut stack = vec![(value, 1usize)];
76
77 while let Some((node, depth)) = stack.pop() {
78 node_count = node_count.saturating_add(1);
79 if node_count > limits.max_nodes {
80 return Err(AppError::new(
81 ErrorCode::InvalidInput,
82 format!(
83 "{name} exceeds maximum JSON node count {}",
84 limits.max_nodes
85 ),
86 ));
87 }
88 if depth > limits.max_depth {
89 return Err(AppError::new(
90 ErrorCode::InvalidInput,
91 format!("{name} exceeds maximum JSON depth {}", limits.max_depth),
92 ));
93 }
94
95 match node {
96 Value::Array(values) => {
97 stack.extend(values.iter().map(|value| (value, depth + 1)));
98 }
99 Value::Object(values) => {
100 for key in values.keys() {
101 check_string_bytes(name, "object key", key.len(), limits)?;
102 total_string_bytes = add_string_bytes(
103 name,
104 total_string_bytes,
105 key.len(),
106 limits.max_total_string_bytes,
107 )?;
108 }
109 stack.extend(values.values().map(|value| (value, depth + 1)));
110 }
111 Value::String(value) => {
112 check_string_bytes(name, "string", value.len(), limits)?;
113 total_string_bytes = add_string_bytes(
114 name,
115 total_string_bytes,
116 value.len(),
117 limits.max_total_string_bytes,
118 )?;
119 }
120 Value::Null | Value::Bool(_) | Value::Number(_) => {}
121 }
122 }
123
124 Ok(())
125}
126
127fn check_string_bytes(
128 name: &str,
129 kind: &str,
130 len: usize,
131 limits: ValidationLimits,
132) -> AppResult<()> {
133 let max = if kind == "object key" {
134 limits.max_key_bytes
135 } else {
136 limits.max_string_bytes
137 };
138
139 if len > max {
140 return Err(AppError::new(
141 ErrorCode::InvalidInput,
142 format!("{name} {kind} exceeds maximum UTF-8 byte length {max}"),
143 ));
144 }
145 Ok(())
146}
147
148fn add_string_bytes(
149 name: &str,
150 current: usize,
151 additional: usize,
152 max_total: usize,
153) -> AppResult<usize> {
154 let total = current.saturating_add(additional);
155 if total > max_total {
156 return Err(AppError::new(
157 ErrorCode::InvalidInput,
158 format!("{name} exceeds maximum cumulative string byte length {max_total}"),
159 ));
160 }
161 Ok(total)
162}