use std::collections::HashSet;
use cedar_policy_core::{
ast::{EntityAttrEvaluationError, EntityUID, Name},
parser::err::{ParseError, ParseErrors},
transitive_closure,
};
use itertools::Itertools;
use miette::Diagnostic;
use thiserror::Error;
use crate::human_schema::parser::HumanSyntaxParseErrors;
#[derive(Debug, Error, Diagnostic)]
pub enum HumanSchemaError {
#[error("{0}")]
#[diagnostic(transparent)]
Core(#[from] SchemaError),
#[error("{0}")]
IO(#[from] std::io::Error),
#[error("{0}")]
Parsing(#[from] HumanSyntaxParseErrors),
}
#[derive(Debug, Diagnostic, Error)]
pub enum SchemaError {
#[error("failed to parse schema: {0}")]
Serde(#[from] serde_json::Error),
#[error("transitive closure computation/enforcement error on action hierarchy: {0}")]
#[diagnostic(transparent)]
ActionTransitiveClosure(Box<transitive_closure::TcError<EntityUID>>),
#[error("transitive closure computation/enforcement error on entity type hierarchy: {0}")]
#[diagnostic(transparent)]
EntityTypeTransitiveClosure(#[from] transitive_closure::TcError<Name>),
#[error("unsupported feature used in schema: {0}")]
#[diagnostic(transparent)]
UnsupportedFeature(UnsupportedFeature),
#[error("undeclared entity type(s): {0:?}")]
#[diagnostic(help(
"any entity types appearing anywhere in a schema need to be declared in `entityTypes`"
))]
UndeclaredEntityTypes(HashSet<String>),
#[error("undeclared action(s): {0:?}")]
#[diagnostic(help("any actions appearing in `memberOf` need to be declared in `actions`"))]
UndeclaredActions(HashSet<String>),
#[error("undeclared common type(s), or common type(s) used in the declaration of another common type: {0:?}")]
#[diagnostic(help("any common types used in entity or context attributes need to be declared in `commonTypes`, and currently, common types may not reference other common types"))]
UndeclaredCommonTypes(HashSet<String>),
#[error("duplicate entity type `{0}`")]
DuplicateEntityType(String),
#[error("duplicate action `{0}`")]
DuplicateAction(String),
#[error("duplicate common type `{0}`")]
DuplicateCommonType(String),
#[error("cycle in action hierarchy containing `{0}`")]
CycleInActionHierarchy(EntityUID),
#[error("parse error in entity type: {}", Self::format_parse_errs(.0))]
#[diagnostic(transparent)]
ParseEntityType(ParseErrors),
#[error("parse error in namespace identifier: {}", Self::format_parse_errs(.0))]
#[diagnostic(transparent)]
ParseNamespace(ParseErrors),
#[error("parse error in extension type: {}", Self::format_parse_errs(.0))]
#[diagnostic(transparent)]
ParseExtensionType(ParseErrors),
#[error("parse error in common type identifier: {}", Self::format_parse_errs(.0))]
#[diagnostic(transparent)]
ParseCommonType(ParseErrors),
#[error("entity type `Action` declared in `entityTypes` list")]
ActionEntityTypeDeclared,
#[error("{0} is declared with a type other than `Record`")]
#[diagnostic(help("{}", match .0 {
ContextOrShape::ActionContext(_) => "action contexts must have type `Record`",
ContextOrShape::EntityTypeShape(_) => "entity type shapes must have type `Record`",
}))]
ContextOrShapeNotRecord(ContextOrShape),
#[error("action `{0}` has an attribute that is an empty set")]
#[diagnostic(help(
"actions are not currently allowed to have attributes whose value is an empty set"
))]
ActionAttributesContainEmptySet(EntityUID),
#[error("action `{0}` has an attribute with unsupported JSON representation: {1}")]
UnsupportedActionAttribute(EntityUID, String),
#[error(transparent)]
#[diagnostic(transparent)]
ActionAttrEval(EntityAttrEvaluationError),
#[error("the `__expr` escape is no longer supported")]
#[diagnostic(help("to create an entity reference, use `__entity`; to create an extension value, use `__extn`; and for all other values, use JSON directly"))]
ExprEscapeUsed,
}
impl From<transitive_closure::TcError<EntityUID>> for SchemaError {
fn from(e: transitive_closure::TcError<EntityUID>) -> Self {
match e {
transitive_closure::TcError::MissingTcEdge { .. } => {
SchemaError::ActionTransitiveClosure(Box::new(e))
}
transitive_closure::TcError::HasCycle { vertex_with_loop } => {
SchemaError::CycleInActionHierarchy(vertex_with_loop)
}
}
}
}
pub type Result<T> = std::result::Result<T, SchemaError>;
impl SchemaError {
fn format_parse_errs(errs: &[ParseError]) -> String {
errs.iter().map(|e| e.to_string()).join(", ")
}
}
#[derive(Debug)]
pub enum ContextOrShape {
ActionContext(EntityUID),
EntityTypeShape(Name),
}
impl std::fmt::Display for ContextOrShape {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ContextOrShape::ActionContext(action) => write!(f, "Context for action {}", action),
ContextOrShape::EntityTypeShape(entity_type) => {
write!(f, "Shape for entity type {}", entity_type)
}
}
}
}
#[derive(Debug, Diagnostic, Error)]
pub enum UnsupportedFeature {
#[error("records and entities with `additionalAttributes` are experimental, but the experimental `partial-validate` feature is not enabled")]
OpenRecordsAndEntities,
#[error("action declared with attributes: [{}]", .0.iter().join(", "))]
ActionAttributes(Vec<String>),
}