api_recipes 0.6.1 → 2.5.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 +4 -4
- data/{LICENSE.txt → MIT-LICENSE} +0 -0
- data/README.md +2 -2
- data/lib/api_recipes.rb +98 -45
- data/lib/api_recipes/api.rb +69 -0
- data/lib/api_recipes/configuration.rb +40 -7
- data/lib/api_recipes/endpoint.rb +115 -14
- data/lib/api_recipes/exceptions.rb +41 -41
- data/lib/api_recipes/response.rb +22 -0
- 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 -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: 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
@@ -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)
|
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
|
22
|
+
|
23
|
+
if configs && !configs.is_a?(Hash)
|
24
|
+
raise ApiRecipes::ApiConfigIsNotAnHash.new(api_name)
|
25
|
+
end
|
14
26
|
|
15
|
-
|
16
|
-
configs = ApiRecipes.
|
17
|
-
|
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
|
18
32
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
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,64 +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
|
|
55
|
-
def self.
|
56
|
-
|
57
|
-
|
83
|
+
def self.set_authorization_for_api(authorization, api_name)
|
84
|
+
api_name = api_name.to_sym
|
85
|
+
|
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
|
58
91
|
end
|
59
|
-
Thread.current[:api_recipes]
|
60
92
|
end
|
61
93
|
|
94
|
+
def self.set_basic_auth_for_api(basic_auth, api_name)
|
95
|
+
api_name = api_name.to_sym
|
62
96
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
else
|
68
|
-
ep = _aprcps_storage[ep_name]
|
69
|
-
end
|
70
|
-
obj.define_singleton_method ep_name do
|
71
|
-
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
|
72
101
|
end
|
73
102
|
end
|
74
103
|
end
|
75
104
|
|
76
|
-
def self.
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
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]
|
82
111
|
end
|
83
112
|
end
|
84
113
|
end
|
85
114
|
|
86
|
-
def self.
|
87
|
-
|
88
|
-
|
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] = {}
|
125
|
+
end
|
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}"
|
89
132
|
end
|
90
|
-
unless ApiRecipes.configuration.
|
91
|
-
ApiRecipes.configuration.
|
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]
|
92
146
|
end
|
93
|
-
ApiRecipes.configuration.endpoints_configs[endpoint_name].merge configs
|
94
147
|
end
|
95
148
|
end
|
96
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
|
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,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
|
58
|
+
end
|
59
|
+
|
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)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
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
|
81
|
+
|
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
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
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
|
121
|
+
|
122
|
+
def required_params_for_path
|
123
|
+
absolute_path.scan(/:(\w+)/).flatten.map { |p| p.to_sym }
|
23
124
|
end
|
24
125
|
end
|
25
126
|
end
|