-
-
Save naftulikay/99f4ab201c7efddb2210930f411e60e4 to your computer and use it in GitHub Desktop.
Strongly Typed Lambda JSON with Serde
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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") | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/// 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>, | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"), | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/// 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) | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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