Skip to content

Commit d9422ff

Browse files
Copilotdiasbruno
andcommitted
Use macro for redis connection binding in rate-limit store
Agent-Logs-Url: https://github.com/cl-sdk/wst/sessions/2c0d4f62-c4fe-49c2-a925-07238fb1818e Co-authored-by: diasbruno <362368+diasbruno@users.noreply.github.com>
1 parent 628ddba commit d9422ff

2 files changed

Lines changed: 42 additions & 49 deletions

File tree

rate-limit/redis-store.lisp

Lines changed: 25 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,7 @@
55
This backend implements the wst.rate-limit.store protocol using Redis hash
66
entries with fields:
77
- \"count\" : current request count
8-
- \"start\" : window start time (universal-time)
9-
10-
Redis access is delegated to COMMAND-FN, allowing integration with any Redis
11-
client library. COMMAND-FN receives command name as a string followed by
12-
command arguments.")
8+
- \"start\" : window start time (universal-time)")
139
(:export
1410
#:redis-store))
1511

@@ -20,29 +16,27 @@ command arguments.")
2016
:initform "wst:rate-limit:"
2117
:reader redis-store-key-prefix)
2218
(window-seconds :initarg :window-seconds
23-
:initform nil
24-
:reader redis-store-window-seconds)
19+
:initform nil
20+
:reader redis-store-window-seconds)
2521
(connection :initarg :connection
2622
:initform nil
27-
:reader redis-store-connection)
28-
(connection-fn :initarg :connection-fn
29-
:initform (lambda (store thunk)
30-
(let ((connection (redis-store-connection store)))
31-
(unless connection
32-
(error "No Redis connection configured. Pass a CL-REDIS connection via :connection when creating the store."))
33-
(let ((redis:*connection* connection))
34-
(funcall thunk))))
35-
:reader redis-store-connection-fn))
23+
:reader redis-store-connection))
3624
(:documentation "Redis-backed implementation of the rate-limit store protocol.
3725
3826
Slots:
3927
- KEY-PREFIX: string prefix used to namespace Redis keys.
4028
- WINDOW-SECONDS: optional TTL set on each saved key via EXPIRE.
41-
- CONNECTION: CL-REDIS connection object used as REDIS:*CONNECTION*.
42-
- CONNECTION-FN: function called as (connection-fn store thunk) to execute Redis calls."))
29+
- CONNECTION: CL-REDIS connection object used as REDIS:*CONNECTION*."))
4330

44-
(defun redis-store--with-connection (store thunk)
45-
(funcall (redis-store-connection-fn store) store thunk))
31+
(defmacro redis-store--with-connection ((store) &body body)
32+
(let ((store-var (gensym))
33+
(connection-var (gensym)))
34+
`(let* ((,store-var ,store)
35+
(,connection-var (redis-store-connection ,store-var)))
36+
(unless ,connection-var
37+
(error "No Redis connection configured. Pass a CL-REDIS connection via :connection when creating the store."))
38+
(let ((redis:*connection* ,connection-var))
39+
,@body))))
4640

4741
(defun redis-store--key (store key)
4842
(format nil "~a~a" (redis-store-key-prefix store) (write-to-string key :readably t)))
@@ -58,8 +52,8 @@ Slots:
5852

5953
(defmethod io.github.cl-sdk.wst.rate-limit.store:fetch-window ((store redis-store) key)
6054
(let* ((redis-key (redis-store--key store key))
61-
(reply (redis-store--with-connection store
62-
(lambda () (redis:hmget redis-key "count" "start"))))
55+
(reply (redis-store--with-connection (store)
56+
(redis:red-hmget redis-key "count" "start")))
6357
(count-raw (and (listp reply) (first reply)))
6458
(start-raw (and (listp reply) (second reply)))
6559
(count (redis-store--integer-or-nil count-raw))
@@ -71,16 +65,15 @@ Slots:
7165
(defmethod io.github.cl-sdk.wst.rate-limit.store:save-window ((store redis-store) key count start-time)
7266
(let* ((redis-key (redis-store--key store key))
7367
(ttl (redis-store-window-seconds store)))
74-
(redis-store--with-connection store
75-
(lambda ()
76-
(redis:hmset redis-key
77-
"count" (write-to-string count)
78-
"start" (write-to-string start-time))))
68+
(redis-store--with-connection (store)
69+
(redis:red-hmset redis-key
70+
"count" (write-to-string count)
71+
"start" (write-to-string start-time)))
7972
(when (and ttl (plusp ttl))
80-
(redis-store--with-connection store
81-
(lambda () (redis:expire redis-key ttl))))
73+
(redis-store--with-connection (store)
74+
(redis:red-expire redis-key ttl)))
8275
t))
8376

84-
(defmethod wst.rate-limit.store:delete-window ((store redis-store) key)
85-
(redis-store--with-connection store
86-
(lambda () (redis:del (redis-store--key store key)))))
77+
(defmethod io.github.cl-sdk.wst.rate-limit.store:delete-window ((store redis-store) key)
78+
(redis-store--with-connection (store)
79+
(redis:red-del (redis-store--key store key))))

t/rate-limit-redis-store-tests.lisp

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -12,38 +12,38 @@
1212
(let ((records (make-hash-table :test #'equal))
1313
(expiries (make-hash-table :test #'equal)))
1414
(values
15-
records
16-
expiries)))
15+
records
16+
expiries)))
1717

1818
(defun ensure-rate-limit-entry (records key)
1919
(or (gethash key records)
2020
(setf (gethash key records) (make-hash-table :test #'equal))))
2121

2222
(defmacro with-mocked-rate-limit-redis ((records expiries) &body body)
23-
`(let* ((hmget-original (symbol-function 'redis:hmget))
24-
(hmset-original (symbol-function 'redis:hmset))
25-
(expire-original (symbol-function 'redis:expire))
26-
(del-original (symbol-function 'redis:del)))
23+
`(let* ((hmget-original (symbol-function 'redis:red-hmget))
24+
(hmset-original (symbol-function 'redis:red-hmset))
25+
(expire-original (symbol-function 'redis:red-expire))
26+
(del-original (symbol-function 'redis:red-del)))
2727
(unwind-protect
2828
(progn
29-
(setf (symbol-function 'redis:hmget)
29+
(setf (symbol-function 'redis:red-hmget)
3030
(lambda (key field &rest fields)
3131
(let* ((entry (gethash key ,records))
3232
(wanted-fields (cons field fields)))
3333
(mapcar (lambda (field-name)
3434
(and entry (gethash field-name entry)))
3535
wanted-fields)))
36-
(symbol-function 'redis:hmset)
36+
(symbol-function 'redis:red-hmset)
3737
(lambda (key &rest fields-and-values)
3838
(let ((entry (ensure-rate-limit-entry ,records key)))
3939
(loop for (field value) on fields-and-values by #'cddr
4040
do (setf (gethash field entry) value)))
4141
"OK")
42-
(symbol-function 'redis:expire)
42+
(symbol-function 'redis:red-expire)
4343
(lambda (key ttl)
4444
(setf (gethash key ,expiries) ttl)
4545
t)
46-
(symbol-function 'redis:del)
46+
(symbol-function 'redis:red-del)
4747
(lambda (key &rest keys)
4848
(let ((removed 0))
4949
(dolist (k (cons key keys) removed)
@@ -52,17 +52,17 @@
5252
(remhash k ,records)
5353
(remhash k ,expiries)))))
5454
,@body)
55-
(setf (symbol-function 'redis:hmget) hmget-original
56-
(symbol-function 'redis:hmset) hmset-original
57-
(symbol-function 'redis:expire) expire-original
58-
(symbol-function 'redis:del) del-original))))
55+
(setf (symbol-function 'redis:red-hmget) hmget-original
56+
(symbol-function 'redis:red-hmset) hmset-original
57+
(symbol-function 'redis:red-expire) expire-original
58+
(symbol-function 'redis:red-del) del-original))))
5959

6060
(5am:def-test redis-rate-limit-store-roundtrip ()
6161
(multiple-value-bind (records expiries)
6262
(make-rate-limit-fake-redis)
6363
(declare (ignore _records _expiries))
6464
(let ((store (make-instance 'io.github.cl-sdk.wst.rate-limit.redis-store:redis-store
65-
:command-fn command-fn)))
65+
:connection (make-rate-limit-fake-redis))))
6666
(multiple-value-bind (count start)
6767
(io.github.cl-sdk.wst.rate-limit.store:fetch-window store :client-a)
6868
(5am:is-false count)
@@ -83,7 +83,7 @@
8383
(make-rate-limit-fake-redis)
8484
(declare (ignore _records))
8585
(let ((store (make-instance 'io.github.cl-sdk.wst.rate-limit.redis-store:redis-store
86-
:command-fn command-fn
87-
:window-seconds 42)))
86+
:window-seconds 42
87+
:connection (make-rate-limit-fake-redis))))
8888
(io.github.cl-sdk.wst.rate-limit.store:save-window store :client-b 1 2000)
8989
(5am:is (= 42 (gethash "wst:rate-limit::CLIENT-B" expiries)))))) ; key uses WRITE-TO-STRING on :CLIENT-B

0 commit comments

Comments
 (0)