versioncake 2.5.0 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (112) hide show
  1. checksums.yaml +6 -14
  2. data/.rspec +2 -0
  3. data/.travis.yml +4 -0
  4. data/Appraisals +12 -0
  5. data/CHANGELOG.md +18 -1
  6. data/CONTRIBUTING.md +2 -2
  7. data/Gemfile.lock +96 -44
  8. data/README.md +79 -29
  9. data/RELEASE.md +1 -1
  10. data/Rakefile +3 -7
  11. data/gemfiles/rails3.2.gemfile +2 -1
  12. data/gemfiles/rails3.2.gemfile.lock +34 -10
  13. data/gemfiles/rails4.0.gemfile +2 -1
  14. data/gemfiles/rails4.0.gemfile.lock +31 -7
  15. data/gemfiles/rails4.1.gemfile +1 -1
  16. data/gemfiles/rails4.1.gemfile.lock +28 -8
  17. data/gemfiles/rails4.2.gemfile +9 -0
  18. data/gemfiles/rails4.2.gemfile.lock +133 -0
  19. data/lib/generators/templates/versioncake.rb +40 -0
  20. data/lib/generators/versioncake/install_generator.rb +12 -0
  21. data/lib/versioncake.rb +24 -3
  22. data/lib/versioncake/configuration.rb +19 -2
  23. data/lib/versioncake/controller_additions.rb +35 -23
  24. data/lib/versioncake/engine.rb +7 -0
  25. data/lib/versioncake/exceptions.rb +4 -0
  26. data/lib/versioncake/rack/middleware.rb +20 -0
  27. data/lib/versioncake/strategies/extraction_strategy.rb +8 -3
  28. data/lib/versioncake/strategies/http_accept_parameter_strategy.rb +2 -2
  29. data/lib/versioncake/strategies/http_header_strategy.rb +2 -2
  30. data/lib/versioncake/strategies/path_parameter_strategy.rb +6 -2
  31. data/lib/versioncake/strategies/query_parameter_strategy.rb +2 -2
  32. data/lib/versioncake/strategies/request_parameter_strategy.rb +2 -2
  33. data/lib/versioncake/test_helpers.rb +14 -0
  34. data/lib/versioncake/version.rb +1 -1
  35. data/lib/versioncake/version_checker.rb +28 -0
  36. data/lib/versioncake/version_context.rb +20 -0
  37. data/lib/versioncake/version_context_service.rb +47 -0
  38. data/lib/versioncake/versioned_request.rb +22 -33
  39. data/lib/versioncake/versioned_resource.rb +14 -0
  40. data/lib/versioncake/view_additions.rb +7 -7
  41. data/{test → spec}/fixtures/partials/_versioned.erb +0 -0
  42. data/{test → spec}/fixtures/partials/_versioned.v1.erb +0 -0
  43. data/{test → spec}/fixtures/partials/_versioned.v2.erb +0 -0
  44. data/{test → spec}/fixtures/partials/_versioned.v3.erb +0 -0
  45. data/{test → spec}/fixtures/partials/another_versioned_partial.erb +0 -0
  46. data/{test → spec}/fixtures/partials/another_versioned_partial.v1.erb +0 -0
  47. data/{test → spec}/fixtures/templates/unversioned.html.erb +0 -0
  48. data/{test → spec}/fixtures/templates/v1_extension_scheme.v3.html.erb +0 -0
  49. data/{test → spec}/fixtures/templates/v1_extension_scheme.v6.json +0 -0
  50. data/{test → spec}/fixtures/templates/versioned.html.erb +0 -0
  51. data/{test → spec}/fixtures/templates/versioned.html.v1.erb +0 -0
  52. data/{test → spec}/fixtures/templates/versioned.html.v2.erb +0 -0
  53. data/{test → spec}/fixtures/templates/versioned.html.v3.erb +0 -0
  54. data/spec/fixtures/test_cases.yml +45 -0
  55. data/spec/integration/controller/renders_controller_spec.rb +73 -0
  56. data/spec/integration/controller/unversioned_controller_spec.rb +12 -0
  57. data/spec/integration/rack/middleware_regression_spec.rb +41 -0
  58. data/spec/integration/view/render_spec.rb +33 -0
  59. data/spec/integration/view/view_additions_spec.rb +51 -0
  60. data/spec/rails_helper.rb +41 -0
  61. data/spec/spec_helper.rb +25 -0
  62. data/spec/test_app/Rakefile +7 -0
  63. data/{test → spec/test_app}/app/controllers/renders_controller.rb +0 -0
  64. data/spec/test_app/app/controllers/unversioned_controller.rb +6 -0
  65. data/spec/test_app/app/views/renders/index.html.erb +1 -0
  66. data/{test → spec/test_app}/app/views/renders/index.html.v1.erb +0 -0
  67. data/{test → spec/test_app}/app/views/renders/index.html.v2.erb +0 -0
  68. data/spec/test_app/app/views/unversioned/index.html.erb +1 -0
  69. data/spec/test_app/config.ru +4 -0
  70. data/{test → spec/test_app}/config/application.rb +4 -6
  71. data/spec/test_app/config/boot.rb +5 -0
  72. data/spec/test_app/config/environment.rb +5 -0
  73. data/spec/test_app/config/initializers/versioncake.rb +45 -0
  74. data/spec/test_app/config/routes.rb +4 -0
  75. data/spec/test_app/script/rails +4 -0
  76. data/spec/unit/cli_spec.rb +36 -0
  77. data/spec/unit/configuration_spec.rb +61 -0
  78. data/spec/unit/strategies/extraction_strategy_spec.rb +71 -0
  79. data/spec/unit/strategies/http_accept_parameter_strategy_spec.rb +20 -0
  80. data/spec/unit/strategies/http_header_strategy_spec.rb +19 -0
  81. data/spec/unit/strategies/path_parameter_strategy_spec.rb +18 -0
  82. data/spec/unit/strategies/query_parameter_strategy_spec.rb +24 -0
  83. data/spec/unit/strategies/request_parameter_strategy_spec.rb +19 -0
  84. data/spec/unit/version_checker_spec.rb +60 -0
  85. data/spec/unit/version_context_service_spec.rb +84 -0
  86. data/spec/unit/version_context_spec.rb +46 -0
  87. data/spec/unit/versioned_request_spec.rb +35 -0
  88. data/spec/unit/versioned_resource_spec.rb +12 -0
  89. data/versioncake.gemspec +5 -2
  90. metadata +91 -70
  91. data/lib/versioncake/railtie.rb +0 -7
  92. data/test/app/views/renders/index.html.erb +0 -1
  93. data/test/config.ru +0 -0
  94. data/test/config/routes.rb +0 -3
  95. data/test/fixtures/test_cases.yml +0 -70
  96. data/test/functional/custom_strategy_controller_test.rb +0 -16
  97. data/test/functional/multiple_strategy_controller_test.rb +0 -24
  98. data/test/functional/renders_controller_test.rb +0 -71
  99. data/test/functional/strategy_controller_test.rb +0 -38
  100. data/test/script/rails +0 -0
  101. data/test/template/render_test.rb +0 -34
  102. data/test/test_helper.rb +0 -21
  103. data/test/unit/cli_test.rb +0 -48
  104. data/test/unit/configuration_test.rb +0 -45
  105. data/test/unit/strategies/extraction_strategy_test.rb +0 -70
  106. data/test/unit/strategies/http_accept_parameter_strategy_test.rb +0 -17
  107. data/test/unit/strategies/http_header_strategy_test.rb +0 -17
  108. data/test/unit/strategies/path_parameter_strategy_test.rb +0 -17
  109. data/test/unit/strategies/query_parameter_strategy_test.rb +0 -22
  110. data/test/unit/strategies/request_parameter_strategy_test.rb +0 -17
  111. data/test/unit/versioned_request_test.rb +0 -44
  112. data/test/unit/view_additions_test.rb +0 -35
