@lodestar/api
Version:
A Typescript REST client for the Ethereum Consensus API
113 lines (96 loc) • 2.86 kB
text/typescript
export enum TokenType {
String = "String",
Variable = "Variable",
}
export type Token = {type: TokenType.String; str: string} | {type: TokenType.Variable; var: string};
type Args = Record<string, string | number>;
export function urlToTokens(path: string): Token[] {
const tokens: Token[] = [];
let ptr = 0;
let inVariable = false;
for (let i = 0, len = path.length; i < len; i++) {
switch (path[i]) {
case "{": {
if (inVariable) {
throw Error(`Invalid path variable not closed: ${path}`);
}
if (i > ptr) {
tokens.push({type: TokenType.String, str: path.slice(ptr, i)});
}
ptr = i + 1;
inVariable = true;
break;
}
case "}": {
if (!inVariable) {
throw Error(`Invalid path variable not open: ${path}`);
}
if (ptr === i) {
throw Error(`Empty variable: ${path}`);
}
tokens.push({type: TokenType.Variable, var: path.slice(ptr, i)});
inVariable = false;
ptr = i + 1;
break;
}
}
}
if (inVariable) {
throw Error(`Invalid path variable not closed: ${path}`);
}
if (ptr < path.length) {
tokens.push({type: TokenType.String, str: path.slice(ptr)});
}
return tokens;
}
/**
* Compile a route URL formatter with syntax `/path/{var1}/{var2}`.
* Returns a function that expects an object `{var1: 1, var2: 2}`, and returns`/path/1/2`.
*
* It's cheap enough to be negligible. For the sample input below it costs:
* - compile: 1010 ns / op
* - execute: 105 ns / op
* - execute with template literal: 12 ns / op
* @param path `/eth/v1/validator/:name/attester/:epoch`
*/
export function compileRouteUrlFormatter(path: string): (arg: Args) => string {
const tokens = urlToTokens(path);
// Return a faster function if there's not ':' token
if (tokens.length === 1 && tokens[0].type === TokenType.String) {
return () => path;
}
const fns = tokens.map((token) => {
switch (token.type) {
case TokenType.String:
return () => token.str;
case TokenType.Variable: {
const argKey = token.var;
return (args: Args) => args[argKey];
}
}
});
return function urlFormatter(args: Args) {
// Don't use .map() or .join(), it's x3 slower
let s = "";
for (const fn of fns) s += fn(args);
return s;
};
}
/**
* Converts notation used in OpenAPI spec '/states/{state_id}',
* into colon notation used by fastify '/states/:state_id'
*/
export function toColonNotationPath(path: string): string {
const tokens = urlToTokens(path);
return tokens
.map((token) => {
switch (token.type) {
case TokenType.String:
return token.str;
case TokenType.Variable: {
return `:${token.var}`;
}
}
})
.join("");
}