Remove validation bits

Discard invalid properties from json decode. JSON output should only
contain valid properties. This saves time in preventing post
processing of output for valid fields.

Ensure round trip validity with validation bits removed and required
properties populated.

Fix bugs in json decode.

Overhaul unit tests to use valijson. Add tests with static examples
to validate against schema. Use and nlohmann for better schema
validation over intrinsic libcper validation.

Example json output before:
{
  "ValidationBits": {
    "LevelValid": false,
    "CorrectedValid": true
  },
  "Level": 1,
  "Corrected": true
}

After:
{
  "Corrected": true
}

Change-Id: I188bdc2827a57d938c22a431238fadfcdc939ab8
Signed-off-by: Aushim Nagarkatti <anagarkatti@nvidia.com>
diff --git a/tests/test-utils.cpp b/tests/test-utils.cpp
index e16413c..5b5925a 100644
--- a/tests/test-utils.cpp
+++ b/tests/test-utils.cpp
@@ -6,15 +6,45 @@
 
 #include <cstdio>
 #include <cstdlib>
+#include <fstream>
+#include <filesystem>
 #include "test-utils.hpp"
 
 #include <libcper/BaseTypes.h>
 #include <libcper/generator/cper-generate.h>
 
+namespace fs = std::filesystem;
+
+// Truly optional properties that shouldn't be added to "required" field for
+// validating the entire schema with validationbits=1
+const static std::map<std::string, std::vector<std::string> >
+	optional_properties_map = {
+		{ "./sections/cper-arm-processor.json",
+		  { "vendorSpecificInfo" } },
+		{ "./cper-json-section-log.json", { "header" } },
+		{ "./sections/cper-cxl-protocol.json",
+		  { "capabilityStructure", "deviceSerial" } },
+		{ "./sections/cper-generic-dmar.json",
+		  { "faultReason", "description" } },
+		{ "./sections/cper-cxl-component.json",
+		  { "cxlComponentEventLog" } },
+	};
+
+nlohmann::json loadJson(const char *filePath)
+{
+	std::ifstream file(filePath);
+	if (!file.is_open()) {
+		std::cerr << "Failed to open file: " << filePath << std::endl;
+	}
+	nlohmann::json out = nlohmann::json::parse(file, nullptr, false);
+	return out;
+}
+
 //Returns a ready-for-use memory stream containing a CPER record with the given sections inside.
 FILE *generate_record_memstream(const char **types, UINT16 num_types,
 				char **buf, size_t *buf_size,
-				int single_section)
+				int single_section,
+				GEN_VALID_BITS_TEST_TYPE validBitsType)
 {
 	//Open a memory stream.
 	FILE *stream = open_memstream(buf, buf_size);
@@ -22,13 +52,162 @@
 	//Generate a section to the stream, close & return.
 	if (!single_section) {
 		generate_cper_record(const_cast<char **>(types), num_types,
-				     stream);
+				     stream, validBitsType);
 	} else {
 		generate_single_section_record(const_cast<char *>(types[0]),
-					       stream);
+					       stream, validBitsType);
 	}
 	fclose(stream);
 
 	//Return fmemopen() buffer for reading.
 	return fmemopen(*buf, *buf_size, "r");
 }
