grape 0.9.0 → 0.10.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of grape might be problematic. Click here for more details.

Files changed (144) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +1 -66
  3. data/.rubocop_todo.yml +78 -17
  4. data/.travis.yml +7 -3
  5. data/Appraisals +7 -0
  6. data/CHANGELOG.md +24 -0
  7. data/CONTRIBUTING.md +7 -0
  8. data/Gemfile +1 -7
  9. data/Guardfile +1 -1
  10. data/README.md +560 -94
  11. data/RELEASING.md +1 -1
  12. data/Rakefile +10 -11
  13. data/UPGRADING.md +211 -3
  14. data/gemfiles/rails_3.gemfile +14 -0
  15. data/gemfiles/rails_4.gemfile +14 -0
  16. data/grape.gemspec +10 -9
  17. data/lib/backports/active_support/deep_dup.rb +49 -0
  18. data/lib/backports/active_support/duplicable.rb +88 -0
  19. data/lib/grape.rb +29 -2
  20. data/lib/grape/api.rb +59 -65
  21. data/lib/grape/dsl/api.rb +19 -0
  22. data/lib/grape/dsl/callbacks.rb +6 -4
  23. data/lib/grape/dsl/configuration.rb +49 -5
  24. data/lib/grape/dsl/helpers.rb +7 -8
  25. data/lib/grape/dsl/inside_route.rb +22 -10
  26. data/lib/grape/dsl/middleware.rb +5 -5
  27. data/lib/grape/dsl/parameters.rb +6 -2
  28. data/lib/grape/dsl/request_response.rb +23 -20
  29. data/lib/grape/dsl/routing.rb +52 -49
  30. data/lib/grape/dsl/settings.rb +110 -0
  31. data/lib/grape/dsl/validations.rb +14 -6
  32. data/lib/grape/endpoint.rb +104 -88
  33. data/lib/grape/exceptions/base.rb +2 -2
  34. data/lib/grape/exceptions/incompatible_option_values.rb +1 -1
  35. data/lib/grape/exceptions/invalid_formatter.rb +1 -1
  36. data/lib/grape/exceptions/invalid_versioner_option.rb +1 -1
  37. data/lib/grape/exceptions/invalid_with_option_for_represent.rb +1 -1
  38. data/lib/grape/exceptions/missing_mime_type.rb +1 -1
  39. data/lib/grape/exceptions/missing_option.rb +1 -1
  40. data/lib/grape/exceptions/missing_vendor_option.rb +1 -1
  41. data/lib/grape/exceptions/unknown_options.rb +1 -1
  42. data/lib/grape/exceptions/unknown_validator.rb +1 -1
  43. data/lib/grape/exceptions/validation.rb +1 -1
  44. data/lib/grape/exceptions/validation_errors.rb +2 -2
  45. data/lib/grape/formatter/serializable_hash.rb +1 -1
  46. data/lib/grape/formatter/xml.rb +1 -1
  47. data/lib/grape/locale/en.yml +2 -0
  48. data/lib/grape/middleware/auth/dsl.rb +26 -21
  49. data/lib/grape/middleware/auth/strategies.rb +1 -1
  50. data/lib/grape/middleware/auth/strategy_info.rb +0 -2
  51. data/lib/grape/middleware/base.rb +2 -2
  52. data/lib/grape/middleware/error.rb +1 -1
  53. data/lib/grape/middleware/formatter.rb +5 -5
  54. data/lib/grape/middleware/versioner.rb +1 -1
  55. data/lib/grape/middleware/versioner/header.rb +3 -3
  56. data/lib/grape/middleware/versioner/param.rb +2 -2
  57. data/lib/grape/middleware/versioner/path.rb +1 -1
  58. data/lib/grape/namespace.rb +1 -1
  59. data/lib/grape/path.rb +9 -3
  60. data/lib/grape/util/content_types.rb +16 -8
  61. data/lib/grape/util/inheritable_setting.rb +74 -0
  62. data/lib/grape/util/inheritable_values.rb +51 -0
  63. data/lib/grape/util/stackable_values.rb +52 -0
  64. data/lib/grape/util/strict_hash_configuration.rb +106 -0
  65. data/lib/grape/validations.rb +0 -220
  66. data/lib/grape/validations/attributes_iterator.rb +21 -0
  67. data/lib/grape/validations/params_scope.rb +176 -0
  68. data/lib/grape/validations/validators/all_or_none.rb +20 -0
  69. data/lib/grape/validations/validators/allow_blank.rb +30 -0
  70. data/lib/grape/validations/validators/at_least_one_of.rb +20 -0
  71. data/lib/grape/validations/validators/base.rb +37 -0
  72. data/lib/grape/validations/{coerce.rb → validators/coerce.rb} +3 -3
  73. data/lib/grape/validations/{default.rb → validators/default.rb} +1 -1
  74. data/lib/grape/validations/validators/exactly_one_of.rb +20 -0
  75. data/lib/grape/validations/validators/multiple_params_base.rb +26 -0
  76. data/lib/grape/validations/validators/mutual_exclusion.rb +25 -0
  77. data/lib/grape/validations/{presence.rb → validators/presence.rb} +2 -2
  78. data/lib/grape/validations/validators/regexp.rb +12 -0
  79. data/lib/grape/validations/validators/values.rb +26 -0
  80. data/lib/grape/version.rb +1 -1
  81. data/spec/grape/api_spec.rb +522 -343
  82. data/spec/grape/dsl/callbacks_spec.rb +4 -4
  83. data/spec/grape/dsl/configuration_spec.rb +48 -9
  84. data/spec/grape/dsl/helpers_spec.rb +6 -13
  85. data/spec/grape/dsl/inside_route_spec.rb +43 -4
  86. data/spec/grape/dsl/middleware_spec.rb +1 -10
  87. data/spec/grape/dsl/parameters_spec.rb +8 -1
  88. data/spec/grape/dsl/request_response_spec.rb +16 -22
  89. data/spec/grape/dsl/routing_spec.rb +21 -5
  90. data/spec/grape/dsl/settings_spec.rb +219 -0
  91. data/spec/grape/dsl/validations_spec.rb +8 -11
  92. data/spec/grape/endpoint_spec.rb +115 -86
  93. data/spec/grape/entity_spec.rb +33 -33
  94. data/spec/grape/exceptions/invalid_formatter_spec.rb +3 -5
  95. data/spec/grape/exceptions/invalid_versioner_option_spec.rb +4 -6
  96. data/spec/grape/exceptions/missing_mime_type_spec.rb +5 -6
  97. data/spec/grape/exceptions/missing_option_spec.rb +3 -5
  98. data/spec/grape/exceptions/unknown_options_spec.rb +3 -5
  99. data/spec/grape/exceptions/unknown_validator_spec.rb +3 -5
  100. data/spec/grape/exceptions/validation_errors_spec.rb +5 -5
  101. data/spec/grape/loading_spec.rb +44 -0
  102. data/spec/grape/middleware/auth/base_spec.rb +0 -4
  103. data/spec/grape/middleware/auth/dsl_spec.rb +2 -4
  104. data/spec/grape/middleware/auth/strategies_spec.rb +5 -6
  105. data/spec/grape/middleware/exception_spec.rb +8 -10
  106. data/spec/grape/middleware/formatter_spec.rb +13 -15
  107. data/spec/grape/middleware/versioner/accept_version_header_spec.rb +10 -10
  108. data/spec/grape/middleware/versioner/header_spec.rb +25 -25
  109. data/spec/grape/middleware/versioner/param_spec.rb +15 -17
  110. data/spec/grape/middleware/versioner/path_spec.rb +1 -2
  111. data/spec/grape/middleware/versioner_spec.rb +0 -1
  112. data/spec/grape/path_spec.rb +66 -45
  113. data/spec/grape/util/inheritable_setting_spec.rb +217 -0
  114. data/spec/grape/util/inheritable_values_spec.rb +63 -0
  115. data/spec/grape/util/stackable_values_spec.rb +115 -0
  116. data/spec/grape/util/strict_hash_configuration_spec.rb +38 -0
  117. data/spec/grape/validations/attributes_iterator_spec.rb +4 -0
  118. data/spec/grape/validations/params_scope_spec.rb +57 -0
  119. data/spec/grape/validations/validators/all_or_none_spec.rb +60 -0
  120. data/spec/grape/validations/validators/allow_blank_spec.rb +170 -0
  121. data/spec/grape/validations/{at_least_one_of_spec.rb → validators/at_least_one_of_spec.rb} +7 -3
  122. data/spec/grape/validations/{coerce_spec.rb → validators/coerce_spec.rb} +8 -11
  123. data/spec/grape/validations/{default_spec.rb → validators/default_spec.rb} +7 -9
  124. data/spec/grape/validations/{exactly_one_of_spec.rb → validators/exactly_one_of_spec.rb} +15 -11
  125. data/spec/grape/validations/{mutual_exclusion_spec.rb → validators/mutual_exclusion_spec.rb} +11 -9
  126. data/spec/grape/validations/{presence_spec.rb → validators/presence_spec.rb} +30 -30
  127. data/spec/grape/validations/{regexp_spec.rb → validators/regexp_spec.rb} +2 -4
  128. data/spec/grape/validations/{values_spec.rb → validators/values_spec.rb} +95 -23
  129. data/spec/grape/validations/{zh-CN.yml → validators/zh-CN.yml} +0 -0
  130. data/spec/grape/validations_spec.rb +335 -70
  131. data/spec/shared/versioning_examples.rb +7 -8
  132. data/spec/spec_helper.rb +2 -0
  133. data/spec/support/basic_auth_encode_helpers.rb +1 -1
  134. data/spec/support/content_type_helpers.rb +1 -1
  135. data/spec/support/versioned_helpers.rb +3 -3
  136. metadata +80 -33
  137. data/lib/grape/util/deep_merge.rb +0 -23
  138. data/lib/grape/util/hash_stack.rb +0 -120
  139. data/lib/grape/validations/at_least_one_of.rb +0 -25
  140. data/lib/grape/validations/exactly_one_of.rb +0 -26
  141. data/lib/grape/validations/mutual_exclusion.rb +0 -25
  142. data/lib/grape/validations/regexp.rb +0 -12
  143. data/lib/grape/validations/values.rb +0 -23
  144. data/spec/grape/util/hash_stack_spec.rb +0 -132
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 065f62bd14d8e5f24acc6b085a2fd2f7b5cc835a
4
- data.tar.gz: 1d95db90f66a6ddcae57416a557ac58977bae3b2
3
+ metadata.gz: 42e83be7840de205451cdb523c0a03f3a079300f
4
+ data.tar.gz: b16053b9a44c001cecae4f670de0073630b7b43e
5
5
  SHA512:
