Skip to content

Build or adopt a real JSON Schema generator for adcp-go #145

@bokelley

Description

@bokelley

Problem

The Go SDK generator is now a structural risk. The current adcp/schemas/generate.py handles simple structs/enums, but the AdCP schemas rely heavily on JSON Schema features that force hand-written Go mirrors today:

  • oneOf / anyOf response variants and polymorphic core shapes
  • allOf composition and flat merged rows
  • inline nested objects, especially response item rows
  • absolute $id / $ref paths like /schemas/3.0.12/core/context.json
  • optional-vs-zero semantics (*bool, *float64)
  • schema drift checks for hand-written inline subshapes

The latest media-buy follow-up needed hand-written CreateMediaBuyResult, MediaBuyData, PackageStatus, and custom marshal logic. That got the wire correct, but it is not a sustainable SDK maintenance model.

Current repo signal

Quick count from the current schema bundle:

  • 587 schema files
  • 329 oneOf
  • 247 anyOf
  • 271 allOf
  • 4,367 inline object schemas
  • 1,094 array item inline object schemas
  • 3,248 $ref
  • 737 additionalProperties: false

Current Go type ownership is already inverted:

  • adcp/types.go: 90 hand-written structs
  • adcp/inputs.go: 14 hand-written structs
  • adcp/governance_types.go: 12 hand-written structs
  • adcp/types_gen.go: 77 generated structs, 142 aliases

Initial candidate smoke tests

github.com/atombender/go-jsonschema

Pros:

  • Real JSON Schema generator, not OpenAPI-only.
  • Claims support for allOf, anyOf, oneOf, if/then/else, refs, enums, and generated validation.

Smoke-test results:

go run github.com/atombender/go-jsonschema@latest -p adcp adcp/schemas/media-buy/create-media-buy-response.json

Generated only:

type CreateMediaBuyResponseJson map[string]interface{}

That is not acceptable for the main pain point: typed create-media-buy variants.

It also failed to resolve AdCP absolute refs in normal repo layout:

go run github.com/atombender/go-jsonschema@latest --only-models -p adcp \
  adcp/schemas/media-buy/get-media-buys-response.json \
  adcp/schemas/core/context.json \
  adcp/schemas/core/account.json

Failure:

could not follow $ref "/schemas/3.0.12/core/context.json" ... cannot resolve schema

quicktype

Pros:

  • Mature multi-language codegen; supports JSON Schema input and Go output.

Smoke-test results:

npx --yes quicktype -s schema -l go --package adcp adcp/schemas/media-buy/create-media-buy-response.json

Failed resolving AdCP absolute refs:

Error: Could not fetch schema , referred to from ...#/oneOf/0/properties/account.

A self-contained oneOf smoke test generated a merged bag-of-fields struct, not a typed union/interface:

type CreateMediaBuyResponse struct {
    Status     *Status `json:"status,omitempty"`
    TaskID     *string `json:"task_id,omitempty"`
    MediaBuyID *string `json:"media_buy_id,omitempty"`
}

This is better than any, but still not the Go SDK shape we want.

OpenAPI generators (ogen, oapi-codegen, OpenAPI Generator)

Worth a deeper spike because ogen advertises generated sum types for oneOf, but these are OpenAPI generators, not direct AdCP JSON Schema generators. We would need a reliable JSON Schema -> OpenAPI components normalization layer first. That may be as much work as building our own generator unless the layer also solves refs, inline extraction, and allOf flattening.

Recommendation

Do a short spike, but bias toward building an AdCP-specific generator unless one candidate clears the acceptance tests below. The generator should be deterministic, repo-local, and generate protocol data shapes only. Response builders and SDK convenience APIs can remain hand-written.

Acceptance criteria

A replacement generator must:

  • resolve AdCP $id / $ref paths without filesystem hacks
  • emit named structs for inline object schemas using stable, predictable names
  • emit typed Go representations for oneOf response variants, preferably sealed interfaces plus variant structs for public SDK APIs
  • merge or represent allOf without dropping fields or creating nested shapes that do not match the wire
  • preserve nil-vs-zero semantics for optional booleans/numbers
  • generate enums with stable names and values
  • preserve additionalProperties through Extra map[string]any where appropriate
  • support schema pointers in drift lint, e.g. get-media-buys-response.json#/properties/media_buys/items
  • produce stable gofmt'd output with low diff churn
  • add golden tests for the known hard cases: create media buy response, get media buys response/package rows, delivery response/package delivery, sync creatives assignments, pricing option/deployment/format oneOfs, governance plan inline arrays
  • avoid adding runtime dependencies to the root module; generator-only dependencies are acceptable if pinned and isolated under adcp/schemas tooling

First implementation path if we build

  1. Add a schema loader that indexes every schema by $id, relative path, and local file path.
  2. Build an intermediate representation with resolved refs and explicit schema pointers.
  3. Extract inline object schemas into named Go types using path-based names plus an override table.
  4. Implement allOf object merge for AdCP's common composition patterns.
  5. Implement oneOf strategies:
    • discriminated const/status branches -> sealed interface + variants
    • structural object variants -> generated wrapper with custom marshal/unmarshal, or explicit override
    • ambiguous/primitive variants -> documented any fallback with lint warning
  6. Replace KNOWN_TYPES / EXEMPT with a typed override config that records why a type is hand-written and which schema pointer it must drift-check against.
  7. Port one area first: media-buy response/request types.

Related follow-up from PR #142 expert pass

MIGRATING.md should explicitly document that Config.GetMediaBuys now returns *GetMediaBuysResponse instead of []MediaBuyData.

Metadata

Metadata

Assignees

No one assigned

    Labels

    claude-triagedenhancementNew feature or requestgo-first-classWork required to make adcp-go a first-class SDK and seller frameworksdkSDK public API, framework, or developer experience work

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions