datafog-core 0.4.3__tar.gz → 0.4.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. {datafog_core-0.4.3 → datafog_core-0.4.4}/Cargo.lock +4 -4
  2. {datafog_core-0.4.3 → datafog_core-0.4.4}/PKG-INFO +21 -1
  3. {datafog_core-0.4.3/crates/core → datafog_core-0.4.4}/README.md +20 -0
  4. {datafog_core-0.4.3 → datafog_core-0.4.4}/bindings/python/Cargo.toml +1 -1
  5. {datafog_core-0.4.3 → datafog_core-0.4.4}/crates/core/Cargo.toml +1 -1
  6. {datafog_core-0.4.3 → datafog_core-0.4.4/crates/core}/README.md +20 -0
  7. {datafog_core-0.4.3 → datafog_core-0.4.4}/crates/core/src/lib.rs +208 -4
  8. {datafog_core-0.4.3 → datafog_core-0.4.4}/pyproject.toml +1 -1
  9. {datafog_core-0.4.3 → datafog_core-0.4.4}/Cargo.toml +0 -0
  10. {datafog_core-0.4.3 → datafog_core-0.4.4}/bindings/python/src/lib.rs +0 -0
  11. {datafog_core-0.4.3 → datafog_core-0.4.4}/bindings/python/tests/api_key_conformance.py +0 -0
  12. {datafog_core-0.4.3 → datafog_core-0.4.4}/bindings/python/tests/bearer_token_conformance.py +0 -0
  13. {datafog_core-0.4.3 → datafog_core-0.4.4}/bindings/python/tests/capabilities_conformance.py +0 -0
  14. {datafog_core-0.4.3 → datafog_core-0.4.4}/bindings/python/tests/credential_overlap_conformance.py +0 -0
  15. {datafog_core-0.4.3 → datafog_core-0.4.4}/bindings/python/tests/credential_uri_conformance.py +0 -0
  16. {datafog_core-0.4.3 → datafog_core-0.4.4}/bindings/python/tests/email_context_conformance.py +0 -0
  17. {datafog_core-0.4.3 → datafog_core-0.4.4}/bindings/python/tests/german_conformance.py +0 -0
  18. {datafog_core-0.4.3 → datafog_core-0.4.4}/bindings/python/tests/jwt_conformance.py +0 -0
  19. {datafog_core-0.4.3 → datafog_core-0.4.4}/bindings/python/tests/npi_conformance.py +0 -0
  20. {datafog_core-0.4.3 → datafog_core-0.4.4}/bindings/python/tests/private_key_conformance.py +0 -0
  21. {datafog_core-0.4.3 → datafog_core-0.4.4}/bindings/python/tests/requirements-typing.txt +0 -0
  22. {datafog_core-0.4.3 → datafog_core-0.4.4}/bindings/python/tests/synthetic_fixtures.py +0 -0
  23. {datafog_core-0.4.3 → datafog_core-0.4.4}/bindings/python/tests/test_installed.py +0 -0
  24. {datafog_core-0.4.3 → datafog_core-0.4.4}/bindings/python/tests/test_python49_integration.py +0 -0
  25. {datafog_core-0.4.3 → datafog_core-0.4.4}/bindings/python/tests/test_typing.py +0 -0
  26. {datafog_core-0.4.3 → datafog_core-0.4.4}/bindings/python/tests/us_routing_number_conformance.py +0 -0
  27. {datafog_core-0.4.3 → datafog_core-0.4.4}/bindings/python/tests/uuid_conformance.py +0 -0
  28. {datafog_core-0.4.3 → datafog_core-0.4.4}/crates/core/examples/german_benchmark.rs +0 -0
  29. {datafog_core-0.4.3 → datafog_core-0.4.4}/crates/core/examples/scan_benchmark.rs +0 -0
  30. {datafog_core-0.4.3 → datafog_core-0.4.4}/crates/core/src/api_key.rs +0 -0
  31. {datafog_core-0.4.3 → datafog_core-0.4.4}/crates/core/src/bearer_token.rs +0 -0
  32. {datafog_core-0.4.3 → datafog_core-0.4.4}/crates/core/src/capabilities.rs +0 -0
  33. {datafog_core-0.4.3 → datafog_core-0.4.4}/crates/core/src/credential_uri.rs +0 -0
  34. {datafog_core-0.4.3 → datafog_core-0.4.4}/crates/core/src/email_context.rs +0 -0
  35. {datafog_core-0.4.3 → datafog_core-0.4.4}/crates/core/src/german.rs +0 -0
  36. {datafog_core-0.4.3 → datafog_core-0.4.4}/crates/core/src/jwt.rs +0 -0
  37. {datafog_core-0.4.3 → datafog_core-0.4.4}/crates/core/src/npi.rs +0 -0
  38. {datafog_core-0.4.3 → datafog_core-0.4.4}/crates/core/src/offsets.rs +0 -0
  39. {datafog_core-0.4.3 → datafog_core-0.4.4}/crates/core/src/private_key.rs +0 -0
  40. {datafog_core-0.4.3 → datafog_core-0.4.4}/crates/core/src/scan_selection_tests.rs +0 -0
  41. {datafog_core-0.4.3 → datafog_core-0.4.4}/crates/core/src/selection_tests.rs +0 -0
  42. {datafog_core-0.4.3 → datafog_core-0.4.4}/crates/core/src/structured.rs +0 -0
  43. {datafog_core-0.4.3 → datafog_core-0.4.4}/crates/core/src/us_routing_number.rs +0 -0
  44. {datafog_core-0.4.3 → datafog_core-0.4.4}/crates/core/src/uuid.rs +0 -0
  45. {datafog_core-0.4.3 → datafog_core-0.4.4}/crates/core/tests/api_key.rs +0 -0
  46. {datafog_core-0.4.3 → datafog_core-0.4.4}/crates/core/tests/bearer_token.rs +0 -0
  47. {datafog_core-0.4.3 → datafog_core-0.4.4}/crates/core/tests/capabilities.rs +0 -0
  48. {datafog_core-0.4.3 → datafog_core-0.4.4}/crates/core/tests/credential_overlaps.rs +0 -0
  49. {datafog_core-0.4.3 → datafog_core-0.4.4}/crates/core/tests/credential_uri.rs +0 -0
  50. {datafog_core-0.4.3 → datafog_core-0.4.4}/crates/core/tests/email_context.rs +0 -0
  51. {datafog_core-0.4.3 → datafog_core-0.4.4}/crates/core/tests/german.rs +0 -0
  52. {datafog_core-0.4.3 → datafog_core-0.4.4}/crates/core/tests/jwt.rs +0 -0
  53. {datafog_core-0.4.3 → datafog_core-0.4.4}/crates/core/tests/npi.rs +0 -0
  54. {datafog_core-0.4.3 → datafog_core-0.4.4}/crates/core/tests/private_key.rs +0 -0
  55. {datafog_core-0.4.3 → datafog_core-0.4.4}/crates/core/tests/support/synthetic_fixtures.rs +0 -0
  56. {datafog_core-0.4.3 → datafog_core-0.4.4}/crates/core/tests/us_routing_number.rs +0 -0
  57. {datafog_core-0.4.3 → datafog_core-0.4.4}/crates/core/tests/uuid.rs +0 -0
  58. {datafog_core-0.4.3 → datafog_core-0.4.4}/python/datafog_core/__init__.py +0 -0
  59. {datafog_core-0.4.3 → datafog_core-0.4.4}/python/datafog_core/__init__.pyi +0 -0
  60. {datafog_core-0.4.3 → datafog_core-0.4.4}/python/datafog_core/py.typed +0 -0
