Endpoints in api-definitions
can optionally specify request and response transformations between the public client and the internal downstream service. The full set of CPR request transforms can be found in Internal Product Docs: Request Transform Glossary and Response Transform Glossary. Each endpoint can have a different set of request and response transformations. The same transformation can also appear multiple times in the chain of transformations specified by each endpoint, with different parameters.
Request and response transformations are very powerful, and have exclusive access during execution to the full ProxyRequestContext
object for the ongoing request. They can modify everything about the request and response, including HTTP headers, body, method, and URI.
In order to implement a request or response transformation from the glossary above, or to extend the capabilities of RestProxy by adding a new type of transformation, follow the steps below.
The requestPathTransform request transform is implemented in the recipe below.
Recipe steps:
- Add the transform function and unit test
- Add the OpenAPI vendor extension to the sample document
- Integrate the transform function into the codegen template
- Add an integration test
- Update documentation
CPR request and response transformations are implemented in the server.cpr
package in the following directory:
-
Request transformations: CprRequestTransforms.java
-
Response transformations: CprResponseTransforms.java
Each transformation function should be implemented as a public method in the appropriate top-level class.
For example, see the requestPathTransform
method in CprRequestTransforms.java
The method signature and return type is flexible, depending on the transformation function. A typical parameter for request transformations is DownstreamRequest
, and ClientResponse
for response transformations. Other parameters will depend on how you want to integrate this CPR into the OpenAPI vendor extension in Step 3.
Note that the method should not be static. This allows CPR functions access to an injected RestProxyConfiguration
object, which allows CPRs to be configured through service.yaml
.
Also note that method mutates DownstreamRequest
-- this is the preferred method of applying request transformations. Each CPR request transform specified for the endpoint has exclusive access to the DownstreamRequest
instance before the next transform in the chain. For response transformations, implementations should modify ClientResponse
, i.e., the response object that will eventually be returned to the client.
To easily add a unit test, add a test class that extends BaseRequestTransformTest. See the Javadoc for detailed usage instructions.
Add a separate class for each CPR transform function.
The RequestPathTransformTest test class is added to exercise the requestPathTransform
function added above.
As part of inheriting from BaseRequestTransformTest
, your test methods get a pre-initialized instance of requestToInternal
filled with fake data that can be used for unit tests.
Run mvn clean package
to ensure that your tests pass. Depending on the complexity of the transform function, this might be a good place to submit a PR for team review.
At this point, the logic for the new CPR transform has been implemented and unit tested, but it has not been integrated with the server yet.
The sample-twilio-com.yaml is a built-in test API document intended to exercise Twilio-specific vendor extensions end-to-end.
Add a new OpenAPI endpoint to the sample-twilio-com.yaml file to exercise just the newly created request transform in end-to-end tests.
paths:
/v1/test/cpr/transform/request/requestPathTransform/{pathParam}:
get:
operationId: testCprRequestTransformRequestPathTransform
parameters:
- name: pathParam
in: path
required: true
schema:
type: string
responses:
'200':
description: OK
x-twilio:
downstreamServiceName: test_api
requestTransforms:
- name: requestPathTransform
path: /test/cpr/{pathParam}/RewrittenPath
Add new assertion code to the preprocessor's request transform validation or response transform validation that ensures inputted definitions are valid. See other examples in the file.
Note that x-twilio.requestTransforms
contains just one request transform -- the one that we are adding in this recipe (requestPathTransform
). The name
property specifies the transform. The remaining properties are dependent on the transform type.
Run mvn generate-sources
to (re-)generate the new resource and its implementation.
- Generated API implementation:
../../rest-proxy-server/target/generated-sources/openapi/src/gen/java/com/twilio/api/proxy/server/impl/V1ApiServiceImpl.java
(not available in source control; generated at build)
Ensure that a method matching the operationId
of the endpoint has been generated in the V1ApiServiceImpl.java
.
After running mvn generate-sources
, locate the generated V1ApiServiceImpl.java
file and the method associated with the endpoint added in Step 2. In our case, it is the method testCprRequestTransformRequestPathTransform()
. The body of this method contains the generated implementation for the API endpoint.
Within the method body, you have access to an instance of ProxyRequestContext called context
that contains the full state for the client request. You also have access to an instance of CprTransformRepository called cprTransforms
. Connecting the OpenAPI vendor extension to the CPR transform function implementation is now a matter of connecting these two.
To get started, edit the method implementation in V1ApiServiceImpl.java
. Note that this is an auto-generated file, so all changes will be overwritten whenever a Maven build is run. However, if you run the server in IntelliJ, any changes you make to V1ApiServiceImpl.java
will be persistent (until the next full Maven build).
The following code is added in the "Request transformations" block of the method implementation:
// ...snip...
// --------------------- Request transformations -------------------------
// CPR request transform: requestPathTransform
cprTransforms.request.requestPathTransform(
context.getDownstreamRequest(), "/test/cpr/{pathParam}/RewrittenPath", pathParam);
// --------------------- End request transformations ---------------------
// ...snip...
Manual testing by running the server in IntelliJ and making a curl request to the endpoint validates that the request transform is working as expected.
The final step is to update the OpenAPI-Generator template to make the integration universal and persistent.
- apiServiceImpl.mustache: JMustache template for the core implementation of each API endpoint.
Look for a section in the file above that marks the start of the request transform chain (or the Response transformations if applicable):
Add the CPR request transform invocation to the template in the following format (surrounded by a conditional) between the lines above.
Verify that the template changes are accurate by running mvn clean package
. If there are any template errors, or if your integration is syntactically incorrect, the build will fail.
If all goes well, the server should now be honoring the OpenAPI x-twilio
vendor extension and executing the new request (or response) transform as part of handling requests to your test endpoint. Congratulations, you're almost done!
A full end-to-end integration test of the new CPR function can be easily added by extending:
- BaseRequestTransformIntegrationTest: specialized version of
BaseProxyIntegrationTest
for easier testing of CPR request transforms. - BaseResponseTransformIntegrationTest: specialized version of
BaseProxyIntegrationTest
for easier testing of CPR response transforms.
A BaseProxyIntegrationTest
is a full end-to-end integration test that spins up the following for each test class:
- an instance of the RestProxy server listening on a random system port
- an independent, Jetty-based "downstream" server listening on a different random port
- an Apache HttpClient instance that points at the RestProxy server
Each child test class has access to the following methods to execute a live request through HttpClient to the listening RestProxy server, to the standalone "downstream", and back through the network call chain. This is about as "end-to-end" as it gets without deploying to real infrastructure.
We add the following new test class RequestPathTransformIntegrationTest.java to the rest-proxy-integration-tests
module.
The test above runs live requests across the following two network links (both on localhost, but executed over actual network requests):
┌───────────────────┐ ┌────────────────┐ ┌──────────────┐
│ HttpClient client ├──►│ RestProxy ├──►│ Downstream │
└───────────────────┘ └────────────────┘ └──────────────┘
This allows tests to exercise end-to-end behavior through Jetty, Jersey, HttpClient, and RestProxy codebases.
Update the list of currently supported transformations in cpr-request-transforms.md or cpr-response-transforms.md