phenotype 0.1.0 → 0.4.3

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
- 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