Skip to content

Instantly share code, notes, and snippets.

@shrutiburman
Created August 2, 2023 06:52
Show Gist options
  • Save shrutiburman/024bde9eb54f6f438e4eff7c142ef641 to your computer and use it in GitHub Desktop.
Save shrutiburman/024bde9eb54f6f438e4eff7c142ef641 to your computer and use it in GitHub Desktop.
Copied contents of rest-proxy contribution guidelines

Add a new CPR transform function

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:

  1. Add the transform function and unit test
  2. Add the OpenAPI vendor extension to the sample document
  3. Integrate the transform function into the codegen template
  4. Add an integration test
  5. Update documentation

1. Add the transform function and unit test

CPR request and response transformations are implemented in the server.cpr package in the following directory:

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.

2. Add the OpenAPI vendor extension to the sample document

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.

3. Integrate the transform function into the codegen template

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.

Look for a section in the file above that marks the start of the request transform chain (or the Response transformations if applicable):

    // --------------------- Request transformations -------------------------
    {{#vendorExtensions.x-twilio.requestTransforms}}
    ...
    {{/vendorExtensions.x-twilio.requestTransforms}}
    // --------------------- End request transformations ---------------------

Add the CPR request transform invocation to the template in the following format (surrounded by a conditional) between the lines above.

    {{#_name_requestPathTransform}}
    // CPR request transform: requestPathTransform
    cprTransforms.request.requestPathTransform(
        context.getDownstreamRequest(), "{{path}}", {{_pathVariables}});
    {{/_name_requestPathTransform}}

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!

4. Add an integration test

A full end-to-end integration test of the new CPR function can be easily added by extending:

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.

5. Update documentation

Update the list of currently supported transformations in cpr-request-transforms.md or cpr-response-transforms.md

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment