1
0
mirror of https://github.com/pgbackrest/pgbackrest.git synced 2025-03-17 20:58:34 +02:00

Add basic C JSON parser.

This commit is contained in:
David Steele 2018-08-09 08:06:23 -04:00
parent 31167d8f98
commit 7993f1a966
11 changed files with 199 additions and 0 deletions

View File

@ -86,6 +86,9 @@ path-close: 97
# Unable to get info for a file
file-info: 98
# Invalid JSON format. Eventually this should be a child of format error and share the same code
json-format: 99
# This error should not be thrown directly -- it serves as a parent for the C errors
runtime: 122

View File

@ -53,6 +53,10 @@
<p>Add <code>uint64</code> variant type and supporting conversion functions.</p>
</release-item>
<release-item>
<p>Add basic C JSON parser.</p>
</release-item>
<release-item>
<p>Allow <code>Buffer</code> object <quote>used size</quote> to be different than <quote>allocated size</quote>. Add functions to manage used size and remaining size and update automatically when possible.</p>
</release-item>

View File

@ -165,6 +165,8 @@ use constant ERROR_PATH_CLOSE => 97;
push @EXPORT, qw(ERROR_PATH_CLOSE);
use constant ERROR_FILE_INFO => 98;
push @EXPORT, qw(ERROR_FILE_INFO);
use constant ERROR_JSON_FORMAT => 99;
push @EXPORT, qw(ERROR_JSON_FORMAT);
use constant ERROR_RUNTIME => 122;
push @EXPORT, qw(ERROR_RUNTIME);
use constant ERROR_INVALID => 123;

View File

@ -214,6 +214,9 @@ common/type/buffer.o: common/type/buffer.c common/assert.h common/debug.h common
common/type/convert.o: common/type/convert.c common/assert.h common/debug.h common/error.auto.h common/error.h common/logLevel.h common/stackTrace.h common/type/convert.h
$(CC) $(CFLAGS) -c common/type/convert.c -o common/type/convert.o
common/type/json.o: common/type/json.c common/debug.h common/log.h common/logLevel.h common/stackTrace.h common/type/convert.h common/type/json.h common/type/keyValue.h common/type/variantList.h
$(CC) $(CFLAGS) -c common/type/json.c -o common/type/json.o
common/type/keyValue.o: common/type/keyValue.c common/debug.h common/error.auto.h common/error.h common/logLevel.h common/memContext.h common/stackTrace.h common/type/buffer.h common/type/convert.h common/type/keyValue.h common/type/list.h common/type/string.h common/type/stringList.h common/type/variant.h common/type/variantList.h
$(CC) $(CFLAGS) -c common/type/keyValue.c -o common/type/keyValue.o

View File

@ -80,6 +80,7 @@ ERROR_DEFINE( 95, CipherError, RuntimeError);
ERROR_DEFINE( 96, ParamInvalidError, RuntimeError);
ERROR_DEFINE( 97, PathCloseError, RuntimeError);
ERROR_DEFINE( 98, FileInfoError, RuntimeError);
ERROR_DEFINE( 99, JsonFormatError, RuntimeError);
ERROR_DEFINE(122, RuntimeError, RuntimeError);
ERROR_DEFINE(123, InvalidError, RuntimeError);
ERROR_DEFINE(124, UnhandledError, RuntimeError);
@ -163,6 +164,7 @@ static const ErrorType *errorTypeList[] =
&ParamInvalidError,
&PathCloseError,
&FileInfoError,
&JsonFormatError,
&RuntimeError,
&InvalidError,
&UnhandledError,

View File

@ -82,6 +82,7 @@ ERROR_DECLARE(CipherError);
ERROR_DECLARE(ParamInvalidError);
ERROR_DECLARE(PathCloseError);
ERROR_DECLARE(FileInfoError);
ERROR_DECLARE(JsonFormatError);
ERROR_DECLARE(RuntimeError);
ERROR_DECLARE(InvalidError);
ERROR_DECLARE(UnhandledError);

111
src/common/type/json.c Normal file
View File

