1// Copyright (C) 2023 The Android Open Source Project 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15import {assertExists} from './logging'; 16import {exists} from './utils'; 17 18export type PathKey = string | number; 19export type Path = PathKey[]; 20 21/** 22 * Gets the |value| at a |path| of |object|. If a portion of the path doesn't 23 * exist, |undefined| is returned. 24 * 25 * Example: 26 * const obj = { 27 * a: [ 28 * {b: 'c'}, 29 * {d: 'e', f: 123}, 30 * ], 31 * }; 32 * getPath(obj, ['a']) -> [{b: 'c'}, {d: 'e', f: 123}] 33 * getPath(obj, ['a', 1]) -> {d: 'e', f: 123} 34 * getPath(obj, ['a', 1, 'd']) -> 'e' 35 * getPath(obj, ['g']) -> undefined 36 * getPath(obj, ['g', 'h']) -> undefined 37 * 38 * Note: This is an appropriate use of `any`, as we are knowingly getting fast 39 * and loose with the type system in this function: it's basically JavaScript. 40 * Attempting to pretend it's anything else would result in superfluous type 41 * assertions which would serve no benefit. 42 */ 43// eslint-disable-next-line @typescript-eslint/no-explicit-any 44export function getPath<T>(obj: any, path: Path): T | undefined { 45 let x = obj; 46 for (const node of path) { 47 if (x === undefined) return undefined; 48 x = x[node]; 49 } 50 return x; 51} 52 53/** 54 * Sets the |value| at |path| of |object|. If the final node of the path doesn't 55 * exist, the value will be created. Otherwise, TypeError is thrown. 56 * 57 * Example: 58 * const obj = { 59 * a: [ 60 * {b: 'c'}, 61 * {d: 'e', f: 123}, 62 * ], 63 * }; 64 * setPath(obj, ['a'], 'foo') -> {a: 'foo'} 65 * setPath(obj, ['a', 1], 'foo') -> {a: [{b: 'c'}, 'foo']} 66 * setPath(obj, ['g'], 'foo') -> {a: [...], g: 'foo'} 67 * setPath(obj, ['g', 'h'], 'foo') -> TypeError! 68 */ 69// eslint-disable-next-line @typescript-eslint/no-explicit-any 70export function setPath<T>(obj: any, path: Path, value: T): void { 71 const pathClone = [...path]; 72 let o = obj; 73 while (pathClone.length > 1) { 74 const p = assertExists(pathClone.shift()); 75 o = o[p]; 76 } 77 78 const p = pathClone.shift(); 79 if (!exists(p)) { 80 throw TypeError('Path array is empty'); 81 } 82 o[p] = value; 83} 84 85export function shallowEquals(a: unknown, b: unknown) { 86 if (a === b) { 87 return true; 88 } 89 if (a === undefined || b === undefined) { 90 return false; 91 } 92 if (a === null || b === null) { 93 return false; 94 } 95 const objA = a as {[_: string]: {}}; 96 const objB = b as {[_: string]: {}}; 97 for (const key of Object.keys(objA)) { 98 if (objA[key] !== objB[key]) { 99 return false; 100 } 101 } 102 for (const key of Object.keys(objB)) { 103 if (objA[key] !== objB[key]) { 104 return false; 105 } 106 } 107 return true; 108} 109 110export function isString(s: unknown): s is string { 111 return typeof s === 'string' || s instanceof String; 112} 113 114// Given a string enum |enum|, check that |value| is a valid member of |enum|. 115export function isEnumValue<T extends {}>( 116 enm: T, 117 value: unknown, 118): value is T[keyof T] { 119 return Object.values(enm).includes(value); 120} 121