aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2026-06-21 19:03:39 +0900
committernsfisis <nsfisis@gmail.com>2026-06-21 19:03:39 +0900
commit9a60b4d6e2ca0c87bf767d31299caae0b91c2706 (patch)
treece8bbe3413ae527bb9dfda41caea19eac096444c
parenta3171eefd8f8e520329bdd8f613e83ba6f0a7c13 (diff)
downloadphp-shirabe-9a60b4d6e2ca0c87bf767d31299caae0b91c2706.tar.gz
php-shirabe-9a60b4d6e2ca0c87bf767d31299caae0b91c2706.tar.zst
php-shirabe-9a60b4d6e2ca0c87bf767d31299caae0b91c2706.zip
feat(php-shim): implement http_build_query via serde_urlencoded
-rw-r--r--Cargo.lock19
-rw-r--r--Cargo.toml1
-rw-r--r--crates/shirabe-php-shim/Cargo.toml1
-rw-r--r--crates/shirabe-php-shim/src/url.rs55
4 files changed, 72 insertions, 4 deletions
diff --git a/Cargo.lock b/Cargo.lock
index e8f7f8d..0702269 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1459,6 +1459,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
[[package]]
+name = "ryu"
+version = "1.0.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f"
+
+[[package]]
name = "same-file"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1556,6 +1562,18 @@ dependencies = [
]
[[package]]
+name = "serde_urlencoded"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
+dependencies = [
+ "form_urlencoded",
+ "itoa",
+ "ryu",
+ "serde",
+]
+
+[[package]]
name = "sha1"
version = "0.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1645,6 +1663,7 @@ dependencies = [
"reqwest",
"serde",
"serde_json",
+ "serde_urlencoded",
"zip",
]
diff --git a/Cargo.toml b/Cargo.toml
index fbfe0d1..1898718 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -23,6 +23,7 @@ regex = "1.12.3"
reqwest = "0.13.4"
serde = { version = "1.0.228", features = ["derive"] }
serde_json = { version = "1.0.149", features = ["preserve_order"] }
+serde_urlencoded = "0.7.1"
sha1 = "0.10.6"
tempfile = "3.27.0"
tokio = { version = "1.52.3", features = ["full"] }
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::<i64>().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<T: serde::Serialize>(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);
+ }
+ }
+ }
}