Skip to content

Commit 1dc1ded

Browse files
authored
Minor edits to ParseError (#908)
1 parent 92e939b commit 1dc1ded

File tree

8 files changed

+89
-114
lines changed

8 files changed

+89
-114
lines changed

cedar-policy-core/src/ast/literal.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ impl std::fmt::Display for Literal {
7171
}
7272

7373
impl std::str::FromStr for Literal {
74-
type Err = parser::err::ParseErrors;
74+
type Err = parser::err::LiteralParseError;
7575

7676
fn from_str(s: &str) -> Result<Literal, Self::Err> {
7777
parser::parse_literal(s)

cedar-policy-core/src/ast/restricted_expr.rs

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -297,7 +297,7 @@ pub enum PartialValueToRestrictedExprError {
297297
}
298298

299299
impl std::str::FromStr for RestrictedExpr {
300-
type Err = RestrictedExprParseError;
300+
type Err = RestrictedExpressionParseError;
301301

302302
fn from_str(s: &str) -> Result<RestrictedExpr, Self::Err> {
303303
parser::parse_restrictedexpr(s)
@@ -596,7 +596,7 @@ impl<'a> Hash for RestrictedExprShapeOnly<'a> {
596596
}
597597

598598
/// Error when constructing a restricted expression from unrestricted
599-
599+
/// expression
600600
#[derive(Debug, Clone, PartialEq, Eq, Error)]
601601
pub enum RestrictedExprError {
602602
/// An expression was expected to be a "restricted" expression, but contained
@@ -654,17 +654,21 @@ impl RestrictedExprError {
654654
}
655655

656656
/// Errors possible from `RestrictedExpr::from_str()`
657+
//
658+
// CAUTION: this type is publicly exported in `cedar-policy`.
659+
// Don't make fields `pub`, don't make breaking changes, and use caution when
660+
// adding public methods.
657661
#[derive(Debug, Clone, PartialEq, Eq, Diagnostic, Error)]
658-
pub enum RestrictedExprParseError {
659-
/// Failed to parse the expression entirely
660-
#[error("failed to parse restricted expression: {0}")]
662+
pub enum RestrictedExpressionParseError {
663+
/// Failed to parse the expression
664+
#[error(transparent)]
661665
#[diagnostic(transparent)]
662666
Parse(#[from] ParseErrors),
663667
/// Parsed successfully as an expression, but failed to construct a
664668
/// restricted expression, for the reason indicated in the underlying error
665669
#[error(transparent)]
666670
#[diagnostic(transparent)]
667-
RestrictedExpr(#[from] RestrictedExprError),
671+
InvalidRestrictedExpression(#[from] RestrictedExprError),
668672
}
669673

670674
#[cfg(test)]
@@ -721,7 +725,7 @@ mod test {
721725
let str = r#"{ foo: 37, bar: "hi", foo: 101 }"#;
722726
assert_eq!(
723727
RestrictedExpr::from_str(str),
724-
Err(RestrictedExprParseError::Parse(ParseErrors(vec![
728+
Err(RestrictedExpressionParseError::Parse(ParseErrors(vec![
725729
ParseError::ToAST(ToASTError::new(
726730
ToASTErrorKind::ExprConstructionError(
727731
ExprConstructionError::DuplicateKeyInRecordLiteral { key: "foo".into() }

cedar-policy-core/src/parser.rs

Lines changed: 44 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ use smol_str::SmolStr;
4040
use std::collections::HashMap;
4141

4242
use crate::ast;
43-
use crate::ast::RestrictedExprParseError;
43+
use crate::ast::RestrictedExpressionParseError;
4444
use crate::est;
4545

4646
/// simple main function for parsing policies
@@ -243,7 +243,7 @@ pub(crate) fn parse_expr(ptext: &str) -> Result<ast::Expr, err::ParseErrors> {
243243
/// `FromStr` impl or its constructors
244244
pub(crate) fn parse_restrictedexpr(
245245
ptext: &str,
246-
) -> Result<ast::RestrictedExpr, RestrictedExprParseError> {
246+
) -> Result<ast::RestrictedExpr, RestrictedExpressionParseError> {
247247
let expr = parse_expr(ptext)?;
248248
Ok(ast::RestrictedExpr::new(expr)?)
249249
}
@@ -286,24 +286,19 @@ pub(crate) fn parse_name(name: &str) -> Result<ast::Name, err::ParseErrors> {
286286
///
287287
/// Private to this crate. Users outside Core should use `Literal`'s `FromStr` impl
288288
/// or its constructors
289-
pub(crate) fn parse_literal(val: &str) -> Result<ast::Literal, err::ParseErrors> {
289+
pub(crate) fn parse_literal(val: &str) -> Result<ast::Literal, err::LiteralParseError> {
290290
let mut errs = err::ParseErrors::new();
291291
let cst = text_to_cst::parse_primary(val)?;
292292
let Some(ast) = cst.to_expr(&mut errs) else {
293-
return Err(errs);
293+
return Err(err::LiteralParseError::Parse(errs));
294294
};
295295
if errs.is_empty() {
296-
match ast.into_expr_kind() {
297-
ast::ExprKind::Lit(v) => Ok(v),
298-
_ => Err(
299-
err::ParseError::ParseLiteral(err::ParseLiteralError::ParseLiteral(
300-
val.to_string(),
301-
))
302-
.into(),
303-
),
296+
match ast.expr_kind() {
297+
ast::ExprKind::Lit(v) => Ok(v.clone()),
298+
_ => Err(err::LiteralParseError::InvalidLiteral(ast)),
304299
}
305300
} else {
306-
Err(errs)
301+
Err(err::LiteralParseError::Parse(errs))
307302
}
308303
}
309304

@@ -1146,4 +1141,40 @@ mod parse_tests {
11461141
"expected `!=`, `&&`, `(`, `*`, `+`, `-`, `.`, `::`, `<`, `<=`, `==`, `>`, `>=`, `[`, `||`, `has`, `in`, `is`, `like`, or `then`",
11471142
)
11481143
}
1144+
1145+
#[test]
1146+
fn string_escapes() {
1147+
// test strings with valid escapes
1148+
// convert a string `s` to `<double-quote> <escaped-form-of-s> <double-quote>`
1149+
// and test if the resulting string literal AST contains exactly `s`
1150+
// for instance, "\u{1F408}"" is converted into r#""\u{1F408}""#,
1151+
// the latter should be parsed into `Literal(String("🐈"))` and
1152+
// `🐈` is represented by '\u{1F408}'
1153+
let test_valid = |s: &str| {
1154+
let r = parse_literal(&format!("\"{}\"", s.escape_default()));
1155+
assert_eq!(r, Ok(ast::Literal::String(s.into())));
1156+
};
1157+
test_valid("\t");
1158+
test_valid("\0");
1159+
test_valid("👍");
1160+
test_valid("🐈");
1161+
test_valid("\u{1F408}");
1162+
test_valid("abc\tde\\fg");
1163+
test_valid("aaa\u{1F408}bcd👍👍👍");
1164+
// test string with invalid escapes
1165+
let test_invalid = |s: &str, en: usize| {
1166+
let r = parse_literal(&format!("\"{}\"", s));
1167+
assert_matches!(r, Err(err::LiteralParseError::Parse(errs)) => {
1168+
assert_eq!(errs.len(), en);
1169+
});
1170+
};
1171+
// invalid escape `\a`
1172+
test_invalid("\\a", 1);
1173+
// invalid escape `\b`
1174+
test_invalid("\\b", 1);
1175+
// invalid escape `\p`
1176+
test_invalid("\\\\aa\\p", 1);
1177+
// invalid escape `\a` and empty unicode escape
1178+
test_invalid(r"\aaa\u{}", 2);
1179+
}
11491180
}

cedar-policy-core/src/parser/cst_to_ast.rs

Lines changed: 4 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -3459,11 +3459,9 @@ mod tests {
34593459
expect_some_error_matches(
34603460
src,
34613461
&errs,
3462-
&ExpectedErrorMessageBuilder::error(
3463-
"the input `\\*` is not a valid escape: InvalidEscape",
3464-
)
3465-
.exactly_one_underline(r#""string\*with\*escaped\*stars""#)
3466-
.build(),
3462+
&ExpectedErrorMessageBuilder::error("the input `\\*` is not a valid escape")
3463+
.exactly_one_underline(r#""string\*with\*escaped\*stars""#)
3464+
.build(),
34673465
);
34683466
}
34693467

@@ -3723,47 +3721,11 @@ mod tests {
37233721
);
37243722
}
37253723

3726-
#[test]
3727-
fn string_escapes() {
3728-
// test strings with valid escapes
3729-
// convert a string `s` to `<double-quote> <escaped-form-of-s> <double-quote>`
3730-
// and test if the resulting string literal AST contains exactly `s`
3731-
// for instance, "\u{1F408}"" is converted into r#""\u{1F408}""#,
3732-
// the latter should be parsed into `Literal(String("🐈"))` and
3733-
// `🐈` is represented by '\u{1F408}'
3734-
let test_valid = |s: &str| {
3735-
let r = parse_literal(&format!("\"{}\"", s.escape_default()));
3736-
assert!(r.is_ok());
3737-
assert_eq!(r.unwrap(), ast::Literal::String(s.into()));
3738-
};
3739-
test_valid("\t");
3740-
test_valid("\0");
3741-
test_valid("👍");
3742-
test_valid("🐈");
3743-
test_valid("\u{1F408}");
3744-
test_valid("abc\tde\\fg");
3745-
test_valid("aaa\u{1F408}bcd👍👍👍");
3746-
// test string with invalid escapes
3747-
let test_invalid = |s: &str, en: usize| {
3748-
let r = parse_literal(&format!("\"{}\"", s));
3749-
assert!(r.is_err());
3750-
assert_eq!(r.unwrap_err().len(), en);
3751-
};
3752-
// invalid escape `\a`
3753-
test_invalid("\\a", 1);
3754-
// invalid escape `\b`
3755-
test_invalid("\\b", 1);
3756-
// invalid escape `\p`
3757-
test_invalid("\\\\aa\\p", 1);
3758-
// invalid escape `\a` and empty unicode escape
3759-
test_invalid(r"\aaa\u{}", 2);
3760-
}
3761-
37623724
#[test]
37633725
fn unescape_err_positions() {
37643726
let assert_invalid_escape = |p_src, underline| {
37653727
assert_matches!(parse_policy_template(None, p_src), Err(e) => {
3766-
expect_err(p_src, &miette::Report::new(e), &ExpectedErrorMessageBuilder::error("the input `\\q` is not a valid escape: InvalidEscape").exactly_one_underline(underline).build());
3728+
expect_err(p_src, &miette::Report::new(e), &ExpectedErrorMessageBuilder::error("the input `\\q` is not a valid escape").exactly_one_underline(underline).build());
37673729
});
37683730
};
37693731
assert_invalid_escape(

cedar-policy-core/src/parser/err.rs

Lines changed: 24 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ use miette::{Diagnostic, LabeledSpan, SourceSpan};
2626
use smol_str::SmolStr;
2727
use thiserror::Error;
2828

29-
use crate::ast::{self, ExprConstructionError, InputInteger, PolicyID, RestrictedExprError, Var};
29+
use crate::ast::{self, Expr, ExprConstructionError, InputInteger, PolicyID, Var};
3030
use crate::parser::fmt::join_with_conjunction;
3131
use crate::parser::loc::Loc;
3232
use crate::parser::node::Node;
@@ -43,55 +43,36 @@ pub(crate) type RawErrorRecovery<'a> = lalr::ErrorRecovery<RawLocation, RawToken
4343

4444
type OwnedRawParseError = lalr::ParseError<RawLocation, String, RawUserError>;
4545

46-
/// For errors during parsing
46+
/// Errors that can occur when parsing Cedar policies or expressions.
47+
//
48+
// CAUTION: this type is publicly exported in `cedar-policy`.
49+
// Don't make fields `pub`, don't make breaking changes, and use caution when
50+
// adding public methods.
4751
#[derive(Clone, Debug, Diagnostic, Error, PartialEq, Eq)]
4852
pub enum ParseError {
49-
/// Error from the CST parser.
53+
/// Error from the text -> CST parser
5054
#[error(transparent)]
5155
#[diagnostic(transparent)]
5256
ToCST(#[from] ToCSTError),
53-
/// Error in the CST -> AST transform, mostly well-formedness issues.
57+
/// Error from the CST -> AST transform
5458
#[error(transparent)]
5559
#[diagnostic(transparent)]
5660
ToAST(#[from] ToASTError),
57-
/// Error concerning restricted expressions.
58-
#[error(transparent)]
59-
#[diagnostic(transparent)]
60-
RestrictedExpr(#[from] RestrictedExprError),
61-
/// Errors concerning parsing literals on their own
62-
#[error(transparent)]
63-
#[diagnostic(transparent)]
64-
ParseLiteral(#[from] ParseLiteralError),
6561
}
6662

67-
impl ParseError {
68-
/// Extract a primary source span locating the error, if one is available.
69-
pub fn primary_source_span(&self) -> Option<SourceSpan> {
70-
match self {
71-
ParseError::ToCST(to_cst_err) => Some(to_cst_err.primary_source_span()),
72-
ParseError::ToAST(to_ast_err) => Some(to_ast_err.source_loc().span),
73-
ParseError::RestrictedExpr(restricted_expr_err) => match restricted_expr_err {
74-
RestrictedExprError::InvalidRestrictedExpression { expr, .. } => {
75-
expr.source_loc().map(|loc| loc.span)
76-
}
77-
},
78-
ParseError::ParseLiteral(parse_lit_err) => parse_lit_err
79-
.labels()
80-
.and_then(|mut it| it.next().map(|lspan| *lspan.inner())),
81-
}
82-
}
83-
}
84-
85-
/// Errors in the top-level parse literal entrypoint
63+
/// Errors possible from `Literal::from_str()`
8664
#[derive(Debug, Clone, PartialEq, Diagnostic, Error, Eq)]
87-
pub enum ParseLiteralError {
88-
/// The top-level parser endpoint for parsing a literal encountered a non-literal.
89-
/// Since this can be any possible other expression, we just return it as a string.
90-
#[error("`{0}` is not a literal")]
91-
ParseLiteral(String),
65+
pub enum LiteralParseError {
66+
/// Failed to parse the input
67+
#[error(transparent)]
68+
#[diagnostic(transparent)]
69+
Parse(#[from] ParseErrors),
70+
/// Parsed successfully as an expression, but failed to construct a literal
71+
#[error("invalid literal: `{0}`")]
72+
InvalidLiteral(Expr),
9273
}
9374

94-
/// Errors in the CST -> AST transform, mostly well-formedness issues.
75+
/// Error from the CST -> AST transform
9576
#[derive(Debug, Error, Clone, PartialEq, Eq)]
9677
#[error("{kind}")]
9778
pub struct ToASTError {
@@ -487,7 +468,7 @@ pub enum InvalidIsError {
487468
WrongOp(cst::RelOp),
488469
}
489470

490-
/// Error from the CST parser.
471+
/// Error from the text -> CST parser
491472
#[derive(Clone, Debug, Error, PartialEq, Eq)]
492473
pub struct ToCSTError {
493474
err: OwnedRawParseError,
@@ -685,6 +666,10 @@ pub fn expected_to_string(expected: &[String], config: &ExpectedTokenConfig) ->
685666
}
686667

687668
/// Multiple parse errors.
669+
//
670+
// CAUTION: this type is publicly exported in `cedar-policy`.
671+
// Don't make fields `pub`, don't make breaking changes, and use caution when
672+
// adding public methods.
688673
#[derive(Clone, Debug, Default, PartialEq, Eq)]
689674
pub struct ParseErrors(pub Vec<ParseError>);
690675

@@ -696,13 +681,8 @@ impl ParseErrors {
696681
ParseErrors(Vec::new())
697682
}
698683

699-
/// Constructs a new, empty `ParseErrors` with the specified capacity.
700-
pub fn with_capacity(capacity: usize) -> Self {
701-
ParseErrors(Vec::with_capacity(capacity))
702-
}
703-
704684
/// Add an error to the `ParseErrors`
705-
pub(super) fn push(&mut self, err: impl Into<ParseError>) {
685+
pub(crate) fn push(&mut self, err: impl Into<ParseError>) {
706686
self.0.push(err.into());
707687
}
708688
}

cedar-policy-core/src/parser/unescape.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -132,9 +132,8 @@ impl std::fmt::Display for UnescapeError {
132132
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
133133
write!(
134134
f,
135-
"the input `{}` is not a valid escape: {:?}",
135+
"the input `{}` is not a valid escape",
136136
&self.input[self.range.clone()],
137-
&self.err
138137
)
139138
}
140139
}

cedar-policy/src/api.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,7 @@ pub use authorizer::Decision;
3232
use cedar_policy_core::ast;
3333
#[cfg(feature = "partial-eval")]
3434
use cedar_policy_core::ast::BorrowedRestrictedExpr;
35-
use cedar_policy_core::ast::{
36-
ContextCreationError, ExprConstructionError, Integer, RestrictedExprParseError,
37-
}; // `ContextCreationError` is unsuitable for `pub use` because it contains internal types like `RestrictedExpr`
35+
use cedar_policy_core::ast::{ContextCreationError, ExprConstructionError, Integer}; // `ContextCreationError` is unsuitable for `pub use` because it contains internal types like `RestrictedExpr`
3836
use cedar_policy_core::authorizer;
3937
use cedar_policy_core::entities::{ContextSchema, Dereference};
4038
use cedar_policy_core::est;
@@ -2927,7 +2925,7 @@ fn ip_extension_name() -> ast::Name {
29272925
}
29282926

29292927
impl FromStr for RestrictedExpression {
2930-
type Err = RestrictedExprParseError;
2928+
type Err = RestrictedExpressionParseError;
29312929

29322930
/// create a `RestrictedExpression` using Cedar syntax
29332931
fn from_str(expression: &str) -> Result<Self, Self::Err> {

cedar-policy/src/api/err.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,15 @@ use crate::EntityUid;
2121
use crate::PolicyId;
2222
use cedar_policy_core::ast;
2323
use cedar_policy_core::ast::Name;
24+
pub use cedar_policy_core::ast::RestrictedExpressionParseError;
2425
use cedar_policy_core::authorizer;
2526
use cedar_policy_core::est;
2627
pub use cedar_policy_core::evaluator::{evaluation_errors, EvaluationError};
2728
pub use cedar_policy_core::extensions::{
2829
extension_function_lookup_errors, ExtensionFunctionLookupError,
2930
};
3031
use cedar_policy_core::parser;
31-
pub use cedar_policy_core::parser::err::ParseErrors;
32+
pub use cedar_policy_core::parser::err::{ParseError, ParseErrors};
3233
pub use cedar_policy_validator::human_schema::SchemaWarning;
3334
pub use cedar_policy_validator::{
3435
TypeErrorKind, UnsupportedFeature, ValidationErrorKind, ValidationWarningKind,

0 commit comments

Comments
 (0)