~subsetpark/whist

8c95adf66c13fc122783949f832936415027304b — Zach Smith 6 months ago 3a46cf9
Integrate main into lit
2 files changed, 107 insertions(+), 1 deletions(-)

M main.janet
M whist.lit
M main.janet => main.janet +8 -1
@@ 1,3 1,4 @@
## main.janet
(import json)
(import init)
(import whist)


@@ 8,10 9,12 @@
(route :get "/api/v1/whist/init" :init)
(route :post "/api/v1/whist/next" :next)

## Config Handler
(defn config [request]
  (def resp (init/config))
  (application/json resp))

## Init Handler
(defn init [request]
  (let [players (get-in request [:query-string :players])
	[p1 p2 p3 p4] (string/split "," players)


@@ 19,7 22,8 @@
  
    (application/json resp)))

(def params
## Next Handler
(def- params
  (params :next
    (validates [:action] :required true)
    (permit [:action :state :players])))


@@ 34,6 38,9 @@
    [state events]
    (application/json {:state state :events events})))


(def app (app {:layout layout :csrf-token false}))

(defn main [& args]
  (server app 9001))


M whist.lit => whist.lit +99 -0
@@ 1169,4 1169,103 @@ player IDs in the game and returns an object with `players` and
@{Init}
---

# The JSON API

In the Tamerlane application, game rules engines are installed by
specifying a **callback URL** for the server to hit. Each call that
the server makes to the rules engine takes the form of an
JSON-formatted HTTP request, and expects a JSON-formatted response.

**[NOTE]**: The protocol around specific status codes and port numbers
is still very much in development.

In Janet, the standard web framework is [Joy][]; we'll define an
extremely simple API using that library.

--- main.janet
(import json)
(import init)
(import whist)

(use joy)

(route :get "/api/v1/whist/config" :config)
(route :get "/api/v1/whist/init" :init)
(route :post "/api/v1/whist/next" :next)

@{Config Handler}
@{Init Handler}
@{Next Handler}

(def app (app {:layout layout :csrf-token false}))

(defn main [& args]
  (server app 9001))
---

@s /api/v1/whist/config

Our config endpoint needs to do very little with the request, as there
are no arguments provided by the Tamerlane server.

--- Config Handler
(defn config [request]
  (def resp (init/config))
  (application/json resp))
---

@s /api/v1/whist/init

Our init endpoint is only slightly more complex; the Tamerlane server
has a single argument to `/init`, which is a list of players. Those
are sent under the query parameter `players` as a comma-separated
string. For instance, a sample call might be

--- Sample API Call
GET http://localhost:9001/api/v1/whist/init?players=North,East,South,West
---

We can handle it simply by splitting on the commas.

--- Init Handler
(defn init [request]
  (let [players (get-in request [:query-string :players])
	[p1 p2 p3 p4] (string/split "," players)
	resp (init/init p1 p2 p3 p4)]
  
    (application/json resp)))
---

@s /api/v1/whist/next

Finally, we need to expose the `next` business logic itself. 

We'd like to be able to do some basic input validation and to return
an error response if we get bad input (if only to make it easier to
develop our application), so we have some very simple parameter
validation associated with this endpoint.

The output of our `next` function is always a two-tuple of state and
events. In fact, the expected response format in the API is an object
with a `state` attribute and an `events` attribute, so we need to
match on the two-tuple that comes from our business logic and return
the response that the Tamerlane server expects.

--- Next Handler
(def- params
  (params :next
    (validates [:action] :required true)
    (permit [:action :state :players])))

(defn next [request]
  (def handler (fiber/new (fn [req]
			    (whist/next (params req))) :e))
  (match (resume handler request)
    {:error error-msg} @{:status 422
			 :body (json/encode {:error error-msg})
			 :headers @{"Content-Type" "application/json"}}
    [state events]
    (application/json {:state state :events events})))
---

@include lit/events.lit
\ No newline at end of file