leptos_server/
action.rs

1use reactive_graph::{
2    actions::{Action, ArcAction},
3    owner::use_context,
4    traits::DefinedAt,
5};
6use server_fn::{
7    error::{FromServerFnError, ServerFnUrlError},
8    ServerFn,
9};
10use std::{ops::Deref, panic::Location, sync::Arc};
11
12/// An error that can be caused by a server action.
13///
14/// This is used for propagating errors from the server to the client when JS/WASM are not
15/// supported.
16#[derive(Clone, Debug, PartialEq, Eq)]
17pub struct ServerActionError {
18    path: Arc<str>,
19    err: Arc<str>,
20}
21
22impl ServerActionError {
23    /// Creates a new error associated with the given path.
24    pub fn new(path: &str, err: &str) -> Self {
25        Self {
26            path: path.into(),
27            err: err.into(),
28        }
29    }
30
31    /// The path with which this error is associated.
32    pub fn path(&self) -> &str {
33        &self.path
34    }
35
36    /// The error message.
37    pub fn err(&self) -> &str {
38        &self.err
39    }
40}
41
42/// An [`ArcAction`] that can be used to call a server function.
43pub struct ArcServerAction<S>
44where
45    S: ServerFn + 'static,
46    S::Output: 'static,
47{
48    inner: ArcAction<S, Result<S::Output, S::Error>>,
49    #[cfg(any(debug_assertions, leptos_debuginfo))]
50    defined_at: &'static Location<'static>,
51}
52
53impl<S> ArcServerAction<S>
54where
55    S: ServerFn + Clone + Send + Sync + 'static,
56    S::Output: Send + Sync + 'static,
57    S::Error: Send + Sync + 'static,
58    S::Error: FromServerFnError,
59{
60    /// Creates a new [`ArcAction`] that will call the server function `S` when dispatched.
61    #[track_caller]
62    pub fn new() -> Self {
63        let err = use_context::<ServerActionError>().and_then(|error| {
64            (error.path() == S::PATH)
65                .then(|| ServerFnUrlError::<S::Error>::decode_err(error.err()))
66                .map(Err)
67        });
68        Self {
69            inner: ArcAction::new_with_value(err, |input: &S| {
70                S::run_on_client(input.clone())
71            }),
72            #[cfg(any(debug_assertions, leptos_debuginfo))]
73            defined_at: Location::caller(),
74        }
75    }
76}
77
78impl<S> Deref for ArcServerAction<S>
79where
80    S: ServerFn + 'static,
81    S::Output: 'static,
82{
83    type Target = ArcAction<S, Result<S::Output, S::Error>>;
84
85    fn deref(&self) -> &Self::Target {
86        &self.inner
87    }
88}
89
90impl<S> Clone for ArcServerAction<S>
91where
92    S: ServerFn + 'static,
93    S::Output: 'static,
94{
95    fn clone(&self) -> Self {
96        Self {
97            inner: self.inner.clone(),
98            #[cfg(any(debug_assertions, leptos_debuginfo))]
99            defined_at: self.defined_at,
100        }
101    }
102}
103
104impl<S> Default for ArcServerAction<S>
105where
106    S: ServerFn + Clone + Send + Sync + 'static,
107    S::Output: Send + Sync + 'static,
108    S::Error: Send + Sync + 'static,
109{
110    fn default() -> Self {
111        Self::new()
112    }
113}
114
115impl<S> DefinedAt for ArcServerAction<S>
116where
117    S: ServerFn + 'static,
118    S::Output: 'static,
119{
120    fn defined_at(&self) -> Option<&'static Location<'static>> {
121        #[cfg(any(debug_assertions, leptos_debuginfo))]
122        {
123            Some(self.defined_at)
124        }
125        #[cfg(not(any(debug_assertions, leptos_debuginfo)))]
126        {
127            None
128        }
129    }
130}
131
132/// An [`Action`] that can be used to call a server function.
133pub struct ServerAction<S>
134where
135    S: ServerFn + 'static,
136    S::Output: 'static,
137{
138    inner: Action<S, Result<S::Output, S::Error>>,
139    #[cfg(any(debug_assertions, leptos_debuginfo))]
140    defined_at: &'static Location<'static>,
141}
142
143impl<S> ServerAction<S>
144where
145    S: ServerFn + Send + Sync + Clone + 'static,
146    S::Output: Send + Sync + 'static,
147    S::Error: Send + Sync + 'static,
148{
149    /// Creates a new [`Action`] that will call the server function `S` when dispatched.
150    pub fn new() -> Self {
151        let err = use_context::<ServerActionError>().and_then(|error| {
152            (error.path() == S::PATH)
153                .then(|| ServerFnUrlError::<S::Error>::decode_err(error.err()))
154                .map(Err)
155        });
156        Self {
157            inner: Action::new_with_value(err, |input: &S| {
158                S::run_on_client(input.clone())
159            }),
160            #[cfg(any(debug_assertions, leptos_debuginfo))]
161            defined_at: Location::caller(),
162        }
163    }
164}
165
166impl<S> Clone for ServerAction<S>
167where
168    S: ServerFn + 'static,
169    S::Output: 'static,
170{
171    fn clone(&self) -> Self {
172        *self
173    }
174}
175
176impl<S> Copy for ServerAction<S>
177where
178    S: ServerFn + 'static,
179    S::Output: 'static,
180{
181}
182
183impl<S> Deref for ServerAction<S>
184where
185    S: ServerFn + Clone + Send + Sync + 'static,
186    S::Output: Send + Sync + 'static,
187    S::Error: Send + Sync + 'static,
188{
189    type Target = Action<S, Result<S::Output, S::Error>>;
190
191    fn deref(&self) -> &Self::Target {
192        &self.inner
193    }
194}
195
196impl<S> From<ServerAction<S>> for Action<S, Result<S::Output, S::Error>>
197where
198    S: ServerFn + 'static,
199    S::Output: 'static,
200{
201    fn from(value: ServerAction<S>) -> Self {
202        value.inner
203    }
204}
205
206impl<S> Default for ServerAction<S>
207where
208    S: ServerFn + Clone + Send + Sync + 'static,
209    S::Output: Send + Sync + 'static,
210    S::Error: Send + Sync + 'static,
211{
212    fn default() -> Self {
213        Self::new()
214    }
215}
216
217impl<S> DefinedAt for ServerAction<S>
218where
219    S: ServerFn + 'static,
220    S::Output: 'static,
221{
222    fn defined_at(&self) -> Option<&'static Location<'static>> {
223        #[cfg(any(debug_assertions, leptos_debuginfo))]
224        {
225            Some(self.defined_at)
226        }
227        #[cfg(not(any(debug_assertions, leptos_debuginfo)))]
228        {
229            None
230        }
231    }
232}