

葡萄是Ruby的休息状API框架。它旨在通过提供简单的DSL来轻松开发静止的API,在机架上运行或补充现有的Web应用程序框架,例如Rail和Sinatra。它具有内置的支持,包括多种格式,子域/前缀限制,内容谈判,版本控制等等。
您正在阅读下一个葡萄版本的文档,应为2.3.0。当前稳定版本为2.2.0。
作为Tidelift订阅的一部分可用。
葡萄的维护者正在与Tidelift合作提供商业支持和维护。节省时间,降低风险并改善代码健康,同时支付葡萄的维护者。单击此处以获取更多详细信息。
Ruby 2.7或需要更新。
葡萄作为宝石可用,以安装运行:
bundle add grape
Grape API是通过将Grape::API子级创建的机架应用程序。下面是一个简单的示例,显示了在重新创建Twitter API的部分中葡萄的一些更常见的特征。
module Twitter
class API < Grape :: API
version 'v1' , using : :header , vendor : 'twitter'
format :json
prefix :api
helpers do
def current_user
@current_user ||= User . authorize! ( env )
end
def authenticate!
error! ( '401 Unauthorized' , 401 ) unless current_user
end
end
resource :statuses do
desc 'Return a public timeline.'
get :public_timeline do
Status . limit ( 20 )
end
desc 'Return a personal timeline.'
get :home_timeline do
authenticate!
current_user . statuses . limit ( 20 )
end
desc 'Return a status.'
params do
requires :id , type : Integer , desc : 'Status ID.'
end
route_param :id do
get do
Status . find ( params [ :id ] )
end
end
desc 'Create a status.'
params do
requires :status , type : String , desc : 'Your status.'
end
post do
authenticate!
Status . create! ( {
user : current_user ,
text : params [ :status ]
} )
end
desc 'Update a status.'
params do
requires :id , type : String , desc : 'Status ID.'
requires :status , type : String , desc : 'Your status.'
end
put ':id' do
authenticate!
current_user . statuses . find ( params [ :id ] ) . update ( {
user : current_user ,
text : params [ :status ]
} )
end
desc 'Delete a status.'
params do
requires :id , type : String , desc : 'Status ID.'
end
delete ':id' do
authenticate!
current_user . statuses . find ( params [ :id ] ) . destroy
end
end
end
end 葡萄的弃用者将自动将其添加到您的应用程序的弃用器中,例如:grape ,以便将应用程序的配置应用于它。
默认情况下,葡萄会在第一个路线上编译路由,可以使用compile!方法。
Twitter :: API . compile!可以将其添加到您的config.ru (如果使用radup), application.rb (如果使用导轨)或任何加载服务器的文件。
上面的示例创建了一个机架应用程序,该应用程序可以从rackup config.ru文件中运行:
run Twitter :: API(带有预加载,您可以使用)
Twitter :: API . compile!
run Twitter :: API并会回应以下路线:
GET /api/statuses/public_timeline
GET /api/statuses/home_timeline
GET /api/statuses/:id
POST /api/statuses
PUT /api/statuses/:id
DELETE /api/statuses/:id
葡萄还将自动响应所有GET的头部和选项,以及所有其他路线的选择。
如果您想在Sinatra等另一个机架框架旁边安装葡萄,则可以使用Rack::Cascade轻松地进行:
# Example config.ru
require 'sinatra'
require 'grape'
class API < Grape :: API
get :hello do
{ hello : 'world' }
end
end
class Web < Sinatra :: Base
get '/' do
'Hello world.'
end
end
use Rack :: Session :: Cookie
run Rack :: Cascade . new [ Web , API ]请注意,使用Rack::Cascade重要的加载应用程序的顺序很重要。如果您想从葡萄中引起自定义404错误(例如error!('Not Found',404) ),则必须是最后的应用程序。如果葡萄应用程序不是最后一个,并且返回404或405响应,则级联将其用作信号来尝试下一个应用程序。这可能会导致不良行为显示错误的应用程序中错误的404页。
将API文件放入app/api中。 Rails期望与Ruby模块的名称和与类名称匹配的文件名匹配的子目录。在我们的示例中, Twitter::API的文件名位置和目录应为app/api/twitter/api.rb 。
修改config/routes :
mount Twitter :: API => '/' Rails的默认自动加载器是Zeitwerk 。默认情况下,它将api呈Api而不是API 。为了使我们的示例起作用,您需要在config/initializers/inflections.rb的底部取消分量,然后将API添加为缩写:
ActiveSupport :: Inflector . inflections ( :en ) do | inflect |
inflect . acronym 'API'
end您可以将多个API实现安装在另一个API实现中。这些不必是不同的版本,但可能是同一API的组成部分。
class Twitter :: API < Grape :: API
mount Twitter :: APIv1
mount Twitter :: APIv2
end您也可以安装在路径上,该路径类似于安装API本身内部的prefix 。
class Twitter :: API < Grape :: API
mount Twitter :: APIv1 => '/v1'
end可以在mount之前或之后进行宣言before/after/rescue_from 。无论如何,它们将被继承。
class Twitter :: API < Grape :: API
before do
header 'X-Base-Header' , 'will be defined for all APIs that are mounted below'
end
rescue_from :all do
error! ( { "error" => "Internal Server Error" } , 500 )
end
mount Twitter :: Users
mount Twitter :: Search
after do
clean_cache!
end
rescue_from ZeroDivisionError do
error! ( { "error" => "Not found" } , 404 )
end
end 您可以将相同的端点安装在两个不同的位置。
class Voting :: API < Grape :: API
namespace 'votes' do
get do
# Your logic
end
post do
# Your logic
end
end
end
class Post :: API < Grape :: API
mount Voting :: API
end
class Comment :: API < Grape :: API
mount Voting :: API
end假设帖子和评论端点已安装在/posts和/comments中,那么您现在应该能够get /posts/votes , post /posts/votes , get /comments/votes以及post /comments/votes 。
您可以配置可再生的端点,以根据安装位置来更改它们的行为方式。
class Voting :: API < Grape :: API
namespace 'votes' do
desc "Vote for your #{ configuration [ :votable ] } "
get do
# Your logic
end
end
end
class Post :: API < Grape :: API
mount Voting :: API , with : { votable : 'posts' }
end
class Comment :: API < Grape :: API
mount Voting :: API , with : { votable : 'comments' }
end请注意,如果您将哈希作为mount的第一个参数,则需要明确地将()围绕参数放置:
# good
mount ( { :: Some :: Api => '/some/api' } , with : { condition : true } )
# bad
mount :: Some :: Api => '/some/api' , with : { condition : true }您可以访问类上的configuration (用作动态属性),内部块(如名称空间)
如果您想在configuration上给出的逻辑,则可以使用given助手。
class ConditionalEndpoint :: API < Grape :: API
given configuration [ :some_setting ] do
get 'mount_this_endpoint_conditionally' do
configuration [ :configurable_response ]
end
end
end如果您希望每次安装端点时运行一大堆逻辑(可以在其中访问configuration哈希)
class ConditionalEndpoint :: API < Grape :: API
mounted do
YourLogger . info "This API was mounted at: #{ Time . now } "
get configuration [ :endpoint_name ] do
configuration [ :configurable_response ]
end
end
end可以通过使用mounted作为表达式来实现更复杂的结果,在该表达式中,在该表达式中,在该configuration中已经将配置评估为哈希。
class ExpressionEndpointAPI < Grape :: API
get ( mounted { configuration [ :route_name ] || 'default_name' } ) do
# some logic
end
end class BasicAPI < Grape :: API
desc 'Statuses index' do
params : ( configuration [ :entity ] || API :: Entities :: Status ) . documentation
end
params do
requires :all , using : ( configuration [ :entity ] || API :: Entities :: Status ) . documentation
end
get '/statuses' do
statuses = Status . all
type = current_user . admin? ? :full : :default
present statuses , with : ( configuration [ :entity ] || API :: Entities :: Status ) , type : type
end
end
class V1 < Grape :: API
version 'v1'
mount BasicAPI , with : { entity : mounted { configuration [ :entity ] || API :: Entities :: Status } }
end
class V2 < Grape :: API
version 'v2'
mount BasicAPI , with : { entity : mounted { configuration [ :entity ] || API :: Entities :: V2 :: Status } }
end 您可以选择通过为每个提供的版本建立单独的Grape::API类,然后将它们集成到主要Grape::API类中,从而提供API的各种版本。确保将较新的版本安装在较旧版本之前。如果找不到特定版本,则默认版本控制方法将请求引导到后续机架中间件。
require 'v1'
require 'v2'
require 'v3'
class App < Grape :: API
mount V3
mount V2
mount V1
end要维护早期API版本的相同端点而不重写它们,您可以在以前的API版本中指示多个版本。
class V1 < Grape :: API
version 'v1' , 'v2' , 'v3'
get '/foo' do
# your code for GET /foo
end
get '/other' do
# your code for GET /other
end
end
class V2 < Grape :: API
version 'v2' , 'v3'
get '/var' do
# your code for GET /var
end
end
class V3 < Grape :: API
version 'v3'
get '/foo' do
# your new code for GET /foo
end
end使用提供的示例,随后的端点将在各种版本中访问:
GET /v1/foo
GET /v1/other
GET /v2/foo # => Same behavior as v1
GET /v2/other # => Same behavior as v1
GET /v2/var # => New endpoint not available in v1
GET /v3/foo # => Different behavior to v1 and v2
GET /v3/other # => Same behavior as v1 and v2
GET /v3/var # => Same behavior as v2客户可以在四种策略中达到您的API端点:: :path , :header , :accept_version_header和:param 。默认策略是:path 。
version 'v1' , using : :path使用此版本策略,客户应在URL中传递所需版本。
curl http://localhost:9292/v1/statuses/public_timeline
version 'v1' , using : :header , vendor : 'twitter'当前,葡萄仅以以下格式支持版本的媒体类型:
vnd.vendor-and-or-resource-v1234+format
基本上, -和+之间的所有令牌都将被解释为版本。
使用此版本策略,客户应在HTTP Accept Head中传递所需版本。
curl -H Accept:application/vnd.twitter-v1+json http://localhost:9292/statuses/public_timeline
默认情况下,当不提供Accept标头时,使用第一个匹配版本。这种行为类似于导轨中的路由。为了避免这种默认行为,可以使用:strict选项。当此选项设置为true时,当不提供正确的Accept标头时,将返回406 Not Acceptable错误。
当提供无效的Accept标头时,如果设置为:cascade false返回406 Not Acceptable错误。否则,如果没有其他路由匹配,则架子将返回404 Not Found错误。
葡萄将评估接受标头中包含的相对质量偏好,并在省略时默认为1.0。在下面的示例中
curl -H "Accept: text/xml;q=0.8, application/json;q=0.9" localhost:1234/resource
version 'v1' , using : :accept_version_header使用此版本策略,客户应通过HTTP Accept-Version标头传递所需版本。
curl -H "Accept-Version:v1" http://localhost:9292/statuses/public_timeline
默认情况下,当不提供Accept-Version标头时,使用第一个匹配版本。这种行为类似于导轨中的路由。为了避免这种默认行为,可以使用:strict选项。当此选项设置为true时,当不提供正确的Accept标头时,将返回406 Not Acceptable错误,并且:cascade选项设置为false 。否则,如果没有其他路由匹配,则架子将返回404 Not Found错误。
version 'v1' , using : :param使用此版本策略,客户端应在URL查询字符串或请求主体中将所需版本作为请求参数传递。
curl http://localhost:9292/statuses/public_timeline?apiver=v1
查询参数的默认名称为“ apiver”,但可以使用:parameter选项指定。
version 'v1' , using : :param , parameter : 'v' curl http://localhost:9292/statuses/public_timeline?v=v1
您可以将描述添加到API方法和名称空间。该描述将由葡萄吹牛使用来生成符合宣传的文档。
注意:描述块仅用于文档,不会影响API行为。
desc 'Returns your public timeline.' do
summary 'summary'
detail 'more details'
params API :: Entities :: Status . documentation
success API :: Entities :: Entity
failure [ [ 401 , 'Unauthorized' , 'Entities::Error' ] ]
default { code : 500 , message : 'InvalidRequest' , model : Entities :: Error }
named 'My named route'
headers XAuthToken : {
description : 'Validates your identity' ,
required : true
} ,
XOptionalHeader : {
description : 'Not really needed' ,
required : false
}
hidden false
deprecated false
is_array true
nickname 'nickname'
produces [ 'application/json' ]
consumes [ 'application/json' ]
tags [ 'tag1' , 'tag2' ]
end
get :public_timeline do
Status . limit ( 20 )
enddetail :更加增强的描述params :直接从Entity定义参数success :(前实体)用于提出此路线成功响应的Entity 。failure :(以前的HTTP_CODES)使用过的故障HTTP代码和实体的定义。default :用于显示此路线的默认响应的定义和Entity 。named :一个助手,可以给路由一个名字,并在文档哈希中找到此名称headers :用过的标题的定义使用Grape.configure在加载时设置全局设置。当前可配置的设置为:
param_builder :设置参数构建器,默认为Grape::Extensions::ActiveSupport::HashWithIndifferentAccess::ParamBuilder 。要更改设置值,请确保在加载时间在某个时刻运行以下代码
Grape . configure do | config |
config . setting = value
end例如,对于param_builder ,以下代码可以在初始化器中运行:
Grape . configure do | config |
config . param_builder = Grape :: Extensions :: Hashie :: Mash :: ParamBuilder
end您还可以配置一个API:
API . configure do | config |
config [ key ] = value
end它将在API内部提供configuration ,就好像它是安装配置一样。
请求参数可通过params哈希对象获得。这包括GET , POST和PUT参数,以及您在路由字符串中指定的任何命名参数。
get :public_timeline do
Status . order ( params [ :sort_by ] )
end参数会自动从POST上的请求主体中填充,并PUT以进行表单输入,JSON和XML内容类型。
请求:
curl -d '{"text": "140 characters"}' 'http://localhost:9292/statuses' -H Content-Type:application/json -v
葡萄端点:
post '/statuses' do
Status . create! ( text : params [ :text ] )
end多部分帖子和看台也得到了支持。
请求:
curl --form image_file='@image.jpg;type=image/jpg' http://localhost:9292/upload
葡萄端点:
post 'upload' do
# file in params[:image_file]
end在任何一个之间发生冲突的情况下:
GET , POST并PUT参数POST上并PUT路由字符串参数将具有优先级。
默认参数可作为ActiveSupport::HashWithIndifferentAccess提供。可以将其更改为例如Ruby Hash或Hashie::Mash的整个API。
class API < Grape :: API
include Grape :: Extensions :: Hashie :: Mash :: ParamBuilder
params do
optional :color , type : String
end
get do
params . color # instead of params[:color]
end该类也可以使用build_with在单个参数块上覆盖,如下所示。
params do
build_with Grape :: Extensions :: Hash :: ParamBuilder
optional :color , type : String
end或全球添加配置Grape.configure.param_builder 。
在上面的示例中, params["color"]将返回nil因为params是普通的Hash 。
可用的参数构建器是Grape::Extensions::Hash::ParamBuilder , Grape::Extensions::ActiveSupport::HashWithIndifferentAccess::ParamBuilder and Grape and Grape::Extensions::Hashie::Mash::ParamBuilder 。
葡萄允许您仅访问由您的params块声明的参数。它将:
考虑以下API端点:
format :json
post 'users/signup' do
{ 'declared_params' => declared ( params ) }
end如果您没有指定任何参数, declared将返回一个空哈希。
要求
curl -X POST -H " Content-Type: application/json " localhost:9292/users/signup -d ' {"user": {"first_name":"first name", "last_name": "last name"}} '回复
{
"declared_params" : {}
}
一旦添加了参数要求,葡萄将仅返回声明的参数。
format :json
params do
optional :user , type : Hash do
optional :first_name , type : String
optional :last_name , type : String
end
end
post 'users/signup' do
{ 'declared_params' => declared ( params ) }
end要求
curl -X POST -H " Content-Type: application/json " localhost:9292/users/signup -d ' {"user": {"first_name":"first name", "last_name": "last name", "random": "never shown"}} '回复
{
"declared_params" : {
"user" : {
"first_name" : " first name " ,
"last_name" : " last name "
}
}
}将包括丢失为类型Hash或Array参数。
format :json
params do
optional :user , type : Hash do
optional :first_name , type : String
optional :last_name , type : String
end
optional :widgets , type : Array
end
post 'users/signup' do
{ 'declared_params' => declared ( params ) }
end要求
curl -X POST -H " Content-Type: application/json " localhost:9292/users/signup -d ' {} '回复
{
"declared_params" : {
"user" : {
"first_name" : null ,
"last_name" : null
},
"widgets" : []
}
}返回的哈希是ActiveSupport::HashWithIndifferentAccess 。
#declared方法在过滤器before不可用,因为在参数胁迫之前对这些方法进行了评估。
默认情况下, declared(params)包括在所有父名称空间中定义的参数。如果您只想从当前名称空间中返回参数,则可以将include_parent_namespaces选项设置为false 。
format :json
namespace :parent do
params do
requires :parent_name , type : String
end
namespace ':parent_name' do
params do
requires :child_name , type : String
end
get ':child_name' do
{
'without_parent_namespaces' => declared ( params , include_parent_namespaces : false ) ,
'with_parent_namespaces' => declared ( params , include_parent_namespaces : true ) ,
}
end
end
end要求
curl -X GET -H " Content-Type: application/json " localhost:9292/parent/foo/bar回复
{
"without_parent_namespaces" : {
"child_name" : " bar "
},
"with_parent_namespaces" : {
"parent_name" : " foo " ,
"child_name" : " bar "
},
}默认情况下, declared(params)包括具有nil值的参数。如果您只想返回未nil参数,则可以使用include_missing选项。默认情况下, include_missing设置为true 。考虑以下API:
format :json
params do
requires :user , type : Hash do
requires :first_name , type : String
optional :last_name , type : String
end
end
post 'users/signup' do
{ 'declared_params' => declared ( params , include_missing : false ) }
end要求
curl -X POST -H " Content-Type: application/json " localhost:9292/users/signup -d ' {"user": {"first_name":"first name", "random": "never shown"}} 'with include_missing:false
{
"declared_params" : {
"user" : {
"first_name" : " first name "
}
}
}with include_missing:true
{
"declared_params" : {
"user" : {
"first_name" : " first name " ,
"last_name" : null
}
}
}它也适用于嵌套的哈希:
format :json
params do
requires :user , type : Hash do
requires :first_name , type : String
optional :last_name , type : String
requires :address , type : Hash do
requires :city , type : String
optional :region , type : String
end
end
end
post 'users/signup' do
{ 'declared_params' => declared ( params , include_missing : false ) }
end要求
curl -X POST -H " Content-Type: application/json " localhost:9292/users/signup -d ' {"user": {"first_name":"first name", "random": "never shown", "address": { "city": "SF"}}} 'with include_missing:false
{
"declared_params" : {
"user" : {
"first_name" : " first name " ,
"address" : {
"city" : " SF "
}
}
}
}with include_missing:true
{
"declared_params" : {
"user" : {
"first_name" : " first name " ,
"last_name" : null ,
"address" : {
"city" : " Zurich " ,
"region" : null
}
}
}
}请注意,具有nil值的属性不会丢失,并且当include_missing设置为false时也将返回:
要求
curl -X POST -H " Content-Type: application/json " localhost:9292/users/signup -d ' {"user": {"first_name":"first name", "last_name": null, "address": { "city": "SF"}}} 'with include_missing:false
{
"declared_params" : {
"user" : {
"first_name" : " first name " ,
"last_name" : null ,
"address" : { "city" : " SF " }
}
}
}默认情况下, declared(params)将不会评估given并返回所有参数。使用evaluate_given评估所有given块,并仅返回given条件下满足的参数。考虑以下API:
format :json
params do
optional :child_id , type : Integer
given :child_id do
requires :father_id , type : Integer
end
end
post 'child' do
{ 'declared_params' => declared ( params , evaluate_given : true ) }
end要求
curl -X POST -H " Content-Type: application/json " localhost:9292/child -d ' {"father_id": 1} '响应estairuate_given:false
{
"declared_params" : {
"child_id" : null ,
"father_id" : 1
}
}响应estairuate_given:true
{
"declared_params" : {
"child_id" : null
}
}它也适用于嵌套的哈希:
format :json
params do
requires :child , type : Hash do
optional :child_id , type : Integer
given :child_id do
requires :father_id , type : Integer
end
end
end
post 'child' do
{ 'declared_params' => declared ( params , evaluate_given : true ) }
end要求
curl -X POST -H " Content-Type: application/json " localhost:9292/child -d ' {"child": {"father_id": 1}} '响应estairuate_given:false
{
"declared_params" : {
"child" : {
"child_id" : null ,
"father_id" : 1
}
}
}响应estairuate_given:true
{
"declared_params" : {
"child" : {
"child_id" : null
}
}
}使用route_param比定义相同名称的常规参数具有更高的优先级:
params do
requires :foo , type : String
end
route_param :foo do
get do
{ value : params [ :foo ] }
end
end要求
curl -X POST -H " Content-Type: application/json " localhost:9292/bar -d ' {"foo": "baz"} '回复
{
"value" : " bar "
}您可以使用params块为参数定义验证和胁迫选项。
params do
requires :id , type : Integer
optional :text , type : String , regexp : / A [a-z]+ z /
group :media , type : Hash do
requires :url
end
optional :audio , type : Hash do
requires :format , type : Symbol , values : [ :mp3 , :wav , :aac , :ogg ] , default : :mp3
end
mutually_exclusive :media , :audio
end
put ':id' do
# params[:id] is an Integer
end指定类型后,将在强制后完成隐式验证,以确保输出类型是声明的类型。
可选参数可以具有默认值。
params do
optional :color , type : String , default : 'blue'
optional :random_number , type : Integer , default : -> { Random . rand ( 1 .. 100 ) }
optional :non_random_number , type : Integer , default : Random . rand ( 1 .. 100 )
end热切评估默认值。上图:non_random_number将对每个params的端点进行评估。要使默认情况下,每个请求都会懒惰地使用lambda,例如:random_number 。
请注意,默认值将传递到指定的任何验证选项。如果没有明确提供:color以下示例将始终失败。
params do
optional :color , type : String , default : 'blue' , values : [ 'red' , 'green' ]
end正确的实现是确保默认值通过所有验证。
params do
optional :color , type : String , default : 'blue' , values : [ 'blue' , 'red' , 'green' ]
end您可以将一个参数的值用作其他参数的默认值。在这种情况下,如果未提供primary_color参数,则其值与color相同。如果他们两个都没有提供,则两个都将具有blue价值。
params do
optional :color , type : String , default : 'blue'
optional :primary_color , type : String , default : -> ( params ) { params [ :color ] }
end以下都是有效类型,由葡萄支撑在开箱即用:
File )请注意,Ruby 2.4和更早版本之间的行为不同。在Ruby 2.4中,由数字组成的值转换为整数,但在早期版本中,它将被视为fixnum。
params do
requires :integers , type : Hash do
requires :int , coerce : Integer
end
end
get '/int' do
params [ :integers ] [ :int ] . class
end
. ..
get '/int' integers : { int : '45' }
#=> Integer in ruby 2.4
#=> Fixnum in earlier ruby versions除了上面列出的默认类型类型外,只要提供明确的胁迫方法,任何类都可以用作类型。如果该类型实现了类级parse方法,则葡萄将自动使用它。此方法必须采用一个字符串参数并返回正确类型的实例,或返回Grape::Types::InvalidValue的实例,该实例可选地接受响应中要返回的消息。
class Color
attr_reader :value
def initialize ( color )
@value = color
end
def self . parse ( value )
return new ( value ) if %w[ blue red green ] . include? ( value )
Grape :: Types :: InvalidValue . new ( 'Unsupported color' )
end
end
params do
requires :color , type : Color , default : Color . new ( 'blue' )
requires :more_colors , type : Array [ Color ] # Collections work
optional :unique_colors , type : Set [ Color ] # Duplicates discarded
end
get '/stuff' do
# params[:color] is already a Color.
params [ :color ] . value
end另外,可以使用coerce_with提供自定义胁迫方法。可以按照优先顺序给出任何类或对象,以实现parse或call方法。该方法必须接受单个字符串参数,并且返回值必须与给定type匹配。
params do
requires :passwd , type : String , coerce_with : Base64 . method ( :decode64 )
requires :loud_color , type : Color , coerce_with : -> ( c ) { Color . parse ( c . downcase ) }
requires :obj , type : Hash , coerce_with : JSON do
requires :words , type : Array [ String ] , coerce_with : -> ( val ) { val . split ( / s +/ ) }
optional :time , type : Time , coerce_with : Chronic
end
end请注意, nil值将调用自定义胁迫方法,而丢失的参数则不会。
使用lambda(也可以使用带有parse方法的类)使用coerce_with的示例,它将解析一个字符串并返回整数数组,与Array[Integer] type匹配。
params do
requires :values , type : Array [ Integer ] , coerce_with : -> ( val ) { val . split ( / s +/ ) . map ( & :to_i ) }
end葡萄会断言,强制值与给定type匹配,如果不这样做,则会拒绝请求。为了覆盖这种行为,自定义类型可以实现parsed?如果值通过类型验证,则应接受单个参数并返回true方法。
class SecureUri
def self . parse ( value )
URI . parse value
end
def self . parsed? ( value )
value . is_a? URI :: HTTPS
end
end
params do
requires :secure_uri , type : SecureUri
end葡萄利用Rack::Request对多部分文件参数的内置支持。可以使用type: File :
params do
requires :avatar , type : File
end
post '/' do
params [ :avatar ] [ :filename ] # => 'avatar.png'
params [ :avatar ] [ :type ] # => 'image/png'
params [ :avatar ] [ :tempfile ] # => #<File>
endJSON类型使用特殊type: JSON声明,葡萄支持以JSON形式的字符串给出的复杂参数。 JSON对象和对象的数组被平等地接受,在任何一种情况下,都将嵌套验证规则应用于所有对象:
params do
requires :json , type : JSON do
requires :int , type : Integer , values : [ 1 , 2 , 3 ]
end
end
get '/' do
params [ :json ] . inspect
end
client . get ( '/' , json : '{"int":1}' ) # => "{:int=>1}"
client . get ( '/' , json : '[{"int":"1"}]' ) # => "[{:int=>1}]"
client . get ( '/' , json : '{"int":4}' ) # => HTTP 400
client . get ( '/' , json : '[{"int":4}]' ) # => HTTP 400另外type: Array[JSON] ,该数组将参数明确标记为对象数组。如果提供了一个对象,它将被包装。
params do
requires :json , type : Array [ JSON ] do
requires :int , type : Integer
end
end
get '/' do
params [ :json ] . each { | obj | ... } # always works
end对于可能提供的JSON结构类型的更严格的控制,请使用type: Array, coerce_with: JSON或type: Hash, coerce_with: JSON 。
可以使用types选项而不是type来声明变体类型参数:
params do
requires :status_code , types : [ Integer , String , Array [ Integer , String ] ]
end
get '/' do
params [ :status_code ] . inspect
end
client . get ( '/' , status_code : 'OK_GOOD' ) # => "OK_GOOD"
client . get ( '/' , status_code : 300 ) # => 300
client . get ( '/' , status_code : %w( 404 NOT FOUND ) ) # => [404, "NOT", "FOUND"]作为一种特殊情况,可以通过传递多个成员以上的成员来type的Set或Array来宣布变体式式收集:
params do
requires :status_codes , type : Array [ Integer , String ]
end
get '/' do
params [ :status_codes ] . inspect
end
client . get ( '/' , status_codes : %w( 1 two ) ) # => [1, "two"]参数可以使用group嵌套,也可以通过调用requires或optional块。在上面的示例中,这意味着params[:media][:url]以及params[:id]和params[:audio][:format]仅在存在params[:audio]时才需要。使用块, group , requires和optional可以接受其他选项type ,该选项类型可以是Array或Hash ,并且默认为Array 。根据值的不同,嵌套参数将被视为哈希的值或数组中的哈希值。
params do
optional :preferences , type : Array do
requires :key
requires :value
end
requires :name , type : Hash do
requires :first_name
requires :last_name
end
end假设您的某些参数仅在给出其他参数时才相关;葡萄使您可以通过参数块中的given方法表达这种关系,例如:
params do
optional :shelf_id , type : Integer
given :shelf_id do
requires :bin_id , type : Integer
end
end在上面的示例中,葡萄会使用blank?检查是否存在shelf_id参数。
given还使用自定义代码的Proc 。下面,仅当category的值foo时,才需要参数description :
params do
optional :category
given category : -> ( val ) { val == 'foo' } do
requires :description
end
end您可以重命名参数:
params do
optional :category , as : :type
given type : -> ( val ) { val == 'foo' } do
requires :description
end
end注意: given参数应为重命名。在示例中,它应该是type ,而不是category 。
可以将参数选项分组。如果要为多个参数提取常见验证或类型,则可能有用。在这些组中,各个参数可以扩展或选择性地覆盖共同的设置,从而使您可以在小组级别维护默认设置,同时在必要时仍在应用特定于参数的规则。
下面的示例显示了参数共享共同选项时的典型情况。
params do
requires :first_name , type : String , regexp : /w+/ , desc : 'First name' , documentation : { in : 'body' }
optional :middle_name , type : String , regexp : /w+/ , desc : 'Middle name' , documentation : { in : 'body' , x : { nullable : true } }
requires :last_name , type : String , regexp : /w+/ , desc : 'Last name' , documentation : { in : 'body' }
end葡萄使您可以通过参数块中的with呈现相同的逻辑,例如:
params do
with ( type : String , regexp : /w+/ , documentation : { in : 'body' } ) do
requires :first_name , desc : 'First name'
optional :middle_name , desc : 'Middle name' , documentation : { x : { nullable : true } }
requires :last_name , desc : 'Last name'
end
end您可以使用嵌套的“ with'块将设置整体组织为层。每个层都可以使用,添加或更改其上方图层的设置。这有助于保持复杂的参数组织和一致,同时仍允许进行特定的自定义。
params do
with ( documentation : { in : 'body' } ) do # Applies documentation to all nested parameters
with ( type : String , regexp : / w +/ ) do # Applies type and validation to names
requires :first_name , desc : 'First name'
requires :last_name , desc : 'Last name'
end
optional :age , type : Integer , desc : 'Age' , documentation : { x : { nullable : true } } # Specific settings for 'age'
end
end您可以使用as重命名参数,当重构现有API时,这很有用:
resource :users do
params do
requires :email_address , as : :email
requires :password
end
post do
User . create! ( declared ( params ) ) # User takes email and password
end
end呼叫declared(params)时将传递的值将as关键。
allow_blank参数可以定义为allow_blank ,以确保它们包含一个值。默认情况下,只requires验证是否在请求中发送了一个参数,无论其值如何。使用allow_blank: false ,空值或空格值仅无效。
allow_blank可以与requires和optional 。如果需要参数,则必须包含一个值。如果是可选的,则可以不在请求中发送它,但是如果它发送,则必须具有一定的值,而不是一个空的字符串/只有空格。
params do
requires :username , allow_blank : false
optional :first_name , allow_blank : false
end values参数可以仅限于具有:values选项的特定值集。
params do
requires :status , type : Symbol , values : [ :not_started , :processing , :done ]
optional :numbers , type : Array [ Integer ] , default : 1 , values : [ 1 , 2 , 3 , 5 , 8 ]
end向:values选项提供一个范围可确保该范围内包含参数(或参数)(使用Range#include? )。
params do
requires :latitude , type : Float , values : - 90.0 ..+ 90.0
requires :longitude , type : Float , values : - 180.0 ..+ 180.0
optional :letters , type : Array [ String ] , values : 'a' .. 'z'
end请注意,无尽的范围还用ActiveSupport> = 6.0支持,但它们要求提供类型。
params do
requires :minimum , type : Integer , values : 10 ..
optional :maximum , type : Integer , values : .. 10
end请注意,两个范围端点都必须是#kind_of?您的:type选项(如果您不提供:type选项,它将被猜测为等于范围的第一个端点的类)。因此以下是无效的:
params do
requires :invalid1 , type : Float , values : 0 .. 10 # 0.kind_of?(Float) => false
optional :invalid2 , values : 0 .. 10.0 # 10.0.kind_of?(0.class) => false
end :values选项也可以与Proc一起提供,并通过每个请求懒惰地评估。如果PROC具有零(即不需要参数),则应返回列表或范围,然后将其用于验证参数。
例如,给定状态模型,您可能需要通过先前在HashTag模型中定义的主题标签来限制。
params do
requires :hashtag , type : String , values : -> { Hashtag . all . map ( & :tag ) }
end另外,可以使用具有ARITY ONE的PROC(即一个参数)来明确验证每个参数值。在这种情况下,如果参数值有效,则预期PROC将返回真实值。如果PROC返回虚假值或升级标准词,则该参数将被视为无效。
params do
requires :number , type : Integer , values : -> ( v ) { v . even? && v < 25 }
end尽管Procs在单个情况下方便,但在使用验证不止一次的情况下,请考虑使用自定义验证器。
请注意,允许_blank验证器在使用时应用:values 。在下面的示例中,没有以下内容:allow_blank不会阻止:state接收空白值是因为:allow_blank默认为true 。
params do
requires :state , type : Symbol , values : [ :active , :inactive ]
end except_values参数可以限制在具有:except_values操作的特定值集中。
except_values验证器的行为与values验证器相似,因为它接受数组,范围或proc。但是,与values验证器不同, except_values仅接受零零的procs。
params do
requires :browser , except_values : [ 'ie6' , 'ie7' , 'ie8' ]
requires :port , except_values : { value : 0 .. 1024 , message : 'is not allowed' }
requires :hashtag , except_values : -> { Hashtag . FORBIDDEN_LIST }
end same_as可以给出一个same_as选项,以确保参数值匹配。
params do
requires :password
requires :password_confirmation , same_as : :password
end length具有支持#length方法的类型的参数可以限制为具有:length选项的特定长度。
验证器接受:min或:max或两个选项或仅两个选项:is验证参数的值是否在给定限制范围内。
params do
requires :code , type : String , length : { is : 2 }
requires :str , type : String , length : { min : 3 }
requires :list , type : [ Integer ] , length : { min : 3 , max : 5 }
requires :hash , type : Hash , length : { max : 5 }
end regexp参数可以限制以将特定的正则表达式与:regexp选项匹配。如果该值与正则表达式不匹配,则将返回错误。请注意,对于requires和optional参数都是正确的。
params do
requires :email , regexp : /.+@.+/
end如果参数是没有值的,则验证器将通过。为了确保参数包含一个值,请使用allow_blank: false 。
params do
requires :email , allow_blank : false , regexp : /.+@.+/
end mutually_exclusive参数可以定义为mutually_exclusive ,以确保其在请求中不同时存在。
params do
optional :beer
optional :wine
mutually_exclusive :beer , :wine
end可以定义多个集:
params do
optional :beer
optional :wine
mutually_exclusive :beer , :wine
optional :scotch
optional :aquavit
mutually_exclusive :scotch , :aquavit
end警告:切勿使用任何必需的参数定义互斥集。两个相互排斥的参数将意味着参数永远不会有效,从而使终点无用。带有可选参数的必需参数互斥将意味着后者永远不会有效。
exactly_one_of参数可以定义为“恰好_one_of”,以确保选择一个参数。
params do
optional :beer
optional :wine
exactly_one_of :beer , :wine
end请注意,使用:default使用mutually_exclusive的默认值将导致多个参数始终具有默认值并引起Grape::Exceptions::Validation互斥异常。
at_least_one_of参数可以定义为“ at_least_one_of”,以确保至少选择一个参数。
params do
optional :beer
optional :wine
optional :juice
at_least_one_of :beer , :wine , :juice
end all_or_none_of参数可以定义为“ all_or_none_of”,以确保选择所有参数。
params do
optional :beer
optional :wine
optional :juice
all_or_none_of :beer , :wine , :juice
end mutually_exclusive , exactly_one_of , at_least_one_of , all_or_none_of所有这些方法都可以在任何嵌套级别使用。
params do
requires :food , type : Hash do
optional :meat
optional :fish
optional :rice
at_least_one_of :meat , :fish , :rice
end
group :drink , type : Hash do
optional :beer
optional :wine
optional :juice
exactly_one_of :beer , :wine , :juice
end
optional :dessert , type : Hash do
optional :cake
optional :icecream
mutually_exclusive :cake , :icecream
end
optional :recipe , type : Hash do
optional :oil
optional :meat
all_or_none_of :oil , :meat
end
end名称空间允许参数定义,并应用于名称空间中的每个方法。
namespace :statuses do
params do
requires :user_id , type : Integer , desc : 'A user ID.'
end
namespace ':user_id' do
desc "Retrieve a user's status."
params do
requires :status_id , type : Integer , desc : 'A status ID.'
end
get ':status_id' do
User . find ( params [ :user_id ] ) . statuses . find ( params [ :status_id ] )
end
end
end namespace方法具有许多别名,包括: group , resource , resources和segment 。使用最适合您的API的内容。
您可以使用route_param方便地将路由参数定义为命名空间。
namespace :statuses do
route_param :id do
desc 'Returns all replies for a status.'
get 'replies' do
Status . find ( params [ :id ] ) . replies
end
desc 'Returns a status.'
get do
Status . find ( params [ :id ] )
end
end
end您还可以通过传递到route_param的选项来定义路由参数类型。
namespace :arithmetic do
route_param :n , type : Integer do
desc 'Returns in power'
get 'power' do
params [ :n ] ** params [ :n ]
end
end
end class AlphaNumeric < Grape :: Validations :: Validators :: Base
def validate_param! ( attr_name , params )
unless params [ attr_name ] =~ / A [[:alnum:]]+ z /
raise Grape :: Exceptions :: Validation . new params : [ @scope . full_name ( attr_name ) ] , message : 'must consist of alpha-numeric characters'
end
end
end params do
requires :text , alpha_numeric : true
end您还可以创建使用参数的自定义类。
class Length < Grape :: Validations :: Validators :: Base
def validate_param! ( attr_name , params )
unless params [ attr_name ] . length <= @option
raise Grape :: Exceptions :: Validation . new params : [ @scope . full_name ( attr_name ) ] , message : "must be at the most #{ @option } characters long"
end
end
end params do
requires :text , length : 140
end您还可以创建使用请求来验证属性的自定义验证。例如,如果您想拥有仅适用于管理员的参数,则可以执行以下操作。
class Admin < Grape :: Validations :: Validators :: Base
def validate ( request )
# return if the param we are checking was not in request
# @attrs is a list containing the attribute we are currently validating
# in our sample case this method once will get called with
# @attrs being [:admin_field] and once with @attrs being [:admin_false_field]
return unless request . params . key? ( @attrs . first )
# check if admin flag is set to true
return unless @option
# check if user is admin or not
# as an example get a token from request and check if it's admin or not
raise Grape :: Exceptions :: Validation . new params : @attrs , message : 'Can not set admin-only field.' unless request . headers [ 'X-Access-Token' ] == 'admin'
end
end并在您的端点定义中使用它为:
params do
optional :admin_field , type : String , admin : true
optional :non_admin_field , type : String
optional :admin_false_field , type : String , admin : false
end每个验证都将拥有自己的验证器实例,这意味着验证器可以具有状态。
收集了验证和胁迫错误,并提出了Grape::Exceptions::ValidationErrors 。如果异常被杀死,它将以400个状态和错误消息响应。验证错误按参数名称分组,可以通过Grape::Exceptions::ValidationErrors#errors访问。
在下面的示例中,来自Grape::Exceptions::ValidationErrors是人类可读的弦,例如“啤酒,葡萄酒相互排斥”。
params do
optional :beer
optional :wine
optional :juice
exactly_one_of :beer , :wine , :juice
end您可以挽救Grape::Exceptions::ValidationErrors ,并以自定义响应响应,或将响应转换为将单个参数和相应错误消息分开的JSON API的良好的JSON。以下rescue_from示例产生[{"params":["beer","wine"],"messages":["are mutually exclusive"]}] 。
format :json
subject . rescue_from Grape :: Exceptions :: ValidationErrors do | e |
error! e , 400
end Grape::Exceptions::ValidationErrors#full_messages将验证消息返回作为数组。 Grape::Exceptions::ValidationErrors#message将消息连接到一个字符串。
对于响应一系列验证消息,您可以使用Grape::Exceptions::ValidationErrors#full_messages 。
format :json
subject . rescue_from Grape :: Exceptions :: ValidationErrors do | e |
error! ( { messages : e . full_messages } , 400 )
end葡萄返回默认情况下发现的所有验证和强制错误。要跳过所有后续验证检查当发现特定参数无效时,请使用fail_fast: true 。
下面的示例将无法检查是否存在:wine除非找到:beer 。
params do
required :beer , fail_fast : true
required :wine
end空参数的结果将是单个Grape::Exceptions::ValidationErrors错误。
同样,如果以下示例中:blah为空白,则不会执行正则表达测试。
params do
required :blah , allow_blank : false , regexp : /blah/ , fail_fast : true
end葡萄支持与参数相关的错误消息的I18N,但是如果未提供默认场所的翻译,则将退缩到英语。有关消息键,请参见En.yml。
如果您的应用程序仅执行可用的环境,并且:EN不包含在您的可用语言环境中,则葡萄不能返回英语,并且会返回转换键以获取错误消息。为了避免这种行为,要么为您的默认场所提供翻译,要么在可用的地区添加:en。
葡萄支持与参数相关和与强制相关的错误消息的自定义验证消息。
presence , allow_blank , values , regexp params do
requires :name , values : { value : 1 .. 10 , message : 'not in range from 1 to 10' } , allow_blank : { value : false , message : 'cannot be blank' } , regexp : { value : /^[a-z]+$/ , message : 'format is invalid' } , message : 'is required'
end same_as params do
requires :password
requires :password_confirmation , same_as : { value : :password , message : 'not match' }
end length params do
requires :code , type : String , length : { is : 2 , message : 'code is expected to be exactly 2 characters long' }
requires :str , type : String , length : { min : 5 , message : 'str is expected to be atleast 5 characters long' }
requires :list , type : [ Integer ] , length : { min : 2 , max : 3 , message : 'list is expected to have between 2 and 3 elements' }
end all_or_none_of params do
optional :beer
optional :wine
optional :juice
all_or_none_of :beer , :wine , :juice , message : "all params are required or none is required"
end mutually_exclusive params do
optional :beer
optional :wine
optional :juice
mutually_exclusive :beer , :wine , :juice , message : "are mutually exclusive cannot pass both params"
end exactly_one_of params do
optional :beer
optional :wine
optional :juice
exactly_one_of :beer , :wine , :juice , message : { exactly_one : "are missing, exactly one parameter is required" , mutual_exclusion : "are mutually exclusive, exactly one parameter is required" }
end at_least_one_of params do
optional :beer
optional :wine
optional :juice
at_least_one_of :beer , :wine , :juice , message : "are missing, please specify at least one param"
end Coerce params do
requires :int , type : { value : Integer , message : "type cast is invalid" }
end With Lambdas params do
requires :name , values : { value : -> { ( 1 .. 10 ) . to_a } , message : 'not in range from 1 to 10' }
end Pass symbols for i18n translations如果您想要自定义验证消息的I18N翻译,则可以传递符号。
params do
requires :name , message : :name_required
end # en.yml
en :
grape :
errors :
format : ! '%{attributes} %{message}'
messages :
name_required : 'must be present' 您还可以覆盖属性名称。
# en.yml
en :
grape :
errors :
format : ! '%{attributes} %{message}'
messages :
name_required : 'must be present'
attributes :
name : 'Oops! Name'会产生'哎呀!名称必须在场'
您无法为默认值设置自定义消息选项,因为它需要Interpolation %{option1}: %{value1} is incompatible with %{option2}: %{value2} 。您可以通过更改en.yml内部的incompatible_option_values消息键来更改默认错误消息的默认错误消息
params do
requires :name , values : { value : -> { ( 1 .. 10 ) . to_a } , message : 'not in range from 1 to 10' } , default : 5
enddry-validation或dry-schema作为上述params DSL的替代方法,您可以使用模式或dry-validation合同来描述端点的参数。如果您已经在应用程序的其他某些部分中使用了上述内容,这可能特别有用。如果没有,您需要在您的Gemfile中添加dry-validation或dry-schema 。
然后使用以前定义的合同或模式致电contract :
CreateOrdersSchema = Dry :: Schema . Params do
required ( :orders ) . array ( :hash ) do
required ( :name ) . filled ( :string )
optional ( :volume ) . maybe ( :integer , lt? : 9 )
end
end
# ...
contract CreateOrdersSchema或使用块,使用架构定义语法:
contract do
required ( :orders ) . array ( :hash ) do
required ( :name ) . filled ( :string )
optional ( :volume ) . maybe ( :integer , lt? : 9 )
end
end后者将定义一个胁迫架构( Dry::Schema.Params )。使用以前的方法时,由您决定输入是否需要胁迫。
params contract也可以在同一API中一起使用,例如描述端点嵌套名称空间的不同部分。
请求标题可通过headers助手或env以其原始形式提供。
get do
error! ( 'Unauthorized' , 401 ) unless headers [ 'Secret-Password' ] == 'swordfish'
end get do
error! ( 'Unauthorized' , 401 ) unless env [ 'HTTP_SECRET_PASSWORD' ] == 'swordfish'
end 上面的示例可能已被要求如下:
curl -H " secret_PassWord: swordfish " ...标题名称将为您标准化。
header辅助名称中,将被胁迫为secret-password 。header辅助名称中,将被强制为大写的烤肉箱案件作为Secret-PassWord 。env集合中,它们出现在所有大写,在蛇案中,并以HTTP_SECRET_PASSWORD为前缀'http_'根据RFC2616第4.2节中定义的HTTP标准,标题名称将被归一化,无论客户端发送了什么。
您可以在API中设置带有header的响应标头。
header 'X-Robots-Tag' , 'noindex'提出error! ,通过其他标题作为参数。在error!称呼。
error! 'Unauthorized' , 401 , 'X-Error-Detail' => 'Invalid token.' 要定义路由,您可以将route方法或速记用于HTTP动词。定义接受任何设置为:any路由的路由。用结肠表示的路径的一部分将被解释为路由参数。
route :get , 'status' do
end
# is the same as
get 'status' do
end
# is the same as
get :status do
end
# is NOT the same as
get ':status' do # this makes params[:status] available
end
# This will make both params[:status_id] and params[:id] available
get 'statuses/:status_id/reviews/:id' do
end要声明一个名称空间,该命名空间前缀在内部所有路由中,请使用namespace方法。 group , resource , resources和segment是此方法的别名。内部的任何端点都将共享其父上下文以及在命名空间上下文中完成的任何配置。
route_param方法是定义参数路由段的方便方法。如果定义类型,它将为此参数添加验证。
route_param :id , type : Integer do
get 'status' do
end
end
# is the same as
namespace ':id' do
params do
requires :id , type : Integer
end
get 'status' do
end
end可选地,您可以使用名称空间或端点上的正则表达式来定义命名路由参数的要求。仅当满足所有要求时,该路线才会匹配。
get ':id' , requirements : { id : /[0-9]*/ } do
Status . find ( params [ :id ] )
end
namespace :outer , requirements : { id : /[0-9]*/ } do
get :id do
end
get ':id/edit' do
end
end 您可以通过给出一个块或一个模块数组来定义端点可以与helpers宏一起使用的辅助方法。
module StatusHelpers
def user_info ( user )
" #{ user } has statused #{ user . statuses } status(s)"
end
end
module HttpCodesHelpers
def unauthorized
401
end
end
class API < Grape :: API
# define helpers with a block
helpers do
def current_user
User . find ( params [ :user_id ] )
end
end
# or mix in an array of modules
helpers StatusHelpers , HttpCodesHelpers
before do
error! ( 'Access Denied' , unauthorized ) unless current_user
end
get 'info' do
# helpers available in your endpoint and filters
user_info ( current_user )
end
end您可以使用helpers定义可重复使用的params 。
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 # aliases: includes, use_scope
end
get do
Collection . page ( params [ :page ] ) . per ( params [ :per_page ] )
end
end您还可以使用共享帮助者定义可重复使用的params 。
module SharedParams
extend Grape :: API :: Helpers
params :period do
optional :start_date
optional :end_date
end
params :pagination do
optional :page , type : Integer
optional :per_page , type : Integer
end
end
class API < Grape :: API
helpers SharedParams
desc 'Get collection.'
params do
use :period , :pagination
end
get do
Collection
. from ( params [ :start_date ] )
. to ( params [ :end_date ] )
. page ( params [ :page ] )
. per ( params [ :per_page ] )
end
end帮助者支持可以帮助设置默认值的块。以下API可以按asc或desc顺序返回按id或created_at排序的集合。
module SharedParams
extend Grape :: API :: Helpers
params :order do | options |
optional :order_by , type : Symbol , values : options [ :order_by ] , default : options [ :default_order_by ]
optional :order , type : Symbol , values : %i( asc desc ) , default : options [ :default_order ]
end
end
class API < Grape :: API
helpers SharedParams
desc 'Get a sorted collection.'
params do
use :order , order_by : %i( id created_at ) , default_order_by : :created_at , default_order : :asc
end
get do
Collection . send ( params [ :order ] , params [ :order_by ] )
end
end 如果您需要在端点内生成路径的方法,请参阅葡萄圈螺旋桨宝石。
您可以使用documentation哈希附加附加文档到params 。
params do
optional :first_name , type : String , documentation : { example : 'Jim' }
requires :last_name , type : String , documentation : { example : 'Smith' }
end如果不需要文档(例如,它是内部API),则可以禁用文档。
class API < Grape :: API
do_not_document!
# endpoints...
end在这种情况下,葡萄不会创建与文档有关的对象,这些对象永远保留在RAM中。
您可以简单地使用cookies方法设置,获取和删除cookie。
class API < Grape :: API
get 'status_count' do
cookies [ :status_count ] ||= 0
cookies [ :status_count ] += 1
{ status_count : cookies [ :status_count ] }
end
delete 'status_count' do
{ status_count : cookies . delete ( :status_count ) }
end
end使用基于哈希的语法设置多个值。
cookies [ :status_count ] = {
value : 0 ,
expires : Time . tomorrow ,
domain : '.twitter.com' ,
path : '/'
}
cookies [ :status_count ] [ :value ] += 1删除带delete cookie。
cookies . delete :status_count指定可选路径。
cookies . delete :status_count , path : '/' 默认情况下,葡萄返回了POST - 要求的201, DELETE要求的204,不返回任何内容,以及所有其他请求的200个状态代码。您可以使用status查询并设置实际的HTTP状态代码
post do
status 202
if status == 200
# do some thing
end
end您还可以使用机架util提供的状态代码符号之一
post do
status :no_content
end 您可以暂时重定向到新的URL(302)或永久性(301)。
redirect '/statuses' redirect '/statuses' , permanent : true 您可以识别与给定路径匹配的端点。
此API返回一个Grape::Endpoint的实例。
class API < Grape :: API
get '/statuses' do
end
end
API . recognize_path '/statuses'由于版本2.1.0 ,因此recognize_path方法考虑了参数类型,以确定哪个端点应与给定路径匹配。
class Books < Grape :: API
resource :books do
route_param :id , type : Integer do
# GET /books/:id
get do
#...
end
end
resource :share do
# POST /books/share
post do
# ....
end
end
end
end
API . recognize_path '/books/1' # => /books/:id
API . recognize_path '/books/share' # => /books/share
API . recognize_path '/books/other' # => nil 当您添加资源的GET路由时,还将自动添加HEAD方法的路由。您可以使用do_not_route_head! 。
class API < Grape :: API
do_not_route_head!
get '/example' do
# only responds to GET
end
end When you add a route for a resource, a route for the OPTIONS method will also be added. The response to an OPTIONS request will include an "Allow" header listing the supported methods. If the resource has before and after callbacks they will be executed, but no other callbacks will run.
class API < Grape :: API
get '/rt_count' do
{ rt_count : current_user . rt_count }
end
params do
requires :value , type : Integer , desc : 'Value to add to the rt count.'
end
put '/rt_count' do
current_user . rt_count += params [ :value ] . to_i
{ rt_count : current_user . rt_count }
end
end curl -v -X OPTIONS http://localhost:3000/rt_count
> OPTIONS /rt_count HTTP/1.1
>
< HTTP/1.1 204 No Content
< Allow: OPTIONS, GET, PUT You can disable this behavior with do_not_route_options! 。
If a request for a resource is made with an unsupported HTTP method, an HTTP 405 (Method Not Allowed) response will be returned. If the resource has before callbacks they will be executed, but no other callbacks will run.
curl -X DELETE -v http://localhost:3000/rt_count/
> DELETE /rt_count/ HTTP/1.1
> Host: localhost:3000
>
< HTTP/1.1 405 Method Not Allowed
< Allow: OPTIONS, GET, PUTYou can abort the execution of an API method by raising errors with error! 。
error! 'Access Denied' , 401 Anything that responds to #to_s can be given as a first argument to error! 。
error! :not_found , 404You can also return JSON formatted objects by raising error! and passing a hash instead of a message.
error! ( { error : 'unexpected error' , detail : 'missing widget' } , 500 ) You can set additional headers for the response. They will be merged with headers set before error!称呼。
error! ( 'Something went wrong' , 500 , 'X-Error-Detail' => 'Invalid token.' )You can present documented errors with a Grape entity using the the grape-entity gem.
module API
class Error < Grape :: Entity
expose :code
expose :message
end
end The following example specifies the entity to use in the http_codes definition.
desc 'My Route' do
failure [ [ 408 , 'Unauthorized' , API :: Error ] ]
end
error! ( { message : 'Unauthorized' } , 408 )The following example specifies the presented entity explicitly in the error message.
desc 'My Route' do
failure [ [ 408 , 'Unauthorized' ] ]
end
error! ( { message : 'Unauthorized' , with : API :: Error } , 408 ) By default Grape returns a 500 status code from error! 。 You can change this with default_error_status .
class API < Grape :: API
default_error_status 400
get '/example' do
error! 'This should have http status code 400'
end
endFor Grape to handle all the 404s for your API, it can be useful to use a catch-all. In its simplest form, it can be like:
route :any , '*path' do
error! # or something else
endIt is very crucial to define this endpoint at the very end of your API , as it literally accepts every request.
Grape can be told to rescue all StandardError exceptions and return them in the API format.
class Twitter :: API < Grape :: API
rescue_from :all
end This mimics default rescue behaviour when an exception type is not provided. Any other exception should be rescued explicitly, see below.
Grape can also rescue from all exceptions and still use the built-in exception handing. This will give the same behavior as rescue_from :all with the addition that Grape will use the exception handling defined by all Exception classes that inherit Grape::Exceptions::Base .
The intent of this setting is to provide a simple way to cover the most common exceptions and return any unexpected exceptions in the API format.
class Twitter :: API < Grape :: API
rescue_from :grape_exceptions
end If you want to customize the shape of grape exceptions returned to the user, to match your :all handler for example, you can pass a block to rescue_from :grape_exceptions .
rescue_from :grape_exceptions do | e |
error! ( e , e . status )
endYou can also rescue specific exceptions.
class Twitter :: API < Grape :: API
rescue_from ArgumentError , UserDefinedError
end In this case UserDefinedError must be inherited from StandardError .
Notice that you could combine these two approaches (rescuing custom errors takes precedence). For example, it's useful for handling all exceptions except Grape validation errors.
class Twitter :: API < Grape :: API
rescue_from Grape :: Exceptions :: ValidationErrors do | e |
error! ( e , 400 )
end
rescue_from :all
endThe error format will match the request format. See "Content-Types" below.
Custom error formatters for existing and additional types can be defined with a proc.
class Twitter :: API < Grape :: API
error_formatter :txt , -> ( message , backtrace , options , env , original_exception ) {
"error: #{ message } from #{ backtrace } "
}
endYou can also use a module or class.
module CustomFormatter
def self . call ( message , backtrace , options , env , original_exception )
{ message : message , backtrace : backtrace }
end
end
class Twitter :: API < Grape :: API
error_formatter :custom , CustomFormatter
end You can rescue all exceptions with a code block. The error! wrapper automatically sets the default error code and content-type.
class Twitter :: API < Grape :: API
rescue_from :all do | e |
error! ( "rescued from #{ e . class . name } " )
end
endOptionally, you can set the format, status code and headers.
class Twitter :: API < Grape :: API
format :json
rescue_from :all do | e |
error! ( { error : 'Server error.' } , 500 , { 'Content-Type' => 'text/error' } )
end
endYou can also rescue all exceptions with a code block and handle the Rack response at the lowest level.
class Twitter :: API < Grape :: API
rescue_from :all do | e |
Rack :: Response . new ( [ e . message ] , 500 , { 'Content-type' => 'text/error' } )
end
endOr rescue specific exceptions.
class Twitter :: API < Grape :: API
rescue_from ArgumentError do | e |
error! ( "ArgumentError: #{ e . message } " )
end
rescue_from NoMethodError do | e |
error! ( "NoMethodError: #{ e . message } " )
end
end By default, rescue_from will rescue the exceptions listed and all their subclasses.
Assume you have the following exception classes defined.
module APIErrors
class ParentError < StandardError ; end
class ChildError < ParentError ; end
end Then the following rescue_from clause will rescue exceptions of type APIErrors::ParentError and its subclasses (in this case APIErrors::ChildError ).
rescue_from APIErrors :: ParentError do | e |
error! ( {
error : " #{ e . class } error" ,
message : e . message
} , e . status )
end To only rescue the base exception class, set rescue_subclasses: false . The code below will rescue exceptions of type RuntimeError but not its subclasses.
rescue_from RuntimeError , rescue_subclasses : false do | e |
error! ( {
status : e . status ,
message : e . message ,
errors : e . errors
} , e . status )
end Helpers are also available inside rescue_from .
class Twitter :: API < Grape :: API
format :json
helpers do
def server_error!
error! ( { error : 'Server error.' } , 500 , { 'Content-Type' => 'text/error' } )
end
end
rescue_from :all do | e |
server_error!
end
end The rescue_from handler must return a Rack::Response object, call error! , or raise an exception (either the original exception or another custom one). The exception raised in rescue_from will be handled outside Grape. For example, if you mount Grape in Rails, the exception will be handle by Rails Action Controller.
Alternately, use the with option in rescue_from to specify a method or a proc .
class Twitter :: API < Grape :: API
format :json
helpers do
def server_error!
error! ( { error : 'Server error.' } , 500 , { 'Content-Type' => 'text/error' } )
end
end
rescue_from :all , with : :server_error!
rescue_from ArgumentError , with : -> { Rack :: Response . new ( 'rescued with a method' , 400 ) }
end Inside the rescue_from block, the environment of the original controller method( .self receiver) is accessible through the #context method.
class Twitter :: API < Grape :: API
rescue_from :all do | e |
user_id = context . params [ :user_id ]
error! ( "error for #{ user_id } " )
end
end You could put rescue_from clauses inside a namespace and they will take precedence over ones defined in the root scope:
class Twitter :: API < Grape :: API
rescue_from ArgumentError do | e |
error! ( "outer" )
end
namespace :statuses do
rescue_from ArgumentError do | e |
error! ( "inner" )
end
get do
raise ArgumentError . new
end
end
end Here 'inner' will be result of handling occurred ArgumentError .
Grape::Exceptions::InvalidVersionHeader , which is raised when the version in the request header doesn't match the currently evaluated version for the endpoint, will never be rescued from a rescue_from block (even a rescue_from :all ) This is because Grape relies on Rack to catch that error and try the next versioned-route for cases where there exist identical Grape endpoints with different versions.
Any exception that is not subclass of StandardError should be rescued explicitly. Usually it is not a case for an application logic as such errors point to problems in Ruby runtime. This is following standard recommendations for exceptions handling.
Grape::API provides a logger method which by default will return an instance of the Logger class from Ruby's standard library.
To log messages from within an endpoint, you need to define a helper to make the logger available in the endpoint context.
class API < Grape :: API
helpers do
def logger
API . logger
end
end
post '/statuses' do
logger . info " #{ current_user } has statused"
end
endTo change the logger level.
class API < Grape :: API
self . logger . level = Logger :: INFO
endYou can also set your own logger.
class MyLogger
def warning ( message )
puts "this is a warning: #{ message } "
end
end
class API < Grape :: API
logger MyLogger . new
helpers do
def logger
API . logger
end
end
get '/statuses' do
logger . warning " #{ current_user } has statused"
end
endFor similar to Rails request logging try the grape_logging or grape-middleware-logger gems.
Your API can declare which content-types to support by using content_type . If you do not specify any, Grape will support XML , JSON , BINARY , and TXT content-types. The default format is :txt ; you can change this with default_format . Essentially, the two APIs below are equivalent.
class Twitter :: API < Grape :: API
# no content_type declarations, so Grape uses the defaults
end
class Twitter :: API < Grape :: API
# the following declarations are equivalent to the defaults
content_type :xml , 'application/xml'
content_type :json , 'application/json'
content_type :binary , 'application/octet-stream'
content_type :txt , 'text/plain'
default_format :txt
end If you declare any content_type whatsoever, the Grape defaults will be overridden. For example, the following API will only support the :xml and :rss content-types, but not :txt , :json , or :binary . Importantly, this means the :txt default format is not supported! So, make sure to set a new default_format .
class Twitter :: API < Grape :: API
content_type :xml , 'application/xml'
content_type :rss , 'application/xml+rss'
default_format :xml
end Serialization takes place automatically. For example, you do not have to call to_json in each JSON API endpoint implementation. The response format (and thus the automatic serialization) is determined in the following order:
format parameter in the query string, if specified.format option, if specified.Accept header.default_format option.:txt .For example, consider the following API.
class MultipleFormatAPI < Grape :: API
content_type :xml , 'application/xml'
content_type :json , 'application/json'
default_format :json
get :hello do
{ hello : 'world' }
end
endGET /hello (with an Accept: */* header) does not have an extension or a format parameter, so it will respond with JSON (the default format).GET /hello.xml has a recognized extension, so it will respond with XML.GET /hello?format=xml has a recognized format parameter, so it will respond with XML.GET /hello.xml?format=json has a recognized extension (which takes precedence over the format parameter), so it will respond with XML.GET /hello.xls (with an Accept: */* header) has an extension, but that extension is not recognized, so it will respond with JSON (the default format).GET /hello.xls with an Accept: application/xml header has an unrecognized extension, but the Accept header corresponds to a recognized format, so it will respond with XML.GET /hello.xls with an Accept: text/plain header has an unrecognized extension and an unrecognized Accept header, so it will respond with JSON (the default format). You can override this process explicitly by calling api_format in the API itself. For example, the following API will let you upload arbitrary files and return their contents as an attachment with the correct MIME type.
class Twitter :: API < Grape :: API
post 'attachment' do
filename = params [ :file ] [ :filename ]
content_type MIME :: Types . type_for ( filename ) [ 0 ] . to_s
api_format :binary # there's no formatter for :binary, data will be returned "as is"
header 'Content-Disposition' , "attachment; filename*=UTF-8'' #{ CGI . escape ( filename ) } "
params [ :file ] [ :tempfile ] . read
end
end You can have your API only respond to a single format with format . If you use this, the API will not respond to file extensions other than specified in format . For example, consider the following API.
class SingleFormatAPI < Grape :: API
format :json
get :hello do
{ hello : 'world' }
end
endGET /hello will respond with JSON.GET /hello.json will respond with JSON.GET /hello.xml , GET /hello.foobar , or any other extension will respond with an HTTP 404 error code.GET /hello?format=xml will respond with an HTTP 406 error code, because the XML format specified by the request parameter is not supported.GET /hello with an Accept: application/xml header will still respond with JSON, since it could not negotiate a recognized content-type from the headers and JSON is the effective default. The formats apply to parsing, too. The following API will only respond to the JSON content-type and will not parse any other input than application/json , application/x-www-form-urlencoded , multipart/form-data , multipart/related and multipart/mixed . All other requests will fail with an HTTP 406 error code.
class Twitter :: API < Grape :: API
format :json
end When the content-type is omitted, Grape will return a 406 error code unless default_format is specified. The following API will try to parse any data without a content-type using a JSON parser.
class Twitter :: API < Grape :: API
format :json
default_format :json
end If you combine format with rescue_from :all , errors will be rendered using the same format. If you do not want this behavior, set the default error formatter with default_error_formatter .
class Twitter :: API < Grape :: API
format :json
content_type :txt , 'text/plain'
default_error_formatter :txt
endCustom formatters for existing and additional types can be defined with a proc.
class Twitter :: API < Grape :: API
content_type :xls , 'application/vnd.ms-excel'
formatter :xls , -> ( object , env ) { object . to_xls }
endYou can also use a module or class.
module XlsFormatter
def self . call ( object , env )
object . to_xls
end
end
class Twitter :: API < Grape :: API
content_type :xls , 'application/vnd.ms-excel'
formatter :xls , XlsFormatter
endBuilt-in formatters are the following.
:json : use object's to_json when available, otherwise call MultiJson.dump:xml : use object's to_xml when available, usually via MultiXml:txt : use object's to_txt when available, otherwise to_s:serializable_hash : use object's serializable_hash when available, otherwise fallback to :json:binary : data will be returned "as is"If a body is present in a request to an API, with a Content-Type header value that is of an unsupported type a "415 Unsupported Media Type" error code will be returned by Grape.
Response statuses that indicate no content as defined by Rack here will bypass serialization and the body entity - though there should be none - will not be modified.
Grape supports JSONP via Rack::JSONP, part of the rack-contrib gem. Add rack-contrib to your Gemfile .
require 'rack/contrib'
class API < Grape :: API
use Rack :: JSONP
format :json
get '/' do
'Hello World'
end
endGrape supports CORS via Rack::CORS, part of the rack-cors gem. Add rack-cors to your Gemfile , then use the middleware in your config.ru file.
require 'rack/cors'
use Rack :: Cors do
allow do
origins '*'
resource '*' , headers : :any , methods : :get
end
end
run Twitter :: API Content-type is set by the formatter. You can override the content-type of the response at runtime by setting the Content-Type header.
class API < Grape :: API
get '/home_timeline_js' do
content_type 'application/javascript'
"var statuses = ...;"
end
end Grape accepts and parses input data sent with the POST and PUT methods as described in the Parameters section above. It also supports custom data formats. You must declare additional content-types via content_type and optionally supply a parser via parser unless a parser is already available within Grape to enable a custom format. Such a parser can be a function or a class.
With a parser, parsed data is available "as-is" in env['api.request.body'] . Without a parser, data is available "as-is" and in env['api.request.input'] .
The following example is a trivial parser that will assign any input with the "text/custom" content-type to :value . The parameter will be available via params[:value] inside the API call.
module CustomParser
def self . call ( object , env )
{ value : object . to_s }
end
end content_type :txt , 'text/plain'
content_type :custom , 'text/custom'
parser :custom , CustomParser
put 'value' do
params [ :value ]
endYou can invoke the above API as follows.
curl -X PUT -d 'data' 'http://localhost:9292/value' -H Content-Type:text/custom -v
You can disable parsing for a content-type with nil . For example, parser :json, nil will disable JSON parsing altogether. The request data is then available as-is in env['api.request.body'] .
Grape uses JSON and ActiveSupport::XmlMini for JSON and XML parsing by default. It also detects and supports multi_json and multi_xml. Adding those gems to your Gemfile and requiring them will enable them and allow you to swap the JSON and XML back-ends.
Grape supports a range of ways to present your data with some help from a generic present method, which accepts two arguments: the object to be presented and the options associated with it. The options hash may include :with , which defines the entity to expose.
Add the grape-entity gem to your Gemfile. Please refer to the grape-entity documentation for more details.
The following example exposes statuses.
module API
module Entities
class Status < Grape :: Entity
expose :user_name
expose :text , documentation : { type : 'string' , desc : 'Status update text.' }
expose :ip , if : { type : :full }
expose :user_type , :user_id , if : -> ( status , options ) { status . user . public? }
expose :digest do | status , options |
Digest :: MD5 . hexdigest ( status . txt )
end
expose :replies , using : API :: Status , as : :replies
end
end
class Statuses < Grape :: API
version 'v1'
desc 'Statuses index' do
params : API :: Entities :: Status . documentation
end
get '/statuses' do
statuses = Status . all
type = current_user . admin? ? :full : :default
present statuses , with : API :: Entities :: Status , type : type
end
end
end You can use entity documentation directly in the params block with using: Entity.documentation .
module API
class Statuses < Grape :: API
version 'v1'
desc 'Create a status'
params do
requires :all , except : [ :ip ] , using : API :: Entities :: Status . documentation . except ( :id )
end
post '/status' do
Status . create! params
end
end
endYou can present with multiple entities using an optional Symbol argument.
get '/statuses' do
statuses = Status . all . page ( 1 ) . per ( 20 )
present :total_page , 10
present :per_page , 20
present :statuses , statuses , with : API :: Entities :: Status
endThe response will be
{
total_page: 10,
per_page: 20,
statuses: []
}
In addition to separately organizing entities, it may be useful to put them as namespaced classes underneath the model they represent.
class Status
def entity
Entity . new ( self )
end
class Entity < Grape :: Entity
expose :text , :user_id
end
end If you organize your entities this way, Grape will automatically detect the Entity class and use it to present your models. In this example, if you added present Status.new to your endpoint, Grape will automatically detect that there is a Status::Entity class and use that as the representative entity. This can still be overridden by using the :with option or an explicit represents call.
You can present hash with Grape::Presenters::Presenter to keep things consistent.
get '/users' do
present { id : 10 , name : :dgz } , with : Grape :: Presenters :: Presenter
endThe response will be
{
id : 10 ,
name : 'dgz'
}It has the same result with
get '/users' do
present :id , 10
present :name , :dgz
end You can use Roar to render HAL or Collection+JSON with the help of grape-roar, which defines a custom JSON formatter and enables presenting entities with Grape's present keyword.
You can use Rabl templates with the help of the grape-rabl gem, which defines a custom Grape Rabl formatter.
You can use Active Model Serializers serializers with the help of the grape-active_model_serializers gem, which defines a custom Grape AMS formatter.
In general, use the binary format to send raw data.
class API < Grape :: API
get '/file' do
content_type 'application/octet-stream'
File . binread 'file.bin'
end
end You can set the response body explicitly with body .
class API < Grape :: API
get '/' do
content_type 'text/plain'
body 'Hello World'
# return value ignored
end
end Use body false to return 204 No Content without any data or content-type.
If you want to empty the body with an HTTP status code other than 204 No Content , you can override the status code after specifying body false as follows
class API < Grape :: API
get '/' do
body false
status 304
end
end You can also set the response to a file with sendfile . This works with the Rack::Sendfile middleware to optimally send the file through your web server software.
class API < Grape :: API
get '/' do
sendfile '/path/to/file'
end
end To stream a file in chunks use stream
class API < Grape :: API
get '/' do
stream '/path/to/file'
end
end If you want to stream non-file data use the stream method and a Stream object. This is an object that responds to each and yields for each chunk to send to the client. Each chunk will be sent as it is yielded instead of waiting for all of the content to be available.
class MyStream
def each
yield 'part 1'
yield 'part 2'
yield 'part 3'
end
end
class API < Grape :: API
get '/' do
stream MyStream . new
end
end Grape has built-in Basic authentication (the given block is executed in the context of the current Endpoint ). Authentication applies to the current namespace and any children, but not parents.
http_basic do | username , password |
# verify user's password here
# IMPORTANT: make sure you use a comparison method which isn't prone to a timing attack
end Grape can use custom Middleware for authentication. How to implement these Middleware have a look at Rack::Auth::Basic or similar implementations.
For registering a Middleware you need the following options:
label - the name for your authenticator to use it laterMiddlewareClass - the MiddlewareClass to use for authenticationoption_lookup_proc - A Proc with one Argument to lookup the options at runtime (return value is an Array as Parameter for the Middleware).例子:
Grape :: Middleware :: Auth :: Strategies . add ( :my_auth , AuthMiddleware , -> ( options ) { [ options [ :realm ] ] } )
auth :my_auth , { realm : 'Test Api' } do | credentials |
# lookup the user's password here
{ 'user1' => 'password1' } [ username ]
endUse Doorkeeper, warden-oauth2 or rack-oauth2 for OAuth2 support.
You can access the controller params, headers, and helpers through the context with the #context method inside any auth middleware inherited from Grape::Middleware::Auth::Base .
Grape routes can be reflected at runtime. This can notably be useful for generating documentation.
Grape exposes arrays of API versions and compiled routes. Each route contains a prefix , version , namespace , method and params . You can add custom route settings to the route metadata with route_setting .
class TwitterAPI < Grape :: API
version 'v1'
desc 'Includes custom settings.'
route_setting :custom , key : 'value'
get do
end
endExamine the routes at runtime.
TwitterAPI :: versions # yields [ 'v1', 'v2' ]
TwitterAPI :: routes # yields an array of Grape::Route objects
TwitterAPI :: routes [ 0 ] . version # => 'v1'
TwitterAPI :: routes [ 0 ] . description # => 'Includes custom settings.'
TwitterAPI :: routes [ 0 ] . settings [ :custom ] # => { key: 'value' } Note that Route#route_xyz methods have been deprecated since 0.15.0 and removed since 2.0.1.
Please use Route#xyz instead.
Note that difference of Route#options and Route#settings .
The options can be referred from your route, it should be set by specifing key and value on verb methods such as get , post and put . The settings can also be referred from your route, but it should be set by specifing key and value on route_setting .
It's possible to retrieve the information about the current route from within an API call with route .
class MyAPI < Grape :: API
desc 'Returns a description of a parameter.'
params do
requires :id , type : Integer , desc : 'Identity.'
end
get 'params/:id' do
route . params [ params [ :id ] ] # yields the parameter description
end
end The current endpoint responding to the request is self within the API block or env['api.endpoint'] elsewhere. The endpoint has some interesting properties, such as source which gives you access to the original code block of the API implementation. This can be particularly useful for building a logger middleware.
class ApiLogger < Grape :: Middleware :: Base
def before
file = env [ 'api.endpoint' ] . source . source_location [ 0 ]
line = env [ 'api.endpoint' ] . source . source_location [ 1 ]
logger . debug "[api] #{ file } : #{ line } "
end
end Blocks can be executed before or after every API call, using before , after , before_validation and after_validation . If the API fails the after call will not be triggered, if you need code to execute for sure use the finally .
Before and after callbacks execute in the following order:
beforebefore_validationafter_validation (upon successful validation)after (upon successful validation and API call)finally (always)Steps 4, 5 and 6 only happen if validation succeeds.
If a request for a resource is made with an unsupported HTTP method (returning HTTP 405) only before callbacks will be executed. The remaining callbacks will be bypassed.
If a request for a resource is made that triggers the built-in OPTIONS handler, only before and after callbacks will be executed. The remaining callbacks will be bypassed.
For example, using a simple before block to set a header.
before do
header 'X-Robots-Tag' , 'noindex'
end You can ensure a block of code runs after every request (including failures) with finally :
finally do
# this code will run after every request (successful or failed)
end名称空间
Callbacks apply to each API call within and below the current namespace:
class MyAPI < Grape :: API
get '/' do
"root - #{ @blah } "
end
namespace :foo do
before do
@blah = 'blah'
end
get '/' do
"root - foo - #{ @blah } "
end
namespace :bar do
get '/' do
"root - foo - bar - #{ @blah } "
end
end
end
endThe behavior is then:
GET / # 'root - '
GET /foo # 'root - foo - blah'
GET /foo/bar # 'root - foo - bar - blah' Params on a namespace (or whichever alias you are using) will also be available when using before_validation or after_validation :
class MyAPI < Grape :: API
params do
requires :blah , type : Integer
end
resource ':blah' do
after_validation do
# if we reach this point validations will have passed
@blah = declared ( params , include_missing : false ) [ :blah ]
end
get '/' do
@blah . class
end
end
endThe behavior is then:
GET /123 # 'Integer'
GET /foo # 400 error - 'blah is invalid'版本控制
When a callback is defined within a version block, it's only called for the routes defined in that block.
class Test < Grape :: API
resource :foo do
version 'v1' , :using => :path do
before do
@output ||= 'v1-'
end
get '/' do
@output += 'hello'
end
end
version 'v2' , :using => :path do
before do
@output ||= 'v2-'
end
get '/' do
@output += 'hello'
end
end
end
endThe behavior is then:
GET /foo/v1 # 'v1-hello'
GET /foo/v2 # 'v2-hello'Altering Responses
Using present in any callback allows you to add data to a response:
class MyAPI < Grape :: API
format :json
after_validation do
present :name , params [ :name ] if params [ :name ]
end
get '/greeting' do
present :greeting , 'Hello!'
end
endThe behavior is then:
GET /greeting # {"greeting":"Hello!"}
GET /greeting ? name=Alan # {"name":"Alan","greeting":"Hello!"} Instead of altering a response, you can also terminate and rewrite it from any callback using error! , including after . This will cause all subsequent steps in the process to not be called. This includes the actual api call and any callbacks
Grape by default anchors all request paths, which means that the request URL should match from start to end to match, otherwise a 404 Not Found is returned. However, this is sometimes not what you want, because it is not always known upfront what can be expected from the call. This is because Rack-mount by default anchors requests to match from the start to the end, or not at all. Rails solves this problem by using a anchor: false option in your routes. In Grape this option can be used as well when a method is defined.
For instance when your API needs to get part of an URL, for instance:
class TwitterAPI < Grape :: API
namespace :statuses do
get '/(*:status)' , anchor : false do
end
end
end This will match all paths starting with '/statuses/'. There is one caveat though: the params[:status] parameter only holds the first part of the request url. Luckily this can be circumvented by using the described above syntax for path specification and using the PATH_INFO Rack environment variable, using env['PATH_INFO'] . This will hold everything that comes after the '/statuses/' part.
You can use instance variables to pass information across the various stages of a request. An instance variable set within a before validator is accessible within the endpoint's code and can also be utilized within the rescue_from handler.
class TwitterAPI < Grape :: API
before do
@var = 1
end
get '/' do
puts @var # => 1
raise
end
rescue_from :all do
puts @var # => 1
end
endThe values of instance variables cannot be shared among various endpoints within the same API. This limitation arises due to Grape generating a new instance for each request made. Consequently, instance variables set within an endpoint during one request differ from those set during a subsequent request, as they exist within separate instances.
class TwitterAPI < Grape :: API
get '/first' do
@var = 1
puts @var # => 1
end
get '/second' do
puts @var # => nil
end
end You can make a custom middleware by using Grape::Middleware::Base . It's inherited from some grape official middlewares in fact.
For example, you can write a middleware to log application exception.
class LoggingError < Grape :: Middleware :: Base
def after
return unless @app_response && @app_response [ 0 ] == 500
env [ 'rack.logger' ] . error ( "Raised error on #{ env [ 'PATH_INFO' ] } " )
end
endYour middleware can overwrite application response as follows, except error case.
class Overwriter < Grape :: Middleware :: Base
def after
[ 200 , { 'Content-Type' => 'text/plain' } , [ 'Overwritten.' ] ]
end
end You can add your custom middleware with use , that push the middleware onto the stack, and you can also control where the middleware is inserted using insert , insert_before and insert_after .
class CustomOverwriter < Grape :: Middleware :: Base
def after
[ 200 , { 'Content-Type' => 'text/plain' } , [ @options [ :message ] ] ]
end
end
class API < Grape :: API
use Overwriter
insert_before Overwriter , CustomOverwriter , message : 'Overwritten again.'
insert 0 , CustomOverwriter , message : 'Overwrites all other middleware.'
get '/' do
end
end You can access the controller params, headers, and helpers through the context with the #context method inside any middleware inherited from Grape::Middleware::Base .
Note that when you're using Grape mounted on Rails you don't have to use Rails middleware because it's already included into your middleware stack. You only have to implement the helpers to access the specific env variable.
If you are using a custom application that is inherited from Rails::Application and need to insert a new middleware among the ones initiated via Rails, you will need to register it manually in your custom application class.
class Company :: Application < Rails :: Application
config . middleware . insert_before ( Rack :: Attack , Middleware :: ApiLogger )
end By default you can access remote IP with request.ip . This is the remote IP address implemented by Rack. Sometimes it is desirable to get the remote IP Rails-style with ActionDispatch::RemoteIp .
Add gem 'actionpack' to your Gemfile and require 'action_dispatch/middleware/remote_ip.rb' . Use the middleware in your API and expose a client_ip helper. See this documentation for additional options.
class API < Grape :: API
use ActionDispatch :: RemoteIp
helpers do
def client_ip
env [ 'action_dispatch.remote_ip' ] . to_s
end
end
get :remote_ip do
{ ip : client_ip }
end
end Use rack-test and define your API as app .
You can test a Grape API with RSpec by making HTTP requests and examining the response.
describe Twitter :: API do
include Rack :: Test :: Methods
def app
Twitter :: API
end
context 'GET /api/statuses/public_timeline' do
it 'returns an empty array of statuses' do
get '/api/statuses/public_timeline'
expect ( last_response . status ) . to eq ( 200 )
expect ( JSON . parse ( last_response . body ) ) . to eq [ ]
end
end
context 'GET /api/statuses/:id' do
it 'returns a status by id' do
status = Status . create!
get "/api/statuses/ #{ status . id } "
expect ( last_response . body ) . to eq status . to_json
end
end
endThere's no standard way of sending arrays of objects via an HTTP GET, so POST JSON data and specify the correct content-type.
describe Twitter :: API do
context 'POST /api/statuses' do
it 'creates many statuses' do
statuses = [ { text : '...' } , { text : '...' } ]
post '/api/statuses' , statuses . to_json , 'CONTENT_TYPE' => 'application/json'
expect ( last_response . body ) . to eq 201
end
end
end You can test with other RSpec-based frameworks, including Airborne, which uses rack-test to make requests.
require 'airborne'
Airborne . configure do | config |
config . rack_app = Twitter :: API
end
describe Twitter :: API do
context 'GET /api/statuses/:id' do
it 'returns a status by id' do
status = Status . create!
get "/api/statuses/ #{ status . id } "
expect_json ( status . as_json )
end
end
end require 'test_helper'
class Twitter :: APITest < MiniTest :: Test
include Rack :: Test :: Methods
def app
Twitter :: API
end
def test_get_api_statuses_public_timeline_returns_an_empty_array_of_statuses
get '/api/statuses/public_timeline'
assert last_response . ok?
assert_equal [ ] , JSON . parse ( last_response . body )
end
def test_get_api_statuses_id_returns_a_status_by_id
status = Status . create!
get "/api/statuses/ #{ status . id } "
assert_equal status . to_json , last_response . body
end
end describe Twitter :: API do
context 'GET /api/statuses/public_timeline' do
it 'returns an empty array of statuses' do
get '/api/statuses/public_timeline'
expect ( response . status ) . to eq ( 200 )
expect ( JSON . parse ( response . body ) ) . to eq [ ]
end
end
context 'GET /api/statuses/:id' do
it 'returns a status by id' do
status = Status . create!
get "/api/statuses/ #{ status . id } "
expect ( response . body ) . to eq status . to_json
end
end
end In Rails, HTTP request tests would go into the spec/requests group. You may want your API code to go into app/api - you can match that layout under spec by adding the following in spec/rails_helper.rb .
RSpec . configure do | config |
config . include RSpec :: Rails :: RequestExampleGroup , type : :request , file_path : /spec / api/
end class Twitter :: APITest < ActiveSupport :: TestCase
include Rack :: Test :: Methods
def app
Rails . application
end
test 'GET /api/statuses/public_timeline returns an empty array of statuses' do
get '/api/statuses/public_timeline'
assert last_response . ok?
assert_equal [ ] , JSON . parse ( last_response . body )
end
test 'GET /api/statuses/:id returns a status by id' do
status = Status . create!
get "/api/statuses/ #{ status . id } "
assert_equal status . to_json , last_response . body
end
end Because helpers are mixed in based on the context when an endpoint is defined, it can be difficult to stub or mock them for testing. The Grape::Endpoint.before_each method can help by allowing you to define behavior on the endpoint that will run before every request.
describe 'an endpoint that needs helpers stubbed' do
before do
Grape :: Endpoint . before_each do | endpoint |
allow ( endpoint ) . to receive ( :helper_name ) . and_return ( 'desired_value' )
end
end
after do
Grape :: Endpoint . before_each nil
end
it 'stubs the helper' do
end
end Use grape-reload.
Add API paths to config/application.rb .
# Auto-load API and its subdirectories
config . paths . add File . join ( 'app' , 'api' ) , glob : File . join ( '**' , '*.rb' )
config . autoload_paths += Dir [ Rails . root . join ( 'app' , 'api' , '*' ) ] Create config/initializers/reload_api.rb .
if Rails . env . development?
ActiveSupport :: Dependencies . explicitly_unloadable_constants << 'Twitter::API'
api_files = Dir [ Rails . root . join ( 'app' , 'api' , '**' , '*.rb' ) ]
api_reloader = ActiveSupport :: FileUpdateChecker . new ( api_files ) do
Rails . application . reload_routes!
end
ActionDispatch :: Callbacks . to_prepare do
api_reloader . execute_if_updated
end
endFor Rails >= 5.1.4, change this:
ActionDispatch :: Callbacks . to_prepare do
api_reloader . execute_if_updated
end因此:
ActiveSupport :: Reloader . to_prepare do
api_reloader . execute_if_updated
endSee StackOverflow #3282655 for more information.
Grape has built-in support for ActiveSupport::Notifications which provides simple hook points to instrument key parts of your application.
The following are currently supported:
The main execution of an endpoint, includes filters and rendering.
The execution of the main content block of the endpoint.
The execution of validators.
Serialization or template rendering.
Grape::Formatter::Json )See the ActiveSupport::Notifications documentation for information on how to subscribe to these events.
Grape integrates with following third-party tools:
Grape is work of hundreds of contributors. You're encouraged to submit pull requests, propose features and discuss issues.
See CONTRIBUTING.
See SECURITY for details.
麻省理工学院许可证。有关详细信息,请参见许可证。
Copyright (c) 2010-2020 Michael Bleigh, Intridea Inc. and Contributors.