@ -0,0 +1,111 @@
/***********************************************************************************************************************************
Convert JSON to/from KeyValue
***********************************************************************************************************************************/
#include <ctype.h>
#include "common/debug.h"
#include "common/log.h"
#include "common/type/json.h"
/***********************************************************************************************************************************
Convert JSON to KeyValue object
Currently this function is only intended to convert the limited types that are included in info files. More types will be added as
needed. Since this function is only intended to read internally-generated JSON it is assumed to be well-formed with no extraneous
whitespace.
***********************************************************************************************************************************/
KeyValue *jsonToKv(const String *json)
{
FUNCTION_DEBUG_BEGIN(logLevelTrace);
FUNCTION_DEBUG_PARAM(STRING, json);
FUNCTION_TEST_ASSERT(json != NULL);
FUNCTION_DEBUG_END();
KeyValue *result = kvNew();
MEM_CONTEXT_TEMP_BEGIN()
{
// We'll examine the string byte by byte
const char *jsonC = strPtr(json);
unsigned int jsonPos = 0;
// Consume the initial delimiter
if (jsonC[jsonPos] == '[')
THROW(JsonFormatError, "arrays not supported");
else if (jsonC[jsonPos] != '{')
THROW_FMT(JsonFormatError, "expected '{' but found '%c'", jsonC[jsonPos]);
// Start parsing key/value pairs
do
{
Variant *value = NULL;
jsonPos++;
// Parse the key which should always be quoted
if (jsonC[jsonPos] != '"')
THROW_FMT(JsonFormatError, "expected '\"' but found '%c'", jsonC[jsonPos]);
unsigned int keyBeginPos = ++jsonPos;
while (jsonC[jsonPos] != '"' && jsonPos < strSize(json) - 1)
jsonPos++;
if (jsonC[jsonPos] != '"')
THROW_FMT(JsonFormatError, "expected '\"' but found '%c'", jsonC[jsonPos]);
String *key = strNewN(jsonC + keyBeginPos, jsonPos - keyBeginPos);
if (strSize(key) == 0)
THROW(JsonFormatError, "zero-length key not allowed");
if (jsonC[++jsonPos] != ':')
THROW_FMT(JsonFormatError, "expected ':' but found '%c'", jsonC[jsonPos]);
unsigned int valueBeginPos = ++jsonPos;
// The value appears to be a string
if (jsonC[jsonPos] == '"')
{
valueBeginPos++;
jsonPos++;
while (jsonC[jsonPos] != '"' && jsonPos < strSize(json) - 1)
jsonPos++;
if (jsonC[jsonPos] != '"')
THROW_FMT(JsonFormatError, "expected '\"' but found '%c'", jsonC[jsonPos]);
value = varNewStr(strNewN(jsonC + valueBeginPos, jsonPos - valueBeginPos));
jsonPos++;
}
// The value appears to be a number
else if (isdigit(jsonC[jsonPos]))
{
while (isdigit(jsonC[jsonPos]) && jsonPos < strSize(json) - 1)
jsonPos++;
String *valueStr = strNewN(jsonC + valueBeginPos, jsonPos - valueBeginPos);
value = varNewUInt64(cvtZToUInt64(strPtr(valueStr)));
}
// Else not sure what it is. Currently booleans and nulls will error.
else
THROW(JsonFormatError, "unknown value type");
kvPut(result, varNewStr(key), value);
}
while (jsonC[jsonPos] == ',');
// Look for end delimiter
if (jsonC[jsonPos] != '}')
THROW_FMT(JsonFormatError, "expected '}' but found '%c'", jsonC[jsonPos]);
}
MEM_CONTEXT_TEMP_END();
FUNCTION_DEBUG_RESULT(KEY_VALUE, result);
}

14
src/common/type/json.h Normal file
View File

@ -0,0 +1,14 @@
/***********************************************************************************************************************************
Convert JSON to/from KeyValue
***********************************************************************************************************************************/
#ifndef COMMON_TYPE_JSON_H
#define COMMON_TYPE_JSON_H
#include "common/type/keyValue.h"
/***********************************************************************************************************************************
Functions
***********************************************************************************************************************************/
KeyValue *jsonToKv(const String *json);
#endif

