Paramesh Korrakuti
Paramesh Korrakuti

Reputation: 2067

Validate request body against dynamic OpenAPI specification with json-schema-validator

I need to build an API to validate the request body against the registered schema for the respective type & subType.

API Contract:

{
    "id": "<any-uuid>",
    "type": "<some-type>",
    "subType": "<some-sub-type>",
    "data": {
        
    }
}

Here, OpenAPI schema will be fetched based on the type and subType and then need to validate the data element against the respective OpenAPI schema.

Wrote the below snippet:

Map<String, Object> data = //get the data object from API request body;
JsonSchemaFactory jsonSchemaFactory = JsonSchemaFactory.getInstance(VersionFlag.V7);
ObjectMapper objectMapper = new ObjectMapper();
JsonNode node = objectMapper.convertValue(data, JsonNode.class);
String schemaJson = // fetch the registered schema for type and subtype
JsonSchema schema = jsonSchemaFactory.getSchema(schemaJson);
Set<ValidationMessage> errors = schema.validate(node);

// Throw exception when errors present in the Json Payload
if (errors.size() > 0) {
    // throw the exception with errors
}

This code is working, when the schema don't have:

  1. Few elements such as openapi, paths, info, components.
  2. When one object not referring other.
{
  "openapi": "3.0.0",
  "paths": {},
  "info": {
    "title": "Patient Info API",
    "version": "v0.1.0"
  },
  "components": {
    "schemas": {
      "Data": {
        "type": "object",
        "required": [
          "action",
          "patient"
        ],
        "properties": {
          "action": {
            "type": "string",
            "enum": [
              "ADMIT",
              "DISCHARGE",
              "TRANSFER"
            ]
          },
          "patient": {
            "$ref": "#/components/schemas/Patient"
          }
        }
      },
      "Patient": {
        "type": "object",
        "required": [
          "firstName",
          "lastName"
        ],
        "properties": {
          "firstName": {
            "type": "string"
          },
          "lastName": {
            "type": "string"
          }
        }
      }
    }
  }
}
{
    "action": "ADMIT",
    "patient": {
        "firstName": "John",
        "lastName": "Doe"
    }
}

Can json-schema-validator help to achieve this?

Upvotes: 0

Views: 3804

Answers (1)

Paramesh Korrakuti
Paramesh Korrakuti

Reputation: 2067

For simplicity, wrote all the logic inside the Spring Controller itself.

package com.schema.validator.controller;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.io.Files;
import com.networknt.schema.*;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.Resource;
import org.springframework.http.ResponseEntity;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.io.IOException;
import java.nio.charset.Charset;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * A REST API to validate the request body against Open API Schema
 *
 */
@RestController
@RequestMapping("schema-validate")
public class SchemaValidatorController {

    private static final ObjectMapper objectMapper = new ObjectMapper();

    private static final JsonSchemaFactory jsonSchemaFactory =
            JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V6);

    @Value("classpath:schema/json-schema.json")
    private Resource schemaResource;

    @PostMapping
    public ResponseEntity<List<String>> validateSchema(@RequestBody Map<String, Object> jsonRequest) throws IOException {

        String schemaJson = Files.asCharSource(schemaResource.getFile(), Charset.defaultCharset()).read();

        final JsonNode requestJsonNode = objectMapper.valueToTree(jsonRequest);
        JsonNode componentNode = objectMapper.readTree(schemaJson).get("components");
        // Get first name in the JsonNode
        JsonNode schemaNode = componentNode.get("schemas").elements().next();

        ((ObjectNode) schemaNode).set("components", componentNode);
        SchemaValidatorsConfig config = new SchemaValidatorsConfig();
        config.setTypeLoose(false);
        config.setHandleNullableField(true);
        JsonSchema jsonSchema = jsonSchemaFactory.getSchema(schemaNode, config);

        Set<ValidationMessage> errors =
                jsonSchema.validate(requestJsonNode, requestJsonNode, "data");

        if (CollectionUtils.isEmpty(errors)) {
            return ResponseEntity.ok(Collections.emptyList());
        }

        return ResponseEntity.ok(errors.stream()
                .map(ValidationMessage::getMessage)
                .collect(Collectors.toList()));
    }

}

Here, json-schema.json created under resources/schema folder.

Limitation: The root element in Open API schema should be declared as a first child of schemas in Open API spec.

Upvotes: 0

Related Questions