grape-security 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (115) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +45 -0
  3. data/.rspec +2 -0
  4. data/.rubocop.yml +70 -0
  5. data/.travis.yml +18 -0
  6. data/.yardopts +2 -0
  7. data/CHANGELOG.md +314 -0
  8. data/CONTRIBUTING.md +118 -0
  9. data/Gemfile +21 -0
  10. data/Guardfile +14 -0
  11. data/LICENSE +20 -0
  12. data/README.md +1777 -0
  13. data/RELEASING.md +105 -0
  14. data/Rakefile +69 -0
  15. data/UPGRADING.md +124 -0
  16. data/grape-security.gemspec +39 -0
  17. data/grape.png +0 -0
  18. data/lib/grape.rb +99 -0
  19. data/lib/grape/api.rb +646 -0
  20. data/lib/grape/cookies.rb +39 -0
  21. data/lib/grape/endpoint.rb +533 -0
  22. data/lib/grape/error_formatter/base.rb +31 -0
  23. data/lib/grape/error_formatter/json.rb +15 -0
  24. data/lib/grape/error_formatter/txt.rb +16 -0
  25. data/lib/grape/error_formatter/xml.rb +15 -0
  26. data/lib/grape/exceptions/base.rb +66 -0
  27. data/lib/grape/exceptions/incompatible_option_values.rb +10 -0
  28. data/lib/grape/exceptions/invalid_formatter.rb +10 -0
  29. data/lib/grape/exceptions/invalid_versioner_option.rb +10 -0
  30. data/lib/grape/exceptions/invalid_with_option_for_represent.rb +10 -0
  31. data/lib/grape/exceptions/missing_mime_type.rb +10 -0
  32. data/lib/grape/exceptions/missing_option.rb +10 -0
  33. data/lib/grape/exceptions/missing_vendor_option.rb +10 -0
  34. data/lib/grape/exceptions/unknown_options.rb +10 -0
  35. data/lib/grape/exceptions/unknown_validator.rb +10 -0
  36. data/lib/grape/exceptions/validation.rb +26 -0
  37. data/lib/grape/exceptions/validation_errors.rb +43 -0
  38. data/lib/grape/formatter/base.rb +31 -0
  39. data/lib/grape/formatter/json.rb +12 -0
  40. data/lib/grape/formatter/serializable_hash.rb +35 -0
  41. data/lib/grape/formatter/txt.rb +11 -0
  42. data/lib/grape/formatter/xml.rb +12 -0
  43. data/lib/grape/http/request.rb +26 -0
  44. data/lib/grape/locale/en.yml +32 -0
  45. data/lib/grape/middleware/auth/base.rb +30 -0
  46. data/lib/grape/middleware/auth/basic.rb +13 -0
  47. data/lib/grape/middleware/auth/digest.rb +13 -0
  48. data/lib/grape/middleware/auth/oauth2.rb +83 -0
  49. data/lib/grape/middleware/base.rb +62 -0
  50. data/lib/grape/middleware/error.rb +89 -0
  51. data/lib/grape/middleware/filter.rb +17 -0
  52. data/lib/grape/middleware/formatter.rb +150 -0
  53. data/lib/grape/middleware/globals.rb +13 -0
  54. data/lib/grape/middleware/versioner.rb +32 -0
  55. data/lib/grape/middleware/versioner/accept_version_header.rb +67 -0
  56. data/lib/grape/middleware/versioner/header.rb +132 -0
  57. data/lib/grape/middleware/versioner/param.rb +42 -0
  58. data/lib/grape/middleware/versioner/path.rb +52 -0
  59. data/lib/grape/namespace.rb +23 -0
  60. data/lib/grape/parser/base.rb +29 -0
  61. data/lib/grape/parser/json.rb +11 -0
  62. data/lib/grape/parser/xml.rb +11 -0
  63. data/lib/grape/path.rb +70 -0
  64. data/lib/grape/route.rb +27 -0
  65. data/lib/grape/util/content_types.rb +18 -0
  66. data/lib/grape/util/deep_merge.rb +23 -0
  67. data/lib/grape/util/hash_stack.rb +120 -0
  68. data/lib/grape/validations.rb +322 -0
  69. data/lib/grape/validations/coerce.rb +63 -0
  70. data/lib/grape/validations/default.rb +25 -0
  71. data/lib/grape/validations/exactly_one_of.rb +26 -0
  72. data/lib/grape/validations/mutual_exclusion.rb +25 -0
  73. data/lib/grape/validations/presence.rb +16 -0
  74. data/lib/grape/validations/regexp.rb +12 -0
  75. data/lib/grape/validations/values.rb +23 -0
  76. data/lib/grape/version.rb +3 -0
  77. data/spec/grape/api_spec.rb +2571 -0
  78. data/spec/grape/endpoint_spec.rb +784 -0
  79. data/spec/grape/entity_spec.rb +324 -0
  80. data/spec/grape/exceptions/invalid_formatter_spec.rb +18 -0
  81. data/spec/grape/exceptions/invalid_versioner_option_spec.rb +18 -0
  82. data/spec/grape/exceptions/missing_mime_type_spec.rb +18 -0
  83. data/spec/grape/exceptions/missing_option_spec.rb +18 -0
  84. data/spec/grape/exceptions/unknown_options_spec.rb +18 -0
  85. data/spec/grape/exceptions/unknown_validator_spec.rb +18 -0
  86. data/spec/grape/exceptions/validation_errors_spec.rb +19 -0
  87. data/spec/grape/middleware/auth/basic_spec.rb +31 -0
  88. data/spec/grape/middleware/auth/digest_spec.rb +47 -0
  89. data/spec/grape/middleware/auth/oauth2_spec.rb +135 -0
  90. data/spec/grape/middleware/base_spec.rb +58 -0
  91. data/spec/grape/middleware/error_spec.rb +45 -0
  92. data/spec/grape/middleware/exception_spec.rb +184 -0
  93. data/spec/grape/middleware/formatter_spec.rb +258 -0
  94. data/spec/grape/middleware/versioner/accept_version_header_spec.rb +121 -0
  95. data/spec/grape/middleware/versioner/header_spec.rb +302 -0
  96. data/spec/grape/middleware/versioner/param_spec.rb +58 -0
  97. data/spec/grape/middleware/versioner/path_spec.rb +44 -0
  98. data/spec/grape/middleware/versioner_spec.rb +22 -0
  99. data/spec/grape/path_spec.rb +229 -0
  100. data/spec/grape/util/hash_stack_spec.rb +132 -0
  101. data/spec/grape/validations/coerce_spec.rb +208 -0
  102. data/spec/grape/validations/default_spec.rb +123 -0
  103. data/spec/grape/validations/exactly_one_of_spec.rb +71 -0
  104. data/spec/grape/validations/mutual_exclusion_spec.rb +61 -0
  105. data/spec/grape/validations/presence_spec.rb +142 -0
  106. data/spec/grape/validations/regexp_spec.rb +40 -0
  107. data/spec/grape/validations/values_spec.rb +152 -0
  108. data/spec/grape/validations/zh-CN.yml +10 -0
  109. data/spec/grape/validations_spec.rb +994 -0
  110. data/spec/shared/versioning_examples.rb +121 -0
  111. data/spec/spec_helper.rb +26 -0
  112. data/spec/support/basic_auth_encode_helpers.rb +3 -0
  113. data/spec/support/content_type_helpers.rb +11 -0
  114. data/spec/support/versioned_helpers.rb +50 -0
  115. metadata +421 -0
