Repository navigation
Conversation
Requests are upper-cased in ResourceDocMiddleware, lookups and comparisons ignore case, and the upperCaseStoredCurrencyCodes migration rewrites stored codes in upper case, logging each column it changed.
transaction-request type and SCA method as a literal URL segment. The hand-kept literal list had fallen behind, so the v7 MOBILE_WALLET template claimed UTILITY, BULK and OPEN_CORRIDOR_PROMISE requests and gave HOLD, CARDANO and Ethereum on v7 an empty 404 instead of falling through to v6; v6 HOLD claimed CARDANO and Ethereum, the v4 TRANSACTION_REQUEST_TYPE doc claimed every v4 type, and Berlin Group getPaymentInformation claimed the signing-basket status and authorisation requests. ResourceDocSelfResolveTest checks that every registered doc's own URL resolves to that doc, and TransactionRequestTypeRoutingTest checks the v7-to-v6 fall-through and that disabling one type leaves the others on.
allows with 400 (OBP-10068) instead of cutting it off when stored. ResourceDocMiddleware checks amounts paired with a currency in the request body and query string; crypto assets are not checked until their real precision is recorded.
upperCaseStoredCurrencyCodes migration, so it also works on H2, which stores names in upper case and is what CI runs on.
createTransactionRequestFreeForm, which now behaves as its ResourceDoc says. Its odd answer came from the matcher treating FREE_FORM as a placeholder, not from a design choice, and went away when FREE_FORM became a literal segment.
request's challenge could credit wrong sandbox account, because the payee was looked up again by routing as the payer wrote it and the empty secondary-routing fallback matched anyone's counterparty. SIMPLE and OPEN_CORRIDOR_PROMISE requests now record their counterparty id at creation and the challenge pays that counterparty.
…ameter is empty GET /banks/BANK_ID/api-products/API_PRODUCT_CODE/subscriptions requires canGetApiProductSubscriptionAtOneBank, which only ResourceDocMiddleware enforces. With an empty API_PRODUCT_CODE segment the http4s route still matches but the middleware finds no ResourceDoc, so the handler runs unvalidated and answers 500 (Bank not found in CallContext) instead of 403 for a caller without the role. The empty-segment scenario fails until the middleware selects the ResourceDoc from the route that serves the request.
…ct the doc by route ResourceDocMiddleware finds the ResourceDoc of a request by matching the URL against the doc templates, separately from the http4s route that runs the handler. The two can disagree, and then the request is validated under one endpoint's roles, enable/disable switch and operationId while another endpoint's handler runs. Add Http4sRoute, which keeps the pattern match of an endpoint so that it can be asked whether it serves a request, and ResourceDocMatcher.selectByRoute, which returns the doc of the first route that does. When several docs share one route, their templates tell them apart. ResourceDocMiddleware tries the route-bound docs first and runs only the selected route, wrapped in the new `wrap` argument; docs without a route are still found by template, so versions that have not been converted behave as before. An implicit conversion from HttpRoutes[IO] keeps the existing `http4sPartialFunction = Some(routes)` sites compiling.
Declare each v7.0.0 endpoint as an Http4sRoute instead of HttpRoutes.of, so the middleware can ask it whether it serves a request. The same ordered doc list now drives both selecting the doc and running the routes, and ResourceDocMiddleware runs only the selected route, wrapped in the idempotency middleware. The v7.0.0 to v6.0.0 bridge asks the routes whether any of them serves a path instead of matching the URL against templates. A request whose path parameter is empty (/banks/B/api-products//subscriptions) is now served by the route that matches it and validated under that route's doc, so its roles are enforced instead of the handler running unvalidated. ResourceDocRouteBindingTest checks that every v7.0.0 doc is bound to a route that serves its own URL and that selecting by route returns that doc.
Explain how an endpoint is declared as an Http4sRoute so the middleware selects its ResourceDoc by route, and how that differs from the template matching that versions not yet converted still use.
GET /obp/v7.0.0/management/traffic/top-callers lists only the 50 busiest caller and endpoint pairs of the window, and the window is the current minute of the JVM. Each pair the test looks for has a single request, so when other suites had made more than 50 pairs in the same minute the pair was cut off and the test failed. It passed alone and failed after AuthSweepTest in one JVM. Clear the record at the start of the scenario so it only sees its own requests.
hongwei1
force-pushed
the
refactor/route-bound-resource-doc
branch
from
October 7, 2026 11:09
4e5c610 to
25f2e72
Compare
… route-selected doc A route can serve a path with an empty segment, such as /banks/B/api-products//subscriptions, and the doc selected for it is the route's. extractPathParams dropped empty segments before comparing the path with the template, so the lengths differed and it returned no parameters. BANK_ID, ACCOUNT_ID and VIEW_ID were then never validated, and the role check asked for the role at bank "": a caller holding the role at bank B was refused with 403, and a handler that needs the bank or account answered 500. Read the parameters by position with the empty segment kept when that matches the template, and fall back to the previous reading otherwise, so docs that are still matched by template behave as before.
ResourceDocRouteBindingTest sent each doc's URL through selectByRoute with the docs in registration order, but the v7.0.0 middleware selects from the docs sorted by segment count, the order its routes are tried in. A doc shadowed by another one in that order would have passed the test and been validated and run as the wrong endpoint in production. Use the ordered list.
…oute serves selectByRoute asked every route-bound doc on every request. The v7.0.0 middleware sits ahead of the older versions in the chain, so a v4 or v3 request paid for about 120 pattern matches in v7.0.0 first, and the docs that share a route were looked up again with a pass over all docs each time. Build an index of the route-bound docs by verb and API version when the middleware is built, with the docs that share a route worked out once, and ask only the routes of the request's verb and version. The v7.0.0 to v6.0.0 bridge uses the same index. When every doc of a group carries its route, a request no route serves is passed on at once, before the CallContext is built: nothing else in the group could serve it, so resolving the caller and running all the routes again could only find nothing. Also report a route that is null (an endpoint val declared after the resourceDocs += line that uses it) by the endpoint's name when the middleware is built, and say why a doc that shares a route with others falls back to the first of them when no template matches.
hongwei1
force-pushed
the
refactor/route-bound-resource-doc
branch
from
October 7, 2026 14:47
2a22501 to
9d18edd
Compare
Add Http4sRoute.chain, which tries handlers in the order given, and ResourceDocMatcher.orderByRoutes, which puts a version's docs in the order its routes are tried. Docs that share a route stay together and an alias of a route counts as the same route. A doc whose route is missing from the chain is an error naming the doc, unless the route is listed as deliberately run outside the middleware; a doc with no route documents an endpoint served elsewhere and is left out. The route index is bucketed by apiShortVersion so Berlin Group and UK Open Banking versions are found as well.
Declare each endpoint as an Http4sRoute and derive the route chain from one list, so the middleware selects the doc of the route that serves a request and runs only that route. The path-rewriting bridge asks the routes whether the version serves a path instead of matching the URL against doc templates.
Declare each endpoint as an Http4sRoute and derive the route chain from one list, so the middleware selects the doc of the route that serves a request and runs only that route. The path-rewriting bridge asks the routes whether the version serves a path instead of matching the URL against doc templates.
Declare each endpoint as an Http4sRoute and derive the route chain from one list, so the middleware selects the doc of the route that serves a request and runs only that route. The path-rewriting bridge asks the routes whether the version serves a path instead of matching the URL against doc templates.
Declare each endpoint as an Http4sRoute and derive the route chain from one list, so the middleware selects the doc of the route that serves a request and runs only that route. The path-rewriting bridge asks the routes whether the version serves a path instead of matching the URL against doc templates. The createTransactionRequestAccount doc now documents the ACCOUNT URL, as the Lift baseline does; a request for a type that has no doc of its own is validated under it, the first doc of the shared route.
Declare each endpoint as an Http4sRoute and derive the route chain from one list, so the middleware selects the doc of the route that serves a request and runs only that route. The path-rewriting bridge asks the routes whether the version serves a path instead of matching the URL against doc templates. createConsent is shared by the EMAIL, SMS and IMPLICIT docs. The message-docs Swagger doc has no route here (Http4sResourceDocs serves it) and stays out of the middleware's docs.
Declare each endpoint as an Http4sRoute and derive the route chain from one list, so the middleware selects the doc of the route that serves a request and runs only that route. The path-rewriting bridge asks the routes whether the version serves a path instead of matching the URL against doc templates.
Declare each endpoint of v1.2.1, v1.3.0, v1.4.0, v2.0.0, v2.1.0 and v2.2.0 as an Http4sRoute and derive the route chain from one list. bankById keeps running before the middleware so an unknown bank answers 400, not 464, and is listed as outside the chain.
Each group lists its routes in order and the aggregator concatenates them, so the middleware selects the doc of the route that serves a request. A signing basket's status and authorisations no longer fall to getPaymentInformation, whose template is made only of placeholders. The v2 routes are declared as lazy vals because their docs are registered before them.
…ceDoc to its route Each group lists its routes in order and the aggregator concatenates them, so the middleware selects the doc of the route that serves a request.
…ect real URLs ResourceDocRouteBindingTest covers all 18 catalogs (v1.2.1 to v7.0.0, Berlin Group v1.3 and v2, UK Open Banking v2.0.0, v3.1.0, v4.0.1): each doc's own route must serve its own URL and selection must return that doc. ResourceDocRouteOrderTest covers orderByRoutes and Http4sRoute.chain. ResourceDocRealCatalogSelectionTest selects real URLs that used to be given another endpoint's doc: the v4.0.0 and v6.0.0 transaction-request types, the types v7.0.0 has no route for, and the Berlin Group signing-basket status and authorisations.
…he parity allowlist The doc now documents the ACCOUNT URL of the Lift baseline; only the VIEW_ID to GRANT_VIEW_ID difference remains, and its digest is recomputed with allowlist_helper.py.
Every doc of every catalog now carries its route, so the middleware selects the doc by asking the routes and passes on a request no route serves. Delete the URL-template index and lookup (buildIndex, findResourceDoc), the guess that a capitalised segment is a placeholder (isTemplateVariable, literalAllCapsSegments), the no-doc branch of the middleware and the caller resolution it needed. ResourceDocMiddleware.apply now returns the routes itself: it runs the route of the selected doc, so it no longer takes the chain as a second argument. A doc with no route, or a null one, fails when the middleware is built and names the doc. Docs that share one route are told apart by the segments the request has in common with each template, with the first doc as the fallback, instead of by a list of enum values. The path parameters read from a template are the four the middleware validates: BANK_ID, ACCOUNT_ID, VIEW_ID and COUNTERPARTY_ID. ResourceDocSelfResolveTest is replaced by ResourceDocRouteBindingTest, which checks the same thing through the routes. ResourceDocMatcherTest keeps the path parameter and CallContext scenarios.
Rewrite the guidance on declaring an endpoint, the route chain, docs that share a route and the empty path segment gotcha, now that no version matches docs by URL template.
elasticSearchWarehouse and elasticSearchMetrics documented /search/warehouse and
/search/metrics, but their routes serve /search/warehouse/{query} and
/search/metrics/{query}: the query is a path segment, so the documented URL could
not be called. Matching docs by template hid this, because the template validated
a URL no route served. The docs now name the segment, and the parity allowlist
records the difference from the Lift baseline.
Authenticating a consent request writes: it creates the consent's user and copies the consent's Roles onto it. Http4sDynamicEntity authenticated inside the write transaction, so those writes were uncommitted when the handler's own checks read them on other connections: a consent that carried the Role was refused with 403 (OBP-20006), and a personal row was owned by the consent user instead of the human who granted the consent, so the consent could not read back what it wrote. The first link of the version chain used to authenticate the request outside any transaction and committed those writes as a side effect; every version now serves only the requests its routes serve, so nothing did. Resolve the caller before the transaction opens, as ResourceDocMiddleware does for every other endpoint. DynamicEntityConsentUserTest covers it: two scenarios failed on the shard that runs v6.0.0.
The holder shared a caller resolved by the first version hop that had no ResourceDoc for a request. Every version group now passes on a request none of its routes serves without resolving anything, so nothing fills or reads it. Update the doc comment of resolveCallerWithoutRateLimiting, whose one user is now the dynamic entity service.
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



No description provided.