tl;dr: the current REST router path parameter validation and path matching make certain endpoint combinations confusing or impossible. Decide how to resolve this.
Update: we are strongly leaning toward Possibility #3 below: prohibit non-required path parameters in the route, but allow them at the Handler level, thereby resolving the path conflict issue, while still allowing the implementation convenience of letting handlers serve multiple routes
Current Status
Consider the following three REST endpoints:
- /myextension/foo
- /myextension/foo/
- /myextension/foo/{id}
The REST PathMatcher will not allow all three of these to exist. It will detect a conflict between #2 and #3
Considering this to be a conflict is necessary because path parameters can be marked as non-required (and therefore omitted from the request). If presented with a request for /foo/, the PathMatcher would not be able to determine whether it should invoke #2 or #3.
It is worth mentioning that in an informal poll of MediaWiki developers familiar with APIs but unfamiliar with the details of the REST PathMatcher, opinions were divided on whether #1 and #2 would conflict, or whether #2 and #3 would conflict. This indicates the current behavior is unclear to developers, which is inherently undesirable if it can be avoided.
Motivating Issue
This is undesirable, because there are real circumstances where we wish to have both /foo/ and /foo/{id}. Specifically, in reimplementing the Reading Lists API in REST as part of RESTBase deprecation (see T336693: Re-implement reading lists REST interface outside RESTbase), we are obligated to match the existing GET /lists/ path currently provided by RESTBase and in use by callers. But we also wish to provide a GET /lists/{id} endpoint, to maintain feature parity with the existing Action API Reading List endpoints (and hopefully retire the Action API endpoints, per T348494: Reading List REST Interface: remove Action API endpoints, leaving us only one API to maintain for Reading Lists).
This is not the only existing API where this will come up. For example /page/ from Page Content has the same challenge. Reading Lists just happens to be the one we noticed first.
Possible Solutions
There are several possible resolutions, including at least:
- Do nothing. For the case of Reading Lists, implement only /lists and /lists/{id}, and redirect /lists/ to /lists at the API Gateway level. In this specific case, {id} is a required parameter, so there would be no ambiguity in a gateway-level redirect. And then hope this situation doesn't come up again in other contexts.
- Disallow non-required path parameters. This would allow the PathMatcher to (theoretically) distinguish between /foo/ and /foo/{id}, because the {id} portion would always be present. It is unconfirmed but seems likely that an efficient modification could be made to PathMatcher to distinguish these cases.
- Disallow non-required path parameters at the PathMatcher level, but continue to allow them at the Handler level, in getParamSettings(). This would allow handlers to still serve multiple routes, some of which may not supply all possible path parameters, while still resolving the motivating issue.
- Allow templates to indicate to PathMatcher whether a path parameter is required or not. PathMatcher could then use this additional information to determine if a conflict truly exists. Downsides include additional complexity in the PathMatcher implementation, additional and perhaps non-obvious consideration for developers creating endpoints, and the possibility that the actual parameter definitions in the REST handlers do not match the endpoint templates. (In other words, a parameter might be indicated as required in the template, but as not required in the Handler code. Automatic detection might be possible at the expense of more complex Handler code, and an unknown increase in execution time.)
- Do something hand-wavey with redirects
- Something else I didn't think of
Some of these possibilities allow us to cleanly move to others in the future if desired. For example, #2 would resolve the current situation and would not prohibit implementing #4 in the future. Also note note that the implementation of non-required parameters is currently incomplete: default values assigned in the handler's parameter definition are ignored, and non-required parameters are always received as empty strings. However, there are some endpoints with non-required path parameters in production, so maybe this is a non-starter:
- HtmlInputTransformHelper
- TransformHandler (in core)
- TransformHandler (in the Parsoid extension, inherits from the core TransformHandler)
- MenteesPrefixSearchHandler
- PageHandler
- DokuApi
Additional Considerations
If we make any choice that continues to support non-required path parameters, we should also make their default values function correctly.