2
0
mirror of https://github.com/openvswitch/ovs synced 2025-09-01 06:45:17 +00:00

Implement JSON parsing and serialization.

This will be used by the upcoming Open vSwitch configuration database.
This commit is contained in:
Ben Pfaff
2009-11-04 14:55:53 -08:00
parent e7f1bf58e4
commit f38b84ea2b
15 changed files with 2337 additions and 0 deletions

View File

@@ -38,6 +38,8 @@ AC_USE_SYSTEM_EXTENSIONS
AC_C_BIGENDIAN
AC_SYS_LARGEFILE
AC_SEARCH_LIBS([pow], [m])
OVS_CHECK_COVERAGE
OVS_CHECK_NDEBUG
OVS_CHECK_NETLINK

View File

@@ -49,6 +49,8 @@ lib_libopenvswitch_a_SOURCES = \
lib/hash.h \
lib/hmap.c \
lib/hmap.h \
lib/json.c \
lib/json.h \
lib/leak-checker.c \
lib/leak-checker.h \
lib/learning-switch.c \
@@ -107,6 +109,8 @@ lib_libopenvswitch_a_SOURCES = \
lib/type-props.h \
lib/unixctl.c \
lib/unixctl.h \
lib/unicode.c \
lib/unicode.h \
lib/util.c \
lib/util.h \
lib/valgrind.h \

View File

