diff --git a/packages/validate/README.md b/packages/validate/README.md index 9b86e68c..dd890cdb 100644 --- a/packages/validate/README.md +++ b/packages/validate/README.md @@ -28,15 +28,30 @@ its usage in `package:test`): var positiveNumberField = IntField('pos_num').match([isPositive]); ``` +A `MapField` can embed a `Form` (forms covered below), and when combined with +`Field.deserialize`, can be used to deserialize structured data as a body field: + +```dart +app.post('/map_field', (req, res) async { + var form = Form(fields: [ + MapField('todo', todoForm).deserialize(Todo.fromMap), + ]); + + var data = await form.validate(req); + print(data['todo'] is Todo); +}); +``` + There are several included field types: -* `TextField` -* `BoolField` -* `NumField` -* `DoubleField` -* `IntField` -* `DateTimeField` -* `FileField` -* `ImageField` +* `TextField` - Standard text input. +* `BoolField` - Checks if a field is present; used for checkboxes. +* `NumField` - Base class that parses input as a number. +* `DoubleField` - Specialization of `NumField` for doubles. +* `IntField` - Specialization of `NumField` for integers. +* `DateTimeField` - Parses an input as an ISO-8601 date. +* `FileField` - Validates a file in `req.uploadedFiles`. +* `ImageField` - Uses `package:image` to decode an `UploadedFile` into an image. +* `MapField` - Validates a Map using a Form. # Forms The `Form` class lets you combine `Field` instances, and decode @@ -55,9 +70,15 @@ var todo = await todoForm.deserialize(req, TodoSerializer.fromMap); // Same as above, but with a Codec (i.e. via `angel_serialize`). var todo = await todoForm.decode(req, todoSerializer); +// Same as above, but returns the plain Map without any deserialization. +var todoMap = await todoForm.validate(req); + // Lower-level functionality, typically not called directly. // Use it if you want to handle validation errors directly, without // throwing exceptions. +var result = await todoForm.read(req); +print(result.isSuccess); +print(result.errors.length); @serializable class _Todo { diff --git a/packages/validate/example/main.dart b/packages/validate/example/main.dart index 9a54f3ab..199316a2 100644 --- a/packages/validate/example/main.dart +++ b/packages/validate/example/main.dart @@ -72,13 +72,18 @@ Future main() async { '''); }); - - app.post('/b', (req, res) async { + + // You can use a [MapField] to embed one [Form] with another. + // In this example, we embed the [todoForm], but also call `.deserialize`, + // so that the final value we see is a [Todo] instance, rather than a [Map]. + app.post('/map_field', (req, res) async { var form = Form(fields: [ - MapField('todo', todoForm), + MapField('todo', todoForm).deserialize(Todo.fromMap), ]); - var data = await form.read(req); - return data; + var data = await form.validate(req); + var b = StringBuffer(); + b.write(data); + return b.toString(); }); app.fallback((req, res) => throw AngelHttpException.notFound()); @@ -103,4 +108,7 @@ class Todo { static Todo fromMap(Map map) { return Todo(map['text'] as String, map['is_complete'] as bool); } + + @override + String toString() => 'Todo($text, $isComplete)'; } diff --git a/packages/validate/lib/src/field.dart b/packages/validate/lib/src/field.dart index 6d6eb8ea..a18713e3 100644 --- a/packages/validate/lib/src/field.dart +++ b/packages/validate/lib/src/field.dart @@ -115,7 +115,9 @@ class _MatchedField extends Field { Future> read( Map fields, Iterable files) async { var result = await inner.read(fields, files); - if (!result.isSuccess) { + if (result == null) { + return null; + } else if (!result.isSuccess) { return result; } else { var errors = []; @@ -149,7 +151,9 @@ class _DeserializeField extends Field { FutureOr> read( Map fields, Iterable files) async { var result = await inner.read(fields, files); - if (!result.isSuccess) { + if (result == null) { + return null; + } else if (!result.isSuccess) { return FieldReadResult.failure(result.errors); } else { return FieldReadResult.success(await converter(result.value));