Skip to main content

sf_api/
response.rs

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