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.
- checksums.yaml +4 -4
- data/.rubocop.yml +1 -66
- data/.rubocop_todo.yml +78 -17
- data/.travis.yml +7 -3
- data/Appraisals +7 -0
- data/CHANGELOG.md +24 -0
- data/CONTRIBUTING.md +7 -0
- data/Gemfile +1 -7
- data/Guardfile +1 -1
- data/README.md +560 -94
- data/RELEASING.md +1 -1
- data/Rakefile +10 -11
- data/UPGRADING.md +211 -3
- data/gemfiles/rails_3.gemfile +14 -0
- data/gemfiles/rails_4.gemfile +14 -0
- data/grape.gemspec +10 -9
- data/lib/backports/active_support/deep_dup.rb +49 -0
- data/lib/backports/active_support/duplicable.rb +88 -0
- data/lib/grape.rb +29 -2
- data/lib/grape/api.rb +59 -65
- data/lib/grape/dsl/api.rb +19 -0
- data/lib/grape/dsl/callbacks.rb +6 -4
- data/lib/grape/dsl/configuration.rb +49 -5
- data/lib/grape/dsl/helpers.rb +7 -8
- data/lib/grape/dsl/inside_route.rb +22 -10
- data/lib/grape/dsl/middleware.rb +5 -5
- data/lib/grape/dsl/parameters.rb +6 -2
- data/lib/grape/dsl/request_response.rb +23 -20
- data/lib/grape/dsl/routing.rb +52 -49
- data/lib/grape/dsl/settings.rb +110 -0
- data/lib/grape/dsl/validations.rb +14 -6
- data/lib/grape/endpoint.rb +104 -88
- data/lib/grape/exceptions/base.rb +2 -2
- data/lib/grape/exceptions/incompatible_option_values.rb +1 -1
- data/lib/grape/exceptions/invalid_formatter.rb +1 -1
- data/lib/grape/exceptions/invalid_versioner_option.rb +1 -1
- data/lib/grape/exceptions/invalid_with_option_for_represent.rb +1 -1
- data/lib/grape/exceptions/missing_mime_type.rb +1 -1
- data/lib/grape/exceptions/missing_option.rb +1 -1
- data/lib/grape/exceptions/missing_vendor_option.rb +1 -1
- data/lib/grape/exceptions/unknown_options.rb +1 -1
- data/lib/grape/exceptions/unknown_validator.rb +1 -1
- data/lib/grape/exceptions/validation.rb +1 -1
- data/lib/grape/exceptions/validation_errors.rb +2 -2
- data/lib/grape/formatter/serializable_hash.rb +1 -1
- data/lib/grape/formatter/xml.rb +1 -1
- data/lib/grape/locale/en.yml +2 -0
- data/lib/grape/middleware/auth/dsl.rb +26 -21
- data/lib/grape/middleware/auth/strategies.rb +1 -1
- data/lib/grape/middleware/auth/strategy_info.rb +0 -2
- data/lib/grape/middleware/base.rb +2 -2
- data/lib/grape/middleware/error.rb +1 -1
- data/lib/grape/middleware/formatter.rb +5 -5
- data/lib/grape/middleware/versioner.rb +1 -1
- data/lib/grape/middleware/versioner/header.rb +3 -3
- data/lib/grape/middleware/versioner/param.rb +2 -2
- data/lib/grape/middleware/versioner/path.rb +1 -1
- data/lib/grape/namespace.rb +1 -1
- data/lib/grape/path.rb +9 -3
- data/lib/grape/util/content_types.rb +16 -8
- data/lib/grape/util/inheritable_setting.rb +74 -0
- data/lib/grape/util/inheritable_values.rb +51 -0
- data/lib/grape/util/stackable_values.rb +52 -0
- data/lib/grape/util/strict_hash_configuration.rb +106 -0
- data/lib/grape/validations.rb +0 -220
- data/lib/grape/validations/attributes_iterator.rb +21 -0
- data/lib/grape/validations/params_scope.rb +176 -0
- data/lib/grape/validations/validators/all_or_none.rb +20 -0
- data/lib/grape/validations/validators/allow_blank.rb +30 -0
- data/lib/grape/validations/validators/at_least_one_of.rb +20 -0
- data/lib/grape/validations/validators/base.rb +37 -0
- data/lib/grape/validations/{coerce.rb → validators/coerce.rb} +3 -3
- data/lib/grape/validations/{default.rb → validators/default.rb} +1 -1
- data/lib/grape/validations/validators/exactly_one_of.rb +20 -0
- data/lib/grape/validations/validators/multiple_params_base.rb +26 -0
- data/lib/grape/validations/validators/mutual_exclusion.rb +25 -0
- data/lib/grape/validations/{presence.rb → validators/presence.rb} +2 -2
- data/lib/grape/validations/validators/regexp.rb +12 -0
- data/lib/grape/validations/validators/values.rb +26 -0
- data/lib/grape/version.rb +1 -1
- data/spec/grape/api_spec.rb +522 -343
- data/spec/grape/dsl/callbacks_spec.rb +4 -4
- data/spec/grape/dsl/configuration_spec.rb +48 -9
- data/spec/grape/dsl/helpers_spec.rb +6 -13
- data/spec/grape/dsl/inside_route_spec.rb +43 -4
- data/spec/grape/dsl/middleware_spec.rb +1 -10
- data/spec/grape/dsl/parameters_spec.rb +8 -1
- data/spec/grape/dsl/request_response_spec.rb +16 -22
- data/spec/grape/dsl/routing_spec.rb +21 -5
- data/spec/grape/dsl/settings_spec.rb +219 -0
- data/spec/grape/dsl/validations_spec.rb +8 -11
- data/spec/grape/endpoint_spec.rb +115 -86
- data/spec/grape/entity_spec.rb +33 -33
- data/spec/grape/exceptions/invalid_formatter_spec.rb +3 -5
- data/spec/grape/exceptions/invalid_versioner_option_spec.rb +4 -6
- data/spec/grape/exceptions/missing_mime_type_spec.rb +5 -6
- data/spec/grape/exceptions/missing_option_spec.rb +3 -5
- data/spec/grape/exceptions/unknown_options_spec.rb +3 -5
- data/spec/grape/exceptions/unknown_validator_spec.rb +3 -5
- data/spec/grape/exceptions/validation_errors_spec.rb +5 -5
- data/spec/grape/loading_spec.rb +44 -0
- data/spec/grape/middleware/auth/base_spec.rb +0 -4
- data/spec/grape/middleware/auth/dsl_spec.rb +2 -4
- data/spec/grape/middleware/auth/strategies_spec.rb +5 -6
- data/spec/grape/middleware/exception_spec.rb +8 -10
- data/spec/grape/middleware/formatter_spec.rb +13 -15
- data/spec/grape/middleware/versioner/accept_version_header_spec.rb +10 -10
- data/spec/grape/middleware/versioner/header_spec.rb +25 -25
- data/spec/grape/middleware/versioner/param_spec.rb +15 -17
- data/spec/grape/middleware/versioner/path_spec.rb +1 -2
- data/spec/grape/middleware/versioner_spec.rb +0 -1
- data/spec/grape/path_spec.rb +66 -45
- data/spec/grape/util/inheritable_setting_spec.rb +217 -0
- data/spec/grape/util/inheritable_values_spec.rb +63 -0
- data/spec/grape/util/stackable_values_spec.rb +115 -0
- data/spec/grape/util/strict_hash_configuration_spec.rb +38 -0
- data/spec/grape/validations/attributes_iterator_spec.rb +4 -0
- data/spec/grape/validations/params_scope_spec.rb +57 -0
- data/spec/grape/validations/validators/all_or_none_spec.rb +60 -0
- data/spec/grape/validations/validators/allow_blank_spec.rb +170 -0
- data/spec/grape/validations/{at_least_one_of_spec.rb → validators/at_least_one_of_spec.rb} +7 -3
- data/spec/grape/validations/{coerce_spec.rb → validators/coerce_spec.rb} +8 -11
- data/spec/grape/validations/{default_spec.rb → validators/default_spec.rb} +7 -9
- data/spec/grape/validations/{exactly_one_of_spec.rb → validators/exactly_one_of_spec.rb} +15 -11
- data/spec/grape/validations/{mutual_exclusion_spec.rb → validators/mutual_exclusion_spec.rb} +11 -9
- data/spec/grape/validations/{presence_spec.rb → validators/presence_spec.rb} +30 -30
- data/spec/grape/validations/{regexp_spec.rb → validators/regexp_spec.rb} +2 -4
- data/spec/grape/validations/{values_spec.rb → validators/values_spec.rb} +95 -23
- data/spec/grape/validations/{zh-CN.yml → validators/zh-CN.yml} +0 -0
- data/spec/grape/validations_spec.rb +335 -70
- data/spec/shared/versioning_examples.rb +7 -8
- data/spec/spec_helper.rb +2 -0
- data/spec/support/basic_auth_encode_helpers.rb +1 -1
- data/spec/support/content_type_helpers.rb +1 -1
- data/spec/support/versioned_helpers.rb +3 -3
- metadata +80 -33
- data/lib/grape/util/deep_merge.rb +0 -23
- data/lib/grape/util/hash_stack.rb +0 -120
- data/lib/grape/validations/at_least_one_of.rb +0 -25
- data/lib/grape/validations/exactly_one_of.rb +0 -26
- data/lib/grape/validations/mutual_exclusion.rb +0 -25
- data/lib/grape/validations/regexp.rb +0 -12
- data/lib/grape/validations/values.rb +0 -23
- 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:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 42e83be7840de205451cdb523c0a03f3a079300f
|
4
|
+
data.tar.gz: b16053b9a44c001cecae4f670de0073630b7b43e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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-
|
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:
|
8
|
+
# Offense count: 29
|
9
9
|
# Cop supports --auto-correct.
|
10
10
|
Lint/UnusedBlockArgument:
|
11
11
|
Enabled: false
|
12
12
|
|
13
|
-
# Offense count:
|
13
|
+
# Offense count: 26
|
14
14
|
# Cop supports --auto-correct.
|
15
15
|
Lint/UnusedMethodArgument:
|
16
16
|
Enabled: false
|
17
17
|
|
18
|
-
# Offense count:
|
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:
|
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:
|
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:
|
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:
|
87
|
+
# Offense count: 15
|
40
88
|
# Cop supports --auto-correct.
|
41
89
|
Style/IndentArray:
|
42
90
|
Enabled: false
|
43
91
|
|
44
|
-
# Offense count:
|
45
|
-
|
46
|
-
|
47
|
-
Style/IndentHash:
|
48
|
-
Enabled: true
|
92
|
+
# Offense count: 18
|
93
|
+
Style/Lambda:
|
94
|
+
Enabled: false
|
49
95
|
|
50
|
-
# Offense count:
|
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:
|
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:
|
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
|
-
|
3
|
+
sudo: false
|
4
4
|
|
5
5
|
rvm:
|
6
|
-
- 2.1
|
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
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.
|
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
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
|
-
- [
|
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
|
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
|
195
|
-
GET /statuses/home_timeline
|
196
|
-
GET /statuses/:id
|
197
|
-
POST /statuses
|
198
|
-
PUT /statuses/:id
|
199
|
-
DELETE /statuses/:id
|
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
|
-
|
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
|
-
|
679
|
+
optional :color, type: String, default: 'blue', values: ['red', 'green']
|
459
680
|
end
|
460
681
|
```
|
461
682
|
|
462
|
-
The
|
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
|
-
|
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
|
-
|
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::
|
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,
|
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::
|
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,
|
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'
|
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'
|
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
|
-
|
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
|
-
|
1606
|
+
get :hello do
|
1607
|
+
{ hello: 'world' }
|
1608
|
+
end
|
1609
|
+
end
|
1610
|
+
```
|
1182
1611
|
|
1183
|
-
|
1184
|
-
|
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
|
1187
|
-
`application/x-www-form-urlencoded`, `multipart/form-data`, `multipart/related` and
|
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
|
1674
|
+
Built-in formatters are the following.
|
1242
1675
|
|
1243
|
-
* `:json
|
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
|
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
|
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
|
-
|
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
|
-
|
1547
|
-
|
1548
|
-
|
1549
|
-
|
1550
|
-
|
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 #
|
1556
|
-
TwitterAPI::routes[0].route_description #
|
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/
|
2189
|
+
describe "GET /api/statuses/public_timeline" do
|
1726
2190
|
it "returns an empty array of statuses" do
|
1727
|
-
get "/api/
|
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/
|
2196
|
+
describe "GET /api/statuses/:id" do
|
1733
2197
|
it "returns a status by id" do
|
1734
2198
|
status = Status.create!
|
1735
|
-
get "/api/
|
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/
|
2211
|
+
describe "GET /api/statuses/public_timeline" do
|
1748
2212
|
it "returns an empty array of statuses" do
|
1749
|
-
get "/api/
|
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/
|
2218
|
+
describe "GET /api/statuses/:id" do
|
1755
2219
|
it "returns a status by id" do
|
1756
2220
|
status = Status.create!
|
1757
|
-
get "/api/
|
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,
|
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
|
-
###
|
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
|
|