use std::{
collections::{HashMap, HashSet},
fmt::Display,
};
use cedar_policy_core::parser::{
err::{expected_to_string, ExpectedTokenConfig},
unescape::UnescapeError,
Loc, Node,
};
use lalrpop_util as lalr;
use lazy_static::lazy_static;
use miette::{Diagnostic, LabeledSpan, SourceSpan};
use nonempty::NonEmpty;
use smol_str::SmolStr;
use thiserror::Error;
use super::ast::PR;
#[derive(Debug, Clone, PartialEq, Eq, Error)]
pub enum UserError {
#[error("An empty list was passed")]
EmptyList,
#[error("Invalid escape codes")]
StringEscape(NonEmpty<UnescapeError>),
#[error("`{0}` is a reserved identifier")]
ReservedIdentifierUsed(SmolStr),
}
pub(crate) type RawLocation = usize;
pub(crate) type RawToken<'a> = lalr::lexer::Token<'a>;
pub(crate) type RawUserError = Node<UserError>;
pub(crate) type RawParseError<'a> = lalr::ParseError<RawLocation, RawToken<'a>, RawUserError>;
pub(crate) type RawErrorRecovery<'a> = lalr::ErrorRecovery<RawLocation, RawToken<'a>, RawUserError>;
type OwnedRawParseError = lalr::ParseError<RawLocation, String, RawUserError>;
lazy_static! {
static ref SCHEMA_TOKEN_CONFIG: ExpectedTokenConfig = ExpectedTokenConfig {
friendly_token_names: HashMap::from([
("IN", "`in`"),
("PRINCIPAL", "`principal`"),
("ACTION", "`action`"),
("RESOURCE", "`resource`"),
("CONTEXT", "`context`"),
("STRINGLIT", "string literal"),
("ENTITY", "`entity`"),
("NAMESPACE", "`namespace`"),
("TYPE", "`type`"),
("SET", "`Set`"),
("IDENTIFIER", "identifier"),
]),
impossible_tokens: HashSet::new(),
special_identifier_tokens: HashSet::from([
"NAMESPACE",
"ENTITY",
"IN",
"TYPE",
"APPLIESTO",
"PRINCIPAL",
"ACTION",
"RESOURCE",
"CONTEXT",
"ATTRIBUTES",
"LONG",
"STRING",
"BOOL",
]),
identifier_sentinel: "IDENTIFIER",
first_set_identifier_tokens: HashSet::from(["SET"]),
first_set_sentinel: "\"{\"",
};
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ParseError {
err: OwnedRawParseError,
}
impl From<RawParseError<'_>> for ParseError {
fn from(err: RawParseError<'_>) -> Self {
Self {
err: err.map_token(|token| token.to_string()),
}
}
}
impl From<RawErrorRecovery<'_>> for ParseError {
fn from(recovery: RawErrorRecovery<'_>) -> Self {
recovery.error.into()
}
}
impl ParseError {
pub fn primary_source_span(&self) -> SourceSpan {
let Self { err } = self;
match err {
OwnedRawParseError::InvalidToken { location } => SourceSpan::from(*location),
OwnedRawParseError::UnrecognizedEof { location, .. } => SourceSpan::from(*location),
OwnedRawParseError::UnrecognizedToken {
token: (token_start, _, token_end),
..
} => SourceSpan::from(*token_start..*token_end),
OwnedRawParseError::ExtraToken {
token: (token_start, _, token_end),
} => SourceSpan::from(*token_start..*token_end),
OwnedRawParseError::User { error } => error.loc.span,
}
}
}
impl Display for ParseError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Self { err } = self;
match err {
OwnedRawParseError::InvalidToken { .. } => write!(f, "invalid token"),
OwnedRawParseError::UnrecognizedEof { .. } => write!(f, "unexpected end of input"),
OwnedRawParseError::UnrecognizedToken {
token: (_, token, _),
..
} => write!(f, "unexpected token `{token}`"),
OwnedRawParseError::ExtraToken {
token: (_, token, _),
..
} => write!(f, "extra token `{token}`"),
OwnedRawParseError::User {
error: Node { node, .. },
} => write!(f, "{node}"),
}
}
}
impl std::error::Error for ParseError {}
impl Diagnostic for ParseError {
fn labels(&self) -> Option<Box<dyn Iterator<Item = LabeledSpan> + '_>> {
let primary_source_span = self.primary_source_span();
let Self { err } = self;
let labeled_span = match err {
OwnedRawParseError::InvalidToken { .. } => LabeledSpan::underline(primary_source_span),
OwnedRawParseError::UnrecognizedEof { expected, .. } => LabeledSpan::new_with_span(
expected_to_string(expected, &SCHEMA_TOKEN_CONFIG),
primary_source_span,
),
OwnedRawParseError::UnrecognizedToken { expected, .. } => LabeledSpan::new_with_span(
expected_to_string(expected, &SCHEMA_TOKEN_CONFIG),
primary_source_span,
),
OwnedRawParseError::ExtraToken { .. } => LabeledSpan::underline(primary_source_span),
OwnedRawParseError::User { .. } => LabeledSpan::underline(primary_source_span),
};
Some(Box::new(std::iter::once(labeled_span)))
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ParseErrors(Box<NonEmpty<ParseError>>);
impl ParseErrors {
pub fn new(first: ParseError, rest: impl IntoIterator<Item = ParseError>) -> Self {
let mut nv = NonEmpty::singleton(first);
let mut v = rest.into_iter().collect::<Vec<_>>();
nv.append(&mut v);
Self(Box::new(nv))
}
pub fn from_iter(i: impl IntoIterator<Item = ParseError>) -> Option<Self> {
let v = i.into_iter().collect::<Vec<_>>();
Some(Self(Box::new(NonEmpty::from_vec(v)?)))
}
pub fn iter(&self) -> impl Iterator<Item = &ParseError> {
self.0.iter()
}
}
impl Display for ParseErrors {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0.first())
}
}
impl std::error::Error for ParseErrors {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
std::error::Error::source(self.0.first())
}
}
impl Diagnostic for ParseErrors {
fn related<'a>(&'a self) -> Option<Box<dyn Iterator<Item = &'a dyn Diagnostic> + 'a>> {
let mut errs = self.iter().map(|err| err as &dyn Diagnostic);
errs.next().map(move |first_err| match first_err.related() {
Some(first_err_related) => Box::new(first_err_related.chain(errs)),
None => Box::new(errs) as Box<dyn Iterator<Item = _>>,
})
}
fn code<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
Diagnostic::code(self.0.first())
}
fn severity(&self) -> Option<miette::Severity> {
Diagnostic::severity(self.0.first())
}
fn help<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
Diagnostic::help(self.0.first())
}
fn url<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
Diagnostic::url(self.0.first())
}
fn source_code(&self) -> Option<&dyn miette::SourceCode> {
Diagnostic::source_code(self.0.first())
}
fn labels(&self) -> Option<Box<dyn Iterator<Item = LabeledSpan> + '_>> {
Diagnostic::labels(self.0.first())
}
fn diagnostic_source(&self) -> Option<&dyn Diagnostic> {
Diagnostic::diagnostic_source(self.0.first())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ToJsonSchemaErrors(NonEmpty<ToJsonSchemaError>);
impl ToJsonSchemaErrors {
pub fn new(errs: NonEmpty<ToJsonSchemaError>) -> Self {
Self(errs)
}
pub fn iter(&self) -> impl Iterator<Item = &ToJsonSchemaError> {
self.0.iter()
}
}
impl IntoIterator for ToJsonSchemaErrors {
type Item = ToJsonSchemaError;
type IntoIter = <NonEmpty<ToJsonSchemaError> as IntoIterator>::IntoIter;
fn into_iter(self) -> Self::IntoIter {
self.0.into_iter()
}
}
impl From<ToJsonSchemaError> for ToJsonSchemaErrors {
fn from(value: ToJsonSchemaError) -> Self {
Self(NonEmpty::singleton(value))
}
}
impl Display for ToJsonSchemaErrors {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0.first()) }
}
impl std::error::Error for ToJsonSchemaErrors {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
self.0.first().source()
}
#[allow(deprecated)]
fn description(&self) -> &str {
self.0.first().description()
}
#[allow(deprecated)]
fn cause(&self) -> Option<&dyn std::error::Error> {
self.0.first().cause()
}
}
impl Diagnostic for ToJsonSchemaErrors {
fn related<'a>(&'a self) -> Option<Box<dyn Iterator<Item = &'a dyn Diagnostic> + 'a>> {
let mut errs = self.iter().map(|err| err as &dyn Diagnostic);
errs.next().map(move |first_err| match first_err.related() {
Some(first_err_related) => Box::new(first_err_related.chain(errs)),
None => Box::new(errs) as Box<dyn Iterator<Item = _>>,
})
}
fn code<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
self.0.first().code()
}
fn severity(&self) -> Option<miette::Severity> {
self.0.first().severity()
}
fn help<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
self.0.first().help()
}
fn url<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
self.0.first().url()
}
fn source_code(&self) -> Option<&dyn miette::SourceCode> {
self.0.first().source_code()
}
fn labels(&self) -> Option<Box<dyn Iterator<Item = LabeledSpan> + '_>> {
self.0.first().labels()
}
fn diagnostic_source(&self) -> Option<&dyn Diagnostic> {
self.0.first().diagnostic_source()
}
}
#[derive(Clone, Debug, Error, PartialEq, Eq)]
pub enum ToJsonSchemaError {
#[error("Duplicate keys: `{key}`")]
DuplicateKeys { key: SmolStr, loc1: Loc, loc2: Loc },
#[error("Duplicate declarations: `{decl}`")]
DuplicateDeclarations { decl: SmolStr, loc1: Loc, loc2: Loc },
#[error("Duplicate context declaration. Action may have at most one context declaration")]
DuplicateContext { loc1: Loc, loc2: Loc },
#[error("Duplicate {kind} decleration. Action may have at most once {kind} declaration")]
DuplicatePR { kind: PR, loc1: Loc, loc2: Loc },
#[error("Duplicate namespace IDs: `{namespace_id}`")]
DuplicateNameSpaces {
namespace_id: SmolStr,
loc1: Option<Loc>,
loc2: Option<Loc>,
},
#[error("Unknown type name: `{}`", .0.node)]
UnknownTypeName(Node<SmolStr>),
#[error("Use reserved namespace `__cedar`")]
UseReservedNamespace(Loc),
}
impl ToJsonSchemaError {
pub fn duplicate_keys(key: SmolStr, loc1: Loc, loc2: Loc) -> Self {
Self::DuplicateKeys { key, loc1, loc2 }
}
pub fn duplicate_decls(decl: SmolStr, loc1: Loc, loc2: Loc) -> Self {
Self::DuplicateDeclarations { decl, loc1, loc2 }
}
pub fn duplicate_namespace(namespace_id: SmolStr, loc1: Loc, loc2: Loc) -> Self {
Self::DuplicateNameSpaces {
namespace_id,
loc1: Some(loc1),
loc2: Some(loc2),
}
}
}
impl Diagnostic for ToJsonSchemaError {
fn labels(&self) -> Option<Box<dyn Iterator<Item = LabeledSpan> + '_>> {
match self {
ToJsonSchemaError::DuplicateDeclarations { loc1, loc2, .. }
| ToJsonSchemaError::DuplicateContext { loc1, loc2 }
| ToJsonSchemaError::DuplicatePR { loc1, loc2, .. }
| ToJsonSchemaError::DuplicateKeys { loc1, loc2, .. } => Some(Box::new(
vec![
LabeledSpan::underline(loc1.span),
LabeledSpan::underline(loc2.span),
]
.into_iter(),
)),
ToJsonSchemaError::DuplicateNameSpaces { loc1, loc2, .. } => {
Some(Box::new([loc1, loc2].into_iter().filter_map(|loc| {
Some(LabeledSpan::underline(loc.as_ref()?.span))
})))
}
ToJsonSchemaError::UnknownTypeName(node) => Some(Box::new(std::iter::once(
LabeledSpan::underline(node.loc.span),
))),
ToJsonSchemaError::UseReservedNamespace(loc) => {
Some(Box::new(std::iter::once(LabeledSpan::underline(loc.span))))
}
}
}
}
#[derive(Debug, Clone, Error, Diagnostic)]
#[diagnostic(severity(warning))]
pub enum SchemaWarning {
#[error("The name `{name}` shadows a builtin Cedar name. You'll have to refer to the builtin as `__cedar::{name}`.")]
ShadowsBuiltin { name: SmolStr, loc: Loc },
#[error("The common type name {name} shadows an entity name")]
ShadowsEntity {
name: SmolStr,
entity_loc: Loc,
common_loc: Loc,
},
#[error("The namespace {name} uses a name that will be reserved in the future. All namespaces beginning with `__` will be reserved in a future version.")]
UsesBuiltinNamespace { name: SmolStr, loc: Loc },
}