grape 4.0.1 → 4.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +102 -1
  3. data/README.md +195 -95
  4. data/UPGRADING.md +364 -0
  5. data/grape.gemspec +1 -1
  6. data/lib/grape/api/instance.rb +57 -30
  7. data/lib/grape/api.rb +50 -20
  8. data/lib/grape/content_types.rb +26 -9
  9. data/lib/grape/cookies.rb +1 -1
  10. data/lib/grape/declared_params_handler.rb +35 -7
  11. data/lib/grape/dsl/declared.rb +5 -4
  12. data/lib/grape/dsl/desc.rb +1 -2
  13. data/lib/grape/dsl/entity.rb +35 -19
  14. data/lib/grape/dsl/headers.rb +3 -3
  15. data/lib/grape/dsl/helpers.rb +5 -8
  16. data/lib/grape/dsl/inside_route.rb +33 -27
  17. data/lib/grape/dsl/logger.rb +10 -4
  18. data/lib/grape/dsl/middleware.rb +4 -9
  19. data/lib/grape/dsl/mounting.rb +149 -0
  20. data/lib/grape/dsl/parameters.rb +78 -104
  21. data/lib/grape/dsl/request_response.rb +35 -22
  22. data/lib/grape/dsl/routing.rb +17 -191
  23. data/lib/grape/dsl/settings.rb +1 -1
  24. data/lib/grape/dsl/version_options.rb +2 -2
  25. data/lib/grape/dsl/versioning.rb +98 -0
  26. data/lib/grape/endpoint/state.rb +15 -0
  27. data/lib/grape/endpoint.rb +229 -137
  28. data/lib/grape/error_formatter/base.rb +28 -13
  29. data/lib/grape/error_formatter/json.rb +3 -0
  30. data/lib/grape/error_formatter/jsonl.rb +15 -0
  31. data/lib/grape/error_formatter/xml.rb +3 -1
  32. data/lib/grape/exceptions/error_response.rb +23 -7
  33. data/lib/grape/exceptions/halt.rb +39 -0
  34. data/lib/grape/exceptions/validation.rb +9 -9
  35. data/lib/grape/exceptions/validation_errors.rb +12 -2
  36. data/lib/grape/formatter/jsonl.rb +34 -0
  37. data/lib/grape/formatter/markdown.rb +17 -0
  38. data/lib/grape/formatter/md.rb +11 -0
  39. data/lib/grape/locale/en.yml +1 -0
  40. data/lib/grape/middleware/auth/base.rb +16 -1
  41. data/lib/grape/middleware/auth/strategy_info.rb +2 -2
  42. data/lib/grape/middleware/base.rb +19 -13
  43. data/lib/grape/middleware/error.rb +133 -35
  44. data/lib/grape/middleware/formatter.rb +235 -94
  45. data/lib/grape/middleware/precomputed_content_types.rb +14 -2
  46. data/lib/grape/middleware/stack.rb +7 -8
  47. data/lib/grape/middleware/versioner/base.rb +26 -10
  48. data/lib/grape/middleware/versioner/header.rb +70 -15
  49. data/lib/grape/middleware/versioner/param.rb +3 -5
  50. data/lib/grape/middleware/versioner/path.rb +20 -11
  51. data/lib/grape/middleware/versioner.rb +1 -1
  52. data/lib/grape/mountable.rb +2 -4
  53. data/lib/grape/namespace.rb +2 -3
  54. data/lib/grape/parser/jsonl.rb +19 -0
  55. data/lib/grape/request.rb +46 -8
  56. data/lib/grape/router/base_route.rb +41 -0
  57. data/lib/grape/router/greedy_route.rb +1 -1
  58. data/lib/grape/router/mustermann_pattern.rb +1 -1
  59. data/lib/grape/router/pattern/path.rb +1 -1
  60. data/lib/grape/router/pattern.rb +11 -5
  61. data/lib/grape/router/route.rb +42 -11
  62. data/lib/grape/router/route_buckets.rb +246 -0
  63. data/lib/grape/router.rb +262 -32
  64. data/lib/grape/util/endpoint_configuration.rb +7 -1
  65. data/lib/grape/util/inheritable_setting.rb +80 -161
  66. data/lib/grape/util/lazy/value.rb +52 -11
  67. data/lib/grape/util/media_type.rb +38 -15
  68. data/lib/grape/util/path_normalizer.rb +7 -2
  69. data/lib/grape/util/registry.rb +9 -2
  70. data/lib/grape/util/scrub.rb +33 -0
  71. data/lib/grape/util/translation.rb +6 -6
  72. data/lib/grape/validations/attributes_iterator.rb +73 -37
  73. data/lib/grape/validations/coerce_options.rb +1 -1
  74. data/lib/grape/validations/params_documentation.rb +15 -7
  75. data/lib/grape/validations/params_scope.rb +176 -43
  76. data/lib/grape/validations/types/custom_type_coercer.rb +2 -3
  77. data/lib/grape/validations/types/custom_type_collection_coercer.rb +1 -1
  78. data/lib/grape/validations/types/dry_type_coercer.rb +20 -12
  79. data/lib/grape/validations/types/multiple_type_coercer.rb +1 -2
  80. data/lib/grape/validations/types/primitive_coercer.rb +17 -0
  81. data/lib/grape/validations/types/variant_collection_coercer.rb +24 -10
  82. data/lib/grape/validations/types.rb +12 -18
  83. data/lib/grape/validations/validations_spec.rb +53 -22
  84. data/lib/grape/validations/validators/allow_blank_validator.rb +2 -2
  85. data/lib/grape/validations/validators/as_validator.rb +2 -3
  86. data/lib/grape/validations/validators/base.rb +124 -25
  87. data/lib/grape/validations/validators/coerce_validator.rb +2 -3
  88. data/lib/grape/validations/validators/default_validator.rb +18 -2
  89. data/lib/grape/validations/validators/except_values_validator.rb +16 -3
  90. data/lib/grape/validations/validators/hash_elements_validator.rb +34 -0
  91. data/lib/grape/validations/validators/length_validator.rb +24 -16
  92. data/lib/grape/validations/validators/multiple_params_base.rb +0 -4
  93. data/lib/grape/validations/validators/values_validator.rb +18 -24
  94. data/lib/grape/version.rb +1 -1
  95. data/lib/grape.rb +3 -0
  96. metadata +18 -11
  97. data/lib/grape/util/lazy/value_array.rb +0 -21
  98. data/lib/grape/util/lazy/value_enumerable.rb +0 -31
  99. data/lib/grape/util/lazy/value_hash.rb +0 -21
  100. data/lib/grape/validations/multiple_attributes_iterator.rb +0 -13
  101. data/lib/grape/validations/single_attribute_iterator.rb +0 -28
data/README.md CHANGED
@@ -10,7 +10,7 @@ Grape is a REST-like API framework for Ruby. It's designed to run on Rack or com
10
10
 
11
11
  ## Stable Release
12
12
 
13
- You're reading the documentation for the stable release of Grape, 4.0.1.
13
+ You're reading the documentation for the stable release of Grape, 4.1.0.
14
14
 
