From 904caeeeb292f56067a545a19a326f77e1cd9ec9 Mon Sep 17 00:00:00 2001 From: Colin Okay Date: Tue, 8 Feb 2022 15:11:02 -0600 Subject: update readme about documentationg enerator; added example --- README.md | 25 ++++++++- example/lazybones-test-docs.md | 69 +++++++++++++++++++++++ example/lazybones-test.lisp | 121 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 212 insertions(+), 3 deletions(-) create mode 100644 example/lazybones-test-docs.md create mode 100644 example/lazybones-test.lisp diff --git a/README.md b/README.md index 4cf2ba0..3476b51 100644 --- a/README.md +++ b/README.md @@ -65,9 +65,6 @@ The following is quick example showing a few things that `lazybones` can do. (lzb:set-canned-response *server* 403 'custom-403 "text/plain" ) (lzb:set-canned-response *server* 500 #p"/path/to/500error.txt" "text/plain" ) - - - ;; PPROVISION-APP makes an app. You can supply an optional name, a symbol. ;; In lieu of a supplied name, the name of the package is used as the app's name. (lzb:provision-app () @@ -169,6 +166,28 @@ The following is quick example showing a few things that `lazybones` can do. ``` +## Generate Documentation + +Generate a markdown file documenting the endpoints of an APP / API like so: + +``` + +;; continuing from above +(in-package :lazybones-test) + +(alexandria:write-string-into-file + (lzb:generate-app-documentation (lzb:app)) + #p"lazybones-test-docs.md" + :if-exists :supersede) + +``` + +Then use your favorite markdown processor to generate HTML or whatever +your preferred documentation distribution format is. See +`example/lazybones-test-docs.md` for an example of the documentation +generator output. + + ## Backends **WARNING** Users can mostly ignore thissection. The API for alternate backends is in diff --git a/example/lazybones-test-docs.md b/example/lazybones-test-docs.md new file mode 100644 index 0000000..e7133f1 --- /dev/null +++ b/example/lazybones-test-docs.md @@ -0,0 +1,69 @@ +# Lazybones Demo App - v0.0.0 + +Just an API that defines some endpoints. These + endpoints aren't meant to accomplish anything. merely to test out + the lazybones HTTP routing framework. + +## Endpoints + +### POST /hello/:who: +*text/plain* + +Route Variables: + +- WHO + +Authorization Required: + +> Request is authorized if it contains the right TESTAPPSESSION +> cookie. Obtain such a cookie by posting to the /login endpoint. + +Post something to hello who + +### GET /hello/:who: +*text/plain* + +Route Variables: + +- WHO + +Just says hello to WHO + +### POST /login +*text/plain* + +Dummy login endpoint for returning a session cookie. Always returns + the "true" and sends a set-cookie header, setting 'testappsession' + to 'coolsessionbro'. + +### GET /person/:person person-by-id: +*application/json* + +Route Variables: + +- PERSON: A Person Instance + +Returns a json representation of the person. + +### POST /search +*text/plain* + +Authorization Required: + +> Randomly decides that the request is authorized + +Echo the search parameters in a nice list, but also has a post-body + +### GET /search +*text/plain* + +Echo the search parameters in a nice list. + +### GET /search/:category to-int: +*text/plain* + +Route Variables: + +- CATEGORY: An Integer + +Echo the search back, but in a specific category \ No newline at end of file diff --git a/example/lazybones-test.lisp b/example/lazybones-test.lisp new file mode 100644 index 0000000..5c7a878 --- /dev/null +++ b/example/lazybones-test.lisp @@ -0,0 +1,121 @@ +(asdf:load-system "lazybones-hunchentoot") + +(defpackage #:lazybones-test + (:use #:cl) + (:local-nicknames (#:lzb #:lazybones)) + (:import-from #:lazybones + #:defendpoint* + #:http-ok + #:http-err)) + +(in-package :lazybones-test) + +;; first make a server and add some custom error responses + +(defvar *server* (lzb:create-server :port 8888)) + +(defun custom-404 () + (format nil "~a wasn't found :(" (lzb:request-path))) ; can use request functiosn + +(defun custom-403 () + "You, in particular, can't do that. :P ") + +(lzb:set-canned-response *server* 404 'custom-404 "text/plain" ) +(lzb:set-canned-response *server* 403 'custom-403 "text/plain" ) +(lzb:set-canned-response *server* 500 #p"/path/to/500error.txt" "text/plain" ) + +;; PPROVISION-APP makes an app. You can supply an optional name, a symbol. +;; In lieu of a supplied name, the name of the package is used as the app's name. +(lzb:provision-app () + :title "Lazybones Demo App" + :version "0.0.0" + :description "Just an API that defines some endpoints. These + endpoints aren't meant to accomplish anything. merely to test out + the lazybones HTTP routing framework." + + :content-type "text/plain" ; default content type of server responses. + :auth 'post-authorizer) ; default authorizor for requests that need it + +(defun post-authorizer () + "Request is authorized if it contains the right TESTAPPSESSION + cookie. Obtain such a cookie by posting to the /login endpoint." + (string-equal "coolsessionbro" (lzb:request-cookie "testappsession"))) + +;; now we install the app to the server +(lzb:install-app *server* (lzb:app)) ; (app) is the default app for this package + +;; DEFENDPOINT* is a macro to define an endpoint and install it into the +;; app whose name is the current package anme. DEFENDPOINT (without the *) +;; allows you to explictly specify the app where the endpoint is installed. + +(defendpoint* :post "/login" () + "Dummy login endpoint for returning a session cookie. Always returns + the \"true\" and sends a set-cookie header, setting 'testappsession' + to 'coolsessionbro'." + (setf (lzb:response-cookie "testappsession") "coolsessionbro") + (http-ok "true")) + +(defendpoint* :get "/hello/:who:" () + "Just says hello to WHO" + (http-ok (format nil "Hello ~a" who))) + +(defendpoint* :post "/hello/:who:" (:auth t) ; use the default authorizor for the app + "Post something to hello who" + (print (lzb:request-header :content-type)) + (let ((body (lzb:request-body))) + (http-ok (format nil "Hello ~a, I got your message ~a" + who body)))) + +(defendpoint* :get "/search" () + "Echo the search parameters in a nice list." + (http-ok (format nil "Query Was:~%~{~a is ~a~%~}~%" + (loop for (x . y) in (lzb:request-parameters) + collect x + collect y)))) + +(defun crapshoot-authorizer () + "Randomly decides that the request is authorized" + (< 5 (random 10))) + +(defendpoint* :post "/search" (:auth 'crapshoot-authorizer) ; use custom authorizer + "Echo the search parameters in a nice list, but also has a post-body" + (http-ok + (with-output-to-string (out) + (format out "Query Was:~%~{~a is ~a~%~}~%" + (loop for (x . y) in (lzb:request-parameters) + collect x + collect y)) + (terpri) + (format out "Decoded Post Body: ~s~%" (lzb:request-body))))) + + +(defun to-int (string) + "An Integer" + (parse-integer string)) + +;; route variables can accept parsers / preformatters +;; these will parse a value and supply it to the argument of the handler. +;; int eh following CATEGORY is an int + +(defendpoint* :get "/search/:category to-int:" () + "Echo the search back, but in a specific category" + (assert (typep category 'integer)) ; just to show you. + (http-ok + (format nil "Searching in ~a with parameters:~%~{~a = ~a~%~}~%" + category + (loop for (x . y) in (lzb:request-parameters) + collect x + collect y)))) + +(defun person-by-id (id) + "A Person Instance" + ;; The real thing might perform some database operation here. If the + ;; operation failed, an error could be signalled, in which case a + ;; 500 response would be sent to the client. + (list :name "Colin" :occupation "Macrologist" :id (parse-integer id))) + +(defendpoint* :get "/person/:person person-by-id:" (:content-type "application/json") + "Returns a json representation of the person." + (http-ok + (jonathan:to-json person))) + -- cgit v1.2.3