@@ -0,0 +1,12 @@
1
+ require 'rails/generators'
2
+
3
+ module Versioncake
4
+ class InstallGenerator < Rails::Generators::Base
5
+ source_root File.expand_path('../../templates', __FILE__)
6
+
7
+ desc 'Creates a Version Cake initializer in your application.'
8
+ def copy_initializer
9
+ template 'versioncake.rb', 'config/initializers/versioncake.rb'
10
+ end
11
+ end
12
+ end
@@ -8,8 +8,29 @@ require 'versioncake/strategies/custom_strategy'
8
8
 
9
9
  require 'versioncake/exceptions'
10
10
  require 'versioncake/configuration'
11
- require 'versioncake/controller_additions'
12
- require 'versioncake/view_additions'
13
11
  require 'versioncake/versioned_request'
14
- require 'versioncake/railtie'
12
+ require 'versioncake/version_checker'
13
+ require 'versioncake/version_context'
14
+ require 'versioncake/version_context_service'
15
+ require 'versioncake/versioned_resource'
16
+ require 'versioncake/rack/middleware'
15
17
  require 'versioncake/cli'
18
+ require 'versioncake/test_helpers'
19
+
20
+ if defined?(Rails)
21
+ require 'versioncake/controller_additions'
22
+ require 'versioncake/view_additions'
23
+ require 'versioncake/engine'
24
+ end
25
+
26
+ module VersionCake
27
+
28
+ mattr_accessor :config
29
+
30
+ self.config = VersionCake::Configuration.new
31
+
32
+ # Yield self on setup for nice config blocks
33
+ def self.setup
34
+ yield self.config
35
+ end
36
+ end
@@ -7,11 +7,13 @@ module VersionCake
7
7
  SUPPORTED_VERSIONS_DEFAULT = (1..10)
