use awsim_core::{AwsError, Body, RequestContext};
use serde_json::{Value, json};
use std::collections::HashMap;
use crate::{
error::{invalid_parameter, resource_conflict, resource_not_found},
state::{LambdaFunction, LambdaState},
util::{decode_zip, now_iso8601, opt_str, require_str, sha256_base64},
};
pub fn function_configuration(f: &LambdaFunction) -> Value {
let env = if f.environment.is_empty() {
json!(null)
} else {
let vars: serde_json::Map<String, Value> = f
.environment
.iter()
.map(|(k, v)| (k.clone(), Value::String(v.clone())))
.collect();
json!({ "Variables": vars })
};
json!({
"FunctionName": f.name,
"FunctionArn": f.arn,
"Runtime": f.runtime,
"Role": f.role,
"Handler": f.handler,
"Description": f.description,
"Timeout": f.timeout,
"MemorySize": f.memory_size,
"CodeSha256": f.code_sha256,
"CodeSize": f.code_size,
"Environment": env,
"Version": f.version,
"LastModified": f.last_modified,
"State": f.state,
"FunctionArn": f.arn,
})
}
fn resolve_code(input: &Value) -> Result<(Option<Vec<u8>>, String, u64), AwsError> {
if let Some(zip_b64) = opt_str(input, "ZipFile") {
let (bytes, hash, size) = decode_zip(zip_b64)?;
return Ok((Some(bytes), hash, size));
}
if let Some(code) = input.get("Code") {
if let Some(zip_b64) = opt_str(code, "ZipFile") {
let (bytes, hash, size) = decode_zip(zip_b64)?;
return Ok((Some(bytes), hash, size));
}
if opt_str(code, "S3Bucket").is_some() {
let placeholder = b"s3-placeholder";
let hash = sha256_base64(placeholder);
return Ok((None, hash, 0));
}
}
if opt_str(input, "S3Bucket").is_some() {
let placeholder = b"s3-placeholder";
let hash = sha256_base64(placeholder);
return Ok((None, hash, 0));
}
Err(invalid_parameter(
"Either ZipFile or S3Bucket/S3Key must be provided in Code",
))
}
pub(crate) fn persist_code(
state: &LambdaState,
function_name: &str,
key: &str,
bytes: Option<Vec<u8>>,
) -> Result<Option<Body>, AwsError> {
let Some(bytes) = bytes else {
return Ok(None);
};
match state.body_store() {
Some(store) => {
let path = store
.write_blob("lambda", function_name, key, &bytes)
.map_err(|e| AwsError::internal(format!("persist function code: {e}")))?;
Ok(Some(Body::OnDisk(path)))
}
None => Ok(Some(Body::InMemory(bytes))),
}
}
pub fn create_function(
state: &LambdaState,
input: &Value,
ctx: &RequestContext,
) -> Result<Value, AwsError> {
let name = require_str(input, "FunctionName")?;
if state.functions.contains_key(name) {
return Err(resource_conflict(format!("Function already exist: {name}")));
}
let role = require_str(input, "Role")?;
let runtime = opt_str(input, "Runtime").map(str::to_string);
let handler = opt_str(input, "Handler").map(str::to_string);
let description = opt_str(input, "Description").unwrap_or("").to_string();
let timeout = input.get("Timeout").and_then(|v| v.as_u64()).unwrap_or(3) as u32;
let memory_size = input
.get("MemorySize")
.and_then(|v| v.as_u64())
.unwrap_or(128) as u32;
let environment: HashMap<String, String> = input
.get("Environment")
.and_then(|e| e.get("Variables"))
.and_then(|v| v.as_object())
.map(|obj| {
obj.iter()
.filter_map(|(k, v)| v.as_str().map(|s| (k.clone(), s.to_string())))
.collect()
})
.unwrap_or_default();
let (code_data, code_sha256, code_size) = resolve_code(input)?;
let code = persist_code(state, name, "$LATEST", code_data)?;
let arn = format!(
"arn:aws:lambda:{}:{}:function:{}",
ctx.region, ctx.account_id, name
);
let now = now_iso8601();
let tags: HashMap<String, String> = input
.get("Tags")
.and_then(|v| v.as_object())
.map(|obj| {
obj.iter()
.filter_map(|(k, v)| v.as_str().map(|s| (k.clone(), s.to_string())))
.collect()
})
.unwrap_or_default();
let func = LambdaFunction {
name: name.to_string(),
arn: arn.clone(),
runtime,
role: role.to_string(),
handler,
description,
timeout,
memory_size,
code_sha256,
code_size,
code,
environment,
version: "$LATEST".to_string(),
versions: vec![],
aliases: HashMap::new(),
last_modified: now,
state: "Active".to_string(),
invocations: vec![],
policy_statements: HashMap::new(),
tags,
reserved_concurrent_executions: None,
provisioned_concurrency: HashMap::new(),
};
let config = function_configuration(&func);
state.functions.insert(name.to_string(), func);
Ok(config)
}
pub fn get_function(
state: &LambdaState,
input: &Value,
_ctx: &RequestContext,
) -> Result<Value, AwsError> {
let name = require_str(input, "FunctionName")?;
let f = state
.functions
.get(name)
.ok_or_else(|| resource_not_found("function", name))?;
let config = function_configuration(&f);
let code_location = format!("http://awsim.local/2015-03-31/functions/{}/code", f.name);
Ok(json!({
"Configuration": config,
"Code": {
"Location": code_location,
"RepositoryType": "S3",
},
}))
}
pub fn get_function_configuration(
state: &LambdaState,
input: &Value,
_ctx: &RequestContext,
) -> Result<Value, AwsError> {
let name = require_str(input, "FunctionName")?;
let f = state
.functions
.get(name)
.ok_or_else(|| resource_not_found("function", name))?;
Ok(function_configuration(&f))
}
pub fn delete_function(state: &LambdaState, input: &Value) -> Result<Value, AwsError> {
let name = require_str(input, "FunctionName")?;
state
.functions
.remove(name)
.ok_or_else(|| resource_not_found("function", name))?;
if let Some(store) = state.body_store()
&& let Err(e) = store.delete_bucket("lambda", name)
{
tracing::warn!(function_name = name, error = %e, "delete persisted function code");
}
Ok(json!({}))
}
pub fn list_functions(
state: &LambdaState,
_input: &Value,
_ctx: &RequestContext,
) -> Result<Value, AwsError> {
let functions: Vec<Value> = state
.functions
.iter()
.map(|f| function_configuration(&f))
.collect();
Ok(json!({ "Functions": functions }))
}
pub fn update_function_code(
state: &LambdaState,
input: &Value,
_ctx: &RequestContext,
) -> Result<Value, AwsError> {
let name = require_str(input, "FunctionName")?;
let (code_data, code_sha256, code_size) = resolve_code(input)?;
let code = persist_code(state, name, "$LATEST", code_data)?;
let mut f = state
.functions
.get_mut(name)
.ok_or_else(|| resource_not_found("function", name))?;
let cache_dir = std::env::temp_dir()
.join("awsim-lambda")
.join(name)
.join("code");
if cache_dir.exists() {
let _ = std::fs::remove_dir_all(&cache_dir);
}
f.code = code;
f.code_sha256 = code_sha256;
f.code_size = code_size;
f.last_modified = now_iso8601();
Ok(function_configuration(&f))
}
pub fn update_function_configuration(
state: &LambdaState,
input: &Value,
_ctx: &RequestContext,
) -> Result<Value, AwsError> {
let name = require_str(input, "FunctionName")?;
let mut f = state
.functions
.get_mut(name)
.ok_or_else(|| resource_not_found("function", name))?;
if let Some(role) = opt_str(input, "Role") {
f.role = role.to_string();
}
if let Some(handler) = opt_str(input, "Handler") {
f.handler = Some(handler.to_string());
}
if let Some(runtime) = opt_str(input, "Runtime") {
f.runtime = Some(runtime.to_string());
}
if let Some(desc) = opt_str(input, "Description") {
f.description = desc.to_string();
}
if let Some(timeout) = input.get("Timeout").and_then(|v| v.as_u64()) {
f.timeout = timeout as u32;
}
if let Some(mem) = input.get("MemorySize").and_then(|v| v.as_u64()) {
f.memory_size = mem as u32;
}
if let Some(env) = input.get("Environment")
&& let Some(vars) = env.get("Variables").and_then(|v| v.as_object())
{
f.environment = vars
.iter()
.filter_map(|(k, v)| v.as_str().map(|s| (k.clone(), s.to_string())))
.collect();
}
f.last_modified = now_iso8601();
Ok(function_configuration(&f))
}