diff --git a/README.md b/README.md index 6a188b0..2bc00e5 100644 --- a/README.md +++ b/README.md @@ -4,4 +4,4 @@ Zig version: `0.13.0-dev.351+64ef45eb0` --- ### Usage -`openapi-typegen -target= path/to/openapi.json path/to/output.js` +`openapi-typegen -target= path/to/openapi.json path/to/output.` diff --git a/build.zig.zon b/build.zig.zon index cbc3f66..afae9cf 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -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", diff --git a/src/Args.zig b/src/Args.zig new file mode 100644 index 0000000..5401134 --- /dev/null +++ b/src/Args.zig @@ -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= \n", + .{}, + ); +} diff --git a/src/Cli.zig b/src/Cli.zig deleted file mode 100644 index 47cf303..0000000 --- a/src/Cli.zig +++ /dev/null @@ -1,113 +0,0 @@ -const std = @import("std"); -const Allocator = std.mem.Allocator; - -const openapi = @import("openapi.zig"); -const sliceEql = @import("util.zig").sliceEql; -const _target = @import("target.zig"); -const Target = _target.Target; -const Jsdoc = _target.Jsdoc; -const Typescript = _target.Typescript; - -const stdout = std.io.getStdOut().writer(); - -const Output = struct { - num_types: usize, - str: []u8, -}; - -const Cli = @This(); -allocator: std.mem.Allocator, -args: Args, -output: ?Output, - -pub fn init(allocator: Allocator, args_in: []const []const u8) !Cli { - // Skip the first argument, which is the program name. - const args = args_in[1..]; - const args_struct = try Args.init(args); - - return Cli{ - .allocator = allocator, - .args = args_struct, - .output = null, - }; -} - -pub fn generate(self: *Cli, input_file: std.fs.File) !Output { - const stat = try input_file.stat(); - const json_contents = try input_file.readToEndAlloc(self.allocator, stat.size); - - const target = self.args.target; - const json = try std.json.parseFromSlice(std.json.Value, self.allocator, json_contents, .{}); - defer json.deinit(); - - var contents = std.ArrayList([]const u8).init(self.allocator); - defer contents.deinit(); - - const schemas = json.value.object.get("components").?.object.get("schemas").?.object; - for (schemas.keys(), schemas.values()) |key, value| { - try contents.append(try target.buildTypedef(self.allocator, key, value)); - } - - const num_types = schemas.keys().len; - const output_str = try std.mem.join(self.allocator, "\n", contents.items); - - return Output{ .num_types = num_types, .str = output_str }; -} - -const Args = struct { - 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= \n", - .{}, - ); - } -}; diff --git a/src/Generator.zig b/src/Generator.zig new file mode 100644 index 0000000..ab7c63e --- /dev/null +++ b/src/Generator.zig @@ -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 }; +} diff --git a/src/main.zig b/src/main.zig index b27cd28..016d64a 100644 --- a/src/main.zig +++ b/src/main.zig @@ -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; @@ -17,18 +17,18 @@ 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", @@ -36,6 +36,6 @@ pub fn main() !void { 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 }, ); } diff --git a/src/openapi.zig b/src/openapi.zig index 0214988..1c62549 100644 --- a/src/openapi.zig +++ b/src/openapi.zig @@ -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; @@ -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; +} diff --git a/src/target.zig b/src/target.zig index 4f10700..0b42588 100644 --- a/src/target.zig +++ b/src/target.zig @@ -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("} "); @@ -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, @@ -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; } @@ -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"); @@ -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(); @@ -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; }