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}