8
8
  VERSION_KEY_DEFAULT = 'api_version'
9
9
 
10
- attr_reader :extraction_strategies, :supported_version_numbers
11
- attr_accessor :default_version, :version_key
10
+ attr_reader :extraction_strategies, :supported_version_numbers, :versioned_resources
11
+ attr_accessor :missing_version, :version_key, :rails_view_versioning
12
12
 
13
13
  def initialize
14
+ @versioned_resources = []
14
15
  @version_key = VERSION_KEY_DEFAULT
16
+ @rails_view_versioning = true
15
17
  self.supported_version_numbers = SUPPORTED_VERSIONS_DEFAULT
16
18
  self.extraction_strategy = :query_parameter
17
19
  end
@@ -44,5 +46,20 @@ module VersionCake
44
46
  @supported_version_numbers.first
45
47
  end
46
48
 
49
+ def resources
50
+ builder = ResourceBuilder.new
51
+ yield builder
52
+ @versioned_resources = builder.resources
53
+ end
54
+ end
55
+
56
+ class ResourceBuilder
57
+ attr_reader :resources
58
+ def initialize
59
+ @resources = []
60
+ end
61
+ def resource(regex, obsolete, unsupported, supported)
62
+ @resources << VersionCake::VersionedResource.new(regex, obsolete, unsupported, supported)
63
+ end
47
64
  end
48
65
  end
@@ -4,52 +4,64 @@ module VersionCake
4
4
  module ControllerAdditions
5
5
  extend ActiveSupport::Concern
6
6
 
7
- attr_accessor :versioned_request
8
-
9
7
  # set_version is the prepend filter that will determine the version of the
10
8
  # requests.
11
9
  included do
12
- prepend_before_filter :set_version
10
+ if respond_to? :prepend_before_action
11
+ prepend_before_action :check_version!
12
+ else
13
+ prepend_before_filter :check_version!
14
+ end
13
15
  end
14
16
 
15
17
  # The explicit version requested by a client, this may not
16
18
  # be the rendered version and may also be nil.
17
- def requested_version
18
- versioned_request.extracted_version
19
+ def request_version
20
+ @request_version ||= version_context.version
19
21
  end
20
22
 
21
- # The requested version by a client or if it's nil the latest or default
22
- # version configured.
23
- def derived_version
24
- versioned_request.version
23
+ # A boolean check to determine if the latest version is requested.
24
+ def is_latest_version?
25
+ version_context.is_latest_version?
25
26
  end
