api_recipes 1.0.0 → 2.5.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 +60 -59
- data/lib/api_recipes/api.rb +69 -0
- data/lib/api_recipes/configuration.rb +8 -8
- data/lib/api_recipes/endpoint.rb +105 -30
- data/lib/api_recipes/exceptions.rb +24 -23
- data/lib/api_recipes/response.rb +18 -1
- data/lib/api_recipes/route.rb +158 -0
- data/lib/api_recipes/settings.rb +9 -6
- data/lib/api_recipes/version.rb +1 -1
- metadata +15 -34
- 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 -12
- data/Guardfile +0 -14
- data/Rakefile +0 -6
- data/api_recipes.gemspec +0 -22
- 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 -192
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: febe7d307961dd407eae97ef0519280abf463e896f8a83471d3242b6f8a7b8a1
|
4
|
+
data.tar.gz: 15df723f847728e9c433223eef128069204aa15344856a4388847860c45304f2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 94627389adf247362789b3213f6ec2f45c4443e43c4212ec1fffa96f4796c4de9c097288ef0d328d657c98eb8590fa6908e89f77f080c09be1b0cb0b9c7f44d9
|
7
|
+
data.tar.gz: a887790e2ac8c3ec7853d2a24735f6c79a7761287e76265854963e80f18320b63bb598365ba75fb316064ebf9c424569e09160f4dc3eb79ce4c09e6326e1972f
|
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
@@ -3,56 +3,57 @@ require 'http'
|
|
3
3
|
require 'api_recipes/utils'
|
4
4
|
require 'api_recipes/exceptions'
|
5
5
|
require 'api_recipes/configuration'
|
6
|
-
require 'api_recipes/
|
6
|
+
require 'api_recipes/route'
|
7
7
|
require 'api_recipes/endpoint'
|
8
|
+
require 'api_recipes/api'
|
8
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
16
|
def self.included(receiver)
|
14
17
|
|
15
|
-
def receiver.
|
16
|
-
unless
|
17
|
-
raise ArgumentError, "
|
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"
|
18
21
|
end
|
19
22
|
|
20
23
|
if configs && !configs.is_a?(Hash)
|
21
|
-
raise ApiRecipes::
|
24
|
+
raise ApiRecipes::ApiConfigIsNotAnHash.new(api_name)
|
22
25
|
end
|
23
26
|
|
24
|
-
|
25
|
-
configs = ApiRecipes.
|
26
|
-
if self.respond_to?
|
27
|
-
raise
|
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)
|
28
31
|
else
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
unless ApiRecipes._aprcps_thread_storage[endpoint_name]
|
36
|
-
ApiRecipes._aprcps_thread_storage[endpoint_name] = {}
|
37
|
-
end
|
38
|
-
unless ApiRecipes._aprcps_thread_storage[endpoint_name][self.class]
|
39
|
-
ApiRecipes._aprcps_thread_storage[endpoint_name][self.class] = ep.clone
|
40
|
-
end
|
41
|
-
ApiRecipes._aprcps_thread_storage[endpoint_name][self.class]
|
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
|
42
38
|
end
|
43
|
-
define_singleton_method
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
ApiRecipes._aprcps_thread_storage[endpoint_name][self] = ep.clone
|
49
|
-
end
|
50
|
-
ApiRecipes._aprcps_thread_storage[endpoint_name][self]
|
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
|
51
44
|
end
|
52
45
|
end
|
53
46
|
end
|
54
47
|
end
|
55
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
|
+
|
56
57
|
def self.configuration
|
57
58
|
unless @configuration
|
58
59
|
@configuration = Configuration.new
|
@@ -68,45 +69,45 @@ module ApiRecipes
|
|
68
69
|
end
|
69
70
|
end
|
70
71
|
|
71
|
-
def self.
|
72
|
-
if _aprcps_global_storage[
|
73
|
-
if auth = _aprcps_global_storage[
|
74
|
-
|
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
|
75
76
|
end
|
76
|
-
if auth = _aprcps_global_storage[
|
77
|
-
|
77
|
+
if auth = _aprcps_global_storage[api.name].authorization
|
78
|
+
api.authorization = auth
|
78
79
|
end
|
79
80
|
end
|
80
81
|
end
|
81
82
|
|
82
|
-
def self.
|
83
|
-
|
83
|
+
def self.set_authorization_for_api(authorization, api_name)
|
84
|
+
api_name = api_name.to_sym
|
84
85
|
|
85
86
|
# Set authorization on thread storage
|
86
|
-
if _aprcps_thread_storage[
|
87
|
-
_aprcps_thread_storage[
|
88
|
-
|
87
|
+
if _aprcps_thread_storage[api_name]
|
88
|
+
_aprcps_thread_storage[api_name].each do |_, api|
|
89
|
+
api.authorization = authorization
|
89
90
|
end
|
90
91
|
end
|
91
92
|
end
|
92
93
|
|
93
|
-
def self.
|
94
|
-
|
94
|
+
def self.set_basic_auth_for_api(basic_auth, api_name)
|
95
|
+
api_name = api_name.to_sym
|
95
96
|
|
96
97
|
# Set authorization on thread storage
|
97
|
-
if _aprcps_thread_storage[
|
98
|
-
_aprcps_thread_storage[
|
99
|
-
|
98
|
+
if _aprcps_thread_storage[api_name]
|
99
|
+
_aprcps_thread_storage[api_name].each do |_, api|
|
100
|
+
api.authorization = basic_auth
|
100
101
|
end
|
101
102
|
end
|
102
103
|
end
|
103
104
|
|
104
|
-
def self.
|
105
|
-
configuration.
|
106
|
-
|
107
|
-
_aprcps_global_storage[
|
108
|
-
define_singleton_method
|
109
|
-
_aprcps_global_storage[
|
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]
|
110
111
|
end
|
111
112
|
end
|
112
113
|
end
|
@@ -125,15 +126,15 @@ module ApiRecipes
|
|
125
126
|
Thread.current[:api_recipes]
|
126
127
|
end
|
127
128
|
|
128
|
-
def self.
|
129
|
-
unless
|
130
|
-
raise ArgumentError, "no
|
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}"
|
131
132
|
end
|
132
|
-
unless ApiRecipes.configuration.
|
133
|
-
ApiRecipes.configuration.
|
133
|
+
unless ApiRecipes.configuration.apis_configs[api_name]
|
134
|
+
ApiRecipes.configuration.apis_configs[api_name] = {}
|
134
135
|
end
|
135
136
|
if configs
|
136
|
-
ApiRecipes.configuration.
|
137
|
+
ApiRecipes.configuration.apis_configs[api_name].merge(configs) do |_, old_val, new_val|
|
137
138
|
if new_val.nil?
|
138
139
|
old_val
|
139
140
|
else
|
@@ -141,7 +142,7 @@ module ApiRecipes
|
|
141
142
|
end
|
142
143
|
end
|
143
144
|
else
|
144
|
-
ApiRecipes.configuration.
|
145
|
+
ApiRecipes.configuration.apis_configs[api_name]
|
145
146
|
end
|
146
147
|
end
|
147
148
|
end
|
@@ -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
|
@@ -3,17 +3,17 @@ module ApiRecipes
|
|
3
3
|
|
4
4
|
attr_accessor :log_to, :log_level
|
5
5
|
|
6
|
-
def
|
7
|
-
raise ArgumentError, '
|
8
|
-
@
|
9
|
-
ApiRecipes.
|
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
10
|
end
|
11
11
|
|
12
|
-
def
|
13
|
-
unless @
|
14
|
-
@
|
12
|
+
def apis_configs
|
13
|
+
unless @apis_configs
|
14
|
+
@apis_configs = {}
|
15
15
|
end
|
16
|
-
@
|
16
|
+
@apis_configs
|
17
17
|
end
|
18
18
|
|
19
19
|
def logger=(logger)
|
data/lib/api_recipes/endpoint.rb
CHANGED
@@ -1,51 +1,126 @@
|
|
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
|
+
private
|
33
|
+
|
34
|
+
def generate_route
|
35
|
+
# Check if we have to generate route for this endpoint
|
36
|
+
if create_route?
|
37
|
+
ensure_route_does_not_clash @name
|
38
|
+
# Generate route
|
39
|
+
attrs = params.dup
|
40
|
+
attrs.delete(:endpoints)
|
41
|
+
# puts "Generating route '#{@name}' with path '#{build_path}'"
|
42
|
+
@route = Route.new(api: @api, endpoint: self, path: build_path, attributes: attrs, req_pars: @request_params)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def generate_children
|
47
|
+
# Generate children endpoints if any
|
48
|
+
# puts "generating children of #{@name}: #{children.inspect}"
|
49
|
+
if children
|
50
|
+
children.each do |ep_name, pars|
|
51
|
+
# puts "Creating Endpoint '#{@name}' child '#{ep_name}' passing path #{build_path}"
|
52
|
+
define_singleton_method ep_name do |*request_params, &block|
|
53
|
+
Endpoint.new api: @api, name: ep_name, path: build_path, params: pars, request_params: request_params, &block
|
20
54
|
end
|
21
55
|
end
|
22
56
|
end
|
57
|
+
# Route.new(api: @api, endpoint: self, name: route_name, attributes: route_attrs, req_pars: request_params).start_request &block
|
23
58
|
end
|
24
59
|
|
25
|
-
def
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
if global?
|
30
|
-
# Set authorization also on every "children" (classes that define the same endpoint)
|
31
|
-
ApiRecipes.set_authorization_for_endpoint auth, name
|
60
|
+
def ensure_route_does_not_clash(route_name)
|
61
|
+
# Check if a method named route_name has already been defined on this object
|
62
|
+
if respond_to? route_name
|
63
|
+
raise RouteNameClashWithExistentMethod.new(@name, route_name)
|
32
64
|
end
|
33
65
|
end
|
34
66
|
|
35
|
-
def
|
36
|
-
|
67
|
+
def create_route?
|
68
|
+
res = params[:route].eql?('yes') || params[:route].eql?(true)
|
69
|
+
res
|
70
|
+
end
|
71
|
+
|
72
|
+
def absolute_path
|
73
|
+
# Append path passed to initialize and (params[:path] || @name)
|
74
|
+
append = params[:path] || @name
|
75
|
+
unless append.empty?
|
76
|
+
"#{@path}/#{append}"
|
77
|
+
else
|
78
|
+
"#{@path}"
|
79
|
+
end.gsub(/\/+/, '/') # remove multiple consecutive '//'
|
80
|
+
end
|
37
81
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
82
|
+
def params=(attrs)
|
83
|
+
unless attrs.is_a? Hash
|
84
|
+
raise ArgumentError, "provided 'attrs' must be an Hash"
|
85
|
+
end
|
86
|
+
# Merge DEFAULT_ROUTE_ATTRIBUTES with Api base_configs
|
87
|
+
# Then merge the result with provided attributes
|
88
|
+
|
89
|
+
@params = Settings::DEFAULT_ROUTE_ATTRIBUTES.inject({}) do |out, key_val|
|
90
|
+
new_val = @api.base_configs[key_val.first]
|
91
|
+
out[key_val.first] = new_val.nil? ? key_val.last : new_val
|
92
|
+
out
|
93
|
+
end.inject({}) do |out, key_val|
|
94
|
+
new_val = attrs[key_val.first]
|
95
|
+
out[key_val.first] = new_val.nil? ? key_val.last : new_val
|
96
|
+
out
|
42
97
|
end
|
43
98
|
end
|
44
99
|
|
45
|
-
|
100
|
+
def build_path
|
101
|
+
final_path = absolute_path
|
102
|
+
# Check if provided path_params match with required path params
|
103
|
+
req_params = required_params_for_path
|
104
|
+
if @path_params.size != req_params.size
|
105
|
+
# puts "\nWARNING\n"
|
106
|
+
raise PathParamsMismatch.new(final_path, req_params, @path_params)
|
107
|
+
end
|
108
|
+
# Replace required_params present in path with params provided by user (@path_params)
|
109
|
+
@path_params.each { |par| final_path.sub! /(:[^\/]+)/, par.to_s }
|
110
|
+
|
111
|
+
final_path
|
112
|
+
end
|
113
|
+
|
114
|
+
def path_params=(params)
|
115
|
+
unless params.is_a? Array
|
116
|
+
raise ArgumentError, 'path params must be an Array'
|
117
|
+
end
|
118
|
+
# Merge route attributes with defaults and deep clone route attributes
|
119
|
+
@path_params = params
|
120
|
+
end
|
46
121
|
|
47
|
-
def
|
48
|
-
|
122
|
+
def required_params_for_path
|
123
|
+
absolute_path.scan(/:(\w+)/).flatten.map { |p| p.to_sym }
|
49
124
|
end
|
50
125
|
end
|
51
126
|
end
|