UNPKG

@firfi/quint-connect

Version:

Model-based testing framework connecting Quint specifications to TypeScript implementations

139 lines 5.49 kB
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