Apply pedantic

This commit is contained in:
Tobe O 2019-09-27 09:28:20 -04:00
parent f7188f8a62
commit 5d8c2ae005
12 changed files with 106 additions and 94 deletions

View file

@ -36,7 +36,7 @@ For convenience's sake, this library also exports `matcher`.
import 'package:angel_validate/angel_validate.dart';
main() {
var validator = new Validator({
var validator = Validator({
'username': isAlphaNum,
'multiple,keys,with,same,rules': [isString, isNotEmpty],
'balance': [
@ -91,7 +91,7 @@ to throw an error if it is not present.
```dart
main() {
var validator = new Validator({
var validator = Validator({
'googleId*': isString,
// You can also use `requireField`
@ -111,7 +111,7 @@ If not present, default values will be filled in *before* validation.
This means that they can still be used with required fields.
```dart
final Validator todo = new Validator({
final Validator todo = Validator({
'text*': isString,
'completed*': isBool
}, defaultValues: {
@ -134,9 +134,9 @@ The function must *synchronously* return a `bool`.
```dart
main() {
var validator = new Validator({
var validator = Validator({
'key*': (key) {
var file = new File('whitelist.txt');
var file = File('whitelist.txt');
return file.readFileSync().contains(key);
}
});
@ -148,7 +148,7 @@ If these are not present, `angel_validate` will *attempt* to generate
a coherent error message on its own.
```dart
new Validator({
Validator({
'age': [greaterThanOrEqualTo(18)]
}, customErrorMessages: {
'age': 'You must be an adult to see this page.'
@ -189,7 +189,7 @@ You can also use `extend` to mark fields as required or forbidden that originall
were not. Default value and custom error message extension is also supported.
```dart
final Validator userValidator = new Validator({
final Validator userValidator = Validator({
'username': isString,
'age': [
isNum,
@ -210,7 +210,7 @@ var ageIsOptional = userValidator.extend({
});
```
Note that by default, new validation rules are simply appended to
Note that by default, validation rules are simply appended to
the existing list. To completely overwrite existing rules, set the
`overwrite` flag to `true`.
@ -246,23 +246,23 @@ a `Validator` instance to the constructor, because it extends the
```dart
main() {
var bio = new Validator({
var bio = Validator({
'age*': [isInt, greaterThanOrEqualTo(0)],
'birthYear*': isInt,
'countryOfOrigin': isString
});
var book = new Validator({
var book = Validator({
'title*': isString,
'year*': [
isNum,
(year) {
return year <= new DateTime.now().year;
return year <= DateTime.now().year;
}
]
});
var author = new Validator({
var author = Validator({
'bio*': bio,
'books*': [
isList,
@ -289,11 +289,11 @@ main() {
import 'package:angel_framework/angel_framework.dart';
import 'package:angel_validate/server.dart';
final Validator echo = new Validator({
final Validator echo = Validator({
'message*': (String message) => message.length >= 5
});
final Validator todo = new Validator({
final Validator todo = Validator({
'text*': isString,
'completed*': isBool
}, defaultValues: {
@ -301,7 +301,7 @@ final Validator todo = new Validator({
});
main() async {
var app = new Angel();
var app = Angel();
app.chain([validate(echo)]).post('/echo', (req, res) async {
res.write('You said: "${req.bodyAsMap["message"]}"');

View file

@ -1,3 +1,4 @@
include: package:pedantic/analysis_options.yaml
analyzer:
strong-mode:
implicit-casts: false

View file

@ -1,24 +1,24 @@
import 'package:angel_validate/angel_validate.dart';
main() {
var bio = new Validator({
var bio = Validator({
'age*': [isInt, greaterThanOrEqualTo(0)],
'birthYear*': isInt,
'countryOfOrigin': isString
});
var book = new Validator({
var book = Validator({
'title*': isString,
'year*': [
isNum,
(year) {
return year <= new DateTime.now().year;
return year <= DateTime.now().year;
}
]
});
// ignore: unused_local_variable
var author = new Validator({
var author = Validator({
'bio*': bio,
'books*': [isList, everyElement(book)]
}, defaultValues: {

View file

@ -52,13 +52,13 @@ RequestHandler filterQuery(Iterable<String> only) {
/// Validates the data in `req.bodyAsMap`, and sets the body to
/// filtered data before continuing the response.
RequestHandler validate(Validator validator,
{String errorMessage: 'Invalid data.'}) {
{String errorMessage = 'Invalid data.'}) {
return (RequestContext req, res) async {
await req.parseBody();
var result = await asyncApplyValidator(validator, req.bodyAsMap, req.app);
if (result.errors.isNotEmpty) {
throw new AngelHttpException.badRequest(
throw AngelHttpException.badRequest(
message: errorMessage, errors: result.errors);
}
@ -73,13 +73,13 @@ RequestHandler validate(Validator validator,
/// Validates the data in `req.queryParameters`, and sets the query to
/// filtered data before continuing the response.
RequestHandler validateQuery(Validator validator,
{String errorMessage: 'Invalid data.'}) {
{String errorMessage = 'Invalid data.'}) {
return (RequestContext req, res) async {
var result =
await asyncApplyValidator(validator, req.queryParameters, req.app);
if (result.errors.isNotEmpty) {
throw new AngelHttpException.badRequest(
throw AngelHttpException.badRequest(
message: errorMessage, errors: result.errors);
}
@ -94,13 +94,13 @@ RequestHandler validateQuery(Validator validator,
/// Validates the data in `e.data`, and sets the data to
/// filtered data before continuing the service event.
HookedServiceEventListener validateEvent(Validator validator,
{String errorMessage: 'Invalid data.'}) {
{String errorMessage = 'Invalid data.'}) {
return (HookedServiceEvent e) async {
var result = await asyncApplyValidator(
validator, e.data as Map, (e.request?.app ?? e.service.app));
if (result.errors.isNotEmpty) {
throw new AngelHttpException.badRequest(
throw AngelHttpException.badRequest(
message: errorMessage, errors: result.errors);
}
@ -120,7 +120,7 @@ Future<ValidationResult> asyncApplyValidator(
for (var key in result.data.keys) {
var value = result.data[key];
var description = new StringDescription("'$key': expected ");
var description = StringDescription("'$key': expected ");
for (var rule in validator.rules[key]) {
if (rule is AngelMatcher) {
@ -135,7 +135,7 @@ Future<ValidationResult> asyncApplyValidator(
}
}
var m = new Map<String, dynamic>.from(result.data);
var m = Map<String, dynamic>.from(result.data);
for (var key in errantKeys) {
m.remove(key);
}

View file

@ -11,7 +11,7 @@ import 'context_aware.dart';
AngelMatcher predicateWithAngel(
FutureOr<bool> Function(String, Object, Angel) f,
[String description = 'satisfies function']) =>
new _PredicateWithAngel(f, description);
_PredicateWithAngel(f, description);
/// Returns an [AngelMatcher] that applies an asynchronously-created [Matcher]
/// to the input.
@ -19,7 +19,7 @@ AngelMatcher predicateWithAngel(
/// Use this to match values against configuration, injections, etc.
AngelMatcher matchWithAngel(FutureOr<Matcher> Function(Object, Map, Angel) f,
[String description = 'satisfies asynchronously created matcher']) =>
new _MatchWithAngel(f, description);
_MatchWithAngel(f, description);
/// Calls [matchWithAngel] without the initial parameter.
AngelMatcher matchWithAngelBinary(
@ -42,7 +42,7 @@ AngelMatcher matchWithAngelNullary(FutureOr<Matcher> Function() f,
/// If [x] is an [AngelMatcher], then it is returned, unmodified.
AngelMatcher wrapAngelMatcher(x) {
if (x is AngelMatcher) return x;
if (x is ContextAwareMatcher) return new _WrappedAngelMatcher(x);
if (x is ContextAwareMatcher) return _WrappedAngelMatcher(x);
return wrapAngelMatcher(wrapContextAwareMatcher(x));
}
@ -50,13 +50,13 @@ AngelMatcher wrapAngelMatcher(x) {
AngelMatcher matchAsync(FutureOr<Matcher> Function(String, Object) matcher,
FutureOr Function() feature,
[String description = 'satisfies asynchronously created matcher']) {
return new _MatchAsync(matcher, feature, description);
return _MatchAsync(matcher, feature, description);
}
/// Returns an [AngelMatcher] that verifies that an item with the given [idField]
/// exists in the service at [servicePath], without throwing a `404` or returning `null`.
AngelMatcher idExistsInService(String servicePath,
{String idField: 'id', String description}) {
{String idField = 'id', String description}) {
return predicateWithAngel(
(key, item, app) async {
try {
@ -115,7 +115,7 @@ class _MatchWithAngel extends AngelMatcher {
@override
Future<bool> matchesWithAngel(
item, String key, Map context, Map matchState, Angel app) {
return new Future.sync(() => f(item, context, app)).then((result) {
return Future.sync(() => f(item, context, app)).then((result) {
return result.matches(item, matchState);
});
}
@ -135,7 +135,7 @@ class _PredicateWithAngel extends AngelMatcher {
@override
Future<bool> matchesWithAngel(
item, String key, Map context, Map matchState, Angel app) {
return new Future<bool>.sync(() => predicate(key, item, app));
return Future<bool>.sync(() => predicate(key, item, app));
}
}

View file

@ -4,14 +4,14 @@ import 'package:matcher/matcher.dart';
ContextAwareMatcher predicateWithContext(
bool Function(Object, String, Map, Map) f,
[String description = 'satisfies function']) {
return new _PredicateWithContext(f, description);
return _PredicateWithContext(f, description);
}
/// Wraps [x] in a [ContextAwareMatcher].
ContextAwareMatcher wrapContextAwareMatcher(x) {
if (x is ContextAwareMatcher)
if (x is ContextAwareMatcher) {
return x;
else if (x is Matcher) return new _WrappedContextAwareMatcher(x);
} else if (x is Matcher) return _WrappedContextAwareMatcher(x);
return wrapContextAwareMatcher(wrapMatcher(x));
}

View file

@ -2,11 +2,11 @@ import 'package:matcher/matcher.dart';
import 'context_aware.dart';
import 'context_validator.dart';
final RegExp _alphaDash = new RegExp(r'^[A-Za-z0-9_-]+$');
final RegExp _alphaNum = new RegExp(r'^[A-Za-z0-9]+$');
final RegExp _email = new RegExp(
final RegExp _alphaDash = RegExp(r'^[A-Za-z0-9_-]+$');
final RegExp _alphaNum = RegExp(r'^[A-Za-z0-9]+$');
final RegExp _email = RegExp(
r"^[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$");
final RegExp _url = new RegExp(
final RegExp _url = RegExp(
r'https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)');
/// Asserts that a `String` is alphanumeric, but also lets it contain dashes or underscores.
@ -126,8 +126,8 @@ ContextValidator requiredWithoutAll(Iterable<String> keys) =>
_require((ctx) => !keys.any(ctx.containsKey));
ContextValidator _require(bool Function(Map) f) {
return new ContextValidator(
return ContextValidator(
(key, context) => f(context) && context.containsKey(key),
(desc, key, _) => new StringDescription('Missing required field "$key".'),
(desc, key, _) => StringDescription('Missing required field "$key".'),
);
}

View file

@ -3,9 +3,9 @@ import 'package:matcher/matcher.dart';
import 'context_aware.dart';
import 'context_validator.dart';
final RegExp _asterisk = new RegExp(r'\*$');
final RegExp _forbidden = new RegExp(r'!$');
final RegExp _optional = new RegExp(r'\?$');
final RegExp _asterisk = RegExp(r'\*$');
final RegExp _forbidden = RegExp(r'!$');
final RegExp _optional = RegExp(r'\?$');
/// Returns a value based the result of a computation.
typedef DefaultValueFunction();
@ -84,11 +84,12 @@ class Validator extends Matcher {
var iterable = [];
_addTo(x) {
if (x is Iterable)
if (x is Iterable) {
x.forEach(_addTo);
else
} else {
iterable.add(x);
}
}
_iterable.forEach(_addTo);
@ -108,8 +109,8 @@ class Validator extends Matcher {
Validator.empty();
Validator(Map<String, dynamic> schema,
{Map<String, dynamic> defaultValues: const {},
Map<String, dynamic> customErrorMessages: const {}}) {
{Map<String, dynamic> defaultValues = const {},
Map<String, dynamic> customErrorMessages = const {}}) {
this.defaultValues.addAll(defaultValues ?? {});
this.customErrorMessages.addAll(customErrorMessages ?? {});
_importSchema(schema);
@ -121,7 +122,7 @@ class Validator extends Matcher {
/// Validates, and filters input data.
ValidationResult check(Map inputData) {
List<String> errors = [];
var input = new Map.from(inputData);
var input = Map.from(inputData);
Map<String, dynamic> data = {};
for (String key in defaultValues.keys) {
@ -133,23 +134,25 @@ class Validator extends Matcher {
for (String field in forbiddenFields) {
if (input.containsKey(field)) {
if (!customErrorMessages.containsKey(field))
if (!customErrorMessages.containsKey(field)) {
errors.add("'$field' is forbidden.");
else
} else {
errors.add(customError(field, input[field]));
}
}
}
for (String field in requiredFields) {
if (!_hasContextValidators(rules[field] ?? [])) {
if (!input.containsKey(field)) {
if (!customErrorMessages.containsKey(field))
if (!customErrorMessages.containsKey(field)) {
errors.add("'$field' is required.");
else
} else {
errors.add(customError(field, 'none'));
}
}
}
}
// Run context validators.
@ -157,7 +160,7 @@ class Validator extends Matcher {
if (key is String && rules.containsKey(key)) {
var valid = true;
var value = input[key];
var description = new StringDescription("'$key': expected ");
var description = StringDescription("'$key': expected ");
for (var matcher in rules[key]) {
if (matcher is ContextValidator) {
@ -192,8 +195,9 @@ class Validator extends Matcher {
}
if (!result) {
if (!customErrorMessages.containsKey(key))
if (!customErrorMessages.containsKey(key)) {
errors.add(matcher.describe(description).toString().trim());
}
valid = false;
break;
}
@ -215,10 +219,10 @@ class Validator extends Matcher {
}
if (errors.isNotEmpty) {
return new ValidationResult().._errors.addAll(errors);
return ValidationResult().._errors.addAll(errors);
}
return new ValidationResult().._data.addAll(data);
return ValidationResult().._data.addAll(data);
}
/// Validates, and filters input data after running [autoParse].
@ -227,29 +231,30 @@ class Validator extends Matcher {
/// Renders the given custom error.
String customError(String key, value) {
if (!customErrorMessages.containsKey(key))
throw new ArgumentError("No custom error message registered for '$key'.");
if (!customErrorMessages.containsKey(key)) {
throw ArgumentError("No custom error message registered for '$key'.");
}
var msg = customErrorMessages[key];
if (msg is String)
if (msg is String) {
return msg.replaceAll('{{value}}', value.toString());
else if (msg is CustomErrorMessageFunction) {
} else if (msg is CustomErrorMessageFunction) {
return msg(value);
}
throw new ArgumentError("Invalid custom error message '$key': $msg");
throw ArgumentError("Invalid custom error message '$key': $msg");
}
/// Validates input data, and throws an error if it is invalid.
///
/// Otherwise, the filtered data is returned.
Map<String, dynamic> enforce(Map inputData,
{String errorMessage: 'Invalid data.'}) {
{String errorMessage = 'Invalid data.'}) {
var result = check(inputData);
if (result._errors.isNotEmpty) {
throw new ValidationException(errorMessage, errors: result._errors);
throw ValidationException(errorMessage, errors: result._errors);
}
return result.data;
@ -263,11 +268,11 @@ class Validator extends Matcher {
/// Creates a copy with additional validation rules.
Validator extend(Map<String, dynamic> schema,
{Map<String, dynamic> defaultValues: const {},
Map<String, dynamic> customErrorMessages: const {},
bool overwrite: false}) {
{Map<String, dynamic> defaultValues = const {},
Map<String, dynamic> customErrorMessages = const {},
bool overwrite = false}) {
Map<String, dynamic> _schema = {};
var child = new Validator.empty()
var child = Validator.empty()
..defaultValues.addAll(this.defaultValues)
..defaultValues.addAll(defaultValues ?? {})
..customErrorMessages.addAll(this.customErrorMessages)
@ -355,18 +360,18 @@ class ValidationResult {
final List<String> _errors = [];
/// The successfully validated data, filtered from the original input.
Map<String, dynamic> get data => new Map<String, dynamic>.unmodifiable(_data);
Map<String, dynamic> get data => Map<String, dynamic>.unmodifiable(_data);
/// A list of errors that resulted in the given data being marked invalid.
///
/// This is empty if validation was successful.
List<String> get errors => new List<String>.unmodifiable(_errors);
List<String> get errors => List<String>.unmodifiable(_errors);
ValidationResult withData(Map<String, dynamic> data) =>
new ValidationResult().._data.addAll(data).._errors.addAll(_errors);
ValidationResult().._data.addAll(data).._errors.addAll(_errors);
ValidationResult withErrors(Iterable<String> errors) =>
new ValidationResult().._data.addAll(_data).._errors.addAll(errors);
ValidationResult().._data.addAll(_data).._errors.addAll(errors);
}
/// Occurs when user-provided data is invalid.
@ -377,8 +382,8 @@ class ValidationException extends AngelHttpException {
/// A descriptive message describing the error.
final String message;
ValidationException(this.message, {List<String> errors: const []})
: super(new FormatException(message),
ValidationException(this.message, {List<String> errors = const []})
: super(FormatException(message),
statusCode: 400,
errors: errors ?? [],
stackTrace: StackTrace.current) {

View file

@ -15,4 +15,5 @@ dev_dependencies:
build_web_compilers: ^0.4.0
logging: ^0.11.0
mock_request:
pedantic: ^1.0.0
test: ^1.0.0

View file

@ -1,10 +1,10 @@
import 'package:angel_validate/angel_validate.dart';
import 'package:test/test.dart';
final Validator emailSchema = new Validator({'to': isEmail},
customErrorMessages: {'to': 'Hello, world!'});
final Validator emailSchema =
Validator({'to': isEmail}, customErrorMessages: {'to': 'Hello, world!'});
final Validator todoSchema = new Validator({
final Validator todoSchema = Validator({
'id': [isInt, isPositive],
'text*': isString,
'completed*': isBool,
@ -32,7 +32,7 @@ main() {
todoSchema
.enforce({'id': 'fool', 'text': 'Hello, world!', 'completed': 4});
// ignore: deprecated_member_use
}, throwsA(new isInstanceOf<ValidationException>()));
}, throwsA(isInstanceOf<ValidationException>()));
});
test('filter', () {

View file

@ -1,3 +1,4 @@
import 'dart:async';
import 'dart:convert';
import 'package:angel_framework/angel_framework.dart';
import 'package:angel_framework/http.dart';
@ -7,7 +8,7 @@ import 'package:logging/logging.dart';
import 'package:mock_request/mock_request.dart';
import 'package:test/test.dart';
final Validator echoSchema = new Validator({'message*': isString});
final Validator echoSchema = Validator({'message*': isString});
void printRecord(LogRecord rec) {
print(rec);
@ -21,8 +22,8 @@ main() {
TestClient client;
setUp(() async {
app = new Angel();
http = new AngelHttp(app, useZone: false);
app = Angel();
http = AngelHttp(app, useZone: false);
app.chain([validate(echoSchema)]).post('/echo',
(RequestContext req, res) async {
@ -30,7 +31,7 @@ main() {
res.write('Hello, ${req.bodyAsMap['message']}!');
});
app.logger = new Logger('angel')..onRecord.listen(printRecord);
app.logger = Logger('angel')..onRecord.listen(printRecord);
client = await connectTo(app);
});
@ -51,12 +52,15 @@ main() {
});
test('enforce', () async {
var rq = new MockHttpRequest('POST', new Uri(path: '/echo'))
var rq = MockHttpRequest('POST', Uri(path: '/echo'))
..headers.add('accept', '*/*')
..headers.add('content-type', 'application/json')
..write(json.encode({'foo': 'bar'}))
..close();
http.handleRequest(rq);
..write(json.encode({'foo': 'bar'}));
scheduleMicrotask(() async {
await rq.close();
await http.handleRequest(rq);
});
var responseBody = await rq.response.transform(utf8.decoder).join();
print('Response: ${responseBody}');

View file

@ -5,7 +5,7 @@ final $errors = querySelector('#errors') as UListElement;
final $form = querySelector('#form') as FormElement;
final $blank = querySelector('[name="blank"]') as InputElement;
final Validator formSchema = new Validator({
final Validator formSchema = Validator({
'firstName*': [isString, isNotEmpty],
'lastName*': [isString, isNotEmpty],
'age*': [isInt, greaterThanOrEqualTo(18)],
@ -15,12 +15,13 @@ final Validator formSchema = new Validator({
'familySize': 1
}, customErrorMessages: {
'age': (age) {
if (age is int && age < 18)
if (age is int && age < 18) {
return 'Only adults can register for passports. Sorry, kid!';
else if (age == null || (age is String && age.trim().isEmpty))
} else if (age == null || (age is String && age.trim().isEmpty)) {
return 'Age is required.';
else
} else {
return 'Age must be a positive integer. Unless you are a monster...';
}
},
'blank':
"I told you to leave that field blank, but instead you typed '{{value}}'..."
@ -54,12 +55,12 @@ main() {
'Number of People in Family: ${passportInfo["familySize"]}'));
} on ValidationException catch (e) {
$errors.children.addAll(e.errors.map((error) {
return new LIElement()..text = error;
return LIElement()..text = error;
}));
}
});
}
LIElement success(String str) => new LIElement()
LIElement success(String str) => LIElement()
..classes.add('success')
..text = str;