diff --git a/src/isbn.zig b/src/isbn.zig index e49f2ab..57af202 100644 --- a/src/isbn.zig +++ b/src/isbn.zig @@ -52,6 +52,9 @@ pub const ISBN = union(enum) { return tmp; }, 13 => { + // 'X' is only valid as the check digit of an ISBN-10; the + // scan loop can only have accepted it at index 9. + if (buf[9] == 'X') return error.ParseError; var tmp: ISBN = .{ .@"13" = undefined, }; @@ -226,6 +229,53 @@ test "13 1" { try std.testing.expectEqual('7', c); } +test "13 2" { + const x: ISBN = try .parse("979-10-90636-07-1"); + try std.testing.expect(x == .@"13"); + try std.testing.expectEqualSlices(u8, "9791090636071", &x.@"13"); + const c = x.computeCheckDigit(); + try std.testing.expectEqual('1', c); +} + +test "10 check digit 0" { + const x: ISBN = try .parse("0-00-000000-0"); + try std.testing.expect(x == .@"10"); + const c = x.computeCheckDigit(); + try std.testing.expectEqual('0', c); +} + +test "13 check digit 0" { + const x: ISBN = try .parse("978-0-00-000020-0"); + try std.testing.expect(x == .@"13"); + const c = x.computeCheckDigit(); + try std.testing.expectEqual('0', c); +} + +test "parse rejects malformed input" { + try std.testing.expectError(error.ParseError, ISBN.parse("")); + try std.testing.expectError(error.ParseError, ISBN.parse("---")); + try std.testing.expectError(error.ParseError, ISBN.parse("0-306-40615")); // 9 digits + try std.testing.expectError(error.ParseError, ISBN.parse("0-306-40615-22")); // 11 digits + try std.testing.expectError(error.ParseError, ISBN.parse("978-0-306-40615")); // 12 digits + try std.testing.expectError(error.ParseError, ISBN.parse("978-0-306-40615-72")); // 14 digits + try std.testing.expectError(error.ParseError, ISBN.parse("0-306-4X615-2")); // X in wrong position + try std.testing.expectError(error.ParseError, ISBN.parse("0-306-40615-A")); // invalid character + try std.testing.expectError(error.ParseError, ISBN.parse("0 306 40615 2")); // space separator +} + +test "parse rejects X in an ISBN-13" { + // 'X' - '0' is 40, which at position 9 (weight 3) contributes + // 120 = 0 (mod 10), so this string is checksum-valid and must be + // rejected before the check-digit comparison. + try std.testing.expectError(error.ParseError, ISBN.parse("978-0-306-40X15-5")); +} + +test "parse rejects invalid check digits" { + try std.testing.expectError(error.InvalidCheckDigit, ISBN.parse("0-306-40615-3")); + try std.testing.expectError(error.InvalidCheckDigit, ISBN.parse("0-8044-2957-4")); // expects X + try std.testing.expectError(error.InvalidCheckDigit, ISBN.parse("978-0-306-40615-8")); +} + test "upgrade and eql" { const ten: ISBN = try .parse("0-306-40615-2"); const thirteen: ISBN = try .parse("978-0-306-40615-7");