Stricter conversion of input JSON.

This commit is contained in:
Sebastian Jeltsch
2025-07-16 12:11:12 +02:00
parent 59e00f0ec5
commit d1729edff0
3 changed files with 140 additions and 16 deletions
+10 -11
View File
@@ -145,7 +145,7 @@ pub async fn create_record_handler(
params_list.push(
lazy_params
.consume()
.map_err(|_| RecordError::BadRequest("Parameter conversion"))?,
.map_err(|_| RecordError::BadRequest("Invalid Parameters"))?,
);
}
@@ -207,6 +207,7 @@ mod test {
use crate::util::{id_to_b64, uuid_to_b64};
use serde_json::json;
use trailbase_sqlite::params;
#[tokio::test]
async fn test_simple_record_api_create() {
@@ -271,16 +272,14 @@ mod test {
.unwrap();
}
let value: Option<i64> = state
.conn()
.read_query_row_f(
"SELECT value FROM simple WHERE owner = ?1",
trailbase_sqlite::params!(user_x),
|row| row.get(0),
)
.await
.unwrap();
assert_eq!(value, Some(9));
assert_eq!(
state
.conn()
.read_query_value::<i64>("SELECT value FROM simple WHERE owner = ?1", params!(user_x))
.await
.unwrap(),
Some(9)
);
{
// Make sure user.id == owner ACL check works
+28 -4
View File
@@ -397,15 +397,39 @@ pub fn simple_json_value_to_param(
try_json_array_to_blob(arr)?
}
serde_json::Value::Null => Value::Null,
serde_json::Value::Bool(b) => Value::Integer(b as i64),
serde_json::Value::Bool(b) => {
if col_type != ColumnDataType::Integer {
return Err(ParamsError::UnexpectedType("Bool", format!("{col_type:?}")));
}
Value::Integer(b as i64)
}
serde_json::Value::String(str) => json_string_to_value(col_type, str)?,
serde_json::Value::Number(number) => {
if let Some(n) = number.as_i64() {
Value::Integer(n)
match col_type {
ColumnDataType::Integer => Value::Integer(n),
// NOTE: "as" is lossy conversion. Does not panic.
ColumnDataType::Real => Value::Real(n as f64),
_ => {
return Err(ParamsError::UnexpectedType("int", format!("{col_type:?}")));
}
}
} else if let Some(n) = number.as_u64() {
Value::Integer(n as i64)
match col_type {
// NOTE: "as" is lossy conversion. Does not panic.
ColumnDataType::Integer => Value::Integer(n as i64),
ColumnDataType::Real => Value::Real(n as f64),
_ => {
return Err(ParamsError::UnexpectedType("uint", format!("{col_type:?}")));
}
}
} else if let Some(n) = number.as_f64() {
Value::Real(n)
match col_type {
ColumnDataType::Real => Value::Real(n),
_ => {
return Err(ParamsError::UnexpectedType("real", format!("{col_type:?}")));
}
}
} else {
warn!("Not a valid number: {number:?}");
return Err(ParamsError::NotANumber);
+102 -1
View File
@@ -66,7 +66,7 @@ pub async fn update_record_handler(
api.has_file_columns(),
lazy_params
.consume()
.map_err(|err| RecordError::Internal(err.into()))?,
.map_err(|_| RecordError::BadRequest("Invalid Parameters"))?,
)
.await
.map_err(|err| RecordError::Internal(err.into()))?;
@@ -77,6 +77,7 @@ pub async fn update_record_handler(
#[cfg(test)]
mod test {
use axum::extract::Query;
use serde_json::json;
use trailbase_sqlite::params;
use super::*;
@@ -94,6 +95,106 @@ mod test {
use crate::test::unpack_json_response;
use crate::util::{b64_to_id, id_to_b64};
#[tokio::test]
async fn test_simple_record_api_update() {
let state = test_state(None).await.unwrap();
state
.conn()
.execute_batch(
r#"
CREATE TABLE "update" (
"id" INTEGER PRIMARY KEY,
"int" INTEGER NOT NULL DEFAULT (-1),
"float" REAL NOT NULL,
"text" TEXT
) STRICT;
"#,
)
.await
.unwrap();
state.schema_metadata().invalidate_all().await.unwrap();
add_record_api_config(
&state,
RecordApiConfig {
name: Some("update_api".to_string()),
table_name: Some("update".to_string()),
acl_world: [
PermissionFlag::Create as i32,
PermissionFlag::Read as i32,
PermissionFlag::Update as i32,
]
.into(),
..Default::default()
},
)
.await
.unwrap();
let _ = create_record_handler(
State(state.clone()),
Path("update_api".to_string()),
Query(CreateRecordQuery::default()),
None,
Either::Json(
json_row_from_value(json!({
"id": 1,
"float": 5,
}))
.unwrap()
.into(),
),
)
.await
.unwrap();
let _ = update_record_handler(
State(state.clone()),
Path(("update_api".to_string(), "1".to_string())),
None,
Either::Json(
json_row_from_value(json!({
"int": 4,
}))
.unwrap()
.into(),
),
)
.await
.unwrap();
assert_eq!(
state
.conn()
.read_query_value::<i64>(r#"SELECT "int" FROM "update" WHERE id = 1"#, ())
.await
.unwrap(),
Some(4)
);
// Test that bad input leads to bad request.
let response = update_record_handler(
State(state.clone()),
Path(("update_api".to_string(), "1".to_string())),
None,
Either::Json(
json_row_from_value(json!({
"int": 4.1,
}))
.unwrap()
.into(),
),
)
.await;
assert!(matches!(
response.err().unwrap(),
RecordError::BadRequest(_)
))
}
#[tokio::test]
async fn test_record_api_update() {
let state = test_state(None).await.unwrap();