LCOV - code coverage report
Current view: top level - src - destination_parser.dart (source / functions) Hit Total Coverage
Test: lcov.info Lines: 81 82 98.8 %
Date: 2022-10-22 16:29:45 Functions: 0 0 -

          Line data    Source code
       1             : import 'package:flutter/foundation.dart';
       2             : 
       3             : import 'destination.dart';
       4             : import 'exceptions.dart';
       5             : 
       6             : /// A base destination parser.
       7             : ///
       8             : /// [DestinationParser] is used to parse the destination object from the
       9             : /// given URI string, and to generate the URI for the destination.
      10             : ///
      11             : /// When subclassed, the certain type of destination parameters must be provided.
      12             : ///
      13             : /// There are two methods, that must be implemented in the specific parser:
      14             : /// [toDestinationParameters] and [toMap].
      15             : ///
      16             : /// If typed parameters are not required, the [DefaultDestinationParser] is used.
      17             : ///
      18             : /// See also:
      19             : /// - [Destination]
      20             : /// - [DestinationParameters]
      21             : /// - [DefaultDestinationParameters]
      22             : /// - [DefaultDestinationParser]
      23             : ///
      24             : abstract class DestinationParser<T extends DestinationParameters> {
      25             :   /// Creates destination parser.
      26             :   ///
      27          16 :   const DestinationParser();
      28             : 
      29             :   /// Creates a destination parameters object of type [T] from the given map.
      30             :   ///
      31             :   /// The key of the map entry is a parameter name, and the value is serialized
      32             :   /// parameter's value.
      33             :   /// This method is used by [parseParameters()] to generate the destination object
      34             :   /// from the given URI string.
      35             :   ///
      36             :   Future<T> toDestinationParameters(Map<String, String> map);
      37             : 
      38             :   /// Converts destination [parameters] object of type [T] to a map.
      39             :   ///
      40             :   /// The key of the map entry is a parameter name, and the value is serialized
      41             :   /// parameter's value.
      42             :   /// This method is used by [uri()] to generate the destination's URI string.
      43             :   ///
      44             :   Map<String, String> toMap(T parameters);
      45             : 
      46             :   /// Check if the provided [uri] string match the base [destination]
      47             :   ///
      48           8 :   bool isMatch(String uri, Destination<T> destination) {
      49          16 :     if ((uri == '/' || uri.isEmpty)) {
      50           3 :       return destination.isHome;
      51             :     }
      52             : 
      53          16 :     final destinationUri = Uri.parse(destination.path);
      54           8 :     final sourceUri = Uri.parse(uri);
      55           8 :     final destinationSegments = destinationUri.pathSegments;
      56           8 :     final sourceSegments = sourceUri.pathSegments;
      57             : 
      58          24 :     if (destinationSegments.length < sourceSegments.length) {
      59             :       return false;
      60             :     }
      61          24 :     final lengthDifference = destinationSegments.length - sourceSegments.length;
      62           8 :     if (lengthDifference > 1 ||
      63          20 :         lengthDifference == 1 && !isPathParameter(destinationSegments.last)) {
      64             :       return false;
      65             :     }
      66          32 :     for (var i = 0; i < destinationSegments.length - lengthDifference; i++) {
      67          24 :       if (destinationSegments[i] != sourceSegments[i] &&
      68          16 :           !isPathParameter(destinationSegments[i])) {
      69             :         return false;
      70             :       }
      71             :     }
      72             :     return true;
      73             :   }
      74             : 
      75             :   /// Check if the path segment string is a valid path parameter placeholder.
      76             :   ///
      77             :   /// The default path parameter format is '{parameterName}'.
      78             :   ///
      79           8 :   bool isPathParameter(String pathSegment) {
      80          15 :     return pathSegment.startsWith('{') && pathSegment.endsWith('}');
      81             :   }
      82             : 
      83             :   /// Extract parameter name from the path segment string.
      84             :   ///
      85             :   /// See [isPathParameter] for default path parameter format.
      86             :   ///
      87           7 :   String parsePathParameterName(String pathSegment) {
      88           7 :     if (!isPathParameter(pathSegment)) {
      89             :       // TODO: Implement custom exception
      90           0 :       throw Exception('$pathSegment is not a valid path parameter');
      91             :     }
      92          21 :     return pathSegment.substring(1, pathSegment.length - 1);
      93             :   }
      94             : 
      95             :   /// Parses parameter values from the specified URI for matched destination.
      96             :   ///
      97             :   /// Returns the copy of [matchedDestination] with actual parameter values parsed from the [uri].
      98             :   /// Uses [toDestinationParameters] implementation to create parameters object of
      99             :   /// type [T].
     100             :   ///
     101             :   /// Also it ensures that raw parameters value in [DestinationParameters.map] are valid.
     102             :   ///
     103             :   /// Throws [DestinationNotMatchException] if the URI does mot match the destination.
     104             :   ///
     105           5 :   Future<Destination<T>> parseParameters(
     106             :       String uri, Destination<T> matchedDestination) async {
     107             :     // TODO: Is this check really needed here?
     108           5 :     if (!isMatch(uri, matchedDestination)) {
     109           2 :       throw DestinationNotMatchException(uri, matchedDestination);
     110             :     }
     111           5 :     final parsedUri = Uri.parse(uri);
     112           5 :     final parametersMap = <String, String>{}
     113          10 :       ..addAll(_parsePathParameters(parsedUri, matchedDestination))
     114          10 :       ..addAll(parsedUri.queryParameters);
     115          10 :     final T parameters = await toDestinationParameters(parametersMap);
     116           5 :     final rawParameters = toMap(parameters);
     117             :     parameters
     118          10 :       ..map.clear()
     119          10 :       ..map.addAll(rawParameters);
     120           5 :     return matchedDestination.withParameters(parameters);
     121             :   }
     122             : 
     123             :   /// Returns URI string for the destination
     124             :   ///
     125             :   /// The [Destination.path] is used for building the URI path segment.
     126             :   /// The URI query segment is built using [Destination.parameters] converted
     127             :   /// by [toMap] implementation from the [Destination.parser].
     128             :   ///
     129           8 :   String uri(Destination destination) {
     130             :     late final Map<String, String> parametersMap;
     131           8 :     if (destination.parameters == null) {
     132             :       parametersMap = const <String, String>{};
     133             :     } else {
     134          18 :       parametersMap = destination.parser.toMap(destination.parameters!);
     135             :     }
     136          16 :     final pathParameters = _getPathParameters(destination.path, parametersMap);
     137           8 :     final queryParameters = _getQueryParameters(pathParameters, parametersMap);
     138          16 :     final path = _fillPathParameters(destination.path, pathParameters);
     139           8 :     return Uri(
     140             :       path: path,
     141           8 :       queryParameters: queryParameters.isNotEmpty ? queryParameters : null,
     142           8 :     ).toString();
     143             :   }
     144             : 
     145           5 :   Map<String, String> _parsePathParameters(
     146             :       Uri uri, Destination<T> baseDestination) {
     147           5 :     final result = <String, String>{};
     148          10 :     final baseDestinationUri = Uri.parse(baseDestination.path);
     149          19 :     for (int i = 0; i < uri.pathSegments.length; i++) {
     150           8 :       final pathSegment = uri.pathSegments[i];
     151           8 :       final baseDestinationPathSegment = baseDestinationUri.pathSegments[i];
     152           4 :       if (isPathParameter(baseDestinationPathSegment)) {
     153             :         final parameterName =
     154           4 :             parsePathParameterName(baseDestinationPathSegment);
     155           4 :         result[parameterName] = pathSegment;
     156             :       }
     157             :     }
     158             :     return result;
     159             :   }
     160             : 
     161           8 :   Map<String, String> _getPathParameters(
     162             :       String path, Map<String, String> parameters) {
     163           8 :     final result = <String, String>{};
     164           8 :     final pathUri = Uri.parse(path);
     165          16 :     for (var pathSegment in pathUri.pathSegments) {
     166           8 :       if (isPathParameter(pathSegment)) {
     167           7 :         final parameterName = parsePathParameterName(pathSegment);
     168           7 :         final value = parameters[parameterName];
     169             :         if (value != null) {
     170           4 :           result[parameterName] = value;
     171             :         }
     172             :       }
     173             :     }
     174             :     return result;
     175             :   }
     176             : 
     177           8 :   Map<String, String> _getQueryParameters(
     178             :       Map<String, String> pathParameters, Map<String, String> parameters) {
     179           8 :     final result = <String, String>{};
     180          12 :     for (MapEntry entry in parameters.entries) {
     181          12 :       if (pathParameters.keys.contains(entry.key)) {
     182             :         continue;
     183             :       }
     184           9 :       result[entry.key] = entry.value;
     185             :     }
     186             :     return result;
     187             :   }
     188             : 
     189           8 :   String _fillPathParameters(String path, Map<String, String> parameters) {
     190           8 :     final pathUri = Uri.parse(path);
     191           8 :     final filledPathSegments = <String>[];
     192          16 :     for (var pathSegment in pathUri.pathSegments) {
     193           8 :       if (isPathParameter(pathSegment)) {
     194           7 :         final parameterName = parsePathParameterName(pathSegment);
     195           7 :         final value = parameters[parameterName];
     196             :         if (value != null) {
     197           4 :           filledPathSegments.add(value);
     198             :         } else {
     199          18 :           if (pathUri.pathSegments.last != pathSegment) {
     200           4 :             filledPathSegments.add(pathSegment);
     201             :           }
     202             :         }
     203             :       } else {
     204           8 :         filledPathSegments.add(pathSegment);
     205             :       }
     206             :     }
     207          16 :     final result = Uri(pathSegments: filledPathSegments).toString();
     208          16 :     return '${path.startsWith('/') ? "/" : ""}$result';
     209             :   }
     210             : }
     211             : 
     212             : /// A default implementation of [DestinationParser].
     213             : ///
     214             : class DefaultDestinationParser
     215             :     extends DestinationParser<DestinationParameters> {
     216             :   /// Creates default destination parser.
     217             :   ///
     218          11 :   const DefaultDestinationParser() : super();
     219             : 
     220           5 :   @override
     221             :   Future<DestinationParameters> toDestinationParameters(
     222             :           Map<String, String> map) =>
     223          10 :       SynchronousFuture(DestinationParameters(map));
     224             : 
     225           6 :   @override
     226          12 :   Map<String, String> toMap(DestinationParameters parameters) => Map.of(parameters.map);
     227             : }

Generated by: LCOV version