From cf191d0f93322002adf00ab07593a332d4dc4eab Mon Sep 17 00:00:00 2001 From: Eduardo San Martin Morote Date: Fri, 29 Aug 2025 15:06:32 +0200 Subject: [PATCH] feat: add string parser --- .../matchers/param-parsers/index.ts | 1 + .../matchers/param-parsers/strings.spec.ts | 143 ++++++++++++++++++ .../matchers/param-parsers/strings.ts | 28 ++++ 3 files changed, 172 insertions(+) create mode 100644 packages/router/src/experimental/route-resolver/matchers/param-parsers/strings.spec.ts create mode 100644 packages/router/src/experimental/route-resolver/matchers/param-parsers/strings.ts diff --git a/packages/router/src/experimental/route-resolver/matchers/param-parsers/index.ts b/packages/router/src/experimental/route-resolver/matchers/param-parsers/index.ts index 0e457cea..c5888f68 100644 --- a/packages/router/src/experimental/route-resolver/matchers/param-parsers/index.ts +++ b/packages/router/src/experimental/route-resolver/matchers/param-parsers/index.ts @@ -3,6 +3,7 @@ import type { ParamParser } from './types' export { PARAM_PARSER_BOOL } from './booleans' export { PARAM_PARSER_INT } from './integers' +export { PARAM_PARSER_STRING } from './strings' export const PATH_PARAM_SINGLE_DEFAULT: ParamParser = {} diff --git a/packages/router/src/experimental/route-resolver/matchers/param-parsers/strings.spec.ts b/packages/router/src/experimental/route-resolver/matchers/param-parsers/strings.spec.ts new file mode 100644 index 00000000..0ea83ef3 --- /dev/null +++ b/packages/router/src/experimental/route-resolver/matchers/param-parsers/strings.spec.ts @@ -0,0 +1,143 @@ +import { describe, expect, it } from 'vitest' +import { PARAM_PARSER_STRING } from './strings' +import { MatchMiss } from '../errors' + +describe('PARAM_PARSER_STRING', () => { + describe('get() - Single Values', () => { + it('returns string values as-is', () => { + expect(PARAM_PARSER_STRING.get('hello')).toBe('hello') + expect(PARAM_PARSER_STRING.get('world')).toBe('world') + expect(PARAM_PARSER_STRING.get('123')).toBe('123') + expect(PARAM_PARSER_STRING.get('true')).toBe('true') + expect(PARAM_PARSER_STRING.get('')).toBe('') + }) + + it('returns empty string for null values', () => { + expect(PARAM_PARSER_STRING.get(null)).toBe('') + }) + + it('returns empty string for undefined values', () => { + expect(PARAM_PARSER_STRING.get(undefined)).toBe('') + }) + }) + + describe('get() - Array Values', () => { + it('returns arrays of strings as-is', () => { + expect(PARAM_PARSER_STRING.get(['hello', 'world'])).toEqual([ + 'hello', + 'world', + ]) + expect(PARAM_PARSER_STRING.get(['one', 'two', 'three'])).toEqual([ + 'one', + 'two', + 'three', + ]) + expect(PARAM_PARSER_STRING.get(['123', 'true', 'false'])).toEqual([ + '123', + 'true', + 'false', + ]) + }) + + it('handles empty arrays', () => { + expect(PARAM_PARSER_STRING.get([])).toEqual([]) + }) + + it('handles arrays with special characters', () => { + expect(PARAM_PARSER_STRING.get(['hello world', 'foo-bar'])).toEqual([ + 'hello world', + 'foo-bar', + ]) + expect( + PARAM_PARSER_STRING.get(['hello@world.com', '!@#$%^&*()']) + ).toEqual(['hello@world.com', '!@#$%^&*()']) + }) + + it('handles arrays with empty strings', () => { + expect(PARAM_PARSER_STRING.get(['', 'hello', ''])).toEqual([ + '', + 'hello', + '', + ]) + expect(PARAM_PARSER_STRING.get([''])).toEqual(['']) + }) + + it('filters out null values from arrays', () => { + expect(PARAM_PARSER_STRING.get(['hello', null, 'world'])).toEqual([ + 'hello', + 'world', + ]) + expect(PARAM_PARSER_STRING.get([null])).toEqual([]) + expect(PARAM_PARSER_STRING.get(['foo', null, null, 'bar'])).toEqual([ + 'foo', + 'bar', + ]) + expect(PARAM_PARSER_STRING.get([null, null])).toEqual([]) + }) + + it('handles mixed arrays with null values', () => { + expect( + PARAM_PARSER_STRING.get([null, 'hello', null, 'world', null]) + ).toEqual(['hello', 'world']) + expect(PARAM_PARSER_STRING.get(['first', null, '', 'last'])).toEqual([ + 'first', + '', + 'last', + ]) + }) + }) + + describe('set() - Single Values', () => { + it('converts values to strings', () => { + expect(PARAM_PARSER_STRING.set('hello')).toBe('hello') + expect(PARAM_PARSER_STRING.set('world')).toBe('world') + expect(PARAM_PARSER_STRING.set('')).toBe('') + }) + + it('returns an empty string for null values', () => { + expect(PARAM_PARSER_STRING.set(null)).toBe('') + }) + + // NOTE: undefined is not allowed as input to set() per the type definition + // it('returns an empty string for undefined values', () => { + // expect(PARAM_PARSER_STRING.set(undefined)).toBe('') + // }) + }) + + describe('set() - Array Values', () => { + it('converts arrays of strings to arrays of strings', () => { + expect(PARAM_PARSER_STRING.set(['hello', 'world'])).toEqual([ + 'hello', + 'world', + ]) + expect(PARAM_PARSER_STRING.set(['one', 'two', 'three'])).toEqual([ + 'one', + 'two', + 'three', + ]) + }) + + it('handles empty arrays', () => { + expect(PARAM_PARSER_STRING.set([])).toEqual([]) + }) + + it('handles arrays with empty strings', () => { + expect(PARAM_PARSER_STRING.set(['', 'hello', ''])).toEqual([ + '', + 'hello', + '', + ]) + expect(PARAM_PARSER_STRING.set([''])).toEqual(['']) + }) + + it('handles arrays with special characters', () => { + expect(PARAM_PARSER_STRING.set(['hello world', 'foo-bar'])).toEqual([ + 'hello world', + 'foo-bar', + ]) + expect( + PARAM_PARSER_STRING.set(['hello@world.com', '!@#$%^&*()']) + ).toEqual(['hello@world.com', '!@#$%^&*()']) + }) + }) +}) diff --git a/packages/router/src/experimental/route-resolver/matchers/param-parsers/strings.ts b/packages/router/src/experimental/route-resolver/matchers/param-parsers/strings.ts new file mode 100644 index 00000000..22fa9e0b --- /dev/null +++ b/packages/router/src/experimental/route-resolver/matchers/param-parsers/strings.ts @@ -0,0 +1,28 @@ +import { ParamParser } from './types' + +const PARAM_STRING_SINGLE = { + get: (value: string | null | undefined): string => value ?? '', + set: (value: string) => String(value), +} satisfies ParamParser + +const PARAM_STRING_REPEATABLE = { + get: (value: (string | null)[]) => + value.filter((v): v is string => v != null), + set: (value: string[]) => value.map(String), +} satisfies ParamParser + +/** + * Native Param parser for strings. + * + * @internal + */ +export const PARAM_PARSER_STRING = { + get: value => + Array.isArray(value) + ? PARAM_STRING_REPEATABLE.get(value) + : PARAM_STRING_SINGLE.get(value), + set: value => + Array.isArray(value) + ? PARAM_STRING_REPEATABLE.set(value) + : PARAM_STRING_SINGLE.set(value ?? ''), +} satisfies ParamParser -- 2.47.3