arti_rpc_client_core/msgs/
response.rs

1//! Support for decoding RPC Responses.
2
3use std::sync::Arc;
4
5use serde::{Deserialize, Serialize};
6
7use super::{AnyRequestId, JsonAnyObj};
8use crate::{
9    conn::ErrorResponse,
10    util::{define_from_for_arc, Utf8CString},
11};
12
13/// An unparsed and unvalidated response, as received from Arti.
14///
15/// (It will have no internal newlines, and a single NL at the end.)
16#[derive(Clone, Debug, derive_more::AsRef)]
17pub struct UnparsedResponse {
18    /// The body of this response.
19    msg: String,
20}
21
22impl UnparsedResponse {
23    /// Construct a new UnparsedResponse.
24    pub(crate) fn new(msg: String) -> Self {
25        Self { msg }
26    }
27}
28
29/// A response that we have validated for correct syntax,
30/// re-encoded in canonical form,
31/// and enough to find the information we need about it
32/// to deliver it to the application.
33#[derive(Clone, Debug)]
34pub(crate) struct ValidatedResponse {
35    /// The re-encoded text of this response.
36    pub(crate) msg: Utf8CString,
37    /// The metadata from this response.
38    pub(crate) meta: ResponseMeta,
39}
40
41/// An error that occurred when trying to decode an RPC response.
42#[derive(Clone, Debug, thiserror::Error)]
43#[non_exhaustive]
44pub(crate) enum DecodeResponseError {
45    /// We couldn't decode a response as json.
46    #[error("Arti sent a message that didn't conform to the RPC protocol")]
47    JsonProtocolViolation(#[source] Arc<serde_json::Error>),
48
49    /// There was something (other than json encoding) wrong with a response.
50    #[error("Arti sent a message that didn't conform to the RPC protocol: {0}")]
51    ProtocolViolation(&'static str),
52
53    /// We decoded the response, but rather than having an `id`,
54    /// it had an error message from Arti with no id.  We treat this as fatal.
55    #[error("Arti reported a fatal error: {0}")]
56    Fatal(ErrorResponse),
57}
58define_from_for_arc!( serde_json::Error => DecodeResponseError [JsonProtocolViolation] );
59
60impl UnparsedResponse {
61    /// If this response is well-formed, and it corresponds to a single request,
62    /// re-encode it and return it as a ValidatedResponse.
63    pub(crate) fn try_validate(self) -> Result<ValidatedResponse, DecodeResponseError> {
64        // We're using serde_json::Value in order to preserve any unrecognized fields
65        // in the response when we re-encode it.
66        //
67        // The alternative would be to preserve unrecognized fields using serde(flatten) and a
68        // JsonMap in each struct.  But that creates a risk of forgetting to do so in some
69        // struct that we create in the future.
70        let json: serde_json::Value = serde_json::from_str(self.as_str())?;
71        let mut msg: String = serde_json::to_string(&json)?;
72        debug_assert!(!msg.contains('\n'));
73        msg.push('\n');
74        let msg: Utf8CString = msg.try_into().map_err(|_| {
75            // (This should be impossible; serde_json rejects NULs.)
76            DecodeResponseError::ProtocolViolation("Unexpected NUL in validated message")
77        })?;
78        let response: Response = serde_json::from_value(json)?;
79        let meta = match ResponseMeta::try_from_response(&response) {
80            Ok(m) => m?,
81            Err(_) => {
82                return Err(DecodeResponseError::Fatal(
83                    ErrorResponse::from_validated_string(msg),
84                ))
85            }
86        };
87        Ok(ValidatedResponse { msg, meta })
88    }
89
90    /// Return the inner `str` for this unparsed message.
91    pub(crate) fn as_str(&self) -> &str {
92        self.msg.as_str()
93    }
94}
95
96impl ValidatedResponse {
97    /// Return true if no additional response should arrive for this request.
98    pub(crate) fn is_final(&self) -> bool {
99        use ResponseKind as K;
100        match self.meta.kind {
101            K::Error | K::Success => true,
102            K::Update => false,
103        }
104    }
105
106    /// Return the request ID associated with this response.
107    pub(crate) fn id(&self) -> &AnyRequestId {
108        &self.meta.id
109    }
110}
111
112/// Metadata extracted from a response while decoding it.
113#[derive(Clone, Debug)]
114#[cfg_attr(test, derive(Eq, PartialEq))]
115pub(crate) struct ResponseMeta {
116    /// The request ID for this response.
117    pub(crate) id: AnyRequestId,
118    /// The kind of response that was received.
119    pub(crate) kind: ResponseKind,
120}
121
122/// A kind of response received from Arti.
123//
124// TODO: Possibly unify or derive from ResponseMetaBodyDe?
125#[derive(Clone, Debug, Eq, PartialEq)]
126pub(crate) enum ResponseKind {
127    /// Arti reports that an error has occurred.
128    Error,
129    /// Arti reports that the request completed successfully.
130    Success,
131    /// Arti reports an incremental update for the request.
132    Update,
133}
134
135/// Serde-only type: decodes enough fields from a response in order to validate it
136/// and route it to the application.
137#[derive(Deserialize, Debug)]
138struct Response {
139    /// The request ID for this response.
140    ///
141    /// This field is mandatory for any non-Error response.
142    id: Option<AnyRequestId>,
143    /// The body as decoded for this response.
144    #[serde(flatten)]
145    body: ResponseBody,
146}
147
148/// Inner type to implement `Response``
149#[derive(Deserialize, Debug)]
150enum ResponseBody {
151    /// Arti reports that an error has occurred.
152    ///
153    /// In this case, we decode the error to make sure it's well-formed.
154    #[serde(rename = "error")]
155    Error(RpcError),
156    /// Arti reports that the request completed successfully.
157    #[serde(rename = "result")]
158    Success(JsonAnyObj),
159    /// Arti reports an incremental update for the request.
160    #[serde(rename = "update")]
161    Update(JsonAnyObj),
162}
163impl<'a> From<&'a ResponseBody> for ResponseKind {
164    fn from(value: &'a ResponseBody) -> Self {
165        use ResponseBody as RMB;
166        use ResponseKind as RK;
167        // TODO RPC: If we keep the current set of types,
168        // we should have this discriminant code be macro-generated.
169        match value {
170            RMB::Error(_) => RK::Error,
171            RMB::Success(_) => RK::Success,
172            RMB::Update(_) => RK::Update,
173        }
174    }
175}
176
177/// Error returned from [`ResponseMeta::try_from_response`] when a response
178/// has no Id field, and therefore indicates a fatal protocol error.
179#[derive(thiserror::Error, Debug, Clone)]
180#[error("Response was fatal (it had no ID)")]
181struct ResponseWasFatal;
182
183impl ResponseMeta {
184    /// Try to extract a `ResponseMeta` from a response.
185    ///
186    /// Return `Err(ResponseWasFatal)` if the ID was missing on an error, and `Err(Err(_))` on any
187    /// other problem.
188    fn try_from_response(
189        response: &Response,
190    ) -> Result<Result<Self, DecodeResponseError>, ResponseWasFatal> {
191        use DecodeResponseError as E;
192        use ResponseBody as Body;
193        match (&response.id, &response.body) {
194            (None, Body::Error(_ignore)) => {
195                // No ID, so this is a fatal response.
196                // Re-encode the response.
197                Err(ResponseWasFatal)
198            }
199            (None, _) => Ok(Err(E::ProtocolViolation("Missing ID field"))),
200            (Some(id), body) => Ok(Ok(ResponseMeta {
201                id: id.clone(),
202                kind: (body).into(),
203            })),
204        }
205    }
206}
207
208/// Try to decode `s` as an error response, and return its error.
209///
210/// (Gives an error if this is not an error response)
211//
212// TODO RPC: Eventually we should try to refactor this out if we can; it is only called in one
213// place.
214pub(crate) fn try_decode_response_as_err(s: &str) -> Result<Option<RpcError>, DecodeResponseError> {
215    let Response { body, .. } = serde_json::from_str(s)?;
216    match body {
217        ResponseBody::Error(e) => Ok(Some(e)),
218        _ => Ok(None),
219    }
220}
221
222/// An error sent by Arti, decoded into its parts.
223#[derive(Clone, Debug, Deserialize, Serialize)]
224#[cfg_attr(test, derive(PartialEq, Eq))]
225pub struct RpcError {
226    /// A human-readable message from Arti.
227    message: String,
228    /// An error code representing the underlying problem.
229    code: RpcErrorCode,
230    /// One or more `ErrorKind`s, encoded as strings.
231    kinds: Vec<String>,
232}
233
234impl RpcError {
235    /// Return the human-readable message that Arti sent as part of this error.
236    pub fn message(&self) -> &str {
237        self.message.as_str()
238    }
239    /// Return the numeric error code from this error.
240    pub fn code(&self) -> RpcErrorCode {
241        self.code
242    }
243    /// Return an iterator over the ErrorKinds for this error.
244    //
245    // Note: This is not a great API for FFI purposes.
246    // But FFI code should get errors as a String, so that's probably fine.
247    pub fn kinds_iter(&self) -> impl Iterator<Item = &'_ str> {
248        self.kinds.iter().map(|s| s.as_ref())
249    }
250}
251
252caret::caret_int! {
253    #[derive(serde::Deserialize, serde::Serialize)]
254    pub struct RpcErrorCode(i32) {
255        /// "The JSON sent is not a valid Request object."
256        INVALID_REQUEST = -32600,
257        /// "The method does not exist ."
258        NO_SUCH_METHOD = -32601,
259        /// "Invalid method parameter(s)."
260        INVALID_PARAMS = -32602,
261        /// "The server suffered some kind of internal problem"
262        INTERNAL_ERROR = -32603,
263        /// "Some requested object was not valid"
264        OBJECT_ERROR = 1,
265        /// "Some other error occurred"
266        REQUEST_ERROR = 2,
267        /// This method exists, but wasn't implemented on this object.
268        METHOD_NOT_IMPL = 3,
269    }
270}
271
272#[cfg(test)]
273mod test {
274    // @@ begin test lint list maintained by maint/add_warning @@
275    #![allow(clippy::bool_assert_comparison)]
276    #![allow(clippy::clone_on_copy)]
277    #![allow(clippy::dbg_macro)]
278    #![allow(clippy::mixed_attributes_style)]
279    #![allow(clippy::print_stderr)]
280    #![allow(clippy::print_stdout)]
281    #![allow(clippy::single_char_pattern)]
282    #![allow(clippy::unwrap_used)]
283    #![allow(clippy::unchecked_duration_subtraction)]
284    #![allow(clippy::useless_vec)]
285    #![allow(clippy::needless_pass_by_value)]
286    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
287
288    use super::*;
289
290    /// Helper: Decode a string into a Response, then convert it into
291    /// a ResponseMeta.
292    fn response_meta(s: &str) -> Result<ResponseMeta, DecodeResponseError> {
293        match ResponseMeta::try_from_response(&serde_json::from_str::<Response>(s)?) {
294            Ok(v) => v,
295            Err(_) => {
296                let utf8 = Utf8CString::try_from(s.to_string())
297                    .map_err(|_| DecodeResponseError::ProtocolViolation("not utf8cstr?"))?;
298                Err(DecodeResponseError::Fatal(
299                    ErrorResponse::from_validated_string(utf8),
300                ))
301            }
302        }
303    }
304
305    #[test]
306    fn response_meta_good() {
307        use ResponseKind as RK;
308        use ResponseMeta as RM;
309        for (s, expected) in [
310            (
311                r#"{"id":7, "result": {}}"#,
312                RM {
313                    id: 7.into(),
314                    kind: RK::Success,
315                },
316            ),
317            (
318                r#"{"id":"hi", "update": {"here":["goes", "nothing"]}}"#,
319                RM {
320                    id: "hi".to_string().into(),
321                    kind: RK::Update,
322                },
323            ),
324            (
325                r#"{"id": 6, "error": {"message":"iffy wobbler", "code":999, "kinds": ["BadVibes"]}}"#,
326                RM {
327                    id: 6.into(),
328                    kind: RK::Error,
329                },
330            ),
331            (
332                r#"{"id": 6, "error": {"message":"iffy wobbler", "code":999, "kinds": ["BadVibes"], "data": {"a":"b"}}}"#,
333                RM {
334                    id: 6.into(),
335                    kind: RK::Error,
336                },
337            ),
338        ] {
339            let got = response_meta(s).unwrap();
340            assert_eq!(got, expected);
341        }
342    }
343
344    #[test]
345    fn response_meta_bad() {
346        macro_rules! check_err {
347            { $s:expr, $p:pat } => {
348                let got_err = response_meta($s).unwrap_err();
349                assert!(matches!(got_err, $p));
350            }
351
352        }
353
354        use DecodeResponseError as E;
355
356        // No ID; arti is saying we screwed up.
357        check_err!(
358            r#"{"error": {"message":"iffy wobbler", "code":999, "kinds": ["BadVibes"], "data": {"a":"b"}}}"#,
359            E::Fatal(_)
360        );
361        // Missing ID on a success.
362        check_err!(r#"{"result": {}}"#, E::ProtocolViolation(_));
363        // Missing ID on an update.
364        check_err!(r#"{"update": {}}"#, E::ProtocolViolation(_));
365        // No recognized type.
366        check_err!(r#"{"id": 7, "flupdate": {}}"#, E::JsonProtocolViolation(_));
367        // Couldn't parse.
368        check_err!(r#"{{{{{"#, E::JsonProtocolViolation(_));
369        // Error is no good.
370        check_err!(
371            r#"{"id": 77 "error": {"message":"iffy wobbler"}}"#,
372            E::JsonProtocolViolation(_)
373        );
374    }
375
376    #[test]
377    fn bad_json() {
378        // we rely on the json parser rejecting some things.
379        for s in [
380            "{ ",         // not complete
381            "",           // Empty.
382            "{ \0 }",     // contains nul byte.
383            "{ \"\0\" }", // string contains nul byte.
384        ] {
385            let r: Result<serde_json::Value, _> = serde_json::from_str(s);
386            assert!(dbg!(r.err()).is_some());
387        }
388    }
389
390    #[test]
391    fn re_encode() {
392        let response = r#"{
393            "id": 6,
394            "error": {
395                "message":"iffy wobbler",
396                "code":999,
397                "kinds": ["BadVibes"],
398                "data": {"a":"b"},
399                "explosion": 22
400             },
401             "xyzzy":"plugh"
402        }"#;
403        let json_orig: serde_json::Value = serde_json::from_str(response).unwrap();
404        let resp = UnparsedResponse::new(response.into());
405        let valid = resp.try_validate().unwrap();
406        let msg: &str = valid.msg.as_ref();
407        let json_reencoded: serde_json::Value = serde_json::from_str(msg).unwrap();
408        // To make sure all fields were preserved, we have to compare the json objects for equality;
409        // we cannot rely on the order of the fields.
410        assert_eq!(json_orig, json_reencoded);
411    }
412}