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