use std::{
collections::HashMap,
convert::{TryFrom, TryInto},
io::Read,
};
#[doc(no_inline)]
pub use http::{
self,
request::Request,
response::{Builder as ResponseBuilder, Response},
};
use http::{
header::{HeaderName, HeaderValue},
request::Builder as RequestBuilder,
};
use itertools::Itertools;
use serde::{Deserialize, Serialize};
use crate::convert::{FromReader, IntoBytes};
type Dict = HashMap<String, String>;
pub mod ext {
#[derive(Default, Debug, Clone, super::Deserialize)]
pub struct RequestContext {
#[serde(rename = "serviceId")]
pub service_id: String,
pub path: String,
#[serde(rename = "httpMethod")]
pub method: String,
pub identity: super::Dict,
#[serde(rename = "sourceIp")]
pub source_ip: String,
pub stage: String,
}
#[derive(Debug, Clone, PartialEq, Eq, super::Deserialize)]
#[serde(transparent)]
pub struct PathParameters(pub super::Dict);
#[derive(Debug, Clone, PartialEq, Eq, super::Deserialize)]
#[serde(transparent)]
pub struct QueryString(pub super::Dict);
#[derive(Debug, Clone, PartialEq, Eq, super::Deserialize)]
#[serde(transparent)]
pub struct QueryStringParameters(pub super::Dict);
#[derive(Debug, Clone, PartialEq, Eq, super::Deserialize)]
#[serde(transparent)]
pub struct HeaderParameters(pub super::Dict);
}
#[doc(hidden)]
#[derive(Debug, Clone, Deserialize)]
pub struct Event {
#[serde(rename = "requestContext")]
context: ext::RequestContext,
#[serde(rename = "httpMethod")]
method: String,
path: String,
#[serde(rename = "pathParameters")]
path_parameters: ext::PathParameters,
#[serde(rename = "queryString")]
query_string: ext::QueryString,
#[serde(rename = "queryStringParameters")]
query_string_parameters: ext::QueryStringParameters,
headers: Dict,
#[serde(rename = "headerParameters")]
header_parameters: ext::HeaderParameters,
body: Option<String>,
}
impl Event {
fn into_request_builder(self) -> Result<(RequestBuilder, String), RequestParseError> {
let mut req = RequestBuilder::new()
.method(self.method.as_str())
.uri(&self.path);
if let Some(headers) = req.headers_mut() {
for (header, value) in self.headers.iter() {
let header = HeaderName::from_bytes(header.as_bytes())
.map_err(|_| RequestParseError::InvalidHeaderName(header.clone()))?;
let value = HeaderValue::from_str(value).map_err(|_| {
RequestParseError::InvalidHeaderValue(header.as_str().to_string())
})?;
headers.append(header, value);
}
}
if let Some(extensions) = req.extensions_mut() {
extensions.insert(self.context);
extensions.insert(self.path_parameters);
extensions.insert(self.query_string);
extensions.insert(self.query_string_parameters);
extensions.insert(self.header_parameters);
}
Ok((req, self.body.unwrap_or_default()))
}
}
#[doc(hidden)]
#[derive(Debug, Clone, Serialize)]
#[serde(untagged)]
pub enum WebResponseHeaderValue {
Single(String),
Multi(Vec<String>),
}
#[doc(hidden)]
#[derive(Debug, Clone, Serialize)]
pub struct WebResponse {
#[serde(rename = "isBase64Encoded")]
base64: bool,
#[serde(rename = "statusCode")]
status: u16,
headers: HashMap<String, WebResponseHeaderValue>,
body: String,
}
#[doc(hidden)]
#[derive(Debug, thiserror::Error)]
pub enum RequestParseError {
#[error("{0}")]
DeserializeError(#[from] serde_json::Error),
#[error("fail to assemble request struct: {0}")]
AssembleError(#[from] http::Error),
#[error("invalid header name for '{0}' in the request")]
InvalidHeaderName(String),
#[error("invalid header value for '{0}' in the request")]
InvalidHeaderValue(String),
#[error("fail to decode base64 body: {0}")]
Base64DecodeError(#[from] base64::DecodeError),
}
#[doc(hidden)]
impl TryInto<Request<String>> for Event {
type Error = RequestParseError;
fn try_into(self) -> Result<Request<String>, Self::Error> {
let (req, body) = self.into_request_builder()?;
Ok(req.body(body)?)
}
}
#[doc(hidden)]
impl TryInto<Request<Vec<u8>>> for Event {
type Error = RequestParseError;
fn try_into(self) -> Result<Request<Vec<u8>>, Self::Error> {
let (req, body) = self.into_request_builder()?;
let bytes = base64::decode(body.as_str())?;
Ok(req.body(bytes)?)
}
}
impl<T> FromReader for Request<T>
where
Event: TryInto<Request<T>, Error = RequestParseError>,
{
type Error = <Event as TryInto<Request<T>>>::Error;
fn from_reader<Reader: Read + Send>(reader: Reader) -> Result<Self, Self::Error> {
let event: Event = serde_json::from_reader(reader)?;
event.try_into()
}
}
#[doc(hidden)]
#[derive(Debug, thiserror::Error)]
pub enum ResponseEncodeError {
#[error("{0}")]
SerializeError(#[from] serde_json::Error),
#[error("invalid header value for '{0}' in the response")]
InvalidHeaderValue(String),
}
fn break_response<T>(
response: Response<T>,
) -> Result<(u16, HashMap<String, WebResponseHeaderValue>, T), ResponseEncodeError> {
let (mut parts, body) = response.into_parts();
let mut headers = HashMap::new();
let mut header_iter = parts.headers.drain().peekable();
while let Some((header, value)) = header_iter.next() {
let header = header.unwrap();
match header_iter.peek() {
Some(&(None, _)) => {
let values = std::iter::once(value)
.chain(
header_iter
.peeking_take_while(|(header, _)| header.is_none())
.map(|(_, value)| value),
)
.map(|value| {
Ok(value
.to_str()
.map_err(|_| {
ResponseEncodeError::InvalidHeaderValue(header.to_string())
})?
.to_string())
})
.collect::<Result<Vec<String>, ResponseEncodeError>>()?;
headers.insert(header.to_string(), WebResponseHeaderValue::Multi(values));
}
_ => {
let value = WebResponseHeaderValue::Single(
value
.to_str()
.map_err(|_| ResponseEncodeError::InvalidHeaderValue(header.to_string()))?
.to_string(),
);
headers.insert(header.to_string(), value);
}
}
}
Ok((parts.status.as_u16(), headers, body))
}
#[doc(hidden)]
impl TryFrom<Response<String>> for WebResponse {
type Error = ResponseEncodeError;
fn try_from(response: Response<String>) -> Result<Self, Self::Error> {
let (status, headers, body) = break_response(response)?;
Ok(Self {
base64: false,
status,
headers,
body,
})
}
}
#[doc(hidden)]
impl TryFrom<Response<Vec<u8>>> for WebResponse {
type Error = ResponseEncodeError;
fn try_from(response: Response<Vec<u8>>) -> Result<Self, Self::Error> {
let (status, headers, body) = break_response(response)?;
Ok(Self {
base64: true,
status,
headers,
body: base64::encode(&body),
})
}
}
impl<T> IntoBytes for Response<T>
where
Response<T>: TryInto<WebResponse, Error = ResponseEncodeError>,
{
type Error = <Response<T> as TryInto<WebResponse>>::Error;
fn into_bytes(self) -> Result<Vec<u8>, Self::Error> {
let response: WebResponse = self.try_into()?;
Ok(serde_json::to_vec(&response)?)
}
}