From 9a60b4d6e2ca0c87bf767d31299caae0b91c2706 Mon Sep 17 00:00:00 2001 From: nsfisis Date: Sun, 21 Jun 2026 19:03:39 +0900 Subject: feat(php-shim): implement http_build_query via serde_urlencoded --- crates/shirabe-php-shim/Cargo.toml | 1 + crates/shirabe-php-shim/src/url.rs | 55 +++++++++++++++++++++++++++++++++++--- 2 files changed, 52 insertions(+), 4 deletions(-) (limited to 'crates/shirabe-php-shim') diff --git a/crates/shirabe-php-shim/Cargo.toml b/crates/shirabe-php-shim/Cargo.toml index 797191e..bd285f5 100644 --- a/crates/shirabe-php-shim/Cargo.toml +++ b/crates/shirabe-php-shim/Cargo.toml @@ -12,6 +12,7 @@ regex.workspace = true reqwest.workspace = true serde.workspace = true serde_json.workspace = true +serde_urlencoded.workspace = true zip.workspace = true [lints] diff --git a/crates/shirabe-php-shim/src/url.rs b/crates/shirabe-php-shim/src/url.rs index ddb46b6..92ed389 100644 --- a/crates/shirabe-php-shim/src/url.rs +++ b/crates/shirabe-php-shim/src/url.rs @@ -82,10 +82,57 @@ pub fn http_build_query_mixed( numeric_prefix: &str, arg_separator: &str, ) -> String { - let _ = (data, numeric_prefix, arg_separator); - todo!() + let mut pairs: Vec<(String, String)> = Vec::new(); + for (key, value) in data { + // numeric_prefix is prepended only to integer keys at the top level. + let key = if key.parse::().is_ok() { + format!("{numeric_prefix}{key}") + } else { + key.clone() + }; + append_query_pairs(&mut pairs, key, value); + } + encode_pairs(&pairs, arg_separator) +} + +pub fn http_build_query( + data: &[(&str, &str)], + numeric_prefix: &str, + arg_separator: &str, +) -> String { + // numeric_prefix only applies to integer keys, which a string-keyed slice never has. + let _ = numeric_prefix; + encode_pairs(data, arg_separator) } -pub fn http_build_query(_data: &[(&str, &str)], _sep_str: &str, _sep: &str) -> String { - todo!() +fn encode_pairs(pairs: T, arg_separator: &str) -> String { + let encoded = serde_urlencoded::to_string(pairs).unwrap(); + if arg_separator == "&" { + encoded + } else { + // serde_urlencoded percent-encodes any literal '&' in keys/values, so the + // only remaining '&' are the separators we are replacing. + encoded.replace('&', arg_separator) + } +} + +fn append_query_pairs(pairs: &mut Vec<(String, String)>, key: String, value: &PhpMixed) { + match value { + // Null values are omitted from the query string entirely. + PhpMixed::Null => {} + PhpMixed::Bool(b) => pairs.push((key, if *b { "1" } else { "0" }.to_string())), + PhpMixed::Int(i) => pairs.push((key, i.to_string())), + PhpMixed::Float(f) => pairs.push((key, f.to_string())), + PhpMixed::String(s) => pairs.push((key, s.clone())), + PhpMixed::List(items) => { + for (i, item) in items.iter().enumerate() { + append_query_pairs(pairs, format!("{key}[{i}]"), item); + } + } + PhpMixed::Array(map) | PhpMixed::Object(map) => { + for (k, v) in map { + append_query_pairs(pairs, format!("{key}[{k}]"), v); + } + } + } } -- cgit v1.3.1