Skip to main content

jsonld_syntax/
keyword.rs

1use std::{borrow::Borrow, convert::TryFrom, fmt};
2
3#[derive(Clone, Copy, Debug)]
4/// Error raised when a string is not a JSON-LD keyword.
5pub struct NotAKeyword<T>(pub T);
6
7/// JSON-LD keywords.
8#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
9#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
10pub enum Keyword {
11    /// `@base`.
12    /// Used to set the base IRI against which to resolve those relative IRI references
13    /// which are otherwise interpreted relative to the document.
14    #[cfg_attr(feature = "serde", serde(rename = "@base"))]
15    Base,
16
17    /// `@container`.
18    /// Used to set the default container type for a term.
19    #[cfg_attr(feature = "serde", serde(rename = "@container"))]
20    Container,
21
22    /// `@context`.
23    /// Used to define the short-hand names that are used throughout a JSON-LD document.
24    #[cfg_attr(feature = "serde", serde(rename = "@context"))]
25    Context,
26
27    /// `@direction`.
28    /// Used to set the base direction of those JSON-LD values that are not typed
29    /// values, that is strings and language-tagged strings.
30    #[cfg_attr(feature = "serde", serde(rename = "@direction"))]
31    Direction,
32
33    /// `@graph`.
34    /// Used to express a graph.
35    #[cfg_attr(feature = "serde", serde(rename = "@graph"))]
36    Graph,
37
38    /// `@id`.
39    /// Used to uniquely identify node objects that are being described in the document with IRIs
40    /// or blank node identifiers.
41    #[cfg_attr(feature = "serde", serde(rename = "@id"))]
42    Id,
43
44    /// `@import`.
45    /// Used in a context definition to load an external context within which the containing
46    /// context definition is merged.
47    #[cfg_attr(feature = "serde", serde(rename = "@import"))]
48    Import,
49
50    /// `@included`.
51    /// Used in a top-level node object to define an included block, for including secondary node
52    /// objects within another node object.
53    #[cfg_attr(feature = "serde", serde(rename = "@included"))]
54    Included,
55
56    /// `@index`.
57    /// Used to specify that a container is used to index information and that processing should
58    /// continue deeper into a JSON data structure.
59    #[cfg_attr(feature = "serde", serde(rename = "@index"))]
60    Index,
61
62    /// `@json`.
63    /// Used as the `@type` value of a JSON literal.
64    #[cfg_attr(feature = "serde", serde(rename = "@json"))]
65    Json,
66
67    /// `@language`.
68    /// Used to specify the language for a particular string value or the default language of a
69    /// JSON-LD document.
70    #[cfg_attr(feature = "serde", serde(rename = "@language"))]
71    Language,
72
73    /// `@list`.
74    /// Used to express an ordered set of data.
75    #[cfg_attr(feature = "serde", serde(rename = "@list"))]
76    List,
77
78    /// `@nest`.
79    /// Used to define a property of a node object that groups together properties of that node,
80    /// but is not an edge in the graph.
81    #[cfg_attr(feature = "serde", serde(rename = "@nest"))]
82    Nest,
83
84    /// `@none`.
85    /// Used as an index value in an index map, id map, language map, type map, or elsewhere where
86    /// a map is used to index into other values, when the indexed node does not have the feature
87    /// being indexed.
88    #[cfg_attr(feature = "serde", serde(rename = "@none"))]
89    None,
90
91    /// `@prefix`.
92    /// With the value true, allows this term to be used to construct a compact IRI when
93    /// compacting.
94    #[cfg_attr(feature = "serde", serde(rename = "@prefix"))]
95    Prefix,
96
97    /// `@propagate`.
98    /// Used in a context definition to change the scope of that context.
99    ///
100    /// By default, it is true, meaning that contexts propagate across node objects
101    /// (other than for type-scoped contexts, which default to false).
102    /// Setting this to false causes term definitions created within that context to be removed
103    /// when entering a new node object.
104    #[cfg_attr(feature = "serde", serde(rename = "@propagate"))]
105    Propagate,
106
107    /// `@protected`.
108    /// Used to prevent term definitions of a context to be overridden by other contexts.
109    #[cfg_attr(feature = "serde", serde(rename = "@protected"))]
110    Protected,
111
112    /// `@reverse`.
113    /// Used to express reverse properties.
114    #[cfg_attr(feature = "serde", serde(rename = "@reverse"))]
115    Reverse,
116
117    /// `@set`.
118    /// Used to express an unordered set of data and to ensure that values are always represented
119    /// as arrays.
120    #[cfg_attr(feature = "serde", serde(rename = "@set"))]
121    Set,
122
123    /// `@type`.
124    /// Used to set the type of a node or the datatype of a typed value.
125    #[cfg_attr(feature = "serde", serde(rename = "@type"))]
126    Type,
127
128    /// `@value`.
129    /// Used to specify the data that is associated with a particular property in the graph.
130    #[cfg_attr(feature = "serde", serde(rename = "@value"))]
131    Value,
132
133    /// `@version`.
134    /// Used in a context definition to set the processing mode.
135    #[cfg_attr(feature = "serde", serde(rename = "@version"))]
136    Version,
137
138    /// `@vocab`.
139    /// Used to expand properties and values in `@type` with a common prefix IRI.
140    #[cfg_attr(feature = "serde", serde(rename = "@vocab"))]
141    Vocab,
142}
143
144impl Keyword {
145    /// Returns the spelling of this keyword, including the leading `@`.
146    #[must_use]
147    pub fn into_str(self) -> &'static str {
148        use Keyword::{
149            Base,
150            Container,
151            Context,
152            Direction,
153            Graph,
154            Id,
155            Import,
156            Included,
157            Index,
158            Json,
159            Language,
160            List,
161            Nest,
162            None,
163            Prefix,
164            Propagate,
165            Protected,
166            Reverse,
167            Set,
168            Type,
169            Value,
170            Version,
171            Vocab,
172        };
173        match self {
174            Base => "@base",
175            Container => "@container",
176            Context => "@context",
177            Direction => "@direction",
178            Graph => "@graph",
179            Id => "@id",
180            Import => "@import",
181            Included => "@included",
182            Index => "@index",
183            Json => "@json",
184            Language => "@language",
185            List => "@list",
186            Nest => "@nest",
187            None => "@none",
188            Prefix => "@prefix",
189            Propagate => "@propagate",
190            Protected => "@protected",
191            Reverse => "@reverse",
192            Set => "@set",
193            Type => "@type",
194            Value => "@value",
195            Version => "@version",
196            Vocab => "@vocab",
197        }
198    }
199}
200
201impl<'a> TryFrom<&'a str> for Keyword {
202    type Error = NotAKeyword<&'a str>;
203
204    fn try_from(s: &'a str) -> Result<Keyword, NotAKeyword<&'a str>> {
205        use Keyword::{
206            Base,
207            Container,
208            Context,
209            Direction,
210            Graph,
211            Id,
212            Import,
213            Included,
214            Index,
215            Json,
216            Language,
217            List,
218            Nest,
219            None,
220            Prefix,
221            Propagate,
222            Protected,
223            Reverse,
224            Set,
225            Type,
226            Value,
227            Version,
228            Vocab,
229        };
230        match s {
231            "@base" => Ok(Base),
232            "@container" => Ok(Container),
233            "@context" => Ok(Context),
234            "@direction" => Ok(Direction),
235            "@graph" => Ok(Graph),
236            "@id" => Ok(Id),
237            "@import" => Ok(Import),
238            "@included" => Ok(Included),
239            "@index" => Ok(Index),
240            "@json" => Ok(Json),
241            "@language" => Ok(Language),
242            "@list" => Ok(List),
243            "@nest" => Ok(Nest),
244            "@none" => Ok(None),
245            "@prefix" => Ok(Prefix),
246            "@propagate" => Ok(Propagate),
247            "@protected" => Ok(Protected),
248            "@reverse" => Ok(Reverse),
249            "@set" => Ok(Set),
250            "@type" => Ok(Type),
251            "@value" => Ok(Value),
252            "@version" => Ok(Version),
253            "@vocab" => Ok(Vocab),
254            _ => Err(NotAKeyword(s)),
255        }
256    }
257}
258
259impl From<Keyword> for &'static str {
260    fn from(k: Keyword) -> &'static str {
261        k.into_str()
262    }
263}
264
265impl fmt::Display for Keyword {
266    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
267        self.into_str().fmt(f)
268    }
269}
270
271/// Checks whether the given string is one of the JSON-LD keywords.
272#[must_use]
273pub fn is_keyword(str: &str) -> bool {
274    Keyword::try_from(str).is_ok()
275}
276
277fn is_alpha(c: char) -> bool {
278    let c = c as u32;
279    (0x41..=0x5a).contains(&c) || (0x61..=0x7a).contains(&c)
280}
281
282/// Checks whether the given string is *keyword-like*: an `@` followed by one
283/// or more ASCII letters.
284///
285/// Such strings are reserved for future revisions of JSON-LD. A keyword-like
286/// term that is not an actual keyword must be ignored during processing rather
287/// than treated as a term.
288#[must_use]
289pub fn is_keyword_like(s: &str) -> bool {
290    if s.len() > 1 {
291        for (i, c) in s.chars().enumerate() {
292            if (i == 0 && c != '@') || (i > 0 && !is_alpha(c)) {
293                return false;
294            }
295        }
296
297        true
298    } else {
299        false
300    }
301}
302
303#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Debug)]
304/// The `@type` keyword, as a type of its own.
305///
306/// Its [`Borrow<str>`](Borrow) implementation yields `"@type"`, which lets a
307/// term lookup that is generic over the key type match the `@type` entry of a
308/// context alongside ordinary terms.
309pub struct KeywordType;
310
311impl KeywordType {
312    /// Returns `"@type"`.
313    #[must_use]
314    pub fn as_str(&self) -> &'static str {
315        "@type"
316    }
317}
318
319impl Borrow<str> for KeywordType {
320    fn borrow(&self) -> &str {
321        self.as_str()
322    }
323}