Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/dhanyasukumaran1/fhir_query_validator/llms.txt

Use this file to discover all available pages before exploring further.

FHIRQueryValidator separates problems into two categories: errors that make a query invalid, and warnings that flag queries which are syntactically correct but may produce surprising results. Understanding this distinction—and writing code that responds appropriately to each—is the foundation of reliable FHIR integration. This guide covers every practical pattern for consuming ValidationResult objects, from simple if checks to aggregated batch reports.

Errors vs. warnings

Errors

Errors mean the query is structurally or semantically invalid. A FHIR server will reject it, or return undefined behavior. Always treat errors as blockers: do not send a query that has errors.

Warnings

Warnings mean the query is valid FHIR syntax but has a characteristic that may return unexpected results—such as a wildcard _include, a deprecated parameter, or an overly broad search. The query can be sent, but you should review the warning and decide whether to proceed.
from fhir_query_validator import FHIRQueryValidator

validator = FHIRQueryValidator()
result = validator.validate_query("Observation?patient=Patient/123&_include=Observation:*")

# result.is_valid is True even when warnings are present
print(result.is_valid)    # True
print(result.errors)      # []
print(result.warnings)    # ["Wildcard _include may return a very large bundle"]
result.is_valid is True when there are no errors. Warnings do not affect is_valid. A result can have is_valid == True and a non-empty warnings list at the same time.

Check is_valid before using a query

The simplest guard is an if result.is_valid check immediately after validation:
result = validator.validate_query("Patient?name=Smith")

if result.is_valid:
    # Safe to use the query
    send_to_fhir_server(query)
else:
    # Do not send; handle the failure
    handle_invalid_query(result)
Avoid inverting the check (if not result.is_valid: raise ...) without also handling the happy path—it’s easy to accidentally fall through to code that uses the query even when validation failed.

Iterate over errors and warnings

Both result.errors and result.warnings are plain Python lists of strings. Iterate over them directly:
result = validator.validate_query("Patient?birthdate=01/15/1990&unknown=foo")

for error in result.errors:
    print(f"Validation error: {error}")

for warning in result.warnings:
    print(f"Validation warning: {warning}")
When both lists are empty and result.is_valid is True, the query passed cleanly with no caveats.

Raise exceptions on validation failure

For code paths where an invalid query is a programming error (rather than a user input error), raise an exception immediately:
def require_valid_query(validator, query: str) -> str:
    """Validate a query and raise ValueError if it has errors."""
    result = validator.validate_query(query)
    if not result.is_valid:
        error_detail = "; ".join(result.errors)
        raise ValueError(
            f"Invalid FHIR query: {error_detail!r}\nQuery: {query!r}"
        )
    return query
For user-facing code where the query comes from input, prefer returning the ValidationResult to the caller so they can display the errors themselves rather than catching an exception.
Use a custom exception type (e.g., FHIRQueryValidationError) instead of ValueError when you want callers to be able to distinguish FHIR validation failures from other value errors in the same call stack.

Log validation results

Structured logging makes it easy to trace validation outcomes in production systems:
import logging

logger = logging.getLogger(__name__)


def validate_and_log(validator, query: str):
    result = validator.validate_query(query)

    if result.is_valid:
        if result.warnings:
            logger.warning(
                "FHIR query valid with warnings",
                extra={"query": query, "warnings": result.warnings},
            )
        else:
            logger.debug("FHIR query valid", extra={"query": query})
    else:
        logger.error(
            "FHIR query invalid",
            extra={"query": query, "errors": result.errors},
        )

    return result
Log at debug for clean queries, warning when warnings are present, and error when the query is invalid. This keeps routine validations out of production log noise while surfacing problems clearly.

Collect and aggregate errors from validate_batch()

validate_batch() returns a list of ValidationResult objects in the same order as the input queries. To find all invalid queries in a batch, zip the inputs with the results:
queries = [
    "Patient?name=Smith",
    "Observation?badparam=x",
    "Encounter?status=finished",
    "MedicationRequest?unknown=y&date=not-a-date",
]

results = validator.validate_batch(queries)

invalid = [
    (query, result)
    for query, result in zip(queries, results)
    if not result.is_valid
]

print(f"{len(invalid)} of {len(queries)} queries are invalid:")
for query, result in invalid:
    print(f"  {query}")
    for error in result.errors:
        print(f"    ERROR: {error}")
To count unique error messages across a batch—useful for identifying recurring problems:
from collections import Counter

all_errors = [
    error
    for result in results
    for error in result.errors
]

for message, count in Counter(all_errors).most_common():
    print(f"  ({count}x) {message}")

Write assertions in tests using validation results

ValidationResult works naturally with assert statements and test framework assertions:
import pytest
from fhir_query_validator import FHIRQueryValidator

validator = FHIRQueryValidator()


def test_valid_patient_query():
    result = validator.validate_query("Patient?name=Smith&birthdate=gt1980-01-01")
    assert result.is_valid, f"Expected valid query; errors: {result.errors}"


def test_invalid_date_format_produces_error():
    result = validator.validate_query("Patient?birthdate=01/15/1990")
    assert not result.is_valid
    assert any("date" in e.lower() for e in result.errors), (
        f"Expected a date-related error; got: {result.errors}"
    )


def test_wildcard_include_produces_warning():
    result = validator.validate_query(
        "Observation?patient=Patient/1&_include=Observation:*"
    )
    assert result.is_valid
    assert result.warnings, "Expected at least one warning for wildcard _include"

Build a validation report from a batch of queries

For audit logs, pre-flight checks, or developer tooling, build a structured report from a batch validation run:
from dataclasses import dataclass, field
from fhir_query_validator import FHIRQueryValidator


@dataclass
class QueryReport:
    query: str
    valid: bool
    errors: list[str] = field(default_factory=list)
    warnings: list[str] = field(default_factory=list)


def build_validation_report(queries: list[str]) -> list[QueryReport]:
    validator = FHIRQueryValidator()
    results = validator.validate_batch(queries)

    return [
        QueryReport(
            query=query,
            valid=result.is_valid,
            errors=list(result.errors),
            warnings=list(result.warnings),
        )
        for query, result in zip(queries, results)
    ]


def print_report(reports: list[QueryReport]) -> None:
    total = len(reports)
    valid_count = sum(1 for r in reports if r.valid)
    print(f"Validation report: {valid_count}/{total} queries valid\n")

    for report in reports:
        status = "PASS" if report.valid else "FAIL"
        print(f"[{status}] {report.query}")
        for error in report.errors:
            print(f"         ERROR:   {error}")
        for warning in report.warnings:
            print(f"         WARNING: {warning}")


# Example usage
queries = [
    "Patient?name=Smith",
    "Observation?patient=Patient/123&status=final",
    "Encounter?badparam=x",
]

reports = build_validation_report(queries)
print_report(reports)
1

Collect all queries

Gather every query string you want to validate into a single list—from configuration files, query builders, or user input.
2

Run validate_batch()

Pass the full list to validate_batch() to get results in one call.
3

Map results to report objects

Pair each query with its ValidationResult and convert to a plain data structure that’s easy to serialize or display.
4

Surface the report

Print, log, or serialize the report. Fail a CI check, send to a monitoring system, or display in a developer UI—the structure is the same regardless of output target.
The QueryReport dataclass in this example is a starting point. Add fields like timestamp, resource_type, or source as needed for your use case. The validator itself only provides is_valid, errors, and warnings—all other metadata comes from your application.

Build docs developers (and LLMs) love