fix(spec): resolve composed object/array body field types (WBC-43)#31
Merged
Conversation
FastAPI/Pydantic emits optional object fields as
`anyOf: [{$ref -> object}, {type: "null"}]`, leaving the wrapper schema
with no direct type or properties. resolveSchemaType only inspected the
top-level type, properties, and items, so these fields fell through to
"string". The generated `api` command then registered a plain scalar flag
(e.g. --runpython) that serialized the raw value as a JSON string,
producing `"runPython":"s3://..."` and an HTTP 422 from POST /runs, which
requires an object `{"uri": "..."}`.
Recurse into the allOf/anyOf/oneOf composition keywords and return the
first member that resolves to a concrete (non-null) type, with a depth
bound to guard against circular refs. The recursive worker returns ""
(not "string") for an unresolved schema, so a genuine string alternative
in a polymorphic union is preferred over a later, stricter type rather
than skipped. Object/array fields are now correctly typed, so the builder
generates the existing JSON flag form (--runpython-json) that accepts and
emits the object body. This fixes runPython, runJar, and every other
composed object/array body field uniformly.
Fixes WBC-43.
sfishel18
approved these changes
Jun 12, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes WBC-43:
wherobots api runs create-job-run --runpython <s3-uri>sentrunPythonas a JSON string, butPOST /runsrequires an object ({"uri": "..."}), so every call failed with HTTP 422. The same shape issue affected--runjarand any other object/array body field.Root cause
The
apicommand tree is generated at runtime from the Wherobots OpenAPI spec.resolveSchemaTypeininternal/spec/parser.goonly inspected a schema's directtype,properties, anditems. FastAPI/Pydantic encodes an optional object field as:The wrapper schema has no direct type or properties, so resolution fell through to
"string". The builder then registered a plain scalar flag (--runpython) that serialized the raw value viasjson.Set, producing"runPython":"s3://..."→ 422.(A bare
$ref— used for required object fields — already resolved correctly; only the composition keywords were missed.)Fix
Recurse into the
allOf/anyOf/oneOfcomposition keywords and return the first member that resolves to a concrete (non-null) type, bounded by a recursion depth guard against circular$refs. The recursive worker returns""(not"string") for an unresolved schema, so a genuinestringalternative in a polymorphic union is preferred over a later, stricter type rather than skipped.Object/array fields are now typed correctly, so the builder generates the existing JSON flag form (
--runpython-json) that validates and emits the object body. This fixesrunPython,runJar, and every other composed object/array body field uniformly — no field-specific special-casing.Design note
I took the principled, generic fix (correct type resolution) rather than teaching the generic
apicommand to wrap a bare string into{"uri": <value>}+ add--runpython-args. That ergonomic shortcut belongs to — and already exists in — the curatedwherobots job-runs createcommand. The genericapisurface stays faithful to the spec, consistent with its existing contract ("Object and array values must be JSON strings") and the pre-existing--metadata-jsonprecedent.Usage after fix
Commits
fix(spec): resolve composed object/array body field typesTesting
internal/spec/parser_test.go:TestParseResolvesComposedObjectBodyFields—anyOf[$ref,null], bare$ref, andanyOf[array,null]all resolve to object/array (the WBC-43 regression guard).TestParseScalarUnionPrefersFirstConcreteType—str | intunion resolves tostring, not the stricter alternative.internal/commands/builder_test.go:TestObjectBodyFieldSerializesAsJSONObject— end-to-end--dry-runconfirms an object body field serializes as"runPython":{"uri":"..."}.go test ./...,go vet ./...,gofmt, andpre-commit run --all-filesall pass.🤖 Generated with Claude Code