versioncake 2.5.0 → 3.0.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.
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