Skip to main content

mlua_batteries/
lib.rs

1//! Batteries-included standard library modules for mlua.
2//!
3//! Each module exposes a single `module(lua) -> LuaResult<LuaTable>` entry point.
4//! Register individually or use [`register_all`] for convenience.
5//!
6//! # Platform support
7//!
8//! This crate targets **Unix server platforms** (Linux, macOS).
9//! Windows is not a supported target.
10//!
11//! # Encoding — UTF-8 only (by design)
12//!
13//! All path arguments are received as Rust [`String`] (UTF-8).
14//! Non-UTF-8 Lua strings are rejected at the `FromLua` boundary.
15//! Returned paths use [`to_string_lossy`](std::path::Path::to_string_lossy),
16//! replacing any non-UTF-8 bytes with U+FFFD.
17//!
18//! ## Why not raw bytes / `OsStr`?
19//!
20//! mlua's `FromLua` for `String` performs UTF-8 validation — non-UTF-8
21//! values produce `FromLuaConversionError` before reaching handler code.
22//! Bypassing this would require accepting `mlua::String` + `as_bytes()`
23//! in every function, converting through `OsStr::from_bytes()`, and
24//! returning `OsStr::as_bytes()` back to Lua.  This adds complexity
25//! across all path-accepting functions for a scenario (non-UTF-8
26//! filenames) that is rare on modern systems.
27//!
28//! References:
29//! - mlua `String::to_str()`: <https://docs.rs/mlua/latest/mlua/struct.String.html>
30//! - mlua string internals: <https://deepwiki.com/mlua-rs/mlua/2.3.4-strings>
31//!
32//! # Quick start
33//!
34//! ```rust,no_run
35//! use mlua::prelude::*;
36//!
37//! let lua = Lua::new();
38//! mlua_batteries::register_all(&lua, "std").unwrap();
39//! // Lua: std.json.encode({a = 1})
40//! // Lua: std.env.get("HOME")
41//! ```
42//!
43//! # Custom configuration
44//!
45//! ```rust,ignore
46//! // Requires the `sandbox` feature.
47//! use mlua::prelude::*;
48//! use mlua_batteries::config::Config;
49//! use mlua_batteries::policy::Sandboxed;
50//!
51//! let lua = Lua::new();
52//! let config = Config::builder()
53//!     .path_policy(Sandboxed::new(["/app/data"]).unwrap().read_only())
54//!     .max_walk_depth(50)
55//!     .build()
56//!     .expect("invalid config");
57//! mlua_batteries::register_all_with(&lua, "std", config).unwrap();
58//! ```
59
60pub mod config;
61pub mod policy;
62
63#[cfg(feature = "base64")]
64pub mod base64;
65#[cfg(feature = "env")]
66pub mod env;
67#[cfg(feature = "fs")]
68pub mod fs;
69#[cfg(feature = "hash")]
70pub mod hash;
71#[cfg(feature = "http")]
72pub mod http;
73#[cfg(feature = "json")]
74pub mod json;
75#[cfg(feature = "llm")]
76pub mod llm;
77#[cfg(feature = "log")]
78pub mod log;
79#[cfg(feature = "path")]
80pub mod path;
81#[cfg(feature = "regex")]
82pub mod regex;
83#[cfg(feature = "schema")]
84pub mod schema;
85#[cfg(feature = "string")]
86pub mod string;
87#[cfg(feature = "time")]
88pub mod time;
89#[cfg(feature = "uuid")]
90pub mod uuid;
91#[cfg(feature = "validate")]
92pub mod validate;
93
94pub(crate) mod util;
95
96use config::Config;
97use mlua::prelude::*;
98
99/// Module factory function type.
100pub type ModuleFactory = fn(&Lua) -> LuaResult<LuaTable>;
101
102/// Register all enabled modules with default configuration.
103///
104/// Equivalent to `register_all_with(lua, namespace, Config::default())`.
105///
106/// # Warning
107///
108/// The default configuration uses [`policy::Unrestricted`], which allows
109/// Lua scripts to access **any** file on the filesystem.  For untrusted
110/// scripts, use [`register_all_with`] with a [`policy::Sandboxed`] policy.
111pub fn register_all(lua: &Lua, namespace: &str) -> LuaResult<LuaTable> {
112    register_all_with(lua, namespace, Config::default())
113}
114
115/// Register all enabled modules with custom configuration.
116///
117/// The [`Config`] is stored in `lua.app_data` and consulted by each
118/// module for policy checks and limit values.
119///
120/// # Calling multiple times
121///
122/// Calling this function again on the same [`Lua`] instance **replaces**
123/// the previous [`Config`] (and the shared HTTP agent, if the `http`
124/// feature is enabled).  Functions registered by earlier calls remain
125/// in the namespace table but will use the **new** Config for all
126/// subsequent invocations.  This is intentional — it allows
127/// reconfiguration — but callers should be aware that there is no
128/// "merge" behaviour.
129pub fn register_all_with(lua: &Lua, namespace: &str, config: Config) -> LuaResult<LuaTable> {
130    lua.set_app_data(config);
131
132    let ns = lua.create_table()?;
133
134    macro_rules! register {
135        ($name:literal, $mod:ident) => {{
136            #[cfg(feature = $name)]
137            ns.set($name, $mod::module(lua)?)?;
138        }};
139    }
140
141    register!("json", json);
142    register!("env", env);
143    register!("path", path);
144    register!("string", string);
145    register!("regex", regex);
146    register!("validate", validate);
147    register!("log", log);
148    register!("uuid", uuid);
149    register!("base64", base64);
150    register!("time", time);
151    register!("fs", fs);
152    register!("http", http);
153    register!("llm", llm);
154    register!("hash", hash);
155    register!("schema", schema);
156
157    lua.globals().set(namespace, ns.clone())?;
158    Ok(ns)
159}
160
161/// Returns a list of `(name, factory)` pairs for all enabled modules.
162///
163/// Each entry is a `(&'static str, fn(&Lua) -> LuaResult<LuaTable>)`.
164/// The list only includes modules whose cargo features are active.
165///
166/// # When to use
167///
168/// Use this when you need per-module registration instead of the
169/// all-in-one [`register_all`]. Common case: integration with
170/// `mlua-pkg`'s `NativeResolver`:
171///
172/// ```rust,ignore
173/// // `ignore`: NativeResolver is from the `mlua-pkg` crate, which is
174/// // not a dependency of this crate. Cannot be compiled in-tree.
175/// let mut resolver = NativeResolver::new();
176/// for (name, factory) in mlua_batteries::module_entries() {
177///     resolver = resolver.add(name, |lua| factory(lua).map(mlua::Value::Table));
178/// }
179/// ```
180pub fn module_entries() -> Vec<(&'static str, ModuleFactory)> {
181    let mut entries: Vec<(&'static str, ModuleFactory)> = Vec::new();
182
183    macro_rules! entry {
184        ($name:literal, $mod:ident) => {{
185            #[cfg(feature = $name)]
186            entries.push(($name, $mod::module));
187        }};
188    }
189
190    entry!("json", json);
191    entry!("env", env);
192    entry!("path", path);
193    entry!("string", string);
194    entry!("regex", regex);
195    entry!("validate", validate);
196    entry!("log", log);
197    entry!("uuid", uuid);
198    entry!("base64", base64);
199    entry!("time", time);
200    entry!("fs", fs);
201    entry!("http", http);
202    entry!("llm", llm);
203    entry!("hash", hash);
204    entry!("schema", schema);
205
206    entries
207}