gitlab-labkit 5.5.0 → 5.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3fe9b7fbeed18b9aced46d0f50b8e0aeeb4442a164818ed00f581a39559a2203
4
- data.tar.gz: 5362ebd8bd9d8f3cf598dd473be4a1078ed1b5eda9e11723c6bba35e5b5ad62c
3
+ metadata.gz: fb00f476a5fed0be71839727d4fb5d8230f773831c8c8cca40ff8090c76cb2c5
4
+ data.tar.gz: c382bf0eed9e55b283eb90c3956a00c8dbf73ad5819fb22e8dd6a1be931fd9ff
5
5
  SHA512:
6
- metadata.gz: a8e09616bd24dc1e5b619ae05c426451ef0b1cb40305832dfd52b04e801cd2f490823cb45592e101469f3cee4edca5a4f6d2190ddcfbded59e0e1282fc5fe01a
7
- data.tar.gz: 5103ce4264e6fca52740485de4c81e17feedeb31c493b2aaeda35a0ecdedbd47c043ec77a15371a1eb949a48c8341393c4fc09bee5458d958de83b195315def8
6
+ metadata.gz: 5932210330aa7c8c4413de43f8ae710508658846c89f1b7b74e1d8d36af564b61034988a6248bf9795d6343602d4b1579d6a9aff48795cda1163c545377932b9
7
+ data.tar.gz: ddc501d350806e384f122f152f6ceb7066487bc4f41078341f1ae13373631adf52a1f5cd427a69e879482cf3dc1dff75bbfe7cd57dd1237094c595e615acc591
data/.gitlab/CODEOWNERS CHANGED
@@ -1 +1 @@
1
- * @reprazent @andrewn @mkaeppler @ayufan @hmerscher @sankalp_gl @hardikgala @nindurkar @ashs2
1
+ * @reprazent @mkaeppler @ayufan @hmerscher @sankalp_gl @hardikgala @nindurkar @ashs2
data/CODEOWNERS CHANGED
@@ -1,4 +1,4 @@
1
1
  # CODEOWNERS is used to lookup assignees for
2
2
  # Renovate Bot dependency change Merge Requests.
3
3
  # https://docs.renovatebot.com/configuration-options/#assigneesfromcodeowners
4
- * @reprazent @andrewn @ayufan @hmerscher @e_forbes @M_Alvarez @mwoolf @sankalp_gl @hardikgala @nindurkar @ashs2
4
+ * @reprazent @ayufan @hmerscher @e_forbes @M_Alvarez @mwoolf @sankalp_gl @hardikgala @nindurkar @ashs2
@@ -512,7 +512,8 @@ result.degraded? # => true if a matched count_distinct rule was skipped
512
512
  result.info # => Result::Info or nil
513
513
  result.evaluations # => every counted Result::Evaluation, in rule order
514
514
  result.to_response_headers
515
- # => { "RateLimit-Limit" => "...", "RateLimit-Remaining" => "...", "RateLimit-Reset" => "<unix-ts>" }
515
+ # => { "RateLimit-Name" => "<rule name>", "RateLimit-Limit" => "...", "RateLimit-Observed" => "...",
516
+ # "RateLimit-Remaining" => "...", "RateLimit-Reset" => "<unix-ts>" }
516
517
  ```
517
518
 
518
519
  `Result::Info` holds the per-window counter snapshot:
@@ -528,6 +529,27 @@ result.to_response_headers
528
529
  `to_response_headers` returns `{}` for an unmatched or error result, so it is
529
530
  safe to merge unconditionally.
530
531
 
532
+ ### Response headers
533
+
534
+ `Result#to_response_headers` reports the most constraining evaluation.
535
+ `Result::Evaluation#to_response_headers` builds the same headers for one evaluation, for a caller that picks its own evaluation across several results.
536
+
537
+ | header | present | value |
538
+ |-----------------------|-------------------|-----------------------------------------------------|
539
+ | `RateLimit-Name` | always | The rule's `name`. |
540
+ | `RateLimit-Limit` | always | `resolved_limit` in the rule's period, at least 0. |
541
+ | `RateLimit-Observed` | always | `count`, as an Integer, at least 0. |
542
+ | `RateLimit-Remaining` | always | `remaining`, as an Integer. |
543
+ | `RateLimit-Reset` | always | `reset_at` as a Unix timestamp. |
544
+ | `Retry-After` | blocking only | Seconds to `reset_at`, rounded up, at least 0. |
545
+ | `RateLimit-ResetTime` | blocking only | `reset_at` as an HTTP date. |
546
+
547
+ `Retry-After` and `RateLimit-ResetTime` belong on the 429, so they appear only when the evaluation blocks.
548
+ For a rule with `ban_for`, `reset_at` is the end of the ban, so these headers report when the ban lifts.
549
+
550
+ `RateLimit-Limit`, `RateLimit-Observed` and `RateLimit-Remaining` all count within the rule's `period`.
551
+ The headers do not state the period, so a client that needs it must know the rule.
552
+
531
553
  ## Fail-open
