ts-10-rule-engine
0.000
Challenge · difficulty 5/5
# Typed rule / query engine
Implement a file **`solution.ts`** that interprets a small, tree-shaped rule language
over plain records and uses it to filter collections of records.
## Types to export
```ts
export type Rule =
| { op: "and"; rules: Rule[] }
| { op: "or"; rules: Rule[] }
| { op: "not"; rule: Rule }
| { op: "eq" | "ne" | "lt" | "lte" | "gt" | "gte"; field: string; value: number | string }
| { op: "in"; field: string; values: Array<number | string> };
export class RuleError extends Error {}
export function evaluate(rule: Rule, record: Record<string, unknown>): boolean;
export function filter(
rule: Rule,
records: Array<Record<string, unknown>>,
): Array<Record<string, unknown>>;
```
## Semantics
`evaluate(rule, record)` returns a boolean by recursively interpreting `rule` against a
single `record`.
### Logical combinators
- `and`: true iff **every** sub-rule in `rules` is true. An empty `rules` array is
**true** (the identity of conjunction).
- `or`: true iff **any** sub-rule in `rules` is true. An empty `rules` array is
**false** (the identity of disjunction).
- `not`: the negation of its single child `rule`.
### Comparisons
Comparison ops look up `record[field]`. Let `fieldValue = record[field]`.
- `eq`: true iff `fieldValue === value`.
- `ne`: true iff `fieldValue !== value`.
- `lt` / `lte` / `gt` / `gte`: ordering comparisons. They are evaluated **only** when
`fieldValue` and `value` have the same comparable type:
- if **both** are numbers, compare numerically;
- if **both** are strings, compare lexicographically (with `<`, `<=`, `>`, `>=`);
- otherwise (the field is missing/`undefined`, or one side is a number and the other
a string, or the field holds some other type) the comparison is **`false`**. It does
**not** throw.
So `{ op: "gt", field: "age", value: 18 }` is `false` for a record with no `age`, and
also `false` if `age` is the string `"20"` (string vs number mismatch).
### Membership
- `in`: true iff `record[field]` is **strictly equal** (`===`) to at least one element
of `values`.
### Validation — when to throw `RuleError`
A rule node is invalid if it is not a well-formed member of the `Rule` union. Invalid
nodes must cause `evaluate` (and therefore `filter`) to throw a `RuleError` (not a plain
`Error`). In particular, throw `RuleError` when:
- `op` is missing or is not one of the known operators;
- `and` / `or` does not carry an array `rules`;
- `not` does not carry a child `rule` object;
- a comparison op (`eq`/`ne`/`lt`/`lte`/`gt`/`gte`) does not carry a string `field`, or
its `value` is neither a number nor a string;
- `in` does not carry a string `field` or an array `values`.
You may validate up front or lazily as you evaluate; either is fine, as long as invalid
rules raise `RuleError`.
## `filter`
`filter(rule, records)` returns a new array containing exactly the records for which
`evaluate(rule, record)` is `true`, **in their original order**. If `rule` is invalid it
propagates the `RuleError`.
## Worked example
```ts
const rule: Rule = {
op: "and",
rules: [
{ op: "gte", field: "age", value: 18 },
{
op: "or",
rules: [
{ op: "eq", field: "country", value: "US" },
{ op: "in", field: "country", values: ["CA", "MX"] },
],
},
{ op: "not", rule: { op: "eq", field: "banned", value: 1 } },
],
};
const people = [
{ name: "a", age: 25, country: "US", banned: 0 }, // kept
{ name: "b", age: 17, country: "US", banned: 0 }, // dropped: age < 18
{ name: "c", age: 40, country: "CA", banned: 1 }, // dropped: banned
{ name: "d", age: 30, country: "FR", banned: 0 }, // dropped: country
{ name: "e", age: 22, country: "MX", banned: 0 }, // kept
];
filter(rule, people); // -> [ {name:"a",...}, {name:"e",...} ]
```
Keep it fully typed: `solution.ts` must pass `tsc --noEmit` in strict mode. Do not use
`any` in the implementation; prefer `unknown` and narrow.
tests/solution.test.ts
import { test } from "node:test";
import { strict as assert } from "node:assert";
import { evaluate, filter, RuleError, type Rule } from "./solution.ts";
test("eq / ne on numbers and strings", () => {
assert.equal(evaluate({ op: "eq", field: "a", value: 1 }, { a: 1 }), true);
assert.equal(evaluate({ op: "eq", field: "a", value: 1 }, { a: 2 }), false);
assert.equal(evaluate({ op: "ne", field: "a", value: 1 }, { a: 2 }), true);
assert.equal(evaluate({ op: "eq", field: "s", value: "x" }, { s: "x" }), true);
assert.equal(evaluate({ op: "ne", field: "s", value: "x" }, { s: "y" }), true);
});
test("eq does not coerce types (number vs string)", () => {
assert.equal(evaluate({ op: "eq", field: "a", value: 1 }, { a: "1" }), false);
assert.equal(evaluate({ op: "ne", field: "a", value: 1 }, { a: "1" }), true);
});
test("numeric lt / lte / gt / gte", () => {
assert.equal(evaluate({ op: "lt", field: "n", value: 10 }, { n: 5 }), true);
assert.equal(evaluate({ op: "lt", field: "n", value: 10 }, { n: 10 }), false);
assert.equal(evaluate({ op: "lte", field: "n", value: 10 }, { n: 10 }), true);
assert.equal(evaluate({ op: "gt", field: "n", value: 10 }, { n: 11 }), true);
assert.equal(evaluate({ op: "gt", field: "n", value: 10 }, { n: 10 }), false);
assert.equal(evaluate({ op: "gte", field: "n", value: 10 }, { n: 10 }), true);
});
test("lexicographic string ordering", () => {
assert.equal(evaluate({ op: "lt", field: "s", value: "banana" }, { s: "apple" }), true);
assert.equal(evaluate({ op: "gt", field: "s", value: "apple" }, { s: "banana" }), true);
assert.equal(evaluate({ op: "gte", field: "s", value: "apple" }, { s: "apple" }), true);
assert.equal(evaluate({ op: "lte", field: "s", value: "apple" }, { s: "apple" }), true);
});
test("ordering with missing field is false", () => {
assert.equal(evaluate({ op: "gt", field: "age", value: 18 }, {}), false);
assert.equal(evaluate({ op: "lt", field: "age", value: 18 }, {}), false);
assert.equal(evaluate({ op: "lte", field: "age", value: 18 }, {}), false);
assert.equal(evaluate({ op: "gte", field: "age", value: 18 }, {}), false);
});
test("ordering with type mismatch is false (no throw)", () => {
// number value, string field
assert.equal(evaluate({ op: "gt", field: "age", value: 18 }, { age: "20" }), false);
// string value, number field
assert.equal(evaluate({ op: "lt", field: "age", value: "20" }, { age: 18 }), false);
// field holds a non-comparable type
assert.equal(evaluate({ op: "gt", field: "age", value: 1 }, { age: true }), false);
});
test("eq with missing field is false, ne is true", () => {
assert.equal(evaluate({ op: "eq", field: "x", value: 1 }, {}), false);
assert.equal(evaluate({ op: "ne", field: "x", value: 1 }, {}), true);
});
test("in membership", () => {
assert.equal(evaluate({ op: "in", field: "c", values: ["US", "CA"] }, { c: "CA" }), true);
assert.equal(evaluate({ op: "in", field: "c", values: ["US", "CA"] }, { c: "FR" }), false);
assert.equal(evaluate({ op: "in", field: "n", values: [1, 2, 3] }, { n: 2 }), true);
// strict equality: "1" is not 1
assert.equal(evaluate({ op: "in", field: "n", values: [1, 2, 3] }, { n: "1" }), false);
// empty values -> never matches
assert.equal(evaluate({ op: "in", field: "n", values: [] }, { n: 1 }), false);
});
test("not negates its child", () => {
assert.equal(evaluate({ op: "not", rule: { op: "eq", field: "a", value: 1 } }, { a: 1 }), false);
assert.equal(evaluate({ op: "not", rule: { op: "eq", field: "a", value: 1 } }, { a: 2 }), true);
});
test("empty and is true, empty or is false", () => {
assert.equal(evaluate({ op: "and", rules: [] }, {}), true);
assert.equal(evaluate({ op: "or", rules: [] }, {}), false);
});
test("and / or short-circuit semantics over multiple rules", () => {
const r: Rule = {
op: "and",
rules: [
{ op: "gte", field: "age", value: 18 },
{ op: "eq", field: "country", value: "US" },
],
};
assert.equal(evaluate(r, { age: 20, country: "US" }), true);
assert.equal(evaluate(r, { age: 17, country: "US" }), false);
assert.equal(evaluate(r, { age: 20, country: "FR" }), false);
const o: Rule = {
op: "or",
rules: [
{ op: "eq", field: "country", value: "US" },
{ op: "eq", field: "country", value: "CA" },
],
};
assert.equal(evaluate(o, { country: "CA" }), true);
assert.equal(evaluate(o, { country: "FR" }), false);
});
test("deeply nested and / or / not", () => {
const rule: Rule = {
op: "and",
rules: [
{ op: "gte", field: "age", value: 18 },
{
op: "or",
rules: [
{ op: "eq", field: "country", value: "US" },
{ op: "in", field: "country", values: ["CA", "MX"] },
],
},
{ op: "not", rule: { op: "eq", field: "banned", value: 1 } },
],
};
assert.equal(evaluate(rule, { age: 25, country: "US", banned: 0 }), true);
assert.equal(evaluate(rule, { age: 17, country: "US", banned: 0 }), false);
assert.equal(evaluate(rule, { age: 40, country: "CA", banned: 1 }), false);
assert.equal(evaluate(rule, { age: 30, country: "FR", banned: 0 }), false);
assert.equal(evaluate(rule, { age: 22, country: "MX", banned: 0 }), true);
});
test("filter keeps matching records in original order", () => {
const rule: Rule = {
op: "and",
rules: [
{ op: "gte", field: "age", value: 18 },
{
op: "or",
rules: [
{ op: "eq", field: "country", value: "US" },
{ op: "in", field: "country", values: ["CA", "MX"] },
],
},
{ op: "not", rule: { op: "eq", field: "banned", value: 1 } },
],
};
const people = [
{ name: "a", age: 25, country: "US", banned: 0 },
{ name: "b", age: 17, country: "US", banned: 0 },
{ name: "c", age: 40, country: "CA", banned: 1 },
{ name: "d", age: 30, country: "FR", banned: 0 },
{ name: "e", age: 22, country: "MX", banned: 0 },
];
assert.deepEqual(
filter(rule, people).map((r) => r["name"]),
["a", "e"],
);
});
test("filter with empty-and matches everything; empty-or matches nothing", () => {
const recs = [{ a: 1 }, { a: 2 }, { a: 3 }];
assert.deepEqual(filter({ op: "and", rules: [] }, recs), recs);
assert.deepEqual(filter({ op: "or", rules: [] }, recs), []);
});
test("RuleError thrown for an unknown op", () => {
assert.throws(() => evaluate({ op: "wat" } as unknown as Rule, {}), RuleError);
});
test("RuleError thrown for malformed and / not / comparison nodes", () => {
// and without an array rules
assert.throws(() => evaluate({ op: "and" } as unknown as Rule, {}), RuleError);
// not without a child rule
assert.throws(() => evaluate({ op: "not" } as unknown as Rule, {}), RuleError);
// comparison without a string field
assert.throws(() => evaluate({ op: "eq", value: 1 } as unknown as Rule, {}), RuleError);
// comparison with a non number/string value
assert.throws(
() => evaluate({ op: "gt", field: "a", value: true } as unknown as Rule, {}),
RuleError,
);
// in without an array values
assert.throws(() => evaluate({ op: "in", field: "a" } as unknown as Rule, {}), RuleError);
// missing op entirely
assert.throws(() => evaluate({} as unknown as Rule, {}), RuleError);
});
test("filter propagates RuleError from an invalid rule", () => {
assert.throws(() => filter({ op: "nope" } as unknown as Rule, [{ a: 1 }]), RuleError);
});
System prompt
You are an expert programmer. Solve the task exactly as specified. Output your solution as fenced code blocks using the required file name(s) and the exact function/type signatures requested. Prefer correctness; do not include prose outside code unless asked.Errors (stderr)
no code extracted from responseReasoning (chain-of-thought)
The user wants me to implement a typed rule/query engine in TypeScript.
I need to create a file `solution.ts` that exports:
- `Rule` type
- `RuleError` class
- `evaluate` function
- `filter` function
Let's break down the requirements:
1. `Rule` type is given.
2. `RuleError` extends `Error`.
3. `evaluate(rule, record)` returns boolean.
- Handles `and`, `or`, `not`, comparisons (`eq`, `ne`, `lt`, `lte`, `gt`, `gte`), `in`.
- `and`: true if all rules true, empty array -> true.
- `or`: true if any rule true, empty array -> false.
- `not`: negation of child rule.
- Comparisons: look up `record[field]`.
- `eq`: `fieldValue === value`
- `ne`: `fieldValue !== value`
- `lt`/`lte`/`gt`/`gte`: only if both are numbers or both are strings. Otherwise false. Missing field -> false.
- `in`: `fieldValue ===` at least one element in `values`.
- Validation: throw `RuleError` if rule is malformed.
- `op` missing or unknown.
- `and`/`or` missing `rules` array.
- `not` missing `rule` object.
- comparison missing string `field` or `value` not number/string.
- `in` missing string `field` or `values` array.
4. `filter(rule, records)` returns filtered array. Propagates `RuleError`.
Let's design the validation and evaluation carefully.
I'll write a helper to validate rules, or validate during evaluation. The prompt says "You may validate up front or lazily as you evaluate; either is fine". I'll do it lazily during evaluation for simplicity, or up front recursively. Actually, doing it during evaluation is fine.
Wait, the type `Rule` is a union. TypeScript's type narrowing will help, but since it's a union of objects with different properties, I need to check `op` first.
Let's define the known ops:
`const KNOWN_OPS = ["and", "or", "not", "eq", "ne", "lt", "lte", "gt", "gte", "in"] as const;`
Validation logic:
- Check if `rule` is an object and not null.
- Check if `op` is a string and in `KNOWN_OPS`.
- Based on `op`, check required properties.
- If invalid, throw `RuleError`.
Let's write out the `evaluate` function:
```typescript
export type Rule =
| { op: "and"; rules: Rule[] }
| { op: "or"; rules: Rule[] }
| { op: "not"; rule: Rule }
| { op: "eq" | "ne" | "lt" | "lte" | "gt" | "gte"; field: string; value: number | string }
| { op: "in"; field: string; values: Array<number | string> };
export class RuleError extends Error {}
function validateRule(rule: unknown): asserts rule is Rule {
if (typeof rule !== "object" || rule === null) {
throw new RuleError("Invalid rule: not an object");
}
const r = rule as Record<string, unknown>;
if (typeof r.op !== "string") {
throw new RuleError("Invalid rule: missing or invalid op");
}
switch (r.op) {
case "and":
case "or":
if (!Array.isArray(r.rules)) throw new RuleError("Invalid rule: missing rules array");
for (const sub of r.rules) validateRule(sub);
break;
case "not":
if (typeof r.rule !== "object" || r.rule === null) throw new RuleError("Invalid rule: missing rule object");
validateRule(r.rule);
break;
case "eq":
case "ne":
case "lt":
case "lte":
case "gt":
case "gte":
if (typeof r.field !== "string") throw new RuleError("Invalid rule: missing or invalid field");
if (typeof r.value !== "number" && typeof r.value !== "string") throw new RuleError("Invalid rule: invalid value");
break;
case "in":
if (typeof r.field !== "string") throw new RuleError("Invalid rule: missing or invalid field");
if (!Array.isArray(r.values)) throw new RuleError("Invalid rule: missing values array");
break;
default:
throw new RuleError("Invalid rule: unknown op");
}
}
```
Wait, the prompt says `Rule` type is exported. I should just use it.
Also, `validateRule` recursively validates. That's fine.
Now `evaluate`:
```typescript
export function evaluate(rule: Rule, record: Record<string, unknown>): boolean {
validateRule(rule); // ensures it's well-formed
switch (rule.op) {
case "and":
return rule.rules.every(r => evaluate(r, record));
case "or":
return rule.rules.some(r => evaluate(r, record));
case "not":
return !evaluate(rule.rule, record);
case "eq":
return record[rule.field] === rule.value;
case "ne":
return record[rule.field] !== rule.value;
case "lt":
case "lte":
case "gt":
case "gte": {
const fv = record[rule.field];
const v = rule.value;
if (typeof fv !== typeof v) return false;
if (typeof fv === "number" && typeof v === "number") {
switch (rule.op) {
case "lt": return fv < v;
case "lte": return fv <= v;
case "gt": return fv > v;
case "gte": return fv >= v;
}
}
if (typeof fv === "string" && typeof v === "string") {
switch (rule.op) {
case "lt": return fv < v;
case "lte": return fv <= v;
case "gt": return fv > v;
case "gte": return fv >= v;
}
}
return false;
}
case "in":
return rule.values.includes(record[rule.field] as number | string);
default:
// Should be unreachable due to validation
return false;
}
}
```
Wait, `rule.values.includes(...)` might have type issues because `record[rule.field]` is `unknown`. I'll cast or check. `rule.values` is `Array<number | string>`. `includes` takes `number | string`. So `record[rule.field] as number | string` is fine, or I can just do `rule.values.some(val => val === record[rule.field])`. Actually, `Array.prototype.includes` works with `unknown` in TS if I cast, or I can just use `some`. Let's stick to `some` or cast safely. Actually, `includes` signature in TS expects the same type. I'll use `rule.values.some(v => v === record[rule.field])`.
Wait, `record[rule.field]` could be `undefined`. `some` handles it fine.
Let's refine `evaluate` to avoid validation overhead if not needed, but the prompt says "You may validate up front or lazily as you evaluate". I'll keep validation inside `evaluate` or separate. Act
…[21691 chars elided]…
issing rule object");
break;
case "eq":
case "ne":
case "lt":
case "lte":
case "gt":
case "gte":
if (typeof r.field !== "string") throw new RuleError("Invalid rule: missing or invalid field");
if (typeof r.value !== "number" && typeof r.value !== "string") throw new RuleError("Invalid rule: invalid value");
break;
case "in":
if (typeof r.field !== "string") throw new RuleError("Invalid rule: missing or invalid field");
if (!Array.isArray(r.values)) throw new RuleError("Invalid rule: missing values array");
break;
default:
throw new RuleError("Invalid rule: unknown op");
}
}
```
This validates the current node. When `evaluate` recurses, it will call `assertValidRule` on the child, validating it then. This is lazy and efficient.
Wait, the prompt says: "A rule node is invalid if it is not a well-formed member of the Rule union. Invalid nodes must cause evaluate ... to throw a RuleError". This implies that if a child is invalid, `evaluate` should throw when it reaches it. My lazy validation does exactly that.
One detail: `assertValidRule` uses `as Record<string, unknown>`. That's fine.
I'll keep it.
Let's verify `tsc --noEmit` strict mode compatibility.
- `Rule` type is exported.
- `RuleError` extends `Error`.
- `evaluate` and `filter` signatures match.
- No `any` used.
- `unknown` used appropriately.
- `assertValidRule` is not exported, which is fine.
- `switch` covers all `op` cases. `default` returns `false` but should be unreachable due to validation. I'll add `// unreachable` or just leave it. TS might complain about `default` not being reachable if `rule.op` is narrowed. Actually, `rule.op` is a union of literals. The `switch` covers all of them. TS will complain that `default` is unreachable. I can remove `default` or use a type assertion, or just leave it. TS strict mode usually warns about unreachable code but doesn't error unless `strict` includes something else. I'll keep `default: return false;` or remove it. Actually, TSSelf-repair attempts
[
{
"total": 1,
"answer": "",
"passed": 0,
"reasoning": "The user wants me to implement a typed rule/query engine in TypeScript.\nI need to create a file `solution.ts` that exports:\n- `Rule` type\n- `RuleError` class\n- `evaluate` function\n- `filter` function\n\nLet's break down the requirements:\n\n1. `Rule` type is given.\n2. `RuleError` extends `Error`.\n3. `evaluate(rule, record)` returns boolean.\n - Handles `and`, `or`, `not`, comparisons (`eq`, `ne`, `lt`, `lte`, `gt`, `gte`), `in`.\n - `and`: true if all rules true, empty array -> true.\n - `or`: true if any rule true, empty array -> false.\n - `not`: negation of child rule.\n - Comparisons: look up `record[field]`.\n - `eq`: `fieldValue === value`\n - `ne`: `fieldValue !== value`\n - `lt`/`lte`/`gt`/`gte`: only if both are numbers or both are strings. Otherwise false. Missing field -> false.\n - `in`: `fieldValue ===` at least one element in `values`.\n - Validation: throw `RuleError` if rule is malformed.\n - `op` missing or unknown.\n - `and`/`or` missing `rules` array.\n - `not` missing `rule` object.\n - comparison missing string `field` or `value` not number/string.\n - `in` missing string `field` or `values` array.\n4. `filter(rule, records)` returns filtered array. Propagates `RuleError`.\n\nLet's design the validation and evaluation carefully.\nI'll write a helper to validate rules, or validate during evaluation. The prompt says \"You may validate up front or lazily as you evaluate; either is fine\". I'll do it lazily during evaluation for simplicity, or up front recursively. Actually, doing it during evaluation is fine.\n\nWait, the type `Rule` is a union. TypeScript's type narrowing will help, but since it's a union of objects with different properties, I need to check `op` first.\n\nLet's define the known ops:\n`const KNOWN_OPS = [\"and\", \"or\", \"not\", \"eq\", \"ne\", \"lt\", \"lte\", \"gt\", \"gte\", \"in\"] as const;`\n\nValidation logic:\n- Check if `rule` is an object and not null.\n- Check if `op` is a string and in `KNOWN_OPS`.\n- Based on `op`, check required properties.\n- If invalid, throw `RuleError`.\n\nLet's write out the `evaluate` function:\n\n```typescript\nexport type Rule =\n | { op: \"and\"; rules: Rule[] }\n | { op: \"or\"; rules: Rule[] }\n | { op: \"not\"; rule: Rule }\n | { op: \"eq\" | \"ne\" | \"lt\" | \"lte\" | \"gt\" | \"gte\"; field: string; value: number | string }\n | { op: \"in\"; field: string; values: Array<number | string> };\n\nexport class RuleError extends Error {}\n\nfunction validateRule(rule: unknown): asserts rule is Rule {\n if (typeof rule !== \"object\" || rule === null) {\n throw new RuleError(\"Invalid rule: not an object\");\n }\n const r = rule as Record<string, unknown>;\n if (typeof r.op !== \"string\") {\n throw new RuleError(\"Invalid rule: missing or invalid op\");\n }\n \n switch (r.op) {\n case \"and\":\n case \"or\":\n if (!Array.isArray(r.rules)) throw new RuleError(\"Invalid rule: missing rules array\");\n for (const sub of r.rules) validateRule(sub);\n break;\n case \"not\":\n if (typeof r.rule !== \"object\" || r.rule === null) throw new RuleError(\"Invalid rule: missing rule object\");\n validateRule(r.rule);\n break;\n case \"eq\":\n case \"ne\":\n case \"lt\":\n case \"lte\":\n case \"gt\":\n case \"gte\":\n if (typeof r.field !== \"string\") throw new RuleError(\"Invalid rule: missing or invalid field\");\n if (typeof r.value !== \"number\" && typeof r.value !== \"string\") throw new RuleError(\"Invalid rule: invalid value\");\n break;\n case \"in\":\n if (typeof r.field !== \"string\") throw new RuleError(\"Invalid rule: missing or invalid field\");\n if (!Array.isArray(r.values)) throw new RuleError(\"Invalid rule: missing values array\");\n break;\n default:\n throw new RuleError(\"Invalid rule: unknown op\");\n }\n}\n```\n\nWait, the prompt says `Rule` type is exported. I should just use it.\nAlso, `validateRule` recursively validates. That's fine.\n\nNow `evaluate`:\n```typescript\nexport function evaluate(rule: Rule, record: Record<string, unknown>): boolean {\n validateRule(rule); // ensures it's well-formed\n \n switch (rule.op) {\n case \"and\":\n return rule.rules.every(r => evaluate(r, record));\n case \"or\":\n return rule.rules.some(r => evaluate(r, record));\n case \"not\":\n return !evaluate(rule.rule, record);\n case \"eq\":\n return record[rule.field] === rule.value;\n case \"ne\":\n return record[rule.field] !== rule.value;\n case \"lt\":\n case \"lte\":\n case \"gt\":\n case \"gte\": {\n const fv = record[rule.field];\n const v = rule.value;\n if (typeof fv !== typeof v) return false;\n if (typeof fv === \"number\" && typeof v === \"number\") {\n switch (rule.op) {\n case \"lt\": return fv < v;\n case \"lte\": return fv <= v;\n case \"gt\": return fv > v;\n case \"gte\": return fv >= v;\n }\n }\n if (typeof fv === \"string\" && typeof v === \"string\") {\n switch (rule.op) {\n case \"lt\": return fv < v;\n case \"lte\": return fv <= v;\n case \"gt\": return fv > v;\n case \"gte\": return fv >= v;\n }\n }\n return false;\n }\n case \"in\":\n return rule.values.includes(record[rule.field] as number | string);\n default:\n // Should be unreachable due to validation\n return false;\n }\n}\n```\n\nWait, `rule.values.includes(...)` might have type issues because `record[rule.field]` is `unknown`. I'll cast or check. `rule.values` is `Array<number | string>`. `includes` takes `number | string`. So `record[rule.field] as number | string` is fine, or I can just do `rule.values.some(val => val === record[rule.field])`. Actually, `Array.prototype.includes` works with `unknown` in TS if I cast, or I can just use `some`. Let's stick to `some` or cast safely. Actually, `includes` signature in TS expects the same type. I'll use `rule.values.some(v => v === record[rule.field])`.\n\nWait, `record[rule.field]` could be `undefined`. `some` handles it fine.\n\nLet's refine `evaluate` to avoid validation overhead if not needed, but the prompt says \"You may validate up front or lazily as you evaluate\". I'll keep validation inside `evaluate` or separate. Act\n…[21691 chars elided]…\nissing rule object\");\n break;\n case \"eq\":\n case \"ne\":\n case \"lt\":\n case \"lte\":\n case \"gt\":\n case \"gte\":\n if (typeof r.field !== \"string\") throw new RuleError(\"Invalid rule: missing or invalid field\");\n if (typeof r.value !== \"number\" && typeof r.value !== \"string\") throw new RuleError(\"Invalid rule: invalid value\");\n break;\n case \"in\":\n if (typeof r.field !== \"string\") throw new RuleError(\"Invalid rule: missing or invalid field\");\n if (!Array.isArray(r.values)) throw new RuleError(\"Invalid rule: missing values array\");\n break;\n default:\n throw new RuleError(\"Invalid rule: unknown op\");\n }\n}\n```\nThis validates the current node. When `evaluate` recurses, it will call `assertValidRule` on the child, validating it then. This is lazy and efficient.\n\nWait, the prompt says: \"A rule node is invalid if it is not a well-formed member of the Rule union. Invalid nodes must cause evaluate ... to throw a RuleError\". This implies that if a child is invalid, `evaluate` should throw when it reaches it. My lazy validation does exactly that.\n\nOne detail: `assertValidRule` uses `as Record<string, unknown>`. That's fine.\nI'll keep it.\n\nLet's verify `tsc --noEmit` strict mode compatibility.\n- `Rule` type is exported.\n- `RuleError` extends `Error`.\n- `evaluate` and `filter` signatures match.\n- No `any` used.\n- `unknown` used appropriately.\n- `assertValidRule` is not exported, which is fine.\n- `switch` covers all `op` cases. `default` returns `false` but should be unreachable due to validation. I'll add `// unreachable` or just leave it. TS might complain about `default` not being reachable if `rule.op` is narrowed. Actually, `rule.op` is a union of literals. The `switch` covers all of them. TS will complain that `default` is unreachable. I can remove `default` or use a type assertion, or just leave it. TS strict mode usually warns about unreachable code but doesn't error unless `strict` includes something else. I'll keep `default: return false;` or remove it. Actually, TS",
"test_error": "no code extracted from response"
},
{
"total": 1,
"answer": "",
"passed": 0,
"reasoning": "The user wants me to implement a TypeScript file `solution.ts` that contains a typed rule/query engine.\nI need to export:\n- `Rule` type\n- `RuleError` class\n- `evaluate` function\n- `filter` function\n\nLet's carefully read the requirements.\n\nTypes:\n```ts\nexport type Rule =\n | { op: \"and\"; rules: Rule[] }\n | { op: \"or\"; rules: Rule[] }\n | { op: \"not\"; rule: Rule }\n | { op: \"eq\" | \"ne\" | \"lt\" | \"lte\" | \"gt\" | \"gte\"; field: string; value: number | string }\n | { op: \"in\"; field: string; values: Array<number | string> };\n```\n\n`RuleError` extends `Error`.\n\n`evaluate(rule: Rule, record: Record<string, unknown>): boolean`\n\n`filter(rule: Rule, records: Array<Record<string, unknown>>): Array<Record<string, unknown>>`\n\nSemantics:\n- `and`: true iff every sub-rule is true. Empty `rules` -> true.\n- `or`: true iff any sub-rule is true. Empty `rules` -> false.\n- `not`: negation of child.\n- Comparisons (`eq`, `ne`, `lt`, `lte`, `gt`, `gte`):\n - `fieldValue = record[field]`\n - `eq`: `fieldValue === value`\n - `ne`: `fieldValue !== value`\n - `lt`/`lte`/`gt`/`gte`: only evaluated if `fieldValue` and `value` have the same comparable type (both numbers or both strings). Otherwise false. Missing/undefined field -> false.\n- `in`: `fieldValue ===` at least one element in `values`.\n\nValidation:\nThrow `RuleError` if:\n- `op` missing or not known.\n- `and`/`or` missing `rules` array.\n- `not` missing `rule` object.\n- comparison op missing string `field`, or `value` not number/string.\n- `in` missing string `field` or array `values`.\n\nWait, the type `Rule` already enforces structure, but the prompt says \"A rule node is invalid if it is not a well-formed member of the `Rule` union. Invalid nodes must cause `evaluate` ... to throw a `RuleError`\". This implies we might receive objects that don't strictly match the type, or we should validate at runtime. Since TypeScript types are erased, we need runtime validation. The prompt says \"You may validate up front or lazily as you evaluate\".\n\nLet's implement runtime validation carefully.\n\nWe can write a helper `validateRule(node: unknown): asserts node is Rule` or just check inside `evaluate`.\nActually, it's easier to just check properties as we go.\n\nLet's define `evaluate`:\n```ts\nexport function evaluate(rule: Rule, record: Record<string, unknown>): boolean {\n if (!rule || typeof rule !== 'object') throw new RuleError(\"Invalid rule\");\n const op = rule.op;\n \n switch (op) {\n case \"and\": {\n if (!Array.isArray(rule.rules)) throw new RuleError(\"and requires rules array\");\n return rule.rules.every(r => evaluate(r, record));\n }\n case \"or\": {\n if (!Array.isArray(rule.rules)) throw new RuleError(\"or requires rules array\");\n return rule.rules.some(r => evaluate(r, record));\n }\n case \"not\": {\n if (typeof rule.rule !== 'object' || rule.rule === null) throw new RuleError(\"not requires rule object\");\n return !evaluate(rule.rule, record);\n }\n case \"eq\":\n case \"ne\":\n case \"lt\":\n case \"lte\":\n case \"gt\":\n case \"gte\": {\n if (typeof rule.field !== 'string') throw new RuleError(\"comparison requires string field\");\n if (typeof rule.value !== 'number' && typeof rule.value !== 'string') throw new RuleError(\"comparison requires number or string value\");\n \n const fieldValue = record[rule.field];\n \n if (op === \"eq\") return fieldValue === rule.value;\n if (op === \"ne\") return fieldValue !== rule.value;\n \n // For ordering comparisons\n if (typeof fieldValue !== typeof rule.value) return false;\n if (typeof fieldValue === 'number' && typeof rule.value === 'number') {\n switch (op) {\n case \"lt\": return fieldValue < rule.value;\n case \"lte\": return fieldValue <= rule.value;\n case \"gt\": return fieldValue > rule.value;\n case \"gte\": return fieldValue >= rule.value;\n }\n }\n if (typeof fieldValue === 'string' && typeof rule.value === 'string') {\n switch (op) {\n case \"lt\": return fieldValue < rule.value;\n case \"lte\": return fieldValue <= rule.value;\n case \"gt\": return fieldValue > rule.value;\n case \"gte\": return fieldValue >= rule.value;\n }\n }\n return false;\n }\n case \"in\": {\n if (typeof rule.field !== 'string') throw new RuleError(\"in requires string field\");\n if (!Array.isArray(rule.values)) throw new RuleError(\"in requires values array\");\n const fieldValue = record[rule.field];\n return rule.values.some(v => fieldValue === v);\n }\n default:\n throw new RuleError(\"Unknown op\");\n }\n}\n```\n\nWait, the prompt says: \"A rule node is invalid if it is not a well-formed member of the `Rule` union.\"\nThis means we should check `op` first. If `op` is missing or not one of the known operators, throw.\nMy switch covers known ops, and default throws. That's fine.\n\nBut what about `rule` being `null` or not an object? The type says `Rule`, but at runtime it could be anything. I'll add a check at the top.\n\nLet's refine validation to be strict but concise.\nActually, the prompt says:\n- `op` is missing or is not one of the known operators;\n- `and` / `or` does not carry an array `rules`;\n- `not` does not carry a child `rule` object;\n- a comparison op does not carry a string `field`, or its `value` is neither a number nor a string;\n- `in` does not carry a string `field` or an array `values`.\n\nI will implement exactly these checks.\n\nOne detail: `not` does not carry a child `rule` object. `typeof rule.rule !== 'object' || rule.rule === null` covers it. But maybe it should just be `if (rule.rule == null || typeof rule.rule !== 'object')`.\n\nAlso, for `and`/`or`, `rules` should be an array. `!Array.isArray(rule.rules)` covers it.\n\nFor `in`, `values` should be an array. `!Array.isArray(rule.values)` covers it.\n\nLet's check the comparison validation: \"does not carry a string field, or its value is neither a number nor a string\".\n`typeof rule.field !== 'string'` or `(typeof rule.value !== 'number' && typeof rule.value !== 'string')`.\n\nOne thing: `rule` parameter type is `Rule`, but in JS/TS runtime it could be anyt\n…[21034 chars elided]…\nle.field];\n \n if (op === \"eq\") return fieldValue === rule.value;\n if (op === \"ne\") return fieldValue !== rule.value;\n \n if (typeof fieldValue !== typeof rule.value) return false;\n \n if (typeof fieldValue === 'number') {\n const val = rule.value as number;\n const fv = fieldValue as number;\n switch (op) {\n case \"lt\": return fv < val;\n case \"lte\": return fv <= val;\n case \"gt\": return fv > val;\n case \"gte\": return fv >= val;\n }\n }\n if (typeof fieldValue === 'string') {\n const val = rule.value as string;\n const fv = fieldValue as string;\n switch (op) {\n case \"lt\": return fv < val;\n case \"lte\": return fv <= val;\n case \"gt\": return fv > val;\n case \"gte\": return fv >= val;\n }\n }\n return false;\n }\n case \"in\": {\n if (typeof rule.field !== 'string') throw new RuleError(\"in requires string field\");\n if (!Array.isArray(rule.values)) throw new RuleError(\"in requires values array\");\n const fieldValue = record[rule.field];\n return rule.values.some(v => fieldValue === v);\n }\n default:\n throw new RuleError(\"Unknown op\");\n }\n}\n\nexport function filter(\n rule: Rule,\n records: Array<Record<string, unknown>>,\n): Array<Record<string, unknown>> {\n return records.filter(r => evaluate(rule, r));\n}\n```\n\nWait, `rule.op` might be `undefined` if `rule` is `{}`. `typeof undefined !== 'string'` is true, so it throws. Good.\nBut `rule` is typed as `Rule`, so TS assumes it has `op`. At runtime it might not. The check handles it.\n\nOne minor point: `rule.rule` in `not` case. `typeof rule.rule !== 'object'` will be true for `null`, `undefined`, primitives. Arrays are objects, but they will fail recursively. That's acceptable.\n\nIs there any case where `rule.op` is a string but not in the union? The `default` case throws. Good.\n\nWhat about `rule.rules` in `and`/`or`? `Array.isArray` checks it. Good.\n\nWhat about `rule.field` in `in`? `",
"test_error": "no code extracted from response"
},
{
"total": 1,
"answer": "",
"passed": 0,
"reasoning": "The user wants me to implement a TypeScript file `solution.ts` that contains a typed rule/query engine.\nI need to export:\n- `Rule` type\n- `RuleError` class\n- `evaluate` function\n- `filter` function\n\nLet's carefully implement each part according to the specification.\n\nTypes:\n```typescript\nexport type Rule =\n | { op: \"and\"; rules: Rule[] }\n | { op: \"or\"; rules: Rule[] }\n | { op: \"not\"; rule: Rule }\n | { op: \"eq\" | \"ne\" | \"lt\" | \"lte\" | \"gt\" | \"gte\"; field: string; value: number | string }\n | { op: \"in\"; field: string; values: Array<number | string> };\n```\n\n`RuleError` class:\n```typescript\nexport class RuleError extends Error {}\n```\n\n`evaluate` function:\nNeeds to handle validation and evaluation.\nValidation rules:\n- `op` missing or not known -> throw RuleError\n- `and`/`or` missing `rules` array -> throw RuleError\n- `not` missing `rule` object -> throw RuleError\n- comparison ops missing `field` string or `value` not number/string -> throw RuleError\n- `in` missing `field` string or `values` array -> throw RuleError\n\nLet's write a validation helper or do it inline.\nActually, it's easier to validate recursively or check each node as we traverse.\n\nWait, the spec says: \"A rule node is invalid if it is not a well-formed member of the Rule union. Invalid nodes must cause evaluate ... to throw a RuleError\".\nSo we can validate on the fly.\n\nLet's define known ops:\n`const KNOWN_OPS = new Set([\"and\", \"or\", \"not\", \"eq\", \"ne\", \"lt\", \"lte\", \"gt\", \"gte\", \"in\"]);`\n\nImplementation of `evaluate`:\n```typescript\nexport function evaluate(rule: Rule, record: Record<string, unknown>): boolean {\n if (typeof rule !== \"object\" || rule === null || !(\"op\" in rule)) {\n throw new RuleError(\"Invalid rule: missing op\");\n }\n const op = rule.op;\n if (!KNOWN_OPS.has(op)) {\n throw new RuleError(`Invalid rule: unknown op \"${op}\"`);\n }\n\n switch (op) {\n case \"and\": {\n if (!Array.isArray((rule as any).rules)) throw new RuleError(\"Invalid rule: and missing rules array\");\n const r = rule as { op: \"and\"; rules: Rule[] };\n return r.rules.every(sub => evaluate(sub, record));\n }\n case \"or\": {\n if (!Array.isArray((rule as any).rules)) throw new RuleError(\"Invalid rule: or missing rules array\");\n const r = rule as { op: \"or\"; rules: Rule[] };\n return r.rules.some(sub => evaluate(sub, record));\n }\n case \"not\": {\n if (typeof (rule as any).rule !== \"object\" || (rule as any).rule === null) throw new RuleError(\"Invalid rule: not missing rule object\");\n const r = rule as { op: \"not\"; rule: Rule };\n return !evaluate(r.rule, record);\n }\n case \"eq\":\n case \"ne\":\n case \"lt\":\n case \"lte\":\n case \"gt\":\n case \"gte\": {\n const r = rule as { op: string; field: string; value: number | string };\n if (typeof r.field !== \"string\") throw new RuleError(\"Invalid rule: comparison missing field string\");\n if (typeof r.value !== \"number\" && typeof r.value !== \"string\") throw new RuleError(\"Invalid rule: comparison value must be number or string\");\n \n const fieldValue = record[r.field];\n \n if (op === \"eq\") return fieldValue === r.value;\n if (op === \"ne\") return fieldValue !== r.value;\n \n // Ordering comparisons\n if (typeof fieldValue !== typeof r.value) return false;\n if (typeof fieldValue !== \"number\" && typeof fieldValue !== \"string\") return false;\n \n // Now both are number or both are string\n const a = fieldValue as number | string;\n const b = r.value;\n \n switch (op) {\n case \"lt\": return a < b;\n case \"lte\": return a <= b;\n case \"gt\": return a > b;\n case \"gte\": return a >= b;\n default: return false; // unreachable\n }\n }\n case \"in\": {\n const r = rule as { op: \"in\"; field: string; values: Array<number | string> };\n if (typeof r.field !== \"string\") throw new RuleError(\"Invalid rule: in missing field string\");\n if (!Array.isArray(r.values)) throw new RuleError(\"Invalid rule: in missing values array\");\n \n const fieldValue = record[r.field];\n return r.values.some(v => fieldValue === v);\n }\n default:\n throw new RuleError(\"Invalid rule\");\n }\n}\n```\n\nWait, `rule` is typed as `Rule`, but TypeScript's type system might complain if I cast to `any` or access properties that aren't guaranteed by the union type without narrowing. Since `Rule` is a union, I can use type guards or just cast carefully. Actually, since the input is typed as `Rule`, TypeScript assumes it matches the union. But the spec says we need to validate invalid nodes. This implies the input might not strictly match `Rule` at runtime, or we should treat it as `unknown` initially and validate. The signature says `rule: Rule`, but in JS/TS, runtime types can differ. I'll stick to the signature but handle validation carefully. To avoid TS errors, I can cast to `any` for validation checks, or just use `rule as any` temporarily. Or better, I'll write a helper that takes `unknown` and validates, but the signature is fixed. I'll just use `rule as any` for validation checks, it's fine.\n\nActually, to be strictly typed and avoid `any`, I can do:\n```typescript\nexport function evaluate(rule: Rule, record: Record<string, unknown>): boolean {\n // Since rule is typed as Rule, TS assumes it's valid. But we must validate at runtime.\n // We'll cast to unknown for validation, then narrow.\n const r = rule as unknown;\n if (typeof r !== \"object\" || r === null) throw new RuleError(\"Invalid rule\");\n const obj = r as Record<string, unknown>;\n const op = obj.op;\n if (typeof op !== \"string\") throw new RuleError(\"Invalid rule: missing op\");\n \n // ... switch on op ...\n}\n```\nBut the signature says `rule: Rule`. I'll just keep it simple. TS strict mode will complain if I access properties not in the union without narrowing. I'll use type predicates or just `as any` for the validation part. It's a common pattern in these tasks.\n\nLet's refine `evaluate`:\n```typescript\nconst KNOWN_OPS = new Set([\"and\", \"or\", \"not\", \"eq\", \"ne\", \"lt\", \"lte\", \"gt\", \"gte\", \"in\"]);\n\nexport function evaluate(rule: Rule, record: Rec\n…[21488 chars elided]…\n = obj.rules;\n if (!Array.isArray(rules)) throw new RuleError(\"Invalid rule: or missing rules array\");\n return rules.some((sub: unknown) => evaluate(sub as Rule, record));\n }\n case \"not\": {\n const child = obj.rule;\n if (typeof child !== \"object\" || child === null) {\n throw new RuleError(\"Invalid rule: not missing rule object\");\n }\n return !evaluate(child as Rule, record);\n }\n case \"eq\":\n case \"ne\":\n case \"lt\":\n case \"lte\":\n case \"gt\":\n case \"gte\": {\n const field = obj.field;\n const value = obj.value;\n if (typeof field !== \"string\") throw new RuleError(\"Invalid rule: comparison missing field string\");\n if (typeof value !== \"number\" && typeof value !== \"string\") throw new RuleError(\"Invalid rule: comparison value must be number or string\");\n \n const fieldValue = record[field];\n \n if (op === \"eq\") return fieldValue === value;\n if (op === \"ne\") return fieldValue !== value;\n \n if (typeof fieldValue !== typeof value) return false;\n if (typeof fieldValue !== \"number\" && typeof fieldValue !== \"string\") return false;\n \n const a = fieldValue as number | string;\n const b = value as number | string;\n \n switch (op) {\n case \"lt\": return a < b;\n case \"lte\": return a <= b;\n case \"gt\": return a > b;\n case \"gte\": return a >= b;\n default: return false;\n }\n }\n case \"in\": {\n const field = obj.field;\n const values = obj.values;\n if (typeof field !== \"string\") throw new RuleError(\"Invalid rule: in missing field string\");\n if (!Array.isArray(values)) throw new RuleError(\"Invalid rule: in missing values array\");\n \n const fieldValue = record[field];\n return values.some((v: unknown) => fieldValue === v);\n }\n default:\n throw new RuleError(\"Invalid rule\");\n }\n}\n\nexport function filter(\n rule: Rule,\n records: Array<Record<string, unknown>>,\n): Array<Record<string, unknown>> {\n return records.filter(rec",
"test_error": ""
}
]