gristlabs_grist-core/app/common/StringUnion.ts
Florent fde6c8142d
Support nonce and acr with OIDC + other improvements and tests (#883)
* Introduces new configuration variables for OIDC:
  - GRIST_OIDC_IDP_ENABLED_PROTECTIONS
  - GRIST_OIDC_IDP_ACR_VALUES
  - GRIST_OIDC_IDP_EXTRA_CLIENT_METADATA
* Implements all supported protections in oidc/Protections.ts
* Includes a better error page for failed OIDC logins
* Includes some other improvements, e.g. to logging, to OIDC
* Adds a large unit test for OIDCConfig
* Adds support for SERVER_NODE_OPTIONS for running tests
* Adds to documentation/develop.md info about GREP_TESTS, VERBOSE, and SERVER_NODE_OPTIONS.
2024-08-08 15:35:37 -04:00

56 lines
1.7 KiB
TypeScript

export class StringUnionError extends TypeError {
constructor(errMessage: string, public readonly actual: string, public readonly values: string[]) {
super(errMessage);
}
}
/**
* TypeScript will infer a string union type from the literal values passed to
* this function. Without `extends string`, it would instead generalize them
* to the common string type.
*
* Example definition:
* const Race = StringUnion(
* "orc",
* "human",
* "night elf",
* "undead",
* );
* type Race = typeof Race.type;
*
* For more details, see:
* https://stackoverflow.com/questions/36836011/checking-validity-of-string
* -literal-union-type-at-runtime?answertab=active#tab-top
*/
export const StringUnion = <UnionType extends string>(...values: UnionType[]) => {
Object.freeze(values);
const valueSet: Set<string> = new Set(values);
const guard = (value: string): value is UnionType => {
return valueSet.has(value);
};
const check = (value: string): UnionType => {
if (!guard(value)) {
const actual = JSON.stringify(value);
const expected = values.map(s => JSON.stringify(s)).join(' | ');
throw new StringUnionError(`Value '${actual}' is not assignable to type '${expected}'.`, actual, values);
}
return value;
};
const checkAll = (arr: string[]): UnionType[] => {
return arr.map(check);
};
/**
* StringUnion.parse(value) returns value when it's valid, and undefined otherwise.
*/
const parse = (value: string|null|undefined): UnionType|undefined => {
return value != null && guard(value) ? value : undefined;
};
const unionNamespace = { guard, check, parse, values, checkAll };
return Object.freeze(unionNamespace as typeof unionNamespace & {type: UnionType});
};