From 723bb7668c892307e63e9180c7adabfeac0615e9 Mon Sep 17 00:00:00 2001 From: Christian Helle Date: Thu, 30 Jul 2026 14:24:23 +0200 Subject: [PATCH 1/5] add transitional test for sse-buffer flag --- src/cli.zig | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/cli.zig b/src/cli.zig index ea6832d..c445852 100644 --- a/src/cli.zig +++ b/src/cli.zig @@ -245,3 +245,18 @@ test "parse generate supports multiple-files flag" { try std.testing.expect(parsed.args.multiple_files); try std.testing.expectEqualStrings("openapi.json", parsed.args.input_path); } + +test "parse generate accepts --sse-buffer flag" { + const argv = [_][:0]const u8{ + "openapi2zig", + "generate", + "-i", + "openapi.json", + "--sse-buffer", + "large", + }; + + const parsed = try parse(&argv); + + try std.testing.expectEqualStrings("openapi.json", parsed.args.input_path); +} From 248de2a676f302467f5531f23e9ef9d5c9b90519 Mon Sep 17 00:00:00 2001 From: Christian Helle Date: Thu, 30 Jul 2026 14:28:55 +0200 Subject: [PATCH 2/5] remove sse-buffer option, hardcode large buffer sizes --- src/cli.zig | 44 +------------------- src/generator.zig | 3 +- src/generators/unified/api_generator.zig | 16 +++---- src/generators/unified/runtime_generator.zig | 15 +------ src/lib.zig | 3 +- 5 files changed, 13 insertions(+), 68 deletions(-) diff --git a/src/cli.zig b/src/cli.zig index c445852..a9acec8 100644 --- a/src/cli.zig +++ b/src/cli.zig @@ -8,25 +8,6 @@ pub const ResourceWrapperMode = enum { hybrid, }; -pub const SseBufferMode = enum { - small, - large, - - pub fn maxLineSize(self: SseBufferMode) usize { - return switch (self) { - .small => 8 * 1024, - .large => 256 * 1024, - }; - } - - pub fn maxEventSize(self: SseBufferMode) usize { - return switch (self) { - .small => 64 * 1024, - .large => 1024 * 1024, - }; - } -}; - pub const CliArgs = struct { input_path: []const u8, output_path: ?[]const u8 = null, @@ -34,7 +15,6 @@ pub const CliArgs = struct { resource_wrappers: ResourceWrapperMode = .paths, models_only: bool = false, multiple_files: bool = false, - sse_buffer: SseBufferMode = .small, }; pub const ParsedArgs = struct { @@ -65,7 +45,6 @@ pub fn parse(args: []const [:0]const u8) !ParsedArgs { var resource_wrappers: ResourceWrapperMode = .paths; var models_only = false; var multiple_files = false; - var sse_buffer: SseBufferMode = .small; var i: usize = 2; while (i < args.len) : (i += 1) { @@ -111,18 +90,6 @@ pub fn parse(args: []const [:0]const u8) !ParsedArgs { models_only = true; } else if (std.mem.eql(u8, arg, "--multiple-files")) { multiple_files = true; - } else if (std.mem.eql(u8, arg, "--sse-buffer")) { - i += 1; - if (i >= args.len) { - printUsage(); - std.debug.print("\nError: SSE buffer mode required\n", .{}); - return error.InvalidArguments; - } - sse_buffer = parseSseBufferMode(args[i]) orelse { - printUsage(); - std.debug.print("\nError: invalid SSE buffer mode '{s}'\n", .{args[i]}); - return error.InvalidArguments; - }; } } @@ -140,7 +107,6 @@ pub fn parse(args: []const [:0]const u8) !ParsedArgs { .resource_wrappers = resource_wrappers, .models_only = models_only, .multiple_files = multiple_files, - .sse_buffer = sse_buffer, }, }; } @@ -153,12 +119,6 @@ fn parseResourceWrapperMode(value: []const u8) ?ResourceWrapperMode { return null; } -fn parseSseBufferMode(value: []const u8) ?SseBufferMode { - if (std.mem.eql(u8, value, "small")) return .small; - if (std.mem.eql(u8, value, "large")) return .large; - return null; -} - fn printUsage() void { std.debug.print( \\ @@ -181,8 +141,6 @@ fn printUsage() void { \\ --models-only Generate only Zig models, skipping the API client. \\ --multiple-files Generate separate output files for models, runtime, and API client \\ into the output directory specified by -o. - \\ --sse-buffer SSE parse buffer size: small (8KB line / 64KB event) - \\ or large (256KB line / 1MB event). (default: small) \\ \\ EXAMPLES: \\ openapi2zig generate -i ./openapi/petstore.json -o api.zig @@ -246,7 +204,7 @@ test "parse generate supports multiple-files flag" { try std.testing.expectEqualStrings("openapi.json", parsed.args.input_path); } -test "parse generate accepts --sse-buffer flag" { +test "parse generate silently ignores --sse-buffer flag" { const argv = [_][:0]const u8{ "openapi2zig", "generate", diff --git a/src/generator.zig b/src/generator.zig index 6d2bd8e..0499bfd 100644 --- a/src/generator.zig +++ b/src/generator.zig @@ -175,7 +175,7 @@ fn generateMultipleFiles(allocator: std.mem.Allocator, io: std.Io, cwd: std.Io.D if (args.models_only) return; - var runtime_gen = RuntimeGenerator.init(allocator, args.sse_buffer); + var runtime_gen = RuntimeGenerator.init(allocator); defer runtime_gen.deinit(); const generated_runtime = try runtime_gen.generate(); defer allocator.free(generated_runtime); @@ -227,3 +227,4 @@ test "unsupported OpenAPI versions return a distinct generator error" { }), ); } + diff --git a/src/generators/unified/api_generator.zig b/src/generators/unified/api_generator.zig index db96c1d..b96e031 100644 --- a/src/generators/unified/api_generator.zig +++ b/src/generators/unified/api_generator.zig @@ -672,15 +672,7 @@ pub const UnifiedApiGenerator = struct { } fn generateSseBufferConstants(self: *UnifiedApiGenerator) !void { - try self.buffer.appendSlice(self.allocator, "const max_sse_line_size = "); - const line_size = try std.fmt.allocPrint(self.allocator, "{d}", .{self.args.sse_buffer.maxLineSize()}); - defer self.allocator.free(line_size); - try self.buffer.appendSlice(self.allocator, line_size); - try self.buffer.appendSlice(self.allocator, ";\nconst max_sse_event_size = "); - const event_size = try std.fmt.allocPrint(self.allocator, "{d}", .{self.args.sse_buffer.maxEventSize()}); - defer self.allocator.free(event_size); - try self.buffer.appendSlice(self.allocator, event_size); - try self.buffer.appendSlice(self.allocator, ";\n\n"); + try self.buffer.appendSlice(self.allocator, "const max_sse_line_size = 262144;\nconst max_sse_event_size = 1048576;\n\n"); } fn generateHttpObserverType(self: *UnifiedApiGenerator) !void { @@ -1333,7 +1325,9 @@ pub const UnifiedApiGenerator = struct { try self.buffer.appendSlice(self.allocator, "std.json.Value"); } } else { - if (param.location == .query and !param.required) try self.buffer.appendSlice(self.allocator, "?"); + if (param.location == .query and !param.required) { + try self.buffer.appendSlice(self.allocator, "?"); + } if (param.schema) |schema| { try self.appendZigQueryTypeFromSchema(schema); } else if (param.type) |param_type| { @@ -1758,12 +1752,14 @@ pub const UnifiedApiGenerator = struct { try self.buffer.appendSlice(self.allocator, " // TODO(#53-followup): multipart/form-data and x-www-form-urlencoded request bodies are not yet supported; falling back to JSON encoding.\n"); try self.buffer.appendSlice(self.allocator, "\n var str: std.Io.Writer.Allocating = .init(allocator);\n"); try self.buffer.appendSlice(self.allocator, " defer str.deinit();\n\n"); + try self.buffer.appendSlice(self.allocator, " try std.json.Stringify.value(requestBody, .{ .emit_null_optional_fields = false }, &str.writer);\n"); try self.buffer.appendSlice(self.allocator, " const payload = str.written();\n"); }, else => { try self.buffer.appendSlice(self.allocator, "\n var str: std.Io.Writer.Allocating = .init(allocator);\n"); try self.buffer.appendSlice(self.allocator, " defer str.deinit();\n\n"); + try self.buffer.appendSlice(self.allocator, " try std.json.Stringify.value(requestBody, .{ .emit_null_optional_fields = false }, &str.writer);\n"); try self.buffer.appendSlice(self.allocator, " const payload = str.written();\n"); }, diff --git a/src/generators/unified/runtime_generator.zig b/src/generators/unified/runtime_generator.zig index 97c3ce0..662cfd1 100644 --- a/src/generators/unified/runtime_generator.zig +++ b/src/generators/unified/runtime_generator.zig @@ -1,16 +1,13 @@ const std = @import("std"); -const cli = @import("../../cli.zig"); pub const RuntimeGenerator = struct { allocator: std.mem.Allocator, buffer: std.ArrayList(u8), - sse_buffer: cli.SseBufferMode, - pub fn init(allocator: std.mem.Allocator, sse_buffer: cli.SseBufferMode) RuntimeGenerator { + pub fn init(allocator: std.mem.Allocator) RuntimeGenerator { return RuntimeGenerator{ .allocator = allocator, .buffer = std.ArrayList(u8).empty, - .sse_buffer = sse_buffer, }; } @@ -97,15 +94,7 @@ pub const RuntimeGenerator = struct { } fn generateSseBufferConstants(self: *RuntimeGenerator) !void { - try self.buffer.appendSlice(self.allocator, "\nconst max_sse_line_size = "); - const line_size = try std.fmt.allocPrint(self.allocator, "{d}", .{self.sse_buffer.maxLineSize()}); - defer self.allocator.free(line_size); - try self.buffer.appendSlice(self.allocator, line_size); - try self.buffer.appendSlice(self.allocator, ";\nconst max_sse_event_size = "); - const event_size = try std.fmt.allocPrint(self.allocator, "{d}", .{self.sse_buffer.maxEventSize()}); - defer self.allocator.free(event_size); - try self.buffer.appendSlice(self.allocator, event_size); - try self.buffer.appendSlice(self.allocator, ";\n\n"); + try self.buffer.appendSlice(self.allocator, "\nconst max_sse_line_size = 262144;\nconst max_sse_event_size = 1048576;\n\n"); } fn generateSseFunctions(self: *RuntimeGenerator) !void { diff --git a/src/lib.zig b/src/lib.zig index 7a6a226..da749ed 100644 --- a/src/lib.zig +++ b/src/lib.zig @@ -268,7 +268,7 @@ pub fn generateCodeMultiple(allocator: std.mem.Allocator, io: std.Io, unified_do return .{ .models = models_with_header }; } - var runtime_gen = RuntimeGenerator.init(allocator, args.sse_buffer); + var runtime_gen = RuntimeGenerator.init(allocator); defer runtime_gen.deinit(); const runtime_code = try runtime_gen.generate(); defer allocator.free(runtime_code); @@ -357,3 +357,4 @@ test { std.testing.refAllDecls(@This()); _ = @import("tests.zig"); } + From 67b1dca730854f91d97764f24ef0f41db3ef3790 Mon Sep 17 00:00:00 2001 From: Christian Helle Date: Thu, 30 Jul 2026 14:30:34 +0200 Subject: [PATCH 3/5] update README and docs to remove sse-buffer flag references --- README.md | 5 +++-- docs/index.html | 10 +++++----- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 612898c..c8c962c 100644 --- a/README.md +++ b/README.md @@ -224,7 +224,7 @@ The `generate` command reads a JSON or YAML OpenAPI/Swagger document from a loca | `-o`, `--output ` | Output file for the generated Zig code. Defaults to `generated.zig`. Parent directories are created when needed. | | `--base-url ` | Base URL baked into the generated `Client`. Defaults to the server URL from the OpenAPI/Swagger document. | | `--resource-wrappers ` | Generate resource wrapper namespaces. Modes: `none`, `tags`, `paths`, `hybrid`. Defaults to `paths`. | -| `--sse-buffer ` | SSE parse buffer size: `small` (8KB line / 64KB event) or `large` (256KB line / 1MB event). Defaults to `small`. | + ### Examples @@ -406,7 +406,7 @@ Generated files are self-contained Zig source files. The current unified generat - `operationResult(...) !ApiResult(T)` for parsed success plus preserved API/parse-error bodies. - Generic helpers such as `requestRaw`, `getRaw`, `postJsonRaw`, `getJsonResult`, and `postJsonResult`. - Query parameter helpers that percent-encode names and string values with `std.Uri.Component.percentEncode`; optional query parameters are nullable. -- Bounded SSE parsing helpers: `parseSseBytes`, `parseSseReader`, `parseSseBytesTyped`, and `parseSseReaderTyped`. SSE buffer size defaults to `small` (8KB line / 64KB event) and can be switched to `large` (256KB line / 1MB event) with `--sse-buffer large`. Stream helpers are generated for every POST operation whose response declares `text/event-stream` content — the function name is `{operationId}Streaming` (with an `Events` variant for typed JSON events). +- Bounded SSE parsing helpers: `parseSseBytes`, `parseSseReader`, `parseSseBytesTyped`, and `parseSseReaderTyped`. SSE buffer size is fixed at 256KB for lines and 1MB for events (previously configurable via `--sse-buffer`). Stream helpers are generated for every POST operation whose response declares `text/event-stream` content — the function name is `{operationId}Streaming` (with an `Events` variant for typed JSON events). - Resource wrapper namespaces by default, for example `pet.get(...)` and `store.order.get(...)`, derived from paths unless `--resource-wrappers` changes the mode. Wrapper names are sanitized generated conveniences, not hand-designed SDK names. Parsed JSON responses use `.ignore_unknown_fields = true` so compatible providers can add response fields without breaking callers. Ambiguous or intentionally open-ended schemas use `std.json.Value`; see [`docs/json-value-typing-policy.md`](docs/json-value-typing-policy.md) for the current policy. For OpenAPI 3.1, the converter has stronger composite-schema handling for object/ref `allOf`, preserved `oneOf`/`anyOf` metadata, and nullable type arrays; do not assume every converter has identical composite support. @@ -617,3 +617,4 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file ## Support If you encounter any issues or have questions, please [open an issue](https://github.com/christianhelle/openapi2zig/issues) on GitHub. + diff --git a/docs/index.html b/docs/index.html index e93e529..5a3ac07 100644 --- a/docs/index.html +++ b/docs/index.html @@ -424,10 +424,7 @@

Options

--resource-wrappers <mode> Resource wrapper mode: none, tags, paths, or hybrid. Defaults to paths. - - --sse-buffer <mode> - SSE parse buffer size: small (8KB line / 64KB event) or large (256KB line / 1MB event). Defaults to small. - + @@ -448,7 +445,7 @@

Current Capabilities

  • ✅ Generated Client with base URL, API key, and borrowed headers
  • ✅ Percent-encoded query parameters and loose response parsing
  • ✅ Raw/result helpers that preserve response bodies
  • -
  • ✅ Bounded raw and typed SSE parser helpers (configurable buffer size)
  • +
  • ✅ Bounded raw and typed SSE parser helpers (large buffer size, 256KB line / 1MB event)
  • ✅ Resource wrapper namespaces generated from paths, tags, or hybrid metadata
  • @@ -798,3 +795,6 @@

    Additional Links

    + + + From f03115efcbd06d4f5a587978edbde3a3debaf7a1 Mon Sep 17 00:00:00 2001 From: Christian Helle Date: Thu, 30 Jul 2026 14:44:33 +0200 Subject: [PATCH 4/5] express buffer constants as readable arithmetic expressions --- src/generators/unified/api_generator.zig | 3 ++- src/generators/unified/runtime_generator.zig | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/generators/unified/api_generator.zig b/src/generators/unified/api_generator.zig index b96e031..2add60a 100644 --- a/src/generators/unified/api_generator.zig +++ b/src/generators/unified/api_generator.zig @@ -672,7 +672,7 @@ pub const UnifiedApiGenerator = struct { } fn generateSseBufferConstants(self: *UnifiedApiGenerator) !void { - try self.buffer.appendSlice(self.allocator, "const max_sse_line_size = 262144;\nconst max_sse_event_size = 1048576;\n\n"); + try self.buffer.appendSlice(self.allocator, "const max_sse_line_size = 256 * 1024;\nconst max_sse_event_size = 1024 * 1024;\n\n"); } fn generateHttpObserverType(self: *UnifiedApiGenerator) !void { @@ -1958,3 +1958,4 @@ test "BodyKind :: classifyBody routes media types correctly" { try t.expectEqual(BodyKind.text, classifyBody("text/plain; charset=utf-8")); try t.expectEqual(BodyKind.form, classifyBody("multipart/form-data; boundary=abc")); } + diff --git a/src/generators/unified/runtime_generator.zig b/src/generators/unified/runtime_generator.zig index 662cfd1..b77a038 100644 --- a/src/generators/unified/runtime_generator.zig +++ b/src/generators/unified/runtime_generator.zig @@ -94,7 +94,7 @@ pub const RuntimeGenerator = struct { } fn generateSseBufferConstants(self: *RuntimeGenerator) !void { - try self.buffer.appendSlice(self.allocator, "\nconst max_sse_line_size = 262144;\nconst max_sse_event_size = 1048576;\n\n"); + try self.buffer.appendSlice(self.allocator, "\nconst max_sse_line_size = 256 * 1024;\nconst max_sse_event_size = 1024 * 1024;\n\n"); } fn generateSseFunctions(self: *RuntimeGenerator) !void { @@ -218,3 +218,4 @@ pub const RuntimeGenerator = struct { ); } }; + From 0cf148096e4deda98a776c0cadacf0a86a03e525 Mon Sep 17 00:00:00 2001 From: Christian Helle Date: Thu, 30 Jul 2026 14:47:28 +0200 Subject: [PATCH 5/5] drop historical sse-buffer footnote from docs --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index c8c962c..97d4e1c 100644 --- a/README.md +++ b/README.md @@ -406,7 +406,7 @@ Generated files are self-contained Zig source files. The current unified generat - `operationResult(...) !ApiResult(T)` for parsed success plus preserved API/parse-error bodies. - Generic helpers such as `requestRaw`, `getRaw`, `postJsonRaw`, `getJsonResult`, and `postJsonResult`. - Query parameter helpers that percent-encode names and string values with `std.Uri.Component.percentEncode`; optional query parameters are nullable. -- Bounded SSE parsing helpers: `parseSseBytes`, `parseSseReader`, `parseSseBytesTyped`, and `parseSseReaderTyped`. SSE buffer size is fixed at 256KB for lines and 1MB for events (previously configurable via `--sse-buffer`). Stream helpers are generated for every POST operation whose response declares `text/event-stream` content — the function name is `{operationId}Streaming` (with an `Events` variant for typed JSON events). +- Bounded SSE parsing helpers: `parseSseBytes`, `parseSseReader`, `parseSseBytesTyped`, and `parseSseReaderTyped`. SSE buffer size is fixed at 256KB for lines and 1MB for events. Stream helpers are generated for every POST operation whose response declares `text/event-stream` content — the function name is `{operationId}Streaming` (with an `Events` variant for typed JSON events). - Resource wrapper namespaces by default, for example `pet.get(...)` and `store.order.get(...)`, derived from paths unless `--resource-wrappers` changes the mode. Wrapper names are sanitized generated conveniences, not hand-designed SDK names. Parsed JSON responses use `.ignore_unknown_fields = true` so compatible providers can add response fields without breaking callers. Ambiguous or intentionally open-ended schemas use `std.json.Value`; see [`docs/json-value-typing-policy.md`](docs/json-value-typing-policy.md) for the current policy. For OpenAPI 3.1, the converter has stronger composite-schema handling for object/ref `allOf`, preserved `oneOf`/`anyOf` metadata, and nullable type arrays; do not assume every converter has identical composite support. @@ -618,3 +618,4 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file If you encounter any issues or have questions, please [open an issue](https://github.com/christianhelle/openapi2zig/issues) on GitHub. +