Refactoring: Split shared types into seperate modules and document alot.

This commit is contained in:
baldo 2022-09-06 19:09:25 +02:00
parent e08ae944c4
commit 843cd37243
31 changed files with 2498 additions and 842 deletions

View file

@ -0,0 +1,30 @@
/**
* Utility functions for enums.
*/
/**
* Helper function to detect unhandled enum fields in `switch` statements at compile time. In case this function
* is called at runtime anyway (which should not happen) it throws a runtime error.
*
* In the example below the compiler will complain if not for all fields of `Enum` a corresponding `case` statement
* exists.
*
* @param field - Unhandled field, the value being switched over.
* @throws {@link Error} - If the function is called at runtime.
*
* @example
* switch (enumValue) {
* case Enum.FIELD1:
* return;
* case Enum.FIELD2:
* return;
*
* ...
*
* default:
* return unhandledEnumField(enumValue);
* }
*/
export function unhandledEnumField(field: never): never {
throw new Error(`Unhandled enum field: ${field}`);
}

View file

@ -0,0 +1,42 @@
/**
* Utility functions for JSON.
*/
import { isJSONValue, JSONObject, JSONValue } from "../types";
/**
* Parses the given `string` and converts it into a {@link JSONValue}.
*
* For the string to be considered valid JSON it has to satisfy the requirements for {@link JSON.parse}.
*
* @param str - `string` to parse.
* @returns The parsed integer JSON value.
* @throws {@link SyntaxError} - If the given `string` does not represent a valid JSON value.
*/
export function parseJSON(str: string): JSONValue {
const json = JSON.parse(str);
if (!isJSONValue(json)) {
throw new Error("Invalid JSON returned. Should never happen.");
}
return json;
}
/**
* Removes `undefined` fields from the given JSON'ish object to make it a valid {@link JSONObject}.
*
* Note: This only happens for fields directly belonging to the given object. No recursive cleanup is performed.
*
* @param obj - Object to remove `undefined` fields from.
* @returns Cleaned up JSON object.
*/
export function filterUndefinedFromJSON(obj: {
[key: string]: JSONValue | undefined;
}): JSONObject {
const result: JSONObject = {};
for (const [key, value] of Object.entries(obj)) {
if (value !== undefined) {
result[key] = value;
}
}
return result;
}

View file

@ -0,0 +1,14 @@
/**
* Utility functions for node related data.
*/
import { MAC, MapId } from "../types";
/**
* Converts the MAC address of a Freifunk node to an id representing it on the community's node map.
*
* @param mac - MAC address of the node
* @returns ID of the node on the map
*/
export function mapIdFromMAC(mac: MAC): MapId {
return mac.toLowerCase().replace(/:/g, "") as MapId;
}

View file

@ -0,0 +1,19 @@
/**
* Helper functions for objects.
*/
import { hasOwnProperty } from "../types";
/**
* If the given value is an object this function returns the property specified by `key` if it exists.
*
* @param arg - Value to treat as an object to look up the property.
* @param key - Key indexing the property.
* @returns The property of the given object indexed by `key` or `undefined` if `arg` is not an object
* or has no property `key`.
*/
export function getFieldIfExists(
arg: unknown,
key: PropertyKey
): unknown | undefined {
return hasOwnProperty(arg, key) ? arg[key] : undefined;
}

View file

@ -1,9 +1,29 @@
import { isString, MAC } from "../types";
/**
* Utility functions all around strings.
*/
import { isInteger, MAC } from "../types";
/**
* Trims the given `string` and replaces multiple whitespaces by one space each.
*
* Can be used to make sure user input has a canonical form.
*
* @param str - `string` to normalize.
* @returns The normalized `string`.
*/
export function normalizeString(str: string): string {
return isString(str) ? str.trim().replace(/\s+/g, " ") : str;
return str.trim().replace(/\s+/g, " ");
}
/**
* Normalizes a {@link MAC} address so that it has a canonical format:
*
* The `MAC` address will be converted so that it is all uppercase with colon as the delimiter, e.g.:
* `12:34:56:78:9A:BC`.
*
* @param mac - `MAC` address to normalize.
* @returns The normalized `MAC` address.
*/
export function normalizeMac(mac: MAC): MAC {
// parts only contains values at odd indexes
const parts = mac
@ -20,9 +40,26 @@ export function normalizeMac(mac: MAC): MAC {
return macParts.join(":") as MAC;
}
/**
* Parses the given `string` and converts it into an integer.
*
* For a `string` to be considered a valid representation of an integer `number` it has to satisfy the
* following criteria:
*
* * The integer is base `10`.
* * The `string` starts with an optional `+` or `-` sign followed by one or more digits.
* * The first digit must not be `0`.
* * The `string` does not contain any other characters.
*
* @param str - `string` to parse.
* @returns The parsed integer `number`.
* @throws {@link SyntaxError} - If the given `string` does not represent a valid integer.
*/
export function parseInteger(str: string): number {
const parsed = parseInt(str, 10);
if (parsed.toString() === str) {
const original = str.startsWith("+") ? str.slice(1) : str;
if (isInteger(parsed) && parsed.toString() === original) {
return parsed;
} else {
throw new SyntaxError(

View file

@ -0,0 +1,28 @@
/**
* Utility functions for "wibbly wobbly timey wimey" stuff.
*/
import { UnixTimestampMilliseconds, UnixTimestampSeconds } from "../types";
/**
* Converts an {@link UnixTimestampMilliseconds} to an {@link UnixTimestampSeconds} rounding down.
*
* @param ms - The timestamp in milliseconds.
* @returns - The timestamp in seconds.
*/
export function toUnixTimestampSeconds(
ms: UnixTimestampMilliseconds
): UnixTimestampSeconds {
return Math.floor(ms / 1000) as UnixTimestampSeconds;
}
/**
* Converts an {@link UnixTimestampSeconds} to an {@link UnixTimestampMilliseconds}.
*
* @param s - The timestamp in seconds.
* @returns - The timestamp in milliseconds.
*/
export function toUnixTimestampMilliseconds(
s: UnixTimestampSeconds
): UnixTimestampMilliseconds {
return (s * 1000) as UnixTimestampMilliseconds;
}