Add multipart/related support for file uploads with metadata#1241
Open
lanej wants to merge 3 commits intooxidecomputer:mainfrom
Open
Add multipart/related support for file uploads with metadata#1241lanej wants to merge 3 commits intooxidecomputer:mainfrom
lanej wants to merge 3 commits intooxidecomputer:mainfrom
Conversation
6f04ec1 to
ab478f1
Compare
ab478f1 to
af4e9af
Compare
Add runtime support for RFC 2387 multipart/related requests to enable Google Workspace API uploads that combine JSON metadata with binary content in a single request. - Add MultipartPart struct with zero-copy semantics using Cow<'a, [u8]> to avoid doubling memory usage for large files - Add MultipartRelatedBody trait for types that can be serialized as RFC 2387 bodies - Add RequestBuilderExt::multipart_related() to apply multipart/related body to requests Security features: - RFC 2045 parameter quoting for content-type values with special chars - Content-ID validation to prevent header injection attacks - Empty parts validation (RFC 2387 requires at least one part) Performance optimizations: - Buffer preallocation based on estimated message size - Unique boundary generation using timestamp (nanos) + PID + counter - Zero-copy for binary fields via Cow::Borrowed RFC 2387 compliance: - Type parameter derived from first part's content-type (root part) - Proper Content-Type and Content-ID headers for each part - CRLF line endings throughout - Proper boundary markers Fixes oxidecomputer#1240
Add code generation for multipart/related request bodies from OpenAPI schemas. Supports both query parameter defaults (separate feature) and multipart/related (issue oxidecomputer#1240). Multipart/related support: - Detect x-rust-type: multipart/related in schemas - Generate {field}_content_type fields for binary fields - Preserve property order from schema (RFC 2387 Section 3.2) - Handle required vs optional fields correctly - Generate builder methods with (value, content_type) parameters - Use crate::progenitor_client:: for module resolution - Add serde_json dependency when needed - Blanket impl for &T allows .multipart_related(&body) Query parameter defaults support (separate feature): - Respect OpenAPI default values for optional parameters - Extract default from schema and pass to builder - Update builder methods to use defaults Generated MultipartRelatedBody impls: - JSON fields serialized via serde_json::to_vec as Cow::Owned - Binary fields as Cow::Borrowed (zero-copy) - Optional fields checked for Some and non-empty - Parts ordered according to schema property order For oxidecomputer#1240
Add test coverage for both multipart/related and query param defaults. Multipart/related tests: - OpenAPI spec with multipart/related endpoints - Test scenarios: builder, positional, tagged - Single file upload (required fields) - Multiple file upload (mixed required/optional) - Simple upload (raw body, no schema) - Verify generated code compiles Query param defaults tests: - OpenAPI spec with default values - Test scenarios across all generation styles - Verify defaults are respected in builder All generated code successfully compiles and passes tests. For oxidecomputer#1240
4faa1e0 to
12e18f1
Compare
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
Adds RFC 2387 multipart/related support for uploading files with metadata in a single request.
Fixes #1240
Background
Per RFC 2387, multipart/related provides a mechanism for representing compound objects that are aggregates of related MIME body parts. This is commonly used by APIs like Google Drive to upload a file and its metadata in a single HTTP request.
Key RFC 2387 Requirements
Part Ordering: Section 3.2 specifies that "If not present the 'root' is the first body part in the Multipart/Related entity." This implementation preserves the OpenAPI schema property order to ensure the root part (typically JSON metadata) appears first.
Content-Type: Section 3.1 requires that each part have an appropriate Content-Type header. Many APIs reject the generic
application/octet-streamand require the actual MIME type (e.g.,text/csv,image/png).Type Parameter: The multipart/related Content-Type header's
typeparameter must specify the MIME type of the root part (Section 3.2).Changes
Runtime Support (progenitor-client)
MultipartRelatedBodytrait for types that can be serialized as multipart/relatedMultipartPartstruct with dynamiccontent_type: Stringfieldtypeparameter derived from first part's content-typeCode Generation (progenitor-impl)
Option<String>for content_type and are excluded from the body when not providedRelated Improvements
API Example
Before (not possible):
// multipart/related was not supportedAfter:
Optional fields (automatically excluded when not provided):
Generated Code
Struct with content_type field:
MultipartRelatedBody implementation respects schema order and skips optional fields:
Design Decisions
Why require content_type at compile time?
Making content_type a required parameter (not defaulting to
application/octet-stream) prevents silent bugs. APIs like Google Drive rejectapplication/octet-streamwith a 400 error, so forcing users to specify the MIME type makes the API safer.Why preserve schema order?
RFC 2387 Section 3.2 specifies that the first part is the "root" by default. Many APIs expect metadata (JSON) before file content. Alphabetical sorting would reverse this (file < metadata), breaking these APIs.
Why Option for optional content_type?
Optional binary fields in the OpenAPI schema should not require a content-type if the field isn't being used. Typify generates
Vec<u8>with#[serde(default)]for optional binary fields, so we check both content_type presence and non-empty bytes before including the part.Why derive type parameter dynamically?
RFC 2387 Section 3.2 requires the
typeparameter to specify the content-type of the root (first) part. Hardcodingapplication/jsonwould be incorrect for binary-only multipart bodies.Testing
query-param-defaultsvalidates default value handlingBreaking Changes
None - this feature is new in this PR.