sf_api/
response.rs

1use std::{collections::HashMap, fmt::Debug, str::FromStr};
2
3use chrono::NaiveDateTime;
4use log::{error, trace, warn};
5
6use crate::error::SFError;
7
8#[ouroboros::self_referencing]
9/// A bunch of new information about the state of the server and/or the
10/// player
11///
12/// NOTE: This has a weird syntax to access, because we do not want to create
13/// 10000 strings on each request and instead just store the raw response body
14/// and references into it. This is faster & uses less memory, but because of
15/// rusts borrow checker requires some weird syntax here.
16// Technically we could do this safely with an iterator, that parses on demand,
17// but send_command() needs to access specific response keys to keep the session
18// running, which means a HashMap needs to be constructed no matter what
19pub struct Response {
20    body: String,
21    #[borrows(body)]
22    #[covariant]
23    resp: HashMap<&'this str, ResponseVal<'this>>,
24    /// We store this to make sure the time calculations are still correct, if
25    /// this response is held any amount of time before being used to update
26    /// character state
27    received_at: NaiveDateTime,
28}
29
30impl Clone for Response {
31    // This is not a good clone..
32    #[allow(clippy::expect_used)]
33    fn clone(&self) -> Self {
34        Self::parse(self.raw_response().to_string(), self.received_at())
35            .expect("Invalid response cloned")
36    }
37}
38
39impl Debug for Response {
40    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
41        f.debug_map()
42            .entries(self.values().iter().map(|a| (a.0, a.1.as_str())))
43            .finish()
44    }
45}
46
47#[cfg(feature = "serde")]
48impl serde::Serialize for Response {
49    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
50    where
51        S: serde::Serializer,
52    {
53        use serde::ser::SerializeStruct;
54        let mut s = serializer.serialize_struct("Response", 2)?;
55        s.serialize_field("body", self.borrow_body())?;
56        s.serialize_field("received_at", &self.received_at())?;
57        s.end()
58    }
59}
60
61#[cfg(feature = "serde")]
62impl<'de> serde::Deserialize<'de> for Response {
63    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
64    where
65        D: serde::Deserializer<'de>,
66    {
67        struct AVisitor;
68
69        impl<'de> serde::de::Visitor<'de> for AVisitor {
70            type Value = Response;
71
72            fn expecting(
73                &self,
74                formatter: &mut std::fmt::Formatter,
75            ) -> std::fmt::Result {
76                formatter.write_str(
77                    "struct Response with fields body and received_at",
78                )
79            }
80
81            fn visit_map<V>(self, mut map: V) -> Result<Self::Value, V::Error>
82            where
83                V: serde::de::MapAccess<'de>,
84            {
85                let mut body = None;
86                let mut received_at = None;
87
88                while let Some(key) = map.next_key()? {
89                    match key {
90                        "body" => {
91                            body = Some(map.next_value()?);
92                        }
93                        "received_at" => {
94                            received_at = Some(map.next_value()?);
95                        }
96                        _ => {
97                            // Ignore unknown fields
98                            map.next_value::<serde::de::IgnoredAny>()?;
99                        }
100                    }
101                }
102
103                let body: String =
104                    body.ok_or_else(|| serde::de::Error::missing_field("q"))?;
105                let received_at: NaiveDateTime = received_at
106                    .ok_or_else(|| serde::de::Error::missing_field("j"))?;
107
108                Response::parse(body, received_at).map_err(|_| {
109                    serde::de::Error::custom("invalid response body")
110                })
111            }
112        }
113
114        deserializer.deserialize_struct(
115            "Response",
116            &["body", "received_at"],
117            AVisitor,
118        )
119    }
120}
121
122impl Response {
123    /// Returns a reference to the hashmap, that contains mappings of response
124    /// keys to values
125    #[must_use]
126    pub fn values(&self) -> &HashMap<&str, ResponseVal<'_>> {
127        self.borrow_resp()
128    }
129
130    /// Returns the raw response from the server. This should only ever be
131    /// necessary for debugging, caching, or in case there is ever a new
132    /// response format in a response, that is not yet supported. You can of
133    /// course also use this to look at how horrible the S&F encoding is..
134    #[must_use]
135    pub fn raw_response(&self) -> &str {
136        self.borrow_body()
137    }
138
139    /// Returns the time, at which the response was received
140    #[must_use]
141    pub fn received_at(&self) -> NaiveDateTime {
142        self.with_received_at(|a| *a)
143    }
144
145    /// Parses a response body from the server into a usable format
146    /// You might want to use this, if you are analyzing responses from the
147    /// browsers network tab. If you are trying to store/read responses to/from
148    /// disk to cache them, or otherwise, you should use the sso feature to
149    /// serialize/deserialize them instead
150    ///
151    /// # Errors
152    /// - `ServerError`: If the server responsed with an error
153    /// - `ParsingError`: If the response does not follow the standard S&F
154    ///   server response schema
155    pub fn parse(
156        og_body: String,
157        received_at: NaiveDateTime,
158    ) -> Result<Response, SFError> {
159        // We can not return from the closure below, so we have to do this work
160        // twice (sadly)
161
162        // NOTE: I think the trims might actually be completely unnecessary.
163        // Pretty sure I mixed them up with command encoding, which is actually
164        // '|' padded
165
166        let body = og_body
167            .trim_end_matches('|')
168            .trim_start_matches(|a: char| !a.is_alphabetic());
169        trace!("Received raw response: {body}");
170
171        if !body.contains(':')
172            && !body.starts_with("success")
173            && !body.starts_with("Success")
174        {
175            return Err(SFError::ParsingError(
176                "unexpected server response",
177                body.to_string(),
178            ));
179        }
180
181        if body.starts_with("error") || body.starts_with("Error") {
182            let raw_error = body.split_once(':').unwrap_or_default().1;
183
184            let error_msg = match raw_error {
185                "adventure index must be 1-3" => "quest index must be 0-2",
186                x => x,
187            };
188
189            return Err(SFError::ServerError(error_msg.to_string()));
190        }
191
192        let resp = ResponseBuilder {
193            body: og_body,
194            resp_builder: |body: &String| {
195                let mut res = HashMap::new();
196                for part in body
197                    .trim_start_matches(|a: char| !a.is_alphabetic())
198                    .trim_end_matches('|')
199                    .split('&')
200                    .filter(|a| !a.is_empty())
201                {
202                    let Some((full_key, value)) = part.split_once(':') else {
203                        warn!("weird k/v in resp: {part}");
204                        continue;
205                    };
206
207                    let (key, sub_key) = match full_key.split_once('.') {
208                        Some(x) => {
209                            // full_key == key.subkey
210                            x
211                        }
212                        None => {
213                            if let Some((k, sk)) = full_key.split_once('(') {
214                                // full_key == key(4)
215                                (k, sk.trim_matches(')'))
216                            } else {
217                                // full_key == key
218                                (full_key, "")
219                            }
220                        }
221                    };
222                    if key.is_empty() {
223                        continue;
224                    }
225
226                    res.insert(key, ResponseVal { value, sub_key });
227                }
228                res
229            },
230            received_at,
231        }
232        .build();
233
234        Ok(resp)
235    }
236}
237
238#[derive(Debug, Clone, Copy)]
239#[allow(clippy::module_name_repetitions)]
240/// This is the raw &str, that the server send as a value to some key. This
241/// often requires extra conversions/parsing to use practically, so we associate
242/// the most common parsing functions as methods to this data.
243pub struct ResponseVal<'a> {
244    value: &'a str,
245    sub_key: &'a str,
246}
247
248impl ResponseVal<'_> {
249    /// Converts the response value into the required type
250    ///
251    /// # Errors
252    /// If the response value can not be parsed into the output
253    /// value, a `ParsingError` will be returned
254    pub fn into<T: FromStr>(self, name: &'static str) -> Result<T, SFError> {
255        self.value.trim().parse().map_err(|_| {
256            error!("Could not convert {name} into target type: {self}");
257            SFError::ParsingError(name, self.value.to_string())
258        })
259    }
260
261    /// Converts the repsponse into a list, by splitting the raw value by '/'
262    /// and converting each value into the required type. If any conversion
263    /// fails, an error is returned
264    ///
265    /// # Errors
266    /// If any of the values in the string can not be parsed into the output
267    /// value, the `ParsingError` for that value will be returned
268    pub fn into_list<T: FromStr>(
269        self,
270        name: &'static str,
271    ) -> Result<Vec<T>, SFError> {
272        let x = &self.value;
273        if x.is_empty() {
274            return Ok(Vec::new());
275        }
276        // Trimming ` ` & `\n` is not required. Might remove this later
277        x.trim_matches(|a| ['/', ' ', '\n'].contains(&a))
278            .split('/')
279            .map(|c| {
280                c.trim().parse::<T>().map_err(|_| {
281                    error!(
282                        "Could not convert {name} into list because of {c}: \
283                         {self}"
284                    );
285                    SFError::ParsingError(name, format!("{c:?}"))
286                })
287            })
288            .collect()
289    }
290
291    /// The way keys are parsed will trim some info from the string. The key for
292    /// the player save `ownplayersave` is actually `ownplayersave.playerSave`.
293    /// As this `.playerSave` is not relevant here and not in most cases, I
294    /// decided to trim that off. More common, this is also just `s`, `r`, or a
295    /// size hint like `(10)`. In some cases though, this information can be
296    /// helpful for parsing. Thus, you can access it here
297    #[must_use]
298    pub fn sub_key(&self) -> &str {
299        self.sub_key
300    }
301
302    /// Returns the raw reference to the internal &str, that the server send
303    #[must_use]
304    pub fn as_str(&self) -> &str {
305        self.value
306    }
307}
308
309impl std::fmt::Display for ResponseVal<'_> {
310    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
311        f.write_str(self.value)
312    }
313}