vector/config/
api.rs

1use std::net::{Ipv4Addr, SocketAddr};
2
3use url::Url;
4use vector_lib::configurable::configurable_component;
5
6/// API options.
7#[configurable_component]
8#[configurable(metadata(
9    docs::warnings = "The API currently does not support authentication. Only enable it in isolated environments or for debugging. It must not be exposed to untrusted clients."
10))]
11#[derive(Clone, Copy, Debug, Eq, PartialEq)]
12#[serde(default, deny_unknown_fields)]
13pub struct Options {
14    /// Whether the GraphQL API is enabled for this Vector instance.
15    #[serde(default = "default_enabled")]
16    #[configurable(metadata(docs::common = true, docs::required = false))]
17    pub enabled: bool,
18
19    /// The network address to which the API should bind. If you're running
20    /// Vector in a Docker container, bind to `0.0.0.0`. Otherwise
21    /// the API will not be exposed outside the container.
22    #[serde(default = "default_address")]
23    #[configurable(metadata(docs::examples = "0.0.0.0:8686"))]
24    #[configurable(metadata(docs::examples = "127.0.0.1:1234"))]
25    #[configurable(metadata(docs::common = true, docs::required = false))]
26    pub address: Option<SocketAddr>,
27
28    /// Whether the [GraphQL Playground](https://github.com/graphql/graphql-playground) is enabled
29    /// for the API. The Playground is accessible via the `/playground` endpoint
30    /// of the address set using the `bind` parameter. Note that the `playground`
31    /// endpoint will only be enabled if the `graphql` endpoint is also enabled.
32    #[serde(default = "default_playground")]
33    #[configurable(metadata(docs::common = false, docs::required = false))]
34    pub playground: bool,
35
36    /// Whether the endpoint for receiving and processing GraphQL queries is
37    /// enabled for the API. The endpoint is accessible via the `/graphql`
38    /// endpoint of the address set using the `bind` parameter.
39    #[serde(default = "default_graphql", skip_serializing_if = "is_true")]
40    #[configurable(metadata(docs::common = true, docs::required = false))]
41    pub graphql: bool,
42}
43
44impl_generate_config_from_default!(Options);
45
46impl Default for Options {
47    fn default() -> Self {
48        Self {
49            enabled: default_enabled(),
50            playground: default_playground(),
51            address: default_address(),
52            graphql: default_graphql(),
53        }
54    }
55}
56
57// serde passes struct fields as reference
58#[allow(clippy::trivially_copy_pass_by_ref)]
59const fn is_true(value: &bool) -> bool {
60    *value
61}
62
63const fn default_enabled() -> bool {
64    false
65}
66
67/// By default, the API binds to 127.0.0.1:8686. This function should remain public;
68/// `vector top`  will use it to determine which to connect to by default, if no URL
69/// override is provided.
70pub fn default_address() -> Option<SocketAddr> {
71    Some(SocketAddr::new(Ipv4Addr::LOCALHOST.into(), 8686))
72}
73
74/// Default GraphQL API address
75pub fn default_graphql_url() -> Url {
76    let addr = default_address().unwrap();
77    Url::parse(&format!("http://{addr}/graphql"))
78        .expect("Couldn't parse default API URL. Please report this.")
79}
80
81const fn default_playground() -> bool {
82    true
83}
84
85const fn default_graphql() -> bool {
86    true
87}
88
89impl Options {
90    pub fn merge(&mut self, other: Self) -> Result<(), String> {
91        // Merge options
92
93        // Try to merge address
94        let address = match (self.address, other.address) {
95            (None, b) => b,
96            (Some(a), None) => Some(a),
97            (Some(a), Some(b)) if a == b => Some(a),
98            // Prefer non default address
99            (Some(a), Some(b)) => {
100                match (Some(a) == default_address(), Some(b) == default_address()) {
101                    (false, false) => return Err(format!("Conflicting `api` address: {a}, {b} .")),
102                    (false, true) => Some(a),
103                    (true, _) => Some(b),
104                }
105            }
106        };
107
108        let options = Options {
109            address,
110            enabled: self.enabled | other.enabled,
111            playground: self.playground & other.playground,
112            graphql: self.graphql & other.graphql,
113        };
114
115        *self = options;
116        Ok(())
117    }
118}
119
120#[test]
121fn bool_merge() {
122    let mut a = Options {
123        enabled: true,
124        address: None,
125        playground: false,
126        graphql: false,
127    };
128
129    a.merge(Options::default()).unwrap();
130
131    assert_eq!(
132        a,
133        Options {
134            enabled: true,
135            address: default_address(),
136            playground: false,
137            graphql: false
138        }
139    );
140}
141
142#[test]
143fn bind_merge() {
144    let address = SocketAddr::new(Ipv4Addr::LOCALHOST.into(), 9000);
145    let mut a = Options {
146        enabled: true,
147        address: Some(address),
148        playground: true,
149        graphql: true,
150    };
151
152    a.merge(Options::default()).unwrap();
153
154    assert_eq!(
155        a,
156        Options {
157            enabled: true,
158            address: Some(address),
159            playground: true,
160            graphql: true,
161        }
162    );
163}
164
165#[test]
166fn bind_conflict() {
167    let mut a = Options {
168        address: Some(SocketAddr::new(Ipv4Addr::LOCALHOST.into(), 9000)),
169        ..Options::default()
170    };
171
172    let b = Options {
173        address: Some(SocketAddr::new(Ipv4Addr::LOCALHOST.into(), 9001)),
174        ..Options::default()
175    };
176
177    assert!(a.merge(b).is_err());
178}