lambda_runtime_api_client/
lib.rs1#![deny(clippy::all, clippy::cargo)]
2#![warn(missing_docs, nonstandard_style, rust_2018_idioms)]
3#![allow(clippy::multiple_crate_versions)]
4
5use futures_util::{future::BoxFuture, FutureExt, TryFutureExt};
8use http::{uri::PathAndQuery, uri::Scheme, Request, Response, Uri};
9use hyper::body::Incoming;
10use hyper_util::client::legacy::connect::HttpConnector;
11use std::{convert::TryInto, fmt::Debug, future};
12
13const USER_AGENT_HEADER: &str = "User-Agent";
14const DEFAULT_USER_AGENT: &str = concat!("aws-lambda-rust/", env!("CARGO_PKG_VERSION"));
15const CUSTOM_USER_AGENT: Option<&str> = option_env!("LAMBDA_RUNTIME_USER_AGENT");
16
17mod error;
18pub use error::*;
19pub mod body;
20
21#[cfg(feature = "tracing")]
22pub mod tracing;
23
24#[derive(Debug)]
26pub struct Client {
27 pub base: Uri,
29 pub client: hyper_util::client::legacy::Client<HttpConnector, body::Body>,
31}
32
33impl Client {
34 pub fn builder() -> ClientBuilder {
36 ClientBuilder {
37 connector: HttpConnector::new(),
38 uri: None,
39 }
40 }
41}
42
43impl Client {
44 pub fn call(&self, req: Request<body::Body>) -> BoxFuture<'static, Result<Response<Incoming>, BoxError>> {
47 let req = match self.set_origin(req) {
51 Ok(req) => req,
52 Err(err) => return future::ready(Err(err)).boxed(),
53 };
54 self.client.request(req).map_err(Into::into).boxed()
55 }
56
57 fn with(base: Uri, connector: HttpConnector) -> Self {
59 let client = hyper_util::client::legacy::Client::builder(hyper_util::rt::TokioExecutor::new())
60 .http1_max_buf_size(1024 * 1024)
61 .build(connector);
62 Self { base, client }
63 }
64
65 fn set_origin<B>(&self, req: Request<B>) -> Result<Request<B>, BoxError> {
66 let (mut parts, body) = req.into_parts();
67 let (scheme, authority, base_path) = {
68 let scheme = self.base.scheme().unwrap_or(&Scheme::HTTP);
69 let authority = self.base.authority().expect("Authority not found");
70 let base_path = self.base.path().trim_end_matches('/');
71 (scheme, authority, base_path)
72 };
73 let path = parts.uri.path_and_query().expect("PathAndQuery not found");
74 let pq: PathAndQuery = format!("{base_path}{path}").parse().expect("PathAndQuery invalid");
75
76 let uri = Uri::builder()
77 .scheme(scheme.as_ref())
78 .authority(authority.as_ref())
79 .path_and_query(pq)
80 .build()
81 .map_err(Box::new)?;
82
83 parts.uri = uri;
84 Ok(Request::from_parts(parts, body))
85 }
86}
87
88pub struct ClientBuilder {
90 connector: HttpConnector,
91 uri: Option<http::Uri>,
92}
93
94impl ClientBuilder {
95 pub fn with_connector(self, connector: HttpConnector) -> ClientBuilder {
97 ClientBuilder {
98 connector,
99 uri: self.uri,
100 }
101 }
102
103 pub fn with_endpoint(self, uri: http::Uri) -> Self {
106 Self { uri: Some(uri), ..self }
107 }
108
109 pub fn build(self) -> Result<Client, Error> {
111 let uri = match self.uri {
112 Some(uri) => uri,
113 None => {
114 let uri = std::env::var("AWS_LAMBDA_RUNTIME_API").expect("Missing AWS_LAMBDA_RUNTIME_API env var");
115 uri.try_into().expect("Unable to convert to URL")
116 }
117 };
118 Ok(Client::with(uri, self.connector))
119 }
120}
121
122pub fn build_request() -> http::request::Builder {
128 const USER_AGENT: &str = match CUSTOM_USER_AGENT {
129 Some(value) => value,
130 None => DEFAULT_USER_AGENT,
131 };
132 http::Request::builder().header(USER_AGENT_HEADER, USER_AGENT)
133}
134
135#[cfg(test)]
136mod tests {
137 use super::*;
138
139 #[test]
140 fn test_set_origin() {
141 let base = "http://localhost:9001";
142 let client = Client::builder().with_endpoint(base.parse().unwrap()).build().unwrap();
143 let req = build_request()
144 .uri("/2018-06-01/runtime/invocation/next")
145 .body(())
146 .unwrap();
147 let req = client.set_origin(req).unwrap();
148 assert_eq!(
149 "http://localhost:9001/2018-06-01/runtime/invocation/next",
150 &req.uri().to_string()
151 );
152 }
153
154 #[test]
155 fn test_set_origin_with_base_path() {
156 let base = "http://localhost:9001/foo";
157 let client = Client::builder().with_endpoint(base.parse().unwrap()).build().unwrap();
158 let req = build_request()
159 .uri("/2018-06-01/runtime/invocation/next")
160 .body(())
161 .unwrap();
162 let req = client.set_origin(req).unwrap();
163 assert_eq!(
164 "http://localhost:9001/foo/2018-06-01/runtime/invocation/next",
165 &req.uri().to_string()
166 );
167
168 let base = "http://localhost:9001/foo/";
169 let client = Client::builder().with_endpoint(base.parse().unwrap()).build().unwrap();
170 let req = build_request()
171 .uri("/2018-06-01/runtime/invocation/next")
172 .body(())
173 .unwrap();
174 let req = client.set_origin(req).unwrap();
175 assert_eq!(
176 "http://localhost:9001/foo/2018-06-01/runtime/invocation/next",
177 &req.uri().to_string()
178 );
179 }
180}