bc_envelope/extension/expressions/
response.rs

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