use std::collections::HashMap;
use chrono::{DateTime, Utc};
use serde_json::Value;
use crate::approval::ApprovalAttestation;
use crate::types::{AssuranceLevel, CanonicalCapability, CanonicalDid, SignerType};
#[derive(Debug, Clone)]
pub struct EvalContext {
pub now: DateTime<Utc>,
pub signer_type: Option<SignerType>,
pub assurance_level: Option<AssuranceLevel>,
pub issuer: CanonicalDid,
pub subject: CanonicalDid,
pub revoked: bool,
pub expires_at: Option<DateTime<Utc>>,
pub timestamp: Option<DateTime<Utc>>,
pub capabilities: Vec<CanonicalCapability>,
pub role: Option<String>,
pub delegated_by: Option<CanonicalDid>,
pub chain_depth: u32,
pub repo: Option<String>,
pub git_ref: Option<String>,
pub paths: Vec<String>,
pub environment: Option<String>,
pub workload_issuer: Option<CanonicalDid>,
pub workload_claims: HashMap<String, String>,
pub attrs: HashMap<String, String>,
pub approvals: Vec<ApprovalAttestation>,
pub audit_metadata: HashMap<String, Value>,
}
impl EvalContext {
pub fn new(now: DateTime<Utc>, issuer: CanonicalDid, subject: CanonicalDid) -> Self {
Self {
now,
signer_type: None,
assurance_level: None,
issuer,
subject,
revoked: false,
expires_at: None,
timestamp: None,
capabilities: Vec::new(),
role: None,
delegated_by: None,
chain_depth: 0,
repo: None,
git_ref: None,
paths: Vec::new(),
environment: None,
workload_issuer: None,
workload_claims: HashMap::new(),
attrs: HashMap::new(),
approvals: Vec::new(),
audit_metadata: HashMap::new(),
}
}
pub fn try_from_strings(
now: DateTime<Utc>,
issuer: &str,
subject: &str,
) -> Result<Self, crate::types::DidParseError> {
let issuer = CanonicalDid::parse(issuer)?;
let subject = CanonicalDid::parse(subject)?;
Ok(Self::new(now, issuer, subject))
}
pub fn signer_type(mut self, signer_type: SignerType) -> Self {
self.signer_type = Some(signer_type);
self
}
pub fn assurance_level(mut self, level: AssuranceLevel) -> Self {
self.assurance_level = Some(level);
self
}
pub fn revoked(mut self, revoked: bool) -> Self {
self.revoked = revoked;
self
}
pub fn expires_at(mut self, expires_at: DateTime<Utc>) -> Self {
self.expires_at = Some(expires_at);
self
}
pub fn timestamp(mut self, timestamp: DateTime<Utc>) -> Self {
self.timestamp = Some(timestamp);
self
}
pub fn capability(mut self, cap: CanonicalCapability) -> Self {
self.capabilities.push(cap);
self
}
pub fn capabilities(mut self, caps: impl IntoIterator<Item = CanonicalCapability>) -> Self {
self.capabilities.extend(caps);
self
}
pub fn role(mut self, role: impl Into<String>) -> Self {
self.role = Some(role.into());
self
}
pub fn delegated_by(mut self, delegator: CanonicalDid) -> Self {
self.delegated_by = Some(delegator);
self
}
pub fn chain_depth(mut self, depth: u32) -> Self {
self.chain_depth = depth;
self
}
pub fn repo(mut self, repo: impl Into<String>) -> Self {
self.repo = Some(repo.into());
self
}
pub fn git_ref(mut self, git_ref: impl Into<String>) -> Self {
self.git_ref = Some(git_ref.into());
self
}
pub fn paths(mut self, paths: impl IntoIterator<Item = impl Into<String>>) -> Self {
self.paths.extend(paths.into_iter().map(Into::into));
self
}
pub fn environment(mut self, env: impl Into<String>) -> Self {
self.environment = Some(env.into());
self
}
pub fn workload_issuer(mut self, issuer: CanonicalDid) -> Self {
self.workload_issuer = Some(issuer);
self
}
pub fn workload_claim(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.workload_claims.insert(key.into(), value.into());
self
}
pub fn attr(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.attrs.insert(key.into(), value.into());
self
}
pub fn approval(mut self, attestation: ApprovalAttestation) -> Self {
self.approvals.push(attestation);
self
}
pub fn audit_meta(mut self, key: impl Into<String>, value: Value) -> Self {
self.audit_metadata.insert(key.into(), value);
self
}
}
#[cfg(test)]
#[allow(clippy::disallowed_methods)]
mod tests {
use super::*;
fn did(s: &str) -> CanonicalDid {
CanonicalDid::parse(s).unwrap()
}
fn cap(s: &str) -> CanonicalCapability {
CanonicalCapability::parse(s).unwrap()
}
#[test]
fn new_context() {
let now = Utc::now();
let ctx = EvalContext::new(now, did("did:keri:issuer"), did("did:keri:subject"));
assert_eq!(ctx.issuer.as_str(), "did:keri:issuer");
assert_eq!(ctx.subject.as_str(), "did:keri:subject");
assert!(!ctx.revoked);
assert!(ctx.expires_at.is_none());
assert!(ctx.capabilities.is_empty());
}
#[test]
fn builder_chain() {
let now = Utc::now();
let ctx = EvalContext::new(now, did("did:keri:issuer"), did("did:keri:subject"))
.revoked(false)
.capability(cap("sign_commit"))
.role("admin")
.repo("org/repo")
.git_ref("refs/heads/main")
.environment("production")
.chain_depth(1);
assert_eq!(ctx.capabilities.len(), 1);
assert_eq!(ctx.role, Some("admin".to_string()));
assert_eq!(ctx.repo, Some("org/repo".to_string()));
assert_eq!(ctx.git_ref, Some("refs/heads/main".to_string()));
assert_eq!(ctx.environment, Some("production".to_string()));
assert_eq!(ctx.chain_depth, 1);
}
#[test]
fn multiple_capabilities() {
let now = Utc::now();
let ctx = EvalContext::new(now, did("did:keri:issuer"), did("did:keri:subject"))
.capabilities([cap("read"), cap("write")]);
assert_eq!(ctx.capabilities.len(), 2);
}
#[test]
fn paths() {
let now = Utc::now();
let ctx = EvalContext::new(now, did("did:keri:issuer"), did("did:keri:subject"))
.paths(["src/lib.rs", "src/main.rs"]);
assert_eq!(ctx.paths.len(), 2);
assert_eq!(ctx.paths[0], "src/lib.rs");
}
#[test]
fn workload_claims() {
let now = Utc::now();
let ctx = EvalContext::new(now, did("did:keri:issuer"), did("did:keri:subject"))
.workload_claim("repo", "org/repo")
.workload_claim("actor", "user");
assert_eq!(
ctx.workload_claims.get("repo"),
Some(&"org/repo".to_string())
);
assert_eq!(ctx.workload_claims.get("actor"), Some(&"user".to_string()));
}
#[test]
fn attrs() {
let now = Utc::now();
let ctx = EvalContext::new(now, did("did:keri:issuer"), did("did:keri:subject"))
.attr("custom_key", "custom_value");
assert_eq!(
ctx.attrs.get("custom_key"),
Some(&"custom_value".to_string())
);
}
#[test]
fn delegated_by() {
let now = Utc::now();
let ctx = EvalContext::new(now, did("did:keri:issuer"), did("did:keri:subject"))
.delegated_by(did("did:keri:delegator"));
assert_eq!(
ctx.delegated_by.as_ref().map(|d| d.as_str()),
Some("did:keri:delegator")
);
}
}