aboutsummaryrefslogtreecommitdiff
path: root/src/guff/api-handler.cr
diff options
context:
space:
mode:
Diffstat (limited to 'src/guff/api-handler.cr')
-rw-r--r--src/guff/api-handler.cr267
1 files 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