Skip to content

Error Reference

flyql ships a single registry of every diagnostic code and parser errno. The registry is the source of truth that keeps Python, JavaScript, and Go in lockstep, and every Diagnostic or parser error a consumer sees carries an optional reference back into it.

The file errors/registry.json enumerates every error flyql can raise or report. Each entry has:

  • a name (symbolic, e.g. ERR_KEY_PARSE_FAILED, CODE_UNKNOWN_COLUMN)
  • a canonical message template
  • an optional description (human-readable context)
  • an optional dynamic_message flag (true when the runtime message interpolates context the template doesn’t show)

A codegen step (make generate-errors) produces per-language modules from the registry — constants, message maps, and a REGISTRY map of ErrorEntry records — so consumers never read the JSON at runtime.

The registry has four categories:

CategoryCode typeUsed by
core_parserintParser.parse() errors
columns_parserintcolumns.Parser.parse() errors
validatorstringdiagnose() (core + columns validators)
matcherstringEvaluator runtime errors (Python only — JS uses native RegExp, Go uses stdlib regexp)

The matcher category does not ship a REGISTRY map — matcher raises plain FlyqlError, not Diagnostic or ParserError. Other categories all expose *_REGISTRY (Py/JS) or *Registry (Go).

Validators return Diagnostic instances. The shape is identical across languages, with the registry entry on the error field (Py/JS) or Entry field (Go — the rename avoids a clash with the Error() method on Go error types).

FieldTypeMeaning
rangeRangesource span the user must edit to fix the issue
messagestringrich, interpolated message (e.g. "column 'foo' is not defined")
severity"error" | "warning"severity level decided at the emit site
codestringshort identifier (e.g. "unknown_column")
error / EntryErrorEntry?registry entry for this code, or null/zero-value if the code isn’t in the registry

The nested ErrorEntry shape:

FieldTypeMeaning
codeint | stringsame as the parent diagnostic’s code (parsers use ints)
namestringsymbolic name for static lookup (e.g. "CODE_UNKNOWN_COLUMN")
messagestringcanonical registry template (un-interpolated)
descriptionstringlonger human-readable context (often empty until backfilled)
dynamic_message / dynamicMessage / DynamicMessagebooltrue when the runtime message carries context the template doesn’t

ParserError (Py/JS) and *ParseError / *ParserError (Go) carry the same registry pointer:

FieldTypeMeaning
errno (Py/JS) / Code (Go)intnumeric error code
message (Py/JS) / Message (Go)stringrich runtime message
range (Py/JS) / Range (Go)Range?source span (core parser only — columns parser does not report a range)
error (Py/JS) / Entry (Go)ErrorEntry (? in Py/JS)registry entry, populated at the terminal raise/return site
from flyql import parse, ParserError
try:
parse("a=")
except ParserError as e:
print(e.errno, e.message)
if e.error is not None:
print(e.error.name, e.error.description)
from flyql import diagnose
from flyql.core import ColumnSchema, Column
from flyql.flyql_type import Type
schema = ColumnSchema.from_columns([Column(name="host", column_type=Type.String)])
for d in diagnose(parse("foo='X'").root, schema):
if d.error is not None:
print(f"{d.error.name}: {d.message}")

Note the field-name asymmetry: Go uses Entry; Python and JavaScript use error. Go convention reserves the field/method name Error for the Error() string method on error types, so the registry pointer lives under Entry.

Some entries carry dynamic_message: true. For these, the runtime message interpolates context the registry template doesn’t capture. Examples:

  • ERR_UNEXPECTED_EOF template: "unexpected EOF". Runtime: "unexpected EOF after 'not'".
  • ERR_KEY_PARSE_FAILED template: "key parsing failed". Runtime: the wrapped key parser’s specific message.

Consumer contract:

  • Tooltips and human-facing display: prefer the runtime message. Use error.description (when non-empty) as supplementary context.
  • Aggregation, grouping, structured logging: use error.code as the key. Use error.message only when you need the canonical template for log fingerprints.
  • Static analysis or doc deep-linking: use error.name (symbolic) — the most stable identifier across releases.

The error / Entry field is optional — it can be missing (null in Py/JS, zero-value ErrorEntry{} in Go) when:

  • User code constructs a Diagnostic directly with a code that isn’t in the registry (e.g. flyql-vue editor wiring with codes like "syntax" or "deprecated").
  • Renderer extension hooks return user-supplied diagnostics with custom codes.
  • External callers construct a ParserError without passing the new arg.

Internal validators and parsers always populate the field via centralized helpers (make_diag Py / makeDiag JS / MakeDiag Go). On registry miss the helpers return the diagnostic with error=None / error=null / zero-value Entry rather than panicking. Drift between code constants and the registry is caught at build time by the parity tests.

Defensive consumer pattern:

if diagnostic.error is not None:
label = diagnostic.error.name
else:
label = diagnostic.code
CategoryCode typeHoldsREGISTRY emitted
core_parserintparser errnos surfaced by ParserError / *ParseErroryes
columns_parserintcolumns parser errnosyes
validatorstringvalidator diagnostic codes (12 entries)yes
matcherstringmatcher runtime errors (Python only)no — matcher raises FlyqlError

The complete list of names, messages, and descriptions lives in errors/registry.json in the repository. Auto-rendering this list from the registry at docs build time is planned future work.