Files
Scoreboard/web/src/shared/internal.ts
T

77 lines
2.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* shared 层内部工具,不对外导出。
*
* HTTP / WebSocket 收到的一切都先当 unknown,再用这些收窄函数校验;
* 直接断言会把服务端字段改名这类问题推迟到运行期某个深处才炸。
*/
/**
* FormData / Blob(含 File)/ URLSearchParams 不是普通对象:
* 展开或 JSON 序列化都会丢掉内容,必须原样交给 fetch(报名表导入就是被展平才丢文件)。
*/
export function isRawBody(value: unknown): value is FormData | Blob | URLSearchParams {
if (typeof FormData !== 'undefined' && value instanceof FormData) return true
if (typeof Blob !== 'undefined' && value instanceof Blob) return true
if (typeof URLSearchParams !== 'undefined' && value instanceof URLSearchParams) return true
return false
}
export function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === 'object' && value !== null && !Array.isArray(value) && !isRawBody(value)
}
export function readString(source: unknown, key: string): string | null {
if (!isRecord(source)) return null
const value = source[key]
return typeof value === 'string' ? value : null
}
export function asRecord(value: unknown): Record<string, unknown> {
return isRecord(value) ? value : {}
}
/** 依次取第一个可用值:容忍服务端把数字写成字符串(settings 表存的是文本)。 */
export function firstString(...values: unknown[]): string | null {
for (const value of values) {
if (typeof value === 'string') return value
}
return null
}
export function firstNumber(def: number, ...values: unknown[]): number {
const found = firstNumberOrNull(...values)
return found === null ? def : found
}
export function firstNumberOrNull(...values: unknown[]): number | null {
for (const value of values) {
if (typeof value === 'number' && Number.isFinite(value)) return value
if (typeof value === 'string' && value.trim() !== '') {
const parsed = Number(value)
if (Number.isFinite(parsed)) return parsed
}
}
return null
}
export function firstBool(def: boolean, ...values: unknown[]): boolean {
for (const value of values) {
if (typeof value === 'boolean') return value
if (typeof value === 'number') return value !== 0
if (typeof value === 'string') {
const normalized = value.trim().toLowerCase()
if (normalized === 'true' || normalized === '1' || normalized === 'yes' || normalized === 'on') {
return true
}
if (normalized === 'false' || normalized === '0' || normalized === 'no' || normalized === 'off') {
return false
}
}
}
return def
}
export function emptyToNull(value: string | null): string | null {
return value === null || value.trim() === '' ? null : value
}