use rong::*;
use std::cell::Cell;
use std::collections::BTreeSet;
#[cfg(feature = "worker")]
pub use rong_worker as worker;
#[derive(Clone, Copy, Debug)]
pub struct ModuleDescriptor {
name: &'static str,
dependencies: &'static [&'static str],
init: fn(&JSContext) -> JSResult<()>,
}
impl ModuleDescriptor {
const fn new(
name: &'static str,
dependencies: &'static [&'static str],
init: fn(&JSContext) -> JSResult<()>,
) -> Self {
Self {
name,
dependencies,
init,
}
}
pub const fn name(&self) -> &'static str {
self.name
}
pub const fn dependencies(&self) -> &'static [&'static str] {
self.dependencies
}
}
#[cfg(test)]
#[derive(Clone, Copy)]
struct ModuleSpec {
name: &'static str,
dependencies: &'static [&'static str],
}
macro_rules! define_modules {
($(($feature:literal, $name:literal, [$($dependency:literal),* $(,)?], $init:path)),* $(,)?) => {
#[cfg(test)]
static MODULE_SPECS: &[ModuleSpec] = &[
$(ModuleSpec {
name: $name,
dependencies: &[$($dependency),*],
}),*
];
static COMPILED_MODULES: &[ModuleDescriptor] = &[
$(
#[cfg(feature = $feature)]
ModuleDescriptor::new($name, &[$($dependency),*], $init)
),*
];
};
}
define_modules!(
("timer", "timer", [], rong_timer::init),
("cron", "cron", [], rong_cron::init),
("event", "event", [], rong_event::init),
("exception", "exception", [], rong_exception::init),
("abort", "abort", [], rong_abort::init),
("encoding", "encoding", [], rong_encoding::init),
("console", "console", [], rong_console::init),
("url", "url", [], rong_url::init),
("buffer", "buffer", [], rong_buffer::init),
("stream", "stream", ["abort"], rong_stream::init),
("compression", "compression", [], rong_compression::init),
(
"command",
"command",
["abort", "buffer", "encoding", "stream"],
rong_command::init
),
(
"http",
"http",
["abort", "exception", "buffer", "url", "stream"],
rong_http::init
),
("assert", "assert", [], rong_assert::init),
("fs", "fs", ["abort", "exception", "stream"], rong_fs::init),
("storage", "storage", [], rong_storage::init),
("redis", "redis", ["abort"], rong_redis::init),
("sqlite", "sqlite", [], rong_sqlite::init),
("worker", "worker", [], rong_worker::init),
("s3", "s3", [], rong_s3::init),
);
const _: () = assert!(COMPILED_MODULES.len() <= u64::BITS as usize);
#[derive(Default)]
struct InitializedModules(Cell<u64>);
fn descriptor_index(name: &str) -> Option<usize> {
COMPILED_MODULES
.iter()
.position(|module| module.name == name)
}
fn resolve_module_mask<I, S>(requested: I) -> JSResult<u64>
where
I: IntoIterator<Item = S>,
S: AsRef<str>,
{
let mut selected = 0_u64;
let mut missing = BTreeSet::new();
for requested_name in requested {
let requested_name = requested_name.as_ref();
if let Some(index) = descriptor_index(requested_name) {
selected |= 1 << index;
} else {
missing.insert(requested_name.to_owned());
}
}
if !missing.is_empty() {
let available = compiled_module_names().collect::<Vec<_>>().join(", ");
return Err(HostError::new(
rong::error::E_INVALID_ARG,
format!(
"Modules are not compiled into this build: {}. Available modules: {available}",
missing.into_iter().collect::<Vec<_>>().join(", ")
),
)
.with_name("TypeError")
.into());
}
loop {
let previous = selected;
for (index, module) in COMPILED_MODULES.iter().enumerate() {
if selected & (1 << index) == 0 {
continue;
}
for dependency in module.dependencies {
let dependency_index = descriptor_index(dependency).ok_or_else(|| {
HostError::new(
rong::error::E_INTERNAL,
format!(
"Module registry is invalid: {} depends on uncompiled module {dependency}",
module.name
),
)
})?;
selected |= 1 << dependency_index;
}
}
if selected == previous {
break;
}
}
Ok(selected)
}
fn initialized_modules(ctx: &JSContext) -> &InitializedModules {
if ctx.get_state::<InitializedModules>().is_none() {
ctx.set_state(InitializedModules::default());
}
ctx.get_state::<InitializedModules>()
.expect("module state was inserted above")
}
pub const fn compiled_modules() -> &'static [ModuleDescriptor] {
COMPILED_MODULES
}
pub fn compiled_module_names() -> impl ExactSizeIterator<Item = &'static str> + DoubleEndedIterator
{
COMPILED_MODULES.iter().map(ModuleDescriptor::name)
}
pub fn is_compiled(name: &str) -> bool {
descriptor_index(name).is_some()
}
pub fn resolve_modules<I, S>(requested: I) -> JSResult<Vec<&'static str>>
where
I: IntoIterator<Item = S>,
S: AsRef<str>,
{
let selected = resolve_module_mask(requested)?;
Ok(COMPILED_MODULES
.iter()
.enumerate()
.filter(|(index, _)| selected & (1 << index) != 0)
.map(|(_, module)| module)
.map(ModuleDescriptor::name)
.collect())
}
pub fn init<I, S>(ctx: &JSContext, modules: I) -> JSResult<()>
where
I: IntoIterator<Item = S>,
S: AsRef<str>,
{
let selected = resolve_module_mask(modules)?;
if selected == 0 {
return Ok(());
}
let state = initialized_modules(ctx);
let mut initialized = state.0.get();
for (index, module) in COMPILED_MODULES.iter().enumerate() {
let bit = 1 << index;
if selected & bit == 0 || initialized & bit != 0 {
continue;
}
(module.init)(ctx)?;
initialized |= bit;
state.0.set(initialized);
}
Ok(())
}
pub fn init_all(ctx: &JSContext) -> JSResult<()> {
init(ctx, compiled_module_names())
}
pub fn initialized_module_names(ctx: &JSContext) -> Vec<&'static str> {
let Some(state) = ctx.get_state::<InitializedModules>() else {
return Vec::new();
};
let initialized = state.0.get();
COMPILED_MODULES
.iter()
.enumerate()
.filter(|(index, _)| initialized & (1 << index) != 0)
.map(|(_, module)| module)
.map(ModuleDescriptor::name)
.collect()
}
pub fn is_initialized(ctx: &JSContext, name: &str) -> bool {
let Some(index) = descriptor_index(name) else {
return false;
};
ctx.get_state::<InitializedModules>()
.is_some_and(|state| state.0.get() & (1 << index) != 0)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn names_are_unique_and_dependencies_are_ordered() {
assert!(MODULE_SPECS.len() <= u64::BITS as usize);
let mut names = BTreeSet::new();
for module in MODULE_SPECS {
assert!(
names.insert(module.name),
"duplicate module: {}",
module.name
);
for dependency in module.dependencies {
assert!(
names.contains(dependency),
"{} depends on missing or later module {dependency}",
module.name
);
}
}
}
#[test]
fn every_compiled_module_resolves_with_its_dependencies() {
for (index, module) in COMPILED_MODULES.iter().enumerate() {
let resolved = resolve_module_mask([module.name]).unwrap();
assert_ne!(resolved & (1 << index), 0);
for dependency in module.dependencies {
let dependency_index = descriptor_index(dependency).unwrap();
assert_ne!(resolved & (1 << dependency_index), 0);
}
}
}
#[test]
fn unknown_modules_fail_resolution() {
assert!(resolve_module_mask(["definitely-not-a-module"]).is_err());
}
}