6
- metadata.gz: 51324bbb02265b2ba09dae20e06039c1cdba09e4fd6f40979328be0bc32bd4fb03628ef90862c41016d14002b8b280906b945532d96b295ab8624dc567a57836
7
- data.tar.gz: 93b0e47cbd02e8faad346d2e43b7f7d3bf56f05497331e87c6969dd17c65e8df651b69777e32f8aa9d68f6b17ae113e0094f6e04bf02fe25962959d71d7c7be5
6
+ metadata.gz: cd442f6727d63e80b631e36005c48ac93e639ad3bfd61f102cdcf2530dd67b528c9576e11bad705052108e7448181b74dd32fd4121100009e0bf42451eea6fa9
7
+ data.tar.gz: 8f79ccab0811111484bb920ae2b27edbe2c498db8e775fa063b4bf67655f5e6022e561fc22cfd6362e17e1ca27be2e857ef0ed51e8f34b00c4b4bed3e9e06847
data/.rubocop.yml CHANGED
@@ -2,71 +2,6 @@ AllCops:
2
2
  Exclude:
3
3
  - vendor/**/*
4
4
  - bin/**/*
5
-
6
- LineLength:
7
- Enabled: false
8
-
9
- MethodLength:
10
- Enabled: false
11
-
12
- ClassLength:
13
- Enabled: false
14
-
15
- Documentation:
16
- # don't require classes to be documented
17
- Enabled: false
18
-
19
- CollectionMethods:
20
- # don't prefer map to collect, recuce to inject
21
- Enabled: false
22
-
23
- Encoding:
24
- # no need to always specify encoding
25
- Enabled: false
26
-
27
- StringLiterals:
28
- # use single or double-quoted strings, as you please
29
- Enabled: false
30
-
31
- Void:
32
- # == operator used in void context in specs
33
- Enabled: false
34
-
35
- SignalException:
36
- # prefer raise to fail
37
- EnforcedStyle: only_raise
38
-
39
- RaiseArgs:
40
- # don't care for what kind of raise
41
- Enabled: false
42
-
43
- PerlBackrefs:
44
- # TODO: regular expression matching with $1, $2, etc.
45
- Enabled: false
46
-
47
- BlockNesting:
48
- # TODO: fix too much nesting
49
- Max: 4
50
-
51
- Lambda:
52
- # TODO: replace all lambda with -> or Proc
53
- Enabled: false
54
-
55
- Blocks:
56
- # allow multi-line blocks like expect { }
57
- Enabled: false
58
-
59
- WordArray:
60
- # %w vs. [ '', ... ]
61
- Enabled: false
62
-
63
- CyclomaticComplexity:
64
- Enabled: false
65
-
66
- DoubleNegation:
67
- Enabled: false
68
-
69
- PredicateName:
70
- Enabled: false
5
+ - gemfiles/**/*
71
6
 
72
7
  inherit_from: .rubocop_todo.yml
data/.rubocop_todo.yml CHANGED
@@ -1,64 +1,125 @@
1
1
  # This configuration was generated by `rubocop --auto-gen-config`
2
- # on 2014-08-12 13:16:39 +0200 using RuboCop version 0.24.1.
2
+ # on 2014-12-16 11:52:50 -0500 using RuboCop version 0.28.0.
3
3
  # The point is for the user to remove these configuration records
4
4
  # one by one as the offenses are removed from the code base.
5
5
  # Note that changes in the inspected code, or installation of new
6
6
  # versions of RuboCop, may require this file to be generated again.
7
7
 
8
- # Offense count: 28
8
+ # Offense count: 29
9
9
  # Cop supports --auto-correct.
10
10
  Lint/UnusedBlockArgument:
11
11
  Enabled: false
12
12
 
13
- # Offense count: 30
13
+ # Offense count: 26
14
14
  # Cop supports --auto-correct.
15
15
  Lint/UnusedMethodArgument:
16
16
  Enabled: false
17
17
 
18
- # Offense count: 5
18
+ # Offense count: 35
19
+ Metrics/AbcSize:
20
+ Max: 50
21
+
22
+ # Offense count: 1
23
+ Metrics/BlockNesting:
24
+ Max: 4
25
+
26
+ # Offense count: 4
27
+ # Configuration parameters: CountComments.
28
+ Metrics/ClassLength:
29
+ Max: 243
30
+
31
+ # Offense count: 15
32
+ Metrics/CyclomaticComplexity:
33
+ Max: 19
34
+
35
+ # Offense count: 545
36
+ # Configuration parameters: AllowURI, URISchemes.
37
+ Metrics/LineLength:
38
+ Max: 198
39
+
40
+ # Offense count: 42
41
+ # Configuration parameters: CountComments.
42
+ Metrics/MethodLength:
43
+ Max: 35
44
+
45
+ # Offense count: 13
46
+ Metrics/PerceivedComplexity:
47
+ Max: 21
48
+
49
+ # Offense count: 26
50
+ # Cop supports --auto-correct.
51
+ Style/Blocks:
52
+ Enabled: false
53
+
54
+ # Offense count: 6
19
55
  # Cop supports --auto-correct.
20
56
  # Configuration parameters: EnforcedStyle, SupportedStyles.
21
57
  Style/ClassCheck:
22
58
  Enabled: false
23
59
 
