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 +5 -5
- data/lib/phenotype.rb +11 -3
- data/lib/phenotype/response_handler.rb +33 -0
- data/lib/phenotype/route_handler.rb +16 -0
- data/lib/phenotype/strategies/accept_header_strategy.rb +16 -0
- data/lib/phenotype/strategies/header_strategy.rb +15 -0
- data/lib/phenotype/strategies/null_strategy.rb +13 -0
- data/lib/phenotype/strategies/param_strategy.rb +15 -0
- data/lib/phenotype/strategies/path_strategy.rb +16 -0
- data/lib/phenotype/version.rb +5 -0
- data/lib/phenotype/versioned_app.rb +29 -0
- data/lib/phenotype/versioner.rb +94 -0
- metadata +43 -39
- data/lib/strategies/header_strategy.rb +0 -22
- data/lib/strategies/null_strategy.rb +0 -9
- data/lib/strategies/param_strategy.rb +0 -18
- data/lib/util/version.rb +0 -3
- data/lib/versioned_app.rb +0 -42
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: a74f97d1b04b395d3808fcc466e4d61f4111ea3ec70719da1a21853c6e429de8
|
4
|
+
data.tar.gz: 302fdceb8bb12142e35aa5c3129dfe956b58239eaea5acf6f7464f5697a09359
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: '09932bce3e0302a316b06a9125e458beb7ec36a08864bc7c5f70845e793f5ff8ca5ce7924816e85ae8fffb1f8a1e09a8e4ca0460467a90f4c39a6f0f1d16a673'
|
7
|
+
data.tar.gz: 2807a0e03379b19afdaceafd20b3ef0c038d7d1d1480121fe253667820650556565cf84a04247408a390d7ba6e6c63e6da460d83f97e043c734a3f5f6364381e
|
data/lib/phenotype.rb
CHANGED
@@ -1,4 +1,12 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
3
|
require 'rack'
|
4
|
-
require '
|
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 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 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,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.
|
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:
|
11
|
+
date: 2021-01-05 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name:
|
14
|
+
name: rack
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- - "
|
17
|
+
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '1
|
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
|
26
|
+
version: '1'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
|
-
name:
|
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.
|
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.
|
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:
|
56
|
+
name: rake
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
58
58
|
requirements:
|
59
|
-
- - "
|
59
|
+
- - ">="
|
60
60
|
- !ruby/object:Gem::Version
|
61
|
-
version: '
|
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: '
|
68
|
+
version: '10.5'
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
|
-
name:
|
70
|
+
name: rspec
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
72
72
|
requirements:
|
73
|
-
- - "
|
73
|
+
- - ">="
|
74
74
|
- !ruby/object:Gem::Version
|
75
|
-
version: '
|
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: '
|
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/
|
91
|
-
- lib/
|
92
|
-
- lib/strategies/
|
93
|
-
- lib/
|
94
|
-
- lib/
|
95
|
-
|
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
|
-
-
|
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: '
|
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
|
-
|
116
|
-
|
117
|
-
signing_key:
|
120
|
+
rubygems_version: 3.2.3
|
121
|
+
signing_key:
|
118
122
|
specification_version: 4
|
119
|
-
summary: phenotype-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,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
|
data/lib/util/version.rb
DELETED
data/lib/versioned_app.rb
DELETED
@@ -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
|