Make typeguards for newtypes match the type instead of isString or isNumber.

This commit is contained in:
baldo 2022-07-28 12:22:57 +02:00
parent f296c6fe26
commit 72543b95ee
3 changed files with 34 additions and 21 deletions

View file

@ -1,11 +1,14 @@
import {ArrayField, Field, RawJsonField} from "sparkson" import {ArrayField, Field, RawJsonField} from "sparkson"
import {ClientConfig, JSONObject, Url} from "./shared"; import {ClientConfig, isSite, isString, JSONObject, toIsNewtype, Url} from "./shared";
// TODO: Replace string types by more specific types like URL, Password, etc.
export type Username = string & { readonly __tag: unique symbol }; export type Username = string & { readonly __tag: unique symbol };
export const isUsername = toIsNewtype(isString, "" as Username);
export type CleartextPassword = string & { readonly __tag: unique symbol }; export type CleartextPassword = string & { readonly __tag: unique symbol };
export const isCleartextPassword = toIsNewtype(isString, "" as CleartextPassword);
export type PasswordHash = string & { readonly __tag: unique symbol }; export type PasswordHash = string & { readonly __tag: unique symbol };
export const isPasswordHash = toIsNewtype(isString, "" as PasswordHash);
export class UsersConfig { export class UsersConfig {
constructor( constructor(

View file

@ -3,6 +3,7 @@ import {
Domain, Domain,
DomainSpecificNodeResponse, DomainSpecificNodeResponse,
EmailAddress, EmailAddress,
isNumber,
JSONObject, JSONObject,
MonitoringResponse, MonitoringResponse,
MonitoringState, MonitoringState,
@ -13,6 +14,7 @@ import {
Site, Site,
StoredNode, StoredNode,
toIsEnum, toIsEnum,
toIsNewtype,
} from "./shared"; } from "./shared";
export * from "./config"; export * from "./config";
@ -90,12 +92,13 @@ export function toMonitoringResponse(node: StoredNode): MonitoringResponse {
}; };
} }
// TODO: Complete interface / class declaration.
export type NodeSecrets = { export type NodeSecrets = {
monitoringToken?: MonitoringToken, monitoringToken?: MonitoringToken,
}; };
export type MailId = number & { readonly __tag: unique symbol }; export type MailId = number & { readonly __tag: unique symbol };
export const isMailId = toIsNewtype(isNumber, NaN as MailId);
export type MailData = JSONObject; export type MailData = JSONObject;
export enum MailType { export enum MailType {

View file

@ -85,6 +85,13 @@ export function isString(arg: unknown): arg is string {
return typeof arg === "string" return typeof arg === "string"
} }
export function toIsNewtype<
Type extends Value & { readonly __tag: symbol },
Value,
>(isValue: TypeGuard<Value>, _example: Type): TypeGuard<Type> {
return (arg: unknown): arg is Type => isValue(arg);
}
export function isNumber(arg: unknown): arg is number { export function isNumber(arg: unknown): arg is number {
return typeof arg === "number" return typeof arg === "number"
} }
@ -114,13 +121,13 @@ export function isOptional<T>(arg: unknown, isT: TypeGuard<T>): arg is (T | unde
} }
export type Url = string & { readonly __tag: unique symbol }; export type Url = string & { readonly __tag: unique symbol };
export const isUrl = isString; export const isUrl = toIsNewtype(isString, "" as Url);
export type Version = string & { readonly __tag: unique symbol }; export type Version = string & { readonly __tag: unique symbol };
export const isVersion = isString; export const isVersion = toIsNewtype(isString, "" as Version);
export type EmailAddress = string & { readonly __tag: unique symbol }; export type EmailAddress = string & { readonly __tag: unique symbol };
export const isEmailAddress = isString; export const isEmailAddress = toIsNewtype(isString, "" as EmailAddress);
export type NodeStatistics = { export type NodeStatistics = {
registered: number; registered: number;
@ -321,31 +328,30 @@ export function isClientConfig(arg: unknown): arg is ClientConfig {
); );
} }
// TODO: Token type.
export type Token = string & { readonly __tag: unique symbol }; export type Token = string & { readonly __tag: unique symbol };
export const isToken = isString; export const isToken = toIsNewtype(isString, "" as Token);
export type FastdKey = string & { readonly __tag: unique symbol }; export type FastdKey = string & { readonly __tag: unique symbol };
export const isFastdKey = isString; export const isFastdKey = toIsNewtype(isString, "" as FastdKey);
export type MAC = string & { readonly __tag: unique symbol }; export type MAC = string & { readonly __tag: unique symbol };
export const isMAC = isString; export const isMAC = toIsNewtype(isString, "" as MAC);
export type DurationSeconds = number & { readonly __tag: unique symbol }; export type DurationSeconds = number & { readonly __tag: unique symbol };
export const isDurationSeconds = isNumber; export const isDurationSeconds = toIsNewtype(isNumber, NaN as DurationSeconds);
export type UnixTimestampSeconds = number & { readonly __tag: unique symbol }; export type UnixTimestampSeconds = number & { readonly __tag: unique symbol };
export const isUnixTimestampSeconds = isNumber; export const isUnixTimestampSeconds = toIsNewtype(isNumber, NaN as UnixTimestampSeconds);
export type UnixTimestampMilliseconds = number & { readonly __tag: unique symbol }; export type UnixTimestampMilliseconds = number & { readonly __tag: unique symbol };
export const isUnixTimestampMilliseconds = isNumber; export const isUnixTimestampMilliseconds = toIsNewtype(isNumber, NaN as UnixTimestampMilliseconds);
export function toUnixTimestampSeconds(ms: UnixTimestampMilliseconds): UnixTimestampSeconds { export function toUnixTimestampSeconds(ms: UnixTimestampMilliseconds): UnixTimestampSeconds {
return Math.floor(ms) as UnixTimestampSeconds; return Math.floor(ms) as UnixTimestampSeconds;
} }
export type MonitoringToken = string & { readonly __tag: unique symbol }; export type MonitoringToken = string & { readonly __tag: unique symbol };
export const isMonitoringToken = isString; export const isMonitoringToken = toIsNewtype(isString, "" as MonitoringToken);
export enum MonitoringState { export enum MonitoringState {
ACTIVE = "active", ACTIVE = "active",
@ -356,15 +362,16 @@ export enum MonitoringState {
export const isMonitoringState = toIsEnum(MonitoringState); export const isMonitoringState = toIsEnum(MonitoringState);
export type NodeId = string & { readonly __tag: unique symbol }; export type NodeId = string & { readonly __tag: unique symbol };
export const isNodeId = toIsNewtype(isString, "" as NodeId);
export type Hostname = string & { readonly __tag: unique symbol }; export type Hostname = string & { readonly __tag: unique symbol }
export const isHostname = isString; export const isHostname = toIsNewtype(isString, "" as Hostname);
export type Nickname = string & { readonly __tag: unique symbol }; export type Nickname = string & { readonly __tag: unique symbol };
export const isNickname = isString; export const isNickname = toIsNewtype(isString, "" as Nickname);
export type Coordinates = string & { readonly __tag: unique symbol }; export type Coordinates = string & { readonly __tag: unique symbol };
export const isCoordinates = isString; export const isCoordinates = toIsNewtype(isString, "" as Coordinates);
/** /**
* Basic node data. * Basic node data.
@ -473,10 +480,10 @@ export enum OnlineState {
export const isOnlineState = toIsEnum(OnlineState); export const isOnlineState = toIsEnum(OnlineState);
export type Site = string & { readonly __tag: unique symbol }; export type Site = string & { readonly __tag: unique symbol };
export const isSite = isString; export const isSite = toIsNewtype(isString, "" as Site);
export type Domain = string & { readonly __tag: unique symbol }; export type Domain = string & { readonly __tag: unique symbol };
export const isDomain = isString; export const isDomain = toIsNewtype(isString, "" as Domain);
/** /**
* Represents a node in the context of a Freifunk site and domain. * Represents a node in the context of a Freifunk site and domain.