Repository navigation
feat(adcp/v3): generate Optional[T] for nullable request fields - #605
torbenbrodt wants to merge 1 commit into
Conversation
A request property whose schema allows null means three things on the wire: omitted (keep the stored value), null (clear it) and a value (replace it). A pointer or `any` field decodes null like omission and drops the key on encode, so `update_media_buy` could not clear a cap. The generator now emits adcp.Optional[T], tagged `omitzero` and never `omitempty`, for a property that has a null branch after $ref resolution (a type list with "null", a null anyOf/oneOf branch, a null enum value or const), is not required, and sits in a struct that a `*-request.json` tool schema reaches and no response or webhook schema reaches. T is what the generator emitted for the property without the pointer, so the union fields that fall back to `any` become Optional[any] and the any-coverage gate keeps tracking them with their reviewed reasons. Fields that change type in types_gen.go: - UpdateMediaBuyRequest: daily_budget_cap, budget_cap_timezone, frequency_cap, bidding - PackageUpdate: budget, min_spend_target, daily_budget_cap, bidding - ControlMediaBuyRequest: daily_budget_cap, budget_cap_timezone, frequency_cap, bidding - PackageControl: budget, min_spend_target, daily_budget_cap, bidding The tools/list input schema for update_media_buy is byte-identical before and after. control_media_buy now advertises its five typed properties, where it advertised nothing for the former `any` fields. lint.py checks the same rule: a non-required request property with a null branch must be Optional[...] in Go, or its nulls must sit below a reviewed untyped parent (comply_test_controller params, CanonicalProposal.forecast, and targeting_overlay until it is typed). Plan.ApprovedSellers is not covered yet; Plan stays exempt. This is a source-incompatible change for callers that set or read these fields; MIGRATING.md has the before and after code. An explicit null is now kept on the wire where it was dropped before. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
|
@torbenbrodt Thanks for the slice. I checked the 16 fields against the pinned 3.2.2 request schemas with my own script and they match. I read the code and did not run the generator, lint or tests.
|
Slice B of the three-state request-field design on #536 (slice A,
Optional[T], merged as #595). #536 closed when 3.3.0-beta.0 merged; that was a side effect of #595's commit footer, not a decision, and B and C are still open.Opens as a draft: it changes the Go type of released fields (escalation category 1 in
AGENTS.md), so it waits for a maintainer's call rather than a review window. The question is under "Breaking" below.What changes
The generator now emits
Optional[T]taggedomitzerofor every non-required request property whose schema has a null branch (typewith"null", ananyOf/oneOfnull branch, or a nullenum/const), in structs reachable from a tool request schema and not shared with any response or webhook schema. Sixteen fields change:UpdateMediaBuyRequestdaily_budget_cap,budget_cap_timezone,frequency_cap,biddingPackageUpdatebudget,min_spend_target,daily_budget_cap,biddingControlMediaBuyRequestdaily_budget_cap,budget_cap_timezone,frequency_cap,biddingPackageControlbudget,min_spend_target,daily_budget_cap,biddingEach schema says null removes the value ("null removes it"). Today a pointer or
anyfield decodesnulllike omission, so a seller can't tell "clear the cap" from "leave it", and re-encoding drops the key.biddingandfrequency_capbecomeOptional[any]and keep their reviewed-any reasons in the coverage gate.tools/list:update_media_buyandcreate_media_buyadvertise byte-identical schemas (a test pinsupdate_media_buyagainst a reflected twin of the old types).control_media_buynow advertises five properties it didn't before, becauseanyfields aren't advertised:daily_budget_cap,budget_cap_timezoneand the packagebudget,daily_budget_cap,min_spend_target, typed["null","number"]or["null","string"]as in the schema. Additive.lint.pyreports a non-required nullable request property that isn'tOptionalin a request-only struct, generated or paired hand-written, and an untyped field hiding nullable properties unless it is on a reviewed list. The list is exactly what lint finds today: test-controllerparams,CanonicalProposal.forecast, andtargeting_overlayonPackageInput,PackageUpdate,PackageControlandProductPurchaseInput(slice C types those). A stale entry fails lint.Plan.approved_sellers(hand-written; fix(adcp/v3): keep an empty approved_sellers list on the wire #594 changes it first) andCreativeInputlocalization(no Go field until feat(adcp/v3): add revision_id and localization to CreativeInput #581).null, a value and a zero value round-trip for all 16 fields; anupdate_media_buycall over MCP, throughAddToolandWithIdempotency, reaches the handler asIsNull()at media-buy and package level and asSome(0)for a zero; every generatedOptionalfield is taggedomitzeroand neveromitempty; generator and lint tests for the rule, the reachability scan (including webhooks) and the reviewed list.MIGRATING.mdgets a section with all 16 fields at 3.2.1, 3.3.0-beta.0 and now, and before/after code;docs/sdk-typing-policy.mddescribes the rule.Breaking
Source-incompatible for callers of these fields: 7 were released in 3.2.1 (
*float64,*stringorany), and 9 first shipped asanyin 3.3.0-beta.0.adcp.Float64(5)becomesadcp.Some(5.0),x.Budget != nilbecomesx.Budget.Get(), and clearing isadcp.Null[float64]()instead ofClearField().MIGRATING.mdhas the table. The wire doesn't change: the fields and "null removes it" are already in the pinned schemas.@bokelley, should this land on
mainon its own, or would you rather fold it into #602? #602 regeneratestypes_gen.goanyway, so whichever lands second only reruns the generator; say the word and I'll retarget it.reference/seller-agent(pinned to v3.0.0) usesupd.Budget != niland will need the same change when it moves to the in-repo module (check 6 of #564).Checks run locally
generate.pyreproducestypes_gen.go;lint.py --strict;--coverage-max-unreviewed-any 0; Python generator and lint tests;go test -race ./...inadcp/v3on Go 1.26 and 1.27;go vet;golangci-lint.@garvitkaushik-123 @sujanchalla0510, a review is welcome while the ruling is open.
🤖 Generated with Claude Code