Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

simplified approach to ensuring Url type is returned from urlAssembly #308

Merged
merged 8 commits into from
Nov 16, 2024
2 changes: 1 addition & 1 deletion docs/api/types/RouterResolve.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,4 @@ If source is `Url`, expected type for params is `never`. Else when source is `TS

## Returns

`string`
`Url`
2 changes: 1 addition & 1 deletion src/components/routerLink.vue
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
import { RouterPushOptions } from '@/types/routerPush'
import { Url, isUrl } from '@/types/url'

type ToCallback = (resolve: RegisteredRouter['resolve']) => string
type ToCallback = (resolve: RegisteredRouter['resolve']) => Url

type RouterLinkProps = {
/**
Expand Down
4 changes: 2 additions & 2 deletions src/services/combinePath.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ test('given 2 paths, returns new Path joined together', () => {

const response = combinePath(aPath, bPath)

expect(response.toString()).toBe('/foo/bar')
expect(response.value).toBe('/foo/bar')
})

test('given 2 paths with params, returns new Path joined together with params', () => {
Expand All @@ -18,7 +18,7 @@ test('given 2 paths with params, returns new Path joined together with params',

const response = combinePath(aPath, bPath)

expect(response.toString()).toBe('/[foz]/[?baz]')
expect(response.value).toBe('/[foz]/[?baz]')
expect(Object.keys(response.params)).toMatchObject(['foz', '?baz'])
})

Expand Down
1 change: 0 additions & 1 deletion src/services/combinePath.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,5 @@ export function combinePath(parentPath: Path, childPath: Path): Path {
return {
value: newPathString,
params: { ...parentPath.params, ...childPath.params },
toString: () => newPathString,
}
}
4 changes: 2 additions & 2 deletions src/services/combineQuery.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ test('given 2 queries, returns new Query joined together', () => {

const response = combineQuery(aQuery, bQuery)

expect(response.toString()).toBe('foo=ABC&bar=123')
expect(response.value).toBe('foo=ABC&bar=123')
})

test('given 2 queries with params, returns new Query joined together with params', () => {
Expand All @@ -18,7 +18,7 @@ test('given 2 queries with params, returns new Query joined together with params

const response = combineQuery(aQuery, bQuery)

expect(response.toString()).toBe('foo=[foz]&bar=[?baz]')
expect(response.value).toBe('foo=[foz]&bar=[?baz]')
expect(Object.keys(response.params)).toMatchObject(['foz', '?baz'])
})

Expand Down
1 change: 0 additions & 1 deletion src/services/combineQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,5 @@ export function combineQuery(parentQuery: Query, childQuery: Query): Query {
return {
value: newQueryString,
params: { ...parentQuery.params, ...childQuery.params },
toString: () => newQueryString,
}
}
2 changes: 1 addition & 1 deletion src/services/createIsExternal.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { createMaybeRelativeUrl } from '@/services/createMaybeRelativeUrl'
import { createMaybeRelativeUrl } from '@/services/maybeRelativeUrl'

export function createIsExternal(host: string | undefined): (url: string) => boolean {
return (url: string) => {
Expand Down
2 changes: 1 addition & 1 deletion src/services/createRouter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { routerRejectionKey } from '@/compositions/useRejection'
import { routerInjectionKey } from '@/compositions/useRouter'
import { createCurrentRoute } from '@/services/createCurrentRoute'
import { createIsExternal } from '@/services/createIsExternal'
import { createMaybeRelativeUrl } from '@/services/createMaybeRelativeUrl'
import { createMaybeRelativeUrl } from '@/services/maybeRelativeUrl'
import { createPropStore, propStoreKey } from '@/services/createPropStore'
import { createRouterHistory } from '@/services/createRouterHistory'
import { routeHookStoreKey, createRouterHooks } from '@/services/createRouterHooks'
Expand Down
4 changes: 3 additions & 1 deletion src/services/createRouterResolve.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,5 +88,7 @@ test('when given an external route with params in host, interpolates param value
test('given a route with hash, interpolates hash value', () => {
const resolve = createRouterResolve(routes)

expect(resolve('parentA', { paramA: 'bar' }, { hash: 'foo' })).toBe('/parentA/bar#foo')
const url = resolve('parentA', { paramA: 'bar' }, { hash: 'foo' })

expect(url).toBe('/parentA/bar#foo')
})
6 changes: 3 additions & 3 deletions src/services/createRouterResolve.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,16 @@ type RouterResolveArgs<
export type RouterResolve<
TRoutes extends Routes
> = {
<TSource extends RoutesName<TRoutes>>(name: TSource, ...args: RouterResolveArgs<TRoutes, TSource>): string,
(url: Url, options?: RouterResolveOptions): string,
<TSource extends RoutesName<TRoutes>>(name: TSource, ...args: RouterResolveArgs<TRoutes, TSource>): Url,
(url: Url, options?: RouterResolveOptions): Url,
}

export function createRouterResolve<const TRoutes extends Routes>(routes: TRoutes): RouterResolve<TRoutes> {
return (
source: Url | RoutesName<TRoutes>,
paramsOrOptions?: Record<string, unknown>,
maybeOptions?: RouterResolveOptions,
): string => {
): Url => {
if (isUrl(source)) {
const options: RouterPushOptions = paramsOrOptions ?? {}

Expand Down
2 changes: 1 addition & 1 deletion src/services/getResolvedRouteForUrl.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { createMaybeRelativeUrl } from '@/services/createMaybeRelativeUrl'
import { createMaybeRelativeUrl } from '@/services/maybeRelativeUrl'
import { createResolvedRouteQuery } from '@/services/createResolvedRouteQuery'
import { getRouteParamValues, routeParamsAreValid } from '@/services/paramValidation'
import { isNamedRoute, routePathMatches, routeQueryMatches, routeHashMatches } from '@/services/routeMatchRules'
Expand Down
16 changes: 1 addition & 15 deletions src/services/hash.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,9 @@ import { stringHasValue } from '@/utilities/guards'

export function hash<THash extends string>(hash?: THash): Hash<THash>
export function hash(hash?: string): Hash {
const value = !stringHasValue(hash) ? undefined : hash.replace(/^#/, '')

function hasValue(): boolean {
return value !== undefined
}

function toString(): string {
if (hasValue()) {
return `#${hash}`
}

