#![warn(
rust_2018_idioms,
trivial_casts,
trivial_numeric_casts,
unreachable_pub,
unused_qualifications
)]
#![forbid(unsafe_code)]
use rsjsonnet_lang::arena::Arena;
use rsjsonnet_lang::interner::InternedStr;
use rsjsonnet_lang::program::{
EvalErrorKind, EvalStackTraceItem, ImportError, NativeError, Program, Thunk, Value,
};
use rsjsonnet_lang::span::SpanId;
pub(crate) struct TestCallbacks;
impl TestCallbacks {
pub(crate) fn new() -> Self {
Self
}
pub(crate) fn init_native_funcs(&mut self, program: &mut Program<'_>) {
program.register_native_func(program.intern_str("returnNum"), &[]);
program.register_native_func(
program.intern_str("isString"),
&[program.intern_str("value")],
);
program.register_native_func(
program.intern_str("lastItemOfFirst"),
&[program.intern_str("array")],
);
program.register_native_func(program.intern_str("failure"), &[]);
}
}
impl<'p> rsjsonnet_lang::program::Callbacks<'p> for TestCallbacks {
fn import(
&mut self,
_program: &mut Program<'p>,
_from: SpanId,
_path: &str,
) -> Result<Thunk<'p>, ImportError> {
unimplemented!();
}
fn import_str(
&mut self,
_program: &mut Program<'p>,
_from: SpanId,
_path: &str,
) -> Result<String, ImportError> {
unimplemented!();
}
fn import_bin(
&mut self,
_program: &mut Program<'p>,
_from: SpanId,
_path: &str,
) -> Result<Vec<u8>, ImportError> {
unimplemented!();
}
fn trace(&mut self, _program: &mut Program<'p>, _message: &str, _stack: &[EvalStackTraceItem]) {
}
fn native_call(
&mut self,
_program: &mut Program<'p>,
name: InternedStr<'p>,
args: &[Value<'p>],
) -> Result<Value<'p>, NativeError> {
match name.value() {
"returnNum" => {
assert!(args.is_empty());
Ok(Value::number(1234.0))
}
"isString" => {
let [arg] = args else {
unreachable!();
};
Ok(Value::bool(arg.is_string()))
}
"lastItemOfFirst" => {
let [arg] = args else {
unreachable!();
};
if let Some(root_items) = arg.to_array() {
if let Some(first_items) = root_items.first().and_then(Value::to_array) {
first_items.last().ok_or(NativeError).cloned()
} else {
Err(NativeError)
}
} else {
Err(NativeError)
}
}
"failure" => Err(NativeError),
_ => unreachable!(),
}
}
}
#[test]
fn test_value_types() {
#[track_caller]
pub(crate) fn test(input: &[u8], check: impl FnOnce(Value<'_>)) {
let arena = Arena::new();
let mut program = Program::new(&arena);
let mut callbacks = TestCallbacks::new();
let (span_ctx, _) = program
.span_manager_mut()
.insert_source_context(input.len());
let thunk = program
.load_source(span_ctx, input, true, "test.jsonnet")
.unwrap();
let value = program.eval_value(&thunk, &mut callbacks).unwrap();
check(value);
}
test(b"null", |value| {
assert!(value.is_null());
});
test(b"true", |value| {
assert_eq!(value.as_bool(), Some(true));
});
test(b"false", |value| {
assert_eq!(value.as_bool(), Some(false));
});
test(b"1.50", |value| {
assert_eq!(value.as_number(), Some(1.5));
});
test(b"\"string\"", |value| {
assert_eq!(value.to_string(), Some("string".into()));
});
test(b"[]", |value| {
let items = value.to_array().unwrap();
assert!(items.is_empty());
});
test(b"[true, false]", |value| {
let items = value.to_array().unwrap();
assert_eq!(items.len(), 2);
assert_eq!(items[0].as_bool(), Some(true));
assert_eq!(items[1].as_bool(), Some(false));
});
test(b"{}", |value| {
let fields = value.to_object().unwrap();
assert!(fields.is_empty());
});
test(b"{y: true, x: false}", |value| {
let fields = value.to_object().unwrap();
assert_eq!(fields.len(), 2);
assert_eq!(fields[0].0.value(), "x");
assert_eq!(fields[0].1.as_bool(), Some(false));
assert_eq!(fields[1].0.value(), "y");
assert_eq!(fields[1].1.as_bool(), Some(true));
});
}
#[test]
fn test_manifest_single_line() {
#[track_caller]
fn test(input: &[u8], expected_result: &str) {
let arena = Arena::new();
let mut program = Program::new(&arena);
let mut callbacks = TestCallbacks::new();
let (span_ctx, _) = program
.span_manager_mut()
.insert_source_context(input.len());
let thunk = program
.load_source(span_ctx, input, true, "test.jsonnet")
.unwrap();
let value = program.eval_value(&thunk, &mut callbacks).unwrap();
let result = program.manifest_json(&value, false);
let result = result.unwrap();
assert_eq!(result, expected_result);
}
test(b"null", "null");
test(b"true", "true");
test(b"false", "false");
test(b"0", "0");
test(b"-0", "-0");
test(b"1.5", "1.5");
test(b"\"string\"", "\"string\"");
test(b"[]", "[ ]");
test(b"[[]]", "[[ ]]");
test(b"[[1, 2]]", "[[1, 2]]");
test(b"[1, 2, 3]", "[1, 2, 3]");
test(b"{}", "{ }");
test(b"{a: 1, b: 2}", "{\"a\": 1, \"b\": 2}");
test(b"{a: 1, b:: 2, c::: 3}", "{\"a\": 1, \"c\": 3}");
test(b"{a: {}}", "{\"a\": { }}");
test(b"{a: {b: 1}}", "{\"a\": {\"b\": 1}}");
test(b"{a:: error \"err\"}", "{ }");
}
#[test]
fn test_manifest_multi_line() {
#[track_caller]
fn test(input: &[u8], expected_result: &str) {
let arena = Arena::new();
let mut program = Program::new(&arena);
let mut callbacks = TestCallbacks::new();
let (span_ctx, _) = program
.span_manager_mut()
.insert_source_context(input.len());
let thunk = program
.load_source(span_ctx, input, true, "test.jsonnet")
.unwrap();
let value = program.eval_value(&thunk, &mut callbacks).unwrap();
let result = program.manifest_json(&value, true);
let result = result.unwrap();
assert_eq!(result, expected_result);
}
test(b"null", "null");
test(b"true", "true");
test(b"false", "false");
test(b"0", "0");
test(b"-0", "-0");
test(b"1.5", "1.5");
test(b"\"string\"", "\"string\"");
test(b"[]", "[ ]");
test(b"[1, 2, 3]", "[\n 1,\n 2,\n 3\n]");
test(b"[[]]", "[\n [ ]\n]");
test(b"[[1, 2]]", "[\n [\n 1,\n 2\n ]\n]");
test(b"{}", "{ }");
test(b"{a: 1, b: 2}", "{\n \"a\": 1,\n \"b\": 2\n}");
test(b"{a: 1, b:: 2, c::: 3}", "{\n \"a\": 1,\n \"c\": 3\n}");
test(b"{a: {}}", "{\n \"a\": { }\n}");
test(b"{a: {b: 1}}", "{\n \"a\": {\n \"b\": 1\n }\n}");
test(b"{a:: error \"err\"}", r"{ }");
}
#[test]
fn test_native() {
#[track_caller]
fn test(input: &[u8], expected: &str) {
let arena = Arena::new();
let mut program = Program::new(&arena);
let mut callbacks = TestCallbacks::new();
callbacks.init_native_funcs(&mut program);
let (span_ctx, _) = program
.span_manager_mut()
.insert_source_context(input.len());
let root_thunk = program
.load_source(span_ctx, input, true, "test.jsonnet")
.unwrap();
let value = program.eval_value(&root_thunk, &mut callbacks).unwrap();
let value_str = program.manifest_json(&value, false).unwrap();
assert_eq!(value_str, expected);
}
test(b"std.native(\"returnNum\")()", "1234");
test(b"std.native(\"isString\")(null)", "false");
test(b"std.native(\"isString\")(\"str\")", "true");
test(
b"std.native(\"lastItemOfFirst\")([[1, 2], [3, 4], [5, 6]])",
"2",
);
test(b"std.native(\"unknown\")", "null");
test(b"std.isFunction(std.native(\"returnNum\"))", "true");
test(b"std.length(std.native(\"returnNum\"))", "0");
test(b"std.length(std.native(\"isString\"))", "1");
#[track_caller]
fn test_fail(input: &[u8]) {
let arena = Arena::new();
let mut program = Program::new(&arena);
let mut callbacks = TestCallbacks::new();
callbacks.init_native_funcs(&mut program);
let (span_ctx, _) = program
.span_manager_mut()
.insert_source_context(input.len());
let thunk = program
.load_source(span_ctx, input, true, "test.jsonnet")
.unwrap();
let error = program.eval_value(&thunk, &mut callbacks).err().unwrap();
assert_eq!(error.kind, EvalErrorKind::NativeCallFailed);
}
test_fail(b"std.native(\"failure\")()");
}
#[test]
#[should_panic(expected = "already registered")]
fn test_native_panic_repeated_func() {
let arena = Arena::new();
let mut program = Program::new(&arena);
let func_name = program.intern_str("nativeFunc");
let param_name = program.intern_str("param");
program.register_native_func(func_name, &[param_name]);
program.register_native_func(func_name, &[param_name]);
}
#[test]
#[should_panic(expected = "repeated parameter name")]
fn test_native_panic_repeated_param() {
let arena = Arena::new();
let mut program = Program::new(&arena);
let func_name = program.intern_str("nativeFunc");
let param_name = program.intern_str("param");
program.register_native_func(func_name, &[param_name, param_name]);
}