Write your own policy with Gravitee.io API Management 4.0.0
Tags: graviteeio, api, opensource, apim
Published: 2023-09-28 05:17:35

The vast world of APIs is constantly evolving. This can be seen with the recent rise in popularity of event-driven architecture connected through modern event brokers (Kafka, Solace, etc.) and asynchronous web APIs. This transition from traditional, REST-dominated architectures with synchronous request-response style communication to mixed architectures dependent on both synchronous and asynchronous protocols requires a modern and versatile API Management solution: enter Gravitee API Management (APIM).

As of Gravitee 4.0, APIM is an event-native API solution. This means Gravitee natively supports asynchronous web APIs and event brokers while still fully supporting synchronous request/response style APIs in a centralized control plane. Regardless of protocol, Gravitee can modify the behavior of the request or response through policies. Gravitee policies fall into several functional categories: security, transformation, restrictions, performance, routing, and monitoring & testing.

Gravitee offers both a free Community Edition and paid Enterprise Edition of our platform. To learn more about what features are available in each edition, check out [our brand-new documentation site](https://documentation.gravitee.io/platform-overview/)!

Gravitee’s protocol and policy support are powered through an extensible plugin system. So while there is built-in support for a number of protocols, policies (and more), the plugin system allows anyone to add new policies or even entrypoints/endpoints to APIM.

![Amazing](https://media.giphy.com/media/L4Bb7zCaP8FB5C769T/giphy.gif)

Yes… I know, this opens up a brave new world of seemingly endless possibilities. But how to get started?

In this article, I’ll teach you how to write your own custom policy using the plugin system, but first, a few housekeeping items.

---

## 🙋‍♂️ Who am I ?
I am [Yann Tavernier](https://twitter.com/ytvrnr) and I am a developer on the amazing APIM team at Gravitee. I'm delighted to walk you through the creation of a policy and how to efficiently test it!

---

## 📝 Prerequisites
- Your favorite Java IDE
- JDK 17
- Maven 3.8

---

## 👨‍🏫 About Gravitee 

If you want more information about the Gravitee platform, head over to the [Gravitee Essentials](https://documentation.gravitee.io/platform-overview/gravitee-essentials/overview) section of our documentation site. 

---

## 🔑 Key APIM components and concepts

Before we start looking at Policy creation and testing, let's provide an overview of all of the key concepts and components that we'll be using in this blog.

### Key components

- **APIM** is the API Management solution created by Gravitee.
- **APIM Gateway**: A reverse proxy layer that brokers, secures, and hardens access to APIs and data streams. It is the component of APIM responsible for handling requests from customers.
- **APIM Management API (mAPI)**: A REST API used to configure and manage APIs and various Gravitee resources.
- **APIM Console**: A graphical user interface to configure Gateways, create APIs, design policies, and publish documentation. Every action in the APIM Management Console is tied directly to the mAPI.

### Key concepts

- **API Publisher** is a role type for APIM, representing the user of the company in charge of designing and publishing the APIs.
- **API** is the representation of your backend inside APIM, and is composed of elements such as documentation, users, design, properties, etc.
- **Plan** is the access and service layer on top of an API for consumer Applications. Consumer Applications need to subscribe to a plan to be able to consume an API.
- **Plugin** is a component that provide additional functionality to the Gravitee ecosystem.
- **Policy** is a service or action that can be executed on an API request or response to perform checks, transformation or other services to it. As well as out of the box policies, you can also create your own. The functionality of the policy is enabled through plugins.

**If you want more information about Gravitee, read this awesome [Dev Guide](https://community.gravitee.io/c/dev-guide/23)! Even better, this guide is directly available on our community forum, so you can ask all your questions.**

### What is a policy?

As mentioned earlier, a policy is essentially a service or action executed on a request, response or a message to perform things like transformations, rate limits, routing, monitoring & testing, etc.

Technically speaking, a policy is nothing more than a piece of Java code consuming an execution context and returning a Completable ([see here)](https://github.com/gravitee-io/gravitee-gateway-api/blob/8b038648cf8588060a818ba2649e1e3df481c8d4/src/main/java/io/gravitee/gateway/jupiter/api/policy/Policy.java#L34).

Let's take a look at the interface (Javadoc removed for readability purpose):

```java
public interface Policy {
    
    String id();
    
    default Completable onRequest(final HttpExecutionContext ctx) {
        return Completable.complete();
    }

    default Completable onResponse(final HttpExecutionContext ctx) {
        return Completable.complete();
    }

    default Completable onMessageRequest(final MessageExecutionContext ctx) {
        return Completable.complete();
    }

    default Completable onMessageResponse(final MessageExecutionContext ctx) {
        return Completable.complete();
    }
}
```

⚠️ Note that, as we are in the reactive world, the policy implementation is used to build a reactive chain and this chain should only be executed when the Gateway subscribes to the policy. In short, this means your policy code must be written in the context of a Completable object creation.

Here is a good example 💪

```java
Completable onRequest(final HttpExecutionContext ctx) {
    return Completable.fromRunnable(() -> request.headers().set("X-DummyHeader", "dummy"));
}
```

And a bad one 💥 (In the following example, the code will be executed before the subscription takes place which can have unintended side effects)

```java
Completable onRequest(final HttpExecutionContext ctx) {
    request.headers().set("X-DummyHeader", "dummy"); // 💥 Not to do
    return Completable.complete();
}
```

---

## 👩‍💻 Creating our own policy


The best way to learn is by doing. I will talk you through all the steps needed to create a fully functional custom policy.  Let's create a **🍕 Pizza Factory policy**.

![Pizza](https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExMTQwc3BhdWlpenJwMnpwaWdudmlxNjR0dmxid3NmbGFnOXFycmhxZiZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/m54Uj4aKkoBpu/giphy.gif)

The goal of this policy is quite simple. It will use the request headers and payload to transform the request into a JSON pizza object. This will be duplicated on the response phase to return the created pizza Object to the user.

The policy will be configurable:
- Users can configure the crust and the sauce.
- To keep consumers of the pizza safe, we will add a toggle to forbid the word "pineapple" or "🍍". If the API request contains pineapple, then the Gateway will return a `406 Not Acceptable`, else `500 Internal Server Error`.

💡 In a more realistic policy, we would have the policy return a `400 Bad Request` to follow standard HTTP error message semantics.

### ✅ Some acceptance criteria

- Headers must follow this format: X-pizza-topping: #value#. You can use it multiple times to add as many toppings as necessary.
- Body must be an array of strings representing the toppings to add.
- If no `X-Pizza-Topping` header is present and the body is empty, then a pizza object is not created and `X-Pizza: No Pizza` header is added.
- If both `X-Pizza-Topping` header and body payload are present, then both are used to compose the pizza object.
- If the body is not an array of strings, then it results in a `400 Bad Request`.
- The body of request or response will be replaced by the pizza object. We assume the content type will be `application/json`.

### 🧪 Initialize it!
To initialize your policy, simply fork or clone https://github.com/gravitee-io/gravitee-policy-template. 
This repository contains the necessary structure and minimum files needed to create a policy. You may need to update some dependencies (in pom.xml) to be consistent with versions used by APIM.

Policies are written in Java and use Maven as the build tool.

Let's take a close look at the structure of the policy.

- `README.adoc` is here to explain the purpose of your policy, how to use it and the error cases. 
💡 This file is used in the Management Console UI of Gravitee APIM as in-app documentation for the users of the platform; therefore, take care to ensure that the README is clear and comprehensive.
- `pom.xml` contains information about the project and configuration details used by Maven to build the project. This base comes with the minimal set of dependencies to develop and build your policy.
- `src/assembly` is used by maven-assembly-plugin to generate the zip of the policy with the right structure. ⚠️ You do not need to modify this file.
- `src/main/java` will contain the policy code
- `src/main/resources/plugin.properties` is the manifest of the policy. It contains informations such as the policy's id, name and icon. For the Pizza Policy, `plugin.properties` file will look like this: 

```properties
id=pizza-factory
name=Pizza
version=${project.version}
description=${project.description}
class=io.gravitee.policy.pizza.PizzaPolicy
type=policy
category=transformation
icon=pizza.svg
```
- `src/main/resources/schemas/schema-form.json` is the representation of the configuration of your policy. It is used to generate the schema and validate the creation of a policy whether using the APIM Management UI or the Management API directly.
- `src/test/java` and `src/test/resources` are here for testing purposes. Here, you can find a `TemplatePolicyIntegrationTest` test class that has already been  implemented. We will discuss it in more detail later, but essentially, it allows for easy creation of unit tests to verify your policy behaves as expected. This is done by deploying an API using this policy on an in-memory Gateway and then calls the API to verify the response matches the expectation.

## 🚀 Let's develop it

I will not detail every step, but instead focus on the steps required to develop a policy in Gravitee.
![Work smarter](https://media.giphy.com/media/qdnMr2j2anHsLTv5TO/giphy.gif)


### 📤 The request phase

Let's go into it. As a reminder, our policy aims to transform the body into a JSON pizza object depending on incoming request headers and payload.
We will modify the `onRequest` method in this way:

```java
@Override
public Completable onRequest(HttpExecutionContext ctx) {
    return ctx.request().onBody(maybeBody -> doSomethingHere());
}
```

The `HttpExecutionContext` allows developers to access different objects tied to API transaction itself:
- `request()`: allows access to the current request which provides accessors to `headers`, `pathParameters`, `HTTP method`, `pathInfo`, `host`, etc. It also provides methods to manipulate the body (`onBody`) or to interrupt the call (`interruptWith`).
- `response()`: the same thing as `request()` except you don't have any request related information.
- `getAttribute(String attribute)`: allows you to manipulate an attribute that has been stored in the context of the API transaction.

💡 The `onBody` method lets you manipulates a `Maybe<Buffer>`. This can handle the uncertainty inherent in every API request: maybe the request has a body, maybe not.

#### 🍕 Creating a pizza

First, we will create the core feature of our policy: the `createPizza` method. As inputs, it needs the incoming payload and headers, and will return a Buffer: our pizza object. As we extract the body and headers from the context object, we do not need to stay in the reactive programming style, we will see later how to do the link.

Our method would look like this:

```java
private Buffer createPizza(Buffer body, HttpHeaders headers) throws IOException {
    final Set<String> toppings = extractToppings(body, headers);

    if (toppings.isEmpty()) {
        return Buffer.buffer();
    }
    verifyPineapple(toppings);

    final Buffer pizzaObject = createPizzaObject(toppings, headers);

    return pizzaObject;
}
```

Some information: 

1. `extractToppings(Buffer body, HttpHeaders headers)` is responsible for the following:
 - extracting the toppings from headers (`X-Pizza-Topping`)
 - extracting the toppings from body. In this case, body must be a valid json array of strings, or else a `NotStringArrayException` will be thrown.
 - returning the set of toppings extracted from body and headers.
2. If the list of toppings is empty, the pizza is not created and an empty buffer is returned.
3. Next, comes the pineapple case. If policy is configured to refuse pineapple, and toppings list contains `pineapple` or `🍍`, then a `PineappleForbiddenException` would be thrown.
4. Finally, the method creates and returns a pizza object which is just the JSON representation of the pizza from the configured `crust`, `sauce` and extracted toppings. A `PizzaProcessingException` could be thrown in case of mapping issue.

This code is fairly straightforward and does not require any knowledge of reactive programming.

#### 🍽️ Serving the pizza

However, the `createPizza` method now needs to be transformed. We have to integrate our `createPizza` method into the reactive chain.

The reactive world, and more precisely [RxJava 3](https://github.com/ReactiveX/RxJava) in our case, does not directly manipulate `Buffer`, it manipulates reactive objects. Those objects implements the Reactive Pattern for different cases:
- `Single` for a single value response
- `Maybe` for a single value, no value or an exception
- `Completable` for a deferred computation without any value, but only indication for completion or exception
- `Flowable` to consume reactive dataflows
- And many others.

Remember, our method returns a `Buffer`: an empty buffer if there are no toppings, or the pizza object as a buffer.
This is a perfect use case for the `Maybe` reactive object:
- If there are no toppings, return `Maybe.empty()`
- Else, return `Maybe.just(pizzaObject)`

The method now looks like this:
```java
private Maybe<Buffer> createPizza(Buffer body, HttpHeaders headers) throws IOException {
    final Set<String> toppings = extractToppings(body, headers);

    if (toppings.isEmpty()) {
        return Maybe.empty();
    }
    verifyPineapple(toppings);

    final Buffer pizzaObject = createPizzaObject(toppings, headers);

    return Maybe.just(pizzaObject);
}
```

Now, we are able to use our method in a reactive way:
```java
@Override
public Completable onRequest(HttpExecutionContext ctx) {
    return ctx
        .request()
        .onBody(maybeBody ->
            maybeBody
                // If no body, then use an empty buffer
                .defaultIfEmpty(Buffer.buffer())
                // Create a pizza from body and headers
                .flatMapMaybe(body -> createPizza(body, ctx.request().headers()))
                // If no pizza has been created, then handle the case
                .switchIfEmpty(handleNoPizza(ctx.request().headers()))
                // Manage errors
                .onErrorResumeNext(handleError(ctx, true))
        );
}
```

Let me provide some further explanation of these changes.

`onBody` lets us manipulate a `Maybe<Buffer>`. This body can indeed be empty if the user provides toppings only using headers.

1. The first step is to have something to manipulate. Using `maybeBody.defaultIfEmpty(Buffer.buffer())` provides an empty buffer to be used in subsequent steps. If we've kept an empty maybe, then we could not continue to the next steps in the reactive chain. There must be something to compute.
2. Now, we have a `Single<Buffer>`. Thanks to the `flatMapMaybe()` operator, we can use the Buffer and return a `Maybe` object. That's exactly what we have done with `Maybe<Buffer> createPizza(Buffer body, HttpHeaders header)`
3. With the `switchIfEmpty()` operator, we can handle the case of an empty pizza, by adding a `X-Pizza: not-created` header with this method:
```java
private static Maybe<Buffer> handleNoPizza(HttpHeaders headers) {
    return Maybe.fromCallable(() -> {
        headers.add(X_PIZZA_HEADER, NOT_CREATED);
        return Buffer.buffer();
    });
}
```
4. Finally, we handle the errors that might have ocurred during the process with the `onErrorResumeNext()` operator. 
Let's look at the error handling in more detail.

#### 💥 Managing errors

The `onErrorResumeNext()` operator allows you to resume the flow with a `Maybe` object that is returned for a particular failure. In other words, it allows to properly manage the error cases that can occur in the reactive chain.

Our requirements were about returning particular HTTP status codes depending on whether we are in request or response phase, and provide details on the type of error:

- REQUEST: 
  - PineappleForbiddenException: `406 - Not Acceptable`
  - All other exceptions: `400 - Bad Request`
- RESPONSE: 
  - All exceptions: `500 - Internal Server Error`

The `HttpExecutionContext` contains an interesting method: `interruptBodyWith(ExecutionFailure failure)`. It allows the developer to fail the current request/response flow with an `ExecutionFailure` object.
This objects can be configured with:
- (required) the HTTP status code to return to the user
- a message to be returned as the body
- a key that can referenced by the Response Template feature of APIM
- and some other features we won't dive into here

The error management code is pretty straightforward to write:
```java
private Function<Throwable, Maybe<Buffer>> handleError(HttpExecutionContext ctx, boolean isOnRequest) {
    return err -> {
        log.warn("It was not possible to create the pizza because: {}", err.getMessage());
        if (isOnRequest && err instanceof PineappleForbiddenException) {
            return ctx.interruptBodyWith(
                new ExecutionFailure(HttpStatusCode.NOT_ACCEPTABLE_406).key(PIZZA_ERROR_KEY).message(err.getMessage())
            );
        }
        return ctx.interruptBodyWith(
            new ExecutionFailure(isOnRequest ? HttpStatusCode.BAD_REQUEST_400 : HttpStatusCode.INTERNAL_SERVER_ERROR_500)
                .key(PIZZA_ERROR_KEY)
                .message(err.getMessage())
        );
    };
}
```

### 🚦 Test your policy

Gravitee APIM comes with what we call the Gateway Testing SDK.

This library can be seen as a [JUnit 5 extension](https://junit.org/junit5/docs/current/user-guide/#extensions) which allow the developer to:
- Write tests as regular JUnit 5 tests
- Run an in-memory gateway (and configure it as needed)
- Deploy inline plugins (directly from code)
- Do real call against the gateway to verify it behaves as expected

In our present case, we will want to validate that if we call an API using the `Pizza Factory Policy`, then we have the expected behavior in term of returned object or error handling.

Some cases to be tested (on both request and response): 
- Should not create a pizza when no topping provided
- Should create a pizza when toppings comes from headers
- Should create a pizza when toppings comes from body
- Should create a pizza when toppings comes from headers and body
- Should not create a pizza when toppings contains pineapple and it's forbidden
- ...

#### Test preparation

APIs are deployed from a JSON definition. You can learn more about Gravitee's API definition [here](https://documentation.gravitee.io/apim/overview/gravitee-api-definitions-and-execution-engines).

Here is the policy configuration portion of an API definition:
```json
"request": [
        {
          "name": "Pizza Factory on request",
          "description": "Create your pizza from request headers!",
          "enabled": true,
          "policy": "pizza-factory",
          "configuration": {
            "crust": "Pan",
            "sauce": "TOMATO",
            "pineappleForbidden": false
          }
        }
      ],
```

This JSON string will be used to deploy the test API on the testing Gateway, and will allow us to call this API for testing.

Next, we need to create a test class. APIM team uses the `IntegrationTest` suffix to distinguish tests using the SDK from regular unit tests.

Here is the minimal code needed to do to run a test with the SDK:

```java
@GatewayTest
@DeployApi("/apis/pizza-api.json")
class PizzaPolicyIntegrationTest extends AbstractPolicyTest<PizzaPolicy, PizzaPolicyConfiguration> {

    @Override
    public void configureEntrypoints(Map<String, EntrypointConnectorPlugin<?, ?>> entrypoints) {
        entrypoints.putIfAbsent("http-proxy", EntrypointBuilder.build("http-proxy", HttpProxyEntrypointConnectorFactory.class));
    }

    @Override
    public void configureEndpoints(Map<String, EndpointConnectorPlugin<?, ?>> endpoints) {
        endpoints.putIfAbsent("http-proxy", EndpointBuilder.build("http-proxy", HttpProxyEndpointConnectorFactory.class));
    }

    @Test
    void should_create_pizza_with_header_toppings_on_request(HttpClient httpClient) {
      // We will focus on the test case later
    }
}
```

1. `@GatewayTest` is a meta-annotation which marks the class as testable with the SDK. It will run the necessary extensions to run the Gateway and initialize components
2. `@DeployApi` deploys an API based on its definition. You can also pass an array of APIs to deploy, where each API must have a distinct name and entrypoint.
  - Used at class level, it will deploy the APIs once for the Gateway instance, meaning all the methods annotated with `@Test` will be able to call those APIs.
  - Used at method level, it will deploy the APIs for the lifetime of the test method and then it will be undeployed.
3. `AbstractPolicyTest<PizzaPolicy, PizzaPolicyConfiguration` is an abstract class allowing you to directly register your policy on the Gateway. You can also extends `AbstractGatewayTest` and register your policy thanks to `AbstractGatewayTest#configurePolicies(Map<String PolicyPlugin policies)`
4. `configureEntrypoints(Map<String, EntrypointConnectorPlugin<?, ?>> entrypoints)` and `configureEndpoints(Map<String, EndpointConnectorPlugin<?, ?>> endpoints)` allow the developer to register entrypoint and endpoint plugins. In this case, `http-proxy` connectors are registered so that the API can be deployed as a `proxy` api.

💡 You can learn more about the difference between Proxy and Message Gateway APIs [here](https://documentation.gravitee.io/apim/guides/create-apis/how-to/v4-api-creation-wizard#step-2-entrypoints).

#### Writing a test

You may have noticed in the previous example that an `HttpClient` is injected as a parameter of the test method:

```java
@Test
void should_create_pizza_with_header_toppings_on_request(HttpClient httpClient) {}
```

It is injected to make your life easier. An important thing to understand is that the Gateway is started on a random available port, and the same applies to the started `Wiremock` used as endpoint.

When deploying an API, the SDK will update the `localhost:8080` endpoints with by the right port and thereby allowing requests to reach `Wiremock`.

The `HttpClient`, in the same way, is configured to reach directly to the Gateway on the right port.

So, we now want to test if a pizza is created from toppings provided as a header when the Pizza Policy is configured on request phase. API is configured to be reached on `/test` path and to contact a backend on `/endpoint` path.

Here is the code of the test, with comments:

```java
@Test
@DisplayName("Should create pizza when toppings provided from headers")
void should_create_pizza_with_header_toppings_on_request(HttpClient httpClient) {
    // 1. Prepare wiremock endpoint to answer with a `200 - OK` and an empty body when `/endpoint` is reached
    wiremock.stubFor(get("/endpoint").willReturn(ok()));

    // 2. Use the HttpClient to call your deployed API, configured with `/test` as context-path
    httpClient
        .rxRequest(HttpMethod.GET, "/test")
        // 2.1 Send the request to the gateway, with toppings header: `x-pizza-topping:peperoni,cheddar`
        .flatMap(request -> request.putHeader(X_PIZZA_HEADER_TOPPING, List.<String>of("peperoni", "cheddar")).rxSend())
        // 2.2 Verify the response status is the expected one and return the body
       .flatMap(response -> {
            assertThat(response.statusCode()).isEqualTo(HttpStatusCode.OK_200);
            return response.body();
        })
        .test()
        .awaitDone(10, TimeUnit.SECONDS)
        // 2.3 Verify the call is complete, and that the body is an empty buffer   
        .assertComplete()
        .assertValue(Buffer.buffer())
        .assertNoErrors();

    // 3. Verify the wiremock endpoint has been called
    //    - on `/endpoint`
    //    - with header `x-pizza:created`
    //    - with the expected pizza object
    wiremock.verify(
        1,
        getRequestedFor(urlPathEqualTo("/endpoint"))
           .withHeader(X_PIZZA_HEADER, equalTo(CREATED))
           .withRequestBody(equalTo("{\"crust\":\"Pan\",\"sauce\":\"TOMATO\",\"toppings\":[\"peperoni\",\"cheddar\"]}"))
    );
}
```

🏆 That's it, you know how to test a policy! You just need to implements the other test cases.

![As expected](https://media.giphy.com/media/7MZ0v9KynmiSA/giphy.gif)

You can find the README of the SDK [here](https://github.com/gravitee-io/gravitee-api-management/tree/22c93ebc2b7c2ab44d3536c67a9097d7877fbcef/gravitee-apim-gateway/gravitee-apim-gateway-tests-sdk).

For more examples of tests, you can take a look [here](https://github.com/gravitee-io/gravitee-api-management/tree/22c93ebc2b7c2ab44d3536c67a9097d7877fbcef/gravitee-apim-integration-tests).

## 🍕 Use it!

Now, it's time to use your policy.
You first need to build the ZIP file that will be deployed on the Management API and on the Gateway of APIM.

Thankfully, this is quite simple. Just run `mvn clean install`. (The policy is preconfigured to apply a prettier check and to enforce code formatting. If you have any issues, you may need to manually run `mvn prettier:write`).

This generates a ZIP file containing all the necessary assets for the policy to be used by APIM. This ZIP file can be found in the `target` folder.

Deploying a plugin is as easy as copying the plugin archive into the dedicated directory. By default, you need to deploy the ZIP in `${GRAVITEE_HOME}/plugins` (more information [here](https://documentation.gravitee.io/apim/overview/plugins#deployment)).

⚠️ You need to restart the Management API and Gateway for the plugin to be loaded.


![Configuration of the Policy in Policy Studio](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/48b0wk2tq2t2qx9fb3e9.png)


![View of the Policy in Policy Studio](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lria60jsbt86fw8qjlr7.png)

## 🤔 What about v2 APIs?

For existing users of Gravitee APIM, you probably have some "v2" APIs.
Those APIs were running with the legacy engine, and policies were written in a different format than what was shown in this blog.

Lucky for you, you can use policies written in 4.0.0 style with existing v2 APIs, 4.0 was designed with backward compatibility in mind. To do so, "v4 Emulation mode" must be enabled by setting the `executionMode` field of your API definition to: `v4-emulation-engine`.

💡 You can learn more about the different Gravitee execution engines, API definitions, and emulation mode [here](https://documentation.gravitee.io/apim/overview/gravitee-api-definitions-and-execution-engines).

--- 

## ✅ To wrap up
In this blog, we just saw how to write, test and deploy a simple custom policy to explore the extensible nature of Gravitee APIM. You can find the complete policy on the [Gravitee Pizza Policy repository](https://github.com/gravitee-io-community/gravitee-policy-pizza).

If this blog inspired you to build your own policy, you can get started quickly by forking the [Policy Template](https://github.com/gravitee-io/gravitee-policy-template).

All are welcome to get involved in the Gravitee Community. From everything to submitting pull requests in the [APIM repo](https://github.com/gravitee-io/gravitee-api-management) to getting involved in our [Gravitee Community Discourse](https://community.gravitee.io/), we'd love to have you join our vibrant community.

Please don't hesitate to ask for support on our community discourse 🤝, we are happy to help!