From 3d679f9d753b17f5f468b5f504ea0a7434f8da17 Mon Sep 17 00:00:00 2001 From: Emil Lerch Date: Fri, 6 Mar 2026 14:59:14 -0800 Subject: [PATCH] introduce deinit on Dividend --- src/cache/store.zig | 9 ++++++--- src/commands/divs.zig | 2 +- src/commands/perf.zig | 2 +- src/models/dividend.zig | 14 +++++++++++++- src/providers/polygon.zig | 25 ++++++++++++++++++++----- src/tui.zig | 2 +- 6 files changed, 42 insertions(+), 12 deletions(-) diff --git a/src/cache/store.zig b/src/cache/store.zig index bbe10de..82fe4b5 100644 --- a/src/cache/store.zig +++ b/src/cache/store.zig @@ -268,10 +268,13 @@ pub const Store = struct { /// Deserialize dividends from SRF data. pub fn deserializeDividends(allocator: std.mem.Allocator, data: []const u8) ![]Dividend { var dividends: std.ArrayList(Dividend) = .empty; - errdefer dividends.deinit(allocator); + errdefer { + for (dividends.items) |d| d.deinit(allocator); + dividends.deinit(allocator); + } var reader = std.Io.Reader.fixed(data); - const parsed = srf.parse(&reader, allocator, .{ .alloc_strings = false }) catch return error.InvalidData; + const parsed = srf.parse(&reader, allocator, .{ .alloc_strings = true }) catch return error.InvalidData; defer parsed.deinit(); for (parsed.records.items) |record| { @@ -794,7 +797,7 @@ test "dividend serialize/deserialize round-trip" { defer allocator.free(data); const parsed = try Store.deserializeDividends(allocator, data); - defer allocator.free(parsed); + defer Dividend.freeSlice(allocator, parsed); try std.testing.expectEqual(@as(usize, 2), parsed.len); diff --git a/src/commands/divs.zig b/src/commands/divs.zig index 0fc1df4..6a4164d 100644 --- a/src/commands/divs.zig +++ b/src/commands/divs.zig @@ -14,7 +14,7 @@ pub fn run(allocator: std.mem.Allocator, svc: *zfin.DataService, symbol: []const return; }, }; - defer allocator.free(result.data); + defer zfin.Dividend.freeSlice(allocator, result.data); if (result.source == .cached) try cli.stderrPrint("(using cached dividend data)\n"); diff --git a/src/commands/perf.zig b/src/commands/perf.zig index 4dfc6fe..04a1806 100644 --- a/src/commands/perf.zig +++ b/src/commands/perf.zig @@ -15,7 +15,7 @@ pub fn run(allocator: std.mem.Allocator, svc: *zfin.DataService, symbol: []const }, }; defer allocator.free(result.candles); - defer if (result.dividends) |d| allocator.free(d); + defer if (result.dividends) |d| zfin.Dividend.freeSlice(allocator, d); if (result.source == .cached) try cli.stderrPrint("(using cached data)\n"); diff --git a/src/models/dividend.zig b/src/models/dividend.zig index 517b5d6..85ba695 100644 --- a/src/models/dividend.zig +++ b/src/models/dividend.zig @@ -1,3 +1,4 @@ +const std = @import("std"); const Date = @import("date.zig").Date; pub const DividendType = enum { @@ -22,6 +23,17 @@ pub const Dividend = struct { frequency: ?u8 = null, /// Classification of the dividend type: DividendType = .unknown, - /// Currency code (e.g., "USD") + /// Currency code (e.g., "USD"). Heap-allocated; freed by deinit(). currency: ?[]const u8 = null, + + /// Free any owned string fields. + pub fn deinit(self: Dividend, allocator: std.mem.Allocator) void { + if (self.currency) |c| allocator.free(c); + } + + /// Free a slice of dividends, calling deinit on each element first. + pub fn freeSlice(allocator: std.mem.Allocator, divs: []const Dividend) void { + for (divs) |d| d.deinit(allocator); + allocator.free(divs); + } }; diff --git a/src/providers/polygon.zig b/src/providers/polygon.zig index 7d3e330..06e5a64 100644 --- a/src/providers/polygon.zig +++ b/src/providers/polygon.zig @@ -50,7 +50,10 @@ pub const Polygon = struct { to: ?Date, ) provider.ProviderError![]Dividend { var all_dividends: std.ArrayList(Dividend) = .empty; - errdefer all_dividends.deinit(allocator); + errdefer { + for (all_dividends.items) |d| d.deinit(allocator); + all_dividends.deinit(allocator); + } var next_url: ?[]const u8 = null; defer if (next_url) |u| allocator.free(u); @@ -248,7 +251,10 @@ fn parseDividendsPage( .record_date = parseDateField(obj, "record_date"), .frequency = parseFrequency(obj), .type = parseDividendType(obj), - .currency = jsonStr(obj.get("currency")), + .currency = if (jsonStr(obj.get("currency"))) |s| + (allocator.dupe(u8, s) catch null) + else + null, }) catch return provider.ProviderError.OutOfMemory; } @@ -442,7 +448,10 @@ test "parseDividendsPage basic" { const allocator = std.testing.allocator; var out = std.ArrayList(Dividend).empty; - defer out.deinit(allocator); + defer { + for (out.items) |d| d.deinit(allocator); + out.deinit(allocator); + } const next_url = try parseDividendsPage(allocator, body, &out); try std.testing.expect(next_url == null); @@ -471,7 +480,10 @@ test "parseDividendsPage with pagination" { const allocator = std.testing.allocator; var out = std.ArrayList(Dividend).empty; - defer out.deinit(allocator); + defer { + for (out.items) |d| d.deinit(allocator); + out.deinit(allocator); + } const next_url = try parseDividendsPage(allocator, body, &out); try std.testing.expect(next_url != null); @@ -488,7 +500,10 @@ test "parseDividendsPage error status" { const allocator = std.testing.allocator; var out = std.ArrayList(Dividend).empty; - defer out.deinit(allocator); + defer { + for (out.items) |d| d.deinit(allocator); + out.deinit(allocator); + } const result = parseDividendsPage(allocator, body, &out); try std.testing.expectError(provider.ProviderError.RequestFailed, result); diff --git a/src/tui.zig b/src/tui.zig index 1fdeeb8..39083bd 100644 --- a/src/tui.zig +++ b/src/tui.zig @@ -1606,7 +1606,7 @@ const App = struct { } fn freeDividends(self: *App) void { - if (self.dividends) |d| self.allocator.free(d); + if (self.dividends) |d| zfin.Dividend.freeSlice(self.allocator, d); self.dividends = null; }