diff options
author | Sean Allred <code@seanallred.com> | 2014-10-31 18:51:00 -0400 |
---|---|---|
committer | Sean Allred <code@seanallred.com> | 2014-10-31 18:51:00 -0400 |
commit | 55111f36af271b85f452d3774beb89b0aa85cfc6 (patch) | |
tree | 31db05e192140e0a28b848d3dafa5665c6772e39 | |
parent | f0f3d7675d6510add4b28fac1cb5823a658c1ec8 (diff) |
Handle case where API response is compressed
The StackExchange API explicitly states that responses to requests are
gzipped. For some reason, they are not zipped when called locally on my
machine, but they are zipped when run from Travis CI.
I do not know if `url-retrieve-synchronously' is performing any magic.
When I curl the request, I receive a gzipped response (as expected). A
proper fix for this would be to somehow advise `url-*' to request as
curl does.
-rw-r--r-- | stack-core.el | 97 | ||||
-rw-r--r-- | tests.el | 8 |
2 files changed, 64 insertions, 41 deletions
diff --git a/stack-core.el b/stack-core.el index 1bdf20f..082af83 100644 --- a/stack-core.el +++ b/stack-core.el @@ -143,51 +143,66 @@ with the given KEYWORD-ARGUMENTS." base (concat base "?" args)))) -(defun stack-core-make-request (method &optional keyword-arguments filter) +(defun stack-core-make-request + (method &optional keyword-arguments filter silent) "Make a request to the StackExchange API using METHOD and optional KEYWORD-ARGUMENTS. If no KEYWORD-ARGUMENTS are given, `stack-core-default-keyword-arguments-alist' is used. Return the entire response as a complex alist." - (let* ((api-response - (let ((call - (stack-core-build-request - method - (cons `(filter . ,(cond - (filter filter) - ((boundp 'stack-filter) - stack-filter))) - (if keyword-arguments keyword-arguments - (stack-core-get-default-keyword-arguments - method))))) - (url-automatic-caching stack-core-cache-requests)) - ;; TODO: url-retrieve-synchronously can return nil if the call is - ;; unsuccessful should handle this case - (stack-message "Request: %s" call) - (with-current-buffer (url-retrieve-synchronously call) - (goto-char (point-min)) - (if (not (search-forward "\n\n" nil t)) - (error "Response headers missing") - (delete-region (point-min) (point)) - (buffer-string))))) - (response - (with-demoted-errors "JSON Error: %s" - (json-read-from-string api-response)))) - (unless response - (stack-message "Printing response as message") - (message response) - (error "Response could not be read by json-read-string")) - (when (assoc 'error_id response) - (error "Request failed: (%s) [%i %s] %s" - method - (cdr (assoc 'error_id response)) - (cdr (assoc 'error_name response)) - (cdr (assoc 'error_message response)))) - (when (< (setq stack-core-remaining-api-requests - (cdr (assoc 'quota_remaining response))) - stack-core-remaining-api-requests-message-threshold) - (stack-message "%d API requests remaining" - stack-core-remaining-api-requests)) - (cdr (assoc 'items response)))) + (let ((url-automatic-caching stack-core-cache-requests) + (call + (stack-core-build-request + method + (cons `(filter . ,(cond (filter filter) + ((boundp 'stack-filter) stack-filter))) + (if keyword-arguments keyword-arguments + (stack-core-get-default-keyword-arguments method)))))) + ;; TODO: url-retrieve-synchronously can return nil if the call is + ;; unsuccessful should handle this case + (unless silent (stack-message "Request: %S" call)) + (let ((response-buffer (url-retrieve-synchronously + call silent))) + (if (not response-buffer) + (error "Something went wrong in `url-retrieve-synchronously'") + (with-current-buffer response-buffer + (let* ((data (progn + (goto-char (point-min)) + (if (not (search-forward "\n\n" nil t)) + (error "Response headers missing") + (delete-region (point-min) (point)) + (buffer-string)))) + (response (ignore-errors + (json-read-from-string data)))) + ;; if response isn't nil, the response was in plain text + (unless response + ;; try to decompress the response + (setq response + (with-demoted-errors "JSON Error: %s" + (shell-command-on-region + (point-min) (point-max) + stack-core-unzip-program + nil t) + (json-read-from-string (buffer-string)))) + ;; If it still fails, error out + (unless response + (stack-message "Unable to parse response") + (stack-message "Printing response as message") + (message "%S" response) + (error "Response could not be read by json-read-string"))) + ;; At this point, either response is a valid data structure + ;; or we have already thrown an error + (when (assoc 'error_id response) + (error "Request failed: (%s) [%i %s] %s" + method + (cdr (assoc 'error_id response)) + (cdr (assoc 'error_name response)) + (cdr (assoc 'error_message response)))) + (when (< (setq stack-core-remaining-api-requests + (cdr (assoc 'quota_remaining response))) + stack-core-remaining-api-requests-message-threshold) + (stack-message "%d API requests remaining" + stack-core-remaining-api-requests)) + (cdr (assoc 'items response)))))))) (provide 'stack-core) ;;; stack-core.el ends here @@ -6,6 +6,8 @@ (unintern symbol))))) ;;; Tests + +(setq stack-core-remaining-api-requests-message-threshold 1000000000) (setq debug-on-error t) (require 'stack-core) @@ -13,9 +15,15 @@ (setq stack-core-remaining-api-requests-message-threshold 50000) +(ert-deftest test-basic-request () + "Test basic request functionality" + (should (stack-core-make-request "sites"))) + (ert-deftest test-question-retrieve () + "Test the ability to receive a list of questions." (should (stack-question-get-questions 'emacs))) (ert-deftest test-bad-request () + "Test a method given a bad set of keywords" (should-error (stack-core-make-request "questions" '(())))) |