use crate::{BaseDirection, CowTerm, Datatype, Term, TermKind};
use alloc::{
borrow::Cow,
string::{String, ToString},
};
use xsd::{DecimalValue, ParseError, PrimitiveValue, Value, primitive::Boolean};
type Language = String;
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum HeapTerm {
Iri(String),
BNode(String),
String(String),
TaggedString(String, Language, Option<BaseDirection>),
TypedValue(Value),
TypedLiteral(String, Datatype),
}
impl HeapTerm {
pub fn iri(value: impl Into<String>) -> Self {
Self::Iri(value.into())
}
pub fn bnode(id: impl Into<String>) -> Self {
Self::BNode(id.into())
}
pub fn string(value: impl Into<String>) -> Self {
Self::String(value.into())
}
pub fn tagged_string(value: impl Into<String>, tag: impl Into<Language>) -> Self {
Self::TaggedString(value.into(), tag.into(), None)
}
pub fn typed_value(value: impl Into<Value>) -> Self {
Self::TypedValue(value.into())
}
pub fn typed_literal(literal: impl Into<String>, datatype: impl Into<Datatype>) -> Self {
Self::TypedLiteral(literal.into(), datatype.into())
}
pub fn kind(&self) -> TermKind {
match self {
Self::Iri(_) => TermKind::Iri,
Self::BNode(_) => TermKind::BNode,
Self::String(_)
| Self::TaggedString(_, _, _)
| Self::TypedValue(_)
| Self::TypedLiteral(_, _) => TermKind::Literal,
}
}
pub fn value_str(&self) -> Cow<'_, str> {
Cow::Borrowed(match self {
Self::Iri(s) => s.as_str(),
Self::BNode(s) => s.as_str(),
Self::String(s) | Self::TaggedString(s, _, _) | Self::TypedLiteral(s, _) => s.as_str(),
Self::TypedValue(Value::Primitive(PrimitiveValue::Boolean(Boolean::FALSE))) => "false",
Self::TypedValue(Value::Primitive(PrimitiveValue::Boolean(Boolean::TRUE))) => "true",
Self::TypedValue(Value::Primitive(
PrimitiveValue::String(s) | PrimitiveValue::AnyUri(s),
)) => s.as_str(),
Self::TypedValue(v) => return Cow::Owned(v.to_string()),
})
}
#[cfg(feature = "serde")]
pub fn to_json(&self) -> Option<serde_json::Value> {
Some(self.clone().into_json())
}
#[cfg(feature = "serde")]
pub fn into_json(self) -> serde_json::Value {
use serde_json::{Number as JsonNumber, Value as JsonValue, json};
match self {
Self::Iri(str) => JsonValue::String(str),
Self::BNode(id) => JsonValue::String(id),
Self::String(val) => json!({ "@value": val }),
Self::TaggedString(val, lang, _) => json!({ "@value": val, "@language": lang }), Self::TypedLiteral(val, r#type) => {
json!({ "@value": val, "@type": r#type.to_string() })
},
Self::TypedValue(Value::Primitive(val)) => match val {
PrimitiveValue::String(val) => json!({ "@value": val }),
PrimitiveValue::Boolean(val) => val.into_json(),
PrimitiveValue::Double(val) => val.into_json(),
val => {
let r#type = val.r#type();
json!({ "@value": val.into_json(), "@type": r#type.curie() })
},
},
Self::TypedValue(Value::Decimal(val)) => match val {
DecimalValue::Integer(val) => json!({ "@value": val, "@type": "xsd:integer" }),
val => {
let r#type = val.r#type();
json!({ "@value": val.into_json(), "@type": r#type.curie() })
},
},
}
}
#[cfg(feature = "blake3")]
pub fn to_b3(&self) -> crate::TermHash {
blake3::hash(self.value_str().as_ref().as_bytes()) }
}
impl Term for HeapTerm {
fn kind(&self) -> TermKind {
self.kind()
}
fn value_str(&self) -> Cow<'_, str> {
self.value_str()
}
}
impl Term for &HeapTerm {
fn kind(&self) -> TermKind {
(*self).kind()
}
fn value_str(&self) -> Cow<'_, str> {
(*self).value_str()
}
}
impl From<&dyn Term> for HeapTerm {
fn from(term: &dyn Term) -> Self {
match term.kind() {
TermKind::Iri => Self::iri(term.value_str()),
TermKind::BNode => Self::bnode(term.value_str()),
TermKind::Literal => Self::string(term.value_str()), }
}
}
impl From<CowTerm<'_>> for HeapTerm {
fn from(input: CowTerm<'_>) -> Self {
Self::from(&input)
}
}
impl From<&CowTerm<'_>> for HeapTerm {
fn from(input: &CowTerm<'_>) -> Self {
match input {
CowTerm::Iri(s) => HeapTerm::Iri(s.to_string()),
CowTerm::BNode(s) => HeapTerm::BNode(s.to_string()),
CowTerm::String(s) => HeapTerm::String(s.to_string()),
CowTerm::TaggedString(s, lang, dir) => {
HeapTerm::TaggedString(s.to_string(), lang.clone(), dir.clone())
},
CowTerm::TypedValue(v) => HeapTerm::TypedValue(v.clone()),
CowTerm::TypedLiteral(s, dt) => HeapTerm::TypedLiteral(s.to_string(), dt.clone()),
}
}
}
impl From<&str> for HeapTerm {
fn from(value: &str) -> Self {
Self::string(value)
}
}
impl From<String> for HeapTerm {
fn from(value: String) -> Self {
Self::String(value)
}
}
impl From<&String> for HeapTerm {
fn from(value: &String) -> Self {
Self::String(value.clone())
}
}
impl From<Boolean> for HeapTerm {
fn from(value: Boolean) -> Self {
Self::TypedValue(value.into())
}
}
impl From<(String, Language)> for HeapTerm {
fn from((literal, language): (String, Language)) -> Self {
Self::TaggedString(literal, language, None)
}
}
impl From<(String, Datatype)> for HeapTerm {
fn from((literal, datatype): (String, Datatype)) -> Self {
let result = match &datatype {
Datatype::Xsd(t) => xsd::parse(&literal, t),
_ => Err(ParseError),
};
match result {
Ok(value) => Self::TypedValue(value),
Err(_) => Self::TypedLiteral(literal, datatype),
}
}
}
impl From<(String, Option<Datatype>, Option<Language>)> for HeapTerm {
fn from((literal, datatype, language): (String, Option<Datatype>, Option<Language>)) -> Self {
match (language, datatype) {
(None, None) => Self::string(literal),
(None, Some(datatype)) => Self::from((literal, datatype)),
(Some(language), None) => Self::tagged_string(literal, language),
(Some(language), Some(_)) => Self::tagged_string(literal, language), }
}
}
#[cfg(feature = "serde")]
impl TryFrom<serde_json::Value> for HeapTerm {
type Error = ();
fn try_from(input: serde_json::Value) -> Result<Self, Self::Error> {
use serde_json::Value;
Ok(match input {
Value::Null => return Err(()), Value::Bool(b) => HeapTerm::TypedValue(b.into()),
Value::Number(n) => HeapTerm::TypedValue(n.as_f64().unwrap().into()), Value::String(s) if s.starts_with("_:") => HeapTerm::BNode(s.clone()),
Value::String(s) => HeapTerm::Iri(s.clone()),
Value::Array(_) => return Err(()), Value::Object(mut input) => {
let value = input.remove("@value").ok_or(())?;
let r#type = input
.remove("@type")
.and_then(|s| s.as_str().map(|s| s.parse::<Datatype>().ok()))
.flatten();
let language = input
.remove("@language")
.and_then(|s| s.as_str().map(|s| s.to_string()));
match (r#type, language) {
(None, None) => HeapTerm::String(value.to_string()),
(None, Some(lang)) => {
HeapTerm::TaggedString(value.to_string(), lang.into(), None)
},
(Some(r#type), None) => HeapTerm::TypedLiteral(value.to_string(), r#type),
(Some(_), Some(_)) => return Err(()), }
},
})
}
}
#[cfg(feature = "serde")]
impl TryFrom<&serde_json::Value> for HeapTerm {
type Error = ();
fn try_from(input: &serde_json::Value) -> Result<Self, Self::Error> {
input.clone().try_into()
}
}