Skip to content
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -342,7 +342,7 @@ pub fn main(init: std.process.Init) !void {
.base_url = "https://api.example.com",
};

const generated_code = try openapi2zig.generateCode(allocator, unified_doc, args);
const generated_code = try openapi2zig.generateCode(allocator, io, unified_doc, args);
defer allocator.free(generated_code);

// Write generated code to file
Expand Down Expand Up @@ -372,7 +372,7 @@ pub fn main(init: std.process.Init) !void {

#### Code Generation

- `generateCode(allocator, unified_doc, args)` - Generate complete Zig code (models + API)
- `generateCode(allocator, io, unified_doc, args)` - Generate complete Zig code (models + API). The output begins with a versioned `<auto-generated>` header (generator version, timestamp, and a regeneration warning); any manual edits will be overwritten when the code is regenerated.
- `generateModels(allocator, unified_doc)` - Generate only model structs
- `generateApi(allocator, unified_doc, args)` - Generate only API client functions

Expand Down
2 changes: 1 addition & 1 deletion examples/example_usage.zig
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ pub fn main(init: std.process.Init) !void {
.base_url = "https://petstore.swagger.io/v2",
};

const generated_code = try openapi2zig.generateCode(allocator, unified_doc, args);
const generated_code = try openapi2zig.generateCode(allocator, io, unified_doc, args);
defer allocator.free(generated_code);

std.debug.print("\nGenerated {} bytes of Zig code\n", .{generated_code.len});
Expand Down
4 changes: 4 additions & 0 deletions generated/generated_v2.zig
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
// <auto-generated>
// this code was generated by openapi2zig 0.3.0 (cf00759) on 2026-07-13 08:44:15 UTC
// changes to this file may cause incorrect behavior and will be lost if the code is regenerated
// </auto-generated>
const std = @import("std");

///////////////////////////////////////////
Expand Down
4 changes: 4 additions & 0 deletions generated/generated_v2_yaml.zig
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
// <auto-generated>
// this code was generated by openapi2zig 0.3.0 (cf00759) on 2026-07-13 08:44:16 UTC
// changes to this file may cause incorrect behavior and will be lost if the code is regenerated
// </auto-generated>
const std = @import("std");

///////////////////////////////////////////
Expand Down
4 changes: 4 additions & 0 deletions generated/generated_v3.zig
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
// <auto-generated>
// this code was generated by openapi2zig 0.3.0 (cf00759) on 2026-07-13 08:44:16 UTC
// changes to this file may cause incorrect behavior and will be lost if the code is regenerated
// </auto-generated>
const std = @import("std");

///////////////////////////////////////////
Expand Down
4 changes: 4 additions & 0 deletions generated/generated_v31.zig
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
// <auto-generated>
// this code was generated by openapi2zig 0.3.0 (cf00759) on 2026-07-13 08:44:15 UTC
// changes to this file may cause incorrect behavior and will be lost if the code is regenerated
// </auto-generated>
const std = @import("std");

///////////////////////////////////////////
Expand Down
4 changes: 4 additions & 0 deletions generated/generated_v31_yaml.zig
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
// <auto-generated>
// this code was generated by openapi2zig 0.3.0 (cf00759) on 2026-07-13 08:44:16 UTC
// changes to this file may cause incorrect behavior and will be lost if the code is regenerated
// </auto-generated>
const std = @import("std");

///////////////////////////////////////////
Expand Down
4 changes: 4 additions & 0 deletions generated/generated_v32.zig
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
// <auto-generated>
// this code was generated by openapi2zig 0.3.0 (cf00759) on 2026-07-13 08:44:16 UTC
// changes to this file may cause incorrect behavior and will be lost if the code is regenerated
// </auto-generated>
const std = @import("std");

///////////////////////////////////////////
Expand Down
4 changes: 4 additions & 0 deletions generated/generated_v3_yaml.zig
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
// <auto-generated>
// this code was generated by openapi2zig 0.3.0 (cf00759) on 2026-07-13 08:44:15 UTC
// changes to this file may cause incorrect behavior and will be lost if the code is regenerated
// </auto-generated>
const std = @import("std");

///////////////////////////////////////////
Expand Down
4 changes: 4 additions & 0 deletions generated/lmstudio.zig
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
// <auto-generated>
// this code was generated by openapi2zig 0.3.0 (cf00759) on 2026-07-13 08:44:15 UTC
// changes to this file may cause incorrect behavior and will be lost if the code is regenerated
// </auto-generated>
const std = @import("std");

///////////////////////////////////////////
Expand Down
8 changes: 7 additions & 1 deletion src/generator.zig
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ const detector = @import("detector.zig");
const models = @import("models.zig");
const input_loader = @import("input_loader.zig");
const yaml_loader = @import("yaml_loader.zig");
const generated_header = @import("generators/generated_header.zig");
const OpenApiConverter = @import("generators/converters/openapi_converter.zig").OpenApiConverter;
const OpenApi31Converter = @import("generators/converters/openapi31_converter.zig").OpenApi31Converter;
const OpenApi32Converter = @import("generators/converters/openapi32_converter.zig").OpenApi32Converter;
Expand Down Expand Up @@ -123,14 +124,19 @@ fn generateCodeFromUnifiedDocument(allocator: std.mem.Allocator, io: std.Io, uni
};
defer if (!args.models_only) allocator.free(generated_code);

const header = try generated_header.renderNowFromBuildInfo(allocator, io);
defer allocator.free(header);
const output_code = try std.mem.concat(allocator, u8, &.{ header, generated_code });
defer allocator.free(output_code);

const output_path = args.output_path orelse default_output_file;
const cwd = std.Io.Dir.cwd();
if (std.fs.path.dirname(output_path)) |dir_path| {
try cwd.createDirPath(io, dir_path);
}
const output_file = try cwd.createFile(io, output_path, .{});
defer output_file.close(io);
try output_file.writeStreamingAll(io, generated_code);
try output_file.writeStreamingAll(io, output_code);
std.log.info("Code generated successfully and written to '{s}'.", .{output_path});
}

