use crate::Result;
use crate::runtime::Runtime;
use mlua::{Lua, Table, Value};
use semver::Version;
pub fn init_module(lua: &Lua, _runtime: &Runtime) -> Result<Table> {
let table = lua.create_table()?;
table.set("compare", lua.create_function(compare)?)?;
table.set("parse", lua.create_function(parse)?)?;
table.set("is_prerelease", lua.create_function(is_prerelease)?)?;
table.set("valid", lua.create_function(valid)?)?;
Ok(table)
}
fn compare(_lua: &Lua, (version1, operator, version2): (String, String, String)) -> mlua::Result<bool> {
let v1 =
parse_version(&version1).map_err(|e| mlua::Error::runtime(format!("Invalid version '{}': {}", version1, e)))?;
let v2 =
parse_version(&version2).map_err(|e| mlua::Error::runtime(format!("Invalid version '{}': {}", version2, e)))?;
let val = match operator.as_str() {
"<" => v1.lt(&v2),
"<=" => v1.le(&v2),
"=" | "==" => v1 == v2,
">=" => v1.ge(&v2),
">" => v1.gt(&v2),
"!=" | "~=" => v1 != v2,
_ => return Err(mlua::Error::RuntimeError(format!("Invalid operator '{}'", operator))),
};
Ok(val)
}
fn parse(_lua: &Lua, version: String) -> mlua::Result<Table> {
let v =
parse_version(&version).map_err(|e| mlua::Error::runtime(format!("Invalid version '{}': {}", version, e)))?;
let table = _lua.create_table()?;
table.set("major", v.major)?;
table.set("minor", v.minor)?;
table.set("patch", v.patch)?;
if !v.pre.is_empty() {
table.set("prerelease", v.pre.to_string())?;
} else {
table.set("prerelease", Value::Nil)?;
}
if !v.build.is_empty() {
table.set("build", v.build.to_string())?;
} else {
table.set("build", Value::Nil)?;
}
Ok(table)
}
fn is_prerelease(_lua: &Lua, version: String) -> mlua::Result<bool> {
let v =
parse_version(&version).map_err(|e| mlua::Error::runtime(format!("Invalid version '{}': {}", version, e)))?;
Ok(!v.pre.is_empty())
}
fn valid(_lua: &Lua, version: String) -> mlua::Result<bool> {
Ok(parse_version(&version).is_ok())
}
fn parse_version(version: &str) -> Result<Version> {
let version = Version::parse(version).map_err(crate::Error::custom)?;
Ok(version)
}
#[cfg(test)]
mod tests {
type Result<T> = core::result::Result<T, Box<dyn std::error::Error>>;
use crate::_test_support::{eval_lua, setup_lua};
use crate::script::lua_script::aip_semver;
#[tokio::test]
async fn test_lua_semver_compare_basic() -> Result<()> {
let lua = setup_lua(aip_semver::init_module, "semver")?;
let test_cases = [
("1.2.3", ">", "1.2.0", true),
("1.2.3", "<", "1.3.0", true),
("1.2.3", "=", "1.2.3", true),
("1.2.3", "==", "1.2.3", true),
("1.2.3", ">=", "1.2.3", true),
("1.2.3", "<=", "1.2.3", true),
("1.2.3", ">", "1.2.3", false),
("1.2.3", "<", "1.2.3", false),
("1.2.3", "!=", "1.2.0", true),
("1.2.3", "~=", "1.2.0", true),
("0.6.12-WIP", ">", "0.6.9", true),
("0.6.7-wip", "<", "0.6.8", true),
("0.6.7-wip", "<", "0.6.7", true),
("0.6.7-alpha", "<", "0.6.7", true),
("0.6.7-alpha.1", "<", "0.6.7-alpha.2", true),
("0.6.7-alpha.1", "<", "0.6.7-beta", true),
];
for (v1, op, v2, expected) in test_cases {
let script = format!(r#"return aip.semver.compare("{}", "{}", "{}")"#, v1, op, v2);
let result: bool = eval_lua(&lua, &script)?.as_bool().ok_or("should be bool")?;
assert_eq!(
result, expected,
"Failed for compare(\"{}\", \"{}\", \"{}\"): expected {}, got {}",
v1, op, v2, expected, result
);
}
Ok(())
}
#[tokio::test]
async fn test_lua_semver_compare_with_prerelease() -> Result<()> {
let lua = setup_lua(aip_semver::init_module, "semver")?;
let test_cases = [
("1.2.3-alpha", ">", "1.2.0", true), ("1.2.0", "<", "1.2.3-alpha", true), ("1.0.0+build", "=", "1.0.0", false), ("1.2.3-alpha", "<", "1.2.3", true), ("1.2.3-alpha", "<", "1.2.4", true), ("1.2.3", ">", "1.2.3-alpha", true), ("1.2.3-alpha", "!=", "1.2.3", true), ("1.0.0+build", "!=", "1.0.0", true), ("1.2.3-alpha", "~=", "1.2.3", true), ];
for (v1, op, v2, expected) in test_cases {
let script = format!(r#"return aip.semver.compare("{}", "{}", "{}")"#, v1, op, v2);
let result: bool = eval_lua(&lua, &script)?.as_bool().ok_or("should be bool")?;
assert_eq!(
result, expected,
"Failed for compare(\"{}\", \"{}\", \"{}\"): expected {}, got {}",
v1, op, v2, expected, result
);
}
Ok(())
}
#[tokio::test]
async fn test_lua_semver_parse() -> Result<()> {
let lua = setup_lua(aip_semver::init_module, "semver")?;
let script = r#"
local result = aip.semver.parse("1.2.3-beta.1+build.123")
return {
major = result.major,
minor = result.minor,
patch = result.patch,
prerelease = result.prerelease,
build = result.build
}
"#;
let result = eval_lua(&lua, script)?;
let json_result = serde_json::to_string(&result)?;
let parsed: serde_json::Value = serde_json::from_str(&json_result)?;
assert_eq!(parsed["major"], 1);
assert_eq!(parsed["minor"], 2);
assert_eq!(parsed["patch"], 3);
assert_eq!(parsed["prerelease"], "beta.1");
assert_eq!(parsed["build"], "build.123");
Ok(())
}
#[tokio::test]
async fn test_lua_semver_is_prerelease() -> Result<()> {
let lua = setup_lua(aip_semver::init_module, "semver")?;
let test_cases = [
("1.2.3", false),
("1.2.3-beta", true),
("0.6.7-WIP", true),
("1.0.0+build.123", false),
("1.0.0-alpha+build.123", true),
];
for (version, expected) in test_cases {
let script = format!(r#"return aip.semver.is_prerelease("{}")"#, version);
let result: bool = eval_lua(&lua, &script)?.as_bool().ok_or("should be bool")?;
assert_eq!(
result, expected,
"Failed for is_prerelease(\"{}\"): expected {}, got {}",
version, expected, result
);
}
Ok(())
}
#[tokio::test]
async fn test_lua_semver_valid() -> Result<()> {
let lua = setup_lua(aip_semver::init_module, "semver")?;
let test_cases = [
("1.2.3", true),
("1.2.3-beta", true),
("0.6.7-WIP", true),
("invalid", false),
("1.0", false),
("1.0.0+build.123", true),
];
for (version, expected) in test_cases {
let script = format!(r#"return aip.semver.valid("{}")"#, version);
let result: bool = eval_lua(&lua, &script)?.as_bool().ok_or("should be bool")?;
assert_eq!(
result, expected,
"Failed for valid(\"{}\"): expected {}, got {}",
version, expected, result
);
}
Ok(())
}
}