1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181
// Copyright (c) 2020 iliana destroyer of worlds <iliana@buttslol.net> // SPDX-License-Identifier: MIT //! A minimalist [AWS Lambda][lambda] [runtime] for Rust. //! //! ```rust,no_run //! fn main() -> ! { //! minlambda::run_ok(|_: serde::de::IgnoredAny| "Hello, world!") //! } //! ``` //! //! [lambda]: https://aws.amazon.com/lambda/ //! [runtime]: https://docs.aws.amazon.com/lambda/latest/dg/runtimes-custom.html //! //! # What it does //! //! minlambda implements the [AWS Lambda runtime interface][interface], deserializing events and //! serializing responses with [Serde JSON][json]. //! //! To communicate with the runtime API over HTTP, minlambda uses a purpose-built HTTP client. //! //! [interface]: https://docs.aws.amazon.com/lambda/latest/dg/runtimes-api.html //! [json]: https://docs.rs/serde_json //! //! # What it doesn't //! //! minlambda doesn't parse [response headers in the invocation event][next] (other than the //! request ID). This includes the function deadline, function ARN, AWS X-Ray tracing header, or //! additional AWS Mobile SDK data. The crate author has never needed these and, well, this is a //! minimal runtime. //! //! minlambda doesn't run your handler in an async runtime. If you're using async code, you can //! create a runtime outside of `lambda::run` and call its blocking function (e.g. Tokio's //! `Runtime::block_on`). [An example for Tokio is available.][tokio-example] //! //! [next]: https://docs.aws.amazon.com/lambda/latest/dg/runtimes-api.html#runtimes-api-next //! [tokio-example]: https://github.com/iliana/minlambda/blob/matriarch/examples/async.rs //! //! # When not to use this //! //! Probably most of the time. //! //! If you're using Lambda to interact with other AWS services, which is very likely, you are //! probably using an SDK (such as [Rusoto]) that probably relies on [hyper] and [Tokio], and //! you're not really reducing your total dependency closure compared to the [AWS Labs //! runtime][awslabs]. //! //! The HTTP client was built to work with Lambda's runtime API, and not to be a generic //! RFC-compliant HTTP client; if Lambda's underlying interface subtly changes, this runtime could //! break unexpectedly. (This probably won't happen: we believe that the subset of the HTTP spec we //! implement is by the book.) //! //! [Rusoto]: https://github.com/rusoto/rusoto //! [hyper]: https://docs.rs/hyper //! [tokio]: https://docs.rs/tokio //! [awslabs]: https://github.com/awslabs/aws-lambda-rust-runtime //! //! # When to use this //! //! You like simple things, or your code already has minimal dependencies. //! //! # Examples //! //! [Some lovely examples are available in our repository.][examples] //! //! [examples]: https://github.com/iliana/minlambda/tree/matriarch/examples //! //! # Building Lambda functions //! //! Building binaries that actually work in the Lambda execution environment is a bit of an art, as //! it contains stable (old) versions of glibc and the like. Your compiler is probably targeting a //! system with newer shared libraries and symbol versions than what the execution environment has //! available, resulting in [cryptic dynamic linker errors at runtime][cryptic]. //! //! If you find the musl libc toolchain reasonable to work with, [building a fully static binary is //! probably the way to go][musl]. If you find containers reasonable to work with, [using //! softprops/lambda-rust is probably the way to go][container]. //! //! [cryptic]: https://github.com/awslabs/aws-lambda-rust-runtime/issues/17 //! [musl]: https://doc.rust-lang.org/edition-guide/rust-2018/platform-and-target-support/musl-support-for-fully-static-binaries.html //! [container]: https://github.com/softprops/lambda-rust //! //! # Disclaimer //! //! The author of this crate works at AWS, but this is not an official AWS project, nor does it //! necessarily represent opinions of or recommended best-practices on AWS. #![forbid(unsafe_code)] #![deny( future_incompatible, missing_copy_implementations, missing_debug_implementations, missing_docs, rust_2018_idioms, unused )] #![warn(clippy::pedantic)] mod http; use serde::{de::DeserializeOwned, Serialize}; use std::net::SocketAddr; /// Retrieves invocation events, calls your handler, and sends back response data within the Lambda /// execution environment. /// /// This function [does not return][diverging] (Lambda will kill processes when unused). /// /// # Panics /// /// This function panics on two fatal error conditions: /// /// * Failing to parse the `AWS_LAMBDA_RUNTIME_API` environment variable as a [`SocketAddr`]. /// * Failing to report an error to the runtime interface. /// /// [diverging]: https://doc.rust-lang.org/stable/rust-by-example/fn/diverging.html pub fn run<F, D, S, E>(handler: F) -> ! where F: FnMut(D) -> Result<S, E>, D: DeserializeOwned, S: Serialize, E: std::fmt::Display + 'static, { let addr: SocketAddr = std::env::var("AWS_LAMBDA_RUNTIME_API") .expect("could not get $AWS_LAMBDA_RUNTIME_API") .parse() .expect("could not parse $AWS_LAMBDA_RUNTIME_API as SocketAddr"); let mut handler = handler; loop { if let Err(inner_err) = run_inner(addr, &mut handler) { if let Err(init_err) = http::post_error( addr, "init/error", "minlambda::Error", &inner_err.to_string(), ) { panic!( "failed to report initialization error: {:?}\ncaused by: {:?}", init_err, inner_err ); } } } } /// [`run`], for handlers that don't return [`Result`]. /// /// This function is otherwise the same as `run`: it does not return and will panic on certain /// unrecoverable errors. pub fn run_ok<F, D, S>(handler: F) -> ! where F: FnMut(D) -> S, D: DeserializeOwned, S: Serialize, { let mut handler = handler; run(|event| Result::Ok::<_, std::convert::Infallible>(handler(event))) } fn run_inner<F, D, S, E>(addr: SocketAddr, handler: &mut F) -> std::io::Result<()> where F: FnMut(D) -> Result<S, E>, D: DeserializeOwned, S: Serialize, E: std::fmt::Display + 'static, { http::get(addr, "invocation/next").and_then(|(request_id, body)| match handler(body) { Ok(response) => http::post( addr, &format!("invocation/{}/response", request_id), &response, ), Err(err) => http::post_error( addr, &format!("invocation/{}/error", request_id), std::any::type_name::<E>(), &err.to_string(), ), }) }