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}