near_api_types/transaction/
result.rs1use std::fmt;
4
5use base64::{Engine as _, engine::general_purpose};
6use borsh;
7use near_openapi_types::{
8 CallResult, ExecutionStatusView, FinalExecutionOutcomeView, FinalExecutionStatus,
9 TxExecutionError, TxExecutionStatus,
10};
11
12use crate::{
13 AccountId, CryptoHash, NearGas, NearToken, Signature,
14 errors::{DataConversionError, ExecutionError},
15 transaction::{SignedTransaction, Transaction},
16};
17
18pub type ExecutionSuccess = ExecutionResult<Value>;
24
25pub type ExecutionFailure = ExecutionResult<TxExecutionError>;
29
30#[non_exhaustive]
35#[must_use = "use `into_result()` to handle potential execution errors"]
36pub struct Execution<T> {
37 pub result: T,
38 pub details: ExecutionFinalResult,
39}
40
41impl<T> Execution<T> {
42 #[track_caller]
43 pub fn assert_success(self) -> T {
44 #[allow(clippy::unwrap_used)]
45 self.into_result().unwrap()
46 }
47
48 #[allow(clippy::result_large_err)]
49 pub fn into_result(self) -> Result<T, ExecutionFailure> {
50 self.details.into_result()?;
51 Ok(self.result)
52 }
53
54 pub const fn is_success(&self) -> bool {
57 self.details.is_success()
58 }
59
60 pub const fn is_failure(&self) -> bool {
63 self.details.is_failure()
64 }
65}
66
67#[derive(Clone)]
71pub(crate) struct ExecutionDetails {
72 pub(crate) transaction_outcome: ExecutionOutcome,
73 pub(crate) transaction: SignedTransaction,
74 pub(crate) receipts: Vec<ExecutionOutcome>,
75}
76
77impl ExecutionDetails {
78 pub const fn outcome(&self) -> &ExecutionOutcome {
80 &self.transaction_outcome
81 }
82
83 pub const fn transaction(&self) -> &Transaction {
84 &self.transaction.transaction
85 }
86
87 pub const fn signature(&self) -> &Signature {
88 &self.transaction.signature
89 }
90
91 pub fn outcomes(&self) -> Vec<&ExecutionOutcome> {
94 let mut outcomes = vec![&self.transaction_outcome];
95 outcomes.extend(self.receipt_outcomes());
96 outcomes
97 }
98
99 pub fn receipt_outcomes(&self) -> &[ExecutionOutcome] {
102 &self.receipts
103 }
104
105 pub fn failures(&self) -> Vec<&ExecutionOutcome> {
108 let mut failures = Vec::new();
109 if matches!(
110 self.transaction_outcome.status,
111 ExecutionStatusView::Failure(_)
112 ) {
113 failures.push(&self.transaction_outcome);
114 }
115 failures.extend(self.receipt_failures());
116 failures
117 }
118
119 pub fn receipt_failures(&self) -> Vec<&ExecutionOutcome> {
121 self.receipts
122 .iter()
123 .filter(|receipt| matches!(receipt.status, ExecutionStatusView::Failure(_)))
124 .collect()
125 }
126
127 pub fn logs(&self) -> Vec<&str> {
129 self.outcomes()
130 .iter()
131 .flat_map(|outcome| &outcome.logs)
132 .map(String::as_str)
133 .collect()
134 }
135}
136
137#[derive(Clone)]
140#[non_exhaustive]
141pub struct ExecutionResult<T> {
142 pub total_gas_burnt: NearGas,
144
145 pub(crate) value: T,
148 pub(crate) details: ExecutionDetails,
151}
152
153impl<T: fmt::Debug> fmt::Debug for ExecutionResult<T> {
154 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
155 f.debug_struct("ExecutionResult")
156 .field("total_gas_burnt", &self.total_gas_burnt)
157 .field("transaction", &self.details.transaction)
158 .field("receipts", &self.details.receipts)
159 .field("value", &self.value)
160 .finish()
161 }
162}
163
164impl fmt::Display for ExecutionResult<TxExecutionError> {
165 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
166 write!(f, "ExecutionFailure: {:?}", self.value)
167 }
168}
169
170impl std::error::Error for ExecutionResult<TxExecutionError> {}
173
174#[derive(Clone)]
177#[must_use = "use `into_result()` to handle potential execution errors"]
178pub struct ExecutionFinalResult {
179 pub total_gas_burnt: NearGas,
181
182 pub(crate) status: FinalExecutionStatus,
183 pub(crate) details: ExecutionDetails,
184}
185
186impl fmt::Debug for ExecutionFinalResult {
187 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
188 f.debug_struct("ExecutionFinalResult")
189 .field("total_gas_burnt", &self.total_gas_burnt)
190 .field("transaction", &self.details.transaction)
191 .field("receipts", &self.details.receipts)
192 .field("status", &self.status)
193 .finish()
194 }
195}
196
197impl TryFrom<FinalExecutionOutcomeView> for ExecutionFinalResult {
198 type Error = DataConversionError;
199 fn try_from(view: FinalExecutionOutcomeView) -> Result<Self, Self::Error> {
200 let FinalExecutionOutcomeView {
201 receipts_outcome,
202 status,
203 transaction,
204 transaction_outcome,
205 } = view;
206
207 let total_gas_burnt = transaction_outcome.outcome.gas_burnt.as_gas()
208 + receipts_outcome
209 .iter()
210 .map(|t| t.outcome.gas_burnt.as_gas())
211 .sum::<u64>();
212
213 let transaction_outcome = transaction_outcome.into();
214 let receipts = receipts_outcome
215 .into_iter()
216 .map(ExecutionOutcome::from)
217 .collect();
218
219 let total_gas_burnt = NearGas::from_gas(total_gas_burnt);
220 Ok(Self {
221 total_gas_burnt,
222 status,
223 details: ExecutionDetails {
224 transaction_outcome,
225 transaction: SignedTransaction::try_from(transaction)?,
226 receipts,
227 },
228 })
229 }
230}
231
232impl ExecutionFinalResult {
233 #[allow(clippy::result_large_err)]
235 pub fn into_result(self) -> Result<ExecutionSuccess, ExecutionFailure> {
236 match self.status {
237 FinalExecutionStatus::SuccessValue(value) => Ok(ExecutionResult {
238 total_gas_burnt: self.total_gas_burnt,
239 value: Value::from_string(value),
240 details: self.details,
241 }),
242 FinalExecutionStatus::Failure(tx_error) => Err(ExecutionResult {
243 total_gas_burnt: self.total_gas_burnt,
244 value: tx_error,
245 details: self.details,
246 }),
247 FinalExecutionStatus::NotStarted | FinalExecutionStatus::Started => {
248 panic!(
249 "called `into_result()` on a transaction that is still pending \
250 (status: {:?}). Use `is_pending()` to check before calling this method, \
251 or use `Transaction::status_with_options()` with a `wait_until` value \
252 of `ExecutedOptimistic` or higher.",
253 self.status
254 )
255 }
256 }
257 }
258
259 #[track_caller]
264 pub fn assert_success(self) -> ExecutionSuccess {
265 #[allow(clippy::unwrap_used)]
266 self.into_result().unwrap()
267 }
268
269 #[track_caller]
270 pub fn assert_failure(self) -> ExecutionResult<TxExecutionError> {
271 #[allow(clippy::unwrap_used)]
272 self.into_result().unwrap_err()
273 }
274
275 pub fn json<T: serde::de::DeserializeOwned>(self) -> Result<T, ExecutionError> {
280 if self.is_pending() {
281 return Err(ExecutionError::ExecutionPendingOrUnknown);
282 }
283
284 let val = self.into_result()?;
285 match val.json() {
286 Err(err) => {
287 if matches!(
290 err,
291 ExecutionError::DataConversionError(
292 DataConversionError::JsonDeserializationError(_)
293 )
294 ) && val.value.repr.is_empty()
295 {
296 return Err(ExecutionError::EofWhileParsingValue);
297 }
298
299 Err(err)
300 }
301 ok => ok,
302 }
303 }
304
305 pub fn borsh<T: borsh::BorshDeserialize>(self) -> Result<T, ExecutionError> {
309 if self.is_pending() {
310 return Err(ExecutionError::ExecutionPendingOrUnknown);
311 }
312
313 self.into_result()?.borsh()
314 }
315
316 pub fn raw_bytes(self) -> Result<Vec<u8>, ExecutionError> {
320 if self.is_pending() {
321 return Err(ExecutionError::ExecutionPendingOrUnknown);
322 }
323
324 self.into_result()?.raw_bytes()
325 }
326
327 pub const fn is_success(&self) -> bool {
330 matches!(self.status, FinalExecutionStatus::SuccessValue(_))
331 }
332
333 pub const fn is_failure(&self) -> bool {
336 matches!(self.status, FinalExecutionStatus::Failure(_))
337 }
338
339 pub const fn is_pending(&self) -> bool {
346 matches!(
347 self.status,
348 FinalExecutionStatus::NotStarted | FinalExecutionStatus::Started
349 )
350 }
351
352 pub const fn outcome(&self) -> &ExecutionOutcome {
354 self.details.outcome()
355 }
356
357 pub const fn transaction(&self) -> &Transaction {
359 self.details.transaction()
360 }
361
362 pub fn outcomes(&self) -> Vec<&ExecutionOutcome> {
365 self.details.outcomes()
366 }
367
368 pub fn receipt_outcomes(&self) -> &[ExecutionOutcome] {
371 self.details.receipt_outcomes()
372 }
373
374 pub fn failures(&self) -> Vec<&ExecutionOutcome> {
377 self.details.failures()
378 }
379
380 pub fn receipt_failures(&self) -> Vec<&ExecutionOutcome> {
382 self.details.receipt_failures()
383 }
384
385 pub fn logs(&self) -> Vec<&str> {
387 self.details.logs()
388 }
389}
390
391#[derive(Clone, Debug)]
401#[must_use = "use `into_result()` to handle potential execution errors and cases when transaction is pending"]
402pub enum TransactionResult {
403 Pending { status: TxExecutionStatus },
408 Full(Box<ExecutionFinalResult>),
410}
411
412impl TransactionResult {
413 #[allow(clippy::result_large_err)]
415 pub fn into_result(self) -> Result<ExecutionSuccess, TransactionResultError> {
416 match self {
417 Self::Full(result) => result
418 .into_result()
419 .map_err(|e| TransactionResultError::Failure(Box::new(e))),
420 Self::Pending { status } => Err(TransactionResultError::Pending(status)),
421 }
422 }
423
424 #[track_caller]
426 pub fn assert_success(self) -> ExecutionSuccess {
427 match self {
428 Self::Full(result) => result.assert_success(),
429 Self::Pending { status } => panic!(
430 "called `assert_success()` on a pending transaction (status: {status:?}). \
431 Use wait_until(TxExecutionStatus::Final) or handle the pending case."
432 ),
433 }
434 }
435
436 pub const fn is_full(&self) -> bool {
438 matches!(self, Self::Full(_))
439 }
440
441 pub const fn is_pending(&self) -> bool {
443 matches!(self, Self::Pending { .. })
444 }
445
446 pub fn into_full(self) -> Option<ExecutionFinalResult> {
448 match self {
449 Self::Full(result) => Some(*result),
450 Self::Pending { .. } => None,
451 }
452 }
453
454 pub fn pending_status(self) -> Option<TxExecutionStatus> {
456 match self {
457 Self::Pending { status } => Some(status),
458 Self::Full(_) => None,
459 }
460 }
461
462 #[track_caller]
464 pub fn assert_failure(self) -> ExecutionResult<TxExecutionError> {
465 match self {
466 Self::Full(result) => result.assert_failure(),
467 Self::Pending { status } => panic!(
468 "called `assert_failure()` on a pending transaction (status: {status:?}). \
469 Use wait_until(TxExecutionStatus::Final) or handle the pending case."
470 ),
471 }
472 }
473
474 pub const fn is_failure(&self) -> bool {
477 match self {
478 Self::Full(result) => result.is_failure(),
479 Self::Pending { .. } => false,
480 }
481 }
482
483 pub const fn is_success(&self) -> bool {
486 match self {
487 Self::Full(result) => result.is_success(),
488 Self::Pending { .. } => false,
489 }
490 }
491
492 #[track_caller]
498 pub fn transaction(&self) -> &Transaction {
499 match self {
500 Self::Full(result) => result.transaction(),
501 Self::Pending { status } => panic!(
502 "called `transaction()` on a pending transaction (status: {status:?}). \
503 Use wait_until(TxExecutionStatus::Final) or handle the pending case."
504 ),
505 }
506 }
507
508 #[track_caller]
514 pub fn logs(&self) -> Vec<&str> {
515 match self {
516 Self::Full(result) => result.logs(),
517 Self::Pending { status } => panic!(
518 "called `logs()` on a pending transaction (status: {status:?}). \
519 Use wait_until(TxExecutionStatus::Final) or handle the pending case."
520 ),
521 }
522 }
523}
524
525#[derive(Debug)]
527pub enum TransactionResultError {
528 Failure(Box<ExecutionFailure>),
530 Pending(TxExecutionStatus),
532}
533
534impl fmt::Display for TransactionResultError {
535 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
536 match self {
537 Self::Failure(err) => write!(f, "Transaction failed: {err}"),
538 Self::Pending(status) => write!(
539 f,
540 "Transaction is pending (status: {status:?}). \
541 Execution results are not yet available."
542 ),
543 }
544 }
545}
546
547impl std::error::Error for TransactionResultError {}
548
549impl ExecutionSuccess {
550 pub fn json<T: serde::de::DeserializeOwned>(&self) -> Result<T, ExecutionError> {
555 Ok(self.value.json()?)
556 }
557
558 pub fn borsh<T: borsh::BorshDeserialize>(&self) -> Result<T, ExecutionError> {
562 Ok(self.value.borsh()?)
563 }
564
565 pub fn raw_bytes(&self) -> Result<Vec<u8>, ExecutionError> {
569 Ok(self.value.raw_bytes()?)
570 }
571}
572
573impl<T> ExecutionResult<T> {
574 pub const fn outcome(&self) -> &ExecutionOutcome {
576 self.details.outcome()
577 }
578
579 pub const fn transaction(&self) -> &Transaction {
581 self.details.transaction()
582 }
583
584 pub const fn signature(&self) -> &Signature {
585 self.details.signature()
586 }
587
588 pub fn outcomes(&self) -> Vec<&ExecutionOutcome> {
591 self.details.outcomes()
592 }
593
594 pub fn receipt_outcomes(&self) -> &[ExecutionOutcome] {
597 self.details.receipt_outcomes()
598 }
599
600 pub fn failures(&self) -> Vec<&ExecutionOutcome> {
603 self.details.failures()
604 }
605
606 pub fn receipt_failures(&self) -> Vec<&ExecutionOutcome> {
608 self.details.receipt_failures()
609 }
610
611 pub fn logs(&self) -> Vec<&str> {
613 self.details.logs()
614 }
615}
616
617#[derive(PartialEq, Eq, Clone, Debug)]
621#[non_exhaustive]
622pub struct ViewResultDetails {
623 pub result: Vec<u8>,
625 pub logs: Vec<String>,
627}
628
629impl ViewResultDetails {
630 pub fn json<T: serde::de::DeserializeOwned>(&self) -> Result<T, DataConversionError> {
635 Ok(serde_json::from_slice(&self.result)?)
636 }
637
638 pub fn borsh<T: borsh::BorshDeserialize>(&self) -> Result<T, DataConversionError> {
642 Ok(borsh::BorshDeserialize::try_from_slice(&self.result)?)
643 }
644}
645
646impl From<CallResult> for ViewResultDetails {
647 fn from(result: CallResult) -> Self {
648 Self {
649 result: result.result,
650 logs: result.logs,
651 }
652 }
653}
654
655#[derive(Clone, Debug)]
658#[non_exhaustive]
659pub struct ExecutionOutcome {
660 pub transaction_hash: CryptoHash,
662 pub block_hash: CryptoHash,
664 pub logs: Vec<String>,
666 pub receipt_ids: Vec<CryptoHash>,
668 pub gas_burnt: NearGas,
670 pub tokens_burnt: NearToken,
674 pub executor_id: AccountId,
677
678 pub(crate) status: ExecutionStatusView,
680}
681
682impl ExecutionOutcome {
683 pub const fn is_success(&self) -> bool {
686 matches!(
687 self.status,
688 ExecutionStatusView::SuccessValue(_) | ExecutionStatusView::SuccessReceiptId(_)
689 )
690 }
691
692 pub const fn is_failure(&self) -> bool {
695 matches!(
696 self.status,
697 ExecutionStatusView::Failure(_) | ExecutionStatusView::Unknown
698 )
699 }
700
701 pub fn into_result(self) -> Result<ValueOrReceiptId, ExecutionError> {
704 match self.status {
705 ExecutionStatusView::SuccessValue(value) => {
706 Ok(ValueOrReceiptId::Value(Value::from_string(value)))
707 }
708 ExecutionStatusView::SuccessReceiptId(hash) => {
709 Ok(ValueOrReceiptId::ReceiptId(hash.into()))
710 }
711 ExecutionStatusView::Failure(err) => {
712 Err(ExecutionError::TransactionExecutionFailed(Box::new(err)))
713 }
714 ExecutionStatusView::Unknown => Err(ExecutionError::ExecutionPendingOrUnknown),
715 }
716 }
717}
718
719#[derive(Debug)]
721pub enum ValueOrReceiptId {
722 Value(Value),
724 ReceiptId(CryptoHash),
727}
728
729#[derive(Debug, Clone)]
733pub struct Value {
734 repr: String,
735}
736
737impl Value {
738 const fn from_string(value: String) -> Self {
739 Self { repr: value }
740 }
741
742 pub fn json<T: serde::de::DeserializeOwned>(&self) -> Result<T, DataConversionError> {
747 let buf = self.raw_bytes()?;
748 Ok(serde_json::from_slice(&buf)?)
749 }
750
751 pub fn borsh<T: borsh::BorshDeserialize>(&self) -> Result<T, DataConversionError> {
755 let buf = self.raw_bytes()?;
756 Ok(borsh::BorshDeserialize::try_from_slice(&buf)?)
757 }
758
759 pub fn raw_bytes(&self) -> Result<Vec<u8>, DataConversionError> {
766 Ok(general_purpose::STANDARD.decode(&self.repr)?)
767 }
768}
769
770impl From<near_openapi_types::ExecutionOutcomeWithIdView> for ExecutionOutcome {
771 fn from(view: near_openapi_types::ExecutionOutcomeWithIdView) -> Self {
772 let near_openapi_types::ExecutionOutcomeWithIdView {
773 id,
774 block_hash,
775 outcome,
776 proof: _, } = view;
778
779 Self {
780 transaction_hash: id.into(),
781 block_hash: block_hash.into(),
782 logs: outcome.logs,
783 receipt_ids: outcome
784 .receipt_ids
785 .into_iter()
786 .map(CryptoHash::from)
787 .collect(),
788 gas_burnt: outcome.gas_burnt,
789 tokens_burnt: outcome.tokens_burnt,
790 executor_id: outcome.executor_id,
791 status: outcome.status,
792 }
793 }
794}