R/utils.R

Defines functions as_tibble.listObjects as_tibble.listBuckets waitForWorkItem waitForFile `$.aps_token` is_expired.aps_token is_expired new_aps_token .resolve_token aps_request aps_perform aps_error

Documented in aps_error as_tibble.listBuckets as_tibble.listObjects is_expired waitForFile waitForWorkItem

#' Create an APS API Error Condition.
#'
#' Create a structured error condition for failures returned by the AutoDesk
#' Platform Services (APS) API. Callers can use
#' \code{tryCatch(..., aps_error = function(e) ...)} to handle these errors.
#' @param message A string. Human-readable error message.
#' @param status An integer. HTTP status code.
#' @param body A list. Parsed JSON body of the error response.
#' @param call The call where the error occurred (internal use).
#' @return A condition object of class \code{c("aps_error", "error", "condition")}.
#' @examples
#' \dontrun{
#' tryCatch(
#'   makeBucket(token = "bad_token"),
#'   aps_error = function(e) cat("HTTP", e$status, "-", e$message)
#' )
#' }
#' @export
aps_error <- function(message, status, body, call = sys.call(-1)) {
  structure(
    class = c("aps_error", "error", "condition"),
    list(message = message, status = status, body = body, call = call)
  )
}

# Internal: wrap httr2::req_perform() and decode APS JSON error bodies
aps_perform <- function(req) {
  tryCatch(
    req_perform(req),
    httr2_http = function(e) {
      body <- tryCatch(
        resp_body_json(e$resp, simplifyVector = FALSE),
        error = function(...) list()
      )
      msg <- body[["message"]] %||% body[["reason"]] %||% conditionMessage(e)
      stop(aps_error(
        message = paste0("APS API error (HTTP ", resp_status(e$resp), "): ", msg),
        status  = resp_status(e$resp),
        body    = body
      ))
    }
  )
}

# Internal: shared httr2 request builder
aps_request <- function(url, token = NULL, method = "GET", timeout = 60) {
  req <- request(url) |>
    req_user_agent("https://github.com/paulgovan/AutoDeskR") |>
    req_timeout(timeout)
  if (!is.null(token))
    req <- req |> req_headers(Authorization = paste0("Bearer ", token))
  if (method != "GET")
    req <- req |> req_method(method)
  req
}

# Internal: resolve an aps_token or plain string to a bearer token string
.resolve_token <- function(token) {
  if (inherits(token, "aps_token")) {
    if (is_expired(token))
      warning("Token may be expired. Consider calling getToken() again.", call. = FALSE)
    token$access_token
  } else {
    token
  }
}

# Internal: aps_token constructor — built from the parsed /authentication/v2/token response
new_aps_token <- function(parsed, url, raw_resp) {
  now <- Sys.time()
  structure(
    list(
      access_token = parsed$access_token,
      token_type   = parsed$token_type,
      expires_in   = parsed$expires_in,
      expires_at   = now + parsed$expires_in - 60L,
      fetched_at   = now,
      path         = url,
      response     = raw_resp
    ),
    class = c("aps_token", "getToken")
  )
}

#' Check Whether an aps_token Has Expired.
#'
#' @param token An \code{aps_token} object returned by \code{\link{getToken}}.
#' @return Logical. \code{TRUE} if the token has expired, \code{FALSE} otherwise.
#' @examples
#' \dontrun{
#' tok <- getToken(id = Sys.getenv("client_id"), secret = Sys.getenv("client_secret"))
#' is_expired(tok)
#' }
#' @export
is_expired <- function(token) UseMethod("is_expired")

#' @export
is_expired.aps_token <- function(token) {
  Sys.time() >= token$expires_at
}

#' @export
`$.aps_token` <- function(x, name) {
  if (name == "content") {
    list(
      access_token = .subset2(x, "access_token"),
      token_type   = .subset2(x, "token_type"),
      expires_in   = .subset2(x, "expires_in")
    )
  } else {
    .subset2(x, name)
  }
}

