Skip to main content

next_rs_actions/
action.rs

1use serde::{Deserialize, Serialize};
2use std::future::Future;
3use std::pin::Pin;
4
5pub type ActionResult<T> = Result<T, ActionError>;
6
7#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct ActionError {
9    pub message: String,
10    pub code: Option<String>,
11}
12
13impl ActionError {
14    pub fn new(message: impl Into<String>) -> Self {
15        Self {
16            message: message.into(),
17            code: None,
18        }
19    }
20
21    pub fn with_code(message: impl Into<String>, code: impl Into<String>) -> Self {
22        Self {
23            message: message.into(),
24            code: Some(code.into()),
25        }
26    }
27}
28
29impl std::fmt::Display for ActionError {
30    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
31        write!(f, "{}", self.message)
32    }
33}
34
35impl std::error::Error for ActionError {}
36
37pub trait ServerAction: Send + Sync {
38    type Input: for<'de> Deserialize<'de> + Send;
39    type Output: Serialize + Send;
40
41    fn action_id(&self) -> &str;
42
43    fn execute(
44        &self,
45        input: Self::Input,
46    ) -> Pin<Box<dyn Future<Output = ActionResult<Self::Output>> + Send>>;
47}
48
49pub struct Action<I, O, F>
50where
51    I: for<'de> Deserialize<'de> + Send,
52    O: Serialize + Send,
53    F: Fn(I) -> Pin<Box<dyn Future<Output = ActionResult<O>> + Send>> + Send + Sync,
54{
55    id: String,
56    handler: F,
57    _phantom: std::marker::PhantomData<(I, O)>,
58}
59
60impl<I, O, F> Action<I, O, F>
61where
62    I: for<'de> Deserialize<'de> + Send,
63    O: Serialize + Send,
64    F: Fn(I) -> Pin<Box<dyn Future<Output = ActionResult<O>> + Send>> + Send + Sync,
65{
66    pub fn new(id: impl Into<String>, handler: F) -> Self {
67        Self {
68            id: id.into(),
69            handler,
70            _phantom: std::marker::PhantomData,
71        }
72    }
73
74    pub fn id(&self) -> &str {
75        &self.id
76    }
77
78    pub async fn call(&self, input: I) -> ActionResult<O> {
79        (self.handler)(input).await
80    }
81}
82
83#[derive(Debug, Clone, Serialize, Deserialize)]
84pub struct ActionRequest {
85    pub action_id: String,
86    pub payload: serde_json::Value,
87}
88
89#[derive(Debug, Clone, Serialize, Deserialize)]
90pub struct ActionResponse {
91    pub success: bool,
92    pub data: Option<serde_json::Value>,
93    pub error: Option<ActionError>,
94}
95
96impl ActionResponse {
97    pub fn success<T: Serialize>(data: T) -> Self {
98        Self {
99            success: true,
100            data: serde_json::to_value(data).ok(),
101            error: None,
102        }
103    }
104
105    pub fn error(error: ActionError) -> Self {
106        Self {
107            success: false,
108            data: None,
109            error: Some(error),
110        }
111    }
112}
113
114#[cfg(test)]
115mod tests {
116    use super::*;
117
118    #[test]
119    fn test_action_error() {
120        let error = ActionError::new("Something went wrong");
121        assert_eq!(error.message, "Something went wrong");
122        assert!(error.code.is_none());
123
124        let error_with_code = ActionError::with_code("Not found", "404");
125        assert_eq!(error_with_code.code, Some("404".to_string()));
126    }
127
128    #[test]
129    fn test_action_response_success() {
130        let response = ActionResponse::success(serde_json::json!({"id": 1}));
131        assert!(response.success);
132        assert!(response.data.is_some());
133        assert!(response.error.is_none());
134    }
135
136    #[test]
137    fn test_action_response_error() {
138        let response = ActionResponse::error(ActionError::new("Failed"));
139        assert!(!response.success);
140        assert!(response.data.is_none());
141        assert!(response.error.is_some());
142    }
143
144    #[tokio::test]
145    async fn test_action_call() {
146        let action = Action::new("test-action", |input: String| {
147            Box::pin(async move { Ok(format!("Hello, {}!", input)) })
148        });
149
150        let result = action.call("World".to_string()).await;
151        assert!(result.is_ok());
152        assert_eq!(result.unwrap(), "Hello, World!");
153    }
154}