LCOV - code coverage report
Current view: top level - src - destination_parser.dart (source / functions) Hit Total Coverage
Test: lcov.info Lines: 88 88 100.0 %
Date: 2023-03-02 15:31:08 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             : /// [parametersFromMap] and [parametersToMap].
      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          19 :   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> parametersFromMap(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> parametersToMap(T parameters);
      45             : 
      46             :   /// Checks if the [destination] matches the [uri].
      47             :   ///
      48             :   /// The destination does match when its [path] structure (URI segments number
      49             :   /// and values, including path parameters) matches given [uri] string.
      50             :   ///
      51           8 :   bool isMatch(String uri, Destination<T> destination) {
      52          16 :     if ((uri == '/' || uri.isEmpty)) {
      53           3 :       return destination.isHome;
      54             :     }
      55             : 
      56          16 :     final destinationUri = Uri.parse(destination.path);
      57           8 :     final sourceUri = Uri.parse(uri);
      58           8 :     final destinationSegments = destinationUri.pathSegments;
      59           8 :     final sourceSegments = sourceUri.pathSegments;
      60             : 
      61          24 :     if (destinationSegments.length < sourceSegments.length) {
      62             :       return false;
      63             :     }
      64          24 :     final lengthDifference = destinationSegments.length - sourceSegments.length;
      65           8 :     if (lengthDifference > 1 ||
      66          20 :         lengthDifference == 1 && !isPathParameter(destinationSegments.last)) {
      67             :       return false;
      68             :     }
      69          32 :     for (var i = 0; i < destinationSegments.length - lengthDifference; i++) {
      70          24 :       if (destinationSegments[i] != sourceSegments[i] &&
      71          16 :           !isPathParameter(destinationSegments[i])) {
      72             :         return false;
      73             :       }
      74             :     }
      75             :     return true;
      76             :   }
      77             : 
      78             :   /// Check if the path segment string is a valid path parameter placeholder.
      79             :   ///
      80             :   /// The default path parameter format is '{parameterName}'.
      81             :   ///
      82           9 :   bool isPathParameter(String pathSegment) {
      83          18 :     return pathSegment.startsWith('{') && pathSegment.endsWith('}');
      84             :   }
      85             : 
      86             :   /// Extract parameter name from the path segment string.
      87             :   ///
      88             :   /// See [isPathParameter] for default path parameter format.
      89             :   ///
      90           9 :   String parsePathParameterName(String pathSegment) {
      91           9 :     if (!isPathParameter(pathSegment)) {
      92             :       // TODO: Implement custom exception
      93           4 :       throw Exception('$pathSegment is not a valid path parameter');
      94             :     }
      95          27 :     return pathSegment.substring(1, pathSegment.length - 1);
      96             :   }
      97             : 
      98             :   /// Parses parameter values from the specified URI for matched destination.
      99             :   ///
     100             :   /// Returns the copy of [matchedDestination] with actual parameter values parsed from the [uri].
     101             :   /// Uses [parametersFromMap] implementation to create parameters object of
     102             :   /// type [T].
     103             :   ///
     104             :   /// Also it ensures that raw parameters value in [DestinationParameters.map] are valid.
     105             :   ///
     106             :   /// Throws [DestinationNotMatchException] if the URI does mot match the destination.
     107             :   ///
     108           6 :   Future<Destination<T>> parseParameters(
     109             :       String uri, Destination<T> matchedDestination) async {
     110             :     // TODO: Is this check really needed here?
     111           6 :     if (!isMatch(uri, matchedDestination)) {
     112           2 :       throw DestinationNotMatchException(uri, matchedDestination);
     113             :     }
     114           6 :     final parsedUri = Uri.parse(uri);
     115           6 :     final parametersMap = <String, String>{}
     116          12 :       ..addAll(_parsePathParameters(parsedUri, matchedDestination))
     117          12 :       ..addAll(parsedUri.queryParameters);
     118          12 :     final T parameters = await parametersFromMap(parametersMap);
     119           6 :     final rawParameters = parametersToMap(parameters);
     120             :     parameters
     121          12 :       ..map.clear()
     122          12 :       ..map.addAll(rawParameters)
     123          18 :       ..map.addAll(_extractReservedParameters(parametersMap));
     124           6 :     return matchedDestination.withParameters(parameters);
     125             :   }
     126             : 
     127             :   /// Returns URI string for the destination
     128             :   ///
     129             :   /// The [Destination.path] is used for building the URI path segment.
     130             :   /// The URI query segment is built using [Destination.parameters] converted
     131             :   /// by [parametersToMap] implementation from the [Destination.parser].
     132             :   ///
     133           9 :   String uri(Destination destination) {
     134             :     late final Map<String, String> parametersMap;
     135           9 :     if (destination.parameters == null) {
     136             :       parametersMap = const <String, String>{};
     137             :     } else {
     138          24 :       parametersMap = destination.parser.parametersToMap(destination.parameters!)
     139          16 :         ..addAll(_extractReservedParameters(
     140          16 :             destination.parameters?.map ?? const <String, String>{}));
     141             :     }
     142          18 :     final pathParameters = _getPathParameters(destination.path, parametersMap);
     143           9 :     final queryParameters = _getQueryParameters(pathParameters, parametersMap);
     144          18 :     final path = _fillPathParameters(destination.path, pathParameters);
     145           9 :     return Uri(
     146             :       path: path,
     147           9 :       queryParameters: queryParameters.isNotEmpty ? queryParameters : null,
     148           9 :     ).toString();
     149             :   }
     150             : 
     151           6 :   Map<String, String> _parsePathParameters(
     152             :       Uri uri, Destination<T> baseDestination) {
     153           6 :     final result = <String, String>{};
     154          12 :     final baseDestinationUri = Uri.parse(baseDestination.path);
     155          23 :     for (int i = 0; i < uri.pathSegments.length; i++) {
     156          10 :       final pathSegment = uri.pathSegments[i];
     157          10 :       final baseDestinationPathSegment = baseDestinationUri.pathSegments[i];
     158           5 :       if (isPathParameter(baseDestinationPathSegment)) {
     159             :         final parameterName =
     160           4 :             parsePathParameterName(baseDestinationPathSegment);
     161           4 :         result[parameterName] = pathSegment;
     162             :       }
     163             :     }
     164             :     return result;
     165             :   }
     166             : 
     167           9 :   Map<String, String> _getPathParameters(
     168             :       String path, Map<String, String> parameters) {
     169           9 :     final result = <String, String>{};
     170           9 :     final pathUri = Uri.parse(path);
     171          18 :     for (var pathSegment in pathUri.pathSegments) {
     172           9 :       if (isPathParameter(pathSegment)) {
     173           9 :         final parameterName = parsePathParameterName(pathSegment);
     174           9 :         final value = parameters[parameterName];
     175             :         if (value != null) {
     176           5 :           result[parameterName] = value;
     177             :         }
     178             :       }
     179             :     }
     180             :     return result;
     181             :   }
     182             : 
     183           9 :   Map<String, String> _getQueryParameters(
     184             :       Map<String, String> pathParameters, Map<String, String> parameters) {
     185           9 :     final result = <String, String>{};
     186          15 :     for (MapEntry entry in parameters.entries) {
     187          18 :       if (pathParameters.keys.contains(entry.key)) {
     188             :         continue;
     189             :       }
     190          12 :       result[entry.key] = entry.value;
     191             :     }
     192             :     return result;
     193             :   }
     194             : 
     195           8 :   Map<String, String> _extractReservedParameters(
     196             :           Map<String, String> parameters) =>
     197          24 :       Map.fromEntries(parameters.entries.where(
     198          18 :           (entry) => DestinationParameters.isReservedParameter(entry.key)));
     199             : 
     200           9 :   String _fillPathParameters(String path, Map<String, String> parameters) {
     201           9 :     final pathUri = Uri.parse(path);
     202           9 :     final filledPathSegments = <String>[];
     203          18 :     for (var pathSegment in pathUri.pathSegments) {
     204           9 :       if (isPathParameter(pathSegment)) {
     205           9 :         final parameterName = parsePathParameterName(pathSegment);
     206           9 :         final value = parameters[parameterName];
     207             :         if (value != null) {
     208           5 :           filledPathSegments.add(value);
     209             :         } else {
     210          21 :           if (pathUri.pathSegments.last != pathSegment) {
     211           3 :             filledPathSegments.add(pathSegment);
     212             :           }
     213             :         }
     214             :       } else {
     215           9 :         filledPathSegments.add(pathSegment);
     216             :       }
     217             :     }
     218          18 :     final result = Uri(pathSegments: filledPathSegments).toString();
     219          18 :     return '${path.startsWith('/') ? "/" : ""}$result';
     220             :   }
     221             : }
     222             : 
     223             : /// A default implementation of [DestinationParser].
     224             : ///
     225             : class DefaultDestinationParser
     226             :     extends DestinationParser<DestinationParameters> {
     227             :   /// Creates default destination parser.
     228             :   ///
     229          13 :   const DefaultDestinationParser() : super();
     230             : 
     231           6 :   @override
     232             :   Future<DestinationParameters> parametersFromMap(
     233             :           Map<String, String> map) =>
     234          12 :       SynchronousFuture(DestinationParameters(map));
     235             : 
     236           6 :   @override
     237             :   Map<String, String> parametersToMap(DestinationParameters parameters) =>
     238          12 :       Map.of(parameters.map);
     239             : }

Generated by: LCOV version