1use crate::{Error, Result};
4use std::time::Duration;
5
6#[derive(Debug, Clone)]
8pub struct Limits {
9 pub max_depth: usize,
11 pub max_anchors: usize,
13 pub max_document_size: usize,
15 pub max_string_length: usize,
17 pub max_alias_depth: usize,
19 pub max_collection_size: usize,
21 pub max_complexity_score: usize,
23 pub timeout: Option<Duration>,
25}
26
27impl Default for Limits {
28 fn default() -> Self {
29 Self {
30 max_depth: 1000,
31 max_anchors: 10_000,
32 max_document_size: 100 * 1024 * 1024, max_string_length: 10 * 1024 * 1024, max_alias_depth: 100,
35 max_collection_size: 1_000_000,
36 max_complexity_score: 1_000_000,
37 timeout: None,
38 }
39 }
40}
41
42impl Limits {
43 pub fn strict() -> Self {
45 Self {
46 max_depth: 50,
47 max_anchors: 100,
48 max_document_size: 1024 * 1024, max_string_length: 64 * 1024, max_alias_depth: 5,
51 max_collection_size: 10_000,
52 max_complexity_score: 10_000,
53 timeout: Some(Duration::from_secs(5)),
54 }
55 }
56
57 pub fn permissive() -> Self {
59 Self {
60 max_depth: 10_000,
61 max_anchors: 100_000,
62 max_document_size: 1024 * 1024 * 1024, max_string_length: 100 * 1024 * 1024, max_alias_depth: 1000,
65 max_collection_size: 10_000_000,
66 max_complexity_score: 100_000_000,
67 timeout: None,
68 }
69 }
70
71 pub fn unlimited() -> Self {
73 Self {
74 max_depth: usize::MAX,
75 max_anchors: usize::MAX,
76 max_document_size: usize::MAX,
77 max_string_length: usize::MAX,
78 max_alias_depth: usize::MAX,
79 max_collection_size: usize::MAX,
80 max_complexity_score: usize::MAX,
81 timeout: None,
82 }
83 }
84}
85
86#[derive(Debug, Clone, Default)]
88pub struct ResourceTracker {
89 current_depth: usize,
90 max_depth_seen: usize,
91 anchor_count: usize,
92 bytes_processed: usize,
93 alias_depth: usize,
94 complexity_score: usize,
95 collection_items: usize,
96}
97
98impl ResourceTracker {
99 pub fn new() -> Self {
101 Self::default()
102 }
103
104 pub fn check_depth(&mut self, limits: &Limits, depth: usize) -> Result<()> {
106 self.current_depth = depth;
107 self.max_depth_seen = self.max_depth_seen.max(depth);
108
109 if depth > limits.max_depth {
110 return Err(Error::limit_exceeded(format!(
111 "Maximum depth {} exceeded",
112 limits.max_depth
113 )));
114 }
115 Ok(())
116 }
117
118 pub fn add_anchor(&mut self, limits: &Limits) -> Result<()> {
120 self.anchor_count += 1;
121 if self.anchor_count > limits.max_anchors {
122 return Err(Error::limit_exceeded(format!(
123 "Maximum anchor count {} exceeded",
124 limits.max_anchors
125 )));
126 }
127 Ok(())
128 }
129
130 pub fn add_bytes(&mut self, limits: &Limits, bytes: usize) -> Result<()> {
132 self.bytes_processed += bytes;
133 if self.bytes_processed > limits.max_document_size {
134 return Err(Error::limit_exceeded(format!(
135 "Maximum document size {} exceeded",
136 limits.max_document_size
137 )));
138 }
139 Ok(())
140 }
141
142 pub fn check_string_length(&self, limits: &Limits, length: usize) -> Result<()> {
144 if length > limits.max_string_length {
145 return Err(Error::limit_exceeded(format!(
146 "Maximum string length {} exceeded",
147 limits.max_string_length
148 )));
149 }
150 Ok(())
151 }
152
153 pub fn enter_alias(&mut self, limits: &Limits) -> Result<()> {
155 if self.alias_depth + 1 > limits.max_alias_depth {
156 return Err(Error::limit_exceeded(format!(
157 "Maximum alias depth {} exceeded",
158 limits.max_alias_depth
159 )));
160 }
161 self.alias_depth += 1;
162 Ok(())
163 }
164
165 pub fn exit_alias(&mut self) {
167 if self.alias_depth > 0 {
168 self.alias_depth -= 1;
169 }
170 }
171
172 pub fn add_collection_item(&mut self, limits: &Limits) -> Result<()> {
174 self.collection_items += 1;
175 if self.collection_items > limits.max_collection_size {
176 return Err(Error::limit_exceeded(format!(
177 "Maximum collection size {} exceeded",
178 limits.max_collection_size
179 )));
180 }
181 Ok(())
182 }
183
184 pub fn add_complexity(&mut self, limits: &Limits, score: usize) -> Result<()> {
186 self.complexity_score += score;
187 if self.complexity_score > limits.max_complexity_score {
188 return Err(Error::limit_exceeded(format!(
189 "Maximum complexity score {} exceeded",
190 limits.max_complexity_score
191 )));
192 }
193 Ok(())
194 }
195
196 pub fn reset(&mut self) {
198 *self = Self::new();
199 }
200
201 pub fn stats(&self) -> ResourceStats {
203 ResourceStats {
204 max_depth: self.max_depth_seen,
205 anchor_count: self.anchor_count,
206 bytes_processed: self.bytes_processed,
207 complexity_score: self.complexity_score,
208 collection_items: self.collection_items,
209 }
210 }
211}
212
213#[derive(Debug, Clone)]
215pub struct ResourceStats {
216 pub max_depth: usize,
218 pub anchor_count: usize,
220 pub bytes_processed: usize,
222 pub complexity_score: usize,
224 pub collection_items: usize,
226}
227
228#[cfg(test)]
229mod tests {
230 use super::*;
231
232 #[test]
233 fn test_default_limits() {
234 let limits = Limits::default();
235 assert_eq!(limits.max_depth, 1000);
236 assert_eq!(limits.max_anchors, 10_000);
237 }
238
239 #[test]
240 fn test_strict_limits() {
241 let limits = Limits::strict();
242 assert_eq!(limits.max_depth, 50);
243 assert_eq!(limits.max_anchors, 100);
244 assert!(limits.timeout.is_some());
245 }
246
247 #[test]
248 fn test_resource_tracker() {
249 let limits = Limits::strict();
250 let mut tracker = ResourceTracker::new();
251
252 assert!(tracker.check_depth(&limits, 10).is_ok());
254 assert!(tracker.check_depth(&limits, 51).is_err());
255
256 for _ in 0..100 {
258 assert!(tracker.add_anchor(&limits).is_ok());
259 }
260 assert!(tracker.add_anchor(&limits).is_err());
261 }
262
263 #[test]
264 fn test_alias_depth_tracking() {
265 let limits = Limits::strict();
266 let mut tracker = ResourceTracker::new();
267
268 for _ in 0..5 {
270 assert!(tracker.enter_alias(&limits).is_ok());
271 }
272 assert!(tracker.enter_alias(&limits).is_err());
273
274 tracker.exit_alias();
276 assert!(tracker.enter_alias(&limits).is_ok());
277 }
278}