Skip to main content

nifi_rust_client/
builder.rs

1use std::time::Duration;
2
3use snafu::ResultExt as _;
4use url::Url;
5
6use crate::NifiClient;
7use crate::NifiError;
8use crate::error::{HttpSnafu, InvalidBaseUrlSnafu, InvalidCertificateSnafu};
9
10/// Builder for [`NifiClient`].
11///
12/// Use this when you need to configure timeouts, proxies, or TLS options beyond
13/// the defaults provided by the convenience constructors.
14///
15/// # Example
16///
17/// ```no_run
18/// use std::time::Duration;
19/// use nifi_rust_client::NifiClientBuilder;
20/// use url::Url;
21///
22/// # async fn example() -> Result<(), nifi_rust_client::NifiError> {
23/// let client = NifiClientBuilder::new("https://nifi.example.com:8443")?
24///     .timeout(Duration::from_secs(60))
25///     .connect_timeout(Duration::from_secs(10))
26///     .proxy(Url::parse("http://proxy.internal:3128").unwrap())
27///     .build()?;
28/// # Ok(())
29/// # }
30/// ```
31#[derive(Debug)]
32pub struct NifiClientBuilder {
33    base_url: Url,
34    timeout: Option<Duration>,
35    connect_timeout: Option<Duration>,
36    proxy_all: Option<Url>,
37    proxy_http: Option<Url>,
38    proxy_https: Option<Url>,
39    danger_accept_invalid_certs: bool,
40    root_certificates: Vec<Vec<u8>>,
41}
42
43impl NifiClientBuilder {
44    /// Create a new builder targeting the given NiFi base URL.
45    ///
46    /// Returns an error if `base_url` cannot be parsed.
47    pub fn new(base_url: &str) -> Result<Self, NifiError> {
48        let base_url = Url::parse(base_url).context(InvalidBaseUrlSnafu)?;
49        Ok(Self {
50            base_url,
51            timeout: None,
52            connect_timeout: None,
53            proxy_all: None,
54            proxy_http: None,
55            proxy_https: None,
56            danger_accept_invalid_certs: false,
57            root_certificates: Vec::new(),
58        })
59    }
60
61    /// Set the total request timeout.
62    ///
63    /// The timeout applies from when the request starts connecting until the
64    /// response body is fully received.
65    pub fn timeout(mut self, duration: Duration) -> Self {
66        self.timeout = Some(duration);
67        self
68    }
69
70    /// Set the TCP connection timeout.
71    pub fn connect_timeout(mut self, duration: Duration) -> Self {
72        self.connect_timeout = Some(duration);
73        self
74    }
75
76    /// Route all traffic (HTTP and HTTPS) through the given proxy.
77    pub fn proxy(mut self, url: Url) -> Self {
78        self.proxy_all = Some(url);
79        self
80    }
81
82    /// Route HTTP traffic through the given proxy.
83    pub fn http_proxy(mut self, url: Url) -> Self {
84        self.proxy_http = Some(url);
85        self
86    }
87
88    /// Route HTTPS traffic through the given proxy.
89    pub fn https_proxy(mut self, url: Url) -> Self {
90        self.proxy_https = Some(url);
91        self
92    }
93
94    /// Skip TLS certificate verification.
95    ///
96    /// Only use this in development against self-signed certificates.
97    pub fn danger_accept_invalid_certs(mut self, accept: bool) -> Self {
98        self.danger_accept_invalid_certs = accept;
99        self
100    }
101
102    /// Trust an additional PEM-encoded CA certificate.
103    ///
104    /// May be called multiple times to add more than one certificate.
105    pub fn add_root_certificate(mut self, pem: &[u8]) -> Self {
106        self.root_certificates.push(pem.to_vec());
107        self
108    }
109
110    /// Build the [`NifiClient`].
111    pub fn build(self) -> Result<NifiClient, NifiError> {
112        let mut builder = reqwest::Client::builder()
113            .danger_accept_invalid_certs(self.danger_accept_invalid_certs);
114
115        if let Some(d) = self.timeout {
116            builder = builder.timeout(d);
117        }
118        if let Some(d) = self.connect_timeout {
119            builder = builder.connect_timeout(d);
120        }
121
122        for pem in &self.root_certificates {
123            let cert = reqwest::Certificate::from_pem(pem).context(InvalidCertificateSnafu)?;
124            builder = builder.add_root_certificate(cert);
125        }
126
127        if let Some(url) = self.proxy_all {
128            let proxy = reqwest::Proxy::all(url.as_str()).context(HttpSnafu)?;
129            builder = builder.proxy(proxy);
130        }
131        if let Some(url) = self.proxy_http {
132            let proxy = reqwest::Proxy::http(url.as_str()).context(HttpSnafu)?;
133            builder = builder.proxy(proxy);
134        }
135        if let Some(url) = self.proxy_https {
136            let proxy = reqwest::Proxy::https(url.as_str()).context(HttpSnafu)?;
137            builder = builder.proxy(proxy);
138        }
139
140        let http = builder.build().context(HttpSnafu)?;
141        Ok(NifiClient::from_parts(self.base_url, http))
142    }
143
144    /// Build a [`DynamicClient`](crate::dynamic::DynamicClient) that auto-detects the NiFi version.
145    ///
146    /// Connects to the NiFi instance, calls the about endpoint to detect
147    /// the API version, and returns a client that dispatches to the correct
148    /// generated API module at runtime.
149    ///
150    /// # Example
151    ///
152    /// ```no_run
153    /// # async fn example() -> Result<(), nifi_rust_client::NifiError> {
154    /// use nifi_rust_client::NifiClientBuilder;
155    ///
156    /// let mut client = NifiClientBuilder::new("https://nifi.example.com:8443")?
157    ///     .danger_accept_invalid_certs(true)
158    ///     .build_dynamic()
159    ///     .await?;
160    ///
161    /// client.login("admin", "password").await?;
162    /// # Ok(())
163    /// # }
164    /// ```
165    #[cfg(feature = "dynamic")]
166    pub async fn build_dynamic(self) -> Result<crate::dynamic::DynamicClient, NifiError> {
167        let client = self.build()?;
168        crate::dynamic::DynamicClient::from_client(client).await
169    }
170}