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`, `true` for override protected,
681 // a copy of remote contexts, and `false` for validate scoped context.
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 false,
692 ))
693 .await
694 .map_err(|e| match e {
695 // A resource-limit abort is not a context error:
696 // let it surface instead of masking it.
697 Error::ContextOverflow => Error::ContextOverflow,
698 _ => Error::InvalidScopedContext,
699 })?;
700
701 // Set the local context of definition to context, and base URL to base URL.
702 definition.context = Some(Box::new(context.clone()));
703 definition.base_url = base_url;
704 }
705
706 // If `value` contains the entry `@language` and does not contain the entry
707 // `@type`:
708 if value.type_.is_none() {
709 if let Some(language_value) = value.language {
710 // Initialize `language` to the value associated with the `@language`
711 // entry, which MUST be either null or a string.
712 // If `language` is not well-formed according to section 2.2.9 of
713 // [BCP47], processors SHOULD issue a warning.
714 // Otherwise, an invalid language mapping error has been detected and
715 // processing is aborted.
716 // Set the `language` mapping of definition to `language`.
717 definition.language = Some(language_value.map(LenientLangTag::to_owned));
718 }
719
720 // If `value` contains the entry `@direction` and does not contain the
721 // entry `@type`:
722 if let Some(direction_value) = value.direction {
723 // Initialize `direction` to the value associated with the `@direction`
724 // entry, which MUST be either null, "ltr", or "rtl".
725 definition.direction = Some(direction_value);
726 }
727 }
728
729 // If value contains the entry @nest:
730 if let Some(nest_value) = value.nest {
731 // If processing mode is json-ld-1.0, an invalid term definition has been
732 // detected and processing is aborted.
733 if options.processing_mode == ProcessingMode::JsonLd1_0 {
734 return Err(Error::InvalidTermDefinition);
735 }
736
737 definition.nest = Some(nest_value.clone());
738 }
739
740 // If value contains the entry @prefix:
741 if let Some(prefix_value) = value.prefix {
742 // If processing mode is json-ld-1.0, or if `term` contains a colon (:) or
743 // slash (/), an invalid term definition has been detected and processing
744 // is aborted.
745 if key.as_str().contains(':') || key.as_str().contains('/') || options.processing_mode == ProcessingMode::JsonLd1_0 {
746 return Err(Error::InvalidTermDefinition);
747 }
748
749 // Set the `prefix` flag to the value associated with the @prefix entry,
750 // which MUST be a boolean.
751 // Otherwise, an invalid @prefix value error has been detected and
752 // processing is aborted.
753 definition.prefix = prefix_value;
754
755 // If the `prefix` flag of `definition` is set to `true`, and its IRI
756 // mapping is a keyword, an invalid term definition has been detected and
757 // processing is aborted.
758 if definition.prefix && definition.value.as_ref().is_some_and(|v| v.is_keyword()) {
759 return Err(Error::InvalidTermDefinition);
760 }
761 }
762
763 // If value contains any entry other than @id, @reverse, @container, @context,
764 // @direction, @index, @language, @nest, @prefix, @protected, or @type, an
765 // invalid term definition error has been detected and processing is aborted.
766 if value.propagate.is_some() {
767 return Err(Error::InvalidTermDefinition);
768 }
769
770 // If override protected is false and previous_definition exists and is protected;
771 if !options.override_protected
772 && let Some(previous_definition) = previous_definition
773 && previous_definition.protected
774 {
775 // If `definition` is not the same as `previous_definition`
776 // (other than the value of protected), a protected term
777 // redefinition error has been detected, and processing is aborted.
778 if definition.modulo_protected_field() != previous_definition.modulo_protected_field() {
779 return Err(Error::ProtectedTermRedefinition);
780 }
781
782 // Set `definition` to `previous definition` to retain the value of
783 // protected.
784 definition.protected = true;
785 }
786
787 // Set the term definition of `term` in `active_context` to `definition` and
788 // set the value associated with `defined`'s entry term to true.
789 active_context.set_normal(key.to_owned(), Some(definition));
790 }
791 _ => {
792 // Otherwise, since keywords cannot be overridden, term MUST NOT be a keyword and
793 // a keyword redefinition error has been detected and processing is aborted.
794 return Err(Error::KeywordRedefinition);
795 }
796 }
797 }
798
799 defined.end(&term);
800 }
801
802 Ok(())
803}