1use super::{
2 Context,
3 ContextEntry,
4 Definition,
5 TermDefinition,
6 definition,
7 term_definition::{self, InvalidNest},
8};
9use crate::{Container, ErrorCode, Keyword, Nullable, TryFromJson};
10use iri_rs::IriRefBuf;
11
12#[derive(Debug, Clone, thiserror::Error)]
13pub enum InvalidContext {
15 #[error("Invalid IRI reference: {0}")]
16 InvalidIriRef(String),
19
20 #[error("Unexpected {0}")]
21 Unexpected(jstrict::Kind, &'static [jstrict::Kind]),
24
25 #[error("Invalid `@direction`")]
26 InvalidDirection,
28
29 #[error("Duplicate key")]
30 DuplicateKey,
33
34 #[error("Invalid term definition")]
35 InvalidTermDefinition,
39
40 #[error("Invalid `@nest` value `{0}`")]
41 InvalidNestValue(String),
44
45 #[error("Context too deeply nested")]
46 TooDeep,
48}
49
50pub const MAX_CONTEXT_DEPTH: usize = 128;
56
57const RED_ZONE: usize = 128 * 1024;
61
62const SEGMENT: usize = 2 * 1024 * 1024;
64
65fn nested_context_try_from_json(value: &jstrict::Value, depth: usize) -> Result<Context, InvalidContext> {
74 stacker::maybe_grow(RED_ZONE, SEGMENT, || context_try_from_json(value, depth))
75}
76
77impl InvalidContext {
78 #[must_use]
80 pub fn code(&self) -> ErrorCode {
81 match self {
82 Self::InvalidIriRef(_) => ErrorCode::InvalidIriMapping,
83 Self::Unexpected(_, _) => ErrorCode::InvalidContextEntry,
84 Self::InvalidDirection => ErrorCode::InvalidBaseDirection,
85 Self::DuplicateKey => ErrorCode::DuplicateKey,
86 Self::InvalidTermDefinition => ErrorCode::InvalidTermDefinition,
87 Self::InvalidNestValue(_) => ErrorCode::InvalidNestValue,
88 Self::TooDeep => ErrorCode::ContextOverflow,
89 }
90 }
91}
92
93impl From<crate::Unexpected> for InvalidContext {
94 fn from(crate::Unexpected(u, e): crate::Unexpected) -> Self {
95 Self::Unexpected(u, e)
96 }
97}
98
99impl TryFromJson for TermDefinition {
100 type Error = InvalidContext;
101
102 fn try_from_json(value: &jstrict::Value) -> Result<Self, InvalidContext> {
103 term_definition_try_from_json(value, 0)
104 }
105}
106
107macro_rules! set_unique {
111 ($field:expr, $value:expr) => {
112 if $field.replace($value).is_some() {
113 return Err(InvalidContext::DuplicateKey);
114 }
115 };
116}
117
118fn term_definition_try_from_json(value: &jstrict::Value, depth: usize) -> Result<TermDefinition, InvalidContext> {
119 match value {
120 jstrict::Value::String(s) => Ok(TermDefinition::Simple(term_definition::Simple(s.as_str().to_owned()))),
121 jstrict::Value::Object(o) => {
122 let mut def = term_definition::Expanded::new();
123
124 for jstrict::object::Entry { key, value } in o {
125 match Keyword::try_from(key.as_str()) {
126 Ok(Keyword::Id) => set_unique!(def.id, Nullable::try_from_json(value)?),
127 Ok(Keyword::Type) => set_unique!(def.type_, Nullable::try_from_json(value)?),
128 Ok(Keyword::Context) => set_unique!(def.context, Box::new(nested_context_try_from_json(value, depth + 1)?)),
129 Ok(Keyword::Reverse) => set_unique!(def.reverse, definition::Key::try_from_json(value)?),
130 Ok(Keyword::Index) => set_unique!(def.index, term_definition::Index::try_from_json(value)?),
131 Ok(Keyword::Language) => set_unique!(def.language, Nullable::try_from_json(value)?),
132 Ok(Keyword::Direction) => set_unique!(def.direction, Nullable::try_from_json(value)?),
133 Ok(Keyword::Container) => {
134 let container = match value {
135 jstrict::Value::Null => Nullable::Null,
136 other => {
137 let container = Container::try_from_json(other)?;
138 Nullable::Some(container)
139 }
140 };
141
142 set_unique!(def.container, container);
143 }
144 Ok(Keyword::Nest) => set_unique!(def.nest, term_definition::Nest::try_from_json(value)?),
145 Ok(Keyword::Prefix) => set_unique!(def.prefix, bool::try_from_json(value)?),
146 Ok(Keyword::Propagate) => set_unique!(def.propagate, bool::try_from_json(value)?),
147 Ok(Keyword::Protected) => set_unique!(def.protected, bool::try_from_json(value)?),
148 _ => return Err(InvalidContext::InvalidTermDefinition),
149 }
150 }
151
152 Ok(TermDefinition::Expanded(Box::new(def)))
153 }
154 unexpected => Err(InvalidContext::Unexpected(unexpected.kind(), &[jstrict::Kind::String, jstrict::Kind::Object])),
155 }
156}
157
158impl TryFromJson for term_definition::Type {
159 type Error = InvalidContext;
160
161 fn try_from_json(value: &jstrict::Value) -> Result<Self, InvalidContext> {
162 match value {
163 jstrict::Value::String(s) => Ok(Self::from(s.as_str().to_owned())),
164 unexpected => Err(InvalidContext::Unexpected(unexpected.kind(), &[jstrict::Kind::String])),
165 }
166 }
167}
168
169impl TryFromJson for definition::TypeContainer {
170 type Error = InvalidContext;
171
172 fn try_from_json(value: &jstrict::Value) -> Result<Self, InvalidContext> {
173 match value {
174 jstrict::Value::String(s) => match Keyword::try_from(s.as_str()) {
175 Ok(Keyword::Set) => Ok(Self::Set),
176 _ => Err(InvalidContext::InvalidTermDefinition),
177 },
178 unexpected => Err(InvalidContext::Unexpected(unexpected.kind(), &[jstrict::Kind::String])),
179 }
180 }
181}
182
183impl TryFromJson for definition::Type {
184 type Error = InvalidContext;
185
186 fn try_from_json(value: &jstrict::Value) -> Result<Self, InvalidContext> {
187 match value {
188 jstrict::Value::Object(o) => {
189 let mut container = None;
190 let mut protected = None;
191
192 for jstrict::object::Entry { key, value } in o {
193 match Keyword::try_from(key.as_str()) {
194 Ok(Keyword::Container) => {
195 if container.replace(definition::TypeContainer::try_from_json(value)?).is_some() {
196 return Err(InvalidContext::DuplicateKey);
197 }
198 }
199 Ok(Keyword::Protected) => {
200 if protected.replace(bool::try_from_json(value)?).is_some() {
201 return Err(InvalidContext::DuplicateKey);
202 }
203 }
204 _ => return Err(InvalidContext::InvalidTermDefinition),
205 }
206 }
207
208 match container {
209 Some(container) => Ok(Self { container, protected }),
210 None => Err(InvalidContext::InvalidTermDefinition),
211 }
212 }
213 unexpected => Err(InvalidContext::Unexpected(unexpected.kind(), &[jstrict::Kind::Object])),
214 }
215 }
216}
217
218impl TryFromJson for definition::Version {
219 type Error = InvalidContext;
220
221 fn try_from_json(value: &jstrict::Value) -> Result<Self, InvalidContext> {
222 match value {
223 jstrict::Value::Number(n) => match n.as_str() {
224 "1.1" => Ok(Self::V1_1),
225 _ => Err(InvalidContext::InvalidTermDefinition),
226 },
227 unexpected => Err(InvalidContext::Unexpected(unexpected.kind(), &[jstrict::Kind::Number])),
228 }
229 }
230}
231
232impl TryFromJson for definition::Vocab {
233 type Error = InvalidContext;
234
235 fn try_from_json(value: &jstrict::Value) -> Result<Self, InvalidContext> {
236 match value {
237 jstrict::Value::String(s) => Ok(Self::from(s.as_str().to_owned())),
238 unexpected => Err(InvalidContext::Unexpected(unexpected.kind(), &[jstrict::Kind::String])),
239 }
240 }
241}
242
243impl TryFromJson for term_definition::Id {
244 type Error = InvalidContext;
245
246 fn try_from_json(value: &jstrict::Value) -> Result<Self, InvalidContext> {
247 match value {
248 jstrict::Value::String(s) => Ok(Self::from(s.as_str().to_owned())),
249 unexpected => Err(InvalidContext::Unexpected(unexpected.kind(), &[jstrict::Kind::String])),
250 }
251 }
252}
253
254impl TryFromJson for definition::Key {
255 type Error = InvalidContext;
256
257 fn try_from_json(value: &jstrict::Value) -> Result<Self, Self::Error> {
258 match value {
259 jstrict::Value::String(s) => Ok(Self::from(s.as_str().to_owned())),
260 unexpected => Err(InvalidContext::Unexpected(unexpected.kind(), &[jstrict::Kind::String])),
261 }
262 }
263}
264
265impl TryFromJson for term_definition::Index {
266 type Error = InvalidContext;
267
268 fn try_from_json(value: &jstrict::Value) -> Result<Self, InvalidContext> {
269 match value {
270 jstrict::Value::String(s) => Ok(Self::from(s.as_str().to_owned())),
271 unexpected => Err(InvalidContext::Unexpected(unexpected.kind(), &[jstrict::Kind::String])),
272 }
273 }
274}
275
276impl TryFromJson for term_definition::Nest {
277 type Error = InvalidContext;
278
279 fn try_from_json(value: &jstrict::Value) -> Result<Self, InvalidContext> {
280 match value {
281 jstrict::Value::String(s) => match Self::try_from(s.as_str().to_owned()) {
282 Ok(nest) => Ok(nest),
283 Err(InvalidNest(s)) => Err(InvalidContext::InvalidNestValue(s)),
284 },
285 unexpected => Err(InvalidContext::Unexpected(unexpected.kind(), &[jstrict::Kind::String])),
286 }
287 }
288}
289
290impl TryFromJson for Context {
291 type Error = InvalidContext;
292
293 fn try_from_json(value: &jstrict::Value) -> Result<Self, InvalidContext> {
294 context_try_from_json(value, 0)
295 }
296}
297
298fn context_try_from_json(value: &jstrict::Value, depth: usize) -> Result<Context, InvalidContext> {
299 if depth >= MAX_CONTEXT_DEPTH {
300 return Err(InvalidContext::TooDeep);
301 }
302
303 match value {
304 jstrict::Value::Array(a) => {
305 let mut many = Vec::with_capacity(a.len());
306
307 for item in a {
308 many.push(context_entry_try_from_json(item, depth)?);
309 }
310
311 Ok(Context::Many(many))
312 }
313 context => Ok(Context::One(context_entry_try_from_json(context, depth)?)),
314 }
315}
316
317impl TryFromJson for ContextEntry {
318 type Error = InvalidContext;
319
320 fn try_from_json(value: &jstrict::Value) -> Result<Self, InvalidContext> {
321 context_entry_try_from_json(value, 0)
322 }
323}
324
325fn context_entry_try_from_json(value: &jstrict::Value, depth: usize) -> Result<ContextEntry, InvalidContext> {
326 match value {
327 jstrict::Value::Null => Ok(ContextEntry::Null),
328 jstrict::Value::String(s) => match IriRefBuf::new(s.as_str().to_owned()) {
329 Ok(iri_ref) => Ok(ContextEntry::IriRef(iri_ref)),
330 Err(e) => Err(InvalidContext::InvalidIriRef(e.0)),
331 },
332 jstrict::Value::Object(o) => {
333 let mut def = Definition::new();
334
335 for jstrict::object::Entry { key, value } in o {
336 match Keyword::try_from(key.as_str()) {
337 Ok(Keyword::Base) => set_unique!(def.base, Nullable::try_from_json(value)?),
338 Ok(Keyword::Import) => set_unique!(def.import, IriRefBuf::try_from_json(value)?),
339 Ok(Keyword::Language) => set_unique!(def.language, Nullable::try_from_json(value)?),
340 Ok(Keyword::Direction) => set_unique!(def.direction, Nullable::try_from_json(value)?),
341 Ok(Keyword::Propagate) => set_unique!(def.propagate, bool::try_from_json(value)?),
342 Ok(Keyword::Protected) => set_unique!(def.protected, bool::try_from_json(value)?),
343 Ok(Keyword::Type) => set_unique!(def.type_, definition::Type::try_from_json(value)?),
344 Ok(Keyword::Version) => set_unique!(def.version, definition::Version::try_from_json(value)?),
345 Ok(Keyword::Vocab) => set_unique!(def.vocab, Nullable::try_from_json(value)?),
346 _ => {
347 let term_def = match value {
348 jstrict::Value::Null => Nullable::Null,
349 other => Nullable::Some(term_definition_try_from_json(other, depth)?),
350 };
351
352 if def.bindings.insert_with(key.as_str().to_owned().into(), term_def).is_some() {
353 return Err(InvalidContext::DuplicateKey);
354 }
355 }
356 }
357 }
358
359 Ok(ContextEntry::Definition(def))
360 }
361 unexpected => Err(InvalidContext::Unexpected(
362 unexpected.kind(),
363 &[jstrict::Kind::Null, jstrict::Kind::String, jstrict::Kind::Object],
364 )),
365 }
366}
367
368#[cfg(test)]
369mod tests {
370 #![allow(clippy::unwrap_used)]
371 use super::*;
372 use crate::Parse;
373
374 fn nested_json(levels: usize) -> jstrict::Value {
375 let mut json = String::new();
376 for _ in 0..levels {
377 json.push_str("{\"a\":{\"@context\":");
378 }
379 json.push_str("{}");
380 for _ in 0..levels {
381 json.push_str("}}");
382 }
383
384 jstrict::Value::parse_str(&json).unwrap().0
385 }
386
387 #[test]
390 fn deeply_nested_context_conversion_fails_gracefully() {
391 let json = nested_json(MAX_CONTEXT_DEPTH + 1);
392 assert!(matches!(Context::try_from_json(&json), Err(InvalidContext::TooDeep)));
393 }
394
395 #[test]
396 fn reasonable_nesting_converts() {
397 let json = nested_json(MAX_CONTEXT_DEPTH - 1);
398 assert!(Context::try_from_json(&json).is_ok());
399 }
400
401 #[test]
406 fn nesting_up_to_the_bound_converts_on_a_small_stack() {
407 let deepest = nested_json(MAX_CONTEXT_DEPTH - 1);
408 let too_deep = nested_json(MAX_CONTEXT_DEPTH + 1);
409
410 let (converted, refused) = std::thread::Builder::new()
411 .stack_size(256 * 1024)
412 .spawn(move || (Context::try_from_json(&deepest).is_ok(), Context::try_from_json(&too_deep)))
413 .unwrap()
414 .join()
415 .unwrap();
416
417 assert!(converted);
418 assert!(matches!(refused, Err(InvalidContext::TooDeep)));
419 }
420
421 #[test]
424 fn duplicate_keyword_entries_are_rejected() {
425 let json = jstrict::Value::parse_str(r#"{"@vocab": "http://a/", "@vocab": "http://b/"}"#).unwrap().0;
426 assert!(matches!(Context::try_from_json(&json), Err(InvalidContext::DuplicateKey)));
427
428 let json = jstrict::Value::parse_str(r#"{"@id": "http://a/x", "@id": "http://b/x"}"#).unwrap().0;
429 assert!(matches!(TermDefinition::try_from_json(&json), Err(InvalidContext::DuplicateKey)));
430 }
431}