Skip to main content

tuwunel_core/utils/string/
unquoted.rs

1use std::ops::Deref;
2
3use serde::{Deserialize, Deserializer, de};
4
5use super::Unquote;
6use crate::{Result, err};
7
8/// Unquoted string which deserialized from a quoted string. Construction from a
9/// &str is infallible such that the input can already be unquoted. Construction
10/// from serde deserialization is fallible and the input must be quoted.
11#[repr(transparent)]
12pub struct Unquoted(str);
13
14impl<'a> Unquoted {
15	#[inline]
16	#[must_use]
17	pub fn as_str(&'a self) -> &'a str { &self.0 }
18}
19
20impl<'a, 'de: 'a> Deserialize<'de> for &'a Unquoted {
21	fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
22		let s = <&'a str>::deserialize(deserializer)?;
23		s.is_quoted()
24			.then_some(s)
25			.ok_or(err!(SerdeDe("expected quoted string")))
26			.map_err(de::Error::custom)
27			.map(Into::into)
28	}
29}
30
31impl<'a> From<&'a str> for &'a Unquoted {
32	fn from(s: &'a str) -> &'a Unquoted {
33		let s: &'a str = s.unquote_infallible();
34
35		//SAFETY: This is a pattern I lifted from ruma-identifiers for strong-type strs
36		// by wrapping in a tuple-struct.
37		#[expect(clippy::transmute_ptr_to_ptr)]
38		unsafe {
39			std::mem::transmute(s)
40		}
41	}
42}
43
44impl Deref for Unquoted {
45	type Target = str;
46
47	fn deref(&self) -> &Self::Target { &self.0 }
48}
49
50impl<'a> AsRef<str> for &'a Unquoted {
51	fn as_ref(&self) -> &'a str { &self.0 }
52}