Skip to content

Chained-condition error paths: second &= leg unparseable, |= chain → 500 with wrong message, parseQuery TypeError without query arg #2435

Open

Description

@kriszyp

Summary

Three related error-path defects around REST chained conditions (&= / |=). None produces silent wrong data (unlike the chained-value coercion bug filed separately) — these fail loudly but wrongly.

1. A second chained leg can never be parsed

skiLengths=ge=170&=le=180&=ge=171 fails to parse with the unrelated error attribute must be specified before equality comparator.

Cause: after each token the parser picks its tokenizer by parser = attribute ? VALUE_PARSER : QUERY_PARSER (resources/search.ts:1571-1574). A chained leg sets attribute = '' (falsy), so parsing continues with QUERY_PARSER, whose operator class cannot produce &= as one token (VALUE_PARSER's [&|=]* run can — that is the only reason the first &= works, since at that point attribute is the real attribute name and truthy). The split & + = then trips the equality-comparator error.

Consequence: the planner's Multiple chained conditions are not currently supported error (resources/Table.ts:3530) is unreachable via REST, and the user gets a misleading message for a syntactically plausible query.

2. |= chains throw a plain 500 with the wrong message

skiLengths=ge=175|=le=180 parses fine (one chained condition, operator: 'or'), then prepareConditions throws Error('Multiple chained conditions are not currently supported') (resources/Table.ts:3530) — a plain Error (→ 500), and the message is wrong twice over: there is only one chained condition, and the unsupported part is the or operator. Should be a 4xx ClientError saying or-chaining is unsupported.

3. parseQuery(str) without a query object crashes on any parse error

parseQuery reads query.parseErrorMessage where query may be undefined — resources/search.ts:1343:

(currentQuery as any).parseError = new SyntaxViolation((query as any).parseErrorMessage);

should read currentQuery.parseErrorMessage. With no query argument, any recorded parse error becomes TypeError: Cannot read properties of undefined (reading 'parseErrorMessage') instead of the intended SyntaxViolation. Production REST always passes a query object (RequestTarget.ts:105), so this only hits direct callers of the public Resource.parseQuery(search) static and internal tooling.

Reproductions

All three reproduce with parseQuery + Table.search on main (a21910c); pinning tests are in the linked verification PR.

Severity

Error-path/UX only: loud failures, wrong shape (misleading message, 500 instead of 400, TypeError instead of SyntaxViolation). No wrong result sets.

Activity

  1. added theissue type on Sep 1, 2026
  2. kriszyp commented on Sep 1, 2026

    @kriszyp
    MemberAuthor

    Independent re-verification of this area turned up a fourth variant, and this one does produce silent wrong data (unlike the three above): a chain whose first leg is a non-range comparator but whose second leg is a valid lt/le is not rejected — it is silently collapsed into an exclusive lower bound, discarding the stated comparator.

    The collapse (resources/Table.ts:3517-3545 on main @ a21910c) validates only the upper leg (must be lt/le/gt/ge check) and classifies anything that isn't gt/ge as the lower leg without validating it; isGe then defaults every unrecognized first-leg comparator (eq, sw, ct, …) to exclusive gt.

    Repro (unit level, same route as the reproductions above):

    sizes: { elements: { type: 'Int' } }, records: 1→[4], 2→[5], 3→[6]
    
    sizes=eq=4&=le=5   → explain: gtle [4,"5"]   → returns [2,3]
                          the one record whose element equals 4 is EXCLUDED;
                          6 is included (the #2433 string-bound no-op compounds it)
    

    The pure-string case shows it with no coercion involvement at all:

    name: String, records: 'exact-four', 'five', 'six'
    
    name=eq=five&=le=z → gtle ("five","z"] → returns only 'six'
                          the record that MATCHES the stated equality is excluded
                          (it sits exactly on the now-exclusive lower bound)
    

    Expected: reject the chain (only gt/ge + lt/le pairs are meaningful) with a 4xx, like the existing rejection of a non-range second leg (tags=sw=pre&=ew=ale throws). Actual: silent reinterpretation, wrong result set on both indexed and unindexed paths.

    Found while independently verifying the element-scoping questions this issue's PR pins — happy for it to be folded into this issue or split out.

    — Claude Fable (dispatch harper-array-elem-scoping-verify)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    Fields

    Priority

    P3

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions