Skip to content

Commit

Permalink
Context for FileLoading errors (#1420)
Browse files Browse the repository at this point in the history
  • Loading branch information
gj authored Nov 30, 2021
1 parent 6bd797e commit ba84a60
Show file tree
Hide file tree
Showing 14 changed files with 92 additions and 67 deletions.
2 changes: 0 additions & 2 deletions languages/python/oso/polar/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
UnrecognizedToken,
PolarTypeError,
StackOverflowError,
FileLoadingError,
PolarRuntimeError,
UnknownError,
OperationalError,
Expand Down Expand Up @@ -70,7 +69,6 @@ def _runtime_error(subkind, message, details):
"Unsupported": UnsupportedError(message, details),
"TypeError": PolarTypeError(message, details),
"StackOverflow": StackOverflowError(message, details),
"FileLoading": FileLoadingError(message, details),
}
return runtime_errors.get(subkind, PolarRuntimeError(message, details))

Expand Down
6 changes: 0 additions & 6 deletions languages/python/oso/polar/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,12 +57,6 @@ class StackOverflowError(PolarRuntimeError):
pass


class FileLoadingError(PolarRuntimeError):
"""Error loading a Polar file"""

pass


class UnregisteredClassError(PolarRuntimeError):
"""Raised on attempts to reference unregistered Python classes from a Polar policy."""

Expand Down
4 changes: 2 additions & 2 deletions languages/python/oso/tests/test_polar.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,14 +49,14 @@ def test_data_conversions(polar, qvar):
def test_load_function(polar, query, qvar):
"""Make sure the load function works."""
filename = Path(__file__).parent / "test_file.polar"
with pytest.raises(exceptions.PolarRuntimeError) as e:
with pytest.raises(exceptions.ValidationError) as e:
polar.load_files([filename, filename])
assert str(e.value).startswith(
f"Problem loading file: File {filename} has already been loaded."
)

renamed = Path(__file__).parent / "test_file_renamed.polar"
with pytest.raises(exceptions.PolarRuntimeError) as e:
with pytest.raises(exceptions.ValidationError) as e:
polar.load_files([filename, renamed])
expected = f"Problem loading file: A file with the same contents as {renamed} named {filename} has already been loaded."
assert str(e.value).startswith(expected)
Expand Down
1 change: 0 additions & 1 deletion languages/ruby/lib/oso/polar/errors.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ class PolarRuntimeError < Error; end
class UnsupportedError < PolarRuntimeError; end
class PolarTypeError < PolarRuntimeError; end
class StackOverflowError < PolarRuntimeError; end
class FileLoadingError < PolarRuntimeError; end

# Errors originating from this side of the FFI boundary.

Expand Down
4 changes: 1 addition & 3 deletions languages/ruby/lib/oso/polar/ffi/error.rb
Original file line number Diff line number Diff line change
Expand Up @@ -74,16 +74,14 @@ def self.get(error_str, enrich_message) # rubocop:disable Metrics/AbcSize, Metri
# @param msg [String]
# @param details [Hash<String, Object>]
# @return [::Oso::Polar::PolarRuntimeError] the object converted into the expected format.
private_class_method def self.runtime_error(kind, msg:, details:) # rubocop:disable Metrics/MethodLength
private_class_method def self.runtime_error(kind, msg:, details:)
case kind
when 'Unsupported'
::Oso::Polar::UnsupportedError.new(msg, details: details)
when 'TypeError'
::Oso::Polar::PolarTypeError.new(msg, details: details)
when 'StackOverflow'
::Oso::Polar::StackOverflowError.new(msg, details: details)
when 'FileLoading'
::Oso::Polar::FileLoadingError.new(msg, details: details)
else
::Oso::Polar::PolarRuntimeError.new(msg, details: details)
end
Expand Down
4 changes: 2 additions & 2 deletions languages/ruby/spec/oso/polar/polar_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -131,8 +131,8 @@ def initialize
# in the core. We should test this once in the core.
it 'errors if file is already loaded' do
expect { subject.load_files([test_file, test_file]) }.to raise_error do |e|
expect(e).to be_an Oso::Polar::FileLoadingError
expect(e.message).to eq("Problem loading file: File #{test_file} has already been loaded.")
expect(e).to be_an Oso::Polar::ValidationError
expect(e.message).to start_with "Problem loading file: File #{test_file} has already been loaded."
end
end

