1 # Copyright (C) The Arvados Authors. All rights reserved.
3 # SPDX-License-Identifier: Apache-2.0
7 getAPIDocument <- function(loc)
9 if (length(grep("^[a-z]+://", loc)) > 0) {
11 serverResponse <- httr::RETRY("GET", url = loc)
12 httr::content(serverResponse, as = "parsed", type = "application/json")
14 jsonlite::read_json(loc)
20 #' Autogenerate classes to interact with Arvados from the Arvados discovery document.
23 generateAPI <- function(discoveryDocument)
25 methodResources <- discoveryDocument$resources
26 resourceNames <- names(methodResources)
28 classDoc <- genAPIClassDoc(methodResources, resourceNames)
29 arvadosAPIHeader <- genAPIClassHeader()
30 arvadosClassMethods <- genClassContent(methodResources, resourceNames)
31 arvadosProjectMethods <- genProjectMethods(methodResources)
32 arvadosAPIFooter <- genAPIClassFooter()
34 arvadosClass <- c(classDoc,
37 arvadosProjectMethods,
40 fileConn <- file("./R/Arvados.R", "w")
42 "# Copyright (C) The Arvados Authors. All rights reserved.",
44 "# SPDX-License-Identifier: Apache-2.0",
48 "#' This class implements a full REST client to the Arvados API.",
50 writeLines(unlist(arvadosClass), fileConn)
55 genAPIClassHeader <- function()
58 "Arvados <- R6::R6Class(",
64 "\t\t#' @description Create a new Arvados API client.",
65 "\t\t#' @param authToken Authentification token. If not specified ARVADOS_API_TOKEN environment variable will be used.",
66 "\t\t#' @param hostName Host name. If not specified ARVADOS_API_HOST environment variable will be used.",
67 "\t\t#' @param numRetries Number which specifies how many times to retry failed service requests.",
68 "\t\t#' @return A new `Arvados` object.",
69 "\t\tinitialize = function(authToken = NULL, hostName = NULL, numRetries = 0)",
71 "\t\t\tif(!is.null(hostName))",
72 "\t\t\t\tSys.setenv(ARVADOS_API_HOST = hostName)",
74 "\t\t\tif(!is.null(authToken))",
75 "\t\t\t\tSys.setenv(ARVADOS_API_TOKEN = authToken)",
77 "\t\t\thostName <- Sys.getenv(\"ARVADOS_API_HOST\")",
78 "\t\t\ttoken <- Sys.getenv(\"ARVADOS_API_TOKEN\")",
80 "\t\t\tif(hostName == \"\" | token == \"\")",
81 "\t\t\t\tstop(paste(\"Please provide host name and authentification token\",",
82 "\t\t\t\t\t\t \"or set ARVADOS_API_HOST and ARVADOS_API_TOKEN\",",
83 "\t\t\t\t\t\t \"environment variables.\"))",
85 "\t\t\tprivate$token <- token",
86 "\t\t\tprivate$host <- paste0(\"https://\", hostName, \"/arvados/v1/\")",
87 "\t\t\tprivate$numRetries <- numRetries",
88 "\t\t\tprivate$REST <- RESTService$new(token, hostName,",
89 "\t\t\t HttpRequest$new(), HttpParser$new(),",
95 genProjectMethods <- function(methodResources)
97 toCallArg <- function(arg) {
98 callArg <- strsplit(arg, " *=")[[1]][1]
99 paste(callArg, callArg, sep=" = ")
101 toCallArgs <- function(argList) {
102 paste0(Map(toCallArg, argList), collapse=", ")
104 groupsMethods <- methodResources[["groups"]][["methods"]]
105 getArgs <- getMethodArguments(groupsMethods[["get"]])
106 createArgs <- getMethodArguments(groupsMethods[["create"]])
107 updateArgs <- getMethodArguments(groupsMethods[["update"]])
108 listArgs <- getMethodArguments(groupsMethods[["list"]])
109 deleteArgs <- getMethodArguments(groupsMethods[["delete"]])
111 c("\t\t#' @description An alias for `groups_get`.",
112 getMethodParams(groupsMethods[["get"]]),
113 "\t\t#' @return A Group object.",
114 getMethodSignature("project_get", getArgs),
116 paste("\t\t\tself$groups_get(", toCallArgs(getArgs), ")", sep=""),
119 "\t\t#' @description A wrapper for `groups_create` that sets `group_class=\"project\"`.",
120 getMethodParams(groupsMethods[["create"]]),
121 "\t\t#' @return A Group object.",
122 getMethodSignature("project_create", createArgs),
124 "\t\t\tgroup <- c(\"group_class\" = \"project\", group)",
125 paste("\t\t\tself$groups_create(", toCallArgs(createArgs), ")", sep=""),
128 "\t\t#' @description A wrapper for `groups_update` that sets `group_class=\"project\"`.",
129 getMethodParams(groupsMethods[["update"]]),
130 "\t\t#' @return A Group object.",
131 getMethodSignature("project_update", updateArgs),
133 "\t\t\tgroup <- c(\"group_class\" = \"project\", group)",
134 paste("\t\t\tself$groups_update(", toCallArgs(updateArgs), ")", sep=""),
137 "\t\t#' @description A wrapper for `groups_list` that adds a filter for `group_class=\"project\"`.",
138 getMethodParams(groupsMethods[["list"]]),
139 "\t\t#' @return A GroupList object.",
140 getMethodSignature("project_list", listArgs),
142 "\t\t\tfilters[[length(filters) + 1]] <- list(\"group_class\", \"=\", \"project\")",
143 paste("\t\t\tself$groups_list(", toCallArgs(listArgs), ")", sep=""),
146 "\t\t#' @description An alias for `groups_delete`.",
147 getMethodParams(groupsMethods[["delete"]]),
148 "\t\t#' @return A Group object.",
149 getMethodSignature("project_delete", deleteArgs),
151 paste("\t\t\tself$groups_delete(", toCallArgs(deleteArgs), ")", sep=""),
154 "\t\t#' @description Test whether or not a project exists.",
155 getMethodParams(groupsMethods[["get"]]),
156 getMethodSignature("project_exist", getArgs),
158 paste("\t\t\tresult <- try(self$groups_get(", toCallArgs(getArgs), "))", sep=""),
159 "\t\t\tif(inherits(result, \"try-error\"))",
160 "\t\t\t\texists <- FALSE",
162 "\t\t\t\texists <- result['group_class'] == \"project\"",
163 "\t\t\tcat(format(exists))",
168 genClassContent <- function(methodResources, resourceNames)
170 arvadosMethods <- Map(function(resource, resourceName)
172 methodNames <- names(resource$methods)
174 functions <- Map(function(methodMetaData, methodName)
176 #NOTE: Index, show and destroy are aliases for the preferred names
177 # "list", "get" and "delete". Until they are removed from discovery
178 # document we will filter them here.
179 if(methodName %in% c("index", "show", "destroy"))
182 methodName <- paste0(resourceName, "_", methodName)
184 getMethodDoc(methodName, methodMetaData),
185 createMethod(methodName, methodMetaData)
188 }, resource$methods, methodNames)
190 unlist(unname(functions))
192 }, methodResources, resourceNames)
197 genAPIClassFooter <- function()
199 c("\t\t#' @description Return the host name of this client's Arvados API server.",
200 "\t\t#' @return Hostname string.",
201 "\t\tgetHostName = function() private$host,",
203 "\t\t#' @description Return the Arvados API token used by this client.",
204 "\t\t#' @return API token string.",
205 "\t\tgetToken = function() private$token,",
207 "\t\t#' @description Set the RESTService object used by this client.",
208 "\t\tsetRESTService = function(newREST) private$REST <- newREST,",
210 "\t\t#' @description Return the RESTService object used by this client.",
211 "\t\t#' @return RESTService object.",
212 "\t\tgetRESTService = function() private$REST",
220 "\t\tnumRetries = NULL",
223 "\tcloneable = FALSE",
227 createMethod <- function(name, methodMetaData)
229 args <- getMethodArguments(methodMetaData)
230 signature <- getMethodSignature(name, args)
231 body <- getMethodBody(methodMetaData)
239 normalizeParamName <- function(name)
241 # Downcase the first letter
242 name <- sub("^(\\w)", "\\L\\1", name, perl=TRUE)
243 # Convert snake_case to camelCase
244 gsub("_(uuid\\b|id\\b|\\w)", "\\U\\1", name, perl=TRUE)
247 getMethodArguments <- function(methodMetaData)
249 request <- methodMetaData$request
252 if(!is.null(request))
254 resourceName <- normalizeParamName(request$properties[[1]][[1]])
257 requestArgs <- resourceName
259 requestArgs <- paste(resourceName, "=", "NULL")
262 argNames <- names(methodMetaData$parameters)
264 args <- sapply(argNames, function(argName)
266 arg <- methodMetaData$parameters[[argName]]
267 argName <- normalizeParamName(argName)
271 return(paste(argName, "=", "NULL"))
280 getMethodSignature <- function(methodName, args)
282 collapsedArgs <- paste0(args, collapse = ", ")
283 lineLengthLimit <- 40
285 if(nchar(collapsedArgs) > lineLengthLimit)
287 return(paste0("\t\t",
288 formatArgs(paste(methodName, "= function("),
289 "\t", args, ")", lineLengthLimit)))
293 return(paste0("\t\t", methodName, " = function(", collapsedArgs, ")"))
297 getMethodBody <- function(methodMetaData)
299 url <- getRequestURL(methodMetaData)
300 headers <- getRequestHeaders()
301 requestQueryList <- getRequestQueryList(methodMetaData)
302 requestBody <- getRequestBody(methodMetaData)
303 request <- getRequest(methodMetaData)
304 response <- getResponse(methodMetaData)
305 errorCheck <- getErrorCheckingCode()
306 returnStatement <- getReturnObject()
310 requestQueryList, "",
312 request, response, "",
316 paste0("\t\t\t", body)
319 getRequestURL <- function(methodMetaData)
321 endPoint <- methodMetaData$path
322 endPoint <- stringr::str_replace_all(endPoint, "\\{", "${")
323 url <- c(paste0("endPoint <- stringr::str_interp(\"", endPoint, "\")"),
324 paste0("url <- paste0(private$host, endPoint)"))
328 getRequestHeaders <- function()
330 c("headers <- list(Authorization = paste(\"Bearer\", private$token), ",
331 " \"Content-Type\" = \"application/json\")")
334 getRequestQueryList <- function(methodMetaData)
336 queryArgs <- names(Filter(function(arg) arg$location == "query",
337 methodMetaData$parameters))
339 if(length(queryArgs) == 0)
340 return("queryArgs <- NULL")
342 queryArgs <- sapply(queryArgs, function(arg) {
343 arg <- normalizeParamName(arg)
346 collapsedArgs <- paste0(queryArgs, collapse = ", ")
348 lineLengthLimit <- 40
350 if(nchar(collapsedArgs) > lineLengthLimit)
351 return(formatArgs("queryArgs <- list(", "\t\t\t\t ", queryArgs, ")",
354 return(paste0("queryArgs <- list(", collapsedArgs, ")"))
357 getRequestBody <- function(methodMetaData)
359 request <- methodMetaData$request
361 if(is.null(request) || !request$required)
362 return("body <- NULL")
364 resourceName <- normalizeParamName(request$properties[[1]][[1]])
366 requestParameterName <- names(request$properties)[1]
368 c(paste0("if(length(", resourceName, ") > 0)"),
369 paste0("\tbody <- jsonlite::toJSON(list(", resourceName, " = ", resourceName, "), "),
370 "\t auto_unbox = TRUE)",
375 getRequest <- function(methodMetaData)
377 method <- methodMetaData$httpMethod
378 c(paste0("response <- private$REST$http$exec(\"", method, "\", url, headers, body,"),
379 " queryArgs, private$numRetries)")
382 getResponse <- function(methodMetaData)
384 "resource <- private$REST$httpParser$parseJSONResponse(response)"
387 getErrorCheckingCode <- function()
389 c("if(!is.null(resource$errors))",
390 "\tstop(resource$errors)")
393 getReturnObject <- function()
398 genAPIClassDoc <- function(methodResources, resourceNames)
402 "#' arv <- Arvados$new(\"your Arvados token\", \"example.arvadosapi.com\")",
404 "#' collection <- arv$collections.get(\"uuid\")",
406 "#' collectionList <- arv$collections.list(list(list(\"name\", \"like\", \"Test%\")))",
407 "#' collectionList <- listAll(arv$collections.list, list(list(\"name\", \"like\", \"Test%\")))",
409 "#' deletedCollection <- arv$collections.delete(\"uuid\")",
411 "#' updatedCollection <- arv$collections.update(list(name = \"New name\", description = \"New description\"),",
414 "#' createdCollection <- arv$collections.create(list(name = \"Example\",",
415 "#' description = \"This is a test collection\"))",
420 getAPIClassMethodList <- function(methodResources, resourceNames)
422 methodList <- unlist(unname(Map(function(resource, resourceName)
424 methodNames <- names(resource$methods)
427 methodNames[!(methodNames %in% c("index", "show", "destroy"))])
429 }, methodResources, resourceNames)))
431 hardcodedMethods <- c("projects.create", "projects.get",
432 "projects.list", "projects.update", "projects.delete")
433 paste0("#' \t\\item{}{\\code{\\link{", sort(c(methodList, hardcodedMethods)), "}}}")
436 getMethodDoc <- function(methodName, methodMetaData)
438 description <- paste("\t\t#' @description", gsub("\n", "\n\t\t#' ", methodMetaData$description))
439 params <- getMethodParams(methodMetaData)
440 returnValue <- paste("\t\t#' @return", methodMetaData$response[["$ref"]], "object.")
442 c(description, params, returnValue)
445 getMethodParams <- function(methodMetaData)
447 request <- methodMetaData$request
450 if(!is.null(request))
452 requestDoc <- unname(unlist(sapply(request$properties, function(prop)
454 className <- sapply(prop, function(ref) ref)
455 objectName <- normalizeParamName(className)
456 paste("\t\t#' @param", objectName, className, "object.")
460 argNames <- names(methodMetaData$parameters)
462 argsDoc <- unname(unlist(sapply(argNames, function(argName)
464 arg <- methodMetaData$parameters[[argName]]
465 paste("\t\t#' @param",
466 normalizeParamName(argName),
467 gsub("\n", "\n\t\t#' ", arg$description)
471 c(requestDoc, argsDoc)
474 #NOTE: Utility functions:
476 # This function is used to split very long lines of code into smaller chunks.
477 # This is usually the case when we pass a lot of named argumets to a function.
478 formatArgs <- function(prependAtStart, prependToEachSplit,
479 args, appendAtEnd, lineLength)
483 args[1:(length(args) - 1)] <- paste0(args[1:(length(args) - 1)], ",")
486 args[1] <- paste0(prependAtStart, args[1])
487 args[length(args)] <- paste0(args[length(args)], appendAtEnd)
489 argsLength <- length(args)
493 while(index <= argsLength)
498 while(nchar(line) < lineLength && index <= argsLength)
500 line <- paste(line, args[index])
504 argLines <- c(argLines, line)
507 argLines <- unlist(argLines)
508 argLinesLen <- length(argLines)
511 argLines[2:argLinesLen] <- paste0(prependToEachSplit, argLines[2:argLinesLen])
516 args <- commandArgs(TRUE)
517 if (length(args) == 0) {
518 loc <- "arvados-v1-discovery.json"
522 discoveryDocument <- getAPIDocument(loc)
523 generateAPI(discoveryDocument)