Skip to content

Instantly share code, notes, and snippets.

@naftulikay
Last active September 14, 2018 00:41
Show Gist options
  • Save naftulikay/99f4ab201c7efddb2210930f411e60e4 to your computer and use it in GitHub Desktop.
Save naftulikay/99f4ab201c7efddb2210930f411e60e4 to your computer and use it in GitHub Desktop.
Strongly Typed Lambda JSON with Serde
use lambda::HttpEventRequestContext;
use std::collections::HashMap;
use std::fmt;
/// The types of authorization events that a Lambda function can handle when used with API gateway as an authorizer
#[derive(Debug,Eq,PartialEq,Serialize,Deserialize)]
pub enum EventType {
#[serde(rename="REQUEST")]
Request,
#[serde(rename="TOKEN")]
Token
}
/// This is an authorization event from API gateway invoking lambda as as authorizer
#[derive(Serialize,Deserialize)]
pub struct Event {
pub headers: Option<HashMap<String, String>>,
#[serde(rename="httpMethod")]
pub http_method: String,
#[serde(rename="methodArn")]
pub method_arn: String,
pub path: String,
#[serde(rename="pathParameters")]
pub path_parameters: Option<HashMap<String, String>>,
#[serde(rename="queryStringParameters")]
pub query_string_parameters: Option<HashMap<String, String>>,
pub resource: String,
#[serde(rename="requestContext")]
pub request_context: HttpEventRequestContext,
#[serde(rename="stageVariables")]
pub stage_variables: Option<HashMap<String, String>>,
#[serde(rename="type")]
pub event_type: EventType,
}
/// A choice between allowing and denying access to a given resource from a Lambda API Gateway authorizer function.
#[derive(Serialize,Deserialize)]
pub enum Effect {
Allow,
Deny
}
impl fmt::Display for Effect {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
Effect::Allow => write!(f, "Allow"),
Effect::Deny => write!(f, "Deny")
}
}
}
use serde_json::Value;
use std::collections::HashMap;
/// A CloudWatch event received by a Lambda function.
#[derive(Serialize,Deserialize)]
pub struct Event {
pub account: String,
pub detail: HashMap<String,Value>,
#[serde(rename="detail-type")]
pub detail_type: String,
pub id: String,
pub region: String,
pub resources: Vec<String>,
pub source: String,
pub time: String,
pub version: String,
}
use chrono::prelude::*;
use std::fmt;
use std::collections::HashMap;
use serde_qs as qs;
/// HttpMethod is what it sounds like.
#[derive(Debug,Eq,PartialEq,Serialize,Deserialize)]
pub enum HttpMethod {
HEAD,
GET,
POST,
PUT,
OPTIONS,
DELETE,
}
impl fmt::Display for HttpMethod {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", match *self {
HttpMethod::HEAD => "HEAD",
HttpMethod::GET => "GET",
HttpMethod::POST => "POST",
HttpMethod::PUT => "PUT",
HttpMethod::OPTIONS => "OPTIONS",
HttpMethod::DELETE => "DELETE"
})
}
}
/// HttpEvent represents an HTTP request made to API gateway that results in a call to lambda.
#[derive(Serialize,Deserialize)]
pub struct HttpEvent {
pub body: Option<String>,
pub headers: Option<HashMap<String, String>>,
#[serde(rename="httpMethod")]
pub http_method: HttpMethod,
#[serde(rename="isBase64Encoded")]
pub is_base64_encoded: bool,
pub path: String,
#[serde(rename="pathParameters")]
pub path_parameters: Option<HashMap<String, String>>,
#[serde(rename="queryStringParameters")]
pub query_string_parameters: Option<HashMap<String, String>>,
pub resource: String,
#[serde(rename="requestContext")]
pub request_context: HttpEventRequestContext,
#[serde(rename="stageVariables")]
pub stage_variables: Option<HashMap<String, String>>,
}
impl HttpEvent {
pub fn get_header(&self, key: &str) -> Option<&str> {
match self.headers {
Some(ref h) => h.get(key).map(|s| s.as_str()),
None => None,
}
}
}
impl fmt::Display for HttpEvent {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{protocol} {method} {path}{querystring} (body? {body})",
protocol = match self.request_context.protocol {
Some(ref s) => s.as_str(),
None => "(unknown)"
},
method = self.http_method,
path = self.path,
querystring = qs::to_string(&self.query_string_parameters).unwrap_or(String::new()),
body = match self.body {
Some(_) => true,
None => false
}
)
}
}
/// An HttpEventRequestContext represents the complex request context in an HttpEvent.
#[derive(Serialize,Deserialize)]
pub struct HttpEventRequestContext {
#[serde(rename="accountId")]
pub account_id: String,
#[serde(rename="apiId")]
pub api_id: String,
#[serde(rename="httpMethod")]
pub http_method: HttpMethod,
pub identity: HashMap<String, Option<String>>,
pub path: String,
pub protocol: Option<String>,
#[serde(rename="requestId")]
pub request_id: String,
#[serde(rename="requestTime")]
pub request_time: Option<String>,
#[serde(rename="requestTimeEpoch")]
pub request_time_epoch: Option<u64>,
#[serde(rename="resourceId")]
pub resource_id: String,
#[serde(rename="resourcePath")]
pub resource_path: String,
pub stage: String,
}
impl HttpEventRequestContext {
pub fn time(&self) -> Option<DateTime<Utc>> {
// TODO Utc::datetime_from_str(&self.request_time, "").ok()
None
}
}
/// Uses a macro (that may already have been merged in serde) which creates an enum of names to numbers.
serde_enum_number!(HttpStatus {
OK = 200,
MovedPermanently = 301,
Found = 302,
TemporaryRedirect = 307,
PermanentRedirect = 308,
BadRequest = 400,
Unauthorized = 401,
Forbidden = 403,
NotFound = 404,
Gone = 410,
InternalServerError = 500,
BadGateway = 502,
});
/// An HttpResponse is a value returned from a Lambda function when invoked from API gateway as a HTTP method call.
#[derive(Serialize,Deserialize)]
pub struct HttpResponse {
#[serde(rename="statusCode")]
pub status: HttpStatus,
pub headers: HashMap<String, String>,
pub body: Option<String>,
#[serde(rename="isBase64Encoded",default)]
pub is_base64: bool,
}
impl HttpResponse {
pub fn empty(status: HttpStatus) -> Self {
HttpResponse {
status: status,
headers: HashMap::new(),
body: None,
is_base64: false,
}
}
pub fn with_body<S: Into<String>>(status: HttpStatus, body: S) -> Self {
HttpResponse {
status: status,
headers: HashMap::new(),
body: Some(body.into()),
is_base64: false,
}
}
pub fn set_header(&mut self, key: &str, value: &str) {
self.headers.insert(String::from(key), String::from(value));
}
pub fn success(&self) -> bool {
return self.status >= HttpStatus::OK && self.status < HttpStatus::BadRequest
}
}
/// Lambda Event Types
/// Defined in https://docs.aws.amazon.com/lambda/latest/dg/eventsources.html
pub mod auth;
pub mod cloudwatch;
mod http;
pub mod s3;
pub mod ses;
pub mod sns;
#[cfg(test)]
mod tests;
pub use self::http::HttpEvent;
pub use self::http::HttpEventRequestContext;
pub use self::http::HttpMethod;
pub use self::http::HttpStatus;
pub use self::http::HttpResponse;
use chrono::prelude::*;
use std::collections::HashMap;
use serde::de::{self, Deserialize, Deserializer};
use serde_json::Value;
use serde_json::Map;
/// Event is the entrypoint to Lambda and a disambiguation of all types of JSON that a Lambda function can receive.
#[derive(Deserialize)]
#[serde(untagged)]
pub enum Event {
CloudWatch(cloudwatch::Event),
Auth(auth::Event),
Http(HttpEvent),
Records(Records),
Unknown(Value),
}
/// A record within a set of records, records can be from S3, SES, SNS, and potentially other services.
#[derive(Deserialize)]
#[serde(tag="eventSource", remote="Record")]
pub enum Record {
#[serde(rename = "aws:s3")]
S3(s3::Record),
#[serde(rename = "aws:ses")]
Ses(ses::Record),
#[serde(rename = "aws:sns")]
Sns(sns::Record),
Unknown(Value),
}
impl<'de> Deserialize<'de> for Record {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where D: Deserializer<'de>
{
let mut map = Map::<String, Value>::deserialize(deserializer)?;
// this is a hack to change the case of the field name; some records have EventName instead of eventName,
// apparently inconsistency in AWS' own data types
if let Some(event_source) = map.remove("EventSource") {
map.insert("eventSource".to_owned(), event_source);
}
Record::deserialize(Value::Object(map)).map_err(de::Error::custom)
}
}
/// Records represents a set of records passed to a Lambda for capturing one or more event(s).
#[derive(Deserialize)]
pub struct Records {
#[serde(rename="Records")]
pub entries: Vec<Record>,
}
use super::*;
use std::fmt;
/// An S3 Record type as a disambiguation between record types (see mod.rs/pub enum Record)
#[derive(Serialize,Deserialize)]
#[serde(rename_all="camelCase")]
pub struct Record {
pub aws_region: String,
pub event_name: ObjectEvent,
pub event_time: String,
pub event_version: String,
pub request_parameters: Option<HashMap<String, String>>,
pub response_elements: Option<HashMap<String, String>>,
#[serde(rename="s3")]
pub event: Event,
pub user_identity: Option<HashMap<String, String>>,
}
/// An S3 event as received by a Lambda function.
#[derive(Serialize,Deserialize)]
#[serde(rename_all="camelCase")]
pub struct Event {
pub configuration_id: Option<String>,
pub object: Object,
pub bucket: Bucket,
#[serde(rename="s3SchemaVersion")]
pub schema_version: String,
}
/// A reference to an S3 bucket.
#[derive(Serialize,Deserialize)]
#[serde(rename_all="camelCase")]
pub struct Bucket {
pub arn: String,
pub name: String,
pub owner_identity: BucketOwnerIdentity,
}
/// Owner identity of an item in a bucket.
#[derive(Serialize,Deserialize)]
#[serde(rename_all="camelCase")]
pub struct BucketOwnerIdentity {
pub principal_id: String,
}
/// A reference to the underlying S3 object which the event concerns itself with.
#[derive(Serialize,Deserialize)]
pub struct Object {
pub key: String,
pub sequencer: Option<String>,
}
/// The type of S3 event received.
#[derive(Serialize,Deserialize)]
pub enum ObjectEvent {
#[serde(rename="ObjectCreated:Put")]
Put,
#[serde(rename="ObjectCreated:Post")]
Post,
#[serde(rename="ObjectCreated:Copy")]
Copied,
#[serde(rename="ObjectCreated:CompleteMultipartUpload")]
CompleteMultipartUpload,
#[serde(rename="ObjectRemoved:Delete")]
Delete,
#[serde(rename="ObjectRemoved:DeleteMarkerCreated")]
DeleteMarkerCreated,
#[serde(rename="ReducedRedundancyLostObject")]
LostObject,
}
impl fmt::Display for ObjectEvent {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
ObjectEvent::Put => write!(f, "s3:ObjectCreated:Put"),
ObjectEvent::Post => write!(f, "s3:ObjectCreated:Post"),
ObjectEvent::Copied => write!(f, "s3:ObjectCreated:Copy"),
ObjectEvent::CompleteMultipartUpload => write!(f, "s3:ObjectCreated:CompleteMultipartUpload"),
ObjectEvent::Delete => write!(f, "s3:ObjectRemoved:Delete"),
ObjectEvent::DeleteMarkerCreated => write!(f, "s3:ObjectRemvoed:DeleteMarkerCreated"),
ObjectEvent::LostObject => write!(f, "s3:ObjectRemoved:LostObject"),
}
}
}
use super::*;
/// An actual SES email contents.
#[derive(Serialize,Deserialize)]
#[serde(rename_all="camelCase")]
pub struct Message {
pub content: String,
#[serde(rename="mail")]
pub details: Details,
pub notification_type: NotificationType,
pub receipt: Receipt,
}
#[derive(Serialize,Deserialize)]
pub enum NotificationType {
Received,
}
#[derive(Serialize,Deserialize)]
#[serde(rename_all="camelCase")]
pub struct Details {
pub destination: Vec<String>,
pub headers_truncated: bool,
pub message_id: String,
pub source: String,
pub timestamp: DateTime<Utc>,
pub headers: Vec<Header>,
pub common_headers: CommonHeaders,
}
#[derive(Serialize,Deserialize)]
pub struct Header {
pub name: String,
pub value: String,
}
#[derive(Serialize,Deserialize)]
#[serde(rename_all="camelCase")]
pub struct CommonHeaders {
// format: %a, %d %b %Y %H:%M:%S %ze
pub date: String,
pub from: Vec<String>,
pub message_id: String,
pub return_path: String,
pub subject: String,
pub to: Vec<String>,
}
#[derive(Serialize,Deserialize)]
pub enum ActionType {
Bounce,
Lambda,
S3,
#[serde(rename="SNS")]
Sns,
Stop,
WorkMail,
}
#[derive(Serialize,Deserialize)]
#[serde(rename_all="camelCase")]
pub struct Receipt {
pub action: Action,
pub dkim_verdict: DkimVerdict,
pub dmarc_policy: Option<DmarcPolicy>,
pub dmarc_verdict: DmarcVerdict,
pub processing_time_millis: u64,
pub recipients: Vec<String>,
pub spam_verdict: SpamVerdict,
pub spf_verdict: SpfVerdict,
pub timestamp: DateTime<Utc>,
pub virus_verdict: VirusVerdict,
}
#[derive(Serialize,Deserialize)]
#[serde(tag="status")]
pub enum Verdict {
#[serde(rename="FAIL")]
Fail,
#[serde(rename="GRAY")]
Gray,
#[serde(rename="PASS")]
Pass,
#[serde(rename="PROCESSING_FAILED")]
ProcessingFailed,
}
pub type DkimVerdict = Verdict;
pub type DmarcVerdict = Verdict;
pub type SpamVerdict = Verdict;
pub type SpfVerdict = Verdict;
pub type VirusVerdict = Verdict;
#[derive(Serialize,Deserialize)]
pub enum DmarcPolicy {
#[serde(rename="NONE")]
Absent,
#[serde(rename="QUARANTINE")]
Quarantine,
#[serde(rename="REJECT")]
Reject,
}
#[derive(Serialize,Deserialize)]
#[serde(tag="type")]
pub enum Action {
Lambda(LambdaAction),
#[serde(rename="SNS")]
Sns(SnsAction),
S3(S3Action),
Bounce(BounceAction),
Stop(StopAction),
WorkMail(WorkMailAction),
}
#[derive(Serialize,Deserialize)]
#[serde(rename_all="camelCase")]
pub struct LambdaAction {
pub function_arn: Option<String>,
pub invocation_type: LambdaInvocationType,
}
#[derive(Eq,PartialEq,Serialize,Deserialize)]
pub enum LambdaInvocationType {
Event,
RequestResponse,
}
#[derive(Serialize,Deserialize)]
#[serde(rename_all="camelCase")]
pub struct SnsAction {
pub topic_arn: String,
}
#[derive(Serialize,Deserialize)]
#[serde(rename_all="camelCase")]
pub struct S3Action {
pub bucket_name: String,
pub object_key: String,
}
#[derive(Serialize,Deserialize)]
#[serde(rename_all="camelCase")]
pub struct BounceAction {
pub smtp_reply_code: String,
pub status_code: String,
pub message: String,
pub sender: String,
}
#[derive(Serialize,Deserialize)]
pub struct StopAction {}
#[derive(Serialize,Deserialize)]
#[serde(rename_all="camelCase")]
pub struct WorkMailAction {
pub organization_arn: String,
}
/// Data mapped from: https://docs.aws.amazon.com/ses/latest/DeveloperGuide/receiving-email-notifications-contents.html
pub mod message;
use super::*;
pub use self::message::Action;
pub use self::message::LambdaInvocationType;
pub use self::message::Message;
/// Transmitted over SNS, this contains a full message payload as opposed to a bounce request.
#[derive(Serialize,Deserialize)]
#[serde(rename_all="camelCase")]
pub struct Record {
pub event_version: String,
#[serde(rename="ses")]
pub event: Event,
}
#[derive(Serialize,Deserialize)]
#[serde(rename_all="camelCase")]
pub struct Event {
#[serde(rename="mail")]
pub details: message::Details,
pub receipt: message::Receipt,
}
/// For a Lambda function invoked as a RequestResponse, this should be returned as to what to do next.
#[derive(Serialize,Deserialize)]
pub struct Response {
#[serde(rename="disposition")]
pub action: ResponseAction,
}
#[derive(Serialize,Deserialize)]
pub enum ResponseAction {
#[serde(rename="CONTINUE")]
Continue,
#[serde(rename="STOP_RULE")]
StopRule,
#[serde(rename="STOP_RULE_SET")]
StopRuleSet,
}
impl Response {
pub fn from(action: ResponseAction) -> Self {
Response { action }
}
pub fn proceed() -> Self {
Response::from(ResponseAction::Continue)
}
pub fn stop_rule() -> Self {
Response::from(ResponseAction::StopRule)
}
pub fn stop_rule_set() -> Self {
Response::from(ResponseAction::StopRuleSet)
}
}
use super::*;
use chrono::prelude::*;
/// Attributes around a SNS message.
#[derive(Serialize,Deserialize)]
#[serde(rename_all="PascalCase")]
pub struct MessageAttribute {
#[serde(rename="Type")]
pub attribute_type: MessageAttributeType,
pub value: String,
}
/// Types of message attributes.
#[derive(Serialize,Deserialize)]
pub enum MessageAttributeType {
#[serde(rename="String")]
UTF8,
Binary,
}
/// Representation of an SNS record as passed in a Records object.
#[derive(Serialize,Deserialize)]
#[serde(rename_all="PascalCase")]
pub struct Record {
pub event_version: String,
pub event_subscription_arn: String,
#[serde(rename="Sns")]
pub event: Event,
}
/// An SNS event as received by a Lambda function within a Records object.
/// See: https://docs.aws.amazon.com/sns/latest/dg/json-formats.html#http-notification-json
#[derive(Serialize,Deserialize)]
#[serde(rename_all="PascalCase")]
pub struct Event {
pub message: String,
pub message_attributes: Option<HashMap<String, MessageAttribute>>,
pub message_id: String,
pub message_type: Option<EventType>,
pub signature: String,
pub signature_version: String,
pub signing_cert_url: String,
pub subject: Option<String>,
pub timestamp: DateTime<Utc>,
pub topic_arn: String,
pub unsubscribe_url: String,
}
#[derive(Serialize,Deserialize)]
pub enum EventType {
Notification
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment