1#![allow(clippy::host_endian_bytes)] use rand::random;
3#[cfg(feature = "serde")]
4use serde::{Deserialize, Deserializer, Serialize, Serializer, de};
5use std::io::Write;
6use std::{fmt, str};
7
8#[derive(Eq, PartialEq, Debug, Clone, Hash)]
23pub struct BlankNode(BlankNodeContent);
24
25#[derive(PartialEq, Eq, Debug, Clone, Hash)]
26enum BlankNodeContent {
27 Named(String),
28 Anonymous { id: [u8; 16], str: IdStr },
29}
30
31impl BlankNode {
32 pub fn new(id: impl Into<String>) -> Result<Self, BlankNodeIdParseError> {
39 let id = id.into();
40 validate_blank_node_identifier(&id)?;
41 Ok(Self::new_unchecked(id))
42 }
43
44 #[inline]
51 pub fn new_unchecked(id: impl Into<String>) -> Self {
52 let id = id.into();
53 if let Some(numerical_id) = to_integer_id(&id) {
54 Self::new_from_unique_id(numerical_id)
55 } else {
56 Self(BlankNodeContent::Named(id))
57 }
58 }
59
60 #[inline]
64 pub fn new_from_unique_id(id: u128) -> Self {
65 Self(BlankNodeContent::Anonymous {
66 id: id.to_ne_bytes(),
67 str: IdStr::new(id),
68 })
69 }
70
71 #[inline]
73 pub fn as_str(&self) -> &str {
74 match &self.0 {
75 BlankNodeContent::Named(id) => id,
76 BlankNodeContent::Anonymous { str, .. } => str.as_str(),
77 }
78 }
79
80 #[inline]
82 pub fn into_string(self) -> String {
83 match self.0 {
84 BlankNodeContent::Named(id) => id,
85 BlankNodeContent::Anonymous { str, .. } => str.as_str().to_owned(),
86 }
87 }
88
89 #[inline]
90 pub fn as_ref(&self) -> BlankNodeRef<'_> {
91 BlankNodeRef(match &self.0 {
92 BlankNodeContent::Named(id) => BlankNodeRefContent::Named(id.as_str()),
93 BlankNodeContent::Anonymous { id, str } => BlankNodeRefContent::Anonymous {
94 id: *id,
95 str: str.as_str(),
96 },
97 })
98 }
99}
100
101impl fmt::Display for BlankNode {
102 #[inline]
103 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
104 self.as_ref().fmt(f)
105 }
106}
107
108impl Default for BlankNode {
109 #[inline]
111 fn default() -> Self {
112 loop {
114 let id = random();
115 let str = IdStr::new(id);
116 if matches!(str.as_str().as_bytes().first(), Some(b'a'..=b'f')) {
117 return Self(BlankNodeContent::Anonymous {
118 id: id.to_ne_bytes(),
119 str,
120 });
121 }
122 }
123 }
124}
125
126#[derive(Eq, PartialEq, Debug, Clone, Copy, Hash)]
141pub struct BlankNodeRef<'a>(BlankNodeRefContent<'a>);
142
143#[derive(PartialEq, Eq, Debug, Clone, Copy, Hash)]
144enum BlankNodeRefContent<'a> {
145 Named(&'a str),
146 Anonymous { id: [u8; 16], str: &'a str },
147}
148
149impl<'a> BlankNodeRef<'a> {
150 pub fn new(id: &'a str) -> Result<Self, BlankNodeIdParseError> {
157 validate_blank_node_identifier(id)?;
158 Ok(Self::new_unchecked(id))
159 }
160
161 #[inline]
168 pub fn new_unchecked(id: &'a str) -> Self {
169 if let Some(numerical_id) = to_integer_id(id) {
170 Self(BlankNodeRefContent::Anonymous {
171 id: numerical_id.to_ne_bytes(),
172 str: id,
173 })
174 } else {
175 Self(BlankNodeRefContent::Named(id))
176 }
177 }
178
179 #[inline]
181 pub const fn as_str(self) -> &'a str {
182 match self.0 {
183 BlankNodeRefContent::Named(id) => id,
184 BlankNodeRefContent::Anonymous { str, .. } => str,
185 }
186 }
187
188 #[inline]
201 pub const fn unique_id(&self) -> Option<u128> {
202 match self.0 {
203 BlankNodeRefContent::Named(_) => None,
204 BlankNodeRefContent::Anonymous { id, .. } => Some(u128::from_ne_bytes(id)),
205 }
206 }
207
208 #[inline]
209 pub fn into_owned(self) -> BlankNode {
210 BlankNode(match self.0 {
211 BlankNodeRefContent::Named(id) => BlankNodeContent::Named(id.to_owned()),
212 BlankNodeRefContent::Anonymous { id, .. } => BlankNodeContent::Anonymous {
213 id,
214 str: IdStr::new(u128::from_ne_bytes(id)),
215 },
216 })
217 }
218}
219
220impl fmt::Display for BlankNodeRef<'_> {
221 #[inline]
222 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
223 write!(f, "_:{}", self.as_str())
224 }
225}
226
227impl<'a> From<&'a BlankNode> for BlankNodeRef<'a> {
228 #[inline]
229 fn from(node: &'a BlankNode) -> Self {
230 node.as_ref()
231 }
232}
233
234impl<'a> From<BlankNodeRef<'a>> for BlankNode {
235 #[inline]
236 fn from(node: BlankNodeRef<'a>) -> Self {
237 node.into_owned()
238 }
239}
240
241impl PartialEq<BlankNode> for BlankNodeRef<'_> {
242 #[inline]
243 fn eq(&self, other: &BlankNode) -> bool {
244 *self == other.as_ref()
245 }
246}
247
248impl PartialEq<BlankNodeRef<'_>> for BlankNode {
249 #[inline]
250 fn eq(&self, other: &BlankNodeRef<'_>) -> bool {
251 self.as_ref() == *other
252 }
253}
254
255#[derive(PartialEq, Eq, Debug, Clone, Hash)]
256struct IdStr([u8; 32]);
257
258impl IdStr {
259 #[inline]
260 fn new(id: u128) -> Self {
261 let mut str = [0; 32];
262 write!(&mut str[..], "{id:x}").unwrap();
263 Self(str)
264 }
265
266 #[inline]
267 fn as_str(&self) -> &str {
268 let len = self.0.iter().position(|x| x == &0).unwrap_or(32);
269 str::from_utf8(&self.0[..len]).unwrap()
270 }
271}
272
273fn validate_blank_node_identifier(id: &str) -> Result<(), BlankNodeIdParseError> {
274 let mut chars = id.chars();
275 let front = chars.next().ok_or(BlankNodeIdParseError)?;
276 match front {
277 '0'..='9'
278 | '_'
279 | ':'
280 | 'A'..='Z'
281 | 'a'..='z'
282 | '\u{00C0}'..='\u{00D6}'
283 | '\u{00D8}'..='\u{00F6}'
284 | '\u{00F8}'..='\u{02FF}'
285 | '\u{0370}'..='\u{037D}'
286 | '\u{037F}'..='\u{1FFF}'
287 | '\u{200C}'..='\u{200D}'
288 | '\u{2070}'..='\u{218F}'
289 | '\u{2C00}'..='\u{2FEF}'
290 | '\u{3001}'..='\u{D7FF}'
291 | '\u{F900}'..='\u{FDCF}'
292 | '\u{FDF0}'..='\u{FFFD}'
293 | '\u{10000}'..='\u{EFFFF}' => (),
294 _ => return Err(BlankNodeIdParseError),
295 }
296 for c in chars {
297 match c {
298 '.' | '-'
300 | '0'..='9'
301 | '\u{00B7}'
302 | '\u{0300}'..='\u{036F}'
303 | '\u{203F}'..='\u{2040}'
304 | '_'
305 | ':'
306 | 'A'..='Z'
307 | 'a'..='z'
308 | '\u{00C0}'..='\u{00D6}'
309 | '\u{00D8}'..='\u{00F6}'
310 | '\u{00F8}'..='\u{02FF}'
311 | '\u{0370}'..='\u{037D}'
312 | '\u{037F}'..='\u{1FFF}'
313 | '\u{200C}'..='\u{200D}'
314 | '\u{2070}'..='\u{218F}'
315 | '\u{2C00}'..='\u{2FEF}'
316 | '\u{3001}'..='\u{D7FF}'
317 | '\u{F900}'..='\u{FDCF}'
318 | '\u{FDF0}'..='\u{FFFD}'
319 | '\u{10000}'..='\u{EFFFF}' => (),
320 _ => return Err(BlankNodeIdParseError),
321 }
322 }
323
324 if id.ends_with('.') {
326 Err(BlankNodeIdParseError)
327 } else {
328 Ok(())
329 }
330}
331
332#[inline]
333fn to_integer_id(id: &str) -> Option<u128> {
334 let digits = id.as_bytes();
335 let mut value: u128 = 0;
336 if let None | Some(b'0') = digits.first() {
337 return None; }
339 for digit in digits {
340 value = value.checked_mul(16)?.checked_add(
341 match *digit {
342 b'0'..=b'9' => digit - b'0',
343 b'a'..=b'f' => digit - b'a' + 10,
344 _ => return None,
345 }
346 .into(),
347 )?;
348 }
349 Some(value)
350}
351
352#[derive(Debug, thiserror::Error)]
354#[error("The blank node identifier is invalid")]
355pub struct BlankNodeIdParseError;
356
357#[cfg(feature = "serde")]
358impl Serialize for BlankNode {
359 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
360 self.as_ref().serialize(serializer)
361 }
362}
363
364#[cfg(feature = "serde")]
365impl Serialize for BlankNodeRef<'_> {
366 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
367 #[derive(Serialize)]
368 #[serde(rename = "BlankNode")]
369 struct Value<'a> {
370 value: &'a str,
371 }
372 Value {
373 value: self.as_str(),
374 }
375 .serialize(serializer)
376 }
377}
378
379#[cfg(feature = "serde")]
380impl<'de> Deserialize<'de> for BlankNode {
381 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
382 where
383 D: Deserializer<'de>,
384 {
385 #[derive(Deserialize)]
386 #[serde(rename = "BlankNode")]
387 struct Value {
388 value: String,
389 }
390 Self::new(Value::deserialize(deserializer)?.value).map_err(de::Error::custom)
391 }
392}
393
394#[cfg(test)]
395mod tests {
396 use super::*;
397 #[cfg(not(target_family = "wasm"))]
398 use std::mem::{align_of, size_of};
399
400 #[test]
401 fn as_str_partial() {
402 let b = BlankNode::new_from_unique_id(0x42);
403 assert_eq!(b.as_str(), "42");
404 }
405
406 #[test]
407 fn as_str_full() {
408 let b = BlankNode::new_from_unique_id(0x7777_6666_5555_4444_3333_2222_1111_0000);
409 assert_eq!(b.as_str(), "77776666555544443333222211110000");
410 }
411
412 #[test]
413 fn new_validation() {
414 BlankNode::new("").unwrap_err();
415 BlankNode::new("a").unwrap();
416 BlankNode::new("-").unwrap_err();
417 BlankNode::new("a-").unwrap();
418 BlankNode::new(".").unwrap_err();
419 BlankNode::new("a.").unwrap_err();
420 BlankNode::new("a.a").unwrap();
421 }
422
423 #[test]
424 fn new_numerical() {
425 assert_eq!(
426 BlankNode::new("100a").unwrap(),
427 BlankNode::new_from_unique_id(0x100a),
428 );
429 assert_ne!(
430 BlankNode::new("100A").unwrap(),
431 BlankNode::new_from_unique_id(0x100a)
432 );
433 }
434
435 #[test]
436 fn test_equals() {
437 assert_eq!(
438 BlankNode::new("100a").unwrap(),
439 BlankNodeRef::new("100a").unwrap()
440 );
441 assert_eq!(
442 BlankNode::new("zzz").unwrap(),
443 BlankNodeRef::new("zzz").unwrap()
444 );
445 }
446
447 #[cfg(target_pointer_width = "64")]
448 #[test]
449 fn test_size_and_alignment() {
450 assert_eq!(size_of::<BlankNode>(), 56);
451 assert_eq!(size_of::<BlankNodeRef<'_>>(), 32);
452 assert_eq!(align_of::<BlankNode>(), 8);
453 assert_eq!(align_of::<BlankNodeRef<'_>>(), 8);
454 }
455
456 #[test]
457 #[cfg(feature = "serde")]
458 fn test_serde() {
459 let b = BlankNode::new("123a").unwrap();
460 let json = serde_json::to_string(&b).unwrap();
461 assert_eq!(json, "{\"value\":\"123a\"}");
462 let b2: BlankNode = serde_json::from_str(&json).unwrap();
463 assert_eq!(b2, b);
464 }
465}