Skip to content

feat(api-sync): auto-generate SDK methods for standard new operations - #40

Merged
ericviana merged 1 commit into
mainfrom
eric/operation-scaffolding
Aug 4, 2026
Merged

ericviana merged 1 commit into
mainfrom
eric/operation-scaffolding

Conversation

@ericviana

Copy link
Copy Markdown
Member

Summary

Extends scripts/api-sync.php with a new operation-insert change kind so that a spec operation with no matching SDK method no longer always requires a human. Every operation newly present relative to .api-sync/spec-snapshot.json is classified:

  • STANDARD (auto-generated): JSON request/response (or no body), maps to an existing resource by tag (resourceForTag()), and the body shape is a flat object of scalar properties (this generator version's scope). A method, plus any needed input/response classes, is synthesized and spliced into the mapped resource file; the SDK's minor version is bumped the same way an enum addition is.
  • NON-STANDARD (still needs-human, but with a specific reason instead of a generic one): multipart/form-data or other non-JSON body, no matching resource for the operation's tag, or a polymorphic/composed (oneOf/anyOf/allOf) or enum/nested-object-bearing schema this generator version doesn't yet synthesize.

Key pieces (all in scripts/api-sync.php):

  • resourceForTag() -- best-effort tag -> resource-file/class table (also backs the fixture-based test, see below).
  • classifyNewOperation() / nonStandardBodyReason() -- the classification boundary.
  • reconcileOperations() -- old-vs-new diff (same event-based mechanism the rest of computeStructuralDiff() uses), with a defensive per-run dedup guard mirroring the existing $scheduledInsertions/$scheduledCases pattern.
  • synthesizeInputClassLines() / synthesizeResponseClassLines() / buildOperationMethodLines() -- code generation, reusing phpTypeFor()/snakeToCamel() and this SDK's documented conventions (ID-empty validation, required-before-optional ctor params, literal+conditional toArray()).
  • findClassBoundaries() -- a dedicated, self-contained line counter for splicing new classes/methods into a resource file. Along the way this uncovered a real bug: PHP tokenizes the { of a "{$expr}" string interpolation as T_CURLY_OPEN (no matching plain '}' token on the close side), which silently unbalances a naive brace counter on every interpolated path string -- i.e. every resource method in this SDK. Fixed by treating T_CURLY_OPEN as an opening brace.
  • computeStructuralDiff() no longer unconditionally reports "new operation" -- that's now reconcileOperations()'s job.
  • computeBump() treats operation-insert the same as enum-member-added (minor bump), wired into the existing single --apply path.

Golden self-test

Added to tests/ApiSync/ApiSyncTest.php, using this repo's existing fixture-based testing convention (a small scratch repo shaped like the real one) rather than mutating the real cloned SDK: the real spec's simplest candidate resources (e.g. PartnerFees) turned out to have pre-existing field-level drift between the committed spec and the hand-written SDK (unrelated to this PR), which would make a real-repo byte-level round-trip assertion meaningless. The fixture keeps the test deterministic and focused on the mechanism itself:

  • buildGoldenRepo() builds a scratch repo with a hand-written Widgets resource (a get(string $id) and a create(CreateWidgetInput $input) method, matching a spec that already has both operations) plus the full .api-sync/*.json set.
  • The golden test deletes both methods and their three generated classes, runs --apply, and asserts: the regenerated code matches the deleted originals' route, HTTP verb, and parameter/return-type shape (checked structurally, not by exact class name -- the generator derives names from the method name); a second --apply is byte-identical (idempotent, no double-insert); and a synthetic multipart operation on the same tag correctly lands in needs-human with the precise reason.
  • A second test confirms --check is CI-red while the operation-insert is pending/unapplied, and clean once applied.

Test plan

  • composer test (pest) -- 50 passed, including the new golden self-test
  • vendor/bin/pint --test on both changed files -- passed
  • php scripts/api-sync.php --check --spec=<current public spec> against this repo's real, unmodified state -- exit 0 (no regression on the real spec/SDK)
  • composer contract-check -- unaffected, still OK

https://claude.ai/code/session_01F1stiNzuNtJXoXtiW9ZCbs

Adds an "operation-insert" change kind to the patcher: a spec operation with
no matching SDK method is now classified STANDARD (JSON body/response,
mapped to an existing resource by tag, flat scalar object shape) or
NON-STANDARD (multipart/form-data, no matching resource, polymorphic
schemas, etc), with a specific needs-human reason for the latter.
STANDARD operations get a method, and any needed input/response classes,
generated and spliced into the mapped resource file automatically, and bump
the SDK's minor version the same way an enum addition does.

Fixes a latent brace-counting bug (string interpolation's T_CURLY_OPEN token
has no matching plain '}' token) uncovered while writing the class-boundary
finder this needed.

Claude-Session: https://claude.ai/code/session_01F1stiNzuNtJXoXtiW9ZCbs
@BernardoSM

Copy link
Copy Markdown
Contributor

✅ Snyk checks have passed. No issues have been found so far.

Status Scan Engine Critical High Medium Low Total (0)
✅ Code Security 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

@ericviana
ericviana merged commit fe9f20e into main Aug 4, 2026
6 checks passed
@ericviana
ericviana deleted the eric/operation-scaffolding branch August 4, 2026 14:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants