API

Macros

RestClient.@globalconfigMacro
@globalconfig expr

Define globalconfig for the current module as expr.

This is a minor convenience macro to avoid having to write the slightly awkward RestClient.globalconfig(::Val{@__MODULE__}) = expr.

Examples

@globalconfig RequestConfig("https://api.example.com")
source
RestClient.@endpointMacro
@endpoint struct ... end
@endpoint [func(...)] -> [struct ... end] -> [payload] -> url -> resulttype

Generate an endpoint implementation from a concise shorthand form.

This macro can be applied to either an endpoint struct definition or a chain of arrow forms joined by ->. These arrow forms represent the flow of information:

  1. The creation of a request, from a function call (optional)
  2. The struct used to represent the request data (optional, autogenerated from the function call)
  3. Any payload provided with the request (if applicable)
  4. The page URL the request is directed to (relative to the base URL)
  5. The type that the response is expected to correspond to

For a basic API, this can be as simple as:

@endpoint israining(city::String) -> "checkrain/{city}" -> Bool

Using this macro is equivalent to implementing a plain struct, along with these endpoint methods:

In our @endpoint israining() ... example, in addition to defining the israining function and an (implicit) IsrainigEndpoint struct, the macro will generate the implementations pagename(rain::IsrainingEndpoint) = "checkrain/$(rain.city)" and responsetype(::IsrainingEndpoint) = Bool.

In addition to applying @endpoint to a sequence of arrow forms you can also apply it to a struct that starts with a special line of non-struct arrows.

For instance, our israining() example could be alternatively written as:

@endpoint struct IsrainingEndpoint
    israining(city) -> "checkrain/{city}" -> Bool
    city::String
end

This generates the exact same code as the initial example form.

For more information on how each component of the shorthand form is interpreted, as well as more complex examples, see the extended help section.

See also: pagename, parameters, payload, responsetype, Request, globalconfig, @globalconfig.

Extended help

Function forms

Should you chose to include a function call as the first arrow form, a function definition with a matching signature will be generated. This requires globalconfig(::Val{@__Module__}) to be defined, as is done by @globalconfig.

Without a struct form, the arguments of the function will be used to generate a struct with a derived name ($(titlecase(funcname))Endpoint), and populate its fields.

With a struct form, all arguments that match a field name will inherit the field's type and default value . All the fields of the struct not present in the signature will be added as keyword arguments. This makes function forms an easy way to prescribe positional, mandatory arguments.

Struct forms

The endpoint struct can be given explicitly, or generated from the function call. For instance, the function call api(target::String; count::Int=1) will generate this struct form:

@kwdef struct ApiEndpoint
    target::String
    count::Int = 1
end

When autogenerated, or not specified, the endpoint supertype is selected from AbstractEndpoint, SingleEndpoint, and ListEndpoint based on the supertype of the output form.

If @endpoint is directly applied to a struct, the first line of the struct is removed and parsed as a series of arrow forms.

@endpoint ApiEndpoint
    api(target; count) -> ...
    target::String
    count::Int = 1
end

This makes no difference to the generated code, and is purely a matter of personal preference (I prefer explicit structs for more complex forms).

Payload forms

HTTP methods like :post, :put, and :patch include a content payload (also referred to as the request body). The payload can be specified with a payload form that names a field of the endpoint struct, names a global variable, or is an expression is evaluated to generate the payload value (with the current endpoint available through the anaphoric variable self).

Unless otherwise specified, an endpoint that includes a payload is assumed to be a :post request (:get without).

@endpoint upload(content::String) -> content -> "create" -> Status

This is equivalent to the explicit HTTP method form:

@endpoint upload(content::String) -> content -> :post("create") -> Status

In this example content is a String, and so a :post request is made to "$BASEURL/create" with content as the payload/body. More complex types are encoded according with writepayload according to dataformat.

URL forms

The endpoint URL is represent by a string, relative to the base URL of the API (a leading / is stripped). Such paths usually consist of a path component, and optionally a query component.

