Validate request body against dynamic OpenAPI specification with json-schema-validator - 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:
Few elements such as openapi, paths, info, components.
When one object not referring other.
API Schema in our database as follows:
{
"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"
}
}
}
}
}
}
The data element in the API request body looks like this.
{
"action": "ADMIT",
"patient": {
"firstName": "John",
"lastName": "Doe"
}
}
Can json-schema-validator can help to achieve this?

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.

Related

jsonschema2pojo gives 1 Properties.java with new POJO for each Json element

I am using Java API like this
public void convertJsonToJavaClass(URL inputJsonUrl, File outputJavaClassDirectory, String packageName, String javaClassName) throws IOException {
com.sun.codemodel.JCodeModel jcodeModel = new com.sun.codemodel.JCodeModel();
GenerationConfig config = new DefaultGenerationConfig() {
#Override
public boolean isGenerateBuilders() {
return true;
}
#Override
public SourceType getSourceType() {
return SourceType.JSON;
}
};
SchemaMapper mapper = new SchemaMapper(new RuleFactory(config, new Jackson2Annotator(config), new SchemaStore()), new SchemaGenerator());
mapper.generate(jcodeModel, javaClassName, packageName, inputJsonUrl);
jcodeModel.build(outputJavaClassDirectory);
}
The input Json schema file has properties that look like this:
{
"type": "object",
"properties": {
"Market": {
"type": "string"
},
"Dealer": {
"type": "integer"
},
"Side": {
"type": "string",
"enum": ["Buy", "Sell", "Pay", "Receive", "Undisclosed"]
},
"Package": {
"type": "string",
"enum": ["Y", "N"]
}
},
"required": ["Market", "Side"]
}
The default output is 5 POJO classes, 1 for each Json element with a Properties.java to collect all the POJO. Whereas I would like 1 POJO with the Json elements converted to Java primitives. What to do please? Is this Json schema layout and Java API code the best practise for jsonschema2pojo? Thanks
The problem was using return SourceType.JSON; for a JsonSchema.
When I changed to return SourceType.JSONSCHEMA; it created the 1 POJO class.

Jmeter - How to get the last Username from the mongoDB from dynamic JSON

I am very new at mongoDB, and I am trying to get the last Username(int) from dynamic json.
The value of the username is always dynamic
Having mongoDB json as:
{
"_id": "39ecb71e-9641-4c7a-9f97-ebe33458e125",
"Username": "043020229",
"Name": "test1",
"Surname": "Tesfye",
"Email": "ab#gmail.com",
"Status": "UNCONF",
"DateOfBirth": {
"$date": "1982-02-25T15:24:17.923Z"
},
"RegistrationDate": {
"$date": "2021-07-13T14:24:25.991Z"
},
"ActivitySouce": "test",
"Password": "test"
}
{
"_id": "b6acb1ea-9629-42ea-94ce-71fc6812301d",
"Username": "095262760",
"Name": "test2",
"Surname": "test",
"Email": "ad#gmail.com",
"Status": "ACT",
"DateOfBirth": {
"$date": "1981-08-31T14:24:54.166Z"
},
"RegistrationDate": {
"$date": "2021-07-110T14:24:53.212Z"
},
"ActivitySouce": "test",
"Password": "test"
}
What I tried using Jmeter JRS223 sampler is:
import com.mongodb.client.MongoClients;
import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoDatabase;
import com.mongodb.client.model.Aggregates;
import com.mongodb.client.model.Accumulators;
import com.mongodb.client.model.Projections;
import com.mongodb.client.model.Filters;
import org.bson.Document;
import org.bson.types.ObjectId;
import static com.mongodb.client.model.Filters.eq;
import com.gmongo.*;
import com.mongodb.*;
import com.mongodb.DBCollection; // import DBCollection class
import org.bson.types.ObjectId
import com.mongodb.BasicDBObject
import com.mongodb.*
try {
MongoClient mongoClient = MongoClients.create("mongodb://localhost:27017/?readPreference=primary&appname=MongoDB%20Compass&ssl=false");
MongoDatabase database = mongoClient.getDatabase("FFF");
MongoCollection<Document> collection = database.getCollection("Users");
Document result = collection.find().sort(new Document("Username", -1)).limit(1);
vars.put("Username", result.get("Username").toString());
mongoClient.close()
}
catch (Exception e) {
SampleResult.setSuccessful(false);
SampleResult.setResponseCode("500");
SampleResult.setResponseMessage("Exception: " + e);
}
the error that i am seeing is:
Response code:500
Response message:Exception: org.codehaus.groovy.runtime.typehandling.GroovyCastException: Cannot cast object 'com.mongodb.client.internal.Java8FindIterableImpl#43dd792c' with class 'com.mongodb.client.internal.Java8FindIterableImpl' to class 'org.bson.Document'
How can I get the latest Username, having this set-up?
Every help is appreciated
This line of code:
Document result = collection.find().sort(new Document("Username", -1)).limit(1);
gives you a FindIterable instance, you need to invoke the relevant function in order to get the Document
So you can quickly fix your script by adding .take(1) to the end of your line:
Document result = collection.find().sort(new Document("Username", -1)).limit(1).take(1);
More information:
find in MongoCollection
MongoDB Performance Testing with JMeter

