Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
fix: Remaining broken unit tests. Also refactor tests to not use macros.
  • Loading branch information
dbanty committed Feb 12, 2021
1 parent b97ce43 commit 47671b7
Show file tree
Hide file tree
Showing 7 changed files with 259 additions and 165 deletions.
2 changes: 2 additions & 0 deletions Cargo.toml
Expand Up @@ -21,7 +21,9 @@ http = "0.2.3"
failure = "0.1.5"
aws_lambda_events = "0.4.0"
tokio = { version = "1", features = ["full"] }
parking_lot = "0.11.1"

[dev-dependencies]
# Enable test-utilities in dev mode only. This is mostly for tests.
tokio = { version = "1", features = ["test-util"] }
tokio-test = "0.4.0"
21 changes: 11 additions & 10 deletions src/builder.rs
@@ -1,10 +1,13 @@
use crate::config::*;
use crate::handler::RocketHandler;
use std::sync::Arc;

use lamedh_http::handler;
use lamedh_runtime::run;
use rocket::local::asynchronous::Client;
use rocket::Rocket;
use std::sync::Arc;

use crate::config::*;
use crate::handler::RocketHandler;
use crate::LazyClient;
use parking_lot::Mutex;

/// A builder to create and configure a [RocketHandler](RocketHandler).
pub struct RocketHandlerBuilder {
Expand Down Expand Up @@ -40,14 +43,12 @@ impl RocketHandlerBuilder {
/// use lamedh_runtime::run;
/// use lamedh_http::handler;
///
/// let rocket_handler = rocket::ignite().lambda().into_handler();
/// let rocket_handler = tokio_test::block_on(rocket::ignite().lambda().into_handler());
/// run(handler(rocket_handler));
/// ```
pub async fn into_handler(self) -> RocketHandler {
// TODO: Change this to async Client?
let client = Arc::new(Client::untracked(self.rocket).await.unwrap());
RocketHandler {
client,
lazy_client: Arc::new(Mutex::new(LazyClient::Uninitialized(Some(self.rocket)))),
config: Arc::new(self.config),
}
}
Expand All @@ -64,9 +65,9 @@ impl RocketHandlerBuilder {
///
/// ```rust,no_run
/// use rocket_lamb::RocketExt;
/// use lambda_http::lambda::lambda;
/// use lamedh_http::lambda::lambda;
///
/// rocket::ignite().lambda().launch();
/// tokio_test::block_on(rocket::ignite().lambda().launch());
/// ```
pub async fn launch(self) -> ! {
run(handler(self.into_handler().await)).await.unwrap();
Expand Down
67 changes: 53 additions & 14 deletions src/handler.rs
@@ -1,31 +1,40 @@
use crate::config::*;
use crate::error::RocketLambError;
use crate::request_ext::RequestExt as _;
use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;

use aws_lambda_events::encodings::Body;
use lamedh_http::{Handler, Request, RequestExt, Response};
use lamedh_runtime::Context;
use parking_lot::Mutex;
use rocket::http::{uri::Uri, Header};
use rocket::local::asynchronous::{Client, LocalRequest, LocalResponse};
use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
use rocket::{Rocket, Route};

use crate::config::*;
use crate::error::RocketLambError;
use crate::request_ext::RequestExt as _;

/// A Lambda handler for API Gateway events that processes requests using a [Rocket](rocket::Rocket) instance.
pub struct RocketHandler {
pub(super) client: Arc<Client>,
pub(super) lazy_client: Arc<Mutex<LazyClient>>,
pub(super) config: Arc<Config>,
}

pub(super) enum LazyClient {
Uninitialized(Option<Rocket>),
Ready(Arc<Client>),
}

impl Handler for RocketHandler {
type Error = failure::Error;
type Response = Response<Body>;
type Fut = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + 'static>>;

fn call(&mut self, req: Request, _ctx: Context) -> Self::Fut {
let client = Arc::clone(&self.client);
let config = Arc::clone(&self.config);
let lazy_client = Arc::clone(&self.lazy_client);
let fut = async {
process_request(client, config, req)
process_request(lazy_client, config, req)
.await
.map_err(failure::Error::from)
.map_err(failure::Error::into)
Expand All @@ -35,10 +44,9 @@ impl Handler for RocketHandler {
}

fn get_path_and_query(config: &Config, req: &Request) -> String {
// TODO: Figure out base path behavior per request since the client doesn't have it now
let mut uri = match config.base_path_behaviour {
BasePathBehaviour::Include | BasePathBehaviour::RemountAndInclude => req.full_path(),
BasePathBehaviour::Exclude => req.api_path().to_owned(),
let mut uri = match &config.base_path_behaviour {
BasePathBehaviour::Include | BasePathBehaviour::RemountAndInclude => dbg!(req.full_path()),
BasePathBehaviour::Exclude => dbg!(req.api_path().to_owned()),
};
let query = req.query_string_parameters();

Expand All @@ -58,15 +66,46 @@ fn get_path_and_query(config: &Config, req: &Request) -> String {
}

async fn process_request(
client: Arc<Client>,
lazy_client: Arc<Mutex<LazyClient>>,
config: Arc<Config>,
req: Request,
) -> Result<Response<Body>, RocketLambError> {
let client = get_client_from_lazy(&lazy_client, &config, &req).await;
let local_req = create_rocket_request(&client, Arc::clone(&config), req)?;
let local_res = local_req.dispatch().await;
create_lambda_response(config, local_res).await
}

async fn get_client_from_lazy(
lazy_client_lock: &Mutex<LazyClient>,
config: &Config,
req: &Request,
) -> Arc<Client> {
let mut lazy_client = lazy_client_lock.lock();
match &mut *lazy_client {
LazyClient::Ready(c) => Arc::clone(&c),
LazyClient::Uninitialized(r) => {
let r = r
.take()
.expect("It should not be possible for this to be None");
let base_path = req.base_path();
let client = if config.base_path_behaviour == BasePathBehaviour::RemountAndInclude
&& !base_path.is_empty()
{
let routes: Vec<Route> = r.routes().cloned().collect();
let rocket = r.mount(&base_path, routes);
Client::untracked(rocket).await.unwrap()
} else {
Client::untracked(r).await.unwrap()
};
let client = Arc::new(client);
let client_clone = Arc::clone(&client);
*lazy_client = LazyClient::Ready(client);
client_clone
}
}
}

fn create_rocket_request(
client: &Client,
config: Arc<Config>,
Expand Down
7 changes: 3 additions & 4 deletions src/lib.rs
Expand Up @@ -8,8 +8,6 @@ This *should* also work with requests from an AWS Application Load Balancer, but
## Usage
```rust,no_run
#![feature(proc_macro_hygiene, decl_macro)]
#[macro_use] extern crate rocket;
use rocket_lamb::RocketExt;
Expand All @@ -18,11 +16,12 @@ fn hello() -> &'static str {
"Hello, world!"
}
fn main() {
#[tokio::main]
async fn main() {
rocket::ignite()
.mount("/hello", routes![hello])
.lambda() // launch the Rocket as a Lambda
.launch();
.launch().await;
}
```
*/
Expand Down
96 changes: 51 additions & 45 deletions src/request_ext.rs
@@ -1,3 +1,7 @@
use aws_lambda_events::event::apigw::{
ApiGatewayProxyRequestContext, ApiGatewayV2httpRequestContext,
ApiGatewayV2httpRequestContextHttpDescription,
};
use http::header::HOST;
use lamedh_http::request::RequestContext;
use lamedh_http::{Request, RequestExt as _};
Expand All @@ -12,7 +16,9 @@ pub(crate) trait RequestExt {

impl RequestExt for Request {
fn full_path(&self) -> String {
if matches!(self.request_context(), RequestContext::Alb(_)) || !is_default_api_gateway_url(self) {
if matches!(self.request_context(), RequestContext::Alb(_))
|| !is_default_api_gateway_url(self)
{
self.uri().path().to_owned()
} else {
let mut path = self.base_path();
Expand All @@ -22,35 +28,35 @@ impl RequestExt for Request {
}

fn base_path(&self) -> String {
// TODO: Find out what this is for and is supposed to return >.>
String::new()
// match self.request_context() {
// RequestContext::ApiGateway {
// stage,
// resource_path,
// ..
// } => {
// if is_default_api_gateway_url(self) {
// format!("/{}", stage)
// } else {
// let resource_path = populate_resource_path(self, resource_path);
// let full_path = self.uri().path();
// let resource_path_index =
// full_path.rfind(&resource_path).unwrap_or_else(|| {
// panic!(
// "Could not find segment '{}' in path '{}'.",
// resource_path, full_path
// )
// });
// full_path[..resource_path_index].to_owned()
// }
// }
// RequestContext::Alb { .. } => String::new(),
// }
let (stage, path) = match self.request_context() {
RequestContext::ApiGatewayV1(ApiGatewayProxyRequestContext {
stage,
resource_path,
..
}) => (stage, resource_path),
RequestContext::ApiGatewayV2(ApiGatewayV2httpRequestContext {
stage,
http: ApiGatewayV2httpRequestContextHttpDescription { path, .. },
..
}) => (stage, path),
RequestContext::Alb(..) => (None, None),
};
if is_default_api_gateway_url(self) {
format!("/{}", stage.unwrap_or("".to_string()))
} else {
let path = populate_resource_path(self, path.unwrap_or("".to_string()));
let full_path = self.uri().path();
let resource_path_index = full_path.rfind(&path).unwrap_or_else(|| {
panic!("Could not find segment '{}' in path '{}'.", path, full_path)
});
full_path[..resource_path_index].to_owned()
}
}

fn api_path(&self) -> &str {
if matches!(self.request_context(), RequestContext::Alb(_)) || is_default_api_gateway_url(self) {
if matches!(self.request_context(), RequestContext::Alb(_))
|| is_default_api_gateway_url(self)
{
self.uri().path()
} else {
&self.uri().path()[self.base_path().len()..]
Expand All @@ -66,21 +72,21 @@ fn is_default_api_gateway_url(req: &Request) -> bool {
.unwrap_or(false)
}

// fn populate_resource_path(req: &Request, resource_path: String) -> String {
// let path_parameters = req.path_parameters();
// resource_path
// .split('/')
// .map(|segment| {
// if segment.starts_with('{') {
// let end = if segment.ends_with("+}") { 2 } else { 1 };
// let param = &segment[1..segment.len() - end];
// path_parameters
// .get(param)
// .unwrap_or_else(|| panic!("Could not find path parameter '{}'.", param))
// } else {
// segment
// }
// })
// .collect::<Vec<&str>>()
// .join("/")
// }
fn populate_resource_path(req: &Request, resource_path: String) -> String {
let path_parameters = req.path_parameters();
resource_path
.split('/')
.map(|segment| {
if segment.starts_with('{') {
let end = if segment.ends_with("+}") { 2 } else { 1 };
let param = &segment[1..segment.len() - end];
path_parameters
.get(param)
.unwrap_or_else(|| panic!("Could not find path parameter '{}'.", param))
} else {
segment
}
})
.collect::<Vec<&str>>()
.join("/")
}

0 comments on commit 47671b7

Please sign in to comment.