api_recipes 0.6.2 → 2.6.0

Sign up to get free protection for your applications and to get access to all the features.
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