Arguments
Arguments are the name given to any input that is being provided by the consumer to an endpoint.
Definining arguments
At the most basic an argument will likely be a scalar value (a string, integer, boolean etc...) and will be defined as so on an endpoint:
class MyEndpoint < Apia::Endpoint
argument :name, :string, required: true
argument :age, :integer, required: true
argument :superhuman, :boolean
end
It is important that all the arguments that you want to receive are defined here so a) they're documented and b) you benefit from built in validation of the input provided.
When defining an argument, you must always specify a name and a type at a minimum. You can also choose to specify whether an argument is required or not.
Types can be any scalar (see list of built-in scalars in README), enum or argument set (see further down this document about sets).
Providing arguments
The consumer will provide arguments in one of two ways usually - they'll be provided as querystring parameters to GET-like requests or in the body of the request for POST-like requests.
Accessing arguments
Arguments are available on the request
object within the action
block of an action. For example:
class MyEndpoint < Apia::Endpoint
argument :name, :string, required: true
def call
request.arguments[:name] # => 'Adam'
end
end
Arrays
If you wish to receive an array of items from a consumer, you can specify that an argument is an array rather than a flat object.
class MyEndpoint < Apia::Endpoint
argument :names, [:string]
action do
request.arguments[:names] # => ['Adam', 'Dave', 'Charlie']
end
end
Argument sets
Argument sets provide you with an option to define a set of multiple arguments that should be provided. It's probably easiest to explain in code...
class UserProperties < Apia::ArgumentSet
argument :name, :string, required: true
argument :date_of_birth, :date
argument :hair_color, :string
argument :favourite_color, :string
argument :favourite_number, :integer
end
class MyEndpoint < Apia::Endpoint
argument :user, UserProperties, required: true
action do
request.arguments[:user][:name]
# or...
request.arguments.dig(:user, :name)
end
end
In this situation, you will specify that the user
argument must be an object containing the arguments defined in the UserProperties
argument set. This is useful in situations where you may want to reuse sets of arguments.
Lookup argument sets
Apia implements a standard method for allowing objects to be looked up from your backend database. These are called lookup argument sets. Also, demonstrated below with some code.
class UserLookup < Apia::LookupArgumentSet
# Define the different fields that users can be looked up by
argument :id, :integer
argument :username, :string
# Define any errors that will be raised if no user is found
potential_error 'UserNotFound' do
code :user_not_found
http_status 404
end
potential_error 'NotPermittedToAccessUser' do
code :not_permitted_to_access_user
http_status 403
end
# Define how the object can be looked up. You can implement your own
# logic for how to handle the set that's provided. It should return the
# object or have previously raised an error.
resolver do |set, request|
if set[:id]
user = User.find_by_id(set[:id])
elsif set[:username]
user = User.find_by_username(set[:username])
end
if user.nil?
raise_error 'UserNotFound'
end
unless user.accessible_by?(request.identity)
raise_error 'NotPermittedToAccessUser'
end
user
end
end
class InfoEndpoint < Apia::Endpoint
argument :user, UserLookup, required: true
def call
# This will return the plain set as a normal argument set would.
user_set = request.arguments[:user]
# Calling `resolve` on this will actually resolve the user to
# the object as defined by the resolver.
user = request.arguments[:user].resolve
end
end
There's a fair amount of code here so let's go through that.
- Firstly, we'll define our lookup argument set. This should probably be in its own file so it can be easily shared across endpoints.
- Within this set we define a list of arguments that are supported. Unlike a normal argument set, you do not need to specify these are being required. The lookup argument set will force the user to provide ONLY ONE of these options. If they provide none an error will be returned and if they provide two or more, a different error will be returned,
- Unlike normal argument sets, lookup sets can actually cause an error to be encountered when resolving them to an object. Because of this, you need to define any potential errors that might be enountered.
- Then, a resolver is defined which defines how to find the required object from the set.
- Finally, we then need to actually reference the argument set from an endpoint. We define the argument as any other argument and then within the action we can call it with
.resolve
which will invoke the resolution. You can pass additional options toresolve
which will be passed through to the resolver block.