haveapi 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/lib/haveapi/action.rb +521 -0
- data/lib/haveapi/actions/default.rb +55 -0
- data/lib/haveapi/actions/paginable.rb +12 -0
- data/lib/haveapi/api.rb +66 -0
- data/lib/haveapi/authentication/base.rb +37 -0
- data/lib/haveapi/authentication/basic/provider.rb +37 -0
- data/lib/haveapi/authentication/chain.rb +110 -0
- data/lib/haveapi/authentication/token/provider.rb +166 -0
- data/lib/haveapi/authentication/token/resources.rb +107 -0
- data/lib/haveapi/authorization.rb +108 -0
- data/lib/haveapi/common.rb +38 -0
- data/lib/haveapi/context.rb +78 -0
- data/lib/haveapi/example.rb +36 -0
- data/lib/haveapi/extensions/action_exceptions.rb +25 -0
- data/lib/haveapi/extensions/base.rb +9 -0
- data/lib/haveapi/extensions/resource_prefetch.rb +7 -0
- data/lib/haveapi/hooks.rb +190 -0
- data/lib/haveapi/metadata.rb +56 -0
- data/lib/haveapi/model_adapter.rb +119 -0
- data/lib/haveapi/model_adapters/active_record.rb +352 -0
- data/lib/haveapi/model_adapters/hash.rb +27 -0
- data/lib/haveapi/output_formatter.rb +57 -0
- data/lib/haveapi/output_formatters/base.rb +29 -0
- data/lib/haveapi/output_formatters/json.rb +9 -0
- data/lib/haveapi/params/param.rb +114 -0
- data/lib/haveapi/params/resource.rb +109 -0
- data/lib/haveapi/params.rb +314 -0
- data/lib/haveapi/public/css/bootstrap-theme.min.css +7 -0
- data/lib/haveapi/public/css/bootstrap.min.css +7 -0
- data/lib/haveapi/public/js/bootstrap.min.js +6 -0
- data/lib/haveapi/public/js/jquery-1.11.1.min.js +4 -0
- data/lib/haveapi/resource.rb +120 -0
- data/lib/haveapi/route.rb +22 -0
- data/lib/haveapi/server.rb +440 -0
- data/lib/haveapi/spec/helpers.rb +103 -0
- data/lib/haveapi/types.rb +24 -0
- data/lib/haveapi/version.rb +3 -0
- data/lib/haveapi/views/doc_layout.erb +27 -0
- data/lib/haveapi/views/doc_sidebars/create-client.erb +20 -0
- data/lib/haveapi/views/doc_sidebars/protocol.erb +42 -0
- data/lib/haveapi/views/index.erb +12 -0
- data/lib/haveapi/views/main_layout.erb +50 -0
- data/lib/haveapi/views/version_page.erb +195 -0
- data/lib/haveapi/views/version_sidebar.erb +42 -0
- data/lib/haveapi.rb +22 -0
- metadata +242 -0
@@ -0,0 +1,190 @@
|
|
1
|
+
module HaveAPI
|
2
|
+
# All registered hooks and connected endpoints are stored
|
3
|
+
# in this module.
|
4
|
+
#
|
5
|
+
# It supports connecting to both class and instance level hooks.
|
6
|
+
# Instance level hooks inherit all class registered hooks, but
|
7
|
+
# it is possible to connect to a specific instance and not for
|
8
|
+
# all instances of a class.
|
9
|
+
#
|
10
|
+
# === \Usage
|
11
|
+
# ==== \Register hooks
|
12
|
+
# class MyClass
|
13
|
+
# include Hookable
|
14
|
+
#
|
15
|
+
# has_hook :myhook
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
# ==== \Class level hooks
|
19
|
+
# # Connect hook
|
20
|
+
# MyClass.connect_hook(:myhook) do |ret, a, b, c|
|
21
|
+
# # a = 1, b = 2, c = 3
|
22
|
+
# puts "Class hook!"
|
23
|
+
# ret
|
24
|
+
# end
|
25
|
+
#
|
26
|
+
# # Call hooks
|
27
|
+
# MyClass.call_hooks(:myhook, args: [1, 2, 3])
|
28
|
+
#
|
29
|
+
# ==== \Instance level hooks
|
30
|
+
# # Create an instance of MyClass
|
31
|
+
# my = MyClass.new
|
32
|
+
#
|
33
|
+
# # Connect hook
|
34
|
+
# my.connect_hook(:myhook) do |ret, a, b, c|
|
35
|
+
# # a = 1, b = 2, c = 3
|
36
|
+
# puts "Instance hook!"
|
37
|
+
# ret
|
38
|
+
# end
|
39
|
+
#
|
40
|
+
# # Call instance hooks
|
41
|
+
# my.call_instance_hooks_for(:myhook, args: [1, 2, 3])
|
42
|
+
# # Call class hooks
|
43
|
+
# my.call_class_hooks_for(:myhook, args: [1, 2, 3])
|
44
|
+
# # Call both instance and class hooks at once
|
45
|
+
# my.call_hooks_for(:myhook, args: [1, 2, 3])
|
46
|
+
module Hooks
|
47
|
+
# Register a hook defined by +klass+ with +name+.
|
48
|
+
# +klass+ is an instance of Class, that is class name, not it's instance.
|
49
|
+
def self.register_hook(klass, name)
|
50
|
+
classified = hook_classify(klass)
|
51
|
+
|
52
|
+
@hooks ||= {}
|
53
|
+
@hooks[classified] ||= {}
|
54
|
+
@hooks[classified][name] = []
|
55
|
+
end
|
56
|
+
|
57
|
+
# Connect class hook defined in +klass+ with +name+ to +block+.
|
58
|
+
# +klass+ is a class name.
|
59
|
+
def self.connect_hook(klass, name, &block)
|
60
|
+
@hooks[hook_classify(klass)][name] << block
|
61
|
+
end
|
62
|
+
|
63
|
+
# Connect instance hook from instance +klass+ with +name+ to +block+.
|
64
|
+
def self.connect_instance_hook(klass, name, &block)
|
65
|
+
unless @hooks[klass]
|
66
|
+
@hooks[klass] = {}
|
67
|
+
|
68
|
+
@hooks[klass.class].each do |k, v|
|
69
|
+
@hooks[klass][k] = []
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
@hooks[klass][name] << block
|
74
|
+
end
|
75
|
+
|
76
|
+
# Call all blocks that are connected to hook in +klass+ with +name+.
|
77
|
+
# +klass+ may be a class name or an object instance.
|
78
|
+
# If +where+ is set, the blocks are executed in it with instance_exec.
|
79
|
+
# +args+ is an array of arguments given to all blocks. The first argument
|
80
|
+
# to all block is always a return value from previous block or +initial+,
|
81
|
+
# which defaults to an empty hash.
|
82
|
+
#
|
83
|
+
# Blocks are executed one by one in the order they were connected.
|
84
|
+
# Blocks must return a hash, that is then passed to the next block
|
85
|
+
# and the return value from the last block is returned to the caller.
|
86
|
+
#
|
87
|
+
# A block may decide that no further blocks should be executed.
|
88
|
+
# In such a case it calls Hooks.stop with the return value. It is then
|
89
|
+
# returned to the caller immediately.
|
90
|
+
def self.call_for(klass, name, where = nil, args: [], initial: {})
|
91
|
+
classified = hook_classify(klass)
|
92
|
+
|
93
|
+
catch(:stop) do
|
94
|
+
return initial unless @hooks[classified]
|
95
|
+
hooks = @hooks[classified][name]
|
96
|
+
return initial unless hooks
|
97
|
+
|
98
|
+
hooks.each do |hook|
|
99
|
+
if where
|
100
|
+
ret = where.instance_exec(initial, *args, &hook)
|
101
|
+
else
|
102
|
+
ret = hook.call(initial, *args)
|
103
|
+
end
|
104
|
+
|
105
|
+
initial.update(ret) if ret
|
106
|
+
end
|
107
|
+
|
108
|
+
initial
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def self.hook_classify(klass)
|
113
|
+
klass.is_a?(String) ? Object.const_get(klass) : klass
|
114
|
+
end
|
115
|
+
|
116
|
+
def self.stop(ret)
|
117
|
+
throw(ret)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
# Classes that define hooks must include this module.
|
122
|
+
module Hookable
|
123
|
+
module ClassMethods
|
124
|
+
# Register a hook named +name+.
|
125
|
+
def has_hook(name)
|
126
|
+
Hooks.register_hook(self.to_s, name)
|
127
|
+
end
|
128
|
+
|
129
|
+
# Connect +block+ to registered hook with +name+.
|
130
|
+
def connect_hook(name, &block)
|
131
|
+
Hooks.connect_hook(self.to_s, name, &block)
|
132
|
+
end
|
133
|
+
|
134
|
+
# Call all hooks for +name+. see Hooks.call_for.
|
135
|
+
def call_hooks(*args)
|
136
|
+
Hooks.call_for(self.to_s, *args)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
module InstanceMethods
|
141
|
+
# Call all instance and class hooks.
|
142
|
+
def call_hooks_for(*args)
|
143
|
+
ret = call_instance_hooks_for(*args)
|
144
|
+
|
145
|
+
if args.last.is_a?(::Hash)
|
146
|
+
args.last.update(initial: ret)
|
147
|
+
call_class_hooks_for(*args)
|
148
|
+
else
|
149
|
+
call_class_hooks_for(*args, initial: ret)
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
# Call only instance hooks.
|
154
|
+
def call_instance_hooks_for(name, where = nil, args: [], initial: {})
|
155
|
+
Hooks.call_for(self, name, where, args: args, initial: initial)
|
156
|
+
end
|
157
|
+
|
158
|
+
# Call only class hooks.
|
159
|
+
def call_class_hooks_for(name, where = nil, args: [], initial: {})
|
160
|
+
Hooks.call_for(self.class, name, where, args: args, initial: initial)
|
161
|
+
end
|
162
|
+
|
163
|
+
# Call hooks for different +klass+.
|
164
|
+
def call_hooks_as_for(klass, *args)
|
165
|
+
ret = call_instance_hooks_as_for(klass, *args)
|
166
|
+
call_class_hooks_as_for(klass.class, *args, initial: ret)
|
167
|
+
end
|
168
|
+
|
169
|
+
# Call only instance hooks for different +klass+.
|
170
|
+
def call_instance_hooks_as_for(klass, *args)
|
171
|
+
Hooks.call_for(klass, *args)
|
172
|
+
end
|
173
|
+
|
174
|
+
# Call only class hooks for different +klass+.
|
175
|
+
def call_class_hooks_as_for(klass, *args)
|
176
|
+
Hooks.call_for(klass, *args)
|
177
|
+
end
|
178
|
+
|
179
|
+
# Connect instance level hook +name+ to +block+.
|
180
|
+
def connect_hook(name, &block)
|
181
|
+
Hooks.connect_instance_hook(self, name, &block)
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
def self.included(base)
|
186
|
+
base.send(:extend, ClassMethods)
|
187
|
+
base.send(:include, InstanceMethods)
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module HaveAPI
|
2
|
+
class Metadata
|
3
|
+
def self.namespace
|
4
|
+
:_meta
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.describe
|
8
|
+
{
|
9
|
+
namespace: namespace
|
10
|
+
}
|
11
|
+
end
|
12
|
+
|
13
|
+
class ActionMetadata
|
14
|
+
attr_writer :action
|
15
|
+
|
16
|
+
def clone
|
17
|
+
m = self.class.new
|
18
|
+
m.action = @action
|
19
|
+
m.instance_variable_set(:@input, @input && @input.clone)
|
20
|
+
m.instance_variable_set(:@output, @output && @output.clone)
|
21
|
+
m
|
22
|
+
end
|
23
|
+
|
24
|
+
def input(layout = :hash, &block)
|
25
|
+
if block
|
26
|
+
@input ||= Params.new(:input, @action)
|
27
|
+
@input.action = @action
|
28
|
+
@input.layout = layout
|
29
|
+
@input.namespace = false
|
30
|
+
@input.add_block(block)
|
31
|
+
else
|
32
|
+
@input
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def output(layout = :hash, &block)
|
37
|
+
if block
|
38
|
+
@output ||= Params.new(:output, @action)
|
39
|
+
@output.action = @action
|
40
|
+
@output.layout = layout
|
41
|
+
@output.namespace = false
|
42
|
+
@output.add_block(block)
|
43
|
+
else
|
44
|
+
@output
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def describe(context)
|
49
|
+
{
|
50
|
+
input: @input && @input.describe(context),
|
51
|
+
output: @output && @output.describe(context)
|
52
|
+
}
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,119 @@
|
|
1
|
+
module HaveAPI
|
2
|
+
# Model adapters are used to automate handling of action
|
3
|
+
# input/output.
|
4
|
+
#
|
5
|
+
# Adapters are chosen based on the +model+ set on a HaveAPI::Resource.
|
6
|
+
# If no +model+ is specified, ModelAdapters::Hash is used as a default
|
7
|
+
# adapter.
|
8
|
+
#
|
9
|
+
# All model adapters are based on this class.
|
10
|
+
class ModelAdapter
|
11
|
+
class << self
|
12
|
+
attr_accessor :adapters
|
13
|
+
|
14
|
+
# Every model adapter must register itself using this method.
|
15
|
+
def register
|
16
|
+
ModelAdapter.adapters ||= []
|
17
|
+
ModelAdapter.adapters << Kernel.const_get(self.to_s)
|
18
|
+
end
|
19
|
+
|
20
|
+
# Returns an adapter suitable for +layout+ and +obj+.
|
21
|
+
# Adapters are iterated over and the first to return true to handle?()
|
22
|
+
# is returned.
|
23
|
+
def for(layout, obj)
|
24
|
+
return ModelAdapters::Hash if !obj || %i(hash hash_list).include?(layout)
|
25
|
+
adapter = @adapters.detect { |adapter| adapter.handle?(layout, obj) }
|
26
|
+
adapter || ModelAdapters::Hash
|
27
|
+
end
|
28
|
+
|
29
|
+
# Shortcut to Input::clean.
|
30
|
+
def input_clean(*args)
|
31
|
+
self::Input.clean(*args)
|
32
|
+
end
|
33
|
+
|
34
|
+
# Shortcut to get an instance of Input model adapter.
|
35
|
+
def input(*args)
|
36
|
+
self::Input.new(*args)
|
37
|
+
end
|
38
|
+
|
39
|
+
# Shortcut to get an instance of Output model adapter.
|
40
|
+
def output(*args)
|
41
|
+
self::Output.new(*args)
|
42
|
+
end
|
43
|
+
|
44
|
+
# Override this method to load validators from +model+
|
45
|
+
# to +params+.
|
46
|
+
def load_validators(model, params)
|
47
|
+
|
48
|
+
end
|
49
|
+
|
50
|
+
# Called when mounting the API. Model adapters may use this method
|
51
|
+
# to add custom meta parameters to +action+. +direction+ is one of
|
52
|
+
# +:input+ and +:output+.
|
53
|
+
def used_by(direction, action)
|
54
|
+
case direction
|
55
|
+
when :input
|
56
|
+
self::Input.used_by(action)
|
57
|
+
when :output
|
58
|
+
self::Output.used_by(action)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# Subclass this class in your adapter and reimplement
|
64
|
+
# necessary methods.
|
65
|
+
class Input
|
66
|
+
def self.used_by(action)
|
67
|
+
|
68
|
+
end
|
69
|
+
|
70
|
+
def initialize(input)
|
71
|
+
@input = input
|
72
|
+
end
|
73
|
+
|
74
|
+
# Return true if input parameters contain parameter
|
75
|
+
# with +name+.
|
76
|
+
def has_param?(name)
|
77
|
+
@input.has_key?(name)
|
78
|
+
end
|
79
|
+
|
80
|
+
# Return parameter with +name+.
|
81
|
+
def [](name)
|
82
|
+
@input[name]
|
83
|
+
end
|
84
|
+
|
85
|
+
# Return model instance from a raw input resource parameter.
|
86
|
+
def self.clean(model, raw, extra)
|
87
|
+
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
# Subclass this class in your adapter and reimplement
|
92
|
+
# necessary methods.
|
93
|
+
class Output
|
94
|
+
def self.used_by(action)
|
95
|
+
|
96
|
+
end
|
97
|
+
|
98
|
+
def initialize(context, obj)
|
99
|
+
@context = context
|
100
|
+
@object = obj
|
101
|
+
end
|
102
|
+
|
103
|
+
# Return true if input parameters contain parameter
|
104
|
+
# with +name+.
|
105
|
+
def has_param?(name)
|
106
|
+
|
107
|
+
end
|
108
|
+
|
109
|
+
# Return a parameter in an appropriate format to be sent to a client.
|
110
|
+
def [](name)
|
111
|
+
|
112
|
+
end
|
113
|
+
|
114
|
+
def meta
|
115
|
+
{}
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|