Refactor some server-side string types into newtypes.

This commit is contained in:
baldo 2022-07-14 20:06:05 +02:00
parent 22b80ef26f
commit 6f3eb92c45
10 changed files with 346 additions and 225 deletions
server/types

View file

@ -1,17 +1,32 @@
import {ArrayField, Field, RawJsonField} from "sparkson"
import {ClientConfig} from "./shared";
import {ClientConfig, to} from "./shared";
// TODO: Replace string types by more specific types like URL, Password, etc.
export type Username = string;
export type CleartextPassword = string;
export type PasswordHash = string;
export type Username = {
value: string;
readonly __tag: unique symbol
};
export type CleartextPassword = {
value: string;
readonly __tag: unique symbol
};
export type PasswordHash = {
value: string;
readonly __tag: unique symbol
};
export class UsersConfig {
public username: Username;
public passwordHash: PasswordHash;
constructor(
@Field("user") public username: Username,
@Field("passwordHash") public passwordHash: PasswordHash,
) {}
@Field("user") username: string,
@Field("passwordHash") passwordHash: string,
) {
this.username = to(username);
this.passwordHash = to(passwordHash);
}
}
export class LoggingConfig {

View file

@ -6,10 +6,37 @@ export type TypeGuard<T> = (arg: unknown) => arg is T;
export type EnumValue<E> = E[keyof E];
export type EnumTypeGuard<E> = TypeGuard<EnumValue<E>>;
export function unhandledEnumField(field: never): never {
throw new Error(`Unhandled enum field: ${field}`);
}
export function to<Type extends { readonly __tag: symbol, value: any } = { readonly __tag: unique symbol, value: never }>(value: Type['value']): Type {
return value as any as Type;
}
export function lift2<Result, Type extends { readonly __tag: symbol, value: any }>(callback: (a: Type["value"], b: Type["value"]) => Result): (newtype1: Type, newtype2: Type) => Result {
return (a, b) => callback(a.value, b.value);
}
export function equal<Result, Type extends { readonly __tag: symbol, value: any }>(a: Type, b: Type): boolean {
return lift2((a, b) => a === b)(a, b);
}
export function isObject(arg: unknown): arg is object {
return arg !== null && typeof arg === "object";
}
export function toIsNewtype<Type extends { readonly __tag: symbol, value: Value } = { readonly __tag: unique symbol, value: never }, Value = any>(isValue: TypeGuard<Value>): TypeGuard<Type> {
// TODO: Add validation pattern.
return (arg: unknown): arg is Type => {
if (!isObject(arg)) {
return false;
}
const newtype = arg as Type;
return isValue(newtype.value);
}
}
export function isArray<T>(arg: unknown, isT: TypeGuard<T>): arg is Array<T> {
if (!Array.isArray(arg)) {
return false;
@ -248,19 +275,32 @@ export function isClientConfig(arg: unknown): arg is ClientConfig {
}
// TODO: Token type.
export type Token = string;
export const isToken = isString;
export type Token = {
value: string;
readonly __tag: unique symbol
};
export const isToken = toIsNewtype<Token>(isString);
export type FastdKey = string;
export const isFastdKey = isString;
export type FastdKey = {
value: string;
readonly __tag: unique symbol
};
export const isFastdKey = toIsNewtype<FastdKey>(isString);
export type MAC = string;
export const isMAC = isString;
export type MAC = {
value: string;
readonly __tag: unique symbol
};
export const isMAC = toIsNewtype<MAC>(isString);
export type UnixTimestampSeconds = number;
export type UnixTimestampMilliseconds = number;
export type MonitoringToken = string;
export type MonitoringToken = {
value: string;
readonly __tag: unique symbol
};
export enum MonitoringState {
ACTIVE = "active",
PENDING = "pending",
@ -269,9 +309,12 @@ export enum MonitoringState {
export const isMonitoringState = toIsEnum(MonitoringState);
export type NodeId = string;
export const isNodeId = isString;
export type NodeId = {
value: string;
readonly __tag: unique symbol
};
// TODO: More Newtypes
export interface Node {
token: Token;
nickname: string;
@ -310,13 +353,20 @@ export enum OnlineState {
ONLINE = "ONLINE",
OFFLINE = "OFFLINE",
}
export const isOnlineState = toIsEnum(OnlineState);
export type Site = string;
export const isSite = isString;
export type Site = {
value: string;
readonly __tag: unique symbol
};
export const isSite = toIsNewtype<Site>(isString);
export type Domain = string;
export const isDomain = isString;
export type Domain = {
value: string;
readonly __tag: unique symbol
};
export const isDomain = toIsNewtype<Domain>(isString);
export interface EnhancedNode extends Node {
site?: Site,
@ -426,7 +476,10 @@ export enum MailSortField {
export const isMailSortField = toIsEnum(MailSortField);
export type GenericSortField = string;
export type GenericSortField = {
value: string;
readonly __tag: unique symbol
};
export enum SortDirection {
ASCENDING = "ASC",