use borrow_or_share::Bos;
use iri_rfc3987::Iri;
use crate::{
BaseDirection,
LanguageTag,
ModelError,
};
pub const RDF_LANG_STRING: &str = "http://www.w3.org/1999/02/22-rdf-syntax-ns#langString";
pub const RDF_DIR_LANG_STRING: &str = "http://www.w3.org/1999/02/22-rdf-syntax-ns#dirLangString";
pub const XSD_STRING: &str = "http://www.w3.org/2001/XMLSchema#string";
#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
#[serde(
deny_unknown_fields,
bound(deserialize = "Text: serde::Deserialize<'de>")
)]
pub enum LiteralFacets<Text: AsRef<str> = String> {
None,
Language(LanguageTag<Text>),
Directional {
language: LanguageTag<Text>,
direction: BaseDirection,
},
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
#[serde(
deny_unknown_fields,
bound(
serialize = "Text: serde::Serialize",
deserialize = "Text: serde::Deserialize<'de>, Iri<Text>: serde::Deserialize<'de>"
)
)]
pub struct LiteralMetadata<Text: Bos<str> + AsRef<str> = String> {
datatype: Iri<Text>,
facets: LiteralFacets<Text>,
}
impl<Text: Bos<str> + AsRef<str>> LiteralMetadata<Text> {
#[must_use]
pub const fn new(datatype: Iri<Text>, facets: LiteralFacets<Text>) -> Self {
Self { datatype, facets }
}
#[must_use]
pub const fn datatype(&self) -> &Iri<Text> {
&self.datatype
}
#[must_use]
pub const fn facets(&self) -> &LiteralFacets<Text> {
&self.facets
}
fn validate_standard(&self) -> Result<(), ModelError> {
let valid = match &self.facets {
LiteralFacets::None => {
self.datatype.as_str() != RDF_LANG_STRING
&& self.datatype.as_str() != RDF_DIR_LANG_STRING
}
LiteralFacets::Language(_) => self.datatype.as_str() == RDF_LANG_STRING,
LiteralFacets::Directional { .. } => self.datatype.as_str() == RDF_DIR_LANG_STRING,
};
if !valid {
return Err(ModelError::InconsistentLiteralFacets);
}
Ok(())
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
#[serde(
try_from = "LiteralInput<Text>",
bound(
serialize = "Text: serde::Serialize",
deserialize = "Text: serde::Deserialize<'de>, Iri<Text>: serde::Deserialize<'de>"
)
)]
pub struct Literal<Text: Bos<str> + AsRef<str> = String> {
lexical: Text,
metadata: LiteralMetadata<Text>,
}
impl<Text: Bos<str> + AsRef<str>> Literal<Text> {
pub fn try_new(lexical: Text, metadata: LiteralMetadata<Text>) -> Result<Self, ModelError> {
metadata.validate_standard()?;
Ok(Self { lexical, metadata })
}
#[must_use]
pub fn lexical(&self) -> &str {
self.lexical.as_ref()
}
#[must_use]
pub const fn metadata(&self) -> &LiteralMetadata<Text> {
&self.metadata
}
}
impl Literal<String> {
pub fn typed(lexical: impl Into<String>, datatype: Iri<String>) -> Result<Self, ModelError> {
Self::try_new(
lexical.into(),
LiteralMetadata::new(datatype, LiteralFacets::None),
)
}
#[must_use]
pub fn string(lexical: impl Into<String>) -> Self {
Self {
lexical: lexical.into(),
metadata: LiteralMetadata::new(Self::builtin(XSD_STRING), LiteralFacets::None),
}
}
#[must_use]
pub fn language_tagged(lexical: impl Into<String>, language: LanguageTag) -> Self {
Self {
lexical: lexical.into(),
metadata: LiteralMetadata::new(
Self::builtin(RDF_LANG_STRING),
LiteralFacets::Language(language),
),
}
}
#[must_use]
pub fn directional(
lexical: impl Into<String>,
language: LanguageTag,
direction: BaseDirection,
) -> Self {
Self {
lexical: lexical.into(),
metadata: LiteralMetadata::new(
Self::builtin(RDF_DIR_LANG_STRING),
LiteralFacets::Directional {
language,
direction,
},
),
}
}
fn builtin(value: &'static str) -> Iri<String> {
Iri::parse(value)
.expect("built-in datatype IRI is valid")
.into()
}
#[must_use]
pub fn as_ref(&self) -> Literal<&str> {
Literal {
lexical: &self.lexical,
metadata: self.metadata.as_ref(),
}
}
}
impl LiteralMetadata<String> {
#[must_use]
pub fn as_ref(&self) -> LiteralMetadata<&str> {
let facets = match &self.facets {
LiteralFacets::None => LiteralFacets::None,
LiteralFacets::Language(tag) => LiteralFacets::Language(tag.as_ref()),
LiteralFacets::Directional {
language,
direction,
} => LiteralFacets::Directional {
language: language.as_ref(),
direction: *direction,
},
};
LiteralMetadata::new(self.datatype.borrow(), facets)
}
}
#[derive(serde::Deserialize)]
#[serde(
rename = "Literal",
deny_unknown_fields,
bound(deserialize = "Text: serde::Deserialize<'de>, Iri<Text>: serde::Deserialize<'de>")
)]
struct LiteralInput<Text: Bos<str> + AsRef<str>> {
lexical: Text,
metadata: LiteralMetadata<Text>,
}
impl<Text: Bos<str> + AsRef<str>> TryFrom<LiteralInput<Text>> for Literal<Text> {
type Error = ModelError;
fn try_from(input: LiteralInput<Text>) -> Result<Self, Self::Error> {
Self::try_new(input.lexical, input.metadata)
}
}