#include #include #include #include #include "jsoncdaccord.h" #include "internal.h" #include "optional.h" #include "../../include/zlog_module.h" json_object *json = NULL; json_object *schema = NULL; json_object *defs = NULL; #ifdef JDAC_STORE static storage_node *storagelist_head = NULL; #endif static char *jdacerrstr[JDAC_ERR_MAX] = {"VALID", "GENERAL ERROR", "JSON FILE NOT FOUND", "SCHEMA FILE NOT FOUND", "WRONG ARGUEMNTS GIVEN", "SCHEMA ERROR", "INVALID", "INVALID TYPE", "INVALID REQUIRED", "INVALID SUBSCHEMA LOGIC (allOf, anyOf, oneOf, or not)", "INVALID CONST", "INVALID ENUMS", "INVALID STRING LENGTH", "INVALID UNIQUE ITEMS", "INVALID UNIQUE CONTAINS", "INVALID PREFIXITEMS", "INVALID ITEMS", "INVALID ARRAY LENGTH", "INVALID NUMBER", "INVALID REFERENCE", "PATTERN NO MATCH", "REGEX MISMATCH", "REGEX MATCH", "REGEX COMPILE FAILED"}; const char *jdac_errorstr(unsigned int jdac_errors) { if (jdac_errors < JDAC_ERR_MAX) { return jdacerrstr[jdac_errors]; } return NULL; } int _jdac_load(const char *jsonfile, const char *jsonschema) { json = json_object_from_file(jsonfile); if (json == NULL) { return JDAC_ERR_JSON_NOT_FOUND; } schema = json_object_from_file(jsonschema); if (schema == NULL) { json_object_put(json); return JDAC_ERR_SCHEMA_NOT_FOUND; } return JDAC_ERR_VALID; } int __jdac_inspect_type(json_object *jobj, const char *type) { if (strcmp(type, "object") == 0) { if (json_object_is_type(jobj, json_type_object)) { return JDAC_ERR_VALID; } } else if (strcmp(type, "array") == 0) { if (json_object_is_type(jobj, json_type_array)) { return JDAC_ERR_VALID; } } else if (strcmp(type, "string") == 0) { if (json_object_is_type(jobj, json_type_string)) { return JDAC_ERR_VALID; } } else if (strcmp(type, "integer") == 0) { if (json_object_is_type(jobj, json_type_int)) { return JDAC_ERR_VALID; } if (json_object_is_type(jobj, json_type_double)) { double value = json_object_get_double(jobj); if (value == round(value)) { // "zero fractional part is an integer" return JDAC_ERR_VALID; } } } else if (strcmp(type, "double") == 0) { if (json_object_is_type(jobj, json_type_double)) { return JDAC_ERR_VALID; } } else if (strcmp(type, "number") == 0) { if (json_object_is_type(jobj, json_type_double) || json_object_is_type(jobj, json_type_int)) { return JDAC_ERR_VALID; } } else if (strcmp(type, "boolean") == 0) { if (json_object_is_type(jobj, json_type_boolean)) { return JDAC_ERR_VALID; } } else if (strcmp(type, "null") == 0) { if (json_object_is_type(jobj, json_type_null)) { return JDAC_ERR_VALID; } } else { LOG_MOD(warn, ZLOG_MOD_JSCHEM, "WARN unknown type in check type %s\n", type); return JDAC_ERR_SCHEMA_ERROR; } return JDAC_ERR_INVALID_TYPE; } int _jdac_check_type(json_object *jobj, json_object *jschema) { json_object *jtype = json_object_object_get(jschema, "type"); if (jtype == NULL) { return JDAC_ERR_VALID; } else if (json_object_is_type(jtype, json_type_string)) { const char *type = json_object_get_string(jtype); return __jdac_inspect_type(jobj, type); } else if (json_object_is_type(jtype, json_type_array)) { int arraylen = json_object_array_length(jtype); for (int i = 0; i < arraylen; i++) { json_object *iobj = json_object_array_get_idx(jtype, i); if (!json_object_is_type(iobj, json_type_string)) { return JDAC_ERR_SCHEMA_ERROR; } const char *type = json_object_get_string(iobj); int err = __jdac_inspect_type(jobj, type); if (err == JDAC_ERR_VALID) { return JDAC_ERR_VALID; } } return JDAC_ERR_INVALID_TYPE; } else { return JDAC_ERR_SCHEMA_ERROR; } } int _jdac_check_required(json_object *jobj, json_object *jschema) { //LOG_MOD(debug, ZLOG_MOD_JSCHEM, "%s\n%s\n", __func__, json_object_to_json_string(jobj)); json_object *jarray = json_object_object_get(jschema, "required"); int missing_required_key = 0; if (jarray) { int arraylen = json_object_array_length(jarray); for (int i = 0; i < arraylen; i++) { json_object *iobj = json_object_array_get_idx(jarray, i); const char *key = json_object_get_string(iobj); if (key) { //LOG_MOD(debug, ZLOG_MOD_JSCHEM, "%s\n", key); // use json_object_object_get_ex becuase of json_type_null types json_object *required_object = NULL; int err = json_object_object_get_ex(jobj, key, &required_object); if (err == 0) { LOG_MOD(debug, ZLOG_MOD_JSCHEM, "required key missing: %s\n", key); missing_required_key = 1; } } } } if (missing_required_key) { return JDAC_ERR_INVALID_REQUIRED; } else { return JDAC_ERR_VALID; } } int _jdac_check_properties(json_object *jobj, json_object *jschema) { // LOG_MOD(debug, ZLOG_MOD_JSCHEM, "%s\n", __func__); json_object *jprops = json_object_object_get(jschema, "properties"); if (jprops) { json_object_object_foreach(jprops, jprop_key, jprop_val) { // LOG_MOD(debug, ZLOG_MOD_JSCHEM, "key of prop is %s\n", jprop_key); json_object *iobj = json_object_object_get(jobj, jprop_key); //LOG_MOD(debug, ZLOG_MOD_JSCHEM, "iobj %s type %d\nkey %s\nval %s\n", json_object_get_string(iobj), json_object_get_type(iobj), jprop_key, json_object_get_string(jprop_val)); if (iobj) { int err = _jdac_validate_instance(iobj, jprop_val); if (err) { return err; } } } } return JDAC_ERR_VALID; } int _jdac_check_prefixItems_and_items(json_object *jobj, json_object *jschema) { json_object *jprefixitems = json_object_object_get(jschema, "prefixItems"); json_object *jitems = json_object_object_get(jschema, "items"); int jobj_arraylen = json_object_array_length(jobj); int prefixitems_arraylen = 0; if (jprefixitems) { if (!json_object_is_type(jprefixitems, json_type_array)) { return JDAC_ERR_SCHEMA_ERROR; } prefixitems_arraylen = json_object_array_length(jprefixitems); for (int i = 0; i < jobj_arraylen && i < prefixitems_arraylen; i++) { //LOG_MOD(debug, ZLOG_MOD_JSCHEM, "i=%d prefixitems\n", i); json_object *iobj = json_object_array_get_idx(jobj, i); json_object *ischema = json_object_array_get_idx(jprefixitems, i); int err = _jdac_validate_instance(iobj, ischema); if (err) { return JDAC_ERR_INVALID_PREFIXITEMS; } } } if (jitems) { if (!json_object_is_type(jitems, json_type_object) && !json_object_is_type(jitems, json_type_boolean)) { return JDAC_ERR_SCHEMA_ERROR; } for (int i = prefixitems_arraylen; i < jobj_arraylen; i++) { //LOG_MOD(debug, ZLOG_MOD_JSCHEM, "i=%d items\n", i); json_object *iobj = json_object_array_get_idx(jobj, i); int err = _jdac_validate_instance(iobj, jitems); if (err) { return JDAC_ERR_INVALID_ITEMS; } } } return JDAC_ERR_VALID; } json_object *_jdac_get_defs_from_ref(json_object *ref) { char key[128]; if (!json_object_is_type(ref, json_type_string)) { return NULL; } const char *refstr = json_object_get_string(ref); if (sscanf(refstr, "#/$defs/%s", key) == 1) { return json_object_object_get(defs, key); } return NULL; } int _jdac_value_is_equal(json_object *jobj1, json_object *jobj2) { if (json_object_equal(jobj1, jobj2)) { return JDAC_ERR_VALID; } if (json_object_is_type(jobj1, json_type_double) && json_object_is_type(jobj2, json_type_int)) { double value = json_object_get_double(jobj1); double value2 = json_object_get_int64(jobj2); if (value == round(value) && value == value2) { return JDAC_ERR_VALID; } } if (json_object_is_type(jobj1, json_type_int) && json_object_is_type(jobj2, json_type_double)) { double value = json_object_get_double(jobj2); double value2 = json_object_get_int64(jobj1); if (value == round(value) && value == value2) { return JDAC_ERR_VALID; } } return JDAC_ERR_INVALID; } int _jdac_check_const(json_object *jobj, json_object *jschema) { json_object *jconst; int err = json_object_object_get_ex(jschema, "const", &jconst); if (err == 0) { return JDAC_ERR_VALID; } err = _jdac_value_is_equal(jobj, jconst); if (err == JDAC_ERR_VALID) { return JDAC_ERR_VALID; } return JDAC_ERR_INVALID_CONST; } int _jdac_check_enums(json_object *jobj, json_object *jschema) { json_object *jenum_array = json_object_object_get(jschema, "enum"); if (!jenum_array) { return JDAC_ERR_VALID; } if (!json_object_is_type(jenum_array, json_type_array)) { return JDAC_ERR_SCHEMA_ERROR; } int arraylen = json_object_array_length(jenum_array); for (int i = 0; i < arraylen; i++) { json_object *ienum = json_object_array_get_idx(jenum_array, i); int err = _jdac_value_is_equal(jobj, ienum); if (err == JDAC_ERR_VALID) { return JDAC_ERR_VALID; } } LOG_MOD(error, ZLOG_MOD_JSCHEM, "ERROR: enum check failed (%s not in enum)\n", json_object_to_json_string(jobj)); return JDAC_ERR_INVALID_ENUMS; } int _jdac_check_uniqueItems(json_object *jobj, json_object *jschema) { json_object *juniq = json_object_object_get(jschema, "uniqueItems"); if (juniq) { if (!json_object_is_type(juniq, json_type_boolean)) { return JDAC_ERR_SCHEMA_ERROR; } if (json_object_get_boolean(juniq) == 0) { return JDAC_ERR_VALID; } int arraylen = json_object_array_length(jobj); for (int i = 0; i < arraylen - 1; i++) { json_object *iobj = json_object_array_get_idx(jobj, i); for (int j = i + 1; j < arraylen; j++) { json_object *uobj = json_object_array_get_idx(jobj, j); if (json_object_equal(iobj, uobj) == 1) { return JDAC_ERR_INVALID_UNIQUEITEMS; } } } } return JDAC_ERR_VALID; } int _jdac_check_maxmin_items(json_object *jobj, json_object *jschema) { int err = JDAC_ERR_VALID; json_object *jmax = json_object_object_get(jschema, "maxItems"); json_object *jmin = json_object_object_get(jschema, "minItems"); int arraylen = json_object_array_length(jobj); if (jmax) { if (json_object_is_type(jmax, json_type_int) || json_object_is_type(jmax, json_type_double)) { int maxitems = json_object_get_double(jmax); if (arraylen > maxitems) { err = JDAC_ERR_INVALID_ARRAYLEN; } } } if (jmin) { if (json_object_is_type(jmin, json_type_int) || json_object_is_type(jmin, json_type_double)) { int minitems = json_object_get_double(jmin); if (arraylen < minitems) { err = JDAC_ERR_INVALID_ARRAYLEN; } } } if (err) { LOG_MOD(error, ZLOG_MOD_JSCHEM, "ERROR: failed at maxItems or minItems check\n"); } return err; } int _jdac_validate_array(json_object *jobj, json_object *jschema) { int err; err = _jdac_check_prefixItems_and_items(jobj, jschema); if (err) { return err; } err = _jdac_check_uniqueItems(jobj, jschema); if (err) { return err; } err = _jdac_check_maxmin_items(jobj, jschema); if (err) { return err; } #ifdef JDAC_CONTAINS err = _jdac_check_contains_and_minmaxcontains(jobj, jschema); if (err) { return err; } #endif return JDAC_ERR_VALID; } int _jdac_validate_object(json_object *jobj, json_object *jschema) { int err; if (defs == NULL) { defs = json_object_object_get(jschema, "$defs"); } err = _jdac_check_required(jobj, jschema); if (err) { return err; } err = _jdac_check_properties(jobj, jschema); if (err) { return err; } #ifdef JDAC_PROPERTYNAMES err = _jdac_check_propertynames(jobj, jschema); if (err) { return err; } #endif #ifdef JDAC_PATTERNPROPERTIES err = _jdac_check_patternproperties(jobj, jschema); if (err) { return err; } #endif #ifdef JDAC_ADDITIONALPROPERTIES err = _jdac_check_additionalproperties(jobj, jschema); if (err) { return err; } #endif return JDAC_ERR_VALID; } int utf8_length(const char *str) { const char *pointer = str; int len = 0; while (pointer[0]) { if ((pointer[0] & 0xC0) != 0x80) { len++; } pointer++; } return len; } int _jdac_validate_string(json_object *jobj, json_object *jschema) { const char *str = json_object_get_string(jobj); //LOG_MOD(debug, ZLOG_MOD_JSCHEM, "strlen of %s %ld %d %d\n", str, strlen(str), json_object_get_string_len(jobj), utf8_length(str)); json_object *jminlen = json_object_object_get(jschema, "minLength"); if (jminlen) { int minlen = json_object_get_int64(jminlen); if (utf8_length(str) < minlen) { return JDAC_ERR_INVALID_STRLEN; } } json_object *jmaxlen = json_object_object_get(jschema, "maxLength"); if (jmaxlen) { int maxlen = json_object_get_int64(jmaxlen); if (utf8_length(str) > maxlen) { return JDAC_ERR_INVALID_STRLEN; } } int err = _jdac_check_enums(jobj, jschema); if (err) { return err; } #ifdef JDAC_PATTERN err = _jdac_check_pattern(jobj, jschema); if (err) { return err; } #endif return JDAC_ERR_VALID; } int _jdac_validate_integer(json_object *jobj, json_object *jschema) { double value = (double)json_object_get_int64(jobj); int err = _jdac_validate_number(jobj, jschema, value); return err; } int _jdac_validate_double(json_object *jobj, json_object *jschema) { double value = json_object_get_double(jobj); int err = _jdac_validate_number(jobj, jschema, value); return err; } int _jdac_validate_number(json_object *jobj, json_object *jschema, double value) { json_object *jmult = json_object_object_get(jschema, "multipleOf"); if (jmult) { double multipland = (double)json_object_get_double(jmult); if (multipland == 0.0) { return JDAC_ERR_SCHEMA_ERROR; } double divided = value / multipland; if (isinf(divided) != 0) { return JDAC_ERR_INVALID_NUMBER; } if (divided != round(divided)) { return JDAC_ERR_INVALID_NUMBER; } } json_object *jmin = json_object_object_get(jschema, "minimum"); if (jmin) { double min = (double)json_object_get_double(jmin); if (value < min) { return JDAC_ERR_INVALID_NUMBER; } } json_object *jexclmin = json_object_object_get(jschema, "exclusiveMinimum"); if (jexclmin) { double min = (double)json_object_get_double(jexclmin); if (value <= min) { return JDAC_ERR_INVALID_NUMBER; } } json_object *jmax = json_object_object_get(jschema, "maximum"); if (jmax) { double max = (double)json_object_get_double(jmax); if (value > max) { return JDAC_ERR_INVALID_NUMBER; } } json_object *jexclmax = json_object_object_get(jschema, "exclusiveMaximum"); if (jexclmax) { double max = (double)json_object_get_double(jexclmax); if (value >= max) { return JDAC_ERR_INVALID_NUMBER; } } return JDAC_ERR_VALID; } int _jdac_validate_boolean(json_object *jobj, json_object *jschema) { // LOG_MOD(debug, ZLOG_MOD_JSCHEM, "%s\n", __func__); return JDAC_ERR_VALID; } int _jdac_validate_instance(json_object *jobj, json_object *jschema) { int err; // LOG_MOD(debug, ZLOG_MOD_JSCHEM, "--validate instance--\n"); // LOG_MOD(debug, ZLOG_MOD_JSCHEM, "%s\n", json_object_get_string(jobj)); // LOG_MOD(debug, ZLOG_MOD_JSCHEM, "%s\n", json_object_get_string(jschema)); #ifdef JDAC_REF err = _jdac_check_ref(jobj, jschema, storagelist_head); if (err) { return err; } #endif // check if jschema is a bool, true or false if (json_object_is_type(jschema, json_type_boolean)) { json_bool value = json_object_get_boolean(jschema); if (value == 0) { return JDAC_ERR_INVALID; } if (value == 1) { return JDAC_ERR_VALID; } } err = _jdac_check_type(jobj, jschema); if (err) { return err; } err = _jdac_check_const(jobj, jschema); if (err) { return err; } err = _jdac_check_enums(jobj, jschema); if (err) { return err; } // if (!json_object_is_type(jobj, json_type_null)) // LOG_MOD(debug, ZLOG_MOD_JSCHEM, "%s\n", json_object_get_string(jobj)); // else // LOG_MOD(debug, ZLOG_MOD_JSCHEM, "jobj was null\n"); // if (!json_object_is_type(jschema, json_type_null)) // LOG_MOD(debug, ZLOG_MOD_JSCHEM, "%s\n", json_object_get_string(jschema)); // else // LOG_MOD(debug, ZLOG_MOD_JSCHEM, "jschema was null\n"); #ifdef JDAC_SUBSCHEMALOGIC err = _jdac_check_subschemalogic(jobj, jschema); if (err) { return err; } #endif json_type type = json_object_get_type(jobj); if (type == json_type_object) { return _jdac_validate_object(jobj, jschema); } else if (type == json_type_array) { return _jdac_validate_array(jobj, jschema); } else if (type == json_type_string) { return _jdac_validate_string(jobj, jschema); } else if (type == json_type_boolean) { return _jdac_validate_boolean(jobj, jschema); } else if (type == json_type_int) { return _jdac_validate_integer(jobj, jschema); } else if (type == json_type_double) { return _jdac_validate_double(jobj, jschema); } else if (type == json_type_null) { return JDAC_ERR_VALID; } else { LOG_MOD(warn, ZLOG_MOD_JSCHEM, "WARN: type %d not handled\n", type); } return JDAC_ERR_VALID; } int jdac_validate(json_object *jobj, json_object *jschema) { #ifdef JDAC_STORE _jdac_store_traverse_json(&storagelist_head, jschema, NULL); _jdac_store_print(storagelist_head); #endif int err = _jdac_validate_instance(jobj, jschema); #ifdef JDAC_STORE _jdac_store_free(&storagelist_head); #endif return err; } int jdac_validate_file(const char *jsonfile, const char *jsonschemafile) { int err = _jdac_load(jsonfile, jsonschemafile); if (err) { return err; } err = jdac_validate(json, schema); json_object_put(json); json_object_put(schema); json = NULL; schema = NULL; defs = NULL; return err; }