diff --git a/src/components/routing/loader.test.ts b/src/components/routing/loader.test.ts index 7f377c37bdb195b003483c4171d22ad557bb8a49..f3456506befd429873f2e7472806a1995c4fcfc1 100644 --- a/src/components/routing/loader.test.ts +++ b/src/components/routing/loader.test.ts @@ -122,4 +122,18 @@ describe('createRequestForRoute', () => { ) expect(result).toEqual({key: {requestKey: 'value'}}) }) + it('works with requestPath', () => { + const result = createRequestForRoute( + [{route: {requestPath: 'some/path'}} as unknown as RouteMatch], + () => ({requestKey: 'value'}), + ) + expect(result).toEqual({some: {path: {requestKey: 'value'}}}) + }) + it('works with requestPath function', () => { + const result = createRequestForRoute( + [{route: {requestKey: () => 'some/path'}} as unknown as RouteMatch], + () => ({requestKey: 'value'}), + ) + expect(result).toEqual({some: {path: {requestKey: 'value'}}}) + }) }) diff --git a/src/components/routing/loader.ts b/src/components/routing/loader.ts index b04bd89010aecf92525fa0234682f5776d5e42af..c5c04bddd73477ca9ecc96edb5b6fb7ebd61e2b2 100644 --- a/src/components/routing/loader.ts +++ b/src/components/routing/loader.ts @@ -1,9 +1,9 @@ -import lodash from 'lodash' import {User} from 'oidc-client-ts' import {graphApi} from '../../utils/api' import {resolveAllMDefs} from '../../utils/metainfo' import {JSONObject} from '../../utils/types' +import {assert} from '../../utils/utils' import {DefaultSearch, LoaderResult, RouteMatch} from './types' export class DoesNotExistError extends Error { @@ -58,8 +58,33 @@ export function getResponseForRoute( return getResponseForPath(path, response) } -export function getRequestKey(match: RouteMatch) { - if (!match.route.requestKey) { +function getRequestPath( + match: RouteMatch, + parentPath?: string, +): string | undefined { + if (match.route.requestPath) { + if (typeof match.route.requestPath === 'string') { + return match.route.requestPath + } else { + return match.route.requestPath({ + ...match, + search: match.search as DefaultSearch, + }) + } + } + const key = getRequestKey(match) + if (key === '') { + return parentPath + } + if (parentPath === undefined) { + return key + } else { + return `${parentPath}/${key}` + } +} + +function getRequestKey(match: RouteMatch) { + if (match.route.requestKey === undefined) { return match.path } if (typeof match.route.requestKey === 'string') { @@ -75,24 +100,32 @@ export function createRequestForRoute( match: RouteMatch[], getRequest: (match: RouteMatch, index: number) => JSONObject | undefined, ): JSONObject { - const request = {} as JSONObject - const lastIndexWithRequest = match.findLastIndex(getRequest) - match.slice(0, lastIndexWithRequest + 1).reduce((request, match, index) => { - const next = getRequest(match, index) || {} - const key = getRequestKey(match) - if (key === '') { - lodash.merge(request, next) - return request - } - if (request[key] !== undefined) { - lodash.merge(request[key], next) - } else { - request[key] = next + const requests: {[path: string]: JSONObject} = {} + let parentPath: string | undefined + match.forEach((match, index) => { + const request = getRequest(match, index) + const path = getRequestPath(match, parentPath) + if (request) { + assert(path !== undefined, 'Request for empty paths are not supported.') + requests[path] = request } - return request[key] as JSONObject - }, request) + parentPath = path + }) + + const rootRequest = {} as JSONObject + Object.entries(requests).forEach(([path, request]) => { + const segments = path.split('/') + let currentRequest: JSONObject = rootRequest + segments.forEach((key) => { + if (currentRequest[key] === undefined) { + currentRequest[key] = {} + } + currentRequest = currentRequest[key] as JSONObject + }) + Object.assign(currentRequest, request) + }) - return request + return rootRequest } export default function loader( diff --git a/src/components/routing/types.ts b/src/components/routing/types.ts index 6047a84f926a9368b740bcddaf7aabd88d186d8a..570860cb0259755649b4775a994882871d922dd8 100644 --- a/src/components/routing/types.ts +++ b/src/components/routing/types.ts @@ -262,6 +262,18 @@ export type Route< | string | ((locationData: RouteMatch<Request, Response, Search>) => string) + /** + * An optional path or function that produces a path. The path will + * replace the API path usually derived from the `path` property of the route + * and its parent routes. Similar to request key, but for the whole path. + * + * This applies to the whole route including all child routes. + * TODO Are there cases where this is bad? Should this be configurable? + */ + requestPath?: + | string + | ((locationData: RouteMatch<Request, Response, Search>) => string) + /** * An optional request or function that produces a request. The request * has to satisfy the type `Request`. The request is used to create