Skip to content

Commit

Permalink
merging params on RouteFlat, setup service to validate those params o…
Browse files Browse the repository at this point in the history
…n route match
  • Loading branch information
stackoverfloweth committed Dec 14, 2023
1 parent 8e8f02c commit 828bd7e
Show file tree
Hide file tree
Showing 9 changed files with 147 additions and 9 deletions.
2 changes: 2 additions & 0 deletions src/types/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
export * from './invalidRouteParamValueError'
export * from './optional'
export * from './params'
export * from './routeFlat'
export * from './routeMethods'
Expand Down
12 changes: 12 additions & 0 deletions src/types/optional.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { Param, ParamGetter } from '@/types/params'
import { getParamValue } from '@/utilities'

export function optional<T extends Param>(param: T): ParamGetter<T | undefined> {
return (value: string | undefined) => {
if (value === undefined) {
return undefined
}

return getParamValue(value, param)
}
}
6 changes: 5 additions & 1 deletion src/types/routeFlat.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import { Param } from '@/types/params'
import { Path } from '@/utilities'

export type RouteFlat = {
name: string,
path: string,
path: string | Path,
regex: RegExp,
params: Record<string, Param[]>,
}
4 changes: 2 additions & 2 deletions src/utilities/createRouter.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { RouteFlat, RouteMethods, Routes } from '@/types'
import { flattenRoutes } from '@/utilities/flattenRoutes'
import { flattenRoutes, routeParamsAreValid } from '@/utilities'

export type Router<T extends Routes> = {
routes: RouteMethods<T, Record<never, never>>,
Expand All @@ -10,7 +10,7 @@ export function createRouter<T extends Routes>(routes: T): Router<T> {
const flattened = flattenRoutes(routes)

function routeMatch(path: string): RouteFlat[] {
return flattened.filter(route => route.regex.test(path))
return flattened.filter(route => route.regex.test(path) && routeParamsAreValid(path, route))
}

return {
Expand Down
69 changes: 67 additions & 2 deletions src/utilities/flattenRoutes.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { describe, expect, test } from 'vitest'
import { Route, Routes } from '@/types'
import { flattenRoutes, generateRouteRegexPattern } from '@/utilities'
import { Route, Routes, optional } from '@/types'
import { flattenRoutes, generateRouteRegexPattern, getRouteParams, path } from '@/utilities'

const component = { template: '<div>This is component</div>' }

Expand Down Expand Up @@ -64,6 +64,71 @@ describe('flattenRoutes', () => {

expect(response.path).toBe(parentRoute.path + childRoute.path)
})

test('given path not as string, still combines to fullPath', () => {
const childRoute: Route = {
name: 'edit-account',
path: path('/:accountId', {
accountId: Number,
}),
component,
}

const parentRoute: Route = {
name: 'accounts',
path: '/accounts',
children: [childRoute],
}

const [response] = flattenRoutes([parentRoute])

expect(response.path).toBe(parentRoute.path + childRoute.path)
})
})

describe('getRouteParams', () => {
test('given path without params, returns empty object', () => {
const path = '/string/example/without/params'

const response = getRouteParams(path)

expect(response).toMatchObject({})
})

test('given path with simple params, returns each param name as type String', () => {
const path = '/parent/:parentId/child/:childId'

const response = getRouteParams(path)

expect(response).toMatchObject({
parentId: String,
childId: String,
})
})

test('given path with optional params, returns each param name as type String with optional', () => {
const path = '/parent/:?parentId/child/:?childId'

const response = getRouteParams(path)

expect(JSON.stringify(response)).toMatch(JSON.stringify({
parentId: optional(String),
childId: optional(String),
}))
})

test('given path not as string, returns each param with corresponding param', () => {
const pathValue = path('/parent/:parentId/child/:childId', {
parentId: Boolean,
})

const response = getRouteParams(pathValue)

expect(response).toMatchObject({
parentId: Boolean,
childId: String,
})
})
})

describe('generateRouteRegexPattern', () => {
Expand Down
16 changes: 12 additions & 4 deletions src/utilities/flattenRoutes.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { RouteFlat, Routes, isParentRoute, isNamedRoute } from '@/types'
import { RouteFlat, Routes, isParentRoute, isNamedRoute, Param } from '@/types'
import { path, Path } from '@/utilities'

export function flattenRoutes(routes: Routes, path = ''): RouteFlat[] {
return routes.reduce<RouteFlat[]>((value, route) => {
Expand All @@ -13,19 +14,26 @@ export function flattenRoutes(routes: Routes, path = ''): RouteFlat[] {
if (isNamedRoute(route)) {
value.push({
name: route.name,
regex: generateRouteRegexPattern(fullPath),
path: fullPath,
regex: generateRouteRegexPattern(fullPath),
params: getRouteParams(route.path),
})
}

return value
}, [])
}

export function generateRouteRegexPattern(path: string): RegExp {
export function getRouteParams(value: string | Path): Record<string, Param[]> {
const { params } = typeof value === 'string' ? path(value, {}) : value

return params
}

export function generateRouteRegexPattern(value: string): RegExp {
const optionalParamRegex = /(:\?[\w]+)(?=\W|$)/g
const requiredParamRegex = /(:[\w]+)(?=\W|$)/g

const routeRegex = path.replace(optionalParamRegex, '([^/]*)').replace(requiredParamRegex, '([^/]+)')
const routeRegex = value.replace(optionalParamRegex, '([^/]*)').replace(requiredParamRegex, '([^/]+)')
return new RegExp(`^${routeRegex}$`)
}
2 changes: 2 additions & 0 deletions src/utilities/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
export * from './createRouter'
export * from './flattenRoutes'
export * from './paramValidation'
export * from './params'
export * from './path'
export * from './random'
35 changes: 35 additions & 0 deletions src/utilities/paramValidation.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { describe, expect, test } from 'vitest'
import { RouteFlat } from '@/types'
import { generateRouteRegexPattern, routeParamsAreValid } from '@/utilities'

describe('routeParamsAreValid', () => {
test('given route without params, always return true', () => {
const path = '/no-params'
const route: RouteFlat = {
name: 'no-params',
path: '/no-params',
regex: generateRouteRegexPattern('/no-params'),
params: {},
}

const response = routeParamsAreValid(path, route)

expect(response).toBe(true)
})

test('given route with params, always return true', () => {
const path = '/ABC'
const route: RouteFlat = {
name: 'simple-params',
path: '/:simple',
regex: generateRouteRegexPattern('/:simple'),
params: {
simple: [String],
},
}

const response = routeParamsAreValid(path, route)

expect(response).toBe(true)
})
})
10 changes: 10 additions & 0 deletions src/utilities/paramValidation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { ExtractParamsFromPath, Route, RouteFlat, Routes } from '@/types'

Check failure on line 1 in src/utilities/paramValidation.ts

View workflow job for this annotation

GitHub Actions / Type Validation

'ExtractParamsFromPath' is declared but its value is never read.

Check failure on line 1 in src/utilities/paramValidation.ts

View workflow job for this annotation

GitHub Actions / Type Validation

'Route' is declared but its value is never read.

Check failure on line 1 in src/utilities/paramValidation.ts

View workflow job for this annotation

GitHub Actions / Type Validation

'Routes' is declared but its value is never read.

Check failure on line 1 in src/utilities/paramValidation.ts

View workflow job for this annotation

GitHub Actions / Type Tests

'Route' is declared but its value is never read.

Check failure on line 1 in src/utilities/paramValidation.ts

View workflow job for this annotation

GitHub Actions / Type Tests

'Routes' is declared but its value is never read.

export function routeParamsAreValid(path: string, route: RouteFlat): boolean {

Check failure on line 3 in src/utilities/paramValidation.ts

View workflow job for this annotation

GitHub Actions / Type Validation

'path' is declared but its value is never read.

Check failure on line 3 in src/utilities/paramValidation.ts

View workflow job for this annotation

GitHub Actions / Type Validation

'route' is declared but its value is never read.

Check failure on line 3 in src/utilities/paramValidation.ts

View workflow job for this annotation

GitHub Actions / Type Tests

'path' is declared but its value is never read.

Check failure on line 3 in src/utilities/paramValidation.ts

View workflow job for this annotation

GitHub Actions / Type Tests

'route' is declared but its value is never read.
// each param
// get string value from string path (param)
// run that through utilities.getParamValue (if non-simple param)
// if it throws exception, consider not swallowing

throw 'not implemented'
}

0 comments on commit 828bd7e

Please sign in to comment.