bc_envelope/extension/expressions/
response.rs

1use core::panic;
2
3use anyhow::{Error, Result, bail};
4use bc_components::{ARID, tags};
5use dcbor::prelude::*;
6
7use crate::{Envelope, EnvelopeEncodable, KnownValue, known_values};
8
9/// A `Response` represents a reply to a `Request` containing either a
10/// successful result or an error.
11///
12/// Responses are part of the expression system that enables distributed
13/// function calls. Each response contains:
14/// - A reference to the original request's ID (ARID) for correlation
15/// - Either a successful result or an error message
16///
17/// The `Response` type is implemented as a wrapper around `Result<(ARID,
18/// Envelope), (Option<ARID>, Envelope)>`, where the `Ok` variant represents a
19/// successful response and the `Err` variant represents an error response.
20///
21/// When serialized to an envelope, responses are tagged with `#6.40011`
22/// (TAG_RESPONSE).
23///
24/// # Examples
25///
26/// ```
27/// use bc_components::ARID;
28/// use bc_envelope::prelude::*;
29///
30/// // Create a request ID (normally this would come from the original request)
31/// let request_id = ARID::new();
32///
33/// // Create a successful response
34/// let success_response =
35///     Response::new_success(request_id).with_result("Transaction completed");
36///
37/// // Create an error response
38/// let error_response =
39///     Response::new_failure(request_id).with_error("Insufficient funds");
40///
41/// // Convert to envelopes
42/// let success_envelope = success_response.into_envelope();
43/// let error_envelope = error_response.into_envelope();
44/// ```
45#[derive(Debug, Clone, PartialEq)]
46pub struct Response(Result<(ARID, Envelope), (Option<ARID>, Envelope)>);
47
48impl std::fmt::Display for Response {
49    /// Formats the response for display, showing its ID and result or error.
50    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
51        write!(f, "Response({})", self.summary())
52    }
53}
54
55impl Response {
56    /// Returns a human-readable summary of the response.
57    pub fn summary(&self) -> String {
58        match &self.0 {
59            Ok((id, result)) => format!(
60                "id: {}, result: {}",
61                id.short_description(),
62                result.format_flat()
63            ),
64            Err((id, error)) => {
65                if let Some(id) = id {
66                    format!(
67                        "id: {} error: {}",
68                        id.short_description(),
69                        error.format_flat()
70                    )
71                } else {
72                    format!("id: 'Unknown' error: {}", error.format_flat())
73                }
74            }
75        }
76    }
77}
78
79impl Envelope {
80    /// Creates an envelope containing the 'Unknown' known value.
81    ///
82    /// This is used when representing an unknown error or value.
83    pub fn unknown() -> Self { known_values::UNKNOWN_VALUE.into_envelope() }
84
85    /// Creates an envelope containing the 'OK' known value.
86    ///
87    /// This is used when a response doesn't need to return any specific value,
88    /// just an acknowledgment that the request was successful.
89    pub fn ok() -> Self { known_values::OK_VALUE.into_envelope() }
90}
91
92impl Response {
93    //
94    // Success Composition
95    //
96
97    /// Creates a new successful response with the specified request ID.
98    ///
99    /// By default, the result will be the 'OK' known value. Use `with_result`
100    /// to set a specific result value.
101    ///
102    /// # Arguments
103    ///
104    /// * `id` - The ID of the request this response corresponds to
105    ///
106    /// # Examples
107    ///
108    /// ```
109    /// use bc_components::ARID;
110    /// use bc_envelope::prelude::*;
111    ///
112    /// let request_id = ARID::new();
113    /// let response = Response::new_success(request_id);
114    /// ```
115    pub fn new_success(id: ARID) -> Self { Self(Ok((id, Envelope::ok()))) }
116
117    //
118    // Failure Composition
119    //
120
121    /// Creates a new failure response with the specified request ID.
122    ///
123    /// By default, the error will be the 'Unknown' known value. Use
124    /// `with_error` to set a specific error message.
125    ///
126    /// # Arguments
127    ///
128    /// * `id` - The ID of the request this response corresponds to
129    ///
130    /// # Examples
131    ///
132    /// ```
133    /// use bc_components::ARID;
134    /// use bc_envelope::prelude::*;
135    ///
136    /// let request_id = ARID::new();
137    /// let response =
138    ///     Response::new_failure(request_id).with_error("Operation failed");
139    /// ```
140    pub fn new_failure(id: ARID) -> Self {
141        Self(Err((Some(id), Envelope::unknown())))
142    }
143
144    /// Creates a new early failure response without a request ID.
145    ///
146    /// An early failure occurs when the error happens before the request
147    /// has been fully processed, so the request ID is not known.
148    ///
149    /// # Examples
150    ///
151    /// ```
152    /// use bc_envelope::prelude::*;
153    ///
154    /// let response =
155    ///     Response::new_early_failure().with_error("Authentication failed");
156    /// ```
157    pub fn new_early_failure() -> Self {
158        Self(Err((None, Envelope::unknown())))
159    }
160}
161
162/// Trait that defines the behavior of a response.
163///
164/// This trait provides methods for composing responses with results or errors,
165/// and for extracting information from responses.
166pub trait ResponseBehavior {
167    //
168    // Success Composition
169    //
170
171    /// Sets the result value for a successful response.
172    ///
173    /// # Panics
174    ///
175    /// This method will panic if called on a failure response.
176    fn with_result(self, result: impl EnvelopeEncodable) -> Self;
177
178    /// Sets the result value for a successful response if provided,
179    /// otherwise sets the result to null.
180    ///
181    /// # Panics
182    ///
183    /// This method will panic if called on a failure response.
184    fn with_optional_result(
185        self,
186        result: Option<impl EnvelopeEncodable>,
187    ) -> Self;
188
189    //
190    // Failure Composition
191    //
192
193    /// Sets the error value for a failure response.
194    ///
195    /// # Panics
196    ///
197    /// This method will panic if called on a successful response.
198    fn with_error(self, error: impl EnvelopeEncodable) -> Self;
199
200    /// Sets the error value for a failure response if provided,
201    /// otherwise leaves the error as the default 'Unknown' value.
202    ///
203    /// # Panics
204    ///
205    /// This method will panic if called on a successful response.
206    fn with_optional_error(self, error: Option<impl EnvelopeEncodable>)
207    -> Self;
208
209    //
210    // Parsing
211    //
212
213    /// Returns true if this is a successful response.
214    fn is_ok(&self) -> bool;
215
216    /// Returns true if this is a failure response.
217    fn is_err(&self) -> bool;
218
219    /// Returns a reference to the ID and result if this is a successful
220    /// response.
221    fn ok(&self) -> Option<&(ARID, Envelope)>;
222
223    /// Returns a reference to the ID (if known) and error if this is a failure
224    /// response.
225    fn err(&self) -> Option<&(Option<ARID>, Envelope)>;
226
227    /// Returns the ID of the request this response corresponds to, if known.
228    fn id(&self) -> Option<ARID>;
229
230    /// Returns the ID of the request this response corresponds to.
231    ///
232    /// # Panics
233    ///
234    /// This method will panic if the ID is not known.
235    fn expect_id(&self) -> ARID { self.id().expect("Expected an ID") }
236
237    /// Returns a reference to the result value if this is a successful
238    /// response.
239    ///
240    /// # Errors
241    ///
242    /// Returns an error if this is a failure response.
243    fn result(&self) -> Result<&Envelope> {
244        self.ok()
245            .map(|(_, result)| result)
246            .ok_or_else(|| Error::msg("Cannot get result from failed response"))
247    }
248
249    /// Extracts a typed result value from a successful response.
250    ///
251    /// # Errors
252    ///
253    /// Returns an error if this is a failure response or if the result
254    /// cannot be converted to the requested type.
255    fn extract_result<T>(&self) -> Result<T>
256    where
257        T: TryFrom<CBOR, Error = dcbor::Error> + 'static,
258    {
259        self.result()?.extract_subject()
260    }
261
262    /// Returns a reference to the error value if this is a failure response.
263    ///
264    /// # Errors
265    ///
266    /// Returns an error if this is a successful response.
267    fn error(&self) -> Result<&Envelope> {
268        self.err().map(|(_, error)| error).ok_or_else(|| {
269            Error::msg("Cannot get error from successful response")
270        })
271    }
272
273    /// Extracts a typed error value from a failure response.
274    ///
275    /// # Errors
276    ///
277    /// Returns an error if this is a successful response or if the error
278    /// cannot be converted to the requested type.
279    fn extract_error<T>(&self) -> Result<T>
280    where
281        T: TryFrom<CBOR, Error = dcbor::Error> + 'static,
282    {
283        self.error()?.extract_subject()
284    }
285}
286
287impl ResponseBehavior for Response {
288    fn with_result(mut self, result: impl EnvelopeEncodable) -> Self {
289        match self.0 {
290            Ok(_) => {
291                self.0 = Ok((self.0.unwrap().0, result.into_envelope()));
292                self
293            }
294            Err(_) => {
295                panic!("Cannot set result on a failed response");
296            }
297        }
298    }
299
300    fn with_optional_result(
301        self,
302        result: Option<impl EnvelopeEncodable>,
303    ) -> Self {
304        if let Some(result) = result {
305            return self.with_result(result);
306        }
307        self.with_result(Envelope::null())
308    }
309
310    fn with_error(mut self, error: impl EnvelopeEncodable) -> Self {
311        match self.0 {
312            Ok(_) => {
313                panic!("Cannot set error on a successful response");
314            }
315            Err(_) => {
316                self.0 = Err((self.0.err().unwrap().0, error.into_envelope()));
317                self
318            }
319        }
320    }
321
322    fn with_optional_error(
323        self,
324        error: Option<impl EnvelopeEncodable>,
325    ) -> Self {
326        if let Some(error) = error {
327            return self.with_error(error);
328        }
329        self
330    }
331
332    fn is_ok(&self) -> bool { self.0.is_ok() }
333
334    fn is_err(&self) -> bool { self.0.is_err() }
335
336    fn ok(&self) -> Option<&(ARID, Envelope)> { self.0.as_ref().ok() }
337
338    fn err(&self) -> Option<&(Option<ARID>, Envelope)> { self.0.as_ref().err() }
339
340    fn id(&self) -> Option<ARID> {
341        match self.0 {
342            Ok((id, _)) => Some(id),
343            Err((id, _)) => id,
344        }
345    }
346}
347
348/// Converts a `Response` to an `Envelope`.
349///
350/// Successful responses have the request ID as the subject and a 'result'
351/// assertion. Failure responses have the request ID (or 'Unknown' if not known)
352/// as the subject and an 'error' assertion.
353impl From<Response> for Envelope {
354    fn from(value: Response) -> Self {
355        match value.0 {
356            Ok((id, result)) => {
357                Envelope::new(CBOR::to_tagged_value(tags::TAG_RESPONSE, id))
358                    .add_assertion(known_values::RESULT, result)
359            }
360            Err((id, error)) => {
361                let subject: Envelope;
362                if let Some(id) = id {
363                    subject = Envelope::new(CBOR::to_tagged_value(
364                        tags::TAG_RESPONSE,
365                        id,
366                    ));
367                } else {
368                    subject = Envelope::new(CBOR::to_tagged_value(
369                        tags::TAG_RESPONSE,
370                        known_values::UNKNOWN_VALUE,
371                    ));
372                }
373                subject.add_assertion(known_values::ERROR, error)
374            }
375        }
376    }
377}
378
379/// Converts an `Envelope` to a `Response`.
380///
381/// The envelope must have a TAG_RESPONSE-tagged subject and either a 'result'
382/// or 'error' assertion (but not both).
383impl TryFrom<Envelope> for Response {
384    type Error = Error;
385
386    fn try_from(envelope: Envelope) -> Result<Self> {
387        let result = envelope.assertion_with_predicate(known_values::RESULT);
388        let error = envelope.assertion_with_predicate(known_values::ERROR);
389
390        if result.is_ok() == error.is_ok() {
391            bail!(crate::Error::InvalidResponse);
392        }
393
394        if result.is_ok() {
395            let id = envelope
396                .subject()
397                .try_leaf()?
398                .try_into_expected_tagged_value(tags::TAG_RESPONSE)?
399                .try_into()?;
400            let result = envelope.object_for_predicate(known_values::RESULT)?;
401            return Ok(Response(Ok((id, result))));
402        }
403
404        if error.is_ok() {
405            let id_value = envelope
406                .subject()
407                .try_leaf()?
408                .try_into_expected_tagged_value(tags::TAG_RESPONSE)?;
409            let known_value = KnownValue::try_from(id_value.clone());
410            let id: Option<ARID>;
411            if let Ok(known_value) = known_value {
412                if known_value == known_values::UNKNOWN_VALUE {
413                    id = None;
414                } else {
415                    bail!(crate::Error::InvalidResponse);
416                }
417            } else {
418                id = Some(id_value.try_into()?);
419            }
420            let error = envelope.object_for_predicate(known_values::ERROR)?;
421            return Ok(Response(Err((id, error))));
422        }
423
424        bail!(crate::Error::InvalidResponse)
425    }
426}
427
428#[cfg(test)]
429mod tests {
430    use hex_literal::hex;
431    use indoc::indoc;
432
433    use super::*;
434
435    fn request_id() -> ARID {
436        ARID::from_data(hex!(
437            "c66be27dbad7cd095ca77647406d07976dc0f35f0d4d654bb0e96dd227a1e9fc"
438        ))
439    }
440
441    #[test]
442    fn test_success_ok() -> Result<()> {
443        crate::register_tags();
444
445        let response = Response::new_success(request_id());
446        let envelope: Envelope = response.clone().into();
447
448        //println!("{}", envelope.format());
449        #[rustfmt::skip]
450        assert_eq!(envelope.format(), (indoc! {r#"
451            response(ARID(c66be27d)) [
452                'result': 'OK'
453            ]
454        "#}).trim());
455
456        let parsed_response = Response::try_from(envelope)?;
457        assert!(parsed_response.is_ok());
458        assert_eq!(parsed_response.expect_id(), request_id());
459        assert_eq!(
460            parsed_response.extract_result::<KnownValue>()?,
461            known_values::OK_VALUE
462        );
463        assert_eq!(response, parsed_response);
464
465        Ok(())
466    }
467
468    #[test]
469    fn test_success_result() -> Result<()> {
470        crate::register_tags();
471
472        let response =
473            Response::new_success(request_id()).with_result("It works!");
474        let envelope: Envelope = response.clone().into();
475
476        //println!("{}", envelope.format());
477        #[rustfmt::skip]
478        assert_eq!(envelope.format(), (indoc! {r#"
479            response(ARID(c66be27d)) [
480                'result': "It works!"
481            ]
482        "#}).trim());
483
484        let parsed_response = Response::try_from(envelope)?;
485        assert!(parsed_response.is_ok());
486        assert_eq!(parsed_response.expect_id(), request_id());
487        assert_eq!(parsed_response.extract_result::<String>()?, "It works!");
488        assert_eq!(response, parsed_response);
489
490        Ok(())
491    }
492
493    #[test]
494    fn test_early_failure() -> Result<()> {
495        crate::register_tags();
496
497        let response = Response::new_early_failure();
498        let envelope: Envelope = response.clone().into();
499
500        // println!("{}", envelope.format());
501        #[rustfmt::skip]
502        assert_eq!(envelope.format(), (indoc! {r#"
503            response('Unknown') [
504                'error': 'Unknown'
505            ]
506        "#}).trim());
507
508        let parsed_response = Response::try_from(envelope)?;
509        assert!(parsed_response.is_err());
510        assert_eq!(parsed_response.id(), None);
511        assert_eq!(
512            parsed_response.extract_error::<KnownValue>()?,
513            known_values::UNKNOWN_VALUE
514        );
515        assert_eq!(response, parsed_response);
516
517        Ok(())
518    }
519
520    #[test]
521    fn test_failure() -> Result<()> {
522        crate::register_tags();
523
524        let response =
525            Response::new_failure(request_id()).with_error("It doesn't work!");
526        let envelope: Envelope = response.clone().into();
527
528        // println!("{}", envelope.format());
529        #[rustfmt::skip]
530        assert_eq!(envelope.format(), (indoc! {r#"
531            response(ARID(c66be27d)) [
532                'error': "It doesn't work!"
533            ]
534        "#}).trim());
535
536        let parsed_response = Response::try_from(envelope)?;
537        assert!(parsed_response.is_err());
538        assert_eq!(parsed_response.id(), Some(request_id()));
539        assert_eq!(
540            parsed_response.extract_error::<String>()?,
541            "It doesn't work!"
542        );
543        assert_eq!(response, parsed_response);
544
545        Ok(())
546    }
547}