Skip to content

Commit

Permalink
v0.3.0: support for TypeScript types generation
Browse files Browse the repository at this point in the history
  • Loading branch information
kshyr committed Jun 10, 2024
1 parent 533940f commit d980058
Show file tree
Hide file tree
Showing 8 changed files with 160 additions and 130 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ Zig version: `0.13.0-dev.351+64ef45eb0`

---
### Usage
`openapi-typegen -target=<jsdoc|ts> path/to/openapi.json path/to/output.js`
`openapi-typegen -target=<jsdoc|ts> path/to/openapi.json path/to/output.<js|ts>`
2 changes: 1 addition & 1 deletion build.zig.zon
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
.{
.name = "openapi-typegen",
.version = "0.2.2",
.version = "0.3.0",
.description = "Generate type definitions from OpenAPI 3.0.x",
.paths = .{
"build.zig",
Expand Down
65 changes: 65 additions & 0 deletions src/Args.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
const std = @import("std");
const _target = @import("target.zig");
const Target = _target.Target;
const Jsdoc = _target.Jsdoc;
const Typescript = _target.Typescript;
const sliceEql = @import("util.zig").sliceEql;

const stdout = std.io.getStdOut().writer();

const Args = @This();
target: Target,
input_file_path: []const u8,
output_file_path: []const u8,

pub fn init(args: []const []const u8) !Args {
const processed_args = try Args.process(args);

return Args{
.target = processed_args.target,
.input_file_path = processed_args.input_file_path,
.output_file_path = processed_args.output_file_path,
};
}

pub fn process(args: []const []const u8) !Args {
var target: ?Target = null;
var input_file_path: ?[]const u8 = null;
var output_file_path: ?[]const u8 = null;

for (args) |arg| {
if (sliceEql(arg, "-h") or sliceEql(arg, "-help")) {
try Args.help();
std.process.cleanExit();
} else if (sliceEql(arg, "-target=jsdoc")) {
target = Target{ .jsdoc = Jsdoc{} };
} else if (sliceEql(arg, "-target=ts") or sliceEql(arg, "-target=typescript")) {
target = Target{ .typescript = Typescript{} };
} else if (input_file_path == null) {
input_file_path = arg;
} else if (output_file_path == null) {
output_file_path = arg;
} else {
try Args.help();
return error.InvalidArgument;
}
}

if (target == null or input_file_path == null or output_file_path == null) {
try Args.help();
return error.InvalidArgument;
}

return Args{
.target = target.?,
.input_file_path = input_file_path.?,
.output_file_path = output_file_path.?,
};
}

pub fn help() !void {
try stdout.print(
"Usage: openapi-typegen -target=<jsdoc|ts> <input_file_path> <output_file_path>\n",
.{},
);
}
113 changes: 0 additions & 113 deletions src/Cli.zig

This file was deleted.

57 changes: 57 additions & 0 deletions src/Generator.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
const std = @import("std");
const Allocator = std.mem.Allocator;

const OpenApi = @import("openapi.zig").OpenApi;
const sliceEql = @import("util.zig").sliceEql;
const _target = @import("target.zig");
const Target = _target.Target;
const Jsdoc = _target.Jsdoc;
const Typescript = _target.Typescript;
const Args = @import("Args.zig");

const stdout = std.io.getStdOut().writer();

const Output = struct {
num_types: usize,
str: []u8,
};

const Generator = @This();
allocator: std.mem.Allocator,
args: Args,
output: ?Output,

pub fn init(allocator: Allocator, args_in: []const []const u8) !Generator {
// Skip the first argument, which is the program name.
const args = args_in[1..];
const args_struct = try Args.init(args);

return Generator{
.allocator = allocator,
.args = args_struct,
.output = null,
};
}

pub fn run(self: *Generator, input_file: std.fs.File) !Output {
const stat = try input_file.stat();
const json_contents = try input_file.readToEndAlloc(self.allocator, stat.size);

const json = try std.json.parseFromSlice(std.json.Value, self.allocator, json_contents, .{});
defer json.deinit();
const openapi = OpenApi.init(json.value);

var output_slices = std.ArrayList([]const u8).init(self.allocator);
defer output_slices.deinit();

const target = self.args.target;
const schemas = openapi.schemas;
for (schemas.keys(), schemas.values()) |key, value| {
try output_slices.append(try target.buildTypedef(self.allocator, key, value));
}

const num_types = schemas.keys().len;
const output_str = try std.mem.join(self.allocator, "\n", output_slices.items);

return Output{ .num_types = num_types, .str = output_str };
}
14 changes: 7 additions & 7 deletions src/main.zig
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ const Allocator = std.mem.Allocator;

const openapi = @import("openapi.zig");
const sliceEql = @import("util.zig").sliceEql;
const Cli = @import("Cli.zig");
const Generator = @import("Generator.zig");
const _target = @import("target.zig");
const Target = _target.Target;
const Jsdoc = _target.Jsdoc;
Expand All @@ -17,25 +17,25 @@ pub fn main() !void {
const args = try std.process.argsAlloc(allocator);
defer std.process.argsFree(allocator, args);

var cli = try Cli.init(allocator, args);
var generator = try Generator.init(allocator, args);

const input_file = try std.fs.cwd().openFile(cli.args.input_file_path, .{ .mode = .read_only });
const input_file = try std.fs.cwd().openFile(generator.args.input_file_path, .{ .mode = .read_only });
defer input_file.close();

const output_file = try std.fs.cwd().createFile(cli.args.output_file_path, .{});
const output_file = try std.fs.cwd().createFile(generator.args.output_file_path, .{});
defer output_file.close();

const output = try cli.generate(input_file);
const output = try generator.run(input_file);
try output_file.writeAll(output.str);

const target = cli.args.target;
const target = generator.args.target;
const target_types_name = switch (target) {
.jsdoc => "JSDoc typedefs",
.typescript => "TypeScript types",
};

try stdout.print(
"Successfully written {d} {s} to {s}.\n",
.{ output.num_types, target_types_name, cli.args.output_file_path },
.{ output.num_types, target_types_name, generator.args.output_file_path },
);
}
22 changes: 22 additions & 0 deletions src/openapi.zig
Original file line number Diff line number Diff line change
@@ -1,5 +1,22 @@
const std = @import("std");

pub const OpenApi = struct {
schemas: std.ArrayHashMap(
[]const u8,
std.json.Value,
std.array_hash_map.StringContext,
true,
),

pub fn init(json: std.json.Value) OpenApi {
const schemas = json.object.get("components").?.object.get("schemas").?.object;
std.debug.print("schemas: {any}\n", .{@TypeOf(schemas)});
return OpenApi{
.schemas = schemas,
};
}
};

pub fn isPropertyRequired(schema_object: std.json.Value, property_key: []const u8) bool {
const required = schema_object.object.get("required") orelse return false;

Expand All @@ -10,3 +27,8 @@ pub fn isPropertyRequired(schema_object: std.json.Value, property_key: []const u
}
return false;
}

pub fn getEnumValues(schema_object: std.json.Value) ?[]const std.json.Value {
const enum_values = schema_object.object.get("enum") orelse return null;
return enum_values.array.items;
}
15 changes: 7 additions & 8 deletions src/target.zig
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ pub const Jsdoc = struct {

for (properties.keys(), properties.values()) |key, value| {
try output.append(" * @property {");
const type_ = try get_jsdoc_type(allocator, value, "");
const type_ = try getJsdocType(allocator, value, "");
try output.append(type_);
try output.append("} ");

Expand All @@ -64,7 +64,7 @@ pub const Jsdoc = struct {
return output_str;
}

fn get_jsdoc_type(
fn getJsdocType(
allocator: std.mem.Allocator,
value: std.json.Value,
array_suffix: []const u8,
Expand All @@ -89,7 +89,7 @@ pub const Jsdoc = struct {
if (value.object.get("type")) |type_| {
if (sliceEql(type_.string, "array")) {
const items = value.object.get("items").?;
const item_type = try get_jsdoc_type(allocator, items, "[]");
const item_type = try getJsdocType(allocator, items, "[]");
return item_type;
}

Expand Down Expand Up @@ -136,7 +136,7 @@ pub const Typescript = struct {
try output.append("?");
}
try output.append(": ");
const type_ = try get_typescript_type(allocator, value, "");
const type_ = try getTypescriptType(allocator, value, "");
try output.append(type_);

try output.append(";\n");
Expand All @@ -149,13 +149,12 @@ pub const Typescript = struct {
return output_str;
}

fn get_typescript_type(
fn getTypescriptType(
allocator: std.mem.Allocator,
value: std.json.Value,
array_suffix: []const u8,
) ![]const u8 {
if (value.object.get("enum")) |enum_| {
const enum_values = enum_.array.items;
if (openapi.getEnumValues(value)) |enum_values| {
var output = std.ArrayList([]const u8).init(allocator);
defer output.deinit();

Expand All @@ -170,7 +169,7 @@ pub const Typescript = struct {
if (value.object.get("type")) |type_| {
if (sliceEql(type_.string, "array")) {
const items = value.object.get("items").?;
const item_type = try get_typescript_type(allocator, items, "[]");
const item_type = try getTypescriptType(allocator, items, "[]");
return item_type;
}

Expand Down

0 comments on commit d980058

Please sign in to comment.