cedar_policy_core/evaluator/
err.rs

1/*
2 * Copyright Cedar Contributors
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      https://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17use crate::ast::*;
18use crate::parser::Loc;
19use itertools::Itertools;
20use miette::{Diagnostic, LabeledSpan};
21use smol_str::SmolStr;
22use std::sync::Arc;
23use thiserror::Error;
24
25/// An error generated while evaluating an expression
26#[derive(Debug, PartialEq, Eq, Clone, Error)]
27#[error("{error_kind}")]
28pub struct EvaluationError {
29    /// The kind of error that occurred
30    error_kind: EvaluationErrorKind,
31    /// Optional advice on how to fix the error
32    advice: Option<String>,
33    /// Source location of the error. (This overrides other sources if present,
34    /// but if this is `None`, we'll check for location info in the
35    /// `.error_kind`.)
36    source_loc: Option<Loc>,
37}
38// How many attrs will we store in an error before cutting off for performance reasons
39const TOO_MANY_ATTRS: usize = 5;
40
41// custom impl of `Diagnostic`
42impl Diagnostic for EvaluationError {
43    fn help<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
44        match (self.error_kind.help(), self.advice.as_ref()) {
45            (Some(help), None) => Some(help),
46            (None, Some(advice)) => Some(Box::new(advice)),
47            (Some(help), Some(advice)) => Some(Box::new(format!("{help}; {advice}"))),
48            (None, None) => None,
49        }
50    }
51
52    fn source_code(&self) -> Option<&dyn miette::SourceCode> {
53        self.source_loc
54            .as_ref()
55            .map(|loc| &loc.src as &dyn miette::SourceCode)
56            .or_else(|| self.error_kind.source_code())
57    }
58
59    fn labels(&self) -> Option<Box<dyn Iterator<Item = miette::LabeledSpan> + '_>> {
60        self.source_loc
61            .as_ref()
62            .map(|loc| {
63                Box::new(std::iter::once(LabeledSpan::underline(loc.span)))
64                    as Box<dyn Iterator<Item = _>>
65            })
66            .or_else(|| self.error_kind.labels())
67    }
68
69    fn code<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
70        self.error_kind.code()
71    }
72
73    fn severity(&self) -> Option<miette::Severity> {
74        self.error_kind.severity()
75    }
76
77    fn url<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
78        self.error_kind.url()
79    }
80
81    fn diagnostic_source(&self) -> Option<&dyn Diagnostic> {
82        self.error_kind.diagnostic_source()
83    }
84
85    fn related<'a>(&'a self) -> Option<Box<dyn Iterator<Item = &'a dyn Diagnostic> + 'a>> {
86        self.error_kind.related()
87    }
88}
89
90impl EvaluationError {
91    /// Extract the kind of issue detected during evaluation
92    pub fn error_kind(&self) -> &EvaluationErrorKind {
93        &self.error_kind
94    }
95
96    /// Extract the source location of the error, if one is attached
97    pub fn source_loc(&self) -> Option<&Loc> {
98        self.source_loc.as_ref()
99    }
100
101    /// Extract the advice attached to the error, if any
102    pub fn advice(&self) -> Option<&str> {
103        self.advice.as_deref()
104    }
105
106    /// Set the advice field of an error
107    pub fn set_advice(&mut self, advice: String) {
108        self.advice = Some(advice);
109    }
110
111    /// Return the `EvaluationError`, but with the new `source_loc` (or `None`).
112    pub(crate) fn with_maybe_source_loc(self, source_loc: Option<Loc>) -> Self {
113        Self { source_loc, ..self }
114    }
115
116    /// Construct a [`EntityDoesNotExist`] error
117    pub(crate) fn entity_does_not_exist(euid: Arc<EntityUID>, source_loc: Option<Loc>) -> Self {
118        Self {
119            error_kind: EvaluationErrorKind::EntityDoesNotExist(euid),
120            advice: None,
121            source_loc,
122        }
123    }
124
125    /// Construct a [`EntityAttrDoesNotExist`] error
126    pub(crate) fn entity_attr_does_not_exist<'a>(
127        entity: Arc<EntityUID>,
128        attr: SmolStr,
129        source_loc: Option<Loc>,
130    ) -> Self {
131        Self {
132            error_kind: EvaluationErrorKind::EntityAttrDoesNotExist { entity, attr },
133            advice: None,
134            source_loc,
135        }
136    }
137
138    /// Construct a [`UnspecifiedEntityAccess`] error
139    pub(crate) fn unspecified_entity_access(attr: SmolStr, source_loc: Option<Loc>) -> Self {
140        Self {
141            error_kind: EvaluationErrorKind::UnspecifiedEntityAccess(attr),
142            advice: None,
143            source_loc,
144        }
145    }
146
147    /// Construct a [`RecordAttrDoesNotExist`] error
148    pub(crate) fn record_attr_does_not_exist<'a>(
149        attr: SmolStr,
150        available_attrs: impl IntoIterator<Item = &'a SmolStr>,
151        source_loc: Option<Loc>,
152    ) -> Self {
153        let alternatives = available_attrs
154            .into_iter()
155            .take(TOO_MANY_ATTRS)
156            .cloned()
157            .collect_vec();
158        Self {
159            error_kind: EvaluationErrorKind::RecordAttrDoesNotExist(attr, alternatives),
160            advice: None,
161            source_loc,
162        }
163    }
164
165    /// Construct a [`TypeError`] error
166    pub(crate) fn type_error(expected: Vec<Type>, actual: &Value) -> Self {
167        Self {
168            error_kind: EvaluationErrorKind::TypeError {
169                expected,
170                actual: actual.type_of(),
171            },
172            advice: None,
173            source_loc: actual.source_loc().cloned(),
174        }
175    }
176
177    pub(crate) fn type_error_single(expected: Type, actual: &Value) -> Self {
178        Self::type_error(vec![expected], actual)
179    }
180
181    /// Construct a [`TypeError`] error with the advice field set
182    pub(crate) fn type_error_with_advice(
183        expected: Vec<Type>,
184        actual: &Value,
185        advice: String,
186    ) -> Self {
187        Self {
188            error_kind: EvaluationErrorKind::TypeError {
189                expected,
190                actual: actual.type_of(),
191            },
192            advice: Some(advice),
193            source_loc: actual.source_loc().cloned(),
194        }
195    }
196
197    pub(crate) fn type_error_with_advice_single(
198        expected: Type,
199        actual: &Value,
200        advice: String,
201    ) -> Self {
202        Self::type_error_with_advice(vec![expected], actual, advice)
203    }
204
205    /// Construct a [`WrongNumArguments`] error
206    pub(crate) fn wrong_num_arguments(
207        function_name: Name,
208        expected: usize,
209        actual: usize,
210        source_loc: Option<Loc>,
211    ) -> Self {
212        Self {
213            error_kind: EvaluationErrorKind::WrongNumArguments {
214                function_name,
215                expected,
216                actual,
217            },
218            advice: None,
219            source_loc,
220        }
221    }
222
223    /// Construct a [`UnlinkedSlot`] error
224    pub(crate) fn unlinked_slot(id: SlotId, source_loc: Option<Loc>) -> Self {
225        Self {
226            error_kind: EvaluationErrorKind::UnlinkedSlot(id),
227            advice: None,
228            source_loc,
229        }
230    }
231
232    /// Construct a [`FailedExtensionFunctionApplication`] error
233    pub(crate) fn failed_extension_function_application(
234        extension_name: Name,
235        msg: String,
236        source_loc: Option<Loc>,
237    ) -> Self {
238        Self {
239            error_kind: EvaluationErrorKind::FailedExtensionFunctionApplication {
240                extension_name,
241                msg,
242            },
243            advice: None,
244            source_loc,
245        }
246    }
247
248    /// Construct a [`NonValue`] error
249    pub(crate) fn non_value(e: Expr) -> Self {
250        let source_loc = e.source_loc().cloned();
251        Self {
252            error_kind: EvaluationErrorKind::NonValue(e),
253            advice: Some("consider using the partial evaluation APIs".into()),
254            source_loc,
255        }
256    }
257
258    /// Construct a [`RecursionLimit`] error
259    #[cfg(not(target_arch = "wasm32"))]
260    pub(crate) fn recursion_limit(source_loc: Option<Loc>) -> Self {
261        Self {
262            error_kind: EvaluationErrorKind::RecursionLimit,
263            advice: None,
264            source_loc,
265        }
266    }
267
268    pub(crate) fn extension_function_lookup(
269        err: crate::extensions::ExtensionFunctionLookupError,
270        source_loc: Option<Loc>,
271    ) -> Self {
272        Self {
273            error_kind: err.into(),
274            advice: None,
275            source_loc,
276        }
277    }
278
279    pub(crate) fn integer_overflow(err: IntegerOverflowError, source_loc: Option<Loc>) -> Self {
280        Self {
281            error_kind: err.into(),
282            advice: None,
283            source_loc,
284        }
285    }
286}
287
288impl From<RestrictedExprError> for EvaluationError {
289    fn from(err: RestrictedExprError) -> Self {
290        Self {
291            error_kind: err.into(),
292            advice: None,
293            source_loc: None, // defer to the source information embedded in the `RestrictedExprError` and thus stored in `error_kind`
294        }
295    }
296}
297
298/// Enumeration of the possible errors that can occur during evaluation
299#[derive(Debug, PartialEq, Eq, Clone, Diagnostic, Error)]
300pub enum EvaluationErrorKind {
301    /// Tried to lookup this entity UID, but it didn't exist in the provided
302    /// entities
303    #[error("entity `{0}` does not exist")]
304    EntityDoesNotExist(Arc<EntityUID>),
305
306    /// Tried to get this attribute, but the specified entity didn't
307    /// have that attribute
308    #[error("`{}` does not have the attribute `{}`", &.entity, &.attr)]
309    EntityAttrDoesNotExist {
310        /// Entity that didn't have the attribute
311        entity: Arc<EntityUID>,
312        /// Name of the attribute it didn't have
313        attr: SmolStr,
314    },
315
316    /// Tried to access an attribute of an unspecified entity
317    #[error("cannot access attribute `{0}` of unspecified entity")]
318    UnspecifiedEntityAccess(SmolStr),
319
320    /// Tried to get an attribute of a (non-entity) record, but that record
321    /// didn't have that attribute
322    #[error("record does not have the attribute `{0}`")]
323    #[diagnostic(help("available attributes: {}", if .1.len() == TOO_MANY_ATTRS { format!("{:?} (truncated, more attributes may exist)", .1) } else { format!("{:?}", .1) } ))]
324    RecordAttrDoesNotExist(SmolStr, Vec<SmolStr>),
325
326    /// An error occurred when looking up an extension function
327    #[error(transparent)]
328    #[diagnostic(transparent)]
329    FailedExtensionFunctionLookup(#[from] crate::extensions::ExtensionFunctionLookupError),
330
331    /// Tried to evaluate an operation on values with incorrect types for that
332    /// operation
333    // INVARIANT `expected` must be non-empty
334    #[error("{}", pretty_type_error(expected, actual))]
335    TypeError {
336        /// Expected (one of) these types
337        expected: Vec<Type>,
338        /// Encountered this type instead
339        actual: Type,
340    },
341
342    /// Wrong number of arguments provided to an extension function
343    #[error("wrong number of arguments provided to extension function `{function_name}`: expected {expected}, got {actual}")]
344    WrongNumArguments {
345        /// arguments to this function
346        function_name: Name,
347        /// expected number of arguments
348        expected: usize,
349        /// actual number of arguments
350        actual: usize,
351    },
352
353    /// Overflow during an integer operation
354    #[error(transparent)]
355    #[diagnostic(transparent)]
356    IntegerOverflow(#[from] IntegerOverflowError),
357
358    /// Error with the use of "restricted" expressions
359    #[error(transparent)]
360    #[diagnostic(transparent)]
361    InvalidRestrictedExpression(#[from] RestrictedExprError),
362
363    /// Thrown when a policy is evaluated with a slot that is not linked to an
364    /// [`EntityUID`]
365    #[error("template slot `{0}` was not linked")]
366    UnlinkedSlot(SlotId),
367
368    /// Evaluation error thrown by an extension function
369    #[error("error while evaluating `{extension_name}` extension function: {msg}")]
370    FailedExtensionFunctionApplication {
371        /// Name of the extension throwing the error
372        extension_name: Name,
373        /// Error message from the extension
374        msg: String,
375    },
376
377    /// This error is raised if an expression contains unknowns and cannot be
378    /// reduced to a [`Value`]. In order to return partial results, use the
379    /// partial evaluation APIs instead.
380    #[error("the expression contains unknown(s): `{0}`")]
381    NonValue(Expr),
382
383    /// Maximum recursion limit reached for expression evaluation
384    #[error("recursion limit reached")]
385    RecursionLimit,
386}
387
388/// helper function for pretty-printing type errors
389/// INVARIANT: `expected` must have at least one value
390fn pretty_type_error(expected: &[Type], actual: &Type) -> String {
391    match expected.len() {
392        // PANIC SAFETY, `expected` is non-empty by invariant
393        #[allow(clippy::unreachable)]
394        0 => unreachable!("should expect at least one type"),
395        // PANIC SAFETY. `len` is 1 in this branch
396        #[allow(clippy::indexing_slicing)]
397        1 => format!("type error: expected {}, got {}", expected[0], actual),
398        _ => {
399            format!(
400                "type error: expected one of [{}], got {actual}",
401                expected.iter().join(", ")
402            )
403        }
404    }
405}
406
407#[derive(Debug, PartialEq, Eq, Clone, Diagnostic, Error)]
408pub enum IntegerOverflowError {
409    /// Overflow during a binary operation
410    #[error("integer overflow while attempting to {} the values `{arg1}` and `{arg2}`", match .op { BinaryOp::Add => "add", BinaryOp::Sub => "subtract", BinaryOp::Mul => "multiply", _ => "perform an operation on" })]
411    BinaryOp {
412        /// overflow while evaluating this operator
413        op: BinaryOp,
414        /// first argument to that operator
415        arg1: Value,
416        /// second argument to that operator
417        arg2: Value,
418    },
419
420    /// Overflow during a unary operation
421    #[error("integer overflow while attempting to {} the value `{arg}`", match .op { UnaryOp::Neg => "negate", _ => "perform an operation on" })]
422    UnaryOp {
423        /// overflow while evaluating this operator
424        op: UnaryOp,
425        /// argument to that operator
426        arg: Value,
427    },
428}
429
430/// Type alias for convenience
431pub type Result<T> = std::result::Result<T, EvaluationError>;