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