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
use std::collections::HashMap;
use std::marker::PhantomData;
use std::sync::Arc;
use std::time::Duration;

use bonsaidb_core::api;
use bonsaidb_core::api::ApiName;
use bonsaidb_core::networking::CURRENT_PROTOCOL_VERSION;
#[cfg(not(target_arch = "wasm32"))]
use fabruic::Certificate;
#[cfg(not(target_arch = "wasm32"))]
use tokio::runtime::Handle;
use url::Url;

use crate::client::{AnyApiCallback, ApiCallback};
#[cfg(not(target_arch = "wasm32"))]
use crate::BlockingClient;
use crate::{AsyncClient, Error};

/// A type marker for [`Builder`] indicating the returned client should be an
/// [`AsyncClient`].
pub struct Async;

/// A type marker for [`Builder`] indicating the returned client should be an
/// [`BlockingClient`].
#[cfg(not(target_arch = "wasm32"))]
pub struct Blocking;

/// Builder for a [`BlockingClient`] or an [`AsyncClient`].
#[must_use]
pub struct Builder<AsyncMode> {
    url: Url,
    protocol_version: &'static str,
    custom_apis: HashMap<ApiName, Option<Arc<dyn AnyApiCallback>>>,
    connect_timeout: Option<Duration>,
    request_timeout: Option<Duration>,
    #[cfg(not(target_arch = "wasm32"))]
    certificate: Option<fabruic::Certificate>,
    #[cfg(not(target_arch = "wasm32"))]
    tokio: Option<Handle>,
    mode: PhantomData<AsyncMode>,
}

impl<AsyncMode> Builder<AsyncMode> {
    /// Creates a new builder for a client connecting to `url`.
    pub(crate) fn new(url: Url) -> Self {
        Self {
            url,
            protocol_version: CURRENT_PROTOCOL_VERSION,
            custom_apis: HashMap::new(),
            request_timeout: None,
            connect_timeout: None,
            #[cfg(not(target_arch = "wasm32"))]
            certificate: None,
            #[cfg(not(target_arch = "wasm32"))]
            tokio: None,
            mode: PhantomData,
        }
    }

    /// Specifies the tokio runtime this client should use for its async tasks.
    /// If not specified, `Client` will try to acquire a handle via
    /// `tokio::runtime::Handle::try_current()`.
    #[cfg(not(target_arch = "wasm32"))]
    #[allow(clippy::missing_const_for_fn)]
    pub fn with_runtime(mut self, handle: Handle) -> Self {
        self.tokio = Some(handle);
        self
    }

    /// Enables using a [`Api`](api::Api) with this client. If you want to
    /// receive out-of-band API requests, set a callback using
    /// `with_custom_api_callback` instead.
    pub fn with_api<Api: api::Api>(mut self) -> Self {
        self.custom_apis.insert(Api::name(), None);
        self
    }

    /// Enables using a [`Api`](api::Api) with this client. `callback` will be
    /// invoked when custom API responses are received from the server.
    pub fn with_api_callback<Api: api::Api>(mut self, callback: ApiCallback<Api>) -> Self {
        self.custom_apis
            .insert(Api::name(), Some(Arc::new(callback)));
        self
    }

    /// Connects to a server using a pinned `certificate`. Only supported with BonsaiDb protocol-based connections.
    #[cfg(not(target_arch = "wasm32"))]
    #[allow(clippy::missing_const_for_fn)]
    pub fn with_certificate(mut self, certificate: Certificate) -> Self {
        self.certificate = Some(certificate);
        self
    }

    /// Overrides the protocol version. Only for testing purposes.
    #[cfg(feature = "test-util")]
    #[allow(clippy::missing_const_for_fn)]
    pub fn with_protocol_version(mut self, version: &'static str) -> Self {
        self.protocol_version = version;
        self
    }

    /// Sets the request timeout for the client.
    ///
    /// If not specified, requests will time out after 60 seconds.
    pub fn with_request_timeout(mut self, timeout: impl Into<Duration>) -> Self {
        self.request_timeout = Some(timeout.into());
        self
    }

    /// Sets the connection timeout for the client.
    ///
    /// If not specified, the client will time out after 60 seconds if a
    /// connection cannot be established.
    pub fn with_connect_timeout(mut self, timeout: impl Into<Duration>) -> Self {
        self.connect_timeout = Some(timeout.into());
        self
    }

    fn finish_internal(self) -> Result<AsyncClient, Error> {
        AsyncClient::new_from_parts(
            self.url,
            self.protocol_version,
            self.custom_apis,
            self.connect_timeout,
            self.request_timeout,
            #[cfg(not(target_arch = "wasm32"))]
            self.certificate,
            #[cfg(not(target_arch = "wasm32"))]
            self.tokio.or_else(|| Handle::try_current().ok()),
        )
    }
}

#[cfg(not(target_arch = "wasm32"))]
impl Builder<Blocking> {
    /// Finishes building the client for use in a blocking (not async) context.
    pub fn build(self) -> Result<BlockingClient, Error> {
        self.finish_internal().map(BlockingClient)
    }
}

impl Builder<Async> {
    /// Finishes building the client for use in a tokio async context.
    pub fn build(self) -> Result<AsyncClient, Error> {
        self.finish_internal()
    }
}