Skip to content

Commit

Permalink
Improved path handling for imports (#127)
Browse files Browse the repository at this point in the history
  • Loading branch information
arduano authored May 26, 2024
1 parent c4d2acd commit b824a5c
Show file tree
Hide file tree
Showing 6 changed files with 81 additions and 45 deletions.
30 changes: 24 additions & 6 deletions nixjs-rt/src/builtins.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -18,6 +18,7 @@ import {
Path,
TRUE,
} from "./lib";
import { dirOf, isAbsolutePath, normalizePath } from "./utils";

type BuiltinsRecord = Record<string, (param: NixType) => NixType>;

Expand Down Expand Up @@ -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];
},
Expand All @@ -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) => {
Expand Down
15 changes: 11 additions & 4 deletions nixjs-rt/src/errors/other.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}
35 changes: 6 additions & 29 deletions nixjs-rt/src/lib.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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
Expand All @@ -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();
Expand Down
33 changes: 33 additions & 0 deletions nixjs-rt/src/utils.ts
Original file line number Diff line number Diff line change
@@ -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);
}
11 changes: 6 additions & 5 deletions src/eval/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ pub enum NixErrorKind {
got: NixTypeKind,
},
Other {
message: String,
codename: String,
},
MissingAttribute {
attr_path: Vec<String>,
Expand Down Expand Up @@ -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")?;
Expand Down Expand Up @@ -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(),
},
}),
}
Expand Down
2 changes: 1 addition & 1 deletion src/tests/builtins.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
);
}
Expand Down

0 comments on commit b824a5c

Please sign in to comment.