phenotype 0.1.0 → 0.4.3

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
- SHA1:
3
- metadata.gz: 88fac70aca1a4fc9f4d1877d8a14b81f2644ea36
4
- data.tar.gz: 5fde94d65d7dbc38e818edb36d71fb93cce8f375
2
+ SHA256:
3
+ metadata.gz: a74f97d1b04b395d3808fcc466e4d61f4111ea3ec70719da1a21853c6e429de8
4
+ data.tar.gz: 302fdceb8bb12142e35aa5c3129dfe956b58239eaea5acf6f7464f5697a09359
5
5
  SHA512:
6
- metadata.gz: 580a7073acd4bff168cbe82f8185ef39dac23b288cf593cc95c6d1ef53213eb4c91ded498b1204c8072747f8e1c4d50248b5a76e2c63a844ff59e22784b230ee
7
- data.tar.gz: 99c51dadf8523327c509b3de9c7366638fd52fddbcda70f43282f2f21f4b32943b9d3933d87b1641ee5fc2ebeb08b14b827ec19f83d9f8528dd981352d3e42c9
6
+ metadata.gz: '09932bce3e0302a316b06a9125e458beb7ec36a08864bc7c5f70845e793f5ff8ca5ce7924816e85ae8fffb1f8a1e09a8e4ca0460467a90f4c39a6f0f1d16a673'
7
+ data.tar.gz: 2807a0e03379b19afdaceafd20b3ef0c038d7d1d1480121fe253667820650556565cf84a04247408a390d7ba6e6c63e6da460d83f97e043c734a3f5f6364381e
@@ -1,4 +1,12 @@
1
- require 'require_all'
2
- require_rel 'strategies'
1
+ # frozen_string_literal: true
2
+
3
3
  require 'rack'
