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