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}