Fixed null safety issues

This commit is contained in:
thomashii@dukefirehawk.com 2021-05-09 19:16:15 +08:00
parent 5b3c589c8e
commit 15926ff45d
42 changed files with 242 additions and 207 deletions

View file

@ -11,7 +11,7 @@
* Added merge_map and migrated to 2.0.0 (6/6 tests passed) * Added merge_map and migrated to 2.0.0 (6/6 tests passed)
* Added mock_request and migrated to 2.0.0 (0/0 tests) * Added mock_request and migrated to 2.0.0 (0/0 tests)
* Migrated angel_framework to 4.0.0 (146/149 tests passed) * Migrated angel_framework to 4.0.0 (146/149 tests passed)
* Migrated angel_auth to 4.0.0 (22/32 tests passed) * Migrated angel_auth to 4.0.0 (21/30 tests passed)
* Migrated angel_configuration to 4.0.0 (6/8 testspassed) * Migrated angel_configuration to 4.0.0 (6/8 testspassed)
* Migrated angel_validate to 4.0.0 (6/7 tests passed) * Migrated angel_validate to 4.0.0 (6/7 tests passed)
* Migrated json_god to 4.0.0 (13/13 tests passed) * Migrated json_god to 4.0.0 (13/13 tests passed)

View file

@ -30,7 +30,7 @@ class AuthToken {
String? ipAddress; String? ipAddress;
late DateTime issuedAt; late DateTime issuedAt;
num? lifeSpan; num lifeSpan;
var userId; var userId;
Map<String, dynamic> payload = {}; Map<String, dynamic> payload = {};
@ -41,12 +41,17 @@ class AuthToken {
DateTime? issuedAt, DateTime? issuedAt,
Map payload = const {}}) { Map payload = const {}}) {
this.issuedAt = issuedAt ?? DateTime.now(); this.issuedAt = issuedAt ?? DateTime.now();
this.payload.addAll(payload.keys
.fold({}, ((out, k) => out?..[k.toString()] = payload[k])) ??
{});
/*
this.payload.addAll(payload.keys.fold( this.payload.addAll(payload.keys.fold(
{}, {},
((out, k) => out..[k.toString()] = payload[k]) ((out, k) => out..[k.toString()] = payload[k])
as Map<String, dynamic>? Function( as Map<String, dynamic>? Function(
Map<String, dynamic>?, dynamic)) ?? Map<String, dynamic>?, dynamic)) ??
{}); {});
*/
} }
factory AuthToken.fromJson(String jsons) => factory AuthToken.fromJson(String jsons) =>
@ -55,10 +60,10 @@ class AuthToken {
factory AuthToken.fromMap(Map data) { factory AuthToken.fromMap(Map data) {
return AuthToken( return AuthToken(
ipAddress: data['aud'].toString(), ipAddress: data['aud'].toString(),
lifeSpan: data['exp'] as num?, lifeSpan: data['exp'] as num,
issuedAt: DateTime.parse(data['iat'].toString()), issuedAt: DateTime.parse(data['iat'].toString()),
userId: data['sub'], userId: data['sub'],
payload: data['pld'] as Map? ?? {}); payload: data['pld'] as Map);
} }
factory AuthToken.parse(String jwt) { factory AuthToken.parse(String jwt) {

View file

@ -5,10 +5,10 @@ import 'package:quiver/core.dart';
/// A common class containing parsing and validation logic for third-party authentication configuration. /// A common class containing parsing and validation logic for third-party authentication configuration.
class ExternalAuthOptions { class ExternalAuthOptions {
/// The user's identifier, otherwise known as an "application id". /// The user's identifier, otherwise known as an "application id".
final String? clientId; final String clientId;
/// The user's secret, other known as an "application secret". /// The user's secret, other known as an "application secret".
final String? clientSecret; final String clientSecret;
/// The user's redirect URI. /// The user's redirect URI.
final Uri redirectUri; final Uri redirectUri;
@ -17,17 +17,11 @@ class ExternalAuthOptions {
final Set<String> scopes; final Set<String> scopes;
ExternalAuthOptions._( ExternalAuthOptions._(
this.clientId, this.clientSecret, this.redirectUri, this.scopes) { this.clientId, this.clientSecret, this.redirectUri, this.scopes);
if (clientId == null) {
throw ArgumentError.notNull('clientId');
} else if (clientSecret == null) {
throw ArgumentError.notNull('clientSecret');
}
}
factory ExternalAuthOptions( factory ExternalAuthOptions(
{required String? clientId, {required String clientId,
required String? clientSecret, required String clientSecret,
required redirectUri, required redirectUri,
Iterable<String> scopes = const []}) { Iterable<String> scopes = const []}) {
if (redirectUri is String) { if (redirectUri is String) {
@ -50,8 +44,8 @@ class ExternalAuthOptions {
/// * `redirect_uri` /// * `redirect_uri`
factory ExternalAuthOptions.fromMap(Map map) { factory ExternalAuthOptions.fromMap(Map map) {
return ExternalAuthOptions( return ExternalAuthOptions(
clientId: map['client_id'] as String?, clientId: map['client_id'] as String,
clientSecret: map['client_secret'] as String?, clientSecret: map['client_secret'] as String,
redirectUri: map['redirect_uri'], redirectUri: map['redirect_uri'],
scopes: map['scopes'] is Iterable scopes: map['scopes'] is Iterable
? ((map['scopes'] as Iterable).map((x) => x.toString())) ? ((map['scopes'] as Iterable).map((x) => x.toString()))
@ -72,15 +66,15 @@ class ExternalAuthOptions {
/// Creates a copy of this object, with the specified changes. /// Creates a copy of this object, with the specified changes.
ExternalAuthOptions copyWith( ExternalAuthOptions copyWith(
{String? clientId, {String clientId = '',
String? clientSecret, String clientSecret = '',
redirectUri, redirectUri,
Iterable<String>? scopes}) { Iterable<String> scopes = const []}) {
return ExternalAuthOptions( return ExternalAuthOptions(
clientId: clientId ?? this.clientId, clientId: clientId,
clientSecret: clientSecret ?? this.clientSecret, clientSecret: clientSecret,
redirectUri: redirectUri ?? this.redirectUri, redirectUri: redirectUri ?? this.redirectUri,
scopes: (scopes ??= []).followedBy(this.scopes), scopes: (scopes).followedBy(this.scopes),
); );
} }
@ -117,7 +111,7 @@ class ExternalAuthOptions {
secret = clientSecret; secret = clientSecret;
} else { } else {
var codeUnits = var codeUnits =
List<int>.filled(asteriskCount ?? clientSecret!.length, $asterisk); List<int>.filled(asteriskCount ?? clientSecret.length, $asterisk);
secret = String.fromCharCodes(codeUnits); secret = String.fromCharCodes(codeUnits);
} }

View file

@ -6,11 +6,14 @@ import 'package:angel_framework/angel_framework.dart';
/// [realm] defaults to `'angel_auth'`. /// [realm] defaults to `'angel_auth'`.
RequestHandler forceBasicAuth<User>({String? realm}) { RequestHandler forceBasicAuth<User>({String? realm}) {
return (RequestContext req, ResponseContext res) async { return (RequestContext req, ResponseContext res) async {
if (req.container!.has<User>()) { if (req.container != null) {
return true; var reqContainer = req.container!;
} else if (req.container!.has<Future<User>>()) { if (reqContainer.has<User>()) {
await req.container!.makeAsync<User>(); return true;
return true; } else if (reqContainer.has<Future<User>>()) {
await reqContainer.makeAsync<User>();
return true;
}
} }
res.headers['www-authenticate'] = 'Basic realm="${realm ?? 'angel_auth'}"'; res.headers['www-authenticate'] = 'Basic realm="${realm ?? 'angel_auth'}"';
@ -31,11 +34,16 @@ RequestHandler requireAuthentication<User>() {
} }
} }
if (req.container!.has<User>() || req.method == 'OPTIONS') { if (req.container != null) {
return true; var reqContainer = req.container!;
} else if (req.container!.has<Future<User>>()) { if (reqContainer.has<User>() || req.method == 'OPTIONS') {
await req.container!.makeAsync<User>(); return true;
return true; } else if (reqContainer.has<Future<User>>()) {
await reqContainer.makeAsync<User>();
return true;
} else {
return _reject(res);
}
} else { } else {
return _reject(res); return _reject(res);
} }

View file

@ -1,6 +1,6 @@
import 'dart:async'; import 'dart:async';
import 'dart:io'; import 'dart:io';
import 'dart:math' as Math; import 'dart:math';
import 'package:angel_framework/angel_framework.dart'; import 'package:angel_framework/angel_framework.dart';
import 'package:crypto/crypto.dart'; import 'package:crypto/crypto.dart';
import 'auth_token.dart'; import 'auth_token.dart';
@ -9,11 +9,11 @@ import 'strategy.dart';
/// Handles authentication within an Angel application. /// Handles authentication within an Angel application.
class AngelAuth<User> { class AngelAuth<User> {
Hmac? _hs256; late Hmac _hs256;
int? _jwtLifeSpan; late int _jwtLifeSpan;
final StreamController<User?> _onLogin = StreamController<User>(), final StreamController<User> _onLogin = StreamController<User>(),
_onLogout = StreamController<User>(); _onLogout = StreamController<User>();
final Math.Random _random = Math.Random.secure(); final Random _random = Random.secure();
final RegExp _rgxBearer = RegExp(r'^Bearer'); final RegExp _rgxBearer = RegExp(r'^Bearer');
/// If `true` (default), then JWT's will be stored and retrieved from a `token` cookie. /// If `true` (default), then JWT's will be stored and retrieved from a `token` cookie.
@ -51,23 +51,25 @@ class AngelAuth<User> {
FutureOr Function(User)? serializer; FutureOr Function(User)? serializer;
/// Deserializes a unique identifier into its associated identity. In most cases, this is a user object or model instance. /// Deserializes a unique identifier into its associated identity. In most cases, this is a user object or model instance.
FutureOr<User> Function(Object?)? deserializer; FutureOr<User> Function(Object)? deserializer;
/// Fires the result of [deserializer] whenever a user signs in to the application. /// Fires the result of [deserializer] whenever a user signs in to the application.
Stream<User?> get onLogin => _onLogin.stream; Stream<User> get onLogin => _onLogin.stream;
/// Fires `req.user`, which is usually the result of [deserializer], whenever a user signs out of the application. /// Fires `req.user`, which is usually the result of [deserializer], whenever a user signs out of the application.
Stream<User?> get onLogout => _onLogout.stream; Stream<User> get onLogout => _onLogout.stream;
/// The [Hmac] being used to encode JWT's. /// The [Hmac] being used to encode JWT's.
Hmac? get hmac => _hs256; Hmac get hmac => _hs256;
String _randomString( String _randomString(
{int length = 32, {int length = 32,
String validChars = String validChars =
"ABCDEFHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_"}) { 'ABCDEFHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_'}) {
var chars = <int>[]; var chars = <int>[];
while (chars.length < length) chars.add(_random.nextInt(validChars.length)); while (chars.length < length) {
chars.add(_random.nextInt(validChars.length));
}
return String.fromCharCodes(chars); return String.fromCharCodes(chars);
} }
@ -83,7 +85,7 @@ class AngelAuth<User> {
this.cookieDomain, this.cookieDomain,
this.cookiePath = '/', this.cookiePath = '/',
this.secureCookies = true, this.secureCookies = true,
this.reviveTokenEndpoint = "/auth/token"}) this.reviveTokenEndpoint = '/auth/token'})
: super() { : super() {
_hs256 = Hmac(sha256, (jwtKey ?? _randomString()).codeUnits); _hs256 = Hmac(sha256, (jwtKey ?? _randomString()).codeUnits);
_jwtLifeSpan = jwtLifeSpan?.toInt() ?? -1; _jwtLifeSpan = jwtLifeSpan?.toInt() ?? -1;
@ -110,7 +112,7 @@ class AngelAuth<User> {
app.container! app.container!
.registerLazySingleton<Future<_AuthResult<User>>>((container) async { .registerLazySingleton<Future<_AuthResult<User>>>((container) async {
var req = container.make<RequestContext>()!; var req = container.make<RequestContext>()!;
var res = container.make<ResponseContext>(); var res = container.make<ResponseContext>()!;
var result = await _decodeJwt(req, res); var result = await _decodeJwt(req, res);
if (result != null) { if (result != null) {
return result; return result;
@ -148,8 +150,8 @@ class AngelAuth<User> {
req.container!.registerSingleton<AuthToken>(token); req.container!.registerSingleton<AuthToken>(token);
} }
if (allowCookie == true) { if (allowCookie) {
_addProtectedCookie(res!, 'token', token.serialize(_hs256!)); _addProtectedCookie(res!, 'token', token.serialize(_hs256));
} }
} }
@ -185,29 +187,29 @@ class AngelAuth<User> {
} }
Future<_AuthResult<User>?> _decodeJwt( Future<_AuthResult<User>?> _decodeJwt(
RequestContext req, ResponseContext? res) async { RequestContext req, ResponseContext res) async {
var jwt = getJwt(req); var jwt = getJwt(req);
if (jwt != null) { if (jwt != null) {
var token = AuthToken.validate(jwt, _hs256!); var token = AuthToken.validate(jwt, _hs256);
if (enforceIp) { if (enforceIp) {
if (req.ip != token.ipAddress) { if (req.ip != token.ipAddress) {
throw AngelHttpException.forbidden( throw AngelHttpException.forbidden(
message: "JWT cannot be accessed from this IP address."); message: 'JWT cannot be accessed from this IP address.');
} }
} }
if (token.lifeSpan! > -1) { if (token.lifeSpan > -1) {
var expiry = var expiry =
token.issuedAt.add(Duration(milliseconds: token.lifeSpan!.toInt())); token.issuedAt.add(Duration(milliseconds: token.lifeSpan.toInt()));
if (!expiry.isAfter(DateTime.now())) { if (!expiry.isAfter(DateTime.now())) {
throw AngelHttpException.forbidden(message: "Expired JWT."); throw AngelHttpException.forbidden(message: 'Expired JWT.');
} }
} }
var user = await deserializer!(token.userId); var user = await deserializer!(token.userId as Object);
_apply(req, res, token, user); _apply(req, res, token, user);
return _AuthResult(user, token); return _AuthResult(user, token);
} }
@ -217,7 +219,7 @@ class AngelAuth<User> {
/// Retrieves a JWT from a request, if any was sent at all. /// Retrieves a JWT from a request, if any was sent at all.
String? getJwt(RequestContext req) { String? getJwt(RequestContext req) {
if (req.headers!.value('Authorization') != null) { if (req.headers?.value('Authorization') != null) {
final authHeader = req.headers!.value('Authorization')!; final authHeader = req.headers!.value('Authorization')!;
// Allow Basic auth to fall through // Allow Basic auth to fall through
@ -228,7 +230,7 @@ class AngelAuth<User> {
req.cookies.any((cookie) => cookie.name == 'token')) { req.cookies.any((cookie) => cookie.name == 'token')) {
return req.cookies.firstWhere((cookie) => cookie.name == 'token').value; return req.cookies.firstWhere((cookie) => cookie.name == 'token').value;
} else if (allowTokenInQuery && } else if (allowTokenInQuery &&
req.uri!.queryParameters['token'] is String) { req.uri?.queryParameters['token'] is String) {
return req.uri!.queryParameters['token']?.toString(); return req.uri!.queryParameters['token']?.toString();
} }
@ -248,10 +250,10 @@ class AngelAuth<User> {
cookie.secure = true; cookie.secure = true;
} }
if (_jwtLifeSpan! > 0) { var lifeSpan = _jwtLifeSpan;
cookie.maxAge ??= _jwtLifeSpan! < 0 ? -1 : _jwtLifeSpan! ~/ 1000; if (lifeSpan > 0) {
cookie.expires ??= cookie.maxAge ??= lifeSpan < 0 ? -1 : lifeSpan ~/ 1000;
DateTime.now().add(Duration(milliseconds: _jwtLifeSpan!)); cookie.expires ??= DateTime.now().add(Duration(milliseconds: lifeSpan));
} }
cookie.domain ??= cookieDomain; cookie.domain ??= cookieDomain;
@ -270,19 +272,19 @@ class AngelAuth<User> {
jwt = body['token']?.toString(); jwt = body['token']?.toString();
} }
if (jwt == null) { if (jwt == null) {
throw AngelHttpException.forbidden(message: "No JWT provided"); throw AngelHttpException.forbidden(message: 'No JWT provided');
} else { } else {
var token = AuthToken.validate(jwt, _hs256!); var token = AuthToken.validate(jwt, _hs256);
if (enforceIp) { if (enforceIp) {
if (req.ip != token.ipAddress) { if (req.ip != token.ipAddress) {
throw AngelHttpException.forbidden( throw AngelHttpException.forbidden(
message: "JWT cannot be accessed from this IP address."); message: 'JWT cannot be accessed from this IP address.');
} }
} }
if (token.lifeSpan! > -1) { if (token.lifeSpan > -1) {
var expiry = token.issuedAt var expiry = token.issuedAt
.add(Duration(milliseconds: token.lifeSpan!.toInt())); .add(Duration(milliseconds: token.lifeSpan.toInt()));
if (!expiry.isAfter(DateTime.now())) { if (!expiry.isAfter(DateTime.now())) {
//print( //print(
@ -293,15 +295,15 @@ class AngelAuth<User> {
} }
if (allowCookie) { if (allowCookie) {
_addProtectedCookie(res, 'token', token.serialize(_hs256!)); _addProtectedCookie(res, 'token', token.serialize(_hs256));
} }
final data = await deserializer!(token.userId); final data = await deserializer!(token.userId as Object);
return {'data': data, 'token': token.serialize(_hs256!)}; return {'data': data, 'token': token.serialize(_hs256)};
} }
} catch (e) { } catch (e) {
if (e is AngelHttpException) rethrow; if (e is AngelHttpException) rethrow;
throw AngelHttpException.badRequest(message: "Malformed JWT"); throw AngelHttpException.badRequest(message: 'Malformed JWT');
} }
} }
@ -347,7 +349,7 @@ class AngelAuth<User> {
// Create JWT // Create JWT
var token = AuthToken( var token = AuthToken(
userId: userId, lifeSpan: _jwtLifeSpan, ipAddress: req.ip); userId: userId, lifeSpan: _jwtLifeSpan, ipAddress: req.ip);
var jwt = token.serialize(_hs256!); var jwt = token.serialize(_hs256);
if (options?.tokenCallback != null) { if (options?.tokenCallback != null) {
if (!req.container!.has<User>()) { if (!req.container!.has<User>()) {
@ -356,7 +358,7 @@ class AngelAuth<User> {
var r = await options!.tokenCallback!(req, res, token, result); var r = await options!.tokenCallback!(req, res, token, result);
if (r != null) return r; if (r != null) return r;
jwt = token.serialize(_hs256!); jwt = token.serialize(_hs256);
} }
_apply(req, res, token, result); _apply(req, res, token, result);
@ -376,7 +378,7 @@ class AngelAuth<User> {
req.accepts('application/json')) { req.accepts('application/json')) {
var user = hasExisting var user = hasExisting
? result ? result
: await deserializer!(await serializer!(result)); : await deserializer!((await serializer!(result)) as Object);
_onLogin.add(user); _onLogin.add(user);
return {"data": user, "token": jwt}; return {"data": user, "token": jwt};
} }
@ -402,38 +404,40 @@ class AngelAuth<User> {
/// Log a user in on-demand. /// Log a user in on-demand.
Future login(AuthToken token, RequestContext req, ResponseContext res) async { Future login(AuthToken token, RequestContext req, ResponseContext res) async {
var user = await deserializer!(token.userId); var user = await deserializer!(token.userId as Object);
_apply(req, res, token, user); _apply(req, res, token, user);
_onLogin.add(user); _onLogin.add(user);
if (allowCookie) { if (allowCookie) {
_addProtectedCookie(res, 'token', token.serialize(_hs256!)); _addProtectedCookie(res, 'token', token.serialize(_hs256));
} }
} }
/// Log a user in on-demand. /// Log a user in on-demand.
Future loginById(userId, RequestContext req, ResponseContext res) async { Future loginById(userId, RequestContext req, ResponseContext res) async {
var user = await deserializer!(userId); var user = await deserializer!(userId as Object);
var token = var token =
AuthToken(userId: userId, lifeSpan: _jwtLifeSpan, ipAddress: req.ip); AuthToken(userId: userId, lifeSpan: _jwtLifeSpan, ipAddress: req.ip);
_apply(req, res, token, user); _apply(req, res, token, user);
_onLogin.add(user); _onLogin.add(user);
if (allowCookie) { if (allowCookie) {
_addProtectedCookie(res, 'token', token.serialize(_hs256!)); _addProtectedCookie(res, 'token', token.serialize(_hs256));
} }
} }
/// Log an authenticated user out. /// Log an authenticated user out.
RequestHandler logout([AngelAuthOptions<User>? options]) { RequestHandler logout([AngelAuthOptions<User>? options]) {
return (RequestContext req, ResponseContext res) async { return (RequestContext req, ResponseContext res) async {
if (req.container!.has<User>()) { if (req.container?.has<User>() == true) {
var user = req.container!.make<User>(); var user = req.container?.make<User>();
_onLogout.add(user); if (user != null) {
_onLogout.add(user);
}
} }
if (allowCookie == true) { if (allowCookie == true) {
res.cookies.removeWhere((cookie) => cookie.name == "token"); res.cookies.removeWhere((cookie) => cookie.name == 'token');
_addProtectedCookie(res, 'token', '""'); _addProtectedCookie(res, 'token', '""');
} }

View file

@ -38,10 +38,13 @@ class LocalAuthStrategy<User> extends AuthStrategy<User> {
User? verificationResult; User? verificationResult;
if (allowBasic) { if (allowBasic) {
var authHeader = req.headers!.value('authorization') ?? ''; var authHeader = req.headers?.value('authorization') ?? '';
if (_rgxBasic.hasMatch(authHeader)) { if (_rgxBasic.hasMatch(authHeader)) {
var base64AuthString = _rgxBasic.firstMatch(authHeader)!.group(1)!; var base64AuthString = _rgxBasic.firstMatch(authHeader)?.group(1);
if (base64AuthString == null) {
return null;
}
var authString = String.fromCharCodes(base64.decode(base64AuthString)); var authString = String.fromCharCodes(base64.decode(base64AuthString));
if (_rgxUsrPass.hasMatch(authString)) { if (_rgxUsrPass.hasMatch(authString)) {
Match usrPassMatch = _rgxUsrPass.firstMatch(authString)!; Match usrPassMatch = _rgxUsrPass.firstMatch(authString)!;
@ -51,7 +54,7 @@ class LocalAuthStrategy<User> extends AuthStrategy<User> {
throw AngelHttpException.badRequest(errors: [invalidMessage]); throw AngelHttpException.badRequest(errors: [invalidMessage]);
} }
if (verificationResult == false || verificationResult == null) { if (verificationResult == null) {
res res
..statusCode = 401 ..statusCode = 401
..headers['www-authenticate'] = 'Basic realm="$realm"'; ..headers['www-authenticate'] = 'Basic realm="$realm"';
@ -68,16 +71,16 @@ class LocalAuthStrategy<User> extends AuthStrategy<User> {
.parseBody() .parseBody()
.then((_) => req.bodyAsMap) .then((_) => req.bodyAsMap)
.catchError((_) => <String, dynamic>{}); .catchError((_) => <String, dynamic>{});
if (body != null) { //if (body != null) {
if (_validateString(body[usernameField]?.toString()) && if (_validateString(body[usernameField].toString()) &&
_validateString(body[passwordField]?.toString())) { _validateString(body[passwordField].toString())) {
verificationResult = await verifier( verificationResult = await verifier(
body[usernameField]?.toString(), body[passwordField]?.toString()); body[usernameField].toString(), body[passwordField].toString());
}
} }
//}
} }
if (verificationResult == false || verificationResult == null) { if (verificationResult == null) {
if (options.failureRedirect != null && if (options.failureRedirect != null &&
options.failureRedirect!.isNotEmpty) { options.failureRedirect!.isNotEmpty) {
await res.redirect(options.failureRedirect, code: 401); await res.redirect(options.failureRedirect, code: 401);

View file

@ -7,6 +7,7 @@ environment:
sdk: '>=2.12.0 <3.0.0' sdk: '>=2.12.0 <3.0.0'
dependencies: dependencies:
angel_framework: angel_framework:
# path: ../framework
git: git:
url: https://github.com/dukefirehawk/angel.git url: https://github.com/dukefirehawk/angel.git
ref: sdk-2.12.x_nnbd ref: sdk-2.12.x_nnbd
@ -23,3 +24,7 @@ dev_dependencies:
logging: ^1.0.0 logging: ^1.0.0
pedantic: ^1.11.0 pedantic: ^1.11.0
test: ^1.17.3 test: ^1.17.3
#dependency_overrides:
# angel_container:
# path: ../container/angel_container

View file

@ -1,12 +1,12 @@
import "package:angel_auth/src/auth_token.dart"; import 'package:angel_auth/src/auth_token.dart';
import "package:crypto/crypto.dart"; import 'package:crypto/crypto.dart';
import "package:test/test.dart"; import 'package:test/test.dart';
main() async { void main() async {
final Hmac hmac = Hmac(sha256, "angel_auth".codeUnits); final hmac = Hmac(sha256, 'angel_auth'.codeUnits);
test("sample serialization", () { test('sample serialization', () {
var token = AuthToken(ipAddress: "localhost", userId: "thosakwe"); var token = AuthToken(ipAddress: 'localhost', userId: 'thosakwe');
var jwt = token.serialize(hmac); var jwt = token.serialize(hmac);
print(jwt); print(jwt);
@ -17,7 +17,7 @@ main() async {
}); });
test('custom payload', () { test('custom payload', () {
var token = AuthToken(ipAddress: "localhost", userId: "thosakwe", payload: { var token = AuthToken(ipAddress: 'localhost', userId: 'thosakwe', payload: {
"foo": "bar", "foo": "bar",
"baz": { "baz": {
"one": 1, "one": 1,

View file

@ -70,6 +70,7 @@ void main() {
); );
}); });
/* Deprecated as clientId and clientSecret cannot be null
test('ensures id not null', () { test('ensures id not null', () {
expect( expect(
() => ExternalAuthOptions( () => ExternalAuthOptions(
@ -89,6 +90,7 @@ void main() {
throwsArgumentError, throwsArgumentError,
); );
}); });
*/
}); });
group('fromMap()', () { group('fromMap()', () {

View file

@ -7,7 +7,7 @@ import 'package:http/http.dart' as http;
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
import 'package:test/test.dart'; import 'package:test/test.dart';
final AngelAuth<Map<String, String>?> auth = AngelAuth<Map<String, String>?>(); final AngelAuth<Map<String, String>> auth = AngelAuth<Map<String, String>>();
var headers = <String, String>{'accept': 'application/json'}; var headers = <String, String>{'accept': 'application/json'};
var localOpts = AngelAuthOptions<Map<String, String>>( var localOpts = AngelAuthOptions<Map<String, String>>(
failureRedirect: '/failure', successRedirect: '/success'); failureRedirect: '/failure', successRedirect: '/success');
@ -113,12 +113,13 @@ void main() async {
auth.strategies.clear(); auth.strategies.clear();
auth.strategies['local'] = auth.strategies['local'] =
LocalAuthStrategy(verifier, forceBasic: true, realm: 'test'); LocalAuthStrategy(verifier, forceBasic: true, realm: 'test');
var response = await client!.get(Uri.parse('$url/hello'), headers: { var response = await client?.get(Uri.parse('$url/hello'), headers: {
'accept': 'application/json', 'accept': 'application/json',
'content-type': 'application/json' 'content-type': 'application/json'
}); });
print(response.headers); print('Header = ${response?.headers}');
print('Body <${response.body}>'); print('Body <${response?.body}>');
expect(response.headers['www-authenticate'], equals('Basic realm="test"')); var head = response?.headers['www-authenticate'];
expect(head, equals('Basic realm="test"'));
}); });
} }

View file

@ -3,7 +3,7 @@ import 'dart:io';
import 'package:angel_auth/angel_auth.dart'; import 'package:angel_auth/angel_auth.dart';
import 'package:test/test.dart'; import 'package:test/test.dart';
const Duration threeDays = const Duration(days: 3); const Duration threeDays = Duration(days: 3);
void main() { void main() {
late Cookie defaultCookie; late Cookie defaultCookie;

View file

@ -18,7 +18,7 @@ final Parser<Map<String, String>> credentials = chain<String>([
match<String>(':'), match<String>(':'),
string.opt(), string.opt(),
]).map<Map<String, String>>( ]).map<Map<String, String>>(
(r) => {'username': r.value![0], 'password': r.value![2]}); (r) => {'username': r.value![0]!, 'password': r.value![2]!});
/// We can actually embed a parser within another parser. /// We can actually embed a parser within another parser.
/// ///

View file

@ -24,7 +24,7 @@ Parser<num> calculatorGrammar() {
expr.space(), expr.space(),
match<Null>(op).space() as Parser<num>, match<Null>(op).space() as Parser<num>,
expr.space(), expr.space(),
]).map((r) => f(r.value![0], r.value![2])), ]).map((r) => f(r.value![0]!, r.value![2]!)),
); );
} }

View file

@ -7,7 +7,7 @@ class _Advance<T> extends Parser<T> {
_Advance(this.parser, this.amount); _Advance(this.parser, this.amount);
@override @override
ParseResult<T> __parse(ParseArgs args) { ParseResult<T?> __parse(ParseArgs args) {
var result = parser._parse(args.increaseDepth()).change(parser: this); var result = parser._parse(args.increaseDepth()).change(parser: this);
if (result.successful) args.scanner.position += amount; if (result.successful) args.scanner.position += amount;
return result; return result;

View file

@ -21,7 +21,7 @@ class _Any<T> extends Parser<T> {
_Any(this.parsers, this.backtrack, this.errorMessage, this.severity); _Any(this.parsers, this.backtrack, this.errorMessage, this.severity);
@override @override
ParseResult<T> _parse(ParseArgs args) { ParseResult<T?> _parse(ParseArgs args) {
var inactive = parsers var inactive = parsers
.where((p) => !args.trampoline.isActive(p, args.scanner.position)); .where((p) => !args.trampoline.isActive(p, args.scanner.position));

View file

@ -1,13 +1,13 @@
part of lex.src.combinator; part of lex.src.combinator;
class _Cache<T> extends Parser<T> { class _Cache<T> extends Parser<T> {
final Map<int, ParseResult<T>> _cache = {}; final Map<int, ParseResult<T?>> _cache = {};
final Parser<T> parser; final Parser<T> parser;
_Cache(this.parser); _Cache(this.parser);
@override @override
ParseResult<T> __parse(ParseArgs args) { ParseResult<T?> __parse(ParseArgs args) {
return _cache.putIfAbsent(args.scanner.position, () { return _cache.putIfAbsent(args.scanner.position, () {
return parser._parse(args.increaseDepth()); return parser._parse(args.increaseDepth());
}).change(parser: this); }).change(parser: this);

View file

@ -3,9 +3,9 @@ part of lex.src.combinator;
/// Expects to parse a sequence of [parsers]. /// Expects to parse a sequence of [parsers].
/// ///
/// If [failFast] is `true` (default), then the first failure to parse will abort the parse. /// If [failFast] is `true` (default), then the first failure to parse will abort the parse.
ListParser<T> chain<T>(Iterable<Parser<T>> parsers, ListParser<T?> chain<T>(Iterable<Parser<T>> parsers,
{bool failFast: true, SyntaxErrorSeverity? severity}) { {bool failFast: true, SyntaxErrorSeverity? severity}) {
return _Chain<T>( return _Chain<T?>(
parsers, failFast != false, severity ?? SyntaxErrorSeverity.error); parsers, failFast != false, severity ?? SyntaxErrorSeverity.error);
} }
@ -17,7 +17,7 @@ class _Alt<T> extends Parser<T> {
_Alt(this.parser, this.errorMessage, this.severity); _Alt(this.parser, this.errorMessage, this.severity);
@override @override
ParseResult<T> __parse(ParseArgs args) { ParseResult<T?> __parse(ParseArgs args) {
var result = parser._parse(args.increaseDepth()); var result = parser._parse(args.increaseDepth());
return result.successful return result.successful
? result ? result
@ -33,7 +33,7 @@ class _Alt<T> extends Parser<T> {
} }
} }
class _Chain<T> extends ListParser<T> { class _Chain<T> extends ListParser<T?> {
final Iterable<Parser<T>> parsers; final Iterable<Parser<T>> parsers;
final bool failFast; final bool failFast;
final SyntaxErrorSeverity severity; final SyntaxErrorSeverity severity;
@ -41,9 +41,9 @@ class _Chain<T> extends ListParser<T> {
_Chain(this.parsers, this.failFast, this.severity); _Chain(this.parsers, this.failFast, this.severity);
@override @override
ParseResult<List<T>> __parse(ParseArgs args) { ParseResult<List<T?>?> __parse(ParseArgs args) {
var errors = <SyntaxError>[]; var errors = <SyntaxError>[];
var results = <T>[]; var results = <T?>[];
var spans = <FileSpan>[]; var spans = <FileSpan>[];
bool successful = true; bool successful = true;
@ -61,9 +61,7 @@ class _Chain<T> extends ListParser<T> {
successful = false; successful = false;
} }
if (result.value != null) { results.add(result.value);
results.add(result.value!);
}
if (result.span != null) { if (result.span != null) {
spans.add(result.span!); spans.add(result.span!);
@ -76,14 +74,14 @@ class _Chain<T> extends ListParser<T> {
span = spans.reduce((a, b) => a.expand(b)); span = spans.reduce((a, b) => a.expand(b));
} }
return ParseResult<List<T>>( return ParseResult<List<T?>?>(
args.trampoline, args.trampoline,
args.scanner, args.scanner,
this, this,
successful, successful,
errors, errors,
span: span, span: span,
value: List<T>.unmodifiable(results), value: List<T?>.unmodifiable(results),
); );
} }

View file

@ -9,7 +9,7 @@ class _Check<T> extends Parser<T> {
_Check(this.parser, this.matcher, this.errorMessage, this.severity); _Check(this.parser, this.matcher, this.errorMessage, this.severity);
@override @override
ParseResult<T> __parse(ParseArgs args) { ParseResult<T?> __parse(ParseArgs args) {
var matchState = {}; var matchState = {};
var result = parser._parse(args.increaseDepth()).change(parser: this); var result = parser._parse(args.increaseDepth()).change(parser: this);
if (!result.successful) if (!result.successful)

View file

@ -67,9 +67,9 @@ class ParseArgs {
/// A parser combinator, which can parse very complicated grammars in a manageable manner. /// A parser combinator, which can parse very complicated grammars in a manageable manner.
abstract class Parser<T> { abstract class Parser<T> {
ParseResult<T> __parse(ParseArgs args); ParseResult<T?> __parse(ParseArgs args);
ParseResult<T> _parse(ParseArgs args) { ParseResult<T?> _parse(ParseArgs args) {
var pos = args.scanner.position; var pos = args.scanner.position;
if (args.trampoline.hasMemoized(this, pos)) if (args.trampoline.hasMemoized(this, pos))
@ -86,7 +86,7 @@ abstract class Parser<T> {
} }
/// Parses text from a [SpanScanner]. /// Parses text from a [SpanScanner].
ParseResult<T> parse(SpanScanner scanner, [int depth = 1]) { ParseResult<T?> parse(SpanScanner scanner, [int depth = 1]) {
var args = ParseArgs(Trampoline(), scanner, depth); var args = ParseArgs(Trampoline(), scanner, depth);
return _parse(args); return _parse(args);
} }
@ -105,7 +105,7 @@ abstract class Parser<T> {
// TODO: Type issue // TODO: Type issue
/// Runs the given function, which changes the returned [ParseResult] into one relating to a [U] object. /// Runs the given function, which changes the returned [ParseResult] into one relating to a [U] object.
Parser<U> change<U>(ParseResult<U> Function(ParseResult<T>) f) { Parser<U> change<U>(ParseResult<U?> Function(ParseResult<T?>) f) {
return _Change<T, U>(this, f); return _Change<T, U>(this, f);
} }
@ -128,7 +128,7 @@ abstract class Parser<T> {
} }
/// Transforms the parse result using a unary function. /// Transforms the parse result using a unary function.
Parser<U> map<U>(U Function(ParseResult<T>) f) { Parser<U> map<U>(U Function(ParseResult<T?>) f) {
return _Map<T, U>(this, f); return _Map<T, U>(this, f);
} }
@ -192,20 +192,18 @@ abstract class Parser<T> {
Parser<List<T>> separatedBy(Parser other) { Parser<List<T>> separatedBy(Parser other) {
var suffix = other.then(this).index(1).cast<T>(); var suffix = other.then(this).index(1).cast<T>();
return this.then(suffix.star()).map((r) { return this.then(suffix.star()).map((r) {
List<dynamic>? v = r.value; var v = r.value;
if (v != null) { if (v == null || v.length < 2) {
var preceding = return [];
v.isEmpty ? [] : (r.value?[0] == null ? [] : [r.value?[0]]);
var out = List<T>.from(preceding);
if (r.value?[1] != null) {
r.value?[1].forEach((element) {
out.add(element as T);
});
}
return out;
} else {
return List<T>.empty(growable: true);
} }
var preceding = v.isEmpty ? [] : (v[0] == null ? [] : [v[0]]);
var out = List<T>.from(preceding);
if (v[1] != null) {
v[1].forEach((element) {
out.add(element as T);
});
}
return out;
}); });
} }
@ -278,7 +276,7 @@ abstract class Parser<T> {
Parser<T> opt({bool backtrack: true}) => _Opt(this, backtrack); Parser<T> opt({bool backtrack: true}) => _Opt(this, backtrack);
/// Sets the value of the [ParseResult]. /// Sets the value of the [ParseResult].
Parser<T> value(T Function(ParseResult<T>) f) { Parser<T> value(T Function(ParseResult<T?>) f) {
return _Value<T>(this, f); return _Value<T>(this, f);
} }
@ -379,7 +377,7 @@ class ParseResult<T> {
scanner, scanner,
parser ?? this.parser, parser ?? this.parser,
successful ?? this.successful, successful ?? this.successful,
errors, errors.isNotEmpty ? errors : this.errors,
span: span ?? this.span, span: span ?? this.span,
value: value ?? this.value, value: value ?? this.value,
); );

View file

@ -7,8 +7,8 @@ class _Compare<T> extends ListParser<T> {
_Compare(this.parser, this.compare); _Compare(this.parser, this.compare);
@override @override
ParseResult<List<T>> __parse(ParseArgs args) { ParseResult<List<T>?> __parse(ParseArgs args) {
ParseResult<List<T>> result = parser._parse(args.increaseDepth()); ParseResult<List<T>?> result = parser._parse(args.increaseDepth());
if (!result.successful) return result; if (!result.successful) return result;
result = result.change( result = result.change(

View file

@ -7,7 +7,7 @@ class _FoldErrors<T> extends Parser<T> {
_FoldErrors(this.parser, this.equal); _FoldErrors(this.parser, this.equal);
@override @override
ParseResult<T> __parse(ParseArgs args) { ParseResult<T?> __parse(ParseArgs args) {
var result = parser._parse(args.increaseDepth()).change(parser: this); var result = parser._parse(args.increaseDepth()).change(parser: this);
var errors = result.errors.fold<List<SyntaxError>>([], (out, e) { var errors = result.errors.fold<List<SyntaxError>>([], (out, e) {
if (!out.any((b) => equal(e, b))) out.add(e); if (!out.any((b) => equal(e, b))) out.add(e);

View file

@ -8,11 +8,26 @@ class _Index<T> extends Parser<T> {
@override @override
ParseResult<T> __parse(ParseArgs args) { ParseResult<T> __parse(ParseArgs args) {
ParseResult<List<T>> result = parser._parse(args.increaseDepth()); ParseResult<List<T>?> result = parser._parse(args.increaseDepth());
Object? value; Object? value;
if (result.successful) if (result.successful) {
value = index == -1 ? result.value!.last : result.value!.elementAt(index); var vList = result.value;
if (vList == null) {
throw ArgumentError("ParseResult is null");
}
if (index == -1) {
value = vList.last;
} else {
if (index < vList.length) {
//TODO: Look at this
// print(">>>>Index: $index, Size: ${vList.length}");
// value =
// index == -1 ? result.value!.last : result.value!.elementAt(index);
value = result.value!.elementAt(index);
}
}
}
return ParseResult<T>( return ParseResult<T>(
args.trampoline, args.trampoline,

View file

@ -16,7 +16,7 @@ class _Longest<T> extends Parser<T> {
_Longest(this.parsers, this.errorMessage, this.severity); _Longest(this.parsers, this.errorMessage, this.severity);
@override @override
ParseResult<T> _parse(ParseArgs args) { ParseResult<T?> _parse(ParseArgs args) {
var inactive = parsers var inactive = parsers
.toList() .toList()
.where((p) => !args.trampoline.isActive(p, args.scanner.position)); .where((p) => !args.trampoline.isActive(p, args.scanner.position));
@ -27,7 +27,7 @@ class _Longest<T> extends Parser<T> {
int replay = args.scanner.position; int replay = args.scanner.position;
var errors = <SyntaxError>[]; var errors = <SyntaxError>[];
var results = <ParseResult<T>>[]; var results = <ParseResult<T?>>[];
for (var parser in inactive) { for (var parser in inactive) {
var result = parser._parse(args.increaseDepth()); var result = parser._parse(args.increaseDepth());
@ -59,10 +59,10 @@ class _Longest<T> extends Parser<T> {
} }
@override @override
ParseResult<T> __parse(ParseArgs args) { ParseResult<T?> __parse(ParseArgs args) {
int replay = args.scanner.position; int replay = args.scanner.position;
var errors = <SyntaxError>[]; var errors = <SyntaxError>[];
var results = <ParseResult<T>>[]; var results = <ParseResult<T?>>[];
for (var parser in parsers) { for (var parser in parsers) {
var result = parser._parse(args.increaseDepth()); var result = parser._parse(args.increaseDepth());

View file

@ -2,14 +2,14 @@ part of lex.src.combinator;
class _Map<T, U> extends Parser<U> { class _Map<T, U> extends Parser<U> {
final Parser<T> parser; final Parser<T> parser;
final U Function(ParseResult<T>) f; final U Function(ParseResult<T?>) f;
_Map(this.parser, this.f); _Map(this.parser, this.f);
@override @override
ParseResult<U> __parse(ParseArgs args) { ParseResult<U?> __parse(ParseArgs args) {
var result = parser._parse(args.increaseDepth()); var result = parser._parse(args.increaseDepth());
return ParseResult<U>( return ParseResult<U?>(
args.trampoline, args.trampoline,
args.scanner, args.scanner,
this, this,
@ -34,12 +34,12 @@ class _Map<T, U> extends Parser<U> {
class _Change<T, U> extends Parser<U> { class _Change<T, U> extends Parser<U> {
final Parser<T> parser; final Parser<T> parser;
final ParseResult<U> Function(ParseResult<T>) f; final ParseResult<U?> Function(ParseResult<T?>) f;
_Change(this.parser, this.f); _Change(this.parser, this.f);
@override @override
ParseResult<U> __parse(ParseArgs args) { ParseResult<U?> __parse(ParseArgs args) {
return f(parser._parse(args.increaseDepth())).change(parser: this); return f(parser._parse(args.increaseDepth())).change(parser: this);
} }

View file

@ -7,7 +7,7 @@ class _MaxDepth<T> extends Parser<T> {
_MaxDepth(this.parser, this.cap); _MaxDepth(this.parser, this.cap);
@override @override
ParseResult<T> __parse(ParseArgs args) { ParseResult<T?> __parse(ParseArgs args) {
if (args.depth > cap) { if (args.depth > cap) {
return ParseResult<T>(args.trampoline, args.scanner, this, false, []); return ParseResult<T>(args.trampoline, args.scanner, this, false, []);
} }

View file

@ -8,7 +8,7 @@ class _Negate<T> extends Parser<T> {
_Negate(this.parser, this.errorMessage, this.severity); _Negate(this.parser, this.errorMessage, this.severity);
@override @override
ParseResult<T> __parse(ParseArgs args) { ParseResult<T?> __parse(ParseArgs args) {
var result = parser._parse(args.increaseDepth()).change(parser: this); var result = parser._parse(args.increaseDepth()).change(parser: this);
if (!result.successful) { if (!result.successful) {

View file

@ -7,7 +7,7 @@ class _Opt<T> extends Parser<T> {
_Opt(this.parser, this.backtrack); _Opt(this.parser, this.backtrack);
@override @override
ParseResult<T> __parse(ParseArgs args) { ParseResult<T?> __parse(ParseArgs args) {
var replay = args.scanner.position; var replay = args.scanner.position;
var result = parser._parse(args.increaseDepth()); var result = parser._parse(args.increaseDepth());
@ -35,9 +35,9 @@ class _ListOpt<T> extends ListParser<T> {
_ListOpt(this.parser, this.backtrack); _ListOpt(this.parser, this.backtrack);
@override @override
ParseResult<List<T>> __parse(ParseArgs args) { ParseResult<List<T>?> __parse(ParseArgs args) {
var replay = args.scanner.position; var replay = args.scanner.position;
ParseResult<List<T>> result = parser._parse(args.increaseDepth()); ParseResult<List<T>?> result = parser._parse(args.increaseDepth());
if (!result.successful) args.scanner.position = replay; if (!result.successful) args.scanner.position = replay;

View file

@ -7,8 +7,8 @@ class _Reduce<T> extends Parser<T> {
_Reduce(this.parser, this.combine); _Reduce(this.parser, this.combine);
@override @override
ParseResult<T> __parse(ParseArgs args) { ParseResult<T?> __parse(ParseArgs args) {
ParseResult<List<T>> result = parser._parse(args.increaseDepth()); ParseResult<List<T>?> result = parser._parse(args.increaseDepth());
if (!result.successful) if (!result.successful)
return ParseResult<T>( return ParseResult<T>(

View file

@ -15,14 +15,14 @@ class Reference<T> extends Parser<T> {
} }
@override @override
ParseResult<T> __parse(ParseArgs args) { ParseResult<T?> __parse(ParseArgs args) {
if (_parser == null) if (_parser == null)
throw StateError('There is no parser assigned to this reference.'); throw StateError('There is no parser assigned to this reference.');
return _parser!._parse(args); return _parser!._parse(args);
} }
@override @override
ParseResult<T> _parse(ParseArgs args) { ParseResult<T?> _parse(ParseArgs args) {
if (_parser == null) if (_parser == null)
throw StateError('There is no parser assigned to this reference.'); throw StateError('There is no parser assigned to this reference.');
return _parser!._parse(args); return _parser!._parse(args);

View file

@ -12,12 +12,12 @@ class _Repeat<T> extends ListParser<T> {
this.backtrack, this.severity); this.backtrack, this.severity);
@override @override
ParseResult<List<T>> __parse(ParseArgs args) { ParseResult<List<T>?> __parse(ParseArgs args) {
var errors = <SyntaxError>[]; var errors = <SyntaxError>[];
var results = <T>[]; var results = <T>[];
var spans = <FileSpan>[]; var spans = <FileSpan>[];
int success = 0, replay = args.scanner.position; int success = 0, replay = args.scanner.position;
ParseResult<T>? result; ParseResult<T?> result;
do { do {
result = parser._parse(args.increaseDepth()); result = parser._parse(args.increaseDepth());
@ -51,7 +51,7 @@ class _Repeat<T> extends ListParser<T> {
} else if (success > count && exact) { } else if (success > count && exact) {
if (backtrack) args.scanner.position = replay; if (backtrack) args.scanner.position = replay;
return ParseResult<List<T>>(args.trampoline, args.scanner, this, false, [ return ParseResult<List<T>?>(args.trampoline, args.scanner, this, false, [
SyntaxError( SyntaxError(
severity, severity,
tooMany, tooMany,

View file

@ -10,7 +10,7 @@ class _Safe<T> extends Parser<T> {
_Safe(this.parser, this.backtrack, this.errorMessage, this.severity); _Safe(this.parser, this.backtrack, this.errorMessage, this.severity);
@override @override
ParseResult<T> __parse(ParseArgs args) { ParseResult<T?> __parse(ParseArgs args) {
var replay = args.scanner.position; var replay = args.scanner.position;
try { try {

View file

@ -2,12 +2,12 @@ part of lex.src.combinator;
class _Value<T> extends Parser<T> { class _Value<T> extends Parser<T> {
final Parser<T> parser; final Parser<T> parser;
final T Function(ParseResult<T>) f; final T Function(ParseResult<T?>) f;
_Value(this.parser, this.f); _Value(this.parser, this.f);
@override @override
ParseResult<T> __parse(ParseArgs args) { ParseResult<T?> __parse(ParseArgs args) {
var result = parser._parse(args.increaseDepth()).change(parser: this); var result = parser._parse(args.increaseDepth()).change(parser: this);
return result.successful ? result.change(value: f(result)) : result; return result.successful ? result.change(value: f(result)) : result;
} }

View file

@ -136,7 +136,7 @@ abstract class Driver<
r.resolveAbsolute(path, method: req.method, strip: false); r.resolveAbsolute(path, method: req.method, strip: false);
var pipeline = MiddlewarePipeline<RequestHandler>(resolved); var pipeline = MiddlewarePipeline<RequestHandler>(resolved);
return Tuple4( return Tuple4(
pipeline.handlers!, pipeline.handlers,
resolved.fold<Map<String, dynamic>>( resolved.fold<Map<String, dynamic>>(
<String, dynamic>{}, (out, r) => out..addAll(r.allParams)), <String, dynamic>{}, (out, r) => out..addAll(r.allParams)),
(resolved.isEmpty ? null : resolved.first.parseResult)!, (resolved.isEmpty ? null : resolved.first.parseResult)!,
@ -164,7 +164,7 @@ abstract class Driver<
..registerSingleton<ParseResult?>(tuple.item3); ..registerSingleton<ParseResult?>(tuple.item3);
if (app.environment.isProduction && app.logger != null) { if (app.environment.isProduction && app.logger != null) {
req.container!.registerSingleton<Stopwatch>(Stopwatch()..start()); req.container?.registerSingleton<Stopwatch>(Stopwatch()..start());
} }
return runPipeline(it, req, res, app) return runPipeline(it, req, res, app)

View file

@ -96,7 +96,7 @@ class HostnameRouter {
var resolved = r.resolveAbsolute(req.path, method: req.method); var resolved = r.resolveAbsolute(req.path, method: req.method);
var pipeline = MiddlewarePipeline<RequestHandler>(resolved); var pipeline = MiddlewarePipeline<RequestHandler>(resolved);
// print('Pipeline: $pipeline'); // print('Pipeline: $pipeline');
for (var handler in pipeline.handlers!) { for (var handler in pipeline.handlers) {
// print(handler); // print(handler);
// Avoid stack overflow. // Avoid stack overflow.
if (handler == handleRequest) { if (handler == handleRequest) {

View file

@ -95,7 +95,7 @@ class Routable extends Router<RequestHandler> {
@override @override
Route<RequestHandler> addRoute( Route<RequestHandler> addRoute(
String method, String path, RequestHandler handler, String method, String path, RequestHandler handler,
{Iterable<RequestHandler>? middleware}) { {Iterable<RequestHandler> middleware = const {}}) {
final handlers = <RequestHandler>[]; final handlers = <RequestHandler>[];
// Merge @Middleware declaration, if any // Merge @Middleware declaration, if any
var reflector = _container?.reflector; var reflector = _container?.reflector;

View file

@ -36,7 +36,7 @@ class Angel extends Routable {
final List<Angel> _children = []; final List<Angel> _children = [];
final Map< final Map<
String, String,
Tuple4<List, Map<String, dynamic>, ParseResult<RouteResult>?, Tuple4<List, Map<String, dynamic>, ParseResult<RouteResult>,
MiddlewarePipeline>> handlerCache = HashMap(); MiddlewarePipeline>> handlerCache = HashMap();
Router<RequestHandler>? _flattened; Router<RequestHandler>? _flattened;
@ -150,8 +150,8 @@ class Angel extends Routable {
@override @override
Route<RequestHandler> addRoute( Route<RequestHandler> addRoute(
String method, String path, RequestHandler handler, String method, String path, RequestHandler handler,
{Iterable<RequestHandler>? middleware}) { {Iterable<RequestHandler> middleware = const []}) {
middleware ??= []; //middleware ??= [];
if (_flattened != null) { if (_flattened != null) {
logger?.warning( logger?.warning(
'WARNING: You added a route ($method $path) to the router, after it had been optimized.'); 'WARNING: You added a route ($method $path) to the router, after it had been optimized.');

View file

@ -11,10 +11,10 @@ final RegExp _straySlashes = RegExp(r'(^/+)|(/+$)');
/// A variation of the [Router] support both hash routing and push state. /// A variation of the [Router] support both hash routing and push state.
abstract class BrowserRouter<T> extends Router<T> { abstract class BrowserRouter<T> extends Router<T> {
/// Fires whenever the active route changes. Fires `null` if none is selected (404). /// Fires whenever the active route changes. Fires `null` if none is selected (404).
Stream<RoutingResult<T>> get onResolve; Stream<RoutingResult<T?>> get onResolve;
/// Fires whenever the active route changes. Fires `null` if none is selected (404). /// Fires whenever the active route changes. Fires `null` if none is selected (404).
Stream<Route<T>> get onRoute; Stream<Route<T?>> get onRoute;
/// Set `hash` to true to use hash routing instead of push state. /// Set `hash` to true to use hash routing instead of push state.
/// `listen` as `true` will call `listen` after initialization. /// `listen` as `true` will call `listen` after initialization.
@ -48,17 +48,17 @@ abstract class _BrowserRouterImpl<T> extends Router<T>
implements BrowserRouter<T> { implements BrowserRouter<T> {
bool _listening = false; bool _listening = false;
Route? _current; Route? _current;
final StreamController<RoutingResult<T>> _onResolve = final StreamController<RoutingResult<T?>> _onResolve =
StreamController<RoutingResult<T>>(); StreamController<RoutingResult<T?>>();
final StreamController<Route<T>> _onRoute = StreamController<Route<T>>(); final StreamController<Route<T?>> _onRoute = StreamController<Route<T?>>();
Route? get currentRoute => _current; Route? get currentRoute => _current;
@override @override
Stream<RoutingResult<T>> get onResolve => _onResolve.stream; Stream<RoutingResult<T?>> get onResolve => _onResolve.stream;
@override @override
Stream<Route<T>> get onRoute => _onRoute.stream; Stream<Route<T?>> get onRoute => _onRoute.stream;
_BrowserRouterImpl({bool listen = false}) : super() { _BrowserRouterImpl({bool listen = false}) : super() {
if (listen != false) this.listen(); if (listen != false) this.listen();
@ -69,8 +69,7 @@ abstract class _BrowserRouterImpl<T> extends Router<T>
void go(Iterable linkParams) => _goTo(navigate(linkParams)); void go(Iterable linkParams) => _goTo(navigate(linkParams));
@override @override
Route on(String path, T handler, Route on(String path, T handler, {Iterable<T> middleware = const []}) =>
{Iterable<T> middleware = const Iterable.empty()}) =>
all(path, handler, middleware: middleware); all(path, handler, middleware: middleware);
void prepareAnchors() { void prepareAnchors() {
@ -108,7 +107,9 @@ abstract class _BrowserRouterImpl<T> extends Router<T>
class _HashRouter<T> extends _BrowserRouterImpl<T> { class _HashRouter<T> extends _BrowserRouterImpl<T> {
_HashRouter({required bool listen}) : super(listen: listen) { _HashRouter({required bool listen}) : super(listen: listen) {
if (listen) this.listen(); if (listen) {
this.listen();
}
} }
@override @override

View file

@ -6,13 +6,13 @@ class RouteGrammar {
static final Parser<String> notSlash = static final Parser<String> notSlash =
match<String>(RegExp(notSlashRgx)).value((r) => r.span?.text ?? ''); match<String>(RegExp(notSlashRgx)).value((r) => r.span?.text ?? '');
static final Parser<Match?> regExp = static final Parser<Match> regExp =
match<Match?>(RegExp(r'\(([^\n)]+)\)([^/]+)?')) match<Match>(RegExp(r'\(([^\n)]+)\)([^/]+)?'))
.value((r) => r.scanner.lastMatch); .value((r) => r.scanner.lastMatch!);
static final Parser<Match?> parameterName = static final Parser<Match> parameterName =
match<Match?>(RegExp('$notSlashRgx?' r':([A-Za-z0-9_]+)' r'([^(/\n])?')) match<Match>(RegExp('$notSlashRgx?' r':([A-Za-z0-9_]+)' r'([^(/\n])?'))
.value((r) => r.scanner.lastMatch); .value((r) => r.scanner.lastMatch!);
static final Parser<ParameterSegment> parameterSegment = chain([ static final Parser<ParameterSegment> parameterSegment = chain([
parameterName, parameterName,

View file

@ -4,10 +4,10 @@ import 'router.dart';
class MiddlewarePipeline<T> { class MiddlewarePipeline<T> {
/// All the possible routes that matched the given path. /// All the possible routes that matched the given path.
final Iterable<RoutingResult<T>> routingResults; final Iterable<RoutingResult<T>> routingResults;
final List<T> _handlers = []; final List<T?> _handlers = [];
/// An ordered list of every handler delegated to handle this request. /// An ordered list of every handler delegated to handle this request.
List<T> get handlers { List<T?> get handlers {
/* /*
if (_handlers != null) return _handlers; if (_handlers != null) return _handlers;
final handlers = <T>[]; final handlers = <T>[];
@ -19,7 +19,7 @@ class MiddlewarePipeline<T> {
return _handlers = handlers; return _handlers = handlers;
*/ */
if (_handlers.isEmpty) { if (_handlers.isNotEmpty) {
return _handlers; return _handlers;
} }

View file

@ -284,7 +284,7 @@ class Router<T> {
/// Finds the first [Route] that matches the given path, /// Finds the first [Route] that matches the given path,
/// with the given method. /// with the given method.
bool resolve(String absolute, String relative, List<RoutingResult<T>> out, bool resolve(String absolute, String relative, List<RoutingResult<T?>> out,
{String method = 'GET', bool strip = true}) { {String method = 'GET', bool strip = true}) {
final cleanRelative = final cleanRelative =
strip == false ? relative : stripStraySlashes(relative); strip == false ? relative : stripStraySlashes(relative);
@ -334,13 +334,13 @@ class Router<T> {
/// Returns the result of [resolve] with [path] passed as /// Returns the result of [resolve] with [path] passed as
/// both `absolute` and `relative`. /// both `absolute` and `relative`.
Iterable<RoutingResult<T>> resolveAbsolute(String path, Iterable<RoutingResult<T?>> resolveAbsolute(String path,
{String method = 'GET', bool strip = true}) => {String method = 'GET', bool strip = true}) =>
resolveAll(path, path, method: method, strip: strip); resolveAll(path, path, method: method, strip: strip);
/// Finds every possible [Route] that matches the given path, /// Finds every possible [Route] that matches the given path,
/// with the given method. /// with the given method.
Iterable<RoutingResult<T>> resolveAll(String absolute, String relative, Iterable<RoutingResult<T?>> resolveAll(String absolute, String relative,
{String method = 'GET', bool strip = true}) { {String method = 'GET', bool strip = true}) {
if (_useCache == true) { if (_useCache == true) {
return _cache.putIfAbsent('$method$absolute', return _cache.putIfAbsent('$method$absolute',

View file

@ -3,7 +3,7 @@ part of angel_route.src.router;
/// Represents a complex result of navigating to a path. /// Represents a complex result of navigating to a path.
class RoutingResult<T> { class RoutingResult<T> {
/// The parse result that matched the given sub-path. /// The parse result that matched the given sub-path.
final ParseResult<RouteResult> parseResult; final ParseResult<RouteResult?> parseResult;
/// A nested instance, if a sub-path was matched. /// A nested instance, if a sub-path was matched.
final Iterable<RoutingResult<T>> nested; final Iterable<RoutingResult<T>> nested;
@ -47,10 +47,10 @@ class RoutingResult<T> {
} }
/// All handlers on this sub-path and its children. /// All handlers on this sub-path and its children.
List<T> get allHandlers { List<T?> get allHandlers {
final handlers = <T>[]; final handlers = <T?>[];
void crawl(RoutingResult<T> result) { void crawl(RoutingResult<T?> result) {
handlers.addAll(result.handlers); handlers.addAll(result.handlers);
if (result.nested.isNotEmpty == true) { if (result.nested.isNotEmpty == true) {

View file

@ -7,6 +7,7 @@ environment:
sdk: '>=2.12.0 <3.0.0' sdk: '>=2.12.0 <3.0.0'
dependencies: dependencies:
combinator: combinator:
# path: ../combinator
git: git:
url: https://github.com/dukefirehawk/angel.git url: https://github.com/dukefirehawk/angel.git
ref: sdk-2.12.x_nnbd ref: sdk-2.12.x_nnbd