This commit is contained in:
Tobe O 2018-06-27 22:34:05 -04:00
parent 53c9de3d5e
commit 1c478f0b4a
12 changed files with 70 additions and 26 deletions

View file

@ -1,3 +0,0 @@
analyzer:
strong-mode: true
exclude: ./scripts-bin/**/*.dart

4
CHANGELOG.md Normal file
View file

@ -0,0 +1,4 @@
# 1.0.4
* `isNonEmptyString` trims strings.
* `ValidationException` extends `AngelHttpException`.
* Added `requireField` and `requireFields`.

View file

@ -92,7 +92,10 @@ to throw an error if it is not present.
```dart
main() {
var validator = new Validator({
'googleId*': isString
'googleId*': isString,
// You can also use `requireField`
requireField('googleId'): isString,
});
}
```

3
analysis_options.yaml Normal file
View file

@ -0,0 +1,3 @@
analyzer:
strong-mode:
implicit-casts: false

View file

@ -4,3 +4,10 @@ library angel_validate;
export 'package:matcher/matcher.dart';
export 'src/matchers.dart';
export 'src/validator.dart';
/// Marks a field name as required.
String requireField(String field) => '$field*';
/// Marks multiple fields as required.
String requireFields(Iterable<String> fields) =>
fields.map(requireField).join(', ');

View file

