use crate::{Direction, LenientLangTag, LenientLangTagBuf, object::InvalidExpandedJson};
#[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct LangString {
#[cfg_attr(feature = "serde", serde(rename = "@value"))]
data: jsonld_syntax::String,
#[cfg_attr(feature = "serde", serde(rename = "@language", skip_serializing_if = "Option::is_none"))]
language: Option<LenientLangTagBuf>,
#[cfg_attr(feature = "serde", serde(rename = "@direction", skip_serializing_if = "Option::is_none"))]
direction: Option<Direction>,
}
#[derive(Clone, Debug, thiserror::Error)]
#[error("language string requires a language tag or direction")]
pub struct InvalidLangString(pub jsonld_syntax::String);
#[derive(Clone, Copy, Debug, thiserror::Error)]
#[error("language string requires a language tag or direction")]
pub struct MissingLangQualifier;
impl LangString {
pub fn new(data: jsonld_syntax::String, language: Option<LenientLangTagBuf>, direction: Option<Direction>) -> Result<Self, InvalidLangString> {
if language.is_some() || direction.is_some() {
Ok(Self { data, language, direction })
} else {
Err(InvalidLangString(data))
}
}
#[inline]
#[must_use]
pub fn with_language(data: jsonld_syntax::String, language: LenientLangTagBuf) -> Self {
Self {
data,
language: Some(language),
direction: None,
}
}
#[inline]
#[must_use]
pub fn with_direction(data: jsonld_syntax::String, direction: Direction) -> Self {
Self {
data,
language: None,
direction: Some(direction),
}
}
#[inline]
#[must_use]
pub fn with_language_and_direction(data: jsonld_syntax::String, language: LenientLangTagBuf, direction: Direction) -> Self {
Self {
data,
language: Some(language),
direction: Some(direction),
}
}
#[must_use]
pub fn into_parts(self) -> (jsonld_syntax::String, Option<LenientLangTagBuf>, Option<Direction>) {
(self.data, self.language, self.direction)
}
#[must_use]
pub fn parts(&self) -> (&str, Option<&LenientLangTagBuf>, Option<&Direction>) {
(&self.data, self.language.as_ref(), self.direction.as_ref())
}
#[inline(always)]
#[must_use]
pub fn as_str(&self) -> &str {
self.data.as_ref()
}
#[inline(always)]
#[must_use]
pub fn language(&self) -> Option<&LenientLangTag> {
self.language.as_ref().map(jsonld_syntax::LenientLangTagBuf::as_lenient_lang_tag_ref)
}
pub fn set_language(&mut self, language: Option<LenientLangTagBuf>) -> Result<(), MissingLangQualifier> {
if self.direction.is_some() || language.is_some() {
self.language = language;
Ok(())
} else {
Err(MissingLangQualifier)
}
}
#[inline(always)]
#[must_use]
pub fn direction(&self) -> Option<Direction> {
self.direction
}
pub fn set_direction(&mut self, direction: Option<Direction>) -> Result<(), MissingLangQualifier> {
if direction.is_some() || self.language.is_some() {
self.direction = direction;
Ok(())
} else {
Err(MissingLangQualifier)
}
}
pub fn set(&mut self, language: Option<LenientLangTagBuf>, direction: Option<Direction>) -> Result<(), MissingLangQualifier> {
if direction.is_some() || language.is_some() {
self.language = language;
self.direction = direction;
Ok(())
} else {
Err(MissingLangQualifier)
}
}
#[must_use]
pub fn as_lang_str(&self) -> LangStr<'_> {
LangStr {
data: &self.data,
language: self.language.as_deref(),
direction: self.direction,
}
}
pub(crate) fn try_from_json(
object: jstrict::Object,
value: jstrict::Value,
language: Option<jstrict::Value>,
direction: Option<jstrict::Value>,
) -> Result<Self, InvalidExpandedJson> {
let data = match value {
jstrict::Value::String(s) => s,
v => return Err(InvalidExpandedJson::Unexpected(v.kind(), jstrict::Kind::String)),
};
let language = match language {
Some(jstrict::Value::String(value)) => {
let (tag, _) = LenientLangTagBuf::new(value.to_string());
Some(tag)
}
Some(v) => return Err(InvalidExpandedJson::Unexpected(v.kind(), jstrict::Kind::String)),
None => None,
};
let direction = match direction {
Some(jstrict::Value::String(value)) => match Direction::try_from(value.as_str()) {
Ok(direction) => Some(direction),
Err(_) => return Err(InvalidExpandedJson::InvalidDirection),
},
Some(v) => return Err(InvalidExpandedJson::Unexpected(v.kind(), jstrict::Kind::String)),
None => None,
};
if object.into_iter().next().is_some() {
return Err(InvalidExpandedJson::UnexpectedEntry);
}
match (language, direction) {
(Some(lang), Some(dir)) => Ok(Self::with_language_and_direction(data, lang, dir)),
(Some(lang), None) => Ok(Self::with_language(data, lang)),
(None, Some(dir)) => Ok(Self::with_direction(data, dir)),
(None, None) => Err(InvalidExpandedJson::InvalidLangString),
}
}
}
#[cfg(feature = "serde")]
impl<'de> serde::Deserialize<'de> for LangString {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
#[derive(serde::Deserialize)]
struct Value {
#[serde(rename = "@value")]
data: jsonld_syntax::String,
#[serde(rename = "@language")]
language: Option<LenientLangTagBuf>,
#[serde(rename = "@direction")]
direction: Option<Direction>,
}
let value = Value::deserialize(deserializer)?;
Self::new(value.data, value.language, value.direction).map_err(serde::de::Error::custom)
}
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct LangStr<'a> {
#[cfg_attr(feature = "serde", serde(rename = "@value"))]
data: &'a str,
#[cfg_attr(feature = "serde", serde(rename = "@language", skip_serializing_if = "Option::is_none"))]
language: Option<&'a LenientLangTag>,
#[cfg_attr(feature = "serde", serde(rename = "@direction", skip_serializing_if = "Option::is_none"))]
direction: Option<Direction>,
}
impl<'a> LangStr<'a> {
pub fn new(data: &'a str, language: Option<&'a LenientLangTag>, direction: Option<Direction>) -> Result<Self, MissingLangQualifier> {
if language.is_some() || direction.is_some() {
Ok(Self { data, language, direction })
} else {
Err(MissingLangQualifier)
}
}
#[must_use]
pub fn into_parts(self) -> (&'a str, Option<&'a LenientLangTag>, Option<Direction>) {
(self.data, self.language, self.direction)
}
#[inline(always)]
#[must_use]
pub fn as_str(&self) -> &'a str {
self.data
}
#[inline(always)]
#[must_use]
pub fn language(&self) -> Option<&'a LenientLangTag> {
self.language
}
#[inline(always)]
#[must_use]
pub fn direction(&self) -> Option<Direction> {
self.direction
}
}