From 104b8da314ce1657c3a1d5c915a4703e10075fb9 Mon Sep 17 00:00:00 2001 From: Tobe O Date: Mon, 4 May 2020 12:35:17 -0400 Subject: [PATCH] validate: Rework Field.read to omit RequestContext --- packages/validate/lib/src/common_fields.dart | 69 ++++++++++++++++---- packages/validate/lib/src/field.dart | 10 +-- packages/validate/lib/src/form.dart | 31 ++++++++- packages/validate/lib/src/form_renderer.dart | 2 + 4 files changed, 93 insertions(+), 19 deletions(-) diff --git a/packages/validate/lib/src/common_fields.dart b/packages/validate/lib/src/common_fields.dart index 8447d30c..fc58662c 100644 --- a/packages/validate/lib/src/common_fields.dart +++ b/packages/validate/lib/src/common_fields.dart @@ -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 { } @override - FutureOr> read(RequestContext req, + FutureOr> read( Map fields, Iterable files) { var value = _normalize(fields[name] as String); if (value == null) { @@ -71,7 +73,7 @@ class BoolField extends Field { renderer.visitBoolField(this); @override - FutureOr> read(RequestContext req, + FutureOr> read( Map fields, Iterable files) { if (fields.containsKey(name)) { return FieldReadResult.success(true); @@ -108,9 +110,9 @@ class NumField extends Field { renderer.visitNumField(this); @override - Future> read(RequestContext req, + Future> read( Map fields, Iterable 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 { max: max); @override - Future> read(RequestContext req, + Future> read( Map fields, Iterable 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 { max: max); @override - Future> read(RequestContext req, + Future> read( Map fields, Iterable 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 { renderer.visitDateTimeField(this); @override - Future> read(RequestContext req, + Future> read( Map fields, Iterable 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 { renderer.visitFileField(this); @override - FutureOr> read(RequestContext req, + FutureOr> read( Map fields, Iterable files) { var file = files.firstWhere((f) => f.name == name, orElse: () => null); if (file == null) { @@ -328,9 +330,9 @@ class ImageField extends Field { renderer.visitImageField(this); @override - FutureOr> read(RequestContext req, + FutureOr> read( Map fields, Iterable 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 { } } } + +class MapField extends Field> { + @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 accept(FormRenderer renderer) => + renderer.visitMapField(this); + + @override + FutureOr>> read( + Map fields, Iterable 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.']); + } + + + } +} diff --git a/packages/validate/lib/src/field.dart b/packages/validate/lib/src/field.dart index d4aa529e..743eb192 100644 --- a/packages/validate/lib/src/field.dart +++ b/packages/validate/lib/src/field.dart @@ -47,7 +47,7 @@ abstract class Field { /// /// If it returns `null` and [isRequired] is `true`, an error must /// be generated. - FutureOr> read(RequestContext req, + FutureOr> read( Map fields, Iterable files); /// Accepts a form renderer. @@ -72,8 +72,8 @@ abstract class Field { 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 extends Field { FutureOr accept(FormRenderer renderer) => inner.accept(renderer); @override - Future> read(RequestContext req, + Future> read( Map fields, Iterable files) async { - var result = await inner.read(req, fields, files); + var result = await inner.read(fields, files); if (!result.isSuccess) { return result; } else { diff --git a/packages/validate/lib/src/form.dart b/packages/validate/lib/src/form.dart index de017afa..1512aace 100644 --- a/packages/validate/lib/src/form.dart +++ b/packages/validate/lib/src/form.dart @@ -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>> readFromMap( + Map map, + {bool query = false}) async { + var out = {}; + var errors = []; + var uploadedFiles = []; + + 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) { diff --git a/packages/validate/lib/src/form_renderer.dart b/packages/validate/lib/src/form_renderer.dart index 858b0b98..b703841b 100644 --- a/packages/validate/lib/src/form_renderer.dart +++ b/packages/validate/lib/src/form_renderer.dart @@ -18,4 +18,6 @@ abstract class FormRenderer { FutureOr visitNumField(NumField field); FutureOr visitTextField(TextField field); + + FutureOr visitMapField(MapField field); }