26
27
 
27
- # A boolean check to determine if the latest version is requested.
28
- def is_latest_version
29
- versioned_request.is_latest_version?
28
+ # A boolean check to determine if the version requested is deprecated.
29
+ def is_deprecated_version?
30
+ version_context.result == :deprecated
30
31
  end
31
32
 
32
33
  protected
33
34
 
34
- # The current requests version information.
35
- def versioned_request
36
- set_version
37
- @versioned_request
35
+ def version_context
36
+ request.env['versioncake.context']
38
37
  end
39
38
 
40
- # Sets the version of the request as well as several accessor variables.
39
+ # Check the version of the request and raise errors when it's invalid. Additionally,
40
+ # setup view versioning if configured.
41
41
  #
42
42
  # @param override_version a version number to use instead of the one extracted
43
43
  # from the request
44
44
  #
45
45
  # @return No explicit return, but several attributes are exposed
46
- def set_version(override_version=nil)
47
- return if @versioned_request.present? && override_version.blank?
48
- @versioned_request = VersionCake::VersionedRequest.new(request, override_version)
49
- if !@versioned_request.is_version_supported?
50
- raise UnsupportedVersionError.new('Unsupported version error')
46
+ def check_version!(override_version=nil)
47
+ return unless version_context
48
+
49
+ case version_context.result
50
+ when :version_invalid, :version_too_high, :version_too_low, :unknown
51
+ raise UnsupportedVersionError.new('Unsupported version error')
52
+ when :obsolete
53
+ raise ObsoleteVersionError.new('The version given is obsolete')
54
+ when :no_version
55
+ raise MissingVersionError.new('No version was given')
51
56
  end
52
- @_lookup_context.versions = @versioned_request.supported_versions
57
+
58
+ if VersionCake.config.rails_view_versioning
59
+ @_lookup_context.versions = version_context.supported_versions.map { |n| :"v#{n}" }
60
+ end
61
+ end
62
+
63
+ def set_version(version)
64
+ @request_version = version
53
65
  end
54
66
  end
55
67
  end
@@ -0,0 +1,7 @@
1
+ module VersionCake
2
+ class Engine < Rails::Engine
3
+ initializer 'version_cake.add_middleware' do |app|
4
+ app.middleware.use VersionCake::Rack::Middleware
5
+ end
6
+ end
7
+ end
@@ -3,4 +3,8 @@ require 'action_controller/metal/exceptions'
3
3
  module VersionCake
4
4
  class UnsupportedVersionError < ::ActionController::RoutingError
5
5
  end
6
+ class ObsoleteVersionError < ::ActionController::RoutingError
7
+ end
8
+ class MissingVersionError < ::ActionController::RoutingError
9
+ end
6
10
  end
@@ -0,0 +1,20 @@
1
+ module VersionCake
2
+ module Rack
3
+ class Middleware
4
+
5
+ def initialize(app)
6
+ @app = app
7
+ @version_service = VersionCake::VersionContextService.new(VersionCake.config)
8
+ end
9
+
10
+ def call(env)
11
+ request = ::Rack::Request.new env
12
+ if context = @version_service.create_context_from_request(request)
13
+ env['versioncake.context'] = context
14
+ end
15
+
16
+ @app.call(env)
17
+ end
18
+ end
19
+ end
20
+ end
@@ -9,15 +9,20 @@ module VersionCake
9
9
  version
10
10
  elsif version.is_a?(String) && /[0-9]+/.match(version)
11
11
  version.to_i
12
- else
12
+ elsif version.nil? # no version was found
13
13
  nil
14
+ else
15
+ raise Exception, "Invalid format for version number."
14
16
  end
15
17
  end
16
18
 
17
19
  def version_key
18
- VersionCake::Railtie.config.versioncake.version_key
20
+ VersionCake.config.version_key
19
21
  end
20
22
 
23
+ # Execute should return a number or a numeric string if it successfully finds a version.
24
+ # If no version is found, nil should be returned. Any other results returned will raise
25
+ # an exception.
21
26
  def execute(request)
