mz_ccsr/
config.rs

1// Copyright Materialize, Inc. and contributors. All rights reserved.
2//
3// Use of this software is governed by the Business Source License
4// included in the LICENSE file.
5//
6// As of the Change Date specified in that file, in accordance with
7// the Business Source License, use of this software will be governed
8// by the Apache License, Version 2.0.
9
10use std::collections::BTreeMap;
11use std::fmt;
12use std::net::SocketAddr;
13use std::sync::Arc;
14use std::time::Duration;
15
16use serde::{Deserialize, Serialize};
17use url::Url;
18
19use crate::client::Client;
20use crate::tls::{Certificate, Identity};
21
22#[derive(Clone, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
23pub struct Auth {
24    pub username: String,
25    pub password: Option<String>,
26}
27
28/// Configuration for a `Client`.
29#[derive(Clone)]
30pub struct ClientConfig {
31    url: Arc<dyn Fn() -> Url + Send + Sync + 'static>,
32    root_certs: Vec<Certificate>,
33    identity: Option<Identity>,
34    auth: Option<Auth>,
35    dns_overrides: BTreeMap<String, Vec<SocketAddr>>,
36}
37
38impl fmt::Debug for ClientConfig {
39    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
40        f.debug_struct("ClientConfig")
41            .field("url", &"...")
42            .field("root_certs", &self.root_certs)
43            .field("identity", &self.identity)
44            .field("auth", &self.auth)
45            .field("dns_overrides", &self.dns_overrides)
46            .finish()
47    }
48}
49
50impl ClientConfig {
51    /// Constructs a new `ClientConfig` that will target the schema registry at
52    /// the specified URL.
53    pub fn new(url: Url) -> ClientConfig {
54        ClientConfig {
55            url: Arc::new(move || url.clone()),
56            root_certs: Vec::new(),
57            identity: None,
58            auth: None,
59            dns_overrides: BTreeMap::new(),
60        }
61    }
62
63    /// Adds a trusted root TLS certificate.
64    ///
65    /// Certificates in the system's certificate store are trusted by default.
66    pub fn add_root_certificate(mut self, cert: Certificate) -> ClientConfig {
67        self.root_certs.push(cert);
68        self
69    }
70
71    /// Enables TLS client authentication with the provided identity.
72    pub fn identity(mut self, identity: Identity) -> ClientConfig {
73        self.identity = Some(identity);
74        self
75    }
76
77    /// Enables HTTP basic authentication with the specified username and
78    /// optional password.
79    pub fn auth(mut self, username: String, password: Option<String>) -> ClientConfig {
80        self.auth = Some(Auth { username, password });
81        self
82    }
83
84    /// Overrides DNS resolution for specific domains to the provided IP
85    /// addresses.
86    ///
87    /// See [`reqwest::ClientBuilder::resolve_to_addrs`].
88    pub fn resolve_to_addrs(mut self, domain: &str, addrs: &[SocketAddr]) -> ClientConfig {
89        self.dns_overrides.insert(domain.into(), addrs.into());
90        self
91    }
92
93    /// Sets a callback that will be used to dynamically override the url
94    /// the client uses.
95    // Note this this doesn't use native `reqwest` `Proxy`s because not all schema
96    // registry implementations support them.
97    pub fn dynamic_url<F: Fn() -> Url + Send + Sync + 'static>(
98        mut self,
99        callback: F,
100    ) -> ClientConfig {
101        self.url = Arc::new(callback);
102        self
103    }
104
105    /// Builds the [`Client`].
106    pub fn build(self) -> Result<Client, anyhow::Error> {
107        let mut builder = reqwest::ClientBuilder::new();
108
109        for root_cert in self.root_certs {
110            builder = builder.add_root_certificate(root_cert.into());
111        }
112
113        if let Some(ident) = self.identity {
114            builder = builder.identity(ident.into());
115        }
116
117        for (domain, addrs) in self.dns_overrides {
118            builder = builder.resolve_to_addrs(&domain, &addrs);
119        }
120
121        // TODO(guswynn): make this configurable.
122        let timeout = Duration::from_secs(60);
123
124        let inner = builder
125            .redirect(reqwest::redirect::Policy::none())
126            .timeout(timeout)
127            .build()
128            .unwrap();
129
130        Client::new(inner, self.url, self.auth, timeout)
131    }
132}