NumField, DoubleField, IntField

This commit is contained in:
Tobe O 2019-10-16 21:09:01 -04:00
parent b3e2e8a401
commit aa31ead75c
5 changed files with 117 additions and 8 deletions

View file

@ -23,7 +23,7 @@ class TextField extends Field<String> {
renderer.visitTextField(this);
@override
FutureOr<FieldReadResult<String>> read(
FutureOr<FieldReadResult<String>> read(RequestContext req,
Map<String, dynamic> fields, Iterable<UploadedFile> files) {
var value = fields[name] as String;
if (trim) {
@ -50,7 +50,7 @@ class BoolField extends Field<bool> {
renderer.visitBoolField(this);
@override
FutureOr<FieldReadResult<bool>> read(
FutureOr<FieldReadResult<bool>> read(RequestContext req,
Map<String, dynamic> fields, Iterable<UploadedFile> files) {
if (fields.containsKey(name)) {
return FieldReadResult.success(true);
@ -59,3 +59,106 @@ class BoolField extends Field<bool> {
}
}
}
/// A [Field] that parses its value as a [num].
class NumField<T extends num> extends Field<T> {
// Reuse text validation logic.
TextField _textField;
/// The minimum/maximum value for the field.
final T min, max;
/// The amount for a form field to increment by.
final num step;
NumField(String name,
{String label, bool isRequired = true, this.max, this.min, this.step})
: super(name, label: label, isRequired: isRequired) {
_textField = TextField(name, label: label, isRequired: isRequired);
}
@override
FutureOr<U> accept<U>(FormRenderer<U> renderer) =>
renderer.visitNumField(this);
@override
Future<FieldReadResult<T>> read(RequestContext req,
Map<String, dynamic> fields, Iterable<UploadedFile> files) async {
var result = await _textField.read(req, fields, files);
if (result == null) {
return null;
} else if (result.isSuccess != true) {
return FieldReadResult.failure(result.errors);
} else {
var value = num.tryParse(result.value);
if (value != null) {
if (min != null && value < min) {
return FieldReadResult.failure(['"$name" can be no less than $min.']);
} else if (max != null && value > max) {
return FieldReadResult.failure(
['"$name" can be no greater than $max.']);
} else {
return FieldReadResult.success(value as T);
}
} else {
return FieldReadResult.failure(['"$name" must be a number.']);
}
}
}
}
/// A [NumField] that coerces its value to a [double].
class DoubleField extends NumField<double> {
DoubleField(String name,
{String label, bool isRequired = true, num step, double min, double max})
: super(name,
label: label,
isRequired: isRequired,
step: step,
min: min,
max: max);
@override
Future<FieldReadResult<double>> read(RequestContext req,
Map<String, dynamic> fields, Iterable<UploadedFile> files) async {
var result = await super.read(req, fields, files);
if (result == null) {
return null;
} else if (!result.isSuccess) {
return FieldReadResult.failure(result.errors);
} else {
return FieldReadResult.success(result.value.toDouble());
}
}
}
/// A [NumField] that requires its value to be an [int].
/// Passing a [double] will result in an error, so [step] defaults to 1.
class IntField extends NumField<int> {
IntField(String name,
{String label, bool isRequired = true, num step = 1, int min, int max})
: super(name,
label: label,
isRequired: isRequired,
step: step,
min: min,
max: max);
@override
Future<FieldReadResult<int>> read(RequestContext req,
Map<String, dynamic> fields, Iterable<UploadedFile> files) async {
var result = await super.read(req, fields, files);
if (result == null) {
return null;
} else if (!result.isSuccess) {
return FieldReadResult.failure(result.errors);
} else {
var value = result.value;
if (value is int) {
return FieldReadResult.success(result.value);
} else {
return FieldReadResult.failure(['"$name" must be an integer.']);
}
}
}
}

View file

@ -44,7 +44,7 @@ abstract class Field<T> {
///
/// If it returns `null` and [isRequired] is `true`, an error must
/// be generated.
FutureOr<FieldReadResult<T>> read(
FutureOr<FieldReadResult<T>> read(RequestContext req,
Map<String, dynamic> fields, Iterable<UploadedFile> files);
/// Accepts a form renderer.
@ -69,8 +69,8 @@ abstract class Field<T> {
await req.parseBody();
uploadedFiles = req.uploadedFiles;
}
var result =
await read(query ? req.queryParameters : req.bodyAsMap, uploadedFiles);
var result = await read(
req, query ? req.queryParameters : req.bodyAsMap, uploadedFiles);
if (result?.isSuccess != true && defaultValue != null) {
return defaultValue;
} else if (result == null) {
@ -99,9 +99,9 @@ class _MatchedField<T> extends Field<T> {
FutureOr<U> accept<U>(FormRenderer<U> renderer) => inner.accept(renderer);
@override
Future<FieldReadResult<T>> read(
Future<FieldReadResult<T>> read(RequestContext req,
Map<String, dynamic> fields, Iterable<UploadedFile> files) async {
var result = await inner.read(fields, files);
var result = await inner.read(req, fields, files);
if (!result.isSuccess) {
return result;
} else {

View file

@ -109,7 +109,7 @@ class Form {
for (var field in fields) {
var result = await field.read(
query ? req.queryParameters : req.bodyAsMap, uploadedFiles);
req, query ? req.queryParameters : req.bodyAsMap, uploadedFiles);
if (result == null && field.isRequired) {
errors.add(reportMissingField(field.name, query: query));
} else if (!result.isSuccess) {

View file

@ -9,5 +9,7 @@ abstract class FormRenderer<T> {
FutureOr<T> visitBoolField(BoolField field);
FutureOr<T> visitNumField(NumField field);
FutureOr<T> visitTextField(TextField field);
}

4
test/all_test.dart Normal file
View file

@ -0,0 +1,4 @@
import 'package:angel_validate/angel_validate.dart';
import 'package:test/test.dart';
void main() {}