Migrated json_god

This commit is contained in:
thomashii 2021-04-10 20:42:55 +08:00
parent d211fc2c3d
commit 5e9172aca9
20 changed files with 161 additions and 137 deletions

View file

@ -2,7 +2,7 @@
* Changed Dart SDK requirements for all packages to ">=2.12.0 <3.0.0" to support NNBD. * Changed Dart SDK requirements for all packages to ">=2.12.0 <3.0.0" to support NNBD.
* Updated pretty_logging to 3.0.0 (0/0 tests) * Updated pretty_logging to 3.0.0 (0/0 tests)
* Updated angel_http_exception to 3.0.0 (0/0 tests) * Updated angel_http_exception to 3.0.0 (0/0 tests)
* Moved angel_cli to https://github.com/dukefirehawk/cli * Moved angel_cli to https://github.com/dukefirehawk/cli (Not migrated)
* Added code_buffer and updated to 2.0.0 (16/16 tests) * Added code_buffer and updated to 2.0.0 (16/16 tests)
* Added combinator and updated to 2.0.0 (16/16 tests) * Added combinator and updated to 2.0.0 (16/16 tests)
* Updated angel_route to 5.0.0 (35/35 tests passed) * Updated angel_route to 5.0.0 (35/35 tests passed)
@ -12,7 +12,20 @@
* Added mock_request and updated to 2.0.0 (0/0 tests) * Added mock_request and updated to 2.0.0 (0/0 tests)
* Updated angel_framework to 4.0.0 (146/149 tests passed) * Updated angel_framework to 4.0.0 (146/149 tests passed)
* Updated angel_auth to 4.0.0 (22/32 test passed) * Updated angel_auth to 4.0.0 (22/32 test passed)
* Updated angel_configuration to 4.0.0 (In progress) * Updated angel_configuration to 4.0.0 (6/8 test passed)
* Updated angel_validate to 4.0.0 (6/7 test passed)
* Updated json_god to 4.0.0 (13/13 test passed)
* Updated angel_client to 3.0.0 (in progress)
* Updated angel_websocket to 3.0.0 (in progress)
* Updated test to 3.0.0 (in progress)
* Updated jael to 3.0.0 (in progress)
* Updated jael_preprocessor to 3.0.0 (in progress)
* Updated angel_jael to 3.0.0 (in progress)
* Updated pub_sub to 3.0.0 (in progress)
* Updated production to 2.0.0 (in progress)
* Updated hot to 3.0.0 (in progress)
* Updated static to 3.0.0 (in progress)
* Update basic-sdk-2.12.x boilerplate (in progress)
# 3.0.0 (Non NNBD) # 3.0.0 (Non NNBD)
* Changed Dart SDK requirements for all packages to ">=2.10.0 <3.0.0" * Changed Dart SDK requirements for all packages to ">=2.10.0 <3.0.0"
@ -25,12 +38,12 @@
* Updated angel_framework to 3.0.0 * Updated angel_framework to 3.0.0
* Updated angel_auth to 3.0.0 * Updated angel_auth to 3.0.0
* Updated angel_configuration to 3.0.0 * Updated angel_configuration to 3.0.0
* Updated jael to 3.0.0 * Updated angel_validate to 3.0.0
* Updated jael_preprocessor to 3.0.0
* Updated validate to 3.0.0
* Added and updated json_god to 3.0.0 * Added and updated json_god to 3.0.0
* Updated angel_client to 3.0.0 * Updated angel_client to 3.0.0
* Updated angel_websocket to 3.0.0 (one issue to be resolved) * Updated angel_websocket to 3.0.0 (one issue to be resolved)
* Updated jael to 3.0.0
* Updated jael_preprocessor to 3.0.0
* Updated test to 3.0.0 * Updated test to 3.0.0
* Updated angel_jael to 3.0.0 (Issue with 2 dependencies) * Updated angel_jael to 3.0.0 (Issue with 2 dependencies)
* Added pub_sub and updated to 3.0.0 * Added pub_sub and updated to 3.0.0

View file

@ -44,7 +44,7 @@ Future<void> _loadYamlFile(Map map, File yamlFile, Map<String, String> env,
} }
} }
for (var key in configMap.keys as Iterable<String>) { for (var key in configMap.keys) {
out[key] = _applyEnv(configMap[key], env, warn); out[key] = _applyEnv(configMap[key], env, warn);
} }

View file

@ -1,7 +1,8 @@
/// A robust library for JSON serialization and deserialization. /// A robust library for JSON serialization and deserialization.
library json_god; library json_god;
import 'package:dart2_constant/convert.dart'; //import 'package:dart2_constant/convert.dart';
import 'dart:convert';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
import 'src/reflection.dart' as reflection; import 'src/reflection.dart' as reflection;
@ -14,4 +15,4 @@ part 'src/util.dart';
@deprecated @deprecated
bool debug = false; bool debug = false;
final Logger logger = new Logger('json_god'); final Logger logger = new Logger('json_god');

