openzeppelin_relayer/config/config_file/
plugin.rs

1use std::collections::HashSet;
2
3use crate::config::ConfigFileError;
4use serde::{Deserialize, Serialize};
5
6// TODO: in case we want to support other languages and add
7// more flexibility to the plugins folder, we should
8// move this to a config file
9const PLUGIN_FILE_TYPE: &str = ".ts";
10const PLUGIN_LANG: &str = "typescript";
11
12#[derive(Debug, Serialize, Deserialize, Clone)]
13pub struct PluginFileConfig {
14    pub id: String,
15    pub path: String,
16    pub timeout: Option<u64>,
17    #[serde(default)]
18    pub emit_logs: bool,
19    #[serde(default)]
20    pub emit_traces: bool,
21}
22
23pub struct PluginsFileConfig {
24    pub plugins: Vec<PluginFileConfig>,
25}
26
27impl PluginsFileConfig {
28    pub fn new(plugins: Vec<PluginFileConfig>) -> Self {
29        Self { plugins }
30    }
31
32    pub fn validate(&self) -> Result<(), ConfigFileError> {
33        let mut ids = HashSet::new();
34        for plugin in &self.plugins {
35            if !ids.insert(plugin.id.clone()) {
36                return Err(ConfigFileError::DuplicateId(plugin.id.clone()));
37            }
38
39            if plugin.id.is_empty() {
40                return Err(ConfigFileError::MissingField("id".into()));
41            }
42
43            if plugin.path.is_empty() {
44                return Err(ConfigFileError::MissingField("path".into()));
45            }
46
47            // validate timeout
48            if let Some(timeout) = plugin.timeout {
49                if timeout == 0 {
50                    return Err(ConfigFileError::InvalidTimeout(timeout));
51                }
52            }
53
54            if !plugin.path.ends_with(PLUGIN_FILE_TYPE) {
55                return Err(ConfigFileError::InvalidFormat(format!(
56                    "Plugin path must be a {PLUGIN_LANG} file (ends with '{PLUGIN_FILE_TYPE}')"
57                )));
58            }
59        }
60
61        Ok(())
62    }
63}