Skip to content

Commit bec5a67

Browse files
author
Davide Melfi
committed
chore: run fmt
1 parent c29772a commit bec5a67

3 files changed

Lines changed: 110 additions & 26 deletions

File tree

breaking_case_example.rs

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
// This is a customer's code that would break when RuntimeApiClientFuture
2+
// changes from RuntimeApiClientFuture<F> to RuntimeApiClientFuture<F, B>
3+
4+
use lambda_runtime::{service_fn, Error, LambdaEvent, Runtime};
5+
use serde_json::Value;
6+
use tower::{Layer, Service};
7+
use std::task::{Context, Poll};
8+
use std::future::Future;
9+
use std::pin::Pin;
10+
11+
// Customer creates a custom middleware layer
12+
pub struct LoggingLayer;
13+
14+
impl<S> Layer<S> for LoggingLayer {
15+
type Service = LoggingService<S>;
16+
17+
fn layer(&self, inner: S) -> Self::Service {
18+
LoggingService { inner }
19+
}
20+
}
21+
22+
pub struct LoggingService<S> {
23+
inner: S,
24+
}
25+
26+
// Here's where the breaking change happens!
27+
// The customer implements Service and explicitly names the Future type
28+
impl<S> Service<lambda_runtime::LambdaInvocation> for LoggingService<S>
29+
where
30+
S: Service<lambda_runtime::LambdaInvocation, Response = ()>,
31+
S::Error: std::error::Error + Send + Sync + 'static,
32+
S::Future: Future<Output = Result<(), S::Error>> + Send + 'static,
33+
{
34+
type Response = ();
35+
type Error = S::Error;
36+
37+
// BREAKING CASE 1: Wrapping the future with explicit type annotation
38+
type Future = Pin<Box<dyn Future<Output = Result<(), S::Error>> + Send>>;
39+
40+
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
41+
self.inner.poll_ready(cx)
42+
}
43+
44+
fn call(&mut self, req: lambda_runtime::LambdaInvocation) -> Self::Future {
45+
println!("Processing invocation: {}", req.context.request_id);
46+
47+
let future = self.inner.call(req);
48+
49+
// BREAKING CASE 2: If customer tries to inspect the future type
50+
// They might write code that depends on the concrete type
51+
Box::pin(async move {
52+
let result = future.await;
53+
println!("Invocation completed");
54+
result
55+
})
56+
}
57+
}
58+
59+
// BREAKING CASE 3: Customer writes a helper function that explicitly
60+
// constrains the Future type based on what they observed
61+
pub fn create_runtime_with_logging<F, EventPayload, Response>(
62+
handler: F,
63+
) -> Runtime<
64+
LoggingService<
65+
// This type signature explicitly names RuntimeApiClientService
66+
// and its associated types, which would break when generics change
67+
impl Service<
68+
lambda_runtime::LambdaInvocation,
69+
Response = (),
70+
Error = lambda_runtime_api_client::BoxError,
71+
// The Future type here implicitly depends on RuntimeApiClientFuture
72+
>
73+
>
74+
>
75+
where
76+
F: Service<LambdaEvent<EventPayload>, Response = Response>,
77+
F::Future: Future<Output = Result<Response, F::Error>>,
78+
F::Error: Into<lambda_runtime::Diagnostic> + std::fmt::Debug,
79+
EventPayload: for<'de> serde::Deserialize<'de>,
80+
Response: lambda_runtime::IntoFunctionResponse<Value, futures::stream::Empty<Result<bytes::Bytes, std::io::Error>>>,
81+
{
82+
Runtime::new(handler).layer(LoggingLayer)
83+
}
84+
85+
#[tokio::main]
86+
async fn main() -> Result<(), Error> {
87+
let handler = service_fn(my_handler);
88+
89+
let runtime = create_runtime_with_logging(handler);
90+
91+
runtime.run().await?;
92+
Ok(())
93+
}
94+
95+
async fn my_handler(event: LambdaEvent<Value>) -> Result<Value, Error> {
96+
Ok(event.payload)
97+
}

lambda-runtime/src/layers/api_client.rs

