-
Notifications
You must be signed in to change notification settings - Fork 12
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Initial implementation of
limit
option (#30)
## Video with context https://www.loom.com/share/b94094839ca04524ab661b8837eebf6e ## Problem At the moment, copycat gives you the ability to generate some fake value corresponding to some input value, for example: ```js copycat.email('real@email.org') // => 'Era_Kunde245@gmail.com' ``` However, there is currently no way to restrict the generated values to be within a given character limit. At [snaplet](https://www.snaplet.dev/), we make use of copycat to replace real values in a database with fake values. This is where the problem comes in: the real values in the database are within some character limit. However, the fake values generated by copycat that we're replacing them with are not always within this same character limit. As a result, we aren't able to use these fake values. ## Solution Add a `limit` option to each copycat API method that generates a string, for example: ``` // generated result will be <= 20 characters copycat.email('real@example.org', { limit: 20 }) ``` ## Approach Copycat makes use of composition to generate values. For example, `copycat.email()` makes use of `copycat.firstName()` and `copycat.lastName()`. The idea is to have each component in each layer of composition be aware of the character limit. For composites like `email`, allocate a `limit` to each component. For example, if the limit is `25`, then give `5` as a limit for `firstName`. Then, for each primitive/leaf (the end of the chain of composition), have it restrict its output to be within that limit. For example, `firstName` makes use of `oneOf()`, and uses it to pick from an array of first names (provided by faker). So this PR replaces this `oneOf()` usage with a new `oneOfString()` function, which will only pick from the list of values that are within that limit. If none are found, it defaults to using `copycat.word()`. For more context on the approach, take a look at the video: https://www.loom.com/share/b94094839ca04524ab661b8837eebf6e
- Loading branch information
Showing
9 changed files
with
485 additions
and
27 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,249 @@ | ||
import { copycat } from '.' | ||
|
||
const NUM_CHECKS = 10 | ||
|
||
export const LIMIT_TRANSFORMATION_NAMES = [ | ||
'email', | ||
'firstName', | ||
'lastName', | ||
'fullName', | ||
] as const | ||
|
||
const generateValues = (limit: number) => { | ||
const results = {} | ||
|
||
for (const name of LIMIT_TRANSFORMATION_NAMES) { | ||
let i = -1 | ||
const fn = copycat[name] | ||
const transformationResults: unknown[] = [] | ||
results[name] = transformationResults | ||
|
||
while (++i < NUM_CHECKS) { | ||
const result = fn(i, { limit }) | ||
expect(result.length).toBeLessThanOrEqual(limit) | ||
transformationResults.push(result) | ||
} | ||
} | ||
|
||
return results | ||
} | ||
|
||
test('limit: medium', () => { | ||
expect(generateValues(25)).toMatchInlineSnapshot(` | ||
Object { | ||
"email": Array [ | ||
"Bo_King114@gmail.net", | ||
"Bo_Koch349@yahoo.net", | ||
"Bo_Ward471@yahoo.com", | ||
"Bo_Toy949@gmail.com", | ||
"Ed_Dare472@yahoo.com", | ||
"Bo_Wiza59@yahoo.info", | ||
"Bo_Toy189@gmail.info", | ||
"Bo_Von60@gmail.biz", | ||
"Ed_Kub941@gmail.org", | ||
"Ed_Cole773@yahoo.net", | ||
], | ||
"firstName": Array [ | ||
"Cindy", | ||
"Amara", | ||
"Zelma", | ||
"Glennie", | ||
"Kaley", | ||
"Betty", | ||
"Laurianne", | ||
"Horace", | ||
"Wilson", | ||
"Kamryn", | ||
], | ||
"fullName": Array [ | ||
"Myrl Heidenreich", | ||
"Ignacio Reinger", | ||
"Vesta Smith", | ||
"Ottis Stark", | ||
"Nolan Rutherford", | ||
"Ernesto Jacobs", | ||
"Eleanora Boyle", | ||
"Jaiden Muller", | ||
"Willow Osinski", | ||
"Jane Glover", | ||
], | ||
"lastName": Array [ | ||
"Nitzsche", | ||
"Ledner", | ||
"Jakubowski", | ||
"Boyle", | ||
"Emard", | ||
"Breitenberg", | ||
"Yundt", | ||
"Davis", | ||
"Zulauf", | ||
"Kuphal", | ||
], | ||
} | ||
`) | ||
}) | ||
|
||
test('limit: small', () => { | ||
expect(generateValues(10)).toMatchInlineSnapshot(` | ||
Object { | ||
"email": Array [ | ||
"VvK1@ko.yu", | ||
"MmK3@mi.ra", | ||
"SsK4@vi.vi", | ||
"KsY9@mi.so", | ||
"MmR4@yu.ko", | ||
"YrK5@yu.vi", | ||
"KkS1@ra.vi", | ||
"MmR6@ko.ra", | ||
"YsM9@yu.yu", | ||
"MyV7@so.yu", | ||
], | ||
"firstName": Array [ | ||
"Eryn", | ||
"Osborne", | ||
"Lamar", | ||
"Lance", | ||
"Frank", | ||
"Breanna", | ||
"Alden", | ||
"Stewart", | ||
"Rebeka", | ||
"Kira", | ||
], | ||
"fullName": Array [ | ||
"Bud Yost", | ||
"Ena Batz", | ||
"Ian Koch", | ||
"Tom Ward", | ||
"Tre Haag", | ||
"Roy Rowe", | ||
"Loy Conn", | ||
"Ima Ward", | ||
"Guy Lowe", | ||
"Rae Fay", | ||
], | ||
"lastName": Array [ | ||
"Abernathy", | ||
"Kris", | ||
"Wyman", | ||
"Kessler", | ||
"Braun", | ||
"Mante", | ||
"Hirthe", | ||
"Abbott", | ||
"Gerlach", | ||
"Dibbert", | ||
], | ||
} | ||
`) | ||
}) | ||
|
||
test('limit: very large', () => { | ||
expect(generateValues(999)).toMatchInlineSnapshot(` | ||
Object { | ||
"email": Array [ | ||
"Liliane_Powlowski114@gmail.net", | ||
"Emely_Buckridge349@yahoo.net", | ||
"Jeffry_Kshlerin471@yahoo.com", | ||
"Norbert_Funk949@gmail.com", | ||
"Lyda_Schowalter472@yahoo.com", | ||
"Kaylie_Yost59@yahoo.info", | ||
"Catherine_Schmitt189@gmail.info", | ||
"Elinore_Kshlerin60@gmail.biz", | ||
"Jace_Boehm941@gmail.org", | ||
"Howell_Bergnaum773@yahoo.net", | ||
], | ||
"firstName": Array [ | ||
"Cindy", | ||
"Amara", | ||
"Zelma", | ||
"Glennie", | ||
"Kaley", | ||
"Betty", | ||
"Laurianne", | ||
"Horace", | ||
"Wilson", | ||
"Kamryn", | ||
], | ||
"fullName": Array [ | ||
"Liliane Heidenreich", | ||
"Emely Reinger", | ||
"Jeffry Smith", | ||
"Norbert Stark", | ||
"Lyda Rutherford", | ||
"Kaylie Jacobs", | ||
"Catherine Boyle", | ||
"Elinore Muller", | ||
"Jace Osinski", | ||
"Howell Glover", | ||
], | ||
"lastName": Array [ | ||
"Nitzsche", | ||
"Ledner", | ||
"Jakubowski", | ||
"Boyle", | ||
"Emard", | ||
"Breitenberg", | ||
"Yundt", | ||
"Davis", | ||
"Zulauf", | ||
"Kuphal", | ||
], | ||
} | ||
`) | ||
}) | ||
|
||
test('limit: tiny', () => { | ||
expect(generateValues(5)).toMatchInlineSnapshot(` | ||
Object { | ||
"email": Array [ | ||
"1@k.y", | ||
"3@m.r", | ||
"4@v.v", | ||
"9@m.s", | ||
"4@y.k", | ||
"5@y.v", | ||
"1@r.v", | ||
"6@k.r", | ||
"9@y.y", | ||
"7@s.y", | ||
], | ||
"firstName": Array [ | ||
"Otto", | ||
"Nils", | ||
"Bria", | ||
"Jo", | ||
"Alec", | ||
"Kaci", | ||
"Adah", | ||
"Clay", | ||
"Nick", | ||
"Isac", | ||
], | ||
"fullName": Array [ | ||
"V Vi", | ||
"M Mi", | ||
"S So", | ||
"K So", | ||
"M Mi", | ||
"Y Ra", | ||
"K Ko", | ||
"M Mi", | ||
"Y So", | ||
"M Yu", | ||
], | ||
"lastName": Array [ | ||
"Dare", | ||
"Rau", | ||
"Howe", | ||
"Kris", | ||
"King", | ||
"Cole", | ||
"Koss", | ||
"Howe", | ||
"Roob", | ||
"Roob", | ||
], | ||
} | ||
`) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,20 +1,32 @@ | ||
import faker from '@faker-js/faker' | ||
import { int, oneOf, join } from 'fictional' | ||
import { int } from 'fictional' | ||
import { firstName } from './firstName' | ||
import { join } from './join' | ||
import { lastName } from './lastName' | ||
import { oneOfString } from './oneOfString' | ||
|
||
import { Input } from './types' | ||
|
||
const maker = join('', [ | ||
firstName, | ||
oneOf(['_', '.']), | ||
lastName, | ||
int.options({ | ||
min: 2, | ||
max: 999, | ||
}), | ||
'@', | ||
oneOf(faker.locales.en!.internet!.free_email!), | ||
]) | ||
interface EmailOptions { | ||
limit?: number | ||
} | ||
|
||
export const email = (input: Input): string => maker(input) | ||
export const email = (input: Input, options: EmailOptions = {}): string => | ||
join( | ||
input, | ||
'', | ||
[ | ||
firstName, | ||
oneOfString(['_', '.']), | ||
lastName, | ||
int.options({ | ||
min: 2, | ||
max: 999, | ||
}), | ||
'@', | ||
oneOfString(['gmail', 'yahoo', 'hotmail']), | ||
'.', | ||
oneOfString(faker.locales.en!.internet!.domain_suffix!), | ||
], | ||
options | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,8 @@ | ||
import faker from '@faker-js/faker' | ||
import { oneOf } from 'fictional' | ||
import { oneOfString } from './oneOfString' | ||
import { word } from './primitives' | ||
|
||
import { Input } from './types' | ||
|
||
export const firstName = (input: Input): string => | ||
oneOf(input, faker.locales.en!.name!.first_name!) | ||
export const firstName = oneOfString( | ||
faker.locales.en!.name!.first_name!, | ||
word.options({ capitalize: true }) | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,7 @@ | ||
import { join } from 'fictional' | ||
|
||
import { Input } from './types' | ||
import { firstName } from './firstName' | ||
import { lastName } from './lastName' | ||
import { join } from './join' | ||
|
||
export const fullName = (input: Input): string => | ||
join(input, ' ', [firstName, lastName]) | ||
export const fullName = (input: Input, options = {}): string => | ||
join(input, ' ', [firstName, lastName], options) |
Oops, something went wrong.