@@ -71,6 +71,31 @@ ds_put_char(struct ds *ds, char c)
*ds_put_uninit(ds, 1) = c;
}
/* Appends unicode code point 'uc' to 'ds' in UTF-8 encoding. */
void
ds_put_utf8(struct ds *ds, int uc)
{
if (uc <= 0x7f) {
ds_put_char(ds, uc);
} else if (uc <= 0x7ff) {
ds_put_char(ds, 0xc0 | (uc >> 6));
ds_put_char(ds, 0x80 | (uc & 0x3f));
} else if (uc <= 0xffff) {
ds_put_char(ds, 0xe0 | (uc >> 12));
ds_put_char(ds, 0x80 | ((uc >> 6) & 0x3f));
ds_put_char(ds, 0x80 | (uc & 0x3f));
} else if (uc <= 0x10ffff) {
ds_put_char(ds, 0xf0 | (uc >> 18));
ds_put_char(ds, 0x80 | ((uc >> 12) & 0x3f));
ds_put_char(ds, 0x80 | ((uc >> 6) & 0x3f));
ds_put_char(ds, 0x80 | (uc & 0x3f));
} else {
/* Invalid code point. Insert the Unicode general substitute
* REPLACEMENT CHARACTER. */
ds_put_utf8(ds, 0xfffd);
}
}
void
ds_put_char_multiple(struct ds *ds, char c, size_t n)
{

View File

@@ -40,6 +40,7 @@ void ds_truncate(struct ds *, size_t new_length);
void ds_reserve(struct ds *, size_t min_length);
char *ds_put_uninit(struct ds *, size_t n);
void ds_put_char(struct ds *, char);
void ds_put_utf8(struct ds *, int uc);
void ds_put_char_multiple(struct ds *, char, size_t n);
void ds_put_buffer(struct ds *, const char *, size_t n);
void ds_put_cstr(struct ds *, const char *);

1569
lib/json.c Normal file

File diff suppressed because it is too large Load Diff

122
lib/json.h Normal file
View File

@@ -0,0 +1,122 @@
/*
* Copyright (c) 2009 Nicira Networks.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef JSON_H
#define JSON_H 1
/* This is an implementation of JavaScript Object Notation (JSON) as specified
* by RFC 4627. It is intended to fully comply with RFC 4627, with the
* following known exceptions and clarifications:
*
* - Null bytes (\u0000) are not allowed in strings.
*
* - Only UTF-8 encoding is supported (RFC 4627 allows for other Unicode
* encodings).
*
* - Names within an object must be unique (RFC 4627 says that they
* "should" be unique).
*/
#include "shash.h"
/* Type of a JSON value. */
enum json_type {
JSON_NULL, /* null */
JSON_FALSE, /* false */
JSON_TRUE, /* true */
JSON_OBJECT, /* {"a": b, "c": d, ...} */
JSON_ARRAY, /* [1, 2, 3, ...] */
JSON_INTEGER, /* 123. */
JSON_REAL, /* 123.456. */
JSON_STRING, /* "..." */
JSON_N_TYPES
};
const char *json_type_to_string(enum json_type);
/* A JSON array. */
struct json_array {
size_t n, n_allocated;
struct json **elems;
};
/* A JSON value. */
struct json {
enum json_type type;
union {
struct shash *object; /* Contains "struct json *"s. */
struct json_array array;
long long int integer;
double real;
char *string;
} u;
};
struct json *json_null_create(void);
struct json *json_boolean_create(bool);
struct json *json_string_create(const char *);
struct json *json_string_create_nocopy(char *);
struct json *json_integer_create(long long int);
struct json *json_real_create(double);
struct json *json_array_create_empty(void);
void json_array_add(struct json *, struct json *element);
void json_array_trim(struct json *);
struct json *json_array_create(struct json **, size_t n);
struct json *json_array_create_2(struct json *, struct json *);
struct json *json_array_create_3(struct json *, struct json *, struct json *);
struct json *json_object_create(void);
void json_object_put(struct json *, const char *name, struct json *value);
void json_object_put_string(struct json *,
const char *name, const char *value);
const char *json_string(const struct json *);
struct json_array *json_array(const struct json *);
struct shash *json_object(const struct json *);
bool json_boolean(const struct json *);
double json_real(const struct json *);
int64_t json_integer(const struct json *);
struct json *json_clone(const struct json *);
void json_destroy(struct json *);
size_t json_hash(const struct json *, size_t basis);
bool json_equal(const struct json *, const struct json *);
/* Parsing JSON. */
enum {
JSPF_TRAILER = 1 << 0 /* Check for garbage following input. */
};
struct json_parser *json_parser_create(int flags);
size_t json_parser_feed(struct json_parser *, const char *, size_t);
bool json_parser_is_done(const struct json_parser *);
struct json *json_parser_finish(struct json_parser *);
void json_parser_abort(struct json_parser *);
struct json *json_from_string(const char *string);
struct json *json_from_file(const char *file_name);
/* Serializing JSON. */
enum {
JSSF_PRETTY = 1 << 0, /* Multiple lines with indentation, if true. */
JSSF_SORT = 1 << 1 /* Object members in sorted order, if true. */
};
char *json_to_string(const struct json *, int flags);
#endif /* json.h */

38
lib/unicode.c Normal file
View File

@@ -0,0 +1,38 @@
/*
* Copyright (c) 2009 Nicira Networks.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <config.h>
#include "unicode.h"
/* Returns the unicode code point corresponding to leading surrogate 'leading'
* and trailing surrogate 'trailing'. The return value will not make any
* sense if 'leading' or 'trailing' are not in the correct ranges for leading
* or trailing surrogates. */
int
utf16_decode_surrogate_pair(int leading, int trailing)
{
/*
* Leading surrogate: 110110wwwwxxxxxx
* Trailing surrogate: 110111xxxxxxxxxx
* Code point: 000uuuuuxxxxxxxxxxxxxxxx
*/
int w = (leading >> 6) & 0xf;
int u = w + 1;
int x0 = leading & 0x3f;
int x1 = trailing & 0x3ff;
return (u << 16) | (x0 << 10) | x1;
}

53
lib/unicode.h Normal file
View File

@@ -0,0 +1,53 @@
/*
* Copyright (c) 2009 Nicira Networks.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef UNICODE_H
#define UNICODE_H 1
#include <stdbool.h>
/* Returns true if 'c' is a Unicode code point, otherwise false. */
static inline bool
uc_is_code_point(int c)
{
return c >= 0 && c <= 0x10ffff;
}
/* Returns true if 'c' is a Unicode code point for a leading surrogate. */
static inline bool
uc_is_leading_surrogate(int c)
{
return c >= 0xd800 && c <= 0xdbff;
}
/* Returns true if 'c' is a Unicode code point for a trailing surrogate. */
static inline bool
uc_is_trailing_surrogate(int c)
{
return c >= 0xdc00 && c <= 0xdfff;
}
/* Returns true if 'c' is a Unicode code point for a leading or trailing
* surrogate. */
static inline bool
uc_is_surrogate(int c)
{
return c >= 0xd800 && c <= 0xdfff;
}
int utf16_decode_surrogate_pair(int leading, int trailing);
#endif /* unicode.h */

View File

@@ -300,3 +300,58 @@ str_to_ullong(const char *s, int base, unsigned long long *ull)
{
return str_to_llong(s, base, (long long *) ull);
}
/* Converts floating-point string 's' into a double. If successful, stores
* the double in '*d' and returns true; on failure, stores 0 in '*d' and
* returns false.
*
* Underflow (e.g. "1e-9999") is not considered an error, but overflow
* (e.g. "1e9999)" is. */
bool
str_to_double(const char *s, double *d)
{
int save_errno = errno;
char *tail;
errno = 0;
*d = strtod(s, &tail);
if (errno == EINVAL || (errno == ERANGE && *d != 0)
|| tail == s || *tail != '\0') {
errno = save_errno;
*d = 0;
return false;
} else {
errno = save_errno;
return true;
}
}
/* Returns the value of 'c' as a hexadecimal digit. */
int
hexit_value(int c)
{
switch (c) {
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
return c - '0';
case 'a': case 'A':
return 0xa;
case 'b': case 'B':
return 0xb;
case 'c': case 'C':
return 0xc;
case 'd': case 'D':
return 0xd;
case 'e': case 'E':
return 0xe;
case 'f': case 'F':
return 0xf;
}
NOT_REACHED();
}

View File

@@ -121,6 +121,10 @@ bool str_to_uint(const char *, int base, unsigned int *);
bool str_to_ulong(const char *, int base, unsigned long *);
bool str_to_ullong(const char *, int base, unsigned long long *);
bool str_to_double(const char *, double *);
int hexit_value(int c);
#ifdef __cplusplus
}
#endif

