import type { DataEngineConfig } from '../../types/DataEngineConfig' import type { FetchType } from '../../types/ExecuteOptions' import type { ResolvedResourceQuery } from '../../types/Query' import type { QueryParameters, QueryParameterValue, } from '../../types/QueryParameters' import { RestAPILink } from '../RestAPILink' import { joinPath } from './path' import { validateResourceQuery } from './validateQuery' const encodeQueryParameter = (param: QueryParameterValue): string => { if (Array.isArray(param)) { return param.map(encodeQueryParameter).join(',') } if (typeof param === 'string') { return encodeURIComponent(param) } if (typeof param === 'number' || typeof param === 'boolean') { return String(param) } if (typeof param === 'object') { throw new Error('Object parameter mappings not yet implemented') } throw new Error('Unknown parameter type') } type ExpandedParam = { key: string value: QueryParameterValue } const queryParametersMapToArray = ( params: QueryParameters ): Array => Object.keys(params).reduce((out, key) => { const value = params[key] if (key === 'filter' && Array.isArray(value)) { value.forEach((item) => { out.push({ key: 'filter', value: item, }) }) } else if (params[key] !== null && params[key] !== undefined) { out.push({ key, value: params[key], }) } return out }, [] as Array) const queryParametersToQueryString = (params: QueryParameters): string => { const expandedParams = queryParametersMapToArray(params) return expandedParams .map( ({ key, value }) => `${encodeURIComponent(key)}=${encodeQueryParameter(value)}` ) .join('&') } const actionPrefix = 'action::' const isAction = (resource: string) => resource.startsWith(actionPrefix) const makeActionPath = (resource: string) => joinPath( 'dhis-web-commons', `${resource.substring(actionPrefix.length)}.action` ) const skipApiVersion = ( resource: string, config: DataEngineConfig ): boolean => { if (resource === 'tracker' || resource.startsWith('tracker/')) { if (!config.serverVersion?.minor || config.serverVersion?.minor < 38) { return true } } // The `/api/ping` endpoint is unversioned if (resource === 'ping') { return true } return false } export const queryToResourcePath = ( link: RestAPILink, query: ResolvedResourceQuery, type: FetchType ): string => { const { resource, id, params = {} } = query const apiBase = skipApiVersion(resource, link.config) ? link.unversionedApiPath : link.versionedApiPath const base = isAction(resource) ? makeActionPath(resource) : joinPath(apiBase, resource, id) validateResourceQuery(query, type) if (Object.keys(params).length) { return `${base}?${queryParametersToQueryString(params)}` } return base }