LCOV - code coverage report
Current view: top level - src/vroute_elements - vpath.dart (source / functions) Hit Total Coverage
Test: lcov.info Lines: 227 251 90.4 %
Date: 2021-04-26 23:10:51 Functions: 0 0 -

          Line data    Source code
       1             : part of '../main.dart';
       2             : 
       3             : /// If the [VRouteElement] contains a path, it should extend this class
       4             : ///
       5             : /// What is does is:
       6             : ///   - Requiring attributes [path], [name], [aliases]
       7             : ///   - Computing attributes [pathRegExp], [aliasesRegExp], [pathParametersKeys] and [aliasesPathParametersKeys]
       8             : ///   - implementing a default [buildRoute] and [getPathFromName] methods for them
       9             : @immutable
      10             : class VPath extends VRouteElement with VoidVGuard, VoidVPopHandler {
      11             :   /// The path (relative or absolute) or this [VRouteElement]
      12             :   ///
      13             :   /// If the path of a subroute is exactly matched, this will be used in
      14             :   /// the route but might be covered by another [VRouteElement.widget]
      15             :   /// The value of the path ca have three form:
      16             :   ///     * starting with '/': The path will be treated as a route path,
      17             :   ///       this is useful to take full advantage of nested routes while
      18             :   ///       conserving the freedom of path naming
      19             :   ///     * not starting with '/': The path corresponding to this route
      20             :   ///       will be the path of the parent route + this path. If this is used
      21             :   ///       directly in the [VRouter] routes, a '/' will be added anyway
      22             :   ///     * be null: In this case this path will match the parent path
      23             :   ///
      24             :   /// Note we use the package [path_to_regexp](https://pub.dev/packages/path_to_regexp)
      25             :   /// so you can use naming such as /user/:id to get the id (see [VRouteElementData.pathParameters]
      26             :   /// You can also use more advance technique using regexp directly in your path, for example
      27             :   /// '.*' will match any route, '/user/:id(\d+)' will match any route starting with user
      28             :   /// and followed by a digit. Here is a recap:
      29             :   /// |     pattern       | matched path |      [VRouter.pathParameters]
      30             :   /// | /user/:username |  /user/evan  |         { username: 'evan' }
      31             :   /// | /user/:id(\d+)  |  /user/123   |             { id: '123' }
      32             :   /// |     .*          |  every path  |             -
      33             :   final String? path;
      34             : 
      35             :   /// A name for the route which will allow you to easily navigate to it
      36             :   /// using [VRouter.of(context).pushNamed]
      37             :   ///
      38             :   /// Note that [name] should be unique w.r.t every [VRouteElement]
      39             :   final String? name;
      40             : 
      41             :   /// Alternative paths that will be matched to this route
      42             :   ///
      43             :   /// Note that path is match first, then every aliases in order
      44             :   final List<String> aliases;
      45             : 
      46             :   /// A boolean to indicate whether this can be a valid [VRouteElement] of the [VRoute] if no
      47             :   /// [VRouteElement] in its [stackedRoute] is matched
      48             :   ///
      49             :   /// This is mainly useful for [VRouteElement]s which are NOT [VRouteElementWithPage]
      50             :   final bool mustMatchStackedRoute;
      51             : 
      52             :   /// The routes which should be included if the constraint on this path are met
      53             :   final List<VRouteElement> stackedRoutes;
      54             : 
      55          14 :   VPath({
      56             :     required this.path,
      57             :     this.name,
      58             :     this.aliases = const [],
      59             :     this.mustMatchStackedRoute = false,
      60             :     required this.stackedRoutes,
      61             :   }) {
      62          56 :     pathRegExp = (path != null) ? pathToRegExp(path!, prefix: true) : null;
      63          46 :     aliasesRegExp = [for (var alias in aliases) pathToRegExp(alias, prefix: true)];
      64          28 :     pathParametersKeys = <String>[];
      65          64 :     aliasesPathParametersKeys = List<List<String>>.generate(aliases.length, (_) => []);
      66             : 
      67             :     // Get local parameters
      68          14 :     if (path != null) {
      69          63 :       final localPath = path!.startsWith('/') ? path!.substring(1) : path!;
      70          28 :       pathToRegExp(localPath, parameters: pathParametersKeys);
      71             :     }
      72             : 
      73          46 :     for (var i = 0; i < aliases.length; i++) {
      74           8 :       final alias = aliases[i];
      75          12 :       final localPath = alias[i].startsWith('/') ? alias.substring(1) : alias;
      76          12 :       pathToRegExp(localPath, parameters: aliasesPathParametersKeys[i]);
      77             :     }
      78             :   }
      79             : 
      80             :   /// RegExp version of the path
      81             :   /// It is created automatically
      82             :   /// If the path starts with '/', it is removed from
      83             :   /// this regExp.
      84             :   late final RegExp? pathRegExp;
      85             : 
      86             :   /// RegExp version of the aliases
      87             :   /// It is created automatically
      88             :   /// If an alias starts with '/', it is removed from
      89             :   /// this regExp.
      90             :   late final List<RegExp> aliasesRegExp;
      91             : 
      92             :   /// Parameters of the path
      93             :   /// It is created automatically
      94             :   late final List<String> pathParametersKeys;
      95             : 
      96             :   /// Parameters of the aliases if any
      97             :   /// It is created automatically
      98             :   late final List<List<String>> aliasesPathParametersKeys;
      99             : 
     100             :   /// What this [buildRoute] does is look if any path or alias can give a valid [VRoute]
     101             :   /// considering this and the stackedRoutes
     102             :   ///
     103             :   /// For more about buildRoute, see [VRouteElement.buildRoute]
     104          12 :   @override
     105             :   VRoute? buildRoute(
     106             :     VPathRequestData vPathRequestData, {
     107             :     required VPathMatch parentVPathMatch,
     108             :   }) {
     109             :     // This will hold the GetPathMatchResult for the path so that we compute it only once
     110             :     late final VPathMatch pathMatch;
     111             : 
     112             :     // This will hold every GetPathMatchResult for the aliases so that we compute them only once
     113          12 :     List<VPathMatch> aliasesMatch = [];
     114             : 
     115             :     // Try to find valid VRoute from stackedRoutes
     116             : 
     117             :     // Check for the path
     118          12 :     pathMatch = getPathMatch(
     119          12 :       entirePath: vPathRequestData.path,
     120          12 :       selfPath: path,
     121          12 :       selfPathRegExp: pathRegExp,
     122          12 :       selfPathParametersKeys: pathParametersKeys,
     123             :       parentVPathMatch: parentVPathMatch,
     124             :     );
     125          12 :     final VRoute? stackedRouteVRoute = getVRouteFromRoutes(
     126             :       vPathRequestData,
     127          12 :       routes: stackedRoutes,
     128           0 :       vPathMatch: pathMatch,
     129             :     );
     130             :     if (stackedRouteVRoute != null) {
     131          12 :       return VRoute(
     132          12 :         vRouteElementNode: VRouteElementNode(
     133             :           this,
     134          12 :           localPath: pathMatch.localPath,
     135          12 :           stackedVRouteElementNode: stackedRouteVRoute.vRouteElementNode,
     136             :         ),
     137          12 :         pages: stackedRouteVRoute.pages,
     138          12 :         pathParameters: stackedRouteVRoute.pathParameters,
     139          36 :         vRouteElements: <VRouteElement>[this] + stackedRouteVRoute.vRouteElements,
     140             :       );
     141             :     }
     142             : 
     143             :     // Check for the aliases
     144          38 :     for (var i = 0; i < aliases.length; i++) {
     145           2 :       aliasesMatch.add(
     146           2 :         getPathMatch(
     147           2 :           entirePath: vPathRequestData.path,
     148           4 :           selfPath: aliases[i],
     149           4 :           selfPathRegExp: aliasesRegExp[i],
     150           4 :           selfPathParametersKeys: aliasesPathParametersKeys[i],
     151             :           parentVPathMatch: parentVPathMatch,
     152             :         ),
     153             :       );
     154           2 :       final VRoute? stackedRouteVRoute = getVRouteFromRoutes(
     155             :         vPathRequestData,
     156           2 :         routes: stackedRoutes,
     157           2 :         vPathMatch: aliasesMatch[i],
     158             :       );
     159             :       if (stackedRouteVRoute != null) {
     160           2 :         return VRoute(
     161           2 :           vRouteElementNode: VRouteElementNode(
     162             :             this,
     163           2 :             localPath: pathMatch.localPath,
     164           2 :             stackedVRouteElementNode: stackedRouteVRoute.vRouteElementNode,
     165             :           ),
     166           2 :           pages: stackedRouteVRoute.pages,
     167           2 :           pathParameters: stackedRouteVRoute.pathParameters,
     168           6 :           vRouteElements: <VRouteElement>[this] + stackedRouteVRoute.vRouteElements,
     169             :         );
     170             :       }
     171             :     }
     172             : 
     173             :     // Else, if no subroute is valid
     174             : 
     175             :     // check if this is an exact match with path
     176          12 :     final vRoute = getVRouteFromSelf(
     177             :       vPathRequestData,
     178           0 :       vPathMatch: pathMatch,
     179             :     );
     180             :     if (vRoute != null) {
     181             :       return vRoute;
     182             :     }
     183             : 
     184             :     // Check exact match for the aliases
     185          38 :     for (var i = 0; i < aliases.length; i++) {
     186           2 :       final vRoute = getVRouteFromSelf(
     187             :         vPathRequestData,
     188           2 :         vPathMatch: aliasesMatch[i],
     189             :       );
     190             :       if (vRoute != null) {
     191             :         return vRoute;
     192             :       }
     193             :     }
     194             : 
     195             :     // Else return null
     196             :     return null;
     197             :   }
     198             : 
     199             :   /// Searches for a valid [VRoute] by asking [VRouteElement]s is [routes] if they can form a valid [VRoute]
     200          12 :   VRoute? getVRouteFromRoutes(
     201             :     VPathRequestData vPathRequestData, {
     202             :     required List<VRouteElement> routes,
     203             :     required VPathMatch vPathMatch,
     204             :   }) {
     205          24 :     for (var vRouteElement in routes) {
     206          12 :       final childVRoute = vRouteElement.buildRoute(
     207             :         vPathRequestData,
     208             :         parentVPathMatch: vPathMatch,
     209             :       );
     210             :       if (childVRoute != null) return childVRoute;
     211             :     }
     212             :   }
     213             : 
     214             :   /// Try to form a [VRoute] where this [VRouteElement] is the last [VRouteElement]
     215             :   /// This is possible is:
     216             :   ///   - [mustMatchStackedRoute] is false
     217             :   ///   - There is a match of the path and it is exact
     218          12 :   VRoute? getVRouteFromSelf(
     219             :     VPathRequestData vPathRequestData, {
     220             :     required VPathMatch vPathMatch,
     221             :   }) {
     222          12 :     if (!mustMatchStackedRoute &&
     223          12 :         vPathMatch is ValidVPathMatch &&
     224          14 :         (vPathMatch.remainingPath.isEmpty)) {
     225           3 :       return VRoute(
     226           6 :         vRouteElementNode: VRouteElementNode(this, localPath: vPathMatch.localPath),
     227           3 :         pages: [],
     228           3 :         pathParameters: vPathMatch.pathParameters,
     229           3 :         vRouteElements: <VRouteElement>[this],
     230             :       );
     231             :     }
     232             :   }
     233             : 
     234             :   /// Returns path information given a local path.
     235             :   ///
     236             :   /// [entirePath] is the whole path, useful when [selfPathRegExp] is absolute
     237             :   /// [remainingPathFromParent] is the path that remain after removing the parent paths, useful when [selfPathRegExp] relative
     238             :   /// [selfPathRegExp] the RegExp corresponding to the path that should be tested
     239             :   ///
     240             :   /// Returns a [VPathMatch] which holds two information:
     241             :   ///   - The remaining path, after having removed the [selfPathRegExp] (null if there is no match)
     242             :   ///   - The path parameters gotten from [selfPathRegExp] and the path, added to the parentPathParameters if relative path
     243          12 :   VPathMatch getPathMatch(
     244             :       {required String entirePath,
     245             :       required String? selfPath,
     246             :       required RegExp? selfPathRegExp,
     247             :       required List<String> selfPathParametersKeys,
     248             :       required VPathMatch parentVPathMatch}) {
     249             : 
     250             :     if (selfPath == null) {
     251             :       return parentVPathMatch;
     252             :     }
     253             : 
     254             :     // if selfPath is not null, neither should selfPathRegExp be
     255           0 :     assert(selfPathRegExp != null);
     256             : 
     257             :     // If our path starts with '/', this is an absolute path
     258          12 :     if ((selfPath.startsWith('/'))) {
     259          12 :       final match = selfPathRegExp!.matchAsPrefix(entirePath);
     260             : 
     261             :       if (match == null) {
     262          24 :         return InvalidVPathMatch(localPath: getConstantLocalPath());
     263             :       }
     264             : 
     265          24 :       var remainingPath = entirePath.substring(match.end);
     266          36 :       final localPath = entirePath.substring(match.start, match.end);
     267          12 :       final pathParameters = extract(selfPathParametersKeys, match)
     268          20 :         ..updateAll((key, value) => Uri.decodeComponent(value));
     269             : 
     270             :       // Remove the trailing '/' in remainingPath if needed
     271          15 :       if (remainingPath.startsWith('/')) remainingPath = remainingPath.replaceFirst('/', '');
     272             : 
     273          12 :       return ValidVPathMatch(
     274             :         remainingPath: remainingPath,
     275             :         pathParameters: pathParameters,
     276             :         localPath: localPath,
     277             :       );
     278             :     }
     279             : 
     280             :     // Else our path is relative
     281           5 :     final String? thisConstantLocalPath = getConstantLocalPath();
     282             :     final String? constantLocalPath =
     283           5 :         (parentVPathMatch.localPath == null && thisConstantLocalPath == null)
     284             :             ? null
     285           5 :             : (parentVPathMatch.localPath == null)
     286             :             ? thisConstantLocalPath
     287             :             : (thisConstantLocalPath == null)
     288           0 :             ? parentVPathMatch.localPath
     289           0 :             : parentVPathMatch.localPath! +
     290           0 :                 (parentVPathMatch.localPath!.endsWith('/') ? '' : '/') +
     291             :                 thisConstantLocalPath;
     292             : 
     293           5 :     if (parentVPathMatch is ValidVPathMatch) {
     294             :       // We try to remove this part of the path from the remainingPathFromParent
     295          10 :       final match = selfPathRegExp!.matchAsPrefix(parentVPathMatch.remainingPath);
     296             : 
     297             :       if (match == null) {
     298           5 :         return InvalidVPathMatch(localPath: constantLocalPath);
     299             :       }
     300             : 
     301          15 :       var remainingPath = parentVPathMatch.remainingPath.substring(match.end);
     302          20 :       final localPath = parentVPathMatch.remainingPath.substring(match.start, match.end);
     303           5 :       final pathParameters = {
     304           5 :         ...parentVPathMatch.pathParameters,
     305           5 :         ...extract(selfPathParametersKeys, match)
     306          11 :           ..updateAll((key, value) => Uri.decodeComponent(value)),
     307             :       };
     308             : 
     309             :       // Remove the trailing '/' in remainingPath if needed
     310           5 :       if (remainingPath.startsWith('/')) remainingPath = remainingPath.replaceFirst('/', '');
     311             : 
     312           5 :       return ValidVPathMatch(
     313             :         remainingPath: remainingPath,
     314             :         pathParameters: pathParameters,
     315             :         localPath: localPath,
     316             :       );
     317             :     }
     318             : 
     319           2 :     return InvalidVPathMatch(localPath: constantLocalPath);
     320             :   }
     321             : 
     322             :   /// Tries to a path from a name
     323             :   ///
     324             :   /// This first asks its stackedRoutes if they have a match
     325             :   /// Else is tries to see if this [VRouteElement] matches the name
     326             :   /// Else return null
     327             :   ///
     328             :   /// Note that not only the name must match but the path parameters must be able to form a
     329             :   /// valid path afterward too
     330           7 :   GetPathFromNameResult getPathFromName(
     331             :     String nameToMatch, {
     332             :     required Map<String, String> pathParameters,
     333             :     required GetNewParentPathResult parentPathResult,
     334             :     required Map<String, String> remainingPathParameters,
     335             :   }) {
     336             :     // A variable to store the new parentPath from the path and aliases
     337           7 :     final List<GetNewParentPathResult> newParentPathResults = [];
     338           7 :     final List<Map<String, String>> newRemainingPathParameters = [];
     339             : 
     340           7 :     final List<GetPathFromNameResult> nameErrorResults = [];
     341             : 
     342             :     // Check if any subroute matches the name using path
     343             : 
     344             :     // Get the new parent path by taking this path into account
     345           7 :     newParentPathResults.add(
     346           7 :       getNewParentPath(
     347             :         parentPathResult,
     348           7 :         thisPath: path,
     349           7 :         thisPathParametersKeys: pathParametersKeys,
     350             :         pathParameters: pathParameters,
     351             :       ),
     352             :     );
     353             : 
     354           7 :     newRemainingPathParameters.add(
     355          21 :       (path != null && path!.startsWith('/'))
     356           7 :           ? Map<String, String>.from(pathParameters)
     357           4 :           : Map<String, String>.from(remainingPathParameters)
     358          19 :         ..removeWhere((key, value) => pathParametersKeys.contains(key)),
     359             :     );
     360             : 
     361          14 :     for (var vRouteElement in stackedRoutes) {
     362           7 :       GetPathFromNameResult childPathFromNameResult = vRouteElement.getPathFromName(
     363             :         nameToMatch,
     364             :         pathParameters: pathParameters,
     365           7 :         parentPathResult: newParentPathResults.last,
     366           7 :         remainingPathParameters: newRemainingPathParameters.last,
     367             :       );
     368           7 :       if (childPathFromNameResult is ValidNameResult) {
     369             :         return childPathFromNameResult;
     370             :       } else {
     371           7 :         nameErrorResults.add(childPathFromNameResult);
     372             :       }
     373             :     }
     374             : 
     375             :     // Check if any subroute matches the name using aliases
     376             : 
     377          24 :     for (var i = 0; i < aliases.length; i++) {
     378             :       // Get the new parent path by taking this alias into account
     379           6 :       newParentPathResults.add(getNewParentPath(
     380             :         parentPathResult,
     381           6 :         thisPath: aliases[i],
     382           6 :         thisPathParametersKeys: aliasesPathParametersKeys[i],
     383             :         pathParameters: pathParameters,
     384             :       ));
     385           3 :       newRemainingPathParameters.add(
     386           9 :         (aliases[i].startsWith('/'))
     387           3 :             ? Map<String, String>.from(pathParameters)
     388           0 :             : Map<String, String>.from(remainingPathParameters)
     389           7 :           ..removeWhere((key, value) => aliasesPathParametersKeys[i].contains(key)),
     390             :       );
     391           6 :       for (var vRouteElement in stackedRoutes) {
     392           3 :         GetPathFromNameResult childPathFromNameResult = vRouteElement.getPathFromName(
     393             :           nameToMatch,
     394             :           pathParameters: pathParameters,
     395           3 :           parentPathResult: newParentPathResults.last,
     396           3 :           remainingPathParameters: newRemainingPathParameters.last,
     397             :         );
     398           3 :         if (childPathFromNameResult is ValidNameResult) {
     399             :           return childPathFromNameResult;
     400             :         } else {
     401           3 :           nameErrorResults.add(childPathFromNameResult);
     402             :         }
     403             :       }
     404             :     }
     405             : 
     406             :     // If no subroute matches the name, try to match this name
     407          14 :     if (name == nameToMatch) {
     408             :       // If path or any alias is valid considering the given path parameters, return this
     409          18 :       for (int i = 0; i < newParentPathResults.length; i++) {
     410           7 :         var newParentPathResult = newParentPathResults[i];
     411           7 :         if (newParentPathResult is ValidParentPathResult) {
     412           7 :           if (newParentPathResult.path == null) {
     413             :             // If this path is null, we add a NullPathErrorNameResult
     414           2 :             nameErrorResults.add(NullPathErrorNameResult(name: nameToMatch));
     415             :           } else {
     416           7 :             final newRemainingPathParameter = newRemainingPathParameters[i];
     417           7 :             if (newRemainingPathParameter.isNotEmpty) {
     418             :               // If there are path parameters remaining, wee add a PathParamsErrorsNameResult
     419           2 :               nameErrorResults.add(
     420           2 :                 PathParamsErrorsNameResult(
     421             :                   name: nameToMatch,
     422           2 :                   values: [
     423           2 :                     OverlyPathParamsError(
     424           4 :                       pathParams: pathParameters.keys.toList(),
     425           6 :                       expectedPathParams: newParentPathResult.pathParameters.keys.toList(),
     426             :                     ),
     427             :                   ],
     428             :                 ),
     429             :               );
     430             :             } else {
     431             :               // Else the result is valid
     432          12 :               return ValidNameResult(path: newParentPathResult.path!);
     433             :             }
     434             :           }
     435             :         } else {
     436           4 :           assert(newParentPathResult is PathParamsErrorNewParentPath);
     437           4 :           nameErrorResults.add(
     438           4 :             PathParamsErrorsNameResult(
     439             :               name: nameToMatch,
     440           4 :               values: [
     441           4 :                 MissingPathParamsError(
     442           8 :                   pathParams: pathParameters.keys.toList(),
     443             :                   missingPathParams:
     444           4 :                       (newParentPathResult as PathParamsErrorNewParentPath).pathParameters,
     445             :                 ),
     446             :               ],
     447             :             ),
     448             :           );
     449             :         }
     450             :       }
     451             :     }
     452             : 
     453             :     // If we don't have any valid result
     454             : 
     455             :     // If some stackedRoute returned PathParamsPopError, aggregate them
     456           5 :     final pathParamsNameErrors = PathParamsErrorsNameResult(
     457             :       name: nameToMatch,
     458           5 :       values: nameErrorResults.fold<List<PathParamsError>>(
     459           5 :         <PathParamsError>[],
     460           5 :         (previousValue, element) {
     461           5 :           return previousValue +
     462          12 :               ((element is PathParamsErrorsNameResult) ? element.values : []);
     463             :         },
     464             :       ),
     465             :     );
     466             : 
     467             :     // If there was any PathParamsPopError, we have some pathParamsPopErrors.values
     468             :     // and therefore should return this
     469          10 :     if (pathParamsNameErrors.values.isNotEmpty) {
     470             :       return pathParamsNameErrors;
     471             :     }
     472             : 
     473             :     // Else try to find a NullPathError
     474             :     if (nameErrorResults
     475          20 :             .indexWhere((childNameResult) => childNameResult is NullPathErrorNameResult) !=
     476           5 :         -1) {
     477           1 :       return NullPathErrorNameResult(name: nameToMatch);
     478             :     }
     479             : 
     480             :     // Else return a NotFoundError
     481           5 :     return NotFoundErrorNameResult(name: nameToMatch);
     482             :   }
     483             : 
     484             :   /// The goal is that, considering [thisPath] and [parentPath] we can form a new parentPath
     485             :   ///
     486             :   /// For more details, see [GetNewParentPathResult]
     487          10 :   GetNewParentPathResult getNewParentPath(
     488             :     GetNewParentPathResult parentPathResult, {
     489             :     required String? thisPath,
     490             :     required List<String> thisPathParametersKeys,
     491             :     required Map<String, String> pathParameters,
     492             :   }) {
     493             :     // First check that we have the path parameters needed to have this path
     494             :     final missingPathParameters =
     495          34 :         thisPathParametersKeys.where((key) => !pathParameters.containsKey(key)).toList();
     496             : 
     497          10 :     if (missingPathParameters.isNotEmpty) {
     498           5 :       if (thisPath!.startsWith('/')) {
     499           4 :         return PathParamsErrorNewParentPath(pathParameters: missingPathParameters);
     500             :       } else {
     501           3 :         return PathParamsErrorNewParentPath(
     502           3 :           pathParameters: [
     503           3 :             if (parentPathResult is PathParamsErrorNewParentPath)
     504           0 :               ...parentPathResult.pathParameters,
     505           3 :             ...missingPathParameters
     506             :           ],
     507             :         );
     508             :       }
     509             :     }
     510             : 
     511             :     if (thisPath == null) {
     512             :       // If the path is null, the new parent path is the same as the previous one
     513             :       return parentPathResult;
     514             :     }
     515             : 
     516          20 :     final localPath = pathToFunction(thisPath)(pathParameters);
     517          10 :     final thisPathParameters = Map<String, String>.from(pathParameters)
     518          24 :       ..removeWhere((key, value) => !thisPathParametersKeys.contains(key));
     519             : 
     520             :     // If the path is absolute
     521          10 :     if (thisPath.startsWith('/')) {
     522          10 :       return ValidParentPathResult(
     523             :         path: localPath,
     524             :         pathParameters: thisPathParameters,
     525             :       );
     526             :     }
     527             : 
     528             :     // Else the path is relative
     529             : 
     530             :     // If the path is relative and the parent path is invalid, then this path is invalid
     531           4 :     if (parentPathResult is PathParamsErrorNewParentPath) {
     532             :       return parentPathResult;
     533             :     }
     534             : 
     535             :     // Else this path is valid
     536           4 :     final parentPathValue = (parentPathResult as ValidParentPathResult).path ?? '';
     537           4 :     return ValidParentPathResult(
     538          12 :       path: parentPathValue + (!parentPathValue.endsWith('/') ? '/' : '') + localPath,
     539          12 :       pathParameters: {...parentPathResult.pathParameters, ...thisPathParameters},
     540             :     );
     541             :   }
     542             : 
     543           9 :   GetPathFromPopResult getPathFromPop(
     544             :     VRouteElement elementToPop, {
     545             :     required Map<String, String> pathParameters,
     546             :     required GetNewParentPathResult parentPathResult,
     547             :   }) {
     548             :     // If vRouteElement is this, then this is the element to pop so we return null
     549           9 :     if (elementToPop == this) {
     550           0 :       if (parentPathResult is ValidParentPathResult) {
     551           0 :         return ValidPopResult(
     552           0 :             path: parentPathResult.path, didPop: true, poppedVRouteElements: [this]);
     553             :       } else {
     554           0 :         assert(parentPathResult is PathParamsErrorNewParentPath);
     555           0 :         return PathParamsPopErrors(
     556           0 :           values: [
     557           0 :             MissingPathParamsError(
     558           0 :               pathParams: pathParameters.keys.toList(),
     559           0 :               missingPathParams: (parentPathResult as PathParamsErrorNewParentPath).pathParameters,
     560             :             ),
     561             :           ],
     562             :         );
     563             :       }
     564             :     }
     565             : 
     566           9 :     final List<GetPathFromPopResult> popErrorResults = [];
     567             : 
     568             :     // Try to match the path given the path parameters
     569           9 :     final newParentPathResult = getNewParentPath(
     570             :       parentPathResult,
     571           9 :       thisPath: path,
     572           9 :       thisPathParametersKeys: pathParametersKeys,
     573             :       pathParameters: pathParameters,
     574             :     );
     575             : 
     576             :     // If the path matched and produced a non null newParentPath, try to pop from the stackedRoutes
     577          18 :     for (var vRouteElement in stackedRoutes) {
     578           9 :       final childPopResult = vRouteElement.getPathFromPop(
     579             :         elementToPop,
     580             :         pathParameters: pathParameters,
     581             :         parentPathResult: newParentPathResult,
     582             :       );
     583           9 :       if (childPopResult is ValidPopResult) {
     584           9 :         if (childPopResult.didPop) {
     585             :           // if the nestedRoute popped, we should pop too
     586           9 :           if (parentPathResult is ValidParentPathResult) {
     587           8 :             return ValidPopResult(
     588           8 :               path: parentPathResult.path,
     589             :               didPop: true,
     590             :               poppedVRouteElements:
     591          24 :                   <VRouteElement>[this] + childPopResult.poppedVRouteElements,
     592             :             );
     593             :           } else {
     594           4 :             assert(parentPathResult is PathParamsErrorNewParentPath);
     595           4 :             popErrorResults.add(
     596           4 :               PathParamsPopErrors(
     597           4 :                 values: [
     598           4 :                   MissingPathParamsError(
     599           8 :                     pathParams: pathParameters.keys.toList(),
     600             :                     missingPathParams:
     601           4 :                         (parentPathResult as PathParamsErrorNewParentPath).pathParameters,
     602             :                   ),
     603             :                 ],
     604             :               ),
     605             :             );
     606             :           }
     607             :         } else {
     608           8 :           return ValidPopResult(
     609           8 :             path: childPopResult.path,
     610             :             didPop: false,
     611           8 :             poppedVRouteElements: childPopResult.poppedVRouteElements,
     612             :           );
     613             :         }
     614             :       } else {
     615           4 :         popErrorResults.add(childPopResult);
     616             :       }
     617             :     }
     618             : 
     619             :     // Try to match the aliases given the path parameters
     620          14 :     for (var i = 0; i < aliases.length; i++) {
     621           3 :       final newParentPathResultFromAlias = getNewParentPath(
     622             :         parentPathResult,
     623           6 :         thisPath: aliases[i],
     624           6 :         thisPathParametersKeys: aliasesPathParametersKeys[i],
     625             :         pathParameters: pathParameters,
     626             :       );
     627             : 
     628             :       // If an alias matched and produced a non null newParentPath, try to pop from the stackedRoutes
     629             :       // Try to pop from the stackedRoutes
     630           6 :       for (var vRouteElement in stackedRoutes) {
     631           3 :         final childPopResult = vRouteElement.getPathFromPop(
     632             :           elementToPop,
     633             :           pathParameters: pathParameters,
     634             :           parentPathResult: newParentPathResultFromAlias,
     635             :         );
     636           3 :         if (childPopResult is ValidPopResult) {
     637           3 :           if (childPopResult.didPop) {
     638             :             // if the nestedRoute popped, we should pop too
     639           2 :             if (parentPathResult is ValidParentPathResult) {
     640           2 :               return ValidPopResult(
     641           2 :                 path: parentPathResult.path,
     642             :                 didPop: true,
     643             :                 poppedVRouteElements:
     644           6 :                     <VRouteElement>[this] + childPopResult.poppedVRouteElements,
     645             :               );
     646             :             } else {
     647           0 :               assert(parentPathResult is PathParamsErrorNewParentPath);
     648           0 :               popErrorResults.add(
     649           0 :                 PathParamsPopErrors(
     650           0 :                   values: [
     651           0 :                     MissingPathParamsError(
     652           0 :                       pathParams: pathParameters.keys.toList(),
     653             :                       missingPathParams:
     654           0 :                           (parentPathResult as PathParamsErrorNewParentPath).pathParameters,
     655             :                     ),
     656             :                   ],
     657             :                 ),
     658             :               );
     659             :             }
     660             :           } else {
     661           3 :             return ValidPopResult(
     662           3 :               path: childPopResult.path,
     663             :               didPop: false,
     664           3 :               poppedVRouteElements: childPopResult.poppedVRouteElements,
     665             :             );
     666             :           }
     667             :         } else {
     668           2 :           popErrorResults.add(childPopResult);
     669             :         }
     670             :       }
     671             :     }
     672             : 
     673             :     // If we don't have any valid result
     674             : 
     675             :     // If some stackedRoute returned PathParamsPopError, aggregate them
     676           4 :     final pathParamsPopErrors = PathParamsPopErrors(
     677           4 :       values: popErrorResults.fold<List<MissingPathParamsError>>(
     678           4 :         <MissingPathParamsError>[],
     679           4 :         (previousValue, element) {
     680          15 :           return previousValue + ((element is PathParamsPopErrors) ? element.values : []);
     681             :         },
     682             :       ),
     683             :     );
     684             : 
     685             :     // If there was any PathParamsPopError, we have some pathParamsPopErrors.values
     686             :     // and therefore should return this
     687           8 :     if (pathParamsPopErrors.values.isNotEmpty) {
     688             :       return pathParamsPopErrors;
     689             :     }
     690             : 
     691             :     // If none of the stackedRoutes popped, this did not pop, and there was no path parameters issue, return not found
     692           3 :     return ErrorNotFoundGetPathFromPopResult();
     693             :   }
     694             : 
     695             :   /// If this [VRouteElement] is in the route but its localPath is null
     696             :   /// we try to find a local path in [path, ...aliases]
     697             :   ///
     698             :   /// This is used in [buildPage] to form the LocalKey
     699             :   /// Note that
     700             :   ///   - We can't use this because animation won't play if path parameters change for example
     701             :   ///   - Using null is not ideal because if we pop from a absolute path, this won't animate as expected
     702          12 :   String? getConstantLocalPath() {
     703          24 :     if (pathParametersKeys.isEmpty) {
     704          11 :       return path;
     705             :     }
     706          18 :     for (var i = 0; i < aliasesPathParametersKeys.length; i++) {
     707           6 :       if (aliasesPathParametersKeys[i].isEmpty) {
     708           4 :         return aliases[i];
     709             :       }
     710             :     }
     711             :     return null;
     712             :   }
     713             : }
     714             : 
     715             : /// The value of the new parentPath in [VRouteElement.getPathFromPop] and [VRouteElement.getPathFromName]
     716             : /// If this path is invalid:
     717             : ///   - return [ValidGetNewParentPathResult(value: parentPathParameter)]
     718             : /// If this path starts with '/':
     719             : ///   - Either the path parameters from [pathParameters] include those of this path and
     720             : ///       we return the corresponding path
     721             : ///   - Or we return [InvalidGetNewParentPathResult(missingPathParameters: this missing path parameters)]
     722             : /// If this path does not start with '/':
     723             : ///   - If the parent path is invalid:
     724             : ///   _  * [InvalidGetNewParentPathResult(missingPathParameters: parentPathParameterResult.missingPathParameters + this missingPathParameters)]
     725             : ///   - If the parent path is not invalid:
     726             : ///   _  * Either the path parameters from [pathParameters] include those of this path and
     727             : ///             we return [ValidGetNewParentPathResult(the parent path + this path)]
     728             : ///   _  * Or we return [InvalidGetNewParentPathResult(missingPathParameters: this missing path parameters)]
     729             : abstract class GetNewParentPathResult {}
     730             : 
     731             : class ValidParentPathResult extends GetNewParentPathResult {
     732             :   /// Null is a valid value, it just means that this path is null and the parent one was as well
     733             :   final String? path;
     734             : 
     735             :   final Map<String, String> pathParameters;
     736             : 
     737          10 :   ValidParentPathResult({required this.path, required this.pathParameters});
     738             : }
     739             : 
     740             : class PathParamsErrorNewParentPath extends GetNewParentPathResult {
     741             :   /// The missing path parameters that prevents us from creating the path
     742             :   final List<String> pathParameters;
     743             : 
     744           5 :   PathParamsErrorNewParentPath({required this.pathParameters});
     745             : }

Generated by: LCOV version 1.14