Skip to content

Validation

This utility provides JSON Schema validation for events and responses, including JMESPath support to unwrap events before validation.

Key features

  • Validate incoming event and response
  • JMESPath support to unwrap events before validation applies
  • Built-in envelopes to unwrap popular event sources payloads

Getting started

Tip: Using JSON Schemas for the first time?

Check this step-by-step tour in the official JSON Schema website.

You can validate inbound and outbound events using validator decorator.

You can also use the standalone validate function, if you want more control over the validation process such as handling a validation error.

We support any JSONSchema draft supported by fastjsonschema library.

Warning

Both validator decorator and validate standalone function expects your JSON Schema to be a dictionary, not a filename.

Validator decorator

Validator decorator is typically used to validate either inbound or functions' response.

It will fail fast with SchemaValidationError exception if event or response doesn't conform with given JSON Schema.

1
2
3
4
5
6
7
from aws_lambda_powertools.utilities.validation import validator

import schemas

@validator(inbound_schema=schemas.INPUT, outbound_schema=schemas.OUTPUT)
def handler(event, context):
    return event
1
2
3
4
{
    "message": "hello world",
    "username": "lessa"
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
INPUT = {
    "$schema": "http://json-schema.org/draft-07/schema",
    "$id": "http://example.com/example.json",
    "type": "object",
    "title": "Sample schema",
    "description": "The root schema comprises the entire JSON document.",
    "examples": [{"message": "hello world", "username": "lessa"}],
    "required": ["message", "username"],
    "properties": {
        "message": {
            "$id": "#/properties/message",
            "type": "string",
            "title": "The message",
            "examples": ["hello world"],
            "maxLength": 100,
        },
        "username": {
            "$id": "#/properties/username",
            "type": "string",
            "title": "The username",
            "examples": ["lessa"],
            "maxLength": 30,
        },
    },
}

OUTPUT = {
    "$schema": "http://json-schema.org/draft-07/schema",
    "$id": "http://example.com/example.json",
    "type": "object",
    "title": "Sample outgoing schema",
    "description": "The root schema comprises the entire JSON document.",
    "examples": [{"statusCode": 200, "body": "response"}],
    "required": ["statusCode", "body"],
    "properties": {
        "statusCode": {"$id": "#/properties/statusCode", "type": "integer", "title": "The statusCode"},
        "body": {"$id": "#/properties/body", "type": "string", "title": "The response"},
    },
}
Note

It's not a requirement to validate both inbound and outbound schemas - You can either use one, or both.

Validate function

Validate standalone function is typically used within the Lambda handler, or any other methods that perform data validation.

You can also gracefully handle schema validation errors by catching SchemaValidationError exception.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
from aws_lambda_powertools.utilities.validation import validate
from aws_lambda_powertools.utilities.validation.exceptions import SchemaValidationError

import schemas

def handler(event, context):
    try:
        validate(event=event, schema=schemas.INPUT)
    except SchemaValidationError as e:
        # do something before re-raising
        raise

    return event
1
2
3
4
{
    "data": "hello world",
    "username": "lessa"
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
INPUT = {
    "$schema": "http://json-schema.org/draft-07/schema",
    "$id": "http://example.com/example.json",
    "type": "object",
    "title": "Sample schema",
    "description": "The root schema comprises the entire JSON document.",
    "examples": [{"message": "hello world", "username": "lessa"}],
    "required": ["message", "username"],
    "properties": {
        "message": {
            "$id": "#/properties/message",
            "type": "string",
            "title": "The message",
            "examples": ["hello world"],
            "maxLength": 100,
        },
        "username": {
            "$id": "#/properties/username",
            "type": "string",
            "title": "The username",
            "examples": ["lessa"],
            "maxLength": 30,
        },
    },
}

OUTPUT = {
    "$schema": "http://json-schema.org/draft-07/schema",
    "$id": "http://example.com/example.json",
    "type": "object",
    "title": "Sample outgoing schema",
    "description": "The root schema comprises the entire JSON document.",
    "examples": [{"statusCode": 200, "body": "response"}],
    "required": ["statusCode", "body"],
    "properties": {
        "statusCode": {"$id": "#/properties/statusCode", "type": "integer", "title": "The statusCode"},
        "body": {"$id": "#/properties/body", "type": "string", "title": "The response"},
    },
}

Unwrapping events prior to validation

You might want to validate only a portion of your event - This is where the envelope parameter is for.

Envelopes are JMESPath expressions to extract a portion of JSON you want before applying JSON Schema validation.

Here is a sample custom EventBridge event, where we only validate what's inside the detail key:

We use the envelope parameter to extract the payload inside the detail key before validating.

1
2
3
4
5
6
7
from aws_lambda_powertools.utilities.validation import validator

import schemas

@validator(inbound_schema=schemas.INPUT, envelope="detail")
def handler(event, context):
    return event
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
{
    "id": "cdc73f9d-aea9-11e3-9d5a-835b769c0d9c",
    "detail-type": "Scheduled Event",
    "source": "aws.events",
    "account": "123456789012",
    "time": "1970-01-01T00:00:00Z",
    "region": "us-east-1",
    "resources": [
        "arn:aws:events:us-east-1:123456789012:rule/ExampleRule"
    ],
    "detail": {
        "message": "hello hello",
        "username": "blah blah"
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
INPUT = {
    "$schema": "http://json-schema.org/draft-07/schema",
    "$id": "http://example.com/example.json",
    "type": "object",
    "title": "Sample schema",
    "description": "The root schema comprises the entire JSON document.",
    "examples": [{"message": "hello world", "username": "lessa"}],
    "required": ["message", "username"],
    "properties": {
        "message": {
            "$id": "#/properties/message",
            "type": "string",
            "title": "The message",
            "examples": ["hello world"],
            "maxLength": 100,
        },
        "username": {
            "$id": "#/properties/username",
            "type": "string",
            "title": "The username",
            "examples": ["lessa"],
            "maxLength": 30,
        },
    },
}

OUTPUT = {
    "$schema": "http://json-schema.org/draft-07/schema",
    "$id": "http://example.com/example.json",
    "type": "object",
    "title": "Sample outgoing schema",
    "description": "The root schema comprises the entire JSON document.",
    "examples": [{"statusCode": 200, "body": "response"}],
    "required": ["statusCode", "body"],
    "properties": {
        "statusCode": {"$id": "#/properties/statusCode", "type": "integer", "title": "The statusCode"},
        "body": {"$id": "#/properties/body", "type": "string", "title": "The response"},
    },
}

This is quite powerful because you can use JMESPath Query language to extract records from arrays, slice and dice, to pipe expressions and function expressions, where you'd extract what you need before validating the actual payload.

Built-in envelopes

This utility comes with built-in envelopes to easily extract the payload from popular event sources.

1
2
3
4
5
6
7
from aws_lambda_powertools.utilities.validation import envelopes, validator

import schemas

@validator(inbound_schema=schemas.INPUT, envelope=envelopes.EVENTBRIDGE)
def handler(event, context):
    return event
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
{
    "id": "cdc73f9d-aea9-11e3-9d5a-835b769c0d9c",
    "detail-type": "Scheduled Event",
    "source": "aws.events",
    "account": "123456789012",
    "time": "1970-01-01T00:00:00Z",
    "region": "us-east-1",
    "resources": [
        "arn:aws:events:us-east-1:123456789012:rule/ExampleRule"
    ],
    "detail": {
        "message": "hello hello",
        "username": "blah blah"
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
INPUT = {
    "$schema": "http://json-schema.org/draft-07/schema",
    "$id": "http://example.com/example.json",
    "type": "object",
    "title": "Sample schema",
    "description": "The root schema comprises the entire JSON document.",
    "examples": [{"message": "hello world", "username": "lessa"}],
    "required": ["message", "username"],
    "properties": {
        "message": {
            "$id": "#/properties/message",
            "type": "string",
            "title": "The message",
            "examples": ["hello world"],
            "maxLength": 100,
        },
        "username": {
            "$id": "#/properties/username",
            "type": "string",
            "title": "The username",
            "examples": ["lessa"],
            "maxLength": 30,
        },
    },
}

OUTPUT = {
    "$schema": "http://json-schema.org/draft-07/schema",
    "$id": "http://example.com/example.json",
    "type": "object",
    "title": "Sample outgoing schema",
    "description": "The root schema comprises the entire JSON document.",
    "examples": [{"statusCode": 200, "body": "response"}],
    "required": ["statusCode", "body"],
    "properties": {
        "statusCode": {"$id": "#/properties/statusCode", "type": "integer", "title": "The statusCode"},
        "body": {"$id": "#/properties/body", "type": "string", "title": "The response"},
    },
}

Here is a handy table with built-in envelopes along with their JMESPath expressions in case you want to build your own.

Envelope name JMESPath expression
API_GATEWAY_REST "powertools_json(body)"
API_GATEWAY_HTTP "powertools_json(body)"
SQS "Records[*].powertools_json(body)"
SNS "Records[0].Sns.Message
EVENTBRIDGE "detail"
CLOUDWATCH_EVENTS_SCHEDULED "detail"
KINESIS_DATA_STREAM "Records[*].kinesis.powertools_json(powertools_base64(data))"
CLOUDWATCH_LOGS "awslogs.powertools_base64_gzip(data)

Advanced

Validating custom formats

Note

JSON Schema DRAFT 7 has many new built-in formats such as date, time, and specifically a regex format which might be a better replacement for a custom format, if you do have control over the schema.

JSON Schemas with custom formats like int64 will fail validation. If you have these, you can pass them using formats parameter:

custom_json_schema_type_format.json
1
2
3
4
5
6
{
    "lastModifiedTime": {
        "format": "int64",
        "type": "integer"
    }
}

For each format defined in a dictionary key, you must use a regex, or a function that returns a boolean to instruct the validator on how to proceed when encountering that type.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
from aws_lambda_powertools.utilities.validation import validate

import schema

custom_format = {
    "int64": True, # simply ignore it,
    "positive": lambda x: False if x < 0 else True
}

validate(event=event, schema=schemas.INPUT, formats=custom_format)
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
INPUT = {
    "$schema": "http://json-schema.org/draft-04/schema#",
    "definitions": {
        "AWSAPICallViaCloudTrail": {
            "properties": {
                "additionalEventData": {"$ref": "#/definitions/AdditionalEventData"},
                "awsRegion": {"type": "string"},
                "errorCode": {"type": "string"},
                "errorMessage": {"type": "string"},
                "eventID": {"type": "string"},
                "eventName": {"type": "string"},
                "eventSource": {"type": "string"},
                "eventTime": {"format": "date-time", "type": "string"},
                "eventType": {"type": "string"},
                "eventVersion": {"type": "string"},
                "recipientAccountId": {"type": "string"},
                "requestID": {"type": "string"},
                "requestParameters": {"$ref": "#/definitions/RequestParameters"},
                "resources": {"items": {"type": "object"}, "type": "array"},
                "responseElements": {"type": ["object", "null"]},
                "sourceIPAddress": {"type": "string"},
                "userAgent": {"type": "string"},
                "userIdentity": {"$ref": "#/definitions/UserIdentity"},
                "vpcEndpointId": {"type": "string"},
                "x-amazon-open-api-schema-readOnly": {"type": "boolean"},
            },
            "required": [
                "eventID",
                "awsRegion",
                "eventVersion",
                "responseElements",
                "sourceIPAddress",
                "eventSource",
                "requestParameters",
                "resources",
                "userAgent",
                "readOnly",
                "userIdentity",
                "eventType",
                "additionalEventData",
                "vpcEndpointId",
                "requestID",
                "eventTime",
                "eventName",
                "recipientAccountId",
            ],
            "type": "object",
        },
        "AdditionalEventData": {
            "properties": {
                "objectRetentionInfo": {"$ref": "#/definitions/ObjectRetentionInfo"},
                "x-amz-id-2": {"type": "string"},
            },
            "required": ["x-amz-id-2"],
            "type": "object",
        },
        "Attributes": {
            "properties": {
                "creationDate": {"format": "date-time", "type": "string"},
                "mfaAuthenticated": {"type": "string"},
            },
            "required": ["mfaAuthenticated", "creationDate"],
            "type": "object",
        },
        "LegalHoldInfo": {
            "properties": {
                "isUnderLegalHold": {"type": "boolean"},
                "lastModifiedTime": {"format": "int64", "type": "integer"},
            },
            "type": "object",
        },
        "ObjectRetentionInfo": {
            "properties": {
                "legalHoldInfo": {"$ref": "#/definitions/LegalHoldInfo"},
                "retentionInfo": {"$ref": "#/definitions/RetentionInfo"},
            },
            "type": "object",
        },
        "RequestParameters": {
            "properties": {
                "bucketName": {"type": "string"},
                "key": {"type": "string"},
                "legal-hold": {"type": "string"},
                "retention": {"type": "string"},
            },
            "required": ["bucketName", "key"],
            "type": "object",
        },
        "RetentionInfo": {
            "properties": {
                "lastModifiedTime": {"format": "int64", "type": "integer"},
                "retainUntilMode": {"type": "string"},
                "retainUntilTime": {"format": "int64", "type": "integer"},
            },
            "type": "object",
        },
        "SessionContext": {
            "properties": {"attributes": {"$ref": "#/definitions/Attributes"}},
            "required": ["attributes"],
            "type": "object",
        },
        "UserIdentity": {
            "properties": {
                "accessKeyId": {"type": "string"},
                "accountId": {"type": "string"},
                "arn": {"type": "string"},
                "principalId": {"type": "string"},
                "sessionContext": {"$ref": "#/definitions/SessionContext"},
                "type": {"type": "string"},
            },
            "required": ["accessKeyId", "sessionContext", "accountId", "principalId", "type", "arn"],
            "type": "object",
        },
    },
    "properties": {
        "account": {"type": "string"},
        "detail": {"$ref": "#/definitions/AWSAPICallViaCloudTrail"},
        "detail-type": {"type": "string"},
        "id": {"type": "string"},
        "region": {"type": "string"},
        "resources": {"items": {"type": "string"}, "type": "array"},
        "source": {"type": "string"},
        "time": {"format": "date-time", "type": "string"},
        "version": {"type": "string"},
    },
    "required": ["detail-type", "resources", "id", "source", "time", "detail", "region", "version", "account"],
    "title": "AWSAPICallViaCloudTrail",
    "type": "object",
    "x-amazon-events-detail-type": "AWS API Call via CloudTrail",
    "x-amazon-events-source": "aws.s3",
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
{
    "account": "123456789012",
    "detail": {
        "additionalEventData": {
            "AuthenticationMethod": "AuthHeader",
            "CipherSuite": "ECDHE-RSA-AES128-GCM-SHA256",
            "SignatureVersion": "SigV4",
            "bytesTransferredIn": 0,
            "bytesTransferredOut": 0,
            "x-amz-id-2": "ejUr9Nd/4IO1juF/a6GOcu+PKrVX6dOH6jDjQOeCJvtARUqzxrhHGrhEt04cqYtAZVqcSEXYqo0=",
        },
        "awsRegion": "us-west-1",
        "eventCategory": "Data",
        "eventID": "be4fdb30-9508-4984-b071-7692221899ae",
        "eventName": "HeadObject",
        "eventSource": "s3.amazonaws.com",
        "eventTime": "2020-12-22T10:05:29Z",
        "eventType": "AwsApiCall",
        "eventVersion": "1.07",
        "managementEvent": False,
        "readOnly": True,
        "recipientAccountId": "123456789012",
        "requestID": "A123B1C123D1E123",
        "requestParameters": {
            "Host": "lambda-artifacts-deafc19498e3f2df.s3.us-west-1.amazonaws.com",
            "bucketName": "lambda-artifacts-deafc19498e3f2df",
            "key": "path1/path2/path3/file.zip",
        },
        "resources": [
            {
                "ARN": "arn:aws:s3:::lambda-artifacts-deafc19498e3f2df/path1/path2/path3/file.zip",
                "type": "AWS::S3::Object",
            },
            {
                "ARN": "arn:aws:s3:::lambda-artifacts-deafc19498e3f2df",
                "accountId": "123456789012",
                "type": "AWS::S3::Bucket",
            },
        ],
        "responseElements": None,
        "sourceIPAddress": "AWS Internal",
        "userAgent": "AWS Internal",
        "userIdentity": {
            "accessKeyId": "ABCDEFGHIJKLMNOPQR12",
            "accountId": "123456789012",
            "arn": "arn:aws:sts::123456789012:assumed-role/role-name1/1234567890123",
            "invokedBy": "AWS Internal",
            "principalId": "ABCDEFGHIJKLMN1OPQRST:1234567890123",
            "sessionContext": {
                "attributes": {"creationDate": "2020-12-09T09:58:24Z", "mfaAuthenticated": "false"},
                "sessionIssuer": {
                    "accountId": "123456789012",
                    "arn": "arn:aws:iam::123456789012:role/role-name1",
                    "principalId": "ABCDEFGHIJKLMN1OPQRST",
                    "type": "Role",
                    "userName": "role-name1",
                },
            },
            "type": "AssumedRole",
        },
        "vpcEndpointId": "vpce-a123cdef",
    },
    "detail-type": "AWS API Call via CloudTrail",
    "id": "e0bad426-0a70-4424-b53a-eb902ebf5786",
    "region": "us-west-1",
    "resources": [],
    "source": "aws.s3",
    "time": "2020-12-22T10:05:29Z",
    "version": "0",
}

Built-in JMESPath functions

You might have events or responses that contain non-encoded JSON, where you need to decode before validating them.

You can use our built-in JMESPath functions within your expressions to do exactly that to decode JSON Strings, base64, and uncompress gzip data.

Info

We use these for built-in envelopes to easily to decode and unwrap events from sources like Kinesis, CloudWatch Logs, etc.


Last update: 2021-12-30
Back to top