Skip to main content

myrmic_sdk/
messages.rs

1use crate::{Bytes, Decoder, Encoder, Result, Sri};
2
3use alloc::string::String;
4use core::marker::PhantomData;
5use myrmic_common::cells::Command;
6
7/// A handler that can be targeted by name.
8///
9/// The `#[cmd]` macro generates a zero-sized marker type per command handler
10/// and implements this on it, so a handler can be referred to at the type level.
11/// `NAME` is the wire name (respecting `#[cmd(name = "...")]`); `Arg` is the
12/// payload the handler decodes. `#[evt]` does not generate a marker: event
13/// handlers are pub/sub and can never be [`Callback`] targets.
14pub trait Handler {
15    /// The handler's wire name (respecting `#[cmd(name = "...")]`).
16    const NAME: &'static str;
17    /// The payload type the handler decodes.
18    type Arg;
19}
20
21/// The command a caller wants a result returned to.
22///
23/// Commands are fire-and-forget, so a handler can't reply through a return
24/// value. Instead the caller sends a `Callback` naming the command to invoke
25/// back on the caller's [`Sri`]. `T` is the payload that target expects, so
26/// [`invoke`](Callback::invoke) is type-checked against it.
27pub struct Callback<T> {
28    command: Command,
29    _payload: PhantomData<T>,
30}
31
32impl<T> Callback<T> {
33    /// Build a callback targeting a handler by its generated marker type; the
34    /// payload type is taken from the handler, so it can't disagree.
35    ///
36    /// `Callback::of::<on_reply>()`
37    pub fn of<H: Handler<Arg = T>>() -> Self {
38        // `H::NAME` originates from `#[cmd]`, which only accepts names that are
39        // valid command identifiers.
40        Self {
41            command: Command::new(H::NAME.into()).expect("handler name is validated by #[cmd]"),
42            _payload: PhantomData,
43        }
44    }
45
46    /// Build a callback from a command name, with the payload type given
47    /// explicitly. Use when no handler marker is in scope.
48    pub fn to(name: impl Into<String>) -> Result<Self> {
49        Ok(Self {
50            command: Command::new(name.into())?,
51            _payload: PhantomData,
52        })
53    }
54}
55
56impl<T: Encoder> Callback<T> {
57    /// Invoke the callback on `sri`, sending `value` as its payload.
58    pub fn invoke(self, sri: Sri, value: &T) -> Result<()> {
59        crate::send(sri, self, value)
60    }
61}
62
63impl<T> From<Callback<T>> for Command {
64    fn from(value: Callback<T>) -> Self {
65        value.command
66    }
67}
68
69impl<T> Encoder for Callback<T> {
70    fn to_bytes(&self) -> Result<Bytes> {
71        Ok(self.command.as_ref().as_bytes().to_vec())
72    }
73}
74
75impl<T> Decoder for Callback<T> {
76    fn from_bytes(bytes: Bytes) -> Result<Self> {
77        let name = core::str::from_utf8(&bytes).map_err(|_| "callback name is not valid utf-8")?;
78
79        Self::to(name)
80    }
81}
82
83impl<T> core::fmt::Debug for Callback<T> {
84    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
85        f.debug_tuple("Callback").field(&self.command).finish()
86    }
87}
88
89mod serde_json {
90    use crate::{Bytes, Decoder, Encoder, Result};
91    use alloc::string::String;
92    use serde_json::{Map, Number, Value};
93
94    macro_rules! json_codec {
95        ($ty:ty, $noun:literal) => {
96            impl Decoder for $ty {
97                fn from_bytes(bytes: Bytes) -> Result<Self> {
98                    serde_json::from_slice(&bytes)
99                        .map_err(|_| concat!("payload is not a valid json ", $noun))
100                }
101            }
102
103            impl Encoder for $ty {
104                fn to_bytes(&self) -> Result<Bytes> {
105                    serde_json::to_vec(self)
106                        .map_err(|_| concat!("unable to serialise json ", $noun))
107                }
108            }
109        };
110    }
111
112    json_codec!(Value, "value");
113    json_codec!(Number, "number");
114    json_codec!(Map<String, Value>, "object");
115}