+
+void iterate_make_required_props(nlohmann::json &jsonSchema,
+				 std::vector<std::string> &optional_props)
+{
+	//id
+	const auto it_id = jsonSchema.find("$id");
+	if (it_id != jsonSchema.end()) {
+		auto id_strptr = it_id->get_ptr<const std::string *>();
+		std::string id_str = *id_strptr;
+		if (id_str.find("header") != std::string::npos ||
+		    id_str.find("section-descriptor") != std::string::npos) {
+			return;
+		}
+	}
+	//oneOf
+	const auto it_oneof = jsonSchema.find("oneOf");
+	if (it_oneof != jsonSchema.end()) {
+		//Iterate over oneOf properties
+		for (auto &oneOfProp : *it_oneof) {
+			iterate_make_required_props(oneOfProp, optional_props);
+		}
+	}
+
+	//items
+	const auto it_items = jsonSchema.find("items");
+	if (it_items != jsonSchema.end()) {
+		iterate_make_required_props(*it_items, optional_props);
+	}
+	//required
+	const auto it_req = jsonSchema.find("required");
+	if (it_req == jsonSchema.end()) {
+		return;
+	}
+
+	//properties
+	const auto it_prop = jsonSchema.find("properties");
+	if (it_prop == jsonSchema.end()) {
+		return;
+	}
+	nlohmann::json &propertyFields = *it_prop;
+	nlohmann::json::array_t property_list;
+	if (propertyFields.is_object()) {
+		for (auto &[key, value] : propertyFields.items()) {
+			const auto it_find_opt_prop =
+				std::find(optional_props.begin(),
+					  optional_props.end(), key);
+			if (it_find_opt_prop == optional_props.end()) {
+				//Add to list if property is not optional
+				property_list.push_back(key);
+			}
+
+			iterate_make_required_props(value, optional_props);
+		}
+	}
+
+	*it_req = property_list;
+}
+
+// Document loader callback function
+const nlohmann::json *documentLoader(const std::string &uri)
+{
+	// Load the schema from a file
+	nlohmann::json *ref_schema = new nlohmann::json;
+	*ref_schema = loadJson(uri.c_str());
+	if (ref_schema->is_discarded()) {
+		std::cerr << "Could not open schema file: " << uri << std::endl;
+	}
+	std::vector<std::string> opt = {};
+	const auto it_optional_file = optional_properties_map.find(uri);
+	if (it_optional_file != optional_properties_map.end()) {
+		opt = it_optional_file->second;
+	}
+	iterate_make_required_props(*ref_schema, opt);
+
+	return ref_schema;
+}
+
+// Document release callback function
+void documentRelease(const nlohmann::json *adapter)
+{
+	delete adapter; // Free the adapter memory
+}
+
+int schema_validate_from_file(const char *schema_file_path,
+			      nlohmann::json &jsonData,
+			      std::string &error_message)
+{
+	// Load the schema
+	nlohmann::json schema_root = loadJson(schema_file_path);
+	if (schema_root.is_discarded()) {
+		std::cerr << "Could not open schema file: " << schema_file_path
+			  << std::endl;
+		return 0;
+	}
+
+	fs::path pathObj(schema_file_path);
+	fs::path base_path = pathObj.parent_path();
+	try {
+		fs::current_path(base_path);
+		// std::cout << "Changed directory to: " << fs::current_path()
+		// 	  << std::endl;
+	} catch (const fs::filesystem_error &e) {
+		std::cerr << "Filesystem error: " << e.what() << std::endl;
+	}
+
+	// Parse the json schema into an internal schema format
+	valijson::Schema schema;
+	valijson::SchemaParser parser;
+	valijson::adapters::NlohmannJsonAdapter schemaDocumentAdapter(
+		schema_root);
+
+	// Set up callbacks for resolving external references
+	try {
+		parser.populateSchema(schemaDocumentAdapter, schema,
+				      documentLoader, documentRelease);
+	} catch (std::exception &e) {
+		std::cerr << "Failed to parse schema: " << e.what()
+			  << std::endl;
+		return 0;
+	}
+
+	// Perform validation
+	valijson::Validator validator(valijson::Validator::kStrongTypes);
+	valijson::ValidationResults results;
+	valijson::adapters::NlohmannJsonAdapter targetDocumentAdapter(jsonData);
+	if (!validator.validate(schema, targetDocumentAdapter, &results)) {
+		std::cerr << "Validation failed." << std::endl;
+		valijson::ValidationResults::Error error;
+		unsigned int errorNum = 1;
+		while (results.popError(error)) {
+			std::string context;
+			std::vector<std::string>::iterator itr =
+				error.context.begin();
+			for (; itr != error.context.end(); itr++) {
+				context += *itr;
+			}
+
+			std::cout << "Error #" << errorNum << std::endl
+				  << "  context: " << context << std::endl
+				  << "  desc:    " << error.description
+				  << std::endl;
+			++errorNum;
+		}
+		return 0;
+	}
+
+	error_message = "Schema validation successful";
+	return 1;
+}