use std::collections::BTreeSet;
use std::fmt;
use std::string::String;
use std::vec::Vec;
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize)]
pub struct Capability(String);
impl Capability {
#[must_use]
pub fn new(name: impl Into<String>) -> Self {
Self(name.into())
}
#[must_use]
pub fn name(&self) -> &str {
&self.0
}
#[must_use]
pub fn matches(&self, pattern: &str) -> bool {
let parts: Vec<&str> = pattern.split(':').collect();
let self_parts: Vec<&str> = self.0.split(':').collect();
for (i, part) in parts.iter().enumerate() {
if *part == "*" {
continue;
}
if self_parts.get(i) != Some(part) {
return false;
}
}
true
}
}
impl fmt::Display for Capability {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl From<String> for Capability {
fn from(s: String) -> Self {
Self(s)
}
}
impl From<&str> for Capability {
fn from(s: &str) -> Self {
Self(s.to_string())
}
}
#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct CapabilitySet {
inner: BTreeSet<Capability>,
}
impl CapabilitySet {
#[must_use]
pub fn empty() -> Self {
Self {
inner: BTreeSet::new(),
}
}
#[must_use]
pub fn new(capabilities: impl IntoIterator<Item = Capability>) -> Self {
Self {
inner: capabilities.into_iter().collect(),
}
}
#[must_use]
pub fn has(&self, capability: &Capability) -> bool {
self.inner.contains(capability)
}
#[must_use]
pub fn has_pattern(&self, pattern: &str) -> bool {
self.inner.iter().any(|c: &Capability| c.matches(pattern))
}
#[must_use]
pub fn has_any(&self, required: &[Capability]) -> bool {
required.iter().any(|c| self.has(c))
}
#[must_use]
pub fn has_all(&self, required: &[Capability]) -> bool {
required.iter().all(|c| self.has(c))
}
#[must_use]
pub fn len(&self) -> usize {
self.inner.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.inner.is_empty()
}
pub fn iter(&self) -> impl Iterator<Item = &Capability> {
self.inner.iter()
}
#[must_use]
pub fn to_vec(&self) -> Vec<Capability> {
self.inner.iter().cloned().collect()
}
}
impl Default for CapabilitySet {
fn default() -> Self {
Self::empty()
}
}
pub mod common {
use super::Capability;
pub fn fs_read(path: &str) -> Capability {
Capability::new(&format!("fs:read:{}", path))
}
pub fn fs_write() -> Capability {
Capability::new("fs:write:*")
}
pub fn network_http() -> Capability {
Capability::new("network:http:*")
}
pub fn network_https() -> Capability {
Capability::new("network:https:*")
}
pub fn process_exec() -> Capability {
Capability::new("process:exec:*")
}
pub fn env_read() -> Capability {
Capability::new("env:read:*")
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_capability_matches() {
let cap = Capability::new("fs:read:/tmp");
assert!(cap.matches("fs:read:/tmp"));
assert!(cap.matches("fs:read:*"));
assert!(!cap.matches("fs:write:*"));
}
#[test]
fn test_capability_set() {
let set = CapabilitySet::new([
Capability::new("fs:read:*"),
Capability::new("network:http:get"),
]);
assert!(set.has(&Capability::new("fs:read:*")));
assert!(set.has(&Capability::new("network:http:get")));
assert!(!set.has(&Capability::new("fs:write:*")));
}
#[test]
fn test_capability_pattern() {
let set = CapabilitySet::new([Capability::new("fs:read:*")]);
assert!(set.has_pattern("fs:read:*"));
assert!(set.has_pattern("fs:read:/tmp"));
assert!(!set.has_pattern("fs:write:*"));
}
}