Skip to content

Commit

Permalink
{beam_location}: [add] bool strictPathPatterns
Browse files Browse the repository at this point in the history
	- closes #464
  • Loading branch information
slovnicki committed Feb 5, 2022
1 parent f0ccfd7 commit 5810c9c
Show file tree
Hide file tree
Showing 4 changed files with 97 additions and 11 deletions.
7 changes: 7 additions & 0 deletions package/lib/src/beam_location.dart
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,13 @@ abstract class BeamLocation<T extends RouteInformationSerializable>
/// For example: '/books/:id' or using regex `RegExp('/test/(?<test>[a-z]+){0,1}')`
List<Pattern> get pathPatterns;

/// Whether [pathPatterns] are strictly matched agains incoming URI.
///
/// If this is false (default), then a path pattern '/some/path' will match
/// '/' and '/some' and '/some/path'.
/// If this is true, then it will match just '/some/path'.
bool get strictPathPatterns => false;

/// Creates and returns the list of pages to be built by the [Navigator]
/// when this [BeamLocation] is beamed to or internally inferred.
///
Expand Down
52 changes: 41 additions & 11 deletions package/lib/src/utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -33,38 +33,68 @@ abstract class Utils {
///
/// Used in [BeamLocation.canHandle] and [chooseBeamLocation].
static bool canBeamLocationHandleUri(BeamLocation beamLocation, Uri uri) {
for (final pathBlueprint in beamLocation.pathPatterns) {
if (pathBlueprint is String) {
if (pathBlueprint == uri.path || pathBlueprint == '/*') {
for (final pathPattern in beamLocation.pathPatterns) {
if (pathPattern is String) {
// If it is an exact match or asterisk pattern
if (pathPattern == uri.path ||
pathPattern == '/*' ||
pathPattern == '*') {
return true;
}

// Clean URI path segments
final uriPathSegments = uri.pathSegments.toList();
if (uriPathSegments.length > 1 && uriPathSegments.last == '') {
uriPathSegments.removeLast();
}
final beamLocationPathBlueprintSegments =
Uri.parse(pathBlueprint).pathSegments;
if (uriPathSegments.length > beamLocationPathBlueprintSegments.length &&
!beamLocationPathBlueprintSegments.contains('*')) {

final pathPatternSegments = Uri.parse(pathPattern).pathSegments;

// If we're in strict mode and URI has fewer segments than pattern,
// we don't have a match so can continue.
if (beamLocation.strictPathPatterns &&
uriPathSegments.length < pathPatternSegments.length) {
continue;
}

// If URI has more segments and pattern doesn't end with asterisk,
// we don't have a match so can continue.
if (uriPathSegments.length > pathPatternSegments.length &&
!pathPatternSegments.last.endsWith('*')) {
continue;
}

var checksPassed = true;
// Iterating through URI segments
for (var i = 0; i < uriPathSegments.length; i++) {
if (beamLocationPathBlueprintSegments[i] == '*') {
// If all checks have passed up to i,
// if pattern has no more segments to traverse and it ended with asterisk,
// it is a match and we can break,
if (pathPatternSegments.length < i + 1 &&
pathPatternSegments.last.endsWith('*')) {
checksPassed = true;
break;
}
if (uriPathSegments[i] != beamLocationPathBlueprintSegments[i] &&
beamLocationPathBlueprintSegments[i][0] != ':') {

// If pattern has asterisk at i-th position,
// anything matches and we can continue.
if (pathPatternSegments[i] == '*') {
continue;
}
// If they are not the same and pattern doesn't expects path parameter,
// there's no match and we can break.
if (uriPathSegments[i] != pathPatternSegments[i] &&
!pathPatternSegments[i].startsWith(':')) {
checksPassed = false;
break;
}
}
// If no check failed, beamLocation can handle this URI.
if (checksPassed) {
return true;
}
} else {
final regexp = tryCastToRegExp(pathBlueprint);
final regexp = tryCastToRegExp(pathPattern);
return regexp.hasMatch(uri.toString());
}
}
Expand Down
26 changes: 26 additions & 0 deletions package/test/beam_location_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -109,4 +109,30 @@ void main() {
);
});
});

testWidgets('strict path patterns', (tester) async {
final delegate = BeamerDelegate(
locationBuilder: BeamerLocationBuilder(
beamLocations: [StrictPatternsLocation()],
),
);

await tester.pumpWidget(
MaterialApp.router(
routerDelegate: delegate,
routeInformationParser: BeamerParser(),
),
);

expect(delegate.currentBeamLocation, isA<NotFound>());

delegate.beamToNamed('/strict');
expect(delegate.currentBeamLocation, isA<StrictPatternsLocation>());

delegate.beamToNamed('/strict/deeper');
expect(delegate.currentBeamLocation, isA<StrictPatternsLocation>());

delegate.beamToNamed('/');
expect(delegate.currentBeamLocation, isA<NotFound>());
});
}
23 changes: 23 additions & 0 deletions package/test/test_locations.dart
Original file line number Diff line number Diff line change
Expand Up @@ -129,3 +129,26 @@ class AsteriskLocation extends BeamLocation<BeamState> {
)
];
}

class StrictPatternsLocation extends BeamLocation<BeamState> {
@override
List<Pattern> get pathPatterns => ['/strict', '/strict/deeper'];

@override
bool get strictPathPatterns => true;

@override
List<BeamPage> buildPages(BuildContext context, BeamState state) {
return [
BeamPage(
key: const ValueKey('strict'),
child: Container(),
),
if (state.pathPatternSegments.contains('deeper'))
BeamPage(
key: const ValueKey('deeper'),
child: Container(),
),
];
}
}

0 comments on commit 5810c9c

Please sign in to comment.