ic_agent/agent/
builder.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
use crate::{
    agent::{agent_config::AgentConfig, Agent},
    AgentError, Identity, NonceFactory, NonceGenerator,
};
use std::sync::Arc;

use super::{route_provider::RouteProvider, HttpService};

/// A builder for an [`Agent`].
#[derive(Default)]
pub struct AgentBuilder {
    config: AgentConfig,
}

impl AgentBuilder {
    /// Create an instance of [Agent] with the information from this builder.
    pub fn build(self) -> Result<Agent, AgentError> {
        Agent::new(self.config)
    }

    /// Set the dynamic transport layer for the [`Agent`], performing continuous discovery of the API boundary nodes
    /// and routing traffic via them based on latency. Cannot be set together with `with_route_provider`.
    ///
    /// See [`DynamicRouteProvider`](super::route_provider::DynamicRouteProvider) if more customization is needed such as polling intervals.
    pub async fn with_background_dynamic_routing(mut self) -> Self {
        assert!(
            self.config.route_provider.is_none(),
            "with_background_dynamic_routing cannot be called with with_route_provider"
        );
        self.config.background_dynamic_routing = true;
        self
    }

    /// Set the URL of the [`Agent`]. Either this or `with_route_provider` must be called (but not both).
    pub fn with_url<S: Into<String>>(mut self, url: S) -> Self {
        assert!(
            self.config.route_provider.is_none(),
            "with_url cannot be called with with_route_provider"
        );
        self.config.url = Some(url.into().parse().unwrap());
        self
    }

    /// Add a `NonceFactory` to this Agent. By default, no nonce is produced.
    pub fn with_nonce_factory(self, nonce_factory: NonceFactory) -> AgentBuilder {
        self.with_nonce_generator(nonce_factory)
    }

    /// Same as [`Self::with_nonce_factory`], but for any `NonceGenerator` type
    pub fn with_nonce_generator<N: 'static + NonceGenerator>(
        self,
        nonce_factory: N,
    ) -> AgentBuilder {
        self.with_arc_nonce_generator(Arc::new(nonce_factory))
    }

    /// Same as [`Self::with_nonce_generator`], but reuses an existing `Arc`.
    pub fn with_arc_nonce_generator(
        mut self,
        nonce_factory: Arc<dyn NonceGenerator>,
    ) -> AgentBuilder {
        self.config.nonce_factory = Arc::new(nonce_factory);
        self
    }

    /// Add an identity provider for signing messages. This is required.
    pub fn with_identity<I>(self, identity: I) -> Self
    where
        I: 'static + Identity,
    {
        self.with_arc_identity(Arc::new(identity))
    }

    /// Same as [`Self::with_identity`], but reuses an existing box
    pub fn with_boxed_identity(self, identity: Box<dyn Identity>) -> Self {
        self.with_arc_identity(Arc::from(identity))
    }

    /// Same as [`Self::with_identity`], but reuses an existing `Arc`
    pub fn with_arc_identity(mut self, identity: Arc<dyn Identity>) -> Self {
        self.config.identity = identity;
        self
    }

    /// Provides a _default_ ingress expiry. This is the delta that will be applied
    /// at the time an update or query is made. The default expiry cannot be a
    /// fixed system time. This is also used when checking certificate timestamps.
    ///
    /// The timestamp corresponding to this duration may be rounded in order to reduce
    /// cache misses. The current implementation rounds to the nearest minute if the
    /// expiry is more than a minute, but this is not guaranteed.
    pub fn with_ingress_expiry(mut self, ingress_expiry: std::time::Duration) -> Self {
        self.config.ingress_expiry = ingress_expiry;
        self
    }

    /// Allows disabling query signature verification. Query signatures improve resilience but require
    /// a separate read-state call to fetch node keys.
    pub fn with_verify_query_signatures(mut self, verify_query_signatures: bool) -> Self {
        self.config.verify_query_signatures = verify_query_signatures;
        self
    }

    /// Sets the maximum number of requests that the agent will make concurrently. The replica is configured
    /// to only permit 50 concurrent requests per client. Set this value lower if you have multiple agents,
    /// to avoid the slowdown of retrying any 429 errors.
    pub fn with_max_concurrent_requests(mut self, max_concurrent_requests: usize) -> Self {
        self.config.max_concurrent_requests = max_concurrent_requests;
        self
    }

    /// Add a `RouteProvider` to this agent, to provide the URLs of boundary nodes.
    pub fn with_route_provider(self, provider: impl RouteProvider + 'static) -> Self {
        self.with_arc_route_provider(Arc::new(provider))
    }

    /// Same as [`Self::with_route_provider`], but reuses an existing `Arc`.
    pub fn with_arc_route_provider(mut self, provider: Arc<dyn RouteProvider>) -> Self {
        assert!(
            !self.config.background_dynamic_routing,
            "with_background_dynamic_routing cannot be called with with_route_provider"
        );
        assert!(
            self.config.url.is_none(),
            "with_url cannot be called with with_route_provider"
        );
        self.config.route_provider = Some(provider);
        self
    }

    /// Provide a pre-configured HTTP client to use. Use this to set e.g. HTTP timeouts or proxy configuration.
    pub fn with_http_client(mut self, client: reqwest::Client) -> Self {
        assert!(
            self.config.http_service.is_none(),
            "with_arc_http_middleware cannot be called with with_http_client"
        );
        self.config.client = Some(client);
        self
    }

    /// Provide a custom `reqwest`-compatible HTTP service, e.g. to add per-request headers for custom boundary nodes.
    /// Most users will not need this and should use `with_http_client`. Cannot be called with `with_http_client`.
    ///
    /// The trait is automatically implemented for any `tower::Service` impl matching the one `reqwest::Client` uses,
    /// including `reqwest-middleware`. This is a low-level interface, and direct implementations must provide all automatic retry logic.
    pub fn with_arc_http_middleware(mut self, service: Arc<dyn HttpService>) -> Self {
        assert!(
            self.config.client.is_none(),
            "with_arc_http_middleware cannot be called with with_http_client"
        );
        self.config.http_service = Some(service);
        self
    }

    /// Retry up to the specified number of times upon encountering underlying TCP errors.
    pub fn with_max_tcp_error_retries(mut self, retries: usize) -> Self {
        self.config.max_tcp_error_retries = retries;
        self
    }

    /// Don't accept HTTP bodies any larger than `max_size` bytes.
    pub fn with_max_response_body_size(mut self, max_size: usize) -> Self {
        self.config.max_response_body_size = Some(max_size);
        self
    }
    /// Set the maximum time to wait for a response from the replica.
    pub fn with_max_polling_time(mut self, max_polling_time: std::time::Duration) -> Self {
        self.config.max_polling_time = max_polling_time;
        self
    }
}