Skip to main content

Module lint

Module lint 

Source
Expand description

Structural + FHIRPath-syntax linting for ViewDefinition documents (#753)

  • see lint::lint_view_definition. Structural + FHIRPath-syntax linting for ViewDefinition documents (#753 evaluation POC, matured into the single lint engine for $sql-run, sof-cli, pysof, and the ViewDefinition editor by #821).

[lint_view_definition] is the single source of truth for “is this JSON document a well-formed ViewDefinition”: it walks a raw serde_json::Value (never a typed helios_fhir resource — a document being edited is often not valid enough to deserialize) and returns every problem it finds, located by RFC 6901 JSON pointer.

This is deliberately structural, syntactic, and — for resource — a name check against the compiled-in resource types: the principle #821 states is “the browser only knows syntax; the server knows FHIR”, and this module is the FHIR side of that split for the ViewDefinition shape itself. It does not evaluate FHIRPath expressions, does not resolve terminology, and does not touch storage: every check here — including the resource name check, which only consults helios_fhir’s compiled-in resource type list — is a pure function of the document.

§What this checks

  • The document is {"resourceType": "ViewDefinition", ...} ([DiagnosticCode::NotAViewDefinition]).
  • Every key, at every node, is one this module’s own key model (see [Node::fields]) allows for that node ([DiagnosticCode::UnknownKey]), has the JSON type the model expects ([DiagnosticCode::WrongType]), and — for required keys — is present ([DiagnosticCode::MissingRequired]) and non-empty ([DiagnosticCode::EmptyRequired]).
  • A select produces some output ([DiagnosticCode::SelectWithoutOutput]) and carries at most one iteration directive ([DiagnosticCode::MultipleIterationDirectives]).
  • Column names don’t collide within one output row ([DiagnosticCode::DuplicateColumnName]).
  • Every FHIRPath expression (column[].path, where[].path, forEach, forEachOrNull, each element of repeat) parses ([DiagnosticCode::FhirPathSyntax]), via helios_fhirpath’s parser — syntax only, never evaluated.
  • Every %name reference inside a FHIRPath expression that parses successfully names something that actually exists: an entry in constant[].name, one of the FHIRPath environment variables the evaluator resolves (%context, %resource, %rootResource, %ucum, %sct, %loinc), or a SQL-on-FHIR environment variable this crate itself binds (%rowIndex) ([DiagnosticCode::UndeclaredConstant]). Locating the reference still doesn’t evaluate the expression — it walks the parsed AST helios_fhirpath::external_constants returns.
  • resource names a resource type of some FHIR version compiled into this build ([DiagnosticCode::UnknownResourceType]) — the one check here that consults helios_fhir (for the list of names), still a pure function of the document (#1014).

§Actionability and localization (#821)

Every [Diagnostic] carries args — the values its English message interpolates, as named strings — and fixes, structural edits (pointer- addressed, never text-addressed: this module never sees source text) believed to resolve it. message itself is always English and never localized here; a caller that wants the diagnostic in another language (the /ui/sql/view-definitions/lint handler, for one) renders its own catalog from code + args instead of using message at all. See [Fix] and the args doc on [Diagnostic] for the exact contract.

[node_keys] exposes the same key model these checks are built on, so a consumer that wants “what keys are valid here” (a completion endpoint, for instance) doesn’t have to duplicate it.

§Example

use helios_sof::lint::{lint_view_definition, DiagnosticCode, Severity};
use serde_json::json;

let doc = json!({
    "resourceType": "ViewDefinition",
    "status": "active",
    "resource": "Patient",
    "select": [{
        "column": [{ "name": "id", "path": "getResourceKey(" }]
    }]
});

let diagnostics = lint_view_definition(&doc);
assert_eq!(diagnostics.len(), 1);
assert_eq!(diagnostics[0].code, DiagnosticCode::FhirPathSyntax);
assert_eq!(diagnostics[0].severity, Severity::Error);
assert_eq!(diagnostics[0].pointer, "/select/0/column/0/path");

Structs§

Diagnostic
One problem lint_view_definition found, located by RFC 6901 JSON pointer ("" is the document root; ~0/~1 escape ~// inside a key).
FunctionInfo
The FHIRPath function catalog and the FHIRPath environment variables, re-exported for a completion endpoint’s function/variable candidates.
KeyInfo
One key node_keys reports as valid at a node, in the order [Node::fields] declares it.
Span
A location inside the string value a Diagnostic points at, expressed in Unicode char offsets — never UTF-8 bytes — so a browser counting Unicode code points (or anything else that is not counting raw bytes) can index into the string directly. Only ever set for DiagnosticCode::FhirPathSyntax and DiagnosticCode::UndeclaredConstant — every other diagnostic already locates itself precisely enough with pointer alone.

Enums§

DiagnosticCode
What kind of problem a Diagnostic reports. #[non_exhaustive]: this is a POC rule set (see the module docs for what is deliberately out of scope), and future work is expected to add codes, not just consumers matching on the ones that exist today.
Fix
A structural edit lint_view_definition believes would resolve (or at least meaningfully address) the Diagnostic it is attached to, expressed purely in terms of an RFC 6901 JSON pointer — never a text position. This module never sees the document’s source text (a browser’s CodeMirror instance does), so it cannot offer a text edit; a pointer is the one location format both sides agree on. #[non_exhaustive]: more fix shapes are expected as the lint grows more rules with obvious one-click resolutions.
FunctionCategory
The FHIRPath function catalog and the FHIRPath environment variables, re-exported for a completion endpoint’s function/variable candidates.
KeyKind
The JSON shape node_keys reports for one key — [Kind] without the nested [Node] a caller outside this module has no use for (and no way to name, since Node itself is private).
Severity
How serious a Diagnostic is. Nothing in this POC blocks Save — both severities are informational, but Warning is reserved for a future check that flags something suspicious rather than something the ViewDefinition spec (or this module’s own key model) outright forbids; every check implemented today reports Error.

Constants§

LINT_DIAGNOSTIC_CODING_SYSTEM
Stable coding.system for the helios_sof::lint diagnostic each OperationOutcome.issue.details.coding[0] carries in lint_operation_outcome. coding.code is the diagnostic’s own DiagnosticCode in the wire form lint_view_definition already serializes it in — see [diagnostic_coding_code].

Functions§

builtin_functions
The FHIRPath function catalog and the FHIRPath environment variables, re-exported for a completion endpoint’s function/variable candidates.
environment_variables
The FHIRPath function catalog and the FHIRPath environment variables, re-exported for a completion endpoint’s function/variable candidates.
lint_operation_outcome
Builds a FHIR OperationOutcome from lint diagnostics: one issue per error-severity diagnostic in diagnostics (any warning is silently dropped — this renders the shape a caller uses to reject a request, not to surface every diagnostic the lint found).
lint_view_definition
Lints doc as a ViewDefinition and returns every diagnostic found, ordered by pointer in document order (array elements sort by their numeric index, not lexicographically) and stable across runs.
node_keys
The keys this module’s key model allows at the node pointer identifies, in [Node::fields]’s own declaration order — the same model lint_view_definition’s unknown-key/missing-required/wrong-type checks are built on, exposed so a caller (a completion endpoint, in particular) can answer “what keys are valid here” without duplicating it.
pointer_to_fhirpath
Renders an RFC 6901 JSON pointer as a dotted FHIRPath-style expression rooted at ViewDefinition, e.g. /select/0/column/1/path becomes ViewDefinition.select[0].column[1].path, and the document root ("") becomes plain ViewDefinition.