perfect-validator
Version:
A TypeScript-based validation library that supports both static and dynamic validation with serializable models.
346 lines (318 loc) • 9.64 kB
text/typescript
import { PerfectValidator } from './types';
import { validateAgainstModel, validateDataModel } from './validators';
import {
deserializeValidationModel,
serializeValidationModel,
getValidationTypeParams,
} from './utils';
import userProfileModel, { testCases } from './utils/model';
export class PV {
private storage?: PerfectValidator.IModelStorage;
private static instance: PV | null = null;
constructor(storage?: PerfectValidator.IModelStorage) {
this.storage = storage;
}
/**
* Static validation with direct model and data
*/
public validateStatic<T>(
data: T,
model: PerfectValidator.ValidationModel,
allowUnknownFields?: boolean
): PerfectValidator.ValidationResponse<T> {
const modelValidation: PerfectValidator.ModelValidationResponse = this.validateModel(
model
);
if (!modelValidation.isValid && modelValidation.errors) {
return {
isValid: false,
errors: modelValidation.errors.map(error => ({
field: 'model',
message: error,
})) as PerfectValidator.ValidationError[],
};
}
return validateAgainstModel(data, model , allowUnknownFields);
}
public static getInstance(storage?: PerfectValidator.IModelStorage): PV {
if (!PV.instance) {
try {
PV.instance = new PV(storage);
} catch (error) {
if (error instanceof Error) {
throw new Error(`Failed to create PV instance: ${error.message}`);
}
throw new Error('Failed to create PV instance: Unknown error');
}
}
return PV.instance;
}
/**
* Dynamic validation using stored model with optional collection
*/
public async validateDynamic<T>(
data: T,
modelName: string,
version?: number,
collection?: string,
allowUnknownFields?: boolean
): Promise<PerfectValidator.ValidationResponse<T>> {
if (!this.storage) {
throw new Error('Storage is required for dynamic validation');
}
try {
let serializedModel: string | null;
if (version !== undefined) {
const modelVersion = await this.storage.getModelVersion(
modelName,
version,
collection
);
if (!modelVersion) {
throw new Error(`Model ${modelName} version ${version} not found`);
}
serializedModel = modelVersion.model;
} else {
const latestVersion = await this.storage.getLatestModelVersion(
modelName,
collection
);
if (!latestVersion) {
throw new Error(`Model ${modelName} not found`);
}
serializedModel = latestVersion.model;
}
if (!serializedModel) {
throw new Error(`Model ${modelName} not found`);
}
const model: PerfectValidator.ValidationModel = deserializeValidationModel(
serializedModel
);
return validateAgainstModel(data, model, allowUnknownFields);
} catch (error) {
if (error instanceof Error) {
return {
isValid: false,
errors: [
{
field: 'model',
message: `Failed to load model: ${error.message}`,
},
],
};
}
return {
isValid: false,
errors: [
{
field: 'model',
message: 'Failed to load model: Unknown error',
},
],
};
}
}
/**
* Store model with validation and optional collection
*/
public async storeModel(
modelName: string,
model: PerfectValidator.ValidationModel,
version?: number,
collection?: string
): Promise<PerfectValidator.ModelValidationResponse> {
try {
if (!this.storage) {
throw new Error('Storage is required for model storage');
}
// Validate and store model with optional collection
const modelValidation = this.validateModel(model);
if (!modelValidation.isValid) {
throw new Error(
`Model validation failed: ${modelValidation.errors?.join(', ')}`
);
}
const serialized = await this.serializeModelSafely(model);
const deserialized = await this.deserializeAndValidate(serialized);
if (version !== undefined) {
await this.storage.storeModelVersion(
modelName,
serialized,
version,
collection
);
} else {
const latestVersion = await this.storage.getLatestModelVersion(
modelName,
collection
);
const newVersion = latestVersion ? latestVersion.version + 1 : 1;
await this.storage.storeModelVersion(
modelName,
serialized,
newVersion,
collection
);
}
return { isValid: true, errors: null };
} catch (error) {
if (error instanceof Error) {
return {
isValid: false,
errors: [`Failed to store model: ${error.message}`],
};
}
return {
isValid: false,
errors: ['Failed to store model: Unknown error'],
};
}
}
/**
* Validate model structure
*/
public validateModel(
model: PerfectValidator.ValidationModel
): PerfectValidator.ModelValidationResponse {
return validateDataModel(model);
}
private async serializeModelSafely(
model: PerfectValidator.ValidationModel
): Promise<string> {
try {
return serializeValidationModel(model);
} catch (error) {
if (error instanceof Error) {
throw new Error(`Model serialization failed: ${error.message}`);
}
throw new Error('Model serialization failed: Unknown error');
}
}
private async deserializeAndValidate(
serialized: string
): Promise<PerfectValidator.ValidationModel> {
try {
const model: PerfectValidator.ValidationModel = deserializeValidationModel(
serialized
);
const validation: PerfectValidator.ModelValidationResponse = validateDataModel(
model
);
if (!validation.isValid) {
throw new Error(
`Invalid model after deserialization: ${validation.errors?.join(
', '
) || 'Unknown error'}`
);
}
return model;
} catch (error) {
if (error instanceof Error) {
throw new Error(`Model deserialization failed: ${error.message}`);
}
throw new Error('Model deserialization failed: Unknown error');
}
}
/**
* Get all supported data types with descriptions
*/
public getDataTypes(): Record<string, PerfectValidator.ValidationType> {
return PerfectValidator.ValidationTypes;
}
/**
* Get example model with different validation cases
*/
public getModelExample(): PerfectValidator.ValidationModel {
return userProfileModel;
}
/**
* Get example data matching the model example
*/
public getDataExample(): Array<{ name: string; data: any }> {
return testCases;
}
public getValidationTypeParams(
type: PerfectValidator.ValidationType
): PerfectValidator.IValidationTypeParams {
try {
return getValidationTypeParams(type);
} catch (error) {
if (error instanceof Error) {
throw new Error(
`Failed to get validation type params: ${error.message}`
);
}
throw new Error('Failed to get validation type params: Unknown error');
}
}
// Add new utility methods for version management
public async getLatestModelVersion(
modelName: string,
collection?: string
): Promise<PerfectValidator.ValidationModel | null> {
if (!this.storage) {
throw new Error('Storage is required');
}
const latestVersion = await this.storage.getLatestModelVersion(
modelName,
collection
);
if (!latestVersion) {
throw new Error(`Model ${modelName} not found`);
}
return deserializeValidationModel(latestVersion.model);
}
/**
* Get model of a specific version
* @param modelName Name of the model
* @param version Version number of the model
* @returns The model version or null if not found
*/
public async getModelVersion(
modelName: string,
version: number,
collection?: string
): Promise<PerfectValidator.ValidationModel | null> {
if (!this.storage) {
throw new Error('Storage is required');
}
try {
const modelVersion: PerfectValidator.ModelVersion | null = await this.storage.getModelVersion(
modelName,
version,
collection
);
if (!modelVersion) {
throw new Error(`Model ${modelName} version ${version} not found`);
}
return deserializeValidationModel(modelVersion.model);
} catch (error) {
if (error instanceof Error) {
throw new Error(`Failed to get model version: ${error.message}`);
}
throw new Error('Failed to get model version: Unknown error');
}
}
/**
* Get list of all versions for a model
* @param modelName Name of the model
* @param collection Optional collection name
* @returns Array of version numbers in descending order (newest first)
*/
public async listModelVersions(
modelName: string,
collection?: string
): Promise<number[]> {
if (!this.storage) {
throw new Error('Storage is required');
}
try {
return await this.storage.listModelVersions(modelName, collection);
} catch (error) {
if (error instanceof Error) {
throw new Error(`Failed to list model versions: ${error.message}`);
}
throw new Error('Failed to list model versions: Unknown error');
}
}
}