Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@tompng
Created July 18, 2020 23:16
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tompng/cd386bb71068cc448b4b45d088f71bdc to your computer and use it in GitHub Desktop.
Save tompng/cd386bb71068cc448b4b45d088f71bdc to your computer and use it in GitHub Desktop.
export const boardsPath = '/boards(.:format)'
export const boardPath = '/boards/:id(.:format)'
type Routes = {
[boardsPath]: {
GET: { params: { format: 'json' | 'html'; color?: string }; response: { a: string } }
POST: { params: { color: string; size: number }; response: { b: string } }
DELETE: { params: {}; response: { c: string } }
}
[boardPath]: {
GET: { params: { format?: 'json' | 'html', id: number }; response: { d: string } }
PATCH: { params: { format?: 'json' | 'html'; id: number; color?: string; size?: number }; response: { e: string } }
}
}
type Method = 'GET' | 'POST' | 'PATCH' | 'DELETE'
type ParamsOf<M extends keyof Routes[Path], Path extends keyof Routes> = Routes[Path][M] extends { params: infer Params } ? Params : never
type ResponseOf<M extends keyof Routes[Path], Path extends keyof Routes> = Routes[Path][M] extends { response: infer Response } ? Response : never
export function wrapApi(apifunc: (method: Method, path: string, params: {}) => Promise<any>) {
return async function<M extends Method & keyof Routes[Path], Path extends keyof Routes>(method: M, path: Path, params: ParamsOf<M, Path> | undefined) {
const [interpolatedPath, restParams] = interpolate(path, params || {})
return await apifunc(method, interpolatedPath, restParams) as ResponseOf<M, Path>
}
}
function interpolate(path: string, params: {}){
let segments: string[] = []
let names: string[] = []
const namesStack: string[][] = []
const segmentsStack: string[][] = []
path.match(/\(|\)|:[a-zA-Z_]+|[^:()]+/g).forEach(f => {
if (f === '(') {
segmentsStack.push(segments)
namesStack.push(names)
segments = []
names = []
} else if (f === ')') {
const last = segments
const lastNames = names
segments = segmentsStack.pop()
names = namesStack.pop()
if (last && segments) {
segments.push(...last)
names.push(...lastNames)
}
} else if (segments) {
if (f[0] === ':') {
const key = f.substr(1)
if (!params[key]) {
if (segmentsStack.length === 0) throw `missing '${key}'`
segments = null
names = null
} else {
segments.push(params[key].toString())
names.push(key)
}
} else {
segments.push(f)
}
}
})
const restParams = { ...params }
names.forEach(key => delete restParams[key])
return [segments.join(''), restParams] as const
}
interpolate('/boards/:id(.:format)/x/(a:xxx/(b:yyy)):id/:id', {id: 3,format:'json',xxx:5,yyy:6})
interpolate('/boards/:id(.:format)', {id: 3})
const railsApi = wrapApi(async (method, path, params) => {
const res = await fetch(path, { method, /* body, headers */ })
return await res.json()
})
const data1 = railsApi('GET', boardsPath, { format: 'json', color: '3' })
const data2 = railsApi('POST', boardsPath, { color: '3', size: 1 })
const data3 = railsApi('GET', boardPath, { id: 1 })
const data4 = railsApi('PATCH', boardPath, { id: 1, color: '3' })
const data5 = railsApi('DELETE', boardsPath, {})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment