diff options
-rw-r--r-- | src/guff/api-handler.cr | 267 |
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 |