Skip to main content

jsonld_context_processing/algorithm/
define.rs

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