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
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
/*
 * Copyright 2021 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

use std::sync::Arc;

use crate::{
    config::ConfigType,
    filters::{CreationError, Filter, StaticFilter},
};

/// An owned pointer to a dynamic [`FilterFactory`] instance.
pub type DynFilterFactory = Box<dyn FilterFactory>;

/// The value returned by [`FilterFactory::create_filter`].
#[derive(Clone)]
#[non_exhaustive]
pub struct FilterInstance(Arc<FilterInstanceData>);

struct FilterInstanceData {
    /// The configuration used to create the filter.
    pub config: serde_json::Value,
    /// The configuration used to create the filter.
    pub label: Option<String>,
    /// The created filter.
    pub filter: Box<dyn Filter>,
}

impl FilterInstance {
    /// Constructs a [`FilterInstance`].
    pub fn new(config: serde_json::Value, filter: Box<dyn Filter>) -> Self {
        Self(Arc::new(FilterInstanceData {
            config,
            label: None,
            filter,
        }))
    }

    pub fn config(&self) -> &serde_json::Value {
        &self.0.config
    }

    pub fn label(&self) -> Option<&str> {
        self.0.label.as_deref()
    }

    pub fn filter(&self) -> &dyn Filter {
        &*self.0.filter
    }
}

/// Provides the name and creation function for a given [`Filter`].
///
pub trait FilterFactory: Sync + Send {
    /// name returns the configuration name for the Filter
    /// The returned string identifies the filter item's path with the following format:
    ///     `quilkin.filters.<module>.<version>.<item-name>`
    ///
    /// where:
    /// - `module`: The rust module name containing the filter item
    /// - `version`: The filter's version.
    /// - `item-name`: The name of the rust item (e.g enum, struct) implementing the filter.
    /// For example the `v1alpha1` version of the debug filter has the name:
    ///     `quilkin.filters.debug_filter.v1alpha1.Debug`
    fn name(&self) -> &'static str;

    /// Returns the schema for the configuration of the [`Filter`].
    fn config_schema(&self) -> schemars::schema::RootSchema;

    /// Returns a filter based on the provided arguments.
    fn create_filter(&self, args: CreateFilterArgs) -> Result<FilterInstance, CreationError>;

    /// Converts YAML configuration into its Protobuf equivalvent.
    fn encode_config_to_protobuf(
        &self,
        args: serde_json::Value,
    ) -> Result<prost_types::Any, CreationError>;

    /// Converts YAML configuration into its Protobuf equivalvent.
    fn encode_config_to_json(
        &self,
        args: prost_types::Any,
    ) -> Result<serde_json::Value, CreationError>;

    /// Returns the [`ConfigType`] from the provided Option, otherwise it returns
    /// Error::MissingConfig if the Option is None.
    fn require_config(&self, config: Option<ConfigType>) -> Result<ConfigType, CreationError> {
        config.ok_or_else(|| CreationError::MissingConfig(self.name()))
    }
}

impl<F> FilterFactory for std::marker::PhantomData<fn() -> F>
where
    F: StaticFilter + 'static,
    CreationError: From<<F::Configuration as TryFrom<F::BinaryConfiguration>>::Error>
        + From<<F::BinaryConfiguration as TryFrom<F::Configuration>>::Error>,
{
    fn name(&self) -> &'static str {
        F::NAME
    }

    fn config_schema(&self) -> schemars::schema::RootSchema {
        schemars::schema_for!(F::Configuration)
    }

    /// Returns a filter based on the provided arguments.
    fn create_filter(&self, args: CreateFilterArgs) -> Result<FilterInstance, CreationError> {
        let (config_json, config): (_, Option<F::Configuration>) = if let Some(config) = args.config
        {
            config
                .deserialize::<F::Configuration, F::BinaryConfiguration>(self.name())
                .map(|(j, c)| (j, Some(c)))?
        } else {
            (serde_json::Value::Null, None)
        };

        Ok(FilterInstance::new(
            config_json,
            Box::from(F::try_from_config(config)?),
        ))
    }

    fn encode_config_to_protobuf(
        &self,
        config: serde_json::Value,
    ) -> Result<prost_types::Any, CreationError> {
        let config: F::Configuration = serde_json::from_value(config)?;

        Ok(prost_types::Any {
            type_url: self.name().into(),
            value: crate::prost::encode::<F::BinaryConfiguration>(&config.try_into()?)?,
        })
    }

    fn encode_config_to_json(
        &self,
        config: prost_types::Any,
    ) -> Result<serde_json::Value, CreationError> {
        if self.name() != config.type_url {
            return Err(crate::filters::CreationError::MismatchedTypes {
                expected: self.name().into(),
                actual: config.type_url,
            });
        }

        let message = <F::BinaryConfiguration as prost::Message>::decode(&*config.value)?;
        let config = F::Configuration::try_from(message)?;

        Ok(serde_json::to_value(&config)?)
    }
}

/// Arguments needed to create a new filter.
pub struct CreateFilterArgs {
    /// Configuration for the filter.
    pub config: Option<ConfigType>,
}

impl CreateFilterArgs {
    /// Create a new instance of [`CreateFilterArgs`].
    pub fn new(config: Option<ConfigType>) -> CreateFilterArgs {
        Self { config }
    }

    /// Creates a new instance of [`CreateFilterArgs`] using a
    /// fixed [`ConfigType`].
    pub fn fixed(config: Option<serde_json::Value>) -> CreateFilterArgs {
        Self::new(config.map(ConfigType::Static))
    }

    /// Creates a new instance of [`CreateFilterArgs`] using a
    /// dynamic [`ConfigType`].
    pub fn dynamic(config: Option<prost_types::Any>) -> CreateFilterArgs {
        CreateFilterArgs::new(config.map(ConfigType::Dynamic))
    }
}