4
- require 'versioned_app'
4
+ require 'phenotype/strategies/accept_header_strategy'
5
+ require 'phenotype/strategies/header_strategy'
6
+ require 'phenotype/strategies/param_strategy'
7
+ require 'phenotype/strategies/path_strategy'
8
+ require 'phenotype/strategies/null_strategy'
9
+ require 'phenotype/versioned_app'
10
+
11
+ module Phenotype
12
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phenotype
4
+ class ResponseHandler
5
+ attr_reader :response
6
+
7
+ def initialize(response)
8
+ @response = response
9
+ end
10
+
11
+ def retry?
12
+ valid_response? && not_found? && cascade?
13
+ end
14
+
15
+ private
16
+
17
+ def valid_response?
18
+ response.is_a?(Array) && rack_response?
19
+ end
20
+
21
+ def rack_response?
22
+ response[0].is_a?(Numeric) && response[1].is_a?(Hash) && response[2].is_a?(Array)
23
+ end
24
+
25
+ def not_found?
26
+ response.first.to_i == 404
27
+ end
28
+
29
+ def cascade?
30
+ response[1]['X-Cascade'].to_s == 'pass'
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phenotype
4
+ class RouteHandler
5
+ attr_reader :cascade, :block
6
+
7
+ def initialize(block:, cascade: false)
8
+ @cascade = cascade
9
+ @block = block
10
+ end
11
+
12
+ def cascade?
13
+ cascade
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phenotype
4
+ class AcceptHeaderStrategy
5
+ attr_reader :mime_type
6
+
7
+ def initialize(mime_type = 'application/vnd.rentpath.api+json')
8
+ @mime_type = mime_type
9
+ end
10
+
11
+ def version(env)
12
+ env['HTTP_ACCEPT'].to_s.match(/#{Regexp.quote(mime_type)};version=(?<version>\d+)/) { |match| match[:version] } ||
13
+ NullStrategy.new
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phenotype
4
+ class HeaderStrategy
5
+ attr_reader :header
6
+
7
+ def initialize(header: 'Sue')
8
+ @header = header
9
+ end
10
+
11
+ def version(env)
12
+ env["HTTP_#{header.upcase}"] || NullStrategy.new
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phenotype
4
+ class NullStrategy
5
+ def version(_env)
6
+ self
7
+ end
8
+
9
+ def to_i
10
+ nil
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phenotype
4
+ class ParamStrategy
5
+ attr_reader :param
6
+
7
+ def initialize(param = 'v')
8
+ @param = param
9
+ end
10
+
11
+ def version(env)
12
+ Rack::Request.new(env).params[param] || NullStrategy.new
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phenotype
4
+ class PathStrategy
5
+ attr_reader :version_pattern
6
+
7
+ def initialize(version_pattern = Regexp.new('^\/v(\d+)\/?'))
8
+ @version_pattern = version_pattern
9
+ end
10
+
11
+ def version(env)
12
+ match = Rack::Request.new(env).path.match(version_pattern)
13
+ match && match[1] || NullStrategy.new
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phenotype
4
+ VERSION = '0.4.3'
5
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'versioner'
4
+ require_relative 'route_handler'
5
+
6
+ module Phenotype
7
+ class VersionedApp
8
+ attr_accessor :strategies, :routes
9
+
10
+ def initialize(strategies: [])
11
+ @routes = {}
12
+ @strategies = strategies
13
+ end
14
+
15
+ def add_strategy(strategy)
16
+ strategies.push(strategy)
17
+ end
18
+
19
+ def add_version(version:, cascade: false, &block)
20
+ raise 'Error - Phenotype version must be numeric' unless version.is_a?(Numeric)
21
+
22
+ routes[version] = RouteHandler.new(cascade: cascade, block: block)
23
+ end
24
+
25
+ def call(env)
26
+ Versioner.new(routes: routes, strategies: strategies, env: env).call
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,94 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'response_handler'
4
+
5
+ module Phenotype
6
+ class Versioner
7
+ attr_reader :routes, :versions, :strategies, :env
8
+
9
+ def initialize(routes: {}, strategies: [], env: {})
10
+ @routes = routes
11
+ @versions = routes.keys.sort.reverse
12
+ @strategies = strategies
13
+ @env = env
14
+ end
15
+
16
+ def call # rubocop:disable Metrics/AbcSize
17
+ return display_errors if errors?
18
+
19
+ cascading_versions.each do |v|
20
+ if strategies.first.instance_of?(PathStrategy) && version_mismatch?(v)
21
+ env['PATH_INFO'] = updated_versioned_path(env['PATH_INFO'], v)
22
+ end
23
+ route = call_route(v)
24
+ handler = ResponseHandler.new(route.block.call(env))
25
+ return handler.response unless route.cascade? && handler.retry?
26
+ end
27
+ end
28
+
29
+ private
30
+
31
+ def version_mismatch?(version)
32
+ path_version && (path_version != version.to_s)
33
+ end
34
+
35
+ def path_version
36
+ path = Rack::Request.new(env).path
37
+ match = path.match(strategies.first.version_pattern)
38
+ match&.captures&.first
39
+ end
40
+
41
+ def updated_versioned_path(path, version)
42
+ path.sub(path_version, version.to_s)
43
+ end
44
+
45
+ def errors?
46
+ strategies.empty? || too_many_strategies? || !supported_version?
47
+ end
48
+
49
+ def display_errors
50
+ return no_strategy_response.call(env) if strategies.empty?
51
+ return too_many_strategies_response.call(env) if too_many_strategies?
52
+ return version_not_found_response.call(env) unless supported_version?
53
+ end
54
+
55
+ def too_many_strategies?
56
+ @too_many_strategies ||= strategies.size > 1
57
+ end
58
+
59
+ def supported_version?
60
+ @supported_version ||= versions.include?(current_version)
61
+ end
62
+
63
+ def current_version
64
+ @current_version ||= strategies.detect { |strat| strat.version(env) }.version(env).to_i
65
+ end
66
+
67
+ def cascading_versions
68
+ @cascading_versions ||= versions.select { |el| el <= current_version }
69
+ end
70
+
71
+ def no_strategy_response
72
+ @no_strategy_response ||= default_response(406, 'No Strategies provided')
73
+ end
74
+
75
+ def too_many_strategies_response
76
+ @too_many_strategies_response ||= default_response(400, 'Too mant strategies provided, Supports only one')
77
+ end
78
+
79
+ def version_not_found_response(message = "Version: #{current_version} is not found")
80
+ default_response(406, message)
81
+ end
82
+
83
+ def call_route(version)
84
+ routes.fetch(
85
+ version,
86
+ version_not_found_response("Unable to satisfy request for Version: #{current_version}")
87
+ )
88
+ end
89
+
90
+ def default_response(status_code, message = '')
91
+ proc { [status_code, { 'Content-Type' => 'text/html' }, [message]] }
92
+ end
93
+ end
94
+ end
metadata CHANGED
@@ -1,102 +1,107 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: phenotype
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.4.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rentpath
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-03-28 00:00:00.000000000 Z
11
+ date: 2021-01-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: require_all
14
+ name: rack
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - "~>"
17
+ - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: '1.3'
19
+ version: '1'
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: '1.3'
26
+ version: '1'
27
27
  - !ruby/object:Gem::Dependency
28
- name: rack
28
+ name: require_all
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - "~>"
31
+ - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: '1.6'
33
+ version: '1.3'
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: '1.6'
40
+ version: '1.3'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: pry
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - "~>"
45
+ - - ">="
46
46
  - !ruby/object:Gem::Version
47
47
  version: '0.10'
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
- - - "~>"
52
+ - - ">="
53
53
  - !ruby/object:Gem::Version
54
54
  version: '0.10'
55
55
  - !ruby/object:Gem::Dependency
56
- name: rspec
56
+ name: rake
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
- - - "~>"
59
+ - - ">="
60
60
  - !ruby/object:Gem::Version
61
- version: '3.4'
61
+ version: '10.5'
62
62
  type: :development
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
- - - "~>"
66
+ - - ">="
67
67
  - !ruby/object:Gem::Version
68
- version: '3.4'
68
+ version: '10.5'
69
69
  - !ruby/object:Gem::Dependency
70
- name: rake
70
+ name: rspec
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
- - - "~>"
73
+ - - ">="
74
74
  - !ruby/object:Gem::Version
75
- version: '10.5'
75
+ version: '3.4'
76
76
  type: :development
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
- - - "~>"
80
+ - - ">="
81
81
  - !ruby/object:Gem::Version
82
- version: '10.5'
82
+ version: '3.4'
83
83
  description: versioning gem that works with rails and rack
84
- email:
84
+ email:
85
85
  executables: []
86
86
  extensions: []
87
87
  extra_rdoc_files: []
88
88
  files:
89
89
  - lib/phenotype.rb
90
- - lib/strategies/header_strategy.rb
91
- - lib/strategies/null_strategy.rb
92
- - lib/strategies/param_strategy.rb
93
- - lib/util/version.rb
94
- - lib/versioned_app.rb
95
- homepage: ''
90
+ - lib/phenotype/response_handler.rb
91
+ - lib/phenotype/route_handler.rb
92
+ - lib/phenotype/strategies/accept_header_strategy.rb
93
+ - lib/phenotype/strategies/header_strategy.rb
94
+ - lib/phenotype/strategies/null_strategy.rb
95
+ - lib/phenotype/strategies/param_strategy.rb
96
+ - lib/phenotype/strategies/path_strategy.rb
97
+ - lib/phenotype/version.rb
98
+ - lib/phenotype/versioned_app.rb
99
+ - lib/phenotype/versioner.rb
100
+ homepage: https://github.com/rentpath/phenotype
96
101
  licenses:
97
- - NA
102
+ - MIT
98
103
  metadata: {}
99
- post_install_message:
104
+ post_install_message:
100
105
  rdoc_options:
101
106
  - "--charset=UTF-8"
102
107
  require_paths:
@@ -105,16 +110,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
105
110
  requirements:
106
111
  - - ">="
107
112
  - !ruby/object:Gem::Version
108
- version: '0'
113
+ version: '2.4'
109
114
  required_rubygems_version: !ruby/object:Gem::Requirement
110
115
  requirements:
111
116
  - - ">="
112
117
  - !ruby/object:Gem::Version
113
118
  version: '0'
114
119
  requirements: []
115
- rubyforge_project:
116
- rubygems_version: 2.4.5.1
117
- signing_key:
120
+ rubygems_version: 3.2.3
121
+ signing_key:
118
122
  specification_version: 4
119
- summary: phenotype-0.1.0
123
+ summary: phenotype-0.4.3
120
124
  test_files: []
@@ -1,22 +0,0 @@
1
- module Phenotype
2
- class HeaderStrategy
3
- attr_reader :header, :header_param, :env
4
- def initialize(header: 'Sue')
5
- @header = header
6
- end
7
-
8
- def get_version(env)
9
- @env = env
10
- version
11
- end
12
-
13
- def version
14
- @version = env[header_key]
15
- end
16
-
17
- private
18
- def header_key
19
- "HTTP_#{header.upcase}"
20
- end
21
- end
22
- end
@@ -1,9 +0,0 @@
1
- module Phenotype
2
- class NullStrategy
3
- def get_version(env)
4
- end
5
-
6
- def version
7
- end
8
- end
9
- end
@@ -1,18 +0,0 @@
1
- require 'rack'
2
- module Phenotype
3
- class ParamStrategy
4
- attr_reader :param, :request
5
- def initialize(param = 'v')
6
- @param = param
7
- end
8
-
9
- def get_version(env)
10
- @request = Rack::Request.new(env)
11
- version
12
- end
13
-
14
- def version
15
- @version ||= request.params[param]
16
- end
17
- end
18
- end
@@ -1,3 +0,0 @@
1
- module Phenotype
2
- VERSION = '0.1.0'
3
- end
@@ -1,42 +0,0 @@
1
- module Phenotype
2
- class VersionedApp
3
- attr_accessor :strategies, :default_routes, :routes
4
- def initialize(strategies: [])
5
- @routes = {}
6
- @strategies = strategies
7
- @default_routes = null_default
8
- end
9
-
10
- def add_strategy(strat)
11
- strategies.push(strat)
12
- end
13
-
14
- def add_version(version: nil, default: false, &block)
15
- @default_routes = block if default
16
- routes[version] = block
17
- end
18
-
19
- def call(env)
20
- strategy = get_strategy(env)
21
- return default_routes.call(env) unless strategy.version
22
- call_route(strategy.version).call(env)
23
- end
24
-
25
- private
26
- def null_default
27
- Proc.new { [404,
28
- { 'Content-Type' => 'text/html', 'Content-Length' => "404".size.to_s},
29
- ["404"]]
30
- }
31
- end
32
-
33
- def call_route(version)
34
- routes.fetch(version, default_routes)
35
- end
36
-
37
- def get_strategy(env)
38
- strategy = strategies.find { |strat| strat.get_version(env) }
39
- strategy || NullStrategy.new
40
- end
41
- end
42
- end