helios-hfs 0.2.0

Helios FHIR Server - A high-performance FHIR R4/R4B/R5/R6 server
# Helios FHIR Server (HFS)

A high-performance FHIR server built in Rust.

An open test server is available at https://hfs.heliossoftware.com/ for experimentation and evaluation.

## Features

- Full FHIR RESTful API support 
- Multiple FHIR version support
- Pluggable storage backends (SQLite, PostgreSQL, MongoDB)
- Content negotiation (JSON)
- Conditional operations with ETag support
- Multi-tenant support via X-Tenant-ID header
- CORS support

## Installation

### From Source

```bash
# Clone the repository
git clone https://github.com/HeliosSoftware/hfs.git
cd hfs

# Build with default features (R4 + SQLite)
cargo build --release -p helios-hfs

# Build with all FHIR versions
cargo build --release -p helios-hfs --features R4,R4B,R5,R6,sqlite
```

## Usage

### Running the Server

```bash
# Run with default settings (R4, SQLite, port 8080)
./target/release/hfs

# Specify a different port
./target/release/hfs --port 3000

# Use an in-memory database
./target/release/hfs --database-url :memory:

# Enable debug logging
./target/release/hfs --log-level debug
```

### Command Line Options

```
Usage: hfs [OPTIONS]

Options:
      --port <PORT>              Server port [env: HFS_SERVER_PORT=] [default: 8080]
      --host <HOST>              Host to bind [env: HFS_SERVER_HOST=] [default: 127.0.0.1]
      --log-level <LOG_LEVEL>    Log level (error, warn, info, debug, trace)
                                 [env: HFS_LOG_LEVEL=] [default: info]
      --database-url <URL>       Database connection URL [env: DATABASE_URL=]
      --data-dir <PATH>          Path to FHIR data directory containing search parameter
                                 definitions [env: HFS_DATA_DIR=] [default: ./data]
      --max-body-size <BYTES>    Maximum request body size [env: HFS_MAX_BODY_SIZE=]
                                 [default: 10485760]
      --request-timeout <SECS>   Request timeout in seconds [env: HFS_REQUEST_TIMEOUT=]
                                 [default: 30]
      --enable-cors              Enable CORS [env: HFS_ENABLE_CORS=] [default: true]
      --cors-origins <ORIGINS>   Allowed CORS origins [env: HFS_CORS_ORIGINS=] [default: *]
  -h, --help                     Print help
  -V, --version                  Print version
```

## Configuration

### Environment Variables

| Variable | Default | Description |
|----------|---------|-------------|
| `HFS_SERVER_PORT` | 8080 | Server port |
| `HFS_SERVER_HOST` | 127.0.0.1 | Host to bind |
| `HFS_LOG_LEVEL` | info | Log level (error, warn, info, debug, trace) |
| `DATABASE_URL` | fhir.db | Database connection string |
| `HFS_DATA_DIR` | ./data | Path to FHIR data directory (search parameters) |
| `HFS_MAX_BODY_SIZE` | 10485760 | Max request body size (bytes; applies to the decompressed body for compressed requests) |
| `HFS_REQUEST_TIMEOUT` | 30 | Request timeout (seconds) |
| `HFS_ENABLE_CORS` | true | Enable CORS |
| `HFS_CORS_ORIGINS` | * | Allowed CORS origins |
| `HFS_CORS_METHODS` | GET,POST,PUT,DELETE,OPTIONS | Allowed HTTP methods |
| `HFS_CORS_HEADERS` | Content-Type,Authorization,X-Requested-With | Allowed headers |
| `HFS_DEFAULT_TENANT` | default | Default tenant ID |
| `HFS_TERMINOLOGY_SERVER` | (none) | Terminology server URL for `:in`/`:not-in` modifiers and FHIRPath `memberOf()`/`subsumes()` |
| `HFS_COMPOSITE_SYNC_MODE` | `asynchronous` | Composite-store write sync mode for ES-backed backends (`sqlite-elasticsearch`, `postgres-elasticsearch`, `mongodb-elasticsearch`, `s3-elasticsearch`). One of `asynchronous`, `synchronous`, `hybrid`. With `asynchronous` (default) the write returns as soon as the primary commits and the search backend is updated on a background worker — lowest latency, but a follow-up search can race the indexing. Use `synchronous` when callers need read-your-write semantics (e.g. integration tests, bulk-load flows that immediately search). Ignored when the storage backend has no search secondary. |

