bc_envelope/extension/expressions/
response.rs

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