Expand Down
42 changes: 42 additions & 0 deletions src/generators/generated_header.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
const std = @import("std");
const version_info = @import("build_info");

pub fn render(allocator: std.mem.Allocator, version: []const u8, timestamp: []const u8) ![]const u8 {
return try std.fmt.allocPrint(allocator,
\\// <auto-generated>
\\// this code was generated by openapi2zig {s} on {s}
\\// changes to this file may cause incorrect behavior and will be lost if the code is regenerated
\\// </auto-generated>
\\
, .{ version, timestamp });
}

pub fn renderNow(allocator: std.mem.Allocator, io: std.Io, version: []const u8) ![]const u8 {
const timestamp = try formatUtcTimestamp(allocator, io);
defer allocator.free(timestamp);
return try render(allocator, version, timestamp);
}

pub fn renderNowFromBuildInfo(allocator: std.mem.Allocator, io: std.Io) ![]const u8 {
const version = try std.fmt.allocPrint(allocator, "{s} ({s})", .{ version_info.VERSION, version_info.GIT_COMMIT });
defer allocator.free(version);
return try renderNow(allocator, io, version);
}

fn formatUtcTimestamp(allocator: std.mem.Allocator, io: std.Io) ![]const u8 {
const now = std.Io.Clock.real.now(io);
const epoch_seconds = std.time.epoch.EpochSeconds{ .secs = @as(u64, @intCast(now.toSeconds())) };
const epoch_day = epoch_seconds.getEpochDay();
const day_seconds = epoch_seconds.getDaySeconds();
const year_day = epoch_day.calculateYearDay();
const month_day = year_day.calculateMonthDay();

return try std.fmt.allocPrint(allocator, "{d}-{d:0>2}-{d:0>2} {d:0>2}:{d:0>2}:{d:0>2} UTC", .{
year_day.year,
month_day.month.numeric(),
month_day.day_index + 1,
day_seconds.getHoursIntoDay(),
day_seconds.getMinutesIntoHour(),
day_seconds.getSecondsIntoMinute(),
});
}
24 changes: 9 additions & 15 deletions src/lib.zig
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@

const std = @import("std");
const yaml_loader = @import("yaml_loader.zig");
const generated_header = @import("generators/generated_header.zig");

