json_ld_core/
lang_string.rs

1use crate::{object::InvalidExpandedJson, Direction, LenientLangTag, LenientLangTagBuf};
2
3/// Language string.
4///
5/// A language string is a string tagged with language and reading direction information.
6///
7/// A valid language string is associated to either a language tag or a direction, or both.
8#[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Debug)]
9#[cfg_attr(feature = "serde", derive(serde::Serialize))]
10pub struct LangString {
11	/// Actual content of the string.
12	#[cfg_attr(feature = "serde", serde(rename = "@value"))]
13	data: json_ld_syntax::String,
14
15	#[cfg_attr(
16		feature = "serde",
17		serde(rename = "@language", skip_serializing_if = "Option::is_none")
18	)]
19	language: Option<LenientLangTagBuf>,
20
21	#[cfg_attr(
22		feature = "serde",
23		serde(rename = "@direction", skip_serializing_if = "Option::is_none")
24	)]
25	direction: Option<Direction>,
26}
27
28/// Raised when something tried to build a language string without language tag or direction.
29#[derive(Clone, Copy, Debug)]
30pub struct InvalidLangString;
31
32impl LangString {
33	/// Create a new language string.
34	pub fn new(
35		data: json_ld_syntax::String,
36		language: Option<LenientLangTagBuf>,
37		direction: Option<Direction>,
38	) -> Result<Self, json_ld_syntax::String> {
39		if language.is_some() || direction.is_some() {
40			Ok(Self {
41				data,
42				language,
43				direction,
44			})
45		} else {
46			Err(data)
47		}
48	}
49
50	pub fn into_parts(
51		self,
52	) -> (
53		json_ld_syntax::String,
54		Option<LenientLangTagBuf>,
55		Option<Direction>,
56	) {
57		(self.data, self.language, self.direction)
58	}
59
60	pub fn parts(&self) -> (&str, Option<&LenientLangTagBuf>, Option<&Direction>) {
61		(&self.data, self.language.as_ref(), self.direction.as_ref())
62	}
63
64	/// Reference to the underlying `str`.
65	#[inline(always)]
66	pub fn as_str(&self) -> &str {
67		self.data.as_ref()
68	}
69
70	/// Gets the associated language tag, if any.
71	#[inline(always)]
72	pub fn language(&self) -> Option<&LenientLangTag> {
73		self.language
74			.as_ref()
75			.map(|tag| tag.as_lenient_lang_tag_ref())
76	}
77
78	/// Sets the associated language tag.
79	///
80	/// If `None` is given, the direction must be set,
81	/// otherwise this function will fail with an [`InvalidLangString`] error.
82	pub fn set_language(
83		&mut self,
84		language: Option<LenientLangTagBuf>,
85	) -> Result<(), InvalidLangString> {
86		if self.direction.is_some() || language.is_some() {
87			self.language = language;
88			Ok(())
89		} else {
90			Err(InvalidLangString)
91		}
92	}
93
94	/// Gets the associated direction, if any.
95	#[inline(always)]
96	pub fn direction(&self) -> Option<Direction> {
97		self.direction
98	}
99
100	/// Sets the associated direction.
101	///
102	/// If `None` is given, a language tag must be set,
103	/// otherwise this function will fail with an [`InvalidLangString`] error.
104	pub fn set_direction(&mut self, direction: Option<Direction>) -> Result<(), InvalidLangString> {
105		if direction.is_some() || self.language.is_some() {
106			self.direction = direction;
107			Ok(())
108		} else {
109			Err(InvalidLangString)
110		}
111	}
112
113	/// Set both the language tag and direction.
114	///
115	/// If both `language` and `direction` are `None`,
116	/// this function will fail with an [`InvalidLangString`] error.
117	pub fn set(
118		&mut self,
119		language: Option<LenientLangTagBuf>,
120		direction: Option<Direction>,
121	) -> Result<(), InvalidLangString> {
122		if direction.is_some() || language.is_some() {
123			self.language = language;
124			self.direction = direction;
125			Ok(())
126		} else {
127			Err(InvalidLangString)
128		}
129	}
130
131	/// Returns a reference to this lang string as a [`LangStr`].
132	pub fn as_lang_str(&self) -> LangStr {
133		LangStr {
134			data: &self.data,
135			language: self.language.as_deref(),
136			direction: self.direction,
137		}
138	}
139
140	pub(crate) fn try_from_json(
141		object: json_syntax::Object,
142		value: json_syntax::Value,
143		language: Option<json_syntax::Value>,
144		direction: Option<json_syntax::Value>,
145	) -> Result<Self, InvalidExpandedJson> {
146		let data = match value {
147			json_syntax::Value::String(s) => s,
148			v => {
149				return Err(InvalidExpandedJson::Unexpected(
150					v.kind(),
151					json_syntax::Kind::String,
152				))
153			}
154		};
155
156		let language = match language {
157			Some(json_syntax::Value::String(value)) => {
158				let (tag, _) = LenientLangTagBuf::new(value.to_string());
159				Some(tag)
160			}
161			Some(v) => {
162				return Err(InvalidExpandedJson::Unexpected(
163					v.kind(),
164					json_syntax::Kind::String,
165				))
166			}
167			None => None,
168		};
169
170		let direction = match direction {
171			Some(json_syntax::Value::String(value)) => match Direction::try_from(value.as_str()) {
172				Ok(direction) => Some(direction),
173				Err(_) => return Err(InvalidExpandedJson::InvalidDirection),
174			},
175			Some(v) => {
176				return Err(InvalidExpandedJson::Unexpected(
177					v.kind(),
178					json_syntax::Kind::String,
179				))
180			}
181			None => None,
182		};
183
184		match object.into_iter().next() {
185			None => Ok(Self::new(data, language, direction).unwrap()),
186			Some(_) => Err(InvalidExpandedJson::UnexpectedEntry),
187		}
188	}
189}
190
191#[cfg(feature = "serde")]
192impl<'de> serde::Deserialize<'de> for LangString {
193	fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
194	where
195		D: serde::Deserializer<'de>,
196	{
197		#[derive(serde::Deserialize)]
198		struct Value {
199			#[serde(rename = "@value")]
200			data: json_ld_syntax::String,
201			#[serde(rename = "@language")]
202			language: Option<LenientLangTagBuf>,
203			#[serde(rename = "@direction")]
204			direction: Option<Direction>,
205		}
206
207		let value = Value::deserialize(deserializer)?;
208
209		Self::new(value.data, value.language, value.direction).map_err(serde::de::Error::custom)
210	}
211}
212
213/// Language string reference.
214///
215/// A language string is a string tagged with language and reading direction information.
216///
217/// A valid language string is associated to either a language tag or a direction, or both.
218#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Debug)]
219#[cfg_attr(feature = "serde", derive(serde::Serialize))]
220pub struct LangStr<'a> {
221	/// Actual content of the string.
222	#[cfg_attr(feature = "serde", serde(rename = "@value"))]
223	data: &'a str,
224
225	#[cfg_attr(
226		feature = "serde",
227		serde(rename = "@language", skip_serializing_if = "Option::is_none")
228	)]
229	language: Option<&'a LenientLangTag>,
230
231	#[cfg_attr(
232		feature = "serde",
233		serde(rename = "@direction", skip_serializing_if = "Option::is_none")
234	)]
235	direction: Option<Direction>,
236}
237
238impl<'a> LangStr<'a> {
239	/// Create a new language string reference.
240	pub fn new(
241		data: &'a str,
242		language: Option<&'a LenientLangTag>,
243		direction: Option<Direction>,
244	) -> Result<Self, InvalidLangString> {
245		if language.is_some() || direction.is_some() {
246			Ok(Self {
247				data,
248				language,
249				direction,
250			})
251		} else {
252			Err(InvalidLangString)
253		}
254	}
255
256	pub fn into_parts(self) -> (&'a str, Option<&'a LenientLangTag>, Option<Direction>) {
257		(self.data, self.language, self.direction)
258	}
259
260	/// Reference to the underlying `str`.
261	#[inline(always)]
262	pub fn as_str(&self) -> &'a str {
263		self.data
264	}
265
266	/// Gets the associated language tag, if any.
267	#[inline(always)]
268	pub fn language(&self) -> Option<&'a LenientLangTag> {
269		self.language
270	}
271
272	/// Gets the associated direction, if any.
273	#[inline(always)]
274	pub fn direction(&self) -> Option<Direction> {
275		self.direction
276	}
277}