Initial commit: gmTouringMiniApp project
This commit is contained in:
@@ -0,0 +1,118 @@
|
||||
import {
|
||||
type BudgetLevel,
|
||||
type Duration,
|
||||
type Interest,
|
||||
type Pace,
|
||||
type Theme,
|
||||
type Transport,
|
||||
TRAVEL_BUDGET_LEVELS,
|
||||
TRAVEL_DURATIONS,
|
||||
TRAVEL_INTERESTS,
|
||||
TRAVEL_PACES,
|
||||
TRAVEL_THEMES,
|
||||
TRAVEL_TRANSPORTS,
|
||||
type TravelPreferences,
|
||||
} from './types'
|
||||
|
||||
const DESTINATION = '深圳市光明区' as const
|
||||
|
||||
export class TravelValidationError extends Error {
|
||||
constructor(message: string) {
|
||||
super(message)
|
||||
this.name = 'TravelValidationError'
|
||||
}
|
||||
}
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
||||
}
|
||||
|
||||
function isEnumValue<T extends string>(value: unknown, allowed: readonly T[]): value is T {
|
||||
return typeof value === 'string' && allowed.includes(value as T)
|
||||
}
|
||||
|
||||
function normalizeEnumArray<T extends string>(
|
||||
value: unknown,
|
||||
allowed: readonly T[],
|
||||
fieldName: string,
|
||||
): T[] {
|
||||
if (!Array.isArray(value) || value.some(item => !isEnumValue(item, allowed)))
|
||||
throw new TravelValidationError(`${fieldName}包含无效选项`)
|
||||
|
||||
const selected = new Set(value as T[])
|
||||
return allowed.filter(item => selected.has(item))
|
||||
}
|
||||
|
||||
function normalizeInteger(value: unknown, fieldName: string, min: number, max: number): number {
|
||||
if (!Number.isInteger(value) || (value as number) < min || (value as number) > max)
|
||||
throw new TravelValidationError(`${fieldName}必须是 ${min} 至 ${max} 的整数`)
|
||||
return value as number
|
||||
}
|
||||
|
||||
export function normalizeTravelPreferences(value: unknown): TravelPreferences {
|
||||
if (!isRecord(value))
|
||||
throw new TravelValidationError('出行偏好格式无效')
|
||||
if (value.destination !== DESTINATION)
|
||||
throw new TravelValidationError('当前仅支持深圳市光明区')
|
||||
|
||||
const themes = normalizeEnumArray<Theme>(value.themes, TRAVEL_THEMES, '出行主题')
|
||||
const interests = normalizeEnumArray<Interest>(value.interests, TRAVEL_INTERESTS, '特色偏好')
|
||||
if (themes.length === 0 && interests.length === 0)
|
||||
throw new TravelValidationError('主题或特色偏好至少选择一项')
|
||||
if (!isEnumValue<Duration>(value.duration, TRAVEL_DURATIONS))
|
||||
throw new TravelValidationError('游玩时长无效')
|
||||
if (!isEnumValue<Pace>(value.pace, TRAVEL_PACES))
|
||||
throw new TravelValidationError('行程节奏无效')
|
||||
if (!isEnumValue<Transport>(value.transport, TRAVEL_TRANSPORTS))
|
||||
throw new TravelValidationError('交通方式无效')
|
||||
if (!isEnumValue<BudgetLevel>(value.budgetLevel, TRAVEL_BUDGET_LEVELS))
|
||||
throw new TravelValidationError('预算档位无效')
|
||||
|
||||
const adults = normalizeInteger(value.adults, '成人数量', 0, 20)
|
||||
const children = normalizeInteger(value.children, '儿童数量', 0, 20)
|
||||
if (adults + children < 1)
|
||||
throw new TravelValidationError('出行总人数至少为 1')
|
||||
if (!Array.isArray(value.childAges) || value.childAges.length !== children)
|
||||
throw new TravelValidationError('儿童年龄数量必须与儿童人数一致')
|
||||
const childAges = value.childAges.map(age => normalizeInteger(age, '儿童年龄', 0, 17))
|
||||
|
||||
if (typeof value.extraRequirements !== 'string')
|
||||
throw new TravelValidationError('补充要求格式无效')
|
||||
const extraRequirements = value.extraRequirements.trim()
|
||||
if (extraRequirements.length > 500)
|
||||
throw new TravelValidationError('补充要求不能超过 500 字')
|
||||
|
||||
return {
|
||||
destination: DESTINATION,
|
||||
themes,
|
||||
duration: value.duration,
|
||||
pace: value.pace,
|
||||
interests,
|
||||
adults,
|
||||
children,
|
||||
childAges,
|
||||
transport: value.transport,
|
||||
budgetLevel: value.budgetLevel,
|
||||
extraRequirements,
|
||||
}
|
||||
}
|
||||
|
||||
export function isTravelPreferences(value: unknown): value is TravelPreferences {
|
||||
try {
|
||||
normalizeTravelPreferences(value)
|
||||
return true
|
||||
}
|
||||
catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
export function cloneTravelPreferences(preferences: TravelPreferences): TravelPreferences {
|
||||
const normalized = normalizeTravelPreferences(preferences)
|
||||
return {
|
||||
...normalized,
|
||||
themes: [...normalized.themes],
|
||||
interests: [...normalized.interests],
|
||||
childAges: [...normalized.childAges],
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user