fakecloud-lambda 0.43.0

Lambda implementation for FakeCloud
Documentation
//! `LambdaService` `event_invoke` family — extracted from service.rs by audit-2026-05-19.

use super::*;

impl LambdaService {
    // ── Event invoke ──

    pub(super) fn ev_key(function: &str, qualifier: &str) -> String {
        format!("{function}:{qualifier}")
    }

    pub(super) fn put_function_event_invoke(
        &self,
        function_name: &str,
        req: &AwsRequest,
    ) -> Result<AwsResponse, AwsServiceError> {
        let body = body(req);
        let qualifier = parse_qualifier(req);
        let function_arn = format!(
            "arn:aws:lambda:{}:{}:function:{}",
            self.region_for(&req.account_id),
            req.account_id,
            function_name
        );
        // Validate Smithy ranges before persisting:
        //   MaximumEventAgeInSeconds: 60..=21600
        //   MaximumRetryAttempts:     0..=2
        // MaximumEventAgeInSeconds is optional: AWS only reports it once set.
        // Store 0 as the "unset" sentinel (0 is outside the valid 60..21600
        // range) so GetFunctionEventInvokeConfig can omit it, which the
        // Terraform resource reads back as 0.
        let event_age = body["MaximumEventAgeInSeconds"].as_i64().unwrap_or(0);
        if event_age != 0 && !(60..=21600).contains(&event_age) {
            return Err(AwsServiceError::aws_error(
                StatusCode::BAD_REQUEST,
                "InvalidParameterValueException",
                format!(
                    "MaximumEventAgeInSeconds must be 60..21600 (got {})",
                    event_age
                ),
            ));
        }
        let retries = body["MaximumRetryAttempts"].as_i64().unwrap_or(2);
        if !(0..=2).contains(&retries) {
            return Err(AwsServiceError::aws_error(
                StatusCode::BAD_REQUEST,
                "InvalidParameterValueException",
                format!("MaximumRetryAttempts must be 0..2 (got {})", retries),
            ));
        }
        let cfg = EventInvokeConfig {
            function_arn: function_arn.clone(),
            maximum_event_age: event_age,
            maximum_retry_attempts: retries,
            destination_config: body.get("DestinationConfig").cloned(),
            last_modified: Utc::now(),
        };
        let mut accounts = self.state.write();
        let state = accounts.get_or_create(&req.account_id);
        state
            .event_invoke_configs
            .insert(Self::ev_key(function_name, &qualifier), cfg.clone());
        ok(event_invoke_json(&cfg))
    }

    /// `UpdateFunctionEventInvokeConfig` merges: only the fields present in the
    /// request are overwritten; anything omitted (notably `DestinationConfig`)
    /// keeps its stored value. This is the difference from Put, which replaces
    /// the whole config. Updating a non-existent config is a not-found error.
    pub(super) fn update_function_event_invoke(
        &self,
        function_name: &str,
        req: &AwsRequest,
    ) -> Result<AwsResponse, AwsServiceError> {
        let body = body(req);
        let qualifier = parse_qualifier(req);
        let key = Self::ev_key(function_name, &qualifier);

        // Validate provided ranges before touching state.
        if let Some(event_age) = body.get("MaximumEventAgeInSeconds").and_then(Value::as_i64) {
            if !(60..=21600).contains(&event_age) {
                return Err(AwsServiceError::aws_error(
                    StatusCode::BAD_REQUEST,
                    "InvalidParameterValueException",
                    format!("MaximumEventAgeInSeconds must be 60..21600 (got {event_age})"),
                ));
            }
        }
        if let Some(retries) = body.get("MaximumRetryAttempts").and_then(Value::as_i64) {
            if !(0..=2).contains(&retries) {
                return Err(AwsServiceError::aws_error(
                    StatusCode::BAD_REQUEST,
                    "InvalidParameterValueException",
                    format!("MaximumRetryAttempts must be 0..2 (got {retries})"),
                ));
            }
        }

        let mut accounts = self.state.write();
        let state = accounts.get_or_create(&req.account_id);
        let cfg = state
            .event_invoke_configs
            .get_mut(&key)
            .ok_or_else(|| not_found("EventInvokeConfig", function_name))?;

        if let Some(event_age) = body.get("MaximumEventAgeInSeconds").and_then(Value::as_i64) {
            cfg.maximum_event_age = event_age;
        }
        if let Some(retries) = body.get("MaximumRetryAttempts").and_then(Value::as_i64) {
            cfg.maximum_retry_attempts = retries;
        }
        if body.get("DestinationConfig").is_some() {
            cfg.destination_config = body.get("DestinationConfig").cloned();
        }
        cfg.last_modified = Utc::now();
        let snapshot = cfg.clone();
        ok(event_invoke_json(&snapshot))
    }

    pub(super) fn get_function_event_invoke(
        &self,
        function_name: &str,
        req: &AwsRequest,
    ) -> Result<AwsResponse, AwsServiceError> {
        let qualifier = parse_qualifier(req);
        let region = self.region_for(&req.account_id);
        self.with_state_read(&req.account_id, &region, |state| {
            state
                .event_invoke_configs
                .get(&Self::ev_key(function_name, &qualifier))
                .map(|c| ok(event_invoke_json(c)))
                .unwrap_or_else(|| Err(not_found("EventInvokeConfig", function_name)))
        })
    }

    pub(super) fn delete_function_event_invoke(
        &self,
        function_name: &str,
        req: &AwsRequest,
    ) -> Result<AwsResponse, AwsServiceError> {
        let qualifier = parse_qualifier(req);
        let mut accounts = self.state.write();
        let state = accounts.get_or_create(&req.account_id);
        state
            .event_invoke_configs
            .remove(&Self::ev_key(function_name, &qualifier));
        empty()
    }

    pub(super) fn list_function_event_invoke(
        &self,
        function_name: &str,
        account_id: &str,
    ) -> Result<AwsResponse, AwsServiceError> {
        let region = self.region_for(account_id);
        self.with_state_read(account_id, &region, |state| {
            let prefix = format!("{function_name}:");
            let configs: Vec<Value> = state
                .event_invoke_configs
                .iter()
                .filter(|(k, _)| k.starts_with(&prefix))
                .map(|(_, c)| event_invoke_json(c))
                .collect();
            ok(json!({"FunctionEventInvokeConfigs": configs}))
        })
    }
}