api_recipes 1.0.0 → 2.7.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fc2b26e8c66139a2ff051ef066bb512b389a48bcbfa2ba2b391f1dc29719426d
4
- data.tar.gz: 281afb372a91edac6bbc1bc6335da2fb413e856ed7f32cff476f9c527d4dc459
3
+ metadata.gz: 8ac0841819d455adb4d85f88d7858e33cfaa17bf35316811f5a6d7c75879b271
4
+ data.tar.gz: ddacdca3095b42cdc14c51e066a52d237c85dfc0ba79419342168d7667f0b12e
5
5
  SHA512:
6
- metadata.gz: 610ff709c864f8cb71f29f42a0a50cd75318e0ce184801f8aa40102d35301b937afdd1c3a56381f783e405763aad2ac4886205809c2b26a07d73a9433e368c81
7
- data.tar.gz: a3a0cd7ac6a1b7aaea7fe3f5989d4e1c7b071f6d7b18ffc99b4fbff56689114407310f37e459cee183a56b78529f51fe39b59712b4dc5cbd31577fc7e451b2b7
6
+ metadata.gz: ec9f37f976b33f9386b4f201f4200ce33fdc526510f31984b591823c093c187037c7c8fc6c967fd33094b41f5343b4810ae251a89d3a9922e55b565f7f648503
7
+ data.tar.gz: 4c2f41152de95562744e8b60e8d14ca4adbbd4a88487d89f52149ba9bfd6b9bff3bf830bedc1aee9c59787e541ad5386d151baea552b11e7c63a8eac1e795b85
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 1.9.3 or higher.
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
- * Write specs (I know... this code has been wrote down on a hurry so please do not beat me)
41
+ * Re-write specs
42
42
  * Write a good Usage
43
43
  * Add more examples
44
44
  * Add documentation
@@ -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/resource'
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.endpoint(endpoint_name, configs = {})
16
- unless endpoint_name.is_a?(String) || endpoint_name.is_a?(Symbol)
17
- raise ArgumentError, "endpoint name must be a Symbol or String"
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::EndpointConfigIsNotAnHash.new(endpoint_name)
24
+ raise ApiRecipes::ApiConfigIsNotAnHash.new(api_name)
22
25
  end
23
26
 
24
- endpoint_name = endpoint_name.to_sym
25
- configs = ApiRecipes._aprcps_merge_endpoints_configs(endpoint_name, configs.deep_symbolize_keys)
26
- if self.respond_to? endpoint_name
27
- raise EndpointNameClashError.new(self, endpoint_name)
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
- ep = Endpoint.new(endpoint_name, configs)
30
- ApiRecipes.copy_global_authorizations_to_endpoint ep
31
- ApiRecipes._aprcps_thread_storage[endpoint_name] = {}
32
- ApiRecipes._aprcps_thread_storage[endpoint_name][self] = ep
33
-
34
- define_method endpoint_name do
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, self)
36
+ ApiRecipes.copy_global_authorizations_to_api api
37
+ api
42
38
  end
43
- define_singleton_method endpoint_name do
44
- unless ApiRecipes._aprcps_thread_storage[endpoint_name]
45
- ApiRecipes._aprcps_thread_storage[endpoint_name] = {}
46
- end
47
- unless ApiRecipes._aprcps_thread_storage[endpoint_name][self]
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, self)
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.copy_global_authorizations_to_endpoint(endpoint)
72
- if _aprcps_global_storage[endpoint.name]
73
- if auth = _aprcps_global_storage[endpoint.name].basic_auth
74
- endpoint.authorization = auth
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[endpoint.name].authorization
77
- endpoint.authorization = auth
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.set_authorization_for_endpoint(authorization, endpoint_name)
83
- endpoint_name = endpoint_name.to_sym
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[endpoint_name]
87
- _aprcps_thread_storage[endpoint_name].each do |_, endpoint|
88
- endpoint.authorization = authorization
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.set_basic_auth_for_endpoint(basic_auth, endpoint_name)
94
- endpoint_name = endpoint_name.to_sym
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[endpoint_name]
98
- _aprcps_thread_storage[endpoint_name].each do |_, endpoint|
99
- endpoint.authorization = basic_auth
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._aprcps_define_global_endpoints
105
- configuration.endpoints_configs.each do |endpoint_name, endpoint_configs|
106
- endpoint_name = endpoint_name.to_sym
107
- _aprcps_global_storage[endpoint_name] = Endpoint.new endpoint_name, endpoint_configs
108
- define_singleton_method endpoint_name do
109
- _aprcps_global_storage[endpoint_name]
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, self
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._aprcps_merge_endpoints_configs(endpoint_name, configs = nil)
129
- unless endpoint_name.is_a?(String) || endpoint_name.is_a?(Symbol)
130
- raise ArgumentError, "no enpoint_name provided. Given: #{endpoint_name.inspect}"
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.endpoints_configs[endpoint_name]
133
- ApiRecipes.configuration.endpoints_configs[endpoint_name] = {}
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.endpoints_configs[endpoint_name].merge(configs) do |_, old_val, new_val|
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.endpoints_configs[endpoint_name]
145
+ ApiRecipes.configuration.apis_configs[api_name]
145
146
  end
146
147
  end
147
148
  end
@@ -0,0 +1,71 @@
1
+ module ApiRecipes
2
+ class Api
3
+
4
+ attr_accessor :name, :configs, :authorization, :basic_auth
5
+ attr_reader :base_configs, :object
6
+
7
+ BASE_CONFIGS_KEYS = [:protocol, :host, :port, :api_version, :timeout, :on_bad_code, :verify_with]
8
+
9
+ def initialize(name, configs, object)
10
+ @name = name
11
+ @configs = ApiRecipes::Settings::DEFAULT.merge configs
12
+ @object = object
13
+
14
+ # Generate some_api.some_endpoint methods
15
+ # e.g. github.users
16
+ endpoints = @configs[:endpoints]
17
+ if endpoints && endpoints.is_a?(Hash)
18
+ endpoints.each do |ep_name, params|
19
+ unless respond_to? ep_name
20
+ # TODO: store endpoints, routes and routes_urls
21
+ define_singleton_method ep_name do |*request_params, &block|
22
+ # puts "API params: #{params}"
23
+ Endpoint.new api: self, name: ep_name, path: path, params: params, request_params: request_params, &block
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+
30
+ def authorization=(auth)
31
+ @authorization = auth
32
+
33
+ # Check if I'm the global api
34
+ if global?
35
+ # Set authorization also on every "children" (classes that define the same api)
36
+ ApiRecipes.set_authorization_for_api auth, name
37
+ end
38
+ end
39
+
40
+ def basic_auth=(auth)
41
+ @basic_auth = auth
42
+
43
+ # Check if I'm the global api
44
+ if global?
45
+ # Set authorization also on every "children" (classes that define the same api)
46
+ ApiRecipes.set_basic_auth_for_api auth, name
47
+ end
48
+ end
49
+
50
+ def base_configs
51
+ @configs.select { |c| BASE_CONFIGS_KEYS.include? c }
52
+ end
53
+
54
+ private
55
+
56
+ def check_route_name_does_not_clash(route_name)
57
+ # Check if a method named route_name has already been defined on this object
58
+ if respond_to? route_name
59
+ raise RouteNameClashWithExistentMethod.new(@name, route_name)
60
+ end
61
+ end
62
+
63
+ def global?
64
+ ApiRecipes._aprcps_global_storage[name] == self
65
+ end
66
+
67
+ def path
68
+ "/#{configs[:base_url]}/"
69
+ end
70
+ end
71
+ end
@@ -1,19 +1,19 @@
1
1
  module ApiRecipes
2
2
  class Configuration
3
3
 
4
- attr_accessor :log_to, :log_level
4
+ attr_accessor :log_to, :log_level, :print_urls
5
5
 
6
- def endpoints_configs=(configs = {})
7
- raise ArgumentError, 'endpoints_configs must be an Hash' unless configs.is_a? Hash
8
- @endpoints_configs = configs.deep_symbolize_keys
9
- ApiRecipes._aprcps_define_global_endpoints
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 endpoints_configs
13
- unless @endpoints_configs
14
- @endpoints_configs = {}
12
+ def apis_configs
13
+ unless @apis_configs
14
+ @apis_configs = {}
15
15
  end
16
- @endpoints_configs
16
+ @apis_configs
17
17
  end
18
18
 
19
19
  def logger=(logger)
@@ -1,51 +1,137 @@
1
1
  module ApiRecipes
2
2
  class Endpoint
3
3
 
4
- attr_accessor :name, :configs, :authorization, :basic_auth
5
- attr_reader :resources
4
+ attr_reader :api, :name, :params, :route, :children
6
5
 
7
- def initialize(name, configs)
6
+ FORWARDABLE_PARAMS = %i[verify_with].freeze
7
+
8
+ def initialize(api: nil, name: nil, path: nil, params: {}, request_params: [], &block)
9
+ @api = api
8
10
  @name = name
