Skip to content

[Do not merge] Refactor to address RFCs #63

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 45 commits into from
Jan 24, 2019
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
a77abb1
Fixed typo for UNHANDLED error constant
sapessi Dec 2, 2018
36bcc8f
Removed RuntimeApiError trait form the client and changed it to recei…
sapessi Dec 3, 2018
f53bd30
Changed HandlerError type to alias Box<dyn Error> to allow the runtim…
sapessi Dec 3, 2018
8b6d8fc
Added custom error examples and updated existing examples
sapessi Dec 3, 2018
2e034d2
Fixed failure example
sapessi Dec 3, 2018
4f20464
Updated comment on HandlerError type
sapessi Dec 3, 2018
d17b7c8
Exported HandlerError in root of the crate
sapessi Dec 3, 2018
08772e9
Added error code to response structure and centralized backtrace gene…
sapessi Dec 3, 2018
a6d2c5b
Updated example in lib docs
sapessi Dec 3, 2018
fee02e8
Temporary fix for failure example
sapessi Dec 3, 2018
4969624
Switched to HandlerError struct that can be created from any Display …
sapessi Dec 4, 2018
0bce533
Addressed code review changes
sapessi Dec 4, 2018
bd3cb34
Changed error handling codes according to latest specs clarified in #23
sapessi Dec 4, 2018
5b9bad9
Removed custom_attribute feature to fix build
sapessi Dec 4, 2018
9ed9a43
Cleaned up lifetimes/borrows
sapessi Dec 27, 2018
7fa1167
New runtime core crate that implements the runtime main loop and defi…
sapessi Dec 27, 2018
5165242
Slimmed down runtime crate that only wraps the runtime core crate
sapessi Dec 27, 2018
3bcb150
Rebased on master
sapessi Dec 27, 2018
ab54a2e
Moved HandlerError to error module for backward compatibility
sapessi Dec 27, 2018
99b1328
Fixed error package in examples
sapessi Dec 28, 2018
3844b43
Addressing CR on newline at the end of file and import grouping
sapessi Dec 28, 2018
fa2a5b1
More style changes from code review
sapessi Dec 28, 2018
b6423f7
Fixed tests, including http crate
sapessi Dec 28, 2018
77459b5
First refactor of errors in the client crate and new errors crate
sapessi Dec 28, 2018
ecbae1c
Bump dependencies
sapessi Jan 4, 2019
6896b1b
New derive crate (experimental) for LambdaErrorExt and new HandlerErr…
sapessi Jan 4, 2019
3f79fcb
Switched back to ApiError and added new is_recoverable() method
sapessi Jan 4, 2019
1b1e624
Parametrized error type and updated examples
sapessi Jan 4, 2019
7de9169
Switched to more descriptive generic names
sapessi Jan 4, 2019
b991438
Added a new LambdaResultExt to simplify error handling in handlers
sapessi Jan 4, 2019
bedbf92
Added build script to generate the metadata file
sapessi Jan 4, 2019
e3f6b34
Changed the runtime client SDK to receive a user-agent as init param.…
sapessi Jan 5, 2019
253a481
Fixed format of build.rs
sapessi Jan 7, 2019
db7f994
Fixed critical issue in runtime client - request URIs were not genera…
sapessi Jan 10, 2019
f0de223
Fixed code issue - committed to fast earlier
sapessi Jan 10, 2019
d705def
New error tests and failure featurs in Cargo. Still cannot get a back…
sapessi Jan 21, 2019
a3aaedb
failure features import
sapessi Jan 21, 2019
fd35180
Added doc comment on lambda! macro
sapessi Jan 21, 2019
8827cdb
merge latest master changes
sapessi Jan 21, 2019
32cd6e0
Fixed failure dep in Cargo (CR feedback)
sapessi Jan 21, 2019
0f54490
Removed unnecesary test (CR feedback)
sapessi Jan 21, 2019
bb61d71
Another round of CR feedback
sapessi Jan 22, 2019
6891eaa
Added comment to error header as per CR
sapessi Jan 24, 2019
ee12d37
Changed order of deps while cleaning up
sapessi Jan 24, 2019
315c979
Removed commented code as per CR
sapessi Jan 24, 2019
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
/target
/.cargo
lambda-runtime/libtest.rmeta
564 changes: 331 additions & 233 deletions Cargo.lock

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
[workspace]
members = [
"lambda-runtime-client",
"lambda-runtime-core",
"lambda-runtime",
"lambda-http"
]
2 changes: 1 addition & 1 deletion lambda-runtime-client/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,6 @@ tokio = "0.1"
http = "0.1"
serde = "^1"
serde_json = "^1"
serde_derive = "^1"
serde_derive = "^1"
log = "0.4"
backtrace = "0.3"
43 changes: 22 additions & 21 deletions lambda-runtime-client/src/client.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,22 @@
use std::{collections::HashMap, fmt};

