diff --git a/src/location/GeoCache.zig b/src/location/GeoCache.zig index 958f9ec..7180182 100644 --- a/src/location/GeoCache.zig +++ b/src/location/GeoCache.zig @@ -88,6 +88,34 @@ pub fn saveIfNeeded(self: *GeoCache) void { self.last_save = now; } +fn load(allocator: std.mem.Allocator, cache: *std.StringHashMap(CachedLocation), content: []const u8) !void { + const CacheData = struct { + name: []const u8, + latitude: f64, + longitude: f64, + }; + + const parsed = try std.json.parseFromSlice( + std.json.ArrayHashMap(CacheData), + allocator, + content, + .{}, + ); + defer parsed.deinit(); + + for (parsed.value.map.keys(), parsed.value.map.values()) |key, value| { + const cache_key = try allocator.dupe(u8, key); + const cache_value = CachedLocation{ + .name = try allocator.dupe(u8, value.name), + .coords = .{ + .latitude = value.latitude, + .longitude = value.longitude, + }, + }; + try cache.put(cache_key, cache_value); + } +} + fn loadFromFile(allocator: std.mem.Allocator, cache: *std.StringHashMap(CachedLocation), file_path: []const u8) !void { const file = try std.fs.cwd().openFile(file_path, .{}); defer file.close(); @@ -95,37 +123,10 @@ fn loadFromFile(allocator: std.mem.Allocator, cache: *std.StringHashMap(CachedLo const content = try file.readToEndAlloc(allocator, 10 * 1024 * 1024); // 10MB max defer allocator.free(content); - const parsed = try std.json.parseFromSlice( - std.json.Value, - allocator, - content, - .{}, - ); - defer parsed.deinit(); - - var it = parsed.value.object.iterator(); - while (it.next()) |entry| { - const obj = entry.value_ptr.object; - const key = try allocator.dupe(u8, entry.key_ptr.*); - const value = CachedLocation{ - .name = try allocator.dupe(u8, obj.get("name").?.string), - .coords = .{ - .latitude = obj.get("latitude").?.float, - .longitude = obj.get("longitude").?.float, - }, - }; - try cache.put(key, value); - } + try load(allocator, cache, content); } -fn saveToFile(self: *GeoCache, file_path: []const u8) !void { - const file = try std.fs.cwd().createFile(file_path, .{}); - defer file.close(); - - var buffer: [4096]u8 = undefined; - var file_writer = file.writer(&buffer); - const writer = &file_writer.interface; - +fn save(self: *GeoCache, writer: *std.Io.Writer) !void { try writer.writeAll("{\n"); var it = self.cache.iterator(); @@ -134,7 +135,7 @@ fn saveToFile(self: *GeoCache, file_path: []const u8) !void { if (!first) try writer.writeAll(",\n"); first = false; - try writer.print(" {any}: {any}", .{ + try writer.print(" {f}: {f}", .{ std.json.fmt(entry.key_ptr.*, .{}), std.json.fmt(.{ .name = entry.value_ptr.name, @@ -145,6 +146,17 @@ fn saveToFile(self: *GeoCache, file_path: []const u8) !void { } try writer.writeAll("\n}\n"); +} + +fn saveToFile(self: *GeoCache, file_path: []const u8) !void { + const file = try std.fs.cwd().createFile(file_path, .{}); + defer file.close(); + + var buffer: [4096]u8 = undefined; + var file_writer = file.writer(&buffer); + const writer = &file_writer.interface; + + try self.save(writer); try writer.flush(); } @@ -176,3 +188,90 @@ test "GeoCache miss returns null" { const result = cache.get("NonExistent"); try std.testing.expect(result == null); } + +test "save produces valid JSON" { + const allocator = std.testing.allocator; + var cache = try GeoCache.init(allocator, null); + defer cache.deinit(); + + try cache.put("London", .{ + .name = "London, UK", + .coords = .{ .latitude = 51.5074, .longitude = -0.1278 }, + }); + try cache.put("Paris", .{ + .name = "Paris, France", + .coords = .{ .latitude = 48.8566, .longitude = 2.3522 }, + }); + + var buffer: [1024]u8 = undefined; + var writer = std.Io.Writer.fixed(&buffer); + try cache.save(&writer); + + const output = buffer[0..writer.end]; + try std.testing.expect(std.mem.indexOf(u8, output, "London") != null); + try std.testing.expect(std.mem.indexOf(u8, output, "Paris") != null); + try std.testing.expect(std.mem.indexOf(u8, output, "51.5074") != null); +} + +test "load parses valid JSON" { + const allocator = std.testing.allocator; + var cache_map = std.StringHashMap(CachedLocation).init(allocator); + defer { + var it = cache_map.iterator(); + while (it.next()) |entry| { + allocator.free(entry.key_ptr.*); + allocator.free(entry.value_ptr.name); + } + cache_map.deinit(); + } + + const json = + \\{ + \\ "London": {"name": "London, UK", "latitude": 51.5074, "longitude": -0.1278}, + \\ "Paris": {"name": "Paris, France", "latitude": 48.8566, "longitude": 2.3522} + \\} + ; + + try load(allocator, &cache_map, json); + + const london = cache_map.get("London"); + try std.testing.expect(london != null); + try std.testing.expectApproxEqAbs(@as(f64, 51.5074), london.?.coords.latitude, 0.0001); + + const paris = cache_map.get("Paris"); + try std.testing.expect(paris != null); + try std.testing.expectApproxEqAbs(@as(f64, 48.8566), paris.?.coords.latitude, 0.0001); +} + +test "save and load round-trip" { + const allocator = std.testing.allocator; + var cache1 = try GeoCache.init(allocator, null); + defer cache1.deinit(); + + try cache1.put("Berlin", .{ + .name = "Berlin, Germany", + .coords = .{ .latitude = 52.5200, .longitude = 13.4050 }, + }); + + var buffer: [1024]u8 = undefined; + var writer = std.Io.Writer.fixed(&buffer); + try cache1.save(&writer); + + var cache2 = std.StringHashMap(CachedLocation).init(allocator); + defer { + var it = cache2.iterator(); + while (it.next()) |entry| { + allocator.free(entry.key_ptr.*); + allocator.free(entry.value_ptr.name); + } + cache2.deinit(); + } + + try load(allocator, &cache2, buffer[0..writer.end]); + + const berlin = cache2.get("Berlin"); + try std.testing.expect(berlin != null); + try std.testing.expectEqualStrings("Berlin, Germany", berlin.?.name); + try std.testing.expectApproxEqAbs(@as(f64, 52.5200), berlin.?.coords.latitude, 0.0001); + try std.testing.expectApproxEqAbs(@as(f64, 13.4050), berlin.?.coords.longitude, 0.0001); +}