libbklr 0.0.2

A simple Bakaláři API library
Documentation
use reqwest::{self, header::HeaderMap};
use serde::{Serialize, Deserialize};

/// A struct representing the interface to the school system API. For information
/// on why these fields are here and basically everything else about the B school system,
/// see <https://github.com/bakalari-api/bakalari-api-v3> (readmes are in czech, use a translator)
#[derive(Serialize, Deserialize)]
pub struct BklrOptions {
    //TODO: Add more verbose descriptions to fields

    #[serde(alias = "bak:ApiVersion")]
    pub api_version: String,
    #[serde(alias = "bak:AppVersion")]
    pub app_version: String,
    #[serde(alias = "bak:UserId")]
    pub user_id: usize,

    pub access_token: String,
    pub refresh_token: String,
    pub id_token: String,
    //  token_type is always "Bearer"
    pub expires_in: i32,
    pub scope: Vec<String>
}

pub struct BklrApi {
    pub url: String,
    pub options: BklrOptions,
    pub client: reqwest::Client
}

#[derive(Serialize, Deserialize)]
pub struct Grade {
        //TODO: Add more verbose descriptions to fields

        mark_date: String,
        edit_date: String,
        caption: String,
        theme: String,
        mark_text: String, // grades don't have to be only numbers - i know, quite silly
        teacher_id: String,
        r#type: char,
        type_note: String,
        weight: u8,
        subject_id: usize,
        is_new: bool,
        is_points: bool,
        calculated_mark_text: String,
        class_rank_text: String, // i have no idea what this means
        id: String,
        points_text: String,
        max_points: usize
}

impl BklrApi {
    pub async fn get_marks(&self) -> Option<Vec<Grade>> {
        let url = &self.url;
        let access_token = &self.options.access_token;
        let formatted_url = format!("{url}/api/3/marks");
        let request = self.client.post(formatted_url).header("Authorization", format!("Bearer {access_token}")).send().await;
        match request {
            Ok(response) => match response.json::<Vec<Grade>>().await {
                Ok(marks) => Some(marks),
                Err(_) => None
            }

            Err(_) => None
        }
    }
}

pub async fn get_api(username: String, password: String, url: String) -> Option<BklrApi> {
    let body = format!("client_id=ANDR&grant_type=password&username={username}&password={password}");
    let mut headers = HeaderMap::new();
    headers.append("Content-Type", "application/x-www-form-urlencoded".parse().unwrap());
    let client = reqwest::Client::builder().default_headers(headers).build().unwrap();
    let response = client.post(&url).body(body).send().await;
    let options: Option<BklrOptions> = match response {
        Ok(res) => Some(res.json::<BklrOptions>().await.unwrap()),
        Err(why) => { println!("{}", why); None }
    };

    if options.is_some() {
        Some(BklrApi { url, options: options.unwrap(), client })
    } else {
        None
    }
}