use crate::services::aws_estate_service::AwsServiceParser;
use crate::types::Document;
use anyhow::{Result, anyhow};
use serde_json::{Value, json};
use std::collections::HashMap;
use tracing::{debug, warn};
use indexmap::IndexMap;
use uuid::Uuid;
use async_trait::async_trait;
pub struct VpcParser;
impl VpcParser {
pub fn new() -> Self {
Self
}
fn create_base_metadata(
account_id: &str,
service: &str,
resource_type: &str,
region: Option<&str>
) -> IndexMap<String, Value> {
let mut metadata = IndexMap::new();
metadata.insert("account_id".to_string(), json!(account_id));
metadata.insert("service".to_string(), json!(service));
metadata.insert("resource_type".to_string(), json!(resource_type));
metadata.insert("cloud_provider".to_string(), json!("aws"));
metadata.insert("document_type".to_string(), json!("aws_estate"));
metadata.insert("last_synced".to_string(), json!(chrono::Utc::now().timestamp()));
if let Some(region_val) = region {
metadata.insert("region".to_string(), json!(region_val));
}
metadata
}
fn generate_arn(
service: &str,
region: &str,
account_id: &str,
resource_type: &str,
resource_id: &str
) -> String {
format!("arn:aws:{}:{}:{}:{}:{}", service, region, account_id, resource_type, resource_id)
}
fn parse_vpcs(&self, account_id: &str, vpcs: &[Value]) -> Result<Vec<Document>> {
let mut documents = Vec::new();
for vpc in vpcs {
let vpc_id = vpc["vpc_id"]
.as_str()
.unwrap_or("unknown");
let region = vpc["region"]
.as_str()
.unwrap_or("us-east-1");
let cidr_block = vpc["cidr_block"]
.as_str()
.unwrap_or("unknown");
let state = vpc["state"]
.as_str()
.unwrap_or("unknown");
let is_default = vpc["is_default"]
.as_bool()
.unwrap_or(false);
let doc_id = Self::generate_arn(
"ec2", region,
account_id,
"vpc",
vpc_id,
);
let content = format!(
"VPC {} in region {} - CIDR: {} - State: {} - Default VPC: {}",
vpc_id, region, cidr_block, state, if is_default { "Yes" } else { "No" }
);
let mut metadata = Self::create_base_metadata(
account_id,
"vpc",
"vpc",
Some(region)
);
metadata.insert("vpc_id".to_string(), json!(vpc_id));
metadata.insert("cidr_block".to_string(), json!(cidr_block));
metadata.insert("state".to_string(), json!(state));
metadata.insert("is_default".to_string(), json!(is_default));
let document = Document::new(
doc_id,
content,
).with_metadata(metadata);
documents.push(document);
debug!("✅ Created VPC document for: {}", vpc_id);
}
Ok(documents)
}
}
#[async_trait]
impl AwsServiceParser for VpcParser {
fn service_name(&self) -> &str {
"vpc"
}
fn can_parse(&self, service_data: &Value) -> bool {
let can_parse = service_data.is_object() && (
service_data.get("vpcs").is_some() ||
service_data.get("total_vpcs").is_some()
);
can_parse
}
async fn parse(&self, account_id: &str, service_data: &Value) -> Result<Vec<Document>> {
debug!("🔍 VPC parser processing data for account: {}", account_id);
let mut documents = Vec::new();
if let Some(vpcs) = service_data.get("vpcs").and_then(|v| v.as_array()) {
if !vpcs.is_empty() {
let mut vpc_docs = self.parse_vpcs(account_id, vpcs)?;
documents.append(&mut vpc_docs);
debug!("✅ Parsed {} VPCs", vpcs.len());
}
}
if documents.is_empty() {
warn!("🟡 VPC parser found no parseable data");
} else {
debug!("🎉 VPC parser generated {} documents", documents.len());
}
Ok(documents)
}
fn get_data_schema(&self) -> Option<Value> {
Some(json!({
"type": "object",
"properties": {
"vpcs": {
"type": "array",
"items": {
"type": "object",
"required": ["vpc_id", "region", "cidr_block", "state"],
"properties": {
"vpc_id": { "type": "string" },
"region": { "type": "string" },
"cidr_block": { "type": "string" },
"state": { "type": "string" },
"is_default": { "type": "boolean" }
}
}
},
"total_vpcs": { "type": "number" },
"permissions": { "type": "object" }
}
}))
}
}