From be74f6a7cffe3fc80604549da5d024652f63fd26 Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Fri, 13 Nov 2015 15:05:12 +0000 Subject: [PATCH] * lisp/emacs-lisp/rest.el: Finish renaming --- lisp/emacs-lisp/rest.el | 244 +++++++++++++++++++--------------------- 1 file changed, 115 insertions(+), 129 deletions(-) diff --git a/lisp/emacs-lisp/rest.el b/lisp/emacs-lisp/rest.el index 592836d1bd8..08408c660de 100644 --- a/lisp/emacs-lisp/rest.el +++ b/lisp/emacs-lisp/rest.el @@ -1,6 +1,6 @@ -;;; api.el --- library for interacting with restful web APIs -*- lexical-binding: t; -*- +;;; rest.el --- library for interacting with restful web APIs -*- lexical-binding: t; -*- -;; Copyright (C) 2015 Artur Malabarba +;; Copyright (C) 2015 Free Software Foundation, Inc. ;; Author: Artur Malabarba ;; Keywords: comm @@ -28,7 +28,7 @@ ;;; Error reporting -(defun api-report-buffer () +(defun rest-report-buffer () "Write TEXT to the *Api Server* buffer." (let ((text (buffer-string))) (with-current-buffer (get-buffer-create "*Api Report*") @@ -37,42 +37,42 @@ (insert text)) (goto-char (point-min))))) -(define-error 'api-error "Unkown api error, see \"*Api Report*\" buffer") -(define-error 'api-page-does-not-exist +(define-error 'rest-error "Unkown REST error, see \"*Api Report*\" buffer") +(define-error 'rest-page-does-not-exist "This page doesn't seem to exist (see \"*Api Report*\" buffer), replied said" - 'api-error) -(define-error 'api-empty-redirect + 'rest-error) +(define-error 'rest-empty-redirect "Redirect received, but Location header not found! (see \"*Api Report*\" buffer)" - 'api-error) -(define-error 'api-unintelligible-result + 'rest-error) +(define-error 'rest-unintelligible-result "Tried contacting server, but I can't understand the reply. See \"*Api Report*\" buffer" - 'api-error) -(define-error 'api-bad-request + 'rest-error) +(define-error 'rest-bad-request "Server didn't understand my request, please you should probably file a bug report" - 'api-error) -(define-error 'api-unauthorized + 'rest-error) +(define-error 'rest-unauthorized "Server says you're not authenticated" - 'api-error) -(define-error 'api-infinite-redirection-loop + 'rest-error) +(define-error 'rest-infinite-redirection-loop "Server is sending us in a redirection loop" - 'api-error) -(define-error 'api-server-error + 'rest-error) +(define-error 'rest-server-error "Something bad happened on the server side, see \"*Api Report*\" buffer" - 'api-error) + 'rest-error) -(defun api-error (signal &rest args) +(defun rest-error (signal &rest args) "Throw an error SIGNAL with ARGS. Also print contents of current buffer to *Api Report*." (declare (indent 1)) - (api-report-buffer) + (rest-report-buffer) (signal signal args)) -(defun api-parse-response-code (&optional is-auth) +(defun rest-parse-response-code (&optional is-auth) "Non-nil if this reponse buffer looks ok. Leave point at the return code on the first line." (goto-char (point-min)) (unless (search-forward-regexp "^HTTP/[.0-9]+ +" nil t) - (api-error 'api-unintelligible-result)) + (rest-error 'rest-unintelligible-result)) (pcase (thing-at-point 'number) ((or 100 204) nil) ;; OK, but no content. ((or 200 202 201 203) t) ;; OK, with content. @@ -80,40 +80,31 @@ Leave point at the return code on the first line." ((or 301 302 303 304 305 307) (if (search-forward-regexp "^Location: *\\([^\s\n\r\t]+\\)" nil 'noerror) (match-string 1) - (api-error 'api-empty-redirect))) + (rest-error 'rest-empty-redirect))) ;; Client errors - ((or 403 404 405 410) (api-error 'api-page-does-not-exist - (substring-no-properties (thing-at-point 'line) 0 -1))) - ((or 400 422 408 409 411 412 413 414 415 416 417) (api-error 'api-bad-request)) - ((or 401 407) (api-error 'api-unauthorized - (if is-auth "try creating a new token" - "you probably need to configure a token"))) - ((pred (<= 500)) (api-error 'api-server-error)) - (_ (api-error 'api-error - (substring (thing-at-point 'line) 0 -1))))) + ((or 403 404 405 410) (rest-error 'rest-page-does-not-exist + (substring-no-properties (thing-at-point 'line) 0 -1))) + ((or 400 422 408 409 411 412 413 414 415 416 417) (rest-error 'rest-bad-request)) + ((or 401 407) (rest-error 'rest-unauthorized + (if is-auth "try creating a new token" + "you probably need to configure a token"))) + ((pred (<= 500)) (rest-error 'rest-server-error)) + (_ (rest-error 'rest-error + (substring (thing-at-point 'line) 0 -1))))) ;;; Requests -(autoload 'auth-source-search "auth-source") -(cl-defmacro api--with-server-buffer (method url &rest body &key async unwind-form - extra-headers &allow-other-keys) +(cl-defmacro rest--with-response-buffer (method url &rest body &key async unwind-form + extra-headers &allow-other-keys) "Run BODY in a Server request buffer. UNWIND-FORM is run no matter what, and doesn't affect the return value." (declare (indent 2) (debug t)) - (let ((call-name (make-symbol "callback")) - (secret (make-symbol "secret"))) + (let ((call-name (make-symbol "callback"))) (while (keywordp (car body)) (setq body (cdr (cdr body)))) - `(let ((,secret (when ,auth - (if (listp ,auth) - (or (car-safe (apply #'auth-source-search - :require '(:secret) :max 1 - ,auth)) - (user-error "This request requires authentication")) - (lambda () ,auth)))) - (,call-name (lambda (status) + `(let ((,call-name (lambda (status) (unwind-protect (progn (when-let ((er (plist-get status :error))) (error "Error retrieving: %s %S" ,url er)) @@ -138,10 +129,10 @@ value." (with-current-buffer buffer (funcall ,call-name nil)))))))) -(defvar-local api-root nil - "Prepended to api method when a full url is not given.") +(defvar-local rest-url-root nil + "Prepended to REST url when a full url is not given.") -(defun api--headers-alist () +(defun rest--headers-alist () "Return an alist of all headers above point." (let ((ac)) (while (search-backward-regexp "^\\(X-[^ ]+\\): *\\(.*?\\)\r?$" nil 'noerror) @@ -149,64 +140,59 @@ value." ac)) ac)) -(autoload 'json-read "json") - -(defvar api--url-depth nil - "Used to detect infinite redirection loops.") - ;;; Authentication -(defun api--auth-source-search (url-obj) +(autoload 'auth-source-search "auth-source") +(defun rest--auth-source-search (url-obj) "Return authentication information for URL-OBJ. URL-OBJ is a value returned by `url-generic-parse-url'. Information is found by running `auth-source-search' with the properties of URL-OBJ." - (let ((type (url-type url-obj)) - (port (url-port url-obj)) + (let ((port (url-port url-obj)) (args (list :require '(:secret) :host (url-host url-obj) :max 1 :user (url-user url-obj)))) - (car (or (apply #'auth-source-search :port port :type type args) - (apply #'auth-source-search :port port args) + (car (or (apply #'auth-source-search :port port args) ;; If URL does not specify a port, try again without the default. (unless (url-portspec url-obj) - (or (apply #'auth-source-search :type type args) - (apply #'auth-source-search args))))))) + (or (apply #'auth-source-search args))))))) -(defun api--get-auth-info (info) +(defun rest--get-auth-info (info) "Return a function that returns (USER . PASSWORD). INFO is a plist returned by `auth-source-search'." (let ((user (plist-get info :user)) (pass (plist-get info :secret))) (lambda () (cons user (funcall pass))))) -(defun api--make-authorization-header (_plist user password) +(defun rest--make-authorization-header (_plist user password) "Return an alist containing an \"Authorization\" header. The car of the list is nil, so this function can be used as the -AUTH-METHOD in `api-action'." +AUTH-METHOD in `rest-action'." `(nil . (("Authorization" . ,(concat "Basic " (base64-encode-string (concat user ":" password))))))) ;;; The function +(autoload 'json-read "json") + ;;;###autoload -(cl-defun api-action (url &rest all-options - &key auth - (method :get) - (reader #'json-read) - (callback #'identity) - async - (max-pages 1) - (next-page-rule '(header "Link")) - extra-headers - (auth-method (if auth #'api--make-authorization-header)) - (return :simple) - -url-history) +(cl-defun rest-action (url &rest all-options + &key auth + (method :get) + (reader #'json-read) + (callback #'identity) + async + (max-pages 1) + (next-page-rule '(header "Link")) + extra-headers + (auth-method (if auth #'rest--make-authorization-header)) + (return :simple) + -url-history) "Contact URL with METHOD. METHOD is a keyword of an http method, defaulting to :get. URL can be a string such as \"user/starred?per_page=100\" to -be appended at the end of `api-root'. It can also be a full url +be appended at the end of `rest-url-root'. It can also be a full url string, in which case it is used verbatim. READER is called as a function with no arguments, with point @@ -245,7 +231,7 @@ replaces URL and the cdr is appended to EXTRA-HEADERS. It is called with a plist, the user string and the password string. The plist contais at least :url, :method, and :extra-headers. -`api-action' can also handle the pagination used in server +`rest-action' can also handle the pagination used in server results by appending together the contents of each page. Use MAX-PAGES to increase the number of pages that are fetched (default 1). @@ -263,25 +249,25 @@ values (string), as per `url-request-extra-headers'. If the http request is unsuccessful, an error is signaled according to the reply. The possible errors are: -`api-bad-request', `api-server-error', `api-unauthorized', -`api-unintelligible-result', `api-empty-redirect', -`api-page-does-not-exist', and `api-infinite-redirection-loop', -all of which inherit from `api-error'. +`rest-bad-request', `rest-server-error', `rest-unauthorized', +`rest-unintelligible-result', `rest-empty-redirect', +`rest-page-does-not-exist', and `rest-infinite-redirection-loop', +all of which inherit from `rest-error'. \(fn URL &key AUTH (METHOD :get) (READER #'json-read) CALLBACK ASYNC AUTH-METHOD (MAX-PAGES 1) NEXT-PAGE-RULE EXTRA-HEADERS RETURN)" (declare (indent 1)) (unless (string-match "\\`https?://" url) - (setq url (concat api-root url))) + (setq url (concat rest-url-root url))) (when (member url -url-history) - (signal 'api-infinite-redirection-loop (cons url api--url-depth))) + (signal 'rest-infinite-redirection-loop (cons url -url-history))) (when auth (let ((href (url-generic-parse-url url))) (when (url-password href) (error "AUTH requested, but URL already contains a password")) (unless (functionp auth) - (setq auth (api--get-auth-info (if (listp auth) - (apply #'auth-source-search auth) - (api--auth-source-search href))))) + (setq auth (rest--get-auth-info (if (listp auth) + (apply #'auth-source-search auth) + (rest--auth-source-search href))))) (pcase-let* ((`(,user . ,pass) (funcall auth)) (`(,new-url . ,headers) (funcall auth-method (list :url url :method method @@ -289,47 +275,47 @@ all of which inherit from `api-error'. user pass))) (when new-url (setq url new-url)) (setq extra-headers (append headers extra-headers))))) - (api--with-server-buffer method url - :extra-headers extra-headers - :-url-depth (cons url -url-history) - :async async - (pcase (api-parse-response-code auth) - (`nil nil) - ((and (pred stringp) link) - (message "Redirected to %s" link) - (apply #'api-action all-options)) - (`t - (let ((next-page - (when (pcase next-page-rule - (`(header ,name) (search-forward-regexp - (format "^%s: .*<\\([^>]+\\)>;" (regexp-quote name)) - nil t)) - (`(regexp ,rx) (search-forward-regexp rx nil t)) - (_ nil)) - (match-string-no-properties 1)))) - (goto-char (point-min)) - (search-forward-regexp "^\r?$") - (let* ((data (unless (eobp) (funcall reader)))) - (if (or (not next-page) - (< max-pages 2)) - (pcase return - (:simple (funcall callback data)) - (:rich `(,(funcall callback data) - (next-page . ,next-page) - ,@(api--headers-alist)))) - (api-action next-page - :auth auth - :method method - :reader reader - :next-page-rule next-page-rule - :return return - :async async - :max-pages (1- max-pages) - :callback (lambda (res) - (funcall callback - (if (listp res) - (append data res) - (vconcat data res)))))))))))) + (rest--with-response-buffer method url + :extra-headers extra-headers + :-url-depth (cons url -url-history) + :async async + (pcase (rest-parse-response-code auth) + (`nil nil) + ((and (pred stringp) link) + (message "Redirected to %s" link) + (apply #'rest-action all-options)) + (`t + (let ((next-page + (when (pcase next-page-rule + (`(header ,name) (search-forward-regexp + (format "^%s: .*<\\([^>]+\\)>;" (regexp-quote name)) + nil t)) + (`(regexp ,rx) (search-forward-regexp rx nil t)) + (_ nil)) + (match-string-no-properties 1)))) + (goto-char (point-min)) + (search-forward-regexp "^\r?$") + (let* ((data (unless (eobp) (funcall reader)))) + (if (or (not next-page) + (< max-pages 2)) + (pcase return + (:simple (funcall callback data)) + (:rich `(,(funcall callback data) + (next-page . ,next-page) + ,@(rest--headers-alist)))) + (rest-action next-page + :auth auth + :method method + :reader reader + :next-page-rule next-page-rule + :return return + :async async + :max-pages (1- max-pages) + :callback (lambda (res) + (funcall callback + (if (listp res) + (append data res) + (vconcat data res)))))))))))) -(provide 'api) -;;; api.el ends here +(provide 'rest) +;;; rest.el ends here