Skip to main content

bpi_rs/session/
cookie.rs

1use crate::{BpiError, BpiResult};
2
3pub type CookiePair = (String, String);
4
5pub fn parse_cookie_header(cookie_header: &str) -> BpiResult<Vec<CookiePair>> {
6    let mut pairs = Vec::new();
7
8    for segment in cookie_header
9        .split(';')
10        .map(str::trim)
11        .filter(|segment| !segment.is_empty())
12    {
13        let Some((key, value)) = segment.split_once('=') else {
14            return Err(BpiError::invalid_parameter(
15                "cookie",
16                "cookie segment must contain '='",
17            ));
18        };
19
20        let key = key.trim();
21        if key.is_empty() {
22            return Err(BpiError::invalid_parameter(
23                "cookie",
24                "cookie key cannot be empty",
25            ));
26        }
27
28        pairs.push((key.to_string(), value.trim().to_string()));
29    }
30
31    if pairs.is_empty() {
32        return Err(BpiError::invalid_parameter(
33            "cookie",
34            "cookie string cannot be empty",
35        ));
36    }
37
38    Ok(pairs)
39}
40
41pub fn format_cookie_pairs(pairs: &[CookiePair]) -> String {
42    pairs
43        .iter()
44        .map(|(key, value)| format!("{key}={value}"))
45        .collect::<Vec<_>>()
46        .join("; ")
47}
48
49#[cfg(test)]
50mod tests {
51    use super::*;
52    use crate::BpiError;
53
54    #[test]
55    fn parse_cookie_header_returns_named_pairs() -> Result<(), BpiError> {
56        let pairs = parse_cookie_header("SESSDATA=session; bili_jct=csrf")?;
57
58        assert_eq!(
59            pairs,
60            vec![
61                ("SESSDATA".to_string(), "session".to_string()),
62                ("bili_jct".to_string(), "csrf".to_string())
63            ]
64        );
65        Ok(())
66    }
67
68    #[test]
69    fn parse_cookie_header_rejects_invalid_segments() {
70        let err = parse_cookie_header("SESSDATA").unwrap_err();
71
72        assert!(matches!(
73            err,
74            BpiError::InvalidParameter {
75                field: "cookie",
76                ..
77            }
78        ));
79    }
80
81    #[test]
82    fn parse_cookie_header_rejects_empty_input() {
83        let err = parse_cookie_header(" ; ").unwrap_err();
84
85        assert!(matches!(
86            err,
87            BpiError::InvalidParameter {
88                field: "cookie",
89                ..
90            }
91        ));
92    }
93
94    #[test]
95    fn format_cookie_pairs_returns_deterministic_header() {
96        let pairs = vec![
97            ("SESSDATA".to_string(), "session".to_string()),
98            ("bili_jct".to_string(), "csrf".to_string()),
99        ];
100
101        assert_eq!(
102            format_cookie_pairs(&pairs),
103            "SESSDATA=session; bili_jct=csrf"
104        );
105    }
106}