validate: Rework Field.read to omit RequestContext

This commit is contained in:
Tobe O 2020-05-04 12:35:17 -04:00
parent 76fe71a474
commit 104b8da314
4 changed files with 93 additions and 19 deletions

View file

@ -1,8 +1,10 @@
import 'dart:async';
import 'dart:convert';
import 'package:angel_framework/angel_framework.dart';
import 'package:http_parser/http_parser.dart';
import 'package:image/image.dart';
import 'field.dart';
import 'form.dart';
import 'form_renderer.dart';
/// A [Field] that accepts plain text.
@ -38,7 +40,7 @@ class TextField extends Field<String> {
}
@override
FutureOr<FieldReadResult<String>> read(RequestContext req,
FutureOr<FieldReadResult<String>> read(
Map<String, dynamic> fields, Iterable<UploadedFile> files) {
var value = _normalize(fields[name] as String);
if (value == null) {
@ -71,7 +73,7 @@ class BoolField extends Field<bool> {
renderer.visitBoolField(this);
@override
FutureOr<FieldReadResult<bool>> read(RequestContext req,
FutureOr<FieldReadResult<bool>> read(
Map<String, dynamic> fields, Iterable<UploadedFile> files) {
if (fields.containsKey(name)) {
return FieldReadResult.success(true);
@ -108,9 +110,9 @@ class NumField<T extends num> extends Field<T> {
renderer.visitNumField(this);
@override
Future<FieldReadResult<T>> read(RequestContext req,
Future<FieldReadResult<T>> read(
Map<String, dynamic> fields, Iterable<UploadedFile> files) async {
var result = await _textField.read(req, fields, files);
var result = await _textField.read(fields, files);
if (result == null) {
return null;
} else if (result.isSuccess != true) {
@ -151,9 +153,9 @@ class DoubleField extends NumField<double> {
max: max);
@override
Future<FieldReadResult<double>> read(RequestContext req,
Future<FieldReadResult<double>> read(
Map<String, dynamic> fields, Iterable<UploadedFile> files) async {
var result = await super.read(req, fields, files);
var result = await super.read(fields, files);
if (result == null) {
return null;
} else if (!result.isSuccess) {
@ -183,9 +185,9 @@ class IntField extends NumField<int> {
max: max);
@override
Future<FieldReadResult<int>> read(RequestContext req,
Future<FieldReadResult<int>> read(
Map<String, dynamic> fields, Iterable<UploadedFile> files) async {
var result = await super.read(req, fields, files);
var result = await super.read(fields, files);
if (result == null) {
return null;
} else if (!result.isSuccess) {
@ -228,9 +230,9 @@ class DateTimeField extends Field<DateTime> {
renderer.visitDateTimeField(this);
@override
Future<FieldReadResult<DateTime>> read(RequestContext req,
Future<FieldReadResult<DateTime>> read(
Map<String, dynamic> fields, Iterable<UploadedFile> files) async {
var result = await _textField.read(req, fields, files);
var result = await _textField.read(fields, files);
if (result == null) {
return null;
} else if (result.isSuccess != true) {
@ -274,7 +276,7 @@ class FileField extends Field<UploadedFile> {
renderer.visitFileField(this);
@override
FutureOr<FieldReadResult<UploadedFile>> read(RequestContext req,
FutureOr<FieldReadResult<UploadedFile>> read(
Map<String, dynamic> fields, Iterable<UploadedFile> files) {
var file = files.firstWhere((f) => f.name == name, orElse: () => null);
if (file == null) {
@ -328,9 +330,9 @@ class ImageField extends Field<Image> {
renderer.visitImageField(this);
@override
FutureOr<FieldReadResult<Image>> read(RequestContext req,
FutureOr<FieldReadResult<Image>> read(
Map<String, dynamic> fields, Iterable<UploadedFile> files) async {
var result = await fileField.read(req, fields, files);
var result = await fileField.read(fields, files);
if (result == null) {
return null;
} else if (!result.isSuccess) {
@ -350,3 +352,44 @@ class ImageField extends Field<Image> {
}
}
}
class MapField extends Field<Map<String, dynamic>> {
@override
final String name;
final Form form;
MapField(this.name, this.form, {String label, bool isRequired = true})
: super(name, 'text', label: label, isRequired: isRequired);
@override
FutureOr<U> accept<U>(FormRenderer<U> renderer) =>
renderer.visitMapField(this);
@override
FutureOr<FieldReadResult<Map<String, dynamic>>> read(
Map<String, dynamic> fields, Iterable<UploadedFile> files) {
var value = fields[name];
Map mapValue;
// Value can be either a Map, or a JSON-encoded Map.
if (value == null) {
return null;
} else if (value is Map) {
mapValue = value;
} else if (value is String) {
var decoded = json.decode(value);
if (decoded is Map) {
mapValue = decoded;
} else {
return FieldReadResult.failure([
'If "$name" is given as a string, then it must produce a map/dict/object when decoded as JSON.'
]);
}
} else {
return FieldReadResult.failure(
['"$name" must be either a map/dict/object, or a string.']);
}
}
}

View file

@ -47,7 +47,7 @@ abstract class Field<T> {
///
/// If it returns `null` and [isRequired] is `true`, an error must
/// be generated.
FutureOr<FieldReadResult<T>> read(RequestContext req,
FutureOr<FieldReadResult<T>> read(
Map<String, dynamic> fields, Iterable<UploadedFile> files);
/// Accepts a form renderer.
@ -72,8 +72,8 @@ abstract class Field<T> {
await req.parseBody();
uploadedFiles = req.uploadedFiles;
}
var result = await read(
req, query ? req.queryParameters : req.bodyAsMap, uploadedFiles);
var result =
await read(query ? req.queryParameters : req.bodyAsMap, uploadedFiles);
if (result?.isSuccess != true && defaultValue != null) {
return defaultValue;
} else if (result == null) {
@ -103,9 +103,9 @@ class _MatchedField<T> extends Field<T> {
FutureOr<U> accept<U>(FormRenderer<U> renderer) => inner.accept(renderer);
@override
Future<FieldReadResult<T>> read(RequestContext req,
Future<FieldReadResult<T>> read(
Map<String, dynamic> fields, Iterable<UploadedFile> files) async {
var result = await inner.read(req, fields, files);
var result = await inner.read(fields, files);
if (!result.isSuccess) {
return result;
} else {

View file

@ -109,7 +109,36 @@ class Form {
for (var field in fields) {
var result = await field.read(
req, query ? req.queryParameters : req.bodyAsMap, uploadedFiles);
query ? req.queryParameters : req.bodyAsMap, uploadedFiles);
if (result == null && field.isRequired) {
errors.add(reportMissingField(field.name, query: query));
} else if (!result.isSuccess) {
errors.addAll(result.errors);
} else {
out[field.name] = result.value;
}
}
if (errors.isNotEmpty) {
return FieldReadResult.failure(errors);
} else {
return FieldReadResult.success(out);
}
}
/// Same as [read], but reads data directly from a [map].
///
/// If [query] is `true`, resulting error messages will be generated as though
/// the field were being read from a map of query parameters.
Future<FieldReadResult<Map<String, dynamic>>> readFromMap(
Map<String, dynamic> map,
{bool query = false}) async {
var out = <String, dynamic>{};
var errors = <String>[];
var uploadedFiles = <UploadedFile>[];
for (var field in fields) {
var result = await field.read(map, uploadedFiles);
if (result == null && field.isRequired) {
errors.add(reportMissingField(field.name, query: query));
} else if (!result.isSuccess) {

View file

@ -18,4 +18,6 @@ abstract class FormRenderer<T> {
FutureOr<T> visitNumField(NumField field);
FutureOr<T> visitTextField(TextField field);
FutureOr<T> visitMapField(MapField field);
}