1use cookie::Cookie;
4use http::{header, HeaderMap, HeaderValue};
5use sova_core::extend::named;
6use sova_core::{App, Next, Plugin, Request, Response};
7use std::collections::HashMap;
8
9#[derive(Debug, Default, Clone)]
11pub struct Cookies {
12 map: HashMap<String, String>,
13}
14
15impl Cookies {
16 pub fn get(&self, name: &str) -> Option<&str> {
17 self.map.get(name).map(|s| s.as_str())
18 }
19
20 pub fn iter(&self) -> impl Iterator<Item = (&str, &str)> {
21 self.map.iter().map(|(k, v)| (k.as_str(), v.as_str()))
22 }
23}
24
25pub struct CookieLayer;
27
28impl Plugin for CookieLayer {
29 fn id(&self) -> &'static str {
30 "cookies"
31 }
32
33 fn meta(&self) -> sova_core::PluginMeta {
34 sova_core::PluginMeta::new("Cookies")
35 .description("Parse Cookie header into request-local Cookies")
36 .version(env!("CARGO_PKG_VERSION"))
37 }
38
39 fn install(self, app: &mut App) {
40 app.state(CookieLayerPresent);
41 app.use_middleware(named("cookies", |mut req: Request, next: Next| async move {
42 let parsed = parse_cookies(&req.headers);
43 req.set(parsed);
44 next(req).await
45 }));
46 }
47}
48
49#[derive(Clone, Copy, Debug)]
51pub struct CookieLayerPresent;
52
53fn parse_cookies(headers: &HeaderMap) -> Cookies {
54 let mut cookies = Cookies::default();
55 if let Some(raw) = headers
56 .get(header::COOKIE)
57 .and_then(|v| v.to_str().ok())
58 {
59 for part in raw.split(';') {
60 if let Ok(c) = Cookie::parse(part.trim()) {
61 cookies
62 .map
63 .insert(c.name().to_string(), c.value().to_string());
64 }
65 }
66 }
67 cookies
68}
69
70pub trait ResponseCookieExt {
72 fn cookie(self, cookie: Cookie<'_>) -> Self;
73}
74
75impl ResponseCookieExt for Response {
76 fn cookie(mut self, cookie: Cookie<'_>) -> Self {
77 let value = cookie.encoded().to_string();
78 if let Ok(v) = HeaderValue::from_str(&value) {
79 self.headers_mut().append(header::SET_COOKIE, v);
80 }
81 self
82 }
83}
84
85pub use cookie::Cookie as CookieBuilder;