feat(storage): OpenAPI codegen tool + generated Storage HTTP client - #1099
feat(storage): OpenAPI codegen tool + generated Storage HTTP client#1099grdsdev wants to merge 75 commits into
Conversation
Coverage Report for CI Build 30479285183Warning Build has drifted: This PR's base is out of sync with its target branch, so coverage data may include unrelated changes. Coverage decreased (-1.7%) to 82.12%Details
Uncovered Changes
Coverage Regressions350 previously-covered lines in 5 files lost coverage.
Coverage Stats
💛 - Coveralls |
…e deleted SSE parser after the package-scoping edit The manual public->package access-control pass left HTTPRuntime non-compiling: checkStatus still referenced HTTPResponse's now-removed bare status/isSuccess (now only on .head), MultipartFormData's new builder API members defaulted to internal instead of package, and ServerSentEvents.swift was deleted outright rather than rescoped, breaking SSE stream parsing entirely.
Follow-up to the HTTPRuntime target isolation: registers the test target in the shared Xcode scheme, adds its spell-check terms, and updates Package.resolved to drop dependencies no longer pulled in by this trimmed-down package graph.
…ase, add addHeader - HTTPTransport.send/stream now throw typed HTTPError instead of any Error; URLSessionTransport already only ever threw HTTPError.transport, so this just tightens the contract. Dropped the redundant `throws` on the private makeURLRequest helper, which never actually threw. - Removed HTTPBody.multipart: callers now build the multipart body themselves via MultipartFormData.buildToTempFile(), set Content-Type, and pass .file. Keeps HTTPTransport from needing to know about multipart assembly. - Added HTTPRequestBuilder.addHeader(_:value:), which merges into an existing header (joined with "; ") instead of replacing it, for repeated directives like Prefer. Both setHeader and addHeader resolve the target key case-insensitively so differently-cased calls merge into one header instead of creating duplicates.
Supabase's own clients don't need it; downstream consumers can parse the raw streamed chunks themselves if they want SSE framing.
…sed doc references - checkStatus now requires a catch-all APIError type instead of falling back to a bare unexpectedStatus(status:body:) case, and reports decode failures as unexpectedResponse(response:underlyingError:) with the full response attached. - Commented out the still-unused HTTPError.encoding case. - Dropped doc comments referencing the (now removed) OpenAPI codegen tool.
…uild - TransferProgress/ProgressHandler were left `public` when the rest of HTTPRuntime was scoped down to `package`; the capability-matrix CI check flagged them as new, unregistered public API. Scope them to `package` like everything else in this target. - FoundationNetworking (swift-corelibs-foundation on Linux) has no async byte-streaming API (`bytes(for:)`/`AsyncBytes`), so URLSessionTransport.stream() failed to compile on Linux. Branch on `canImport(FoundationNetworking)`: Linux buffers the full response via `data(for:)` and yields it as a single chunk instead of streaming incrementally; Apple platforms keep the existing incremental byte-stream implementation.
…RuntimeTestHelpers
…e and dictionary Adds HTTPRuntimeTestHelpersTests to the shared Xcode scheme in alphabetical order between HelpersTests and HTTPRuntimeTests. Includes dictionary.txt entries for Xcode-related terms and formatting fixes to HTTPRuntimeTestHelpers source files.
…rget-085a05 # Conflicts: # Package.resolved # Package.swift
…r directives
addHeader joined repeated directives with "; ", conflicting with this repo's
Prefer convention (HTTPFields.appendOrUpdate, PostgrestQueryBuilder/
PostgrestTransformBuilder all use ","), which would make two addHeader("Prefer", ...)
calls read as one directive-with-parameter instead of two. Also replace an
existing directive sharing the same key prefix instead of duplicating it,
mirroring appendOrUpdate.
Addresses review feedback from spydon on #1121.
docs/superpowers/ was already gitignored (deduped a redundant earlier entry too) but these two files were tracked before the rule existed.
Consolidates the inline-object-with-properties hoisting check that was hand-copied at three call sites (parseObjectProperties, parseRequestBody, parseResponseBody) into a single hoistInlineObjectIfPresent helper, matching the pattern already used for hoistUnionIfPresent and hoistArrayOfObjectIfPresent. parseParameter is intentionally left untouched since it doesn't support inline-object hoisting.
ArgumentParser's default kebab-casing of "OpenAPICodegen" produces "open-api-codegen", which doesn't match the actual executable name and would mislead anyone who copies the --help/error text verbatim.
…ipartFormData API tools/openapi-codegen's emitter still hardcoded `public` on every generated declaration and emitted the old MultipartFormData.Part/.append API, both of which no longer compile against HTTPRuntime's package-scoped types and rewritten builder-style MultipartFormData. Threads a new AccessLevel enum (default .internal) through every emitter helper and adds a --access-level CLI option; multipart request bodies now build via the .addFile/.addText fluent chain. Regenerates Sources/StorageOpenAPI with the new internal default.
2bab4f7 to
ae7aa4e
Compare
|
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
The following capabilities are marked
These may have been renamed, removed, or never registered. Please update the capability matrix. |
Generated clients call checkStatus(errorTypes:) with no catchAll for operations whose spec declares no error schema at all. Falls back to .unexpectedResponse instead of requiring every call site to supply a catch-all error type.
ae7aa4e to
df98ec1
Compare
…dAPI Adds scripts/generate-openapi-clients.sh to regenerate Sources/<Module>/Generated from openapi/*.json via tools/openapi-codegen. Generated declarations now nest under a per-module enum namespace (e.g. StorageBackendAPI) so schema-derived type names can never collide with hand-written public types, replacing the now-removed Sources/StorageOpenAPI target. Also fixes an emitted `var builder` never-mutated warning and makes the emitter indent nested output itself instead of relying on swift-format.
Adds storage.vectors.{createBucket,getBucket,listBuckets,deleteBucket}
for Supabase Storage's alpha "vector buckets" feature, mirroring
supabase-js's supabase.storage.vectors client. Indexes and vector data
operations (put/get/query/delete vectors) are out of scope for this
pass.
The vector bucket paths/schemas are merged into openapi/storage.json
from the real docs:export output of supabase/storage's
claude/reverent-ramanujan-32755f branch (supabase/storage#1215), which
adds response schemas for these 4 endpoints (previously only errors
were documented). Sources/Storage/Generated is regenerated from that
spec via tools/openapi-codegen.
Since the generated StorageBackendAPI.Client and its models are
module-internal by convention, a new StorageVectorsClient wraps it with
a public API: VectorBucket/ListVectorBucketsResponse replace the
internal generated schemas, and the internal ErrorSchema is translated
to the existing public StorageError. StorageSessionTransport bridges
StorageClientConfiguration's StorageHTTPSession onto HTTPRuntime's
HTTPTransport, so the generated client goes through the same session
(auth headers, test stubbing) as the rest of Storage.
The whole surface is gated behind @_spi(Experimental), matching the
precedent set by Auth's WebAuthn support, since this is an alpha
feature that may change in a breaking way before general availability.
Built on #1121/#1099 (the isolated HTTPRuntime target and
openapi-codegen tool) rather than main.
…48d88 # Conflicts: # Sources/HTTPRuntime/HTTPError.swift # Sources/HTTPRuntime/HTTPMethod.swift # Sources/HTTPRuntime/MultipartFormData.swift # Sources/HTTPRuntime/URLSessionTransport.swift # dictionary.txt
Adds openapi-codegen-test and openapi-codegen-regenerate-check to ci-success, skipped on PRs that don't touch tools/openapi-codegen, openapi/, or the generation script.
docs/superpowers/ is gitignored but these two predate the rule.
Was a single line; formatting it makes spec changes reviewable in PR diffs. No content change — regenerating the Storage client from this file produces byte-identical output.
Nothing in the codebase (generated or hand-written) ever passed it, so errorTypes lookups always fell through to unexpectedResponse regardless.
Updates openapi/storage.json: adds 5XX error responses across endpoints,
an error `code` field, vectorBucketSchema/getVectorBucketResponse, and -
notably - the multipart/form-data requestBody for object upload endpoints,
which the spec previously omitted entirely.
That last change exercises the codegen's multipart code path for the first
time, surfacing two bugs in SwiftEmitter fixed here: generated code
referenced the bare `MultipartFormData` name, which a target module can
shadow with its own same-named type (Storage already has one, a legacy
Alamofire-derived implementation) - qualified as HTTPRuntime.MultipartFormData.
Also HTTPBody dropped its .multipart case upstream; build the body to a
temp file and send it as .file(...), matching HTTPRuntime's current
streaming-upload API.
Regenerated Sources/Storage/Generated/{Models,Client}.swift accordingly.
The Storage backend now emits specs declaring newer OpenAPI versions
whose schemas use JSON Schema 2020-12 nullable idioms (`type: [X,
"null"]`, `anyOf: [X, {type: "null"}]`) that the 3.0-only OpenAPIKit30
parser can't decode at all.
Migrate openapi-codegen from OpenAPIKit30 to OpenAPIKit's unsuffixed
3.1/3.2 module (same vendored dependency, different product). Type
arrays and null-branch unions now resolve to plain nullable properties
natively; add parser handling so a null-collapsed anyOf/oneOf isn't
mistaken for a real tagged union. The remaining pre-decode
normalization step only drops anyOf/oneOf-of-required validation
constructs that OpenAPIKit has no representation for regardless of
spec version.
Sync openapi/storage.json from the backend's 3.2 export and
regenerate. The export doesn't include operationIds or named
component schemas yet, so the generated client is temporarily reduced
to 2 schemas and no operations — a backend follow-up will restore
full coverage.
…s OpenAPI spec Add openapi/functions.json (from supabase/edge-functions-ingress#459, bumped to 3.2) and wire Functions into the codegen module list. Also teach the codegen to model a `*/*` request body as raw Data/Content-Type instead of silently dropping it in favor of application/json, and fix HTTPError usage in the streaming-response error path (unexpectedStatus never existed).
…ck stream status - Invoke accepts GET/POST/PUT/PATCH/DELETE, not just POST; add sibling operations (invokeFunctionGet/Put/Patch/Delete) alongside invokeFunction. - Expose the binary request body as HTTPBody (not Data) so callers can pass .file(url) to stream an upload without buffering it into memory. - Route streaming responses through HTTPResponseStream.checkStatus so a failing stream decodes typed API errors the same way buffered responses do, instead of only ever surfacing a generic unexpectedResponse.
…I spec Keep FunctionsClient hand-written, on its existing Helpers-based transport. The codegen/runtime capabilities added along the way (HTTPBody request bodies, multi-method operations, HTTPResponseStream.checkStatus) stay, since they're general tool/runtime features, not specific to Functions.
Summary
tools/openapi-codegen, standalone SPM package, depends onOpenAPIKit30andswift-argument-parser) that parses an OpenAPI 3.0.3 document into an internal IR and emits Swift models + a client targeting a new zero-dependencyHTTPRuntime(copied from a prior spike intoSources/HTTPRuntime).openapi/storage.json, fixed for SDK generation by supabase/storage#1215) and the client generated from it (Sources/StorageOpenAPI/) — not yet wired into the publicStorageAPI. This proves the generator works end-to-end against a real spec; reconciling naming/shape with the hand-writtenStorageFileApi/StorageBucketApiand actually adopting the generated client is future work.docs/superpowers/specs/2026-07-08-storage-openapi-codegen-design.md. Implementation plan:docs/superpowers/plans/2026-07-08-storage-openapi-codegen.md.The original plan had 15 tasks. Running the generator against Storage's real spec repeatedly surfaced constructs the plan didn't anticipate — each was checked with the user before extending the generator's scope. That produced follow-up tasks hoisting inline enums and inline objects (in schema properties, parameters, request/response bodies, and array items) into named Swift types, supporting
oneOf/anyOfunions as tagged enums with hand-rolledCodable, treating typeless "fragment" schemas as freeform, skipping operations without anoperationId, and fixing two non-determinism bugs plus a naming bug and a missing Xcode scheme entry along the way. The generator now produces the full real spec deterministically: 71 schemas, 60 operations.A final whole-branch review (and further follow-ups) found and fixed: a third non-deterministic iteration (multipart fields), a broken emission path for array-typed query parameters, an emitter crash for union-typed parameters (now rejected at parse time — no well-defined query/header wire representation), consolidated the inline-object hoisting logic that had been hand-copied across four call sites into one shared helper, and replaced the CLI's hand-rolled argument parsing with
swift-argument-parser.Test plan
tools/openapi-codegen's own test suite: 51 tests, 10 suites, all passingxcodebuildtest suite for the main package (PLATFORM=IOS XCODEBUILD_ARGUMENT=test ./scripts/xcodebuild.sh), confirmedHTTPRuntimeTests/StorageOpenAPITestsactually execute (not just present inPackage.swift), zero regressions in Auth/Functions/Helpers/PostgREST/Realtime/Storage/Supabaseopenapi/storage.jsonwith the committed CLI produces byte-identical output to what's committedSources/StorageFileApi.swift/StorageBucketApi.swift/Types.swift/SupabaseStorage.swiftconfirmed untouched — the generated client is additive onlystringConversionExpressiongap for arrays of hoisted (non-scalar) types in parameter position — not exercised by Storage's current spec