use crate::context_expr::ContextExpr;
use crate::error::{PolicyError, Result};
use crate::policy::{Action, Policy, PolicyRule, Resource};
use alloc::collections::BTreeMap;
use alloc::string::{String, ToString};
use alloc::vec::Vec;
#[derive(Debug, Default)]
pub struct PolicyRuleBuilder {
peer_id: Option<String>,
action: Option<Action>,
resource: Option<Resource>,
expires_at: Option<u64>,
attributes: BTreeMap<String, String>,
context_expr: Option<ContextExpr>,
}
impl PolicyRuleBuilder {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn for_peer(mut self, peer_id: impl Into<String>) -> Self {
self.peer_id = Some(peer_id.into());
self
}
#[must_use]
pub fn allow(mut self, action: Action) -> Self {
self.action = Some(action);
self
}
#[must_use]
pub fn on(mut self, resource: Resource) -> Self {
self.resource = Some(resource);
self
}
#[must_use]
pub const fn expires_at(mut self, timestamp: u64) -> Self {
self.expires_at = Some(timestamp);
self
}
#[must_use]
pub fn with_attribute(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.attributes.insert(key.into(), value.into());
self
}
pub fn with_context_expr(mut self, expr: impl AsRef<str>) -> Result<Self> {
let parsed = ContextExpr::parse(expr.as_ref())?;
self.context_expr = Some(parsed);
Ok(self)
}
pub fn build(self) -> Result<PolicyRule> {
let peer_id = self
.peer_id
.ok_or_else(|| PolicyError::InvalidRule("peer_id is required".to_string()))?;
let action = self
.action
.ok_or_else(|| PolicyError::InvalidRule("action is required".to_string()))?;
let resource = self
.resource
.ok_or_else(|| PolicyError::InvalidRule("resource is required".to_string()))?;
Ok(PolicyRule {
peer_id,
action,
resource,
expires_at: self.expires_at,
attributes: self.attributes,
context_expr: self.context_expr,
})
}
}
#[derive(Debug)]
pub struct PolicyBuilder {
name: String,
valid_duration_secs: u64,
rules: Vec<PolicyRule>,
metadata: BTreeMap<String, String>,
timestamp: Option<u64>,
}
impl PolicyBuilder {
#[must_use]
pub fn new(name: impl Into<String>) -> Self {
Self {
timestamp: None,
name: name.into(),
rules: Vec::new(),
metadata: BTreeMap::new(),
valid_duration_secs: 30 * 24 * 60 * 60,
}
}
#[must_use]
pub fn with_timestamp(mut self, timestamp: u64) -> Self {
self.timestamp = Some(timestamp);
self
}
#[must_use]
pub const fn valid_for(mut self, duration_secs: u64) -> Self {
self.valid_duration_secs = duration_secs;
self
}
#[must_use]
pub fn add_rule_with<F>(mut self, f: F) -> Self
where
F: FnOnce(PolicyRuleBuilder) -> PolicyRuleBuilder,
{
let builder = f(PolicyRuleBuilder::new());
if let Ok(rule) = builder.build() {
self.rules.push(rule);
}
self
}
#[must_use]
pub fn add_rule(mut self, rule: PolicyRule) -> Self {
self.rules.push(rule);
self
}
#[must_use]
pub fn with_metadata(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.metadata.insert(key.into(), value.into());
self
}
pub fn build(self) -> Result<Policy> {
let now = self.timestamp.unwrap_or(0);
let mut policy = Policy::new(self.name, self.valid_duration_secs, now)?;
for rule in self.rules {
policy = policy.add_rule(rule)?;
}
for (key, value) in self.metadata {
policy = policy.with_metadata(key, value);
}
policy.validate()?;
Ok(policy)
}
}
impl Action {
#[must_use]
pub const fn read() -> Self {
Self::Read
}
#[must_use]
pub const fn write() -> Self {
Self::Write
}
#[must_use]
pub const fn execute() -> Self {
Self::Execute
}
#[must_use]
pub const fn delete() -> Self {
Self::Delete
}
#[must_use]
pub const fn all() -> Self {
Self::All
}
#[must_use]
pub fn custom(name: impl Into<String>) -> Self {
Self::Custom(name.into())
}
}
impl Resource {
#[must_use]
pub fn file(path: impl Into<String>) -> Self {
Self::File(path.into())
}
#[must_use]
pub fn usb(device: impl Into<String>) -> Self {
Self::Usb(device.into())
}
#[must_use]
pub fn tunnel(name: impl Into<String>) -> Self {
Self::Tunnel(name.into())
}
#[must_use]
pub const fn all() -> Self {
Self::All
}
#[must_use]
pub fn custom(resource_type: impl Into<String>, path: impl Into<String>) -> Self {
Self::Custom {
resource_type: resource_type.into(),
path: path.into(),
}
}
}