use cedar_policy_core::ast::PolicyID;
use cedar_policy_core::parser::Loc;
use miette::Diagnostic;
use thiserror::Error;
use crate::{TypeErrorKind, ValidationWarning};
#[derive(Debug)]
pub struct ValidationResult<'a> {
validation_errors: Vec<ValidationError<'a>>,
validation_warnings: Vec<ValidationWarning<'a>>,
}
impl<'a> ValidationResult<'a> {
pub fn new(
errors: impl IntoIterator<Item = ValidationError<'a>>,
warnings: impl IntoIterator<Item = ValidationWarning<'a>>,
) -> Self {
Self {
validation_errors: errors.into_iter().collect(),
validation_warnings: warnings.into_iter().collect(),
}
}
pub fn validation_passed(&self) -> bool {
self.validation_errors.is_empty()
}
pub fn validation_errors(&self) -> impl Iterator<Item = &ValidationError> {
self.validation_errors.iter()
}
pub fn validation_warnings(&self) -> impl Iterator<Item = &ValidationWarning> {
self.validation_warnings.iter()
}
pub fn into_errors_and_warnings(
self,
) -> (
impl Iterator<Item = ValidationError<'a>>,
impl Iterator<Item = ValidationWarning<'a>>,
) {
(
self.validation_errors.into_iter(),
self.validation_warnings.into_iter(),
)
}
}
#[derive(Debug)]
#[cfg_attr(test, derive(Eq, PartialEq))]
pub struct ValidationError<'a> {
location: SourceLocation<'a>,
error_kind: ValidationErrorKind,
}
impl<'a> ValidationError<'a> {
pub(crate) fn with_policy_id(
id: &'a PolicyID,
source_loc: Option<Loc>,
error_kind: ValidationErrorKind,
) -> Self {
Self {
error_kind,
location: SourceLocation::new(id, source_loc),
}
}
pub fn into_location_and_error_kind(self) -> (SourceLocation<'a>, ValidationErrorKind) {
(self.location, self.error_kind)
}
pub fn error_kind(&self) -> &ValidationErrorKind {
&self.error_kind
}
pub fn location(&self) -> &SourceLocation {
&self.location
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct SourceLocation<'a> {
policy_id: &'a PolicyID,
source_loc: Option<Loc>,
}
impl<'a> SourceLocation<'a> {
pub(crate) fn new(policy_id: &'a PolicyID, source_loc: Option<Loc>) -> Self {
Self {
policy_id,
source_loc,
}
}
pub fn policy_id(&self) -> &'a PolicyID {
self.policy_id
}
pub fn source_loc(&self) -> Option<&Loc> {
self.source_loc.as_ref()
}
}
#[derive(Debug, Clone, Diagnostic, Error)]
#[cfg_attr(test, derive(Eq, PartialEq))]
#[non_exhaustive]
pub enum ValidationErrorKind {
#[error(transparent)]
#[diagnostic(transparent)]
UnrecognizedEntityType(#[from] UnrecognizedEntityType),
#[error(transparent)]
#[diagnostic(transparent)]
UnrecognizedActionId(#[from] UnrecognizedActionId),
#[error(transparent)]
#[diagnostic(transparent)]
InvalidActionApplication(#[from] InvalidActionApplication),
#[error(transparent)]
#[diagnostic(transparent)]
TypeError(#[from] TypeErrorKind),
#[error(transparent)]
#[diagnostic(transparent)]
UnspecifiedEntity(#[from] UnspecifiedEntityError),
}
impl ValidationErrorKind {
pub(crate) fn unrecognized_entity_type(
actual_entity_type: String,
suggested_entity_type: Option<String>,
) -> ValidationErrorKind {
UnrecognizedEntityType {
actual_entity_type,
suggested_entity_type,
}
.into()
}
pub(crate) fn unrecognized_action_id(
actual_action_id: String,
suggested_action_id: Option<String>,
) -> ValidationErrorKind {
UnrecognizedActionId {
actual_action_id,
suggested_action_id,
}
.into()
}
pub(crate) fn invalid_action_application(
would_in_fix_principal: bool,
would_in_fix_resource: bool,
) -> ValidationErrorKind {
InvalidActionApplication {
would_in_fix_principal,
would_in_fix_resource,
}
.into()
}
pub(crate) fn type_error(type_error: TypeErrorKind) -> ValidationErrorKind {
type_error.into()
}
pub(crate) fn unspecified_entity(entity_id: String) -> ValidationErrorKind {
UnspecifiedEntityError { entity_id }.into()
}
}
#[derive(Debug, Clone, Error)]
#[cfg_attr(test, derive(Eq, PartialEq))]
#[error("unrecognized entity type `{actual_entity_type}`")]
pub struct UnrecognizedEntityType {
pub(crate) actual_entity_type: String,
pub(crate) suggested_entity_type: Option<String>,
}
impl Diagnostic for UnrecognizedEntityType {
fn help<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
match &self.suggested_entity_type {
Some(s) => Some(Box::new(format!("did you mean `{s}`?"))),
None => None,
}
}
}
#[derive(Debug, Clone, Error)]
#[cfg_attr(test, derive(Eq, PartialEq))]
#[error("unrecognized action `{actual_action_id}`")]
pub struct UnrecognizedActionId {
pub(crate) actual_action_id: String,
pub(crate) suggested_action_id: Option<String>,
}
impl Diagnostic for UnrecognizedActionId {
fn help<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
match &self.suggested_action_id {
Some(s) => Some(Box::new(format!("did you mean `{s}`?"))),
None => None,
}
}
}
#[derive(Debug, Clone, Error)]
#[cfg_attr(test, derive(Eq, PartialEq))]
#[error("unable to find an applicable action given the policy head constraints")]
pub struct InvalidActionApplication {
pub(crate) would_in_fix_principal: bool,
pub(crate) would_in_fix_resource: bool,
}
impl Diagnostic for InvalidActionApplication {
fn help<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
match (self.would_in_fix_principal, self.would_in_fix_resource) {
(true, false) => Some(Box::new(
"try replacing `==` with `in` in the principal clause",
)),
(false, true) => Some(Box::new(
"try replacing `==` with `in` in the resource clause",
)),
(true, true) => Some(Box::new(
"try replacing `==` with `in` in the principal clause and the resource clause",
)),
(false, false) => None,
}
}
}
#[derive(Debug, Clone, Diagnostic, Error)]
#[cfg_attr(test, derive(Eq, PartialEq))]
#[error("unspecified entity with id `{entity_id}`")]
#[diagnostic(help("unspecified entities cannot be used in policies"))]
pub struct UnspecifiedEntityError {
pub(crate) entity_id: String,
}