wip
This commit is contained in:
102
packages/racing/domain/value-objects/LeagueName.ts
Normal file
102
packages/racing/domain/value-objects/LeagueName.ts
Normal file
@@ -0,0 +1,102 @@
|
||||
/**
|
||||
* Domain Value Object: LeagueName
|
||||
*
|
||||
* Represents a valid league name with validation rules.
|
||||
*/
|
||||
|
||||
export interface LeagueNameValidationResult {
|
||||
valid: boolean;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
export const LEAGUE_NAME_CONSTRAINTS = {
|
||||
minLength: 3,
|
||||
maxLength: 64,
|
||||
pattern: /^[a-zA-Z0-9].*$/, // Must start with alphanumeric
|
||||
forbiddenPatterns: [
|
||||
/^\s/, // No leading whitespace
|
||||
/\s$/, // No trailing whitespace
|
||||
/\s{2,}/, // No multiple consecutive spaces
|
||||
],
|
||||
} as const;
|
||||
|
||||
export class LeagueName {
|
||||
readonly value: string;
|
||||
|
||||
private constructor(value: string) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate a league name without creating the value object
|
||||
*/
|
||||
static validate(value: string): LeagueNameValidationResult {
|
||||
const trimmed = value?.trim() ?? '';
|
||||
|
||||
if (!trimmed) {
|
||||
return { valid: false, error: 'League name is required' };
|
||||
}
|
||||
|
||||
if (trimmed.length < LEAGUE_NAME_CONSTRAINTS.minLength) {
|
||||
return {
|
||||
valid: false,
|
||||
error: `League name must be at least ${LEAGUE_NAME_CONSTRAINTS.minLength} characters`,
|
||||
};
|
||||
}
|
||||
|
||||
if (trimmed.length > LEAGUE_NAME_CONSTRAINTS.maxLength) {
|
||||
return {
|
||||
valid: false,
|
||||
error: `League name must be ${LEAGUE_NAME_CONSTRAINTS.maxLength} characters or less`,
|
||||
};
|
||||
}
|
||||
|
||||
if (!LEAGUE_NAME_CONSTRAINTS.pattern.test(trimmed)) {
|
||||
return {
|
||||
valid: false,
|
||||
error: 'League name must start with a letter or number',
|
||||
};
|
||||
}
|
||||
|
||||
for (const forbidden of LEAGUE_NAME_CONSTRAINTS.forbiddenPatterns) {
|
||||
if (forbidden.test(value)) {
|
||||
return {
|
||||
valid: false,
|
||||
error: 'League name cannot have leading/trailing spaces or multiple consecutive spaces',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return { valid: true };
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a LeagueName from a string value
|
||||
*/
|
||||
static create(value: string): LeagueName {
|
||||
const validation = this.validate(value);
|
||||
if (!validation.valid) {
|
||||
throw new Error(validation.error);
|
||||
}
|
||||
return new LeagueName(value.trim());
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to create a LeagueName, returning null if invalid
|
||||
*/
|
||||
static tryCreate(value: string): LeagueName | null {
|
||||
const validation = this.validate(value);
|
||||
if (!validation.valid) {
|
||||
return null;
|
||||
}
|
||||
return new LeagueName(value.trim());
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
equals(other: LeagueName): boolean {
|
||||
return this.value === other.value;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user