gn-ranking-client-rs 0.1.1

Client library for the gamenight ranking service
Documentation
use std::{collections::HashMap, env, fs, path::PathBuf, result};

use reqwest;
use serde::de::DeserializeOwned;

pub mod models;


pub struct RankingClient {
    client: reqwest::Client,
    api_key: String,
    routes: HashMap<String, models::Route>,
}

impl RankingClient {

    pub fn new(api_key: String) -> Self {
        let routes = Self::load_default_routes();

        Self {
            client: reqwest::Client::new(),
            api_key,
            routes,
        }
    }

    fn load_default_routes() -> HashMap<String, models::Route> {
        let content = Self::substitute_env_vars(include_str!("../routes.yml"));
        serde_yaml::from_str(&content).expect("Failed to parse routes file")
    }

    fn substitute_env_vars(content: &str) -> String {
        let mut substituted = content.to_string();
        for (key, value) in env::vars() {
            let placeholder = format!("${{{}}}", key);
            substituted = substituted.replace(&placeholder, &value);
        }
        substituted
    }


    pub fn load_route_conf(&mut self, path: &str) {
        let routes = Self::load_routes(PathBuf::from(path));
        self.routes = routes;
    }

    pub async fn create<'a, M, R>(&self, model: M, route_id: &str) -> Result<R, Box<dyn std::error::Error>>
    where
        M: serde::Serialize,
        R: DeserializeOwned
    {
        let url = &self.routes.get(route_id).unwrap().path;

        let response = self.client.post(url).json(&model).header("access_token", &self.api_key).send().await?.error_for_status()?;

        Ok(response.json().await?)
    }

    pub async fn read<'a, F, R>(&self, filter: F, route_id: &str) -> Result<R, Box<dyn std::error::Error>>
    where
        F: serde::Serialize,
        R: DeserializeOwned
    {
        let url = &self.routes.get(route_id).unwrap().path;

        let response = self.client.get(url).query(&filter).header("access_token", &self.api_key).send().await?.error_for_status()?;

        Ok(response.json().await?)
    }

    pub async fn game_init(&self, game: models::create::Game) -> Result<models::read::Game, Box<dyn std::error::Error>> {
        Ok(self.create(game, "game_init").await?)
    }

    pub async fn match_replay_create(&self, result: models::create::ReplayData) -> Result<models::read::Game, Box<dyn std::error::Error>> {
        Ok(self.create(result, "replay_data").await?)
    }

    pub async fn game_read(&self, filter: models::filter::Game) -> Result<Vec<models::read::Game>, Box<dyn std::error::Error>> {
        Ok(self.read(filter, "game_read").await?)
    }

    pub async fn match_init(&self, _match: models::create::Match) -> Result<models::read::Match, Box<dyn std::error::Error>> {
        Ok(self.create(_match, "match_init").await?)
    }

    pub async fn player_games_read(&self, filter: models::filter::PlayerGame) -> Result<Vec<models::read::PlayerGame>, Box<dyn std::error::Error>> {
        Ok(self.read(filter, "player_games_read").await?)
    }

    pub async fn player_stars(&self, player_id: &str, game_name: &str, game_mode: &str) -> Result<i32, Box<dyn std::error::Error>> {
        let filter = models::filter::PlayerGame {
            player_id: Some(player_id.to_string()),
            game_name: Some(game_name.to_string()),
            game_mode: Some(game_mode.to_string()),
            in_order: Some(true),
        };
        Ok(self.player_games_read(filter).await?.first().ok_or_else(|| "Game not found")?.game_stars)
    }

    fn load_routes(path: PathBuf) -> HashMap<String, models::Route> {
        let content = fs::read_to_string(path).expect("Failed to read routes file");
        let content = Self::substitute_env_vars(content.as_str());
        serde_yaml::from_str(&content).expect("Failed to parse routes file")
    }
}

#[cfg(test)]
mod tests {
    use super::*;

}