Skip to main content

rdf_structural_model/
blank_node.rs

1//! Blank-node identity explicitly scoped by the application.
2
3use borrow_or_share::Bos;
4use iri_rfc3987::Iri;
5use serde::{
6    Deserialize,
7    Deserializer,
8};
9
10use crate::ModelError;
11
12/// An abstract blank-node identity, independent of a concrete syntax label.
13///
14/// Scope is an application-assigned IRI identifying a shared blank-node domain.
15/// Use distinct scopes when importing independent sources. This crate never
16/// creates scopes or allocates global identifiers.
17#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
18#[serde(
19    deny_unknown_fields,
20    bound(
21        serialize = "Text: serde::Serialize",
22        deserialize = "Text: serde::Deserialize<'de>, Iri<Text>: serde::Deserialize<'de>"
23    )
24)]
25pub struct BlankNodeId<Text: Bos<str> + AsRef<str> = String> {
26    scope: Iri<Text>,
27    #[serde(deserialize_with = "BlankNodeId::deserialize_label")]
28    label: Text,
29}
30
31impl<Text: Bos<str> + AsRef<str>> BlankNodeId<Text> {
32    /// Create an identity in an explicitly supplied scope.
33    ///
34    /// # Errors
35    /// Returns [`ModelError::EmptyBlankNode`] if the abstract identifier is
36    /// empty.
37    pub fn try_new(scope: Iri<Text>, label: Text) -> Result<Self, ModelError> {
38        Self::validate_label(label.as_ref())?;
39        Ok(Self { scope, label })
40    }
41
42    /// The application-assigned scope IRI.
43    #[must_use]
44    pub const fn scope(&self) -> &Iri<Text> {
45        &self.scope
46    }
47
48    /// The abstract identifier within that scope, without syntax decoration.
49    #[must_use]
50    pub fn label(&self) -> &str {
51        self.label.as_ref()
52    }
53
54    fn validate_label(label: &str) -> Result<(), ModelError> {
55        if label.is_empty() {
56            return Err(ModelError::EmptyBlankNode);
57        }
58        Ok(())
59    }
60
61    fn deserialize_label<'de, D: Deserializer<'de>>(deserializer: D) -> Result<Text, D::Error>
62    where
63        Text: Deserialize<'de>,
64    {
65        let label = Text::deserialize(deserializer)?;
66        Self::validate_label(label.as_ref()).map_err(serde::de::Error::custom)?;
67        Ok(label)
68    }
69}
70
71impl BlankNodeId<String> {
72    /// Borrow scope and label without copying their text.
73    #[must_use]
74    pub fn as_ref(&self) -> BlankNodeId<&str> {
75        BlankNodeId {
76            scope: self.scope.borrow(),
77            label: &self.label,
78        }
79    }
80}