1use std::borrow::Cow;
7
8pub use iota_grpc_types::{
9 field::FieldMask, field_mask_normalize, google::rpc::Status as RpcStatus,
10 proto::TryFromProtoError,
11};
12use iota_grpc_types::{
13 proto::GrpcConversionError,
14 v1::{
15 bcs::BcsData,
16 ledger_service::{ObjectResult, TransactionResult, object_result, transaction_result},
17 object::Object as ProtoObject,
18 transaction::{ExecutedTransaction, Transaction as ProtoTransaction},
19 transaction_execution_service::{
20 ExecuteTransactionResult, SimulateTransactionResult, SimulatedTransaction,
21 execute_transaction_result, simulate_transaction_result,
22 },
23 types::ObjectId as ProtoObjectId,
24 },
25};
26use iota_types::{ObjectId, TransactionDigest, Version};
27use serde::Serialize;
28
29use super::MetadataEnvelope;
30
31#[derive(Debug, thiserror::Error)]
33#[non_exhaustive]
34pub enum Error {
35 #[error("proto conversion error: {0}")]
37 ProtoConversion(#[from] Box<TryFromProtoError>),
38
39 #[error("server error (code {code}): {msg}", code = .0.code, msg = .0.message)]
42 Server(RpcStatus),
43
44 #[error("protocol error: {0}")]
46 Protocol(ProtocolError),
47
48 #[error("signature conversion error: {0}")]
50 Signature(GrpcConversionError),
51
52 #[error("empty request: at least one item must be provided")]
54 EmptyRequest,
55
56 #[error("stream ended unexpectedly: server indicated more results with has_next=true")]
58 UnexpectedEndOfStream,
59
60 #[error("grpc error: {0}")]
62 Grpc(Box<tonic::Status>),
63}
64
65impl Error {
66 pub fn is_not_found(&self) -> bool {
78 match self {
79 Error::Server(status) => status.code == i32::from(tonic::Code::NotFound),
80 Error::Grpc(status) => status.code() == tonic::Code::NotFound,
81 _ => false,
82 }
83 }
84}
85
86impl From<TryFromProtoError> for Error {
87 fn from(err: TryFromProtoError) -> Self {
88 Error::ProtoConversion(Box::new(err))
89 }
90}
91
92impl From<tonic::Status> for Error {
93 fn from(status: tonic::Status) -> Self {
94 Error::Grpc(Box::new(status))
95 }
96}
97
98impl From<Error> for tonic::Status {
99 fn from(err: Error) -> Self {
100 match err {
101 Error::ProtoConversion(e) => {
102 tonic::Status::internal(format!("proto conversion error: {e}"))
103 }
104 Error::Server(status) => status.to_tonic_status(),
105 Error::Protocol(err) => tonic::Status::internal(format!("protocol error: {err}")),
106 Error::Signature(err) => {
107 tonic::Status::internal(format!("signature conversion error: {err}"))
108 }
109 Error::EmptyRequest => {
110 tonic::Status::invalid_argument("empty request: at least one item must be provided")
111 }
112 Error::UnexpectedEndOfStream => {
113 tonic::Status::internal("stream ended unexpectedly: has_next was true")
114 }
115 Error::Grpc(status) => *status,
116 }
117 }
118}
119
120#[derive(Debug, thiserror::Error)]
122#[non_exhaustive]
123pub enum ProtocolError {
124 #[error("unknown {0} variant")]
126 UnknownVariant(&'static str),
127
128 #[error("empty response field: {0}")]
130 EmptyResponseField(&'static str),
131
132 #[error("checkpoint stream error: {0}")]
134 CheckpointStream(#[from] CheckpointStreamError),
135
136 #[error("expected {expected} results, got {actual}")]
139 UnexpectedResultCount { expected: usize, actual: usize },
140
141 #[error("requested object {expected} at position {position}, but got {actual}")]
144 UnexpectedObject {
145 position: usize,
146 expected: ObjectId,
147 actual: ObjectId,
148 },
149
150 #[error("requested transaction {expected} at position {position}, but got {actual}")]
153 UnexpectedTransaction {
154 position: usize,
155 expected: TransactionDigest,
156 actual: TransactionDigest,
157 },
158}
159
160#[derive(Debug, thiserror::Error)]
162#[non_exhaustive]
163pub enum CheckpointStreamError {
164 #[error("received {data_kind} before checkpoint header")]
166 DataBeforeHeader { data_kind: &'static str },
167
168 #[error("new checkpoint header before previous completed")]
170 IncompleteCheckpoint,
171
172 #[error("end marker sequence number {actual} does not match checkpoint {expected}")]
174 SequenceNumberMismatch { expected: u64, actual: u64 },
175
176 #[error("unknown checkpoint data payload type")]
178 UnknownPayload,
179
180 #[error("stream ended with incomplete data for checkpoint {sequence_number}")]
182 IncompleteStream { sequence_number: u64 },
183}
184
185pub type Result<T> = std::result::Result<T, Error>;
187
188#[derive(Clone, Debug)]
207pub struct ReadMask<'a>(Cow<'a, str>);
208
209impl<'a> ReadMask<'a> {
210 pub fn as_str(&self) -> &str {
212 &self.0
213 }
214}
215
216impl<'a> From<&'a str> for ReadMask<'a> {
217 fn from(s: &'a str) -> Self {
218 Self(Cow::Borrowed(s))
219 }
220}
221
222impl From<String> for ReadMask<'_> {
223 fn from(s: String) -> Self {
224 Self(Cow::Owned(s))
225 }
226}
227
228impl From<&[&str]> for ReadMask<'_> {
229 fn from(paths: &[&str]) -> Self {
231 Self(Cow::Owned(field_mask_normalize(&paths.join(","))))
232 }
233}
234
235impl<const N: usize> From<&[&str; N]> for ReadMask<'_> {
236 fn from(paths: &[&str; N]) -> Self {
238 Self::from(paths.as_slice())
239 }
240}
241
242impl From<FieldMask> for ReadMask<'_> {
243 fn from(mask: FieldMask) -> Self {
245 Self(Cow::Owned(field_mask_normalize(&mask.paths.join(","))))
246 }
247}
248
249pub fn saturating_usize_to_u32(value: usize) -> u32 {
252 u32::try_from(value).unwrap_or(u32::MAX)
253}
254
255pub trait ProtoResult {
261 type Value;
263
264 fn into_result(self) -> Result<Self::Value>;
266}
267
268pub fn into_item_results<T: ProtoResult>(batch: Vec<T>) -> Vec<Result<T::Value>> {
276 batch.into_iter().map(ProtoResult::into_result).collect()
277}
278
279pub fn check_result_count<T>(results: &[T], expected: usize) -> Result<()> {
284 if results.len() == expected {
285 Ok(())
286 } else {
287 Err(Error::Protocol(ProtocolError::UnexpectedResultCount {
288 expected,
289 actual: results.len(),
290 }))
291 }
292}
293
294pub fn check_object_identity(
300 results: &[Result<ProtoObject>],
301 requested: &[(ObjectId, Option<Version>)],
302) -> Result<()> {
303 for (position, (result, (expected, _))) in results.iter().zip(requested).enumerate() {
304 let Ok(object) = result else { continue };
305 let Some(actual) = answered_object_id(object)? else {
306 continue;
307 };
308 if actual != *expected {
309 return Err(Error::Protocol(ProtocolError::UnexpectedObject {
310 position,
311 expected: *expected,
312 actual,
313 }));
314 }
315 }
316 Ok(())
317}
318
319pub fn check_transaction_identity(
324 results: &[Result<ExecutedTransaction>],
325 requested: &[TransactionDigest],
326) -> Result<()> {
327 for (position, (result, expected)) in results.iter().zip(requested).enumerate() {
328 let Ok(transaction) = result else { continue };
329 let Some(actual) = answered_transaction_digest(transaction)? else {
330 continue;
331 };
332 if actual != *expected {
333 return Err(Error::Protocol(ProtocolError::UnexpectedTransaction {
334 position,
335 expected: *expected,
336 actual,
337 }));
338 }
339 }
340 Ok(())
341}
342
343fn answered_object_id(object: &ProtoObject) -> Result<Option<ObjectId>> {
347 if let Some(id) = object
348 .reference
349 .as_ref()
350 .and_then(|reference| reference.object_id.as_ref())
351 {
352 return Ok(Some(id.try_into()?));
353 }
354 if object.bcs.is_some() {
355 return Ok(Some(object.object()?.id()));
356 }
357 Ok(None)
358}
359
360fn answered_transaction_digest(
364 transaction: &ExecutedTransaction,
365) -> Result<Option<TransactionDigest>> {
366 let Some(transaction) = transaction.transaction.as_ref() else {
367 return Ok(None);
368 };
369 if let Some(digest) = transaction.digest.as_ref() {
370 return Ok(Some(digest.try_into()?));
371 }
372 if transaction.bcs.is_some() {
373 return Ok(Some(transaction.transaction()?.digest()));
374 }
375 Ok(None)
376}
377
378impl ProtoResult for ObjectResult {
379 type Value = ProtoObject;
380
381 fn into_result(self) -> Result<Self::Value> {
382 match self.result {
383 Some(object_result::Result::Object(obj)) => Ok(obj),
384 Some(object_result::Result::Error(e)) => Err(Error::Server(e)),
385 None => Err(TryFromProtoError::missing("result").into()),
386 Some(_) => Err(Error::Protocol(ProtocolError::UnknownVariant(
387 "object result",
388 ))),
389 }
390 }
391}
392
393impl ProtoResult for TransactionResult {
394 type Value = ExecutedTransaction;
395
396 fn into_result(self) -> Result<Self::Value> {
397 match self.result {
398 Some(transaction_result::Result::ExecutedTransaction(tx)) => Ok(tx),
399 Some(transaction_result::Result::Error(e)) => Err(Error::Server(e)),
400 None => Err(TryFromProtoError::missing("result").into()),
401 Some(_) => Err(Error::Protocol(ProtocolError::UnknownVariant(
402 "transaction result",
403 ))),
404 }
405 }
406}
407
408impl ProtoResult for ExecuteTransactionResult {
409 type Value = ExecutedTransaction;
410
411 fn into_result(self) -> Result<Self::Value> {
412 match self.result {
413 Some(execute_transaction_result::Result::ExecutedTransaction(tx)) => Ok(tx),
414 Some(execute_transaction_result::Result::Error(e)) => Err(Error::Server(e)),
415 None => Err(TryFromProtoError::missing("result").into()),
416 Some(_) => Err(Error::Protocol(ProtocolError::UnknownVariant(
417 "execute transaction result",
418 ))),
419 }
420 }
421}
422
423impl ProtoResult for SimulateTransactionResult {
424 type Value = SimulatedTransaction;
425
426 fn into_result(self) -> Result<Self::Value> {
427 match self.result {
428 Some(simulate_transaction_result::Result::SimulatedTransaction(tx)) => Ok(tx),
429 Some(simulate_transaction_result::Result::Error(e)) => Err(Error::Server(e)),
430 None => Err(TryFromProtoError::missing("result").into()),
431 Some(_) => Err(Error::Protocol(ProtocolError::UnknownVariant(
432 "simulate transaction result",
433 ))),
434 }
435 }
436}
437
438pub async fn collect_stream<T, I, F>(
450 mut stream: tonic::Streaming<T>,
451 metadata: tonic::metadata::MetadataMap,
452 extract: F,
453) -> Result<MetadataEnvelope<Vec<I>>>
454where
455 F: Fn(T) -> Result<(bool, Vec<I>)>,
456{
457 let mut results = Vec::new();
458 let mut has_next = false;
459
460 while let Some(response) = stream.message().await? {
461 let (next, items) = extract(response)?;
462 has_next = next;
463 results.extend(items);
464 }
465
466 if has_next {
467 return Err(Error::UnexpectedEndOfStream);
468 }
469
470 Ok(MetadataEnvelope::new(results, metadata))
471}
472
473#[derive(Clone, Debug)]
478pub struct Page<T> {
479 pub items: Vec<T>,
481 pub next_page_token: Option<::prost::bytes::Bytes>,
483}
484
485macro_rules! define_list_query {
530 (
532 $(#[$meta:meta])*
533 pub struct $query_name:ident {
534 service_client: $service_client_type:ty,
535 request: $request_type:ty,
536 item: $item_type:ty,
537 rpc_method: $rpc_method:ident,
538 items_field: $items_field:ident,
539 }
540 ) => {
541 $crate::api::define_list_query! {
542 @impl
543 $(#[$meta])*
544 pub struct $query_name {
545 service_client: $service_client_type,
546 request: $request_type,
547 item: $item_type,
548 rpc_method: $rpc_method,
549 items_field: $items_field,
550 map_item: |item| $crate::api::Result::Ok(item),
551 }
552 }
553 };
554
555 (
558 $(#[$meta:meta])*
559 pub struct $query_name:ident {
560 service_client: $service_client_type:ty,
561 request: $request_type:ty,
562 item: $item_type:ty,
563 rpc_method: $rpc_method:ident,
564 items_field: $items_field:ident,
565 map_item: $map_item:expr,
566 }
567 ) => {
568 $crate::api::define_list_query! {
569 @impl
570 $(#[$meta])*
571 pub struct $query_name {
572 service_client: $service_client_type,
573 request: $request_type,
574 item: $item_type,
575 rpc_method: $rpc_method,
576 items_field: $items_field,
577 map_item: |item| $map_item(&item),
578 }
579 }
580 };
581
582 (
583 @impl
584 $(#[$meta:meta])*
585 pub struct $query_name:ident {
586 service_client: $service_client_type:ty,
587 request: $request_type:ty,
588 item: $item_type:ty,
589 rpc_method: $rpc_method:ident,
590 items_field: $items_field:ident,
591 map_item: $map_item:expr,
592 }
593 ) => {
594 $(#[$meta])*
595 pub struct $query_name {
596 service_client: $service_client_type,
597 base_request: $request_type,
598 max_message_size: Option<usize>,
599 page_size: Option<u32>,
600 page_token: Option<::prost::bytes::Bytes>,
601 }
602
603 impl $query_name {
604 pub(crate) fn new(
605 service_client: $service_client_type,
606 base_request: $request_type,
607 max_message_size: Option<usize>,
608 page_size: Option<u32>,
609 page_token: Option<::prost::bytes::Bytes>,
610 ) -> Self {
611 Self {
612 service_client,
613 base_request,
614 max_message_size,
615 page_size,
616 page_token,
617 }
618 }
619
620 pub async fn collect(
624 self,
625 limit: impl Into<Option<u32>>,
626 ) -> $crate::api::Result<$crate::api::MetadataEnvelope<Vec<$item_type>>> {
627 let limit = limit.into();
628 let mut all_items: Vec<$item_type> = Vec::new();
629 let mut next_page_token = self.page_token;
630 let mut result_metadata = None;
631 let mut service_client = self.service_client;
632
633 loop {
634 let mut request = self.base_request.clone();
635
636 let effective_page_size = match (self.page_size, limit) {
639 (Some(ps), Some(l)) => {
640 let remaining = (l as usize).saturating_sub(all_items.len());
641 Some(ps.min(remaining as u32))
642 }
643 (Some(ps), None) => Some(ps),
644 (None, Some(l)) => {
645 let remaining = (l as usize).saturating_sub(all_items.len());
646 Some(remaining as u32)
647 }
648 (None, None) => None,
649 };
650 if let Some(ps) = effective_page_size {
651 request = request.with_page_size(ps);
652 }
653 if let Some(token) = next_page_token.take() {
654 request = request.with_page_token(token);
655 }
656 if let Some(max_size) = self.max_message_size {
657 request = request.with_max_message_size_bytes(
658 $crate::api::saturating_usize_to_u32(max_size),
659 );
660 }
661
662 let response = service_client.$rpc_method(request).await?;
663 let (body, metadata) =
664 $crate::api::MetadataEnvelope::from(response).into_parts();
665 if result_metadata.is_none() {
666 result_metadata = Some(metadata);
667 }
668
669 let map_item = $map_item;
670 for item in body.$items_field {
671 all_items.push(map_item(item)?);
672 }
673
674 match body.next_page_token {
675 Some(token) => next_page_token = Some(token),
676 None => break,
677 }
678
679 if limit.is_some_and(|l| all_items.len() >= l as usize) {
680 break;
681 }
682 }
683
684 Ok($crate::api::MetadataEnvelope::new(
685 all_items,
686 result_metadata.unwrap_or_default(),
687 ))
688 }
689 }
690
691 impl ::std::future::IntoFuture for $query_name {
692 type Output = $crate::api::Result<
693 $crate::api::MetadataEnvelope<$crate::api::Page<$item_type>>,
694 >;
695 type IntoFuture = ::std::pin::Pin<
696 Box<dyn ::std::future::Future<Output = Self::Output> + Send>,
697 >;
698
699 fn into_future(self) -> Self::IntoFuture {
700 Box::pin(async move {
701 let mut service_client = self.service_client;
702 let mut request = self.base_request;
703
704 if let Some(ps) = self.page_size {
705 request = request.with_page_size(ps);
706 }
707 if let Some(token) = self.page_token {
708 request = request.with_page_token(token);
709 }
710 if let Some(max_size) = self.max_message_size {
711 request = request.with_max_message_size_bytes(
712 $crate::api::saturating_usize_to_u32(max_size),
713 );
714 }
715
716 let response = service_client.$rpc_method(request).await?;
717 let (body, metadata) =
718 $crate::api::MetadataEnvelope::from(response).into_parts();
719
720 let map_item = $map_item;
721 let items = body
722 .$items_field
723 .into_iter()
724 .map(map_item)
725 .collect::<$crate::api::Result<Vec<$item_type>>>()?;
726
727 Ok($crate::api::MetadataEnvelope::new(
728 $crate::api::Page {
729 items,
730 next_page_token: body.next_page_token,
731 },
732 metadata,
733 ))
734 })
735 }
736 }
737 };
738}
739
740pub(crate) use define_list_query;
741
742pub fn proto_object_id(id: ObjectId) -> ProtoObjectId {
744 ProtoObjectId::default().with_object_id(Vec::from(id))
745}
746
747pub fn build_proto_transaction<T: Serialize>(
749 data: &T,
750 digest: TransactionDigest,
751) -> Result<ProtoTransaction> {
752 let bcs = BcsData::serialize(data)
753 .map_err(|e| Error::from(TryFromProtoError::invalid("transaction", e)))?;
754
755 let proto_transaction = ProtoTransaction::default()
756 .with_digest(digest)
757 .with_bcs(bcs);
758
759 Ok(proto_transaction)
760}
761
762#[cfg(test)]
763mod tests {
764 use iota_grpc_types::{
765 google::rpc::Status,
766 v1::{
767 object::Object, types::ObjectReference as ProtoObjectReference,
768 versioned::VersionedObject,
769 },
770 };
771 use iota_types::{
772 Address, MoveStruct, ObjectData, ObjectId, Owner, StructTag, TransactionDigest, Version,
773 };
774
775 use super::{
776 BcsData, Error, ExecutedTransaction, ObjectResult, ProtoTransaction, ProtocolError, Result,
777 check_object_identity, check_transaction_identity, into_item_results, proto_object_id,
778 };
779
780 #[test]
781 fn a_per_item_error_keeps_the_surrounding_items() {
782 let batch = vec![
783 ObjectResult::default().with_object(Object::default()),
784 ObjectResult::default().with_error(Status {
785 code: tonic::Code::NotFound.into(),
786 message: "Object 0x2 not found".to_owned(),
787 details: Vec::new(),
788 }),
789 ObjectResult::default().with_object(Object::default()),
790 ];
791
792 let items = into_item_results(batch);
793
794 assert_eq!(items.len(), 3);
795 assert!(items[0].is_ok());
796 assert!(matches!(items[1], Err(Error::Server(_))));
797 assert!(items[2].is_ok());
798 }
799
800 #[test]
801 fn an_all_error_batch_still_yields_one_item_per_request() {
802 let batch = vec![
803 ObjectResult::default().with_error(Status::default()),
804 ObjectResult::default().with_error(Status::default()),
805 ];
806
807 assert_eq!(into_item_results(batch).len(), 2);
808 }
809
810 fn object_id(byte: u8) -> ObjectId {
811 ObjectId::new([byte; ObjectId::LENGTH])
812 }
813
814 fn transaction_digest(byte: u8) -> TransactionDigest {
815 TransactionDigest::new([byte; TransactionDigest::LENGTH])
816 }
817
818 fn answered(id: ObjectId) -> Result<Object> {
821 let mut object = Object::default();
822 object.reference =
823 Some(ProtoObjectReference::default().with_object_id(proto_object_id(id)));
824 Ok(object)
825 }
826
827 fn answered_with_bcs_only(id: ObjectId) -> Object {
829 let mut contents = Vec::from(id);
830 contents.extend_from_slice(&0u64.to_le_bytes());
831 let move_struct = MoveStruct::new(
832 StructTag::new_gas_coin().into(),
833 Version::from_u64(1),
834 contents,
835 )
836 .expect("contents contain a full object id");
837 let object = iota_types::Object::new(
838 ObjectData::Struct(move_struct),
839 Owner::Address(Address::ZERO),
840 TransactionDigest::ZERO,
841 0,
842 );
843
844 let mut answered = Object::default();
845 answered.bcs =
846 Some(BcsData::serialize(&VersionedObject::V1(object)).expect("object serializes"));
847 answered
848 }
849
850 fn answered_transaction(digest: TransactionDigest) -> ExecutedTransaction {
853 ExecutedTransaction::default()
854 .with_transaction(ProtoTransaction::default().with_digest(digest))
855 }
856
857 #[test]
858 fn objects_answered_in_request_order_are_accepted() {
859 let requested = [(object_id(1), None), (object_id(2), None)];
860 let results = vec![answered(object_id(1)), answered(object_id(2))];
861
862 assert!(check_object_identity(&results, &requested).is_ok());
863 }
864
865 #[test]
866 fn a_substituted_object_is_rejected_with_both_ids() {
867 let requested = [(object_id(1), None), (object_id(2), None)];
868 let results = vec![answered(object_id(1)), answered(object_id(9))];
869
870 let err = check_object_identity(&results, &requested).unwrap_err();
871 let Error::Protocol(ProtocolError::UnexpectedObject {
872 position,
873 expected,
874 actual,
875 }) = err
876 else {
877 panic!("expected an UnexpectedObject error, got {err}");
878 };
879 assert_eq!(position, 1);
880 assert_eq!(expected, object_id(2));
881 assert_eq!(actual, object_id(9));
882 }
883
884 #[test]
885 fn a_position_the_server_errored_on_has_no_id_to_check() {
886 let requested = [(object_id(1), None), (object_id(2), None)];
887 let results = vec![
888 answered(object_id(1)),
889 Err(Error::Server(Status {
890 code: tonic::Code::NotFound.into(),
891 message: String::new(),
892 details: Vec::new(),
893 })),
894 ];
895
896 assert!(check_object_identity(&results, &requested).is_ok());
897 }
898
899 #[test]
900 fn an_object_answered_with_bcs_only_is_checked_against_the_id_in_its_bcs() {
901 let requested = [(object_id(1), None)];
902 let results = vec![Ok(answered_with_bcs_only(object_id(9)))];
903
904 let err = check_object_identity(&results, &requested).unwrap_err();
905 let Error::Protocol(ProtocolError::UnexpectedObject {
906 expected, actual, ..
907 }) = err
908 else {
909 panic!("expected an UnexpectedObject error, got {err}");
910 };
911 assert_eq!(expected, object_id(1));
912 assert_eq!(actual, object_id(9));
913 }
914
915 #[test]
916 fn an_object_carrying_neither_a_reference_nor_bcs_has_nothing_to_check() {
917 let requested = [(object_id(1), None)];
918 let results = vec![Ok(Object::default())];
919
920 assert!(check_object_identity(&results, &requested).is_ok());
921 }
922
923 #[test]
924 fn a_substituted_transaction_is_rejected_with_both_digests() {
925 let requested = [transaction_digest(1), transaction_digest(2)];
926 let results = vec![
927 Ok(answered_transaction(transaction_digest(1))),
928 Ok(answered_transaction(transaction_digest(9))),
929 ];
930
931 let err = check_transaction_identity(&results, &requested).unwrap_err();
932 let Error::Protocol(ProtocolError::UnexpectedTransaction {
933 position,
934 expected,
935 actual,
936 }) = err
937 else {
938 panic!("expected an UnexpectedTransaction error, got {err}");
939 };
940 assert_eq!(position, 1);
941 assert_eq!(expected, transaction_digest(2));
942 assert_eq!(actual, transaction_digest(9));
943 }
944
945 #[test]
946 fn not_found_is_recognized_at_the_call_and_item_level() {
947 let item_level = Error::Server(Status {
948 code: tonic::Code::NotFound.into(),
949 message: String::new(),
950 details: Vec::new(),
951 });
952 let call_level = Error::from(tonic::Status::not_found("gone"));
953 let other = Error::Server(Status {
954 code: tonic::Code::Internal.into(),
955 message: String::new(),
956 details: Vec::new(),
957 });
958
959 assert!(item_level.is_not_found());
960 assert!(call_level.is_not_found());
961 assert!(!other.is_not_found());
962 assert!(!Error::EmptyRequest.is_not_found());
963 }
964}