Skip to main content

edda_client/
monsters.rs

1//! Client for the monster service: monsters, and the full text of spells
2//! and conditions.
3
4use edda_types::{
5    monster::{Monster, MonsterSheet, MonsterSummary},
6    rules_text::{Entry, RulesText},
7};
8use uuid::Uuid;
9
10use crate::{ClientError, check, http_client, send};
11
12/// The monster service's client: monsters, spells and conditions.
13#[derive(Debug, Clone)]
14pub struct MonsterClient {
15    base_url: String,
16    http: reqwest::Client,
17}
18
19impl MonsterClient {
20    /// A client for the monster service at `base_url`, without a token.
21    pub fn new(base_url: impl Into<String>) -> Self {
22        Self {
23            base_url: base_url.into().trim_end_matches('/').to_string(),
24            http: http_client(None),
25        }
26    }
27
28    /// Sends `token` (from [`crate::auth::AuthClient::login`]) with every
29    /// request.
30    #[must_use]
31    pub fn with_token(mut self, token: &str) -> Self {
32        self.http = http_client(Some(token));
33        self
34    }
35
36    /// The shared monsters of the campaign's edition and its own, by name;
37    /// only those whose name contains `search`, if given.
38    pub async fn list(&self, search: Option<&str>) -> Result<Vec<MonsterSummary>, ClientError> {
39        let mut request = self.http.get(self.url("/monsters"));
40        if let Some(search) = search {
41            request = request.query(&[("search", search)]);
42        }
43        send(request).await
44    }
45
46    /// One monster's stat block.
47    pub async fn get(&self, id: Uuid) -> Result<Monster, ClientError> {
48        send(self.http.get(self.monster_url(id))).await
49    }
50
51    /// Adds a monster to the campaign's own.
52    pub async fn create(&self, sheet: &MonsterSheet) -> Result<Monster, ClientError> {
53        send(self.http.post(self.url("/monsters")).json(sheet)).await
54    }
55
56    /// Replaces one of the campaign's own monsters ([`ClientError::Forbidden`]
57    /// for a shared one).
58    pub async fn replace(&self, id: Uuid, sheet: &MonsterSheet) -> Result<Monster, ClientError> {
59        send(self.http.put(self.monster_url(id)).json(sheet)).await
60    }
61
62    /// Deletes one of the campaign's own monsters.
63    pub async fn delete(&self, id: Uuid) -> Result<(), ClientError> {
64        let response = self.http.delete(self.monster_url(id)).send().await?;
65        check(response).await.map(drop)
66    }
67
68    /// The shared spells (`T` = [`edda_types::rules_text::SpellText`])
69    /// or conditions of the campaign's edition and its own, whole, by name;
70    /// only those whose name contains `search`, if given.
71    pub async fn texts<T: RulesText>(
72        &self,
73        search: Option<&str>,
74    ) -> Result<Vec<Entry<T>>, ClientError> {
75        let mut request = self.http.get(self.url(&format!("/{}", T::KIND)));
76        if let Some(search) = search {
77            request = request.query(&[("search", search)]);
78        }
79        send(request).await
80    }
81
82    /// One spell or condition.
83    pub async fn text<T: RulesText>(&self, id: Uuid) -> Result<Entry<T>, ClientError> {
84        send(self.http.get(self.text_url::<T>(id))).await
85    }
86
87    /// Adds one to the campaign's own.
88    pub async fn create_text<T: RulesText>(&self, text: &T) -> Result<Entry<T>, ClientError> {
89        send(
90            self.http
91                .post(self.url(&format!("/{}", T::KIND)))
92                .json(text),
93        )
94        .await
95    }
96
97    /// Replaces one of the campaign's own ([`ClientError::Forbidden`] for a
98    /// shared one).
99    pub async fn replace_text<T: RulesText>(
100        &self,
101        id: Uuid,
102        text: &T,
103    ) -> Result<Entry<T>, ClientError> {
104        send(self.http.put(self.text_url::<T>(id)).json(text)).await
105    }
106
107    /// Deletes one of the campaign's own spells or conditions.
108    pub async fn delete_text<T: RulesText>(&self, id: Uuid) -> Result<(), ClientError> {
109        let response = self.http.delete(self.text_url::<T>(id)).send().await?;
110        check(response).await.map(drop)
111    }
112
113    fn text_url<T: RulesText>(&self, id: Uuid) -> String {
114        self.url(&format!("/{}/{id}", T::KIND))
115    }
116
117    fn url(&self, path: &str) -> String {
118        format!("{}{path}", self.base_url)
119    }
120
121    fn monster_url(&self, id: Uuid) -> String {
122        self.url(&format!("/monsters/{id}"))
123    }
124}