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