next_rs_actions/
action.rs1use 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}