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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 41b256b43696edaa5735f827046946ee0ee438ec69fc90387cfc3c9a841828b8
4
- data.tar.gz: dcab748e71d294af415263f5e1c6a5c2c55374f782d34ec3e389069aaa132431
3
+ metadata.gz: febe7d307961dd407eae97ef0519280abf463e896f8a83471d3242b6f8a7b8a1
4
+ data.tar.gz: 15df723f847728e9c433223eef128069204aa15344856a4388847860c45304f2
5
5
  SHA512:
6
- metadata.gz: 42963a27d5644ea0e1b6afc22f393cfe5f9db95a8d2bfa83beffaeca7ee8bd59995c227c124b1ab0df059b950d175a74175fa05bde1c8a88f01b294c514bc4ba
7
- data.tar.gz: 0b5ea14d92e44fc67ea5b8af1d1debd654b43974c5b98c88c8ba45555567fb2980387a6fa5e2ed3dd9ff964b7981cd976c40005c65b3f51f6c181a60ac6f0607
6
+ metadata.gz: 94627389adf247362789b3213f6ec2f45c4443e43c4212ec1fffa96f4796c4de9c097288ef0d328d657c98eb8590fa6908e89f77f080c09be1b0cb0b9c7f44d9
7
+ data.tar.gz: a887790e2ac8c3ec7853d2a24735f6c79a7761287e76265854963e80f18320b63bb598365ba75fb316064ebf9c424569e09160f4dc3eb79ce4c09e6326e1972f
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
@@ -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/resource'
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(base)
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
- def base.endpoint(endpoint_name, configs = {})
16
- configs = ApiRecipes._aprcps_merge_endpoints_configs(endpoint_name, configs.deep_symbolize_keys)
17
- endpoint_name = endpoint_name.to_sym
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
- # Define 'endpoint_name' method for the class
20
- ApiRecipes._aprcps_define_class_endpoint endpoint_name, configs, self, true
21
- # Define 'endpoint_name' method for the class' instances
22
- ApiRecipes._aprcps_define_instance_endpoint endpoint_name, self
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._aprcps_define_global_endpoints
43
- configuration.endpoints_configs.each do |endpoint_name, endpoint_configs|
44
- unless method_defined? endpoint_name
45
- unless _aprcps_storage[endpoint_name]
46
- _aprcps_storage[endpoint_name] = Endpoint.new endpoint_name, endpoint_configs
47
- end
48
- define_singleton_method endpoint_name do
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._aprcps_storage
56
- unless Thread.current[:api_recipes]
57
- Thread.current[:api_recipes] = {}
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
- def self._aprcps_define_class_endpoint(ep_name, configs, obj, overwrite)
64
- unless obj.method_defined? ep_name
65
- if overwrite
66
- ep = Endpoint.new(ep_name, configs)
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._aprcps_define_instance_endpoint(ep_name, obj)
77
- obj.instance_eval do
78
- unless obj.method_defined? ep_name
79
- define_method ep_name do
80
- self.class.send ep_name
81
- end
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._aprcps_merge_endpoints_configs(endpoint_name, configs)
87
- if configs && !configs.is_a?(Hash)
88
- raise ApiRecipes::EndpointConfigIsNotAnHash.new(endpoint_name)
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.endpoints_configs[endpoint_name]
91
- ApiRecipes.configuration.endpoints_configs[endpoint_name] = {}
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
- def endpoints_configs=(configs = {})
5
- raise ArgumentError, 'endpoints_configs must be an Hash' unless configs.is_a? Hash
6
- @endpoints_configs = configs.deep_symbolize_keys
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 endpoints_configs
10
- unless @endpoints_configs
11
- @endpoints_configs = {}
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
@@ -1,25 +1,126 @@
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
+ def initialize(api: nil, name: nil, path: nil, params: {}, request_params: [], &block)
7
+ @api = api
8
8
  @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
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