blob: bfdba7977661d5610303ec4659c87485655a1027 [file] [log] [blame]
// 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<T>(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<T>(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<T extends {}>(
enm: T,
value: unknown,
): value is T[keyof T] {
return Object.values(enm).includes(value);
}