use crate::event::EventKind;
use serde::{Deserialize, Serialize};
use std::fmt;
#[derive(Clone, Debug, PartialEq, Serialize)]
pub struct Denial {
pub gate: &'static str,
pub code: String,
pub message: String,
pub context: Vec<(String, String)>,
}
impl Denial {
pub fn new(gate: &'static str, message: impl Into<String>) -> Self {
Self {
gate,
code: String::new(),
message: message.into(),
context: vec![],
}
}
pub fn with_code(mut self, code: impl Into<String>) -> Self {
self.code = code.into();
self
}
pub fn with_context(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.context.push((key.into(), value.into()));
self
}
}
impl fmt::Display for Denial {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "[{}] {}", self.gate, self.message)
}
}
impl std::error::Error for Denial {}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub struct GateId(String);
#[derive(Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum GateIdError {
Empty,
NonAscii,
}
impl GateId {
pub fn new(value: impl Into<String>) -> Result<Self, GateIdError> {
let value = value.into();
if value.is_empty() {
return Err(GateIdError::Empty);
}
if !value.is_ascii() {
return Err(GateIdError::NonAscii);
}
Ok(Self(value))
}
pub(crate) fn from_name(value: &str) -> Self {
if let Ok(gate_id) = Self::new(value.to_owned()) {
return gate_id;
}
debug_assert!(
false,
"gate names used for denial tracing must be non-empty ASCII: {value:?}"
);
let mut fallback = String::from("invalid:");
for byte in value.as_bytes() {
use std::fmt::Write as _;
let _ = write!(&mut fallback, "{byte:02x}");
}
Self(fallback)
}
#[must_use]
pub fn as_str(&self) -> &str {
&self.0
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum Verdict {
Permit,
Deny {
code: String,
message: String,
context: Vec<(String, String)>,
},
Skipped,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct GateEvaluation {
gate_id: GateId,
verdict: Verdict,
evidence_hash: Option<[u8; 32]>,
}
impl GateEvaluation {
#[must_use]
pub fn new(gate_id: GateId, verdict: Verdict, evidence_hash: Option<[u8; 32]>) -> Self {
Self {
gate_id,
verdict,
evidence_hash,
}
}
#[must_use]
pub fn gate_id(&self) -> &GateId {
&self.gate_id
}
#[must_use]
pub fn verdict(&self) -> &Verdict {
&self.verdict
}
#[must_use]
pub fn evidence_hash(&self) -> Option<[u8; 32]> {
self.evidence_hash
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct DenialPayload {
evaluations: Vec<GateEvaluation>,
pipeline_id: Option<String>,
proposed_kind: EventKind,
proposed_content_hash: Option<[u8; 32]>,
}
impl DenialPayload {
pub(crate) fn new(
evaluations: Vec<GateEvaluation>,
pipeline_id: Option<String>,
proposed_kind: EventKind,
proposed_content_hash: Option<[u8; 32]>,
) -> Self {
Self {
evaluations,
pipeline_id,
proposed_kind,
proposed_content_hash,
}
}
#[must_use]
pub fn evaluations(&self) -> &[GateEvaluation] {
&self.evaluations
}
#[must_use]
pub fn pipeline_id(&self) -> Option<&str> {
self.pipeline_id.as_deref()
}
#[must_use]
pub fn proposed_kind(&self) -> EventKind {
self.proposed_kind
}
#[must_use]
pub fn proposed_content_hash(&self) -> Option<[u8; 32]> {
self.proposed_content_hash
}
}
impl fmt::Display for GateIdError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Empty => write!(f, "gate id cannot be empty"),
Self::NonAscii => write!(f, "gate id must be ASCII"),
}
}
}
impl std::error::Error for GateIdError {}