json_ld_context_processing_next/algorithm/define.rs
1use super::{expand_iri_simple, expand_iri_with, Environment, Merged};
2use crate::{Error, Options, ProcessingStack, Warning, WarningHandler};
3use iref::{Iri, IriRef};
4use json_ld_core_next::{
5 context::{NormalTermDefinition, TypeTermDefinition},
6 Container, Context, Id, Loader, ProcessingMode, Term, Type, ValidId,
7};
8use json_ld_syntax_next::{
9 context::{
10 definition::{EntryValueRef, KeyOrKeyword, KeyOrKeywordRef},
11 term_definition::{self, IdRef},
12 },
13 CompactIri, ContainerKind, ExpandableRef, Keyword, LenientLangTag, Nullable,
14};
15use rdf_types::{BlankId, VocabularyMut};
16use std::{collections::HashMap, hash::Hash};
17
18fn is_gen_delim(c: char) -> bool {
19 matches!(c, ':' | '/' | '?' | '#' | '[' | ']' | '@')
20}
21
22// Checks if the input term is an IRI ending with a gen-delim character, or a blank node identifier.
23fn is_gen_delim_or_blank<T, B>(
24 vocabulary: &impl VocabularyMut<Iri = T, BlankId = B>,
25 t: &Term<T, B>,
26) -> bool {
27 match t {
28 Term::Id(Id::Valid(ValidId::Blank(_))) => true,
29 Term::Id(Id::Valid(ValidId::Iri(id))) => {
30 if let Some(c) = vocabulary.iri(id).unwrap().as_str().chars().last() {
31 is_gen_delim(c)
32 } else {
33 false
34 }
35 }
36 _ => false,
37 }
38}
39
40/// Checks if the the given character is included in the given string anywhere but at the first or last position.
41fn contains_between_boundaries(id: &str, c: char) -> bool {
42 if let Some(i) = id.find(c) {
43 let j = id.rfind(c).unwrap();
44 i > 0 && j < id.len() - 1
45 } else {
46 false
47 }
48}
49
50#[derive(Default)]
51pub struct DefinedTerms(HashMap<KeyOrKeyword, DefinedTerm>);
52
53impl DefinedTerms {
54 pub fn new() -> Self {
55 Self::default()
56 }
57
58 pub fn begin(&mut self, key: &KeyOrKeyword) -> Result<bool, Error> {
59 match self.0.get(key) {
60 Some(d) => {
61 if d.pending {
62 Err(Error::CyclicIriMapping)
63 } else {
64 Ok(false)
65 }
66 }
67 None => {
68 self.0.insert(key.clone(), DefinedTerm { pending: true });
69
70 Ok(true)
71 }
72 }
73 }
74
75 pub fn end(&mut self, key: &KeyOrKeyword) {
76 self.0.get_mut(key).unwrap().pending = false
77 }
78}
79
80pub struct DefinedTerm {
81 pending: bool,
82}
83
84/// Follows the `https://www.w3.org/TR/json-ld11-api/#create-term-definition` algorithm.
85/// Default value for `base_url` is `None`. Default values for `protected` and `override_protected` are `false`.
86#[allow(clippy::too_many_arguments)]
87pub async fn define<'a, N, L, W>(
88 mut env: Environment<'a, N, L, W>,
89 active_context: &'a mut Context<N::Iri, N::BlankId>,
90 local_context: &'a Merged<'a>,
91 term: KeyOrKeywordRef<'a>,
92 defined: &'a mut DefinedTerms,
93 remote_contexts: ProcessingStack<N::Iri>,
94 base_url: Option<N::Iri>,
95 protected: bool,
96 options: Options,
97) -> Result<(), Error>
98where
99 N: VocabularyMut,
100 N::Iri: Clone + Eq + Hash,
101 N::BlankId: Clone + PartialEq,
102 L: Loader,
103 W: WarningHandler<N>,
104{
105 let term = term.to_owned();
106 if defined.begin(&term)? {
107 if term.is_empty() {
108 return Err(Error::InvalidTermDefinition);
109 }
110
111 // Initialize `value` to a copy of the value associated with the entry `term` in
112 // `local_context`.
113 if let Some(value) = local_context.get(&term) {
114 // Set the value associated with defined's term entry to false.
115 // This indicates that the term definition is now being created but is not yet
116 // complete.
117 // Done with `defined.begin`.
118 match value {
119 // If term is @type, ...
120 EntryValueRef::Type(d) => {
121 // ... and processing mode is json-ld-1.0, a keyword
122 // redefinition error has been detected and processing is aborted.
123 if options.processing_mode == ProcessingMode::JsonLd1_0 {
124 return Err(Error::KeywordRedefinition);
125 }
126
127 let previous_definition = active_context.set_type(None);
128
129 // At this point, `value` MUST be a map with only either or both of the
130 // following entries:
131 // An entry for @container with value @set.
132 // An entry for @protected.
133 // Any other value means that a keyword redefinition error has been detected
134 // and processing is aborted.
135 // Checked during parsing.
136 let mut definition = TypeTermDefinition {
137 container: d.container,
138 ..Default::default()
139 };
140
141 if let Some(protected) = d.protected {
142 if options.processing_mode == ProcessingMode::JsonLd1_0 {
143 return Err(Error::InvalidTermDefinition);
144 }
145
146 definition.protected = protected
147 }
148
149 // If override protected is false and previous_definition exists and is protected;
150 if !options.override_protected {
151 if let Some(previous_definition) = previous_definition {
152 if previous_definition.protected {
153 // If `definition` is not the same as `previous_definition`
154 // (other than the value of protected), a protected term
155 // redefinition error has been detected, and processing is aborted.
156 if definition.modulo_protected_field()
157 != previous_definition.modulo_protected_field()
158 {
159 return Err(Error::ProtectedTermRedefinition);
160 }
161
162 // Set `definition` to `previous definition` to retain the value of
163 // protected.
164 definition.protected = true;
165 }
166 }
167 }
168
169 active_context.set_type(Some(definition));
170 }
171 EntryValueRef::Definition(d) => {
172 let key = term.as_key().unwrap();
173 // Initialize `previous_definition` to any existing term definition for `term` in
174 // `active_context`, removing that term definition from active context.
175 let previous_definition = active_context.set_normal(key.clone(), None);
176
177 let simple_term = !d.map(|d| d.is_expanded()).unwrap_or(false);
178 let value = term_definition::ExpandedRef::from(d);
179
180 // Create a new term definition, `definition`, initializing `prefix` flag to
181 // `false`, `protected` to `protected`, and `reverse_property` to `false`.
182 let mut definition = NormalTermDefinition::<N::Iri, N::BlankId> {
183 protected,
184 ..Default::default()
185 };
186
187 // If the @protected entry in value is true set the protected flag in
188 // definition to true.
189 if let Some(protected) = value.protected {
190 // If processing mode is json-ld-1.0, an invalid term definition has
191 // been detected and processing is aborted.
192 if options.processing_mode == ProcessingMode::JsonLd1_0 {
193 return Err(Error::InvalidTermDefinition);
194 }
195
196 definition.protected = protected;
197 }
198
199 // If value contains the entry @type:
200 if let Some(type_) = value.type_ {
201 // Set `typ` to the result of IRI expanding type, using local context,
202 // and defined.
203 let typ = expand_iri_with(
204 Environment {
205 vocabulary: env.vocabulary,
206 loader: env.loader,
207 warnings: env.warnings,
208 },
209 active_context,
210 type_.cast(),
211 false,
212 Some(options.vocab),
213 local_context,
214 defined,
215 remote_contexts.clone(),
216 options,
217 )
218 .await?;
219
220 if let Some(typ) = typ {
221 // If the expanded type is @json or @none, and processing mode is
222 // json-ld-1.0, an invalid type mapping error has been detected and
223 // processing is aborted.
224 if options.processing_mode == ProcessingMode::JsonLd1_0
225 && (typ == Term::Keyword(Keyword::Json)
226 || typ == Term::Keyword(Keyword::None))
227 {
228 return Err(Error::InvalidTypeMapping);
229 }
230
231 if let Ok(typ) = typ.try_into() {
232 // Set the type mapping for definition to type.
233 definition.typ = Some(typ);
234 } else {
235 return Err(Error::InvalidTypeMapping);
236 }
237 }
238 }
239
240 // If `value` contains the entry @reverse:
241 if let Some(reverse_value) = value.reverse {
242 // If `value` contains `@id` or `@nest`, entries, an invalid reverse
243 // property error has been detected and processing is aborted.
244 if value.id.is_some() || value.nest.is_some() {
245 return Err(Error::InvalidReverseProperty);
246 }
247
248 // If the value associated with the @reverse entry is a string having
249 // the form of a keyword, return; processors SHOULD generate a warning.
250 if reverse_value.is_keyword_like() {
251 env.warnings.handle(
252 env.vocabulary,
253 Warning::KeywordLikeValue(reverse_value.to_string()),
254 );
255 return Ok(());
256 }
257
258 // Otherwise, set the IRI mapping of definition to the result of IRI
259 // expanding the value associated with the @reverse entry, using
260 // local context, and defined.
261 // If the result does not have the form of an IRI or a blank node
262 // identifier, an invalid IRI mapping error has been detected and
263 // processing is aborted.
264 match expand_iri_with(
265 env,
266 active_context,
267 Nullable::Some(reverse_value.as_str().into()),
268 false,
269 Some(options.vocab),
270 local_context,
271 defined,
272 remote_contexts,
273 options,
274 )
275 .await?
276 {
277 Some(Term::Id(mapping)) if mapping.is_valid() => {
278 definition.value = Some(Term::Id(mapping))
279 }
280 _ => return Err(Error::InvalidIriMapping),
281 }
282
283 // If `value` contains an `@container` entry, set the `container`
284 // mapping of `definition` to an array containing its value;
285 // if its value is neither `@set`, nor `@index`, nor null, an
286 // invalid reverse property error has been detected (reverse properties
287 // only support set- and index-containers) and processing is aborted.
288 if let Some(container_value) = value.container {
289 match container_value {
290 Nullable::Null => (),
291 Nullable::Some(container_value) => {
292 let container_value =
293 Container::from_syntax(Nullable::Some(container_value))
294 .map_err(|_| Error::InvalidReverseProperty)?;
295
296 if matches!(container_value, Container::Set | Container::Index)
297 {
298 definition.container = container_value
299 } else {
300 return Err(Error::InvalidReverseProperty);
301 }
302 }
303 };
304 }
305
306 // Set the `reverse_property` flag of `definition` to `true`.
307 definition.reverse_property = true;
308
309 // Set the term definition of `term` in `active_context` to
310 // `definition` and the value associated with `defined`'s entry `term`
311 // to `true` and return.
312 active_context.set_normal(key.to_owned(), Some(definition));
313 defined.end(&term);
314 return Ok(());
315 }
316
317 match value.id {
318 // If `value` contains the entry `@id` and its value does not equal `term`:
319 Some(id_value)
320 if id_value.cast::<KeyOrKeywordRef>() != Nullable::Some(key.into()) =>
321 {
322 match id_value {
323 // If the `@id` entry of value is `null`, the term is not used for IRI
324 // expansion, but is retained to be able to detect future redefinitions
325 // of this term.
326 Nullable::Null => (),
327 Nullable::Some(id_value) => {
328 // Otherwise:
329 // If the value associated with the `@id` entry is not a
330 // keyword, but has the form of a keyword, return;
331 // processors SHOULD generate a warning.
332 if id_value.is_keyword_like() && !id_value.is_keyword() {
333 debug_assert!(Keyword::try_from(id_value.as_str()).is_err());
334 env.warnings.handle(
335 env.vocabulary,
336 Warning::KeywordLikeValue(id_value.to_string()),
337 );
338 return Ok(());
339 }
340
341 // Otherwise, set the IRI mapping of `definition` to the result
342 // of IRI expanding the value associated with the `@id` entry,
343 // using `local_context`, and `defined`.
344 definition.value = match expand_iri_with(
345 Environment {
346 vocabulary: env.vocabulary,
347 loader: env.loader,
348 warnings: env.warnings,
349 },
350 active_context,
351 Nullable::Some(id_value.into()),
352 false,
353 Some(options.vocab),
354 local_context,
355 defined,
356 remote_contexts.clone(),
357 options,
358 )
359 .await?
360 {
361 Some(Term::Keyword(Keyword::Context)) => {
362 // if it equals `@context`, an invalid keyword alias error has
363 // been detected and processing is aborted.
364 return Err(Error::InvalidKeywordAlias);
365 }
366 Some(Term::Id(prop)) if !prop.is_valid() => {
367 // If the resulting IRI mapping is neither a keyword,
368 // nor an IRI, nor a blank node identifier, an
369 // invalid IRI mapping error has been detected and processing
370 // is aborted;
371 return Err(Error::InvalidIriMapping);
372 }
373 value => value,
374 };
375
376 // If `term` contains a colon (:) anywhere but as the first or
377 // last character of `term`, or if it contains a slash (/)
378 // anywhere:
379 if contains_between_boundaries(key.as_str(), ':')
380 || key.as_str().contains('/')
381 {
382 // Set the value associated with `defined`'s `term` entry
383 // to `true`.
384 defined.end(&term);
385
386 // If the result of IRI expanding `term` using
387 // `local_context`, and `defined`, is not the same as the
388 // IRI mapping of definition, an invalid IRI mapping error
389 // has been detected and processing is aborted.
390 let expanded_term = expand_iri_with(
391 Environment {
392 vocabulary: env.vocabulary,
393 loader: env.loader,
394 warnings: env.warnings,
395 },
396 active_context,
397 Nullable::Some((&term).into()),
398 false,
399 Some(options.vocab),
400 local_context,
401 defined,
402 remote_contexts.clone(),
403 options,
404 )
405 .await?;
406 if definition.value != expanded_term {
407 return Err(Error::InvalidIriMapping);
408 }
409 }
410
411 // If `term` contains neither a colon (:) nor a slash (/),
412 // simple term is true, and if the IRI mapping of definition
413 // is either an IRI ending with a gen-delim character,
414 // or a blank node identifier, set the `prefix` flag in
415 // `definition` to true.
416 if !key.as_str().contains(':')
417 && !key.as_str().contains('/') && simple_term
418 && is_gen_delim_or_blank(
419 env.vocabulary,
420 definition.value.as_ref().unwrap(),
421 ) {
422 definition.prefix = true;
423 }
424 }
425 }
426 }
427 Some(Nullable::Some(IdRef::Keyword(Keyword::Type))) => {
428 // Otherwise, if `term` is ``@type`, set the IRI mapping of definition to
429 // `@type`.
430 definition.value = Some(Term::Keyword(Keyword::Type))
431 }
432 _ => {
433 // Otherwise if the `term` contains a colon (:) anywhere after the first
434 // character.
435 if let KeyOrKeyword::Key(term) = &term {
436 if let Ok(compact_iri) = CompactIri::new(term.as_str()) {
437 // If `term` is a compact IRI with a prefix that is an entry in local
438 // context a dependency has been found.
439 // Use this algorithm recursively passing `active_context`,
440 // `local_context`, the prefix as term, and `defined`.
441 Box::pin(define(
442 Environment {
443 vocabulary: env.vocabulary,
444 loader: env.loader,
445 warnings: env.warnings,
446 },
447 active_context,
448 local_context,
449 KeyOrKeywordRef::Key(compact_iri.prefix().into()),
450 defined,
451 remote_contexts.clone(),
452 None,
453 false,
454 options.with_no_override(),
455 ))
456 .await?;
457
458 // If `term`'s prefix has a term definition in `active_context`, set the
459 // IRI mapping of `definition` to the result of concatenating the value
460 // associated with the prefix's IRI mapping and the term's suffix.
461 if let Some(prefix_definition) =
462 active_context.get(compact_iri.prefix())
463 {
464 let mut result = String::new();
465
466 if let Some(prefix_key) = prefix_definition.value() {
467 if let Some(prefix_iri) = prefix_key.as_iri() {
468 result = env
469 .vocabulary
470 .iri(prefix_iri)
471 .unwrap()
472 .to_string()
473 }
474 }
475
476 result.push_str(compact_iri.suffix());
477
478 if let Ok(iri) = Iri::new(result.as_str()) {
479 definition.value =
480 Some(Term::Id(Id::iri(env.vocabulary.insert(iri))))
481 } else {
482 return Err(Error::InvalidIriMapping);
483 }
484 }
485 }
486
487 // not a compact IRI
488 if definition.value.is_none() {
489 if let Ok(blank_id) = BlankId::new(term.as_str()) {
490 definition.value = Some(Term::Id(Id::blank(
491 env.vocabulary.insert_blank_id(blank_id),
492 )))
493 } else if let Ok(iri_ref) = IriRef::new(term.as_str()) {
494 match iri_ref.as_iri() {
495 Some(iri) => {
496 definition.value = Some(Term::Id(Id::iri(
497 env.vocabulary.insert(iri),
498 )))
499 }
500 None => {
501 if iri_ref.as_str().contains('/') {
502 // Term is a relative IRI reference.
503 // Set the IRI mapping of definition to the result of IRI expanding
504 // term.
505 match expand_iri_simple(
506 &mut env,
507 active_context,
508 Nullable::Some(ExpandableRef::String(
509 iri_ref.as_str(),
510 )),
511 false,
512 Some(options.vocab),
513 )? {
514 Some(Term::Id(Id::Valid(
515 ValidId::Iri(id),
516 ))) => definition.value = Some(id.into()),
517 // If the resulting IRI mapping is not an IRI, an invalid IRI mapping
518 // error has been detected and processing is aborted.
519 _ => return Err(Error::InvalidIriMapping),
520 }
521 }
522 }
523 }
524 }
525
526 // not a compact IRI, IRI, IRI reference or blank node id.
527 if definition.value.is_none() {
528 if let Some(context_vocabulary) =
529 active_context.vocabulary()
530 {
531 // Otherwise, if `active_context` has a vocabulary mapping, the IRI mapping
532 // of `definition` is set to the result of concatenating the value
533 // associated with the vocabulary mapping and `term`.
534 // If it does not have a vocabulary mapping, an invalid IRI mapping error
535 // been detected and processing is aborted.
536 if let Some(vocabulary_iri) =
537 context_vocabulary.as_iri()
538 {
539 let mut result = env
540 .vocabulary
541 .iri(vocabulary_iri)
542 .unwrap()
543 .to_string();
544 result.push_str(key.as_str());
545 if let Ok(iri) = Iri::new(result.as_str()) {
546 definition.value =
547 Some(Term::<N::Iri, N::BlankId>::from(
548 env.vocabulary.insert(iri),
549 ))
550 } else {
551 return Err(Error::InvalidIriMapping);
552 }
553 } else {
554 return Err(Error::InvalidIriMapping);
555 }
556 } else {
557 // If it does not have a vocabulary mapping, an invalid IRI mapping error
558 // been detected and processing is aborted.
559 return Err(Error::InvalidIriMapping);
560 }
561 }
562 }
563 }
564 }
565 }
566
567 // If value contains the entry @container:
568 if let Some(container_value) = value.container {
569 // If the container value is @graph, @id, or @type, or is otherwise not a
570 // string, generate an invalid container mapping error and abort processing
571 // if processing mode is json-ld-1.0.
572 if options.processing_mode == ProcessingMode::JsonLd1_0 {
573 match container_value {
574 Nullable::Null
575 | Nullable::Some(
576 json_ld_syntax_next::Container::Many(_)
577 | json_ld_syntax_next::Container::One(
578 ContainerKind::Graph
579 | ContainerKind::Id
580 | ContainerKind::Type,
581 ),
582 ) => return Err(Error::InvalidContainerMapping),
583 _ => (),
584 }
585 }
586
587 let container_value = Container::from_syntax(container_value)
588 .map_err(|_| Error::InvalidContainerMapping)?;
589
590 // Initialize `container` to the value associated with the `@container`
591 // entry, which MUST be either `@graph`, `@id`, `@index`, `@language`,
592 // `@list`, `@set`, `@type`, or an array containing exactly any one of
593 // those keywords, an array containing `@graph` and either `@id` or
594 // `@index` optionally including `@set`, or an array containing a
595 // combination of `@set` and any of `@index`, `@graph`, `@id`, `@type`,
596 // `@language` in any order.
597 // Otherwise, an invalid container mapping has been detected and processing
598 // is aborted.
599 definition.container = container_value;
600
601 // Set the container mapping of definition to container coercing to an
602 // array, if necessary.
603 // already done.
604
605 // If the `container` mapping of definition includes `@type`:
606 if definition.container.contains(ContainerKind::Type) {
607 if let Some(typ) = &definition.typ {
608 // If type mapping in definition is neither `@id` nor `@vocab`,
609 // an invalid type mapping error has been detected and processing
610 // is aborted.
611 match typ {
612 Type::Id | Type::Vocab => (),
613 _ => return Err(Error::InvalidTypeMapping),
614 }
615 } else {
616 // If type mapping in definition is undefined, set it to @id.
617 definition.typ = Some(Type::Id)
618 }
619 }
620 }
621
622 // If value contains the entry @index:
623 if let Some(index_value) = value.index {
624 // If processing mode is json-ld-1.0 or container mapping does not include
625 // `@index`, an invalid term definition has been detected and processing
626 // is aborted.
627 if !definition.container.contains(ContainerKind::Index)
628 || options.processing_mode == ProcessingMode::JsonLd1_0
629 {
630 return Err(Error::InvalidTermDefinition);
631 }
632
633 // Initialize `index` to the value associated with the `@index` entry,
634 // which MUST be a string expanding to an IRI.
635 // Otherwise, an invalid term definition has been detected and processing
636 // is aborted.
637 match expand_iri_simple(
638 &mut env,
639 active_context,
640 Nullable::Some(index_value.as_str().into()),
641 false,
642 Some(options.vocab),
643 )? {
644 Some(Term::Id(Id::Valid(ValidId::Iri(_)))) => (),
645 _ => return Err(Error::InvalidTermDefinition),
646 }
647
648 definition.index = Some(index_value.to_owned())
649 }
650
651 // If `value` contains the entry `@context`:
652 if let Some(context) = value.context {
653 // If processing mode is json-ld-1.0, an invalid term definition has been
654 // detected and processing is aborted.
655 if options.processing_mode == ProcessingMode::JsonLd1_0 {
656 return Err(Error::InvalidTermDefinition);
657 }
658
659 // Initialize `context` to the value associated with the @context entry,
660 // which is treated as a local context.
661 // done.
662
663 // Invoke the Context Processing algorithm using the `active_context`,
664 // `context` as local context, `base_url`, and `true` for override
665 // protected.
666 // If any error is detected, an invalid scoped context error has been
667 // detected and processing is aborted.
668 Box::pin(super::process_context(
669 env,
670 active_context,
671 context,
672 remote_contexts.clone(),
673 base_url.clone(),
674 options.with_override(),
675 ))
676 .await
677 .map_err(|_| Error::InvalidScopedContext)?;
678
679 // Set the local context of definition to context, and base URL to base URL.
680 definition.context = Some(Box::new(context.clone()));
681 definition.base_url = base_url;
682 }
683
684 // If `value` contains the entry `@language` and does not contain the entry
685 // `@type`:
686 if value.type_.is_none() {
687 if let Some(language_value) = value.language {
688 // Initialize `language` to the value associated with the `@language`
689 // entry, which MUST be either null or a string.
690 // If `language` is not well-formed according to section 2.2.9 of
691 // [BCP47], processors SHOULD issue a warning.
692 // Otherwise, an invalid language mapping error has been detected and
693 // processing is aborted.
694 // Set the `language` mapping of definition to `language`.
695 definition.language =
696 Some(language_value.map(LenientLangTag::to_owned));
697 }
698
699 // If `value` contains the entry `@direction` and does not contain the
700 // entry `@type`:
701 if let Some(direction_value) = value.direction {
702 // Initialize `direction` to the value associated with the `@direction`
703 // entry, which MUST be either null, "ltr", or "rtl".
704 definition.direction = Some(direction_value);
705 }
706 }
707
708 // If value contains the entry @nest:
709 if let Some(nest_value) = value.nest {
710 // If processing mode is json-ld-1.0, an invalid term definition has been
711 // detected and processing is aborted.
712 if options.processing_mode == ProcessingMode::JsonLd1_0 {
713 return Err(Error::InvalidTermDefinition);
714 }
715
716 definition.nest = Some(nest_value.clone());
717 }
718
719 // If value contains the entry @prefix:
720 if let Some(prefix_value) = value.prefix {
721 // If processing mode is json-ld-1.0, or if `term` contains a colon (:) or
722 // slash (/), an invalid term definition has been detected and processing
723 // is aborted.
724 if key.as_str().contains(':')
725 || key.as_str().contains('/')
726 || options.processing_mode == ProcessingMode::JsonLd1_0
727 {
728 return Err(Error::InvalidTermDefinition);
729 }
730
731 // Set the `prefix` flag to the value associated with the @prefix entry,
732 // which MUST be a boolean.
733 // Otherwise, an invalid @prefix value error has been detected and
734 // processing is aborted.
735 definition.prefix = prefix_value;
736
737 // If the `prefix` flag of `definition` is set to `true`, and its IRI
738 // mapping is a keyword, an invalid term definition has been detected and
739 // processing is aborted.
740 if definition.prefix && definition.value.as_ref().unwrap().is_keyword() {
741 return Err(Error::InvalidTermDefinition);
742 }
743 }
744
745 // If value contains any entry other than @id, @reverse, @container, @context,
746 // @direction, @index, @language, @nest, @prefix, @protected, or @type, an
747 // invalid term definition error has been detected and processing is aborted.
748 if value.propagate.is_some() {
749 return Err(Error::InvalidTermDefinition);
750 }
751
752 // If override protected is false and previous_definition exists and is protected;
753 if !options.override_protected {
754 if let Some(previous_definition) = previous_definition {
755 if previous_definition.protected {
756 // If `definition` is not the same as `previous_definition`
757 // (other than the value of protected), a protected term
758 // redefinition error has been detected, and processing is aborted.
759 if definition.modulo_protected_field()
760 != previous_definition.modulo_protected_field()
761 {
762 return Err(Error::ProtectedTermRedefinition);
763 }
764
765 // Set `definition` to `previous definition` to retain the value of
766 // protected.
767 definition.protected = true;
768 }
769 }
770 }
771
772 // Set the term definition of `term` in `active_context` to `definition` and
773 // set the value associated with `defined`'s entry term to true.
774 active_context.set_normal(key.to_owned(), Some(definition));
775 }
776 _ => {
777 // Otherwise, since keywords cannot be overridden, term MUST NOT be a keyword and
778 // a keyword redefinition error has been detected and processing is aborted.
779 return Err(Error::KeywordRedefinition);
780 }
781 }
782 }
783
784 defined.end(&term);
785 }
786
787 Ok(())
788}