# The Config File
As we previously discussed, the config file is a zum.toml
file, writen using the TOML (opens new window) markup language. This file should contain two top level keys: the metadata
key and the endpoints
key.
# The metadata
key
The metadata
key contains everything that is needed in order to query the API, but is not data about an endpoint of the API itself. This key can hold the following keys:
server
(required): The base URL where the API is hosted.
As an example, the first lines of your zum.toml
file for a development environment should probably look similar to this:
[metadata]
server = "http://localhost:8000"
This indicates to zum
that the API endpoints are located at http://localhost:8000
. Easy enough, right?
# The endpoints
key
The endpoints
key contains every endpoint that you want to be able to interact with from zum
. Each endpoint can hold the following keys:
route
(required): The route to the endpoint itself.method
(required): The HTTP method (opens new window) to use when querying the endpoint.params
: The values to replace on theroute
with user-given values.headers
: The header keys to send with the request.body
: The body keys (and optionally types) to send with the request.
Notice that each endpoint is a sub-key within the endpoints
top level key. This really shouldn't mean a lot to you as the developer, it just means that, when defining an endpoint, you should do it like this:
[endpoints.dada]
route = "/song"
method = "get"
Notice that the endpoint name (dada
) follows a endpoints.
, which is the TOML convention for creating sub-keys within a certain key. If you defined the metadata
key as the example on the previous section, to interact with that particular endpoint all you need to do is to run:
zum dada
With that, zum
will send a GET
HTTP request to http://localhost:8000/song
🎵. Just 5 lines on a TOML file!
You should now have a pretty reasonable understanding about exactly how the route
and method
keys work within each endpoint. Let's explore how the rest of the keys function!
# The params
of an endpoint
This key is only required when you want to be able to interpolate a value on the route
. For example, if you had a library of songs and wanted to GET /songs/{id}
, you would probably want to pass the id
value of the song through the CLI. To define this behaviour, let's define our TOML endpoint:
[endpoints.get-song]
route = "/songs/{id}"
method = "get"
params = ["id"]
There are a couple of interesting things in here. The first one is that there is now a bracket-surrounded chunk of the route
string. This is the chunk that is intended to be replaced by a CLI input. The second interesting thing is that the params
attribute is an array. This makes sense, as there might be more than one element to replace on the route
, but we'll talk about this in a tiny bit. Finally, notice that the only element of the array matches what's inside the brackets of the route
. This is very important, as it tells zum
to replace that chunk of the route
with what gets passed through the CLI. Speaking of the CLI, how do we use this endpoint?
zum get-song 57
Now, zum
will send a GET
HTTP request to http://localhost:8000/songs/57
. Pretty cool!
Notice that you can place the brackets anywhere on the route
string. This makes for an interesting posibility, as you can use it to query something inside an entity, for example. Imagine that you have an album
entity with id
8 and you wanted to get all the songs inside that album with the string "saucy" on their names (and, of course, you have an endpoint that allows that). You could then define the endpoint as follows:
[endpoints.match-album-songs]
route = "/albums/{id}/songs?query={query}"
method = "get"
params = ["id", "query"]
Notice how we now have two parameters. To execute this query, all we need to do is to run:
zum 8 saucy
As you probably guessed, this will send a GET
HTTP request to http://localhost:8000/albums/8/songs?query=saucy
.
Here is when the array nature of the params
key comes into play. So far, everything about this has been kind of natural. The first element to be replaced on the route
was the id
, the first element defined on the params
array was the id
and the first element that we passed to the CLI was the id
. But for some people, it might be natural to call the CLI passing the query first, and then passing the id
. Fixing this is easy. Just revert the order of the params on the params
array!
[endpoints.match-album-songs]
route = "/albums/{id}/songs?query={query}"
method = "get"
params = ["query", "id"]
Now, you can call:
zum saucy 8
The order of the params
array tells zum
which CLI parameter to associate with each route
replacement 🎉.
# The headers
of an endpoint
The headers
are defined exactly the same as the params
. They are an array of strings, and their order modifies the order that is expected from the CLI. Let's see a small example to illustrate how to use them. Imagine that you have an API that requires JWT (opens new window) authorization to GET
the songs of its catalog. Let's define that endpoint:
[endpoints.get-authorized-catalog]
route = "/catalog"
method = "get"
headers = ["Authorization"]
Now, to acquire the catalog, we would need to run:
zum get-authorized-catalog "Bearer super-secret-token"
⚠ Warning
Notice that, for the first time, we surrounded something with quotes on the CLI. The reason we did this is that, without the quotes, the console has no way of knowing if you want to pass a parameter with a space in the middle or if you want to pass multiple parameters, so it defaults to receiving the words as multiple parameters. To stop this from happening, you can surround the string in quotes, and now the whole string will be interpreted as only one parameter with the space in the middle of the string. This will be handy on future examples, so keep it in mind.
This will send a GET
request to http://localhost:8000/catalog
with the following headers:
{
"Authorization": "Bearer super-secret-token"
}
And now you have your authorization-protected music catalog!
# The body
of an endpoint
Just like params
and headers
, the body
(the body of the request) gets defined as an array:
[endpoints.create-living-being]
route = "/living-beings"
method = "post"
body = ["name", "city"]
To run this endpoint, you just need to run:
zum create-living-being Dani Santiago
This will send a POST
request to http://localhost:8000/living-beings
with the following request body:
{
"name": "Dani",
"city": "Santiago"
}
As always, order matters.
[endpoints.create-living-being]
route = "/living-beings"
method = "post"
body = ["city", "name"]
Now, to get the same result as before, you should run:
zum create-living-being Santiago Dani
But what about types? On the examples above, all the request body parameters are being sent as strings. Of course, you can cast the values to some types. The supported types are:
string
integer
float
boolean
(true
orfalse
accepted)null
(null
accepted)
To declare a type, let's imagine you have an API endpoint that receives two numbers number1
and number2
and adds them together. To describe this endpoint, you can use the following description:
[endpoints.add]
route = "/add"
method = "post"
body = [
{ name = "number1", type = "integer" },
{ name = "number2", type = "integer" }
]
Notice how we are using a "new" dictionary notation, where the name
key corresponds to the name of the body's key and the type
key corresponds to the desired type. Now, when you run
zum add 5 8
The request body will be:
{
"number1": 5,
"number2": 8
}
Keep in mind that you can mix and match the definitions. You can even define the parameter with the dictionary notation and not include its type. You could define, for example:
[endpoints.example]
route = "/example"
method = "post"
body = [
"parameter1",
{ name = "parameter2", type = "float" },
{ name = "parameter3" }
]
Now, parameter1
will be sent as a string, parameter2
will be casted as a float and parameter3
will also be sent as a string.
# Combining params
, headers
and body
Of course, sometimes you need to use some params
, some headers
and a body
. For example, if you wanted to create a song inside an authorization-protected album (a nested entity), you would need to use the album's id as a param
, the "Authorization" key inside the headers
to get the authorization and the new song's data as the body
. For this example, the song has a name
(which is a string) and a duration
in seconds (which is an integer). Let's describe this situation!
[endpoints.create-song]
route = "/albums/{id}/songs"
method = "post"
params = ["id"]
headers = ["Authorization"]
body = [
"name",
{ name = "duration", type = "integer" }
]
Now, you can call the endpoint using:
zum create-song 8 "Bearer super-secret-token" "Con Altura" 161
This will call POST /albums/8/songs
with the following headers:
{
"Authorization": "Bearer super-secret-token"
}
And the following request body:
{
"name": "Con Altura",
"duration": 161
}
As you can probably tell, zum
receives the params
first on the CLI, then the headers
and then the body
. In pythonic terms, what zum
does is that it kind of unpacks the three arrays consecutively, something like the following:
arguments = [*params, *headers, *body]
zum(arguments)
← Basic Usage The CLI →