API
Macros
RestClient.@globalconfig — Macro
@globalconfig config [auth=scheme | auth=(scheme, policy)]Define globalconfig for the current module as config, and optionally a module-wide authentication default applied to endpoints defined with @endpoint.
This avoids having to write the slightly awkward RestClient.globalconfig(::Val{@__MODULE__}) = config by hand. Similarly, the optional auth clause defines a RestClient.globalauthdefault(::Val{@__MODULE__}) method. The auth argument is may take one of two forms:
auth = scheme: theAuthSchemeendpoints in this module use (e.g.BearerAuth()); the request'skeyis applied to it. With no policy given, the policy is:off— declare the scheme, but send it only where an endpoint opts in.auth = (scheme, policy): also set theauthpolicy, one of::off— never send auth (the scheme is declared but unused here);:optional— send thekeyif one is set, but do not require it;:required— send thekey, and reject a request that has none before sending.
See also: globalconfig, RequestConfig, authscheme.
Examples
@globalconfig RequestConfig("https://api.example.com")
# Send a Bearer token on every endpoint when a key is set:
@globalconfig RequestConfig("https://api.example.com") auth=(BearerAuth(), :optional)
# Require a key on every endpoint (mark public ones `:noauth`):
@globalconfig RequestConfig("https://api.example.com") auth=(BearerAuth(), :required)RestClient.@endpoint — Macro
@endpoint struct ... end
@endpoint [func(...)] -> [struct ... end] -> [payload] -> url -> resulttypeGenerate 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:
- The creation of a request, from a function call (optional)
- The
structused to represent the request data (optional, autogenerated from the function call) - Any payload provided with the request (if applicable)
- The page URL the request is directed to (relative to the base URL)
- 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}" -> BoolUsing this macro is equivalent to implementing a plain struct, along with these endpoint methods:
urlpathparameters(if needed)payload(optionally)responsetype
In our @endpoint israining() ... example, in addition to defining the israining function and an (implicit) IsrainigEndpoint struct, the macro will generate the implementations urlpath(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
endThis 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: urlpath, 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, with Union{..., Nothing} typed fields defaulting to nothing. This makes function forms an easy way to prescribe positional, mandatory arguments with the flexibility to fully specify the endpoint with keyword 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
endWhen autogenerated, or not specified, the endpoint supertype is selected from AbstractEndpoint, SingleEndpoint, ListEndpoint, and StreamEndpoint based on the supertype of the output form (a Stream output selects StreamEndpoint; see the streaming manual).
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
endThis 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" -> StatusThis is equivalent to the explicit HTTP method form:
@endpoint upload(content::String) -> content -> :post("create") -> StatusIn 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 endWhen 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
endThis 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.urlpath(shuf::ShuffleEndpoint) =
"deck/$(shuf.deck)/shuffle"
RestClient.parameters(shuf::ShuffleEndpoint) =
["remaining" => string(shuf.remaining)]
RestClient.responsetype(shuf::ShuffleEndpoint) = DeckRestClient.@jsondef — Macro
@jsondef [:noshow] [kind] struct ... endDefine a struct that can be read from and written as JSON.
This macro conveniently combines the following pieces:
@kwdefto define keyword constructors for the struct.- Custom
Base.showmethod to show the struct with keyword arguments, omitting default values (suppress with the:noshowoption). - Registration of JSON field-name mapping with the active JSON backend.
RestClient.dataformatto 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.
The optional kind argument selects the JSON representation, defaulting to Struct. Both backends support Struct, Dict, Array, Vector, Nothing, and String; for String, the user must define Base.string and a T(::AbstractString) constructor. The JSON3 backend additionally supports Number and Bool; with the JSON.jl backend these require user-defined StructUtils.lift / StructUtils.lower methods.
For an open-ended JSON object or array, a struct wrapping a single AbstractDict (kind=Dict) or AbstractVector (kind=Array/Vector) field decodes the whole payload into that field (recursively) and wraps it. Any other shape declares only dataformat, leaving the backend integration to the user.
@jsondef Dict struct Translations
entries::Dict{String, String} # {"en": "Hello", "fr": "Bonjour"} → Translations(...)
endThis macro is implemented in package extensions, and so requires either JSON or JSON3 to be loaded before it can be used. If both are loaded, the backend is chosen based on which of JSON or JSON3 is using'd in the calling module.
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
endRestClient.@xmldef — Macro
@xmldef struct ... endDefine a struct that can be used with XML.
This macro conveniently combines the following pieces:
- XML deserialization
@kwdefto define keyword constructors for the struct.RestClient.dataformatto 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:
nodetagto extract all immediate children with a given tag namerelative/node/paths*to extract all childrentext()to extract the text content of a node@attrto extract the value of an attributenodetag[i]to extract thei-th child of typenodetagnodetag[last()]to extract the last child of typenodetag
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
endRestClient.@formdef — Macro
@formdef struct ... endDefine a struct that serialises as FormFormat. Supports the name."form_key"::Type syntax for remapping a field's form name.
Example
@formdef struct Login
username::String
password::String
remember_me."remember-me"::Bool = false
endRestClient.@multipartdef — Macro
@multipartdef struct ... endDefine a struct that serialises as MultipartFormat. Each field is encoded with its type's own dataformat: @jsondef fields become JSON parts, Vector{UInt8} / IO fields become octet-stream parts, others become text parts.
Example
@multipartdef struct Upload
file::Vector{UInt8}
metadata::Metadata # `@jsondef struct Metadata ... end`
note::Union{String, Nothing} = nothing
endRequest types
RestClient.Request — Type
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. This consists of the request configuration and target endpoint itself. The kind and endpoint::E are put as type parameters so that they may participate in method dispatch.
See also: AbstractEndpoint, RequestConfig, perform.
Examples
julia> Request{:get}(RequestConfig(...), MyEndpoint(...))
Request{:get, MyEndpoint}(RequestConfig(...),MyEndpoint(...))Data flow
╭─╴config╶────────────────────────────╮
│ ╎ │
│ ╎ ╭─▶ responsetype ╾─────┼────────────────┬──▶ dataformat ╾───╮
Request╶─┤ ╰╶╶╶╶╶╶╶╶│ │ ╰─────────╮ │
│ ├─▶ urlpath ╾────╮ │ ╓┄┄*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 argumentRestClient.RequestConfig — Type
RequestConfigThe general configuration for a request to the API, not tied to any specific endpoint.
At a minimum, this holds the base URL of the API and a lock for handling rate-limiting.
Other fields are optional, but may be useful for changing the way requests are performed or handling authentication. The following additional fields can be provided as keyword arguments:
key::Union{Nothing, String}: The secret used to authenticate requests. How it is presented on the wire is declared by endpoints.timeout::Float64: The timeout for the request, in seconds. This is passed toDownloads.downloadand defaults toInf.cache::Bool: Whether to cache the response, using lifetime information from standard HTTP response headers. Defaults totrue.
See also: @globalconfig, Request.
Examples
julia> RequestConfig("https://api.example.com", timeout = 5, cache = false)
RequestConfig("https://api.example.com"; timeout = 5.0, cache = false)
julia> RequestConfig("https://api.example.com", key = ENV["API_SECRET_KEY"])
RequestConfig("https://api.example.com"; key = "*****", cache = true)RestClient.AbstractEndpoint — Type
AbstractEndpointAbstract 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.
Cross-cutting concerns (auth, logging, shared validation) are best expressed by defining an API-specific abstract subtype and attaching generic methods to it.
Interface
urlpath([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) -> AnyAll of these functions but urlpath have default implementations.
See also: Request, dataformat, interpretresponse.
RestClient.SingleEndpoint — Type
SingleEndpoint <: AbstractEndpointAbstract supertype for API endpoints that return a single value.
See also: AbstractEndpoint, SingleResponse, Single.
RestClient.ListEndpoint — Type
ListEndpoint <: AbstractEndpointAbstract supertype for API endpoints that return a list of values.
See also: AbstractEndpoint, ListResponse, List.
Response types
RestClient.SingleResponse — Type
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.
RestClient.Single — Type
Single{T, E<:SingleEndpoint}Holds a single value of type T returned from an API endpoint, along with request information and metadata.
See also: SingleEndpoint, SingleResponse.
RestClient.ListResponse — Type
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.
RestClient.List — Type
List{T, E<:ListEndpoint}Holds a list of values of type T returned from an API endpoint, along with request information and metadata.
See also: ListEndpoint, ListResponse, nextpage.
Errors
A failed perform raises one of these. A request that does not validate raises a MalformedRequest before anything is sent; an unsuccessful HTTP status raises a ResponseError carrying the server's response body. A transport failure (DNS, connection refused, TLS, timeout) surfaces as the underlying Downloads.RequestError.
RestClient.ResponseError — Type
ResponseError <: ExceptionAn exception type for requests that complete but return an unsuccessful HTTP status (outside 200:299).
Unlike a bare transport failure, the server's response is available: response holds the status and headers, and body holds the raw response body, which APIs routinely use to explain why a request failed.
RestClient.MalformedRequest — Type
MalformedRequest <: ExceptionAn exception type for requests that fail validation.
Streaming
RestClient.Stream — Type
Stream{T, F}A lazily-consumed stream of elements of type T, framed by F (a symbol, e.g. :SSE/:NDJSON). A peer of Single/List, selected through an endpoint's output type; F defaults to :SSE.
Iterate it to pull decoded elements as bytes arrive (blocking per frame). It is a plain value: close it from any task to end iteration cleanly (see the manual on cancellation), and a dropped, undrained stream is reclaimed by its finalizer.
See also: isstreamend.
RestClient.StreamEndpoint — Type
StreamEndpoint <: AbstractEndpointSupertype for endpoints returning a Stream. The @endpoint macro selects it for any Stream-typed output; perform dispatches on it to the streaming request path.
RestClient.SSEvent — Type
SSEvent{T}A Server-Sent Event with payload data::T (undecoded bytes off the wire, or the decoded T), the dispatching event type, and the sticky last-event-id.
Destructures as (event, data) or (event, data, id), and exposes each as a field.
RestClient.isstreamend — Function
isstreamend(endpoint::AbstractEndpoint, frame) -> BoolWhether frame terminates the stream from endpoint. Applied to the undecoded public frame before it is decoded or emitted, so a terminator is never seen by the consumer. The default is false (stop only at EOF, correct for any stream); a client library defines one method per API over the frame's public accessors:
isstreamend(::ChatEndpoint, fr::SSEvent) = fr.data == codeunits("[DONE]") # OpenAI
isstreamend(::MessagesEndpoint, fr::SSEvent) = fr.event == "message_stop" # AnthropicFraming extension interface
These methods define a stream framing. The shipped :SSE and :NDJSON framings cover the common cases; a client library implementing a new framing defines these.
RestClient.StreamFormat — Type
StreamFormat{F, E<:AbstractFormat} <: AbstractFormatThe format of a streamed body: framing F (a symbol, e.g. :SSE/:NDJSON) over an element format E. readframe/framestate/mimetype dispatch on it by F.
RestClient.readframe — Function
readframe(bs::IO, state, fmt::StreamFormat) -> frame | nothingRead one complete frame from bs, returning the framing's public frame with its undecoded payload bytes, or nothing at clean EOF. state is framestate's value, reused across calls. Dispatched on fmt's framing.
The read blocks on bs, reassembling a frame split across network reads via Base's copyuntil/read; it touches no stream internals.
RestClient.framestate — Function
framestate(fmt::StreamFormat)The reusable per-stream state readframe threads across calls. Defaults to a fresh IOBuffer (the reused scratch for delimiter framings); a framing that carries state across frames overrides it.
RestClient.framedata — Function
framedata(frame) -> payloadExtract a frame's payload: the field whose type is the frame's type parameter. A framing whose payload is located differently can override this.
RestClient.remake — Function
remake(frame, data) -> frameReturn a copy of frame with its payload replaced by data (re-typed to data's type), preserving the other fields. This is the decode step: {bytes} → {T}. The payload field is located as in framedata.
RestClient.elementpayload — Function
elementpayload(::Type{Elt}) -> TypeThe decode target for a stream element type. Defaults to Elt; a framing whose element type wraps its payload (e.g. SSEvent{T} ⇒ T) overrides it.
Request interface
RestClient.globalconfig — Function
globalconfig(::Val{::Module}) -> RequestConfigReturn the global configuration for the given module.
This is used in @endpoint generated API functions.
See also: @globalconfig, RequestConfig.
RestClient.validate — Function
validate([config::RequestConfig], endpoint::AbstractEndpoint) ->
Union{Nothing, AbstractString, AbstractVector{<:AbstractString}}Check if the request to endpoint according to config is valid.
The return value determines whether the request proceeds and, if not, what problem to report:
nothingorString[]means the request is valid and should proceed- a string or vector of strings means the request is invalid and should be aborted, with the string(s) as the reason(s)
This is also the appropriate place to emit warnings about non-critical potential issues with the request, using @warn or @debug.
The default implementation returns nothing (proceed).
RestClient.perform — Function
perform(req::Request{kind}) -> AnyValidate 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.).
See also: Request.
RestClient.urlpath — Function
urlpath([config::RequestConfig], endpoint::AbstractEndpoint) -> StringReturn 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.
RestClient.headers — Function
headers([config::RequestConfig], endpoint::AbstractEndpoint) -> Vector{Pair{String, String}}Return headers for the given endpoint.
The default implementation returns an empty list.
RestClient.parameters — Function
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.
RestClient.payload — Function
payload([config::RequestConfig], endpoint::AbstractEndpoint) -> AnyReturn the payload for the given endpoint.
This is used for POST requests, and is sent as the body of the request.
RestClient.authscheme — Function
authscheme(endpoint::AbstractEndpoint) -> AuthSchemeReturn how endpoint authenticates: the AuthScheme describing where the request's secret (config.key) is placed on the wire. The library applies it to key automatically, so a standard scheme needs no headers method.
The default is NoAuth. Declare a scheme once per API, typically on a shared endpoint supertype:
RestClient.authscheme(::MyAPIEndpoint) = BearerAuth() # Authorization: Bearer <key>
RestClient.authscheme(::OtherEndpoint) = HeaderAuth("X-API-Key")For a scheme the provided ones do not cover (request signing, OAuth flows), read config.key in a headers method directly instead.
RestClient.authpolicy — Function
authpolicy(endpoint::AbstractEndpoint) -> SymbolHow endpoint uses authentication, one of:
:off— never send auth (the default);:optional— apply theauthschemeto the request'skeyif one is set, but do not require it;:required— apply it, and reject a request whoseconfig.keyisnothingduring validation, before sending, rather than relying on the server to return 401.
The @endpoint macro sets this from the module's @globalconfig auth= default, or per endpoint via an :auth (required), :authoption (optional), or :noauth (off) marker.
Authentication
An endpoint declares which scheme it authenticates with via an authscheme method, and whether a key is off/optional/required via authpolicy; the secret itself is the RequestConfig's key. Both are usually set in one place with @globalconfig's auth= option.
RestClient.AuthScheme — Type
AuthSchemeSupertype for authentication schemes: how an API presents its secret on the wire. A scheme holds only API-specific shape (e.g. a header name), never the secret — the secret is the key of a RequestConfig. Declare a scheme for an endpoint with authscheme, and it is applied to key on every request.
The provided schemes are BearerAuth, BasicAuth, HeaderAuth, QueryAuth, and NoAuth (the default).
Non-standard authentication schemes can be handled by adjusting headers and parameters of endpoints on a case-by-case basis.
RestClient.BearerAuth — Type
BearerAuth() <: AuthSchemeBearer-token authentication: the key is sent as Authorization: Bearer <key>. The common scheme for OAuth2 access tokens and most modern token-based APIs.
RestClient.BasicAuth — Type
BasicAuth() <: AuthSchemeHTTP Basic authentication (RFC 7617): the key (a "username:password" string) is sent as Authorization: Basic <base64>. Set key = "user:pass" on the config.
RestClient.HeaderAuth — Type
HeaderAuth(name) <: AuthSchemeThe key is sent in the named request header, e.g. HeaderAuth("X-API-Key"). For APIs that authenticate with a custom header rather than Authorization.
RestClient.QueryAuth — Type
QueryAuth(name) <: AuthSchemeThe key is sent as the named URL query parameter, e.g. QueryAuth("api_key"). For APIs that authenticate via the query string. Prefer a header scheme where the API allows it: query parameters are more likely to be logged by proxies and servers.
RestClient.NoAuth — Type
NoAuth() <: AuthSchemeNo authentication: the default authscheme. Contributes nothing, regardless of key.
Response interface
RestClient.responsetype — Function
responsetype(endpoint::AbstractEndpoint) -> TypeReturn the type of the response for the given endpoint.
Together with dataformat, this is used to parse the response.
If Vector{UInt8} (the default implementation), the response data is provided raw.
RestClient.postprocess — Function
postprocess([response::Downloads.Response], request::Request, data) -> AnyPost-process the data returned by the request.
There are three generic implementations provided:
- For
SingleEndpointrequests that return aSingleResponse, thedatais wrapped in aSingleobject. - For
ListEndpointrequests that return aListResponse, thedataare wrapped in aListobject. - For all other endpoints, the data is returned as-is.
RestClient.contents — Function
contents(response::SingleResponse{T}) -> TReturn the content of the response.
contents(response::ListResponse{T}) -> Vector{T}Return the items of the response.
RestClient.metadata — Function
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.
Pagination interface
RestClient.nextpage — Function
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.
The generic List implementation returns nothing.
See the extended help for implementation suggestions.
Extended help
There are two more methods that can be implemented to support pagination:
nextpage(request::Request) -> Union{Request, Nothing}
nextpage(endpoint::AbstractEndpoint) -> Union{AbstractEndpoint, Nothing}When implementing this method, a good approach to follow is:
- Implement
thispagenumberandremainingpages. These are both optional, but pass through the information to theListdisplay method, which is nice to have. - Consider how the request should be modified to fetch the next page, and accordingly implement
nextpagefor either the endpoint, request, or list. - Create a modified endpoint or request using
RestClient.setfield - When implementing the
List-based method, callperformwith the modified request
For example, randomuser.me has a paginated API, and so you could implement support for it with:
@globalconfig RequestConfig("https://randomuser.me/api/")
@jsondef struct User
gender::String
name::@NamedTuple{title::String, first::String, last::String}
email::String
location::@NamedTuple{country::String, state::String, city::String}
# ...and lots more
end
@jsondef struct UsersResponse <: ListResponse{User}
results::Vector{User}
info::@NamedTuple{seed::String, page::Int, results::Int, version::String}
end
@endpoint struct UsersEndpoint
users(; page, results, seed) -> "?{page}&{results}&{seed}" -> UsersResponse
page::Int = 1
results::Union{Int, Nothing} = 5
seed::Union{String, Nothing}
end
# Pagination implementation
RestClient.thispagenumber(users::UsersEndpoint) = users.page
RestClient.nextpage(users::UsersEndpoint) =
RestClient.setfield(users, :page, users.page + 1)This can then be used like so:
julia> userlist = users(seed = "abc")
List{User} holding 5 items, page 1:
[...]
julia> nextpage(userlist)
List{User} holding 5 items, page 2:
[...]RestClient.thispagenumber — Function
thispagenumber(response::List) -> Union{Int, Nothing}Return the current page number of response, if known.
The generic ::List implementation returns nothing.
RestClient.remainingpages — Function
remainingpages(response::List) -> Union{Int, Nothing}Return the number of remaining pages after response, if known.
The generic ::List implementation returns nothing.
Caching interface
RestClient.cachelifetime — Function
cachelifetime(req::Request, res::Downloads.Response) -> Union{DateTime, Integer, Nothing}Determine the expiry time of a cached response, based on the request and response.
Specialisation
To specialise this function for a specific endpoint, define one of the following methods:
cachelifetime(req::Request{kind, <:AbstractEndpoint}, res::Downloads.Response)
cachelifetime([conf::RequestConfig], endpoint::AbstractEndpoint, res::Downloads.Response)Content formatting
RestClient.AbstractFormat — Type
AbstractFormatAbstract supertype for response formats.
Typically, you will want to create a singleton subtype and then implement the following methods for it:
- An
interpretresponsemethod so a response in that format can be parsed/interpreted as a specific type. - A
writepayloadmethod so data can be sent in that format. - A
mimetypemethod so that appropriate headers can be set when sending or receiving data in this format.
See also: RawFormat, JSONFormat.
RestClient.RawFormat — Type
RawFormat <: AbstractFormatSingleton type for raw response formats.
RestClient.JSONFormat — Type
JSONFormat{backend} <: AbstractFormatSingleton type for JSON request/response formats. The backend parameter selects the JSON implementation: :json for JSON.jl, :json3 for JSON3.jl.
The zero-argument constructor JSONFormat() picks an available backend at the time of construction.
RestClient.XMLFormat — Type
XMLFormat <: AbstractFormatSingleton type for XML request/response formats.
RestClient.FormFormat — Type
FormFormat <: AbstractFormatapplication/x-www-form-urlencoded payloads.
RestClient.MultipartFormat — Type
MultipartFormat <: AbstractFormatmultipart/form-data payloads. Each property of the serialised value becomes a MIME part, encoded with the field type's own dataformat.
RestClient.dataformat — Function
dataformat([endpoint::AbstractEndpoint], ::Type{T}) -> AbstractFormatReturn 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.
RestClient.interpretresponse — Function
interpretresponse(data::AbstractVector{UInt8}, fmt::AbstractFormat, ::Type{T}) -> value::TInterpret data as a response of type T according to fmt.
For JSONFormat, the backend-specific methods are implemented in package extensions (JSONExt for JSON.jl, JSON3Ext for JSON3.jl).
RestClient.writepayload — Function
writepayload(dest::IO, fmt::AbstractFormat, data)Write data to dest according to fmt.
RestClient.mimetype — Function
mimetype(::Type{<:AbstractFormat}) -> Union{String, Nothing}Return the MIME type for the given format, if known.