aboutsummaryrefslogtreecommitdiff
path: root/sx-request.el
diff options
context:
space:
mode:
Diffstat (limited to 'sx-request.el')
-rw-r--r--sx-request.el98
1 files changed, 73 insertions, 25 deletions
diff --git a/sx-request.el b/sx-request.el
index d982057..89c9a59 100644
--- a/sx-request.el
+++ b/sx-request.el
@@ -19,7 +19,30 @@
;;; Commentary:
+;; API requests are handled on three separate tiers:
+;;
+;; `sx-method-call':
;;
+;; This is the function that should be used most often, since it
+;; runs necessary checks (authentication) and provides basic
+;; processing of the result for consistency.
+;;
+;; `sx-request-make':
+;;
+;; This is the fundamental function for interacting with the API.
+;; It makes no provisions for 'common' usage, but it does ensure
+;; data is retrieved successfully or an appropriate signal is
+;; thrown.
+;;
+;; `url.el' and `json.el':
+;;
+;; The whole solution is built upon `url-retrieve-synchronously'
+;; for making the request and `json-read-from-string' for parsing
+;; it into a properly symbolic data structure.
+;;
+;; When at all possible, use ~sx-method-call~. There are specialized
+;; cases for the use of ~sx-request-make~ outside of =sx-method.el=, but
+;; these must be well-documented inline with the code.
;;; Code:
@@ -44,26 +67,28 @@
(format "https://api.stackexchange.com/%s/" sx-request-api-version)
"The base URL to make requests from.")
-(defcustom sx-request-silent-p
- t
- "When `t', requests default to being silent.")
-
+;;; @TODO Shouldn't this be made moot by our caching system?
(defcustom sx-request-cache-p
t
"Cache requests made to the StackExchange API.")
(defcustom sx-request-unzip-program
"gunzip"
- "program used to unzip the response")
+ "Program used to unzip the response if it is compressed.
+
+This program must accept compressed data on standard input.")
(defvar sx-request-remaining-api-requests
nil
- "The number of API requests remaining according to the most
-recent call. Set by `sx-request-make'.")
+ "The number of API requests remaining.
+
+Set by `sx-request-make'.")
(defcustom sx-request-remaining-api-requests-message-threshold
50
- "After `sx-request-remaining-api-requests' drops below this
+ "Lower bound for printed warnings of API usage limits.
+
+After `sx-request-remaining-api-requests' drops below this
number, `sx-request-make' will begin printing out the
number of requests left every time it finishes a call.")
@@ -71,26 +96,44 @@ number of requests left every time it finishes a call.")
;;; Making Requests
(defun sx-request-make
- (method &optional args need-auth use-post silent)
+ (method &optional args need-auth use-post)
+ "Make a request to the API, executing METHOD with ARGS.
+
+You should almost certainly be using `sx-method-call' instead of
+this function.
+
+Returns cleaned response content.
+See (`sx-encoding-clean-content-deep').
+
+The full call is built with `sx-request-build', prepending
+`sx-request-api-key' to receive a higher quota. This call is
+then resolved with `url-retrieve-synchronously' to a temporary
+buffer that it returns. The headers are then stripped using a
+search a blank line (\"\\n\\n\"). The main body of the response
+is then tested with `sx-encoding-gzipped-buffer-p' for
+compression. If it is compressed, `sx-request-unzip-program' is
+called to uncompress the response. The uncompressed respons is
+then read with `json-read-from-string'.
+
+`sx-request-remaining-api-requests' is updated appropriately and
+the main content of the response is returned."
(let ((url-automatic-caching sx-request-cache-p)
(url-inhibit-uncompression t)
- (silent (or silent sx-request-silent-p))
(request-method (if use-post "POST" "GET"))
(request-args
(sx-request--build-keyword-arguments args nil need-auth))
(request-url (concat sx-request-api-root method)))
- (unless silent (sx-message "Request: %S" request-url))
+ (sx-message "Request: %S" request-url)
(let ((response-buffer (sx-request--request request-url
request-args
- request-method
- silent)))
+ request-method)))
(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; response corrupt")
+ (error "Headers missing; response corrupt")
(delete-region (point-min) (point))
(buffer-string))))
(response-zipped-p (sx-encoding-gzipped-p data))
@@ -100,6 +143,8 @@ number of requests left every time it finishes a call.")
sx-request-unzip-program
nil t)
(buffer-string)))
+ ;; @TODO should use `condition-case' here -- set
+ ;; RESPONSE to 'corrupt or something
(response (with-demoted-errors "`json' error: %S"
(json-read-from-string data))))
(when (and (not response) (string-equal data "{}"))
@@ -110,8 +155,7 @@ number of requests left every time it finishes a call.")
(when .error_id
(error "Request failed: (%s) [%i %s] %S"
.method .error_id .error_name .error_message))
- (when (< (setq sx-request-remaining-api-requests
- .quota_remaining)
+ (when (< (setq sx-request-remaining-api-requests .quota_remaining)
sx-request-remaining-api-requests-message-threshold)
(sx-message "%d API requests reamining"
sx-request-remaining-api-requests))
@@ -120,19 +164,23 @@ number of requests left every time it finishes a call.")
;;; Support Functions
-(defun sx-request--request (url args method silent)
+(defun sx-request--request (url args method)
+ "Return the response buffer for URL with ARGS using METHOD."
(let ((url-request-method method)
(url-request-extra-headers
'(("Content-Type" . "application/x-www-form-urlencoded")))
(url-request-data args))
- (cond
- ((equal '(24 . 4) (cons emacs-major-version emacs-minor-version))
- (url-retrieve-synchronously url silent))
- (t (url-retrieve-synchronously url)))))
+ (url-retrieve-synchronously url)))
+
(defun sx-request--build-keyword-arguments (alist &optional
- kv-value-sep need-auth)
- "Build a \"key=value&key=value&...\"-style string with the elements
+ kv-sep need-auth)
+ "Format ALIST as a key-value list joined with KV-SEP.
+
+If authentication is needed, include it also or error if it is
+not available.
+
+Build a \"key=value&key=value&...\"-style string with the elements
of ALIST. If any value in the alist is `nil', that pair will not
be included in the return. If you wish to pass a notion of
false, use the symbol `false'. Each element is processed with
@@ -148,7 +196,7 @@ false, use the symbol `false'. Each element is processed with
;; Pass user error when asking to warn
(warn
(user-error
- "This query requires authentication. Please run `M-x sx-auth-authenticate' and try again."))
+ "This query requires authentication; run `M-x sx-auth-authenticate' and try again"))
((not auth)
(lwarn "stack-mode" :debug
"This query requires authentication")
@@ -161,7 +209,7 @@ false, use the symbol `false'. Each element is processed with
(concat
(sx--thing-as-string (car pair))
"="
- (sx--thing-as-string (cdr pair) kv-value-sep)))
+ (sx--thing-as-string (cdr pair) kv-sep)))
(delq nil (mapcar
(lambda (pair)
(when (cdr pair) pair))