use crate::temporal;
use crate::{Class, Item, TextValue, UrlValue};
use serde::{
Deserialize, Deserializer, Serialize, Serializer,
de::{self, MapAccess, Visitor},
};
use std::fmt;
use std::str::FromStr;
pub type NodeList = Vec<PropertyValue>;
pub type Properties = std::collections::BTreeMap<String, NodeList>;
fn short_circuit_url_deserialization<'de, D>(d: D) -> Result<url::Url, D::Error>
where
D: serde::Deserializer<'de>,
{
let string_form = String::deserialize(d)?;
let url_form = url::Url::parse(&string_form).map_err(serde::de::Error::custom)?;
if url_form.as_str() != string_form {
Err(serde::de::Error::custom(
"This string doesn't represent a valid URL despite looking like one.",
))
} else {
Ok(url_form)
}
}
fn short_circuit_plain_text_deserialization<'de, D>(d: D) -> Result<String, D::Error>
where
D: serde::Deserializer<'de>,
{
let string_form = String::deserialize(d)?;
url::Url::from_str(&string_form)
.map_err(serde::de::Error::custom)
.map(|u| u.as_str().to_string())
.and_then(|u| {
if u == string_form && !u.contains(|c: char| c.is_whitespace()) && !u.contains('\n') {
Err(serde::de::Error::invalid_type(
de::Unexpected::Other("URL"),
&"plain 'ol string",
))
} else {
Ok(string_form.clone())
}
})
.or_else(|r: D::Error| {
if r.to_string().starts_with("invalid type: URL") {
Err(r)
} else {
temporal::Value::from_str(&string_form)
.map_err(serde::de::Error::custom)
.map(|u| u.to_string())
.and_then(|u| {
if u == string_form {
Err(serde::de::Error::invalid_type(
de::Unexpected::Str("temporal data"),
&"plain 'ol string",
))
} else {
Ok(string_form.clone())
}
})
}
})
.or_else(|r: D::Error| {
if r.to_string().starts_with("invalid type: URL")
|| r.to_string().contains("temporal data")
{
Err(r)
} else {
Ok(string_form)
}
})
}
fn short_circuit_text_value_deserialization<'de, D>(d: D) -> Result<TextValue, D::Error>
where
D: serde::Deserializer<'de>,
{
short_circuit_plain_text_deserialization(d).map(TextValue::new)
}
fn short_circuit_url_value_deserialization<'de, D>(d: D) -> Result<UrlValue, D::Error>
where
D: serde::Deserializer<'de>,
{
short_circuit_url_deserialization(d).map(UrlValue::new)
}
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(untagged, rename_all = "kebab-case")]
pub enum PropertyValue {
#[serde(deserialize_with = "short_circuit_text_value_deserialization")]
Plain(TextValue),
#[serde(deserialize_with = "short_circuit_url_value_deserialization")]
Url(UrlValue),
Temporal(temporal::Value),
Fragment(Fragment),
#[serde(with = "referenced_item")]
Item(Item),
Image(Image),
}
impl PropertyValue {
pub fn is_empty(&self) -> bool {
match self {
Self::Temporal(_) | Self::Url(_) | Self::Image(_) => false,
Self::Plain(s) => s.is_empty(),
Self::Fragment(f) => f.is_empty(),
Self::Item(i) => i.is_empty(),
}
}
}
impl From<Fragment> for PropertyValue {
fn from(v: Fragment) -> Self {
Self::Fragment(v)
}
}
impl From<Item> for PropertyValue {
fn from(item: Item) -> Self {
PropertyValue::Item(item)
}
}
impl From<String> for PropertyValue {
fn from(s: String) -> Self {
PropertyValue::Plain(TextValue::new(s))
}
}
impl From<&str> for PropertyValue {
fn from(s: &str) -> Self {
PropertyValue::Plain(TextValue::new(s.to_string()))
}
}
impl From<temporal::Stamp> for PropertyValue {
fn from(t: temporal::Stamp) -> Self {
Self::Temporal(temporal::Value::Timestamp(t))
}
}
impl From<temporal::Duration> for PropertyValue {
fn from(t: temporal::Duration) -> Self {
Self::Temporal(temporal::Value::Duration(t))
}
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct PropertyWithMetadata {
pub value: PropertyValue,
pub metadata: Option<()>,
}
#[derive(
Debug, Clone, PartialEq, Eq, serde::Serialize, Default, serde::Deserialize, PartialOrd, Ord,
)]
#[serde(rename_all = "kebab-case")]
pub struct Fragment {
#[serde(skip_serializing_if = "String::is_empty")]
pub html: String,
#[serde(default, skip_serializing_if = "String::is_empty")]
pub value: String,
#[cfg(feature = "per_element_lang")]
#[serde(default, skip_serializing_if = "Option::is_none")]
pub lang: Option<String>,
#[serde(skip)]
pub links: Vec<String>,
}
impl Fragment {
#[cfg(not(feature = "per_element_lang"))]
pub fn new(html: String, value: String, links: Vec<String>) -> Self {
Self { html, value, links }
}
#[cfg(feature = "per_element_lang")]
pub fn new(html: String, value: String, links: Vec<String>, lang: Option<String>) -> Self {
Self {
html,
value,
lang,
links,
}
}
#[cfg(feature = "per_element_lang")]
pub fn new_without_lang(html: String, value: String, links: Vec<String>) -> Self {
Self {
html,
value,
lang: None,
links,
}
}
pub fn is_empty(&self) -> bool {
self.value.is_empty()
}
pub fn links(&self) -> &[String] {
&self.links
}
}
#[derive(Clone, PartialEq, Eq, serde::Deserialize, serde::Serialize, PartialOrd, Ord)]
#[serde(rename_all = "kebab-case")]
pub struct Image {
pub value: url::Url,
#[serde(default)]
pub alt: Option<String>,
}
impl std::fmt::Debug for Image {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Image")
.field("value", &self.value.to_string())
.field("alt", &self.alt)
.finish()
}
}
mod referenced_item {
use super::*;
use crate::{Items, Properties, ValueKind};
use std::collections::BTreeMap;
type Value = Item;
struct ItemVisitor;
#[derive(serde::Deserialize, Debug)]
#[serde(field_identifier, rename_all = "kebab-case")]
enum ItemDeserializationFields {
Children,
Value,
Id,
Properties,
r#Type,
}
impl<'de> Visitor<'de> for ItemVisitor {
type Value = Value;
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str("expecting null or an map representing an item")
}
fn visit_map<A>(self, mut item_map: A) -> Result<Self::Value, A::Error>
where
A: MapAccess<'de>,
{
let mut children: Items = Default::default();
let mut value: Option<ValueKind> = Default::default();
let mut id: Option<String> = Default::default();
let mut types = Vec::new();
let mut properties = Properties::default();
while let Some(property) = item_map.next_key()? {
match property {
ItemDeserializationFields::Children => {
let new_items = item_map.next_value::<Vec<Item>>()?.into_iter();
if children.is_empty() && new_items.len() > 0 {
children = new_items.collect::<Vec<Item>>().into();
} else {
children.extend(new_items);
}
}
ItemDeserializationFields::Value => {
if value.is_none() {
value = item_map.next_value::<Option<ValueKind>>()?;
}
}
ItemDeserializationFields::Id => {
if id.is_none() {
id = item_map.next_value::<Option<String>>()?;
}
}
ItemDeserializationFields::Type => {
types.extend(item_map.next_value::<Vec<Class>>()?);
}
ItemDeserializationFields::Properties => {
properties.extend(item_map.next_value::<BTreeMap<String, _>>()?);
}
}
}
let item = Item {
r#type: types,
properties,
id,
value,
children,
..Default::default()
};
Ok(item)
}
}
pub fn serialize<S>(item: &Value, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_some(&Some(item))
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<Value, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_struct(
"Item",
&["type", "properties", "id", "value", "children"],
ItemVisitor,
)
}
}