chromium/ash/webui/recorder_app_ui/resources/core/utils/assert.ts

// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

/**
 * Verify |condition| is truthy and return |condition| if so.
 *
 * @param condition A condition to check for truthiness.  Note that this
 *     may be used to test whether a value is defined or not, and we don't want
 *     to force a cast to Boolean.
 * @param optMessage A message to show on failure.
 */
export function assert(
  condition: boolean,
  optMessage?: string,
): asserts condition {
  if (!condition) {
    let message = 'Assertion failed';
    if (optMessage !== undefined) {
      message = message + ': ' + optMessage;
    }
    throw new Error(message);
  }
}

/**
 * Call this from places in the code that should never be reached.
 *
 * This code should only be hit in the case of serious programmer error.
 *
 * @param optMessage A message to show when this is hit.
 */
export function assertNotReached(optMessage = 'Unreachable code hit'): never {
  assert(false, optMessage);
}

/**
 * Throw an exception on unexpected values.
 *
 * This can be used along with type narrowing to ensure at compile time that all
 * possible types for a value have been handled.
 *
 * A common use-case is in switch statements:
 *
 * ```
 * // enumValue: Enum.A | Enum.B
 * switch(enumValue) {
 *   case Enum.A:
 *     break;
 *   case Enum.B:
 *     break;
 *   default:
 *     assertExhaustive(enumValue);
 *     break;
 * }
 * ```.
 *
 * @param value The value to be checked.
 * @param optMessage An optional error message to throw.
 */
export function assertExhaustive(
  value: never,
  optMessage = `unexpected value ${value}`,
): never {
  assert(false, optMessage);
}

type EnumObj = Record<string, number|string>;

/**
 * Check if a string or number value is a variant of an enum.
 *
 * @param enumType The enum type to be checked.
 * @param value Value to be checked.
 * @return The value if it's an enum variant, null otherwise.
 */
export function checkEnumVariant<T extends EnumObj>(
  enumType: T,
  value: unknown,
): T[keyof T]|null {
  // `includes` work for any value types.
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
  if (!Object.values(enumType).includes(value as T[keyof T])) {
    return null;
  }

  // We just checked that the value is a valid enum variant, so the cast is
  // safe.
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
  return value as T[keyof T];
}

/**
 * Asserts that a string or number value is a variant of an enum.
 *
 * @param enumType The enum type to be checked.
 * @param value Value to be checked.
 * @return The value if it's an enum variant, throws assertion error otherwise.
 */
export function assertEnumVariant<T extends EnumObj>(
  enumType: T,
  value: unknown,
): T[keyof T] {
  const ret = checkEnumVariant(enumType, value);
  assert(ret !== null, `${value} is not a valid enum variant`);
  return ret;
}

// "unknown" doesn't work well here if the constructor have overloads with
// different numbers of argument and strictNullChecks on.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
type Constructor<T> = new (...args: any[]) => T;

/**
 * Check if a value is an instance of a class.
 *
 * @param value The value to check.
 * @param ctor A user-defined constructor.
 */
export function checkInstanceof<T>(
  value: unknown,
  ctor: Constructor<T>,
): T|null {
  if (!(value instanceof ctor)) {
    return null;
  }
  return value;
}

/**
 * Assert a value is an instance of a class.
 *
 * @param value The value to check.
 * @param ctor A user-defined constructor.
 * @param optMessage A message to show when this is hit.
 */
export function assertInstanceof<T>(
  value: unknown,
  ctor: Constructor<T>,
  optMessage?: string,
): T {
  // We don't use assert immediately here so that we avoid constructing an error
  // message if we don't have to.
  if (!(value instanceof ctor)) {
    assert(
      false,
      optMessage ?? `Value ${value} is not a[n] ${ctor.name ?? typeof ctor}`,
    );
  }
  return value;
}

/**
 * Assert a value is not null or undefined.
 *
 * @param value The value to check.
 * @param optMessage A message to show when this is hit.
 */
export function assertExists<T>(
  value: T|null|undefined,
  optMessage?: string,
): T {
  if (value === null || value === undefined) {
    assert(false, optMessage ?? `Value is ${value}`);
  }
  return value;
}

/**
 * Assert a value is a string.
 *
 * @param value The value to check.
 * @param optMessage A message to show when this is hit.
 */
export function assertString(value: unknown, optMessage?: string): string {
  assert(
    typeof value === 'string',
    optMessage ?? `Value ${value} is not a string`,
  );
  return value;
}