use async_trait::async_trait;
use anyhow::{Result, anyhow};
use serde_json::{json, Value};
use std::collections::HashMap;
use indexmap::IndexMap;
use tracing::{debug, warn};
use crate::types::Document;
use crate::services::aws_estate_service::AwsServiceParser;
pub struct LambdaParser;
impl LambdaParser {
pub fn new() -> Self {
Self
}
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 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 parse_lambda_functions(&self, account_id: &str, functions: &[Value]) -> Result<Vec<Document>> {
let mut documents = Vec::new();
for function in functions {
let function_name = function["name"]
.as_str()
.or_else(|| function["function_name"].as_str())
.or_else(|| function["FunctionName"].as_str())
.unwrap_or("unknown");
let region = function["region"]
.as_str()
.unwrap_or("us-east-1");
let runtime = function["runtime"]
.as_str()
.unwrap_or("unknown");
let timeout = function["timeout"]
.as_u64()
.unwrap_or(0);
let memory_size = function["memory_size"]
.as_u64()
.unwrap_or(0);
let handler = function["handler"]
.as_str()
.unwrap_or("unknown");
let last_modified = function["last_modified"]
.as_str()
.unwrap_or("unknown");
let doc_id = Self::generate_arn(
"lambda",
region,
account_id,
"function",
function_name,
);
let content = format!(
"Lambda Function {} in region {} - Runtime: {} - Memory: {}MB - Timeout: {}s - Handler: {}",
function_name, region, runtime, memory_size, timeout, handler
);
let mut metadata = Self::create_base_metadata(
account_id,
"lambda",
"lambda-function",
Some(region),
);
metadata.insert("function_name".to_string(), json!(function_name));
metadata.insert("runtime".to_string(), json!(runtime));
metadata.insert("timeout".to_string(), json!(timeout));
metadata.insert("memory_size".to_string(), json!(memory_size));
metadata.insert("handler".to_string(), json!(handler));
metadata.insert("last_modified".to_string(), json!(last_modified));
if let Some(code_size) = function.get("code_size").and_then(|v| v.as_u64()) {
metadata.insert("code_size".to_string(), json!(code_size));
}
if let Some(role) = function.get("role").and_then(|v| v.as_str()) {
metadata.insert("role".to_string(), json!(role));
}
if let Some(environment) = function.get("environment") {
metadata.insert("environment".to_string(), environment.clone());
}
if let Some(layers) = function.get("layers") {
metadata.insert("layers".to_string(), layers.clone());
}
if let Some(permissions) = function.get("permissions") {
metadata.insert("iam_permissions".to_string(), permissions.clone());
}
metadata.insert("tags".to_string(), json!({
"FunctionName": function_name,
"Runtime": runtime
}));
let mut doc = Document::new(doc_id, content);
doc.metadata = metadata;
documents.push(doc);
}
Ok(documents)
}
fn parse_layers_summary(&self, account_id: &str, service_data: &Value) -> Result<Option<Document>> {
if let Some(layers_count) = service_data.get("layers_count").and_then(|v| v.as_u64()) {
if layers_count > 0 {
let doc_id = format!("arn:aws:lambda:global:{}:layers-summary", account_id);
let content = format!(
"Lambda Layers Summary: {} total layers available for reuse | Cloud Provider: aws",
layers_count
);
let mut metadata = Self::create_base_metadata(
account_id,
"lambda",
"lambda-layers-summary",
Some("global"),
);
metadata.insert("layers_count".to_string(), json!(layers_count));
let mut doc = Document::new(doc_id, content);
doc.metadata = metadata;
return Ok(Some(doc));
}
}
Ok(None)
}
fn parse_event_sources_summary(&self, account_id: &str, service_data: &Value) -> Result<Option<Document>> {
if let Some(event_sources_count) = service_data.get("event_sources_count").and_then(|v| v.as_u64()) {
if event_sources_count > 0 {
let doc_id = format!("arn:aws:lambda:global:{}:event-sources-summary", account_id);
let content = format!(
"Lambda Event Sources Summary: {} event source mappings configured | Cloud Provider: aws",
event_sources_count
);
let mut metadata = Self::create_base_metadata(
account_id,
"lambda",
"lambda-event-sources-summary",
Some("global"),
);
metadata.insert("event_sources_count".to_string(), json!(event_sources_count));
let mut doc = Document::new(doc_id, content);
doc.metadata = metadata;
return Ok(Some(doc));
}
}
Ok(None)
}
}
#[async_trait]
impl AwsServiceParser for LambdaParser {
fn service_name(&self) -> &str {
"lambda"
}
fn can_parse(&self, service_data: &Value) -> bool {
service_data.is_object() && (
service_data.get("functions").is_some() ||
service_data.get("layers_count").is_some() ||
service_data.get("event_sources_count").is_some()
)
}
async fn parse(&self, account_id: &str, service_data: &Value) -> Result<Vec<Document>> {
debug!("🔍 Lambda parser processing data for account: {}", account_id);
let mut documents = Vec::new();
if let Some(functions) = service_data.get("functions").and_then(|v| v.as_array()) {
if !functions.is_empty() {
let mut function_docs = self.parse_lambda_functions(account_id, functions)?;
documents.append(&mut function_docs);
debug!("✅ Parsed {} Lambda functions", functions.len());
}
}
if let Some(layers_doc) = self.parse_layers_summary(account_id, service_data)? {
documents.push(layers_doc);
debug!("✅ Parsed Lambda layers summary");
}
if let Some(event_sources_doc) = self.parse_event_sources_summary(account_id, service_data)? {
documents.push(event_sources_doc);
debug!("✅ Parsed Lambda event sources summary");
}
if documents.is_empty() {
warn!("🟡 Lambda parser found no parseable data");
} else {
debug!("🎉 Lambda parser generated {} documents", documents.len());
}
Ok(documents)
}
fn get_data_schema(&self) -> Option<Value> {
Some(json!({
"type": "object",
"properties": {
"functions": {
"type": "array",
"items": {
"type": "object",
"required": ["function_name", "region", "runtime", "handler"],
"properties": {
"function_name": {"type": "string"},
"region": {"type": "string"},
"runtime": {"type": "string"},
"timeout": {"type": "number"},
"memory_size": {"type": "number"},
"handler": {"type": "string"},
"last_modified": {"type": "string"},
"code_size": {"type": "number"},
"role": {"type": "string"},
"environment": {"type": "object"},
"layers": {"type": "array"},
"permissions": {"type": "array"}
}
}
},
"layers_count": {"type": "number"},
"event_sources_count": {"type": "number"}
}
}))
}
}