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}