flams_router_base/
lib.rs

1#![allow(clippy::must_use_candidate)]
2#![cfg_attr(docsrs, feature(doc_auto_cfg))]
3
4#[cfg(any(
5    all(feature = "ssr", feature = "hydrate", not(feature = "docs-only")),
6    not(any(feature = "ssr", feature = "hydrate"))
7))]
8compile_error!("exactly one of the features \"ssr\" or \"hydrate\" must be enabled");
9
10mod login_state;
11pub use login_state::*;
12pub mod uris;
13pub mod ws;
14
15use leptos::{either::EitherOf3, prelude::*};
16
17pub fn vscode_link(
18    archive: &flams_ontology::uris::ArchiveId,
19    rel_path: &str,
20) -> impl IntoView + use<> {
21    let href = format!("vscode://kwarc.flams/open?a={archive}&rp={rel_path}");
22    view! {
23        <a href=href><thaw::Icon icon=icondata_tb::TbBrandVscodeOutline/></a>
24    }
25}
26
27#[component]
28pub fn RequireLogin<Ch: IntoView + 'static>(children: TypedChildren<Ch>) -> impl IntoView {
29    require_login(children.into_inner())
30}
31
32pub fn require_login<Ch: IntoView + 'static>(
33    children: impl FnOnce() -> Ch + Send + 'static,
34) -> impl IntoView {
35    use flams_web_utils::components::{Spinner, display_error};
36
37    let children = std::sync::Arc::new(flams_utils::parking_lot::Mutex::new(Some(children)));
38    move || match LoginState::get() {
39        LoginState::Loading => EitherOf3::A(view!(<Spinner/>)),
40        LoginState::Admin | LoginState::NoAccounts | LoginState::User { is_admin: true, .. } => {
41            EitherOf3::B((children.clone().lock().take()).map(|f| f()))
42        }
43        _ => EitherOf3::C(view!(<div>{display_error("Not logged in".into())}</div>)),
44    }
45}
46
47#[cfg(feature = "ssr")]
48/// #### Errors
49pub fn get_oauth() -> Result<(flams_git::gl::auth::GitLabOAuth, String), String> {
50    use flams_git::gl::auth::GitLabOAuth;
51    use leptos::prelude::*;
52    let Some(session) = use_context::<axum_login::AuthSession<flams_database::DBBackend>>() else {
53        return Err("Internal Error".to_string());
54    };
55    let Some(user) = session.user else {
56        return Err("Not logged in".to_string());
57    };
58    let Some(oauth): Option<GitLabOAuth> = expect_context() else {
59        return Err("Not Gitlab integration set up".to_string());
60    };
61    Ok((oauth, user.secret))
62}
63
64pub trait ServerFnExt {
65    type Output;
66    type Error;
67    #[cfg(feature = "hydrate")]
68    #[allow(async_fn_in_trait)]
69    async fn call_remote(self, url: String) -> Result<Self::Output, Self::Error>;
70}
71/*
72#[cfg(feature = "hydrate")]
73mod hydrate {
74    use super::ServerFnExt;
75    use leptos::{
76        prelude::{FromServerFnError, ServerFnError},
77        server_fn::{
78            self,
79            codec::{FromReq, FromRes, IntoReq, IntoRes},
80            request::BrowserMockReq,
81            response::BrowserMockRes,
82        },
83    };
84
85    impl<
86        In: server_fn::codec::Encoding,
87        Out: server_fn::codec::Encoding,
88        Err: server_fn::serde::Serialize
89            + server_fn::serde::de::DeserializeOwned
90            + std::fmt::Debug
91            + Clone
92            + Send
93            + Sync
94            + std::fmt::Display
95            + std::str::FromStr
96            + 'static,
97        F: leptos::server_fn::ServerFn<
98                Client = leptos::server_fn::client::browser::BrowserClient,
99                Server = leptos::server_fn::mock::BrowserMockServer,
100                Protocol = leptos::server_fn::Http<In, Out>,
101                Error = ServerFnError<Err>,
102                InputStreamError = ServerFnError<Err>,
103                OutputStreamError = ServerFnError<Err>,
104            > + FromReq<In, BrowserMockReq, F::Error>
105            + IntoReq<In, server_fn::request::browser::BrowserRequest, F::Error>
106            + server_fn::serde::Serialize
107            + server_fn::serde::de::DeserializeOwned
108            + std::fmt::Debug
109            + Clone
110            + Send
111            + Sync
112            + for<'de> server_fn::serde::Deserialize<'de>,
113    > ServerFnExt for F
114    where
115        F::Output: IntoRes<Out, BrowserMockRes, F::Error>
116            + FromRes<Out, server_fn::response::browser::BrowserResponse, F::Error>
117            + IntoReq<In, server_fn::request::browser::BrowserRequest, F::Error>,
118    {
119        type Output = <Self as leptos::server_fn::ServerFn>::Output;
120        type Error = F::Error;
121        #[cfg(feature = "hydrate")]
122        async fn call_remote(self, url: String) -> Result<Self::Output, Self::Error> {
123            use server_fn::response::ClientRes;
124            let input = self;
125            let path = format!("{}{}", url, F::PATH);
126            let path: &str = &path;
127            // ---------------------------------------
128            /*
129            Ok(<F::Protocol as server_fn::Protocol<
130                F,
131                F::Output,
132                F::Client,
133                F::Server,
134                F::Error,
135                F::Error,
136                F::Error,
137            >>::run_client(path, input)
138            .await?)
139            */
140
141            // create and send request on client
142            let req = input.into_req(path, Out::CONTENT_TYPE)?;
143            let req = TODO SOMETHING ELSE;
144            let res =
145                <leptos::server_fn::client::browser::BrowserClient as server_fn::client::Client<
146                    ServerFnError<Err>,
147                    ServerFnError<Err>,
148                    ServerFnError<Err>,
149                >>::send(req)
150                .await?;
151
152            let status = <server_fn::response::browser::BrowserResponse as ClientRes<
153                ServerFnError<Err>,
154            >>::status(&res);
155            let location = <server_fn::response::browser::BrowserResponse as ClientRes<
156                ServerFnError<Err>,
157            >>::location(&res);
158            let has_redirect_header = <server_fn::response::browser::BrowserResponse as ClientRes<
159                ServerFnError<Err>,
160            >>::has_redirect(&res);
161
162            // if it returns an error status, deserialize the error using the error's decoder.
163            let res = if (400..=599).contains(&status) {
164                let bytes = <server_fn::response::browser::BrowserResponse as ClientRes<
165                    ServerFnError<Err>,
166                >>::try_into_bytes(res);
167                Err(ServerFnError::de(bytes.await?))
168            } else {
169                // otherwise, deserialize the body as is
170                let output =
171                    <Self::Output as FromRes<Out, _, ServerFnError<Err>>>::from_res(res).await?;
172                Ok(output)
173            }?;
174
175            // if redirected, call the redirect hook (if that's been set)
176            if (300..=399).contains(&status) || has_redirect_header {
177                server_fn::redirect::call_redirect_hook(&location);
178            }
179            Ok(res)
180        }
181    }
182}
183 */
184
185#[cfg(feature = "hydrate")]
186mod hydrate {
187    use super::ServerFnExt;
188    use bytes::Bytes;
189    use futures::{Stream, StreamExt};
190    use leptos::server_fn::codec::{Encoding, FromReq, FromRes, IntoReq, IntoRes};
191    use leptos::server_fn::request::BrowserMockReq;
192    use leptos::server_fn::request::browser::{BrowserRequest as OrigBrowserRequest, Request};
193    use leptos::server_fn::response::BrowserMockRes;
194    use leptos::server_fn::response::browser::BrowserResponse as OrigBrowserResponse;
195    use leptos::{
196        prelude::*,
197        server_fn::{request::ClientReq, response::ClientRes},
198        wasm_bindgen::JsCast,
199    };
200    use send_wrapper::SendWrapper;
201    use wasm_streams::ReadableStream;
202
203    // -------------------------------------
204    //
205    struct Paired<
206        In,
207        F: leptos::server_fn::ServerFn<
208                Client = leptos::server_fn::client::browser::BrowserClient,
209                Server = leptos::server_fn::mock::BrowserMockServer,
210                Protocol = leptos::server_fn::Http<In, server_fn::codec::Json>, //OutputEncoding = leptos::server_fn::codec::Json,
211            >,
212    > {
213        sfn: F,
214        url: String,
215    }
216
217    struct BrowserRequest(SendWrapper<Request>);
218    struct BrowserFormData(SendWrapper<leptos::web_sys::FormData>);
219    struct EncodingWrap<E: Encoding>(E);
220
221    impl<E: Encoding> server_fn::ContentType for EncodingWrap<E> {
222        const CONTENT_TYPE: &'static str = E::CONTENT_TYPE;
223    }
224    impl<E: Encoding> Encoding for EncodingWrap<E> {
225        const METHOD: http::Method = E::METHOD;
226    }
227
228    impl<
229        In: Encoding,
230        Err: server_fn::serde::Serialize
231            + server_fn::serde::de::DeserializeOwned
232            + std::fmt::Debug
233            + Clone
234            + Send
235            + Sync
236            + std::fmt::Display
237            + std::str::FromStr
238            + 'static,
239        F: leptos::server_fn::ServerFn<
240                Client = leptos::server_fn::client::browser::BrowserClient,
241                Server = leptos::server_fn::mock::BrowserMockServer,
242                Protocol = leptos::server_fn::Http<In, server_fn::codec::Json>,
243                Error = ServerFnError<Err>,
244                InputStreamError = ServerFnError<Err>,
245                OutputStreamError = ServerFnError<Err>,
246            > + FromReq<In, BrowserMockReq, ServerFnError<Err>>,
247    > FromReq<EncodingWrap<In>, BrowserMockReq, ServerFnError<Err>> for Paired<In, F>
248    {
249        async fn from_req(req: BrowserMockReq) -> Result<Self, ServerFnError<Err>> {
250            Ok(Self {
251                sfn: <F as FromReq<In, BrowserMockReq, ServerFnError<Err>>>::from_req(req).await?,
252                url: String::new(),
253            })
254        }
255    }
256
257    impl<
258        In: Encoding,
259        Err: server_fn::serde::Serialize
260            + server_fn::serde::de::DeserializeOwned
261            + std::fmt::Debug
262            + Clone
263            + Send
264            + Sync
265            + std::fmt::Display
266            + std::str::FromStr
267            + 'static,
268        F: leptos::server_fn::ServerFn<
269                Client = leptos::server_fn::client::browser::BrowserClient,
270                Server = leptos::server_fn::mock::BrowserMockServer,
271                Protocol = leptos::server_fn::Http<In, server_fn::codec::Json>,
272                Error = ServerFnError<Err>,
273                InputStreamError = ServerFnError<Err>,
274                OutputStreamError = ServerFnError<Err>,
275            > + IntoReq<In, OrigBrowserRequest, ServerFnError<Err>>,
276    > IntoReq<EncodingWrap<In>, BrowserRequest, ServerFnError<Err>> for Paired<In, F>
277    {
278        fn into_req(
279            self,
280            _path: &str,
281            accepts: &str,
282        ) -> Result<BrowserRequest, ServerFnError<Err>> {
283            let Paired { sfn, url } = self;
284            let path = format!("{}{}", url, F::PATH);
285            let req = <F as IntoReq<In, OrigBrowserRequest, _>>::into_req(sfn, &path, accepts)?;
286            let req: Request = req.into();
287            Ok(BrowserRequest(SendWrapper::new(req)))
288        }
289    }
290
291    impl<
292        In: Encoding,
293        Err: server_fn::serde::Serialize
294            + server_fn::serde::de::DeserializeOwned
295            + std::fmt::Debug
296            + Clone
297            + Send
298            + Sync
299            + std::fmt::Display
300            + std::str::FromStr
301            + 'static,
302        F: leptos::server_fn::ServerFn<
303                Client = leptos::server_fn::client::browser::BrowserClient,
304                Server = leptos::server_fn::mock::BrowserMockServer,
305                Protocol = leptos::server_fn::Http<In, server_fn::codec::Json>,
306                Error = ServerFnError<Err>,
307                InputStreamError = ServerFnError<Err>,
308                OutputStreamError = ServerFnError<Err>,
309            >,
310    > leptos::server_fn::ServerFn for Paired<In, F>
311    where
312        F: FromReq<In, BrowserMockReq, ServerFnError<Err>>
313            + IntoReq<In, OrigBrowserRequest, ServerFnError<Err>>
314            + server_fn::serde::Serialize
315            + server_fn::serde::de::DeserializeOwned
316            + std::fmt::Debug
317            + Clone
318            + Send
319            + Sync
320            + for<'de> server_fn::serde::Deserialize<'de>,
321        /*Paired<In, F>: IntoReq<In, OrigBrowserRequest, ServerFnError<Err>>
322        + FromReq<In, BrowserMockReq, ServerFnError<Err>>
323        + Send,*/
324        F::Output: IntoRes<server_fn::codec::Json, BrowserMockRes, ServerFnError<Err>>
325            + FromRes<server_fn::codec::Json, OrigBrowserResponse, ServerFnError<Err>>
326            + Send
327            + for<'de> server_fn::serde::Deserialize<'de>,
328    {
329        const PATH: &'static str = F::PATH;
330        type Client = ClientWrap;
331        type Server = F::Server;
332        type Protocol = leptos::server_fn::Http<EncodingWrap<In>, server_fn::codec::Json>;
333        type Output = F::Output;
334        type Error = ServerFnError<Err>;
335        type InputStreamError = ServerFnError<Err>;
336        type OutputStreamError = ServerFnError<Err>;
337        fn middlewares() -> Vec<
338            std::sync::Arc<
339                dyn server_fn::middleware::Layer<
340                        <Self::Server as server_fn::server::Server<Self::Error>>::Request,
341                        <Self::Server as server_fn::server::Server<Self::Error>>::Response,
342                    >,
343            >,
344        > {
345            F::middlewares()
346        }
347        async fn run_body(self) -> Result<Self::Output, Self::Error> {
348            unreachable!()
349        }
350    }
351
352    impl<E: FromServerFnError> ClientReq<E> for BrowserRequest {
353        type FormData = BrowserFormData;
354
355        fn try_new_req_query(
356            path: &str,
357            content_type: &str,
358            accepts: &str,
359            query: &str,
360            method: http::Method,
361        ) -> Result<Self, E> {
362            let mut url = String::with_capacity(path.len() + 1 + query.len());
363            url.push_str(path);
364            url.push('?');
365            url.push_str(query);
366            let inner = match method {
367                http::Method::GET => Request::get(&url),
368                http::Method::DELETE => Request::delete(&url),
369                http::Method::POST => Request::post(&url),
370                http::Method::PUT => Request::put(&url),
371                http::Method::PATCH => Request::patch(&url),
372                m => {
373                    return Err(E::from_server_fn_error(
374                        ServerFnErrorErr::UnsupportedRequestMethod(m.to_string()),
375                    ));
376                }
377            };
378            Ok(Self(SendWrapper::new(
379                inner
380                    .header("Content-Type", content_type)
381                    .header("Accept", accepts)
382                    .build()
383                    .map_err(|e| {
384                        E::from_server_fn_error(ServerFnErrorErr::Request(e.to_string()))
385                    })?,
386            )))
387        }
388
389        fn try_new_req_text(
390            path: &str,
391            content_type: &str,
392            accepts: &str,
393            body: String,
394            method: http::Method,
395        ) -> Result<Self, E> {
396            let url = path;
397            let inner = match method {
398                http::Method::POST => Request::post(&url),
399                http::Method::PATCH => Request::patch(&url),
400                http::Method::PUT => Request::put(&url),
401                m => {
402                    return Err(E::from_server_fn_error(
403                        ServerFnErrorErr::UnsupportedRequestMethod(m.to_string()),
404                    ));
405                }
406            };
407            Ok(Self(SendWrapper::new(
408                inner
409                    .header("Content-Type", content_type)
410                    .header("Accept", accepts)
411                    .body(body)
412                    .map_err(|e| {
413                        E::from_server_fn_error(ServerFnErrorErr::Request(e.to_string()))
414                    })?,
415            )))
416        }
417
418        fn try_new_req_bytes(
419            path: &str,
420            content_type: &str,
421            accepts: &str,
422            body: Bytes,
423            method: http::Method,
424        ) -> Result<Self, E> {
425            let url = path;
426            let body: &[u8] = &body;
427            let body = leptos::web_sys::js_sys::Uint8Array::from(body).buffer();
428            let inner = match method {
429                http::Method::POST => Request::post(&url),
430                http::Method::PATCH => Request::patch(&url),
431                http::Method::PUT => Request::put(&url),
432                m => {
433                    return Err(E::from_server_fn_error(
434                        ServerFnErrorErr::UnsupportedRequestMethod(m.to_string()),
435                    ));
436                }
437            };
438            Ok(Self(SendWrapper::new(
439                inner
440                    .header("Content-Type", content_type)
441                    .header("Accept", accepts)
442                    .body(body)
443                    .map_err(|e| {
444                        E::from_server_fn_error(ServerFnErrorErr::Request(e.to_string()))
445                    })?,
446            )))
447        }
448
449        fn try_new_req_multipart(
450            path: &str,
451            accepts: &str,
452            body: Self::FormData,
453            method: http::Method,
454        ) -> Result<Self, E> {
455            let url = path;
456            let inner = match method {
457                http::Method::POST => Request::post(&url),
458                http::Method::PATCH => Request::patch(&url),
459                http::Method::PUT => Request::put(&url),
460                m => {
461                    return Err(E::from_server_fn_error(
462                        ServerFnErrorErr::UnsupportedRequestMethod(m.to_string()),
463                    ));
464                }
465            };
466            Ok(Self(SendWrapper::new(
467                inner
468                    .header("Accept", accepts)
469                    .body(body.0.take())
470                    .map_err(|e| {
471                        E::from_server_fn_error(ServerFnErrorErr::Request(e.to_string()))
472                    })?,
473            )))
474        }
475
476        fn try_new_req_form_data(
477            path: &str,
478            accepts: &str,
479            content_type: &str,
480            body: Self::FormData,
481            method: http::Method,
482        ) -> Result<Self, E> {
483            let form_data = body.0.take();
484            let url_params =
485                leptos::web_sys::UrlSearchParams::new_with_str_sequence_sequence(&form_data)
486                    .map_err(|e| {
487                        E::from_server_fn_error(ServerFnErrorErr::Serialization(
488                            e.as_string().unwrap_or_else(|| {
489                                "Could not serialize FormData to URLSearchParams".to_string()
490                            }),
491                        ))
492                    })?;
493            let inner = match method {
494                http::Method::POST => Request::post(path),
495                http::Method::PUT => Request::put(path),
496                http::Method::PATCH => Request::patch(path),
497                m => {
498                    return Err(E::from_server_fn_error(
499                        ServerFnErrorErr::UnsupportedRequestMethod(m.to_string()),
500                    ));
501                }
502            };
503            Ok(Self(SendWrapper::new(
504                inner
505                    .header("Content-Type", content_type)
506                    .header("Accept", accepts)
507                    .body(url_params)
508                    .map_err(|e| {
509                        E::from_server_fn_error(ServerFnErrorErr::Request(e.to_string()))
510                    })?,
511            )))
512        }
513
514        fn try_new_req_streaming(
515            path: &str,
516            accepts: &str,
517            content_type: &str,
518            body: impl Stream<Item = Bytes> + Send + 'static,
519            method: http::Method,
520        ) -> Result<Self, E> {
521            fn streaming_request(
522                path: &str,
523                accepts: &str,
524                content_type: &str,
525                method: http::Method,
526                body: impl Stream<Item = Bytes> + 'static,
527            ) -> Result<Request, leptos::wasm_bindgen::JsValue> {
528                use leptos::wasm_bindgen::JsValue;
529                let stream = ReadableStream::from_stream(body.map(|bytes| {
530                    let data = leptos::web_sys::js_sys::Uint8Array::from(bytes.as_ref());
531                    let data = JsValue::from(data);
532                    Ok(data) as Result<JsValue, JsValue>
533                }))
534                .into_raw();
535
536                let headers = leptos::web_sys::Headers::new()?;
537                headers.append("Content-Type", content_type)?;
538                headers.append("Accept", accepts)?;
539
540                let init = leptos::web_sys::RequestInit::new();
541                init.set_headers(&headers);
542                init.set_method(method.as_str());
543                init.set_body(&stream);
544
545                // Chrome requires setting `duplex: "half"` on streaming requests
546                leptos::web_sys::js_sys::Reflect::set(
547                    &init,
548                    &JsValue::from_str("duplex"),
549                    &JsValue::from_str("half"),
550                )?;
551                let req = leptos::web_sys::Request::new_with_str_and_init(path, &init)?;
552                Ok(Request::from(req))
553            }
554
555            // TODO abort signal
556            let request =
557                streaming_request(path, accepts, content_type, method, body).map_err(|e| {
558                    E::from_server_fn_error(ServerFnErrorErr::Request(format!("{e:?}")))
559                })?;
560            Ok(Self(SendWrapper::new(request)))
561        }
562    }
563
564    struct ClientWrap;
565    impl<
566        Error: FromServerFnError + Send,
567        InputStreamError: FromServerFnError,
568        OutputStreamError: FromServerFnError,
569    > leptos::server_fn::client::Client<Error, InputStreamError, OutputStreamError> for ClientWrap
570    {
571        type Request = BrowserRequest;
572        type Response = BrowserResponse;
573
574        fn send(req: BrowserRequest) -> impl Future<Output = Result<Self::Response, Error>> + Send {
575            SendWrapper::new(async move {
576                let request = req.0.take();
577                let res = request
578                    .send()
579                    .await
580                    .map(|res| BrowserResponse(SendWrapper::new(res)))
581                    .map_err(|e| {
582                        Error::from_server_fn_error(ServerFnErrorErr::Request(e.to_string()))
583                    });
584                res
585            })
586        }
587        async fn open_websocket(
588            _: &str,
589        ) -> Result<
590            (
591                impl Stream<Item = Result<Bytes, Bytes>> + Send + 'static,
592                impl futures::Sink<Bytes> + Send + 'static,
593            ),
594            Error,
595        > {
596            Err::<(futures::stream::BoxStream<Result<Bytes, Bytes>>, Vec<Bytes>), _>(
597                Error::from_server_fn_error(ServerFnErrorErr::ServerError(
598                    "Not implemented".to_string(),
599                )),
600            )
601        }
602
603        fn spawn(future: impl Future<Output = ()> + Send + 'static) {
604            wasm_bindgen_futures::spawn_local(future);
605        }
606    }
607
608    struct BrowserResponse(SendWrapper<leptos::server_fn::response::browser::Response>);
609
610    impl<E: FromServerFnError> ClientRes<E> for BrowserResponse {
611        fn try_into_string(self) -> impl Future<Output = Result<String, E>> + Send {
612            SendWrapper::new(async move {
613                self.0.text().await.map_err(|e| {
614                    E::from_server_fn_error(ServerFnErrorErr::Deserialization(e.to_string()))
615                })
616            })
617        }
618
619        fn try_into_bytes(self) -> impl Future<Output = Result<Bytes, E>> + Send {
620            SendWrapper::new(async move {
621                self.0.binary().await.map(Bytes::from).map_err(|e| {
622                    E::from_server_fn_error(ServerFnErrorErr::Deserialization(e.to_string()))
623                })
624            })
625        }
626
627        fn try_into_stream(
628            self,
629        ) -> Result<impl Stream<Item = Result<Bytes, Bytes>> + Send + Sync + 'static, E> {
630            let stream = ReadableStream::from_raw(self.0.body().unwrap())
631                .into_stream()
632                .map(|data| match data {
633                    Err(e) => {
634                        leptos::web_sys::console::error_1(&e);
635                        Err(format!("{e:?}").into())
636                    }
637                    Ok(data) => {
638                        let data = data.unchecked_into::<leptos::web_sys::js_sys::Uint8Array>();
639                        let mut buf = Vec::new();
640                        let length = data.length();
641                        buf.resize(length as usize, 0);
642                        data.copy_to(&mut buf);
643                        Ok(Bytes::from(buf))
644                    }
645                });
646            Ok(SendWrapper::new(stream))
647        }
648
649        fn status(&self) -> u16 {
650            self.0.status()
651        }
652
653        fn status_text(&self) -> String {
654            self.0.status_text()
655        }
656
657        fn location(&self) -> String {
658            self.0
659                .headers()
660                .get("Location")
661                .unwrap_or_else(|| self.0.url())
662        }
663
664        fn has_redirect(&self) -> bool {
665            self.0
666                .headers()
667                .get(leptos::server_fn::redirect::REDIRECT_HEADER)
668                .is_some()
669        }
670    }
671
672    // -------------------------------------
673
674    impl<
675        In: Encoding,
676        Err: server_fn::serde::Serialize
677            + server_fn::serde::de::DeserializeOwned
678            + std::fmt::Debug
679            + Clone
680            + Send
681            + Sync
682            + std::fmt::Display
683            + std::str::FromStr
684            + 'static,
685        F: leptos::server_fn::ServerFn<
686                Client = leptos::server_fn::client::browser::BrowserClient,
687                Server = leptos::server_fn::mock::BrowserMockServer,
688                Protocol = leptos::server_fn::Http<In, server_fn::codec::Json>,
689                Error = ServerFnError<Err>,
690                InputStreamError = ServerFnError<Err>,
691                OutputStreamError = ServerFnError<Err>,
692            >,
693    > ServerFnExt for F
694    where
695        F: FromReq<In, BrowserMockReq, ServerFnError<Err>>
696            + IntoReq<In, OrigBrowserRequest, ServerFnError<Err>>
697            + server_fn::serde::Serialize
698            + server_fn::serde::de::DeserializeOwned
699            + std::fmt::Debug
700            + Clone
701            + Send
702            + Sync
703            + for<'de> server_fn::serde::Deserialize<'de>,
704        F::Output: IntoRes<server_fn::codec::Json, BrowserMockRes, ServerFnError<Err>>
705            + FromRes<server_fn::codec::Json, OrigBrowserResponse, ServerFnError<Err>>
706            + Send
707            + for<'de> server_fn::serde::Deserialize<'de>,
708    {
709        type Output = <Self as leptos::server_fn::ServerFn>::Output;
710        type Error = <Self as leptos::server_fn::ServerFn>::Error;
711        #[cfg(feature = "hydrate")]
712        async fn call_remote(self, url: String) -> Result<Self::Output, Self::Error> {
713            use leptos::server_fn::ServerFn;
714            Paired { sfn: self, url }.run_on_client().await
715        }
716    }
717}