Skip to content

Fix: validate $filter before streaming response (return 400 not malformed 200)#873

Open
davetha wants to merge 1 commit into
flat3:5.xfrom
davetha:fix/pre-streaming-filter-validation
Open

Fix: validate $filter before streaming response (return 400 not malformed 200)#873
davetha wants to merge 1 commit into
flat3:5.xfrom
davetha:fix/pre-streaming-filter-validation

Conversation

@davetha
Copy link
Copy Markdown

@davetha davetha commented Mar 25, 2026

Summary

When $filter references an unknown property (e.g. $filter=BadField eq 'value'), Lodata starts streaming the HTTP 200 response before evaluating the filter. The filter parse error then occurs mid-stream, injecting an error JSON fragment into the already-started response body. The client receives malformed output that is neither a valid OData error response (HTTP 400) nor valid JSON.

Per the OData specification, an invalid $filter should return HTTP 400 Bad Request before any response body is sent.

Changes

Added assertValidFilter() in EntitySet::get(), called immediately after the existing assertValidOrderBy() and before the StreamedResponse callback is registered. This eagerly parses the filter expression using the entity set's own filter parser, causing any ParserException (which extends BadRequestException) to be thrown before streaming begins.

How it works

The call chain for a GET request is:

  1. get() — validates orderby, now validates filter, then registers the streaming callback
  2. Streaming callback → emitJson()query()generateWhere()generateTree()

With this fix, an invalid $filter throws at step 1, before StreamedResponse::sendContent() begins writing. The exception propagates through Laravel's normal error handling and produces a clean HTTP 400.

Impact

  • Invalid $filter now returns proper HTTP 400 instead of malformed HTTP 200
  • Mirrors the existing assertValidOrderBy() pattern exactly
  • Valid requests parse the filter tree twice (once for validation, once in generateWhere()) — negligible cost for correctness
  • No new exception types; ParserException already maps to HTTP 400

Testing

  • PHP syntax check passes
  • Pattern follows existing assertValidOrderBy() precedent

When $filter references an unknown property or is syntactically invalid,
Lodata previously started writing the HTTP 200 response body
({"@odata.context":"...","value":[) before the filter was parsed, then
injected an OData-error trailer mid-stream.  Clients received a malformed
response that was neither a valid OData error (HTTP 400) nor valid JSON.

Per the OData spec, an invalid $filter must return HTTP 400 Bad Request
before any response body is sent.

Add assertValidFilter() to EntitySet, mirroring the existing
assertValidOrderBy() pattern.  It eagerly calls generateTree() on the
filter expression — using the entity set's own filter parser so custom
parsers are respected — and throws ParserException (a BadRequestException)
if the expression is invalid.  The call site in get() is before
setResourceCallback(), so the exception propagates before the StreamedResponse
callback is registered or invoked.

Valid requests are unaffected: the filter tree is parsed once here for
validation, then again inside generateWhere() when the query executes.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant