spikard 0.3.4 → 0.3.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/LICENSE +1 -1
- data/README.md +659 -659
- data/ext/spikard_rb/Cargo.toml +17 -17
- data/ext/spikard_rb/extconf.rb +10 -10
- data/ext/spikard_rb/src/lib.rs +6 -6
- data/lib/spikard/app.rb +386 -386
- data/lib/spikard/background.rb +27 -27
- data/lib/spikard/config.rb +396 -396
- data/lib/spikard/converters.rb +13 -13
- data/lib/spikard/handler_wrapper.rb +113 -113
- data/lib/spikard/provide.rb +214 -214
- data/lib/spikard/response.rb +173 -173
- data/lib/spikard/schema.rb +243 -243
- data/lib/spikard/sse.rb +111 -111
- data/lib/spikard/streaming_response.rb +44 -44
- data/lib/spikard/testing.rb +221 -221
- data/lib/spikard/upload_file.rb +131 -131
- data/lib/spikard/version.rb +5 -5
- data/lib/spikard/websocket.rb +59 -59
- data/lib/spikard.rb +43 -43
- data/sig/spikard.rbs +366 -360
- data/vendor/crates/spikard-core/Cargo.toml +40 -40
- data/vendor/crates/spikard-core/src/bindings/mod.rs +3 -3
- data/vendor/crates/spikard-core/src/bindings/response.rs +133 -133
- data/vendor/crates/spikard-core/src/debug.rs +63 -63
- data/vendor/crates/spikard-core/src/di/container.rs +726 -726
- data/vendor/crates/spikard-core/src/di/dependency.rs +273 -273
- data/vendor/crates/spikard-core/src/di/error.rs +118 -118
- data/vendor/crates/spikard-core/src/di/factory.rs +538 -538
- data/vendor/crates/spikard-core/src/di/graph.rs +545 -545
- data/vendor/crates/spikard-core/src/di/mod.rs +192 -192
- data/vendor/crates/spikard-core/src/di/resolved.rs +411 -411
- data/vendor/crates/spikard-core/src/di/value.rs +283 -283
- data/vendor/crates/spikard-core/src/errors.rs +39 -39
- data/vendor/crates/spikard-core/src/http.rs +153 -153
- data/vendor/crates/spikard-core/src/lib.rs +29 -29
- data/vendor/crates/spikard-core/src/lifecycle.rs +422 -422
- data/vendor/crates/spikard-core/src/parameters.rs +722 -722
- data/vendor/crates/spikard-core/src/problem.rs +310 -310
- data/vendor/crates/spikard-core/src/request_data.rs +189 -189
- data/vendor/crates/spikard-core/src/router.rs +249 -249
- data/vendor/crates/spikard-core/src/schema_registry.rs +183 -183
- data/vendor/crates/spikard-core/src/type_hints.rs +304 -304
- data/vendor/crates/spikard-core/src/validation.rs +699 -699
- data/vendor/crates/spikard-http/Cargo.toml +68 -58
- data/vendor/crates/spikard-http/src/auth.rs +247 -247
- data/vendor/crates/spikard-http/src/background.rs +249 -249
- data/vendor/crates/spikard-http/src/bindings/mod.rs +3 -3
- data/vendor/crates/spikard-http/src/bindings/response.rs +1 -1
- data/vendor/crates/spikard-http/src/body_metadata.rs +8 -8
- data/vendor/crates/spikard-http/src/cors.rs +490 -490
- data/vendor/crates/spikard-http/src/debug.rs +63 -63
- data/vendor/crates/spikard-http/src/di_handler.rs +423 -423
- data/vendor/crates/spikard-http/src/handler_response.rs +190 -190
- data/vendor/crates/spikard-http/src/handler_trait.rs +228 -228
- data/vendor/crates/spikard-http/src/handler_trait_tests.rs +284 -284
- data/vendor/crates/spikard-http/src/lib.rs +529 -529
- data/vendor/crates/spikard-http/src/lifecycle/adapter.rs +149 -149
- data/vendor/crates/spikard-http/src/lifecycle.rs +428 -428
- data/vendor/crates/spikard-http/src/middleware/mod.rs +285 -285
- data/vendor/crates/spikard-http/src/middleware/multipart.rs +86 -86
- data/vendor/crates/spikard-http/src/middleware/urlencoded.rs +147 -147
- data/vendor/crates/spikard-http/src/middleware/validation.rs +287 -287
- data/vendor/crates/spikard-http/src/openapi/mod.rs +309 -309
- data/vendor/crates/spikard-http/src/openapi/parameter_extraction.rs +190 -190
- data/vendor/crates/spikard-http/src/openapi/schema_conversion.rs +308 -308
- data/vendor/crates/spikard-http/src/openapi/spec_generation.rs +195 -195
- data/vendor/crates/spikard-http/src/parameters.rs +1 -1
- data/vendor/crates/spikard-http/src/problem.rs +1 -1
- data/vendor/crates/spikard-http/src/query_parser.rs +369 -369
- data/vendor/crates/spikard-http/src/response.rs +399 -399
- data/vendor/crates/spikard-http/src/router.rs +1 -1
- data/vendor/crates/spikard-http/src/schema_registry.rs +1 -1
- data/vendor/crates/spikard-http/src/server/handler.rs +87 -87
- data/vendor/crates/spikard-http/src/server/lifecycle_execution.rs +98 -98
- data/vendor/crates/spikard-http/src/server/mod.rs +805 -805
- data/vendor/crates/spikard-http/src/server/request_extraction.rs +119 -119
- data/vendor/crates/spikard-http/src/sse.rs +447 -447
- data/vendor/crates/spikard-http/src/testing/form.rs +14 -14
- data/vendor/crates/spikard-http/src/testing/multipart.rs +60 -60
- data/vendor/crates/spikard-http/src/testing/test_client.rs +285 -285
- data/vendor/crates/spikard-http/src/testing.rs +377 -377
- data/vendor/crates/spikard-http/src/type_hints.rs +1 -1
- data/vendor/crates/spikard-http/src/validation.rs +1 -1
- data/vendor/crates/spikard-http/src/websocket.rs +324 -324
- data/vendor/crates/spikard-rb/Cargo.toml +42 -42
- data/vendor/crates/spikard-rb/build.rs +8 -8
- data/vendor/crates/spikard-rb/src/background.rs +63 -63
- data/vendor/crates/spikard-rb/src/config.rs +294 -294
- data/vendor/crates/spikard-rb/src/conversion.rs +453 -453
- data/vendor/crates/spikard-rb/src/di.rs +409 -409
- data/vendor/crates/spikard-rb/src/handler.rs +625 -625
- data/vendor/crates/spikard-rb/src/lib.rs +2771 -2771
- data/vendor/crates/spikard-rb/src/lifecycle.rs +274 -274
- data/vendor/crates/spikard-rb/src/server.rs +283 -283
- data/vendor/crates/spikard-rb/src/sse.rs +231 -231
- data/vendor/crates/spikard-rb/src/test_client.rs +404 -404
- data/vendor/crates/spikard-rb/src/test_sse.rs +143 -143
- data/vendor/crates/spikard-rb/src/test_websocket.rs +221 -221
- data/vendor/crates/spikard-rb/src/websocket.rs +233 -233
- metadata +1 -79
- data/vendor/spikard-core/Cargo.toml +0 -40
- data/vendor/spikard-core/src/bindings/mod.rs +0 -3
- data/vendor/spikard-core/src/bindings/response.rs +0 -133
- data/vendor/spikard-core/src/debug.rs +0 -63
- data/vendor/spikard-core/src/di/container.rs +0 -726
- data/vendor/spikard-core/src/di/dependency.rs +0 -273
- data/vendor/spikard-core/src/di/error.rs +0 -118
- data/vendor/spikard-core/src/di/factory.rs +0 -538
- data/vendor/spikard-core/src/di/graph.rs +0 -545
- data/vendor/spikard-core/src/di/mod.rs +0 -192
- data/vendor/spikard-core/src/di/resolved.rs +0 -411
- data/vendor/spikard-core/src/di/value.rs +0 -283
- data/vendor/spikard-core/src/http.rs +0 -153
- data/vendor/spikard-core/src/lib.rs +0 -28
- data/vendor/spikard-core/src/lifecycle.rs +0 -422
- data/vendor/spikard-core/src/parameters.rs +0 -719
- data/vendor/spikard-core/src/problem.rs +0 -310
- data/vendor/spikard-core/src/request_data.rs +0 -189
- data/vendor/spikard-core/src/router.rs +0 -249
- data/vendor/spikard-core/src/schema_registry.rs +0 -183
- data/vendor/spikard-core/src/type_hints.rs +0 -304
- data/vendor/spikard-core/src/validation.rs +0 -699
- data/vendor/spikard-http/Cargo.toml +0 -58
- data/vendor/spikard-http/src/auth.rs +0 -247
- data/vendor/spikard-http/src/background.rs +0 -249
- data/vendor/spikard-http/src/bindings/mod.rs +0 -3
- data/vendor/spikard-http/src/bindings/response.rs +0 -1
- data/vendor/spikard-http/src/body_metadata.rs +0 -8
- data/vendor/spikard-http/src/cors.rs +0 -490
- data/vendor/spikard-http/src/debug.rs +0 -63
- data/vendor/spikard-http/src/di_handler.rs +0 -423
- data/vendor/spikard-http/src/handler_response.rs +0 -190
- data/vendor/spikard-http/src/handler_trait.rs +0 -228
- data/vendor/spikard-http/src/handler_trait_tests.rs +0 -284
- data/vendor/spikard-http/src/lib.rs +0 -529
- data/vendor/spikard-http/src/lifecycle/adapter.rs +0 -149
- data/vendor/spikard-http/src/lifecycle.rs +0 -428
- data/vendor/spikard-http/src/middleware/mod.rs +0 -285
- data/vendor/spikard-http/src/middleware/multipart.rs +0 -86
- data/vendor/spikard-http/src/middleware/urlencoded.rs +0 -147
- data/vendor/spikard-http/src/middleware/validation.rs +0 -287
- data/vendor/spikard-http/src/openapi/mod.rs +0 -309
- data/vendor/spikard-http/src/openapi/parameter_extraction.rs +0 -190
- data/vendor/spikard-http/src/openapi/schema_conversion.rs +0 -308
- data/vendor/spikard-http/src/openapi/spec_generation.rs +0 -195
- data/vendor/spikard-http/src/parameters.rs +0 -1
- data/vendor/spikard-http/src/problem.rs +0 -1
- data/vendor/spikard-http/src/query_parser.rs +0 -369
- data/vendor/spikard-http/src/response.rs +0 -399
- data/vendor/spikard-http/src/router.rs +0 -1
- data/vendor/spikard-http/src/schema_registry.rs +0 -1
- data/vendor/spikard-http/src/server/handler.rs +0 -80
- data/vendor/spikard-http/src/server/lifecycle_execution.rs +0 -98
- data/vendor/spikard-http/src/server/mod.rs +0 -805
- data/vendor/spikard-http/src/server/request_extraction.rs +0 -119
- data/vendor/spikard-http/src/sse.rs +0 -447
- data/vendor/spikard-http/src/testing/form.rs +0 -14
- data/vendor/spikard-http/src/testing/multipart.rs +0 -60
- data/vendor/spikard-http/src/testing/test_client.rs +0 -285
- data/vendor/spikard-http/src/testing.rs +0 -377
- data/vendor/spikard-http/src/type_hints.rs +0 -1
- data/vendor/spikard-http/src/validation.rs +0 -1
- data/vendor/spikard-http/src/websocket.rs +0 -324
- data/vendor/spikard-rb/Cargo.toml +0 -42
- data/vendor/spikard-rb/build.rs +0 -8
- data/vendor/spikard-rb/src/background.rs +0 -63
- data/vendor/spikard-rb/src/config.rs +0 -294
- data/vendor/spikard-rb/src/conversion.rs +0 -392
- data/vendor/spikard-rb/src/di.rs +0 -409
- data/vendor/spikard-rb/src/handler.rs +0 -534
- data/vendor/spikard-rb/src/lib.rs +0 -2020
- data/vendor/spikard-rb/src/lifecycle.rs +0 -267
- data/vendor/spikard-rb/src/server.rs +0 -283
- data/vendor/spikard-rb/src/sse.rs +0 -231
- data/vendor/spikard-rb/src/test_client.rs +0 -404
- data/vendor/spikard-rb/src/test_sse.rs +0 -143
- data/vendor/spikard-rb/src/test_websocket.rs +0 -221
- data/vendor/spikard-rb/src/websocket.rs +0 -233
|
@@ -1,190 +1,190 @@
|
|
|
1
|
-
use axum::{
|
|
2
|
-
BoxError,
|
|
3
|
-
body::Body,
|
|
4
|
-
http::{HeaderMap, HeaderName, HeaderValue, StatusCode},
|
|
5
|
-
response::Response as AxumResponse,
|
|
6
|
-
};
|
|
7
|
-
use bytes::Bytes;
|
|
8
|
-
use futures::{Stream, StreamExt};
|
|
9
|
-
use std::pin::Pin;
|
|
10
|
-
|
|
11
|
-
/// Unified response type that can represent either a ready response or a streaming body.
|
|
12
|
-
///
|
|
13
|
-
/// This enum allows handlers to return either:
|
|
14
|
-
/// - A complete response that's ready to send (`Response` variant)
|
|
15
|
-
/// - A streaming response with potentially unbounded data (`Stream` variant)
|
|
16
|
-
///
|
|
17
|
-
/// # Variants
|
|
18
|
-
///
|
|
19
|
-
/// * `Response` - A complete Axum response ready to send to the client. Use this for
|
|
20
|
-
/// responses where you have all the data ready (files, JSON bodies, HTML, etc.)
|
|
21
|
-
///
|
|
22
|
-
/// * `Stream` - A streaming response that produces data chunks over time. Use this for:
|
|
23
|
-
/// - Large files (avoid loading entire file in memory)
|
|
24
|
-
/// - Server-Sent Events (SSE)
|
|
25
|
-
/// - Long-polling responses
|
|
26
|
-
/// - Real-time data feeds
|
|
27
|
-
/// - Any unbounded or very large responses
|
|
28
|
-
///
|
|
29
|
-
/// # Examples
|
|
30
|
-
///
|
|
31
|
-
/// ```ignore
|
|
32
|
-
/// // Regular response
|
|
33
|
-
/// let response = AxumResponse::builder()
|
|
34
|
-
/// .status(StatusCode::OK)
|
|
35
|
-
/// .body(Body::from("Hello"))
|
|
36
|
-
/// .unwrap();
|
|
37
|
-
/// let handler_response = HandlerResponse::from(response);
|
|
38
|
-
///
|
|
39
|
-
/// // Streaming response
|
|
40
|
-
/// let stream = futures::stream::iter(vec![
|
|
41
|
-
/// Ok::<_, Box<dyn std::error::Error>>(Bytes::from("chunk1")),
|
|
42
|
-
/// Ok(Bytes::from("chunk2")),
|
|
43
|
-
/// ]);
|
|
44
|
-
/// let response = HandlerResponse::stream(stream)
|
|
45
|
-
/// .with_status(StatusCode::OK);
|
|
46
|
-
/// ```
|
|
47
|
-
pub enum HandlerResponse {
|
|
48
|
-
/// A complete response ready to send
|
|
49
|
-
Response(AxumResponse<Body>),
|
|
50
|
-
/// A streaming response with custom status and headers
|
|
51
|
-
Stream {
|
|
52
|
-
/// The byte stream that will be sent to the client
|
|
53
|
-
stream: Pin<Box<dyn Stream<Item = Result<Bytes, BoxError>> + Send + 'static>>,
|
|
54
|
-
/// HTTP status code for the response
|
|
55
|
-
status: StatusCode,
|
|
56
|
-
/// Response headers to send
|
|
57
|
-
headers: HeaderMap,
|
|
58
|
-
},
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
impl From<AxumResponse<Body>> for HandlerResponse {
|
|
62
|
-
fn from(response: AxumResponse<Body>) -> Self {
|
|
63
|
-
HandlerResponse::Response(response)
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
impl HandlerResponse {
|
|
68
|
-
/// Convert the handler response into an Axum response.
|
|
69
|
-
///
|
|
70
|
-
/// Consumes the `HandlerResponse` and produces an `AxumResponse<Body>` ready
|
|
71
|
-
/// to be sent to the client. For streaming responses, wraps the stream in an
|
|
72
|
-
/// Axum Body.
|
|
73
|
-
///
|
|
74
|
-
/// # Returns
|
|
75
|
-
/// An `AxumResponse<Body>` ready to be returned from an Axum handler
|
|
76
|
-
pub fn into_response(self) -> AxumResponse<Body> {
|
|
77
|
-
match self {
|
|
78
|
-
HandlerResponse::Response(response) => response,
|
|
79
|
-
HandlerResponse::Stream {
|
|
80
|
-
stream,
|
|
81
|
-
status,
|
|
82
|
-
mut headers,
|
|
83
|
-
} => {
|
|
84
|
-
let body = Body::from_stream(stream);
|
|
85
|
-
let mut response = AxumResponse::new(body);
|
|
86
|
-
*response.status_mut() = status;
|
|
87
|
-
response.headers_mut().extend(headers.drain());
|
|
88
|
-
response
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/// Create a streaming response from any async stream of byte chunks.
|
|
94
|
-
///
|
|
95
|
-
/// Wraps an async stream of byte chunks into a `HandlerResponse::Stream`.
|
|
96
|
-
/// This is useful for large files, real-time data, or any unbounded response.
|
|
97
|
-
///
|
|
98
|
-
/// # Type Parameters
|
|
99
|
-
/// * `S` - The stream type implementing `Stream<Item = Result<Bytes, E>>`
|
|
100
|
-
/// * `E` - The error type that can be converted to `BoxError`
|
|
101
|
-
///
|
|
102
|
-
/// # Arguments
|
|
103
|
-
/// * `stream` - An async stream that yields byte chunks or errors
|
|
104
|
-
///
|
|
105
|
-
/// # Returns
|
|
106
|
-
/// A `HandlerResponse` with 200 OK status and empty headers (customize with
|
|
107
|
-
/// `with_status()` and `with_header()`)
|
|
108
|
-
///
|
|
109
|
-
/// # Example
|
|
110
|
-
///
|
|
111
|
-
/// ```ignore
|
|
112
|
-
/// use futures::stream;
|
|
113
|
-
/// use spikard_http::HandlerResponse;
|
|
114
|
-
/// use bytes::Bytes;
|
|
115
|
-
///
|
|
116
|
-
/// let stream = stream::iter(vec![
|
|
117
|
-
/// Ok::<_, Box<dyn std::error::Error>>(Bytes::from("Hello ")),
|
|
118
|
-
/// Ok(Bytes::from("World")),
|
|
119
|
-
/// ]);
|
|
120
|
-
/// let response = HandlerResponse::stream(stream)
|
|
121
|
-
/// .with_status(StatusCode::OK);
|
|
122
|
-
/// ```
|
|
123
|
-
pub fn stream<S, E>(stream: S) -> Self
|
|
124
|
-
where
|
|
125
|
-
S: Stream<Item = Result<Bytes, E>> + Send + 'static,
|
|
126
|
-
E: Into<BoxError>,
|
|
127
|
-
{
|
|
128
|
-
let mapped = stream.map(|chunk| chunk.map_err(Into::into));
|
|
129
|
-
HandlerResponse::Stream {
|
|
130
|
-
stream: Box::pin(mapped),
|
|
131
|
-
status: StatusCode::OK,
|
|
132
|
-
headers: HeaderMap::new(),
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
/// Override the HTTP status code for the streaming response.
|
|
137
|
-
///
|
|
138
|
-
/// Sets the HTTP status code to be used in the response. This only affects
|
|
139
|
-
/// `Stream` variants; regular responses already have their status set.
|
|
140
|
-
///
|
|
141
|
-
/// # Arguments
|
|
142
|
-
/// * `status` - The HTTP status code to use (e.g., `StatusCode::OK`)
|
|
143
|
-
///
|
|
144
|
-
/// # Returns
|
|
145
|
-
/// Self for method chaining
|
|
146
|
-
///
|
|
147
|
-
/// # Example
|
|
148
|
-
///
|
|
149
|
-
/// ```ignore
|
|
150
|
-
/// let response = HandlerResponse::stream(my_stream)
|
|
151
|
-
/// .with_status(StatusCode::PARTIAL_CONTENT);
|
|
152
|
-
/// ```
|
|
153
|
-
pub fn with_status(mut self, status: StatusCode) -> Self {
|
|
154
|
-
if let HandlerResponse::Stream { status: s, .. } = &mut self {
|
|
155
|
-
*s = status;
|
|
156
|
-
}
|
|
157
|
-
self
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
/// Insert or replace a header on the streaming response.
|
|
161
|
-
///
|
|
162
|
-
/// Adds an HTTP header to the response. This only affects `Stream` variants;
|
|
163
|
-
/// regular responses already have their headers set. If a header with the same
|
|
164
|
-
/// name already exists, it will be replaced.
|
|
165
|
-
///
|
|
166
|
-
/// # Arguments
|
|
167
|
-
/// * `name` - The header name (e.g., `HeaderName::from_static("content-type")`)
|
|
168
|
-
/// * `value` - The header value
|
|
169
|
-
///
|
|
170
|
-
/// # Returns
|
|
171
|
-
/// Self for method chaining
|
|
172
|
-
///
|
|
173
|
-
/// # Example
|
|
174
|
-
///
|
|
175
|
-
/// ```ignore
|
|
176
|
-
/// use axum::http::{HeaderName, HeaderValue};
|
|
177
|
-
///
|
|
178
|
-
/// let response = HandlerResponse::stream(my_stream)
|
|
179
|
-
/// .with_header(
|
|
180
|
-
/// HeaderName::from_static("content-type"),
|
|
181
|
-
/// HeaderValue::from_static("application/octet-stream")
|
|
182
|
-
/// );
|
|
183
|
-
/// ```
|
|
184
|
-
pub fn with_header(mut self, name: HeaderName, value: HeaderValue) -> Self {
|
|
185
|
-
if let HandlerResponse::Stream { headers, .. } = &mut self {
|
|
186
|
-
headers.insert(name, value);
|
|
187
|
-
}
|
|
188
|
-
self
|
|
189
|
-
}
|
|
190
|
-
}
|
|
1
|
+
use axum::{
|
|
2
|
+
BoxError,
|
|
3
|
+
body::Body,
|
|
4
|
+
http::{HeaderMap, HeaderName, HeaderValue, StatusCode},
|
|
5
|
+
response::Response as AxumResponse,
|
|
6
|
+
};
|
|
7
|
+
use bytes::Bytes;
|
|
8
|
+
use futures::{Stream, StreamExt};
|
|
9
|
+
use std::pin::Pin;
|
|
10
|
+
|
|
11
|
+
/// Unified response type that can represent either a ready response or a streaming body.
|
|
12
|
+
///
|
|
13
|
+
/// This enum allows handlers to return either:
|
|
14
|
+
/// - A complete response that's ready to send (`Response` variant)
|
|
15
|
+
/// - A streaming response with potentially unbounded data (`Stream` variant)
|
|
16
|
+
///
|
|
17
|
+
/// # Variants
|
|
18
|
+
///
|
|
19
|
+
/// * `Response` - A complete Axum response ready to send to the client. Use this for
|
|
20
|
+
/// responses where you have all the data ready (files, JSON bodies, HTML, etc.)
|
|
21
|
+
///
|
|
22
|
+
/// * `Stream` - A streaming response that produces data chunks over time. Use this for:
|
|
23
|
+
/// - Large files (avoid loading entire file in memory)
|
|
24
|
+
/// - Server-Sent Events (SSE)
|
|
25
|
+
/// - Long-polling responses
|
|
26
|
+
/// - Real-time data feeds
|
|
27
|
+
/// - Any unbounded or very large responses
|
|
28
|
+
///
|
|
29
|
+
/// # Examples
|
|
30
|
+
///
|
|
31
|
+
/// ```ignore
|
|
32
|
+
/// // Regular response
|
|
33
|
+
/// let response = AxumResponse::builder()
|
|
34
|
+
/// .status(StatusCode::OK)
|
|
35
|
+
/// .body(Body::from("Hello"))
|
|
36
|
+
/// .unwrap();
|
|
37
|
+
/// let handler_response = HandlerResponse::from(response);
|
|
38
|
+
///
|
|
39
|
+
/// // Streaming response
|
|
40
|
+
/// let stream = futures::stream::iter(vec![
|
|
41
|
+
/// Ok::<_, Box<dyn std::error::Error>>(Bytes::from("chunk1")),
|
|
42
|
+
/// Ok(Bytes::from("chunk2")),
|
|
43
|
+
/// ]);
|
|
44
|
+
/// let response = HandlerResponse::stream(stream)
|
|
45
|
+
/// .with_status(StatusCode::OK);
|
|
46
|
+
/// ```
|
|
47
|
+
pub enum HandlerResponse {
|
|
48
|
+
/// A complete response ready to send
|
|
49
|
+
Response(AxumResponse<Body>),
|
|
50
|
+
/// A streaming response with custom status and headers
|
|
51
|
+
Stream {
|
|
52
|
+
/// The byte stream that will be sent to the client
|
|
53
|
+
stream: Pin<Box<dyn Stream<Item = Result<Bytes, BoxError>> + Send + 'static>>,
|
|
54
|
+
/// HTTP status code for the response
|
|
55
|
+
status: StatusCode,
|
|
56
|
+
/// Response headers to send
|
|
57
|
+
headers: HeaderMap,
|
|
58
|
+
},
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
impl From<AxumResponse<Body>> for HandlerResponse {
|
|
62
|
+
fn from(response: AxumResponse<Body>) -> Self {
|
|
63
|
+
HandlerResponse::Response(response)
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
impl HandlerResponse {
|
|
68
|
+
/// Convert the handler response into an Axum response.
|
|
69
|
+
///
|
|
70
|
+
/// Consumes the `HandlerResponse` and produces an `AxumResponse<Body>` ready
|
|
71
|
+
/// to be sent to the client. For streaming responses, wraps the stream in an
|
|
72
|
+
/// Axum Body.
|
|
73
|
+
///
|
|
74
|
+
/// # Returns
|
|
75
|
+
/// An `AxumResponse<Body>` ready to be returned from an Axum handler
|
|
76
|
+
pub fn into_response(self) -> AxumResponse<Body> {
|
|
77
|
+
match self {
|
|
78
|
+
HandlerResponse::Response(response) => response,
|
|
79
|
+
HandlerResponse::Stream {
|
|
80
|
+
stream,
|
|
81
|
+
status,
|
|
82
|
+
mut headers,
|
|
83
|
+
} => {
|
|
84
|
+
let body = Body::from_stream(stream);
|
|
85
|
+
let mut response = AxumResponse::new(body);
|
|
86
|
+
*response.status_mut() = status;
|
|
87
|
+
response.headers_mut().extend(headers.drain());
|
|
88
|
+
response
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/// Create a streaming response from any async stream of byte chunks.
|
|
94
|
+
///
|
|
95
|
+
/// Wraps an async stream of byte chunks into a `HandlerResponse::Stream`.
|
|
96
|
+
/// This is useful for large files, real-time data, or any unbounded response.
|
|
97
|
+
///
|
|
98
|
+
/// # Type Parameters
|
|
99
|
+
/// * `S` - The stream type implementing `Stream<Item = Result<Bytes, E>>`
|
|
100
|
+
/// * `E` - The error type that can be converted to `BoxError`
|
|
101
|
+
///
|
|
102
|
+
/// # Arguments
|
|
103
|
+
/// * `stream` - An async stream that yields byte chunks or errors
|
|
104
|
+
///
|
|
105
|
+
/// # Returns
|
|
106
|
+
/// A `HandlerResponse` with 200 OK status and empty headers (customize with
|
|
107
|
+
/// `with_status()` and `with_header()`)
|
|
108
|
+
///
|
|
109
|
+
/// # Example
|
|
110
|
+
///
|
|
111
|
+
/// ```ignore
|
|
112
|
+
/// use futures::stream;
|
|
113
|
+
/// use spikard_http::HandlerResponse;
|
|
114
|
+
/// use bytes::Bytes;
|
|
115
|
+
///
|
|
116
|
+
/// let stream = stream::iter(vec![
|
|
117
|
+
/// Ok::<_, Box<dyn std::error::Error>>(Bytes::from("Hello ")),
|
|
118
|
+
/// Ok(Bytes::from("World")),
|
|
119
|
+
/// ]);
|
|
120
|
+
/// let response = HandlerResponse::stream(stream)
|
|
121
|
+
/// .with_status(StatusCode::OK);
|
|
122
|
+
/// ```
|
|
123
|
+
pub fn stream<S, E>(stream: S) -> Self
|
|
124
|
+
where
|
|
125
|
+
S: Stream<Item = Result<Bytes, E>> + Send + 'static,
|
|
126
|
+
E: Into<BoxError>,
|
|
127
|
+
{
|
|
128
|
+
let mapped = stream.map(|chunk| chunk.map_err(Into::into));
|
|
129
|
+
HandlerResponse::Stream {
|
|
130
|
+
stream: Box::pin(mapped),
|
|
131
|
+
status: StatusCode::OK,
|
|
132
|
+
headers: HeaderMap::new(),
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/// Override the HTTP status code for the streaming response.
|
|
137
|
+
///
|
|
138
|
+
/// Sets the HTTP status code to be used in the response. This only affects
|
|
139
|
+
/// `Stream` variants; regular responses already have their status set.
|
|
140
|
+
///
|
|
141
|
+
/// # Arguments
|
|
142
|
+
/// * `status` - The HTTP status code to use (e.g., `StatusCode::OK`)
|
|
143
|
+
///
|
|
144
|
+
/// # Returns
|
|
145
|
+
/// Self for method chaining
|
|
146
|
+
///
|
|
147
|
+
/// # Example
|
|
148
|
+
///
|
|
149
|
+
/// ```ignore
|
|
150
|
+
/// let response = HandlerResponse::stream(my_stream)
|
|
151
|
+
/// .with_status(StatusCode::PARTIAL_CONTENT);
|
|
152
|
+
/// ```
|
|
153
|
+
pub fn with_status(mut self, status: StatusCode) -> Self {
|
|
154
|
+
if let HandlerResponse::Stream { status: s, .. } = &mut self {
|
|
155
|
+
*s = status;
|
|
156
|
+
}
|
|
157
|
+
self
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/// Insert or replace a header on the streaming response.
|
|
161
|
+
///
|
|
162
|
+
/// Adds an HTTP header to the response. This only affects `Stream` variants;
|
|
163
|
+
/// regular responses already have their headers set. If a header with the same
|
|
164
|
+
/// name already exists, it will be replaced.
|
|
165
|
+
///
|
|
166
|
+
/// # Arguments
|
|
167
|
+
/// * `name` - The header name (e.g., `HeaderName::from_static("content-type")`)
|
|
168
|
+
/// * `value` - The header value
|
|
169
|
+
///
|
|
170
|
+
/// # Returns
|
|
171
|
+
/// Self for method chaining
|
|
172
|
+
///
|
|
173
|
+
/// # Example
|
|
174
|
+
///
|
|
175
|
+
/// ```ignore
|
|
176
|
+
/// use axum::http::{HeaderName, HeaderValue};
|
|
177
|
+
///
|
|
178
|
+
/// let response = HandlerResponse::stream(my_stream)
|
|
179
|
+
/// .with_header(
|
|
180
|
+
/// HeaderName::from_static("content-type"),
|
|
181
|
+
/// HeaderValue::from_static("application/octet-stream")
|
|
182
|
+
/// );
|
|
183
|
+
/// ```
|
|
184
|
+
pub fn with_header(mut self, name: HeaderName, value: HeaderValue) -> Self {
|
|
185
|
+
if let HandlerResponse::Stream { headers, .. } = &mut self {
|
|
186
|
+
headers.insert(name, value);
|
|
187
|
+
}
|
|
188
|
+
self
|
|
189
|
+
}
|
|
190
|
+
}
|