Library (crates)
Three crates make up the project:
octofhir-sof— the network-free engine: parsing, in-memory evaluation, and SQL generation.octofhir-sof-lint— offline lint and validation of ViewDefinitions.octofhir-sof-cli— the thinoctofhir-sofbinary over the two above.
octofhir-sof
Section titled “octofhir-sof”use octofhir_sof::{ViewDefinition, execute, SqlGenerator};
let view = ViewDefinition::parse(&json_text)?;
// In-memory evaluation — returns a ViewResult (rows + column metadata):let result = execute(&view, &resources).await?;
// Or generate SQL:let sql = SqlGenerator::new().generate(&view)?.sql;Execution
Section titled “Execution”execute(&view, &resources) -> Result<ViewResult>— evaluate a view over a slice ofserde_json::Valueresources in memory.CompiledView::compile(&view).await?thencompiled.execute_resource(&resource).await?— compile once and run resource-at-a-time for bounded-memory streaming;compiled.columns()exposes the output columns.ViewRunner::new(pool).run(&view).await— run against a PostgreSQL pool.ViewResultcarriescolumns,row_count,to_json_array(), etc.
SQL generation
Section titled “SQL generation”SqlGenerator::new()/SqlGenerator::new().with_dialect(Dialect)— build a generator;.generate(&view)?returns aGeneratedSql(sqlpluscolumns: Vec<GeneratedColumn>).Dialect—PostgresorDuckDb(parses from"postgres"/"duckdb"/"ansi").create_table(name, &columns, dialect) -> String— emitCREATE TABLEDDL.
ViewDefinition model
Section titled “ViewDefinition model”ViewDefinition (with Column, SelectColumn, Constant, WhereClause)
parses from text with ViewDefinition::parse(&str) or from a value with
ViewDefinition::from_json(&value).
Output writers
Section titled “Output writers”octofhir_sof::output::get_writer(format) returns a writer for csv, ndjson,
json (and parquet with the feature) that serializes a ViewResult to any
Write sink.
Native Parquet temporal columns
Section titled “Native Parquet temporal columns”Enable the optional writer with:
[dependencies]octofhir-sof = { version = "0.1.4", features = ["parquet"] }use octofhir_sof::output::{OutputWriter, ParquetWriter, TemporalEncoding};
let writer = ParquetWriter::new().with_temporal(TemporalEncoding::Native);let mut file = std::fs::File::create("result.parquet")?;writer.write(&result, &mut file)?;The same configuration works with AsyncOutputWriter::write and
AsyncOutputWriter::write_streaming. Parquet currently buffers the result;
this option does not add bounded-memory Parquet streaming.
| ColumnType | Text (default) | Native |
|---|---|---|
| Date | Utf8 | Date32 |
| DateTime, Instant | Utf8 | Timestamp(Microsecond, UTC) |
| Time | Utf8 | Time64(Microsecond) |
new(), default(), get_writer("parquet") and AsyncWriter::new("parquet")
continue to use Text. TemporalEncoding::parse / FromStr accept text
(string alias) and native, ignoring case and surrounding whitespace.
Native mode converts 2026 to January 1 and 2026-03 to March 1; date-only
timestamps become midnight UTC. Explicit offsets normalize to UTC; timestamps
without offsets are treated as UTC. Time values may include seconds and
fractions, or just hours and minutes. Precision finer than microseconds is
truncated. Conversion is lenient, not strict FHIR validation, including for
instant columns. Invalid or non-string values become null with a tracing
warning containing the column name/type but not the source value. Missing and
null values stay null silently. A non-nullable column containing invalid values
returns Error::Output rather than silently changing its schema.
Native output discards original precision and offsets; Text preserves the original strings. There is no additional precision column. Other column types, including JSON collection columns, are unaffected. SQL and ANSI type mappings are unchanged. Embedders control their own tracing subscriber; the CLI sends warnings to stderr.
The core library does not depend on reqwest, with or without Parquet. The lint
crate still includes HTTP dependencies for schema/package providers.
octofhir-sof-lint
Section titled “octofhir-sof-lint”use octofhir_sof_lint::{ lint, lint_view, lint_sql, lint_shareable, validate_structure, FhirSchemaProvider, Finding, Severity,};validate_structure(&view) -> Vec<Finding>— structural spec checks (FH06–FH10), no package needed.lint(&view, &provider) -> Vec<Finding>— structural + schema-driven selector checks (FH01–FH05), without generated-SQL analysis.lint_view— selector-only validation.lint_sqlexplicitly opts in to generated-SQL analysis; it is not called bylint.lint_shareable(&view, &allowed_custom) -> Vec<Finding>— the ShareableViewDefinition FHIRPath subset check (FH11).FhirSchemaProvider::load(package, version).await?— load a FHIR package through the canonical manager;with_schemas([...])builds one from in-memory schemas.Finding—{ code, message, severity, location, help_url }; thehelp_urlfor anFH*rule points at its rule reference page.