sails_rs/client/
mod.rs

1use crate::prelude::*;
2use core::{
3    any::TypeId,
4    error::Error,
5    marker::PhantomData,
6    pin::Pin,
7    task::{Context, Poll},
8};
9use futures::Stream;
10
11#[cfg(feature = "gtest")]
12#[cfg(not(target_arch = "wasm32"))]
13mod gtest_env;
14#[cfg(feature = "gtest")]
15#[cfg(not(target_arch = "wasm32"))]
16pub use gtest_env::{BlockRunMode, GtestEnv, GtestError, GtestParams};
17
18#[cfg(feature = "gclient")]
19#[cfg(not(target_arch = "wasm32"))]
20mod gclient_env;
21#[cfg(feature = "gclient")]
22#[cfg(not(target_arch = "wasm32"))]
23pub use gclient_env::{GclientEnv, GclientError, GclientParams};
24
25mod gstd_env;
26pub use gstd_env::{GstdEnv, GstdParams};
27
28pub(crate) const PENDING_CALL_INVALID_STATE: &str =
29    "PendingCall polled after completion or invalid state";
30pub(crate) const PENDING_CTOR_INVALID_STATE: &str =
31    "PendingCtor polled after completion or invalid state";
32
33pub trait GearEnv: Clone {
34    type Params: Default;
35    type Error: Error;
36    type MessageState;
37
38    fn deploy<P: Program>(&self, code_id: CodeId, salt: Vec<u8>) -> Deployment<P, Self> {
39        Deployment::new(self.clone(), code_id, salt)
40    }
41}
42
43pub trait Program: Sized {
44    fn deploy(code_id: CodeId, salt: Vec<u8>) -> Deployment<Self, GstdEnv> {
45        Deployment::new(GstdEnv, code_id, salt)
46    }
47
48    fn client(program_id: ActorId) -> Actor<Self, GstdEnv> {
49        Actor::new(GstdEnv, program_id)
50    }
51}
52
53pub type Route = &'static str;
54
55#[derive(Debug, Clone)]
56pub struct Deployment<A, E: GearEnv = GstdEnv> {
57    env: E,
58    code_id: CodeId,
59    salt: Vec<u8>,
60    _phantom: PhantomData<A>,
61}
62
63impl<A, E: GearEnv> Deployment<A, E> {
64    pub fn new(env: E, code_id: CodeId, salt: Vec<u8>) -> Self {
65        Deployment {
66            env,
67            code_id,
68            salt,
69            _phantom: PhantomData,
70        }
71    }
72
73    pub fn with_env<N: GearEnv>(self, env: &N) -> Deployment<A, N> {
74        let Self {
75            env: _,
76            code_id,
77            salt,
78            _phantom: _,
79        } = self;
80        Deployment {
81            env: env.clone(),
82            code_id,
83            salt,
84            _phantom: PhantomData,
85        }
86    }
87
88    pub fn pending_ctor<T: CallCodec>(self, args: T::Params) -> PendingCtor<A, T, E> {
89        PendingCtor::new(self.env, self.code_id, self.salt, args)
90    }
91}
92
93#[derive(Debug, Clone)]
94pub struct Actor<A, E: GearEnv = GstdEnv> {
95    env: E,
96    id: ActorId,
97    _phantom: PhantomData<A>,
98}
99
100impl<A, E: GearEnv> Actor<A, E> {
101    pub fn new(env: E, id: ActorId) -> Self {
102        Actor {
103            env,
104            id,
105            _phantom: PhantomData,
106        }
107    }
108
109    pub fn id(&self) -> ActorId {
110        self.id
111    }
112
113    pub fn with_env<N: GearEnv>(self, env: &N) -> Actor<A, N> {
114        let Self {
115            env: _,
116            id,
117            _phantom: _,
118        } = self;
119        Actor {
120            env: env.clone(),
121            id,
122            _phantom: PhantomData,
123        }
124    }
125
126    pub fn with_actor_id(mut self, actor_id: ActorId) -> Self {
127        self.id = actor_id;
128        self
129    }
130
131    pub fn service<S>(&self, route: Route) -> Service<S, E> {
132        Service::new(self.env.clone(), self.id, route)
133    }
134}
135
136#[derive(Debug, Clone)]
137pub struct Service<S, E: GearEnv = GstdEnv> {
138    env: E,
139    actor_id: ActorId,
140    route: Route,
141    _phantom: PhantomData<S>,
142}
143
144impl<S, E: GearEnv> Service<S, E> {
145    pub fn new(env: E, actor_id: ActorId, route: Route) -> Self {
146        Service {
147            env,
148            actor_id,
149            route,
150            _phantom: PhantomData,
151        }
152    }
153
154    pub fn actor_id(&self) -> ActorId {
155        self.actor_id
156    }
157
158    pub fn route(&self) -> Route {
159        self.route
160    }
161
162    pub fn with_actor_id(mut self, actor_id: ActorId) -> Self {
163        self.actor_id = actor_id;
164        self
165    }
166
167    pub fn pending_call<T: CallCodec>(&self, args: T::Params) -> PendingCall<T, E> {
168        PendingCall::new(self.env.clone(), self.actor_id, self.route, args)
169    }
170
171    #[cfg(not(target_arch = "wasm32"))]
172    pub fn listener(&self) -> ServiceListener<S::Event, E>
173    where
174        S: ServiceWithEvents,
175    {
176        ServiceListener::new(self.env.clone(), self.actor_id, self.route)
177    }
178}
179
180#[cfg(not(target_arch = "wasm32"))]
181pub trait ServiceWithEvents {
182    type Event: Event;
183}
184
185#[cfg(not(target_arch = "wasm32"))]
186pub struct ServiceListener<D: Event, E: GearEnv> {
187    env: E,
188    actor_id: ActorId,
189    route: Route,
190    _phantom: PhantomData<D>,
191}
192
193#[cfg(not(target_arch = "wasm32"))]
194impl<D: Event, E: GearEnv> ServiceListener<D, E> {
195    pub fn new(env: E, actor_id: ActorId, route: Route) -> Self {
196        ServiceListener {
197            env,
198            actor_id,
199            route,
200            _phantom: PhantomData,
201        }
202    }
203
204    pub async fn listen(
205        &self,
206    ) -> Result<impl Stream<Item = (ActorId, D)> + Unpin, <E as GearEnv>::Error>
207    where
208        E: Listener<Error = <E as GearEnv>::Error>,
209    {
210        let self_id = self.actor_id;
211        let prefix = self.route;
212        self.env
213            .listen(move |(actor_id, payload)| {
214                if actor_id != self_id {
215                    return None;
216                }
217                D::decode_event(prefix, payload).ok().map(|e| (actor_id, e))
218            })
219            .await
220    }
221}
222
223pin_project_lite::pin_project! {
224    pub struct PendingCall<T: CallCodec, E: GearEnv> {
225        env: E,
226        destination: ActorId,
227        route: Route,
228        params: Option<E::Params>,
229        args: Option<T::Params>,
230        #[pin]
231        state: Option<E::MessageState>
232    }
233}
234
235impl<T: CallCodec, E: GearEnv> PendingCall<T, E> {
236    pub fn new(env: E, destination: ActorId, route: Route, args: T::Params) -> Self {
237        PendingCall {
238            env,
239            destination,
240            route,
241            params: None,
242            args: Some(args),
243            state: None,
244        }
245    }
246
247    pub fn with_destination(mut self, actor_id: ActorId) -> Self {
248        self.destination = actor_id;
249        self
250    }
251
252    pub fn with_params(mut self, f: impl FnOnce(E::Params) -> E::Params) -> Self {
253        self.params = Some(f(self.params.unwrap_or_default()));
254        self
255    }
256
257    #[inline]
258    fn take_encoded_args_and_params(&mut self) -> (Vec<u8>, E::Params) {
259        let args = self
260            .args
261            .take()
262            .unwrap_or_else(|| panic!("{PENDING_CALL_INVALID_STATE}"));
263        let payload = T::encode_params_with_prefix(self.route, &args);
264        let params = self.params.take().unwrap_or_default();
265        (payload, params)
266    }
267}
268
269pin_project_lite::pin_project! {
270    pub struct PendingCtor<A, T: CallCodec, E: GearEnv> {
271        env: E,
272        code_id: CodeId,
273        params: Option<E::Params>,
274        salt: Option<Vec<u8>>,
275        args: Option<T::Params>,
276        _actor: PhantomData<A>,
277        #[pin]
278        state: Option<E::MessageState>,
279        program_id: Option<ActorId>,
280    }
281}
282
283impl<A, T: CallCodec, E: GearEnv> PendingCtor<A, T, E> {
284    pub fn new(env: E, code_id: CodeId, salt: Vec<u8>, args: T::Params) -> Self {
285        PendingCtor {
286            env,
287            code_id,
288            params: None,
289            salt: Some(salt),
290            args: Some(args),
291            _actor: PhantomData,
292            state: None,
293            program_id: None,
294        }
295    }
296
297    pub fn with_params(mut self, f: impl FnOnce(E::Params) -> E::Params) -> Self {
298        self.params = Some(f(self.params.unwrap_or_default()));
299        self
300    }
301}
302
303pub trait CallCodec {
304    const ROUTE: Route;
305    type Params: Encode;
306    type Reply: Decode + 'static;
307
308    fn encode_params(value: &Self::Params) -> Vec<u8> {
309        let size = Encode::encoded_size(Self::ROUTE) + Encode::encoded_size(value);
310        let mut result = Vec::with_capacity(size);
311        Encode::encode_to(Self::ROUTE, &mut result);
312        Encode::encode_to(value, &mut result);
313        result
314    }
315
316    fn encode_params_with_prefix(prefix: Route, value: &Self::Params) -> Vec<u8> {
317        let size = Encode::encoded_size(prefix)
318            + Encode::encoded_size(Self::ROUTE)
319            + Encode::encoded_size(value);
320        let mut result = Vec::with_capacity(size);
321        Encode::encode_to(prefix, &mut result);
322        Encode::encode_to(Self::ROUTE, &mut result);
323        Encode::encode_to(value, &mut result);
324        result
325    }
326
327    fn decode_reply(payload: impl AsRef<[u8]>) -> Result<Self::Reply, parity_scale_codec::Error> {
328        let mut value = payload.as_ref();
329        if Self::is_empty_tuple::<Self::Reply>() {
330            return Decode::decode(&mut value);
331        }
332        // Decode payload as `(String, Self::Reply)`
333        let route = String::decode(&mut value)?;
334        if route != Self::ROUTE {
335            return Err("Invalid reply prefix".into());
336        }
337        Decode::decode(&mut value)
338    }
339
340    fn decode_reply_with_prefix(
341        prefix: Route,
342        payload: impl AsRef<[u8]>,
343    ) -> Result<Self::Reply, parity_scale_codec::Error> {
344        let mut value = payload.as_ref();
345        if Self::is_empty_tuple::<Self::Reply>() {
346            return Decode::decode(&mut value);
347        }
348        // Decode payload as `(String, String, Self::Reply)`
349        let route = String::decode(&mut value)?;
350        if route != prefix {
351            return Err("Invalid reply prefix".into());
352        }
353        let route = String::decode(&mut value)?;
354        if route != Self::ROUTE {
355            return Err("Invalid reply prefix".into());
356        }
357        Decode::decode(&mut value)
358    }
359
360    fn with_optimized_encode<R>(
361        prefix: Route,
362        value: &Self::Params,
363        f: impl FnOnce(&[u8]) -> R,
364    ) -> R {
365        let size = Encode::encoded_size(prefix)
366            + Encode::encoded_size(Self::ROUTE)
367            + Encode::encoded_size(value);
368        gcore::stack_buffer::with_byte_buffer(size, |buffer| {
369            let mut buffer_writer = crate::utils::MaybeUninitBufferWriter::new(buffer);
370            Encode::encode_to(prefix, &mut buffer_writer);
371            Encode::encode_to(Self::ROUTE, &mut buffer_writer);
372            Encode::encode_to(value, &mut buffer_writer);
373            buffer_writer.with_buffer(f)
374        })
375    }
376
377    fn is_empty_tuple<T: 'static>() -> bool {
378        TypeId::of::<T>() == TypeId::of::<()>()
379    }
380}
381
382#[macro_export]
383macro_rules! params_struct_impl {
384    (
385        $env:ident,
386        $name:ident { $( $(#[$attr:meta])* $vis:vis $field:ident: $ty:ty ),* $(,)?  }
387    ) => {
388        #[derive(Debug, Default)]
389        pub struct $name {
390            $(
391                $(#[$attr])* $vis $field : Option< $ty >,
392            )*
393        }
394
395        impl $name {
396            $(
397                paste::paste! {
398                    $(#[$attr])*
399                    pub fn [<with_ $field>](mut self, $field: $ty) -> Self {
400                        self.$field = Some($field);
401                        self
402                    }
403                }
404            )*
405        }
406
407        impl<A, T: CallCodec> PendingCtor<A, T, $env> {
408            $(
409                paste::paste! {
410                    $(#[$attr])*
411                    pub fn [<with_ $field>](self, $field: $ty) -> Self {
412                        self.with_params(|params| params.[<with_ $field>]($field))
413                    }
414                }
415            )*
416        }
417
418        impl<T: CallCodec> PendingCall<T, $env> {
419            $(
420                paste::paste! {
421                    $(#[$attr])*
422                    pub fn [<with_ $field>](self, $field: $ty) -> Self {
423                        self.with_params(|params| params.[<with_ $field>]($field))
424                    }
425                }
426            )*
427        }
428    };
429}
430
431#[macro_export]
432macro_rules! params_for_pending_impl {
433    (
434        $env:ident,
435        $name:ident { $( $(#[$attr:meta])* $vis:vis $field:ident: $ty:ty ),* $(,)?  }
436    ) => {
437        impl $name {
438            $(
439                paste::paste! {
440                    $(#[$attr])*
441                    pub fn [<with_ $field>](mut self, $field: $ty) -> Self {
442                        self.$field = Some($field);
443                        self
444                    }
445                }
446            )*
447        }
448
449        impl<A, T: CallCodec> PendingCtor<A, T, $env> {
450            $(
451                paste::paste! {
452                    $(#[$attr])*
453                    pub fn [<with_ $field>](self, $field: $ty) -> Self {
454                        self.with_params(|params| params.[<with_ $field>]($field))
455                    }
456                }
457            )*
458        }
459
460        impl<T: CallCodec> PendingCall<T, $env> {
461            $(
462                paste::paste! {
463                    $(#[$attr])*
464                    pub fn [<with_ $field>](self, $field: $ty) -> Self {
465                        self.with_params(|params| params.[<with_ $field>]($field))
466                    }
467                }
468            )*
469        }
470    };
471}
472
473#[macro_export]
474macro_rules! io_struct_impl {
475    (
476        $name:ident ( $( $param:ident : $ty:ty ),* ) -> $reply:ty
477    ) => {
478        pub struct $name(());
479        impl $name {
480            pub fn encode_params($( $param: $ty, )* ) -> Vec<u8> {
481                <$name as CallCodec>::encode_params(&( $( $param, )* ))
482            }
483            pub fn encode_params_with_prefix(prefix: Route, $( $param: $ty, )* ) -> Vec<u8> {
484                <$name as CallCodec>::encode_params_with_prefix(prefix, &( $( $param, )* ))
485            }
486        }
487        impl CallCodec for $name {
488            const ROUTE: &'static str = stringify!($name);
489            type Params = ( $( $ty, )* );
490            type Reply = $reply;
491        }
492    };
493}
494
495#[allow(unused_macros)]
496macro_rules! str_scale_encode {
497    ($s:ident) => {{
498        const S: &str = stringify!($s);
499        assert!(S.len() <= 63, "Ident too long for encoding");
500        const LEN: u8 = S.len() as u8;
501        const BYTES: [u8; LEN as usize + 1] = {
502            const fn to_array(s: &str) -> [u8; LEN as usize + 1] {
503                let bytes = s.as_bytes();
504                let mut out = [0u8; LEN as usize + 1];
505                out[0] = LEN << 2;
506                let mut i = 0;
507                while i < LEN as usize {
508                    out[i + 1] = bytes[i];
509                    i += 1;
510                }
511                out
512            }
513            to_array(S)
514        };
515        BYTES.as_slice()
516    }};
517}
518
519#[allow(async_fn_in_trait)]
520pub trait Listener {
521    type Error: Error;
522
523    async fn listen<E, F: FnMut((ActorId, Vec<u8>)) -> Option<(ActorId, E)>>(
524        &self,
525        f: F,
526    ) -> Result<impl Stream<Item = (ActorId, E)> + Unpin, Self::Error>;
527}
528
529#[cfg(not(target_arch = "wasm32"))]
530pub trait Event: Decode {
531    const EVENT_NAMES: &'static [Route];
532
533    fn decode_event(
534        prefix: Route,
535        payload: impl AsRef<[u8]>,
536    ) -> Result<Self, parity_scale_codec::Error> {
537        let mut payload = payload.as_ref();
538        let route = String::decode(&mut payload)?;
539        if route != prefix {
540            return Err("Invalid event prefix".into());
541        }
542        let evt_name = String::decode(&mut payload)?;
543        for (idx, &name) in Self::EVENT_NAMES.iter().enumerate() {
544            if evt_name == name {
545                let idx = idx as u8;
546                let bytes = [&[idx], payload].concat();
547                let mut event_bytes = &bytes[..];
548                return Decode::decode(&mut event_bytes);
549            }
550        }
551        Err("Invalid event name".into())
552    }
553}
554
555#[cfg(test)]
556mod tests {
557    use super::*;
558    io_struct_impl!(Add (value: u32) -> u32);
559    io_struct_impl!(Value () -> u32);
560
561    #[test]
562    fn test_str_encode() {
563        const ADD: &[u8] = str_scale_encode!(Add);
564        assert_eq!(ADD, &[12, 65, 100, 100]);
565
566        const VALUE: &[u8] = str_scale_encode!(Value);
567        assert_eq!(VALUE, &[20, 86, 97, 108, 117, 101]);
568    }
569
570    #[test]
571    fn test_io_struct_impl() {
572        let add = Add::encode_params(42);
573        assert_eq!(add, &[12, 65, 100, 100, 42, 0, 0, 0]);
574
575        let value = Add::encode_params_with_prefix("Counter", 42);
576        assert_eq!(
577            value,
578            &[
579                28, 67, 111, 117, 110, 116, 101, 114, 12, 65, 100, 100, 42, 0, 0, 0
580            ]
581        );
582
583        let value = Value::encode_params();
584        assert_eq!(value, &[20, 86, 97, 108, 117, 101]);
585
586        let value = Value::encode_params_with_prefix("Counter");
587        assert_eq!(
588            value,
589            &[
590                28, 67, 111, 117, 110, 116, 101, 114, 20, 86, 97, 108, 117, 101
591            ]
592        );
593    }
594}