use miette::Diagnostic;
use thiserror::Error;
use validation_errors::UnrecognizedActionIdHelp;
use std::collections::BTreeSet;
use cedar_policy_core::ast::{EntityType, Expr, PolicyID};
use cedar_policy_core::parser::Loc;
use crate::types::{EntityLUB, Type};
pub mod validation_errors;
pub mod validation_warnings;
#[derive(Debug)]
pub struct ValidationResult {
validation_errors: Vec<ValidationError>,
validation_warnings: Vec<ValidationWarning>,
}
impl ValidationResult {
pub fn new(
errors: impl IntoIterator<Item = ValidationError>,
warnings: impl IntoIterator<Item = ValidationWarning>,
) -> 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>,
impl Iterator<Item = ValidationWarning>,
) {
(
self.validation_errors.into_iter(),
self.validation_warnings.into_iter(),
)
}
}
#[derive(Clone, Debug, Diagnostic, Error, Hash, Eq, PartialEq)]
pub enum ValidationError {
#[error(transparent)]
#[diagnostic(transparent)]
UnrecognizedEntityType(#[from] validation_errors::UnrecognizedEntityType),
#[error(transparent)]
#[diagnostic(transparent)]
UnrecognizedActionId(#[from] validation_errors::UnrecognizedActionId),
#[error(transparent)]
#[diagnostic(transparent)]
InvalidActionApplication(#[from] validation_errors::InvalidActionApplication),
#[error(transparent)]
#[diagnostic(transparent)]
UnexpectedType(#[from] validation_errors::UnexpectedType),
#[error(transparent)]
#[diagnostic(transparent)]
IncompatibleTypes(#[from] validation_errors::IncompatibleTypes),
#[error(transparent)]
#[diagnostic(transparent)]
UnsafeAttributeAccess(#[from] validation_errors::UnsafeAttributeAccess),
#[error(transparent)]
#[diagnostic(transparent)]
UnsafeOptionalAttributeAccess(#[from] validation_errors::UnsafeOptionalAttributeAccess),
#[error(transparent)]
#[diagnostic(transparent)]
UnsafeTagAccess(#[from] validation_errors::UnsafeTagAccess),
#[error(transparent)]
#[diagnostic(transparent)]
NoTagsAllowed(#[from] validation_errors::NoTagsAllowed),
#[error(transparent)]
#[diagnostic(transparent)]
UndefinedFunction(#[from] validation_errors::UndefinedFunction),
#[error(transparent)]
#[diagnostic(transparent)]
WrongNumberArguments(#[from] validation_errors::WrongNumberArguments),
#[diagnostic(transparent)]
#[error(transparent)]
FunctionArgumentValidation(#[from] validation_errors::FunctionArgumentValidation),
#[diagnostic(transparent)]
#[error(transparent)]
EmptySetForbidden(#[from] validation_errors::EmptySetForbidden),
#[diagnostic(transparent)]
#[error(transparent)]
NonLitExtConstructor(#[from] validation_errors::NonLitExtConstructor),
#[error(transparent)]
#[diagnostic(transparent)]
HierarchyNotRespected(#[from] validation_errors::HierarchyNotRespected),
#[error(transparent)]
#[diagnostic(transparent)]
InternalInvariantViolation(#[from] validation_errors::InternalInvariantViolation),
#[cfg(feature = "level-validate")]
#[error(transparent)]
#[diagnostic(transparent)]
EntityDerefLevelViolation(#[from] validation_errors::EntityDerefLevelViolation),
}
impl ValidationError {
pub(crate) fn unrecognized_entity_type(
source_loc: Option<Loc>,
policy_id: PolicyID,
actual_entity_type: String,
suggested_entity_type: Option<String>,
) -> Self {
validation_errors::UnrecognizedEntityType {
source_loc,
policy_id,
actual_entity_type,
suggested_entity_type,
}
.into()
}
pub(crate) fn unrecognized_action_id(
source_loc: Option<Loc>,
policy_id: PolicyID,
actual_action_id: String,
hint: Option<UnrecognizedActionIdHelp>,
) -> Self {
validation_errors::UnrecognizedActionId {
source_loc,
policy_id,
actual_action_id,
hint,
}
.into()
}
pub(crate) fn invalid_action_application(
source_loc: Option<Loc>,
policy_id: PolicyID,
would_in_fix_principal: bool,
would_in_fix_resource: bool,
) -> Self {
validation_errors::InvalidActionApplication {
source_loc,
policy_id,
would_in_fix_principal,
would_in_fix_resource,
}
.into()
}
pub(crate) fn expected_one_of_types(
source_loc: Option<Loc>,
policy_id: PolicyID,
expected: impl IntoIterator<Item = Type>,
actual: Type,
help: Option<validation_errors::UnexpectedTypeHelp>,
) -> Self {
validation_errors::UnexpectedType {
source_loc,
policy_id,
expected: expected.into_iter().collect::<BTreeSet<_>>(),
actual,
help,
}
.into()
}
pub(crate) fn incompatible_types(
source_loc: Option<Loc>,
policy_id: PolicyID,
types: impl IntoIterator<Item = Type>,
hint: validation_errors::LubHelp,
context: validation_errors::LubContext,
) -> Self {
validation_errors::IncompatibleTypes {
source_loc,
policy_id,
types: types.into_iter().collect::<BTreeSet<_>>(),
hint,
context,
}
.into()
}
pub(crate) fn unsafe_attribute_access(
source_loc: Option<Loc>,
policy_id: PolicyID,
attribute_access: validation_errors::AttributeAccess,
suggestion: Option<String>,
may_exist: bool,
) -> Self {
validation_errors::UnsafeAttributeAccess {
source_loc,
policy_id,
attribute_access,
suggestion,
may_exist,
}
.into()
}
pub(crate) fn unsafe_optional_attribute_access(
source_loc: Option<Loc>,
policy_id: PolicyID,
attribute_access: validation_errors::AttributeAccess,
) -> Self {
validation_errors::UnsafeOptionalAttributeAccess {
source_loc,
policy_id,
attribute_access,
}
.into()
}
pub(crate) fn unsafe_tag_access(
source_loc: Option<Loc>,
policy_id: PolicyID,
entity_ty: Option<EntityLUB>,
tag: Expr<Option<Type>>,
) -> Self {
validation_errors::UnsafeTagAccess {
source_loc,
policy_id,
entity_ty,
tag,
}
.into()
}
pub(crate) fn no_tags_allowed(
source_loc: Option<Loc>,
policy_id: PolicyID,
entity_ty: Option<EntityType>,
) -> Self {
validation_errors::NoTagsAllowed {
source_loc,
policy_id,
entity_ty,
}
.into()
}
pub(crate) fn undefined_extension(
source_loc: Option<Loc>,
policy_id: PolicyID,
name: String,
) -> Self {
validation_errors::UndefinedFunction {
source_loc,
policy_id,
name,
}
.into()
}
pub(crate) fn wrong_number_args(
source_loc: Option<Loc>,
policy_id: PolicyID,
expected: usize,
actual: usize,
) -> Self {
validation_errors::WrongNumberArguments {
source_loc,
policy_id,
expected,
actual,
}
.into()
}
pub(crate) fn function_argument_validation(
source_loc: Option<Loc>,
policy_id: PolicyID,
msg: String,
) -> Self {
validation_errors::FunctionArgumentValidation {
source_loc,
policy_id,
msg,
}
.into()
}
pub(crate) fn empty_set_forbidden(source_loc: Option<Loc>, policy_id: PolicyID) -> Self {
validation_errors::EmptySetForbidden {
source_loc,
policy_id,
}
.into()
}
pub(crate) fn non_lit_ext_constructor(source_loc: Option<Loc>, policy_id: PolicyID) -> Self {
validation_errors::NonLitExtConstructor {
source_loc,
policy_id,
}
.into()
}
pub(crate) fn hierarchy_not_respected(
source_loc: Option<Loc>,
policy_id: PolicyID,
in_lhs: Option<EntityType>,
in_rhs: Option<EntityType>,
) -> Self {
validation_errors::HierarchyNotRespected {
source_loc,
policy_id,
in_lhs,
in_rhs,
}
.into()
}
pub(crate) fn internal_invariant_violation(
source_loc: Option<Loc>,
policy_id: PolicyID,
) -> Self {
validation_errors::InternalInvariantViolation {
source_loc,
policy_id,
}
.into()
}
}
#[derive(Debug, Clone, PartialEq, Diagnostic, Error, Eq, Hash)]
pub enum ValidationWarning {
#[diagnostic(transparent)]
#[error(transparent)]
MixedScriptString(#[from] validation_warnings::MixedScriptString),
#[diagnostic(transparent)]
#[error(transparent)]
BidiCharsInString(#[from] validation_warnings::BidiCharsInString),
#[diagnostic(transparent)]
#[error(transparent)]
BidiCharsInIdentifier(#[from] validation_warnings::BidiCharsInIdentifier),
#[diagnostic(transparent)]
#[error(transparent)]
MixedScriptIdentifier(#[from] validation_warnings::MixedScriptIdentifier),
#[diagnostic(transparent)]
#[error(transparent)]
ConfusableIdentifier(#[from] validation_warnings::ConfusableIdentifier),
#[diagnostic(transparent)]
#[error(transparent)]
ImpossiblePolicy(#[from] validation_warnings::ImpossiblePolicy),
}
impl ValidationWarning {
pub(crate) fn mixed_script_string(
source_loc: Option<Loc>,
policy_id: PolicyID,
string: impl Into<String>,
) -> Self {
validation_warnings::MixedScriptString {
source_loc,
policy_id,
string: string.into(),
}
.into()
}
pub(crate) fn bidi_chars_strings(
source_loc: Option<Loc>,
policy_id: PolicyID,
string: impl Into<String>,
) -> Self {
validation_warnings::BidiCharsInString {
source_loc,
policy_id,
string: string.into(),
}
.into()
}
pub(crate) fn mixed_script_identifier(
source_loc: Option<Loc>,
policy_id: PolicyID,
id: impl Into<String>,
) -> Self {
validation_warnings::MixedScriptIdentifier {
source_loc,
policy_id,
id: id.into(),
}
.into()
}
pub(crate) fn bidi_chars_identifier(
source_loc: Option<Loc>,
policy_id: PolicyID,
id: impl Into<String>,
) -> Self {
validation_warnings::BidiCharsInIdentifier {
source_loc,
policy_id,
id: id.into(),
}
.into()
}
pub(crate) fn confusable_identifier(
source_loc: Option<Loc>,
policy_id: PolicyID,
id: impl Into<String>,
) -> Self {
validation_warnings::ConfusableIdentifier {
source_loc,
policy_id,
id: id.into(),
}
.into()
}
pub(crate) fn impossible_policy(source_loc: Option<Loc>, policy_id: PolicyID) -> Self {
validation_warnings::ImpossiblePolicy {
source_loc,
policy_id,
}
.into()
}
}