Skip to content

Commit d0676c0

Browse files
Copilotdiasbruno
andauthored
fix accept specificity precedence and apply review suggestions
Agent-Logs-Url: https://github.com/cl-sdk/io.github.cl-sdk.wst/sessions/8bd1c9ad-db36-47af-8308-90945d4f986d Co-authored-by: diasbruno <362368+diasbruno@users.noreply.github.com>
1 parent 0da526f commit d0676c0

2 files changed

Lines changed: 55 additions & 9 deletions

File tree

request-accept/package.lisp

Lines changed: 35 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,17 @@
1515
(defconstant +ascii-printable-end+ 126)
1616
;; C0 control upper bound (US, 31) used to reject control chars except HTAB.
1717
(defconstant +ascii-control-end+ 31)
18+
(defconstant +invalid-media-range-specificity+ -1)
1819

19-
(defparameter *default-response-accept* '(:|text/plain| ("q" "1.0")))
20+
(defparameter *default-response-accept* '(:|text/plain| ("q" . "1.0")))
2021

2122
(defgeneric respond-with (implementation content request response)
22-
(:documentation "Render CONTENT according to IMPLEMENTATION (a selected media type).")
23+
(:documentation "Render CONTENT according to IMPLEMENTATION (a selected media type).
24+
25+
The default method returns RESPONSE unchanged.")
2326
(:method ((implementation t) content request response)
24-
content))
27+
(declare (ignore implementation content request))
28+
response))
2529

2630
(defun %find-response-accept-for-type (response-accepts media-type)
2731
(if (string-equal media-type "*")
@@ -37,10 +41,12 @@
3741
(media-type (first mime-sub))
3842
(media-subtype (second mime-sub)))
3943
(cond
40-
((and (string-equal "*" media-type)
44+
((and media-type media-subtype
45+
(string-equal "*" media-type)
4146
(string-equal "*" media-subtype))
4247
(car response-accepts))
43-
((string-equal "*" media-subtype)
48+
((and media-subtype
49+
(string-equal "*" media-subtype))
4450
(%find-response-accept-for-type response-accepts media-type))
4551
(t
4652
(find (car request-accept) response-accepts :test #'eq)))))
@@ -129,13 +135,33 @@ REQUEST-ACCEPTS must be ordered by preference (for example, output from
129135
(defun %process-request-accepts (request-accepts)
130136
(labels ((get-accept-entry-quality-value (entry)
131137
(or (find-if (lambda (item) (string-equal (car item) "q"))
132-
entry)
133-
'("q" . "1.0"))))
138+
entry)
139+
'("q" . "1.0")))
140+
(media-range-specificity (entry)
141+
(let* ((parts (split "/" (string (car entry))))
142+
(media-type (first parts))
143+
(media-subtype (second parts)))
144+
(cond
145+
((and media-type media-subtype
146+
(string-equal media-type "*")
147+
(string-equal media-subtype "*"))
148+
0)
149+
((and media-subtype
150+
(string-equal media-subtype "*"))
151+
1)
152+
((and media-type media-subtype)
153+
2)
154+
(t
155+
+invalid-media-range-specificity+)))))
134156
(sort request-accepts
135157
(lambda (a b)
136158
(let* ((qa (get-accept-entry-quality-value (cdr a)))
137-
(qb (get-accept-entry-quality-value (cdr b))))
138-
(> (serapeum:parse-float (cdr qa)) (serapeum:parse-float (cdr qb))))))))
159+
(qb (get-accept-entry-quality-value (cdr b)))
160+
(qa-value (serapeum:parse-float (cdr qa)))
161+
(qb-value (serapeum:parse-float (cdr qb))))
162+
(if (= qa-value qb-value)
163+
(> (media-range-specificity a) (media-range-specificity b))
164+
(> qa-value qb-value)))))))
139165

140166
(defun parse-request-accept (accept-header)
141167
"Parse an HTTP Accept header into media-range entries.

t/request-accept-tests.lisp

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,33 @@
3030
(io.github.cl-sdk.wst.request-accept:parse-request-accept
3131
"text/plain; foo"))))
3232

33+
(5am:def-test parse-request-accept-prioritizes-specific-on-equal-quality ()
34+
(5am:is (equal '((:|application/json| ("q" . "1.0"))
35+
(:|text/*| ("q" . "1.0")))
36+
(io.github.cl-sdk.wst.request-accept:parse-request-accept
37+
"text/*, application/json"))))
38+
3339
(5am:def-test find-best-response-accept-picks-supported-wildcard-type ()
3440
(5am:is (equal '(:|text/html| ("q" . "1.0"))
3541
(io.github.cl-sdk.wst.request-accept:find-best-response-accept
3642
'(:|application/json| :|text/html|)
3743
'((:|text/*| ("q" . "1.0"))
3844
(:|application/*| ("q" . "0.9")))))))
3945

46+
(5am:def-test find-best-response-accept-prefers-specific-media-type ()
47+
(5am:is (equal '(:|application/json| ("q" . "1.0"))
48+
(io.github.cl-sdk.wst.request-accept:find-best-response-accept
49+
'(:|application/json| :|text/html|)
50+
(io.github.cl-sdk.wst.request-accept:parse-request-accept
51+
"text/*, application/json")))))
52+
53+
(5am:def-test find-best-response-accept-ignores-malformed-media-range ()
54+
(5am:is (equal '(:|application/json| ("q" . "1.0"))
55+
(io.github.cl-sdk.wst.request-accept:find-best-response-accept
56+
'(:|application/json| :|text/html|)
57+
(io.github.cl-sdk.wst.request-accept:parse-request-accept
58+
"text, application/json")))))
59+
4060
(5am:def-test find-best-response-accept-falls-back-to-any-response-for-star-star ()
4161
(5am:is (equal '(:|application/json| ("q" . "0.8"))
4262
(io.github.cl-sdk.wst.request-accept:find-best-response-accept

0 commit comments

Comments
 (0)