Fixed null safety issues
This commit is contained in:
parent
5b3c589c8e
commit
15926ff45d
42 changed files with 242 additions and 207 deletions
|
@ -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)
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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', '""');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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
|
|
@ -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,
|
||||||
|
|
|
@ -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()', () {
|
||||||
|
|
|
@ -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"'));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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.
|
||||||
///
|
///
|
||||||
|
|
|
@ -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]!)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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));
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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,
|
||||||
);
|
);
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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());
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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, []);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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>(
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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.');
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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',
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue