tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

params_utils.ts (5548B)


      1 import { TestParams } from '../framework/fixture.js';
      2 import { ResolveType, UnionToIntersection } from '../util/types.js';
      3 import { assert } from '../util/util.js';
      4 
      5 import { comparePublicParamsPaths, Ordering } from './query/compare.js';
      6 import { kWildcard, kParamSeparator, kParamKVSeparator } from './query/separators.js';
      7 
      8 export type JSONWithUndefined =
      9  | undefined
     10  | null
     11  | number
     12  | string
     13  | boolean
     14  | readonly JSONWithUndefined[]
     15  // Ideally this would recurse into JSONWithUndefined, but it breaks code.
     16  | { readonly [k: string]: unknown };
     17 export interface TestParamsRW {
     18  [k: string]: JSONWithUndefined;
     19 }
     20 export type TestParamsIterable = Iterable<TestParams>;
     21 
     22 export function paramKeyIsPublic(key: string): boolean {
     23  return !key.startsWith('_');
     24 }
     25 
     26 export function extractPublicParams(params: TestParams): TestParams {
     27  const publicParams: TestParamsRW = {};
     28  for (const k of Object.keys(params)) {
     29    if (paramKeyIsPublic(k)) {
     30      publicParams[k] = params[k];
     31    }
     32  }
     33  return publicParams;
     34 }
     35 
     36 /** Used to escape reserved characters in URIs */
     37 const kPercent = '%';
     38 
     39 export const badParamValueChars = new RegExp(
     40  '[' + kParamKVSeparator + kParamSeparator + kWildcard + kPercent + ']'
     41 );
     42 
     43 export function publicParamsEquals(x: TestParams, y: TestParams): boolean {
     44  return comparePublicParamsPaths(x, y) === Ordering.Equal;
     45 }
     46 
     47 export type KeyOfNeverable<T> = T extends never ? never : keyof T;
     48 export type AllKeysFromUnion<T> = keyof T | KeyOfNeverable<UnionToIntersection<T>>;
     49 export type KeyOfOr<T, K, Default> = K extends keyof T ? T[K] : Default;
     50 
     51 /**
     52 * Flatten a union of interfaces into a single interface encoding the same type.
     53 *
     54 * Flattens a union in such a way that:
     55 * `{ a: number, b?: undefined } | { b: string, a?: undefined }`
     56 * (which is the value type of `[{ a: 1 }, { b: 1 }]`)
     57 * becomes `{ a: number | undefined, b: string | undefined }`.
     58 *
     59 * And also works for `{ a: number } | { b: string }` which maps to the same.
     60 */
     61 export type FlattenUnionOfInterfaces<T> = {
     62  [K in AllKeysFromUnion<T>]: KeyOfOr<
     63    T,
     64    // If T always has K, just take T[K] (union of C[K] for each component C of T):
     65    K,
     66    // Otherwise, take the union of C[K] for each component C of T, PLUS undefined:
     67    undefined | KeyOfOr<UnionToIntersection<T>, K, void>
     68  >;
     69 };
     70 
     71 /* eslint-disable-next-line @typescript-eslint/no-unused-vars */
     72 function typeAssert<_ extends 'pass'>() {}
     73 {
     74  type Test<T, U> = [T] extends [U]
     75    ? [U] extends [T]
     76      ? 'pass'
     77      : { actual: ResolveType<T>; expected: U }
     78    : { actual: ResolveType<T>; expected: U };
     79 
     80  type T01 = { a: number } | { b: string };
     81  type T02 = { a: number } | { b?: string };
     82  type T03 = { a: number } | { a?: number };
     83  type T04 = { a: number } | { a: string };
     84  type T05 = { a: number } | { a?: string };
     85 
     86  type T11 = { a: number; b?: undefined } | { a?: undefined; b: string };
     87 
     88  type T21 = { a: number; b?: undefined } | { b: string };
     89  type T22 = { a: number; b?: undefined } | { b?: string };
     90  type T23 = { a: number; b?: undefined } | { a?: number };
     91  type T24 = { a: number; b?: undefined } | { a: string };
     92  type T25 = { a: number; b?: undefined } | { a?: string };
     93  type T26 = { a: number; b?: undefined } | { a: undefined };
     94  type T27 = { a: number; b?: undefined } | { a: undefined; b: undefined };
     95 
     96  /* prettier-ignore */ {
     97    typeAssert<Test<FlattenUnionOfInterfaces<T01>, { a: number | undefined; b: string | undefined }>>();
     98    typeAssert<Test<FlattenUnionOfInterfaces<T02>, { a: number | undefined; b: string | undefined }>>();
     99    typeAssert<Test<FlattenUnionOfInterfaces<T03>, { a: number | undefined }>>();
    100    typeAssert<Test<FlattenUnionOfInterfaces<T04>, { a: number | string }>>();
    101    typeAssert<Test<FlattenUnionOfInterfaces<T05>, { a: number | string | undefined }>>();
    102 
    103    typeAssert<Test<FlattenUnionOfInterfaces<T11>, { a: number | undefined; b: string | undefined }>>();
    104 
    105    typeAssert<Test<FlattenUnionOfInterfaces<T22>, { a: number | undefined; b: string | undefined }>>();
    106    typeAssert<Test<FlattenUnionOfInterfaces<T23>, { a: number | undefined; b: undefined }>>();
    107    typeAssert<Test<FlattenUnionOfInterfaces<T24>, { a: number | string; b: undefined }>>();
    108    typeAssert<Test<FlattenUnionOfInterfaces<T25>, { a: number | string | undefined; b: undefined }>>();
    109    typeAssert<Test<FlattenUnionOfInterfaces<T27>, { a: number | undefined; b: undefined }>>();
    110 
    111    // Unexpected test results - hopefully okay to ignore these
    112    typeAssert<Test<FlattenUnionOfInterfaces<T21>, { b: string | undefined }>>();
    113    typeAssert<Test<FlattenUnionOfInterfaces<T26>, { a: number | undefined }>>();
    114  }
    115 }
    116 
    117 export type Merged<A, B> = MergedFromFlat<A, FlattenUnionOfInterfaces<B>>;
    118 export type MergedFromFlat<A, B> = {
    119  [K in keyof A | keyof B]: K extends keyof B ? B[K] : K extends keyof A ? A[K] : never;
    120 };
    121 
    122 /** Merges two objects into one `{ ...a, ...b }` and return it with a flattened type. */
    123 export function mergeParams<A extends {}, B extends {}>(a: A, b: B): Merged<A, B> {
    124  return { ...a, ...b } as Merged<A, B>;
    125 }
    126 
    127 /**
    128 * Merges two objects into one `{ ...a, ...b }` and asserts they had no overlapping keys.
    129 * This is slower than {@link mergeParams}.
    130 */
    131 export function mergeParamsChecked<A extends {}, B extends {}>(a: A, b: B): Merged<A, B> {
    132  const merged = mergeParams(a, b);
    133  assert(
    134    Object.keys(merged).length === Object.keys(a).length + Object.keys(b).length,
    135    () => `Duplicate key between ${JSON.stringify(a)} and ${JSON.stringify(b)}`
    136  );
    137  return merged;
    138 }