1use std::{borrow::Borrow, fmt, sync::Arc};
2
3use indoc::formatdoc;
4use serde::Serialize;
5use strum_macros::AsRefStr;
6
7use super::{
8 resource_block::Declaration,
9 rules::Rule,
10 sources::{Context, Source},
11 terms::{Operation, Symbol, Term},
12};
13
14pub type PolarResult<T> = Result<T, PolarError>;
15
16#[derive(Debug, Clone, Serialize)]
17pub enum ErrorKind {
18 Parse(ParseError),
19 Runtime(RuntimeError),
20 Operational(OperationalError),
21 Validation(ValidationError),
22}
23
24impl fmt::Display for ErrorKind {
25 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
26 match self {
27 Self::Parse(e) => write!(f, "{}", e),
28 Self::Runtime(e) => write!(f, "{}", e),
29 Self::Operational(e) => write!(f, "{}", e),
30 Self::Validation(e) => write!(f, "{}", e),
31 }
32 }
33}
34
35#[derive(Debug, Clone, Serialize)]
41#[serde(into = "FormattedPolarError")]
42pub struct PolarError(pub ErrorKind);
43
44impl std::error::Error for PolarError {}
45
46impl fmt::Display for PolarError {
47 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
48 write!(f, "{}", self.0)?;
49 if let Some(context) = self.get_context() {
50 write!(f, "{}", context)?;
51 }
52 Ok(())
53 }
54}
55
56impl PolarError {
57 pub fn kind(&self) -> String {
58 use ErrorKind::*;
59 match &self.0 {
60 Operational(o) => "OperationalError::".to_string() + o.as_ref(),
61 Parse(p) => "ParseError::".to_string() + p.kind.as_ref(),
62 Runtime(r) => "RuntimeError::".to_string() + r.as_ref(),
63 Validation(v) => "ValidationError::".to_string() + v.as_ref(),
64 }
65 }
66
67 pub fn get_context(&self) -> Option<Context> {
68 use ErrorKind::*;
69 use OperationalError::*;
70 use ParseErrorKind::*;
71 use RuntimeError::*;
72 use ValidationError::*;
73
74 match &self.0 {
75 Parse(e) => match &e.kind {
76 DuplicateKey { key: token, loc }
79 | ExtraToken { token, loc }
80 | IntegerOverflow { token, loc }
81 | InvalidFloat { token, loc }
82 | ReservedWord { token, loc }
83 | UnrecognizedToken { token, loc } => {
84 Some(Context::new(e.source.clone(), *loc, loc + token.len()))
85 }
86
87 InvalidTokenCharacter { loc, .. }
90 | InvalidToken { loc }
91 | UnrecognizedEOF { loc } => Some(Context::new(e.source.clone(), *loc, *loc)),
92
93 WrongValueType { term, .. } => term.parsed_context().cloned(),
95 },
96
97 Runtime(e) => match e {
98 Application { term, .. } => term.as_ref().and_then(Term::parsed_context).cloned(),
100
101 ArithmeticError { term }
103 | TypeError { term, .. }
104 | UnhandledPartial { term, .. }
105 | Unsupported { term, .. } => term.parsed_context().cloned(),
106
107 StackOverflow { .. }
109 | QueryTimeout { .. }
110 | IncompatibleBindings { .. }
111 | DataFilteringFieldMissing { .. }
112 | DataFilteringUnsupportedOp { .. }
113 | InvalidRegistration { .. }
114 | QueryForUndefinedRule { .. }
115 | MultipleLoadError => None,
116 },
117
118 Validation(e) => match e {
119 ResourceBlock { term, .. }
121 | SingletonVariable { term, .. }
122 | UndefinedRuleCall { term }
123 | DuplicateResourceBlockDeclaration {
124 declaration: term, ..
125 }
126 | UnregisteredClass { term, .. } => term.parsed_context().cloned(),
127
128 InvalidRule { rule, .. }
130 | InvalidRuleType {
131 rule_type: rule, ..
132 } => rule.parsed_context().cloned(),
133
134 MissingRequiredRule { rule_type } => {
136 if rule_type.name.0 == "has_relation" {
137 rule_type.parsed_context().cloned()
138 } else {
139 None
142 }
143 }
144
145 FileLoading {
147 filename, contents, ..
148 } => {
149 let source = Arc::new(Source::new_with_name(filename, contents));
150 Some(Context::new(source, 0, 0))
151 }
152 },
153
154 Operational(e) => match e {
155 UnexpectedValue { received, .. } => received.parsed_context().cloned(),
157 InvalidState { .. } | Serialization { .. } | Unknown => None,
159 },
160 }
161 }
162}
163
164#[cfg(test)]
165impl PolarError {
166 pub fn unwrap_parse(self) -> ParseErrorKind {
167 match self.0 {
168 ErrorKind::Parse(ParseError { kind, .. }) => kind,
169 e => panic!("Expected ErrorKind::Parse; was: {}", e),
170 }
171 }
172
173 pub fn unwrap_runtime(self) -> RuntimeError {
174 match self.0 {
175 ErrorKind::Runtime(e) => e,
176 e => panic!("Expected ErrorKind::Runtime; was: {}", e),
177 }
178 }
179
180 pub fn unwrap_validation(self) -> ValidationError {
181 match self.0 {
182 ErrorKind::Validation(e) => e,
183 e => panic!("Expected ErrorKind::Validation; was: {}", e),
184 }
185 }
186}
187
188#[derive(Clone, Serialize)]
189pub struct FormattedPolarError {
190 pub kind: ErrorKind,
191 pub formatted: String,
192}
193
194impl From<PolarError> for FormattedPolarError {
195 fn from(other: PolarError) -> Self {
196 Self {
197 formatted: other.to_string(),
198 kind: other.0,
199 }
200 }
201}
202
203#[derive(Clone, Serialize)]
204#[serde(transparent)]
205pub struct ParseError {
206 pub kind: ParseErrorKind,
207 #[serde(skip_serializing)]
208 pub source: Arc<Source>,
209}
210
211impl fmt::Debug for ParseError {
213 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
214 f.debug_struct("ParseError")
215 .field("kind", &self.kind)
216 .finish()
217 }
218}
219
220impl fmt::Display for ParseError {
221 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
222 write!(f, "{}", self.kind)
223 }
224}
225
226impl From<ParseError> for PolarError {
227 fn from(err: ParseError) -> Self {
228 Self(ErrorKind::Parse(err))
229 }
230}
231
232#[derive(AsRefStr, Clone, Debug, Serialize)]
233pub enum ParseErrorKind {
234 IntegerOverflow {
235 token: String,
236 loc: usize,
237 },
238 InvalidTokenCharacter {
239 token: String,
240 c: char,
241 loc: usize,
242 },
243 InvalidToken {
244 loc: usize,
245 },
246 #[allow(clippy::upper_case_acronyms)]
247 UnrecognizedEOF {
248 loc: usize,
249 },
250 UnrecognizedToken {
251 token: String,
252 loc: usize,
253 },
254 ExtraToken {
255 token: String,
256 loc: usize,
257 },
258 ReservedWord {
259 token: String,
260 loc: usize,
261 },
262 InvalidFloat {
263 token: String,
264 loc: usize,
265 },
266 WrongValueType {
267 loc: usize,
268 term: Term,
269 expected: String,
270 },
271 DuplicateKey {
272 loc: usize,
273 key: String,
274 },
275}
276
277impl fmt::Display for ParseErrorKind {
278 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
279 match self {
280 Self::IntegerOverflow { token, .. } => {
281 write!(f, "'{}' caused an integer overflow", token.escape_debug())
282 }
283 Self::InvalidTokenCharacter { token, c, .. } => write!(
284 f,
285 "'{}' is not a valid character. Found in {}",
286 c.escape_debug(),
287 token.escape_debug()
288 ),
289 Self::InvalidToken { .. } => write!(f, "found an unexpected sequence of characters"),
290 Self::UnrecognizedEOF { .. } => write!(
291 f,
292 "hit the end of the file unexpectedly. Did you forget a semi-colon"
293 ),
294 Self::UnrecognizedToken { token, .. } => write!(
295 f,
296 "did not expect to find the token '{}'",
297 token.escape_debug()
298 ),
299 Self::ExtraToken { token, .. } => write!(
300 f,
301 "did not expect to find the token '{}'",
302 token.escape_debug()
303 ),
304 Self::ReservedWord { token, .. } => write!(
305 f,
306 "{} is a reserved Polar word and cannot be used here",
307 token.escape_debug()
308 ),
309 Self::InvalidFloat { token, .. } => write!(
310 f,
311 "{} was parsed as a float, but is invalid",
312 token.escape_debug()
313 ),
314 Self::WrongValueType { term, expected, .. } => {
315 write!(f, "Wrong value type: {}. Expected a {}", term, expected)
316 }
317 Self::DuplicateKey { key, .. } => {
318 write!(f, "Duplicate key: {}", key)
319 }
320 }
321 }
322}
323
324#[derive(AsRefStr, Clone, Debug, Serialize)]
325pub enum RuntimeError {
326 ArithmeticError {
327 term: Term,
329 },
330 Unsupported {
331 msg: String,
332 term: Term,
334 },
335 TypeError {
336 msg: String,
337 stack_trace: String,
338 term: Term,
340 },
341 StackOverflow {
342 msg: String,
343 },
344 QueryTimeout {
345 elapsed: u64,
346 timeout: u64,
347 },
348 Application {
349 msg: String,
350 stack_trace: String,
351 term: Option<Term>,
353 },
354 IncompatibleBindings {
355 msg: String,
356 },
357 UnhandledPartial {
358 var: Symbol,
359 term: Term,
361 },
362 DataFilteringFieldMissing {
363 var_type: String,
364 field: String,
365 },
366 DataFilteringUnsupportedOp {
367 operation: Operation,
368 },
369 InvalidRegistration {
371 sym: Symbol,
372 msg: String,
373 },
374 MultipleLoadError,
375 QueryForUndefinedRule {
378 name: String,
379 },
380}
381
382impl From<RuntimeError> for PolarError {
383 fn from(err: RuntimeError) -> Self {
384 Self(ErrorKind::Runtime(err))
385 }
386}
387
388impl fmt::Display for RuntimeError {
389 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
390 match self {
391 Self::ArithmeticError { term } => write!(f, "Arithmetic error: {}", term),
392 Self::Unsupported { msg, .. } => write!(f, "Not supported: {}", msg),
393 Self::TypeError {
394 msg, stack_trace, ..
395 } => {
396 writeln!(f, "{}", stack_trace)?;
397 write!(f, "Type error: {}", msg)
398 }
399 Self::StackOverflow { msg } => {
400 write!(f, "{}", msg)
401 }
402 Self::QueryTimeout { elapsed, timeout } => write!(f, "Query timeout: Query running for {}ms, which exceeds the timeout of {}ms. To disable timeouts, set the POLAR_TIMEOUT_MS environment variable to 0.", elapsed, timeout),
403 Self::Application {
404 msg, stack_trace, ..
405 } => {
406 writeln!(f, "{}", stack_trace)?;
407 write!(f, "Application error: {}", msg)
408 }
409 Self::IncompatibleBindings { msg } => {
410 write!(f, "Attempted binding was incompatible: {}", msg)
411 }
412 Self::UnhandledPartial { var, term } => {
413 write!(
414 f,
415 "Found an unhandled partial in the query result: {var}
416
417This can happen when there is a variable used inside a rule
418which is not related to any of the query inputs.
419
420For example: f(_x) if y.a = 1 and y.b = 2;
421
422In this example, the variable `y` is constrained by `a = 1 and b = 2`,
423but we cannot resolve these constraints without further information.
424
425The unhandled partial is for variable {var}.
426The expression is: {expr}
427",
428 var = var,
429 expr = term,
430 )
431 }
432 Self::DataFilteringFieldMissing { var_type, field } => {
433 let msg = formatdoc!(
434 r#"Unregistered field or relation: {var_type}.{field}
435
436 Please include `{field}` in the `fields` parameter of your
437 `register_class` call for {var_type}. For example, in Python:
438
439 oso.register_class({var_type}, fields={{
440 "{field}": <type or relation>
441 }})
442
443 For more information please refer to our documentation:
444 https://docs.osohq.com/guides/data_filtering.html
445 "#,
446 var_type = var_type,
447 field = field
448 );
449 write!(f, "{}", msg)
450 }
451 Self::DataFilteringUnsupportedOp { operation } => {
452 let msg = formatdoc!(
453 r#"Unsupported operation:
454 {:?}/{}
455 in the expression:
456 {}
457 in a data filtering query.
458
459 This operation is not currently supported for data filtering.
460 For more information please refer to our documentation:
461 https://docs.osohq.com/guides/data_filtering.html
462 "#,
463 operation.operator,
464 operation.args.len(),
465 operation
466 );
467 write!(f, "{}", msg)
468 }
469 Self::InvalidRegistration { sym, msg } => {
470 write!(f, "Invalid attempt to register '{}': {}", sym, msg)
471 }
472 Self::MultipleLoadError => write!(f, "Cannot load additional Polar code -- all Polar code must be loaded at the same time."),
473 Self::QueryForUndefinedRule { name } => write!(f, "Query for undefined rule `{}`", name),
474 }
475 }
476}
477
478#[derive(AsRefStr, Clone, Debug, Serialize)]
479pub enum OperationalError {
480 InvalidState { msg: String },
482 Serialization { msg: String },
484 UnexpectedValue {
487 expected: &'static str,
488 received: Term,
489 },
490 Unknown,
492}
493
494impl From<OperationalError> for PolarError {
495 fn from(err: OperationalError) -> Self {
496 Self(ErrorKind::Operational(err))
497 }
498}
499
500impl fmt::Display for OperationalError {
501 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
502 match self {
503 Self::InvalidState { msg } => write!(f, "Invalid state: {}", msg),
504 Self::Serialization { msg } => write!(f, "Serialization error: {}", msg),
505 Self::UnexpectedValue { expected, received } => write!(
506 f,
507 "Unexpected value.\n Expected: {expected}\n Received: {received}"
508 ),
509 Self::Unknown => write!(
510 f,
511 "We hit an unexpected error.\n\
512 Please submit a bug report at <https://github.com/osohq/oso/issues>"
513 ),
514 }
515 }
516}
517
518#[derive(AsRefStr, Clone, Debug, Serialize)]
519pub enum ValidationError {
520 FileLoading {
521 #[serde(skip_serializing)]
522 filename: String,
523 #[serde(skip_serializing)]
524 contents: String,
525 msg: String,
526 },
527 MissingRequiredRule {
528 rule_type: Rule,
529 },
530 InvalidRule {
531 rule: Rule,
533 msg: String,
534 },
535 InvalidRuleType {
536 rule_type: Rule,
538 msg: String,
539 },
540 UndefinedRuleCall {
543 term: Term,
545 },
546 ResourceBlock {
547 term: Term,
549 msg: String,
550 },
555 SingletonVariable {
556 term: Term,
558 },
559 UnregisteredClass {
560 term: Term,
562 },
563 DuplicateResourceBlockDeclaration {
564 resource: Term,
566 declaration: Term,
568 existing: Declaration,
569 new: Declaration,
570 },
571}
572
573impl From<ValidationError> for PolarError {
574 fn from(err: ValidationError) -> Self {
575 Self(ErrorKind::Validation(err))
576 }
577}
578
579impl fmt::Display for ValidationError {
580 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
581 match self {
582 Self::FileLoading { msg, .. } => write!(f, "Problem loading file: {}", msg),
583 Self::InvalidRule { rule, msg } => {
584 write!(f, "Invalid rule: {} {}", rule, msg)
585 }
586 Self::InvalidRuleType { rule_type, msg } => {
587 write!(f, "Invalid rule type: {}\n\t{}", rule_type, msg)
588 }
589 Self::UndefinedRuleCall { term } => {
590 write!(f, "Call to undefined rule: {}", term)
591 }
592 Self::MissingRequiredRule { rule_type } => {
593 write!(f, "Missing implementation for required rule {}", rule_type)
594 }
595 Self::ResourceBlock { msg, .. } => {
596 write!(f, "{}", msg)
597 }
598 Self::SingletonVariable { term } => {
599 write!(f, "Singleton variable {term} is unused or undefined; try renaming to _{term} or _", term=term)
600 }
601 Self::UnregisteredClass { term } => {
602 write!(f, "Unregistered class: {}", term)
603 }
604 Self::DuplicateResourceBlockDeclaration {
605 resource,
606 declaration,
607 existing,
608 new,
609 } => {
610 write!(
611 f,
612 "Cannot overwrite existing {} declaration {} in resource {} with {}",
613 existing, declaration, resource, new
614 )
615 }
616 }
617 }
618}
619
620pub(crate) fn invalid_state<T, U>(msg: T) -> PolarResult<U>
621where
622 T: AsRef<str>,
623{
624 let msg = msg.as_ref().into();
625 Err(OperationalError::InvalidState { msg }.into())
626}
627
628pub(crate) fn unexpected_value<T>(expected: &'static str, received: Term) -> PolarResult<T> {
629 Err(OperationalError::UnexpectedValue { expected, received }.into())
630}
631
632pub(crate) fn unsupported<T, U, V>(msg: T, term: U) -> PolarResult<V>
633where
634 T: AsRef<str>,
635 U: Borrow<Term>,
636{
637 let msg = msg.as_ref().into();
638 let term = term.borrow().clone();
639 Err(RuntimeError::Unsupported { msg, term }.into())
640}
641
642pub(crate) fn df_unsupported_op<T>(operation: Operation) -> PolarResult<T> {
643 Err(RuntimeError::DataFilteringUnsupportedOp { operation }.into())
644}
645
646pub(crate) fn df_field_missing<T, U, V>(var_type: T, field: U) -> PolarResult<V>
647where
648 T: AsRef<str>,
649 U: AsRef<str>,
650{
651 Err(RuntimeError::DataFilteringFieldMissing {
652 var_type: var_type.as_ref().into(),
653 field: field.as_ref().into(),
654 }
655 .into())
656}