15
15
  ## Project Resources
16
16
 
@@ -27,7 +27,7 @@ The maintainers of Grape are working with Tidelift to deliver commercial support
27
27
 
28
28
  ## Installation
29
29
 
30
- Ruby 3.3 or newer is required.
30
+ Ruby 3.3.1 or newer is required.
31
31
 
32
32
  Grape is available as a gem, to install it run:
33
33
 
@@ -124,7 +124,7 @@ Grape's [deprecator](https://api.rubyonrails.org/v7.1.0/classes/ActiveSupport/De
124
124
  ### All
125
125
 
126
126
 
127
- By default Grape will compile the routes on the first route, but it is possible to pre-load routes using the `compile!` method.
127
+ By default Grape will compile the routes on the first request, but it is possible to pre-load routes using the `compile!` method.
128
128
 
129
129
  ```ruby
130
130
  Twitter::API.compile!
@@ -351,7 +351,7 @@ end
351
351
  ```ruby
352
352
  class BasicAPI < Grape::API
353
353
  desc 'Statuses index' do
354
- params: (configuration[:entity] || API::Entities::Status).documentation
354
+ params((configuration[:entity] || API::Entities::Status).documentation)
355
355
  end
356
356
  params do
357
357
  requires :all, using: (configuration[:entity] || API::Entities::Status).documentation
@@ -466,7 +466,7 @@ Using this versioning strategy, clients should pass the desired version in the H
466
466
 
467
467
  curl -H Accept:application/vnd.twitter-v1+json http://localhost:9292/statuses/public_timeline
468
468
 
469
- By default, the first matching version is used when no `Accept` header is supplied. This behavior is similar to routing in Rails. To circumvent this default behavior, one could use the `:strict` option. When this option is set to `true`, a `406 Not Acceptable` error is returned when no correct `Accept` header is supplied.
469
+ By default, the first matching version is used when no `Accept` header is supplied. This behavior is similar to routing in Rails. To circumvent this default behavior, one could use the `:strict` option. When this option is set to `true`, a `406 Not Acceptable` error is returned when no correct `Accept` header is supplied and the `:cascade` option is set to `false`. Otherwise a `404 Not Found` error is returned by Rack if no other route matches.
470
470
 
471
471
  When an invalid `Accept` header is supplied, a `406 Not Acceptable` error is returned if the `:cascade` option is set to `false`. Otherwise a `404 Not Found` error is returned by Rack if no other route matches.
472
472
 
@@ -492,7 +492,7 @@ By default, the first matching version is used when no `Accept-Version` header i
492
492
  version 'v1', using: :param
493
493
  ```
494
494
 
495
- Using this versioning strategy, clients should pass the desired version as a request parameter, either in the URL query string or in the request body.
495
+ Using this versioning strategy, clients should pass the desired version as a parameter in the URL query string. A version given in the request body is not read.
496
496
 
497
497
  curl http://localhost:9292/statuses/public_timeline?apiver=v1
498
498
 
@@ -507,7 +507,7 @@ version 'v1', using: :param, parameter: 'v'
507
507
 
508
508
  ## Linting
509
509
 
510
- You can check whether your API is in conformance with the [Rack's specification](https://github.com/rack/rack/blob/main/SPEC.rdoc) by calling `lint!` at the API level or through [configuration](#configuration).
510
+ You can check whether your API is in conformance with the [Rack's specification](https://github.com/rack/rack/blob/main/SPEC.rdoc) by calling `lint!` at the top level of the API you serve, or through [configuration](#configuration). Every response the API gives is then checked once with `Rack::Lint`, including the 404 for a path no route matches and the responses of Rack apps mounted with `mount`. A `lint!` inside a namespace, or in an API mounted into another one, has no effect.
511
511
 
512
512
  ```ruby
513
513
  class Api < Grape::API
@@ -539,7 +539,7 @@ desc 'Returns your public timeline.' do
539
539
  params API::Entities::Status.documentation
540
540
  success API::Entities::Entity
541
541
  failure [[401, 'Unauthorized', 'Entities::Error']]
542
- default { code: 500, message: 'InvalidRequest', model: Entities::Error }
542
+ default_response code: 500, message: 'InvalidRequest', model: Entities::Error
543
543
  named 'My named route'
