dnd_character/api/
shared.rs1use 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#[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 pub async fn new_day(&mut self) {
122 self.hp = self.max_hp();
123 self.classes.new_day().await;
124 }
125}