rest-bind hygeine pasted by andyjpb on Tue Oct 14 20:11:16 2014

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; REST Procedure Call
;;; Generates wrappers to REST-like HTTP APIs
;;;
;;;  Copyright (C) 2012, Andy Bennett
;;;  All rights reserved.
;;;
;;;  Redistribution and use in source and binary forms, with or without
;;;  modification, are permitted provided that the following conditions are met:
;;;
;;;  Redistributions of source code must retain the above copyright notice, this
;;;  list of conditions and the following disclaimer.
;;;  Redistributions in binary form must reproduce the above copyright notice,
;;;  this list of conditions and the following disclaimer in the documentation
;;;  and/or other materials provided with the distribution.
;;;  Neither the name of the author nor the names of its contributors may be
;;;  used to endorse or promote products derived from this software without
;;;  specific prior written permission.
;;;
;;;  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
;;;  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
;;;  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
;;;  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
;;;  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
;;;  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
;;;  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
;;;  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
;;;  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
;;;  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
;;;  POSSIBILITY OF SUCH DAMAGE.
;;;
;;; Andy Bennett , 2012/10/29
;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(module rest-bind
 (define-method)

(import chicken scheme)
(import-for-syntax chicken srfi-1)
(use data-structures)
(use intarweb uri-common)

; (define-method (name args...) endpoint writer reader #!optional header)
; -> if no writer is provided, generates a procedure (name #!optional args...)
;    otherwise generates a procedure (name body #!optional args...)
; endpoint is the URI provided for the API call
; writer is a procedure of one argument.
;   writer is called with body
;   writer should return something suitable for passing to
;   call-with-input-request
;     i.e. a string containing the raw data to send, an alist or a pair of
;     values: a procedure that accepts a port and writes the response data to
;     it and an alist of extra headers. If you supply a pair of values then do
;     not forget to include a content-length header in the accompanying alist.
(define-syntax define-method
  (ir-macro-transformer
    (lambda (expr inject compare)
      (assert (pair? expr))
      (let* ((proc (car expr))
	     (defn (cdr expr))
	     (_ (assert (list? defn)))
	     (_ (assert (or (= 4 (length defn)) (= 5 (length defn)))))
	     (sig      (first defn))
	     (name     (car sig))
	     (args     (cdr sig))
	     (pred     (lambda (x) (not (eqv? '#!key x))))
	     (params   (drop-while pred args))
	     (params   (if (null? params) params (cdr params)))
	     (uri-args  (take-while pred args))
	     (mutate-args (reverse
			    (fold (lambda (arg args)
				    (if (list? arg)
				      (cons
					`(,(first arg) (,(second arg) ,(first arg)))
					args)))
				  '() uri-args)))
	     (uri-args    (reverse
			    (fold (lambda (arg args)
				    (cons
				      (if (list? arg) (first arg) arg)
				      args))
				  '() uri-args)))
	     (proc-args (reverse
			  (fold (lambda (arg args)
				  (cond ((symbol? arg) (cons arg args))
					((string? arg) args)
					(else (abort (conc "Cannot handle " arg)))))
				'() uri-args)))
	     (uri/req  (second defn))
	     (writer   (third defn))
	     (reader   (fourth defn))
	     (header-reader (if (= 5 (length defn)) (fifth defn) #f))
	     (proc-args (if writer
			  `(,@proc-args body)
			  `(,@proc-args)))
	     (proc-args (if (not (null? params))
			  `(,@proc-args #!key ,@params)
			  `(,@proc-args))))
	;(list 'quote
	      `(define ,name
		 (let* ((writer  ,writer)
			,@(if header-reader `((header-reader ,header-reader)) '())
			(uri/req ,uri/req)
			(uri     (cond ((request? uri/req) (request-uri uri/req))
				       ((uri? uri/req) uri/req)
				       (else (uri-reference uri/req))))
			(req     (cond ((request? uri/req) uri/req)
				       (else (make-request method: (if writer 'POST 'GET)))))
			)
		   (lambda ,proc-args
		     (let* (,@(if (null? mutate-args) '() mutate-args)
			    ,@(if (not (null? uri-args))
			      `((uri (update-uri uri path: (append (uri-path uri) (map ->string (list ,@uri-args)))))
				      ;(_ (pp (uri-path uri)))
			       )
			      '())
			   ,@(if (not (null? params))
			       `((uri (update-uri uri query: (append (uri-query uri)
								     (list ,@(map (lambda (param)
										    ``(,',param . ,(if ,param
												     (->string ,param)
												     ,param)))
										  params))))))
				   '())
			   (req (update-request req uri: uri))
			   )


		       (fprintf (current-error-port) "\n\n\nREQUEST\n")
		       (write-request (update-request req port: (current-error-port)))
		       (fprintf (current-error-port) "\n")


		       (receive (reader uri response)
				(call-with-input-request req ,(if writer '(writer body) #f) ,reader)
				,(if header-reader
					'(values (header-reader (response-headers response)) reader (list uri response))
					'(values reader (list uri response))))
				))))
	      ;)
	))))

)

-----

(use http-client uri-common intarweb rest-bind medea)

(define-method (account/info (path (lambda (arg) (string-append "hello-" path)))) "https://api.dropbox.com/1/account/info" #f read-json)

(define-method (account/info (path (lambda (arg) (string-append "hello-" arg)))) "https://api.dropbox.com/1/account/info" #f read-json)

pp output pasted by C-Keen on Tue Oct 14 20:24:05 2014

#;6> (define-method (account/info (path (lambda (arg) (string-append "hello-" arg)))) "https://api.dropbox.com/1/account/info" #f read-json)
'(define account/info963
   (let* ((writer #f)
          (uri/req "https://api.dropbox.com/1/account/info")
          (uri (cond ((request? uri/req) (request-uri uri/req))
                     ((uri? uri/req) uri/req)
                     (else (uri-reference uri/req))))
          (req (cond ((request? uri/req) uri/req)
                     (else (make-request method: (if writer 'POST 'GET))))))
     (lambda (path964)
       (let* ((path964
                ((lambda965 (arg966) (string-append967 "hello-" arg966))
                 path964))
              (uri (update-uri
                     uri
                     path:
                     (append (uri-path uri) (map ->string (list path964)))))
              (req (update-request req uri: uri)))
         (fprintf (current-error-port) "\n\n\nREQUEST\n")
         (write-request (update-request req port: (current-error-port)))
         (fprintf (current-error-port) "\n")
         (receive
           (reader uri response)
           (call-with-input-request req #f read-json968)
           (values reader (list uri response)))))))

csi pasted by andyjpb on Tue Oct 14 20:25:42 2014

$ csi

CHICKEN
(c) 2008-2014, The Chicken Team
(c) 2000-2007, Felix L. Winkelmann
Version 4.9.0rc1 (rev 3cf1967)
linux-unix-gnu-x86-64 [ 64bit manyargs dload ptables ]
compiled 2014-04-17 on hd-t1179cl (Linux)

; loading /home/local/andyjpb/.csirc ...
; loading /usr/local/chicken-4.9.0/lib/chicken/7/parley.import.so ...
; loading /usr/local/chicken-4.9.0/lib/chicken/7/chicken.import.so ...
; loading /usr/local/chicken-4.9.0/lib/chicken/7/data-structures.import.so ...
; loading /usr/local/chicken-4.9.0/lib/chicken/7/extras.import.so ...
; loading /usr/local/chicken-4.9.0/lib/chicken/7/ports.import.so ...
; loading /usr/local/chicken-4.9.0/lib/chicken/7/posix.import.so ...
; loading /usr/local/chicken-4.9.0/lib/chicken/7/srfi-1.import.so ...
; loading /usr/local/chicken-4.9.0/lib/chicken/7/srfi-13.import.so ...
; loading /usr/local/chicken-4.9.0/lib/chicken/7/srfi-18.import.so ...
; loading /usr/local/chicken-4.9.0/lib/chicken/7/stty.import.so ...
; loading /usr/local/chicken-4.9.0/lib/chicken/7/srfi-69.import.so ...
; loading /usr/local/chicken-4.9.0/lib/chicken/7/foreign.import.so ...
; loading /usr/local/chicken-4.9.0/lib/chicken/7/foreigners.import.so ...
; loading /usr/local/chicken-4.9.0/lib/chicken/7/parley.so ...
; loading /usr/local/chicken-4.9.0/lib/chicken/7/stty.so ...
#;1> (use http-client uri-common intarweb rest-bind medea)
; loading /usr/local/chicken-4.9.0/lib/chicken/7/http-client.import.so ...
; loading /usr/local/chicken-4.9.0/lib/chicken/7/lolevel.import.so ...
; loading /usr/local/chicken-4.9.0/lib/chicken/7/files.import.so ...
; loading /usr/local/chicken-4.9.0/lib/chicken/7/tcp.import.so ...
; loading /usr/local/chicken-4.9.0/lib/chicken/7/intarweb.import.so ...
; loading /usr/local/chicken-4.9.0/lib/chicken/7/srfi-14.import.so ...
; loading /usr/local/chicken-4.9.0/lib/chicken/7/irregex.import.so ...
; loading /usr/local/chicken-4.9.0/lib/chicken/7/base64.import.so ...
; loading /usr/local/chicken-4.9.0/lib/chicken/7/defstruct.import.so ...
; loading /usr/local/chicken-4.9.0/lib/chicken/7/uri-common.import.so ...
; loading /usr/local/chicken-4.9.0/lib/chicken/7/matchable.import.so ...
; loading /usr/local/chicken-4.9.0/lib/chicken/7/uri-generic.import.so ...
; loading /usr/local/chicken-4.9.0/lib/chicken/7/srfi-4.import.so ...
; loading /usr/local/chicken-4.9.0/lib/chicken/7/message-digest.import.so ...
; loading /usr/local/chicken-4.9.0/lib/chicken/7/message-digest-primitive.import.so ...
; loading /usr/local/chicken-4.9.0/lib/chicken/7/type-checks.import.so ...
; loading /usr/local/chicken-4.9.0/lib/chicken/7/type-errors.import.so ...
; loading /usr/local/chicken-4.9.0/lib/chicken/7/message-digest-type.import.so ...
; loading /usr/local/chicken-4.9.0/lib/chicken/7/blob-hexadecimal.import.so ...
; loading /usr/local/chicken-4.9.0/lib/chicken/7/to-hex.import.so ...
; loading /usr/local/chicken-4.9.0/lib/chicken/7/string-hexadecimal.import.so ...
; loading /usr/local/chicken-4.9.0/lib/chicken/7/message-digest-parameters.import.so ...
; loading /usr/local/chicken-4.9.0/lib/chicken/7/variable-item.import.so ...
; loading /usr/local/chicken-4.9.0/lib/chicken/7/message-digest-bv.import.so ...
; loading /usr/local/chicken-4.9.0/lib/chicken/7/message-digest-support.import.so ...
; loading /usr/local/chicken-4.9.0/lib/chicken/7/message-digest-int.import.so ...
; loading /usr/local/chicken-4.9.0/lib/chicken/7/blob-set-int.import.so ...
; loading /usr/local/chicken-4.9.0/lib/chicken/7/message-digest-srfi-4.import.so ...
; loading /usr/local/chicken-4.9.0/lib/chicken/7/message-digest-update-item.import.so ...
; loading /usr/local/chicken-4.9.0/lib/chicken/7/miscmacros.import.so ...
; loading /usr/local/chicken-4.9.0/lib/chicken/7/message-digest-item.import.so ...
; loading /usr/local/chicken-4.9.0/lib/chicken/7/md5.import.so ...
; loading /usr/local/chicken-4.9.0/lib/chicken/7/string-utils.import.so ...
; loading /usr/local/chicken-4.9.0/lib/chicken/7/memoized-string.import.so ...
; loading /usr/local/chicken-4.9.0/lib/chicken/7/lookup-table.import.so ...
; loading /usr/local/chicken-4.9.0/lib/chicken/7/record-variants.import.so ...
; loading /usr/local/chicken-4.9.0/lib/chicken/7/unicode-utils.import.so ...
; loading /usr/local/chicken-4.9.0/lib/chicken/7/sendfile.import.so ...
; loading /usr/local/chicken-4.9.0/lib/chicken/7/rest-bind.import.so ...
; loading /usr/local/chicken-4.9.0/lib/chicken/7/medea.import.so ...
; loading /usr/local/chicken-4.9.0/lib/chicken/7/lazy-seq.import.so ...
; loading /usr/local/chicken-4.9.0/lib/chicken/7/comparse.import.so ...
; loading /usr/local/chicken-4.9.0/lib/chicken/7/latch.import.so ...
; loading /usr/local/chicken-4.9.0/lib/chicken/7/trie.import.so ...
; loading /usr/local/chicken-4.9.0/lib/chicken/7/http-client.so ...
; loading /usr/local/chicken-4.9.0/lib/chicken/7/intarweb.so ...
; loading /usr/local/chicken-4.9.0/lib/chicken/7/base64.so ...
; loading /usr/local/chicken-4.9.0/lib/chicken/7/defstruct.so ...
; loading /usr/local/chicken-4.9.0/lib/chicken/7/uri-common.so ...
; loading /usr/local/chicken-4.9.0/lib/chicken/7/matchable.so ...
; loading /usr/local/chicken-4.9.0/lib/chicken/7/uri-generic.so ...
; loading /usr/local/chicken-4.9.0/lib/chicken/7/message-digest.so ...
; loading /usr/local/chicken-4.9.0/lib/chicken/7/message-digest-primitive.so ...
; loading /usr/local/chicken-4.9.0/lib/chicken/7/type-checks.so ...
; loading /usr/local/chicken-4.9.0/lib/chicken/7/type-errors.so ...
; loading /usr/local/chicken-4.9.0/lib/chicken/7/message-digest-type.so ...
; loading /usr/local/chicken-4.9.0/lib/chicken/7/blob-hexadecimal.so ...
; loading /usr/local/chicken-4.9.0/lib/chicken/7/to-hex.so ...
; loading /usr/local/chicken-4.9.0/lib/chicken/7/string-hexadecimal.so ...
; loading /usr/local/chicken-4.9.0/lib/chicken/7/message-digest-parameters.so ...
; loading /usr/local/chicken-4.9.0/lib/chicken/7/variable-item.so ...
; loading /usr/local/chicken-4.9.0/lib/chicken/7/message-digest-bv.so ...
; loading /usr/local/chicken-4.9.0/lib/chicken/7/message-digest-support.so ...
; loading /usr/local/chicken-4.9.0/lib/chicken/7/message-digest-int.so ...
; loading /usr/local/chicken-4.9.0/lib/chicken/7/blob-set-int.so ...
; loading /usr/local/chicken-4.9.0/lib/chicken/7/message-digest-srfi-4.so ...
; loading /usr/local/chicken-4.9.0/lib/chicken/7/message-digest-update-item.so ...
; loading /usr/local/chicken-4.9.0/lib/chicken/7/message-digest-item.so ...
; loading /usr/local/chicken-4.9.0/lib/chicken/7/md5.so ...
; loading /usr/local/chicken-4.9.0/lib/chicken/7/string-utils.so ...
; loading /usr/local/chicken-4.9.0/lib/chicken/7/memoized-string.so ...
; loading /usr/local/chicken-4.9.0/lib/chicken/7/lookup-table.so ...
; loading /usr/local/chicken-4.9.0/lib/chicken/7/unicode-utils.so ...
; loading /usr/local/chicken-4.9.0/lib/chicken/7/sendfile.so ...
; loading /usr/local/chicken-4.9.0/lib/chicken/7/openssl.import.so ...
; loading /usr/local/chicken-4.9.0/lib/chicken/7/openssl.so ...
; loading /usr/local/chicken-4.9.0/lib/chicken/7/rest-bind.so ...
; loading /usr/local/chicken-4.9.0/lib/chicken/7/medea.so ...
; loading /usr/local/chicken-4.9.0/lib/chicken/7/lazy-seq.so ...
; loading /usr/local/chicken-4.9.0/lib/chicken/7/comparse.so ...
; loading /usr/local/chicken-4.9.0/lib/chicken/7/trie.so ...
#;2> (pp (define-method (account/info (path (lambda (arg) (string-append "hello-" path)))) "https://api.dropbox.com/1/account/info" #f read-json))
(define account/info
  (let* ((writer #f)
         (uri/req "https://api.dropbox.com/1/account/info")
         (uri (cond ((request? uri/req) (request-uri uri/req))
                    ((uri? uri/req) uri/req)
                    (else (uri-reference uri/req))))
         (req (cond ((request? uri/req) uri/req)
                    (else (make-request method: (if writer 'POST 'GET))))))
    (lambda (path)
      (let* ((path ((lambda (arg) (string-append "hello-" path)) path))
             (uri (update-uri
                    uri
                    path:
                    (append (uri-path uri) (map ->string (list path)))))
             (req (update-request req uri: uri)))
        (fprintf (current-error-port) "\n\n\nREQUEST\n")
        (write-request (update-request req port: (current-error-port)))
        (fprintf (current-error-port) "\n")
        (receive
          (reader uri response)
          (call-with-input-request req #f read-json)
          (values reader (list uri response)))))))
#;3> 

expand pasted by andyjpb on Tue Oct 14 20:31:08 2014

#;3> (pp (expand (define-method (account/info (path (lambda (arg) (string-append "hello-" path)))) "https://api.dropbox.com/1/account/info" #f read-json)))
(##core#set!
  account/info
  (let* ((writer #f)
         (uri/req "https://api.dropbox.com/1/account/info")
         (uri (cond ((request? uri/req) (request-uri uri/req))
                    ((uri? uri/req) uri/req)
                    (else (uri-reference uri/req))))
         (req (cond ((request? uri/req) uri/req)
                    (else (make-request method: (if writer 'POST 'GET))))))
    (lambda (path)
      (let* ((path ((lambda (arg) (string-append "hello-" path)) path))
             (uri (update-uri
                    uri
                    path:
                    (append (uri-path uri) (map ->string (list path)))))
             (req (update-request req uri: uri)))
        (fprintf (current-error-port) "\n\n\nREQUEST\n")
        (write-request (update-request req port: (current-error-port)))
        (fprintf (current-error-port) "\n")
        (receive
          (reader uri response)
          (call-with-input-request req #f read-json)
          (values reader (list uri response)))))))

DSSSL-alike added by andyjpb on Tue Oct 14 20:59:40 2014

-a-

(define-method
  (account/info
    (path (cut string-append "hello-" <>))
    pos-arg-2
    #!key
    (key-arg-1 (cut string-append "hi-" <>))
    key-arg-2
    (key-arg-3 "default"))
  "http://example.net/"
  #f
  read-json)

-b-

(define-method
  (account/info
    (path (cut string-append "hello-" <>))
    pos-arg-2
    #!key
    (key-arg-1 (cut string-append "hi-" <>))
    key-arg-2
    key-arg-3)
  "http://example.net/?key-arg-3=default"
  #f
  read-json)