// Core version detection
pub const ApiVersion = @import("detector.zig").OpenApiVersion;
Expand Down Expand Up @@ -213,34 +214,27 @@ pub fn generateApi(allocator: std.mem.Allocator, unified_doc: UnifiedDocument, a
///
/// Parameters:
/// - allocator: Memory allocator to use for code generation
/// - io: Standard I/O context for reading the generation timestamp
/// - unified_doc: The unified document containing schema and operation definitions
/// - args: CLI arguments for customizing code generation
///
/// Returns:
/// - String containing complete generated Zig code
pub fn generateCode(allocator: std.mem.Allocator, unified_doc: UnifiedDocument, args: CliArgs) ![]const u8 {
pub fn generateCode(allocator: std.mem.Allocator, io: std.Io, unified_doc: UnifiedDocument, args: CliArgs) ![]const u8 {
const models_code = try generateModels(allocator, unified_doc);
errdefer allocator.free(models_code);
defer allocator.free(models_code);

const header = try generated_header.renderNowFromBuildInfo(allocator, io);
defer allocator.free(header);

if (args.models_only) {
return models_code;
return try std.mem.concat(allocator, u8, &.{ header, models_code });
}
defer allocator.free(models_code);

const api_code = try generateApi(allocator, unified_doc, args);
defer allocator.free(api_code);

Comment thread
coderabbitai[bot] marked this conversation as resolved.
const header =
\\///////////////////////////////////////////
\\// Generated Zig code from OpenAPI
\\///////////////////////////////////////////
\\
\\const std = @import("std");
\\
\\
;

return try std.fmt.allocPrint(allocator, "{s}{s}\n{s}", .{ header, models_code, api_code });
return try std.mem.concat(allocator, u8, &.{ header, models_code, "\n", api_code });
}

fn convertDocument(allocator: std.mem.Allocator, doc: anytype, comptime Converter: type) !UnifiedDocument {
Expand Down
2 changes: 2 additions & 0 deletions src/tests.zig
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const resource_wrapper_tests = @import("tests/resource_wrapper_tests.zig");
const model_typing_tests = @import("tests/model_typing_tests.zig");
const binary_payload_tests = @import("tests/binary_payload_tests.zig");
const schema_bounds_parsing_tests = @import("tests/schema_bounds_parsing_tests.zig");
const generated_header_tests = @import("tests/generated_header_tests.zig");
const media_type = @import("media_type.zig");
const generator = @import("generator.zig");
const cli = @import("cli.zig");
Expand All @@ -26,6 +27,7 @@ comptime {
_ = model_typing_tests;
_ = binary_payload_tests;
_ = schema_bounds_parsing_tests;
_ = generated_header_tests;
_ = media_type;
_ = generator;
_ = cli;
Expand Down
48 changes: 48 additions & 0 deletions src/tests/generated_header_tests.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
const std = @import("std");
const generated_header = @import("../generators/generated_header.zig");
const openapi2zig = @import("../lib.zig");
const test_utils = @import("test_utils.zig");

test "render emits expected header" {
var gpa = test_utils.createTestAllocator();
const allocator = gpa.allocator();
defer std.debug.assert(gpa.deinit() == .ok);

const header = try generated_header.render(allocator, "0.6.0 (a1b2c3d)", "2026-07-12 21:49:56 UTC");
defer allocator.free(header);

try std.testing.expectEqualStrings(
\\// <auto-generated>
\\// this code was generated by openapi2zig 0.6.0 (a1b2c3d) on 2026-07-12 21:49:56 UTC
\\// changes to this file may cause incorrect behavior and will be lost if the code is regenerated
\\// </auto-generated>
\\
, header);
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

test "generateCode output includes generated header" {
var gpa = test_utils.createTestAllocator();
const allocator = gpa.allocator();
defer std.debug.assert(gpa.deinit() == .ok);

const json =
\\{
\\ "openapi": "3.0.0",
\\ "info": { "title": "fixture", "version": "1.0.0" },
\\ "paths": {}
\\}
;

var unified = try openapi2zig.parseToUnified(allocator, json);
defer unified.deinit(allocator);

const code = try openapi2zig.generateCode(allocator, std.testing.io, unified, .{
.input_path = "fixture.json",
.models_only = true,
});
defer allocator.free(code);

try std.testing.expect(std.mem.indexOf(u8, code, "this code was generated by openapi2zig") != null);
try std.testing.expect(std.mem.indexOf(u8, code, "changes to this file may cause incorrect behavior") != null);
try std.testing.expect(std.mem.indexOf(u8, code, "will be lost if the code is regenerated") != null);
}
Loading