Class: Apiculture::App

Inherits:
Object
  • Object
show all
Defined in:
lib/apiculture/app.rb

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeApp

Returns a new instance of App.



86
87
88
89
90
# File 'lib/apiculture/app.rb', line 86

def initialize
  @status = 200
  @content_type = 'text/plain'
  @params = Apiculture::IndifferentHash.new
end

Instance Attribute Details

#envObject (readonly)

Returns the value of attribute env.



83
84
85
# File 'lib/apiculture/app.rb', line 83

def env
  @env
end

#paramsObject (readonly)

Returns the value of attribute params.



84
85
86
# File 'lib/apiculture/app.rb', line 84

def params
  @params
end

#requestObject (readonly)

Returns the value of attribute request.



82
83
84
# File 'lib/apiculture/app.rb', line 82

def request
  @request
end

Class Method Details

.actionsObject



31
32
33
# File 'lib/apiculture/app.rb', line 31

def actions
  @actions || []
end

.call(env) ⇒ Object



72
73
74
75
76
77
78
79
80
# File 'lib/apiculture/app.rb', line 72

def self.call(env)
  app = new
  Rack::Builder.new do
    (@middleware_configurations || []).each do |middleware_args|
      use(*middleware_args)
    end
    run ->(env) { app.call_without_middleware(env) }
  end.to_app.call(env)
end

.define_action(http_method, url_path, **options, &handler_blk) ⇒ Object



35
36
37
38
# File 'lib/apiculture/app.rb', line 35

def define_action(http_method, url_path, **options, &handler_blk)
  @actions ||= []
  @actions << [http_method.to_s.upcase, url_path, options, handler_blk]
end

.delete(url, **options, &handler_blk) ⇒ Object



27
28
29
# File 'lib/apiculture/app.rb', line 27

def delete(url, **options, &handler_blk)
  define_action :delete, url, **options, &handler_blk
end

.get(url, **options, &handler_blk) ⇒ Object



15
16
17
# File 'lib/apiculture/app.rb', line 15

def get(url, **options, &handler_blk)
  define_action :get, url, **options, &handler_blk
end

.middleware_configurationsObject



11
12
13
# File 'lib/apiculture/app.rb', line 11

def middleware_configurations
  @middleware_configurations || []
end

.post(url, **options, &handler_blk) ⇒ Object



19
20
21
# File 'lib/apiculture/app.rb', line 19

def post(url, **options, &handler_blk)
  define_action :post, url, **options, &handler_blk
end

.put(url, **options, &handler_blk) ⇒ Object



23
24
25
# File 'lib/apiculture/app.rb', line 23

def put(url, **options, &handler_blk)
  define_action :put, url, **options, &handler_blk
end

.use(middlreware_factory, middleware_options, &middleware_blk) ⇒ Object



6
7
8
9
# File 'lib/apiculture/app.rb', line 6

def use(middlreware_factory, middleware_options, &middleware_blk)
  @middleware_configurations ||= []
  @middleware_configurations << [middleware_factory, middleware_options, middleware_blk]
end

Instance Method Details

#call_without_middleware(env) ⇒ Object



41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
# File 'lib/apiculture/app.rb', line 41

def call_without_middleware(env)
  @env = env

  # First try to route via actions...
  given_http_method = env.fetch('REQUEST_METHOD')
  given_path = env.fetch('PATH_INFO')
  given_path = '/' + given_path unless given_path.start_with?('/')

  action_list = self.class.actions
  # TODO: I believe Sinatra matches bottom-up, not top-down.
  action_list.reverse.each do | (action_http_method, action_url_path, action_options, action_handler_callable)|
    route_pattern = Mustermann.new(action_url_path)
    if given_http_method == action_http_method && route_params = route_pattern.params(given_path)
      @request = Rack::Request.new(env)
      @params.merge!(@request.params)
      @route_params = route_params

      match = route_pattern.match(given_path)
      @route_params['captures'] = match.captures unless match.nil?
      @params.merge!(@route_params)
      return perform_action_block(&action_handler_callable)
    end
  end

  # and if nothing works out - respond with a 404
  out = JSON.pretty_generate({
    error: 'No matching action found for %s %s' % [given_http_method, given_path],
  })
  [404, {'Content-Type' => 'application/json', 'Content-Length' => out.bytesize.to_s}, [out]]
end

#content_type(new_type) ⇒ Object



92
93
94
# File 'lib/apiculture/app.rb', line 92

def content_type(new_type)
  @content_type = Rack::Mime.mime_type('.%s' % new_type)
end

#halt(rack_status, rack_headers, rack_body) ⇒ Object



100
101
102
# File 'lib/apiculture/app.rb', line 100

def halt(rack_status, rack_headers, rack_body)
  throw :halt, [rack_status, rack_headers, rack_body]
end

#perform_action_block(&blk) ⇒ Object



104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
# File 'lib/apiculture/app.rb', line 104

def perform_action_block(&blk)
  # Executes the action in a Sinatra-like fashion - passing the route parameter values as
  # arguments to the given block/callable. This is where in the future we should ditch
  # the Sinatra calling conventions - Sinatra mandates that the action accept the route parameters
  # as arguments and grab all the useful stuff from instance methods like `params` etc. whereas
  # we probably want to have just Rack apps mounted per route (under an action)
  response = catch(:halt) do
    body_string_or_rack_triplet = instance_exec(*@route_params.values, &blk)

    if rack_triplet?(body_string_or_rack_triplet)
      return body_string_or_rack_triplet
    end

   [@status, {'Content-Type' => @content_type}, [body_string_or_rack_triplet]]
  end

  return response
end

#rack_triplet?(maybe_triplet) ⇒ Boolean

Returns:

  • (Boolean)


123
124
125
126
127
128
129
130
# File 'lib/apiculture/app.rb', line 123

def rack_triplet?(maybe_triplet)
  maybe_triplet.is_a?(Array) &&
  maybe_triplet.length == 3 &&
  maybe_triplet[0].is_a?(Integer) &&
  maybe_triplet[1].is_a?(Hash) &&
  maybe_triplet[1].keys.all? {|k| k.is_a?(String) } &&
  maybe_triplet[2].respond_to?(:each)
end

#status(status_code) ⇒ Object



96
97
98
# File 'lib/apiculture/app.rb', line 96

def status(status_code)
  @status = status_code.to_i
end