validate: Add MapField, Field.deserialize, Field.decode

This commit is contained in:
Tobe O 2020-05-04 12:54:48 -04:00
parent 6338abd79e
commit 1ed5d20ad6
3 changed files with 48 additions and 8 deletions

View file

@ -73,6 +73,14 @@ Future<void> main() async {
'''); ''');
}); });
app.post('/b', (req, res) async {
var form = Form(fields: [
MapField('todo', todoForm),
]);
var data = await form.read(req);
return data;
});
app.fallback((req, res) => throw AngelHttpException.notFound()); app.fallback((req, res) => throw AngelHttpException.notFound());
app.errorHandler = (e, req, res) { app.errorHandler = (e, req, res) {
@ -82,7 +90,7 @@ Future<void> main() async {
} }
}; };
await http.startServer('127.0.0.1', 3000); await http.startServer('127.0.0.1', 3011);
print('Listening at ${http.uri}'); print('Listening at ${http.uri}');
} }

View file

@ -353,12 +353,12 @@ class ImageField extends Field<Image> {
} }
} }
/// A [Field] implementation that reads structured data by decoding it with a [Form].
class MapField extends Field<Map<String, dynamic>> { class MapField extends Field<Map<String, dynamic>> {
@override /// The underlying [Form] that specifies the types of data this object must contained.
final String name;
final Form form; final Form form;
MapField(this.name, this.form, {String label, bool isRequired = true}) MapField(String name, this.form, {String label, bool isRequired = true})
: super(name, 'text', label: label, isRequired: isRequired); : super(name, 'text', label: label, isRequired: isRequired);
@override @override
@ -369,17 +369,17 @@ class MapField extends Field<Map<String, dynamic>> {
FutureOr<FieldReadResult<Map<String, dynamic>>> read( FutureOr<FieldReadResult<Map<String, dynamic>>> read(
Map<String, dynamic> fields, Iterable<UploadedFile> files) { Map<String, dynamic> fields, Iterable<UploadedFile> files) {
var value = fields[name]; var value = fields[name];
Map mapValue; Map<String, dynamic> mapValue;
// Value can be either a Map, or a JSON-encoded Map. // Value can be either a Map, or a JSON-encoded Map.
if (value == null) { if (value == null) {
return null; return null;
} else if (value is Map) { } else if (value is Map) {
mapValue = value; mapValue = value.cast();
} else if (value is String) { } else if (value is String) {
var decoded = json.decode(value); var decoded = json.decode(value);
if (decoded is Map) { if (decoded is Map) {
mapValue = decoded; mapValue = decoded.cast();
} else { } else {
return FieldReadResult.failure([ return FieldReadResult.failure([
'If "$name" is given as a string, then it must produce a map/dict/object when decoded as JSON.' 'If "$name" is given as a string, then it must produce a map/dict/object when decoded as JSON.'
@ -390,6 +390,6 @@ class MapField extends Field<Map<String, dynamic>> {
['"$name" must be either a map/dict/object, or a string.']); ['"$name" must be either a map/dict/object, or a string.']);
} }
return form.readFromMap(mapValue);
} }
} }

View file

@ -1,4 +1,5 @@
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:matcher/matcher.dart'; import 'package:matcher/matcher.dart';
import 'form.dart'; import 'form.dart';
@ -57,6 +58,14 @@ abstract class Field<T> {
/// [matchers] fails. /// [matchers] fails.
Field<T> match(Iterable<Matcher> matchers) => _MatchedField(this, matchers); Field<T> match(Iterable<Matcher> matchers) => _MatchedField(this, matchers);
/// Wraps this instance in one that calls the [converter] to deserialize
/// the value into another type.
Field<U> deserialize<U>(FutureOr<U> Function(T) converter) =>
_DeserializeField(this, converter);
/// Same as [deserialize], but uses a [codec] to deserialize data.
Field<U> decode<U>(Codec<U, T> codec) => deserialize(codec.decode);
/// Calls [read], and returns the retrieve value from the body. /// Calls [read], and returns the retrieve value from the body.
/// ///
/// If [query] is `true` (default: `false`), then the value will /// If [query] is `true` (default: `false`), then the value will
@ -124,3 +133,26 @@ class _MatchedField<T> extends Field<T> {
} }
} }
} }
class _DeserializeField<T, U> extends Field<U> {
final Field<T> inner;
final FutureOr<U> Function(T) converter;
_DeserializeField(this.inner, this.converter)
: super(inner.name, inner.type,
label: inner.label, isRequired: inner.isRequired);
@override
FutureOr<X> accept<X>(FormRenderer<X> renderer) => inner.accept(renderer);
@override
FutureOr<FieldReadResult<U>> read(
Map<String, dynamic> fields, Iterable<UploadedFile> files) async {
var result = await inner.read(fields, files);
if (!result.isSuccess) {
return FieldReadResult.failure(result.errors);
} else {
return FieldReadResult.success(await converter(result.value));
}
}
}