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.

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.

  1. copy tutorial-1.cod.edn from public/tutorial to your codex directory
  2. enter protean doc -f tutorial-1.cod.edn
  3. open the index.html file created in silk_templates/site

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.

  1. copy tutorial-2.cod.edn from public/tutorial to your codex directory
  2. enter protean doc -f tutorial-2.cod.edn
  3. open the index.html file created in silk_templates/site

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.

  1. copy tutorial-3.cod.edn from public/tutorial to your codex directory
  2. enter protean doc -f tutorial-3.cod.edn
  3. open the index.html file created in silk_templates/site

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.

  1. run protean-server
  2. run protean services
  3. run protean service-usage -n tutorial-3
  4. 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"
}
      

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.

  1. copy tutorial-5.cod.edn and tutorial-5.sim.edn from public/tutorial to your codex directory
  2. run protean-server
  3. run protean services
  4. run protean service-usage -n tutorial-5
  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"
}
      

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.

  1. copy tutorial-6.cod.edn and tutorial-6.sim.edn from public/tutorial to your codex directory
  2. run protean-server
  3. run protean services
  4. run protean service-usage -n tutorial-6
  5. 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"
}