Package 'nectar'

Title: A Framework for Web API Packages
Description: An opinionated framework for use within api-wrapping R packages.
Authors: Jon Harmon [aut, cre, cph] , R Consortium [fnd]
Maintainer: Jon Harmon <[email protected]>
License: MIT + file LICENSE
Version: 0.0.0.9005
Built: 2025-01-31 22:29:43 UTC
Source: https://github.com/jonthegeek/nectar

Help Index


Send a request to an API

Description

[Questioning]

This function implements an opinionated framework for making API calls. It is intended to be used inside an API client package. It serves as a wrapper around the req_ family of functions, such as httr2::request(), as well as httr2::req_perform() and httr2::req_perform_iterative(), and, by default, httr2::resp_body_json().

Usage

call_api(
  base_url,
  ...,
  path = NULL,
  query = NULL,
  body = NULL,
  mime_type = NULL,
  method = NULL,
  auth_fn = NULL,
  auth_args = list(),
  response_parser = resp_tidy,
  response_parser_args = list(),
  next_req_fn = NULL,
  max_reqs = Inf,
  max_tries_per_req = 3,
  additional_user_agent = NULL
)

Arguments

base_url

(⁠length-1 character⁠) The part of the url that is shared by all calls to the API. In some cases there may be a family of base URLs, from which you will need to choose one.

...

These dots are for future extensions and must be empty.

path

(character or list) The route to an API endpoint. Optionally, a list or character vector with the path as one or more unnamed arguments (which will be concatenated with "/") plus named arguments to glue::glue() into the path.

query

(character or list) An optional list or character vector of parameters to pass in the query portion of the request. Can also include a .multi argument to pass to httr2::req_url_query() to control how elements containing multiple values are handled.

body

(multiple types) An object to use as the body of the request. If any component of the body is a path, pass it through fs::path() or otherwise give it the class "fs_path" to indicate that it is a path.

mime_type

(⁠length-1 character⁠) The mime type of any files present in the body. Some APIs allow you to leave this as NULL for them to guess.

method

(⁠length-1 character⁠, optional) If the method is something other than GET or POST, supply it. Case is ignored.

auth_fn

(function) A function to use to authenticate the request. By default (NULL), no authentication is performed.

auth_args

(list) An optional list of arguments to the auth_fn function.

response_parser

(function) A function to parse the server response (resp). Defaults to httr2::resp_body_json(), since JSON responses are common. Set this to NULL to return the raw response from httr2::req_perform().

response_parser_args

(list) Additional arguments to pass to the response_parser.

next_req_fn

(function) An optional function that takes the previous response (resp) and request (req), and returns a new request. This function is passed as next_req in a call to httr2::req_perform_iterative(). This function can usually be generated using one of the iteration helpers described in httr2::iterate_with_offset(). By default, choose_pagination_fn() is used to check for a pagination policy (see req_pagination_policy()), and returns NULL if no such policy is defined.

max_reqs

(⁠length-1 integer⁠) The maximum number of separate requests to perform. Passed to the max_reqs argument of httr2::req_perform_iterative() when next_req is supplied. You will mostly likely want to change the default value (2) to Inf after you validate that the request works.

max_tries_per_req

(⁠length-1 integer⁠) The maximum number of times to attempt each individual request. Passed to the max_tries argument of httr2::req_retry().

additional_user_agent

(⁠length-1 character⁠) A string to identify where a request is coming from. We automatically include information about your package and nectar, but use this to provide additional details. Default NULL.

Value

The response from the API, parsed by the response_parser.

See Also

req_prepare(), req_perform_opinionated(), and resp_parse() for finer control of the process.


Extract a pagination policy from a request

Description

If a request has a pagination policy defined by req_pagination_policy(), extract the pagination_fn from that policy. Otherwise return NULL.

Usage

choose_pagination_fn(req)

Arguments

req

(httr2_request) A httr2::request() object.

Value

The pagination function, or NULL.

Examples

req <- httr2::request("https://example.com")
req <- req_pagination_policy(req, httr2::iterate_with_offset("page"))
choose_pagination_fn(req)

Discard empty elements

Description

Discard empty elements in nested lists.

Usage

compact_nested_list(lst)

Arguments

lst

A (nested) list to filter.

Value

The list, minus empty elements and branches.

Examples

