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}