Nothing
#' API Service Base Class
#'
#' @description
#' Base R6 class for creating API service wrappers. This class provides a
#' foundation for building service-specific API clients with authentication,
#' endpoint management, and configuration.
#'
#' @export
api_service <- R6::R6Class(
classname = "api_service",
lock_objects = FALSE,
cloneable = FALSE,
private = list(
.chain = NULL,
.endpoints = NULL,
.config = NULL
),
public = list(
#' @field .client An [api_client] instance for making API requests
.client = NULL,
#' @description
#' Create a new API service instance
#'
#' @param client An [api_client] instance. If `NULL`, a new client will be created.
#' @param chain A [credential_chain] instance for authentication. Optional.
#' @param endpoints A named list where names are endpoint paths (e.g., "v1.0", "beta")
#' and values are R6 class objects (not instances) to use for creating resources.
#' Defaults to an empty list. If the value is `NULL`, [api_resource] will be used.
#' @param config A list of configuration options. Defaults to an empty list.
#'
#' @return A new `api_service` object
initialize = function(
client = NULL,
chain = NULL,
endpoints = list(),
config = list()
) {
self$.client <- client
private$.chain <- chain
private$.config <- config
# Validate that endpoints is a list
if (!is.list(endpoints)) {
cli::cli_abort("{.arg endpoints} must be a list.")
}
# Get endpoint paths (names)
endpoint_paths <- names(endpoints)
# Validate endpoint paths
validate_endpoint_paths(endpoint_paths)
private$.endpoints <- endpoints
# Loop over endpoints and add api_resources
if (length(endpoints) > 0) {
for (i in seq_along(endpoints)) {
endpoint_path <- endpoint_paths[i]
resource_class <- endpoints[[i]]
# If resource_class is NULL, use default api_resource
if (is.null(resource_class)) {
resource_class <- api_resource
}
# Check if resource_class is already an R6 object instance
if (R6::is.R6(resource_class)) {
# Already an R6 object, assign directly
self[[endpoint_path]] <- resource_class
} else {
# Validate that resource_class is an R6ClassGenerator
if (!R6::is.R6Class(resource_class)) {
cli::cli_abort(
"Endpoint {.val {endpoint_path}} must specify an R6 class, not {.cls {class(resource_class)}}."
)
}
# Create resource instance for this endpoint
resource <- resource_class$new(
client = self$.client,
endpoint = endpoint_path
)
# Add as a field with the endpoint path name
self[[endpoint_path]] <- resource
}
# Lock the endpoint field to make it read-only
lockBinding(endpoint_path, self)
}
}
# Lock all fields to make them read-only
lockBinding(".client", self)
lockBinding(".chain", private)
lockBinding(".endpoints", private)
lockBinding(".config", private)
}
)
)
#' Validate Endpoint Paths
#'
#' @description
#' Validates that endpoint paths (names of the endpoints list) are single strings
#' without spaces and contain only valid URL path characters.
#'
#' @param endpoint_paths A character vector of endpoint path strings to validate
#'
#' @return Invisibly returns `NULL` if validation passes, otherwise throws an error
#'
#' @noRd
validate_endpoint_paths <- function(endpoint_paths) {
if (length(endpoint_paths) == 0) {
return(invisible(NULL))
}
for (i in seq_along(endpoint_paths)) {
endpoint_path <- endpoint_paths[i]
# Check if it's a character string
if (!is.character(endpoint_path) || length(endpoint_path) != 1) {
cli::cli_abort(
"Endpoint path at position {i} must be a single character string"
)
}
# Check if it's not empty
if (!nzchar(endpoint_path)) {
cli::cli_abort(
"Endpoint path at position {i} must not be an empty string"
)
}
# Check for spaces
if (grepl("\\s", endpoint_path)) {
cli::cli_abort(
"Endpoint path at position {i} ({.val {endpoint_path}}) contains spaces"
)
}
# Check for valid URL path characters (alphanumeric, hyphens, underscores, dots, slashes)
if (!grepl("^[a-zA-Z0-9._/-]+$", endpoint_path)) {
cli::cli_abort(
c(
"Endpoint path at position {i} ({.val {endpoint_path}}) contains invalid characters.",
"i" = "Only alphanumeric, '.', '_', '-', and '/' are allowed."
)
)
}
}
invisible(NULL)
}
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.