x <- list(
  a = list(
    b = letters,
    c = NULL,
    d = 1:5
  ),
  e = NULL,
  f = 1:3
)
compact_nested_list(x)

Use a provided function

Description

When constructing API calls programmatically, you may encounter situations where an upstream task should indicate which function to apply. For example, one endpoint might use a special auth function that isn't used by other endpoints. This function exists to make coding such situations easier.

Usage

do_if_fn_defined(x, fn = NULL, ..., call = rlang::caller_env())

Arguments

x

An object to potentially modify, such as a httr2::request() object.

fn

A function to apply to x. If fn is NULL, x is returned unchanged.

...

Additional arguments to pass to fn.

call

(environment) The environment from which a function was called, e.g. rlang::caller_env() (the default). The environment will be mentioned in error messages as the source of the error. This argument is particularly useful for functions that are intended to be called as utilities inside other functions.

Value

The object, potentially modified.

Examples

build_api_req <- function(endpoint, auth_fn = NULL, ...) {
  req <- httr2::request("https://example.com")
  req <- httr2::req_url_path_append(req, endpoint)
  do_if_fn_defined(req, auth_fn, ...)
}

# Most endpoints of this API do not require authentication.
unsecure_req <- build_api_req("unsecure_endpoint")
unsecure_req$headers

# But one endpoint requires authentication.
secure_req <- build_api_req(
  "secure_endpoint", httr2::req_auth_bearer_token, "secret-token"
)
secure_req$headers$Authorization

Iteration helper for json cursors

Description

This function is intended as a replacement for httr2::iterate_with_cursor() for the common situation where the response body is json, and the cursor can be "empty" in various ways. Even within a single API, some endpoints might return a NULL next_cursor to indicate that there are no more pages of results, while other endpoints might return "". This function normalizes all such results to NULL.

Usage

iterate_with_json_cursor(param_name = "cursor", next_cursor_path)

Arguments

param_name

(⁠length-1 character⁠) The name of the cursor parameter in the request.

next_cursor_path

(character) A vector indicating the path to the next_cursor element in the body of the response. For example, for the Slack API, this value is c("response_metadata", "next_cursor"), while for the Crossref Unified Resource API, this value is "next-cursor".

Value

A function that takes the response and the previous request, and returns the next request if there are more results.


Authenticate with an API key

Description

Many APIs provide API keys that can be used to authenticate requests (or, often, provide other information about the user). This function helps to apply those keys to requests.

Usage

req_auth_api_key(
  req,
  parameter_name,
  ...,
  api_key = NULL,
  location = c("header", "query", "cookie"),
  call = rlang::caller_env()
)

Arguments

req

(httr2_request) A httr2::request() object.

parameter_name

(⁠length-1 character⁠) The name of the parameter to use in the header, query, or cookie.

...

These dots are for future extensions and must be empty.

api_key

(⁠length-1 character⁠ or NULL) The API key to use. If this value is NULL, req is returned unchanged.

location

(⁠length-1 character⁠) Where the API key should be passed. One of "header" (default), "query", or "cookie".

call

(environment) The environment from which a function was called, e.g. rlang::caller_env() (the default). The environment will be mentioned in error messages as the source of the error. This argument is particularly useful for functions that are intended to be called as utilities inside other functions.

Value

A httr2::request() object with additional class nectar_request.


Setup a basic API request

Description

For a given API, the base_url and user agent will generally be the same for every call to that API. Use this function to prepare that piece of the request once for easy reuse.

Usage

req_init(
  base_url,
  ...,
  additional_user_agent = NULL,
  call = rlang::caller_env()
)

Arguments

base_url

(⁠length-1 character⁠) The part of the url that is shared by all calls to the API. In some cases there may be a family of base URLs, from which you will need to choose one.

...

These dots are for future extensions and must be empty.

additional_user_agent

(⁠length-1 character⁠) A string to identify where a request is coming from. We automatically include information about your package and nectar, but use this to provide additional details. Default NULL.

call

(environment) The environment from which a function was called, e.g. rlang::caller_env() (the default). The environment will be mentioned in error messages as the source of the error. This argument is particularly useful for functions that are intended to be called as utilities inside other functions.

Value

A httr2::request() object with additional class nectar_request.

See Also

Other opinionated request functions: req_modify(), req_pagination_policy(), req_prepare(), req_tidy_policy()

Examples

req_init("https://example.com")
req_init(
  "https://example.com",
  additional_user_agent = "my_api_client (https://my.api.client)"
)

Modify an API request for a particular endpoint

Description

Modify the basic request for an API by adding a path and any other path-specific properties.

Usage

req_modify(
  req,
  ...,
  path = NULL,
  query = NULL,
  body = NULL,
  mime_type = NULL,
  method = NULL,
  call = rlang::caller_env()
)

Arguments

req

(httr2_request) A httr2::request() object.

...

These dots are for future extensions and must be empty.

path

(character or list) The route to an API endpoint. Optionally, a list or character vector with the path as one or more unnamed arguments (which will be concatenated with "/") plus named arguments to glue::glue() into the path.

query

(character or list) An optional list or character vector of parameters to pass in the query portion of the request. Can also include a .multi argument to pass to httr2::req_url_query() to control how elements containing multiple values are handled.

body

(multiple types) An object to use as the body of the request. If any component of the body is a path, pass it through fs::path() or otherwise give it the class "fs_path" to indicate that it is a path.

mime_type

(⁠length-1 character⁠) The mime type of any files present in the body. Some APIs allow you to leave this as NULL for them to guess.

method

(⁠length-1 character⁠, optional) If the method is something other than GET or POST, supply it. Case is ignored.

call

(environment) The environment from which a function was called, e.g. rlang::caller_env() (the default). The environment will be mentioned in error messages as the source of the error. This argument is particularly useful for functions that are intended to be called as utilities inside other functions.

Value

A httr2::request() object with additional class nectar_request.

See Also

Other opinionated request functions: req_init(), req_pagination_policy(), req_prepare(), req_tidy_policy()

Examples

req_base <- req_init("https://example.com")
req_modify(req_base, path = c("specific/{path}", path = "endpoint"))
req_modify(req_base, query = c("param1" = "value1", "param2" = "value2"))

Define a pagination policy for a request

Description

APIs generally have a specified method for requesting multiple pages of results (or sometimes two or three methods). The methods are sometimes documented within a given endpoint, and sometimes documented at the "top" of the documentation. Use this function to attach a pagination policy to a request, so that req_perform_opinionated() can automatically handle pagination.

Usage

req_pagination_policy(req, pagination_fn, call = rlang::caller_env())

Arguments

req

(httr2_request) A httr2::request() object.

pagination_fn

(function) A function that takes the previous response (resp) to generate the next request in a call to httr2::req_perform_iterative(). This function can usually be generated using one of the iteration helpers described in httr2::iterate_with_offset(). This function will be extracted from the request by req_perform_opinionated() and passed on as next_req to httr2::req_perform_iterative().

call

(environment) The environment from which a function was called, e.g. rlang::caller_env() (the default). The environment will be mentioned in error messages as the source of the error. This argument is particularly useful for functions that are intended to be called as utilities inside other functions.

Value

A httr2::request() object with additional class nectar_request.

See Also

Other opinionated request functions: req_init(), req_modify(), req_prepare(), req_tidy_policy()

Examples

req <- httr2::request("https://example.com")
req_pagination_policy(req, httr2::iterate_with_offset("page"))

Perform a request with opinionated defaults

Description

This function ensures that a request has httr2::req_retry() applied, and then performs the request, using either httr2::req_perform_iterative() (if a next_req_fn function is supplied) or httr2::req_perform() (if not).

Usage

req_perform_opinionated(
  req,
  ...,
  next_req_fn = choose_pagination_fn(req),
  max_reqs = 2,
  max_tries_per_req = 3
)

Arguments

req

The first request to perform.

...

These dots are for future extensions and must be empty.

next_req_fn

(function) An optional function that takes the previous response (resp) and request (req), and returns a new request. This function is passed as next_req in a call to httr2::req_perform_iterative(). This function can usually be generated using one of the iteration helpers described in httr2::iterate_with_offset(). By default, choose_pagination_fn() is used to check for a pagination policy (see req_pagination_policy()), and returns NULL if no such policy is defined.

max_reqs

(⁠length-1 integer⁠) The maximum number of separate requests to perform. Passed to the max_reqs argument of httr2::req_perform_iterative() when next_req is supplied. You will mostly likely want to change the default value (2) to Inf after you validate that the request works.

max_tries_per_req

