From 38bd0d357c482d863e9f9f4759de749d0d8ebf1f Mon Sep 17 00:00:00 2001 From: Trezy Date: Wed, 18 Mar 2026 13:10:33 -0500 Subject: [PATCH] fix: coerce query params based on lexicon schema --- src/xrpc/mod.rs | 54 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 53 insertions(+), 1 deletion(-) diff --git a/src/xrpc/mod.rs b/src/xrpc/mod.rs index d17f5bb..9681b9f 100644 --- a/src/xrpc/mod.rs +++ b/src/xrpc/mod.rs @@ -50,6 +50,54 @@ fn parse_query_params(query: &str) -> HashMap { .collect() } +/// Coerce query-param values from strings to their lexicon-declared types. +/// +/// HTTP query params arrive as strings. Without this, Lua scripts receive +/// `"25"` (a string) for `params.limit`, which Postgres rejects when used +/// in LIMIT (`argument of LIMIT must be type bigint, not type text`). +fn coerce_params(params: &mut HashMap, parameters: &Value) { + let properties = match parameters.get("properties").and_then(|p| p.as_object()) { + Some(p) => p, + None => return, + }; + for (key, schema) in properties { + let type_str = match schema.get("type").and_then(|t| t.as_str()) { + Some(t) => t, + None => continue, + }; + let Some(val) = params.get(key) else { + continue; + }; + let Some(s) = val.as_str() else { + continue; + }; + match type_str { + "integer" => { + if let Ok(n) = s.parse::() { + params.insert(key.clone(), Value::Number(n.into())); + } + } + "boolean" => match s { + "true" | "1" => { + params.insert(key.clone(), Value::Bool(true)); + } + "false" | "0" => { + params.insert(key.clone(), Value::Bool(false)); + } + _ => {} + }, + "number" => { + if let Ok(n) = s.parse::() + && let Some(num) = serde_json::Number::from_f64(n) + { + params.insert(key.clone(), Value::Number(num)); + } + } + _ => {} + } + } +} + /// Proxy an unrecognized XRPC method to its home AppView resolved via DNS. async fn proxy_to_authority( state: &AppState, @@ -140,7 +188,7 @@ pub async fn xrpc_get( mut parts: Parts, ) -> Result { let raw_query = raw_query.unwrap_or_default(); - let params = parse_query_params(&raw_query); + let mut params = parse_query_params(&raw_query); let client_ip = extract_client_ip(&parts); let claims = Claims::from_request_parts(&mut parts, &state).await.ok(); @@ -205,6 +253,10 @@ pub async fn xrpc_get( ))); } + if let Some(ref param_schema) = lexicon.parameters { + coerce_params(&mut params, param_schema); + } + let mut response = query::handle_query(&state, &method, ¶ms, &lexicon, claims.as_ref()).await?; if let CheckResult::Allowed { -- 2.51.2