3
0
Files
corteza/lib/js/src/cast.ts
2022-11-14 09:26:42 +01:00

189 lines
4.8 KiB
TypeScript

// const iso8601check = /^([\\+-]?\d{4}(?!\d{2}\b))((-?)((0[1-9]|1[0-2])(\3([12]\d|0[1-9]|3[01]))?|W([0-4]\d|5[0-2])(-?[1-7])?|(00[1-9]|0[1-9]\d|[12]\d{2}|3([0-5]\d|6[1-6])))([T\s]((([01]\d|2[0-3])((:?)[0-5]\d)?|24:?00)([.,]\d+(?!:))?)?(\17[0-5]\d([.,]\d+)?)?([zZ]|([\\+-])([01]\d|2[0-3]):?([0-5]\d)?)?)?)?$/
const uint64zeropad = '00000000000000000000'
/**
* Reasons behind this small snippet:
* - backend is using uint64 as prefered type for handling CortezaID (of all things)
* - JavaScript can not (without external help) deal with uint64
* - Backend's JSON marshaller converts uint64 to string
* - Backend's JSON unmarshaller raises error when given anything but string with a number inside
*
* Until this last thing is fixed or has a proper workaround, we're stuck with this.
*/
export const NoID = '0'
export function ISO8601Date (ts?: unknown): Date|undefined {
if (ts instanceof Date) {
return ts
}
if (typeof ts === 'string' || typeof ts === 'number') {
return new Date(ts)
}
return undefined
}
interface Caster<T> {
(input: unknown): T;
}
/**
* Is native class?
*
* @param thing
* @returns {boolean}
*/
// function isNativeClass (o: unknown): boolean {
// return typeof o === 'function' &&
// Object.prototype.hasOwnProperty.call(o, 'prototype') &&
// !Object.prototype.hasOwnProperty.call(o, 'arguments')
// }
/**
* Casts value to <type> or returns default
*/
export function PropCast<T> (type: Caster<T>, o: {[_: string]: unknown}|undefined, prop: string): T|undefined {
if (o === undefined || Object.prototype.hasOwnProperty.call(o, prop)) {
return undefined
}
return type(o.prop)
}
/**
* Tests if a given value looks like corteza ID
* @param ID
* @constructor
*/
export function IsCortezaID (ID: unknown): boolean {
if (typeof ID !== 'string') {
return false
}
if (!/^\d+$/.test(ID)) {
return false
}
return true
}
/**
* @return {string}
*/
export function CortezaID (value: unknown): string {
if (!value) {
return NoID
}
if (typeof value === 'number') {
return value.toString()
}
if (IsCortezaID(value)) {
return value as string
}
throw new Error('Invalid CortezaID value')
}
/**
* Apply caster interface that satisfies basic casting functions + String, Number etc...
*/
interface ApplyCaster {
(val: unknown): unknown;
}
/**
* Apply takes all given props, their values (from src) and assignes them to props (on dst)
*
* A casting function can be used (see ApplyCaster) to modify the values before assigning them
*/
export function Apply<DST, SRC, T extends keyof DST> (dst: DST, src: SRC, cast: ApplyCaster|keyof DST, ...props: (keyof DST)[]): void {
if (typeof cast !== 'function') {
// Handle case where we do not use caster
props.unshift(cast)
// and use String as a caster, effectively forcing
// cast-to-string on all applied values to
cast = String
}
if (typeof src !== 'object') {
return
}
props.forEach(prop => {
// prop must exist on dst
if (!Object.prototype.hasOwnProperty.call(dst, prop)) {
return
}
// prop must exist on src
if (!Object.prototype.hasOwnProperty.call(src, prop)) {
return
}
// sProp is prop from source
const sProp = (prop as unknown) as keyof SRC
// value on src should be defined
if (src[sProp] === undefined || src[sProp] === null) {
return
}
// Cast value from src to type of value from prop on dst
let val = (src[sProp] as unknown) as DST[T]
// Run value through cast fn
val = ((cast as ApplyCaster)(val) as unknown) as DST[T]
if (val === undefined) {
return
}
// Assign (valid ony) value to dst
dst[prop] = val
})
}
export function ApplyWhitelisted<DST, SRC, WL, T extends keyof DST> (dst: DST, src: SRC, whitelist: (DST[T])[], ...props: (keyof DST)[]): void {
if (typeof src !== 'object') {
return
}
props.forEach(prop => {
// prop must exist on dst
if (!Object.prototype.hasOwnProperty.call(dst, prop)) {
return
}
// prop must exist on src
if (!Object.prototype.hasOwnProperty.call(src, prop)) {
return
}
// sProp is prop from source
const sProp = (prop as unknown) as keyof SRC
// value on src should be defined
if (src[sProp] === undefined) {
return
}
// Cast value from src to type of value from prop on dst
const val = (src[sProp] as unknown) as DST[T]
if (whitelist.includes(val)) {
dst[prop] = val
}
})
}
export function makeIDSortable (ID?: string): string {
// We're using uint64 for CortezaID and JavaScript does not know how to handle this type
// natively. We get the value from backend as string anyway and we need to prefix
// it with '0' to ensure string sorting does what we need it to.
return uint64zeropad.substr((ID || '').length) + (ID || '')
}