~kmaasrud/pimalaya-webfinger

f8e70473c1306c2612fca2272902bfee8b5a65a0 — Knut Magnus Aasrud 10 months ago 3aea3cc
feat: add registered relation types
2 files changed, 342 insertions(+), 2 deletions(-)

M src/lib.rs
A src/rel.rs
M src/lib.rs => src/lib.rs +55 -2
@@ 1,8 1,10 @@
mod rel;
mod uri;

use serde::{Deserialize, Serialize};
use std::collections::HashMap;

pub use rel::Rel;
pub use uri::Uri;

#[derive(Debug, Deserialize, PartialEq, Serialize)]


@@ 18,15 20,64 @@ pub struct ResourceDescriptor {

#[derive(Debug, Deserialize, PartialEq, Serialize)]
pub struct Link {
    rel: Uri, // or registered relation
    mime_type: Option<String>,
    href: Option<Uri>,
    rel: RelOrUri,
    #[serde(default)]
    titles: HashMap<String, String>,
    #[serde(default)]
    properties: HashMap<Uri, Option<String>>,
}

impl Link {
    pub fn new(uri: Uri) -> Self {
        Self {
            rel: RelOrUri::Uri(uri),
            mime_type: None,
            href: None,
            titles: HashMap::new(),
            properties: HashMap::new(),
        }
    }

    /// Insert a property to the link.
    pub fn property(mut self, key: Uri, value: Option<String>) -> Self {
        self.properties.insert(key, value);
        self
    }

    /// Returns the link's `rel` value as a string.
    pub fn rel(&self) -> String {
        match &self.rel {
            RelOrUri::Rel(rel) => rel.as_ref().to_string(),
            RelOrUri::Uri(uri) => uri.to_string(),
        }
    }

    /// Returns `true` if the link's `rel` is a URI.
    pub fn rel_is_uri(&self) -> bool {
        match &self.rel {
            RelOrUri::Rel(_) => false,
            RelOrUri::Uri(_) => true,
        }
    }

    /// Returns `true` if the link's `rel` is a registered relation type.
    pub fn rel_is_registered(&self) -> bool {
        match &self.rel {
            RelOrUri::Rel(_) => true,
            RelOrUri::Uri(_) => false,
        }
    }
}

#[derive(Debug, Deserialize, PartialEq, Serialize)]
#[serde(untagged)]
enum RelOrUri {
    Rel(Rel),
    Uri(Uri),
}

#[cfg(test)]
mod tests {
    use super::*;


@@ 53,7 104,9 @@ mod tests {
                aliases: vec![],
                properties: HashMap::new(),
                links: vec![Link {
                    rel: Uri::try_from("http://openid.net/specs/connect/1.0/issuer").unwrap(),
                    rel: RelOrUri::Uri(
                        Uri::try_from("http://openid.net/specs/connect/1.0/issuer").unwrap()
                    ),
                    mime_type: None,
                    href: Some(Uri::try_from("https://openid.example.com").unwrap()),
                    titles: HashMap::new(),

A src/rel.rs => src/rel.rs +287 -0
@@ 0,0 1,287 @@
use serde::{Deserialize, Serialize};

/// A link relation type as defined in [RFC 8288](https://www.rfc-editor.org/rfc/rfc8288.html).
#[derive(Debug, Deserialize, PartialEq, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum Rel {
    About,
    Acl,
    Alternate,
    Amphtml,
    Appendix,
    #[serde(rename = "apple-touch-icon")]
    AppleTouchIcon,
    #[serde(rename = "apple-touch-startup-image")]
    AppleTouchStartupImage,
    Archives,
    Attachment,
    Author,
    #[serde(rename = "blocked-by")]
    BlockedBy,
    Bookmark,
    Canonical,
    Chapter,
    #[serde(rename = "cite-as")]
    CiteAs,
    Collection,
    Contents,
    ConvertedFrom,
    Copyright,
    #[serde(rename = "create-form")]
    CreateForm,
    Current,
    DescribedBy,
    Describes,
    Disclosure,
    #[serde(rename = "dns-prefetch")]
    DnsPrefetch,
    Duplicate,
    Edit,
    #[serde(rename = "edit-form")]
    EditForm,
    #[serde(rename = "edit-media")]
    EditMedia,
    Enclosure,
    External,
    First,
    Glossary,
    Help,
    Home,
    Hosts,
    Hub,
    Icon,
    Index,
    #[serde(rename = "in-reply-to")]
    InReplyTo,
    IntervalAfter,
    IntervalBefore,
    IntervalContains,
    IntervalDisjoint,
    IntervalDuring,
    IntervalEquals,
    IntervalFinishedBy,
    IntervalFinishes,
    IntervalIn,
    IntervalMeets,
    IntervalMetBy,
    IntervalOverlappedBy,
    IntervalOverlaps,
    IntervalStartedBy,
    IntervalStarts,
    Item,
    Last,
    #[serde(rename = "latest-version")]
    LatestVersion,
    License,
    Linkset,
    Lrdd,
    Manifest,
    MaskIcon,
    MediaFeed,
    Memento,
    MicroPub,
    ModulePreload,
    Monitor,
    MonitorGroup,
    Next,
    NextArchive,
    NoFollow,
    NoOpener,
    NoReferrer,
    Opener,
    #[serde(rename = "openid2.local_id")]
    OpenId2LocalId,
    #[serde(rename = "openid2.provider")]
    OpenId2Provider,
    Original,
    P3pv1,
    Payment,
    Pingback,
    PreConnect,
    #[serde(rename = "predecessor-version")]
    PredecessorVersion,
    PreFetch,
    PreLoad,
    PreRender,
    Prev,
    Preview,
    Previous,
    #[serde(rename = "prev-archive")]
    PrevArchive,
    #[serde(rename = "privacy-policy")]
    PrivacyPolicy,
    Profile,
    Publication,
    Related,
    RestConf,
    Replies,
    RuleInput,
    Search,
    Section,
    #[serde(rename = "self")]
    Self_,
    Service,
    #[serde(rename = "service-desc")]
    ServiceDesc,
    #[serde(rename = "service-doc")]
    ServiceDoc,
    #[serde(rename = "service-meta")]
    ServiceMeta,
    #[serde(rename = "sip-trunking-capability")]
    SipTrunkingCapability,
    Sponsored,
    Start,
    Status,
    Stylesheet,
    Subsection,
    #[serde(rename = "successor-version")]
    SuccessorVersion,
    Sunset,
    Tag,
    #[serde(rename = "terms-of-service")]
    TermsOfService,
    TimeGate,
    TimeMap,
    Type,
    Ugc,
    Up,
    #[serde(rename = "version-history")]
    VersionHistory,
    Via,
    WebMention,
    #[serde(rename = "working-copy")]
    WorkingCopy,
    #[serde(rename = "working-copy-of")]
    WorkingCopyOf,
}

impl AsRef<str> for Rel {
    fn as_ref(&self) -> &str {
        match self {
            Rel::About => "about",
            Rel::Acl => "acl",
            Rel::Alternate => "alternate",
            Rel::Amphtml => "amphtml",
            Rel::Appendix => "appendix",
            Rel::AppleTouchIcon => "apple-touch-icon",
            Rel::AppleTouchStartupImage => "apple-touch-startup-image",
            Rel::Archives => "archives",
            Rel::Attachment => "attachment",
            Rel::Author => "author",
            Rel::BlockedBy => "blocked-by",
            Rel::Bookmark => "bookmark",
            Rel::Canonical => "canonical",
            Rel::Chapter => "chapter",
            Rel::CiteAs => "cite-as",
            Rel::Collection => "collection",
            Rel::Contents => "contents",
            Rel::ConvertedFrom => "converted-from",
            Rel::Copyright => "copyright",
            Rel::CreateForm => "create-form",
            Rel::Current => "current",
            Rel::DescribedBy => "described-by",
            Rel::Describes => "describes",
            Rel::Disclosure => "disclosure",
            Rel::DnsPrefetch => "dns-prefetch",
            Rel::Duplicate => "duplicate",
            Rel::Edit => "edit",
            Rel::EditForm => "edit-form",
            Rel::EditMedia => "edit-media",
            Rel::Enclosure => "enclosure",
            Rel::External => "external",
            Rel::First => "first",
            Rel::Glossary => "glossary",
            Rel::Help => "help",
            Rel::Home => "home",
            Rel::Hosts => "hosts",
            Rel::Hub => "hub",
            Rel::Icon => "icon",
            Rel::Index => "index",
            Rel::InReplyTo => "in-reply-to",
            Rel::IntervalAfter => "interval-after",
            Rel::IntervalBefore => "interval-before",
            Rel::IntervalContains => "interval-contains",
            Rel::IntervalDisjoint => "interval-disjoint",
            Rel::IntervalDuring => "interval-during",
            Rel::IntervalEquals => "interval-equals",
            Rel::IntervalFinishedBy => "interval-finished-by",
            Rel::IntervalFinishes => "interval-finishes",
            Rel::IntervalIn => "interval-in",
            Rel::IntervalMeets => "interval-meets",
            Rel::IntervalMetBy => "interval-met-by",
            Rel::IntervalOverlappedBy => "interval-overlapped-by",
            Rel::IntervalOverlaps => "interval-overlaps",
            Rel::IntervalStartedBy => "interval-started-by",
            Rel::IntervalStarts => "interval-starts",
            Rel::Item => "item",
            Rel::Last => "last",
            Rel::LatestVersion => "latest-version",
            Rel::License => "license",
            Rel::Linkset => "linkset",
            Rel::Lrdd => "lrdd",
            Rel::Manifest => "manifest",
            Rel::MaskIcon => "mask-icon",
            Rel::MediaFeed => "media-feed",
            Rel::Memento => "memento",
            Rel::MicroPub => "micropub",
            Rel::ModulePreload => "module-preload",
            Rel::Monitor => "monitor",
            Rel::MonitorGroup => "monitor-group",
            Rel::Next => "next",
            Rel::NextArchive => "next-archive",
            Rel::NoFollow => "nofollow",
            Rel::NoOpener => "noopener",
            Rel::NoReferrer => "noreferrer",
            Rel::Opener => "opener",
            Rel::OpenId2LocalId => "openid2.local_id",
            Rel::OpenId2Provider => "openid2.provider",
            Rel::Original => "original",
            Rel::P3pv1 => "p3pv1",
            Rel::Payment => "payment",
            Rel::Pingback => "pingback",
            Rel::PreConnect => "preconnect",
            Rel::PredecessorVersion => "predecessor-version",
            Rel::PreFetch => "prefetch",
            Rel::PreLoad => "preload",
            Rel::PreRender => "prerender",
            Rel::Prev => "prev",
            Rel::Preview => "preview",
            Rel::Previous => "previous",
            Rel::PrevArchive => "prev-archive",
            Rel::PrivacyPolicy => "privacy-policy",
            Rel::Profile => "profile",
            Rel::Publication => "publication",
            Rel::Related => "related",
            Rel::RestConf => "restconf",
            Rel::Replies => "replies",
            Rel::RuleInput => "rule-input",
            Rel::Search => "search",
            Rel::Section => "section",
            Rel::Self_ => "self",
            Rel::Service => "service",
            Rel::ServiceDesc => "service-desc",
            Rel::ServiceDoc => "service-doc",
            Rel::ServiceMeta => "service-meta",
            Rel::SipTrunkingCapability => "sip-trunking-capability",
            Rel::Sponsored => "sponsored",
            Rel::Start => "start",
            Rel::Status => "status",
            Rel::Stylesheet => "stylesheet",
            Rel::Subsection => "subsection",
            Rel::SuccessorVersion => "successor-version",
            Rel::Sunset => "sunset",
            Rel::Tag => "tag",
            Rel::TermsOfService => "terms-of-service",
            Rel::TimeGate => "timegate",
            Rel::TimeMap => "timemap",
            Rel::Type => "type",
            Rel::Ugc => "ugc",
            Rel::Up => "up",
            Rel::VersionHistory => "version-history",
            Rel::Via => "via",
            Rel::WebMention => "webmention",
            Rel::WorkingCopy => "working-copy",
            Rel::WorkingCopyOf => "working-copy-of",
        }
    }
}