Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/go_router/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 14.2.7

- Fixes issue so that the parseRouteInformationWithContext can handle non-http Uris.

## 14.2.6

- Fixes replace and pushReplacement uri when only one route match in current route match list.
Expand Down
14 changes: 6 additions & 8 deletions packages/go_router/lib/src/configuration.dart
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,8 @@ class RouteConfiguration {
'The default location of a StatefulShellBranch cannot be '
'a parameterized route');
} else {
final RouteMatchList matchList = findMatch(branch.initialLocation!);
final RouteMatchList matchList =
findMatch(Uri.parse(branch.initialLocation!));
assert(
!matchList.isError,
'initialLocation (${matchList.uri}) of StatefulShellBranch must '
Expand Down Expand Up @@ -292,9 +293,7 @@ class RouteConfiguration {
}

/// Finds the routes that matched the given URL.
RouteMatchList findMatch(String location, {Object? extra}) {
final Uri uri = Uri.parse(canonicalUri(location));

RouteMatchList findMatch(Uri uri, {Object? extra}) {
final Map<String, String> pathParameters = <String, String>{};
final List<RouteMatchBase> matches =
_getLocRouteMatches(uri, pathParameters);
Expand All @@ -315,14 +314,13 @@ class RouteConfiguration {

/// Reparse the input RouteMatchList
RouteMatchList reparse(RouteMatchList matchList) {
RouteMatchList result =
findMatch(matchList.uri.toString(), extra: matchList.extra);
RouteMatchList result = findMatch(matchList.uri, extra: matchList.extra);

for (final ImperativeRouteMatch imperativeMatch
in matchList.matches.whereType<ImperativeRouteMatch>()) {
final ImperativeRouteMatch match = ImperativeRouteMatch(
pageKey: imperativeMatch.pageKey,
matches: findMatch(imperativeMatch.matches.uri.toString(),
matches: findMatch(imperativeMatch.matches.uri,
extra: imperativeMatch.matches.extra),
completer: imperativeMatch.completer);
result = result.push(match);
Expand Down Expand Up @@ -461,7 +459,7 @@ class RouteConfiguration {
List<RouteMatchList> redirectHistory,
) {
try {
final RouteMatchList newMatch = findMatch(newLocation);
final RouteMatchList newMatch = findMatch(Uri.parse(newLocation));
_addRedirect(redirectHistory, newMatch, previousLocation);
return newMatch;
} on GoException catch (e) {
Expand Down
2 changes: 1 addition & 1 deletion packages/go_router/lib/src/match.dart
Original file line number Diff line number Diff line change
Expand Up @@ -944,7 +944,7 @@ class _RouteMatchListDecoder
?.decode(encodedExtra[RouteMatchListCodec._encodedKey]);
}
RouteMatchList matchList =
configuration.findMatch(rootLocation, extra: extra);
configuration.findMatch(Uri.parse(rootLocation), extra: extra);

final List<Object?>? imperativeMatches =
input[RouteMatchListCodec._imperativeMatchesKey] as List<Object?>?;
Expand Down
28 changes: 10 additions & 18 deletions packages/go_router/lib/src/parser.dart
Original file line number Diff line number Diff line change
Expand Up @@ -80,25 +80,17 @@ class GoRouteInformationParser extends RouteInformationParser<RouteMatchList> {
});
}

late final RouteMatchList initialMatches;
if (routeInformation.uri.hasEmptyPath) {
String newUri = '${routeInformation.uri.origin}/';
if (routeInformation.uri.hasQuery) {
newUri += '?${routeInformation.uri.query}';
}
if (routeInformation.uri.hasFragment) {
newUri += '#${routeInformation.uri.fragment}';
}
initialMatches = configuration.findMatch(
newUri,
extra: state.extra,
);
} else {
initialMatches = configuration.findMatch(
routeInformation.uri.toString(),
extra: state.extra,
);
Uri uri = routeInformation.uri;
if (uri.hasEmptyPath) {
uri = uri.replace(path: '/');
} else if (uri.path.length > 1 && uri.path.endsWith('/')) {
// Remove trailing `/`.
uri = uri.replace(path: uri.path.substring(0, uri.path.length - 1));
}
final RouteMatchList initialMatches = configuration.findMatch(
uri,
extra: state.extra,
);
if (initialMatches.isError) {
log('No initial matches: ${routeInformation.uri.path}');
}
Expand Down
36 changes: 0 additions & 36 deletions packages/go_router/lib/src/path_utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'misc/errors.dart';
import 'route.dart';

final RegExp _parameterRegExp = RegExp(r':(\w+)(\((?:\\.|[^\\()])+\))?');
Expand Down Expand Up @@ -119,41 +118,6 @@ String concatenatePaths(String parentPath, String childPath) {
return '${parentPath == '/' ? '' : parentPath}/$childPath';
}

/// Normalizes the location string.
String canonicalUri(String loc) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The logic here is moved to inline in parseRouteInformationWithContext

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

where is parseRouteInformationWithContext ? is it inside go_router?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes it is a method in GoRouteInformationParser

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh you mean parseRouteInformationWithDependencies

if (loc.isEmpty) {
throw GoException('Location cannot be empty.');
}
String canon = Uri.parse(loc).toString();
canon = canon.endsWith('?') ? canon.substring(0, canon.length - 1) : canon;
final Uri uri = Uri.parse(canon);

// remove trailing slash except for when you shouldn't, e.g.
// /profile/ => /profile
// / => /
// /login?from=/ => /login?from=/
canon = uri.path.endsWith('/') &&
uri.path != '/' &&
!uri.hasQuery &&
!uri.hasFragment
? canon.substring(0, canon.length - 1)
: canon;

// replace '/?', except for first occurrence, from path only
// /login/?from=/ => /login?from=/
// /?from=/ => /?from=/
final int pathStartIndex = uri.host.isNotEmpty
? uri.toString().indexOf(uri.host) + uri.host.length
: uri.hasScheme
? uri.toString().indexOf(uri.scheme) + uri.scheme.length
: 0;
if (pathStartIndex < canon.length) {
canon = canon.replaceFirst('/?', '?', pathStartIndex + 1);
}

return canon;
}

/// Builds an absolute path for the provided route.
String? fullPathForRoute(
RouteBase targetRoute, String parentFullpath, List<RouteBase> routes) {
Expand Down
2 changes: 1 addition & 1 deletion packages/go_router/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name: go_router
description: A declarative router for Flutter based on Navigation 2 supporting
deep linking, data-driven routes and more
version: 14.2.6
version: 14.2.7
repository: https://github.com/flutter/packages/tree/main/packages/go_router
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+go_router%22

Expand Down
4 changes: 2 additions & 2 deletions packages/go_router/test/matching_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,8 @@ void main() {
);
final RouteMatchListCodec codec = RouteMatchListCodec(configuration);

final RouteMatchList list1 = configuration.findMatch('/a');
final RouteMatchList list2 = configuration.findMatch('/b');
final RouteMatchList list1 = configuration.findMatch(Uri.parse('/a'));
final RouteMatchList list2 = configuration.findMatch(Uri.parse('/b'));
list1.push(ImperativeRouteMatch(
pageKey: const ValueKey<String>('/b-p0'),
matches: list2,
Expand Down
62 changes: 62 additions & 0 deletions packages/go_router/test/parser_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,68 @@ void main() {
expect(matches[1].route, routes[0].routes[0]);
});

testWidgets('GoRouteInformationParser can handle empty path for non http uri',
(WidgetTester tester) async {
final List<GoRoute> routes = <GoRoute>[
GoRoute(
path: '/',
builder: (_, __) => const Placeholder(),
routes: <GoRoute>[
GoRoute(
path: 'abc',
builder: (_, __) => const Placeholder(),
),
],
),
];
final GoRouteInformationParser parser = await createParser(
tester,
routes: routes,
redirectLimit: 100,
redirect: (_, __) => null,
);

final BuildContext context = tester.element(find.byType(Router<Object>));

final RouteMatchList matchesObj =
await parser.parseRouteInformationWithDependencies(
createRouteInformation('elbaapp://domain'), context);
final List<RouteMatchBase> matches = matchesObj.matches;
expect(matches.length, 1);
expect(matchesObj.uri.toString(), 'elbaapp://domain/');
});

testWidgets('GoRouteInformationParser cleans up uri',
(WidgetTester tester) async {
final List<GoRoute> routes = <GoRoute>[
GoRoute(
path: '/',
builder: (_, __) => const Placeholder(),
routes: <GoRoute>[
GoRoute(
path: 'abc',
builder: (_, __) => const Placeholder(),
),
],
),
];
final GoRouteInformationParser parser = await createParser(
tester,
routes: routes,
redirectLimit: 100,
redirect: (_, __) => null,
);

final BuildContext context = tester.element(find.byType(Router<Object>));

final RouteMatchList matchesObj =
await parser.parseRouteInformationWithDependencies(
createRouteInformation('http://domain/abc/?query=bde'), context);
final List<RouteMatchBase> matches = matchesObj.matches;
expect(matches.length, 2);
expect(matchesObj.uri.toString(), 'http://domain/abc?query=bde');
});

testWidgets(
"GoRouteInformationParser can parse deeplink root route and maintain uri's scheme, host, query and fragment",
(WidgetTester tester) async {
Expand Down
21 changes: 0 additions & 21 deletions packages/go_router/test/path_utils_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -92,25 +92,4 @@ void main() {
verifyThrows('/', '');
verifyThrows('', '');
});

test('canonicalUri', () {
void verify(String path, String expected) =>
expect(canonicalUri(path), expected);
verify('/a', '/a');
verify('/a/', '/a');
verify('/', '/');
verify('/a/b/', '/a/b');
verify('https://www.example.com/', 'https://www.example.com/');
verify('https://www.example.com/a', 'https://www.example.com/a');
verify('https://www.example.com/a/', 'https://www.example.com/a');
verify('https://www.example.com/a/b/', 'https://www.example.com/a/b');
verify('https://www.example.com/?', 'https://www.example.com/');
verify('https://www.example.com/?a=b', 'https://www.example.com/?a=b');
verify('https://www.example.com/?a=/', 'https://www.example.com/?a=/');
verify('https://www.example.com/a/?b=c', 'https://www.example.com/a?b=c');
verify('https://www.example.com/#a/', 'https://www.example.com/#a/');

expect(() => canonicalUri('::::'), throwsA(isA<FormatException>()));
expect(() => canonicalUri(''), throwsA(anything));
});
}