Lines changed: 10 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -156,15 +156,17 @@ where
156156
{
157157
async move {
158158
let mut body = response.into_body();
159-
159+
160160
while let Some(frame_result) = body.frame().await {
161161
match frame_result {
162162
Ok(frame) => {
163163
if let Ok(trailers) = frame.into_trailers() {
164164
// Check for Lambda-Runtime-Function-Response-Status: timeout
165165
if let Some(status) = trailers.get("Lambda-Runtime-Function-Response-Status") {
166166
if status == "timeout" {
167-
warn!("Lambda invocation timed out - response was not sent within the configured timeout");
167+
warn!(
168+
"Lambda invocation timed out - response was not sent within the configured timeout"
169+
);
168170
}
169171
}
170172
}
@@ -205,23 +207,15 @@ mod tests {
205207
// Create a response body with trailers indicating timeout
206208
let body_data = Bytes::from_static(b"response body");
207209
let mut trailers = http::HeaderMap::new();
208-
trailers.insert(
209-
"Lambda-Runtime-Function-Response-Status",
210-
"timeout".parse().unwrap(),
211-
);
210+
trailers.insert("Lambda-Runtime-Function-Response-Status", "timeout".parse().unwrap());
212211

213212
// Create a stream with data frame followed by trailer frame
214-
let frames: Vec<Result<Frame<Bytes>, Infallible>> = vec![
215-
Ok(Frame::data(body_data)),
216-
Ok(Frame::trailers(trailers)),
217-
];
213+
let frames: Vec<Result<Frame<Bytes>, Infallible>> =
214+
vec![Ok(Frame::data(body_data)), Ok(Frame::trailers(trailers))];
218215
let stream_body: TestBody = StreamBody::new(futures::stream::iter(frames));
219216

220217
// Create a mock response
221-
let response: Response<TestBody> = Response::builder()
222-
.status(200)
223-
.body(stream_body)
224-
.unwrap();
218+
let response: Response<TestBody> = Response::builder().status(200).body(stream_body).unwrap();
225219

226220
// Test the reconcile_response function directly
227221
let result = reconcile_response(response).await;
@@ -239,10 +233,7 @@ mod tests {
239233
.flat_map(|span| span.events())
240234
.filter(|event| {
241235
event.metadata().level() == &tracing::Level::WARN
242-
&& event
243-
.message()
244-
.map(|msg| msg.contains("timed out"))
245-
.unwrap_or(false)
236+
&& event.message().map(|msg| msg.contains("timed out")).unwrap_or(false)
246237
})
247238
.collect();
248239

@@ -259,10 +250,7 @@ mod tests {
259250
let frames: Vec<Result<Frame<Bytes>, Infallible>> = vec![Ok(Frame::data(body_data))];
260251
let stream_body: TestBody = StreamBody::new(futures::stream::iter(frames));
261252

262-
let response: Response<TestBody> = Response::builder()
263-
.status(200)
264-
.body(stream_body)
265-
.unwrap();
253+
let response: Response<TestBody> = Response::builder().status(200).body(stream_body).unwrap();
266254

267255
// Test the reconcile_response function directly
268256
let result = reconcile_response(response).await;

lambda-runtime/src/runtime.rs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1214,7 +1214,7 @@ mod endpoint_tests {
12141214
) -> Result<serde_json::Value, Error> {
12151215
let count = call_count.fetch_add(1, Ordering::SeqCst);
12161216
let request_id = &event.context.request_id;
1217-
1217+
12181218
// Alternate between errors and successes
12191219
if count % 2 == 0 {
12201220
// Even calls: return error (simulating timeout or other failure)
@@ -1228,9 +1228,8 @@ mod endpoint_tests {
12281228
}
12291229
}
12301230

1231-
let handler = crate::service_fn(move |event| {
1232-
error_then_success_handler(event, handler_call_count_clone.clone())
1233-
});
1231+
let handler =
1232+
crate::service_fn(move |event| error_then_success_handler(event, handler_call_count_clone.clone()));
12341233
let client = Arc::new(Client::builder().with_endpoint(base).build()?);
12351234

12361235
let runtime = Runtime {

0 commit comments

Comments
 (0)