Skip to content

Add support for binary payloads#55

Open
christianhelle wants to merge 15 commits into
mainfrom
support-binary-payloads-opus
Open

Add support for binary payloads#55
christianhelle wants to merge 15 commits into
mainfrom
support-binary-payloads-opus

Conversation

@christianhelle

@christianhelle christianhelle commented May 30, 2026

Copy link
Copy Markdown
Owner

Closes #53

Summary by CodeRabbit

Release Notes

  • New Features

    • Added support for binary request bodies with automatic Content-Type header generation.
    • Improved media-type selection for JSON, binary, and text request payloads.
    • Added OpenAPI 2.0 binary upload sample specification.
  • Documentation

    • Added "Request body content types" section documenting media-type handling and known limitations (multipart/form-data and URL-encoded bodies not yet supported).
  • Tests

    • Added comprehensive binary payload validation tests across OpenAPI and Swagger converters.

Captures the declared media type on body parameters so converters can
record it at parse time and the generator can route binary/text payloads
without sniffing schemas. The field is allocator-owned and freed in
Parameter.deinit. No behavior change yet — converters and generator
are wired in subsequent commits.
Records the selected media type on Parameter.content_type with the
existing JSON-first preference rule (application/json wins; then any
*+json suffix; then the first declared media type). Strings are duped
into the unified allocator. ref request bodies stay null pending a
future ref-resolution follow-up. No generator behavior change yet.
Mirrors the v3.0 selection rule on Parameter.content_type for the v3.1
and v3.2 converters: application/json wins, then any *+json suffix,
then the first declared media type. Strings are duped into the unified
allocator. Adds smoke tests covering octet-stream and JSON-vs-XML.
Threads operation-level consumes (with spec-level fallback) into
convertParameter for body parameters. Applies the same JSON-preference
rule used by the v3.x converters: application/json wins, then any
*+json suffix, otherwise the first listed media type. Adds a minimal
Swagger 2.0 fixture exercising both operation-level octet-stream and
spec-level JSON inheritance, and pairs it with two converter tests.
Adds a private BodyKind enum (none/json/binary/text/form) and a
classifyBody helper that maps content-type strings to body kinds.
Pure refactor: no call sites yet, generated output is byte-identical.
Unit-tests cover JSON, +json suffix, octet-stream, image/audio/video,
*/*, text/*, multipart/*, and x-www-form-urlencoded.
Switches the runtime appendClientHeaders helper from a bool flag plus
hard-coded application/json to an optional content_type parameter.
Updates the three emit sites (requestRaw, SSE, direct path) to pass
"application/json" or null, preserving runtime behaviour for JSON
operations. Generated client snapshots refreshed accordingly.
Resolves #53. Operations whose request body content type classifies as
binary (octet-stream, image/*, audio/*, video/*, */*, application/*)
or text (text/*) now generate `requestBody: []const u8` parameters
and pass the bytes straight to `http.fetch` with the captured
Content-Type header. Multipart/x-www-form-urlencoded bodies still fall
back to JSON encoding with a TODO marker pending follow-up work.

JSON-bodied operations are unchanged. Generated client snapshots
refreshed; uploadFile in generated_v3.zig now takes `[]const u8`,
emits `application/octet-stream`, and never JSON-stringifies the
payload.
Adds a short Request Body Content Types subsection describing the new
[]const u8 parameter for binary/text bodies and the JSON-fallback
behaviour for multipart/form-data and x-www-form-urlencoded request
bodies pending follow-up work.
Scribe closeout for binary request-body support (issue #53):
- Merged design, test plan, and approval artifacts into decisions.md
- Recorded orchestration logs for Lando (design + review), Fenster (implementation), Starkiller (test planning)
- Updated agent history files with outcome summary
- All validation gates green: zig build test, zig build run-generate
- Binary payloads now flow as []const u8 with correct Content-Type headers
- JSON bodies unchanged (snapshot stability preserved)
The 'loadFromUrl returns ConnectionFailed for unreachable host' test made a real TCP connection to 192.0.2.1:9999 on every 'zig build test' run. This is non-deterministic: the connect either crashes a worker thread with error.Unexpected (IO_TIMEOUT) during shutdown on Windows, or returns a LoadError other than ConnectionFailed, failing the assertion. Gate it behind skipIntegrationTests() like the other network tests, matching the file's documented intent that network tests are skipped by default to keep unit tests deterministic.
Under zig build test the test executable runs with --listen=- (IPC over
stdout via std.zig.Server). On Zig 0.16/Windows, any byte written to stderr
from within a test (std.debug.print, or std.log.warn/err which route through
it) makes the test process exit nonzero, which the build reports as
ailed command: ...test.exe ... --listen=-. std.log.info/debug are filtered
at the default testing.log_level (.warn) and write nothing, so they are safe.

Changes:
- Test files: replace informational std.debug.print with std.log.info
  (silent at the default test log level).
- Leak-detection blocks: replace the print-on-leak with @Panic so a leak now
  fails the test instead of merely printing (and breaking the runner).
- generator.zig: remove redundant error-context prints (errors are already
  surfaced by main.zig) and convert progress messages to std.log.info.
- input_loader.zig: convert error-context prints to std.log.info, preserving
  file/URL context for the CLI while keeping tests silent.

Verified: clean-cache zig build test passes with no ailed command;
zig build run-generate-v3 and running the generated code still succeed;
zig fmt --check passes on LF.
Copilot AI review requested due to automatic review settings May 30, 2026 20:07
@coderabbitai

coderabbitai Bot commented May 30, 2026

Copy link
Copy Markdown
Contributor

Linter diff in the way? Review this PR in Change Stack to focus on meaningful changes and expand context only when needed.

Review Change Stack

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c134252e-3f85-4d7a-9c0c-f2f07af996de

📥 Commits

Reviewing files that changed from the base of the PR and between d82183c and 7c5b78a.

⛔ Files ignored due to path filters (7)
  • generated/generated_v2.zig is excluded by !**/generated/**
  • generated/generated_v2_yaml.zig is excluded by !**/generated/**
  • generated/generated_v3.zig is excluded by !**/generated/**
  • generated/generated_v31.zig is excluded by !**/generated/**
  • generated/generated_v31_yaml.zig is excluded by !**/generated/**
  • generated/generated_v32.zig is excluded by !**/generated/**
  • generated/generated_v3_yaml.zig is excluded by !**/generated/**
📒 Files selected for processing (24)
  • .squad/agents/fenster/history.md
  • .squad/agents/lando/history.md
  • .squad/agents/starkiller/history.md
  • .squad/decisions.md
  • README.md
  • openapi/v2.0/binary-upload.json
  • src/generator.zig
  • src/generators/converters/openapi31_converter.zig
  • src/generators/converters/openapi32_converter.zig
  • src/generators/converters/openapi_converter.zig
  • src/generators/converters/swagger_converter.zig
  • src/generators/unified/api_generator.zig
  • src/input_loader.zig
  • src/media_type.zig
  • src/models/common/document.zig
  • src/tests.zig
  • src/tests/binary_payload_tests.zig
  • src/tests/comprehensive_converter_tests.zig
  • src/tests/openapi_v31_tests.zig
  • src/tests/openapi_v32_tests.zig
  • src/tests/openapi_v3_tests.zig
  • src/tests/swagger_v2_tests.zig
  • src/tests/test_input_loader.zig
  • src/tests/unified_converter_tests.zig

📝 Walkthrough

Walkthrough

This PR implements media-type-aware request-body support for binary content. The changes introduce media-type parsing utilities, extend the parameter model to capture content types, update all OpenAPI/Swagger converters to select and preserve media types using priority rules, classify request bodies in the code generator to emit appropriate payloads ([]const u8 for binary/text, JSON for others), and add comprehensive test coverage. Additionally, logging infrastructure is migrated from debug printing to standard logging.

Changes

Binary Request-Body Implementation

Layer / File(s) Summary
Media-type parsing and selection utilities
src/media_type.zig
New module with baseMediaType(), isJson(), isJsonSuffix(), and selectBestJsonKey() to extract base media types, detect JSON types (exact or via +json suffix), and deterministically select best JSON-related media type from a map, with unit tests.
Parameter model extension
src/models/common/document.zig
Parameter struct gains optional content_type field for request bodies; Parameter.deinit updated to free content_type when present.
OpenAPI v3.0/v3.1 converter media-type selection
src/generators/converters/openapi_converter.zig, src/generators/converters/openapi31_converter.zig
Both converters import mime module and use selectBestJsonKey() in convertRequestBody to select best media type, derive schema, and populate Parameter.content_type.
OpenAPI v3.2 converter media-type selection
src/generators/converters/openapi32_converter.zig
Replaces hardcoded application/json preference with selectBestJsonKey() to determine both schema and content_type for request body parameters.
Swagger v2.0 converter with consumes threading
src/generators/converters/swagger_converter.zig
Adds spec_consumes field, threads document-level and operation-level consumes context through parameter conversion, implements selectConsumesMedia() helper to select best media type from consumes list and populate Parameter.content_type.
API generator body classification and code routing
src/generators/unified/api_generator.zig
Introduces BodyKind enum and helpers (classifyBody(), findBodyParam(), bodyKindFor()) to classify request bodies. Updates requestRaw() to delegate to requestRawWithContentType() with optional content type. Refactors generateFunctionRaw() and generateFunctionBodyDirect() to switch on body kind: binary/text emit raw []const u8 payloads, JSON/form emit JSON-encoded payloads, none sends null. Generated function signatures updated so binary/text bodies are typed as []const u8. Generated appendClientHeaders() accepts optional content_type and emits Content-Type only when non-null. Includes unit test for classifyBody() covering JSON, binary, text, form media types.

Test Coverage and Logging Infrastructure

Layer / File(s) Summary
Test fixture and module setup
openapi/v2.0/binary-upload.json, src/tests.zig
Adds Swagger 2.0 fixture with /upload (binary octet-stream) and /echo (JSON) endpoints. Wires binary_payload_tests.zig and media_type.zig into test compilation.
Binary payload conversion and client code tests
src/tests/binary_payload_tests.zig
Comprehensive test module with helpers for loading/parsing specs and extracting operations. Validates: Parameter.content_type ownership/freeing semantics, media-type selection across all converter versions (v3.0 octet-stream, JSON vs XML preference, vendor +json selection, v3.1/v3.2 JSON preference, Swagger v2.0 consumes precedence), JSON media types with parameters, generated client code (vendor +json Content-Type propagation, binary body as []const u8, JSON bodies still encoded).
Logging infrastructure in generator and loader
src/generator.zig, src/input_loader.zig
Replaces std.debug.print with std.log.info for informational messages (version detection, parsing success); removes debug prints from error paths so errors are returned directly.
Logging migration across test suites
src/tests/comprehensive_converter_tests.zig, src/tests/openapi_v3_tests.zig, src/tests/openapi_v31_tests.zig, src/tests/openapi_v32_tests.zig, src/tests/swagger_v2_tests.zig, src/tests/test_input_loader.zig, src/tests/unified_converter_tests.zig
Replaces std.debug.print with std.log.info for status/diagnostic messages; updates allocator leak detection in test_input_loader.zig to panic instead of print; refines unreachable-host test to use reserved documentation IP (192.0.2.1) and gate behind skipIntegrationTests().
Squad documentation and decision records
.squad/agents/fenster/history.md, .squad/agents/lando/history.md, .squad/agents/starkiller/history.md, .squad/decisions.md, README.md
Updates squad timelines documenting design/review for issue #53; adds decision record specifying binary MIME handling, parameter model extension, body-kind classification, and scope boundaries (with TODOs for deferred multipart/form/streaming); documents limitation for multipart/form-data and application/x-www-form-urlencoded in README.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

A rabbit hops through binary streams,
Raw bytes in []const u8 dreams,
No JSON chains when octet-streams gleam,
Content-Type headers flow with the scheme,
Issue #53 solved with media-aware beams! 🐰✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The pull request title 'Add support for binary payloads' directly and clearly summarizes the main objective of the changeset: implementing end-to-end support for binary and text request bodies in the generated Zig client.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch support-binary-payloads-opus

Comment @coderabbitai help to get the list of available commands and usage tips.

@christianhelle christianhelle self-assigned this May 30, 2026
@christianhelle christianhelle added the enhancement New feature or request label May 30, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/tests/binary_payload_tests.zig`:
- Around line 83-309: Several tests (e.g., test "v3.0 converter :: octet-stream
uploadFile body captures content_type", "v3.0 converter :: addPet body prefers
application/json content_type", "v3.0 converter :: JSON wins when both JSON and
XML present", and the other eight listed) create a test allocator via
test_utils.createTestAllocator() but never call the required leak-checking
defer; after each occurrence of "const allocator = gpa.allocator();" add "defer
std.debug.assert(gpa.deinit() == .ok);" so each test that declares var gpa =
test_utils.createTestAllocator() (look for the gpa/allocator pattern inside the
tests named in the diff and functions OpenApiConverter.init /
OpenApi31Converter.init / OpenApi32Converter.init / SwaggerConverter.init usage)
performs the proper deinit assertion to enable leak detection.
- Around line 311-337: Both tests in this file use testing.allocator directly
(see the two test blocks "generated v3.0 :: uploadFile takes []const u8
requestBody and emits octet-stream Content-Type" and "generated v3.0 :: addPet
still uses JSON encoding for application/json body"); replace those usages with
a test allocator created via test_utils.createTestAllocator() (e.g., const
allocator = test_utils.createTestAllocator();), use that allocator for
readFileAlloc and any allocations, and ensure you call the allocator
cleanup/destructor (defer allocator.destroy() or the project’s recommended
teardown) to enable leak detection; update references to allocator in both test
blocks (file_contents, defer allocator.free(file_contents), etc.) accordingly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b6a32088-517f-4177-9177-7fa607e7a0eb

📥 Commits

Reviewing files that changed from the base of the PR and between d82183c and 9497476.

⛔ Files ignored due to path filters (7)
  • generated/generated_v2.zig is excluded by !**/generated/**
  • generated/generated_v2_yaml.zig is excluded by !**/generated/**
  • generated/generated_v3.zig is excluded by !**/generated/**
  • generated/generated_v31.zig is excluded by !**/generated/**
  • generated/generated_v31_yaml.zig is excluded by !**/generated/**
  • generated/generated_v32.zig is excluded by !**/generated/**
  • generated/generated_v3_yaml.zig is excluded by !**/generated/**
📒 Files selected for processing (24)
  • .squad/agents/fenster/history.md
  • .squad/agents/lando/history.md
  • .squad/agents/starkiller/history.md
  • .squad/decisions.md
  • README.md
  • openapi/v2.0/binary-upload.json
  • src/generator.zig
  • src/generators/converters/openapi31_converter.zig
  • src/generators/converters/openapi32_converter.zig
  • src/generators/converters/openapi_converter.zig
  • src/generators/converters/swagger_converter.zig
  • src/generators/unified/api_generator.zig
  • src/generators/unified/model_generator.zig
  • src/input_loader.zig
  • src/models/common/document.zig
  • src/tests.zig
  • src/tests/binary_payload_tests.zig
  • src/tests/comprehensive_converter_tests.zig
  • src/tests/openapi_v31_tests.zig
  • src/tests/openapi_v32_tests.zig
  • src/tests/openapi_v3_tests.zig
  • src/tests/swagger_v2_tests.zig
  • src/tests/test_input_loader.zig
  • src/tests/unified_converter_tests.zig

Comment thread src/tests/binary_payload_tests.zig
Comment thread src/tests/binary_payload_tests.zig

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds first-class support for non-JSON request bodies by propagating request-body media type through the unified IR and using it to generate raw-byte request payload handling (fixing issue #53 where application/octet-stream bodies were always JSON-encoded).

Changes:

  • Extend the unified Parameter model with content_type and capture it in v2.0/v3.0/v3.1/v3.2 converters (JSON-first selection).
  • Update the unified API generator to classify body kind (json/binary/text/form) and emit []const u8 + raw payload handling and dynamic Content-Type header when appropriate.
  • Add targeted tests + fixtures, adjust tests/logging to avoid stderr output, and document binary body support/limits in the README.

Reviewed changes

Copilot reviewed 24 out of 31 changed files in this pull request and generated 18 comments.

Show a summary per file
File Description
src/models/common/document.zig Adds Parameter.content_type with ownership/deinit support.
src/generators/converters/openapi_converter.zig Captures selected request-body media type for OpenAPI v3.0.
src/generators/converters/openapi31_converter.zig Captures selected request-body media type for OpenAPI v3.1.
src/generators/converters/openapi32_converter.zig Captures selected request-body media type for OpenAPI v3.2.
src/generators/converters/swagger_converter.zig Resolves Swagger v2 consumes into Parameter.content_type for body params.
src/generators/unified/api_generator.zig Adds body-kind classification and generates raw-byte payload + dynamic Content-Type.
src/generators/unified/model_generator.zig Adjusts reserved identifier list (notably removing type).
src/generator.zig Switches status output to std.log.info and removes some debug prints.
src/input_loader.zig Replaces debug prints with std.log.info in load errors.
README.md Documents request body content-type behavior and current limitations.
openapi/v2.0/binary-upload.json Adds a Swagger v2 fixture for binary upload/consumes precedence.
src/tests/binary_payload_tests.zig New tests for IR capture + generated-code assertions for binary vs JSON bodies.
src/tests.zig Registers the new binary payload test module.
src/tests/test_input_loader.zig Avoids stderr prints, gates unreachable-host test behind integration flag.
src/tests/unified_converter_tests.zig Switches test diagnostics from std.debug.print to std.log.info.
src/tests/openapi_v3_tests.zig Switches test diagnostics from std.debug.print to std.log.info.
src/tests/openapi_v31_tests.zig Switches test diagnostics from std.debug.print to std.log.info.
src/tests/openapi_v32_tests.zig Switches test diagnostics from std.debug.print to std.log.info.
src/tests/swagger_v2_tests.zig Switches test diagnostics from std.debug.print to std.log.info.
src/tests/comprehensive_converter_tests.zig Switches test diagnostics from std.debug.print to std.log.info.
generated/generated_v2.zig Regenerated snapshot reflecting header handling changes.
generated/generated_v2_yaml.zig Regenerated snapshot reflecting header handling changes.
generated/generated_v3.zig Regenerated snapshot reflecting binary upload raw-body path.
generated/generated_v3_yaml.zig Regenerated snapshot reflecting binary upload raw-body path.
generated/generated_v31.zig Regenerated snapshot reflecting header helper signature changes.
generated/generated_v31_yaml.zig Regenerated snapshot reflecting header helper signature changes.
generated/generated_v32.zig Regenerated snapshot reflecting header helper signature changes.
.squad/decisions.md Records binary request body support decision/log entry.
.squad/agents/starkiller/history.md Adds Starkiller binary payload test-plan history note.
.squad/agents/lando/history.md Adds Lando design/review notes for binary payload work.
.squad/agents/fenster/history.md Adds Fenster implementation history note for binary payload support.

Comment on lines +25 to +40
fn classifyBody(content_type: ?[]const u8) BodyKind {
const ct = content_type orelse return .json;
if (ct.len == 0) return .json;
if (std.ascii.eqlIgnoreCase(ct, "application/json")) return .json;
if (endsWithIgnoreCase(ct, "+json")) return .json;
if (std.ascii.eqlIgnoreCase(ct, "application/x-www-form-urlencoded")) return .form;
if (startsWithIgnoreCase(ct, "multipart/")) return .form;
if (startsWithIgnoreCase(ct, "text/")) return .text;
if (std.ascii.eqlIgnoreCase(ct, "application/octet-stream")) return .binary;
if (startsWithIgnoreCase(ct, "image/")) return .binary;
if (startsWithIgnoreCase(ct, "audio/")) return .binary;
if (startsWithIgnoreCase(ct, "video/")) return .binary;
if (std.ascii.eqlIgnoreCase(ct, "*/*")) return .binary;
if (startsWithIgnoreCase(ct, "application/")) return .binary;
return .binary;
}
Comment on lines 1441 to +1461
@@ -1347,7 +1455,13 @@ pub const UnifiedApiGenerator = struct {
try self.buffer.appendSlice(self.allocator, " var headers = std.ArrayList(std.http.Header).empty;\n");
try self.buffer.appendSlice(self.allocator, " defer headers.deinit(allocator);\n");
try self.buffer.appendSlice(self.allocator, " const auth_header = try appendClientHeaders(allocator, &headers, client, ");
try self.buffer.appendSlice(self.allocator, if (has_body_param) "true" else "false");
if (has_body_param) {
try self.buffer.appendSlice(self.allocator, "\"");
try self.buffer.appendSlice(self.allocator, direct_ct);
try self.buffer.appendSlice(self.allocator, "\"");
Comment on lines 326 to 349
var mut_request_body = requestBody.*;
var schema: ?Schema = null;
var selected_key: ?[]const u8 = null;
if (mut_request_body.content.get("application/json")) |media_type| {
selected_key = "application/json";
if (media_type.schema) |schema_or_ref| {
schema = try self.convertSchemaOrReference(schema_or_ref);
}
} else if (selectJsonSuffixKey(mut_request_body.content)) |key| {
if (mut_request_body.content.get(key)) |media_type| {
selected_key = key;
if (media_type.schema) |schema_or_ref| {
schema = try self.convertSchemaOrReference(schema_or_ref);
}
}
} else if (mut_request_body.content.count() > 0) {
var it = mut_request_body.content.iterator();
if (it.next()) |entry| {
selected_key = entry.key_ptr.*;
if (entry.value_ptr.schema) |schema_or_ref| {
schema = try self.convertSchemaOrReference(schema_or_ref);
}
}
}
Comment on lines 587 to 610
var mut_request_body = requestBody.*;
var schema: ?Schema = null;
var selected_key: ?[]const u8 = null;
if (mut_request_body.content.get("application/json")) |media_type| {
selected_key = "application/json";
if (media_type.schema) |schema_or_ref| {
schema = try self.convertSchemaOrReference(schema_or_ref);
}
} else if (selectJsonSuffixKey31(mut_request_body.content)) |key| {
if (mut_request_body.content.get(key)) |media_type| {
selected_key = key;
if (media_type.schema) |schema_or_ref| {
schema = try self.convertSchemaOrReference(schema_or_ref);
}
}
} else if (mut_request_body.content.count() > 0) {
var it = mut_request_body.content.iterator();
if (it.next()) |entry| {
selected_key = entry.key_ptr.*;
if (entry.value_ptr.schema) |schema_or_ref| {
schema = try self.convertSchemaOrReference(schema_or_ref);
}
}
}
Comment on lines 340 to 363
var mut_request_body = requestBody.*;
var schema: ?Schema = null;
var selected_key: ?[]const u8 = null;
if (mut_request_body.content.get("application/json")) |media_type| {
selected_key = "application/json";
if (media_type.schema) |schema_or_ref| {
schema = try self.convertSchemaOrReference(schema_or_ref);
}
} else if (selectJsonSuffixKey32(mut_request_body.content)) |key| {
if (mut_request_body.content.get(key)) |media_type| {
selected_key = key;
if (media_type.schema) |schema_or_ref| {
schema = try self.convertSchemaOrReference(schema_or_ref);
}
}
} else if (mut_request_body.content.count() > 0) {
var it = mut_request_body.content.iterator();
if (it.next()) |entry| {
selected_key = entry.key_ptr.*;
if (entry.value_ptr.schema) |schema_or_ref| {
schema = try self.convertSchemaOrReference(schema_or_ref);
}
}
}
Comment on lines 1441 to +1461
@@ -1347,7 +1455,13 @@ pub const UnifiedApiGenerator = struct {
try self.buffer.appendSlice(self.allocator, " var headers = std.ArrayList(std.http.Header).empty;\n");
try self.buffer.appendSlice(self.allocator, " defer headers.deinit(allocator);\n");
try self.buffer.appendSlice(self.allocator, " const auth_header = try appendClientHeaders(allocator, &headers, client, ");
try self.buffer.appendSlice(self.allocator, if (has_body_param) "true" else "false");
if (has_body_param) {
try self.buffer.appendSlice(self.allocator, "\"");
try self.buffer.appendSlice(self.allocator, direct_ct);
try self.buffer.appendSlice(self.allocator, "\"");
Comment on lines 326 to 349
var mut_request_body = requestBody.*;
var schema: ?Schema = null;
var selected_key: ?[]const u8 = null;
if (mut_request_body.content.get("application/json")) |media_type| {
selected_key = "application/json";
if (media_type.schema) |schema_or_ref| {
schema = try self.convertSchemaOrReference(schema_or_ref);
}
} else if (selectJsonSuffixKey(mut_request_body.content)) |key| {
if (mut_request_body.content.get(key)) |media_type| {
selected_key = key;
if (media_type.schema) |schema_or_ref| {
schema = try self.convertSchemaOrReference(schema_or_ref);
}
}
} else if (mut_request_body.content.count() > 0) {
var it = mut_request_body.content.iterator();
if (it.next()) |entry| {
selected_key = entry.key_ptr.*;
if (entry.value_ptr.schema) |schema_or_ref| {
schema = try self.convertSchemaOrReference(schema_or_ref);
}
}
}
Comment on lines 587 to 610
var mut_request_body = requestBody.*;
var schema: ?Schema = null;
var selected_key: ?[]const u8 = null;
if (mut_request_body.content.get("application/json")) |media_type| {
selected_key = "application/json";
if (media_type.schema) |schema_or_ref| {
schema = try self.convertSchemaOrReference(schema_or_ref);
}
} else if (selectJsonSuffixKey31(mut_request_body.content)) |key| {
if (mut_request_body.content.get(key)) |media_type| {
selected_key = key;
if (media_type.schema) |schema_or_ref| {
schema = try self.convertSchemaOrReference(schema_or_ref);
}
}
} else if (mut_request_body.content.count() > 0) {
var it = mut_request_body.content.iterator();
if (it.next()) |entry| {
selected_key = entry.key_ptr.*;
if (entry.value_ptr.schema) |schema_or_ref| {
schema = try self.convertSchemaOrReference(schema_or_ref);
}
}
}
Comment on lines 340 to 363
var mut_request_body = requestBody.*;
var schema: ?Schema = null;
var selected_key: ?[]const u8 = null;
if (mut_request_body.content.get("application/json")) |media_type| {
selected_key = "application/json";
if (media_type.schema) |schema_or_ref| {
schema = try self.convertSchemaOrReference(schema_or_ref);
}
} else if (selectJsonSuffixKey32(mut_request_body.content)) |key| {
if (mut_request_body.content.get(key)) |media_type| {
selected_key = key;
if (media_type.schema) |schema_or_ref| {
schema = try self.convertSchemaOrReference(schema_or_ref);
}
}
} else if (mut_request_body.content.count() > 0) {
var it = mut_request_body.content.iterator();
if (it.next()) |entry| {
selected_key = entry.key_ptr.*;
if (entry.value_ptr.schema) |schema_or_ref| {
schema = try self.convertSchemaOrReference(schema_or_ref);
}
}
}
Comment on lines +340 to +349
fn selectConsumesMedia(list: []const []const u8) ?[]const u8 {
if (list.len == 0) return null;
for (list) |m| {
if (std.mem.eql(u8, m, "application/json")) return m;
}
for (list) |m| {
if (std.mem.endsWith(u8, m, "+json")) return m;
}
return list[0];
}
christianhelle and others added 2 commits May 30, 2026 22:26
Address PR #55 review (copilot-pull-request-reviewer): media types with
parameters (e.g. "application/json; charset=utf-8") were compared verbatim,
causing misclassification and non-deterministic JSON-first selection.

- Add src/media_type.zig with baseMediaType/isJson/isJsonSuffix helpers and a
  deterministic selectBestJsonKey (exact JSON > +json suffix > lexicographic
  fallback), with unit tests.
- classifyBody: strip parameters before classification so parameterized JSON
  bodies are JSON-encoded rather than emitted as raw []const u8.
- generateFunctionBodyDirect: force Content-Type application/json for form
  bodies that fall back to JSON encoding (multipart/x-www-form-urlencoded not
  yet supported) so the header matches the actual payload.
- OpenAPI v3.0/v3.1/v3.2 convertRequestBody: select best content-type key via
  normalized comparison; remove now-unused selectJsonSuffixKey helpers.
- Swagger v2 selectConsumesMedia: compare on normalized base media type while
  returning the original string for header emission.
- Add regression tests for parameterized media types.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 25 out of 32 changed files in this pull request and generated 2 comments.

Comment on lines 13 to 23
fn isReservedIdent(name: []const u8) bool {
const reserved = [_][]const u8{
"addrspace", "align", "allowzero", "and", "anyerror", "anyframe", "anyopaque", "anytype",
"asm", "async", "await", "bool", "break", "callconv", "catch", "comptime",
"const", "continue", "defer", "else", "enum", "errdefer", "error", "export",
"extern", "false", "fn", "for", "if", "inline", "isize", "linksection",
"noalias", "noreturn", "nosuspend", "null", "opaque", "or", "orelse", "packed",
"pub", "resume", "return", "struct", "suspend", "switch", "test", "threadlocal",
"true", "try", "type", "undefined", "union", "unreachable", "usize", "usingnamespace",
"var", "void", "volatile", "while",
"addrspace", "align", "allowzero", "and", "anyerror", "anyframe", "anyopaque", "anytype",
"asm", "async", "await", "bool", "break", "callconv", "catch", "comptime",
"const", "continue", "defer", "else", "enum", "errdefer", "error", "export",
"extern", "false", "fn", "for", "if", "inline", "isize", "linksection",
"noalias", "noreturn", "nosuspend", "null", "opaque", "or", "orelse", "packed",
"pub", "resume", "return", "struct", "suspend", "switch", "test", "threadlocal",
"true", "try", "undefined", "union", "unreachable", "usize", "usingnamespace", "var",
"void", "volatile", "while",
};
Comment on lines 336 to 342
\\pub fn requestRaw(client: *Client, method: std.http.Method, url: []const u8, payload: ?[]const u8) !RawResponse {
\\ const allocator = client.allocator;
\\ var headers = std.ArrayList(std.http.Header).empty;
\\ defer headers.deinit(allocator);
\\ const auth_header = try appendClientHeaders(allocator, &headers, client, payload != null, "application/json");
\\ const content_type: ?[]const u8 = if (payload != null) "application/json" else null;
\\ const auth_header = try appendClientHeaders(allocator, &headers, client, content_type, "application/json");
\\ defer if (auth_header) |value| allocator.free(value);
…tRaw

Address PR #55 review (copilot-pull-request-reviewer, 2 findings):

- model_generator: re-add "type" to the reserved-identifier list. Although a
  bare 'type' is valid as a struct *field* name, the same appendIdentifier path
  also emits declaration/type names, where 'pub const type = struct {...}' is
  invalid Zig ("name shadows primitive 'type'"). Escaping as @"type" keeps
  all contexts valid; regenerated snapshots accordingly.
- api_generator: requestRaw hard-coded Content-Type application/json for any
  payload. Add requestRawWithContentType helper (requestRaw delegates with
  application/json) and route declared non-application/json JSON media types
  (vendor +json, parameterized keys) through it from generateFunctionRaw so the
  emitted Content-Type matches Parameter.content_type.
- Add a regression test asserting a vendor +json body propagates its media type
  to requestRawWithContentType.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

application/octet-stream content

2 participants