blob: 5b5925a5a9a4198eed6eddcddb4c9472b48d826b [file] [log] [blame]
/**
* Defines utility functions for testing CPER-JSON IR output from the cper-parse library.
*
* Author: Lawrence.Tang@arm.com
**/
#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,
GEN_VALID_BITS_TEST_TYPE validBitsType)
{
//Open a memory stream.
FILE *stream = open_memstream(buf, buf_size);
//Generate a section to the stream, close & return.
if (!single_section) {
generate_cper_record(const_cast<char **>(types), num_types,
stream, validBitsType);
} else {
generate_single_section_record(const_cast<char *>(types[0]),
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;
}