Repository navigation
Apply the type-evolution policy: non_exhaustive on what HEY answers - #166
Conversation
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
c4ffcf2 to
c5e24a5
Compare
There was a problem hiding this comment.
🟢 Approval recommended
The classification matches all 51 request-side and 86 response-side generated structs, with focused generator and migration coverage.
Pull request overview
Applies the Rust API-evolution policy established in #163 by making response types and open enums non-exhaustive while preserving request-type construction.
[!TIP]
If you aren't ready for review, convert to a draft PR.
Click "Convert to draft" or rungh pr ready --undo.
Click "Ready for review" or rungh pr readyto reengage.
Changes:
- Classifies generated schemas by request-body reachability.
- Marks generated and handwritten response types and open enums
#[non_exhaustive]. - Updates tests and migration documentation for the resulting API changes.
File summaries
| File | Description |
|---|---|
AGENTS.md |
Clarifies route metadata evolution policy. |
MIGRATING.md |
Documents required consumer migrations. |
rust/generator/src/emit/types.rs |
Emits the non-exhaustive attribute. |
rust/generator/src/fixtures.rs |
Adds end-to-end generation coverage. |
rust/generator/src/model.rs |
Classifies request-side schema reachability. |
rust/hey-sdk/README.md |
Updates public versioning policy. |
rust/hey-sdk/src/client.rs |
Marks raw responses non-exhaustive. |
rust/hey-sdk/src/error.rs |
Opens the error-code enum. |
rust/hey-sdk/src/form.rs |
Marks form responses non-exhaustive. |
rust/hey-sdk/src/generated/types.rs |
Regenerates response-side types. |
rust/hey-sdk/src/oauth.rs |
Marks OAuth tokens non-exhaustive. |
rust/hey-sdk/src/observability.rs |
Opens hook request/result types. |
rust/hey-sdk/src/route.rs |
Opens model-controlled route enums. |
rust/hey-sdk/src/services/boxes.rs |
Opens box kinds. |
rust/hey-sdk/src/services/calendar_changes.rs |
Protects change-feed result types. |
rust/hey-sdk/src/services/calendar_events.rs |
Opens recurrence/countdown enums. |
rust/hey-sdk/src/services/calendars.rs |
Protects calendar list results. |
rust/hey-sdk/src/services/contacts.rs |
Protects conflict details. |
rust/hey-sdk/src/services/extenzions.rs |
Protects extension results. |
rust/hey-sdk/src/services/identity.rs |
Opens time formats. |
rust/hey-sdk/src/services/postings.rs |
Opens posting result types and slots. |
rust/hey-sdk/src/services/search.rs |
Protects search results. |
rust/hey-sdk/src/services/stickies.rs |
Opens sticky sizes. |
rust/hey-sdk/src/services/workflows.rs |
Protects workflow summaries. |
rust/hey-sdk/src/url.rs |
Protects recognized route matches. |
rust/hey-sdk/tests/form.rs |
Tests form responses through actual requests. |
rust/hey-sdk/tests/recordings.rs |
Migrates response construction to mutation. |
rust/hey-sdk/tests/services_extenzions.rs |
Avoids literal response construction. |
rust/hey-sdk/tests/services_workflows.rs |
Compares workflow result fields. |
Review details
- Files reviewed: 26/27 changed files
- Comments generated: 0
- Review effort level: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
|
Adversarial review (Codex CLI, gpt-6-astra at xhigh, read-only) over the hand-written diff: one finding, folded in 0784ea5. MIGRATING advised |
0784ea5 to
57c6b22
Compare
Every type the crate decodes and hands back, and every enum whose values are HEY's to grow, is #[non_exhaustive]; what a caller sends stays literal. The generator decides by reachability from the request bodies — a schema a body mentions however deep is the caller's to build, the rest is read back and only read — so a field the model adds to a response is an addition and one it adds to a request is a break, as the README says. The hand-written side follows the same line: the route table, the client and form responses, what the hooks report, the OAuth token, the parsed results in the services, and the open enums. OperationInfo, the configs, the OAuth requests, the cursors and the closed enums stay constructible. Tests that built a response by hand now read one back or compare fields. MIGRATING.md says what changes for code outside the crate.
57c6b22 to
e8c02c1
Compare
|
Rebased onto #163 at e42e451 (which is On the Gate at e8c02c1: |
Since v0.30.0 the Rust crate's response types and enums are #[non_exhaustive] (#166, breaking), the Rust client honors each route's retry policy and the Go client holds its retry settings as a ceiling over it (#154, #164), operations carry tracing spans behind a feature (#165), a workflow stage is read out of the page HEY serves (#169), and the crate publishes to crates.io on the tag (#162). cargo semver-checks against v0.30.0 asks for the minor. The bump script still looked for the README dependency line #160 replaced. The READMEs now show the crates.io requirement, which names the minor, and the git dependency pinned to the release tag, which names the whole version; the script moves both and checks each.
Tracking card: hey-sdk Rust: public API governance. Second of two, stacked on #163 (base is its branch; GitHub retargets to
mainwhen it merges). Merge order: #163, then this PR. It depends on #163 for the lint table the tests now satisfy and the policy text it applies; both are kept rebased onmainand converged together. This is the decision that has to land before the first crates.io publish: adding#[non_exhaustive]later is itself a breaking change.What is
#[non_exhaustive]now, and why. The rule from #163, applied: what the crate decodes and hands back is read, never built; what a caller sends stays literal.rust/generator/src/model.rsmark_request_side,emit/types.rs): the generator walks out from every request body to everything it mentions, however deep, and marks those schemas request-side; every other struct schema is emitted#[non_exhaustive]. On today's model that is 51 request-side structs (every*RequestContentand the payloads they carry —MessagePayload,ContactPayload,StickyPayload, …) and 86 response-side ones (Mailbox,Posting,Topic,Contact,Calendar, the*ResponseContentenvelopes, the error bodies, …); no schema is on both sides. The*Paramsstructs are untouched. Tests:model.rs(a body, what it mentions through a list, and a shape shared with a response all stay request-side; the rest do not),emit/types.rs(the attribute is emitted for one and not the other), and a generator fixture running the whole thing end to end.WorkflowSummary,CalendarChanges,RecordingChanges,DeletedRecording,DeletedCalendar,PostingChanges,SearchResults,ContactConflict,ListedCalendar,CalendarList,services::extenzions::Extenzion,url::Match.client::Response,FormResponse,oauth::Token, and what the hooks hand back —RequestInfo,RequestResult.ErrorCode,route::Pagination,route::ParamKind,BoxKind,StickySize,BubbleUpSlot,RepeatFrequency,CountdownUnit,TimeFormat.Left constructible, on purpose:
OperationInfo(Operation::infotakes one from a caller describing its own form-backed call);Route,RouteParam,Retry(a caller may write a route for a path the model lacks and hand it toClient::operation— #154'stests/retry_policy.rsdoes exactly that, which is how this line got drawn);Config, the resilience configs,ExchangeRequest,RefreshRequest,ServerMetadata,Pkce,CachedResponse, the*Cursorstructs; and the closed enumsClearanceStatus,OccurrenceScope,RepeatUntil,ParamRole. A field added to any of those is a0.MINOR.What is and isn't breaking afterward. A field added to a response-side type or a variant added to an open enum is a
0.x.PATCH, provided what HEY already sends still decodes (a required field with no decoding default is not additive). A field added to a request-side type is a0.MINOR.cargo semver-checksagainst #163's head reports exactly the two checks this change is:struct_marked_non_exhaustiveandenum_marked_non_exhaustive, nothing else.Migration (
MIGRATING.md, new at the repo root, Rust section for the first release after 0.30.0): literal construction of a response-side type stops compiling,..Default::default()included — build withT::default()and set fields; amatchover an open enum needs a_arm; reading, methods,Clone,PartialEq, serde are untouched. The crate's own tests show the shape:tests/recordings.rsbuilds aRecordingthroughdefault();tests/form.rsreads aFormResponseback from a mock instead of building one;tests/services_extenzions.rsandtests/services_workflows.rscompare fields.Enums from the model. None today (
openapi.jsondeclares noenum), so the generator invents none; theUnknown(String)shape is policy in #163 and lands as emit the day the model declares one.The
breakinglabel, and why it is the instrument.api-compat-rustrunscargo semver-checksagainst the base commit and fails on any major-class change; itsif:skips the job when the pull request carries thebreakinglabel, the same escape hatch the Go job has, and the crate's own policy text names for a pre-1.0 break (README, Versioning). The label is on. The alternative the job's comment offers — bumpingCargo.tomlto0.31.0in the PR so the checker judges it a permitted major — is wrong here: the Go module and the Rust crate share one version and one tag, and the version moves when a release is cut, not inside a feature PR. The break itself is intended and is the whole point: pre-1.0,0.30 → 0.31is the major, and#[non_exhaustive]has to be on the response types before the first crates.io publish, because adding it afterward is itself the same break against a public baseline.cargo semver-checks --baseline-rev <#163 head>on the current head reports exactlystruct_marked_non_exhaustiveandenum_marked_non_exhaustive, nothing else, so the label excuses only what this PR sets out to do.mainis not branch-protected, so a skipped job holds nothing up.Local gate on Rust 1.98.1, at the current head (rebased with #163 onto
mainafter #165 and #170):make -C rust check(fmt, clippy--all-targets -D warnings, test — 48 suites, doc-D warnings,--no-default-features, examples, deny, publish dry run) exit 0,rs-check-driftexit 0,conformance-rs195/195;cargo check -p hey-sdkon 1.88.0 with--all-featuresand--no-default-featuresboth build;cargo semver-checksas above.Review. Adversarial Codex CLI pass done (gpt-6-astra at xhigh, read-only) over the hand-written diff: one finding, folded in (the fixture test says where a result without
Defaultcomes from). Copilot recommended approval; no inline threads. Babysitting to convergence.