From 29e9dd481b9e21608e323e8f69bcd4945873121f Mon Sep 17 00:00:00 2001 From: Simon Hartcher Date: Wed, 23 Apr 2025 11:51:53 +1000 Subject: [PATCH] refactor: extract date methods into shared date lib --- build.zig | 77 ++++++++++++------------ build.zig.zon | 4 ++ lib/date/build.zig | 35 +++++++++++ lib/date/build.zig.zon | 17 ++++++ src/date.zig => lib/date/src/parsing.zig | 4 -- lib/date/src/root.zig | 9 +++ lib/date/src/timestamp.zig | 67 +++++++++++++++++++++ src/aws.zig | 2 +- src/aws_signing.zig | 2 +- src/xml_shaper.zig | 2 +- 10 files changed, 175 insertions(+), 44 deletions(-) create mode 100644 lib/date/build.zig create mode 100644 lib/date/build.zig.zon rename src/date.zig => lib/date/src/parsing.zig (99%) create mode 100644 lib/date/src/root.zig create mode 100644 lib/date/src/timestamp.zig diff --git a/build.zig b/build.zig index 8f9fbaa..a4dbce3 100644 --- a/build.zig +++ b/build.zig @@ -19,14 +19,7 @@ const test_targets = [_]std.Target.Query{ }; pub fn build(b: *Builder) !void { - // Standard target options allows the person running `zig build` to choose - // what target to build for. Here we do not override the defaults, which - // means any target is allowed, and the default is native. Other options - // for restricting supported target set are available. const target = b.standardTargetOptions(.{}); - - // Standard release options allow the person running `zig build` to select - // between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall. const optimize = b.standardOptimizeOption(.{}); const no_llvm = b.option( @@ -46,6 +39,7 @@ pub fn build(b: *Builder) !void { "test-filter", "Skip tests that do not match any of the specified filters", ) orelse &.{}; + // TODO: Embed the current git version in the code. We can do this // by looking for .git/HEAD (if it exists, follow the ref to /ref/heads/whatevs, // grab that commit, and use b.addOptions/exe.addOptions to generate the @@ -58,13 +52,17 @@ pub fn build(b: *Builder) !void { // executable // TODO: This executable should not be built when importing as a package. // It relies on code gen and is all fouled up when getting imported - const exe = b.addExecutable(.{ - .name = "demo", + const mod_exe = b.createModule(.{ .root_source_file = b.path("src/main.zig"), .target = target, .optimize = optimize, }); - exe.use_llvm = !no_llvm; + + const exe = b.addExecutable(.{ + .name = "demo", + .root_module = mod_exe, + .use_llvm = !no_llvm, + }); // External dependencies const dep_smithy = b.dependency("smithy", .{ @@ -72,16 +70,31 @@ pub fn build(b: *Builder) !void { .optimize = optimize, }); const mod_smithy = dep_smithy.module("smithy"); - exe.root_module.addImport("smithy", mod_smithy); // not sure this should be here... + mod_exe.addImport("smithy", mod_smithy); // not sure this should be here... const dep_zeit = b.dependency("zeit", .{ .target = target, .optimize = optimize, }); const mod_zeit = dep_zeit.module("zeit"); - exe.root_module.addImport("zeit", mod_zeit); + mod_exe.addImport("zeit", mod_zeit); // End External dependencies + // Private modules/dependencies + const mod_json = b.createModule(.{ + .root_source_file = b.path("codegen/src/json.zig"), + .target = target, + .optimize = optimize, + }); + + const dep_date = b.dependency("date", .{ + .target = target, + .optimize = optimize, + }); + const mod_date = dep_date.module("date"); + mod_exe.addImport("date", mod_date); + // End private modules/dependencies + const run_cmd = b.addRunArtifact(exe); run_cmd.step.dependOn(b.getInstallStep()); if (b.args) |args| { @@ -93,14 +106,19 @@ pub fn build(b: *Builder) !void { const cg = b.step("gen", "Generate zig service code from smithy models"); - const cg_exe = b.addExecutable(.{ - .name = "codegen", + const cg_mod = b.createModule(.{ .root_source_file = b.path("codegen/src/main.zig"), // We need this generated for the host, not the real target .target = b.graph.host, .optimize = if (b.verbose) .Debug else .ReleaseSafe, }); - cg_exe.root_module.addImport("smithy", mod_smithy); + cg_mod.addImport("smithy", mod_smithy); + cg_mod.addImport("date", mod_date); + + const cg_exe = b.addExecutable(.{ + .name = "codegen", + .root_module = cg_mod, + }); var cg_cmd = b.addRunArtifact(cg_exe); cg_cmd.addArg("--models"); cg_cmd.addArg(try std.fs.path.join( @@ -133,21 +151,6 @@ pub fn build(b: *Builder) !void { exe.step.dependOn(cg); - // Codegen private modules - const mod_json = b.createModule(.{ - .root_source_file = b.path("codegen/src/json.zig"), - .target = target, - .optimize = optimize, - }); - - const mod_date = b.createModule(.{ - .root_source_file = b.path("codegen/src/date.zig"), - .target = target, - .optimize = optimize, - }); - mod_date.addImport("zeit", mod_zeit); - // End codegen private modules - // This allows us to have each module depend on the // generated service manifest. const service_manifest_module = b.createModule(.{ @@ -165,16 +168,20 @@ pub fn build(b: *Builder) !void { // Expose module to others const mod_aws = b.addModule("aws", .{ .root_source_file = b.path("src/aws.zig"), + .target = target, + .optimize = optimize, }); mod_aws.addImport("smithy", mod_smithy); mod_aws.addImport("service_manifest", service_manifest_module); mod_aws.addImport("date", mod_date); + mod_aws.addImport("zeit", mod_zeit); // Expose module to others - _ = b.addModule("aws-signing", .{ + const mod_aws_signing = b.addModule("aws-signing", .{ .root_source_file = b.path("src/aws_signing.zig"), - .imports = &.{.{ .name = "smithy", .module = mod_smithy }}, }); + mod_aws_signing.addImport("date", mod_date); + mod_aws_signing.addImport("smithy", mod_smithy); // Similar to creating the run step earlier, this exposes a `test` step to // the `zig build --help` menu, providing a way for the user to request @@ -234,14 +241,10 @@ pub fn build(b: *Builder) !void { // Creates a step for unit testing. This only builds the test executable // but does not run it. const smoke_test = b.addTest(.{ - .root_source_file = b.path("src/aws.zig"), - .target = target, - .optimize = optimize, + .root_module = mod_aws, .filters = test_filters, }); smoke_test.use_llvm = !no_llvm; - smoke_test.root_module.addImport("smithy", mod_smithy); - smoke_test.root_module.addImport("service_manifest", service_manifest_module); smoke_test.step.dependOn(cg); const run_smoke_test = b.addRunArtifact(smoke_test); diff --git a/build.zig.zon b/build.zig.zon index c73887c..6f3c913 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -7,6 +7,7 @@ "build.zig.zon", "src", "codegen", + "lib", "README.md", "LICENSE", }, @@ -24,5 +25,8 @@ .url = "git+https://github.com/rockorager/zeit#fb6557ad4bd0cd0f0f728ae978061d7fe992c528", .hash = "zeit-0.6.0-5I6bk29nAgDhK6AVMtXMWhkKTYgUncrWjnlI_8X9DPSd", }, + .date = .{ + .path = "lib/date", + }, }, } diff --git a/lib/date/build.zig b/lib/date/build.zig new file mode 100644 index 0000000..738438e --- /dev/null +++ b/lib/date/build.zig @@ -0,0 +1,35 @@ +const std = @import("std"); + +pub fn build(b: *std.Build) void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + + const lib_mod = b.addModule("date", .{ + .root_source_file = b.path("src/root.zig"), + .target = target, + .optimize = optimize, + }); + + const lib = b.addLibrary(.{ + .linkage = .static, + .name = "date", + .root_module = lib_mod, + }); + + b.installArtifact(lib); + + const lib_unit_tests = b.addTest(.{ + .root_module = lib_mod, + }); + + const run_lib_unit_tests = b.addRunArtifact(lib_unit_tests); + + const test_step = b.step("test", "Run unit tests"); + test_step.dependOn(&run_lib_unit_tests.step); + + const dep_zeit = b.dependency("zeit", .{ + .target = target, + .optimize = optimize, + }); + lib_mod.addImport("zeit", dep_zeit.module("zeit")); +} diff --git a/lib/date/build.zig.zon b/lib/date/build.zig.zon new file mode 100644 index 0000000..0e962d4 --- /dev/null +++ b/lib/date/build.zig.zon @@ -0,0 +1,17 @@ +.{ + .name = .date, + .version = "0.0.0", + .fingerprint = 0xaa9e377a226d739e, // Changing this has security and trust implications. + .minimum_zig_version = "0.14.0", + .dependencies = .{ + .zeit = .{ + .url = "git+https://github.com/rockorager/zeit#fb6557ad4bd0cd0f0f728ae978061d7fe992c528", + .hash = "zeit-0.6.0-5I6bk29nAgDhK6AVMtXMWhkKTYgUncrWjnlI_8X9DPSd", + }, + }, + .paths = .{ + "build.zig", + "build.zig.zon", + "src", + }, +} diff --git a/src/date.zig b/lib/date/src/parsing.zig similarity index 99% rename from src/date.zig rename to lib/date/src/parsing.zig index fba3f15..ed74cd9 100644 --- a/src/date.zig +++ b/lib/date/src/parsing.zig @@ -3,12 +3,8 @@ // really requires the TZ DB. const std = @import("std"); -const codegen_date = @import("date"); - const log = std.log.scoped(.date); -pub const Timestamp = codegen_date.Timestamp; - pub const DateTime = struct { day: u8, month: u8, year: u16, hour: u8, minute: u8, second: u8 }; const SECONDS_PER_DAY = 86400; //* 24* 60 * 60 */ diff --git a/lib/date/src/root.zig b/lib/date/src/root.zig new file mode 100644 index 0000000..6dcc303 --- /dev/null +++ b/lib/date/src/root.zig @@ -0,0 +1,9 @@ +const std = @import("std"); +const testing = std.testing; + +pub usingnamespace @import("parsing.zig"); +pub usingnamespace @import("timestamp.zig"); + +test { + testing.refAllDeclsRecursive(@This()); +} diff --git a/lib/date/src/timestamp.zig b/lib/date/src/timestamp.zig new file mode 100644 index 0000000..c5a62e8 --- /dev/null +++ b/lib/date/src/timestamp.zig @@ -0,0 +1,67 @@ +const std = @import("std"); +const zeit = @import("zeit"); + +pub const DateFormat = enum { + rfc1123, + iso8601, +}; + +pub const Timestamp = enum(zeit.Nanoseconds) { + _, + + pub fn jsonStringify(value: Timestamp, options: anytype, out_stream: anytype) !void { + _ = options; + + const instant = try zeit.instant(.{ + .source = .{ + .unix_nano = @intFromEnum(value), + }, + }); + + try out_stream.writeAll("\""); + try instant.time().gofmt(out_stream, "Mon, 02 Jan 2006 15:04:05 GMT"); + try out_stream.writeAll("\""); + } + + pub fn parse(val: []const u8) !Timestamp { + const date_format = blk: { + if (std.ascii.isDigit(val[0])) { + break :blk DateFormat.iso8601; + } else { + break :blk DateFormat.rfc1123; + } + }; + + const ins = try zeit.instant(.{ + .source = switch (date_format) { + DateFormat.iso8601 => .{ + .iso8601 = val, + }, + DateFormat.rfc1123 => .{ + .rfc1123 = val, + }, + }, + }); + + return @enumFromInt(ins.timestamp); + } +}; + +test Timestamp { + const in_date = "Wed, 23 Apr 2025 11:23:45 GMT"; + + const expected_ts: Timestamp = @enumFromInt(1745407425000000000); + const actual_ts = try Timestamp.parse(in_date); + + try std.testing.expectEqual(expected_ts, actual_ts); + + var buf: [100]u8 = undefined; + var fbs = std.io.fixedBufferStream(&buf); + var counting_writer = std.io.countingWriter(fbs.writer()); + try Timestamp.jsonStringify(expected_ts, .{}, counting_writer.writer()); + + const expected_json = "\"" ++ in_date ++ "\""; + const actual_json = buf[0..counting_writer.bytes_written]; + + try std.testing.expectEqualStrings(expected_json, actual_json); +} diff --git a/src/aws.zig b/src/aws.zig index 05c4a67..e259731 100644 --- a/src/aws.zig +++ b/src/aws.zig @@ -6,7 +6,7 @@ const awshttp = @import("aws_http.zig"); const json = @import("json.zig"); const url = @import("url.zig"); const case = @import("case.zig"); -const date = @import("date.zig"); +const date = @import("date"); const servicemodel = @import("servicemodel.zig"); const xml_shaper = @import("xml_shaper.zig"); const xml_serializer = @import("xml_serializer.zig"); diff --git a/src/aws_signing.zig b/src/aws_signing.zig index f504357..f07d28c 100644 --- a/src/aws_signing.zig +++ b/src/aws_signing.zig @@ -1,7 +1,7 @@ const std = @import("std"); const base = @import("aws_http_base.zig"); const auth = @import("aws_authentication.zig"); -const date = @import("date.zig"); +const date = @import("date"); const scoped_log = std.log.scoped(.aws_signing); diff --git a/src/xml_shaper.zig b/src/xml_shaper.zig index 172d7be..acded96 100644 --- a/src/xml_shaper.zig +++ b/src/xml_shaper.zig @@ -1,6 +1,6 @@ const std = @import("std"); const xml = @import("xml.zig"); -const date = @import("date.zig"); +const date = @import("date"); const log = std.log.scoped(.xml_shaper);