Skip to content

Commit 18ba518

Browse files
committed
cleanup location logic
1 parent c5a1fcb commit 18ba518

File tree

4 files changed

+64
-41
lines changed

4 files changed

+64
-41
lines changed

src/errors/kinds.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use strum::{Display, EnumMessage};
77
/// NOTE: if an error has parameters:
88
/// * the variables in the message need to match the enum struct
99
/// * you need to add an entry to the `render` enum to render the error message as a template
10-
/// * you need to ad an entry to the `py_dict` enum to generate `ctx` for error messages
10+
/// * you need to add an entry to the `py_dict` enum to generate `ctx` for error messages
1111
#[derive(Debug, Display, EnumMessage, Clone)]
1212
#[strum(serialize_all = "snake_case")]
1313
pub enum ErrorKind {

src/errors/line_error.rs

+6-9
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use pyo3::prelude::*;
33
use crate::input::{Input, JsonInput};
44

55
use super::kinds::ErrorKind;
6-
use super::location::{new_location, LocItem, Location};
6+
use super::location::{LocItem, Location};
77
use super::validation_exception::{pretty_py_line_errors, PyLineError};
88

99
pub type ValResult<'a, T> = Result<T, ValError<'a>>;
@@ -55,7 +55,7 @@ pub fn pretty_line_errors(py: Python, line_errors: Vec<ValLineError>) -> String
5555
pub struct ValLineError<'a> {
5656
pub kind: ErrorKind,
5757
// location is reversed so that adding an "outer" location item is pushing, it's reversed before showing to the user
58-
pub reverse_location: Location,
58+
pub location: Location,
5959
pub input_value: InputValue<'a>,
6060
}
6161

@@ -64,25 +64,22 @@ impl<'a> ValLineError<'a> {
6464
Self {
6565
kind,
6666
input_value: input.as_error_value(),
67-
reverse_location: None,
67+
location: Location::default(),
6868
}
6969
}
7070

7171
pub fn new_with_loc(kind: ErrorKind, input: &'a impl Input<'a>, loc: impl Into<LocItem>) -> ValLineError<'a> {
7272
Self {
7373
kind,
7474
input_value: input.as_error_value(),
75-
reverse_location: Some(vec![loc.into()]),
75+
location: Location::new_some(loc.into()),
7676
}
7777
}
7878

7979
/// location is stored reversed so it's quicker to add "outer" items as that's what we always do
8080
/// hence `push` here instead of `insert`
8181
pub fn with_outer_location(mut self, loc_item: LocItem) -> Self {
82-
match self.reverse_location {
83-
Some(ref mut rev_loc) => rev_loc.push(loc_item),
84-
None => self.reverse_location = new_location(loc_item),
85-
};
82+
self.location.with_outer(loc_item);
8683
self
8784
}
8885

