pub struct Response { /* private fields */ }Expand description
HTTP response.
Implementations§
Source§impl Response
impl Response
Sourcepub fn with_status(status: StatusCode) -> Response
pub fn with_status(status: StatusCode) -> Response
Create a response with the given status.
Examples found in repository?
124fn json_error(status: StatusCode, detail: &str) -> Response {
125 let body = serde_json::json!({ "detail": detail });
126 Response::with_status(status)
127 .header("content-type", b"application/json".to_vec())
128 .body(ResponseBody::Bytes(body.to_string().into_bytes()))
129}
130
131fn json_response(status: StatusCode, value: &impl Serialize) -> Response {
132 match serde_json::to_string(value) {
133 Ok(text) => Response::with_status(status)
134 .header("content-type", b"application/json".to_vec())
135 .body(ResponseBody::Bytes(text.into_bytes())),
136 Err(err) => json_error(
137 StatusCode::INTERNAL_SERVER_ERROR,
138 &format!("JSON serialization failed: {err}"),
139 ),
140 }
141}
142
143fn validate_input(input: &UserInput) -> Option<Response> {
144 if input.name.trim().is_empty() {
145 return Some(json_error(
146 StatusCode::BAD_REQUEST,
147 "name must not be empty",
148 ));
149 }
150 if !input.email.contains('@') {
151 return Some(json_error(
152 StatusCode::BAD_REQUEST,
153 "email must contain '@'",
154 ));
155 }
156 None
157}
158
159// ============================================================================
160// Handlers
161// ============================================================================
162
163fn create_user(_ctx: &RequestContext, req: &mut Request) -> std::future::Ready<Response> {
164 let input = match parse_json_body::<UserInput>(req) {
165 Ok(v) => v,
166 Err(r) => return std::future::ready(r),
167 };
168 if let Some(r) = validate_input(&input) {
169 return std::future::ready(r);
170 }
171
172 let user = with_db(|db| {
173 let id = db.next_id;
174 db.next_id += 1;
175 let user = User {
176 id,
177 name: input.name,
178 email: input.email,
179 };
180 db.users.insert(id, user.clone());
181 user
182 });
183
184 std::future::ready(json_response(StatusCode::CREATED, &user))
185}
186
187fn list_users(_ctx: &RequestContext, _req: &mut Request) -> std::future::Ready<Response> {
188 let users = with_db(|db| {
189 let mut v: Vec<User> = db.users.values().cloned().collect();
190 v.sort_by_key(|u| u.id);
191 v
192 });
193 std::future::ready(json_response(StatusCode::OK, &users))
194}
195
196fn get_user(_ctx: &RequestContext, req: &mut Request) -> std::future::Ready<Response> {
197 let id = match extract_user_id(req) {
198 Ok(id) => id,
199 Err(r) => return std::future::ready(r),
200 };
201 let result = with_db(|db| db.users.get(&id).cloned());
202 match result {
203 Some(user) => std::future::ready(json_response(StatusCode::OK, &user)),
204 None => std::future::ready(json_error(
205 StatusCode::NOT_FOUND,
206 &format!("User {id} not found"),
207 )),
208 }
209}
210
211fn update_user(_ctx: &RequestContext, req: &mut Request) -> std::future::Ready<Response> {
212 let id = match extract_user_id(req) {
213 Ok(id) => id,
214 Err(r) => return std::future::ready(r),
215 };
216 let input = match parse_json_body::<UserInput>(req) {
217 Ok(v) => v,
218 Err(r) => return std::future::ready(r),
219 };
220 if let Some(r) = validate_input(&input) {
221 return std::future::ready(r);
222 }
223
224 let result = with_db(|db| {
225 db.users.get_mut(&id).map(|user| {
226 user.name = input.name;
227 user.email = input.email;
228 user.clone()
229 })
230 });
231 match result {
232 Some(user) => std::future::ready(json_response(StatusCode::OK, &user)),
233 None => std::future::ready(json_error(
234 StatusCode::NOT_FOUND,
235 &format!("User {id} not found"),
236 )),
237 }
238}
239
240fn delete_user(_ctx: &RequestContext, req: &mut Request) -> std::future::Ready<Response> {
241 let id = match extract_user_id(req) {
242 Ok(id) => id,
243 Err(r) => return std::future::ready(r),
244 };
245 let removed = with_db(|db| db.users.remove(&id).is_some());
246 if removed {
247 std::future::ready(Response::with_status(StatusCode::NO_CONTENT))
248 } else {
249 std::future::ready(json_error(
250 StatusCode::NOT_FOUND,
251 &format!("User {id} not found"),
252 ))
253 }
254}More examples
106fn login_handler(_ctx: &RequestContext, req: &mut Request) -> std::future::Ready<Response> {
107 // For this demo, we only check Content-Type and return a fixed token.
108 // Real applications should parse the body and validate credentials.
109
110 // Check Content-Type
111 let is_json = req
112 .headers()
113 .get("content-type")
114 .is_some_and(|ct| ct.starts_with(b"application/json"));
115
116 if !is_json {
117 let error = serde_json::json!({
118 "detail": "Content-Type must be application/json"
119 });
120 return std::future::ready(
121 Response::with_status(StatusCode::UNSUPPORTED_MEDIA_TYPE)
122 .header("content-type", b"application/json".to_vec())
123 .body(ResponseBody::Bytes(error.to_string().into_bytes())),
124 );
125 }
126
127 // For demo purposes, we don't validate credentials - just return the token
128 // In production:
129 // 1. Parse the request body as LoginRequest
130 // 2. Verify username/password against your database
131 // 3. Generate a unique, cryptographically secure token
132 // 4. Store token -> user_id mapping (with expiration)
133
134 let response = LoginResponse {
135 access_token: DEMO_BEARER_VALUE.to_string(),
136 token_type: "bearer",
137 };
138
139 std::future::ready(
140 Response::ok()
141 .header("content-type", b"application/json".to_vec())
142 .body(ResponseBody::Bytes(json_bytes(&response))),
143 )
144}
145
146/// Handler for protected endpoint - requires valid bearer token.
147///
148/// This handler manually extracts and validates the bearer token:
149/// 1. Gets the Authorization header
150/// 2. Verifies it uses the Bearer scheme
151/// 3. Validates the token against our secret using constant-time comparison
152///
153/// Returns appropriate error responses for each failure mode.
154fn protected_handler(_ctx: &RequestContext, req: &mut Request) -> std::future::Ready<Response> {
155 // Step 1: Get the Authorization header
156 let Some(auth_header) = req.headers().get("authorization") else {
157 // Missing header -> 401 Unauthorized
158 let body = serde_json::json!({
159 "detail": "Not authenticated"
160 });
161 return std::future::ready(
162 Response::with_status(StatusCode::UNAUTHORIZED)
163 .header("www-authenticate", b"Bearer".to_vec())
164 .header("content-type", b"application/json".to_vec())
165 .body(ResponseBody::Bytes(body.to_string().into_bytes())),
166 );
167 };
168
169 // Step 2: Parse the Authorization header
170 let Ok(auth_str) = std::str::from_utf8(auth_header) else {
171 // Invalid UTF-8 -> 401 Unauthorized
172 let body = serde_json::json!({
173 "detail": "Invalid authentication credentials"
174 });
175 return std::future::ready(
176 Response::with_status(StatusCode::UNAUTHORIZED)
177 .header("www-authenticate", b"Bearer".to_vec())
178 .header("content-type", b"application/json".to_vec())
179 .body(ResponseBody::Bytes(body.to_string().into_bytes())),
180 );
181 };
182
183 // Step 3: Check for "Bearer " prefix (case-insensitive for the scheme)
184 let Some(bearer_value) = auth_str
185 .strip_prefix("Bearer ")
186 .or_else(|| auth_str.strip_prefix("bearer "))
187 else {
188 // Wrong scheme -> 401 Unauthorized
189 let body = serde_json::json!({
190 "detail": "Invalid authentication credentials"
191 });
192 return std::future::ready(
193 Response::with_status(StatusCode::UNAUTHORIZED)
194 .header("www-authenticate", b"Bearer".to_vec())
195 .header("content-type", b"application/json".to_vec())
196 .body(ResponseBody::Bytes(body.to_string().into_bytes())),
197 );
198 };
199
200 let bearer_value = bearer_value.trim();
201 if bearer_value.is_empty() {
202 // Empty token -> 401 Unauthorized
203 let body = serde_json::json!({
204 "detail": "Invalid authentication credentials"
205 });
206 return std::future::ready(
207 Response::with_status(StatusCode::UNAUTHORIZED)
208 .header("www-authenticate", b"Bearer".to_vec())
209 .header("content-type", b"application/json".to_vec())
210 .body(ResponseBody::Bytes(body.to_string().into_bytes())),
211 );
212 }
213
214 // Step 4: Validate the bearer value using constant-time comparison
215 if !bearer_value.secure_eq(DEMO_BEARER_VALUE) {
216 // Invalid token -> 403 Forbidden
217 let body = serde_json::json!({
218 "detail": "Invalid token"
219 });
220 return std::future::ready(
221 Response::with_status(StatusCode::FORBIDDEN)
222 .header("content-type", b"application/json".to_vec())
223 .body(ResponseBody::Bytes(body.to_string().into_bytes())),
224 );
225 }
226
227 // Token is valid - return protected data
228 let user_info = UserInfo {
229 username: "demo_user".to_string(),
230 message: "You have accessed a protected resource!".to_string(),
231 };
232
233 std::future::ready(
234 Response::ok()
235 .header("content-type", b"application/json".to_vec())
236 .body(ResponseBody::Bytes(json_bytes(&user_info))),
237 )
238}Sourcepub fn ok() -> Response
pub fn ok() -> Response
Create a 200 OK response.
Examples found in repository?
More examples
86fn public_handler(_ctx: &RequestContext, _req: &mut Request) -> std::future::Ready<Response> {
87 let body = serde_json::json!({
88 "message": "This is a public endpoint - no authentication required!"
89 });
90 std::future::ready(
91 Response::ok()
92 .header("content-type", b"application/json".to_vec())
93 .body(ResponseBody::Bytes(body.to_string().into_bytes())),
94 )
95}
96
97/// Handler for the login endpoint.
98///
99/// In a real application:
100/// 1. Validate username/password against a database
101/// 2. Generate a unique token (JWT or random)
102/// 3. Store the token with associated user info
103/// 4. Return the token to the client
104///
105/// For this demo, we accept any credentials and return a fixed token.
106fn login_handler(_ctx: &RequestContext, req: &mut Request) -> std::future::Ready<Response> {
107 // For this demo, we only check Content-Type and return a fixed token.
108 // Real applications should parse the body and validate credentials.
109
110 // Check Content-Type
111 let is_json = req
112 .headers()
113 .get("content-type")
114 .is_some_and(|ct| ct.starts_with(b"application/json"));
115
116 if !is_json {
117 let error = serde_json::json!({
118 "detail": "Content-Type must be application/json"
119 });
120 return std::future::ready(
121 Response::with_status(StatusCode::UNSUPPORTED_MEDIA_TYPE)
122 .header("content-type", b"application/json".to_vec())
123 .body(ResponseBody::Bytes(error.to_string().into_bytes())),
124 );
125 }
126
127 // For demo purposes, we don't validate credentials - just return the token
128 // In production:
129 // 1. Parse the request body as LoginRequest
130 // 2. Verify username/password against your database
131 // 3. Generate a unique, cryptographically secure token
132 // 4. Store token -> user_id mapping (with expiration)
133
134 let response = LoginResponse {
135 access_token: DEMO_BEARER_VALUE.to_string(),
136 token_type: "bearer",
137 };
138
139 std::future::ready(
140 Response::ok()
141 .header("content-type", b"application/json".to_vec())
142 .body(ResponseBody::Bytes(json_bytes(&response))),
143 )
144}
145
146/// Handler for protected endpoint - requires valid bearer token.
147///
148/// This handler manually extracts and validates the bearer token:
149/// 1. Gets the Authorization header
150/// 2. Verifies it uses the Bearer scheme
151/// 3. Validates the token against our secret using constant-time comparison
152///
153/// Returns appropriate error responses for each failure mode.
154fn protected_handler(_ctx: &RequestContext, req: &mut Request) -> std::future::Ready<Response> {
155 // Step 1: Get the Authorization header
156 let Some(auth_header) = req.headers().get("authorization") else {
157 // Missing header -> 401 Unauthorized
158 let body = serde_json::json!({
159 "detail": "Not authenticated"
160 });
161 return std::future::ready(
162 Response::with_status(StatusCode::UNAUTHORIZED)
163 .header("www-authenticate", b"Bearer".to_vec())
164 .header("content-type", b"application/json".to_vec())
165 .body(ResponseBody::Bytes(body.to_string().into_bytes())),
166 );
167 };
168
169 // Step 2: Parse the Authorization header
170 let Ok(auth_str) = std::str::from_utf8(auth_header) else {
171 // Invalid UTF-8 -> 401 Unauthorized
172 let body = serde_json::json!({
173 "detail": "Invalid authentication credentials"
174 });
175 return std::future::ready(
176 Response::with_status(StatusCode::UNAUTHORIZED)
177 .header("www-authenticate", b"Bearer".to_vec())
178 .header("content-type", b"application/json".to_vec())
179 .body(ResponseBody::Bytes(body.to_string().into_bytes())),
180 );
181 };
182
183 // Step 3: Check for "Bearer " prefix (case-insensitive for the scheme)
184 let Some(bearer_value) = auth_str
185 .strip_prefix("Bearer ")
186 .or_else(|| auth_str.strip_prefix("bearer "))
187 else {
188 // Wrong scheme -> 401 Unauthorized
189 let body = serde_json::json!({
190 "detail": "Invalid authentication credentials"
191 });
192 return std::future::ready(
193 Response::with_status(StatusCode::UNAUTHORIZED)
194 .header("www-authenticate", b"Bearer".to_vec())
195 .header("content-type", b"application/json".to_vec())
196 .body(ResponseBody::Bytes(body.to_string().into_bytes())),
197 );
198 };
199
200 let bearer_value = bearer_value.trim();
201 if bearer_value.is_empty() {
202 // Empty token -> 401 Unauthorized
203 let body = serde_json::json!({
204 "detail": "Invalid authentication credentials"
205 });
206 return std::future::ready(
207 Response::with_status(StatusCode::UNAUTHORIZED)
208 .header("www-authenticate", b"Bearer".to_vec())
209 .header("content-type", b"application/json".to_vec())
210 .body(ResponseBody::Bytes(body.to_string().into_bytes())),
211 );
212 }
213
214 // Step 4: Validate the bearer value using constant-time comparison
215 if !bearer_value.secure_eq(DEMO_BEARER_VALUE) {
216 // Invalid token -> 403 Forbidden
217 let body = serde_json::json!({
218 "detail": "Invalid token"
219 });
220 return std::future::ready(
221 Response::with_status(StatusCode::FORBIDDEN)
222 .header("content-type", b"application/json".to_vec())
223 .body(ResponseBody::Bytes(body.to_string().into_bytes())),
224 );
225 }
226
227 // Token is valid - return protected data
228 let user_info = UserInfo {
229 username: "demo_user".to_string(),
230 message: "You have accessed a protected resource!".to_string(),
231 };
232
233 std::future::ready(
234 Response::ok()
235 .header("content-type", b"application/json".to_vec())
236 .body(ResponseBody::Bytes(json_bytes(&user_info))),
237 )
238}Sourcepub fn no_content() -> Response
pub fn no_content() -> Response
Create a 204 No Content response.
Sourcepub fn internal_error() -> Response
pub fn internal_error() -> Response
Create a 500 Internal Server Error response.
Sourcepub fn partial_content() -> Response
pub fn partial_content() -> Response
Create a 206 Partial Content response.
Used for range requests. You should also set the Content-Range header.
§Example
use fastapi_core::{Response, ResponseBody};
let response = Response::partial_content()
.header("Content-Range", b"bytes 0-499/1000".to_vec())
.header("Accept-Ranges", b"bytes".to_vec())
.body(ResponseBody::Bytes(partial_data));Sourcepub fn range_not_satisfiable() -> Response
pub fn range_not_satisfiable() -> Response
Create a 416 Range Not Satisfiable response.
Used when a Range header specifies a range that cannot be satisfied.
You should also set the Content-Range header with the resource size.
§Example
use fastapi_core::Response;
let response = Response::range_not_satisfiable()
.header("Content-Range", b"bytes */1000".to_vec());Sourcepub fn not_modified() -> Response
pub fn not_modified() -> Response
Create a 304 Not Modified response.
Used for conditional requests where the resource has not changed. The response body is empty per HTTP spec.
Sourcepub fn precondition_failed() -> Response
pub fn precondition_failed() -> Response
Create a 412 Precondition Failed response.
Used when a conditional request’s precondition (e.g., If-Match) fails.
Sourcepub fn with_weak_etag(self, etag: impl Into<String>) -> Response
pub fn with_weak_etag(self, etag: impl Into<String>) -> Response
Set a weak ETag header on this response.
Automatically prefixes with W/ if not already present.
Sourcepub fn header(
self,
name: impl Into<String>,
value: impl Into<Vec<u8>>,
) -> Response
pub fn header( self, name: impl Into<String>, value: impl Into<Vec<u8>>, ) -> Response
Add a header.
§Security
Header names are validated to contain only valid token characters. Header values are sanitized to prevent CRLF injection attacks. Invalid characters in names will cause the header to be silently dropped.
Examples found in repository?
124fn json_error(status: StatusCode, detail: &str) -> Response {
125 let body = serde_json::json!({ "detail": detail });
126 Response::with_status(status)
127 .header("content-type", b"application/json".to_vec())
128 .body(ResponseBody::Bytes(body.to_string().into_bytes()))
129}
130
131fn json_response(status: StatusCode, value: &impl Serialize) -> Response {
132 match serde_json::to_string(value) {
133 Ok(text) => Response::with_status(status)
134 .header("content-type", b"application/json".to_vec())
135 .body(ResponseBody::Bytes(text.into_bytes())),
136 Err(err) => json_error(
137 StatusCode::INTERNAL_SERVER_ERROR,
138 &format!("JSON serialization failed: {err}"),
139 ),
140 }
141}More examples
86fn public_handler(_ctx: &RequestContext, _req: &mut Request) -> std::future::Ready<Response> {
87 let body = serde_json::json!({
88 "message": "This is a public endpoint - no authentication required!"
89 });
90 std::future::ready(
91 Response::ok()
92 .header("content-type", b"application/json".to_vec())
93 .body(ResponseBody::Bytes(body.to_string().into_bytes())),
94 )
95}
96
97/// Handler for the login endpoint.
98///
99/// In a real application:
100/// 1. Validate username/password against a database
101/// 2. Generate a unique token (JWT or random)
102/// 3. Store the token with associated user info
103/// 4. Return the token to the client
104///
105/// For this demo, we accept any credentials and return a fixed token.
106fn login_handler(_ctx: &RequestContext, req: &mut Request) -> std::future::Ready<Response> {
107 // For this demo, we only check Content-Type and return a fixed token.
108 // Real applications should parse the body and validate credentials.
109
110 // Check Content-Type
111 let is_json = req
112 .headers()
113 .get("content-type")
114 .is_some_and(|ct| ct.starts_with(b"application/json"));
115
116 if !is_json {
117 let error = serde_json::json!({
118 "detail": "Content-Type must be application/json"
119 });
120 return std::future::ready(
121 Response::with_status(StatusCode::UNSUPPORTED_MEDIA_TYPE)
122 .header("content-type", b"application/json".to_vec())
123 .body(ResponseBody::Bytes(error.to_string().into_bytes())),
124 );
125 }
126
127 // For demo purposes, we don't validate credentials - just return the token
128 // In production:
129 // 1. Parse the request body as LoginRequest
130 // 2. Verify username/password against your database
131 // 3. Generate a unique, cryptographically secure token
132 // 4. Store token -> user_id mapping (with expiration)
133
134 let response = LoginResponse {
135 access_token: DEMO_BEARER_VALUE.to_string(),
136 token_type: "bearer",
137 };
138
139 std::future::ready(
140 Response::ok()
141 .header("content-type", b"application/json".to_vec())
142 .body(ResponseBody::Bytes(json_bytes(&response))),
143 )
144}
145
146/// Handler for protected endpoint - requires valid bearer token.
147///
148/// This handler manually extracts and validates the bearer token:
149/// 1. Gets the Authorization header
150/// 2. Verifies it uses the Bearer scheme
151/// 3. Validates the token against our secret using constant-time comparison
152///
153/// Returns appropriate error responses for each failure mode.
154fn protected_handler(_ctx: &RequestContext, req: &mut Request) -> std::future::Ready<Response> {
155 // Step 1: Get the Authorization header
156 let Some(auth_header) = req.headers().get("authorization") else {
157 // Missing header -> 401 Unauthorized
158 let body = serde_json::json!({
159 "detail": "Not authenticated"
160 });
161 return std::future::ready(
162 Response::with_status(StatusCode::UNAUTHORIZED)
163 .header("www-authenticate", b"Bearer".to_vec())
164 .header("content-type", b"application/json".to_vec())
165 .body(ResponseBody::Bytes(body.to_string().into_bytes())),
166 );
167 };
168
169 // Step 2: Parse the Authorization header
170 let Ok(auth_str) = std::str::from_utf8(auth_header) else {
171 // Invalid UTF-8 -> 401 Unauthorized
172 let body = serde_json::json!({
173 "detail": "Invalid authentication credentials"
174 });
175 return std::future::ready(
176 Response::with_status(StatusCode::UNAUTHORIZED)
177 .header("www-authenticate", b"Bearer".to_vec())
178 .header("content-type", b"application/json".to_vec())
179 .body(ResponseBody::Bytes(body.to_string().into_bytes())),
180 );
181 };
182
183 // Step 3: Check for "Bearer " prefix (case-insensitive for the scheme)
184 let Some(bearer_value) = auth_str
185 .strip_prefix("Bearer ")
186 .or_else(|| auth_str.strip_prefix("bearer "))
187 else {
188 // Wrong scheme -> 401 Unauthorized
189 let body = serde_json::json!({
190 "detail": "Invalid authentication credentials"
191 });
192 return std::future::ready(
193 Response::with_status(StatusCode::UNAUTHORIZED)
194 .header("www-authenticate", b"Bearer".to_vec())
195 .header("content-type", b"application/json".to_vec())
196 .body(ResponseBody::Bytes(body.to_string().into_bytes())),
197 );
198 };
199
200 let bearer_value = bearer_value.trim();
201 if bearer_value.is_empty() {
202 // Empty token -> 401 Unauthorized
203 let body = serde_json::json!({
204 "detail": "Invalid authentication credentials"
205 });
206 return std::future::ready(
207 Response::with_status(StatusCode::UNAUTHORIZED)
208 .header("www-authenticate", b"Bearer".to_vec())
209 .header("content-type", b"application/json".to_vec())
210 .body(ResponseBody::Bytes(body.to_string().into_bytes())),
211 );
212 }
213
214 // Step 4: Validate the bearer value using constant-time comparison
215 if !bearer_value.secure_eq(DEMO_BEARER_VALUE) {
216 // Invalid token -> 403 Forbidden
217 let body = serde_json::json!({
218 "detail": "Invalid token"
219 });
220 return std::future::ready(
221 Response::with_status(StatusCode::FORBIDDEN)
222 .header("content-type", b"application/json".to_vec())
223 .body(ResponseBody::Bytes(body.to_string().into_bytes())),
224 );
225 }
226
227 // Token is valid - return protected data
228 let user_info = UserInfo {
229 username: "demo_user".to_string(),
230 message: "You have accessed a protected resource!".to_string(),
231 };
232
233 std::future::ready(
234 Response::ok()
235 .header("content-type", b"application/json".to_vec())
236 .body(ResponseBody::Bytes(json_bytes(&user_info))),
237 )
238}Sourcepub fn remove_header(self, name: &str) -> Response
pub fn remove_header(self, name: &str) -> Response
Remove all headers matching name (case-insensitive).
This is useful for middleware that needs to suppress or replace headers produced by handlers or other middleware.
Sourcepub fn body(self, body: ResponseBody) -> Response
pub fn body(self, body: ResponseBody) -> Response
Set the body.
Examples found in repository?
More examples
124fn json_error(status: StatusCode, detail: &str) -> Response {
125 let body = serde_json::json!({ "detail": detail });
126 Response::with_status(status)
127 .header("content-type", b"application/json".to_vec())
128 .body(ResponseBody::Bytes(body.to_string().into_bytes()))
129}
130
131fn json_response(status: StatusCode, value: &impl Serialize) -> Response {
132 match serde_json::to_string(value) {
133 Ok(text) => Response::with_status(status)
134 .header("content-type", b"application/json".to_vec())
135 .body(ResponseBody::Bytes(text.into_bytes())),
136 Err(err) => json_error(
137 StatusCode::INTERNAL_SERVER_ERROR,
138 &format!("JSON serialization failed: {err}"),
139 ),
140 }
141}86fn public_handler(_ctx: &RequestContext, _req: &mut Request) -> std::future::Ready<Response> {
87 let body = serde_json::json!({
88 "message": "This is a public endpoint - no authentication required!"
89 });
90 std::future::ready(
91 Response::ok()
92 .header("content-type", b"application/json".to_vec())
93 .body(ResponseBody::Bytes(body.to_string().into_bytes())),
94 )
95}
96
97/// Handler for the login endpoint.
98///
99/// In a real application:
100/// 1. Validate username/password against a database
101/// 2. Generate a unique token (JWT or random)
102/// 3. Store the token with associated user info
103/// 4. Return the token to the client
104///
105/// For this demo, we accept any credentials and return a fixed token.
106fn login_handler(_ctx: &RequestContext, req: &mut Request) -> std::future::Ready<Response> {
107 // For this demo, we only check Content-Type and return a fixed token.
108 // Real applications should parse the body and validate credentials.
109
110 // Check Content-Type
111 let is_json = req
112 .headers()
113 .get("content-type")
114 .is_some_and(|ct| ct.starts_with(b"application/json"));
115
116 if !is_json {
117 let error = serde_json::json!({
118 "detail": "Content-Type must be application/json"
119 });
120 return std::future::ready(
121 Response::with_status(StatusCode::UNSUPPORTED_MEDIA_TYPE)
122 .header("content-type", b"application/json".to_vec())
123 .body(ResponseBody::Bytes(error.to_string().into_bytes())),
124 );
125 }
126
127 // For demo purposes, we don't validate credentials - just return the token
128 // In production:
129 // 1. Parse the request body as LoginRequest
130 // 2. Verify username/password against your database
131 // 3. Generate a unique, cryptographically secure token
132 // 4. Store token -> user_id mapping (with expiration)
133
134 let response = LoginResponse {
135 access_token: DEMO_BEARER_VALUE.to_string(),
136 token_type: "bearer",
137 };
138
139 std::future::ready(
140 Response::ok()
141 .header("content-type", b"application/json".to_vec())
142 .body(ResponseBody::Bytes(json_bytes(&response))),
143 )
144}
145
146/// Handler for protected endpoint - requires valid bearer token.
147///
148/// This handler manually extracts and validates the bearer token:
149/// 1. Gets the Authorization header
150/// 2. Verifies it uses the Bearer scheme
151/// 3. Validates the token against our secret using constant-time comparison
152///
153/// Returns appropriate error responses for each failure mode.
154fn protected_handler(_ctx: &RequestContext, req: &mut Request) -> std::future::Ready<Response> {
155 // Step 1: Get the Authorization header
156 let Some(auth_header) = req.headers().get("authorization") else {
157 // Missing header -> 401 Unauthorized
158 let body = serde_json::json!({
159 "detail": "Not authenticated"
160 });
161 return std::future::ready(
162 Response::with_status(StatusCode::UNAUTHORIZED)
163 .header("www-authenticate", b"Bearer".to_vec())
164 .header("content-type", b"application/json".to_vec())
165 .body(ResponseBody::Bytes(body.to_string().into_bytes())),
166 );
167 };
168
169 // Step 2: Parse the Authorization header
170 let Ok(auth_str) = std::str::from_utf8(auth_header) else {
171 // Invalid UTF-8 -> 401 Unauthorized
172 let body = serde_json::json!({
173 "detail": "Invalid authentication credentials"
174 });
175 return std::future::ready(
176 Response::with_status(StatusCode::UNAUTHORIZED)
177 .header("www-authenticate", b"Bearer".to_vec())
178 .header("content-type", b"application/json".to_vec())
179 .body(ResponseBody::Bytes(body.to_string().into_bytes())),
180 );
181 };
182
183 // Step 3: Check for "Bearer " prefix (case-insensitive for the scheme)
184 let Some(bearer_value) = auth_str
185 .strip_prefix("Bearer ")
186 .or_else(|| auth_str.strip_prefix("bearer "))
187 else {
188 // Wrong scheme -> 401 Unauthorized
189 let body = serde_json::json!({
190 "detail": "Invalid authentication credentials"
191 });
192 return std::future::ready(
193 Response::with_status(StatusCode::UNAUTHORIZED)
194 .header("www-authenticate", b"Bearer".to_vec())
195 .header("content-type", b"application/json".to_vec())
196 .body(ResponseBody::Bytes(body.to_string().into_bytes())),
197 );
198 };
199
200 let bearer_value = bearer_value.trim();
201 if bearer_value.is_empty() {
202 // Empty token -> 401 Unauthorized
203 let body = serde_json::json!({
204 "detail": "Invalid authentication credentials"
205 });
206 return std::future::ready(
207 Response::with_status(StatusCode::UNAUTHORIZED)
208 .header("www-authenticate", b"Bearer".to_vec())
209 .header("content-type", b"application/json".to_vec())
210 .body(ResponseBody::Bytes(body.to_string().into_bytes())),
211 );
212 }
213
214 // Step 4: Validate the bearer value using constant-time comparison
215 if !bearer_value.secure_eq(DEMO_BEARER_VALUE) {
216 // Invalid token -> 403 Forbidden
217 let body = serde_json::json!({
218 "detail": "Invalid token"
219 });
220 return std::future::ready(
221 Response::with_status(StatusCode::FORBIDDEN)
222 .header("content-type", b"application/json".to_vec())
223 .body(ResponseBody::Bytes(body.to_string().into_bytes())),
224 );
225 }
226
227 // Token is valid - return protected data
228 let user_info = UserInfo {
229 username: "demo_user".to_string(),
230 message: "You have accessed a protected resource!".to_string(),
231 };
232
233 std::future::ready(
234 Response::ok()
235 .header("content-type", b"application/json".to_vec())
236 .body(ResponseBody::Bytes(json_bytes(&user_info))),
237 )
238}Set a cookie on the response.
Adds a Set-Cookie header with the serialized cookie value.
Multiple cookies can be set by calling this method multiple times.
§Example
use fastapi_core::{Response, SameSite, SetCookie};
let response = Response::ok()
.set_cookie(SetCookie::new("session", "abc123").http_only(true))
.set_cookie(SetCookie::new("prefs", "dark").same_site(SameSite::Lax));Delete a cookie by setting it to expire immediately.
This sets the cookie with an empty value and Max-Age=0, which tells
the browser to remove the cookie.
§Example
use fastapi_core::Response;
let response = Response::ok()
.delete_cookie("session");Sourcepub fn status(&self) -> StatusCode
pub fn status(&self) -> StatusCode
Get the status code.
Sourcepub fn body_ref(&self) -> &ResponseBody
pub fn body_ref(&self) -> &ResponseBody
Get the body.
Sourcepub fn into_parts(self) -> (StatusCode, Vec<(String, Vec<u8>)>, ResponseBody)
pub fn into_parts(self) -> (StatusCode, Vec<(String, Vec<u8>)>, ResponseBody)
Decompose this response into its parts.
Sourcepub fn rebuild_with_headers(self, headers: Vec<(String, Vec<u8>)>) -> Response
pub fn rebuild_with_headers(self, headers: Vec<(String, Vec<u8>)>) -> Response
Rebuilds this response with the given headers, preserving status and body.
This is useful for middleware that needs to modify the response but preserve original headers.
§Example
let (status, headers, body) = response.into_parts();
// ... modify headers ...
let new_response = Response::with_status(status)
.body(body)
.rebuild_with_headers(headers);