fix: handle NixPath in nixValueToJson
This commit is contained in:
@@ -2,7 +2,7 @@
|
|||||||
* Conversion and serialization builtin functions
|
* Conversion and serialization builtin functions
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { NixValue, NixString, NixPath } from "../types";
|
import type { NixString, NixValue } from "../types";
|
||||||
import { isStringWithContext, isNixPath } from "../types";
|
import { isStringWithContext, isNixPath } from "../types";
|
||||||
import { force } from "../thunk";
|
import { force } from "../thunk";
|
||||||
import { type NixStringContext, mkStringWithContext, addBuiltContext } from "../string-context";
|
import { type NixStringContext, mkStringWithContext, addBuiltContext } from "../string-context";
|
||||||
@@ -59,7 +59,7 @@ export const fromTOML = (e: NixValue): never => {
|
|||||||
|
|
||||||
export const toJSON = (e: NixValue): NixString => {
|
export const toJSON = (e: NixValue): NixString => {
|
||||||
const context: Set<string> = new Set();
|
const context: Set<string> = new Set();
|
||||||
const string = JSON.stringify(nixValueToJson(e, new Set(), context));
|
const string = JSON.stringify(nixValueToJson(e, true, context, true));
|
||||||
if (context.size === 0) {
|
if (context.size === 0) {
|
||||||
return string;
|
return string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import type { NixValue, NixAttrs } from "../types";
|
import type { NixValue, NixAttrs } from "../types";
|
||||||
import { forceStringValue, forceList, forceStringNoCtx } from "../type-assert";
|
import { forceStringValue, forceList, forceStringNoCtx } from "../type-assert";
|
||||||
import { force, createThunk } from "../thunk";
|
import { force } from "../thunk";
|
||||||
import {
|
import {
|
||||||
type DerivationData,
|
type DerivationData,
|
||||||
type OutputInfo,
|
type OutputInfo,
|
||||||
@@ -99,17 +99,6 @@ const extractOutputs = (attrs: NixAttrs, structuredAttrs: boolean): string[] =>
|
|||||||
return outputs;
|
return outputs;
|
||||||
};
|
};
|
||||||
|
|
||||||
const extractOutputsForWrapper = (attrs: NixAttrs): string[] => {
|
|
||||||
if (!("outputs" in attrs)) {
|
|
||||||
return ["out"];
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME: trace context?
|
|
||||||
const outputs = forceList(attrs.outputs).map(forceStringValue);
|
|
||||||
validateOutputs(outputs);
|
|
||||||
return outputs;
|
|
||||||
};
|
|
||||||
|
|
||||||
const extractArgs = (attrs: NixAttrs, outContext: NixStringContext): string[] => {
|
const extractArgs = (attrs: NixAttrs, outContext: NixStringContext): string[] => {
|
||||||
if (!("args" in attrs)) {
|
if (!("args" in attrs)) {
|
||||||
return [];
|
return [];
|
||||||
@@ -126,12 +115,7 @@ const structuredAttrsExcludedKeys = new Set([
|
|||||||
"args",
|
"args",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const specialAttrs = new Set([
|
const specialAttrs = new Set(["args", "__ignoreNulls", "__contentAddressed", "__impure"]);
|
||||||
"args",
|
|
||||||
"__ignoreNulls",
|
|
||||||
"__contentAddressed",
|
|
||||||
"__impure",
|
|
||||||
]);
|
|
||||||
|
|
||||||
const sortedJsonStringify = (obj: Record<string, any>): string => {
|
const sortedJsonStringify = (obj: Record<string, any>): string => {
|
||||||
const sortedKeys = Object.keys(obj).sort();
|
const sortedKeys = Object.keys(obj).sort();
|
||||||
@@ -159,7 +143,7 @@ const extractEnv = (
|
|||||||
if (ignoreNulls && forcedValue === null) {
|
if (ignoreNulls && forcedValue === null) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
jsonAttrs[key] = nixValueToJson(value, new Set(), outContext);
|
jsonAttrs[key] = nixValueToJson(value, true, outContext, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (key === "allowedReferences") {
|
if (key === "allowedReferences") {
|
||||||
@@ -295,7 +279,6 @@ export const derivationStrict = (args: NixValue): NixAttrs => {
|
|||||||
const drvArgs = extractArgs(attrs, collectedContext);
|
const drvArgs = extractArgs(attrs, collectedContext);
|
||||||
const env = extractEnv(attrs, structuredAttrs, ignoreNulls, collectedContext, drvName);
|
const env = extractEnv(attrs, structuredAttrs, ignoreNulls, collectedContext, drvName);
|
||||||
|
|
||||||
|
|
||||||
const { inputDrvs, inputSrcs } = extractInputDrvsAndSrcs(collectedContext);
|
const { inputDrvs, inputSrcs } = extractInputDrvsAndSrcs(collectedContext);
|
||||||
|
|
||||||
const collectDrvReferences = (): string[] => {
|
const collectDrvReferences = (): string[] => {
|
||||||
@@ -429,47 +412,6 @@ export const derivationStrict = (args: NixValue): NixAttrs => {
|
|||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const derivation = (args: NixValue): NixAttrs => {
|
export const derivation = (_: NixValue): NixAttrs => {
|
||||||
const attrs = forceAttrs(args);
|
throw new Error("unreachable: placeholder derivation implementation called")
|
||||||
|
|
||||||
const outputs: string[] = extractOutputsForWrapper(attrs);
|
|
||||||
|
|
||||||
const strictThunk = createThunk(() => derivationStrict(args), "derivationStrict");
|
|
||||||
|
|
||||||
const commonAttrs: NixAttrs = { ...attrs };
|
|
||||||
|
|
||||||
const outputToAttrListElement = (outputName: string): { name: string; value: NixAttrs } => {
|
|
||||||
const value: NixAttrs = {
|
|
||||||
...commonAttrs,
|
|
||||||
outPath: createThunk(() => (force(strictThunk) as NixAttrs)[outputName], `outPath_${outputName}`),
|
|
||||||
drvPath: createThunk(() => (force(strictThunk) as NixAttrs).drvPath, "drvPath"),
|
|
||||||
type: "derivation",
|
|
||||||
outputName,
|
|
||||||
};
|
|
||||||
return { name: outputName, value };
|
|
||||||
};
|
|
||||||
|
|
||||||
const outputsList = outputs.map(outputToAttrListElement);
|
|
||||||
|
|
||||||
for (const { name: outputName } of outputsList) {
|
|
||||||
commonAttrs[outputName] = createThunk(
|
|
||||||
() => outputsList.find((o) => o.name === outputName)!.value,
|
|
||||||
`output_${outputName}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
commonAttrs.all = createThunk(() => outputsList.map((o) => o.value), "all_outputs");
|
|
||||||
commonAttrs.drvAttrs = attrs;
|
|
||||||
|
|
||||||
for (const { value: outputObj } of outputsList) {
|
|
||||||
for (const { name: outputName } of outputsList) {
|
|
||||||
outputObj[outputName] = createThunk(
|
|
||||||
() => outputsList.find((o) => o.name === outputName)!.value,
|
|
||||||
`output_${outputName}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
outputObj.all = createThunk(() => outputsList.map((o) => o.value), "all_outputs");
|
|
||||||
outputObj.drvAttrs = attrs;
|
|
||||||
}
|
|
||||||
|
|
||||||
return outputsList[0].value;
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,49 +1,57 @@
|
|||||||
import { HAS_CONTEXT, NixStringContext } from "./string-context";
|
import { HAS_CONTEXT, NixStringContext } from "./string-context";
|
||||||
import { force } from "./thunk";
|
import { force, isThunk } from "./thunk";
|
||||||
import type { NixValue } from "./types";
|
import type { NixValue } from "./types";
|
||||||
import { isStringWithContext } from "./types";
|
import { isStringWithContext, IS_PATH } from "./types";
|
||||||
|
|
||||||
export const nixValueToJson = (
|
export const nixValueToJson = (
|
||||||
value: NixValue,
|
value: NixValue,
|
||||||
seen = new Set<object>(),
|
strict: boolean,
|
||||||
outContext?: NixStringContext,
|
outContext: NixStringContext,
|
||||||
|
copyToStore: boolean,
|
||||||
|
seen: Set<NixValue> = new Set(),
|
||||||
): any => {
|
): any => {
|
||||||
const v = force(value);
|
const v = strict ? force(value) : value;
|
||||||
|
|
||||||
|
if (isThunk(v) || typeof v === "function")
|
||||||
|
throw new Error(`cannot convert ${isThunk(v) ? "thunk" : "lambda"} to JSON`);
|
||||||
if (v === null) return null;
|
if (v === null) return null;
|
||||||
if (typeof v === "boolean") return v;
|
|
||||||
if (typeof v === "string") return v;
|
|
||||||
if (typeof v === "number") return v;
|
|
||||||
|
|
||||||
if (typeof v === "object" && HAS_CONTEXT in v && "context" in v) {
|
|
||||||
if (outContext) {
|
|
||||||
for (const elem of v.context) {
|
|
||||||
outContext.add(elem);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return v.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof v === "bigint") {
|
if (typeof v === "bigint") {
|
||||||
const num = Number(v);
|
const num = Number(v);
|
||||||
if (v > Number.MAX_SAFE_INTEGER || v < Number.MIN_SAFE_INTEGER) {
|
if (v > Number.MAX_SAFE_INTEGER || v < Number.MIN_SAFE_INTEGER) {
|
||||||
console.warn(`derivation: integer ${v} exceeds safe range, precision may be lost in __structuredAttrs`);
|
console.warn(`integer ${v} exceeds safe range, precision may be lost`);
|
||||||
}
|
}
|
||||||
return num;
|
return num;
|
||||||
}
|
}
|
||||||
|
if (typeof v === "number") return v;
|
||||||
if (typeof v === "object" && v !== null) {
|
if (typeof v === "boolean") return v;
|
||||||
if (seen.has(v)) {
|
if (typeof v === "string") return v;
|
||||||
throw new Error("derivation: circular reference detected in __structuredAttrs");
|
if (typeof v === "object" && HAS_CONTEXT in v) {
|
||||||
|
for (const elem of v.context) {
|
||||||
|
outContext.add(elem);
|
||||||
}
|
}
|
||||||
seen.add(v);
|
return v.value;
|
||||||
|
}
|
||||||
|
if (typeof v === "object" && IS_PATH in v) {
|
||||||
|
if (copyToStore) {
|
||||||
|
const storePath = Deno.core.ops.op_copy_path_to_store(v.value);
|
||||||
|
outContext.add(storePath);
|
||||||
|
return storePath;
|
||||||
|
} else {
|
||||||
|
return v.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (seen.has(v)) {
|
||||||
|
throw new Error("cycle detected in toJSON");
|
||||||
|
} else {
|
||||||
|
seen.add(v)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Array.isArray(v)) {
|
if (Array.isArray(v)) {
|
||||||
return v.map((item) => nixValueToJson(item, seen, outContext));
|
return v.map((item) => nixValueToJson(item, strict, outContext, copyToStore, seen));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof v === "object") {
|
// NixAttrs
|
||||||
if ("__toString" in v && typeof force(v.__toString) === "function") {
|
if ("__toString" in v && typeof force(v.__toString) === "function") {
|
||||||
const toStringMethod = force(v.__toString) as (self: typeof v) => NixValue;
|
const toStringMethod = force(v.__toString) as (self: typeof v) => NixValue;
|
||||||
const result = force(toStringMethod(v));
|
const result = force(toStringMethod(v));
|
||||||
@@ -58,24 +66,17 @@ export const nixValueToJson = (
|
|||||||
}
|
}
|
||||||
return result.value;
|
return result.value;
|
||||||
}
|
}
|
||||||
return nixValueToJson(result, seen, outContext);
|
return nixValueToJson(result, strict, outContext, copyToStore, seen);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ("outPath" in v) {
|
if ("outPath" in v) {
|
||||||
return nixValueToJson(v.outPath, seen, outContext);
|
return nixValueToJson(v.outPath, strict, outContext, copyToStore,seen);
|
||||||
}
|
}
|
||||||
|
|
||||||
const result: Record<string, any> = {};
|
const result: Record<string, any> = {};
|
||||||
const keys = Object.keys(v).sort();
|
const keys = Object.keys(v).sort();
|
||||||
for (const key of keys) {
|
for (const key of keys) {
|
||||||
result[key] = nixValueToJson((v as Record<string, NixValue>)[key], seen, outContext);
|
result[key] = nixValueToJson(v[key], strict, outContext, copyToStore, seen);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof v === "function") {
|
|
||||||
throw new Error("derivation: cannot serialize function in __structuredAttrs");
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Error(`derivation: cannot serialize ${typeof v} to JSON`);
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -43,8 +43,7 @@ export const escapeString = (s: string): string => {
|
|||||||
|
|
||||||
const quoteString = (s: string): string => `"${s}"`;
|
const quoteString = (s: string): string => `"${s}"`;
|
||||||
|
|
||||||
const cmpByKey = <T>(a: [string, T], b: [string, T]): number =>
|
const cmpByKey = <T>(a: [string, T], b: [string, T]): number => (a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0);
|
||||||
a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0;
|
|
||||||
|
|
||||||
export const generateAterm = (drv: DerivationData): string => {
|
export const generateAterm = (drv: DerivationData): string => {
|
||||||
const outputEntries: string[] = [];
|
const outputEntries: string[] = [];
|
||||||
|
|||||||
@@ -7,7 +7,13 @@ import type { NixValue, NixList, NixAttrs, NixString, NixPath } from "./types";
|
|||||||
import { isNixPath } from "./types";
|
import { isNixPath } from "./types";
|
||||||
import { force } from "./thunk";
|
import { force } from "./thunk";
|
||||||
import { forceNumeric, forceList, forceAttrs, coerceNumeric } from "./type-assert";
|
import { forceNumeric, forceList, forceAttrs, coerceNumeric } from "./type-assert";
|
||||||
import { type NixStringContext, getStringValue, getStringContext, mergeContexts, mkStringWithContext } from "./string-context";
|
import {
|
||||||
|
type NixStringContext,
|
||||||
|
getStringValue,
|
||||||
|
getStringContext,
|
||||||
|
mergeContexts,
|
||||||
|
mkStringWithContext,
|
||||||
|
} from "./string-context";
|
||||||
import { coerceToString, StringCoercionMode } from "./builtins/conversion";
|
import { coerceToString, StringCoercionMode } from "./builtins/conversion";
|
||||||
import { mkPath } from "./path";
|
import { mkPath } from "./path";
|
||||||
import { typeOf, isNixString } from "./builtins/type-check";
|
import { typeOf, isNixString } from "./builtins/type-check";
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ export class NixThunk implements NixThunkInterface {
|
|||||||
* @param value - Value to check
|
* @param value - Value to check
|
||||||
* @returns true if value is a NixThunk
|
* @returns true if value is a NixThunk
|
||||||
*/
|
*/
|
||||||
export const isThunk = (value: unknown): value is NixThunkInterface => {
|
export const isThunk = (value: NixValue): value is NixThunkInterface => {
|
||||||
return value !== null && typeof value === "object" && IS_THUNK in value && value[IS_THUNK] === true;
|
return value !== null && typeof value === "object" && IS_THUNK in value && value[IS_THUNK] === true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -541,7 +541,7 @@ fn structured_attrs_rejects_functions() {
|
|||||||
|
|
||||||
assert!(result.is_err());
|
assert!(result.is_err());
|
||||||
let err_msg = result.unwrap_err().to_string();
|
let err_msg = result.unwrap_err().to_string();
|
||||||
assert!(err_msg.contains("function") && err_msg.contains("serialize"));
|
assert!(err_msg.contains("cannot convert lambda to JSON"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
Reference in New Issue
Block a user