ADD self link for item in Custom ResponseHandler

I have the following ResponseHandler to override a Data Rest Response Handlers. Instead of returning all data, the handler returns only data for the owner (logged in user- currently hardcoded).
package com.osde.prepo.controller;
import com.osde.prepo.entity.Company;
import com.osde.prepo.repository.CompanyRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.rest.webmvc.RepositoryRestController;
import org.springframework.hateoas.CollectionModel;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.ArrayList;
import java.util.List;
import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo;
import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.methodOn;
/**
* customer controller to override some Data Rest Response Handlers
*/
#RepositoryRestController
public class CompanyController {
Logger logger = LoggerFactory.getLogger(CompanyController.class);
private final CompanyRepository companyRepository;
#Autowired
public CompanyController(CompanyRepository repo) {
companyRepository = repo;
}
#RequestMapping(method = RequestMethod.GET, value = "/companies")
public #ResponseBody
ResponseEntity<?> getAllCompaniesForCurrentUser() {
logger.info("custom implementation for get called!!");
List<Company> companies = new ArrayList<>();
companies = companyRepository.findByOwnerId("google-oauth2|107634743108791790006");
// convert to HATEOAS
CollectionModel<Company> resources = CollectionModel.of(companies);
resources.add(linkTo(methodOn(CompanyController.class)
.getAllCompaniesForCurrentUser())
.withSelfRel());
return ResponseEntity.ok(resources);
}
}
I get the following response, when consuming the API:
{
"_embedded": {
"companies": [
{
"ownerId": "google-oauth2|107634743108791790006",
"name": "Company 1",
"city": "Ort 1",
"country": "Germany",
"profile": "We are 1 ...",
"logoUrl": null
}
]
},
"_links": {
"self": {
"href": "http://localhost:8080/companies"
}
}
}
The original API has the following response (including self-links to the items) How do I have to change my code to get this response (paging is not of interest so far!)?
{
"_embedded": {
"companies": [
{
"ownerId": "google-oauth2|107634743108791790006",
"name": "Company 1",
"city": "Ort 1",
"country": "Germany",
"profile": "We are 1 ...",
"logoUrl": null,
"_links": {
"self": {
"href": "http://localhost:8080/api/companies/1"
},
"company": {
"href": "http://localhost:8080/api/companies/1"
}
}
}
]
},
"_links": {
"self": {
"href": "http://localhost:8080/api/companies/"
},
"profile": {
"href": "http://localhost:8080/api/profile/companies"
},
"search": {
"href": "http://localhost:8080/api/companies/search"
}
},
"page": {
"size": 20,
"totalElements": 1,
"totalPages": 1,
"number": 0
}
}
Here is a sample code to achieve this. I have assumed that your Entity has a getter for id as getId().It can be changed as per the code.
There is a need to extend this RepresentationModel on the entity class to get the add method.
eg
public class Company extends RepresentationModel<Company> implements Serializable {
#RequestMapping(method = RequestMethod.GET, value = "/companies")
public #ResponseBody
ResponseEntity<?> getAllCompaniesForCurrentUser() {
logger.info("custom implementation for get called!!");
List<Company> companies = companyRepository.findByOwnerId("google-oauth2|107634743108791790006");
for (Company company : companies) {
Link selfLink = linkTo(methodOn(CompanyController.class)
.getCompaniesById(company.getId())).withSelfRel();
company.add(selfLink);
}
Link link = linkTo(methodOn(CompanyController.class).getAllCompaniesForCurrentUser()).withSelfRel();
CollectionModel<Company> result = CollectionModel.of(companies, link);
return ResponseEntity.ok().body(companies);
}
There is an another way to do this as well. This won't require any change on the Entity class.
import org.springframework.hateoas.EntityModel;
#RequestMapping(method = RequestMethod.GET, value = "/companies")
public #ResponseBody
ResponseEntity<?> getAllCompaniesForCurrentUser() {
logger.info("custom implementation for get called!!");
List<EntityModel<Company>> companies = companyRepository
.findByOwnerId("google-oauth2|107634743108791790006")
.stream()
.map(this::generateLinks)
.collect(Collectors.toList());
CollectionModel<EntityModel<Company>> resource = CollectionModel.of(companies);
resource.add(entityLinks.linkToCollectionResource(Company.class));
resource.add(entityLinks.linksToSearchResources(Company.class));
return ResponseEntity.ok().body(resource);
}
private EntityModel<Company> generateLinks(Company company) {
EntityModel<Company> resource = EntityModel.of(company);
resource.add(entityLinks.linkToItemResource(Company.class, company.getId()).withSelfRel());
resource.add(entityLinks.linkToCollectionResource(Company.class));
resource.add(entityLinks.linksToSearchResources(Company.class));
return resource;
}
That points to the correct direction!
Now I get the following output:
[
{
"companyId": 1,
"ownerId": "google-oauth2|107634743108791790006",
"name": "Company 1",
"city": "Ort 1",
"country": "Germany",
"profile": "We are 1 ...",
"logoUrl": null,
"links": [
{
"rel": "self",
"href": "http://localhost:8080/api/companies/1"
},
{
"rel": "companies",
"href": "http://localhost:8080/api/companies{?page,size,sort}"
}
]
}
]
How to adjust the following aspects:
link to companies should be on the collection-level and just be http://localhost:8080/api/companies
rename links to _links

Search on Mongo Repositories using Criteria Query with MongoTemplate not working correctly in Mongo+springBoot app

in this app I'm doing I also inserted a feature that allows me to look for specific items inside my objects of my Database repositories. For that, I used mongo template.
Every object in my repository gets compounded of the following items:
[
{
"id": "5f759b198dfb247ccd6280b2",
"name": "Probando lo que cree",
"text": "Enrique Gordon",
"description": "Un poquito de todo ",
"images": [
"R0lGODlhLAEsAff"
],
"videos": [
"AAAAIGZ0eXBpc29"
],
"date": null,
"allComments": null
},
{
"id": "5f759d2b8dfb247ccd6280ba",
"name": "Probando lo que cree",
"text": "Enrique Gordon",
"description": "Algo nuevo",
"images": [
"R0lGODlhLAEsAff"
],
"videos": [
"AAAAIGZ0eXBpc29"
],
"date": null,
"allComments": null
},
{
"id": "5f75a5e2275d7d34914d2d98",
"name": "Zamorano",
"text": "Zamorano",
"description": "Zamorano",
"images": [
"iVBORw0KGgoAAAA",
"/9j/4T/+RXhpZgA"
],
"videos": [
"AAAAIGZ0eXBpc29"
],
"date": null,
"allComments": null
}
]
Thus having this in mind I initialized in my repository a function which returns a query bearing with the items : description, text, and name
REPOSITORY
public List<Post> searchPosts(String search){
return mongoTemplate.aggregate(Aggregation.newAggregation(
Aggregation.match(new Criteria().orOperator(
Criteria.where("text").regex(search),
Criteria.where("description").regex(search),
Criteria.where("name").regex(search),
))
),"Post",Post.class).getMappedResults();
}
*The post is the class already initialized with getters and setters, having in mind the concepts text, name , and description too
Then on my end point having in mind that criteria I pass whatever it brings as a path variable to my end point
CONTROLLER
#GetMapping("/post/{search}/search")
public List<Post> getSearchedPosts(#PathVariable ("search") String search){
return postRepository.searchPosts(search);
}
But for any reason when I bring to test the process in the postman, it brings me all the objects, but not the queried one.
Am I omitting something in the java query function?
You won't need aggregation for this.
Try this - Hope this would work.
Query query= new Query(
new Criteria()
.orOperator(
Criteria.where("text").regex(search),
Criteria.where("description").regex(search),
Criteria.where("name").regex(search)
)
);
return mongoTemplate.find(query, Post.class);
I have tried this on my local env with the sample documents you provided. I used Zamorano as search string. I am getting only one result. Here is my code -
import lombok.AllArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
#RestController
#AllArgsConstructor
public class SOController {
#Autowired
MongoTemplate mongoTemplate;
#GetMapping("/post/{search}/search")
public List<Post> getSearchedPosts(#PathVariable("search") String search) {
Query query = new Query(
new Criteria()
.orOperator(
Criteria.where("text").regex(search),
Criteria.where("description").regex(search),
Criteria.where("name").regex(search)
)
);
return mongoTemplate.find(query, Post.class, "so");
}
}
And this is img from postman -
PS - Make sure your imports are correct.

How to set array of records Using GenericRecordBuilder

I'm trying to turn a Scala object (i.e case class) into byte array.
In order to do so, I'm inserting the object content into a GenericRecordBuilder using its specific schema, and eventually using GenericDatumWriter i turn it into a byte array.
I have no problem to set primitive types, and array of primitive types into the GenericRecordBuilder.
But, I need help with Inserting array of records into the GenericRecordBuilder, and create a byte array from it.
What is the right way to insert array of records into the GenericRecordBuilder?
Here is part of what i'm trying to do:
This is the Schema:
{
"type": "record",
"name": "test1",
"namespace": "ns",
"fields": [
{
"name": "t_name",
"type": "string",
"default": "a"
},
{
"name": "t_num",
"type": "int",
"default": 0
},
{"name" : "t_arr", "type":
["null",
{"type": "array", "items": {
"name": "t_arr_a",
"type": "record",
"fields": [
{
"name": "t_arr_f1",
"type": "int",
"default": 0
},
{
"name": "t_arr_f2",
"type": "int",
"default": 0
}
]
}
}
]
}
]
}
This is the Scala class that populate the GenericRecordBuilder and transform it into byte Array:
package utils
import java.io.ByteArrayOutputStream
import org.apache.avro.{Schema, generic}
import org.apache.avro.generic.{GenericData, GenericDatumWriter}
import org.apache.avro.io.EncoderFactory
import org.apache.avro.generic.GenericRecordBuilder
object CheckRecBuilder extends App {
val avroSchema: Schema = new Schema.Parser().parse(this.getClass.getResourceAsStream("/data/myschema.avsc"))
val recordBuilder = new GenericRecordBuilder(avroSchema)
recordBuilder.set("t_name", "X")
recordBuilder.set("t_num", 100)
recordBuilder.set("t_arr", ???)
val record = recordBuilder.build()
val w = new GenericDatumWriter[GenericData.Record](avroSchema)
val outputStream = new ByteArrayOutputStream()
val e = EncoderFactory.get.binaryEncoder(outputStream, null)
w.write(record, e)
val barr = outputStream.toByteArray
println("End")
}
I manged to set the array of objects.
I wonder if there is a better or righter way for doing it.
Here is what I did:
Created a case class:
case class t_arr_a(t_arr_f1:Int, t_arr_f2:Int)
Created a method that transform case class into a GenericData.Record:
def caseClassToGenericDataRecord(cc:Product, schema:Schema): GenericData.Record = {
val childRecord = new GenericData.Record(schema.getElementType)
val values = cc.productIterator
cc.getClass.getDeclaredFields.map(f => childRecord.put(f.getName, values.next ))
childRecord
}
Updated the class CheckRecBuilder above:
replaced:
recordBuilder.set("t_arr", ???)
With:
val childSchema = new GenericData.Record(avroSchema2).getSchema.getField("t_arr").schema().getTypes().get(1)
val tArray = Array(t_arr_a(2,4), t_arr_a(25,14))
val tArrayGRecords: util.List[GenericData.Record]
= Some(yy.map(x => caseClassToGenericDataRecord(x,childSchema))).map(arr => java.util.Arrays.asList(arr: _*)).orNull
recordBuilder.set("t_arr", tArrayGRecords)