blob: 321541d32016bd5018f24e894d7a38ac9e5876f0 [file] [log] [blame]
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'configuration.dart';
import 'logging.dart';
import 'path_utils.dart';
import 'typedefs.dart';
export 'route.dart';
export 'state.dart';
/// The route configuration for GoRouter configured by the app.
class RouteConfiguration {
/// Constructs a [RouteConfiguration].
required this.routes,
required this.redirectLimit,
required this.topRedirect,
}) {
_cacheNameToPath('', routes);
assert(() {;
return true;
for (final GoRoute route in routes) {
if (!route.path.startsWith('/')) {
throw RouteConfigurationError(
'top-level path must start with "/": ${route.path}');
/// The list of top level routes used by [GoRouterDelegate].
final List<GoRoute> routes;
/// The limit for the number of consecutive redirects.
final int redirectLimit;
/// Top level page redirect.
final GoRouterRedirect topRedirect;
final Map<String, String> _nameToPath = <String, String>{};
/// Looks up the url location by a [GoRoute]'s name.
String namedLocation(
String name, {
Map<String, String> params = const <String, String>{},
Map<String, String> queryParams = const <String, String>{},
}) {
assert(() {'getting location for name: '
'${params.isEmpty ? '' : ', params: $params'}'
'${queryParams.isEmpty ? '' : ', queryParams: $queryParams'}');
return true;
final String keyName = name.toLowerCase();
assert(_nameToPath.containsKey(keyName), 'unknown route name: $name');
final String path = _nameToPath[keyName]!;
assert(() {
// Check that all required params are present
final List<String> paramNames = <String>[];
patternToRegExp(path, paramNames);
for (final String paramName in paramNames) {
'missing param "$paramName" for $path');
// Check that there are no extra params
for (final String key in params.keys) {
assert(paramNames.contains(key), 'unknown param "$key" for $path');
return true;
final Map<String, String> encodedParams = <String, String>{
for (final MapEntry<String, String> param in params.entries)
param.key: Uri.encodeComponent(param.value)
final String location = patternToPath(path, encodedParams);
return Uri(path: location, queryParameters: queryParams).toString();
String toString() {
return 'RouterConfiguration: $routes';
String _debugKnownRoutes() {
final StringBuffer sb = StringBuffer();
sb.writeln('Full paths for routes:');
_debugFullPathsFor(routes, '', 0, sb);
if (_nameToPath.isNotEmpty) {
sb.writeln('known full paths for route names:');
for (final MapEntry<String, String> e in _nameToPath.entries) {
sb.writeln(' ${e.key} => ${e.value}');
return sb.toString();
void _debugFullPathsFor(
List<GoRoute> routes, String parentFullpath, int depth, StringBuffer sb) {
for (final GoRoute route in routes) {
final String fullpath = concatenatePaths(parentFullpath, route.path);
sb.writeln(' => ${''.padLeft(depth * 2)}$fullpath');
_debugFullPathsFor(route.routes, fullpath, depth + 1, sb);
void _cacheNameToPath(String parentFullPath, List<GoRoute> childRoutes) {
for (final GoRoute route in childRoutes) {
final String fullPath = concatenatePaths(parentFullPath, route.path);
if ( != null) {
final String name =!.toLowerCase();
'duplication fullpaths for name '
'"$name":${_nameToPath[name]}, $fullPath');
_nameToPath[name] = fullPath;
if (route.routes.isNotEmpty) {
_cacheNameToPath(fullPath, route.routes);
/// Thrown when the [RouteConfiguration] is invalid.
class RouteConfigurationError extends Error {
/// [RouteConfigurationError] constructor.
/// The error message.
final String message;
String toString() => 'Route configuration error: $message';