544
544
  headers XAuthToken: {
545
545
  description: 'Validates your identity',
@@ -578,8 +578,11 @@ end
578
578
  Use `Grape.configure` to set up global settings at load time.
579
579
  Currently the configurable settings are:
580
580
 
581
- * `param_builder`: Sets the [Parameter Builder](#parameters), defaults to `Grape::Extensions::ActiveSupport::HashWithIndifferentAccess::ParamBuilder`.
581
+ * `param_builder`: Sets the [Parameter Builder](#parameters), defaults to `:hash_with_indifferent_access`.
582
+ * `lint`: Checks every response against Rack's specification, defaults to `false`. See [Linting](#linting).
583
+ * `warn_on_helper_overrides`: Warns when a helper method masks a `Grape::Endpoint` instance method, defaults to `false`.
582
584
  * `raise_rendering_errors`: Lets an error response that cannot be rendered propagate out of the middleware stack instead of being answered with a failsafe `500`, defaults to `false`. See [When the error response itself cannot be rendered](#when-the-error-response-itself-cannot-be-rendered).
585
+ * `raise_rescue_handler_errors`: Lets an exception raised inside a `rescue_from` block propagate out of the middleware stack instead of being redispatched, defaults to `false`. See [Re-raising from inside a `rescue_from` block](#re-raising-from-inside-a-rescue_from-block).
583
586
 
584
587
  To change a setting value make sure that at some point during load time the following code runs
585
588
 
@@ -620,7 +623,7 @@ get :public_timeline do
620
623
  end
621
624
  ```
622
625
 
623
- Parameters are automatically populated from the request body on `POST`, `PUT` and `QUERY` for form input, JSON and XML content-types.
626
+ Parameters are automatically populated from the request body on `POST`, `PUT`, `PATCH`, `DELETE` and `QUERY` for form input, JSON and XML content-types.
624
627
 
625
628
  The request:
626
629
 
@@ -656,7 +659,7 @@ In the case of conflict between either of:
656
659
 
657
660
  * route string parameters
658
661
  * `GET`, `POST` and `PUT` parameters
659
- * the contents of the request body on `POST` and `PUT`
662
+ * the contents of the request body on `POST`, `PUT`, `PATCH`, `DELETE` and `QUERY`
660
663
 
661
664
  Route string parameters will have precedence.
662
665
 
@@ -674,6 +677,7 @@ class API < Grape::API
674
677
  get do
675
678
  params.color # instead of params[:color]
676
679
  end
680
+ end
677
681
  ```
678
682
 
679
683
  The class can also be overridden on individual parameter blocks using `build_with` as follows.
@@ -845,7 +849,7 @@ curl -X GET -H "Content-Type: application/json" localhost:9292/parent/foo/bar
845
849
  "with_parent_namespaces": {
846
850
  "parent_name": "foo",
847
851
  "child_name": "bar"
848
- },
852
+ }
849
853
  }
850
854
  ````
851
855
 
@@ -950,7 +954,7 @@ curl -X POST -H "Content-Type: application/json" localhost:9292/users/signup -d
950
954
  "first_name": "first name",
951
955
  "last_name": null,
952
956
  "address": {
953
- "city": "Zurich",
957
+ "city": "SF",
954
958
  "region": null
955
959
  }
956
960
  }
@@ -1081,11 +1085,13 @@ curl -X POST -H "Content-Type: application/json" localhost:9292/child -d '{"chil
1081
1085
  Using `route_param` takes higher precedence over a regular parameter defined with same name:
1082
1086
 
1083
1087
  ```ruby
1088
+ format :json
1089
+
1084
1090
  params do
1085
1091
  requires :foo, type: String
1086
1092
  end
1087
1093
  route_param :foo do
1088
- get do
1094
+ post do
1089
1095
  { value: params[:foo] }
1090
1096
  end
1091
1097
  end
@@ -1117,7 +1123,7 @@ params do
1117
1123
  requires :url
1118
1124
  end
1119
1125
  optional :audio, type: Hash do
1120
- requires :format, type: Symbol, values: [:mp3, :wav, :aac, :ogg], default: :mp3
1126
+ requires :format, type: Symbol, values: [:mp3, :wav, :aac, :ogg]
1121
1127
  end
1122
1128
  mutually_exclusive :media, :audio
1123
1129
  end
@@ -1141,7 +1147,7 @@ end
1141
1147
  Default values are eagerly evaluated. Above `:non_random_number` will evaluate to the same number for each call to the endpoint of this `params` block. To have the default evaluate lazily with each request use a lambda, like `:random_number` above.
1142
1148
 
1143
1149
  Note that default values will be passed through to any validation options specified.
1144
- The following example will always fail if `:color` is not explicitly provided.
1150
+ The following example raises `Grape::Exceptions::IncompatibleOptionValues` when the API is defined, since the default is not one of the allowed values.
1145
1151
 
1146
1152
  ```ruby
1147
1153
  params do
@@ -1185,7 +1191,7 @@ The following are all valid types, supported out of the box by Grape:
1185
1191
 
1186
1192
  ### Custom Types and Coercions
1187
1193
 
1188
- Aside from the default set of supported types listed above, any class can be used as a type as long as an explicit coercion method is supplied. If the type implements a class-level `parse` method, Grape will use it automatically. This method must take one string argument and return an instance of the correct type, or return an instance of `Grape::Types::InvalidValue` which optionally accepts a message to be returned in the response.
1194
+ Aside from the default set of supported types listed above, any class can be used as a type as long as an explicit coercion method is supplied. If the type implements a class-level `parse` method, Grape will use it automatically. This method must take one string argument and return an instance of the correct type, or return an instance of `Grape::Validations::Types::InvalidValue` which optionally accepts a message to be returned in the response.
1189
1195
 
1190
1196
  ```ruby
1191
1197
  class Color
@@ -1197,12 +1203,12 @@ class Color
1197
1203
  def self.parse(value)
1198
1204
  return new(value) if %w[blue red green].include?(value)
1199
1205
 
1200
- Grape::Types::InvalidValue.new('Unsupported color')
1206
+ Grape::Validations::Types::InvalidValue.new('Unsupported color')
1201
1207
  end
1202
1208
  end
1203
1209
 
1204
1210
  params do
1205
- requires :color, type: Color, default: Color.new('blue')
1211
+ optional :color, type: Color, default: Color.new('blue')
1206
1212
  requires :more_colors, type: Array[Color] # Collections work
1207
1213
  optional :unique_colors, type: Set[Color] # Duplicates discarded
1208
1214
  end
@@ -1284,8 +1290,8 @@ get '/' do
1284
1290
  params[:json].inspect
1285
1291
  end
1286
1292
 
1287
- client.get('/', json: '{"int":1}') # => "{:int=>1}"
1288
- client.get('/', json: '[{"int":"1"}]') # => "[{:int=>1}]"
1293
+ client.get('/', json: '{"int":1}') # => '{"int" => 1}'
1294
+ client.get('/', json: '[{"int":"1"}]') # => '[{"int" => 1}]'
1289
1295
 
1290
1296
  client.get('/', json: '{"int":4}') # => HTTP 400
1291
1297
  client.get('/', json: '[{"int":4}]') # => HTTP 400
@@ -1389,7 +1395,7 @@ end
1389
1395
 
1390
1396
  Parameters can be nested using `group` or by calling `requires` or `optional` with a block.
1391
1397
  In the [above example](#parameter-validation-and-coercion), this means `params[:media][:url]` is required along with `params[:id]`, and `params[:audio][:format]` is required only if `params[:audio]` is present.
1392
- With a block, `group`, `requires` and `optional` accept an additional option `type` which can be either `Array` or `Hash`, and defaults to `Array`. Depending on the value, the nested parameters will be treated either as values of a hash or as values of hashes in an array.
1398
+ With a block, `group`, `requires` and `optional` require an additional option `type`, which can be either `Array` or `Hash`; a block without one raises `Grape::Exceptions::MissingGroupType`. Depending on the value, the nested parameters will be treated either as values of a hash or as values of hashes in an array.
1393
1399
 
1394
1400
  ```ruby
1395
1401
  params do
@@ -1436,13 +1442,13 @@ You can rename parameters:
1436
1442
  ```ruby
1437
1443
  params do
1438
1444
  optional :category, as: :type
1439
- given type: ->(val) { val == 'foo' } do
1445
+ given category: ->(val) { val == 'foo' } do
1440
1446
  requires :description
1441
1447
  end
1442
1448
  end
1443
1449
  ```
1444
1450
 
1445
- Note: param in `given` should be the renamed one. In the example, it should be `type`, not `category`.
1451
+ Note: `given` takes the name a param is declared with, not the one `as` renames it to. In the example, it is `category`, not `type`: the renaming applies only to [`declared(params)`](#declared), and naming `type` raises `Grape::Exceptions::UnknownParameter`.
1446
1452
 
1447
1453
  ### Group Options
1448
1454
 
@@ -1453,9 +1459,9 @@ The example below presents a typical case when parameters share common options.
1453
1459
 
1454
1460
  ```ruby
1455
1461
  params do
1456
- requires :first_name, type: String, regexp: /w+/, desc: 'First name', documentation: { in: 'body' }
1457
- optional :middle_name, type: String, regexp: /w+/, desc: 'Middle name', documentation: { in: 'body', x: { nullable: true } }
1458
- requires :last_name, type: String, regexp: /w+/, desc: 'Last name', documentation: { in: 'body' }
1462
+ requires :first_name, type: String, regexp: /\w+/, desc: 'First name', documentation: { in: 'body' }
1463
+ optional :middle_name, type: String, regexp: /\w+/, desc: 'Middle name', documentation: { in: 'body', x: { nullable: true } }
1464
+ requires :last_name, type: String, regexp: /\w+/, desc: 'Last name', documentation: { in: 'body' }
1459
1465
  end
1460
1466
  ```
1461
1467
 
@@ -1463,7 +1469,7 @@ Grape allows you to present the same logic through the `with` method in your par
1463
1469
 
1464
1470
  ```ruby
1465
1471
  params do
1466
- with(type: String, regexp: /w+/, documentation: { in: 'body' }) do
1472
+ with(type: String, regexp: /\w+/, documentation: { in: 'body' }) do
1467
1473
  requires :first_name, desc: 'First name'
1468
1474
  optional :middle_name, desc: 'Middle name', documentation: { x: { nullable: true } }
1469
1475
  requires :last_name, desc: 'Last name'
@@ -1527,7 +1533,7 @@ Parameters can be restricted to a specific set of values with the `:values` opti
1527
1533
  ```ruby
1528
1534
  params do
1529
1535
  requires :status, type: Symbol, values: [:not_started, :processing, :done]
1530
- optional :numbers, type: Array[Integer], default: 1, values: [1, 2, 3, 5, 8]
1536
+ optional :numbers, type: Array[Integer], default: [1], values: [1, 2, 3, 5, 8]
1531
1537
  end
1532
1538
  ```
1533
1539
 
@@ -1545,7 +1551,7 @@ Note endless ranges are also supported with ActiveSupport >= 6.0, but they requi
1545
1551
 
1546
1552
  ```ruby
1547
1553
  params do
1548
- requires :minimum, type: Integer, values: 10..
1554
+ requires :minimum, type: Integer, values: (10..)
1549
1555
  optional :maximum, type: Integer, values: ..10
1550
1556
  end
1551
1557
  ```
@@ -1573,10 +1579,10 @@ Combined with `:type`, ranges (open or closed) cover common numeric bound checks
1573
1579
 
1574
1580
  ```ruby
1575
1581
  params do
1576
- requires :quantity, type: Integer, values: 1.. # must be a positive integer
1582
+ requires :quantity, type: Integer, values: (1..) # must be a positive integer
1577
1583
  requires :discount, type: Float, values: 0.0..100.0 # must be between 0 and 100
1578
1584
  requires :rating, type: Integer, values: 5..5 # must be exactly 5
1579
- requires :numbers, type: [Integer], values: 1.. # every element must be positive
1585
+ requires :numbers, type: [Integer], values: (1..) # every element must be positive
1580
1586
  end
1581
1587
  ```
1582
1588
 
@@ -1620,9 +1626,9 @@ The `except_values` validator behaves similarly to the `values` validator in tha
1620
1626
  ```ruby
1621
1627
  params do
1622
1628
  requires :browser, except_values: [ 'ie6', 'ie7', 'ie8' ]
1623
- requires :port, except_values: { value: 0..1024, message: 'is not allowed' }
1629
+ requires :port, type: Integer, except_values: { value: 0..1024, message: 'is not allowed' }
1624
1630
  requires :negative, type: Integer, except_values: ..-1
1625
- requires :hashtag, except_values: -> { Hashtag.FORBIDDEN_LIST }
1631
+ requires :hashtag, except_values: -> { Hashtag::FORBIDDEN_LIST }
1626
1632
  end
1627
1633
  ```
1628
1634
 
@@ -1842,7 +1848,7 @@ end
1842
1848
  You can also create custom classes that take parameters.
1843
1849
 
1844
1850
  ```ruby
1845
- class Length < Grape::Validations::Validators::Base
1851
+ class MaxLength < Grape::Validations::Validators::Base
1846
1852
  def validate_param!(attr_name, params)
1847
1853
  return if params[attr_name].length <= @options
1848
1854
 
@@ -1853,10 +1859,12 @@ end
1853
1859
 
1854
1860
  ```ruby
1855
1861
  params do
1856
- requires :text, length: 140
1862
+ requires :text, max_length: 140
1857
1863
  end
1858
1864
  ```
1859
1865
 
1866
+ A validator is registered under its class name, underscored and without a `Validator` suffix, for every API in the process. Pick a name that no built-in validator uses: a class named `Length` would replace the built-in [`length`](#length) validator everywhere.
1867
+
1860
1868
  You can also create custom validation that use request to validate the attribute. For example if you want to have parameters that are available to only admins, you can do the following.
1861
1869
 
1862
1870
  ```ruby
@@ -2113,7 +2121,7 @@ params do
2113
2121
  requires :name, message: :name_required
2114
2122
  end
2115
2123
  ```
2116
- ```ruby
2124
+ ```yaml
2117
2125
  # en.yml
2118
2126
 
2119
2127
  en:
@@ -2128,7 +2136,7 @@ en:
2128
2136
 
2129
2137
  You can also override attribute names.
2130
2138
 
2131
- ```ruby
2139
+ ```yaml
2132
2140
  # en.yml
2133
2141
 
2134
2142
  en:
@@ -2148,7 +2156,7 @@ You cannot set a custom message option for Default as it requires interpolation
2148
2156
 
2149
2157
  ```ruby
2150
2158
  params do
2151
- requires :name, values: { value: -> { (1..10).to_a }, message: 'not in range from 1 to 10' }, default: 5
2159
+ optional :name, values: { value: -> { (1..10).to_a }, message: 'not in range from 1 to 10' }, default: 5
2152
2160
  end
2153
2161
  ```
2154
2162
 
@@ -2213,8 +2221,7 @@ curl -H "secret_PassWord: swordfish" ...
2213
2221
 
2214
2222
  The header name will have been normalized for you.
2215
2223
 
2216
- - In the `header` helper names will be coerced into a downcased kebab case as `secret-password` if using Rack 3.
2217
- - In the `header` helper names will be coerced into a capitalized kebab case as `Secret-PassWord` if using Rack < 3.
2224
+ - In the `headers` helper names will be coerced into a downcased kebab case as `secret-password`, and looked up case-insensitively, so `headers['Secret-Password']` finds it too. On Rack < 3, a standard header such as `User-Agent` keeps its usual capitalization.
2218
2225
  - In the `env` collection they appear in all uppercase, in snake case, and prefixed with 'HTTP_' as `HTTP_SECRET_PASSWORD`
2219
2226
 
2220
2227
  The header name will have been normalized per HTTP standards defined in [RFC2616 Section 4.2](https://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2) regardless of what is being sent by a client.
@@ -2293,7 +2300,7 @@ get ':id', requirements: { id: /[0-9]*/ } do
2293
2300
  end
2294
2301
 
2295
2302
  namespace :outer, requirements: { id: /[0-9]*/ } do
2296
- get :id do
2303
+ get ':id' do
2297
2304
  end
2298
2305
 
2299
2306
  get ':id/edit' do
@@ -2351,7 +2358,7 @@ route_param :name, requirements: /.+/ do
2351
2358
  end
2352
2359
  ```
2353
2360
 
2354
- Such a regexp claims the extension as well, so `GET /a.b.json` hands the endpoint `"a.b.json"` and format-by-extension no longer applies to that route. A narrower constraint leaves the suffix alone: `get ':n', requirements: { n: Float }` matches `/4.2` and still reads `.json` as the format.
2361
+ Such a regexp claims the extension as well, so `GET /a.b.json` hands the endpoint `"a.b.json"`. The response format is still read off the end of the path, though, whatever the route captured: with no `format` of its own, the API above answers `GET /a.b.json` in JSON, and `GET /a.b.xml` with a `500`, since the XML formatter cannot render a String. Declaring `format` settles the response format whatever the extension. A narrower constraint leaves the suffix alone: `get ':n', requirements: { n: Float }` matches `/4.2` and still reads `.json` as the format.
2355
2362
 
2356
2363
  ### The QUERY Method
2357
2364
 
@@ -2528,13 +2535,15 @@ You can set, get and delete your cookies very simply using `cookies` method.
2528
2535
  ```ruby
2529
2536
  class API < Grape::API
2530
2537
  get 'status_count' do
2531
- cookies[:status_count] ||= 0
2532
- cookies[:status_count] += 1
2538
+ # a cookie the client sent back is a String, so convert before counting
2539
+ cookies[:status_count] = cookies[:status_count].to_i + 1
2533
2540
  { status_count: cookies[:status_count] }
2534
2541
  end
2535
2542
 
2536
2543
  delete 'status_count' do
2537
- { status_count: cookies.delete(:status_count) }
2544
+ status_count = cookies[:status_count]
2545
+ cookies.delete(:status_count)
2546
+ { status_count: status_count }
2538
2547
  end
2539
2548
  end
2540
2549
  ```
@@ -2655,7 +2664,7 @@ class API < Grape::API
2655
2664
  end
2656
2665
  ```
2657
2666
 
2658
- When you add a route for a resource, a route for the `OPTIONS` method will also be added. The response to an OPTIONS request will include an "Allow" header listing the supported methods. If the resource has `before` and `after` callbacks they will be executed, but no other callbacks will run.
2667
+ When you add a route for a resource, a route for the `OPTIONS` method will also be added. The response to an OPTIONS request will include an "Allow" header listing the supported methods. If the resource has `before`, `after` and `finally` callbacks they will be executed, but no other callbacks will run.
2659
2668
 
2660
2669
  ```ruby
2661
2670
  class API < Grape::API
@@ -2679,12 +2688,12 @@ curl -v -X OPTIONS http://localhost:3000/rt_count
2679
2688
  > OPTIONS /rt_count HTTP/1.1
2680
2689
  >
2681
2690
  < HTTP/1.1 204 No Content
2682
- < Allow: OPTIONS, GET, PUT
2691
+ < Allow: OPTIONS, GET, PUT, HEAD
2683
2692
  ```
2684
2693
 
2685
2694
  You can disable this behavior with `do_not_route_options!`.
2686
2695
 
2687
- If a request for a resource is made with an unsupported HTTP method, an HTTP 405 (Method Not Allowed) response will be returned. If the resource has `before` callbacks they will be executed, but no other callbacks will run.
2696
+ If a request for a resource is made with an unsupported HTTP method, an HTTP 405 (Method Not Allowed) response will be returned. If the resource has `before` and `finally` callbacks they will be executed, but no other callbacks will run.
2688
2697
 
2689
2698
  ``` shell
2690
2699
  curl -X DELETE -v http://localhost:3000/rt_count/
@@ -2693,7 +2702,7 @@ curl -X DELETE -v http://localhost:3000/rt_count/
2693
2702
  > Host: localhost:3000
2694
2703
  >
2695
2704
  < HTTP/1.1 405 Method Not Allowed
2696
- < Allow: OPTIONS, GET, PUT
2705
+ < Allow: OPTIONS, GET, PUT, HEAD
2697
2706
  ```
2698
2707
 
2699
2708
  ## Raising Exceptions
@@ -2722,6 +2731,17 @@ You can set additional headers for the response. They will be merged with header
2722
2731
  error!('Something went wrong', 500, 'X-Error-Detail' => 'Invalid token.')
2723
2732
  ```
2724
2733
 
2734
+ `error!` raises a `Grape::Exceptions::Halt`, which Grape renders as the response without handing it to `rescue_from`. Since it is an exception, a database transaction it leaves is rolled back. It also reaches Grape from a fiber or thread whose result the route waits for, such as an `Async` task.
2735
+
2736
+ ```ruby
2737
+ Order.transaction do
2738
+ order = Order.create!(declared(params))
2739
+ error!('Out of stock', 409) unless order.product.in_stock? # the order is rolled back
2740
+ end
2741
+ ```
2742
+
2743
+ It is a `StandardError`, so a `rescue => e` around `error!` catches it as well. Rescue `Grape::Exceptions::Halt` first and re-raise it to let it through.
2744
+
2725
2745
  You can present documented errors with a Grape entity using the [grape-entity](https://github.com/ruby-grape/grape-entity) gem.
2726
2746
 
2727
2747
  ```ruby
@@ -2733,22 +2753,22 @@ module API
2733
2753
  end
2734
2754
  ```
2735
2755
 
2736
- The following example specifies the entity to use in the `http_codes` definition.
2756
+ The following example specifies the entity to use in the `failure` definition.
2737
2757
 
2738
2758
  ```ruby
2739
2759
  desc 'My Route' do
2740
- failure [[408, 'Unauthorized', API::Error]]
2760
+ failure [[401, 'Unauthorized', API::Error]]
2741
2761
  end
2742
- error!({ message: 'Unauthorized' }, 408)
2762
+ error!({ message: 'Unauthorized' }, 401)
2743
2763
  ```
2744
2764
 
2745
2765
  The following example specifies the presented entity explicitly in the error message.
2746
2766
 
2747
2767
  ```ruby
2748
2768
  desc 'My Route' do
2749
- failure [[408, 'Unauthorized']]
2769
+ failure [[401, 'Unauthorized']]
2750
2770
  end
2751
- error!({ message: 'Unauthorized', with: API::Error }, 408)
2771
+ error!({ message: 'Unauthorized', with: API::Error }, 401)
2752
2772
  ```
2753
2773
 
2754
2774
  ### Default Error HTTP Status Code
@@ -2771,7 +2791,7 @@ In its simplest form, it can be like:
2771
2791
 
2772
2792
  ```ruby
2773
2793
  route :any, '*path' do
2774
- error! # or something else
2794
+ error!('Not Found', 404) # or something else
2775
2795
  end
2776
2796
  ```
2777
2797
 
@@ -2830,7 +2850,7 @@ class Twitter::API < Grape::API
2830
2850
  end
2831
2851
  ```
2832
2852
 
2833
- In this case ```UserDefinedError``` must be inherited from ```StandardError```.
2853
+ A class listed explicitly is rescued whether or not it inherits from `StandardError`; see [Exceptions that should be rescued explicitly](#exceptions-that-should-be-rescued-explicitly).
2834
2854
 
2835
2855
  When several classes could match, the one registered **first** in a scope wins — as with the clauses of a Ruby `rescue`. Register the more specific class before the broader one, or the narrower handler never runs:
2836
2856
 
@@ -2869,7 +2889,7 @@ end
2869
2889
 
2870
2890
  The error format will match the request format. See "Content-Types" below.
2871
2891
 
2872
- Custom error formatters for existing and additional types can be defined with a proc. The formatter receives a `Grape::Exceptions::ErrorResponse` value object as `error:` plus three context kwargs — `env:`, `include_backtrace:`, `include_original_exception:`. Pull just the keys you need with `**` to ignore the rest:
2892
+ Custom error formatters for existing and additional types can be defined with a proc. The formatter receives a frozen `Grape::Exceptions::ErrorResponse` as `error:` (read its `status`, `message`, `headers`, `backtrace` and `original_exception`), plus three context kwargs — `env:`, `include_backtrace:`, `include_original_exception:`. Pull just the keys you need with `**` to ignore the rest:
2873
2893
 
2874
2894
  ```ruby
2875
2895
  class Twitter::API < Grape::API
@@ -2884,7 +2904,7 @@ You can also use a module or class.
2884
2904
  ```ruby
2885
2905
  module CustomFormatter
2886
2906
  def self.call(error:, **)
2887
- { status: error.status, message: error.message, backtrace: error.backtrace }
2907
+ { status: error.status, message: error.message, backtrace: error.backtrace }.to_json
2888
2908
  end
2889
2909
  end
2890
2910
 
@@ -2951,6 +2971,16 @@ When this handler is registered the framework hands the original exception to yo
2951
2971
 
2952
2972
  A second raise inside the redispatched handler is not redispatched again — it goes straight to the framework's generic 500. This bounds the chain at one redispatch and prevents loops.
2953
2973
 
2974
+ To have an exception raised inside a `rescue_from` block propagate out of the middleware stack instead, so that Rack middleware above Grape can act on it:
2975
+
2976
+ ```ruby
2977
+ Grape.configure do |config|
2978
+ config.raise_rescue_handler_errors = true
2979
+ end
2980
+ ```
2981
+
2982
+ With this on the exception is re-raised untouched: it is not redispatched, so `rescue_from :internal_grape_exceptions` never sees it and neither `rack.exception` nor `grape.exception` is set. A block that calls `error!` still renders as usual.
2983
+
2954
2984
  You can also rescue all exceptions with a code block and handle the Rack response at the lowest level.
2955
2985
 
2956
2986
  ```ruby
@@ -2993,7 +3023,7 @@ rescue_from APIErrors::ParentError do |e|
2993
3023
  error!({
2994
3024
  error: "#{e.class} error",
2995
3025
  message: e.message
2996
- }, e.status)
3026
+ }, 500)
2997
3027
  end
2998
3028
  ```
2999
3029
 
@@ -3003,10 +3033,8 @@ The code below will rescue exceptions of type `RuntimeError` but _not_ its subcl
3003
3033
  ```ruby
3004
3034
  rescue_from RuntimeError, rescue_subclasses: false do |e|
3005
3035
  error!({
3006
- status: e.status,
3007
- message: e.message,
3008
- errors: e.errors
3009
- }, e.status)
3036
+ message: e.message
3037
+ }, 500)
3010
3038
  end
3011
3039
  ```
3012
3040
 
@@ -3027,7 +3055,7 @@ class Twitter::API < Grape::API
3027
3055
  end
3028
3056
  ```
3029
3057
 
3030
- The `rescue_from` handler must return a `Rack::Response` object, call `error!`, or raise an exception (either the original exception or another custom one). The exception raised in `rescue_from` will be handled outside Grape. For example, if you mount Grape in Rails, the exception will be handle by [Rails Action Controller](https://guides.rubyonrails.org/action_controller_overview.html#rescue).
3058
+ The `rescue_from` handler must return a `Rack::Response` object, call `error!`, or raise an exception (either the original exception or another custom one). An exception raised in `rescue_from` is dispatched to the handler registered for its class; when there is none, Grape answers it itself, as described in [Re-raising from inside a `rescue_from` block](#re-raising-from-inside-a-rescue_from-block).
3031
3059
 
3032
3060
  Alternately, use the `with` option in `rescue_from` to specify a method or a `proc`.
3033
3061
 
@@ -3171,6 +3199,8 @@ class API < Grape::API
3171
3199
  end
3172
3200
  ```
3173
3201
 
3202
+ A logger belongs to the API it is set on, and to the APIs mounted under it that set none of their own. An API that sets none, and is mounted under none that does, logs to a default shared by every such API.
3203
+
3174
3204
  For similar to Rails request logging try the [grape_logging](https://github.com/aserafin/grape_logging) or [grape-middleware-logger](https://github.com/ridiculous/grape-middleware-logger) gems.
3175
3205
 
3176
3206
  ## API Formats
@@ -3199,7 +3229,7 @@ If you declare any `content_type` whatsoever, the Grape defaults will be overrid
3199
3229
  ```ruby
3200
3230
  class Twitter::API < Grape::API
3201
3231
  content_type :xml, 'application/xml'
3202
- content_type :rss, 'application/xml+rss'
3232
+ content_type :rss, 'application/rss+xml'
3203
3233
 
3204
3234
  default_format :xml
3205
3235
  end
@@ -3269,7 +3299,7 @@ end
3269
3299
  * `GET /hello?format=xml` will respond with an HTTP 406 error code, because the XML format specified by the request parameter is not supported.
3270
3300
  * `GET /hello` with an `Accept: application/xml` header will still respond with JSON, since it could not negotiate a recognized content-type from the headers and JSON is the effective default.
3271
3301
 
3272
- The formats apply to parsing, too. The following API will only respond to the JSON content-type and will not parse any other input than `application/json`, `application/x-www-form-urlencoded`, `multipart/form-data`, `multipart/related` and `multipart/mixed`. All other requests will fail with an HTTP 406 error code.
3302
+ The formats apply to parsing, too. The following API will only respond to the JSON content-type and will not parse any other input than `application/json`, `application/x-www-form-urlencoded`, `multipart/form-data`, `multipart/related` and `multipart/mixed`. All other requests will fail with an HTTP 415 error code.
3273
3303
 
3274
3304
  ```ruby
3275
3305
  class Twitter::API < Grape::API
@@ -3277,7 +3307,7 @@ class Twitter::API < Grape::API
3277
3307
  end
3278
3308
  ```
3279
3309
 
3280
- When the content-type is omitted, Grape will return a 406 error code unless `default_format` is specified.
3310
+ When the content-type is omitted, Grape will return a 415 error code unless `default_format` is specified.
3281
3311
  The following API will try to parse any data without a content-type using a JSON parser.
3282
3312
 
3283
3313
  ```ruby
@@ -3288,13 +3318,14 @@ end
3288
3318
  ```
3289
3319
 
3290
3320
  If you combine `format` with `rescue_from :all`, errors will be rendered using the same format.
3291
- If you do not want this behavior, set the default error formatter with `default_error_formatter`.
3321
+ `default_error_formatter` renders the errors of a format that has no error formatter of its own, such as one you declared yourself; it does not replace the error formatter of a built-in format like `:json`. To change how errors of a built-in format are rendered, register one for it with [`error_formatter`](#exception-handling).
3292
3322
 
3293
3323
  ```ruby
3294
3324
  class Twitter::API < Grape::API
3295
- format :json
3296
- content_type :txt, 'text/plain'
3297
- default_error_formatter :txt
3325
+ content_type :xls, 'application/vnd.ms-excel'
3326
+ format :xls
3327
+
3328
+ default_error_formatter :json # :xls has no error formatter of its own
3298
3329
  end
3299
3330
  ```
3300
3331
 
@@ -3329,6 +3360,31 @@ Built-in formatters are the following.
3329
3360
  * `:txt`: use object's `to_txt` when available, otherwise `to_s`
3330
3361
  * `:serializable_hash`: use object's `serializable_hash` when available, otherwise fallback to `:json`
3331
3362
  * `:binary`: data will be returned "as is"
3363
+ * `:jsonl`: one line per element of an Array or Enumerator, otherwise one line, each rendered as `:json` renders it. Not a default content type, see [JSON Lines](#json-lines)
3364
+ * `:md`, `:markdown`: use object's `to_markdown` when available, return a String "as is", otherwise raise `Grape::Exceptions::InvalidFormatter`. Not default content types, see [Serving Markdown](#serving-markdown)
3365
+
3366
+ #### Serving Markdown
3367
+
3368
+ `:md` and `:markdown` are not among the default content types. Declare either one to answer clients that ask for Markdown, such as LLM agents sending `Accept: text/markdown`.
3369
+
3370
+ ```ruby
3371
+ class API < Grape::API
3372
+ content_type :json, 'application/json'
3373
+ content_type :md, 'text/markdown; charset=utf-8'
3374
+ default_format :json
3375
+
3376
+ get '/pages/:id' do
3377
+ Page.find(params[:id]) # renders with Page#to_markdown
3378
+ end
3379
+ end
3380
+ ```
3381
+
3382
+ * `GET /pages/1.md`, `GET /pages/1.markdown`, `GET /pages/1?format=md`, `?format=markdown` and `GET /pages/1` with an `Accept: text/markdown` header answer `text/markdown`.
3383
+ * `:md` and `:markdown` name the same format: declaring either one answers both extensions, under the name declared. A request for `.markdown` on the API above negotiates `:md`, so a `formatter :md` of your own renders it too.
3384
+ * `to_markdown` is the method Rails' `render markdown:` calls (Rails 8.1), so an object written for a Rails controller renders the same way in Grape.
3385
+ * Grape does not convert anything else to Markdown. When the client negotiated Markdown, a body without `to_markdown` that is not a String answers in the default format instead; when the API pins `format :md`, it answers `500`.
3386
+ * [RFC 7763](https://www.rfc-editor.org/rfc/rfc7763) makes `charset` a required parameter of `text/markdown`, hence `charset=utf-8`.
3387
+ * Errors are rendered as plain text, which is valid Markdown.
3332
3388
 
3333
3389
  #### Serving pre-rendered JSON
3334
3390
 
@@ -3369,6 +3425,50 @@ If a body is present in a request to an API, with a Content-Type header value th
3369
3425
 
3370
3426
  Response statuses that indicate no content as defined by [Rack](https://github.com/rack) [here](https://github.com/rack/rack/blob/master/lib/rack/utils.rb#L567) will bypass serialization and the body entity - though there should be none - will not be modified.
3371
3427
 
3428
+ #### JSON Lines
3429
+
3430
+ `:jsonl` serves [JSON Lines](https://jsonlines.org): one JSON value per line, each line ended by a newline. It is not among the default content types, so declare it, as `application/jsonl`, or `application/x-ndjson` if your clients expect that one.
3431
+
3432
+ ```ruby
3433
+ class API < Grape::API
3434
+ content_type :jsonl, 'application/jsonl'
3435
+ format :jsonl
3436
+
3437
+ get :users do
3438
+ User.all # {"id":1,"name":"Ada"}\n{"id":2,"name":"Grace"}\n
3439
+ end
3440
+ end
3441
+ ```
3442
+
3443
+ An `Array` renders one line per element and any other object one line, each as the `:json` formatter renders it, so a `Grape::PrecompiledJson` element is served as its own line. A `String` is served as it is, taken to be JSON Lines already.
3444
+
3445
+ An `Enumerator` is streamed: each element is rendered only as the server reads the body, so a large export is never held in memory whole.
3446
+
3447
+ ```ruby
3448
+ get :export do
3449
+ User.find_each.lazy.map { |user| user.slice(:id, :name) }
3450
+ end
3451
+ ```
3452
+
3453
+ The status and headers of a streamed response are sent before its first line is rendered, so an exception raised while it streams is past `rescue_from`: the response just ends early.
3454
+
3455
+ A request body of `application/jsonl` is parsed into an Array of its lines' values, skipping blank lines. Like a JSON body that is an Array, it is available in `env['api.request.body']` and is not merged into `params`. To validate the lines as params, wrap them in a Hash with a parser of your own.
3456
+
3457
+ ```ruby
3458
+ parser :jsonl, ->(body, env) { { items: Grape::Parser::Jsonl.call(body, env) } }
3459
+
3460
+ params do
3461
+ requires :items, type: Array do
3462
+ requires :name, type: String
3463
+ end
3464
+ end
3465
+ post :import do
3466
+ declared(params)[:items]
3467
+ end
3468
+ ```
3469
+
3470
+ An error renders as a single JSON line.
3471
+
3372
3472
  ### JSONP
3373
3473
 
3374
3474
  Grape supports JSONP via [Rack::JSONP](https://github.com/rack/rack-contrib), part of the [rack-contrib](https://github.com/rack/rack-contrib) gem. Add `rack-contrib` to your `Gemfile`.
@@ -3418,7 +3518,7 @@ end
3418
3518
 
3419
3519
  ## API Data Formats
3420
3520
 
3421
- Grape accepts and parses input data sent with the POST and PUT methods as described in the Parameters section above. It also supports custom data formats. You must declare additional content-types via `content_type` and optionally supply a parser via `parser` unless a parser is already available within Grape to enable a custom format. Such a parser can be a function or a class.
3521
+ Grape accepts and parses input data sent with the `POST`, `PUT`, `PATCH`, `DELETE` and `QUERY` methods as described in the Parameters section above. It also supports custom data formats. You must declare additional content-types via `content_type` and optionally supply a parser via `parser` unless a parser is already available within Grape to enable a custom format. Such a parser can be a function or a class.
3422
3522
 
3423
3523
  With a parser, parsed data is available "as-is" in `env['api.request.body']`.
3424
3524
  Without a parser, data is available "as-is" and in `env['api.request.input']`.
@@ -3476,9 +3576,9 @@ module API
3476
3576
  expose :ip, if: { type: :full }
3477
3577
  expose :user_type, :user_id, if: ->(status, options) { status.user.public? }
3478
3578
  expose :digest do |status, options|
3479
- Digest::MD5.hexdigest(status.txt)
3579
+ Digest::MD5.hexdigest(status.text)
3480
3580
  end
3481
- expose :replies, using: API::Status, as: :replies
3581
+ expose :replies, using: API::Entities::Status, as: :replies
3482
3582
  end
3483
3583
  end
3484
3584
 
@@ -3486,7 +3586,7 @@ module API
3486
3586
  version 'v1'
3487
3587
 
3488
3588
  desc 'Statuses index' do
3489
- params: API::Entities::Status.documentation
3589
+ params API::Entities::Status.documentation
3490
3590
  end
3491
3591
  get '/statuses' do
3492
3592
  statuses = Status.all
@@ -3556,9 +3656,9 @@ You can present `hash` with `Grape::Presenters::Presenter` to keep things consis
3556
3656
 
3557
3657
  ```ruby
3558
3658
  get '/users' do
3559
- present { id: 10, name: :dgz }, with: Grape::Presenters::Presenter
3659
+ present({ id: 10, name: :dgz }, with: Grape::Presenters::Presenter)
3560
3660
  end
3561
- ````
3661
+ ```
3562
3662
  The response will be
3563
3663
 
3564
3664
  ```ruby
@@ -3697,9 +3797,9 @@ Example:
3697
3797
  Grape::Middleware::Auth::Strategies.add(:my_auth, AuthMiddleware, ->(options) { [options[:realm]] } )
3698
3798
 
3699
3799
 
3700
- auth :my_auth, { realm: 'Test Api'} do |credentials|
3800
+ auth :my_auth, realm: 'Test Api' do |username, password|
3701
3801
  # lookup the user's password here
3702
- { 'user1' => 'password1' }[username]
3802
+ { 'user1' => 'password1' }[username] == password
3703
3803
  end
3704
3804
 
3705
3805
  ```
@@ -3728,7 +3828,7 @@ end
3728
3828
  Examine the routes at runtime.
3729
3829
 
3730
3830
  ```ruby
3731
- TwitterAPI::versions # yields [ 'v1', 'v2' ]
3831
+ TwitterAPI::versions # yields ['v1']
3732
3832
  TwitterAPI::routes # yields an array of Grape::Route objects
3733
3833
  TwitterAPI::routes[0].version # => 'v1'
3734
3834
  TwitterAPI::routes[0].description # => 'Includes custom settings.'
@@ -3755,7 +3855,7 @@ class MyAPI < Grape::API
3755
3855
  requires :id, type: Integer, desc: 'Identity.'
3756
3856
  end
3757
3857
  get 'params/:id' do
3758
- route.params[params[:id]] # yields the parameter description
3858
+ route.params['id'] # yields the parameter description
3759
3859
  end
3760
3860
  end
3761
3861
  ```
@@ -3767,7 +3867,7 @@ class ApiLogger < Grape::Middleware::Base
3767
3867
  def before
3768
3868
  file = env['api.endpoint'].source.source_location[0]
3769
3869
  line = env['api.endpoint'].source.source_location[1]
3770
- logger.debug "[api] #{file}:#{line}"
3870
+ env['rack.logger']&.debug "[api] #{file}:#{line}"
3771
3871
  end
3772
3872
  end
3773
3873
  ```
@@ -3789,9 +3889,9 @@ Before and after callbacks execute in the following order:
3789
3889
 
3790
3890
  Steps 4, 5 and 6 only happen if validation succeeds.
3791
3891
 
3792
- If a request for a resource is made with an unsupported HTTP method (returning HTTP 405) only `before` callbacks will be executed. The remaining callbacks will be bypassed.
3892
+ If a request for a resource is made with an unsupported HTTP method (returning HTTP 405) only `before` and `finally` callbacks will be executed. The remaining callbacks will be bypassed.
3793
3893
 
3794
- If a request for a resource is made that triggers the built-in `OPTIONS` handler, only `before` and `after` callbacks will be executed. The remaining callbacks will be bypassed.
3894
+ If a request for a resource is made that triggers the built-in `OPTIONS` handler, only `before`, `after` and `finally` callbacks will be executed. The remaining callbacks will be bypassed.
3795
3895
 
3796
3896
  For example, using a simple `before` block to set a header.
3797
3897
 
@@ -3869,7 +3969,7 @@ The behavior is then:
3869
3969
 
3870
3970
  ```bash
3871
3971
  GET /123 # 'Integer'
3872
- GET /foo # 400 error - 'blah is invalid'
3972
+ GET /foo # 404, the declared Integer narrows the path to digits
3873
3973
  ```
3874
3974
 
3875
3975
  **Versioning**
@@ -3903,8 +4003,8 @@ end
3903
4003
  The behavior is then:
3904
4004
 
3905
4005
  ```bash
3906
- GET /foo/v1 # 'v1-hello'
3907
- GET /foo/v2 # 'v2-hello'
4006
+ GET /v1/foo # 'v1-hello'
4007
+ GET /v2/foo # 'v2-hello'
3908
4008
  ```
3909
4009
 
3910
4010
  **Altering Responses**
@@ -3936,7 +4036,7 @@ Instead of altering a response, you can also terminate and rewrite it from any c
3936
4036
 
3937
4037
  ## Anchoring
3938
4038
 
3939
- Grape by default anchors all request paths, which means that the request URL should match from start to end to match, otherwise a `404 Not Found` is returned. However, this is sometimes not what you want, because it is not always known upfront what can be expected from the call. This is because Rack-mount by default anchors requests to match from the start to the end, or not at all.
4039
+ Grape by default anchors all request paths, which means that the request URL should match from start to end to match, otherwise a `404 Not Found` is returned. However, this is sometimes not what you want, because it is not always known upfront what can be expected from the call.
3940
4040
  Rails solves this problem by using a `anchor: false` option in your routes.
3941
4041
  In Grape this option can be used as well when a method is defined.
3942
4042
 
@@ -4121,7 +4221,7 @@ describe Twitter::API do
4121
4221
  it 'creates many statuses' do
4122
4222
  statuses = [{ text: '...' }, { text: '...'}]
4123
4223
  post '/api/statuses', statuses.to_json, 'CONTENT_TYPE' => 'application/json'
4124
- expect(last_response.body).to eq 201
4224
+ expect(last_response.status).to eq 201
4125
4225
  end
4126
4226
  end
4127
4227
  end
@@ -4154,7 +4254,7 @@ end
4154
4254
  ```ruby
4155
4255
  require 'test_helper'
4156
4256
 
4157
- class Twitter::APITest < MiniTest::Test
4257
+ class Twitter::APITest < Minitest::Test
4158
4258
  include Rack::Test::Methods
4159
4259
 
4160
4260
  def app
@@ -4380,10 +4480,10 @@ For example, `payload[:endpoint].body` provides the current state of the respons
4380
4480
  ```ruby
4381
4481
  ActiveSupport::Notifications.subscribe(/v1/) do |name, start, finish, id, payload|
4382
4482
  hook_record = {
4383
- hook: name
4384
- status: payload[:env]&.dig("api.endpoint")&.status
4385
- format: payload[:env]&.dig("api.format")
4386
- body: payload[:endpoint]&.body
4483
+ hook: name,
4484
+ status: payload[:env]&.dig("api.endpoint")&.status,
4485
+ format: payload[:env]&.dig("api.format"),
4486
+ body: payload[:endpoint]&.body,
4387
4487
  duration: (finish - start) * 1000
4388
4488
  }
4389
4489
  # your code to save the notification