return ''
}
const value = !stringHasValue(hash) ? undefined : hash.replace(/^#*/, '')

return {
value,
hasValue,
toString,
}
}
1 change: 0 additions & 1 deletion src/services/host.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,5 @@ export function host(value: string, params: Record<string, Param | undefined>):
return {
value,
params: getParamsForString(value, params),
toString: () => value,
}
}
2 changes: 1 addition & 1 deletion src/services/insertBaseRoute.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,5 +36,5 @@ test('given value for base, returns routes with base prefixed', () => {

const response = insertBaseRoute(routes, base)

expect(response.every((route) => route.path.toString().startsWith('/kitbag'))).toBe(true)
expect(response.every((route) => route.path.value.startsWith('/kitbag'))).toBe(true)
})
98 changes: 98 additions & 0 deletions src/services/maybeRelativeUrl.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import { describe, expect, test } from 'vitest'
import { createMaybeRelativeUrl, maybeRelativeUrlToString } from '@/services/maybeRelativeUrl'

describe('createMaybeRelativeUrl', () => {
test('given relative url, returns host and protocol undefined', () => {
const url = '/foo?bar=123'

const parts = createMaybeRelativeUrl(url)

expect(parts.host).toBe(undefined)
expect(parts.protocol).toBe(undefined)
})

test('given absolute url with path, returns everything up to pathname', () => {
const url = 'https://kitbag.dev/foo'

const parts = createMaybeRelativeUrl(url)

expect(parts.host).toBe('kitbag.dev')
expect(parts.protocol).toBe('https:')
expect(parts.pathname).toBe('/foo')
expect(parts.search).toBe('')
expect(parts.hash).toBe('')
})

test('given absolute url with path and query, returns everything up to search', () => {
const url = 'https://kitbag.dev/foo?bar=123'

const parts = createMaybeRelativeUrl(url)

expect(parts.host).toBe('kitbag.dev')
expect(parts.protocol).toBe('https:')
expect(parts.pathname).toBe('/foo')
expect(parts.search).toBe('?bar=123')
expect(parts.hash).toBe('')
})

test('given absolute url with path, query, and hash, returns everything', () => {
const url = 'https://kitbag.dev/foo?bar=123#zoo'

const parts = createMaybeRelativeUrl(url)

expect(parts.host).toBe('kitbag.dev')
expect(parts.protocol).toBe('https:')
expect(parts.pathname).toBe('/foo')
expect(parts.search).toBe('?bar=123')
expect(parts.hash).toBe('#zoo')
})
})

describe('maybeRelativeUrlToString', () => {
test('given parts without host, protocol, or path, returns forward slash to satisfy Url', () => {
const url = maybeRelativeUrlToString({})

expect(url).toBe('/')
})

test('given parts without host, protocol, or valid path, returns forward slash to satisfy Url', () => {
const url = maybeRelativeUrlToString({
pathname: 'foo',
})

expect(url).toBe('/foo')
})

test('given parts without host and protocol, returns url starting with forward slash', () => {
const parts = {
pathname: '/foo',
search: '?bar=123',
}

const url = maybeRelativeUrlToString(parts)

expect(url).toBe('/foo?bar=123')
})

test('given parts with searchParams, does nothing', () => {
const parts = {
pathname: '/foo',
searchParams: new URLSearchParams([['bar', '123']]),
}

const url = maybeRelativeUrlToString(parts)

expect(url).toBe('/foo')
})

test('given parts with host and protocol, returns value that satisfies Url', () => {
const parts = {
host: 'kitbag.dev',
protocol: 'https:',
}

const url = maybeRelativeUrlToString(parts)

expect(url).toBe('https://kitbag.dev')
})
})
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import { asUrl, Url } from '@/types/url'
import { stringHasValue } from '@/utilities'

export type MaybeRelativeUrl = {
protocol?: string,
host?: string,
Expand All @@ -7,6 +10,23 @@ export type MaybeRelativeUrl = {
hash: string,
}

export function maybeRelativeUrlToString({ protocol, host, pathname, search, hash }: Partial<Omit<MaybeRelativeUrl, 'searchParams'>>): Url {
const protocolWithoutSlashesPattern = /^https?:$/
const cleanProtocol = protocol && protocolWithoutSlashesPattern.test(protocol) ? `${protocol}//` : protocol

const joined = [
cleanProtocol,
host,
pathname,
search,
hash,
]
.filter((part) => stringHasValue(part))
.join('')

return asUrl(joined)
}

export function createMaybeRelativeUrl(value: string): MaybeRelativeUrl {
const isRelative = !value.startsWith('http')

Expand Down
4 changes: 2 additions & 2 deletions src/services/paramValidation.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { createMaybeRelativeUrl } from '@/services/createMaybeRelativeUrl'
import { createMaybeRelativeUrl } from '@/services/maybeRelativeUrl'
import { getParamValue } from '@/services/params'
import { getParamValueFromUrl } from '@/services/paramsFinder'
import { Path } from '@/types/path'
Expand Down Expand Up @@ -32,7 +32,7 @@ function getPathParams(path: Path, url: string): Record<string, unknown> {
for (const [key, param] of Object.entries(path.params)) {
const isOptional = key.startsWith('?')
const paramName = isOptional ? key.slice(1) : key
const stringValue = getParamValueFromUrl(decodedValueFromUrl, path.toString(), key)
const stringValue = getParamValueFromUrl(decodedValueFromUrl, path.value, key)
const paramValue = getParamValue(stringValue, param, isOptional)

values[paramName] = paramValue
Expand Down
1 change: 0 additions & 1 deletion src/services/path.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,5 @@ export function path(value: string, params: Record<string, Param | undefined>):
return {
value,
params: getParamsForString(value, params),
toString: () => value,
}
}
1 change: 0 additions & 1 deletion src/services/query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,5 @@ export function query(value: string, params: Record<string, Param | undefined>):
return {
value,
params: getParamsForString(value, params),
toString: () => value,
}
}
6 changes: 3 additions & 3 deletions src/services/routeMatchRules.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { createMaybeRelativeUrl } from '@/services/createMaybeRelativeUrl'
import { createMaybeRelativeUrl } from '@/services/maybeRelativeUrl'
import { generateRoutePathRegexPattern, generateRouteQueryRegexPatterns } from '@/services/routeRegex'
import { RouteMatchRule } from '@/types/routeMatchRule'

Expand All @@ -22,7 +22,7 @@ export const routeQueryMatches: RouteMatchRule = (route, url) => {

export const routeHashMatches: RouteMatchRule = (route, url) => {
const { hash } = createMaybeRelativeUrl(url)
const value = route.hash.toString()
const { value } = route.hash

return !route.hash.hasValue() || value.toLowerCase() === hash.toLowerCase()
return value === undefined || `#${value.toLowerCase()}` === hash.toLowerCase()
}
12 changes: 6 additions & 6 deletions src/services/routeMatchScore.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { createMaybeRelativeUrl } from '@/services/createMaybeRelativeUrl'
import { createMaybeRelativeUrl } from '@/services/maybeRelativeUrl'
import { getParamValueFromUrl } from '@/services/paramsFinder'
import { Route } from '@/types/route'
import { routeHashMatches } from './routeMatchRules'

type RouteSortMethod = (aRoute: Route, bRoute: Route) => number

Expand Down Expand Up @@ -29,11 +30,10 @@ export function getRouteScoreSortMethod(url: string): RouteSortMethod {
return sortAfter
}

const { hash } = createMaybeRelativeUrl(url)
if (aRoute.hash.toString() === hash) {
if (routeHashMatches(aRoute, url)) {
return sortBefore
}
if (bRoute.hash.toString() === hash) {
if (routeHashMatches(bRoute, url)) {
return sortAfter
}

Expand All @@ -46,13 +46,13 @@ export function countExpectedPathParams(route: Route, actualPath: string): numbe
.filter((key) => key.startsWith('?'))
.map((key) => key)

const missing = optionalParams.filter((expected) => getParamValueFromUrl(actualPath, route.path.toString(), expected) === undefined)
const missing = optionalParams.filter((expected) => getParamValueFromUrl(actualPath, route.path.value, expected) === undefined)

return optionalParams.length - missing.length
}

export function countExpectedQueryParams(route: Route, actualQuery: URLSearchParams): number {
const expectedQuery = new URLSearchParams(route.query.toString())
const expectedQuery = new URLSearchParams(route.query.value)
const expectedQueryKeys = Array.from(expectedQuery.keys())

const missing = expectedQueryKeys.filter((expected) => !actualQuery.has(expected))
Expand Down
2 changes: 1 addition & 1 deletion src/services/routeRegex.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ describe('generateRoutePathRegexPattern', () => {

const result = generateRoutePathRegexPattern(route)

const expected = new RegExp(`^${route.path}$`, 'i')
const expected = new RegExp(`^${route.path.value}$`, 'i')
expect(result.toString()).toBe(expected.toString())
})

Expand Down
4 changes: 2 additions & 2 deletions src/services/routeRegex.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,13 @@ export function splitByMatches(string: string, regexp: RegExp): string[] {
}

export function generateRoutePathRegexPattern(route: Route): RegExp {
const pathRegex = replaceParamSyntaxWithCatchAllsAndEscapeRest(route.path.toString())
const pathRegex = replaceParamSyntaxWithCatchAllsAndEscapeRest(route.path.value)

return new RegExp(`^${pathRegex}$`, 'i')
}

export function generateRouteQueryRegexPatterns(route: Route): RegExp[] {
const queryParams = new URLSearchParams(route.query.toString())
const queryParams = new URLSearchParams(route.query.value)

return Array
.from(queryParams.entries())
Expand Down
Loading
Loading