openzeppelin_relayer/config/config_file/
plugin.rs

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