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
request
argument. 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
allowedArguments
allowedArguments = list( # ... allowed arguments for other requests are listed here newRequest = c('argumentForNewRequest', 'otherArgForNewRequest'))
-
Add the mandatory arguments: Similar to
allowedArguments
list, 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 namedmandatoryArguments
mandatoryArguments = list(# ... mandatory arguments for other requests are listed here newRequest = c('argumentForNewRequest'))
In this case when the endpoint is called with
request = newRequest
it will give a warning if any arguments other thanargumentForNewRequest
andotherArgForNewRequest
is provided and an error ifargumentForNewRequest
is 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.
glue
function is used to do this in a clean looking way. Any arguments for a request is added bystringArg
,logicArg
andnumberArg
functions.stringArg
allows both numbers and strings since many string inputs also accept identifiers in numeric form. It also usesURLencode
on the input.logicArg
makes sure the input is logical andnumberArg
makes 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 theaddName
argument 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_String
stringArg(anArg = 'Arbitrary*String',anotherArg = 'Arbitrary_String',addName= FALSE)
## Arbitrary%2AString&Arbitrary_String
Note 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
content
object, 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 ournewRequest
we 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
test
to be visible totestthat
package. 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')
-