import {map} from "@http4t/result"; import {exactlyChars, exactlySegments, upToChars, upToSegments} from "./consume"; import {ConsumeUntil} from "./ConsumeUntil"; import {join} from "./Joined"; import {literal} from "./Literal"; import {BooleanPath} from "./parsers/BooleanPath"; import {FloatPath} from "./parsers/FloatPath"; import {IntPath} from "./parsers/IntParser"; import {SplitStringPath} from "./parsers/SplitStringPath"; import {PathMatcher, PathResult} from "./PathMatcher"; export const v = { segment: ConsumeUntil.nextSlashOrEnd, upToChars: (count: number) => new ConsumeUntil(upToChars(count)), exactlyChars: (count: number) => new ConsumeUntil(exactlyChars(count)), upToSegments: (count: number) => new SplitStringPath( new ConsumeUntil(upToSegments(count)), '/'), exactlySegments: (count: number) => new SplitStringPath( new ConsumeUntil(exactlySegments(count)), '/'), restOfPath: new SplitStringPath(ConsumeUntil.endOfPath, '/'), restOfPathSegments: new SplitStringPath(ConsumeUntil.endOfPath, '/'), boolean: new BooleanPath(ConsumeUntil.nextSlashOrEnd), float: new FloatPath(ConsumeUntil.nextSlashOrEnd), int: new IntPath(ConsumeUntil.nextSlashOrEnd), binary: new IntPath(ConsumeUntil.nextSlashOrEnd, 2), hex: new IntPath(ConsumeUntil.nextSlashOrEnd, 16), }; export type VariablePaths = { readonly [K in keyof T]: PathMatcher }; export type Variable = { key: K }; export type Variables = { readonly [K in keyof T]: Variable }; export class VariablePath implements PathMatcher<{ K: T[K] }> { constructor( private readonly key: K, private readonly value: PathMatcher) { } consume(path: string): PathResult<{ K: T[K] }> { return map( this.value.consume(path), value => ({...value, value: {[this.key]: value.value} as { K: T[K] }})); } expand(value: { K: T[K] }): string { return this.value.expand((value as any)[this.key]); } } export type SegmentsFn = (vars: Variables) => (Variable | string)[]; export function variablesPath( variablePaths: VariablePaths, segmentFn: SegmentsFn) : PathMatcher { const variables = Object.keys(variablePaths).reduce((acc, key) => { (acc as any)[key] = {key}; return acc }, {} as Variables); const segments = segmentFn(variables); const segmentKeys = segments.reduce( (keys, segment) => typeof segment === 'string' ? keys : keys.add(segment.key), new Set()); const variableKeys = Object.keys(variablePaths) as (keyof T)[]; const missingKeys = variableKeys.filter(k => !segmentKeys.has(k)); if (missingKeys.length > 0) throw new Error(`segmentFn did not populate all keys. Missing: ${missingKeys.join(', ')}`); const segmentPaths: (PathMatcher | VariablePath)[] = segments.map(s => typeof s === 'string' ? literal(s) : new VariablePath(s.key, variablePaths[s.key])); return join(...segmentPaths); }