dnd_character/api/
shared.rs1use 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#[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 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 character
117 .as_table_mut()
118 .unwrap()
119 .insert("max_hp".to_string(), toml::Value::try_from(self.max_hp())?);
120
121 if !features.is_empty() {
122 character
123 .as_table_mut()
124 .unwrap()
125 .insert("features".to_string(), toml::Value::try_from(features)?);
126 }
127
128 Ok(toml::to_string_pretty(&character)?)
129 }
130
131 pub async fn new_day(&mut self) {
133 self.hp = self.max_hp();
134 self.classes.new_day().await;
135 }
136}