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    // Create Term Definition only runs for a term the local context binds;
161    // IRI expansion reaches here with every string it expands, most of which
162    // are not terms. The empty term is the one unbound string that is an error.
163    let Some(term) = local_context.bound_key(term) else {
164        return if term.as_str().is_empty() {
165            Err(Error::InvalidTermDefinition)
166        } else {
167            Ok(())
168        };
169    };
170    if defined.begin(&term)? {
171        if term.is_empty() {
172            return Err(Error::InvalidTermDefinition);
173        }
174
175        // Initialize `value` to a copy of the value associated with the entry `term` in
176        // `local_context`.
177        if let Some(value) = local_context.get(&term) {
178            // Set the value associated with defined's term entry to false.
179            // This indicates that the term definition is now being created but is not yet
180            // complete.
181            // Done with `defined.begin`.
182            match value {
183                // If term is @type, ...
184                EntryValueRef::Type(d) => {
185                    // ... and processing mode is json-ld-1.0, a keyword
186                    // redefinition error has been detected and processing is aborted.
187                    if options.processing_mode == ProcessingMode::JsonLd1_0 {
188                        return Err(Error::KeywordRedefinition);
189                    }
190
191                    let previous_definition = active_context.set_type(None);
192
193                    // At this point, `value` MUST be a map with only either or both of the
194                    // following entries:
195                    // An entry for @container with value @set.
196                    // An entry for @protected.
197                    // Any other value means that a keyword redefinition error has been detected
198                    // and processing is aborted.
199                    // Checked during parsing.
200                    let mut definition = TypeTermDefinition {
201                        container: d.container,
202                        ..Default::default()
203                    };
204
205                    if let Some(protected) = d.protected {
206                        if options.processing_mode == ProcessingMode::JsonLd1_0 {
207                            return Err(Error::InvalidTermDefinition);
208                        }
209
210                        definition.protected = protected;
211                    }
212
213                    // If override protected is false and previous_definition exists and is protected;
214                    if !options.override_protected
215                        && let Some(previous_definition) = previous_definition
216                        && previous_definition.protected
217                    {
218                        // If `definition` is not the same as `previous_definition`
219                        // (other than the value of protected), a protected term
220                        // redefinition error has been detected, and processing is aborted.
221                        if definition.modulo_protected_field() != previous_definition.modulo_protected_field() {
222                            return Err(Error::ProtectedTermRedefinition);
223                        }
224
225                        // Set `definition` to `previous definition` to retain the value of
226                        // protected.
227                        definition.protected = true;
228                    }
229
230                    active_context.set_type(Some(definition));
231                }
232                EntryValueRef::Definition(d) => {
233                    // SAFETY: at this point `term` is a `KeyOrKeyword::Key`,
234                    // since the `Type` arm above returns early.
235                    let key = unsafe { term.as_key().unwrap_unchecked() };
236                    // Initialize `previous_definition` to any existing term definition for `term` in
237                    // `active_context`, removing that term definition from active context.
238                    let previous_definition = active_context.set_normal(key.clone(), None);
239
240                    let simple_term = !d.map(jsonld_syntax::context::TermDefinition::is_expanded).unwrap_or(false);
241                    let value = term_definition::ExpandedRef::from(d);
242
243                    // Create a new term definition, `definition`, initializing `prefix` flag to
244                    // `false`, `protected` to `protected`, and `reverse_property` to `false`.
245                    let mut definition = NormalTermDefinition::<N::Iri, N::BlankId> {
246                        protected,
247                        ..Default::default()
248                    };
249
250                    // If the @protected entry in value is true set the protected flag in
251                    // definition to true.
252                    if let Some(protected) = value.protected {
253                        // If processing mode is json-ld-1.0, an invalid term definition has
254                        // been detected and processing is aborted.
255                        if options.processing_mode == ProcessingMode::JsonLd1_0 {
256                            return Err(Error::InvalidTermDefinition);
257                        }
258
259                        definition.protected = protected;
260                    }
261
262                    // If value contains the entry @type:
263                    if let Some(type_) = value.type_ {
264                        // Set `typ` to the result of IRI expanding type, using local context,
265                        // and defined.
266                        let typ = expand_iri_with(
267                            Environment {
268                                vocabulary: env.vocabulary,
269                                loader: env.loader,
270                                warnings: env.warnings,
271                            },
272                            active_context,
273                            type_.cast(),
274                            false,
275                            Some(options.vocab),
276                            local_context,
277                            defined,
278                            remote_contexts.clone(),
279                            options,
280                        )
281                        .await?;
282
283                        if let Some(typ) = typ {
284                            let typ = Arc::try_unwrap(typ).unwrap_or_else(|a| (*a).clone());
285                            // If the expanded type is @json or @none, and processing mode is
286                            // json-ld-1.0, an invalid type mapping error has been detected and
287                            // processing is aborted.
288                            if options.processing_mode == ProcessingMode::JsonLd1_0
289                                && (typ == Term::Keyword(Keyword::Json) || typ == Term::Keyword(Keyword::None))
290                            {
291                                return Err(Error::InvalidTypeMapping);
292                            }
293
294                            if let Ok(typ) = typ.try_into() {
295                                // Set the type mapping for definition to type.
296                                definition.typ = Some(typ);
297                            } else {
298                                return Err(Error::InvalidTypeMapping);
299                            }
300                        }
301                    }
302
303                    // If `value` contains the entry @reverse:
304                    if let Some(reverse_value) = value.reverse {
305                        // If `value` contains `@id` or `@nest`, entries, an invalid reverse
306                        // property error has been detected and processing is aborted.
307                        if value.id.is_some() || value.nest.is_some() {
308                            return Err(Error::InvalidReverseProperty);
309                        }
310
311                        // If the value associated with the @reverse entry is a string having
312                        // the form of a keyword, return; processors SHOULD generate a warning.
313                        if reverse_value.is_keyword_like() {
314                            env.warnings.handle(env.vocabulary, Warning::KeywordLikeValue(reverse_value.to_string()));
315                            return Ok(());
316                        }
317
318                        // Otherwise, set the IRI mapping of definition to the result of IRI
319                        // expanding the value associated with the @reverse entry, using
320                        // local context, and defined.
321                        // If the result does not have the form of an IRI or a blank node
322                        // identifier, an invalid IRI mapping error has been detected and
323                        // processing is aborted.
324                        match expand_iri_with(
325                            Environment {
326                                vocabulary: env.vocabulary,
327                                loader: env.loader,
328                                warnings: env.warnings,
329                            },
330                            active_context,
331                            Nullable::Some(reverse_value.as_str().into()),
332                            false,
333                            Some(options.vocab),
334                            local_context,
335                            defined,
336                            remote_contexts.clone(),
337                            options,
338                        )
339                        .await?
340                        {
341                            Some(arc) if matches!(arc.as_ref(), Term::Id(m) if m.is_valid()) => definition.value = Some(arc),
342                            _ => return Err(Error::InvalidIriMapping),
343                        }
344
345                        // If `value` contains an `@container` entry, set the `container`
346                        // mapping of `definition` to an array containing its value;
347                        // if its value is neither `@set`, nor `@index`, nor null, an
348                        // invalid reverse property error has been detected (reverse properties
349                        // only support set- and index-containers) and processing is aborted.
350                        if let Some(container_value) = value.container {
351                            match container_value {
352                                Nullable::Null => (),
353                                Nullable::Some(container_value) => {
354                                    let container_value = Container::from_syntax(Nullable::Some(container_value)).map_err(|_| Error::InvalidReverseProperty)?;
355
356                                    if matches!(container_value, Container::Set | Container::Index) {
357                                        definition.container = container_value;
358                                    } else {
359                                        return Err(Error::InvalidReverseProperty);
360                                    }
361                                }
362                            }
363                        }
364
365                        // Set the `reverse_property` flag of `definition` to `true`.
366                        definition.reverse_property = true;
367                    }
368
369                    // The specification's step 14 onwards is skipped for reverse
370                    // properties: as amended, the algorithm falls through from
371                    // `@reverse` but takes the "Otherwise" branch. See
372                    // https://github.com/w3c/json-ld-api/issues/565.
373                    if !definition.reverse_property {
374                        match value.id {
375                            // If `value` contains the entry `@id` and its value does not equal `term`:
376                            Some(id_value) if id_value.cast::<KeyOrKeywordRef>() != Nullable::Some(key.into()) => {
377                                match id_value {
378                                    // If the `@id` entry of value is `null`, the term is not used for IRI
379                                    // expansion, but is retained to be able to detect future redefinitions
380                                    // of this term.
381                                    Nullable::Null => (),
382                                    Nullable::Some(id_value) => {
383                                        // Otherwise:
384                                        // If the value associated with the `@id` entry is not a
385                                        // keyword, but has the form of a keyword, return;
386                                        // processors SHOULD generate a warning.
387                                        if id_value.is_keyword_like() && !id_value.is_keyword() {
388                                            debug_assert!(Keyword::try_from(id_value.as_str()).is_err());
389                                            env.warnings.handle(env.vocabulary, Warning::KeywordLikeValue(id_value.to_string()));
390                                            return Ok(());
391                                        }
392
393                                        // Otherwise, set the IRI mapping of `definition` to the result
394                                        // of IRI expanding the value associated with the `@id` entry,
395                                        // using `local_context`, and `defined`.
396                                        definition.value = match expand_iri_with(
397                                            Environment {
398                                                vocabulary: env.vocabulary,
399                                                loader: env.loader,
400                                                warnings: env.warnings,
401                                            },
402                                            active_context,
403                                            Nullable::Some(id_value.into()),
404                                            false,
405                                            Some(options.vocab),
406                                            local_context,
407                                            defined,
408                                            remote_contexts.clone(),
409                                            options,
410                                        )
411                                        .await?
412                                        {
413                                            Some(arc) if arc.as_ref() == &Term::Keyword(Keyword::Context) => {
414                                                // if it equals `@context`, an invalid keyword alias error has
415                                                // been detected and processing is aborted.
416                                                return Err(Error::InvalidKeywordAlias);
417                                            }
418                                            Some(arc) if matches!(arc.as_ref(), Term::Id(p) if !p.is_valid()) => {
419                                                // If the resulting IRI mapping is neither a keyword,
420                                                // nor an IRI, nor a blank node identifier, an
421                                                // invalid IRI mapping error has been detected and processing
422                                                // is aborted;
423                                                return Err(Error::InvalidIriMapping);
424                                            }
425                                            value => value,
426                                        };
427
428                                        // If `term` contains a colon (:) anywhere but as the first or
429                                        // last character of `term`, or if it contains a slash (/)
430                                        // anywhere:
431                                        //
432                                        // This round-trip check was introduced in JSON-LD 1.1; 1.0 let
433                                        // a term that looks like a compact IRI expand to something else
434                                        // (`expand#t0026`, `expand#t0071`).
435                                        if options.processing_mode != ProcessingMode::JsonLd1_0
436                                            && (contains_between_boundaries(key.as_str(), ':') || key.as_str().contains('/'))
437                                        {
438                                            // Set the value associated with `defined`'s `term` entry
439                                            // to `true`.
440                                            defined.end(&term);
441
442                                            // If the result of IRI expanding `term` using
443                                            // `local_context`, and `defined`, is not the same as the
444                                            // IRI mapping of definition, an invalid IRI mapping error
445                                            // has been detected and processing is aborted.
446                                            let expanded_term = expand_iri_with(
447                                                Environment {
448                                                    vocabulary: env.vocabulary,
449                                                    loader: env.loader,
450                                                    warnings: env.warnings,
451                                                },
452                                                active_context,
453                                                Nullable::Some((&term).into()),
454                                                false,
455                                                Some(options.vocab),
456                                                local_context,
457                                                defined,
458                                                remote_contexts.clone(),
459                                                options,
460                                            )
461                                            .await?;
462                                            if definition.value.as_deref() != expanded_term.as_deref() {
463                                                return Err(Error::InvalidIriMapping);
464                                            }
465                                        }
466
467                                        // If `term` contains neither a colon (:) nor a slash (/),
468                                        // simple term is true, and if the IRI mapping of definition
469                                        // is either an IRI ending with a gen-delim character,
470                                        // or a blank node identifier, set the `prefix` flag in
471                                        // `definition` to true.
472                                        if !key.as_str().contains(':')
473                                            && !key.as_str().contains('/')
474                                            && simple_term
475                                            && definition.value.as_ref().is_some_and(|v| is_gen_delim_or_blank(env.vocabulary, v))
476                                        {
477                                            definition.prefix = true;
478                                        }
479                                    }
480                                }
481                            }
482                            Some(Nullable::Some(IdRef::Keyword(Keyword::Type))) => {
483                                // Otherwise, if `term` is ``@type`, set the IRI mapping of definition to
484                                // `@type`.
485                                definition.value = Some(Arc::new(Term::Keyword(Keyword::Type)));
486                            }
487                            _ => {
488                                // Otherwise if the `term` contains a colon (:) anywhere after the first
489                                // character.
490                                if let KeyOrKeyword::Key(term) = &term {
491                                    if let Ok(compact_iri) = CompactIri::new(term.as_str()) {
492                                        // If `term` is a compact IRI with a prefix that is an entry in local
493                                        // context a dependency has been found.
494                                        // Use this algorithm recursively passing `active_context`,
495                                        // `local_context`, the prefix as term, and `defined`.
496                                        Recursion::new(define(
497                                            Environment {
498                                                vocabulary: env.vocabulary,
499                                                loader: env.loader,
500                                                warnings: env.warnings,
501                                            },
502                                            active_context,
503                                            local_context,
504                                            KeyOrKeywordRef::Key(compact_iri.prefix().into()),
505                                            defined,
506                                            remote_contexts.clone(),
507                                            None,
508                                            false,
509                                            options.with_no_override(),
510                                        ))
511                                        .await?;
512
513                                        // If `term`'s prefix has a term definition in `active_context`, set the
514                                        // IRI mapping of `definition` to the result of concatenating the value
515                                        // associated with the prefix's IRI mapping and the term's suffix.
516                                        if let Some(prefix_definition) = active_context.get(compact_iri.prefix()) {
517                                            let mut result = String::new();
518
519                                            if let Some(prefix_key) = prefix_definition.value()
520                                                && let Some(prefix_iri) = prefix_key.as_iri()
521                                                && let Some(iri) = env.vocabulary.iri(prefix_iri)
522                                            {
523                                                result = iri.to_string();
524                                            }
525
526                                            result.push_str(compact_iri.suffix());
527
528                                            if let Ok(iri) = Iri::parse(result.as_str()) {
529                                                definition.value = Some(Arc::new(Term::Id(Id::iri(env.vocabulary.insert(iri)))));
530                                            } else {
531                                                return Err(Error::InvalidIriMapping);
532                                            }
533                                        }
534                                    }
535
536                                    // not a compact IRI
537                                    if definition.value.is_none() {
538                                        if let Ok(blank_id) = BlankId::new(term.as_str()) {
539                                            definition.value = Some(Arc::new(Term::Id(Id::blank(env.vocabulary.insert_blank_id(blank_id)))));
540                                        } else if let Ok(iri_ref) = IriRef::parse(term.as_str()) {
541                                            match Iri::try_from(iri_ref) {
542                                                Ok(iri) => definition.value = Some(Arc::new(Term::Id(Id::iri(env.vocabulary.insert(iri))))),
543                                                Err(_) => {
544                                                    if iri_ref.as_str().contains('/') {
545                                                        // Term is a relative IRI reference.
546                                                        // Set the IRI mapping of definition to the result of IRI expanding
547                                                        // term.
548                                                        match expand_iri_simple(
549                                                            &mut env,
550                                                            active_context,
551                                                            Nullable::Some(ExpandableRef::String(iri_ref.as_str())),
552                                                            false,
553                                                            Some(options.vocab),
554                                                        )? {
555                                                            Some(arc) if matches!(arc.as_ref(), Term::Id(Id::Valid(ValidId::Iri(_)))) => {
556                                                                definition.value = Some(arc);
557                                                            }
558                                                            // If the resulting IRI mapping is not an IRI, an invalid IRI mapping
559                                                            // error has been detected and processing is aborted.
560                                                            _ => return Err(Error::InvalidIriMapping),
561                                                        }
562                                                    }
563                                                }
564                                            }
565                                        }
566
567                                        // not a compact IRI, IRI, IRI reference or blank node id.
568                                        if definition.value.is_none() {
569                                            if let Some(context_vocabulary) = active_context.vocabulary() {
570                                                // Otherwise, if `active_context` has a vocabulary mapping, the IRI mapping
571                                                // of `definition` is set to the result of concatenating the value
572                                                // associated with the vocabulary mapping and `term`.
573                                                // If it does not have a vocabulary mapping, an invalid IRI mapping error
574                                                // has been detected and processing is aborted.
575                                                if let Some(vocabulary_iri) = context_vocabulary.as_iri() {
576                                                    let mut result = env.vocabulary.iri(vocabulary_iri).map(|i| i.to_string()).unwrap_or_default();
577                                                    result.push_str(key.as_str());
578                                                    if let Ok(iri) = Iri::parse(result.as_str()) {
579                                                        definition.value = Some(Arc::new(Term::<N::Iri, N::BlankId>::from(env.vocabulary.insert(iri))));
580                                                    } else {
581                                                        return Err(Error::InvalidIriMapping);
582                                                    }
583                                                } else {
584                                                    return Err(Error::InvalidIriMapping);
585                                                }
586                                            } else {
587                                                // If it does not have a vocabulary mapping, an invalid IRI mapping error
588                                                // has been detected and processing is aborted.
589                                                return Err(Error::InvalidIriMapping);
590                                            }
591                                        }
592                                    }
593                                }
594                            }
595                        }
596                    }
597
598                    // If value contains the entry @container:
599                    if let Some(container_value) = value.container {
600                        // If the container value is @graph, @id, or @type, or is otherwise not a
601                        // string, generate an invalid container mapping error and abort processing
602                        // if processing mode is json-ld-1.0.
603                        if options.processing_mode == ProcessingMode::JsonLd1_0 {
604                            match container_value {
605                                Nullable::Null
606                                | Nullable::Some(
607                                    jsonld_syntax::Container::Many(_)
608                                    | jsonld_syntax::Container::One(ContainerKind::Graph | ContainerKind::Id | ContainerKind::Type),
609                                ) => return Err(Error::InvalidContainerMapping),
610                                Nullable::Some(_) => (),
611                            }
612                        }
613
614                        let container_value = Container::from_syntax(container_value).map_err(|_| Error::InvalidContainerMapping)?;
615
616                        // Initialize `container` to the value associated with the `@container`
617                        // entry, which MUST be either `@graph`, `@id`, `@index`, `@language`,
618                        // `@list`, `@set`, `@type`, or an array containing exactly any one of
619                        // those keywords, an array containing `@graph` and either `@id` or
620                        // `@index` optionally including `@set`, or an array containing a
621                        // combination of `@set` and any of `@index`, `@graph`, `@id`, `@type`,
622                        // `@language` in any order.
623                        // Otherwise, an invalid container mapping has been detected and processing
624                        // is aborted.
625                        definition.container = container_value;
626
627                        // Set the container mapping of definition to container coercing to an
628                        // array, if necessary.
629                        // already done.
630
631                        // If the `container` mapping of definition includes `@type`:
632                        if definition.container.contains(ContainerKind::Type) {
633                            if let Some(typ) = &definition.typ {
634                                // If type mapping in definition is neither `@id` nor `@vocab`,
635                                // an invalid type mapping error has been detected and processing
636                                // is aborted.
637                                match typ {
638                                    Type::Id | Type::Vocab => (),
639                                    _ => return Err(Error::InvalidTypeMapping),
640                                }
641                            } else {
642                                // If type mapping in definition is undefined, set it to @id.
643                                definition.typ = Some(Type::Id);
644                            }
645                        }
646                    }
647
648                    // If value contains the entry @index:
649                    if let Some(index_value) = value.index {
650                        // If processing mode is json-ld-1.0 or container mapping does not include
651                        // `@index`, an invalid term definition has been detected and processing
652                        // is aborted.
653                        if !definition.container.contains(ContainerKind::Index) || options.processing_mode == ProcessingMode::JsonLd1_0 {
654                            return Err(Error::InvalidTermDefinition);
655                        }
656
657                        // Initialize `index` to the value associated with the `@index` entry,
658                        // which MUST be a string expanding to an IRI.
659                        // Otherwise, an invalid term definition has been detected and processing
660                        // is aborted.
661                        match expand_iri_simple(
662                            &mut env,
663                            active_context,
664                            Nullable::Some(index_value.as_str().into()),
665                            false,
666                            Some(options.vocab),
667                        )?
668                        .as_deref()
669                        {
670                            Some(Term::Id(Id::Valid(ValidId::Iri(_)))) => (),
671                            _ => return Err(Error::InvalidTermDefinition),
672                        }
673
674                        definition.index = Some(index_value.to_owned());
675                    }
676
677                    // If `value` contains the entry `@context`:
678                    if let Some(context) = value.context {
679                        // If processing mode is json-ld-1.0, an invalid term definition has been
680                        // detected and processing is aborted.
681                        if options.processing_mode == ProcessingMode::JsonLd1_0 {
682                            return Err(Error::InvalidTermDefinition);
683                        }
684
685                        // Initialize `context` to the value associated with the @context entry,
686                        // which is treated as a local context.
687                        // done.
688
689                        // Invoke the Context Processing algorithm using the `active_context`,
690                        // `context` as local context, `base_url`, `true` for override protected,
691                        // a copy of remote contexts, and `false` for validate scoped context.
692                        // If any error is detected, an invalid scoped context error has been
693                        // detected and processing is aborted.
694                        Recursion::new(super::process_context(
695                            env,
696                            active_context,
697                            context,
698                            remote_contexts.clone(),
699                            base_url.clone(),
700                            options.with_override(),
701                            false,
702                        ))
703                        .await
704                        .map_err(|e| match e {
705                            // A resource-limit abort is not a context error:
706                            // let it surface instead of masking it.
707                            Error::ContextOverflow => Error::ContextOverflow,
708                            _ => Error::InvalidScopedContext,
709                        })?;
710
711                        // Set the local context of definition to context, and base URL to base URL.
712                        definition.context = Some(Box::new(context.clone()));
713                        definition.base_url = base_url;
714                    }
715
716                    // If `value` contains the entry `@language` and does not contain the entry
717                    // `@type`:
718                    if value.type_.is_none() {
719                        if let Some(language_value) = value.language {
720                            // Initialize `language` to the value associated with the `@language`
721                            // entry, which MUST be either null or a string.
722                            // If `language` is not well-formed according to section 2.2.9 of
723                            // [BCP47], processors SHOULD issue a warning.
724                            // Otherwise, an invalid language mapping error has been detected and
725                            // processing is aborted.
726                            // Set the `language` mapping of definition to `language`.
727                            definition.language = Some(language_value.map(LenientLangTag::to_owned));
728                        }
729
730                        // If `value` contains the entry `@direction` and does not contain the
731                        // entry `@type`:
732                        if let Some(direction_value) = value.direction {
733                            // Initialize `direction` to the value associated with the `@direction`
734                            // entry, which MUST be either null, "ltr", or "rtl".
735                            definition.direction = Some(direction_value);
736                        }
737                    }
738
739                    // If value contains the entry @nest:
740                    if let Some(nest_value) = value.nest {
741                        // If processing mode is json-ld-1.0, an invalid term definition has been
742                        // detected and processing is aborted.
743                        if options.processing_mode == ProcessingMode::JsonLd1_0 {
744                            return Err(Error::InvalidTermDefinition);
745                        }
746
747                        definition.nest = Some(nest_value.clone());
748                    }
749
750                    // If value contains the entry @prefix:
751                    if let Some(prefix_value) = value.prefix {
752                        // If processing mode is json-ld-1.0, or if `term` contains a colon (:) or
753                        // slash (/), an invalid term definition has been detected and processing
754                        // is aborted.
755                        if key.as_str().contains(':') || key.as_str().contains('/') || options.processing_mode == ProcessingMode::JsonLd1_0 {
756                            return Err(Error::InvalidTermDefinition);
757                        }
758
759                        // Set the `prefix` flag to the value associated with the @prefix entry,
760                        // which MUST be a boolean.
761                        // Otherwise, an invalid @prefix value error has been detected and
762                        // processing is aborted.
763                        definition.prefix = prefix_value;
764
765                        // If the `prefix` flag of `definition` is set to `true`, and its IRI
766                        // mapping is a keyword, an invalid term definition has been detected and
767                        // processing is aborted.
768                        if definition.prefix && definition.value.as_ref().is_some_and(|v| v.is_keyword()) {
769                            return Err(Error::InvalidTermDefinition);
770                        }
771                    }
772
773                    // If value contains any entry other than @id, @reverse, @container, @context,
774                    // @direction, @index, @language, @nest, @prefix, @protected, or @type, an
775                    // invalid term definition error has been detected and processing is aborted.
776                    if value.propagate.is_some() {
777                        return Err(Error::InvalidTermDefinition);
778                    }
779
780                    // If override protected is false and previous_definition exists and is protected;
781                    if !options.override_protected
782                        && let Some(previous_definition) = previous_definition
783                        && previous_definition.protected
784                    {
785                        // If `definition` is not the same as `previous_definition`
786                        // (other than the value of protected), a protected term
787                        // redefinition error has been detected, and processing is aborted.
788                        if definition.modulo_protected_field() != previous_definition.modulo_protected_field() {
789                            return Err(Error::ProtectedTermRedefinition);
790                        }
791
792                        // Set `definition` to `previous definition` to retain the value of
793                        // protected.
794                        definition.protected = true;
795                    }
796
797                    // Set the term definition of `term` in `active_context` to `definition` and
798                    // set the value associated with `defined`'s entry term to true.
799                    active_context.set_normal(key.clone(), Some(definition));
800                }
801                _ => {
802                    // Otherwise, since keywords cannot be overridden, term MUST NOT be a keyword and
803                    // a keyword redefinition error has been detected and processing is aborted.
804                    return Err(Error::KeywordRedefinition);
805                }
806            }
807        }
808
809        defined.end(&term);
810    }
811
812    Ok(())
813}