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
selectproduces 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 ofrepeat) parses ([DiagnosticCode::FhirPathSyntax]), viahelios_fhirpath’s parser — syntax only, never evaluated. - Every
%namereference inside a FHIRPath expression that parses successfully names something that actually exists: an entry inconstant[].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 ASThelios_fhirpath::external_constantsreturns. resourcenames a resource type of some FHIR version compiled into this build ([DiagnosticCode::UnknownResourceType]) — the one check here that consultshelios_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_definitionfound, located by RFC 6901 JSON pointer (""is the document root;~0/~1escape~//inside a key). - Function
Info - The FHIRPath function catalog and the FHIRPath environment variables,
re-exported for a completion endpoint’s
function/variablecandidates. - KeyInfo
- One key
node_keysreports as valid at a node, in the order [Node::fields] declares it. - Span
- A location inside the string value a
Diagnosticpoints at, expressed in Unicodecharoffsets — 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 forDiagnosticCode::FhirPathSyntaxandDiagnosticCode::UndeclaredConstant— every other diagnostic already locates itself precisely enough withpointeralone.
Enums§
- Diagnostic
Code - What kind of problem a
Diagnosticreports.#[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_definitionbelieves would resolve (or at least meaningfully address) theDiagnosticit 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. - Function
Category - The FHIRPath function catalog and the FHIRPath environment variables,
re-exported for a completion endpoint’s
function/variablecandidates. - KeyKind
- The JSON shape
node_keysreports for one key — [Kind] without the nested [Node] a caller outside this module has no use for (and no way to name, sinceNodeitself is private). - Severity
- How serious a
Diagnosticis. Nothing in this POC blocks Save — both severities are informational, butWarningis 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 reportsError.
Constants§
- LINT_
DIAGNOSTIC_ CODING_ SYSTEM - Stable
coding.systemfor thehelios_sof::lintdiagnostic eachOperationOutcome.issue.details.coding[0]carries inlint_operation_outcome.coding.codeis the diagnostic’s ownDiagnosticCodein the wire formlint_view_definitionalready 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/variablecandidates. - environment_
variables - The FHIRPath function catalog and the FHIRPath environment variables,
re-exported for a completion endpoint’s
function/variablecandidates. - lint_
operation_ outcome - Builds a FHIR
OperationOutcomefrom lint diagnostics: oneissueper error-severity diagnostic indiagnostics(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
docas a ViewDefinition and returns every diagnostic found, ordered bypointerin 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
pointeridentifies, in [Node::fields]’s own declaration order — the same modellint_view_definition’sunknown-key/missing-required/wrong-typechecks 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/pathbecomesViewDefinition.select[0].column[1].path, and the document root ("") becomes plainViewDefinition.