blob: 7d08492119433ac8ad9f486c034030d06c8bb0ca [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 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import 'matching.dart';
import 'path_utils.dart';
import 'route.dart';
/// An instance of a GoRoute plus information about the current location.
class RouteMatch {
/// Constructor for [RouteMatch].
RouteMatch({
required this.route,
required this.subloc,
required this.fullpath,
required this.encodedParams,
required this.queryParams,
required this.queryParametersAll,
required this.extra,
required this.error,
this.pageKey,
}) : fullUriString = _addQueryParams(subloc, queryParametersAll),
assert(Uri.parse(subloc).queryParameters.isEmpty),
assert(Uri.parse(fullpath).queryParameters.isEmpty),
assert(() {
for (final MapEntry<String, String> p in encodedParams.entries) {
assert(p.value == Uri.encodeComponent(Uri.decodeComponent(p.value)),
'encodedParams[${p.key}] is not encoded properly: "${p.value}"');
}
return true;
}());
// ignore: public_member_api_docs
static RouteMatch? match({
required RouteBase route,
required String restLoc, // e.g. person/p1
required String parentSubloc, // e.g. /family/f2
required String fullpath, // e.g. /family/:fid/person/:pid
required Map<String, String> queryParams,
required Map<String, List<String>> queryParametersAll,
required Object? extra,
}) {
if (route is ShellRoute) {
return RouteMatch(
route: route,
subloc: restLoc,
fullpath: '',
encodedParams: <String, String>{},
queryParams: queryParams,
queryParametersAll: queryParametersAll,
extra: extra,
error: null,
// Provide a unique pageKey to ensure that the page for this ShellRoute is
// reused.
pageKey: ValueKey<String>(route.hashCode.toString()),
);
} else if (route is GoRoute) {
assert(!route.path.contains('//'));
final RegExpMatch? match = route.matchPatternAsPrefix(restLoc);
if (match == null) {
return null;
}
final Map<String, String> encodedParams = route.extractPathParams(match);
final String pathLoc = patternToPath(route.path, encodedParams);
final String subloc = concatenatePaths(parentSubloc, pathLoc);
return RouteMatch(
route: route,
subloc: subloc,
fullpath: fullpath,
encodedParams: encodedParams,
queryParams: queryParams,
queryParametersAll: queryParametersAll,
extra: extra,
error: null,
);
}
throw MatcherError('Unexpected route type: $route', restLoc);
}
/// The matched route.
final RouteBase route;
/// The matched location.
final String subloc; // e.g. /family/f2
/// The matched template.
final String fullpath; // e.g. /family/:fid
/// Parameters for the matched route, URI-encoded.
final Map<String, String> encodedParams;
/// The URI query split into a map according to the rules specified for FORM
/// post in the [HTML 4.01 specification section
/// 17.13.4](https://www.w3.org/TR/REC-html40/interact/forms.html#h-17.13.4
/// "HTML 4.01 section 17.13.4").
///
/// If a key occurs more than once in the query string, it is mapped to an
/// arbitrary choice of possible value.
///
/// If the request is `a/b/?q1=v1&q2=v2&q2=v3`, then [queryParameter] will be
/// `{q1: 'v1', q2: 'v2'}`.
///
/// See also
/// * [queryParametersAll] that can provide a map that maps keys to all of
/// their values.
final Map<String, String> queryParams;
/// Returns the URI query split into a map according to the rules specified
/// for FORM post in the [HTML 4.01 specification section
/// 17.13.4](https://www.w3.org/TR/REC-html40/interact/forms.html#h-17.13.4
/// "HTML 4.01 section 17.13.4").
///
/// Keys are mapped to lists of their values. If a key occurs only once, its
/// value is a singleton list. If a key occurs with no value, the empty string
/// is used as the value for that occurrence.
///
/// If the request is `a/b/?q1=v1&q2=v2&q2=v3`, then [queryParameterAll] with
/// be `{q1: ['v1'], q2: ['v2', 'v3']}`.
final Map<String, List<String>> queryParametersAll;
/// An extra object to pass along with the navigation.
final Object? extra;
/// An exception if there was an error during matching.
final Exception? error;
/// Optional value key of type string, to hold a unique reference to a page.
final ValueKey<String>? pageKey;
/// The full uri string
final String fullUriString; // e.g. /family/12?query=14
static String _addQueryParams(
String loc, Map<String, dynamic> queryParametersAll) {
final Uri uri = Uri.parse(loc);
assert(uri.queryParameters.isEmpty);
return Uri(
path: uri.path,
queryParameters:
queryParametersAll.isEmpty ? null : queryParametersAll)
.toString();
}
/// Parameters for the matched route, URI-decoded.
Map<String, String> get decodedParams => <String, String>{
for (final MapEntry<String, String> param in encodedParams.entries)
param.key: Uri.decodeComponent(param.value)
};
/// For use by the Router architecture as part of the RouteMatch
@override
String toString() => 'RouteMatch($fullpath, $encodedParams)';
}