Let Users Search for This or That
Combining searches with OR, without a flag nobody trusts
Every filter in this series has narrowed the result: this brand and this model and nothing archived. That is what users mean most of the time, until someone asks for the vehicles of one brand or the ones with an intervention on record, and most APIs answer with a bolted-on filterMode=any flag that nobody quite trusts. OR has a proper home, and it is not a flag: it is the shape of the request.
Universal or Regira? AND within a search object, OR across them is a contract any API can adopt for the body of a search request. The POST /list and POST /search endpoints that accept such an array and run each object through the same pipeline come with Regira Entities.
Where OR lives: the POST endpoints
The endpoint table in part 1 has two POST routes, /list and /search, that take the search object from the request body instead of the query string. They exist only on a complex registration, the one with a typed sort enum and includes enum from the sorting part. The sample has both kinds: Vehicle is complex, Product is simple (an inline lambda, GETs only), so POST $URL/products/search answers 405 Method Not Allowed while the same call on /vehicles works below. If the entity you want OR on is registered the simple way, give it the complex registration and the controller base that mirrors it; the endpoints come with that controller.
OR is an array
Within one search object, filters AND, as they have all series long. To express OR, post an array of search objects: each object becomes a query of its own, and the results union, deduplicated.
curl -X POST "$URL/vehicles/search" -H "Content-Type: application/json" \
-d '[{"brandId":[2]},{"hasIntervention":true}]'
# count 3: TRK-001, TRK-003, VAN-001
The arithmetic: brand 2 (Scania) matches TRK-003; hasIntervention matches TRK-001, TRK-003 and VAN-001. The union is three vehicles, and TRK-003, which matched both halves, appears once.
The rule is easy to say out loud, AND within an object, OR across objects (disjunctive normal form, if you want the textbook name), and there is no new mechanism behind it. Each object in the array runs the same builders this series built, the queries union, and then sorting, includes and paging apply to the combined result. That is why OR composes with everything else instead of fighting it:
curl -X POST "$URL/vehicles/search?sortBy=Code&includes=Brand&pageSize=2" \
-H "Content-Type: application/json" \
-d '[{"brandId":[2]},{"hasIntervention":true}]'
# count 3, first page of 2, code-sorted, brands joined
Why not a filterMode=any flag? A flag switches the whole request between all-AND and all-OR. An array of objects says this and that, or this other thing, one object per alternative, with the same filters, the same builders and no new parameter to document.
The series in four lines
- Name your filters in one record, translate them in one small builder, and the pipeline composes them into one SQL statement — the basics.
- Sorting, includes and paging are typed the same way: an enum the client sends, translated once — sort, include, page.
- The rules every query obeys are registered once and apply everywhere — global filters, with archiving and free text built on them.
- OR is an array of search objects, run through the same pipeline — this part.
Runnable: the series' API sample includes the POST-an-array union endpoint this post describes — grab it and dotnet run.
Docs: Regira Entities — web endpoints, services, built-in features. The Fleet sample with a full production Vehicle pipeline is on GitHub.