const std = @import("std"); pub fn parseToolArgsObject(alloc: std.mem.Allocator, args_json: []const u8) !std.json.ObjectMap { const parsed = try std.json.parseFromSlice(std.json.Value, alloc, args_json, .{}); // The returned object map is backed by the parse allocation; callers should // use an arena or allocator lifetime that outlives all borrowed values. if (parsed.value != .object) return error.InvalidToolArguments; return parsed.value.object; } pub fn requiredStringArg(args: std.json.ObjectMap, key: []const u8) ![]const u8 { const value = args.get(key) orelse return error.InvalidToolArguments; if (value != .string) return error.InvalidToolArguments; return value.string; } pub fn optionalStringArg(args: std.json.ObjectMap, key: []const u8) ?[]const u8 { const value = args.get(key) orelse return null; if (value != .string) return null; return value.string; } pub fn optionalBoolArg(args: std.json.ObjectMap, key: []const u8) ?bool { const value = args.get(key) orelse return null; if (value != .bool) return null; return value.bool; } pub fn optionalIntArg(args: std.json.ObjectMap, key: []const u8) ?i64 { const value = args.get(key) orelse return null; if (value != .integer) return null; return value.integer; } test "parseToolArgsObject parses object roots" { var arena_state = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena_state.deinit(); const arena = arena_state.allocator(); const args = try parseToolArgsObject(arena, "{\"path\":\"src/main.zig\"}"); try std.testing.expectEqualStrings("src/main.zig", args.get("path").?.string); } test "parseToolArgsObject rejects non-object roots" { var arena_state = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena_state.deinit(); const arena = arena_state.allocator(); try std.testing.expectError(error.InvalidToolArguments, parseToolArgsObject(arena, "[]")); } test "requiredStringArg rejects missing and wrong-type values" { var arena_state = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena_state.deinit(); const arena = arena_state.allocator(); const args = try parseToolArgsObject(arena, "{\"path\":\"src/main.zig\",\"count\":3}"); try std.testing.expectEqualStrings("src/main.zig", try requiredStringArg(args, "path")); try std.testing.expectError(error.InvalidToolArguments, requiredStringArg(args, "missing")); try std.testing.expectError(error.InvalidToolArguments, requiredStringArg(args, "count")); } test "optional typed args return payloads only for matching tags" { var arena_state = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena_state.deinit(); const arena = arena_state.allocator(); const args = try parseToolArgsObject(arena, "{\"name\":\"fx\",\"enabled\":true,\"count\":3,\"other\":1.25}"); try std.testing.expectEqualStrings("fx", optionalStringArg(args, "name").?); try std.testing.expect(optionalStringArg(args, "missing") == null); try std.testing.expect(optionalStringArg(args, "enabled") == null); try std.testing.expectEqual(true, optionalBoolArg(args, "enabled").?); try std.testing.expect(optionalBoolArg(args, "missing") == null); try std.testing.expect(optionalBoolArg(args, "name") == null); try std.testing.expectEqual(@as(i64, 3), optionalIntArg(args, "count").?); try std.testing.expect(optionalIntArg(args, "missing") == null); try std.testing.expect(optionalIntArg(args, "other") == null); }