"some/path/for/my/api?query=param&other=value"

Within this endpoint URL, you can include references to fields of the struct (or global variables) by surrounding them with curly braces.

"page/{somefield}?param={another}&{globalvar}=7"

Parameters with a value of nothing are omitted from the query string.

For convenience, a parameter by the same name as the field can be referred to by the field name alone (e.g. ?{var} instead of ?var={var}).

In more complex cases, arbitrary Julia code can be included in the curly braces. This code will be evaluated with the endpoint value bound the the anaphoric variable self as well as config for the request configuration.

"page/{if self.new \"new\" else \"fetch\" end}/{id}"

If that's not enough, you can also use an arbitrary expression in place of the endpoint string:

if self.create "pages/create/" else "pages/fetch/id/" * self.id end

When using expression instead of a string though, curly braces are not interpreted as field references.

Specifying the HTTP method

By default, it is guessed whether the API expects a :get or :post request based on the presence or absence of an input form. This can be explicitly specified by wrapping the URL form in an HTTP method symbol, for example:

:head("existence/{entity}")

Response type forms

It is sensible to parse the raw response into a more informative Julia representation. The response type specifies what type the response can be parsed to, which is performed by interpretresponse according to dataformat.

Often the result type will name a struct defined with [@jsondef].

The parsed result can be reinterpreted into another form with postprocess. By default, ListResponse-type responses from a ListEndpoint are restructured into a List to facilitate paging.

Examples

@endpoint struct ShuffleEndpoint <: SingleEndpoint
    "deck/{deck}/shuffle?remaining={ifelse(self.remaining, '1', '0')}" -> Deck
    deck::String
    remaining::Bool
end

This is equivalent to defining the struct by itself, and then separately defining the three basic endpoint methods.

struct ShuffleEndpoint <: SingleEndpoint
    deck::String
    remaining::Bool
end

RestClient.pagename(shuf::ShuffleEndpoint) =
    "deck/$(shuf.deck)/shuffle"
RestClient.parameters(shuf::ShuffleEndpoint) =
    ["remaining" => string(shuf.remaining)]
RestClient.responsetype(shuf::ShuffleEndpoint) = Deck
source
RestClient.@jsondefMacro
@jsondef [kind] struct ... end

Define a struct that can be used with JSON3.

This macro conveniently combines the following pieces:

  • @kwdef to define keyword constructors for the struct.
  • Custom Base.show method to show the struct with keyword arguments, omitting default values.
  • StructTypes.StructType to define the struct type, and StructTypes.names (if needed) to define the JSON field mapping.
  • RestClient.dataformat to declare that this struct is JSON-formatted.

Note the name."json_field" syntax demonstrated in the examples, that allows for declaration of the JSON object key that should be mapped to the field.

Optionally the JSON representation kind can be specified. It defaults to Struct, but can any of: Struct, Dict, Array, Vector, String, Number, Bool, Nothing.

Soft JSON3 dependency

This macro is implemented in a package extension, and so requires JSON3 to be loaded before it can be used.

Examples

@jsondef struct DocumentStatus
    exists::Bool  # Required, goes by 'exists' in JSON too
    status."document_status"::Union{String, Nothing} = nothing
    url."document_url"::Union{String, Nothing} = nothing
    age::Int = 0  # Known as 'age' in JSON too, defaults to 0
end
source
RestClient.@xmldefMacro
@xmldef struct ... end

Define a struct that can be used with XML.

This macro conveniently combines the following pieces:

  • XML deserialization
  • @kwdef to define keyword constructors for the struct.
  • RestClient.dataformat to declare that this struct is XML-formatted.

Note the name."xpath" syntax demonstrated in the examples, that allows for declaration of the (simple) XPath that should be used to extract the field. The subset of supported XPath components are:

  • nodetag to extract all immediate children with a given tag name
  • relative/node/paths
  • * to extract all children
  • text() to extract the text content of a node
  • @attr to extract the value of an attribute
  • nodetag[i] to extract the i-th child of type nodetag
  • nodetag[last()] to extract the last child of type nodetag
