Skip to content

Commit 8fdcdfe

Browse files
committed
docs(jmespath): docs for utility
1 parent 3b6eedf commit 8fdcdfe

15 files changed

+369
-46
lines changed
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { extractDataFromEnvelope } from '@aws-lambda-powertools/jmespath/envelopes';
2+
import { Logger } from '@aws-lambda-powertools/logger';
3+
4+
const logger = new Logger();
5+
6+
export const handler = async (event: { payload: string }): Promise<void> => {
7+
const logGroup = extractDataFromEnvelope<string>(
8+
event,
9+
'powertools_base64_gzip(payload) | powertools_json(@).logGroup'
10+
);
11+
12+
logger.info('Log group name', { logGroup });
13+
};
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"payload": "H4sIACZAXl8C/52PzUrEMBhFX2UILpX8tPbHXWHqIOiq3Q1F0ubrWEiakqTWofTdTYYB0YWL2d5zvnuTFellBIOedoiyKH5M0iwnlKH7HZL6dDB6ngLDfLFYctUKjie9gHFaS/sAX1xNEq525QxwFXRGGMEkx4Th491rUZdV3YiIZ6Ljfd+lfSyAtZloacQgAkqSJCGhxM6t7cwwuUGPz4N0YKyvO6I9WDeMPMSo8Z4Ca/kJ6vMEYW5f1MX7W1lVxaG8vqX8hNFdjlc0iCBBSF4ERT/3Pl7RbMGMXF2KZMh/C+gDpNS7RRsp0OaRGzx0/t8e0jgmcczyLCWEePhni/23JWalzjdu0a3ZvgEaNLXeugEAAA=="
3+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { extractDataFromEnvelope } from '@aws-lambda-powertools/jmespath/envelopes';
2+
import { Logger } from '@aws-lambda-powertools/logger';
3+
4+
const logger = new Logger();
5+
6+
export const handler = async (event: { payload: string }): Promise<void> => {
7+
const data = extractDataFromEnvelope<string>(
8+
event,
9+
'powertools_json(powertools_base64(payload))'
10+
);
11+
12+
logger.info('Decoded payload', { data });
13+
};
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"payload": "eyJ1c2VyX2lkIjogMTIzLCAicHJvZHVjdF9pZCI6IDEsICJxdWFudGl0eSI6IDIsICJwcmljZSI6IDEwLjQwLCAiY3VycmVuY3kiOiAiVVNEIn0="
3+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"Records": [
3+
{
4+
"application": "app",
5+
"datetime": "2022-01-01T00:00:00.000Z",
6+
"notification": "GyYA+AXhZKk/K5DkanoQSTYpSKMwwxXh8DRWVo9A1hLqAQ=="
7+
}
8+
]
9+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { fromBase64 } from '@aws-lambda-powertools/commons/utils/base64';
2+
import { extractDataFromEnvelope } from '@aws-lambda-powertools/jmespath/envelopes';
3+
import { PowertoolsFunctions } from '@aws-lambda-powertools/jmespath/functions';
4+
import { Logger } from '@aws-lambda-powertools/logger';
5+
import { brotliDecompressSync } from 'node:zlib';
6+
7+
const logger = new Logger();
8+
9+
// prettier-ignore
10+
class CustomFunctions extends PowertoolsFunctions {
11+
@PowertoolsFunctions.signature({ // (1)!
12+
argumentsSpecs: [['string']],
13+
variadic: false,
14+
})
15+
public funcDecodeBrotliCompression(value: string): string { // (2)!
16+
const encoded = fromBase64(value, 'base64');
17+
const uncompressed = brotliDecompressSync(encoded);
18+
19+
return uncompressed.toString();
20+
}
21+
}
22+
23+
export const handler = async (event: { payload: string }): Promise<void> => {
24+
const message = extractDataFromEnvelope<string>(
25+
event,
26+
'Records[*].decode_brotli_compression(notification) | [*].powertools_json(@).message',
27+
{ customFunctions: new CustomFunctions() }
28+
);
29+
30+
logger.info('Decoded message', { message });
31+
};
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
{
2+
"version": "2.0",
3+
"routeKey": "ANY /createpayment",
4+
"rawPath": "/createpayment",
5+
"rawQueryString": "",
6+
"headers": {
7+
"Header1": "value1",
8+
"Header2": "value2"
9+
},
10+
"requestContext": {
11+
"accountId": "123456789012",
12+
"apiId": "api-id",
13+
"domainName": "id.execute-api.us-east-1.amazonaws.com",
14+
"domainPrefix": "id",
15+
"http": {
16+
"method": "POST",
17+
"path": "/createpayment",
18+
"protocol": "HTTP/1.1",
19+
"sourceIp": "ip",
20+
"userAgent": "agent"
21+
},
22+
"requestId": "id",
23+
"routeKey": "ANY /createpayment",
24+
"stage": "$default",
25+
"time": "10/Feb/2021:13:40:43 +0000",
26+
"timeEpoch": 1612964443723
27+
},
28+
"body": "{\"user\":\"xyz\",\"product_id\":\"123456789\"}",
29+
"isBase64Encoded": false
30+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import {
2+
IdempotencyConfig,
3+
makeIdempotent,
4+
} from '@aws-lambda-powertools/idempotency';
5+
import { DynamoDBPersistenceLayer } from '@aws-lambda-powertools/idempotency/dynamodb';
6+
import type { APIGatewayEvent } from 'aws-lambda';
7+
import { randomUUID } from 'node:crypto';
8+
9+
const persistenceStore = new DynamoDBPersistenceLayer({
10+
tableName: 'IdempotencyTable',
11+
});
12+
13+
export const handler = makeIdempotent(
14+
async (event: APIGatewayEvent) => {
15+
const body = JSON.parse(event.body || '{}');
16+
const { user, productId } = body;
17+
18+
const result = await createSubscriptionPayment(user, productId);
19+
20+
return {
21+
statusCode: 200,
22+
body: JSON.stringify({
23+
paymentId: result.id,
24+
message: 'success',
25+
}),
26+
};
27+
},
28+
{
29+
persistenceStore,
30+
config: new IdempotencyConfig({
31+
eventKeyJmesPath: 'powertools_json(body)',
32+
}),
33+
}
34+
);
35+
36+
const createSubscriptionPayment = async (
37+
user: string,
38+
productId: string
39+
): Promise<{ id: string; message: string }> => {
40+
const payload = { user, productId };
41+
const response = await fetch('https://httpbin.org/anything', {
42+
method: 'POST',
43+
body: JSON.stringify(payload),
44+
});
45+
46+
if (!response.ok) {
47+
throw new Error('Failed to create subscription payment');
48+
}
49+
50+
return { id: randomUUID(), message: 'paid' };
51+
};

docs/utilities/jmespath.md

Lines changed: 30 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ You can use the `extractDataFromEnvelope` function with any [JMESPath expression
3131
Another common use case is to fetch deeply nested data, filter, flatten, and more.
3232

3333
=== "extractDataFromBuiltinEnvelope.ts"
34-
```typescript hl_lines="1 13 20"
34+
```typescript hl_lines="1 13 17 20 22"
3535
--8<-- "docs/snippets/jmespath/extractDataFromEnvelope.ts"
3636
```
3737

@@ -91,16 +91,16 @@ Use `powertools_json` function to decode any JSON string anywhere a JMESPath exp
9191
9292
This sample will deserialize the JSON string within the `body` key before [Idempotency](./idempotency.md){target="_blank"} processes it.
9393

94-
=== "powertools_json_idempotency_jmespath.py"
94+
=== "powertoolsJsonIdempotencyJmespath.ts"
9595

96-
```python hl_lines="16"
97-
--8<-- "examples/jmespath_functions/src/powertools_json_idempotency_jmespath.py"
96+
```ts hl_lines="31"
97+
--8<-- "docs/snippets/jmespath/powertoolsJsonIdempotencyJmespath.ts"
9898
```
9999

100-
=== "powertools_json_idempotency_jmespath.json"
100+
=== "powertoolsJsonIdempotencyJmespath.json"
101101

102102
```json hl_lines="28"
103-
--8<-- "examples/jmespath_functions/src/powertools_json_idempotency_jmespath.json"
103+
--8<-- "docs/snippets/jmespath/powertoolsJsonIdempotencyJmespath.json"
104104
```
105105

106106
#### powertools_base64 function
@@ -109,22 +109,16 @@ Use `powertools_base64` function to decode any base64 data.
109109

110110
This sample will decode the base64 value within the `data` key, and deserialize the JSON string before processing.
111111

112-
=== "powertools_base64_jmespath_function.py"
112+
=== "powertoolsBase64Jmespath.ts"
113113

114-
```python hl_lines="7 10 37 49 53 55 57"
115-
--8<-- "examples/jmespath_functions/src/powertools_base64_jmespath_function.py"
114+
```ts hl_lines="9"
115+
--8<-- "docs/snippets/jmespath/powertoolsBase64Jmespath.ts"
116116
```
117117

118-
=== "powertools_base64_jmespath_schema.py"
119-
120-
```python hl_lines="7 8 10 12 17 19 24 26 31 33 38 40"
121-
--8<-- "examples/jmespath_functions/src/powertools_base64_jmespath_schema.py"
122-
```
123-
124-
=== "powertools_base64_jmespath_payload.json"
118+
=== "powertoolsBase64JmespathPayload.json"
125119

126120
```json
127-
--8<-- "examples/jmespath_functions/src/powertools_base64_jmespath_payload.json"
121+
--8<-- "docs/snippets/jmespath/powertoolsBase64JmespathPayload.json"
128122
```
129123

130124
#### powertools_base64_gzip function
@@ -133,41 +127,42 @@ Use `powertools_base64_gzip` function to decompress and decode base64 data.
133127

134128
This sample will decompress and decode base64 data from Cloudwatch Logs, then use JMESPath pipeline expression to pass the result for decoding its JSON string.
135129

136-
=== "powertools_base64_gzip_jmespath_function.py"
137-
138-
```python hl_lines="6 10 15 29 31 33 35"
139-
--8<-- "examples/jmespath_functions/src/powertools_base64_gzip_jmespath_function.py"
140-
```
141-
142-
=== "powertools_base64_gzip_jmespath_schema.py"
130+
=== "powertoolsBase64GzipJmespath.ts"
143131

144-
```python hl_lines="7-15 17 19 24 26 31 33 38 40"
145-
--8<-- "examples/jmespath_functions/src/powertools_base64_gzip_jmespath_schema.py"
132+
```ts hl_lines="9"
133+
--8<-- "docs/snippets/jmespath/powertoolsBase64GzipJmespath.ts"
146134
```
147135

148-
=== "powertools_base64_gzip_jmespath_payload.json"
136+
=== "powertoolsBase64GzipJmespathPayload.json"
149137

150138
```json
151-
--8<-- "examples/jmespath_functions/src/powertools_base64_gzip_jmespath_payload.json"
139+
--8<-- "docs/snippets/jmespath/powertoolsBase64GzipJmespathPayload.json"
152140
```
153141

154142
### Bring your own JMESPath function
155143

156144
???+ warning
157145
This should only be used for advanced use cases where you have special formats not covered by the built-in functions.
158146

159-
For special binary formats that you want to decode before processing, you can bring your own [JMESPath function](https://github.com/jmespath/jmespath.py#custom-functions){target="_blank" rel="nofollow"} and any additional option via `jmespath_options` param. To keep Powertools for AWS Lambda (TypeScript) built-in functions, you can extend the `PowertoolsFunctions` class.
147+
For special binary formats that you want to decode before processing, you can bring your own JMESPath function by extending the `PowertoolsFunctions` class.
160148

161-
Here is an example of how to decompress messages using [zlib](https://docs.python.org/3/library/zlib.html){target="_blank" rel="nofollow"}:
149+
Here is an example of how to decompress messages compressed using the [Brotli compression algorithm](https://nodejs.org/api/zlib.html#zlibbrotlidecompressbuffer-options-callback){target="_blank" rel="nofollow"}:
162150

163-
=== "powertools_custom_jmespath_function.py"
151+
=== "PowertoolsCustomFunction.ts"
164152

165-
```python hl_lines="9 14 17-18 23 34 39 41 43"
166-
--8<-- "examples/jmespath_functions/src/powertools_custom_jmespath_function.py"
153+
```ts hl_lines="3 9 25-26"
154+
--8<--
155+
docs/snippets/jmespath/powertoolsCustomFunction.ts::8
156+
docs/snippets/jmespath/powertoolsCustomFunction.ts:10:
157+
158+
--8<--
167159
```
168160

169-
=== "powertools_custom_jmespath_function.json"
161+
1. The function signature can be enforced at runtime by using the `@Functions.signature` decorator.
162+
2. The name of the function must start with the `func` prefix.
163+
164+
=== "powertoolsCustomFunction.json"
170165

171166
```json
172-
--8<-- "examples/jmespath_functions/src/powertools_custom_jmespath_function.json"
167+
--8<-- "docs/snippets/jmespath/powertoolsCustomFunction.json"
173168
```

mkdocs.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ nav:
1919
- utilities/parameters.md
2020
- utilities/idempotency.md
2121
- utilities/batch.md
22+
- utilities/jmespath.md
2223
- Processes:
2324
- Roadmap: roadmap.md
2425
- Versioning policy: versioning.md
@@ -100,6 +101,7 @@ plugins:
100101
glob:
101102
- snippets/node_modules/*
102103
- snippets/package.json
104+
- snippets/CHANGELOG.md
103105

104106
extra_css:
105107
- stylesheets/extra.css

packages/jmespath/package.json

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,20 +43,28 @@
4343
"import": "./lib/esm/envelopes.js",
4444
"require": "./lib/cjs/envelopes.js"
4545
},
46+
"./functions": {
47+
"import": "./lib/esm/PowertoolsFunctions.js",
48+
"require": "./lib/cjs/PowertoolsFunctions.js"
49+
},
4650
"./types": {
4751
"import": "./lib/esm/types.js",
4852
"require": "./lib/cjs/types.js"
4953
}
5054
},
5155
"typesVersions": {
5256
"*": {
53-
"types": [
54-
"lib/cjs/types.d.ts",
55-
"lib/esm/types.d.ts"
56-
],
5757
"envelopes": [
5858
"lib/cjs/envelopes.d.ts",
5959
"lib/esm/envelopes.d.ts"
60+
],
61+
"functions": [
62+
"lib/cjs/PowertoolsFunctions.d.ts",
63+
"lib/esm/PowertoolsFunctions.d.ts"
64+
],
65+
"types": [
66+
"lib/cjs/types.d.ts",
67+
"lib/esm/types.d.ts"
6068
]
6169
}
6270
},
@@ -84,4 +92,4 @@
8492
"typescript",
8593
"nodejs"
8694
]
87-
}
95+
}

packages/jmespath/src/Functions.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ import { arityCheck, typeCheck } from './utils.js';
4848
* ```
4949
*/
5050
class Functions {
51+
public methods: Set<string> = new Set();
5152
/**
5253
* Get the absolute value of the provided number.
5354
*
@@ -540,6 +541,35 @@ class Functions {
540541
return Object.values(arg);
541542
}
542543

544+
public introspectMethods(scope?: Functions): Set<string> {
545+
const prototype = Object.getPrototypeOf(this);
546+
const ownName = prototype.constructor.name;
547+
const methods = new Set<string>();
548+
if (ownName !== 'Functions') {
549+
for (const method of prototype.introspectMethods(scope)) {
550+
methods.add(method);
551+
}
552+
}
553+
554+
// This block is executed for every class in the inheritance chain
555+
for (const method of Object.getOwnPropertyNames(
556+
Object.getPrototypeOf(this)
557+
)) {
558+
method !== 'constructor' &&
559+
method.startsWith('func') &&
560+
methods.add(method);
561+
}
562+
563+
// This block will be executed only if the scope is the outermost class
564+
if (this.methods) {
565+
for (const method of methods) {
566+
this.methods.add(method);
567+
}
568+
}
569+
570+
return methods;
571+
}
572+
543573
/**
544574
* Decorator to enforce the signature of a function at runtime.
545575
*

0 commit comments

Comments
 (0)