feat(storage): OpenAPI codegen tool + generated Storage HTTP client#1099
Draft
grdsdev wants to merge 44 commits into
Draft
feat(storage): OpenAPI codegen tool + generated Storage HTTP client#1099grdsdev wants to merge 44 commits into
grdsdev wants to merge 44 commits into
Conversation
Adds parseResponses/parseResponseBody (OpenAPI response -> IRResponse, skipping default/range status codes) and parseOperations (walks every path/method into IROperation). parseDocument ties schema and operation parsing together into the single IRDocument entry point the emitter and CLI will consume.
Adds SwiftEmitter.emitClient(_:clientName:), which turns IRDocument operations into a Swift client struct with one async throws method per operation: HTTPRequestBuilder construction with path/query/header parameters, JSON and multipart request bodies, JSON and binary response bodies, and typed per-status-code error decoding via HTTPResponse.checkStatus(errorTypes:).
Reads a spec file, runs it through OpenAPIParsing.parseDocument and SwiftEmitter.emitModels/emitClient, and writes Models.swift and <Module>Client.swift to the output directory. Marks OpenAPIParsing, SwiftEmitter, and the IR types public so the executable target (a separate module from OpenAPICodegenCore) can call them.
Replace multiple .contains(...) substring checks in ModelEmitterTests, ClientEmitterTests, and EndToEndTests with assertInlineSnapshot, so a single snapshot catches unintended changes anywhere in the generated Swift output instead of only the specific substrings a .contains check happened to look for.
…laky snapshot tests parseObjectProperties iterated objectContext.properties (an OpenAPIKit OrderedDictionary populated via JSONDecoder's container.allKeys) without sorting. Decodable's keyed-container key order isn't guaranteed stable across decode calls, so the emitted struct's property/CodingKeys order could differ run to run, causing EndToEndTests to flake (confirmed: 1 failure in 5 consecutive runs, id/public swapped). Sort by property name before iterating, matching the sort-for-determinism pattern already used elsewhere in this parser (parseNamedSchema/parseDocument's schema sort, parseOperations's operation sort, parseResponses's status-code sort).
GET /metrics (Storage's Prometheus scrape endpoint) has no operationId. It isn't part of the typed API surface anyone calls from Swift, so skip such operations rather than failing the whole parse.
Runs tools/openapi-codegen against openapi/storage.json and wires the output into the main package as an internal StorageOpenAPI target (not yet a public product, not yet consumed by Storage). Fixes surfaced by the first real build of generated output: - SwiftEmitter didn't mark import Foundation/HTTPRuntime as `public import`, so InternalImportsByDefault rejected the public APIError conformance and the client's public URL-typed init. - stringConversionExpression used String.init for enum-backed query/ header params, which is ambiguous for RawRepresentable<String> types; now emits .rawValue for schema-ref parameters. - errorTypesLiteral emitted `[]` for operations with no error responses, which Swift can't infer as [Int: any APIError.Type]; now emits `[:]`. - HTTPRuntime.HTTPMethod was missing .options/.trace, needed by Storage's tusOptions* operations. Also tightens two ExistentialAny warnings in emitted Codable conformances (any Decoder/any Encoder) and adds emitter test coverage for the enum-backed query parameter case.
Adds tests for StorageOpenAPIClient using a fake HTTPTransport: JSON success decoding (bucketGet), typed-error decoding via the real 403 error mapping (ErrorSchema), unmapped-status fallback (HTTPError), and the current bodyless behavior of objectUpload (multipart/file request bodies are not wired up yet in the generated client).
Coverage Report for CI Build 29018885975Coverage decreased (-5.2%) to 77.87%Details
Uncovered Changes
Coverage Regressions6 previously-covered lines in 2 files lost coverage.
Coverage Stats
💛 - Coveralls |
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.
…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.
…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.
…/Generated Drop the separate StorageOpenAPI SPM target and generate the client directly into Sources/Storage/Generated with internal access control, so the generated types share a module with the hand-written Storage API. Fixes a same-module HTTPRuntime/Helpers type name collision (HTTPRequest/HTTPResponse/HTTPError) surfaced by the move in the 3 relocated test files by qualifying with the HTTPRuntime module name.
Starts wiring StorageBucketApi's read methods to the generated StorageGeneratedClient instead of hand-rolled requests, with a backward-compatible error translation layer (ErrorSchema/HTTPError -> StorageError). Fixes along the way: - StorageApi's generatedClient transport now merges this instance's dynamic headers (apikey/Authorization/custom setHeader values) into every request via a new HeaderInjectingTransport wrapper - the generated client builds requests with empty headers otherwise, which was sending unauthenticated requests. - Updated the remaining StorageHTTPSession(fetch:upload:) call sites (tests) for the uploadFromFile/bytes parameters added earlier. - Renamed StorageOpenAPIClient -> StorageGeneratedClient in the 3 moved test files to match the regenerated client's actual name.
Contributor
|
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
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