Line data Source code
1 : import 'package:beamer/beamer.dart';
2 : import 'package:flutter/material.dart';
3 :
4 : abstract class Utils {
5 : /// Traverses [beamLocations] and returns the one whose one of
6 : /// `pathBlueprints` contains the [uri], ignoring concrete path parameters.
7 : ///
8 : /// Upon finding such [BeamLocation], configures it with
9 : /// `pathParameters` and `queryParameters` from [uri].
10 : ///
11 : /// If [beamLocations] don't contain a match, [NotFound] will be returned
12 : /// configured with [uri].
13 3 : static BeamLocation chooseBeamLocation(
14 : Uri uri,
15 : List<BeamLocation> beamLocations, {
16 : Map<String, dynamic> data = const <String, dynamic>{},
17 : }) {
18 6 : for (var beamLocation in beamLocations) {
19 3 : if (canBeamLocationHandleUri(beamLocation, uri)) {
20 : return beamLocation
21 6 : ..state = beamLocation.createState(
22 3 : BeamState(
23 3 : pathBlueprintSegments: uri.pathSegments,
24 3 : queryParameters: uri.queryParameters,
25 : data: data,
26 : ),
27 : );
28 : }
29 : }
30 4 : return NotFound(path: uri.path);
31 : }
32 :
33 : /// Can a [beamLocation], depending on its `pathBlueprints`, handle the [uri].
34 : ///
35 : /// Used in [BeamLocation.canHandle] and [chooseBeamLocation].
36 3 : static bool canBeamLocationHandleUri(BeamLocation beamLocation, Uri uri) {
37 6 : for (var pathBlueprint in beamLocation.pathBlueprints) {
38 3 : if (pathBlueprint is String) {
39 9 : if (pathBlueprint == uri.path || pathBlueprint == '/*') {
40 : return true;
41 : }
42 6 : final uriPathSegments = List.from(uri.pathSegments);
43 10 : if (uriPathSegments.length > 1 && uriPathSegments.last == '') {
44 1 : uriPathSegments.removeLast();
45 : }
46 : final beamLocationPathBlueprintSegments =
47 6 : Uri.parse(pathBlueprint).pathSegments;
48 9 : if (uriPathSegments.length > beamLocationPathBlueprintSegments.length &&
49 1 : !beamLocationPathBlueprintSegments.contains('*')) {
50 : continue;
51 : }
52 : var checksPassed = true;
53 9 : for (int i = 0; i < uriPathSegments.length; i++) {
54 6 : if (beamLocationPathBlueprintSegments[i] == '*') {
55 : checksPassed = true;
56 : break;
57 : }
58 9 : if (uriPathSegments[i] != beamLocationPathBlueprintSegments[i] &&
59 9 : beamLocationPathBlueprintSegments[i][0] != ':') {
60 : checksPassed = false;
61 : break;
62 : }
63 : }
64 : if (checksPassed) {
65 : return true;
66 : }
67 : } else {
68 1 : final regexp = tryCastToRegExp(pathBlueprint);
69 2 : return regexp.hasMatch(uri.toString());
70 : }
71 : }
72 : return false;
73 : }
74 :
75 : /// Creates a state for [BeamLocation] based on incoming [uri].
76 : ///
77 : /// Used in [BeamState.copyForLocation].
78 9 : static BeamState createBeamState(
79 : Uri uri, {
80 : BeamLocation? beamLocation,
81 : Map<String, dynamic> data = const <String, dynamic>{},
82 : }) {
83 : if (beamLocation != null) {
84 : // TODO: abstract this and reuse in canBeamLocationHandleUri
85 16 : for (var pathBlueprint in beamLocation.pathBlueprints) {
86 8 : if (pathBlueprint is String) {
87 23 : if (pathBlueprint == uri.path || pathBlueprint == '/*') {
88 8 : BeamState(
89 8 : pathBlueprintSegments: uri.pathSegments,
90 8 : queryParameters: uri.queryParameters,
91 : data: data,
92 : );
93 : }
94 16 : final uriPathSegments = List.from(uri.pathSegments);
95 30 : if (uriPathSegments.length > 1 && uriPathSegments.last == '') {
96 1 : uriPathSegments.removeLast();
97 : }
98 : final beamLocationPathBlueprintSegments =
99 16 : Uri.parse(pathBlueprint).pathSegments;
100 8 : var pathSegments = <String>[];
101 8 : var pathParameters = <String, String>{};
102 16 : if (uriPathSegments.length >
103 8 : beamLocationPathBlueprintSegments.length &&
104 6 : !beamLocationPathBlueprintSegments.contains('*')) {
105 : continue;
106 : }
107 : var checksPassed = true;
108 24 : for (int i = 0; i < uriPathSegments.length; i++) {
109 16 : if (beamLocationPathBlueprintSegments[i] == '*') {
110 5 : pathSegments = List<String>.from(uriPathSegments);
111 : checksPassed = true;
112 : break;
113 : }
114 24 : if (uriPathSegments[i] != beamLocationPathBlueprintSegments[i] &&
115 12 : beamLocationPathBlueprintSegments[i][0] != ':') {
116 : checksPassed = false;
117 : break;
118 24 : } else if (beamLocationPathBlueprintSegments[i][0] == ':') {
119 10 : pathParameters[beamLocationPathBlueprintSegments[i]
120 10 : .substring(1)] = uriPathSegments[i];
121 10 : pathSegments.add(beamLocationPathBlueprintSegments[i]);
122 : } else {
123 16 : pathSegments.add(uriPathSegments[i]);
124 : }
125 : }
126 : if (checksPassed) {
127 8 : return BeamState(
128 : pathBlueprintSegments: pathSegments,
129 : pathParameters: pathParameters,
130 8 : queryParameters: uri.queryParameters,
131 : data: data,
132 : );
133 : }
134 : } else {
135 2 : final regexp = tryCastToRegExp(pathBlueprint);
136 2 : var pathParameters = <String, String>{};
137 2 : final url = uri.toString();
138 :
139 2 : if (regexp.hasMatch(url)) {
140 6 : regexp.allMatches(url).forEach((match) {
141 5 : match.groupNames.forEach((groupName) {
142 2 : pathParameters[groupName] = match.namedGroup(groupName) ?? '';
143 : });
144 : });
145 2 : return BeamState(
146 2 : pathBlueprintSegments: uri.pathSegments,
147 : pathParameters: pathParameters,
148 2 : queryParameters: uri.queryParameters,
149 : data: data,
150 : );
151 : }
152 : }
153 : }
154 : }
155 9 : return BeamState(
156 9 : pathBlueprintSegments: uri.pathSegments,
157 9 : queryParameters: uri.queryParameters,
158 : data: data,
159 : );
160 : }
161 :
162 6 : static bool urisMatch(dynamic blueprint, Uri exact) {
163 6 : if (blueprint is String) {
164 6 : blueprint = Uri.parse(blueprint);
165 6 : final blueprintSegments = blueprint.pathSegments;
166 6 : final exactSegment = exact.pathSegments;
167 18 : if (blueprintSegments.length != exactSegment.length) {
168 : return false;
169 : }
170 18 : for (int i = 0; i < blueprintSegments.length; i++) {
171 12 : if (blueprintSegments[i].startsWith(':')) {
172 : continue;
173 : }
174 18 : if (blueprintSegments[i] != exactSegment[i]) {
175 : return false;
176 : }
177 : }
178 : return true;
179 : } else {
180 1 : blueprint = tryCastToRegExp(blueprint);
181 2 : return blueprint.hasMatch(exact.toString());
182 : }
183 : }
184 :
185 : /// Wraps the casting of pathBlueprint to RegExp inside a try-catch
186 : /// and throws a nice FlutterError.
187 3 : static RegExp tryCastToRegExp(dynamic pathBlueprint) {
188 : try {
189 : return pathBlueprint as RegExp;
190 1 : } on TypeError catch (_) {
191 2 : throw FlutterError.fromParts([
192 1 : DiagnosticsNode.message('Path blueprint can either be:',
193 : level: DiagnosticLevel.summary),
194 1 : DiagnosticsNode.message('1. String'),
195 1 : DiagnosticsNode.message('2. RegExp instance')
196 : ]);
197 : }
198 : }
199 :
200 : /// Removes the trailing / in an URI String and returns the result.
201 : ///
202 : /// If there is no trailing /, returns the input.
203 9 : static String trimmed(String uri) {
204 27 : if (uri.length > 1 && uri.endsWith('/')) {
205 3 : return uri.substring(0, uri.length - 1);
206 : }
207 : return uri;
208 : }
209 : }
|