match method

RequestParams? match(
  1. List<String> components
)

Matches a path to the pattern.

Returns the path parameters if the path (from the components) matches. Returns null if the path does not match the pattern.

For a path to match:

  • literal segments must be present in the path and have the same value
  • variable segments must be present in the path, but can have any value
  • optional segments must have the exact same value or be absent
  • wildcard segments match zero or more segments in the rest of the path.

If the pattern has variable segments or wildcard segments, the segment(s) from the path that matches them are returned in the result.

For example, a pattern that consists solely of literal segments must match the path exactly. The pattern "~/foo/bar" will only match the path "/foo/bar".

The pattern "~/foo/:xyz" will match the path "/foo/bar" and the result will contain "bar" as the value for the key "xyz". It will also match the path "/foo/baz" and the result will contain "baz" as the value for "xyz".

The pattern "~/foo/bar?/baz" has an optional segment. It will match the paths "/foo/bar/baz" or "/foo/baz".

The pattern ~/foo/* has a wildcard segment. If the path is "/foo/bar", "bar" is the value of the key *. If the path is "/foo/abc/def", the value of the key * is "abc/def". If the path is "/foo/x/y/z", the value of the key * is "x/y/z".

Implementation

RequestParams? match(List<String> components) {
  final result = RequestParams._internalConstructor();

  if (_segments.isEmpty &&
      (components.isEmpty ||
          components.length == 1 && components.first.isEmpty)) {
    // Special handling for matching the "~/" pattern.
    //
    // While the for-loop below will correctly handle the situation of
    // empty components, it will not match when the components contains
    // exactly one empty string component. This code handles both sitautions.
    //
    // The first situation occurs when there is no prefix
    // (e.g. https://example.com matching "~/") or there
    // is a prefix and the requested path does not end with a slash
    // (e.g. prefix is "foobar" and https://example.com/foobar is requested).
    // Triggering this when components is empty is optional: it simply saves
    // going through the for-loop below.
    //
    // The second situation occurs when there is a prefix and the requested
    // path ends with a slash (e.g. prefix is "foobar" and
    // https://example.com/foobar/ is requested). This situation commonly
    // happens when a reverse proxy is redirecting URLs with "/foobar/" to
    // the server configure with a prefix.
    // Trigger this when components contains one empty string is necessary
    // to handle this situation. Otherwise, the rule will not match when it
    // should.
    //
    // Another way to think of this special case is: when no prefix is used
    // there is no difference between https://example.com and
    // https://example.com/, so they should both match "~/". When there
    // is a prefix, https://example.com/foobar and https://example.com/foobar/
    // are distinct URLs, but they should both still match the same "~/".

    return result;
  }

  var componentIndex = 0;
  var segmentIndex = 0;
  for (var segment in _segments) {
    String? component;
    if (components.length <= componentIndex) {
      if (wildcard == segment) {
        component = null; // wildcard can match no components
      } else {
        return null; // no component(s) to match this segment
      }
    } else {
      component = components[componentIndex];
    }

    if (_isVariable(segment)) {
      // Variable segment
      result._add(_variableName(segment), component!);
      componentIndex++;
    } else if (wildcard == segment) {
      // Wildcard segment
      final numSegmentsLeft = _segments.length - segmentIndex - 1;
      final numConsumed =
          components.length - componentIndex - numSegmentsLeft;
      if (numConsumed < 0) {
        return null; // insufficient components to satisfy rest of pattern
      }
      result._add(
          wildcard,
          components
              .getRange(componentIndex, componentIndex + numConsumed)
              .join(_pathSeparator));
      componentIndex += numConsumed;
    } else if (_isOptional(segment)) {
      // Optional segment
      if (component == _optionalName(segment)) {
        // path component matches the optional segment: skip over it
        componentIndex++;
      } else {
        // assume the optional segment is not present
      }
    } else if (segment == component) {
      // Literal segment
      componentIndex++;
    } else {
      // No match
      return null;
    }

    segmentIndex++;
  }

  if (componentIndex != components.length) {
    return null; // some components did not match
  }

  return result;
}