diff --git a/packages/go_router/CHANGELOG.md b/packages/go_router/CHANGELOG.md index 519e547189e..137f8d2329d 100644 --- a/packages/go_router/CHANGELOG.md +++ b/packages/go_router/CHANGELOG.md @@ -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. diff --git a/packages/go_router/lib/src/configuration.dart b/packages/go_router/lib/src/configuration.dart index d1cca5ce61f..8be984ecb6f 100644 --- a/packages/go_router/lib/src/configuration.dart +++ b/packages/go_router/lib/src/configuration.dart @@ -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 ' @@ -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 pathParameters = {}; final List matches = _getLocRouteMatches(uri, pathParameters); @@ -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()) { 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); @@ -461,7 +459,7 @@ class RouteConfiguration { List redirectHistory, ) { try { - final RouteMatchList newMatch = findMatch(newLocation); + final RouteMatchList newMatch = findMatch(Uri.parse(newLocation)); _addRedirect(redirectHistory, newMatch, previousLocation); return newMatch; } on GoException catch (e) { diff --git a/packages/go_router/lib/src/match.dart b/packages/go_router/lib/src/match.dart index fba08f83865..4d24416284e 100644 --- a/packages/go_router/lib/src/match.dart +++ b/packages/go_router/lib/src/match.dart @@ -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? imperativeMatches = input[RouteMatchListCodec._imperativeMatchesKey] as List?; diff --git a/packages/go_router/lib/src/parser.dart b/packages/go_router/lib/src/parser.dart index 72f944b0c5a..d1981898a8b 100644 --- a/packages/go_router/lib/src/parser.dart +++ b/packages/go_router/lib/src/parser.dart @@ -80,25 +80,17 @@ class GoRouteInformationParser extends RouteInformationParser { }); } - 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}'); } diff --git a/packages/go_router/lib/src/path_utils.dart b/packages/go_router/lib/src/path_utils.dart index 49a7b33b3eb..872f7786b26 100644 --- a/packages/go_router/lib/src/path_utils.dart +++ b/packages/go_router/lib/src/path_utils.dart @@ -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+)(\((?:\\.|[^\\()])+\))?'); @@ -119,41 +118,6 @@ String concatenatePaths(String parentPath, String childPath) { return '${parentPath == '/' ? '' : parentPath}/$childPath'; } -/// Normalizes the location string. -String canonicalUri(String loc) { - 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 routes) { diff --git a/packages/go_router/pubspec.yaml b/packages/go_router/pubspec.yaml index bef70c82d06..6499a9cf4f2 100644 --- a/packages/go_router/pubspec.yaml +++ b/packages/go_router/pubspec.yaml @@ -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 diff --git a/packages/go_router/test/matching_test.dart b/packages/go_router/test/matching_test.dart index 89cbccb8a5e..f2dffbd3de1 100644 --- a/packages/go_router/test/matching_test.dart +++ b/packages/go_router/test/matching_test.dart @@ -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('/b-p0'), matches: list2, diff --git a/packages/go_router/test/parser_test.dart b/packages/go_router/test/parser_test.dart index 31b8a50e569..9cb4aa2a071 100644 --- a/packages/go_router/test/parser_test.dart +++ b/packages/go_router/test/parser_test.dart @@ -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 routes = [ + GoRoute( + path: '/', + builder: (_, __) => const Placeholder(), + routes: [ + 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)); + + final RouteMatchList matchesObj = + await parser.parseRouteInformationWithDependencies( + createRouteInformation('elbaapp://domain'), context); + final List matches = matchesObj.matches; + expect(matches.length, 1); + expect(matchesObj.uri.toString(), 'elbaapp://domain/'); + }); + + testWidgets('GoRouteInformationParser cleans up uri', + (WidgetTester tester) async { + final List routes = [ + GoRoute( + path: '/', + builder: (_, __) => const Placeholder(), + routes: [ + 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)); + + final RouteMatchList matchesObj = + await parser.parseRouteInformationWithDependencies( + createRouteInformation('http://domain/abc/?query=bde'), context); + final List 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 { diff --git a/packages/go_router/test/path_utils_test.dart b/packages/go_router/test/path_utils_test.dart index 5a656df135e..a495e4d1a05 100644 --- a/packages/go_router/test/path_utils_test.dart +++ b/packages/go_router/test/path_utils_test.dart @@ -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())); - expect(() => canonicalUri(''), throwsA(anything)); - }); }