## FHIR Version Support

Build with specific FHIR versions using feature flags:

```bash
# R4 only (default)
cargo build -p helios-hfs --features R4,sqlite

# R5 only
cargo build -p helios-hfs --no-default-features --features R5,sqlite

# Multiple versions
cargo build -p helios-hfs --features R4,R4B,R5,R6,sqlite
```

## Database Backends

### SQLite (Default)

```bash
cargo build -p helios-hfs --features sqlite

# Run with file-based database
./target/release/hfs --database-url ./data/fhir.db

# Run with in-memory database
./target/release/hfs --database-url :memory:
```

### PostgreSQL

```bash
cargo build -p helios-hfs --no-default-features --features R4,postgres

./target/release/hfs --database-url "postgresql://user:pass@localhost/fhir"
```

### MongoDB

```bash
cargo build -p helios-hfs --no-default-features --features R4,mongodb

./target/release/hfs --database-url "mongodb://localhost:27017/fhir"
```

## API Endpoints

| Interaction | Method | URL |
|------------|--------|-----|
| capabilities | GET | `/metadata` |
| read | GET | `/[type]/[id]` |
| vread | GET | `/[type]/[id]/_history/[vid]` |
| update | PUT | `/[type]/[id]` |
| patch | PATCH | `/[type]/[id]` |
| delete | DELETE | `/[type]/[id]` |
| create | POST | `/[type]` |
| search | GET/POST | `/[type]?params` or `/[type]/_search` |
| history (instance) | GET | `/[type]/[id]/_history` |
| history (type) | GET | `/[type]/_history` |
| history (system) | GET | `/_history` |
| batch/transaction | POST | `/` |
| health | GET | `/health` |

## Examples

### Create a Patient

```bash
curl -X POST http://localhost:8080/Patient \
  -H "Content-Type: application/fhir+json" \
  -d '{
    "resourceType": "Patient",
    "name": [{"family": "Smith", "given": ["John"]}],
    "birthDate": "1970-01-01"
  }'
```

### Read a Patient

```bash
curl http://localhost:8080/Patient/123
```

### Search for Patients

```bash
curl "http://localhost:8080/Patient?family=Smith"
```

### Get CapabilityStatement

```bash
curl http://localhost:8080/metadata
```

## Batch and Transaction Support

HFS supports both batch and transaction bundles for processing multiple operations in a single request.

> **Note:** Transaction bundles require ACID transaction support. The default SQLite backend fully supports transactions. If using other backends, check the capability matrix in the persistence crate documentation.

### Transaction Bundle (Atomic)

All operations succeed or all fail. Entries are processed in FHIR-specified order: DELETE → POST → PUT → GET.

```bash
curl -X POST http://localhost:8080/ \
  -H "Content-Type: application/fhir+json" \
  -d '{
    "resourceType": "Bundle",
    "type": "transaction",
    "entry": [
      {
        "fullUrl": "urn:uuid:patient-1",
        "resource": {
          "resourceType": "Patient",
          "name": [{"family": "Smith"}]
        },
        "request": {
          "method": "POST",
          "url": "Patient"
        }
      },
      {
        "resource": {
          "resourceType": "Observation",
          "subject": {"reference": "urn:uuid:patient-1"},
          "code": {"text": "Blood Pressure"}
        },
        "request": {
          "method": "POST",
          "url": "Observation"
        }
      }
    ]
  }'
```

