api_recipes 0.6.2 → 0.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: 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
  - - ">="