Endpoint functions in gemmaAPI follow a simple pattern and their documentation is lifted from the documentation page for the restful API unless R specific clarifications/changes are required.
-
Update the documentation: The request will be specified by the
requestargument. Add the name and description of the request as an item under the request section as shown below#' @param request Character. If NULL retrieves the dataset object. Otherwise #' \itemize{ #' \item \code{platforms}: Retrieves platforms for the given dataset #' \item \code{newRequest}: does whatever the request does #' }If the request has arguments create another itemized list under the request name and description
#' @param request Character. If NULL retrieves the dataset object. Otherwise #' \itemize{ #' \item \code{platforms}: Retrieves platforms for the given dataset #' \item \code{newRequest}: does whatever the request does #' \itemize{ #' \item \code{argumentForNewRequest}: this arg is needed because... #' \item \code{otherArgForNewRequest}: this arg is needed because... #' } #' } -
Add the request as an aviable option: All endpoint functions check request with
match.args. Go to this line and add your requestrequest = match.args(request, choices = c('platforms', 'newRequest'))
-
Add the accepted arguments as available arguments: All endpoint functions check for allowed arguments for a given request. If the new request arguments, add them to the list named
allowedArgumentsallowedArguments = list( # ... allowed arguments for other requests are listed here newRequest = c('argumentForNewRequest', 'otherArgForNewRequest'))
-
Add the mandatory arguments: Similar to
allowedArgumentslist, all endpoint functions check to see if all mandatory arguments for a request is probided. If the new request has mandatory arguments add them to the list namedmandatoryArgumentsmandatoryArguments = list(# ... mandatory arguments for other requests are listed here newRequest = c('argumentForNewRequest'))
In this case when the endpoint is called with
request = newRequestit will give a warning if any arguments other thanargumentForNewRequestandotherArgForNewRequestis provided and an error ifargumentForNewRequestis not provided.- Build the URL for the request: Below the lines you just edited, you'll see logic for building the URLs. Normally, a base url is constructed and request specific additions are added based on what request is made.
gluefunction is used to do this in a clean looking way. Any arguments for a request is added bystringArg,logicArgandnumberArgfunctions.stringArgallows both numbers and strings since many string inputs also accept identifiers in numeric form. It also usesURLencodeon the input.logicArgmakes sure the input is logical andnumberArgmakes sure it is numeric. If the inputs of these functions are NULL they'll return empty strings so no need to check if a non-mandatory input is provided or not. Note that theaddNameargument can be used to control if names of the arguments should be added.
stringArg(anArg = 'Arbitrary*String',anotherArg = 'Arbitrary_String',addName= TRUE)
## anArg=Arbitrary%2AString&anotherArg=Arbitrary_StringstringArg(anArg = 'Arbitrary*String',anotherArg = 'Arbitrary_String',addName= FALSE)
## Arbitrary%2AString&Arbitrary_StringNote that if the request does not follow the basic formula, you may have to re-write the url from scratch.
if(request ='platforms'){ url = glue::glue(url,'/platforms') } else if(request=='newRequest'){ url = glue::glue(url,'/newRequest?',stringArg(argumentForNewRequest = argumentForNewRequest),'&', logicArg(otherArgForNewRequest = otherArgForNewRequest)) }
- Build the URL for the request: Below the lines you just edited, you'll see logic for building the URLs. Normally, a base url is constructed and request specific additions are added based on what request is made.
-
If the output is a list, name the elements: If the output of the request is a gzipped table, it will be read and returned as a dataframe, but in most cases output is a JSON which is returned as a list.
The output is saved in
contentobject, whose elements are renamed before returning. An identifying property of the elements is used as the name. This property must always be defined and ideally be unique to the element. For instance in the below example, platforms always have shortNames and they are good identifying strings so they are used. For ournewRequestwe choose thenamingElement.if(return){ if(is.null(request)){ names(content) = content %>% purrr::map_chr('officialSymbol') } else if (request %in% 'plaftorm'){ names(content) = content %>% purrr::map_chr('shortName') } else if(request %in% 'newRequest'){ names(content) = content %>% purrr::map_chr('namingElement') } }
-
Write tests: Each endpoint has it's own test file in 'tests/testthat' repo New test files must start with the name
testto be visible totestthatpackage. A minimal testing should be done to ensure the output has the right type and and is not empty. Certain behaviors of Gemma API changed over time so it is good to provide tests for inputs with expected lengths so you can fail if something changes in the future again. Errors from absance of mandatory inputs could be included. Below some examples are provided.-
Validating the output type
testthat::expect_is(datasetInfo('GSE12679',request = 'differential', qValueThreshold = 1),'list')
-
Validating the length of the output
testthat::expect_length(datasetInfo('GSE81454'),1) testthat::expect_true(length(datasetInfo('GSE81454', request = 'samples'))>0)
-
Error from absance of mandatory input
testthat::expect_error(datasetInfo('GSE81454',request = 'differential'), regexp = 'qValueThreshold')
-