View file

@ -3,14 +3,14 @@ part of json_god;
/// Deserializes a JSON string into a Dart datum. /// Deserializes a JSON string into a Dart datum.
/// ///
/// You can also provide an output Type to attempt to serialize the JSON into. /// You can also provide an output Type to attempt to serialize the JSON into.
deserialize(String json, {Type outputType}) { deserialize(String json, {Type? outputType}) {
var deserialized = deserializeJson(json, outputType: outputType); var deserialized = deserializeJson(json, outputType: outputType);
logger.info("Deserialization result: $deserialized"); logger.info("Deserialization result: $deserialized");
return deserialized; return deserialized;
} }
/// Deserializes JSON into data, without validating it. /// Deserializes JSON into data, without validating it.
deserializeJson(String s, {Type outputType}) { deserializeJson(String s, {Type? outputType}) {
logger.info("Deserializing the following JSON: $s"); logger.info("Deserializing the following JSON: $s");
if (outputType == null) { if (outputType == null) {
@ -23,7 +23,7 @@ deserializeJson(String s, {Type outputType}) {
} }
/// Deserializes some JSON-serializable value into a usable Dart value. /// Deserializes some JSON-serializable value into a usable Dart value.
deserializeDatum(value, {Type outputType}) { deserializeDatum(value, {Type? outputType}) {
if (outputType != null) { if (outputType != null) {
return reflection.deserialize(value, outputType, deserializeDatum); return reflection.deserialize(value, outputType, deserializeDatum);
} else if (value is List) { } else if (value is List) {

View file

@ -7,7 +7,7 @@ const Symbol hashCodeSymbol = #hashCode;
const Symbol runtimeTypeSymbol = #runtimeType; const Symbol runtimeTypeSymbol = #runtimeType;
typedef Serializer(value); typedef Serializer(value);
typedef Deserializer(value, {Type outputType}); typedef Deserializer(value, {Type? outputType});
List<Symbol> _findGetters(ClassMirror classMirror) { List<Symbol> _findGetters(ClassMirror classMirror) {
List<Symbol> result = []; List<Symbol> result = [];
@ -110,7 +110,7 @@ _deserializeFromJsonByReflection(
throw new ArgumentError('$outputType is not a class.'); throw new ArgumentError('$outputType is not a class.');
} }
var type = typeMirror as ClassMirror; var type = typeMirror;
var fromJson = var fromJson =
new Symbol('${MirrorSystem.getName(type.simpleName)}.fromJson'); new Symbol('${MirrorSystem.getName(type.simpleName)}.fromJson');
@ -166,7 +166,7 @@ _deserializeFromJsonByReflection(
Symbol symbolForGetter = classMirror.instanceMembers.keys Symbol symbolForGetter = classMirror.instanceMembers.keys
.firstWhere((x) => x == searchSymbol); .firstWhere((x) => x == searchSymbol);
Type requiredType = classMirror Type requiredType = classMirror
.instanceMembers[symbolForGetter].returnType.reflectedType; .instanceMembers[symbolForGetter]!.returnType.reflectedType;
if (data[key].runtimeType != requiredType) { if (data[key].runtimeType != requiredType) {
logger.info("Currently, $key is a ${data[key].runtimeType}."); logger.info("Currently, $key is a ${data[key].runtimeType}.");
logger.info("However, $key must be a $requiredType."); logger.info("However, $key must be a $requiredType.");

View file

@ -3,23 +3,23 @@ part of json_god;
/// Serializes any arbitrary Dart datum to JSON. Supports schema validation. /// Serializes any arbitrary Dart datum to JSON. Supports schema validation.
String serialize(value) { String serialize(value) {
var serialized = serializeObject(value); var serialized = serializeObject(value);
logger.info('Serialization result: $serialized'); logger.info('Serialization result: $serialized');
return json.encode(serialized); return json.encode(serialized);
} }
/// Transforms any Dart datum into a value acceptable to json.encode. /// Transforms any Dart datum into a value acceptable to json.encode.
serializeObject(value) { serializeObject(value) {
if (_isPrimitive(value)) { if (_isPrimitive(value)) {
logger.info("Serializing primitive value: $value"); logger.info("Serializing primitive value: $value");
return value; return value;
} else if (value is DateTime) { } else if (value is DateTime) {
logger.info("Serializing this DateTime: $value"); logger.info("Serializing this DateTime: $value");
return value.toIso8601String(); return value.toIso8601String();
} else if (value is Iterable) { } else if (value is Iterable) {
logger.info("Serializing this Iterable: $value"); logger.info("Serializing this Iterable: $value");
return value.map(serializeObject).toList(); return value.map(serializeObject).toList();
} else if (value is Map) { } else if (value is Map) {
logger.info("Serializing this Map: $value"); logger.info("Serializing this Map: $value");
return serializeMap(value); return serializeMap(value);
} else } else
return serializeObject(reflection.serialize(value, serializeObject)); return serializeObject(reflection.serialize(value, serializeObject));

View file

@ -1,14 +1,14 @@
name: json_god name: json_god
version: 3.0.0 version: 4.0.0
authors: authors:
- Tobe O <thosakwe@gmail.com> - Tobe O <thosakwe@gmail.com>
description: Easy JSON serialization and deserialization in Dart. description: Easy JSON serialization and deserialization in Dart.
homepage: https://github.com/thosakwe/json_god homepage: https://github.com/thosakwe/json_god
environment: environment:
sdk: ">=2.10.0 <3.0.0" sdk: '>=2.12.0 <3.0.0'
dependencies: dependencies:
dart2_constant: ^1.0.0 #dart2_constant: ^1.0.0
logging: ^1.0.0 logging: ^1.0.1
dev_dependencies: dev_dependencies:
stack_trace: ^1.0.0 stack_trace: ^1.10.0
test: any test: ^1.16.8

View file

@ -47,15 +47,15 @@ testDeserializationOfMaps() {
} }
class Pokedex { class Pokedex {
Map<String, int> pokemon; Map<String, int>? pokemon;
} }
testDeserializationOfMapsWithReflection() { testDeserializationOfMapsWithReflection() {
var s = '{"pokemon": {"Bulbasaur": 1, "Deoxys": 382}}'; var s = '{"pokemon": {"Bulbasaur": 1, "Deoxys": 382}}';
var pokedex = god.deserialize(s, outputType: Pokedex) as Pokedex; var pokedex = god.deserialize(s, outputType: Pokedex) as Pokedex;
expect(pokedex.pokemon, hasLength(2)); expect(pokedex.pokemon, hasLength(2));
expect(pokedex.pokemon['Bulbasaur'], 1); expect(pokedex.pokemon!['Bulbasaur'], 1);
expect(pokedex.pokemon['Deoxys'], 382); expect(pokedex.pokemon!['Deoxys'], 382);
} }
testDeserializationOfListsAsWellAsViaReflection() { testDeserializationOfListsAsWellAsViaReflection() {

View file

@ -1,4 +1,6 @@
import 'package:dart2_constant/convert.dart'; //import 'package:dart2_constant/convert.dart';
import 'dart:convert';
import 'package:json_god/json_god.dart' as god; import 'package:json_god/json_god.dart' as god;
import 'package:test/test.dart'; import 'package:test/test.dart';
import 'shared.dart'; import 'shared.dart';

View file

@ -5,20 +5,20 @@ import 'package:stack_trace/stack_trace.dart';
void printRecord(LogRecord rec) { void printRecord(LogRecord rec) {
print(rec); print(rec);
if (rec.error != null) print(rec.error); if (rec.error != null) print(rec.error);
if (rec.stackTrace != null) print(new Chain.forTrace(rec.stackTrace).terse); if (rec.stackTrace != null) print(new Chain.forTrace(rec.stackTrace!).terse);
} }
class SampleNestedClass { class SampleNestedClass {
String bar; String? bar;
SampleNestedClass([String this.bar]); SampleNestedClass([String? this.bar]);
} }
class SampleClass { class SampleClass {
String hello; String? hello;
List<SampleNestedClass> nested = []; List<SampleNestedClass> nested = [];
SampleClass([String this.hello]); SampleClass([String? this.hello]);
} }
@WithSchemaUrl( @WithSchemaUrl(

View file

@ -20,7 +20,7 @@ main() {
} }
class Foo { class Foo {
String text; String? text;
String get foo => 'poo$text'; String get foo => 'poo$text';

View file

@ -55,17 +55,19 @@ RequestHandler validate(Validator validator,
{String errorMessage = 'Invalid data.'}) { {String errorMessage = 'Invalid data.'}) {
return (RequestContext req, res) async { return (RequestContext req, res) async {
await req.parseBody(); await req.parseBody();
var result = await asyncApplyValidator(validator, req.bodyAsMap, req.app); var app = req.app;
if (app != null) {
var result = await asyncApplyValidator(validator, req.bodyAsMap, app);
if (result.errors.isNotEmpty) { if (result.errors.isNotEmpty) {
throw AngelHttpException.badRequest( throw AngelHttpException.badRequest(
message: errorMessage, errors: result.errors); message: errorMessage, errors: result.errors);
}
req.bodyAsMap
..clear()
..addAll(result.data);
} }
req.bodyAsMap
..clear()
..addAll(result.data);
return true; return true;
}; };
} }
@ -75,18 +77,20 @@ RequestHandler validate(Validator validator,
RequestHandler validateQuery(Validator validator, RequestHandler validateQuery(Validator validator,
{String errorMessage = 'Invalid data.'}) { {String errorMessage = 'Invalid data.'}) {
return (RequestContext req, res) async { return (RequestContext req, res) async {
var result = var app = req.app;
await asyncApplyValidator(validator, req.queryParameters, req.app); if (app != null) {
var result =
await asyncApplyValidator(validator, req.queryParameters, app);
if (result.errors.isNotEmpty) { if (result.errors.isNotEmpty) {
throw AngelHttpException.badRequest( throw AngelHttpException.badRequest(
message: errorMessage, errors: result.errors); message: errorMessage, errors: result.errors);
}
req.queryParameters
..clear()
..addAll(result.data);
} }
req.queryParameters
..clear()
..addAll(result.data);
return true; return true;
}; };
} }
@ -96,17 +100,19 @@ RequestHandler validateQuery(Validator validator,
HookedServiceEventListener validateEvent(Validator validator, HookedServiceEventListener validateEvent(Validator validator,
{String errorMessage = 'Invalid data.'}) { {String errorMessage = 'Invalid data.'}) {
return (HookedServiceEvent e) async { return (HookedServiceEvent e) async {
var result = await asyncApplyValidator( var app = e.request?.app ?? e.service.app;
validator, e.data as Map, (e.request?.app ?? e.service.app)); if (app != null) {
var result = await asyncApplyValidator(validator, e.data as Map, app);
if (result.errors.isNotEmpty) { if (result.errors.isNotEmpty) {
throw AngelHttpException.badRequest( throw AngelHttpException.badRequest(
message: errorMessage, errors: result.errors); message: errorMessage, errors: result.errors);
}
e.data
..clear()
..addAll(result.data);
} }
e.data
..clear()
..addAll(result.data);
}; };
} }
@ -122,7 +128,7 @@ Future<ValidationResult> asyncApplyValidator(
var value = result.data[key]; var value = result.data[key];
var description = StringDescription("'$key': expected "); var description = StringDescription("'$key': expected ");
for (var rule in validator.rules[key]) { for (var rule in validator.rules[key]!) {
if (rule is AngelMatcher) { if (rule is AngelMatcher) {
var r = await rule.matchesWithAngel(value, key, result.data, {}, app); var r = await rule.matchesWithAngel(value, key, result.data, {}, app);

View file

@ -56,7 +56,7 @@ AngelMatcher matchAsync(FutureOr<Matcher> Function(String, Object) matcher,
/// Returns an [AngelMatcher] that verifies that an item with the given [idField] /// Returns an [AngelMatcher] that verifies that an item with the given [idField]
/// exists in the service at [servicePath], without throwing a `404` or returning `null`. /// exists in the service at [servicePath], without throwing a `404` or returning `null`.
AngelMatcher idExistsInService(String servicePath, AngelMatcher idExistsInService(String servicePath,
{String idField = 'id', String description}) { {String idField = 'id', String? description}) {
return predicateWithAngel( return predicateWithAngel(
(key, item, app) async { (key, item, app) async {
try { try {
@ -108,14 +108,13 @@ class _MatchWithAngel extends AngelMatcher {
_MatchWithAngel(this.f, this.description); _MatchWithAngel(this.f, this.description);
@override @override
Description describe(Description description) => this.description == null Description describe(Description description) =>
? description description.add(this.description);
: description.add(this.description);
@override @override
Future<bool> matchesWithAngel( Future<bool> matchesWithAngel(
item, String key, Map context, Map matchState, Angel app) { item, String key, Map context, Map matchState, Angel app) {
return Future.sync(() => f(item, context, app)).then((result) { return Future.sync(() => f(item as Object, context, app)).then((result) {
return result.matches(item, matchState); return result.matches(item, matchState);
}); });
} }
@ -128,14 +127,13 @@ class _PredicateWithAngel extends AngelMatcher {
_PredicateWithAngel(this.predicate, this.description); _PredicateWithAngel(this.predicate, this.description);
@override @override
Description describe(Description description) => this.description == null Description describe(Description description) =>
? description description.add(this.description);
: description.add(this.description);
@override @override
Future<bool> matchesWithAngel( Future<bool> matchesWithAngel(
item, String key, Map context, Map matchState, Angel app) { item, String key, Map context, Map matchState, Angel app) {
return Future<bool>.sync(() => predicate(key, item, app)); return Future<bool>.sync(() => predicate(key, item as Object, app));
} }
} }
@ -147,15 +145,14 @@ class _MatchAsync extends AngelMatcher {
_MatchAsync(this.matcher, this.feature, this.description); _MatchAsync(this.matcher, this.feature, this.description);
@override @override
Description describe(Description description) => this.description == null Description describe(Description description) =>
? description description.add(this.description);
: description.add(this.description);
@override @override
Future<bool> matchesWithAngel( Future<bool> matchesWithAngel(
item, String key, Map context, Map matchState, Angel app) async { item, String key, Map context, Map matchState, Angel app) async {
var f = await feature(); var f = await feature();
var m = await matcher(key, f); var m = await matcher(key, f as Object);
var c = wrapAngelMatcher(m); var c = wrapAngelMatcher(m);
return await c.matchesWithAngel(item, key, context, matchState, app); return await c.matchesWithAngel(item, key, context, matchState, app);
} }

View file

@ -44,10 +44,9 @@ class _PredicateWithContext extends ContextAwareMatcher {
_PredicateWithContext(this.f, this.desc); _PredicateWithContext(this.f, this.desc);
@override @override
Description describe(Description description) => Description describe(Description description) => description.add(desc);
desc == null ? description : description.add(desc);
@override @override
bool matchesWithContext(item, String key, Map context, Map matchState) => bool matchesWithContext(item, String key, Map context, Map matchState) =>
f(item, key, context, matchState); f(item as Object, key, context, matchState);
} }

View file

@ -11,34 +11,36 @@ final RegExp _url = RegExp(
/// Asserts that a `String` is alphanumeric, but also lets it contain dashes or underscores. /// Asserts that a `String` is alphanumeric, but also lets it contain dashes or underscores.
final Matcher isAlphaDash = predicate( final Matcher isAlphaDash = predicate(
(value) => value is String && _alphaDash.hasMatch(value), (dynamic value) => value is String && _alphaDash.hasMatch(value),
'alphanumeric (dashes and underscores are allowed)'); 'alphanumeric (dashes and underscores are allowed)');
/// Asserts that a `String` is alphanumeric, but also lets it contain dashes or underscores. /// Asserts that a `String` is alphanumeric, but also lets it contain dashes or underscores.
/// ///
final Matcher isAlphaNum = predicate( final Matcher isAlphaNum = predicate(
(value) => value is String && _alphaNum.hasMatch(value), 'alphanumeric'); (dynamic value) => value is String && _alphaNum.hasMatch(value),
'alphanumeric');
/// Asserts that a value either equals `true` or `false`. /// Asserts that a value either equals `true` or `false`.
final Matcher isBool = predicate((value) => value is bool, 'a bool'); final Matcher isBool = predicate((dynamic value) => value is bool, 'a bool');
/// Asserts that a `String` complies to the RFC 5322 e-mail standard. /// Asserts that a `String` complies to the RFC 5322 e-mail standard.
final Matcher isEmail = predicate( final Matcher isEmail = predicate(
(value) => value is String && _email.hasMatch(value), (dynamic value) => value is String && _email.hasMatch(value),
'a valid e-mail address'); 'a valid e-mail address');
/// Asserts that a value is an `int`. /// Asserts that a value is an `int`.
final Matcher isInt = predicate((value) => value is int, 'an integer'); final Matcher isInt = predicate((dynamic value) => value is int, 'an integer');
/// Asserts that a value is a `num`. /// Asserts that a value is a `num`.
final Matcher isNum = predicate((value) => value is num, 'a number'); final Matcher isNum = predicate((dynamic value) => value is num, 'a number');
/// Asserts that a value is a `String`. /// Asserts that a value is a `String`.
final Matcher isString = predicate((value) => value is String, 'a string'); final Matcher isString =
predicate((dynamic value) => value is String, 'a string');
/// Asserts that a value is a non-empty `String`. /// Asserts that a value is a non-empty `String`.
final Matcher isNonEmptyString = predicate( final Matcher isNonEmptyString = predicate(
(value) => value is String && value.trim().isNotEmpty, (dynamic value) => value is String && value.trim().isNotEmpty,
'a non-empty string'); 'a non-empty string');
/// Asserts that a value, presumably from a checkbox, is positive. /// Asserts that a value, presumably from a checkbox, is positive.
@ -47,9 +49,9 @@ final Matcher isChecked =
/// Ensures that a string is an ISO-8601 date string. /// Ensures that a string is an ISO-8601 date string.
final Matcher isIso8601DateString = predicate( final Matcher isIso8601DateString = predicate(
(x) { (dynamic x) {
try { try {
return x is String && DateTime.parse(x) != null; return x is String;
} catch (_) { } catch (_) {
return false; return false;
} }
@ -64,7 +66,7 @@ final Matcher isIso8601DateString = predicate(
/// https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*) /// https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)
/// ``` /// ```
final Matcher isUrl = predicate( final Matcher isUrl = predicate(
(value) => value is String && _url.hasMatch(value), (dynamic value) => value is String && _url.hasMatch(value),
'a valid url, starting with http:// or https://'); 'a valid url, starting with http:// or https://');
/// Use [isUrl] instead. /// Use [isUrl] instead.
@ -73,12 +75,12 @@ final Matcher isurl = isUrl;
/// Enforces a minimum length on a string. /// Enforces a minimum length on a string.
Matcher minLength(int length) => predicate( Matcher minLength(int length) => predicate(
(value) => value is String && value.length >= length, (dynamic value) => value is String && value.length >= length,
'a string at least $length character(s) long'); 'a string at least $length character(s) long');
/// Limits the maximum length of a string. /// Limits the maximum length of a string.
Matcher maxLength(int length) => predicate( Matcher maxLength(int length) => predicate(
(value) => value is String && value.length <= length, (dynamic value) => value is String && value.length <= length,
'a string no longer than $length character(s) long'); 'a string no longer than $length character(s) long');
/// Asserts that for a key `x`, the context contains an identical item `x_confirmed`. /// Asserts that for a key `x`, the context contains an identical item `x_confirmed`.

View file

@ -8,17 +8,17 @@ final RegExp _forbidden = RegExp(r'!$');
final RegExp _optional = RegExp(r'\?$'); final RegExp _optional = RegExp(r'\?$');
/// Returns a value based the result of a computation. /// Returns a value based the result of a computation.
typedef DefaultValueFunction(); typedef DefaultValueFunction = Function();
/// Generates an error message based on the given input. /// Generates an error message based on the given input.
typedef String CustomErrorMessageFunction(item); typedef CustomErrorMessageFunction = String Function(dynamic item);
/// Determines if a value is valid. /// Determines if a value is valid.
typedef bool Filter(value); typedef Filter = bool Function(dynamic value);
/// Converts the desired fields to their numeric representations, if present. /// Converts the desired fields to their numeric representations, if present.
Map<String, dynamic> autoParse(Map inputData, Iterable<String> fields) { Map<String, dynamic> autoParse(Map inputData, Iterable<String> fields) {
Map<String, dynamic> data = {}; var data = <String, dynamic>{};
for (var key in inputData.keys) { for (var key in inputData.keys) {
if (!fields.contains(key)) { if (!fields.contains(key)) {
@ -83,7 +83,7 @@ class Validator extends Matcher {
schema[keys] is Iterable ? schema[keys] : [schema[keys]]; schema[keys] is Iterable ? schema[keys] : [schema[keys]];
var iterable = []; var iterable = [];
_addTo(x) { void _addTo(x) {
if (x is Iterable) { if (x is Iterable) {
x.forEach(_addTo); x.forEach(_addTo);
} else { } else {
@ -111,8 +111,8 @@ class Validator extends Matcher {
Validator(Map<String, dynamic> schema, Validator(Map<String, dynamic> schema,
{Map<String, dynamic> defaultValues = const {}, {Map<String, dynamic> defaultValues = const {},
Map<String, dynamic> customErrorMessages = const {}}) { Map<String, dynamic> customErrorMessages = const {}}) {
this.defaultValues.addAll(defaultValues ?? {}); this.defaultValues.addAll(defaultValues);
this.customErrorMessages.addAll(customErrorMessages ?? {}); this.customErrorMessages.addAll(customErrorMessages);
_importSchema(schema); _importSchema(schema);
} }
@ -121,18 +121,18 @@ class Validator extends Matcher {
/// Validates, and filters input data. /// Validates, and filters input data.
ValidationResult check(Map inputData) { ValidationResult check(Map inputData) {
List<String> errors = []; var errors = <String>[];
var input = Map.from(inputData); var input = Map.from(inputData);
Map<String, dynamic> data = {}; var data = <String, dynamic>{};
for (String key in defaultValues.keys) { for (var key in defaultValues.keys) {
if (!input.containsKey(key)) { if (!input.containsKey(key)) {
var value = defaultValues[key]; var value = defaultValues[key];
input[key] = value is DefaultValueFunction ? value() : value; input[key] = value is DefaultValueFunction ? value() : value;
} }
} }
for (String field in forbiddenFields) { for (var field in forbiddenFields) {
if (input.containsKey(field)) { if (input.containsKey(field)) {
if (!customErrorMessages.containsKey(field)) { if (!customErrorMessages.containsKey(field)) {
errors.add("'$field' is forbidden."); errors.add("'$field' is forbidden.");
@ -142,7 +142,7 @@ class Validator extends Matcher {
} }
} }
for (String field in requiredFields) { for (var field in requiredFields) {
if (!_hasContextValidators(rules[field] ?? [])) { if (!_hasContextValidators(rules[field] ?? [])) {
if (!input.containsKey(field)) { if (!input.containsKey(field)) {
if (!customErrorMessages.containsKey(field)) { if (!customErrorMessages.containsKey(field)) {
@ -162,7 +162,7 @@ class Validator extends Matcher {
var value = input[key]; var value = input[key];
var description = StringDescription("'$key': expected "); var description = StringDescription("'$key': expected ");
for (var matcher in rules[key]) { for (var matcher in rules[key]!) {
if (matcher is ContextValidator) { if (matcher is ContextValidator) {
if (!matcher.validate(key, input)) { if (!matcher.validate(key, input)) {
errors.add(matcher errors.add(matcher
@ -175,7 +175,7 @@ class Validator extends Matcher {
} }
if (valid) { if (valid) {
for (Matcher matcher in rules[key]) { for (var matcher in rules[key]!) {
try { try {
if (matcher is Validator) { if (matcher is Validator) {
var result = matcher.check(value as Map); var result = matcher.check(value as Map);
@ -271,12 +271,12 @@ class Validator extends Matcher {
{Map<String, dynamic> defaultValues = const {}, {Map<String, dynamic> defaultValues = const {},
Map<String, dynamic> customErrorMessages = const {}, Map<String, dynamic> customErrorMessages = const {},
bool overwrite = false}) { bool overwrite = false}) {
Map<String, dynamic> _schema = {}; var _schema = <String, dynamic>{};
var child = Validator.empty() var child = Validator.empty()
..defaultValues.addAll(this.defaultValues) ..defaultValues.addAll(this.defaultValues)
..defaultValues.addAll(defaultValues ?? {}) ..defaultValues.addAll(defaultValues)
..customErrorMessages.addAll(this.customErrorMessages) ..customErrorMessages.addAll(this.customErrorMessages)
..customErrorMessages.addAll(customErrorMessages ?? {}) ..customErrorMessages.addAll(customErrorMessages)
..requiredFields.addAll(requiredFields) ..requiredFields.addAll(requiredFields)
..rules.addAll(rules); ..rules.addAll(rules);
@ -320,7 +320,7 @@ class Validator extends Matcher {
return; return;
} }
rules[key].add(rule); rules[key]!.add(rule);
} }
/// Adds all given [rules]. /// Adds all given [rules].
@ -331,7 +331,7 @@ class Validator extends Matcher {
/// Removes a [rule]. /// Removes a [rule].
void removeRule(String key, Matcher rule) { void removeRule(String key, Matcher rule) {
if (rules.containsKey(key)) { if (rules.containsKey(key)) {
rules[key].remove(rule); rules[key]!.remove(rule);
} }
} }
@ -377,17 +377,19 @@ class ValidationResult {
/// Occurs when user-provided data is invalid. /// Occurs when user-provided data is invalid.
class ValidationException extends AngelHttpException { class ValidationException extends AngelHttpException {
/// A list of errors that resulted in the given data being marked invalid. /// A list of errors that resulted in the given data being marked invalid.
@override
final List<String> errors = []; final List<String> errors = [];
/// A descriptive message describing the error. /// A descriptive message describing the error.
@override
final String message; final String message;
ValidationException(this.message, {Iterable<String> errors = const []}) ValidationException(this.message, {Iterable<String> errors = const []})
: super(FormatException(message), : super(FormatException(message),
statusCode: 400, statusCode: 400,
errors: (errors ?? <String>[]).toSet().toList(), errors: (errors).toSet().toList(),
stackTrace: StackTrace.current) { stackTrace: StackTrace.current) {
if (errors != null) this.errors.addAll(errors.toSet()); this.errors.addAll(errors.toSet());
} }
@override @override
@ -400,8 +402,10 @@ class ValidationException extends AngelHttpException {
return 'Validation error: ${errors.first}'; return 'Validation error: ${errors.first}';
} }
var messages = ['${errors.length} validation errors:\n'] var messages = [
..addAll(errors.map((error) => '* $error')); '${errors.length} validation errors:\n',
...errors.map((error) => '* $error')
];
return messages.join('\n'); return messages.join('\n');
} }

View file

@ -1,21 +1,21 @@
name: angel_validate name: angel_validate
description: Cross-platform request body validation library based on `matcher`. description: Cross-platform request body validation library based on `matcher`.
version: 3.0.0 version: 4.0.0
author: Tobe O <thosakwe@gmail.com> author: Tobe O <thosakwe@gmail.com>
homepage: https://github.com/angel-dart/validate homepage: https://github.com/angel-dart/validate
publish_to: none publish_to: none
environment: environment:
sdk: ">=2.10.0 <3.0.0" sdk: '>=2.12.0 <3.0.0'
dependencies: dependencies:
angel_framework: angel_framework:
git: git:
url: https://github.com/dukefirehawk/angel.git url: https://github.com/dukefirehawk/angel.git
ref: sdk-2.12.x ref: sdk-2.12.x_nnbd
path: packages/framework path: packages/framework
angel_http_exception: angel_http_exception:
git: git:
url: https://github.com/dukefirehawk/angel.git url: https://github.com/dukefirehawk/angel.git
ref: sdk-2.12.x ref: sdk-2.12.x_nnbd
path: packages/http_exception path: packages/http_exception
matcher: ^0.12.0 matcher: ^0.12.0
dev_dependencies: dev_dependencies:
@ -26,7 +26,7 @@ dev_dependencies:
# path: packages/test # path: packages/test
build_runner: ^1.11.1 build_runner: ^1.11.1
build_web_compilers: ^2.12.2 build_web_compilers: ^2.12.2
# logging: ^0.11.0 # logging: ^0.11.0
mock_request: # mock_request:
pedantic: ^1.0.0 pedantic: ^1.0.0
test: ^1.15.7 test: ^1.15.7

View file

@ -43,7 +43,7 @@ main() {
test('comma in schema', () { test('comma in schema', () {
expect(todoSchema.rules.keys, allOf(contains('foo'), contains('bar'))); expect(todoSchema.rules.keys, allOf(contains('foo'), contains('bar')));
expect([todoSchema.rules['foo'].first, todoSchema.rules['bar'].first], expect([todoSchema.rules['foo']!.first, todoSchema.rules['bar']!.first],
everyElement(predicate((x) => x == isTrue))); everyElement(predicate((dynamic x) => x == isTrue)));
}); });
} }

View file

@ -18,21 +18,21 @@ void printRecord(LogRecord rec) {
} }
void main() { void main() {
Angel app; Angel? app;
AngelHttp http; late AngelHttp http;
//TestClient client; //TestClient client;
setUp(() async { setUp(() async {
app = Angel(); app = Angel();
http = AngelHttp(app, useZone: false); http = AngelHttp(app!, useZone: false);
app.chain([validate(echoSchema)]).post('/echo', app!.chain([validate(echoSchema)]).post('/echo',
(RequestContext req, res) async { (RequestContext req, res) async {
await req.parseBody(); await req.parseBody();
res.write('Hello, ${req.bodyAsMap['message']}!'); res.write('Hello, ${req.bodyAsMap['message']}!');
}); });
app.logger = Logger('angel')..onRecord.listen(printRecord); app!.logger = Logger('angel')..onRecord.listen(printRecord);
//client = await connectTo(app); //client = await connectTo(app);
}); });

View file

@ -2,9 +2,9 @@ import 'dart:html';
import 'package:angel_validate/angel_validate.dart'; import 'package:angel_validate/angel_validate.dart';
final $errors = querySelector('#errors') as UListElement; final $errors = querySelector('#errors') as UListElement?;
final $form = querySelector('#form') as FormElement; final $form = querySelector('#form') as FormElement?;
final $blank = querySelector('[name="blank"]') as InputElement; final $blank = querySelector('[name="blank"]') as InputElement?;
final Validator formSchema = Validator({ final Validator formSchema = Validator({
'firstName*': [isString, isNotEmpty], 'firstName*': [isString, isNotEmpty],
@ -29,9 +29,9 @@ final Validator formSchema = Validator({
}); });
main() { main() {
$form.onSubmit.listen((e) { $form!.onSubmit.listen((e) {
e.preventDefault(); e.preventDefault();
$errors.children.clear(); $errors!.children.clear();
var formData = {}; var formData = {};
@ -39,7 +39,7 @@ main() {
formData[key] = (querySelector('[name="$key"]') as InputElement).value; formData[key] = (querySelector('[name="$key"]') as InputElement).value;
}); });
if ($blank.value.isNotEmpty) formData['blank'] = $blank.value; if ($blank!.value!.isNotEmpty) formData['blank'] = $blank!.value;
print('Form data: $formData'); print('Form data: $formData');
@ -47,7 +47,7 @@ main() {
var passportInfo = var passportInfo =
formSchema.enforceParsed(formData, ['age', 'familySize']); formSchema.enforceParsed(formData, ['age', 'familySize']);
$errors.children $errors!.children
..add(success('Successfully registered for a passport.')) ..add(success('Successfully registered for a passport.'))
..add(success('First Name: ${passportInfo["firstName"]}')) ..add(success('First Name: ${passportInfo["firstName"]}'))
..add(success('Last Name: ${passportInfo["lastName"]}')) ..add(success('Last Name: ${passportInfo["lastName"]}'))
@ -55,7 +55,7 @@ main() {
..add(success( ..add(success(
'Number of People in Family: ${passportInfo["familySize"]}')); 'Number of People in Family: ${passportInfo["familySize"]}'));
} on ValidationException catch (e) { } on ValidationException catch (e) {
$errors.children.addAll(e.errors.map((error) { $errors!.children.addAll(e.errors.map((error) {
return LIElement()..text = error; return LIElement()..text = error;
})); }));
} }