The `urn:uuid:patient-1` reference is automatically resolved to the actual Patient ID after creation.

### Batch Bundle (Independent)

Each operation is processed independently; failures don't affect other entries.

```bash
curl -X POST http://localhost:8080/ \
  -H "Content-Type: application/fhir+json" \
  -d '{
    "resourceType": "Bundle",
    "type": "batch",
    "entry": [
      {
        "request": {
          "method": "GET",
          "url": "Patient/123"
        }
      },
      {
        "request": {
          "method": "DELETE",
          "url": "Patient/456"
        }
      }
    ]
  }'
```

### Current Limitations

The following FHIR bundle features are not yet implemented:
- Conditional reference resolution (`Patient?identifier=12345`)
- PATCH method in bundles
- Prefer header handling (`return=minimal`, etc.)

## Search Parameter Configuration

HFS loads FHIR SearchParameter definitions from JSON bundle files to enable comprehensive search functionality. By default, these files are expected in a `data/` directory relative to the working directory or executable.

### Data Directory Structure

```
data/
├── search-parameters-r4.json   # FHIR R4 SearchParameters (HL7 spec)
├── search-parameters-r4b.json  # FHIR R4B SearchParameters (HL7 spec)
├── search-parameters-r5.json   # FHIR R5 SearchParameters (HL7 spec)
├── search-parameters-r6.json   # FHIR R6 SearchParameters (auto-downloaded at build time)
└── *.json                      # Custom SearchParameter files (see below)
```

### Search Parameter Loading

On startup, HFS loads SearchParameters in this order:
1. **Minimal fallback** - Built-in `_id`, `_lastUpdated`, `_tag`, `_profile`, `_security` (always available)
2. **Spec file** - Loads from the appropriate `search-parameters-*.json` based on configured FHIR version
3. **Custom files** - Loads any additional `.json` files in the data directory (not matching `search-parameters-*.json`)
4. **Stored parameters** - Loads any custom SearchParameters POSTed to the server

### Custom SearchParameter Files

You can add custom SearchParameters by placing JSON files in the data directory. Each file can contain:
- A single SearchParameter resource
- An array of SearchParameter resources
- A FHIR Bundle containing SearchParameter resources

Example custom SearchParameter file (`data/custom-search-params.json`):
```json
{
  "resourceType": "SearchParameter",
  "id": "patient-mrn",
  "url": "http://example.org/fhir/SearchParameter/patient-mrn",
  "name": "mrn",
  "status": "active",
  "code": "mrn",
  "base": ["Patient"],
  "type": "token",
  "expression": "Patient.identifier.where(type.coding.code='MR')"
}
```

Or as a Bundle:
```json
{
  "resourceType": "Bundle",
  "type": "collection",
  "entry": [
    {
      "resource": {
        "resourceType": "SearchParameter",
        "url": "http://example.org/fhir/SearchParameter/patient-mrn",
        ...
      }
    }
  ]
}
```

### Custom Data Directory

Specify a custom location for the data files:

```bash
# Via command line
./target/release/hfs --data-dir /opt/hfs/data

# Via environment variable
HFS_DATA_DIR=/opt/hfs/data ./target/release/hfs
```

If the spec file is missing, HFS logs a warning and continues with minimal fallback parameters. This ensures the server can start even without the full spec files, though search functionality will be limited.

### R6 Automatic Download

When building with the R6 feature enabled, the `search-parameters-r6.json` file is automatically downloaded from the HL7 build server during compilation. The download is skipped if:
- The file already exists and is less than 24 hours old
- The `DOCS_RS` environment variable is set (for docs.rs builds)

## Multi-Tenant Support

Use the `X-Tenant-ID` header to isolate data between tenants:

```bash
curl -H "X-Tenant-ID: clinic-a" http://localhost:8080/Patient
curl -H "X-Tenant-ID: clinic-b" http://localhost:8080/Patient
```