validate: Add MapField, Field.deserialize, Field.decode
This commit is contained in:
parent
6338abd79e
commit
1ed5d20ad6
3 changed files with 48 additions and 8 deletions
|
@ -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}');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue