// Copyright (C) 2023 The Android Open Source Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import {assertExists} from './logging'; import {exists} from './utils'; export type PathKey = string | number; export type Path = PathKey[]; /** * Gets the |value| at a |path| of |object|. If a portion of the path doesn't * exist, |undefined| is returned. * * Example: * const obj = { * a: [ * {b: 'c'}, * {d: 'e', f: 123}, * ], * }; * getPath(obj, ['a']) -> [{b: 'c'}, {d: 'e', f: 123}] * getPath(obj, ['a', 1]) -> {d: 'e', f: 123} * getPath(obj, ['a', 1, 'd']) -> 'e' * getPath(obj, ['g']) -> undefined * getPath(obj, ['g', 'h']) -> undefined * * Note: This is an appropriate use of `any`, as we are knowingly getting fast * and loose with the type system in this function: it's basically JavaScript. * Attempting to pretend it's anything else would result in superfluous type * assertions which would serve no benefit. */ // eslint-disable-next-line @typescript-eslint/no-explicit-any export function getPath(obj: any, path: Path): T | undefined { let x = obj; for (const node of path) { if (x === undefined) return undefined; x = x[node]; } return x; } /** * Sets the |value| at |path| of |object|. If the final node of the path doesn't * exist, the value will be created. Otherwise, TypeError is thrown. * * Example: * const obj = { * a: [ * {b: 'c'}, * {d: 'e', f: 123}, * ], * }; * setPath(obj, ['a'], 'foo') -> {a: 'foo'} * setPath(obj, ['a', 1], 'foo') -> {a: [{b: 'c'}, 'foo']} * setPath(obj, ['g'], 'foo') -> {a: [...], g: 'foo'} * setPath(obj, ['g', 'h'], 'foo') -> TypeError! */ // eslint-disable-next-line @typescript-eslint/no-explicit-any export function setPath(obj: any, path: Path, value: T): void { const pathClone = [...path]; let o = obj; while (pathClone.length > 1) { const p = assertExists(pathClone.shift()); o = o[p]; } const p = pathClone.shift(); if (!exists(p)) { throw TypeError('Path array is empty'); } o[p] = value; } export function shallowEquals(a: unknown, b: unknown) { if (a === b) { return true; } if (a === undefined || b === undefined) { return false; } if (a === null || b === null) { return false; } const objA = a as {[_: string]: {}}; const objB = b as {[_: string]: {}}; for (const key of Object.keys(objA)) { if (objA[key] !== objB[key]) { return false; } } for (const key of Object.keys(objB)) { if (objA[key] !== objB[key]) { return false; } } return true; } export function isString(s: unknown): s is string { return typeof s === 'string' || s instanceof String; } // Given a string enum |enum|, check that |value| is a valid member of |enum|. export function isEnumValue( enm: T, value: unknown, ): value is T[keyof T] { return Object.values(enm).includes(value); }