@@ -80,7 +80,7 @@ checksum = "914a755b7c2d4af2bdcff7ce1739e2db9a1b81a9b07123d8015786ae03c0980d"
80
80
 
81
81
  [[package]]
82
82
  name = "datafog-core"
83
- version = "0.4.3"
83
+ version = "0.4.4"
84
84
  dependencies = [
85
85
  "base64",
86
86
  "futures",
@@ -94,7 +94,7 @@ dependencies = [
94
94
 
95
95
  [[package]]
96
96
  name = "datafog-core-python"
97
- version = "0.4.3"
97
+ version = "0.4.4"
98
98
  dependencies = [
99
99
  "datafog-core",
100
100
  "pyo3",
@@ -104,7 +104,7 @@ dependencies = [
104
104
 
105
105
  [[package]]
106
106
  name = "datafog-node"
107
- version = "0.4.3"
107
+ version = "0.4.4"
108
108
  dependencies = [
109
109
  "datafog-core",
110
110
  "napi",
@@ -115,7 +115,7 @@ dependencies = [
115
115
 
116
116
  [[package]]
117
117
  name = "datafog-wasm"
118
- version = "0.4.3"
118
+ version = "0.4.4"
119
119
  dependencies = [
120
120
  "datafog-core",
121
121
  "serde",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: datafog-core
3
- Version: 0.4.3
3
+ Version: 0.4.4
4
4
  Classifier: Development Status :: 3 - Alpha
5
5
  Classifier: License :: OSI Approved :: MIT License
6
6
  Classifier: Programming Language :: Python :: 3
@@ -38,6 +38,13 @@ existing detectors and returns field paths plus string-local findings. No model
38
38
  or dictionary download is needed. See [person-field discovery](docs/guides/person-discovery.mdx)
39
39
  for `scan_structured` / `scanStructured` and structured transformation APIs.
40
40
 
41
+ Phone candidates without `+` are interpreted as North American numbers: ten
42
+ digits, optionally preceded by `1`, with area and exchange codes starting with
43
+ `2` through `9`. Candidates starting with `+` retain the existing permissive
44
+ 7–15 digit check, including shortened synthetic examples such as `+1-555-1002`.
45
+ These checks do not validate country calling codes or assigned phone numbers,
46
+ and numeric identifiers that fit the accepted patterns can still match `PHONE`.
47
+
41
48
  Both ranges use zero-based, end-exclusive offsets. The byte range addresses the
42
49
  UTF-8 input; the code-point range addresses Unicode scalar values. Rule-based
43
50
  detectors currently report no confidence score. Node.js and browser WASM also
@@ -86,6 +93,19 @@ or `format: "sql"` to preserve email boundaries. See the
86
93
  [email source-context policy](docs/guides/email-boundaries.mdx) for supported
87
94
  syntax, raw escaped spans, and limitations. Default text scanning is unchanged.
88
95
 
96
+ ## Credit-card detection and decimal numbers
97
+
98
+ To reduce decimal false positives, `CREDIT_CARD` detection suppresses contiguous
99
+ digit candidates immediately after a digit followed by `.` or `,`, such as
100
+ `3.4111111111111111` and `3,4111111111111111`. Cards containing spaces or hyphens,
101
+ such as `3.4111-1111-1111-1111`, still undergo the normal card checks.
102
+
103
+ In plain text, `3,4111111111111111` could also be two CSV fields: `3` and a card
104
+ number. The detector cannot distinguish that structure from a comma-decimal
105
+ number, so it suppresses the card candidate. For CSV input, parse the CSV and
106
+ scan each field separately so a card value is checked without the preceding
107
+ field and delimiter.
108
+
89
109
  ## Packages
90
110
 
91
111
  | Runtime | Distribution | Import | Status |
@@ -15,6 +15,13 @@ existing detectors and returns field paths plus string-local findings. No model
15
15
  or dictionary download is needed. See [person-field discovery](docs/guides/person-discovery.mdx)
16
16
  for `scan_structured` / `scanStructured` and structured transformation APIs.
17
17
 
18
+ Phone candidates without `+` are interpreted as North American numbers: ten
19
+ digits, optionally preceded by `1`, with area and exchange codes starting with
20
+ `2` through `9`. Candidates starting with `+` retain the existing permissive
21
+ 7–15 digit check, including shortened synthetic examples such as `+1-555-1002`.
22
+ These checks do not validate country calling codes or assigned phone numbers,
23
+ and numeric identifiers that fit the accepted patterns can still match `PHONE`.
24
+
18
25
  Both ranges use zero-based, end-exclusive offsets. The byte range addresses the
19
26
  UTF-8 input; the code-point range addresses Unicode scalar values. Rule-based
20
27
  detectors currently report no confidence score. Node.js and browser WASM also
@@ -63,6 +70,19 @@ or `format: "sql"` to preserve email boundaries. See the
63
70
  [email source-context policy](docs/guides/email-boundaries.mdx) for supported
64
71
  syntax, raw escaped spans, and limitations. Default text scanning is unchanged.
65
72
 
73
+ ## Credit-card detection and decimal numbers
74
+
75
+ To reduce decimal false positives, `CREDIT_CARD` detection suppresses contiguous
76
+ digit candidates immediately after a digit followed by `.` or `,`, such as
77
+ `3.4111111111111111` and `3,4111111111111111`. Cards containing spaces or hyphens,
78
+ such as `3.4111-1111-1111-1111`, still undergo the normal card checks.
79
+
80
+ In plain text, `3,4111111111111111` could also be two CSV fields: `3` and a card
81
+ number. The detector cannot distinguish that structure from a comma-decimal
82
+ number, so it suppresses the card candidate. For CSV input, parse the CSV and
83
+ scan each field separately so a card value is checked without the preceding
84
+ field and delimiter.
85
+
66
86
  ## Packages
67
87
 
68
88
  | Runtime | Distribution | Import | Status |
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "datafog-core-python"
3
- version = "0.4.3"
3
+ version = "0.4.4"
4
4
  edition = "2024"
5
5
  rust-version = "1.88"
6
6
  description = "Python bindings for datafog-core"
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "datafog-core"
3
- version = "0.4.3"
3
+ version = "0.4.4"
4
4
  edition = "2024"
5
5
  rust-version = "1.88"
6
6
  description = "Fast structured PII detection library"
@@ -15,6 +15,13 @@ existing detectors and returns field paths plus string-local findings. No model
15
15
  or dictionary download is needed. See [person-field discovery](docs/guides/person-discovery.mdx)
16
16
  for `scan_structured` / `scanStructured` and structured transformation APIs.
17
17
 
18
+ Phone candidates without `+` are interpreted as North American numbers: ten
19
+ digits, optionally preceded by `1`, with area and exchange codes starting with
20
+ `2` through `9`. Candidates starting with `+` retain the existing permissive
21
+ 7–15 digit check, including shortened synthetic examples such as `+1-555-1002`.
22
+ These checks do not validate country calling codes or assigned phone numbers,
23
+ and numeric identifiers that fit the accepted patterns can still match `PHONE`.
24
+
18
25
  Both ranges use zero-based, end-exclusive offsets. The byte range addresses the
19
26
  UTF-8 input; the code-point range addresses Unicode scalar values. Rule-based
20
27
  detectors currently report no confidence score. Node.js and browser WASM also
@@ -63,6 +70,19 @@ or `format: "sql"` to preserve email boundaries. See the
63
70
  [email source-context policy](docs/guides/email-boundaries.mdx) for supported
64
71
  syntax, raw escaped spans, and limitations. Default text scanning is unchanged.
65
72
 
73
+ ## Credit-card detection and decimal numbers
74
+
75
+ To reduce decimal false positives, `CREDIT_CARD` detection suppresses contiguous
76
+ digit candidates immediately after a digit followed by `.` or `,`, such as
77
+ `3.4111111111111111` and `3,4111111111111111`. Cards containing spaces or hyphens,
78
+ such as `3.4111-1111-1111-1111`, still undergo the normal card checks.
79
+
80
+ In plain text, `3,4111111111111111` could also be two CSV fields: `3` and a card
81
+ number. The detector cannot distinguish that structure from a comma-decimal
82
+ number, so it suppresses the card candidate. For CSV input, parse the CSV and
83
+ scan each field separately so a card value is checked without the preceding
84
+ field and delimiter.
85
+
66
86
  ## Packages
67
87
 
68
88
  | Runtime | Distribution | Import | Status |
@@ -3157,10 +3157,30 @@ fn is_valid_phone(candidate: &str) -> bool {
3157
3157
  .collect();
3158
3158
 
3159
3159
  if candidate.starts_with('+') {
3160
- (7..=15).contains(&digits.len())
3161
- } else {
3162
- digits.len() == 10 || (digits.len() == 11 && digits.starts_with('1'))
3160
+ return (7..=15).contains(&digits.len());
3163
3161
  }
3162
+
3163
+ let national_number = match digits.len() {
3164
+ 10 => digits.as_str(),
3165
+ 11 if digits.starts_with('1') => &digits[1..],
3166
+ _ => return false,
3167
+ };
3168
+
3169
+ follows_nanp_rule(national_number.as_bytes())
3170
+ }
3171
+
3172
+ /// Checks that the 1st and 4th digits of a 10-digit North American number are
3173
+ /// between 2 and 9. These are the first digits of the area code and the
3174
+ /// exchange code, and the North American Numbering Plan does not allow either
3175
+ /// to start with 0 or 1.
3176
+ ///
3177
+ /// This is a special case to stop Unix timestamps, such as `1800005580`, being
3178
+ /// detected as phone numbers. It only works while timestamps start with 1,
3179
+ /// which is true until 2033. It is not full phone number validation. If better
3180
+ /// vetting is needed, Google's libphonenumber is a good alternative.
3181
+ fn follows_nanp_rule(national_number: &[u8]) -> bool {
3182
+ let is_valid_code_start = |digit: u8| (b'2'..=b'9').contains(&digit);
3183
+ is_valid_code_start(national_number[0]) && is_valid_code_start(national_number[3])
3164
3184
  }
3165
3185
 
3166
3186
  fn detect_phone(text: &str, candidates: &mut Vec<Candidate>) {
@@ -3348,6 +3368,12 @@ fn card_parts_at(bytes: &[u8], start: usize) -> (usize, Vec<u8>) {
3348
3368
  (last_digit_end, digits)
3349
3369
  }
3350
3370
 
3371
+ /// A contiguous digit run after `3.` or `3,` may be a decimal fraction.
3372
+ /// Comma-separated fields are ambiguous without structured input context.
3373
+ fn follows_decimal_separator(bytes: &[u8], start: usize) -> bool {
3374
+ start >= 2 && matches!(bytes[start - 1], b'.' | b',') && bytes[start - 2].is_ascii_digit()
3375
+ }
3376
+
3351
3377
  fn detect_credit_card(text: &str, candidates: &mut Vec<Candidate>) {
3352
3378
  let bytes = text.as_bytes();
3353
3379
  let mut start = 0;
@@ -3361,7 +3387,10 @@ fn detect_credit_card(text: &str, candidates: &mut Vec<Candidate>) {
3361
3387
 
3362
3388
  let (end, digits) = card_parts_at(bytes, start);
3363
3389
 
3364
- if end == start || (end < bytes.len() && bytes[end].is_ascii_alphanumeric()) {
3390
+ if end == start
3391
+ || (end < bytes.len() && bytes[end].is_ascii_alphanumeric())
3392
+ || (follows_decimal_separator(bytes, start) && end - start == digits.len())
3393
+ {
3365
3394
  start += 1;
3366
3395
  continue;
3367
3396
  }
@@ -4365,6 +4394,122 @@ mod tests {
4365
4394
  assert!(scan("order212-555-0100x").is_empty());
4366
4395
  }
4367
4396
 
4397
+ #[test]
4398
+ fn detects_phone_numbers_following_nanp_rule() {
4399
+ for phone in [
4400
+ "2125550100",
4401
+ "212-555-0100",
4402
+ "212.555.0100",
4403
+ "1-212-555-0100",
4404
+ "12125550100",
4405
+ "9995550100",
4406
+ "2122220100",
4407
+ ] {
4408
+ let text = format!("Call {phone}.");
4409
+ assert_eq!(
4410
+ scan(&text),
4411
+ vec![expected_ascii_finding(
4412
+ "PHONE",
4413
+ phone,
4414
+ 5,
4415
+ 5 + phone.len(),
4416
+ "datafog-core/phone",
4417
+ )],
4418
+ "{text}"
4419
+ );
4420
+ }
4421
+ }
4422
+
4423
+ #[test]
4424
+ fn rejects_phone_candidates_breaking_nanp_rule() {
4425
+ for text in [
4426
+ "Call 0125550100.", // area code starts with 0
4427
+ "Call (112) 555-0100.", // area code starts with 1
4428
+ "Call 212-055-0100.", // exchange code starts with 0
4429
+ "Call 1-212-155-0100.", // exchange code starts with 1
4430
+ "\"start\": 1800005580,", // Unix timestamp
4431
+ "\"end\": 1800021600", // Unix timestamp
4432
+ ] {
4433
+ assert!(scan(text).is_empty(), "{text}");
4434
+ }
4435
+ }
4436
+
4437
+ #[test]
4438
+ fn preserves_permissive_plus_phone_detection() {
4439
+ for phone in [
4440
+ "+1234567",
4441
+ "+1-555-1002",
4442
+ "+1-112-555-0100",
4443
+ "+1-212-155-0100",
4444
+ "+44 20 7946 0958",
4445
+ "+999222333444555",
4446
+ ] {
4447
+ let text = format!("Call {phone}.");
4448
+ assert_eq!(
4449
+ scan(&text),
4450
+ vec![expected_ascii_finding(
4451
+ "PHONE",
4452
+ phone,
4453
+ 5,
4454
+ 5 + phone.len(),
4455
+ "datafog-core/phone",
4456
+ )],
4457
+ "{text}"
4458
+ );
4459
+ }
4460
+ }
4461
+
4462
+ #[test]
4463
+ fn rejects_plus_phone_candidates_outside_length_or_boundary_rules() {
4464
+ for text in [
4465
+ "Call +123456.",
4466
+ "Call +9992223334445556.",
4467
+ "order+44 20 7946 0958",
4468
+ "Call +44 20 7946 0958x",
4469
+ ] {
4470
+ assert!(scan(text).is_empty(), "{text}");
4471
+ }
4472
+ }
4473
+
4474
+ #[test]
4475
+ fn reports_phone_ranges_in_unicode_text() {
4476
+ for phone in ["212-555-0100", "1-212-555-0100", "+1-555-1002"] {
4477
+ let text = format!("☎👋 {phone}。");
4478
+ let findings = scan(&text);
4479
+ assert_eq!(
4480
+ findings,
4481
+ vec![expected_finding(
4482
+ "PHONE",
4483
+ phone,
4484
+ (8, 8 + phone.len()),
4485
+ (3, 3 + phone.len()),
4486
+ "datafog-core/phone",
4487
+ )],
4488
+ "{text}"
4489
+ );
4490
+ let finding = &findings[0];
4491
+ assert_eq!(
4492
+ &text[finding.byte_range.start..finding.byte_range.end],
4493
+ phone
4494
+ );
4495
+ assert_eq!(
4496
+ text.chars()
4497
+ .skip(finding.codepoint_range.start)
4498
+ .take(finding.codepoint_range.end - finding.codepoint_range.start)
4499
+ .collect::<String>(),
4500
+ phone
4501
+ );
4502
+ assert_eq!(
4503
+ utf16_range(&text, finding.byte_range).unwrap(),
4504
+ TextRange {
4505
+ start: 4,
4506
+ end: 4 + phone.len(),
4507
+ }
4508
+ );
4509
+ }
4510
+ assert!(scan("☎👋 1800005580。").is_empty());
4511
+ }
4512
+
4368
4513
  #[test]
4369
4514
  fn detects_dashed_ssn() {
4370
4515
  assert_eq!(
@@ -4415,11 +4560,70 @@ mod tests {
4415
4560
  assert_eq!(scan("6011111111111117")[0].entity_type, "CREDIT_CARD");
4416
4561
  }
4417
4562
 
4563
+ #[test]
4564
+ fn detects_credit_card_after_separator_without_leading_digit() {
4565
+ for prefix in [".", ",", "no.", "no,"] {
4566
+ for digits in ["4222222222222", "4111111111111111", "4111111111111111110"] {
4567
+ let text = format!("{prefix}{digits}");
4568
+ assert_eq!(
4569
+ scan(&text),
4570
+ vec![expected_ascii_finding(
4571
+ "CREDIT_CARD",
4572
+ digits,
4573
+ prefix.len(),
4574
+ prefix.len() + digits.len(),
4575
+ "datafog-core/credit-card",
4576
+ )],
4577
+ "{text}"
4578
+ );
4579
+ }
4580
+ }
4581
+ }
4582
+
4583
+ #[test]
4584
+ fn detects_formatted_credit_card_after_digit_and_separator() {
4585
+ for prefix in ["3.", "3,"] {
4586
+ for card in [
4587
+ "4222-2222-22222",
4588
+ "4111-1111-1111-1111",
4589
+ "4111 1111 1111 1111",
4590
+ "4111-1111-1111-1111110",
4591
+ ] {
4592
+ let text = format!("{prefix}{card}");
4593
+ assert_eq!(
4594
+ scan(&text)
4595
+ .into_iter()
4596
+ .filter(|finding| finding.entity_type == "CREDIT_CARD")
4597
+ .collect::<Vec<_>>(),
4598
+ vec![expected_ascii_finding(
4599
+ "CREDIT_CARD",
4600
+ card,
4601
+ prefix.len(),
4602
+ prefix.len() + card.len(),
4603
+ "datafog-core/credit-card",
4604
+ )],
4605
+ "{text}"
4606
+ );
4607
+ }
4608
+ }
4609
+ }
4610
+
4418
4611
  #[test]
4419
4612
  fn rejects_invalid_credit_card_candidates() {
4420
4613
  assert!(scan("4111-1111-1111-1112").is_empty()); // bad Luhn checksum
4421
4614
  assert!(scan("1234-5678-9012-3452").is_empty()); // unsupported prefix
4422
4615
  assert!(scan("x4111-1111-1111-1111y").is_empty()); // embedded
4616
+ for fraction in [
4617
+ "4222222222222",
4618
+ "4111111111111111",
4619
+ "4111111111111111110",
4620
+ "41111111111111111100",
4621
+ ] {
4622
+ for separator in [".", ","] {
4623
+ let text = format!("3{separator}{fraction}");
4624
+ assert!(scan(&text).is_empty(), "{text}"); // decimal fraction
4625
+ }
4626
+ }
4423
4627
  }
4424
4628
 
4425
4629
  #[test]
@@ -4,7 +4,7 @@ build-backend = "maturin"
4
4
 
5
5
  [project]
6
6
  name = "datafog-core"
7
- version = "0.4.3"
7
+ version = "0.4.4"
8
8
  description = "Fast structured PII detection implemented in Rust"
9
9
  readme = "README.md"
10
10
  license = "MIT"
File without changes