For way too long we had to use transformation policy (<set-body>
, throw new Exception
, <on-error>
) to validate JSON schema.
No more =)
We finally have content validation policies:
The policy validates the following content in the request or response against the schema:
- Presence of all required properties.
- Absence of additional properties, if the schema has the additionalProperties field set to false.
- Types of all properties. For example, if a schema specifies a property as an integer, the request (or response) must include an integer and not another type, such as a string.
- The format of the properties, if specified in the schema - for example, regex (if the pattern keyword is specified), minimum for integers, and so on.
And by "finally" I mean, since a few months now. But I don't see many posts about it, so here we are =)
Let's assume I already have an APIM bicep template that provisions API Management Instance.
I will now extend my Bicep template with schema to be used in validation, and in definitions.
Defining JSON schema
First things first. Below is my schema and payload example.
main.bicep
var schemaExampleUser1 = 'john.doe@${tenantName}.onmicrosoft.com'
var schemaPersonRequired = [
'firstName'
'lastName'
]
var schemaPerson = {
firstName: {
type: 'string'
}
lastName: {
type: 'string'
}
age: {
type: 'integer'
minimum: 0
}
email: {
type: 'string'
format: 'email'
pattern: '^\\S+@\\S+\\.\\S+$'
}
}
var personExample = {
firstName: 'John'
lastName: 'Doe'
age: 25
email: schemaExampleUser1
}
API Management resources
Policies
I have a number of policies that I want to mix-and-match depending on the API. To keep things neat, I keep them in separate variables, and compose a final definition as needed.
main.bicep
var authorizationPolicy = '''
<!-- Service Bus Authorization-->
<authentication-managed-identity resource="https://servicebus.azure.net/" output-token-variable-name="msi-access-token" ignore-error="false" />
<set-header name="Authorization" exists-action="override">
<value>@("Bearer " + (string)context.Variables["msi-access-token"])</value>
</set-header>
<set-header name="Content-Type" exists-action="override">
<value>application/atom+xml;type=entry;charset=utf-8</value>
</set-header>
<set-header name="BrokerProperties" exists-action="override">
<value>{}</value>
</set-header>
'''
var mockResponse = '<mock-response status-code="200" content-type="application/json" />'
var validatePersonPolicy = '''
<validate-content unspecified-content-type-action="detect" max-size="102400" size-exceeded-action="prevent" errors-variable-name="validationErrors">
<content type="application/json" validate-as="json" action="prevent" schema-id="Person" />
</validate-content>
'''
var policySchema = '''
<!--ADD {0}-->
<policies>
<inbound>
<base />
<!-- Validation -->
{1}
<!-- Authorization -->
{2}
<!-- Mock response -->
{3}
</inbound>
<backend>
<base />
</backend>
<outbound>
<base />
</outbound>
<on-error>
<base />
</on-error>
</policies>
'''
var personPolicy = format(policySchema, 'PORTFOLIO', validatePersonPolicy, authorizationPolicy, '<!-- N/A -->')
Schema
Now it's time to create schema definition in the already existing API Management instance.
main.bicep
resource apiManagement 'Microsoft.ApiManagement/service@2021-08-01' existing = {
name: serviceName
}
resource apiManagement_schemaPerson 'Microsoft.ApiManagement/service/schemas@2021-08-01' = {
name: 'Person'
parent: apiManagement
properties: {
schemaType: 'json'
description: 'Schema for a Person Object'
document: any({
type: 'array'
items: {
type: 'object'
properties: schemaPerson
required: schemaPersonRequired
}
})
}
}
After deployment, you will find it under APIs/Schemas:
API
Next step is to create an API, definition, and an operation. I'm referencing the schema and the example created in the first step.
In this example, the Add Person
operation saves the payload to a ServiceBus queue, that's why the urlTemplate
is set to /${serviceBusQueueName1}/messages
.
I'm mentioning it just to point out you have to remember about the "messages" at the end of the url ;)
main.bicep
resource apiManagement_apiName 'Microsoft.ApiManagement/service/apis@2021-08-01' = {
name: '${serviceName}/${apiName}'
properties: {
displayName: apiDisplayName
subscriptionRequired: true
path: 'person-schema-validation'
protocols: [
'https'
]
isCurrent: true
description: 'Personal data ingestion'
subscriptionKeyParameterNames: {
header: 'Subscription-Key-Header-Name'
query: 'subscription-key-query-param-name'
}
}
}
resource apiManagement_apiName_apiSchemaGuid 'Microsoft.ApiManagement/service/apis/schemas@2021-08-01' = {
parent: apiManagement_apiName
name: apiSchemaGuid
properties: {
contentType: 'application/vnd.oai.openapi.components+json'
document: any({
components: {
schemas: {
Definition_Person: {
type: 'object'
properties: schemaPerson
required: schemaPersonRequired
example: personExample
}
}
}
})
}
}
resource apiManagement_apiName_operation_addPerson 'Microsoft.ApiManagement/service/apis/operations@2021-08-01' = {
parent: apiManagement_apiName
name: operation_addPerson
dependsOn: [
apiManagement_apiName_apiSchemaGuid
]
properties: {
request: {
headers: [
{
name: 'Content-Type'
type: 'string'
required: true
values: [
'application/json'
]
}
]
representations: [
{
contentType: 'application/json'
schemaId: apiSchemaGuid
typeName: 'Definition_Person'
}
]
}
displayName: 'Add Person'
description: 'Add Person Information to ServiceBus. \nThe Request Body is parsed to ensure correct schema.'
method: 'POST'
urlTemplate: '/${serviceBusQueueName1}/messages'
}
}
Schema Validation
And now, that I have everything in place, I can use schema validation. Finally!
The first policy is actually not a validation. It is applied to all the operations, and it ensures that the backend url is set to the ServiceBus Endpoint.
The second policy ensures that the payload receives passes schema validation.
main.bicep
resource serviceName_apiName_policy 'Microsoft.ApiManagement/service/apis/policies@2021-08-01' = {
parent: apiManagement_apiName
name: 'policy'
properties: {
value: '<!-- All operations-->\r\n<policies>\r\n <inbound>\r\n <base/>\r\n <set-backend-service base-url="${serviceBusEndpoint}" />\r\n <set-header name="Content-Type" exists-action="override">\r\n <value>application/json</value>\r\n </set-header>\r\n </inbound>\r\n <backend>\r\n <base />\r\n </backend>\r\n <outbound>\r\n <base />\r\n </outbound>\r\n <on-error>\r\n <base />\r\n </on-error>\r\n</policies>'
format: 'rawxml'
}
}
resource apiManagement_apiName_operation_addPerson_policy 'Microsoft.ApiManagement/service/apis/operations/policies@2021-08-01' = {
parent: apiManagement_apiName_operation_addPerson
name: 'policy'
properties: {
value: personPolicy
format: 'rawxml'
}
}
The bicep template is on GitHub.
Top comments (2)
Your validate-content defines schema-id="Portfolio" but I can't see "Portfolio" defined anywhere else, so this doesn't seem correct?
thank you @mattfrear for letting me know. 🙏 It should indeed be "Person" and not "Portfolio".
I have another schema used productively and I missed the schema-id parameter when preparing the sample