R/utils.R

Defines functions paginate_results is_access_denied format_generic_access_error parse_json_response handle_api_error validate_required_string `%||%`

Documented in format_generic_access_error handle_api_error is_access_denied paginate_results parse_json_response validate_required_string

#' Null-coalescing Operator
#'
#' Returns the left-hand side if it's not NULL, otherwise returns the right-hand side.
#'
#' @param x First value to check.
#' @param y Default value if x is NULL.
#'
#' @return x if not NULL, otherwise y.
#' @name null-coalesce
#' @keywords internal
`%||%` <- function(x, y) {
  if (is.null(x)) y else x
}


#' Validate Required String Parameter
#'
#' Internal function to validate that a string parameter is not empty or NULL.
#'
#' @param value Character. Value to validate.
#' @param param_name Character. Name of the parameter (for error messages).
#'
#' @return NULL (throws error if validation fails).
#' @keywords internal
validate_required_string <- function(value, param_name) {
  if (is.null(value) || length(value) == 0 || value == "") {
    stop(sprintf("Error: %s is required and cannot be empty.", param_name), call. = FALSE)
  }
  invisible(NULL)
}


#' Handle API Error Response
#'
#' Internal function to handle API error responses and generate user-friendly error messages.
#'
#' @param response HTTP response object from httr.
#' @param endpoint Character. Endpoint that was called (for error context).
#'
#' @return NULL (throws error with formatted message).
#' @keywords internal
handle_api_error <- function(response, endpoint) {
  status <- httr2::resp_status(response)
  
  # Try to extract error message from response body
  error_msg <- tryCatch({
    body <- httr2::resp_body_json(response)
    body$message %||% body$error %||% "Unknown error"
  }, error = function(e) {
    "Unable to parse error response"
  })
  
  # Format error based on status code
  if (status == 401) {
    stop(sprintf(
      "Authentication failed (401).\nEndpoint: %s\nPlease check your API key and workspace ID.",
      endpoint
    ), call. = FALSE)
  } else if (status == 403) {
    stop(sprintf(
      "Access denied (403).\nEndpoint: %s\nYou do not have permission to access this resource.\nDetails: %s",
      endpoint,
      error_msg
    ), call. = FALSE)
  } else if (status == 404) {
    stop(sprintf(
      "Resource not found (404).\nEndpoint: %s\nThis resource does not exist or you do not have access.\nDetails: %s",
      endpoint,
      error_msg
    ), call. = FALSE)
  } else if (status >= 500) {
    stop(sprintf(
      "Server error (%d).\nEndpoint: %s\nThe server encountered an error. Please try again later.\nDetails: %s",
      status,
      endpoint,
      error_msg
    ), call. = FALSE)
  } else {
    stop(sprintf(
      "API request failed (%d).\nEndpoint: %s\nDetails: %s",
      status,
      endpoint,
      error_msg
    ), call. = FALSE)
  }
}


#' Parse JSON Response
#'
#' Internal function to safely parse JSON response from API.
#'
#' @param response HTTP response object from httr2.
#'
#' @return Parsed JSON object.
#' @keywords internal
parse_json_response <- function(response) {
  tryCatch({
    httr2::resp_body_json(response, simplifyVector = FALSE)
  }, error = function(e) {
    stop(sprintf("Error parsing JSON response: %s", e$message), call. = FALSE)
  })
}


#' Format Error Message with Generic Access Denial
#'
#' Returns a generic error message that doesn't leak information about resource existence.
#' Used for schema/table access errors in accordance with security policy.
#'
#' @param resource_type Character. Type of resource (e.g., "schema", "table").
#' @param resource_name Character. Name of the resource (optional).
#'
#' @return Character. Generic error message.
#' @keywords internal
format_generic_access_error <- function(resource_type, resource_name = NULL) {
  if (!is.null(resource_name) && resource_name != "") {
    sprintf("This %s does not exist or you do not have access.", resource_type)
  } else {
    sprintf("This %s does not exist or you do not have access.", resource_type)
  }
}


#' Check if Response Indicates Access Denial
#'
#' Internal function to check if API response indicates access denial
#' (could be either non-existent or unauthorized).
#'
#' @param response HTTP response object from httr2.
#'
#' @return Logical. TRUE if access is denied.
#' @keywords internal
is_access_denied <- function(response) {
  status <- httr2::resp_status(response)
  return(status == 403 || status == 404)
}


#' Paginate Results
#'
#' Internal function to handle pagination of large result sets.
#'
#' @param data List. Full result set.
#' @param page Integer. Page number (0-indexed).
#' @param page_size Integer. Number of items per page.
#'
#' @return List with paginated data and metadata.
#' @keywords internal
paginate_results <- function(data, page = 0, page_size = 20) {
  total <- length(data)
  start_idx <- page * page_size + 1
  end_idx <- min((page + 1) * page_size, total)
  
  if (start_idx > total) {
    return(list(
      data = list(),
      page = page,
      page_size = page_size,
      total = total,
      pages = ceiling(total / page_size)
    ))
  }
  
  list(
    data = data[start_idx:end_idx],
    page = page,
    page_size = page_size,
    total = total,
    pages = ceiling(total / page_size)
  )
}

Try the cloudosR package in your browser

Any scripts or data that you put into this service are public.

cloudosR documentation built on June 1, 2026, 5:07 p.m.