Skip to main content

alopex_cli/client/
admin_resources.rs

1use serde::Deserialize;
2
3use crate::client::http::{ClientResult, HttpClient};
4
5#[derive(Debug, Default)]
6pub struct AdminResourcesRequest {
7    pub limit: Option<usize>,
8    pub include_columnar_columns: Option<bool>,
9    pub columnar_column_limit: Option<usize>,
10    pub kv_prefix: Option<String>,
11}
12
13#[derive(Debug, Deserialize)]
14pub struct AdminResourcesResponse {
15    pub sql_tables: Vec<SqlTableResource>,
16    pub columnar_segments: Vec<ColumnarSegmentResource>,
17    pub kv_keys: Vec<String>,
18    pub truncated: TruncatedSections,
19}
20
21#[derive(Debug, Deserialize)]
22pub struct TruncatedSections {
23    pub sql_tables: bool,
24    pub columnar_segments: bool,
25    pub kv_keys: bool,
26}
27
28#[derive(Debug, Deserialize)]
29pub struct SqlTableResource {
30    pub name: String,
31    pub columns: Vec<SqlColumnResource>,
32}
33
34#[derive(Debug, Deserialize)]
35pub struct SqlColumnResource {
36    pub name: String,
37    #[allow(dead_code)]
38    pub data_type: String,
39}
40
41#[derive(Debug, Deserialize)]
42pub struct ColumnarSegmentResource {
43    pub id: String,
44    pub columns: Option<Vec<String>>,
45}
46
47pub async fn fetch_admin_resources(
48    client: &HttpClient,
49    request: &AdminResourcesRequest,
50) -> ClientResult<AdminResourcesResponse> {
51    let path = build_query_path(request);
52    client.get_json(&path).await
53}
54
55fn build_query_path(request: &AdminResourcesRequest) -> String {
56    let mut params = Vec::new();
57    if let Some(limit) = request.limit {
58        params.push(format!("limit={limit}"));
59    }
60    if let Some(include) = request.include_columnar_columns {
61        params.push(format!("include_columnar_columns={include}"));
62    }
63    if let Some(columnar_column_limit) = request.columnar_column_limit {
64        params.push(format!("columnar_column_limit={columnar_column_limit}"));
65    }
66    if let Some(prefix) = request.kv_prefix.as_deref() {
67        params.push(format!("kv_prefix={}", encode_query_component(prefix)));
68    }
69
70    if params.is_empty() {
71        "api/admin/resources".to_string()
72    } else {
73        format!("api/admin/resources?{}", params.join("&"))
74    }
75}
76
77fn encode_query_component(value: &str) -> String {
78    let mut out = String::with_capacity(value.len());
79    for b in value.bytes() {
80        match b {
81            b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'-' | b'.' | b'_' | b'~' => {
82                out.push(b as char)
83            }
84            _ => out.push_str(&format!("%{b:02X}")),
85        }
86    }
87    out
88}
89
90#[cfg(test)]
91mod tests {
92    use super::{build_query_path, AdminResourcesRequest};
93
94    #[test]
95    fn build_query_path_includes_params() {
96        let request = AdminResourcesRequest {
97            limit: Some(10),
98            include_columnar_columns: Some(true),
99            columnar_column_limit: Some(5),
100            kv_prefix: Some("app/".to_string()),
101        };
102        let path = build_query_path(&request);
103        assert!(path.starts_with("api/admin/resources?"));
104        assert!(path.contains("limit=10"));
105        assert!(path.contains("include_columnar_columns=true"));
106        assert!(path.contains("columnar_column_limit=5"));
107        assert!(path.contains("kv_prefix=app%2F"));
108    }
109}