View File

@ -5046,6 +5046,8 @@ static const EmbeddedModule embeddedModule[] =
"push @EXPORT, qw(ERROR_PATH_CLOSE);\n"
"use constant ERROR_FILE_INFO => 98;\n"
"push @EXPORT, qw(ERROR_FILE_INFO);\n"
"use constant ERROR_JSON_FORMAT => 99;\n"
"push @EXPORT, qw(ERROR_JSON_FORMAT);\n"
"use constant ERROR_RUNTIME => 122;\n"
"push @EXPORT, qw(ERROR_RUNTIME);\n"
"use constant ERROR_INVALID => 123;\n"

View File

@ -185,6 +185,13 @@ unit:
coverage:
common/type/variantList: full
# ----------------------------------------------------------------------------------------------------------------------------
- name: type-json
total: 1
coverage:
common/type/json: full
# ----------------------------------------------------------------------------------------------------------------------------
- name: type-key-value
total: 2

View File

@ -0,0 +1,50 @@
/***********************************************************************************************************************************
Test Convert JSON to/from KeyValue
***********************************************************************************************************************************/
/***********************************************************************************************************************************
Test Run
***********************************************************************************************************************************/
void
testRun(void)
{
FUNCTION_HARNESS_VOID();
// *****************************************************************************************************************************
if (testBegin("jsonToKv()"))
{
TEST_ERROR(jsonToKv(strNew("[")), JsonFormatError, "arrays not supported");
TEST_ERROR(jsonToKv(strNew("<")), JsonFormatError, "expected '{' but found '<'");
TEST_ERROR(jsonToKv(strNew("{>")), JsonFormatError, "expected '\"' but found '>'");
TEST_ERROR(jsonToKv(strNew("{\"\"")), JsonFormatError, "zero-length key not allowed");
TEST_ERROR(jsonToKv(strNew("{\"key1\"")), JsonFormatError, "expected ':' but found '");
TEST_ERROR(jsonToKv(strNew("{\"key1'")), JsonFormatError, "expected '\"' but found '''");
TEST_ERROR(jsonToKv(strNew("{\"key1\":t")), JsonFormatError, "unknown value type");
TEST_ERROR(jsonToKv(strNew("{\"key1\":123")), JsonFormatError, "expected '}' but found '3'");
TEST_ERROR(jsonToKv(strNew("{\"key1\":123>")), JsonFormatError, "expected '}' but found '>'");
TEST_ERROR(jsonToKv(strNew("{\"key1\":\"123'}")), JsonFormatError, "expected '\"' but found '}'");
// -------------------------------------------------------------------------------------------------------------------------
KeyValue *kv = NULL;
TEST_ASSIGN(kv, jsonToKv(strNew("{\"key1\":123}")), "single integer value");
TEST_RESULT_UINT(varUInt64(kvGet(kv, varNewStr(strNew("key1")))), 123, " check integer");
TEST_ASSIGN(kv, jsonToKv(strNew("{\"key1\":\"value1\"}")), "single string value");
TEST_RESULT_STR(strPtr(varStr(kvGet(kv, varNewStr(strNew("key1"))))), "value1", " check string");
TEST_ASSIGN(
kv,
jsonToKv(
strNew(
"{\"db-catalog-version\":201409291,\"db-control-version\":942,\"db-system-id\":6116111691796124355,"
"\"db-version\":\"9.4\"}")),
"multiple values");
TEST_RESULT_UINT(varUInt64(kvGet(kv, varNewStr(strNew("db-catalog-version")))), 201409291, " check integer");
TEST_RESULT_UINT(varUInt64(kvGet(kv, varNewStr(strNew("db-control-version")))), 942, " check integer");
TEST_RESULT_UINT(varUInt64(kvGet(kv, varNewStr(strNew("db-system-id")))), 6116111691796124355, " check integer");
TEST_RESULT_STR(strPtr(varStr(kvGet(kv, varNewStr(strNew("db-version"))))), "9.4", " check string");
}
FUNCTION_HARNESS_RESULT_VOID();
}