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}