(defpackage #:microcms (:nicknames #:microcms/main) (:use #:cl) (:import-from #:alexandria #:alist-plist #:assoc-value #:remove-from-plist #:symbolicate) (:import-from #:jonathan #:to-json #:parse) (:import-from #:dexador #:request #:http-request-failed) (:import-from #:quri #:make-uri #:render-uri) (:import-from #:kebab #:to-camel-case) (:export #:*api-key* #:*service-domain* #:define-list-client #:define-object-client)) (in-package #:microcms) (defparameter *api-key* nil) (defparameter *service-domain* nil) (defun %get-list (endpoint &optional query) (%request :get endpoint "" query)) (defun %get-list-detail (endpoint id &optional (query nil)) (%request :get endpoint id query)) (defun %create (endpoint content &optional query) (let ((id (getf content :|id|)) (pure-content (remove-from-plist content :|id|))) (if id (%put endpoint id pure-content) (%post endpoint pure-content query)))) (defun %put (endpoint id content &optional query) (%request :put endpoint id query content)) (defun %post (endpoint content &optional query) (%request :post endpoint "" query content)) (defun %update (endpoint id content) (%request :patch endpoint id nil content)) (defun %delete (endpoint id) (%request :delete endpoint id)) (defun %get-object (endpoint) (%request :get endpoint)) (defun %request (method endpoint &optional (path "") (query nil) (body nil)) (let* ((url (%build-uri endpoint path query)) (headers `(("X-MICROCMS-API-KEY" . ,*api-key*) ("Content-Type" . "application/json")))) (format t "API request url: ~a~%" url) (handler-case (multiple-value-bind (resp-body status resp-headers) (request url :method method :headers headers :content (and body (to-json body)) :force-binary nil) (format t "API response status: ~a~%" status) (when (and (stringp resp-body) (search "application/json" (gethash "content-type" resp-headers))) (parse resp-body))) (http-request-failed () '(:|error| "API request failed"))))) (defun %build-uri (endpoint &optional (path "") (query nil)) (let ((uri (make-uri :scheme "https" :host (format nil "~A.microcms.io" *service-domain*) :path (format nil "/api/v1/~A/~A" endpoint path) :query (%build-query query)))) (render-uri uri))) (defun %build-query (query) (loop :for (key val) :on query :by #'cddr :collect (cons (to-camel-case (symbol-name key)) val))) (defmacro define-list-client (endpoint) (let ((str-endpoint (string-downcase (string endpoint)))) `(progn (defun ,(symbolicate 'get- endpoint '-list) (&optional query) (%get-list ,str-endpoint query)) (defun ,(symbolicate 'get- endpoint '-list-detail) (id &optional query) (%get-list-detail ,str-endpoint id query)) (defun ,(symbolicate 'create- endpoint) (content &optional query) (%create ,str-endpoint content query)) (defun ,(symbolicate 'update- endpoint) (id content) (%update ,str-endpoint id content)) (defun ,(symbolicate 'delete- endpoint) (id) (%delete ,str-endpoint id)) nil))) (defmacro define-object-client (endpoint) (let ((str-endpoint (string-downcase (string endpoint)))) `(progn (defun ,(symbolicate 'get- endpoint '-object) () (%get-object ,str-endpoint)) (defun ,(symbolicate 'update- endpoint) (content) (%update ,str-endpoint nil content)) nil)))