MotorSignal-App/lib/services/clubs/club_slug_validator.dart

103 lines
2.8 KiB
Dart

import '../social/handle_validator.dart';
/// Result of a Club slug validation check.
class ClubSlugValidationResult {
final bool isValid;
final String? error;
final String? normalizedSlug;
const ClubSlugValidationResult._({
required this.isValid,
this.error,
this.normalizedSlug,
});
factory ClubSlugValidationResult.valid(String normalized) =>
ClubSlugValidationResult._(isValid: true, normalizedSlug: normalized);
factory ClubSlugValidationResult.invalid(String error) =>
ClubSlugValidationResult._(isValid: false, error: error);
}
/// Normalization and validation logic for MotorSignal Club slugs.
class ClubSlugValidator {
static const int minLength = 3;
static const int maxLength = 30;
static final RegExp _formatRegex = RegExp(r'^[a-z0-9][a-z0-9-]{1,28}[a-z0-9]$');
/// Reserved words specific to clubs, chapters, and platform organizations.
static const Set<String> clubReservedWords = {
'club',
'clubs',
'motorcycle',
'motorcycles',
'mc',
'chapter',
'chapters',
'national',
'international',
'official',
'rally',
'groupride',
'grouprides',
'event',
'events',
'all',
'global',
'feed',
'roster',
'members',
'settings',
'admin',
};
/// Normalizes a raw slug: trims whitespace and lowercases.
static String normalize(String rawSlug) => rawSlug.trim().toLowerCase();
/// Validates a candidate slug format and checks against reserved words.
static ClubSlugValidationResult validate(String rawSlug) {
final normalized = normalize(rawSlug);
if (normalized.isEmpty) {
return ClubSlugValidationResult.invalid('Club slug cannot be empty');
}
if (normalized.length < minLength) {
return ClubSlugValidationResult.invalid(
'Club slug must be at least $minLength characters',
);
}
if (normalized.length > maxLength) {
return ClubSlugValidationResult.invalid(
'Club slug cannot exceed $maxLength characters',
);
}
if (normalized.contains('--')) {
return ClubSlugValidationResult.invalid(
'Club slug cannot contain consecutive hyphens',
);
}
if (!_formatRegex.hasMatch(normalized)) {
return ClubSlugValidationResult.invalid(
'Club slug must start and end with a letter or number, and contain only lowercase letters, numbers, and hyphens',
);
}
// Check against standard platform handles (admin, support, motorsignal, etc.)
if (HandleValidator.reservedHandles.contains(normalized)) {
return ClubSlugValidationResult.invalid('This club slug is reserved');
}
// Check against club-specific reserved words
if (clubReservedWords.contains(normalized)) {
return ClubSlugValidationResult.invalid('This club slug is reserved');
}
return ClubSlugValidationResult.valid(normalized);
}
}