@@ -0,0 +1,105 @@
1
+ Releasing Grape
2
+ ===============
3
+
4
+ There're no particular rules about when to release Grape. Release bug fixes frequenty, features not so frequently and breaking API changes rarely.
5
+
6
+ ### Release
7
+
8
+ Run tests, check that all tests succeed locally.
9
+
10
+ ```
11
+ bundle install
12
+ rake
13
+ ```
14
+
15
+ Check that the last build succeeded in [Travis CI](https://travis-ci.org/intridea/grape) for all supported platforms.
16
+
17
+ Those with r/w permissions to the [master Intridea repository](https://github.com/intridea/grape) generally have large Grape-based projects. Point one to Grape HEAD and run all your API tests to catch any obvious regressions.
18
+
19
+ ```
20
+ gem grape, github: 'intridea/grape'
21
+ ```
22
+
23
+ Increment the version, modify [lib/grape/version.rb](lib/grape/version.rb).
24
+
25
+ * Increment the third number if the release has bug fixes and/or very minor features, only (eg. change `0.5.1` to `0.5.2`).
26
+ * Increment the second number if the release contains major features or breaking API changes (eg. change `0.5.1` to `0.6.0`).
27
+
28
+ Modify the "Stable Release" section in [README.md](README.md). Change the text to reflect that this is going to be the documentation for a stable release. Remove references to the previous release of Grape. Keep the file open, you'll have to undo this change after the release.
29
+
30
+ ```
31
+ ## Stable Release
32
+
33
+ You're reading the documentation for the stable release of Grape, 0.6.0.
34
+ ```
35
+
36
+ Change "Next Release" in [CHANGELOG.md](CHANGELOG.md) to the new version.
37
+
38
+ ```
39
+ 0.6.0 (9/16/2013)
40
+ =================
41
+ ```
42
+
43
+ Remove the line with "Your contribution here.", since there will be no more contributions to this release.
44
+
45
+ Commit your changes.
46
+
47
+ ```
48
+ git add README.md CHANGELOG.md lib/grape/version.rb
49
+ git commit -m "Preparing for release, 0.6.0."
50
+ git push origin master
51
+ ```
52
+
53
+ Release.
54
+
55
+ ```
56
+ $ rake release
57
+
58
+ grape 0.6.0 built to pkg/grape-0.6.0.gem.
59
+ Tagged v0.6.0.
60
+ Pushed git commits and tags.
61
+ Pushed grape 0.6.0 to rubygems.org.
62
+ ```
63
+
64
+ ### Prepare for the Next Version
65
+
66
+ Modify the "Stable Release" section in [README.md](README.md). Change the text to reflect that this is going to be the next release.
67
+
68
+ ```
69
+ ## Stable Release
70
+
71
+ You're reading the documentation for the next release of Grape, which should be 0.6.1.
72
+ The current stable release is [0.6.0](https://github.com/intridea/grape/blob/v0.6.0/README.md).
73
+ ```
74
+
75
+ Add the next release to [CHANGELOG.md](CHANGELOG.md).
76
+
77
+ ```
78
+ Next Release
79
+ ============
80
+
81
+ * Your contribution here.
82
+ ```
83
+
84
+ Comit your changes.
85
+
86
+ ```
87
+ git add CHANGELOG.md README.md
88
+ git commit -m "Preparing for next release."
89
+ git push origin master
90
+ ```
91
+
92
+ ### Make an Announcement
93
+
94
+ Make an announcement on the [ruby-grape@googlegroups.com](mailto:ruby-grape@googlegroups.com) mailing list. The general format is as follows.
95
+
96
+ ```
97
+ Grape 0.6.0 has been released.
98
+
99
+ There were 8 contributors to this release, not counting documentation.
100
+
101
+ Please note the breaking API change in ...
102
+
103
+ [copy/paste CHANGELOG here]
104
+
105
+ ```
@@ -0,0 +1,69 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ Bundler.setup :default, :test, :development
4
+
5
+ Bundler::GemHelper.install_tasks
6
+
7
+ require 'rspec/core/rake_task'
8
+ RSpec::Core::RakeTask.new(:spec) do |spec|
9
+ spec.pattern = 'spec/**/*_spec.rb'
10
+ end
11
+
12
+ RSpec::Core::RakeTask.new(:rcov) do |spec|
13
+ spec.pattern = 'spec/**/*_spec.rb'
14
+ spec.rcov = true
15
+ end
16
+
17
+ task :spec
18
+
19
+ require 'rainbow/ext/string' unless String.respond_to?(:color)
20
+ require 'rubocop/rake_task'
21
+ Rubocop::RakeTask.new(:rubocop)
22
+
23
+ task default: [:rubocop, :spec]
24
+
25
+ begin
26
+ require 'yard'
27
+ DOC_FILES = ['lib/**/*.rb', 'README.md']
28
+
29
+ YARD::Rake::YardocTask.new(:doc) do |t|
30
+ t.files = DOC_FILES
31
+ end
32
+
33
+ namespace :doc do
34
+ YARD::Rake::YardocTask.new(:pages) do |t|
35
+ t.files = DOC_FILES
36
+ t.options = ['-o', '../grape.doc/docs']
37
+ end
38
+
39
+ namespace :pages do
40
+
41
+ desc "Check out gh-pages."
42
+ task :checkout do
43
+ dir = File.dirname(__FILE__) + '/../grape.doc'
44
+ unless Dir.exist?(dir)
45
+ Dir.mkdir(dir)
46
+ Dir.chdir(dir) do
47
+ system("git init")
48
+ system("git remote add origin git@github.com:intridea/grape.git")
49
+ system("git pull")
50
+ system("git checkout gh-pages")
51
+ end
52
+ end
53
+ end
54
+
55
+ desc 'Generate and publish YARD docs to GitHub pages.'
56
+ task :publish => ['doc:pages:checkout', 'doc:pages'] do
57
+ Dir.chdir(File.dirname(__FILE__) + '/../grape.doc') do
58
+ system("git checkout gh-pages")
59
+ system("git add .")
60
+ system("git add -u")
61
+ system("git commit -m 'Generating docs for version #{Grape::VERSION}.'")
62
+ system("git push origin gh-pages")
63
+ end
64
+ end
65
+ end
66
+ end
67
+ rescue LoadError
68
+ puts "You need to install YARD."
69
+ end
@@ -0,0 +1,124 @@
1
+ Upgrading Grape
2
+ ===============
3
+
4
+ ### Upgrading to >= 0.7.0
5
+
6
+ #### Changes in Exception Handling
7
+
8
+ Assume you have the following exception classes defined.
9
+
10
+ ```ruby
11
+ class ParentError < StandardError; end
12
+ class ChildError < ParentError; end
13
+ ```
14
+
15
+ In Grape <= 0.6.1, the `rescue_from` keyword only handled the exact exception being raised. The following code would rescue `ParentError`, but not `ChildError`.
16
+
17
+ ```ruby
18
+ rescue_from ParentError do |e|
19
+ # only rescue ParentError
20
+ end
21
+ ```
22
+
23
+ This made it impossible to rescue an exception hieararchy, which is a more sensible default. In Grape 0.7.0 or newer, both `ParentError` and `ChildError` are rescued.
24
+
25
+ ```ruby
26
+ rescue_from ParentError do |e|
27
+ # rescue both ParentError and ChildError
28
+ end
29
+ ```
30
+
31
+ To only rescue the base exception class, set `rescue_subclasses: false`.
32
+
33
+ ```ruby
34
+ rescue_from ParentError, rescue_subclasses: false do |e|
35
+ # only rescue ParentError
36
+ end
37
+ ```
38
+
39
+ See [#544](https://github.com/intridea/grape/pull/544) for more information.
40
+
41
+
42
+ #### Changes in the Default HTTP Status Code
43
+
44
+ In Grape <= 0.6.1, the default status code returned from `error!` was 403.
45
+
46
+ ```ruby
47
+ error! "You may not reticulate this spline!" # yields HTTP error 403
48
+ ```
49
+
50
+ This was a bad default value, since 403 means "Forbidden". Change any call to `error!` that does not specify a status code to specify one. The new default value is a more sensible default of 500, which is "Internal Server Error".
51
+
52
+ ```ruby
53
+ error! "You may not reticulate this spline!", 403 # yields HTTP error 403
54
+ ```
55
+
56
+ You may also use `default_error_status` to change the global default.
57
+
58
+ ```ruby
59
+ default_error_status 400
60
+ ```
61
+
62
+ See [#525](https://github.com/intridea/Grape/pull/525) for more information.
63
+
64
+
65
+ #### Changes in Parameter Declaration and Validation
66
+
67
+ In Grape <= 0.6.1, `group`, `optional` and `requires` keywords with a block accepted either an `Array` or a `Hash`.
68
+
69
+ ```ruby
70
+ params do
71
+ requires :id, type: Integer
72
+ group :name do
73
+ requires :first_name
74
+ requires :last_name
75
+ end
76
+ end
77
+ ```
78
+
79
+ This caused the ambiguity and unexpected errors described in [#543](https://github.com/intridea/Grape/issues/543).
80
+
81
+ In Grape 0.7.0, the `group`, `optional` and `requires` keywords take an additional `type` attribute which defaults to `Array`. This means that without a `type` attribute, these nested parameters will no longer accept a single hash, only an array (of hashes).
82
+
83
+ Whereas in 0.6.1 the API above accepted the following json, it no longer does in 0.7.0.
84
+
85
+ ```json
86
+ {
87
+ "id": 1,
88
+ "name": {
89
+ "first_name": "John",
90
+ "last_name" : "Doe"
91
+ }
92
+ }
93
+ ```
94
+
95
+ The `params` block should now read as follows.
96
+
97
+ ```ruby
98
+ params do
99
+ requires :id, type: Integer
100
+ requires :name, type: Hash do
101
+ requires :first_name
102
+ requires :last_name
103
+ end
104
+ end
105
+ ```
106
+
107
+ See [#545](https://github.com/intridea/Grape/pull/545) for more information.
108
+
109
+
110
+ ### Upgrading to 0.6.0
111
+
112
+ In Grape <= 0.5.0, only the first validation error was raised and processing aborted. Validation errors are now collected and a single `Grape::Exceptions::ValidationErrors` exception is raised. You can access the collection of validation errors as `.errors`.
113
+
114
+ ```ruby
115
+ rescue_from Grape::Exceptions::Validations do |e|
116
+ Rack::Response.new({
117
+ status: 422,
118
+ message: e.message,
119
+ errors: e.errors
120
+ }.to_json, 422)
121
+ end
122
+ ```
123
+
124
+ For more information see [#462](https://github.com/intridea/grape/issues/462).
@@ -0,0 +1,39 @@
1
+ $:.push File.expand_path("../lib", __FILE__)
2
+ require "grape/version"
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = "grape-security"
6
+ s.version = Grape::VERSION
7
+ s.platform = Gem::Platform::RUBY
8
+ s.authors = ["Michael Bleigh"]
9
+ s.email = ["michael@intridea.com"]
10
+ s.homepage = "https://github.com/intridea/grape"
11
+ s.summary = %q{Backported security patched version] A simple Ruby framework for building REST-like APIs.}
12
+ s.description = %q{[Backported security patched version] A Ruby framework for rapid API development with great conventions.}
13
+ s.license = "MIT"
14
+
15
+ s.rubyforge_project = "grape"
16
+
17
+ s.add_runtime_dependency 'rack', '>= 1.3.0'
18
+ s.add_runtime_dependency 'rack-mount'
19
+ s.add_runtime_dependency 'rack-accept'
20
+ s.add_runtime_dependency 'activesupport'
21
+ s.add_runtime_dependency 'multi_json', '>= 1.3.2'
22
+ s.add_runtime_dependency 'multi_xml', '>= 0.5.2'
23
+ s.add_runtime_dependency 'hashie', '>= 2.1.0'
24
+ s.add_runtime_dependency 'virtus', '>= 1.0.0'
25
+ s.add_runtime_dependency 'builder'
26
+
27
+ s.add_development_dependency 'grape-entity', '>= 0.2.0'
28
+ s.add_development_dependency 'rake'
29
+ s.add_development_dependency 'maruku'
30
+ s.add_development_dependency 'yard'
31
+ s.add_development_dependency 'rack-test'
32
+ s.add_development_dependency 'rspec', '~> 2.9'
33
+ s.add_development_dependency 'bundler'
34
+
35
+ s.files = `git ls-files`.split("\n")
36
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
37
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
38
+ s.require_paths = ["lib"]
39
+ end
Binary file
@@ -0,0 +1,99 @@
1
+ require 'logger'
2
+ require 'rack'
3
+ require 'rack/mount'
4
+ require 'rack/builder'
5
+ require 'rack/accept'
6
+ require 'rack/auth/basic'
7
+ require 'rack/auth/digest/md5'
8
+ require 'hashie'
9
+ require 'set'
10
+ require 'active_support/core_ext/hash/indifferent_access'
11
+ require 'active_support/ordered_hash'
12
+ require 'active_support/core_ext/object/conversions'
13
+ require 'active_support/core_ext/array/extract_options'
14
+ require 'grape/util/deep_merge'
15
+ require 'grape/util/content_types'
16
+ require 'multi_json'
17
+ require 'multi_xml'
18
+ require 'virtus'
19
+ require 'i18n'
20
+ require 'thread'
21
+
22
+ I18n.load_path << File.expand_path('../grape/locale/en.yml', __FILE__)
23
+
24
+ module Grape
25
+ autoload :API, 'grape/api'
26
+ autoload :Endpoint, 'grape/endpoint'
27
+
28
+ autoload :Route, 'grape/route'
29
+ autoload :Namespace, 'grape/namespace'
30
+
31
+ autoload :Path, 'grape/path'
32
+
33
+ autoload :Cookies, 'grape/cookies'
34
+ autoload :Validations, 'grape/validations'
35
+ autoload :Request, 'grape/http/request'
36
+
37
+ module Exceptions
38
+ autoload :Base, 'grape/exceptions/base'
39
+ autoload :Validation, 'grape/exceptions/validation'
40
+ autoload :ValidationErrors, 'grape/exceptions/validation_errors'
41
+ autoload :MissingVendorOption, 'grape/exceptions/missing_vendor_option'
42
+ autoload :MissingMimeType, 'grape/exceptions/missing_mime_type'
43
+ autoload :MissingOption, 'grape/exceptions/missing_option'
44
+ autoload :InvalidFormatter, 'grape/exceptions/invalid_formatter'
45
+ autoload :InvalidVersionerOption, 'grape/exceptions/invalid_versioner_option'
46
+ autoload :UnknownValidator, 'grape/exceptions/unknown_validator'
47
+ autoload :UnknownOptions, 'grape/exceptions/unknown_options'
48
+ autoload :InvalidWithOptionForRepresent, 'grape/exceptions/invalid_with_option_for_represent'
49
+ autoload :IncompatibleOptionValues, 'grape/exceptions/incompatible_option_values'
50
+ end
51
+
52
+ module ErrorFormatter
53
+ autoload :Base, 'grape/error_formatter/base'
54
+ autoload :Json, 'grape/error_formatter/json'
55
+ autoload :Txt, 'grape/error_formatter/txt'
56
+ autoload :Xml, 'grape/error_formatter/xml'
57
+ end
58
+
59
+ module Formatter
60
+ autoload :Base, 'grape/formatter/base'
61
+ autoload :Json, 'grape/formatter/json'
62
+ autoload :SerializableHash, 'grape/formatter/serializable_hash'
63
+ autoload :Txt, 'grape/formatter/txt'
64
+ autoload :Xml, 'grape/formatter/xml'
65
+ end
66
+
67
+ module Parser
68
+ autoload :Base, 'grape/parser/base'
69
+ autoload :Json, 'grape/parser/json'
70
+ autoload :Xml, 'grape/parser/xml'
71
+ end
72
+
73
+ module Middleware
74
+ autoload :Base, 'grape/middleware/base'
75
+ autoload :Versioner, 'grape/middleware/versioner'
76
+ autoload :Formatter, 'grape/middleware/formatter'
77
+ autoload :Error, 'grape/middleware/error'
78
+
79
+ module Auth
80
+ autoload :OAuth2, 'grape/middleware/auth/oauth2'
81
+ autoload :Base, 'grape/middleware/auth/base'
82
+ autoload :Basic, 'grape/middleware/auth/basic'
83
+ autoload :Digest, 'grape/middleware/auth/digest'
84
+ end
85
+
86
+ module Versioner
87
+ autoload :Path, 'grape/middleware/versioner/path'
88
+ autoload :Header, 'grape/middleware/versioner/header'
89
+ autoload :Param, 'grape/middleware/versioner/param'
90
+ autoload :AcceptVersionHeader, 'grape/middleware/versioner/accept_version_header'
91
+ end
92
+ end
93
+
94
+ module Util
95
+ autoload :HashStack, 'grape/util/hash_stack'
96
+ end
97
+ end
98
+
99
+ require 'grape/version'
@@ -0,0 +1,646 @@
1
+ module Grape
2
+ # The API class is the primary entry point for
3
+ # creating Grape APIs.Users should subclass this
4
+ # class in order to build an API.
5
+ class API
6
+ extend Validations::ClassMethods
7
+
8
+ class << self
9
+ attr_reader :endpoints, :instance, :routes, :route_set, :settings, :versions
10
+ attr_writer :logger
11
+
12
+ LOCK = Mutex.new
13
+
14
+ def logger(logger = nil)
15
+ if logger
16
+ @logger = logger
17
+ else
18
+ @logger ||= Logger.new($stdout)
19
+ end
20
+ end
21
+
22
+ def reset!
23
+ @settings = Grape::Util::HashStack.new
24
+ @route_set = Rack::Mount::RouteSet.new
25
+ @endpoints = []
26
+ @routes = nil
27
+ reset_validations!
28
+ end
29
+
30
+ def compile
31
+ @instance ||= new
32
+ end
33
+
34
+ def change!
35
+ @instance = nil
36
+ end
37
+
38
+ def call(env)
39
+ LOCK.synchronize { compile } unless instance
40
+ call!(env)
41
+ end
42
+
43
+ def call!(env)
44
+ instance.call(env)
45
+ end
46
+
47
+ # Set a configuration value for this namespace.
48
+ #
49
+ # @param key [Symbol] The key of the configuration variable.
50
+ # @param value [Object] The value to which to set the configuration variable.
51
+ def set(key, value)
52
+ settings[key.to_sym] = value
53
+ end
54
+
55
+ # Add to a configuration value for this
56
+ # namespace.
57
+ #
58
+ # @param key [Symbol] The key of the configuration variable.
59
+ # @param value [Object] The value to which to set the configuration variable.
60
+ def imbue(key, value)
61
+ settings.imbue(key, value)
62
+ end
63
+
64
+ # Define a root URL prefix for your entire API.
65
+ def prefix(prefix = nil)
66
+ prefix ? set(:root_prefix, prefix) : settings[:root_prefix]
67
+ end
68
+
69
+ # Do not route HEAD requests to GET requests automatically
70
+ def do_not_route_head!
71
+ set(:do_not_route_head, true)
72
+ end
73
+
74
+ # Do not automatically route OPTIONS
75
+ def do_not_route_options!
76
+ set(:do_not_route_options, true)
77
+ end
78
+
79
+ # Specify an API version.
80
+ #
81
+ # @example API with legacy support.
82
+ # class MyAPI < Grape::API
83
+ # version 'v2'
84
+ #
85
+ # get '/main' do
86
+ # {some: 'data'}
87
+ # end
88
+ #
89
+ # version 'v1' do
90
+ # get '/main' do
91
+ # {legacy: 'data'}
92
+ # end
93
+ # end
94
+ # end
95
+ #
96
+ def version(*args, &block)
97
+ if args.any?
98
+ options = args.pop if args.last.is_a? Hash
99
+ options ||= {}
100
+ options = { using: :path }.merge(options)
101
+
102
+ raise Grape::Exceptions::MissingVendorOption.new if options[:using] == :header && !options.key?(:vendor)
103
+
104
+ @versions = versions | args
105
+ nest(block) do
106
+ set(:version, args)
107
+ set(:version_options, options)
108
+ end
109
+ end
110
+
111
+ @versions.last unless @versions.nil?
112
+ end
113
+
114
+ # Add a description to the next namespace or function.
115
+ def desc(description, options = {})
116
+ @last_description = options.merge(description: description)
117
+ end
118
+
119
+ # Specify the default format for the API's serializers.
120
+ # May be `:json` or `:txt` (default).
121
+ def default_format(new_format = nil)
122
+ new_format ? set(:default_format, new_format.to_sym) : settings[:default_format]
123
+ end
124
+
125
+ # Specify the format for the API's serializers.
126
+ # May be `:json`, `:xml`, `:txt`, etc.
127
+ def format(new_format = nil)
128
+ if new_format
129
+ set(:format, new_format.to_sym)
130
+ # define the default error formatters
131
+ set(:default_error_formatter, Grape::ErrorFormatter::Base.formatter_for(new_format, {}))
132
+ # define a single mime type
133
+ mime_type = content_types[new_format.to_sym]
134
+ raise Grape::Exceptions::MissingMimeType.new(new_format) unless mime_type
135
+ settings.imbue(:content_types, new_format.to_sym => mime_type)
136
+ else
137
+ settings[:format]
138
+ end
139
+ end
140
+
141
+ # Specify a custom formatter for a content-type.
142
+ def formatter(content_type, new_formatter)
143
+ settings.imbue(:formatters, content_type.to_sym => new_formatter)
144
+ end
145
+
146
+ # Specify a custom parser for a content-type.
147
+ def parser(content_type, new_parser)
148
+ settings.imbue(:parsers, content_type.to_sym => new_parser)
149
+ end
150
+
151
+ # Specify a default error formatter.
152
+ def default_error_formatter(new_formatter_name = nil)
153
+ if new_formatter_name
154
+ new_formatter = Grape::ErrorFormatter::Base.formatter_for(new_formatter_name, {})
155
+ set(:default_error_formatter, new_formatter)
156
+ else
157
+ settings[:default_error_formatter]
158
+ end
159
+ end
160
+
161
+ def error_formatter(format, options)
162
+ if options.is_a?(Hash) && options.key?(:with)
163
+ formatter = options[:with]
164
+ else
165
+ formatter = options
166
+ end
167
+
168
+ settings.imbue(:error_formatters, format.to_sym => formatter)
169
+ end
170
+
171
+ # Specify additional content-types, e.g.:
172
+ # content_type :xls, 'application/vnd.ms-excel'
173
+ def content_type(key, val)
174
+ settings.imbue(:content_types, key.to_sym => val)
175
+ end
176
+
177
+ # All available content types.
178
+ def content_types
179
+ Grape::ContentTypes.content_types_for(settings[:content_types])
180
+ end
181
+
182
+ # Specify the default status code for errors.
183
+ def default_error_status(new_status = nil)
184
+ new_status ? set(:default_error_status, new_status) : settings[:default_error_status]
185
+ end
186
+
187
+ # Allows you to rescue certain exceptions that occur to return
188
+ # a grape error rather than raising all the way to the
189
+ # server level.
190
+ #
191
+ # @example Rescue from custom exceptions
192
+ # class ExampleAPI < Grape::API
193
+ # class CustomError < StandardError; end
194
+ #
195
+ # rescue_from CustomError
196
+ # end
197
+ #
198
+ # @overload rescue_from(*exception_classes, options = {})
199
+ # @param [Array] exception_classes A list of classes that you want to rescue, or
200
+ # the symbol :all to rescue from all exceptions.
201
+ # @param [Block] block Execution block to handle the given exception.
202
+ # @param [Hash] options Options for the rescue usage.
203
+ # @option options [Boolean] :backtrace Include a backtrace in the rescue response.
204
+ # @option options [Boolean] :rescue_subclasses Also rescue subclasses of exception classes
205
+ # @param [Proc] handler Execution proc to handle the given exception as an
206
+ # alternative to passing a block
207
+ def rescue_from(*args, &block)
208
+ if args.last.is_a?(Proc)
209
+ handler = args.pop
210
+ elsif block_given?
211
+ handler = block
212
+ end
213
+
214
+ options = args.last.is_a?(Hash) ? args.pop : {}
215
+ handler ||= proc { options[:with] } if options.key?(:with)
216
+
217
+ if args.include?(:all)
218
+ set(:rescue_all, true)
219
+ imbue :all_rescue_handler, handler
220
+ else
221
+ handler_type =
222
+ case options[:rescue_subclasses]
223
+ when nil, true
224
+ :rescue_handlers
225
+ else
226
+ :base_only_rescue_handlers
227
+ end
228
+
229
+ imbue handler_type, Hash[args.map { |arg| [arg, handler] }]
230
+ end
231
+
232
+ imbue(:rescue_options, options)
233
+ end
234
+
235
+ # Allows you to specify a default representation entity for a
236
+ # class. This allows you to map your models to their respective
237
+ # entities once and then simply call `present` with the model.
238
+ #
239
+ # @example
240
+ # class ExampleAPI < Grape::API
241
+ # represent User, with: Entity::User
242
+ #
243
+ # get '/me' do
244
+ # present current_user # with: Entity::User is assumed
245
+ # end
246
+ # end
247
+ #
248
+ # Note that Grape will automatically go up the class ancestry to
249
+ # try to find a representing entity, so if you, for example, define
250
+ # an entity to represent `Object` then all presented objects will
251
+ # bubble up and utilize the entity provided on that `represent` call.
252
+ #
253
+ # @param model_class [Class] The model class that will be represented.
254
+ # @option options [Class] :with The entity class that will represent the model.
255
+ def represent(model_class, options)
256
+ raise Grape::Exceptions::InvalidWithOptionForRepresent.new unless options[:with] && options[:with].is_a?(Class)
257
+ imbue(:representations, model_class => options[:with])
258
+ end
259
+
260
+ # Add helper methods that will be accessible from any
261
+ # endpoint within this namespace (and child namespaces).
262
+ #
263
+ # When called without a block, all known helpers within this scope
264
+ # are included.
265
+ #
266
+ # @param [Module] new_mod optional module of methods to include
267
+ # @param [Block] block optional block of methods to include
268
+ #
269
+ # @example Define some helpers.
270
+ #
271
+ # class ExampleAPI < Grape::API
272
+ # helpers do
273
+ # def current_user
274
+ # User.find_by_id(params[:token])
275
+ # end
276
+ # end
277
+ # end
278
+ #
279
+ def helpers(new_mod = nil, &block)
280
+ if block_given? || new_mod
281
+ mod = settings.peek[:helpers] || Module.new
282
+ if new_mod
283
+ inject_api_helpers_to_mod(new_mod) if new_mod.is_a?(Helpers)
284
+ mod.class_eval do
285
+ include new_mod
286
+ end
287
+ end
288
+ if block_given?
289
+ inject_api_helpers_to_mod(mod) do
290
+ mod.class_eval(&block)
291
+ end
292
+ end
293
+ set(:helpers, mod)
294
+ else
295
+ mod = Module.new
296
+ settings.stack.each do |s|
297
+ mod.send :include, s[:helpers] if s[:helpers]
298
+ end
299
+ change!
300
+ mod
301
+ end
302
+ end
303
+
304
+ # Add an authentication type to the API. Currently
305
+ # only `:http_basic`, `:http_digest` and `:oauth2` are supported.
306
+ def auth(type = nil, options = {}, &block)
307
+ if type
308
+ set(:auth, { type: type.to_sym, proc: block }.merge(options))
309
+ else
310
+ settings[:auth]
311
+ end
312
+ end
313
+
314
+ # Add HTTP Basic authorization to the API.
315
+ #
316
+ # @param [Hash] options A hash of options.
317
+ # @option options [String] :realm "API Authorization" The HTTP Basic realm.
318
+ def http_basic(options = {}, &block)
319
+ options[:realm] ||= "API Authorization"
320
+ auth :http_basic, options, &block
321
+ end
322
+
323
+ def http_digest(options = {}, &block)
324
+ options[:realm] ||= "API Authorization"
325
+ options[:opaque] ||= "secret"
326
+ auth :http_digest, options, &block
327
+ end
328
+
329
+ def mount(mounts)
330
+ mounts = { mounts => '/' } unless mounts.respond_to?(:each_pair)
331
+ mounts.each_pair do |app, path|
332
+ if app.respond_to?(:inherit_settings, true)
333
+ app_settings = settings.clone
334
+ mount_path = Rack::Mount::Utils.normalize_path([settings[:mount_path], path].compact.join("/"))
335
+ app_settings.set :mount_path, mount_path
336
+ app.inherit_settings(app_settings)
337
+ end
338
+ endpoints << Grape::Endpoint.new(
339
+ settings.clone,
340
+ method: :any,
341
+ path: path,
342
+ app: app
343
+ )
344
+ end
345
+ end
346
+
347
+ # Defines a route that will be recognized
348
+ # by the Grape API.
349
+ #
350
+ # @param methods [HTTP Verb] One or more HTTP verbs that are accepted by this route. Set to `:any` if you want any verb to be accepted.
351
+ # @param paths [String] One or more strings representing the URL segment(s) for this route.
352
+ #
353
+ # @example Defining a basic route.
354
+ # class MyAPI < Grape::API
355
+ # route(:any, '/hello') do
356
+ # {hello: 'world'}
357
+ # end
358
+ # end
359
+ def route(methods, paths = ['/'], route_options = {}, &block)
360
+ endpoint_options = {
361
+ method: methods,
362
+ path: paths,
363
+ route_options: (@namespace_description || {}).deep_merge(@last_description || {}).deep_merge(route_options || {})
364
+ }
365
+ endpoints << Grape::Endpoint.new(settings.clone, endpoint_options, &block)
366
+
367
+ @last_description = nil
368
+ reset_validations!
369
+ end
370
+
371
+ def before(&block)
372
+ imbue(:befores, [block])
373
+ end
374
+
375
+ def before_validation(&block)
376
+ imbue(:before_validations, [block])
377
+ end
378
+
379
+ def after_validation(&block)
380
+ imbue(:after_validations, [block])
381
+ end
382
+
383
+ def after(&block)
384
+ imbue(:afters, [block])
385
+ end
386
+
387
+ def get(paths = ['/'], options = {}, &block)
388
+ route('GET', paths, options, &block)
389
+ end
390
+
391
+ def post(paths = ['/'], options = {}, &block)
392
+ route('POST', paths, options, &block)
393
+ end
394
+
395
+ def put(paths = ['/'], options = {}, &block)
396
+ route('PUT', paths, options, &block)
397
+ end
398
+
399
+ def head(paths = ['/'], options = {}, &block)
400
+ route('HEAD', paths, options, &block)
401
+ end
402
+
403
+ def delete(paths = ['/'], options = {}, &block)
404
+ route('DELETE', paths, options, &block)
405
+ end
406
+
407
+ def options(paths = ['/'], options = {}, &block)
408
+ route('OPTIONS', paths, options, &block)
409
+ end
410
+
411
+ def patch(paths = ['/'], options = {}, &block)
412
+ route('PATCH', paths, options, &block)
413
+ end
414
+
415
+ def namespace(space = nil, options = {}, &block)
416
+ if space || block_given?
417
+ previous_namespace_description = @namespace_description
418
+ @namespace_description = (@namespace_description || {}).deep_merge(@last_description || {})
419
+ @last_description = nil
420
+ nest(block) do
421
+ set(:namespace, Namespace.new(space, options)) if space
422
+ end
423
+ @namespace_description = previous_namespace_description
424
+ else
425
+ Namespace.joined_space_path(settings)
426
+ end
427
+ end
428
+
429
+ # Thie method allows you to quickly define a parameter route segment
430
+ # in your API.
431
+ #
432
+ # @param param [Symbol] The name of the parameter you wish to declare.
433
+ # @option options [Regexp] You may supply a regular expression that the declared parameter must meet.
434
+ def route_param(param, options = {}, &block)
435
+ options = options.dup
436
+ options[:requirements] = { param.to_sym => options[:requirements] } if options[:requirements].is_a?(Regexp)
437
+ namespace(":#{param}", options, &block)
438
+ end
439
+
440
+ alias_method :group, :namespace
441
+ alias_method :resource, :namespace
442
+ alias_method :resources, :namespace
443
+ alias_method :segment, :namespace
444
+
445
+ # Create a scope without affecting the URL.
446
+ #
447
+ # @param name [Symbol] Purely placebo, just allows to to name the scope to make the code more readable.
448
+ def scope(name = nil, &block)
449
+ nest(block)
450
+ end
451
+
452
+ # Apply a custom middleware to the API. Applies
453
+ # to the current namespace and any children, but
454
+ # not parents.
455
+ #
456
+ # @param middleware_class [Class] The class of the middleware you'd like
457
+ # to inject.
458
+ def use(middleware_class, *args, &block)
459
+ arr = [middleware_class, *args]
460
+ arr << block if block_given?
461
+ imbue(:middleware, [arr])
462
+ end
463
+
464
+ # Retrieve an array of the middleware classes
465
+ # and arguments that are currently applied to the
466
+ # application.
467
+ def middleware
468
+ settings.stack.inject([]) do |a, s|
469
+ a += s[:middleware] if s[:middleware]
470
+ a
471
+ end
472
+ end
473
+
474
+ # An array of API routes.
475
+ def routes
476
+ @routes ||= prepare_routes
477
+ end
478
+
479
+ def versions
480
+ @versions ||= []
481
+ end
482
+
483
+ def cascade(value = nil)
484
+ if value.nil?
485
+ settings.key?(:cascade) ? !!settings[:cascade] : true
486
+ else
487
+ set(:cascade, value)
488
+ end
489
+ end
490
+
491
+ protected
492
+
493
+ def prepare_routes
494
+ routes = []
495
+ endpoints.each do |endpoint|
496
+ routes.concat(endpoint.routes)
497
+ end
498
+ routes
499
+ end
500
+
501
+ # Execute first the provided block, then each of the
502
+ # block passed in. Allows for simple 'before' setups
503
+ # of settings stack pushes.
504
+ def nest(*blocks, &block)
505
+ blocks.reject! { |b| b.nil? }
506
+ if blocks.any?
507
+ settings.push # create a new context to eval the follow
508
+ instance_eval(&block) if block_given?
509
+ blocks.each { |b| instance_eval(&b) }
510
+ settings.pop # when finished, we pop the context
511
+ reset_validations!
512
+ else
513
+ instance_eval(&block)
514
+ end
515
+ end
516
+
517
+ def inherited(subclass)
518
+ subclass.reset!
519
+ subclass.logger = logger.clone
520
+ end
521
+
522
+ def inherit_settings(other_stack)
523
+ settings.prepend other_stack
524
+ endpoints.each do |e|
525
+ e.settings.prepend(other_stack)
526
+ e.options[:app].inherit_settings(other_stack) if e.options[:app].respond_to?(:inherit_settings, true)
527
+ end
528
+ end
529
+
530
+ def inject_api_helpers_to_mod(mod, &block)
531
+ mod.extend(Helpers)
532
+ yield if block_given?
533
+ mod.api_changed(self)
534
+ end
535
+ end
536
+
537
+ def initialize
538
+ @route_set = Rack::Mount::RouteSet.new
539
+ add_head_not_allowed_methods_and_options_methods
540
+ self.class.endpoints.each do |endpoint|
541
+ endpoint.mount_in(@route_set)
542
+ end
543
+ @route_set.freeze
544
+ end
545
+
546
+ def call(env)
547
+ status, headers, body = @route_set.call(env)
548
+ headers.delete('X-Cascade') unless cascade?
549
+ [status, headers, body]
550
+ end
551
+
552
+ # Some requests may return a HTTP 404 error if grape cannot find a matching
553
+ # route. In this case, Rack::Mount adds a X-Cascade header to the response
554
+ # and sets it to 'pass', indicating to grape's parents they should keep
555
+ # looking for a matching route on other resources.
556
+ #
557
+ # In some applications (e.g. mounting grape on rails), one might need to trap
558
+ # errors from reaching upstream. This is effectivelly done by unsetting
559
+ # X-Cascade. Default :cascade is true.
560
+ def cascade?
561
+ return !!self.class.settings[:cascade] if self.class.settings.key?(:cascade)
562
+ return !!self.class.settings[:version_options][:cascade] if self.class.settings[:version_options] && self.class.settings[:version_options].key?(:cascade)
563
+ true
564
+ end
565
+
566
+ reset!
567
+
568
+ private
569
+
570
+ # For every resource add a 'OPTIONS' route that returns an HTTP 204 response
571
+ # with a list of HTTP methods that can be called. Also add a route that
572
+ # will return an HTTP 405 response for any HTTP method that the resource
573
+ # cannot handle.
574
+ def add_head_not_allowed_methods_and_options_methods
575
+ methods_per_path = {}
576
+ self.class.endpoints.each do |endpoint|
577
+ routes = endpoint.routes
578
+ routes.each do |route|
579
+ methods_per_path[route.route_path] ||= []
580
+ methods_per_path[route.route_path] << route.route_method
581
+ end
582
+ end
583
+
584
+ # The paths we collected are prepared (cf. Path#prepare), so they
585
+ # contain already versioning information when using path versioning.
586
+ # Disable versioning so adding a route won't prepend versioning
587
+ # informations again.
588
+ without_versioning do
589
+ methods_per_path.each do |path, methods|
590
+ allowed_methods = methods.dup
591
+ unless self.class.settings[:do_not_route_head]
592
+ allowed_methods |= ['HEAD'] if allowed_methods.include?('GET')
593
+ end
594
+
595
+ allow_header = (['OPTIONS'] | allowed_methods).join(', ')
596
+ unless self.class.settings[:do_not_route_options]
597
+ unless allowed_methods.include?('OPTIONS')
598
+ self.class.options(path, {}) do
599
+ header 'Allow', allow_header
600
+ status 204
601
+ ''
602
+ end
603
+ end
604
+ end
605
+
606
+ not_allowed_methods = %w(GET PUT POST DELETE PATCH HEAD) - allowed_methods
607
+ not_allowed_methods << 'OPTIONS' if self.class.settings[:do_not_route_options]
608
+ self.class.route(not_allowed_methods, path) do
609
+ header 'Allow', allow_header
610
+ status 405
611
+ ''
612
+ end
613
+ end
614
+ end
615
+ end
616
+
617
+ def without_versioning(&block)
618
+ self.class.settings.push(version: nil, version_options: nil)
619
+ yield
620
+ self.class.settings.pop
621
+ end
622
+
623
+ # This module extends user defined helpers
624
+ # to provide some API-specific functionality
625
+ module Helpers
626
+ attr_accessor :api
627
+ def params(name, &block)
628
+ @named_params ||= {}
629
+ @named_params.merge! name => block
630
+ end
631
+
632
+ def api_changed(new_api)
633
+ @api = new_api
634
+ process_named_params
635
+ end
636
+
637
+ protected
638
+
639
+ def process_named_params
640
+ if @named_params && @named_params.any?
641
+ api.imbue(:named_params, @named_params)
642
+ end
643
+ end
644
+ end
645
+ end
646
+ end