openzeppelin_relayer/services/plugins/
mod.rs1use 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}