use crate::{Config, Error};
use http::{HeaderMap, HeaderValue};
use serde::{Deserialize, Serialize};
use std::{collections::HashMap, convert::TryFrom};
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct Diagnostic {
pub(crate) error_type: String,
pub(crate) error_message: String,
}
#[test]
fn round_trip_lambda_error() -> Result<(), Error> {
use serde_json::{json, Value};
let expected = json!({
"errorType": "InvalidEventDataError",
"errorMessage": "Error parsing event data.",
});
let actual: Diagnostic = serde_json::from_value(expected.clone())?;
let actual: Value = serde_json::to_value(actual)?;
assert_eq!(expected, actual);
Ok(())
}
#[derive(Debug, Clone, PartialEq)]
pub struct RequestId(pub String);
#[derive(Debug, Clone, PartialEq)]
pub struct InvocationDeadline(pub u64);
#[derive(Debug, Clone, PartialEq)]
pub struct FunctionArn(pub String);
#[derive(Debug, Clone, PartialEq)]
pub struct XRayTraceId(pub String);
#[derive(Debug, Clone, PartialEq)]
struct MobileClientContext(String);
#[derive(Debug, Clone, PartialEq)]
struct MobileClientIdentity(String);
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
pub struct ClientContext {
#[serde(default)]
pub client: ClientApplication,
#[serde(default)]
pub custom: HashMap<String, String>,
#[serde(default)]
pub environment: HashMap<String, String>,
}
#[derive(Serialize, Deserialize, Default, Clone, Debug, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct ClientApplication {
pub installation_id: String,
pub app_title: String,
pub app_version_name: String,
pub app_version_code: String,
pub app_package_name: String,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
pub struct CognitoIdentity {
pub identity_id: String,
pub identity_pool_id: String,
}
#[non_exhaustive]
#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)]
pub struct Context {
pub request_id: String,
pub deadline: u64,
pub invoked_function_arn: String,
pub xray_trace_id: String,
pub client_context: Option<ClientContext>,
pub identity: Option<CognitoIdentity>,
pub env_config: Config,
}
impl TryFrom<HeaderMap> for Context {
type Error = Error;
fn try_from(headers: HeaderMap) -> Result<Self, Self::Error> {
let client_context: Option<ClientContext> = if let Some(value) = headers.get("lambda-runtime-client-context") {
serde_json::from_str(value.to_str()?)?
} else {
None
};
let identity: Option<CognitoIdentity> = if let Some(value) = headers.get("lambda-runtime-cognito-identity") {
serde_json::from_str(value.to_str()?)?
} else {
None
};
let ctx = Context {
request_id: headers
.get("lambda-runtime-aws-request-id")
.expect("missing lambda-runtime-aws-request-id header")
.to_str()?
.to_owned(),
deadline: headers
.get("lambda-runtime-deadline-ms")
.expect("missing lambda-runtime-deadline-ms header")
.to_str()?
.parse::<u64>()?,
invoked_function_arn: headers
.get("lambda-runtime-invoked-function-arn")
.unwrap_or(&HeaderValue::from_static(
"No header lambda-runtime-invoked-function-arn found.",
))
.to_str()?
.to_owned(),
xray_trace_id: headers
.get("lambda-runtime-trace-id")
.unwrap_or(&HeaderValue::from_static(""))
.to_str()?
.to_owned(),
client_context,
identity,
..Default::default()
};
Ok(ctx)
}
}
#[derive(Clone, Debug)]
pub struct LambdaEvent<T> {
pub payload: T,
pub context: Context,
}
impl<T> LambdaEvent<T> {
pub fn new(payload: T, context: Context) -> Self {
Self { payload, context }
}
pub fn into_parts(self) -> (T, Context) {
(self.payload, self.context)
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn context_with_expected_values_and_types_resolves() {
let mut headers = HeaderMap::new();
headers.insert("lambda-runtime-aws-request-id", HeaderValue::from_static("my-id"));
headers.insert("lambda-runtime-deadline-ms", HeaderValue::from_static("123"));
headers.insert(
"lambda-runtime-invoked-function-arn",
HeaderValue::from_static("arn::myarn"),
);
headers.insert("lambda-runtime-trace-id", HeaderValue::from_static("arn::myarn"));
let tried = Context::try_from(headers);
assert!(tried.is_ok());
}
#[test]
fn context_with_certain_missing_headers_still_resolves() {
let mut headers = HeaderMap::new();
headers.insert("lambda-runtime-aws-request-id", HeaderValue::from_static("my-id"));
headers.insert("lambda-runtime-deadline-ms", HeaderValue::from_static("123"));
let tried = Context::try_from(headers);
assert!(tried.is_ok());
}
#[test]
fn context_with_client_context_resolves() {
let mut custom = HashMap::new();
custom.insert("key".to_string(), "value".to_string());
let mut environment = HashMap::new();
environment.insert("key".to_string(), "value".to_string());
let client_context = ClientContext {
client: ClientApplication {
installation_id: String::new(),
app_title: String::new(),
app_version_name: String::new(),
app_version_code: String::new(),
app_package_name: String::new(),
},
custom,
environment,
};
let client_context_str = serde_json::to_string(&client_context).unwrap();
let mut headers = HeaderMap::new();
headers.insert("lambda-runtime-aws-request-id", HeaderValue::from_static("my-id"));
headers.insert("lambda-runtime-deadline-ms", HeaderValue::from_static("123"));
headers.insert(
"lambda-runtime-client-context",
HeaderValue::from_str(&client_context_str).unwrap(),
);
let tried = Context::try_from(headers);
assert!(tried.is_ok());
let tried = tried.unwrap();
assert!(tried.client_context.is_some());
assert_eq!(tried.client_context.unwrap(), client_context);
}
#[test]
fn context_with_empty_client_context_resolves() {
let mut headers = HeaderMap::new();
headers.insert("lambda-runtime-aws-request-id", HeaderValue::from_static("my-id"));
headers.insert("lambda-runtime-deadline-ms", HeaderValue::from_static("123"));
headers.insert("lambda-runtime-client-context", HeaderValue::from_static("{}"));
let tried = Context::try_from(headers);
assert!(tried.is_ok());
assert!(tried.unwrap().client_context.is_some());
}
#[test]
fn context_with_identity_resolves() {
let cognito_identity = CognitoIdentity {
identity_id: String::new(),
identity_pool_id: String::new(),
};
let cognito_identity_str = serde_json::to_string(&cognito_identity).unwrap();
let mut headers = HeaderMap::new();
headers.insert("lambda-runtime-aws-request-id", HeaderValue::from_static("my-id"));
headers.insert("lambda-runtime-deadline-ms", HeaderValue::from_static("123"));
headers.insert(
"lambda-runtime-cognito-identity",
HeaderValue::from_str(&cognito_identity_str).unwrap(),
);
let tried = Context::try_from(headers);
assert!(tried.is_ok());
let tried = tried.unwrap();
assert!(tried.identity.is_some());
assert_eq!(tried.identity.unwrap(), cognito_identity);
}
#[test]
fn context_with_bad_deadline_type_is_err() {
let mut headers = HeaderMap::new();
headers.insert("lambda-runtime-aws-request-id", HeaderValue::from_static("my-id"));
headers.insert(
"lambda-runtime-deadline-ms",
HeaderValue::from_static("BAD-Type,not <u64>"),
);
headers.insert(
"lambda-runtime-invoked-function-arn",
HeaderValue::from_static("arn::myarn"),
);
headers.insert("lambda-runtime-trace-id", HeaderValue::from_static("arn::myarn"));
let tried = Context::try_from(headers);
assert!(tried.is_err());
}
#[test]
fn context_with_bad_client_context_is_err() {
let mut headers = HeaderMap::new();
headers.insert("lambda-runtime-aws-request-id", HeaderValue::from_static("my-id"));
headers.insert("lambda-runtime-deadline-ms", HeaderValue::from_static("123"));
headers.insert(
"lambda-runtime-client-context",
HeaderValue::from_static("BAD-Type,not JSON"),
);
let tried = Context::try_from(headers);
assert!(tried.is_err());
}
#[test]
fn context_with_empty_identity_is_err() {
let mut headers = HeaderMap::new();
headers.insert("lambda-runtime-aws-request-id", HeaderValue::from_static("my-id"));
headers.insert("lambda-runtime-deadline-ms", HeaderValue::from_static("123"));
headers.insert("lambda-runtime-cognito-identity", HeaderValue::from_static("{}"));
let tried = Context::try_from(headers);
assert!(tried.is_err());
}
#[test]
fn context_with_bad_identity_is_err() {
let mut headers = HeaderMap::new();
headers.insert("lambda-runtime-aws-request-id", HeaderValue::from_static("my-id"));
headers.insert("lambda-runtime-deadline-ms", HeaderValue::from_static("123"));
headers.insert(
"lambda-runtime-cognito-identity",
HeaderValue::from_static("BAD-Type,not JSON"),
);
let tried = Context::try_from(headers);
assert!(tried.is_err());
}
#[test]
#[should_panic]
#[allow(unused_must_use)]
fn context_with_missing_request_id_should_panic() {
let mut headers = HeaderMap::new();
headers.insert("lambda-runtime-aws-request-id", HeaderValue::from_static("my-id"));
headers.insert(
"lambda-runtime-invoked-function-arn",
HeaderValue::from_static("arn::myarn"),
);
headers.insert("lambda-runtime-trace-id", HeaderValue::from_static("arn::myarn"));
Context::try_from(headers);
}
#[test]
#[should_panic]
#[allow(unused_must_use)]
fn context_with_missing_deadline_should_panic() {
let mut headers = HeaderMap::new();
headers.insert("lambda-runtime-deadline-ms", HeaderValue::from_static("123"));
headers.insert(
"lambda-runtime-invoked-function-arn",
HeaderValue::from_static("arn::myarn"),
);
headers.insert("lambda-runtime-trace-id", HeaderValue::from_static("arn::myarn"));
Context::try_from(headers);
}
}
impl Context {
pub fn with_config(self, config: &Config) -> Self {
Self {
env_config: config.clone(),
..self
}
}
}