mirror of
https://github.com/mauiaaron/apple2.git
synced 2025-01-12 21:30:07 +00:00
Add JSON mutation functions and tests
This commit is contained in:
parent
01b25527fe
commit
a87092bbd8
362
src/json_parse.c
362
src/json_parse.c
@ -15,7 +15,23 @@
|
||||
#define JSON_LENGTH 16
|
||||
#define DEFAULT_NUMTOK 16
|
||||
#define MAX_INDENT 16
|
||||
#define STACK_BUFSIZ 256
|
||||
|
||||
#define _JSMN_NONSTRING (JSMN_OBJECT)
|
||||
|
||||
#define QUOTE "\""
|
||||
#define QUOTE_LEN (sizeof(QUOTE)-1)
|
||||
#define COLON ":"
|
||||
#define COLON_LEN (sizeof(COLON)-1)
|
||||
#define COMMA ","
|
||||
#define COMMA_LEN (sizeof(COMMA)-1)
|
||||
|
||||
#define DEBUG_JSON 1
|
||||
#if DEBUG_JSON
|
||||
# define JSON_LOG(...) LOG(__VA_ARGS__)
|
||||
#else
|
||||
# define JSON_LOG(...)
|
||||
#endif
|
||||
|
||||
static bool _json_write(const char *jsonString, size_t buflen, int fd) {
|
||||
ssize_t idx = 0;
|
||||
@ -32,6 +48,35 @@ static bool _json_write(const char *jsonString, size_t buflen, int fd) {
|
||||
return idx == buflen;
|
||||
}
|
||||
|
||||
static char *_json_copyAndQuote(const char *str, size_t *newLen) {
|
||||
|
||||
char *qCopy = NULL;
|
||||
do {
|
||||
if (!str) {
|
||||
break;
|
||||
}
|
||||
|
||||
size_t len = strlen(str);
|
||||
|
||||
qCopy = MALLOC(QUOTE_LEN + len + QUOTE_LEN + 1);
|
||||
if (!qCopy) {
|
||||
break;
|
||||
}
|
||||
*newLen = QUOTE_LEN + len + QUOTE_LEN;
|
||||
|
||||
char *p = qCopy;
|
||||
memcpy(p, QUOTE, QUOTE_LEN);
|
||||
p += QUOTE_LEN;
|
||||
memcpy(p, str, len);
|
||||
p += len;
|
||||
memcpy(p, QUOTE, QUOTE_LEN);
|
||||
p += QUOTE_LEN;
|
||||
*p = '\0';
|
||||
} while (0);
|
||||
|
||||
return qCopy;
|
||||
}
|
||||
|
||||
// recursive
|
||||
static bool _json_prettyPrint(JSON_s *parsedData, int start, int end, const unsigned int indent, int fd) {
|
||||
|
||||
@ -167,7 +212,7 @@ static int _json_createFromString(const char *jsonString, INOUT JSON_ref *jsonRe
|
||||
break;
|
||||
}
|
||||
|
||||
JSON_s *parsedData = MALLOC(sizeof(*parsedData));
|
||||
JSON_s *parsedData = CALLOC(sizeof(*parsedData), 1);
|
||||
if (!parsedData) {
|
||||
break;
|
||||
}
|
||||
@ -176,14 +221,13 @@ static int _json_createFromString(const char *jsonString, INOUT JSON_ref *jsonRe
|
||||
if (!parsedData) {
|
||||
break;
|
||||
}
|
||||
parsedData->jsonString = STRDUP(jsonString);
|
||||
parsedData->jsonTokens = NULL;
|
||||
|
||||
unsigned int numTokens = DEFAULT_NUMTOK;
|
||||
do {
|
||||
if (!parsedData->jsonTokens) {
|
||||
parsedData->jsonTokens = CALLOC(numTokens, sizeof(jsmntok_t));
|
||||
if (!parsedData->jsonTokens) {
|
||||
if (UNLIKELY(!parsedData->jsonTokens)) {
|
||||
ERRLOG("WHOA3 : %s", strerror(errno));
|
||||
break;
|
||||
}
|
||||
@ -191,7 +235,7 @@ static int _json_createFromString(const char *jsonString, INOUT JSON_ref *jsonRe
|
||||
//LOG("reallocating json tokens ...");
|
||||
numTokens <<= 1;
|
||||
jsmntok_t *newTokens = REALLOC(parsedData->jsonTokens, numTokens * sizeof(jsmntok_t));
|
||||
if (!newTokens) {
|
||||
if (UNLIKELY(!newTokens)) {
|
||||
ERRLOG("WHOA4 : %s", strerror(errno));
|
||||
break;
|
||||
}
|
||||
@ -199,7 +243,7 @@ static int _json_createFromString(const char *jsonString, INOUT JSON_ref *jsonRe
|
||||
parsedData->jsonTokens = newTokens;
|
||||
}
|
||||
jsmn_init(&parser);
|
||||
errCount = jsmn_parse(&parser, parsedData->jsonString, jsonLen, parsedData->jsonTokens, numTokens-(numTokens/2));
|
||||
errCount = jsmn_parse(&parser, jsonString, jsonLen, parsedData->jsonTokens, numTokens-(numTokens/2));
|
||||
} while (errCount == JSMN_ERROR_NOMEM);
|
||||
|
||||
if (errCount == JSMN_ERROR_NOMEM) {
|
||||
@ -225,6 +269,7 @@ static int _json_createFromString(const char *jsonString, INOUT JSON_ref *jsonRe
|
||||
break;
|
||||
}
|
||||
|
||||
parsedData->jsonString = STRDUP(jsonString);
|
||||
parsedData->numTokens = errCount;
|
||||
parsedData->jsonLen = jsonLen;
|
||||
|
||||
@ -254,7 +299,7 @@ int json_createFromFD(int fd, INOUT JSON_ref *jsonRef) {
|
||||
|
||||
jsonLen = JSON_LENGTH*2;
|
||||
jsonString = MALLOC(jsonLen);
|
||||
if (jsonString == NULL) {
|
||||
if (UNLIKELY(jsonString == NULL)) {
|
||||
ERRLOG("WHOA : %s", strerror(errno));
|
||||
break;
|
||||
}
|
||||
@ -272,7 +317,7 @@ int json_createFromFD(int fd, INOUT JSON_ref *jsonRef) {
|
||||
//LOG("reallocating json string ...");
|
||||
jsonLen <<= 1;
|
||||
char *newString = REALLOC(jsonString, jsonLen);
|
||||
if (!newString) {
|
||||
if (UNLIKELY(!newString)) {
|
||||
ERRLOG("WHOA2 : %s", strerror(errno));
|
||||
bytesRead = -1;
|
||||
break;
|
||||
@ -333,19 +378,15 @@ int json_createFromString(const char *jsonString, INOUT JSON_ref *jsonRef) {
|
||||
return _json_createFromString(jsonString, jsonRef, strlen(jsonString));
|
||||
}
|
||||
|
||||
static bool _json_mapGetStringValue(const JSON_s *map, const char *key, INOUT char **val, INOUT int *len) {
|
||||
static bool _json_mapGetStringValue(const JSON_s *map, const char *key, INOUT int *index) {
|
||||
bool foundMatch = false;
|
||||
|
||||
do {
|
||||
if (!val) {
|
||||
break;
|
||||
}
|
||||
if (!len) {
|
||||
if (!index) {
|
||||
break;
|
||||
}
|
||||
|
||||
*val = NULL;
|
||||
*len = -1;
|
||||
*index = -1;
|
||||
|
||||
int tokCount = map->numTokens;
|
||||
if (tokCount < 0) {
|
||||
@ -383,11 +424,7 @@ static bool _json_mapGetStringValue(const JSON_s *map, const char *key, INOUT ch
|
||||
if (size == keySize) {
|
||||
foundMatch = (strncmp(key, &map->jsonString[start], size) == 0);
|
||||
if (foundMatch) {
|
||||
start = valTok.start;
|
||||
end = valTok.end;
|
||||
assert(end >= start && "bug");
|
||||
*len = end - start;
|
||||
*val = &map->jsonString[start];
|
||||
*index = idx;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -399,13 +436,49 @@ static bool _json_mapGetStringValue(const JSON_s *map, const char *key, INOUT ch
|
||||
return foundMatch;
|
||||
}
|
||||
|
||||
int json_mapCopyJSON(const JSON_ref jsonRef, const char *key, INOUT JSON_ref *val) {
|
||||
JSON_s *map = (JSON_s *)jsonRef;
|
||||
|
||||
int idx = 0;
|
||||
jsmnerr_t errCount = JSMN_ERROR_NOMEM;
|
||||
do {
|
||||
bool foundMatch = _json_mapGetStringValue(map, key, &idx);
|
||||
if (!foundMatch) {
|
||||
break;
|
||||
}
|
||||
|
||||
jsmntok_t tok = map->jsonTokens[idx];
|
||||
assert(tok.end >= tok.start && "bug");
|
||||
int len = tok.end - tok.start;
|
||||
if (len<=0) {
|
||||
break;
|
||||
}
|
||||
|
||||
char *str = STRNDUP(&map->jsonString[tok.start], len);
|
||||
if (!str) {
|
||||
break;
|
||||
}
|
||||
|
||||
errCount = json_createFromString(str, val);
|
||||
FREE(str);
|
||||
|
||||
} while (0);
|
||||
|
||||
return errCount;
|
||||
}
|
||||
|
||||
bool json_mapCopyStringValue(const JSON_ref jsonRef, const char *key, INOUT char **val) {
|
||||
JSON_s *map = (JSON_s *)jsonRef;
|
||||
int len = 0;
|
||||
bool foundMatch = _json_mapGetStringValue(map, key, val, &len);
|
||||
|
||||
int idx = 0;
|
||||
bool foundMatch = _json_mapGetStringValue(map, key, &idx);
|
||||
if (foundMatch) {
|
||||
*val = len>0 ? STRNDUP(*val, len) : STRDUP("");
|
||||
jsmntok_t tok = map->jsonTokens[idx];
|
||||
assert(tok.end >= tok.start && "bug");
|
||||
int len = tok.end - tok.start;
|
||||
*val = len>0 ? STRNDUP(&map->jsonString[tok.start], len) : STRDUP("");
|
||||
}
|
||||
|
||||
return foundMatch;
|
||||
}
|
||||
|
||||
@ -418,10 +491,13 @@ bool json_mapParseLongValue(const JSON_ref jsonRef, const char *key, INOUT long
|
||||
break;
|
||||
}
|
||||
|
||||
int len = 0;
|
||||
char *str = NULL;
|
||||
foundMatch = _json_mapGetStringValue(map, key, &str, &len);
|
||||
int idx = 0;
|
||||
foundMatch = _json_mapGetStringValue(map, key, &idx);
|
||||
if (foundMatch) {
|
||||
jsmntok_t tok = map->jsonTokens[idx];
|
||||
assert(tok.end >= tok.start && "bug");
|
||||
int len = tok.end - tok.start;
|
||||
char *str = &map->jsonString[tok.start];
|
||||
char ch = str[len];
|
||||
str[len] = '\0';
|
||||
*val = strtol(str, NULL, base);
|
||||
@ -432,6 +508,32 @@ bool json_mapParseLongValue(const JSON_ref jsonRef, const char *key, INOUT long
|
||||
return foundMatch;
|
||||
}
|
||||
|
||||
bool json_mapParseBoolValue(const JSON_ref jsonRef, const char *key, INOUT bool *val) {
|
||||
JSON_s *map = (JSON_s *)jsonRef;
|
||||
bool foundMatch = false;
|
||||
|
||||
do {
|
||||
if (!val) {
|
||||
break;
|
||||
}
|
||||
|
||||
int idx = 0;
|
||||
foundMatch = _json_mapGetStringValue(map, key, &idx);
|
||||
if (foundMatch) {
|
||||
jsmntok_t tok = map->jsonTokens[idx];
|
||||
assert(tok.end >= tok.start && "bug");
|
||||
int len = tok.end - tok.start;
|
||||
char *str = &map->jsonString[tok.start];
|
||||
char ch = str[len];
|
||||
str[len] = '\0';
|
||||
*val = (strncasecmp("false", str, sizeof("false")) != 0);
|
||||
str[len] = ch;
|
||||
}
|
||||
} while (0);
|
||||
|
||||
return foundMatch;
|
||||
}
|
||||
|
||||
bool json_mapParseFloatValue(const JSON_ref jsonRef, const char *key, INOUT float *val) {
|
||||
JSON_s *map = (JSON_s *)jsonRef;
|
||||
bool foundMatch = false;
|
||||
@ -441,10 +543,13 @@ bool json_mapParseFloatValue(const JSON_ref jsonRef, const char *key, INOUT floa
|
||||
break;
|
||||
}
|
||||
|
||||
int len = 0;
|
||||
char *str = NULL;
|
||||
foundMatch = _json_mapGetStringValue(map, key, &str, &len);
|
||||
int idx = 0;
|
||||
foundMatch = _json_mapGetStringValue(map, key, &idx);
|
||||
if (foundMatch) {
|
||||
jsmntok_t tok = map->jsonTokens[idx];
|
||||
assert(tok.end >= tok.start && "bug");
|
||||
int len = tok.end - tok.start;
|
||||
char *str = &map->jsonString[tok.start];
|
||||
char ch = str[len];
|
||||
str[len] = '\0';
|
||||
*val = strtof(str, NULL);
|
||||
@ -455,6 +560,200 @@ bool json_mapParseFloatValue(const JSON_ref jsonRef, const char *key, INOUT floa
|
||||
return foundMatch;
|
||||
}
|
||||
|
||||
// 2016/03/02 : HACK NOTE TODO FIXME : we do nasty string splicing because I haven't settled on a C library to use for
|
||||
// collections. It would be nice to be able to use something like CoreFoundation (CFArray, CFDictionary, etc), but
|
||||
// don't believe the APSL license is GPL-compatible. (Should investigate whether Swift OpenSource drop includes
|
||||
// low-level C libs with a compatible FOSS license ;-)
|
||||
static bool _json_mapSetValue(const JSON_ref jsonRef, const char *key, const char *val, jsmntype_t valType) {
|
||||
JSON_s *map = (JSON_s *)jsonRef;
|
||||
|
||||
bool didSetValue = false;
|
||||
char *newVal = NULL;
|
||||
do {
|
||||
if (!map) {
|
||||
break;
|
||||
}
|
||||
if (!key) {
|
||||
break;
|
||||
}
|
||||
if (!val) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (map->jsonTokens[0].type != JSMN_OBJECT) {
|
||||
ERRLOG("Map JSON : object not a map!");
|
||||
break;
|
||||
}
|
||||
|
||||
size_t spliceBegin = 0;
|
||||
size_t spliceEnd = 0;
|
||||
size_t valLen = strlen(val);
|
||||
|
||||
int idx = 0;
|
||||
bool foundMatch = _json_mapGetStringValue(map, key, &idx);
|
||||
if (foundMatch) {
|
||||
jsmntok_t tok = map->jsonTokens[idx];
|
||||
assert(tok.end >= tok.start && "bug");
|
||||
spliceBegin = tok.start;
|
||||
spliceEnd = tok.end;
|
||||
|
||||
if (tok.type == JSMN_STRING) {
|
||||
if (valType == JSMN_STRING) {
|
||||
// string -> string
|
||||
assert(map->jsonString[spliceBegin-1] == '"');
|
||||
assert(map->jsonString[spliceEnd] == '"');
|
||||
} else {
|
||||
// string -> non-string (strip previous quotes)
|
||||
--spliceBegin;
|
||||
++spliceEnd;
|
||||
assert(map->jsonString[spliceBegin] == '"');
|
||||
assert(map->jsonString[spliceEnd-1] == '"');
|
||||
}
|
||||
} else {
|
||||
if (valType == JSMN_STRING) {
|
||||
// non-string -> string
|
||||
newVal = _json_copyAndQuote(val, &valLen);
|
||||
if (!newVal) {
|
||||
break;
|
||||
}
|
||||
val = newVal;
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
jsmntok_t tok = map->jsonTokens[0];
|
||||
|
||||
size_t keyLen = strlen(key);
|
||||
size_t quoLen = (valType == JSMN_STRING) ? QUOTE_LEN : 0;
|
||||
size_t comLen = tok.size ? COMMA_LEN : 0;
|
||||
|
||||
int keyValLen = QUOTE_LEN + keyLen + QUOTE_LEN + COLON_LEN + quoLen + valLen + quoLen + comLen;
|
||||
newVal = MALLOC(keyValLen+1);
|
||||
if (!newVal) {
|
||||
break;
|
||||
}
|
||||
|
||||
char *p = newVal;
|
||||
|
||||
memcpy(p, QUOTE, QUOTE_LEN);
|
||||
p += QUOTE_LEN;
|
||||
memcpy(p, key, keyLen);
|
||||
p += keyLen;
|
||||
memcpy(p, QUOTE, QUOTE_LEN);
|
||||
p += QUOTE_LEN;
|
||||
|
||||
memcpy(p, COLON, COLON_LEN);
|
||||
p += COLON_LEN;
|
||||
|
||||
if (quoLen) {
|
||||
memcpy(p, QUOTE, quoLen);
|
||||
p += quoLen;
|
||||
}
|
||||
memcpy(p, val, valLen);
|
||||
p += valLen;
|
||||
if (quoLen) {
|
||||
memcpy(p, QUOTE, quoLen);
|
||||
p += quoLen;
|
||||
}
|
||||
|
||||
if (comLen) {
|
||||
memcpy(p, COMMA, comLen);
|
||||
p += comLen;
|
||||
}
|
||||
newVal[keyValLen] = '\0';
|
||||
|
||||
spliceBegin = tok.start+1; // insert at beginning
|
||||
spliceEnd = spliceBegin;
|
||||
|
||||
val = newVal;
|
||||
valLen = keyValLen;
|
||||
}
|
||||
|
||||
assert(spliceBegin <= spliceEnd);
|
||||
assert(spliceBegin > 0); // must always begin with '{'
|
||||
assert(spliceEnd < map->jsonLen); // must always close with '}'
|
||||
|
||||
int prefixLen = spliceBegin;
|
||||
int suffixLen = (map->jsonLen - spliceEnd);
|
||||
|
||||
int newLen = prefixLen+valLen+suffixLen;
|
||||
|
||||
char *jsonString = MALLOC(newLen + 1);
|
||||
if (!jsonString) {
|
||||
break;
|
||||
}
|
||||
memcpy(jsonString, &map->jsonString[0], prefixLen);
|
||||
memcpy(jsonString+prefixLen, val, valLen);
|
||||
memcpy(jsonString+prefixLen+valLen, &map->jsonString[spliceEnd], suffixLen);
|
||||
jsonString[newLen] = '\0';
|
||||
|
||||
JSON_ref newRef = NULL;
|
||||
int errCount = json_createFromString(jsonString, &newRef);
|
||||
if (errCount < 0) {
|
||||
ERRLOG("Cannot set new JSON value err : %d", errCount);
|
||||
} else {
|
||||
JSON_s *newMap = (JSON_s *)newRef;
|
||||
FREE(map->jsonString);
|
||||
FREE(map->jsonTokens);
|
||||
*map = *newMap;
|
||||
newMap->jsonString = NULL;
|
||||
newMap->jsonTokens = NULL;
|
||||
json_destroy(&newRef);
|
||||
didSetValue = true;
|
||||
}
|
||||
|
||||
FREE(jsonString);
|
||||
} while (0);
|
||||
|
||||
if (newVal) {
|
||||
FREE(newVal);
|
||||
}
|
||||
|
||||
return didSetValue;
|
||||
}
|
||||
|
||||
bool json_mapSetStringValue(const JSON_ref jsonRef, const char *key, const char *val) {
|
||||
return _json_mapSetValue(jsonRef, key, val, JSMN_STRING);
|
||||
}
|
||||
|
||||
bool json_mapSetRawStringValue(const JSON_ref jsonRef, const char *key, const char *val) {
|
||||
return _json_mapSetValue(jsonRef, key, val, _JSMN_NONSTRING);
|
||||
}
|
||||
|
||||
bool json_mapSetJSONValue(const JSON_ref jsonRef, const char *key, const JSON_ref jsonSubRef) {
|
||||
bool didSetValue = false;
|
||||
do {
|
||||
if (!jsonRef) {
|
||||
break;
|
||||
}
|
||||
|
||||
didSetValue = _json_mapSetValue(jsonRef, key, ((JSON_s *)jsonSubRef)->jsonString, _JSMN_NONSTRING);
|
||||
} while (0);
|
||||
|
||||
return didSetValue;
|
||||
}
|
||||
|
||||
bool json_mapSetLongValue(const JSON_ref jsonRef, const char *key, long val) {
|
||||
char buf[STACK_BUFSIZ];
|
||||
buf[0] = '\0';
|
||||
snprintf(buf, STACK_BUFSIZ, "%ld", val);
|
||||
return _json_mapSetValue(jsonRef, key, buf, JSMN_PRIMITIVE);
|
||||
}
|
||||
|
||||
bool json_mapSetBoolValue(const JSON_ref jsonRef, const char *key, bool val) {
|
||||
char buf[STACK_BUFSIZ];
|
||||
buf[0] = '\0';
|
||||
snprintf(buf, STACK_BUFSIZ, "%s", val ? "true" : "false");
|
||||
return _json_mapSetValue(jsonRef, key, buf, JSMN_PRIMITIVE);
|
||||
}
|
||||
|
||||
bool json_mapSetFloatValue(const JSON_ref jsonRef, const char *key, float val) {
|
||||
char buf[STACK_BUFSIZ];
|
||||
buf[0] = '\0';
|
||||
snprintf(buf, STACK_BUFSIZ, "%f", val);
|
||||
return _json_mapSetValue(jsonRef, key, buf, JSMN_PRIMITIVE);
|
||||
}
|
||||
|
||||
bool json_serialize(JSON_ref jsonRef, int fd, bool pretty) {
|
||||
JSON_s *parsedData = (JSON_s *)jsonRef;
|
||||
if (pretty) {
|
||||
@ -470,8 +769,15 @@ void json_destroy(JSON_ref *jsonRef) {
|
||||
}
|
||||
|
||||
JSON_s *parsedData = (JSON_s *)*jsonRef;
|
||||
if (!parsedData) {
|
||||
return;
|
||||
}
|
||||
|
||||
FREE(parsedData->jsonString);
|
||||
FREE(parsedData->jsonTokens);
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wincompatible-pointer-types-discards-qualifiers"
|
||||
FREE(*jsonRef);
|
||||
#pragma clang diagnostic pop
|
||||
}
|
||||
|
||||
|
@ -18,6 +18,9 @@
|
||||
// opaque type
|
||||
typedef const struct JSON_s *JSON_ref;
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// constructors
|
||||
|
||||
// parses string into tokens. returns positive token count or negative jsmnerr_t error code.
|
||||
int json_createFromString(const char *jsonString, INOUT JSON_ref *jsonRef);
|
||||
|
||||
@ -30,15 +33,39 @@ int json_createFromFD(int fd, INOUT JSON_ref *jsonRef);
|
||||
// ----------------------------------------------------------------------------
|
||||
// map functions
|
||||
|
||||
// get JSON_ref value for key in map JSON, returns true upon success and allocated JSON_ref
|
||||
int json_mapCopyJSON(const JSON_ref map, const char *key, INOUT JSON_ref *val);
|
||||
|
||||
// get string value for key in map JSON, returns true upon success and strdup()'d value in *val
|
||||
bool json_mapCopyStringValue(const JSON_ref map, const char *key, INOUT char **val);
|
||||
|
||||
// get long value for key in map JSON, returns true upon success
|
||||
bool json_mapParseLongValue(const JSON_ref map, const char *key, INOUT long *val, const long base);
|
||||
|
||||
// get bool value for key in map JSON, returns true upon success
|
||||
bool json_mapParseBoolValue(const JSON_ref map, const char *key, INOUT bool *val);
|
||||
|
||||
// get float value for key in map JSON, returns true upon success
|
||||
bool json_mapParseFloatValue(const JSON_ref map, const char *key, INOUT float *val);
|
||||
|
||||
// set string value for key in map JSON, returns true upon success
|
||||
bool json_mapSetStringValue(const JSON_ref map, const char *key, const char *val);
|
||||
|
||||
// set raw string (does not add quotes) value for key in map JSON, returns true upon success
|
||||
bool json_mapSetRawStringValue(const JSON_ref map, const char *key, const char *val);
|
||||
|
||||
// set JSON_ref value for key in map JSON, returns true upon success
|
||||
bool json_mapSetJSONValue(const JSON_ref map, const char *key, const JSON_ref val);
|
||||
|
||||
// set long value for key in map JSON, returns true upon success
|
||||
bool json_mapSetLongValue(const JSON_ref map, const char *key, long val);
|
||||
|
||||
// set bool value for key in map JSON, returns true upon success
|
||||
bool json_mapSetBoolValue(const JSON_ref map, const char *key, bool val);
|
||||
|
||||
// set float value for key in map JSON, returns true upon success
|
||||
bool json_mapSetFloatValue(const JSON_ref map, const char *key, float val);
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// array functions
|
||||
|
||||
@ -48,10 +75,12 @@ bool json_mapParseFloatValue(const JSON_ref map, const char *key, INOUT float *v
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// serialization
|
||||
|
||||
// serialize to file descriptor
|
||||
bool json_serialize(JSON_ref json, int fd, bool pretty);
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// destroys internal allocated memory (if any)
|
||||
// destructor
|
||||
void json_destroy(JSON_ref *jsonRef);
|
||||
|
||||
#endif
|
||||
|
@ -24,33 +24,34 @@ static void testprefs_teardown(void *unused) {
|
||||
|
||||
static const char *get_default_preferences(void) {
|
||||
return
|
||||
"{\n"
|
||||
" \"cpu\" : {\n"
|
||||
" \"speed\" : 1.0,\n"
|
||||
" \"altspeed\" : 4.0\n"
|
||||
" },\n"
|
||||
" \"disk\" : {\n"
|
||||
" \"diskPath\" : \"/usr/local/games/apple2/disks\"\n"
|
||||
" },\n"
|
||||
" \"video\" : {\n"
|
||||
" \"color\" : \"interpolated\"\n"
|
||||
" },\n"
|
||||
" \"speaker\" : {\n"
|
||||
" \"volume\" : 5\n"
|
||||
" },\n"
|
||||
" \"joystick\" : {\n"
|
||||
" \"variant\" : \"keypad\",\n"
|
||||
" \"pcJoystickParms\" : \"128 128 255 1 255 1\",\n"
|
||||
" \"kpJoystickParms\" : \"8 1\"\n"
|
||||
" },\n"
|
||||
" \"keyboard\" : {\n"
|
||||
" \"caps\" : true\n"
|
||||
" }\n"
|
||||
"}\n"
|
||||
"{"
|
||||
" \"audio\" : {"
|
||||
" \"speakerVolume\" : 4,"
|
||||
" \"mbVolume\" : 2"
|
||||
" },"
|
||||
" \"interface\" : {"
|
||||
" \"diskPath\" : \"/usr/local/games/apple2/disks\""
|
||||
" },"
|
||||
" \"joystick\" : {"
|
||||
" \"variant\" : \"keypad\","
|
||||
" \"pcJoystickParms\" : \"128 128 255 1 255 1\","
|
||||
" \"kpJoystickParms\" : \"8 1\""
|
||||
" },"
|
||||
" \"keyboard\" : {"
|
||||
" \"caps\" : true"
|
||||
" }",
|
||||
" \"video\" : {"
|
||||
" \"color\" : \"interpolated\""
|
||||
" },"
|
||||
" \"vm\" : {"
|
||||
" \"speed\" : 1.0,"
|
||||
" \"altSpeed\" : 4.0"
|
||||
" }"
|
||||
"}"
|
||||
;
|
||||
}
|
||||
|
||||
static const char *get_sample_json_1(void) {
|
||||
static const char *get_sample_json_0(void) {
|
||||
return
|
||||
" { "
|
||||
" \"key0\" : \"a value zero\", "
|
||||
@ -170,7 +171,7 @@ TEST test_json_map_0(JSON_ref parsedData) {
|
||||
|
||||
TEST test_json_map_1() {
|
||||
|
||||
const char *testMapStr0 = get_sample_json_1();
|
||||
const char *testMapStr0 = get_sample_json_0();
|
||||
|
||||
JSON_ref parsedData = NULL;
|
||||
int tokCount = json_createFromString(testMapStr0, &parsedData);
|
||||
@ -184,7 +185,7 @@ TEST test_json_map_1() {
|
||||
|
||||
TEST test_json_serialization() {
|
||||
|
||||
const char *testMapStr0 = get_sample_json_1();
|
||||
const char *testMapStr0 = get_sample_json_0();
|
||||
|
||||
JSON_ref parsedData = NULL;
|
||||
int tokCount = json_createFromString(testMapStr0, &parsedData);
|
||||
@ -198,7 +199,8 @@ TEST test_json_serialization() {
|
||||
json_serialize(parsedData, fd, /*pretty:*/false);
|
||||
json_destroy(&parsedData);
|
||||
lseek(fd, 0, SEEK_SET);
|
||||
json_createFromFD(fd, &parsedData);
|
||||
tokCount = json_createFromFD(fd, &parsedData);
|
||||
ASSERT(tokCount > 0);
|
||||
|
||||
test_json_map_0(parsedData);
|
||||
|
||||
@ -210,7 +212,7 @@ TEST test_json_serialization() {
|
||||
|
||||
TEST test_json_serialization_pretty() {
|
||||
|
||||
const char *testMapStr0 = get_sample_json_1();
|
||||
const char *testMapStr0 = get_sample_json_0();
|
||||
|
||||
JSON_ref parsedData = NULL;
|
||||
int tokCount = json_createFromString(testMapStr0, &parsedData);
|
||||
@ -224,7 +226,8 @@ TEST test_json_serialization_pretty() {
|
||||
json_serialize(parsedData, fd, /*pretty:*/true);
|
||||
json_destroy(&parsedData);
|
||||
lseek(fd, 0, SEEK_SET);
|
||||
json_createFromFD(fd, &parsedData);
|
||||
tokCount = json_createFromFD(fd, &parsedData);
|
||||
ASSERT(tokCount > 0);
|
||||
|
||||
do {
|
||||
long lVal;
|
||||
@ -334,11 +337,259 @@ TEST test_json_serialization_pretty() {
|
||||
PASS();
|
||||
}
|
||||
|
||||
TEST test_json_invalid_bareKey() {
|
||||
|
||||
JSON_ref parsedData = NULL;
|
||||
jsmnerr_t errCount = (jsmnerr_t)json_createFromString("{ aBareKey : \"aNonBareVal\" }", &parsedData);
|
||||
ASSERT(errCount == JSMN_ERROR_INVAL);
|
||||
|
||||
json_destroy(&parsedData);
|
||||
|
||||
PASS();
|
||||
}
|
||||
|
||||
TEST test_json_invalid_bareVal() {
|
||||
|
||||
JSON_ref parsedData = NULL;
|
||||
jsmnerr_t errCount = (jsmnerr_t)json_createFromString("{ \"aNonBareKey\" : aBareVal }", &parsedData);
|
||||
ASSERT(errCount == JSMN_ERROR_INVAL);
|
||||
|
||||
json_destroy(&parsedData);
|
||||
|
||||
PASS();
|
||||
}
|
||||
|
||||
TEST test_json_map_invalid_danglingComma() {
|
||||
|
||||
JSON_ref parsedData = NULL;
|
||||
jsmnerr_t errCount = (jsmnerr_t)json_createFromString("{ \"aNonBareKey\" : \"aNonBareVal\", }", &parsedData);
|
||||
ASSERT(errCount == JSMN_ERROR_INVAL);
|
||||
|
||||
json_destroy(&parsedData);
|
||||
|
||||
PASS();
|
||||
}
|
||||
|
||||
TEST test_json_map_invalid_danglingKey() {
|
||||
|
||||
JSON_ref parsedData = NULL;
|
||||
jsmnerr_t errCount = (jsmnerr_t)json_createFromString("{ \"aNonBareKey\" : \"aNonBareVal\", \"aNoneBareButDanglingKey\" }", &parsedData);
|
||||
ASSERT(errCount == JSMN_ERROR_INVAL);
|
||||
|
||||
json_destroy(&parsedData);
|
||||
|
||||
PASS();
|
||||
}
|
||||
|
||||
TEST test_json_map_mutation_1() {
|
||||
|
||||
JSON_ref parsedData = NULL;
|
||||
int tokCount = json_createFromString("{}", &parsedData);
|
||||
ASSERT(tokCount == 1);
|
||||
|
||||
bool ok = false;
|
||||
char *val = NULL;
|
||||
long lVal = 0;
|
||||
float fVal = 0.f;
|
||||
|
||||
ok = json_mapSetStringValue(parsedData, "key0", "val0");
|
||||
ASSERT(ok);
|
||||
ok = json_mapCopyStringValue(parsedData, "key0", &val);
|
||||
ASSERT(ok);
|
||||
ASSERT(strcmp(val, "val0") == 0);
|
||||
FREE(val);
|
||||
|
||||
ok = json_mapSetStringValue(parsedData, "key1", "val1");
|
||||
ASSERT(ok);
|
||||
ok = json_mapCopyStringValue(parsedData, "key1", &val);
|
||||
ASSERT(ok);
|
||||
ASSERT(strcmp(val, "val1") == 0);
|
||||
FREE(val);
|
||||
|
||||
ok = json_mapSetLongValue(parsedData, "longKey0", 42);
|
||||
ASSERT(ok);
|
||||
ok = json_mapCopyStringValue(parsedData, "longKey0", &val);
|
||||
ASSERT(ok);
|
||||
ASSERT(strcmp(val, "42") == 0);
|
||||
FREE(val);
|
||||
ok = json_mapParseLongValue(parsedData, "longKey0", &lVal, 10);
|
||||
ASSERT(ok);
|
||||
ASSERT(lVal == 42);
|
||||
|
||||
ok = json_mapSetStringValue(parsedData, "key0", "");
|
||||
ASSERT(ok);
|
||||
ok = json_mapCopyStringValue(parsedData, "key0", &val);
|
||||
ASSERT(ok);
|
||||
ASSERT(strlen(val) == 0);
|
||||
FREE(val);
|
||||
|
||||
ok = json_mapSetStringValue(parsedData, "key1", "hello world");
|
||||
ASSERT(ok);
|
||||
ok = json_mapCopyStringValue(parsedData, "key1", &val);
|
||||
ASSERT(ok);
|
||||
ASSERT(strcmp(val, "hello world") == 0);
|
||||
FREE(val);
|
||||
|
||||
ok = json_mapSetFloatValue(parsedData, "floatKey0", 0.25);
|
||||
ASSERT(ok);
|
||||
ok = json_mapParseFloatValue(parsedData, "floatKey0", &fVal);
|
||||
ASSERT(ok);
|
||||
ASSERT(fVal == 0.25);
|
||||
|
||||
ok = json_mapSetFloatValue(parsedData, "floatKey0", -0.5);
|
||||
ASSERT(ok);
|
||||
ok = json_mapParseFloatValue(parsedData, "floatKey0", &fVal);
|
||||
ASSERT(ok);
|
||||
ASSERT(fVal == -0.5);
|
||||
|
||||
// test sub maps ...
|
||||
|
||||
do {
|
||||
JSON_ref parsedSubData = NULL;
|
||||
|
||||
tokCount = json_createFromString("{}", &parsedSubData);
|
||||
ASSERT(tokCount == 1);
|
||||
|
||||
ok = json_mapSetStringValue(parsedSubData, "foo", "bar");
|
||||
ASSERT(ok);
|
||||
|
||||
do {
|
||||
JSON_ref parsedSubSubData = NULL;
|
||||
|
||||
tokCount = json_createFromString("{}", &parsedSubSubData);
|
||||
ASSERT(tokCount == 1);
|
||||
|
||||
ok = json_mapSetStringValue(parsedSubSubData, "subFoo", "subBar");
|
||||
ASSERT(ok);
|
||||
|
||||
ok = json_mapSetJSONValue(parsedSubData, "subKey0", parsedSubSubData);
|
||||
ASSERT(ok);
|
||||
|
||||
json_destroy(&parsedSubSubData);
|
||||
} while (0);
|
||||
|
||||
ok = json_mapSetJSONValue(parsedData, "key2", parsedSubData);
|
||||
ASSERT(ok);
|
||||
|
||||
json_destroy(&parsedSubData);
|
||||
} while (0);
|
||||
|
||||
ok = json_mapCopyStringValue(parsedData, "key2", &val);
|
||||
ASSERT(ok);
|
||||
ASSERT(strcmp(val, "{\"subKey0\":{\"subFoo\":\"subBar\"},\"foo\":\"bar\"}") == 0); // HACK : testing whitespace implementation details
|
||||
FREE(val);
|
||||
|
||||
do {
|
||||
JSON_ref parsedSubData = NULL;
|
||||
|
||||
ok = json_mapCopyJSON(parsedData, "key2", &parsedSubData);
|
||||
ASSERT(ok);
|
||||
ASSERT(parsedSubData);
|
||||
|
||||
ok = json_mapCopyStringValue(parsedSubData, "foo", &val);
|
||||
ASSERT(ok);
|
||||
ASSERT(strcmp(val, "bar") == 0);
|
||||
FREE(val);
|
||||
|
||||
ok = json_mapCopyStringValue(parsedSubData, "subKey0", &val);
|
||||
ASSERT(ok);
|
||||
ASSERT(strcmp(val, "{\"subFoo\":\"subBar\"}") == 0); // HACK : testing whitespace implementation details
|
||||
FREE(val);
|
||||
do {
|
||||
JSON_ref parsedSubSubData = NULL;
|
||||
ok = json_mapCopyJSON(parsedSubData, "subKey0", &parsedSubSubData);
|
||||
ASSERT(ok);
|
||||
ASSERT(parsedSubSubData);
|
||||
|
||||
ok = json_mapCopyStringValue(parsedSubSubData, "subFoo", &val);
|
||||
ASSERT(ok);
|
||||
ASSERT(strcmp(val, "subBar") == 0);
|
||||
FREE(val);
|
||||
|
||||
json_destroy(&parsedSubSubData);
|
||||
} while (0);
|
||||
|
||||
json_destroy(&parsedSubData);
|
||||
} while (0);
|
||||
|
||||
// setting invalid JSON does not clobber existing data ...
|
||||
ok = json_mapSetRawStringValue(parsedData, "key2", "{ aBareKey : \"this is invalid\" }");
|
||||
ASSERT(!ok);
|
||||
|
||||
ok = json_mapCopyStringValue(parsedData, "key2", &val);
|
||||
ASSERT(ok);
|
||||
ASSERT(strcmp(val, "{\"subKey0\":{\"subFoo\":\"subBar\"},\"foo\":\"bar\"}") == 0); // HACK : testing whitespace implementation details
|
||||
FREE(val);
|
||||
|
||||
ok = json_mapSetRawStringValue(parsedData, "key2", "{ \"aNotBareKey\" : \"this should now be valid\" }");
|
||||
ASSERT(ok);
|
||||
|
||||
ok = json_mapCopyStringValue(parsedData, "key2", &val);
|
||||
ASSERT(ok);
|
||||
ASSERT(strcmp(val, "{ \"aNotBareKey\" : \"this should now be valid\" }") == 0); // HACK : testing whitespace implementation details
|
||||
FREE(val);
|
||||
|
||||
json_destroy(&parsedData);
|
||||
PASS();
|
||||
}
|
||||
|
||||
#if 0
|
||||
TEST test_prefs_loadString_1() {
|
||||
const char *prefsJSON = get_default_preferences();
|
||||
bool loaded = prefs_loadString(prefsJSON);
|
||||
ASSERT(loaded);
|
||||
bool ok = prefs_loadString(prefsJSON);
|
||||
ASSERT(ok);
|
||||
|
||||
char *val = NULL;
|
||||
bool bVal = false;
|
||||
long lVal = 0;
|
||||
bool fVal = 0.f;
|
||||
|
||||
ok = prefs_parseLongValue(PREF_DOMAIN_AUDIO, "speakerVolume", &lVal);
|
||||
ASSERT(ok);
|
||||
ASSERT(lVal == 4);
|
||||
|
||||
ok = prefs_parseLongValue(PREF_DOMAIN_AUDIO, "mbVolume", &lVal);
|
||||
ASSERT(ok);
|
||||
ASSERT(lVal == 2);
|
||||
|
||||
ok = prefs_copyStringValue(PREF_DOMAIN_INTERFACE, "diskPath", &val);
|
||||
ASSERT(ok);
|
||||
ASSERT(strcmp(val, "/usr/local/games/apple2/disks") == 0);
|
||||
FREE(val);
|
||||
|
||||
ok = prefs_copyStringValue(PREF_DOMAIN_JOYSTICK, "variant", &val);
|
||||
ASSERT(ok);
|
||||
ASSERT(strcmp(val, "keypad") == 0);
|
||||
FREE(val);
|
||||
|
||||
ok = prefs_copyStringValue(PREF_DOMAIN_JOYSTICK, "pcJoystickParms", &val);
|
||||
ASSERT(ok);
|
||||
ASSERT(strcmp(val, "128 128 255 1 255 1") == 0);
|
||||
FREE(val);
|
||||
|
||||
ok = prefs_copyStringValue(PREF_DOMAIN_JOYSTICK, "kpJoystickParms", &val);
|
||||
ASSERT(ok);
|
||||
ASSERT(strcmp(val, "8 1") == 0);
|
||||
FREE(val);
|
||||
|
||||
ok = prefs_parseBoolValue(PREF_DOMAIN_KEYBOARD, "caps", &bVal);
|
||||
ASSERT(ok);
|
||||
ASSERT(bVal == true);
|
||||
|
||||
ok = prefs_copyStringValue(PREF_DOMAIN_VIDEO, "color", &val);
|
||||
ASSERT(ok);
|
||||
ASSERT(strcmp(val, "interpolated") == 0);
|
||||
FREE(val);
|
||||
|
||||
ok = prefs_parseFloatValue(PREF_DOMAIN_VM, "speed", &fVal);
|
||||
ASSERT(ok);
|
||||
ASSERT(fVal == 1.f);
|
||||
|
||||
ok = prefs_parseFloatValue(PREF_DOMAIN_VM, "altSpeed", &fVal);
|
||||
ASSERT(ok);
|
||||
ASSERT(fVal == 4.f);
|
||||
|
||||
PASS();
|
||||
}
|
||||
#endif
|
||||
|
||||
@ -348,7 +599,7 @@ TEST test_prefs_loadString_1() {
|
||||
GREATEST_SUITE(test_suite_prefs) {
|
||||
GREATEST_SET_SETUP_CB(testprefs_setup, NULL);
|
||||
GREATEST_SET_TEARDOWN_CB(testprefs_teardown, NULL);
|
||||
GREATEST_SET_BREAKPOINT_CB(test_breakpoint, NULL);
|
||||
//GREATEST_SET_BREAKPOINT_CB(test_breakpoint, NULL);
|
||||
|
||||
// TESTS --------------------------
|
||||
test_thread_running = true;
|
||||
@ -358,6 +609,15 @@ GREATEST_SUITE(test_suite_prefs) {
|
||||
RUN_TESTp(test_json_serialization);
|
||||
RUN_TESTp(test_json_serialization_pretty);
|
||||
|
||||
RUN_TESTp(test_json_invalid_bareKey);
|
||||
RUN_TESTp(test_json_invalid_bareVal);
|
||||
RUN_TESTp(test_json_map_invalid_danglingComma);
|
||||
RUN_TESTp(test_json_map_invalid_danglingKey);
|
||||
|
||||
RUN_TESTp(test_json_map_mutation_1);
|
||||
|
||||
//RUN_TESTp(test_prefs_loadString_1);
|
||||
|
||||
// --------------------------------
|
||||
pthread_mutex_unlock(&interface_mutex);
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user