-
-
Notifications
You must be signed in to change notification settings - Fork 16
[WIP] Add Data Fixtures for Lambda Event Types #31
base: master
Are you sure you want to change the base?
Conversation
Tests and documentation forthcoming. I am using these data structures for my serverless event bus in Rust using Crowbar.
5c57ad7
to
39b1ea6
Compare
I like this. :) Can you add what has been done and what remains as checkboxes to the pull request description so we can see the path to this leaving a WIP state? |
🎉 so happy that you're welcoming this PR. I will create a separate issue and track things there. There are so many different data types to support, so this PR will maybe only cover a subset with future on the way, hope that's okay. |
Yeah, I don't have an issue with incremental development over time. I'm hoping I'll have a chance to do a good read through of all of this soon. |
@ilianaw can you please go turn on Travis CI for this project? I have updated with a build badge and Travis configuration for building and testing. EDIT: Actually, I will submit Travis support in a separate PR. |
Latest adds support for CloudWatch AutoScaling events. |
I'd love to see this branch ship fast with additional followups to add other events types. Commented with extra thoughts on the checkbox ticket |
#[derive(Serialize,Deserialize)] | ||
pub struct HttpEvent { | ||
pub body: Option<String>, | ||
pub headers: Option<HashMap<String, String>>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One small thought. I did something similar initially in a separate project and found that the extra option type to be a bit annoying from a usability perspective. Espectially those new to rust
9 | println!("{:?}", ev.query_string_parameters.and_then(|m| m.get("foo")));
| ---------------------------------------------------------^--------------
| | | |
| | | `m` dropped here while still borrowed
| | borrowed value does not live long enough
| borrowed value needs to live until here
Was't a very great experience.
I found a way to deserialize the nullable maps into default values greatly improved usability for new comers
as well as reduced verbosity while retaining the same level of rust runtime saftey.
println!("{:?}", ev.query_string_parameters.and_then(|m| m.get("foo")))
println!("{:?}", ev.query_string_parameters.get("foo"));
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, let's deserialize things like maps and vectors into default (empty) values.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I really think that kind of thing helps the ergonomics for people using this.
} | ||
|
||
#[derive(Serialize,Deserialize)] | ||
pub struct HttpEventRequestContext { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you can largely get a way with a struct level #[serde(rename_all = "camelCase")]
here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
note: comments like this are not really blockers for me and could easily fit into followups
|
||
impl HttpResponse { | ||
|
||
pub fn empty(status: HttpStatus) -> Self { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm trying to convince myself to drop an existing external effort to add types to crowbar. Something I'd be missing here is a Default impl
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
} | ||
} | ||
|
||
serde_aux_enum_number_declare!(HttpStatus { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd maybe suggest not going too overboard with type mappings here. I see crowbar being something as a low level mapping to raw primitive types, in this case a u16 would do. The reasoning behind this line of thinking is that you could then more easily adapt to and not duplicate efforts to create a typed interface like http on top of crowbar. As is you couldn't build a mapping to the http crate on top of this because if the incomplete mapping of http statuses. I'd much prefer the crowbar retain the most primitive types and enable layering of extra layers on top in another crate.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So should we rely on another crate for this? I do actually like having strongly typed things.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd actually not recommend adding any more crate dependencies on crowbar. I don't see crowbar as a framework but as an faithful representation of lambda. I'm thinking now it its really api gateway, the service, that defines what status codes you can dispatch lambdas on so that may actually be a more restrictive set. I redact my previous comment!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree that a u16 would do for the HTTP status.
I experimented a lot with the http
crate for another project involving Lambda@Edge. It helped with initial development but I eventually ripped it out because it was doing a lot of validation that CloudFront already does for you.
impl HttpEventRequestContext { | ||
|
||
pub fn time(&self) -> Option<DateTime<Utc>> { | ||
// Utc::datetime_from_str(&self.request_time, "").ok()e |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you could probably ditch a hard coupling of crowbar to a crate by letting it be a user decision to select a time parsing crate and let crowbar be smaller.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How can we genericize this to work like that?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we didn't want to represent this as a string that lambda events do, you could maybe use a feature + cfg. I don't feel really strongly about this. Only that I'd like to see crowbar have a solve only the minimal set of problems with a minimal set of dependencies. Motivations are that it lets users pick a crate they prefer for representing time typically so that they only have one crate for dealing with time in their projects ( think about what hyper did with tls ) and it frees up crowbar up from a bit of maintenance by not worrying falling behind on its dependencies. I also don't feel too strongly here but that's my two cents. I'd feel otherwise if rust had a std lib way of representing timestamps.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think genericizing the time crate is necessary to land this PR.
|
||
impl HttpEvent { | ||
|
||
pub fn get_header(&self, key: &str) -> Option<&str> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are some some growing community recommendations on avoiding get_ where you can https://rust-lang-nursery.github.io/api-guidelines/naming.html#getter-names-follow-rust-convention-c-getter
Instead a simple .header("Foo")
will do
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Roger that.
|
||
impl Response { | ||
|
||
pub fn from(action: ResponseAction) -> Self { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not impl the From trait instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's do it 👍
I will start breaking this apart into a PR per service supported, I think that's probably the most prudent course of action. One thing that is probably a really good idea is to find a way to efficiently nest CloudWatch style events and deserialize them more efficiently than I'm currently doing. |
Can you link to an example in this pull or sketch out in a comment what you mean? |
Overall I'm happy with this, and thanks to @softprops for the additional review. I'm fine with continuing with this PR, or breaking it apart as a PR per service (the latter would be great as its easier for others to help write the structs out). I have two thoughts:
|
Yes. This used to not be true but is now a default of serde. |
I apologize for my lateness, just getting back from vacation. I will get caught up this week. |
No worries! ^_^; |
TL;DR the events received from Lambda can come via a few sources:
Therefore, our enums for representing all kinds of incoming events should basically include these three, and then users can disambiguate between the implementations (ie I will probably break this PR down into tiny pieces and make one PR for each service to add. I wholeheartedly agree with removing |
Is there any way to help move this forward? I'd love to help |
Tests and documentation forthcoming. I am using these data structures for my serverless event bus in Rust using Crowbar.
My lambda handler looks something like this:
Thus, I do routing based on event type. For example, SNS:
My multi-event router:
This dispatching occurs in around 400-500 microseconds and works extremely well to make things type safe.
Obviously there's a lot here and it's not done. If @ilianaw or others have any input, RFC is open to comments 😃