openapi2zig

Generate type-safe Zig client code from OpenAPI specifications

A CLI tool written in Zig that generates API client code in Zig from OpenAPI specifications. Supports Swagger v2.0 and OpenAPI v3.0, v3.1, and v3.2 specifications. Generate idiomatic, type-safe Zig client code for your APIs.

CI Status Zig Version License

Installation

This tool is distributed as a compiled binary for multiple platforms.

Quick Install Script (Recommended)

The easiest way to install openapi2zig on your system.

Bash
curl -fsSL https://christianhelle.com/openapi2zig/install | bash
PowerShell
irm https://christianhelle.com/openapi2zig/install.ps1 | iex

Manual Download

Download the latest release for your platform:

  • Linux x86_64: openapi2zig-linux-x86_64.tar.gz
  • macOS x86_64: openapi2zig-macos-x86_64.tar.gz
  • macOS ARM64: openapi2zig-macos-aarch64.tar.gz
  • Windows x86_64: openapi2zig-windows-x86_64.zip
Download Latest Release

Install from Snap Store

Install the latest build for Linux from the Snap Store:

Bash
snap install --edge openapi2zig

Build from Source

For developers who want to build from source:

Build Commands
git clone https://github.com/christianhelle/openapi2zig.git
cd openapi2zig
zig build

Development Environment

Get started with contributing to openapi2zig quickly using our pre-configured development environments.

🐳 VS Code Dev Containers (Local)

If you prefer local development with Docker, use our dev container configuration for a consistent development environment.

  1. Install VS Code and the Dev Containers extension
  2. Clone the repository and open it in VS Code
  3. When prompted, click "Reopen in Container"
  4. VS Code will build and configure the development environment automatically

⚙️ Manual Setup

For traditional local development, install Zig following the official installation guide.

Prerequisites: Zig v0.16.0 or later

Development Workflow

Once your development environment is ready:

Build the project:

Shell
zig build

Run tests:

Shell
zig build test

Run the application:

Shell
zig build run

Usage

Command-line Interface

The generate command reads a JSON OpenAPI/Swagger document from a local file or http/https URL, auto-detects the spec version, and writes one Zig source file.

Shell
openapi2zig generate [options]

Options

Flag Description
-i, --input <PATH_OR_URL> OpenAPI/Swagger JSON spec from a file path or URL. Required.
-o, --output <path> Output file for generated Zig code. Defaults to generated.zig.
--base-url <url> Base URL baked into the generated Client; defaults to the spec server URL.
--resource-wrappers <mode> Resource wrapper mode: none, tags, paths, or hybrid. Defaults to paths.

Examples

Shell
openapi2zig generate -i openapi/v3.0/petstore.json -o api.zig --base-url https://petstore3.swagger.io/api/v3

Current Capabilities

  • ✅ Swagger v2.0 specification parsing and generation
  • ✅ OpenAPI v3.0, v3.1, and v3.2 parsing and generation
  • ✅ Local files and remote http/https JSON specs
  • ✅ 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
  • ✅ Resource wrapper namespaces generated from paths, tags, or hybrid metadata

Example Generated Code

These snippets match the current generated/generated_v3.zig style produced by zig build run-generate-v3.

Generated output structure

  • Schema declarations such as Pet, Order, and helper types.
  • A reusable Client that owns std.http.Client.
  • Owned(T), RawResponse, ParseErrorResponse, and ApiResult(T) wrappers.
  • Endpoint triplets: operation, operationRaw, and operationResult.
  • Percent-encoded query parameters and loose JSON response parsing.
  • Bounded raw/typed SSE helpers, OpenAI stream helpers when matching operation IDs exist, and default path-based resource wrappers.
  • OpenAPI 3.1 composite-schema metadata is stronger than earlier converters, but unsupported ambiguous shapes still use std.json.Value.

Models

Zig
pub const Tag = struct {
    id: ?i64 = null,
    name: ?[]const u8 = null,
};

pub const Category = struct {
    id: ?i64 = null,
    name: ?[]const u8 = null,
};

pub const Pet = struct {
    status: ?[]const u8 = null,
    tags: ?[]const Tag = null,
    category: ?Category = null,
    id: ?i64 = null,
    name: []const u8,
    photoUrls: []const []const u8,
};

Client

Zig
pub const Client = struct {
    allocator: std.mem.Allocator,
    io: std.Io,
    http: std.http.Client,
    api_key: []const u8,
    base_url: []const u8 = "https://petstore3.swagger.io/api/v3",
    organization: ?[]const u8 = null,
    project: ?[]const u8 = null,
    default_headers: []const std.http.Header = &.{},

    pub fn init(allocator: std.mem.Allocator, io: std.Io, api_key: []const u8) Client {
        return .{
            .allocator = allocator,
            .io = io,
            .http = .{ .allocator = allocator, .io = io },
            .api_key = api_key,
        };
    }

    pub fn deinit(self: *Client) void {
        self.http.deinit();
    }

    pub fn withBaseUrl(self: *Client, base_url: []const u8) void {
        self.base_url = base_url;
    }
};

Endpoint helpers

Zig
pub fn getPetById(client: *Client, petId: i64) !Owned(Pet) {
    var result = try getPetByIdResult(client, petId);
    switch (result) {
        .ok => |ok| return ok,
        .api_error => |*err| {
            err.deinit();
            return error.ResponseError;
        },
        .parse_error => |*err| {
            err.raw.deinit();
            return error.ResponseParseError;
        },
    }
}

pub fn getPetByIdRaw(client: *Client, petId: i64) !RawResponse {
    const allocator = client.allocator;
    var uri_buf: std.Io.Writer.Allocating = .init(allocator);
    defer uri_buf.deinit();
    try uri_buf.writer.print("{s}/pet/{d}", .{ client.base_url, petId });
    const payload: ?[]const u8 = null;

    return requestRaw(client, std.http.Method.GET, uri_buf.written(), payload);
}

pub fn getPetByIdResult(client: *Client, petId: i64) !ApiResult(Pet) {
    return parseRawResponse(Pet, try getPetByIdRaw(client, petId));
}

Calling generated code

Zig
var client = api.Client.init(allocator, io, "");
defer client.deinit();
client.withBaseUrl("https://petstore3.swagger.io/api/v3");

var pet = try api.getPetById(&client, 1);
defer pet.deinit();
std.debug.print("pet name: {s}\n", .{pet.value().name});

var wrapped = try api.pet.get(&client, 1);
defer wrapped.deinit();

Contributing

We welcome contributions to openapi2zig! Here's how you can help:

1

Fork the Repository

Create your own fork of the openapi2zig repository on GitHub.

2

Create a Feature Branch

Create a new branch for your feature or bug fix.

git checkout -b feature/amazing-feature
3

Make Your Changes

Implement your feature or fix, ensuring you follow the coding standards.

4

Test Your Changes

Run the test suite and ensure all tests pass.

zig build test
5

Submit a Pull Request

Push your changes and create a pull request for review.

Code Style

This project follows standard Zig formatting. Use zig fmt to format your code before committing.

Format Check
zig fmt --check src/
zig fmt --check build.zig