api_recipes 0.6.2 → 2.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/{LICENSE.txt → MIT-LICENSE} +0 -0
- data/README.md +2 -2
- data/lib/api_recipes.rb +97 -45
- data/lib/api_recipes/api.rb +69 -0
- data/lib/api_recipes/configuration.rb +40 -7
- data/lib/api_recipes/endpoint.rb +119 -14
- data/lib/api_recipes/exceptions.rb +41 -41
- data/lib/api_recipes/response.rb +22 -0
- data/lib/api_recipes/route.rb +156 -0
- data/lib/api_recipes/settings.rb +9 -6
- data/lib/api_recipes/version.rb +1 -1
- metadata +14 -47
- data/.gitignore +0 -16
- data/.rspec +0 -2
- data/.travis.yml +0 -10
- data/CHANGELOG.md +0 -15
- data/CODE_OF_CONDUCT.md +0 -13
- data/Gemfile +0 -16
- data/Guardfile +0 -15
- data/Rakefile +0 -6
- data/api_recipes.gemspec +0 -23
- data/api_recipes.png +0 -0
- data/bin/console +0 -14
- data/bin/setup +0 -7
- data/examples/authorization.rb +0 -42
- data/examples/authorization_with_default_headers.rb +0 -41
- data/examples/basic_auth.rb +0 -32
- data/examples/config/apis.yml +0 -43
- data/examples/custom_configs.rb +0 -51
- data/examples/delete_me.rb +0 -21
- data/examples/multiple_endpoints.rb +0 -54
- data/examples/simple_usage.rb +0 -38
- data/lib/api_recipes/resource.rb +0 -165
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 977a895fe8c06312c73575391e759f6bd23c91fbdad1d26dfb5bb66dfc494b09
|
4
|
+
data.tar.gz: 507c600f1ab08ea1bcec035d8a5e60178da5d08c4e5f49beb84fba64829f39b5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a4bdfe19e5994165e5d2ab7256b66887c54951f2df93cd44417b4c019f10979532188ff21f581fbfb5ff8528c9d04063eda93326cefeda396a26a4d397c5d5dd
|
7
|
+
data.tar.gz: b67d63a82f8199e9c38bf043a0d3ba192d7613b34a3955d7aef0ef6df0d5a20017fce3be9083be8e0a697a6e96d7fcdfecb37b0aed3f43742f3600c7deb429cc
|
data/{LICENSE.txt → MIT-LICENSE}
RENAMED
File without changes
|
data/README.md
CHANGED
@@ -28,7 +28,7 @@ Or install it yourself as:
|
|
28
28
|
|
29
29
|
## Compatibility
|
30
30
|
|
31
|
-
Ruby
|
31
|
+
Ruby 2.5.0 or higher.
|
32
32
|
|
33
33
|
## Usage
|
34
34
|
|
@@ -38,7 +38,7 @@ In the meantime take a look at the ``` examples ``` folder. Thank you.
|
|
38
38
|
|
39
39
|
## TODO
|
40
40
|
|
41
|
-
*
|
41
|
+
* Re-write specs
|
42
42
|
* Write a good Usage
|
43
43
|
* Add more examples
|
44
44
|
* Add documentation
|
data/lib/api_recipes.rb
CHANGED
@@ -1,28 +1,59 @@
|
|
1
1
|
require 'http'
|
2
|
-
require 'oj'
|
3
2
|
|
4
3
|
require 'api_recipes/utils'
|
5
4
|
require 'api_recipes/exceptions'
|
6
5
|
require 'api_recipes/configuration'
|
7
|
-
require 'api_recipes/
|
6
|
+
require 'api_recipes/route'
|
8
7
|
require 'api_recipes/endpoint'
|
8
|
+
require 'api_recipes/api'
|
9
|
+
require 'api_recipes/response'
|
9
10
|
require 'api_recipes/settings'
|
10
11
|
|
12
|
+
# TODO: Sistema i default nelle config
|
13
|
+
|
11
14
|
module ApiRecipes
|
12
15
|
|
13
|
-
def self.included(
|
16
|
+
def self.included(receiver)
|
14
17
|
|
15
|
-
def
|
16
|
-
|
17
|
-
|
18
|
+
def receiver.api(api_name, configs = {})
|
19
|
+
unless api_name.is_a?(String) || api_name.is_a?(Symbol)
|
20
|
+
raise ArgumentError, "api name must be a Symbol or String"
|
21
|
+
end
|
18
22
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
+
if configs && !configs.is_a?(Hash)
|
24
|
+
raise ApiRecipes::ApiConfigIsNotAnHash.new(api_name)
|
25
|
+
end
|
26
|
+
|
27
|
+
api_name = api_name.to_sym
|
28
|
+
# configs = ApiRecipes._aprcps_merge_apis_configs(api_name, configs.deep_symbolize_keys)
|
29
|
+
if self.respond_to? api_name
|
30
|
+
raise ApiNameClashError.new(self, api_name)
|
31
|
+
else
|
32
|
+
|
33
|
+
define_method api_name do
|
34
|
+
configs = ApiRecipes._aprcps_merge_apis_configs(api_name, configs.deep_symbolize_keys)
|
35
|
+
api = Api.new(api_name, configs)
|
36
|
+
ApiRecipes.copy_global_authorizations_to_api api
|
37
|
+
api
|
38
|
+
end
|
39
|
+
define_singleton_method api_name do
|
40
|
+
configs = ApiRecipes._aprcps_merge_apis_configs(api_name, configs.deep_symbolize_keys)
|
41
|
+
api = Api.new(api_name, configs)
|
42
|
+
ApiRecipes.copy_global_authorizations_to_api api
|
43
|
+
api
|
44
|
+
end
|
45
|
+
end
|
23
46
|
end
|
24
47
|
end
|
25
48
|
|
49
|
+
def self.print_urls=(value)
|
50
|
+
@print_urls = value
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.print_urls
|
54
|
+
@print_urls || false
|
55
|
+
end
|
56
|
+
|
26
57
|
def self.configuration
|
27
58
|
unless @configuration
|
28
59
|
@configuration = Configuration.new
|
@@ -33,65 +64,86 @@ module ApiRecipes
|
|
33
64
|
def self.configure
|
34
65
|
if block_given?
|
35
66
|
yield(configuration)
|
36
|
-
_aprcps_define_global_endpoints
|
37
67
|
else
|
38
68
|
configuration
|
39
69
|
end
|
40
70
|
end
|
41
71
|
|
42
|
-
def self.
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
_aprcps_storage[endpoint_name]
|
50
|
-
end
|
72
|
+
def self.copy_global_authorizations_to_api(api)
|
73
|
+
if _aprcps_global_storage[api.name]
|
74
|
+
if auth = _aprcps_global_storage[api.name].basic_auth
|
75
|
+
api.basic_auth = auth
|
76
|
+
end
|
77
|
+
if auth = _aprcps_global_storage[api.name].authorization
|
78
|
+
api.authorization = auth
|
51
79
|
end
|
52
80
|
end
|
53
81
|
end
|
54
82
|
|
83
|
+
def self.set_authorization_for_api(authorization, api_name)
|
84
|
+
api_name = api_name.to_sym
|
55
85
|
|
56
|
-
|
57
|
-
|
58
|
-
|
86
|
+
# Set authorization on thread storage
|
87
|
+
if _aprcps_thread_storage[api_name]
|
88
|
+
_aprcps_thread_storage[api_name].each do |_, api|
|
89
|
+
api.authorization = authorization
|
90
|
+
end
|
59
91
|
end
|
60
|
-
Thread.current[:api_recipes]
|
61
92
|
end
|
62
93
|
|
94
|
+
def self.set_basic_auth_for_api(basic_auth, api_name)
|
95
|
+
api_name = api_name.to_sym
|
63
96
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
else
|
69
|
-
ep = _aprcps_storage[ep_name]
|
70
|
-
end
|
71
|
-
obj.define_singleton_method ep_name do
|
72
|
-
ep
|
97
|
+
# Set authorization on thread storage
|
98
|
+
if _aprcps_thread_storage[api_name]
|
99
|
+
_aprcps_thread_storage[api_name].each do |_, api|
|
100
|
+
api.authorization = basic_auth
|
73
101
|
end
|
74
102
|
end
|
75
103
|
end
|
76
104
|
|
77
|
-
def self.
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
105
|
+
def self._aprcps_define_global_apis
|
106
|
+
configuration.apis_configs.each do |api_name, api_configs|
|
107
|
+
api_name = api_name.to_sym
|
108
|
+
_aprcps_global_storage[api_name] = Api.new api_name, api_configs
|
109
|
+
define_singleton_method api_name do
|
110
|
+
_aprcps_global_storage[api_name]
|
83
111
|
end
|
84
112
|
end
|
85
113
|
end
|
86
114
|
|
87
|
-
def self.
|
88
|
-
|
89
|
-
|
115
|
+
def self._aprcps_global_storage
|
116
|
+
unless @storage
|
117
|
+
@storage = {}
|
118
|
+
end
|
119
|
+
@storage
|
120
|
+
end
|
121
|
+
|
122
|
+
def self._aprcps_thread_storage
|
123
|
+
unless Thread.current[:api_recipes]
|
124
|
+
Thread.current[:api_recipes] = {}
|
90
125
|
end
|
91
|
-
|
92
|
-
|
126
|
+
Thread.current[:api_recipes]
|
127
|
+
end
|
128
|
+
|
129
|
+
def self._aprcps_merge_apis_configs(api_name, configs = nil)
|
130
|
+
unless api_name.is_a?(String) || api_name.is_a?(Symbol)
|
131
|
+
raise ArgumentError, "no api_name provided. Given: #{api_name.inspect}"
|
132
|
+
end
|
133
|
+
unless ApiRecipes.configuration.apis_configs[api_name]
|
134
|
+
ApiRecipes.configuration.apis_configs[api_name] = {}
|
135
|
+
end
|
136
|
+
if configs
|
137
|
+
ApiRecipes.configuration.apis_configs[api_name].merge(configs) do |_, old_val, new_val|
|
138
|
+
if new_val.nil?
|
139
|
+
old_val
|
140
|
+
else
|
141
|
+
new_val
|
142
|
+
end
|
143
|
+
end
|
144
|
+
else
|
145
|
+
ApiRecipes.configuration.apis_configs[api_name]
|
93
146
|
end
|
94
|
-
ApiRecipes.configuration.endpoints_configs[endpoint_name].merge configs
|
95
147
|
end
|
96
148
|
end
|
97
149
|
|
@@ -0,0 +1,69 @@
|
|
1
|
+
module ApiRecipes
|
2
|
+
class Api
|
3
|
+
|
4
|
+
attr_accessor :name, :configs, :authorization, :basic_auth
|
5
|
+
attr_reader :base_configs
|
6
|
+
|
7
|
+
BASE_CONFIGS_KEYS = [:protocol, :host, :port, :api_version, :timeout, :on_bad_code]
|
8
|
+
|
9
|
+
def initialize(name, configs)
|
10
|
+
@name = name
|
11
|
+
@configs = ApiRecipes::Settings::DEFAULT.merge configs
|
12
|
+
|
13
|
+
# Generate some_api.some_endpoint methods
|
14
|
+
# e.g. github.users
|
15
|
+
endpoints = @configs[:endpoints]
|
16
|
+
if endpoints && endpoints.is_a?(Hash)
|
17
|
+
endpoints.each do |ep_name, params|
|
18
|
+
unless respond_to? ep_name
|
19
|
+
define_singleton_method ep_name do |*request_params, &block|
|
20
|
+
# puts "API params: #{params}"
|
21
|
+
Endpoint.new api: self, name: ep_name, path: path, params: params, request_params: request_params, &block
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def authorization=(auth)
|
29
|
+
@authorization = auth
|
30
|
+
|
31
|
+
# Check if I'm the global api
|
32
|
+
if global?
|
33
|
+
# Set authorization also on every "children" (classes that define the same api)
|
34
|
+
ApiRecipes.set_authorization_for_api auth, name
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def basic_auth=(auth)
|
39
|
+
@basic_auth = auth
|
40
|
+
|
41
|
+
# Check if I'm the global api
|
42
|
+
if global?
|
43
|
+
# Set authorization also on every "children" (classes that define the same api)
|
44
|
+
ApiRecipes.set_basic_auth_for_api auth, name
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def base_configs
|
49
|
+
@configs.select { |c| BASE_CONFIGS_KEYS.include? c }
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def check_route_name_does_not_clash(route_name)
|
55
|
+
# Check if a method named route_name has already been defined on this object
|
56
|
+
if respond_to? route_name
|
57
|
+
raise RouteNameClashWithExistentMethod.new(@name, route_name)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def global?
|
62
|
+
ApiRecipes._aprcps_global_storage[name] == self
|
63
|
+
end
|
64
|
+
|
65
|
+
def path
|
66
|
+
"/#{configs[:base_url]}/"
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -1,16 +1,49 @@
|
|
1
1
|
module ApiRecipes
|
2
2
|
class Configuration
|
3
3
|
|
4
|
-
|
5
|
-
|
6
|
-
|
4
|
+
attr_accessor :log_to, :log_level, :print_urls
|
5
|
+
|
6
|
+
def apis_configs=(configs = {})
|
7
|
+
raise ArgumentError, 'apis_configs must be an Hash' unless configs.is_a? Hash
|
8
|
+
@apis_configs = configs.deep_symbolize_keys
|
9
|
+
ApiRecipes._aprcps_define_global_apis
|
10
|
+
end
|
11
|
+
|
12
|
+
def apis_configs
|
13
|
+
unless @apis_configs
|
14
|
+
@apis_configs = {}
|
15
|
+
end
|
16
|
+
@apis_configs
|
17
|
+
end
|
18
|
+
|
19
|
+
def logger=(logger)
|
20
|
+
@logger = logger
|
7
21
|
end
|
8
22
|
|
9
|
-
def
|
10
|
-
unless @
|
11
|
-
|
23
|
+
def logger
|
24
|
+
unless @logger
|
25
|
+
log = ::Logger.new(log_to)
|
26
|
+
log.level = normalize_log_level
|
27
|
+
log.progname = 'ApiRecipes'
|
28
|
+
@logger = log
|
29
|
+
end
|
30
|
+
|
31
|
+
@logger
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
# @private
|
37
|
+
def normalize_log_level
|
38
|
+
case @log_level
|
39
|
+
when :debug, ::Logger::DEBUG, 'debug' then ::Logger::DEBUG
|
40
|
+
when :info, ::Logger::INFO, 'info' then ::Logger::INFO
|
41
|
+
when :warn, ::Logger::WARN, 'warn' then ::Logger::WARN
|
42
|
+
when :error, ::Logger::ERROR, 'error' then ::Logger::ERROR
|
43
|
+
when :fatal, ::Logger::FATAL, 'fatal' then ::Logger::FATAL
|
44
|
+
else
|
45
|
+
Logger::ERROR
|
12
46
|
end
|
13
|
-
@endpoints_configs
|
14
47
|
end
|
15
48
|
end
|
16
49
|
end
|
data/lib/api_recipes/endpoint.rb
CHANGED
@@ -1,25 +1,130 @@
|
|
1
1
|
module ApiRecipes
|
2
2
|
class Endpoint
|
3
3
|
|
4
|
-
|
5
|
-
attr_reader :resources
|
4
|
+
attr_reader :api, :name, :params, :route, :children
|
6
5
|
|
7
|
-
def initialize(name,
|
6
|
+
def initialize(api: nil, name: nil, path: nil, params: {}, request_params: [], &block)
|
7
|
+
@api = api
|
8
8
|
@name = name
|
9
|
-
@
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
@
|
14
|
-
@
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
9
|
+
@path = path.to_s
|
10
|
+
new_params = params.dup || {}
|
11
|
+
self.params = new_params
|
12
|
+
@children = new_params.delete :endpoints
|
13
|
+
@route = nil
|
14
|
+
@request_params = request_params.extract_options!
|
15
|
+
self.path_params = request_params
|
16
|
+
|
17
|
+
generate_route
|
18
|
+
generate_children
|
19
|
+
if block_given?
|
20
|
+
run &block
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def run(&block)
|
25
|
+
if @route
|
26
|
+
@route.start_request &block
|
27
|
+
else
|
28
|
+
raise NoRouteExists.new @name
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def url
|
33
|
+
@route.url
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def generate_route
|
39
|
+
# Check if we have to generate route for this endpoint
|
40
|
+
if create_route?
|
41
|
+
ensure_route_does_not_clash @name
|
42
|
+
# Generate route
|
43
|
+
attrs = params.dup
|
44
|
+
attrs.delete(:endpoints)
|
45
|
+
# puts "Generating route '#{@name}' with path '#{build_path}'"
|
46
|
+
@route = Route.new(api: @api, endpoint: self, path: build_path, attributes: attrs, req_pars: @request_params)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def generate_children
|
51
|
+
# Generate children endpoints if any
|
52
|
+
# puts "generating children of #{@name}: #{children.inspect}"
|
53
|
+
if children
|
54
|
+
children.each do |ep_name, pars|
|
55
|
+
# puts "Creating Endpoint '#{@name}' child '#{ep_name}' passing path #{build_path}"
|
56
|
+
define_singleton_method ep_name do |*request_params, &block|
|
57
|
+
Endpoint.new api: @api, name: ep_name, path: build_path, params: pars, request_params: request_params, &block
|
20
58
|
end
|
21
59
|
end
|
22
60
|
end
|
61
|
+
# Route.new(api: @api, endpoint: self, name: route_name, attributes: route_attrs, req_pars: request_params).start_request &block
|
62
|
+
end
|
63
|
+
|
64
|
+
def ensure_route_does_not_clash(route_name)
|
65
|
+
# Check if a method named route_name has already been defined on this object
|
66
|
+
if respond_to? route_name
|
67
|
+
raise RouteNameClashWithExistentMethod.new(@name, route_name)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def create_route?
|
72
|
+
res = params[:route].eql?('yes') || params[:route].eql?(true)
|
73
|
+
res
|
74
|
+
end
|
75
|
+
|
76
|
+
def absolute_path
|
77
|
+
# Append path passed to initialize and (params[:path] || @name)
|
78
|
+
append = params[:path] || @name
|
79
|
+
unless append.empty?
|
80
|
+
"#{@path}/#{append}"
|
81
|
+
else
|
82
|
+
"#{@path}"
|
83
|
+
end.gsub(/\/+/, '/') # remove multiple consecutive '//'
|
84
|
+
end
|
85
|
+
|
86
|
+
def params=(attrs)
|
87
|
+
unless attrs.is_a? Hash
|
88
|
+
raise ArgumentError, "provided 'attrs' must be an Hash"
|
89
|
+
end
|
90
|
+
# Merge DEFAULT_ROUTE_ATTRIBUTES with Api base_configs
|
91
|
+
# Then merge the result with provided attributes
|
92
|
+
|
93
|
+
@params = Settings::DEFAULT_ROUTE_ATTRIBUTES.inject({}) do |out, key_val|
|
94
|
+
new_val = @api.base_configs[key_val.first]
|
95
|
+
out[key_val.first] = new_val.nil? ? key_val.last : new_val
|
96
|
+
out
|
97
|
+
end.inject({}) do |out, key_val|
|
98
|
+
new_val = attrs[key_val.first]
|
99
|
+
out[key_val.first] = new_val.nil? ? key_val.last : new_val
|
100
|
+
out
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def build_path
|
105
|
+
final_path = absolute_path
|
106
|
+
# Check if provided path_params match with required path params
|
107
|
+
req_params = required_params_for_path
|
108
|
+
if @path_params.size != req_params.size
|
109
|
+
# puts "\nWARNING\n"
|
110
|
+
raise PathParamsMismatch.new(final_path, req_params, @path_params)
|
111
|
+
end
|
112
|
+
# Replace required_params present in path with params provided by user (@path_params)
|
113
|
+
@path_params.each { |par| final_path.sub! /(:[^\/]+)/, par.to_s }
|
114
|
+
|
115
|
+
final_path
|
116
|
+
end
|
117
|
+
|
118
|
+
def path_params=(params)
|
119
|
+
unless params.is_a? Array
|
120
|
+
raise ArgumentError, 'path params must be an Array'
|
121
|
+
end
|
122
|
+
# Merge route attributes with defaults and deep clone route attributes
|
123
|
+
@path_params = params
|
124
|
+
end
|
125
|
+
|
126
|
+
def required_params_for_path
|
127
|
+
absolute_path.scan(/:(\w+)/).flatten.map { |p| p.to_sym }
|
23
128
|
end
|
24
129
|
end
|
25
130
|
end
|