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-12-25 21:41:53 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          17 :   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             :   /// 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          17 :     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           8 :   String parsePathParameterName(String pathSegment) {
      91           8 :     if (!isPathParameter(pathSegment)) {
      92             :       // TODO: Implement custom exception
      93           0 :       throw Exception('$pathSegment is not a valid path parameter');
      94             :     }
      95          24 :     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 [toDestinationParameters] 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           5 :   Future<Destination<T>> parseParameters(
     109             :       String uri, Destination<T> matchedDestination) async {
     110             :     // TODO: Is this check really needed here?
     111           5 :     if (!isMatch(uri, matchedDestination)) {
     112           2 :       throw DestinationNotMatchException(uri, matchedDestination);
     113             :     }
     114           5 :     final parsedUri = Uri.parse(uri);
     115           5 :     final parametersMap = <String, String>{}
     116          10 :       ..addAll(_parsePathParameters(parsedUri, matchedDestination))
     117          10 :       ..addAll(parsedUri.queryParameters);
     118          10 :     final T parameters = await toDestinationParameters(parametersMap);
     119           5 :     final rawParameters = toMap(parameters);
     120             :     parameters
     121          10 :       ..map.clear()
     122          10 :       ..map.addAll(rawParameters);
     123           5 :     return matchedDestination.withParameters(parameters);
     124             :   }
     125             : 
     126             :   /// Returns URI string for the destination
     127             :   ///
     128             :   /// The [Destination.path] is used for building the URI path segment.
     129             :   /// The URI query segment is built using [Destination.parameters] converted
     130             :   /// by [toMap] implementation from the [Destination.parser].
     131             :   ///
     132           9 :   String uri(Destination destination) {
     133             :     late final Map<String, String> parametersMap;
     134           9 :     if (destination.parameters == null) {
     135             :       parametersMap = const <String, String>{};
     136             :     } else {
     137          18 :       parametersMap = destination.parser.toMap(destination.parameters!);
     138             :     }
     139          18 :     final pathParameters = _getPathParameters(destination.path, parametersMap);
     140           9 :     final queryParameters = _getQueryParameters(pathParameters, parametersMap);
     141          18 :     final path = _fillPathParameters(destination.path, pathParameters);
     142           9 :     return Uri(
     143             :       path: path,
     144           9 :       queryParameters: queryParameters.isNotEmpty ? queryParameters : null,
     145           9 :     ).toString();
     146             :   }
     147             : 
     148           5 :   Map<String, String> _parsePathParameters(
     149             :       Uri uri, Destination<T> baseDestination) {
     150           5 :     final result = <String, String>{};
     151          10 :     final baseDestinationUri = Uri.parse(baseDestination.path);
     152          19 :     for (int i = 0; i < uri.pathSegments.length; i++) {
     153           8 :       final pathSegment = uri.pathSegments[i];
     154           8 :       final baseDestinationPathSegment = baseDestinationUri.pathSegments[i];
     155           4 :       if (isPathParameter(baseDestinationPathSegment)) {
     156             :         final parameterName =
     157           4 :             parsePathParameterName(baseDestinationPathSegment);
     158           4 :         result[parameterName] = pathSegment;
     159             :       }
     160             :     }
     161             :     return result;
     162             :   }
     163             : 
     164           9 :   Map<String, String> _getPathParameters(
     165             :       String path, Map<String, String> parameters) {
     166           9 :     final result = <String, String>{};
     167           9 :     final pathUri = Uri.parse(path);
     168          18 :     for (var pathSegment in pathUri.pathSegments) {
     169           9 :       if (isPathParameter(pathSegment)) {
     170           8 :         final parameterName = parsePathParameterName(pathSegment);
     171           8 :         final value = parameters[parameterName];
     172             :         if (value != null) {
     173           4 :           result[parameterName] = value;
     174             :         }
     175             :       }
     176             :     }
     177             :     return result;
     178             :   }
     179             : 
     180           9 :   Map<String, String> _getQueryParameters(
     181             :       Map<String, String> pathParameters, Map<String, String> parameters) {
     182           9 :     final result = <String, String>{};
     183          13 :     for (MapEntry entry in parameters.entries) {
     184          12 :       if (pathParameters.keys.contains(entry.key)) {
     185             :         continue;
     186             :       }
     187           9 :       result[entry.key] = entry.value;
     188             :     }
     189             :     return result;
     190             :   }
     191             : 
     192           9 :   String _fillPathParameters(String path, Map<String, String> parameters) {
     193           9 :     final pathUri = Uri.parse(path);
     194           9 :     final filledPathSegments = <String>[];
     195          18 :     for (var pathSegment in pathUri.pathSegments) {
     196           9 :       if (isPathParameter(pathSegment)) {
     197           8 :         final parameterName = parsePathParameterName(pathSegment);
     198           8 :         final value = parameters[parameterName];
     199             :         if (value != null) {
     200           4 :           filledPathSegments.add(value);
     201             :         } else {
     202          21 :           if (pathUri.pathSegments.last != pathSegment) {
     203           4 :             filledPathSegments.add(pathSegment);
     204             :           }
     205             :         }
     206             :       } else {
     207           9 :         filledPathSegments.add(pathSegment);
     208             :       }
     209             :     }
     210          18 :     final result = Uri(pathSegments: filledPathSegments).toString();
     211          18 :     return '${path.startsWith('/') ? "/" : ""}$result';
     212             :   }
     213             : }
     214             : 
     215             : /// A default implementation of [DestinationParser].
     216             : ///
     217             : class DefaultDestinationParser
     218             :     extends DestinationParser<DestinationParameters> {
     219             :   /// Creates default destination parser.
     220             :   ///
     221          12 :   const DefaultDestinationParser() : super();
     222             : 
     223           5 :   @override
     224             :   Future<DestinationParameters> toDestinationParameters(
     225             :           Map<String, String> map) =>
     226          10 :       SynchronousFuture(DestinationParameters(map));
     227             : 
     228           6 :   @override
     229             :   Map<String, String> toMap(DestinationParameters parameters) =>
     230          12 :       Map.of(parameters.map);
     231             : }

Generated by: LCOV version