1
tests/.gitignore vendored
View File

@@ -5,6 +5,7 @@
/test-flows
/test-hash
/test-hmap
/test-json
/test-list
/test-stp
/test-type-props

View File

@@ -8,6 +8,7 @@ TESTSUITE_AT = \
tests/testsuite.at \
tests/lcov-pre.at \
tests/library.at \
tests/json.at \
tests/timeval.at \
tests/lockfile.at \
tests/stp.at \
@@ -60,6 +61,10 @@ noinst_PROGRAMS += tests/test-hmap
tests_test_hmap_SOURCES = tests/test-hmap.c
tests_test_hmap_LDADD = lib/libopenvswitch.a
noinst_PROGRAMS += tests/test-json
tests_test_json_SOURCES = tests/test-json.c
tests_test_json_LDADD = lib/libopenvswitch.a
noinst_PROGRAMS += tests/test-list
tests_test_list_SOURCES = tests/test-list.c
tests_test_list_LDADD = lib/libopenvswitch.a

297
tests/json.at Normal file
View File

@@ -0,0 +1,297 @@
m4_define([JSON_CHECK_POSITIVE],
[AT_SETUP([$1])
AT_KEYWORDS([json positive])
AT_CHECK([printf %s "AS_ESCAPE([$2])" > input])
OVS_CHECK_LCOV([test-json $4 input], [0], [$3
], [])
AT_CLEANUP])
m4_define([JSON_CHECK_NEGATIVE],
[AT_SETUP([$1])
AT_KEYWORDS([json negative])
AT_CHECK([printf %s "AS_ESCAPE([$2])" > input])
OVS_CHECK_LCOV([test-json $4 input], [1], [$3
])
AT_CLEANUP])
AT_BANNER([JSON -- arrays])
JSON_CHECK_POSITIVE([empty array], [[ [ ] ]], [[[]]])
JSON_CHECK_POSITIVE([single-element array], [[ [ 1 ] ]], [[[1]]])
JSON_CHECK_POSITIVE([2-element array], [[ [ 1, 2 ] ]], [[[1,2]]])
JSON_CHECK_POSITIVE([many-element array],
[[ [ 1, 2, 3, 4, 5 ] ]],
[[[1,2,3,4,5]]])
JSON_CHECK_NEGATIVE([missing comma], [[ [ 1, 2, 3 4, 5 ] ]],
[error: syntax error expecting '@:>@' or ','])
JSON_CHECK_NEGATIVE([trailing comma not allowed],
[[[1,2,]]], [error: syntax error expecting value])
JSON_CHECK_NEGATIVE([doubled comma not allowed],
[[[1,,2]]], [error: syntax error expecting value])
AT_BANNER([JSON -- strings])
JSON_CHECK_POSITIVE([empty string], [[[ "" ]]], [[[""]]])
JSON_CHECK_POSITIVE([1-character strings],
[[[ "a", "b", "c" ]]],
[[["a","b","c"]]])
JSON_CHECK_POSITIVE([escape sequences],
[[[ " \" \\ \/ \b \f \n \r \t" ]]],
[[[" \" \\ / \b \f \n \r \t"]]])
JSON_CHECK_POSITIVE([Unicode escape sequences],
[[[ " \u0022 \u005c \u002F \u0008 \u000c \u000A \u000d \u0009" ]]],
[[[" \" \\ / \b \f \n \r \t"]]])
JSON_CHECK_POSITIVE([surrogate pairs],
[[["\ud834\udd1e"]]],
[[["𝄞"]]])
JSON_CHECK_NEGATIVE([a string by itself is not valid JSON], ["xxx"],
[error: syntax error at beginning of input])
JSON_CHECK_NEGATIVE([end of line in quoted string],
[[["xxx
"]]],
[error: U+000A must be escaped in quoted string])
JSON_CHECK_NEGATIVE([formfeed in quoted string],
[[["xxx "]]],
[error: U+000C must be escaped in quoted string])
JSON_CHECK_NEGATIVE([bad escape in quoted string],
[[["\x12"]]],
[error: bad escape \x])
JSON_CHECK_NEGATIVE([\u must be followed by 4 hex digits],
[[["\u1x"]]],
[error: malformed \u escape])
JSON_CHECK_NEGATIVE([isolated leading surrogate not allowed],
[[["\ud834xxx"]]],
[error: malformed escaped surrogate pair])
JSON_CHECK_NEGATIVE([surrogatess must paired properly],
[[["\ud834\u1234"]]],
[error: second half of escaped surrogate pair is not trailing surrogate])
JSON_CHECK_NEGATIVE([null bytes not allowed],
[[["\u0000"]]],
[error: null bytes not supported in quoted strings])
AT_SETUP([end of input in quoted string])
AT_KEYWORDS([json negative])
AT_CHECK([printf '\"xxx' | test-json -], [1],
[error: unexpected end of input in quoted string
])
AT_CLEANUP
AT_BANNER([JSON -- objects])
JSON_CHECK_POSITIVE([empty object], [[{ }]], [[{}]])
JSON_CHECK_POSITIVE([simple object],
[[{"b": 2, "a": 1, "c": 3}]],
[[{"a":1,"b":2,"c":3}]])
JSON_CHECK_NEGATIVE([bad value], [[{"a": }, "b": 2]],
[error: syntax error expecting value])
JSON_CHECK_NEGATIVE([missing colon], [[{"b": 2, "a" 1, "c": 3}]],
[error: syntax error parsing object expecting ':'])
JSON_CHECK_NEGATIVE([missing comma], [[{"b": 2 "a" 1, "c": 3}]],
[error: syntax error expecting '}' or ','])
JSON_CHECK_NEGATIVE([trailing comma not allowed],
[[{"b": 2, "a": 1, "c": 3, }]],
[[error: syntax error parsing object expecting string]])
JSON_CHECK_NEGATIVE([doubled comma not allowed],
[[{"b": 2, "a": 1,, "c": 3}]],
[[error: syntax error parsing object expecting string]])
JSON_CHECK_NEGATIVE([names must be strings],
[[{1: 2}]],
[[error: syntax error parsing object expecting string]])
AT_BANNER([JSON -- literal names])
JSON_CHECK_POSITIVE([null], [[[ null ]]], [[[null]]])
JSON_CHECK_POSITIVE([false], [[[ false ]]], [[[false]]])
JSON_CHECK_POSITIVE([true], [[[ true ]]], [[[true]]])
JSON_CHECK_NEGATIVE([a literal by itself is not valid JSON], [null],
[error: syntax error at beginning of input])
JSON_CHECK_NEGATIVE([nullify is invalid], [[[ nullify ]]],
[error: invalid keyword 'nullify'])
JSON_CHECK_NEGATIVE([nubs is invalid], [[[ nubs ]]],
[error: invalid keyword 'nubs'])
JSON_CHECK_NEGATIVE([xxx is invalid], [[[ xxx ]]],
[error: invalid keyword 'xxx'])
AT_BANNER([JSON -- numbers])
JSON_CHECK_POSITIVE(
[integers expressed as reals],
[[[1.0000000000,
2.00000000000000000000000000000000000,
2e5,
2.1234e4,
2.1230e3,
0e-10000,
0e10000]]],
[[[1,2,200000,21234,2123,0,0]]])
JSON_CHECK_POSITIVE(
[large integers],
[[[9223372036854775807, -9223372036854775808]]],
[[[9223372036854775807,-9223372036854775808]]])
JSON_CHECK_POSITIVE(
[large integers expressed as reals],
[[[9223372036854775807.0, -9223372036854775808.0,
92233720.36854775807e11, -9.223372036854775808e18]]],
[[[9223372036854775807,-9223372036854775808,9223372036854775807,-9223372036854775808]]])
# It seems likely that the following test will fail on some system that
# rounds slightly differently in arithmetic or in printf, but I'd like
# to keep it this way until we run into such a system.
JSON_CHECK_POSITIVE(
[large integers that overflow to reals],
[[[9223372036854775807000, -92233720368547758080000]]],
[[[9.22337203685478e+21,-9.22337203685478e+22]]])
JSON_CHECK_POSITIVE(
[negative zero],
[[[-0, -0.0, 1e-9999, -1e-9999]]],
[[[0,0,0,0]]])
JSON_CHECK_POSITIVE(
[reals],
[[[0.0, 1.0, 2.0, 3.0, 3.5, 81.250]]],
[[[0,1,2,3,3.5,81.25]]])
JSON_CHECK_POSITIVE(
[scientific notation],
[[[1e3, 1E3, 2.5E2, 1e+3, 125e-3, 3.125e-2, 3125e-05, 1.525878906e-5]]],
[[[1000,1000,250,1000,0.125,0.03125,0.03125,1.525878906e-05]]])
JSON_CHECK_POSITIVE(
[negative reals],
[[[-0, -1.0, -2.0, -3.0, -3.5, -8.1250]]],
[[[0,-1,-2,-3,-3.5,-8.125]]])
JSON_CHECK_POSITIVE(
[negative scientific notation],
[[[-1e3, -1E3, -2.5E2, -1e+3, -125e-3, -3.125e-2, -3125e-05, -1.525878906e-5]]],
[[[-1000,-1000,-250,-1000,-0.125,-0.03125,-0.03125,-1.525878906e-05]]])
JSON_CHECK_POSITIVE(
[1e-9999 underflows to 0],
[[[1e-9999]]],
[[[0]]])
JSON_CHECK_NEGATIVE([a number by itself is not valid JSON], [1],
[error: syntax error at beginning of input])
JSON_CHECK_NEGATIVE(
[leading zeros not allowed],
[[[0123]]],
[error: leading zeros not allowed])
JSON_CHECK_NEGATIVE(
[1e9999 is too big],
[[[1e9999]]],
[error: number outside valid range])
JSON_CHECK_NEGATIVE(
[exponent bigger than INT_MAX],
[[[1e9999999999999999999]]],
[error: exponent outside valid range])
JSON_CHECK_NEGATIVE(
[decimal point must be followed by digit],
[[[1.]]],
[error: decimal point must be followed by digit])
JSON_CHECK_NEGATIVE(
[exponent must contain at least one digit (1)],
[[[1e]]],
[error: exponent must contain at least one digit])
JSON_CHECK_NEGATIVE(
[exponent must contain at least one digit (2)],
[[[1e+]]],
[error: exponent must contain at least one digit])
JSON_CHECK_NEGATIVE(
[exponent must contain at least one digit (3)],
[[[1e-]]],
[error: exponent must contain at least one digit])
AT_BANNER([JSON -- RFC 4627 examples])
JSON_CHECK_POSITIVE([RFC 4267 object example],
[[{
"Image": {
"Width": 800,
"Height": 600,
"Title": "View from 15th Floor",
"Thumbnail": {
"Url": "http://www.example.com/image/481989943",
"Height": 125,
"Width": "100"
},
"IDs": [116, 943, 234, 38793]
}
}]],
[[{"Image":{"Height":600,"IDs":[116,943,234,38793],"Thumbnail":{"Height":125,"Url":"http://www.example.com/image/481989943","Width":"100"},"Title":"View from 15th Floor","Width":800}}]])
JSON_CHECK_POSITIVE([RFC 4267 array example],
[[[
{
"precision": "zip",
"Latitude": 37.7668,
"Longitude": -122.3959,
"Address": "",
"City": "SAN FRANCISCO",
"State": "CA",
"Zip": "94107",
"Country": "US"
},
{
"precision": "zip",
"Latitude": 37.371991,
"Longitude": -122.026020,
"Address": "",
"City": "SUNNYVALE",
"State": "CA",
"Zip": "94085",
"Country": "US"
}
]]],
[[[{"Address":"","City":"SAN FRANCISCO","Country":"US","Latitude":37.7668,"Longitude":-122.3959,"State":"CA","Zip":"94107","precision":"zip"},{"Address":"","City":"SUNNYVALE","Country":"US","Latitude":37.371991,"Longitude":-122.02602,"State":"CA","Zip":"94085","precision":"zip"}]]])
AT_BANNER([JSON -- pathological cases])
JSON_CHECK_NEGATIVE([trailing garbage], [[[1]null]],
[error: trailing garbage at end of input])
JSON_CHECK_NEGATIVE([formfeeds are not valid white space],
[[[ ]]], [error: invalid character U+000c])
JSON_CHECK_NEGATIVE([';' is not a valid token],
[;], [error: invalid character ';'])
JSON_CHECK_NEGATIVE([arrays nesting too deep],
[m4_for([i], [0], [1002], [1], [@<:@])dnl
m4_for([i], [0], [1002], [1], [@:>@])],
[error: input exceeds maximum nesting depth 1000])
JSON_CHECK_NEGATIVE([objects nesting too deep],
[m4_for([i], [0], [1002], [1], [{"x":])dnl
m4_for([i], [0], [1002], [1], [}])],
[error: input exceeds maximum nesting depth 1000])
AT_SETUP([input may not be empty])
AT_KEYWORDS([json negative])
AT_CHECK([test-json /dev/null], [1], [error: empty input stream
])
AT_CLEANUP
AT_BANNER([JSON -- multiple inputs])
JSON_CHECK_POSITIVE([multiple adjacent objects], [[{}{}{}]], [[{}
{}
{}]],
[--multiple])
JSON_CHECK_POSITIVE([multiple space-separated objects], [[{} {} {}]], [[{}
{}
{}]],
[--multiple])
JSON_CHECK_POSITIVE([multiple objects on separate lines], [[{}
{}
{}]], [[{}
{}
{}]],
[--multiple])
JSON_CHECK_POSITIVE([multiple objects and arrays], [[{}[]{}[]]], [[{}
[]
{}
[]]],
[--multiple])
JSON_CHECK_NEGATIVE([garbage between multiple objects], [[{}x{}]], [[{}
error: invalid keyword 'x'
{}]], [--multiple])
JSON_CHECK_NEGATIVE([garbage after multiple objects], [[{}{}x]], [[{}
{}
error: invalid keyword 'x']], [--multiple])

160
tests/test-json.c Normal file
View File

@@ -0,0 +1,160 @@
/*
* Copyright (c) 2009 Nicira Networks.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <config.h>
#include "json.h"
#include <ctype.h>
#include <errno.h>
#include <getopt.h>
#include <stdio.h>
#include "util.h"
/* --pretty: If set, the JSON output is pretty-printed, instead of printed as
* compactly as possible. */
static int pretty = 0;
/* --multiple: If set, the input is a sequence of JSON objects or arrays,
* instead of exactly one object or array. */
static int multiple = 0;
static bool
print_and_free_json(struct json *json)
{
bool ok;
if (json->type == JSON_STRING) {
printf("error: %s\n", json->u.string);
ok = false;
} else {
char *s = json_to_string(json, JSSF_SORT | (pretty ? JSSF_PRETTY : 0));
puts(s);
free(s);
ok = true;
}
json_destroy(json);
return ok;
}
static bool
refill(FILE *file, void *buffer, size_t buffer_size, size_t *n, size_t *used)
{
*used = 0;
if (feof(file)) {
*n = 0;
return false;
} else {
*n = fread(buffer, 1, buffer_size, file);
if (ferror(file)) {
ovs_fatal(errno, "Error reading input file");
}
return *n > 0;
}
}
static bool
parse_multiple(const char *input_file)
{
struct json_parser *parser;
char buffer[BUFSIZ];
size_t n, used;
FILE *file;
bool ok;
file = fopen(input_file, "r");
if (!file) {
ovs_fatal(errno, "Cannot open \"%s\"", input_file);
}
parser = NULL;
n = used = 0;
ok = true;
while (used < n || refill(file, buffer, sizeof buffer, &n, &used)) {
if (!parser && isspace((unsigned char) buffer[used])) {
/* Skip white space. */
used++;
} else {
if (!parser) {
parser = json_parser_create(0);
}
used = n - json_parser_feed(parser, &buffer[used], n - used);
if (used < n) {
if (!print_and_free_json(json_parser_finish(parser))) {
ok = false;
}
parser = NULL;
}
}
}
if (parser) {
if (!print_and_free_json(json_parser_finish(parser))) {
ok = false;
}
}
return ok;
}
int
main(int argc, char *argv[])
{
const char *input_file;
bool ok;
set_program_name(argv[0]);
for (;;) {
static const struct option options[] = {
{"pretty", no_argument, &pretty, 1},
{"multiple", no_argument, &multiple, 1},
};
int option_index = 0;
int c = getopt_long (argc, argv, "", options, &option_index);
if (c == -1) {
break;
}
switch (c) {
case 0:
break;
case '?':
exit(1);
default:
abort();
}
}
if (argc - optind != 1) {
ovs_fatal(0, "usage: %s [--pretty] [--multiple] INPUT.json",
program_name);
}
input_file = argv[optind];
if (!strcmp(input_file, "-")) {
input_file = "/dev/stdin";
}
if (multiple) {
ok = parse_multiple(input_file);
} else {
ok = print_and_free_json(json_from_file(input_file));
}
return !ok;
}

View File

@@ -19,6 +19,7 @@ AT_TESTED([ovs-vsctl])
m4_include([tests/lcov-pre.at])
m4_include([tests/library.at])
m4_include([tests/json.at])
m4_include([tests/timeval.at])
m4_include([tests/lockfile.at])
m4_include([tests/stp.at])