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 kumo_prometheus::declare_metric;
6use kumo_prometheus::prometheus::HistogramTimer;
7use mlua::{
8 FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, Lua, LuaSerdeExt, MetaMethod, RegistryKey, Table,
9 UserData, UserDataMethods, Value,
10};
11use parking_lot::FairMutex as Mutex;
12pub use pastey as paste;
13use serde::Serialize;
14use std::borrow::Cow;
15use std::collections::HashSet;
16use std::path::PathBuf;
17use std::sync::atomic::{AtomicBool, Ordering};
18use std::sync::{LazyLock, Once};
19use std::time::Instant;
20
21pub mod epoch;
22mod pool;
23
24static POLICY_FILE: LazyLock<Mutex<Option<PathBuf>>> = LazyLock::new(|| Mutex::new(None));
25static FUNCS: LazyLock<Mutex<Vec<RegisterFunc>>> = LazyLock::new(|| Mutex::new(vec![]));
26
27declare_metric! {
28static LUA_LOAD_COUNT: IntCounter("lua_load_count");
30}
31
32declare_metric! {
33static LUA_COUNT: IntGauge("lua_count");
35}
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);
42
43declare_metric! {
44static LATENCY_HIST: HistogramVec(
46 "lua_event_latency",
47 &["event"]);
48}
49
50declare_metric! {
51static EVENT_STARTED_COUNT: CounterVec(
53 "lua_event_started",
54 &["event"]
55 );
56}
57
58pub type RegisterFunc = fn(&Lua) -> anyhow::Result<()>;
59
60fn latency_timer(label: &str) -> HistogramTimer {
61 EVENT_STARTED_COUNT
62 .get_metric_with_label_values(&[label])
63 .expect("to get counter")
64 .inc();
65 LATENCY_HIST
66 .get_metric_with_label_values(&[label])
67 .expect("to get histo")
68 .start_timer()
69}
70
71#[derive(Debug)]
72struct LuaConfigInner {
73 lua: Lua,
74 created: Instant,
75 use_count: usize,
76 epoch: ConfigEpoch,
77}
78
79impl Drop for LuaConfigInner {
80 fn drop(&mut self) {
81 LUA_COUNT.dec();
82 }
83}
84
85#[derive(Debug)]
86pub struct LuaConfig {
87 inner: Option<LuaConfigInner>,
88}
89
90pub async fn set_policy_path(path: PathBuf) -> anyhow::Result<()> {
91 POLICY_FILE.lock().replace(path);
92 let config = load_config().await?;
93 config.put();
94 Ok(())
95}
96
97fn get_policy_path() -> Option<PathBuf> {
98 POLICY_FILE.lock().clone()
99}
100
101fn get_funcs() -> Vec<RegisterFunc> {
102 FUNCS.lock().clone()
103}
104pub fn is_validating() -> bool {
105 VALIDATE_ONLY.load(Ordering::Relaxed)
106}
107
108pub fn validation_failed() -> bool {
109 VALIDATION_FAILED.load(Ordering::Relaxed)
110}
111
112pub fn set_validation_failed() {
113 VALIDATION_FAILED.store(true, Ordering::Relaxed)
114}
115
116pub async fn load_config() -> anyhow::Result<LuaConfig> {
117 if let Some(pool) = pool_get() {
118 return Ok(pool);
119 }
120
121 LUA_LOAD_COUNT.inc();
122 let lua = Lua::new();
123 let created = Instant::now();
124 let epoch = get_current_epoch();
125
126 {
127 let globals = lua.globals();
128
129 if is_validating() {
130 globals.set("_VALIDATING_CONFIG", true)?;
131 }
132
133 let package: Table = globals.get("package")?;
134 let package_path: String = package.get("path")?;
135 let mut path_array: Vec<String> = package_path.split(";").map(|s| s.to_owned()).collect();
136
137 fn prefix_path(array: &mut Vec<String>, path: &str) {
138 array.insert(0, format!("{}/?.lua", path));
139 array.insert(1, format!("{}/?/init.lua", path));
140 }
141
142 prefix_path(&mut path_array, "/opt/kumomta/etc/policy");
143 prefix_path(&mut path_array, "/opt/kumomta/share");
144
145 #[cfg(debug_assertions)]
146 prefix_path(&mut path_array, "assets");
147
148 package.set("path", path_array.join(";"))?;
149 }
150
151 register_declared_events();
152
153 for func in get_funcs() {
154 (func)(&lua)?;
155 }
156
157 if let Some(policy) = get_policy_path() {
158 let code = tokio::fs::read_to_string(&policy)
159 .await
160 .with_context(|| format!("reading policy file {policy:?}"))?;
161
162 let func = {
163 let chunk = lua.load(&code);
164 let chunk = chunk.set_name(policy.to_string_lossy());
165 chunk.into_function()?
166 };
167
168 let _timer = latency_timer("context-creation");
169 func.call_async::<()>(()).await?;
170 }
171 LUA_COUNT.inc();
172
173 Ok(LuaConfig {
174 inner: Some(LuaConfigInner {
175 lua,
176 created,
177 use_count: 1,
178 epoch,
179 }),
180 })
181}
182
183pub fn register(func: RegisterFunc) {
184 FUNCS.lock().push(func);
185}
186
187impl LuaConfig {
188 fn set_current_event(&mut self, name: &str) -> mlua::Result<()> {
189 self.inner
190 .as_mut()
191 .unwrap()
192 .lua
193 .globals()
194 .set("_KUMO_CURRENT_EVENT", name.to_string())
195 }
196
197 pub fn lua(&self) -> anyhow::Result<&Lua> {
200 Ok(&self
201 .inner
202 .as_ref()
203 .context("LuaConfig is missing the inner Lua context")?
204 .lua)
205 }
206
207 pub fn convert_args_to_multi<A: Serialize>(
210 &self,
211 args: &[A],
212 ) -> anyhow::Result<mlua::MultiValue> {
213 let lua = self.inner.as_ref().unwrap();
214 let mut arg_vec = vec![];
215 for a in args.iter() {
216 arg_vec.push(lua.lua.to_value(a)?);
217 }
218 Ok(mlua::MultiValue::from_vec(arg_vec))
219 }
220
221 pub async fn convert_args_and_call_callback<A: Serialize>(
223 &mut self,
224 sig: &CallbackSignature<Value, ()>,
225 args: A,
226 ) -> anyhow::Result<()> {
227 let lua = self.inner.as_mut().unwrap();
228 let args = lua.lua.to_value(&args)?;
229
230 let name = sig.name();
231 let decorated_name = sig.decorated_name();
232
233 match lua
234 .lua
235 .named_registry_value::<mlua::Function>(&decorated_name)
236 {
237 Ok(func) => {
238 let _timer = latency_timer(name);
239 Ok(func.call_async(args).await?)
240 }
241 _ => anyhow::bail!("{name} has not been registered"),
242 }
243 }
244
245 pub fn put(mut self) {
263 if let Some(inner) = self.inner.take() {
264 pool_put(inner);
265 }
266 }
267
268 pub async fn call_callback<'a, A: IntoLuaMulti + Clone, R: FromLuaMulti>(
269 &mut self,
270 sig: &'a CallbackSignature<A, R>,
271 args: A,
272 ) -> anyhow::Result<CallbackDisposition<'a, R>> {
273 let name = sig.name();
274 self.set_current_event(name)?;
275 let lua = self.inner.as_mut().unwrap();
276 sig.call(&lua.lua, args).await
277 }
278
279 pub async fn async_call_callback<A: IntoLuaMulti + Clone, R: FromLuaMulti + Default>(
280 &mut self,
281 sig: &CallbackSignature<A, R>,
282 args: A,
283 ) -> anyhow::Result<R> {
284 let name = sig.name();
285 self.set_current_event(name)?;
286 let lua = self.inner.as_mut().unwrap();
287 Ok(sig.call(&lua.lua, args).await?.or_default())
288 }
289
290 pub async fn async_call_callback_non_default<A: IntoLuaMulti + Clone, R: FromLuaMulti>(
291 &mut self,
292 sig: &CallbackSignature<A, R>,
293 args: A,
294 ) -> anyhow::Result<R> {
295 let name = sig.name();
296 self.set_current_event(name)?;
297 let lua = self.inner.as_mut().unwrap();
298 sig.call(&lua.lua, args).await?.require_value()
299 }
300
301 pub async fn async_call_callback_non_default_opt<A: IntoLuaMulti + Clone, R: FromLua>(
302 &mut self,
303 sig: &CallbackSignature<A, Option<R>>,
304 args: A,
305 ) -> anyhow::Result<Option<R>> {
306 let name = sig.name();
307 self.set_current_event(name)?;
308 let lua = self.inner.as_mut().unwrap();
309 let result = sig.call(&lua.lua, args).await?;
310 match result.result {
311 None => Ok(None),
312 Some(result) => Ok(result),
313 }
314 }
315
316 pub fn remove_registry_value(&mut self, value: RegistryKey) -> anyhow::Result<()> {
317 Ok(self
318 .inner
319 .as_mut()
320 .unwrap()
321 .lua
322 .remove_registry_value(value)?)
323 }
324
325 pub async fn async_call_ctor<A: IntoLuaMulti + Clone>(
328 &mut self,
329 sig: &CallbackSignature<A, Value>,
330 args: A,
331 ) -> anyhow::Result<RegistryKey> {
332 let name = sig.name();
333 anyhow::ensure!(
334 !sig.allow_multiple(),
335 "ctor event signature for {name} is defined as allow_multiple, which is not supported"
336 );
337
338 let decorated_name = sig.decorated_name();
339 self.set_current_event(name)?;
340
341 let inner = self.inner.as_mut().unwrap();
342
343 let func = inner
344 .lua
345 .named_registry_value::<mlua::Function>(&decorated_name)?;
346
347 let _timer = latency_timer(name);
348 let value: Value = func.call_async(args.clone()).await?;
349 drop(func);
350
351 Ok(inner.lua.create_registry_value(value)?)
352 }
353
354 pub async fn with_registry_value<F, R, FUT>(
357 &mut self,
358 value: &RegistryKey,
359 func: F,
360 ) -> anyhow::Result<R>
361 where
362 R: FromLuaMulti,
363 F: FnOnce(Value) -> anyhow::Result<FUT>,
364 FUT: std::future::Future<Output = anyhow::Result<R>>,
365 {
366 let inner = self.inner.as_mut().unwrap();
367 let value = inner.lua.registry_value(value)?;
368 let future = (func)(value)?;
369 future.await
370 }
371}
372
373pub fn get_or_create_module(lua: &Lua, name: &str) -> anyhow::Result<mlua::Table> {
374 let globals = lua.globals();
375 let package: Table = globals.get("package")?;
376 let loaded: Table = package.get("loaded")?;
377
378 let module = loaded.get(name)?;
379 match module {
380 Value::Nil => {
381 let module = lua.create_table()?;
382 loaded.set(name, module.clone())?;
383 Ok(module)
384 }
385 Value::Table(table) => Ok(table),
386 wat => anyhow::bail!(
387 "cannot register module {} as package.loaded.{} is already set to a value of type {}",
388 name,
389 name,
390 wat.type_name()
391 ),
392 }
393}
394
395pub fn get_or_create_sub_module(lua: &Lua, name_path: &str) -> anyhow::Result<mlua::Table> {
400 let mut parent = get_or_create_module(lua, "kumo")?;
401 let mut path_so_far = String::new();
402
403 for name in name_path.split('.') {
404 if !path_so_far.is_empty() {
405 path_so_far.push('.');
406 }
407 path_so_far.push_str(name);
408
409 let sub = parent.get(name)?;
410 match sub {
411 Value::Nil => {
412 let sub = lua.create_table()?;
413 parent.set(name, sub.clone())?;
414 parent = sub;
415 }
416 Value::Table(sub) => {
417 parent = sub;
418 }
419 wat => anyhow::bail!(
420 "cannot register module kumo.{path_so_far} as it is already set to a value of type {}",
421 wat.type_name()
422 ),
423 }
424 }
425
426 Ok(parent)
427}
428
429pub fn any_err<E: std::fmt::Display>(err: E) -> mlua::Error {
431 mlua::Error::external(format!("{err:#}"))
432}
433
434pub fn impl_pairs_and_index<T, M>(methods: &mut M)
441where
442 T: UserData + Serialize,
443 M: UserDataMethods<T>,
444{
445 methods.add_meta_method(MetaMethod::Pairs, move |lua, this, _: ()| {
446 let Ok(serde_json::Value::Object(map)) = serde_json::to_value(this).map_err(any_err) else {
447 return Err(mlua::Error::external("must serialize to Map"));
448 };
449
450 let mut value_iter = map.into_iter();
451
452 let iter_func = lua.create_function_mut(
453 move |lua, (_state, _control): (Value, Value)| match value_iter.next() {
454 Some((key, value)) => {
455 let key = lua.to_value(&key)?;
456 let value = lua.to_value(&value)?;
457 Ok((key, value))
458 }
459 None => Ok((Value::Nil, Value::Nil)),
460 },
461 )?;
462
463 Ok((Value::Function(iter_func), Value::Nil, Value::Nil))
464 });
465
466 methods.add_meta_method(MetaMethod::Index, move |lua, this, field: Value| {
467 let value = lua.to_value(this)?;
468 match value {
469 Value::Table(t) => t.get(field),
470 _ => Ok(Value::Nil),
471 }
472 });
473
474 methods.add_meta_method(MetaMethod::Len, move |lua, this, _: ()| {
475 let value = lua.to_value(this)?;
476 match value {
477 Value::Table(v) => v.len(),
478 Value::String(v) => Ok(v.as_bytes().len() as i64),
479 _ => Ok(0),
480 }
481 });
482}
483
484pub fn materialize_to_lua_value(lua: &Lua, value: mlua::Value) -> mlua::Result<mlua::Value> {
492 match value {
493 mlua::Value::UserData(ud) => {
494 let mt = ud.metatable()?;
495 let Ok(pairs) = mt.get::<mlua::Function>("__pairs") else {
496 let value = ud.into_lua(lua)?;
497 return Err(mlua::Error::external(format!(
498 "cannot materialize_to_lua_value {value:?} \
499 because it has no __pairs metamethod"
500 )));
501 };
502 let tbl = lua.create_table()?;
503 let (iter_func, state, mut control): (mlua::Function, mlua::Value, mlua::Value) =
504 pairs.call(mlua::Value::UserData(ud.clone()))?;
505
506 loop {
507 let (k, v): (mlua::Value, mlua::Value) =
508 iter_func.call((state.clone(), control))?;
509 if k.is_nil() {
510 break;
511 }
512
513 tbl.set(k.clone(), materialize_to_lua_value(lua, v)?)?;
514 control = k;
515 }
516
517 Ok(mlua::Value::Table(tbl))
518 }
519 mlua::Value::Table(t) => {
520 let tbl = lua.create_table()?;
521 for pair in t.pairs::<mlua::Value, mlua::Value>() {
522 let (k, v) = pair?;
523 tbl.set(k.clone(), materialize_to_lua_value(lua, v)?)?;
524 }
525 Ok(mlua::Value::Table(tbl))
526 }
527 value => Ok(value),
528 }
529}
530
531pub struct SerdeWrappedValue<T>(pub T);
533
534impl<T: serde::Serialize> serde::Serialize for SerdeWrappedValue<T> {
535 fn serialize<S>(
536 &self,
537 s: S,
538 ) -> Result<<S as serde::Serializer>::Ok, <S as serde::Serializer>::Error>
539 where
540 S: serde::Serializer,
541 {
542 self.0.serialize(s)
543 }
544}
545
546impl<T: Clone> Clone for SerdeWrappedValue<T> {
547 fn clone(&self) -> Self {
548 SerdeWrappedValue(self.0.clone())
549 }
550}
551
552impl<T: Default> Default for SerdeWrappedValue<T> {
553 fn default() -> Self {
554 SerdeWrappedValue(Default::default())
555 }
556}
557
558impl<T: serde::Serialize> SerdeWrappedValue<T> {
559 pub fn to_lua_value(&self, lua: &Lua) -> mlua::Result<mlua::Value> {
560 lua.to_value_with(&self.0, serialize_options())
561 }
562}
563
564impl<T: serde::Serialize> IntoLua for SerdeWrappedValue<T> {
565 fn into_lua(self, lua: &Lua) -> mlua::Result<mlua::Value> {
566 lua.to_value_with(&self.0, serialize_options())
567 }
568}
569
570impl<T: serde::de::DeserializeOwned> FromLua for SerdeWrappedValue<T> {
571 fn from_lua(value: mlua::Value, lua: &Lua) -> mlua::Result<SerdeWrappedValue<T>> {
572 let inner: T = from_lua_value(lua, value)?;
573 Ok(SerdeWrappedValue(inner))
574 }
575}
576
577impl<T> std::ops::Deref for SerdeWrappedValue<T> {
578 type Target = T;
579 fn deref(&self) -> &T {
580 &self.0
581 }
582}
583
584impl<T> std::ops::DerefMut for SerdeWrappedValue<T> {
585 fn deref_mut(&mut self) -> &mut T {
586 &mut self.0
587 }
588}
589
590pub fn from_lua_value<R>(lua: &Lua, value: mlua::Value) -> mlua::Result<R>
595where
596 R: serde::de::DeserializeOwned,
597{
598 let value_cloned = value.clone();
599 match lua.from_value(value) {
600 Ok(r) => Ok(r),
601 Err(err) => match materialize_to_lua_value(lua, value_cloned.clone()) {
602 Ok(materialized) => match lua.from_value(materialized.clone()) {
603 Ok(r) => Ok(r),
604 Err(err) => {
605 let mut serializer = serde_json::Serializer::new(Vec::new());
606 let serialized = match materialized.serialize(&mut serializer) {
607 Ok(_) => String::from_utf8_lossy(&serializer.into_inner()).to_string(),
608 Err(err) => format!("<unable to encode as json: {err:#}>"),
609 };
610 Err(mlua::Error::external(format!(
611 "{err:#}, while processing {serialized}"
612 )))
613 }
614 },
615 Err(materialize_err) => Err(mlua::Error::external(format!(
616 "{err:#}, while processing a userdata. \
617 Additionally, encountered {materialize_err:#} \
618 when trying to iterate the pairs of that userdata"
619 ))),
620 },
621 }
622}
623
624pub struct CallbackSignature<A, R>
638where
639 A: IntoLuaMulti,
640 R: FromLuaMulti,
641{
642 marker: std::marker::PhantomData<(A, R)>,
643 allow_multiple: bool,
644 name: Cow<'static, str>,
645}
646
647#[linkme::distributed_slice]
648pub static CALLBACK_SIGNATURES: [fn()];
649
650#[macro_export]
678macro_rules! declare_event {
679 ($vis:vis static $sym:ident: Multiple($name:literal $(,)? $($param_name:ident: $args:ty),* $(,)? ) -> $ret:ty;) => {
680 $vis static $sym: ::std::sync::LazyLock<
681 $crate::CallbackSignature<($($args),*), $ret>> =
682 ::std::sync::LazyLock::new(|| $crate::CallbackSignature::new_with_multiple($name));
683
684 $crate::paste::paste! {
685 #[linkme::distributed_slice($crate::CALLBACK_SIGNATURES)]
686 static [<CALLBACK_SIG_REGISTER_ $sym>]: fn() = || {
687 $sym.register();
688 };
689 }
690 };
691 ($vis:vis static $sym:ident: Single($name:literal $(,)? $($param_name:ident: $args:ty),* $(,)? ) -> $ret:ty;) => {
692 $vis static $sym: ::std::sync::LazyLock<
693 $crate::CallbackSignature<($($args),*), $ret>> =
694 ::std::sync::LazyLock::new(|| $crate::CallbackSignature::new($name));
695
696 $crate::paste::paste! {
697 #[linkme::distributed_slice($crate::CALLBACK_SIGNATURES)]
698 static [<CALLBACK_SIG_REGISTER_ $sym>]: fn() = || {
699 $sym.register();
700 };
701 }
702 };
703}
704
705fn register_declared_events() {
712 static ONCE: Once = Once::new();
713 ONCE.call_once(|| {
714 for reg_func in CALLBACK_SIGNATURES {
715 reg_func();
716 }
717 });
718}
719
720impl<A, R> CallbackSignature<A, R>
721where
722 A: IntoLuaMulti,
723 R: FromLuaMulti,
724{
725 pub fn new<S: Into<Cow<'static, str>>>(name: S) -> Self {
726 let name = name.into();
727
728 Self {
729 marker: std::marker::PhantomData,
730 allow_multiple: false,
731 name,
732 }
733 }
734
735 pub fn new_with_multiple<S: Into<Cow<'static, str>>>(name: S) -> Self {
739 let name = name.into();
740
741 Self {
742 marker: std::marker::PhantomData,
743 allow_multiple: true,
744 name,
745 }
746 }
747
748 pub fn register(&self) {
749 if self.allow_multiple {
750 CALLBACK_ALLOWS_MULTIPLE
751 .lock()
752 .insert(self.name.to_string());
753 }
754 }
755
756 pub fn raise_error_if_allow_multiple(&self) -> anyhow::Result<()> {
757 anyhow::ensure!(
758 !self.allow_multiple(),
759 "handler {} is set to allow multiple handlers \
760 but is registered with a single instance. This indicates that \
761 register() was not called on the signature when initializing \
762 the lua context. Please report this issue to the KumoMTA team!",
763 self.name
764 );
765 Ok(())
766 }
767
768 pub fn allow_multiple(&self) -> bool {
771 self.allow_multiple
772 }
773
774 pub fn name(&self) -> &str {
775 &self.name
776 }
777
778 pub fn decorated_name(&self) -> String {
779 decorate_callback_name(&self.name)
780 }
781}
782
783impl<A, R> CallbackSignature<A, R>
784where
785 A: IntoLuaMulti + Clone,
786 R: FromLuaMulti,
787{
788 pub async fn call<'a>(
794 &'a self,
795 lua: &Lua,
796 args: A,
797 ) -> anyhow::Result<CallbackDisposition<'a, R>> {
798 let name = self.name();
799 let decorated_name = self.decorated_name();
800
801 match lua.named_registry_value::<mlua::Value>(&decorated_name)? {
802 Value::Table(tbl) => {
803 for func in tbl.sequence_values::<mlua::Function>().collect::<Vec<_>>() {
804 let func = func?;
805 let _timer = latency_timer(name);
806 let result: mlua::MultiValue = func.call_async(args.clone()).await?;
807 if result.is_empty() {
808 continue;
810 }
811 let result = R::from_lua_multi(result, lua)?;
812 return Ok(CallbackDisposition {
813 handler_was_defined: true,
814 result: Some(result),
815 event_name: name,
816 });
817 }
818 Ok(CallbackDisposition {
819 handler_was_defined: false,
820 result: None,
821 event_name: name,
822 })
823 }
824 Value::Function(func) => {
825 self.raise_error_if_allow_multiple()?;
826 let _timer = latency_timer(name);
827 Ok(CallbackDisposition {
828 handler_was_defined: true,
829 result: Some(func.call_async(args.clone()).await?),
830 event_name: name,
831 })
832 }
833 _ => Ok(CallbackDisposition {
834 handler_was_defined: false,
835 result: None,
836 event_name: name,
837 }),
838 }
839 }
840}
841
842pub fn does_callback_allow_multiple(name: &str) -> bool {
843 CALLBACK_ALLOWS_MULTIPLE.lock().contains(name)
844}
845
846pub fn decorate_callback_name(name: &str) -> String {
847 format!("kumomta-on-{name}")
848}
849
850pub struct CallbackDisposition<'a, T> {
853 pub handler_was_defined: bool,
858 pub result: Option<T>,
860 pub event_name: &'a str,
862}
863
864impl<'a, T> CallbackDisposition<'a, T> {
865 pub fn require_value(mut self) -> anyhow::Result<T> {
868 if !self.handler_was_defined {
869 anyhow::bail!("Event {} has not been registered", self.event_name);
870 }
871 match self.result.take() {
872 Some(value) => Ok(value),
873 None => anyhow::bail!("Event {} did not return a value", self.event_name),
874 }
875 }
876}
877impl<'a, T: Default> CallbackDisposition<'a, T> {
878 pub fn or_default(self) -> T {
880 self.result.unwrap_or_default()
881 }
882}
883
884pub fn serialize_options() -> mlua::SerializeOptions {
885 mlua::SerializeOptions::new()
886 .serialize_none_to_null(false)
887 .serialize_unit_to_null(false)
888}