Skip to contents

Screen hand-written wrappers, propose explicit inputs, request, and docs mappings for one service, and keep only the proposals that verify_adoption() confirms are equivalent.

Usage

propose_mappings(root, files, service, config = "specmill.yml", callbacks = new.env(parent = emptyenv()), route = NULL, bindings = NULL, prelude = NULL)

Arguments

root

Explicit existing client root directory.

files

Source files to screen, relative to root.

service

ID of the service whose helper and selected operations the wrappers map to. The service must be defined in its own service file.

config

Project YAML path relative to root.

callbacks

Explicit environment containing named development callbacks.

route

Optional function(name, call) returning the operation key, such as "GET /items/{item_id}", for a wrapper's helper call. By default, literal method and path helper arguments must match a selected operation exactly.

bindings

Optional function(expr) returning a request binding (for example list(callback = "batch_limit")) for a helper argument expression, or NULL when it does not apply.

prelude

Optional function(code) receiving the wrapper's body statements. It returns NULL, or a list with statements (how many leading statements it accounts for) and arguments (request bindings for the helper arguments those statements build).

Value

A list with operations (per wrapper: file, status "proposed" or "retained", reason, and the matched operation key), proposals (verified mappings keyed by operation), yaml (the proposals as a service operations: map), and contracts and files as returned by verify_adoption().

Details

Nothing in root is written. A wrapper is proposed when its body is a single call to the service helper (optionally result <- helper(...); result or return(result)), after any statements the prelude callback accounts for. Its defaults must be plain data literals, and its roxygen block may use only a title, a lifecycle badge and description, @param, @return, @export, and @examples that call the wrapper with literal inputs. Helper arguments bind a formal to from, a data literal to value, and a list() of those to object or array; the bindings callback handles anything else. Every other wrapper is retained with a reason, as is any proposal that fails verification against the staged service configuration.

Review yaml, copy it into the service file, and adopt as described in verify_adoption().

See also

Examples

# Recognize `query <- list(); if (!is.null(x)) query$key <- x; ...` before the helper call.
# The optional values it collects are exactly a compact_object binding.
query_prelude <- function(code) {
  if (!length(code) || !identical(code[[1L]], quote(query <- list()))) {
    return(NULL)
  }
  fields <- list()
  for (statement in code[-1L]) {
    assignment <- if (is.call(statement) && identical(statement[[1L]], as.name('if'))) statement[[3L]]
    target <- if (is.call(assignment) && identical(assignment[[1L]], as.name('<-'))) assignment[[2L]]
    value <- if (!is.null(target)) assignment[[3L]]
    if (
      !is.call(target) || !identical(target[[1L]], as.name('$')) ||
        !identical(target[[2L]], as.name('query')) || !is.symbol(value) ||
        !identical(statement[[2L]], call('!', call('is.null', value)))
    ) {
      break
    }
    fields[[as.character(target[[3L]])]] <- list(from = list('params', as.character(value)))
  }
  list(statements = 1L + length(fields), arguments = list(query = list(compact_object = fields)))
}
if (FALSE) { # \dontrun{
proposal <- specmill::propose_mappings(root, 'R/items.R', 'catalogue', prelude = query_prelude)
proposal$operations
cat(proposal$yaml)
} # }