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