elersong / fireorm24

ORM for Firebase Firestore 🔥 updated for 2024
MIT License
1 stars 0 forks source link

Support Custom Validation Libraries #34

Open elersong opened 2 months ago

elersong commented 2 months ago

Description

Fireorm is tightly coupled with class-validator for model validation. To provide flexibility, we should allow the use of arbitrary validation libraries. This would involve decoupling Fireorm from class-validator and introducing a validation interface that can accommodate various validation libraries.

Steps to Reproduce

  1. Attempt to use a validation library other than class-validator with Fireorm.
  2. Notice the tight coupling with class-validator and the difficulty in substituting another library.

Expected Behavior

Ability to use any validation library with Fireorm by providing a custom validator through the initialization options.

Actual Behavior

Fireorm is tightly coupled with class-validator, making it challenging to use other validation libraries.

Acceptance Criteria

Additional Context

Proposed API Changes

  1. Introduce Validation Interface:

    • Define a validation interface to support various validation libraries.
    interface IEntityValidator {
     (entity: IEntity, validateOptions?: Record<string, unknown>): Array<unknown>;
    }
  2. Update Initialization Options:

    • Modify the initialization options to accept a custom validator.
    interface IFireormInitOptions  {
     validateModels?: boolean;
     validate?: IEntityValidator;
    }
    
    function initialize(firestore: Firestore, options: IFireormInitOptions = {}) {
     // Existing initialization logic...
     if (options.validateModels && options.validate) {
       this.validator = options.validate;
     } else {
       // Default to class-validator
       const { validate } = require('class-validator');
       this.validator = validate;
     }
    }
  3. Decouple Fireorm from class-validator:

    • Refactor the codebase to use the validation interface instead of directly relying on class-validator.
    class Repository {
     async create(entity: IEntity) {
       if (this.config.validateModels) {
         const errors = this.config.validator(entity);
         if (errors.length) {
           throw errors;
         }
       }
       // Existing create logic...
     }
    }
  4. Unit Tests:

    • Add unit tests to validate the functionality with different validation libraries.
    test('should validate entity using custom validator', async () => {
     const customValidator: IEntityValidator = (entity) => {
       if (!entity.name) return ['Name is required'];
       return [];
     };
    
     initialize(firestore, { validateModels: true, validate: customValidator });
    
     const repo = getRepository(MyEntity);
     const entity = new MyEntity();
     entity.name = '';
    
     await expect(repo.create(entity)).rejects.toEqual(['Name is required']);
    });

Example Implementation

import { getFirestore } from 'my-firestore-setup';
import { initialize } from 'fireorm';

class MyValidator implements IEntityValidator {
  validate(entity: IEntity, validateOptions?: Record<string, unknown>): Array<unknown> {
    const errors = [];
    if (!entity.name) errors.push('Name is required');
    return errors;
  }
}

const customValidator = new MyValidator().validate;

initialize(getFirestore(), { validateModels: true, validate: customValidator });

@Collection("myEntities")
class MyEntity {
  id: string;
  name: string;
}

const repo = getRepository(MyEntity);
const entity = new MyEntity();
entity.id = "1";
entity.name = "";

await repo.create(entity); // Should throw 'Name is required'

Original Issue