diff --git a/library/CHANGELOG.md b/library/CHANGELOG.md index e95526f71..78e1545fb 100644 --- a/library/CHANGELOG.md +++ b/library/CHANGELOG.md @@ -4,6 +4,7 @@ All notable changes to the library will be documented in this file. ## vX.X.X (Month DD, YYYY) +- Add `normalize` action to normalize strings (issue #691) - Add support for async schemas to `entriesFromList` util - Add support for numbers and symbols to `entriesFromList` util (issue #492) - Add `key` property to `SetPathItem` type to improve DX (issue #693, #694) diff --git a/library/src/actions/index.ts b/library/src/actions/index.ts index 02002be20..971764bf3 100644 --- a/library/src/actions/index.ts +++ b/library/src/actions/index.ts @@ -47,6 +47,7 @@ export * from './minSize/index.ts'; export * from './minValue/index.ts'; export * from './multipleOf/index.ts'; export * from './nonEmpty/index.ts'; +export * from './normalize/index.ts'; export * from './notBytes/index.ts'; export * from './notLength/index.ts'; export * from './notSize/index.ts'; diff --git a/library/src/actions/normalize/index.ts b/library/src/actions/normalize/index.ts new file mode 100644 index 000000000..eb7d2a202 --- /dev/null +++ b/library/src/actions/normalize/index.ts @@ -0,0 +1 @@ +export * from './normalize.ts'; diff --git a/library/src/actions/normalize/normalize.test-d.ts b/library/src/actions/normalize/normalize.test-d.ts new file mode 100644 index 000000000..da0521642 --- /dev/null +++ b/library/src/actions/normalize/normalize.test-d.ts @@ -0,0 +1,31 @@ +import { describe, expectTypeOf, test } from 'vitest'; +import type { InferInput, InferIssue, InferOutput } from '../../types/index.ts'; +import { normalize, type NormalizeAction } from './normalize.ts'; + +describe('normalize', () => { + describe('should return action object', () => { + test('with undefined form', () => { + expectTypeOf(normalize()).toEqualTypeOf>(); + }); + + test('with defined form', () => { + expectTypeOf(normalize('NFKC')).toEqualTypeOf>(); + }); + }); + + describe('should infer correct types', () => { + type Action = NormalizeAction; + + test('of input', () => { + expectTypeOf>().toEqualTypeOf(); + }); + + test('of output', () => { + expectTypeOf>().toEqualTypeOf(); + }); + + test('of issue', () => { + expectTypeOf>().toEqualTypeOf(); + }); + }); +}); diff --git a/library/src/actions/normalize/normalize.test.ts b/library/src/actions/normalize/normalize.test.ts new file mode 100644 index 000000000..b6f89c927 --- /dev/null +++ b/library/src/actions/normalize/normalize.test.ts @@ -0,0 +1,60 @@ +import { describe, expect, test } from 'vitest'; +import { normalize, type NormalizeAction } from './normalize.ts'; + +describe('normalize', () => { + describe('should return action object', () => { + const baseAction: Omit, 'form'> = { + kind: 'transformation', + type: 'normalize', + reference: normalize, + async: false, + _run: expect.any(Function), + }; + + test('with undefined form', () => { + expect(normalize()).toStrictEqual({ + ...baseAction, + form: undefined, + } satisfies NormalizeAction); + }); + + test('with defined form', () => { + expect(normalize('NFKD')).toStrictEqual({ + ...baseAction, + form: 'NFKD', + } satisfies NormalizeAction<'NFKD'>); + }); + }); + + describe('should normalize string', () => { + test('with undefined form', () => { + expect( + normalize()._run({ typed: true, value: '\u00F1' }, {}) + ).toStrictEqual({ + typed: true, + value: 'ñ', + }); + expect( + normalize()._run({ typed: true, value: '\u006E\u0303' }, {}) + ).toStrictEqual({ + typed: true, + value: 'ñ', + }); + }); + + test('with defined form', () => { + expect( + normalize('NFKD')._run({ typed: true, value: '\uFB00' }, {}) + ).toStrictEqual({ + typed: true, + value: 'ff', + }); + expect( + normalize('NFKD')._run({ typed: true, value: '\u0066\u0066' }, {}) + ).toStrictEqual({ + typed: true, + value: 'ff', + }); + }); + }); +}); diff --git a/library/src/actions/normalize/normalize.ts b/library/src/actions/normalize/normalize.ts new file mode 100644 index 000000000..e093d7a1f --- /dev/null +++ b/library/src/actions/normalize/normalize.ts @@ -0,0 +1,59 @@ +import type { BaseTransformation } from '../../types/index.ts'; + +/** + * Normalize form type. + */ +export type NormalizeForm = 'NFC' | 'NFD' | 'NFKC' | 'NFKD'; + +/** + * Normalize action type. + */ +export interface NormalizeAction + extends BaseTransformation { + /** + * The action type. + */ + readonly type: 'normalize'; + /** + * The action reference. + */ + readonly reference: typeof normalize; + /** + * The normalization form. + */ + readonly form: TForm; +} + +/** + * Creates a normalize transformation action. + * + * @returns A normalize action. + */ +export function normalize(): NormalizeAction; + +/** + * Creates a normalize transformation action. + * + * @param form The normalization form. + * + * @returns A normalize action. + */ +export function normalize( + form: TForm +): NormalizeAction; + +export function normalize( + form?: NormalizeForm +): NormalizeAction { + return { + kind: 'transformation', + type: 'normalize', + reference: normalize, + async: false, + form, + _run(dataset) { + dataset.value = dataset.value.normalize(this.form); + return dataset; + }, + }; +} diff --git a/website/src/routes/api/(actions)/normalize/index.mdx b/website/src/routes/api/(actions)/normalize/index.mdx new file mode 100644 index 000000000..fe5a90f50 --- /dev/null +++ b/website/src/routes/api/(actions)/normalize/index.mdx @@ -0,0 +1,58 @@ +--- +title: normalize +description: Creates a normalize transformation action. +source: /actions/normalize/normalize.ts +contributors: + - fabian-hiller +--- + +import { ApiList, Property } from '~/components'; +import { properties } from './properties'; + +# normalize + +Creates a normalize transformation action. + +```ts +const Action = v.normalize(form); +``` + +## Generics + +- `TForm` + +## Parameters + +- `form` + +## Returns + +- `Action` + +## Examples + +The following examples show how `normalize` can be used. + +### Normalized string + +Schema to normalize a string. + +```ts +const StringSchema = v.pipe(v.string(), v.normalize()); +``` + +## Related + +The following APIs can be combined with `normalize`. + +### Schemas + + + +### Methods + + + +### Utils + + diff --git a/website/src/routes/api/(actions)/normalize/properties.ts b/website/src/routes/api/(actions)/normalize/properties.ts new file mode 100644 index 000000000..a237738a4 --- /dev/null +++ b/website/src/routes/api/(actions)/normalize/properties.ts @@ -0,0 +1,31 @@ +import type { PropertyProps } from '~/components'; + +export const properties: Record = { + TForm: { + modifier: 'extends', + type: { + type: 'union', + options: [ + { + type: 'custom', + name: 'NormalizeForm', + href: '../NormalizeForm/', + }, + 'undefined', + ], + }, + }, + form: { + type: { + type: 'custom', + name: 'TForm', + }, + }, + Action: { + type: { + type: 'custom', + name: 'NormalizeAction', + href: '../NormalizeAction/', + }, + }, +}; diff --git a/website/src/routes/api/menu.md b/website/src/routes/api/menu.md index f19b81a85..8cf5dec9b 100644 --- a/website/src/routes/api/menu.md +++ b/website/src/routes/api/menu.md @@ -120,6 +120,7 @@ - [minValue](/api/minValue/) - [multipleOf](/api/multipleOf/) - [nonEmpty](/api/nonEmpty/) +- [normalize](/api/normalize/) - [notBytes](/api/notBytes/) - [notLength](/api/notLength/) - [notSize](/api/notSize/)