openzeppelin_relayer/services/plugins/
mod.rs

1//! Plugins service module for handling plugins execution and interaction with relayer
2
3use std::sync::Arc;
4
5use crate::{
6    jobs::JobProducerTrait,
7    models::{AppState, PluginCallRequest},
8};
9use actix_web::web;
10use async_trait::async_trait;
11use serde::{Deserialize, Serialize};
12use thiserror::Error;
13use uuid::Uuid;
14
15pub mod runner;
16pub use runner::*;
17
18pub mod relayer_api;
19pub use relayer_api::*;
20
21pub mod script_executor;
22pub use script_executor::*;
23
24pub mod socket;
25pub use socket::*;
26
27#[cfg(test)]
28use mockall::automock;
29
30#[derive(Error, Debug, Serialize)]
31pub enum PluginError {
32    #[error("Socket error: {0}")]
33    SocketError(String),
34    #[error("Plugin error: {0}")]
35    PluginError(String),
36    #[error("Relayer error: {0}")]
37    RelayerError(String),
38    #[error("Plugin execution error: {0}")]
39    PluginExecutionError(String),
40}
41
42impl From<PluginError> for String {
43    fn from(error: PluginError) -> Self {
44        error.to_string()
45    }
46}
47
48#[derive(Debug, Serialize, Deserialize, utoipa::ToSchema)]
49pub struct PluginCallResponse {
50    pub success: bool,
51    pub return_value: String,
52    pub message: String,
53    pub logs: Vec<LogEntry>,
54    pub error: String,
55    pub traces: Vec<serde_json::Value>,
56}
57
58#[derive(Default)]
59pub struct PluginService<R: PluginRunnerTrait> {
60    runner: R,
61}
62
63impl<R: PluginRunnerTrait> PluginService<R> {
64    pub fn new(runner: R) -> Self {
65        Self { runner }
66    }
67
68    async fn call_plugin<J: JobProducerTrait + 'static>(
69        &self,
70        code_path: String,
71        plugin_call_request: PluginCallRequest,
72        state: Arc<web::ThinData<AppState<J>>>,
73    ) -> Result<PluginCallResponse, PluginError> {
74        let socket_path = format!("/tmp/{}.sock", Uuid::new_v4());
75        let script_params = plugin_call_request.params.to_string();
76        let result = self
77            .runner
78            .run(&socket_path, code_path, script_params, state)
79            .await;
80
81        match result {
82            Ok(script_result) => Ok(PluginCallResponse {
83                success: true,
84                message: "Plugin called successfully".to_string(),
85                return_value: script_result.return_value,
86                logs: script_result.logs,
87                error: script_result.error,
88                traces: script_result.trace,
89            }),
90            Err(e) => Err(PluginError::PluginExecutionError(e.to_string())),
91        }
92    }
93}
94
95#[async_trait]
96#[cfg_attr(test, automock)]
97pub trait PluginServiceTrait<J: JobProducerTrait + 'static>: Send + Sync {
98    fn new(runner: PluginRunner) -> Self;
99    async fn call_plugin(
100        &self,
101        code_path: String,
102        plugin_call_request: PluginCallRequest,
103        state: Arc<web::ThinData<AppState<J>>>,
104    ) -> Result<PluginCallResponse, PluginError>;
105}
106
107#[async_trait]
108impl<J: JobProducerTrait + 'static> PluginServiceTrait<J> for PluginService<PluginRunner> {
109    fn new(runner: PluginRunner) -> Self {
110        Self::new(runner)
111    }
112
113    async fn call_plugin(
114        &self,
115        code_path: String,
116        plugin_call_request: PluginCallRequest,
117        state: Arc<web::ThinData<AppState<J>>>,
118    ) -> Result<PluginCallResponse, PluginError> {
119        self.call_plugin(code_path, plugin_call_request, state)
120            .await
121    }
122}
123
124#[cfg(test)]
125mod tests {
126    use crate::{
127        jobs::MockJobProducerTrait, models::PluginModel,
128        utils::mocks::mockutils::create_mock_app_state,
129    };
130
131    use super::*;
132
133    #[tokio::test]
134    async fn test_call_plugin() {
135        let plugin = PluginModel {
136            id: "test-plugin".to_string(),
137            path: "test-path".to_string(),
138        };
139        let app_state: AppState<MockJobProducerTrait> =
140            create_mock_app_state(None, None, None, Some(vec![plugin])).await;
141
142        let mut plugin_runner = MockPluginRunnerTrait::default();
143
144        plugin_runner
145            .expect_run::<MockJobProducerTrait>()
146            .returning(|_, _, _, _| {
147                Ok(ScriptResult {
148                    logs: vec![LogEntry {
149                        level: LogLevel::Log,
150                        message: "test-log".to_string(),
151                    }],
152                    error: "test-error".to_string(),
153                    return_value: "test-result".to_string(),
154                    trace: Vec::new(),
155                })
156            });
157
158        let plugin_service = PluginService::<MockPluginRunnerTrait>::new(plugin_runner);
159        let result = plugin_service
160            .call_plugin(
161                "test-plugin".to_string(),
162                PluginCallRequest {
163                    params: serde_json::Value::Null,
164                },
165                Arc::new(web::ThinData(app_state)),
166            )
167            .await;
168        assert!(result.is_ok());
169        let result = result.unwrap();
170        assert!(result.success);
171        assert_eq!(result.return_value, "test-result");
172    }
173
174    #[tokio::test]
175    async fn test_from_plugin_error_to_string() {
176        let error = PluginError::PluginExecutionError("test-error".to_string());
177        let result: String = error.into();
178        assert_eq!(result, "Plugin execution error: test-error");
179    }
180}