validate: Rework Field.read to omit RequestContext
This commit is contained in:
parent
76fe71a474
commit
104b8da314
4 changed files with 93 additions and 19 deletions
|
@ -1,8 +1,10 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
import 'dart:convert';
|
||||||
import 'package:angel_framework/angel_framework.dart';
|
import 'package:angel_framework/angel_framework.dart';
|
||||||
import 'package:http_parser/http_parser.dart';
|
import 'package:http_parser/http_parser.dart';
|
||||||
import 'package:image/image.dart';
|
import 'package:image/image.dart';
|
||||||
import 'field.dart';
|
import 'field.dart';
|
||||||
|
import 'form.dart';
|
||||||
import 'form_renderer.dart';
|
import 'form_renderer.dart';
|
||||||
|
|
||||||
/// A [Field] that accepts plain text.
|
/// A [Field] that accepts plain text.
|
||||||
|
@ -38,7 +40,7 @@ class TextField extends Field<String> {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
FutureOr<FieldReadResult<String>> read(RequestContext req,
|
FutureOr<FieldReadResult<String>> read(
|
||||||
Map<String, dynamic> fields, Iterable<UploadedFile> files) {
|
Map<String, dynamic> fields, Iterable<UploadedFile> files) {
|
||||||
var value = _normalize(fields[name] as String);
|
var value = _normalize(fields[name] as String);
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
|
@ -71,7 +73,7 @@ class BoolField extends Field<bool> {
|
||||||
renderer.visitBoolField(this);
|
renderer.visitBoolField(this);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
FutureOr<FieldReadResult<bool>> read(RequestContext req,
|
FutureOr<FieldReadResult<bool>> read(
|
||||||
Map<String, dynamic> fields, Iterable<UploadedFile> files) {
|
Map<String, dynamic> fields, Iterable<UploadedFile> files) {
|
||||||
if (fields.containsKey(name)) {
|
if (fields.containsKey(name)) {
|
||||||
return FieldReadResult.success(true);
|
return FieldReadResult.success(true);
|
||||||
|
@ -108,9 +110,9 @@ class NumField<T extends num> extends Field<T> {
|
||||||
renderer.visitNumField(this);
|
renderer.visitNumField(this);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<FieldReadResult<T>> read(RequestContext req,
|
Future<FieldReadResult<T>> read(
|
||||||
Map<String, dynamic> fields, Iterable<UploadedFile> files) async {
|
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) {
|
if (result == null) {
|
||||||
return null;
|
return null;
|
||||||
} else if (result.isSuccess != true) {
|
} else if (result.isSuccess != true) {
|
||||||
|
@ -151,9 +153,9 @@ class DoubleField extends NumField<double> {
|
||||||
max: max);
|
max: max);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<FieldReadResult<double>> read(RequestContext req,
|
Future<FieldReadResult<double>> read(
|
||||||
Map<String, dynamic> fields, Iterable<UploadedFile> files) async {
|
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) {
|
if (result == null) {
|
||||||
return null;
|
return null;
|
||||||
} else if (!result.isSuccess) {
|
} else if (!result.isSuccess) {
|
||||||
|
@ -183,9 +185,9 @@ class IntField extends NumField<int> {
|
||||||
max: max);
|
max: max);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<FieldReadResult<int>> read(RequestContext req,
|
Future<FieldReadResult<int>> read(
|
||||||
Map<String, dynamic> fields, Iterable<UploadedFile> files) async {
|
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) {
|
if (result == null) {
|
||||||
return null;
|
return null;
|
||||||
} else if (!result.isSuccess) {
|
} else if (!result.isSuccess) {
|
||||||
|
@ -228,9 +230,9 @@ class DateTimeField extends Field<DateTime> {
|
||||||
renderer.visitDateTimeField(this);
|
renderer.visitDateTimeField(this);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<FieldReadResult<DateTime>> read(RequestContext req,
|
Future<FieldReadResult<DateTime>> read(
|
||||||
Map<String, dynamic> fields, Iterable<UploadedFile> files) async {
|
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) {
|
if (result == null) {
|
||||||
return null;
|
return null;
|
||||||
} else if (result.isSuccess != true) {
|
} else if (result.isSuccess != true) {
|
||||||
|
@ -274,7 +276,7 @@ class FileField extends Field<UploadedFile> {
|
||||||
renderer.visitFileField(this);
|
renderer.visitFileField(this);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
FutureOr<FieldReadResult<UploadedFile>> read(RequestContext req,
|
FutureOr<FieldReadResult<UploadedFile>> read(
|
||||||
Map<String, dynamic> fields, Iterable<UploadedFile> files) {
|
Map<String, dynamic> fields, Iterable<UploadedFile> files) {
|
||||||
var file = files.firstWhere((f) => f.name == name, orElse: () => null);
|
var file = files.firstWhere((f) => f.name == name, orElse: () => null);
|
||||||
if (file == null) {
|
if (file == null) {
|
||||||
|
@ -328,9 +330,9 @@ class ImageField extends Field<Image> {
|
||||||
renderer.visitImageField(this);
|
renderer.visitImageField(this);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
FutureOr<FieldReadResult<Image>> read(RequestContext req,
|
FutureOr<FieldReadResult<Image>> read(
|
||||||
Map<String, dynamic> fields, Iterable<UploadedFile> files) async {
|
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) {
|
if (result == null) {
|
||||||
return null;
|
return null;
|
||||||
} else if (!result.isSuccess) {
|
} 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.']);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -47,7 +47,7 @@ abstract class Field<T> {
|
||||||
///
|
///
|
||||||
/// If it returns `null` and [isRequired] is `true`, an error must
|
/// If it returns `null` and [isRequired] is `true`, an error must
|
||||||
/// be generated.
|
/// be generated.
|
||||||
FutureOr<FieldReadResult<T>> read(RequestContext req,
|
FutureOr<FieldReadResult<T>> read(
|
||||||
Map<String, dynamic> fields, Iterable<UploadedFile> files);
|
Map<String, dynamic> fields, Iterable<UploadedFile> files);
|
||||||
|
|
||||||
/// Accepts a form renderer.
|
/// Accepts a form renderer.
|
||||||
|
@ -72,8 +72,8 @@ abstract class Field<T> {
|
||||||
await req.parseBody();
|
await req.parseBody();
|
||||||
uploadedFiles = req.uploadedFiles;
|
uploadedFiles = req.uploadedFiles;
|
||||||
}
|
}
|
||||||
var result = await read(
|
var result =
|
||||||
req, query ? req.queryParameters : req.bodyAsMap, uploadedFiles);
|
await read(query ? req.queryParameters : req.bodyAsMap, uploadedFiles);
|
||||||
if (result?.isSuccess != true && defaultValue != null) {
|
if (result?.isSuccess != true && defaultValue != null) {
|
||||||
return defaultValue;
|
return defaultValue;
|
||||||
} else if (result == null) {
|
} else if (result == null) {
|
||||||
|
@ -103,9 +103,9 @@ class _MatchedField<T> extends Field<T> {
|
||||||
FutureOr<U> accept<U>(FormRenderer<U> renderer) => inner.accept(renderer);
|
FutureOr<U> accept<U>(FormRenderer<U> renderer) => inner.accept(renderer);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<FieldReadResult<T>> read(RequestContext req,
|
Future<FieldReadResult<T>> read(
|
||||||
Map<String, dynamic> fields, Iterable<UploadedFile> files) async {
|
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) {
|
if (!result.isSuccess) {
|
||||||
return result;
|
return result;
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -109,7 +109,36 @@ class Form {
|
||||||
|
|
||||||
for (var field in fields) {
|
for (var field in fields) {
|
||||||
var result = await field.read(
|
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) {
|
if (result == null && field.isRequired) {
|
||||||
errors.add(reportMissingField(field.name, query: query));
|
errors.add(reportMissingField(field.name, query: query));
|
||||||
} else if (!result.isSuccess) {
|
} else if (!result.isSuccess) {
|
||||||
|
|
|
@ -18,4 +18,6 @@ abstract class FormRenderer<T> {
|
||||||
FutureOr<T> visitNumField(NumField field);
|
FutureOr<T> visitNumField(NumField field);
|
||||||
|
|
||||||
FutureOr<T> visitTextField(TextField field);
|
FutureOr<T> visitTextField(TextField field);
|
||||||
|
|
||||||
|
FutureOr<T> visitMapField(MapField field);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue