eth-avatars 0.0.2

A library for fetching Ethereum avatars.
Documentation
use std::str::FromStr;
use strum::{Display, EnumString};

use crate::{Locator, LocatorError, Resource};

pub mod gateway;
pub use gateway::IpfsGateway;

#[derive(Debug, Clone, Copy, PartialEq, Eq, EnumString, Display)]
#[strum(ascii_case_insensitive, serialize_all = "snake_case")]
pub enum IpfsSchema {
    Ipfs,
    Ipns,
}

#[derive(Debug, PartialEq, Eq)]
pub struct Ipfs {
    pub schema: IpfsSchema,
    pub cid: String,
    pub path: Option<String>,
}

impl Locator for Ipfs {
    fn of(resource: &Resource) -> Option<&Self> {
        match resource {
            Resource::Ipfs(ipfs) => Some(ipfs),
            _ => None,
        }
    }
}

impl FromStr for Ipfs {
    type Err = LocatorError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let (schema, rest) = s.split_once(':').ok_or(LocatorError::IpfsSchema)?;
        let schema: IpfsSchema = schema.parse().map_err(|_| LocatorError::IpfsSchema)?;
        let rest = rest.trim_start_matches('/');

        let (cid, path) = match rest.split_once('/') {
            Some((cid, path)) => (cid, Some(path)),
            None => (rest, None),
        };

        if cid.is_empty() {
            return Err(LocatorError::IpfsEmptyCid);
        }

        Ok(Self {
            schema,
            cid: cid.to_string(),
            path: path.filter(|path| !path.is_empty()).map(str::to_string),
        })
    }
}

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

    #[tokio::test]
    async fn ipfs_url_no_path() {
        let input: Ipfs = "ipfs://bafkreifnrjhkl7ccr2ifwn2n7ap6dh2way25a6w5x2szegvj5pt4b5nvfu"
            .parse()
            .unwrap();
        assert_eq!(input.schema, IpfsSchema::Ipfs);
        assert_eq!(
            input.cid,
            "bafkreifnrjhkl7ccr2ifwn2n7ap6dh2way25a6w5x2szegvj5pt4b5nvfu"
        );
        assert_eq!(input.path, None);
    }

    #[tokio::test]
    async fn ipfs_url_short_schema() {
        let input: Ipfs = "ipfs:/bafkreifnrjhkl7ccr2ifwn2n7ap6dh2way25a6w5x2szegvj5pt4b5nvfu"
            .parse()
            .unwrap();
        assert_eq!(input.schema, IpfsSchema::Ipfs);
        assert_eq!(
            input.cid,
            "bafkreifnrjhkl7ccr2ifwn2n7ap6dh2way25a6w5x2szegvj5pt4b5nvfu"
        );
        assert_eq!(input.path, None);
    }

    #[tokio::test]
    async fn ipfs_url_ipns() {
        let input: Ipfs = "ipns://bafkreifnrjhkl7ccr2ifwn2n7ap6dh2way25a6w5x2szegvj5pt4b5nvfu"
            .parse()
            .unwrap();
        assert_eq!(input.schema, IpfsSchema::Ipns);
        assert_eq!(
            input.cid,
            "bafkreifnrjhkl7ccr2ifwn2n7ap6dh2way25a6w5x2szegvj5pt4b5nvfu"
        );
        assert_eq!(input.path, None);
    }

    #[tokio::test]
    async fn ipfs_url_path() {
        let input: Ipfs =
            "ipfs://bafkreifnrjhkl7ccr2ifwn2n7ap6dh2way25a6w5x2szegvj5pt4b5nvfu/hello/world.txt"
                .parse()
                .unwrap();
        assert_eq!(input.schema, IpfsSchema::Ipfs);
        assert_eq!(
            input.cid,
            "bafkreifnrjhkl7ccr2ifwn2n7ap6dh2way25a6w5x2szegvj5pt4b5nvfu"
        );
        assert_eq!(input.path.unwrap(), "hello/world.txt");
    }
}