api_recipes 0.6.2 → 0.7.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: 409a786b8eff1b1fd48bdbb1f5c8a14cea860056581f86f0c994ce8436874f53
4
+ data.tar.gz: d8923466ea1f2109a8528e2d7e55d402c5ed2e45b714612081bda920d3e03fe4
5
5
  SHA512:
6
- metadata.gz: 7bfb47fbd9ac54c246068a42f768bdbfd9382ad2604d403c4ff1ffbae8d925136020b8fee18e39c0200dcc4ec2bfe3cb92fc594e2c4a6f8a8ef3d22bc6260c07
7
- data.tar.gz: 42e766ed0288f8487a0645006d5d054f072884043e563f9e0e89aaf670d02223efff7c790d028991655674e47151e12da9c725668914f5356b32586ac3e2dce4
6
+ metadata.gz: 70aa364742f8dc27d38c1fa0f5d9fec9a0c6b294421fac1413ed34379b39f2c28ae4cb993d51f67bfa4ddda16fe6b8074fe6646680b7a3fcb45c8d98e3f75d5b
7
+ data.tar.gz: fd4885cd695a4356c4f57417f77bc914a9b0104372d8eb37fb6d0531831348e9da38f2d99a394fbdc9802cd9a20bc99b3289cf4c18f92683b0fd9f9644d5f555
data/Gemfile CHANGED
@@ -1,16 +1,12 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
- # Specify your gem's dependencies in api_recipes.gemspec
4
3
  gemspec
5
4
 
6
- gem 'rake', '~> 10.0'
7
-
8
- group :development do
9
- gem 'guard', '~> 2.13.0'
10
- gem 'guard-rspec', '~> 4.6.4'
11
- gem 'guard-bundler', '~> 2.1.0'
12
- end
5
+ gem 'rake', '~> 12.3'
13
6
 
14
7
  group :test do
15
- gem 'rspec', '~> 3.3'
8
+ gem 'rspec', '~> 3.8.0'
9
+ gem 'guard', '~> 2.15.0'
10
+ gem 'guard-rspec', '~> 4.7.3'
11
+ gem 'guard-bundler', '~> 2.2.1'
16
12
  end
data/Guardfile CHANGED
@@ -2,7 +2,6 @@ guard :rspec, cmd: 'bundle exec rspec' do
2
2
  require 'guard/rspec/dsl'
3
3
  dsl = Guard::RSpec::Dsl.new(self)
4
4
 
5
-
6
5
  # RSpec files
7
6
  rspec = dsl.rspec
8
7
  watch(rspec.spec_helper) { rspec.spec_dir }
data/api_recipes.gemspec CHANGED
@@ -16,8 +16,8 @@ Gem::Specification.new do |spec|
16
16
  spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
17
17
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
18
18
  spec.require_paths = ['lib']
19
- spec.required_ruby_version = '>= 2.1.0'
19
+ spec.required_ruby_version = '> 2.2.0'
20
20
 
21
- spec.add_dependency 'oj', '~> 3.5.0'
22
- spec.add_dependency 'http', '~> 2.0.3'
21
+ spec.add_dependency 'oj', '~> 3.7.0'
22
+ spec.add_dependency 'http', '~> 4.1.1'
23
23
  end
data/lib/api_recipes.rb CHANGED
@@ -10,16 +10,46 @@ require 'api_recipes/settings'
10
10
 
11
11
  module ApiRecipes
12
12
 
13
- def self.included(base)
13
+ def self.included(receiver)
14
14
 
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
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
+ end
18
19
 
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
20
+ if configs && !configs.is_a?(Hash)
21
+ raise ApiRecipes::EndpointConfigIsNotAnHash.new(endpoint_name)
22
+ end
23
+
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)
28
+ 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]
42
+ 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]
51
+ end
52
+ end
23
53
  end
24
54
  end
25
55
 
@@ -33,65 +63,86 @@ module ApiRecipes
33
63
  def self.configure
34
64
  if block_given?
35
65
  yield(configuration)
36
- _aprcps_define_global_endpoints
37
66
  else
38
67
  configuration
39
68
  end
40
69
  end
41
70
 
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
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
75
+ end
76
+ if auth = _aprcps_global_storage[endpoint.name].authorization
77
+ endpoint.authorization = auth
51
78
  end