532
554
 
533
555
  The evaluator wraps `check` and `peek` in a broad rescue. Any `StandardError`
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "time" # Time#httpdate
4
+
3
5
  module Labkit
4
6
  module RateLimit
5
7
  # Result is the return value of Limiter#check.
@@ -113,18 +115,13 @@ module Labkit
113
115
  @most_constraining ||= @evaluations.min
114
116
  end
115
117
 
116
- # Returns RFC-compliant rate limit response headers, or {} when no rule matched or an error occurred.
117
- # Keys: RateLimit-Limit, RateLimit-Remaining, RateLimit-Reset (Unix timestamp).
118
- # remaining is coerced to Integer for header output even when info.remaining is fractional;
119
- # the RateLimit header spec requires integer values.
118
+ # Returns the rate limit response headers of the most constraining
119
+ # evaluation, or {} when no rule matched, a :skip rule matched, or an
120
+ # error occurred. See Result::Evaluation#to_response_headers for the keys.
120
121
  def to_response_headers
121
122
  return {} unless matched? && !error? && info
122
123
 
123
- {
124
- "RateLimit-Limit" => info.resolved_limit.to_i.to_s,
125
- "RateLimit-Remaining" => info.remaining.to_i.to_s,
126
- "RateLimit-Reset" => info.reset_at.to_i.to_s
127
- }
124
+ most_constraining.to_response_headers
128
125
  end
129
126
  end
130
127
 
@@ -155,6 +152,32 @@ module Labkit
155
152
  constraint_rank <=> other.constraint_rank
156
153
  end
157
154
 
155
+ # Returns the rate limit response headers for this evaluation. Values are
156
+ # Strings, and counts are coerced to Integer because the RateLimit header
157
+ # spec requires integer values even when count is fractional.
158
+ #
159
+ # RateLimit-Name (the rule name), RateLimit-Limit, RateLimit-Observed,
160
+ # RateLimit-Remaining and RateLimit-Reset (Unix timestamp) are always
161
+ # present. Limit, Observed and Remaining all count within the rule's own
162
+ # period. A blocking evaluation also gets Retry-After (seconds) and
163
+ # RateLimit-ResetTime (HTTP date), which belong on the 429.
164
+ def to_response_headers
165
+ headers = {}
166
+ headers["RateLimit-Name"] = rule.name
167
+ # Floored at 0 like remaining: a callable limit can resolve below zero
168
+ # to deny all, and an older client could credit a counter below zero.
169
+ headers["RateLimit-Limit"] = [info.resolved_limit.to_i, 0].max.to_s
170
+ headers["RateLimit-Observed"] = [info.count.to_i, 0].max.to_s
171
+ headers["RateLimit-Remaining"] = info.remaining.to_i.to_s
172
+ headers["RateLimit-Reset"] = info.reset_at.to_i.to_s
173
+ return headers unless block?
174
+
175
+ # Rounded up so a client that honours it does not retry before the reset.
176
+ headers["Retry-After"] = [(info.reset_at - Time.now.utc).ceil, 0].max.to_s
177
+ headers["RateLimit-ResetTime"] = info.reset_at.httpdate
178
+ headers
179
+ end
180
+
158
181
  protected
159
182
 
160
183
  def constraint_rank
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gitlab-labkit
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.5.0
4
+ version: 5.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Newdigate