Skip to content

Commit

Permalink
feat: add missing string checks (#63)
Browse files Browse the repository at this point in the history
Closes #62
Closes #61
Closes #60
Closes #59
Closes #58
  • Loading branch information
THEtheChad committed Jul 15, 2023
1 parent 81dcb9c commit 8b34b04
Show file tree
Hide file tree
Showing 7 changed files with 128 additions and 18 deletions.
16 changes: 15 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@
"zod": ">=3.0.0"
},
"dependencies": {
"@paralleldrive/cuid2": "^2.2.0"
"@paralleldrive/cuid2": "^2.2.0",
"ulid": "^2.3.0"
},
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^5.59.11",
Expand Down
5 changes: 5 additions & 0 deletions src/fixture/generators/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@ import {
CuidGenerator,
DateTimeGenerator,
EmailGenerator,
IpGenerator,
StringGenerator,
UlidGenerator,
UrlGenerator,
UuidGenerator,
} from './string';
Expand All @@ -35,6 +37,8 @@ import { UndefinedGenerator, VoidGenerator } from './undefined';
import { UnionGenerator } from './union';

export const DEFAULT_FIXTURE_GENERATORS = [
IpGenerator,
UlidGenerator,
ArrayGenerator,
BigIntGenerator,
BigIntMultipleOfGenerator,
Expand Down Expand Up @@ -84,6 +88,7 @@ export {
EmailGenerator,
EnumGenerator,
FunctionGenerator,
IpGenerator,
LazyGenerator,
LiteralGenerator,
MapGenerator,
Expand Down
82 changes: 68 additions & 14 deletions src/fixture/generators/string/index.ts
Original file line number Diff line number Diff line change
@@ -1,39 +1,55 @@
import { ZodString } from '@/internal/zod';
import { Generator } from '@/transformer/generator';
import type { Runner } from '@/transformer/runner';
import { monotonicFactory } from 'ulid';
import type { ZodStringDef } from 'zod';

const ulid = monotonicFactory();
const prefixPattern = (str: string) => `^.{${str.length}}`;
const suffixPattern = (str: string) => `.{${str.length}}$`;

function formatString(transform: Runner, def: ZodStringDef, value: string) {
const checks = transform.utils.checks(def.checks);

let max = checks.find('max')?.value;
let min = checks.find('min')?.value ?? 0;
const length = checks.find('length')?.value;
const isUpperCase = checks.has('toUpperCase');
const isLowerCase = checks.has('toLowerCase');
const includes = checks.find('includes')?.value;
const startsWith = checks.find('startsWith')?.value;
const endsWith = checks.find('endsWith')?.value;
const emoji = checks.has('emoji');
const isTrimmed = checks.has('trim');
const isUpperCase = checks.has('toUpperCase');
const isLowerCase = checks.has('toLowerCase');

if (length) {
min = length;
max = length;
}

if (max && (startsWith || endsWith)) {
if (
max &&
value.length < max + (startsWith?.length ?? 0) + (endsWith?.length ?? 0)
) {
value = value.slice(
0,
max - (startsWith?.length ?? 0) - (endsWith?.length ?? 0)
);
}
if (min != null && value.length < min) {
const diff = min - value.length;
value += transform.utils.random.string({ min: diff, max: diff });
}

if (max != null) {
value = value.slice(0, max);
}

if (includes) {
const prefix = startsWith ? prefixPattern(startsWith) : '';
value = value.replace(
new RegExp(`(${prefix}).{${includes.length}}`),
(_, prefix) => prefix + includes
);
}

if (startsWith) {
value = startsWith + value;
value = value.replace(new RegExp(prefixPattern(startsWith)), startsWith);
}

if (endsWith) {
value = value + endsWith;
value = value.replace(new RegExp(suffixPattern(endsWith)), endsWith);
}

if (isUpperCase) {
Expand All @@ -42,6 +58,14 @@ function formatString(transform: Runner, def: ZodStringDef, value: string) {
value = value.toLowerCase();
}

if (isTrimmed) {
value = value.trim();
}

if (emoji) {
value = value.replace(/./g, () => transform.utils.random.emoji());
}

return max ? value.slice(0, max) : value;
}

Expand All @@ -67,6 +91,13 @@ export const StringGenerator = Generator({
},
});

export const UlidGenerator = Generator({
schema: ZodString,
filter: ({ def, transform }) =>
transform.utils.checks(def.checks).has('ulid'),
output: () => ulid(),
});

export const UrlGenerator = Generator({
schema: ZodString,
filter: ({ def, transform }) => transform.utils.checks(def.checks).has('url'),
Expand Down Expand Up @@ -103,6 +134,29 @@ export const CuidGenerator = Generator({
},
});

export const IpGenerator = Generator({
schema: ZodString,
filter: ({ def, transform }) => transform.utils.checks(def.checks).has('ip'),
output: ({ def, transform }) => {
const version =
transform.utils.checks(def.checks).find('ip')?.version ??
transform.utils.random.from(['v4', 'v6']);

if (version === 'v4') {
return transform.utils
.n(() => transform.utils.random.int({ min: 1, max: 255 }), 4)
.join('.');
}

return transform.utils
.n(
() => transform.utils.random.int({ min: 0, max: 65535 }).toString(16),
8
)
.join(':');
},
});

export const Cuid2Generator = Generator({
schema: ZodString,
filter: ({ def, transform }) =>
Expand Down
33 changes: 32 additions & 1 deletion src/fixture/generators/string/string.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ import {
CuidGenerator,
DateTimeGenerator,
EmailGenerator,
IpGenerator,
StringGenerator,
UlidGenerator,
UrlGenerator,
UuidGenerator,
} from '.';
Expand All @@ -17,6 +19,8 @@ import { OptionalGenerator } from '../optional';

describe('create strings', () => {
const transform = new ConstrainedTransformer().extend([
IpGenerator,
UlidGenerator,
UuidGenerator,
CuidGenerator,
Cuid2Generator,
Expand Down Expand Up @@ -131,6 +135,10 @@ describe('create strings', () => {
expect(uppercase).toMatch(/^[^a-z]*$/);
});

test('creates a valid ulid', () => {
expect(transform).toReasonablySatisfy(z.string().ulid());
});

test('produces a valid string with a start and end', () => {
expect(transform).toReasonablySatisfy(
z.string().startsWith('begin').endsWith('end')
Expand Down Expand Up @@ -178,7 +186,24 @@ describe('create strings', () => {
expect(value).toHaveLength(6000);
});

test('creates a large string using length with startsWith and EndsWith', () => {
test('creates a string with endsWith', () => {
const value = transform.fromSchema(
z.string().startsWith('foxy').includes('mama')
) as string;
expect(value.startsWith('foxy')).toBeTruthy();
expect(value.includes('mama')).toBeTruthy();
});

test('correctly trims string', () => {
const value = transform.fromSchema(z.string().endsWith(' ').trim());
expect(value).toHaveLength(10);
});

test('correctly creates an emoji string', () => {
expect(transform).toReasonablySatisfy(z.string().emoji());
});

test('creates a large string using length with startsWith and endsWith', () => {
const value = transform.fromSchema(
z.string().length(6000).startsWith('start_').endsWith('_end')
) as string;
Expand All @@ -187,6 +212,12 @@ describe('create strings', () => {
expect(value.endsWith('_end')).toBeTruthy();
});

test('creates a proper IP address', () => {
expect(transform).toReasonablySatisfy(z.string().ip());
expect(transform).toReasonablySatisfy(z.string().ip({ version: 'v4' }));
expect(transform).toReasonablySatisfy(z.string().ip({ version: 'v6' }));
});

test('creates a large string using min and max', () => {
const value = transform.fromSchema(
z.string().min(6000).max(7000).startsWith('start_').endsWith('_end')
Expand Down
5 changes: 5 additions & 0 deletions src/transformer/utils/Randomization.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,11 @@ export class Randomization {
return copy as T[];
}

emoji() {
const codePoint = this.int({ min: 0x1f601, max: 0x1f64f });
return String.fromCodePoint(codePoint);
}

string(config: { min?: number; max?: number }) {
let min = config.min ?? this.defaults.string.min;
let max = config.max ?? this.defaults.string.max;
Expand Down
2 changes: 1 addition & 1 deletion vite.lib.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export default defineConfig({
`zod-fixture.${format}.${format === 'umd' ? 'cjs' : 'js'}`,
},
rollupOptions: {
external: ['@paralleldrive/cuid2'],
external: ['@paralleldrive/cuid2', 'ulid'],
},
},
optimizeDeps: {
Expand Down

0 comments on commit 8b34b04

Please sign in to comment.