grape-0.13.0/ 0000755 0000041 0000041 00000000000 12563420522 012722 5 ustar www-data www-data grape-0.13.0/Rakefile 0000644 0000041 0000041 00000003307 12563420522 014372 0 ustar www-data www-data require 'rubygems'
require 'bundler'
Bundler.setup :default, :test, :development
Bundler::GemHelper.install_tasks
require 'rspec/core/rake_task'
RSpec::Core::RakeTask.new(:spec) do |spec|
spec.pattern = 'spec/**/*_spec.rb'
end
RSpec::Core::RakeTask.new(:rcov) do |spec|
spec.pattern = 'spec/**/*_spec.rb'
spec.rcov = true
end
task :spec
require 'rainbow/ext/string' unless String.respond_to?(:color)
require 'rubocop/rake_task'
RuboCop::RakeTask.new
task default: [:rubocop, :spec]
begin
require 'yard'
DOC_FILES = ['lib/**/*.rb', 'README.md']
YARD::Rake::YardocTask.new(:doc) do |t|
t.files = DOC_FILES
end
namespace :doc do
YARD::Rake::YardocTask.new(:pages) do |t|
t.files = DOC_FILES
t.options = ['-o', '../grape.doc/docs']
end
namespace :pages do
desc 'Check out gh-pages.'
task :checkout do
dir = File.dirname(__FILE__) + '/../grape.doc'
unless Dir.exist?(dir)
Dir.mkdir(dir)
Dir.chdir(dir) do
system('git init')
system('git remote add origin git@github.com:ruby-grape/grape.git')
system('git pull')
system('git checkout gh-pages')
end
end
end
desc 'Generate and publish YARD docs to GitHub pages.'
task publish: ['doc:pages:checkout', 'doc:pages'] do
Dir.chdir(File.dirname(__FILE__) + '/../grape.doc') do
system('git checkout gh-pages')
system('git add .')
system('git add -u')
system("git commit -m 'Generating docs for version #{Grape::VERSION}.'")
system('git push origin gh-pages')
end
end
end
end
rescue LoadError
puts 'You need to install YARD.'
end
grape-0.13.0/UPGRADING.md 0000644 0000041 0000041 00000035576 12563420522 014604 0 ustar www-data www-data Upgrading Grape
===============
### Upgrading to >= 0.12.0
#### Changes in middleware
The Rack response object is no longer converted to an array by the formatter, enabling streaming. If your custom middleware is accessing `@app_response`, update it to expect a `Rack::Response` instance instead of an array.
For example,
```ruby
class CacheBusterMiddleware < Grape::Middleware::Base
def after
@app_response[1]['Expires'] = Time.at(0).utc.to_s
@app_response
end
end
```
becomes
```ruby
class CacheBusterMiddleware < Grape::Middleware::Base
def after
@app_response.headers['Expires'] = Time.at(0).utc.to_s
@app_response
end
end
```
See [#1029](https://github.com/ruby-grape/grape/pull/1029) for more information.
#### Changes in present
Using `present` with objects that responded to `merge` would cause early evaluation of the represented object, with unexpected side-effects, such as missing parameters or environment within rendering code. Grape now only merges represented objects with a previously rendered body, usually when multiple `present` calls are made in the same route.
See [grape-with-roar#5](https://github.com/dblock/grape-with-roar/issues/5) and [#1023](https://github.com/ruby-grape/grape/issues/1023).
#### Changes to regexp validator
Parameters with `nil` value will now pass `regexp` validation. To disallow `nil` value for an endpoint, add `allow_blank: false`.
```ruby
params do
requires :email, allow_blank: false, regexp: /.+@.+/
end
```
See [#957](https://github.com/ruby-grape/grape/pull/957) for more information.
#### Replace error_response with error! in rescue_from blocks
Note: `error_response` is being deprecated, not removed.
```ruby
def error!(message, status = options[:default_status], headers = {}, backtrace = [])
headers = { 'Content-Type' => content_type }.merge(headers)
rack_response(format_message(message, backtrace), status, headers)
end
```
For example,
```
error_response({ message: { message: 'No such page.', id: 'missing_page' }, status: 404, headers: { 'Content-Type' => 'api/error' })
```
becomes
```
error!({ message: 'No such page.', id: 'missing_page' }, 404, { 'Content-Type' => 'api/error' })
```
`error!` also supports just passing a message. `error!('Server error.')` and `format: :json` returns the following JSON response
```
{ 'error': 'Server error. }
```
with a status code of 500 and a Content Type of text/error.
Optionally, also replace `Rack::Response.new` with `error!.`
The following are equivalent:
```
Rack::Response.new([ e.message ], 500, { "Content-type" => "text/error" }).finish
error!(e)
```
See [#889](https://github.com/ruby-grape/grape/issues/889) for more information.
#### Changes to routes when using `format`
Version 0.10.0 has introduced a change via [#809](https://github.com/ruby-grape/grape/pull/809) whereas routes no longer got file-type suffixes added if you declared a single API `format`. This has been reverted, it's now again possible to call API with proper suffix when single `format` is defined:
```ruby
class API < Grape::API
format :json
get :hello do
{ hello: 'world' }
end
end
```
Will respond with JSON to `/hello` **and** `/hello.json`.
Will respond with 404 to `/hello.xml`, `/hello.txt` etc.
See the [#1001](https://github.com/ruby-grape/grape/pull/1001) and [#914](https://github.com/ruby-grape/grape/issues/914) for more info.
### Upgrading to >= 0.11.0
#### Added Rack 1.6.0 support
Grape now supports, but doesn't require Rack 1.6.0. If you encounter an issue with parsing requests larger than 128KB, explictly require Rack 1.6.0 in your Gemfile.
```ruby
gem 'rack', '~> 1.6.0'
```
See [#559](https://github.com/ruby-grape/grape/issues/559) for more information.
#### Removed route_info
Key route_info is excluded from params.
See [#879](https://github.com/ruby-grape/grape/pull/879) for more information.
#### Fix callbacks within a version block
Callbacks defined in a version block are only called for the routes defined in that block. This was a regression introduced in Grape 0.10.0, and is fixed in this version.
See [#901](https://github.com/ruby-grape/grape/pull/901) for more information.
#### Make type of group of parameters required
Groups of parameters now require their type to be set explicitly as Array or Hash.
Not setting the type now results in MissingGroupTypeError, unsupported type will raise UnsupportedTypeError.
See [#886](https://github.com/ruby-grape/grape/pull/886) for more information.
### Upgrading to >= 0.10.1
#### Changes to `declared(params, include_missing: false)`
Attributes with `nil` values or with values that evaluate to `false` are no longer considered *missing* and will be returned when `include_missing` is set to `false`.
See [#864](https://github.com/ruby-grape/grape/pull/864) for more information.
### Upgrading to >= 0.10.0
#### Changes to content-types
The following content-types have been removed:
* atom (application/atom+xml)
* rss (application/rss+xml)
* jsonapi (application/jsonapi)
This is because they have never been properly supported.
#### Changes to desc
New block syntax:
Former:
```ruby
desc "some descs",
detail: 'more details',
entity: API::Entities::Entity,
params: API::Entities::Status.documentation,
named: 'a name',
headers: [XAuthToken: {
description: 'Valdates your identity',
required: true
}
get nil, http_codes: [
[401, 'Unauthorized', API::Entities::BaseError],
[404, 'not found', API::Entities::Error]
] do
```
Now:
```ruby
desc "some descs" do
detail 'more details'
params API::Entities::Status.documentation
success API::Entities::Entity
failure [
[401, 'Unauthorized', API::Entities::BaseError],
[404, 'not found', API::Entities::Error]
]
named 'a name'
headers [
XAuthToken: {
description: 'Valdates your identity',
required: true
},
XOptionalHeader: {
description: 'Not really needed',
required: false
}
]
end
```
#### Changes to Route Options and Descriptions
A common hack to extend Grape with custom DSL methods was manipulating `@last_description`.
``` ruby
module Grape
module Extensions
module SortExtension
def sort(value)
@last_description ||= {}
@last_description[:sort] ||= {}
@last_description[:sort].merge! value
value
end
end
Grape::API.extend self
end
end
```
You could access this value from within the API with `route.route_sort` or, more generally, via `env['api.endpoint'].options[:route_options][:sort]`.
This will no longer work, use the documented and supported `route_setting`.
``` ruby
module Grape
module Extensions
module SortExtension
def sort(value)
route_setting :sort, sort: value
value
end
end
Grape::API.extend self
end
end
```
To retrieve this value at runtime from within an API, use `env['api.endpoint'].route_setting(:sort)` and when introspecting a mounted API, use `route.route_settings[:sort]`.
#### Accessing Class Variables from Helpers
It used to be possible to fetch an API class variable from a helper function. For example:
```ruby
@@static_variable = 42
helpers do
def get_static_variable
@@static_variable
end
end
get do
get_static_variable
end
```
This will no longer work. Use a class method instead of a helper.
```ruby
@@static_variable = 42
def self.get_static_variable
@@static_variable
end
get do
get_static_variable
end
```
For more information see [#836](https://github.com/ruby-grape/grape/issues/836).
#### Changes to Custom Validators
To implement a custom validator, you need to inherit from `Grape::Validations::Base` instead of `Grape::Validations::Validator`.
For more information see [Custom Validators](https://github.com/ruby-grape/grape#custom-validators) in the documentation.
#### Changes to Raising Grape::Exceptions::Validation
In previous versions raising `Grape::Exceptions::Validation` required a single `param`.
```ruby
raise Grape::Exceptions::Validation, param: :id, message_key: :presence
```
The `param` argument has been deprecated and is now an array of `params`, accepting multiple values.
```ruby
raise Grape::Exceptions::Validation, params: [:id], message_key: :presence
```
#### Changes to routes when using `format`
Routes will no longer get file-type suffixes added if you declare a single API `format`. For example,
```ruby
class API < Grape::API
format :json
get :hello do
{ hello: 'world' }
end
end
```
Pre-0.10.0, this would respond with JSON to `/hello`, `/hello.json`, `/hello.xml`, `/hello.txt`, etc.
Now, this will only respond with JSON to `/hello`, but will be a 404 when trying to access `/hello.json`, `/hello.xml`, `/hello.txt`, etc.
If you declare further `content_type`s, this behavior will be circumvented. For example, the following API will respond with JSON to `/hello`, `/hello.json`, `/hello.xml`, `/hello.txt`, etc.
```ruby
class API < Grape::API
format :json
content_type :json, 'application/json'
get :hello do
{ hello: 'world' }
end
end
```
See the [the updated API Formats documentation](https://github.com/ruby-grape/grape#api-formats) and [#809](https://github.com/ruby-grape/grape/pull/809) for more info.
#### Changes to Evaluation of Permitted Parameter Values
Permitted and default parameter values are now only evaluated lazily for each request when declared as a proc. The following code would raise an error at startup time.
```ruby
params do
optional :v, values: -> { [:x, :y] }, default: -> { :z } }
end
```
Remove the proc to get the previous behavior.
```ruby
params do
optional :v, values: [:x, :y], default: :z }
end
```
See [#801](https://github.com/ruby-grape/grape/issues/801) for more information.
#### Changes to version
If version is used with a block, the callbacks defined within that version block are not scoped to that individual block. In other words, the callback would be inherited by all versions blocks that follow the first one e.g
```ruby
class API < Grape::API
resource :foo do
version 'v1', :using => :path do
before do
@output ||= 'hello1'
end
get '/' do
@output += '-v1'
end
end
version 'v2', :using => :path do
before do
@output ||= 'hello2'
end
get '/:id' do
@output += '-v2'
end
end
end
end
```
when making a API call `GET /foo/v2/1`, the API would set instance variable `@output` to `hello1-v2`
See [#898](https://github.com/ruby-grape/grape/issues/898) for more information.
### Upgrading to >= 0.9.0
#### Changes in Authentication
The following middleware classes have been removed:
* `Grape::Middleware::Auth::Basic`
* `Grape::Middleware::Auth::Digest`
* `Grape::Middleware::Auth::OAuth2`
When you use theses classes directly like:
```ruby
module API
class Root < Grape::API
class Protected < Grape::API
use Grape::Middleware::Auth::OAuth2,
token_class: 'AccessToken',
parameter: %w(access_token api_key)
```
you have to replace these classes.
As replacement can be used
* `Grape::Middleware::Auth::Basic` => [`Rack::Auth::Basic`](https://github.com/rack/rack/blob/master/lib/rack/auth/basic.rb)
* `Grape::Middleware::Auth::Digest` => [`Rack::Auth::Digest::MD5`](https://github.com/rack/rack/blob/master/lib/rack/auth/digest/md5.rb)
* `Grape::Middleware::Auth::OAuth2` => [warden-oauth2](https://github.com/opperator/warden-oauth2) or [rack-oauth2](https://github.com/nov/rack-oauth2)
If this is not possible you can extract the middleware files from [grape v0.7.0](https://github.com/ruby-grape/grape/tree/v0.7.0/lib/grape/middleware/auth)
and host these files within your application
See [#703](https://github.com/ruby-grape/Grape/pull/703) for more information.
### Upgrading to >= 0.7.0
#### Changes in Exception Handling
Assume you have the following exception classes defined.
```ruby
class ParentError < StandardError; end
class ChildError < ParentError; end
```
In Grape <= 0.6.1, the `rescue_from` keyword only handled the exact exception being raised. The following code would rescue `ParentError`, but not `ChildError`.
```ruby
rescue_from ParentError do |e|
# only rescue ParentError
end
```
This made it impossible to rescue an exception hieararchy, which is a more sensible default. In Grape 0.7.0 or newer, both `ParentError` and `ChildError` are rescued.
```ruby
rescue_from ParentError do |e|
# rescue both ParentError and ChildError
end
```
To only rescue the base exception class, set `rescue_subclasses: false`.
```ruby
rescue_from ParentError, rescue_subclasses: false do |e|
# only rescue ParentError
end
```
See [#544](https://github.com/ruby-grape/grape/pull/544) for more information.
#### Changes in the Default HTTP Status Code
In Grape <= 0.6.1, the default status code returned from `error!` was 403.
```ruby
error! "You may not reticulate this spline!" # yields HTTP error 403
```
This was a bad default value, since 403 means "Forbidden". Change any call to `error!` that does not specify a status code to specify one. The new default value is a more sensible default of 500, which is "Internal Server Error".
```ruby
error! "You may not reticulate this spline!", 403 # yields HTTP error 403
```
You may also use `default_error_status` to change the global default.
```ruby
default_error_status 400
```
See [#525](https://github.com/ruby-grape/Grape/pull/525) for more information.
#### Changes in Parameter Declaration and Validation
In Grape <= 0.6.1, `group`, `optional` and `requires` keywords with a block accepted either an `Array` or a `Hash`.
```ruby
params do
requires :id, type: Integer
group :name do
requires :first_name
requires :last_name
end
end
```
This caused the ambiguity and unexpected errors described in [#543](https://github.com/ruby-grape/Grape/issues/543).
In Grape 0.7.0, the `group`, `optional` and `requires` keywords take an additional `type` attribute which defaults to `Array`. This means that without a `type` attribute, these nested parameters will no longer accept a single hash, only an array (of hashes).
Whereas in 0.6.1 the API above accepted the following json, it no longer does in 0.7.0.
```json
{
"id": 1,
"name": {
"first_name": "John",
"last_name" : "Doe"
}
}
```
The `params` block should now read as follows.
```ruby
params do
requires :id, type: Integer
requires :name, type: Hash do
requires :first_name
requires :last_name
end
end
```
See [#545](https://github.com/ruby-grape/Grape/pull/545) for more information.
### Upgrading to 0.6.0
In Grape <= 0.5.0, only the first validation error was raised and processing aborted. Validation errors are now collected and a single `Grape::Exceptions::ValidationErrors` exception is raised. You can access the collection of validation errors as `.errors`.
```ruby
rescue_from Grape::Exceptions::Validations do |e|
Rack::Response.new({
status: 422,
message: e.message,
errors: e.errors
}.to_json, 422)
end
```
For more information see [#462](https://github.com/ruby-grape/grape/issues/462).
grape-0.13.0/Gemfile 0000644 0000041 0000041 00000000236 12563420522 014216 0 ustar www-data www-data source 'https://rubygems.org'
gemspec
group :development, :test do
gem 'rubocop', '~> 0.31.0'
gem 'guard'
gem 'guard-rspec'
gem 'guard-rubocop'
end
grape-0.13.0/.rubocop_todo.yml 0000644 0000041 0000041 00000003513 12563420522 016223 0 ustar www-data www-data # This configuration was generated by `rubocop --auto-gen-config`
# on 2015-06-04 09:15:17 -0400 using RuboCop version 0.31.0.
# The point is for the user to remove these configuration records
# one by one as the offenses are removed from the code base.
# Note that changes in the inspected code, or installation of new
# versions of RuboCop, may require this file to be generated again.
# Offense count: 37
Metrics/AbcSize:
Max: 48
# Offense count: 2
Metrics/BlockNesting:
Max: 4
# Offense count: 4
# Configuration parameters: CountComments.
Metrics/ClassLength:
Max: 252
# Offense count: 23
Metrics/CyclomaticComplexity:
Max: 20
# Offense count: 675
# Configuration parameters: AllowURI, URISchemes.
Metrics/LineLength:
Max: 198
# Offense count: 44
# Configuration parameters: CountComments.
Metrics/MethodLength:
Max: 35
# Offense count: 8
# Configuration parameters: CountComments.
Metrics/ModuleLength:
Max: 271
# Offense count: 17
Metrics/PerceivedComplexity:
Max: 22
# Offense count: 26
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, SupportedStyles, ProceduralMethods, FunctionalMethods, IgnoredMethods.
Style/BlockDelimiters:
Enabled: false
# Offense count: 174
Style/Documentation:
Enabled: false
# Offense count: 7
Style/DoubleNegation:
Enabled: false
# Offense count: 5
Style/EachWithObject:
Enabled: false
# Offense count: 15
# Configuration parameters: MinBodyLength.
Style/GuardClause:
Enabled: false
# Offense count: 4
# Cop supports --auto-correct.
Style/Lambda:
Enabled: false
# Offense count: 3
Style/MultilineTernaryOperator:
Enabled: false
# Offense count: 3
# Configuration parameters: NamePrefix, NamePrefixBlacklist.
Style/PredicateName:
Enabled: false
# Offense count: 13
# Configuration parameters: EnforcedStyle, SupportedStyles.
Style/RaiseArgs:
Enabled: false
grape-0.13.0/.rspec 0000644 0000041 0000041 00000000037 12563420522 014037 0 ustar www-data www-data --color
--format=documentation
grape-0.13.0/grape.gemspec 0000644 0000041 0000041 00000003166 12563420522 015373 0 ustar www-data www-data $LOAD_PATH.push File.expand_path('../lib', __FILE__)
require 'grape/version'
Gem::Specification.new do |s|
s.name = 'grape'
s.version = Grape::VERSION
s.platform = Gem::Platform::RUBY
s.authors = ['Michael Bleigh']
s.email = ['michael@intridea.com']
s.homepage = 'https://github.com/ruby-grape/grape'
s.summary = 'A simple Ruby framework for building REST-like APIs.'
s.description = 'A Ruby framework for rapid API development with great conventions.'
s.license = 'MIT'
s.add_runtime_dependency 'rack', '>= 1.3.0'
s.add_runtime_dependency 'rack-mount'
s.add_runtime_dependency 'rack-accept'
s.add_runtime_dependency 'activesupport'
s.add_runtime_dependency 'multi_json', '>= 1.3.2'
s.add_runtime_dependency 'multi_xml', '>= 0.5.2'
s.add_runtime_dependency 'hashie', '>= 2.1.0'
s.add_runtime_dependency 'virtus', '>= 1.0.0'
s.add_runtime_dependency 'builder'
s.add_development_dependency 'grape-entity', '>= 0.4.4'
s.add_development_dependency 'rake'
s.add_development_dependency 'maruku'
s.add_development_dependency 'yard'
s.add_development_dependency 'rack-test'
s.add_development_dependency 'rspec', '~> 3.0'
s.add_development_dependency 'bundler'
s.add_development_dependency 'cookiejar'
s.add_development_dependency 'rack-contrib'
s.add_development_dependency 'mime-types'
s.add_development_dependency 'appraisal'
s.files = `git ls-files`.split("\n")
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
s.require_paths = ['lib']
end
grape-0.13.0/spec/ 0000755 0000041 0000041 00000000000 12563420522 013654 5 ustar www-data www-data grape-0.13.0/spec/grape/ 0000755 0000041 0000041 00000000000 12563420522 014752 5 ustar www-data www-data grape-0.13.0/spec/grape/middleware/ 0000755 0000041 0000041 00000000000 12563420522 017067 5 ustar www-data www-data grape-0.13.0/spec/grape/middleware/exception_spec.rb 0000644 0000041 0000041 00000013472 12563420522 022433 0 ustar www-data www-data require 'spec_helper'
require 'active_support/core_ext/hash'
describe Grape::Middleware::Error do
# raises a text exception
class ExceptionApp
class << self
def call(_env)
fail 'rain!'
end
end
end
# raises a hash error
class ErrorHashApp
class << self
def error!(message, status)
throw :error, message: { error: message, detail: 'missing widget' }, status: status
end
def call(_env)
error!('rain!', 401)
end
end
end
# raises an error!
class AccessDeniedApp
class << self
def error!(message, status)
throw :error, message: message, status: status
end
def call(_env)
error!('Access Denied', 401)
end
end
end
# raises a custom error
class CustomError < Grape::Exceptions::Base
end
class CustomErrorApp
class << self
def call(_env)
fail CustomError, status: 400, message: 'failed validation'
end
end
end
attr_reader :app
it 'does not trap errors by default' do
@app ||= Rack::Builder.app do
use Spec::Support::EndpointFaker
use Grape::Middleware::Error
run ExceptionApp
end
expect { get '/' }.to raise_error
end
context 'with rescue_all set to true' do
it 'sets the message appropriately' do
@app ||= Rack::Builder.app do
use Spec::Support::EndpointFaker
use Grape::Middleware::Error, rescue_all: true
run ExceptionApp
end
get '/'
expect(last_response.body).to eq('rain!')
end
it 'defaults to a 500 status' do
@app ||= Rack::Builder.app do
use Spec::Support::EndpointFaker
use Grape::Middleware::Error, rescue_all: true
run ExceptionApp
end
get '/'
expect(last_response.status).to eq(500)
end
it 'is possible to specify a different default status code' do
@app ||= Rack::Builder.app do
use Spec::Support::EndpointFaker
use Grape::Middleware::Error, rescue_all: true, default_status: 500
run ExceptionApp
end
get '/'
expect(last_response.status).to eq(500)
end
it 'is possible to return errors in json format' do
@app ||= Rack::Builder.app do
use Spec::Support::EndpointFaker
use Grape::Middleware::Error, rescue_all: true, format: :json
run ExceptionApp
end
get '/'
expect(last_response.body).to eq('{"error":"rain!"}')
end
it 'is possible to return hash errors in json format' do
@app ||= Rack::Builder.app do
use Spec::Support::EndpointFaker
use Grape::Middleware::Error, rescue_all: true, format: :json
run ErrorHashApp
end
get '/'
expect(['{"error":"rain!","detail":"missing widget"}',
'{"detail":"missing widget","error":"rain!"}']).to include(last_response.body)
end
it 'is possible to return errors in jsonapi format' do
@app ||= Rack::Builder.app do
use Spec::Support::EndpointFaker
use Grape::Middleware::Error, rescue_all: true, format: :jsonapi
run ExceptionApp
end
get '/'
expect(last_response.body).to eq('{"error":"rain!"}')
end
it 'is possible to return hash errors in jsonapi format' do
@app ||= Rack::Builder.app do
use Spec::Support::EndpointFaker
use Grape::Middleware::Error, rescue_all: true, format: :jsonapi
run ErrorHashApp
end
get '/'
expect(['{"error":"rain!","detail":"missing widget"}',
'{"detail":"missing widget","error":"rain!"}']).to include(last_response.body)
end
it 'is possible to return errors in xml format' do
@app ||= Rack::Builder.app do
use Spec::Support::EndpointFaker
use Grape::Middleware::Error, rescue_all: true, format: :xml
run ExceptionApp
end
get '/'
expect(last_response.body).to eq("\n\n rain!\n\n")
end
it 'is possible to return hash errors in xml format' do
@app ||= Rack::Builder.app do
use Spec::Support::EndpointFaker
use Grape::Middleware::Error, rescue_all: true, format: :xml
run ErrorHashApp
end
get '/'
expect(["\n\n missing widget\n rain!\n\n",
"\n\n rain!\n missing widget\n\n"]).to include(last_response.body)
end
it 'is possible to specify a custom formatter' do
@app ||= Rack::Builder.app do
use Spec::Support::EndpointFaker
use Grape::Middleware::Error, rescue_all: true,
format: :custom,
error_formatters: {
custom: lambda do |message, _backtrace, _options, _env|
{ custom_formatter: message }.inspect
end
}
run ExceptionApp
end
get '/'
expect(last_response.body).to eq('{:custom_formatter=>"rain!"}')
end
it 'does not trap regular error! codes' do
@app ||= Rack::Builder.app do
use Spec::Support::EndpointFaker
use Grape::Middleware::Error
run AccessDeniedApp
end
get '/'
expect(last_response.status).to eq(401)
end
it 'responds to custom Grape exceptions appropriately' do
@app ||= Rack::Builder.app do
use Spec::Support::EndpointFaker
use Grape::Middleware::Error, rescue_all: false
run CustomErrorApp
end
get '/'
expect(last_response.status).to eq(400)
expect(last_response.body).to eq('failed validation')
end
end
end
grape-0.13.0/spec/grape/middleware/versioner/ 0000755 0000041 0000041 00000000000 12563420522 021103 5 ustar www-data www-data grape-0.13.0/spec/grape/middleware/versioner/header_spec.rb 0000644 0000041 0000041 00000024214 12563420522 023675 0 ustar www-data www-data require 'spec_helper'
describe Grape::Middleware::Versioner::Header do
let(:app) { ->(env) { [200, env, env] } }
subject { Grape::Middleware::Versioner::Header.new(app, @options || {}) }
before do
@options = {
version_options: {
using: :header,
vendor: 'vendor'
}
}
end
context 'api.type and api.subtype' do
it 'sets type and subtype to first choice of content type if no preference given' do
status, _, env = subject.call('HTTP_ACCEPT' => '*/*')
expect(env['api.type']).to eql 'application'
expect(env['api.subtype']).to eql 'vnd.vendor+xml'
expect(status).to eq(200)
end
it 'sets preferred type' do
status, _, env = subject.call('HTTP_ACCEPT' => 'application/*')
expect(env['api.type']).to eql 'application'
expect(env['api.subtype']).to eql 'vnd.vendor+xml'
expect(status).to eq(200)
end
it 'sets preferred type and subtype' do
status, _, env = subject.call('HTTP_ACCEPT' => 'text/plain')
expect(env['api.type']).to eql 'text'
expect(env['api.subtype']).to eql 'plain'
expect(status).to eq(200)
end
end
context 'api.format' do
it 'is set' do
status, _, env = subject.call('HTTP_ACCEPT' => 'application/vnd.vendor+json')
expect(env['api.format']).to eql 'json'
expect(status).to eq(200)
end
it 'is nil if not provided' do
status, _, env = subject.call('HTTP_ACCEPT' => 'application/vnd.vendor')
expect(env['api.format']).to eql nil
expect(status).to eq(200)
end
['v1', :v1].each do |version|
context 'when version is set to #{version{ ' do
before do
@options[:versions] = [version]
end
it 'is set' do
status, _, env = subject.call('HTTP_ACCEPT' => 'application/vnd.vendor-v1+json')
expect(env['api.format']).to eql 'json'
expect(status).to eq(200)
end
it 'is nil if not provided' do
status, _, env = subject.call('HTTP_ACCEPT' => 'application/vnd.vendor-v1')
expect(env['api.format']).to eql nil
expect(status).to eq(200)
end
end
end
end
context 'api.vendor' do
it 'is set' do
status, _, env = subject.call('HTTP_ACCEPT' => 'application/vnd.vendor')
expect(env['api.vendor']).to eql 'vendor'
expect(status).to eq(200)
end
it 'is set if format provided' do
status, _, env = subject.call('HTTP_ACCEPT' => 'application/vnd.vendor+json')
expect(env['api.vendor']).to eql 'vendor'
expect(status).to eq(200)
end
it 'fails with 406 Not Acceptable if vendor is invalid' do
expect { subject.call('HTTP_ACCEPT' => 'application/vnd.othervendor+json').last }
.to raise_exception do |exception|
expect(exception).to be_a(Grape::Exceptions::InvalidAcceptHeader)
expect(exception.headers).to eql('X-Cascade' => 'pass')
expect(exception.status).to eql 406
expect(exception.message).to include 'API vendor or version not found'
end
end
context 'when version is set' do
before do
@options[:versions] = ['v1']
end
it 'is set' do
status, _, env = subject.call('HTTP_ACCEPT' => 'application/vnd.vendor-v1')
expect(env['api.vendor']).to eql 'vendor'
expect(status).to eq(200)
end
it 'is set if format provided' do
status, _, env = subject.call('HTTP_ACCEPT' => 'application/vnd.vendor-v1+json')
expect(env['api.vendor']).to eql 'vendor'
expect(status).to eq(200)
end
it 'fails with 406 Not Acceptable if vendor is invalid' do
expect { subject.call('HTTP_ACCEPT' => 'application/vnd.othervendor-v1+json').last }
.to raise_exception do |exception|
expect(exception).to be_a(Grape::Exceptions::InvalidAcceptHeader)
expect(exception.headers).to eql('X-Cascade' => 'pass')
expect(exception.status).to eql 406
expect(exception.message).to include('API vendor or version not found')
end
end
end
end
context 'api.version' do
before do
@options[:versions] = ['v1']
end
it 'is set' do
status, _, env = subject.call('HTTP_ACCEPT' => 'application/vnd.vendor-v1')
expect(env['api.version']).to eql 'v1'
expect(status).to eq(200)
end
it 'is set if format provided' do
status, _, env = subject.call('HTTP_ACCEPT' => 'application/vnd.vendor-v1+json')
expect(env['api.version']).to eql 'v1'
expect(status).to eq(200)
end
it 'fails with 406 Not Acceptable if version is invalid' do
expect { subject.call('HTTP_ACCEPT' => 'application/vnd.vendor-v2+json').last }.to raise_exception do |exception|
expect(exception).to be_a(Grape::Exceptions::InvalidAcceptHeader)
expect(exception.headers).to eql('X-Cascade' => 'pass')
expect(exception.status).to eql 406
expect(exception.message).to include('API vendor or version not found')
end
end
end
it 'succeeds if :strict is not set' do
expect(subject.call('HTTP_ACCEPT' => '').first).to eq(200)
expect(subject.call({}).first).to eq(200)
end
it 'succeeds if :strict is set to false' do
@options[:version_options][:strict] = false
expect(subject.call('HTTP_ACCEPT' => '').first).to eq(200)
expect(subject.call({}).first).to eq(200)
end
context 'when :strict is set' do
before do
@options[:versions] = ['v1']
@options[:version_options][:strict] = true
end
it 'fails with 406 Not Acceptable if header is not set' do
expect { subject.call({}).last }.to raise_exception do |exception|
expect(exception).to be_a(Grape::Exceptions::InvalidAcceptHeader)
expect(exception.headers).to eql('X-Cascade' => 'pass')
expect(exception.status).to eql 406
expect(exception.message).to include('Accept header must be set.')
end
end
it 'fails with 406 Not Acceptable if header is empty' do
expect { subject.call('HTTP_ACCEPT' => '').last }.to raise_exception do |exception|
expect(exception).to be_a(Grape::Exceptions::InvalidAcceptHeader)
expect(exception.headers).to eql('X-Cascade' => 'pass')
expect(exception.status).to eql 406
expect(exception.message).to include('Accept header must be set.')
end
end
it 'fails with 406 Not Acceptable if type is a range' do
expect { subject.call('HTTP_ACCEPT' => '*/*').last }.to raise_exception do |exception|
expect(exception).to be_a(Grape::Exceptions::InvalidAcceptHeader)
expect(exception.headers).to eql('X-Cascade' => 'pass')
expect(exception.status).to eql 406
expect(exception.message).to include('Accept header must not contain ranges ("*").')
end
end
it 'fails with 406 Not Acceptable if subtype is a range' do
expect { subject.call('HTTP_ACCEPT' => 'application/*').last }.to raise_exception do |exception|
expect(exception).to be_a(Grape::Exceptions::InvalidAcceptHeader)
expect(exception.headers).to eql('X-Cascade' => 'pass')
expect(exception.status).to eql 406
expect(exception.message).to include('Accept header must not contain ranges ("*").')
end
end
it 'succeeds if proper header is set' do
expect(subject.call('HTTP_ACCEPT' => 'application/vnd.vendor-v1+json').first).to eq(200)
end
end
context 'when :strict and :cascade=>false are set' do
before do
@options[:versions] = ['v1']
@options[:version_options][:strict] = true
@options[:version_options][:cascade] = false
end
it 'fails with 406 Not Acceptable if header is not set' do
expect { subject.call({}).last }.to raise_exception do |exception|
expect(exception).to be_a(Grape::Exceptions::InvalidAcceptHeader)
expect(exception.headers).to eql({})
expect(exception.status).to eql 406
expect(exception.message).to include('Accept header must be set.')
end
end
it 'fails with 406 Not Acceptable if header is empty' do
expect { subject.call('HTTP_ACCEPT' => '').last }.to raise_exception do |exception|
expect(exception).to be_a(Grape::Exceptions::InvalidAcceptHeader)
expect(exception.headers).to eql({})
expect(exception.status).to eql 406
expect(exception.message).to include('Accept header must be set.')
end
end
it 'fails with 406 Not Acceptable if type is a range' do
expect { subject.call('HTTP_ACCEPT' => '*/*').last }.to raise_exception do |exception|
expect(exception).to be_a(Grape::Exceptions::InvalidAcceptHeader)
expect(exception.headers).to eql({})
expect(exception.status).to eql 406
expect(exception.message).to include('Accept header must not contain ranges ("*").')
end
end
it 'fails with 406 Not Acceptable if subtype is a range' do
expect { subject.call('HTTP_ACCEPT' => 'application/*').last }.to raise_exception do |exception|
expect(exception).to be_a(Grape::Exceptions::InvalidAcceptHeader)
expect(exception.headers).to eql({})
expect(exception.status).to eql 406
expect(exception.message).to include('Accept header must not contain ranges ("*").')
end
end
it 'succeeds if proper header is set' do
expect(subject.call('HTTP_ACCEPT' => 'application/vnd.vendor-v1+json').first).to eq(200)
end
end
context 'when multiple versions are specified' do
before do
@options[:versions] = %w(v1 v2)
end
it 'succeeds with v1' do
expect(subject.call('HTTP_ACCEPT' => 'application/vnd.vendor-v1+json').first).to eq(200)
end
it 'succeeds with v2' do
expect(subject.call('HTTP_ACCEPT' => 'application/vnd.vendor-v2+json').first).to eq(200)
end
it 'fails with another version' do
expect { subject.call('HTTP_ACCEPT' => 'application/vnd.vendor-v3+json') }.to raise_exception do |exception|
expect(exception).to be_a(Grape::Exceptions::InvalidAcceptHeader)
expect(exception.headers).to eql('X-Cascade' => 'pass')
expect(exception.status).to eql 406
expect(exception.message).to include('API vendor or version not found')
end
end
end
end
grape-0.13.0/spec/grape/middleware/versioner/path_spec.rb 0000644 0000041 0000041 00000002673 12563420522 023406 0 ustar www-data www-data require 'spec_helper'
describe Grape::Middleware::Versioner::Path do
let(:app) { ->(env) { [200, env, env['api.version']] } }
subject { Grape::Middleware::Versioner::Path.new(app, @options || {}) }
it 'sets the API version based on the first path' do
expect(subject.call('PATH_INFO' => '/v1/awesome').last).to eq('v1')
end
it 'does not cut the version out of the path' do
expect(subject.call('PATH_INFO' => '/v1/awesome')[1]['PATH_INFO']).to eq('/v1/awesome')
end
it 'provides a nil version if no path is given' do
expect(subject.call('PATH_INFO' => '/').last).to be_nil
end
context 'with a pattern' do
before { @options = { pattern: /v./i } }
it 'sets the version if it matches' do
expect(subject.call('PATH_INFO' => '/v1/awesome').last).to eq('v1')
end
it 'ignores the version if it fails to match' do
expect(subject.call('PATH_INFO' => '/awesome/radical').last).to be_nil
end
end
[%w(v1 v2), [:v1, :v2], [:v1, 'v2'], ['v1', :v2]].each do |versions|
context 'with specified versions as #{versions}' do
before { @options = { versions: versions } }
it 'throws an error if a non-allowed version is specified' do
expect(catch(:error) { subject.call('PATH_INFO' => '/v3/awesome') }[:status]).to eq(404)
end
it 'allows versions that have been specified' do
expect(subject.call('PATH_INFO' => '/v1/asoasd').last).to eq('v1')
end
end
end
end
grape-0.13.0/spec/grape/middleware/versioner/accept_version_header_spec.rb 0000644 0000041 0000041 00000006315 12563420522 026763 0 ustar www-data www-data require 'spec_helper'
describe Grape::Middleware::Versioner::AcceptVersionHeader do
let(:app) { ->(env) { [200, env, env] } }
subject { Grape::Middleware::Versioner::AcceptVersionHeader.new(app, @options || {}) }
before do
@options = {
version_options: {
using: :accept_version_header
}
}
end
context 'api.version' do
before do
@options[:versions] = ['v1']
end
it 'is set' do
status, _, env = subject.call('HTTP_ACCEPT_VERSION' => 'v1')
expect(env['api.version']).to eql 'v1'
expect(status).to eq(200)
end
it 'is set if format provided' do
status, _, env = subject.call('HTTP_ACCEPT_VERSION' => 'v1')
expect(env['api.version']).to eql 'v1'
expect(status).to eq(200)
end
it 'fails with 406 Not Acceptable if version is not supported' do
expect do
subject.call('HTTP_ACCEPT_VERSION' => 'v2').last
end.to throw_symbol(
:error,
status: 406,
headers: { 'X-Cascade' => 'pass' },
message: 'The requested version is not supported.'
)
end
end
it 'succeeds if :strict is not set' do
expect(subject.call('HTTP_ACCEPT_VERSION' => '').first).to eq(200)
expect(subject.call({}).first).to eq(200)
end
it 'succeeds if :strict is set to false' do
@options[:version_options][:strict] = false
expect(subject.call('HTTP_ACCEPT_VERSION' => '').first).to eq(200)
expect(subject.call({}).first).to eq(200)
end
context 'when :strict is set' do
before do
@options[:versions] = ['v1']
@options[:version_options][:strict] = true
end
it 'fails with 406 Not Acceptable if header is not set' do
expect do
subject.call({}).last
end.to throw_symbol(
:error,
status: 406,
headers: { 'X-Cascade' => 'pass' },
message: 'Accept-Version header must be set.'
)
end
it 'fails with 406 Not Acceptable if header is empty' do
expect do
subject.call('HTTP_ACCEPT_VERSION' => '').last
end.to throw_symbol(
:error,
status: 406,
headers: { 'X-Cascade' => 'pass' },
message: 'Accept-Version header must be set.'
)
end
it 'succeeds if proper header is set' do
expect(subject.call('HTTP_ACCEPT_VERSION' => 'v1').first).to eq(200)
end
end
context 'when :strict and :cascade=>false are set' do
before do
@options[:versions] = ['v1']
@options[:version_options][:strict] = true
@options[:version_options][:cascade] = false
end
it 'fails with 406 Not Acceptable if header is not set' do
expect do
subject.call({}).last
end.to throw_symbol(
:error,
status: 406,
headers: {},
message: 'Accept-Version header must be set.'
)
end
it 'fails with 406 Not Acceptable if header is empty' do
expect do
subject.call('HTTP_ACCEPT_VERSION' => '').last
end.to throw_symbol(
:error,
status: 406,
headers: {},
message: 'Accept-Version header must be set.'
)
end
it 'succeeds if proper header is set' do
expect(subject.call('HTTP_ACCEPT_VERSION' => 'v1').first).to eq(200)
end
end
end
grape-0.13.0/spec/grape/middleware/versioner/param_spec.rb 0000644 0000041 0000041 00000004415 12563420522 023546 0 ustar www-data www-data require 'spec_helper'
describe Grape::Middleware::Versioner::Param do
let(:app) { ->(env) { [200, env, env['api.version']] } }
subject { Grape::Middleware::Versioner::Param.new(app, @options || {}) }
it 'sets the API version based on the default param (apiver)' do
env = Rack::MockRequest.env_for('/awesome', params: { 'apiver' => 'v1' })
expect(subject.call(env)[1]['api.version']).to eq('v1')
end
it 'cuts (only) the version out of the params' do
env = Rack::MockRequest.env_for('/awesome', params: { 'apiver' => 'v1', 'other_param' => '5' })
env['rack.request.query_hash'] = Rack::Utils.parse_nested_query(env['QUERY_STRING'])
expect(subject.call(env)[1]['rack.request.query_hash']['apiver']).to be_nil
expect(subject.call(env)[1]['rack.request.query_hash']['other_param']).to eq('5')
end
it 'provides a nil version if no version is given' do
env = Rack::MockRequest.env_for('/')
expect(subject.call(env).last).to be_nil
end
context 'with specified parameter name' do
before { @options = { parameter: 'v' } }
it 'sets the API version based on the custom parameter name' do
env = Rack::MockRequest.env_for('/awesome', params: { 'v' => 'v1' })
expect(subject.call(env)[1]['api.version']).to eq('v1')
end
it 'does not set the API version based on the default param' do
env = Rack::MockRequest.env_for('/awesome', params: { 'apiver' => 'v1' })
expect(subject.call(env)[1]['api.version']).to be_nil
end
end
context 'with specified versions' do
before { @options = { versions: %w(v1 v2) } }
it 'throws an error if a non-allowed version is specified' do
env = Rack::MockRequest.env_for('/awesome', params: { 'apiver' => 'v3' })
expect(catch(:error) { subject.call(env) }[:status]).to eq(404)
end
it 'allows versions that have been specified' do
env = Rack::MockRequest.env_for('/awesome', params: { 'apiver' => 'v1' })
expect(subject.call(env)[1]['api.version']).to eq('v1')
end
end
it 'returns a 200 when no version is set (matches the first version found)' do
@options = {
versions: ['v1'],
version_options: { using: :header }
}
env = Rack::MockRequest.env_for('/awesome', params: {})
expect(subject.call(env).first).to eq(200)
end
end
grape-0.13.0/spec/grape/middleware/auth/ 0000755 0000041 0000041 00000000000 12563420522 020030 5 ustar www-data www-data grape-0.13.0/spec/grape/middleware/auth/strategies_spec.rb 0000644 0000041 0000041 00000004057 12563420522 023547 0 ustar www-data www-data require 'spec_helper'
require 'base64'
describe Grape::Middleware::Auth::Strategies do
context 'Basic Auth' do
def app
proc = ->(u, p) { u && p && u == p }
Rack::Builder.new do |b|
b.use Grape::Middleware::Error
b.use(Grape::Middleware::Auth::Base, type: :http_basic, proc: proc)
b.run ->(_env) { [200, {}, ['Hello there.']] }
end
end
it 'throws a 401 if no auth is given' do
@proc = -> { false }
get '/whatever'
expect(last_response.status).to eq(401)
end
it 'authenticates if given valid creds' do
get '/whatever', {}, 'HTTP_AUTHORIZATION' => encode_basic_auth('admin', 'admin')
expect(last_response.status).to eq(200)
end
it 'throws a 401 is wrong auth is given' do
get '/whatever', {}, 'HTTP_AUTHORIZATION' => encode_basic_auth('admin', 'wrong')
expect(last_response.status).to eq(401)
end
end
context 'Digest MD5 Auth' do
RSpec::Matchers.define :be_challenge do
match do |actual_response|
actual_response.status == 401 &&
actual_response['WWW-Authenticate'] =~ /^Digest / &&
actual_response.body.empty?
end
end
module StrategiesSpec
class Test < Grape::API
http_digest(realm: 'Test Api', opaque: 'secret') do |username|
{ 'foo' => 'bar' }[username]
end
get '/test' do
[{ hey: 'you' }, { there: 'bar' }, { foo: 'baz' }]
end
end
end
def app
StrategiesSpec::Test
end
it 'is a digest authentication challenge' do
get '/test'
expect(last_response).to be_challenge
end
it 'throws a 401 if no auth is given' do
get '/test'
expect(last_response.status).to eq(401)
end
it 'authenticates if given valid creds' do
digest_authorize 'foo', 'bar'
get '/test'
expect(last_response.status).to eq(200)
end
it 'throws a 401 if given invalid creds' do
digest_authorize 'bar', 'foo'
get '/test'
expect(last_response.status).to eq(401)
end
end
end
grape-0.13.0/spec/grape/middleware/auth/dsl_spec.rb 0000644 0000041 0000041 00000003241 12563420522 022151 0 ustar www-data www-data require 'spec_helper'
describe Grape::Middleware::Auth::DSL do
subject { Class.new(Grape::API) }
let(:block) { ->() {} }
let(:settings) do
{
opaque: 'secret',
proc: block,
realm: 'API Authorization',
type: :http_digest
}
end
describe '.auth' do
it 'stets auth parameters' do
expect(subject).to receive(:use).with(Grape::Middleware::Auth::Base, settings)
subject.auth :http_digest, realm: settings[:realm], opaque: settings[:opaque], &settings[:proc]
expect(subject.auth).to eq(settings)
end
it 'can be called multiple times' do
expect(subject).to receive(:use).with(Grape::Middleware::Auth::Base, settings)
expect(subject).to receive(:use).with(Grape::Middleware::Auth::Base, settings.merge(realm: 'super_secret'))
subject.auth :http_digest, realm: settings[:realm], opaque: settings[:opaque], &settings[:proc]
first_settings = subject.auth
subject.auth :http_digest, realm: 'super_secret', opaque: settings[:opaque], &settings[:proc]
expect(subject.auth).to eq(settings.merge(realm: 'super_secret'))
expect(subject.auth.object_id).not_to eq(first_settings.object_id)
end
end
describe '.http_basic' do
it 'stets auth parameters' do
subject.http_basic realm: 'my_realm', &settings[:proc]
expect(subject.auth).to eq(realm: 'my_realm', type: :http_basic, proc: block)
end
end
describe '.http_digest' do
it 'stets auth parameters' do
subject.http_digest realm: 'my_realm', opaque: 'my_opaque', &settings[:proc]
expect(subject.auth).to eq(realm: 'my_realm', type: :http_digest, proc: block, opaque: 'my_opaque')
end
end
end
grape-0.13.0/spec/grape/middleware/auth/base_spec.rb 0000644 0000041 0000041 00000001351 12563420522 022301 0 ustar www-data www-data require 'spec_helper'
require 'base64'
describe Grape::Middleware::Auth::Base do
subject do
Class.new(Grape::API) do
http_basic realm: 'my_realm' do |user, password|
user && password && user == password
end
get '/authorized' do
'DONE'
end
end
end
def app
subject
end
it 'authenticates if given valid creds' do
get '/authorized', {}, 'HTTP_AUTHORIZATION' => encode_basic_auth('admin', 'admin')
expect(last_response.status).to eq(200)
expect(last_response.body).to eq('DONE')
end
it 'throws a 401 is wrong auth is given' do
get '/authorized', {}, 'HTTP_AUTHORIZATION' => encode_basic_auth('admin', 'wrong')
expect(last_response.status).to eq(401)
end
end
grape-0.13.0/spec/grape/middleware/error_spec.rb 0000644 0000041 0000041 00000003246 12563420522 021564 0 ustar www-data www-data require 'spec_helper'
require 'grape-entity'
describe Grape::Middleware::Error do
module ErrorSpec
class ErrorEntity < Grape::Entity
expose :code
expose :static
def static
'static text'
end
end
end
class ErrApp
class << self
attr_accessor :error
attr_accessor :format
def call(_env)
throw :error, error
end
end
end
def app
opts = options
Rack::Builder.app do
use Spec::Support::EndpointFaker
use Grape::Middleware::Error, opts
run ErrApp
end
end
let(:options) { { default_message: 'Aww, hamburgers.' } }
it 'sets the status code appropriately' do
ErrApp.error = { status: 410 }
get '/'
expect(last_response.status).to eq(410)
end
it 'sets the error message appropriately' do
ErrApp.error = { message: 'Awesome stuff.' }
get '/'
expect(last_response.body).to eq('Awesome stuff.')
end
it 'defaults to a 500 status' do
ErrApp.error = {}
get '/'
expect(last_response.status).to eq(500)
end
it 'has a default message' do
ErrApp.error = {}
get '/'
expect(last_response.body).to eq('Aww, hamburgers.')
end
context 'with http code' do
let(:options) { { default_message: 'Aww, hamburgers.' } }
it 'adds the status code if wanted' do
ErrApp.error = { message: { code: 200 } }
get '/'
expect(last_response.body).to eq({ code: 200 }.to_json)
end
it 'presents an error message' do
ErrApp.error = { message: { code: 200, with: ErrorSpec::ErrorEntity } }
get '/'
expect(last_response.body).to eq({ code: 200, static: 'static text' }.to_json)
end
end
end
grape-0.13.0/spec/grape/middleware/globals_spec.rb 0000644 0000041 0000041 00000001602 12563420522 022050 0 ustar www-data www-data require 'spec_helper'
describe Grape::Middleware::Globals do
subject { Grape::Middleware::Globals.new(blank_app) }
before { allow(subject).to receive(:dup).and_return(subject) }
let(:blank_app) { ->(_env) { [200, {}, 'Hi there.'] } }
it 'calls through to the app' do
expect(subject.call({})).to eq([200, {}, 'Hi there.'])
end
context 'environment' do
it 'should set the grape.request environment' do
subject.call({})
expect(subject.env['grape.request']).to be_a(Grape::Request)
end
it 'should set the grape.request.headers environment' do
subject.call({})
expect(subject.env['grape.request.headers']).to be_a(Hash)
end
it 'should set the grape.request.params environment' do
subject.call('QUERY_STRING' => 'test=1', 'rack.input' => StringIO.new)
expect(subject.env['grape.request.params']).to be_a(Hash)
end
end
end
grape-0.13.0/spec/grape/middleware/versioner_spec.rb 0000644 0000041 0000041 00000001131 12563420522 022436 0 ustar www-data www-data require 'spec_helper'
describe Grape::Middleware::Versioner do
let(:klass) { Grape::Middleware::Versioner }
it 'recognizes :path' do
expect(klass.using(:path)).to eq(Grape::Middleware::Versioner::Path)
end
it 'recognizes :header' do
expect(klass.using(:header)).to eq(Grape::Middleware::Versioner::Header)
end
it 'recognizes :param' do
expect(klass.using(:param)).to eq(Grape::Middleware::Versioner::Param)
end
it 'recognizes :accept_version_header' do
expect(klass.using(:accept_version_header)).to eq(Grape::Middleware::Versioner::AcceptVersionHeader)
end
end
grape-0.13.0/spec/grape/middleware/base_spec.rb 0000644 0000041 0000041 00000004526 12563420522 021347 0 ustar www-data www-data require 'spec_helper'
describe Grape::Middleware::Base do
subject { Grape::Middleware::Base.new(blank_app) }
let(:blank_app) { ->(_) { [200, {}, 'Hi there.'] } }
before do
# Keep it one object for testing.
allow(subject).to receive(:dup).and_return(subject)
end
it 'has the app as an accessor' do
expect(subject.app).to eq(blank_app)
end
it 'calls through to the app' do
expect(subject.call({})).to eq([200, {}, 'Hi there.'])
end
context 'callbacks' do
it 'calls #before' do
expect(subject).to receive(:before)
end
it 'calls #after' do
expect(subject).to receive(:after)
end
after { subject.call!({}) }
end
it 'is able to access the response' do
subject.call({})
expect(subject.response).to be_kind_of(Rack::Response)
end
describe '#response' do
subject { Grape::Middleware::Base.new(response) }
context Array do
let(:response) { ->(_) { [204, { abc: 1 }, 'test'] } }
it 'status' do
subject.call({})
expect(subject.response.status).to eq(204)
end
it 'body' do
subject.call({})
expect(subject.response.body).to eq(['test'])
end
it 'header' do
subject.call({})
expect(subject.response.header).to have_key(:abc)
end
end
context Rack::Response do
let(:response) { ->(_) { Rack::Response.new('test', 204, abc: 1) } }
it 'status' do
subject.call({})
expect(subject.response.status).to eq(204)
end
it 'body' do
subject.call({})
expect(subject.response.body).to eq(['test'])
end
it 'header' do
subject.call({})
expect(subject.response.header).to have_key(:abc)
end
end
end
context 'options' do
it 'persists options passed at initialization' do
expect(Grape::Middleware::Base.new(blank_app, abc: true).options[:abc]).to be true
end
context 'defaults' do
class ExampleWare < Grape::Middleware::Base
def default_options
{ monkey: true }
end
end
it 'persists the default options' do
expect(ExampleWare.new(blank_app).options[:monkey]).to be true
end
it 'overrides default options when provided' do
expect(ExampleWare.new(blank_app, monkey: false).options[:monkey]).to be false
end
end
end
end
grape-0.13.0/spec/grape/middleware/formatter_spec.rb 0000644 0000041 0000041 00000022550 12563420522 022435 0 ustar www-data www-data require 'spec_helper'
describe Grape::Middleware::Formatter do
subject { Grape::Middleware::Formatter.new(app) }
before { allow(subject).to receive(:dup).and_return(subject) }
let(:app) { ->(_env) { [200, {}, [@body || { 'foo' => 'bar' }]] } }
context 'serialization' do
it 'looks at the bodies for possibly serializable data' do
@body = { 'abc' => 'def' }
_, _, bodies = *subject.call('PATH_INFO' => '/somewhere', 'HTTP_ACCEPT' => 'application/json')
bodies.each { |b| expect(b).to eq(MultiJson.dump(@body)) }
end
it 'calls #to_json since default format is json' do
@body = ['foo']
@body.instance_eval do
def to_json
"\"bar\""
end
end
subject.call('PATH_INFO' => '/somewhere', 'HTTP_ACCEPT' => 'application/json').to_a.last.each { |b| expect(b).to eq('"bar"') }
end
it 'calls #to_json if the content type is jsonapi' do
@body = { 'foos' => [{ 'bar' => 'baz' }] }
@body.instance_eval do
def to_json
"{\"foos\":[{\"bar\":\"baz\"}] }"
end
end
subject.call('PATH_INFO' => '/somewhere', 'HTTP_ACCEPT' => 'application/vnd.api+json').to_a.last.each { |b| expect(b).to eq('{"foos":[{"bar":"baz"}] }') }
end
it 'calls #to_xml if the content type is xml' do
@body = 'string'
@body.instance_eval do
def to_xml
''
end
end
subject.call('PATH_INFO' => '/somewhere.xml', 'HTTP_ACCEPT' => 'application/json').to_a.last.each { |b| expect(b).to eq('') }
end
end
context 'error handling' do
let(:formatter) { double(:formatter) }
before do
allow(Grape::Formatter::Base).to receive(:formatter_for) { formatter }
end
it 'rescues formatter-specific exceptions' do
allow(formatter).to receive(:call) { fail Grape::Exceptions::InvalidFormatter.new(String, 'xml') }
expect do
catch(:error) { subject.call('PATH_INFO' => '/somewhere.xml', 'HTTP_ACCEPT' => 'application/json') }
end.to_not raise_error
end
it 'does not rescue other exceptions' do
allow(formatter).to receive(:call) { fail StandardError }
expect do
catch(:error) { subject.call('PATH_INFO' => '/somewhere.xml', 'HTTP_ACCEPT' => 'application/json') }
end.to raise_error
end
end
context 'detection' do
it 'uses the xml extension if one is provided' do
subject.call('PATH_INFO' => '/info.xml')
expect(subject.env['api.format']).to eq(:xml)
end
it 'uses the json extension if one is provided' do
subject.call('PATH_INFO' => '/info.json')
expect(subject.env['api.format']).to eq(:json)
end
it 'uses the format parameter if one is provided' do
subject.call('PATH_INFO' => '/info', 'QUERY_STRING' => 'format=json')
expect(subject.env['api.format']).to eq(:json)
subject.call('PATH_INFO' => '/info', 'QUERY_STRING' => 'format=xml')
expect(subject.env['api.format']).to eq(:xml)
end
it 'uses the default format if none is provided' do
subject.call('PATH_INFO' => '/info')
expect(subject.env['api.format']).to eq(:txt)
end
it 'uses the requested format if provided in headers' do
subject.call('PATH_INFO' => '/info', 'HTTP_ACCEPT' => 'application/json')
expect(subject.env['api.format']).to eq(:json)
end
it 'uses the file extension format if provided before headers' do
subject.call('PATH_INFO' => '/info.txt', 'HTTP_ACCEPT' => 'application/json')
expect(subject.env['api.format']).to eq(:txt)
end
end
context 'accept header detection' do
it 'detects from the Accept header' do
subject.call('PATH_INFO' => '/info', 'HTTP_ACCEPT' => 'application/xml')
expect(subject.env['api.format']).to eq(:xml)
end
it 'uses quality rankings to determine formats' do
subject.call('PATH_INFO' => '/info', 'HTTP_ACCEPT' => 'application/json; q=0.3,application/xml; q=1.0')
expect(subject.env['api.format']).to eq(:xml)
subject.call('PATH_INFO' => '/info', 'HTTP_ACCEPT' => 'application/json; q=1.0,application/xml; q=0.3')
expect(subject.env['api.format']).to eq(:json)
end
it 'handles quality rankings mixed with nothing' do
subject.call('PATH_INFO' => '/info', 'HTTP_ACCEPT' => 'application/json,application/xml; q=1.0')
expect(subject.env['api.format']).to eq(:xml)
end
it 'parses headers with other attributes' do
subject.call('PATH_INFO' => '/info', 'HTTP_ACCEPT' => 'application/json; abc=2.3; q=1.0,application/xml; q=0.7')
expect(subject.env['api.format']).to eq(:json)
end
it 'parses headers with vendor and api version' do
subject.call('PATH_INFO' => '/info', 'HTTP_ACCEPT' => 'application/vnd.test-v1+xml')
expect(subject.env['api.format']).to eq(:xml)
end
it 'parses headers with symbols as hash keys' do
subject.call('PATH_INFO' => '/info', 'http_accept' => 'application/xml', system_time: '091293')
expect(subject.env[:system_time]).to eq('091293')
end
end
context 'content-type' do
it 'is set for json' do
_, headers, = subject.call('PATH_INFO' => '/info.json')
expect(headers['Content-type']).to eq('application/json')
end
it 'is set for xml' do
_, headers, = subject.call('PATH_INFO' => '/info.xml')
expect(headers['Content-type']).to eq('application/xml')
end
it 'is set for txt' do
_, headers, = subject.call('PATH_INFO' => '/info.txt')
expect(headers['Content-type']).to eq('text/plain')
end
it 'is set for custom' do
subject.options[:content_types] = {}
subject.options[:content_types][:custom] = 'application/x-custom'
_, headers, = subject.call('PATH_INFO' => '/info.custom')
expect(headers['Content-type']).to eq('application/x-custom')
end
end
context 'format' do
it 'uses custom formatter' do
subject.options[:content_types] = {}
subject.options[:content_types][:custom] = "don't care"
subject.options[:formatters][:custom] = ->(_obj, _env) { 'CUSTOM FORMAT' }
_, _, body = subject.call('PATH_INFO' => '/info.custom')
expect(body.body).to eq(['CUSTOM FORMAT'])
end
it 'uses default json formatter' do
@body = ['blah']
_, _, body = subject.call('PATH_INFO' => '/info.json')
expect(body.body).to eq(['["blah"]'])
end
it 'uses custom json formatter' do
subject.options[:formatters][:json] = ->(_obj, _env) { 'CUSTOM JSON FORMAT' }
_, _, body = subject.call('PATH_INFO' => '/info.json')
expect(body.body).to eq(['CUSTOM JSON FORMAT'])
end
end
context 'input' do
%w(POST PATCH PUT DELETE).each do |method|
['application/json', 'application/json; charset=utf-8'].each do |content_type|
context content_type do
it 'parses the body from #{method} and copies values into rack.request.form_hash' do
io = StringIO.new('{"is_boolean":true,"string":"thing"}')
subject.call(
'PATH_INFO' => '/info',
'REQUEST_METHOD' => method,
'CONTENT_TYPE' => content_type,
'rack.input' => io,
'CONTENT_LENGTH' => io.length
)
expect(subject.env['rack.request.form_hash']['is_boolean']).to be true
expect(subject.env['rack.request.form_hash']['string']).to eq('thing')
end
end
end
it "parses the chunked body from #{method} and copies values into rack.request.from_hash" do
io = StringIO.new('{"is_boolean":true,"string":"thing"}')
subject.call(
'PATH_INFO' => '/infol',
'REQUEST_METHOD' => method,
'CONTENT_TYPE' => 'application/json',
'rack.input' => io,
'HTTP_TRANSFER_ENCODING' => 'chunked'
)
expect(subject.env['rack.request.form_hash']['is_boolean']).to be true
expect(subject.env['rack.request.form_hash']['string']).to eq('thing')
end
it 'rewinds IO' do
io = StringIO.new('{"is_boolean":true,"string":"thing"}')
io.read
subject.call(
'PATH_INFO' => '/infol',
'REQUEST_METHOD' => method,
'CONTENT_TYPE' => 'application/json',
'rack.input' => io,
'HTTP_TRANSFER_ENCODING' => 'chunked'
)
expect(subject.env['rack.request.form_hash']['is_boolean']).to be true
expect(subject.env['rack.request.form_hash']['string']).to eq('thing')
end
it 'parses the body from an xml #{method} and copies values into rack.request.from_hash' do
io = StringIO.new('Test')
subject.call(
'PATH_INFO' => '/info.xml',
'REQUEST_METHOD' => method,
'CONTENT_TYPE' => 'application/xml',
'rack.input' => io,
'CONTENT_LENGTH' => io.length
)
expect(subject.env['rack.request.form_hash']['thing']['name']).to eq('Test')
end
[Rack::Request::FORM_DATA_MEDIA_TYPES, Rack::Request::PARSEABLE_DATA_MEDIA_TYPES].flatten.each do |content_type|
it "ignores #{content_type}" do
io = StringIO.new('name=Other+Test+Thing')
subject.call(
'PATH_INFO' => '/info',
'REQUEST_METHOD' => method,
'CONTENT_TYPE' => content_type,
'rack.input' => io,
'CONTENT_LENGTH' => io.length
)
expect(subject.env['rack.request.form_hash']).to be_nil
end
end
end
end
end
grape-0.13.0/spec/grape/exceptions/ 0000755 0000041 0000041 00000000000 12563420522 017133 5 ustar www-data www-data grape-0.13.0/spec/grape/exceptions/invalid_accept_header_spec.rb 0000644 0000041 0000041 00000025722 12563420522 024757 0 ustar www-data www-data require 'spec_helper'
describe Grape::Exceptions::InvalidAcceptHeader do
shared_examples_for 'a valid request' do
it 'does return with status 200' do
expect(last_response.status).to eq 200
end
it 'does return the expected result' do
expect(last_response.body).to eq('beer received')
end
end
shared_examples_for 'a cascaded request' do
it 'does not find a matching route' do
expect(last_response.status).to eq 404
end
end
shared_examples_for 'a not-cascaded request' do
it 'does not include the X-Cascade=pass header' do
expect(last_response.headers['X-Cascade']).to be_nil
end
it 'does not accept the request' do
expect(last_response.status).to eq 406
end
end
shared_examples_for 'a rescued request' do
it 'does not include the X-Cascade=pass header' do
expect(last_response.headers['X-Cascade']).to be_nil
end
it 'does show rescue handler processing' do
expect(last_response.status).to eq 400
expect(last_response.body).to eq('message was processed')
end
end
context 'API with cascade=false and rescue_from :all handler' do
subject { Class.new(Grape::API) }
before do
subject.version 'v99', using: :header, vendor: 'vendorname', format: :json, cascade: false
subject.rescue_from :all do |e|
rack_response 'message was processed', 400, e[:headers]
end
subject.get '/beer' do
'beer received'
end
end
def app
subject
end
context 'that received a request with correct vendor and version' do
before { get '/beer', {}, 'HTTP_ACCEPT' => 'application/vnd.vendorname-v99' }
it_should_behave_like 'a valid request'
end
context 'that receives' do
context 'an invalid version in the request' do
before do
get '/beer', {}, 'HTTP_ACCEPT' => 'application/vnd.vendorname-v77',
'CONTENT_TYPE' => 'application/json'
end
it_should_behave_like 'a rescued request'
end
context 'an invalid vendor in the request' do
before do
get '/beer', {}, 'HTTP_ACCEPT' => 'application/vnd.invalidvendor-v99',
'CONTENT_TYPE' => 'application/json'
end
it_should_behave_like 'a rescued request'
end
end
end
context 'API with cascade=false and without a rescue handler' do
subject { Class.new(Grape::API) }
before do
subject.version 'v99', using: :header, vendor: 'vendorname', format: :json, cascade: false
subject.get '/beer' do
'beer received'
end
end
def app
subject
end
context 'that received a request with correct vendor and version' do
before { get '/beer', {}, 'HTTP_ACCEPT' => 'application/vnd.vendorname-v99' }
it_should_behave_like 'a valid request'
end
context 'that receives' do
context 'an invalid version in the request' do
before { get '/beer', {}, 'HTTP_ACCEPT' => 'application/vnd.vendorname-v77' }
it_should_behave_like 'a not-cascaded request'
end
context 'an invalid vendor in the request' do
before { get '/beer', {}, 'HTTP_ACCEPT' => 'application/vnd.invalidvendor-v99' }
it_should_behave_like 'a not-cascaded request'
end
end
end
context 'API with cascade=false and with rescue_from :all handler and http_codes' do
subject { Class.new(Grape::API) }
before do
subject.version 'v99', using: :header, vendor: 'vendorname', format: :json, cascade: false
subject.rescue_from :all do |e|
rack_response 'message was processed', 400, e[:headers]
end
subject.desc 'Get beer' do
failure [[400, 'Bad Request'], [401, 'Unauthorized'], [403, 'Forbidden'],
[404, 'Resource not found'], [406, 'API vendor or version not found'],
[500, 'Internal processing error']]
end
subject.get '/beer' do
'beer received'
end
end
def app
subject
end
context 'that received a request with correct vendor and version' do
before { get '/beer', {}, 'HTTP_ACCEPT' => 'application/vnd.vendorname-v99' }
it_should_behave_like 'a valid request'
end
context 'that receives' do
context 'an invalid version in the request' do
before do
get '/beer', {}, 'HTTP_ACCEPT' => 'application/vnd.vendorname-v77',
'CONTENT_TYPE' => 'application/json'
end
it_should_behave_like 'a rescued request'
end
context 'an invalid vendor in the request' do
before do
get '/beer', {}, 'HTTP_ACCEPT' => 'application/vnd.invalidvendor-v99',
'CONTENT_TYPE' => 'application/json'
end
it_should_behave_like 'a rescued request'
end
end
end
context 'API with cascade=false, http_codes but without a rescue handler' do
subject { Class.new(Grape::API) }
before do
subject.version 'v99', using: :header, vendor: 'vendorname', format: :json, cascade: false
subject.desc 'Get beer' do
failure [[400, 'Bad Request'], [401, 'Unauthorized'], [403, 'Forbidden'],
[404, 'Resource not found'], [406, 'API vendor or version not found'],
[500, 'Internal processing error']]
end
subject.get '/beer' do
'beer received'
end
end
def app
subject
end
context 'that received a request with correct vendor and version' do
before { get '/beer', {}, 'HTTP_ACCEPT' => 'application/vnd.vendorname-v99' }
it_should_behave_like 'a valid request'
end
context 'that receives' do
context 'an invalid version in the request' do
before { get '/beer', {}, 'HTTP_ACCEPT' => 'application/vnd.vendorname-v77' }
it_should_behave_like 'a not-cascaded request'
end
context 'an invalid vendor in the request' do
before { get '/beer', {}, 'HTTP_ACCEPT' => 'application/vnd.invalidvendor-v99' }
it_should_behave_like 'a not-cascaded request'
end
end
end
context 'API with cascade=true and rescue_from :all handler' do
subject { Class.new(Grape::API) }
before do
subject.version 'v99', using: :header, vendor: 'vendorname', format: :json, cascade: true
subject.rescue_from :all do |e|
rack_response 'message was processed', 400, e[:headers]
end
subject.get '/beer' do
'beer received'
end
end
def app
subject
end
context 'that received a request with correct vendor and version' do
before { get '/beer', {}, 'HTTP_ACCEPT' => 'application/vnd.vendorname-v99' }
it_should_behave_like 'a valid request'
end
context 'that receives' do
context 'an invalid version in the request' do
before do
get '/beer', {}, 'HTTP_ACCEPT' => 'application/vnd.vendorname-v77',
'CONTENT_TYPE' => 'application/json'
end
it_should_behave_like 'a cascaded request'
end
context 'an invalid vendor in the request' do
before do
get '/beer', {}, 'HTTP_ACCEPT' => 'application/vnd.invalidvendor-v99',
'CONTENT_TYPE' => 'application/json'
end
it_should_behave_like 'a cascaded request'
end
end
end
context 'API with cascade=true and without a rescue handler' do
subject { Class.new(Grape::API) }
before do
subject.version 'v99', using: :header, vendor: 'vendorname', format: :json, cascade: true
subject.get '/beer' do
'beer received'
end
end
def app
subject
end
context 'that received a request with correct vendor and version' do
before { get '/beer', {}, 'HTTP_ACCEPT' => 'application/vnd.vendorname-v99' }
it_should_behave_like 'a valid request'
end
context 'that receives' do
context 'an invalid version in the request' do
before { get '/beer', {}, 'HTTP_ACCEPT' => 'application/vnd.vendorname-v77' }
it_should_behave_like 'a cascaded request'
end
context 'an invalid vendor in the request' do
before { get '/beer', {}, 'HTTP_ACCEPT' => 'application/vnd.invalidvendor-v99' }
it_should_behave_like 'a cascaded request'
end
end
end
context 'API with cascade=true and with rescue_from :all handler and http_codes' do
subject { Class.new(Grape::API) }
before do
subject.version 'v99', using: :header, vendor: 'vendorname', format: :json, cascade: true
subject.rescue_from :all do |e|
rack_response 'message was processed', 400, e[:headers]
end
subject.desc 'Get beer' do
failure [[400, 'Bad Request'], [401, 'Unauthorized'], [403, 'Forbidden'],
[404, 'Resource not found'], [406, 'API vendor or version not found'],
[500, 'Internal processing error']]
end
subject.get '/beer' do
'beer received'
end
end
def app
subject
end
context 'that received a request with correct vendor and version' do
before { get '/beer', {}, 'HTTP_ACCEPT' => 'application/vnd.vendorname-v99' }
it_should_behave_like 'a valid request'
end
context 'that receives' do
context 'an invalid version in the request' do
before do
get '/beer', {}, 'HTTP_ACCEPT' => 'application/vnd.vendorname-v77',
'CONTENT_TYPE' => 'application/json'
end
it_should_behave_like 'a cascaded request'
end
context 'an invalid vendor in the request' do
before do
get '/beer', {}, 'HTTP_ACCEPT' => 'application/vnd.invalidvendor-v99',
'CONTENT_TYPE' => 'application/json'
end
it_should_behave_like 'a cascaded request'
end
end
end
context 'API with cascade=true, http_codes but without a rescue handler' do
subject { Class.new(Grape::API) }
before do
subject.version 'v99', using: :header, vendor: 'vendorname', format: :json, cascade: true
subject.desc 'Get beer' do
failure [[400, 'Bad Request'], [401, 'Unauthorized'], [403, 'Forbidden'],
[404, 'Resource not found'], [406, 'API vendor or version not found'],
[500, 'Internal processing error']]
end
subject.get '/beer' do
'beer received'
end
end
def app
subject
end
context 'that received a request with correct vendor and version' do
before { get '/beer', {}, 'HTTP_ACCEPT' => 'application/vnd.vendorname-v99' }
it_should_behave_like 'a valid request'
end
context 'that receives' do
context 'an invalid version in the request' do
before { get '/beer', {}, 'HTTP_ACCEPT' => 'application/vnd.vendorname-v77' }
it_should_behave_like 'a cascaded request'
end
context 'an invalid vendor in the request' do
before { get '/beer', {}, 'HTTP_ACCEPT' => 'application/vnd.invalidvendor-v99' }
it_should_behave_like 'a cascaded request'
end
end
end
end
grape-0.13.0/spec/grape/exceptions/invalid_versioner_option_spec.rb 0000644 0000041 0000041 00000000533 12563420522 025605 0 ustar www-data www-data # encoding: utf-8
require 'spec_helper'
describe Grape::Exceptions::InvalidVersionerOption do
describe '#message' do
let(:error) do
described_class.new('headers')
end
it 'contains the problem in the message' do
expect(error.message).to include(
'Unknown :using for versioner: headers'
)
end
end
end
grape-0.13.0/spec/grape/exceptions/invalid_formatter_spec.rb 0000644 0000041 0000041 00000000520 12563420522 024200 0 ustar www-data www-data # encoding: utf-8
require 'spec_helper'
describe Grape::Exceptions::InvalidFormatter do
describe '#message' do
let(:error) do
described_class.new(String, 'xml')
end
it 'contains the problem in the message' do
expect(error.message).to include(
'cannot convert String to xml'
)
end
end
end
grape-0.13.0/spec/grape/exceptions/unknown_validator_spec.rb 0000644 0000041 0000041 00000000506 12563420522 024237 0 ustar www-data www-data # encoding: utf-8
require 'spec_helper'
describe Grape::Exceptions::UnknownValidator do
describe '#message' do
let(:error) do
described_class.new('gt_10')
end
it 'contains the problem in the message' do
expect(error.message).to include(
'unknown validator: gt_10'
)
end
end
end
grape-0.13.0/spec/grape/exceptions/body_parse_errors_spec.rb 0000644 0000041 0000041 00000006160 12563420522 024220 0 ustar www-data www-data require 'spec_helper'
describe Grape::Exceptions::ValidationErrors do
context 'api with rescue_from :all handler' do
subject { Class.new(Grape::API) }
before do
subject.rescue_from :all do |_e|
rack_response 'message was processed', 400
end
subject.params do
requires :beer
end
subject.post '/beer' do
'beer received'
end
end
def app
subject
end
context 'with content_type json' do
it 'can recover from failed body parsing' do
post '/beer', 'test', 'CONTENT_TYPE' => 'application/json'
expect(last_response.status).to eq 400
expect(last_response.body).to eq('message was processed')
end
end
context 'with content_type xml' do
it 'can recover from failed body parsing' do
post '/beer', 'test', 'CONTENT_TYPE' => 'application/xml'
expect(last_response.status).to eq 400
expect(last_response.body).to eq('message was processed')
end
end
context 'with content_type text' do
it 'can recover from failed body parsing' do
post '/beer', 'test', 'CONTENT_TYPE' => 'text/plain'
expect(last_response.status).to eq 400
expect(last_response.body).to eq('message was processed')
end
end
context 'with no specific content_type' do
it 'can recover from failed body parsing' do
post '/beer', 'test', {}
expect(last_response.status).to eq 400
expect(last_response.body).to eq('message was processed')
end
end
end
context 'api without a rescue handler' do
subject { Class.new(Grape::API) }
before do
subject.params do
requires :beer
end
subject.post '/beer' do
'beer received'
end
end
def app
subject
end
context 'and with content_type json' do
it 'can recover from failed body parsing' do
post '/beer', 'test', 'CONTENT_TYPE' => 'application/json'
expect(last_response.status).to eq 400
expect(last_response.body).to include('message body does not match declared format')
expect(last_response.body).to include('application/json')
end
end
context 'with content_type xml' do
it 'can recover from failed body parsing' do
post '/beer', 'test', 'CONTENT_TYPE' => 'application/xml'
expect(last_response.status).to eq 400
expect(last_response.body).to include('message body does not match declared format')
expect(last_response.body).to include('application/xml')
end
end
context 'with content_type text' do
it 'can recover from failed body parsing' do
post '/beer', 'test', 'CONTENT_TYPE' => 'text/plain'
expect(last_response.status).to eq 400
expect(last_response.body).to eq('beer is missing')
end
end
context 'and with no specific content_type' do
it 'can recover from failed body parsing' do
post '/beer', 'test', {}
expect(last_response.status).to eq 400
# plain response with text/html
expect(last_response.body).to eq('beer is missing')
end
end
end
end
grape-0.13.0/spec/grape/exceptions/missing_mime_type_spec.rb 0000644 0000041 0000041 00000000725 12563420522 024217 0 ustar www-data www-data require 'spec_helper'
describe Grape::Exceptions::MissingMimeType do
describe '#message' do
let(:error) do
described_class.new('new_json')
end
it 'contains the problem in the message' do
expect(error.message).to include 'missing mime type for new_json'
end
it 'contains the resolution in the message' do
expect(error.message).to include "or add your own with content_type :new_json, 'application/new_json' "
end
end
end
grape-0.13.0/spec/grape/exceptions/unknown_options_spec.rb 0000644 0000041 0000041 00000000476 12563420522 023753 0 ustar www-data www-data # encoding: utf-8
require 'spec_helper'
describe Grape::Exceptions::UnknownOptions do
describe '#message' do
let(:error) do
described_class.new([:a, :b])
end
it 'contains the problem in the message' do
expect(error.message).to include(
'unknown options: '
)
end
end
end
grape-0.13.0/spec/grape/exceptions/missing_option_spec.rb 0000644 0000041 0000041 00000000510 12563420522 023527 0 ustar www-data www-data # encoding: utf-8
require 'spec_helper'
describe Grape::Exceptions::MissingOption do
describe '#message' do
let(:error) do
described_class.new(:path)
end
it 'contains the problem in the message' do
expect(error.message).to include(
'You must specify :path options.'
)
end
end
end
grape-0.13.0/spec/grape/exceptions/validation_errors_spec.rb 0000644 0000041 0000041 00000004252 12563420522 024223 0 ustar www-data www-data require 'spec_helper'
require 'ostruct'
describe Grape::Exceptions::ValidationErrors do
let(:validation_message) { 'FooBar is invalid' }
let(:validation_error) { OpenStruct.new(params: [validation_message]) }
context 'initialize' do
let(:headers) {
{
'A-Header-Key' => 'A-Header-Value'
}
}
subject do
described_class.new(errors: [validation_error], headers: headers)
end
it 'should assign headers through base class' do
expect(subject.headers).to eq(headers)
end
end
context 'message' do
context 'is not repeated' do
let(:error) do
described_class.new(errors: [validation_error, validation_error])
end
subject(:message) { error.message.split(',').map(&:strip) }
it { expect(message).to include validation_message }
it { expect(message.size).to eq 1 }
end
end
describe '#full_messages' do
context 'with errors' do
let(:validation_error_1) { Grape::Exceptions::Validation.new(params: ['id'], message_key: 'presence') }
let(:validation_error_2) { Grape::Exceptions::Validation.new(params: ['name'], message_key: 'presence') }
subject { described_class.new(errors: [validation_error_1, validation_error_2]).full_messages }
it 'returns an array with each errors full message' do
expect(subject).to contain_exactly('id is missing', 'name is missing')
end
end
end
context 'api' do
subject { Class.new(Grape::API) }
def app
subject
end
it 'can return structured json with separate fields' do
subject.format :json
subject.rescue_from Grape::Exceptions::ValidationErrors do |e|
error!(e, 400)
end
subject.params do
optional :beer
optional :wine
optional :juice
exactly_one_of :beer, :wine, :juice
end
subject.get '/exactly_one_of' do
'exactly_one_of works!'
end
get '/exactly_one_of', beer: 'string', wine: 'anotherstring'
expect(last_response.status).to eq(400)
expect(JSON.parse(last_response.body)).to eq([
'params' => %w(beer wine),
'messages' => ['are mutually exclusive']
])
end
end
end
grape-0.13.0/spec/grape/api/ 0000755 0000041 0000041 00000000000 12563420522 015523 5 ustar www-data www-data grape-0.13.0/spec/grape/api/nested_helpers_spec.rb 0000644 0000041 0000041 00000001577 12563420522 022100 0 ustar www-data www-data require 'spec_helper'
describe Grape::API::Helpers do
subject do
module HelperMethods
extend Grape::API::Helpers
def current_user
@current_user ||= params[:current_user]
end
end
class Nested < Grape::API
resource :level1 do
helpers HelperMethods
get do
current_user
end
resource :level2 do
get do
current_user
end
end
end
end
class Main < Grape::API
mount Nested
end
Main
end
def app
subject
end
it 'can access helpers from a mounted resource' do
get '/level1', current_user: 'hello'
expect(last_response.body).to eq('hello')
end
it 'can access helpers from a mounted resource in a nested resource' do
get '/level1/level2', current_user: 'world'
expect(last_response.body).to eq('world')
end
end
grape-0.13.0/spec/grape/api/shared_helpers_spec.rb 0000644 0000041 0000041 00000001201 12563420522 022044 0 ustar www-data www-data require 'spec_helper'
describe Grape::API::Helpers do
module SharedParams
extend Grape::API::Helpers
params :pagination do
optional :page, type: Integer
optional :size, type: Integer
end
end
subject do
Class.new(Grape::API) do
helpers SharedParams
format :json
params do
use :pagination
end
get do
declared(params, include_missing: true)
end
end
end
def app
subject
end
it 'defines parameters' do
get '/'
expect(last_response.status).to eq 200
expect(last_response.body).to eq({ page: nil, size: nil }.to_json)
end
end
grape-0.13.0/spec/grape/api/custom_validations_spec.rb 0000644 0000041 0000041 00000002337 12563420522 022776 0 ustar www-data www-data require 'spec_helper'
describe Grape::Validations do
before do
class DefaultLength < Grape::Validations::Base
def validate_param!(attr_name, params)
@option = params[:max].to_i if params.key?(:max)
unless params[attr_name].length <= @option
fail Grape::Exceptions::Validation, params: [@scope.full_name(attr_name)], message: "must be at the most #{@option} characters long"
end
end
end
end
subject do
Class.new(Grape::API) do
params do
requires :text, default_length: 140
end
get do
'bacon'
end
end
end
def app
subject
end
context 'using a custom length validator' do
it 'under 140 characters' do
get '/', text: 'abc'
expect(last_response.status).to eq 200
expect(last_response.body).to eq 'bacon'
end
it 'over 140 characters' do
get '/', text: 'a' * 141
expect(last_response.status).to eq 400
expect(last_response.body).to eq 'text must be at the most 140 characters long'
end
it 'specified in the query string' do
get '/', text: 'a' * 141, max: 141
expect(last_response.status).to eq 200
expect(last_response.body).to eq 'bacon'
end
end
end
grape-0.13.0/spec/grape/api/deeply_included_options_spec.rb 0000644 0000041 0000041 00000001773 12563420522 023776 0 ustar www-data www-data require 'spec_helper'
module API
module Defaults
extend ActiveSupport::Concern
included do
format :json
end
end
module Admin
module Defaults
extend ActiveSupport::Concern
include API::Defaults
end
class Users < Grape::API
include API::Admin::Defaults
resource :users do
get do
status 200
end
end
end
end
end
class Main < Grape::API
mount API::Admin::Users
end
describe Grape::API do
subject { Main }
def app
subject
end
it 'works for unspecified format' do
get '/users'
expect(last_response.status).to eql 200
expect(last_response.content_type).to eql 'application/json'
end
it 'works for specified format' do
get '/users.json'
expect(last_response.status).to eql 200
expect(last_response.content_type).to eql 'application/json'
end
it "doesn't work for format different than specified" do
get '/users.txt'
expect(last_response.status).to eql 404
end
end
grape-0.13.0/spec/grape/entity_spec.rb 0000644 0000041 0000041 00000021507 12563420522 017632 0 ustar www-data www-data require 'spec_helper'
require 'grape_entity'
describe Grape::Entity do
subject { Class.new(Grape::API) }
def app
subject
end
describe '#present' do
it 'sets the object as the body if no options are provided' do
inner_body = nil
subject.get '/example' do
present(abc: 'def')
inner_body = body
end
get '/example'
expect(inner_body).to eql(abc: 'def')
end
it 'calls through to the provided entity class if one is given' do
entity_mock = Object.new
allow(entity_mock).to receive(:represent)
subject.get '/example' do
present Object.new, with: entity_mock
end
get '/example'
end
it 'pulls a representation from the class options if it exists' do
entity = Class.new(Grape::Entity)
allow(entity).to receive(:represent).and_return('Hiya')
subject.represent Object, with: entity
subject.get '/example' do
present Object.new
end
get '/example'
expect(last_response.body).to eq('Hiya')
end
it 'pulls a representation from the class options if the presented object is a collection of objects' do
entity = Class.new(Grape::Entity)
allow(entity).to receive(:represent).and_return('Hiya')
class TestObject
end
class FakeCollection
def first
TestObject.new
end
end
subject.represent TestObject, with: entity
subject.get '/example' do
present [TestObject.new]
end
subject.get '/example2' do
present FakeCollection.new
end
get '/example'
expect(last_response.body).to eq('Hiya')
get '/example2'
expect(last_response.body).to eq('Hiya')
end
it 'pulls a representation from the class ancestor if it exists' do
entity = Class.new(Grape::Entity)
allow(entity).to receive(:represent).and_return('Hiya')
subclass = Class.new(Object)
subject.represent Object, with: entity
subject.get '/example' do
present subclass.new
end
get '/example'
expect(last_response.body).to eq('Hiya')
end
it 'automatically uses Klass::Entity if that exists' do
some_model = Class.new
entity = Class.new(Grape::Entity)
allow(entity).to receive(:represent).and_return('Auto-detect!')
some_model.const_set :Entity, entity
subject.get '/example' do
present some_model.new
end
get '/example'
expect(last_response.body).to eq('Auto-detect!')
end
it 'automatically uses Klass::Entity based on the first object in the collection being presented' do
some_model = Class.new
entity = Class.new(Grape::Entity)
allow(entity).to receive(:represent).and_return('Auto-detect!')
some_model.const_set :Entity, entity
subject.get '/example' do
present [some_model.new]
end
get '/example'
expect(last_response.body).to eq('Auto-detect!')
end
it 'does not run autodetection for Entity when explicitely provided' do
entity = Class.new(Grape::Entity)
some_array = []
subject.get '/example' do
present some_array, with: entity
end
expect(some_array).not_to receive(:first)
get '/example'
end
it 'does not use #first method on ActiveRecord::Relation to prevent needless sql query' do
entity = Class.new(Grape::Entity)
some_relation = Class.new
some_model = Class.new
allow(entity).to receive(:represent).and_return('Auto-detect!')
allow(some_relation).to receive(:first)
allow(some_relation).to receive(:klass).and_return(some_model)
some_model.const_set :Entity, entity
subject.get '/example' do
present some_relation
end
expect(some_relation).not_to receive(:first)
get '/example'
expect(last_response.body).to eq('Auto-detect!')
end
it 'autodetection does not use Entity if it is not a presenter' do
some_model = Class.new
entity = Class.new
some_model.class.const_set :Entity, entity
subject.get '/example' do
present some_model
end
get '/example'
expect(entity).not_to receive(:represent)
end
it 'adds a root key to the output if one is given' do
inner_body = nil
subject.get '/example' do
present({ abc: 'def' }, root: :root)
inner_body = body
end
get '/example'
expect(inner_body).to eql(root: { abc: 'def' })
end
[:json, :serializable_hash].each do |format|
it 'presents with #{format}' do
entity = Class.new(Grape::Entity)
entity.root 'examples', 'example'
entity.expose :id
subject.format format
subject.get '/example' do
c = Class.new do
attr_reader :id
def initialize(id)
@id = id
end
end
present c.new(1), with: entity
end
get '/example'
expect(last_response.status).to eq(200)
expect(last_response.body).to eq('{"example":{"id":1}}')
end
it 'presents with #{format} collection' do
entity = Class.new(Grape::Entity)
entity.root 'examples', 'example'
entity.expose :id
subject.format format
subject.get '/examples' do
c = Class.new do
attr_reader :id
def initialize(id)
@id = id
end
end
examples = [c.new(1), c.new(2)]
present examples, with: entity
end
get '/examples'
expect(last_response.status).to eq(200)
expect(last_response.body).to eq('{"examples":[{"id":1},{"id":2}]}')
end
end
it 'presents with xml' do
entity = Class.new(Grape::Entity)
entity.root 'examples', 'example'
entity.expose :name
subject.format :xml
subject.get '/example' do
c = Class.new do
attr_reader :name
def initialize(args)
@name = args[:name] || 'no name set'
end
end
present c.new(name: 'johnnyiller'), with: entity
end
get '/example'
expect(last_response.status).to eq(200)
expect(last_response.headers['Content-type']).to eq('application/xml')
expect(last_response.body).to eq <<-XML
johnnyiller
XML
end
it 'presents with json' do
entity = Class.new(Grape::Entity)
entity.root 'examples', 'example'
entity.expose :name
subject.format :json
subject.get '/example' do
c = Class.new do
attr_reader :name
def initialize(args)
@name = args[:name] || 'no name set'
end
end
present c.new(name: 'johnnyiller'), with: entity
end
get '/example'
expect(last_response.status).to eq(200)
expect(last_response.headers['Content-type']).to eq('application/json')
expect(last_response.body).to eq('{"example":{"name":"johnnyiller"}}')
end
it 'presents with jsonp utilising Rack::JSONP' do
require 'rack/contrib'
# Include JSONP middleware
subject.use Rack::JSONP
entity = Class.new(Grape::Entity)
entity.root 'examples', 'example'
entity.expose :name
# Rack::JSONP expects a standard JSON response in UTF-8 format
subject.format :json
subject.formatter :json, lambda { |object, _|
object.to_json.encode('utf-8')
}
subject.get '/example' do
c = Class.new do
attr_reader :name
def initialize(args)
@name = args[:name] || 'no name set'
end
end
present c.new(name: 'johnnyiller'), with: entity
end
get '/example?callback=abcDef'
expect(last_response.status).to eq(200)
expect(last_response.headers['Content-type']).to eq('application/javascript')
expect(last_response.body).to include 'abcDef({"example":{"name":"johnnyiller"}})'
end
context 'present with multiple entities' do
it 'present with multiple entities using optional symbol' do
user = Class.new do
attr_reader :name
def initialize(args)
@name = args[:name] || 'no name set'
end
end
user1 = user.new(name: 'user1')
user2 = user.new(name: 'user2')
entity = Class.new(Grape::Entity)
entity.expose :name
subject.format :json
subject.get '/example' do
present :page, 1
present :user1, user1, with: entity
present :user2, user2, with: entity
end
get '/example'
expect_response_json = {
'page' => 1,
'user1' => { 'name' => 'user1' },
'user2' => { 'name' => 'user2' }
}
expect(JSON(last_response.body)).to eq(expect_response_json)
end
end
end
end
grape-0.13.0/spec/grape/api_spec.rb 0000644 0000041 0000041 00000251727 12563420522 017100 0 ustar www-data www-data require 'spec_helper'
require 'shared/versioning_examples'
require 'grape-entity'
describe Grape::API do
subject { Class.new(Grape::API) }
def app
subject
end
describe '.prefix' do
it 'routes root through with the prefix' do
subject.prefix 'awesome/sauce'
subject.get do
'Hello there.'
end
get 'awesome/sauce/'
expect(last_response.status).to eql 200
expect(last_response.body).to eql 'Hello there.'
end
it 'routes through with the prefix' do
subject.prefix 'awesome/sauce'
subject.get :hello do
'Hello there.'
end
get 'awesome/sauce/hello'
expect(last_response.body).to eql 'Hello there.'
get '/hello'
expect(last_response.status).to eql 404
end
it 'supports OPTIONS' do
subject.prefix 'awesome/sauce'
subject.get do
'Hello there.'
end
options 'awesome/sauce'
expect(last_response.status).to eql 204
expect(last_response.body).to be_blank
end
it 'disallows POST' do
subject.prefix 'awesome/sauce'
subject.get
post 'awesome/sauce'
expect(last_response.status).to eql 405
end
end
describe '.version' do
context 'when defined' do
it 'returns version value' do
subject.version 'v1'
expect(subject.version).to eq('v1')
end
end
context 'when not defined' do
it 'returns nil' do
expect(subject.version).to be_nil
end
end
end
describe '.version using path' do
it_should_behave_like 'versioning' do
let(:macro_options) do
{
using: :path
}
end
end
end
describe '.version using param' do
it_should_behave_like 'versioning' do
let(:macro_options) do
{
using: :param,
parameter: 'apiver'
}
end
end
end
describe '.version using header' do
it_should_behave_like 'versioning' do
let(:macro_options) do
{
using: :header,
vendor: 'mycompany',
format: 'json'
}
end
end
# Behavior as defined by rfc2616 when no header is defined
# http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
describe 'no specified accept header' do
# subject.version 'v1', using: :header
# subject.get '/hello' do
# 'hello'
# end
# it 'routes' do
# get '/hello'
# last_response.status.should eql 200
# end
end
# pending 'routes if any media type is allowed'
end
describe '.version using accept_version_header' do
it_should_behave_like 'versioning' do
let(:macro_options) do
{
using: :accept_version_header
}
end
end
end
describe '.represent' do
it 'requires a :with option' do
expect { subject.represent Object, {} }.to raise_error(Grape::Exceptions::InvalidWithOptionForRepresent)
end
it 'adds the association to the :representations setting' do
klass = Class.new
subject.represent Object, with: klass
expect(Grape::DSL::Configuration.stacked_hash_to_hash(subject.namespace_stackable(:representations))[Object]).to eq(klass)
end
end
describe '.namespace' do
it 'is retrievable and converted to a path' do
internal_namespace = nil
subject.namespace :awesome do
internal_namespace = namespace
end
expect(internal_namespace).to eql('/awesome')
end
it 'comes after the prefix and version' do
subject.prefix :rad
subject.version 'v1', using: :path
subject.namespace :awesome do
get('/hello') { 'worked' }
end
get '/rad/v1/awesome/hello'
expect(last_response.body).to eq('worked')
end
it 'cancels itself after the block is over' do
internal_namespace = nil
subject.namespace :awesome do
internal_namespace = namespace
end
expect(subject.namespace).to eql('/')
end
it 'is stackable' do
internal_namespace = nil
internal_second_namespace = nil
subject.namespace :awesome do
internal_namespace = namespace
namespace :rad do
internal_second_namespace = namespace
end
end
expect(internal_namespace).to eq('/awesome')
expect(internal_second_namespace).to eq('/awesome/rad')
end
it 'accepts path segments correctly' do
inner_namespace = nil
subject.namespace :members do
namespace '/:member_id' do
inner_namespace = namespace
get '/' do
params[:member_id]
end
end
end
get '/members/23'
expect(last_response.body).to eq('23')
expect(inner_namespace).to eq('/members/:member_id')
end
it 'is callable with nil just to push onto the stack' do
subject.namespace do
version 'v2', using: :path
get('/hello') { 'inner' }
end
subject.get('/hello') { 'outer' }
get '/v2/hello'
expect(last_response.body).to eq('inner')
get '/hello'
expect(last_response.body).to eq('outer')
end
%w(group resource resources segment).each do |als|
it '`.#{als}` is an alias' do
inner_namespace = nil
subject.send(als, :awesome) do
inner_namespace = namespace
end
expect(inner_namespace).to eq '/awesome'
end
end
end
describe '.route_param' do
it 'adds a parameterized route segment namespace' do
subject.namespace :users do
route_param :id do
get do
params[:id]
end
end
end
get '/users/23'
expect(last_response.body).to eq('23')
end
it 'should be able to define requirements with a single hash' do
subject.namespace :users do
route_param :id, requirements: /[0-9]+/ do
get do
params[:id]
end
end
end
get '/users/michael'
expect(last_response.status).to eq(404)
get '/users/23'
expect(last_response.status).to eq(200)
end
end
describe '.route' do
it 'allows for no path' do
subject.namespace :votes do
get do
'Votes'
end
post do
'Created a Vote'
end
end
get '/votes'
expect(last_response.body).to eql 'Votes'
post '/votes'
expect(last_response.body).to eql 'Created a Vote'
end
it 'handles empty calls' do
subject.get '/'
get '/'
expect(last_response.body).to eql ''
end
describe 'root routes should work with' do
before do
subject.format :txt
subject.content_type :json, 'application/json'
subject.formatter :json, ->(object, _env) { object }
def subject.enable_root_route!
get('/') { 'root' }
end
end
after do
expect(last_response.body).to eql 'root'
end
describe 'path versioned APIs' do
before do
subject.version 'v1', using: :path
subject.enable_root_route!
end
it 'without a format' do
versioned_get '/', 'v1', using: :path
end
it 'with a format' do
get '/v1/.json'
end
end
it 'header versioned APIs' do
subject.version 'v1', using: :header, vendor: 'test'
subject.enable_root_route!
versioned_get '/', 'v1', using: :header, vendor: 'test'
end
it 'header versioned APIs with multiple headers' do
subject.version %w(v1 v2), using: :header, vendor: 'test'
subject.enable_root_route!
versioned_get '/', 'v1', using: :header, vendor: 'test'
versioned_get '/', 'v2', using: :header, vendor: 'test'
end
it 'param versioned APIs' do
subject.version 'v1', using: :param
subject.enable_root_route!
versioned_get '/', 'v1', using: :param
end
it 'Accept-Version header versioned APIs' do
subject.version 'v1', using: :accept_version_header
subject.enable_root_route!
versioned_get '/', 'v1', using: :accept_version_header
end
it 'unversioned APIs' do
subject.enable_root_route!
get '/'
end
end
it 'allows for multiple paths' do
subject.get(['/abc', '/def']) do
'foo'
end
get '/abc'
expect(last_response.body).to eql 'foo'
get '/def'
expect(last_response.body).to eql 'foo'
end
context 'format' do
before(:each) do
allow_any_instance_of(Object).to receive(:to_json).and_return('abc')
allow_any_instance_of(Object).to receive(:to_txt).and_return('def')
subject.get('/abc') do
Object.new
end
end
it 'allows .json' do
get '/abc.json'
expect(last_response.status).to eq(200)
expect(last_response.body).to eql 'abc' # json-encoded symbol
end
it 'allows .txt' do
get '/abc.txt'
expect(last_response.status).to eq(200)
expect(last_response.body).to eql 'def' # raw text
end
end
it 'allows for format without corrupting a param' do
subject.get('/:id') do
{ 'id' => params[:id] }
end
get '/awesome.json'
expect(last_response.body).to eql '{"id":"awesome"}'
end
it 'allows for format in namespace with no path' do
subject.namespace :abc do
get do
['json']
end
end
get '/abc.json'
expect(last_response.body).to eql '["json"]'
end
it 'allows for multiple verbs' do
subject.route([:get, :post], '/abc') do
'hiya'
end
subject.endpoints.first.routes.each do |route|
expect(route.route_path).to eql '/abc(.:format)'
end
get '/abc'
expect(last_response.body).to eql 'hiya'
post '/abc'
expect(last_response.body).to eql 'hiya'
end
[:put, :post].each do |verb|
context verb do
['string', :symbol, 1, -1.1, {}, [], true, false, nil].each do |object|
it "allows a(n) #{object.class} json object in params" do
subject.format :json
subject.send(verb) do
env['api.request.body']
end
send verb, '/', MultiJson.dump(object), 'CONTENT_TYPE' => 'application/json'
expect(last_response.status).to eq(verb == :post ? 201 : 200)
expect(last_response.body).to eql MultiJson.dump(object)
expect(last_request.params).to eql({})
end
it 'stores input in api.request.input' do
subject.format :json
subject.send(verb) do
env['api.request.input']
end
send verb, '/', MultiJson.dump(object), 'CONTENT_TYPE' => 'application/json'
expect(last_response.status).to eq(verb == :post ? 201 : 200)
expect(last_response.body).to eql MultiJson.dump(object).to_json
end
context 'chunked transfer encoding' do
it 'stores input in api.request.input' do
subject.format :json
subject.send(verb) do
env['api.request.input']
end
send verb, '/', MultiJson.dump(object), 'CONTENT_TYPE' => 'application/json', 'HTTP_TRANSFER_ENCODING' => 'chunked', 'CONTENT_LENGTH' => nil
expect(last_response.status).to eq(verb == :post ? 201 : 200)
expect(last_response.body).to eql MultiJson.dump(object).to_json
end
end
end
end
end
it 'allows for multipart paths' do
subject.route([:get, :post], '/:id/first') do
'first'
end
subject.route([:get, :post], '/:id') do
'ola'
end
subject.route([:get, :post], '/:id/first/second') do
'second'
end
get '/1'
expect(last_response.body).to eql 'ola'
post '/1'
expect(last_response.body).to eql 'ola'
get '/1/first'
expect(last_response.body).to eql 'first'
post '/1/first'
expect(last_response.body).to eql 'first'
get '/1/first/second'
expect(last_response.body).to eql 'second'
end
it 'allows for :any as a verb' do
subject.route(:any, '/abc') do
'lol'
end
%w(get post put delete options patch).each do |m|
send(m, '/abc')
expect(last_response.body).to eql 'lol'
end
end
verbs = %w(post get head delete put options patch)
verbs.each do |verb|
it 'allows and properly constrain a #{verb.upcase} method' do
subject.send(verb, '/example') do
verb
end
send(verb, '/example')
expect(last_response.body).to eql verb == 'head' ? '' : verb
# Call it with a method other than the properly constrained one.
send(used_verb = verbs[(verbs.index(verb) + 2) % verbs.size], '/example')
expect(last_response.status).to eql used_verb == 'options' ? 204 : 405
end
end
it 'returns a 201 response code for POST by default' do
subject.post('example') do
'Created'
end
post '/example'
expect(last_response.status).to eql 201
expect(last_response.body).to eql 'Created'
end
it 'returns a 405 for an unsupported method with an X-Custom-Header' do
subject.before { header 'X-Custom-Header', 'foo' }
subject.get 'example' do
'example'
end
put '/example'
expect(last_response.status).to eql 405
expect(last_response.body).to eql ''
expect(last_response.headers['X-Custom-Header']).to eql 'foo'
end
specify '405 responses includes an Allow header specifying supported methods' do
subject.get 'example' do
'example'
end
subject.post 'example' do
'example'
end
put '/example'
expect(last_response.headers['Allow']).to eql 'OPTIONS, GET, POST, HEAD'
end
specify '405 responses includes an Content-Type header' do
subject.get 'example' do
'example'
end
subject.post 'example' do
'example'
end
put '/example'
expect(last_response.headers['Content-Type']).to eql 'text/plain'
end
it 'adds an OPTIONS route that returns a 204, an Allow header and a X-Custom-Header' do
subject.before { header 'X-Custom-Header', 'foo' }
subject.get 'example' do
'example'
end
options '/example'
expect(last_response.status).to eql 204
expect(last_response.body).to eql ''
expect(last_response.headers['Allow']).to eql 'OPTIONS, GET, HEAD'
expect(last_response.headers['X-Custom-Header']).to eql 'foo'
end
it 'allows HEAD on a GET request' do
subject.get 'example' do
'example'
end
head '/example'
expect(last_response.status).to eql 200
expect(last_response.body).to eql ''
end
it 'overwrites the default HEAD request' do
subject.head 'example' do
error! 'nothing to see here', 400
end
subject.get 'example' do
'example'
end
head '/example'
expect(last_response.status).to eql 400
end
end
context 'do_not_route_head!' do
before :each do
subject.do_not_route_head!
subject.get 'example' do
'example'
end
end
it 'options does not contain HEAD' do
options '/example'
expect(last_response.status).to eql 204
expect(last_response.body).to eql ''
expect(last_response.headers['Allow']).to eql 'OPTIONS, GET'
end
it 'does not allow HEAD on a GET request' do
head '/example'
expect(last_response.status).to eql 405
end
end
context 'do_not_route_options!' do
before :each do
subject.do_not_route_options!
subject.get 'example' do
'example'
end
end
it 'options does not exist' do
options '/example'
expect(last_response.status).to eql 405
end
end
describe 'filters' do
it 'adds a before filter' do
subject.before { @foo = 'first' }
subject.before { @bar = 'second' }
subject.get '/' do
"#{@foo} #{@bar}"
end
get '/'
expect(last_response.body).to eql 'first second'
end
it 'adds a before filter to current and child namespaces only' do
subject.get '/' do
"root - #{@foo}"
end
subject.namespace :blah do
before { @foo = 'foo' }
get '/' do
"blah - #{@foo}"
end
namespace :bar do
get '/' do
"blah - bar - #{@foo}"
end
end
end
get '/'
expect(last_response.body).to eql 'root - '
get '/blah'
expect(last_response.body).to eql 'blah - foo'
get '/blah/bar'
expect(last_response.body).to eql 'blah - bar - foo'
end
it 'adds a after_validation filter' do
subject.after_validation { @foo = "first #{params[:id] }:#{params[:id].class}" }
subject.after_validation { @bar = 'second' }
subject.params do
requires :id, type: Integer
end
subject.get '/' do
"#{@foo} #{@bar}"
end
get '/', id: '32'
expect(last_response.body).to eql 'first 32:Fixnum second'
end
it 'adds a after filter' do
m = double('after mock')
subject.after { m.do_something! }
subject.after { m.do_something! }
subject.get '/' do
@var ||= 'default'
end
expect(m).to receive(:do_something!).exactly(2).times
get '/'
expect(last_response.body).to eql 'default'
end
it 'calls all filters when validation passes' do
a = double('before mock')
b = double('before_validation mock')
c = double('after_validation mock')
d = double('after mock')
subject.params do
requires :id, type: Integer
end
subject.resource ':id' do
before { a.do_something! }
before_validation { b.do_something! }
after_validation { c.do_something! }
after { d.do_something! }
get do
'got it'
end
end
expect(a).to receive(:do_something!).exactly(1).times
expect(b).to receive(:do_something!).exactly(1).times
expect(c).to receive(:do_something!).exactly(1).times
expect(d).to receive(:do_something!).exactly(1).times
get '/123'
expect(last_response.status).to eql 200
expect(last_response.body).to eql 'got it'
end
it 'calls only before filters when validation fails' do
a = double('before mock')
b = double('before_validation mock')
c = double('after_validation mock')
d = double('after mock')
subject.params do
requires :id, type: Integer
end
subject.resource ':id' do
before { a.do_something! }
before_validation { b.do_something! }
after_validation { c.do_something! }
after { d.do_something! }
get do
'got it'
end
end
expect(a).to receive(:do_something!).exactly(1).times
expect(b).to receive(:do_something!).exactly(1).times
expect(c).to receive(:do_something!).exactly(0).times
expect(d).to receive(:do_something!).exactly(0).times
get '/abc'
expect(last_response.status).to eql 400
expect(last_response.body).to eql 'id is invalid'
end
it 'calls filters in the correct order' do
i = 0
a = double('before mock')
b = double('before_validation mock')
c = double('after_validation mock')
d = double('after mock')
subject.params do
requires :id, type: Integer
end
subject.resource ':id' do
before { a.here(i += 1) }
before_validation { b.here(i += 1) }
after_validation { c.here(i += 1) }
after { d.here(i += 1) }
get do
'got it'
end
end
expect(a).to receive(:here).with(1).exactly(1).times
expect(b).to receive(:here).with(2).exactly(1).times
expect(c).to receive(:here).with(3).exactly(1).times
expect(d).to receive(:here).with(4).exactly(1).times
get '/123'
expect(last_response.status).to eql 200
expect(last_response.body).to eql 'got it'
end
end
context 'format' do
before do
subject.get('/foo') { 'bar' }
end
it 'sets content type for txt format' do
get '/foo'
expect(last_response.headers['Content-Type']).to eq('text/plain')
end
it 'sets content type for xml' do
get '/foo.xml'
expect(last_response.headers['Content-Type']).to eq('application/xml')
end
it 'sets content type for json' do
get '/foo.json'
expect(last_response.headers['Content-Type']).to eq('application/json')
end
it 'sets content type for serializable hash format' do
get '/foo.serializable_hash'
expect(last_response.headers['Content-Type']).to eq('application/json')
end
it 'sets content type for binary format' do
get '/foo.binary'
expect(last_response.headers['Content-Type']).to eq('application/octet-stream')
end
it 'returns raw data when content type binary' do
image_filename = 'grape.png'
file = File.open(image_filename, 'rb') { |io| io.read }
subject.format :binary
subject.get('/binary_file') { File.binread(image_filename) }
get '/binary_file'
expect(last_response.headers['Content-Type']).to eq('application/octet-stream')
expect(last_response.body).to eq(file)
end
it 'returns the content of the file with file' do
file_content = 'This is some file content'
test_file = Tempfile.new('test')
test_file.write file_content
test_file.rewind
subject.get('/file') { file test_file }
get '/file'
expect(last_response.headers['Content-Length']).to eq('25')
expect(last_response.headers['Content-Type']).to eq('text/plain')
expect(last_response.body).to eq(file_content)
end
it 'streams the content of the file with stream' do
test_stream = Enumerator.new do |blk|
blk.yield 'This is some'
blk.yield ' file content'
end
subject.use Rack::Chunked
subject.get('/stream') { stream test_stream }
get '/stream', {}, 'HTTP_VERSION' => 'HTTP/1.1'
expect(last_response.headers['Content-Type']).to eq('text/plain')
expect(last_response.headers['Content-Length']).to eq(nil)
expect(last_response.headers['Cache-Control']).to eq('no-cache')
expect(last_response.headers['Transfer-Encoding']).to eq('chunked')
expect(last_response.body).to eq("c\r\nThis is some\r\nd\r\n file content\r\n0\r\n\r\n")
end
it 'sets content type for error' do
subject.get('/error') { error!('error in plain text', 500) }
get '/error'
expect(last_response.headers['Content-Type']).to eql 'text/plain'
end
it 'sets content type for json error' do
subject.format :json
subject.get('/error') { error!('error in json', 500) }
get '/error.json'
expect(last_response.status).to eql 500
expect(last_response.headers['Content-Type']).to eql 'application/json'
end
it 'sets content type for xml error' do
subject.format :xml
subject.get('/error') { error!('error in xml', 500) }
get '/error'
expect(last_response.status).to eql 500
expect(last_response.headers['Content-Type']).to eql 'application/xml'
end
context 'with a custom content_type' do
before do
subject.content_type :custom, 'application/custom'
subject.formatter :custom, ->(_object, _env) { 'custom' }
subject.get('/custom') { 'bar' }
subject.get('/error') { error!('error in custom', 500) }
end
it 'sets content type' do
get '/custom.custom'
expect(last_response.headers['Content-Type']).to eql 'application/custom'
end
it 'sets content type for error' do
get '/error.custom'
expect(last_response.headers['Content-Type']).to eql 'application/custom'
end
end
context 'env["api.format"]' do
before do
subject.post 'attachment' do
filename = params[:file][:filename]
content_type MIME::Types.type_for(filename)[0].to_s
env['api.format'] = :binary # there's no formatter for :binary, data will be returned "as is"
header 'Content-Disposition', "attachment; filename*=UTF-8''#{URI.escape(filename)}"
params[:file][:tempfile].read
end
end
['/attachment.png', 'attachment'].each do |url|
it "uploads and downloads a PNG file via #{url}" do
image_filename = 'grape.png'
post url, file: Rack::Test::UploadedFile.new(image_filename, 'image/png', true)
expect(last_response.status).to eq(201)
expect(last_response.headers['Content-Type']).to eq('image/png')
expect(last_response.headers['Content-Disposition']).to eq("attachment; filename*=UTF-8''grape.png")
File.open(image_filename, 'rb') do |io|
expect(last_response.body).to eq io.read
end
end
end
it 'uploads and downloads a Ruby file' do
filename = __FILE__
post '/attachment.rb', file: Rack::Test::UploadedFile.new(filename, 'application/x-ruby', true)
expect(last_response.status).to eq(201)
expect(last_response.headers['Content-Type']).to eq('application/x-ruby')
expect(last_response.headers['Content-Disposition']).to eq("attachment; filename*=UTF-8''api_spec.rb")
File.open(filename, 'rb') do |io|
expect(last_response.body).to eq io.read
end
end
end
end
context 'custom middleware' do
module ApiSpec
class PhonyMiddleware
def initialize(app, *args)
@args = args
@app = app
@block = true if block_given?
end
def call(env)
env['phony.args'] ||= []
env['phony.args'] << @args
env['phony.block'] = true if @block
@app.call(env)
end
end
end
describe '.middleware' do
it 'includes middleware arguments from settings' do
subject.use ApiSpec::PhonyMiddleware, 'abc', 123
expect(subject.middleware).to eql [[ApiSpec::PhonyMiddleware, 'abc', 123]]
end
it 'includes all middleware from stacked settings' do
subject.use ApiSpec::PhonyMiddleware, 123
subject.use ApiSpec::PhonyMiddleware, 'abc'
subject.use ApiSpec::PhonyMiddleware, 'foo'
expect(subject.middleware).to eql [
[ApiSpec::PhonyMiddleware, 123],
[ApiSpec::PhonyMiddleware, 'abc'],
[ApiSpec::PhonyMiddleware, 'foo']
]
end
end
describe '.use' do
it 'adds middleware' do
subject.use ApiSpec::PhonyMiddleware, 123
expect(subject.middleware).to eql [[ApiSpec::PhonyMiddleware, 123]]
end
it 'does not show up outside the namespace' do
inner_middleware = nil
subject.use ApiSpec::PhonyMiddleware, 123
subject.namespace :awesome do
use ApiSpec::PhonyMiddleware, 'abc'
inner_middleware = middleware
end
expect(subject.middleware).to eql [[ApiSpec::PhonyMiddleware, 123]]
expect(inner_middleware).to eql [[ApiSpec::PhonyMiddleware, 123], [ApiSpec::PhonyMiddleware, 'abc']]
end
it 'calls the middleware' do
subject.use ApiSpec::PhonyMiddleware, 'hello'
subject.get '/' do
env['phony.args'].first.first
end
get '/'
expect(last_response.body).to eql 'hello'
end
it 'adds a block if one is given' do
block = -> {}
subject.use ApiSpec::PhonyMiddleware, &block
expect(subject.middleware).to eql [[ApiSpec::PhonyMiddleware, block]]
end
it 'uses a block if one is given' do
block = -> {}
subject.use ApiSpec::PhonyMiddleware, &block
subject.get '/' do
env['phony.block'].inspect
end
get '/'
expect(last_response.body).to eq('true')
end
it 'does not destroy the middleware settings on multiple runs' do
block = -> {}
subject.use ApiSpec::PhonyMiddleware, &block
subject.get '/' do
env['phony.block'].inspect
end
2.times do
get '/'
expect(last_response.body).to eq('true')
end
end
it 'mounts behind error middleware' do
m = Class.new(Grape::Middleware::Base) do
def before
throw :error, message: 'Caught in the Net', status: 400
end
end
subject.use m
subject.get '/' do
end
get '/'
expect(last_response.status).to eq(400)
expect(last_response.body).to eq('Caught in the Net')
end
end
end
describe '.http_basic' do
it 'protects any resources on the same scope' do
subject.http_basic do |u, _p|
u == 'allow'
end
subject.get(:hello) { 'Hello, world.' }
get '/hello'
expect(last_response.status).to eql 401
get '/hello', {}, 'HTTP_AUTHORIZATION' => encode_basic_auth('allow', 'whatever')
expect(last_response.status).to eql 200
end
it 'is scopable' do
subject.get(:hello) { 'Hello, world.' }
subject.namespace :admin do
http_basic do |u, _p|
u == 'allow'
end
get(:hello) { 'Hello, world.' }
end
get '/hello'
expect(last_response.status).to eql 200
get '/admin/hello'
expect(last_response.status).to eql 401
end
it 'is callable via .auth as well' do
subject.auth :http_basic do |u, _p|
u == 'allow'
end
subject.get(:hello) { 'Hello, world.' }
get '/hello'
expect(last_response.status).to eql 401
get '/hello', {}, 'HTTP_AUTHORIZATION' => encode_basic_auth('allow', 'whatever')
expect(last_response.status).to eql 200
end
it 'has access to the current endpoint' do
basic_auth_context = nil
subject.http_basic do |u, _p|
basic_auth_context = self
u == 'allow'
end
subject.get(:hello) { 'Hello, world.' }
get '/hello', {}, 'HTTP_AUTHORIZATION' => encode_basic_auth('allow', 'whatever')
expect(basic_auth_context).to be_an_instance_of(Grape::Endpoint)
end
it 'has access to helper methods' do
subject.helpers do
def authorize(u, p)
u == 'allow' && p == 'whatever'
end
end
subject.http_basic do |u, p|
authorize(u, p)
end
subject.get(:hello) { 'Hello, world.' }
get '/hello', {}, 'HTTP_AUTHORIZATION' => encode_basic_auth('allow', 'whatever')
expect(last_response.status).to eql 200
get '/hello', {}, 'HTTP_AUTHORIZATION' => encode_basic_auth('disallow', 'whatever')
expect(last_response.status).to eql 401
end
it 'can set instance variables accessible to routes' do
subject.http_basic do |u, _p|
@hello = 'Hello, world.'
u == 'allow'
end
subject.get(:hello) { @hello }
get '/hello', {}, 'HTTP_AUTHORIZATION' => encode_basic_auth('allow', 'whatever')
expect(last_response.status).to eql 200
expect(last_response.body).to eql 'Hello, world.'
end
end
describe '.logger' do
subject do
Class.new(Grape::API) do
def self.io
@io ||= StringIO.new
end
logger ::Logger.new(io)
end
end
it 'returns an instance of Logger class by default' do
expect(subject.logger.class).to eql Logger
end
it 'allows setting a custom logger' do
mylogger = Class.new
subject.logger mylogger
expect(mylogger).to receive(:info).exactly(1).times
subject.logger.info 'this will be logged'
end
it 'defaults to a standard logger log format' do
t = Time.at(100)
allow(Time).to receive(:now).and_return(t)
if ActiveSupport::VERSION::MAJOR >= 4
expect(subject.io).to receive(:write).with("I, [#{Logger::Formatter.new.send(:format_datetime, t)}\##{Process.pid}] INFO -- : this will be logged\n")
else
expect(subject.io).to receive(:write).with("this will be logged\n")
end
subject.logger.info 'this will be logged'
end
end
describe '.helpers' do
it 'is accessible from the endpoint' do
subject.helpers do
def hello
'Hello, world.'
end
end
subject.get '/howdy' do
hello
end
get '/howdy'
expect(last_response.body).to eql 'Hello, world.'
end
it 'is scopable' do
subject.helpers do
def generic
'always there'
end
end
subject.namespace :admin do
helpers do
def secret
'only in admin'
end
end
get '/secret' do
[generic, secret].join ':'
end
end
subject.get '/generic' do
[generic, respond_to?(:secret)].join ':'
end
get '/generic'
expect(last_response.body).to eql 'always there:false'
get '/admin/secret'
expect(last_response.body).to eql 'always there:only in admin'
end
it 'is reopenable' do
subject.helpers do
def one
1
end
end
subject.helpers do
def two
2
end
end
subject.get 'howdy' do
[one, two]
end
expect { get '/howdy' }.not_to raise_error
end
it 'allows for modules' do
mod = Module.new do
def hello
'Hello, world.'
end
end
subject.helpers mod
subject.get '/howdy' do
hello
end
get '/howdy'
expect(last_response.body).to eql 'Hello, world.'
end
it 'allows multiple calls with modules and blocks' do
subject.helpers Module.new do
def one
1
end
end
subject.helpers Module.new do
def two
2
end
end
subject.helpers do
def three
3
end
end
subject.get 'howdy' do
[one, two, three]
end
expect { get '/howdy' }.not_to raise_error
end
end
describe '.scope' do
# TODO: refactor this to not be tied to versioning. How about a generic
# .setting macro?
it 'scopes the various settings' do
subject.prefix 'new'
subject.scope :legacy do
prefix 'legacy'
get '/abc' do
'abc'
end
end
subject.get '/def' do
'def'
end
get '/new/abc'
expect(last_response.status).to eql 404
get '/legacy/abc'
expect(last_response.status).to eql 200
get '/legacy/def'
expect(last_response.status).to eql 404
get '/new/def'
expect(last_response.status).to eql 200
end
end
describe '.rescue_from' do
it 'does not rescue errors when rescue_from is not set' do
subject.get '/exception' do
fail 'rain!'
end
expect { get '/exception' }.to raise_error
end
it 'rescues all errors if rescue_from :all is called' do
subject.rescue_from :all
subject.get '/exception' do
fail 'rain!'
end
get '/exception'
expect(last_response.status).to eql 500
expect(last_response.body).to eq 'rain!'
end
it 'rescues all errors with a json formatter' do
subject.format :json
subject.default_format :json
subject.rescue_from :all
subject.get '/exception' do
fail 'rain!'
end
get '/exception'
expect(last_response.status).to eql 500
expect(last_response.body).to eq({ error: 'rain!' }.to_json)
end
it 'rescues only certain errors if rescue_from is called with specific errors' do
subject.rescue_from ArgumentError
subject.get('/rescued') { fail ArgumentError }
subject.get('/unrescued') { fail 'beefcake' }
get '/rescued'
expect(last_response.status).to eql 500
expect { get '/unrescued' }.to raise_error
end
context 'CustomError subclass of Grape::Exceptions::Base' do
before do
class CustomError < Grape::Exceptions::Base; end
end
it 'does not re-raise exceptions of type Grape::Exceptions::Base' do
subject.get('/custom_exception') { fail CustomError }
expect { get '/custom_exception' }.not_to raise_error
end
it 'rescues custom grape exceptions' do
subject.rescue_from CustomError do |e|
rack_response('New Error', e.status)
end
subject.get '/custom_error' do
fail CustomError, status: 400, message: 'Custom Error'
end
get '/custom_error'
expect(last_response.status).to eq(400)
expect(last_response.body).to eq('New Error')
end
end
it 'can rescue exceptions raised in the formatter' do
formatter = double(:formatter)
allow(formatter).to receive(:call) { fail StandardError }
allow(Grape::Formatter::Base).to receive(:formatter_for) { formatter }
subject.rescue_from :all do |_e|
rack_response('Formatter Error', 500)
end
subject.get('/formatter_exception') { 'Hello world' }
get '/formatter_exception'
expect(last_response.status).to eql 500
expect(last_response.body).to eq('Formatter Error')
end
end
describe '.rescue_from klass, block' do
it 'rescues Exception' do
subject.rescue_from RuntimeError do |e|
rack_response("rescued from #{e.message}", 202)
end
subject.get '/exception' do
fail 'rain!'
end
get '/exception'
expect(last_response.status).to eql 202
expect(last_response.body).to eq('rescued from rain!')
end
context 'custom errors' do
before do
class ConnectionError < RuntimeError; end
class DatabaseError < RuntimeError; end
class CommunicationError < StandardError; end
end
it 'rescues an error via rescue_from :all' do
subject.rescue_from :all do |e|
rack_response("rescued from #{e.class.name}", 500)
end
subject.get '/exception' do
fail ConnectionError
end
get '/exception'
expect(last_response.status).to eql 500
expect(last_response.body).to eq('rescued from ConnectionError')
end
it 'rescues a specific error' do
subject.rescue_from ConnectionError do |e|
rack_response("rescued from #{e.class.name}", 500)
end
subject.get '/exception' do
fail ConnectionError
end
get '/exception'
expect(last_response.status).to eql 500
expect(last_response.body).to eq('rescued from ConnectionError')
end
it 'rescues a subclass of an error by default' do
subject.rescue_from RuntimeError do |e|
rack_response("rescued from #{e.class.name}", 500)
end
subject.get '/exception' do
fail ConnectionError
end
get '/exception'
expect(last_response.status).to eql 500
expect(last_response.body).to eq('rescued from ConnectionError')
end
it 'rescues multiple specific errors' do
subject.rescue_from ConnectionError do |e|
rack_response("rescued from #{e.class.name}", 500)
end
subject.rescue_from DatabaseError do |e|
rack_response("rescued from #{e.class.name}", 500)
end
subject.get '/connection' do
fail ConnectionError
end
subject.get '/database' do
fail DatabaseError
end
get '/connection'
expect(last_response.status).to eql 500
expect(last_response.body).to eq('rescued from ConnectionError')
get '/database'
expect(last_response.status).to eql 500
expect(last_response.body).to eq('rescued from DatabaseError')
end
it 'does not rescue a different error' do
subject.rescue_from RuntimeError do |e|
rack_response("rescued from #{e.class.name}", 500)
end
subject.get '/uncaught' do
fail CommunicationError
end
expect { get '/uncaught' }.to raise_error(CommunicationError)
end
end
end
describe '.rescue_from klass, lambda' do
it 'rescues an error with the lambda' do
subject.rescue_from ArgumentError, -> {
rack_response('rescued with a lambda', 400)
}
subject.get('/rescue_lambda') { fail ArgumentError }
get '/rescue_lambda'
expect(last_response.status).to eq(400)
expect(last_response.body).to eq('rescued with a lambda')
end
it 'can execute the lambda with an argument' do
subject.rescue_from ArgumentError, ->(e) {
rack_response(e.message, 400)
}
subject.get('/rescue_lambda') { fail ArgumentError, 'lambda takes an argument' }
get '/rescue_lambda'
expect(last_response.status).to eq(400)
expect(last_response.body).to eq('lambda takes an argument')
end
end
describe '.rescue_from klass, with: method' do
it 'rescues an error with the specified message' do
def rescue_arg_error
Rack::Response.new('rescued with a method', 400)
end
subject.rescue_from ArgumentError, with: rescue_arg_error
subject.get('/rescue_method') { fail ArgumentError }
get '/rescue_method'
expect(last_response.status).to eq(400)
expect(last_response.body).to eq('rescued with a method')
end
end
describe '.rescue_from klass, rescue_subclasses: boolean' do
before do
module APIErrors
class ParentError < StandardError; end
class ChildError < ParentError; end
end
end
it 'rescues error as well as subclass errors with rescue_subclasses option set' do
subject.rescue_from APIErrors::ParentError, rescue_subclasses: true do |e|
rack_response("rescued from #{e.class.name}", 500)
end
subject.get '/caught_child' do
fail APIErrors::ChildError
end
subject.get '/caught_parent' do
fail APIErrors::ParentError
end
subject.get '/uncaught_parent' do
fail StandardError
end
get '/caught_child'
expect(last_response.status).to eql 500
get '/caught_parent'
expect(last_response.status).to eql 500
expect { get '/uncaught_parent' }.to raise_error(StandardError)
end
it 'sets rescue_subclasses to true by default' do
subject.rescue_from APIErrors::ParentError do |e|
rack_response("rescued from #{e.class.name}", 500)
end
subject.get '/caught_child' do
fail APIErrors::ChildError
end
get '/caught_child'
expect(last_response.status).to eql 500
end
it 'does not rescue child errors if rescue_subclasses is false' do
subject.rescue_from APIErrors::ParentError, rescue_subclasses: false do |e|
rack_response("rescued from #{e.class.name}", 500)
end
subject.get '/uncaught' do
fail APIErrors::ChildError
end
expect { get '/uncaught' }.to raise_error(APIErrors::ChildError)
end
end
describe '.error_format' do
it 'rescues all errors and return :txt' do
subject.rescue_from :all
subject.format :txt
subject.get '/exception' do
fail 'rain!'
end
get '/exception'
expect(last_response.body).to eql 'rain!'
end
it 'rescues all errors and return :txt with backtrace' do
subject.rescue_from :all, backtrace: true
subject.format :txt
subject.get '/exception' do
fail 'rain!'
end
get '/exception'
expect(last_response.body.start_with?("rain!\r\n")).to be true
end
it 'rescues all errors with a default formatter' do
subject.default_format :foo
subject.content_type :foo, 'text/foo'
subject.rescue_from :all
subject.get '/exception' do
fail 'rain!'
end
get '/exception.foo'
expect(last_response.body).to start_with 'rain!'
end
it 'defaults the error formatter to format' do
subject.format :json
subject.rescue_from :all
subject.content_type :json, 'application/json'
subject.content_type :foo, 'text/foo'
subject.get '/exception' do
fail 'rain!'
end
get '/exception.json'
expect(last_response.body).to eq('{"error":"rain!"}')
get '/exception.foo'
expect(last_response.body).to eq('{"error":"rain!"}')
end
context 'class' do
before :each do
class CustomErrorFormatter
def self.call(message, _backtrace, _options, _env)
"message: #{message} @backtrace"
end
end
end
it 'returns a custom error format' do
subject.rescue_from :all, backtrace: true
subject.error_formatter :txt, CustomErrorFormatter
subject.get '/exception' do
fail 'rain!'
end
get '/exception'
expect(last_response.body).to eq('message: rain! @backtrace')
end
end
describe 'with' do
context 'class' do
before :each do
class CustomErrorFormatter
def self.call(message, _backtrace, _option, _env)
"message: #{message} @backtrace"
end
end
end
it 'returns a custom error format' do
subject.rescue_from :all, backtrace: true
subject.error_formatter :txt, with: CustomErrorFormatter
subject.get('/exception') { fail 'rain!' }
get '/exception'
expect(last_response.body).to eq('message: rain! @backtrace')
end
end
end
it 'rescues all errors and return :json' do
subject.rescue_from :all
subject.format :json
subject.get '/exception' do
fail 'rain!'
end
get '/exception'
expect(last_response.body).to eql '{"error":"rain!"}'
end
it 'rescues all errors and return :json with backtrace' do
subject.rescue_from :all, backtrace: true
subject.format :json
subject.get '/exception' do
fail 'rain!'
end
get '/exception'
json = MultiJson.load(last_response.body)
expect(json['error']).to eql 'rain!'
expect(json['backtrace'].length).to be > 0
end
it 'rescues error! and return txt' do
subject.format :txt
subject.get '/error' do
error!('Access Denied', 401)
end
get '/error'
expect(last_response.body).to eql 'Access Denied'
end
it 'rescues error! and return json' do
subject.format :json
subject.get '/error' do
error!('Access Denied', 401)
end
get '/error'
expect(last_response.body).to eql '{"error":"Access Denied"}'
end
end
describe '.content_type' do
it 'sets additional content-type' do
subject.content_type :xls, 'application/vnd.ms-excel'
subject.get :excel do
'some binary content'
end
get '/excel.xls'
expect(last_response.content_type).to eq('application/vnd.ms-excel')
end
it 'allows to override content-type' do
subject.get :content do
content_type 'text/javascript'
'var x = 1;'
end
get '/content'
expect(last_response.content_type).to eq('text/javascript')
end
it 'removes existing content types' do
subject.content_type :xls, 'application/vnd.ms-excel'
subject.get :excel do
'some binary content'
end
get '/excel.json'
expect(last_response.status).to eq(406)
expect(last_response.body).to eq("The requested format 'txt' is not supported.")
end
end
describe '.formatter' do
context 'multiple formatters' do
before :each do
subject.formatter :json, ->(object, _env) { "{\"custom_formatter\":\"#{object[:some] }\"}" }
subject.formatter :txt, ->(object, _env) { "custom_formatter: #{object[:some] }" }
subject.get :simple do
{ some: 'hash' }
end
end
it 'sets one formatter' do
get '/simple.json'
expect(last_response.body).to eql '{"custom_formatter":"hash"}'
end
it 'sets another formatter' do
get '/simple.txt'
expect(last_response.body).to eql 'custom_formatter: hash'
end
end
context 'custom formatter' do
before :each do
subject.content_type :json, 'application/json'
subject.content_type :custom, 'application/custom'
subject.formatter :custom, ->(object, _env) { "{\"custom_formatter\":\"#{object[:some] }\"}" }
subject.get :simple do
{ some: 'hash' }
end
end
it 'uses json' do
get '/simple.json'
expect(last_response.body).to eql '{"some":"hash"}'
end
it 'uses custom formatter' do
get '/simple.custom', 'HTTP_ACCEPT' => 'application/custom'
expect(last_response.body).to eql '{"custom_formatter":"hash"}'
end
end
context 'custom formatter class' do
module CustomFormatter
def self.call(object, _env)
"{\"custom_formatter\":\"#{object[:some] }\"}"
end
end
before :each do
subject.content_type :json, 'application/json'
subject.content_type :custom, 'application/custom'
subject.formatter :custom, CustomFormatter
subject.get :simple do
{ some: 'hash' }
end
end
it 'uses json' do
get '/simple.json'
expect(last_response.body).to eql '{"some":"hash"}'
end
it 'uses custom formatter' do
get '/simple.custom', 'HTTP_ACCEPT' => 'application/custom'
expect(last_response.body).to eql '{"custom_formatter":"hash"}'
end
end
end
describe '.parser' do
it 'parses data in format requested by content-type' do
subject.format :json
subject.post '/data' do
{ x: params[:x] }
end
post '/data', '{"x":42}', 'CONTENT_TYPE' => 'application/json'
expect(last_response.status).to eq(201)
expect(last_response.body).to eq('{"x":42}')
end
context 'lambda parser' do
before :each do
subject.content_type :txt, 'text/plain'
subject.content_type :custom, 'text/custom'
subject.parser :custom, ->(object, _env) { { object.to_sym => object.to_s.reverse } }
subject.put :simple do
params[:simple]
end
end
['text/custom', 'text/custom; charset=UTF-8'].each do |content_type|
it "uses parser for #{content_type}" do
put '/simple', 'simple', 'CONTENT_TYPE' => content_type
expect(last_response.status).to eq(200)
expect(last_response.body).to eql 'elpmis'
end
end
end
context 'custom parser class' do
module CustomParser
def self.call(object, _env)
{ object.to_sym => object.to_s.reverse }
end
end
before :each do
subject.content_type :txt, 'text/plain'
subject.content_type :custom, 'text/custom'
subject.parser :custom, CustomParser
subject.put :simple do
params[:simple]
end
end
it 'uses custom parser' do
put '/simple', 'simple', 'CONTENT_TYPE' => 'text/custom'
expect(last_response.status).to eq(200)
expect(last_response.body).to eql 'elpmis'
end
end
context 'multi_xml' do
it "doesn't parse yaml" do
subject.put :yaml do
params[:tag]
end
put '/yaml', 'a123', 'CONTENT_TYPE' => 'application/xml'
expect(last_response.status).to eq(400)
expect(last_response.body).to eql 'Disallowed type attribute: "symbol"'
end
end
context 'none parser class' do
before :each do
subject.parser :json, nil
subject.put 'data' do
"body: #{env['api.request.body'] }"
end
end
it 'does not parse data' do
put '/data', 'not valid json', 'CONTENT_TYPE' => 'application/json'
expect(last_response.status).to eq(200)
expect(last_response.body).to eq('body: not valid json')
end
end
end
describe '.default_format' do
before :each do
subject.format :json
subject.default_format :json
end
it 'returns data in default format' do
subject.get '/data' do
{ x: 42 }
end
get '/data'
expect(last_response.status).to eq(200)
expect(last_response.body).to eq('{"x":42}')
end
it 'parses data in default format' do
subject.post '/data' do
{ x: params[:x] }
end
post '/data', '{"x":42}', 'CONTENT_TYPE' => ''
expect(last_response.status).to eq(201)
expect(last_response.body).to eq('{"x":42}')
end
end
describe '.default_error_status' do
it 'allows setting default_error_status' do
subject.rescue_from :all
subject.default_error_status 200
subject.get '/exception' do
fail 'rain!'
end
get '/exception'
expect(last_response.status).to eql 200
end
it 'has a default error status' do
subject.rescue_from :all
subject.get '/exception' do
fail 'rain!'
end
get '/exception'
expect(last_response.status).to eql 500
end
it 'uses the default error status in error!' do
subject.rescue_from :all
subject.default_error_status 400
subject.get '/exception' do
error! 'rain!'
end
get '/exception'
expect(last_response.status).to eql 400
end
end
context 'http_codes' do
let(:error_presenter) do
Class.new(Grape::Entity) do
expose :code
expose :static
def static
'some static text'
end
end
end
it 'is used as presenter' do
subject.desc 'some desc', http_codes: [
[408, 'Unauthorized', error_presenter]
]
subject.get '/exception' do
error!({ code: 408 }, 408)
end
get '/exception'
expect(last_response.status).to eql 408
expect(last_response.body).to eql({ code: 408, static: 'some static text' }.to_json)
end
it 'presented with' do
error = { code: 408, with: error_presenter }
subject.get '/exception' do
error! error, 408
end
get '/exception'
expect(last_response.status).to eql 408
expect(last_response.body).to eql({ code: 408, static: 'some static text' }.to_json)
end
end
context 'routes' do
describe 'empty api structure' do
it 'returns an empty array of routes' do
expect(subject.routes).to eq([])
end
end
describe 'single method api structure' do
before(:each) do
subject.get :ping do
'pong'
end
end
it 'returns one route' do
expect(subject.routes.size).to eq(1)
route = subject.routes[0]
expect(route.route_version).to be_nil
expect(route.route_path).to eq('/ping(.:format)')
expect(route.route_method).to eq('GET')
end
end
describe 'api structure with two versions and a namespace' do
before :each do
subject.version 'v1', using: :path
subject.get 'version' do
api.version
end
# version v2
subject.version 'v2', using: :path
subject.prefix 'p'
subject.namespace 'n1' do
namespace 'n2' do
get 'version' do
api.version
end
end
end
end
it 'returns the latest version set' do
expect(subject.version).to eq('v2')
end
it 'returns versions' do
expect(subject.versions).to eq(%w(v1 v2))
end
it 'sets route paths' do
expect(subject.routes.size).to be >= 2
expect(subject.routes[0].route_path).to eq('/:version/version(.:format)')
expect(subject.routes[1].route_path).to eq('/p/:version/n1/n2/version(.:format)')
end
it 'sets route versions' do
expect(subject.routes[0].route_version).to eq('v1')
expect(subject.routes[1].route_version).to eq('v2')
end
it 'sets a nested namespace' do
expect(subject.routes[1].route_namespace).to eq('/n1/n2')
end
it 'sets prefix' do
expect(subject.routes[1].route_prefix).to eq('p')
end
end
describe 'api structure with additional parameters' do
before(:each) do
subject.params do
requires :token, desc: 'a token'
optional :limit, desc: 'the limit'
end
subject.get 'split/:string' do
params[:string].split(params[:token], (params[:limit] || 0).to_i)
end
end
it 'splits a string' do
get '/split/a,b,c.json', token: ','
expect(last_response.body).to eq('["a","b","c"]')
end
it 'splits a string with limit' do
get '/split/a,b,c.json', token: ',', limit: '2'
expect(last_response.body).to eq('["a","b,c"]')
end
it 'sets route_params' do
expect(subject.routes.map { |route|
{ params: route.route_params }
}).to eq [
{
params: {
'string' => '',
'token' => { required: true, desc: 'a token' },
'limit' => { required: false, desc: 'the limit' }
}
}
]
end
end
describe 'api structure with multiple apis' do
before(:each) do
subject.params do
requires :one, desc: 'a token'
optional :two, desc: 'the limit'
end
subject.get 'one' do
end
subject.params do
requires :three, desc: 'a token'
optional :four, desc: 'the limit'
end
subject.get 'two' do
end
end
it 'sets route_params' do
expect(subject.routes.map { |route|
{ params: route.route_params }
}).to eq [
{
params: {
'one' => { required: true, desc: 'a token' },
'two' => { required: false, desc: 'the limit' }
}
},
{
params: {
'three' => { required: true, desc: 'a token' },
'four' => { required: false, desc: 'the limit' }
}
}
]
end
end
describe 'api structure with an api without params' do
before(:each) do
subject.params do
requires :one, desc: 'a token'
optional :two, desc: 'the limit'
end
subject.get 'one' do
end
subject.get 'two' do
end
end
it 'sets route_params' do
expect(subject.routes.map { |route|
{ params: route.route_params }
}).to eq [
{
params: {
'one' => { required: true, desc: 'a token' },
'two' => { required: false, desc: 'the limit' }
}
},
{
params: {}
}
]
end
end
describe 'api with a custom route setting' do
before(:each) do
subject.route_setting :custom, key: 'value'
subject.get 'one'
end
it 'exposed' do
expect(subject.routes.count).to eq 1
route = subject.routes.first
expect(route.route_settings[:custom]).to eq(key: 'value')
end
end
describe 'status' do
it 'can be set to arbitrary Fixnum value' do
subject.get '/foo' do
status 210
end
get '/foo'
expect(last_response.status).to eq 210
end
it 'can be set with a status code symbol' do
subject.get '/foo' do
status :see_other
end
get '/foo'
expect(last_response.status).to eq 303
end
end
end
context 'desc' do
it 'empty array of routes' do
expect(subject.routes).to eq([])
end
it 'empty array of routes' do
subject.desc 'grape api'
expect(subject.routes).to eq([])
end
it 'describes a method' do
subject.desc 'first method'
subject.get :first do; end
expect(subject.routes.length).to eq(1)
route = subject.routes.first
expect(route.route_description).to eq('first method')
expect(route.route_foo).to be_nil
expect(route.route_params).to eq({})
end
it 'describes methods separately' do
subject.desc 'first method'
subject.get :first do; end
subject.desc 'second method'
subject.get :second do; end
expect(subject.routes.count).to eq(2)
expect(subject.routes.map { |route|
{ description: route.route_description, params: route.route_params }
}).to eq [
{ description: 'first method', params: {} },
{ description: 'second method', params: {} }
]
end
it 'resets desc' do
subject.desc 'first method'
subject.get :first do; end
subject.get :second do; end
expect(subject.routes.map { |route|
{ description: route.route_description, params: route.route_params }
}).to eq [
{ description: 'first method', params: {} },
{ description: nil, params: {} }
]
end
it 'namespaces and describe arbitrary parameters' do
subject.namespace 'ns' do
desc 'ns second', foo: 'bar'
get 'second' do; end
end
expect(subject.routes.map { |route|
{ description: route.route_description, foo: route.route_foo, params: route.route_params }
}).to eq [
{ description: 'ns second', foo: 'bar', params: {} }
]
end
it 'includes details' do
subject.desc 'method', details: 'method details'
subject.get 'method' do; end
expect(subject.routes.map { |route|
{ description: route.route_description, details: route.route_details, params: route.route_params }
}).to eq [
{ description: 'method', details: 'method details', params: {} }
]
end
it 'describes a method with parameters' do
subject.desc 'Reverses a string.', params: { 's' => { desc: 'string to reverse', type: 'string' } }
subject.get 'reverse' do
params[:s].reverse
end
expect(subject.routes.map { |route|
{ description: route.route_description, params: route.route_params }
}).to eq [
{ description: 'Reverses a string.', params: { 's' => { desc: 'string to reverse', type: 'string' } } }
]
end
it 'does not inherit param descriptions in consequent namespaces' do
subject.desc 'global description'
subject.params do
requires :param1
optional :param2
end
subject.namespace 'ns1' do
get do; end
end
subject.params do
optional :param2
end
subject.namespace 'ns2' do
get do; end
end
routes_doc = subject.routes.map { |route|
{ description: route.route_description, params: route.route_params }
}
expect(routes_doc).to eq [
{ description: 'global description',
params: {
'param1' => { required: true },
'param2' => { required: false }
}
},
{ description: 'global description',
params: {
'param2' => { required: false }
}
}
]
end
it 'merges the parameters of the namespace with the parameters of the method' do
subject.desc 'namespace'
subject.params do
requires :ns_param, desc: 'namespace parameter'
end
subject.namespace 'ns' do
desc 'method'
params do
optional :method_param, desc: 'method parameter'
end
get 'method' do; end
end
routes_doc = subject.routes.map { |route|
{ description: route.route_description, params: route.route_params }
}
expect(routes_doc).to eq [
{ description: 'method',
params: {
'ns_param' => { required: true, desc: 'namespace parameter' },
'method_param' => { required: false, desc: 'method parameter' }
}
}
]
end
it 'merges the parameters of nested namespaces' do
subject.desc 'ns1'
subject.params do
optional :ns_param, desc: 'ns param 1'
requires :ns1_param, desc: 'ns1 param'
end
subject.namespace 'ns1' do
desc 'ns2'
params do
requires :ns_param, desc: 'ns param 2'
requires :ns2_param, desc: 'ns2 param'
end
namespace 'ns2' do
desc 'method'
params do
optional :method_param, desc: 'method param'
end
get 'method' do; end
end
end
expect(subject.routes.map { |route|
{ description: route.route_description, params: route.route_params }
}).to eq [
{ description: 'method',
params: {
'ns_param' => { required: true, desc: 'ns param 2' },
'ns1_param' => { required: true, desc: 'ns1 param' },
'ns2_param' => { required: true, desc: 'ns2 param' },
'method_param' => { required: false, desc: 'method param' }
}
}
]
end
it 'groups nested params and prevents overwriting of params with same name in different groups' do
subject.desc 'method'
subject.params do
group :group1, type: Array do
optional :param1, desc: 'group1 param1 desc'
requires :param2, desc: 'group1 param2 desc'
end
group :group2, type: Array do
optional :param1, desc: 'group2 param1 desc'
requires :param2, desc: 'group2 param2 desc'
end
end
subject.get 'method' do; end
expect(subject.routes.map(&:route_params)).to eq [{
'group1' => { required: true, type: 'Array' },
'group1[param1]' => { required: false, desc: 'group1 param1 desc' },
'group1[param2]' => { required: true, desc: 'group1 param2 desc' },
'group2' => { required: true, type: 'Array' },
'group2[param1]' => { required: false, desc: 'group2 param1 desc' },
'group2[param2]' => { required: true, desc: 'group2 param2 desc' }
}]
end
it 'uses full name of parameters in nested groups' do
subject.desc 'nesting'
subject.params do
requires :root_param, desc: 'root param'
group :nested, type: Array do
requires :nested_param, desc: 'nested param'
end
end
subject.get 'method' do; end
expect(subject.routes.map { |route|
{ description: route.route_description, params: route.route_params }
}).to eq [
{ description: 'nesting',
params: {
'root_param' => { required: true, desc: 'root param' },
'nested' => { required: true, type: 'Array' },
'nested[nested_param]' => { required: true, desc: 'nested param' }
}
}
]
end
it 'allows to set the type attribute on :group element' do
subject.params do
group :foo, type: Array do
optional :bar
end
end
end
it 'parses parameters when no description is given' do
subject.params do
requires :one_param, desc: 'one param'
end
subject.get 'method' do; end
expect(subject.routes.map { |route|
{ description: route.route_description, params: route.route_params }
}).to eq [
{ description: nil, params: { 'one_param' => { required: true, desc: 'one param' } } }
]
end
it 'does not symbolize params' do
subject.desc 'Reverses a string.', params: { 's' => { desc: 'string to reverse', type: 'string' } }
subject.get 'reverse/:s' do
params[:s].reverse
end
expect(subject.routes.map { |route|
{ description: route.route_description, params: route.route_params }
}).to eq [
{ description: 'Reverses a string.', params: { 's' => { desc: 'string to reverse', type: 'string' } } }
]
end
end
describe '.mount' do
let(:mounted_app) { ->(_env) { [200, {}, ['MOUNTED']] } }
context 'with a bare rack app' do
before do
subject.mount mounted_app => '/mounty'
end
it 'makes a bare Rack app available at the endpoint' do
get '/mounty'
expect(last_response.body).to eq('MOUNTED')
end
it 'anchors the routes, passing all subroutes to it' do
get '/mounty/awesome'
expect(last_response.body).to eq('MOUNTED')
end
it 'is able to cascade' do
subject.mount lambda { |env|
headers = {}
headers['X-Cascade'] == 'pass' unless env['PATH_INFO'].include?('boo')
[200, headers, ['Farfegnugen']]
} => '/'
get '/boo'
expect(last_response.body).to eq('Farfegnugen')
get '/mounty'
expect(last_response.body).to eq('MOUNTED')
end
end
context 'without a hash' do
it 'calls through setting the route to "/"' do
subject.mount mounted_app
get '/'
expect(last_response.body).to eq('MOUNTED')
end
end
context 'mounting an API' do
it 'applies the settings of the mounting api' do
subject.version 'v1', using: :path
subject.namespace :cool do
app = Class.new(Grape::API)
app.get('/awesome') do
'yo'
end
mount app
end
get '/v1/cool/awesome'
expect(last_response.body).to eq('yo')
end
it 'applies the settings to nested mounted apis' do
subject.version 'v1', using: :path
subject.namespace :cool do
inner_app = Class.new(Grape::API)
inner_app.get('/awesome') do
'yo'
end
app = Class.new(Grape::API)
app.mount inner_app
mount app
end
get '/v1/cool/awesome'
expect(last_response.body).to eq('yo')
end
it 'inherits rescues even when some defined by mounted' do
subject.rescue_from :all do |e|
rack_response("rescued from #{e.message}", 202)
end
app = Class.new(Grape::API)
subject.namespace :mounted do
app.rescue_from ArgumentError
app.get('/fail') { fail 'doh!' }
mount app
end
get '/mounted/fail'
expect(last_response.status).to eql 202
expect(last_response.body).to eq('rescued from doh!')
end
it 'collects the routes of the mounted api' do
subject.namespace :cool do
app = Class.new(Grape::API)
app.get('/awesome') {}
app.post('/sauce') {}
mount app
end
expect(subject.routes.size).to eq(2)
expect(subject.routes.first.route_path).to match(%r{\/cool\/awesome})
expect(subject.routes.last.route_path).to match(%r{\/cool\/sauce})
end
it 'mounts on a path' do
subject.namespace :cool do
app = Class.new(Grape::API)
app.get '/awesome' do
'sauce'
end
mount app => '/mounted'
end
get '/mounted/cool/awesome'
expect(last_response.status).to eq(200)
expect(last_response.body).to eq('sauce')
end
it 'mounts on a nested path' do
APP1 = Class.new(Grape::API)
APP2 = Class.new(Grape::API)
APP2.get '/nice' do
'play'
end
# note that the reverse won't work, mount from outside-in
APP3 = subject
APP3.mount APP1 => '/app1'
APP1.mount APP2 => '/app2'
get '/app1/app2/nice'
expect(last_response.status).to eq(200)
expect(last_response.body).to eq('play')
options '/app1/app2/nice'
expect(last_response.status).to eq(204)
end
it 'responds to options' do
app = Class.new(Grape::API)
app.get '/colour' do
'red'
end
app.namespace :pears do
get '/colour' do
'green'
end
end
subject.namespace :apples do
mount app
end
get '/apples/colour'
expect(last_response.status).to eql 200
expect(last_response.body).to eq('red')
options '/apples/colour'
expect(last_response.status).to eql 204
get '/apples/pears/colour'
expect(last_response.status).to eql 200
expect(last_response.body).to eq('green')
options '/apples/pears/colour'
expect(last_response.status).to eql 204
end
it 'responds to options with path versioning' do
subject.version 'v1', using: :path
subject.namespace :apples do
app = Class.new(Grape::API)
app.get('/colour') do
'red'
end
mount app
end
get '/v1/apples/colour'
expect(last_response.status).to eql 200
expect(last_response.body).to eq('red')
options '/v1/apples/colour'
expect(last_response.status).to eql 204
end
it 'mounts a versioned API with nested resources' do
api = Class.new(Grape::API) do
version 'v1'
resources :users do
get :hello do
'hello users'
end
end
end
subject.mount api
get '/v1/users/hello'
expect(last_response.body).to eq('hello users')
end
it 'mounts a prefixed API with nested resources' do
api = Class.new(Grape::API) do
prefix 'api'
resources :users do
get :hello do
'hello users'
end
end
end
subject.mount api
get '/api/users/hello'
expect(last_response.body).to eq('hello users')
end
it 'applies format to a mounted API with nested resources' do
api = Class.new(Grape::API) do
format :json
resources :users do
get do
{ users: true }
end
end
end
subject.mount api
get '/users'
expect(last_response.body).to eq({ users: true }.to_json)
end
it 'applies auth to a mounted API with nested resources' do
api = Class.new(Grape::API) do
format :json
http_basic do |username, password|
username == 'username' && password == 'password'
end
resources :users do
get do
{ users: true }
end
end
end
subject.mount api
get '/users'
expect(last_response.status).to eq(401)
get '/users', {}, 'HTTP_AUTHORIZATION' => encode_basic_auth('username', 'password')
expect(last_response.body).to eq({ users: true }.to_json)
end
it 'mounts multiple versioned APIs with nested resources' do
api1 = Class.new(Grape::API) do
version 'one', using: :header, vendor: 'test'
resources :users do
get :hello do
'one'
end
end
end
api2 = Class.new(Grape::API) do
version 'two', using: :header, vendor: 'test'
resources :users do
get :hello do
'two'
end
end
end
subject.mount api1
subject.mount api2
versioned_get '/users/hello', 'one', using: :header, vendor: 'test'
expect(last_response.body).to eq('one')
versioned_get '/users/hello', 'two', using: :header, vendor: 'test'
expect(last_response.body).to eq('two')
end
end
end
describe '.endpoints' do
it 'adds one for each route created' do
subject.get '/'
subject.post '/'
expect(subject.endpoints.size).to eq(2)
end
end
describe '.compile' do
it 'sets the instance' do
expect(subject.instance).to be_nil
subject.compile
expect(subject.instance).to be_kind_of(subject)
end
end
describe '.change!' do
it 'invalidates any compiled instance' do
subject.compile
subject.change!
expect(subject.instance).to be_nil
end
end
describe '.endpoint' do
before(:each) do
subject.format :json
subject.get '/endpoint/options' do
{
path: options[:path],
source_location: source.source_location
}
end
end
it 'path' do
get '/endpoint/options'
options = MultiJson.load(last_response.body)
expect(options['path']).to eq(['/endpoint/options'])
expect(options['source_location'][0]).to include 'api_spec.rb'
expect(options['source_location'][1].to_i).to be > 0
end
end
describe '.route' do
context 'plain' do
before(:each) do
subject.get '/' do
route.route_path
end
subject.get '/path' do
route.route_path
end
end
it 'provides access to route info' do
get '/'
expect(last_response.body).to eq('/(.:format)')
get '/path'
expect(last_response.body).to eq('/path(.:format)')
end
end
context 'with desc' do
before(:each) do
subject.desc 'returns description'
subject.get '/description' do
route.route_description
end
subject.desc 'returns parameters', params: { 'x' => 'y' }
subject.get '/params/:id' do
route.route_params[params[:id]]
end
end
it 'returns route description' do
get '/description'
expect(last_response.body).to eq('returns description')
end
it 'returns route parameters' do
get '/params/x'
expect(last_response.body).to eq('y')
end
end
end
describe '.format' do
context ':txt' do
before(:each) do
subject.format :txt
subject.content_type :json, 'application/json'
subject.get '/meaning_of_life' do
{ meaning_of_life: 42 }
end
end
it 'forces txt without an extension' do
get '/meaning_of_life'
expect(last_response.body).to eq({ meaning_of_life: 42 }.to_s)
end
it 'does not force txt with an extension' do
get '/meaning_of_life.json'
expect(last_response.body).to eq({ meaning_of_life: 42 }.to_json)
end
it 'forces txt from a non-accepting header' do
get '/meaning_of_life', {}, 'HTTP_ACCEPT' => 'application/json'
expect(last_response.body).to eq({ meaning_of_life: 42 }.to_s)
end
end
context ':txt only' do
before(:each) do
subject.format :txt
subject.get '/meaning_of_life' do
{ meaning_of_life: 42 }
end
end
it 'forces txt without an extension' do
get '/meaning_of_life'
expect(last_response.body).to eq({ meaning_of_life: 42 }.to_s)
end
it 'accepts specified extension' do
get '/meaning_of_life.txt'
expect(last_response.body).to eq({ meaning_of_life: 42 }.to_s)
end
it 'does not accept extensions other than specified' do
get '/meaning_of_life.json'
expect(last_response.status).to eq(404)
end
it 'forces txt from a non-accepting header' do
get '/meaning_of_life', {}, 'HTTP_ACCEPT' => 'application/json'
expect(last_response.body).to eq({ meaning_of_life: 42 }.to_s)
end
end
context ':json' do
before(:each) do
subject.format :json
subject.content_type :txt, 'text/plain'
subject.get '/meaning_of_life' do
{ meaning_of_life: 42 }
end
end
it 'forces json without an extension' do
get '/meaning_of_life'
expect(last_response.body).to eq({ meaning_of_life: 42 }.to_json)
end
it 'does not force json with an extension' do
get '/meaning_of_life.txt'
expect(last_response.body).to eq({ meaning_of_life: 42 }.to_s)
end
it 'forces json from a non-accepting header' do
get '/meaning_of_life', {}, 'HTTP_ACCEPT' => 'text/html'
expect(last_response.body).to eq({ meaning_of_life: 42 }.to_json)
end
it 'can be overwritten with an explicit content type' do
subject.get '/meaning_of_life_with_content_type' do
content_type 'text/plain'
{ meaning_of_life: 42 }.to_s
end
get '/meaning_of_life_with_content_type'
expect(last_response.body).to eq({ meaning_of_life: 42 }.to_s)
end
it 'raised :error from middleware' do
middleware = Class.new(Grape::Middleware::Base) do
def before
throw :error, message: 'Unauthorized', status: 42
end
end
subject.use middleware
subject.get do
end
get '/'
expect(last_response.status).to eq(42)
expect(last_response.body).to eq({ error: 'Unauthorized' }.to_json)
end
end
context ':serializable_hash' do
before(:each) do
class SerializableHashExample
def serializable_hash
{ abc: 'def' }
end
end
subject.format :serializable_hash
end
it 'instance' do
subject.get '/example' do
SerializableHashExample.new
end
get '/example'
expect(last_response.body).to eq('{"abc":"def"}')
end
it 'root' do
subject.get '/example' do
{ 'root' => SerializableHashExample.new }
end
get '/example'
expect(last_response.body).to eq('{"root":{"abc":"def"}}')
end
it 'array' do
subject.get '/examples' do
[SerializableHashExample.new, SerializableHashExample.new]
end
get '/examples'
expect(last_response.body).to eq('[{"abc":"def"},{"abc":"def"}]')
end
end
context ':xml' do
before(:each) do
subject.format :xml
end
it 'string' do
subject.get '/example' do
'example'
end
get '/example'
expect(last_response.status).to eq(500)
expect(last_response.body).to eq <<-XML
cannot convert String to xml
XML
end
it 'hash' do
subject.get '/example' do
ActiveSupport::OrderedHash[
:example1, 'example1',
:example2, 'example2'
]
end
get '/example'
expect(last_response.status).to eq(200)
expect(last_response.body).to eq <<-XML
example1
example2
XML
end
it 'array' do
subject.get '/example' do
%w(example1 example2)
end
get '/example'
expect(last_response.status).to eq(200)
expect(last_response.body).to eq <<-XML
example1
example2
XML
end
it 'raised :error from middleware' do
middleware = Class.new(Grape::Middleware::Base) do
def before
throw :error, message: 'Unauthorized', status: 42
end
end
subject.use middleware
subject.get do
end
get '/'
expect(last_response.status).to eq(42)
expect(last_response.body).to eq <<-XML
Unauthorized
XML
end
end
end
context 'catch-all' do
before do
api1 = Class.new(Grape::API)
api1.version 'v1', using: :path
api1.get 'hello' do
'v1'
end
api2 = Class.new(Grape::API)
api2.version 'v2', using: :path
api2.get 'hello' do
'v2'
end
subject.mount api1
subject.mount api2
end
[true, false].each do |anchor|
it "anchor=#{anchor}" do
subject.route :any, '*path', anchor: anchor do
error!("Unrecognized request path: #{params[:path] } - #{env['PATH_INFO'] }#{env['SCRIPT_NAME'] }", 404)
end
get '/v1/hello'
expect(last_response.status).to eq(200)
expect(last_response.body).to eq('v1')
get '/v2/hello'
expect(last_response.status).to eq(200)
expect(last_response.body).to eq('v2')
get '/foobar'
expect(last_response.status).to eq(404)
expect(last_response.body).to eq('Unrecognized request path: foobar - /foobar')
end
end
end
context 'cascading' do
context 'via version' do
it 'cascades' do
subject.version 'v1', using: :path, cascade: true
get '/v1/hello'
expect(last_response.status).to eq(404)
expect(last_response.headers['X-Cascade']).to eq('pass')
end
it 'does not cascade' do
subject.version 'v2', using: :path, cascade: false
get '/v2/hello'
expect(last_response.status).to eq(404)
expect(last_response.headers.keys).not_to include 'X-Cascade'
end
end
context 'via endpoint' do
it 'cascades' do
subject.cascade true
get '/hello'
expect(last_response.status).to eq(404)
expect(last_response.headers['X-Cascade']).to eq('pass')
end
it 'does not cascade' do
subject.cascade false
get '/hello'
expect(last_response.status).to eq(404)
expect(last_response.headers.keys).not_to include 'X-Cascade'
end
end
end
context 'with json default_error_formatter' do
it 'returns json error' do
subject.content_type :json, 'application/json'
subject.default_error_formatter :json
subject.get '/something' do
'foo'
end
get '/something'
expect(last_response.status).to eq(406)
expect(last_response.body).to eq("{\"error\":\"The requested format 'txt' is not supported.\"}")
end
end
context 'body' do
context 'false' do
before do
subject.get '/blank' do
body false
end
end
it 'returns blank body' do
get '/blank'
expect(last_response.status).to eq(204)
expect(last_response.body).to be_blank
end
end
context 'plain text' do
before do
subject.get '/text' do
content_type 'text/plain'
body 'Hello World'
'ignored'
end
end
it 'returns blank body' do
get '/text'
expect(last_response.status).to eq(200)
expect(last_response.body).to eq 'Hello World'
end
end
end
end
grape-0.13.0/spec/grape/endpoint_spec.rb 0000644 0000041 0000041 00000076655 12563420522 020154 0 ustar www-data www-data require 'spec_helper'
describe Grape::Endpoint do
subject { Class.new(Grape::API) }
def app
subject
end
describe '.before_each' do
after { Grape::Endpoint.before_each(nil) }
it 'should be settable via block' do
block = lambda { |_endpoint| 'noop' }
Grape::Endpoint.before_each(&block)
expect(Grape::Endpoint.before_each).to eq(block)
end
it 'should be settable via reference' do
block = lambda { |_endpoint| 'noop' }
Grape::Endpoint.before_each block
expect(Grape::Endpoint.before_each).to eq(block)
end
it 'should be able to override a helper' do
subject.get('/') { current_user }
expect { get '/' }.to raise_error(NameError)
Grape::Endpoint.before_each do |endpoint|
allow(endpoint).to receive(:current_user).and_return('Bob')
end
get '/'
expect(last_response.body).to eq('Bob')
Grape::Endpoint.before_each(nil)
expect { get '/' }.to raise_error(NameError)
end
end
describe '#initialize' do
it 'takes a settings stack, options, and a block' do
p = proc {}
expect do
Grape::Endpoint.new(Grape::Util::InheritableSetting.new, {
path: '/',
method: :get
}, &p)
end.not_to raise_error
end
end
it 'sets itself in the env upon call' do
subject.get('/') { 'Hello world.' }
get '/'
expect(last_request.env['api.endpoint']).to be_kind_of(Grape::Endpoint)
end
describe '#status' do
it 'is callable from within a block' do
subject.get('/home') do
status 206
'Hello'
end
get '/home'
expect(last_response.status).to eq(206)
expect(last_response.body).to eq('Hello')
end
it 'is set as default to 200 for get' do
memoized_status = nil
subject.get('/home') do
memoized_status = status
'Hello'
end
get '/home'
expect(last_response.status).to eq(200)
expect(memoized_status).to eq(200)
expect(last_response.body).to eq('Hello')
end
it 'is set as default to 201 for post' do
memoized_status = nil
subject.post('/home') do
memoized_status = status
'Hello'
end
post '/home'
expect(last_response.status).to eq(201)
expect(memoized_status).to eq(201)
expect(last_response.body).to eq('Hello')
end
end
describe '#header' do
it 'is callable from within a block' do
subject.get('/hey') do
header 'X-Awesome', 'true'
'Awesome'
end
get '/hey'
expect(last_response.headers['X-Awesome']).to eq('true')
end
end
describe '#headers' do
before do
subject.get('/headers') do
headers.to_json
end
end
it 'includes request headers' do
get '/headers'
expect(JSON.parse(last_response.body)).to eq(
'Host' => 'example.org',
'Cookie' => ''
)
end
it 'includes additional request headers' do
get '/headers', nil, 'HTTP_X_GRAPE_CLIENT' => '1'
expect(JSON.parse(last_response.body)['X-Grape-Client']).to eq('1')
end
it 'includes headers passed as symbols' do
env = Rack::MockRequest.env_for('/headers')
env['HTTP_SYMBOL_HEADER'.to_sym] = 'Goliath passes symbols'
body = subject.call(env)[2].body.first
expect(JSON.parse(body)['Symbol-Header']).to eq('Goliath passes symbols')
end
end
describe '#cookies' do
it 'is callable from within a block' do
subject.get('/get/cookies') do
cookies['my-awesome-cookie1'] = 'is cool'
cookies['my-awesome-cookie2'] = {
value: 'is cool too',
domain: 'my.example.com',
path: '/',
secure: true
}
cookies[:cookie3] = 'symbol'
cookies['cookie4'] = 'secret code here'
end
get('/get/cookies')
expect(last_response.headers['Set-Cookie'].split("\n").sort).to eql [
'cookie3=symbol',
'cookie4=secret+code+here',
'my-awesome-cookie1=is+cool',
'my-awesome-cookie2=is+cool+too; domain=my.example.com; path=/; secure'
]
end
it 'sets browser cookies and does not set response cookies' do
subject.get('/username') do
cookies[:username]
end
get('/username', {}, 'HTTP_COOKIE' => 'username=mrplum; sandbox=true')
expect(last_response.body).to eq('mrplum')
expect(last_response.headers['Set-Cookie']).to be_nil
end
it 'sets and update browser cookies' do
subject.get('/username') do
cookies[:sandbox] = true if cookies[:sandbox] == 'false'
cookies[:username] += '_test'
end
get('/username', {}, 'HTTP_COOKIE' => 'username=user; sandbox=false')
expect(last_response.body).to eq('user_test')
expect(last_response.headers['Set-Cookie']).to match(/username=user_test/)
expect(last_response.headers['Set-Cookie']).to match(/sandbox=true/)
end
it 'deletes cookie' do
subject.get('/test') do
sum = 0
cookies.each do |name, val|
sum += val.to_i
cookies.delete name
end
sum
end
get '/test', {}, 'HTTP_COOKIE' => 'delete_this_cookie=1; and_this=2'
expect(last_response.body).to eq('3')
cookies = Hash[last_response.headers['Set-Cookie'].split("\n").map do |set_cookie|
cookie = CookieJar::Cookie.from_set_cookie 'http://localhost/test', set_cookie
[cookie.name, cookie]
end]
expect(cookies.size).to eq(2)
%w(and_this delete_this_cookie).each do |cookie_name|
cookie = cookies[cookie_name]
expect(cookie).not_to be_nil
expect(cookie.value).to eq('deleted')
expect(cookie.expired?).to be true
end
end
it 'deletes cookies with path' do
subject.get('/test') do
sum = 0
cookies.each do |name, val|
sum += val.to_i
cookies.delete name, path: '/test'
end
sum
end
get('/test', {}, 'HTTP_COOKIE' => 'delete_this_cookie=1; and_this=2')
expect(last_response.body).to eq('3')
cookies = Hash[last_response.headers['Set-Cookie'].split("\n").map do |set_cookie|
cookie = CookieJar::Cookie.from_set_cookie 'http://localhost/test', set_cookie
[cookie.name, cookie]
end]
expect(cookies.size).to eq(2)
%w(and_this delete_this_cookie).each do |cookie_name|
cookie = cookies[cookie_name]
expect(cookie).not_to be_nil
expect(cookie.value).to eq('deleted')
expect(cookie.path).to eq('/test')
expect(cookie.expired?).to be true
end
end
end
describe '#declared' do
before do
subject.params do
requires :first
optional :second
optional :third, default: 'third-default'
optional :nested, type: Hash do
optional :fourth
end
end
end
it 'has as many keys as there are declared params' do
inner_params = nil
subject.get '/declared' do
inner_params = declared(params).keys
''
end
get '/declared?first=present'
expect(last_response.status).to eq(200)
expect(inner_params.size).to eq(4)
end
it 'has a optional param with default value all the time' do
inner_params = nil
subject.get '/declared' do
inner_params = declared(params)
''
end
get '/declared?first=one'
expect(last_response.status).to eq(200)
expect(inner_params[:third]).to eql('third-default')
end
it 'builds nested params' do
inner_params = nil
subject.get '/declared' do
inner_params = declared(params)
''
end
get '/declared?first=present&nested[fourth]=1'
expect(last_response.status).to eq(200)
expect(inner_params[:nested].keys.size).to eq 1
end
it 'builds nested params when given array' do
subject.get '/dummy' do
end
subject.params do
requires :first
optional :second
optional :third, default: 'third-default'
optional :nested, type: Array do
optional :fourth
end
end
inner_params = nil
subject.get '/declared' do
inner_params = declared(params)
''
end
get '/declared?first=present&nested[][fourth]=1&nested[][fourth]=2'
expect(last_response.status).to eq(200)
expect(inner_params[:nested].size).to eq 2
end
it 'builds nested params' do
inner_params = nil
subject.get '/declared' do
inner_params = declared(params)
''
end
get '/declared?first=present&nested[fourth]=1'
expect(last_response.status).to eq(200)
expect(inner_params[:nested].keys.size).to eq 1
end
context 'sets nested array when the param is missing' do
it 'to be array when include_missing is true' do
inner_params = nil
subject.get '/declared' do
inner_params = declared(params, include_missing: true)
''
end
get '/declared?first=present'
expect(last_response.status).to eq(200)
expect(inner_params[:nested]).to be_a(Array)
end
it 'to be nil when include_missing is false' do
inner_params = nil
subject.get '/declared' do
inner_params = declared(params, include_missing: false)
''
end
get '/declared?first=present'
expect(last_response.status).to eq(200)
expect(inner_params[:nested]).to be_nil
end
end
it 'filters out any additional params that are given' do
inner_params = nil
subject.get '/declared' do
inner_params = declared(params)
''
end
get '/declared?first=one&other=two'
expect(last_response.status).to eq(200)
expect(inner_params.key?(:other)).to eq false
end
it 'stringifies if that option is passed' do
inner_params = nil
subject.get '/declared' do
inner_params = declared(params, stringify: true)
''
end
get '/declared?first=one&other=two'
expect(last_response.status).to eq(200)
expect(inner_params['first']).to eq 'one'
end
it 'does not include missing attributes if that option is passed' do
subject.get '/declared' do
error! 400, 'expected nil' if declared(params, include_missing: false)[:second]
''
end
get '/declared?first=one&other=two'
expect(last_response.status).to eq(200)
end
it 'includes attributes with value that evaluates to false' do
subject.params do
requires :first
optional :boolean
end
subject.post '/declared' do
error!('expected false', 400) if declared(params, include_missing: false)[:boolean] != false
''
end
post '/declared', MultiJson.dump(first: 'one', boolean: false), 'CONTENT_TYPE' => 'application/json'
expect(last_response.status).to eq(201)
end
it 'includes attributes with value that evaluates to nil' do
subject.params do
requires :first
optional :second
end
subject.post '/declared' do
error!('expected nil', 400) unless declared(params, include_missing: false)[:second].nil?
''
end
post '/declared', MultiJson.dump(first: 'one', second: nil), 'CONTENT_TYPE' => 'application/json'
expect(last_response.status).to eq(201)
end
it 'does not include missing attributes when there are nested hashes' do
subject.get '/dummy' do
end
subject.params do
requires :first
optional :second
optional :third, default: nil
optional :nested, type: Hash do
optional :fourth, default: nil
optional :fifth, default: nil
requires :nested_nested, type: Hash do
optional :sixth, default: 'sixth-default'
optional :seven, default: nil
end
end
end
inner_params = nil
subject.get '/declared' do
inner_params = declared(params, include_missing: false)
''
end
get '/declared?first=present&nested[fourth]=&nested[nested_nested][sixth]=sixth'
expect(last_response.status).to eq(200)
expect(inner_params[:first]).to eq 'present'
expect(inner_params[:nested].keys).to eq %w(fourth fifth nested_nested)
expect(inner_params[:nested][:fourth]).to eq ''
expect(inner_params[:nested][:nested_nested].keys).to eq %w(sixth seven)
expect(inner_params[:nested][:nested_nested][:sixth]).to eq 'sixth'
end
end
describe '#declared; call from child namespace' do
before do
subject.format :json
subject.namespace :something do
params do
requires :id, type: Integer
end
resource ':id' do
params do
requires :foo
optional :bar
end
get do
{
params: params,
declared_params: declared(params)
}
end
params do
requires :happy
optional :days
end
get '/test' do
{
params: params,
declared_params: declared(params, include_parent_namespaces: false)
}
end
end
end
end
it 'should include params defined in the parent namespace' do
get '/something/123', foo: 'test', extra: 'hello'
expect(last_response.status).to eq 200
json = JSON.parse(last_response.body, symbolize_names: true)
expect(json[:params][:id]).to eq 123
expect(json[:declared_params].keys).to match_array [:foo, :bar, :id]
end
it 'does not include params defined in the parent namespace with include_parent_namespaces: false' do
get '/something/123/test', happy: 'test', extra: 'hello'
expect(last_response.status).to eq 200
json = JSON.parse(last_response.body, symbolize_names: true)
expect(json[:params][:id]).to eq 123
expect(json[:declared_params].keys).to match_array [:happy, :days]
end
end
describe '#params' do
it 'is available to the caller' do
subject.get('/hey') do
params[:howdy]
end
get '/hey?howdy=hey'
expect(last_response.body).to eq('hey')
end
it 'parses from path segments' do
subject.get('/hey/:id') do
params[:id]
end
get '/hey/12'
expect(last_response.body).to eq('12')
end
it 'deeply converts nested params' do
subject.get '/location' do
params[:location][:city]
end
get '/location?location[city]=Dallas'
expect(last_response.body).to eq('Dallas')
end
context 'with special requirements' do
it 'parses email param with provided requirements for params' do
subject.get('/:person_email', requirements: { person_email: /.*/ }) do
params[:person_email]
end
get '/someone@example.com'
expect(last_response.body).to eq('someone@example.com')
get 'someone@example.com.pl'
expect(last_response.body).to eq('someone@example.com.pl')
end
it 'parses many params with provided regexps' do
subject.get('/:person_email/test/:number', requirements: { person_email: /someone@(.*).com/, number: /[0-9]/ }) do
params[:person_email] << params[:number]
end
get '/someone@example.com/test/1'
expect(last_response.body).to eq('someone@example.com1')
get '/someone@testing.wrong/test/1'
expect(last_response.status).to eq(404)
get 'someone@test.com/test/wrong_number'
expect(last_response.status).to eq(404)
get 'someone@test.com/wrong_middle/1'
expect(last_response.status).to eq(404)
end
context 'namespace requirements' do
before :each do
subject.namespace :outer, requirements: { person_email: /abc@(.*).com/ } do
get('/:person_email') do
params[:person_email]
end
namespace :inner, requirements: { number: /[0-9]/, person_email: /someone@(.*).com/ }do
get '/:person_email/test/:number' do
params[:person_email] << params[:number]
end
end
end
end
it 'parse email param with provided requirements for params' do
get '/outer/abc@example.com'
expect(last_response.body).to eq('abc@example.com')
end
it "should override outer namespace's requirements" do
get '/outer/inner/someone@testing.wrong/test/1'
expect(last_response.status).to eq(404)
get '/outer/inner/someone@testing.com/test/1'
expect(last_response.status).to eq(200)
expect(last_response.body).to eq('someone@testing.com1')
end
end
end
context 'from body parameters' do
before(:each) do
subject.post '/request_body' do
params[:user]
end
subject.put '/request_body' do
params[:user]
end
end
it 'converts JSON bodies to params' do
post '/request_body', MultiJson.dump(user: 'Bobby T.'), 'CONTENT_TYPE' => 'application/json'
expect(last_response.body).to eq('Bobby T.')
end
it 'does not convert empty JSON bodies to params' do
put '/request_body', '', 'CONTENT_TYPE' => 'application/json'
expect(last_response.body).to eq('')
end
it 'converts XML bodies to params' do
post '/request_body', 'Bobby T.', 'CONTENT_TYPE' => 'application/xml'
expect(last_response.body).to eq('Bobby T.')
end
it 'converts XML bodies to params' do
put '/request_body', 'Bobby T.', 'CONTENT_TYPE' => 'application/xml'
expect(last_response.body).to eq('Bobby T.')
end
it 'does not include parameters not defined by the body' do
subject.post '/omitted_params' do
error! 400, 'expected nil' if params[:version]
params[:user]
end
post '/omitted_params', MultiJson.dump(user: 'Bob'), 'CONTENT_TYPE' => 'application/json'
expect(last_response.status).to eq(201)
expect(last_response.body).to eq('Bob')
end
end
it 'responds with a 406 for an unsupported content-type' do
subject.format :json
# subject.content_type :json, "application/json"
subject.put '/request_body' do
params[:user]
end
put '/request_body', 'Bobby T.', 'CONTENT_TYPE' => 'application/xml'
expect(last_response.status).to eq(406)
expect(last_response.body).to eq('{"error":"The requested content-type \'application/xml\' is not supported."}')
end
context 'content type with params' do
before do
subject.format :json
subject.content_type :json, 'application/json; charset=utf-8'
subject.post do
params[:data]
end
post '/', MultiJson.dump(data: { some: 'payload' }), 'CONTENT_TYPE' => 'application/json'
end
it 'should not response with 406 for same type without params' do
expect(last_response.status).not_to be 406
end
it 'should response with given content type in headers' do
expect(last_response.headers['Content-Type']).to eq 'application/json; charset=utf-8'
end
end
context 'precedence' do
before do
subject.format :json
subject.namespace '/:id' do
get do
{
params: params[:id]
}
end
post do
{
params: params[:id]
}
end
put do
{
params: params[:id]
}
end
end
end
it 'route string params have higher precedence than body params' do
post '/123', { id: 456 }.to_json
expect(JSON.parse(last_response.body)['params']).to eq '123'
put '/123', { id: 456 }.to_json
expect(JSON.parse(last_response.body)['params']).to eq '123'
end
it 'route string params have higher precedence than URL params' do
get '/123?id=456'
expect(JSON.parse(last_response.body)['params']).to eq '123'
post '/123?id=456'
expect(JSON.parse(last_response.body)['params']).to eq '123'
end
end
end
describe '#error!' do
it 'accepts a message' do
subject.get('/hey') do
error! 'This is not valid.'
'This is valid.'
end
get '/hey'
expect(last_response.status).to eq(500)
expect(last_response.body).to eq('This is not valid.')
end
it 'accepts a code' do
subject.get('/hey') do
error! 'Unauthorized.', 401
end
get '/hey'
expect(last_response.status).to eq(401)
expect(last_response.body).to eq('Unauthorized.')
end
it 'accepts an object and render it in format' do
subject.get '/hey' do
error!({ 'dude' => 'rad' }, 403)
end
get '/hey.json'
expect(last_response.status).to eq(403)
expect(last_response.body).to eq('{"dude":"rad"}')
end
it 'can specifiy headers' do
subject.get '/hey' do
error!({ 'dude' => 'rad' }, 403, 'X-Custom' => 'value')
end
get '/hey.json'
expect(last_response.status).to eq(403)
expect(last_response.headers['X-Custom']).to eq('value')
end
it 'sets the status code for the endpoint' do
memoized_endpoint = nil
subject.get '/hey' do
memoized_endpoint = self
error!({ 'dude' => 'rad' }, 403, 'X-Custom' => 'value')
end
get '/hey.json'
expect(memoized_endpoint.status).to eq(403)
end
end
describe '#redirect' do
it 'redirects to a url with status 302' do
subject.get('/hey') do
redirect '/ha'
end
get '/hey'
expect(last_response.status).to eq 302
expect(last_response.headers['Location']).to eq '/ha'
expect(last_response.body).to eq ''
end
it 'has status code 303 if it is not get request and it is http 1.1' do
subject.post('/hey') do
redirect '/ha'
end
post '/hey', {}, 'HTTP_VERSION' => 'HTTP/1.1'
expect(last_response.status).to eq 303
expect(last_response.headers['Location']).to eq '/ha'
end
it 'support permanent redirect' do
subject.get('/hey') do
redirect '/ha', permanent: true
end
get '/hey'
expect(last_response.status).to eq 301
expect(last_response.headers['Location']).to eq '/ha'
expect(last_response.body).to eq ''
end
end
it 'does not persist params between calls' do
subject.post('/new') do
params[:text]
end
post '/new', text: 'abc'
expect(last_response.body).to eq('abc')
post '/new', text: 'def'
expect(last_response.body).to eq('def')
end
it 'resets all instance variables (except block) between calls' do
subject.helpers do
def memoized
@memoized ||= params[:howdy]
end
end
subject.get('/hello') do
memoized
end
get '/hello?howdy=hey'
expect(last_response.body).to eq('hey')
get '/hello?howdy=yo'
expect(last_response.body).to eq('yo')
end
it 'allows explicit return calls' do
subject.get('/home') do
return 'Hello'
end
get '/home'
expect(last_response.status).to eq(200)
expect(last_response.body).to eq('Hello')
end
describe '.generate_api_method' do
it 'raises NameError if the method name is already in use' do
expect do
Grape::Endpoint.generate_api_method('version', &proc {})
end.to raise_error(NameError)
end
it 'raises ArgumentError if a block is not given' do
expect do
Grape::Endpoint.generate_api_method('GET without a block method')
end.to raise_error(ArgumentError)
end
it 'returns a Proc' do
expect(Grape::Endpoint.generate_api_method('GET test for a proc', &proc {})).to be_a Proc
end
end
context 'filters' do
describe 'before filters' do
it 'runs the before filter if set' do
subject.before { env['before_test'] = 'OK' }
subject.get('/before_test') { env['before_test'] }
get '/before_test'
expect(last_response.body).to eq('OK')
end
end
describe 'after filters' do
it 'overrides the response body if it sets it' do
subject.after { body 'after' }
subject.get('/after_test') { 'during' }
get '/after_test'
expect(last_response.body).to eq('after')
end
it 'does not override the response body with its return' do
subject.after { 'after' }
subject.get('/after_test') { 'body' }
get '/after_test'
expect(last_response.body).to eq('body')
end
end
end
context 'anchoring' do
verbs = %w(post get head delete put options patch)
verbs.each do |verb|
it 'allows for the anchoring option with a #{verb.upcase} method' do
subject.send(verb, '/example', anchor: true) do
verb
end
send(verb, '/example/and/some/more')
expect(last_response.status).to eql 404
end
it 'anchors paths by default for the #{verb.upcase} method' do
subject.send(verb, '/example') do
verb
end
send(verb, '/example/and/some/more')
expect(last_response.status).to eql 404
end
it 'responds to /example/and/some/more for the non-anchored #{verb.upcase} method' do
subject.send(verb, '/example', anchor: false) do
verb
end
send(verb, '/example/and/some/more')
expect(last_response.status).to eql verb == 'post' ? 201 : 200
expect(last_response.body).to eql verb == 'head' ? '' : verb
end
end
end
context 'request' do
it 'should be set to the url requested' do
subject.get('/url') do
request.url
end
get '/url'
expect(last_response.body).to eq('http://example.org/url')
end
['v1', :v1].each do |version|
it 'should include version #{version}' do
subject.version version, using: :path
subject.get('/url') do
request.url
end
get "/#{version}/url"
expect(last_response.body).to eq("http://example.org/#{version}/url")
end
end
it 'should include prefix' do
subject.version 'v1', using: :path
subject.prefix 'api'
subject.get('/url') do
request.url
end
get '/api/v1/url'
expect(last_response.body).to eq('http://example.org/api/v1/url')
end
end
context 'version headers' do
before do
# NOTE: a 404 is returned instead of the 406 if cascade: false is not set.
subject.version 'v1', using: :header, vendor: 'ohanapi', cascade: false
subject.get '/test' do
'Hello!'
end
end
it 'result in a 406 response if they are invalid' do
get '/test', {}, 'HTTP_ACCEPT' => 'application/vnd.ohanapi.v1+json'
expect(last_response.status).to eq(406)
end
it 'result in a 406 response if they cannot be parsed by rack-accept' do
get '/test', {}, 'HTTP_ACCEPT' => 'application/vnd.ohanapi.v1+json; version=1'
expect(last_response.status).to eq(406)
end
end
context 'binary' do
before do
subject.get do
file FileStreamer.new(__FILE__)
end
end
it 'suports stream objects in response' do
get '/'
expect(last_response.status).to eq 200
expect(last_response.body).to eq File.read(__FILE__)
end
end
context 'validation errors' do
before do
subject.before do
header['Access-Control-Allow-Origin'] = '*'
end
subject.params do
requires :id, type: String
end
subject.get do
'should not get here'
end
end
it 'returns the errors, and passes headers' do
get '/'
expect(last_response.status).to eq 400
expect(last_response.body).to eq 'id is missing'
expect(last_response.headers['Access-Control-Allow-Origin']).to eq('*')
end
end
context 'instrumentation' do
before do
subject.before do
# Placeholder
end
subject.get do
'hello'
end
@events = []
@subscriber = ActiveSupport::Notifications.subscribe(/grape/) do |*args|
@events << ActiveSupport::Notifications::Event.new(*args)
end
end
after do
ActiveSupport::Notifications.unsubscribe(@subscriber)
end
it 'notifies AS::N' do
get '/'
# In order that the events finalized (time each block ended)
expect(@events).to contain_exactly(
have_attributes(name: 'endpoint_run_filters.grape', payload: { endpoint: an_instance_of(Grape::Endpoint),
filters: a_collection_containing_exactly(an_instance_of(Proc)),
type: :before }),
have_attributes(name: 'endpoint_run_filters.grape', payload: { endpoint: an_instance_of(Grape::Endpoint),
filters: [],
type: :before_validation }),
have_attributes(name: 'endpoint_run_filters.grape', payload: { endpoint: an_instance_of(Grape::Endpoint),
filters: [],
type: :after_validation }),
have_attributes(name: 'endpoint_render.grape', payload: { endpoint: an_instance_of(Grape::Endpoint) }),
have_attributes(name: 'endpoint_run_filters.grape', payload: { endpoint: an_instance_of(Grape::Endpoint),
filters: [],
type: :after }),
have_attributes(name: 'endpoint_run.grape', payload: { endpoint: an_instance_of(Grape::Endpoint),
env: an_instance_of(Hash) })
)
# In order that events were initialized
expect(@events.sort_by(&:time)).to contain_exactly(
have_attributes(name: 'endpoint_run.grape', payload: { endpoint: an_instance_of(Grape::Endpoint),
env: an_instance_of(Hash) }),
have_attributes(name: 'endpoint_run_filters.grape', payload: { endpoint: an_instance_of(Grape::Endpoint),
filters: a_collection_containing_exactly(an_instance_of(Proc)),
type: :before }),
have_attributes(name: 'endpoint_run_filters.grape', payload: { endpoint: an_instance_of(Grape::Endpoint),
filters: [],
type: :before_validation }),
have_attributes(name: 'endpoint_run_filters.grape', payload: { endpoint: an_instance_of(Grape::Endpoint),
filters: [],
type: :after_validation }),
have_attributes(name: 'endpoint_render.grape', payload: { endpoint: an_instance_of(Grape::Endpoint) }),
have_attributes(name: 'endpoint_run_filters.grape', payload: { endpoint: an_instance_of(Grape::Endpoint),
filters: [],
type: :after })
)
end
end
end
grape-0.13.0/spec/grape/validations/ 0000755 0000041 0000041 00000000000 12563420522 017267 5 ustar www-data www-data grape-0.13.0/spec/grape/validations/params_scope_spec.rb 0000644 0000041 0000041 00000021657 12563420522 023315 0 ustar www-data www-data require 'spec_helper'
describe Grape::Validations::ParamsScope do
subject do
Class.new(Grape::API)
end
def app
subject
end
context 'setting a default' do
let(:documentation) { subject.routes.first.route_params }
context 'when the default value is truthy' do
before do
subject.params do
optional :int, type: Integer, default: 42
end
subject.get
end
it 'adds documentation about the default value' do
expect(documentation).to have_key('int')
expect(documentation['int']).to have_key(:default)
expect(documentation['int'][:default]).to eq(42)
end
end
context 'when the default value is false' do
before do
subject.params do
optional :bool, type: Virtus::Attribute::Boolean, default: false
end
subject.get
end
it 'adds documentation about the default value' do
expect(documentation).to have_key('bool')
expect(documentation['bool']).to have_key(:default)
expect(documentation['bool'][:default]).to eq(false)
end
end
context 'when the default value is nil' do
before do
subject.params do
optional :object, type: Object, default: nil
end
subject.get
end
it 'adds documentation about the default value' do
expect(documentation).to have_key('object')
expect(documentation['object']).to have_key(:default)
expect(documentation['object'][:default]).to eq(nil)
end
end
end
context 'without a default' do
before do
subject.params do
optional :object, type: Object
end
subject.get
end
it 'does not add documentation for the default value' do
documentation = subject.routes.first.route_params
expect(documentation).to have_key('object')
expect(documentation['object']).not_to have_key(:default)
end
end
context 'setting description' do
[:desc, :description].each do |description_type|
it "allows setting #{description_type}" do
subject.params do
requires :int, type: Integer, description_type => 'My very nice integer'
end
subject.get '/single' do
'int works'
end
get '/single', int: 420
expect(last_response.status).to eq(200)
expect(last_response.body).to eq('int works')
end
end
end
context 'when using custom types' do
class CustomType
attr_reader :value
def self.parse(value)
fail if value == 'invalid'
new(value)
end
def initialize(value)
@value = value
end
end
it 'coerces the parameter via the type\'s parse method' do
subject.params do
requires :foo, type: CustomType
end
subject.get('/types') { params[:foo].value }
get '/types', foo: 'valid'
expect(last_response.status).to eq(200)
expect(last_response.body).to eq('valid')
get '/types', foo: 'invalid'
expect(last_response.status).to eq(400)
expect(last_response.body).to match(/foo is invalid/)
end
end
context 'array without coerce type explicitly given' do
it 'sets the type based on first element' do
subject.params do
requires :periods, type: Array, values: -> { %w(day month) }
end
subject.get('/required') { 'required works' }
get '/required', periods: %w(day month)
expect(last_response.status).to eq(200)
expect(last_response.body).to eq('required works')
end
it 'fails to call API without Array type' do
subject.params do
requires :periods, type: Array, values: -> { %w(day month) }
end
subject.get('/required') { 'required works' }
get '/required', periods: 'day'
expect(last_response.status).to eq(400)
expect(last_response.body).to eq('periods is invalid')
end
it 'raises exception when values are of different type' do
expect do
subject.params { requires :numbers, type: Array, values: [1, 'definitely not a number', 3] }
end.to raise_error Grape::Exceptions::IncompatibleOptionValues
end
it 'raises exception when range values have different endpoint types' do
expect do
subject.params { requires :numbers, type: Array, values: 0.0..10 }
end.to raise_error Grape::Exceptions::IncompatibleOptionValues
end
end
context 'with range values' do
context "when left range endpoint isn't #kind_of? the type" do
it 'raises exception' do
expect do
subject.params { requires :latitude, type: Integer, values: -90.0..90 }
end.to raise_error Grape::Exceptions::IncompatibleOptionValues
end
end
context "when right range endpoint isn't #kind_of? the type" do
it 'raises exception' do
expect do
subject.params { requires :latitude, type: Integer, values: -90..90.0 }
end.to raise_error Grape::Exceptions::IncompatibleOptionValues
end
end
context 'when both range endpoints are #kind_of? the type' do
it 'accepts values in the range' do
subject.params do
requires :letter, type: String, values: 'a'..'z'
end
subject.get('/letter') { params[:letter] }
get '/letter', letter: 'j'
expect(last_response.status).to eq(200)
expect(last_response.body).to eq('j')
end
it 'rejects values outside the range' do
subject.params do
requires :letter, type: String, values: 'a'..'z'
end
subject.get('/letter') { params[:letter] }
get '/letter', letter: 'J'
expect(last_response.status).to eq(400)
expect(last_response.body).to eq('letter does not have a valid value')
end
end
end
context 'parameters in group' do
it 'errors when no type is provided' do
expect do
subject.params do
group :a do
requires :b
end
end
end.to raise_error Grape::Exceptions::MissingGroupTypeError
expect do
subject.params do
optional :a do
requires :b
end
end
end.to raise_error Grape::Exceptions::MissingGroupTypeError
end
it 'allows Hash as type' do
subject.params do
group :a, type: Hash do
requires :b
end
end
subject.get('/group') { 'group works' }
get '/group', a: { b: true }
expect(last_response.status).to eq(200)
expect(last_response.body).to eq('group works')
subject.params do
optional :a, type: Hash do
requires :b
end
end
get '/optional_type_hash'
end
it 'allows Array as type' do
subject.params do
group :a, type: Array do
requires :b
end
end
subject.get('/group') { 'group works' }
get '/group', a: [{ b: true }]
expect(last_response.status).to eq(200)
expect(last_response.body).to eq('group works')
subject.params do
optional :a, type: Array do
requires :b
end
end
get '/optional_type_array'
end
it 'errors with an unsupported type' do
expect do
subject.params do
group :a, type: Set do
requires :b
end
end
end.to raise_error Grape::Exceptions::UnsupportedGroupTypeError
expect do
subject.params do
optional :a, type: Set do
requires :b
end
end
end.to raise_error Grape::Exceptions::UnsupportedGroupTypeError
end
end
context 'when validations are dependent on a parameter' do
before do
subject.params do
optional :a
given :a do
requires :b
end
end
subject.get('/test') { declared(params).to_json }
end
it 'applies the validations only if the parameter is present' do
get '/test'
expect(last_response.status).to eq(200)
get '/test', a: true
expect(last_response.status).to eq(400)
expect(last_response.body).to eq('b is missing')
get '/test', a: true, b: true
expect(last_response.status).to eq(200)
end
it 'raises an error if the dependent parameter was never specified' do
expect do
subject.params do
given :c do
end
end
end.to raise_error(Grape::Exceptions::UnknownParameter)
end
it 'includes the parameter within #declared(params)' do
get '/test', a: true, b: true
expect(JSON.parse(last_response.body)).to eq('a' => 'true', 'b' => 'true')
end
it 'returns a sensible error message within a nested context' do
subject.params do
requires :bar, type: Hash do
optional :a
given :a do
requires :b
end
end
end
subject.get('/nested') { 'worked' }
get '/nested', bar: { a: true }
expect(last_response.status).to eq(400)
expect(last_response.body).to eq('bar[b] is missing')
end
end
end
grape-0.13.0/spec/grape/validations/attributes_iterator_spec.rb 0000644 0000041 0000041 00000000116 12563420522 024723 0 ustar www-data www-data require 'spec_helper'
describe Grape::Validations::AttributesIterator do
end
grape-0.13.0/spec/grape/validations/validators/ 0000755 0000041 0000041 00000000000 12563420522 021437 5 ustar www-data www-data grape-0.13.0/spec/grape/validations/validators/exactly_one_of_spec.rb 0000644 0000041 0000041 00000004166 12563420522 026003 0 ustar www-data www-data require 'spec_helper'
describe Grape::Validations::ExactlyOneOfValidator do
describe '#validate!' do
let(:scope) do
Struct.new(:opts) do
def params(arg)
arg
end
def required?; end
end
end
let(:exactly_one_of_params) { [:beer, :wine, :grapefruit] }
let(:validator) { described_class.new(exactly_one_of_params, {}, false, scope.new) }
context 'when all restricted params are present' do
let(:params) { { beer: true, wine: true, grapefruit: true } }
it 'raises a validation exception' do
expect do
validator.validate! params
end.to raise_error(Grape::Exceptions::Validation)
end
context 'mixed with other params' do
let(:mixed_params) { params.merge!(other: true, andanother: true) }
it 'still raises a validation exception' do
expect do
validator.validate! mixed_params
end.to raise_error(Grape::Exceptions::Validation)
end
end
end
context 'when a subset of restricted params are present' do
let(:params) { { beer: true, grapefruit: true } }
it 'raises a validation exception' do
expect do
validator.validate! params
end.to raise_error(Grape::Exceptions::Validation)
end
end
context 'when params keys come as strings' do
let(:params) { { 'beer' => true, 'grapefruit' => true } }
it 'raises a validation exception' do
expect do
validator.validate! params
end.to raise_error(Grape::Exceptions::Validation)
end
end
context 'when none of the restricted params is selected' do
let(:params) { { somethingelse: true } }
it 'raises a validation exception' do
expect do
validator.validate! params
end.to raise_error(Grape::Exceptions::Validation)
end
end
context 'when exactly one of the restricted params is selected' do
let(:params) { { beer: true, somethingelse: true } }
it 'does not raise a validation exception' do
expect(validator.validate!(params)).to eql params
end
end
end
end
grape-0.13.0/spec/grape/validations/validators/coerce_spec.rb 0000644 0000041 0000041 00000015547 12563420522 024252 0 ustar www-data www-data # encoding: utf-8
require 'spec_helper'
describe Grape::Validations::CoerceValidator do
subject do
Class.new(Grape::API)
end
def app
subject
end
describe 'coerce' do
module CoerceValidatorSpec
class User
include Virtus.model
attribute :id, Integer
attribute :name, String
end
end
context 'i18n' do
after :each do
I18n.locale = :en
end
it 'i18n error on malformed input' do
I18n.load_path << File.expand_path('../zh-CN.yml', __FILE__)
I18n.reload!
I18n.locale = 'zh-CN'.to_sym
subject.params do
requires :age, type: Integer
end
subject.get '/single' do
'int works'
end
get '/single', age: '43a'
expect(last_response.status).to eq(400)
expect(last_response.body).to eq('å¹´é¾„æ ¼å¼ä¸æ£ç¡®')
end
it 'gives an english fallback error when default locale message is blank' do
I18n.locale = 'pt-BR'.to_sym
subject.params do
requires :age, type: Integer
end
subject.get '/single' do
'int works'
end
get '/single', age: '43a'
expect(last_response.status).to eq(400)
expect(last_response.body).to eq('age is invalid')
end
end
it 'error on malformed input' do
subject.params do
requires :int, type: Integer
end
subject.get '/single' do
'int works'
end
get '/single', int: '43a'
expect(last_response.status).to eq(400)
expect(last_response.body).to eq('int is invalid')
get '/single', int: '43'
expect(last_response.status).to eq(200)
expect(last_response.body).to eq('int works')
end
it 'error on malformed input (Array)' do
subject.params do
requires :ids, type: Array[Integer]
end
subject.get '/array' do
'array int works'
end
get 'array', ids: %w(1 2 az)
expect(last_response.status).to eq(400)
expect(last_response.body).to eq('ids is invalid')
get 'array', ids: %w(1 2 890)
expect(last_response.status).to eq(200)
expect(last_response.body).to eq('array int works')
end
context 'complex objects' do
it 'error on malformed input for complex objects' do
subject.params do
requires :user, type: CoerceValidatorSpec::User
end
subject.get '/user' do
'complex works'
end
get '/user', user: '32'
expect(last_response.status).to eq(400)
expect(last_response.body).to eq('user is invalid')
get '/user', user: { id: 32, name: 'Bob' }
expect(last_response.status).to eq(200)
expect(last_response.body).to eq('complex works')
end
end
context 'coerces' do
it 'Integer' do
subject.params do
requires :int, coerce: Integer
end
subject.get '/int' do
params[:int].class
end
get '/int', int: '45'
expect(last_response.status).to eq(200)
expect(last_response.body).to eq('Fixnum')
end
context 'Array' do
it 'Array of Integers' do
subject.params do
requires :arry, coerce: Array[Integer]
end
subject.get '/array' do
params[:arry][0].class
end
get '/array', arry: %w(1 2 3)
expect(last_response.status).to eq(200)
expect(last_response.body).to eq('Fixnum')
end
it 'Array of Bools' do
subject.params do
requires :arry, coerce: Array[Virtus::Attribute::Boolean]
end
subject.get '/array' do
params[:arry][0].class
end
get 'array', arry: [1, 0]
expect(last_response.status).to eq(200)
expect(last_response.body).to eq('TrueClass')
end
it 'Array of Complex' do
subject.params do
requires :arry, coerce: Array[CoerceValidatorSpec::User]
end
subject.get '/array' do
params[:arry].size
end
get 'array', arry: [31]
expect(last_response.status).to eq(400)
expect(last_response.body).to eq('arry is invalid')
get 'array', arry: { id: 31, name: 'Alice' }
expect(last_response.status).to eq(400)
expect(last_response.body).to eq('arry is invalid')
get 'array', arry: [{ id: 31, name: 'Alice' }]
expect(last_response.status).to eq(200)
expect(last_response.body).to eq('1')
end
end
context 'Set' do
it 'Set of Integers' do
subject.params do
requires :set, coerce: Set[Integer]
end
subject.get '/set' do
params[:set].first.class
end
get '/set', set: Set.new([1, 2, 3, 4]).to_a
expect(last_response.status).to eq(200)
expect(last_response.body).to eq('Fixnum')
end
it 'Set of Bools' do
subject.params do
requires :set, coerce: Set[Virtus::Attribute::Boolean]
end
subject.get '/set' do
params[:set].first.class
end
get '/set', set: Set.new([1, 0]).to_a
expect(last_response.status).to eq(200)
expect(last_response.body).to eq('TrueClass')
end
end
it 'Bool' do
subject.params do
requires :bool, coerce: Virtus::Attribute::Boolean
end
subject.get '/bool' do
params[:bool].class
end
get '/bool', bool: 1
expect(last_response.status).to eq(200)
expect(last_response.body).to eq('TrueClass')
get '/bool', bool: 0
expect(last_response.status).to eq(200)
expect(last_response.body).to eq('FalseClass')
get '/bool', bool: 'false'
expect(last_response.status).to eq(200)
expect(last_response.body).to eq('FalseClass')
get '/bool', bool: 'true'
expect(last_response.status).to eq(200)
expect(last_response.body).to eq('TrueClass')
end
it 'file' do
subject.params do
requires :file, coerce: Rack::Multipart::UploadedFile
end
subject.post '/upload' do
params[:file].filename
end
post '/upload', file: Rack::Test::UploadedFile.new(__FILE__)
expect(last_response.status).to eq(201)
expect(last_response.body).to eq(File.basename(__FILE__).to_s)
end
it 'Nests integers' do
subject.params do
requires :integers, type: Hash do
requires :int, coerce: Integer
end
end
subject.get '/int' do
params[:integers][:int].class
end
get '/int', integers: { int: '45' }
expect(last_response.status).to eq(200)
expect(last_response.body).to eq('Fixnum')
end
end
end
end
grape-0.13.0/spec/grape/validations/validators/default_spec.rb 0000644 0000041 0000041 00000016124 12563420522 024426 0 ustar www-data www-data require 'spec_helper'
describe Grape::Validations::DefaultValidator do
module ValidationsSpec
module DefaultValidatorSpec
class API < Grape::API
default_format :json
params do
optional :id
optional :type, default: 'default-type'
end
get '/' do
{ id: params[:id], type: params[:type] }
end
params do
optional :type1, default: 'default-type1'
optional :type2, default: 'default-type2'
end
get '/user' do
{ type1: params[:type1], type2: params[:type2] }
end
params do
requires :id
optional :type1, default: 'default-type1'
optional :type2, default: 'default-type2'
end
get '/message' do
{ id: params[:id], type1: params[:type1], type2: params[:type2] }
end
params do
optional :random, default: -> { Random.rand }
optional :not_random, default: Random.rand
end
get '/numbers' do
{ random_number: params[:random], non_random_number: params[:non_random_number] }
end
params do
optional :array, type: Array do
requires :name
optional :with_default, default: 'default'
end
end
get '/array' do
{ array: params[:array] }
end
end
end
end
def app
ValidationsSpec::DefaultValidatorSpec::API
end
it 'set default value for optional param' do
get('/')
expect(last_response.status).to eq(200)
expect(last_response.body).to eq({ id: nil, type: 'default-type' }.to_json)
end
it 'set default values for optional params' do
get('/user')
expect(last_response.status).to eq(200)
expect(last_response.body).to eq({ type1: 'default-type1', type2: 'default-type2' }.to_json)
end
it 'set default values for missing params in the request' do
get('/user?type2=value2')
expect(last_response.status).to eq(200)
expect(last_response.body).to eq({ type1: 'default-type1', type2: 'value2' }.to_json)
end
it 'set default values for optional params and allow to use required fields in the same time' do
get('/message?id=1')
expect(last_response.status).to eq(200)
expect(last_response.body).to eq({ id: '1', type1: 'default-type1', type2: 'default-type2' }.to_json)
end
it 'sets lambda based defaults at the time of call' do
get('/numbers')
expect(last_response.status).to eq(200)
before = JSON.parse(last_response.body)
get('/numbers')
expect(last_response.status).to eq(200)
after = JSON.parse(last_response.body)
expect(before['non_random_number']).to eq(after['non_random_number'])
expect(before['random_number']).not_to eq(after['random_number'])
end
it 'sets default values for grouped arrays' do
get('/array?array[][name]=name&array[][name]=name2&array[][with_default]=bar2')
expect(last_response.status).to eq(200)
expect(last_response.body).to eq({ array: [{ name: 'name', with_default: 'default' }, { name: 'name2', with_default: 'bar2' }] }.to_json)
end
context 'optional group with defaults' do
subject do
Class.new(Grape::API) do
default_format :json
end
end
def app
subject
end
context 'optional array without default value includes optional param with default value' do
before do
subject.params do
optional :optional_array, type: Array do
optional :foo_in_optional_array, default: 'bar'
end
end
subject.post '/optional_array' do
{ optional_array: params[:optional_array] }
end
end
it 'returns nil for optional array if param is not provided' do
post '/optional_array'
expect(last_response.status).to eq(201)
expect(last_response.body).to eq({ optional_array: nil }.to_json)
end
end
context 'optional array with default value includes optional param with default value' do
before do
subject.params do
optional :optional_array_with_default, type: Array, default: [] do
optional :foo_in_optional_array, default: 'bar'
end
end
subject.post '/optional_array_with_default' do
{ optional_array_with_default: params[:optional_array_with_default] }
end
end
it 'sets default value for optional array if param is not provided' do
post '/optional_array_with_default'
expect(last_response.status).to eq(201)
expect(last_response.body).to eq({ optional_array_with_default: [] }.to_json)
end
end
context 'optional hash without default value includes optional param with default value' do
before do
subject.params do
optional :optional_hash_without_default, type: Hash do
optional :foo_in_optional_hash, default: 'bar'
end
end
subject.post '/optional_hash_without_default' do
{ optional_hash_without_default: params[:optional_hash_without_default] }
end
end
it 'returns nil for optional hash if param is not provided' do
post '/optional_hash_without_default'
expect(last_response.status).to eq(201)
expect(last_response.body).to eq({ optional_hash_without_default: nil }.to_json)
end
end
context 'optional hash with default value includes optional param with default value' do
before do
subject.params do
optional :optional_hash_with_default, type: Hash, default: {} do
optional :foo_in_optional_hash, default: 'bar'
end
end
subject.post '/optional_hash_with_default_empty_hash' do
{ optional_hash_with_default: params[:optional_hash_with_default] }
end
subject.params do
optional :optional_hash_with_default, type: Hash, default: { foo_in_optional_hash: 'parent_default' } do
optional :some_param
optional :foo_in_optional_hash, default: 'own_default'
end
end
subject.post '/optional_hash_with_default_inner_params' do
{ foo_in_optional_hash: params[:optional_hash_with_default][:foo_in_optional_hash] }
end
end
it 'sets default value for optional hash if param is not provided' do
post '/optional_hash_with_default_empty_hash'
expect(last_response.status).to eq(201)
expect(last_response.body).to eq({ optional_hash_with_default: {} }.to_json)
end
it 'sets default value from parent defaults for inner param if parent param is not provided' do
post '/optional_hash_with_default_inner_params'
expect(last_response.status).to eq(201)
expect(last_response.body).to eq({ foo_in_optional_hash: 'parent_default' }.to_json)
end
it 'sets own default value for inner param if parent param is provided' do
post '/optional_hash_with_default_inner_params', optional_hash_with_default: { some_param: 'param' }
expect(last_response.status).to eq(201)
expect(last_response.body).to eq({ foo_in_optional_hash: 'own_default' }.to_json)
end
end
end
end
grape-0.13.0/spec/grape/validations/validators/at_least_one_of_spec.rb 0000644 0000041 0000041 00000003670 12563420522 026125 0 ustar www-data www-data require 'spec_helper'
describe Grape::Validations::AtLeastOneOfValidator do
describe '#validate!' do
let(:scope) do
Struct.new(:opts) do
def params(arg)
arg
end
def required?; end
end
end
let(:at_least_one_of_params) { [:beer, :wine, :grapefruit] }
let(:validator) { described_class.new(at_least_one_of_params, {}, false, scope.new) }
context 'when all restricted params are present' do
let(:params) { { beer: true, wine: true, grapefruit: true } }
it 'does not raise a validation exception' do
expect(validator.validate!(params)).to eql params
end
context 'mixed with other params' do
let(:mixed_params) { params.merge!(other: true, andanother: true) }
it 'does not raise a validation exception' do
expect(validator.validate!(mixed_params)).to eql mixed_params
end
end
end
context 'when a subset of restricted params are present' do
let(:params) { { beer: true, grapefruit: true } }
it 'does not raise a validation exception' do
expect(validator.validate!(params)).to eql params
end
end
context 'when params keys come as strings' do
let(:params) { { 'beer' => true, 'grapefruit' => true } }
it 'does not raise a validation exception' do
expect(validator.validate!(params)).to eql params
end
end
context 'when none of the restricted params is selected' do
let(:params) { { somethingelse: true } }
it 'raises a validation exception' do
expect do
validator.validate! params
end.to raise_error(Grape::Exceptions::Validation)
end
end
context 'when exactly one of the restricted params is selected' do
let(:params) { { beer: true, somethingelse: true } }
it 'does not raise a validation exception' do
expect(validator.validate!(params)).to eql params
end
end
end
end
grape-0.13.0/spec/grape/validations/validators/zh-CN.yml 0000644 0000041 0000041 00000000336 12563420522 023103 0 ustar www-data www-data zh-CN:
grape:
errors:
format: ! '%{attributes}%{message}'
attributes:
age: 年龄
messages:
coerce: 'æ ¼å¼ä¸æ£ç¡®'
presence: '请填写'
regexp: 'æ ¼å¼ä¸æ£ç¡®'
grape-0.13.0/spec/grape/validations/validators/presence_spec.rb 0000644 0000041 0000041 00000015463 12563420522 024613 0 ustar www-data www-data require 'spec_helper'
describe Grape::Validations::PresenceValidator do
subject do
Class.new(Grape::API) do
format :json
end
end
def app
subject
end
context 'without validation' do
before do
subject.resource :bacons do
get do
'All the bacon'
end
end
end
it 'does not validate for any params' do
get '/bacons'
expect(last_response.status).to eq(200)
expect(last_response.body).to eq('All the bacon'.to_json)
end
end
context 'with a required regexp parameter supplied in the POST body' do
before do
subject.format :json
subject.params do
requires :id, regexp: /^[0-9]+$/
end
subject.post do
{ ret: params[:id] }
end
end
it 'validates id' do
post '/'
expect(last_response.status).to eq(400)
expect(last_response.body).to eq('{"error":"id is missing"}')
io = StringIO.new('{"id" : "a56b"}')
post '/', {}, 'rack.input' => io, 'CONTENT_TYPE' => 'application/json', 'CONTENT_LENGTH' => io.length
expect(last_response.body).to eq('{"error":"id is invalid"}')
expect(last_response.status).to eq(400)
io = StringIO.new('{"id" : 56}')
post '/', {}, 'rack.input' => io, 'CONTENT_TYPE' => 'application/json', 'CONTENT_LENGTH' => io.length
expect(last_response.body).to eq('{"ret":56}')
expect(last_response.status).to eq(201)
end
end
context 'with a required non-empty string' do
before do
subject.params do
requires :email, type: String, allow_blank: false, regexp: /^\S+$/
end
subject.get do
'Hello'
end
end
it 'requires when missing' do
get '/'
expect(last_response.status).to eq(400)
expect(last_response.body).to eq('{"error":"email is missing, email is empty"}')
end
it 'requires when empty' do
get '/', email: ''
expect(last_response.status).to eq(400)
expect(last_response.body).to eq('{"error":"email is empty, email is invalid"}')
end
it 'valid when set' do
get '/', email: 'bob@example.com'
expect(last_response.status).to eq(200)
expect(last_response.body).to eq('Hello'.to_json)
end
end
context 'with required parameters and no type' do
before do
subject.params do
requires :name, :company
end
subject.get do
'Hello'
end
end
it 'validates name, company' do
get '/'
expect(last_response.status).to eq(400)
expect(last_response.body).to eq('{"error":"name is missing"}')
get '/', name: 'Bob'
expect(last_response.status).to eq(400)
expect(last_response.body).to eq('{"error":"company is missing"}')
get '/', name: 'Bob', company: 'TestCorp'
expect(last_response.status).to eq(200)
expect(last_response.body).to eq('Hello'.to_json)
end
end
context 'with nested parameters' do
before do
subject.params do
requires :user, type: Hash do
requires :first_name
requires :last_name
end
end
subject.get '/nested' do
'Nested'
end
end
it 'validates nested parameters' do
get '/nested'
expect(last_response.status).to eq(400)
expect(last_response.body).to eq('{"error":"user is missing, user[first_name] is missing, user[last_name] is missing"}')
get '/nested', user: { first_name: 'Billy' }
expect(last_response.status).to eq(400)
expect(last_response.body).to eq('{"error":"user[last_name] is missing"}')
get '/nested', user: { first_name: 'Billy', last_name: 'Bob' }
expect(last_response.status).to eq(200)
expect(last_response.body).to eq('Nested'.to_json)
end
end
context 'with triply nested required parameters' do
before do
subject.params do
requires :admin, type: Hash do
requires :admin_name
requires :super, type: Hash do
requires :user, type: Hash do
requires :first_name
requires :last_name
end
end
end
end
subject.get '/nested_triple' do
'Nested triple'
end
end
it 'validates triple nested parameters' do
get '/nested_triple'
expect(last_response.status).to eq(400)
expect(last_response.body).to include '{"error":"admin is missing'
get '/nested_triple', user: { first_name: 'Billy' }
expect(last_response.status).to eq(400)
expect(last_response.body).to include '{"error":"admin is missing'
get '/nested_triple', admin: { super: { first_name: 'Billy' } }
expect(last_response.status).to eq(400)
expect(last_response.body).to eq('{"error":"admin[admin_name] is missing, admin[super][user] is missing, admin[super][user][first_name] is missing, admin[super][user][last_name] is missing"}')
get '/nested_triple', super: { user: { first_name: 'Billy', last_name: 'Bob' } }
expect(last_response.status).to eq(400)
expect(last_response.body).to include '{"error":"admin is missing'
get '/nested_triple', admin: { super: { user: { first_name: 'Billy' } } }
expect(last_response.status).to eq(400)
expect(last_response.body).to eq('{"error":"admin[admin_name] is missing, admin[super][user][last_name] is missing"}')
get '/nested_triple', admin: { admin_name: 'admin', super: { user: { first_name: 'Billy' } } }
expect(last_response.status).to eq(400)
expect(last_response.body).to eq('{"error":"admin[super][user][last_name] is missing"}')
get '/nested_triple', admin: { admin_name: 'admin', super: { user: { first_name: 'Billy', last_name: 'Bob' } } }
expect(last_response.status).to eq(200)
expect(last_response.body).to eq('Nested triple'.to_json)
end
end
context 'with reused parameter documentation once required and once optional' do
before do
docs = { name: { type: String, desc: 'some name' } }
subject.params do
requires :all, using: docs
end
subject.get '/required' do
'Hello required'
end
subject.params do
optional :all, using: docs
end
subject.get '/optional' do
'Hello optional'
end
end
it 'works with required' do
get '/required'
expect(last_response.status).to eq(400)
expect(last_response.body).to eq('{"error":"name is missing"}')
get '/required', name: 'Bob'
expect(last_response.status).to eq(200)
expect(last_response.body).to eq('Hello required'.to_json)
end
it 'works with optional' do
get '/optional'
expect(last_response.status).to eq(200)
expect(last_response.body).to eq('Hello optional'.to_json)
get '/optional', name: 'Bob'
expect(last_response.status).to eq(200)
expect(last_response.body).to eq('Hello optional'.to_json)
end
end
end
grape-0.13.0/spec/grape/validations/validators/values_spec.rb 0000644 0000041 0000041 00000020714 12563420522 024301 0 ustar www-data www-data require 'spec_helper'
describe Grape::Validations::ValuesValidator do
class ValuesModel
DEFAULT_VALUES = ['valid-type1', 'valid-type2', 'valid-type3']
class << self
def values
@values ||= []
[DEFAULT_VALUES + @values].flatten.uniq
end
def add_value(value)
@values ||= []
@values << value
end
end
end
module ValidationsSpec
module ValuesValidatorSpec
class API < Grape::API
default_format :json
params do
requires :type, values: ValuesModel.values
end
get '/' do
{ type: params[:type] }
end
params do
optional :type, values: ValuesModel.values, default: 'valid-type2'
end
get '/default/valid' do
{ type: params[:type] }
end
params do
optional :type, values: -> { ValuesModel.values }, default: 'valid-type2'
end
get '/lambda' do
{ type: params[:type] }
end
params do
optional :type, values: ValuesModel.values, default: -> { ValuesModel.values.sample }
end
get '/default_lambda' do
{ type: params[:type] }
end
params do
optional :type, values: -> { ValuesModel.values }, default: -> { ValuesModel.values.sample }
end
get '/default_and_values_lambda' do
{ type: params[:type] }
end
params do
optional :type, type: Boolean, desc: 'A boolean', values: [true]
end
get '/values/optional_boolean' do
{ type: params[:type] }
end
params do
requires :type, type: Integer, desc: 'An integer', values: [10, 11], default: 10
end
get '/values/coercion' do
{ type: params[:type] }
end
params do
requires :type, type: Array[Integer], desc: 'An integer', values: [10, 11], default: 10
end
get '/values/array_coercion' do
{ type: params[:type] }
end
params do
optional :optional, type: Array do
requires :type, values: %w(a b)
end
end
get '/optional_with_required_values'
end
end
end
def app
ValidationsSpec::ValuesValidatorSpec::API
end
it 'allows a valid value for a parameter' do
get('/', type: 'valid-type1')
expect(last_response.status).to eq 200
expect(last_response.body).to eq({ type: 'valid-type1' }.to_json)
end
it 'does not allow an invalid value for a parameter' do
get('/', type: 'invalid-type')
expect(last_response.status).to eq 400
expect(last_response.body).to eq({ error: 'type does not have a valid value' }.to_json)
end
context 'nil value for a parameter' do
it 'does not allow for root params scope' do
get('/', type: nil)
expect(last_response.status).to eq 400
expect(last_response.body).to eq({ error: 'type does not have a valid value' }.to_json)
end
it 'allows for a required param in child scope' do
get('/optional_with_required_values')
expect(last_response.status).to eq 200
end
end
it 'allows a valid default value' do
get('/default/valid')
expect(last_response.status).to eq 200
expect(last_response.body).to eq({ type: 'valid-type2' }.to_json)
end
it 'allows a proc for values' do
get('/lambda', type: 'valid-type1')
expect(last_response.status).to eq 200
expect(last_response.body).to eq({ type: 'valid-type1' }.to_json)
end
it 'does not validate updated values without proc' do
ValuesModel.add_value('valid-type4')
get('/', type: 'valid-type4')
expect(last_response.status).to eq 400
expect(last_response.body).to eq({ error: 'type does not have a valid value' }.to_json)
end
it 'validates against values in a proc' do
ValuesModel.add_value('valid-type4')
get('/lambda', type: 'valid-type4')
expect(last_response.status).to eq 200
expect(last_response.body).to eq({ type: 'valid-type4' }.to_json)
end
it 'does not allow an invalid value for a parameter using lambda' do
get('/lambda', type: 'invalid-type')
expect(last_response.status).to eq 400
expect(last_response.body).to eq({ error: 'type does not have a valid value' }.to_json)
end
it 'validates default value from proc' do
get('/default_lambda')
expect(last_response.status).to eq 200
end
it 'validates default value from proc against values in a proc' do
get('/default_and_values_lambda')
expect(last_response.status).to eq 200
end
it 'raises IncompatibleOptionValues on an invalid default value from proc' do
subject = Class.new(Grape::API)
expect do
subject.params { optional :type, values: ['valid-type1', 'valid-type2', 'valid-type3'], default: ValuesModel.values.sample + '_invalid' }
end.to raise_error Grape::Exceptions::IncompatibleOptionValues
end
it 'raises IncompatibleOptionValues on an invalid default value' do
subject = Class.new(Grape::API)
expect do
subject.params { optional :type, values: ['valid-type1', 'valid-type2', 'valid-type3'], default: 'invalid-type' }
end.to raise_error Grape::Exceptions::IncompatibleOptionValues
end
it 'raises IncompatibleOptionValues when type is incompatible with values array' do
subject = Class.new(Grape::API)
expect do
subject.params { optional :type, values: ['valid-type1', 'valid-type2', 'valid-type3'], type: Symbol }
end.to raise_error Grape::Exceptions::IncompatibleOptionValues
end
it 'allows values to be true or false when setting the type to boolean' do
get('/values/optional_boolean', type: true)
expect(last_response.status).to eq 200
expect(last_response.body).to eq({ type: true }.to_json)
end
it 'allows values to be a kind of the coerced type not just an instance of it' do
get('/values/coercion', type: 10)
expect(last_response.status).to eq 200
expect(last_response.body).to eq({ type: 10 }.to_json)
end
it 'allows values to be a kind of the coerced type in an array' do
get('/values/array_coercion', type: [10])
expect(last_response.status).to eq 200
expect(last_response.body).to eq({ type: [10] }.to_json)
end
it 'raises IncompatibleOptionValues when values contains a value that is not a kind of the type' do
subject = Class.new(Grape::API)
expect do
subject.params { requires :type, values: [10.5, 11], type: Integer }
end.to raise_error Grape::Exceptions::IncompatibleOptionValues
end
context 'with a lambda values' do
subject do
Class.new(Grape::API) do
params do
optional :type, type: String, values: -> { [SecureRandom.uuid] }, default: -> { SecureRandom.uuid }
end
get '/random_values'
end
end
def app
subject
end
before do
expect(SecureRandom).to receive(:uuid).and_return('foo').once
end
it 'only evaluates values dynamically with each request' do
get '/random_values', type: 'foo'
expect(last_response.status).to eq 200
end
it 'chooses default' do
get '/random_values'
expect(last_response.status).to eq 200
end
end
context 'with a range of values' do
subject(:app) do
Class.new(Grape::API) do
params do
optional :value, type: Float, values: 0.0..10.0
end
get '/value' do
{ value: params[:value] }.to_json
end
params do
optional :values, type: Array[Float], values: 0.0..10.0
end
get '/values' do
{ values: params[:values] }.to_json
end
end
end
it 'allows a single value inside of the range' do
get('/value', value: 5.2)
expect(last_response.status).to eq 200
expect(last_response.body).to eq({ value: 5.2 }.to_json)
end
it 'allows an array of values inside of the range' do
get('/values', values: [8.6, 7.5, 3, 0.9])
expect(last_response.status).to eq 200
expect(last_response.body).to eq({ values: [8.6, 7.5, 3.0, 0.9] }.to_json)
end
it 'rejects a single value outside the range' do
get('/value', value: 'a')
expect(last_response.status).to eq 400
expect(last_response.body).to eq('value is invalid, value does not have a valid value')
end
it 'rejects an array of values if any of them are outside the range' do
get('/values', values: [8.6, 75, 3, 0.9])
expect(last_response.status).to eq 400
expect(last_response.body).to eq('values does not have a valid value')
end
end
end
grape-0.13.0/spec/grape/validations/validators/allow_blank_spec.rb 0000644 0000041 0000041 00000017515 12563420522 025274 0 ustar www-data www-data require 'spec_helper'
describe Grape::Validations::AllowBlankValidator do
module ValidationsSpec
module AllowBlankValidatorSpec
class API < Grape::API
default_format :json
params do
requires :name, allow_blank: false
end
get
params do
optional :name, allow_blank: false
end
get '/disallow_blank_optional_param'
params do
requires :name, allow_blank: true
end
get '/allow_blank'
params do
requires :val, type: DateTime, allow_blank: true
end
get '/allow_datetime_blank'
params do
requires :val, type: DateTime, allow_blank: false
end
get '/disallow_datetime_blank'
params do
requires :val, type: DateTime
end
get '/default_allow_datetime_blank'
params do
requires :val, type: Date, allow_blank: true
end
get '/allow_date_blank'
params do
requires :val, type: Integer, allow_blank: true
end
get '/allow_integer_blank'
params do
requires :val, type: Float, allow_blank: true
end
get '/allow_float_blank'
params do
requires :val, type: Fixnum, allow_blank: true
end
get '/allow_fixnum_blank'
params do
requires :val, type: Symbol, allow_blank: true
end
get '/allow_symbol_blank'
params do
requires :val, type: Boolean, allow_blank: true
end
get '/allow_boolean_blank'
params do
requires :val, type: Boolean, allow_blank: false
end
get '/disallow_boolean_blank'
params do
optional :user, type: Hash do
requires :name, allow_blank: false
end
end
get '/disallow_blank_required_param_in_an_optional_group'
params do
optional :user, type: Hash do
requires :name, type: Date, allow_blank: true
end
end
get '/allow_blank_date_param_in_an_optional_group'
params do
optional :user, type: Hash do
optional :name, allow_blank: false
requires :age
end
end
get '/disallow_blank_optional_param_in_an_optional_group'
params do
requires :user, type: Hash do
requires :name, allow_blank: false
end
end
get '/disallow_blank_required_param_in_a_required_group'
params do
requires :user, type: Hash do
requires :name, allow_blank: false
end
end
get '/disallow_string_value_in_a_required_hash_group'
params do
requires :user, type: Hash do
optional :name, allow_blank: false
end
end
get '/disallow_blank_optional_param_in_a_required_group'
params do
optional :user, type: Hash do
optional :name, allow_blank: false
end
end
get '/disallow_string_value_in_an_optional_hash_group'
end
end
end
def app
ValidationsSpec::AllowBlankValidatorSpec::API
end
context 'invalid input' do
it 'refuses empty string' do
get '/', name: ''
expect(last_response.status).to eq(400)
get '/disallow_datetime_blank', val: ''
expect(last_response.status).to eq(400)
end
it 'refuses only whitespaces' do
get '/', name: ' '
expect(last_response.status).to eq(400)
get '/', name: " \n "
expect(last_response.status).to eq(400)
get '/', name: "\n"
expect(last_response.status).to eq(400)
end
it 'refuses nil' do
get '/', name: nil
expect(last_response.status).to eq(400)
end
end
context 'valid input' do
it 'accepts valid input' do
get '/', name: 'bob'
expect(last_response.status).to eq(200)
end
it 'accepts empty input when allow_blank is false' do
get '/allow_blank', name: ''
expect(last_response.status).to eq(200)
end
it 'accepts empty input' do
get '/default_allow_datetime_blank', val: ''
expect(last_response.status).to eq(200)
end
it 'accepts empty when datetime allow_blank' do
get '/allow_datetime_blank', val: ''
expect(last_response.status).to eq(200)
end
it 'accepts empty when date allow_blank' do
get '/allow_date_blank', val: ''
expect(last_response.status).to eq(200)
end
context 'allow_blank when Numeric' do
it 'accepts empty when integer allow_blank' do
get '/allow_integer_blank', val: ''
expect(last_response.status).to eq(200)
end
it 'accepts empty when float allow_blank' do
get '/allow_float_blank', val: ''
expect(last_response.status).to eq(200)
end
it 'accepts empty when fixnum allow_blank' do
get '/allow_fixnum_blank', val: ''
expect(last_response.status).to eq(200)
end
end
it 'accepts empty when symbol allow_blank' do
get '/allow_symbol_blank', val: ''
expect(last_response.status).to eq(200)
end
it 'accepts empty when boolean allow_blank' do
get '/allow_boolean_blank', val: ''
expect(last_response.status).to eq(200)
end
it 'accepts false when boolean allow_blank' do
get '/disallow_boolean_blank', val: false
expect(last_response.status).to eq(200)
end
end
context 'in an optional group' do
context 'as a required param' do
it 'accepts a missing group, even with a disallwed blank param' do
get '/disallow_blank_required_param_in_an_optional_group'
expect(last_response.status).to eq(200)
end
it 'accepts a nested missing date value' do
get '/allow_blank_date_param_in_an_optional_group', user: { name: '' }
expect(last_response.status).to eq(200)
end
it 'refuses a blank value in an existing group' do
get '/disallow_blank_required_param_in_an_optional_group', user: { name: '' }
expect(last_response.status).to eq(400)
end
end
context 'as an optional param' do
it 'accepts a missing group, even with a disallwed blank param' do
get '/disallow_blank_optional_param_in_an_optional_group'
expect(last_response.status).to eq(200)
end
it 'accepts a nested missing optional value' do
get '/disallow_blank_optional_param_in_an_optional_group', user: { age: '29' }
expect(last_response.status).to eq(200)
end
it 'refuses a blank existing value in an existing scope' do
get '/disallow_blank_optional_param_in_an_optional_group', user: { age: '29', name: '' }
expect(last_response.status).to eq(400)
end
end
end
context 'in a required group' do
context 'as a required param' do
it 'refuses a blank value in a required existing group' do
get '/disallow_blank_required_param_in_a_required_group', user: { name: '' }
expect(last_response.status).to eq(400)
end
it 'refuses a string value in a required hash group' do
get '/disallow_string_value_in_a_required_hash_group', user: ''
expect(last_response.status).to eq(400)
end
end
context 'as an optional param' do
it 'accepts a nested missing value' do
get '/disallow_blank_optional_param_in_a_required_group', user: { age: '29' }
expect(last_response.status).to eq(200)
end
it 'refuses a blank existing value in an existing scope' do
get '/disallow_blank_optional_param_in_a_required_group', user: { age: '29', name: '' }
expect(last_response.status).to eq(400)
end
it 'refuses a string value in an optional hash group' do
get '/disallow_string_value_in_an_optional_hash_group', user: ''
expect(last_response.status).to eq(400)
end
end
end
end
grape-0.13.0/spec/grape/validations/validators/regexp_spec.rb 0000644 0000041 0000041 00000001516 12563420522 024273 0 ustar www-data www-data require 'spec_helper'
describe Grape::Validations::RegexpValidator do
module ValidationsSpec
module RegexpValidatorSpec
class API < Grape::API
default_format :json
params do
requires :name, regexp: /^[a-z]+$/
end
get do
end
end
end
end
def app
ValidationsSpec::RegexpValidatorSpec::API
end
context 'invalid input' do
it 'refuses inapppopriate' do
get '/', name: 'invalid name'
expect(last_response.status).to eq(400)
end
it 'refuses empty' do
get '/', name: ''
expect(last_response.status).to eq(400)
end
end
it 'accepts nil' do
get '/', name: nil
expect(last_response.status).to eq(200)
end
it 'accepts valid input' do
get '/', name: 'bob'
expect(last_response.status).to eq(200)
end
end
grape-0.13.0/spec/grape/validations/validators/mutual_exclusion_spec.rb 0000644 0000041 0000041 00000003456 12563420522 026406 0 ustar www-data www-data require 'spec_helper'
describe Grape::Validations::MutualExclusionValidator do
describe '#validate!' do
let(:scope) do
Struct.new(:opts) do
def params(arg)
arg
end
end
end
let(:mutually_exclusive_params) { [:beer, :wine, :grapefruit] }
let(:validator) { described_class.new(mutually_exclusive_params, {}, false, scope.new) }
context 'when all mutually exclusive params are present' do
let(:params) { { beer: true, wine: true, grapefruit: true } }
it 'raises a validation exception' do
expect do
validator.validate! params
end.to raise_error(Grape::Exceptions::Validation)
end
context 'mixed with other params' do
let(:mixed_params) { params.merge!(other: true, andanother: true) }
it 'still raises a validation exception' do
expect do
validator.validate! mixed_params
end.to raise_error(Grape::Exceptions::Validation)
end
end
end
context 'when a subset of mutually exclusive params are present' do
let(:params) { { beer: true, grapefruit: true } }
it 'raises a validation exception' do
expect do
validator.validate! params
end.to raise_error(Grape::Exceptions::Validation)
end
end
context 'when params keys come as strings' do
let(:params) { { 'beer' => true, 'grapefruit' => true } }
it 'raises a validation exception' do
expect do
validator.validate! params
end.to raise_error(Grape::Exceptions::Validation)
end
end
context 'when no mutually exclusive params are present' do
let(:params) { { beer: true, somethingelse: true } }
it 'params' do
expect(validator.validate!(params)).to eql params
end
end
end
end
grape-0.13.0/spec/grape/validations/validators/all_or_none_spec.rb 0000644 0000041 0000041 00000003356 12563420522 025274 0 ustar www-data www-data require 'spec_helper'
describe Grape::Validations::AllOrNoneOfValidator do
describe '#validate!' do
let(:scope) do
Struct.new(:opts) do
def params(arg)
arg
end
def required?; end
end
end
let(:all_or_none_params) { [:beer, :wine, :grapefruit] }
let(:validator) { described_class.new(all_or_none_params, {}, false, scope.new) }
context 'when all restricted params are present' do
let(:params) { { beer: true, wine: true, grapefruit: true } }
it 'does not raise a validation exception' do
expect(validator.validate!(params)).to eql params
end
context 'mixed with other params' do
let(:mixed_params) { params.merge!(other: true, andanother: true) }
it 'does not raise a validation exception' do
expect(validator.validate!(mixed_params)).to eql mixed_params
end
end
end
context 'when none of the restricted params is selected' do
let(:params) { { somethingelse: true } }
it 'does not raise a validation exception' do
expect(validator.validate!(params)).to eql params
end
end
context 'when only a subset of restricted params are present' do
let(:params) { { beer: true, grapefruit: true } }
it 'raises a validation exception' do
expect do
validator.validate! params
end.to raise_error(Grape::Exceptions::Validation)
end
context 'mixed with other params' do
let(:mixed_params) { params.merge!(other: true, andanother: true) }
it 'raise a validation exception' do
expect do
validator.validate! params
end.to raise_error(Grape::Exceptions::Validation)
end
end
end
end
end
grape-0.13.0/spec/grape/dsl/ 0000755 0000041 0000041 00000000000 12563420522 015534 5 ustar www-data www-data grape-0.13.0/spec/grape/dsl/request_response_spec.rb 0000644 0000041 0000041 00000016124 12563420522 022505 0 ustar www-data www-data require 'spec_helper'
module Grape
module DSL
module RequestResponseSpec
class Dummy
include Grape::DSL::RequestResponse
def self.set(key, value)
settings[key.to_sym] = value
end
def self.imbue(key, value)
settings.imbue(key, value)
end
end
end
describe RequestResponse do
subject { Class.new(RequestResponseSpec::Dummy) }
let(:c_type) { 'application/json' }
let(:format) { 'txt' }
describe '.default_format' do
it 'sets the default format' do
expect(subject).to receive(:namespace_inheritable).with(:default_format, :format)
subject.default_format :format
end
it 'returns the format without paramter' do
subject.default_format :format
expect(subject.default_format).to eq :format
end
end
describe '.format' do
it 'sets a new format' do
expect(subject).to receive(:namespace_inheritable).with(:format, format.to_sym)
expect(subject).to receive(:namespace_inheritable).with(:default_error_formatter, Grape::ErrorFormatter::Txt)
subject.format format
end
end
describe '.formatter' do
it 'sets the formatter for a content type' do
expect(subject).to receive(:namespace_stackable).with(:formatters, c_type.to_sym => :formatter)
subject.formatter c_type, :formatter
end
end
describe '.parser' do
it 'sets a parser for a content type' do
expect(subject).to receive(:namespace_stackable).with(:parsers, c_type.to_sym => :parser)
subject.parser c_type, :parser
end
end
describe '.default_error_formatter' do
it 'sets a new error formatter' do
expect(subject).to receive(:namespace_inheritable).with(:default_error_formatter, Grape::ErrorFormatter::Json)
subject.default_error_formatter :json
end
end
describe '.error_formatter' do
it 'sets a error_formatter' do
format = 'txt'
expect(subject).to receive(:namespace_stackable).with(:error_formatters, format.to_sym => :error_formatter)
subject.error_formatter format, :error_formatter
end
it 'understands syntactic sugar' do
expect(subject).to receive(:namespace_stackable).with(:error_formatters, format.to_sym => :error_formatter)
subject.error_formatter format, with: :error_formatter
end
end
describe '.content_type' do
it 'sets a content type for a format' do
expect(subject).to receive(:namespace_stackable).with(:content_types, format.to_sym => c_type)
subject.content_type format, c_type
end
end
describe '.content_types' do
it 'returns all content types' do
expect(subject.content_types).to eq(xml: 'application/xml',
serializable_hash: 'application/json',
json: 'application/json',
txt: 'text/plain',
binary: 'application/octet-stream')
end
end
describe '.default_error_status' do
it 'sets a default error status' do
expect(subject).to receive(:namespace_inheritable).with(:default_error_status, 500)
subject.default_error_status 500
end
end
describe '.rescue_from' do
describe ':all' do
it 'sets rescue all to true' do
expect(subject).to receive(:namespace_inheritable).with(:rescue_all, true)
expect(subject).to receive(:namespace_inheritable).with(:all_rescue_handler, nil)
subject.rescue_from :all
end
it 'sets given proc as rescue handler' do
rescue_handler_proc = proc {}
expect(subject).to receive(:namespace_inheritable).with(:rescue_all, true)
expect(subject).to receive(:namespace_inheritable).with(:all_rescue_handler, rescue_handler_proc)
subject.rescue_from :all, rescue_handler_proc
end
it 'sets given block as rescue handler' do
rescue_handler_proc = proc {}
expect(subject).to receive(:namespace_inheritable).with(:rescue_all, true)
expect(subject).to receive(:namespace_inheritable).with(:all_rescue_handler, rescue_handler_proc)
subject.rescue_from :all, &rescue_handler_proc
end
it 'sets a rescue handler declared through :with option' do
expect(subject).to receive(:namespace_inheritable).with(:rescue_all, true)
expect(subject).to receive(:namespace_inheritable).with(:all_rescue_handler, an_instance_of(Proc))
subject.rescue_from :all, with: 'ExampleHandler'
end
end
describe 'list of exceptions is passed' do
it 'sets hash of exceptions as rescue handlers' do
expect(subject).to receive(:namespace_stackable).with(:rescue_handlers, StandardError => nil)
expect(subject).to receive(:namespace_stackable).with(:rescue_options, {})
subject.rescue_from StandardError
end
it 'rescues only base handlers if rescue_subclasses: false option is passed' do
expect(subject).to receive(:namespace_stackable).with(:base_only_rescue_handlers, StandardError => nil)
expect(subject).to receive(:namespace_stackable).with(:rescue_options, rescue_subclasses: false)
subject.rescue_from StandardError, rescue_subclasses: false
end
it 'sets given proc as rescue handler for each key in hash' do
rescue_handler_proc = proc {}
expect(subject).to receive(:namespace_stackable).with(:rescue_handlers, StandardError => rescue_handler_proc)
expect(subject).to receive(:namespace_stackable).with(:rescue_options, {})
subject.rescue_from StandardError, rescue_handler_proc
end
it 'sets given block as rescue handler for each key in hash' do
rescue_handler_proc = proc {}
expect(subject).to receive(:namespace_stackable).with(:rescue_handlers, StandardError => rescue_handler_proc)
expect(subject).to receive(:namespace_stackable).with(:rescue_options, {})
subject.rescue_from StandardError, &rescue_handler_proc
end
it 'sets a rescue handler declared through :with option for each key in hash' do
expect(subject).to receive(:namespace_stackable).with(:rescue_handlers, StandardError => an_instance_of(Proc))
expect(subject).to receive(:namespace_stackable).with(:rescue_options, with: 'ExampleHandler')
subject.rescue_from StandardError, with: 'ExampleHandler'
end
end
end
describe '.represent' do
it 'sets a presenter for a class' do
presenter = Class.new
expect(subject).to receive(:namespace_stackable).with(:representations, ThisClass: presenter)
subject.represent :ThisClass, with: presenter
end
end
end
end
end
grape-0.13.0/spec/grape/dsl/parameters_spec.rb 0000644 0000041 0000041 00000012056 12563420522 021242 0 ustar www-data www-data require 'spec_helper'
module Grape
module DSL
module ParametersSpec
class Dummy
include Grape::DSL::Parameters
attr_accessor :api, :element, :parent
def validate_attributes(*args)
@validate_attributes = *args
end
# rubocop:disable TrivialAccessors
def validate_attributes_reader
@validate_attributes
end
# rubocop:enable TrivialAccessors
def push_declared_params(*args)
@push_declared_params = args
end
# rubocop:disable TrivialAccessors
def push_declared_params_reader
@push_declared_params
end
# rubocop:enable TrivialAccessors
def validates(*args)
@validates = *args
end
# rubocop:disable TrivialAccessors
def validates_reader
@validates
end
# rubocop:enable TrivialAccessors
end
end
describe Parameters do
subject { ParametersSpec::Dummy.new }
describe '#use' do
before do
allow_message_expectations_on_nil
allow(subject.api).to receive(:namespace_stackable).with(:named_params)
end
let(:options) { { option: 'value' } }
let(:named_params) { { params_group: proc {} } }
it 'calls processes associated with named params' do
allow(Grape::DSL::Configuration).to receive(:stacked_hash_to_hash).and_return(named_params)
expect(subject).to receive(:instance_exec).with(options).and_yield
subject.use :params_group, options
end
it 'raises error when non-existent named param is called' do
allow(Grape::DSL::Configuration).to receive(:stacked_hash_to_hash).and_return({})
expect { subject.use :params_group }.to raise_error('Params :params_group not found!')
end
end
describe '#use_scope' do
it 'is alias to #use' do
expect(subject.method(:use_scope)).to eq subject.method(:use)
end
end
describe '#includes' do
it 'is alias to #use' do
expect(subject.method(:includes)).to eq subject.method(:use)
end
end
describe '#requires' do
it 'adds a required parameter' do
subject.requires :id, type: Integer, desc: 'Identity.'
expect(subject.validate_attributes_reader).to eq([[:id], { type: Integer, desc: 'Identity.', presence: true }])
expect(subject.push_declared_params_reader).to eq([[:id]])
end
end
describe '#optional' do
it 'adds an optional parameter' do
subject.optional :id, type: Integer, desc: 'Identity.'
expect(subject.validate_attributes_reader).to eq([[:id], { type: Integer, desc: 'Identity.' }])
expect(subject.push_declared_params_reader).to eq([[:id]])
end
end
describe '#mutually_exclusive' do
it 'adds an mutally exclusive parameter validation' do
subject.mutually_exclusive :media, :audio
expect(subject.validates_reader).to eq([[:media, :audio], { mutual_exclusion: true }])
end
end
describe '#exactly_one_of' do
it 'adds an exactly of one parameter validation' do
subject.exactly_one_of :media, :audio
expect(subject.validates_reader).to eq([[:media, :audio], { exactly_one_of: true }])
end
end
describe '#at_least_one_of' do
it 'adds an at least one of parameter validation' do
subject.at_least_one_of :media, :audio
expect(subject.validates_reader).to eq([[:media, :audio], { at_least_one_of: true }])
end
end
describe '#all_or_none_of' do
it 'adds an all or none of parameter validation' do
subject.all_or_none_of :media, :audio
expect(subject.validates_reader).to eq([[:media, :audio], { all_or_none_of: true }])
end
end
describe '#group' do
it 'is alias to #requires' do
expect(subject.method(:group)).to eq subject.method(:requires)
end
end
describe '#params' do
it 'inherits params from parent' do
parent_params = { foo: 'bar' }
subject.parent = Object.new
allow(subject.parent).to receive(:params).and_return(parent_params)
expect(subject.params({})).to eq parent_params
end
describe 'when params argument is an array of hashes' do
it 'returns values of each hash for @element key' do
subject.element = :foo
expect(subject.params([{ foo: 'bar' }, { foo: 'baz' }])).to eq(%w(bar baz))
end
end
describe 'when params argument is a hash' do
it 'returns value for @element key' do
subject.element = :foo
expect(subject.params(foo: 'bar')).to eq('bar')
end
end
describe 'when params argument is not a array or a hash' do
it 'returns empty hash' do
subject.element = Object.new
expect(subject.params(Object.new)).to eq({})
end
end
end
end
end
end
grape-0.13.0/spec/grape/dsl/routing_spec.rb 0000644 0000041 0000041 00000020232 12563420522 020561 0 ustar www-data www-data require 'spec_helper'
module Grape
module DSL
module RoutingSpec
class Dummy
include Grape::DSL::Routing
end
end
describe Routing do
subject { Class.new(RoutingSpec::Dummy) }
let(:proc) { ->() {} }
let(:options) { { a: :b } }
let(:path) { '/dummy' }
describe '.version' do
it 'sets a version for route' do
version = 'v1'
expect(subject).to receive(:namespace_inheritable).with(:version, [version])
expect(subject).to receive(:namespace_inheritable).with(:version_options, using: :path)
expect(subject.version(version)).to eq(version)
end
end
describe '.prefix' do
it 'sets a prefix for route' do
prefix = '/api'
expect(subject).to receive(:namespace_inheritable).with(:root_prefix, prefix)
subject.prefix prefix
end
end
describe '.do_not_route_head!' do
it 'sets do not route head option' do
expect(subject).to receive(:namespace_inheritable).with(:do_not_route_head, true)
subject.do_not_route_head!
end
end
describe '.do_not_route_options!' do
it 'sets do not route options option' do
expect(subject).to receive(:namespace_inheritable).with(:do_not_route_options, true)
subject.do_not_route_options!
end
end
describe '.mount' do
it 'mounts on a nested path' do
subject = Class.new(Grape::API)
app1 = Class.new(Grape::API)
app2 = Class.new(Grape::API)
app2.get '/nice' do
'play'
end
subject.mount app1 => '/app1'
app1.mount app2 => '/app2'
expect(subject.inheritable_setting.to_hash[:namespace]).to eq({})
expect(subject.inheritable_setting.to_hash[:namespace_inheritable]).to eq({})
expect(app1.inheritable_setting.to_hash[:namespace_stackable]).to eq(mount_path: ['/app1'])
expect(app2.inheritable_setting.to_hash[:namespace_stackable]).to eq(mount_path: ['/app1', '/app2'])
end
end
describe '.route' do
before do
allow(subject).to receive(:endpoints).and_return([])
allow(subject).to receive(:route_end)
allow(subject).to receive(:reset_validations!)
end
it 'marks end of the route' do
expect(subject).to receive(:route_end)
subject.route(:any)
end
it 'resets validations' do
expect(subject).to receive(:reset_validations!)
subject.route(:any)
end
it 'defines a new endpoint' do
expect { subject.route(:any) }
.to change { subject.endpoints.count }.from(0).to(1)
end
it 'does not duplicate identical endpoints' do
subject.route(:any)
expect { subject.route(:any) }
.to_not change(subject.endpoints, :count)
end
it 'generates correct endpoint options' do
allow(subject).to receive(:route_setting).with(:description).and_return(fiz: 'baz')
allow(Grape::DSL::Configuration).to receive(:stacked_hash_to_hash).and_return(nuz: 'naz')
expect(Grape::Endpoint).to receive(:new) do |_inheritable_setting, endpoint_options|
expect(endpoint_options[:method]).to eq :get
expect(endpoint_options[:path]).to eq '/foo'
expect(endpoint_options[:for]).to eq subject
expect(endpoint_options[:route_options]).to eq(foo: 'bar', fiz: 'baz', params: { nuz: 'naz' })
end.and_yield
subject.route(:get, '/foo', { foo: 'bar' }, &proc {})
end
end
describe '.get' do
it 'delegates to .route' do
expect(subject).to receive(:route).with('GET', path, options)
subject.get path, options, &proc
end
end
describe '.post' do
it 'delegates to .route' do
expect(subject).to receive(:route).with('POST', path, options)
subject.post path, options, &proc
end
end
describe '.put' do
it 'delegates to .route' do
expect(subject).to receive(:route).with('PUT', path, options)
subject.put path, options, &proc
end
end
describe '.head' do
it 'delegates to .route' do
expect(subject).to receive(:route).with('HEAD', path, options)
subject.head path, options, &proc
end
end
describe '.delete' do
it 'delegates to .route' do
expect(subject).to receive(:route).with('DELETE', path, options)
subject.delete path, options, &proc
end
end
describe '.options' do
it 'delegates to .route' do
expect(subject).to receive(:route).with('OPTIONS', path, options)
subject.options path, options, &proc
end
end
describe '.patch' do
it 'delegates to .route' do
expect(subject).to receive(:route).with('PATCH', path, options)
subject.patch path, options, &proc
end
end
describe '.namespace' do
let(:new_namespace) { Object.new }
it 'creates a new namespace with given name and options' do
expect(subject).to receive(:within_namespace).and_yield
expect(subject).to receive(:nest).and_yield
expect(Namespace).to receive(:new).with(:foo, foo: 'bar').and_return(new_namespace)
expect(subject).to receive(:namespace_stackable).with(:namespace, new_namespace)
subject.namespace :foo, foo: 'bar', &proc {}
end
it 'calls #joined_space_path on Namespace' do
result_of_namspace_stackable = Object.new
allow(subject).to receive(:namespace_stackable).and_return(result_of_namspace_stackable)
expect(Namespace).to receive(:joined_space_path).with(result_of_namspace_stackable)
subject.namespace
end
end
describe '.group' do
it 'is alias to #namespace' do
expect(subject.method(:group)).to eq subject.method(:namespace)
end
end
describe '.resource' do
it 'is alias to #namespace' do
expect(subject.method(:resource)).to eq subject.method(:namespace)
end
end
describe '.resources' do
it 'is alias to #namespace' do
expect(subject.method(:resources)).to eq subject.method(:namespace)
end
end
describe '.segment' do
it 'is alias to #namespace' do
expect(subject.method(:segment)).to eq subject.method(:namespace)
end
end
describe '.routes' do
let(:routes) { Object.new }
it 'returns value received from #prepare_routes' do
expect(subject).to receive(:prepare_routes).and_return(routes)
expect(subject.routes).to eq routes
end
context 'when #routes was already called once' do
before do
allow(subject).to receive(:prepare_routes).and_return(routes)
subject.routes
end
it 'it does not call prepare_routes again' do
expect(subject).to_not receive(:prepare_routes)
expect(subject.routes).to eq routes
end
end
end
describe '.route_param' do
it 'calls #namespace with given params' do
expect(subject).to receive(:namespace).with(':foo', {}).and_yield
subject.route_param('foo', {}, &proc {})
end
let(:regex) { /(.*)/ }
let!(:options) { { requirements: regex } }
it 'nests requirements option under param name' do
expect(subject).to receive(:namespace) do |_param, options|
expect(options[:requirements][:foo]).to eq regex
end
subject.route_param('foo', options, &proc {})
end
it 'does not modify options parameter' do
allow(subject).to receive(:namespace)
expect { subject.route_param('foo', options, &proc {}) }
.to_not change { options }
end
end
describe '.versions' do
it 'returns last defined version' do
subject.version 'v1'
subject.version 'v2'
expect(subject.version).to eq('v2')
end
end
end
end
end
grape-0.13.0/spec/grape/dsl/configuration_spec.rb 0000644 0000041 0000041 00000004464 12563420522 021752 0 ustar www-data www-data require 'spec_helper'
module Grape
module DSL
module ConfigurationSpec
class Dummy
include Grape::DSL::Configuration
end
end
describe Configuration do
subject { Class.new(ConfigurationSpec::Dummy) }
let(:logger) { double(:logger) }
describe '.logger' do
it 'sets a logger' do
subject.logger logger
expect(subject.logger).to eq logger
end
it 'returns a logger' do
expect(subject.logger logger).to eq logger
end
end
describe '.desc' do
it 'sets a description' do
desc_text = 'The description'
options = { message: 'none' }
subject.desc desc_text, options
expect(subject.namespace_setting(:description)).to eq(options.merge(description: desc_text))
expect(subject.route_setting(:description)).to eq(options.merge(description: desc_text))
end
it 'can be set with a block' do
expected_options = {
description: 'The description',
detail: 'more details',
params: { first: :param },
entity: Object,
http_codes: [[401, 'Unauthorized', 'Entities::Error']],
named: 'My named route',
headers: [XAuthToken: {
description: 'Valdates your identity',
required: true
},
XOptionalHeader: {
description: 'Not really needed',
required: false
}
]
}
subject.desc 'The description' do
detail 'more details'
params(first: :param)
success Object
failure [[401, 'Unauthorized', 'Entities::Error']]
named 'My named route'
headers [XAuthToken: {
description: 'Valdates your identity',
required: true
},
XOptionalHeader: {
description: 'Not really needed',
required: false
}
]
end
expect(subject.namespace_setting(:description)).to eq(expected_options)
expect(subject.route_setting(:description)).to eq(expected_options)
end
end
end
end
end
grape-0.13.0/spec/grape/dsl/helpers_spec.rb 0000644 0000041 0000041 00000002456 12563420522 020544 0 ustar www-data www-data require 'spec_helper'
module Grape
module DSL
module HelpersSpec
class Dummy
include Grape::DSL::Helpers
# rubocop:disable TrivialAccessors
def self.mod
namespace_stackable(:helpers).first
end
# rubocop:enable TrivialAccessors
end
end
describe Helpers do
subject { Class.new(HelpersSpec::Dummy) }
let(:proc) do
lambda do |*|
def test
:test
end
end
end
describe '.helpers' do
it 'adds a module with the given block' do
expect(subject).to receive(:namespace_stackable).with(:helpers, kind_of(Grape::DSL::Helpers::BaseHelper)).and_call_original
expect(subject).to receive(:namespace_stackable).with(:helpers).and_call_original
subject.helpers(&proc)
expect(subject.mod.instance_methods).to include(:test)
end
it 'uses provided modules' do
mod = Module.new
expect(subject).to receive(:namespace_stackable).with(:helpers, kind_of(Grape::DSL::Helpers::BaseHelper)).and_call_original
expect(subject).to receive(:namespace_stackable).with(:helpers).and_call_original
subject.helpers(mod, &proc)
expect(subject.mod).to eq mod
end
end
end
end
end
grape-0.13.0/spec/grape/dsl/validations_spec.rb 0000644 0000041 0000041 00000003750 12563420522 021415 0 ustar www-data www-data require 'spec_helper'
module Grape
module DSL
module ValidationsSpec
class Dummy
include Grape::DSL::Validations
end
end
describe Validations do
subject { ValidationsSpec::Dummy }
describe '.reset_validations!' do
before do
subject.namespace_stackable :declared_params, ['dummy']
subject.namespace_stackable :validations, ['dummy']
subject.namespace_stackable :params, ['dummy']
subject.route_setting :description, description: 'lol', params: ['dummy']
subject.reset_validations!
end
after do
subject.unset_route_setting :description
end
it 'resets declared params' do
expect(subject.namespace_stackable(:declared_params)).to eq []
end
it 'resets validations' do
expect(subject.namespace_stackable(:validations)).to eq []
end
it 'resets params' do
expect(subject.namespace_stackable(:params)).to eq []
end
it 'resets documentation params' do
expect(subject.route_setting(:description)[:params]).to be_nil
end
it 'does not reset documentation description' do
expect(subject.route_setting(:description)[:description]).to eq 'lol'
end
end
describe '.params' do
it 'returns a ParamsScope' do
expect(subject.params).to be_a Grape::Validations::ParamsScope
end
it 'evaluates block' do
expect { subject.params { fail 'foo' } }.to raise_error RuntimeError, 'foo'
end
end
describe '.document_attribute' do
before do
subject.document_attribute([full_name: 'xxx'], foo: 'bar')
end
it 'creates a param documentation' do
expect(subject.namespace_stackable(:params)).to eq(['xxx' => { foo: 'bar' }])
expect(subject.route_setting(:description)).to eq(params: { 'xxx' => { foo: 'bar' } })
end
end
end
end
end
grape-0.13.0/spec/grape/dsl/inside_route_spec.rb 0000644 0000041 0000041 00000022652 12563420522 021573 0 ustar www-data www-data require 'spec_helper'
module Grape
module DSL
module InsideRouteSpec
class Dummy
include Grape::DSL::InsideRoute
attr_reader :env, :request, :new_settings
def initialize
@env = {}
@header = {}
@new_settings = { namespace_inheritable: {}, namespace_stackable: {} }
end
end
end
describe Endpoint do
subject { InsideRouteSpec::Dummy.new }
describe '#version' do
it 'defaults to nil' do
expect(subject.version).to be nil
end
it 'returns env[api.version]' do
subject.env['api.version'] = 'dummy'
expect(subject.version).to eq 'dummy'
end
end
describe '#error!' do
it 'throws :error' do
expect { subject.error! 'Not Found', 404 }.to throw_symbol(:error)
end
describe 'thrown' do
before do
catch(:error) { subject.error! 'Not Found', 404 }
end
it 'sets status' do
expect(subject.status).to eq 404
end
end
describe 'default_error_status' do
before do
subject.namespace_inheritable(:default_error_status, 500)
catch(:error) { subject.error! 'Unknown' }
end
it 'sets status to default_error_status' do
expect(subject.status).to eq 500
end
end
# self.status(status || settings[:default_error_status])
# throw :error, message: message, status: self.status, headers: headers
end
describe '#redirect' do
describe 'default' do
before do
subject.redirect '/'
end
it 'sets status to 302' do
expect(subject.status).to eq 302
end
it 'sets location header' do
expect(subject.header['Location']).to eq '/'
end
end
describe 'permanent' do
before do
subject.redirect '/', permanent: true
end
it 'sets status to 301' do
expect(subject.status).to eq 301
end
it 'sets location header' do
expect(subject.header['Location']).to eq '/'
end
end
end
describe '#status' do
%w(GET PUT DELETE OPTIONS).each do |method|
it 'defaults to 200 on GET' do
request = Grape::Request.new(Rack::MockRequest.env_for('/', method: method))
expect(subject).to receive(:request).and_return(request)
expect(subject.status).to eq 200
end
end
it 'defaults to 201 on POST' do
request = Grape::Request.new(Rack::MockRequest.env_for('/', method: 'POST'))
expect(subject).to receive(:request).and_return(request)
expect(subject.status).to eq 201
end
it 'returns status set' do
subject.status 501
expect(subject.status).to eq 501
end
it 'accepts symbol for status' do
subject.status :see_other
expect(subject.status).to eq 303
end
it 'raises error if unknow symbol is passed' do
expect { subject.status :foo_bar }
.to raise_error(ArgumentError, 'Status code :foo_bar is invalid.')
end
it 'accepts unknown Fixnum status codes' do
expect { subject.status 210 }.to_not raise_error
end
it 'raises error if status is not a fixnum or symbol' do
expect { subject.status Object.new }
.to raise_error(ArgumentError, 'Status code must be Fixnum or Symbol.')
end
end
describe '#header' do
describe 'set' do
before do
subject.header 'Name', 'Value'
end
it 'returns value' do
expect(subject.header['Name']).to eq 'Value'
expect(subject.header('Name')).to eq 'Value'
end
end
it 'returns nil' do
expect(subject.header['Name']).to be nil
expect(subject.header('Name')).to be nil
end
end
describe '#content_type' do
describe 'set' do
before do
subject.content_type 'text/plain'
end
it 'returns value' do
expect(subject.content_type).to eq 'text/plain'
end
end
it 'returns default' do
expect(subject.content_type).to be nil
end
end
describe '#cookies' do
it 'returns an instance of Cookies' do
expect(subject.cookies).to be_a Grape::Cookies
end
end
describe '#body' do
describe 'set' do
before do
subject.body 'body'
end
it 'returns value' do
expect(subject.body).to eq 'body'
end
end
describe 'false' do
before do
subject.body false
end
it 'sets status to 204' do
expect(subject.body).to eq ''
expect(subject.status).to eq 204
end
end
it 'returns default' do
expect(subject.body).to be nil
end
end
describe '#file' do
describe 'set' do
before do
subject.file 'file'
end
it 'returns value wrapped in FileResponse' do
expect(subject.file).to eq Grape::Util::FileResponse.new('file')
end
end
it 'returns default' do
expect(subject.file).to be nil
end
end
describe '#stream' do
describe 'set' do
before do
subject.header 'Cache-Control', 'cache'
subject.header 'Content-Length', 123
subject.header 'Transfer-Encoding', 'base64'
subject.stream 'file'
end
it 'returns value wrapped in FileResponse' do
expect(subject.stream).to eq Grape::Util::FileResponse.new('file')
end
it 'also sets result of file to value wrapped in FileResponse' do
expect(subject.file).to eq Grape::Util::FileResponse.new('file')
end
it 'sets Cache-Control header to no-cache' do
expect(subject.header['Cache-Control']).to eq 'no-cache'
end
it 'sets Content-Length header to nil' do
expect(subject.header['Content-Length']).to eq nil
end
it 'sets Transfer-Encoding header to nil' do
expect(subject.header['Transfer-Encoding']).to eq nil
end
end
it 'returns default' do
expect(subject.file).to be nil
end
end
describe '#route' do
before do
subject.env['rack.routing_args'] = {}
subject.env['rack.routing_args'][:route_info] = 'dummy'
end
it 'returns route_info' do
expect(subject.route).to eq 'dummy'
end
end
describe '#present' do
# see entity_spec.rb for entity representation spec coverage
describe 'dummy' do
before do
subject.present 'dummy'
end
it 'presents dummy object' do
expect(subject.body).to eq 'dummy'
end
end
describe 'with' do
describe 'entity' do
let(:entity_mock) do
entity_mock = Object.new
allow(entity_mock).to receive(:represent).and_return('dummy')
entity_mock
end
describe 'instance' do
before do
subject.present 'dummy', with: entity_mock
end
it 'presents dummy object' do
expect(subject.body).to eq 'dummy'
end
end
end
end
describe 'multiple entities' do
let(:entity_mock1) do
entity_mock1 = Object.new
allow(entity_mock1).to receive(:represent).and_return(dummy1: 'dummy1')
entity_mock1
end
let(:entity_mock2) do
entity_mock2 = Object.new
allow(entity_mock2).to receive(:represent).and_return(dummy2: 'dummy2')
entity_mock2
end
describe 'instance' do
before do
subject.present 'dummy1', with: entity_mock1
subject.present 'dummy2', with: entity_mock2
end
it 'presents both dummy objects' do
expect(subject.body[:dummy1]).to eq 'dummy1'
expect(subject.body[:dummy2]).to eq 'dummy2'
end
end
end
describe 'non mergeable entity' do
let(:entity_mock1) do
entity_mock1 = Object.new
allow(entity_mock1).to receive(:represent).and_return(dummy1: 'dummy1')
entity_mock1
end
let(:entity_mock2) do
entity_mock2 = Object.new
allow(entity_mock2).to receive(:represent).and_return('not a hash')
entity_mock2
end
describe 'instance' do
it 'fails' do
subject.present 'dummy1', with: entity_mock1
expect do
subject.present 'dummy2', with: entity_mock2
end.to raise_error ArgumentError, 'Representation of type String cannot be merged.'
end
end
end
end
describe '#declared' do
# see endpoint_spec.rb#declared for spec coverage
it 'returns an empty hash' do
expect(subject.declared({})).to eq({})
end
end
end
end
end
grape-0.13.0/spec/grape/dsl/middleware_spec.rb 0000644 0000041 0000041 00000001350 12563420522 021207 0 ustar www-data www-data require 'spec_helper'
module Grape
module DSL
module MiddlewareSpec
class Dummy
include Grape::DSL::Middleware
end
end
describe Middleware do
subject { Class.new(MiddlewareSpec::Dummy) }
let(:proc) { ->() {} }
describe '.use' do
it 'adds a middleware' do
expect(subject).to receive(:namespace_stackable).with(:middleware, [:my_middleware, :arg1, proc])
subject.use :my_middleware, :arg1, &proc
end
end
describe '.middleware' do
it 'returns the middleware stack' do
subject.use :my_middleware, :arg1, &proc
expect(subject.middleware).to eq [[:my_middleware, :arg1, proc]]
end
end
end
end
end
grape-0.13.0/spec/grape/dsl/callbacks_spec.rb 0000644 0000041 0000041 00000002220 12563420522 021006 0 ustar www-data www-data require 'spec_helper'
module Grape
module DSL
module CallbacksSpec
class Dummy
include Grape::DSL::Callbacks
end
end
describe Callbacks do
subject { Class.new(CallbacksSpec::Dummy) }
let(:proc) { ->() {} }
describe '.before' do
it 'adds a block to "before"' do
expect(subject).to receive(:namespace_stackable).with(:befores, proc)
subject.before(&proc)
end
end
describe '.before_validation' do
it 'adds a block to "before_validation"' do
expect(subject).to receive(:namespace_stackable).with(:before_validations, proc)
subject.before_validation(&proc)
end
end
describe '.after_validation' do
it 'adds a block to "after_validation"' do
expect(subject).to receive(:namespace_stackable).with(:after_validations, proc)
subject.after_validation(&proc)
end
end
describe '.after' do
it 'adds a block to "after"' do
expect(subject).to receive(:namespace_stackable).with(:afters, proc)
subject.after(&proc)
end
end
end
end
end
grape-0.13.0/spec/grape/dsl/settings_spec.rb 0000644 0000041 0000041 00000015474 12563420522 020746 0 ustar www-data www-data require 'spec_helper'
module Grape
module DSL
module SettingsSpec
class Dummy
include Grape::DSL::Settings
def reset_validations!; end
end
end
describe Settings do
subject { SettingsSpec::Dummy.new }
describe '#unset' do
it 'deletes a key from settings' do
subject.namespace_setting :dummy, 1
expect(subject.inheritable_setting.namespace.keys).to include(:dummy)
subject.unset :namespace, :dummy
expect(subject.inheritable_setting.namespace.keys).not_to include(:dummy)
end
end
describe '#get_or_set' do
it 'sets a values' do
subject.get_or_set :namespace, :dummy, 1
expect(subject.namespace_setting(:dummy)).to eq 1
end
it 'returns a value when nil is new value is provided' do
subject.get_or_set :namespace, :dummy, 1
expect(subject.get_or_set(:namespace, :dummy, nil)).to eq 1
end
end
describe '#global_setting' do
it 'delegates to get_or_set' do
expect(subject).to receive(:get_or_set).with(:global, :dummy, 1)
subject.global_setting(:dummy, 1)
end
end
describe '#route_setting' do
it 'delegates to get_or_set' do
expect(subject).to receive(:get_or_set).with(:route, :dummy, 1)
subject.route_setting(:dummy, 1)
end
it 'sets a value until the next route' do
subject.route_setting :some_thing, :foo_bar
expect(subject.route_setting(:some_thing)).to eq :foo_bar
subject.route_end
expect(subject.route_setting(:some_thing)).to be_nil
end
end
describe '#namespace_setting' do
it 'delegates to get_or_set' do
expect(subject).to receive(:get_or_set).with(:namespace, :dummy, 1)
subject.namespace_setting(:dummy, 1)
end
it 'sets a value until the end of a namespace' do
subject.namespace_start
subject.namespace_setting :some_thing, :foo_bar
expect(subject.namespace_setting(:some_thing)).to eq :foo_bar
subject.namespace_end
expect(subject.namespace_setting(:some_thing)).to be_nil
end
it 'resets values after leaving nested namespaces' do
subject.namespace_start
subject.namespace_setting :some_thing, :foo_bar
expect(subject.namespace_setting(:some_thing)).to eq :foo_bar
subject.namespace_start
expect(subject.namespace_setting(:some_thing)).to be_nil
subject.namespace_end
expect(subject.namespace_setting(:some_thing)).to eq :foo_bar
subject.namespace_end
expect(subject.namespace_setting(:some_thing)).to be_nil
end
end
describe '#namespace_inheritable' do
it 'delegates to get_or_set' do
expect(subject).to receive(:get_or_set).with(:namespace_inheritable, :dummy, 1)
subject.namespace_inheritable(:dummy, 1)
end
it 'inherits values from surrounding namespace' do
subject.namespace_start
subject.namespace_inheritable(:some_thing, :foo_bar)
expect(subject.namespace_inheritable(:some_thing)).to eq :foo_bar
subject.namespace_start
expect(subject.namespace_inheritable(:some_thing)).to eq :foo_bar
subject.namespace_inheritable(:some_thing, :foo_bar_2)
expect(subject.namespace_inheritable(:some_thing)).to eq :foo_bar_2
subject.namespace_end
expect(subject.namespace_inheritable(:some_thing)).to eq :foo_bar
subject.namespace_end
end
end
describe '#namespace_stackable' do
it 'delegates to get_or_set' do
expect(subject).to receive(:get_or_set).with(:namespace_stackable, :dummy, 1)
subject.namespace_stackable(:dummy, 1)
end
it 'stacks values from surrounding namespace' do
subject.namespace_start
subject.namespace_stackable(:some_thing, :foo_bar)
expect(subject.namespace_stackable(:some_thing)).to eq [:foo_bar]
subject.namespace_start
expect(subject.namespace_stackable(:some_thing)).to eq [:foo_bar]
subject.namespace_stackable(:some_thing, :foo_bar_2)
expect(subject.namespace_stackable(:some_thing)).to eq [:foo_bar, :foo_bar_2]
subject.namespace_end
expect(subject.namespace_stackable(:some_thing)).to eq [:foo_bar]
subject.namespace_end
end
end
describe '#api_class_setting' do
it 'delegates to get_or_set' do
expect(subject).to receive(:get_or_set).with(:api_class, :dummy, 1)
subject.api_class_setting(:dummy, 1)
end
end
describe '#within_namespace' do
it 'calls start and end for a namespace' do
expect(subject).to receive :namespace_start
expect(subject).to receive :namespace_end
subject.within_namespace do
end
end
it 'returns the last result' do
result = subject.within_namespace do
1
end
expect(result).to eq 1
end
end
describe 'complex scenario' do
it 'plays well' do
obj1 = SettingsSpec::Dummy.new
obj2 = SettingsSpec::Dummy.new
obj3 = SettingsSpec::Dummy.new
obj1_copy = nil
obj2_copy = nil
obj3_copy = nil
obj1.within_namespace do
obj1.namespace_stackable(:some_thing, :obj1)
expect(obj1.namespace_stackable(:some_thing)).to eq [:obj1]
obj1_copy = obj1.inheritable_setting.point_in_time_copy
end
expect(obj1.namespace_stackable(:some_thing)).to eq []
expect(obj1_copy.namespace_stackable[:some_thing]).to eq [:obj1]
obj2.within_namespace do
obj2.namespace_stackable(:some_thing, :obj2)
expect(obj2.namespace_stackable(:some_thing)).to eq [:obj2]
obj2_copy = obj2.inheritable_setting.point_in_time_copy
end
expect(obj2.namespace_stackable(:some_thing)).to eq []
expect(obj2_copy.namespace_stackable[:some_thing]).to eq [:obj2]
obj3.within_namespace do
obj3.namespace_stackable(:some_thing, :obj3)
expect(obj3.namespace_stackable(:some_thing)).to eq [:obj3]
obj3_copy = obj3.inheritable_setting.point_in_time_copy
end
expect(obj3.namespace_stackable(:some_thing)).to eq []
expect(obj3_copy.namespace_stackable[:some_thing]).to eq [:obj3]
obj1.top_level_setting.inherit_from obj2_copy.point_in_time_copy
obj2.top_level_setting.inherit_from obj3_copy.point_in_time_copy
expect(obj1_copy.namespace_stackable[:some_thing]).to eq [:obj3, :obj2, :obj1]
end
end
end
end
end
grape-0.13.0/spec/grape/integration/ 0000755 0000041 0000041 00000000000 12563420522 017275 5 ustar www-data www-data grape-0.13.0/spec/grape/integration/rack_spec.rb 0000644 0000041 0000041 00000001607 12563420522 021560 0 ustar www-data www-data require 'spec_helper'
describe Rack do
it 'correctly populates params from a Tempfile' do
input = Tempfile.new 'rubbish'
begin
app = Class.new(Grape::API) do
format :json
post do
{ params_keys: params.keys }
end
end
input.write({ test: '123' * 10_000 }.to_json)
input.rewind
options = {
input: input,
method: 'POST',
'CONTENT_TYPE' => 'application/json'
}
env = Rack::MockRequest.env_for('/', options)
unless RUBY_PLATFORM == 'java'
major, minor, release = Rack.release.split('.').map(&:to_i)
pending 'Rack 1.5.3 or 1.6.1 required' unless major >= 1 && ((minor == 5 && release >= 3) || (minor >= 6))
end
expect(JSON.parse(app.call(env)[2].body.first)['params_keys']).to match_array('test')
ensure
input.close
input.unlink
end
end
end
grape-0.13.0/spec/grape/presenters/ 0000755 0000041 0000041 00000000000 12563420522 017144 5 ustar www-data www-data grape-0.13.0/spec/grape/presenters/presenter_spec.rb 0000644 0000041 0000041 00000003215 12563420522 022513 0 ustar www-data www-data require 'spec_helper'
module Grape
module Presenters
module InsideRouteSpec
class Dummy
include Grape::DSL::InsideRoute
attr_reader :env, :request, :new_settings
def initialize
@env = {}
@header = {}
@new_settings = { namespace_inheritable: {}, namespace_stackable: {} }
end
end
end
describe Presenter do
describe 'represent' do
let(:object_mock) do
Object.new
end
it 'represent object' do
expect(Presenter.represent(object_mock)).to eq object_mock
end
end
subject { InsideRouteSpec::Dummy.new }
describe 'present' do
let(:hash_mock) do
{ key: :value }
end
describe 'instance' do
before do
subject.present hash_mock, with: Grape::Presenters::Presenter
end
it 'presents dummy hash' do
expect(subject.body).to eq hash_mock
end
end
describe 'multiple presenter' do
let(:hash_mock1) do
{ key1: :value1 }
end
let(:hash_mock2) do
{ key2: :value2 }
end
describe 'instance' do
before do
subject.present hash_mock1, with: Grape::Presenters::Presenter
subject.present hash_mock2, with: Grape::Presenters::Presenter
end
it 'presents both dummy presenter' do
expect(subject.body[:key1]).to eq hash_mock1[:key1]
expect(subject.body[:key2]).to eq hash_mock2[:key2]
end
end
end
end
end
end
end
grape-0.13.0/spec/grape/validations_spec.rb 0000644 0000041 0000041 00000132512 12563420522 020632 0 ustar www-data www-data require 'spec_helper'
describe Grape::Validations do
subject { Class.new(Grape::API) }
def app
subject
end
describe 'params' do
context 'optional' do
it 'validates when params is present' do
subject.params do
optional :a_number, regexp: /^[0-9]+$/
end
subject.get '/optional' do
'optional works!'
end
get '/optional', a_number: 'string'
expect(last_response.status).to eq(400)
expect(last_response.body).to eq('a_number is invalid')
get '/optional', a_number: 45
expect(last_response.status).to eq(200)
expect(last_response.body).to eq('optional works!')
end
it "doesn't validate when param not present" do
subject.params do
optional :a_number, regexp: /^[0-9]+$/
end
subject.get '/optional' do
'optional works!'
end
get '/optional'
expect(last_response.status).to eq(200)
expect(last_response.body).to eq('optional works!')
end
it 'adds to declared parameters' do
subject.params do
optional :some_param
end
expect(subject.route_setting(:declared_params)).to eq([:some_param])
end
end
context 'optional using Grape::Entity documentation' do
def define_optional_using
documentation = { field_a: { type: String }, field_b: { type: String } }
subject.params do
optional :all, using: documentation
end
end
before do
define_optional_using
subject.get '/optional' do
'optional with using works'
end
end
it 'adds entity documentation to declared params' do
define_optional_using
expect(subject.route_setting(:declared_params)).to eq([:field_a, :field_b])
end
it 'works when field_a and field_b are not present' do
get '/optional'
expect(last_response.status).to eq(200)
expect(last_response.body).to eq('optional with using works')
end
it 'works when field_a is present' do
get '/optional', field_a: 'woof'
expect(last_response.status).to eq(200)
expect(last_response.body).to eq('optional with using works')
end
it 'works when field_b is present' do
get '/optional', field_b: 'woof'
expect(last_response.status).to eq(200)
expect(last_response.body).to eq('optional with using works')
end
end
context 'required' do
before do
subject.params do
requires :key, type: String
end
subject.get('/required') { 'required works' }
subject.put('/required') { { key: params[:key] }.to_json }
end
it 'errors when param not present' do
get '/required'
expect(last_response.status).to eq(400)
expect(last_response.body).to eq('key is missing')
end
it "doesn't throw a missing param when param is present" do
get '/required', key: 'cool'
expect(last_response.status).to eq(200)
expect(last_response.body).to eq('required works')
end
it 'adds to declared parameters' do
subject.params do
requires :some_param
end
expect(subject.route_setting(:declared_params)).to eq([:some_param])
end
it 'works when required field is present but nil' do
put '/required', { key: nil }.to_json, 'CONTENT_TYPE' => 'application/json'
expect(last_response.status).to eq(200)
expect(JSON.parse(last_response.body)).to eq('key' => nil)
end
end
context 'requires :all using Grape::Entity documentation' do
def define_requires_all
documentation = {
required_field: { type: String },
optional_field: { type: String }
}
subject.params do
requires :all, except: :optional_field, using: documentation
end
end
before do
define_requires_all
subject.get '/required' do
'required works'
end
end
it 'adds entity documentation to declared params' do
define_requires_all
expect(subject.route_setting(:declared_params)).to eq([:required_field, :optional_field])
end
it 'errors when required_field is not present' do
get '/required'
expect(last_response.status).to eq(400)
expect(last_response.body).to eq('required_field is missing')
end
it 'works when required_field is present' do
get '/required', required_field: 'woof'
expect(last_response.status).to eq(200)
expect(last_response.body).to eq('required works')
end
end
context 'requires :none using Grape::Entity documentation' do
def define_requires_none
documentation = {
required_field: { type: String },
optional_field: { type: String }
}
subject.params do
requires :none, except: :required_field, using: documentation
end
end
before do
define_requires_none
subject.get '/required' do
'required works'
end
end
it 'adds entity documentation to declared params' do
define_requires_none
expect(subject.route_setting(:declared_params)).to eq([:required_field, :optional_field])
end
it 'errors when required_field is not present' do
get '/required'
expect(last_response.status).to eq(400)
expect(last_response.body).to eq('required_field is missing')
end
it 'works when required_field is present' do
get '/required', required_field: 'woof'
expect(last_response.status).to eq(200)
expect(last_response.body).to eq('required works')
end
end
context 'requires :all or :none but except a non-existent field using Grape::Entity documentation' do
context 'requires :all' do
def define_requires_all
documentation = {
required_field: { type: String },
optional_field: { type: String }
}
subject.params do
requires :all, except: :non_existent_field, using: documentation
end
end
it 'adds only the entity documentation to declared params, nothing more' do
define_requires_all
expect(subject.route_setting(:declared_params)).to eq([:required_field, :optional_field])
end
end
context 'requires :none' do
def define_requires_none
documentation = {
required_field: { type: String },
optional_field: { type: String }
}
subject.params do
requires :none, except: :non_existent_field, using: documentation
end
end
it 'adds only the entity documentation to declared params, nothing more' do
expect { define_requires_none }.to raise_error(ArgumentError)
end
end
end
context 'required with an Array block' do
before do
subject.params do
requires :items, type: Array do
requires :key
end
end
subject.get('/required') { 'required works' }
subject.put('/required') { { items: params[:items] }.to_json }
end
it 'errors when param not present' do
get '/required'
expect(last_response.status).to eq(400)
expect(last_response.body).to eq('items is missing')
end
it 'errors when param is not an Array' do
get '/required', items: 'hello'
expect(last_response.status).to eq(400)
expect(last_response.body).to eq('items is invalid, items[key] is missing')
get '/required', items: { key: 'foo' }
expect(last_response.status).to eq(400)
expect(last_response.body).to eq('items is invalid')
end
it "doesn't throw a missing param when param is present" do
get '/required', items: [{ key: 'hello' }, { key: 'world' }]
expect(last_response.status).to eq(200)
expect(last_response.body).to eq('required works')
end
it "doesn't throw a missing param when param is present but empty" do
put '/required', { items: [] }.to_json, 'CONTENT_TYPE' => 'application/json'
expect(last_response.status).to eq(200)
expect(JSON.parse(last_response.body)).to eq('items' => [])
end
it 'adds to declared parameters' do
subject.params do
requires :items, type: Array do
requires :key
end
end
expect(subject.route_setting(:declared_params)).to eq([items: [:key]])
end
end
context 'required with a Hash block' do
before do
subject.params do
requires :items, type: Hash do
requires :key
end
end
subject.get '/required' do
'required works'
end
end
it 'errors when param not present' do
get '/required'
expect(last_response.status).to eq(400)
expect(last_response.body).to eq('items is missing, items[key] is missing')
end
it 'errors when param is not a Hash' do
get '/required', items: 'hello'
expect(last_response.status).to eq(400)
expect(last_response.body).to eq('items is invalid, items[key] is missing')
get '/required', items: [{ key: 'foo' }]
expect(last_response.status).to eq(400)
expect(last_response.body).to eq('items is invalid')
end
it "doesn't throw a missing param when param is present" do
get '/required', items: { key: 'hello' }
expect(last_response.status).to eq(200)
expect(last_response.body).to eq('required works')
end
it 'adds to declared parameters' do
subject.params do
requires :items, type: Array do
requires :key
end
end
expect(subject.route_setting(:declared_params)).to eq([items: [:key]])
end
end
context 'group' do
before do
subject.params do
group :items, type: Array do
requires :key
end
end
subject.get '/required' do
'required works'
end
end
it 'errors when param not present' do
get '/required'
expect(last_response.status).to eq(400)
expect(last_response.body).to eq('items is missing')
end
it "doesn't throw a missing param when param is present" do
get '/required', items: [key: 'hello', key: 'world']
expect(last_response.status).to eq(200)
expect(last_response.body).to eq('required works')
end
it 'adds to declared parameters' do
subject.params do
group :items, type: Array do
requires :key
end
end
expect(subject.route_setting(:declared_params)).to eq([items: [:key]])
end
end
context 'group params with nested params which has a type' do
let(:invalid_items) { { items: '' } }
before do
subject.params do
optional :items, type: Array do
optional :key1, type: String
optional :key2, type: String
end
end
subject.post '/group_with_nested' do
'group with nested works'
end
end
it 'errors when group param is invalid'do
post '/group_with_nested', items: invalid_items
expect(last_response.status).to eq(400)
end
end
context 'custom validator for a Hash' do
module DateRangeValidations
class DateRangeValidator < Grape::Validations::Base
def validate_param!(attr_name, params)
unless params[attr_name][:from] <= params[attr_name][:to]
fail Grape::Exceptions::Validation, params: [@scope.full_name(attr_name)], message: "'from' must be lower or equal to 'to'"
end
end
end
end
before do
subject.params do
optional :date_range, date_range: true, type: Hash do
requires :from, type: Integer
requires :to, type: Integer
end
end
subject.get('/optional') do
'optional works'
end
subject.params do
requires :date_range, date_range: true, type: Hash do
requires :from, type: Integer
requires :to, type: Integer
end
end
subject.get('/required') do
'required works'
end
end
context 'which is optional' do
it "doesn't throw an error if the validation passes" do
get '/optional', date_range: { from: 1, to: 2 }
expect(last_response.status).to eq(200)
end
it 'errors if the validation fails' do
get '/optional', date_range: { from: 2, to: 1 }
expect(last_response.status).to eq(400)
end
end
context 'which is required' do
it "doesn't throw an error if the validation passes" do
get '/required', date_range: { from: 1, to: 2 }
expect(last_response.status).to eq(200)
end
it 'errors if the validation fails' do
get '/required', date_range: { from: 2, to: 1 }
expect(last_response.status).to eq(400)
end
end
end
context 'validation within arrays' do
before do
subject.params do
group :children, type: Array do
requires :name
group :parents, type: Array do
requires :name
end
end
end
subject.get '/within_array' do
'within array works'
end
end
it 'can handle new scopes within child elements' do
get '/within_array', children: [
{ name: 'John', parents: [{ name: 'Jane' }, { name: 'Bob' }] },
{ name: 'Joe', parents: [{ name: 'Josie' }] }
]
expect(last_response.status).to eq(200)
expect(last_response.body).to eq('within array works')
end
it 'errors when a parameter is not present' do
get '/within_array', children: [
{ name: 'Jim', parents: [{}] },
{ name: 'Job', parents: [{ name: 'Joy' }] }
]
# NOTE: with body parameters in json or XML or similar this
# should actually fail with: children[parents][name] is missing.
expect(last_response.status).to eq(400)
expect(last_response.body).to eq('children[parents] is missing')
end
it 'safely handles empty arrays and blank parameters' do
# NOTE: with body parameters in json or XML or similar this
# should actually return 200, since an empty array is valid.
get '/within_array', children: []
expect(last_response.status).to eq(400)
expect(last_response.body).to eq('children is missing')
get '/within_array', children: [name: 'Jay']
expect(last_response.status).to eq(400)
expect(last_response.body).to eq('children[parents] is missing')
end
it 'errors when param is not an Array' do
# NOTE: would be nicer if these just returned 'children is invalid'
get '/within_array', children: 'hello'
expect(last_response.status).to eq(400)
expect(last_response.body).to eq('children is invalid, children[name] is missing, children[parents] is missing, children[parents] is invalid, children[parents][name] is missing')
get '/within_array', children: { name: 'foo' }
expect(last_response.status).to eq(400)
expect(last_response.body).to eq('children is invalid, children[parents] is missing')
get '/within_array', children: [name: 'Jay', parents: { name: 'Fred' }]
expect(last_response.status).to eq(400)
expect(last_response.body).to eq('children[parents] is invalid')
end
end
context 'with block param' do
before do
subject.params do
requires :planets, type: Array do
requires :name
end
end
subject.get '/req' do
'within array works'
end
subject.put '/req' do
''
end
subject.params do
group :stars, type: Array do
requires :name
end
end
subject.get '/grp' do
'within array works'
end
subject.put '/grp' do
''
end
subject.params do
requires :name
optional :moons, type: Array do
requires :name
end
end
subject.get '/opt' do
'within array works'
end
subject.put '/opt' do
''
end
end
it 'requires defaults to Array type' do
get '/req', planets: 'Jupiter, Saturn'
expect(last_response.status).to eq(400)
expect(last_response.body).to eq('planets is invalid, planets[name] is missing')
get '/req', planets: { name: 'Jupiter' }
expect(last_response.status).to eq(400)
expect(last_response.body).to eq('planets is invalid')
get '/req', planets: [{ name: 'Venus' }, { name: 'Mars' }]
expect(last_response.status).to eq(200)
put_with_json '/req', planets: []
expect(last_response.status).to eq(200)
end
it 'optional defaults to Array type' do
get '/opt', name: 'Jupiter', moons: 'Europa, Ganymede'
expect(last_response.status).to eq(400)
expect(last_response.body).to eq('moons is invalid, moons[name] is missing')
get '/opt', name: 'Jupiter', moons: { name: 'Ganymede' }
expect(last_response.status).to eq(400)
expect(last_response.body).to eq('moons is invalid')
get '/opt', name: 'Jupiter', moons: [{ name: 'Io' }, { name: 'Callisto' }]
expect(last_response.status).to eq(200)
put_with_json '/opt', name: 'Venus'
expect(last_response.status).to eq(200)
put_with_json '/opt', name: 'Mercury', moons: []
expect(last_response.status).to eq(200)
end
it 'group defaults to Array type' do
get '/grp', stars: 'Sun'
expect(last_response.status).to eq(400)
expect(last_response.body).to eq('stars is invalid, stars[name] is missing')
get '/grp', stars: { name: 'Sun' }
expect(last_response.status).to eq(400)
expect(last_response.body).to eq('stars is invalid')
get '/grp', stars: [{ name: 'Sun' }]
expect(last_response.status).to eq(200)
put_with_json '/grp', stars: []
expect(last_response.status).to eq(200)
end
end
context 'validation within arrays with JSON' do
before do
subject.params do
group :children, type: Array do
requires :name
group :parents, type: Array do
requires :name
end
end
end
subject.put '/within_array' do
'within array works'
end
end
it 'can handle new scopes within child elements' do
put_with_json '/within_array', children: [
{ name: 'John', parents: [{ name: 'Jane' }, { name: 'Bob' }] },
{ name: 'Joe', parents: [{ name: 'Josie' }] }
]
expect(last_response.status).to eq(200)
expect(last_response.body).to eq('within array works')
end
it 'errors when a parameter is not present' do
put_with_json '/within_array', children: [
{ name: 'Jim', parents: [{}] },
{ name: 'Job', parents: [{ name: 'Joy' }] }
]
expect(last_response.status).to eq(400)
expect(last_response.body).to eq('children[parents][name] is missing')
end
it 'safely handles empty arrays and blank parameters' do
put_with_json '/within_array', children: []
expect(last_response.status).to eq(200)
put_with_json '/within_array', children: [name: 'Jay']
expect(last_response.status).to eq(400)
expect(last_response.body).to eq('children[parents] is missing')
end
end
context 'optional with an Array block' do
before do
subject.params do
optional :items, type: Array do
requires :key
end
end
subject.get '/optional_group' do
'optional group works'
end
end
it "doesn't throw a missing param when the group isn't present" do
get '/optional_group'
expect(last_response.status).to eq(200)
expect(last_response.body).to eq('optional group works')
end
it "doesn't throw a missing param when both group and param are given" do
get '/optional_group', items: [{ key: 'foo' }]
expect(last_response.status).to eq(200)
expect(last_response.body).to eq('optional group works')
end
it 'errors when group is present, but required param is not' do
get '/optional_group', items: [{ not_key: 'foo' }]
expect(last_response.status).to eq(400)
expect(last_response.body).to eq('items[key] is missing')
end
it "errors when param is present but isn't an Array" do
get '/optional_group', items: 'hello'
expect(last_response.status).to eq(400)
expect(last_response.body).to eq('items is invalid, items[key] is missing')
get '/optional_group', items: { key: 'foo' }
expect(last_response.status).to eq(400)
expect(last_response.body).to eq('items is invalid')
end
it 'adds to declared parameters' do
subject.params do
optional :items, type: Array do
requires :key
end
end
expect(subject.route_setting(:declared_params)).to eq([items: [:key]])
end
end
context 'nested optional Array blocks' do
before do
subject.params do
optional :items, type: Array do
requires :key
optional(:optional_subitems, type: Array) { requires :value }
requires(:required_subitems, type: Array) { requires :value }
end
end
subject.get('/nested_optional_group') { 'nested optional group works' }
end
it 'does no internal validations if the outer group is blank' do
get '/nested_optional_group'
expect(last_response.status).to eq(200)
expect(last_response.body).to eq('nested optional group works')
end
it 'does internal validations if the outer group is present' do
get '/nested_optional_group', items: [{ key: 'foo' }]
expect(last_response.status).to eq(400)
expect(last_response.body).to eq('items[required_subitems] is missing')
get '/nested_optional_group', items: [{ key: 'foo', required_subitems: [{ value: 'bar' }] }]
expect(last_response.status).to eq(200)
expect(last_response.body).to eq('nested optional group works')
end
it 'handles deep nesting' do
get '/nested_optional_group', items: [{ key: 'foo', required_subitems: [{ value: 'bar' }], optional_subitems: [{ not_value: 'baz' }] }]
expect(last_response.status).to eq(400)
expect(last_response.body).to eq('items[optional_subitems][value] is missing')
get '/nested_optional_group', items: [{ key: 'foo', required_subitems: [{ value: 'bar' }], optional_subitems: [{ value: 'baz' }] }]
expect(last_response.status).to eq(200)
expect(last_response.body).to eq('nested optional group works')
end
it 'handles validation within arrays' do
get '/nested_optional_group', items: [{ key: 'foo' }]
expect(last_response.status).to eq(400)
expect(last_response.body).to eq('items[required_subitems] is missing')
get '/nested_optional_group', items: [{ key: 'foo', required_subitems: [{ value: 'bar' }] }]
expect(last_response.status).to eq(200)
expect(last_response.body).to eq('nested optional group works')
get '/nested_optional_group', items: [{ key: 'foo', required_subitems: [{ value: 'bar' }], optional_subitems: [{ not_value: 'baz' }] }]
expect(last_response.status).to eq(400)
expect(last_response.body).to eq('items[optional_subitems][value] is missing')
end
it 'adds to declared parameters' do
subject.params do
optional :items, type: Array do
requires :key
optional(:optional_subitems, type: Array) { requires :value }
requires(:required_subitems, type: Array) { requires :value }
end
end
expect(subject.route_setting(:declared_params)).to eq([items: [:key, { optional_subitems: [:value] }, { required_subitems: [:value] }]])
end
end
context 'multiple validation errors' do
before do
subject.params do
requires :yolo
requires :swag
end
subject.get '/two_required' do
'two required works'
end
end
it 'throws the validation errors' do
get '/two_required'
expect(last_response.status).to eq(400)
expect(last_response.body).to match(/yolo is missing/)
expect(last_response.body).to match(/swag is missing/)
end
end
context 'custom validation' do
module CustomValidations
class Customvalidator < Grape::Validations::Base
def validate_param!(attr_name, params)
unless params[attr_name] == 'im custom'
fail Grape::Exceptions::Validation, params: [@scope.full_name(attr_name)], message: 'is not custom!'
end
end
end
end
context 'when using optional with a custom validator' do
before do
subject.params do
optional :custom, customvalidator: true
end
subject.get '/optional_custom' do
'optional with custom works!'
end
end
it 'validates when param is present' do
get '/optional_custom', custom: 'im custom'
expect(last_response.status).to eq(200)
expect(last_response.body).to eq('optional with custom works!')
get '/optional_custom', custom: 'im wrong'
expect(last_response.status).to eq(400)
expect(last_response.body).to eq('custom is not custom!')
end
it "skips validation when parameter isn't present" do
get '/optional_custom'
expect(last_response.status).to eq(200)
expect(last_response.body).to eq('optional with custom works!')
end
it 'validates with custom validator when param present and incorrect type' do
subject.params do
optional :custom, type: String, customvalidator: true
end
get '/optional_custom', custom: 123
expect(last_response.status).to eq(400)
expect(last_response.body).to eq('custom is not custom!')
end
end
context 'when using requires with a custom validator' do
before do
subject.params do
requires :custom, customvalidator: true
end
subject.get '/required_custom' do
'required with custom works!'
end
end
it 'validates when param is present' do
get '/required_custom', custom: 'im wrong, validate me'
expect(last_response.status).to eq(400)
expect(last_response.body).to eq('custom is not custom!')
get '/required_custom', custom: 'im custom'
expect(last_response.status).to eq(200)
expect(last_response.body).to eq('required with custom works!')
end
it 'validates when param is not present' do
get '/required_custom'
expect(last_response.status).to eq(400)
expect(last_response.body).to eq('custom is missing, custom is not custom!')
end
context 'nested namespaces' do
before do
subject.params do
requires :custom, customvalidator: true
end
subject.namespace 'nested' do
get 'one' do
'validation failed'
end
namespace 'nested' do
get 'two' do
'validation failed'
end
end
end
subject.namespace 'peer' do
get 'one' do
'no validation required'
end
namespace 'nested' do
get 'two' do
'no validation required'
end
end
end
subject.namespace 'unrelated' do
params do
requires :name
end
get 'one' do
'validation required'
end
namespace 'double' do
get 'two' do
'no validation required'
end
end
end
end
specify 'the parent namespace uses the validator' do
get '/nested/one', custom: 'im wrong, validate me'
expect(last_response.status).to eq(400)
expect(last_response.body).to eq('custom is not custom!')
end
specify 'the nested namespace inherits the custom validator' do
get '/nested/nested/two', custom: 'im wrong, validate me'
expect(last_response.status).to eq(400)
expect(last_response.body).to eq('custom is not custom!')
end
specify 'peer namespaces does not have the validator' do
get '/peer/one', custom: 'im not validated'
expect(last_response.status).to eq(200)
expect(last_response.body).to eq('no validation required')
end
specify 'namespaces nested in peers should also not have the validator' do
get '/peer/nested/two', custom: 'im not validated'
expect(last_response.status).to eq(200)
expect(last_response.body).to eq('no validation required')
end
specify 'when nested, specifying a route should clear out the validations for deeper nested params' do
get '/unrelated/one'
expect(last_response.status).to eq(400)
get '/unrelated/double/two'
expect(last_response.status).to eq(200)
end
end
end
context 'when using options on param' do
module CustomValidations
class CustomvalidatorWithOptions < Grape::Validations::Base
def validate_param!(attr_name, params)
unless params[attr_name] == @option[:text]
fail Grape::Exceptions::Validation, params: [@scope.full_name(attr_name)], message: @option[:error_message]
end
end
end
end
before do
subject.params do
optional :custom, customvalidator_with_options: { text: 'im custom with options', error_message: 'is not custom with options!' }
end
subject.get '/optional_custom' do
'optional with custom works!'
end
end
it 'validates param with custom validator with options' do
get '/optional_custom', custom: 'im custom with options'
expect(last_response.status).to eq(200)
expect(last_response.body).to eq('optional with custom works!')
get '/optional_custom', custom: 'im wrong'
expect(last_response.status).to eq(400)
expect(last_response.body).to eq('custom is not custom with options!')
end
end
end # end custom validation
context 'named' do
context 'can be defined' do
it 'in helpers' do
subject.helpers do
params :pagination do
end
end
end
it 'in helper module which kind of Grape::DSL::Helpers::BaseHelper' do
module SharedParams
extend Grape::DSL::Helpers::BaseHelper
params :pagination do
end
end
subject.helpers SharedParams
end
end
context 'can be included in usual params' do
before do
module SharedParams
extend Grape::DSL::Helpers::BaseHelper
params :period do
optional :start_date
optional :end_date
end
end
subject.helpers SharedParams
subject.helpers do
params :pagination do
optional :page, type: Integer
optional :per_page, type: Integer
end
end
end
it 'by #use' do
subject.params do
use :pagination
end
expect(subject.route_setting(:declared_params)).to eq [:page, :per_page]
end
it 'by #use with multiple params' do
subject.params do
use :pagination, :period
end
expect(subject.route_setting(:declared_params)).to eq [:page, :per_page, :start_date, :end_date]
end
end
context 'with block' do
before do
subject.helpers do
params :order do |options|
optional :order, type: Symbol, values: [:asc, :desc], default: options[:default_order]
optional :order_by, type: Symbol, values: options[:order_by], default: options[:default_order_by]
end
end
subject.format :json
subject.params do
use :order, default_order: :asc, order_by: [:name, :created_at], default_order_by: :created_at
end
subject.get '/order' do
{
order: params[:order],
order_by: params[:order_by]
}
end
end
it 'returns defaults' do
get '/order'
expect(last_response.status).to eq(200)
expect(last_response.body).to eq({ order: :asc, order_by: :created_at }.to_json)
end
it 'overrides default value for order' do
get '/order?order=desc'
expect(last_response.status).to eq(200)
expect(last_response.body).to eq({ order: :desc, order_by: :created_at }.to_json)
end
it 'overrides default value for order_by' do
get '/order?order_by=name'
expect(last_response.status).to eq(200)
expect(last_response.body).to eq({ order: :asc, order_by: :name }.to_json)
end
it 'fails with invalid value' do
get '/order?order=invalid'
expect(last_response.status).to eq(400)
expect(last_response.body).to eq('{"error":"order does not have a valid value"}')
end
end
end
context 'documentation' do
it 'can be included with a hash' do
documentation = { example: 'Joe' }
subject.params do
requires 'first_name', documentation: documentation
end
subject.get '/' do
end
expect(subject.routes.first.route_params['first_name'][:documentation]).to eq(documentation)
end
end
context 'mutually exclusive' do
context 'optional params' do
it 'errors when two or more are present' do
subject.params do
optional :beer
optional :wine
optional :juice
mutually_exclusive :beer, :wine, :juice
end
subject.get '/mutually_exclusive' do
'mutually_exclusive works!'
end
get '/mutually_exclusive', beer: 'string', wine: 'anotherstring'
expect(last_response.status).to eq(400)
expect(last_response.body).to eq 'beer, wine are mutually exclusive'
end
end
context 'more than one set of mutually exclusive params' do
it 'errors for all sets' do
subject.params do
optional :beer
optional :wine
mutually_exclusive :beer, :wine
optional :nested, type: Hash do
optional :scotch
optional :aquavit
mutually_exclusive :scotch, :aquavit
end
optional :nested2, type: Array do
optional :scotch2
optional :aquavit2
mutually_exclusive :scotch2, :aquavit2
end
end
subject.get '/mutually_exclusive' do
'mutually_exclusive works!'
end
get '/mutually_exclusive', beer: 'true', wine: 'true', nested: { scotch: 'true', aquavit: 'true' }, nested2: [{ scotch2: 'true' }, { scotch2: 'true', aquavit2: 'true' }]
expect(last_response.status).to eq(400)
expect(last_response.body).to eq 'beer, wine are mutually exclusive, scotch, aquavit are mutually exclusive, scotch2, aquavit2 are mutually exclusive'
end
end
context 'in a group' do
it 'works when only one from the set is present' do
subject.params do
group :drink, type: Hash do
optional :wine
optional :beer
optional :juice
mutually_exclusive :beer, :wine, :juice
end
end
subject.get '/mutually_exclusive_group' do
'mutually_exclusive_group works!'
end
get '/mutually_exclusive_group', drink: { beer: 'true' }
expect(last_response.status).to eq(200)
end
it 'errors when more than one from the set is present' do
subject.params do
group :drink, type: Hash do
optional :wine
optional :beer
optional :juice
mutually_exclusive :beer, :wine, :juice
end
end
subject.get '/mutually_exclusive_group' do
'mutually_exclusive_group works!'
end
get '/mutually_exclusive_group', drink: { beer: 'true', juice: 'true', wine: 'true' }
expect(last_response.status).to eq(400)
end
end
context 'mutually exclusive params inside Hash group' do
it 'invalidates if request param is invalid type' do
subject.params do
optional :wine, type: Hash do
optional :grape
optional :country
mutually_exclusive :grape, :country
end
end
subject.post '/mutually_exclusive' do
'mutually_exclusive works!'
end
post '/mutually_exclusive', wine: '2015 sauvignon'
expect(last_response.status).to eq(400)
expect(last_response.body).to eq 'wine is invalid'
end
end
end
context 'exactly one of' do
context 'params' do
before :each do
subject.params do
optional :beer
optional :wine
optional :juice
exactly_one_of :beer, :wine, :juice
end
subject.get '/exactly_one_of' do
'exactly_one_of works!'
end
end
it 'errors when none are present' do
get '/exactly_one_of'
expect(last_response.status).to eq(400)
expect(last_response.body).to eq 'beer, wine, juice are missing, exactly one parameter must be provided'
end
it 'succeeds when one is present' do
get '/exactly_one_of', beer: 'string'
expect(last_response.status).to eq(200)
expect(last_response.body).to eq 'exactly_one_of works!'
end
it 'errors when two or more are present' do
get '/exactly_one_of', beer: 'string', wine: 'anotherstring'
expect(last_response.status).to eq(400)
expect(last_response.body).to eq 'beer, wine are mutually exclusive'
end
end
context 'nested params' do
before :each do
subject.params do
requires :nested, type: Hash do
optional :beer_nested
optional :wine_nested
optional :juice_nested
exactly_one_of :beer_nested, :wine_nested, :juice_nested
end
optional :nested2, type: Array do
optional :beer_nested2
optional :wine_nested2
optional :juice_nested2
exactly_one_of :beer_nested2, :wine_nested2, :juice_nested2
end
end
subject.get '/exactly_one_of_nested' do
'exactly_one_of works!'
end
end
it 'errors when none are present' do
get '/exactly_one_of_nested'
expect(last_response.status).to eq(400)
expect(last_response.body).to eq 'nested is missing, beer_nested, wine_nested, juice_nested are missing, exactly one parameter must be provided'
end
it 'succeeds when one is present' do
get '/exactly_one_of_nested', nested: { beer_nested: 'string' }
expect(last_response.status).to eq(200)
expect(last_response.body).to eq 'exactly_one_of works!'
end
it 'errors when two or more are present' do
get '/exactly_one_of_nested', nested: { beer_nested: 'string' }, nested2: [{ beer_nested2: 'string', wine_nested2: 'anotherstring' }]
expect(last_response.status).to eq(400)
expect(last_response.body).to eq 'beer_nested2, wine_nested2 are mutually exclusive'
end
end
end
context 'at least one of' do
context 'params' do
before :each do
subject.params do
optional :beer
optional :wine
optional :juice
at_least_one_of :beer, :wine, :juice
end
subject.get '/at_least_one_of' do
'at_least_one_of works!'
end
end
it 'errors when none are present' do
get '/at_least_one_of'
expect(last_response.status).to eq(400)
expect(last_response.body).to eq 'beer, wine, juice are missing, at least one parameter must be provided'
end
it 'does not error when one is present' do
get '/at_least_one_of', beer: 'string'
expect(last_response.status).to eq(200)
expect(last_response.body).to eq 'at_least_one_of works!'
end
it 'does not error when two are present' do
get '/at_least_one_of', beer: 'string', wine: 'string'
expect(last_response.status).to eq(200)
expect(last_response.body).to eq 'at_least_one_of works!'
end
end
context 'nested params' do
before :each do
subject.params do
requires :nested, type: Hash do
optional :beer_nested
optional :wine_nested
optional :juice_nested
at_least_one_of :beer_nested, :wine_nested, :juice_nested
end
optional :nested2, type: Array do
optional :beer_nested2
optional :wine_nested2
optional :juice_nested2
at_least_one_of :beer_nested2, :wine_nested2, :juice_nested2
end
end
subject.get '/at_least_one_of_nested' do
'at_least_one_of works!'
end
end
it 'errors when none are present' do
get '/at_least_one_of_nested'
expect(last_response.status).to eq(400)
expect(last_response.body).to eq 'nested is missing, beer_nested, wine_nested, juice_nested are missing, at least one parameter must be provided'
end
it 'does not error when one is present' do
get '/at_least_one_of_nested', nested: { beer_nested: 'string' }, nested2: [{ beer_nested2: 'string' }]
expect(last_response.status).to eq(200)
expect(last_response.body).to eq 'at_least_one_of works!'
end
it 'does not error when two are present' do
get '/at_least_one_of_nested', nested: { beer_nested: 'string', wine_nested: 'string' }, nested2: [{ beer_nested2: 'string', wine_nested2: 'string' }]
expect(last_response.status).to eq(200)
expect(last_response.body).to eq 'at_least_one_of works!'
end
end
end
context 'in a group' do
it 'works when only one from the set is present' do
subject.params do
group :drink, type: Hash do
optional :wine
optional :beer
optional :juice
exactly_one_of :beer, :wine, :juice
end
end
subject.get '/exactly_one_of_group' do
'exactly_one_of_group works!'
end
get '/exactly_one_of_group', drink: { beer: 'true' }
expect(last_response.status).to eq(200)
end
it 'errors when no parameter from the set is present' do
subject.params do
group :drink, type: Hash do
optional :wine
optional :beer
optional :juice
exactly_one_of :beer, :wine, :juice
end
end
subject.get '/exactly_one_of_group' do
'exactly_one_of_group works!'
end
get '/exactly_one_of_group', drink: {}
expect(last_response.status).to eq(400)
end
it 'errors when more than one from the set is present' do
subject.params do
group :drink, type: Hash do
optional :wine
optional :beer
optional :juice
exactly_one_of :beer, :wine, :juice
end
end
subject.get '/exactly_one_of_group' do
'exactly_one_of_group works!'
end
get '/exactly_one_of_group', drink: { beer: 'true', juice: 'true', wine: 'true' }
expect(last_response.status).to eq(400)
end
it 'does not falsely think the param is there if it is provided outside the block' do
subject.params do
group :drink, type: Hash do
optional :wine
optional :beer
optional :juice
exactly_one_of :beer, :wine, :juice
end
end
subject.get '/exactly_one_of_group' do
'exactly_one_of_group works!'
end
get '/exactly_one_of_group', drink: { foo: 'bar' }, beer: 'true'
expect(last_response.status).to eq(400)
end
end
end
end
grape-0.13.0/spec/grape/util/ 0000755 0000041 0000041 00000000000 12563420522 015727 5 ustar www-data www-data grape-0.13.0/spec/grape/util/inheritable_values_spec.rb 0000644 0000041 0000041 00000004264 12563420522 023141 0 ustar www-data www-data require 'spec_helper'
module Grape
module Util
describe InheritableValues do
let(:parent) { InheritableValues.new }
subject { InheritableValues.new(parent) }
describe '#delete' do
it 'deletes a key' do
subject[:some_thing] = :new_foo_bar
subject.delete :some_thing
expect(subject[:some_thing]).to be_nil
end
it 'does not delete parent values' do
parent[:some_thing] = :foo
subject[:some_thing] = :new_foo_bar
subject.delete :some_thing
expect(subject[:some_thing]).to eq :foo
end
end
describe '#[]' do
it 'returns a value' do
subject[:some_thing] = :foo
expect(subject[:some_thing]).to eq :foo
end
it 'returns parent value when no value is set' do
parent[:some_thing] = :foo
expect(subject[:some_thing]).to eq :foo
end
it 'overwrites parent value with the current one' do
parent[:some_thing] = :foo
subject[:some_thing] = :foo_bar
expect(subject[:some_thing]).to eq :foo_bar
end
it 'parent values are not changed' do
parent[:some_thing] = :foo
subject[:some_thing] = :foo_bar
expect(parent[:some_thing]).to eq :foo
end
end
describe '#[]=' do
it 'sets a value' do
subject[:some_thing] = :foo
expect(subject[:some_thing]).to eq :foo
end
end
describe '#to_hash' do
it 'returns a Hash representation' do
parent[:some_thing] = :foo
subject[:some_thing_more] = :foo_bar
expect(subject.to_hash).to eq(some_thing: :foo, some_thing_more: :foo_bar)
end
end
describe '#clone' do
let(:obj_cloned) { subject.clone }
context 'complex (i.e. not primitive) data types (ex. entity classes, please see bug #891)' do
let(:description) { { entity: double } }
before { subject[:description] = description }
it 'copies values; does not duplicate them' do
expect(obj_cloned[:description]).to eq description
end
end
end
end
end
end
grape-0.13.0/spec/grape/util/parameter_types_spec.rb 0000644 0000041 0000041 00000002530 12563420522 022472 0 ustar www-data www-data require 'spec_helper'
describe Grape::ParameterTypes do
class FooType
def self.parse(_)
end
end
class BarType
def self.parse
end
end
describe '::primitive?' do
[
Integer, Float, Numeric, BigDecimal,
Virtus::Attribute::Boolean, String, Symbol,
Date, DateTime, Time, Rack::Multipart::UploadedFile
].each do |type|
it "recognizes #{type} as a primitive" do
expect(described_class.primitive?(type)).to be_truthy
end
end
it 'identifies unknown types' do
expect(described_class.primitive?(Object)).to be_falsy
expect(described_class.primitive?(FooType)).to be_falsy
end
end
describe '::structure?' do
[
Hash, Array, Set
].each do |type|
it "recognizes #{type} as a structure" do
expect(described_class.structure?(type)).to be_truthy
end
end
end
describe '::custom_type?' do
it 'returns false if the type does not respond to :parse' do
expect(described_class.custom_type?(Object)).to be_falsy
end
it 'returns true if the type responds to :parse with one argument' do
expect(described_class.custom_type?(FooType)).to be_truthy
end
it 'returns false if the type\'s #parse method takes other than one argument' do
expect(described_class.custom_type?(BarType)).to be_falsy
end
end
end
grape-0.13.0/spec/grape/util/inheritable_setting_spec.rb 0000644 0000041 0000041 00000020663 12563420522 023320 0 ustar www-data www-data require 'spec_helper'
module Grape
module Util
describe InheritableSetting do
before :each do
InheritableSetting.reset_global!
end
let(:parent) do
Grape::Util::InheritableSetting.new.tap do |settings|
settings.global[:global_thing] = :global_foo_bar
settings.namespace[:namespace_thing] = :namespace_foo_bar
settings.namespace_inheritable[:namespace_inheritable_thing] = :namespace_inheritable_foo_bar
settings.namespace_stackable[:namespace_stackable_thing] = :namespace_stackable_foo_bar
settings.route[:route_thing] = :route_foo_bar
end
end
let(:other_parent) do
Grape::Util::InheritableSetting.new.tap do |settings|
settings.namespace[:namespace_thing] = :namespace_foo_bar_other
settings.namespace_inheritable[:namespace_inheritable_thing] = :namespace_inheritable_foo_bar_other
settings.namespace_stackable[:namespace_stackable_thing] = :namespace_stackable_foo_bar_other
settings.route[:route_thing] = :route_foo_bar_other
end
end
before :each do
subject.inherit_from parent
end
describe '#global' do
it 'sets a global value' do
subject.global[:some_thing] = :foo_bar
expect(subject.global[:some_thing]).to eq :foo_bar
subject.global[:some_thing] = :foo_bar_next
expect(subject.global[:some_thing]).to eq :foo_bar_next
end
it 'sets the global inherited values' do
expect(subject.global[:global_thing]).to eq :global_foo_bar
end
it 'overrides global values' do
subject.global[:global_thing] = :global_new_foo_bar
expect(parent.global[:global_thing]).to eq :global_new_foo_bar
end
it 'should handle different parents' do
subject.global[:global_thing] = :global_new_foo_bar
subject.inherit_from other_parent
expect(parent.global[:global_thing]).to eq :global_new_foo_bar
expect(other_parent.global[:global_thing]).to eq :global_new_foo_bar
end
end
describe '#api_class' do
it 'is specific to the class' do
subject.api_class[:some_thing] = :foo_bar
parent.api_class[:some_thing] = :some_thing
expect(subject.api_class[:some_thing]).to eq :foo_bar
expect(parent.api_class[:some_thing]).to eq :some_thing
end
end
describe '#namespace' do
it 'sets a value until the end of a namespace' do
subject.namespace[:some_thing] = :foo_bar
expect(subject.namespace[:some_thing]).to eq :foo_bar
end
it 'uses new values when a new namespace starts' do
subject.namespace[:namespace_thing] = :new_namespace_foo_bar
expect(subject.namespace[:namespace_thing]).to eq :new_namespace_foo_bar
expect(parent.namespace[:namespace_thing]).to eq :namespace_foo_bar
end
end
describe '#namespace_inheritable' do
it 'works with inheritable values' do
expect(subject.namespace_inheritable[:namespace_inheritable_thing]).to eq :namespace_inheritable_foo_bar
end
it 'should handle different parents' do
expect(subject.namespace_inheritable[:namespace_inheritable_thing]).to eq :namespace_inheritable_foo_bar
subject.inherit_from other_parent
expect(subject.namespace_inheritable[:namespace_inheritable_thing]).to eq :namespace_inheritable_foo_bar_other
subject.inherit_from parent
expect(subject.namespace_inheritable[:namespace_inheritable_thing]).to eq :namespace_inheritable_foo_bar
subject.inherit_from other_parent
subject.namespace_inheritable[:namespace_inheritable_thing] = :my_thing
expect(subject.namespace_inheritable[:namespace_inheritable_thing]).to eq :my_thing
subject.inherit_from parent
expect(subject.namespace_inheritable[:namespace_inheritable_thing]).to eq :my_thing
end
end
describe '#namespace_stackable' do
it 'works with stackable values' do
expect(subject.namespace_stackable[:namespace_stackable_thing]).to eq [:namespace_stackable_foo_bar]
subject.inherit_from other_parent
expect(subject.namespace_stackable[:namespace_stackable_thing]).to eq [:namespace_stackable_foo_bar_other]
end
end
describe '#route' do
it 'sets a value until the next route' do
subject.route[:some_thing] = :foo_bar
expect(subject.route[:some_thing]).to eq :foo_bar
subject.route_end
expect(subject.route[:some_thing]).to be_nil
end
it 'works with route values' do
expect(subject.route[:route_thing]).to eq :route_foo_bar
end
end
describe '#api_class' do
it 'is specific to the class' do
subject.api_class[:some_thing] = :foo_bar
expect(subject.api_class[:some_thing]).to eq :foo_bar
end
end
describe '#inherit_from' do
it 'notifies clones' do
new_settings = subject.point_in_time_copy
expect(new_settings).to receive(:inherit_from).with(other_parent)
subject.inherit_from other_parent
end
end
describe '#point_in_time_copy' do
let!(:cloned_obj) { subject.point_in_time_copy }
it 'resets point_in_time_copies' do
expect(cloned_obj.point_in_time_copies).to be_empty
end
it 'decouples namespace values' do
subject.namespace[:namespace_thing] = :namespace_foo_bar
cloned_obj.namespace[:namespace_thing] = :new_namespace_foo_bar
expect(subject.namespace[:namespace_thing]).to eq :namespace_foo_bar
end
it 'decouples namespace inheritable values' do
expect(cloned_obj.namespace_inheritable[:namespace_inheritable_thing]).to eq :namespace_inheritable_foo_bar
subject.namespace_inheritable[:namespace_inheritable_thing] = :my_thing
expect(subject.namespace_inheritable[:namespace_inheritable_thing]).to eq :my_thing
expect(cloned_obj.namespace_inheritable[:namespace_inheritable_thing]).to eq :namespace_inheritable_foo_bar
cloned_obj.namespace_inheritable[:namespace_inheritable_thing] = :my_cloned_thing
expect(cloned_obj.namespace_inheritable[:namespace_inheritable_thing]).to eq :my_cloned_thing
expect(subject.namespace_inheritable[:namespace_inheritable_thing]).to eq :my_thing
end
it 'decouples namespace stackable values' do
expect(cloned_obj.namespace_stackable[:namespace_stackable_thing]).to eq [:namespace_stackable_foo_bar]
subject.namespace_stackable[:namespace_stackable_thing] = :other_thing
expect(subject.namespace_stackable[:namespace_stackable_thing]).to eq [:namespace_stackable_foo_bar, :other_thing]
expect(cloned_obj.namespace_stackable[:namespace_stackable_thing]).to eq [:namespace_stackable_foo_bar]
end
it 'decouples route values' do
expect(cloned_obj.route[:route_thing]).to eq :route_foo_bar
subject.route[:route_thing] = :new_route_foo_bar
expect(cloned_obj.route[:route_thing]).to eq :route_foo_bar
end
it 'adds itself to original as clone' do
expect(subject.point_in_time_copies).to include(cloned_obj)
end
end
describe '#to_hash' do
it 'return all settings as a hash' do
subject.global[:global_thing] = :global_foo_bar
subject.namespace[:namespace_thing] = :namespace_foo_bar
subject.namespace_inheritable[:namespace_inheritable_thing] = :namespace_inheritable_foo_bar
subject.namespace_stackable[:namespace_stackable_thing] = [:namespace_stackable_foo_bar]
subject.route[:route_thing] = :route_foo_bar
expect(subject.to_hash).to include(global: { global_thing: :global_foo_bar })
expect(subject.to_hash).to include(namespace: { namespace_thing: :namespace_foo_bar })
expect(subject.to_hash).to include(namespace_inheritable: {
namespace_inheritable_thing: :namespace_inheritable_foo_bar })
expect(subject.to_hash).to include(namespace_stackable: { namespace_stackable_thing: [:namespace_stackable_foo_bar, [:namespace_stackable_foo_bar]] })
expect(subject.to_hash).to include(route: { route_thing: :route_foo_bar })
end
end
end
end
end
grape-0.13.0/spec/grape/util/stackable_values_spec.rb 0000644 0000041 0000041 00000007534 12563420522 022607 0 ustar www-data www-data require 'spec_helper'
module Grape
module Util
describe StackableValues do
let(:parent) { StackableValues.new }
subject { StackableValues.new(parent) }
describe '#keys' do
it 'returns all key' do
subject[:some_thing] = :foo_bar
subject[:some_thing_else] = :foo_bar
expect(subject.keys).to eq [:some_thing, :some_thing_else].sort
end
it 'returns merged keys with parent' do
parent[:some_thing] = :foo
parent[:some_thing_else] = :foo
subject[:some_thing] = :foo_bar
subject[:some_thing_more] = :foo_bar
expect(subject.keys).to eq [:some_thing, :some_thing_else, :some_thing_more].sort
end
end
describe '#delete' do
it 'deletes a key' do
subject[:some_thing] = :new_foo_bar
subject.delete :some_thing
expect(subject[:some_thing]).to eq []
end
it 'does not delete parent values' do
parent[:some_thing] = :foo
subject[:some_thing] = :new_foo_bar
subject.delete :some_thing
expect(subject[:some_thing]).to eq [:foo]
end
end
describe '#[]' do
it 'returns an array of values' do
subject[:some_thing] = :foo
expect(subject[:some_thing]).to eq [:foo]
end
it 'returns parent value when no value is set' do
parent[:some_thing] = :foo
expect(subject[:some_thing]).to eq [:foo]
end
it 'combines parent and actual values' do
parent[:some_thing] = :foo
subject[:some_thing] = :foo_bar
expect(subject[:some_thing]).to eq [:foo, :foo_bar]
end
it 'parent values are not changed' do
parent[:some_thing] = :foo
subject[:some_thing] = :foo_bar
expect(parent[:some_thing]).to eq [:foo]
end
end
describe '#[]=' do
it 'sets a value' do
subject[:some_thing] = :foo
expect(subject[:some_thing]).to eq [:foo]
end
it 'pushes further values' do
subject[:some_thing] = :foo
subject[:some_thing] = :bar
expect(subject[:some_thing]).to eq [:foo, :bar]
end
it 'can handle array values' do
subject[:some_thing] = :foo
subject[:some_thing] = [:bar, :more]
expect(subject[:some_thing]).to eq [:foo, [:bar, :more]]
parent[:some_thing_else] = [:foo, :bar]
subject[:some_thing_else] = [:some, :bar, :foo]
expect(subject[:some_thing_else]).to eq [[:foo, :bar], [:some, :bar, :foo]]
end
end
describe '#to_hash' do
it 'returns a Hash representation' do
parent[:some_thing] = :foo
subject[:some_thing] = [:bar, :more]
subject[:some_thing_more] = :foo_bar
expect(subject.to_hash).to eq(some_thing: [:foo, [:bar, :more]], some_thing_more: [:foo_bar])
end
end
describe '#clone' do
let(:obj_cloned) { subject.clone }
it 'copies all values' do
parent = StackableValues.new
child = StackableValues.new parent
grandchild = StackableValues.new child
parent[:some_thing] = :foo
child[:some_thing] = [:bar, :more]
grandchild[:some_thing] = :grand_foo_bar
grandchild[:some_thing_more] = :foo_bar
expect(grandchild.clone.to_hash).to eq(some_thing: [:foo, [:bar, :more], :grand_foo_bar], some_thing_more: [:foo_bar])
end
context 'complex (i.e. not primitive) data types (ex. middleware, please see bug #930)' do
let(:middleware) { double }
before { subject[:middleware] = middleware }
it 'copies values; does not duplicate them' do
expect(obj_cloned[:middleware]).to eq [middleware]
end
end
end
end
end
end
grape-0.13.0/spec/grape/util/strict_hash_configuration_spec.rb 0000644 0000041 0000041 00000001726 12563420522 024536 0 ustar www-data www-data require 'spec_helper'
module Grape
module Util
describe 'StrictHashConfiguration' do
subject do
Class.new do
include Grape::Util::StrictHashConfiguration.module(:config1, :config2, config3: [:config4], config5: [config6: [:config7, :config8]])
end
end
it 'set nested configs' do
subject.configure do
config1 'alpha'
config2 'beta'
config3 do
config4 'gamma'
end
local_var = 8
config5 do
config6 do
config7 7
config8 local_var
end
end
end
expect(subject.settings).to eq(config1: 'alpha',
config2: 'beta',
config3: { config4: 'gamma' },
config5: { config6: { config7: 7, config8: 8 } }
)
end
end
end
end
grape-0.13.0/spec/grape/path_spec.rb 0000644 0000041 0000041 00000016354 12563420522 017256 0 ustar www-data www-data require 'spec_helper'
module Grape
describe Path do
describe '#initialize' do
it 'remembers the path' do
path = Path.new('/:id', anything, anything)
expect(path.raw_path).to eql('/:id')
end
it 'remembers the namespace' do
path = Path.new(anything, '/users', anything)
expect(path.namespace).to eql('/users')
end
it 'remebers the settings' do
path = Path.new(anything, anything, foo: 'bar')
expect(path.settings).to eql(foo: 'bar')
end
end
describe '#mount_path' do
it 'is nil when no mount path setting exists' do
path = Path.new(anything, anything, {})
expect(path.mount_path).to be_nil
end
it 'is nil when the mount path is nil' do
path = Path.new(anything, anything, mount_path: nil)
expect(path.mount_path).to be_nil
end
it 'splits the mount path' do
path = Path.new(anything, anything, mount_path: %w(foo bar))
expect(path.mount_path).to eql(%w(foo bar))
end
end
describe '#root_prefix' do
it 'is nil when no root prefix setting exists' do
path = Path.new(anything, anything, {})
expect(path.root_prefix).to be_nil
end
it 'is nil when the mount path is nil' do
path = Path.new(anything, anything, root_prefix: nil)
expect(path.root_prefix).to be_nil
end
it 'splits the mount path' do
path = Path.new(anything, anything, root_prefix: 'hello/world')
expect(path.root_prefix).to eql(%w(hello world))
end
end
describe '#uses_path_versioning?' do
it 'is false when the version setting is nil' do
path = Path.new(anything, anything, version: nil)
expect(path.uses_path_versioning?).to be false
end
it 'is false when the version option is header' do
path = Path.new(
anything,
anything,
version: 'v1',
version_options: { using: :header }
)
expect(path.uses_path_versioning?).to be false
end
it 'is true when the version option is path' do
path = Path.new(
anything,
anything,
version: 'v1',
version_options: { using: :path }
)
expect(path.uses_path_versioning?).to be true
end
end
describe '#has_namespace?' do
it 'is false when the namespace is nil' do
path = Path.new(anything, nil, anything)
expect(path).not_to have_namespace
end
it 'is false when the namespace starts with whitespace' do
path = Path.new(anything, ' /foo', anything)
expect(path).not_to have_namespace
end
it 'is false when the namespace is the root path' do
path = Path.new(anything, '/', anything)
expect(path).not_to have_namespace
end
it 'is true otherwise' do
path = Path.new(anything, '/world', anything)
expect(path).to have_namespace
end
end
describe '#has_path?' do
it 'is false when the path is nil' do
path = Path.new(nil, anything, anything)
expect(path).not_to have_path
end
it 'is false when the path starts with whitespace' do
path = Path.new(' /foo', anything, anything)
expect(path).not_to have_path
end
it 'is false when the path is the root path' do
path = Path.new('/', anything, anything)
expect(path).not_to have_path
end
it 'is true otherwise' do
path = Path.new('/hello', anything, anything)
expect(path).to have_path
end
end
describe '#path' do
context 'mount_path' do
it 'is not included when it is nil' do
path = Path.new(nil, nil, mount_path: '/foo/bar')
expect(path.path).to eql '/foo/bar'
end
it 'is included when it is not nil' do
path = Path.new(nil, nil, {})
expect(path.path).to eql('/')
end
end
context 'root_prefix' do
it 'is not included when it is nil' do
path = Path.new(nil, nil, {})
expect(path.path).to eql('/')
end
it 'is included after the mount path' do
path = Path.new(
nil,
nil,
mount_path: '/foo',
root_prefix: '/hello'
)
expect(path.path).to eql('/foo/hello')
end
end
it 'uses the namespace after the mount path and root prefix' do
path = Path.new(
nil,
'namespace',
mount_path: '/foo',
root_prefix: '/hello'
)
expect(path.path).to eql('/foo/hello/namespace')
end
it 'uses the raw path after the namespace' do
path = Path.new(
'raw_path',
'namespace',
mount_path: '/foo',
root_prefix: '/hello'
)
expect(path.path).to eql('/foo/hello/namespace/raw_path')
end
end
describe '#suffix' do
context 'when using a specific format' do
it 'accepts specified format' do
path = Path.new(nil, nil, {})
allow(path).to receive(:uses_specific_format?) { true }
allow(path).to receive(:settings) { { format: :json } }
expect(path.suffix).to eql('(.json)')
end
end
context 'when path versioning is used' do
it "includes a '/'" do
path = Path.new(nil, nil, {})
allow(path).to receive(:uses_specific_format?) { false }
allow(path).to receive(:uses_path_versioning?) { true }
expect(path.suffix).to eql('(/.:format)')
end
end
context 'when path versioning is not used' do
it "does not include a '/' when the path has a namespace" do
path = Path.new(nil, 'namespace', {})
allow(path).to receive(:uses_specific_format?) { false }
allow(path).to receive(:uses_path_versioning?) { true }
expect(path.suffix).to eql('(.:format)')
end
it "does not include a '/' when the path has a path" do
path = Path.new('/path', nil, {})
allow(path).to receive(:uses_specific_format?) { false }
allow(path).to receive(:uses_path_versioning?) { true }
expect(path.suffix).to eql('(.:format)')
end
it "includes a '/' otherwise" do
path = Path.new(nil, nil, {})
allow(path).to receive(:uses_specific_format?) { false }
allow(path).to receive(:uses_path_versioning?) { true }
expect(path.suffix).to eql('(/.:format)')
end
end
end
describe '#path_with_suffix' do
it 'combines the path and suffix' do
path = Path.new(nil, nil, {})
allow(path).to receive(:path) { '/the/path' }
allow(path).to receive(:suffix) { 'suffix' }
expect(path.path_with_suffix).to eql('/the/pathsuffix')
end
context 'when using a specific format' do
it 'might have a suffix with specified format' do
path = Path.new(nil, nil, {})
allow(path).to receive(:path) { '/the/path' }
allow(path).to receive(:uses_specific_format?) { true }
allow(path).to receive(:settings) { { format: :json } }
expect(path.path_with_suffix).to eql('/the/path(.json)')
end
end
end
end
end
grape-0.13.0/spec/grape/loading_spec.rb 0000644 0000041 0000041 00000001452 12563420522 017730 0 ustar www-data www-data require 'spec_helper'
describe Grape::API do
let(:jobs_api) do
Class.new(Grape::API) do
namespace :one do
namespace :two do
namespace :three do
get :one do
end
get :two do
end
end
end
end
end
end
let(:combined_api) do
JobsApi = jobs_api
Class.new(Grape::API) do
version :v1, using: :accept_version_header, cascade: true
mount JobsApi
end
end
subject do
CombinedApi = combined_api
Class.new(Grape::API) do
format :json
mount CombinedApi => '/'
end
end
def app
subject
end
it 'execute first request in reasonable time' do
started = Time.now
get '/mount1/nested/test_method'
expect(Time.now - started).to be < 5
end
end
grape-0.13.0/spec/spec_helper.rb 0000644 0000041 0000041 00000001242 12563420522 016471 0 ustar www-data www-data $LOAD_PATH.unshift(File.dirname(__FILE__))
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), 'support'))
require 'grape'
require 'rubygems'
require 'bundler'
Bundler.setup :default, :test
require 'json'
require 'rack/test'
require 'base64'
require 'cookiejar'
require 'json'
require 'mime/types'
Dir["#{File.dirname(__FILE__)}/support/*.rb"].each do |file|
require file
end
I18n.enforce_available_locales = false
RSpec.configure do |config|
config.include Rack::Test::Methods
config.raise_errors_for_deprecations!
config.before(:each) { Grape::Util::InheritableSetting.reset_global! }
end
grape-0.13.0/spec/shared/ 0000755 0000041 0000041 00000000000 12563420522 015122 5 ustar www-data www-data grape-0.13.0/spec/shared/versioning_examples.rb 0000644 0000041 0000041 00000011055 12563420522 021532 0 ustar www-data www-data shared_examples_for 'versioning' do
it 'sets the API version' do
subject.format :txt
subject.version 'v1', macro_options
subject.get :hello do
"Version: #{request.env['api.version'] }"
end
versioned_get '/hello', 'v1', macro_options
expect(last_response.body).to eql 'Version: v1'
end
it 'adds the prefix before the API version' do
subject.format :txt
subject.prefix 'api'
subject.version 'v1', macro_options
subject.get :hello do
"Version: #{request.env['api.version'] }"
end
versioned_get '/hello', 'v1', macro_options.merge(prefix: 'api')
expect(last_response.body).to eql 'Version: v1'
end
it 'is able to specify version as a nesting' do
subject.version 'v2', macro_options
subject.get '/awesome' do
'Radical'
end
subject.version 'v1', macro_options do
get '/legacy' do
'Totally'
end
end
versioned_get '/awesome', 'v1', macro_options
expect(last_response.status).to eql 404
versioned_get '/awesome', 'v2', macro_options
expect(last_response.status).to eql 200
versioned_get '/legacy', 'v1', macro_options
expect(last_response.status).to eql 200
versioned_get '/legacy', 'v2', macro_options
expect(last_response.status).to eql 404
end
it 'is able to specify multiple versions' do
subject.version 'v1', 'v2', macro_options
subject.get 'awesome' do
'I exist'
end
versioned_get '/awesome', 'v1', macro_options
expect(last_response.status).to eql 200
versioned_get '/awesome', 'v2', macro_options
expect(last_response.status).to eql 200
versioned_get '/awesome', 'v3', macro_options
expect(last_response.status).to eql 404
end
context 'with different versions for the same endpoint' do
context 'without a prefix' do
it 'allows the same endpoint to be implemented' do
subject.format :txt
subject.version 'v2', macro_options
subject.get 'version' do
request.env['api.version']
end
subject.version 'v1', macro_options do
get 'version' do
'version ' + request.env['api.version']
end
end
versioned_get '/version', 'v2', macro_options
expect(last_response.status).to eq(200)
expect(last_response.body).to eq('v2')
versioned_get '/version', 'v1', macro_options
expect(last_response.status).to eq(200)
expect(last_response.body).to eq('version v1')
end
end
context 'with a prefix' do
it 'allows the same endpoint to be implemented' do
subject.format :txt
subject.prefix 'api'
subject.version 'v2', macro_options
subject.get 'version' do
request.env['api.version']
end
subject.version 'v1', macro_options do
get 'version' do
'version ' + request.env['api.version']
end
end
versioned_get '/version', 'v1', macro_options.merge(prefix: subject.prefix)
expect(last_response.status).to eq(200)
expect(last_response.body).to eq('version v1')
versioned_get '/version', 'v2', macro_options.merge(prefix: subject.prefix)
expect(last_response.status).to eq(200)
expect(last_response.body).to eq('v2')
end
end
end
context 'with before block defined within a version block' do
it 'calls before block that is defined within the version block' do
subject.format :txt
subject.prefix 'api'
subject.version 'v2', macro_options do
before do
@output ||= 'v2-'
end
get 'version' do
@output += 'version'
end
end
subject.version 'v1', macro_options do
before do
@output ||= 'v1-'
end
get 'version' do
@output += 'version'
end
end
versioned_get '/version', 'v1', macro_options.merge(prefix: subject.prefix)
expect(last_response.status).to eq(200)
expect(last_response.body).to eq('v1-version')
versioned_get '/version', 'v2', macro_options.merge(prefix: subject.prefix)
expect(last_response.status).to eq(200)
expect(last_response.body).to eq('v2-version')
end
end
it 'does not overwrite version parameter with API version' do
subject.format :txt
subject.version 'v1', macro_options
subject.params { requires :version }
subject.get :api_version_with_version_param do
params[:version]
end
versioned_get '/api_version_with_version_param?version=1', 'v1', macro_options
expect(last_response.body).to eql '1'
end
end
grape-0.13.0/spec/support/ 0000755 0000041 0000041 00000000000 12563420522 015370 5 ustar www-data www-data grape-0.13.0/spec/support/endpoint_faker.rb 0000644 0000041 0000041 00000000706 12563420522 020710 0 ustar www-data www-data module Spec
module Support
class EndpointFaker
class FakerAPI < Grape::API
get '/' do
end
end
def initialize(app, endpoint = FakerAPI.endpoints.first)
@app = app
@endpoint = endpoint
end
def call(env)
@endpoint.instance_exec do
@request = Grape::Request.new(env.dup)
end
@app.call(env.merge('api.endpoint' => @endpoint))
end
end
end
end
grape-0.13.0/spec/support/versioned_helpers.rb 0000644 0000041 0000041 00000002662 12563420522 021443 0 ustar www-data www-data # Versioning
# Returns the path with options[:version] prefixed if options[:using] is :path.
# Returns normal path otherwise.
def versioned_path(options = {})
case options[:using]
when :path
File.join('/', options[:prefix] || '', options[:version], options[:path])
when :param
File.join('/', options[:prefix] || '', options[:path])
when :header
File.join('/', options[:prefix] || '', options[:path])
when :accept_version_header
File.join('/', options[:prefix] || '', options[:path])
else
fail ArgumentError.new("unknown versioning strategy: #{options[:using]}")
end
end
def versioned_headers(options)
case options[:using]
when :path
{} # no-op
when :param
{} # no-op
when :header
{
'HTTP_ACCEPT' => [
"application/vnd.#{options[:vendor] }-#{options[:version] }",
options[:format]
].compact.join('+')
}
when :accept_version_header
{
'HTTP_ACCEPT_VERSION' => "#{options[:version] }"
}
else
fail ArgumentError.new("unknown versioning strategy: #{options[:using]}")
end
end
def versioned_get(path, version_name, version_options = {})
path = versioned_path(version_options.merge(version: version_name, path: path))
headers = versioned_headers(version_options.merge(version: version_name))
params = {}
if version_options[:using] == :param
params = { version_options[:parameter] => version_name }
end
get path, params, headers
end
grape-0.13.0/spec/support/content_type_helpers.rb 0000644 0000041 0000041 00000000503 12563420522 022150 0 ustar www-data www-data module ContentTypeHelpers
%w(put patch post delete).each do |method|
define_method :"#{method}_with_json" do |uri, params = {}, env = {}, &block|
params = params.to_json
env['CONTENT_TYPE'] ||= 'application/json'
send(method, uri, params, env, &block)
end
end
end
include(ContentTypeHelpers)
grape-0.13.0/spec/support/basic_auth_encode_helpers.rb 0000644 0000041 0000041 00000000146 12563420522 023057 0 ustar www-data www-data def encode_basic_auth(username, password)
'Basic ' + Base64.encode64("#{username}:#{password}")
end
grape-0.13.0/spec/support/file_streamer.rb 0000644 0000041 0000041 00000000270 12563420522 020535 0 ustar www-data www-data class FileStreamer
def initialize(file_path)
@file_path = file_path
end
def each(&blk)
File.open(@file_path, 'rb') do |file|
file.each(10, &blk)
end
end
end
grape-0.13.0/.travis.yml 0000644 0000041 0000041 00000000402 12563420522 015027 0 ustar www-data www-data language: ruby
rvm:
- 2.2
- 2.1
- 2.0.0
- rbx-2.2.10
- jruby-19mode
- ruby-head
- jruby-head
matrix:
allow_failures:
- rvm: ruby-head
- rvm: jruby-head
gemfile:
- Gemfile
- gemfiles/rails_3.gemfile
- gemfiles/rails_4.gemfile
grape-0.13.0/lib/ 0000755 0000041 0000041 00000000000 12563420522 013470 5 ustar www-data www-data grape-0.13.0/lib/grape/ 0000755 0000041 0000041 00000000000 12563420522 014566 5 ustar www-data www-data grape-0.13.0/lib/grape/middleware/ 0000755 0000041 0000041 00000000000 12563420522 016703 5 ustar www-data www-data grape-0.13.0/lib/grape/middleware/formatter.rb 0000644 0000041 0000041 00000012367 12563420522 021244 0 ustar www-data www-data require 'grape/middleware/base'
module Grape
module Middleware
class Formatter < Base
def default_options
{
default_format: :txt,
formatters: {},
parsers: {}
}
end
def before
negotiate_content_type
read_body_input
end
def after
status, headers, bodies = *@app_response
if bodies.is_a?(Grape::Util::FileResponse)
headers = ensure_content_type(headers)
response =
Rack::Response.new([], status, headers) do |resp|
resp.body = bodies.file
end
else
# Allow content-type to be explicitly overwritten
api_format = mime_types[headers[Grape::Http::Headers::CONTENT_TYPE]] || env['api.format']
formatter = Grape::Formatter::Base.formatter_for(api_format, options)
begin
bodymap = bodies.collect do |body|
formatter.call(body, env)
end
headers = ensure_content_type(headers)
response = Rack::Response.new(bodymap, status, headers)
rescue Grape::Exceptions::InvalidFormatter => e
throw :error, status: 500, message: e.message
end
end
response
end
private
# Set the content type header for the API format if it is not already present.
#
# @param headers [Hash]
# @return [Hash]
def ensure_content_type(headers)
if headers[Grape::Http::Headers::CONTENT_TYPE]
headers
else
headers.merge(Grape::Http::Headers::CONTENT_TYPE => content_type_for(env['api.format']))
end
end
def request
@request ||= Rack::Request.new(env)
end
# store read input in env['api.request.input']
def read_body_input
if (request.post? || request.put? || request.patch? || request.delete?) &&
(!request.form_data? || !request.media_type) &&
(!request.parseable_data?) &&
(request.content_length.to_i > 0 || request.env[Grape::Http::Headers::HTTP_TRANSFER_ENCODING] == 'chunked')
if (input = env['rack.input'])
input.rewind
body = env['api.request.input'] = input.read
begin
read_rack_input(body) if body && body.length > 0
ensure
input.rewind
end
end
end
end
# store parsed input in env['api.request.body']
def read_rack_input(body)
fmt = mime_types[request.media_type] if request.media_type
fmt ||= options[:default_format]
if content_type_for(fmt)
parser = Grape::Parser::Base.parser_for fmt, options
if parser
begin
body = (env['api.request.body'] = parser.call(body, env))
if body.is_a?(Hash)
if env['rack.request.form_hash']
env['rack.request.form_hash'] = env['rack.request.form_hash'].merge(body)
else
env['rack.request.form_hash'] = body
end
env['rack.request.form_input'] = env['rack.input']
end
rescue Grape::Exceptions::Base => e
raise e
rescue StandardError => e
throw :error, status: 400, message: e.message
end
else
env['api.request.body'] = body
end
else
throw :error, status: 406, message: "The requested content-type '#{request.media_type}' is not supported."
end
end
def negotiate_content_type
fmt = format_from_extension || format_from_params || options[:format] || format_from_header || options[:default_format]
if content_type_for(fmt)
env['api.format'] = fmt
else
throw :error, status: 406, message: "The requested format '#{fmt}' is not supported."
end
end
def format_from_extension
parts = request.path.split('.')
if parts.size > 1
extension = parts.last
# avoid symbol memory leak on an unknown format
return extension.to_sym if content_type_for(extension)
end
nil
end
def format_from_params
fmt = Rack::Utils.parse_nested_query(env[Grape::Http::Headers::QUERY_STRING])[Grape::Http::Headers::FORMAT]
# avoid symbol memory leak on an unknown format
return fmt.to_sym if content_type_for(fmt)
fmt
end
def format_from_header
mime_array.each do |t|
return mime_types[t] if mime_types.key?(t)
end
nil
end
def mime_array
accept = env[Grape::Http::Headers::HTTP_ACCEPT]
return [] unless accept
accept_into_mime_and_quality = %r{
(
\w+/[\w+.-]+) # eg application/vnd.example.myformat+xml
(?:
(?:;[^,]*?)? # optionally multiple formats in a row
;\s*q=([\d.]+) # optional "quality" preference (eg q=0.5)
)?
}x
vendor_prefix_pattern = /vnd\.[^+]+\+/
accept.scan(accept_into_mime_and_quality)
.sort_by { |_, quality_preference| -quality_preference.to_f }
.map { |mime, _| mime.sub(vendor_prefix_pattern, '') }
end
end
end
end
grape-0.13.0/lib/grape/middleware/versioner/ 0000755 0000041 0000041 00000000000 12563420522 020717 5 ustar www-data www-data grape-0.13.0/lib/grape/middleware/versioner/path.rb 0000644 0000041 0000041 00000002725 12563420522 022206 0 ustar www-data www-data require 'grape/middleware/base'
module Grape
module Middleware
module Versioner
# This middleware sets various version related rack environment variables
# based on the uri path and removes the version substring from the uri
# path. If the version substring does not match any potential initialized
# versions, a 404 error is thrown.
#
# Example: For a uri path
# /v1/resource
#
# The following rack env variables are set and path is rewritten to
# '/resource':
#
# env['api.version'] => 'v1'
#
class Path < Base
def default_options
{
pattern: /.*/i
}
end
def before
path = env[Grape::Http::Headers::PATH_INFO].dup
if prefix && path.index(prefix) == 0
path.sub!(prefix, '')
path = Rack::Mount::Utils.normalize_path(path)
end
pieces = path.split('/')
potential_version = pieces[1]
if potential_version =~ options[:pattern]
if options[:versions] && !options[:versions].find { |v| v.to_s == potential_version }
throw :error, status: 404, message: '404 API Version Not Found'
end
env['api.version'] = potential_version
end
end
private
def prefix
Rack::Mount::Utils.normalize_path(options[:prefix].to_s) if options[:prefix]
end
end
end
end
end
grape-0.13.0/lib/grape/middleware/versioner/param.rb 0000644 0000041 0000041 00000002772 12563420522 022354 0 ustar www-data www-data require 'grape/middleware/base'
module Grape
module Middleware
module Versioner
# This middleware sets various version related rack environment variables
# based on the request parameters and removes that parameter from the
# request parameters for subsequent middleware and API.
# If the version substring does not match any potential initialized
# versions, a 404 error is thrown.
# If the version substring is not passed the version (highest mounted)
# version will be used.
#
# Example: For a uri path
# /resource?apiver=v1
#
# The following rack env variables are set and path is rewritten to
# '/resource':
#
# env['api.version'] => 'v1'
class Param < Base
def default_options
{
parameter: 'apiver'
}
end
def before
paramkey = options[:parameter]
potential_version = Rack::Utils.parse_nested_query(env[Grape::Http::Headers::QUERY_STRING])[paramkey]
unless potential_version.nil?
if options[:versions] && !options[:versions].find { |v| v.to_s == potential_version }
throw :error, status: 404, message: '404 API Version Not Found', headers: { Grape::Http::Headers::X_CASCADE => 'pass' }
end
env['api.version'] = potential_version
env['rack.request.query_hash'].delete(paramkey) if env.key? 'rack.request.query_hash'
end
end
end
end
end
end
grape-0.13.0/lib/grape/middleware/versioner/header.rb 0000644 0000041 0000041 00000012125 12563420522 022475 0 ustar www-data www-data require 'grape/middleware/base'
module Grape
module Middleware
module Versioner
# This middleware sets various version related rack environment variables
# based on the HTTP Accept header with the pattern:
# application/vnd.:vendor-:version+:format
#
# Example: For request header
# Accept: application/vnd.mycompany-v1+json
#
# The following rack env variables are set:
#
# env['api.type'] => 'application'
# env['api.subtype'] => 'vnd.mycompany-v1+json'
# env['api.vendor] => 'mycompany'
# env['api.version] => 'v1'
# env['api.format] => 'json'
#
# If version does not match this route, then a 406 is raised with
# X-Cascade header to alert Rack::Mount to attempt the next matched
# route.
class Header < Base
def before
header = rack_accept_header
if strict?
# If no Accept header:
if header.qvalues.empty?
fail Grape::Exceptions::InvalidAcceptHeader.new('Accept header must be set.', error_headers)
end
# Remove any acceptable content types with ranges.
header.qvalues.reject! do |media_type, _|
Rack::Accept::Header.parse_media_type(media_type).find { |s| s == '*' }
end
# If all Accept headers included a range:
if header.qvalues.empty?
fail Grape::Exceptions::InvalidAcceptHeader.new('Accept header must not contain ranges ("*").',
error_headers)
end
end
media_type = header.best_of available_media_types
if media_type
type, subtype = Rack::Accept::Header.parse_media_type media_type
env['api.type'] = type
env['api.subtype'] = subtype
if /\Avnd\.([a-z0-9*.]+)(?:-([a-z0-9*\-.]+))?(?:\+([a-z0-9*\-.+]+))?\z/ =~ subtype
env['api.vendor'] = Regexp.last_match[1]
env['api.version'] = Regexp.last_match[2]
env['api.format'] = Regexp.last_match[3] # weird that Grape::Middleware::Formatter also does this
end
# If none of the available content types are acceptable:
elsif strict?
fail Grape::Exceptions::InvalidAcceptHeader.new('406 Not Acceptable', error_headers)
# If all acceptable content types specify a vendor or version that doesn't exist:
elsif header.values.all? { |header_value| has_vendor?(header_value) || version?(header_value) }
fail Grape::Exceptions::InvalidAcceptHeader.new('API vendor or version not found.', error_headers)
end
end
private
def available_media_types
available_media_types = []
content_types.each do |extension, _media_type|
versions.reverse_each do |version|
available_media_types += ["application/vnd.#{vendor}-#{version}+#{extension}", "application/vnd.#{vendor}-#{version}"]
end
available_media_types << "application/vnd.#{vendor}+#{extension}"
end
available_media_types << "application/vnd.#{vendor}"
content_types.each do |_, media_type|
available_media_types << media_type
end
available_media_types.flatten
end
def rack_accept_header
Rack::Accept::MediaType.new env[Grape::Http::Headers::HTTP_ACCEPT]
rescue RuntimeError => e
raise Grape::Exceptions::InvalidAcceptHeader.new(e.message, error_headers)
end
def versions
options[:versions] || []
end
def vendor
options[:version_options] && options[:version_options][:vendor]
end
def strict?
options[:version_options] && options[:version_options][:strict]
end
# By default those errors contain an `X-Cascade` header set to `pass`, which allows nesting and stacking
# of routes (see [Rack::Mount](https://github.com/josh/rack-mount) for more information). To prevent
# this behavior, and not add the `X-Cascade` header, one can set the `:cascade` option to `false`.
def cascade?
if options[:version_options] && options[:version_options].key?(:cascade)
!!options[:version_options][:cascade]
else
true
end
end
def error_headers
cascade? ? { Grape::Http::Headers::X_CASCADE => 'pass' } : {}
end
# @param [String] media_type a content type
# @return [Boolean] whether the content type sets a vendor
def has_vendor?(media_type)
_, subtype = Rack::Accept::Header.parse_media_type media_type
subtype[/\Avnd\.[a-z0-9*.]+/]
end
# @param [String] media_type a content type
# @return [Boolean] whether the content type sets an API version
def version?(media_type)
_, subtype = Rack::Accept::Header.parse_media_type media_type
subtype[/\Avnd\.[a-z0-9*.]+-[a-z0-9*\-.]+/]
end
end
end
end
end
grape-0.13.0/lib/grape/middleware/versioner/accept_version_header.rb 0000644 0000041 0000041 00000004243 12563420522 025563 0 ustar www-data www-data require 'grape/middleware/base'
module Grape
module Middleware
module Versioner
# This middleware sets various version related rack environment variables
# based on the HTTP Accept-Version header
#
# Example: For request header
# Accept-Version: v1
#
# The following rack env variables are set:
#
# env['api.version'] => 'v1'
#
# If version does not match this route, then a 406 is raised with
# X-Cascade header to alert Rack::Mount to attempt the next matched
# route.
class AcceptVersionHeader < Base
def before
potential_version = (env[Grape::Http::Headers::HTTP_ACCEPT_VERSION] || '').strip
if strict?
# If no Accept-Version header:
if potential_version.empty?
throw :error, status: 406, headers: error_headers, message: 'Accept-Version header must be set.'
end
end
unless potential_version.empty?
# If the requested version is not supported:
unless versions.any? { |v| v.to_s == potential_version }
throw :error, status: 406, headers: error_headers, message: 'The requested version is not supported.'
end
env['api.version'] = potential_version
end
end
private
def versions
options[:versions] || []
end
def strict?
options[:version_options] && options[:version_options][:strict]
end
# By default those errors contain an `X-Cascade` header set to `pass`, which allows nesting and stacking
# of routes (see [Rack::Mount](https://github.com/josh/rack-mount) for more information). To prevent
# this behavior, and not add the `X-Cascade` header, one can set the `:cascade` option to `false`.
def cascade?
if options[:version_options] && options[:version_options].key?(:cascade)
!!options[:version_options][:cascade]
else
true
end
end
def error_headers
cascade? ? { Grape::Http::Headers::X_CASCADE => 'pass' } : {}
end
end
end
end
end
grape-0.13.0/lib/grape/middleware/auth/ 0000755 0000041 0000041 00000000000 12563420522 017644 5 ustar www-data www-data grape-0.13.0/lib/grape/middleware/auth/strategy_info.rb 0000644 0000041 0000041 00000000465 12563420522 023053 0 ustar www-data www-data module Grape
module Middleware
module Auth
StrategyInfo = Struct.new(:auth_class, :settings_fetcher) do
def create(app, options, &block)
strategy_args = settings_fetcher.call(options)
auth_class.new(app, *strategy_args, &block)
end
end
end
end
end
grape-0.13.0/lib/grape/middleware/auth/strategies.rb 0000644 0000041 0000041 00000001220 12563420522 022336 0 ustar www-data www-data module Grape
module Middleware
module Auth
module Strategies
module_function
def add(label, strategy, option_fetcher = ->(_) { [] })
auth_strategies[label] = StrategyInfo.new(strategy, option_fetcher)
end
def auth_strategies
@auth_strategies ||= {
http_basic: StrategyInfo.new(Rack::Auth::Basic, ->(settings) { [settings[:realm]] }),
http_digest: StrategyInfo.new(Rack::Auth::Digest::MD5, ->(settings) { [settings[:realm], settings[:opaque]] })
}
end
def [](label)
auth_strategies[label]
end
end
end
end
end
grape-0.13.0/lib/grape/middleware/auth/base.rb 0000644 0000041 0000041 00000001763 12563420522 021112 0 ustar www-data www-data require 'rack/auth/basic'
module Grape
module Middleware
module Auth
class Base
attr_accessor :options, :app, :env
def initialize(app, options = {})
@app = app
@options = options || {}
end
def context
env['api.endpoint']
end
def call(env)
dup._call(env)
end
def _call(env)
self.env = env
if options.key?(:type)
auth_proc = options[:proc]
auth_proc_context = context
strategy_info = Grape::Middleware::Auth::Strategies[options[:type]]
throw(:error, status: 401, message: 'API Authorization Failed.') unless strategy_info.present?
strategy = strategy_info.create(@app, options) do |*args|
auth_proc_context.instance_exec(*args, &auth_proc)
end
strategy.call(env)
else
app.call(env)
end
end
end
end
end
end
grape-0.13.0/lib/grape/middleware/auth/dsl.rb 0000644 0000041 0000041 00000002353 12563420522 020756 0 ustar www-data www-data require 'rack/auth/basic'
require 'active_support/concern'
module Grape
module Middleware
module Auth
module DSL
extend ActiveSupport::Concern
module ClassMethods
# Add an authentication type to the API. Currently
# only `:http_basic`, `:http_digest` are supported.
def auth(type = nil, options = {}, &block)
if type
namespace_inheritable(:auth, { type: type.to_sym, proc: block }.merge(options))
use Grape::Middleware::Auth::Base, namespace_inheritable(:auth)
else
namespace_inheritable(:auth)
end
end
# Add HTTP Basic authorization to the API.
#
# @param [Hash] options A hash of options.
# @option options [String] :realm "API Authorization" The HTTP Basic realm.
def http_basic(options = {}, &block)
options[:realm] ||= 'API Authorization'
auth :http_basic, options, &block
end
def http_digest(options = {}, &block)
options[:realm] ||= 'API Authorization'
options[:opaque] ||= 'secret'
auth :http_digest, options, &block
end
end
end
end
end
end
grape-0.13.0/lib/grape/middleware/filter.rb 0000644 0000041 0000041 00000000737 12563420522 020524 0 ustar www-data www-data module Grape
module Middleware
# This is a simple middleware for adding before and after filters
# to Grape APIs. It is used like so:
#
# use Grape::Middleware::Filter, before: lambda { do_something }, after: lambda { do_something }
class Filter < Base
def before
app.instance_eval(&options[:before]) if options[:before]
end
def after
app.instance_eval(&options[:after]) if options[:after]
end
end
end
end
grape-0.13.0/lib/grape/middleware/globals.rb 0000644 0000041 0000041 00000000537 12563420522 020660 0 ustar www-data www-data require 'grape/middleware/base'
module Grape
module Middleware
class Globals < Base
def before
request = Grape::Request.new(@env)
@env['grape.request'] = request
@env['grape.request.headers'] = request.headers
@env['grape.request.params'] = request.params if @env['rack.input']
end
end
end
end
grape-0.13.0/lib/grape/middleware/versioner.rb 0000644 0000041 0000041 00000001572 12563420522 021251 0 ustar www-data www-data # Versioners set env['api.version'] when a version is defined on an API and
# on the requests. The current methods for determining version are:
#
# :header - version from HTTP Accept header.
# :path - version from uri. e.g. /v1/resource
# :param - version from uri query string, e.g. /v1/resource?apiver=v1
#
# See individual classes for details.
module Grape
module Middleware
module Versioner
module_function
# @param strategy [Symbol] :path, :header or :param
# @return a middleware class based on strategy
def using(strategy)
case strategy
when :path
Path
when :header
Header
when :param
Param
when :accept_version_header
AcceptVersionHeader
else
fail Grape::Exceptions::InvalidVersionerOption.new(strategy)
end
end
end
end
end
grape-0.13.0/lib/grape/middleware/base.rb 0000644 0000041 0000041 00000003166 12563420522 020150 0 ustar www-data www-data module Grape
module Middleware
class Base
attr_reader :app, :env, :options
# @param [Rack Application] app The standard argument for a Rack middleware.
# @param [Hash] options A hash of options, simply stored for use by subclasses.
def initialize(app, options = {})
@app = app
@options = default_options.merge(options)
end
def default_options
{}
end
def call(env)
dup.call!(env)
end
def call!(env)
@env = env
before
@app_response = @app.call(@env)
after || @app_response
end
# @abstract
# Called before the application is called in the middleware lifecycle.
def before
end
# @abstract
# Called after the application is called in the middleware lifecycle.
# @return [Response, nil] a Rack SPEC response or nil to call the application afterwards.
def after
end
def response
return @app_response if @app_response.is_a?(Rack::Response)
Rack::Response.new(@app_response[2], @app_response[0], @app_response[1])
end
def content_type_for(format)
HashWithIndifferentAccess.new(content_types)[format]
end
def content_types
ContentTypes.content_types_for(options[:content_types])
end
def content_type
content_type_for(env['api.format'] || options[:format]) || 'text/html'
end
def mime_types
content_types.each_with_object({}) do |(k, v), types_without_params|
types_without_params[k] = v.split(';').first
end.invert
end
end
end
end
grape-0.13.0/lib/grape/middleware/error.rb 0000644 0000041 0000041 00000006544 12563420522 020372 0 ustar www-data www-data require 'grape/middleware/base'
module Grape
module Middleware
class Error < Base
def default_options
{
default_status: 500, # default status returned on error
default_message: '',
format: :txt,
formatters: {},
error_formatters: {},
rescue_all: false, # true to rescue all exceptions
rescue_subclasses: true, # rescue subclasses of exceptions listed
rescue_options: { backtrace: false }, # true to display backtrace
rescue_handlers: {}, # rescue handler blocks
base_only_rescue_handlers: {}, # rescue handler blocks rescuing only the base class
all_rescue_handler: nil # rescue handler block to rescue from all exceptions
}
end
def call!(env)
@env = env
begin
error_response(catch(:error) do
return @app.call(@env)
end)
rescue StandardError => e
is_rescuable = rescuable?(e.class)
if e.is_a?(Grape::Exceptions::Base) && !is_rescuable
handler = ->(arg) { error_response(arg) }
else
raise unless is_rescuable
handler = find_handler(e.class)
end
handler.nil? ? handle_error(e) : exec_handler(e, &handler)
end
end
def find_handler(klass)
handler = options[:rescue_handlers].find(-> { [] }) { |error, _| klass <= error }[1]
handler ||= options[:base_only_rescue_handlers][klass]
handler ||= options[:all_rescue_handler]
handler
end
def rescuable?(klass)
options[:rescue_all] || (options[:rescue_handlers] || []).any? { |error, _handler| klass <= error } || (options[:base_only_rescue_handlers] || []).include?(klass)
end
def exec_handler(e, &handler)
if handler.lambda? && handler.arity == 0
instance_exec(&handler)
else
instance_exec(e, &handler)
end
end
def error!(message, status = options[:default_status], headers = {}, backtrace = [])
headers = { Grape::Http::Headers::CONTENT_TYPE => content_type }.merge(headers)
rack_response(format_message(message, backtrace), status, headers)
end
def handle_error(e)
error_response(message: e.message, backtrace: e.backtrace)
end
# TODO: This method is deprecated. Refactor out.
def error_response(error = {})
status = error[:status] || options[:default_status]
message = error[:message] || options[:default_message]
headers = { Grape::Http::Headers::CONTENT_TYPE => content_type }
headers.merge!(error[:headers]) if error[:headers].is_a?(Hash)
backtrace = error[:backtrace] || []
rack_response(format_message(message, backtrace), status, headers)
end
def rack_response(message, status = options[:default_status], headers = { Grape::Http::Headers::CONTENT_TYPE => content_type })
Rack::Response.new([message], status, headers).finish
end
def format_message(message, backtrace)
format = env['api.format'] || options[:format]
formatter = Grape::ErrorFormatter::Base.formatter_for(format, options)
throw :error, status: 406, message: "The requested format '#{format}' is not supported." unless formatter
formatter.call(message, backtrace, options, env)
end
end
end
end
grape-0.13.0/lib/grape/namespace.rb 0000644 0000041 0000041 00000002301 12563420522 017043 0 ustar www-data www-data module Grape
# A container for endpoints or other namespaces, which allows for both
# logical grouping of endpoints as well as sharing commonconfiguration.
# May also be referred to as group, segment, or resource.
class Namespace
attr_reader :space, :options
# @param space [String] the name of this namespace
# @param options [Hash] options hash
# @option options :requirements [Hash] param-regex pairs, all of which must
# be met by a request's params for all endpoints in this namespace, or
# validation will fail and return a 422.
def initialize(space, options = {})
@space = space.to_s
@options = options
end
# Retrieves the requirements from the options hash, if given.
# @return [Hash]
def requirements
options[:requirements] || {}
end
# (see ::joined_space_path)
def self.joined_space(settings)
(settings || []).map(&:space).join('/')
end
# Join the namespaces from a list of settings to create a path prefix.
# @param settings [Array] list of Grape::Util::InheritableSettings.
def self.joined_space_path(settings)
Rack::Mount::Utils.normalize_path(joined_space(settings))
end
end
end
grape-0.13.0/lib/grape/exceptions/ 0000755 0000041 0000041 00000000000 12563420522 016747 5 ustar www-data www-data grape-0.13.0/lib/grape/exceptions/missing_option.rb 0000644 0000041 0000041 00000000325 12563420522 022335 0 ustar www-data www-data # encoding: utf-8
module Grape
module Exceptions
class MissingOption < Base
def initialize(option)
super(message: compose_message('missing_option', option: option))
end
end
end
end
grape-0.13.0/lib/grape/exceptions/validation.rb 0000644 0000041 0000041 00000001204 12563420522 021423 0 ustar www-data www-data require 'grape/exceptions/base'
module Grape
module Exceptions
class Validation < Grape::Exceptions::Base
attr_accessor :params
def initialize(args = {})
fail 'Params are missing:' unless args.key? :params
@params = args[:params]
args[:message] = translate_message(args[:message_key]) if args.key? :message_key
super
end
# remove all the unnecessary stuff from Grape::Exceptions::Base like status
# and headers when converting a validation error to json or string
def as_json(*_args)
to_s
end
def to_s
message
end
end
end
end
grape-0.13.0/lib/grape/exceptions/missing_group_type.rb 0000644 0000041 0000041 00000000311 12563420522 023215 0 ustar www-data www-data # encoding: utf-8
module Grape
module Exceptions
class MissingGroupTypeError < Base
def initialize
super(message: compose_message('missing_group_type'))
end
end
end
end
grape-0.13.0/lib/grape/exceptions/invalid_versioner_option.rb 0000644 0000041 0000041 00000000356 12563420522 024412 0 ustar www-data www-data # encoding: utf-8
module Grape
module Exceptions
class InvalidVersionerOption < Base
def initialize(strategy)
super(message: compose_message('invalid_versioner_option', strategy: strategy))
end
end
end
end
grape-0.13.0/lib/grape/exceptions/missing_vendor_option.rb 0000644 0000041 0000041 00000000312 12563420522 023706 0 ustar www-data www-data # encoding: utf-8
module Grape
module Exceptions
class MissingVendorOption < Base
def initialize
super(message: compose_message('missing_vendor_option'))
end
end
end
end
grape-0.13.0/lib/grape/exceptions/incompatible_option_values.rb 0000644 0000041 0000041 00000000472 12563420522 024714 0 ustar www-data www-data # encoding: utf-8
module Grape
module Exceptions
class IncompatibleOptionValues < Base
def initialize(option1, value1, option2, value2)
super(message: compose_message('incompatible_option_values', option1: option1, value1: value1, option2: option2, value2: value2))
end
end
end
end
grape-0.13.0/lib/grape/exceptions/validation_errors.rb 0000644 0000041 0000041 00000002512 12563420522 023022 0 ustar www-data www-data require 'grape/exceptions/base'
module Grape
module Exceptions
class ValidationErrors < Grape::Exceptions::Base
include Enumerable
attr_reader :errors
def initialize(args = {})
@errors = {}
args[:errors].each do |validation_error|
@errors[validation_error.params] ||= []
@errors[validation_error.params] << validation_error
end
super message: full_messages.join(', '), status: 400, headers: args[:headers]
end
def each
errors.each_pair do |attribute, errors|
errors.each do |error|
yield attribute, error
end
end
end
def as_json(_opts = {})
errors.map do |k, v|
{
params: k,
messages: v.map(&:to_s)
}
end
end
def to_json(_opts = {})
as_json.to_json
end
def full_messages
map { |attributes, error| full_message(attributes, error) }.uniq
end
private
def full_message(attributes, error)
I18n.t(
'grape.errors.format'.to_sym,
default: '%{attributes} %{message}',
attributes: attributes.count == 1 ? translate_attribute(attributes.first) : translate_attributes(attributes),
message: error.message
)
end
end
end
end
grape-0.13.0/lib/grape/exceptions/invalid_with_option_for_represent.rb 0000644 0000041 0000041 00000000340 12563420522 026277 0 ustar www-data www-data # encoding: utf-8
module Grape
module Exceptions
class InvalidWithOptionForRepresent < Base
def initialize
super(message: compose_message('invalid_with_option_for_represent'))
end
end
end
end
grape-0.13.0/lib/grape/exceptions/invalid_accept_header.rb 0000644 0000041 0000041 00000000415 12563420522 023551 0 ustar www-data www-data # encoding: utf-8
module Grape
module Exceptions
class InvalidAcceptHeader < Base
def initialize(message, headers)
super(message: compose_message('invalid_accept_header', message: message), status: 406, headers: headers)
end
end
end
end
grape-0.13.0/lib/grape/exceptions/unknown_parameter.rb 0000644 0000041 0000041 00000000330 12563420522 023027 0 ustar www-data www-data # encoding: utf-8
module Grape
module Exceptions
class UnknownParameter < Base
def initialize(param)
super(message: compose_message('unknown_parameter', param: param))
end
end
end
end
grape-0.13.0/lib/grape/exceptions/unknown_options.rb 0000644 0000041 0000041 00000000332 12563420522 022544 0 ustar www-data www-data # encoding: utf-8
module Grape
module Exceptions
class UnknownOptions < Base
def initialize(options)
super(message: compose_message('unknown_options', options: options))
end
end
end
end
grape-0.13.0/lib/grape/exceptions/invalid_formatter.rb 0000644 0000041 0000041 00000000371 12563420522 023006 0 ustar www-data www-data # encoding: utf-8
module Grape
module Exceptions
class InvalidFormatter < Base
def initialize(klass, to_format)
super(message: compose_message('invalid_formatter', klass: klass, to_format: to_format))
end
end
end
end
grape-0.13.0/lib/grape/exceptions/unknown_validator.rb 0000644 0000041 0000041 00000000363 12563420522 023042 0 ustar www-data www-data # encoding: utf-8
module Grape
module Exceptions
class UnknownValidator < Base
def initialize(validator_type)
super(message: compose_message('unknown_validator', validator_type: validator_type))
end
end
end
end
grape-0.13.0/lib/grape/exceptions/invalid_message_body.rb 0000644 0000041 0000041 00000000374 12563420522 023447 0 ustar www-data www-data # encoding: utf-8
module Grape
module Exceptions
class InvalidMessageBody < Base
def initialize(body_format)
super(message: compose_message('invalid_message_body', body_format: body_format), status: 400)
end
end
end
end
grape-0.13.0/lib/grape/exceptions/base.rb 0000644 0000041 0000041 00000004426 12563420522 020214 0 ustar www-data www-data module Grape
module Exceptions
class Base < StandardError
BASE_MESSAGES_KEY = 'grape.errors.messages'
BASE_ATTRIBUTES_KEY = 'grape.errors.attributes'
FALLBACK_LOCALE = :en
attr_reader :status, :message, :headers
def initialize(args = {})
@status = args[:status] || nil
@message = args[:message] || nil
@headers = args[:headers] || nil
end
def [](index)
send index
end
protected
# TODO: translate attribute first
# if BASE_ATTRIBUTES_KEY.key respond to a string message, then short_message is returned
# if BASE_ATTRIBUTES_KEY.key respond to a Hash, means it may have problem , summary and resolution
def compose_message(key, attributes = {})
short_message = translate_message(key, attributes)
if short_message.is_a? Hash
@problem = problem(key, attributes)
@summary = summary(key, attributes)
@resolution = resolution(key, attributes)
[['Problem', @problem], ['Summary', @summary], ['Resolution', @resolution]].reduce('') do |message, detail_array|
message << "\n#{detail_array[0]}:\n #{detail_array[1]}" unless detail_array[1].blank?
message
end
else
short_message
end
end
def problem(key, attributes)
translate_message("#{key}.problem", attributes)
end
def summary(key, attributes)
translate_message("#{key}.summary", attributes)
end
def resolution(key, attributes)
translate_message("#{key}.resolution", attributes)
end
def translate_attributes(keys, options = {})
keys.map do |key|
translate("#{BASE_ATTRIBUTES_KEY}.#{key}", { default: key }.merge(options))
end.join(', ')
end
def translate_attribute(key, options = {})
translate("#{BASE_ATTRIBUTES_KEY}.#{key}", { default: key }.merge(options))
end
def translate_message(key, options = {})
translate("#{BASE_MESSAGES_KEY}.#{key}", { default: '' }.merge(options))
end
def translate(key, options = {})
message = ::I18n.translate(key, options)
message.present? ? message : ::I18n.translate(key, options.merge(locale: FALLBACK_LOCALE))
end
end
end
end
grape-0.13.0/lib/grape/exceptions/missing_mime_type.rb 0000644 0000041 0000041 00000000346 12563420522 023020 0 ustar www-data www-data # encoding: utf-8
module Grape
module Exceptions
class MissingMimeType < Base
def initialize(new_format)
super(message: compose_message('missing_mime_type', new_format: new_format))
end
end
end
end
grape-0.13.0/lib/grape/exceptions/unsupported_group_type.rb 0000644 0000041 0000041 00000000321 12563420522 024135 0 ustar www-data www-data # encoding: utf-8
module Grape
module Exceptions
class UnsupportedGroupTypeError < Base
def initialize
super(message: compose_message('unsupported_group_type'))
end
end
end
end
grape-0.13.0/lib/grape/formatter/ 0000755 0000041 0000041 00000000000 12563420522 016571 5 ustar www-data www-data grape-0.13.0/lib/grape/formatter/xml.rb 0000644 0000041 0000041 00000000424 12563420522 017716 0 ustar www-data www-data module Grape
module Formatter
module Xml
class << self
def call(object, _env)
return object.to_xml if object.respond_to?(:to_xml)
fail Grape::Exceptions::InvalidFormatter.new(object.class, 'xml')
end
end
end
end
end
grape-0.13.0/lib/grape/formatter/json.rb 0000644 0000041 0000041 00000000354 12563420522 020071 0 ustar www-data www-data module Grape
module Formatter
module Json
class << self
def call(object, _env)
return object.to_json if object.respond_to?(:to_json)
MultiJson.dump(object)
end
end
end
end
end
grape-0.13.0/lib/grape/formatter/serializable_hash.rb 0000644 0000041 0000041 00000002067 12563420522 022574 0 ustar www-data www-data module Grape
module Formatter
module SerializableHash
class << self
def call(object, _env)
return object if object.is_a?(String)
return MultiJson.dump(serialize(object)) if serializable?(object)
return object.to_json if object.respond_to?(:to_json)
MultiJson.dump(object)
end
private
def serializable?(object)
object.respond_to?(:serializable_hash) || object.is_a?(Array) && !object.map { |o| o.respond_to? :serializable_hash }.include?(false) || object.is_a?(Hash)
end
def serialize(object)
if object.respond_to? :serializable_hash
object.serializable_hash
elsif object.is_a?(Array) && !object.map { |o| o.respond_to? :serializable_hash }.include?(false)
object.map(&:serializable_hash)
elsif object.is_a?(Hash)
object.inject({}) do |h, (k, v)|
h[k] = serialize(v)
h
end
else
object
end
end
end
end
end
end
grape-0.13.0/lib/grape/formatter/txt.rb 0000644 0000041 0000041 00000000316 12563420522 017735 0 ustar www-data www-data module Grape
module Formatter
module Txt
class << self
def call(object, _env)
object.respond_to?(:to_txt) ? object.to_txt : object.to_s
end
end
end
end
end
grape-0.13.0/lib/grape/formatter/base.rb 0000644 0000041 0000041 00000001334 12563420522 020031 0 ustar www-data www-data module Grape
module Formatter
module Base
class << self
FORMATTERS = {
json: Grape::Formatter::Json,
jsonapi: Grape::Formatter::Json,
serializable_hash: Grape::Formatter::SerializableHash,
txt: Grape::Formatter::Txt,
xml: Grape::Formatter::Xml
}
def formatters(options)
FORMATTERS.merge(options[:formatters] || {})
end
def formatter_for(api_format, options = {})
spec = formatters(options)[api_format]
case spec
when nil
->(obj, _env) { obj }
when Symbol
method(spec)
else
spec
end
end
end
end
end
end
grape-0.13.0/lib/grape/path.rb 0000644 0000041 0000041 00000003201 12563420522 016043 0 ustar www-data www-data module Grape
# Represents a path to an endpoint.
class Path
def self.prepare(raw_path, namespace, settings)
Path.new(raw_path, namespace, settings).path_with_suffix
end
attr_reader :raw_path, :namespace, :settings
def initialize(raw_path, namespace, settings)
@raw_path = raw_path
@namespace = namespace
@settings = settings
end
def mount_path
settings[:mount_path]
end
def root_prefix
split_setting(:root_prefix)
end
def uses_specific_format?
!!(settings[:format] && settings[:content_types].size == 1)
end
def uses_path_versioning?
!!(settings[:version] && settings[:version_options][:using] == :path)
end
def has_namespace?
namespace && namespace.to_s =~ /^\S/ && namespace != '/'
end
def has_path?
raw_path && raw_path.to_s =~ /^\S/ && raw_path != '/'
end
def suffix
if uses_specific_format?
"(.#{settings[:format]})"
elsif !uses_path_versioning? || (has_namespace? || has_path?)
'(.:format)'
else
'(/.:format)'
end
end
def path
Rack::Mount::Utils.normalize_path(parts.join('/'))
end
def path_with_suffix
"#{path}#{suffix}"
end
def to_s
path_with_suffix
end
private
def parts
parts = [mount_path, root_prefix].compact
parts << ':version' if uses_path_versioning?
parts << namespace.to_s
parts << raw_path.to_s
parts.flatten.reject { |part| part == '/' }
end
def split_setting(key)
return if settings[key].nil?
settings[key].to_s.split('/')
end
end
end
grape-0.13.0/lib/grape/parser/ 0000755 0000041 0000041 00000000000 12563420522 016062 5 ustar www-data www-data grape-0.13.0/lib/grape/parser/xml.rb 0000644 0000041 0000041 00000000554 12563420522 017213 0 ustar www-data www-data module Grape
module Parser
module Xml
class << self
def call(object, _env)
MultiXml.parse(object)
rescue MultiXml::ParseError
# handle XML parsing errors via the rescue handlers or provide error message
raise Grape::Exceptions::InvalidMessageBody, 'application/xml'
end
end
end
end
end
grape-0.13.0/lib/grape/parser/json.rb 0000644 0000041 0000041 00000000560 12563420522 017361 0 ustar www-data www-data module Grape
module Parser
module Json
class << self
def call(object, _env)
MultiJson.load(object)
rescue MultiJson::ParseError
# handle JSON parsing errors via the rescue handlers or provide error message
raise Grape::Exceptions::InvalidMessageBody, 'application/json'
end
end
end
end
end
grape-0.13.0/lib/grape/parser/base.rb 0000644 0000041 0000041 00000001105 12563420522 017316 0 ustar www-data www-data module Grape
module Parser
module Base
class << self
PARSERS = {
json: Grape::Parser::Json,
jsonapi: Grape::Parser::Json,
xml: Grape::Parser::Xml
}
def parsers(options)
PARSERS.merge(options[:parsers] || {})
end
def parser_for(api_format, options = {})
spec = parsers(options)[api_format]
case spec
when nil
nil
when Symbol
method(spec)
else
spec
end
end
end
end
end
end
grape-0.13.0/lib/grape/api/ 0000755 0000041 0000041 00000000000 12563420522 015337 5 ustar www-data www-data grape-0.13.0/lib/grape/api/helpers.rb 0000644 0000041 0000041 00000000154 12563420522 017326 0 ustar www-data www-data module Grape
class API
module Helpers
include Grape::DSL::Helpers::BaseHelper
end
end
end
grape-0.13.0/lib/grape/api.rb 0000644 0000041 0000041 00000016103 12563420522 015665 0 ustar www-data www-data module Grape
# The API class is the primary entry point for creating Grape APIs. Users
# should subclass this class in order to build an API.
class API
include Grape::DSL::API
class << self
attr_reader :instance
# A class-level lock to ensure the API is not compiled by multiple
# threads simultaneously within the same process.
LOCK = Mutex.new
# Clears all defined routes, endpoints, etc., on this API.
def reset!
@route_set = Rack::Mount::RouteSet.new
@endpoints = []
@routes = nil
reset_validations!
end
# Parses the API's definition and compiles it into an instance of
# Grape::API.
def compile
@instance ||= new
end
# Wipe the compiled API so we can recompile after changes were made.
def change!
@instance = nil
end
# This is the interface point between Rack and Grape; it accepts a request
# from Rack and ultimately returns an array of three values: the status,
# the headers, and the body. See [the rack specification]
# (http://www.rubydoc.info/github/rack/rack/master/file/SPEC) for more.
def call(env)
LOCK.synchronize { compile } unless instance
call!(env)
end
# A non-synchronized version of ::call.
def call!(env)
instance.call(env)
end
# Create a scope without affecting the URL.
#
# @param _name [Symbol] Purely placebo, just allows to name the scope to
# make the code more readable.
def scope(_name = nil, &block)
within_namespace do
nest(block)
end
end
# (see #cascade?)
def cascade(value = nil)
if value.nil?
inheritable_setting.namespace_inheritable.keys.include?(:cascade) ? !!namespace_inheritable(:cascade) : true
else
namespace_inheritable(:cascade, value)
end
end
protected
def prepare_routes
endpoints.map(&:routes).flatten
end
# Execute first the provided block, then each of the
# block passed in. Allows for simple 'before' setups
# of settings stack pushes.
def nest(*blocks, &block)
blocks.reject!(&:nil?)
if blocks.any?
instance_eval(&block) if block_given?
blocks.each { |b| instance_eval(&b) }
reset_validations!
else
instance_eval(&block)
end
end
def inherited(subclass)
subclass.reset!
subclass.logger = logger.clone
end
def inherit_settings(other_settings)
top_level_setting.inherit_from other_settings.point_in_time_copy
endpoints.each(&:reset_routes!)
@routes = nil
end
end
# Builds the routes from the defined endpoints, effectively compiling
# this API into a usable form.
def initialize
@route_set = Rack::Mount::RouteSet.new
add_head_not_allowed_methods_and_options_methods
self.class.endpoints.each do |endpoint|
endpoint.mount_in(@route_set)
end
@route_set.freeze
end
# Handle a request. See Rack documentation for what `env` is.
def call(env)
result = @route_set.call(env)
result[1].delete(Grape::Http::Headers::X_CASCADE) unless cascade?
result
end
# Some requests may return a HTTP 404 error if grape cannot find a matching
# route. In this case, Rack::Mount adds a X-Cascade header to the response
# and sets it to 'pass', indicating to grape's parents they should keep
# looking for a matching route on other resources.
#
# In some applications (e.g. mounting grape on rails), one might need to trap
# errors from reaching upstream. This is effectivelly done by unsetting
# X-Cascade. Default :cascade is true.
def cascade?
return !!self.class.namespace_inheritable(:cascade) if self.class.inheritable_setting.namespace_inheritable.keys.include?(:cascade)
return !!self.class.namespace_inheritable(:version_options)[:cascade] if self.class.namespace_inheritable(:version_options) && self.class.namespace_inheritable(:version_options).key?(:cascade)
true
end
reset!
private
# For every resource add a 'OPTIONS' route that returns an HTTP 204 response
# with a list of HTTP methods that can be called. Also add a route that
# will return an HTTP 405 response for any HTTP method that the resource
# cannot handle.
def add_head_not_allowed_methods_and_options_methods
methods_per_path = {}
self.class.endpoints.each do |endpoint|
routes = endpoint.routes
routes.each do |route|
methods_per_path[route.route_path] ||= []
methods_per_path[route.route_path] << route.route_method
end
end
# The paths we collected are prepared (cf. Path#prepare), so they
# contain already versioning information when using path versioning.
# Disable versioning so adding a route won't prepend versioning
# informations again.
without_root_prefix do
without_versioning do
methods_per_path.each do |path, methods|
allowed_methods = methods.dup
unless self.class.namespace_inheritable(:do_not_route_head)
allowed_methods |= [Grape::Http::Headers::HEAD] if allowed_methods.include?(Grape::Http::Headers::GET)
end
allow_header = ([Grape::Http::Headers::OPTIONS] | allowed_methods).join(', ')
unless self.class.namespace_inheritable(:do_not_route_options)
unless allowed_methods.include?(Grape::Http::Headers::OPTIONS)
self.class.options(path, {}) do
header 'Allow', allow_header
status 204
''
end
end
end
not_allowed_methods = %w(GET PUT POST DELETE PATCH HEAD) - allowed_methods
not_allowed_methods << Grape::Http::Headers::OPTIONS if self.class.namespace_inheritable(:do_not_route_options)
self.class.route(not_allowed_methods, path) do
header 'Allow', allow_header
status 405
''
end
end
end
end
end
# Allows definition of endpoints that ignore the versioning configuration
# used by the rest of your API.
def without_versioning(&_block)
old_version = self.class.namespace_inheritable(:version)
old_version_options = self.class.namespace_inheritable(:version_options)
self.class.namespace_inheritable_to_nil(:version)
self.class.namespace_inheritable_to_nil(:version_options)
yield
self.class.namespace_inheritable(:version, old_version)
self.class.namespace_inheritable(:version_options, old_version_options)
end
# Allows definition of endpoints that ignore the root prefix used by the
# rest of your API.
def without_root_prefix(&_block)
old_prefix = self.class.namespace_inheritable(:root_prefix)
self.class.namespace_inheritable_to_nil(:root_prefix)
yield
self.class.namespace_inheritable(:root_prefix, old_prefix)
end
end
end
grape-0.13.0/lib/grape/endpoint.rb 0000644 0000041 0000041 00000027002 12563420522 016734 0 ustar www-data www-data module Grape
# An Endpoint is the proxy scope in which all routing
# blocks are executed. In other words, any methods
# on the instance level of this class may be called
# from inside a `get`, `post`, etc.
class Endpoint
include Grape::DSL::Settings
attr_accessor :block, :source, :options
attr_reader :env, :request, :headers, :params
include Grape::DSL::InsideRoute
class << self
def before_each(new_setup = false, &block)
if new_setup == false
if block_given?
@before_each = block
else
return @before_each
end
else
@before_each = new_setup
end
end
# @api private
#
# Create an UnboundMethod that is appropriate for executing an endpoint
# route.
#
# The unbound method allows explicit calls to +return+ without raising a
# +LocalJumpError+. The method will be removed, but a +Proc+ reference to
# it will be returned. The returned +Proc+ expects a single argument: the
# instance of +Endpoint+ to bind to the method during the call.
#
# @param [String, Symbol] method_name
# @return [Proc]
# @raise [NameError] an instance method with the same name already exists
def generate_api_method(method_name, &block)
if instance_methods.include?(method_name.to_sym) || instance_methods.include?(method_name.to_s)
fail NameError.new("method #{method_name.inspect} already exists and cannot be used as an unbound method name")
end
define_method(method_name, &block)
method = instance_method(method_name)
remove_method(method_name)
proc do |endpoint_instance|
ActiveSupport::Notifications.instrument('endpoint_render.grape', endpoint: endpoint_instance) do
method.bind(endpoint_instance).call
end
end
end
end
def initialize(new_settings, options = {}, &block)
require_option(options, :path)
require_option(options, :method)
self.inheritable_setting = new_settings.point_in_time_copy
route_setting(:saved_declared_params, namespace_stackable(:declared_params))
route_setting(:saved_validations, namespace_stackable(:validations))
namespace_stackable(:representations, []) unless namespace_stackable(:representations)
namespace_inheritable(:default_error_status, 500) unless namespace_inheritable(:default_error_status)
@options = options
@options[:path] = Array(options[:path])
@options[:path] << '/' if options[:path].empty?
@options[:method] = Array(options[:method])
@options[:route_options] ||= {}
if block_given?
@source = block
@block = self.class.generate_api_method(method_name, &block)
end
end
def require_option(options, key)
fail Grape::Exceptions::MissingOption.new(key) unless options.key?(key)
end
def method_name
[options[:method],
Namespace.joined_space(namespace_stackable(:namespace)),
(namespace_stackable(:mount_path) || []).join('/'),
options[:path].join('/')
].join(' ')
end
def routes
@routes ||= endpoints ? endpoints.collect(&:routes).flatten : prepare_routes
end
def reset_routes!
endpoints.map(&:reset_routes!) if endpoints
@namespace = nil
@routes = nil
end
def mount_in(route_set)
if endpoints
endpoints.each do |e|
e.mount_in(route_set)
end
else
@routes = nil
routes.each do |route|
methods = [route.route_method]
if !namespace_inheritable(:do_not_route_head) && route.route_method == Grape::Http::Headers::GET
methods << Grape::Http::Headers::HEAD
end
methods.each do |method|
route_set.add_route(self, {
path_info: route.route_compiled,
request_method: method
}, route_info: route)
end
end
end
end
def prepare_routes_requirements
endpoint_requirements = options[:route_options][:requirements] || {}
all_requirements = (namespace_stackable(:namespace).map(&:requirements) << endpoint_requirements)
all_requirements.reduce({}) do |base_requirements, single_requirements|
base_requirements.merge!(single_requirements)
end
end
def prepare_routes_path_params(path)
path_params = {}
# named parameters in the api path
regex = Rack::Mount::RegexpWithNamedGroups.new(path)
named_params = regex.named_captures.map { |nc| nc[0] } - %w(version format)
named_params.each { |named_param| path_params[named_param] = '' }
# route parameters declared via desc or appended to the api declaration
route_params = options[:route_options][:params]
path_params.merge! route_params if route_params
path_params
end
def prepare_routes
options[:method].map do |method|
options[:path].map do |path|
prepared_path = prepare_path(path)
anchor = options[:route_options].fetch(:anchor, true)
path = compile_path(prepared_path, anchor && !options[:app], prepare_routes_requirements)
request_method = (method.to_s.upcase unless method == :any)
Route.new(options[:route_options].clone.merge(
prefix: namespace_inheritable(:root_prefix),
version: namespace_inheritable(:version) ? namespace_inheritable(:version).join('|') : nil,
namespace: namespace,
method: request_method,
path: prepared_path,
params: prepare_routes_path_params(path),
compiled: path,
settings: inheritable_setting.route.except(:saved_declared_params, :saved_validations)
))
end
end.flatten
end
def prepare_path(path)
path_settings = inheritable_setting.to_hash[:namespace_stackable].merge(inheritable_setting.to_hash[:namespace_inheritable])
Path.prepare(path, namespace, path_settings)
end
def namespace
@namespace ||= Namespace.joined_space_path(namespace_stackable(:namespace))
end
def compile_path(prepared_path, anchor = true, requirements = {})
endpoint_options = {}
endpoint_options[:version] = /#{namespace_inheritable(:version).join('|')}/ if namespace_inheritable(:version)
endpoint_options.merge!(requirements)
Rack::Mount::Strexp.compile(prepared_path, endpoint_options, %w( / . ? ), anchor)
end
def call(env)
dup.call!(env)
end
def call!(env)
extend helpers
env['api.endpoint'] = self
if options[:app]
options[:app].call(env)
else
builder = build_middleware
builder.run ->(arg) { run(arg) }
builder.call(env)
end
end
# Return the collection of endpoints within this endpoint.
# This is the case when an Grape::API mounts another Grape::API.
def endpoints
options[:app].endpoints if options[:app] && options[:app].respond_to?(:endpoints)
end
def equals?(e)
(options == e.options) && (inheritable_setting.to_hash == e.inheritable_setting.to_hash)
end
protected
def run(env)
ActiveSupport::Notifications.instrument('endpoint_run.grape', endpoint: self, env: env) do
@env = env
@header = {}
@request = Grape::Request.new(env)
@params = @request.params
@headers = @request.headers
cookies.read(@request)
self.class.before_each.call(self) if self.class.before_each
run_filters befores, :before
run_filters before_validations, :before_validation
# Retrieve validations from this namespace and all parent namespaces.
validation_errors = []
# require 'pry-byebug'; binding.pry
route_setting(:saved_validations).each do |validator|
begin
validator.validate!(params)
rescue Grape::Exceptions::Validation => e
validation_errors << e
end
end
if validation_errors.any?
fail Grape::Exceptions::ValidationErrors, errors: validation_errors, headers: header
end
run_filters after_validations, :after_validation
response_object = @block ? @block.call(self) : nil
run_filters afters, :after
cookies.write(header)
# The Body commonly is an Array of Strings, the application instance itself, or a File-like object.
response_object = file || [body || response_object]
[status, header, response_object]
end
end
def build_middleware
b = Rack::Builder.new
b.use Rack::Head
b.use Grape::Middleware::Error,
format: namespace_inheritable(:format),
content_types: Grape::DSL::Configuration.stacked_hash_to_hash(namespace_stackable(:content_types)),
default_status: namespace_inheritable(:default_error_status),
rescue_all: namespace_inheritable(:rescue_all),
default_error_formatter: namespace_inheritable(:default_error_formatter),
error_formatters: Grape::DSL::Configuration.stacked_hash_to_hash(namespace_stackable(:error_formatters)),
rescue_options: Grape::DSL::Configuration.stacked_hash_to_hash(namespace_stackable(:rescue_options)) || {},
rescue_handlers: Grape::DSL::Configuration.stacked_hash_to_hash(namespace_stackable(:rescue_handlers)) || {},
base_only_rescue_handlers: Grape::DSL::Configuration.stacked_hash_to_hash(namespace_stackable(:base_only_rescue_handlers)) || {},
all_rescue_handler: namespace_inheritable(:all_rescue_handler)
(namespace_stackable(:middleware) || []).each do |m|
m = m.dup
block = m.pop if m.last.is_a?(Proc)
if block
b.use(*m, &block)
else
b.use(*m)
end
end
if namespace_inheritable(:version)
b.use Grape::Middleware::Versioner.using(namespace_inheritable(:version_options)[:using]),
versions: namespace_inheritable(:version) ? namespace_inheritable(:version).flatten : nil,
version_options: namespace_inheritable(:version_options),
prefix: namespace_inheritable(:root_prefix)
end
b.use Grape::Middleware::Formatter,
format: namespace_inheritable(:format),
default_format: namespace_inheritable(:default_format) || :txt,
content_types: Grape::DSL::Configuration.stacked_hash_to_hash(namespace_stackable(:content_types)),
formatters: Grape::DSL::Configuration.stacked_hash_to_hash(namespace_stackable(:formatters)),
parsers: Grape::DSL::Configuration.stacked_hash_to_hash(namespace_stackable(:parsers))
b
end
def helpers
mod = Module.new
(namespace_stackable(:helpers) || []).each do |mod_to_include|
mod.send :include, mod_to_include
end
mod
end
def run_filters(filters, type = :other)
ActiveSupport::Notifications.instrument('endpoint_run_filters.grape', endpoint: self, filters: filters, type: type) do
(filters || []).each do |filter|
instance_eval(&filter)
end
end
end
def befores
namespace_stackable(:befores) || []
end
def before_validations
namespace_stackable(:before_validations) || []
end
def after_validations
namespace_stackable(:after_validations) || []
end
def afters
namespace_stackable(:afters) || []
end
end
end
grape-0.13.0/lib/grape/version.rb 0000644 0000041 0000041 00000000110 12563420522 016570 0 ustar www-data www-data module Grape
# The current version of Grape.
VERSION = '0.13.0'
end
grape-0.13.0/lib/grape/validations/ 0000755 0000041 0000041 00000000000 12563420522 017103 5 ustar www-data www-data grape-0.13.0/lib/grape/validations/params_scope.rb 0000644 0000041 0000041 00000024000 12563420522 022100 0 ustar www-data www-data module Grape
module Validations
class ParamsScope
attr_accessor :element, :parent
include Grape::DSL::Parameters
# Open up a new ParamsScope, allowing parameter definitions per
# Grape::DSL::Params.
# @param opts [Hash] options for this scope
# @option opts :element [Symbol] the element that contains this scope; for
# this to be relevant, @parent must be set
# @option opts :parent [ParamsScope] the scope containing this scope
# @option opts :api [API] the API endpoint to modify
# @option opts :optional [Boolean] whether or not this scope needs to have
# any parameters set or not
# @option opts :type [Class] a type meant to govern this scope (deprecated)
# @option opts :dependent_on [Symbol] if present, this scope should only
# validate if this param is present in the parent scope
# @yield the instance context, open for parameter definitions
def initialize(opts, &block)
@element = opts[:element]
@parent = opts[:parent]
@api = opts[:api]
@optional = opts[:optional] || false
@type = opts[:type]
@dependent_on = opts[:dependent_on]
@declared_params = []
instance_eval(&block) if block_given?
configure_declared_params
end
# @return [Boolean] whether or not this entire scope needs to be
# validated
def should_validate?(parameters)
return false if @optional && params(parameters).respond_to?(:all?) && params(parameters).all?(&:blank?)
return false if @dependent_on && params(parameters).try(:[], @dependent_on).blank?
return true if parent.nil?
parent.should_validate?(parameters)
end
# @return [String] the proper attribute name, with nesting considered.
def full_name(name)
case
when nested?
# Find our containing element's name, and append ours.
"#{@parent.full_name(@element)}[#{name}]"
when lateral?
# Find the name of the element as if it was at the
# same nesting level as our parent.
@parent.full_name(name)
else
# We must be the root scope, so no prefix needed.
name.to_s
end
end
# @return [Boolean] whether or not this scope is the root-level scope
def root?
!@parent
end
# A nested scope is contained in one of its parent's elements.
# @return [Boolean] whether or not this scope is nested
def nested?
@parent && @element
end
# A lateral scope is subordinate to its parent, but its keys are at the
# same level as its parent and thus is not contained within an element.
# @return [Boolean] whether or not this scope is lateral
def lateral?
@parent && !@element
end
# @return [Boolean] whether or not this scope needs to be present, or can
# be blank
def required?
!@optional
end
protected
# Adds a parameter declaration to our list of validations.
# @param attrs [Array] (see Grape::DSL::Parameters#requires)
def push_declared_params(attrs)
@declared_params.concat attrs
end
private
def require_required_and_optional_fields(context, opts)
if context == :all
optional_fields = Array(opts[:except])
required_fields = opts[:using].keys - optional_fields
else # context == :none
required_fields = Array(opts[:except])
optional_fields = opts[:using].keys - required_fields
end
required_fields.each do |field|
field_opts = opts[:using][field]
fail ArgumentError, "required field not exist: #{field}" unless field_opts
requires(field, field_opts)
end
optional_fields.each do |field|
field_opts = opts[:using][field]
optional(field, field_opts) if field_opts
end
end
def require_optional_fields(context, opts)
optional_fields = opts[:using].keys
optional_fields -= Array(opts[:except]) unless context == :all
optional_fields.each do |field|
field_opts = opts[:using][field]
optional(field, field_opts) if field_opts
end
end
def validate_attributes(attrs, opts, &block)
validations = opts.clone
validations[:type] ||= Array if block
validates(attrs, validations)
end
# Returns a new parameter scope, subordinate to the current one and nested
# under the parameter corresponding to `attrs.first`.
# @param attrs [Array] the attributes passed to the `requires` or
# `optional` invocation that opened this scope.
# @param optional [Boolean] whether the parameter this are nested under
# is optional or not (and hence, whether this block's params will be).
# @yield parameter scope
def new_scope(attrs, optional = false, &block)
# if required params are grouped and no type or unsupported type is provided, raise an error
type = attrs[1] ? attrs[1][:type] : nil
if attrs.first && !optional
fail Grape::Exceptions::MissingGroupTypeError.new if type.nil?
fail Grape::Exceptions::UnsupportedGroupTypeError.new unless [Array, Hash].include?(type)
end
opts = attrs[1] || { type: Array }
self.class.new(api: @api, element: attrs.first, parent: self, optional: optional, type: opts[:type], &block)
end
# Returns a new parameter scope, not nested under any current-level param
# but instead at the same level as the current scope.
# @param options [Hash] options to control how this new scope behaves
# @option options :dependent_on [Symbol] if given, specifies that this
# scope should only validate if this parameter from the above scope is
# present
# @yield parameter scope
def new_lateral_scope(options, &block)
self.class.new(
api: @api,
element: nil,
parent: self,
options: @optional,
type: Hash,
dependent_on: options[:dependent_on],
&block)
end
# Pushes declared params to parent or settings
def configure_declared_params
if nested?
@parent.push_declared_params [element => @declared_params]
else
@api.namespace_stackable(:declared_params, @declared_params)
@api.route_setting(:declared_params, []) unless @api.route_setting(:declared_params)
@api.route_setting(:declared_params).concat @declared_params
end
end
def validates(attrs, validations)
doc_attrs = { required: validations.keys.include?(:presence) }
# special case (type = coerce)
validations[:coerce] = validations.delete(:type) if validations.key?(:type)
coerce_type = validations[:coerce]
doc_attrs[:type] = coerce_type.to_s if coerce_type
desc = validations.delete(:desc) || validations.delete(:description)
doc_attrs[:desc] = desc if desc
default = validations[:default]
doc_attrs[:default] = default if validations.key?(:default)
values = validations[:values]
doc_attrs[:values] = values if values
coerce_type = guess_coerce_type(coerce_type, values)
# default value should be present in values array, if both exist and are not procs
check_incompatible_option_values(values, default)
# type should be compatible with values array, if both exist
validate_value_coercion(coerce_type, values)
doc_attrs[:documentation] = validations.delete(:documentation) if validations.key?(:documentation)
full_attrs = attrs.collect { |name| { name: name, full_name: full_name(name) } }
@api.document_attribute(full_attrs, doc_attrs)
# Validate for presence before any other validators
if validations.key?(:presence) && validations[:presence]
validate('presence', validations[:presence], attrs, doc_attrs)
validations.delete(:presence)
end
# Before we run the rest of the validators, lets handle
# whatever coercion so that we are working with correctly
# type casted values
if validations.key? :coerce
validate('coerce', validations[:coerce], attrs, doc_attrs)
validations.delete(:coerce)
end
validations.each do |type, options|
validate(type, options, attrs, doc_attrs)
end
end
def guess_coerce_type(coerce_type, values)
return coerce_type if !values || values.is_a?(Proc)
return values.first.class if coerce_type == Array && (values.is_a?(Range) || !values.empty?)
coerce_type
end
def check_incompatible_option_values(values, default)
return unless values && default
return if values.is_a?(Proc) || default.is_a?(Proc)
return if values.include?(default)
fail Grape::Exceptions::IncompatibleOptionValues.new(:default, default, :values, values)
end
def validate(type, options, attrs, doc_attrs)
validator_class = Validations.validators[type.to_s]
if validator_class
value = validator_class.new(attrs, options, doc_attrs[:required], self)
@api.namespace_stackable(:validations, value)
else
fail Grape::Exceptions::UnknownValidator.new(type)
end
end
def validate_value_coercion(coerce_type, values)
return unless coerce_type && values
return if values.is_a?(Proc)
coerce_type = coerce_type.first if coerce_type.is_a?(Array)
value_types = values.is_a?(Range) ? [values.begin, values.end] : values
if coerce_type == Virtus::Attribute::Boolean
value_types = value_types.map { |type| Virtus::Attribute.build(type) }
end
if value_types.any? { |v| !v.is_a?(coerce_type) }
fail Grape::Exceptions::IncompatibleOptionValues.new(:type, coerce_type, :values, values)
end
end
end
end
end
grape-0.13.0/lib/grape/validations/validators/ 0000755 0000041 0000041 00000000000 12563420522 021253 5 ustar www-data www-data grape-0.13.0/lib/grape/validations/validators/default.rb 0000644 0000041 0000041 00000001256 12563420522 023230 0 ustar www-data www-data module Grape
module Validations
class DefaultValidator < Base
def initialize(attrs, options, required, scope)
@default = options
super
end
def validate_param!(attr_name, params)
params[attr_name] = @default.is_a?(Proc) ? @default.call : @default unless params.key?(attr_name)
end
def validate!(params)
return unless @scope.should_validate?(params)
attrs = AttributesIterator.new(self, @scope, params)
attrs.each do |resource_params, attr_name|
if resource_params[attr_name].nil?
validate_param!(attr_name, resource_params)
end
end
end
end
end
end
grape-0.13.0/lib/grape/validations/validators/values.rb 0000644 0000041 0000041 00000001345 12563420522 023102 0 ustar www-data www-data module Grape
module Validations
class ValuesValidator < Base
def initialize(attrs, options, required, scope)
@values = options
super
end
def validate_param!(attr_name, params)
return unless params[attr_name] || required_for_root_scope?
values = @values.is_a?(Proc) ? @values.call : @values
param_array = params[attr_name].nil? ? [nil] : Array.wrap(params[attr_name])
unless param_array.all? { |param| values.include?(param) }
fail Grape::Exceptions::Validation, params: [@scope.full_name(attr_name)], message_key: :values
end
end
private
def required_for_root_scope?
@required && @scope.root?
end
end
end
end
grape-0.13.0/lib/grape/validations/validators/exactly_one_of.rb 0000644 0000041 0000041 00000001076 12563420522 024602 0 ustar www-data www-data module Grape
module Validations
require 'grape/validations/validators/mutual_exclusion'
class ExactlyOneOfValidator < MutualExclusionValidator
def validate!(params)
super
if scope_requires_params && none_of_restricted_params_is_present
fail Grape::Exceptions::Validation, params: all_keys, message_key: :exactly_one
end
params
end
private
def none_of_restricted_params_is_present
scoped_params.any? { |resource_params| keys_in_common(resource_params).empty? }
end
end
end
end
grape-0.13.0/lib/grape/validations/validators/all_or_none.rb 0000644 0000041 0000041 00000001127 12563420522 024070 0 ustar www-data www-data module Grape
module Validations
require 'grape/validations/validators/multiple_params_base'
class AllOrNoneOfValidator < MultipleParamsBase
def validate!(params)
super
if scope_requires_params && only_subset_present
fail Grape::Exceptions::Validation, params: all_keys, message_key: :all_or_none
end
params
end
private
def only_subset_present
scoped_params.any? { |resource_params| keys_in_common(resource_params).length > 0 && keys_in_common(resource_params).length < attrs.length }
end
end
end
end
grape-0.13.0/lib/grape/validations/validators/coerce.rb 0000644 0000041 0000041 00000004703 12563420522 023044 0 ustar www-data www-data module Grape
class API
Boolean = Virtus::Attribute::Boolean # rubocop:disable ConstantName
end
module Validations
class CoerceValidator < Base
def validate_param!(attr_name, params)
fail Grape::Exceptions::Validation, params: [@scope.full_name(attr_name)], message_key: :coerce unless params.is_a? Hash
new_value = coerce_value(@option, params[attr_name])
if valid_type?(new_value)
params[attr_name] = new_value
else
fail Grape::Exceptions::Validation, params: [@scope.full_name(attr_name)], message_key: :coerce
end
end
class InvalidValue; end
private
def _valid_array_type?(type, values)
values.all? do |val|
_valid_single_type?(type, val)
end
end
def _valid_single_type?(klass, val)
# allow nil, to ignore when a parameter is absent
return true if val.nil?
if klass == Virtus::Attribute::Boolean
val.is_a?(TrueClass) || val.is_a?(FalseClass) || (val.is_a?(String) && val.empty?)
elsif klass == Rack::Multipart::UploadedFile
val.is_a?(Hashie::Mash) && val.key?(:tempfile)
elsif [DateTime, Date, Numeric].any? { |vclass| vclass >= klass }
return true if val.is_a?(String) && val.empty?
val.is_a?(klass)
else
val.is_a?(klass)
end
end
def valid_type?(val)
if val.instance_of?(InvalidValue)
false
elsif @option.is_a?(Array) || @option.is_a?(Set)
_valid_array_type?(@option.first, val)
else
_valid_single_type?(@option, val)
end
end
def coerce_value(type, val)
# Don't coerce things other than nil to Arrays or Hashes
return val || [] if type == Array
return val || Set.new if type == Set
return val || {} if type == Hash
# To support custom types that Virtus can't easily coerce, pass in an
# explicit coercer. Custom types must implement a `parse` class method.
converter_options = {}
if ParameterTypes.custom_type?(type)
converter_options[:coercer] = type.method(:parse)
end
converter = Virtus::Attribute.build(type, converter_options)
converter.coerce(val)
# not the prettiest but some invalid coercion can currently trigger
# errors in Virtus (see coerce_spec.rb:75)
rescue
InvalidValue.new
end
end
end
end
grape-0.13.0/lib/grape/validations/validators/multiple_params_base.rb 0000644 0000041 0000041 00000001100 12563420522 025760 0 ustar www-data www-data module Grape
module Validations
class MultipleParamsBase < Base
attr_reader :scoped_params
def validate!(params)
@scoped_params = [@scope.params(params)].flatten
params
end
private
def scope_requires_params
@scope.required? || scoped_params.any?(&:any?)
end
def keys_in_common(resource_params)
return [] unless resource_params.is_a?(Hash)
(all_keys & resource_params.stringify_keys.keys).map(&:to_s)
end
def all_keys
attrs.map(&:to_s)
end
end
end
end
grape-0.13.0/lib/grape/validations/validators/presence.rb 0000644 0000041 0000041 00000000664 12563420522 023412 0 ustar www-data www-data module Grape
module Validations
class PresenceValidator < Base
def validate!(params)
return unless @scope.should_validate?(params)
super
end
def validate_param!(attr_name, params)
unless params.respond_to?(:key?) && params.key?(attr_name)
fail Grape::Exceptions::Validation, params: [@scope.full_name(attr_name)], message_key: :presence
end
end
end
end
end
grape-0.13.0/lib/grape/validations/validators/base.rb 0000644 0000041 0000041 00000002030 12563420522 022505 0 ustar www-data www-data module Grape
module Validations
class Base
attr_reader :attrs
def initialize(attrs, options, required, scope)
@attrs = Array(attrs)
@option = options
@required = required
@scope = scope
end
def validate!(params)
attributes = AttributesIterator.new(self, @scope, params)
attributes.each do |resource_params, attr_name|
if @required || (resource_params.respond_to?(:key?) && resource_params.key?(attr_name))
validate_param!(attr_name, resource_params)
end
end
end
def self.convert_to_short_name(klass)
ret = klass.name.gsub(/::/, '/')
.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
.gsub(/([a-z\d])([A-Z])/, '\1_\2')
.tr('-', '_')
.downcase
File.basename(ret, '_validator')
end
def self.inherited(klass)
short_name = convert_to_short_name(klass)
Validations.register_validator(short_name, klass)
end
end
end
end
grape-0.13.0/lib/grape/validations/validators/at_least_one_of.rb 0000644 0000041 0000041 00000001063 12563420522 024721 0 ustar www-data www-data module Grape
module Validations
require 'grape/validations/validators/multiple_params_base'
class AtLeastOneOfValidator < MultipleParamsBase
def validate!(params)
super
if scope_requires_params && no_exclusive_params_are_present
fail Grape::Exceptions::Validation, params: all_keys, message_key: :at_least_one
end
params
end
private
def no_exclusive_params_are_present
scoped_params.any? { |resource_params| keys_in_common(resource_params).empty? }
end
end
end
end
grape-0.13.0/lib/grape/validations/validators/allow_blank.rb 0000644 0000041 0000041 00000002046 12563420522 024067 0 ustar www-data www-data module Grape
module Validations
class AllowBlankValidator < Base
def validate_param!(attr_name, params)
return if @option || !params.is_a?(Hash)
value = params[attr_name]
value = value.strip if value.respond_to?(:strip)
key_exists = params.key?(attr_name)
if @scope.root?
# root scope. validate if it's a required param. if it's optional, validate only if key exists in hash
should_validate = @required || key_exists
else # nested scope
should_validate = # required param, and scope contains some values (if scoping element contains no values, treat as blank)
(@required && params.present?) ||
# optional param but key inside scoping element exists
(!@required && params.key?(attr_name))
end
return unless should_validate
unless value == false || value.present?
fail Grape::Exceptions::Validation, params: [@scope.full_name(attr_name)], message_key: :blank
end
end
end
end
end
grape-0.13.0/lib/grape/validations/validators/mutual_exclusion.rb 0000644 0000041 0000041 00000001315 12563420522 025200 0 ustar www-data www-data module Grape
module Validations
require 'grape/validations/validators/multiple_params_base'
class MutualExclusionValidator < MultipleParamsBase
attr_reader :processing_keys_in_common
def validate!(params)
super
if two_or_more_exclusive_params_are_present
fail Grape::Exceptions::Validation, params: processing_keys_in_common, message_key: :mutual_exclusion
end
params
end
private
def two_or_more_exclusive_params_are_present
scoped_params.any? do |resource_params|
@processing_keys_in_common = keys_in_common(resource_params)
@processing_keys_in_common.length > 1
end
end
end
end
end
grape-0.13.0/lib/grape/validations/validators/regexp.rb 0000644 0000041 0000041 00000000562 12563420522 023075 0 ustar www-data www-data module Grape
module Validations
class RegexpValidator < Base
def validate_param!(attr_name, params)
if params.key?(attr_name) &&
!params[attr_name].nil? && !(params[attr_name].to_s =~ @option)
fail Grape::Exceptions::Validation, params: [@scope.full_name(attr_name)], message_key: :regexp
end
end
end
end
end
grape-0.13.0/lib/grape/validations/attributes_iterator.rb 0000644 0000041 0000041 00000000733 12563420522 023532 0 ustar www-data www-data module Grape
module Validations
class AttributesIterator
include Enumerable
def initialize(validator, scope, params)
@attrs = validator.attrs
@params = scope.params(params)
@params = (@params.is_a?(Array) ? @params : [@params])
end
def each
@params.each do |resource_params|
@attrs.each do |attr_name|
yield resource_params, attr_name
end
end
end
end
end
end
grape-0.13.0/lib/grape/dsl/ 0000755 0000041 0000041 00000000000 12563420522 015350 5 ustar www-data www-data grape-0.13.0/lib/grape/dsl/inside_route.rb 0000644 0000041 0000041 00000024302 12563420522 020367 0 ustar www-data www-data require 'active_support/concern'
module Grape
module DSL
module InsideRoute
extend ActiveSupport::Concern
include Grape::DSL::Settings
# A filtering method that will return a hash
# consisting only of keys that have been declared by a
# `params` statement against the current/target endpoint or parent
# namespaces.
#
# @param params [Hash] The initial hash to filter. Usually this will just be `params`
# @param options [Hash] Can pass `:include_missing`, `:stringify` and `:include_parent_namespaces`
# options. `:include_parent_namespaces` defaults to true, hence must be set to false if
# you want only to return params declared against the current/target endpoint.
def declared(params, options = {}, declared_params = nil)
options[:include_missing] = true unless options.key?(:include_missing)
options[:include_parent_namespaces] = true unless options.key?(:include_parent_namespaces)
if declared_params.nil?
declared_params = (!options[:include_parent_namespaces] ? route_setting(:declared_params) :
(route_setting(:saved_declared_params) || [])).flatten(1) || []
end
unless declared_params
fail ArgumentError, 'Tried to filter for declared parameters but none exist.'
end
if params.is_a? Array
params.map do |param|
declared(param || {}, options, declared_params)
end
else
declared_params.inject(Hashie::Mash.new) do |hash, key|
key = { key => nil } unless key.is_a? Hash
key.each_pair do |parent, children|
output_key = options[:stringify] ? parent.to_s : parent.to_sym
next unless options[:include_missing] || params.key?(parent)
hash[output_key] = if children
children_params = params[parent] || (children.is_a?(Array) ? [] : {})
declared(children_params, options, Array(children))
else
params[parent]
end
end
hash
end
end
end
# The API version as specified in the URL.
def version
env['api.version']
end
# End the request and display an error to the
# end user with the specified message.
#
# @param message [String] The message to display.
# @param status [Integer] the HTTP Status Code. Defaults to default_error_status, 500 if not set.
def error!(message, status = nil, headers = nil)
self.status(status || namespace_inheritable(:default_error_status))
throw :error, message: message, status: self.status, headers: headers
end
# Redirect to a new url.
#
# @param url [String] The url to be redirect.
# @param options [Hash] The options used when redirect.
# :permanent, default false.
def redirect(url, options = {})
merged_options = { permanent: false }.merge(options)
if merged_options[:permanent]
status 301
else
if env[Grape::Http::Headers::HTTP_VERSION] == 'HTTP/1.1' && request.request_method.to_s.upcase != Grape::Http::Headers::GET
status 303
else
status 302
end
end
header 'Location', url
body ''
end
# Set or retrieve the HTTP status code.
#
# @param status [Integer] The HTTP Status Code to return for this request.
def status(status = nil)
case status
when Symbol
if Rack::Utils::SYMBOL_TO_STATUS_CODE.keys.include?(status)
@status = Rack::Utils.status_code(status)
else
fail ArgumentError, "Status code :#{status} is invalid."
end
when Fixnum
@status = status
when nil
return @status if @status
case request.request_method.to_s.upcase
when Grape::Http::Headers::POST
201
else
200
end
else
fail ArgumentError, 'Status code must be Fixnum or Symbol.'
end
end
# Set an individual header or retrieve
# all headers that have been set.
def header(key = nil, val = nil)
if key
val ? @header[key.to_s] = val : @header.delete(key.to_s)
else
@header
end
end
# Set response content-type
def content_type(val = nil)
if val
header(Grape::Http::Headers::CONTENT_TYPE, val)
else
header[Grape::Http::Headers::CONTENT_TYPE]
end
end
# Set or get a cookie
#
# @example
# cookies[:mycookie] = 'mycookie val'
# cookies['mycookie-string'] = 'mycookie string val'
# cookies[:more] = { value: '123', expires: Time.at(0) }
# cookies.delete :more
#
def cookies
@cookies ||= Cookies.new
end
# Allows you to define the response body as something other than the
# return value.
#
# @example
# get '/body' do
# body "Body"
# "Not the Body"
# end
#
# GET /body # => "Body"
def body(value = nil)
if value
@body = value
elsif value == false
@body = ''
status 204
else
@body
end
end
# Allows you to define the response as a file-like object.
#
# @example
# get '/file' do
# file FileStreamer.new(...)
# end
#
# GET /file # => "contents of file"
def file(value = nil)
if value
@file = Grape::Util::FileResponse.new(value)
else
@file
end
end
# Allows you to define the response as a streamable object.
#
# If Content-Length and Transfer-Encoding are blank (among other conditions),
# Rack assumes this response can be streamed in chunks.
#
# @example
# get '/stream' do
# stream FileStreamer.new(...)
# end
#
# GET /stream # => "chunked contents of file"
#
# See:
# * https://github.com/rack/rack/blob/99293fa13d86cd48021630fcc4bd5acc9de5bdc3/lib/rack/chunked.rb
# * https://github.com/rack/rack/blob/99293fa13d86cd48021630fcc4bd5acc9de5bdc3/lib/rack/etag.rb
def stream(value = nil)
header 'Content-Length', nil
header 'Transfer-Encoding', nil
header 'Cache-Control', 'no-cache' # Skips ETag generation (reading the response up front)
file(value)
end
# Allows you to make use of Grape Entities by setting
# the response body to the serializable hash of the
# entity provided in the `:with` option. This has the
# added benefit of automatically passing along environment
# and version information to the serialization, making it
# very easy to do conditional exposures. See Entity docs
# for more info.
#
# @example
#
# get '/users/:id' do
# present User.find(params[:id]),
# with: API::Entities::User,
# admin: current_user.admin?
# end
def present(*args)
options = args.count > 1 ? args.extract_options! : {}
key, object = if args.count == 2 && args.first.is_a?(Symbol)
args
else
[nil, args.first]
end
entity_class = entity_class_for_obj(object, options)
root = options.delete(:root)
representation = if entity_class
entity_representation_for(entity_class, object, options)
else
object
end
representation = { root => representation } if root
if key
representation = (@body || {}).merge(key => representation)
elsif entity_class.present? && @body
fail ArgumentError, "Representation of type #{representation.class} cannot be merged." unless representation.respond_to?('merge')
representation = @body.merge(representation)
end
body representation
end
# Returns route information for the current request.
#
# @example
#
# desc "Returns the route description."
# get '/' do
# route.route_description
# end
def route
env['rack.routing_args'][:route_info]
end
# Attempt to locate the Entity class for a given object, if not given
# explicitly. This is done by looking for the presence of Klass::Entity,
# where Klass is the class of the `object` parameter, or one of its
# ancestors.
# @param object [Object] the object to locate the Entity class for
# @param options [Hash]
# @option options :with [Class] the explicit entity class to use
# @return [Class] the located Entity class, or nil if none is found
def entity_class_for_obj(object, options)
entity_class = options.delete(:with)
if entity_class.nil?
# entity class not explicitely defined, auto-detect from relation#klass or first object in the collection
object_class = if object.respond_to?(:klass)
object.klass
else
object.respond_to?(:first) ? object.first.class : object.class
end
object_class.ancestors.each do |potential|
entity_class ||= (Grape::DSL::Configuration.stacked_hash_to_hash(namespace_stackable(:representations)) || {})[potential]
end
entity_class ||= object_class.const_get(:Entity) if object_class.const_defined?(:Entity) && object_class.const_get(:Entity).respond_to?(:represent)
end
entity_class
end
# @return the representation of the given object as done through
# the given entity_class.
def entity_representation_for(entity_class, object, options)
embeds = { env: env }
embeds[:version] = env['api.version'] if env['api.version']
entity_class.represent(object, embeds.merge(options))
end
end
end
end
grape-0.13.0/lib/grape/dsl/request_response.rb 0000644 0000041 0000041 00000013752 12563420522 021313 0 ustar www-data www-data require 'active_support/concern'
module Grape
module DSL
module RequestResponse
extend ActiveSupport::Concern
include Grape::DSL::Configuration
module ClassMethods
# Specify the default format for the API's serializers.
# May be `:json` or `:txt` (default).
def default_format(new_format = nil)
namespace_inheritable(:default_format, new_format.nil? ? nil : new_format.to_sym)
end
# Specify the format for the API's serializers.
# May be `:json`, `:xml`, `:txt`, etc.
def format(new_format = nil)
if new_format
namespace_inheritable(:format, new_format.to_sym)
# define the default error formatters
namespace_inheritable(:default_error_formatter, Grape::ErrorFormatter::Base.formatter_for(new_format, {}))
# define a single mime type
mime_type = content_types[new_format.to_sym]
fail Grape::Exceptions::MissingMimeType.new(new_format) unless mime_type
namespace_stackable(:content_types, new_format.to_sym => mime_type)
else
namespace_inheritable(:format)
end
end
# Specify a custom formatter for a content-type.
def formatter(content_type, new_formatter)
namespace_stackable(:formatters, content_type.to_sym => new_formatter)
end
# Specify a custom parser for a content-type.
def parser(content_type, new_parser)
namespace_stackable(:parsers, content_type.to_sym => new_parser)
end
# Specify a default error formatter.
def default_error_formatter(new_formatter_name = nil)
if new_formatter_name
new_formatter = Grape::ErrorFormatter::Base.formatter_for(new_formatter_name, {})
namespace_inheritable(:default_error_formatter, new_formatter)
else
namespace_inheritable(:default_error_formatter)
end
end
def error_formatter(format, options)
if options.is_a?(Hash) && options.key?(:with)
formatter = options[:with]
else
formatter = options
end
namespace_stackable(:error_formatters, format.to_sym => formatter)
end
# Specify additional content-types, e.g.:
# content_type :xls, 'application/vnd.ms-excel'
def content_type(key, val)
namespace_stackable(:content_types, key.to_sym => val)
end
# All available content types.
def content_types
c_types = Grape::DSL::Configuration.stacked_hash_to_hash(namespace_stackable(:content_types))
Grape::ContentTypes.content_types_for c_types
end
# Specify the default status code for errors.
def default_error_status(new_status = nil)
namespace_inheritable(:default_error_status, new_status)
end
# Allows you to rescue certain exceptions that occur to return
# a grape error rather than raising all the way to the
# server level.
#
# @example Rescue from custom exceptions
# class ExampleAPI < Grape::API
# class CustomError < StandardError; end
#
# rescue_from CustomError
# end
#
# @overload rescue_from(*exception_classes, options = {})
# @param [Array] exception_classes A list of classes that you want to rescue, or
# the symbol :all to rescue from all exceptions.
# @param [Block] block Execution block to handle the given exception.
# @param [Hash] options Options for the rescue usage.
# @option options [Boolean] :backtrace Include a backtrace in the rescue response.
# @option options [Boolean] :rescue_subclasses Also rescue subclasses of exception classes
# @param [Proc] handler Execution proc to handle the given exception as an
# alternative to passing a block.
def rescue_from(*args, &block)
if args.last.is_a?(Proc)
handler = args.pop
elsif block_given?
handler = block
end
options = args.last.is_a?(Hash) ? args.pop : {}
handler ||= proc { options[:with] } if options.key?(:with)
if args.include?(:all)
namespace_inheritable(:rescue_all, true)
namespace_inheritable :all_rescue_handler, handler
else
handler_type =
case options[:rescue_subclasses]
when nil, true
:rescue_handlers
else
:base_only_rescue_handlers
end
namespace_stackable handler_type, Hash[args.map { |arg| [arg, handler] }]
end
namespace_stackable(:rescue_options, options)
end
# Allows you to specify a default representation entity for a
# class. This allows you to map your models to their respective
# entities once and then simply call `present` with the model.
#
# @example
# class ExampleAPI < Grape::API
# represent User, with: Entity::User
#
# get '/me' do
# present current_user # with: Entity::User is assumed
# end
# end
#
# Note that Grape will automatically go up the class ancestry to
# try to find a representing entity, so if you, for example, define
# an entity to represent `Object` then all presented objects will
# bubble up and utilize the entity provided on that `represent` call.
#
# @param model_class [Class] The model class that will be represented.
# @option options [Class] :with The entity class that will represent the model.
def represent(model_class, options)
fail Grape::Exceptions::InvalidWithOptionForRepresent.new unless options[:with] && options[:with].is_a?(Class)
namespace_stackable(:representations, model_class => options[:with])
end
end
end
end
end
grape-0.13.0/lib/grape/dsl/configuration.rb 0000644 0000041 0000041 00000010104 12563420522 020540 0 ustar www-data www-data require 'active_support/concern'
module Grape
module DSL
module Configuration
extend ActiveSupport::Concern
module ClassMethods
attr_writer :logger
include Grape::DSL::Settings
# Set or retrive the configured logger. If none was configured, this
# method will create a new one, logging to stdout.
# @param logger [Object] the new logger to use
def logger(logger = nil)
if logger
global_setting(:logger, logger)
else
global_setting(:logger) || global_setting(:logger, Logger.new($stdout))
end
end
# Add a description to the next namespace or function.
# @param description [String] descriptive string for this endpoint
# or namespace
# @param options [Hash] other properties you can set to describe the
# endpoint or namespace. Optional.
# @option options :detail [String] additional detail about this endpoint
# @option options :params [Hash] param types and info. normally, you set
# these via the `params` dsl method.
# @option options :entity [Grape::Entity] the entity returned upon a
# successful call to this action
# @option options :http_codes [Array[Array]] possible HTTP codes this
# endpoint may return, with their meanings, in a 2d array
# @option options :named [String] a specific name to help find this route
# @option options :headers [Hash] HTTP headers this method can accept
# @yield a block yielding an instance context with methods mapping to
# each of the above, except that :entity is also aliased as #success
# and :http_codes is aliased as #failure.
#
# @example
#
# desc 'create a user'
# post '/users' do
# # ...
# end
#
# desc 'find a user' do
# detail 'locates the user from the given user ID'
# failure [ [404, 'Couldn\'t find the given user' ] ]
# success User::Entity
# end
# get '/user/:id' do
# # ...
# end
#
def desc(description, options = {}, &config_block)
if block_given?
config_class = Grape::DSL::Configuration.desc_container
config_class.configure do
description description
end
config_class.configure(&config_block)
options = config_class.settings
else
options = options.merge(description: description)
end
namespace_setting :description, options
route_setting :description, options
end
def description_field(field, value = nil)
if value
description = route_setting(:description)
description ||= route_setting(:description, {})
description[field] = value
else
description = route_setting(:description)
description[field] if description
end
end
def unset_description_field(field)
description = route_setting(:description)
description.delete(field) if description
end
end
module_function
# Merge multiple layers of settings into one hash.
def stacked_hash_to_hash(settings)
return nil if settings.nil? || settings.blank?
settings.each_with_object(ActiveSupport::OrderedHash.new) { |value, result| result.deep_merge!(value) }
end
# Returns an object which configures itself via an instance-context DSL.
def desc_container
Module.new do
include Grape::Util::StrictHashConfiguration.module(
:description,
:detail,
:params,
:entity,
:http_codes,
:named,
:headers
)
def config_context.success(*args)
entity(*args)
end
def config_context.failure(*args)
http_codes(*args)
end
end
end
end
end
end
grape-0.13.0/lib/grape/dsl/api.rb 0000644 0000041 0000041 00000000663 12563420522 016453 0 ustar www-data www-data require 'active_support/concern'
module Grape
module DSL
module API
extend ActiveSupport::Concern
include Grape::Middleware::Auth::DSL
include Grape::DSL::Validations
include Grape::DSL::Callbacks
include Grape::DSL::Configuration
include Grape::DSL::Helpers
include Grape::DSL::Middleware
include Grape::DSL::RequestResponse
include Grape::DSL::Routing
end
end
end
grape-0.13.0/lib/grape/dsl/helpers.rb 0000644 0000041 0000041 00000004405 12563420522 017342 0 ustar www-data www-data require 'active_support/concern'
module Grape
module DSL
module Helpers
extend ActiveSupport::Concern
include Grape::DSL::Configuration
module ClassMethods
# Add helper methods that will be accessible from any
# endpoint within this namespace (and child namespaces).
#
# When called without a block, all known helpers within this scope
# are included.
#
# @param [Module] new_mod optional module of methods to include
# @param [Block] block optional block of methods to include
#
# @example Define some helpers.
#
# class ExampleAPI < Grape::API
# helpers do
# def current_user
# User.find_by_id(params[:token])
# end
# end
# end
#
def helpers(new_mod = nil, &block)
if block_given? || new_mod
mod = new_mod || Module.new
if new_mod
inject_api_helpers_to_mod(new_mod) if new_mod.is_a?(BaseHelper)
end
if block_given?
inject_api_helpers_to_mod(mod) do
mod.class_eval(&block)
end
end
namespace_stackable(:helpers, mod)
else
mod = Module.new
namespace_stackable(:helpers).each do |mod_to_include|
mod.send :include, mod_to_include
end
change!
mod
end
end
protected
def inject_api_helpers_to_mod(mod, &_block)
mod.extend(BaseHelper)
yield if block_given?
mod.api_changed(self)
end
end
# This module extends user defined helpers
# to provide some API-specific functionality.
module BaseHelper
attr_accessor :api
def params(name, &block)
@named_params ||= {}
@named_params[name] = block
end
def api_changed(new_api)
@api = new_api
process_named_params
end
protected
def process_named_params
if @named_params && @named_params.any?
api.namespace_stackable(:named_params, @named_params)
end
end
end
end
end
end
grape-0.13.0/lib/grape/dsl/middleware.rb 0000644 0000041 0000041 00000001553 12563420522 020016 0 ustar www-data www-data require 'active_support/concern'
module Grape
module DSL
module Middleware
extend ActiveSupport::Concern
include Grape::DSL::Configuration
module ClassMethods
# Apply a custom middleware to the API. Applies
# to the current namespace and any children, but
# not parents.
#
# @param middleware_class [Class] The class of the middleware you'd like
# to inject.
def use(middleware_class, *args, &block)
arr = [middleware_class, *args]
arr << block if block_given?
namespace_stackable(:middleware, arr)
end
# Retrieve an array of the middleware classes
# and arguments that are currently applied to the
# application.
def middleware
namespace_stackable(:middleware) || []
end
end
end
end
end
grape-0.13.0/lib/grape/dsl/routing.rb 0000644 0000041 0000041 00000014561 12563420522 017373 0 ustar www-data www-data require 'active_support/concern'
module Grape
module DSL
module Routing
extend ActiveSupport::Concern
include Grape::DSL::Configuration
module ClassMethods
attr_reader :endpoints, :routes, :route_set
# Specify an API version.
#
# @example API with legacy support.
# class MyAPI < Grape::API
# version 'v2'
#
# get '/main' do
# {some: 'data'}
# end
#
# version 'v1' do
# get '/main' do
# {legacy: 'data'}
# end
# end
# end
#
def version(*args, &block)
if args.any?
options = args.pop if args.last.is_a? Hash
options ||= {}
options = { using: :path }.merge(options)
fail Grape::Exceptions::MissingVendorOption.new if options[:using] == :header && !options.key?(:vendor)
@versions = versions | args
if block_given?
within_namespace do
namespace_inheritable(:version, args)
namespace_inheritable(:version_options, options)
instance_eval(&block)
end
else
namespace_inheritable(:version, args)
namespace_inheritable(:version_options, options)
end
# reset_validations!
end
@versions.last unless @versions.nil?
end
# Define a root URL prefix for your entire API.
def prefix(prefix = nil)
namespace_inheritable(:root_prefix, prefix)
end
# Do not route HEAD requests to GET requests automatically.
def do_not_route_head!
namespace_inheritable(:do_not_route_head, true)
end
# Do not automatically route OPTIONS.
def do_not_route_options!
namespace_inheritable(:do_not_route_options, true)
end
def mount(mounts)
mounts = { mounts => '/' } unless mounts.respond_to?(:each_pair)
mounts.each_pair do |app, path|
in_setting = inheritable_setting
if app.respond_to?(:inheritable_setting, true)
mount_path = Rack::Mount::Utils.normalize_path(path)
app.top_level_setting.namespace_stackable[:mount_path] = mount_path
app.inherit_settings(inheritable_setting)
in_setting = app.top_level_setting
# app.regenerate_endpoints(in_setting)
app.change!
change!
end
endpoints << Grape::Endpoint.new(
in_setting,
method: :any,
path: path,
app: app,
for: self
)
end
end
# Defines a route that will be recognized
# by the Grape API.
#
# @param methods [HTTP Verb] One or more HTTP verbs that are accepted by this route. Set to `:any` if you want any verb to be accepted.
# @param paths [String] One or more strings representing the URL segment(s) for this route.
#
# @example Defining a basic route.
# class MyAPI < Grape::API
# route(:any, '/hello') do
# {hello: 'world'}
# end
# end
def route(methods, paths = ['/'], route_options = {}, &block)
endpoint_options = {
method: methods,
path: paths,
for: self,
route_options: ({
params: Grape::DSL::Configuration.stacked_hash_to_hash(namespace_stackable(:params)) || {}
}).deep_merge(route_setting(:description) || {}).deep_merge(route_options || {})
}
new_endpoint = Grape::Endpoint.new(inheritable_setting, endpoint_options, &block)
endpoints << new_endpoint unless endpoints.any? { |e| e.equals?(new_endpoint) }
route_end
reset_validations!
end
%w(get post put head delete options patch).each do |meth|
define_method meth do |*args, &block|
options = args.extract_options!
paths = args.first || ['/']
route(meth.upcase, paths, options, &block)
end
end
# Declare a "namespace", which prefixes all subordinate routes with its
# name. Any endpoints within a namespace, or group, resource, segment,
# etc., will share their parent context as well as any configuration
# done in the namespace context.
#
# @example
#
# namespace :foo do
# get 'bar' do
# # defines the endpoint: GET /foo/bar
# end
# end
def namespace(space = nil, options = {}, &block)
if space || block_given?
within_namespace do
previous_namespace_description = @namespace_description
@namespace_description = (@namespace_description || {}).deep_merge(namespace_setting(:description) || {})
nest(block) do
if space
namespace_stackable(:namespace, Namespace.new(space, options))
end
end
@namespace_description = previous_namespace_description
end
else
Namespace.joined_space_path(namespace_stackable(:namespace))
end
end
alias_method :group, :namespace
alias_method :resource, :namespace
alias_method :resources, :namespace
alias_method :segment, :namespace
# An array of API routes.
def routes
@routes ||= prepare_routes
end
# Remove all defined routes.
def reset_routes!
@routes = nil
end
# Thie method allows you to quickly define a parameter route segment
# in your API.
#
# @param param [Symbol] The name of the parameter you wish to declare.
# @option options [Regexp] You may supply a regular expression that the declared parameter must meet.
def route_param(param, options = {}, &block)
options = options.dup
options[:requirements] = { param.to_sym => options[:requirements] } if options[:requirements].is_a?(Regexp)
namespace(":#{param}", options, &block)
end
# @return array of defined versions
def versions
@versions ||= []
end
end
end
end
end
grape-0.13.0/lib/grape/dsl/parameters.rb 0000644 0000041 0000041 00000015770 12563420522 020052 0 ustar www-data www-data require 'active_support/concern'
module Grape
module DSL
# Defines DSL methods, meant to be applied to a ParamsScope, which define
# and describe the parameters accepted by an endpoint, or all endpoints
# within a namespace.
module Parameters
extend ActiveSupport::Concern
# Include reusable params rules among current.
# You can define reusable params with helpers method.
#
# @example
#
# class API < Grape::API
# helpers do
# params :pagination do
# optional :page, type: Integer
# optional :per_page, type: Integer
# end
# end
#
# desc "Get collection"
# params do
# use :pagination
# end
# get do
# Collection.page(params[:page]).per(params[:per_page])
# end
# end
def use(*names)
named_params = Grape::DSL::Configuration.stacked_hash_to_hash(@api.namespace_stackable(:named_params)) || {}
options = names.last.is_a?(Hash) ? names.pop : {}
names.each do |name|
params_block = named_params.fetch(name) do
fail "Params :#{name} not found!"
end
instance_exec(options, ¶ms_block)
end
end
alias_method :use_scope, :use
alias_method :includes, :use
# Require one or more parameters for the current endpoint.
#
# @param attrs list of parameter names, or, if :using is
# passed as an option, which keys to include (:all or :none) from
# the :using hash. The last key can be a hash, which specifies
# options for the parameters
# @option attrs :type [Class] the type to coerce this parameter to before
# passing it to the endpoint. See Grape::ParameterTypes for supported
# types, or use a class that defines `::parse` as a custom type
# @option attrs :desc [String] description to document this parameter
# @option attrs :default [Object] default value, if parameter is optional
# @option attrs :values [Array] permissable values for this field. If any
# other value is given, it will be handled as a validation error
# @option attrs :using [Hash[Symbol => Hash]] a hash defining keys and
# options, like that returned by Grape::Entity#documentation. The value
# of each key is an options hash accepting the same parameters
# @option attrs :except [Array[Symbol]] a list of keys to exclude from
# the :using Hash. The meaning of this depends on if :all or :none was
# passed; :all + :except will make the :except fields optional, whereas
# :none + :except will make the :except fields required
#
# @example
#
# params do
# # Basic usage: require a parameter of a certain type
# requires :user_id, type: Integer
#
# # You don't need to specify type; String is default
# requires :foo
#
# # Multiple params can be specified at once if they share
# # the same options.
# requires :x, :y, :z, type: Date
#
# # Nested parameters can be handled as hashes. You must
# # pass in a block, within which you can use any of the
# # parameters DSL methods.
# requires :user, type: Hash do
# requires :name, type: String
# end
# end
def requires(*attrs, &block)
orig_attrs = attrs.clone
opts = attrs.last.is_a?(Hash) ? attrs.pop.clone : {}
opts[:presence] = true
if opts[:using]
require_required_and_optional_fields(attrs.first, opts)
else
validate_attributes(attrs, opts, &block)
block_given? ? new_scope(orig_attrs, &block) :
push_declared_params(attrs)
end
end
# Allow, but don't require, one or more parameters for the current
# endpoint.
# @param (see #requires)
# @option (see #requires)
def optional(*attrs, &block)
orig_attrs = attrs.clone
opts = attrs.last.is_a?(Hash) ? attrs.pop.clone : {}
type = opts[:type]
# check type for optional parameter group
if attrs && block_given?
fail Grape::Exceptions::MissingGroupTypeError.new if type.nil?
fail Grape::Exceptions::UnsupportedGroupTypeError.new unless [Array, Hash].include?(type)
end
if opts[:using]
require_optional_fields(attrs.first, opts)
else
validate_attributes(attrs, opts, &block)
block_given? ? new_scope(orig_attrs, true, &block) :
push_declared_params(attrs)
end
end
# Disallow the given parameters to be present in the same request.
# @param attrs [*Symbol] parameters to validate
def mutually_exclusive(*attrs)
validates(attrs, mutual_exclusion: true)
end
# Require exactly one of the given parameters to be present.
# @param (see #mutually_exclusive)
def exactly_one_of(*attrs)
validates(attrs, exactly_one_of: true)
end
# Require at least one of the given parameters to be present.
# @param (see #mutually_exclusive)
def at_least_one_of(*attrs)
validates(attrs, at_least_one_of: true)
end
# Require that either all given params are present, or none are.
# @param (see #mutually_exclusive)
def all_or_none_of(*attrs)
validates(attrs, all_or_none_of: true)
end
# Define a block of validations which should be applied if and only if
# the given parameter is present. The parameters are not nested.
# @param attr [Symbol] the parameter which, if present, triggers the
# validations
# @throws Grape::Exceptions::UnknownParameter if `attr` has not been
# defined in this scope yet
# @yield a parameter definition DSL
def given(attr, &block)
fail Grape::Exceptions::UnknownParameter.new(attr) unless declared_param?(attr)
new_lateral_scope(dependent_on: attr, &block)
end
# Test for whether a certain parameter has been defined in this params
# block yet.
# @returns [Boolean] whether the parameter has been defined
def declared_param?(param)
# @declared_params also includes hashes of options and such, but those
# won't be flattened out.
@declared_params.flatten.include?(param)
end
alias_method :group, :requires
# @param params [Hash] initial hash of parameters
# @return hash of parameters relevant for the current scope
# @api private
def params(params)
params = @parent.params(params) if @parent
if @element
if params.is_a?(Array)
params = params.flat_map { |el| el[@element] || {} }
elsif params.is_a?(Hash)
params = params[@element] || {}
else
params = {}
end
end
params
end
end
end
end
grape-0.13.0/lib/grape/dsl/callbacks.rb 0000644 0000041 0000041 00000002563 12563420522 017622 0 ustar www-data www-data require 'active_support/concern'
module Grape
module DSL
# Blocks can be executed before or after every API call, using `before`, `after`,
# `before_validation` and `after_validation`.
#
# Before and after callbacks execute in the following order:
#
# 1. `before`
# 2. `before_validation`
# 3. _validations_
# 4. `after_validation`
# 5. _the API call_
# 6. `after`
#
# Steps 4, 5 and 6 only happen if validation succeeds.
module Callbacks
extend ActiveSupport::Concern
include Grape::DSL::Configuration
module ClassMethods
# Execute the given block before validation, coercion, or any endpoint
# code is executed.
def before(&block)
namespace_stackable(:befores, block)
end
# Execute the given block after `before`, but prior to validation or
# coercion.
def before_validation(&block)
namespace_stackable(:before_validations, block)
end
# Execute the given block after validations and coercions, but before
# any endpoint code.
def after_validation(&block)
namespace_stackable(:after_validations, block)
end
# Execute the given block after the endpoint code has run.
def after(&block)
namespace_stackable(:afters, block)
end
end
end
end
end
grape-0.13.0/lib/grape/dsl/validations.rb 0000644 0000041 0000041 00000002226 12563420522 020214 0 ustar www-data www-data require 'active_support/concern'
module Grape
module DSL
module Validations
extend ActiveSupport::Concern
include Grape::DSL::Configuration
module ClassMethods
# Clears all defined parameters and validations.
def reset_validations!
unset_namespace_stackable :declared_params
unset_namespace_stackable :validations
unset_namespace_stackable :params
unset_description_field :params
end
# Opens a root-level ParamsScope, defining parameter coercions and
# validations for the endpoint.
# @yield instance context of the new scope
def params(&block)
Grape::Validations::ParamsScope.new(api: self, type: Hash, &block)
end
def document_attribute(names, opts)
setting = description_field(:params)
setting ||= description_field(:params, {})
Array(names).each do |name|
setting[name[:full_name].to_s] ||= {}
setting[name[:full_name].to_s].merge!(opts)
namespace_stackable(:params, name[:full_name].to_s => opts)
end
end
end
end
end
end
grape-0.13.0/lib/grape/dsl/settings.rb 0000644 0000041 0000041 00000010317 12563420522 017537 0 ustar www-data www-data require 'active_support/concern'
module Grape
module DSL
# Keeps track of settings (impemented as key-value pairs, grouped by
# types), in two contexts: top-level settings which apply globally no
# matter where they're defined, and inheritable settings which apply only
# in the current scope and scopes nested under it.
module Settings
extend ActiveSupport::Concern
attr_accessor :inheritable_setting, :top_level_setting
# Fetch our top-level settings, which apply to all endpoints in the API.
def top_level_setting
@top_level_setting ||= Grape::Util::InheritableSetting.new
end
# Fetch our current inheritable settings, which are inherited by
# nested scopes but not shared across siblings.
def inheritable_setting
@inheritable_setting ||= Grape::Util::InheritableSetting.new.tap { |new_settings| new_settings.inherit_from top_level_setting }
end
# @param type [Symbol]
# @param key [Symbol]
def unset(type, key)
setting = inheritable_setting.send(type)
setting.delete key
end
# @param type [Symbol]
# @param key [Symbol]
# @param value [Object] will be stored if the value is currently empty
# @return either the old value, if it wasn't nil, or the given value
def get_or_set(type, key, value)
setting = inheritable_setting.send(type)
if value.nil?
setting[key]
else
setting[key] = value
end
end
# @param key [Symbol]
# @param value [Object]
# @return (see #get_or_set)
def global_setting(key, value = nil)
get_or_set :global, key, value
end
# @param key [Symbol]
def unset_global_setting(key)
unset :global, key
end
# (see #global_setting)
def route_setting(key, value = nil)
get_or_set :route, key, value
end
# (see #unset_global_setting)
def unset_route_setting(key)
unset :route, key
end
# (see #global_setting)
def namespace_setting(key, value = nil)
get_or_set :namespace, key, value
end
# (see #unset_global_setting)
def unset_namespace_setting(key)
unset :namespace_setting, key
end
# (see #global_setting)
def namespace_inheritable(key, value = nil)
get_or_set :namespace_inheritable, key, value
end
# (see #unset_global_setting)
def unset_namespace_inheritable(key)
unset :namespace_inheritable, key
end
# @param key [Symbol]
def namespace_inheritable_to_nil(key)
inheritable_setting.namespace_inheritable[key] = nil
end
# (see #global_setting)
def namespace_stackable(key, value = nil)
get_or_set :namespace_stackable, key, value
end
# (see #unset_global_setting)
def unset_namespace_stackable(key)
unset :namespace_stackable, key
end
# (see #global_setting)
def api_class_setting(key, value = nil)
get_or_set :api_class, key, value
end
# (see #unset_global_setting)
def unset_api_class_setting(key)
unset :api_class_setting, key
end
# Fork our inheritable settings to a new instance, copied from our
# parent's, but separate so we won't modify it. Every call to this
# method should have an answering call to #namespace_end.
def namespace_start
@inheritable_setting = Grape::Util::InheritableSetting.new.tap { |new_settings| new_settings.inherit_from inheritable_setting }
end
# Set the inheritable settings pointer back up by one level.
def namespace_end
route_end
@inheritable_setting = inheritable_setting.parent
end
# Stop defining settings for the current route and clear them for the
# next, within a namespace.
def route_end
inheritable_setting.route_end
end
# Execute the block within a context where our inheritable settings are forked
# to a new copy (see #namespace_start).
def within_namespace(&_block)
namespace_start
result = yield if block_given?
namespace_end
reset_validations!
result
end
end
end
end
grape-0.13.0/lib/grape/presenters/ 0000755 0000041 0000041 00000000000 12563420522 016760 5 ustar www-data www-data grape-0.13.0/lib/grape/presenters/presenter.rb 0000644 0000041 0000041 00000000220 12563420522 021306 0 ustar www-data www-data module Grape
module Presenters
class Presenter
def self.represent(object, _options = {})
object
end
end
end
end
grape-0.13.0/lib/grape/http/ 0000755 0000041 0000041 00000000000 12563420522 015545 5 ustar www-data www-data grape-0.13.0/lib/grape/http/headers.rb 0000644 0000041 0000041 00000001433 12563420522 017506 0 ustar www-data www-data module Grape
module Http
module Headers
# https://github.com/rack/rack/blob/master/lib/rack.rb
HTTP_VERSION = 'HTTP_VERSION'.freeze
PATH_INFO = 'PATH_INFO'.freeze
QUERY_STRING = 'QUERY_STRING'.freeze
CONTENT_TYPE = 'Content-Type'.freeze
GET = 'GET'.freeze
POST = 'POST'.freeze
PUT = 'PUT'.freeze
PATCH = 'PATCH'.freeze
DELETE = 'DELETE'.freeze
HEAD = 'HEAD'.freeze
OPTIONS = 'OPTIONS'.freeze
HTTP_ACCEPT_VERSION = 'HTTP_ACCEPT_VERSION'.freeze
X_CASCADE = 'X-Cascade'.freeze
HTTP_TRANSFER_ENCODING = 'HTTP_TRANSFER_ENCODING'.freeze
HTTP_ACCEPT = 'HTTP_ACCEPT'.freeze
FORMAT = 'format'.freeze
end
end
end
grape-0.13.0/lib/grape/http/request.rb 0000644 0000041 0000041 00000001457 12563420522 017571 0 ustar www-data www-data module Grape
class Request < Rack::Request
ROUTING_ARGS = 'rack.routing_args'
HTTP_PREFIX = 'HTTP_'
UNDERSCORE = '_'
MINUS = '-'
def params
@params ||= begin
params = Hashie::Mash.new(super)
if env[ROUTING_ARGS]
args = env[ROUTING_ARGS].dup
# preserve version from query string parameters
args.delete(:version)
args.delete(:route_info)
params.deep_merge!(args)
end
params
end
end
def headers
@headers ||= env.dup.inject({}) do |h, (k, v)|
if k.to_s.start_with? HTTP_PREFIX
k = k[5..-1]
k.tr!(UNDERSCORE, MINUS)
k.downcase!
k.gsub!(/^.|[-_\s]./, &:upcase!)
h[k] = v
end
h
end
end
end
end
grape-0.13.0/lib/grape/validations.rb 0000644 0000041 0000041 00000000770 12563420522 017434 0 ustar www-data www-data module Grape
# Registry to store and locate known Validators.
module Validations
class << self
attr_accessor :validators
end
self.validators = {}
# Register a new validator, so it can be used to validate parameters.
# @param short_name [String] all lower-case, no spaces
# @param klass [Class] the validator class. Should inherit from
# Validations::Base.
def self.register_validator(short_name, klass)
validators[short_name] = klass
end
end
end
grape-0.13.0/lib/grape/util/ 0000755 0000041 0000041 00000000000 12563420522 015543 5 ustar www-data www-data grape-0.13.0/lib/grape/util/strict_hash_configuration.rb 0000644 0000041 0000041 00000005172 12563420522 023337 0 ustar www-data www-data module Grape
module Util
module StrictHashConfiguration
extend ActiveSupport::Concern
module DSL
extend ActiveSupport::Concern
module ClassMethods
def settings
config_context.to_hash
end
def configure(&block)
config_context.instance_exec(&block)
end
end
end
class SettingsContainer
def initialize
@settings = {}
@contexts = {}
end
def to_hash
@settings.to_hash
end
end
def self.config_class(*args)
new_config_class = Class.new(SettingsContainer)
args.each do |setting_name|
if setting_name.respond_to? :values
nested_settings_methods(setting_name, new_config_class)
else
simple_settings_methods(setting_name, new_config_class)
end
end
new_config_class
end
def self.simple_settings_methods(setting_name, new_config_class)
setting_name_sym = setting_name.to_sym
new_config_class.class_eval do
define_method setting_name do |new_value|
@settings[setting_name_sym] = new_value
end
end
end
def self.nested_settings_methods(setting_name, new_config_class)
new_config_class.class_eval do
setting_name.each_pair do |key, value|
define_method "#{key}_context" do
@contexts[key] ||= Grape::Util::StrictHashConfiguration.config_class(*value).new
end
define_method key do |&block|
send("#{key}_context").instance_exec(&block)
end
end
define_method 'to_hash' do
merge_hash = setting_name.keys.each_with_object({}) { |k, hash| hash[k] = send("#{k}_context").to_hash }
@settings.to_hash.merge(
merge_hash
)
end
end
end
def self.module(*args)
new_module = Module.new do
extend ActiveSupport::Concern
include DSL
end
new_module.tap do |mod|
class_mod = create_class_mod(args)
mod.const_set(:ClassMethods, class_mod)
end
end
def self.create_class_mod(args)
new_module = Module.new do
def config_context
@config_context ||= config_class.new
end
end
new_module.tap do |class_mod|
new_config_class = config_class(*args)
class_mod.send(:define_method, :config_class) do
@config_context ||= new_config_class
end
end
end
end
end
end
grape-0.13.0/lib/grape/util/stackable_values.rb 0000644 0000041 0000041 00000002344 12563420522 021403 0 ustar www-data www-data module Grape
module Util
class StackableValues
attr_accessor :inherited_values
attr_reader :new_values
attr_reader :froozen_values
def initialize(inherited_values = {})
@inherited_values = inherited_values
@new_values = {}
@froozen_values = {}
end
def [](name)
return @froozen_values[name] if @froozen_values.key? name
value = [@inherited_values[name], @new_values[name]]
value.compact!
value.flatten!(1)
value
end
def []=(name, value)
fail if @froozen_values.key? name
@new_values[name] ||= []
@new_values[name].push value
end
def delete(key)
new_values.delete key
end
attr_writer :new_values
def keys
(@new_values.keys + @inherited_values.keys).sort.uniq
end
def to_hash
keys.each_with_object({}) do |key, result|
result[key] = self[key]
end
end
def freeze_value(key)
@froozen_values[key] = self[key].freeze
end
def initialize_copy(other)
super
self.inherited_values = other.inherited_values
self.new_values = other.new_values.dup
end
end
end
end
grape-0.13.0/lib/grape/util/content_types.rb 0000644 0000041 0000041 00000001432 12563420522 020766 0 ustar www-data www-data module Grape
module ContentTypes
# Content types are listed in order of preference.
CONTENT_TYPES = ActiveSupport::OrderedHash[
:xml, 'application/xml',
:serializable_hash, 'application/json',
:json, 'application/json',
:binary, 'application/octet-stream',
:txt, 'text/plain'
]
def self.content_types_for_settings(settings)
return nil if settings.nil? || settings.blank?
settings.each_with_object(ActiveSupport::OrderedHash.new) { |value, result| result.merge!(value) }
end
def self.content_types_for(from_settings)
if from_settings.present?
from_settings
else
Grape::ContentTypes::CONTENT_TYPES
end
end
end
end
grape-0.13.0/lib/grape/util/inheritable_values.rb 0000644 0000041 0000041 00000001623 12563420522 021737 0 ustar www-data www-data module Grape
module Util
class InheritableValues
attr_accessor :inherited_values
attr_accessor :new_values
def initialize(inherited_values = {})
self.inherited_values = inherited_values
self.new_values = {}
end
def [](name)
values[name]
end
def []=(name, value)
new_values[name] = value
end
def delete(key)
new_values.delete key
end
def merge(new_hash)
values.merge(new_hash)
end
def keys
(new_values.keys + inherited_values.keys).sort.uniq
end
def to_hash
values.clone
end
def initialize_copy(other)
super
self.inherited_values = other.inherited_values
self.new_values = other.new_values.dup
end
protected
def values
@inherited_values.merge(@new_values)
end
end
end
end
grape-0.13.0/lib/grape/util/file_response.rb 0000644 0000041 0000041 00000000706 12563420522 020730 0 ustar www-data www-data module Grape
module Util
# A simple class used to identify responses which represent files and do not
# need to be formatted or pre-read by Rack::Response
class FileResponse
attr_reader :file
# @param file [Object]
def initialize(file)
@file = file
end
# Equality provided mostly for tests.
#
# @return [Boolean]
def ==(other)
file == other.file
end
end
end
end
grape-0.13.0/lib/grape/util/inheritable_setting.rb 0000644 0000041 0000041 00000006126 12563420522 022120 0 ustar www-data www-data module Grape
module Util
# A branchable, inheritable settings object which can store both stackable
# and inheritable values (see InheritableValues and StackableValues).
class InheritableSetting
attr_accessor :route, :api_class, :namespace, :namespace_inheritable, :namespace_stackable
attr_accessor :parent, :point_in_time_copies
# Retrieve global settings.
def self.global
@global ||= {}
end
# Clear all global settings.
# @api private
# @note only for testing
def self.reset_global!
@global = {}
end
# Instantiate a new settings instance, with blank values. The fresh
# instance can then be set to inherit from an existing instance (see
# #inherit_from).
def initialize
self.route = {}
self.api_class = {}
self.namespace = InheritableValues.new # only inheritable from a parent when
# used with a mount, or should every API::Class be a separate namespace by default?
self.namespace_inheritable = InheritableValues.new
self.namespace_stackable = StackableValues.new
self.point_in_time_copies = []
self.parent = nil
end
# Return the class-level global properties.
def global
self.class.global
end
# Set our inherited values to the given parent's current values. Also,
# update the inherited values on any settings instances which were forked
# from us.
# @param parent [InheritableSetting]
def inherit_from(parent)
return if parent.nil?
self.parent = parent
namespace_inheritable.inherited_values = parent.namespace_inheritable
namespace_stackable.inherited_values = parent.namespace_stackable
self.route = parent.route.merge(route)
point_in_time_copies.map { |cloned_one| cloned_one.inherit_from parent }
end
# Create a point-in-time copy of this settings instance, with clones of
# all our values. Note that, should this instance's parent be set or
# changed via #inherit_from, it will copy that inheritence to any copies
# which were made.
def point_in_time_copy
self.class.new.tap do |new_setting|
point_in_time_copies << new_setting
new_setting.point_in_time_copies = []
new_setting.namespace = namespace.clone
new_setting.namespace_inheritable = namespace_inheritable.clone
new_setting.namespace_stackable = namespace_stackable.clone
new_setting.route = route.clone
new_setting.api_class = api_class
new_setting.inherit_from(parent)
end
end
# Resets the instance store of per-route settings.
# @api private
def route_end
@route = {}
end
# Return a serializable hash of our values.
def to_hash
{
global: global.clone,
route: route.clone,
namespace: namespace.to_hash,
namespace_inheritable: namespace_inheritable.to_hash,
namespace_stackable: namespace_stackable.to_hash
}
end
end
end
end
grape-0.13.0/lib/grape/util/parameter_types.rb 0000644 0000041 0000041 00000002752 12563420522 021302 0 ustar www-data www-data module Grape
module ParameterTypes
# Types representing a single value, which are coerced through Virtus
# or special logic in Grape.
PRIMITIVES = [
# Numerical
Integer,
Float,
BigDecimal,
Numeric,
# Date/time
Date,
DateTime,
Time,
# Misc
Virtus::Attribute::Boolean,
String,
Symbol,
Rack::Multipart::UploadedFile
]
# Types representing data structures.
STRUCTURES = [
Hash,
Array,
Set
]
# @param type [Class] type to check
# @return [Boolean] whether or not the type is known by Grape as a valid
# type for a single value
def self.primitive?(type)
PRIMITIVES.include?(type)
end
# @param type [Class] type to check
# @return [Boolean] whether or not the type is known by Grape as a valid
# data structure type
# @note This method does not yet consider 'complex types', which inherit
# Virtus.model.
def self.structure?(type)
STRUCTURES.include?(type)
end
# A valid custom type must implement a class-level `parse` method, taking
# one String argument and returning the parsed value in its correct type.
# @param type [Class] type to check
# @return [Boolean] whether or not the type can be used as a custom type
def self.custom_type?(type)
!primitive?(type) &&
!structure?(type) &&
type.respond_to?(:parse) &&
type.method(:parse).arity == 1
end
end
end
grape-0.13.0/lib/grape/error_formatter/ 0000755 0000041 0000041 00000000000 12563420522 020002 5 ustar www-data www-data grape-0.13.0/lib/grape/error_formatter/xml.rb 0000644 0000041 0000041 00000001061 12563420522 021125 0 ustar www-data www-data module Grape
module ErrorFormatter
module Xml
class << self
def call(message, backtrace, options = {}, env = nil)
message = Grape::ErrorFormatter::Base.present(message, env)
result = message.is_a?(Hash) ? message : { message: message }
if (options[:rescue_options] || {})[:backtrace] && backtrace && !backtrace.empty?
result = result.merge(backtrace: backtrace)
end
result.respond_to?(:to_xml) ? result.to_xml(root: :error) : result.to_s
end
end
end
end
end
grape-0.13.0/lib/grape/error_formatter/json.rb 0000644 0000041 0000041 00000001001 12563420522 021270 0 ustar www-data www-data module Grape
module ErrorFormatter
module Json
class << self
def call(message, backtrace, options = {}, env = nil)
message = Grape::ErrorFormatter::Base.present(message, env)
result = message.is_a?(String) ? { error: message } : message
if (options[:rescue_options] || {})[:backtrace] && backtrace && !backtrace.empty?
result = result.merge(backtrace: backtrace)
end
MultiJson.dump(result)
end
end
end
end
end
grape-0.13.0/lib/grape/error_formatter/txt.rb 0000644 0000041 0000041 00000001007 12563420522 021144 0 ustar www-data www-data module Grape
module ErrorFormatter
module Txt
class << self
def call(message, backtrace, options = {}, env = nil)
message = Grape::ErrorFormatter::Base.present(message, env)
result = message.is_a?(Hash) ? MultiJson.dump(message) : message
if (options[:rescue_options] || {})[:backtrace] && backtrace && !backtrace.empty?
result += "\r\n "
result += backtrace.join("\r\n ")
end
result
end
end
end
end
end
grape-0.13.0/lib/grape/error_formatter/base.rb 0000644 0000041 0000041 00000003476 12563420522 021253 0 ustar www-data www-data module Grape
module ErrorFormatter
module Base
class << self
FORMATTERS = {
serializable_hash: Grape::ErrorFormatter::Json,
json: Grape::ErrorFormatter::Json,
jsonapi: Grape::ErrorFormatter::Json,
txt: Grape::ErrorFormatter::Txt,
xml: Grape::ErrorFormatter::Xml
}
def formatters(options)
FORMATTERS.merge(options[:error_formatters] || {})
end
def formatter_for(api_format, options = {})
spec = formatters(options)[api_format]
case spec
when nil
options[:default_error_formatter] || Grape::ErrorFormatter::Txt
when Symbol
method(spec)
else
spec
end
end
end
module_function
def present(message, env)
present_options = {}
present_options[:with] = message.delete(:with) if message.is_a?(Hash)
presenter = env['api.endpoint'].entity_class_for_obj(message, present_options)
unless presenter || env['rack.routing_args'].nil?
# env['api.endpoint'].route does not work when the error occurs within a middleware
# the Endpoint does not have a valid env at this moment
http_codes = env['rack.routing_args'][:route_info].route_http_codes || []
found_code = http_codes.find do |http_code|
(http_code[0].to_i == env['api.endpoint'].status) && http_code[2].respond_to?(:represent)
end if env['api.endpoint'].request
presenter = found_code[2] if found_code
end
if presenter
embeds = { env: env }
embeds[:version] = env['api.version'] if env['api.version']
message = presenter.represent(message, embeds).serializable_hash
end
message
end
end
end
end
grape-0.13.0/lib/grape/cookies.rb 0000644 0000041 0000041 00000001521 12563420522 016546 0 ustar www-data www-data module Grape
class Cookies
def initialize
@cookies = {}
@send_cookies = {}
end
def read(request)
request.cookies.each do |name, value|
@cookies[name.to_s] = value
end
end
def write(header)
@cookies.select { |key, _value| @send_cookies[key] == true }.each do |name, value|
cookie_value = value.is_a?(Hash) ? value : { value: value }
Rack::Utils.set_cookie_header! header, name, cookie_value
end
end
def [](name)
@cookies[name.to_s]
end
def []=(name, value)
@cookies[name.to_s] = value
@send_cookies[name.to_s] = true
end
def each(&block)
@cookies.each(&block)
end
def delete(name, opts = {})
options = opts.merge(value: 'deleted', expires: Time.at(0))
self.[]=(name, options)
end
end
end
grape-0.13.0/lib/grape/route.rb 0000644 0000041 0000041 00000001362 12563420522 016253 0 ustar www-data www-data module Grape
# A compiled route for inspection.
class Route
# @api private
def initialize(options = {})
@options = options || {}
end
# @api private
def method_missing(method_id, *arguments)
match = /route_([_a-zA-Z]\w*)/.match(method_id.to_s)
if match
@options[match.captures.last.to_sym]
else
super
end
end
# Generate a short, human-readable representation of this route.
def to_s
"version=#{route_version}, method=#{route_method}, path=#{route_path}"
end
private
# This is defined so that certain Ruby methods which attempt to call #to_ary
# on objects, e.g. Array#join, will not hit #method_missing.
def to_ary
nil
end
end
end
grape-0.13.0/lib/grape/locale/ 0000755 0000041 0000041 00000000000 12563420522 016025 5 ustar www-data www-data grape-0.13.0/lib/grape/locale/en.yml 0000644 0000041 0000041 00000004377 12563420522 017165 0 ustar www-data www-data en:
grape:
errors:
format: ! '%{attributes} %{message}'
messages:
coerce: 'is invalid'
presence: 'is missing'
regexp: 'is invalid'
blank: 'is empty'
values: 'does not have a valid value'
missing_vendor_option:
problem: 'missing :vendor option.'
summary: 'when version using header, you must specify :vendor option. '
resolution: "eg: version 'v1', using: :header, vendor: 'twitter'"
missing_mime_type:
problem: 'missing mime type for %{new_format}'
resolution:
"you can choose existing mime type from Grape::ContentTypes::CONTENT_TYPES
or add your own with content_type :%{new_format}, 'application/%{new_format}'
"
invalid_with_option_for_represent:
problem: 'You must specify an entity class in the :with option.'
resolution: 'eg: represent User, :with => Entity::User'
missing_option: 'You must specify :%{option} options.'
invalid_formatter: 'cannot convert %{klass} to %{to_format}'
invalid_versioner_option:
problem: 'Unknown :using for versioner: %{strategy}'
resolution: 'available strategy for :using is :path, :header, :param'
unknown_validator: 'unknown validator: %{validator_type}'
unknown_options: 'unknown options: %{options}'
unknown_parameter: 'unknown parameter: %{param}'
incompatible_option_values: '%{option1}: %{value1} is incompatible with %{option2}: %{value2}'
mutual_exclusion: 'are mutually exclusive'
at_least_one: 'are missing, at least one parameter must be provided'
exactly_one: 'are missing, exactly one parameter must be provided'
all_or_none: 'provide all or none of parameters'
missing_group_type: 'group type is required'
unsupported_group_type: 'group type must be Array or Hash'
invalid_message_body:
problem: "message body does not match declared format"
resolution:
"when specifying %{body_format} as content-type, you must pass valid
%{body_format} in the request's 'body'
"
invalid_accept_header:
problem: 'Invalid accept header'
resolution: '%{message}'
grape-0.13.0/lib/grape.rb 0000644 0000041 0000041 00000010256 12563420522 015117 0 ustar www-data www-data require 'logger'
require 'rack'
require 'rack/mount'
require 'rack/builder'
require 'rack/accept'
require 'rack/auth/basic'
require 'rack/auth/digest/md5'
require 'hashie'
require 'set'
require 'active_support/version'
require 'active_support/core_ext/hash/indifferent_access'
require 'active_support/ordered_hash'
require 'active_support/core_ext/object/conversions'
require 'active_support/core_ext/array/extract_options'
require 'active_support/core_ext/hash/deep_merge'
require 'active_support/core_ext/hash/except'
require 'active_support/dependencies/autoload'
require 'active_support/notifications'
require 'multi_json'
require 'multi_xml'
require 'virtus'
require 'i18n'
require 'thread'
I18n.load_path << File.expand_path('../grape/locale/en.yml', __FILE__)
module Grape
extend ActiveSupport::Autoload
eager_autoload do
autoload :API
autoload :Endpoint
autoload :Route
autoload :Namespace
autoload :Path
autoload :Cookies
autoload :Validations
autoload :Request, 'grape/http/request'
end
module Http
extend ActiveSupport::Autoload
eager_autoload do
autoload :Headers
end
end
module Exceptions
extend ActiveSupport::Autoload
autoload :Base
autoload :Validation
autoload :ValidationErrors
autoload :MissingVendorOption
autoload :MissingMimeType
autoload :MissingOption
autoload :InvalidFormatter
autoload :InvalidVersionerOption
autoload :UnknownValidator
autoload :UnknownOptions
autoload :UnknownParameter
autoload :InvalidWithOptionForRepresent
autoload :IncompatibleOptionValues
autoload :MissingGroupTypeError, 'grape/exceptions/missing_group_type'
autoload :UnsupportedGroupTypeError, 'grape/exceptions/unsupported_group_type'
autoload :InvalidMessageBody
autoload :InvalidAcceptHeader
end
module ErrorFormatter
extend ActiveSupport::Autoload
autoload :Base
autoload :Json
autoload :Txt
autoload :Xml
end
module Formatter
extend ActiveSupport::Autoload
autoload :Base
autoload :Json
autoload :SerializableHash
autoload :Txt
autoload :Xml
end
module Parser
extend ActiveSupport::Autoload
autoload :Base
autoload :Json
autoload :Xml
end
module Middleware
extend ActiveSupport::Autoload
autoload :Base
autoload :Versioner
autoload :Formatter
autoload :Error
autoload :Globals
module Auth
extend ActiveSupport::Autoload
autoload :Base
autoload :DSL
autoload :StrategyInfo
autoload :Strategies
end
module Versioner
extend ActiveSupport::Autoload
autoload :Path
autoload :Header
autoload :Param
autoload :AcceptVersionHeader
end
end
module Util
extend ActiveSupport::Autoload
autoload :InheritableValues
autoload :StackableValues
autoload :InheritableSetting
autoload :StrictHashConfiguration
autoload :FileResponse
end
module DSL
extend ActiveSupport::Autoload
eager_autoload do
autoload :API
autoload :Callbacks
autoload :Settings
autoload :Configuration
autoload :InsideRoute
autoload :Helpers
autoload :Middleware
autoload :Parameters
autoload :RequestResponse
autoload :Routing
autoload :Validations
end
end
class API
extend ActiveSupport::Autoload
autoload :Helpers
end
module Presenters
extend ActiveSupport::Autoload
autoload :Presenter
end
end
require 'grape/util/content_types'
require 'grape/util/parameter_types'
require 'grape/validations/validators/base'
require 'grape/validations/attributes_iterator'
require 'grape/validations/validators/allow_blank'
require 'grape/validations/validators/at_least_one_of'
require 'grape/validations/validators/coerce'
require 'grape/validations/validators/default'
require 'grape/validations/validators/exactly_one_of'
require 'grape/validations/validators/mutual_exclusion'
require 'grape/validations/validators/presence'
require 'grape/validations/validators/regexp'
require 'grape/validations/validators/values'
require 'grape/validations/params_scope'
require 'grape/validations/validators/all_or_none'
require 'grape/version'
grape-0.13.0/gemfiles/ 0000755 0000041 0000041 00000000000 12563420522 014515 5 ustar www-data www-data grape-0.13.0/gemfiles/rails_3.gemfile 0000644 0000041 0000041 00000000354 12563420522 017405 0 ustar www-data www-data # This file was generated by Appraisal
source 'https://rubygems.org'
gem 'rails', '3.2.19'
group :development, :test do
gem 'rubocop', '~> 0.31.0'
gem 'guard'
gem 'guard-rspec'
gem 'guard-rubocop'
end
gemspec :path => '../'
grape-0.13.0/gemfiles/rails_4.gemfile 0000644 0000041 0000041 00000000353 12563420522 017405 0 ustar www-data www-data # This file was generated by Appraisal
source 'https://rubygems.org'
gem 'rails', '4.1.6'
group :development, :test do
gem 'rubocop', '~> 0.31.0'
gem 'guard'
gem 'guard-rspec'
gem 'guard-rubocop'
end
gemspec :path => '../'
grape-0.13.0/.rubocop.yml 0000644 0000041 0000041 00000000152 12563420522 015172 0 ustar www-data www-data AllCops:
Exclude:
- vendor/**/*
- bin/**/*
- gemfiles/**/*
inherit_from: .rubocop_todo.yml
grape-0.13.0/metadata.yml 0000644 0000041 0000041 00000042550 12563420522 015233 0 ustar www-data www-data --- !ruby/object:Gem::Specification
name: grape
version: !ruby/object:Gem::Version
version: 0.13.0
platform: ruby
authors:
- Michael Bleigh
autorequire:
bindir: bin
cert_chain: []
date: 2015-08-10 00:00:00.000000000 Z
dependencies:
- !ruby/object:Gem::Dependency
name: rack
requirement: !ruby/object:Gem::Requirement
requirements:
- - '>='
- !ruby/object:Gem::Version
version: 1.3.0
type: :runtime
prerelease: false
version_requirements: !ruby/object:Gem::Requirement
requirements:
- - '>='
- !ruby/object:Gem::Version
version: 1.3.0
- !ruby/object:Gem::Dependency
name: rack-mount
requirement: !ruby/object:Gem::Requirement
requirements:
- - '>='
- !ruby/object:Gem::Version
version: '0'
type: :runtime
prerelease: false
version_requirements: !ruby/object:Gem::Requirement
requirements:
- - '>='
- !ruby/object:Gem::Version
version: '0'
- !ruby/object:Gem::Dependency
name: rack-accept
requirement: !ruby/object:Gem::Requirement
requirements:
- - '>='
- !ruby/object:Gem::Version
version: '0'
type: :runtime
prerelease: false
version_requirements: !ruby/object:Gem::Requirement
requirements:
- - '>='
- !ruby/object:Gem::Version
version: '0'
- !ruby/object:Gem::Dependency
name: activesupport
requirement: !ruby/object:Gem::Requirement
requirements:
- - '>='
- !ruby/object:Gem::Version
version: '0'
type: :runtime
prerelease: false
version_requirements: !ruby/object:Gem::Requirement
requirements:
- - '>='
- !ruby/object:Gem::Version
version: '0'
- !ruby/object:Gem::Dependency
name: multi_json
requirement: !ruby/object:Gem::Requirement
requirements:
- - '>='
- !ruby/object:Gem::Version
version: 1.3.2
type: :runtime
prerelease: false
version_requirements: !ruby/object:Gem::Requirement
requirements:
- - '>='
- !ruby/object:Gem::Version
version: 1.3.2
- !ruby/object:Gem::Dependency
name: multi_xml
requirement: !ruby/object:Gem::Requirement
requirements:
- - '>='
- !ruby/object:Gem::Version
version: 0.5.2
type: :runtime
prerelease: false
version_requirements: !ruby/object:Gem::Requirement
requirements:
- - '>='
- !ruby/object:Gem::Version
version: 0.5.2
- !ruby/object:Gem::Dependency
name: hashie
requirement: !ruby/object:Gem::Requirement
requirements:
- - '>='
- !ruby/object:Gem::Version
version: 2.1.0
type: :runtime
prerelease: false
version_requirements: !ruby/object:Gem::Requirement
requirements:
- - '>='
- !ruby/object:Gem::Version
version: 2.1.0
- !ruby/object:Gem::Dependency
name: virtus
requirement: !ruby/object:Gem::Requirement
requirements:
- - '>='
- !ruby/object:Gem::Version
version: 1.0.0
type: :runtime
prerelease: false
version_requirements: !ruby/object:Gem::Requirement
requirements:
- - '>='
- !ruby/object:Gem::Version
version: 1.0.0
- !ruby/object:Gem::Dependency
name: builder
requirement: !ruby/object:Gem::Requirement
requirements:
- - '>='
- !ruby/object:Gem::Version
version: '0'
type: :runtime
prerelease: false
version_requirements: !ruby/object:Gem::Requirement
requirements:
- - '>='
- !ruby/object:Gem::Version
version: '0'
- !ruby/object:Gem::Dependency
name: grape-entity
requirement: !ruby/object:Gem::Requirement
requirements:
- - '>='
- !ruby/object:Gem::Version
version: 0.4.4
type: :development
prerelease: false
version_requirements: !ruby/object:Gem::Requirement
requirements:
- - '>='
- !ruby/object:Gem::Version
version: 0.4.4
- !ruby/object:Gem::Dependency
name: rake
requirement: !ruby/object:Gem::Requirement
requirements:
- - '>='
- !ruby/object:Gem::Version
version: '0'
type: :development
prerelease: false
version_requirements: !ruby/object:Gem::Requirement
requirements:
- - '>='
- !ruby/object:Gem::Version
version: '0'
- !ruby/object:Gem::Dependency
name: maruku
requirement: !ruby/object:Gem::Requirement
requirements:
- - '>='
- !ruby/object:Gem::Version
version: '0'
type: :development
prerelease: false
version_requirements: !ruby/object:Gem::Requirement
requirements:
- - '>='
- !ruby/object:Gem::Version
version: '0'
- !ruby/object:Gem::Dependency
name: yard
requirement: !ruby/object:Gem::Requirement
requirements:
- - '>='
- !ruby/object:Gem::Version
version: '0'
type: :development
prerelease: false
version_requirements: !ruby/object:Gem::Requirement
requirements:
- - '>='
- !ruby/object:Gem::Version
version: '0'
- !ruby/object:Gem::Dependency
name: rack-test
requirement: !ruby/object:Gem::Requirement
requirements:
- - '>='
- !ruby/object:Gem::Version
version: '0'
type: :development
prerelease: false
version_requirements: !ruby/object:Gem::Requirement
requirements:
- - '>='
- !ruby/object:Gem::Version
version: '0'
- !ruby/object:Gem::Dependency
name: rspec
requirement: !ruby/object:Gem::Requirement
requirements:
- - ~>
- !ruby/object:Gem::Version
version: '3.0'
type: :development
prerelease: false
version_requirements: !ruby/object:Gem::Requirement
requirements:
- - ~>
- !ruby/object:Gem::Version
version: '3.0'
- !ruby/object:Gem::Dependency
name: bundler
requirement: !ruby/object:Gem::Requirement
requirements:
- - '>='
- !ruby/object:Gem::Version
version: '0'
type: :development
prerelease: false
version_requirements: !ruby/object:Gem::Requirement
requirements:
- - '>='
- !ruby/object:Gem::Version
version: '0'
- !ruby/object:Gem::Dependency
name: cookiejar
requirement: !ruby/object:Gem::Requirement
requirements:
- - '>='
- !ruby/object:Gem::Version
version: '0'
type: :development
prerelease: false
version_requirements: !ruby/object:Gem::Requirement
requirements:
- - '>='
- !ruby/object:Gem::Version
version: '0'
- !ruby/object:Gem::Dependency
name: rack-contrib
requirement: !ruby/object:Gem::Requirement
requirements:
- - '>='
- !ruby/object:Gem::Version
version: '0'
type: :development
prerelease: false
version_requirements: !ruby/object:Gem::Requirement
requirements:
- - '>='
- !ruby/object:Gem::Version
version: '0'
- !ruby/object:Gem::Dependency
name: mime-types
requirement: !ruby/object:Gem::Requirement
requirements:
- - '>='
- !ruby/object:Gem::Version
version: '0'
type: :development
prerelease: false
version_requirements: !ruby/object:Gem::Requirement
requirements:
- - '>='
- !ruby/object:Gem::Version
version: '0'
- !ruby/object:Gem::Dependency
name: appraisal
requirement: !ruby/object:Gem::Requirement
requirements:
- - '>='
- !ruby/object:Gem::Version
version: '0'
type: :development
prerelease: false
version_requirements: !ruby/object:Gem::Requirement
requirements:
- - '>='
- !ruby/object:Gem::Version
version: '0'
description: A Ruby framework for rapid API development with great conventions.
email:
- michael@intridea.com
executables: []
extensions: []
extra_rdoc_files: []
files:
- .gitignore
- .rspec
- .rubocop.yml
- .rubocop_todo.yml
- .travis.yml
- .yardopts
- Appraisals
- CHANGELOG.md
- CONTRIBUTING.md
- Gemfile
- Guardfile
- LICENSE
- README.md
- RELEASING.md
- Rakefile
- UPGRADING.md
- gemfiles/rails_3.gemfile
- gemfiles/rails_4.gemfile
- grape.gemspec
- grape.png
- lib/grape.rb
- lib/grape/api.rb
- lib/grape/api/helpers.rb
- lib/grape/cookies.rb
- lib/grape/dsl/api.rb
- lib/grape/dsl/callbacks.rb
- lib/grape/dsl/configuration.rb
- lib/grape/dsl/helpers.rb
- lib/grape/dsl/inside_route.rb
- lib/grape/dsl/middleware.rb
- lib/grape/dsl/parameters.rb
- lib/grape/dsl/request_response.rb
- lib/grape/dsl/routing.rb
- lib/grape/dsl/settings.rb
- lib/grape/dsl/validations.rb
- lib/grape/endpoint.rb
- lib/grape/error_formatter/base.rb
- lib/grape/error_formatter/json.rb
- lib/grape/error_formatter/txt.rb
- lib/grape/error_formatter/xml.rb
- lib/grape/exceptions/base.rb
- lib/grape/exceptions/incompatible_option_values.rb
- lib/grape/exceptions/invalid_accept_header.rb
- lib/grape/exceptions/invalid_formatter.rb
- lib/grape/exceptions/invalid_message_body.rb
- lib/grape/exceptions/invalid_versioner_option.rb
- lib/grape/exceptions/invalid_with_option_for_represent.rb
- lib/grape/exceptions/missing_group_type.rb
- lib/grape/exceptions/missing_mime_type.rb
- lib/grape/exceptions/missing_option.rb
- lib/grape/exceptions/missing_vendor_option.rb
- lib/grape/exceptions/unknown_options.rb
- lib/grape/exceptions/unknown_parameter.rb
- lib/grape/exceptions/unknown_validator.rb
- lib/grape/exceptions/unsupported_group_type.rb
- lib/grape/exceptions/validation.rb
- lib/grape/exceptions/validation_errors.rb
- lib/grape/formatter/base.rb
- lib/grape/formatter/json.rb
- lib/grape/formatter/serializable_hash.rb
- lib/grape/formatter/txt.rb
- lib/grape/formatter/xml.rb
- lib/grape/http/headers.rb
- lib/grape/http/request.rb
- lib/grape/locale/en.yml
- lib/grape/middleware/auth/base.rb
- lib/grape/middleware/auth/dsl.rb
- lib/grape/middleware/auth/strategies.rb
- lib/grape/middleware/auth/strategy_info.rb
- lib/grape/middleware/base.rb
- lib/grape/middleware/error.rb
- lib/grape/middleware/filter.rb
- lib/grape/middleware/formatter.rb
- lib/grape/middleware/globals.rb
- lib/grape/middleware/versioner.rb
- lib/grape/middleware/versioner/accept_version_header.rb
- lib/grape/middleware/versioner/header.rb
- lib/grape/middleware/versioner/param.rb
- lib/grape/middleware/versioner/path.rb
- lib/grape/namespace.rb
- lib/grape/parser/base.rb
- lib/grape/parser/json.rb
- lib/grape/parser/xml.rb
- lib/grape/path.rb
- lib/grape/presenters/presenter.rb
- lib/grape/route.rb
- lib/grape/util/content_types.rb
- lib/grape/util/file_response.rb
- lib/grape/util/inheritable_setting.rb
- lib/grape/util/inheritable_values.rb
- lib/grape/util/parameter_types.rb
- lib/grape/util/stackable_values.rb
- lib/grape/util/strict_hash_configuration.rb
- lib/grape/validations.rb
- lib/grape/validations/attributes_iterator.rb
- lib/grape/validations/params_scope.rb
- lib/grape/validations/validators/all_or_none.rb
- lib/grape/validations/validators/allow_blank.rb
- lib/grape/validations/validators/at_least_one_of.rb
- lib/grape/validations/validators/base.rb
- lib/grape/validations/validators/coerce.rb
- lib/grape/validations/validators/default.rb
- lib/grape/validations/validators/exactly_one_of.rb
- lib/grape/validations/validators/multiple_params_base.rb
- lib/grape/validations/validators/mutual_exclusion.rb
- lib/grape/validations/validators/presence.rb
- lib/grape/validations/validators/regexp.rb
- lib/grape/validations/validators/values.rb
- lib/grape/version.rb
- spec/grape/api/custom_validations_spec.rb
- spec/grape/api/deeply_included_options_spec.rb
- spec/grape/api/nested_helpers_spec.rb
- spec/grape/api/shared_helpers_spec.rb
- spec/grape/api_spec.rb
- spec/grape/dsl/callbacks_spec.rb
- spec/grape/dsl/configuration_spec.rb
- spec/grape/dsl/helpers_spec.rb
- spec/grape/dsl/inside_route_spec.rb
- spec/grape/dsl/middleware_spec.rb
- spec/grape/dsl/parameters_spec.rb
- spec/grape/dsl/request_response_spec.rb
- spec/grape/dsl/routing_spec.rb
- spec/grape/dsl/settings_spec.rb
- spec/grape/dsl/validations_spec.rb
- spec/grape/endpoint_spec.rb
- spec/grape/entity_spec.rb
- spec/grape/exceptions/body_parse_errors_spec.rb
- spec/grape/exceptions/invalid_accept_header_spec.rb
- spec/grape/exceptions/invalid_formatter_spec.rb
- spec/grape/exceptions/invalid_versioner_option_spec.rb
- spec/grape/exceptions/missing_mime_type_spec.rb
- spec/grape/exceptions/missing_option_spec.rb
- spec/grape/exceptions/unknown_options_spec.rb
- spec/grape/exceptions/unknown_validator_spec.rb
- spec/grape/exceptions/validation_errors_spec.rb
- spec/grape/integration/rack_spec.rb
- spec/grape/loading_spec.rb
- spec/grape/middleware/auth/base_spec.rb
- spec/grape/middleware/auth/dsl_spec.rb
- spec/grape/middleware/auth/strategies_spec.rb
- spec/grape/middleware/base_spec.rb
- spec/grape/middleware/error_spec.rb
- spec/grape/middleware/exception_spec.rb
- spec/grape/middleware/formatter_spec.rb
- spec/grape/middleware/globals_spec.rb
- spec/grape/middleware/versioner/accept_version_header_spec.rb
- spec/grape/middleware/versioner/header_spec.rb
- spec/grape/middleware/versioner/param_spec.rb
- spec/grape/middleware/versioner/path_spec.rb
- spec/grape/middleware/versioner_spec.rb
- spec/grape/path_spec.rb
- spec/grape/presenters/presenter_spec.rb
- spec/grape/util/inheritable_setting_spec.rb
- spec/grape/util/inheritable_values_spec.rb
- spec/grape/util/parameter_types_spec.rb
- spec/grape/util/stackable_values_spec.rb
- spec/grape/util/strict_hash_configuration_spec.rb
- spec/grape/validations/attributes_iterator_spec.rb
- spec/grape/validations/params_scope_spec.rb
- spec/grape/validations/validators/all_or_none_spec.rb
- spec/grape/validations/validators/allow_blank_spec.rb
- spec/grape/validations/validators/at_least_one_of_spec.rb
- spec/grape/validations/validators/coerce_spec.rb
- spec/grape/validations/validators/default_spec.rb
- spec/grape/validations/validators/exactly_one_of_spec.rb
- spec/grape/validations/validators/mutual_exclusion_spec.rb
- spec/grape/validations/validators/presence_spec.rb
- spec/grape/validations/validators/regexp_spec.rb
- spec/grape/validations/validators/values_spec.rb
- spec/grape/validations/validators/zh-CN.yml
- spec/grape/validations_spec.rb
- spec/shared/versioning_examples.rb
- spec/spec_helper.rb
- spec/support/basic_auth_encode_helpers.rb
- spec/support/content_type_helpers.rb
- spec/support/endpoint_faker.rb
- spec/support/file_streamer.rb
- spec/support/versioned_helpers.rb
homepage: https://github.com/ruby-grape/grape
licenses:
- MIT
metadata: {}
post_install_message:
rdoc_options: []
require_paths:
- lib
required_ruby_version: !ruby/object:Gem::Requirement
requirements:
- - '>='
- !ruby/object:Gem::Version
version: '0'
required_rubygems_version: !ruby/object:Gem::Requirement
requirements:
- - '>='
- !ruby/object:Gem::Version
version: '0'
requirements: []
rubyforge_project:
rubygems_version: 2.4.5
signing_key:
specification_version: 4
summary: A simple Ruby framework for building REST-like APIs.
test_files:
- spec/grape/api/custom_validations_spec.rb
- spec/grape/api/deeply_included_options_spec.rb
- spec/grape/api/nested_helpers_spec.rb
- spec/grape/api/shared_helpers_spec.rb
- spec/grape/api_spec.rb
- spec/grape/dsl/callbacks_spec.rb
- spec/grape/dsl/configuration_spec.rb
- spec/grape/dsl/helpers_spec.rb
- spec/grape/dsl/inside_route_spec.rb
- spec/grape/dsl/middleware_spec.rb
- spec/grape/dsl/parameters_spec.rb
- spec/grape/dsl/request_response_spec.rb
- spec/grape/dsl/routing_spec.rb
- spec/grape/dsl/settings_spec.rb
- spec/grape/dsl/validations_spec.rb
- spec/grape/endpoint_spec.rb
- spec/grape/entity_spec.rb
- spec/grape/exceptions/body_parse_errors_spec.rb
- spec/grape/exceptions/invalid_accept_header_spec.rb
- spec/grape/exceptions/invalid_formatter_spec.rb
- spec/grape/exceptions/invalid_versioner_option_spec.rb
- spec/grape/exceptions/missing_mime_type_spec.rb
- spec/grape/exceptions/missing_option_spec.rb
- spec/grape/exceptions/unknown_options_spec.rb
- spec/grape/exceptions/unknown_validator_spec.rb
- spec/grape/exceptions/validation_errors_spec.rb
- spec/grape/integration/rack_spec.rb
- spec/grape/loading_spec.rb
- spec/grape/middleware/auth/base_spec.rb
- spec/grape/middleware/auth/dsl_spec.rb
- spec/grape/middleware/auth/strategies_spec.rb
- spec/grape/middleware/base_spec.rb
- spec/grape/middleware/error_spec.rb
- spec/grape/middleware/exception_spec.rb
- spec/grape/middleware/formatter_spec.rb
- spec/grape/middleware/globals_spec.rb
- spec/grape/middleware/versioner/accept_version_header_spec.rb
- spec/grape/middleware/versioner/header_spec.rb
- spec/grape/middleware/versioner/param_spec.rb
- spec/grape/middleware/versioner/path_spec.rb
- spec/grape/middleware/versioner_spec.rb
- spec/grape/path_spec.rb
- spec/grape/presenters/presenter_spec.rb
- spec/grape/util/inheritable_setting_spec.rb
- spec/grape/util/inheritable_values_spec.rb
- spec/grape/util/parameter_types_spec.rb
- spec/grape/util/stackable_values_spec.rb
- spec/grape/util/strict_hash_configuration_spec.rb
- spec/grape/validations/attributes_iterator_spec.rb
- spec/grape/validations/params_scope_spec.rb
- spec/grape/validations/validators/all_or_none_spec.rb
- spec/grape/validations/validators/allow_blank_spec.rb
- spec/grape/validations/validators/at_least_one_of_spec.rb
- spec/grape/validations/validators/coerce_spec.rb
- spec/grape/validations/validators/default_spec.rb
- spec/grape/validations/validators/exactly_one_of_spec.rb
- spec/grape/validations/validators/mutual_exclusion_spec.rb
- spec/grape/validations/validators/presence_spec.rb
- spec/grape/validations/validators/regexp_spec.rb
- spec/grape/validations/validators/values_spec.rb
- spec/grape/validations/validators/zh-CN.yml
- spec/grape/validations_spec.rb
- spec/shared/versioning_examples.rb
- spec/spec_helper.rb
- spec/support/basic_auth_encode_helpers.rb
- spec/support/content_type_helpers.rb
- spec/support/endpoint_faker.rb
- spec/support/file_streamer.rb
- spec/support/versioned_helpers.rb
has_rdoc:
grape-0.13.0/.gitignore 0000644 0000041 0000041 00000000564 12563420522 014717 0 ustar www-data www-data ## MAC OS
.DS_Store
.com.apple.timemachine.supported
## TEXTMATE
*.tmproj
tmtags
## EMACS
*~
\#*
.\#*
## REDCAR
.redcar
## VIM
*.swp
*.swo
## RUBYMINE
.idea
## PROJECT::GENERAL
coverage
doc
pkg
.rvmrc
.bundle
.yardoc/*
dist
Gemfile.lock
gemfiles/*.lock
tmp
## Rubinius
.rbx
## Bundler binstubs
bin
## ripper-tags and gem-ctags
tags
## PROJECT::SPECIFIC
.project
grape-0.13.0/grape.png 0000644 0000041 0000041 00000010260 12563420522 014525 0 ustar www-data www-data ‰PNG
IHDR X { 1#˜l tEXtSoftware Adobe ImageReadyqÉe<