xmtp_api_d14n/protocol/traits/
dependency_resolution.rs

1use std::{collections::HashSet, marker::PhantomData};
2
3use derive_builder::UninitializedFieldError;
4use xmtp_common::{MaybeSend, MaybeSync, RetryableError};
5use xmtp_proto::api::BodyError;
6
7use crate::protocol::{CursorStoreError, Envelope, EnvelopeError, types::RequiredDependency};
8
9pub struct Resolved<E> {
10    pub resolved: Vec<E>,
11    /// list of envelopes that could not be resolved with this strategy
12    pub unresolved: Option<HashSet<RequiredDependency>>,
13}
14
15impl<E> Resolved<E> {
16    pub fn new(envelopes: Vec<E>, unresolved: Option<HashSet<RequiredDependency>>) -> Self {
17        Self {
18            resolved: envelopes,
19            unresolved,
20        }
21    }
22}
23
24#[xmtp_common::async_trait]
25pub trait ResolveDependencies: MaybeSend + MaybeSync {
26    type ResolvedEnvelope: Envelope<'static> + MaybeSend + MaybeSync;
27    /// Resolve dependencies, starting with a list of dependencies. Should try to resolve
28    /// all dependents after `dependency`, if `Dependency` is missing as well.
29    /// * Once resolved, these dependencies may have missing dependencies of their own.
30    /// # Returns
31    /// * `Vec<Self::ResolvedEnvelope>`: The list of envelopes which were resolved.
32    async fn resolve(
33        &self,
34        missing: HashSet<RequiredDependency>,
35    ) -> Result<Resolved<Self::ResolvedEnvelope>, ResolutionError>;
36}
37
38#[xmtp_common::async_trait]
39impl<T> ResolveDependencies for &T
40where
41    T: ResolveDependencies,
42{
43    type ResolvedEnvelope = T::ResolvedEnvelope;
44    async fn resolve(
45        &self,
46        missing: HashSet<RequiredDependency>,
47    ) -> Result<Resolved<Self::ResolvedEnvelope>, ResolutionError> {
48        <T as ResolveDependencies>::resolve(*self, missing).await
49    }
50}
51
52/// A resolver that does not even attempt to try and get dependencies
53pub struct NoopResolver;
54
55#[xmtp_common::async_trait]
56impl ResolveDependencies for NoopResolver {
57    type ResolvedEnvelope = ();
58    async fn resolve(
59        &self,
60        m: HashSet<RequiredDependency>,
61    ) -> Result<Resolved<()>, ResolutionError> {
62        Ok(Resolved {
63            resolved: vec![],
64            unresolved: Some(m),
65        })
66    }
67}
68
69#[derive(Clone, Copy, Default)]
70pub struct TypedNoopResolver<T> {
71    _marker: PhantomData<T>,
72}
73
74impl<T> TypedNoopResolver<T> {
75    pub fn new() -> Self {
76        Self {
77            _marker: PhantomData,
78        }
79    }
80}
81
82#[xmtp_common::async_trait]
83impl<T> ResolveDependencies for TypedNoopResolver<T>
84where
85    T: Envelope<'static>,
86{
87    type ResolvedEnvelope = T;
88    async fn resolve(
89        &self,
90        m: HashSet<RequiredDependency>,
91    ) -> Result<Resolved<T>, ResolutionError> {
92        Ok(Resolved {
93            resolved: Vec::<T>::new(),
94            unresolved: Some(m),
95        })
96    }
97}
98
99#[derive(thiserror::Error, Debug)]
100pub enum ResolutionError {
101    #[error(transparent)]
102    Envelope(#[from] EnvelopeError),
103    #[error(transparent)]
104    Body(#[from] BodyError),
105    #[error("{0}")]
106    Api(Box<dyn RetryableError>),
107    #[error(transparent)]
108    Build(#[from] UninitializedFieldError),
109    #[error("Resolution failed  to find all missing dependant envelopes")]
110    ResolutionFailed,
111    #[error(transparent)]
112    Store(#[from] CursorStoreError),
113}
114
115impl RetryableError for ResolutionError {
116    fn is_retryable(&self) -> bool {
117        use ResolutionError::*;
118        match self {
119            Envelope(e) => e.is_retryable(),
120            Body(b) => b.is_retryable(),
121            Api(a) => a.is_retryable(),
122            Build(_) => false,
123            ResolutionFailed => false,
124            Store(s) => s.is_retryable(),
125        }
126    }
127}
128
129impl ResolutionError {
130    pub fn api<E: RetryableError + 'static>(e: E) -> Self {
131        ResolutionError::Api(Box::new(e))
132    }
133}