From 3f3e22711164b83c5d72deb66c30b01c1425dd83 Mon Sep 17 00:00:00 2001 From: jasperteo Date: Sun, 18 Aug 2024 19:29:22 +0800 Subject: [PATCH 1/6] Added nanoid() validation action --- library/src/actions/index.ts | 1 + library/src/actions/nanoid/index.ts | 1 + library/src/actions/nanoid/nanoid.test-d.ts | 43 +++++++ library/src/actions/nanoid/nanoid.test.ts | 121 ++++++++++++++++++ library/src/actions/nanoid/nanoid.ts | 105 +++++++++++++++ library/src/regex.ts | 5 + .../src/routes/api/(actions)/nanoid/index.mdx | 66 ++++++++++ .../routes/api/(actions)/nanoid/properties.ts | 58 +++++++++ .../src/routes/api/(methods)/pipe/index.mdx | 1 + .../src/routes/api/(schemas)/any/index.mdx | 1 + .../src/routes/api/(schemas)/string/index.mdx | 1 + .../routes/api/(schemas)/unknown/index.mdx | 1 + website/src/routes/api/menu.md | 1 + .../(main-concepts)/pipelines/index.mdx | 1 + 14 files changed, 406 insertions(+) create mode 100644 library/src/actions/nanoid/index.ts create mode 100644 library/src/actions/nanoid/nanoid.test-d.ts create mode 100644 library/src/actions/nanoid/nanoid.test.ts create mode 100644 library/src/actions/nanoid/nanoid.ts create mode 100644 website/src/routes/api/(actions)/nanoid/index.mdx create mode 100644 website/src/routes/api/(actions)/nanoid/properties.ts diff --git a/library/src/actions/index.ts b/library/src/actions/index.ts index 25797b515..230d99e6d 100644 --- a/library/src/actions/index.ts +++ b/library/src/actions/index.ts @@ -48,6 +48,7 @@ export * from './minLength/index.ts'; export * from './minSize/index.ts'; export * from './minValue/index.ts'; export * from './multipleOf/index.ts'; +export * from './nanoid/index.ts'; export * from './nonEmpty/index.ts'; export * from './normalize/index.ts'; export * from './notBytes/index.ts'; diff --git a/library/src/actions/nanoid/index.ts b/library/src/actions/nanoid/index.ts new file mode 100644 index 000000000..f7b62accd --- /dev/null +++ b/library/src/actions/nanoid/index.ts @@ -0,0 +1 @@ +export * from './nanoid.ts'; diff --git a/library/src/actions/nanoid/nanoid.test-d.ts b/library/src/actions/nanoid/nanoid.test-d.ts new file mode 100644 index 000000000..d41405145 --- /dev/null +++ b/library/src/actions/nanoid/nanoid.test-d.ts @@ -0,0 +1,43 @@ +import { describe, expectTypeOf, test } from 'vitest'; +import type { InferInput, InferIssue, InferOutput } from '../../types/index.ts'; +import { nanoid, type NanoIDAction, type NanoIDIssue } from './nanoid.ts'; + +describe('nanoid', () => { + describe('should return action object', () => { + test('with undefined message', () => { + type Action = NanoIDAction; + expectTypeOf(nanoid()).toEqualTypeOf(); + expectTypeOf( + nanoid(undefined) + ).toEqualTypeOf(); + }); + + test('with string message', () => { + expectTypeOf(nanoid('message')).toEqualTypeOf< + NanoIDAction + >(); + }); + + test('with function message', () => { + expectTypeOf(nanoid string>(() => 'message')).toEqualTypeOf< + NanoIDAction string> + >(); + }); + }); + + describe('should infer correct types', () => { + type Action = NanoIDAction; + + test('of input', () => { + expectTypeOf>().toEqualTypeOf(); + }); + + test('of output', () => { + expectTypeOf>().toEqualTypeOf(); + }); + + test('of issue', () => { + expectTypeOf>().toEqualTypeOf>(); + }); + }); +}); diff --git a/library/src/actions/nanoid/nanoid.test.ts b/library/src/actions/nanoid/nanoid.test.ts new file mode 100644 index 000000000..1c57adc67 --- /dev/null +++ b/library/src/actions/nanoid/nanoid.test.ts @@ -0,0 +1,121 @@ +import { describe, expect, test } from 'vitest'; +import { NANO_ID_REGEX } from '../../regex.ts'; +import { expectActionIssue, expectNoActionIssue } from '../../vitest/index.ts'; +import { nanoid, type NanoIDAction, type NanoIDIssue } from './nanoid.ts'; + +describe('nanoid', () => { + describe('should return action object', () => { + const baseAction: Omit, 'message'> = { + kind: 'validation', + type: 'nanoid', + reference: nanoid, + expects: null, + requirement: NANO_ID_REGEX, + async: false, + _run: expect.any(Function), + }; + + test('with undefined message', () => { + const action: NanoIDAction = { + ...baseAction, + message: undefined, + }; + expect(nanoid()).toStrictEqual(action); + expect(nanoid(undefined)).toStrictEqual(action); + }); + + test('with string message', () => { + expect(nanoid('message')).toStrictEqual({ + ...baseAction, + message: 'message', + } satisfies NanoIDAction); + }); + + test('with function message', () => { + const message = () => 'message'; + expect(nanoid(message)).toStrictEqual({ + ...baseAction, + message, + } satisfies NanoIDAction); + }); + }); + + describe('should return dataset without issues', () => { + const action = nanoid(); + + test('for untyped inputs', () => { + expect(action._run({ typed: false, value: null }, {})).toStrictEqual({ + typed: false, + value: null, + }); + }); + + test('for default ID length', () => { + expectNoActionIssue(action, [ + 'OIjC22zGKp_rrTYQBb3xt', + '4EdTtQZCc5GIFA9ABjEsQ', + '8hiQ-95aV5qCnB4x0rPBu', + 'aIojG8uVOE0-1ANOZLugU', + 'ODz0tL5pgbQvpbptus6LL', + ]); + }); + + test('for 36 characters ID length', () => { + expectNoActionIssue(action, [ + 'GnNK4jChVqRzfS3D1tKlK1gyBFHet40wRME0', + 'yKzhkwFV6MorFYCWvfrEra_cl2_C9AJVnr-P', + 'wGGeRInEDWjHjyJF0jJBnYcxmdOW0yghjL_N', + 'ER5WiemGh-BCwKfbqwa-dwzesKCALiti_kgn', + '3Pnm_UOzO-9JgSc82cvqVw8pdq3wXCEcRVDM', + ]); + }); + + test('for 2 characters ID length', () => { + expectNoActionIssue(action, ['cf', 'Ka', '7G', 'gx', 'VS']); + }); + + test('for hyphens and underscores', () => { + expectNoActionIssue(action, ['_40', '-8K', 'Y-z', 'uI_', 'Mk-']); + }); + }); + + describe('should return dataset with issues', () => { + const action = nanoid('message'); + const baseIssue: Omit, 'input' | 'received'> = { + kind: 'validation', + type: 'nanoid', + expected: null, + message: 'message', + requirement: NANO_ID_REGEX, + }; + + test('for empty strings', () => { + expectActionIssue(action, baseIssue, ['', ' ', '\n']); + }); + + test('for string with spaces', () => { + expectActionIssue(action, baseIssue, [ + 'TvVHTtJLv4 kMeb_tDipeg', + ' rqer3NNc7F6ZIIcZjabce', + '0-3_fzh-XNFlwL6JR95GT ', + ]); + }); + + test('for not between 2 to 36 characters', () => { + expectActionIssue(action, baseIssue, [ + 'F', + 'carpyVLA_Lb2H-C3fEhPwhn5w7bbZA-KzSdRG3vMe', + ]); + }); + + test('for special chars', () => { + expectActionIssue(action, baseIssue, [ + '@1o5BK76uGc-mbqeprAvXc', + '#Lcb2qbTsjS98y9Vf-G15k', + '$3WZ4tXxsuiDBezXIJKlPu', + '%gSjBHLFDO67bE-nbgBRiO', + '&2zYmqr0APdImhdxC69t4_', + ]); + }); + }); +}); diff --git a/library/src/actions/nanoid/nanoid.ts b/library/src/actions/nanoid/nanoid.ts new file mode 100644 index 000000000..f2fd83cd4 --- /dev/null +++ b/library/src/actions/nanoid/nanoid.ts @@ -0,0 +1,105 @@ +import { NANO_ID_REGEX } from '../../regex.ts'; +import type { + BaseIssue, + BaseValidation, + Dataset, + ErrorMessage, +} from '../../types/index.ts'; +import { _addIssue } from '../../utils/index.ts'; + +/** + * Nano ID issue type. + */ +export interface NanoIDIssue extends BaseIssue { + /** + * The issue kind. + */ + readonly kind: 'validation'; + /** + * The issue type. + */ + readonly type: 'nanoid'; + /** + * The expected property. + */ + readonly expected: null; + /** + * The received property. + */ + readonly received: `"${string}"`; + /** + * The Nano ID regex. + */ + readonly requirement: RegExp; +} + +/** + * Nano ID action type. + */ +export interface NanoIDAction< + TInput extends string, + TMessage extends ErrorMessage> | undefined, +> extends BaseValidation> { + /** + * The action type. + */ + readonly type: 'nanoid'; + /** + * The action reference. + */ + readonly reference: typeof nanoid; + /** + * The expected property. + */ + readonly expects: null; + /** + * The Nano ID regex. + */ + readonly requirement: RegExp; + /** + * The error message. + */ + readonly message: TMessage; +} + +/** + * Creates a [Nano ID](https://github.com/ai/nanoid) validation action. + * + * @returns A Nano ID action. + */ +export function nanoid(): NanoIDAction< + TInput, + undefined +>; + +/** + * Creates a [Nano ID](https://github.com/ai/nanoid) validation action. + * + * @param message The error message. + * + * @returns A Nano ID action. + */ +export function nanoid< + TInput extends string, + const TMessage extends ErrorMessage> | undefined, +>(message: TMessage): NanoIDAction; + +export function nanoid( + message?: ErrorMessage> +): NanoIDAction> | undefined> { + return { + kind: 'validation', + type: 'nanoid', + reference: nanoid, + async: false, + expects: null, + requirement: NANO_ID_REGEX, + message, + _run(dataset, config) { + if (dataset.typed && !this.requirement.test(dataset.value)) { + _addIssue(this, 'Nano ID', dataset, config); + } + return dataset as Dataset>; + }, + }; +} diff --git a/library/src/regex.ts b/library/src/regex.ts index bfe13e512..d9e813d87 100644 --- a/library/src/regex.ts +++ b/library/src/regex.ts @@ -122,6 +122,11 @@ export const MAC64_REGEX: RegExp = export const MAC_REGEX: RegExp = /^(?:[\da-f]{2}:){5}[\da-f]{2}$|^(?:[\da-f]{2}-){5}[\da-f]{2}$|^(?:[\da-f]{4}\.){2}[\da-f]{4}$|^(?:[\da-f]{2}:){7}[\da-f]{2}$|^(?:[\da-f]{2}-){7}[\da-f]{2}$|^(?:[\da-f]{4}\.){3}[\da-f]{4}$|^(?:[\da-f]{4}:){3}[\da-f]{4}$/iu; +/** + * [Nano ID](https://github.com/ai/nanoid) regex. + */ +export const NANO_ID_REGEX: RegExp = /^[\w-]{2,36}$/u; + /** * [Octal](https://en.wikipedia.org/wiki/Octal) regex. */ diff --git a/website/src/routes/api/(actions)/nanoid/index.mdx b/website/src/routes/api/(actions)/nanoid/index.mdx new file mode 100644 index 000000000..9cb03ae07 --- /dev/null +++ b/website/src/routes/api/(actions)/nanoid/index.mdx @@ -0,0 +1,66 @@ +--- +title: nanoid +description: Creates a Nano ID validation action. +source: /actions/nanoid/nanoid.ts +contributors: + - jasperteo +--- + +import { ApiList, Property } from '~/components'; +import { properties } from './properties'; + +# nanoid + +Creates a [Nano ID](https://github.com/ai/nanoid) validation action. + +```ts +const Action = v.nanoid(message); +``` + +## Generics + +- `TInput` +- `TMessage` + +## Parameters + +- `message` + +### Explanation + +With `nanoid` you can validate the formatting of a string. If the input is not an Nano ID, you can use `message` to customize the error message. + +## Returns + +- `Action` + +## Examples + +The following examples show how `nanoid` can be used. + +### Nano ID schema + +Schema to validate a Nano ID. + +```ts +const NanoIDSchema = v.pipe( + v.string(), + v.nanoid('The Nano ID is badly formatted.') +); +``` + +## Related + +The following APIs can be combined with `nanoid`. + +### Schemas + + + +### Methods + + + +### Utils + + diff --git a/website/src/routes/api/(actions)/nanoid/properties.ts b/website/src/routes/api/(actions)/nanoid/properties.ts new file mode 100644 index 000000000..b71f149e7 --- /dev/null +++ b/website/src/routes/api/(actions)/nanoid/properties.ts @@ -0,0 +1,58 @@ +import type { PropertyProps } from '~/components'; + +export const properties: Record = { + TInput: { + modifier: 'extends', + type: 'string', + }, + TMessage: { + modifier: 'extends', + type: { + type: 'union', + options: [ + { + type: 'custom', + name: 'ErrorMessage', + href: '../ErrorMessage/', + generics: [ + { + type: 'custom', + name: 'NanoIDIssue', + href: '../NanoIDIssue/', + generics: [ + { + type: 'custom', + name: 'TInput', + }, + ], + }, + ], + }, + 'undefined', + ], + }, + }, + message: { + type: { + type: 'custom', + name: 'TMessage', + }, + }, + Action: { + type: { + type: 'custom', + name: 'NanoIDAction', + href: '../NanoIDAction/', + generics: [ + { + type: 'custom', + name: 'TInput', + }, + { + type: 'custom', + name: 'TMessage', + }, + ], + }, + }, +}; diff --git a/website/src/routes/api/(methods)/pipe/index.mdx b/website/src/routes/api/(methods)/pipe/index.mdx index 106622f62..6f4ba987d 100644 --- a/website/src/routes/api/(methods)/pipe/index.mdx +++ b/website/src/routes/api/(methods)/pipe/index.mdx @@ -199,6 +199,7 @@ The following APIs can be combined with `pipe`. 'minSize', 'minValue', 'multipleOf', + 'nanoid', 'nonEmpty', 'notBytes', 'notLength', diff --git a/website/src/routes/api/(schemas)/any/index.mdx b/website/src/routes/api/(schemas)/any/index.mdx index 6eb4c3f3f..5b0675bd9 100644 --- a/website/src/routes/api/(schemas)/any/index.mdx +++ b/website/src/routes/api/(schemas)/any/index.mdx @@ -129,6 +129,7 @@ The following APIs can be combined with `any`. 'minSize', 'minValue', 'multipleOf', + 'nanoid', 'nonEmpty', 'notBytes', 'notLength', diff --git a/website/src/routes/api/(schemas)/string/index.mdx b/website/src/routes/api/(schemas)/string/index.mdx index 6d065e48b..eb2f410f8 100644 --- a/website/src/routes/api/(schemas)/string/index.mdx +++ b/website/src/routes/api/(schemas)/string/index.mdx @@ -171,6 +171,7 @@ The following APIs can be combined with `string`. 'minBytes', 'minLength', 'minValue', + 'nanoid', 'nonEmpty', 'notBytes', 'notLength', diff --git a/website/src/routes/api/(schemas)/unknown/index.mdx b/website/src/routes/api/(schemas)/unknown/index.mdx index 0d72d862a..c4bc7e4ea 100644 --- a/website/src/routes/api/(schemas)/unknown/index.mdx +++ b/website/src/routes/api/(schemas)/unknown/index.mdx @@ -129,6 +129,7 @@ The following APIs can be combined with `unknwon`. 'minSize', 'minValue', 'multipleOf', + 'nanoid', 'nonEmpty', 'notBytes', 'notLength', diff --git a/website/src/routes/api/menu.md b/website/src/routes/api/menu.md index be1185072..817d4ded2 100644 --- a/website/src/routes/api/menu.md +++ b/website/src/routes/api/menu.md @@ -121,6 +121,7 @@ - [minSize](/api/minSize/) - [minValue](/api/minValue/) - [multipleOf](/api/multipleOf/) +- [nanoid](/api/nanoid/) - [nonEmpty](/api/nonEmpty/) - [normalize](/api/normalize/) - [notBytes](/api/notBytes/) diff --git a/website/src/routes/guides/(main-concepts)/pipelines/index.mdx b/website/src/routes/guides/(main-concepts)/pipelines/index.mdx index cb50791ad..7f268ff97 100644 --- a/website/src/routes/guides/(main-concepts)/pipelines/index.mdx +++ b/website/src/routes/guides/(main-concepts)/pipelines/index.mdx @@ -89,6 +89,7 @@ Pipeline validation actions examine the input and, if the input does not meet a 'minSize', 'minValue', 'multipleOf', + 'nanoid', 'nonEmpty', 'notBytes', 'notLength', From 90383f7fb9cc30336586b25e96e0a91b22b8354d Mon Sep 17 00:00:00 2001 From: jasperteo Date: Fri, 23 Aug 2024 02:25:21 +0800 Subject: [PATCH 2/6] updated with an option to pass length via first argument of function --- library/src/actions/nanoid/nanoid.test-d.ts | 10 ++-- library/src/actions/nanoid/nanoid.test.ts | 52 ++++++++----------- library/src/actions/nanoid/nanoid.ts | 26 +++++++--- library/src/regex.ts | 2 +- .../src/routes/api/(actions)/nanoid/index.mdx | 7 +-- .../routes/api/(actions)/nanoid/properties.ts | 3 ++ 6 files changed, 55 insertions(+), 45 deletions(-) diff --git a/library/src/actions/nanoid/nanoid.test-d.ts b/library/src/actions/nanoid/nanoid.test-d.ts index d41405145..be8442821 100644 --- a/library/src/actions/nanoid/nanoid.test-d.ts +++ b/library/src/actions/nanoid/nanoid.test-d.ts @@ -8,20 +8,20 @@ describe('nanoid', () => { type Action = NanoIDAction; expectTypeOf(nanoid()).toEqualTypeOf(); expectTypeOf( - nanoid(undefined) + nanoid(21, undefined) ).toEqualTypeOf(); }); test('with string message', () => { - expectTypeOf(nanoid('message')).toEqualTypeOf< + expectTypeOf(nanoid(21, 'message')).toEqualTypeOf< NanoIDAction >(); }); test('with function message', () => { - expectTypeOf(nanoid string>(() => 'message')).toEqualTypeOf< - NanoIDAction string> - >(); + expectTypeOf( + nanoid string>(21, () => 'message') + ).toEqualTypeOf string>>(); }); }); diff --git a/library/src/actions/nanoid/nanoid.test.ts b/library/src/actions/nanoid/nanoid.test.ts index 1c57adc67..4219164a9 100644 --- a/library/src/actions/nanoid/nanoid.test.ts +++ b/library/src/actions/nanoid/nanoid.test.ts @@ -11,6 +11,7 @@ describe('nanoid', () => { reference: nanoid, expects: null, requirement: NANO_ID_REGEX, + length: 21, async: false, _run: expect.any(Function), }; @@ -25,7 +26,7 @@ describe('nanoid', () => { }); test('with string message', () => { - expect(nanoid('message')).toStrictEqual({ + expect(nanoid(21, 'message')).toStrictEqual({ ...baseAction, message: 'message', } satisfies NanoIDAction); @@ -33,7 +34,7 @@ describe('nanoid', () => { test('with function message', () => { const message = () => 'message'; - expect(nanoid(message)).toStrictEqual({ + expect(nanoid(21, message)).toStrictEqual({ ...baseAction, message, } satisfies NanoIDAction); @@ -42,6 +43,7 @@ describe('nanoid', () => { describe('should return dataset without issues', () => { const action = nanoid(); + const action10 = nanoid(10); test('for untyped inputs', () => { expect(action._run({ typed: false, value: null }, {})).toStrictEqual({ @@ -60,27 +62,19 @@ describe('nanoid', () => { ]); }); - test('for 36 characters ID length', () => { - expectNoActionIssue(action, [ - 'GnNK4jChVqRzfS3D1tKlK1gyBFHet40wRME0', - 'yKzhkwFV6MorFYCWvfrEra_cl2_C9AJVnr-P', - 'wGGeRInEDWjHjyJF0jJBnYcxmdOW0yghjL_N', - 'ER5WiemGh-BCwKfbqwa-dwzesKCALiti_kgn', - '3Pnm_UOzO-9JgSc82cvqVw8pdq3wXCEcRVDM', + test('for 10 characters ID length', () => { + expectNoActionIssue(action10, [ + 'ix9v8KdiSn', + '7nU_kDcYdd', + 'gltYQWemBD', + 'btbKlDDIa-', + 't_XWeU7xf3', ]); }); - - test('for 2 characters ID length', () => { - expectNoActionIssue(action, ['cf', 'Ka', '7G', 'gx', 'VS']); - }); - - test('for hyphens and underscores', () => { - expectNoActionIssue(action, ['_40', '-8K', 'Y-z', 'uI_', 'Mk-']); - }); }); describe('should return dataset with issues', () => { - const action = nanoid('message'); + const action = nanoid(21, 'message'); const baseIssue: Omit, 'input' | 'received'> = { kind: 'validation', type: 'nanoid', @@ -95,26 +89,26 @@ describe('nanoid', () => { test('for string with spaces', () => { expectActionIssue(action, baseIssue, [ - 'TvVHTtJLv4 kMeb_tDipeg', - ' rqer3NNc7F6ZIIcZjabce', - '0-3_fzh-XNFlwL6JR95GT ', + 'BImGM 7USGakXaVhydHgO', + 'LBjowKnkbk95kK3IoUV7 ', + ' vM7SGqVFmPS5tw7fII-G', ]); }); - test('for not between 2 to 36 characters', () => { + test('for not 21 characters', () => { expectActionIssue(action, baseIssue, [ - 'F', - 'carpyVLA_Lb2H-C3fEhPwhn5w7bbZA-KzSdRG3vMe', + '8BHRbyPSzWSLK0JAQp', + 'pSnYAH-kCzsXWx7Lu5mQ72Hy', ]); }); test('for special chars', () => { expectActionIssue(action, baseIssue, [ - '@1o5BK76uGc-mbqeprAvXc', - '#Lcb2qbTsjS98y9Vf-G15k', - '$3WZ4tXxsuiDBezXIJKlPu', - '%gSjBHLFDO67bE-nbgBRiO', - '&2zYmqr0APdImhdxC69t4_', + '@1o5BK76uGc-mbqeprAvX', + '#Lcb2qbTsjS98y9Vf-G15', + '$3WZ4tXxsuiDBezXIJKlP', + '%gSjBHLFDO67bE-nbgBRi', + '&2zYmqr0APdImhdxC69t4', ]); }); }); diff --git a/library/src/actions/nanoid/nanoid.ts b/library/src/actions/nanoid/nanoid.ts index f2fd83cd4..88cadc7d0 100644 --- a/library/src/actions/nanoid/nanoid.ts +++ b/library/src/actions/nanoid/nanoid.ts @@ -26,7 +26,7 @@ export interface NanoIDIssue extends BaseIssue { /** * The received property. */ - readonly received: `"${string}"`; + readonly received: string; /** * The Nano ID regex. */ @@ -60,21 +60,27 @@ export interface NanoIDAction< * The error message. */ readonly message: TMessage; + /** + * The expected length of the Nano ID. + */ + readonly length?: number; } /** * Creates a [Nano ID](https://github.com/ai/nanoid) validation action. * + * @param length The expected length of the Nano ID. + * * @returns A Nano ID action. */ -export function nanoid(): NanoIDAction< - TInput, - undefined ->; +export function nanoid( + length?: number +): NanoIDAction; /** * Creates a [Nano ID](https://github.com/ai/nanoid) validation action. * + * @param length The expected length of the Nano ID. * @param message The error message. * * @returns A Nano ID action. @@ -82,9 +88,10 @@ export function nanoid(): NanoIDAction< export function nanoid< TInput extends string, const TMessage extends ErrorMessage> | undefined, ->(message: TMessage): NanoIDAction; +>(length?: number, message?: TMessage): NanoIDAction; export function nanoid( + length: number = 21, message?: ErrorMessage> ): NanoIDAction> | undefined> { return { @@ -95,8 +102,13 @@ export function nanoid( expects: null, requirement: NANO_ID_REGEX, message, + length, _run(dataset, config) { - if (dataset.typed && !this.requirement.test(dataset.value)) { + if ( + dataset.typed && + (!this.requirement.test(dataset.value) || + dataset.value.length !== this.length) + ) { _addIssue(this, 'Nano ID', dataset, config); } return dataset as Dataset>; diff --git a/library/src/regex.ts b/library/src/regex.ts index d9e813d87..58c184a71 100644 --- a/library/src/regex.ts +++ b/library/src/regex.ts @@ -125,7 +125,7 @@ export const MAC_REGEX: RegExp = /** * [Nano ID](https://github.com/ai/nanoid) regex. */ -export const NANO_ID_REGEX: RegExp = /^[\w-]{2,36}$/u; +export const NANO_ID_REGEX: RegExp = /^[\w-]+$/u; /** * [Octal](https://en.wikipedia.org/wiki/Octal) regex. diff --git a/website/src/routes/api/(actions)/nanoid/index.mdx b/website/src/routes/api/(actions)/nanoid/index.mdx index 9cb03ae07..ee538db36 100644 --- a/website/src/routes/api/(actions)/nanoid/index.mdx +++ b/website/src/routes/api/(actions)/nanoid/index.mdx @@ -14,7 +14,7 @@ import { properties } from './properties'; Creates a [Nano ID](https://github.com/ai/nanoid) validation action. ```ts -const Action = v.nanoid(message); +const Action = v.nanoid(length, message); ``` ## Generics @@ -24,11 +24,12 @@ const Action = v.nanoid(message); ## Parameters +- `length` - `message` ### Explanation -With `nanoid` you can validate the formatting of a string. If the input is not an Nano ID, you can use `message` to customize the error message. +With `nanoid` you can validate the formatting of a string. If the input is not an Nano ID, you can use `message` to customize the error message. You can also specify the `length` of the Nano ID, which defaults to 21 if empty. ## Returns @@ -45,7 +46,7 @@ Schema to validate a Nano ID. ```ts const NanoIDSchema = v.pipe( v.string(), - v.nanoid('The Nano ID is badly formatted.') + v.nanoid(21, 'The Nano ID is badly formatted.') ); ``` diff --git a/website/src/routes/api/(actions)/nanoid/properties.ts b/website/src/routes/api/(actions)/nanoid/properties.ts index b71f149e7..c42eab0d7 100644 --- a/website/src/routes/api/(actions)/nanoid/properties.ts +++ b/website/src/routes/api/(actions)/nanoid/properties.ts @@ -38,6 +38,9 @@ export const properties: Record = { name: 'TMessage', }, }, + length: { + type: 'number', + }, Action: { type: { type: 'custom', From 2baf8ed464b3f23561736dc367cb888392fbe43d Mon Sep 17 00:00:00 2001 From: jasperteo Date: Sat, 24 Aug 2024 22:26:38 +0800 Subject: [PATCH 3/6] removed length as an argument for nanoid() action --- library/src/actions/nanoid/nanoid.test-d.ts | 10 +++---- library/src/actions/nanoid/nanoid.test.ts | 30 +++++-------------- library/src/actions/nanoid/nanoid.ts | 24 ++++----------- .../src/routes/api/(actions)/nanoid/index.mdx | 7 ++--- .../routes/api/(actions)/nanoid/properties.ts | 3 -- 5 files changed, 22 insertions(+), 52 deletions(-) diff --git a/library/src/actions/nanoid/nanoid.test-d.ts b/library/src/actions/nanoid/nanoid.test-d.ts index be8442821..d41405145 100644 --- a/library/src/actions/nanoid/nanoid.test-d.ts +++ b/library/src/actions/nanoid/nanoid.test-d.ts @@ -8,20 +8,20 @@ describe('nanoid', () => { type Action = NanoIDAction; expectTypeOf(nanoid()).toEqualTypeOf(); expectTypeOf( - nanoid(21, undefined) + nanoid(undefined) ).toEqualTypeOf(); }); test('with string message', () => { - expectTypeOf(nanoid(21, 'message')).toEqualTypeOf< + expectTypeOf(nanoid('message')).toEqualTypeOf< NanoIDAction >(); }); test('with function message', () => { - expectTypeOf( - nanoid string>(21, () => 'message') - ).toEqualTypeOf string>>(); + expectTypeOf(nanoid string>(() => 'message')).toEqualTypeOf< + NanoIDAction string> + >(); }); }); diff --git a/library/src/actions/nanoid/nanoid.test.ts b/library/src/actions/nanoid/nanoid.test.ts index 4219164a9..6a65e1bfa 100644 --- a/library/src/actions/nanoid/nanoid.test.ts +++ b/library/src/actions/nanoid/nanoid.test.ts @@ -11,7 +11,6 @@ describe('nanoid', () => { reference: nanoid, expects: null, requirement: NANO_ID_REGEX, - length: 21, async: false, _run: expect.any(Function), }; @@ -26,7 +25,7 @@ describe('nanoid', () => { }); test('with string message', () => { - expect(nanoid(21, 'message')).toStrictEqual({ + expect(nanoid('message')).toStrictEqual({ ...baseAction, message: 'message', } satisfies NanoIDAction); @@ -34,7 +33,7 @@ describe('nanoid', () => { test('with function message', () => { const message = () => 'message'; - expect(nanoid(21, message)).toStrictEqual({ + expect(nanoid(message)).toStrictEqual({ ...baseAction, message, } satisfies NanoIDAction); @@ -43,7 +42,6 @@ describe('nanoid', () => { describe('should return dataset without issues', () => { const action = nanoid(); - const action10 = nanoid(10); test('for untyped inputs', () => { expect(action._run({ typed: false, value: null }, {})).toStrictEqual({ @@ -52,29 +50,24 @@ describe('nanoid', () => { }); }); - test('for default ID length', () => { + test('for with _ and - symbols', () => { expectNoActionIssue(action, [ 'OIjC22zGKp_rrTYQBb3xt', - '4EdTtQZCc5GIFA9ABjEsQ', '8hiQ-95aV5qCnB4x0rPBu', 'aIojG8uVOE0-1ANOZLugU', - 'ODz0tL5pgbQvpbptus6LL', ]); }); - test('for 10 characters ID length', () => { - expectNoActionIssue(action10, [ - 'ix9v8KdiSn', - '7nU_kDcYdd', - 'gltYQWemBD', - 'btbKlDDIa-', - 't_XWeU7xf3', + test('for alphabets and numerals only', () => { + expectNoActionIssue(action, [ + '4EdTtQZCc5GIFA9ABjEsQ', + 'ODz0tL5pgbQvpbptus6LL', ]); }); }); describe('should return dataset with issues', () => { - const action = nanoid(21, 'message'); + const action = nanoid('message'); const baseIssue: Omit, 'input' | 'received'> = { kind: 'validation', type: 'nanoid', @@ -95,13 +88,6 @@ describe('nanoid', () => { ]); }); - test('for not 21 characters', () => { - expectActionIssue(action, baseIssue, [ - '8BHRbyPSzWSLK0JAQp', - 'pSnYAH-kCzsXWx7Lu5mQ72Hy', - ]); - }); - test('for special chars', () => { expectActionIssue(action, baseIssue, [ '@1o5BK76uGc-mbqeprAvX', diff --git a/library/src/actions/nanoid/nanoid.ts b/library/src/actions/nanoid/nanoid.ts index 88cadc7d0..c8068d3ba 100644 --- a/library/src/actions/nanoid/nanoid.ts +++ b/library/src/actions/nanoid/nanoid.ts @@ -60,27 +60,21 @@ export interface NanoIDAction< * The error message. */ readonly message: TMessage; - /** - * The expected length of the Nano ID. - */ - readonly length?: number; } /** * Creates a [Nano ID](https://github.com/ai/nanoid) validation action. * - * @param length The expected length of the Nano ID. - * * @returns A Nano ID action. */ -export function nanoid( - length?: number -): NanoIDAction; +export function nanoid(): NanoIDAction< + TInput, + undefined +>; /** * Creates a [Nano ID](https://github.com/ai/nanoid) validation action. * - * @param length The expected length of the Nano ID. * @param message The error message. * * @returns A Nano ID action. @@ -88,10 +82,9 @@ export function nanoid( export function nanoid< TInput extends string, const TMessage extends ErrorMessage> | undefined, ->(length?: number, message?: TMessage): NanoIDAction; +>(message: TMessage): NanoIDAction; export function nanoid( - length: number = 21, message?: ErrorMessage> ): NanoIDAction> | undefined> { return { @@ -102,13 +95,8 @@ export function nanoid( expects: null, requirement: NANO_ID_REGEX, message, - length, _run(dataset, config) { - if ( - dataset.typed && - (!this.requirement.test(dataset.value) || - dataset.value.length !== this.length) - ) { + if (dataset.typed && !this.requirement.test(dataset.value)) { _addIssue(this, 'Nano ID', dataset, config); } return dataset as Dataset>; diff --git a/website/src/routes/api/(actions)/nanoid/index.mdx b/website/src/routes/api/(actions)/nanoid/index.mdx index ee538db36..9cb03ae07 100644 --- a/website/src/routes/api/(actions)/nanoid/index.mdx +++ b/website/src/routes/api/(actions)/nanoid/index.mdx @@ -14,7 +14,7 @@ import { properties } from './properties'; Creates a [Nano ID](https://github.com/ai/nanoid) validation action. ```ts -const Action = v.nanoid(length, message); +const Action = v.nanoid(message); ``` ## Generics @@ -24,12 +24,11 @@ const Action = v.nanoid(length, message); ## Parameters -- `length` - `message` ### Explanation -With `nanoid` you can validate the formatting of a string. If the input is not an Nano ID, you can use `message` to customize the error message. You can also specify the `length` of the Nano ID, which defaults to 21 if empty. +With `nanoid` you can validate the formatting of a string. If the input is not an Nano ID, you can use `message` to customize the error message. ## Returns @@ -46,7 +45,7 @@ Schema to validate a Nano ID. ```ts const NanoIDSchema = v.pipe( v.string(), - v.nanoid(21, 'The Nano ID is badly formatted.') + v.nanoid('The Nano ID is badly formatted.') ); ``` diff --git a/website/src/routes/api/(actions)/nanoid/properties.ts b/website/src/routes/api/(actions)/nanoid/properties.ts index c42eab0d7..b71f149e7 100644 --- a/website/src/routes/api/(actions)/nanoid/properties.ts +++ b/website/src/routes/api/(actions)/nanoid/properties.ts @@ -38,9 +38,6 @@ export const properties: Record = { name: 'TMessage', }, }, - length: { - type: 'number', - }, Action: { type: { type: 'custom', From a9204c171a6694c0ba1c6324fc5d1e3fac3fbb1a Mon Sep 17 00:00:00 2001 From: jasperteo Date: Sat, 24 Aug 2024 22:33:29 +0800 Subject: [PATCH 4/6] removed length as an argument for nanoid() action --- library/src/actions/nanoid/nanoid.test-d.ts | 10 +++---- library/src/actions/nanoid/nanoid.test.ts | 30 ++++++++++++++----- library/src/actions/nanoid/nanoid.ts | 24 +++++++++++---- .../src/routes/api/(actions)/nanoid/index.mdx | 7 +++-- .../routes/api/(actions)/nanoid/properties.ts | 3 ++ 5 files changed, 52 insertions(+), 22 deletions(-) diff --git a/library/src/actions/nanoid/nanoid.test-d.ts b/library/src/actions/nanoid/nanoid.test-d.ts index d41405145..be8442821 100644 --- a/library/src/actions/nanoid/nanoid.test-d.ts +++ b/library/src/actions/nanoid/nanoid.test-d.ts @@ -8,20 +8,20 @@ describe('nanoid', () => { type Action = NanoIDAction; expectTypeOf(nanoid()).toEqualTypeOf(); expectTypeOf( - nanoid(undefined) + nanoid(21, undefined) ).toEqualTypeOf(); }); test('with string message', () => { - expectTypeOf(nanoid('message')).toEqualTypeOf< + expectTypeOf(nanoid(21, 'message')).toEqualTypeOf< NanoIDAction >(); }); test('with function message', () => { - expectTypeOf(nanoid string>(() => 'message')).toEqualTypeOf< - NanoIDAction string> - >(); + expectTypeOf( + nanoid string>(21, () => 'message') + ).toEqualTypeOf string>>(); }); }); diff --git a/library/src/actions/nanoid/nanoid.test.ts b/library/src/actions/nanoid/nanoid.test.ts index 6a65e1bfa..4219164a9 100644 --- a/library/src/actions/nanoid/nanoid.test.ts +++ b/library/src/actions/nanoid/nanoid.test.ts @@ -11,6 +11,7 @@ describe('nanoid', () => { reference: nanoid, expects: null, requirement: NANO_ID_REGEX, + length: 21, async: false, _run: expect.any(Function), }; @@ -25,7 +26,7 @@ describe('nanoid', () => { }); test('with string message', () => { - expect(nanoid('message')).toStrictEqual({ + expect(nanoid(21, 'message')).toStrictEqual({ ...baseAction, message: 'message', } satisfies NanoIDAction); @@ -33,7 +34,7 @@ describe('nanoid', () => { test('with function message', () => { const message = () => 'message'; - expect(nanoid(message)).toStrictEqual({ + expect(nanoid(21, message)).toStrictEqual({ ...baseAction, message, } satisfies NanoIDAction); @@ -42,6 +43,7 @@ describe('nanoid', () => { describe('should return dataset without issues', () => { const action = nanoid(); + const action10 = nanoid(10); test('for untyped inputs', () => { expect(action._run({ typed: false, value: null }, {})).toStrictEqual({ @@ -50,24 +52,29 @@ describe('nanoid', () => { }); }); - test('for with _ and - symbols', () => { + test('for default ID length', () => { expectNoActionIssue(action, [ 'OIjC22zGKp_rrTYQBb3xt', + '4EdTtQZCc5GIFA9ABjEsQ', '8hiQ-95aV5qCnB4x0rPBu', 'aIojG8uVOE0-1ANOZLugU', + 'ODz0tL5pgbQvpbptus6LL', ]); }); - test('for alphabets and numerals only', () => { - expectNoActionIssue(action, [ - '4EdTtQZCc5GIFA9ABjEsQ', - 'ODz0tL5pgbQvpbptus6LL', + test('for 10 characters ID length', () => { + expectNoActionIssue(action10, [ + 'ix9v8KdiSn', + '7nU_kDcYdd', + 'gltYQWemBD', + 'btbKlDDIa-', + 't_XWeU7xf3', ]); }); }); describe('should return dataset with issues', () => { - const action = nanoid('message'); + const action = nanoid(21, 'message'); const baseIssue: Omit, 'input' | 'received'> = { kind: 'validation', type: 'nanoid', @@ -88,6 +95,13 @@ describe('nanoid', () => { ]); }); + test('for not 21 characters', () => { + expectActionIssue(action, baseIssue, [ + '8BHRbyPSzWSLK0JAQp', + 'pSnYAH-kCzsXWx7Lu5mQ72Hy', + ]); + }); + test('for special chars', () => { expectActionIssue(action, baseIssue, [ '@1o5BK76uGc-mbqeprAvX', diff --git a/library/src/actions/nanoid/nanoid.ts b/library/src/actions/nanoid/nanoid.ts index c8068d3ba..88cadc7d0 100644 --- a/library/src/actions/nanoid/nanoid.ts +++ b/library/src/actions/nanoid/nanoid.ts @@ -60,21 +60,27 @@ export interface NanoIDAction< * The error message. */ readonly message: TMessage; + /** + * The expected length of the Nano ID. + */ + readonly length?: number; } /** * Creates a [Nano ID](https://github.com/ai/nanoid) validation action. * + * @param length The expected length of the Nano ID. + * * @returns A Nano ID action. */ -export function nanoid(): NanoIDAction< - TInput, - undefined ->; +export function nanoid( + length?: number +): NanoIDAction; /** * Creates a [Nano ID](https://github.com/ai/nanoid) validation action. * + * @param length The expected length of the Nano ID. * @param message The error message. * * @returns A Nano ID action. @@ -82,9 +88,10 @@ export function nanoid(): NanoIDAction< export function nanoid< TInput extends string, const TMessage extends ErrorMessage> | undefined, ->(message: TMessage): NanoIDAction; +>(length?: number, message?: TMessage): NanoIDAction; export function nanoid( + length: number = 21, message?: ErrorMessage> ): NanoIDAction> | undefined> { return { @@ -95,8 +102,13 @@ export function nanoid( expects: null, requirement: NANO_ID_REGEX, message, + length, _run(dataset, config) { - if (dataset.typed && !this.requirement.test(dataset.value)) { + if ( + dataset.typed && + (!this.requirement.test(dataset.value) || + dataset.value.length !== this.length) + ) { _addIssue(this, 'Nano ID', dataset, config); } return dataset as Dataset>; diff --git a/website/src/routes/api/(actions)/nanoid/index.mdx b/website/src/routes/api/(actions)/nanoid/index.mdx index 9cb03ae07..ee538db36 100644 --- a/website/src/routes/api/(actions)/nanoid/index.mdx +++ b/website/src/routes/api/(actions)/nanoid/index.mdx @@ -14,7 +14,7 @@ import { properties } from './properties'; Creates a [Nano ID](https://github.com/ai/nanoid) validation action. ```ts -const Action = v.nanoid(message); +const Action = v.nanoid(length, message); ``` ## Generics @@ -24,11 +24,12 @@ const Action = v.nanoid(message); ## Parameters +- `length` - `message` ### Explanation -With `nanoid` you can validate the formatting of a string. If the input is not an Nano ID, you can use `message` to customize the error message. +With `nanoid` you can validate the formatting of a string. If the input is not an Nano ID, you can use `message` to customize the error message. You can also specify the `length` of the Nano ID, which defaults to 21 if empty. ## Returns @@ -45,7 +46,7 @@ Schema to validate a Nano ID. ```ts const NanoIDSchema = v.pipe( v.string(), - v.nanoid('The Nano ID is badly formatted.') + v.nanoid(21, 'The Nano ID is badly formatted.') ); ``` diff --git a/website/src/routes/api/(actions)/nanoid/properties.ts b/website/src/routes/api/(actions)/nanoid/properties.ts index b71f149e7..c42eab0d7 100644 --- a/website/src/routes/api/(actions)/nanoid/properties.ts +++ b/website/src/routes/api/(actions)/nanoid/properties.ts @@ -38,6 +38,9 @@ export const properties: Record = { name: 'TMessage', }, }, + length: { + type: 'number', + }, Action: { type: { type: 'custom', From 8276045cbe49b2a987ef981553be319a2f3a53c7 Mon Sep 17 00:00:00 2001 From: jasperteo Date: Sat, 24 Aug 2024 22:35:59 +0800 Subject: [PATCH 5/6] removed length as an argument for nanoid() action --- library/src/actions/nanoid/nanoid.test-d.ts | 10 +++---- library/src/actions/nanoid/nanoid.test.ts | 30 +++++-------------- library/src/actions/nanoid/nanoid.ts | 24 ++++----------- .../src/routes/api/(actions)/nanoid/index.mdx | 7 ++--- .../routes/api/(actions)/nanoid/properties.ts | 3 -- 5 files changed, 22 insertions(+), 52 deletions(-) diff --git a/library/src/actions/nanoid/nanoid.test-d.ts b/library/src/actions/nanoid/nanoid.test-d.ts index be8442821..d41405145 100644 --- a/library/src/actions/nanoid/nanoid.test-d.ts +++ b/library/src/actions/nanoid/nanoid.test-d.ts @@ -8,20 +8,20 @@ describe('nanoid', () => { type Action = NanoIDAction; expectTypeOf(nanoid()).toEqualTypeOf(); expectTypeOf( - nanoid(21, undefined) + nanoid(undefined) ).toEqualTypeOf(); }); test('with string message', () => { - expectTypeOf(nanoid(21, 'message')).toEqualTypeOf< + expectTypeOf(nanoid('message')).toEqualTypeOf< NanoIDAction >(); }); test('with function message', () => { - expectTypeOf( - nanoid string>(21, () => 'message') - ).toEqualTypeOf string>>(); + expectTypeOf(nanoid string>(() => 'message')).toEqualTypeOf< + NanoIDAction string> + >(); }); }); diff --git a/library/src/actions/nanoid/nanoid.test.ts b/library/src/actions/nanoid/nanoid.test.ts index 4219164a9..6a65e1bfa 100644 --- a/library/src/actions/nanoid/nanoid.test.ts +++ b/library/src/actions/nanoid/nanoid.test.ts @@ -11,7 +11,6 @@ describe('nanoid', () => { reference: nanoid, expects: null, requirement: NANO_ID_REGEX, - length: 21, async: false, _run: expect.any(Function), }; @@ -26,7 +25,7 @@ describe('nanoid', () => { }); test('with string message', () => { - expect(nanoid(21, 'message')).toStrictEqual({ + expect(nanoid('message')).toStrictEqual({ ...baseAction, message: 'message', } satisfies NanoIDAction); @@ -34,7 +33,7 @@ describe('nanoid', () => { test('with function message', () => { const message = () => 'message'; - expect(nanoid(21, message)).toStrictEqual({ + expect(nanoid(message)).toStrictEqual({ ...baseAction, message, } satisfies NanoIDAction); @@ -43,7 +42,6 @@ describe('nanoid', () => { describe('should return dataset without issues', () => { const action = nanoid(); - const action10 = nanoid(10); test('for untyped inputs', () => { expect(action._run({ typed: false, value: null }, {})).toStrictEqual({ @@ -52,29 +50,24 @@ describe('nanoid', () => { }); }); - test('for default ID length', () => { + test('for with _ and - symbols', () => { expectNoActionIssue(action, [ 'OIjC22zGKp_rrTYQBb3xt', - '4EdTtQZCc5GIFA9ABjEsQ', '8hiQ-95aV5qCnB4x0rPBu', 'aIojG8uVOE0-1ANOZLugU', - 'ODz0tL5pgbQvpbptus6LL', ]); }); - test('for 10 characters ID length', () => { - expectNoActionIssue(action10, [ - 'ix9v8KdiSn', - '7nU_kDcYdd', - 'gltYQWemBD', - 'btbKlDDIa-', - 't_XWeU7xf3', + test('for alphabets and numerals only', () => { + expectNoActionIssue(action, [ + '4EdTtQZCc5GIFA9ABjEsQ', + 'ODz0tL5pgbQvpbptus6LL', ]); }); }); describe('should return dataset with issues', () => { - const action = nanoid(21, 'message'); + const action = nanoid('message'); const baseIssue: Omit, 'input' | 'received'> = { kind: 'validation', type: 'nanoid', @@ -95,13 +88,6 @@ describe('nanoid', () => { ]); }); - test('for not 21 characters', () => { - expectActionIssue(action, baseIssue, [ - '8BHRbyPSzWSLK0JAQp', - 'pSnYAH-kCzsXWx7Lu5mQ72Hy', - ]); - }); - test('for special chars', () => { expectActionIssue(action, baseIssue, [ '@1o5BK76uGc-mbqeprAvX', diff --git a/library/src/actions/nanoid/nanoid.ts b/library/src/actions/nanoid/nanoid.ts index 88cadc7d0..c8068d3ba 100644 --- a/library/src/actions/nanoid/nanoid.ts +++ b/library/src/actions/nanoid/nanoid.ts @@ -60,27 +60,21 @@ export interface NanoIDAction< * The error message. */ readonly message: TMessage; - /** - * The expected length of the Nano ID. - */ - readonly length?: number; } /** * Creates a [Nano ID](https://github.com/ai/nanoid) validation action. * - * @param length The expected length of the Nano ID. - * * @returns A Nano ID action. */ -export function nanoid( - length?: number -): NanoIDAction; +export function nanoid(): NanoIDAction< + TInput, + undefined +>; /** * Creates a [Nano ID](https://github.com/ai/nanoid) validation action. * - * @param length The expected length of the Nano ID. * @param message The error message. * * @returns A Nano ID action. @@ -88,10 +82,9 @@ export function nanoid( export function nanoid< TInput extends string, const TMessage extends ErrorMessage> | undefined, ->(length?: number, message?: TMessage): NanoIDAction; +>(message: TMessage): NanoIDAction; export function nanoid( - length: number = 21, message?: ErrorMessage> ): NanoIDAction> | undefined> { return { @@ -102,13 +95,8 @@ export function nanoid( expects: null, requirement: NANO_ID_REGEX, message, - length, _run(dataset, config) { - if ( - dataset.typed && - (!this.requirement.test(dataset.value) || - dataset.value.length !== this.length) - ) { + if (dataset.typed && !this.requirement.test(dataset.value)) { _addIssue(this, 'Nano ID', dataset, config); } return dataset as Dataset>; diff --git a/website/src/routes/api/(actions)/nanoid/index.mdx b/website/src/routes/api/(actions)/nanoid/index.mdx index ee538db36..9cb03ae07 100644 --- a/website/src/routes/api/(actions)/nanoid/index.mdx +++ b/website/src/routes/api/(actions)/nanoid/index.mdx @@ -14,7 +14,7 @@ import { properties } from './properties'; Creates a [Nano ID](https://github.com/ai/nanoid) validation action. ```ts -const Action = v.nanoid(length, message); +const Action = v.nanoid(message); ``` ## Generics @@ -24,12 +24,11 @@ const Action = v.nanoid(length, message); ## Parameters -- `length` - `message` ### Explanation -With `nanoid` you can validate the formatting of a string. If the input is not an Nano ID, you can use `message` to customize the error message. You can also specify the `length` of the Nano ID, which defaults to 21 if empty. +With `nanoid` you can validate the formatting of a string. If the input is not an Nano ID, you can use `message` to customize the error message. ## Returns @@ -46,7 +45,7 @@ Schema to validate a Nano ID. ```ts const NanoIDSchema = v.pipe( v.string(), - v.nanoid(21, 'The Nano ID is badly formatted.') + v.nanoid('The Nano ID is badly formatted.') ); ``` diff --git a/website/src/routes/api/(actions)/nanoid/properties.ts b/website/src/routes/api/(actions)/nanoid/properties.ts index c42eab0d7..b71f149e7 100644 --- a/website/src/routes/api/(actions)/nanoid/properties.ts +++ b/website/src/routes/api/(actions)/nanoid/properties.ts @@ -38,9 +38,6 @@ export const properties: Record = { name: 'TMessage', }, }, - length: { - type: 'number', - }, Action: { type: { type: 'custom', From 9f25d7810e975fa08e6b24ddb9f3ed78a4e0b69d Mon Sep 17 00:00:00 2001 From: Fabian Hiller Date: Tue, 27 Aug 2024 21:14:14 +0200 Subject: [PATCH 6/6] Improve unit tests and docs for nanoid action --- library/CHANGELOG.md | 4 +++ library/src/actions/nanoid/nanoid.test.ts | 28 +++++++++++++------ .../src/routes/api/(actions)/cuid2/index.mdx | 9 +++++- .../routes/api/(actions)/isoTime/index.mdx | 2 +- .../src/routes/api/(actions)/nanoid/index.mdx | 7 ++++- .../routes/api/(async)/customAsync/index.mdx | 1 + .../api/(async)/fallbackAsync/index.mdx | 1 + .../api/(async)/intersectAsync/index.mdx | 1 + .../routes/api/(async)/lazyAsync/index.mdx | 1 + .../src/routes/api/(methods)/config/index.mdx | 1 + .../routes/api/(methods)/fallback/index.mdx | 1 + .../src/routes/api/(schemas)/custom/index.mdx | 1 + .../routes/api/(schemas)/intersect/index.mdx | 1 + .../src/routes/api/(schemas)/lazy/index.mdx | 1 + .../src/routes/api/(schemas)/union/index.mdx | 1 + 15 files changed, 48 insertions(+), 12 deletions(-) diff --git a/library/CHANGELOG.md b/library/CHANGELOG.md index 99022ef4c..099bc9812 100644 --- a/library/CHANGELOG.md +++ b/library/CHANGELOG.md @@ -2,6 +2,10 @@ All notable changes to the library will be documented in this file. +## vX.X.X (Month DD, YYYY) + +- Add `nanoid` action to validate Nano IDs (pull request #789) + ## v0.39.0 (August 24, 2024) - Add support for `exactOptionalPropertyTypes` config (issue #385) diff --git a/library/src/actions/nanoid/nanoid.test.ts b/library/src/actions/nanoid/nanoid.test.ts index 6a65e1bfa..b68fe7385 100644 --- a/library/src/actions/nanoid/nanoid.test.ts +++ b/library/src/actions/nanoid/nanoid.test.ts @@ -50,18 +50,27 @@ describe('nanoid', () => { }); }); - test('for with _ and - symbols', () => { + test('for normal Nano IDs', () => { expectNoActionIssue(action, [ - 'OIjC22zGKp_rrTYQBb3xt', - '8hiQ-95aV5qCnB4x0rPBu', - 'aIojG8uVOE0-1ANOZLugU', + 'NOi6NWfhDRpgzBYFRR-uE', + 'D7j9AWMA6anLPDE2_2uHz', + 'g_Se_MXrTmRJpmcp8cN5m', + 'Oc0XNYtCgyrX-x2T33z3E', + 'gGCr-6yBmZkOTJQ1oLAFr', ]); }); - test('for alphabets and numerals only', () => { + test('for single char', () => { + expectNoActionIssue(action, ['a', 'z', 'A', 'Z', '0', '9', '_', '-']); + }); + + test('for two chars', () => { + expectNoActionIssue(action, ['aa', 'zz', 'AZ', '09', '_-', '9A']); + }); + + test('for long IDs', () => { expectNoActionIssue(action, [ - '4EdTtQZCc5GIFA9ABjEsQ', - 'ODz0tL5pgbQvpbptus6LL', + '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_-', ]); }); }); @@ -80,11 +89,11 @@ describe('nanoid', () => { expectActionIssue(action, baseIssue, ['', ' ', '\n']); }); - test('for string with spaces', () => { + test('for blank spaces', () => { expectActionIssue(action, baseIssue, [ + ' vM7SGqVFmPS5tw7fII-G', 'BImGM 7USGakXaVhydHgO', 'LBjowKnkbk95kK3IoUV7 ', - ' vM7SGqVFmPS5tw7fII-G', ]); }); @@ -95,6 +104,7 @@ describe('nanoid', () => { '$3WZ4tXxsuiDBezXIJKlP', '%gSjBHLFDO67bE-nbgBRi', '&2zYmqr0APdImhdxC69t4', + '–gGCr6yBmZkOTJQ1oLAFr', ]); }); }); diff --git a/website/src/routes/api/(actions)/cuid2/index.mdx b/website/src/routes/api/(actions)/cuid2/index.mdx index 78fa1ab57..198ffa7d7 100644 --- a/website/src/routes/api/(actions)/cuid2/index.mdx +++ b/website/src/routes/api/(actions)/cuid2/index.mdx @@ -6,6 +6,7 @@ contributors: - fabian-hiller --- +import { Link } from '@builder.io/qwik-city'; import { ApiList, Property } from '~/components'; import { properties } from './properties'; @@ -30,6 +31,8 @@ const Action = v.cuid2(message); With `cuid2` you can validate the formatting of a string. If the input is not an Cuid2, you can use `message` to customize the error message. +> Since Cuid2s are not limited to a fixed length, it is recommended to combine `cuid2` with `length` to ensure the correct length. + ## Returns - `Action` @@ -43,7 +46,11 @@ The following examples show how `cuid2` can be used. Schema to validate an Cuid2. ```ts -const Cuid2Schema = v.pip(v.string(), v.cuid2('The Cuid2 is badly formatted.')); +const Cuid2Schema = v.pipe( + v.string(), + v.cuid2('The Cuid2 is badly formatted.'), + v.length(10, 'The Cuid2 must be 10 characters long.') +); ``` ## Related diff --git a/website/src/routes/api/(actions)/isoTime/index.mdx b/website/src/routes/api/(actions)/isoTime/index.mdx index a173cfdc6..83e9bc29e 100644 --- a/website/src/routes/api/(actions)/isoTime/index.mdx +++ b/website/src/routes/api/(actions)/isoTime/index.mdx @@ -45,7 +45,7 @@ The following examples show how `isoTime` can be used. Schema to validate an ISO time. ```ts -const IsoTimeSchema = v.pip( +const IsoTimeSchema = v.pipe( v.string(), v.isoTime('The time is badly formatted.') ); diff --git a/website/src/routes/api/(actions)/nanoid/index.mdx b/website/src/routes/api/(actions)/nanoid/index.mdx index 9cb03ae07..1f0863083 100644 --- a/website/src/routes/api/(actions)/nanoid/index.mdx +++ b/website/src/routes/api/(actions)/nanoid/index.mdx @@ -4,8 +4,10 @@ description: Creates a Nano ID validation action. source: /actions/nanoid/nanoid.ts contributors: - jasperteo + - fabian-hiller --- +import { Link } from '@builder.io/qwik-city'; import { ApiList, Property } from '~/components'; import { properties } from './properties'; @@ -38,6 +40,8 @@ With `nanoid` you can validate the formatting of a string. If the input is not a The following examples show how `nanoid` can be used. +> Since Nano IDs are not limited to a fixed length, it is recommended to combine `nanoid` with `length` to ensure the correct length. + ### Nano ID schema Schema to validate a Nano ID. @@ -45,7 +49,8 @@ Schema to validate a Nano ID. ```ts const NanoIDSchema = v.pipe( v.string(), - v.nanoid('The Nano ID is badly formatted.') + v.nanoid('The Nano ID is badly formatted.'), + v.length(21, 'The Nano ID must be 21 characters long.') ); ``` diff --git a/website/src/routes/api/(async)/customAsync/index.mdx b/website/src/routes/api/(async)/customAsync/index.mdx index 4c3c28d8c..fef6cf184 100644 --- a/website/src/routes/api/(async)/customAsync/index.mdx +++ b/website/src/routes/api/(async)/customAsync/index.mdx @@ -154,6 +154,7 @@ The following APIs can be combined with `customAsync`. 'minSize', 'minValue', 'multipleOf', + 'nanoid', 'nonEmpty', 'notBytes', 'notLength', diff --git a/website/src/routes/api/(async)/fallbackAsync/index.mdx b/website/src/routes/api/(async)/fallbackAsync/index.mdx index dfe94e5f3..ec88d4a6f 100644 --- a/website/src/routes/api/(async)/fallbackAsync/index.mdx +++ b/website/src/routes/api/(async)/fallbackAsync/index.mdx @@ -182,6 +182,7 @@ The following APIs can be combined with `fallbackAsync`. 'minSize', 'minValue', 'multipleOf', + 'nanoid', 'nonEmpty', 'notBytes', 'notLength', diff --git a/website/src/routes/api/(async)/intersectAsync/index.mdx b/website/src/routes/api/(async)/intersectAsync/index.mdx index ea36a86ec..5e941ae92 100644 --- a/website/src/routes/api/(async)/intersectAsync/index.mdx +++ b/website/src/routes/api/(async)/intersectAsync/index.mdx @@ -185,6 +185,7 @@ The following APIs can be combined with `intersectAsync`. 'minSize', 'minValue', 'multipleOf', + 'nanoid', 'nonEmpty', 'notBytes', 'notLength', diff --git a/website/src/routes/api/(async)/lazyAsync/index.mdx b/website/src/routes/api/(async)/lazyAsync/index.mdx index 57ac99034..0769706dc 100644 --- a/website/src/routes/api/(async)/lazyAsync/index.mdx +++ b/website/src/routes/api/(async)/lazyAsync/index.mdx @@ -217,6 +217,7 @@ The following APIs can be combined with `lazyAsync`. 'minSize', 'minValue', 'multipleOf', + 'nanoid', 'nonEmpty', 'notBytes', 'notLength', diff --git a/website/src/routes/api/(methods)/config/index.mdx b/website/src/routes/api/(methods)/config/index.mdx index cb727350c..ae0d156b4 100644 --- a/website/src/routes/api/(methods)/config/index.mdx +++ b/website/src/routes/api/(methods)/config/index.mdx @@ -198,6 +198,7 @@ The following APIs can be combined with `config`. 'minSize', 'minValue', 'multipleOf', + 'nanoid', 'nonEmpty', 'notBytes', 'notLength', diff --git a/website/src/routes/api/(methods)/fallback/index.mdx b/website/src/routes/api/(methods)/fallback/index.mdx index b25af27c2..1c55f39f1 100644 --- a/website/src/routes/api/(methods)/fallback/index.mdx +++ b/website/src/routes/api/(methods)/fallback/index.mdx @@ -196,6 +196,7 @@ The following APIs can be combined with `fallback`. 'minSize', 'minValue', 'multipleOf', + 'nanoid', 'nonEmpty', 'notBytes', 'notLength', diff --git a/website/src/routes/api/(schemas)/custom/index.mdx b/website/src/routes/api/(schemas)/custom/index.mdx index 6c6e15559..e0ace0339 100644 --- a/website/src/routes/api/(schemas)/custom/index.mdx +++ b/website/src/routes/api/(schemas)/custom/index.mdx @@ -158,6 +158,7 @@ The following APIs can be combined with `custom`. 'minSize', 'minValue', 'multipleOf', + 'nanoid', 'nonEmpty', 'notBytes', 'notLength', diff --git a/website/src/routes/api/(schemas)/intersect/index.mdx b/website/src/routes/api/(schemas)/intersect/index.mdx index 0df46bfec..0fde37503 100644 --- a/website/src/routes/api/(schemas)/intersect/index.mdx +++ b/website/src/routes/api/(schemas)/intersect/index.mdx @@ -181,6 +181,7 @@ The following APIs can be combined with `intersect`. 'minSize', 'minValue', 'multipleOf', + 'nanoid', 'nonEmpty', 'notBytes', 'notLength', diff --git a/website/src/routes/api/(schemas)/lazy/index.mdx b/website/src/routes/api/(schemas)/lazy/index.mdx index 9b0773ce9..64cad44df 100644 --- a/website/src/routes/api/(schemas)/lazy/index.mdx +++ b/website/src/routes/api/(schemas)/lazy/index.mdx @@ -217,6 +217,7 @@ The following APIs can be combined with `lazy`. 'minSize', 'minValue', 'multipleOf', + 'nanoid', 'nonEmpty', 'notBytes', 'notLength', diff --git a/website/src/routes/api/(schemas)/union/index.mdx b/website/src/routes/api/(schemas)/union/index.mdx index baba7f59c..51d8941c0 100644 --- a/website/src/routes/api/(schemas)/union/index.mdx +++ b/website/src/routes/api/(schemas)/union/index.mdx @@ -197,6 +197,7 @@ The following APIs can be combined with `union`. 'minSize', 'minValue', 'multipleOf', + 'nanoid', 'nonEmpty', 'notBytes', 'notLength',