24
- # Offense count: 6
60
+ # Offense count: 157
61
+ Style/Documentation:
62
+ Enabled: false
63
+
64
+ # Offense count: 7
65
+ Style/DoubleNegation:
66
+ Enabled: false
67
+
68
+ # Offense count: 5
25
69
  Style/EachWithObject:
26
70
  Enabled: false
27
71
 
28
- # Offense count: 11
72
+ # Offense count: 1
73
+ Style/EmptyElse:
74
+ Enabled: false
75
+
76
+ # Offense count: 14
29
77
  # Configuration parameters: MinBodyLength.
30
78
  Style/GuardClause:
31
79
  Enabled: false
32
80
 
33
- # Offense count: 1
81
+ # Offense count: 3
34
82
  # Cop supports --auto-correct.
35
83
  # Configuration parameters: EnforcedStyle, SupportedStyles.
36
84
  Style/HashSyntax:
37
85
  Enabled: false
38
86
 
39
- # Offense count: 14
87
+ # Offense count: 15
40
88
  # Cop supports --auto-correct.
41
89
  Style/IndentArray:
42
90
  Enabled: false
43
91
 
44
- # Offense count: 2
45
- # Cop supports --auto-correct.
46
- # Configuration parameters: EnforcedStyle, SupportedStyles.
47
- Style/IndentHash:
48
- Enabled: true
92
+ # Offense count: 18
93
+ Style/Lambda:
94
+ Enabled: false
49
95
 
50
- # Offense count: 3
51
- # Configuration parameters: EnforcedStyle, SupportedStyles.
96
+ # Offense count: 1
97
+ # Configuration parameters: EnforcedStyle, MinBodyLength, SupportedStyles.
52
98
  Style/Next:
53
99
  Enabled: false
54
100
 
55
- # Offense count: 2
101
+ # Offense count: 3
56
102
  # Cop supports --auto-correct.
57
103
  # Configuration parameters: PreferredDelimiters.
58
104
  Style/PercentLiteralDelimiters:
59
105
  Enabled: false
60
106
 
61
- # Offense count: 1
107
+ # Offense count: 3
108
+ # Configuration parameters: NamePrefix, NamePrefixBlacklist.
109
+ Style/PredicateName:
110
+ Enabled: false
111
+
112
+ # Offense count: 9
113
+ # Configuration parameters: EnforcedStyle, SupportedStyles.
114
+ Style/RaiseArgs:
115
+ Enabled: false
116
+
117
+ # Offense count: 4
118
+ # Configuration parameters: MaxSlashes.
119
+ Style/RegexpLiteral:
120
+ Enabled: false
121
+
122
+ # Offense count: 4
62
123
  # Cop supports --auto-correct.
63
124
  # Configuration parameters: EnforcedStyle, SupportedStyles.
64
125
  Style/SpaceBeforeBlockBraces:
data/.travis.yml CHANGED
@@ -1,10 +1,9 @@
1
1
  language: ruby
2
2
 
3
- cache: bundler
3
+ sudo: false
4
4
 
5
5
  rvm:
6
- - 2.1.2
7
- - 2.1.0
6
+ - 2.1
8
7
  - 2.0.0
9
8
  - 1.9.3
10
9
  - rbx-2.2.10
@@ -16,3 +15,8 @@ matrix:
16
15
  allow_failures:
17
16
  - rvm: ruby-head
18
17
  - rvm: jruby-head
