1use crate::{Direction, LenientLangTag, LenientLangTagBuf, object::InvalidExpandedJson};
2
3#[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Debug)]
9#[cfg_attr(feature = "serde", derive(serde::Serialize))]
10pub struct LangString {
11 #[cfg_attr(feature = "serde", serde(rename = "@value"))]
13 data: jsonld_syntax::String,
14
15 #[cfg_attr(feature = "serde", serde(rename = "@language", skip_serializing_if = "Option::is_none"))]
16 language: Option<LenientLangTagBuf>,
17
18 #[cfg_attr(feature = "serde", serde(rename = "@direction", skip_serializing_if = "Option::is_none"))]
19 direction: Option<Direction>,
20}
21
22#[derive(Clone, Debug, thiserror::Error)]
27#[error("language string requires a language tag or direction")]
28pub struct InvalidLangString(pub jsonld_syntax::String);
29
30#[derive(Clone, Copy, Debug, thiserror::Error)]
33#[error("language string requires a language tag or direction")]
34pub struct MissingLangQualifier;
35
36impl LangString {
37 pub fn new(data: jsonld_syntax::String, language: Option<LenientLangTagBuf>, direction: Option<Direction>) -> Result<Self, InvalidLangString> {
43 if language.is_some() || direction.is_some() {
44 Ok(Self { data, language, direction })
45 } else {
46 Err(InvalidLangString(data))
47 }
48 }
49
50 #[inline]
52 #[must_use]
53 pub fn with_language(data: jsonld_syntax::String, language: LenientLangTagBuf) -> Self {
54 Self {
55 data,
56 language: Some(language),
57 direction: None,
58 }
59 }
60
61 #[inline]
63 #[must_use]
64 pub fn with_direction(data: jsonld_syntax::String, direction: Direction) -> Self {
65 Self {
66 data,
67 language: None,
68 direction: Some(direction),
69 }
70 }
71
72 #[inline]
74 #[must_use]
75 pub fn with_language_and_direction(data: jsonld_syntax::String, language: LenientLangTagBuf, direction: Direction) -> Self {
76 Self {
77 data,
78 language: Some(language),
79 direction: Some(direction),
80 }
81 }
82
83 #[must_use]
86 pub fn into_parts(self) -> (jsonld_syntax::String, Option<LenientLangTagBuf>, Option<Direction>) {
87 (self.data, self.language, self.direction)
88 }
89
90 #[must_use]
92 pub fn parts(&self) -> (&str, Option<&LenientLangTagBuf>, Option<&Direction>) {
93 (&self.data, self.language.as_ref(), self.direction.as_ref())
94 }
95
96 #[inline(always)]
98 #[must_use]
99 pub fn as_str(&self) -> &str {
100 self.data.as_ref()
101 }
102
103 #[inline(always)]
105 #[must_use]
106 pub fn language(&self) -> Option<&LenientLangTag> {
107 self.language.as_ref().map(jsonld_syntax::LenientLangTagBuf::as_lenient_lang_tag_ref)
108 }
109
110 pub fn set_language(&mut self, language: Option<LenientLangTagBuf>) -> Result<(), MissingLangQualifier> {
119 if self.direction.is_some() || language.is_some() {
120 self.language = language;
121 Ok(())
122 } else {
123 Err(MissingLangQualifier)
124 }
125 }
126
127 #[inline(always)]
129 #[must_use]
130 pub fn direction(&self) -> Option<Direction> {
131 self.direction
132 }
133
134 pub fn set_direction(&mut self, direction: Option<Direction>) -> Result<(), MissingLangQualifier> {
143 if direction.is_some() || self.language.is_some() {
144 self.direction = direction;
145 Ok(())
146 } else {
147 Err(MissingLangQualifier)
148 }
149 }
150
151 pub fn set(&mut self, language: Option<LenientLangTagBuf>, direction: Option<Direction>) -> Result<(), MissingLangQualifier> {
160 if direction.is_some() || language.is_some() {
161 self.language = language;
162 self.direction = direction;
163 Ok(())
164 } else {
165 Err(MissingLangQualifier)
166 }
167 }
168
169 #[must_use]
171 pub fn as_lang_str(&self) -> LangStr<'_> {
172 LangStr {
173 data: &self.data,
174 language: self.language.as_deref(),
175 direction: self.direction,
176 }
177 }
178
179 pub(crate) fn try_from_json(
180 object: jstrict::Object,
181 value: jstrict::Value,
182 language: Option<jstrict::Value>,
183 direction: Option<jstrict::Value>,
184 ) -> Result<Self, InvalidExpandedJson> {
185 let data = match value {
186 jstrict::Value::String(s) => s,
187 v => return Err(InvalidExpandedJson::Unexpected(v.kind(), jstrict::Kind::String)),
188 };
189
190 let language = match language {
191 Some(jstrict::Value::String(value)) => {
192 let (tag, _) = LenientLangTagBuf::new(value.to_string());
193 Some(tag)
194 }
195 Some(v) => return Err(InvalidExpandedJson::Unexpected(v.kind(), jstrict::Kind::String)),
196 None => None,
197 };
198
199 let direction = match direction {
200 Some(jstrict::Value::String(value)) => match Direction::try_from(value.as_str()) {
201 Ok(direction) => Some(direction),
202 Err(_) => return Err(InvalidExpandedJson::InvalidDirection),
203 },
204 Some(v) => return Err(InvalidExpandedJson::Unexpected(v.kind(), jstrict::Kind::String)),
205 None => None,
206 };
207
208 if object.into_iter().next().is_some() {
209 return Err(InvalidExpandedJson::UnexpectedEntry);
210 }
211
212 match (language, direction) {
213 (Some(lang), Some(dir)) => Ok(Self::with_language_and_direction(data, lang, dir)),
214 (Some(lang), None) => Ok(Self::with_language(data, lang)),
215 (None, Some(dir)) => Ok(Self::with_direction(data, dir)),
216 (None, None) => Err(InvalidExpandedJson::InvalidLangString),
217 }
218 }
219}
220
221#[cfg(feature = "serde")]
222impl<'de> serde::Deserialize<'de> for LangString {
223 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
224 where
225 D: serde::Deserializer<'de>,
226 {
227 #[derive(serde::Deserialize)]
228 struct Value {
229 #[serde(rename = "@value")]
230 data: jsonld_syntax::String,
231 #[serde(rename = "@language")]
232 language: Option<LenientLangTagBuf>,
233 #[serde(rename = "@direction")]
234 direction: Option<Direction>,
235 }
236
237 let value = Value::deserialize(deserializer)?;
238
239 Self::new(value.data, value.language, value.direction).map_err(serde::de::Error::custom)
240 }
241}
242
243#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Debug)]
249#[cfg_attr(feature = "serde", derive(serde::Serialize))]
250pub struct LangStr<'a> {
251 #[cfg_attr(feature = "serde", serde(rename = "@value"))]
253 data: &'a str,
254
255 #[cfg_attr(feature = "serde", serde(rename = "@language", skip_serializing_if = "Option::is_none"))]
256 language: Option<&'a LenientLangTag>,
257
258 #[cfg_attr(feature = "serde", serde(rename = "@direction", skip_serializing_if = "Option::is_none"))]
259 direction: Option<Direction>,
260}
261
262impl<'a> LangStr<'a> {
263 pub fn new(data: &'a str, language: Option<&'a LenientLangTag>, direction: Option<Direction>) -> Result<Self, MissingLangQualifier> {
269 if language.is_some() || direction.is_some() {
270 Ok(Self { data, language, direction })
271 } else {
272 Err(MissingLangQualifier)
273 }
274 }
275
276 #[must_use]
279 pub fn into_parts(self) -> (&'a str, Option<&'a LenientLangTag>, Option<Direction>) {
280 (self.data, self.language, self.direction)
281 }
282
283 #[inline(always)]
285 #[must_use]
286 pub fn as_str(&self) -> &'a str {
287 self.data
288 }
289
290 #[inline(always)]
292 #[must_use]
293 pub fn language(&self) -> Option<&'a LenientLangTag> {
294 self.language
295 }
296
297 #[inline(always)]
299 #[must_use]
300 pub fn direction(&self) -> Option<Direction> {
301 self.direction
302 }
303}