@@ -548,6 +548,63 @@ 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+
551608async fn run_sql < ' a > (
552609 request : & ' a RequestInfo ,
553610 db_connection : & mut DbConn ,
@@ -571,7 +628,7 @@ async fn run_sql<'a>(
571628 . with_context ( || format ! ( "run_sql: invalid path {sql_file_path:?}" ) ) ?;
572629 let mut tmp_req = if let Some ( variables) = variables {
573630 let mut tmp_req = request. clone_without_variables ( ) ;
574- let variables: ParamMap = serde_json :: from_str ( & variables) ?;
631+ let variables = parse_run_sql_variables ( & variables) ?;
575632 tmp_req. get_variables = variables;
576633 tmp_req
577634 } else {
0 commit comments