use crate::error::{ApiError, ErrorResponse, ERROR_TYPE_UNHANDLED};
use hyper::{
client::HttpConnector,
header::{self, HeaderMap, HeaderValue},
rt::{Future, Stream},
Body, Client, Method, Request, Uri,
};
use serde_derive::Deserialize;
use log::*;
use serde_derive::*;
use serde_json;
use std::{collections::HashMap, fmt};
use tokio::runtime::Runtime;

use crate::error::{ApiError, ErrorResponse, RuntimeApiError};

const RUNTIME_API_VERSION: &str = "2018-06-01";
const API_CONTENT_TYPE: &str = "application/json";
const API_ERROR_CONTENT_TYPE: &str = "application/vnd.aws.lambda.error+json";
const RUNTIME_ERROR_HEADER: &str = "Lambda-Runtime-Function-Error-Type";
// TODO: Perhaps use a macro to generate this
const CLIENT_USER_AGENT: &str = "AWS_Lambda_Rust/0.1.0";

/// Enum of the headers returned by Lambda's `/next` API call.
pub enum LambdaHeaders {
Expand Down Expand Up @@ -126,7 +127,7 @@ pub struct RuntimeClient {
endpoint: String,
}

impl RuntimeClient {
impl<'ev> RuntimeClient {
/// Creates a new instance of the Runtime APIclient SDK. The http client has timeouts disabled and
/// will always send a `Connection: keep-alive` header.
pub fn new(endpoint: String, runtime: Option<Runtime>) -> Result<Self, ApiError> {
Expand All @@ -147,7 +148,7 @@ impl RuntimeClient {
}
}

impl RuntimeClient {
impl<'ev> RuntimeClient {
/// Polls for new events to the Runtime APIs.
pub fn next_event(&self) -> Result<(Vec<u8>, EventContext), ApiError> {
let uri = format!(
Expand Down Expand Up @@ -184,7 +185,7 @@ impl RuntimeClient {
}
let ctx = self.get_event_context(&resp.headers())?;
let out = resp.into_body().concat2().wait()?;
let buf: Vec<u8> = out.into_bytes().to_vec();
let buf = out.into_bytes().to_vec();

trace!(
"Received new event for request id {}. Event length {} bytes",
Expand Down Expand Up @@ -212,7 +213,7 @@ impl RuntimeClient {
///
/// # Returns
/// A `Result` object containing a bool return value for the call or an `error::ApiError` instance.
pub fn event_response(&self, request_id: &str, output: Vec<u8>) -> Result<(), ApiError> {
pub fn event_response(&self, request_id: &str, output: &[u8]) -> Result<(), ApiError> {
let uri: Uri = format!(
"http://{}/{}/runtime/invocation/{}/response",
self.endpoint, RUNTIME_API_VERSION, request_id
Expand Down Expand Up @@ -260,7 +261,7 @@ impl RuntimeClient {
///
/// # Returns
/// A `Result` object containing a bool return value for the call or an `error::ApiError` instance.
pub fn event_error(&self, request_id: &str, e: &dyn RuntimeApiError) -> Result<(), ApiError> {
pub fn event_error(&self, request_id: &str, e: &ErrorResponse) -> Result<(), ApiError> {
let uri: Uri = format!(
"http://{}/{}/runtime/invocation/{}/error",
self.endpoint, RUNTIME_API_VERSION, request_id
Expand All @@ -269,9 +270,9 @@ impl RuntimeClient {
trace!(
"Posting error to runtime API for request {}: {}",
request_id,
e.to_response().error_message
e.error_message
);
let req = self.get_runtime_error_request(&uri, &e.to_response());
let req = self.get_runtime_error_request(&uri, &e);

match self.http_client.request(req).wait() {
Ok(resp) => {
Expand Down Expand Up @@ -307,12 +308,12 @@ impl RuntimeClient {
/// # Panics
/// If it cannot send the init error. In this case we panic to force the runtime
/// to restart.
pub fn fail_init(&self, e: &dyn RuntimeApiError) {
pub fn fail_init(&self, e: &ErrorResponse) {
let uri: Uri = format!("http://{}/{}/runtime/init/error", self.endpoint, RUNTIME_API_VERSION)
.parse()
.expect("Could not generate Runtime URI");
error!("Calling fail_init Runtime API: {}", e.to_response().error_message);
let req = self.get_runtime_error_request(&uri, &e.to_response());
error!("Calling fail_init Runtime API: {}", e.error_message);
let req = self.get_runtime_error_request(&uri, &e);

self.http_client
.request(req)
Expand All @@ -331,9 +332,7 @@ impl RuntimeClient {
pub fn get_endpoint(&self) -> String {
self.endpoint.clone()
}
}

impl RuntimeClient {
/// Creates a Hyper `Request` object for the given `Uri` and `Body`. Sets the
/// HTTP method to `POST` and the `Content-Type` header value to `application/json`.
///
Expand All @@ -344,25 +343,27 @@ impl RuntimeClient {
///
/// # Returns
/// A Populated Hyper `Request` object.
fn get_runtime_post_request(&self, uri: &Uri, body: Vec<u8>) -> Request<Body> {
fn get_runtime_post_request(&self, uri: &Uri, body: &[u8]) -> Request<Body> {
Request::builder()
.method(Method::POST)
.uri(uri.clone())
.header(header::CONTENT_TYPE, header::HeaderValue::from_static(API_CONTENT_TYPE))
.body(Body::from(body))
.header(header::USER_AGENT, header::HeaderValue::from_static(CLIENT_USER_AGENT))
.body(Body::from(body.to_owned()))
.unwrap()
}

fn get_runtime_error_request(&self, uri: &Uri, e: &ErrorResponse) -> Request<Body> {
let body = serde_json::to_vec(e).expect("Could not turn error object into response JSON");
let body = serde_json::to_vec(&e).expect("Could not turn error object into response JSON");
Request::builder()
.method(Method::POST)
.uri(uri.clone())
.header(
header::CONTENT_TYPE,
header::HeaderValue::from_static(API_ERROR_CONTENT_TYPE),
)
.header(RUNTIME_ERROR_HEADER, HeaderValue::from_static("RuntimeError")) // TODO: We should add this code to the error object.
.header(header::USER_AGENT, header::HeaderValue::from_static(CLIENT_USER_AGENT))
.header(RUNTIME_ERROR_HEADER, HeaderValue::from_static(ERROR_TYPE_UNHANDLED))
.body(Body::from(body))
.unwrap()
}
Expand Down
103 changes: 56 additions & 47 deletions lambda-runtime-client/src/error.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,31 @@
//! This module defines the `RuntimeApiError` trait that developers should implement
//! to send their custom errors to the AWS Lambda Runtime Client SDK. The module also
//! defines the `ApiError` type returned by the `RuntimeClient` implementations.
use std::{env, error::Error, fmt, io, num::ParseIntError, option::Option};

use backtrace;
use http::{header::ToStrError, uri::InvalidUri};
use hyper;
use serde_derive::Serialize;
use log::*;
use serde_derive::*;
use serde_json;
use std::{
env,
error::Error,
fmt::{self, Display},
io,
num::ParseIntError,
option::Option,
};

/// Error type description for the `ErrorResponse` event. This type should be returned
/// for errors that were handled by the function code or framework.
pub const ERROR_TYPE_HANDLED: &str = "Handled";
#[allow(dead_code)]
pub(crate) const ERROR_TYPE_HANDLED: &str = "Handled";
/// Error type description for the `ErrorResponse` event. This type is used for unhandled,
/// unexpcted errors.
pub const ERROR_TYPE_UNHANDLED: &str = "Unhandled";
pub(crate) const ERROR_TYPE_UNHANDLED: &str = "Unhandled";
/// Error type for the error responses to the Runtime APIs. In the future, this library
/// should use a customer-generated error code
pub const RUNTIME_ERROR_TYPE: &str = "RustRuntimeError";

/// This object is used to generate requests to the Lambda Runtime APIs.
/// It is used for both the error response APIs and fail init calls.
Expand All @@ -37,6 +48,38 @@ pub struct ErrorResponse {
}

impl ErrorResponse {
/// Creates a new instance of the `ErrorResponse` object with the given parameters. If the
/// `RUST_BACKTRACE` env variable is `1` the `ErrorResponse` is populated with the backtrace
/// collected through the [`backtrace` craete](https://crates.io/crates/backtrace).
///
/// # Arguments
///
/// * `message` The error message to be returned to the APIs. Normally the error description()
/// * `err_type` The error type. Use the `ERROR_TYPE_HANDLED` and `ERROR_TYPE_UNHANDLED`.
/// * `code` A custom error code
///
/// # Return
/// A new instance of the `ErrorResponse` object.
fn new(message: String, err_type: String) -> ErrorResponse {
let mut err = ErrorResponse {
error_message: message,
error_type: err_type,
stack_trace: Option::default(),
};
let is_backtrace = env::var("RUST_BACKTRACE");
if is_backtrace.is_ok() && is_backtrace.unwrap() == "1" {
trace!("Begin backtrace collection");
let trace = Option::from(backtrace::Backtrace::new());
let trace_string = format!("{:?}", trace)
.lines()
.map(|s| s.to_string())
.collect::<Vec<String>>();
trace!("Completed backtrace collection");
err.stack_trace = Option::from(trace_string);
}
err
}

/// Creates a new `RuntimeError` object with the handled error type.
///
/// # Arguments
Expand All @@ -46,11 +89,7 @@ impl ErrorResponse {
/// # Return
/// A populated `RuntimeError` object that can be used with the Lambda Runtime API.
pub fn handled(message: String) -> ErrorResponse {
ErrorResponse {
error_message: message,
error_type: String::from(ERROR_TYPE_HANDLED),
stack_trace: Option::default(),
}
ErrorResponse::new(message, RUNTIME_ERROR_TYPE.to_owned())
}

/// Creates a new `RuntimeError` object with the unhandled error type.
Expand All @@ -62,33 +101,20 @@ impl ErrorResponse {
/// # Return
/// A populated `RuntimeError` object that can be used with the Lambda Runtime API.
pub fn unhandled(message: String) -> ErrorResponse {
ErrorResponse {
error_message: message,
error_type: String::from(ERROR_TYPE_UNHANDLED),
stack_trace: Option::default(),
}
ErrorResponse::new(message, RUNTIME_ERROR_TYPE.to_owned())
}
}

/// Custom errors for the framework should implement this trait. The client calls
/// the `to_response()` method automatically to produce an object that can be serialized
/// and sent to the Lambda Runtime APIs.
pub trait RuntimeApiError {
/// Creates a `RuntimeError` object for the current error. This is
/// then serialized and sent to the Lambda runtime APIs.
///
/// # Returns
/// A populated `RuntimeError` object.
fn to_response(&self) -> ErrorResponse;
impl<T: Display + Send + Sync> From<Box<T>> for ErrorResponse {
fn from(e: Box<T>) -> Self {
Self::handled(format!("{}", e))
}
}

/// Represents an error generated by the Lambda Runtime API client.
#[derive(Debug, Clone)]
pub struct ApiError {
msg: String,
/// The `Backtrace` object from the `backtrace` crate used to store
/// the stack trace of the error.
pub backtrace: Option<backtrace::Backtrace>,
/// Whether the current error is recoverable. If the error is not
/// recoverable a runtime should panic to force the Lambda service
/// to restart the execution environment.
Expand All @@ -97,16 +123,8 @@ pub struct ApiError {

impl ApiError {
pub(crate) fn new(description: &str) -> ApiError {
let mut trace: Option<backtrace::Backtrace> = None;
let is_backtrace = env::var("RUST_BACKTRACE");
if is_backtrace.is_ok() && is_backtrace.unwrap() == "1" {
trace!("Begin backtrace collection");
trace = Option::from(backtrace::Backtrace::new());
trace!("Completed backtrace collection");
}
ApiError {
msg: String::from(description),
backtrace: trace,
recoverable: true,
}
}
Expand Down Expand Up @@ -135,6 +153,8 @@ impl Error for ApiError {
None
}
}
unsafe impl Send for ApiError {}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, why's this is necessary? would be useful to add a comment.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will go away, with the next iteration all errors will switch to failure. I have quite a bit of legacy error handling code in here. Just wanted to make one big change at a time.

unsafe impl Sync for ApiError {}

impl From<serde_json::Error> for ApiError {
fn from(e: serde_json::Error) -> Self {
Expand Down Expand Up @@ -171,14 +191,3 @@ impl From<io::Error> for ApiError {
ApiError::new(e.description())
}
}

impl RuntimeApiError for ApiError {
fn to_response(&self) -> ErrorResponse {
let backtrace = format!("{:?}", self.backtrace);
let trace_vec = backtrace.lines().map(|s| s.to_string()).collect::<Vec<String>>();
let mut err = ErrorResponse::unhandled(self.msg.clone());
err.stack_trace = Option::from(trace_vec);

err
}
}
5 changes: 2 additions & 3 deletions lambda-runtime-client/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#![warn(missing_docs)]
#![deny(warnings)]
#![allow(clippy::new_ret_no_self)]
//! Rust client SDK for the AWS Lambda Runtime APIs. This crate defines
//! a `RuntimeClient` that encapsulates interactions with AWS Lambda's Runtime
//! APIs.
Expand Down Expand Up @@ -56,9 +57,7 @@
//! }
//! ```

#[macro_use]
extern crate log;

mod client;
pub mod error;

pub use crate::client::*;
23 changes: 23 additions & 0 deletions lambda-runtime-core/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
[package]
name = "lambda_runtime_core"
version = "0.1.0"
authors = ["Stefano Buliani", "David Barsky"]
description = "Rust runtime for AWS Lambda"
keywords = ["AWS", "Lambda", "Runtime", "Rust"]
license = "Apache-2.0"
homepage = "https://github.com/awslabs/aws-lambda-rust-runtime"
repository = "https://github.com/awslabs/aws-lambda-rust-runtime"
documentation = "https://docs.rs/lambda_runtime"
edition = "2018"

[dependencies]
log = "^0.4"
hyper = "^0.12"
tokio = "^0.1"
backtrace = "^0.3"
lambda_runtime_client = { path = "../lambda-runtime-client", version = "^0.1" }
chrono = "^0.4"

[dev-dependencies]
failure = "^0.1"
simple_logger = "^1"
Loading