18
+
19
+ gemfile:
20
+ - Gemfile
21
+ - gemfiles/rails_3.gemfile
22
+ - gemfiles/rails_4.gemfile
data/Appraisals ADDED
@@ -0,0 +1,7 @@
1
+ appraise "rails-3" do
2
+ gem "rails", "3.2.19"
3
+ end
4
+
5
+ appraise "rails-4" do
6
+ gem "rails", "4.1.6"
7
+ end
data/CHANGELOG.md CHANGED
@@ -1,3 +1,27 @@
1
+ 0.10.0 (12/19/2014)
2
+ ===================
3
+
4
+ * [#803](https://github.com/intridea/grape/pull/803), [#820](https://github.com/intridea/grape/pull/820): Added `all_or_none_of` parameter validator - [@loveltyoic](https://github.com/loveltyoic), [@natecj](https://github.com/natecj).
5
+ * [#774](https://github.com/intridea/grape/pull/774): Extended `mutually_exclusive`, `exactly_one_of`, `at_least_one_of` to work inside any kind of group: `requires` or `optional`, `Hash` or `Array` - [@ShPakvel](https://github.com/ShPakvel).
6
+ * [#743](https://github.com/intridea/grape/pull/743): Added `allow_blank` parameter validator to validate non-empty strings - [@elado](https://github.com/elado).
7
+ * [#745](https://github.com/intridea/grape/pull/745): Removed `atom+xml`, `rss+xml`, and `jsonapi` content-types - [@akabraham](https://github.com/akabraham).
8
+ * [#745](https://github.com/intridea/grape/pull/745): Added `:binary, application/octet-stream` content-type - [@akabraham](https://github.com/akabraham).
9
+ * [#757](https://github.com/intridea/grape/pull/757): Changed `desc` can now be used with a block syntax - [@dspaeth-faber](https://github.com/dspaeth-faber).
10
+ * [#779](https://github.com/intridea/grape/pull/779): Fixed using `values` with a `default` proc - [@ShPakvel](https://github.com/ShPakvel).
11
+ * [#799](https://github.com/intridea/grape/pull/799): Fixed custom validators with required `Hash`, `Array` types - [@bwalex](https://github.com/bwalex).
12
+ * [#784](https://github.com/intridea/grape/pull/784): Fixed `present` to not overwrite the previously added contents of the response body whebn called more than once - [@mfunaro](https://github.com/mfunaro).
13
+ * [#809](https://github.com/intridea/grape/pull/809): Removed automatic `(.:format)` suffix on paths if you're using only one format (e.g., with `format :json`, `/path` will respond with JSON but `/path.xml` will be a 404) - [@ajvondrak](https://github.com/ajvondrak).
14
+ * [#816](https://github.com/intridea/grape/pull/816): Added ability to filter out missing params if params is a nested hash with `declared(params, include_missing: false)` - [@georgimitev](https://github.com/georgimitev).
15
+ * [#819](https://github.com/intridea/grape/pull/819): Allowed both `desc` and `description` in the params DSL - [@mzikherman](https://github.com/mzikherman).
16
+ * [#821](https://github.com/intridea/grape/pull/821): Fixed passing string value when hash is expected in params - [@rebelact](https://github.com/rebelact).
17
+ * [#824](https://github.com/intridea/grape/pull/824): Validate array params against list of acceptable values - [@dnd](https://github.com/dnd).
18
+ * [#813](https://github.com/intridea/grape/pull/813): Routing methods dsl refactored to get rid of explicit `paths` parameter - [@AlexYankee](https://github.com/AlexYankee).
19
+ * [#826](https://github.com/intridea/grape/pull/826): Find `coerce_type` for `Array` when not specified - [@manovotn](https://github.com/manovotn).
20
+ * [#645](https://github.com/intridea/grape/issues/645): Invoking `body false` will return `204 No Content` - [@dblock](https://github.com/dblock).
21
+ * [#801](https://github.com/intridea/grape/issues/801): Only evaluate permitted parameter `values` and `default` lazily on each request when declared as a proc - [@dblock](https://github.com/dblock).
22
+ * [#679](https://github.com/intridea/grape/issues/679): Fixed `OPTIONS` method returning 404 when combined with `prefix`- [@dblock](https://github.com/dblock).
23
+ * [#679](https://github.com/intridea/grape/issues/679): Fixed unsupported methods returning 404 instead of 405 when combined with `prefix`- [@dblock](https://github.com/dblock).
24
+
1
25
  0.9.0 (8/27/2014)
2
26
  =================
3
27
 
data/CONTRIBUTING.md CHANGED
@@ -32,6 +32,13 @@ bundle install
32
32
  bundle exec rake
33
33
  ```
34
34
 
35
+ Run tests against all supported versions of Rails.
36
+
37
+ ```
38
+ appraisal install
39
+ appraisal rake spec
40
+ ```
41
+
35
42
  #### Write Tests
36
43
 
37
44
  Try to write a test that reproduces the problem you're trying to fix or describes a feature that you want to build. Add to [spec/grape](spec/grape).
data/Gemfile CHANGED
@@ -3,14 +3,8 @@ source 'http://rubygems.org'
3
3
  gemspec
4
4
 
5
5
  group :development, :test do
6
- gem 'rubocop', '~> 0.24.1'
6
+ gem 'rubocop', '~> 0.28.0'
7
7
  gem 'guard'
8
8
  gem 'guard-rspec'
9
9
  gem 'guard-rubocop'
10
10
  end
11
-
12
- platforms :rbx do
13
- gem 'rubysl'
14
- gem 'rubinius-developer_tools'
15
- gem 'racc'
16
- end
data/Guardfile CHANGED
@@ -1,7 +1,7 @@
1
1
  guard :rspec, all_on_start: true, cmd: 'bundle exec rspec' do
2
2
  watch(%r{^spec/.+_spec\.rb$})
3
3
  watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
4
- watch('spec/spec_helper.rb') { "spec" }
4
+ watch('spec/spec_helper.rb') { 'spec' }
5
5
  end
6
6
 
7
7
  guard :rubocop do
data/README.md CHANGED
@@ -11,6 +11,7 @@
11
11
  - [Basic Usage](#basic-usage)
12
12
  - [Mounting](#mounting)
13
13
  - [Rack](#rack)
14
+ - [ActiveRecord without Rails](#activerecord-without-rails)
14
15
  - [Alongside Sinatra (or other frameworks)](#alongside-sinatra-or-other-frameworks)
15
16
  - [Rails](#rails)
16
17
  - [Modules](#modules)
@@ -21,7 +22,10 @@
21
22
  - [Param](#param)
22
23
  - [Describing Methods](#describing-methods)
23
24
  - [Parameters](#parameters)
25
+ - [Declared](#declared)
26
+ - [Include Missing](#include-missing)
24
27
  - [Parameter Validation and Coercion](#parameter-validation-and-coercion)
28
+ - [Built-in Validators](#built-in-validators)
25
29
  - [Namespace Validation and Coercion](#namespace-validation-and-coercion)
26
30
  - [Custom Validators](#custom-validators)
27
31
  - [Validation Errors](#validation-errors)
@@ -50,17 +54,22 @@
50
54
  - [Hypermedia](#hypermedia)
51
55
  - [Rabl](#rabl)
52
56
  - [Active Model Serializers](#active-model-serializers)
57
+ - [Sending Raw or No Data](#sending-raw-or-no-data)
53
58
  - [Authentication](#authentication)
54
59
  - [Describing and Inspecting an API](#describing-and-inspecting-an-api)
55
60
  - [Current Route and Endpoint](#current-route-and-endpoint)
56
61
  - [Before and After](#before-and-after)
57
62
  - [Anchoring](#anchoring)
63
+ - [Using Custom Middleware](#using-custom-middleware)
64
+ - [Rails Middleware](#rails-middleware)
65
+ - [Remote IP](#remote-ip)
58
66
  - [Writing Tests](#writing-tests)
59
67
  - [Writing Tests with Rack](#writing-tests-with-rack)
60
68
  - [Writing Tests with Rails](#writing-tests-with-rails)
61
69
  - [Stubbing Helpers](#stubbing-helpers)
62
70
  - [Reloading API Changes in Development](#reloading-api-changes-in-development)
63
- - [Rails 3.x](#rails-3x)
71
+ - [Reloading in Rack Applications](#reloading-in-rack-applications)
72
+ - [Reloading in Rails Applications](#reloading-in-rails-applications)
64
73
  - [Performance Monitoring](#performance-monitoring)
65
74
  - [Contributing to Grape](#contributing-to-grape)
66
75
  - [Hacking on Grape](#hacking-on-grape)
@@ -77,7 +86,8 @@ content negotiation, versioning and much more.
77
86
 
78
87
  ## Stable Release
79
88
 
80
- You're reading the documentation for Grape [0.9.0](https://github.com/intridea/grape/blob/v0.9.0/README.md).
89
+ You're reading the documentation for the stable release of Grape, 0.10.0.
90
+ Please read [UPGRADING](UPGRADING.md) when upgrading from a previous version.
81
91
 
82
92
  ## Project Resources
83
93
 
@@ -107,6 +117,7 @@ module Twitter
107
117
  class API < Grape::API
108
118
  version 'v1', using: :header, vendor: 'twitter'
109
119
  format :json
120
+ prefix :api
110
121
 
111
122
  helpers do
112
123
  def current_user
@@ -191,15 +202,29 @@ run Twitter::API
191
202
 
192
203
  And would respond to the following routes:
193
204
 
194
- GET /statuses/public_timeline(.json)
195
- GET /statuses/home_timeline(.json)
196
- GET /statuses/:id(.json)
197
- POST /statuses(.json)
198
- PUT /statuses/:id(.json)
199
- DELETE /statuses/:id(.json)
205
+ GET /api/statuses/public_timeline
206
+ GET /api/statuses/home_timeline
207
+ GET /api/statuses/:id
208
+ POST /api/statuses
209
+ PUT /api/statuses/:id
210
+ DELETE /api/statuses/:id
200
211
 
201
212
  Grape will also automatically respond to HEAD and OPTIONS for all GET, and just OPTIONS for all other routes.
202
213
 
214
+ ### ActiveRecord without Rails
215
+
216
+ If you want to use ActiveRecord within Grape, you will need to make sure that ActiveRecord's connection pool
217
+ is handled correctly.
218
+
219
+ The easiest way to achieve that is by using ActiveRecord's `ConnectionManagement` middleware in your
220
+ `config.ru` before mounting Grape, e.g.:
221
+
222
+ ```ruby
223
+ use ActiveRecord::ConnectionAdapters::ConnectionManagement
224
+
225
+ run Twitter::API
226
+ ```
227
+
203
228
  ### Alongside Sinatra (or other frameworks)
204
229
 
205
230
  If you wish to mount Grape alongside another Rack framework such as Sinatra, you can do so easily using
@@ -244,6 +269,13 @@ Modify `config/routes`:
244
269
  mount Twitter::API => '/'
245
270
  ```
246
271
 
272
+ Additionally, if the version of your Rails is 4.0+ and the application uses the default model layer of ActiveRecord, you will want to use the `hashie_rails` [gem](http://rubygems.org/gems/hashie_rails). This gem disables the security feature of `strong_params` at the model layer, allowing you the use of Grape's own params validation instead.
273
+
274
+ ```ruby
275
+ # Gemfile
276
+ gem "hashie_rails"
277
+ ```
278
+
247
279
  See below for additional code that enables reloading of API changes in development.
248
280
 
249
281
  ### Modules
@@ -355,12 +387,34 @@ version 'v1', using: :param, parameter: "v"
355
387
  You can add a description to API methods and namespaces.
356
388
 
357
389
  ```ruby
358
- desc "Returns your public timeline."
390
+ desc "Returns your public timeline." do
391
+ detail 'more details'
392
+ params API::Entities::Status.documentation
393
+ success API::Entities::Entity
394
+ failure [[401, 'Unauthorized', "Entities::Error"]]
395
+ named 'My named route'
396
+ headers [XAuthToken: {
397
+ description: 'Valdates your identity',
398
+ required: true
399
+ },
400
+ XOptionalHeader: {
401
+ description: 'Not really needed',
402
+ required: false
403
+ }
404
+ ]
405
+ end
359
406
  get :public_timeline do
360
407
  Status.limit(20)
361
408
  end
362
409
  ```
363
410
 
411
+ * `detail`: A more enhanced description
412
+ * `params`: Define parameters directly from an `Entity`
413
+ * `success`: (former entity) The `Entity` to be used to present by default this route
414
+ * `failure`: (former http_codes) A definition of the used failure HTTP Codes and Entities
415
+ * `named`: A helper to give a route a name and find it with this name in the documentation Hash
416
+ * `headers`: A definition of the used Headers
417
+
364
418
  ## Parameters
365
419
 
366
420
  Request parameters are available through the `params` hash object. This includes `GET`, `POST`
@@ -413,6 +467,176 @@ In the case of conflict between either of:
413
467
 
414
468
  route string parameters will have precedence.
415
469
 
470
+ #### Declared
471
+
472
+ Grape allows you to access only the parameters that have been declared by your `params` block. It filters out the params that have been passed, but are not allowed. Let's have the following api:
473
+
474
+ ````ruby
475
+ format :json
476
+
477
+ post 'users/signup' do
478
+ { "declared_params" => declared(params) }
479
+ end
480
+ ````
481
+
482
+ If we do not specify any params, declared will return an empty hash.
483
+
484
+ **Request**
485
+
486
+ ````bash
487
+ curl -X POST -H "Content-Type: application/json" localhost:9292/users/signup -d '{"user": {"first_name":"first name", "last_name": "last name"}}'
488
+ ````
489
+
490
+ **Response**
491
+
492
+ ````json
493
+ {
494
+ "declared_params": {}
495
+ }
496
+
497
+ ````
498
+
499
+ Once we add parameters requirements, grape will start returning only the declared params.
500
+
501
+ ````ruby
502
+ format :json
503
+
504
+ params do
505
+ requires :user, type: Hash do
506
+ requires :first_name, type: String
507
+ requires :last_name, type: String
508
+ end
509
+ end
510
+
511
+ post 'users/signup' do
512
+ { "declared_params" => declared(params) }
513
+ end
514
+ ````
515
+
516
+ **Request**
517
+
518
+ ````bash
519
+ curl -X POST -H "Content-Type: application/json" localhost:9292/users/signup -d '{"user": {"first_name":"first name", "last_name": "last name", "random": "never shown"}}'
520
+ ````
521
+
522
+ **Response**
523
+
524
+ ````json
525
+ {
526
+ "declared_params": {
527
+ "user": {
528
+ "first_name": "first name",
529
+ "last_name": "last name"
530
+ }
531
+ }
532
+ }
533
+ ````
534
+
535
+ #### Include missing
536
+
537
+ By default `declared(params)` returns parameters that has `nil` value. If you want to return only the parameters that have any value, you can use the `include_missing` option. By default it is `true`. Let's have the following api:
538
+
539
+ ````ruby
540
+ format :json
541
+
542
+ params do
543
+ requires :first_name, type: String
544
+ optional :last_name, type: String
545
+ end
546
+
547
+ post 'users/signup' do
548
+ { "declared_params" => declared(params, include_missing: false) }
549
+ end
550
+ ````
551
+
552
+ **Request**
553
+
554
+ ````bash
555
+ curl -X POST -H "Content-Type: application/json" localhost:9292/users/signup -d '{"user": {"first_name":"first name", "random": "never shown"}}'
556
+ ````
557
+
558
+ **Response with include_missing:false**
559
+
560
+ ````json
561
+ {
562
+ "declared_params": {
563
+ "user": {
564
+ "first_name": "first name"
565
+ }
566
+ }
567
+ }
568
+ ````
569
+
570
+ **Response with include_missing:true**
571
+
572
+ ````json
573
+ {
574
+ "declared_params": {
575
+ "first_name": "first name",
576
+ "last_name": null
577
+ }
578
+ }
579
+ ````
580
+
581
+ It also works on nested hashes:
582
+
583
+ ````ruby
584
+ format :json
585
+
586
+ params do
587
+ requires :user, :type => Hash do
588
+ requires :first_name, type: String
589
+ optional :last_name, type: String
590
+ requires :address, :type => Hash do
591
+ requires :city, type: String
592
+ optional :region, type: String
593
+ end
594
+ end
595
+ end
596
+
597
+ post 'users/signup' do
598
+ { "declared_params" => declared(params, include_missing: false) }
599
+ end
600
+ ````
601
+
602
+ **Request**
603
+
604
+ ````bash
605
+ curl -X POST -H "Content-Type: application/json" localhost:9292/users/signup -d '{"user": {"first_name":"first name", "random": "never shown", "address": { "city": "SF"}}}'
606
+ ````
607
+
608
+ **Response with include_missing:false**
609
+
610
+ ````json
611
+ {
612
+ "declared_params": {
613
+ "user": {
614
+ "first_name": "first name",
615
+ "address": {
616
+ "city": "SF"
617
+ }
618
+ }
619
+ }
620
+ }
621
+ ````
622
+
623
+ **Response with include_missing:true**
624
+
625
+ ````json
626
+ {
627
+ "declared_params": {
628
+ "user": {
629
+ "first_name": "first name",
630
+ "last_name": null,
631
+ "address": {
632
+ "city": "Zurich",
633
+ "region": null
634
+ }
635
+ }
636
+ }
637
+ }
638
+ ````
639
+
416
640
  ## Parameter Validation and Coercion
417
641
 
418
642
  You can define validations and coercion options for your parameters using a `params` block.
@@ -447,27 +671,25 @@ params do
447
671
  end
448
672
  ```
449
673
 
450
- Parameters can be restricted to a specific set of values with the `:values` option.
451
-
452
- Default values are eagerly evaluated. Above `:non_random_number` will evaluate to the same
453
- number for each call to the endpoint of this `params` block. To have the default evaluate
454
- at calltime use a lambda, like `:random_number` above.
674
+ Note that default values will be passed through to any validation options specified.
675
+ The following example will always fail if `:color` is not explicitly provided.
455
676
 
456
677
  ```ruby
457
678
  params do
458
- requires :status, type: Symbol, values: [:not_started, :processing, :done]
679
+ optional :color, type: String, default: 'blue', values: ['red', 'green']
459
680
  end
460
681
  ```
461
682
 
462
- The `:values` option can also be supplied with a `Proc` to be evalutated at runtime. For example, given a status
463
- model you may want to restrict by hashtags that you have previously defined in the `HashTag` model.
683
+ The correct implementation is to ensure the default value passes all validations.
464
684
 
465
685
  ```ruby
466
686
  params do
467
- requires :hashtag, type: String, values: -> { Hashtag.all.map(&:tag) }
687
+ optional :color, type: String, default: 'blue', values: ['blue', 'red', 'green']
468
688
  end
469
689
  ```
470
690
 
691
+ #### Validation of Nested Parameters
692
+
471
693
  Parameters can be nested using `group` or by calling `requires` or `optional` with a block.
472
694
  In the above example, this means `params[:media][:url]` is required along with `params[:id]`,
473
695
  and `params[:audio][:format]` is required only if `params[:audio]` is present.
@@ -489,6 +711,66 @@ params do
489
711
  end
490
712
  ```
491
713
 
714
+ ### Built-in Validators
715
+
716
+ #### `allow_blank`
717
+
718
+ Parameters can be defined as `allow_blank`, ensuring that they contain a value. By default, `requires`
719
+ only validates that a parameter was sent in the request, regardless its value. With `allow_blank`,
720
+ empty values or whitespace only values are invalid.
721
+
722
+ `allow_blank` can be combined with both `requires` and `optional`. If the parameter is required, it has to contain
723
+ a value. If it's optional, it's possible to not send it in the request, but if it's being sent, it has to have
724
+ some value, and not an empty string/only whitespaces.
725
+
726
+
727
+ ```ruby
728
+ params do
729
+ requires :username, allow_blank: false
730
+ optional :first_name, allow_blank: false
731
+ end
732
+ ```
733
+
734
+ #### `values`
735
+
736
+ Parameters can be restricted to a specific set of values with the `:values` option.
737
+
738
+ Default values are eagerly evaluated. Above `:non_random_number` will evaluate to the same
739
+ number for each call to the endpoint of this `params` block. To have the default evaluate
740
+ lazily with each request use a lambda, like `:random_number` above.
741
+
742
+ ```ruby
743
+ params do
744
+ requires :status, type: Symbol, values: [:not_started, :processing, :done]
745
+ optional :numbers, type: Array[Integer], default: 1, values: [1, 2, 3, 5, 8]
746
+ end
747
+ ```
748
+
749
+ The `:values` option can also be supplied with a `Proc`, evaluated lazily with each request.
750
+ For example, given a status model you may want to restrict by hashtags that you have
751
+ previously defined in the `HashTag` model.
752
+
753
+ ```ruby
754
+ params do
755
+ requires :hashtag, type: String, values: -> { Hashtag.all.map(&:tag) }
756
+ end
757
+ ```
758
+
759
+ #### `regexp`
760
+
761
+ Parameters can be restricted to match a specific regular expression with the `:regexp` option. If the value
762
+ is nil or does not match the regular expression an error will be returned. Note that this is true for both `requires`
763
+ and `optional` parameters.
764
+
765
+ ```ruby
766
+ params do
767
+ requires :email, regexp: /.+@.+/
768
+ end
769
+ ```
770
+
771
+
772
+ #### `mutually_exclusive`
773
+
492
774
  Parameters can be defined as `mutually_exclusive`, ensuring that they aren't present at the same time in a request.
493
775
 
494
776
  ```ruby
@@ -514,6 +796,8 @@ end
514
796
 
515
797
  **Warning**: Never define mutually exclusive sets with any required params. Two mutually exclusive required params will mean params are never valid, thus making the endpoint useless. One required param mutually exclusive with an optional param will mean the latter is never valid.
516
798
 
799
+ #### `exactly_one_of`
800
+
517
801
  Parameters can be defined as 'exactly_one_of', ensuring that exactly one parameter gets selected.
518
802
 
519
803
  ```ruby
@@ -524,6 +808,8 @@ params do
524
808
  end
525
809
  ```
526
810
 
811
+ #### `at_least_one_of`
812
+
527
813
  Parameters can be defined as 'at_least_one_of', ensuring that at least one parameter gets selected.
528
814
 
529
815
  ```ruby
@@ -531,7 +817,51 @@ params do
531
817
  optional :beer
532
818
  optional :wine
533
819
  optional :juice
534
- at_least_one :beer, :wine, :juice
820
+ at_least_one_of :beer, :wine, :juice
821
+ end
822
+ ```
823
+
824
+ #### `all_or_none_of`
825
+
826
+ Parameters can be defined as 'all_or_none_of', ensuring that all or none of parameters gets selected.
827
+
828
+ ```ruby
829
+ params do
830
+ optional :beer
831
+ optional :wine
832
+ optional :juice
833
+ all_or_none_of :beer, :wine, :juice
834
+ end
835
+ ```
836
+
837
+ #### Nested `mutually_exclusive`, `exactly_one_of`, `at_least_one_of`, `all_or_none_of`
838
+
839
+ All of these methods can be used at any nested level.
840
+
841
+ ```ruby
842
+ params do
843
+ requires :food do
844
+ optional :meat
845
+ optional :fish
846
+ optional :rice
847
+ at_least_one_of :meat, :fish, :rice
848
+ end
849
+ group :drink do
850
+ optional :beer
851
+ optional :wine
852
+ optional :juice
853
+ exactly_one_of :beer, :wine, :juice
854
+ end
855
+ optional :dessert do
856
+ optional :cake
857
+ optional :icecream
858
+ mutually_exclusive :cake, :icecream
859
+ end
860
+ optional :recipe do
861
+ optional :oil
862
+ optional :meat
863
+ all_or_none_of :oil, :meat
864
+ end
535
865
  end
536
866
  ```
537
867
 
@@ -579,10 +909,10 @@ end
579
909
  ### Custom Validators
580
910
 
581
911
  ```ruby
582
- class AlphaNumeric < Grape::Validations::Validator
912
+ class AlphaNumeric < Grape::Validations::Base
583
913
  def validate_param!(attr_name, params)
584
914
  unless params[attr_name] =~ /^[[:alnum:]]+$/
585
- raise Grape::Exceptions::Validation, param: @scope.full_name(attr_name), message: "must consist of alpha-numeric characters"
915
+ raise Grape::Exceptions::Validation, params: [@scope.full_name(attr_name)], message: "must consist of alpha-numeric characters"
586
916
  end
587
917
  end
588
918
  end
@@ -597,10 +927,10 @@ end
597
927
  You can also create custom classes that take parameters.
598
928
 
599
929
  ```ruby
600
- class Length < Grape::Validations::SingleOptionValidator
930
+ class Length < Grape::Validations::Base
601
931
  def validate_param!(attr_name, params)
602
932
  unless params[attr_name].length <= @option
603
- raise Grape::Exceptions::Validation, param: @scope.full_name(attr_name), message: "must be at the most #{@option} characters long"
933
+ raise Grape::Exceptions::Validation, params: [@scope.full_name(attr_name)], message: "must be at the most #{@option} characters long"
604
934
  end
605
935
  end
606
936
  end
@@ -956,14 +1286,18 @@ end
956
1286
  The following example specifies the entity to use in the `http_codes` definition.
957
1287
 
958
1288
  ```
959
- desc 'My Route', http_codes: [[408, 'Unauthorized', API::Error]]
1289
+ desc 'My Route' do
1290
+ failure [[408, 'Unauthorized', API::Error]]
1291
+ end
960
1292
  error!({ message: 'Unauthorized' }, 408)
961
1293
  ```
962
1294
 
963
1295
  The following example specifies the presented entity explicitly in the error message.
964
1296
 
965
1297
  ```ruby
966
- desc 'My Route', http_codes: [[408, 'Unauthorized']]
1298
+ desc 'My Route' do
1299
+ failure [[408, 'Unauthorized']]
1300
+ end
967
1301
  error!({ message: 'Unauthorized', with: API::Error }, 408)
968
1302
  ```
969
1303
 
@@ -1176,16 +1510,115 @@ end
1176
1510
 
1177
1511
  ## API Formats
1178
1512
 
1179
- By default, Grape supports _XML_, _JSON_, and _TXT_ content-types. The default format is `:txt`.
1513
+ Your API can declare which content-types to support by using `content_type`. If you do not specify any, Grape will support
1514
+ _XML_, _JSON_, _BINARY_, and _TXT_ content-types. The default format is `:txt`; you can change this with `default_format`.
1515
+ Essentially, the two APIs below are equivalent.
1516
+
1517
+ ```ruby
1518
+ class Twitter::API < Grape::API
1519
+ # no content_type declarations, so Grape uses the defaults
1520
+ end
1521
+
1522
+ class Twitter::API < Grape::API
1523
+ # the following declarations are equivalent to the defaults
1524
+
1525
+ content_type :xml, 'application/xml'
1526
+ content_type :json, 'application/json'
1527
+ content_type :binary, 'application/octet-stream'
1528
+ content_type :txt, 'text/plain'
1529
+
1530
+ default_format :txt
1531
+ end
1532
+ ```
1533
+
1534
+ If you declare any `content_type` whatsoever, the Grape defaults will be overridden. For example, the following API will only
1535
+ support the `:xml` and `:rss` content-types, but not `:txt`, `:json`, or `:binary`. Importantly, this means the `:txt`
1536
+ default format is not supported! So, make sure to set a new `default_format`.
1537
+
1538
+ ```ruby
1539
+ class Twitter::API < Grape::API
1540
+ content_type :xml, 'application/xml'
1541
+ content_type :rss, 'application/xml+rss'
1542
+
1543
+ default_format :xml
1544
+ end
1545
+ ```
1546
+
1547
+ Serialization takes place automatically. For example, you do not have to call `to_json` in each JSON API endpoint
1548
+ implementation. The response format (and thus the automatic serialization) is determined in the following order:
1549
+ * Use the file extension, if specified. If the file is .json, choose the JSON format.
1550
+ * Use the value of the `format` parameter in the query string, if specified.
1551
+ * Use the format set by the `format` option, if specified.
1552
+ * Attempt to find an acceptable format from the `Accept` header.
1553
+ * Use the default format, if specified by the `default_format` option.
1554
+ * Default to `:txt`.
1555
+
1556
+ For example, consider the following API.
1557
+
1558
+ ```ruby
1559
+ class MultipleFormatAPI < Grape::API
1560
+ content_type :xml, 'application/xml'
1561
+ content_type :json, 'application/json'
1562
+
1563
+ default_format :json
1564
+
1565
+ get :hello do
1566
+ { hello: 'world' }
1567
+ end
1568
+ end
1569
+ ```
1570
+
1571
+ * `GET /hello` (with an `Accept: */*` header) does not have an extension or a `format` parameter, so it will respond with
1572
+ JSON (the default format).
1573
+ * `GET /hello.xml` has a recognized extension, so it will respond with XML.
1574
+ * `GET /hello?format=xml` has a recognized `format` parameter, so it will respond with XML.
1575
+ * `GET /hello.xml?format=json` has a recognized extension (which takes precedence over the `format` parameter), so it will
1576
+ respond with XML.
1577
+ * `GET /hello.xls` (with an `Accept: */*` header) has an extension, but that extension is not recognized, so it will respond
1578
+ with JSON (the default format).
1579
+ * `GET /hello.xls` with an `Accept: application/xml` header has an unrecognized extension, but the `Accept` header
1580
+ corresponds to a recognized format, so it will respond with XML.
1581
+ * `GET /hello.xls` with an `Accept: text/plain` header has an unrecognized extension *and* an unrecognized `Accept` header,
1582
+ so it will respond with JSON (the default format).
1583
+
1584
+ You can override this process explicitly by specifying `env['api.format']` in the API itself.
1585
+ For example, the following API will let you upload arbitrary files and return their contents as an attachment with the correct MIME type.
1586
+
1587
+ ```ruby
1588
+ class Twitter::API < Grape::API
1589
+ post "attachment" do
1590
+ filename = params[:file][:filename]
1591
+ content_type MIME::Types.type_for(filename)[0].to_s
1592
+ env['api.format'] = :binary # there's no formatter for :binary, data will be returned "as is"
1593
+ header "Content-Disposition", "attachment; filename*=UTF-8''#{URI.escape(filename)}"
1594
+ params[:file][:tempfile].read
1595
+ end
1596
+ end
1597
+ ```
1598
+
1599
+ You can have your API only respond to a single format with `format`. If you use this, the API will **not** respond to file
1600
+ extensions. For example, consider the following API.
1601
+
1602
+ ```ruby
1603
+ class SingleFormatAPI < Grape::API
1604
+ format :json
1180
1605
 
1181
- Serialization takes place automatically. For example, you do not have to call `to_json` in each JSON API implementation.
1606
+ get :hello do
1607
+ { hello: 'world' }
1608
+ end
1609
+ end
1610
+ ```
1182
1611
 
1183
- Your API can declare which types to support by using `content_type`. Response format is determined by the
1184
- request's extension, an explicit `format` parameter in the query string, or `Accept` header.
1612
+ * `GET /hello` will respond with JSON.
1613
+ * `GET /hello.xml`, `GET /hello.json`, `GET /hello.foobar`, or *any* other extension will respond with an HTTP 404 error code.
1614
+ * `GET /hello?format=xml` will respond with an HTTP 406 error code, because the XML format specified by the request parameter
1615
+ is not supported.
1616
+ * `GET /hello` with an `Accept: application/xml` header will still respond with JSON, since it could not negotiate a
1617
+ recognized content-type from the headers and JSON is the effective default.
1185
1618
 
1186
- The following API will only respond to the JSON content-type and will not parse any other input than `application/json`,
1187
- `application/x-www-form-urlencoded`, `multipart/form-data`, `multipart/related` and `multipart/mixed`. All other requests
1188
- will fail with an HTTP 406 error code.
1619
+ The formats apply to parsing, too. The following API will only respond to the JSON content-type and will not parse any other
1620
+ input than `application/json`, `application/x-www-form-urlencoded`, `multipart/form-data`, `multipart/related` and
1621
+ `multipart/mixed`. All other requests will fail with an HTTP 406 error code.
1189
1622
 
1190
1623
  ```ruby
1191
1624
  class Twitter::API < Grape::API
@@ -1238,45 +1671,13 @@ class Twitter::API < Grape::API
1238
1671
  end
1239
1672
  ```
1240
1673
 
1241
- Built-in formats are the following.
1674
+ Built-in formatters are the following.
1242
1675
 
1243
- * `:json` and `:jsonapi`: use object's `to_json` when available, otherwise call `MultiJson.dump`
1676
+ * `:json`: use object's `to_json` when available, otherwise call `MultiJson.dump`
1244
1677
  * `:xml`: use object's `to_xml` when available, usually via `MultiXml`, otherwise call `to_s`
1245
1678
  * `:txt`: use object's `to_txt` when available, otherwise `to_s`
1246
1679
  * `:serializable_hash`: use object's `serializable_hash` when available, otherwise fallback to `:json`
1247
-
1248
- Use `default_format` to set the fallback format when the format could not be determined from the `Accept` header.
1249
- See below for the order for choosing the API format.
1250
-
1251
- ```ruby
1252
- class Twitter::API < Grape::API
1253
- default_format :json
1254
- end
1255
- ```
1256
-
1257
- The order for choosing the format is the following.
1258
-
1259
- * Use the file extension, if specified. If the file is .json, choose the JSON format.
1260
- * Use the value of the `format` parameter in the query string, if specified.
1261
- * Use the format set by the `format` option, if specified.
1262
- * Attempt to find an acceptable format from the `Accept` header.
1263
- * Use the default format, if specified by the `default_format` option.
1264
- * Default to `:txt`.
1265
-
1266
- You can override this process explicitly by specifying `env['api.format']` in the API itself.
1267
- For example, the following API will let you upload arbitrary files and return their contents as an attachment with the correct MIME type.
1268
-
1269
- ```ruby
1270
- class Twitter::API < Grape::API
1271
- post "attachment" do
1272
- filename = params[:file][:filename]
1273
- content_type MIME::Types.type_for(filename)[0].to_s
1274
- env['api.format'] = :binary # there's no formatter for :binary, data will be returned "as is"
1275
- header "Content-Disposition", "attachment; filename*=UTF-8''#{URI.escape(filename)}"
1276
- params[:file][:tempfile].read
1277
- end
1278
- end
1279
- ```
1680
+ * `:binary`: data will be returned "as is"
1280
1681
 
1281
1682
  ### JSONP
1282
1683
 
@@ -1398,9 +1799,9 @@ module API
1398
1799
  class Statuses < Grape::API
1399
1800
  version 'v1'
1400
1801
 
1401
- desc 'Statuses index', {
1802
+ desc 'Statuses index' do
1402
1803
  params: API::Entities::Status.documentation
1403
- }
1804
+ end
1404
1805
  get '/statuses' do
1405
1806
  statuses = Status.all
1406
1807
  type = current_user.admin? ? :full : :default
@@ -1486,6 +1887,32 @@ You can use [Active Model Serializers](https://github.com/rails-api/active_model
1486
1887
  [grape-active_model_serializers](https://github.com/jrhe/grape-active_model_serializers) gem, which defines a custom Grape AMS
1487
1888
  formatter.
1488
1889
 
1890
+ ## Sending Raw or No Data
1891
+
1892
+ In general, use the binary format to send raw data.
1893
+
1894
+ ```ruby
1895
+ class API < Grape::API
1896
+ get '/file' do
1897
+ content_type 'application/octet-stream'
1898
+ File.binread 'file.bin'
1899
+ end
1900
+ end
1901
+ ```
1902
+
1903
+ You can also set the response body explicitly with `body`.
1904
+
1905
+ ```ruby
1906
+ class API < Grape::API
1907
+ get '/' do
1908
+ content_type 'text/plain'
1909
+ body 'Hello World'
1910
+ # return value ignored
1911
+ end
1912
+ end
1913
+ ```
1914
+
1915
+ Use `body false` to return `204 No Content` without any data or content-type.
1489
1916
 
1490
1917
  ## Authentication
1491
1918
 
@@ -1515,7 +1942,7 @@ Grape can use custom Middleware for authentication. How to implement these
1515
1942
  Middleware have a look at `Rack::Auth::Basic` or similar implementations.
1516
1943
 
1517
1944
 
1518
- For registering a Middlewar you need the following options:
1945
+ For registering a Middleware you need the following options:
1519
1946
 
1520
1947
  * `label` - the name for your authenticator to use it later
1521
1948
  * `MiddlewareClass` - the MiddlewareClass to use for authentication
@@ -1529,7 +1956,7 @@ Example:
1529
1956
  Grape::Middleware::Auth::Strategies.add(:my_auth, AuthMiddleware, ->(options) { [options[:realm]] } )
1530
1957
 
1531
1958
 
1532
- auth :my_auth ,{ real: 'Test Api'} do |credentials|
1959
+ auth :my_auth, { realm: 'Test Api'} do |credentials|
1533
1960
  # lookup the user's password here
1534
1961
  { 'user1' => 'password1' }[username]
1535
1962
  end
@@ -1540,26 +1967,34 @@ Use [warden-oauth2](https://github.com/opperator/warden-oauth2) or [rack-oauth2]
1540
1967
 
1541
1968
  ## Describing and Inspecting an API
1542
1969
 
1543
- Grape routes can be reflected at runtime. This can notably be useful for generating
1544
- documentation.
1970
+ Grape routes can be reflected at runtime. This can notably be useful for generating documentation.
1971
+
1972
+ Grape exposes arrays of API versions and compiled routes. Each route contains a `route_prefix`, `route_version`, `route_namespace`, `route_method`, `route_path` and `route_params`. You can add custom route settings to the route metadata with `route_setting`.
1973
+
1974
+ ```ruby
1975
+ class TwitterAPI < Grape::API
1976
+ version 'v1'
1977
+ desc "Includes custom settings."
1978
+ route_setting :custom, key: 'value'
1979
+ get do
1545
1980
 
1546
- Grape exposes arrays of API versions and compiled routes. Each route
1547
- contains a `route_prefix`, `route_version`, `route_namespace`, `route_method`,
1548
- `route_path` and `route_params`. The description and the optional hash that
1549
- follows the API path may contain any number of keys and its values are also
1550
- accessible via dynamically-generated `route_[name]` functions.
1981
+ end
1982
+ end
1983
+ ```
1984
+
1985
+ Examine the routes at runtime.
1551
1986
 
1552
1987
  ```ruby
1553
1988
  TwitterAPI::versions # yields [ 'v1', 'v2' ]
1554
1989
  TwitterAPI::routes # yields an array of Grape::Route objects
1555
- TwitterAPI::routes[0].route_version # yields 'v1'
1556
- TwitterAPI::routes[0].route_description # etc.
1990
+ TwitterAPI::routes[0].route_version # => 'v1'
1991
+ TwitterAPI::routes[0].route_description # => 'Includes custom settings.'
1992
+ TwitterAPI::routes[0].route_settings[:custom] # => { key: 'value' }
1557
1993
  ```
1558
1994
 
1559
1995
  ## Current Route and Endpoint
1560
1996
 
1561
- It's possible to retrieve the information about the current route from within an API
1562
- call with `route`.
1997
+ It's possible to retrieve the information about the current route from within an API call with `route`.
1563
1998
 
1564
1999
  ```ruby
1565
2000
  class MyAPI < Grape::API
@@ -1703,6 +2138,35 @@ specification and using the `PATH_INFO` Rack environment variable, using
1703
2138
  `env["PATH_INFO"]`. This will hold everything that comes after the '/statuses/'
1704
2139
  part.
1705
2140
 
2141
+ # Using Custom Middleware
2142
+
2143
+ ## Rails Middleware
2144
+
2145
+ Note that when you're using Grape mounted on Rails you don't have to use Rails middleware because it's already included into your middleware stack.
2146
+ You only have to implement the helpers to access the specific `env` variable.
2147
+
2148
+ ### Remote IP
2149
+
2150
+ By default you can access remote IP with `request.ip`. This is the remote IP address implemented by Rack. Sometimes it is desirable to get the remote IP [Rails-style](http://stackoverflow.com/questions/10997005/whats-the-difference-between-request-remote-ip-and-request-ip-in-rails) with `ActionDispatch::RemoteIp`.
2151
+
2152
+ Add `gem 'actionpack'` to your Gemfile and `require 'action_dispatch/middleware/remote_ip.rb'`. Use the middleware in your API and expose a `client_ip` helper. See [this documentation](http://api.rubyonrails.org/classes/ActionDispatch/RemoteIp.html) for additional options.
2153
+
2154
+ ```ruby
2155
+ class API < Grape::API
2156
+ use ActionDispatch::RemoteIp
2157
+
2158
+ helpers do
2159
+ def client_ip
2160
+ env["action_dispatch.remote_ip"].to_s
2161
+ end
2162
+ end
2163
+
2164
+ get :remopte_ip do
2165
+ { ip: client_ip }
2166
+ end
2167
+ end
2168
+ ```
2169
+
1706
2170
  ## Writing Tests
1707
2171
 
1708
2172
  You can test a Grape API with RSpec by making HTTP requests and examining the response.
@@ -1722,17 +2186,17 @@ describe Twitter::API do
1722
2186
  end
1723
2187
 
1724
2188
  describe Twitter::API do
1725
- describe "GET /api/v1/statuses" do
2189
+ describe "GET /api/statuses/public_timeline" do
1726
2190
  it "returns an empty array of statuses" do
1727
- get "/api/v1/statuses"
2191
+ get "/api/statuses/public_timeline"
1728
2192
  expect(last_response.status).to eq(200)
1729
2193
  expect(JSON.parse(last_response.body)).to eq []
1730
2194
  end
1731
2195
  end
1732
- describe "GET /api/v1/statuses/:id" do
2196
+ describe "GET /api/statuses/:id" do
1733
2197
  it "returns a status by id" do
1734
2198
  status = Status.create!
1735
- get "/api/v1/statuses/#{status.id}"
2199
+ get "/api/statuses/#{status.id}"
1736
2200
  expect(last_response.body).to eq status.to_json
1737
2201
  end
1738
2202
  end
@@ -1744,17 +2208,17 @@ end
1744
2208
 
1745
2209
  ```ruby
1746
2210
  describe Twitter::API do
1747
- describe "GET /api/v1/statuses" do
2211
+ describe "GET /api/statuses/public_timeline" do
1748
2212
  it "returns an empty array of statuses" do
1749
- get "/api/v1/statuses"
2213
+ get "/api/statuses/public_timeline"
1750
2214
  expect(response.status).to eq(200)
1751
2215
  expect(JSON.parse(response.body)).to eq []
1752
2216
  end
1753
2217
  end
1754
- describe "GET /api/v1/statuses/:id" do
2218
+ describe "GET /api/statuses/:id" do
1755
2219
  it "returns a status by id" do
1756
2220
  status = Status.create!
1757
- get "/api/v1/statuses/#{status.id}"
2221
+ get "/api/statuses/#{status.id}"
1758
2222
  expect(response.body).to eq status.to_json
1759
2223
  end
1760
2224
  end
@@ -1766,9 +2230,7 @@ In Rails, HTTP request tests would go into the `spec/requests` group. You may wa
1766
2230
 
1767
2231
  ```ruby
1768
2232
  RSpec.configure do |config|
1769
- config.include RSpec::Rails::RequestExampleGroup, type: :request, example_group: {
1770
- file_path: /spec\/api/
1771
- }
2233
+ config.include RSpec::Rails::RequestExampleGroup, type: :request, file_path: /spec\/api/
1772
2234
  end
1773
2235
  ```
1774
2236
 
@@ -1799,7 +2261,11 @@ end
1799
2261
 
1800
2262
  ## Reloading API Changes in Development
1801
2263
 
1802
- ### Rails 3.x
2264
+ ### Reloading in Rack Applications
2265
+
2266
+ Use [grape-reload](https://github.com/AlexYankee/grape-reload).
2267
+
2268
+ ### Reloading in Rails Applications
1803
2269
 
1804
2270
  Add API paths to `config/application.rb`.
1805
2271