config/
lib.rs

1use crate::epoch::{get_current_epoch, ConfigEpoch};
2use crate::pool::{pool_get, pool_put};
3pub use crate::pool::{set_gc_on_put, set_max_age, set_max_spare, set_max_use};
4use anyhow::Context;
5use mlua::{
6    FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, Lua, LuaSerdeExt, MetaMethod, RegistryKey, Table,
7    UserData, UserDataMethods, Value,
8};
9use parking_lot::FairMutex as Mutex;
10pub use pastey as paste;
11use prometheus::{CounterVec, HistogramTimer, HistogramVec};
12use serde::Serialize;
13use std::borrow::Cow;
14use std::collections::HashSet;
15use std::path::PathBuf;
16use std::sync::atomic::{AtomicBool, Ordering};
17use std::sync::{LazyLock, Once};
18use std::time::Instant;
19
20pub mod epoch;
21mod pool;
22
23static POLICY_FILE: LazyLock<Mutex<Option<PathBuf>>> = LazyLock::new(|| Mutex::new(None));
24static FUNCS: LazyLock<Mutex<Vec<RegisterFunc>>> = LazyLock::new(|| Mutex::new(vec![]));
25static LUA_LOAD_COUNT: LazyLock<metrics::Counter> = LazyLock::new(|| {
26    metrics::describe_counter!(
27        "lua_load_count",
28        "how many times the policy lua script has been \
29         loaded into a new context"
30    );
31    metrics::counter!("lua_load_count")
32});
33static LUA_COUNT: LazyLock<metrics::Gauge> = LazyLock::new(|| {
34    metrics::describe_gauge!("lua_count", "the number of lua contexts currently alive");
35    metrics::gauge!("lua_count")
36});
37static CALLBACK_ALLOWS_MULTIPLE: LazyLock<Mutex<HashSet<String>>> =
38    LazyLock::new(|| Mutex::new(HashSet::new()));
39
40pub static VALIDATE_ONLY: AtomicBool = AtomicBool::new(false);
41pub static VALIDATION_FAILED: AtomicBool = AtomicBool::new(false);
42static LATENCY_HIST: LazyLock<HistogramVec> = LazyLock::new(|| {
43    prometheus::register_histogram_vec!(
44        "lua_event_latency",
45        "how long a given lua event callback took",
46        &["event"]
47    )
48    .unwrap()
49});
50static EVENT_STARTED_COUNT: LazyLock<CounterVec> = LazyLock::new(|| {
51    prometheus::register_counter_vec!(
52        "lua_event_started",
53        "Incremented each time we start to call a lua event callback. Use lua_event_latency_count to track completed events",
54        &["event"]
55    )
56    .unwrap()
57});
58
59pub type RegisterFunc = fn(&Lua) -> anyhow::Result<()>;
60
61fn latency_timer(label: &str) -> HistogramTimer {
62    EVENT_STARTED_COUNT
63        .get_metric_with_label_values(&[label])
64        .expect("to get counter")
65        .inc();
66    LATENCY_HIST
67        .get_metric_with_label_values(&[label])
68        .expect("to get histo")
69        .start_timer()
70}
71
72#[derive(Debug)]
73struct LuaConfigInner {
74    lua: Lua,
75    created: Instant,
76    use_count: usize,
77    epoch: ConfigEpoch,
78}
79
80impl Drop for LuaConfigInner {
81    fn drop(&mut self) {
82        LUA_COUNT.decrement(1.);
83    }
84}
85
86#[derive(Debug)]
87pub struct LuaConfig {
88    inner: Option<LuaConfigInner>,
89}
90
91pub async fn set_policy_path(path: PathBuf) -> anyhow::Result<()> {
92    POLICY_FILE.lock().replace(path);
93    let config = load_config().await?;
94    config.put();
95    Ok(())
96}
97
98fn get_policy_path() -> Option<PathBuf> {
99    POLICY_FILE.lock().clone()
100}
101
102fn get_funcs() -> Vec<RegisterFunc> {
103    FUNCS.lock().clone()
104}
105pub fn is_validating() -> bool {
106    VALIDATE_ONLY.load(Ordering::Relaxed)
107}
108
109pub fn validation_failed() -> bool {
110    VALIDATION_FAILED.load(Ordering::Relaxed)
111}
112
113pub fn set_validation_failed() {
114    VALIDATION_FAILED.store(true, Ordering::Relaxed)
115}
116
117pub async fn load_config() -> anyhow::Result<LuaConfig> {
118    if let Some(pool) = pool_get() {
119        return Ok(pool);
120    }
121
122    LUA_LOAD_COUNT.increment(1);
123    let lua = Lua::new();
124    let created = Instant::now();
125    let epoch = get_current_epoch();
126
127    {
128        let globals = lua.globals();
129
130        if is_validating() {
131            globals.set("_VALIDATING_CONFIG", true)?;
132        }
133
134        let package: Table = globals.get("package")?;
135        let package_path: String = package.get("path")?;
136        let mut path_array: Vec<String> = package_path.split(";").map(|s| s.to_owned()).collect();
137
138        fn prefix_path(array: &mut Vec<String>, path: &str) {
139            array.insert(0, format!("{}/?.lua", path));
140            array.insert(1, format!("{}/?/init.lua", path));
141        }
142
143        prefix_path(&mut path_array, "/opt/kumomta/etc/policy");
144        prefix_path(&mut path_array, "/opt/kumomta/share");
145
146        #[cfg(debug_assertions)]
147        prefix_path(&mut path_array, "assets");
148
149        package.set("path", path_array.join(";"))?;
150    }
151
152    register_declared_events();
153
154    for func in get_funcs() {
155        (func)(&lua)?;
156    }
157
158    if let Some(policy) = get_policy_path() {
159        let code = tokio::fs::read_to_string(&policy)
160            .await
161            .with_context(|| format!("reading policy file {policy:?}"))?;
162
163        let func = {
164            let chunk = lua.load(&code);
165            let chunk = chunk.set_name(policy.to_string_lossy());
166            chunk.into_function()?
167        };
168
169        let _timer = latency_timer("context-creation");
170        func.call_async::<()>(()).await?;
171    }
172    LUA_COUNT.increment(1.);
173
174    Ok(LuaConfig {
175        inner: Some(LuaConfigInner {
176            lua,
177            created,
178            use_count: 1,
179            epoch,
180        }),
181    })
182}
183
184pub fn register(func: RegisterFunc) {
185    FUNCS.lock().push(func);
186}
187
188impl LuaConfig {
189    fn set_current_event(&mut self, name: &str) -> mlua::Result<()> {
190        self.inner
191            .as_mut()
192            .unwrap()
193            .lua
194            .globals()
195            .set("_KUMO_CURRENT_EVENT", name.to_string())
196    }
197
198    /// Convert an array of args into a MultiValue that can be passed
199    /// to a callback signature
200    pub fn convert_args_to_multi<A: Serialize>(
201        &self,
202        args: &[A],
203    ) -> anyhow::Result<mlua::MultiValue> {
204        let lua = self.inner.as_ref().unwrap();
205        let mut arg_vec = vec![];
206        for a in args.iter() {
207            arg_vec.push(lua.lua.to_value(a)?);
208        }
209        Ok(mlua::MultiValue::from_vec(arg_vec))
210    }
211
212    /// Intended to be used together with kumo.spawn_task
213    pub async fn convert_args_and_call_callback<A: Serialize>(
214        &mut self,
215        sig: &CallbackSignature<Value, ()>,
216        args: A,
217    ) -> anyhow::Result<()> {
218        let lua = self.inner.as_mut().unwrap();
219        let args = lua.lua.to_value(&args)?;
220
221        let name = sig.name();
222        let decorated_name = sig.decorated_name();
223
224        match lua
225            .lua
226            .named_registry_value::<mlua::Function>(&decorated_name)
227        {
228            Ok(func) => {
229                let _timer = latency_timer(name);
230                Ok(func.call_async(args).await?)
231            }
232            _ => anyhow::bail!("{name} has not been registered"),
233        }
234    }
235
236    /// Explicitly put the config object back into its containing pool.
237    /// Ideally we'd do this automatically when the object is dropped,
238    /// but lua's garbage collection makes this problematic:
239    /// if a future whose graph contains an async lua call within
240    /// this config object is cancelled (eg: simply stopped without
241    /// calling it again), and the config object is not explicitly garbage
242    /// collected, any futures and data owned by any dependencies of
243    /// the cancelled future remain alive until the next gc run,
244    /// which can cause things like async locks and semaphores to
245    /// have a lifetime extended by the maximum age of the lua context.
246    ///
247    /// The combat this, consumers of LuaConfig should explicitly
248    /// call `config.put()` after successfully using the config
249    /// object.
250    ///
251    /// Or framing it another way: consumers must not call `config.put()`
252    /// if a transitive dep might have been cancelled.
253    pub fn put(mut self) {
254        if let Some(inner) = self.inner.take() {
255            pool_put(inner);
256        }
257    }
258
259    pub async fn async_call_callback<A: IntoLuaMulti + Clone, R: FromLuaMulti + Default>(
260        &mut self,
261        sig: &CallbackSignature<A, R>,
262        args: A,
263    ) -> anyhow::Result<R> {
264        let name = sig.name();
265        self.set_current_event(name)?;
266        let lua = self.inner.as_mut().unwrap();
267        async_call_callback(&lua.lua, sig, args).await
268    }
269
270    pub async fn async_call_callback_non_default<A: IntoLuaMulti + Clone, R: FromLuaMulti>(
271        &mut self,
272        sig: &CallbackSignature<A, R>,
273        args: A,
274    ) -> anyhow::Result<R> {
275        let name = sig.name();
276        self.set_current_event(name)?;
277        let lua = self.inner.as_mut().unwrap();
278        async_call_callback_non_default(&lua.lua, sig, args).await
279    }
280
281    pub async fn async_call_callback_non_default_opt<A: IntoLuaMulti + Clone, R: FromLua>(
282        &mut self,
283        sig: &CallbackSignature<A, Option<R>>,
284        args: A,
285    ) -> anyhow::Result<Option<R>> {
286        let name = sig.name();
287        let decorated_name = sig.decorated_name();
288        self.set_current_event(name)?;
289        let lua = self.inner.as_mut().unwrap();
290
291        match lua
292            .lua
293            .named_registry_value::<mlua::Value>(&decorated_name)?
294        {
295            Value::Table(tbl) => {
296                for func in tbl.sequence_values::<mlua::Function>().collect::<Vec<_>>() {
297                    let func = func?;
298                    let _timer = latency_timer(name);
299                    let result: mlua::MultiValue = func.call_async(args.clone()).await?;
300                    if result.is_empty() {
301                        // Continue with other handlers
302                        continue;
303                    }
304                    let result = R::from_lua_multi(result, &lua.lua)?;
305                    return Ok(Some(result));
306                }
307                Ok(None)
308            }
309            Value::Function(func) => {
310                sig.raise_error_if_allow_multiple()?;
311                let _timer = latency_timer(name);
312                let value: Value = func.call_async(args.clone()).await?;
313
314                match value {
315                    Value::Nil => Ok(None),
316                    value => {
317                        let result = R::from_lua(value, &lua.lua)?;
318                        Ok(Some(result))
319                    }
320                }
321            }
322            _ => Ok(None),
323        }
324    }
325
326    pub fn remove_registry_value(&mut self, value: RegistryKey) -> anyhow::Result<()> {
327        Ok(self
328            .inner
329            .as_mut()
330            .unwrap()
331            .lua
332            .remove_registry_value(value)?)
333    }
334
335    /// Call a constructor registered via `on`. Returns a registry key that can be
336    /// used to reference the returned value again later on this same Lua instance
337    pub async fn async_call_ctor<A: IntoLuaMulti + Clone>(
338        &mut self,
339        sig: &CallbackSignature<A, Value>,
340        args: A,
341    ) -> anyhow::Result<RegistryKey> {
342        let name = sig.name();
343        anyhow::ensure!(
344            !sig.allow_multiple(),
345            "ctor event signature for {name} is defined as allow_multiple, which is not supported"
346        );
347
348        let decorated_name = sig.decorated_name();
349        self.set_current_event(name)?;
350
351        let inner = self.inner.as_mut().unwrap();
352
353        let func = inner
354            .lua
355            .named_registry_value::<mlua::Function>(&decorated_name)?;
356
357        let _timer = latency_timer(name);
358        let value: Value = func.call_async(args.clone()).await?;
359        drop(func);
360
361        Ok(inner.lua.create_registry_value(value)?)
362    }
363
364    /// Operate on an object/value that was previously constructed via
365    /// async_call_ctor.
366    pub async fn with_registry_value<F, R, FUT>(
367        &mut self,
368        value: &RegistryKey,
369        func: F,
370    ) -> anyhow::Result<R>
371    where
372        R: FromLuaMulti,
373        F: FnOnce(Value) -> anyhow::Result<FUT>,
374        FUT: std::future::Future<Output = anyhow::Result<R>>,
375    {
376        let inner = self.inner.as_mut().unwrap();
377        let value = inner.lua.registry_value(value)?;
378        let future = (func)(value)?;
379        future.await
380    }
381}
382
383pub async fn async_call_callback<A: IntoLuaMulti + Clone, R: FromLuaMulti + Default>(
384    lua: &Lua,
385    sig: &CallbackSignature<A, R>,
386    args: A,
387) -> anyhow::Result<R> {
388    let name = sig.name();
389    let decorated_name = sig.decorated_name();
390
391    match lua.named_registry_value::<mlua::Value>(&decorated_name)? {
392        Value::Table(tbl) => {
393            for func in tbl.sequence_values::<mlua::Function>().collect::<Vec<_>>() {
394                let func = func?;
395                let _timer = latency_timer(name);
396                let result: mlua::MultiValue = func.call_async(args.clone()).await?;
397                if result.is_empty() {
398                    // Continue with other handlers
399                    continue;
400                }
401                let result = R::from_lua_multi(result, lua)?;
402                return Ok(result);
403            }
404            Ok(R::default())
405        }
406        Value::Function(func) => {
407            sig.raise_error_if_allow_multiple()?;
408            let _timer = latency_timer(name);
409            Ok(func.call_async(args.clone()).await?)
410        }
411        _ => Ok(R::default()),
412    }
413}
414
415pub async fn async_call_callback_non_default<A: IntoLuaMulti + Clone, R: FromLuaMulti>(
416    lua: &Lua,
417    sig: &CallbackSignature<A, R>,
418    args: A,
419) -> anyhow::Result<R> {
420    let name = sig.name();
421    let decorated_name = sig.decorated_name();
422
423    match lua.named_registry_value::<mlua::Value>(&decorated_name)? {
424        Value::Table(tbl) => {
425            for func in tbl.sequence_values::<mlua::Function>().collect::<Vec<_>>() {
426                let func = func?;
427                let _timer = latency_timer(name);
428                let result: mlua::MultiValue = func.call_async(args.clone()).await?;
429                if result.is_empty() {
430                    // Continue with other handlers
431                    continue;
432                }
433                let result = R::from_lua_multi(result, lua)?;
434                return Ok(result);
435            }
436            anyhow::bail!("invalid return type for {name} event");
437        }
438        Value::Function(func) => {
439            sig.raise_error_if_allow_multiple()?;
440            let _timer = latency_timer(name);
441            Ok(func.call_async(args.clone()).await?)
442        }
443        _ => anyhow::bail!("Event {name} has not been registered"),
444    }
445}
446
447pub fn get_or_create_module(lua: &Lua, name: &str) -> anyhow::Result<mlua::Table> {
448    let globals = lua.globals();
449    let package: Table = globals.get("package")?;
450    let loaded: Table = package.get("loaded")?;
451
452    let module = loaded.get(name)?;
453    match module {
454        Value::Nil => {
455            let module = lua.create_table()?;
456            loaded.set(name, module.clone())?;
457            Ok(module)
458        }
459        Value::Table(table) => Ok(table),
460        wat => anyhow::bail!(
461            "cannot register module {} as package.loaded.{} is already set to a value of type {}",
462            name,
463            name,
464            wat.type_name()
465        ),
466    }
467}
468
469/// Given a name path like `foo` or `foo.bar.baz`, sets up the module
470/// registry hierarchy to instantiate that path.
471/// Returns the leaf node of that path to allow the caller to
472/// register/assign functions etc. into it
473pub fn get_or_create_sub_module(lua: &Lua, name_path: &str) -> anyhow::Result<mlua::Table> {
474    let mut parent = get_or_create_module(lua, "kumo")?;
475    let mut path_so_far = String::new();
476
477    for name in name_path.split('.') {
478        if !path_so_far.is_empty() {
479            path_so_far.push('.');
480        }
481        path_so_far.push_str(name);
482
483        let sub = parent.get(name)?;
484        match sub {
485            Value::Nil => {
486                let sub = lua.create_table()?;
487                parent.set(name, sub.clone())?;
488                parent = sub;
489            }
490            Value::Table(sub) => {
491                parent = sub;
492            }
493            wat => anyhow::bail!(
494                "cannot register module kumo.{path_so_far} as it is already set to a value of type {}",
495                wat.type_name()
496            ),
497        }
498    }
499
500    Ok(parent)
501}
502
503/// Helper for mapping back to lua errors
504pub fn any_err<E: std::fmt::Display>(err: E) -> mlua::Error {
505    mlua::Error::external(format!("{err:#}"))
506}
507
508/// Provides implementations of __pairs, __index and __len metamethods
509/// for a type that is Serialize and UserData.
510/// Neither implementation is considered to be ideal, as we must
511/// first serialize the value into a json Value which is then either
512/// iterated over, or indexed to produce the appropriate result for
513/// the metamethod.
514pub fn impl_pairs_and_index<T, M>(methods: &mut M)
515where
516    T: UserData + Serialize,
517    M: UserDataMethods<T>,
518{
519    methods.add_meta_method(MetaMethod::Pairs, move |lua, this, _: ()| {
520        let Ok(serde_json::Value::Object(map)) = serde_json::to_value(this).map_err(any_err) else {
521            return Err(mlua::Error::external("must serialize to Map"));
522        };
523
524        let mut value_iter = map.into_iter();
525
526        let iter_func = lua.create_function_mut(
527            move |lua, (_state, _control): (Value, Value)| match value_iter.next() {
528                Some((key, value)) => {
529                    let key = lua.to_value(&key)?;
530                    let value = lua.to_value(&value)?;
531                    Ok((key, value))
532                }
533                None => Ok((Value::Nil, Value::Nil)),
534            },
535        )?;
536
537        Ok((Value::Function(iter_func), Value::Nil, Value::Nil))
538    });
539
540    methods.add_meta_method(MetaMethod::Index, move |lua, this, field: Value| {
541        let value = lua.to_value(this)?;
542        match value {
543            Value::Table(t) => t.get(field),
544            _ => Ok(Value::Nil),
545        }
546    });
547
548    methods.add_meta_method(MetaMethod::Len, move |lua, this, _: ()| {
549        let value = lua.to_value(this)?;
550        match value {
551            Value::Table(v) => v.len(),
552            Value::String(v) => Ok(v.as_bytes().len() as i64),
553            _ => Ok(0),
554        }
555    });
556}
557
558/// This function will try to obtain a native lua representation
559/// of the provided value. It does this by attempting to iterate
560/// the pairs of any userdata it finds as either the value itself
561/// or the values of a table value by recursively applying
562/// materialize_to_lua_value to the value.
563/// This produces a lua value that can then be processed by the
564/// Deserialize impl on Value.
565pub fn materialize_to_lua_value(lua: &Lua, value: mlua::Value) -> mlua::Result<mlua::Value> {
566    match value {
567        mlua::Value::UserData(ud) => {
568            let mt = ud.metatable()?;
569            let Ok(pairs) = mt.get::<mlua::Function>("__pairs") else {
570                let value = ud.into_lua(lua)?;
571                return Err(mlua::Error::external(format!(
572                    "cannot materialize_to_lua_value {value:?} \
573                     because it has no __pairs metamethod"
574                )));
575            };
576            let tbl = lua.create_table()?;
577            let (iter_func, state, mut control): (mlua::Function, mlua::Value, mlua::Value) =
578                pairs.call(mlua::Value::UserData(ud.clone()))?;
579
580            loop {
581                let (k, v): (mlua::Value, mlua::Value) =
582                    iter_func.call((state.clone(), control))?;
583                if k.is_nil() {
584                    break;
585                }
586
587                tbl.set(k.clone(), materialize_to_lua_value(lua, v)?)?;
588                control = k;
589            }
590
591            Ok(mlua::Value::Table(tbl))
592        }
593        mlua::Value::Table(t) => {
594            let tbl = lua.create_table()?;
595            for pair in t.pairs::<mlua::Value, mlua::Value>() {
596                let (k, v) = pair?;
597                tbl.set(k.clone(), materialize_to_lua_value(lua, v)?)?;
598            }
599            Ok(mlua::Value::Table(tbl))
600        }
601        value => Ok(value),
602    }
603}
604
605/// Helper wrapper type for passing/returning serde encoded values from/to lua
606pub struct SerdeWrappedValue<T>(pub T);
607
608impl<T: serde::Serialize> SerdeWrappedValue<T> {
609    pub fn to_lua_value(&self, lua: &Lua) -> mlua::Result<mlua::Value> {
610        lua.to_value_with(&self.0, serialize_options())
611    }
612}
613
614impl<T: serde::Serialize> IntoLua for SerdeWrappedValue<T> {
615    fn into_lua(self, lua: &Lua) -> mlua::Result<mlua::Value> {
616        lua.to_value_with(&self.0, serialize_options())
617    }
618}
619
620impl<T: serde::de::DeserializeOwned> FromLua for SerdeWrappedValue<T> {
621    fn from_lua(value: mlua::Value, lua: &Lua) -> mlua::Result<SerdeWrappedValue<T>> {
622        let inner: T = from_lua_value(lua, value)?;
623        Ok(SerdeWrappedValue(inner))
624    }
625}
626
627impl<T> std::ops::Deref for SerdeWrappedValue<T> {
628    type Target = T;
629    fn deref(&self) -> &T {
630        &self.0
631    }
632}
633
634impl<T> std::ops::DerefMut for SerdeWrappedValue<T> {
635    fn deref_mut(&mut self) -> &mut T {
636        &mut self.0
637    }
638}
639
640/// Convert from a lua value to a deserializable type,
641/// with a slightly more helpful error message in case of failure.
642/// NOTE: the ", while processing" portion of the error messages generated
643/// here is coupled with a regex in typing.lua!
644pub fn from_lua_value<R>(lua: &Lua, value: mlua::Value) -> mlua::Result<R>
645where
646    R: serde::de::DeserializeOwned,
647{
648    let value_cloned = value.clone();
649    match lua.from_value(value) {
650        Ok(r) => Ok(r),
651        Err(err) => match materialize_to_lua_value(lua, value_cloned.clone()) {
652            Ok(materialized) => match lua.from_value(materialized.clone()) {
653                Ok(r) => Ok(r),
654                Err(err) => {
655                    let mut serializer = serde_json::Serializer::new(Vec::new());
656                    let serialized = match materialized.serialize(&mut serializer) {
657                        Ok(_) => String::from_utf8_lossy(&serializer.into_inner()).to_string(),
658                        Err(err) => format!("<unable to encode as json: {err:#}>"),
659                    };
660                    Err(mlua::Error::external(format!(
661                        "{err:#}, while processing {serialized}"
662                    )))
663                }
664            },
665            Err(materialize_err) => Err(mlua::Error::external(format!(
666                "{err:#}, while processing a userdata. \
667                    Additionally, encountered {materialize_err:#} \
668                    when trying to iterate the pairs of that userdata"
669            ))),
670        },
671    }
672}
673
674/// CallbackSignature is a bit sugar to aid with statically typing event callback
675/// function invocation.
676///
677/// The idea is that you declare a signature instance that is typed
678/// with its argument tuple (A), and its return type tuple (R).
679///
680/// The signature instance can then be used to invoke the callback by name.
681///
682/// The register method allows pre-registering events so that `kumo.on`
683/// can reason about them better.  The main function enabled by this is
684/// `allow_multiple`; when that is set to true, `kumo.on` will allow
685/// recording multiple callback instances, calling them in sequence
686/// until one of them returns a value.
687pub struct CallbackSignature<A, R>
688where
689    A: IntoLuaMulti,
690    R: FromLuaMulti,
691{
692    marker: std::marker::PhantomData<(A, R)>,
693    allow_multiple: bool,
694    name: Cow<'static, str>,
695}
696
697#[linkme::distributed_slice]
698pub static CALLBACK_SIGNATURES: [fn()];
699
700/// Helper for declaring a named event handler callback signature.
701///
702/// Usage looks like:
703///
704/// ```rust,ignore
705/// declare_event! {
706/// pub static GET_Q_CONFIG_SIG: Multiple(
707///         "get_queue_config",
708///         domain: &'static str,
709///         tenant: Option<&'static str>,
710///         campaign: Option<&'static str>,
711///         routing_domain: Option<&'static str>,
712///     ) -> QueueConfig;
713/// }
714/// ```
715///
716/// A handler can be either `Single` or `Multiple`, indicating whether
717/// only a single registration or multiple registrations are permitted.
718/// The string literal is the name of the event, followed by a fn-style
719/// parameter list which names each parameter in sequence, followed by
720/// the return value.  The names are not currently used in any way,
721/// but enhance the readability of the code.
722///
723/// In addition to declaring the signature in a global, some glue
724/// is generated that will register the signature appropriately
725/// so that lua knows whether it is single or multiple and can
726/// act appropriately when `kumo.on` is called.
727#[macro_export]
728macro_rules! declare_event {
729    ($vis:vis static $sym:ident: Multiple($name:literal $(,)? $($param_name:ident: $args:ty),* $(,)? ) -> $ret:ty;) => {
730        $vis static $sym: ::std::sync::LazyLock<
731            $crate::CallbackSignature<($($args),*), $ret>> =
732                ::std::sync::LazyLock::new(|| $crate::CallbackSignature::new_with_multiple($name));
733
734        $crate::paste::paste! {
735            #[linkme::distributed_slice($crate::CALLBACK_SIGNATURES)]
736            static [<CALLBACK_SIG_REGISTER_ $sym>]: fn() = || {
737                $sym.register();
738            };
739        }
740    };
741    ($vis:vis static $sym:ident: Single($name:literal $(,)? $($param_name:ident: $args:ty),* $(,)? ) -> $ret:ty;) => {
742        $vis static $sym: ::std::sync::LazyLock<
743            $crate::CallbackSignature<($($args),*), $ret>> =
744                ::std::sync::LazyLock::new(|| $crate::CallbackSignature::new($name));
745
746        $crate::paste::paste! {
747            #[linkme::distributed_slice($crate::CALLBACK_SIGNATURES)]
748            static [<CALLBACK_SIG_REGISTER_ $sym>]: fn() = || {
749                $sym.register();
750            };
751        }
752    };
753}
754
755/// For each event handler CallbackSignature that was declared via
756/// `declare_event!`, call its `.register()` method to register
757/// it so that `kumo.on` can give appropriate messaging if misused,
758/// and so that runtime dispatch will work correctly.
759///
760/// This should be called once, prior to running any lua code.
761fn register_declared_events() {
762    static ONCE: Once = Once::new();
763    ONCE.call_once(|| {
764        for reg_func in CALLBACK_SIGNATURES {
765            reg_func();
766        }
767    });
768}
769
770impl<A, R> CallbackSignature<A, R>
771where
772    A: IntoLuaMulti,
773    R: FromLuaMulti,
774{
775    pub fn new<S: Into<Cow<'static, str>>>(name: S) -> Self {
776        let name = name.into();
777
778        Self {
779            marker: std::marker::PhantomData,
780            allow_multiple: false,
781            name,
782        }
783    }
784
785    /// Make sure that you call .register() on this from
786    /// eg: mod_kumo::register in order for it to be instantiated
787    /// and visible to the config loader
788    pub fn new_with_multiple<S: Into<Cow<'static, str>>>(name: S) -> Self {
789        let name = name.into();
790
791        Self {
792            marker: std::marker::PhantomData,
793            allow_multiple: true,
794            name,
795        }
796    }
797
798    pub fn register(&self) {
799        if self.allow_multiple {
800            CALLBACK_ALLOWS_MULTIPLE
801                .lock()
802                .insert(self.name.to_string());
803        }
804    }
805
806    pub fn raise_error_if_allow_multiple(&self) -> anyhow::Result<()> {
807        anyhow::ensure!(
808            !self.allow_multiple(),
809            "handler {} is set to allow multiple handlers \
810                    but is registered with a single instance. This indicates that \
811                    register() was not called on the signature when initializing \
812                    the lua context. Please report this issue to the KumoMTA team!",
813            self.name
814        );
815        Ok(())
816    }
817
818    /// Return true if this signature allows multiple instances to be registered
819    /// and called.
820    pub fn allow_multiple(&self) -> bool {
821        self.allow_multiple
822    }
823
824    pub fn name(&self) -> &str {
825        &self.name
826    }
827
828    pub fn decorated_name(&self) -> String {
829        decorate_callback_name(&self.name)
830    }
831}
832
833pub fn does_callback_allow_multiple(name: &str) -> bool {
834    CALLBACK_ALLOWS_MULTIPLE.lock().contains(name)
835}
836
837pub fn decorate_callback_name(name: &str) -> String {
838    format!("kumomta-on-{name}")
839}
840
841pub fn serialize_options() -> mlua::SerializeOptions {
842    mlua::SerializeOptions::new()
843        .serialize_none_to_null(false)
844        .serialize_unit_to_null(false)
845}