(⁠length-1 integer⁠) The maximum number of times to attempt each individual request. Passed to the max_tries argument of httr2::req_retry().

Value

A list of httr2::response() objects, one for each request performed. The list has additional class nectar_responses.


Append package information to user agent

Description

Add information about nectar and the calling package (if called from a package) to the user agent string.

Usage

req_pkg_user_agent(
  req,
  pkg_name = get_pkg_name(call),
  pkg_url = NULL,
  call = rlang::caller_env()
)

Arguments

req

(httr2_request) A httr2::request() object.

pkg_name

(⁠length-1 character⁠) The name of the calling package. This will usually be automatically determined based on the source of the call.

pkg_url

(⁠length-1 character⁠) A url for information about the calling package (default NULL).

call

(environment) The environment from which a function was called, e.g. rlang::caller_env() (the default). The environment will be mentioned in error messages as the source of the error. This argument is particularly useful for functions that are intended to be called as utilities inside other functions.

Value

A httr2::request() object with additional class nectar_request.

Examples

req <- httr2::request("https://example.com")
req$options$useragent
req_pkg_user_agent(req)$options$useragent
req_pkg_user_agent(req, "stbl")$options$useragent

Prepare a request for an API

Description

This function implements an opinionated framework for preparing an API request. It is intended to be used inside an API client package. It serves as a wrapper around the req_ family of functions, such as httr2::request().

Usage

req_prepare(
  base_url,
  ...,
  path = NULL,
  query = NULL,
  body = NULL,
  mime_type = NULL,
  method = NULL,
  additional_user_agent = NULL,
  auth_fn = NULL,
  auth_args = list(),
  tidy_fn = NULL,
  tidy_args = list(),
  pagination_fn = NULL,
  call = rlang::caller_env()
)

Arguments

base_url

(⁠length-1 character⁠) The part of the url that is shared by all calls to the API. In some cases there may be a family of base URLs, from which you will need to choose one.

...

These dots are for future extensions and must be empty.

path

(character or list) The route to an API endpoint. Optionally, a list or character vector with the path as one or more unnamed arguments (which will be concatenated with "/") plus named arguments to glue::glue() into the path.

query

(character or list) An optional list or character vector of parameters to pass in the query portion of the request. Can also include a .multi argument to pass to httr2::req_url_query() to control how elements containing multiple values are handled.

body

(multiple types) An object to use as the body of the request. If any component of the body is a path, pass it through fs::path() or otherwise give it the class "fs_path" to indicate that it is a path.

mime_type

(⁠length-1 character⁠) The mime type of any files present in the body. Some APIs allow you to leave this as NULL for them to guess.

method

(⁠length-1 character⁠, optional) If the method is something other than GET or POST, supply it. Case is ignored.

additional_user_agent

(⁠length-1 character⁠) A string to identify where a request is coming from. We automatically include information about your package and nectar, but use this to provide additional details. Default NULL.

auth_fn

(function) A function to use to authenticate the request. By default (NULL), no authentication is performed.

auth_args

(list) An optional list of arguments to the auth_fn function.

tidy_fn

(function) A function that will be invoked by resp_tidy() to tidy the response.

tidy_args

(list) A list of additional arguments to pass to tidy_fn.

pagination_fn

(function) A function that takes the previous response (resp) to generate the next request in a call to httr2::req_perform_iterative(). This function can usually be generated using one of the iteration helpers described in httr2::iterate_with_offset(). This function will be extracted from the request by req_perform_opinionated() and passed on as next_req to httr2::req_perform_iterative().

call

(environment) The environment from which a function was called, e.g. rlang::caller_env() (the default). The environment will be mentioned in error messages as the source of the error. This argument is particularly useful for functions that are intended to be called as utilities inside other functions.

Value

A httr2::request() object with additional class nectar_request.

See Also

Other opinionated request functions: req_init(), req_modify(), req_pagination_policy(), req_tidy_policy()


Define a tidy policy for a request

Description

API responses generally follow a structured format. Use this function to define a policy that will be used by resp_tidy() to extract the relevant portion of a response and wrangle it into a desired format.

Usage

req_tidy_policy(
  req,
  tidy_fn = resp_body_auto,
  tidy_args = list(),
  call = rlang::caller_env()
)

Arguments

req

(httr2_request) A httr2::request() object.

tidy_fn

(function) A function that will be invoked by resp_tidy() to tidy the response.

