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?
119fn json_error(status: StatusCode, detail: &str) -> Response {
120 let body = serde_json::json!({ "detail": detail });
121 Response::with_status(status)
122 .header("content-type", b"application/json".to_vec())
123 .body(ResponseBody::Bytes(body.to_string().into_bytes()))
124}
125
126fn json_response(status: StatusCode, value: &impl Serialize) -> Response {
127 Response::with_status(status)
128 .header("content-type", b"application/json".to_vec())
129 .body(ResponseBody::Bytes(
130 serde_json::to_string(value).unwrap().into_bytes(),
131 ))
132}
133
134fn validate_input(input: &UserInput) -> Option<Response> {
135 if input.name.trim().is_empty() {
136 return Some(json_error(
137 StatusCode::BAD_REQUEST,
138 "name must not be empty",
139 ));
140 }
141 if !input.email.contains('@') {
142 return Some(json_error(
143 StatusCode::BAD_REQUEST,
144 "email must contain '@'",
145 ));
146 }
147 None
148}
149
150// ============================================================================
151// Handlers
152// ============================================================================
153
154fn create_user(_ctx: &RequestContext, req: &mut Request) -> std::future::Ready<Response> {
155 let input = match parse_json_body::<UserInput>(req) {
156 Ok(v) => v,
157 Err(r) => return std::future::ready(r),
158 };
159 if let Some(r) = validate_input(&input) {
160 return std::future::ready(r);
161 }
162
163 let user = with_db(|db| {
164 let id = db.next_id;
165 db.next_id += 1;
166 let user = User {
167 id,
168 name: input.name,
169 email: input.email,
170 };
171 db.users.insert(id, user.clone());
172 user
173 });
174
175 std::future::ready(json_response(StatusCode::CREATED, &user))
176}
177
178fn list_users(_ctx: &RequestContext, _req: &mut Request) -> std::future::Ready<Response> {
179 let users = with_db(|db| {
180 let mut v: Vec<User> = db.users.values().cloned().collect();
181 v.sort_by_key(|u| u.id);
182 v
183 });
184 std::future::ready(json_response(StatusCode::OK, &users))
185}
186
187fn get_user(_ctx: &RequestContext, req: &mut Request) -> std::future::Ready<Response> {
188 let id = match extract_user_id(req) {
189 Ok(id) => id,
190 Err(r) => return std::future::ready(r),
191 };
192 let result = with_db(|db| db.users.get(&id).cloned());
193 match result {
194 Some(user) => std::future::ready(json_response(StatusCode::OK, &user)),
195 None => std::future::ready(json_error(
196 StatusCode::NOT_FOUND,
197 &format!("User {id} not found"),
198 )),
199 }
200}
201
202fn update_user(_ctx: &RequestContext, req: &mut Request) -> std::future::Ready<Response> {
203 let id = match extract_user_id(req) {
204 Ok(id) => id,
205 Err(r) => return std::future::ready(r),
206 };
207 let input = match parse_json_body::<UserInput>(req) {
208 Ok(v) => v,
209 Err(r) => return std::future::ready(r),
210 };
211 if let Some(r) = validate_input(&input) {
212 return std::future::ready(r);
213 }
214
215 let result = with_db(|db| {
216 db.users.get_mut(&id).map(|user| {
217 user.name = input.name;
218 user.email = input.email;
219 user.clone()
220 })
221 });
222 match result {
223 Some(user) => std::future::ready(json_response(StatusCode::OK, &user)),
224 None => std::future::ready(json_error(
225 StatusCode::NOT_FOUND,
226 &format!("User {id} not found"),
227 )),
228 }
229}
230
231fn delete_user(_ctx: &RequestContext, req: &mut Request) -> std::future::Ready<Response> {
232 let id = match extract_user_id(req) {
233 Ok(id) => id,
234 Err(r) => return std::future::ready(r),
235 };
236 let removed = with_db(|db| db.users.remove(&id).is_some());
237 if removed {
238 std::future::ready(Response::with_status(StatusCode::NO_CONTENT))
239 } else {
240 std::future::ready(json_error(
241 StatusCode::NOT_FOUND,
242 &format!("User {id} not found"),
243 ))
244 }
245}More examples
107fn login_handler(_ctx: &RequestContext, req: &mut Request) -> std::future::Ready<Response> {
108 // In a real app, we would parse the JSON body and validate credentials.
109 // For this demo, we just check that it's a POST with some body.
110
111 // Check Content-Type
112 let is_json = req
113 .headers()
114 .get("content-type")
115 .is_some_and(|ct| ct.starts_with(b"application/json"));
116
117 if !is_json {
118 let error = serde_json::json!({
119 "detail": "Content-Type must be application/json"
120 });
121 return std::future::ready(
122 Response::with_status(StatusCode::UNSUPPORTED_MEDIA_TYPE)
123 .header("content-type", b"application/json".to_vec())
124 .body(ResponseBody::Bytes(error.to_string().into_bytes())),
125 );
126 }
127
128 // For demo purposes, we don't validate credentials - just return the token
129 // In production, you would:
130 // 1. Parse the request body as LoginRequest
131 // 2. Verify username/password against your database
132 // 3. Generate a unique, cryptographically secure token
133 // 4. Store token -> user_id mapping (with expiration)
134
135 let response = LoginResponse {
136 access_token: SECRET_TOKEN.to_string(),
137 token_type: "bearer",
138 };
139
140 std::future::ready(
141 Response::ok()
142 .header("content-type", b"application/json".to_vec())
143 .body(ResponseBody::Bytes(
144 serde_json::to_string(&response).unwrap().into_bytes(),
145 )),
146 )
147}
148
149/// Handler for protected endpoint - requires valid bearer token.
150///
151/// This handler manually extracts and validates the bearer token:
152/// 1. Gets the Authorization header
153/// 2. Verifies it uses the Bearer scheme
154/// 3. Validates the token against our secret using constant-time comparison
155///
156/// Returns appropriate error responses for each failure mode.
157fn protected_handler(_ctx: &RequestContext, req: &mut Request) -> std::future::Ready<Response> {
158 // Step 1: Get the Authorization header
159 let Some(auth_header) = req.headers().get("authorization") else {
160 // Missing header -> 401 Unauthorized
161 let body = serde_json::json!({
162 "detail": "Not authenticated"
163 });
164 return std::future::ready(
165 Response::with_status(StatusCode::UNAUTHORIZED)
166 .header("www-authenticate", b"Bearer".to_vec())
167 .header("content-type", b"application/json".to_vec())
168 .body(ResponseBody::Bytes(body.to_string().into_bytes())),
169 );
170 };
171
172 // Step 2: Parse the Authorization header
173 let Ok(auth_str) = std::str::from_utf8(auth_header) else {
174 // Invalid UTF-8 -> 401 Unauthorized
175 let body = serde_json::json!({
176 "detail": "Invalid authentication credentials"
177 });
178 return std::future::ready(
179 Response::with_status(StatusCode::UNAUTHORIZED)
180 .header("www-authenticate", b"Bearer".to_vec())
181 .header("content-type", b"application/json".to_vec())
182 .body(ResponseBody::Bytes(body.to_string().into_bytes())),
183 );
184 };
185
186 // Step 3: Check for "Bearer " prefix (case-insensitive for the scheme)
187 let Some(token) = auth_str
188 .strip_prefix("Bearer ")
189 .or_else(|| auth_str.strip_prefix("bearer "))
190 else {
191 // Wrong scheme -> 401 Unauthorized
192 let body = serde_json::json!({
193 "detail": "Invalid authentication credentials"
194 });
195 return std::future::ready(
196 Response::with_status(StatusCode::UNAUTHORIZED)
197 .header("www-authenticate", b"Bearer".to_vec())
198 .header("content-type", b"application/json".to_vec())
199 .body(ResponseBody::Bytes(body.to_string().into_bytes())),
200 );
201 };
202
203 let token = token.trim();
204 if token.is_empty() {
205 // Empty token -> 401 Unauthorized
206 let body = serde_json::json!({
207 "detail": "Invalid authentication credentials"
208 });
209 return std::future::ready(
210 Response::with_status(StatusCode::UNAUTHORIZED)
211 .header("www-authenticate", b"Bearer".to_vec())
212 .header("content-type", b"application/json".to_vec())
213 .body(ResponseBody::Bytes(body.to_string().into_bytes())),
214 );
215 }
216
217 // Step 4: Validate the token using constant-time comparison
218 // Create a BearerToken for secure comparison
219 let bearer_token = BearerToken::new(token);
220 if !bearer_token.secure_eq(SECRET_TOKEN) {
221 // Invalid token -> 403 Forbidden
222 let body = serde_json::json!({
223 "detail": "Invalid token"
224 });
225 return std::future::ready(
226 Response::with_status(StatusCode::FORBIDDEN)
227 .header("content-type", b"application/json".to_vec())
228 .body(ResponseBody::Bytes(body.to_string().into_bytes())),
229 );
230 }
231
232 // Token is valid - return protected data
233 let user_info = UserInfo {
234 username: "demo_user".to_string(),
235 message: "You have accessed a protected resource!".to_string(),
236 };
237
238 std::future::ready(
239 Response::ok()
240 .header("content-type", b"application/json".to_vec())
241 .body(ResponseBody::Bytes(
242 serde_json::to_string(&user_info).unwrap().into_bytes(),
243 )),
244 )
245}Sourcepub fn ok() -> Response
pub fn ok() -> Response
Create a 200 OK response.
Examples found in repository?
More examples
87fn public_handler(_ctx: &RequestContext, _req: &mut Request) -> std::future::Ready<Response> {
88 let body = serde_json::json!({
89 "message": "This is a public endpoint - no authentication required!"
90 });
91 std::future::ready(
92 Response::ok()
93 .header("content-type", b"application/json".to_vec())
94 .body(ResponseBody::Bytes(body.to_string().into_bytes())),
95 )
96}
97
98/// Handler for the login endpoint.
99///
100/// In a real application, this would:
101/// 1. Validate username/password against a database
102/// 2. Generate a unique token (JWT or random)
103/// 3. Store the token with associated user info
104/// 4. Return the token to the client
105///
106/// For this demo, we accept any credentials and return a fixed token.
107fn login_handler(_ctx: &RequestContext, req: &mut Request) -> std::future::Ready<Response> {
108 // In a real app, we would parse the JSON body and validate credentials.
109 // For this demo, we just check that it's a POST with some body.
110
111 // Check Content-Type
112 let is_json = req
113 .headers()
114 .get("content-type")
115 .is_some_and(|ct| ct.starts_with(b"application/json"));
116
117 if !is_json {
118 let error = serde_json::json!({
119 "detail": "Content-Type must be application/json"
120 });
121 return std::future::ready(
122 Response::with_status(StatusCode::UNSUPPORTED_MEDIA_TYPE)
123 .header("content-type", b"application/json".to_vec())
124 .body(ResponseBody::Bytes(error.to_string().into_bytes())),
125 );
126 }
127
128 // For demo purposes, we don't validate credentials - just return the token
129 // In production, you would:
130 // 1. Parse the request body as LoginRequest
131 // 2. Verify username/password against your database
132 // 3. Generate a unique, cryptographically secure token
133 // 4. Store token -> user_id mapping (with expiration)
134
135 let response = LoginResponse {
136 access_token: SECRET_TOKEN.to_string(),
137 token_type: "bearer",
138 };
139
140 std::future::ready(
141 Response::ok()
142 .header("content-type", b"application/json".to_vec())
143 .body(ResponseBody::Bytes(
144 serde_json::to_string(&response).unwrap().into_bytes(),
145 )),
146 )
147}
148
149/// Handler for protected endpoint - requires valid bearer token.
150///
151/// This handler manually extracts and validates the bearer token:
152/// 1. Gets the Authorization header
153/// 2. Verifies it uses the Bearer scheme
154/// 3. Validates the token against our secret using constant-time comparison
155///
156/// Returns appropriate error responses for each failure mode.
157fn protected_handler(_ctx: &RequestContext, req: &mut Request) -> std::future::Ready<Response> {
158 // Step 1: Get the Authorization header
159 let Some(auth_header) = req.headers().get("authorization") else {
160 // Missing header -> 401 Unauthorized
161 let body = serde_json::json!({
162 "detail": "Not authenticated"
163 });
164 return std::future::ready(
165 Response::with_status(StatusCode::UNAUTHORIZED)
166 .header("www-authenticate", b"Bearer".to_vec())
167 .header("content-type", b"application/json".to_vec())
168 .body(ResponseBody::Bytes(body.to_string().into_bytes())),
169 );
170 };
171
172 // Step 2: Parse the Authorization header
173 let Ok(auth_str) = std::str::from_utf8(auth_header) else {
174 // Invalid UTF-8 -> 401 Unauthorized
175 let body = serde_json::json!({
176 "detail": "Invalid authentication credentials"
177 });
178 return std::future::ready(
179 Response::with_status(StatusCode::UNAUTHORIZED)
180 .header("www-authenticate", b"Bearer".to_vec())
181 .header("content-type", b"application/json".to_vec())
182 .body(ResponseBody::Bytes(body.to_string().into_bytes())),
183 );
184 };
185
186 // Step 3: Check for "Bearer " prefix (case-insensitive for the scheme)
187 let Some(token) = auth_str
188 .strip_prefix("Bearer ")
189 .or_else(|| auth_str.strip_prefix("bearer "))
190 else {
191 // Wrong scheme -> 401 Unauthorized
192 let body = serde_json::json!({
193 "detail": "Invalid authentication credentials"
194 });
195 return std::future::ready(
196 Response::with_status(StatusCode::UNAUTHORIZED)
197 .header("www-authenticate", b"Bearer".to_vec())
198 .header("content-type", b"application/json".to_vec())
199 .body(ResponseBody::Bytes(body.to_string().into_bytes())),
200 );
201 };
202
203 let token = token.trim();
204 if token.is_empty() {
205 // Empty token -> 401 Unauthorized
206 let body = serde_json::json!({
207 "detail": "Invalid authentication credentials"
208 });
209 return std::future::ready(
210 Response::with_status(StatusCode::UNAUTHORIZED)
211 .header("www-authenticate", b"Bearer".to_vec())
212 .header("content-type", b"application/json".to_vec())
213 .body(ResponseBody::Bytes(body.to_string().into_bytes())),
214 );
215 }
216
217 // Step 4: Validate the token using constant-time comparison
218 // Create a BearerToken for secure comparison
219 let bearer_token = BearerToken::new(token);
220 if !bearer_token.secure_eq(SECRET_TOKEN) {
221 // Invalid token -> 403 Forbidden
222 let body = serde_json::json!({
223 "detail": "Invalid token"
224 });
225 return std::future::ready(
226 Response::with_status(StatusCode::FORBIDDEN)
227 .header("content-type", b"application/json".to_vec())
228 .body(ResponseBody::Bytes(body.to_string().into_bytes())),
229 );
230 }
231
232 // Token is valid - return protected data
233 let user_info = UserInfo {
234 username: "demo_user".to_string(),
235 message: "You have accessed a protected resource!".to_string(),
236 };
237
238 std::future::ready(
239 Response::ok()
240 .header("content-type", b"application/json".to_vec())
241 .body(ResponseBody::Bytes(
242 serde_json::to_string(&user_info).unwrap().into_bytes(),
243 )),
244 )
245}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?
119fn json_error(status: StatusCode, detail: &str) -> Response {
120 let body = serde_json::json!({ "detail": detail });
121 Response::with_status(status)
122 .header("content-type", b"application/json".to_vec())
123 .body(ResponseBody::Bytes(body.to_string().into_bytes()))
124}
125
126fn json_response(status: StatusCode, value: &impl Serialize) -> Response {
127 Response::with_status(status)
128 .header("content-type", b"application/json".to_vec())
129 .body(ResponseBody::Bytes(
130 serde_json::to_string(value).unwrap().into_bytes(),
131 ))
132}More examples
87fn public_handler(_ctx: &RequestContext, _req: &mut Request) -> std::future::Ready<Response> {
88 let body = serde_json::json!({
89 "message": "This is a public endpoint - no authentication required!"
90 });
91 std::future::ready(
92 Response::ok()
93 .header("content-type", b"application/json".to_vec())
94 .body(ResponseBody::Bytes(body.to_string().into_bytes())),
95 )
96}
97
98/// Handler for the login endpoint.
99///
100/// In a real application, this would:
101/// 1. Validate username/password against a database
102/// 2. Generate a unique token (JWT or random)
103/// 3. Store the token with associated user info
104/// 4. Return the token to the client
105///
106/// For this demo, we accept any credentials and return a fixed token.
107fn login_handler(_ctx: &RequestContext, req: &mut Request) -> std::future::Ready<Response> {
108 // In a real app, we would parse the JSON body and validate credentials.
109 // For this demo, we just check that it's a POST with some body.
110
111 // Check Content-Type
112 let is_json = req
113 .headers()
114 .get("content-type")
115 .is_some_and(|ct| ct.starts_with(b"application/json"));
116
117 if !is_json {
118 let error = serde_json::json!({
119 "detail": "Content-Type must be application/json"
120 });
121 return std::future::ready(
122 Response::with_status(StatusCode::UNSUPPORTED_MEDIA_TYPE)
123 .header("content-type", b"application/json".to_vec())
124 .body(ResponseBody::Bytes(error.to_string().into_bytes())),
125 );
126 }
127
128 // For demo purposes, we don't validate credentials - just return the token
129 // In production, you would:
130 // 1. Parse the request body as LoginRequest
131 // 2. Verify username/password against your database
132 // 3. Generate a unique, cryptographically secure token
133 // 4. Store token -> user_id mapping (with expiration)
134
135 let response = LoginResponse {
136 access_token: SECRET_TOKEN.to_string(),
137 token_type: "bearer",
138 };
139
140 std::future::ready(
141 Response::ok()
142 .header("content-type", b"application/json".to_vec())
143 .body(ResponseBody::Bytes(
144 serde_json::to_string(&response).unwrap().into_bytes(),
145 )),
146 )
147}
148
149/// Handler for protected endpoint - requires valid bearer token.
150///
151/// This handler manually extracts and validates the bearer token:
152/// 1. Gets the Authorization header
153/// 2. Verifies it uses the Bearer scheme
154/// 3. Validates the token against our secret using constant-time comparison
155///
156/// Returns appropriate error responses for each failure mode.
157fn protected_handler(_ctx: &RequestContext, req: &mut Request) -> std::future::Ready<Response> {
158 // Step 1: Get the Authorization header
159 let Some(auth_header) = req.headers().get("authorization") else {
160 // Missing header -> 401 Unauthorized
161 let body = serde_json::json!({
162 "detail": "Not authenticated"
163 });
164 return std::future::ready(
165 Response::with_status(StatusCode::UNAUTHORIZED)
166 .header("www-authenticate", b"Bearer".to_vec())
167 .header("content-type", b"application/json".to_vec())
168 .body(ResponseBody::Bytes(body.to_string().into_bytes())),
169 );
170 };
171
172 // Step 2: Parse the Authorization header
173 let Ok(auth_str) = std::str::from_utf8(auth_header) else {
174 // Invalid UTF-8 -> 401 Unauthorized
175 let body = serde_json::json!({
176 "detail": "Invalid authentication credentials"
177 });
178 return std::future::ready(
179 Response::with_status(StatusCode::UNAUTHORIZED)
180 .header("www-authenticate", b"Bearer".to_vec())
181 .header("content-type", b"application/json".to_vec())
182 .body(ResponseBody::Bytes(body.to_string().into_bytes())),
183 );
184 };
185
186 // Step 3: Check for "Bearer " prefix (case-insensitive for the scheme)
187 let Some(token) = auth_str
188 .strip_prefix("Bearer ")
189 .or_else(|| auth_str.strip_prefix("bearer "))
190 else {
191 // Wrong scheme -> 401 Unauthorized
192 let body = serde_json::json!({
193 "detail": "Invalid authentication credentials"
194 });
195 return std::future::ready(
196 Response::with_status(StatusCode::UNAUTHORIZED)
197 .header("www-authenticate", b"Bearer".to_vec())
198 .header("content-type", b"application/json".to_vec())
199 .body(ResponseBody::Bytes(body.to_string().into_bytes())),
200 );
201 };
202
203 let token = token.trim();
204 if token.is_empty() {
205 // Empty token -> 401 Unauthorized
206 let body = serde_json::json!({
207 "detail": "Invalid authentication credentials"
208 });
209 return std::future::ready(
210 Response::with_status(StatusCode::UNAUTHORIZED)
211 .header("www-authenticate", b"Bearer".to_vec())
212 .header("content-type", b"application/json".to_vec())
213 .body(ResponseBody::Bytes(body.to_string().into_bytes())),
214 );
215 }
216
217 // Step 4: Validate the token using constant-time comparison
218 // Create a BearerToken for secure comparison
219 let bearer_token = BearerToken::new(token);
220 if !bearer_token.secure_eq(SECRET_TOKEN) {
221 // Invalid token -> 403 Forbidden
222 let body = serde_json::json!({
223 "detail": "Invalid token"
224 });
225 return std::future::ready(
226 Response::with_status(StatusCode::FORBIDDEN)
227 .header("content-type", b"application/json".to_vec())
228 .body(ResponseBody::Bytes(body.to_string().into_bytes())),
229 );
230 }
231
232 // Token is valid - return protected data
233 let user_info = UserInfo {
234 username: "demo_user".to_string(),
235 message: "You have accessed a protected resource!".to_string(),
236 };
237
238 std::future::ready(
239 Response::ok()
240 .header("content-type", b"application/json".to_vec())
241 .body(ResponseBody::Bytes(
242 serde_json::to_string(&user_info).unwrap().into_bytes(),
243 )),
244 )
245}Sourcepub fn body(self, body: ResponseBody) -> Response
pub fn body(self, body: ResponseBody) -> Response
Set the body.
Examples found in repository?
More examples
119fn json_error(status: StatusCode, detail: &str) -> Response {
120 let body = serde_json::json!({ "detail": detail });
121 Response::with_status(status)
122 .header("content-type", b"application/json".to_vec())
123 .body(ResponseBody::Bytes(body.to_string().into_bytes()))
124}
125
126fn json_response(status: StatusCode, value: &impl Serialize) -> Response {
127 Response::with_status(status)
128 .header("content-type", b"application/json".to_vec())
129 .body(ResponseBody::Bytes(
130 serde_json::to_string(value).unwrap().into_bytes(),
131 ))
132}87fn public_handler(_ctx: &RequestContext, _req: &mut Request) -> std::future::Ready<Response> {
88 let body = serde_json::json!({
89 "message": "This is a public endpoint - no authentication required!"
90 });
91 std::future::ready(
92 Response::ok()
93 .header("content-type", b"application/json".to_vec())
94 .body(ResponseBody::Bytes(body.to_string().into_bytes())),
95 )
96}
97
98/// Handler for the login endpoint.
99///
100/// In a real application, this would:
101/// 1. Validate username/password against a database
102/// 2. Generate a unique token (JWT or random)
103/// 3. Store the token with associated user info
104/// 4. Return the token to the client
105///
106/// For this demo, we accept any credentials and return a fixed token.
107fn login_handler(_ctx: &RequestContext, req: &mut Request) -> std::future::Ready<Response> {
108 // In a real app, we would parse the JSON body and validate credentials.
109 // For this demo, we just check that it's a POST with some body.
110
111 // Check Content-Type
112 let is_json = req
113 .headers()
114 .get("content-type")
115 .is_some_and(|ct| ct.starts_with(b"application/json"));
116
117 if !is_json {
118 let error = serde_json::json!({
119 "detail": "Content-Type must be application/json"
120 });
121 return std::future::ready(
122 Response::with_status(StatusCode::UNSUPPORTED_MEDIA_TYPE)
123 .header("content-type", b"application/json".to_vec())
124 .body(ResponseBody::Bytes(error.to_string().into_bytes())),
125 );
126 }
127
128 // For demo purposes, we don't validate credentials - just return the token
129 // In production, you would:
130 // 1. Parse the request body as LoginRequest
131 // 2. Verify username/password against your database
132 // 3. Generate a unique, cryptographically secure token
133 // 4. Store token -> user_id mapping (with expiration)
134
135 let response = LoginResponse {
136 access_token: SECRET_TOKEN.to_string(),
137 token_type: "bearer",
138 };
139
140 std::future::ready(
141 Response::ok()
142 .header("content-type", b"application/json".to_vec())
143 .body(ResponseBody::Bytes(
144 serde_json::to_string(&response).unwrap().into_bytes(),
145 )),
146 )
147}
148
149/// Handler for protected endpoint - requires valid bearer token.
150///
151/// This handler manually extracts and validates the bearer token:
152/// 1. Gets the Authorization header
153/// 2. Verifies it uses the Bearer scheme
154/// 3. Validates the token against our secret using constant-time comparison
155///
156/// Returns appropriate error responses for each failure mode.
157fn protected_handler(_ctx: &RequestContext, req: &mut Request) -> std::future::Ready<Response> {
158 // Step 1: Get the Authorization header
159 let Some(auth_header) = req.headers().get("authorization") else {
160 // Missing header -> 401 Unauthorized
161 let body = serde_json::json!({
162 "detail": "Not authenticated"
163 });
164 return std::future::ready(
165 Response::with_status(StatusCode::UNAUTHORIZED)
166 .header("www-authenticate", b"Bearer".to_vec())
167 .header("content-type", b"application/json".to_vec())
168 .body(ResponseBody::Bytes(body.to_string().into_bytes())),
169 );
170 };
171
172 // Step 2: Parse the Authorization header
173 let Ok(auth_str) = std::str::from_utf8(auth_header) else {
174 // Invalid UTF-8 -> 401 Unauthorized
175 let body = serde_json::json!({
176 "detail": "Invalid authentication credentials"
177 });
178 return std::future::ready(
179 Response::with_status(StatusCode::UNAUTHORIZED)
180 .header("www-authenticate", b"Bearer".to_vec())
181 .header("content-type", b"application/json".to_vec())
182 .body(ResponseBody::Bytes(body.to_string().into_bytes())),
183 );
184 };
185
186 // Step 3: Check for "Bearer " prefix (case-insensitive for the scheme)
187 let Some(token) = auth_str
188 .strip_prefix("Bearer ")
189 .or_else(|| auth_str.strip_prefix("bearer "))
190 else {
191 // Wrong scheme -> 401 Unauthorized
192 let body = serde_json::json!({
193 "detail": "Invalid authentication credentials"
194 });
195 return std::future::ready(
196 Response::with_status(StatusCode::UNAUTHORIZED)
197 .header("www-authenticate", b"Bearer".to_vec())
198 .header("content-type", b"application/json".to_vec())
199 .body(ResponseBody::Bytes(body.to_string().into_bytes())),
200 );
201 };
202
203 let token = token.trim();
204 if token.is_empty() {
205 // Empty token -> 401 Unauthorized
206 let body = serde_json::json!({
207 "detail": "Invalid authentication credentials"
208 });
209 return std::future::ready(
210 Response::with_status(StatusCode::UNAUTHORIZED)
211 .header("www-authenticate", b"Bearer".to_vec())
212 .header("content-type", b"application/json".to_vec())
213 .body(ResponseBody::Bytes(body.to_string().into_bytes())),
214 );
215 }
216
217 // Step 4: Validate the token using constant-time comparison
218 // Create a BearerToken for secure comparison
219 let bearer_token = BearerToken::new(token);
220 if !bearer_token.secure_eq(SECRET_TOKEN) {
221 // Invalid token -> 403 Forbidden
222 let body = serde_json::json!({
223 "detail": "Invalid token"
224 });
225 return std::future::ready(
226 Response::with_status(StatusCode::FORBIDDEN)
227 .header("content-type", b"application/json".to_vec())
228 .body(ResponseBody::Bytes(body.to_string().into_bytes())),
229 );
230 }
231
232 // Token is valid - return protected data
233 let user_info = UserInfo {
234 username: "demo_user".to_string(),
235 message: "You have accessed a protected resource!".to_string(),
236 };
237
238 std::future::ready(
239 Response::ok()
240 .header("content-type", b"application/json".to_vec())
241 .body(ResponseBody::Bytes(
242 serde_json::to_string(&user_info).unwrap().into_bytes(),
243 )),
244 )
245}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, Cookie, SameSite};
let response = Response::ok()
.set_cookie(Cookie::new("session", "abc123").http_only(true))
.set_cookie(Cookie::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);