|
| 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 | +``` |
0 commit comments