Soft XML dependency

This macro is implemented in a package extension, and so requires XML to be loaded before it can be used.

Examples

@xmldef struct DocumentStatus
    exists."status/@exists"::Bool
    status."status/text()"::String
    url."status/@url"::Union{String, Nothing} = nothing
    age."status/@age"::Int
end
source

Request types

RestClient.RequestType
Request{kind, E<:AbstractEndpoint}

A request to an API endpoint, with a specific configuration.

This is the complete set of information required to make a kind HTTP request to an endpoint E.

See also: AbstractEndpoint, RequestConfig.

Data flow

         ╭─╴config╶────────────────────────────╮
         │     ╎                               │
         │     ╎        ╭─▶ responsetype ╾─────┼────────────────┬──▶ dataformat ╾───╮
Request╶─┤     ╰╶╶╶╶╶╶╶╶│                      │                ╰─────────╮         │
         │              ├─▶ pagename ╾───╮     │      ╓┄┄*debug*┄┄╖       │  ╭──────╯
         │              │                ├──▶ url ╾─┬─━─▶ request ┊ ╭─▶ interpret ╾──▶ data
         ├─╴endpoint╶───┼─▶ parameters ╾─╯          │ ┊      ┠────━─┤                   │
         │              │                           ├─━──▶ cache  ┊ │             ╭─────╯
         │              ├─▶ parameters ╾────────────┤ ┊           ┊ ╰─────────╮   │
         │              │                           │ ╙┄┄┄┄┄┄┄┄┄┄┄╜        postprocess ╾──▶ result
         │             *╰─▶ payload ─▶ writepayload╶╯                           │
         │                    ╰─▶ dataformat ╾╯                                 │
         ╰─────────┬────────────────────────────────────────────────────────────╯
                   ╰────▶ validate (before initiating the request)

 * Only for POST requests   ╶╶ Optional first argument
source
RestClient.AbstractEndpointType
AbstractEndpoint

Abstract supertype for API endpoints.

Usually you will want to subtype either SingleEndpoint or ListEndpoint, which share the same interface as AbstractEndpoint but have additional semantics.

Interface

pagename([config::RequestConfig], endpoint::AbstractEndpoint) -> String
headers([config::RequestConfig], endpoint::AbstractEndpoint) -> Vector{Pair{String, String}}
parameters([config::RequestConfig], endpoint::AbstractEndpoint) -> Vector{Pair{String, String}}
responsetype(endpoint::AbstractEndpoint) -> Union{Type, Nothing}
validate([config::RequestConfig], endpoint::AbstractEndpoint) -> Bool
postprocess([response::Downloads.Response], request::Request, data) -> Any

All of these functions but pagename have default implementations.

See also: Request, dataformat, interpretresponse.

source

Response types

RestClient.SingleResponseType
SingleResponse{T}

Abstract supertype for responses that contain a single T item and (optionally) metadata.

Interface

Subtypes of SingleResponse may need to define these two methods:

contents(single::SingleResponse{T}) -> T
metadata(single::SingleResponse{T}) -> Dict{Symbol, Any}

Both have generic implementations that are sufficient for simple cases.

source
RestClient.ListResponseType
ListResponse{T}

Abstract supertype for responses that contain a list of T items and (optionally) metadata.

Interface

Subtypes of ListResponse may need to define these two methods:

contents(list::ListResponse{T}) -> Vector{T}
metadata(list::ListResponse{T}) -> Dict{Symbol, Any}

Both have generic implementations that are sufficient for simple cases.

source

Request interface

RestClient.globalconfigFunction
globalconfig(::Val{::Module}) -> RequestConfig

Return the global configuration for the given module.

This is used in @endpoint generated API functions.

Warning

