autobaan/src/common/request.ts
Collin Duncan a18e100d34
Adding some pretty stuff
Reorganized code and added some unit tests for reservations and requests
2021-11-24 00:00:22 +01:00

116 lines
3 KiB
TypeScript

import dayjs, { Dayjs } from 'dayjs'
import isSameOrBefore from 'dayjs/plugin/isSameOrBefore'
dayjs.extend(isSameOrBefore)
import { DateRange, Opponent } from './reservation'
export interface ReservationRequest {
username: string
password: string
dateRanges: {
start: Dayjs
end: Dayjs
}[]
opponent: Opponent
}
export enum ValidationErrorCode {
UNDEFINED_REQUEST_BODY = 1,
INVALID_REQUEST_BODY = 2,
INVALID_DATE_RANGE = 3,
INVALID_START_OR_END_DATE = 4,
INVALID_OPPONENT = 5,
}
export class ValidationError extends Error {
public readonly code: ValidationErrorCode
constructor(message: string, code: ValidationErrorCode) {
super(message)
this.code = code
}
}
export const validateRequest = (
body: string
): ReservationRequest => {
const request = validateRequestBody(body)
validateRequestDateRanges(request.dateRanges)
validateRequestOpponent(request.opponent)
return request
}
const validateRequestBody = (body?: string): ReservationRequest => {
if (body === undefined) {
throw new ValidationError('Invalid request', ValidationErrorCode.UNDEFINED_REQUEST_BODY)
}
const jsonBody = transformRequestBody(body)
const { username, password, opponent, dateRanges } = jsonBody
if (
!username ||
username.length < 1 ||
!password ||
password.length < 1 ||
!dateRanges ||
dateRanges.length < 1 ||
(opponent && opponent.id && opponent.id.length < 1) ||
(opponent && opponent.name && opponent.name.length < 1)
) {
throw new ValidationError('Invalid request', ValidationErrorCode.INVALID_REQUEST_BODY)
}
return jsonBody
}
const transformRequestBody = (body: string): ReservationRequest => {
const json = JSON.parse(body)
const dateRanges: DateRange[] = json.dateRanges?.map(
({ start, end }: { start: string; end: string }): DateRange => {
return { start: dayjs(start), end: dayjs(end) }
}
)
return {
username: json.username,
password: json.password,
opponent: json.opponent,
dateRanges,
}
}
const validateRequestDateRanges = (dateRanges: DateRange[]): void => {
for (let i = 0; i < dateRanges.length; i++) {
// checking that both dates are valid
const { start, end } = dateRanges[i]
if (!start.isValid() || !end.isValid()) {
throw new ValidationError('Invalid request', ValidationErrorCode.INVALID_DATE_RANGE)
}
// checking that:
// 1. start occurs after now
// 2. start occurs before or same as end
// 3. start and end fall on same YYYY/MM/DD
if (
!start.isAfter(dayjs()) ||
!start.isSameOrBefore(end) ||
start.format('YYYY MM DD') !== end.format('YYYY MM DD')
) {
throw new ValidationError('Invalid request', ValidationErrorCode.INVALID_START_OR_END_DATE)
}
}
}
const validateRequestOpponent = (opponent?: Opponent): void => {
if (!opponent) return
const { id, name } = opponent
if (
typeof id !== 'string' ||
typeof name !== 'string' ||
id.length < 1 ||
name.length < 1
) {
throw new ValidationError('Invalid request', ValidationErrorCode.INVALID_OPPONENT)
}
}