#' Wait for a Model Derivative Translation to Complete.
#'
#' Polls \code{\link{checkFile}} at a fixed interval until the translation
#' reaches a terminal state (\code{"success"}, \code{"failed"}, or
#' \code{"timeout"}).
#' @param urn A string. Base64-encoded source URN.
#' @param token A string or \code{aps_token} object with \code{data:read} scope.
#' @param interval Seconds between polls. Defaults to \code{5}.
#' @param timeout Maximum seconds to wait before aborting. Defaults to
#'   \code{300}.
#' @param verbose If \code{TRUE} (default), prints a message after each poll.
#' @return The final \code{\link{checkFile}} response object.
#' @examples
#' \dontrun{
#' myEncodedUrn <- jsonlite::base64_enc(myUrn)
#' resp <- translateSvf(urn = myEncodedUrn, token = myToken)
#' done <- waitForFile(urn = myEncodedUrn, token = myToken)
#' done$content$status
#' }
#' @export
waitForFile <- function(urn, token, interval = 5, timeout = 300, verbose = TRUE) {
  deadline <- Sys.time() + timeout
  repeat {
    resp   <- checkFile(urn = urn, token = token)
    status <- resp$content$status
    if (verbose) message("Translation status: ", status)
    if (status %in% c("success", "failed", "timeout")) return(resp)
    if (Sys.time() > deadline)
      stop("waitForFile() timed out after ", timeout, " seconds.")
    Sys.sleep(interval)
  }
}

#' Wait for a Design Automation WorkItem to Complete.
#'
#' Polls \code{\link{checkPdf}} at a fixed interval until the WorkItem reaches
#' a terminal state (any status other than \code{"inprogress"} or
#' \code{"pending"}).
#' @param id A string. WorkItem ID from \code{makePdf()$content$id}.
#' @param token A string or \code{aps_token} object with \code{code:all} scope.
#' @param interval Seconds between polls. Defaults to \code{5}.
#' @param timeout Maximum seconds to wait before aborting. Defaults to
#'   \code{300}.
#' @param verbose If \code{TRUE} (default), prints a message after each poll.
#' @return The final \code{\link{checkPdf}} response object.
#' @examples
#' \dontrun{
#' resp <- makePdf(source = mySource, destination = myDest, token = myToken)
#' done <- waitForWorkItem(id = resp$content$id, token = myToken)
#' done$content$status
#' }
#' @export
waitForWorkItem <- function(id, token, interval = 5, timeout = 300, verbose = TRUE) {
  deadline <- Sys.time() + timeout
  repeat {
    resp   <- checkPdf(id = id, token = token)
    status <- resp$content$status
    if (verbose) message("WorkItem status: ", status)
    if (!identical(status, "inprogress") && !identical(status, "pending"))
      return(resp)
    if (Sys.time() > deadline)
      stop("waitForWorkItem() timed out after ", timeout, " seconds.")
    Sys.sleep(interval)
  }
}

#' Convert a listBuckets Response to a Tibble.
#'
#' Requires the \code{tibble} package.
#' @param x A \code{listBuckets} response object.
#' @param ... Additional arguments (unused).
#' @return A \code{\link[tibble]{tibble}} with one row per bucket and columns
#'   \code{bucketKey}, \code{bucketOwner}, and \code{policyKey}.
#' @examples
#' \dontrun{
#' library(tibble)
#' listBuckets(token = myToken) |> as_tibble()
#' }
#' @exportS3Method tibble::as_tibble
as_tibble.listBuckets <- function(x, ...) {
  if (!requireNamespace("tibble", quietly = TRUE))
    stop("Package 'tibble' needed. Install with: install.packages('tibble')")
  items <- x$content$items %||% list()
  tibble::tibble(
    bucketKey   = vapply(items, `[[`, character(1), "bucketKey"),
    bucketOwner = vapply(items, `[[`, character(1), "bucketOwner"),
    policyKey   = vapply(items, `[[`, character(1), "policyKey")
  )
}

#' Convert a listObjects Response to a Tibble.
#'
#' Requires the \code{tibble} package.
#' @param x A \code{listObjects} response object.
#' @param ... Additional arguments (unused).
#' @return A \code{\link[tibble]{tibble}} with one row per object and columns
#'   \code{objectKey}, \code{objectId}, \code{size}, and \code{location}.
#' @examples
#' \dontrun{
#' library(tibble)
#' listObjects(token = myToken, bucket = "mybucket") |> as_tibble()
#' }
#' @exportS3Method tibble::as_tibble
as_tibble.listObjects <- function(x, ...) {
  if (!requireNamespace("tibble", quietly = TRUE))
    stop("Package 'tibble' needed. Install with: install.packages('tibble')")
  items <- x$content$items %||% list()
  tibble::tibble(
    objectKey = vapply(items, `[[`, character(1), "objectKey"),
    objectId  = vapply(items, `[[`, character(1), "objectId"),
    size      = vapply(items, function(i) as.integer(i[["size"]]), integer(1)),
    location  = vapply(items, `[[`, character(1), "location")
  )
}

Try the AutoDeskR package in your browser

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

AutoDeskR documentation built on May 28, 2026, 5:08 p.m.