Be careful not to accidentally define this function in a way that generates a new RequestConfig every time it is called, as this will cause state information (like rate limits) to be lost between requests.

source
RestClient.performFunction
perform(req::Request{kind}) -> Any

Validate and perform the request req, and return the result.

The specific behaviour is determined by the kind of the request, which corresponds to an HTTP method name (:get, :post, etc.).

source
RestClient.pagenameFunction
pagename([config::RequestConfig], endpoint::AbstractEndpoint) -> String

Return the name of the page for the given endpoint.

This is combined with the base URL and parameters to form the full URL for the request.

Note

Part of the AbstractEndpoint interface.

source
RestClient.headersFunction
headers([config::RequestConfig], endpoint::AbstractEndpoint) -> Vector{Pair{String, String}}

Return headers for the given endpoint.

The default implementation returns an empty list.

Default Content-Type

When no Content-Type header is provided and mimetype is defined for the format, the Content-Type header is set to the value from mimetype.

source
RestClient.parametersFunction
parameters([config::RequestConfig], endpoint::AbstractEndpoint) -> Vector{Pair{String, String}}

Return URI parameters for the given endpoint.

This are combined with the endpoint URL to form the full query URL.

The default implementation returns an empty list.

Note

Part of the AbstractEndpoint interface.

source
RestClient.payloadFunction
payload([config::RequestConfig], endpoint::AbstractEndpoint) -> Any

Return the payload for the given endpoint.

This is used for POST requests, and is sent as the body of the request.

Note

Part of the AbstractEndpoint interface.

source

Response interface

RestClient.validateFunction
validate([config::RequestConfig], endpoint::AbstractEndpoint) -> Bool

Check if the request to endpoint according to config is valid.

This is called before the request is made, and can be used to check if the request is well-formed. This is the appropriate place to emit warnings about potential issues with the request.

Return true if the request should proceed, false otherwise.

The default implementation always returns true.

Note

Part of the AbstractEndpoint interface.

source
RestClient.responsetypeFunction
responsetype(endpoint::AbstractEndpoint) -> Type

Return the type of the response for the given endpoint.

Together with dataformat, this is used to parse the response.

If IO (the default implementation), the response is not parsed at all.

Note

Part of the AbstractEndpoint interface.

source
RestClient.postprocessFunction
postprocess([response::Downloads.Response], request::Request, data) -> Any

Post-process the data returned by the request.

There are three generic implementations provided:

  • For SingleEndpoint requests that return a SingleResponse, the data is wrapped in a Single object.
  • For ListEndpoint requests that return a ListResponse, the data are wrapped in a List object.
  • For all other endpoints, the data is returned as-is.
Note

Part of the AbstractEndpoint interface.

source
RestClient.contentsFunction
content(response::SingleResponse{T}) -> T

Return the content of the response.

source
content(response::ListResponse{T}) -> Vector{T}

Return the items of the response.

source
RestClient.metadataFunction
metadata(response::SingleResponse) -> Dict{Symbol, Any}
metadata(response::ListResponse) -> Dict{Symbol, Any}

Return metadata for the given response.

The default implementation returns an empty dictionary.

source
RestClient.nextpageFunction
nextpage(response::List) -> Union{List, Nothing}

Fetch the next page of results after response.

If there are no more pages, or this method is not available for the given endpoint, return nothing.

source
RestClient.remainingpagesFunction
remainingpages(response::List) -> Union{Int, Nothing}

Return the number of remaining pages after response, if known.

source

Content formatting

RestClient.dataformatFunction
dataformat([endpoint::AbstractEndpoint], ::Type{T}) -> AbstractFormat

Return the expected format that T is represented by in requests to and responses from endpoint.

Using the default dataformat(::Type) method, the format is RawFormat.

A dataformat(::Type) method is automatically defined when invoking @jsondef.

source
RestClient.interpretresponseFunction
interpretresponse(data::IO, fmt::AbstractFormat, ::Type{T}) -> value::T

Interpret data as a response of type T according to fmt.

source