did_doc 0.3.1

Library for loading/saving DID documents.
Documentation
use crate::{DidError, DidErrorKind};

use std::{
    collections::BTreeMap,
    default::Default,
    fmt,
    str::FromStr
};

use nom::{
    bytes::complete::{is_a, is_not, tag, take_while},
    character::complete::char,
    combinator::{map, map_res, opt},
    multi::separated_list,
    sequence::preceded,
    IResult,
};

use serde::de::{self, Deserialize, Deserializer, Visitor};
use serde::ser::{Serialize, Serializer};

#[derive(Debug)]
pub struct Uri {
    empty: bool,
    pub id: String,
    pub method: String,
    pub params: Option<BTreeMap<String, String>>,
    pub query: Option<BTreeMap<String, String>>,
    pub fragment: Option<String>,
}

impl Uri {
    pub fn new() -> Self {
        Uri::default()
    }

    pub fn is_empty(&self) -> bool {
        self.empty
    }
}

impl PartialEq<&str> for Uri {
    fn eq(&self, rhs: &&str) -> bool {
        let s = self.to_string();
        s == *rhs
    }
}

impl PartialEq<str> for Uri {
    fn eq(&self, rhs: &str) -> bool {
        let s = self.to_string();
        s == rhs
    }
}

impl PartialEq for Uri {
    fn eq(&self, rhs: &Uri) -> bool {
        let ls = self.to_string();
        let rs = rhs.to_string();
        ls == rs
    }
}

impl Default for Uri {
    fn default() -> Self {
        Uri {
            empty: true,
            id: String::default(),
            method: String::default(),
            params: None,
            query: None,
            fragment: None
        }
    }
}

impl FromStr for Uri {
    type Err = DidError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match parse_did_string(s.as_bytes()) {
            Ok((_, d)) => Ok(d),
            Err(_) => Err(DidError::from_kind(DidErrorKind::InvalidUri)),
        }
    }
}

impl Clone for Uri {
    fn clone(&self) -> Self {
        Uri {
            empty: self.empty,
            id: self.id.clone(),
            method: self.method.clone(),
            params: self.params.clone(),
            query: self.query.clone(),
            fragment: self.fragment.clone(),
        }
    }
}

impl std::fmt::Display for Uri {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
        if self.empty {
            return write!(f, "");
        }

        let mut params = String::new();
        if let Some(p) = &self.params {
            params.push(';');
            params.push_str(
                &p.iter()
                    .map(|(k, v)| format!("{}={}", k, v))
                    .collect::<Vec<String>>()
                    .join(";"),
            );
        }
        let mut query = String::new();
        if let Some(q) = &self.query {
            query.push('?');
            query.push_str(
                &q.iter()
                    .map(|(k, v)| format!("{}={}", k, v))
                    .collect::<Vec<String>>()
                    .join("&"),
            );
        }
        let mut fragment = String::new();
        if let Some(f) = &self.fragment {
            fragment = format!("#{}", f);
        }

        write!(
            f,
            "did:{}:{}{}{}{}",
            self.method, self.id, params, query, fragment
        )
    }
}

fn parse_did_string(i: &[u8]) -> IResult<&[u8], Uri> {
    if i.len() == 0 {
        return Ok((i, Uri {
            empty: true,
            id: String::default(),
            method: String::default(),
            params: None,
            query: None,
            fragment: None
        }));
    }

    let (i, _) = tag("did:")(i)?;
    let (i, method) = map(take_while(is_did_method_char), std::str::from_utf8)(i)?;
    let (i, _) = char(':')(i)?;
    let (i, id) = map(take_while(is_did_id_char), std::str::from_utf8)(i)?;
    let (i, params) = opt(did_params)(i)?;
    let (i, query) = opt(did_query)(i)?;
    let (i, fragment) = opt(did_fragment)(i)?;

    Ok((
        i,
        Uri {
            empty: false,
            id: id.unwrap().to_string(),
            method: method.unwrap().to_string(),
            params: params.map(|m| {
                m.into_iter()
                    .map(|(k, v)| (k.to_string(), v.to_string()))
                    .collect()
            }),
            query: query.map(|m| {
                m.into_iter()
                    .map(|(k, v)| (k.to_string(), v.to_string()))
                    .collect()
            }),
            fragment: fragment.map(|s| s.to_string()),
        },
    ))
}
fn is_did_method_char(c: u8) -> bool {
    let c = c as char;
    c.is_ascii_lowercase() || c.is_ascii_digit()
}
fn is_did_id_char(c: u8) -> bool {
    let c = c as char;
    c.is_ascii_alphanumeric() || c == '.' || c == '_' || c == '-'
}

