Skip to main content

jsonld_syntax/
lang.rs

1pub use langtag::{InvalidLangTag, LangTag, LangTagBuf};
2use std::{borrow::Borrow, fmt, hash::Hash, ops::Deref};
3
4use crate::utils::{case_insensitive_cmp, case_insensitive_eq, case_insensitive_hash};
5
6/// Language tag that may not be well-formed.
7///
8/// JSON-LD accepts any string as a `@language` value, so an ill-formed tag must
9/// be preserved rather than rejected. Comparison, ordering and hashing are
10/// ASCII-case-insensitive, as language tags are case-insensitive.
11// `repr(transparent)` guarantees the layout assumed by the `&str` transmutes
12// in `new` and `as_lenient_lang_tag_ref`.
13#[derive(Debug)]
14#[repr(transparent)]
15#[cfg_attr(feature = "serde", derive(serde::Serialize))]
16#[cfg_attr(feature = "serde", serde(transparent))]
17pub struct LenientLangTag(str);
18
19impl LenientLangTag {
20    /// Borrows the given string as a language tag, along with the error
21    /// produced by validating it, if it is not well-formed.
22    #[must_use]
23    pub fn new(s: &str) -> (&Self, Option<InvalidLangTag<&str>>) {
24        let err = LangTag::new(s).err();
25        // SAFETY: `Self` is `repr(transparent)` over `str`, and this type
26        // accepts any string, well-formed or not.
27        (unsafe { &*(std::ptr::from_ref::<str>(s) as *const Self) }, err)
28    }
29
30    /// Returns this value as a byte slice.
31    #[must_use]
32    pub fn as_bytes(&self) -> &[u8] {
33        self.0.as_bytes()
34    }
35
36    /// Returns this value as a string slice.
37    #[must_use]
38    pub fn as_str(&self) -> &str {
39        &self.0
40    }
41
42    /// Checks whether this tag is a well-formed language tag.
43    #[must_use]
44    pub fn is_well_formed(&self) -> bool {
45        LangTag::new(self.as_str()).is_ok()
46    }
47
48    /// Borrows this tag as a well-formed [`LangTag`], or returns `None` if it
49    /// is not well-formed.
50    #[must_use]
51    pub fn as_well_formed(&self) -> Option<&LangTag> {
52        LangTag::new(self.as_str()).ok()
53    }
54}
55
56impl PartialEq for LenientLangTag {
57    fn eq(&self, other: &Self) -> bool {
58        case_insensitive_eq(self.as_bytes(), other.as_bytes())
59    }
60}
61
62impl Eq for LenientLangTag {}
63
64impl PartialOrd for LenientLangTag {
65    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
66        Some(self.cmp(other))
67    }
68}
69
70impl Ord for LenientLangTag {
71    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
72        case_insensitive_cmp(self.as_bytes(), other.as_bytes())
73    }
74}
75
76impl Hash for LenientLangTag {
77    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
78        case_insensitive_hash(self.as_bytes(), state);
79    }
80}
81
82impl ToOwned for LenientLangTag {
83    type Owned = LenientLangTagBuf;
84
85    fn to_owned(&self) -> Self::Owned {
86        LenientLangTagBuf(self.0.to_owned())
87    }
88}
89
90// No `Borrow<str>` impl: `LenientLangTag`'s `Eq`/`Ord`/`Hash` are
91// case-insensitive while `str`'s are not, and the `Borrow` contract requires
92// them to agree. Use `AsRef<str>`/`as_str` to reach the raw string.
93impl AsRef<str> for LenientLangTag {
94    fn as_ref(&self) -> &str {
95        self.as_str()
96    }
97}
98
99impl fmt::Display for LenientLangTag {
100    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
101        self.0.fmt(f)
102    }
103}
104
105/// Owned language tag that may not be well-formed.
106#[derive(Debug, Clone)]
107pub struct LenientLangTagBuf(String);
108
109impl LenientLangTagBuf {
110    /// Takes ownership of the given string as a language tag, along with the
111    /// error produced by validating it, if it is not well-formed.
112    #[must_use]
113    pub fn new(s: String) -> (Self, Option<InvalidLangTag<String>>) {
114        let err = LangTag::new(s.as_str()).err().map(|InvalidLangTag(s)| InvalidLangTag(s.to_owned()));
115        (Self(s), err)
116    }
117
118    /// Borrows this value as a [`LenientLangTag`].
119    #[must_use]
120    pub fn as_lenient_lang_tag_ref(&self) -> &LenientLangTag {
121        // SAFETY: `LenientLangTag` is `repr(transparent)` over `str`.
122        unsafe { &*(std::ptr::from_ref::<str>(self.0.as_str()) as *const LenientLangTag) }
123    }
124
125    /// Unwraps the underlying string.
126    #[must_use]
127    pub fn into_string(self) -> String {
128        self.0
129    }
130
131    /// Converts this tag into a well-formed [`LangTagBuf`], failing if it is
132    /// not well-formed.
133    ///
134    /// # Errors
135    ///
136    /// Returns the string unchanged when it is not a well-formed language tag.
137    pub fn into_well_formed(self) -> Result<LangTagBuf, InvalidLangTag<String>> {
138        LangTagBuf::new(self.0)
139    }
140}
141
142impl Deref for LenientLangTagBuf {
143    type Target = LenientLangTag;
144
145    fn deref(&self) -> &Self::Target {
146        self.as_lenient_lang_tag_ref()
147    }
148}
149
150impl PartialEq for LenientLangTagBuf {
151    fn eq(&self, other: &Self) -> bool {
152        self.as_lenient_lang_tag_ref().eq(other.as_lenient_lang_tag_ref())
153    }
154}
155
156impl Eq for LenientLangTagBuf {}
157
158impl PartialOrd for LenientLangTagBuf {
159    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
160        Some(self.cmp(other))
161    }
162}
163
164impl Ord for LenientLangTagBuf {
165    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
166        self.as_lenient_lang_tag_ref().cmp(other.as_lenient_lang_tag_ref())
167    }
168}
169
170impl Hash for LenientLangTagBuf {
171    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
172        self.as_lenient_lang_tag_ref().hash(state);
173    }
174}
175
176impl Borrow<LenientLangTag> for LenientLangTagBuf {
177    fn borrow(&self) -> &LenientLangTag {
178        self.as_lenient_lang_tag_ref()
179    }
180}
181
182impl AsRef<LenientLangTag> for LenientLangTagBuf {
183    fn as_ref(&self) -> &LenientLangTag {
184        self.as_lenient_lang_tag_ref()
185    }
186}
187
188impl From<LangTagBuf> for LenientLangTagBuf {
189    fn from(tag: LangTagBuf) -> Self {
190        Self(tag.into_string())
191    }
192}
193
194impl From<String> for LenientLangTagBuf {
195    fn from(tag: String) -> Self {
196        Self(tag)
197    }
198}
199
200impl fmt::Display for LenientLangTagBuf {
201    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
202        self.0.fmt(f)
203    }
204}
205
206#[cfg(feature = "serde")]
207impl serde::Serialize for LenientLangTagBuf {
208    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
209    where
210        S: serde::Serializer,
211    {
212        self.as_str().serialize(serializer)
213    }
214}
215
216#[cfg(feature = "serde")]
217impl<'de> serde::Deserialize<'de> for LenientLangTagBuf {
218    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
219    where
220        D: serde::Deserializer<'de>,
221    {
222        Ok(Self(String::deserialize(deserializer)?))
223    }
224}