#[warn(missing_docs, invalid_doc_attributes, unused, deprecated, clippy::perf)]
#[deny(rustdoc::broken_intra_doc_links, dead_code, unsafe_code)]
pub mod algorithms;
pub mod error;
pub mod http;
pub mod standards;
pub mod traits;
mod test;
#[doc(inline)]
pub use error::Error;
use microformats_types::Fragment;
use url::Url;
pub fn parse_content_value<V>(v_opt: V, url: &Url) -> Vec<Fragment>
where
V: TryInto<serde_json::Value>,
{
use serde_json::Value;
if let Ok(value) = v_opt.try_into() {
match value {
Value::String(text) => vec![Fragment {
value: text.clone(),
html: text,
..Default::default()
}],
Value::Object(obj) => {
vec![serde_json::from_value::<Fragment>(Value::Object(obj)).unwrap_or_default()]
}
Value::Array(values) => values
.into_iter()
.flat_map(|v| parse_content_value(v, url))
.collect::<Vec<_>>(),
_ => vec![],
}
} else {
Vec::default()
}
}
#[test]
fn parse_content_value_from_string() {
let u: Url = "http://foo.com".parse().unwrap();
assert_eq!(
parse_content_value(Some(serde_json::json!("plain text")), &u),
vec![Fragment {
html: "plain text".to_string(),
value: "plain text".to_string(),
lang: None
}],
"pulls out plain text"
);
assert_eq!(
parse_content_value(Some(serde_json::json!("<strong>rich text</strong>")), &u),
vec![Fragment {
html: "<strong>rich text</strong>".to_string(),
value: "rich text".to_string(),
lang: None
}],
"pulls out rich text"
);
}
#[test]
fn parse_content_value_from_object() {
let u: Url = "http://foo.com".parse().unwrap();
assert_eq!(
parse_content_value(
Some(serde_json::json!({"html":"<strong>plain text</strong>", "value": "plain text"})),
&u
),
vec![Fragment {
html: "<strong>plain text</strong>".to_string(),
value: "plain text".to_string(),
lang: None
}],
"pulls out object highlighting HTML"
);
}
#[test]
fn parse_content_value_from_array() {
let u: Url = "http://foo.com".parse().unwrap();
assert_eq!(
parse_content_value(
Some(
serde_json::json!([{"html":"<strong>plain text</strong>", "value": "plain text"}])
),
&u
),
vec![Fragment {
html: "<strong>plain text</strong>".to_string(),
value: "plain text".to_string(),
lang: None
}],
"pulls out plain text"
);
}
#[test]
fn parse_content_value_from_unsupported_type() {
let u: Url = "http://foo.com".parse().unwrap();
assert_eq!(
parse_content_value(Some(serde_json::json!(3)), &u),
Vec::default(),
"does not attempt to unfurl incompatible type"
);
}
mod timestamp {
use serde::de::Deserializer;
struct FromIntegerVisitor;
impl<'de> serde::de::Visitor<'de> for FromIntegerVisitor {
type Value = chrono::DateTime<chrono::Utc>;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("a 64-bit integer representing a timestamp")
}
fn visit_i8<E>(self, v: i8) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
i64::try_from(v)
.map_err(serde::de::Error::custom)
.and_then(|i| self.visit_i64(i))
}
fn visit_u8<E>(self, v: u8) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
i64::try_from(v)
.map_err(serde::de::Error::custom)
.and_then(|i| self.visit_i64(i))
}
fn visit_i16<E>(self, v: i16) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
i64::try_from(v)
.map_err(serde::de::Error::custom)
.and_then(|i| self.visit_i64(i))
}
fn visit_u16<E>(self, v: u16) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
i64::try_from(v)
.map_err(serde::de::Error::custom)
.and_then(|i| self.visit_i64(i))
}
fn visit_i32<E>(self, v: i32) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
i64::try_from(v)
.map_err(serde::de::Error::custom)
.and_then(|i| self.visit_i64(i))
}
fn visit_u32<E>(self, v: u32) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
i64::try_from(v)
.map_err(serde::de::Error::custom)
.and_then(|i| self.visit_i64(i))
}
fn visit_i128<E>(self, v: i128) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
i64::try_from(v)
.map_err(serde::de::Error::custom)
.and_then(|i| self.visit_i64(i))
}
fn visit_u128<E>(self, v: u128) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
i64::try_from(v)
.map_err(serde::de::Error::custom)
.and_then(|i| self.visit_i64(i))
}
fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
i64::try_from(v)
.map_err(serde::de::Error::custom)
.and_then(|i| self.visit_i64(i))
}
fn visit_i64<E>(self, value: i64) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
if let Some(ndt) = chrono::NaiveDateTime::from_timestamp_opt(value, 0) {
Ok(chrono::TimeZone::from_utc_datetime(&chrono::Utc, &ndt))
} else {
Err(serde::de::Error::custom(
chrono::RoundingError::TimestampExceedsLimit,
))
}
}
}
pub fn serialize<S>(
dt: &chrono::DateTime<chrono::Utc>,
serializer: S,
) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_i64(dt.timestamp())
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<chrono::DateTime<chrono::Utc>, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_i64(FromIntegerVisitor)
}
}