2024-10-12 10:39:20 +00:00
|
|
|
/// Support for using `angel_validate` with the Protevus Framework.
|
2024-10-13 01:45:27 +00:00
|
|
|
library protevus_validate.server;
|
2021-02-21 02:47:23 +00:00
|
|
|
|
|
|
|
import 'dart:async';
|
|
|
|
|
2024-10-13 01:45:27 +00:00
|
|
|
import 'package:protevus_framework/protevus_framework.dart';
|
2021-02-21 02:47:23 +00:00
|
|
|
import 'src/async.dart';
|
2024-10-13 01:45:27 +00:00
|
|
|
import 'protevus_validate.dart';
|
2021-02-21 02:47:23 +00:00
|
|
|
export 'src/async.dart';
|
2024-10-13 01:45:27 +00:00
|
|
|
export 'protevus_validate.dart';
|
2021-02-21 02:47:23 +00:00
|
|
|
|
|
|
|
/// Auto-parses numbers in `req.bodyAsMap`.
|
|
|
|
RequestHandler autoParseBody(List<String> fields) {
|
|
|
|
return (RequestContext req, res) async {
|
|
|
|
await req.parseBody();
|
|
|
|
req.bodyAsMap.addAll(autoParse(req.bodyAsMap, fields));
|
|
|
|
return true;
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Auto-parses numbers in `req.queryParameters`.
|
|
|
|
RequestHandler autoParseQuery(List<String> fields) {
|
|
|
|
return (RequestContext req, res) async {
|
|
|
|
req.queryParameters.addAll(autoParse(req.queryParameters, fields));
|
|
|
|
return true;
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Filters unwanted data out of `req.bodyAsMap`.
|
|
|
|
RequestHandler filterBody(Iterable<String> only) {
|
|
|
|
return (RequestContext req, res) async {
|
|
|
|
await req.parseBody();
|
|
|
|
var filtered = filter(req.bodyAsMap, only);
|
|
|
|
req.bodyAsMap
|
|
|
|
..clear()
|
|
|
|
..addAll(filtered);
|
|
|
|
return true;
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Filters unwanted data out of `req.queryParameters`.
|
|
|
|
RequestHandler filterQuery(Iterable<String> only) {
|
|
|
|
return (RequestContext req, res) async {
|
|
|
|
var filtered = filter(req.queryParameters, only);
|
|
|
|
req.queryParameters
|
|
|
|
..clear()
|
|
|
|
..addAll(filtered);
|
|
|
|
return true;
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
/// 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.'}) {
|
|
|
|
return (RequestContext req, res) async {
|
|
|
|
await req.parseBody();
|
2021-04-10 12:42:55 +00:00
|
|
|
var app = req.app;
|
|
|
|
if (app != null) {
|
|
|
|
var result = await asyncApplyValidator(validator, req.bodyAsMap, app);
|
2021-02-21 02:47:23 +00:00
|
|
|
|
2021-04-10 12:42:55 +00:00
|
|
|
if (result.errors.isNotEmpty) {
|
2024-10-12 10:35:14 +00:00
|
|
|
throw ProtevusHttpException.badRequest(
|
2021-04-10 12:42:55 +00:00
|
|
|
message: errorMessage, errors: result.errors);
|
|
|
|
}
|
2021-02-21 02:47:23 +00:00
|
|
|
|
2021-04-10 12:42:55 +00:00
|
|
|
req.bodyAsMap
|
|
|
|
..clear()
|
|
|
|
..addAll(result.data);
|
|
|
|
}
|
2021-02-21 02:47:23 +00:00
|
|
|
return true;
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
/// 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.'}) {
|
|
|
|
return (RequestContext req, res) async {
|
2021-04-10 12:42:55 +00:00
|
|
|
var app = req.app;
|
|
|
|
if (app != null) {
|
|
|
|
var result =
|
|
|
|
await asyncApplyValidator(validator, req.queryParameters, app);
|
|
|
|
|
|
|
|
if (result.errors.isNotEmpty) {
|
2024-10-12 10:35:14 +00:00
|
|
|
throw ProtevusHttpException.badRequest(
|
2021-04-10 12:42:55 +00:00
|
|
|
message: errorMessage, errors: result.errors);
|
|
|
|
}
|
2021-02-21 02:47:23 +00:00
|
|
|
|
2021-04-10 12:42:55 +00:00
|
|
|
req.queryParameters
|
|
|
|
..clear()
|
|
|
|
..addAll(result.data);
|
2021-02-21 02:47:23 +00:00
|
|
|
}
|
|
|
|
return true;
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
/// 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.'}) {
|
|
|
|
return (HookedServiceEvent e) async {
|
2021-04-10 12:42:55 +00:00
|
|
|
var app = e.request?.app ?? e.service.app;
|
2022-08-17 12:15:23 +00:00
|
|
|
var result = await asyncApplyValidator(validator, e.data as Map, app);
|
2021-02-21 02:47:23 +00:00
|
|
|
|
2022-08-17 12:15:23 +00:00
|
|
|
if (result.errors.isNotEmpty) {
|
2024-10-12 10:35:14 +00:00
|
|
|
throw ProtevusHttpException.badRequest(
|
2022-08-17 12:15:23 +00:00
|
|
|
message: errorMessage, errors: result.errors);
|
2021-04-10 12:42:55 +00:00
|
|
|
}
|
2022-08-17 12:15:23 +00:00
|
|
|
|
|
|
|
e.data
|
|
|
|
..clear()
|
|
|
|
..addAll(result.data);
|
2021-02-21 02:47:23 +00:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Asynchronously apply a [validator], running any [AngelMatcher]s.
|
|
|
|
Future<ValidationResult> asyncApplyValidator(
|
2024-10-12 10:35:14 +00:00
|
|
|
Validator validator, Map data, Protevus app) async {
|
2021-02-21 02:47:23 +00:00
|
|
|
var result = validator.check(data);
|
|
|
|
if (result.errors.isNotEmpty) return result;
|
|
|
|
|
|
|
|
var errantKeys = <String>[], errors = <String>[];
|
|
|
|
|
|
|
|
for (var key in result.data.keys) {
|
|
|
|
var value = result.data[key];
|
|
|
|
var description = StringDescription("'$key': expected ");
|
|
|
|
|
2021-04-10 12:42:55 +00:00
|
|
|
for (var rule in validator.rules[key]!) {
|
2021-02-21 02:47:23 +00:00
|
|
|
if (rule is AngelMatcher) {
|
|
|
|
var r = await rule.matchesWithAngel(value, key, result.data, {}, app);
|
|
|
|
|
|
|
|
if (!r) {
|
|
|
|
errors.add(rule.describe(description).toString().trim());
|
|
|
|
errantKeys.add(key);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
var m = Map<String, dynamic>.from(result.data);
|
|
|
|
for (var key in errantKeys) {
|
|
|
|
m.remove(key);
|
|
|
|
}
|
|
|
|
|
|
|
|
return result.withData(m).withErrors(errors);
|
|
|
|
}
|