Conversation
System-level search (`GET [base]?params`, `POST [base]/_search`) was never routed, yet the CapabilityStatement listed `search-system` unconditionally. Nothing in `create_fhir_router` claimed `GET /` or `/_search`, so: - `GET /?_type=Patient` fell through as a bare `405` with no body; - `[base]/_search` was caught by the resource-type gate and answered "'_search' is not a resource type" (404); - under `url_path`/`both` tenant routing an unprefixed `POST /_search` was worse: `_search` parses as a tenant id, so the prefix stripper rewrote it to `POST /` - the batch endpoint - for a tenant named `_search`, and a transaction Bundle sent there was committed into that tenant; - with the web UI mounted, `GET /?_type=Patient` was redirected to `/ui`, because the root redirect ignored the query. Per the decision on #1338, system-level search is refused explicitly for now: - `GET /` and `GET|POST /_search` are routed to `search_system_not_supported_handler`, which returns `RestError::NotImplemented` (501, `not-supported`) through the normal error rendering, with a message pointing at type-level search. The tenant is resolved first, as on every other route; auth still answers 401 first. - `_search` is a reserved first path segment, so it is never taken for a URL-path tenant. - The UI's root redirect applies to the bare root only; a `GET /` carrying a query is handed to the FHIR router. - `/metadata` no longer lists `search-system`. `transaction`/`batch`/ `history-system` are unchanged. - `POST /` stays the batch/transaction endpoint whatever its Content-Type. The unrouted `search_system_handler` is kept as the starting point for the deferred implementation and documented as not routed; it needs `MultiTypeSearchProvider`, which only SQLite and PostgreSQL implement. Fixes #1338
RestError::NotImplemented renders "Feature '<feature>' is not implemented.", so the feature text names the interaction and the alternative rather than being a sentence of its own. Refs #1338
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Implements the decision recorded on #1338: system-level search is refused explicitly for now.
GET [base]?…(with or without_type) andPOST [base]/_searchanswer501 Not Implementedwith anOperationOutcome(error/not-supported) that points the client at type-level search, and the CapabilityStatement no longer listssearch-system. Implementing system-level search stays deferred.Fixes #1338
Observed pre-fix behavior (running
hfs, SQLite,origin/main8d12b3a)header_onlyurl_path/bothGET /,GET /?_type=Patient,GET /?_type=Patient,Observation&_id=1,GET /?_lastUpdated=gt2020405, no body307 -> /ui(query ignored)405(also under/acme/)POST /_search(form),GET /_search404OperationOutcome "'_search' is not a resource type…" (the #989 gate - not the bare 404 the issue describes)/acme/_search: same 404. UnprefixedPOST /_search: treated asPOST /(batch) for a tenant named_searchPOST /form body400invalid"Invalid JSON"POST /Bundle (batch, transaction)200200200/metadatarest.interactiontransaction, batch, history-system, search-systemThe
url_path/bothrow is a real defect, not just an unhelpful status:_searchparses as a tenant id, so the prefix stripper rewrote/_searchto/. A transaction Bundle POSTed to/_searchreturned200and the Patient was then readable atGET /_search/Patient?family=Leak(total: 1).Root cause
create_fhir_routerregistered onlyposton/and nothing on/_search, whilecapabilities.rspushedsearch-systemas an unconditional literal.handlers::search::search_system_handlerexists but was never mounted (and cannot be as-is: it needsMultiTypeSearchProvider, which only SQLite and PostgreSQL implement, while the router is generic over every backend).Fix
handlers/search.rs: newsearch_system_not_supported_handler->RestError::NotImplemented(501 /not-supported), i.e. the server's normal error rendering. It takesTenantExtractor, so a bad tenant is refused as on every other route. The unroutedsearch_system_handleris kept as the starting point for the deferred work and documented as not routed, with what it lacks.routing/fhir_routes.rs:GET /andGET|POST /_searchroute to it.POST /is unchanged (batch/transaction whatever the Content-Type - the spec's POST form of this search is/_search).handlers/capabilities.rs:search-systemremoved.transaction(capability-driven),batchandhistory-systemuntouched;history-systemis truthful (GET /_historyis routed and the router requiresSystemHistoryProvider).middleware/tenant_prefix.rs(outside my stated ownership, one entry):_searchadded toRESERVED_SYSTEM_PATHSso it is never taken for a URL-path tenant. Without it the new/_searchroute is unreachable unprefixed underurl_path/both, and the Bundle-into-tenant-_searchbehavior above remains.crates/ui/src/lib.rs+crates/ui/Cargo.toml(outside my stated ownership, minimal): the root redirect (hfs: redirect GET / to /ui so the bare host lands on the web UI (mirroring HTS) #896) now applies to the bare root only; aGET /that carries a query is handed to the FHIR router (tower::ServiceExt::oneshot;towerwas already a dev-dependency ofhelios-ui,Cargo.lockis unchanged). Without this,GET /?_type=Patientkeeps redirecting a FHIR client to an HTML page whenever the UI is mounted (the default).MultiTypeSearchProviderdoc note (REST does not route it, System-level search is not routed: GET /?_type=… returns 405, POST /_search returns 404 #1338),crates/restREADME / crate docs,run-hfs-serverskill endpoint table,search-spec-assessment.md_typerow.Behavior changes clients can observe
GET [base]/GET [base]?…: bare405->501+ OperationOutcome (headless or with a query). With the UI mounted, bareGET /andGET /?still307 -> /ui;GET /?<anything>is now501instead of a redirect.GET|POST [base]/_search:404"not a resource type" ->501+ OperationOutcome.url_path/both:_searchcan no longer be addressed as a URL-path tenant;POST /_searchwith a Bundle is refused (501) instead of being committed into tenant_search./metadata:rest.interactionno longer containssearch-system.401first (auth layer is outside the router) - covered by a test.POST /batch/transaction, form-encodedPOST /(400 invalid), type-level search GET/POST,/health,/_history,/ui.Verification
Ran and passed:
cargo test -p helios-rest- 1725 passed, 0 failed (includes the newtests/system_search_not_supported.rs, 9 tests: every rejected form xheader_only/url_path/bothx default/header/URL-prefix tenant; Bundle to/_searchnot committed; batch + transaction onPOST /in every mode with a positive-control search;/metadata; type-level search; 401-before-501).cargo test -p helios-ui- 726 passed, 0 failed (newroot_get_with_a_query_reaches_the_fhir_app; existingroot_redirects_to_uiandroot_post_still_reaches_the_fhir_batch_handlerunchanged).hfs(UI mounted /url_path): results as in the table above, plus CORS headers present on the501.cargo +1.98.1 clippy -p helios-rest -p helios-ui -p helios-persistence --all-targets -- -D warnings <CI allow-list>- clean.cargo +1.98.1 test --workspace --all-features --no-run --exclude pysof- compiles (pre-merge state; the later merge oforigin/mainbrought only persistence seeder/CI changes).cargo fmt -p helios-rest -p helios-ui -p helios-persistence -- --check- clean.origin/main:cargo test -p helios-rest(1725 passed),cargo test -p helios-ui --test router_http(177 passed),cargo check -p helios-hfs -p helios-ui -p helios-hts-ui- all pass.Attempted, did not complete: running the new test file under
--all-features(the disk filled during that rebuild; the all-features compile above had already succeeded). No code in this PR iscfg-gated.Not run: Playwright/UI e2e (grepped
crates/ui/e2efor expectations aboutGET //search-system: none), non-SQLite backends (no backend code touched).Found, not fixed
RestErroris rendered asapplication/json, ignoringAccept: application/fhir+xml/_format(server-wide; the new501behaves like every other error).GET ?_type=Patient(system search inside a Bundle) answers400 value"Entry request.url is empty" - refused, but with a misleading message (handlers/batch.rs, not touched)._useris not a reserved first segment either, so underurl_pathan unprefixed/_user/settingsis read as tenant_user(-> 404). Same class as_search; left alone.crates/persistence/docs/search-spec-assessment.mdhas other stale rows (_list,_contained); only the_typerow was corrected.Stacking
Branched from
origin/main; no stacking. Does not touchhandlers/compartment.rs(#1423).