use crate::ast::Literal;
use crate::resources::{registry, ParamValue, ResourceDefinition};
use crate::types::{ResolvedType, TypedArg, TypedExprKind};
pub fn resolve_member(module: &str, member: &str) -> ResolvedType {
let registry = registry::get();
if let Some(def) = registry.get(module, member) {
return build_function_type(def);
}
match module {
"IAM" => resolve_iam_member(member),
"EC2" => resolve_ec2_member(member),
"Secret" => resolve_secret_member(member),
_ => ResolvedType::Unknown,
}
}
fn build_function_type(def: &ResourceDefinition) -> ResolvedType {
let params: Vec<(String, ResolvedType)> = def
.all_params()
.iter()
.map(|p| (p.name.to_string(), p.param_type.clone()))
.collect();
ResolvedType::Function {
params,
returns: Box::new(def.returns.clone()),
}
}
pub fn resolve_return_type(
module_type: &ResolvedType,
function: &str,
_args: &[TypedArg],
) -> ResolvedType {
match module_type {
ResolvedType::Module(name) => {
let registry = registry::get();
if let Some(def) = registry.get(name, function) {
return def.returns.clone();
}
match name.as_str() {
"IAM" => resolve_iam_return(function),
"EC2" => resolve_ec2_return(function),
"Secret" => resolve_secret_return(function),
_ => ResolvedType::Unknown,
}
}
_ => ResolvedType::Unknown,
}
}
pub fn get_function_signature(
module: &str,
function: &str,
) -> Option<(Vec<(String, ResolvedType)>, ResolvedType)> {
let member_type = resolve_member(module, function);
match member_type {
ResolvedType::Function { params, returns } => Some((params, *returns)),
_ => None,
}
}
pub fn get_resource_definition(
module: &str,
function: &str,
) -> Option<&'static ResourceDefinition> {
registry::get().get(module, function)
}
#[derive(Debug, Clone)]
pub struct SecurityParamInfo {
pub param_name: &'static str,
pub span: crate::ast::Span,
}
pub fn check_security_params(
module: &str,
function: &str,
args: &[TypedArg],
) -> Option<SecurityParamInfo> {
let registry = registry::get();
let def = registry.get(module, function)?;
for arg in args {
if let Some(ref name) = arg.name {
if let Some(value) = typed_expr_to_param_value(&arg.value.kind) {
if let Some(param_name) = def.check_security(name, &value) {
return Some(SecurityParamInfo {
param_name,
span: arg.value.span,
});
}
}
}
}
None
}
#[derive(Debug, Clone)]
pub struct PreferredParamInfo {
pub param_name: &'static str,
pub recommended: String,
pub span: crate::ast::Span, }
pub fn check_preferred_params(
module: &str,
function: &str,
args: &[TypedArg],
) -> Vec<PreferredParamInfo> {
let registry = registry::get();
let def = match registry.get(module, function) {
Some(d) => d,
None => return Vec::new(),
};
let mut overrides = Vec::new();
for arg in args {
if let Some(ref name) = arg.name {
if let Some(value) = typed_expr_to_param_value(&arg.value.kind) {
if let Some((param_name, recommended)) = def.check_preferred(name, &value) {
let recommended_str = match recommended {
ParamValue::Bool(b) => b.to_string(),
ParamValue::String(s) => s.clone(),
ParamValue::Number(n) => n.to_string(),
ParamValue::None => "none".to_string(),
};
overrides.push(PreferredParamInfo {
param_name,
recommended: recommended_str,
span: arg.value.span, });
}
}
}
}
overrides
}
#[derive(Debug, Clone)]
pub struct UnknownParamInfo {
pub param_name: String,
pub span: crate::ast::Span,
pub suggestion: Option<&'static str>,
pub known_params: Vec<&'static str>,
}
pub fn check_unknown_params(
module: &str,
function: &str,
args: &[TypedArg],
) -> Vec<UnknownParamInfo> {
let registry = registry::get();
let def = match registry.get(module, function) {
Some(d) => d,
None => return Vec::new(),
};
let mut unknown = Vec::new();
let known_params: Vec<&'static str> = def
.required_params
.iter()
.map(|p| p.name)
.chain(def.optional_params.iter().map(|p| p.name))
.chain(def.security_params.iter().map(|p| p.param.name))
.chain(def.preferred_params.iter().map(|p| p.param.name))
.collect();
for arg in args {
if let Some(ref name) = arg.name {
if !known_params.contains(&name.as_str()) {
let suggestion = find_similar_param(name, &known_params);
unknown.push(UnknownParamInfo {
param_name: name.clone(),
span: arg.value.span,
suggestion,
known_params: known_params.clone(),
});
}
}
}
unknown
}
fn find_similar_param<'a>(name: &str, known: &[&'a str]) -> Option<&'a str> {
let name_lower = name.to_lowercase();
for ¶m in known {
let param_lower = param.to_lowercase();
let distance = levenshtein_distance(&name_lower, ¶m_lower);
if distance <= 2 && distance < name.len() / 2 {
return Some(param);
}
}
None
}
fn levenshtein_distance(a: &str, b: &str) -> usize {
let a_chars: Vec<char> = a.chars().collect();
let b_chars: Vec<char> = b.chars().collect();
let m = a_chars.len();
let n = b_chars.len();
if m == 0 {
return n;
}
if n == 0 {
return m;
}
let mut prev: Vec<usize> = (0..=n).collect();
let mut curr = vec![0; n + 1];
for i in 1..=m {
curr[0] = i;
for j in 1..=n {
let cost = if a_chars[i - 1] == b_chars[j - 1] {
0
} else {
1
};
curr[j] = (prev[j] + 1).min((curr[j - 1] + 1).min(prev[j - 1] + cost));
}
std::mem::swap(&mut prev, &mut curr);
}
prev[n]
}
fn typed_expr_to_param_value(kind: &TypedExprKind) -> Option<ParamValue> {
match kind {
TypedExprKind::Literal(Literal::Bool(b)) => Some(ParamValue::Bool(*b)),
TypedExprKind::Literal(Literal::String(s)) => Some(ParamValue::String(s.clone())),
TypedExprKind::Literal(Literal::Number(n)) => Some(ParamValue::Number(*n)),
_ => None,
}
}
fn resolve_iam_member(member: &str) -> ResolvedType {
match member {
"createRole" => ResolvedType::Function {
params: vec![
("name".to_string(), ResolvedType::String),
("assumeRolePolicy".to_string(), ResolvedType::String),
],
returns: Box::new(ResolvedType::IamRole),
},
"createPolicy" => ResolvedType::Function {
params: vec![
("name".to_string(), ResolvedType::String),
("document".to_string(), ResolvedType::String),
],
returns: Box::new(ResolvedType::IamPolicy),
},
"attachPolicy" => ResolvedType::Function {
params: vec![
("role".to_string(), ResolvedType::IamRole),
("policy".to_string(), ResolvedType::IamPolicy),
],
returns: Box::new(ResolvedType::Void),
},
_ => ResolvedType::Unknown,
}
}
fn resolve_iam_return(function: &str) -> ResolvedType {
match function {
"createRole" => ResolvedType::IamRole,
"createPolicy" => ResolvedType::IamPolicy,
"attachPolicy" => ResolvedType::Void,
_ => ResolvedType::Unknown,
}
}
fn resolve_ec2_member(member: &str) -> ResolvedType {
match member {
"attachInstanceProfile" => ResolvedType::Function {
params: vec![
("instance".to_string(), ResolvedType::String),
("profile".to_string(), ResolvedType::IamRole),
],
returns: Box::new(ResolvedType::Void),
},
_ => ResolvedType::Unknown,
}
}
fn resolve_ec2_return(function: &str) -> ResolvedType {
match function {
"attachInstanceProfile" => ResolvedType::Void,
_ => ResolvedType::Unknown,
}
}
fn resolve_secret_member(member: &str) -> ResolvedType {
match member {
"get" => ResolvedType::Function {
params: vec![("path".to_string(), ResolvedType::String)],
returns: Box::new(ResolvedType::String),
},
_ => ResolvedType::Unknown,
}
}
fn resolve_secret_return(function: &str) -> ResolvedType {
match function {
"get" => ResolvedType::String,
_ => ResolvedType::Unknown,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_s3_member_resolution() {
let t = resolve_member("S3", "createBucket");
assert!(matches!(t, ResolvedType::Function { .. }));
}
#[test]
fn test_s3_return_type() {
let module_type = ResolvedType::Module("S3".to_string());
let t = resolve_return_type(&module_type, "createBucket", &[]);
assert_eq!(t, ResolvedType::Bucket);
}
#[test]
fn test_unknown_member() {
let t = resolve_member("Unknown", "foo");
assert_eq!(t, ResolvedType::Unknown);
}
#[test]
fn test_resource_definition_lookup() {
let def = get_resource_definition("S3", "createBucket");
assert!(def.is_some());
let def = def.unwrap();
assert_eq!(def.required_params.len(), 1);
assert_eq!(def.security_params.len(), 2); assert_eq!(def.preferred_params.len(), 2); }
}