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

@ -72,6 +72,14 @@ Future<void> main() async {
</html>
''');
});
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());
@ -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}');
}

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>> {
@override
final String name;
/// The underlying [Form] that specifies the types of data this object must contained.
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);
@override
@ -369,17 +369,17 @@ class MapField extends Field<Map<String, dynamic>> {
FutureOr<FieldReadResult<Map<String, dynamic>>> read(
Map<String, dynamic> fields, Iterable<UploadedFile> files) {
var value = fields[name];
Map mapValue;
Map<String, dynamic> mapValue;
// Value can be either a Map, or a JSON-encoded Map.
if (value == null) {
return null;
} else if (value is Map) {
mapValue = value;
mapValue = value.cast();
} else if (value is String) {
var decoded = json.decode(value);
if (decoded is Map) {
mapValue = decoded;
mapValue = decoded.cast();
} else {
return FieldReadResult.failure([
'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.']);
}
return form.readFromMap(mapValue);
}
}

View file

@ -1,4 +1,5 @@
import 'dart:async';
import 'dart:convert';
import 'package:angel_framework/angel_framework.dart';
import 'package:matcher/matcher.dart';
import 'form.dart';
@ -57,6 +58,14 @@ abstract class Field<T> {
/// [matchers] fails.
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.
///
/// 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));
}
}
}