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