52
79
  end
53
80
  end
54
81
 
82
+ def self.set_authorization_for_endpoint(authorization, endpoint_name)
83
+ endpoint_name = endpoint_name.to_sym
55
84
 
56
- def self._aprcps_storage
57
- unless Thread.current[:api_recipes]
58
- Thread.current[:api_recipes] = {}
85
+ # 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
89
+ end
59
90
  end
60
- Thread.current[:api_recipes]
61
91
  end
62
92
 
93
+ def self.set_basic_auth_for_endpoint(basic_auth, endpoint_name)
94
+ endpoint_name = endpoint_name.to_sym
63
95
 
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
96
+ # 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
73
100
  end
74
101
  end
75
102
  end
76
103
 
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
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]
83
110
  end
84
111
  end
85
112
  end
86
113
 
87
- def self._aprcps_merge_endpoints_configs(endpoint_name, configs)
88
- if configs && !configs.is_a?(Hash)
89
- raise ApiRecipes::EndpointConfigIsNotAnHash.new(endpoint_name)
114
+ def self._aprcps_global_storage
115
+ unless @storage
116
+ @storage = {}
117
+ end
118
+ @storage
119
+ end
120
+
121
+ def self._aprcps_thread_storage
122
+ unless Thread.current[:api_recipes]
123
+ Thread.current[:api_recipes] = {}
124
+ end
125
+ Thread.current[:api_recipes]
126
+ end
127
+
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}"
90
131
  end
91
132
  unless ApiRecipes.configuration.endpoints_configs[endpoint_name]
92
133
  ApiRecipes.configuration.endpoints_configs[endpoint_name] = {}
93
134
  end
94
- ApiRecipes.configuration.endpoints_configs[endpoint_name].merge configs
135
+ if configs
136
+ ApiRecipes.configuration.endpoints_configs[endpoint_name].merge(configs) do |_, old_val, new_val|
137
+ if new_val.nil?
138
+ old_val
139
+ else
140
+ new_val
141
+ end
142
+ end
143
+ else
144
+ ApiRecipes.configuration.endpoints_configs[endpoint_name]
145
+ end
95
146
  end
96
147
  end
97
148
 
@@ -1,9 +1,12 @@
1
1
  module ApiRecipes
2
2
  class Configuration
3
3
 
4
+ attr_accessor :log_to, :log_level
5
+
4
6
  def endpoints_configs=(configs = {})
5
7
  raise ArgumentError, 'endpoints_configs must be an Hash' unless configs.is_a? Hash
6
8
  @endpoints_configs = configs.deep_symbolize_keys
9
+ ApiRecipes._aprcps_define_global_endpoints
7
10
  end
8
11
 
9
12
  def endpoints_configs
@@ -12,5 +15,35 @@ module ApiRecipes
12
15
  end
13
16
  @endpoints_configs
14
17
  end
18
+
19
+ def logger=(logger)
20
+ @logger = logger
21
+ end
22
+
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
46
+ end
47
+ end
15
48
  end
16
49
  end
@@ -21,5 +21,31 @@ module ApiRecipes
21
21
  end
22
22
  end
23
23
  end
24
+
25
+ def authorization=(auth)
26
+ @authorization = auth
27
+
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
32
+ end
33
+ end
34
+
35
+ def basic_auth=(auth)
36
+ @basic_auth = auth
37
+
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
42
+ end
43
+ end
44
+
45
+ private
46
+
47
+ def global?
48
+ ApiRecipes._aprcps_global_storage[name] == self
49
+ end
24
50
  end
25
51
  end
@@ -1,16 +1,15 @@
1
1
  module ApiRecipes
2
- class RouteNameClashError < Exception
3
- attr_reader :route, :resource
4
2
 
5
- def initialize(message = nil, route = nil, resource = nil)
6
- @route = route; @resource = resource
7
- if message
8
- # Nothing to do
9
- elsif route
10
- message = "route name (#{@route}) can't be equal to resource name (#{@resource}). Please change route or resource name."
11
- else
12
- message = "route name can't be equal to resource name. Please change route or resource names."
13
- end
3
+ class EndpointNameClashError < Exception
4
+ def initialize(object, endpoint_name)
5
+ message = "#{object.class} already defines a method called '#{endpoint_name}'. Tip: change endpoint name"
6
+ super(message)
7
+ end
8
+ end
9
+
10
+ class RouteAndResourceNamesClashError < Exception
11
+ def initialize(route_name, resource_name)
12
+ message = "route name (#{route_name}) can't be equal to resource name (#{resource_name}). Please change route or resource name."
14
13
  super(message)
