diff --git a/nixjs-rt/src/builtins.ts b/nixjs-rt/src/builtins.ts index 7f01095..8e6d4a3 100644 --- a/nixjs-rt/src/builtins.ts +++ b/nixjs-rt/src/builtins.ts @@ -1,4 +1,4 @@ -import { err, errType, errTypes } from "./errors"; +import { err, errType, errTypes, highlighted } from "./errors"; import { abortError } from "./errors/abort"; import { otherError } from "./errors/other"; import { typeMismatchError } from "./errors/typeError"; @@ -18,6 +18,7 @@ import { Path, TRUE, } from "./lib"; +import { dirOf, isAbsolutePath, normalizePath } from "./utils"; type BuiltinsRecord = Record NixType>; @@ -341,7 +342,10 @@ export function getBuiltins() { ); } if (listStrict.values.length === 0) { - throw otherError("Cannot fetch the first element in an empty list."); + throw otherError( + "Cannot fetch the first element in an empty list.", + "builtins-head-on-empty-list", + ); } return listStrict.values[0]; }, @@ -354,12 +358,26 @@ export function getBuiltins() { throw builtinBasicTypeMismatchError("import", pathStrict, expected); } - const pathValue = pathStrict.toJs(); + let pathValue = ""; + if (pathStrict instanceof NixString) { + pathValue = normalizePath(pathStrict.toJs()); + } else if (pathStrict instanceof Path) { + pathValue = pathStrict.toJs(); + } + + // Check if it's an absolute path. Relative paths are not allowed. + // Path data types are always automatically absolute. + if (!isAbsolutePath(pathValue)) { + throw otherError( + err`string ${highlighted(JSON.stringify(pathValue))} doesn't represent an absolute path. Only absolute paths are allowed for imports.`, + "builtins-import-non-absolute-path", + ); + } - const resultingFn: (ctx: EvalCtx) => NixType = importNixModule(pathValue); + const resultingFn = importNixModule(pathValue); - const ctx = new EvalCtx(pathValue); - return resultingFn(ctx); + const newCtx = new EvalCtx(dirOf(pathValue)); + return resultingFn(newCtx); }, intersectAttrs: (arg) => { diff --git a/nixjs-rt/src/errors/other.ts b/nixjs-rt/src/errors/other.ts index 8691a31..b00e1df 100644 --- a/nixjs-rt/src/errors/other.ts +++ b/nixjs-rt/src/errors/other.ts @@ -2,14 +2,21 @@ import { ErrorMessage, err, NixError, instanceToClass } from "."; import { NixTypeClass, NixTypeInstance } from "../lib"; export class NixOtherError { - constructor(public readonly message: string) {} + constructor( + public readonly message: ErrorMessage, + public readonly codename: string, + ) {} toDefaultErrorMessage(): ErrorMessage { - return err`${this.message}`; + return this.message; } } -export function otherError(message: string) { - let other = new NixOtherError(message); +export function otherError(message: string | ErrorMessage, codename: string) { + if (typeof message === "string") { + message = err`${message}`; + } + + let other = new NixOtherError(message, codename); return new NixError(other, other.toDefaultErrorMessage()); } diff --git a/nixjs-rt/src/lib.ts b/nixjs-rt/src/lib.ts index 6e746cc..1184e7d 100644 --- a/nixjs-rt/src/lib.ts +++ b/nixjs-rt/src/lib.ts @@ -20,6 +20,7 @@ import { couldntFindVariableError, } from "./errors/variable"; import { NixAbortError } from "./errors/abort"; +import { isAbsolutePath, joinPaths, normalizePath } from "./utils"; // Error re-exports export { NixError } from "./errors"; @@ -535,6 +536,7 @@ class AttrsetBuilder implements Scope { if (attrPath.length === 0) { throw otherError( "Cannot add an undefined attribute name to the attrset.", + "attrset-add-undefined-attrname", ); } const attrName = attrPath[0].toStrict(); @@ -1291,34 +1293,6 @@ export function recursiveStrictAttrset(theAttrset: Attrset): Attrset { return theAttrset; } -function isAbsolutePath(path: string): boolean { - return path.startsWith("/"); -} - -function joinPaths(abs_base: string, path: string): string { - return `${abs_base}/${path}`; -} - -function normalizePath(path: string): string { - let segments = path.split("/"); - let normalizedSegments = new Array(); - for (const segment of segments) { - switch (segment) { - case "": - break; - case ".": - break; - case "..": - normalizedSegments.pop(); - break; - default: - normalizedSegments.push(segment); - break; - } - } - return (isAbsolutePath(path) ? "/" : "") + normalizedSegments.join("/"); -} - /** * If given an attrset entry like `a = value`, then this function returns just the given value. * If the attrset has multiple segments (e.g. `a.b.c = value`), then this function returns @@ -1330,7 +1304,10 @@ function _attrPathToValue( value: NixType, ): undefined | NixType { if (attrPath.length === 0) { - throw otherError("Unexpected attr path of zero length."); + throw otherError( + "Unexpected attr path of zero length.", + "attrset-attrpath-zero-length", + ); } let attrName = attrPath[0].toStrict(); diff --git a/nixjs-rt/src/utils.ts b/nixjs-rt/src/utils.ts new file mode 100644 index 0000000..6396920 --- /dev/null +++ b/nixjs-rt/src/utils.ts @@ -0,0 +1,33 @@ +export function isAbsolutePath(path: string): boolean { + return path.startsWith("/"); +} + +export function joinPaths(abs_base: string, path: string): string { + return `${abs_base}/${path}`; +} + +export function normalizePath(path: string): string { + let segments = path.split("/"); + let normalizedSegments: string[] = []; + for (const segment of segments) { + switch (segment) { + case "": + break; + case ".": + break; + case "..": + normalizedSegments.pop(); + break; + default: + normalizedSegments.push(segment); + break; + } + } + return (isAbsolutePath(path) ? "/" : "") + normalizedSegments.join("/"); +} + +export function dirOf(path: string) { + // Return everything before the final slash + const lastSlash = path.lastIndexOf("/"); + return path.substring(0, lastSlash); +} diff --git a/src/eval/error.rs b/src/eval/error.rs index dfda09b..dc034b1 100644 --- a/src/eval/error.rs +++ b/src/eval/error.rs @@ -72,7 +72,7 @@ pub enum NixErrorKind { got: NixTypeKind, }, Other { - message: String, + codename: String, }, MissingAttribute { attr_path: Vec, @@ -153,9 +153,10 @@ fn try_js_error_to_rust( NixErrorKind::TypeMismatch { expected, got } } "NixOtherError" => { - let message_js = get_js_value_key(scope, &kind_js, "message")?; - let message = message_js.to_rust_string_lossy(scope); - NixErrorKind::Other { message } + let codename_js = get_js_value_key(scope, &kind_js, "codename")?; + let codename = codename_js.to_rust_string_lossy(scope); + + NixErrorKind::Other { codename } } "NixMissingAttributeError" => { let attr_path_js = get_js_value_key(scope, &kind_js, "attrPath")?; @@ -211,7 +212,7 @@ fn nix_type_class_to_enum( "Unexpected type name: {name}" ))], kind: NixErrorKind::Other { - message: format!("Unexpected type name: {name}"), + codename: "unknown-type".to_owned(), }, }), } diff --git a/src/tests/builtins.rs b/src/tests/builtins.rs index f9240bb..fe0a8f8 100644 --- a/src/tests/builtins.rs +++ b/src/tests/builtins.rs @@ -476,7 +476,7 @@ mod head { assert_eq!( eval_err("builtins.head []"), NixErrorKind::Other { - message: "Cannot fetch the first element in an empty list.".to_string() + codename: "builtins-head-on-empty-list".to_string() } ); }