tidy_args

(list) A list of additional arguments to pass to tidy_fn.

call

(environment) The environment from which a function was called, e.g. rlang::caller_env() (the default). The environment will be mentioned in error messages as the source of the error. This argument is particularly useful for functions that are intended to be called as utilities inside other functions.

Value

A httr2::request() object with additional class nectar_request.

See Also

Other opinionated request functions: req_init(), req_modify(), req_pagination_policy(), req_prepare()

Examples

req <- httr2::request("https://example.com")
req_tidy_policy(req, httr2::resp_body_json, list(simplifyVector = TRUE))

Automatically choose a body parser

Description

Use the Content-Type header (extracted using httr2::resp_content_type()) of a response to automatically choose and apply a body parser, such as httr2::resp_body_json() or resp_body_csv().

Usage

resp_body_auto(resp)

Arguments

resp

(httr2_response) A single httr2::response() object (as returned by httr2::req_perform()).

Value

The parsed response body.


Extract tabular data from response body

Description

Extract tabular data in comma-separated or tab-separated format from a response body.

Usage

resp_body_csv(resp, check_type = TRUE)

resp_body_tsv(resp, check_type = TRUE)

Arguments

resp

(httr2_response) A single httr2::response() object (as returned by httr2::req_perform()).

check_type

(⁠length-1 logical⁠) Whether to check that the response has the expected content type. Set to FALSE if the response is not specifically tagged as the proper type.

Value

The parsed response body as a data frame.


Extract response body into list

Description

Extract response body into list

Usage

resp_body_separate(resp, resp_body_fn = resp_body_auto)

Arguments

resp

(httr2_response) A single httr2::response() object (as returned by httr2::req_perform()).

resp_body_fn

(function) A function to extract the body of the response. Default: resp_body_auto().

Value

The parsed response body wrapped in a list(). This is useful for things like raw vectors that you wish to parse with httr2::resps_data().


Parse one or more responses

Description

[Questioning]

If you have implemented the full nectar framework, use resp_tidy() directly to parse your responses. We may continue to support resp_parse(), but it is most useful as a bridge to the full framework.

httr2 provides two methods for performing requests: httr2::req_perform(), which returns a single httr2::response() object, and httr2::req_perform_iterative(), which returns a list of httr2::response() objects. This function automatically determines whether a single response or multiple responses have been returned, and parses the responses appropriately.

Usage

resp_parse(resps, ...)

## Default S3 method:
resp_parse(
  resps,
  ...,
  arg = rlang::caller_arg(resps),
  call = rlang::caller_env()
)

## S3 method for class 'httr2_response'
resp_parse(resps, ..., response_parser = resp_tidy)

Arguments

resps

(httr2_response, nectar_responses, or list) A single httr2::response() object (as returned by httr2::req_perform()) or a list of such objects (as returned by req_perform_opinionated() or httr2::req_perform_iterative()).

...

Additional arguments passed on to the response_parser function (in addition to resps).

arg

(⁠length-1 character⁠) An argument name as a string. This argument will be mentioned in error messages as the input that is at the origin of a problem.

call

(environment) The environment from which a function was called, e.g. rlang::caller_env() (the default). The environment will be mentioned in error messages as the source of the error. This argument is particularly useful for functions that are intended to be called as utilities inside other functions.

response_parser

(function) A function to parse the server response (resp). Defaults to httr2::resp_body_json(), since JSON responses are common. Set this to NULL to return the raw response from httr2::req_perform().

Value

The response parsed by the response_parser. If resps was a list, the parsed responses are concatenated when possible. Unlike httr2::resps_data, this function does not concatenate raw vector responses.


Extract and clean an API response

Description

API responses generally follow a structured format. Use this function to extract the relevant portion of a response, and wrangle it into a desired format. This function is most useful when the response was fetched with a request that includes a tidying policy defined via req_tidy_policy().

Usage

resp_tidy(resps)

Arguments

resps

(httr2_response, nectar_responses, or list) A single httr2::response() object (as returned by httr2::req_perform()) or a list of such objects (as returned by req_perform_opinionated() or httr2::req_perform_iterative()).

Value

The extracted and cleaned response, or, for a list of responses, those responses cleaned then concatenated via httr2::resps_data(). By default, the response is processed with resp_body_auto().

See Also

