Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions crates/fhir-validator/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ repository.workspace = true
rust-version.workspace = true
description = "FHIR resource validator for HFS — FHIR Schema based structural and profile validation"
homepage = "https://github.com/HeliosSoftware/hfs/tree/main/crates/fhir-validator"
readme = "README.md"
keywords = ["helios-software", "hl7", "fhir", "validation", "fhir-schema"]

[features]
Expand Down Expand Up @@ -35,17 +36,23 @@ serde_json = { workspace = true, features = ["preserve_order"] }
indexmap = { version = "2", features = ["serde"] }
# Schema pack (de)compression.
flate2 = "1"
# FHIR NPM `.tgz` extract (package materialization).
tar = "0.4"
# Optional integrity sidecar for cached package tarballs.
sha2 = "0.10"
# Primitive value regexes (FHIR spec patterns carried in the packs).
regex = "1"
# Dyn-compatible async TerminologyProvider.
async-trait = "0.1"
thiserror = "2"
clap = { version = "4", features = ["derive"], optional = true }
helios-fhirpath = { path = "../fhirpath", version = "0.2.1", optional = true, default-features = false }

[dev-dependencies]
serde = { workspace = true }
serde_json = { workspace = true }
tokio = { version = "1", features = ["rt", "macros"] }
tempfile = "3"

[[bin]]
name = "generate-schema-packs"
Expand Down
36 changes: 36 additions & 0 deletions crates/fhir-validator/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# helios-fhir-validator

FHIR resource validation for [Helios FHIR Server](https://github.com/HeliosSoftware/hfs), built on the [FHIR Schema](https://fhir-schema.github.io/fhir-schema/) approach: StructureDefinitions compile to differential JSON-schema-like forms and validate via **cooperative schema sets** (no snapshot flattening).

## Features

- Structural validation: unknown elements, cardinality, choices, fixed/pattern, `maxLength` / `minValue` / `maxValue`, primitives
- Profile layering: `meta.profile`, caller profiles, slicing (pattern / type / profile / binding + reslices), extension sugar
- Deferred effects: FHIRPath invariants (`fhirpath` feature) and terminology bindings (required; optional extensible warnings)
- Embedded core schema + terminology packs for R4 / R4B / R5 / R6 (feature-gated)
- FHIR NPM / IG package cache, offline dependency resolution, `fhirVersions` checks
- QuestionnaireResponse validation against a Questionnaire definition
- Authoring helpers (`editor`) for “what can I add here?” UIs

## Quick start

```rust
use helios_fhir_validator::{SchemaRegistry, ValidationOptions, Validator};
use std::sync::Arc;

let mut registry = SchemaRegistry::new();
// …insert schemas or use packs::core_registry(FhirVersion::R4)
let validator = Validator::new(Arc::new(registry));
let outcome = validator.validate_sync(&resource, &ValidationOptions::default());
```

## Packages

See [docs/packages.md](docs/packages.md) for cache layout, `HFS_FHIR_PACKAGE_*` operator config, and the bundled sample IG under `tests/fixtures/packages/`.

## Tests

```bash
cargo test -p helios-fhir-validator
cargo test -p helios-fhir-validator -- --ignored # whole-pack smoke
```
80 changes: 80 additions & 0 deletions crates/fhir-validator/docs/packages.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# FHIR NPM package materialization

Package overlays use the same `SchemaRegistry` + `CompositeResolver` path as
core packs and tenant-uploaded StructureDefinitions (#232). This document
covers **materialization proper**: cache layout, sources, dependency
resolution, and operator configuration.

## Cache vs sources

| Concept | Role |
|---------|------|
| **Cache** (`HFS_FHIR_PACKAGE_CACHE`) | Durable expanded packages: `{cache}/{name}/{version}/` |
| **Sources** (`HFS_FHIR_PACKAGE_SOURCES`) | Where packages are **installed from** at boot (local or URL) |
| **Roots** (`HFS_FHIR_PACKAGES`) | Which `name@version` layers to load; defaults to packages installed from sources |

`.staging/` and `.downloads/` under the cache root are **internal** (temp unpack /
HTTP fetch). They are not package sources.

## Accepted local sources (`PackageCache::ensure_from_path`)

- FHIR NPM `.tgz` / `.tar.gz` file (any path, e.g. IG publisher
`output/atrius.fhir.r4.india.en.tgz` or `output/package.tgz`)
- Expanded package directory with `package.json` or `package/package.json`
- IG publisher **`output/`** directory: prefers `package.tgz`; if several
`*.tgz` exist, pass one file explicitly (do **not** treat the whole HTML
tree as a package)

## Configuration

| Variable | Purpose |
|----------|---------|
| `HFS_FHIR_PACKAGE_CACHE` | Cache root (required when sources/packages are set) |
| `HFS_FHIR_PACKAGE_SOURCES` | Comma-separated local paths and/or `http(s)://…/*.tgz` URLs |
| `HFS_FHIR_PACKAGES` | Optional `name@version` roots; if omitted, uses ids from sources |

### Examples

Bundled test fixture (check out of tree):

```bash
export HFS_FHIR_PACKAGE_CACHE=$PWD/fhir-package-cache
export HFS_FHIR_PACKAGE_SOURCES=crates/fhir-validator/tests/fixtures/packages/sample.tgz
export HFS_VALIDATION_MODE=enforce
# defaults to example.fhir.r4.sample@0.1.0 from the tarball
```

Publisher tarball or `output/` on disk:

```bash
export HFS_FHIR_PACKAGE_SOURCES=/path/to/ig/output/package.tgz
# or: /path/to/ig/output (picks package.tgz when unique)
```

Published URL:

```bash
export HFS_FHIR_PACKAGE_SOURCES=https://example.org/fhir/r4/sample/package.tgz
```

See `tests/fixtures/packages/README.md` for the sample IG contents and rebuild script.

## Resolver order

`CompositeResolver` (earlier wins):

1. Tenant stored-StructureDefinition overlay (optional)
2. Package layers — dependents before transitive deps
3. Embedded core schema pack

## What is loaded

Only **StructureDefinition** resources become schemas. Abstract infrastructure
roots (`Element`, `BackboneElement`, `Resource`, `DomainResource`) are skipped.
CodeSystem / ValueSet files are discovered for operators but must be imported
via HTS, not the schema registry.

## Library API

See `helios_fhir_validator::packages`: `PackageCache`, `ensure_from_path`,
`resolve_packages`, `materialize_package`, `materialize_package_layers`.
1 change: 1 addition & 0 deletions crates/fhir-validator/src/bin/validator_cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ fn main() -> ExitCode {
profiles: args.profiles.clone(),
use_meta_profiles: !args.no_meta_profiles,
unknown_profile: UnknownProfilePolicy::Warn,
..Default::default()
};
let outcome = validator.validate_sync(&resource, &opts);

Expand Down
7 changes: 6 additions & 1 deletion crates/fhir-validator/src/converter/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,12 @@ pub(crate) struct Ed {
pub short: Option<String>,
#[serde(default)]
pub constraint: Vec<EdConstraint>,
/// Everything else — scanned for `fixed[x]` / `pattern[x]`.
#[serde(rename = "maxLength")]
pub max_length: Option<u64>,
#[serde(rename = "sliceIsConstraining")]
pub slice_is_constraining: Option<bool>,
/// Everything else — scanned for `fixed[x]` / `pattern[x]` /
/// `minValue[x]` / `maxValue[x]`.
#[serde(flatten)]
pub rest: serde_json::Map<String, Value>,
}
Expand Down
Loading