Overview
A progressive tutorial
This is a progressive tutorial designed to gradually demonstrate the main features of Protean. It is aimed to be fun so each part will follow on from the previous, the aim being to build an online text adventure game driven by a RESTful API for exploring it, and building it.
Part 1 - Encoding
The basics
Protean encodes RESTful API's using a codex. A codex is pretty much just some
Clojure code in a file with an extension cod.edn
. The codex is the
single point of truth. Nothing leaks from Protean into your source code... no
annotations, no pollution of any kind.
{ :title "Tutorial 1" :doc "Demonstrates the simplest possibe codex structure" "tutorial-1" {"play" {:get {:rsp {:204 {}}}}} }
Explanation
This is just about the simplest codex. Out of this we get :
- an endpoint
tutorial-1/play
- a
get
method - a nice title for the api docs
Run it
We will start with creating the apidocs for the service defined in this tutorial.
-
copy
tutorial-1.cod.edn
frompublic/tutorial
to your codex directory -
enter
protean doc -f tutorial-1.cod.edn
-
open the
index.html
file created insilk_templates/site
Part 2 - Encoding
Documentation and types
{ :includes ["defaults.edn"] :title "Tutorial 2" "tutorial-2" { "play/${stateId}" { :get { :doc " A single player REST adventure world A simple text adventure world for one player. Sample usage may be something like `/tutorial-2/play/cave`, indicating the player is in a cave. " :vars { "stateId" {:type :Int :doc "ID for the state of the game"} } :rsp { :204 {} :404 {} } } } } }
Explanation
This adds a few things to the previous codex definition :
- a path parameter
stateId
- a simple doc string
- some free preferences for response status codes and types in 'defaults.edn'
- some variable information defining the type of the path parameter
- a 404 (Not Found) error response
Run it
Create your apidocs again.
-
copy
tutorial-2.cod.edn
frompublic/tutorial
to your codex directory -
enter
protean doc -f tutorial-2.cod.edn
-
open the
index.html
file created insilk_templates/site
Part 3 - Encoding
Response bodies
{ :includes ["defaults.edn"] :title "Tutorial 3" "tutorial-3" { "play/${stateId}" { :get { :doc " A single player REST adventure world A simple text adventure world for one player. Sample usage may be something like `/tutorial-3/play/cave`, indicating the player is in a cave. " :vars { "stateId" {:type :Int :doc "ID for the state of the game"} } :rsp { :200 { :body-examples ["public/tutorial/3/200-ref.json"] } :503 { :headers {"Content-Type" "application/problem+json" "Content-Language" "en"} :body-examples ["public/tutorial/3/lazy-server-gremlins.json"] } } } } } }
Explanation
We now add some new concepts to the previous codex definition :
- a response body for a 200 (OK) response
- swap the 404 response for a 503 and add a response header
Where it is possible for a response to have a body it is good practice to include a reference example in the codex. It is entirely possible that an error response, like the 503 defined in this example could have a completely different body
Run it
Create your apidocs again.
-
copy
tutorial-3.cod.edn
frompublic/tutorial
to your codex directory -
enter
protean doc -f tutorial-3.cod.edn
-
open the
index.html
file created insilk_templates/site
Part 4 - Simulation
The basics
Explanation
You do not need to do anything to get a basic simulation for free. It has always been available.
Run it
Run the simulation, using the tutorial-3 codex which you have already copied into your codex directory.
-
run
protean-server
-
run
protean services
-
run
protean service-usage -n tutorial-3
- select the curl statement generated as output of the previous step and execute it
You should see output like :
{ "description": "You are in a cave, it is very dark" }
Part 5 - Simulation
Simulating errors
To override the default simulation behaviour (which returns success responses) create a sim extension file. We will re-use the codex from tutorial-3 (renaming it to tutorial-5) and place the new sim extension in the same location.
The renamed codex is listed below.
{ :includes ["defaults.edn"] :title "Tutorial 5" "tutorial-5" { "play/${stateId}" { :get { :doc " A single player REST adventure world A simple text adventure world for one player. Sample usage may be something like `/tutorial-5/play/cave`, indicating the player is in a cave. " :vars { "stateId" {:type :Int :doc "ID for the state of the game"} } :rsp { :200 { :body-examples ["public/tutorial/3/200-ref.json"] } :503 { :headers {"Content-Type" "application/problem+json" "Content-Language" "en"} :body-examples ["public/tutorial/3/lazy-server-gremlins.json"] } } } } } }
Now we list our first sim extension.
(refer 'protean.api.transformation.sim) { "tutorial-5" { "play/${stateId}" { :get [#(error)] } } }
Explanation
In our first sim extension example we override the get method to return a random error status code, which in this case must be the 503 we defined in the codex. As you can see the general structure of the sim matches that of the codex.
Run it
Run the simulation.
-
copy
tutorial-5.cod.edn
andtutorial-5.sim.edn
frompublic/tutorial
to your codex directory -
run
protean-server
-
run
protean services
-
run
protean service-usage -n tutorial-5
- select the curl statement generated as output of the previous step and execute it
You should see output like :
{ "type": "http://proteanic.org/api/problems/examples/lazy-server-gremlins", "title": "The service is unavailable - lazy server gremlins.", "detail": "You have asked for something, but the service is unavailable, the server gremlins cannot be bothered to do any work right now.", "instance": "http://host.port/tutorial-3/play" }
Part 6 - Simulation
Simulating dynamic responses
{ :includes ["defaults.edn"] :title "Tutorial 6" "tutorial-6" { "play/${stateId}" { :types { :StateId "(cave|forest)" } :get { :doc " A single player REST adventure world A simple text adventure world for one player. Sample usage may be something like `/tutorial-6/play/cave`, indicating the player is in a cave. " :vars { "stateId" {:type :StateId :doc "ID for the state of the game"} } :rsp { :200 { :body-examples ["public/tutorial/3/200-ref.json"] } :404 {} } } } } }
Now the sim extension.
(refer 'protean.api.transformation.sim) (defn param2rsp [data-path] (if-let [rsp (rsp-body-file data-path (path-param "stateId") ".json")] (slurp rsp) (respond 404))) { "tutorial-6" { "play/${stateId}" { :get [#(param2rsp "public/tutorial/6")] } } }
Explanation
In the codex above we have converged on a slightly more realistic example. Our endpoint includes a path parameter, and we are now provisioning for cases where the resource requested does not exist.
We have created a custom type for our path parameter, indicating that its value can be either 'cave' or 'forest'.
In our sim extension we now look up a response body based on the value of the path parameter. We provide two nonsense responses in json files.
Run it
Run the simulation.
-
copy
tutorial-6.cod.edn
andtutorial-6.sim.edn
frompublic/tutorial
to your codex directory -
run
protean-server
-
run
protean services
-
run
protean service-usage -n tutorial-6
- select the curl statement generated as output of the previous step, ensure the path parameter is either cave or forest and execute it
You should see output like :
{ "description": "You are in a cave, it is very dark" }