1use crate::{Error, Namespace, Set};
2use std::collections::{BTreeMap, HashMap};
3
4use serde::{Deserialize, Serialize};
5use serde_json::Value;
6
7fn eq_set_is_empty<T: Eq>(s: &Set<T>) -> bool {
8 s.as_ref().is_empty()
9}
10
11#[derive(Clone, Default, Debug, Serialize, Deserialize)]
13pub struct Capability {
14 #[serde(default, skip_serializing_if = "eq_set_is_empty", rename = "def")]
16 pub default_actions: Set<String>,
17 #[serde(default, skip_serializing_if = "BTreeMap::is_empty", rename = "tar")]
19 pub targeted_actions: BTreeMap<String, Set<String>>,
20 #[serde(default, skip_serializing_if = "HashMap::is_empty", rename = "ext")]
27 pub extra_fields: HashMap<String, Value>,
28}
29
30impl Capability {
31 pub fn can(&self, target: &str, action: &str) -> bool {
33 self.can_default(action)
34 || self
35 .targeted_actions
36 .get(target)
37 .map(|actions| actions.as_ref().contains_alike(action))
38 .unwrap_or(false)
39 }
40
41 pub fn can_default(&self, action: &str) -> bool {
43 self.default_actions.as_ref().contains_alike(action)
44 }
45
46 pub(crate) fn encode(&self) -> Result<String, Error> {
47 serde_json::to_vec(self)
48 .map_err(Error::Ser)
49 .map(|bytes| base64::encode_config(bytes, base64::URL_SAFE_NO_PAD))
50 }
51
52 pub(crate) fn decode(encoded: &str) -> Result<Self, Error> {
53 base64::decode_config(encoded, base64::URL_SAFE_NO_PAD)
54 .map_err(Error::Base64Decode)
55 .and_then(|bytes| serde_json::from_slice(&bytes).map_err(Error::De))
56 }
57
58 pub(crate) fn to_statement_lines<'l>(
59 &'l self,
60 namespace: &'l Namespace,
61 ) -> impl Iterator<Item = String> + 'l {
62 let default_actions = std::iter::once(self.default_actions.as_ref().join(", "))
63 .filter(|actions| !actions.is_empty())
64 .map(move |actions| format!("{}: {} for any.", namespace, actions));
65
66 let action_sets: Set<&[String]> =
67 self.targeted_actions.values().map(AsRef::as_ref).collect();
68
69 let targeted_actions = action_sets.into_iter().map(move |action_set| {
70 let targets = self
71 .targeted_actions
72 .iter()
73 .filter(|(_, actions)| actions.as_ref() == action_set)
74 .map(|(target, _)| target.as_ref())
75 .collect::<Vec<&str>>();
76 format!(
77 "{}: {} for {}.",
78 namespace,
79 action_set.join(", "),
80 targets.join(", ")
81 )
82 });
83
84 default_actions.chain(targeted_actions)
85 }
86}
87
88trait Contains<T: ?Sized> {
89 fn contains_alike(&self, other: &T) -> bool;
90}
91
92impl Contains<str> for [String] {
93 fn contains_alike(&self, other: &str) -> bool {
94 self.iter().any(|i| i == other)
95 }
96}