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