use super::dispatch::{invoke_list, invoke_sdk};
use super::registry::get_resource;
use crate::aws::client::AwsClients;
use anyhow::{anyhow, Result};
use serde_json::Value;
#[derive(Debug, Clone, Default)]
pub struct ResourceFilter {
pub name: String,
pub values: Vec<String>,
pub filter_type: String,
}
impl ResourceFilter {
pub fn new(name: &str, values: Vec<String>) -> Self {
Self {
name: name.to_string(),
values,
filter_type: "scalar".to_string(),
}
}
pub fn with_type(name: &str, values: Vec<String>, filter_type: &str) -> Self {
Self {
name: name.to_string(),
values,
filter_type: filter_type.to_string(),
}
}
}
#[derive(Debug, Clone)]
pub struct PaginatedResult {
pub items: Vec<Value>,
pub next_token: Option<String>,
}
pub async fn fetch_resources_paginated(
resource_key: &str,
clients: &AwsClients,
filters: &[ResourceFilter],
page_token: Option<&str>,
) -> Result<PaginatedResult> {
let resource_def =
get_resource(resource_key).ok_or_else(|| anyhow!("Unknown resource: {}", resource_key))?;
let mut params = resource_def.sdk_method_params.clone();
if !filters.is_empty() {
if let Value::Object(ref mut map) = params {
for filter in filters {
match filter.filter_type.as_str() {
"ec2_filter" => {
let filter_key = format!("filter:{}", filter.name);
map.insert(
filter_key,
Value::Array(
filter
.values
.iter()
.map(|v| Value::String(v.clone()))
.collect(),
),
);
}
_ => {
let value = if filter.values.len() == 1 {
Value::String(filter.values[0].clone())
} else {
Value::Array(
filter
.values
.iter()
.map(|v| Value::String(v.clone()))
.collect(),
)
};
map.insert(filter.name.clone(), value);
}
}
}
}
}
if let Some(token) = page_token {
if let Value::Object(ref mut map) = params {
map.insert("_page_token".to_string(), Value::String(token.to_string()));
}
}
let response = if resource_def.has_api_config() {
invoke_list(resource_key, clients, ¶ms).await?
} else {
invoke_sdk(
&resource_def.service,
&resource_def.sdk_method,
clients,
¶ms,
)
.await?
};
let mut items = extract_items(&response, &resource_def.response_path)?;
if !resource_def.preserve_order {
let sort_field = &resource_def.name_field;
items.sort_by(|a, b| {
let a_val = a.get(sort_field).and_then(|v| v.as_str()).unwrap_or("");
let b_val = b.get(sort_field).and_then(|v| v.as_str()).unwrap_or("");
a_val.cmp(b_val)
});
}
let next_token = response
.get("_next_token")
.and_then(|v| v.as_str())
.map(|s| s.to_string());
Ok(PaginatedResult { items, next_token })
}
fn extract_items(response: &Value, path: &str) -> Result<Vec<Value>> {
let parts: Vec<&str> = path.split('.').collect();
let mut current = response.clone();
for part in parts {
current = current
.get(part)
.cloned()
.ok_or_else(|| anyhow!("Path '{}' not found in response", path))?;
}
match current {
Value::Array(arr) => Ok(arr),
_ => Err(anyhow!(
"Expected array at path '{}', got {:?}",
path,
current
)),
}
}
pub fn extract_json_value(item: &Value, path: &str) -> String {
let parts: Vec<&str> = path.split('.').collect();
let mut current = item.clone();
for part in parts {
current = match current {
Value::Object(map) => {
if part == "Name" && map.contains_key("Tags") {
if let Some(Value::Object(tags)) = map.get("Tags") {
if let Some(Value::String(name)) = tags.get("Name") {
return name.clone();
}
}
}
map.get(part).cloned().unwrap_or(Value::Null)
}
Value::Array(arr) => {
if part == "length" {
return arr.len().to_string();
}
if let Ok(idx) = part.parse::<usize>() {
arr.get(idx).cloned().unwrap_or(Value::Null)
} else {
Value::Null
}
}
_ => Value::Null,
};
}
match current {
Value::String(s) => s,
Value::Number(n) => n.to_string(),
Value::Bool(b) => {
if b {
"Yes".to_string()
} else {
"No".to_string()
}
}
Value::Null => "-".to_string(),
_ => "-".to_string(),
}
}