@firfi/quint-connect
Version:
Model-based testing framework connecting Quint specifications to TypeScript implementations
139 lines • 5.49 kB
JavaScript
import { Cause, Effect, Exit, Option, Schema } from "effect";
import { transformITFValue } from "@firfi/itf-trace-parser";
import { quintRun } from "./runner/runner.js";
export { defaultConfig } from "./driver/types.js";
export { QuintError, QuintNotFoundError } from "./cli/quint.js";
export { NoTracesError, StateMismatchError, TraceReplayError } from "./runner/runner.js";
export const stateCheck = (deserializeState, compareState) => ({ compareState, deserializeState });
// Implementation
export function defineDriver(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
schemaOrFactory,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
maybeFactory
// eslint-disable-next-line @typescript-eslint/no-explicit-any
) {
if (typeof schemaOrFactory === "function") {
// Raw mode
const factory = schemaOrFactory;
return () => {
const result = factory();
return {
actions: {},
step: result.step,
...(result.getState ? { getState: result.getState } : {}),
...(result.config ? { config: result.config } : {})
};
};
}
// Typed mode
const schema = schemaOrFactory;
const factory = maybeFactory;
return () => {
const result = factory();
const actions = {};
for (const name of Object.keys(schema)) {
actions[name] = { picks: schema[name], handler: result[name] };
}
return {
actions,
...(result.getState ? { getState: result.getState } : {}),
...(result.config ? { config: result.config } : {})
};
};
}
const wrapAction = (actionDef) => {
const fields = {};
for (const key of Object.keys(actionDef.picks)) {
fields[key] = Schema.Unknown;
}
return {
picks: Schema.Struct(fields),
handler: (rawPicks) => Effect.promise(async () => {
const decoded = {};
for (const [key, schema] of Object.entries(actionDef.picks)) {
const raw = rawPicks[key];
const transformed = raw === undefined ? undefined : transformITFValue(raw);
const result = schema["~standard"].validate(transformed);
const resolved = result instanceof Promise ? await result : result;
if (resolved.issues) {
throw new Error(`Pick "${key}" validation failed: ${resolved.issues.map((i) => i.message).join(", ")}`);
}
decoded[key] = resolved.value;
}
await Promise.resolve(actionDef.handler(decoded));
})
};
};
const wrapDriver = (simple) => {
const actions = {};
for (const [action, def] of Object.entries(simple.actions)) {
actions[action] = wrapAction(def);
}
const simpleGetState = simple.getState;
const simpleStep = simple.step;
return {
actions,
...(simpleGetState !== undefined ? { getState: () => Effect.sync(simpleGetState) } : {}),
...(simple.config !== undefined ? { config: simple.config } : {}),
...(simpleStep !== undefined
? {
step: (action, picks) => Effect.promise(async () => {
await Promise.resolve(simpleStep(action, picks));
})
}
: {})
};
};
export const run = (opts) => {
const { driver, stateCheck: sc, ...runOpts } = opts;
const program = quintRun({
...runOpts,
driverFactory: {
create: () => Effect.promise(async () => wrapDriver(await Promise.resolve(driver())))
},
stateCheck: sc !== undefined
? {
compareState: sc.compareState,
deserializeState: (raw) => Effect.sync(() => sc.deserializeState(transformITFValue(raw)))
}
: undefined
});
return Effect.runPromiseExit(program).then((exit) => {
if (Exit.isSuccess(exit))
return exit.value;
const failure = Cause.failureOption(exit.cause);
if (Option.isSome(failure))
throw failure.value;
const defect = Cause.dieOption(exit.cause);
if (Option.isSome(defect))
throw defect.value;
throw new Error("Unknown error in quint-connect run()");
});
};
export const pickFrom = (nondetPicks, key, schema) => {
const raw = nondetPicks.get(key);
if (raw === undefined)
return undefined;
// Unwrap Quint Option: { tag: "Some", value: x } | { tag: "None", ... }
if (typeof raw !== "object" || raw === null || !("tag" in raw)) {
throw new Error(`pickFrom "${key}": expected Quint Option (Some/None), got: ${JSON.stringify(raw)}`);
}
const variant = raw;
if (variant.tag === "None")
return undefined;
if (variant.tag !== "Some") {
throw new Error(`pickFrom "${key}": expected Option tag "Some" or "None", got: "${variant.tag}"`);
}
const transformed = transformITFValue(variant.value);
const result = schema["~standard"].validate(transformed);
if (result instanceof Promise) {
throw new Error("pickFrom does not support async schemas");
}
if (result.issues) {
throw new Error(`pickFrom "${key}" validation failed: ${result.issues.map((i) => i.message).join(", ")}`);
}
return result.value;
};
export { transformITFValue } from "@firfi/itf-trace-parser";
//# sourceMappingURL=simple.js.map