From 9dd635d63e2626badcca0e244dc553b43319e5da Mon Sep 17 00:00:00 2001
From: Paul Duncan <pabs@pablotron.org>
Date: Sun, 6 Mar 2016 01:14:10 -0500
Subject: populate api handler

---
 src/guff/api-handler.cr | 267 +++++++++++++++++++++++++++++++++++-------------
 1 file changed, 197 insertions(+), 70 deletions(-)

diff --git a/src/guff/api-handler.cr b/src/guff/api-handler.cr
index 7928d3c..3192447 100644
--- a/src/guff/api-handler.cr
+++ b/src/guff/api-handler.cr
@@ -1,80 +1,164 @@
+require "json"
 require "./handler"
 
+private macro define_method_calls(hash)
+  case namespace
+  {% for namespace, methods in hash %}
+    when "{{ namespace.id }}"
+      case method
+      {% for method in methods %}
+        when "{{ method }}"
+          do_{{ namespace.id }}_{{ method }}(context, get_method_args(
+            context.request.query_params,
+            "{{ namespace.id }}",
+            "{{ method.id }}"
+          ))
+      {% end %}
+      else
+        raise "unknown method"
+      end
+  {% end %}
+  else
+    raise "unknown namespace"
+  end
+end
+
 module Guff
-  record APIContext,
-    context : HTTP::Server::Context,
-    model : Model
-
-  API = {
-    "posts": {
-      "get_posts": {
-        text: "Get posts matching query.",
-
-        args: {
-          "q": {
-            text:     "Search string.",
-            type:     :text,
-            required: false,
-            default:  "",
+  class APIHandler < Handler
+    API = {
+      "post": {
+        "get_posts": {
+          text: "Get posts matching query.",
+
+          args: {
+            "q": {
+              text:     "Search string.",
+              type:     :text,
+              required: false,
+              default:  "",
+            },
+
+            "page": {
+              text:     "Page number",
+              type:     :int,
+              required: false,
+              default:  "1",
+            },
+
+            "tags": {
+              text:     "Comma-separated list of tags (union)",
+              type:     :tags,
+              required: false,
+              default:  "1",
+            },
+
+            "sort": {
+              text:     "Sort order of results",
+              type:     :sort,
+              required: false,
+              default:  "date,desc",
+            },
           },
+        },
+      },
 
-          "page": {
-            text:     "Page number",
-            type:     :int,
-            required: false,
-            default:  "1",
-          },
+      "test": {
+        "version": {
+          text: "Get version",
+        },
 
-          "tags": {
-            text:     "Comma-separated list of tags (union)",
-            type:     :tags,
-            required: false,
-            default:  "1",
-          },
+        "get_posts": {
+          text: "Test get posts",
+        },
 
-          "sort": {
-            text:     "Sort order of results",
-            type:     :sort,
-            required: false,
-            default:  "date,desc",
-          },
+        "error": {
+          text: "Test error response",
         },
+      }
+    }
 
-        method: ->(c : APIContext) {
-          "get_posts"
-        }
-      },
-    },
-  }
+    TYPE_CHECKS = {
+      text: /.*/,
+      int:  /^\d+$/,
+      tags: /^[a-z0-9_,-]+$/,
+      sort: /^[a-z0-9_]+,(?:asc|desc)$/,
+    }
 
-  API_PATH_RE = %r{
-    ^/api
+    PATH_RE = %r{
+      ^/api
 
-    (?:
-      # method call
       (?:
-        /
-        (?<namespace>[a-z0-9_-]+)
-        /
-        (?<method>[a-z0-9]+)
+        # method call
+        (?:
+          /
+          (?<namespace>[a-z0-9_-]+)
+          /
+          (?<method>[a-z0-9_]+)
+        )
+
+        |
+
+        # index.html
+        /(?:index(?:\.html|)|)
+
+        |
+
+        # implicit index (no trailing slash)
       )
 
-      |
+      $
+    }mx
+
+    CONTENT_TYPES = {
+      "development":  "text/html; charset=utf-8",
+      "production":   "application/json; charset=utf8",
+    }
 
-      # index.html
-      /(?:index(?:\.html|)|)
-      
-      |
+    private def get_content_type
+      CONTENT_TYPES[@model.config["environment"]]
+    end
 
-      # implicit index (no trailing slash)
+    private def get_method_args(
+      params    : HTTP::Params,
+      namespace : String,
+      method    : String
     )
+      return {} of String => String unless (
+        API[namespace]? &&
+        API[namespace][method] &&
+        API[namespace][method][:args]?
+      )
 
-    $
-  }mx
+      # get method args
+      args = API[namespace][method][:args] as \
+        Hash(String, Hash(Symbol, String | Symbol | Bool))
+
+      args.keys.reduce({} of String => String) do |r, arg_name|
+        arg_data = args[arg_name] as Hash(Symbol, String|Symbol|Bool)
+
+        # check for required parameter
+        if arg_data[:required] && !params.has_key?(arg_name)
+          raise "missing required parameter: %s" % [arg_name]
+        end
+
+        # get value
+        val = params.fetch(arg_name, arg_data[:default] as String)
+
+        # check value
+        if !TYPE_CHECKS[arg_data[:type]].match(val)
+          raise "invalid parameter format: %s" % [arg_name]
+        end
+
+        # add value to result
+        r[arg_name] = val
+
+        # return result
+        r
+      end
+    end
 
-  class APIHandler < Handler
     def call(context : HTTP::Server::Context)
-      if md = (context.request.path || "").match(API_PATH_RE)
+      if md = (context.request.path || "").match(PATH_RE)
         if md["namespace"]?
           # method call
           do_call(context, md["namespace"], md["method"])
@@ -92,21 +176,28 @@ module Guff
       namespace : String,
       method    : String
     )
+      context.response.content_type = get_content_type
+
       # method call
-# 
-#       fn = API[namespace][method][:method] as Proc(APIContext, String)
-#       context.response.puts fn.call(APIContext.new(context, @model))
-# 
-      page = HTMLPageView.new(
-        "TODO: API Call",
-        "<p>API Call: namespace = %s, call = %s</p>" % [
-          namespace,
-          method
-        ]
-      )
+      json = begin
+        # equivalent to send("do_#{namespace}_#{method}".intern, context)
+        define_method_calls({
+          post: [
+            get_posts,
+          ],
+
+          test: [
+            version,
+            get_posts,
+            error,
+          ],
+        })
+      rescue e
+        { "error": e.to_s }.to_json
+      end
 
-      context.response.content_type = page.content_type
-      context.response.puts page
+      # send body
+      context.response.puts json
     end
 
     private def do_docs(context : HTTP::Server::Context)
@@ -118,5 +209,41 @@ module Guff
       context.response.content_type = page.content_type
       context.response.puts page
     end
+
+    ################
+    # post methods #
+    ################
+
+    private def do_post_get_posts(
+      context : HTTP::Server::Context,
+      args    : Hash(String, String)
+    )
+      [{foo: "bar"}, {foo: "asdf"}].to_json
+    end
+
+    ################
+    # test methods #
+    ################
+
+    private def do_test_version(
+      context : HTTP::Server::Context,
+      args    : Hash(String, String)
+    )
+      {version: Guff::VERSION}.to_json
+    end
+
+    private def do_test_get_posts(
+      context : HTTP::Server::Context,
+      args    : Hash(String, String)
+    )
+      [{foo: "bar"}, {foo: "asdf"}].to_json
+    end
+
+    private def do_test_error(
+      context : HTTP::Server::Context,
+      args    : Hash(String, String)
+    )
+      raise "some random error"
+    end
   end
 end
-- 
cgit v1.2.3