Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mounted router fall back to parent router when route is not found #883

Draft
wants to merge 18 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions R/plumber-static.R
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,10 @@ PlumberStatic <- R6Class(
path <- httpuv::decodeURIComponent(path)
abs.path <- resolve_path(direc, path)
if (is.null(abs.path)) {
# TODO: Should this be inherited from a parent router?
val <- private$notFoundHandler(req=req, res=res)
return(val)
# If the file doesn't exist and isn't mounted,
# the 404 handler will be called anyways.
# So, we can always `forward()` here when the file isn't found.
return(forward())
}

ext <- tools::file_ext(abs.path)
Expand Down
8 changes: 8 additions & 0 deletions R/plumber-step.R
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,14 @@ resetForward <- function() {
exec$forward <- FALSE
}

# Handle mounted routes not being found
routeNotFound <- function() {
structure(list(), class = "plumber_route_not_found")
}
isRouteNotFound <- function(x) {
inherits(x, "plumber_route_not_found")
}

#' plumber step R6 class
#' @description an object representing a step in the lifecycle of the treatment
#' of a request by a plumber router.
Expand Down
34 changes: 30 additions & 4 deletions R/plumber.R
Original file line number Diff line number Diff line change
Expand Up @@ -767,13 +767,31 @@ Plumber <- R6Class(
function(...) {
resetForward()
# TODO: support globbing?

if (nchar(path) >= nchar(mountPath) && substr(path, 0, nchar(mountPath)) == mountPath) {
# This is a prefix match or exact match. Let this router handle.
# This is a prefix match or exact match. Let mount attempt handle.

# Mark that the route is being handled within a mount.
# Allows for the mount to forward to the next mount instead of 404.
prev_mount_status <- req$`_IS_MOUNT`
req$`_IS_MOUNT` <- TRUE

# First trim the prefix off of the PATH_INFO element
cur_path_info <- req$PATH_INFO
req$PATH_INFO <- substr(req$PATH_INFO, nchar(mountPath), nchar(req$PATH_INFO))
return(private$mnts[[mountPath]]$route(req, res))

# Handle route
ret <- private$mnts[[mountPath]]$route(req, res)

# Undo path info and mount status changes
req$PATH_INFO <- cur_path_info
req$`_IS_MOUNT` <- prev_mount_status
schloerke marked this conversation as resolved.
Show resolved Hide resolved

if (isRouteNotFound(ret)) {
# Forward to the parent router if mounted router can't handle route
return(forward())
}
# Return the regular value from the mounted router
return(ret)
} else {
return(forward())
}
Expand Down Expand Up @@ -825,7 +843,15 @@ Plumber <- R6Class(
}

# Notify that there is no route found
private$notFoundHandler(req = req, res = res)
is_mount <- req$`_IS_MOUNT`
if (isTRUE(is_mount)) {
# If this is a mounted router, we need to forward to the parent router
# This value is used above when retrieving values from a mount
# Do not change this value without updating the recursive mount code above
return(routeNotFound())
} else {
private$notFoundHandler(req = req, res = res)
}
}
steps <- append(steps, list(notFoundStep))

Expand Down
14 changes: 14 additions & 0 deletions tests/testthat/files/router.R
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,17 @@ function(res){
function(){
"dual path"
}

#* @plumber
function(pr) {
mnt_1 <-
pr() %>%
pr_get("/hello", function() "say hello")
mnt_2 <-
pr() %>%
pr_get("/world", function() "say hello world")

pr %>%
pr_mount("/say", mnt_1) %>%
pr_mount("/say/hello", mnt_2)
}
6 changes: 6 additions & 0 deletions tests/testthat/test-routing.R
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ test_that("Routing to errors and 404s works", {
expect_equal(r$route(make_req("GET", "/path1"), res), "dual path")
expect_equal(r$route(make_req("GET", "/path2"), res), "dual path")

## Mounts fall back to parent router when route is not found
# Mount at `/say` with route `/hello`
expect_equal(r$route(make_req("GET", "/say/hello"), res), "say hello")
# Mount at `/say/hello` with route `/world`
expect_equal(r$route(make_req("GET", "/say/hello/world"), res), "say hello world")

expect_equal(errors, 0)
expect_equal(notFounds, 0)

Expand Down