@ -88,7 +88,7 @@ RequestMiddleware validateQuery(Validator validator,
HookedServiceEventListener validateEvent(Validator validator,
{String errorMessage: 'Invalid data.'}) {
return (HookedServiceEvent e) {
var result = validator.check(e.data);
var result = validator.check(e.data as Map);
if (result.errors.isNotEmpty) {
throw new AngelHttpException.badRequest(

View file

@ -34,7 +34,8 @@ final Matcher isString = predicate((value) => value is String, 'a String');
/// Asserts that a value is a non-empty `String`.
final Matcher isNonEmptyString = predicate(
(value) => value is String && value.isNotEmpty, 'a non-empty String');
(value) => value is String && value.trim().isNotEmpty,
'a non-empty String');
/// Asserts that a `String` is an `http://` or `https://` URL.
///

View file

@ -1,3 +1,4 @@
import 'package:angel_http_exception/angel_http_exception.dart';
import 'package:matcher/matcher.dart';
final RegExp _asterisk = new RegExp(r'\*$');
@ -19,13 +20,13 @@ Map<String, dynamic> autoParse(Map inputData, Iterable<String> fields) {
for (var key in inputData.keys) {
if (!fields.contains(key)) {
data[key] = inputData[key];
data[key.toString()] = inputData[key];
} else {
try {
var n = inputData[key] is num
? inputData[key]
: num.parse(inputData[key].toString());
data[key] = n == n.toInt() ? n.toInt() : n;
data[key.toString()] = n == n.toInt() ? n.toInt() : n;
} catch (e) {
// Invalid number, don't pass it
}
@ -38,7 +39,7 @@ Map<String, dynamic> autoParse(Map inputData, Iterable<String> fields) {
/// Removes undesired fields from a `Map`.
Map<String, dynamic> filter(Map inputData, Iterable<String> only) {
return inputData.keys.fold(<String, dynamic>{}, (map, key) {
if (only.contains(key)) map[key] = inputData[key];
if (only.contains(key.toString())) map[key.toString()] = inputData[key];
return map;
});
}
@ -153,7 +154,7 @@ class Validator extends Matcher {
for (Matcher matcher in rules[key]) {
try {
if (matcher is Validator) {
var result = matcher.check(value);
var result = matcher.check(value as Map);
if (result.errors.isNotEmpty) {
errors.addAll(result.errors);
@ -310,7 +311,7 @@ class Validator extends Matcher {
@override
bool matches(item, Map matchState) {
enforce(item);
enforce(item as Map);
return true;
}
@ -333,14 +334,18 @@ class ValidationResult {
}
/// Occurs when user-provided data is invalid.
class ValidationException {
class ValidationException extends AngelHttpException {
/// A list of errors that resulted in the given data being marked invalid.
final List<String> errors = [];
/// A descriptive message describing the error.
final String message;
ValidationException(this.message, {List<String> errors: const []}) {
ValidationException(this.message, {List<String> errors: const []})
: super(new FormatException(message),
statusCode: 400,
errors: errors ?? [],
stackTrace: StackTrace.current) {
if (errors != null) this.errors.addAll(errors);
}

View file

@ -1,15 +1,17 @@
name: angel_validate
description: Cross-platform validation library based on `matcher`.
version: 1.0.3
version: 1.0.4
author: Tobe O <thosakwe@gmail.com>
homepage: https://github.com/angel-dart/validate
environment:
sdk: ">=1.19.0"
sdk: ">=1.19.0 <3.0.0"
dependencies:
angel_framework: ^1.0.0-dev
angel_http_exception: ^1.0.0
matcher: ^0.12.0
dev_dependencies:
angel_diagnostics: ^1.0.0-dev
angel_test: ^1.0.0-dev
browser: ^0.10.0
dart2_constant: ^1.0.0
logging: ^0.11.0
test: ^0.12.18

View file

@ -22,6 +22,11 @@ main() {
expect(result.errors.first, equals('Hello, world!'));
});
test('requireField', () => expect(requireField('foo'), 'foo*'));
test('requireFields',
() => expect(requireFields(['foo', 'bar']), 'foo*, bar*'));
test('todo', () {
expect(() {
todoSchema

View file

@ -1,30 +1,40 @@
import 'dart:io';
import 'package:angel_diagnostics/angel_diagnostics.dart';
import 'package:angel_framework/angel_framework.dart';
import 'package:angel_test/angel_test.dart';
import 'package:angel_validate/server.dart';
import 'package:dart2_constant/convert.dart';
import 'package:logging/logging.dart';
import 'package:mock_request/mock_request.dart';
import 'package:test/test.dart';
final Validator echoSchema = new Validator({'message*': isString});
void printRecord(LogRecord rec) {
print(rec);
if (rec.error != null) print(rec.error);
if (rec.stackTrace != null) print(rec.stackTrace);
}
main() {
Angel app;
AngelHttp http;
TestClient client;
setUp(() async {
app = new Angel();
http = new AngelHttp(app, useZone: false);
app.chain(validate(echoSchema)).post('/echo',
(RequestContext req, res) async {
res.write('Hello, ${req.body['message']}!');
});
await app.configure(logRequests(new File('log.txt')));
app.logger = new Logger('angel')..onRecord.listen(printRecord);
client = await connectTo(app);
});
tearDown(() async {
await client.close();
await http.close();
app = null;
client = null;
});
@ -32,17 +42,23 @@ main() {
group('echo', () {
test('validate', () async {
var response = await client.post('/echo',
body: {'message': 'world'}, headers: {HttpHeaders.ACCEPT: '*/*'});
body: {'message': 'world'}, headers: {'accept': '*/*'});
print('Response: ${response.body}');
expect(response, hasStatus(HttpStatus.OK));
expect(response, hasStatus(200));
expect(response.body, equals('Hello, world!'));
});
test('enforce', () async {
var response = await client.post('/echo',
body: {'foo': 'bar'}, headers: {HttpHeaders.ACCEPT: '*/*'});
print('Response: ${response.body}');
expect(response, hasStatus(HttpStatus.BAD_REQUEST));
var rq = new MockHttpRequest('POST', new Uri(path: '/echo'))
..headers.add('accept', '*/*')
..headers.add('content-type', 'application/json')
..write(json.encode({'foo': 'bar'}))
..close();
http.handleRequest(rq);
var responseBody = await rq.response.transform(utf8.decoder).join();
print('Response: ${responseBody}');
expect(rq.response.statusCode, 400);
});
});
}

View file

@ -3,6 +3,7 @@
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/.dart_tool" />
<excludeFolder url="file://$MODULE_DIR$/.pub" />
<excludeFolder url="file://$MODULE_DIR$/build" />
</content>