1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
//! [UCAN Ability] for a given [Resource].
//!
//! [Resource]: url::Url
//! [UCAN Ability]: <https://github.com/ucan-wg/spec/#23-ability>

use crate::{Error, Unit};
use libipld::{serde::from_ipld, Ipld};
use serde::{Deserialize, Serialize};
use std::{borrow::Cow, fmt};

/// A newtype wrapper for `call` fields, which contain a [UCAN Ability].
///
/// Abilities describe the verb portion of the capability: an ability that can
/// be performed on a resource. For instance, the standard HTTP methods such as
/// GET, PUT, and POST would be possible can values for an http resource.
///
/// The precise format is left open-ended, but by convention is namespaced with
/// a single slash.
///
/// # Example
///
/// ```
/// use homestar_invocation::task::instruction::Ability;
///
/// Ability::from("msg/send");
/// Ability::from("crud/update");
/// ```
///
/// Abilities are case-insensitive, and don't respect wrapping whitespace:
///
/// ```
/// use homestar_invocation::task::instruction::Ability;
///
/// let ability = Ability::from("eXaMpLe/tEsT");
/// assert_eq!(ability.to_string(), "example/test".to_string());
/// ```
///
/// [UCAN Ability]: <https://github.com/ucan-wg/spec/#23-ability>
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
#[repr(transparent)]
pub struct Ability(String);

impl fmt::Display for Ability {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.0)
    }
}

impl<'a> From<&'a str> for Ability {
    fn from(ability: &'a str) -> Ability {
        Ability(ability.trim().to_lowercase())
    }
}

impl From<String> for Ability {
    fn from(ability: String) -> Ability {
        Ability::from(ability.as_str())
    }
}

impl From<Ability> for Ipld {
    fn from(ability: Ability) -> Ipld {
        ability.0.into()
    }
}

impl TryFrom<Ipld> for Ability {
    type Error = Error<Unit>;

    fn try_from(ipld: Ipld) -> Result<Self, Self::Error> {
        let ability = from_ipld::<String>(ipld)?;
        Ok(Ability::from(ability))
    }
}

impl<'a> From<Ability> for Cow<'a, Ability> {
    fn from(ability: Ability) -> Self {
        Cow::Owned(ability)
    }
}

impl<'a> From<&'a Ability> for Cow<'a, Ability> {
    fn from(ability: &'a Ability) -> Self {
        Cow::Borrowed(ability)
    }
}

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

    #[test]
    fn ipld_roundtrip() {
        let ability = Ability::from("wasm/run");
        let ipld = Ipld::from(ability.clone());

        assert_eq!(ipld, Ipld::String("wasm/run".to_string()));
        assert_eq!(ability, ipld.try_into().unwrap())
    }

    #[test]
    fn ser_de() {
        let ability = Ability::from("wasm/run");
        let ser = serde_json::to_string(&ability).unwrap();
        let de = serde_json::from_str(&ser).unwrap();

        assert_eq!(ability, de);
    }
}