dnd_character/api/
shared.rs

1use cynic::http::CynicReqwestError;
2use std::collections::HashMap;
3
4use crate::Character;
5use crate::api::classes::LevelSpellcasting;
6
7#[derive(Debug, thiserror::Error)]
8pub enum ApiError {
9    #[error("Reqwest error: {0}")]
10    Reqwest(#[from] CynicReqwestError),
11    #[error("Schema error")]
12    Schema,
13}
14
15//noinspection RsCompileErrorMacro
16#[cynic::schema("dnd5eapi")]
17pub(super) mod schema {}
18
19#[derive(Debug)]
20pub enum CheckError {
21    InvalidRace,
22    InvalidClass,
23    InvalidBackground,
24    InvalidAlignment,
25    InvalidAbilities,
26}
27
28mod race_query {
29    use super::schema;
30    use crate::api::shared::ApiError;
31    use crate::{Character, GRAPHQL_API_URL};
32    use cynic::QueryBuilder;
33    use cynic::http::ReqwestExt;
34    use reqwest::Client;
35
36    #[derive(cynic::QueryVariables, Debug)]
37    struct SpeedQueryVariables {
38        pub index: String,
39    }
40
41    #[derive(cynic::QueryFragment, Debug)]
42    #[cynic(graphql_type = "Query", variables = "SpeedQueryVariables")]
43    struct SpeedQuery {
44        #[arguments(index: $index)]
45        pub race: Option<RaceSpeed>,
46    }
47
48    #[derive(cynic::QueryFragment, Debug)]
49    #[cynic(graphql_type = "Race")]
50    struct RaceSpeed {
51        pub speed: i32,
52    }
53
54    impl Character {
55        pub async fn get_base_speed(&self) -> Result<i32, ApiError> {
56            let op = SpeedQuery::build(SpeedQueryVariables {
57                index: self.race_index.clone(),
58            });
59
60            let speed = Client::new()
61                .post(GRAPHQL_API_URL.as_str())
62                .run_graphql(op)
63                .await?
64                .data
65                .ok_or(ApiError::Schema)?
66                .race
67                .ok_or(ApiError::Schema)?
68                .speed;
69
70            Ok(speed)
71        }
72    }
73}
74
75impl Character {
76    pub async fn get_spellcasting_slots(
77        &self,
78    ) -> Result<HashMap<String, LevelSpellcasting>, ApiError> {
79        let mut spellcasting_slots = HashMap::new();
80        for class in self.classes.0.iter() {
81            let spellcasting_slots_class = class.1.get_spellcasting_slots().await?;
82            if let Some(spellcasting_slots_class) = spellcasting_slots_class {
83                spellcasting_slots.insert(class.0.clone(), spellcasting_slots_class);
84            }
85        }
86        Ok(spellcasting_slots)
87    }
88
89    pub async fn get_features(&self, passive: bool) -> Result<Vec<String>, ApiError> {
90        let mut features = Vec::new();
91        for class in self.classes.0.iter() {
92            let features_class = class.1.get_levels_features(None, passive).await?;
93            features.extend(features_class);
94        }
95        Ok(features)
96    }
97
98    #[cfg(feature = "serde")]
99    pub async fn rich_data(&self) -> Result<serde_json::Value, ApiError> {
100        use serde_json::json;
101
102        let spellcasting_slots = self.get_spellcasting_slots().await?;
103        let features = self.get_features(true).await?;
104
105        let mut character = json!(self);
106
107        if !spellcasting_slots.is_empty() {
108            character["spellcasting_slots"] = json!(spellcasting_slots);
109        }
110
111        character["max_hp"] = json!(self.max_hp());
112
113        if !features.is_empty() {
114            character["features"] = json!(features);
115        }
116
117        Ok(character)
118    }
119
120    /// Call this method every day to reset daily vars
121    pub async fn new_day(&mut self) {
122        self.hp = self.max_hp();
123        self.classes.new_day().await;
124    }
125}