Class: Labkit::RateLimit::Matcher

Inherits:
Object
  • Object
show all
Defined in:
lib/labkit/rate_limit/matcher.rb

Overview

Matcher is the internal representation of a single key/value predicate in a Rule#match hash. Rule.new normalizes every match value through Matcher.build; the Evaluator calls Matcher#match? per identifier value.

Accepted input shapes (everything else raises ArgumentError):

- any plain value (String, Symbol, Integer, ...) -> :eq matcher
- a Regexp instance                              -> :re matcher (Ruby convenience)
- { eq: <value> }                                -> :eq matcher (canonical, YAML-compatible)
- { re: <String|Regexp> }                        -> :re matcher (canonical, YAML-compatible)
- { oneOf: <Array> }                             -> :oneOf matcher (set membership, YAML-compatible)

A bare Array is rejected rather than treated as :oneOf, so a value that was meant as a single matcher hash but arrived as an Array fails loudly instead of silently becoming a membership test.

:oneOf membership is Set#include? (eql?/hash equality), not :eq's ==, so cross-type numerics differ (1 == 1.0, but [1] does not match 1.0).

Hash-key naming follows the metrics-catalog selector pattern. Glob, prefix, and other matcher kinds are intentionally out of scope here; see gitlab-com/gl-infra/production-engineering#28853 for that follow-up.

An :re matcher coerces the identifier value via #to_s before applying the regex, so callers can match non-String identifier values such as Integer status codes (e.g. { re: "^5" } against status: 503).

Constant Summary collapse

KNOWN_HASH_KEYS =
%i[eq re oneOf].freeze
MAX_REGEX_SOURCE_LENGTH =
200
ERROR_INSPECT_LIMIT =
80
MATCH_TIMEOUT_SECONDS =

Wall-clock budget for a single #match? call. Without it a match is bounded only by whatever global the host sets (40s in GitLab Rails, unbounded elsewhere), and a timeout reaches Evaluator's fail-open rescue, so the request goes unlimited.

5ms is ~119x the slowest real match measured against GitLab's RackAttack path patterns on inputs up to 16KB (worst: 0.0419ms, none timed out). Re-measure if a rule ever needs nested quantifiers or backreferences, which are what make backtracking exponential.

0.005

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.build(input) ⇒ Object



48
49
50
51
52
53
54
55
56
57
58
59
# File 'lib/labkit/rate_limit/matcher.rb', line 48

def self.build(input)
  case input
  when Regexp
    new(type: :re, value: with_match_timeout(input))
  when Hash
    from_hash(input)
  when Array
    raise ArgumentError, invalid_shape_error(input)
  else
    new(type: :eq, value: input)
  end
end

Instance Method Details

#match?(identifier_value) ⇒ Boolean

Returns:

  • (Boolean)


141
142
143
144
145
146
147
148
149
150
151
152
# File 'lib/labkit/rate_limit/matcher.rb', line 141

def match?(identifier_value)
  case type
  when :eq
    value == identifier_value
  when :oneOf
    value.include?(identifier_value)
  when :re
    value.match?(identifier_value.to_s)
  else
    raise ArgumentError, "unknown matcher type: #{type.inspect}"
  end
end