1#![allow(clippy::unimplemented)] use crate::error::{JsonLdErrorCode, JsonLdSyntaxError};
3use crate::{JsonLdProfile, JsonLdProfileSet};
4use json_event_parser::{JsonEvent, JsonSyntaxError, SliceJsonParser};
5use oxiri::Iri;
6use std::borrow::Cow;
7use std::collections::HashMap;
8use std::error::Error;
9use std::panic::{RefUnwindSafe, UnwindSafe};
10use std::slice;
11use std::sync::{Arc, Mutex};
12
13type LoadDocumentCallback = dyn Fn(
14 &str,
15 &JsonLdLoadDocumentOptions,
16 ) -> Result<JsonLdRemoteDocument, Box<dyn Error + Send + Sync>>
17 + Send
18 + Sync
19 + UnwindSafe
20 + RefUnwindSafe;
21
22#[derive(PartialEq, Eq, Clone, Copy)]
23pub enum JsonLdProcessingMode {
24 JsonLd1_0,
25 }
27
28#[derive(Eq, PartialEq, Debug, Clone)]
29pub enum JsonNode {
30 String(String),
31 Number(String),
32 Boolean(bool),
33 Null,
34 Array(Vec<JsonNode>),
35 Object(HashMap<String, JsonNode>),
36}
37
38#[derive(Default, Clone)]
39pub struct JsonLdContext {
40 pub base_iri: Option<Iri<String>>,
41 pub original_base_url: Option<Iri<String>>,
42 pub vocabulary_mapping: Option<String>,
43 pub default_language: Option<String>,
44 pub term_definitions: HashMap<String, JsonLdTermDefinition>,
45 pub previous_context: Option<Box<JsonLdContext>>,
46}
47
48impl JsonLdContext {
49 pub fn new_empty(original_base_url: Option<Iri<String>>) -> Self {
50 JsonLdContext {
51 base_iri: original_base_url.clone(),
52 original_base_url,
53 vocabulary_mapping: None,
54 default_language: None,
55 term_definitions: HashMap::new(),
56 previous_context: None,
57 }
58 }
59}
60
61#[derive(Clone)]
62pub struct JsonLdTermDefinition {
63 pub iri_mapping: Option<Option<String>>,
65 pub prefix_flag: bool,
66 pub protected: bool,
67 pub reverse_property: bool,
68 pub base_url: Option<Iri<String>>,
69 pub container_mapping: &'static [&'static str],
70 pub language_mapping: Option<Option<String>>,
71 pub type_mapping: Option<String>,
72}
73
74pub struct JsonLdContextProcessor {
75 pub processing_mode: JsonLdProcessingMode,
76 pub lenient: bool, pub max_context_recursion: usize,
78 pub remote_context_cache: Arc<Mutex<HashMap<String, (Option<Iri<String>>, JsonNode)>>>,
79 pub load_document_callback: Option<Arc<LoadDocumentCallback>>,
80}
81
82pub struct JsonLdLoadDocumentOptions {
84 pub request_profile: JsonLdProfileSet,
86}
87
88pub struct JsonLdRemoteDocument {
90 pub document: Vec<u8>,
92 pub document_url: String,
94}
95
96impl JsonLdContextProcessor {
97 pub fn process_context(
99 &self,
100 active_context: &JsonLdContext,
101 local_context: JsonNode,
102 base_url: Option<&Iri<String>>,
103 remote_contexts: &mut Vec<String>,
104 override_protected: bool,
105 mut propagate: bool,
106 validate_scoped_context: bool,
107 errors: &mut Vec<JsonLdSyntaxError>,
108 ) -> JsonLdContext {
109 let mut result = active_context.clone();
111 if let JsonNode::Object(local_context) = &local_context {
113 if let Some(propagate_node) = local_context.get("@propagate") {
114 if let JsonNode::Boolean(new) = propagate_node {
115 propagate = *new;
116 } else {
117 errors.push(JsonLdSyntaxError::msg("@propagate value must be a boolean"))
118 }
119 }
120 }
121 if !propagate && result.previous_context.is_none() {
123 result.previous_context = Some(Box::new(active_context.clone()));
124 }
125 let local_context = if let JsonNode::Array(c) = local_context {
127 c
128 } else {
129 vec![local_context]
130 };
131 for context in local_context {
133 let context = match context {
134 JsonNode::Null => {
136 if !override_protected {
138 for (name, def) in &active_context.term_definitions {
139 if def.protected {
140 errors.push(JsonLdSyntaxError::msg_and_code(format!("Definition of {name} will be overridden even if it's protected"), JsonLdErrorCode::InvalidContextNullification));
141 }
142 }
143 }
144 result = JsonLdContext::new_empty(active_context.original_base_url.clone());
146 continue;
148 }
149 JsonNode::String(context) => {
151 let context = match if let Some(base_url) = base_url {
153 base_url.resolve(&context)
154 } else {
155 Iri::parse(context.clone())
156 } {
157 Ok(url) => url.into_inner(),
158 Err(e) => {
159 errors.push(JsonLdSyntaxError::msg_and_code(
160 format!("Invalid remote context URL '{context}': {e}"),
161 JsonLdErrorCode::LoadingDocumentFailed,
162 ));
163 continue;
164 }
165 };
166 if !validate_scoped_context && remote_contexts.contains(&context) {
168 continue;
169 }
170 if remote_contexts.len() >= self.max_context_recursion {
172 errors.push(JsonLdSyntaxError::msg_and_code(
173 format!(
174 "This processor only allows {} remote context, threshold exceeded",
175 self.max_context_recursion
176 ),
177 JsonLdErrorCode::ContextOverflow,
178 ));
179 continue;
180 }
181 remote_contexts.push(context.clone());
182 let mut remote_context_cache = self.remote_context_cache.lock().unwrap(); let (loaded_context_base, loaded_context_content) =
184 if let Some(loaded_context) = remote_context_cache.get(&context) {
185 loaded_context.clone()
187 } else {
188 let Some(load_document_callback) = &self.load_document_callback else {
190 errors.push(JsonLdSyntaxError::msg_and_code(
191 "No LoadDocumentCallback has been set to load remote contexts",
192 JsonLdErrorCode::LoadingRemoteContextFailed,
193 ));
194 continue;
195 };
196 let context_document = match load_document_callback(
197 &context,
198 &JsonLdLoadDocumentOptions {
199 request_profile: JsonLdProfile::Context.into(),
200 },
201 ) {
202 Ok(document) => document,
203 Err(e) => {
204 errors.push(JsonLdSyntaxError::msg_and_code(
205 format!("Failed to load remote context {context}: {e}"),
206 JsonLdErrorCode::LoadingRemoteContextFailed,
207 ));
208 continue;
209 }
210 };
211 let parsed_document =
212 match json_slice_to_node(&context_document.document) {
213 Ok(d) => d,
214 Err(e) => {
215 errors.push(JsonLdSyntaxError::msg_and_code(
216 format!(
217 "Failed to parse remote context {context}: {e}"
218 ),
219 JsonLdErrorCode::LoadingRemoteContextFailed,
220 ));
221 continue;
222 }
223 };
224 let JsonNode::Object(parsed_document) = parsed_document else {
225 errors.push(JsonLdSyntaxError::msg_and_code(
226 format!("Remote context {context} must be a map"),
227 JsonLdErrorCode::InvalidRemoteContext,
228 ));
229 continue;
230 };
231 let Some(loaded_context) = parsed_document
232 .into_iter()
233 .find_map(|(k, v)| (k == "@context").then_some(v))
234 else {
235 errors.push(JsonLdSyntaxError::msg_and_code(
236 format!(
237 "Remote context {context} must be contain a @context key"
238 ),
239 JsonLdErrorCode::InvalidRemoteContext,
240 ));
241 continue;
242 };
243 let document_url = Iri::parse(context_document.document_url).ok();
244 remote_context_cache.insert(
245 context.clone(),
246 (document_url.clone(), loaded_context.clone()),
247 );
248 (document_url, loaded_context)
249 };
250 result = self.process_context(
252 &result,
253 loaded_context_content,
254 loaded_context_base.as_ref(),
255 remote_contexts,
256 false,
257 true,
258 validate_scoped_context,
259 errors,
260 );
261 assert_eq!(
262 remote_contexts.pop(),
263 Some(context),
264 "The remote context stack is invalid"
265 );
266 continue;
267 }
268 JsonNode::Array(_) | JsonNode::Number(_) | JsonNode::Boolean(_) => {
270 errors.push(JsonLdSyntaxError::msg_and_code(
271 "@context value must be null, a string or an object",
272 JsonLdErrorCode::InvalidLocalContext,
273 ));
274 continue;
275 }
276 JsonNode::Object(context) => context,
278 };
279 let mut key_values = HashMap::new();
280 let mut protected = false;
281 for (key, value) in context {
282 match key.as_str() {
283 "@version" => {
285 if let JsonNode::Number(version) = value {
287 if version != "1.1" {
288 errors.push(JsonLdSyntaxError::msg_and_code(
289 format!(
290 "The only supported @version value is 1.1, found {version}"
291 ),
292 JsonLdErrorCode::InvalidVersionValue,
293 ));
294 }
295 } else {
296 errors.push(JsonLdSyntaxError::msg_and_code(
297 "@version value must be a number",
298 JsonLdErrorCode::InvalidVersionValue,
299 ));
300 }
301 if self.processing_mode == JsonLdProcessingMode::JsonLd1_0 {
303 errors.push(JsonLdSyntaxError::msg_and_code(
304 "@version is only supported in JSON-LD 1.1",
305 JsonLdErrorCode::ProcessingModeConflict,
306 ));
307 }
308 }
309 "@import" => {
311 if self.processing_mode == JsonLdProcessingMode::JsonLd1_0 {
313 errors.push(JsonLdSyntaxError::msg_and_code(
314 "@import is only supported in JSON-LD 1.1",
315 JsonLdErrorCode::InvalidContextEntry,
316 ));
317 continue;
318 }
319 unimplemented!()
320 }
321 "@base" => {
323 if remote_contexts.is_empty() {
324 match value {
325 JsonNode::Null => {
327 result.base_iri = None;
328 }
329 JsonNode::String(value) => {
331 if self.lenient {
332 result.base_iri =
333 Some(if let Some(base_iri) = &result.base_iri {
334 base_iri.resolve_unchecked(&value)
335 } else {
336 Iri::parse_unchecked(value.clone())
337 })
338 } else {
339 match if let Some(base_iri) = &result.base_iri {
340 base_iri.resolve(&value)
341 } else {
342 Iri::parse(value.clone())
343 } {
344 Ok(iri) => result.base_iri = Some(iri),
345 Err(e) => errors.push(JsonLdSyntaxError::msg_and_code(
346 format!("Invalid @base '{value}': {e}"),
347 JsonLdErrorCode::InvalidBaseIri,
348 )),
349 }
350 }
351 }
352 _ => errors.push(JsonLdSyntaxError::msg_and_code(
353 "@base value must be a string",
354 JsonLdErrorCode::InvalidBaseIri,
355 )),
356 }
357 }
358 }
359 "@vocab" => {
361 match value {
362 JsonNode::Null => {
364 result.vocabulary_mapping = None;
365 }
366 JsonNode::String(value) => {
368 if value.starts_with("_:") || self.lenient {
370 result.vocabulary_mapping = Some(value);
371 } else {
372 match Iri::parse(value.as_str()) {
373 Ok(_) => result.vocabulary_mapping = Some(value),
374 Err(e) => errors.push(JsonLdSyntaxError::msg_and_code(
375 format!("Invalid @vocab '{value}': {e}"),
376 JsonLdErrorCode::InvalidVocabMapping,
377 )),
378 }
379 }
380 }
381 _ => errors.push(JsonLdSyntaxError::msg_and_code(
382 "@vocab value must be a string",
383 JsonLdErrorCode::InvalidVocabMapping,
384 )),
385 }
386 }
387 "@language" => {
389 match value {
390 JsonNode::Null => {
392 result.default_language = None;
393 }
394 JsonNode::String(value) => result.default_language = Some(value),
396 _ => errors.push(JsonLdSyntaxError::msg_and_code(
397 "@language value must be a string",
398 JsonLdErrorCode::InvalidDefaultLanguage,
399 )),
400 }
401 }
402 "@direction" => {
404 if self.processing_mode == JsonLdProcessingMode::JsonLd1_0 {
406 errors.push(JsonLdSyntaxError::msg_and_code(
407 "@direction is only supported in JSON-LD 1.1",
408 JsonLdErrorCode::InvalidContextEntry,
409 ));
410 continue;
411 }
412 unimplemented!()
413 }
414 "@propagate" => {
416 if self.processing_mode == JsonLdProcessingMode::JsonLd1_0 {
418 errors.push(JsonLdSyntaxError::msg_and_code(
419 "@propagate is only supported in JSON-LD 1.1",
420 JsonLdErrorCode::InvalidContextEntry,
421 ));
422 continue;
423 }
424 unimplemented!()
425 }
426 "@protected" => {
428 if self.processing_mode == JsonLdProcessingMode::JsonLd1_0 {
429 errors.push(JsonLdSyntaxError::msg_and_code(
430 "@protected is only supported in JSON-LD 1.1",
431 JsonLdErrorCode::InvalidContextEntry,
432 ));
433 }
434 match value {
435 JsonNode::Boolean(value) => protected = value,
436 _ => errors.push(JsonLdSyntaxError::msg_and_code(
437 "@protected value must be a boolean",
438 JsonLdErrorCode::InvalidProtectedValue,
439 )),
440 }
441 }
442 _ => {
443 key_values.insert(key, value);
444 }
445 }
446 }
447 let mut defined = HashMap::new();
448 for term in key_values.keys() {
449 self.create_term_definition(
450 &mut result,
451 &key_values,
452 term,
453 &mut defined,
454 base_url,
455 protected,
456 override_protected,
457 remote_contexts,
458 errors,
459 )
460 }
461 }
462 result
464 }
465
466 #[allow(clippy::only_used_in_recursion)] fn create_term_definition(
469 &self,
470 active_context: &mut JsonLdContext,
471 local_context: &HashMap<String, JsonNode>,
472 term: &str,
473 defined: &mut HashMap<String, bool>,
474 base_url: Option<&Iri<String>>,
475 protected: bool,
476 override_protected: bool,
477 remote_contexts: &mut Vec<String>,
478 errors: &mut Vec<JsonLdSyntaxError>,
479 ) {
480 if let Some(defined_value) = defined.get(term) {
482 if !defined_value {
483 errors.push(JsonLdSyntaxError::msg_and_code(
484 "Cyclic IRI mapping, ignoring",
485 JsonLdErrorCode::CyclicIriMapping,
486 ))
487 }
488 return;
489 }
490 if term.is_empty() {
492 errors.push(JsonLdSyntaxError::msg_and_code(
493 "@context terms must not be the empty strings",
494 JsonLdErrorCode::InvalidTermDefinition,
495 ));
496 return;
497 }
498 defined.insert(term.into(), false);
499 if term == "@type" {
501 if self.processing_mode == JsonLdProcessingMode::JsonLd1_0 {
502 errors.push(JsonLdSyntaxError::msg_and_code(
503 "@type keyword can't be redefined in JSON-LD 1.0 @context",
504 JsonLdErrorCode::KeywordRedefinition,
505 ));
506 return;
507 }
508 unimplemented!()
509 } else if has_keyword_form(term) {
510 if is_keyword(term) {
512 errors.push(JsonLdSyntaxError::msg_and_code(
513 format!("{term} keyword can't be redefined in context"),
514 JsonLdErrorCode::KeywordRedefinition,
515 ));
516 }
517 return;
518 }
519 let previous_definition = active_context.term_definitions.remove(term);
521 let value = match local_context.get(term) {
522 Some(JsonNode::Null) => Cow::Owned([("@id".to_owned(), JsonNode::Null)].into()),
524 Some(JsonNode::String(id)) => {
526 Cow::Owned([("@id".to_owned(), JsonNode::String(id.clone()))].into())
527 }
528 Some(JsonNode::Object(map)) => Cow::Borrowed(map),
530 _ => {
531 errors.push(JsonLdSyntaxError::msg_and_code(
532 "Term definition value must be null, a string or a map",
533 JsonLdErrorCode::InvalidTermDefinition,
534 ));
535 return;
536 }
537 };
538 let mut definition = JsonLdTermDefinition {
540 iri_mapping: None,
541 prefix_flag: false,
542 protected,
543 reverse_property: false,
544 base_url: None,
545 container_mapping: &[],
546 language_mapping: None,
547 type_mapping: None,
548 };
549 for (key, key_value) in value.as_ref() {
550 match key.as_str() {
551 "@protected" => {
553 if self.processing_mode == JsonLdProcessingMode::JsonLd1_0 {
554 errors.push(JsonLdSyntaxError::msg_and_code(
555 "@protected keyword can't be used in JSON-LD 1.0 @context",
556 JsonLdErrorCode::InvalidTermDefinition,
557 ));
558 continue;
559 }
560 unimplemented!()
561 }
562 "@type" => {
564 let JsonNode::String(r#type) = key_value else {
566 errors.push(JsonLdSyntaxError::msg_and_code(
567 "The value of @type in a context must be a string",
568 JsonLdErrorCode::InvalidTypeMapping,
569 ));
570 continue;
571 };
572 let Some(r#type) = self.expand_iri(
574 active_context,
575 r#type.as_str().into(),
576 false,
577 true,
578 Some(local_context),
579 defined,
580 errors,
581 ) else {
582 errors.push(JsonLdSyntaxError::msg_and_code(
583 format!("Invalid @type value in context: {type}"),
584 JsonLdErrorCode::InvalidTypeMapping,
585 ));
586 continue;
587 };
588 if matches!(r#type.as_ref(), "@json" | "@none")
590 && self.processing_mode == JsonLdProcessingMode::JsonLd1_0
591 {
592 errors.push(JsonLdSyntaxError::msg_and_code(
593 format!(
594 "@type value {type} in a context is only supported in JSON-LD 1.1"
595 ),
596 JsonLdErrorCode::InvalidTypeMapping,
597 ));
598 }
599 let is_keyword = has_keyword_form(&r#type);
601 if is_keyword
602 && !matches!(r#type.as_ref(), "@id" | "@json" | "@none" | "@vocab")
603 || r#type.starts_with("_:")
604 {
605 errors.push(JsonLdSyntaxError::msg_and_code(
606 format!("Invalid @type value in context: {type}"),
607 JsonLdErrorCode::InvalidTypeMapping,
608 ));
609 }
610 if !self.lenient && !is_keyword {
611 if let Err(e) = Iri::parse(r#type.as_ref()) {
612 errors.push(JsonLdSyntaxError::msg_and_code(
613 format!("Invalid @type iri '{type}': {e}"),
614 JsonLdErrorCode::InvalidTypeMapping,
615 ));
616 }
617 }
618 definition.type_mapping = Some(r#type.into());
620 }
621 "@reverse" => {
623 if value.contains_key("@id") {
625 errors.push(JsonLdSyntaxError::msg_and_code(
626 "@reverse and @id cannot be used together in a context",
627 JsonLdErrorCode::InvalidReverseProperty,
628 ));
629 continue;
630 }
631 if value.contains_key("@nest") {
632 errors.push(JsonLdSyntaxError::msg_and_code(
633 "@reverse and @nest cannot be used together in a context",
634 JsonLdErrorCode::InvalidReverseProperty,
635 ));
636 continue;
637 }
638 let JsonNode::String(value) = key_value else {
640 errors.push(JsonLdSyntaxError::msg_and_code(
641 "@reverse value must be a string in a context",
642 JsonLdErrorCode::InvalidIriMapping,
643 ));
644 continue;
645 };
646 if let Some(iri) = self.expand_iri(
648 active_context,
649 value.into(),
650 false,
651 true,
652 Some(local_context),
653 defined,
654 errors,
655 ) {
656 if self.lenient && !has_keyword_form(&iri)
657 || !self.lenient
658 && (iri.starts_with("_:") || Iri::parse(iri.as_ref()).is_ok())
659 {
660 definition.iri_mapping = Some(Some(iri.into()));
661 } else {
662 errors.push(JsonLdSyntaxError::msg_and_code(
663 format!("{iri} is not a valid IRI or blank node"),
664 JsonLdErrorCode::InvalidIriMapping,
665 ));
666 definition.iri_mapping = Some(None);
667 }
668 } else {
669 definition.iri_mapping = Some(None);
670 }
671 definition.iri_mapping = Some(
672 self.expand_iri(
673 active_context,
674 value.into(),
675 false,
676 true,
677 Some(local_context),
678 defined,
679 errors,
680 )
681 .map(Into::into),
682 );
683 definition.reverse_property = true;
685 }
686 "@id" => {
688 match key_value {
689 JsonNode::Null => {
691 definition.iri_mapping = Some(None);
692 }
693 JsonNode::String(id) => {
694 if id == term {
695 continue;
696 }
697 let Some(expanded) = self.expand_iri(
698 active_context,
699 id.into(),
700 false,
701 true,
702 Some(local_context),
703 defined,
704 errors,
705 ) else {
706 definition.iri_mapping = Some(None);
708 continue;
709 };
710 if expanded == "@context" {
712 errors.push(JsonLdSyntaxError::msg_and_code(
713 "@context cannot be aliased with @id: @context",
714 JsonLdErrorCode::InvalidKeywordAlias,
715 ));
716 continue;
717 }
718 definition.iri_mapping = Some(Some(expanded.into()));
719 if term
721 .as_bytes()
722 .get(1..term.len() - 1)
723 .is_some_and(|t| t.contains(&b':'))
724 || term.contains('/')
725 {
726 defined.insert(term.into(), true);
728 let expended_term = self.expand_iri(
729 active_context,
730 term.into(),
731 false,
732 true,
733 Some(local_context),
734 defined,
735 errors,
736 );
737 if expended_term.as_deref()
739 != definition.iri_mapping.as_ref().and_then(|o| o.as_deref())
740 {
741 errors.push(JsonLdSyntaxError::msg_and_code(
742 format!("Inconsistent expansion of {term}"),
743 JsonLdErrorCode::InvalidIriMapping,
744 ))
745 }
746 }
747 if !term.contains(':')
749 && !term.contains('/')
750 && definition.iri_mapping.as_ref().is_some_and(|iri| {
751 iri.as_ref().is_some_and(|iri| {
752 iri.ends_with(|c| {
753 matches!(c, ':' | '/' | '?' | '#' | '[' | ']' | '@')
754 }) || iri.starts_with("_:")
755 })
756 })
757 {
758 definition.prefix_flag = true;
759 }
760 }
761 _ => {
763 definition.iri_mapping = Some(None);
764 errors.push(JsonLdSyntaxError::msg_and_code(
765 "@id value must be a string",
766 JsonLdErrorCode::InvalidIriMapping,
767 ))
768 }
769 }
770 }
771 "@container" => {
773 const ALLOWED_CONTAINER_MAPPINGS: &[&[&str]] = &[
774 &["@index"],
775 &["@language"],
776 &["@list"],
777 &["@set"],
778 &["@index", "@set"],
779 &["@language", "@set"],
780 &["@graph"],
781 &["@graph", "@id"],
782 &["@graph", "@index"],
783 &["@graph", "@id", "@set"],
784 &["@graph", "@index", "@set"],
785 &["@id"],
786 &["@id", "@set"],
787 &["@type"],
788 &["@type", "@set"],
789 ];
790
791 let mut container_mapping = Vec::new();
793 for value in if let JsonNode::Array(value) = key_value {
794 if self.processing_mode == JsonLdProcessingMode::JsonLd1_0 {
795 errors.push(JsonLdSyntaxError::msg_and_code(
796 "@container definition with multiple values is not supported in JSON-LD 1.0",
797 JsonLdErrorCode::InvalidContainerMapping,
798 ));
799 continue;
800 }
801 value.as_slice()
802 } else {
803 slice::from_ref(key_value)
804 } {
805 if let JsonNode::String(container) = value {
806 container_mapping.push(container.as_str());
807 } else {
808 errors.push(JsonLdSyntaxError::msg_and_code(
809 "@container value must be a string or an array of strings",
810 JsonLdErrorCode::InvalidContainerMapping,
811 ));
812 }
813 }
814 container_mapping.sort_unstable();
815 let Some(container_mapping) = ALLOWED_CONTAINER_MAPPINGS
816 .iter()
817 .find_map(|c| (*c == container_mapping).then_some(*c))
818 else {
819 errors.push(JsonLdSyntaxError::msg_and_code(
820 "Not supported @container value combination",
821 JsonLdErrorCode::InvalidContainerMapping,
822 ));
823 continue;
824 };
825 if self.processing_mode == JsonLdProcessingMode::JsonLd1_0 {
827 let mut found = false;
828 for bad in ["@graph", "@id", "@type"] {
829 if container_mapping.contains(&bad) {
830 errors.push(JsonLdSyntaxError::msg_and_code(
831 format!("{bad} container is not supported in JSON-LD 1.0"),
832 JsonLdErrorCode::InvalidContainerMapping,
833 ));
834 found = true;
835 }
836 }
837 if found {
838 continue;
839 }
840 }
841 definition.container_mapping = container_mapping;
843 if container_mapping.contains(&"@type") {
845 if let Some(type_mapping) = &definition.type_mapping {
846 if !["@id", "@vocab"].contains(&type_mapping.as_str()) {
847 errors.push(JsonLdSyntaxError::msg_and_code(
848 format!("Type mapping must be @id or @vocab, not {type_mapping} when used with @type container"),
849 JsonLdErrorCode::InvalidContainerMapping,
850 ));
851 }
852 } else {
853 definition.type_mapping = Some("@id".into());
854 }
855 }
856 }
857 "@index" => {
859 errors.push(JsonLdSyntaxError::msg("@index is not implemented yet"));
860 }
862 "@context" => {
864 errors.push(JsonLdSyntaxError::msg(
865 "@context in context is not implemented yet",
866 )); }
868 "@language" => {
870 if value.contains_key("@type") {
871 errors.push(JsonLdSyntaxError::msg_and_code(
872 "Both @language and @type can't be set at the same time",
873 JsonLdErrorCode::InvalidLanguageMapping,
874 ));
875 }
876 definition.language_mapping = Some(match key_value {
877 JsonNode::String(language) => Some(language.clone()),
878 JsonNode::Null => None,
879 _ => {
880 errors.push(JsonLdSyntaxError::msg_and_code(
881 "@language value must be a string or null",
882 JsonLdErrorCode::InvalidLanguageMapping,
883 ));
884 continue;
885 }
886 })
887 }
888 "@direction" => {
890 if self.processing_mode == JsonLdProcessingMode::JsonLd1_0 {
892 errors.push(JsonLdSyntaxError::msg_and_code(
893 "@direction is only supported in JSON-LD 1.1",
894 JsonLdErrorCode::InvalidTermDefinition,
895 ));
896 continue;
897 }
898 unimplemented!()
899 }
900 "@nest" => {
902 if self.processing_mode == JsonLdProcessingMode::JsonLd1_0 {
904 errors.push(JsonLdSyntaxError::msg_and_code(
905 "@nest is only supported in JSON-LD 1.1",
906 JsonLdErrorCode::InvalidTermDefinition,
907 ));
908 continue;
909 }
910 unimplemented!()
911 }
912 "@prefix" => {
914 if self.processing_mode == JsonLdProcessingMode::JsonLd1_0 {
916 errors.push(JsonLdSyntaxError::msg_and_code(
917 "@prefix is only supported in JSON-LD 1.1",
918 JsonLdErrorCode::InvalidTermDefinition,
919 ));
920 continue;
921 }
922 unimplemented!()
923 }
924 _ => errors.push(JsonLdSyntaxError::msg_and_code(
926 format!("Unexpected key in term definition '{key}'"),
927 JsonLdErrorCode::InvalidTermDefinition,
928 )),
929 }
930 }
931 if definition.reverse_property
933 && !matches!(definition.container_mapping, [] | ["@index" | "@set"])
934 {
935 errors.push(JsonLdSyntaxError::msg_and_code(
936 "@reverse is only compatible with @index or @set containers",
937 JsonLdErrorCode::InvalidReverseProperty,
938 ))
939 }
940 if definition.iri_mapping.is_none() {
941 if let Some((prefix, suffix)) = term.split_once(':').and_then(|(prefix, suffix)| {
942 if prefix.is_empty() {
943 suffix.split_once(':')
945 } else {
946 Some((prefix, suffix))
947 }
948 }) {
949 if local_context.contains_key(prefix) {
951 self.create_term_definition(
953 active_context,
954 local_context,
955 prefix,
956 defined,
957 base_url,
958 false,
959 false,
960 remote_contexts,
961 errors,
962 )
963 }
964 if let Some(term_definition) = active_context.term_definitions.get(prefix) {
965 if let Some(Some(iri_mapping)) = &term_definition.iri_mapping {
967 definition.iri_mapping = Some(Some(format!("{iri_mapping}{suffix}")));
968 } else {
969 errors.push(JsonLdSyntaxError::msg(format!(
970 "The prefix '{prefix}' is not associated with an IRI in the context"
971 )));
972 }
973 } else {
974 definition.iri_mapping = Some(Some(term.into()));
976 }
977 } else if term.contains('/') {
978 if let Some(iri) = self.expand_iri(
980 active_context,
981 term.into(),
982 false,
983 true,
984 Some(local_context),
985 defined,
986 errors,
987 ) {
988 if has_keyword_form(&iri) {
989 errors.push(JsonLdSyntaxError::msg_and_code(
990 format!("Context term @id is not allowed to be a keyword, {iri} found"),
991 JsonLdErrorCode::InvalidIriMapping,
992 ))
993 } else if iri.starts_with("_:") {
994 errors.push(JsonLdSyntaxError::msg_and_code(
995 format!(
996 "Context term @id is not allowed to be a blank node, {iri} found"
997 ),
998 JsonLdErrorCode::InvalidIriMapping,
999 ))
1000 } else {
1001 definition.iri_mapping = Some(Some(iri.into()));
1002 }
1003 }
1004 } else if term == "@type" {
1005 definition.iri_mapping = Some(Some("@type".into()));
1007 } else {
1008 if let Some(vocabulary_mapping) = &active_context.vocabulary_mapping {
1010 definition.iri_mapping = Some(Some(format!("{vocabulary_mapping}{term}")));
1011 } else {
1012 errors.push(JsonLdSyntaxError::msg_and_code(
1013 format!(
1014 "No @vocab key to build an IRI from context {term} term definition"
1015 ),
1016 JsonLdErrorCode::InvalidIriMapping,
1017 ))
1018 }
1019 }
1020 }
1021 if !override_protected {
1023 if let Some(previous_definition) = previous_definition {
1024 if previous_definition.protected {
1025 if definition.prefix_flag != previous_definition.prefix_flag
1027 || definition.reverse_property != previous_definition.reverse_property
1028 || definition.iri_mapping != previous_definition.iri_mapping
1029 || definition.base_url != previous_definition.base_url
1030 {
1031 errors.push(JsonLdSyntaxError::msg_and_code(
1033 format!("Overriding the protected term {term}"),
1034 JsonLdErrorCode::ProtectedTermRedefinition,
1035 ));
1036 }
1037 definition = previous_definition;
1039 }
1040 }
1041 }
1042 active_context
1044 .term_definitions
1045 .insert(term.into(), definition);
1046 defined.insert(term.into(), true);
1047 }
1048
1049 pub fn expand_iri<'a>(
1051 &self,
1052 active_context: &mut JsonLdContext,
1053 value: Cow<'a, str>,
1054 document_relative: bool,
1055 vocab: bool,
1056 local_context: Option<&HashMap<String, JsonNode>>,
1057 defined: &mut HashMap<String, bool>,
1058 errors: &mut Vec<JsonLdSyntaxError>,
1059 ) -> Option<Cow<'a, str>> {
1060 if has_keyword_form(&value) {
1061 return is_keyword(&value).then_some(value);
1063 }
1064 if let Some(local_context) = local_context {
1066 if local_context.contains_key(value.as_ref())
1067 && defined.get(value.as_ref()) != Some(&true)
1068 {
1069 self.create_term_definition(
1070 active_context,
1071 local_context,
1072 &value,
1073 defined,
1074 None,
1075 false,
1076 false,
1077 &mut Vec::new(),
1078 errors,
1079 )
1080 }
1081 }
1082 if let Some(term_definition) = active_context.term_definitions.get(value.as_ref()) {
1083 if let Some(iri_mapping) = &term_definition.iri_mapping {
1084 let iri_mapping = iri_mapping.as_ref()?;
1085 if is_keyword(iri_mapping) {
1087 return Some(iri_mapping.clone().into());
1088 }
1089 if vocab {
1091 return Some(iri_mapping.clone().into());
1092 }
1093 }
1094 }
1095 if let Some((prefix, suffix)) = value.split_once(':') {
1097 if prefix == "_" || suffix.starts_with("//") {
1099 return Some(value);
1100 }
1101 if let Some(local_context) = local_context {
1103 if local_context.contains_key(prefix) && defined.get(prefix) != Some(&true) {
1104 self.create_term_definition(
1105 active_context,
1106 local_context,
1107 prefix,
1108 defined,
1109 None,
1110 false,
1111 false,
1112 &mut Vec::new(),
1113 errors,
1114 )
1115 }
1116 }
1117 if let Some(term_definition) = active_context.term_definitions.get(prefix) {
1119 if let Some(Some(iri_mapping)) = &term_definition.iri_mapping {
1120 if term_definition.prefix_flag {
1121 return Some(format!("{iri_mapping}{suffix}").into());
1122 }
1123 }
1124 }
1125 if Iri::parse(value.as_ref()).is_ok() {
1127 return Some(value);
1128 }
1129 }
1130 if vocab {
1132 if let Some(vocabulary_mapping) = &active_context.vocabulary_mapping {
1133 return Some(format!("{vocabulary_mapping}{value}").into());
1134 }
1135 }
1136 if document_relative {
1138 if let Some(base_iri) = &active_context.base_iri {
1139 if self.lenient {
1140 return Some(base_iri.resolve_unchecked(&value).into_inner().into());
1141 } else if let Ok(value) = base_iri.resolve(&value) {
1142 return Some(base_iri.resolve_unchecked(&value).into_inner().into());
1143 }
1144 }
1145 }
1146
1147 Some(value)
1148 }
1149}
1150
1151pub fn has_keyword_form(value: &str) -> bool {
1152 value
1153 .strip_prefix('@')
1154 .is_some_and(|suffix| suffix.bytes().all(|b| b.is_ascii_alphabetic()))
1155}
1156
1157fn is_keyword(value: &str) -> bool {
1158 matches!(
1159 value,
1160 "@base"
1161 | "@container"
1162 | "@context"
1163 | "@direction"
1164 | "@graph"
1165 | "@id"
1166 | "@import"
1167 | "@included"
1168 | "@index"
1169 | "@json"
1170 | "@language"
1171 | "@list"
1172 | "@nest"
1173 | "@none"
1174 | "@prefix"
1175 | "@propagate"
1176 | "@protected"
1177 | "@reverse"
1178 | "@set"
1179 | "@type"
1180 | "@value"
1181 | "@version"
1182 | "@vocab"
1183 )
1184}
1185
1186fn json_slice_to_node(data: &[u8]) -> Result<JsonNode, JsonSyntaxError> {
1187 let mut parser = SliceJsonParser::new(data);
1188 json_node_from_events(std::iter::from_fn(|| match parser.parse_next() {
1189 Ok(JsonEvent::Eof) => None,
1190 Ok(event) => Some(Ok(event)),
1191 Err(e) => Some(Err(e)),
1192 }))
1193}
1194
1195enum BuildingObjectOrArrayNode {
1196 Object(HashMap<String, JsonNode>),
1197 ObjectWithPendingKey(HashMap<String, JsonNode>, String),
1198 Array(Vec<JsonNode>),
1199}
1200
1201pub fn json_node_from_events<'a>(
1202 events: impl IntoIterator<Item = Result<JsonEvent<'a>, JsonSyntaxError>>,
1203) -> Result<JsonNode, JsonSyntaxError> {
1204 let mut stack = Vec::new();
1205 for event in events {
1206 if let Some(result) = match event? {
1207 JsonEvent::String(value) => {
1208 after_to_node_event(&mut stack, JsonNode::String(value.into()))
1209 }
1210 JsonEvent::Number(value) => {
1211 after_to_node_event(&mut stack, JsonNode::Number(value.into()))
1212 }
1213 JsonEvent::Boolean(value) => after_to_node_event(&mut stack, JsonNode::Boolean(value)),
1214 JsonEvent::Null => after_to_node_event(&mut stack, JsonNode::Null),
1215 JsonEvent::EndArray | JsonEvent::EndObject => {
1216 let value = match stack.pop() {
1217 Some(BuildingObjectOrArrayNode::Object(object)) => JsonNode::Object(object),
1218 Some(BuildingObjectOrArrayNode::Array(array)) => JsonNode::Array(array),
1219 _ => unreachable!(),
1220 };
1221 after_to_node_event(&mut stack, value)
1222 }
1223 JsonEvent::StartArray => {
1224 stack.push(BuildingObjectOrArrayNode::Array(Vec::new()));
1225 None
1226 }
1227 JsonEvent::StartObject => {
1228 stack.push(BuildingObjectOrArrayNode::Object(HashMap::new()));
1229 None
1230 }
1231 JsonEvent::ObjectKey(key) => {
1232 if let Some(BuildingObjectOrArrayNode::Object(object)) = stack.pop() {
1233 stack.push(BuildingObjectOrArrayNode::ObjectWithPendingKey(
1234 object,
1235 key.into(),
1236 ));
1237 }
1238 None
1239 }
1240 JsonEvent::Eof => unreachable!(),
1241 } {
1242 return Ok(result);
1243 }
1244 }
1245 unreachable!("The JSON emitted by the parser mut be valid")
1246}
1247
1248fn after_to_node_event(
1249 stack: &mut Vec<BuildingObjectOrArrayNode>,
1250 new_value: JsonNode,
1251) -> Option<JsonNode> {
1252 match stack.pop() {
1253 Some(BuildingObjectOrArrayNode::ObjectWithPendingKey(mut object, key)) => {
1254 object.insert(key, new_value);
1255 stack.push(BuildingObjectOrArrayNode::Object(object));
1256 None
1257 }
1258 Some(BuildingObjectOrArrayNode::Object(object)) => {
1259 stack.push(BuildingObjectOrArrayNode::Object(object));
1260 None
1261 }
1262 Some(BuildingObjectOrArrayNode::Array(mut array)) => {
1263 array.push(new_value);
1264 stack.push(BuildingObjectOrArrayNode::Array(array));
1265 None
1266 }
1267 None => Some(new_value),
1268 }
1269}