homestar_invocation/task/instruction/
ability.rs

1//! [UCAN Ability] for a given [Resource].
2//!
3//! [Resource]: url::Url
4//! [UCAN Ability]: <https://github.com/ucan-wg/spec/#23-ability>
5
6use crate::{Error, Unit};
7use libipld::{serde::from_ipld, Ipld};
8use serde::{Deserialize, Serialize};
9use std::{borrow::Cow, fmt};
10
11/// A newtype wrapper for `call` fields, which contain a [UCAN Ability].
12///
13/// Abilities describe the verb portion of the capability: an ability that can
14/// be performed on a resource. For instance, the standard HTTP methods such as
15/// GET, PUT, and POST would be possible can values for an http resource.
16///
17/// The precise format is left open-ended, but by convention is namespaced with
18/// a single slash.
19///
20/// # Example
21///
22/// ```
23/// use homestar_invocation::task::instruction::Ability;
24///
25/// Ability::from("msg/send");
26/// Ability::from("crud/update");
27/// ```
28///
29/// Abilities are case-insensitive, and don't respect wrapping whitespace:
30///
31/// ```
32/// use homestar_invocation::task::instruction::Ability;
33///
34/// let ability = Ability::from("eXaMpLe/tEsT");
35/// assert_eq!(ability.to_string(), "example/test".to_string());
36/// ```
37///
38/// [UCAN Ability]: <https://github.com/ucan-wg/spec/#23-ability>
39#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
40#[repr(transparent)]
41pub struct Ability(String);
42
43impl fmt::Display for Ability {
44    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
45        write!(f, "{}", self.0)
46    }
47}
48
49impl<'a> From<&'a str> for Ability {
50    fn from(ability: &'a str) -> Ability {
51        Ability(ability.trim().to_lowercase())
52    }
53}
54
55impl From<String> for Ability {
56    fn from(ability: String) -> Ability {
57        Ability::from(ability.as_str())
58    }
59}
60
61impl From<Ability> for Ipld {
62    fn from(ability: Ability) -> Ipld {
63        ability.0.into()
64    }
65}
66
67impl TryFrom<Ipld> for Ability {
68    type Error = Error<Unit>;
69
70    fn try_from(ipld: Ipld) -> Result<Self, Self::Error> {
71        let ability = from_ipld::<String>(ipld)?;
72        Ok(Ability::from(ability))
73    }
74}
75
76impl<'a> From<Ability> for Cow<'a, Ability> {
77    fn from(ability: Ability) -> Self {
78        Cow::Owned(ability)
79    }
80}
81
82impl<'a> From<&'a Ability> for Cow<'a, Ability> {
83    fn from(ability: &'a Ability) -> Self {
84        Cow::Borrowed(ability)
85    }
86}
87
88#[cfg(test)]
89mod test {
90    use super::*;
91
92    #[test]
93    fn ipld_roundtrip() {
94        let ability = Ability::from("wasm/run");
95        let ipld = Ipld::from(ability.clone());
96
97        assert_eq!(ipld, Ipld::String("wasm/run".to_string()));
98        assert_eq!(ability, ipld.try_into().unwrap())
99    }
100
101    #[test]
102    fn ser_de() {
103        let ability = Ability::from("wasm/run");
104        let ser = serde_json::to_string(&ability).unwrap();
105        let de = serde_json::from_str(&ser).unwrap();
106
107        assert_eq!(ability, de);
108    }
109}