arch_toolkit/aur/utils.rs
1//! Utility functions for AUR operations.
2
3use serde_json::Value;
4use std::fmt::Write;
5
6/// What: Percent-encode a string for use in URLs according to RFC 3986.
7///
8/// Inputs:
9/// - `input`: String to encode.
10///
11/// Output:
12/// - Returns a percent-encoded string where reserved characters are escaped.
13///
14/// Details:
15/// - Unreserved characters as per RFC 3986 (`A-Z`, `a-z`, `0-9`, `-`, `.`, `_`, `~`) are left as-is.
16/// - Space is encoded as `%20` (not `+`).
17/// - All other bytes are encoded as two uppercase hexadecimal digits prefixed by `%`.
18/// - Operates on raw bytes from the input string; any non-ASCII bytes are hex-escaped.
19#[must_use]
20pub fn percent_encode(input: &str) -> String {
21 let mut out = String::with_capacity(input.len());
22 for &b in input.as_bytes() {
23 match b {
24 b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'-' | b'.' | b'_' | b'~' => {
25 out.push(b as char);
26 }
27 b' ' => out.push_str("%20"),
28 _ => {
29 out.push('%');
30 let _ = write!(out, "{b:02X}");
31 }
32 }
33 }
34 out
35}
36
37/// What: Extract a string value from a JSON object by key, defaulting to empty string.
38///
39/// Inputs:
40/// - `v`: JSON value to extract from.
41/// - `key`: Key to look up in the JSON object.
42///
43/// Output:
44/// - Returns the string value if found, or an empty string if the key is missing or not a string.
45///
46/// Details:
47/// - Returns `""` if the key is missing or the value is not a string type.
48#[must_use]
49pub fn s(v: &Value, key: &str) -> String {
50 v.get(key)
51 .and_then(Value::as_str)
52 .unwrap_or_default()
53 .to_string()
54}
55
56/// What: Extract a string value from a JSON object by key with fallback keys.
57///
58/// Inputs:
59/// - `v`: JSON value to extract from.
60/// - `keys`: Array of keys to try in order.
61///
62/// Output:
63/// - Returns the first string value found, or an empty string if none found.
64///
65/// Details:
66/// - Tries each key in order until one returns a string value.
67#[must_use]
68#[allow(dead_code)] // Utility function that may be used in future modules
69pub fn ss(v: &Value, keys: &[&str]) -> String {
70 for key in keys {
71 if let Some(s) = v.get(key).and_then(Value::as_str) {
72 return s.to_string();
73 }
74 }
75 String::new()
76}
77
78/// What: Extract an array of strings from a JSON object by trying keys in order.
79///
80/// Inputs:
81/// - `v`: JSON value to extract from.
82/// - `keys`: Array of candidate keys to try in order.
83///
84/// Output:
85/// - Returns the first found array as `Vec<String>`, filtering out non-string elements.
86/// - Returns an empty vector if no array of strings is found.
87///
88/// Details:
89/// - Tries keys in the order provided and returns the first array found.
90/// - Filters out non-string elements from the array.
91#[must_use]
92pub fn arrs(v: &Value, keys: &[&str]) -> Vec<String> {
93 for key in keys {
94 if let Some(arr) = v.get(key).and_then(Value::as_array) {
95 return arr
96 .iter()
97 .filter_map(Value::as_str)
98 .map(ToString::to_string)
99 .collect();
100 }
101 }
102 Vec::new()
103}
104
105/// What: Extract an unsigned 64-bit integer by trying multiple keys and representations.
106///
107/// Inputs:
108/// - `v`: JSON value to extract from.
109/// - `keys`: Array of candidate keys to try in order.
110///
111/// Output:
112/// - Returns `Some(u64)` if a valid value is found, or `None` if no usable value is found.
113///
114/// Details:
115/// - Accepts any of the following representations for the first matching key:
116/// - JSON `u64`
117/// - JSON `i64` convertible to `u64` (must be positive)
118/// - String that parses as `u64`
119/// - Tries keys in the order provided and returns the first match.
120#[must_use]
121pub fn u64_of(v: &Value, keys: &[&str]) -> Option<u64> {
122 for key in keys {
123 if let Some(n) = v.get(key) {
124 if let Some(u) = n.as_u64() {
125 return Some(u);
126 }
127 if let Some(i) = n.as_i64()
128 && let Ok(u) = u64::try_from(i)
129 && u > 0
130 {
131 return Some(u);
132 }
133 if let Some(s) = n.as_str()
134 && let Ok(p) = s.parse::<u64>()
135 {
136 return Some(p);
137 }
138 }
139 }
140 None
141}