vector_config/
stdlib.rs

1use std::{
2    cell::RefCell,
3    collections::{BTreeMap, BTreeSet, HashMap, HashSet},
4    hash::Hash,
5    net::{Ipv4Addr, SocketAddr},
6    num::{
7        NonZeroI8, NonZeroI16, NonZeroI32, NonZeroI64, NonZeroU8, NonZeroU16, NonZeroU32,
8        NonZeroU64, NonZeroUsize,
9    },
10    path::PathBuf,
11    time::Duration,
12};
13
14use indexmap::IndexMap;
15use serde_json::{Number, Value};
16use vector_config_common::{attributes::CustomAttribute, constants, validation::Validation};
17use vrl::value::KeyString;
18
19use crate::{
20    Configurable, GenerateError, Metadata, ToValue,
21    num::ConfigurableNumber,
22    schema::{
23        SchemaGenerator, SchemaObject, assert_string_schema_for_map, generate_array_schema,
24        generate_bool_schema, generate_map_schema, generate_number_schema,
25        generate_optional_schema, generate_set_schema, generate_string_schema,
26    },
27    str::ConfigurableString,
28};
29
30// Unit type.
31impl Configurable for () {
32    fn generate_schema(_: &RefCell<SchemaGenerator>) -> Result<SchemaObject, GenerateError> {
33        // Using a unit type in a configuration-related type is never actually valid.
34        Err(GenerateError::InvalidType)
35    }
36}
37
38impl ToValue for () {
39    fn to_value(&self) -> Value {
40        Value::Null
41    }
42}
43
44// Null and boolean.
45impl<T> Configurable for Option<T>
46where
47    T: Configurable + ToValue + 'static,
48{
49    fn referenceable_name() -> Option<&'static str> {
50        match T::referenceable_name() {
51            None => None,
52            Some(_) => Some(std::any::type_name::<Self>()),
53        }
54    }
55
56    fn is_optional() -> bool {
57        true
58    }
59
60    fn metadata() -> Metadata {
61        Metadata::with_transparent(true)
62    }
63
64    fn validate_metadata(metadata: &Metadata) -> Result<(), GenerateError> {
65        // We have to convert from `Metadata` to `Metadata` which erases the default value.
66        let converted = metadata.convert();
67        T::validate_metadata(&converted)
68    }
69
70    fn generate_schema(
71        generator: &RefCell<SchemaGenerator>,
72    ) -> Result<SchemaObject, GenerateError> {
73        generate_optional_schema(&T::as_configurable_ref(), generator)
74    }
75}
76
77impl<T: ToValue> ToValue for Option<T> {
78    fn to_value(&self) -> Value {
79        match self {
80            None => Value::Null,
81            Some(inner) => inner.to_value(),
82        }
83    }
84}
85
86impl Configurable for bool {
87    fn metadata() -> Metadata {
88        Metadata::with_transparent(true)
89    }
90
91    fn generate_schema(_: &RefCell<SchemaGenerator>) -> Result<SchemaObject, GenerateError> {
92        Ok(generate_bool_schema())
93    }
94}
95
96impl ToValue for bool {
97    fn to_value(&self) -> Value {
98        Value::Bool(*self)
99    }
100}
101
102// Strings.
103impl Configurable for String {
104    fn metadata() -> Metadata {
105        Metadata::with_transparent(true)
106    }
107
108    fn generate_schema(_: &RefCell<SchemaGenerator>) -> Result<SchemaObject, GenerateError> {
109        Ok(generate_string_schema())
110    }
111}
112
113impl ToValue for String {
114    fn to_value(&self) -> Value {
115        Value::String(self.clone())
116    }
117}
118
119impl Configurable for KeyString {
120    fn metadata() -> Metadata {
121        Metadata::with_transparent(true)
122    }
123
124    fn generate_schema(_: &RefCell<SchemaGenerator>) -> Result<SchemaObject, GenerateError> {
125        Ok(generate_string_schema())
126    }
127}
128
129impl ToValue for KeyString {
130    fn to_value(&self) -> Value {
131        Value::String(self.clone().into())
132    }
133}
134
135impl Configurable for char {
136    fn metadata() -> Metadata {
137        let mut metadata = Metadata::with_transparent(true);
138        metadata.add_validation(Validation::Length {
139            minimum: Some(1),
140            maximum: Some(1),
141        });
142        metadata
143    }
144
145    fn generate_schema(_: &RefCell<SchemaGenerator>) -> Result<SchemaObject, GenerateError> {
146        Ok(generate_string_schema())
147    }
148}
149
150impl ToValue for char {
151    fn to_value(&self) -> Value {
152        Value::String(format!("{self}"))
153    }
154}
155
156// Numbers.
157macro_rules! impl_configurable_numeric {
158    ($ty:ty => $into:expr) => {
159        impl Configurable for $ty {
160            fn metadata() -> Metadata {
161                let mut metadata = Metadata::with_transparent(true);
162                let numeric_type = <Self as ConfigurableNumber>::class();
163                metadata.add_custom_attribute(CustomAttribute::kv(
164                    constants::DOCS_META_NUMERIC_TYPE,
165                    numeric_type,
166                ));
167
168                metadata
169            }
170
171            fn validate_metadata(metadata: &Metadata) -> Result<(), GenerateError> {
172                $crate::__ensure_numeric_validation_bounds::<Self>(metadata)
173            }
174
175            fn generate_schema(
176                _: &RefCell<SchemaGenerator>,
177            ) -> Result<SchemaObject, GenerateError> {
178                Ok(generate_number_schema::<Self>())
179            }
180        }
181
182        impl ToValue for $ty {
183            fn to_value(&self) -> Value {
184                let into = $into;
185                Value::Number(into(*self))
186            }
187        }
188    };
189}
190
191impl_configurable_numeric!(u8 => Into::into);
192impl_configurable_numeric!(u16 => Into::into);
193impl_configurable_numeric!(u32 => Into::into);
194impl_configurable_numeric!(u64 => Into::into);
195impl_configurable_numeric!(usize => Into::into);
196impl_configurable_numeric!(i8 => Into::into);
197impl_configurable_numeric!(i16 => Into::into);
198impl_configurable_numeric!(i32 => Into::into);
199impl_configurable_numeric!(i64 => Into::into);
200impl_configurable_numeric!(isize => Into::into);
201impl_configurable_numeric!(f32 => |v| Number::from_f64(v as f64).expect("Could not convert number to JSON"));
202impl_configurable_numeric!(f64 => |v| Number::from_f64(v).expect("Could not convert number to JSON"));
203impl_configurable_numeric!(NonZeroU8 => |v: NonZeroU8| v.get().into());
204impl_configurable_numeric!(NonZeroU16 => |v: NonZeroU16| v.get().into());
205impl_configurable_numeric!(NonZeroU32 => |v: NonZeroU32| v.get().into());
206impl_configurable_numeric!(NonZeroU64 => |v: NonZeroU64| v.get().into());
207impl_configurable_numeric!(NonZeroI8 => |v: NonZeroI8| v.get().into());
208impl_configurable_numeric!(NonZeroI16 => |v: NonZeroI16| v.get().into());
209impl_configurable_numeric!(NonZeroI32 => |v: NonZeroI32| v.get().into());
210impl_configurable_numeric!(NonZeroI64 => |v: NonZeroI64| v.get().into());
211impl_configurable_numeric!(NonZeroUsize => |v: NonZeroUsize| v.get().into());
212
213// Arrays and maps.
214impl<T> Configurable for Vec<T>
215where
216    T: Configurable + ToValue + 'static,
217{
218    fn metadata() -> Metadata {
219        T::metadata().convert()
220    }
221
222    fn validate_metadata(metadata: &Metadata) -> Result<(), GenerateError> {
223        let converted = metadata.convert();
224        T::validate_metadata(&converted)
225    }
226
227    fn generate_schema(
228        generator: &RefCell<SchemaGenerator>,
229    ) -> Result<SchemaObject, GenerateError> {
230        generate_array_schema(&T::as_configurable_ref(), generator)
231    }
232}
233
234impl<T: ToValue> ToValue for Vec<T> {
235    fn to_value(&self) -> Value {
236        Value::Array(self.iter().map(ToValue::to_value).collect())
237    }
238}
239
240impl<K, V> Configurable for BTreeMap<K, V>
241where
242    K: ConfigurableString + Ord + ToValue + 'static,
243    V: Configurable + ToValue + 'static,
244{
245    fn is_optional() -> bool {
246        // A map with required fields would be... an object.  So if you want that, make a struct
247        // instead, not a map.
248        true
249    }
250
251    fn metadata() -> Metadata {
252        Metadata::with_transparent(true)
253    }
254
255    fn validate_metadata(metadata: &Metadata) -> Result<(), GenerateError> {
256        let converted = metadata.convert();
257        V::validate_metadata(&converted)
258    }
259
260    fn generate_schema(
261        generator: &RefCell<SchemaGenerator>,
262    ) -> Result<SchemaObject, GenerateError> {
263        // Make sure our key type is _truly_ a string schema.
264        assert_string_schema_for_map(
265            &K::as_configurable_ref(),
266            generator,
267            std::any::type_name::<Self>(),
268        )?;
269
270        generate_map_schema(&V::as_configurable_ref(), generator)
271    }
272}
273
274impl<K, V> ToValue for BTreeMap<K, V>
275where
276    K: ToString,
277    V: ToValue,
278{
279    fn to_value(&self) -> Value {
280        Value::Object(
281            self.iter()
282                .map(|(k, v)| (k.to_string(), v.to_value()))
283                .collect(),
284        )
285    }
286}
287
288impl<V> Configurable for BTreeSet<V>
289where
290    V: Configurable + ToValue + Eq + Hash + 'static,
291{
292    fn metadata() -> Metadata {
293        Metadata::with_transparent(true)
294    }
295
296    fn validate_metadata(metadata: &Metadata) -> Result<(), GenerateError> {
297        let converted = metadata.convert();
298        V::validate_metadata(&converted)
299    }
300
301    fn generate_schema(
302        generator: &RefCell<SchemaGenerator>,
303    ) -> Result<SchemaObject, GenerateError> {
304        generate_set_schema(&V::as_configurable_ref(), generator)
305    }
306}
307
308impl<V: ToValue> ToValue for BTreeSet<V> {
309    fn to_value(&self) -> Value {
310        Value::Array(self.iter().map(ToValue::to_value).collect())
311    }
312}
313
314impl<K, V> Configurable for HashMap<K, V>
315where
316    K: ConfigurableString + ToValue + Hash + Eq + 'static,
317    V: Configurable + ToValue + 'static,
318{
319    fn is_optional() -> bool {
320        // A map with required fields would be... an object.  So if you want that, make a struct
321        // instead, not a map.
322        true
323    }
324
325    fn metadata() -> Metadata {
326        Metadata::with_transparent(true)
327    }
328
329    fn validate_metadata(metadata: &Metadata) -> Result<(), GenerateError> {
330        let converted = metadata.convert();
331        V::validate_metadata(&converted)
332    }
333
334    fn generate_schema(
335        generator: &RefCell<SchemaGenerator>,
336    ) -> Result<SchemaObject, GenerateError> {
337        // Make sure our key type is _truly_ a string schema.
338        assert_string_schema_for_map(
339            &K::as_configurable_ref(),
340            generator,
341            std::any::type_name::<Self>(),
342        )?;
343
344        generate_map_schema(&V::as_configurable_ref(), generator)
345    }
346}
347
348impl<K, V> ToValue for HashMap<K, V>
349where
350    K: ToString,
351    V: ToValue,
352{
353    fn to_value(&self) -> Value {
354        Value::Object(
355            self.iter()
356                .map(|(k, v)| (k.to_string(), v.to_value()))
357                .collect(),
358        )
359    }
360}
361
362impl<V> Configurable for HashSet<V>
363where
364    V: Configurable + ToValue + Eq + Hash + 'static,
365{
366    fn metadata() -> Metadata {
367        Metadata::with_transparent(true)
368    }
369
370    fn validate_metadata(metadata: &Metadata) -> Result<(), GenerateError> {
371        let converted = metadata.convert();
372        V::validate_metadata(&converted)
373    }
374
375    fn generate_schema(
376        generator: &RefCell<SchemaGenerator>,
377    ) -> Result<SchemaObject, GenerateError> {
378        generate_set_schema(&V::as_configurable_ref(), generator)
379    }
380}
381
382impl<V> ToValue for HashSet<V>
383where
384    V: ToValue,
385{
386    fn to_value(&self) -> Value {
387        Value::Array(self.iter().map(ToValue::to_value).collect())
388    }
389}
390
391// Additional types that do not map directly to scalars.
392impl Configurable for SocketAddr {
393    fn referenceable_name() -> Option<&'static str> {
394        Some("stdlib::SocketAddr")
395    }
396
397    fn metadata() -> Metadata {
398        let mut metadata = Metadata::default();
399        metadata.set_description("An internet socket address, either IPv4 or IPv6.");
400        metadata
401    }
402
403    fn generate_schema(_: &RefCell<SchemaGenerator>) -> Result<SchemaObject, GenerateError> {
404        // TODO: We don't need anything other than a string schema to (de)serialize a `SocketAddr`,
405        // but we eventually should have validation since the format for the possible permutations
406        // is well-known and can be easily codified.
407        Ok(generate_string_schema())
408    }
409}
410
411impl ToValue for SocketAddr {
412    fn to_value(&self) -> Value {
413        Value::String(self.to_string())
414    }
415}
416
417impl Configurable for Ipv4Addr {
418    fn referenceable_name() -> Option<&'static str> {
419        Some("stdlib::Ipv4Addr")
420    }
421
422    fn metadata() -> Metadata {
423        let mut metadata = Metadata::default();
424        metadata.set_description("An IPv4 address.");
425        metadata
426    }
427
428    fn generate_schema(_: &RefCell<SchemaGenerator>) -> Result<SchemaObject, GenerateError> {
429        // TODO: We don't need anything other than a string schema to (de)serialize a `Ipv4Addr`,
430        // but we eventually should have validation since the format for the possible permutations
431        // is well-known and can be easily codified.
432        Ok(generate_string_schema())
433    }
434}
435
436impl ToValue for Ipv4Addr {
437    fn to_value(&self) -> Value {
438        Value::String(self.to_string())
439    }
440}
441
442impl Configurable for PathBuf {
443    fn referenceable_name() -> Option<&'static str> {
444        Some("stdlib::PathBuf")
445    }
446
447    fn metadata() -> Metadata {
448        let mut metadata = Metadata::default();
449        metadata.set_description("A file path.");
450
451        // Taken from
452        // https://stackoverflow.com/questions/44289075/regular-expression-to-validate-windows-and-linux-path-with-extension
453        // and manually checked against common Linux and Windows paths. It's probably not 100% correct, but it
454        // definitely covers the most basic cases.
455        const PATH_REGEX: &str = r#"(\/.*|[a-zA-Z]:\\(?:([^<>:"\/\\|?*]*[^<>:"\/\\|?*.]\\|..\\)*([^<>:"\/\\|?*]*[^<>:"\/\\|?*.]\\?|..\\))?)"#;
456        metadata.add_validation(Validation::Pattern(PATH_REGEX.to_string()));
457
458        metadata
459    }
460
461    fn generate_schema(_: &RefCell<SchemaGenerator>) -> Result<SchemaObject, GenerateError> {
462        Ok(generate_string_schema())
463    }
464}
465
466impl ToValue for PathBuf {
467    fn to_value(&self) -> Value {
468        Value::String(self.display().to_string())
469    }
470}
471
472// The use of `Duration` is deprecated and will be removed in a future version
473impl Configurable for Duration {
474    fn referenceable_name() -> Option<&'static str> {
475        Some("stdlib::Duration")
476    }
477
478    fn metadata() -> Metadata {
479        let mut metadata = Metadata::default();
480        metadata.set_description("An duration of time.");
481        metadata
482    }
483
484    fn generate_schema(_: &RefCell<SchemaGenerator>) -> Result<SchemaObject, GenerateError> {
485        let mut properties = IndexMap::default();
486        properties.insert("secs".into(), generate_number_schema::<u64>());
487        properties.insert("nsecs".into(), generate_number_schema::<u32>());
488
489        let mut required = BTreeSet::default();
490        required.insert("secs".into());
491        required.insert("nsecs".into());
492
493        Ok(crate::schema::generate_struct_schema(
494            properties, required, None,
495        ))
496    }
497}
498
499impl ToValue for Duration {
500    fn to_value(&self) -> Value {
501        serde_json::to_value(self).expect("Could not convert duration to JSON")
502    }
503}