Skip to content

Use TS to avoid set the param() by hand #4

@hidegh

Description

@hidegh

So working with strings within TS is weird, here's a sample what we can achieve there:

        var validator = require('fluent-validator');
        var validation = validator()
            .validate(this.Data, x => <TestPersonModel>x.LastName).isUppercase().isAlpha()
            .validate(this.Data, x => <TestPersonModel>x.SSN).isNotEmpty().and.matches("\\d{3}-\\d{2}-\\d{4}").and.isLength(11)
        ;

        // custom message
        validation.validate(this.Data, x => <TestPersonModel>x.FirstName).passes(x => x == "Abc", "Must be Abc");
        validation.validate(this.Data, x => <TestPersonModel>x.Age).passes(x => validator().isGreaterOrEql(x, 18), "Must be at least 18yrs old.");

        var errors = validation.getErrors();

Fortunately it's easy to extend/override fluent-validator default behavior and export it under a different node module. The code to override and to work like above is below. Also worth to notice that it does not break existing code, so might be worth to consider to add it to the library itself (not a big deal to compile it down to JS).

'use strict';

/*
SAMPLE:
var validator = require("fluent-validation-extension");
var errors = validator().validate(m, x => x.Name).isAlpha().getErrors();


 */

var fluentValidator = require('fluent-validator');

module.exports = function(value) {

    var inst = fluentValidator();
    return patchValidator(inst);
};

var originalValidate;

function patchValidator(validatorInst)
{
    originalValidate = validatorInst.validate;
    validatorInst.validate = extendedValidate.bind(null);
    return validatorInst;
}

// NOTE: see fluent-ts-validator for the original selector code
function extendedValidate<T, TProperty>(obj: T, expression: (instance: T) => TProperty) : any
{
    if (expression) {
        let propertySelector = getPropertySelector(expression);
        let value = getValue(obj, propertySelector)
        return originalValidate(value).param(propertySelector);
    }

    return originalValidate(obj);
}

var memberNamesExtractor = new RegExp("return (.*);?\\b");
var memberNamesExtractorArrowFunctions = new RegExp("=>(.*)");

function getPropertySelector<TResult>(name: (x?: TResult) => any) {
    let match = memberNamesExtractor.exec(name + "") || memberNamesExtractorArrowFunctions.exec(name + "");
    if (match == null)
        throw new Error("The function does not contain a statement matching 'return variableName;'");

    let expression = match[1].toString();

    let firstDotIndex = expression.indexOf('.');
    let propertySelector = expression.substring(firstDotIndex + 1);

    return propertySelector;
}

function getValue(obj: any, propertySelector: string) : any
{
    /*
    // NOTE: simple solution with prop1.prop2...propN - no support for arrays...
    var memberNames = propertySelector.split('.')
    var value = obj;
    for (let member of memberNames) {
        value = value[member]
    }
    */

    // NOTE: the don't do way
    // value = eval("obj." + propertySelector);

    // NOTE: alternative way to eval()
    let value = new Function('obj', "return obj." + propertySelector)(obj);

    return value;
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions