dnd_character/api/
shared.rs

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