use std::collections::BTreeMap;
use serde::{Deserialize, Serialize};
use thiserror::Error;
use crate::{ClauseLabel, FactId, ObligationId, ParamKey, Presence, ReasonCode, TraceValue};
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum DenyShape {
Forbidden,
Hidden,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum Effect<O> {
Permit(O),
Deny,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct Decision<O> {
pub effect: Effect<O>,
pub obligations: Vec<ObligationId>,
pub trace: DecisionTrace<O>,
}
impl<O> Decision<O> {
#[must_use]
pub const fn is_permit(&self) -> bool {
matches!(self.effect, Effect::Permit(_))
}
}
impl<O: Serialize + Clone> Decision<O> {
pub fn to_trace(&self) -> Result<Trace, TraceError> {
let decisive = match &self.trace.decisive {
DecisiveClause::Permit {
granted,
satisfied,
label,
} => TraceClause::Permit {
granted: serde_json::to_value(granted).map_err(TraceError::Outcome)?,
satisfied: satisfied.clone(),
label: label.clone(),
},
DecisiveClause::Deny {
denied,
unsatisfied,
label,
reason,
shape,
} => TraceClause::Deny {
denied: denied
.as_ref()
.map(serde_json::to_value)
.transpose()
.map_err(TraceError::Outcome)?,
unsatisfied: unsatisfied.clone(),
label: label.clone(),
reason: reason.clone(),
shape: *shape,
},
};
Ok(Trace {
consulted: self.trace.consulted.clone(),
decisive,
})
}
pub fn denial_reason(&self) -> Result<Option<DenialReason>, TraceError> {
if !matches!(self.effect, Effect::Deny) {
return Ok(None);
}
let DecisiveClause::Deny {
denied,
unsatisfied,
label,
reason,
shape,
} = &self.trace.decisive
else {
return Ok(None);
};
let code = match (reason, label) {
(Some(reason), _) => reason.clone(),
(None, Some(label)) => ReasonCode::new(label.as_str()).map_err(TraceError::Identity)?,
(None, None) => return Ok(None),
};
let mut params = BTreeMap::new();
for (index, fact) in unsatisfied.iter().enumerate() {
let key = if index == 0 {
ParamKey::new("missing_fact").map_err(TraceError::Identity)?
} else {
ParamKey::new(format!("missing_fact_{index}")).map_err(TraceError::Identity)?
};
params.insert(key, ReasonValue::Fact(fact.clone()));
}
if let Some(outcome) = denied {
params.insert(
ParamKey::new("denied_outcome").map_err(TraceError::Identity)?,
ReasonValue::Outcome(serde_json::to_value(outcome).map_err(TraceError::Outcome)?),
);
}
Ok(Some(DenialReason {
code,
params,
shape: *shape,
}))
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct DecisionTrace<O> {
pub consulted: Vec<(FactId, Presence)>,
pub decisive: DecisiveClause<O>,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum DecisiveClause<O> {
Permit {
granted: O,
satisfied: Vec<FactId>,
label: Option<ClauseLabel>,
},
Deny {
denied: Option<O>,
unsatisfied: Vec<FactId>,
label: Option<ClauseLabel>,
reason: Option<ReasonCode>,
shape: DenyShape,
},
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct Trace {
pub consulted: Vec<(FactId, Presence)>,
pub decisive: TraceClause,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum TraceClause {
Permit {
granted: TraceValue,
satisfied: Vec<FactId>,
label: Option<ClauseLabel>,
},
Deny {
denied: Option<TraceValue>,
unsatisfied: Vec<FactId>,
label: Option<ClauseLabel>,
reason: Option<ReasonCode>,
shape: DenyShape,
},
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct DenialReason {
pub code: ReasonCode,
pub params: BTreeMap<ParamKey, ReasonValue>,
pub shape: DenyShape,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum ReasonValue {
Str(String),
Int(i64),
Fact(FactId),
Outcome(TraceValue),
}
#[derive(Debug, Error)]
pub enum TraceError {
#[error("failed to serialize outcome for trace")]
Outcome(#[source] serde_json::Error),
#[error(transparent)]
Identity(#[from] crate::GatekeepError),
}