Skip to content

Commit cc1538b

Browse files
committed
feat: add cookie manipulation endpoints
- Add GET /cookies endpoint to view all cookies - Add GET /cookies/set/{name}/{value} endpoint to set cookies - Add GET /cookies/delete/{name} endpoint to delete cookies - Include comprehensive test coverage for all endpoints
1 parent 9c62862 commit cc1538b

File tree

2 files changed

+155
-0
lines changed

2 files changed

+155
-0
lines changed

src/cookies.rs

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
use fastly::http::StatusCode;
2+
use fastly::{Error, mime, Request, Response};
3+
use serde_json::{json, to_string_pretty};
4+
5+
#[utoipa::path(
6+
get,
7+
path = "/cookies",
8+
tag = "Cookies",
9+
responses(
10+
(status = 200, description = "Returns all cookies.", content_type = "application/json"),
11+
)
12+
)]
13+
/// Returns cookie data.
14+
pub fn get_cookies(req: &Request) -> Result<Response, Error> {
15+
let cookies: Vec<(String, String)> = req.get_header_str("cookie")
16+
.map(|cookie_str| {
17+
cookie_str.split(';')
18+
.filter_map(|cookie| {
19+
let mut parts = cookie.trim().splitn(2, '=');
20+
let name = parts.next()?.trim().to_string();
21+
let value = parts.next()?.trim().to_string();
22+
Some((name, value))
23+
})
24+
.collect()
25+
})
26+
.unwrap_or_default();
27+
28+
let cookies_map: serde_json::Map<String, serde_json::Value> = cookies
29+
.into_iter()
30+
.map(|(name, value)| (name, json!(value)))
31+
.collect();
32+
33+
let resp = json!({
34+
"cookies": cookies_map,
35+
});
36+
37+
Ok(Response::from_status(StatusCode::OK)
38+
.with_content_type(mime::APPLICATION_JSON)
39+
.with_body(to_string_pretty(&resp).unwrap_or_default()))
40+
}
41+
42+
#[utoipa::path(
43+
get,
44+
path = "/cookies/set/{name}/{value}",
45+
tag = "Cookies",
46+
params(
47+
("name" = String, Path, description = "Name of the cookie to set"),
48+
("value" = String, Path, description = "Value to set for the cookie")
49+
),
50+
responses(
51+
(status = 302, description = "Sets a cookie and redirects to /cookies", content_type = "application/json"),
52+
)
53+
)]
54+
/// Sets a cookie and redirects to /cookies.
55+
pub fn set_cookie(req: &Request) -> Result<Response, Error> {
56+
use regex_lite::Regex;
57+
58+
let caps = Regex::new(r"/cookies/set/([^/]+)/([^/]+)$")?
59+
.captures(req.get_path());
60+
61+
if let Some(caps) = caps {
62+
let name = caps.get(1).map(|m| m.as_str()).unwrap_or("");
63+
let value = caps.get(2).map(|m| m.as_str()).unwrap_or("");
64+
65+
return Ok(Response::from_status(StatusCode::FOUND)
66+
.with_header("Location", "/cookies")
67+
.with_header("Set-Cookie", format!("{}={}; Path=/", name, value)));
68+
}
69+
70+
Ok(Response::from_status(StatusCode::BAD_REQUEST)
71+
.with_content_type(mime::TEXT_PLAIN)
72+
.with_body("Invalid cookie parameters"))
73+
}
74+
75+
#[utoipa::path(
76+
get,
77+
path = "/cookies/delete/{name}",
78+
tag = "Cookies",
79+
params(
80+
("name" = String, Path, description = "Name of the cookie to delete")
81+
),
82+
responses(
83+
(status = 302, description = "Deletes a cookie and redirects to /cookies", content_type = "application/json"),
84+
)
85+
)]
86+
/// Deletes a cookie and redirects to /cookies.
87+
pub fn delete_cookie(req: &Request) -> Result<Response, Error> {
88+
use regex_lite::Regex;
89+
90+
let caps = Regex::new(r"/cookies/delete/([^/]+)$")?
91+
.captures(req.get_path());
92+
93+
if let Some(caps) = caps {
94+
let name = caps.get(1).map(|m| m.as_str()).unwrap_or("");
95+
96+
return Ok(Response::from_status(StatusCode::FOUND)
97+
.with_header("Location", "/cookies")
98+
.with_header("Set-Cookie", format!("{}=; Path=/; Expires=Thu, 01 Jan 1970 00:00:00 GMT", name)));
99+
}
100+
101+
Ok(Response::from_status(StatusCode::BAD_REQUEST)
102+
.with_content_type(mime::TEXT_PLAIN)
103+
.with_body("Invalid cookie name"))
104+
}
105+
106+
#[cfg(test)]
107+
mod test {
108+
use super::*;
109+
110+
#[test]
111+
fn test_cookies() {
112+
let req = &Request::from_client()
113+
.with_header("cookie", "foo=bar; baz=qux");
114+
let resp = get_cookies(req);
115+
assert!(resp.is_ok());
116+
let resp = resp.unwrap();
117+
assert_eq!(resp.get_status(), StatusCode::OK);
118+
assert_eq!(resp.get_content_type(), Some(mime::APPLICATION_JSON));
119+
let json_str = resp.into_body_str();
120+
let json: serde_json::Value = serde_json::from_str(&json_str).unwrap();
121+
assert_eq!(json["cookies"]["foo"], "bar");
122+
assert_eq!(json["cookies"]["baz"], "qux");
123+
}
124+
125+
#[test]
126+
fn test_set_cookie() {
127+
let req = &Request::from_client()
128+
.with_path("/cookies/set/foo/bar");
129+
let resp = set_cookie(req);
130+
assert!(resp.is_ok());
131+
let resp = resp.unwrap();
132+
assert_eq!(resp.get_status(), StatusCode::FOUND);
133+
assert_eq!(resp.get_header_str("Location"), Some("/cookies"));
134+
assert_eq!(resp.get_header_str("Set-Cookie"), Some("foo=bar; Path=/"));
135+
}
136+
137+
#[test]
138+
fn test_delete_cookie() {
139+
let req = &Request::from_client()
140+
.with_path("/cookies/delete/foo");
141+
let resp = delete_cookie(req);
142+
assert!(resp.is_ok());
143+
let resp = resp.unwrap();
144+
assert_eq!(resp.get_status(), StatusCode::FOUND);
145+
assert_eq!(resp.get_header_str("Location"), Some("/cookies"));
146+
assert!(resp.get_header_str("Set-Cookie").unwrap().contains("foo="));
147+
assert!(resp.get_header_str("Set-Cookie").unwrap().contains("Expires="));
148+
}
149+
}

