fixed how array fields are sent in multipart form POSTs - #249
Conversation
It’s not necessary to url_encode the field keys, and that breaks timestamp_granularities[], among other things
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9d94e79a2b
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
|
Thanks for reporting this and for working through the We’ve consolidated this fix with file-array and nested-object multipart handling in #348, implemented once in the shared serializer and covered by generated-model and wire-format tests. I’m closing this PR in favor of #348 so the remaining review and discussion have a single home. Really appreciate the contribution. |
## Summary - encode multipart arrays recursively as `field[]`, including both primitive and file arrays - flatten nested multipart objects with bracket notation, including arrays of nested objects - preserve scalar field names and streaming file behavior - escape both `name` and `filename` `Content-Disposition` parameters - keep the fix entirely in the openai-ruby runtime; no generated model, OpenAPI, or Castiron changes are needed ## Root cause The shared multipart writer repeated top-level primitive and file arrays using the scalar field name and serialized nested hashes as JSON parts. The API's form parser expects the same global brackets format used by openai-python and by the transformed OpenAPI examples. Before: - `timestamp_granularities=word` and `timestamp_granularities=segment` - duplicate scalar `image` file parts - one JSON `expires_after` part After: - `timestamp_granularities[]=word` and `timestamp_granularities[]=segment` - duplicate `image[]` file parts, while a single image remains `image` - `expires_after[anchor]=created_at` and `expires_after[seconds]=3600` The implementation replaces the old top-level array special case with one recursive field writer. It preserves insertion order, array order, duplicate values, and streaming file contents without materializing a flattened body. This consolidates and supersedes openai#249, openai#311, and openai#339. Fixes openai#201. Fixes openai#212. Fixes openai#253. ## Validation - `mise exec ruby@4.0.6 -- bundle exec ruby -Itest test/openai/internal/util_test.rb` — 45 runs, 178 assertions - `mise exec ruby@4.0.6 -- ./scripts/test` — 522 runs, 1,709 assertions - `mise exec ruby@4.0.6 -- bundle exec rake lint` — 2,596 files, no RuboCop offenses; Sorbet clean; 1,211 RBS files valid - `mise exec ruby@4.0.6 -- bundle exec rake build:gem` — built `openai-0.78.0.gem` - RBI and RBS formatting plus `git diff --check` - thermo-nuclear maintainability review and a separate correctness/readability/architecture/security/performance review, repeated after findings until clean
This is a naive approach to properly supporting array fields in multipart encoded POST requests.
The issue I was having was that this code was not correctly sending
timestamp_granularitiesto the server (see example):The server is expecting an array field, where the key is
timestamp_granularities[]. However, theurl_encodesanitization pass encodes the square brackets and the server doesn't decode it. Sanitization should be less aggressive, and only escapeLF,CR,", and\I believe.