From 3e1dce463ae143fb3d579f147d805a22c62d58b3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 17 Jan 2026 16:26:54 +0000 Subject: [PATCH 1/4] Initial plan From 740e1bdd8fb7e3031c207cb99fb666258f5c3e66 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 17 Jan 2026 16:34:35 +0000 Subject: [PATCH 2/4] Replace manual form encoding with serde_urlencoded library Co-authored-by: daflyinbed <21363956+daflyinbed@users.noreply.github.com> --- Cargo.lock | 1 + Cargo.toml | 1 + src/aliyun/cdn.rs | 29 ++--------------------------- 3 files changed, 4 insertions(+), 27 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 125e137..aac15ba 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1473,6 +1473,7 @@ dependencies = [ "sentry", "serde", "serde_json", + "serde_urlencoded", "serde_variant", "sha2", "sqlx", diff --git a/Cargo.toml b/Cargo.toml index e379dde..2cc5dd4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,7 @@ path = "src/main.rs" [dependencies] serde = { version = "1.0.228", features = [ "derive" ] } serde_json = "1.0.149" +serde_urlencoded = "0.7.1" tokio = { version = "1.49.0", features = [ "signal", "rt-multi-thread", diff --git a/src/aliyun/cdn.rs b/src/aliyun/cdn.rs index 43c962d..e216f04 100644 --- a/src/aliyun/cdn.rs +++ b/src/aliyun/cdn.rs @@ -267,7 +267,8 @@ impl AliyunCdnClient { form_params.insert("Force".to_string(), force.to_string()); } - let form_body = build_form_urlencoded_body(&form_params); + let form_body = serde_urlencoded::to_string(&form_params) + .context("Failed to encode form parameters")?; // Sign the request (ACS3-HMAC-SHA256). For this API, the form body must be included // in the body hash, so keep the canonical query empty. @@ -327,29 +328,3 @@ impl AliyunCdnClient { Ok(result) } } - -fn build_form_urlencoded_body(params: &BTreeMap) -> String { - params - .iter() - .map(|(k, v)| format!("{}={}", form_urlencode(k), form_urlencode(v))) - .collect::>() - .join("&") -} - -// application/x-www-form-urlencoded encoding. -// - Space becomes '+' -// - Unreserved characters are not escaped -// - Everything else is percent-encoded with upper-case hex -fn form_urlencode(input: &str) -> String { - let mut out = String::with_capacity(input.len()); - for &b in input.as_bytes() { - match b { - b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'-' | b'_' | b'.' | b'~' => { - out.push(b as char) - } - b' ' => out.push('+'), - _ => out.push_str(&format!("%{:02X}", b)), - } - } - out -} From d88d784538cc7d3b661e449e487b172ecaf19c37 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 17 Jan 2026 17:01:53 +0000 Subject: [PATCH 3/4] Use struct instead of BTreeMap for form parameters in RefreshObjectCaches Co-authored-by: daflyinbed <21363956+daflyinbed@users.noreply.github.com> --- src/aliyun/cdn.rs | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/src/aliyun/cdn.rs b/src/aliyun/cdn.rs index e216f04..19e959b 100644 --- a/src/aliyun/cdn.rs +++ b/src/aliyun/cdn.rs @@ -119,6 +119,20 @@ pub struct RefreshObjectCachesRequest { pub force: Option, } +/// Form parameters for RefreshObjectCaches API +/// This struct is used for URL encoding the request body +#[derive(Debug, Clone, Serialize)] +struct RefreshObjectCachesFormParams { + #[serde(rename = "ObjectPath")] + object_path: String, + + #[serde(rename = "ObjectType", skip_serializing_if = "Option::is_none")] + object_type: Option, + + #[serde(rename = "Force", skip_serializing_if = "Option::is_none")] + force: Option, +} + /// Response from RefreshObjectCaches API #[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] pub struct RefreshObjectCachesResponse { @@ -257,15 +271,11 @@ impl AliyunCdnClient { ) -> AppResult { // RefreshObjectCaches is a POST request with parameters in an HTML form body. // Reference: https://help.aliyun.com/zh/cdn/developer-reference/api-cdn-2018-05-10-refreshobjectcaches - let mut form_params = BTreeMap::new(); - form_params.insert("ObjectPath".to_string(), request.object_path.clone()); - - if let Some(ref object_type) = request.object_type { - form_params.insert("ObjectType".to_string(), object_type.clone()); - } - if let Some(force) = request.force { - form_params.insert("Force".to_string(), force.to_string()); - } + let form_params = RefreshObjectCachesFormParams { + object_path: request.object_path.clone(), + object_type: request.object_type.clone(), + force: request.force.map(|f| f.to_string()), + }; let form_body = serde_urlencoded::to_string(&form_params) .context("Failed to encode form parameters")?; From 7c3016e395592858a712a66a78eb1d51212a7fbf Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 18 Jan 2026 00:55:48 +0000 Subject: [PATCH 4/4] Use bool type for force field instead of String Co-authored-by: daflyinbed <21363956+daflyinbed@users.noreply.github.com> --- src/aliyun/cdn.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/aliyun/cdn.rs b/src/aliyun/cdn.rs index 19e959b..d1b7aef 100644 --- a/src/aliyun/cdn.rs +++ b/src/aliyun/cdn.rs @@ -130,7 +130,7 @@ struct RefreshObjectCachesFormParams { object_type: Option, #[serde(rename = "Force", skip_serializing_if = "Option::is_none")] - force: Option, + force: Option, } /// Response from RefreshObjectCaches API @@ -274,7 +274,7 @@ impl AliyunCdnClient { let form_params = RefreshObjectCachesFormParams { object_path: request.object_path.clone(), object_type: request.object_type.clone(), - force: request.force.map(|f| f.to_string()), + force: request.force, }; let form_body = serde_urlencoded::to_string(&form_params)