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 = "kv")]
76pub mod kv;
77#[cfg(feature = "llm")]
78pub mod llm;
79#[cfg(feature = "log")]
80pub mod log;
81#[cfg(feature = "path")]
82pub mod path;
83#[cfg(feature = "regex")]
84pub mod regex;
85#[cfg(feature = "schema")]
86pub mod schema;
87#[cfg(feature = "sql")]
88pub mod sql;
89#[cfg(feature = "string")]
90pub mod string;
91#[cfg(feature = "task")]
92pub mod task;
93#[cfg(feature = "time")]
94pub mod time;
95#[cfg(feature = "uuid")]
96pub mod uuid;
97#[cfg(feature = "validate")]
98pub mod validate;
99
100pub(crate) mod util;
101
102use config::Config;
103use mlua::prelude::*;
104
105/// Module factory function type.
106pub type ModuleFactory = fn(&Lua) -> LuaResult<LuaTable>;
107
108/// Register all enabled modules with default configuration.
109///
110/// Equivalent to `register_all_with(lua, namespace, Config::default())`.
111///
112/// # Warning
113///
114/// The default configuration uses [`policy::Unrestricted`], which allows
115/// Lua scripts to access **any** file on the filesystem.  For untrusted
116/// scripts, use [`register_all_with`] with a [`policy::Sandboxed`] policy.
117pub fn register_all(lua: &Lua, namespace: &str) -> LuaResult<LuaTable> {
118    register_all_with(lua, namespace, Config::default())
119}
120
121/// Register all enabled modules with custom configuration.
122///
123/// The [`Config`] is stored in `lua.app_data` and consulted by each
124/// module for policy checks and limit values.
125///
126/// # Calling multiple times
127///
128/// Calling this function again on the same [`Lua`] instance **replaces**
129/// the previous [`Config`] (and the shared HTTP agent, if the `http`
130/// feature is enabled).  Functions registered by earlier calls remain
131/// in the namespace table but will use the **new** Config for all
132/// subsequent invocations.  This is intentional — it allows
133/// reconfiguration — but callers should be aware that there is no
134/// "merge" behaviour.
135pub fn register_all_with(lua: &Lua, namespace: &str, config: Config) -> LuaResult<LuaTable> {
136    lua.set_app_data(config);
137
138    let ns = lua.create_table()?;
139
140    macro_rules! register {
141        ($name:literal, $mod:ident) => {{
142            #[cfg(feature = $name)]
143            ns.set($name, $mod::module(lua)?)?;
144        }};
145    }
146
147    register!("json", json);
148    register!("env", env);
149    register!("path", path);
150    register!("string", string);
151    register!("regex", regex);
152    register!("validate", validate);
153    register!("log", log);
154    register!("uuid", uuid);
155    register!("base64", base64);
156    register!("time", time);
157    register!("fs", fs);
158    register!("http", http);
159    register!("llm", llm);
160    register!("hash", hash);
161    register!("schema", schema);
162
163    lua.globals().set(namespace, ns.clone())?;
164    Ok(ns)
165}
166
167/// Returns a list of `(name, factory)` pairs for all enabled modules.
168///
169/// Each entry is a `(&'static str, fn(&Lua) -> LuaResult<LuaTable>)`.
170/// The list only includes modules whose cargo features are active.
171///
172/// # When to use
173///
174/// Use this when you need per-module registration instead of the
175/// all-in-one [`register_all`]. Common case: integration with
176/// `mlua-pkg`'s `NativeResolver`:
177///
178/// ```rust,ignore
179/// // `ignore`: NativeResolver is from the `mlua-pkg` crate, which is
180/// // not a dependency of this crate. Cannot be compiled in-tree.
181/// let mut resolver = NativeResolver::new();
182/// for (name, factory) in mlua_batteries::module_entries() {
183///     resolver = resolver.add(name, |lua| factory(lua).map(mlua::Value::Table));
184/// }
185/// ```
186pub fn module_entries() -> Vec<(&'static str, ModuleFactory)> {
187    let mut entries: Vec<(&'static str, ModuleFactory)> = Vec::new();
188
189    macro_rules! entry {
190        ($name:literal, $mod:ident) => {{
191            #[cfg(feature = $name)]
192            entries.push(($name, $mod::module));
193        }};
194    }
195
196    entry!("json", json);
197    entry!("env", env);
198    entry!("path", path);
199    entry!("string", string);
200    entry!("regex", regex);
201    entry!("validate", validate);
202    entry!("log", log);
203    entry!("uuid", uuid);
204    entry!("base64", base64);
205    entry!("time", time);
206    entry!("fs", fs);
207    entry!("http", http);
208    entry!("llm", llm);
209    entry!("hash", hash);
210    entry!("schema", schema);
211
212    entries
213}