wip
This commit is contained in:
@@ -1,9 +1,11 @@
|
||||
/**
|
||||
* Domain Value Object: LeagueDescription
|
||||
*
|
||||
*
|
||||
* Represents a valid league description with validation rules.
|
||||
*/
|
||||
|
||||
import { RacingDomainValidationError } from '../errors/RacingDomainError';
|
||||
|
||||
export interface LeagueDescriptionValidationResult {
|
||||
valid: boolean;
|
||||
error?: string;
|
||||
@@ -63,7 +65,7 @@ export class LeagueDescription {
|
||||
static create(value: string): LeagueDescription {
|
||||
const validation = this.validate(value);
|
||||
if (!validation.valid) {
|
||||
throw new Error(validation.error);
|
||||
throw new RacingDomainValidationError(validation.error ?? 'Invalid league description');
|
||||
}
|
||||
return new LeagueDescription(value.trim());
|
||||
}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
/**
|
||||
* Domain Value Object: LeagueName
|
||||
*
|
||||
*
|
||||
* Represents a valid league name with validation rules.
|
||||
*/
|
||||
|
||||
import { RacingDomainValidationError } from '../errors/RacingDomainError';
|
||||
|
||||
export interface LeagueNameValidationResult {
|
||||
valid: boolean;
|
||||
error?: string;
|
||||
@@ -76,7 +78,7 @@ export class LeagueName {
|
||||
static create(value: string): LeagueName {
|
||||
const validation = this.validate(value);
|
||||
if (!validation.valid) {
|
||||
throw new Error(validation.error);
|
||||
throw new RacingDomainValidationError(validation.error ?? 'Invalid league name');
|
||||
}
|
||||
return new LeagueName(value.trim());
|
||||
}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { RacingDomainValidationError } from '../errors/RacingDomainError';
|
||||
|
||||
export class LeagueTimezone {
|
||||
private readonly id: string;
|
||||
|
||||
constructor(id: string) {
|
||||
if (!id || id.trim().length === 0) {
|
||||
throw new Error('LeagueTimezone id must be a non-empty string');
|
||||
throw new RacingDomainValidationError('LeagueTimezone id must be a non-empty string');
|
||||
}
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ export class LeagueVisibility {
|
||||
if (value === 'unranked' || value === 'private') {
|
||||
return LeagueVisibility.unranked();
|
||||
}
|
||||
throw new Error(`Invalid league visibility: ${value}`);
|
||||
throw new RacingDomainValidationError(`Invalid league visibility: ${value}`);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -51,39 +51,39 @@ export class LiveryDecal {
|
||||
|
||||
private static validate(props: LiveryDecalProps): void {
|
||||
if (!props.id || props.id.trim().length === 0) {
|
||||
throw new Error('LiveryDecal ID is required');
|
||||
throw new RacingDomainValidationError('LiveryDecal ID is required');
|
||||
}
|
||||
|
||||
if (!props.imageUrl || props.imageUrl.trim().length === 0) {
|
||||
throw new Error('LiveryDecal imageUrl is required');
|
||||
throw new RacingDomainValidationError('LiveryDecal imageUrl is required');
|
||||
}
|
||||
|
||||
if (props.x < 0 || props.x > 1) {
|
||||
throw new Error('LiveryDecal x coordinate must be between 0 and 1 (normalized)');
|
||||
throw new RacingDomainValidationError('LiveryDecal x coordinate must be between 0 and 1 (normalized)');
|
||||
}
|
||||
|
||||
if (props.y < 0 || props.y > 1) {
|
||||
throw new Error('LiveryDecal y coordinate must be between 0 and 1 (normalized)');
|
||||
throw new RacingDomainValidationError('LiveryDecal y coordinate must be between 0 and 1 (normalized)');
|
||||
}
|
||||
|
||||
if (props.width <= 0 || props.width > 1) {
|
||||
throw new Error('LiveryDecal width must be between 0 and 1 (normalized)');
|
||||
throw new RacingDomainValidationError('LiveryDecal width must be between 0 and 1 (normalized)');
|
||||
}
|
||||
|
||||
if (props.height <= 0 || props.height > 1) {
|
||||
throw new Error('LiveryDecal height must be between 0 and 1 (normalized)');
|
||||
throw new RacingDomainValidationError('LiveryDecal height must be between 0 and 1 (normalized)');
|
||||
}
|
||||
|
||||
if (!Number.isInteger(props.zIndex) || props.zIndex < 0) {
|
||||
throw new Error('LiveryDecal zIndex must be a non-negative integer');
|
||||
throw new RacingDomainValidationError('LiveryDecal zIndex must be a non-negative integer');
|
||||
}
|
||||
|
||||
if (props.rotation < 0 || props.rotation > 360) {
|
||||
throw new Error('LiveryDecal rotation must be between 0 and 360 degrees');
|
||||
throw new RacingDomainValidationError('LiveryDecal rotation must be between 0 and 360 degrees');
|
||||
}
|
||||
|
||||
if (!props.type) {
|
||||
throw new Error('LiveryDecal type is required');
|
||||
throw new RacingDomainValidationError('LiveryDecal type is required');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
* Represents membership fee configuration for league drivers
|
||||
*/
|
||||
|
||||
import { RacingDomainValidationError } from '../errors/RacingDomainError';
|
||||
|
||||
import type { Money } from './Money';
|
||||
|
||||
export type MembershipFeeType = 'season' | 'monthly' | 'per_race';
|
||||
@@ -23,15 +25,15 @@ export class MembershipFee {
|
||||
|
||||
static create(type: MembershipFeeType, amount: Money): MembershipFee {
|
||||
if (!type) {
|
||||
throw new Error('MembershipFee type is required');
|
||||
throw new RacingDomainValidationError('MembershipFee type is required');
|
||||
}
|
||||
|
||||
if (!amount) {
|
||||
throw new Error('MembershipFee amount is required');
|
||||
throw new RacingDomainValidationError('MembershipFee amount is required');
|
||||
}
|
||||
|
||||
if (amount.amount < 0) {
|
||||
throw new Error('MembershipFee amount cannot be negative');
|
||||
throw new RacingDomainValidationError('MembershipFee amount cannot be negative');
|
||||
}
|
||||
|
||||
return new MembershipFee({ type, amount });
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
* Represents a monetary amount with currency and platform fee calculation
|
||||
*/
|
||||
|
||||
import { RacingDomainValidationError } from '../errors/RacingDomainError';
|
||||
|
||||
export type Currency = 'USD' | 'EUR' | 'GBP';
|
||||
|
||||
export class Money {
|
||||
@@ -18,10 +20,10 @@ export class Money {
|
||||
|
||||
static create(amount: number, currency: Currency = 'USD'): Money {
|
||||
if (amount < 0) {
|
||||
throw new Error('Money amount cannot be negative');
|
||||
throw new RacingDomainValidationError('Money amount cannot be negative');
|
||||
}
|
||||
if (!Number.isFinite(amount)) {
|
||||
throw new Error('Money amount must be a finite number');
|
||||
throw new RacingDomainValidationError('Money amount must be a finite number');
|
||||
}
|
||||
return new Money(amount, currency);
|
||||
}
|
||||
@@ -47,7 +49,7 @@ export class Money {
|
||||
*/
|
||||
add(other: Money): Money {
|
||||
if (this.currency !== other.currency) {
|
||||
throw new Error('Cannot add money with different currencies');
|
||||
throw new RacingDomainValidationError('Cannot add money with different currencies');
|
||||
}
|
||||
return new Money(this.amount + other.amount, this.currency);
|
||||
}
|
||||
@@ -57,11 +59,11 @@ export class Money {
|
||||
*/
|
||||
subtract(other: Money): Money {
|
||||
if (this.currency !== other.currency) {
|
||||
throw new Error('Cannot subtract money with different currencies');
|
||||
throw new RacingDomainValidationError('Cannot subtract money with different currencies');
|
||||
}
|
||||
const result = this.amount - other.amount;
|
||||
if (result < 0) {
|
||||
throw new Error('Subtraction would result in negative amount');
|
||||
throw new RacingDomainValidationError('Subtraction would result in negative amount');
|
||||
}
|
||||
return new Money(result, this.currency);
|
||||
}
|
||||
@@ -71,7 +73,7 @@ export class Money {
|
||||
*/
|
||||
isGreaterThan(other: Money): boolean {
|
||||
if (this.currency !== other.currency) {
|
||||
throw new Error('Cannot compare money with different currencies');
|
||||
throw new RacingDomainValidationError('Cannot compare money with different currencies');
|
||||
}
|
||||
return this.amount > other.amount;
|
||||
}
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
import { RacingDomainValidationError } from '../errors/RacingDomainError';
|
||||
|
||||
export class RaceTimeOfDay {
|
||||
readonly hour: number;
|
||||
readonly minute: number;
|
||||
|
||||
constructor(hour: number, minute: number) {
|
||||
if (!Number.isInteger(hour) || hour < 0 || hour > 23) {
|
||||
throw new Error(`RaceTimeOfDay hour must be between 0 and 23, got ${hour}`);
|
||||
throw new RacingDomainValidationError(`RaceTimeOfDay hour must be between 0 and 23, got ${hour}`);
|
||||
}
|
||||
if (!Number.isInteger(minute) || minute < 0 || minute > 59) {
|
||||
throw new Error(`RaceTimeOfDay minute must be between 0 and 59, got ${minute}`);
|
||||
throw new RacingDomainValidationError(`RaceTimeOfDay minute must be between 0 and 59, got ${minute}`);
|
||||
}
|
||||
|
||||
this.hour = hour;
|
||||
@@ -17,7 +19,7 @@ export class RaceTimeOfDay {
|
||||
static fromString(value: string): RaceTimeOfDay {
|
||||
const match = /^(\d{2}):(\d{2})$/.exec(value);
|
||||
if (!match) {
|
||||
throw new Error(`RaceTimeOfDay string must be in HH:MM 24h format, got "${value}"`);
|
||||
throw new RacingDomainValidationError(`RaceTimeOfDay string must be in HH:MM 24h format, got "${value}"`);
|
||||
}
|
||||
|
||||
const hour = Number(match[1]);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { WeekdaySet } from './WeekdaySet';
|
||||
import { MonthlyRecurrencePattern } from './MonthlyRecurrencePattern';
|
||||
import { RacingDomainValidationError } from '../errors/RacingDomainError';
|
||||
|
||||
export type RecurrenceStrategyKind = 'weekly' | 'everyNWeeks' | 'monthlyNthWeekday';
|
||||
|
||||
@@ -34,7 +35,9 @@ export class RecurrenceStrategyFactory {
|
||||
|
||||
static everyNWeeks(intervalWeeks: number, weekdays: WeekdaySet): RecurrenceStrategy {
|
||||
if (!Number.isInteger(intervalWeeks) || intervalWeeks < 1 || intervalWeeks > 12) {
|
||||
throw new Error('everyNWeeks intervalWeeks must be an integer between 1 and 12');
|
||||
throw new RacingDomainValidationError(
|
||||
'everyNWeeks intervalWeeks must be an integer between 1 and 12',
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { LeagueTimezone } from './LeagueTimezone';
|
||||
|
||||
import { RacingDomainValidationError } from '../errors/RacingDomainError';
|
||||
|
||||
export class ScheduledRaceSlot {
|
||||
readonly roundNumber: number;
|
||||
readonly scheduledAt: Date;
|
||||
@@ -7,10 +9,10 @@ export class ScheduledRaceSlot {
|
||||
|
||||
constructor(params: { roundNumber: number; scheduledAt: Date; timezone: LeagueTimezone }) {
|
||||
if (!Number.isInteger(params.roundNumber) || params.roundNumber <= 0) {
|
||||
throw new Error('ScheduledRaceSlot.roundNumber must be a positive integer');
|
||||
throw new RacingDomainValidationError('ScheduledRaceSlot.roundNumber must be a positive integer');
|
||||
}
|
||||
if (!(params.scheduledAt instanceof Date) || Number.isNaN(params.scheduledAt.getTime())) {
|
||||
throw new Error('ScheduledRaceSlot.scheduledAt must be a valid Date');
|
||||
throw new RacingDomainValidationError('ScheduledRaceSlot.scheduledAt must be a valid Date');
|
||||
}
|
||||
|
||||
this.roundNumber = params.roundNumber;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { RaceTimeOfDay } from './RaceTimeOfDay';
|
||||
import { LeagueTimezone } from './LeagueTimezone';
|
||||
import type { RecurrenceStrategy } from './RecurrenceStrategy';
|
||||
import { RacingDomainValidationError } from '../errors/RacingDomainError';
|
||||
|
||||
export class SeasonSchedule {
|
||||
readonly startDate: Date;
|
||||
@@ -17,10 +18,10 @@ export class SeasonSchedule {
|
||||
plannedRounds: number;
|
||||
}) {
|
||||
if (!(params.startDate instanceof Date) || Number.isNaN(params.startDate.getTime())) {
|
||||
throw new Error('SeasonSchedule.startDate must be a valid Date');
|
||||
throw new RacingDomainValidationError('SeasonSchedule.startDate must be a valid Date');
|
||||
}
|
||||
if (!Number.isInteger(params.plannedRounds) || params.plannedRounds <= 0) {
|
||||
throw new Error('SeasonSchedule.plannedRounds must be a positive integer');
|
||||
throw new RacingDomainValidationError('SeasonSchedule.plannedRounds must be a positive integer');
|
||||
}
|
||||
|
||||
this.startDate = new Date(
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
export type Weekday = 'Mon' | 'Tue' | 'Wed' | 'Thu' | 'Fri' | 'Sat' | 'Sun';
|
||||
|
||||
import { RacingDomainInvariantError } from '../errors/RacingDomainError';
|
||||
|
||||
export const ALL_WEEKDAYS: Weekday[] = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
|
||||
|
||||
export function weekdayToIndex(day: Weekday): number {
|
||||
@@ -20,6 +22,6 @@ export function weekdayToIndex(day: Weekday): number {
|
||||
return 7;
|
||||
default:
|
||||
// This should be unreachable because Weekday is a closed union.
|
||||
throw new Error(`Unknown weekday: ${day}`);
|
||||
throw new RacingDomainInvariantError(`Unknown weekday: ${day}`);
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,13 @@
|
||||
import type { Weekday } from './Weekday';
|
||||
import { weekdayToIndex } from './Weekday';
|
||||
import { RacingDomainValidationError } from '../errors/RacingDomainError';
|
||||
|
||||
export class WeekdaySet {
|
||||
private readonly days: Weekday[];
|
||||
|
||||
constructor(days: Weekday[]) {
|
||||
if (!Array.isArray(days) || days.length === 0) {
|
||||
throw new Error('WeekdaySet requires at least one weekday');
|
||||
throw new RacingDomainValidationError('WeekdaySet requires at least one weekday');
|
||||
}
|
||||
|
||||
const unique = Array.from(new Set(days));
|
||||
|
||||
Reference in New Issue
Block a user