shift_client/types/
account_page.rs

1use crate::util::extract_csrf_token;
2use scraper::{Html, Selector};
3
4/// Error that may occur while parsing an [`AccountPage`].
5#[derive(Debug, thiserror::Error)]
6pub enum FromHtmlError {
7    /// Missing csrf token
8    #[error("missing csrf token")]
9    MissingCsrfToken,
10    /// Missing email
11    #[error("missing email")]
12    MissingEmail,
13    /// Missing display name
14    #[error("missing display name")]
15    MissingDisplayName,
16    /// Missing first name
17    #[error("missing first name")]
18    MissingFirstName,
19}
20
21/// The account page
22#[derive(Debug)]
23pub struct AccountPage {
24    /// The csrf token
25    pub csrf_token: String,
26    /// The email
27    pub email: String,
28    /// The display name
29    pub display_name: String,
30    /// The first name
31    pub first_name: String,
32}
33
34impl AccountPage {
35    /// Parse an [`AccountPage`] from html
36    pub(crate) fn from_html(html: &Html) -> Result<Self, FromHtmlError> {
37        let csrf_token = extract_csrf_token(html)
38            .ok_or(FromHtmlError::MissingCsrfToken)?
39            .to_string();
40
41        let email = get_text_by_id(html, "current_email")
42            .ok_or(FromHtmlError::MissingEmail)?
43            .to_string();
44
45        let display_name = get_text_by_id(html, "current_display_name")
46            .ok_or(FromHtmlError::MissingDisplayName)?
47            .to_string();
48
49        let first_name = get_text_by_id(html, "current_first_name")
50            .ok_or(FromHtmlError::MissingFirstName)?
51            .to_string();
52
53        Ok(Self {
54            csrf_token,
55            email,
56            display_name,
57            first_name,
58        })
59    }
60}
61
62fn get_text_by_id<'a>(html: &'a Html, id: &str) -> Option<&'a str> {
63    let selector = Selector::parse(&format!("#{id}")).ok()?;
64    let element = html.select(&selector).next()?;
65    element.text().next()
66}
67
68#[cfg(test)]
69mod test {
70    use super::*;
71
72    const SAMPLE_1: &str = include_str!("../../test_data/account.html");
73
74    #[test]
75    fn sample_1() {
76        let html = Html::parse_document(SAMPLE_1);
77        let page = AccountPage::from_html(&html).expect("invalid account page");
78        dbg!(page);
79    }
80}