src/main.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
mod assets;
22
mod auth;
3+
mod cookies;
34
mod dynamic_data;
45
mod http_methods;
56
mod images;
@@ -19,6 +20,7 @@ use utoipa::OpenApi;
1920
#[openapi(
2021
paths(
2122
auth::bearer, auth::basic_auth,
23+
cookies::get_cookies, cookies::set_cookie, cookies::delete_cookie,
2224
dynamic_data::uuid, dynamic_data::delay_get, dynamic_data::delay_post, dynamic_data::base64,
2325
dynamic_data::bytes,
2426
http_methods::delete, http_methods::get, http_methods::put, http_methods::post, http_methods::patch,
@@ -39,6 +41,7 @@ use utoipa::OpenApi;
3941
),
4042
tags(
4143
(name = "Auth", description = "Auth methods"),
44+
(name = "Cookies", description = "Cookie manipulation"),
4245
(name = "Dynamic data", description = "Generates random and dynamic data"),
4346
(name = "HTTP Methods", description = "Testing different HTTP verbs"),
4447
(name = "Redirects", description = "Returns different redirect responses"),
@@ -93,6 +96,9 @@ fn main(mut req: Request) -> Result<Response, Error> {
9396
use ReqHandler::*;
9497
let routes = vec![
9598
(Method::GET, Regex::new(r"/swagger\.json$")?, Handler(rr_swagger)),
99+
(Method::GET, Regex::new(r"^/cookies$")?, Handler(cookies::get_cookies)),
100+
(Method::GET, Regex::new(r"^/cookies/set/([^/]+)/([^/]+)$")?, Handler(cookies::set_cookie)),
101+
(Method::GET, Regex::new(r"^/cookies/delete/([^/]+)$")?, Handler(cookies::delete_cookie)),
96102
(Method::GET, Regex::new(r"^/status/((\d{3},?)+)$")?, Handler(status_codes::get)),
97103
(Method::POST, Regex::new(r"^/status/(\d{3})$")?, MutHandler(status_codes::post)),
98104
(Method::PUT, Regex::new(r"^/status/(\d{3})$")?, MutHandler(status_codes::put)),

0 commit comments

Comments
 (0)