platform/README.md
2016-12-25 20:36:59 -05:00

6.6 KiB

validate

version 0.0.0 build status

(Not yet production ready, still missing several tests and a few matchers)

Validation library based on the matcher library, with Angel support. Why re-invent the wheel, when you can use the same validators you already use for tests?

This library runs both on the server, and on the client. Thus, you can use the same validation rules for forms on the server, and on the frontend.

For convenience's sake, this library also exports matcher.

Examples

Creating a Validator

import 'package:angel_validate/angel_validate.dart';

main() {
    var validator = new Validator({
        'username': isAlphaNum,
        'balance': [
            greaterThanOrEqualTo(0),
            lessThan(1000000)
        ]
    });
}

Validating data

The Validator will filter out fields that have no validation rules. You can rest easy knowing that attackers cannot slip extra data into your applications.

main() {
    var result = validator.check(formData);

    if (!result.errors.isNotEmpty) {
        // Invalid data
    } else {
        // Safely handle filtered data
        return someSecureOperation(result.data);
    }
}

You can enforce validation rules, and throw an error if validation fails.

main() {
    try {
        // `enforce` will return the filtered data.
        var safeData = validator.enforce(formData);
    } on ValidationException catch(e) {
        print(e.errors);
    }
}

Required Fields

Fields are optional by default.

Suffix a field name with a '*' to mark it as required, and to throw an error if it is not present.

main() {
    var validator = new Validator({
        'googleId*': isString
    });
}

Default values

If not present, default values will be filled in before validation. This means that they can still be used with required fields.

final Validator todo = new Validator({
    'text*': isString,
    'completed*': isBool
}, defaultValues: {
    'completed': false
});

Default values can also be parameterless, synchronous functions that return a single value.

Custom Validator Functions

Creating a whole Matcher class is sometimes cumbersome, but if you pass a function to the constructor, it will be wrapped in a Matcher instance.

(It simply returns the value of calling predicate.)

The function must synchronously return a bool.

main() {
    var validator = new Validator({
        'key*': (key) {
            var file = new File('whitelist.txt');
            return file.readFileSync().contains(key);
        }
    });
}

Extending Validators

You can add situation-specific rules within a child validator. You can also use extend to mark fields as required that originally were not. Default value extension is also supported.

final Validator userValidator = new Validator({
    'username': isString,
    'age': [
        isNum,
        greaterThanOrEqualTo(18)
    ]
});

To mark a field as now optional, and no longer required, suffix its name with a '?'.

var ageIsOptional = userValidator.extend({
    'age?': [
        isNum,
        greaterThanOrEqualTo(13)
    ]
});

Note that by default, new validation rules are simply appended to the existing list. To completely overwrite existing rules, set the overwrite flag to true.

register(Map userData) {
    var teenUser = userValidator.extend({
        'age': lessThan(18)
    }, overwrite: true);    
}

Bundled Matchers

This library includes some Matchers for common validations, including:

  • isAlphaDash: Asserts a String matches the Regular Expression /^[A-Za-z0-9_-]$/.
  • isAlphaNum: Asserts a String matches the Regular Expression /^[A-Za-z0-9]$/
  • isBool: Asserts that a value either equals true or false.
  • isEmail: Asserts a String complies to the RFC 5322 e-mail standard.
  • isInt: Asserts a value is an int.
  • isNum: Asserts a value is a num.
  • isString: Asserts that a value is a String.

The remaining functionality is effectively implemented by the matcher package.

Nested Validators

Very often, the data we validate contains other data within. You can pass a Validator instance to the constructor, because it extends the Matcher class.

main() {
    var bio = new Validator({
        'age*': [isInteger, greaterThanOrEqualTo(0)],
        'birthYear*': isInteger,
        'countryOfOrigin': isString
    });

    var book = new Validator({
        'title*': isString,
        'year*': [
            isNum,
            (year) {
                return year <= new DateTime.now().year;
            }
        ]
    });

    var author = new Validator({
        'bio*': bio,
        'books*': [
            isList,
            everyElement(book)
        ]
    }, defaultValues: {
        'books': []
    });
}

Use with Angel

server.dart exposes three helper middleware:

  • validate(validator): Validates and filters req.body, and throws an AngelHttpException.BadRequest if data is invalid.
  • validateEvent(validator): Sets e.data to the result of validation on a service event.
  • validateQuery(validator): Same as validate, but operates req.query.
import 'package:angel_framework/angel_framework.dart';
import 'package:angel_validate/server.dart';

final Validator echo = new Validator({
    'message*': (String message) => message.length >= 5
});

final Validator todo = new Validator({
    'text*': isString,
    'completed*': isBool
}, defaultValues: {
    'completed': false
});

main() async {
    var app = new Angel();

    app.chain(validate(echo)).post('/echo', (req, res) async {
        res.write('You said: "${req.body["message"]}"');
    });

    app.service('api/todos')
        ..beforeCreated.listen(validateEvent(todo))
        ..beforeUpdated.listen(validateEvent(todo));

    await app.startServer();
}