northstar_runtime/common/
non_nul_string.rs1use serde::{Deserialize, Serialize, Serializer};
2use std::{
3 convert::{TryFrom, TryInto},
4 ffi::CString,
5 fmt::{self, Formatter},
6 ops::Deref,
7 path::Path,
8};
9use thiserror::Error;
10use validator::ValidateLength;
11
12#[derive(Clone, Eq, PartialOrd, Ord, PartialEq, Hash)]
14pub struct NonNulString(String);
15
16impl NonNulString {
17 pub fn as_str(&self) -> &str {
19 self.0.as_str()
20 }
21
22 pub unsafe fn from_str_unchecked(s: &str) -> Self {
27 Self(s.to_string())
28 }
29
30 pub unsafe fn from_string_unchecked(s: String) -> Self {
35 Self(s)
36 }
37}
38
39#[derive(Error, Clone, Eq, PartialEq, Debug, Serialize, Deserialize)]
41#[error("invalid null byte in string")]
42pub struct InvalidNulChar(usize);
43
44impl InvalidNulChar {
45 pub fn pos(&self) -> usize {
47 self.0
48 }
49}
50
51impl fmt::Display for NonNulString {
52 fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
53 write!(f, "{}", self.0)
54 }
55}
56
57impl fmt::Debug for NonNulString {
58 fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
59 write!(f, "\"{}\"", self.0)
60 }
61}
62
63impl AsRef<[u8]> for NonNulString {
64 fn as_ref(&self) -> &[u8] {
65 self.0.as_bytes()
66 }
67}
68
69impl AsRef<str> for NonNulString {
70 fn as_ref(&self) -> &str {
71 &self.0
72 }
73}
74
75impl AsRef<Path> for NonNulString {
76 fn as_ref(&self) -> &Path {
77 Path::new(self.0.as_str())
78 }
79}
80
81impl Deref for NonNulString {
82 type Target = str;
83
84 fn deref(&self) -> &Self::Target {
85 &self.0
86 }
87}
88
89impl From<NonNulString> for CString {
90 fn from(s: NonNulString) -> Self {
91 unsafe { CString::from_vec_unchecked(s.0.into()) }
92 }
93}
94
95impl From<NonNulString> for String {
96 fn from(s: NonNulString) -> String {
97 s.0
98 }
99}
100
101impl TryFrom<String> for NonNulString {
102 type Error = InvalidNulChar;
103
104 fn try_from(value: String) -> Result<Self, Self::Error> {
105 if let Some(pos) = memchr::memchr(b'\0', value.as_bytes()) {
106 Err(InvalidNulChar(pos))
107 } else {
108 Ok(NonNulString(value))
109 }
110 }
111}
112
113impl TryFrom<&str> for NonNulString {
114 type Error = InvalidNulChar;
115
116 fn try_from(value: &str) -> Result<Self, Self::Error> {
117 value.to_string().try_into()
118 }
119}
120
121impl Serialize for NonNulString {
122 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
123 where
124 S: Serializer,
125 {
126 serializer.serialize_str(&self.0)
127 }
128}
129
130impl<'de> Deserialize<'de> for NonNulString {
131 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
132 where
133 D: serde::Deserializer<'de>,
134 {
135 struct Visitor;
136
137 impl serde::de::Visitor<'_> for Visitor {
138 type Value = NonNulString;
139
140 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
141 formatter.write_str("string without nul bytes")
142 }
143
144 fn visit_str<E: serde::de::Error>(self, v: &str) -> Result<Self::Value, E> {
145 v.try_into().map_err(|_| E::custom("invalid string"))
146 }
147 }
148
149 deserializer.deserialize_str(Visitor)
150 }
151}
152
153impl ValidateLength<u64> for &NonNulString {
155 fn length(&self) -> Option<u64> {
156 Some(self.0.len() as u64)
157 }
158}
159
160#[test]
161fn try_from() {
162 assert!(NonNulString::try_from("hello").is_ok());
163 assert!(NonNulString::try_from("helloš¤”").is_ok());
164}
165
166#[test]
167fn try_from_with_nul() {
168 assert!(NonNulString::try_from("hel\0lo").is_err());
169 assert!(NonNulString::try_from("\0hello").is_err());
170 assert!(NonNulString::try_from("hello\0").is_err());
171}
172
173#[test]
174#[allow(clippy::unwrap_used)]
175fn serialize() {
176 assert!(matches!(
177 serde_json::to_string(&NonNulString::try_from("hello").unwrap()),
178 Ok(s) if s == "\"hello\""
179 ));
180}
181
182#[test]
183#[allow(clippy::unwrap_used)]
184fn deserialize() {
185 assert!(matches!(
186 serde_json::from_str::<NonNulString>("\"hello\""),
187 Ok(n) if n == NonNulString::try_from("hello").unwrap()
188 ));
189 assert!(serde_json::from_str::<NonNulString>("\"a\0\"").is_err());
190}
191
192#[test]
193fn deserialize_with_nul() {
194 assert!(serde_json::from_str::<NonNulString>("\"hel\0lo\"").is_err());
195}