Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ OpenDataMask connects to your source databases, applies configurable masking/gen
- **File (CSV/JSON)** — AES-encrypted upload/download, multipart REST endpoints

### Data Masking & Generation
- **47 generator types** powered by [Datafaker](https://datafaker.net/): names, addresses, emails, phones, SSNs, credit cards, IBANs, IPs, VINs, medical codes, and more
- **61 generator types** powered by [Datafaker](https://datafaker.net/): names, addresses, emails, phones, SSNs, credit cards, IBANs, IPs, VINs, medical codes, job titles, nationalities, and more
- **Composite generators**: `PARTIAL_MASK`, `FORMAT_PRESERVING`, `CONDITIONAL`, `SEQUENTIAL`
- **Deterministic consistency**: HMAC-SHA256 seeding ensures same input → same output across runs; linked columns share a Faker instance
- **Generator presets**: 24 built-in system presets; workspace-level custom presets; one-click apply
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,20 @@ class GeneratorService(
GeneratorType.LICENSE_PLATE -> faker.vehicle().licensePlate()
GeneratorType.ORGANIZATION -> faker.company().name()
GeneratorType.ACCOUNT_NUMBER -> faker.regexify("[0-9]{10}")
GeneratorType.TITLE -> faker.name().prefix()
GeneratorType.JOB_TITLE -> faker.job().title()
GeneratorType.NATIONALITY -> faker.nation().nationality()
GeneratorType.COMPANY_NAME -> faker.company().name()
GeneratorType.DEPARTMENT -> faker.commerce().department()
GeneratorType.CURRENCY_CODE -> faker.currency().code()
GeneratorType.DOMAIN_NAME -> faker.internet().domainName()
GeneratorType.USER_AGENT -> faker.internet().userAgent().replace(Regex("\\s+"), " ")
GeneratorType.LATITUDE -> faker.address().latitude()
Comment on lines +112 to +115
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

USER_AGENT generation compiles a new Regex("\\s+") on every call via replace(Regex(...)), which can be a hot path when generating large datasets. Consider using a precompiled regex constant (e.g., in a companion object) or an alternative that avoids per-call regex construction.

Copilot uses AI. Check for mistakes.
GeneratorType.LONGITUDE -> faker.address().longitude()
GeneratorType.TIME_ZONE -> faker.address().timeZone()
GeneratorType.BOOLEAN -> faker.bool().bool()
GeneratorType.LOREM -> faker.lorem().paragraph()
GeneratorType.TIMESTAMP -> java.sql.Timestamp(faker.date().past(365 * 10, java.util.concurrent.TimeUnit.DAYS).time)
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TIMESTAMP uses a fully-qualified java.util.concurrent.TimeUnit.DAYS while earlier generators in the same method use the imported TimeUnit.DAYS. For consistency/readability, use the same TimeUnit reference throughout this when block.

Suggested change
GeneratorType.TIMESTAMP -> java.sql.Timestamp(faker.date().past(365 * 10, java.util.concurrent.TimeUnit.DAYS).time)
GeneratorType.TIMESTAMP -> java.sql.Timestamp(faker.date().past(365 * 10, TimeUnit.DAYS).time)

Copilot uses AI. Check for mistakes.
GeneratorType.PARTIAL_MASK -> {
val s = originalValue?.toString() ?: return null
val maskChar = (params?.get("maskChar") ?: "*").firstOrNull() ?: '*'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,18 @@ enum class GeneratorType {
VIN, LICENSE_PLATE,
// Other
ORGANIZATION, ACCOUNT_NUMBER,
// Personal extended
TITLE, JOB_TITLE, NATIONALITY,
// Business
COMPANY_NAME, DEPARTMENT,
// Financial extended
CURRENCY_CODE,
// Network extended
DOMAIN_NAME, USER_AGENT,
// Location extended
LATITUDE, LONGITUDE, TIME_ZONE,
// Data utilities
BOOLEAN, LOREM, TIMESTAMP,
// Composite / PK generators
CONDITIONAL, PARTIAL_MASK, FORMAT_PRESERVING, SEQUENTIAL, RANDOM_INT
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -202,4 +202,102 @@ class GeneratorServiceTest {
assertNotNull(result)
assertInstanceOf(java.math.BigDecimal::class.java, result)
}

@Test
fun `TITLE generates a non-blank name prefix`() {
val result = service.generateValue(GeneratorType.TITLE, null, null)
assertNotNull(result)
assertTrue((result as String).isNotBlank())
}

@Test
fun `JOB_TITLE generates a non-blank job title`() {
val result = service.generateValue(GeneratorType.JOB_TITLE, null, null)
assertNotNull(result)
assertTrue((result as String).isNotBlank())
}

@Test
fun `NATIONALITY generates a non-blank nationality`() {
val result = service.generateValue(GeneratorType.NATIONALITY, null, null)
assertNotNull(result)
assertTrue((result as String).isNotBlank())
}

@Test
fun `COMPANY_NAME generates a non-blank company name`() {
val result = service.generateValue(GeneratorType.COMPANY_NAME, null, null)
assertNotNull(result)
assertTrue((result as String).isNotBlank())
}

@Test
fun `DEPARTMENT generates a non-blank department`() {
val result = service.generateValue(GeneratorType.DEPARTMENT, null, null)
assertNotNull(result)
assertTrue((result as String).isNotBlank())
}

@Test
fun `CURRENCY_CODE generates a non-blank currency code`() {
val result = service.generateValue(GeneratorType.CURRENCY_CODE, null, null)
assertNotNull(result)
assertTrue((result as String).isNotBlank())
}

@Test
fun `DOMAIN_NAME generates a non-blank domain`() {
val result = service.generateValue(GeneratorType.DOMAIN_NAME, null, null)
assertNotNull(result)
assertTrue((result as String).isNotBlank())
}

@Test
fun `USER_AGENT generates a non-blank user agent`() {
val result = service.generateValue(GeneratorType.USER_AGENT, null, null)
assertNotNull(result)
assertTrue((result as String).isNotBlank())
}

@Test
fun `LATITUDE generates a non-blank latitude`() {
val result = service.generateValue(GeneratorType.LATITUDE, null, null)
assertNotNull(result)
assertTrue((result as String).isNotBlank())
}

@Test
fun `LONGITUDE generates a non-blank longitude`() {
val result = service.generateValue(GeneratorType.LONGITUDE, null, null)
assertNotNull(result)
assertTrue((result as String).isNotBlank())
}

@Test
fun `TIME_ZONE generates a non-blank timezone`() {
val result = service.generateValue(GeneratorType.TIME_ZONE, null, null)
assertNotNull(result)
assertTrue((result as String).isNotBlank())
}

@Test
fun `BOOLEAN generates a Boolean value`() {
val result = service.generateValue(GeneratorType.BOOLEAN, null, null)
assertNotNull(result)
assertInstanceOf(java.lang.Boolean::class.java, result)
}

@Test
fun `LOREM generates a non-blank paragraph`() {
val result = service.generateValue(GeneratorType.LOREM, null, null)
assertNotNull(result)
assertTrue((result as String).isNotBlank())
}

@Test
fun `TIMESTAMP generates a java sql Timestamp`() {
val result = service.generateValue(GeneratorType.TIMESTAMP, null, null)
assertNotNull(result)
assertInstanceOf(java.sql.Timestamp::class.java, result)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,12 @@ class EnumAlignmentTest {
"IP_ADDRESS", "IPV6_ADDRESS", "MAC_ADDRESS", "URL",
"VIN", "LICENSE_PLATE",
"ORGANIZATION", "ACCOUNT_NUMBER",
"TITLE", "JOB_TITLE", "NATIONALITY",
"COMPANY_NAME", "DEPARTMENT",
"CURRENCY_CODE",
"DOMAIN_NAME", "USER_AGENT",
"LATITUDE", "LONGITUDE", "TIME_ZONE",
"BOOLEAN", "LOREM", "TIMESTAMP",
"CONDITIONAL", "PARTIAL_MASK", "FORMAT_PRESERVING", "SEQUENTIAL", "RANDOM_INT"
)
assertEquals(expected, values, "GeneratorType values do not match canonical set")
Expand Down
4 changes: 4 additions & 0 deletions docs/user-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,10 @@ OpenDataMask includes 60+ built-in generators. Key examples:
| `IP_ADDRESS` | `192.168.1.42` |
| `ICD_CODE` | `J45.909` |
| `IBAN` | `GB29NWBK60161331926819` |
| `JOB_TITLE` | `Senior Software Engineer` |
| `NATIONALITY` | `Canadian` |
| `DOMAIN_NAME` | `example.com` |
| `TIMESTAMP` | `2024-06-15 14:30:00` |
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The TIMESTAMP example (2024-06-15 14:30:00) likely won’t match actual output: java.sql.Timestamp.toString() typically includes fractional seconds (e.g., .0 or milliseconds). Consider updating the example to include fractional seconds or clarifying the displayed format.

Suggested change
| `TIMESTAMP` | `2024-06-15 14:30:00` |
| `TIMESTAMP` | `2024-06-15 14:30:00.0` |

Copilot uses AI. Check for mistakes.
| `PARTIAL_MASK` | `J*** D***` (preserves format) |
| `NULL` | `null` |
| `CONSTANT` | Fixed value (configured in params) |
Expand Down
10 changes: 6 additions & 4 deletions docs/website/guide.html
Original file line number Diff line number Diff line change
Expand Up @@ -130,12 +130,14 @@ <h2 id="generators">Generator Types</h2>
<p>OpenDataMask includes 60+ built-in generators covering personal, financial, medical, and network data:</p>
<table>
<tr><th>Category</th><th>Generators</th></tr>
<tr><td>Personal</td><td>NAME, FIRST_NAME, LAST_NAME, EMAIL, PHONE, BIRTH_DATE, GENDER</td></tr>
<tr><td>Address</td><td>ADDRESS, STREET_ADDRESS, CITY, STATE, ZIP_CODE, COUNTRY, GPS_COORDINATES</td></tr>
<tr><td>Personal</td><td>NAME, FIRST_NAME, LAST_NAME, EMAIL, PHONE, BIRTH_DATE, GENDER, TITLE, JOB_TITLE, NATIONALITY</td></tr>
<tr><td>Address</td><td>ADDRESS, STREET_ADDRESS, CITY, STATE, ZIP_CODE, COUNTRY, GPS_COORDINATES, LATITUDE, LONGITUDE, TIME_ZONE</td></tr>
Comment on lines +133 to +134
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This row is labeled Address but now includes LATITUDE, LONGITUDE, and TIME_ZONE, which are described as "Location" generators in the PR description. Consider renaming the category (e.g., Location) or splitting into a separate row to keep the table taxonomy consistent.

Copilot uses AI. Check for mistakes.
<tr><td>Identity</td><td>SSN, PASSPORT_NUMBER, DRIVERS_LICENSE, MEDICAL_RECORD_NUMBER</td></tr>
<tr><td>Financial</td><td>CREDIT_CARD, IBAN, SWIFT_CODE, MONEY_AMOUNT, BTC_ADDRESS, ACCOUNT_NUMBER</td></tr>
<tr><td>Network</td><td>IP_ADDRESS, IPV6_ADDRESS, MAC_ADDRESS, URL</td></tr>
<tr><td>Financial</td><td>CREDIT_CARD, IBAN, SWIFT_CODE, MONEY_AMOUNT, BTC_ADDRESS, ACCOUNT_NUMBER, CURRENCY_CODE</td></tr>
<tr><td>Network</td><td>IP_ADDRESS, IPV6_ADDRESS, MAC_ADDRESS, URL, DOMAIN_NAME, USER_AGENT</td></tr>
<tr><td>Business</td><td>ORGANIZATION, COMPANY_NAME, DEPARTMENT</td></tr>
<tr><td>Medical</td><td>ICD_CODE, HEALTH_PLAN_NUMBER</td></tr>
<tr><td>Data Utilities</td><td>BOOLEAN, LOREM, TIMESTAMP</td></tr>
<tr><td>Control</td><td>NULL, CONSTANT, PARTIAL_MASK, FORMAT_PRESERVING, SEQUENTIAL, RANDOM_INT</td></tr>
</table>

Expand Down
20 changes: 20 additions & 0 deletions frontend/src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,26 @@ export enum GeneratorType {
// Other
ORGANIZATION = 'ORGANIZATION',
ACCOUNT_NUMBER = 'ACCOUNT_NUMBER',
// Personal extended
TITLE = 'TITLE',
JOB_TITLE = 'JOB_TITLE',
NATIONALITY = 'NATIONALITY',
// Business
COMPANY_NAME = 'COMPANY_NAME',
DEPARTMENT = 'DEPARTMENT',
// Financial extended
CURRENCY_CODE = 'CURRENCY_CODE',
// Network extended
DOMAIN_NAME = 'DOMAIN_NAME',
USER_AGENT = 'USER_AGENT',
// Location extended
LATITUDE = 'LATITUDE',
LONGITUDE = 'LONGITUDE',
TIME_ZONE = 'TIME_ZONE',
// Data utilities
BOOLEAN = 'BOOLEAN',
LOREM = 'LOREM',
TIMESTAMP = 'TIMESTAMP',
// Composite / PK generators
CONDITIONAL = 'CONDITIONAL',
PARTIAL_MASK = 'PARTIAL_MASK',
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/views/__tests__/ConnectionsView.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ describe('ConnectionType enum', () => {
})

describe('GeneratorType enum', () => {
it('includes all 47 generator types', () => {
expect(Object.keys(GeneratorType).length).toBe(47)
it('includes all 61 generator types', () => {
expect(Object.keys(GeneratorType).length).toBe(61)
expect(GeneratorType.NAME).toBe('NAME')
expect(GeneratorType.EMAIL).toBe('EMAIL')
expect(GeneratorType.NULL).toBe('NULL')
Expand Down
Loading