Skip to content

Commit 91c7ecf

Browse files
authored
module validation (#138)
1 parent b4d4e50 commit 91c7ecf

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+3174
-0
lines changed

docs/content/utilities/validation.mdx

Lines changed: 331 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,331 @@
1+
---
2+
title: Validation
3+
description: Utility
4+
---
5+
6+
import Note from "../../src/components/Note"
7+
8+
This utility provides JSON Schema validation for payloads held within events and response used in AWS Lambda.
9+
10+
**Key features**
11+
* Validate incoming events and responses
12+
* Built-in validation for most common events (API Gateway, SNS, SQS, ...)
13+
* JMESPath support validate only a sub part of the event
14+
15+
## Install
16+
17+
To install this utility, add the following dependency to your project.
18+
19+
```xml
20+
<dependency>
21+
<groupId>software.amazon.lambda</groupId>
22+
<artifactId>powertools-validation</artifactId>
23+
<version>0.5.0-beta</version>
24+
</dependency>
25+
```
26+
27+
And configure the aspectj-maven-plugin to compile-time weave (CTW) the
28+
aws-lambda-powertools-java aspects into your project. You may already have this
29+
plugin in your pom. In that case add the dependency to the `aspectLibraries`
30+
section.
31+
32+
```xml
33+
<build>
34+
<plugins>
35+
...
36+
<plugin>
37+
<groupId>org.codehaus.mojo</groupId>
38+
<artifactId>aspectj-maven-plugin</artifactId>
39+
<version>1.11</version>
40+
<configuration>
41+
<source>1.8</source>
42+
<target>1.8</target>
43+
<complianceLevel>1.8</complianceLevel>
44+
<aspectLibraries>
45+
<!-- highlight-start -->
46+
<aspectLibrary>
47+
<groupId>software.amazon.lambda</groupId>
48+
<artifactId>powertools-validation</artifactId>
49+
</aspectLibrary>
50+
<!-- highlight-end -->
51+
</aspectLibraries>
52+
</configuration>
53+
<executions>
54+
<execution>
55+
<goals>
56+
<goal>compile</goal>
57+
</goals>
58+
</execution>
59+
</executions>
60+
</plugin>
61+
...
62+
</plugins>
63+
</build>
64+
```
65+
66+
## Validating events
67+
68+
You can validate inbound and outbound events using `@Validation` annotation.
69+
70+
You can also use the `Validator#validate()` methods, if you want more control over the validation process such as handling a validation error.
71+
72+
We support JSON schema version 4, 6, 7 and 201909 (from [jmespath-jackson library](https://github.com/burtcorp/jmespath-java)).
73+
74+
### @Validation annotation
75+
76+
`@Validation` annotation is used to validate either inbound events or functions' response.
77+
78+
It will fail fast with `ValidationException` if an event or response doesn't conform with given JSON Schema.
79+
80+
While it is easier to specify a json schema file in the classpath (using the notation `"classpath:/path/to/schema.json"`), you can also provide a JSON String containing the schema.
81+
82+
```java
83+
public class MyFunctionHandler implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> {
84+
85+
@Override
86+
@Validation(inboundSchema = "classpath:/schema_in.json", outboundSchema = "classpath:/schema_out.json")
87+
public APIGatewayProxyResponseEvent handleRequest(APIGatewayProxyRequestEvent input, Context context) {
88+
// ...
89+
return something;
90+
}
91+
}
92+
```
93+
94+
**NOTE**: It's not a requirement to validate both inbound and outbound schemas - You can either use one, or both.
95+
96+
### Validate function
97+
98+
Validate standalone function is used within the Lambda handler, or any other methods that perform data validation.
99+
100+
You can also gracefully handle schema validation errors by catching `ValidationException`.
101+
102+
```java
103+
import static software.amazon.lambda.powertools.validation.Validator.*;
104+
105+
public class MyFunctionHandler implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> {
106+
107+
@Override
108+
public APIGatewayProxyResponseEvent handleRequest(APIGatewayProxyRequestEvent input, Context context) {
109+
try {
110+
validate(input, "classpath:/schema.json");
111+
} catch (ValidationException ex) {
112+
// do something before throwing it
113+
throw ex;
114+
}
115+
116+
// ...
117+
return something;
118+
}
119+
}
120+
```
121+
**NOTE**: Schemas are stored in memory for reuse, to avoid loading them from file each time.
122+
123+
## Built-in events and responses
124+
125+
For the following events and responses, the Validator will automatically perform validation on the content.
126+
127+
** Events **
128+
129+
Type of event | Class | Path to content |
130+
------------------------------------------------- | ------------------------------------------------- | -------------------------------------------------
131+
API Gateway REST | APIGatewayProxyRequestEvent | `body`
132+
API Gateway HTTP | APIGatewayV2HTTPEvent | `body`
133+
Application Load Balancer | ApplicationLoadBalancerRequestEvent | `body`
134+
Cloudformation Custom Resource | CloudFormationCustomResourceEvent | `resourceProperties`
135+
CloudWatch Logs | CloudWatchLogsEvent | `awslogs.powertools_base64_gzip(data)`
136+
EventBridge / Cloudwatch | ScheduledEvent | `detail`
137+
Kafka | KafkaEvent | `records[*][*].value`
138+
Kinesis | KinesisEvent | `Records[*].kinesis.powertools_base64(data)`
139+
Kinesis Firehose | KinesisFirehoseEvent | `Records[*].powertools_base64(data)`
140+
Kinesis Analytics from Firehose | KinesisAnalyticsFirehoseInputPreprocessingEvent | `Records[*].powertools_base64(data)`
141+
Kinesis Analytics from Streams | KinesisAnalyticsStreamsInputPreprocessingEvent | `Records[*].powertools_base64(data)`
142+
SNS | SNSEvent | `Records[*].Sns.Message`
143+
SQS | SQSEvent | `Records[*].body`
144+
145+
** Responses **
146+
147+
Type of response | Class | Path to content (envelope)
148+
------------------------------------------------- | ------------------------------------------------- | -------------------------------------------------
149+
API Gateway REST | APIGatewayProxyResponseEvent} | `body`
150+
API Gateway HTTP | APIGatewayV2HTTPResponse} | `body`
151+
API Gateway WebSocket | APIGatewayV2WebSocketResponse} | `body`
152+
Load Balancer | ApplicationLoadBalancerResponseEvent} | `body`
153+
Kinesis Analytics | KinesisAnalyticsInputPreprocessingResponse} | `Records[*].powertools_base64(data)``
154+
155+
## Custom events and responses
156+
157+
You can also validate any Event or Response type, once you have the appropriate schema.
158+
159+
Sometimes, you might want to validate only a portion of it - This is where the envelope parameter is for.
160+
161+
Envelopes are [JMESPath expressions](https://jmespath.org/tutorial.html) to extract a portion of JSON you want before applying JSON Schema validation.
162+
163+
Here is a custom event where we only want to validate each products:
164+
165+
```json
166+
{
167+
"basket": {
168+
"products" : [
169+
{
170+
"id": 43242,
171+
"name": "FooBar XY",
172+
"price": 258
173+
},
174+
{
175+
"id": 765,
176+
"name": "BarBaz AB",
177+
"price": 43.99
178+
}
179+
]
180+
}
181+
}
182+
```
183+
184+
Here is how you'd use the `envelope` parameter to extract the payload inside the products key before validating:
185+
186+
```java
187+
public class MyCustomEventHandler implements RequestHandler<MyCustomEvent, String> {
188+
189+
@Override
190+
@Validation(inboundSchema = "classpath:/my_custom_event_schema.json",
191+
envelope = "basket.products[*]")
192+
public String handleRequest(MyCustomEvent input, Context context) {
193+
return "OK";
194+
}
195+
}
196+
```
197+
198+
This is quite powerful because you can use JMESPath Query language to extract records from
199+
[arrays, slice and dice](https://jmespath.org/tutorial.html#list-and-slice-projections),
200+
to [pipe expressions](https://jmespath.org/tutorial.html#pipe-expressions)
201+
and [function](https://jmespath.org/tutorial.html#functions) expressions, where you'd extract what you need before validating the actual payload.
202+
203+
## JMESPath functions
204+
205+
JMESPath functions ensure to make an operation on a specific part of the json.validate
206+
207+
Powertools provides two built-in functions:
208+
209+
### powertools_base64 function
210+
211+
Use `powertools_base64` function to decode any base64 data.
212+
213+
This sample will decode the base64 value within the data key, and decode the JSON string into a valid JSON before we can validate it:
214+
215+
```json
216+
{
217+
"data" : "ewogICJpZCI6IDQzMjQyLAogICJuYW1lIjogIkZvb0JhciBYWSIsCiAgInByaWNlIjogMjU4Cn0="
218+
}
219+
```
220+
221+
```java
222+
public class MyEventHandler implements RequestHandler<MyEvent, String> {
223+
224+
@Override
225+
public String handleRequest(MyEvent myEvent, Context context) {
226+
validate(myEvent, "classpath:/schema.json", "powertools_base64(data)");
227+
return "OK";
228+
}
229+
}
230+
```
231+
232+
### powertools_base64_gzip function
233+
234+
Use `powertools_base64_gzip` function to decompress and decode base64 data.
235+
236+
This sample will decompress and decode base64 data:
237+
238+
```json
239+
{
240+
"data" : "H4sIAAAAAAAA/6vmUlBQykxRslIwMTYyMdIBcfMSc1OBAkpu+flOiUUKEZFKYOGCosxkkLiRqQVXLQDnWo6bOAAAAA=="
241+
}
242+
```
243+
244+
```java
245+
public class MyEventHandler implements RequestHandler<MyEvent, String> {
246+
247+
@Override
248+
public String handleRequest(MyEvent myEvent, Context context) {
249+
validate(myEvent, "classpath:/schema.json", "powertools_base64_gzip(data)");
250+
return "OK";
251+
}
252+
}
253+
```
254+
255+
**NOTE:** You don't need any function to transform a JSON String into a JSON object, powertools-validation will do it for you.
256+
In the 2 previous example, data contains JSON. Just provide the function to transform the base64 / gzipped / ... string into a clear JSON string.
257+
258+
### Bring your own JMESPath function
259+
260+
<Note type="warning">
261+
This should only be used for advanced use cases where you have special formats not covered by the built-in functions.
262+
New functions will be added to the 2 built-in ones.
263+
</Note>
264+
265+
266+
Your function must extend `io.burt.jmespath.function.BaseFunction`, take a String as parameter and return a String.
267+
You can read the [doc](https://github.com/burtcorp/jmespath-java#adding-custom-functions) for more information.
268+
269+
Here is an example that takes some xml and transform it into json:
270+
```java
271+
public class XMLFunction extends BaseFunction {
272+
public Base64Function() {
273+
super("powertools_xml", ArgumentConstraints.typeOf(JmesPathType.STRING));
274+
}
275+
276+
@Override
277+
protected <T> T callFunction(Adapter<T> runtime, List<FunctionArgument<T>> arguments) {
278+
T value = arguments.get(0).value();
279+
String xmlString = runtime.toString(value);
280+
281+
String jsonString = // ... transform xmlString to json
282+
283+
return runtime.createString(jsonString);
284+
}
285+
}
286+
```
287+
288+
Once your function is created, you need to add it to powertools:
289+
290+
```java
291+
ValidatorConfig.get().addFunction(new XMLFunction());
292+
```
293+
294+
You can then use it to do your validation:
295+
```java
296+
public class MyXMLEventHandler implements RequestHandler<MyEventWithXML, String> {
297+
298+
@Override
299+
public String handleRequest(MyEventWithXML myEvent, Context context) {
300+
validate(myEvent, "classpath:/schema.json", "powertools_xml(path.to.xml_data)");
301+
return "OK";
302+
}
303+
}
304+
```
305+
or using annotation:
306+
```java
307+
public class MyXMLEventHandler implements RequestHandler<MyEventWithXML, String> {
308+
309+
@Override
310+
@Validation(inboundSchema="classpath:/schema.json", envelope="powertools_xml(path.to.xml_data)")
311+
public String handleRequest(MyEventWithXML myEvent, Context context) {
312+
return "OK";
313+
}
314+
}
315+
```
316+
317+
## Change the schema version
318+
By default, powertools-validation is configured with [V7](https://json-schema.org/draft-07/json-schema-release-notes.html).
319+
You can use the `ValidatorConfig` to change that behaviour:
320+
321+
```java
322+
ValidatorConfig.get().setSchemaVersion(SpecVersion.VersionFlag.V4);
323+
```
324+
325+
## Advanced ObjectMapper settings
326+
If you need to configure the Jackson ObjectMapper, you can use the `ValidatorConfig`:
327+
328+
```java
329+
ObjectMapper objectMapper= ValidatorConfig.get().getObjectMapper();
330+
// update (de)serializationConfig or other properties
331+
```

docs/gatsby-config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ module.exports = {
3131
'utilities/sqs_large_message_handling',
3232
'utilities/batch',
3333
'utilities/parameters',
34+
'utilities/validation'
3435
],
3536
},
3637
navConfig: {

example/HelloWorldFunction/pom.xml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,11 @@
3333
<artifactId>powertools-parameters</artifactId>
3434
<version>0.5.0-beta</version>
3535
</dependency>
36+
<dependency>
37+
<groupId>software.amazon.lambda</groupId>
38+
<artifactId>powertools-validation</artifactId>
39+
<version>0.5.0-beta</version>
40+
</dependency>
3641
<dependency>
3742
<groupId>software.amazon.lambda</groupId>
3843
<artifactId>powertools-sqs</artifactId>
@@ -99,6 +104,10 @@
99104
<groupId>software.amazon.lambda</groupId>
100105
<artifactId>powertools-sqs</artifactId>
101106
</aspectLibrary>
107+
<aspectLibrary>
108+
<groupId>software.amazon.lambda</groupId>
109+
<artifactId>powertools-validation</artifactId>
110+
</aspectLibrary>
102111
</aspectLibraries>
103112
</configuration>
104113
<executions>

0 commit comments

Comments
 (0)