use std::collections::BTreeMap;
use serde::{Deserialize, Serialize};
use url::Url;
use crate::kind;
use crate::value::{HasId, OneOrMany};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Link {
#[serde(skip_serializing_if = "Option::is_none")]
pub id: Option<Url>,
#[serde(rename = "type", default = "Link::default_kind")]
pub kind: OneOrMany<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub href: Option<Url>,
#[serde(skip_serializing_if = "Option::is_none")]
pub rel: Option<OneOrMany<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub media_type: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub name_map: Option<BTreeMap<String, String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub hreflang: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub height: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub width: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub preview: Option<Box<crate::object::ObjectRef>>,
#[serde(flatten)]
pub extra: BTreeMap<String, serde_json::Value>,
}
impl Link {
fn default_kind() -> OneOrMany<String> {
OneOrMany::one(kind::core::LINK.to_owned())
}
#[must_use]
pub fn new(href: Url) -> Self {
Self {
id: None,
kind: Self::default_kind(),
href: Some(href),
rel: None,
media_type: None,
name: None,
name_map: None,
hreflang: None,
height: None,
width: None,
preview: None,
extra: BTreeMap::new(),
}
}
#[must_use]
pub fn mention(href: Url) -> Self {
let mut link = Self::new(href);
link.kind = OneOrMany::one(kind::link::MENTION.to_owned());
link
}
#[must_use]
pub fn hashtag(href: Url, name: impl Into<String>) -> Self {
let mut link = Self::new(href);
link.kind = OneOrMany::one(kind::link::HASHTAG.to_owned());
link.name = Some(name.into());
link
}
#[must_use]
pub fn is_kind(&self, kind: &str) -> bool {
self.kind.iter().any(|k| k == kind)
}
}
impl HasId for Link {
fn id(&self) -> Option<&Url> {
self.id.as_ref()
}
}
#[cfg(test)]
mod tests {
use pretty_assertions::assert_eq;
use serde_json::json;
use super::*;
#[test]
fn link_defaults_type_to_link() {
let link = Link::new(Url::parse("https://example/x").unwrap());
let v = serde_json::to_value(&link).unwrap();
assert_eq!(v["type"], json!("Link"));
}
#[test]
fn mention_sets_mention_type() {
let link = Link::mention(Url::parse("https://example/u/alice").unwrap());
assert!(link.is_kind(kind::link::MENTION));
}
#[test]
fn mastodon_style_mention_roundtrips() {
let raw = json!({
"type": "Mention",
"href": "https://mastodon.social/@alice",
"name": "@alice@mastodon.social"
});
let link: Link = serde_json::from_value(raw.clone()).unwrap();
assert_eq!(link.name.as_deref(), Some("@alice@mastodon.social"));
let back = serde_json::to_value(&link).unwrap();
assert_eq!(back, raw);
}
}