use std::collections::BTreeSet;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum OwnerKind {
Runtime,
Agent,
User,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(deny_unknown_fields, rename_all = "camelCase")]
pub struct OwnerRef {
pub kind: OwnerKind,
pub id: String,
}
impl OwnerRef {
pub fn runtime(id: impl Into<String>) -> Self {
Self {
kind: OwnerKind::Runtime,
id: id.into(),
}
}
pub fn agent(id: impl Into<String>) -> Self {
Self {
kind: OwnerKind::Agent,
id: id.into(),
}
}
pub fn user(id: impl Into<String>) -> Self {
Self {
kind: OwnerKind::User,
id: id.into(),
}
}
pub fn from_principal(principal: &crate::PrincipalRef) -> Self {
match principal.kind {
crate::PrincipalKind::User => Self::user(&principal.id),
crate::PrincipalKind::Agent => Self::agent(&principal.id),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields, rename_all = "camelCase")]
pub struct PatchOwnersRequest {
pub expected_version: u64,
#[serde(default)]
pub add: Vec<OwnerRef>,
#[serde(default)]
pub remove: Vec<OwnerRef>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum OwnerValidationError {
Empty,
Duplicate,
NotSubset,
}
pub fn normalize_owners(owners: Vec<OwnerRef>) -> Result<Vec<OwnerRef>, OwnerValidationError> {
if owners.is_empty() {
return Err(OwnerValidationError::Empty);
}
let mut set = BTreeSet::new();
for owner in owners {
if !set.insert(owner.clone()) {
return Err(OwnerValidationError::Duplicate);
}
}
Ok(set.into_iter().collect())
}
pub fn owners_subset_of(candidate: &[OwnerRef], parent: &[OwnerRef]) -> bool {
let parent_set: BTreeSet<_> = parent.iter().collect();
candidate.iter().all(|owner| parent_set.contains(owner))
}
pub fn owners_intersect(caller: &[OwnerRef], resource: &[OwnerRef]) -> bool {
let resource_set: BTreeSet<_> = resource.iter().collect();
caller.iter().any(|owner| resource_set.contains(owner))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn normalize_rejects_empty() {
assert!(matches!(
normalize_owners(vec![]),
Err(OwnerValidationError::Empty)
));
}
#[test]
fn normalize_rejects_duplicate() {
assert!(matches!(
normalize_owners(vec![
OwnerRef::runtime("rt-1"),
OwnerRef::runtime("rt-1"),
]),
Err(OwnerValidationError::Duplicate)
));
}
#[test]
fn subset_and_intersect() {
let parent = vec![OwnerRef::user("u1"), OwnerRef::agent("agt-1")];
let child = vec![OwnerRef::agent("agt-1")];
assert!(owners_subset_of(&child, &parent));
assert!(!owners_subset_of(&parent, &child));
assert!(owners_intersect(
&[OwnerRef::runtime("rt-1")],
&[OwnerRef::runtime("rt-1"), OwnerRef::user("u1")]
));
assert!(!owners_intersect(
&[OwnerRef::runtime("rt-2")],
&[OwnerRef::runtime("rt-1")]
));
}
}