Skip to main content

jsonld_expansion/
literal.rs

1use crate::{ActiveProperty, WarningHandler, expand_iri, node_id_of_term};
2use jsonld_context_processing::algorithm::{Action, RejectVocab};
3use jsonld_core::{Context, Environment, IndexedObject, LangString, Node, Object, Type, Value, object::value::Literal};
4use jsonld_syntax::{ErrorCode, LenientLangTag, Nullable};
5use jstrict::Number;
6use rdfx::vocabulary::VocabularyMut;
7
8/// Scalar taken as it appears in the input document.
9pub(crate) enum GivenLiteralValue<'a> {
10    Boolean(bool),
11    Number(&'a Number),
12    String(&'a str),
13}
14
15/// Error raised when value expansion is asked to expand something that is not
16/// a JSON scalar, such as an array or an object.
17#[derive(Debug, thiserror::Error)]
18#[error("not a literal value")]
19pub struct NotALiteral;
20
21impl<'a> GivenLiteralValue<'a> {
22    pub fn new(value: &'a jstrict::Value) -> Result<Self, NotALiteral> {
23        match value {
24            jstrict::Value::Boolean(b) => Ok(Self::Boolean(*b)),
25            jstrict::Value::Number(n) => Ok(Self::Number(n)),
26            jstrict::Value::String(s) => Ok(Self::String(s)),
27            _ => Err(NotALiteral),
28        }
29    }
30
31    pub fn as_str(&self) -> Option<&'a str> {
32        match self {
33            Self::String(s) => Some(s),
34            _ => None,
35        }
36    }
37}
38
39/// Value handed to the Value Expansion algorithm.
40pub(crate) enum LiteralValue<'a> {
41    /// A scalar read from the input document.
42    Given(GivenLiteralValue<'a>),
43    /// A string the algorithm produced itself, as when an index map key is
44    /// re-expanded as the value of the index property.
45    Inferred(jsonld_syntax::String),
46}
47
48impl LiteralValue<'_> {
49    pub fn as_str(&self) -> Option<&str> {
50        match self {
51            Self::Given(v) => v.as_str(),
52            Self::Inferred(s) => Some(s.as_str()),
53        }
54    }
55}
56
57pub(crate) type ExpandedLiteral<T, B> = IndexedObject<T, B>;
58
59/// Error raised while expanding a scalar into a literal or a node reference.
60#[derive(Debug, thiserror::Error)]
61pub enum LiteralExpansionError {
62    /// The active property has a type mapping that is neither `@id`, `@vocab`,
63    /// `@none` nor an IRI, so it cannot be used as the datatype of the
64    /// literal.
65    #[error("Invalid `@type` value")]
66    InvalidTypeValue,
67
68    /// The value had to be expanded through the vocabulary mapping (`@vocab`),
69    /// which the [`Policy`](crate::Policy) in use forbids.
70    #[error("Forbidden use of `@vocab`")]
71    ForbiddenVocab,
72
73    /// The active property has an `@id` type mapping, but the value expanded
74    /// to nothing, leaving no identifier for the node reference.
75    #[error("IRI expansion produced no result")]
76    IdExpansionEmpty,
77
78    /// The value to expand is not a JSON scalar.
79    #[error(transparent)]
80    NotALiteral(#[from] NotALiteral),
81}
82
83impl LiteralExpansionError {
84    /// Returns the JSON-LD error code this error is reported under.
85    #[must_use]
86    pub fn code(&self) -> ErrorCode {
87        match self {
88            Self::InvalidTypeValue => ErrorCode::InvalidTypeValue,
89            Self::ForbiddenVocab => ErrorCode::InvalidVocabMapping,
90            Self::IdExpansionEmpty => ErrorCode::InvalidIdValue,
91            Self::NotALiteral(_) => ErrorCode::InvalidValueObject,
92        }
93    }
94}
95
96impl From<RejectVocab> for LiteralExpansionError {
97    fn from(_value: RejectVocab) -> Self {
98        Self::ForbiddenVocab
99    }
100}
101
102pub(crate) type LiteralExpansionResult<T, B> = Result<ExpandedLiteral<T, B>, LiteralExpansionError>;
103
104/// Expands a scalar into a value object, or into a node reference when the
105/// active property maps its values to identifiers.
106///
107/// Implements the [Value Expansion
108/// algorithm](https://www.w3.org/TR/json-ld11-api/#value-expansion): the type,
109/// language and direction mappings of the active property, or the defaults of
110/// the active context, decide what the scalar becomes.
111pub(crate) fn expand_literal<N, L, W>(
112    mut env: Environment<N, L, W>,
113    vocab_policy: Action,
114    active_context: &Context<N::Iri, N::BlankId>,
115    active_property: ActiveProperty<'_>,
116    value: LiteralValue,
117) -> LiteralExpansionResult<N::Iri, N::BlankId>
118where
119    N: VocabularyMut,
120    N::Iri: Clone,
121    N::BlankId: Clone,
122    W: WarningHandler<N>,
123{
124    let active_property_definition = active_property.get_from(active_context);
125    let active_property_type = if let Some(active_property_definition) = active_property_definition {
126        active_property_definition.typ().cloned()
127    } else {
128        None
129    };
130
131    match active_property_type {
132        // If the `active_property` has a type mapping in `active_context` that is `@id`, and the
133        // `value` is a string, return a new map containing a single entry where the key is `@id` and
134        // the value is the result of IRI expanding `value` using `true` for `document_relative` and
135        // `false` for vocab.
136        Some(Type::Id) if let Some(s) = value.as_str() => {
137            let mut node = Node::new();
138            let Some(id_term) = expand_iri(&mut env, active_context, Nullable::Some(s.into()), true, None)? else {
139                return Err(LiteralExpansionError::IdExpansionEmpty);
140            };
141            node.id = node_id_of_term(id_term);
142            Ok(Object::node(node).into())
143        }
144
145        // If `active_property` has a type mapping in active context that is `@vocab`, and the
146        // value is a string, return a new map containing a single entry where the key is
147        // `@id` and the value is the result of IRI expanding `value` using `true` for
148        // document relative.
149        Some(Type::Vocab) if let Some(s) = value.as_str() => {
150            let mut node = Node::new();
151
152            let ty = expand_iri(&mut env, active_context, Nullable::Some(s.into()), true, Some(vocab_policy))?;
153
154            if let Some(ty) = ty {
155                let id = node_id_of_term(ty);
156                node.id = id;
157            }
158
159            Ok(Object::node(node).into())
160        }
161
162        _ => {
163            // Otherwise, initialize `result` to a map with an `@value` entry whose value is set to
164            // `value`.
165            let result: Literal = match value {
166                LiteralValue::Given(v) => match v {
167                    GivenLiteralValue::Boolean(b) => Literal::Boolean(b),
168                    GivenLiteralValue::Number(n) => Literal::Number(jstrict::NumberBuf::from_number(n)),
169                    GivenLiteralValue::String(s) => Literal::String(s.into()),
170                },
171                LiteralValue::Inferred(s) => Literal::String(s),
172            };
173
174            // If `active_property` has a type mapping in active context, other than `@id`,
175            // `@vocab`, or `@none`, add `@type` to `result` and set its value to the value
176            // associated with the type mapping.
177            let mut ty = None;
178            match active_property_type {
179                None | Some(Type::Id | Type::Vocab | Type::None) => {
180                    // Otherwise, if value is a string:
181                    if let Literal::String(s) = result {
182                        // Initialize `language` to the language mapping for
183                        // `active_property` in `active_context`, if any, otherwise to the
184                        // default language of `active_context`.
185                        let language = if let Some(active_property_definition) = active_property_definition {
186                            if let Some(language) = active_property_definition.language() {
187                                language.cloned().option()
188                            } else {
189                                active_context.default_language().map(LenientLangTag::to_owned)
190                            }
191                        } else {
192                            active_context.default_language().map(LenientLangTag::to_owned)
193                        };
194
195                        // Initialize `direction` to the direction mapping for
196                        // `active_property` in `active_context`, if any, otherwise to the
197                        // default base direction of `active_context`.
198                        let direction = if let Some(active_property_definition) = active_property_definition {
199                            if let Some(direction) = active_property_definition.direction() {
200                                direction.option()
201                            } else {
202                                active_context.default_base_direction()
203                            }
204                        } else {
205                            active_context.default_base_direction()
206                        };
207
208                        // If `language` is not null, add `@language` to result with the value
209                        // `language`.
210                        // If `direction` is not null, add `@direction` to result with the
211                        // value `direction`.
212                        return match LangString::new(s, language, direction) {
213                            Ok(lang_str) => Ok(Object::Value(Value::LangString(lang_str)).into()),
214                            Err(jsonld_core::InvalidLangString(s)) => Ok(Object::Value(Value::Literal(Literal::String(s), None)).into()),
215                        };
216                    }
217                }
218
219                Some(t) => {
220                    if let Ok(t) = t.into_iri() {
221                        ty = Some(t);
222                    } else {
223                        return Err(LiteralExpansionError::InvalidTypeValue);
224                    }
225                }
226            }
227
228            Ok(Object::Value(Value::Literal(result, ty)).into())
229        }
230    }
231}