Skip to content

Commit

Permalink
updated context custom docs
Browse files Browse the repository at this point in the history
  • Loading branch information
ntotten committed Nov 14, 2023
1 parent fabe536 commit 8d5a066
Show file tree
Hide file tree
Showing 5 changed files with 187 additions and 8 deletions.
119 changes: 119 additions & 0 deletions docs/articles/context-data.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
---
title: ContextData - Sharing Request Data
sidebar_label: ContextData
---

It is often useful to store data throughout the life of a single request. This
data could be used on multiple policies, handlers, or for logging. However,
because the Zuplo runtime is asynchronous, you cannot simply create global
variables and reference them in other modules if the value is unique accross
requests. Additionally, using a traditional data structure like a `Map` is also
not recommended as it can build up memory over time.

## ContextData

The `ContextData` utility allows safely storing data that is tied to a specific
request. Additionally, this utility ensures that data stored is properly garbage
collected (removed from memory) when the request is complete.

**`ContextData.set`**

```ts
ContextData.set(context, "my-data", { prop1: "hello world" });
```

**`ContextData.get`**

```ts
const data = ContextData.get(context, "my-data");
```

**`set` (instance function)**

```ts
const myData = new ContextData("my-data");
myData.set(context, { prop1: "hello world" });
```

**`get` (instance function)**

```ts
const myData = new ContextData("my-data");
const data = myData.get(context);
```

### Typing

The methods of `CustomData` support generics in order to support typing.

```ts
const myData = new ContextData<{ key: string }>("my-data");
ContextData.get<{ key: string }>(context, "my-data");
ContextData.set<{ key: string }>(context, "my-data", { key: "hello" });
```

## How NOT to Share Data

Below are a few examples of how **NOT** to share data in your API.

::: danger

Do NOT write code like this in your API. It will not work reliably. These are
examples of what NOT to do. See the next section for best practices.

:::

The first example uses a simple shared global variable called `currentRequestId`
to store the current requestId. On the surface this looks straightforward.
However, because the gateway is being shared among many requests who are all
running at the same time, the value of `currentRequestId` is completely
unpredictable when your API is under load.

```ts title="/modules/policies.ts"
let currentRequestId: string | undefined;

export function myFirstPolicy(request: ZuploRequest, context: ZuploContext) {
currentRequestId = context.requestId;
context.log.info(`The current requestId is: ${currentRequestId}`);
}

export function mySecondPolicy(request: ZuploRequest, context: ZuploContext) {
currentRequestId = context.requestId;
context.log.info(`The current requestId is: ${currentRequestId}`);
}
```

## Using ContextData

Below you will find examples of how to use `ContextData` to safely share data
throughout the lifecycle of a request.

### Using static methods

```ts
import { CustomData, ZuploContext, ZuploRequest } from "@zuplo/runtime";

export function myFirstPolicy(request: ZuploRequest, context: ZuploContext) {
CustomData.set(context, "currentRequestId", context.requestId;

const currentRequestId = ContextData.get(context, "currentRequestId");
context.log.info(`The current requestId is: ${currentRequestId}`);
}
```
### Using Shared Variable
Reusable class shared across modules The shared module allows other modules to
access the same storage without passing the string name or type around.
```ts
export const myData = new ContextData<{ prop1: string }>("my-data");
```
Then use the class in another module.
```ts
import { myData } from "./my-module";
const data = myData.get(context);
myData.set(context, data);
```
55 changes: 55 additions & 0 deletions docs/articles/route-custom.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
---
title: Route Custom Data
---

Each route in your OpenAPI file allows for specifying custom properties on your
route that can be referenced in code. Because the OpenAPI document allows
extensibility by adding `x-` properties, you can add custom data as needed to
your operations and then read that data in code.

## Custom Data in OpenAPI File

The example below shows how to add custom data and operation with a property
`x-custom`.

```json title="/config/routes.oas.json"
{
"/my-route": {
"get": {
// highlight-start
"x-custom": {
"hello": "world"
},
// highlight-end
"operationId": "c18da63b-bd4d-433f-a634-1da9913958c0",
"x-zuplo-route": {
"handler": {
"module": "$import(@zuplo/runtime)",
"export": "urlForwardHandler",
"options": {
"baseUrl": "https://echo.zuplo.io",
"forwardSearch": true
}
}
}
}
}
}
```

## Custom Data in Code

Custom data can be accessed through the `context.route.raw()` function. This
gives you full access to the underlying data of the OpenAPI operation. By
default this function returns `unknown`, but you can pass it a custom object or
for the full OpenAPI operation, use `OpenAPIV3_1.OperationObject` exported from
`openapi-types`

```ts
import { ZuploContext, ZuploRequest } from "@zuplo/runtime";

export async function echo(request: ZuploRequest, context: ZuploContext) {
const data = context.route.raw<{ "x-custom": { hello: string } }>();
context.log.info(`My custom data: ${data["x-custom"].hello}`);
}
```
17 changes: 11 additions & 6 deletions docs/articles/runtime-extensions.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ export function runtimeInit(runtime: RuntimeExtensions) {
runtime.problemResponseFormat = (
{ problem, statusText, additionalHeaders },
request,
context
context,
) => {
// Build the response body
const body = JSON.stringify(problem, null, 2);
Expand Down Expand Up @@ -121,7 +121,7 @@ import { ZuploContext, ZuploRequest } from "@zuplo/runtime";
export async function pluginWithHook(
request: ZuploRequest,
context: ZuploContext,
policyName: string
policyName: string,
) {
const cloned = request.clone();
context.addResponseSendingFinalHook(
Expand All @@ -131,7 +131,7 @@ export async function pluginWithHook(
method: "GET",
body,
});
}
},
);

return request;
Expand All @@ -148,14 +148,19 @@ The example below shows how to use a route's custom property to set the path on
the outgoing event to a custom value.

```ts
import { AwsLambdaHandlerExtensions, RuntimeExtensions } from "@zuplo/runtime";
import {
AwsLambdaHandlerExtensions,
RuntimeExtensions,
ContextData,
} from "@zuplo/runtime";

export function runtimeInit(runtime: RuntimeExtensions) {
AwsLambdaHandlerExtensions.addSendingAwsLambdaEventHook(
async (request, context, event: AwsLambdaEventV1) => {
event.path = context.custom.lambdaPath ?? event.path;
const lambdaPath = ContextData.get(context, "lambdaPath");
event.path = lambdaPath ?? event.path;
return event;
}
},
);
}
```
2 changes: 1 addition & 1 deletion policies/ab-test-inbound/intro.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@ result of a randomly generated number. A/B tests could also be performed on
properties such as the `request.user`.

A/B tests can also be combined with other policies by passing data to downstream
policies. For example, you could set `context.custom.myProperty` based on the
policies. For example, you could save a value in `ContextData` based on the
results of the A/B test and use that value in a later policy to modify the
request.
2 changes: 1 addition & 1 deletion policies/ab-test-outbound/intro.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@ result of a randomly generated number. A/B tests could also be performed on
properties such as the `request.user`.

A/B tests can also be combined with other policies by passing data to downstream
policies. For example, you could set `context.custom.myProperty` based on the
policies. For example, you could save a value in `ContextData` based on the
results of the A/B test and use that value in a later policy to modify the
request.

0 comments on commit 8d5a066

Please sign in to comment.