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

Generated by: LCOV version 1.14