use crate::rvm::program::RuleType;
use crate::value::Value;
use alloc::string::String;
use alloc::vec::Vec;
use core::fmt;
use serde::ser::{SerializeStruct, Serializer};
use serde::Serialize;
const fn is_undefined(value: &Value) -> bool {
matches!(*value, Value::Undefined)
}
const fn is_absent(value: &Option<Value>) -> bool {
matches!(*value, None | Some(Value::Undefined))
}
const fn is_reported(value: Option<&ReportedValue>) -> bool {
match value {
Some(reported) => match *reported {
ReportedValue::Known(ref known) => !is_undefined(known),
ReportedValue::Redacted => true,
},
None => false,
}
}
fn field_count(written: &[bool]) -> usize {
written.iter().filter(|flag| **flag).count()
}
fn serialize_reported<S: SerializeStruct>(
state: &mut S,
field: &'static str,
flag: &'static str,
value: Option<&ReportedValue>,
) -> Result<(), S::Error> {
let Some(reported) = value else {
state.skip_field(field)?;
return state.skip_field(flag);
};
match *reported {
ReportedValue::Known(ref known) if !is_undefined(known) => {
state.serialize_field(field, known)?;
state.skip_field(flag)
}
ReportedValue::Known(_) => {
state.skip_field(field)?;
state.skip_field(flag)
}
ReportedValue::Redacted => {
state.skip_field(field)?;
state.serialize_field(flag, &true)
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize)]
#[non_exhaustive]
pub struct ExplanationReport {
#[serde(skip_serializing_if = "is_undefined")]
pub query_result: Value,
pub rules: Vec<RuleExplanation>,
}
#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub struct RuleExplanation {
pub name: String,
pub kind: RuleKind,
pub result: ReportedValue,
pub emissions: Vec<EmissionExplanation>,
pub definitions: Vec<DefinitionExplanation>,
}
impl Serialize for RuleExplanation {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
let len = field_count(&[
true,
true,
is_reported(Some(&self.result)),
!self.emissions.is_empty(),
!self.definitions.is_empty(),
]);
let mut state = serializer.serialize_struct("RuleExplanation", len)?;
state.serialize_field("name", &self.name)?;
state.serialize_field("kind", &self.kind)?;
serialize_reported(&mut state, "result", "result_redacted", Some(&self.result))?;
if self.emissions.is_empty() {
state.skip_field("emissions")?;
} else {
state.serialize_field("emissions", &self.emissions)?;
}
if self.definitions.is_empty() {
state.skip_field("definitions")?;
} else {
state.serialize_field("definitions", &self.definitions)?;
}
state.end()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum RuleKind {
Complete,
PartialSet,
PartialObject,
Function,
}
impl From<RuleType> for RuleKind {
fn from(rule_type: RuleType) -> Self {
match rule_type {
RuleType::Complete => Self::Complete,
RuleType::PartialSet => Self::PartialSet,
RuleType::PartialObject => Self::PartialObject,
}
}
}
#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub struct EmissionExplanation {
pub index: usize,
pub definition: usize,
pub key: Option<ReportedValue>,
pub value: ReportedValue,
pub conditions: Vec<ConditionExplanation>,
}
impl Serialize for EmissionExplanation {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
let len = field_count(&[
true,
true,
is_reported(self.key.as_ref()),
is_reported(Some(&self.value)),
true,
]);
let mut state = serializer.serialize_struct("EmissionExplanation", len)?;
state.serialize_field("index", &self.index)?;
state.serialize_field("definition", &self.definition)?;
serialize_reported(&mut state, "key", "key_redacted", self.key.as_ref())?;
serialize_reported(&mut state, "value", "value_redacted", Some(&self.value))?;
state.serialize_field("conditions", &self.conditions)?;
state.end()
}
}
#[derive(Debug, Clone, PartialEq, Serialize)]
#[non_exhaustive]
pub struct DefinitionExplanation {
pub index: usize,
pub body: usize,
pub outcome: Outcome,
#[serde(skip_serializing_if = "Option::is_none")]
pub location: Option<SourceLocation>,
pub conditions: Vec<ConditionExplanation>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum Outcome {
Success,
Failure,
Undefined,
Assumed,
}
#[derive(Debug, Clone, PartialEq, Serialize)]
#[non_exhaustive]
pub struct ConditionExplanation {
pub text: String,
pub outcome: Outcome,
pub kind: ConditionKind,
#[serde(skip_serializing_if = "Option::is_none")]
pub location: Option<SourceLocation>,
#[serde(skip_serializing_if = "Option::is_none")]
pub operator: Option<Operator>,
#[serde(skip_serializing_if = "Option::is_none")]
pub left: Option<Operand>,
#[serde(skip_serializing_if = "Option::is_none")]
pub right: Option<Operand>,
#[serde(skip_serializing_if = "Option::is_none")]
pub witness: Option<LoopWitness>,
#[serde(skip_serializing_if = "Option::is_none")]
pub binding: Option<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum ConditionKind {
Comparison,
Membership,
Iteration,
Truthiness,
Existence,
Negation,
Binding,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
#[non_exhaustive]
pub enum Operator {
#[serde(rename = "==")]
Eq,
#[serde(rename = "!=")]
Ne,
#[serde(rename = "<")]
Lt,
#[serde(rename = "<=")]
Le,
#[serde(rename = ">")]
Gt,
#[serde(rename = ">=")]
Ge,
#[serde(rename = "in")]
In,
}
impl fmt::Display for Operator {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match *self {
Self::Eq => "==",
Self::Ne => "!=",
Self::Lt => "<",
Self::Le => "<=",
Self::Gt => ">",
Self::Ge => ">=",
Self::In => "in",
})
}
}
#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub enum ReportedValue {
Known(Value),
Redacted,
}
#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub struct Operand {
pub value: ReportedValue,
pub path: Option<String>,
}
impl Serialize for Operand {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
let len = field_count(&[self.path.is_some(), is_reported(Some(&self.value))]);
let mut state = serializer.serialize_struct("Operand", len)?;
match self.path {
Some(ref path) => state.serialize_field("path", path)?,
None => state.skip_field("path")?,
}
serialize_reported(&mut state, "value", "redacted", Some(&self.value))?;
state.end()
}
}
#[derive(Debug, Clone, PartialEq, Serialize)]
#[non_exhaustive]
pub struct LoopWitness {
pub iterations: u32,
pub successes: u32,
#[serde(skip_serializing_if = "Option::is_none")]
pub sample_key: Option<Operand>,
#[serde(skip_serializing_if = "Option::is_none")]
pub sample_value: Option<Operand>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
#[non_exhaustive]
pub struct SourceLocation {
pub file: String,
pub line: usize,
pub column: usize,
}
#[derive(Debug, Clone, PartialEq, Serialize)]
#[non_exhaustive]
pub struct PartialEvalResult {
pub kind: RuleKind,
pub status: ResidualStatus,
#[serde(skip_serializing_if = "is_undefined")]
pub result: Value,
pub residual_queries: Vec<ResidualQuery>,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub warnings: Vec<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum ResidualStatus {
Exact,
PossibleConflict,
Unsupported,
}
#[derive(Debug, Clone, PartialEq, Serialize)]
#[non_exhaustive]
pub struct ResidualQuery {
#[serde(skip_serializing_if = "is_absent")]
pub key: Option<Value>,
#[serde(skip_serializing_if = "is_undefined")]
pub value: Value,
pub conditions: Vec<ResidualCondition>,
}
#[derive(Debug, Clone, PartialEq, Serialize)]
#[non_exhaustive]
pub struct ResidualCondition {
pub text: String,
pub kind: ResidualKind,
#[serde(skip_serializing_if = "Option::is_none")]
pub operator: Option<Operator>,
#[serde(skip_serializing_if = "Option::is_none")]
pub input_path: Option<String>,
#[serde(skip_serializing_if = "is_absent")]
pub value: Option<Value>,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub negated: Vec<ResidualCondition>,
#[serde(skip_serializing_if = "Option::is_none")]
pub rule: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub location: Option<SourceLocation>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum ResidualKind {
Exists,
Truthy,
ConditionHolds,
CollectionExists,
NegationHolds,
}