fn did_params(i: &[u8]) -> IResult<&[u8], BTreeMap<&str, &str>> {
    let (i, lst) = preceded(char(';'), separated_list(char(';'), param_item))(i)?;

    Ok((i, lst.into_iter().collect()))
}
fn param_item(i: &[u8]) -> IResult<&[u8], (&str, &str)> {
    let (i, key) = param_token(i)?;
    let (i, _) = char('=')(i)?;
    let (i, val) = param_token(i)?;
    Ok((i, (key, val)))
}
fn param_token(i: &[u8]) -> IResult<&[u8], &str> {
    map_res(
        is_a("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789%.-_:"),
        std::str::from_utf8,
    )(i)
}

fn did_query(i: &[u8]) -> IResult<&[u8], BTreeMap<&str, &str>> {
    let (i, lst) = preceded(char('?'), separated_list(char('&'), query_item))(i)?;

    Ok((i, lst.into_iter().collect()))
}
fn query_item(i: &[u8]) -> IResult<&[u8], (&str, &str)> {
    let (i, key) = query_token(i)?;
    let (i, _) = char('=')(i)?;
    let (i, val) = query_token(i)?;
    Ok((i, (key, val)))
}
fn query_token(i: &[u8]) -> IResult<&[u8], &str> {
    map_res(is_not("&=:#[]"), std::str::from_utf8)(i)
}

fn did_fragment(i: &[u8]) -> IResult<&[u8], &str> {
    preceded(char('#'), map_res(is_not(":#[]"), std::str::from_utf8))(i)
}

impl<'de> Deserialize<'de> for Uri {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        struct UriVisitor;

        impl<'de> Visitor<'de> for UriVisitor {
            type Value = Uri;

            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
                formatter.write_str("DID string")
            }

            fn visit_str<E>(self, value: &str) -> Result<Uri, E>
            where
                E: de::Error,
            {
                match Uri::from_str(value) {
                    Ok(d) => Ok(d),
                    Err(e) => Err(de::Error::custom(e.to_string()))
                }
            }
        }

        deserializer.deserialize_any(UriVisitor)
    }
}

impl Serialize for Uri {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        let s = self.to_string();
        serializer.serialize_str(s.as_str())
    }
}

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

    #[test]
    fn test_did_params() {
        let p = b";a=b;c=d";
        let d = did_params(p).unwrap().1;
        assert_eq!(d.get("a"), Some(&"b"));
        assert_eq!(d.get("c"), Some(&"d"));
        let p = b";a=b";
        let d = did_params(p).unwrap().1;
        assert_eq!(d.get("a"), Some(&"b"));
        assert_eq!(d.get("c"), None);
    }

    #[test]
    fn test_did_fragment() {
        let fragment = b"#first-page";
        let d = did_fragment(fragment).unwrap().1;
        assert_eq!(d, std::str::from_utf8(&fragment[1..]).unwrap());
    }

    #[test]
    fn test_did_query() {
        let q = b"?a=b&c=d";
        let d = did_query(q).unwrap().1;
        assert_eq!(d.get("a"), Some(&"b"));
        assert_eq!(d.get("c"), Some(&"d"));
        let q = b"?%61=%62";
        let d = did_query(q).unwrap().1;
        assert_eq!(d.get("%61"), Some(&"%62"));
    }

}