resp_tidy_json() for an opinionated response parser for JSON responses, resp_body_auto() (etc) for a family of response parsers that attempts to automatically select the appropriate parser based on the response content type, httr2::resp_body_raw() (etc) for the underlying httr2 response parsers, and resp_parse() for an alternative approach to dealing with responses (particularly useful if the request does not include a resp_tidy policy).


Extract and clean a JSON API response

Description

Parse the body of a response with httr2::resp_body_json(), extract a named subset of that body, and tidy the result with tibblify::tibblify().

Usage

resp_tidy_json(resp, spec = NULL, unspecified = "list", subset_path = NULL)

Arguments

resp

(httr2_response) A single httr2::response() object (as returned by httr2::req_perform()).

spec

(tspec or NULL) A specification used by tibblify::tibblify() to parse the extracted body of resp. When spec is NULL (the default), tibblify::tibblify() will attempt to guess a spec.

unspecified

(⁠length-1 character⁠) A string that describes what happens if the extracted body of resp contains fields that are not specified in spec. While tibblify::tibblify() defaults to NULL for this value, we set it to list so that the body will still parse when resp contains extra data without throwing errors.

subset_path

(character) An optional vector indicating the path to the "real" object within the body of resp. For example, many APIs return a body with information about the status of the response, cache information, perhaps pagination information, and then the actual data in a field such as data. If the desired part of the response body is in data$objects, the value of this argument should be c("data", "object").

Value

The tibblified response body.


Error informatively for unknown response types

Description

If you have not defined a parser for a response type, use this function to return useful information to help construct a parser.

Usage

resp_tidy_unknown(resp, call = rlang::caller_env())

Arguments

resp

(httr2_response) A single httr2::response() object (as returned by httr2::req_perform()).

call

(environment) The environment from which a function was called, e.g. rlang::caller_env() (the default). The environment will be mentioned in error messages as the source of the error. This argument is particularly useful for functions that are intended to be called as utilities inside other functions.

Value

This function always throws an error. The error lists the names of the response pieces after parsing with resp_body_auto().


Ensure an argument is a length-1 character

Description

Calls to APIs often require a string argument. This function ensures that those arguments are length-1, non-NA character vectors, or length-1, non-NA vectors that can be coerced to character vectors. This is intended to ensure that calls to the API will not fail with predictable errors, thus avoiding unnecessary internet traffic.

Usage

stabilize_string(
  x,
  ...,
  regex = NULL,
  arg = rlang::caller_arg(x),
  call = rlang::caller_env()
)

Arguments

x

The argument to stabilize.

...

Arguments passed on to stbl::stabilize_chr_scalar

x_class

Character. The class name of x to use in error messages. Use this if you remove a special class from x before checking its coercion, but want the error message to match the original class.

regex

Character scalar. An optional regex pattern to compare the value(s) of x against. If a complex regex pattern throws an error, try installing the stringi package with install.packages("stringi").

arg

An argument name as a string. This argument will be mentioned in error messages as the input that is at the origin of a problem.

call

The execution environment of a currently running function, e.g. caller_env(). The function will be mentioned in error messages as the source of the error. See the call argument of abort() for more information.

Value

x coerced to a length-1 character vector, if possible.

Examples

stabilize_string("a")
stabilize_string(1.1)
x <- letters
try(stabilize_string(x))
x <- NULL
try(stabilize_string(x))
x <- character()
try(stabilize_string(x))
x <- NA
try(stabilize_string(x))

Normalize a URL

Description

This function normalizes a URL by adding a trailing slash to the base if it is missing. It is mainly for testing and other comparisons.

Usage

url_normalize(url)

Arguments

url

A URL to normalize.

Value

A normalized URL

Examples

identical(
  url_normalize("https://example.com"),
  url_normalize("https://example.com/")
)
identical(
  url_normalize("https://example.com?param=value"),
  url_normalize("https://example.com/?param=value")
)

Add path elements to a URL

Description

Append zero or more path elements to a URL without duplicating "/" characters. Based on httr2::req_url_path_append().

Usage

url_path_append(url, ...)

Arguments

url

A URL to modify.

...

Path elements to append, as strings.

Value

A modified URL.

Examples

url_path_append("https://example.com", "api", "v1", "users")
url_path_append("https://example.com/", "/api", "/v1", "/users")
url_path_append("https://example.com/", "/api/v1/users")