Skip to content

Commit d9cf9f3

Browse files
cursoragentlovasoa
andcommitted
Refactor: Use serde_path_to_error for better error reporting
Co-authored-by: contact <[email protected]>
1 parent e73787e commit d9cf9f3

File tree

4 files changed

+69
-60
lines changed

4 files changed

+69
-60
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ tokio = { version = "1.24.1", features = ["macros", "rt", "process", "sync"] }
4444
tokio-stream = "0.1.9"
4545
anyhow = "1"
4646
serde = "1"
47+
serde_path_to_error = "0.1"
4748
serde_json = { version = "1.0.82", features = [
4849
"preserve_order",
4950
"raw_value",

src/webserver/database/sqlpage_functions/functions.rs

Lines changed: 11 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -548,63 +548,6 @@ async fn request_method(request: &RequestInfo) -> String {
548548
request.method.to_string()
549549
}
550550

551-
fn parse_run_sql_variables(raw: &str) -> anyhow::Result<ParamMap> {
552-
let value: serde_json::Value =
553-
serde_json::from_str(raw).with_context(|| "run_sql: unable to parse variables as JSON")?;
554-
let object = value.as_object().ok_or_else(|| {
555-
anyhow!(
556-
"run_sql: the second argument must be a JSON object whose values are strings or arrays of strings. Example: {{\"name\": \"Alice\", \"store_ids\": [\"1\", \"2\"]}}"
557-
)
558-
})?;
559-
let mut parsed = ParamMap::with_capacity(object.len());
560-
for (key, value) in object {
561-
let entry = match value {
562-
serde_json::Value::String(s) => SingleOrVec::Single(s.clone()),
563-
serde_json::Value::Array(values) => {
564-
let mut strings = Vec::with_capacity(values.len());
565-
for (idx, item) in values.iter().enumerate() {
566-
let Some(string_value) = item.as_str() else {
567-
anyhow::bail!(
568-
"run_sql: variable {key:?} must be an array of strings. Item at index {idx} is {item}"
569-
);
570-
};
571-
strings.push(string_value.to_owned());
572-
}
573-
SingleOrVec::Vec(strings)
574-
}
575-
_ => {
576-
anyhow::bail!(
577-
"run_sql: variable {key:?} must be a string or an array of strings, but found {value}"
578-
);
579-
}
580-
};
581-
parsed.insert(key.clone(), entry);
582-
}
583-
Ok(parsed)
584-
}
585-
586-
#[test]
587-
fn parse_run_sql_variables_accepts_strings_and_arrays() {
588-
let vars =
589-
parse_run_sql_variables(r#"{"city":"Paris","ids":["1","2"]}"#).expect("valid variables");
590-
assert_eq!(
591-
vars.get("city"),
592-
Some(&SingleOrVec::Single("Paris".to_string()))
593-
);
594-
assert_eq!(
595-
vars.get("ids"),
596-
Some(&SingleOrVec::Vec(vec!["1".to_string(), "2".to_string()]))
597-
);
598-
}
599-
600-
#[test]
601-
fn parse_run_sql_variables_rejects_invalid_values() {
602-
let err = parse_run_sql_variables(r#"{"city":1}"#).expect_err("should fail");
603-
let err_string = err.to_string();
604-
assert!(err_string.contains(r#"variable "city""#));
605-
assert!(err_string.contains("string or an array of strings"));
606-
}
607-
608551
async fn run_sql<'a>(
609552
request: &'a RequestInfo,
610553
db_connection: &mut DbConn,
@@ -628,7 +571,17 @@ async fn run_sql<'a>(
628571
.with_context(|| format!("run_sql: invalid path {sql_file_path:?}"))?;
629572
let mut tmp_req = if let Some(variables) = variables {
630573
let mut tmp_req = request.clone_without_variables();
631-
let variables = parse_run_sql_variables(&variables)?;
574+
let mut deserializer = serde_json::Deserializer::from_str(&variables);
575+
let variables: ParamMap =
576+
serde_path_to_error::deserialize(&mut deserializer).map_err(|err| {
577+
let path = err.path().to_string();
578+
let context = if path.is_empty() {
579+
"run_sql: unable to parse the variables argument".to_string()
580+
} else {
581+
format!("run_sql: invalid value for the variables argument at {path}")
582+
};
583+
anyhow::Error::new(err.into_inner()).context(context)
584+
})?;
632585
tmp_req.get_variables = variables;
633586
tmp_req
634587
} else {

src/webserver/http.rs

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -226,13 +226,41 @@ async fn render_sql(
226226
resp_recv.await.map_err(ErrorInternalServerError)
227227
}
228228

229-
#[derive(Debug, serde::Serialize, serde::Deserialize, PartialEq, Clone)]
230-
#[serde(untagged)]
229+
#[derive(Debug, serde::Serialize, PartialEq, Clone)]
231230
pub enum SingleOrVec {
232231
Single(String),
233232
Vec(Vec<String>),
234233
}
235234

235+
impl<'de> serde::Deserialize<'de> for SingleOrVec {
236+
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
237+
where
238+
D: serde::Deserializer<'de>,
239+
{
240+
let value = serde_json::Value::deserialize(deserializer)?;
241+
match value {
242+
serde_json::Value::String(s) => Ok(SingleOrVec::Single(s)),
243+
serde_json::Value::Array(values) => {
244+
let mut strings = Vec::with_capacity(values.len());
245+
for (idx, item) in values.into_iter().enumerate() {
246+
match item {
247+
serde_json::Value::String(s) => strings.push(s),
248+
other => {
249+
return Err(D::Error::custom(format!(
250+
"expected an array of strings, but item at index {idx} is {other}"
251+
)))
252+
}
253+
}
254+
}
255+
Ok(SingleOrVec::Vec(strings))
256+
}
257+
other => Err(D::Error::custom(format!(
258+
"expected a string or an array of strings, but found {other}"
259+
))),
260+
}
261+
}
262+
}
263+
236264
impl std::fmt::Display for SingleOrVec {
237265
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
238266
match self {
@@ -263,6 +291,7 @@ impl SingleOrVec {
263291
}
264292
}
265293
}
294+
266295
fn take_vec(&mut self) -> Vec<String> {
267296
match self {
268297
SingleOrVec::Single(x) => vec![mem::take(x)],
@@ -279,6 +308,31 @@ impl SingleOrVec {
279308
}
280309
}
281310

311+
#[cfg(test)]
312+
mod single_or_vec_tests {
313+
use super::SingleOrVec;
314+
315+
#[test]
316+
fn deserializes_string_and_array_values() {
317+
let single: SingleOrVec = serde_json::from_str(r#""hello""#).unwrap();
318+
assert_eq!(single, SingleOrVec::Single("hello".to_string()));
319+
let array: SingleOrVec = serde_json::from_str(r#"["a","b"]"#).unwrap();
320+
assert_eq!(
321+
array,
322+
SingleOrVec::Vec(vec!["a".to_string(), "b".to_string()])
323+
);
324+
}
325+
326+
#[test]
327+
fn rejects_non_string_items() {
328+
let err = serde_json::from_str::<SingleOrVec>(r#"["a", 1]"#).unwrap_err();
329+
assert!(
330+
err.to_string()
331+
.contains("expected an array of strings, but item at index 1 is 1"),
332+
"{err}"
333+
);
334+
}
335+
}
282336
async fn process_sql_request(
283337
req: &mut ServiceRequest,
284338
sql_path: PathBuf,

0 commit comments

Comments
 (0)