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=""),
156 genClassContent <- function(methodResources, resourceNames)
158 arvadosMethods <- Map(function(resource, resourceName)
160 methodNames <- names(resource$methods)
162 functions <- Map(function(methodMetaData, methodName)
164 #NOTE: Index, show and destroy are aliases for the preferred names
165 # "list", "get" and "delete". Until they are removed from discovery
166 # document we will filter them here.
167 if(methodName %in% c("index", "show", "destroy"))
170 methodName <- paste0(resourceName, "_", methodName)
172 getMethodDoc(methodName, methodMetaData),
173 createMethod(methodName, methodMetaData)
176 }, resource$methods, methodNames)
178 unlist(unname(functions))
180 }, methodResources, resourceNames)
185 genAPIClassFooter <- function()
187 c("\t\t#' @description Return the host name of this client's Arvados API server.",
188 "\t\t#' @return Hostname string.",
189 "\t\tgetHostName = function() private$host,",
191 "\t\t#' @description Return the Arvados API token used by this client.",
192 "\t\t#' @return API token string.",
193 "\t\tgetToken = function() private$token,",
195 "\t\t#' @description Set the RESTService object used by this client.",
196 "\t\tsetRESTService = function(newREST) private$REST <- newREST,",
198 "\t\t#' @description Return the RESTService object used by this client.",
199 "\t\t#' @return RESTService object.",
200 "\t\tgetRESTService = function() private$REST",
208 "\t\tnumRetries = NULL",
211 "\tcloneable = FALSE",
215 createMethod <- function(name, methodMetaData)
217 args <- getMethodArguments(methodMetaData)
218 signature <- getMethodSignature(name, args)
219 body <- getMethodBody(methodMetaData)
227 normalizeParamName <- function(name)
229 # Downcase the first letter
230 name <- sub("^(\\w)", "\\L\\1", name, perl=TRUE)
231 # Convert snake_case to camelCase
232 gsub("_(uuid\\b|id\\b|\\w)", "\\U\\1", name, perl=TRUE)
235 getMethodArguments <- function(methodMetaData)
237 request <- methodMetaData$request
240 if(!is.null(request))
242 resourceName <- normalizeParamName(request$properties[[1]][[1]])
245 requestArgs <- resourceName
247 requestArgs <- paste(resourceName, "=", "NULL")
250 argNames <- names(methodMetaData$parameters)
252 args <- sapply(argNames, function(argName)
254 arg <- methodMetaData$parameters[[argName]]
255 argName <- normalizeParamName(argName)
259 return(paste(argName, "=", "NULL"))
268 getMethodSignature <- function(methodName, args)
270 collapsedArgs <- paste0(args, collapse = ", ")
271 lineLengthLimit <- 40
273 if(nchar(collapsedArgs) > lineLengthLimit)
275 return(paste0("\t\t",
276 formatArgs(paste(methodName, "= function("),
277 "\t", args, ")", lineLengthLimit)))
281 return(paste0("\t\t", methodName, " = function(", collapsedArgs, ")"))
285 getMethodBody <- function(methodMetaData)
287 url <- getRequestURL(methodMetaData)
288 headers <- getRequestHeaders()
289 requestQueryList <- getRequestQueryList(methodMetaData)
290 requestBody <- getRequestBody(methodMetaData)
291 request <- getRequest(methodMetaData)
292 response <- getResponse(methodMetaData)
293 errorCheck <- getErrorCheckingCode()
294 returnStatement <- getReturnObject()
298 requestQueryList, "",
300 request, response, "",
304 paste0("\t\t\t", body)
307 getRequestURL <- function(methodMetaData)
309 endPoint <- methodMetaData$path
310 endPoint <- stringr::str_replace_all(endPoint, "\\{", "${")
311 url <- c(paste0("endPoint <- stringr::str_interp(\"", endPoint, "\")"),
312 paste0("url <- paste0(private$host, endPoint)"))
316 getRequestHeaders <- function()
318 c("headers <- list(Authorization = paste(\"Bearer\", private$token), ",
319 " \"Content-Type\" = \"application/json\")")
322 getRequestQueryList <- function(methodMetaData)
324 queryArgs <- names(Filter(function(arg) arg$location == "query",
325 methodMetaData$parameters))
327 if(length(queryArgs) == 0)
328 return("queryArgs <- NULL")
330 queryArgs <- sapply(queryArgs, function(arg) {
331 arg <- normalizeParamName(arg)
334 collapsedArgs <- paste0(queryArgs, collapse = ", ")
336 lineLengthLimit <- 40
338 if(nchar(collapsedArgs) > lineLengthLimit)
339 return(formatArgs("queryArgs <- list(", "\t\t\t\t ", queryArgs, ")",
342 return(paste0("queryArgs <- list(", collapsedArgs, ")"))
345 getRequestBody <- function(methodMetaData)
347 request <- methodMetaData$request
349 if(is.null(request) || !request$required)
350 return("body <- NULL")
352 resourceName <- normalizeParamName(request$properties[[1]][[1]])
354 requestParameterName <- names(request$properties)[1]
356 c(paste0("if(length(", resourceName, ") > 0)"),
357 paste0("\tbody <- jsonlite::toJSON(list(", resourceName, " = ", resourceName, "), "),
358 "\t auto_unbox = TRUE)",
363 getRequest <- function(methodMetaData)
365 method <- methodMetaData$httpMethod
366 c(paste0("response <- private$REST$http$exec(\"", method, "\", url, headers, body,"),
367 " queryArgs, private$numRetries)")
370 getResponse <- function(methodMetaData)
372 "resource <- private$REST$httpParser$parseJSONResponse(response)"
375 getErrorCheckingCode <- function()
377 c("if(!is.null(resource$errors))",
378 "\tstop(resource$errors)")
381 getReturnObject <- function()
386 genAPIClassDoc <- function(methodResources, resourceNames)
390 "#' arv <- Arvados$new(\"your Arvados token\", \"example.arvadosapi.com\")",
392 "#' collection <- arv$collections.get(\"uuid\")",
394 "#' collectionList <- arv$collections.list(list(list(\"name\", \"like\", \"Test%\")))",
395 "#' collectionList <- listAll(arv$collections.list, list(list(\"name\", \"like\", \"Test%\")))",
397 "#' deletedCollection <- arv$collections.delete(\"uuid\")",
399 "#' updatedCollection <- arv$collections.update(list(name = \"New name\", description = \"New description\"),",
402 "#' createdCollection <- arv$collections.create(list(name = \"Example\",",
403 "#' description = \"This is a test collection\"))",
408 getAPIClassMethodList <- function(methodResources, resourceNames)
410 methodList <- unlist(unname(Map(function(resource, resourceName)
412 methodNames <- names(resource$methods)
415 methodNames[!(methodNames %in% c("index", "show", "destroy"))])
417 }, methodResources, resourceNames)))
419 hardcodedMethods <- c("projects.create", "projects.get",
420 "projects.list", "projects.update", "projects.delete")
421 paste0("#' \t\\item{}{\\code{\\link{", sort(c(methodList, hardcodedMethods)), "}}}")
424 getMethodDoc <- function(methodName, methodMetaData)
426 description <- paste("\t\t#' @description", gsub("\n", "\n\t\t#' ", methodMetaData$description))
427 params <- getMethodParams(methodMetaData)
428 returnValue <- paste("\t\t#' @return", methodMetaData$response[["$ref"]], "object.")
430 c(description, params, returnValue)
433 getMethodParams <- function(methodMetaData)
435 request <- methodMetaData$request
438 if(!is.null(request))
440 requestDoc <- unname(unlist(sapply(request$properties, function(prop)
442 className <- sapply(prop, function(ref) ref)
443 objectName <- normalizeParamName(className)
444 paste("\t\t#' @param", objectName, className, "object.")
448 argNames <- names(methodMetaData$parameters)
450 argsDoc <- unname(unlist(sapply(argNames, function(argName)
452 arg <- methodMetaData$parameters[[argName]]
453 paste("\t\t#' @param",
454 normalizeParamName(argName),
455 gsub("\n", "\n\t\t#' ", arg$description)
459 c(requestDoc, argsDoc)
462 #NOTE: Utility functions:
464 # This function is used to split very long lines of code into smaller chunks.
465 # This is usually the case when we pass a lot of named argumets to a function.
466 formatArgs <- function(prependAtStart, prependToEachSplit,
467 args, appendAtEnd, lineLength)
471 args[1:(length(args) - 1)] <- paste0(args[1:(length(args) - 1)], ",")
474 args[1] <- paste0(prependAtStart, args[1])
475 args[length(args)] <- paste0(args[length(args)], appendAtEnd)
477 argsLength <- length(args)
481 while(index <= argsLength)
486 while(nchar(line) < lineLength && index <= argsLength)
488 line <- paste(line, args[index])
492 argLines <- c(argLines, line)
495 argLines <- unlist(argLines)
496 argLinesLen <- length(argLines)
499 argLines[2:argLinesLen] <- paste0(prependToEachSplit, argLines[2:argLinesLen])
504 args <- commandArgs(TRUE)
505 if (length(args) == 0) {
506 loc <- "arvados-v1-discovery.json"
510 discoveryDocument <- getAPIDocument(loc)
511 generateAPI(discoveryDocument)