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);
}
}