diff options
Diffstat (limited to 'sx-request.el')
-rw-r--r-- | sx-request.el | 98 |
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)) |