9
- @configs = ApiRecipes::Settings::DEFAULT.merge configs
10
-
11
- # Generate some_endpoint.some_resource methods
12
- # e.g. github.users
13
- @resources = [] unless @resources
14
- @configs[:routes].each do |resource, routes|
15
- @resources << resource
16
- res = Resource.new resource, self, routes
17
- unless respond_to? resource
18
- define_singleton_method resource do
19
- res
11
+ @path = path.to_s
12
+ new_params = params.dup || {}
13
+ self.params = new_params
14
+ @children = new_params.delete :endpoints
15
+ @route = nil
16
+ @request_params = request_params.extract_options!
17
+ self.path_params = request_params
18
+
19
+ generate_route
20
+ generate_children
21
+ if block_given?
22
+ run &block
23
+ end
24
+ end
25
+
26
+ def run(&block)
27
+ if @route
28
+ @route.start_request &block
29
+ else
30
+ raise NoRouteExists.new @name
31
+ end
32
+ end
33
+
34
+ def url
35
+ @route.url
36
+ end
37
+
38
+ private
39
+
40
+ def generate_route
41
+ # Check if we have to generate route for this endpoint
42
+ if create_route?
43
+ ensure_route_does_not_clash @name
44
+ # Generate route
45
+ attrs = params.dup
46
+ attrs.delete(:endpoints)
47
+ # puts "Generating route '#{@name}' with path '#{build_path}'"
48
+ @route = Route.new(api: @api, endpoint: self, path: build_path, attributes: attrs, req_pars: @request_params)
49
+ end
50
+ end
51
+
52
+ def generate_children
53
+ # Generate children endpoints if any
54
+ # puts "generating children of #{@name}: #{children.inspect}"
55
+ if children
56
+ children.each do |ep_name, pars|
57
+ pars = forwardable_params.merge(pars) if pars
58
+ # TODO: Merge pars with params
59
+ # puts "Creating Endpoint '#{@name}' child '#{ep_name}' passing path #{build_path}"
60
+ define_singleton_method ep_name do |*request_params, &block|
61
+ Endpoint.new api: @api, name: ep_name, path: build_path, params: pars, request_params: request_params, &block
20
62
  end
21
63
  end
22
64
  end
65
+ # Route.new(api: @api, endpoint: self, name: route_name, attributes: route_attrs, req_pars: request_params).start_request &block
66
+ end
67
+
68
+ def ensure_route_does_not_clash(route_name)
69
+ # Check if a method named route_name has already been defined on this object
70
+ if respond_to? route_name
71
+ raise RouteNameClashWithExistentMethod.new(@name, route_name)
72
+ end
23
73
  end
24
74
 
25
- def authorization=(auth)
26
- @authorization = auth
75
+ def create_route?
76
+ res = params[:route].eql?('yes') || params[:route].eql?(true)
77
+ res
78
+ end
79
+
80
+ def absolute_path
81
+ # Append path passed to initialize and (params[:path] || @name)
82
+ append = params[:path] || @name
83
+ unless append.empty?
84
+ "#{@path}/#{append}"
85
+ else
86
+ "#{@path}"
87
+ end.gsub(/\/+/, '/') # remove multiple consecutive '//'
88
+ end
27
89
 
28
- # Check if I'm the global endpoint
29
- if global?
30
- # Set authorization also on every "children" (classes that define the same endpoint)
31
- ApiRecipes.set_authorization_for_endpoint auth, name
90
+ def params=(attrs)
91
+ unless attrs.is_a? Hash
92
+ raise ArgumentError, "provided 'attrs' must be an Hash"
93
+ end
94
+ # Merge DEFAULT_ROUTE_ATTRIBUTES with Api base_configs
95
+ # Then merge the result with provided attributes
96
+ @params = Settings::DEFAULT_ROUTE_ATTRIBUTES.inject({}) do |out, key_val|
97
+ new_val = @api.base_configs[key_val.first]
98
+ out[key_val.first] = new_val.nil? ? key_val.last : new_val
99
+ out
100
+ end.inject({}) do |out, key_val|
101
+ new_val = attrs[key_val.first]
102
+ out[key_val.first] = new_val.nil? ? key_val.last : new_val
103
+ out
32
104
  end
33
105
  end
34
106
 
35
- def basic_auth=(auth)
36
- @basic_auth = auth
107
+ def build_path
108
+ final_path = absolute_path
109
+ # Check if provided path_params match with required path params
110
+ req_params = required_params_for_path
111
+ if @path_params.size != req_params.size
112
+ # puts "\nWARNING\n"
113
+ raise PathParamsMismatch.new(final_path, req_params, @path_params)
114
+ end
115
+ # Replace required_params present in path with params provided by user (@path_params)
116
+ @path_params.each { |par| final_path.sub! /(:[^\/]+)/, par.to_s }
117
+
118
+ final_path
119
+ end
37
120
 
38
- # Check if I'm the global endpoint
39
- if global?
40
- # Set authorization also on every "children" (classes that define the same endpoint)
41
- ApiRecipes.set_basic_auth_for_endpoint auth, name
121
+ def path_params=(params)
122
+ unless params.is_a? Array
123
+ raise ArgumentError, 'path params must be an Array'
42
124
  end
125
+ # Merge route attributes with defaults and deep clone route attributes
126
+ @path_params = params
43
127
  end
44
128
 
45
- private
129
+ def required_params_for_path
130
+ absolute_path.scan(/:(\w+)/).flatten.map { |p| p.to_sym }
131
+ end
46
132
 
47
- def global?
48
- ApiRecipes._aprcps_global_storage[name] == self
133
+ def forwardable_params
134
+ params.select { |k, v| FORWARDABLE_PARAMS.include? k }
49
135
  end
50
136
  end
51
137
  end