Statifier.Validator (Statifier v2.0.0)

Copy Markdown View Source

The third arrow of the parser pipeline, and the only gate in front of the Machine compiler (docs/architecture.md principle 4): a %Statifier.Document{} in, {:ok, document, warnings} | {:error, errors, warnings} out. The interpreter only ever accepts a compiled Machine, so there is no "validate if not already validated" fallback anywhere downstream - this pass is the single place a malformed document is ever caught.

It also carries a second, non-fatal finding channel (ADR-0033): a check in @warning_checks reports a Statifier.Validator.Warning, a document conformance finding the engine has a defined behavior for either way, and it never gates compilation the way an @checks finding does.

Five contracts, inherited from Statifier.Lowering's own and stated here so a reader does not have to infer them from the code:

  1. Collect-all, never fail-fast. Every check runs on every call, over the whole document; a document that trips five distinct checks reports five errors, not one. This applies to both channels.
  2. Document-order sort. The accumulated error list, and separately the accumulated warning list, is sorted by location.start_offset before it is returned, regardless of which check happened to report first.
  3. Never a partial result. validate/2 returns {:ok, document, warnings} only when the accumulated error list is empty, and document is always the caller's own input, unchanged - the validator never rewrites what it is given (contrast v1's source rewriting at ../statifier/lib/statifier/parser/scxml.ex:76-103).
  4. source must be the document's own binary. validate/2 takes the exact source document was parsed from, guarded by is_binary/1. Passing a different binary produces wrong Location.slice/2 results in any check that reads it, not a crash - there is no arity-1 convenience that could silently disable those checks.
  5. Both arms carry warnings. The error arm carries the warning list too - dropping warnings whenever an error also fires would make the warning channel fail-fast against the error channel, which contract 1 does not ask for on its own.

Summary

Functions

Runs all twenty @checks and the @warning_checks against document, collecting every finding on each channel rather than stopping at the first one, and returns {:ok, document, warnings} when no error fired or {:error, errors, warnings} otherwise - both arms carry warnings per contract 5. Each of errors and warnings is sorted by location.start_offset regardless of which check reported it. document is returned unchanged on success - this pass never rewrites its input.

Functions

validate(document, source, opts \\ [])

@spec validate(
  document :: Statifier.Document.t(),
  source :: binary(),
  opts :: keyword()
) ::
  {:ok, Statifier.Document.t(), [Statifier.Validator.Warning.t()]}
  | {:error, [Statifier.Validator.Error.t()], [Statifier.Validator.Warning.t()]}

Runs all twenty @checks and the @warning_checks against document, collecting every finding on each channel rather than stopping at the first one, and returns {:ok, document, warnings} when no error fired or {:error, errors, warnings} otherwise - both arms carry warnings per contract 5. Each of errors and warnings is sorted by location.start_offset regardless of which check reported it. document is returned unchanged on success - this pass never rewrites its input.

source must be the exact binary document was parsed from; a mismatched source produces wrong Location.slice/2 results in any check that reads it rather than raising, so callers must not pass a substitute binary.

opts defaults to [] and is threaded straight onto Statifier.Validator.Context.build/3; today's one recognized option is invoke_content_markup: true (ADR-0042), which Checks.Boilerplate reads off the context to relax its root-namespace check. See Statifier.compile/2 for what the flag is for and why it exists.