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))",
166 "\t\t#' @description A convenience wrapper for `project_update` to set project metadata properties.",
167 "\t\t#' @param listProperties List of new properties.",
168 "\t\t#' @param uuid UUID of the project to update.",
169 "\t\t#' @return A Group object.",
170 "\t\tproject_properties_set = function(listProperties, uuid)",
172 "\t\t\tself$project_update(list(\"properties\" = listProperties), uuid)",
175 "\t\t#' @description Get a project and update it with additional properties.",
176 "\t\t#' @param properties List of new properties.",
177 "\t\t#' @param uuid UUID of the project to update.",
178 "\t\t#' @return A Group object.",
179 "\t\tproject_properties_append = function(properties, uuid)",
181 "\t\t\tproj <- private$get_project_by_list(uuid, list('uuid', 'properties'))",
182 "\t\t\tnewListOfProperties <- c(proj$properties, properties)",
183 "\t\t\tuniqueProperties <- unique(unlist(newListOfProperties))",
184 "\t\t\tnewProperties <- suppressWarnings(newListOfProperties[which(newListOfProperties == uniqueProperties)])",
185 "\t\t\tself$project_properties_set(newProperties, proj$uuid)",
188 "\t\t#' @description Get properties of a project.",
189 "\t\t#' @param uuid The UUID of the project to query.",
190 "\t\tproject_properties_get = function(uuid)",
192 "\t\t\tprivate$get_project_by_list(uuid, list('uuid', 'properties'))$properties",
195 "\t\t#' @description Delete one property from a project by name.",
196 "\t\t#' @param oneProp Name of the property to delete.",
197 "\t\t#' @param uuid The UUID of the project to update.",
198 "\t\t#' @return A Group object.",
199 "\t\tproject_properties_delete = function(oneProp, uuid)",
201 "\t\t\tprojProp <- self$project_properties_get(uuid)",
202 "\t\t\tprojProp[[oneProp]] <- NULL",
203 "\t\t\tself$project_properties_set(projProp, uuid)",
206 "\t\t#' @description Convenience wrapper of `links_list` to create a permission link.",
207 "\t\t#' @param type The type of permission: one of `'can_read'`, `'can_write'`, or `'can_manage'`.",
208 "\t\t#' @param uuid The UUID of the object to grant permission to.",
209 "\t\t#' @param user The UUID of the user or group who receives this permission.",
210 "\t\t#' @return A Link object if one was updated, else NULL.",
211 "\t\tproject_permission_give = function(type, uuid, user)",
213 "\t\t\tlink <- list(",
214 "\t\t\t\t'link_class' = 'permission',",
215 "\t\t\t\t'name' = type,",
216 "\t\t\t\t'head_uuid' = uuid,",
217 "\t\t\t\t'tail_uuid' = user)",
218 "\t\t\tself$links_create(link)",
221 "\t\t#' @description Find an existing permission link and update its level.",
222 "\t\t#' @param typeOld The type of permission to find: one of `'can_read'`, `'can_write'`, or `'can_manage'`.",
223 "\t\t#' @param typeNew The type of permission to set: one of `'can_read'`, `'can_write'`, or `'can_manage'`.",
224 "\t\t#' @param uuid The UUID of the object to grant permission to.",
225 "\t\t#' @param user The UUID of the user or group who receives this permission.",
226 "\t\t#' @return A Link object if one was updated, else NULL.",
227 "\t\tproject_permission_update = function(typeOld, typeNew, uuid, user)",
229 "\t\t\tlinks <- self$links_list(filters = list(",
230 "\t\t\t\t\tlist('link_class', '=', 'permission'),",
231 "\t\t\t\t\tlist('name', '=', typeOld),",
232 "\t\t\t\t\tlist('head_uuid', '=', uuid),",
233 "\t\t\t\t\tlist('tail_uuid', '=', user)",
234 "\t\t\t\t), select=list('uuid'), count = 'none')$items",
235 "\t\t\tif (length(links) == 0) {",
236 "\t\t\t\tcat(format('No permission granted'))",
238 "\t\t\t\tself$links_update(list('name' = typeNew), links[[1]]$uuid)",
242 "\t\t#' @description Delete an existing permission link.",
243 "\t\t#' @param type The type of permission to delete: one of `'can_read'`, `'can_write'`, or `'can_manage'`.",
244 "\t\t#' @param uuid The UUID of the object to grant permission to.",
245 "\t\t#' @param user The UUID of the user or group who receives this permission.",
246 "\t\t#' @return A Link object if one was deleted, else NULL.",
247 "\t\tproject_permission_delete = function(type, uuid, user)",
249 "\t\t\tlinks <- self$links_list(filters = list(",
250 "\t\t\t\t\tlist('link_class', '=', 'permission'),",
251 "\t\t\t\t\tlist('name', '=', type),",
252 "\t\t\t\t\tlist('head_uuid', '=', uuid),",
253 "\t\t\t\t\tlist('tail_uuid', '=', user)",
254 "\t\t\t\t), select=list('uuid'), count = 'none')$items",
255 "\t\t\tif (length(links) == 0) {",
256 "\t\t\t\tcat(format('No permission granted'))",
258 "\t\t\t\tself$links_delete(links[[1]]$uuid)",
262 "\t\t#' @description Check for an existing permission link.",
263 "\t\t#' @param type The type of permission to check: one of `'can_read'`, `'can_write'`, `'can_manage'`, or `NULL` (the default).",
264 "\t\t#' @param uuid The UUID of the object to check permission on.",
265 "\t\t#' @param user The UUID of the user or group to check permission for.",
266 "\t\t#' @return If `type` is `NULL`, the list of matching permission links.",
267 "\t\t#' Otherwise, prints and invisibly returns the level of the found permission link.",
268 "\t\tproject_permission_check = function(uuid, user, type = NULL)",
270 "\t\t\tfilters <- list(",
271 "\t\t\t\tlist('link_class', '=', 'permission'),",
272 "\t\t\t\tlist('head_uuid', '=', uuid),",
273 "\t\t\t\tlist('tail_uuid', '=', user))",
274 "\t\t\tif (!is.null(type)) {",
275 "\t\t\t\tfilters <- c(filters, list(list('name', '=', type)))",
277 "\t\t\tlinks <- self$links_list(filters = filters, count='none')$items",
278 "\t\t\tif (is.null(type)) {",
281 "\t\t\t\tprint(links[[1]]$name)",
287 genClassContent <- function(methodResources, resourceNames)
289 arvadosMethods <- Map(function(resource, resourceName)
291 methodNames <- names(resource$methods)
293 functions <- Map(function(methodMetaData, methodName)
295 #NOTE: Index, show and destroy are aliases for the preferred names
296 # "list", "get" and "delete". Until they are removed from discovery
297 # document we will filter them here.
298 if(methodName %in% c("index", "show", "destroy"))
301 methodName <- paste0(resourceName, "_", methodName)
303 getMethodDoc(methodName, methodMetaData),
304 createMethod(methodName, methodMetaData)
307 }, resource$methods, methodNames)
309 unlist(unname(functions))
311 }, methodResources, resourceNames)
316 genAPIClassFooter <- function()
318 c("\t\t#' @description Return the host name of this client's Arvados API server.",
319 "\t\t#' @return Hostname string.",
320 "\t\tgetHostName = function() private$host,",
322 "\t\t#' @description Return the Arvados API token used by this client.",
323 "\t\t#' @return API token string.",
324 "\t\tgetToken = function() private$token,",
326 "\t\t#' @description Set the RESTService object used by this client.",
327 "\t\tsetRESTService = function(newREST) private$REST <- newREST,",
329 "\t\t#' @description Return the RESTService object used by this client.",
330 "\t\t#' @return RESTService object.",
331 "\t\tgetRESTService = function() private$REST",
338 "\t\tnumRetries = NULL,",
339 "\t\tget_project_by_list = function(uuid, select = NULL)",
341 "\t\t\tself$groups_list(",
342 "\t\t\t\tfilters = list(list('uuid', '=', uuid), list('group_class', '=', 'project')),",
343 "\t\t\t\tselect = select,",
344 "\t\t\t\tcount = 'none'",
345 "\t\t\t)$items[[1]]",
349 "\tcloneable = FALSE",
353 createMethod <- function(name, methodMetaData)
355 args <- getMethodArguments(methodMetaData)
356 signature <- getMethodSignature(name, args)
357 body <- getMethodBody(methodMetaData)
365 normalizeParamName <- function(name)
367 # Downcase the first letter
368 name <- sub("^(\\w)", "\\L\\1", name, perl=TRUE)
369 # Convert snake_case to camelCase
370 gsub("_(uuid\\b|id\\b|\\w)", "\\U\\1", name, perl=TRUE)
373 getMethodArguments <- function(methodMetaData)
375 request <- methodMetaData$request
378 if(!is.null(request))
380 resourceName <- normalizeParamName(request$properties[[1]][[1]])
383 requestArgs <- resourceName
385 requestArgs <- paste(resourceName, "=", "NULL")
388 argNames <- names(methodMetaData$parameters)
390 args <- sapply(argNames, function(argName)
392 arg <- methodMetaData$parameters[[argName]]
393 argName <- normalizeParamName(argName)
397 return(paste(argName, "=", "NULL"))
406 getMethodSignature <- function(methodName, args)
408 collapsedArgs <- paste0(args, collapse = ", ")
409 lineLengthLimit <- 40
411 if(nchar(collapsedArgs) > lineLengthLimit)
413 return(paste0("\t\t",
414 formatArgs(paste(methodName, "= function("),
415 "\t", args, ")", lineLengthLimit)))
419 return(paste0("\t\t", methodName, " = function(", collapsedArgs, ")"))
423 getMethodBody <- function(methodMetaData)
425 url <- getRequestURL(methodMetaData)
426 headers <- getRequestHeaders()
427 requestQueryList <- getRequestQueryList(methodMetaData)
428 requestBody <- getRequestBody(methodMetaData)
429 request <- getRequest(methodMetaData)
430 response <- getResponse(methodMetaData)
431 errorCheck <- getErrorCheckingCode(methodMetaData)
432 returnStatement <- getReturnObject()
436 requestQueryList, "",
438 request, response, "",
442 paste0("\t\t\t", body)
445 getRequestURL <- function(methodMetaData)
447 endPoint <- methodMetaData$path
448 endPoint <- stringr::str_replace_all(endPoint, "\\{", "${")
449 url <- c(paste0("endPoint <- stringr::str_interp(\"", endPoint, "\")"),
450 paste0("url <- paste0(private$host, endPoint)"))
454 getRequestHeaders <- function()
456 c("headers <- list(Authorization = paste(\"Bearer\", private$token), ",
457 " \"Content-Type\" = \"application/json\")")
460 getRequestQueryList <- function(methodMetaData)
462 queryArgs <- names(Filter(function(arg) arg$location == "query",
463 methodMetaData$parameters))
465 if(length(queryArgs) == 0)
466 return("queryArgs <- NULL")
468 queryArgs <- sapply(queryArgs, function(arg) {
469 arg <- normalizeParamName(arg)
472 collapsedArgs <- paste0(queryArgs, collapse = ", ")
474 lineLengthLimit <- 40
476 if(nchar(collapsedArgs) > lineLengthLimit)
477 return(formatArgs("queryArgs <- list(", "\t\t\t\t ", queryArgs, ")",
480 return(paste0("queryArgs <- list(", collapsedArgs, ")"))
483 getRequestBody <- function(methodMetaData)
485 request <- methodMetaData$request
487 if(is.null(request) || !request$required)
488 return("body <- NULL")
490 resourceName <- normalizeParamName(request$properties[[1]][[1]])
492 requestParameterName <- names(request$properties)[1]
494 c(paste0("if(length(", resourceName, ") > 0)"),
495 paste0("\tbody <- jsonlite::toJSON(list(", resourceName, " = ", resourceName, "), "),
496 "\t auto_unbox = TRUE)",
501 getRequest <- function(methodMetaData)
503 method <- methodMetaData$httpMethod
504 c(paste0("response <- private$REST$http$exec(\"", method, "\", url, headers, body,"),
505 " queryArgs, private$numRetries)")
508 getResponse <- function(methodMetaData)
510 "resource <- private$REST$httpParser$parseJSONResponse(response)"
513 getErrorCheckingCode <- function(methodMetaData)
515 if ("ensure_unique_name" %in% names(methodMetaData$parameters)) {
516 body <- c("\tif (identical(sub('Entity:.*', '', resource$errors), '//railsapi.internal/arvados/v1/collections: 422 Unprocessable ')) {",
517 "\t\tresource <- cat(format('An object with the given name already exists with this owner. If you want to update it use the update method instead'))",
519 "\t\tstop(resource$errors)",
522 body <- "\tstop(resource$errors)"
524 c("if(!is.null(resource$errors)) {", body, "}")
527 getReturnObject <- function()
532 genAPIClassDoc <- function(methodResources, resourceNames)
536 "#' arv <- Arvados$new(\"your Arvados token\", \"example.arvadosapi.com\")",
538 "#' collection <- arv$collections.get(\"uuid\")",
540 "#' collectionList <- arv$collections.list(list(list(\"name\", \"like\", \"Test%\")))",
541 "#' collectionList <- listAll(arv$collections.list, list(list(\"name\", \"like\", \"Test%\")))",
543 "#' deletedCollection <- arv$collections.delete(\"uuid\")",
545 "#' updatedCollection <- arv$collections.update(list(name = \"New name\", description = \"New description\"),",
548 "#' createdCollection <- arv$collections.create(list(name = \"Example\",",
549 "#' description = \"This is a test collection\"))",
554 getAPIClassMethodList <- function(methodResources, resourceNames)
556 methodList <- unlist(unname(Map(function(resource, resourceName)
558 methodNames <- names(resource$methods)
561 methodNames[!(methodNames %in% c("index", "show", "destroy"))])
563 }, methodResources, resourceNames)))
565 hardcodedMethods <- c("projects.create", "projects.get",
566 "projects.list", "projects.update", "projects.delete")
567 paste0("#' \t\\item{}{\\code{\\link{", sort(c(methodList, hardcodedMethods)), "}}}")
570 getMethodDoc <- function(methodName, methodMetaData)
572 description <- paste("\t\t#' @description", gsub("\n", "\n\t\t#' ", methodMetaData$description))
573 params <- getMethodParams(methodMetaData)
574 returnValue <- paste("\t\t#' @return", methodMetaData$response[["$ref"]], "object.")
576 c(description, params, returnValue)
579 getMethodParams <- function(methodMetaData)
581 request <- methodMetaData$request
584 if(!is.null(request))
586 requestDoc <- unname(unlist(sapply(request$properties, function(prop)
588 className <- sapply(prop, function(ref) ref)
589 objectName <- normalizeParamName(className)
590 paste("\t\t#' @param", objectName, className, "object.")
594 argNames <- names(methodMetaData$parameters)
596 argsDoc <- unname(unlist(sapply(argNames, function(argName)
598 arg <- methodMetaData$parameters[[argName]]
599 paste("\t\t#' @param",
600 normalizeParamName(argName),
601 gsub("\n", "\n\t\t#' ", arg$description)
605 c(requestDoc, argsDoc)
608 #NOTE: Utility functions:
610 # This function is used to split very long lines of code into smaller chunks.
611 # This is usually the case when we pass a lot of named argumets to a function.
612 formatArgs <- function(prependAtStart, prependToEachSplit,
613 args, appendAtEnd, lineLength)
617 args[1:(length(args) - 1)] <- paste0(args[1:(length(args) - 1)], ",")
620 args[1] <- paste0(prependAtStart, args[1])
621 args[length(args)] <- paste0(args[length(args)], appendAtEnd)
623 argsLength <- length(args)
627 while(index <= argsLength)
632 while(nchar(line) < lineLength && index <= argsLength)
634 line <- paste(line, args[index])
638 argLines <- c(argLines, line)
641 argLines <- unlist(argLines)
642 argLinesLen <- length(argLines)
645 argLines[2:argLinesLen] <- paste0(prependToEachSplit, argLines[2:argLinesLen])
650 args <- commandArgs(TRUE)
651 if (length(args) == 0) {
652 loc <- "arvados-v1-discovery.json"
656 discoveryDocument <- getAPIDocument(loc)
657 generateAPI(discoveryDocument)