haveapi 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|