@@ -95,7 +92,7 @@ impl<'a> ValLineError<'a> {
9592
pub fn into_new<'b>(self, py: Python) -> ValLineError<'b> {
9693
ValLineError {
9794
kind: self.kind,
98-
reverse_location: self.reverse_location,
95+
location: self.location,
9996
input_value: self.input_value.to_object(py).into(),
10097
}
10198
}

src/errors/location.rs

+52-13
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use std::fmt;
22

33
use pyo3::prelude::*;
4+
use pyo3::types::PyList;
45

56
/// Used to store individual items of the error location, e.g. a string for key/field names
67
/// or a number for array indices.
@@ -51,21 +52,59 @@ impl ToPyObject for LocItem {
5152
/// Error locations are represented by a vector of `LocItem`s.
5253
/// e.g. if the error occurred in the third member of a list called `foo`,
5354
/// the location would be `["foo", 2]`.
54-
pub type Location = Option<Vec<LocItem>>;
55+
/// Note: location in List is stored in **REVERSE** so adding an "outer" item to location involves
56+
/// pushing to the vec which is faster than inserting and shifting everything along.
57+
/// Then when "using" location in `Display` and `ToPyObject` order has to be reversed
58+
#[derive(Debug, Clone)]
59+
pub enum Location {
60+
// no location, avoid creating an unnecessary vec
61+
Empty,
62+
// store the in a vec of LocItems, Note: this is the REVERSE of location, see above
63+
// we could perhaps use a smallvec or similar here, probably only worth it if we store a Cow in LocItem
64+
List(Vec<LocItem>),
65+
}
66+
67+
impl Default for Location {
68+
fn default() -> Self {
69+
Self::Empty
70+
}
71+
}
72+
73+
impl ToPyObject for Location {
74+
fn to_object(&self, py: Python<'_>) -> PyObject {
75+
match self {
76+
Self::List(loc) => loc.iter().rev().collect::<Vec<_>>().to_object(py),
77+
Self::Empty => PyList::empty(py).to_object(py),
78+
}
79+
}
80+
}
5581

56-
pub fn reverse_location(location: Location) -> Location {
57-
match location {
58-
Some(loc) => match loc.len() {
59-
0..=1 => Some(loc),
60-
_ => Some(loc.into_iter().rev().collect()),
61-
},
62-
None => None,
82+
impl fmt::Display for Location {
83+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
84+
match self {
85+
Self::List(loc) => {
86+
let loc_str = loc.iter().rev().map(|i| i.to_string()).collect::<Vec<_>>();
87+
writeln!(f, "{}", loc_str.join(" -> "))
88+
}
89+
Self::Empty => Ok(()),
90+
}
6391
}
6492
}
6593

66-
pub fn new_location(item: LocItem) -> Location {
67-
// 3 is plucked out of the air, should it just be 1?
68-
let mut loc = Vec::with_capacity(3);
69-
loc.push(item);
70-
Some(loc)
94+
impl Location {
95+
/// create a new location vec with a value, 3 is plucked out of thin air, should it just be 1?
96+
pub fn new_some(item: LocItem) -> Self {
97+
let mut loc = Vec::with_capacity(3);
98+
loc.push(item);
99+
Self::List(loc)
100+
}
101+
102+
pub fn with_outer(&mut self, loc_item: LocItem) {
103+
match self {
104+
Self::List(ref mut loc) => loc.push(loc_item),
105+
Self::Empty => {
106+
*self = Self::new_some(loc_item);
107+
}
108+
};
109+
}
71110
}

src/errors/validation_exception.rs

+5-18
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,7 @@ use std::fmt::Write;
44

55
use pyo3::exceptions::PyValueError;
66
use pyo3::prelude::*;
7-
use pyo3::types::{PyDict, PyList};
8-
9-
use crate::errors::location::reverse_location;
7+
use pyo3::types::PyDict;
108

119
use crate::input::repr_string;
1210

@@ -146,7 +144,7 @@ impl<'a> IntoPy<PyLineError> for ValLineError<'a> {
146144
fn into_py(self, py: Python<'_>) -> PyLineError {
147145
PyLineError {
148146
kind: self.kind,
149-
location: reverse_location(self.reverse_location),
147+
location: self.location,
150148
input_value: self.input_value.to_object(py),
151149
}
152150
}
@@ -157,7 +155,7 @@ impl<'a> From<PyLineError> for ValLineError<'a> {
157155
fn from(py_line_error: PyLineError) -> Self {
158156
Self {
159157
kind: py_line_error.kind,
160-
reverse_location: reverse_location(py_line_error.location),
158+
location: py_line_error.location,
161159
input_value: py_line_error.input_value.into(),
162160
}
163161
}
@@ -167,11 +165,7 @@ impl PyLineError {
167165
pub fn as_dict(&self, py: Python) -> PyResult<PyObject> {
168166
let dict = PyDict::new(py);
169167
dict.set_item("kind", self.kind.to_string())?;
170-
if let Some(ref location) = self.location {
171-
dict.set_item("loc", location.to_object(py))?;
172-
} else {
173-
dict.set_item("loc", PyList::empty(py).to_object(py))?;
174-
}
168+
dict.set_item("loc", self.location.to_object(py))?;
175169
dict.set_item("message", self.kind.render())?;
176170
dict.set_item("input_value", &self.input_value)?;
177171
if let Some(context) = self.kind.py_dict(py)? {
@@ -182,14 +176,7 @@ impl PyLineError {
182176

183177
fn pretty(&self, py: Option<Python>) -> Result<String, fmt::Error> {
184178
let mut output = String::with_capacity(200);
185-
if let Some(ref location) = self.location {
186-
let loc = location
187-
.iter()
188-
.map(|i| i.to_string())
189-
.collect::<Vec<String>>()
190-
.join(" -> ");
191-
writeln!(output, "{}", &loc)?;
192-
}
179+
write!(output, "{}", self.location)?;
193180

194181
write!(output, " {} [kind={}", self.kind.render(), self.kind)?;
195182

0 commit comments

Comments
 (0)