Skip to main content

jsonld_syntax/context/definition/
key.rs

1use crate::{CompactIri, Keyword, intern::intern_static};
2use iri_rs::Iri;
3use rdfx::BlankId;
4use std::{borrow::Borrow, cmp::Ordering, fmt, hash::Hash};
5
6/// Context key.
7///
8/// Stores the canonical `&'static str` returned by the process-wide interner
9/// directly, so [`Key::as_str`] is a single load and equality on equal logical
10/// strings short-circuits via pointer equality (the interner guarantees a
11/// unique allocation per interned string).
12///
13/// Interned strings are never freed: memory usage grows with the number of
14/// distinct keys the process parses. See the [`crate::intern`] module
15/// documentation for the implications when parsing untrusted input.
16#[derive(Clone, Copy, Debug)]
17pub struct Key(&'static str);
18
19impl Key {
20    /// Parses this key as an IRI, returning `None` if it is not one.
21    #[must_use]
22    pub fn as_iri(&self) -> Option<Iri<&str>> {
23        Iri::parse(self.0).ok()
24    }
25
26    /// Parses this key as a compact IRI, returning `None` if it is not one.
27    #[must_use]
28    pub fn as_compact_iri(&self) -> Option<&CompactIri> {
29        CompactIri::new(self.0).ok()
30    }
31
32    /// Parses this key as a blank node identifier, returning `None` if it is
33    /// not one.
34    #[must_use]
35    pub fn as_blank_id(&self) -> Option<&BlankId> {
36        BlankId::new(self.0).ok()
37    }
38
39    /// Returns the interned string of this key.
40    #[must_use]
41    pub fn as_str(&self) -> &'static str {
42        self.0
43    }
44
45    /// Returns the length of this key, in bytes.
46    #[must_use]
47    pub fn len(&self) -> usize {
48        self.0.len()
49    }
50
51    /// Checks whether this key is the empty string.
52    #[must_use]
53    pub fn is_empty(&self) -> bool {
54        self.0.is_empty()
55    }
56
57    /// Copies this key into a newly allocated `String`.
58    #[must_use]
59    pub fn into_string(self) -> String {
60        self.0.to_owned()
61    }
62
63    /// Checks whether this key is keyword-like: an `@` followed by ASCII
64    /// letters. See [`crate::is_keyword_like`].
65    #[must_use]
66    pub fn is_keyword_like(&self) -> bool {
67        crate::is_keyword_like(self.0)
68    }
69}
70
71impl PartialEq for Key {
72    fn eq(&self, other: &Self) -> bool {
73        // Interned strings are pointer-unique, so a pointer/length match is
74        // sufficient and faster than byte comparison.
75        std::ptr::eq(self.0.as_ptr(), other.0.as_ptr()) && self.0.len() == other.0.len()
76    }
77}
78
79impl Eq for Key {}
80
81impl PartialOrd for Key {
82    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
83        Some(self.cmp(other))
84    }
85}
86
87impl Ord for Key {
88    fn cmp(&self, other: &Self) -> Ordering {
89        if self == other { Ordering::Equal } else { self.0.cmp(other.0) }
90    }
91}
92
93impl From<jstrict::object::Key> for Key {
94    fn from(k: jstrict::object::Key) -> Self {
95        Self::from(k.into_string())
96    }
97}
98
99#[allow(clippy::derived_hash_with_manual_eq)]
100impl Hash for Key {
101    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
102        // Hash via the underlying string so that `Borrow<str>` lookups
103        // (e.g. `HashMap::get(&"foo")`) stay equivalent.
104        self.0.hash(state);
105    }
106}
107
108impl From<String> for Key {
109    fn from(k: String) -> Self {
110        Self(intern_static(&k))
111    }
112}
113
114impl<'a> From<&'a str> for Key {
115    fn from(value: &'a str) -> Self {
116        Self(intern_static(value))
117    }
118}
119
120impl fmt::Display for Key {
121    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
122        self.as_str().fmt(f)
123    }
124}
125
126impl Borrow<str> for Key {
127    fn borrow(&self) -> &str {
128        self.as_str()
129    }
130}
131
132#[cfg(feature = "serde")]
133impl serde::Serialize for Key {
134    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
135    where
136        S: serde::Serializer,
137    {
138        serializer.serialize_str(self.as_str())
139    }
140}
141
142#[cfg(feature = "serde")]
143impl<'de> serde::Deserialize<'de> for Key {
144    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
145    where
146        D: serde::Deserializer<'de>,
147    {
148        struct Visitor;
149
150        impl serde::de::Visitor<'_> for Visitor {
151            type Value = Key;
152
153            fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
154                f.write_str("a string")
155            }
156
157            // `visit_borrowed_str` and `visit_string` forward here by
158            // default; the interner only needs `&str`, so one method covers
159            // borrowing formats, owned buffers (`from_reader`, escaped
160            // keys) and serde's internal `Content` buffering
161            // (`#[serde(flatten)]`) alike.
162            fn visit_str<E: serde::de::Error>(self, v: &str) -> Result<Key, E> {
163                Ok(Key::from(v))
164            }
165        }
166
167        deserializer.deserialize_str(Visitor)
168    }
169}
170
171#[derive(Clone, Copy, PartialEq, Eq, Debug)]
172/// Borrowed context definition key.
173pub struct KeyRef<'a>(&'a str);
174
175impl<'a> KeyRef<'a> {
176    /// Checks whether this key is the empty string.
177    #[must_use]
178    pub fn is_empty(&self) -> bool {
179        self.0.is_empty()
180    }
181
182    /// Checks whether this key is keyword-like: an `@` followed by ASCII
183    /// letters. See [`crate::is_keyword_like`].
184    #[must_use]
185    pub fn is_keyword_like(&self) -> bool {
186        crate::is_keyword_like(self.as_str())
187    }
188
189    /// Returns the borrowed string of this key.
190    #[must_use]
191    pub fn as_str(&self) -> &'a str {
192        self.0
193    }
194
195    /// Interns this key and returns the owned [`Key`].
196    #[must_use]
197    pub fn to_owned(self) -> Key {
198        Key::from(self.0)
199    }
200}
201
202impl<'a> From<&'a str> for KeyRef<'a> {
203    fn from(s: &'a str) -> Self {
204        Self(s)
205    }
206}
207
208impl<'a> From<&'a Key> for KeyRef<'a> {
209    fn from(k: &'a Key) -> Self {
210        Self(k.as_str())
211    }
212}
213
214impl fmt::Display for KeyRef<'_> {
215    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
216        self.0.fmt(f)
217    }
218}
219
220#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)]
221/// Context definition key that may also be a JSON-LD keyword.
222pub enum KeyOrKeyword {
223    /// A JSON-LD keyword.
224    Keyword(Keyword),
225    /// A term key.
226    Key(Key),
227}
228
229impl KeyOrKeyword {
230    /// Checks whether this is the empty key. A keyword is never empty.
231    #[must_use]
232    pub fn is_empty(&self) -> bool {
233        match self {
234            Self::Keyword(_) => false,
235            Self::Key(k) => k.is_empty(),
236        }
237    }
238
239    /// Returns the keyword, or `None` if this is a term key.
240    #[must_use]
241    pub fn into_keyword(self) -> Option<Keyword> {
242        match self {
243            Self::Keyword(k) => Some(k),
244            Self::Key(_) => None,
245        }
246    }
247
248    /// Returns the term key, or `None` if this is a keyword.
249    #[must_use]
250    pub fn into_key(self) -> Option<Key> {
251        match self {
252            Self::Keyword(_) => None,
253            Self::Key(k) => Some(k),
254        }
255    }
256
257    /// Returns the keyword, or `None` if this is a term key.
258    #[must_use]
259    pub fn as_keyword(&self) -> Option<Keyword> {
260        match self {
261            Self::Keyword(k) => Some(*k),
262            Self::Key(_) => None,
263        }
264    }
265
266    /// Borrows the term key, or returns `None` if this is a keyword.
267    #[must_use]
268    pub fn as_key(&self) -> Option<&Key> {
269        match self {
270            Self::Keyword(_) => None,
271            Self::Key(k) => Some(k),
272        }
273    }
274
275    /// Returns this value as it is spelled in the context.
276    #[must_use]
277    pub fn as_str(&self) -> &str {
278        match self {
279            Self::Keyword(k) => k.into_str(),
280            Self::Key(k) => k.as_str(),
281        }
282    }
283}
284
285#[allow(clippy::derived_hash_with_manual_eq)]
286impl Hash for KeyOrKeyword {
287    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
288        self.as_str().hash(state);
289    }
290}
291
292impl fmt::Display for KeyOrKeyword {
293    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
294        match self {
295            Self::Key(k) => k.fmt(f),
296            Self::Keyword(k) => k.fmt(f),
297        }
298    }
299}
300
301#[derive(Clone, Copy, PartialEq, Eq)]
302/// Borrowed context definition key that may also be a keyword.
303pub enum KeyOrKeywordRef<'a> {
304    /// A JSON-LD keyword.
305    Keyword(Keyword),
306    /// A term key.
307    Key(KeyRef<'a>),
308}
309
310impl<'a> KeyOrKeywordRef<'a> {
311    /// Interns the term key, if any, and returns the owned [`KeyOrKeyword`].
312    #[must_use]
313    pub fn to_owned(self) -> KeyOrKeyword {
314        match self {
315            Self::Keyword(k) => KeyOrKeyword::Keyword(k),
316            Self::Key(k) => KeyOrKeyword::Key(k.to_owned()),
317        }
318    }
319
320    /// Returns this value as it is spelled in the context.
321    #[must_use]
322    pub fn as_str(&self) -> &'a str {
323        match self {
324            Self::Keyword(k) => k.into_str(),
325            Self::Key(k) => k.as_str(),
326        }
327    }
328}
329
330impl<'a> From<&'a str> for KeyOrKeywordRef<'a> {
331    fn from(s: &'a str) -> Self {
332        match Keyword::try_from(s) {
333            Ok(k) => Self::Keyword(k),
334            Err(_) => Self::Key(s.into()),
335        }
336    }
337}
338
339impl<'a> From<&'a KeyOrKeyword> for KeyOrKeywordRef<'a> {
340    fn from(k: &'a KeyOrKeyword) -> Self {
341        match k {
342            KeyOrKeyword::Keyword(k) => Self::Keyword(*k),
343            KeyOrKeyword::Key(k) => Self::Key(k.into()),
344        }
345    }
346}
347
348impl<'a> From<KeyRef<'a>> for KeyOrKeywordRef<'a> {
349    fn from(k: KeyRef<'a>) -> Self {
350        Self::Key(k)
351    }
352}
353
354impl<'a> From<&'a Key> for KeyOrKeywordRef<'a> {
355    fn from(k: &'a Key) -> Self {
356        Self::Key(k.into())
357    }
358}
359
360/// Context definition key that may also be the `@type` entry.
361pub enum KeyOrType {
362    /// A term key.
363    Key(Key),
364    /// The `@type` entry.
365    Type,
366}
367
368impl KeyOrType {
369    /// Returns this value as it is spelled in the context.
370    #[must_use]
371    pub fn as_str(&self) -> &str {
372        match self {
373            Self::Key(k) => k.as_str(),
374            Self::Type => "@type",
375        }
376    }
377}
378
379impl fmt::Display for KeyOrType {
380    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
381        self.as_str().fmt(f)
382    }
383}
384
385#[cfg(all(test, feature = "serde"))]
386mod tests {
387    #![allow(clippy::unwrap_used)]
388    use super::*;
389    use jstrict::Parse;
390
391    /// `#[serde(flatten)]` on `Definition::bindings` buffers entries into
392    /// serde's internal `Content` representation and replays keys through
393    /// `visit_str` (transient, not borrowed). The former
394    /// `<&str>::deserialize`-based impl failed on that path.
395    #[test]
396    fn key_deserializes_through_serde_content_buffer() {
397        let (value, _) = jstrict::Value::parse_str(r#"{"@vocab": "http://example.com/", "term": "http://example.com/term"}"#).unwrap();
398        let definition: crate::context::Definition = crate::from_value(value).unwrap();
399        assert!(definition.bindings.get(&Key::from("term")).is_some());
400    }
401
402    /// Owned-string input (`visit_string` → `visit_str`), as produced by
403    /// escaped keys and reader-based formats.
404    #[test]
405    fn key_deserializes_from_owned_string() {
406        let (value, _) = jstrict::Value::parse_str(r#""api""#).unwrap();
407        let key: Key = crate::from_value(value).unwrap();
408        assert_eq!(key.as_str(), "api");
409    }
410}