22
27
  raise Exception, "ExtractionStrategy requires execute to be implemented"
23
28
  end
@@ -50,4 +55,4 @@ module VersionCake
50
55
  end
51
56
  end
52
57
  end
53
- end
58
+ end
@@ -2,8 +2,8 @@ module VersionCake
2
2
  class HttpAcceptParameterStrategy < ExtractionStrategy
3
3
 
4
4
  def execute(request)
5
- if request.headers.key?("HTTP_ACCEPT") &&
6
- match = request.headers["HTTP_ACCEPT"].match(/#{version_key}=([0-9]+)/)
5
+ if request.env.key?('HTTP_ACCEPT') &&
6
+ match = request.env['HTTP_ACCEPT'].match(/#{version_key}=([0-9]+)/)
7
7
  match[1]
8
8
  end
9
9
  end
@@ -2,8 +2,8 @@ module VersionCake
2
2
  class HttpHeaderStrategy < ExtractionStrategy
3
3
 
4
4
  def execute(request)
5
- if request.headers.key? "HTTP_X_#{version_key.upcase}"
6
- request.headers["HTTP_X_#{version_key.upcase}"]
5
+ if request.env.key? "HTTP_#{version_key.upcase}"
6
+ request.env["HTTP_#{version_key.upcase}"]
7
7
  end
8
8
  end
9
9
 
@@ -2,9 +2,13 @@ module VersionCake
2
2
  class PathParameterStrategy < ExtractionStrategy
3
3
 
4
4
  def execute(request)
5
- if request.path_parameters.key? version_key.to_sym
6
- request.path_parameters[version_key.to_sym]
5
+ version = nil
6
+ request.path.split('/').find do |part|
7
+ next unless match = part.match(%r{v(?<version>\d+)})
8
+ version = match[:version]
9
+ break
7
10
  end
11
+ version
8
12
  end
9
13
 
10
14
  end
@@ -2,8 +2,8 @@ module VersionCake
2
2
  class QueryParameterStrategy < ExtractionStrategy
3
3
 
4
4
  def execute(request)
5
- if request.query_parameters.key? version_key.to_sym
6
- request.query_parameters[version_key.to_sym].to_s
5
+ if request.GET.key? version_key
6
+ request.GET[version_key].to_s
7
7
  end
8
8
  end
9
9
 
@@ -2,8 +2,8 @@ module VersionCake
2
2
  class RequestParameterStrategy < ExtractionStrategy
3
3
 
4
4
  def execute(request)
5
- if request.request_parameters.has_key? version_key.to_sym
6
- request.request_parameters[version_key.to_sym]
5
+ if request.POST.has_key? version_key
6
+ request.POST[version_key]
7
7
  end
8
8
  end
9
9
 
@@ -0,0 +1,14 @@
1
+ module VersionCake
2
+ module TestHelpers
3
+ # Test helper the mimics the middleware because we do not
4
+ # have middleware during tests.
5
+ def set_request_version(resource, version)
6
+ service = VersionCake::VersionContextService.new(VersionCake.config)
7
+ @request.env['versioncake.context'] = service.create_context resource, version
8
+ end
9
+
10
+ def set_version_context(status, resource=nil, version=nil)
11
+ @request.env['versioncake.context'] = VersionCake::VersionContext.new(version, resource, status)
12
+ end
13
+ end
14
+ end
@@ -1,3 +1,3 @@
1
1
  module VersionCake
2
- VERSION = "2.5.0"
2
+ VERSION = '3.0.0'
3
3
  end
@@ -0,0 +1,28 @@
1
+ module VersionCake
2
+ class VersionChecker
3
+ attr_reader :result
4
+ def initialize(version, resource)
5
+ @version, @resource = resource, version
6
+ end
7
+
8
+ def execute
9
+ @result = if @version.nil?
10
+ :no_version
11
+ elsif !@version.is_a? Integer
12
+ :invalid_format
13
+ elsif @resource.obsolete_versions.include? @version
14
+ :obsolete
15
+ elsif @resource.deprecated_versions.include? @version
16
+ :deprecated
17
+ elsif @resource.supported_versions.include? @version
18
+ :supported
19
+ elsif @version > @resource.supported_versions.last
20
+ :version_too_high
21
+ elsif @version < @resource.supported_versions.first
22
+ :version_too_low
23
+ else
24
+ :unknown
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,20 @@
1
+ module VersionCake
2
+ class VersionContext
3
+ attr_reader :version, :resource, :result
4
+
5
+ def initialize(version, resource, result)
6
+ @version, @resource, @result = version, resource, result
7
+ end
8
+
9
+ # A boolean check to determine if the latest version is requested.
10
+ def is_latest_version?
11
+ @version == @resource.latest_version
12
+ end
13
+
14
+ # Ordered versions that are equal to or lower
15
+ # than the requested version.
16
+ def supported_versions
17
+ @resource.supported_versions.sort.reverse.reject { |v| v > @version }
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,47 @@
1
+ module VersionCake
2
+ class VersionContextService
3
+
4
+ def initialize(config)
5
+ @versioned_resources = config.versioned_resources
6
+ @default_version = config.missing_version
7
+ @strategies = config.extraction_strategies
8
+ end
9
+
10
+ def create_context_from_request(raw_request)
11
+ return unless resource = find_resource(raw_request.path)
12
+
13
+ request = VersionCake::VersionedRequest.new(
14
+ raw_request,
15
+ @strategies,
16
+ @default_version
17
+ )
18
+ request.execute
19
+
20
+ result = if request.failed
21
+ :invalid_version
22
+ else
23
+ check_version(resource, request.version)
24
+ end
25
+
26
+ VersionCake::VersionContext.new(request.version, resource, result)
27
+ end
28
+
29
+ def create_context(uri, version)
30
+ return unless resource = find_resource(uri)
31
+
32
+ result = check_version(resource, version)
33
+
34
+ VersionCake::VersionContext.new(version, resource, result)
35
+ end
36
+
37
+ private
38
+
39
+ def check_version(resource, version)
40
+ VersionCake::VersionChecker.new(resource, version).execute
41
+ end
42
+
43
+ def find_resource(uri)
44
+ @versioned_resources.find { |resource| resource.uri.match uri }
45
+ end
46
+ end
47
+ end
@@ -1,45 +1,34 @@
1
1
  module VersionCake
2
2
  class VersionedRequest
3
- attr_reader :version, :extracted_version
3
+ attr_reader :failed, :version
4
4
 
5
- def initialize(request, version_override=nil)
6
- derive_version(request, version_override)
5
+ def initialize(request, strategies, default_version=nil)
6
+ @request, @strategies, @default_version, @failed = request, strategies, default_version, false
7
7
  end
8
8
 
9
- def supported_versions
10
- config.supported_versions(@version)
11
- end
12
-
13
- def is_latest_version?
14
- @version == config.latest_version
15
- end
16
-
17
- def is_version_supported?
18
- config.supports_version? @version
9
+ def execute
10
+ begin
11
+ extracted_version = extract_version
12
+
13
+ if extracted_version.nil?
14
+ @version = @default_version
15
+ else
16
+ @version = extracted_version
17
+ end
18
+ rescue Exception
19
+ @failed = true
20
+ end
19
21
  end
20
22
 
21
23
  private
22
24
 
23
- def config
24
- VersionCake::Railtie.config.versioncake
25
- end
26
-
27
- def apply_strategies(request)
28
- version = nil
29
- config.extraction_strategies.each do |strategy|
30
- version = strategy.extract(request)
31
- break unless version.nil?
32
- end
33
- version
34
- end
35
-
36
- def derive_version(request, version_override)
37
- if version_override
38
- @version = version_override
39
- else
40
- @extracted_version = apply_strategies(request)
41
- @version = @extracted_version || config.default_version || config.latest_version
25
+ def extract_version
26
+ extracted_version = nil
27
+ @strategies.each do |strategy|
28
+ extracted_version = strategy.extract(@request)
29
+ break unless extracted_version.nil?
42
30
  end
31
+ extracted_version
43
32
  end
44
33
  end
45
- end
34
+ end