1use crate::error::{JsonLdErrorCode, JsonLdSyntaxError};
2use crate::{JsonLdProcessingMode, JsonLdProfile, JsonLdProfileSet};
3use json_event_parser::{JsonEvent, JsonSyntaxError, SliceJsonParser};
4use oxiri::Iri;
5use std::borrow::Cow;
6use std::collections::HashMap;
7use std::error::Error;
8use std::panic::{RefUnwindSafe, UnwindSafe};
9use std::slice;
10use std::sync::{Arc, Mutex};
11
12type LoadDocumentCallback = dyn Fn(
13 &str,
14 &JsonLdLoadDocumentOptions,
15 ) -> Result<JsonLdRemoteDocument, Box<dyn Error + Send + Sync>>
16 + Send
17 + Sync
18 + UnwindSafe
19 + RefUnwindSafe;
20
21#[derive(Eq, PartialEq, Debug, Clone)]
22pub enum JsonNode {
23 String(String),
24 Number(String),
25 Boolean(bool),
26 Null,
27 Array(Vec<JsonNode>),
28 Object(HashMap<String, JsonNode>),
29}
30
31#[derive(Default, Clone)]
32pub struct JsonLdContext {
33 pub base_iri: Option<Iri<String>>,
34 pub original_base_url: Option<Iri<String>>,
35 pub vocabulary_mapping: Option<String>,
36 pub default_language: Option<String>,
37 pub default_direction: Option<&'static str>,
38 pub term_definitions: HashMap<String, JsonLdTermDefinition>,
39 pub previous_context: Option<Arc<JsonLdContext>>,
40}
41
42impl JsonLdContext {
43 pub fn new_empty(original_base_url: Option<Iri<String>>) -> Self {
44 JsonLdContext {
45 base_iri: original_base_url.clone(),
46 original_base_url,
47 vocabulary_mapping: None,
48 default_language: None,
49 default_direction: None,
50 term_definitions: HashMap::new(),
51 previous_context: None,
52 }
53 }
54}
55
56#[derive(Clone)]
57pub struct JsonLdTermDefinition {
58 pub iri_mapping: Option<Option<String>>,
60 pub prefix_flag: bool,
61 pub protected: bool,
62 pub reverse_property: bool,
63 pub base_url: Option<Iri<String>>,
64 pub context: Option<JsonNode>,
65 pub container_mapping: &'static [&'static str],
66 pub direction_mapping: Option<Option<&'static str>>,
67 pub index_mapping: Option<String>,
68 pub language_mapping: Option<Option<String>>,
69 pub nest_value: Option<String>,
70 pub type_mapping: Option<String>,
71}
72
73pub struct JsonLdContextProcessor {
74 pub processing_mode: JsonLdProcessingMode,
75 pub lenient: bool, pub max_context_recursion: usize,
77 pub remote_context_cache: Arc<Mutex<HashMap<String, (Option<Iri<String>>, JsonNode)>>>,
78 pub load_document_callback: Option<Arc<LoadDocumentCallback>>,
79}
80
81pub struct JsonLdLoadDocumentOptions {
83 pub request_profile: JsonLdProfileSet,
85}
86
87pub struct JsonLdRemoteDocument {
89 pub document: Vec<u8>,
91 pub document_url: String,
93}
94
95impl JsonLdContextProcessor {
96 pub fn process_context(
98 &self,
99 active_context: &JsonLdContext,
100 local_context: JsonNode,
101 base_url: Option<&Iri<String>>,
102 remote_contexts: &mut Vec<String>,
103 override_protected: bool,
104 mut propagate: bool,
105 validate_scoped_context: bool,
106 errors: &mut Vec<JsonLdSyntaxError>,
107 ) -> JsonLdContext {
108 let mut result = active_context.clone();
110 if let JsonNode::Object(local_context) = &local_context {
112 if let Some(propagate_node) = local_context.get("@propagate") {
113 if let JsonNode::Boolean(new) = propagate_node {
114 propagate = *new;
115 } else {
116 errors.push(JsonLdSyntaxError::msg_and_code(
117 "@propagate value must be a boolean",
118 JsonLdErrorCode::InvalidPropagateValue,
119 ))
120 }
121 }
122 }
123 if !propagate && result.previous_context.is_none() {
125 result.previous_context = Some(Arc::new(active_context.clone()));
126 }
127 let local_context = if let JsonNode::Array(c) = local_context {
129 c
130 } else {
131 vec![local_context]
132 };
133 for context in local_context {
135 let mut context = match context {
136 JsonNode::Null => {
138 if !override_protected {
140 for (name, def) in &active_context.term_definitions {
141 if def.protected {
142 errors.push(JsonLdSyntaxError::msg_and_code(format!("Definition of {name} will be overridden even if it's protected"), JsonLdErrorCode::InvalidContextNullification));
143 }
144 }
145 }
146 let mut new_result =
148 JsonLdContext::new_empty(active_context.original_base_url.clone());
149 if !propagate {
150 new_result.previous_context = Some(Arc::new(result));
151 }
152 result = new_result;
153 continue;
155 }
156 JsonNode::String(context) => {
158 let context = match if let Some(base_url) = base_url {
160 base_url.resolve(&context)
161 } else {
162 Iri::parse(context.clone())
163 } {
164 Ok(url) => url.into_inner(),
165 Err(e) => {
166 errors.push(JsonLdSyntaxError::msg_and_code(
167 format!("Invalid remote context URL '{context}': {e}"),
168 JsonLdErrorCode::LoadingDocumentFailed,
169 ));
170 continue;
171 }
172 };
173 if !validate_scoped_context && remote_contexts.contains(&context) {
175 continue;
176 }
177 if remote_contexts.len() >= self.max_context_recursion {
179 errors.push(JsonLdSyntaxError::msg_and_code(
180 format!(
181 "This processor only allows {} remote context, threshold exceeded",
182 self.max_context_recursion
183 ),
184 JsonLdErrorCode::ContextOverflow,
185 ));
186 continue;
187 }
188 remote_contexts.push(context.clone());
189 let (loaded_context_base, loaded_context_content) =
190 match self.load_remote_context(&context) {
191 Ok(r) => r,
192 Err(e) => {
193 errors.push(e);
194 continue;
195 }
196 };
197 result = self.process_context(
199 &result,
200 loaded_context_content,
201 loaded_context_base.as_ref(),
202 remote_contexts,
203 false,
204 true,
205 validate_scoped_context,
206 errors,
207 );
208 assert_eq!(
209 remote_contexts.pop(),
210 Some(context),
211 "The remote context stack is invalid"
212 );
213 continue;
214 }
215 JsonNode::Array(_) | JsonNode::Number(_) | JsonNode::Boolean(_) => {
217 errors.push(JsonLdSyntaxError::msg_and_code(
218 "@context value must be null, a string or an object",
219 JsonLdErrorCode::InvalidLocalContext,
220 ));
221 continue;
222 }
223 JsonNode::Object(context) => context,
225 };
226 if let Some(value) = context.remove("@version") {
228 if let JsonNode::Number(version) = value {
230 if version != "1.1" {
231 errors.push(JsonLdSyntaxError::msg_and_code(
232 format!("The only supported @version value is 1.1, found {version}"),
233 JsonLdErrorCode::InvalidVersionValue,
234 ));
235 }
236 } else {
237 errors.push(JsonLdSyntaxError::msg_and_code(
238 "@version value must be a number",
239 JsonLdErrorCode::InvalidVersionValue,
240 ));
241 }
242 if self.processing_mode == JsonLdProcessingMode::JsonLd1_0 {
244 errors.push(JsonLdSyntaxError::msg_and_code(
245 "@version is only supported in JSON-LD 1.1",
246 JsonLdErrorCode::ProcessingModeConflict,
247 ));
248 }
249 }
250 if let Some(value) = context.remove("@import") {
252 if self.processing_mode == JsonLdProcessingMode::JsonLd1_0 {
254 errors.push(JsonLdSyntaxError::msg_and_code(
255 "@import is only supported in JSON-LD 1.1",
256 JsonLdErrorCode::InvalidContextEntry,
257 ));
258 }
259 let JsonNode::String(import) = value else {
261 errors.push(JsonLdSyntaxError::msg_and_code(
262 "@import must be a string",
263 JsonLdErrorCode::InvalidImportValue,
264 ));
265 continue;
266 };
267 let import = match if let Some(base_url) = base_url {
269 base_url.resolve(&import)
270 } else {
271 Iri::parse(import.clone())
272 } {
273 Ok(import) => import,
274 Err(e) => {
275 errors.push(JsonLdSyntaxError::msg_and_code(
276 format!("Invalid @import iri {import}: {e}"),
277 JsonLdErrorCode::InvalidImportValue,
278 ));
279 continue;
280 }
281 };
282 let (_, loaded_context_content) = match self.load_remote_context(&import) {
284 Ok(r) => r,
285 Err(e) => {
286 errors.push(e);
287 continue;
288 }
289 };
290 let JsonNode::Object(loaded_context_content) = loaded_context_content else {
292 errors.push(JsonLdSyntaxError::msg_and_code(
293 format!("Imported context {import} must be an object"),
294 JsonLdErrorCode::InvalidRemoteContext,
295 ));
296 continue;
297 };
298 if loaded_context_content.contains_key("@import") {
300 errors.push(JsonLdSyntaxError::msg_and_code(
301 format!("Imported context {import} must not contain an @import key"),
302 JsonLdErrorCode::InvalidContextEntry,
303 ));
304 continue;
305 }
306 for (key, value) in loaded_context_content {
308 context.entry(key).or_insert(value);
309 }
310 }
311 if let Some(value) = context.remove("@base") {
313 if remote_contexts.is_empty() {
314 match value {
315 JsonNode::Null => {
317 result.base_iri = None;
318 }
319 JsonNode::String(value) => {
321 if self.lenient {
322 result.base_iri = Some(if let Some(base_iri) = &result.base_iri {
323 base_iri.resolve_unchecked(&value)
324 } else {
325 Iri::parse_unchecked(value.clone())
326 })
327 } else {
328 match if let Some(base_iri) = &result.base_iri {
329 base_iri.resolve(&value)
330 } else {
331 Iri::parse(value.clone())
332 } {
333 Ok(iri) => result.base_iri = Some(iri),
334 Err(e) => errors.push(JsonLdSyntaxError::msg_and_code(
335 format!("Invalid @base '{value}': {e}"),
336 JsonLdErrorCode::InvalidBaseIri,
337 )),
338 }
339 }
340 }
341 _ => errors.push(JsonLdSyntaxError::msg_and_code(
342 "@base value must be a string",
343 JsonLdErrorCode::InvalidBaseIri,
344 )),
345 }
346 }
347 }
348 if let Some(value) = context.remove("@vocab") {
350 match value {
351 JsonNode::Null => {
353 result.vocabulary_mapping = None;
354 }
355 JsonNode::String(value) => {
357 if let Some(vocab) = self
358 .expand_iri(
359 &mut result,
360 value.as_str().into(),
361 true,
362 true,
363 None,
364 &mut HashMap::new(),
365 errors,
366 )
367 .filter(|iri| !has_keyword_form(iri))
368 {
369 result.vocabulary_mapping = Some(vocab.into());
370 } else {
371 errors.push(JsonLdSyntaxError::msg_and_code(
372 format!("Invalid @vocab '{value}'"),
373 JsonLdErrorCode::InvalidVocabMapping,
374 ));
375 }
376 }
377 _ => errors.push(JsonLdSyntaxError::msg_and_code(
378 "@vocab value must be a string",
379 JsonLdErrorCode::InvalidVocabMapping,
380 )),
381 }
382 }
383 if let Some(value) = context.remove("@language") {
385 match value {
386 JsonNode::Null => {
388 result.default_language = None;
389 }
390 JsonNode::String(value) => result.default_language = Some(value),
392 _ => errors.push(JsonLdSyntaxError::msg_and_code(
393 "@language value must be a string or null",
394 JsonLdErrorCode::InvalidDefaultLanguage,
395 )),
396 }
397 }
398 if let Some(value) = context.remove("@direction") {
400 if self.processing_mode == JsonLdProcessingMode::JsonLd1_0 {
402 errors.push(JsonLdSyntaxError::msg_and_code(
403 "@direction is only supported in JSON-LD 1.1",
404 JsonLdErrorCode::InvalidContextEntry,
405 ));
406 }
407 match value {
408 JsonNode::Null => {
410 result.default_direction = None;
411 }
412 JsonNode::String(value) => match value.as_str() {
414 "ltr" => result.default_direction = Some("ltr"),
415 "rtl" => result.default_direction = Some("rtl"),
416 _ => errors.push(JsonLdSyntaxError::msg_and_code(
417 format!("@direction value must be 'ltr' or 'rtl', found '{value}'"),
418 JsonLdErrorCode::InvalidBaseDirection,
419 )),
420 },
421 _ => errors.push(JsonLdSyntaxError::msg_and_code(
422 "@direction value must be a string or null",
423 JsonLdErrorCode::InvalidBaseDirection,
424 )),
425 }
426 }
427 if let Some(value) = context.remove("@propagate") {
429 if self.processing_mode == JsonLdProcessingMode::JsonLd1_0 {
431 errors.push(JsonLdSyntaxError::msg_and_code(
432 "@propagate is only supported in JSON-LD 1.1",
433 JsonLdErrorCode::InvalidContextEntry,
434 ));
435 }
436 if !matches!(value, JsonNode::Boolean(_)) {
438 errors.push(JsonLdSyntaxError::msg_and_code(
439 "@propagate value must be a boolean",
440 JsonLdErrorCode::InvalidPropagateValue,
441 ));
442 continue;
443 }
444 }
445 let mut protected = false;
447 if let Some(value) = context.remove("@protected") {
448 if self.processing_mode == JsonLdProcessingMode::JsonLd1_0 {
449 errors.push(JsonLdSyntaxError::msg_and_code(
450 "@protected is only supported in JSON-LD 1.1",
451 JsonLdErrorCode::InvalidContextEntry,
452 ));
453 }
454 if let JsonNode::Boolean(value) = value {
455 protected = value
456 } else {
457 errors.push(JsonLdSyntaxError::msg_and_code(
458 "@protected value must be a boolean",
459 JsonLdErrorCode::InvalidProtectedValue,
460 ))
461 }
462 }
463 let mut defined = HashMap::new();
464 for term in context.keys() {
465 self.create_term_definition(
466 &mut result,
467 &context,
468 term,
469 &mut defined,
470 base_url,
471 protected,
472 override_protected,
473 remote_contexts,
474 errors,
475 )
476 }
477 }
478 result
480 }
481
482 fn create_term_definition(
484 &self,
485 active_context: &mut JsonLdContext,
486 local_context: &HashMap<String, JsonNode>,
487 term: &str,
488 defined: &mut HashMap<String, bool>,
489 base_url: Option<&Iri<String>>,
490 protected: bool,
491 override_protected: bool,
492 remote_contexts: &mut Vec<String>,
493 errors: &mut Vec<JsonLdSyntaxError>,
494 ) {
495 if let Some(defined_value) = defined.get(term) {
497 if !defined_value {
498 errors.push(JsonLdSyntaxError::msg_and_code(
499 "Cyclic IRI mapping",
500 JsonLdErrorCode::CyclicIriMapping,
501 ))
502 }
503 return;
504 }
505 if term.is_empty() {
507 errors.push(JsonLdSyntaxError::msg_and_code(
508 "@context terms must not be the empty strings",
509 JsonLdErrorCode::InvalidTermDefinition,
510 ));
511 return;
512 }
513 defined.insert(term.into(), false);
514 let Some(value) = local_context.get(term) else {
516 unreachable!();
517 };
518 if term == "@type" {
520 if self.processing_mode == JsonLdProcessingMode::JsonLd1_0 {
521 errors.push(JsonLdSyntaxError::msg_and_code(
522 "@type keyword can't be redefined in JSON-LD 1.0 @context",
523 JsonLdErrorCode::KeywordRedefinition,
524 ));
525 }
526 if let JsonNode::Object(value) = value {
527 if value.is_empty() {
528 errors.push(JsonLdSyntaxError::msg_and_code(
529 "@type keyword definition can't be empty",
530 JsonLdErrorCode::KeywordRedefinition,
531 ));
532 return;
533 }
534 for (key, key_value) in value {
535 match key.as_str() {
536 "@protected" => (),
537 "@container" => match key_value {
538 JsonNode::String(s) if s == "@set" => (),
539 JsonNode::Array(s)
540 if s.iter().all(|v| {
541 if let JsonNode::String(s) = v {
542 s == "@set"
543 } else {
544 false
545 }
546 }) => {}
547 _ => {
548 errors.push(JsonLdSyntaxError::msg_and_code(
549 "@type definition only allowed @container is @set",
550 JsonLdErrorCode::KeywordRedefinition,
551 ));
552 return;
553 }
554 },
555 _ => {
556 errors.push(JsonLdSyntaxError::msg_and_code(
557 format!("@type definition can only contain @protected and @container keywords, {key} found"),
558 JsonLdErrorCode::KeywordRedefinition,
559 ));
560 return;
561 }
562 }
563 }
564 } else {
565 errors.push(JsonLdSyntaxError::msg_and_code(
566 "@type definition must be an object",
567 JsonLdErrorCode::KeywordRedefinition,
568 ));
569 return;
570 }
571 } else if has_keyword_form(term) {
572 if is_keyword(term) {
574 errors.push(JsonLdSyntaxError::msg_and_code(
575 format!("{term} keyword can't be redefined in context"),
576 JsonLdErrorCode::KeywordRedefinition,
577 ));
578 }
579 return;
580 }
581 let previous_definition = active_context.term_definitions.remove(term);
583 let value = match value {
584 JsonNode::Null => Cow::Owned([("@id".to_owned(), JsonNode::Null)].into()),
586 JsonNode::String(id) => {
588 Cow::Owned([("@id".to_owned(), JsonNode::String(id.clone()))].into())
589 }
590 JsonNode::Object(map) => Cow::Borrowed(map),
592 _ => {
593 errors.push(JsonLdSyntaxError::msg_and_code(
594 "Term definition value must be null, a string or a map",
595 JsonLdErrorCode::InvalidTermDefinition,
596 ));
597 return;
598 }
599 };
600 let mut definition = JsonLdTermDefinition {
602 iri_mapping: None,
603 prefix_flag: false,
604 protected,
605 reverse_property: false,
606 base_url: None,
607 context: None,
608 container_mapping: &[],
609 direction_mapping: None,
610 index_mapping: None,
611 language_mapping: None,
612 nest_value: None,
613 type_mapping: None,
614 };
615 if let Some(key_value) = value.get("@protected") {
617 if self.processing_mode == JsonLdProcessingMode::JsonLd1_0 {
618 errors.push(JsonLdSyntaxError::msg_and_code(
619 "@protected keyword can't be used in JSON-LD 1.0 @context",
620 JsonLdErrorCode::InvalidTermDefinition,
621 ));
622 }
623 let JsonNode::Boolean(key_value) = key_value else {
624 errors.push(JsonLdSyntaxError::msg_and_code(
625 "@protected value must be a boolean",
626 JsonLdErrorCode::InvalidProtectedValue,
627 ));
628 return;
629 };
630 definition.protected = *key_value;
631 }
632 if let Some(key_value) = value.get("@type") {
634 let JsonNode::String(r#type) = key_value else {
636 errors.push(JsonLdSyntaxError::msg_and_code(
637 "The value of @type in a context must be a string",
638 JsonLdErrorCode::InvalidTypeMapping,
639 ));
640 return;
641 };
642 let Some(r#type) = self.expand_iri(
644 active_context,
645 r#type.as_str().into(),
646 false,
647 true,
648 Some(local_context),
649 defined,
650 errors,
651 ) else {
652 errors.push(JsonLdSyntaxError::msg_and_code(
653 format!("Invalid @type value in context: {type}"),
654 JsonLdErrorCode::InvalidTypeMapping,
655 ));
656 return;
657 };
658 if matches!(r#type.as_ref(), "@json" | "@none")
660 && self.processing_mode == JsonLdProcessingMode::JsonLd1_0
661 {
662 errors.push(JsonLdSyntaxError::msg_and_code(
663 format!("@type value {type} in a context is only supported in JSON-LD 1.1"),
664 JsonLdErrorCode::InvalidTypeMapping,
665 ));
666 }
667 let is_keyword = has_keyword_form(&r#type);
669 if is_keyword && !matches!(r#type.as_ref(), "@id" | "@json" | "@none" | "@vocab")
670 || r#type.starts_with("_:")
671 {
672 errors.push(JsonLdSyntaxError::msg_and_code(
673 format!("Invalid @type value in context: {type}"),
674 JsonLdErrorCode::InvalidTypeMapping,
675 ));
676 }
677 if !self.lenient && !is_keyword {
678 if let Err(e) = Iri::parse(r#type.as_ref()) {
679 errors.push(JsonLdSyntaxError::msg_and_code(
680 format!("Invalid @type iri '{type}': {e}"),
681 JsonLdErrorCode::InvalidTypeMapping,
682 ));
683 }
684 }
685 definition.type_mapping = Some(r#type.into());
687 }
688 if let Some(key_value) = value.get("@reverse") {
690 if value.contains_key("@id") {
692 errors.push(JsonLdSyntaxError::msg_and_code(
693 "@reverse and @id cannot be used together in a context",
694 JsonLdErrorCode::InvalidReverseProperty,
695 ));
696 return;
697 }
698 if value.contains_key("@nest") {
699 errors.push(JsonLdSyntaxError::msg_and_code(
700 "@reverse and @nest cannot be used together in a context",
701 JsonLdErrorCode::InvalidReverseProperty,
702 ));
703 return;
704 }
705 let JsonNode::String(key_value) = key_value else {
707 errors.push(JsonLdSyntaxError::msg_and_code(
708 "@reverse value must be a string in a context",
709 JsonLdErrorCode::InvalidIriMapping,
710 ));
711 return;
712 };
713 if has_keyword_form(key_value) {
715 return;
716 }
717 if let Some(iri) = self.expand_iri(
719 active_context,
720 key_value.into(),
721 false,
722 true,
723 Some(local_context),
724 defined,
725 errors,
726 ) {
727 if self.lenient && !has_keyword_form(&iri)
728 || !self.lenient && (iri.starts_with("_:") || Iri::parse(iri.as_ref()).is_ok())
729 {
730 definition.iri_mapping = Some(Some(iri.into()));
731 } else {
732 errors.push(JsonLdSyntaxError::msg_and_code(
733 format!("{iri} is not a valid IRI or blank node"),
734 JsonLdErrorCode::InvalidIriMapping,
735 ));
736 definition.iri_mapping = Some(None);
737 }
738 } else {
739 definition.iri_mapping = Some(None);
740 }
741 definition.iri_mapping = Some(
742 self.expand_iri(
743 active_context,
744 key_value.into(),
745 false,
746 true,
747 Some(local_context),
748 defined,
749 errors,
750 )
751 .map(Into::into),
752 );
753 if let Some(container_entry) = value.get("@container") {
755 match container_entry {
756 JsonNode::Null => (),
757 JsonNode::String(container_entry) => {
758 if !matches!(container_entry.as_str(), "@index" | "@set") {
759 errors.push(JsonLdSyntaxError::msg_and_code(
760 "@reverse is only compatible with @index and @set containers",
761 JsonLdErrorCode::InvalidReverseProperty,
762 ));
763 }
764 }
765 _ => {
766 errors.push(JsonLdSyntaxError::msg_and_code(
767 "@container value must be a string or null",
768 JsonLdErrorCode::InvalidReverseProperty,
769 ));
770 }
771 }
772 }
773 definition.reverse_property = true;
775 } else if let Some(key_value) = value.get("@id").filter(|v| {
776 if let JsonNode::String(v) = v {
777 v != term
778 } else {
779 true
780 }
781 }) {
782 match key_value {
784 JsonNode::Null => {
786 definition.iri_mapping = Some(None);
787 }
788 JsonNode::String(id) => {
789 if id == term {
790 return;
791 }
792 let Some(expanded) = self.expand_iri(
793 active_context,
794 id.into(),
795 false,
796 true,
797 Some(local_context),
798 defined,
799 errors,
800 ) else {
801 definition.iri_mapping = Some(None);
803 return;
804 };
805 if expanded == "@context" {
807 errors.push(JsonLdSyntaxError::msg_and_code(
808 "@context cannot be aliased with @id: @context",
809 JsonLdErrorCode::InvalidKeywordAlias,
810 ));
811 return;
812 }
813 definition.iri_mapping = Some(Some(expanded.into()));
814 if term
816 .as_bytes()
817 .get(1..term.len() - 1)
818 .is_some_and(|t| t.contains(&b':'))
819 || term.contains('/')
820 {
821 defined.insert(term.into(), true);
823 let expended_term = self.expand_iri(
825 active_context,
826 term.into(),
827 false,
828 true,
829 Some(local_context),
830 defined,
831 errors,
832 );
833 if expended_term.as_deref()
834 != definition.iri_mapping.as_ref().and_then(|o| o.as_deref())
835 {
836 errors.push(JsonLdSyntaxError::msg_and_code(
837 if let (Some(expended_term), Some(Some(iri_mapping))) = (&expended_term, &definition.iri_mapping) {
838 format!("Inconsistent expansion of {term} between {expended_term} and {iri_mapping}")
839 } else {
840 format!("Inconsistent expansion of {term}")
841 },
842 JsonLdErrorCode::InvalidIriMapping,
843 ))
844 }
845 }
846 if !term.contains(':')
848 && !term.contains('/')
849 && definition.iri_mapping.as_ref().is_some_and(|iri| {
850 iri.as_ref().is_some_and(|iri| {
851 iri.ends_with(|c| {
852 matches!(c, ':' | '/' | '?' | '#' | '[' | ']' | '@')
853 }) || iri.starts_with("_:")
854 })
855 })
856 {
857 definition.prefix_flag = true;
858 }
859 }
860 _ => {
862 definition.iri_mapping = Some(None);
863 errors.push(JsonLdSyntaxError::msg_and_code(
864 "@id value must be a string",
865 JsonLdErrorCode::InvalidIriMapping,
866 ))
867 }
868 }
869 } else if let Some((prefix, suffix)) = term.split_once(':').and_then(|(prefix, suffix)| {
870 if prefix.is_empty() {
871 suffix.split_once(':')
873 } else {
874 Some((prefix, suffix))
875 }
876 }) {
877 if local_context.contains_key(prefix) {
879 self.create_term_definition(
881 active_context,
882 local_context,
883 prefix,
884 defined,
885 base_url,
886 false,
887 false,
888 remote_contexts,
889 errors,
890 )
891 }
892 if let Some(term_definition) = active_context.term_definitions.get(prefix) {
893 if let Some(Some(iri_mapping)) = &term_definition.iri_mapping {
895 definition.iri_mapping = Some(Some(format!("{iri_mapping}{suffix}")));
896 }
897 } else {
898 definition.iri_mapping = Some(Some(term.into()));
900 }
901 } else if term.contains('/') {
902 let iri = match if let Some(base_url) = base_url {
904 base_url.resolve(term)
905 } else {
906 Iri::parse(term.to_owned())
907 } {
908 Ok(iri) => iri.into_inner(),
909 Err(e) => {
910 errors.push(JsonLdSyntaxError::msg_and_code(
911 format!("Invalid term relative IRI '{term}': {e}"),
912 JsonLdErrorCode::InvalidIriMapping,
913 ));
914 return;
915 }
916 };
917 definition.iri_mapping = Some(Some(iri));
918 } else if term == "@type" {
919 definition.iri_mapping = Some(Some("@type".into()));
921 } else {
922 if let Some(vocabulary_mapping) = &active_context.vocabulary_mapping {
924 definition.iri_mapping = Some(Some(format!("{vocabulary_mapping}{term}")));
925 } else {
926 errors.push(JsonLdSyntaxError::msg_and_code(
927 format!("No @vocab key to build an IRI from context {term} term definition"),
928 JsonLdErrorCode::InvalidIriMapping,
929 ))
930 }
931 }
932 if let Some(key_value) = value.get("@container") {
934 const ALLOWED_CONTAINER_MAPPINGS: &[&[&str]] = &[
935 &["@index"],
936 &["@index", "@set"],
937 &["@language"],
938 &["@language", "@set"],
939 &["@graph"],
940 &["@graph", "@set"],
941 &["@graph", "@id"],
942 &["@graph", "@id", "@set"],
943 &["@graph", "@index"],
944 &["@graph", "@index", "@set"],
945 &["@id"],
946 &["@id", "@set"],
947 &["@list"],
948 &["@set"],
949 &["@type"],
950 &["@type", "@set"],
951 ];
952
953 let mut container_mapping = Vec::new();
955 for value in if let JsonNode::Array(value) = key_value {
956 if self.processing_mode == JsonLdProcessingMode::JsonLd1_0 {
957 errors.push(JsonLdSyntaxError::msg_and_code(
958 "@container definition with multiple values is not supported in JSON-LD 1.0",
959 JsonLdErrorCode::InvalidContainerMapping,
960 ));
961 }
962 value.as_slice()
963 } else {
964 slice::from_ref(key_value)
965 } {
966 if let JsonNode::String(container) = value {
967 container_mapping.push(container.as_str());
968 } else {
969 errors.push(JsonLdSyntaxError::msg_and_code(
970 "@container value must be a string or an array of strings",
971 JsonLdErrorCode::InvalidContainerMapping,
972 ));
973 }
974 }
975 container_mapping.sort_unstable();
976 let Some(container_mapping) = ALLOWED_CONTAINER_MAPPINGS
977 .iter()
978 .find_map(|c| (*c == container_mapping).then_some(*c))
979 else {
980 errors.push(JsonLdSyntaxError::msg_and_code(
981 "Not supported @container value combination",
982 JsonLdErrorCode::InvalidContainerMapping,
983 ));
984 return;
985 };
986 if self.processing_mode == JsonLdProcessingMode::JsonLd1_0 {
988 if let Some(bad) = ["@graph", "@id", "@type"]
989 .into_iter()
990 .find(|k| container_mapping.contains(k))
991 {
992 errors.push(JsonLdSyntaxError::msg_and_code(
993 format!("{bad} container is not supported in JSON-LD 1.0"),
994 JsonLdErrorCode::InvalidContainerMapping,
995 ));
996 }
997 }
998 definition.container_mapping = container_mapping;
1000 if container_mapping.contains(&"@type") {
1002 if let Some(type_mapping) = &definition.type_mapping {
1003 if !["@id", "@vocab"].contains(&type_mapping.as_str()) {
1004 errors.push(JsonLdSyntaxError::msg_and_code(
1005 format!("Type mapping must be @id or @vocab, not {type_mapping} when used with @type container"),
1006 JsonLdErrorCode::InvalidTypeMapping,
1007 ));
1008 }
1009 } else {
1010 definition.type_mapping = Some("@id".into());
1011 }
1012 }
1013 }
1014 if let Some(key_value) = value.get("@index") {
1016 if self.processing_mode == JsonLdProcessingMode::JsonLd1_0 {
1018 errors.push(JsonLdSyntaxError::msg_and_code(
1019 "@index inside of term definitions is only supported in JSON-LD 1.1",
1020 JsonLdErrorCode::InvalidTermDefinition,
1021 ));
1022 }
1023 if !definition.container_mapping.contains(&"@index") {
1024 errors.push(JsonLdSyntaxError::msg_and_code(
1025 "@index inside of term definitions is only allowed when @container is set to @index",
1026 JsonLdErrorCode::InvalidTermDefinition,
1027 ));
1028 }
1029 let JsonNode::String(index) = key_value else {
1031 errors.push(JsonLdSyntaxError::msg_and_code(
1032 "@index value must be a string",
1033 JsonLdErrorCode::InvalidTermDefinition,
1034 ));
1035 return;
1036 };
1037 let Some(expanded_index) = self.expand_iri(
1038 active_context,
1039 index.into(),
1040 false,
1041 true,
1042 Some(local_context),
1043 defined,
1044 errors,
1045 ) else {
1046 errors.push(JsonLdSyntaxError::msg_and_code(
1047 "@index value must be a valid IRI",
1048 JsonLdErrorCode::InvalidTermDefinition,
1049 ));
1050 return;
1051 };
1052 if self.lenient
1053 && (has_keyword_form(&expanded_index) || expanded_index.starts_with("_:"))
1054 || !self.lenient && Iri::parse(expanded_index.as_ref()).is_err()
1055 {
1056 errors.push(JsonLdSyntaxError::msg_and_code(
1057 "@index value must be a valid IRI",
1058 JsonLdErrorCode::InvalidTermDefinition,
1059 ));
1060 return;
1061 }
1062 definition.index_mapping = Some(index.into());
1064 }
1065 if let Some(key_value) = value.get("@context") {
1067 if self.processing_mode == JsonLdProcessingMode::JsonLd1_0 {
1069 errors.push(JsonLdSyntaxError::msg_and_code(
1070 "@context inside of term definitions is only supported in JSON-LD 1.1",
1071 JsonLdErrorCode::InvalidTermDefinition,
1072 ));
1073 }
1074 let context = key_value;
1076 let error_count = errors.len();
1078 self.process_context(
1079 active_context,
1080 context.clone(),
1081 base_url,
1082 remote_contexts,
1083 true,
1084 true,
1085 false,
1086 errors,
1087 );
1088 for error in errors.drain(error_count..).collect::<Vec<_>>() {
1089 errors.push(JsonLdSyntaxError::msg_and_code(
1090 format!("Invalid scoped context: {error}"),
1091 JsonLdErrorCode::InvalidScopedContext,
1092 ));
1093 }
1094 definition.context = Some(context.clone());
1096 definition.base_url = base_url.cloned();
1097 }
1098 if let Some(key_value) = value.get("@language") {
1100 if value.contains_key("@type") {
1101 errors.push(JsonLdSyntaxError::msg_and_code(
1102 "Both @language and @type can't be set at the same time",
1103 JsonLdErrorCode::InvalidLanguageMapping,
1104 ));
1105 }
1106 definition.language_mapping = Some(match key_value {
1107 JsonNode::String(language) => Some(language.clone()),
1108 JsonNode::Null => None,
1109 _ => {
1110 errors.push(JsonLdSyntaxError::msg_and_code(
1111 "@language value must be a string or null",
1112 JsonLdErrorCode::InvalidLanguageMapping,
1113 ));
1114 return;
1115 }
1116 })
1117 }
1118 if let Some(key_value) = value.get("@direction") {
1120 if self.processing_mode == JsonLdProcessingMode::JsonLd1_0 {
1122 errors.push(JsonLdSyntaxError::msg_and_code(
1123 "@direction is only supported in JSON-LD 1.1",
1124 JsonLdErrorCode::InvalidTermDefinition,
1125 ));
1126 }
1127 match key_value {
1128 JsonNode::Null => {
1130 definition.direction_mapping = Some(None);
1131 }
1132 JsonNode::String(value) => match value.as_str() {
1134 "ltr" => definition.direction_mapping = Some(Some("ltr")),
1135 "rtl" => definition.direction_mapping = Some(Some("rtl")),
1136 _ => errors.push(JsonLdSyntaxError::msg_and_code(
1137 format!("@direction value must be 'ltr' or 'rtl', found '{value}'"),
1138 JsonLdErrorCode::InvalidBaseDirection,
1139 )),
1140 },
1141 _ => errors.push(JsonLdSyntaxError::msg_and_code(
1142 "@direction value must be a string or null",
1143 JsonLdErrorCode::InvalidBaseDirection,
1144 )),
1145 }
1146 }
1147 if let Some(key_value) = value.get("@nest") {
1149 if self.processing_mode == JsonLdProcessingMode::JsonLd1_0 {
1151 errors.push(JsonLdSyntaxError::msg_and_code(
1152 "@nest is only supported in JSON-LD 1.1",
1153 JsonLdErrorCode::InvalidTermDefinition,
1154 ));
1155 }
1156 let JsonNode::String(value) = key_value else {
1158 errors.push(JsonLdSyntaxError::msg_and_code(
1159 "@nest value must be a string",
1160 JsonLdErrorCode::InvalidNestValue,
1161 ));
1162 return;
1163 };
1164 if is_keyword(value) && value != "@nest" {
1165 errors.push(JsonLdSyntaxError::msg_and_code(
1166 "@nest value must not be a keyword other than @nest",
1167 JsonLdErrorCode::InvalidNestValue,
1168 ));
1169 return;
1170 }
1171 definition.nest_value = Some(value.into());
1172 }
1173 if let Some(key_value) = value.get("@prefix") {
1175 if self.processing_mode == JsonLdProcessingMode::JsonLd1_0 {
1177 errors.push(JsonLdSyntaxError::msg_and_code(
1178 "@prefix is only supported in JSON-LD 1.1",
1179 JsonLdErrorCode::InvalidTermDefinition,
1180 ));
1181 }
1182 if term.contains(':') {
1183 errors.push(JsonLdSyntaxError::msg_and_code(
1184 format!("@prefix cannot be set on terms like {term} that contains a :"),
1185 JsonLdErrorCode::InvalidTermDefinition,
1186 ));
1187 return;
1188 }
1189 if term.contains('/') {
1190 errors.push(JsonLdSyntaxError::msg_and_code(
1191 format!("@prefix cannot be set on terms like {term} that contains a /"),
1192 JsonLdErrorCode::InvalidTermDefinition,
1193 ));
1194 return;
1195 }
1196 let JsonNode::Boolean(value) = key_value else {
1198 errors.push(JsonLdSyntaxError::msg_and_code(
1199 "@prefix value must be a boolean",
1200 JsonLdErrorCode::InvalidPrefixValue,
1201 ));
1202 return;
1203 };
1204 definition.prefix_flag = *value;
1205 if definition.prefix_flag
1207 && definition
1208 .iri_mapping
1209 .as_ref()
1210 .is_some_and(|d| d.as_ref().is_some_and(|d| is_keyword(d)))
1211 {
1212 errors.push(JsonLdSyntaxError::msg_and_code(
1213 format!("@prefix cannot be set on terms like {term} that are keywords"),
1214 JsonLdErrorCode::InvalidTermDefinition,
1215 ));
1216 return;
1217 }
1218 }
1219 if let Some(key) = value.keys().find(|k| {
1221 !matches!(
1222 k.as_str(),
1223 "@id"
1224 | "@reverse"
1225 | "@container"
1226 | "@context"
1227 | "@direction"
1228 | "@index"
1229 | "@language"
1230 | "@nest"
1231 | "@prefix"
1232 | "@protected"
1233 | "@type"
1234 )
1235 }) {
1236 errors.push(JsonLdSyntaxError::msg_and_code(
1237 format!("Unexpected key in term definition '{key}'"),
1238 JsonLdErrorCode::InvalidTermDefinition,
1239 ));
1240 }
1241 if !override_protected {
1243 if let Some(previous_definition) = previous_definition {
1244 if previous_definition.protected {
1245 if definition.iri_mapping != previous_definition.iri_mapping
1247 || definition.prefix_flag != previous_definition.prefix_flag
1248 || definition.reverse_property != previous_definition.reverse_property
1249 || definition.base_url != previous_definition.base_url
1250 || definition.context != previous_definition.context
1251 || definition.container_mapping != previous_definition.container_mapping
1252 || definition.direction_mapping != previous_definition.direction_mapping
1253 || definition.index_mapping != previous_definition.index_mapping
1254 || definition.language_mapping != previous_definition.language_mapping
1255 || definition.nest_value != previous_definition.nest_value
1256 || definition.type_mapping != previous_definition.type_mapping
1257 {
1258 errors.push(JsonLdSyntaxError::msg_and_code(
1259 format!("Overriding the protected term {term}"),
1260 JsonLdErrorCode::ProtectedTermRedefinition,
1261 ));
1262 }
1263 definition = previous_definition;
1265 }
1266 }
1267 }
1268 active_context
1270 .term_definitions
1271 .insert(term.into(), definition);
1272 defined.insert(term.into(), true);
1273 }
1274
1275 pub fn expand_iri<'a>(
1279 &self,
1280 active_context: &mut JsonLdContext,
1281 value: Cow<'a, str>,
1282 document_relative: bool,
1283 vocab: bool,
1284 local_context: Option<&HashMap<String, JsonNode>>,
1285 defined: &mut HashMap<String, bool>,
1286 errors: &mut Vec<JsonLdSyntaxError>,
1287 ) -> Option<Cow<'a, str>> {
1288 if has_keyword_form(&value) {
1289 return is_keyword(&value).then_some(value);
1291 }
1292 if let Some(local_context) = local_context {
1294 if local_context.contains_key(value.as_ref())
1295 && defined.get(value.as_ref()) != Some(&true)
1296 {
1297 self.create_term_definition(
1298 active_context,
1299 local_context,
1300 &value,
1301 defined,
1302 None,
1303 false,
1304 false,
1305 &mut Vec::new(),
1306 errors,
1307 )
1308 }
1309 }
1310 if let Some(term_definition) = active_context.term_definitions.get(value.as_ref()) {
1311 if let Some(iri_mapping) = &term_definition.iri_mapping {
1312 let iri_mapping = iri_mapping.as_ref()?;
1313 if is_keyword(iri_mapping) {
1315 return Some(iri_mapping.clone().into());
1316 }
1317 if vocab {
1319 return Some(iri_mapping.clone().into());
1320 }
1321 }
1322 }
1323 if let Some((prefix, suffix)) = value.split_once(':') {
1325 if prefix == "_" || suffix.starts_with("//") {
1327 return Some(value);
1328 }
1329 if let Some(local_context) = local_context {
1331 if local_context.contains_key(prefix) && defined.get(prefix) != Some(&true) {
1332 self.create_term_definition(
1333 active_context,
1334 local_context,
1335 prefix,
1336 defined,
1337 None,
1338 false,
1339 false,
1340 &mut Vec::new(),
1341 errors,
1342 )
1343 }
1344 }
1345 if let Some(term_definition) = active_context.term_definitions.get(prefix) {
1347 if let Some(Some(iri_mapping)) = &term_definition.iri_mapping {
1348 if term_definition.prefix_flag {
1349 return Some(format!("{iri_mapping}{suffix}").into());
1350 }
1351 }
1352 }
1353 if Iri::parse(value.as_ref()).is_ok() {
1355 return Some(value);
1356 }
1357 }
1358 if vocab {
1360 if let Some(vocabulary_mapping) = &active_context.vocabulary_mapping {
1361 return Some(format!("{vocabulary_mapping}{value}").into());
1362 }
1363 }
1364 if document_relative {
1366 if let Some(base_iri) = &active_context.base_iri {
1367 if self.lenient {
1368 return Some(base_iri.resolve_unchecked(&value).into_inner().into());
1369 } else if let Ok(value) = base_iri.resolve(&value) {
1370 return Some(base_iri.resolve_unchecked(&value).into_inner().into());
1371 }
1372 }
1373 }
1374
1375 Some(value)
1376 }
1377
1378 fn load_remote_context(
1379 &self,
1380 url: &str,
1381 ) -> Result<(Option<Iri<String>>, JsonNode), JsonLdSyntaxError> {
1382 #[expect(clippy::unwrap_in_result)]
1383 let mut remote_context_cache = self.remote_context_cache.lock().unwrap();
1384 if let Some(loaded_context) = remote_context_cache.get(url) {
1385 return Ok(loaded_context.clone());
1387 }
1388
1389 let Some(load_document_callback) = &self.load_document_callback else {
1391 return Err(JsonLdSyntaxError::msg_and_code(
1392 "No LoadDocumentCallback has been set to load remote contexts",
1393 JsonLdErrorCode::LoadingRemoteContextFailed,
1394 ));
1395 };
1396 let context_document = match load_document_callback(
1397 url,
1398 &JsonLdLoadDocumentOptions {
1399 request_profile: JsonLdProfile::Context.into(),
1400 },
1401 ) {
1402 Ok(document) => document,
1403 Err(e) => {
1404 return Err(JsonLdSyntaxError::msg_and_code(
1405 format!("Failed to load remote context {url}: {e}"),
1406 JsonLdErrorCode::LoadingRemoteContextFailed,
1407 ));
1408 }
1409 };
1410 let parsed_document = match json_slice_to_node(&context_document.document) {
1411 Ok(d) => d,
1412 Err(e) => {
1413 return Err(JsonLdSyntaxError::msg_and_code(
1414 format!("Failed to parse remote context {url}: {e}"),
1415 JsonLdErrorCode::LoadingRemoteContextFailed,
1416 ));
1417 }
1418 };
1419 let JsonNode::Object(parsed_document) = parsed_document else {
1420 return Err(JsonLdSyntaxError::msg_and_code(
1421 format!("Remote context {url} must be a map"),
1422 JsonLdErrorCode::InvalidRemoteContext,
1423 ));
1424 };
1425 let Some(loaded_context) = parsed_document
1426 .into_iter()
1427 .find_map(|(k, v)| (k == "@context").then_some(v))
1428 else {
1429 return Err(JsonLdSyntaxError::msg_and_code(
1430 format!("Remote context {url} must be contain a @context key"),
1431 JsonLdErrorCode::InvalidRemoteContext,
1432 ));
1433 };
1434 let document_url = Iri::parse(context_document.document_url).ok();
1435 remote_context_cache.insert(url.into(), (document_url.clone(), loaded_context.clone()));
1436 Ok((document_url, loaded_context))
1437 }
1438}
1439
1440pub fn has_keyword_form(value: &str) -> bool {
1441 value
1442 .strip_prefix('@')
1443 .is_some_and(|suffix| !suffix.is_empty() && suffix.bytes().all(|b| b.is_ascii_alphabetic()))
1444}
1445
1446pub fn is_keyword(value: &str) -> bool {
1447 matches!(
1448 value,
1449 "@base"
1450 | "@container"
1451 | "@context"
1452 | "@direction"
1453 | "@graph"
1454 | "@id"
1455 | "@import"
1456 | "@included"
1457 | "@index"
1458 | "@json"
1459 | "@language"
1460 | "@list"
1461 | "@nest"
1462 | "@none"
1463 | "@prefix"
1464 | "@propagate"
1465 | "@protected"
1466 | "@reverse"
1467 | "@set"
1468 | "@type"
1469 | "@value"
1470 | "@version"
1471 | "@vocab"
1472 )
1473}
1474
1475fn json_slice_to_node(data: &[u8]) -> Result<JsonNode, JsonSyntaxError> {
1476 let mut parser = SliceJsonParser::new(data);
1477 json_node_from_events(std::iter::from_fn(|| match parser.parse_next() {
1478 Ok(JsonEvent::Eof) => None,
1479 Ok(event) => Some(Ok(event)),
1480 Err(e) => Some(Err(e)),
1481 }))
1482}
1483
1484enum BuildingObjectOrArrayNode {
1485 Object(HashMap<String, JsonNode>),
1486 ObjectWithPendingKey(HashMap<String, JsonNode>, String),
1487 Array(Vec<JsonNode>),
1488}
1489
1490pub fn json_node_from_events<'a>(
1491 events: impl IntoIterator<Item = Result<JsonEvent<'a>, JsonSyntaxError>>,
1492) -> Result<JsonNode, JsonSyntaxError> {
1493 let mut stack = Vec::new();
1494 for event in events {
1495 if let Some(result) = match event? {
1496 JsonEvent::String(value) => {
1497 after_to_node_event(&mut stack, JsonNode::String(value.into()))
1498 }
1499 JsonEvent::Number(value) => {
1500 after_to_node_event(&mut stack, JsonNode::Number(value.into()))
1501 }
1502 JsonEvent::Boolean(value) => after_to_node_event(&mut stack, JsonNode::Boolean(value)),
1503 JsonEvent::Null => after_to_node_event(&mut stack, JsonNode::Null),
1504 JsonEvent::EndArray | JsonEvent::EndObject => {
1505 let value = match stack.pop() {
1506 Some(BuildingObjectOrArrayNode::Object(object)) => JsonNode::Object(object),
1507 Some(BuildingObjectOrArrayNode::Array(array)) => JsonNode::Array(array),
1508 _ => unreachable!(),
1509 };
1510 after_to_node_event(&mut stack, value)
1511 }
1512 JsonEvent::StartArray => {
1513 stack.push(BuildingObjectOrArrayNode::Array(Vec::new()));
1514 None
1515 }
1516 JsonEvent::StartObject => {
1517 stack.push(BuildingObjectOrArrayNode::Object(HashMap::new()));
1518 None
1519 }
1520 JsonEvent::ObjectKey(key) => {
1521 if let Some(BuildingObjectOrArrayNode::Object(object)) = stack.pop() {
1522 stack.push(BuildingObjectOrArrayNode::ObjectWithPendingKey(
1523 object,
1524 key.into(),
1525 ));
1526 }
1527 None
1528 }
1529 JsonEvent::Eof => unreachable!(),
1530 } {
1531 return Ok(result);
1532 }
1533 }
1534 unreachable!("The JSON emitted by the parser mut be valid")
1535}
1536
1537fn after_to_node_event(
1538 stack: &mut Vec<BuildingObjectOrArrayNode>,
1539 new_value: JsonNode,
1540) -> Option<JsonNode> {
1541 match stack.pop() {
1542 Some(BuildingObjectOrArrayNode::ObjectWithPendingKey(mut object, key)) => {
1543 object.insert(key, new_value);
1544 stack.push(BuildingObjectOrArrayNode::Object(object));
1545 None
1546 }
1547 Some(BuildingObjectOrArrayNode::Object(object)) => {
1548 stack.push(BuildingObjectOrArrayNode::Object(object));
1549 None
1550 }
1551 Some(BuildingObjectOrArrayNode::Array(mut array)) => {
1552 array.push(new_value);
1553 stack.push(BuildingObjectOrArrayNode::Array(array));
1554 None
1555 }
1556 None => Some(new_value),
1557 }
1558}