15
14
  end
16
15
  end
@@ -44,15 +43,8 @@ module ApiRecipes
44
43
  end
45
44
 
46
45
  class EndpointConfigIsNotAnHash < Exception
47
- attr_reader :endpoint
48
-
49
- def initialize(message = nil, endpoint = nil)
50
- @endpoint = endpoint
51
- if message
52
- # Nothing to do
53
- else
54
- message = "provided config for endpoint '#{@endpoint}' must be an Hash"
55
- end
46
+ def initialize(endpoint)
47
+ message = "provided config for endpoint '#{endpoint}' must be an Hash"
56
48
  super(message)
57
49
  end
58
50
  end
@@ -1,8 +1,6 @@
1
1
  module ApiRecipes
2
2
  class Resource
3
3
 
4
- attr_accessor :authorization, :basic_auth
5
-
6
4
  def initialize(name, endpoint, routes = {})
7
5
  @name = name
8
6
  @routes = routes
@@ -81,8 +79,9 @@ module ApiRecipes
81
79
  # e.g. webapp.alarms.index
82
80
  def generate_routes
83
81
  @routes.each do |route, attrs|
82
+ # Check if route name clashes with resource name
84
83
  if route.eql? @name
85
- raise RouteNameClashError.new(route, @name)
84
+ raise RouteAndResourceNamesClashError.new(route, @name)
86
85
  end
87
86
  unless respond_to? route.to_sym
88
87
  define_singleton_method route.to_sym do |*params, &block|
@@ -95,8 +94,8 @@ module ApiRecipes
95
94
  self
96
95
  end
97
96
 
98
- def per_kind_timeout
99
- settings.fetch(:timeout, ApiRecipes::Settings::GLOBAL_TIMEOUT)/3.0
97
+ def timeout
98
+ settings.fetch(:timeout, ApiRecipes::Settings::GLOBAL_TIMEOUT)
100
99
  end
101
100
 
102
101
  def port
@@ -111,22 +110,15 @@ module ApiRecipes
111
110
  def request_with_auth
112
111
  req = HTTP
113
112
  req = req.headers(extract_headers)
114
- .timeout(
115
- :global,
116
- write: per_kind_timeout,
117
- connect: per_kind_timeout,
118
- read: per_kind_timeout
119
- )
113
+ .timeout(timeout)
120
114
 
115
+ basic_auth = @endpoint.basic_auth
121
116
  if basic_auth
122
117
  req = req.basic_auth basic_auth
123
- elsif ba = @endpoint.basic_auth
124
- req = req.basic_auth ba
125
118
  end
119
+ authorization = @endpoint.authorization
126
120
  if authorization
127
121
  req = req.auth authorization
128
- elsif auth = @endpoint.authorization
129
- req = req.auth auth
130
122
  end
131
123
  req
132
124
  end
@@ -1,3 +1,3 @@
1
1
  module ApiRecipes
2
- VERSION = '0.6.2'
2
+ VERSION = '0.7.0'
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: api_recipes
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.2
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alessandro Verlato
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-06-27 00:00:00.000000000 Z
11
+ date: 2019-07-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: oj
@@ -16,28 +16,28 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: 3.5.0
19
+ version: 3.7.0
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: 3.5.0
26
+ version: 3.7.0
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: http
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: 2.0.3
33
+ version: 4.1.1
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: 2.0.3
40
+ version: 4.1.1
41
41
  description:
42
42
  email:
43
43
  - averlato@gmail.com
@@ -85,9 +85,9 @@ require_paths:
85
85
  - lib
86
86
  required_ruby_version: !ruby/object:Gem::Requirement
87
87
  requirements:
88
- - - ">="
88
+ - - ">"
89
89
  - !ruby/object:Gem::Version
90
- version: 2.1.0
90
+ version: 2.2.0
91
91
  required_rubygems_version: !ruby/object:Gem::Requirement
92
92
  requirements:
93
93
  - - ">="