Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
57 changes: 21 additions & 36 deletions src/aliyun/cdn.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,20 @@ pub struct RefreshObjectCachesRequest {
pub force: Option<bool>,
}

/// 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<String>,

#[serde(rename = "Force", skip_serializing_if = "Option::is_none")]
force: Option<bool>,
}

/// Response from RefreshObjectCaches API
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct RefreshObjectCachesResponse {
Expand Down Expand Up @@ -257,17 +271,14 @@ impl AliyunCdnClient {
) -> AppResult<RefreshObjectCachesResponse> {
// 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,
};

let form_body = build_form_urlencoded_body(&form_params);
let form_body = serde_urlencoded::to_string(&form_params)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot 改成用一个struct而不是btreemap

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. Created RefreshObjectCachesFormParams struct with proper serde field renaming for URL encoding. Commit d88d784

.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.
Expand Down Expand Up @@ -327,29 +338,3 @@ impl AliyunCdnClient {
Ok(result)
}
}

fn build_form_urlencoded_body(params: &BTreeMap<String, String>) -> String {
params
.iter()
.map(|(k, v)| format!("{}={}", form_urlencode(k), form_urlencode(v)))
.collect::<Vec<_>>()
.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
}