ajt-validator
Version:
Validation library for JavaScript and TypeScript
182 lines (181 loc) • 7.13 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.GenderValidator = exports.GenderOption = void 0;
const base_1 = require("../base");
/**
* Standard gender options
*/
var GenderOption;
(function (GenderOption) {
GenderOption["MALE"] = "male";
GenderOption["FEMALE"] = "female";
GenderOption["NON_BINARY"] = "non-binary";
GenderOption["OTHER"] = "other";
GenderOption["PREFER_NOT_TO_SAY"] = "prefer-not-to-say";
})(GenderOption || (exports.GenderOption = GenderOption = {}));
/**
* Validator for gender fields
* Supports standard options, custom entries, and abbreviations
*/
class GenderValidator extends base_1.BaseValidator {
/**
* Create a new gender validator
* @param options Configuration options
*/
constructor(options = {}) {
super();
this.lowercaseAllowedValues = [];
// Set default standard values from enum
this.standardValues = Object.values(GenderOption);
// Initialize default options
this.options = Object.assign({ allowedValues: this.standardValues, allowCustom: false, customMaxLength: 50, normalize: true, caseSensitive: false, allowAbbreviations: true }, options);
// Default abbreviation mapping
this.abbreviations = Object.assign({ 'm': GenderOption.MALE, 'f': GenderOption.FEMALE, 'nb': GenderOption.NON_BINARY, 'o': GenderOption.OTHER, 'x': GenderOption.OTHER, 'pnts': GenderOption.PREFER_NOT_TO_SAY, 'prefer not': GenderOption.PREFER_NOT_TO_SAY }, (this.options.abbreviationMap || {}));
// Precompute lowercase allowed values for case-insensitive matching
if (!this.options.caseSensitive && this.options.allowedValues) {
this.lowercaseAllowedValues = this.options.allowedValues.map(val => val.toLowerCase());
}
}
/**
* Validate a gender value
* @param value Gender string to validate
* @returns Validation result with additional metadata
*/
validate(value) {
// Check for null/empty/non-string values
if (value === null || value === undefined || value === '') {
return this.createError('GENDER_REQUIRED', 'Gender value is required');
}
// Convert to string if it's not already
const stringValue = String(value);
let processedValue = stringValue;
// Normalize if enabled
if (this.options.normalize) {
processedValue = stringValue.trim();
if (!this.options.caseSensitive) {
processedValue = processedValue.toLowerCase();
}
}
// Check if it's a standard or allowed value
const allowedValues = this.options.allowedValues || this.standardValues;
const isAllowed = this.checkAllowedValue(processedValue, allowedValues);
if (isAllowed) {
return {
isValid: true,
value: stringValue,
normalizedValue: processedValue,
isStandardOption: this.checkIsStandardOption(processedValue)
};
}
// Check abbreviations if enabled
if (this.options.allowAbbreviations) {
const expandedValue = this.expandAbbreviation(processedValue);
if (expandedValue && this.checkAllowedValue(expandedValue, allowedValues)) {
return {
isValid: true,
value: stringValue,
normalizedValue: processedValue,
expandedValue,
isStandardOption: this.checkIsStandardOption(expandedValue)
};
}
}
// Allow custom value if enabled
if (this.options.allowCustom) {
if (processedValue.length > (this.options.customMaxLength || 50)) {
return this.createError('GENDER_TOO_LONG', `Custom gender value cannot exceed ${this.options.customMaxLength} characters`);
}
return {
isValid: true,
value: stringValue,
normalizedValue: processedValue,
isStandardOption: false
};
}
// If we get here, the value is not valid
return this.createError('INVALID_GENDER', `Gender must be one of: ${allowedValues.join(', ')}`);
}
/**
* Check if value exists in allowed values list
* @param value Value to check
* @param allowedValues List of allowed values
* @returns Whether the value is allowed
*/
checkAllowedValue(value, allowedValues) {
if (this.options.caseSensitive) {
return allowedValues.indexOf(value) !== -1;
}
else {
// Use precomputed lowercase values if available
if (this.lowercaseAllowedValues.length > 0) {
return this.lowercaseAllowedValues.indexOf(value.toLowerCase()) !== -1;
}
// Fallback for dynamic allowed values
return allowedValues.some(allowed => allowed.toLowerCase() === value.toLowerCase());
}
}
/**
* Check if a value is a standard option
* @param value Value to check (assumed to be already normalized)
* @returns Whether the value is a standard option
*/
checkIsStandardOption(value) {
if (this.options.caseSensitive) {
return this.standardValues.indexOf(value) !== -1;
}
else {
const lowercaseValue = value.toLowerCase();
return this.standardValues.some(standard => standard.toLowerCase() === lowercaseValue);
}
}
/**
* Expand an abbreviation to its full form
* @param abbr Abbreviation to expand
* @returns Expanded value if found, or undefined
*/
expandAbbreviation(abbr) {
// Direct lookup for case-sensitive mode
if (this.options.caseSensitive) {
return this.abbreviations[abbr];
}
// Case-insensitive lookup
const lowerAbbr = abbr.toLowerCase();
// First try direct lowercase match
if (this.abbreviations[lowerAbbr]) {
return this.abbreviations[lowerAbbr];
}
// If not found, search for case-insensitive match in keys
const keys = Object.keys(this.abbreviations);
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
if (key.toLowerCase() === lowerAbbr) {
return this.abbreviations[key];
}
}
return undefined;
}
/**
* Get all standard gender options
* @returns List of standard gender values
*/
getStandardOptions() {
return [...this.standardValues];
}
/**
* Get all allowed abbreviations
* @returns Map of abbreviations to full values
*/
getAbbreviations() {
return Object.assign({}, this.abbreviations);
}
/**
* Create an error result for gender validation
*/
createError(code, message) {
return {
isValid: false,
errors: [{ code, message }]
};
}
}
exports.GenderValidator = GenderValidator;