Skip to content

feat(schema,config): pluggable parser registry (#129)#201

Merged
zeevdr merged 2 commits into
mainfrom
pluggable-parser
Apr 27, 2026
Merged

feat(schema,config): pluggable parser registry (#129)#201
zeevdr merged 2 commits into
mainfrom
pluggable-parser

Conversation

@zeevdr
Copy link
Copy Markdown
Member

@zeevdr zeevdr commented Apr 27, 2026

Closes #129. Final cross-cutting issue in the Schema Spec v0.1.0 milestone.

Summary

Extracts the schema and config YAML parsers behind a small `Parser` interface + package-level registry. Each spec version is now a single sibling file (e.g. `parser_v1.go`) that declares a parser struct and registers it via `init()`. Adding v2 in the future means landing one new file — the existing v1 path stays untouched, and nothing else in the codebase changes.

Design

```go
type Parser interface {
SpecVersion() string
Parse(data []byte) (*pb.Schema, error)
Marshal(s *pb.Schema) ([]byte, error)
}

func Register(p Parser) // called from each parser's init()
func Dispatch(data []byte) (*pb.Schema, error) // routes by spec_version
func MarshalSchemaAt(s *pb.Schema, version string) (...) // emits in chosen version
func SupportedVersions() []string // surfaced in errors
func LatestVersion() string // default for export
```

(Symmetric in `internal/config/` — `DispatchImport` carries the schema's `fieldTypes` since the config parser needs them for type-aware coercion, and returns a `ParsedImport` struct so the service layer can pick up the YAML's `description:` for audit purposes.)

Layer split

  • Layer 1 (per-version, in the parser): YAML shape — required keys, regex patterns, type enums, basic structural lint.
  • Layer 2 (version-agnostic, in the service): referential integrity (redirect_to, dependentRequired field existence), prefix-overlap rejection, range sanity, runtime cross-field rules (dependentRequired).

The dispatch point sits between the two — `Dispatch` returns a `*pb.Schema`, then service.go runs the layer-2 checks on that proto value, identical regardless of which version produced it.

Wire surface

  • Adds optional `spec_version` field to `ExportSchemaRequest` (proto field 3) and `ExportConfigRequest` (proto field 3). Additive — buf breaking passes.
  • Default behavior unchanged: when `spec_version` is omitted, the server emits the highest registered version. With v1 as the only registered parser, that's the same output as before.
  • Unknown `spec_version` produces `InvalidArgument` with the supported version list in the error message.

Test coverage

  • `internal/schema/parser_test.go` and `internal/config/parser_test.go`:
    • SupportedVersions / LatestVersion
    • Dispatch routing — happy path, missing spec_version, unknown version, malformed YAML
    • MarshalSchemaAt — default (latest), explicit v1, unknown version rejected
    • Round-trip: `Dispatch → MarshalSchemaAt → Dispatch` preserves all fields including `dependentRequired` and `validations`
  • Existing `yaml_test.go` suites unchanged — `parser_v1` is a thin wrapper over the same underlying functions.

Docs

  • `docs/concepts/meta-schema.md` gains a "Multi-version evolution" section describing the plug-in pattern and the `spec_version` request field.
  • `.agents/context/schema-spec.md` updated with the registry shape so future-me / next contributor knows where v2 plugs in.

Test plan

  • `make test` passes (full suite, race detector on)
  • `make lint` passes
  • `make generate docs` regenerated and committed
  • No semantic changes to existing v1 parsing — yaml_test.go suites pass without modification

Closes #129.

Extracts the schema and config YAML parsers behind a small Parser
interface + package-level registry. Each spec version is now a single
sibling file (e.g. parser_v1.go) that declares a parser struct and
registers it via init(). Adding v2 in the future means landing one new
file — the existing v1 path stays untouched, and nothing else in the
codebase changes.

The service layer dispatches through the registry on ImportSchema /
ImportConfig (peeks `spec_version:` from the YAML header, looks up the
registered parser, delegates) and on ExportSchema / ExportConfig (uses
the request's optional `spec_version` field, defaulting to the highest
registered version when absent). Layer-2 semantic checks (referential
integrity, prefix-overlap, dependentRequired field-existence, range
sanity) run on the proto value AFTER dispatch — they're version-agnostic
and shared across all parsers.

Unknown spec_version values produce a clear InvalidArgument error
listing the supported versions.

Wire surface:

- Adds optional `spec_version` field to ExportSchemaRequest (proto field
  3) and ExportConfigRequest (proto field 3). Additive change — no
  break for existing clients.
- New entry points: schema.Dispatch, schema.MarshalSchemaAt,
  schema.SupportedVersions, schema.LatestVersion (and symmetric for
  config — DispatchImport / MarshalConfigAt). All exported from the
  parent package; v1 file uses package-private helpers.

Test coverage:

- New parser_test.go in both packages: SupportedVersions / LatestVersion
  / Dispatch (route, missing spec_version, unknown version, malformed
  YAML) / MarshalSchemaAt (default, explicit version, unknown version)
  / round-trip (Dispatch → Marshal → Dispatch preserves all fields,
  including dependentRequired and validations).
- Existing yaml_test.go suites unchanged — parser_v1 is a thin wrapper
  over the same underlying functions.

Docs:

- meta-schema.md gains a "Multi-version evolution" section describing
  the plug-in pattern and the spec_version request field.
- schema-spec.md updated with the registry shape.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@zeevdr zeevdr added this to the Schema Spec v0.1.0 milestone Apr 27, 2026
@zeevdr zeevdr added sdk SDK changes server Server changes size: L Larger effort — multiple days, design decisions needed priority: P0 Blocks alpha or release labels Apr 27, 2026
@github-actions
Copy link
Copy Markdown

Thanks for your first pull request! We appreciate the contribution.

Before review, please make sure:

  • make all passes (generate, lint, test, build)
  • The PR targets main and has a clear description
  • See CONTRIBUTING.md for the full checklist

The new parser_test.go suites in internal/schema/ and internal/config/
push internal coverage from 80.0% → 81.9%. Ratchet the threshold so
future regressions don't slide silently back below the new bar. Same
for sdk/adminclient (97.2 → 97.6) and sdk/tools (95.8 → 96.1) — small
incidental upticks from indirect coverage of the new registry methods.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@zeevdr zeevdr merged commit 4a9ca52 into main Apr 27, 2026
17 checks passed
@zeevdr zeevdr deleted the pluggable-parser branch April 27, 2026 17:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

priority: P0 Blocks alpha or release sdk SDK changes server Server changes size: L Larger effort — multiple days, design decisions needed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Pluggable schema parser: support multiple spec versions on the backend

1 participant