feat/x-fhir-query - #99
Conversation
…HIR server communication - Add `FHIRHttpClientInterface` to standardize FHIR REST server interaction. - Implement `FHIRHttpClient` for graceful, version-aware, and typed access to FHIR servers. - Add `NullFHIRHttpClient` for offline-first workflows, ensuring features degrade gracefully in the absence of a live server. - Integrate HTTP client functionality into `$populate` service with `XFhirQuery` support. - Update package-integrity workflow to include `fhir-http-client` repo.
A searchset Bundle from a live x-fhir-query fetch can carry _include'd resources and OperationOutcome entries alongside actual matches. XFhirQueryPopulationDataProvider took every entry.resource, so those would be bound as spurious %<name> context results in $populate.
Covers that evaluated launch-context values are always percent-encoded before substitution and that FHIRHttpClient always joins onto the configured base URL, so a resolved search string can never redirect a request to another host. Also adds an explicit timeout-to-null test and the end-to-end empty-substitution (all-holes-empty) case.
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
Benchmark ResultsSerialization + FHIRPath benchmarks✅ Regression check passedComparison vs baselineRun |
❌ FHIR Model Generation Test OUT OF DATEThis automated test regenerates the R4B FHIR models and verifies that the generation functionality works correctly with the changes in this PR, then checks that the committed models are up to date. 📋 Click to view detailed outputM src/Component/Models/src/R4B/Profile/ExampleLipidProfile.php |
…berOf() ResolveFunction/MemberOfFunction previously hand-rolled PSR-18/17 HTTP calls with a duplicated fetch() helper. Relative Type/id and canonical searches now go through FHIRHttpClientInterface, matching the pattern HttpFHIRTerminologyClient already established; a raw PSR-18 client is kept only for resolve()'s absolute-URL case, which a base-URL-bound FHIRHttpClientInterface implementation cannot express. Removes FHIRPathEvaluator's setFhirServerUrl/setTerminologyUrl string setters (unused outside tests) in favour of setFhirHttpClient/ setTerminologyHttpClient, and wires an optional FHIRHttpClientInterface into FHIRPathService's constructor so the new DI alias in services.yaml is actually reachable.
FHIRHttpClient/XFhirQueryPopulationDataProvider previously fetched a single search page only. FHIRHttpClientInterface gains followLink(), which resolves a server-supplied Bundle.link.url to a same-origin relative path before dispatching through the existing baseUrl join -- rejecting the URL outright when its origin or path prefix doesn't match, since a next link (unlike a resolved search string) is absolute and server-supplied, so it isn't covered by the existing SSRF guarantee. XFhirQueryPopulationDataProvider follows next links via a plain FHIRPath expression (link.where(relation = 'next').url), which reads uniformly across FHIR versions despite Bundle.link.relation's type differing between R4/R4B and R5. Pagination is bounded to 50 pages; a page that fails to fetch for any reason (no further link, an off-host rejection, or a transport error) simply stops pagination, returning whatever pages were fetched successfully -- consistent with the graceful-degradation posture already used throughout this transport stack.
Benchmark ResultsSerialization + FHIRPath benchmarks✅ Regression check passedComparison vs baselineRun |
There was a problem hiding this comment.
Pull request overview
This PR integrates a new HttpClient component into the toolkit to provide a shared, graceful-degrading FHIR transport (FHIRHttpClientInterface + implementation), and extends SDC $populate to optionally support live application/x-fhir-query context population via a provider seam—while keeping the default behavior offline-first. It also tightens configuration typing and updates CI/test/static-analysis wiring to include the new component.
Changes:
- Added the
HttpClientcomponent (transport client, null client, x-fhir-query template resolver) and wired it into Composer autoloading, PHPUnit, PHPStan, and package-integrity checks. - Extended SDC population with an opt-in
QueryPopulationDataProviderInterface+XFhirQueryPopulationDataProviderto execute resolved x-fhir-query searches and page through Bundlenextlinks. - Refactored FHIRPath
resolve()/memberOf()to useFHIRHttpClientInterfacefor server-relative requests, and moved shared contracts intoMetadata\Contract.
Reviewed changes
Copilot reviewed 51 out of 51 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| src/Component/Validation/tests/Unit/Validator/FHIRValueSetBindingValidatorStrictMaxValueSetTest.php | Updates terminology client interface import to Metadata\Contract. |
| src/Component/Validation/tests/Unit/Validator/FHIRValueSetBindingValidatorExtensibleTest.php | Updates terminology client interface import to Metadata\Contract. |
| src/Component/Validation/tests/Unit/PreferredServerAwareTerminologyClientTest.php | Updates imports for terminology client + result value object contracts. |
| src/Component/Validation/tests/Unit/Fixture/InMemoryFHIRTerminologyClientFactory.php | Updates imports for terminology factory/client contracts. |
| src/Component/Validation/src/Validator/FHIRValueSetBindingValidator.php | Switches validator to Metadata\Contract\FHIRTerminologyClientInterface. |
| src/Component/Validation/src/PreferredServerAwareTerminologyClient.php | Switches to Metadata\Contract result + client interface imports. |
| src/Component/Validation/src/NullFHIRTerminologyClient.php | Switches to Metadata\Contract result + client interface imports. |
| src/Component/Validation/src/InMemoryFHIRTerminologyClient.php | Switches to Metadata\Contract result + client interface imports. |
| src/Component/Validation/src/FHIRQuestionnaireValidator.php | Adopts Metadata\Contract terminology factory/client contracts. |
| src/Component/Validation/composer.json | Removes now-relocated HTTP/cache deps from Validation component. |
| src/Component/Sdc/tests/Unit/XFhirQueryPopulationDataProviderTest.php | Adds unit tests for Bundle entry extraction + pagination behavior. |
| src/Component/Sdc/tests/Unit/FHIRQuestionnaireXFhirQueryPopulateTest.php | Adds end-to-end tests for opt-in x-fhir-query context population behavior. |
| src/Component/Sdc/src/XFhirQueryPopulationDataProvider.php | Implements live-fetch provider using FHIRHttpClientInterface + FHIRPath for Bundle traversal/pagination. |
| src/Component/Sdc/src/PopulateContext.php | Adds optional queryProvider seam and updates offline-first contract docs. |
| src/Component/Sdc/src/FHIRQuestionnairePopulateService.php | Adds x-fhir-query context handling for variable and itemPopulationContext when provider is present. |
| src/Component/Sdc/src/Contract/QueryPopulationDataProviderInterface.php | Introduces provider contract for resolved search execution. |
| src/Component/Sdc/composer.json | Adds dependency on ardenexal/fhir-http-client. |
| src/Component/Metadata/src/Contract/FHIRTerminologyClientInterface.php | Moves terminology client contract namespace to Metadata\Contract. |
| src/Component/Metadata/src/Contract/FHIRTerminologyClientFactoryInterface.php | Moves terminology factory contract namespace to Metadata\Contract. |
| src/Component/Metadata/src/Contract/FHIRHttpClientInterface.php | Introduces cross-component contract for FHIR server transport. |
| src/Component/Metadata/src/Contract/CodingValidationResult.php | Moves value object namespace to Metadata\Contract. |
| src/Component/HttpClient/tests/Unit/XFhirQuerySpikeTest.php | Adds gating tests proving required FHIRPath capabilities for x-fhir-query templates. |
| src/Component/HttpClient/tests/Unit/XFhirQuery/XFhirQueryResolverTest.php | Adds comprehensive resolver tests (substitution, encoding, empty policy, SSRF guardrail). |
| src/Component/HttpClient/tests/Unit/NullFHIRHttpClientTest.php | Adds tests for null-object HTTP client behavior. |
| src/Component/HttpClient/tests/Unit/HttpFHIRTerminologyClientTest.php | Moves terminology client tests to HttpClient component and adapts to shared transport client. |
| src/Component/HttpClient/tests/Unit/FHIRHttpClientTest.php | Adds tests for base URL joining, graceful-null errors, SSRF guardrails, and followLink origin enforcement. |
| src/Component/HttpClient/tests/Unit/CodingValidationResultTest.php | Moves test to use Metadata\Contract\CodingValidationResult. |
| src/Component/HttpClient/tests/Unit/CachingFHIRTerminologyClientTest.php | Updates imports/namespaces for caching terminology client in HttpClient component. |
| src/Component/HttpClient/src/XFhirQuery/XFhirQueryResolver.php | Adds pure offline x-fhir-query template resolver. |
| src/Component/HttpClient/src/NullFHIRHttpClient.php | Adds null-object implementation of FHIRHttpClientInterface. |
| src/Component/HttpClient/src/HttpFHIRTerminologyClientFactory.php | Moves terminology factory into HttpClient component and adapts to shared transport. |
| src/Component/HttpClient/src/HttpFHIRTerminologyClient.php | Refactors terminology client to depend on FHIRHttpClientInterface transport. |
| src/Component/HttpClient/src/FHIRHttpClient.php | Implements shared transport client with graceful-null posture and same-origin followLink enforcement. |
| src/Component/HttpClient/src/CachingFHIRTerminologyClient.php | Moves caching decorator into HttpClient component and updates contracts. |
| src/Component/HttpClient/README.md | Documents the new HttpClient component, pagination model, and security posture. |
| src/Component/HttpClient/composer.json | Adds standalone package manifest for ardenexal/fhir-http-client. |
| src/Component/FHIRPath/tests/Unit/Function/ResolveFunctionTest.php | Updates resolve() tests to use FHIRHttpClientInterface for relative/canonical resolution. |
| src/Component/FHIRPath/tests/Unit/Function/MemberOfFunctionTest.php | Updates memberOf() tests to use FHIRHttpClientInterface terminology transport. |
| src/Component/FHIRPath/src/Service/FHIRPathService.php | Allows injecting FHIRHttpClientInterface into the evaluator for resolve()/memberOf(). |
| src/Component/FHIRPath/src/Function/ResolveFunction.php | Refactors canonical/relative resolution to use FHIRHttpClientInterface; keeps PSR-18 for absolute URLs. |
| src/Component/FHIRPath/src/Function/MemberOfFunction.php | Refactors terminology calls to use FHIRHttpClientInterface and updates configuration expectations. |
| src/Component/FHIRPath/src/Evaluator/FHIRPathEvaluator.php | Replaces URL configuration with injectable FHIR HTTP client(s) for resolve()/memberOf(). |
| src/Bundle/FHIRBundle/tests/Unit/FHIRBundleConfigurationValidationTest.php | Updates wiring expectations for relocated terminology client and contract namespace. |
| src/Bundle/FHIRBundle/src/Resources/config/services.yaml | Registers NullFHIRHttpClient and aliases FHIRHttpClientInterface + updated terminology client alias. |
| src/Bundle/FHIRBundle/src/DependencyInjection/FHIRExtension.php | Updates imports/types for relocated terminology client + contracts. |
| src/Bundle/FHIRBundle/composer.json | Adds dependency on ardenexal/fhir-http-client. |
| phpunit.dist.xml | Includes HttpClient component unit tests + source in coverage. |
| phpstan.neon | Adds Sdc + HttpClient component source paths for analysis. |
| demo/config/reference.php | Tightens psalm type annotations and updates default FHIR version + cache options docs. |
| composer.json | Adds PSR-4 autoload/autoload-dev mappings for HttpClient component. |
| .github/workflows/package-integrity.yml | Registers HttpClient and Sdc components for integrity checks. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…-fhir-query transport sameOriginPath() matched a base path like "/fhir" against any path sharing that string prefix (e.g. "/fhir2/..."), which could misroute Bundle.link[next] pagination to the wrong path on a same-origin server. It now requires a whole path-segment match. evaluateAndFormat() kept an atom that formatted to an empty string (unsupported result type, empty CodeableConcept.coding, or a coding/identifier with neither system nor code) as a substituted value instead of treating it as empty per the resolver's own documented policy, producing malformed parameters like "subject=" instead of dropping them.
Benchmark ResultsSerialization + FHIRPath benchmarks✅ Regression check passedComparison vs baselineRun |
This pull request introduces several configuration and dependency updates, primarily focused on adding support for the new
HttpClientcomponent and improving type strictness in configuration files. The changes also include updates to test and static analysis configurations to ensure the new component is properly integrated and covered.Component Integration and Dependency Updates:
fhir-http-clientpackage as a required dependency insrc/Bundle/FHIRBundle/composer.jsonand mapped its namespace incomposer.json. [1] [2]HttpClientcomponent in the package integrity workflow, PHPStan configuration, and PHPUnit configuration to ensure code quality checks and tests include the new component. [1] [2] [3] [4]HttpClientcomponent and its tests incomposer.json. [1] [2]Configuration and Type Improvements:
demo/config/reference.phpto enforce stricter types (e.g., convertingstring|list<T>tolist<T>, and similar for other union types), improving type safety and consistency. [1] [2] [3] [4] [5] [6] [7] [8] [9] [10] [11] [12] [13] [14]FhirConfigto"R4"and added new cache configuration options for terminology validation.These updates ensure the codebase is ready for development and testing with the new
HttpClientcomponent and that configuration files are more robust and type-safe.