api_recipes 0.6.2 → 2.6.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: 2f6c660a507389c702d45be8f603b3836aa36173a71e74d0def460c7827b4e20
4
- data.tar.gz: 69573a634db4da03e59902f147cd725a1035ddf7ba6f082b215943fddc784582
3
+ metadata.gz: 977a895fe8c06312c73575391e759f6bd23c91fbdad1d26dfb5bb66dfc494b09
4
+ data.tar.gz: 507c600f1ab08ea1bcec035d8a5e60178da5d08c4e5f49beb84fba64829f39b5
5
5
  SHA512:
6
- metadata.gz: 7bfb47fbd9ac54c246068a42f768bdbfd9382ad2604d403c4ff1ffbae8d925136020b8fee18e39c0200dcc4ec2bfe3cb92fc594e2c4a6f8a8ef3d22bc6260c07
7
- data.tar.gz: 42e766ed0288f8487a0645006d5d054f072884043e563f9e0e89aaf670d02223efff7c790d028991655674e47151e12da9c725668914f5356b32586ac3e2dce4
6
+ metadata.gz: a4bdfe19e5994165e5d2ab7256b66887c54951f2df93cd44417b4c019f10979532188ff21f581fbfb5ff8528c9d04063eda93326cefeda396a26a4d397c5d5dd
7
+ data.tar.gz: b67d63a82f8199e9c38bf043a0d3ba192d7613b34a3955d7aef0ef6df0d5a20017fce3be9083be8e0a697a6e96d7fcdfecb37b0aed3f43742f3600c7deb429cc
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)
14
17
 
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
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
- # 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
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._aprcps_define_global_endpoints
43
- configuration.endpoints_configs.each do |endpoint_name, endpoint_configs|
44
- unless method_defined? endpoint_name
45
- define_singleton_method endpoint_name do
46
- unless _aprcps_storage[endpoint_name]
47
- _aprcps_storage[endpoint_name] = Endpoint.new endpoint_name, endpoint_configs
48
- end
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
- def self._aprcps_storage
57
- unless Thread.current[:api_recipes]
58
- Thread.current[:api_recipes] = {}
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
- def self._aprcps_define_class_endpoint(ep_name, configs, obj, overwrite)
65
- unless obj.method_defined? ep_name
66
- if overwrite
67
- ep = Endpoint.new(ep_name, configs)
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._aprcps_define_instance_endpoint(ep_name, obj)
78
- obj.instance_eval do
79
- unless obj.method_defined? ep_name
80
- define_method ep_name do
81
- self.class.send ep_name
82
- 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]
83
111
  end
84
112
  end
85
113
  end
86
114
 
87
- def self._aprcps_merge_endpoints_configs(endpoint_name, configs)
88
- if configs && !configs.is_a?(Hash)
89
- 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] = {}
90
125
  end
91
- unless ApiRecipes.configuration.endpoints_configs[endpoint_name]
92
- ApiRecipes.configuration.endpoints_configs[endpoint_name] = {}
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
- 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, :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 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,130 @@
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
+ 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