1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
#![deny(missing_docs)]

use std::cell::RefCell;

use serde_json::Value;

use crate::{
    schema::{SchemaGenerator, SchemaObject},
    GenerateError, Metadata,
};

/// A type that can be represented in a Vector configuration.
///
/// In Vector, we want to be able to generate a schema for our configuration such that we can have a Rust-agnostic
/// definition of exactly what is configurable, what values are allowed, what bounds exist, and so on and so forth.
///
/// `Configurable` provides the machinery to allow describing and encoding the shape of a type, recursively, so that by
/// instrumenting all transitive types of the configuration, the schema can be discovered by generating the schema from
/// some root type.
pub trait Configurable {
    /// Gets the referenceable name of this value, if any.
    ///
    /// When specified, this implies the value is both complex and standardized, and should be
    /// reused within any generated schema it is present in.
    fn referenceable_name() -> Option<&'static str>
    where
        Self: Sized,
    {
        None
    }

    /// Whether or not this value is optional.
    ///
    /// This is specifically used to determine when a field is inherently optional, such as a field
    /// that is a true map like `HashMap<K, V>`. This doesn't apply to objects (i.e. structs)
    /// because structs are implicitly non-optional: they have a fixed shape and size, and so on.
    ///
    /// Maps, by definition, are inherently free-form, and thus inherently optional. Thus, this
    /// method should likely not be overridden except for implementing `Configurable` for map
    /// types. If you're using it for something else, you are expected to know what you're doing.
    fn is_optional() -> bool
    where
        Self: Sized,
    {
        false
    }

    /// Gets the metadata for this value.
    fn metadata() -> Metadata
    where
        Self: Sized,
    {
        Metadata::default()
    }

    /// Validates the given metadata against this type.
    ///
    /// This allows for validating specific aspects of the given metadata, such as validation
    /// bounds, and so on, to ensure they are valid for the given type. In some cases, such as with
    /// numeric types, there is a limited amount of validation that can occur within the
    /// `Configurable` derive macro, and additional validation must happen at runtime when the
    /// `Configurable` trait is being used, which this method allows for.
    fn validate_metadata(_metadata: &Metadata) -> Result<(), GenerateError>
    where
        Self: Sized,
    {
        Ok(())
    }

    /// Generates the schema for this value.
    ///
    /// # Errors
    ///
    /// If an error occurs while generating the schema, an error variant will be returned describing
    /// the issue.
    fn generate_schema(gen: &RefCell<SchemaGenerator>) -> Result<SchemaObject, GenerateError>
    where
        Self: Sized;

    /// Create a new configurable reference table.
    fn as_configurable_ref() -> ConfigurableRef
    where
        Self: Sized + 'static,
    {
        ConfigurableRef::new::<Self>()
    }
}

/// A type that can be converted directly to a `serde_json::Value`. This is used when translating
/// the default value in a `Metadata` into a schema object.
pub trait ToValue {
    /// Convert this value into a `serde_json::Value`. Must not fail.
    fn to_value(&self) -> Value;
}

/// A pseudo-reference to a type that can be represented in a Vector configuration. This is
/// composed of references to all the class trait functions.
pub struct ConfigurableRef {
    // TODO: Turn this into a plain value once this is resolved:
    // https://github.com/rust-lang/rust/issues/63084
    type_name: fn() -> &'static str,
    // TODO: Turn this into a plain value once const trait functions are implemented
    // Ref: https://github.com/rust-lang/rfcs/pull/911
    referenceable_name: fn() -> Option<&'static str>,
    make_metadata: fn() -> Metadata,
    validate_metadata: fn(&Metadata) -> Result<(), GenerateError>,
    generate_schema: fn(&RefCell<SchemaGenerator>) -> Result<SchemaObject, GenerateError>,
}

impl ConfigurableRef {
    /// Create a new configurable reference table.
    pub const fn new<T: Configurable + 'static>() -> Self {
        Self {
            type_name: std::any::type_name::<T>,
            referenceable_name: T::referenceable_name,
            make_metadata: T::metadata,
            validate_metadata: T::validate_metadata,
            generate_schema: T::generate_schema,
        }
    }

    pub(crate) fn type_name(&self) -> &'static str {
        (self.type_name)()
    }
    pub(crate) fn referenceable_name(&self) -> Option<&'static str> {
        (self.referenceable_name)()
    }
    pub(crate) fn make_metadata(&self) -> Metadata {
        (self.make_metadata)()
    }
    pub(crate) fn validate_metadata(&self, metadata: &Metadata) -> Result<(), GenerateError> {
        (self.validate_metadata)(metadata)
    }
    pub(crate) fn generate_schema(
        &self,
        gen: &RefCell<SchemaGenerator>,
    ) -> Result<SchemaObject, GenerateError> {
        (self.generate_schema)(gen)
    }
}