You've already forked pgbackrest
mirror of
https://github.com/pgbackrest/pgbackrest.git
synced 2025-07-15 01:04:37 +02:00
Add basic C JSON parser.
This commit is contained in:
@ -86,6 +86,9 @@ path-close: 97
|
|||||||
# Unable to get info for a file
|
# Unable to get info for a file
|
||||||
file-info: 98
|
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
|
# This error should not be thrown directly -- it serves as a parent for the C errors
|
||||||
runtime: 122
|
runtime: 122
|
||||||
|
|
||||||
|
@ -53,6 +53,10 @@
|
|||||||
<p>Add <code>uint64</code> variant type and supporting conversion functions.</p>
|
<p>Add <code>uint64</code> variant type and supporting conversion functions.</p>
|
||||||
</release-item>
|
</release-item>
|
||||||
|
|
||||||
|
<release-item>
|
||||||
|
<p>Add basic C JSON parser.</p>
|
||||||
|
</release-item>
|
||||||
|
|
||||||
<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>
|
<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>
|
</release-item>
|
||||||
|
@ -165,6 +165,8 @@ use constant ERROR_PATH_CLOSE => 97;
|
|||||||
push @EXPORT, qw(ERROR_PATH_CLOSE);
|
push @EXPORT, qw(ERROR_PATH_CLOSE);
|
||||||
use constant ERROR_FILE_INFO => 98;
|
use constant ERROR_FILE_INFO => 98;
|
||||||
push @EXPORT, qw(ERROR_FILE_INFO);
|
push @EXPORT, qw(ERROR_FILE_INFO);
|
||||||
|
use constant ERROR_JSON_FORMAT => 99;
|
||||||
|
push @EXPORT, qw(ERROR_JSON_FORMAT);
|
||||||
use constant ERROR_RUNTIME => 122;
|
use constant ERROR_RUNTIME => 122;
|
||||||
push @EXPORT, qw(ERROR_RUNTIME);
|
push @EXPORT, qw(ERROR_RUNTIME);
|
||||||
use constant ERROR_INVALID => 123;
|
use constant ERROR_INVALID => 123;
|
||||||
|
@ -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
|
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
|
$(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
|
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
|
$(CC) $(CFLAGS) -c common/type/keyValue.c -o common/type/keyValue.o
|
||||||
|
|
||||||
|
@ -80,6 +80,7 @@ ERROR_DEFINE( 95, CipherError, RuntimeError);
|
|||||||
ERROR_DEFINE( 96, ParamInvalidError, RuntimeError);
|
ERROR_DEFINE( 96, ParamInvalidError, RuntimeError);
|
||||||
ERROR_DEFINE( 97, PathCloseError, RuntimeError);
|
ERROR_DEFINE( 97, PathCloseError, RuntimeError);
|
||||||
ERROR_DEFINE( 98, FileInfoError, RuntimeError);
|
ERROR_DEFINE( 98, FileInfoError, RuntimeError);
|
||||||
|
ERROR_DEFINE( 99, JsonFormatError, RuntimeError);
|
||||||
ERROR_DEFINE(122, RuntimeError, RuntimeError);
|
ERROR_DEFINE(122, RuntimeError, RuntimeError);
|
||||||
ERROR_DEFINE(123, InvalidError, RuntimeError);
|
ERROR_DEFINE(123, InvalidError, RuntimeError);
|
||||||
ERROR_DEFINE(124, UnhandledError, RuntimeError);
|
ERROR_DEFINE(124, UnhandledError, RuntimeError);
|
||||||
@ -163,6 +164,7 @@ static const ErrorType *errorTypeList[] =
|
|||||||
&ParamInvalidError,
|
&ParamInvalidError,
|
||||||
&PathCloseError,
|
&PathCloseError,
|
||||||
&FileInfoError,
|
&FileInfoError,
|
||||||
|
&JsonFormatError,
|
||||||
&RuntimeError,
|
&RuntimeError,
|
||||||
&InvalidError,
|
&InvalidError,
|
||||||
&UnhandledError,
|
&UnhandledError,
|
||||||
|
@ -82,6 +82,7 @@ ERROR_DECLARE(CipherError);
|
|||||||
ERROR_DECLARE(ParamInvalidError);
|
ERROR_DECLARE(ParamInvalidError);
|
||||||
ERROR_DECLARE(PathCloseError);
|
ERROR_DECLARE(PathCloseError);
|
||||||
ERROR_DECLARE(FileInfoError);
|
ERROR_DECLARE(FileInfoError);
|
||||||
|
ERROR_DECLARE(JsonFormatError);
|
||||||
ERROR_DECLARE(RuntimeError);
|
ERROR_DECLARE(RuntimeError);
|
||||||
ERROR_DECLARE(InvalidError);
|
ERROR_DECLARE(InvalidError);
|
||||||
ERROR_DECLARE(UnhandledError);
|
ERROR_DECLARE(UnhandledError);
|
||||||
|
111
src/common/type/json.c
Normal file
111
src/common/type/json.c
Normal 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
14
src/common/type/json.h
Normal 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
|
@ -5046,6 +5046,8 @@ static const EmbeddedModule embeddedModule[] =
|
|||||||
"push @EXPORT, qw(ERROR_PATH_CLOSE);\n"
|
"push @EXPORT, qw(ERROR_PATH_CLOSE);\n"
|
||||||
"use constant ERROR_FILE_INFO => 98;\n"
|
"use constant ERROR_FILE_INFO => 98;\n"
|
||||||
"push @EXPORT, qw(ERROR_FILE_INFO);\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"
|
"use constant ERROR_RUNTIME => 122;\n"
|
||||||
"push @EXPORT, qw(ERROR_RUNTIME);\n"
|
"push @EXPORT, qw(ERROR_RUNTIME);\n"
|
||||||
"use constant ERROR_INVALID => 123;\n"
|
"use constant ERROR_INVALID => 123;\n"
|
||||||
|
@ -185,6 +185,13 @@ unit:
|
|||||||
coverage:
|
coverage:
|
||||||
common/type/variantList: full
|
common/type/variantList: full
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------------------------------------------------------------
|
||||||
|
- name: type-json
|
||||||
|
total: 1
|
||||||
|
|
||||||
|
coverage:
|
||||||
|
common/type/json: full
|
||||||
|
|
||||||
# ----------------------------------------------------------------------------------------------------------------------------
|
# ----------------------------------------------------------------------------------------------------------------------------
|
||||||
- name: type-key-value
|
- name: type-key-value
|
||||||
total: 2
|
total: 2
|
||||||
|
50
test/src/module/common/typeJsonTest.c
Normal file
50
test/src/module/common/typeJsonTest.c
Normal 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();
|
||||||
|
}
|
Reference in New Issue
Block a user