Skip to main content

stack_deploy/lambda/
raw.rs

1#[derive(Clone, Debug, clap::Parser)]
2pub struct App {
3    #[clap(subcommand)]
4    command: Command,
5}
6
7#[derive(Clone, Debug, PartialEq)]
8pub struct FunctionName(String);
9
10impl std::str::FromStr for FunctionName {
11    type Err = &'static str;
12
13    fn from_str(input: &str) -> Result<FunctionName, Self::Err> {
14        Ok(Self(String::from(input)))
15    }
16}
17
18impl From<&FunctionName> for String {
19    fn from(value: &FunctionName) -> Self {
20        value.0.clone()
21    }
22}
23
24impl App {
25    pub async fn run(&self, lambda: &aws_sdk_lambda::client::Client) {
26        self.command.run(lambda).await
27    }
28}
29
30#[derive(Clone, Debug, clap::ValueEnum)]
31pub enum OutputFormat {
32    JSON,
33    RAW,
34}
35
36impl OutputFormat {
37    fn print(&self, body: aws_sdk_lambda::primitives::Blob) {
38        match self {
39            Self::JSON => Self::print_json(body),
40            Self::RAW => Self::print_raw(body),
41        }
42    }
43
44    fn print_raw(body: aws_sdk_lambda::primitives::Blob) {
45        println!("{body:#?}")
46    }
47
48    fn print_json(body: aws_sdk_lambda::primitives::Blob) {
49        println!(
50            "{}",
51            serde_json::from_slice::<serde_json::Value>(body.into_inner().as_ref()).unwrap()
52        )
53    }
54}
55
56#[derive(Clone, Debug, clap::Parser)]
57pub enum Command {
58    /// Invoke raw lambda function
59    Invoke {
60        /// Function name to execute
61        #[arg(long = "function-name")]
62        function_name: FunctionName,
63        /// Output format
64        #[arg(long = "output-format")]
65        output_format: OutputFormat,
66    },
67}
68
69impl Command {
70    pub async fn run(&self, lambda: &aws_sdk_lambda::client::Client) {
71        match self {
72            Self::Invoke {
73                function_name,
74                output_format,
75            } => invoke(lambda, function_name, output_format).await,
76        }
77    }
78}
79
80#[must_use]
81pub fn decode_log(log_result: Option<String>) -> String {
82    log_result.map_or_else(
83        || String::from("Log field empty!"),
84        |result| {
85            base64::Engine::decode(&base64::engine::general_purpose::STANDARD, result).map_or_else(
86                |error| format!("Log base64 decode failed!: {error:#?}"),
87                |bytes| {
88                    String::from_utf8(bytes)
89                        .unwrap_or_else(|error| format!("Log utf8 decode failed!: {error:#?}"))
90                },
91            )
92        },
93    )
94}
95
96async fn invoke(
97    lambda: &aws_sdk_lambda::client::Client,
98    function_name: &FunctionName,
99    output_format: &OutputFormat,
100) {
101    let response = lambda
102        .invoke()
103        .function_name(function_name)
104        .log_type(aws_sdk_lambda::types::LogType::Tail)
105        .send()
106        .await;
107
108    match response {
109        Err(error) => panic!(
110            "Lambda function failed to invoke: {:#?}",
111            error.into_service_error()
112        ),
113        Ok(output) => {
114            if let Some(error) = output.function_error {
115                panic!(
116                    "Lambda invoked but errored: Function Error: {:#?}, log: {}",
117                    error,
118                    decode_log(output.log_result)
119                )
120            } else {
121                output_format.print(output.payload.unwrap())
122            }
123        }
124    }
125}