Expand Down
13 changes: 5 additions & 8 deletions languages/rust/oso/tests/test_polar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -245,15 +245,12 @@ fn test_already_loaded_file_error() -> oso::Result<()> {
let err = oso.oso.load_files(vec![&path, &path]).unwrap_err();

assert!(
matches!(&err,
OsoError::Polar(polar_error::PolarError {
kind:
polar_error::ErrorKind::Runtime(polar_error::RuntimeError::FileLoading { .. }),
matches!(&err, OsoError::Polar(polar_error::PolarError {
kind: polar_error::ErrorKind::Validation(polar_error::ValidationError::FileLoading { .. }),
..
})
if err.to_string() == format!("Problem loading file: File {} has already been loaded.", path.to_string_lossy())),
"Error was {:?}",
&err
}) if err.to_string().starts_with(&format!("Problem loading file: File {} has already been loaded.", path.to_string_lossy()))),
"Error was {}",
err
);

Ok(())
Expand Down
7 changes: 3 additions & 4 deletions polar-core/src/diagnostic/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,13 @@ impl Diagnostic {
/// second `ResourceBlock` error when rewriting a shorthand rule involving the relation.
pub fn is_unrecoverable(&self) -> bool {
use super::error::{
ErrorKind::{Parse, Runtime, Validation},
RuntimeError::FileLoading,
ValidationError::ResourceBlock,
ErrorKind::{Parse, Validation},
ValidationError::{FileLoading, ResourceBlock},
};
matches!(
self,
Diagnostic::Error(PolarError {
kind: Parse(_) | Runtime(FileLoading { .. }) | Validation(ResourceBlock { .. }),
kind: Parse(_) | Validation(FileLoading { .. }) | Validation(ResourceBlock { .. }),
..
})
)
Expand Down
22 changes: 14 additions & 8 deletions polar-core/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ impl PolarError {
Parse(DuplicateKey { .. }) => "ParseError::DuplicateKey",
Runtime(Application { .. }) => "RuntimeError::Application",
Runtime(ArithmeticError { .. }) => "RuntimeError::ArithmeticError",
Runtime(FileLoading { .. }) => "RuntimeError::FileLoading",
Runtime(IncompatibleBindings { .. }) => "RuntimeError::IncompatibleBindings",
Runtime(QueryTimeout { .. }) => "RuntimeError::QueryTimeout",
Runtime(StackOverflow { .. }) => "RuntimeError::StackOverflow",
Expand All @@ -54,8 +53,10 @@ impl PolarError {
}
Runtime(InvalidRegistration { .. }) => "RuntimeError::InvalidRegistration",
Runtime(InvalidState { .. }) => "RuntimeError::InvalidState",
Runtime(MultipleLoadError) => "RuntimeError::MultipleLoadError",
Operational(Serialization { .. }) => "OperationalError::Serialization",
Operational(Unknown) => "OperationalError::Unknown",
Validation(FileLoading { .. }) => "ValidationError::FileLoading",
Validation(InvalidRule { .. }) => "ValidationError::InvalidRule",
Validation(InvalidRuleType { .. }) => "ValidationError::InvalidRuleType",
Validation(ResourceBlock { .. }) => "ValidationError::ResourceBlock",
Expand Down Expand Up @@ -273,10 +274,6 @@ pub enum RuntimeError {
/// Option<Term> where the error arose, tracked for lexical context.
term: Option<Term>,
},
// TODO(gj): consider moving to ValidationError.
FileLoading {
msg: String,
},
IncompatibleBindings {
msg: String,
},
Expand All @@ -301,6 +298,7 @@ pub enum RuntimeError {
InvalidState {
msg: String,
},
MultipleLoadError,
}

impl RuntimeError {
Expand All @@ -323,12 +321,12 @@ impl RuntimeError {
// These errors never have context.
StackOverflow { .. }
| QueryTimeout { .. }
| FileLoading { .. }
| IncompatibleBindings { .. }
| DataFilteringFieldMissing { .. }
| DataFilteringUnsupportedOp { .. }
| InvalidRegistration { .. }
| InvalidState { .. } => None,
| InvalidState { .. }
| MultipleLoadError => None,
};

let context = context.map(|(span, source)| Context {
Expand Down Expand Up @@ -368,7 +366,6 @@ impl fmt::Display for RuntimeError {
writeln!(f, "{}", stack_trace)?;
write!(f, "Application error: {}", msg)
}
Self::FileLoading { msg } => write!(f, "Problem loading file: {}", msg),
Self::IncompatibleBindings { msg } => {
write!(f, "Attempted binding was incompatible: {}", msg)
}
Expand Down Expand Up @@ -429,6 +426,7 @@ The expression is: {expr}
// TODO(gj): move this back to `OperationalError` during The Next Great Diagnostic
// Refactor.
Self::InvalidState { msg } => write!(f, "Invalid state: {}", msg),
Self::MultipleLoadError => write!(f, "Cannot load additional Polar code -- all Polar code must be loaded at the same time."),
}
}
}
Expand Down Expand Up @@ -467,6 +465,10 @@ impl fmt::Display for OperationalError {

#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ValidationError {
FileLoading {
source: Source,
msg: String,
},
MissingRequiredRule {
rule_type: Rule,
},
Expand Down Expand Up @@ -541,6 +543,9 @@ impl ValidationError {
None
}
}

// These errors always pertain to a specific file but not to a specific place therein.
FileLoading { source, .. } => Some(((0, 0), source.to_owned())),
};

let context = context.map(|(span, source)| Context {
Expand All @@ -558,6 +563,7 @@ impl ValidationError {
impl fmt::Display for ValidationError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::FileLoading { msg, .. } => write!(f, "Problem loading file: {}", msg),
Self::InvalidRule { rule, msg } => {
write!(f, "Invalid rule: {} {}", rule, msg)
}
Expand Down
22 changes: 11 additions & 11 deletions polar-core/src/kb.rs
Original file line number Diff line number Diff line change
Expand Up @@ -664,26 +664,29 @@ impl KnowledgeBase {
self.resource_blocks.clear();
}

fn check_file(&self, src: &str, filename: &str) -> Result<(), RuntimeError> {
fn check_file(&self, src: &str, filename: &str) -> Result<(), ValidationError> {
match (
self.loaded_content.get(src),
self.loaded_files.get(filename).is_some(),
) {
(Some(other_file), true) if other_file == filename => {
return Err(RuntimeError::FileLoading {
return Err(ValidationError::FileLoading {
source: Source::new(Some(filename), src),
msg: format!("File {} has already been loaded.", filename),
})
}
(_, true) => {
return Err(RuntimeError::FileLoading {
return Err(ValidationError::FileLoading {
source: Source::new(Some(filename), src),
msg: format!(
"A file with the name {}, but different contents has already been loaded.",
filename
),
})
}
(Some(other_file), _) => {
return Err(RuntimeError::FileLoading {
return Err(ValidationError::FileLoading {
source: Source::new(Some(filename), src),
msg: format!(
"A file with the same contents as {} named {} has already been loaded.",
filename, other_file
Expand Down Expand Up @@ -878,10 +881,7 @@ impl KnowledgeBase {
mod tests {
use super::*;

use crate::error::{
ErrorKind::{Runtime, Validation},
PolarError,
};
use crate::error::{ErrorKind::Validation, PolarError};

#[test]
/// Test validation implemented in `check_file()`.
Expand All @@ -900,7 +900,7 @@ mod tests {
// Cannot load source1 a second time.
let msg = match kb.add_source(source1).unwrap_err() {
PolarError {
kind: Runtime(RuntimeError::FileLoading { msg }),
kind: Validation(ValidationError::FileLoading { msg, .. }),
..
} => msg,
e => panic!("{}", e),
Expand All @@ -914,7 +914,7 @@ mod tests {
};
let msg = match kb.add_source(source2).unwrap_err() {
PolarError {
kind: Runtime(RuntimeError::FileLoading { msg }),
kind: Validation(ValidationError::FileLoading { msg, .. }),
..
} => msg,
e => panic!("{}", e),
Expand All @@ -935,7 +935,7 @@ mod tests {
};
let msg = match kb.add_source(source3).unwrap_err() {
PolarError {
kind: Runtime(RuntimeError::FileLoading { msg }),
kind: Validation(ValidationError::FileLoading { msg, .. }),
..
} => msg,
e => panic!("{}", e),
Expand Down
35 changes: 16 additions & 19 deletions polar-core/src/polar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,6 @@ impl Default for Polar {
}
}

const MULTIPLE_LOAD_ERROR_MSG: &str =
"Cannot load additional Polar code -- all Polar code must be loaded at the same time.";

impl Polar {
pub fn new() -> Self {
// TODO(@gkaemmer): pulling this from an environment variable is a hack
Expand Down Expand Up @@ -185,8 +182,7 @@ impl Polar {
pub fn load(&self, sources: Vec<Source>) -> PolarResult<()> {
if let Ok(kb) = self.kb.read() {
if kb.has_rules() {
let msg = MULTIPLE_LOAD_ERROR_MSG.to_owned();
return Err(RuntimeError::FileLoading { msg }.with_context(&*kb));
return Err(RuntimeError::MultipleLoadError.with_context(&*kb));
}
}

Expand Down Expand Up @@ -311,7 +307,10 @@ impl Polar {
#[cfg(test)]
mod tests {
use super::*;
use crate::error::{ErrorKind::Runtime, PolarError};
use crate::error::{
ErrorKind::{Runtime, Validation},
PolarError,
};

#[test]
fn can_load_and_query() {
Expand All @@ -333,24 +332,22 @@ mod tests {
polar.load(vec![source.clone()]).unwrap();

// Loading twice is not.
let msg = match polar.load(vec![source]).unwrap_err() {
assert!(matches!(
polar.load(vec![source.clone()]).unwrap_err(),
PolarError {
kind: Runtime(RuntimeError::FileLoading { msg }),
kind: Runtime(RuntimeError::MultipleLoadError),
..
} => msg,
e => panic!("{}", e),
};
assert_eq!(msg, MULTIPLE_LOAD_ERROR_MSG);
}
));

// Even with load_str().
let msg = match polar.load_str(src).unwrap_err() {
assert!(matches!(
polar.load(vec![source]).unwrap_err(),
PolarError {
kind: Runtime(RuntimeError::FileLoading { msg }),
kind: Runtime(RuntimeError::MultipleLoadError),
..
} => msg,
e => panic!("{}", e),
};
assert_eq!(msg, MULTIPLE_LOAD_ERROR_MSG);
}
));
}

#[test]
Expand All @@ -363,7 +360,7 @@ mod tests {

let msg = match polar.load(vec![source.clone(), source]).unwrap_err() {
PolarError {
kind: Runtime(RuntimeError::FileLoading { msg }),
kind: Validation(ValidationError::FileLoading { msg, .. }),
..
} => msg,
e => panic!("{}", e),
Expand Down
9 changes: 9 additions & 0 deletions polar-core/src/sources.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,15 @@ pub struct Source {
pub src: String,
}

impl Source {
pub(crate) fn new(filename: Option<&str>, src: &str) -> Self {
Self {
filename: filename.map(Into::into),
src: src.into(),
}
}
}

pub struct Sources {
/// Map from term ID to `Source`.
sources: HashMap<u64, Source>,
Expand Down
Loading

1 comment on commit ba84a60

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rust Benchmark

Benchmark suite Current: ba84a60 Previous: 6bd797e Ratio
rust_get_attribute 71034 ns/iter (± 18018) 80437 ns/iter (± 11428) 0.88
n_plus_one/100 3271991 ns/iter (± 63417) 3657532 ns/iter (± 198068) 0.89
n_plus_one/500 15893841 ns/iter (± 335006) 17975566 ns/iter (± 1061565) 0.88
n_plus_one/1000 31232576 ns/iter (± 756400) 35031560 ns/iter (± 1648396) 0.89
unify_once 979 ns/iter (± 56) 1047 ns/iter (± 108) 0.94
unify_twice 2611 ns/iter (± 80) 2777 ns/iter (± 537) 0.94
many_rules 63008 ns/iter (± 1940) 68660 ns/iter (± 4089) 0.92
fib/5 589742 ns/iter (± 11942) 635475 ns/iter (± 33618) 0.93
prime/3 27581 ns/iter (± 1353) 29251 ns/iter (± 3790) 0.94
prime/23 27676 ns/iter (± 1298) 28869 ns/iter (± 3724) 0.96
prime/43 27681 ns/iter (± 1370) 29472 ns/iter (± 1910) 0.94
prime/83 27827 ns/iter (± 1414) 31016 ns/iter (± 2743) 0.90
prime/255 26196 ns/iter (± 1157) 27806 ns/iter (± 1678) 0.94
indexed/100 6020 ns/iter (± 691) 6947 ns/iter (± 1418) 0.87
indexed/500 7513 ns/iter (± 1975) 8143 ns/iter (± 2924) 0.92
indexed/1000 9158 ns/iter (± 363) 10700 ns/iter (± 9685) 0.86
indexed/10000 21750 ns/iter (± 2350) 29400 ns/iter (± 187714) 0.74
not 6560 ns/iter (± 196) 7475 ns/iter (± 766) 0.88
double_not 14115 ns/iter (± 430) 16346 ns/iter (± 1091) 0.86
De_Morgan_not 8945 ns/iter (± 295) 10494 ns/iter (± 1750) 0.85
load_policy 1243253 ns/iter (± 15817) 1337469 ns/iter (± 86805) 0.93
partial_and/1 57831 ns/iter (± 2478) 65083 ns/iter (± 15655) 0.89
partial_and/5 215544 ns/iter (± 6868) 240367 ns/iter (± 21686) 0.90
partial_and/10 420752 ns/iter (± 12967) 454818 ns/iter (± 30577) 0.93
partial_and/20 857665 ns/iter (± 16190) 934172 ns/iter (± 43251) 0.92
partial_and/40 1773014 ns/iter (± 25294) 1989971 ns/iter (± 82718) 0.89
partial_and/80 3798317 ns/iter (± 73371) 4303979 ns/iter (± 185200) 0.88
partial_and/100 4937730 ns/iter (± 60245) 5644073 ns/iter (± 245319) 0.87
partial_rule_depth/1 176321 ns/iter (± 8038) 195102 ns/iter (± 14005) 0.90
partial_rule_depth/5 543584 ns/iter (± 12286) 601658 ns/iter (± 31954) 0.90
partial_rule_depth/10 1110231 ns/iter (± 17933) 1223982 ns/iter (± 66878) 0.91
partial_rule_depth/20 2886195 ns/iter (± 43521) 3307266 ns/iter (± 173174) 0.87
partial_rule_depth/40 9548214 ns/iter (± 203780) 11547672 ns/iter (± 568337) 0.83
partial_rule_depth/80 48818015 ns/iter (± 611443) 67846549 ns/iter (± 4393548) 0.72
partial_rule_depth/100 85708376 ns/iter (± 1126602) 118729410 ns/iter (± 4902186) 0.72

This comment was automatically generated by workflow using github-action-benchmark.

Please sign in to comment.