From 45e5df33f4fcc022cef20fbf65e2b99a7ebeb9aa Mon Sep 17 00:00:00 2001 From: Christian Lindig Date: Mon, 8 Aug 2016 14:01:03 +0100 Subject: [PATCH 1/8] CP-18272 introduce #ifdef around change in RRD format change 1.0.0/1.1.0 Signed-off-by: Christian Lindig --- librrd.c | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/librrd.c b/librrd.c index 4457017..b24467d 100644 --- a/librrd.c +++ b/librrd.c @@ -144,17 +144,27 @@ json_for_source(RRD_SOURCE * source) } json_object_set_string(src, "value_type", value_type); +#define RRD_TRANSPORT_1_0_0 +#ifdef RRD_TRANSPORT_1_1_0 +#define GAUGE "gauge" +#define ABSOLUTE "absolute" +#define DERIVE "derive" +#else +#define GAUGE "absolute" +#define ABSOLUTE "rate" +#define DERIVE "absolute_to_rate" +#endif char *scale = NULL; switch (source->scale) { case RRD_GAUGE: - scale = "gauge"; + scale = GAUGE; break; case RRD_ABSOLUTE: - scale = "absolute"; + scale = ABSOLUTE; break; case RRD_DERIVE: - scale = "derive"; + scale = DERIVE; break; default: abort(); From 677887d7bce5b4439713e1203f1dc98f7715e563 Mon Sep 17 00:00:00 2001 From: Christian Lindig Date: Mon, 8 Aug 2016 14:01:53 +0100 Subject: [PATCH 2/8] CP-18272 add integration tests using ocaml-rrd-transport framework This commit adds test that use rrdreader built with the OCaml rrd-transport framework. Files written by the library are read back the by the tool. This is an integration test as the rrd-transport framework is the ultimate consumer of RRD files. Signed-off-by: Christian Lindig --- .travis.yml | 1 - Makefile | 21 ++++++++++++++++----- ocaml/rrdreader.ml | 40 ++++++++++++++++++++++++++++++++++------ 3 files changed, 50 insertions(+), 12 deletions(-) diff --git a/.travis.yml b/.travis.yml index a59147b..81b8e8d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,5 +10,4 @@ matrix: script: - make parson - make - - make test diff --git a/Makefile b/Makefile index 238d30e..893ea37 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,9 @@ # vim: set ts=8 sw=8 noet: # +# To run the tests you need OCaml and the ocaml-rrd-transport-devel +# package: +# +# yum install -y ocaml-rrd-transport-devel # CC = gcc @@ -25,12 +29,18 @@ clean: cd ocaml; $(OCB) -clean .PHONY: test -test: rrdtest rrdclient +test: rrdtest rrdclient rrdreader ./rrdtest seq 1 10 | ./rrdclient rrdclient.rrd - seq 1 10 \ - | while read i; do echo $$i; sleep 1; done \ - | ./rrdclient rrdclient.rrd + seq 1 10 | while read i; do \ + echo $$i | ./rrdclient rrdclient.rrd ;\ + ./ocaml/rrdreader.native rrdclient.rrd ;\ + done + seq 1 20 | while read i; do \ + echo $$i | ./rrdclient rrdclient.rrd ;\ + sleep 1 ;\ + done & ./ocaml/rrdreader.native -l 2 rrdclient.rrd \ + || echo "a final exception Rrd_protocol.No_update is OK" .PHONY: valgrind valgrind: rrdtest @@ -94,6 +104,7 @@ librrd.o: parson/parson.h librrd.h # OCaml test utility # You need: yum install -y ocaml-rrd-transport-devel +.PHONY: rrdreader rrdreader: - cd ocaml; $(OCB) -pkg rrd-transport -tag thread rrdreader.native + cd ocaml; $(OCB) -pkgs rrd-transport,unix -tag thread rrdreader.native diff --git a/ocaml/rrdreader.ml b/ocaml/rrdreader.ml index 371889d..6fd4b56 100644 --- a/ocaml/rrdreader.ml +++ b/ocaml/rrdreader.ml @@ -25,6 +25,15 @@ open Rrd_protocol +exception Error of string +let error fmt = Printf.kprintf (fun msg -> raise (Error msg)) fmt + +let finally opn cls = + let res = try opn () with exn -> cls (); raise exn + in + cls (); + res + let string_of_data_source owner ds = let owner_string = match owner with | Rrd.Host -> "Host" @@ -58,15 +67,34 @@ let print payload = let read path = let v2 = Rrd_protocol_v2.protocol in let reader = Rrd_reader.FileReader.create path v2 in - reader.Rrd_reader.read_payload () - |> print + finally + (fun () -> + reader.Rrd_reader.read_payload () + |> print) + (fun () -> + reader.Rrd_reader.cleanup ()) + +let read_loop seconds path = + let v2 = Rrd_protocol_v2.protocol in + let reader = Rrd_reader.FileReader.create path v2 in + let rec loop () = + ( reader.Rrd_reader.read_payload () |> print + ; Unix.sleep seconds + ; loop () + ) in + finally loop reader.Rrd_reader.cleanup + +let atoi str = + try int_of_string str with Failure _ -> error "not an int: %s" str let main () = - let args = Array.to_list Sys.argv in - let this = Sys.executable_name in + let args = Array.to_list Sys.argv in + let this = Sys.executable_name in + let printf = Printf.printf in match args with - | [_;path] -> read path; exit 0 - | _ -> Printf.printf "usage: %s file.rrd\n" this; exit 1 + | [_;path] -> read path; exit 0 + | [_;"-l";s;path] -> read_loop (atoi s) path + | _ -> printf "usage: [-l secs] %s file.rrd\n" this; exit 1 let () = main () From 17748b1f832957b94354cd1f9b1117ad0e1ea17e Mon Sep 17 00:00:00 2001 From: Christian Lindig Date: Mon, 8 Aug 2016 14:09:24 +0100 Subject: [PATCH 3/8] CP-18272 change URL in README to point to correct Travis CI icon Signed-off-by: Christian Lindig --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 97dfeed..5272f44 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ -[![Build Status](https://travis-ci.org/lindig/rrd-client-lib-1.svg?branch=master)](https://travis-ci.org/lindig/rrd-client-lib-1) +[![Build Status](https://travis-ci.org/xapi-project/rrd-client-lib.svg?branch=master)](https://travis-ci.org/xapi-project/rrd-client-lib) # rrd-client-lib - A Library to Provide RRD Data From c4ad411c604c29eaf9a219590c3a58f5814bddb8 Mon Sep 17 00:00:00 2001 From: Christian Lindig Date: Mon, 8 Aug 2016 16:55:52 +0100 Subject: [PATCH 4/8] CP-18271 fix indentation Signed-off-by: Christian Lindig --- ocaml/rrdreader.ml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/ocaml/rrdreader.ml b/ocaml/rrdreader.ml index 6fd4b56..87ef7f9 100644 --- a/ocaml/rrdreader.ml +++ b/ocaml/rrdreader.ml @@ -29,10 +29,9 @@ exception Error of string let error fmt = Printf.kprintf (fun msg -> raise (Error msg)) fmt let finally opn cls = - let res = try opn () with exn -> cls (); raise exn - in - cls (); - res + let res = try opn () with exn -> cls (); raise exn in + cls (); + res let string_of_data_source owner ds = let owner_string = match owner with From 8d9413aad10a6b9d318a9cc6c32bd5b936aef65a Mon Sep 17 00:00:00 2001 From: Christian Lindig Date: Mon, 8 Aug 2016 20:17:51 +0100 Subject: [PATCH 5/8] CP-18272 include Parson library as directory (was: Git submodule) Including the code makes packaging easier. The MIT license of Parson is compatible with the MIT license of rrd-client-lib. The name of the parson directory points to the commit we are using. Signed-off-by: Christian Lindig --- .gitmodules | 3 - README.md | 5 +- parson | 2 +- parson-a1c356e/Makefile | 20 + parson-a1c356e/README.md | 146 ++ parson-a1c356e/package.json | 12 + parson-a1c356e/parson.c | 1795 ++++++++++++++++++++++ parson-a1c356e/parson.h | 231 +++ parson-a1c356e/tests.c | 572 +++++++ parson-a1c356e/tests/test_1_1.txt | 59 + parson-a1c356e/tests/test_1_2.txt | 1 + parson-a1c356e/tests/test_1_3.txt | 6 + parson-a1c356e/tests/test_2.txt | 31 + parson-a1c356e/tests/test_2_comments.txt | 41 + parson-a1c356e/tests/test_2_pretty.txt | 49 + parson-a1c356e/tests/test_5.txt | 15 + 16 files changed, 2980 insertions(+), 8 deletions(-) mode change 160000 => 120000 parson create mode 100644 parson-a1c356e/Makefile create mode 100644 parson-a1c356e/README.md create mode 100644 parson-a1c356e/package.json create mode 100644 parson-a1c356e/parson.c create mode 100644 parson-a1c356e/parson.h create mode 100644 parson-a1c356e/tests.c create mode 100644 parson-a1c356e/tests/test_1_1.txt create mode 100644 parson-a1c356e/tests/test_1_2.txt create mode 100644 parson-a1c356e/tests/test_1_3.txt create mode 100644 parson-a1c356e/tests/test_2.txt create mode 100644 parson-a1c356e/tests/test_2_comments.txt create mode 100644 parson-a1c356e/tests/test_2_pretty.txt create mode 100644 parson-a1c356e/tests/test_5.txt diff --git a/.gitmodules b/.gitmodules index 39d0d29..e69de29 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +0,0 @@ -[submodule "parson"] - path = parson - url = https://github.com/kgabis/parson.git diff --git a/README.md b/README.md index 5272f44..d6e114e 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,6 @@ The `Makefile` builds the library and a simple test. The implementation relies on a small JSON library that is included as a Git submodule. This needs to be initialised: - make parson make make test @@ -28,9 +27,7 @@ needs to be initialised: ## Parson The JSON library [Parson](https://github.com/kgabis/parson.git) is -included as a Git submodule. A submodule points to a specific commit in -an external repository and does not track its master branch as this -advances. Instead, it needs to be updated explicitly. +included as a copy of the source code. ## Documentation - Overview diff --git a/parson b/parson deleted file mode 160000 index a1c356e..0000000 --- a/parson +++ /dev/null @@ -1 +0,0 @@ -Subproject commit a1c356eaa9a0a2b31d05b7ff597b570afb421165 diff --git a/parson b/parson new file mode 120000 index 0000000..9a2ff6c --- /dev/null +++ b/parson @@ -0,0 +1 @@ +parson-a1c356e \ No newline at end of file diff --git a/parson-a1c356e/Makefile b/parson-a1c356e/Makefile new file mode 100644 index 0000000..98654de --- /dev/null +++ b/parson-a1c356e/Makefile @@ -0,0 +1,20 @@ +CC = gcc +CFLAGS = -O0 -g -Wall -Wextra -std=c89 -pedantic-errors + +CPPC = g++ +CPPFLAGS = -O0 -g -Wall -Wextra + +all: test testcpp + +.PHONY: test testcpp +test: tests.c parson.c + $(CC) $(CFLAGS) -o $@ tests.c parson.c + ./$@ + +testcpp: tests.c parson.c + $(CPPC) $(CPPFLAGS) -o $@ tests.c parson.c + ./$@ + +clean: + rm -f test *.o + diff --git a/parson-a1c356e/README.md b/parson-a1c356e/README.md new file mode 100644 index 0000000..33c7310 --- /dev/null +++ b/parson-a1c356e/README.md @@ -0,0 +1,146 @@ +##About +Parson is a lighweight [json](http://json.org) library written in C. + +##Features +* Full JSON support +* Lightweight (only 2 files) +* Simple API +* Addressing json values with dot notation (similiar to C structs or objects in most OO languages, e.g. "objectA.objectB.value") +* C89 compatible +* Test suites + +##Installation +Run: +``` +git clone https://github.com/kgabis/parson.git +``` +and copy parson.h and parson.c to you source code tree. + +Run ```make test``` to compile and run tests. + +##Examples +###Parsing JSON +Here is a function, which prints basic commit info (date, sha and author) from a github repository. +```c +void print_commits_info(const char *username, const char *repo) { + JSON_Value *root_value; + JSON_Array *commits; + JSON_Object *commit; + size_t i; + + char curl_command[512]; + char cleanup_command[256]; + char output_filename[] = "commits.json"; + + /* it ain't pretty, but it's not a libcurl tutorial */ + sprintf(curl_command, + "curl -s \"https://api.github.com/repos/%s/%s/commits\" > %s", + username, repo, output_filename); + sprintf(cleanup_command, "rm -f %s", output_filename); + system(curl_command); + + /* parsing json and validating output */ + root_value = json_parse_file(output_filename); + if (json_value_get_type(root_value) != JSONArray) { + system(cleanup_command); + return; + } + + /* getting array from root value and printing commit info */ + commits = json_value_get_array(root_value); + printf("%-10.10s %-10.10s %s\n", "Date", "SHA", "Author"); + for (i = 0; i < json_array_get_count(commits); i++) { + commit = json_array_get_object(commits, i); + printf("%.10s %.10s %s\n", + json_object_dotget_string(commit, "commit.author.date"), + json_object_get_string(commit, "sha"), + json_object_dotget_string(commit, "commit.author.name")); + } + + /* cleanup code */ + json_value_free(root_value); + system(cleanup_command); +} + +``` +Calling ```print_commits_info("torvalds", "linux");``` prints: +``` +Date SHA Author +2012-10-15 dd8e8c4a2c David Rientjes +2012-10-15 3ce9e53e78 Michal Marek +2012-10-14 29bb4cc5e0 Randy Dunlap +2012-10-15 325adeb55e Ralf Baechle +2012-10-14 68687c842c Russell King +2012-10-14 ddffeb8c4d Linus Torvalds +... +``` + +###Persistence +In this example I'm using parson to save user information to a file and then load it and validate later. +```c +void persistence_example(void) { + JSON_Value *schema = json_parse_string("{\"name\":\"\"}"); + JSON_Value *user_data = json_parse_file("user_data.json"); + char buf[256]; + const char *name = NULL; + if (user_data == NULL || json_validate(schema, user_data) != JSONSuccess) { + puts("Enter your name:"); + scanf("%s", buf); + user_data = json_value_init_object(); + json_object_set_string(json_object(user_data), "name", buf); + json_serialize_to_file(user_data, "user_data.json"); + } + name = json_object_get_string(json_object(user_data), "name"); + printf("Hello, %s.", name); + json_value_free(schema); + json_value_free(user_data); + return; +} +``` + +###Serialization +Creating JSON values is very simple thanks to the dot notation. +Object hierarchy is automatically created when addressing specific fields. +In the following example I create a simple JSON value containing basic information about a person. +```c +void serialization_example(void) { + JSON_Value *root_value = json_value_init_object(); + JSON_Object *root_object = json_value_get_object(root_value); + char *serialized_string = NULL; + json_object_set_string(root_object, "name", "John Smith"); + json_object_set_number(root_object, "age", 25); + json_object_dotset_string(root_object, "address.city", "Cupertino"); + json_object_dotset_value(root_object, "contact.emails", json_parse_string("[\"email@example.com\",\"email2@example.com\"]")); + serialized_string = json_serialize_to_string_pretty(root_value); + puts(serialized_string); + json_free_serialized_string(serialized_string); + json_value_free(root_value); +} + +``` + +Output: +``` +{ + "name": "John Smith", + "age": 25, + "address": { + "city": "Cupertino" + }, + "contact": { + "emails": [ + "email@example.com", + "email2@example.com" + ] + } +} +``` + +##Contributing + +I will always merge *working* bug fixes. However, if you want to add something to the API, +I *won't* merge it without prior discussion. +Remember to follow parson's code style and write appropriate tests. + +##License +[The MIT License (MIT)](http://opensource.org/licenses/mit-license.php) \ No newline at end of file diff --git a/parson-a1c356e/package.json b/parson-a1c356e/package.json new file mode 100644 index 0000000..76f80da --- /dev/null +++ b/parson-a1c356e/package.json @@ -0,0 +1,12 @@ +{ + "name": "parson", + "version": "0.0.0", + "repo": "kgabis/parson", + "description": "Small json parser and reader", + "keywords": [ "json", "parser" ], + "license": "MIT", + "src": [ + "parson.c", + "parson.h" + ] +} diff --git a/parson-a1c356e/parson.c b/parson-a1c356e/parson.c new file mode 100644 index 0000000..4727324 --- /dev/null +++ b/parson-a1c356e/parson.c @@ -0,0 +1,1795 @@ +/* + Parson ( http://kgabis.github.com/parson/ ) + Copyright (c) 2012 - 2016 Krzysztof Gabis + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ +#ifdef _MSC_VER +#ifndef _CRT_SECURE_NO_WARNINGS +#define _CRT_SECURE_NO_WARNINGS +#endif /* _CRT_SECURE_NO_WARNINGS */ +#endif /* _MSC_VER */ + +#include "parson.h" + +#include +#include +#include +#include +#include + +#define STARTING_CAPACITY 15 +#define ARRAY_MAX_CAPACITY 122880 /* 15*(2^13) */ +#define OBJECT_MAX_CAPACITY 960 /* 15*(2^6) */ +#define MAX_NESTING 19 +#define DOUBLE_SERIALIZATION_FORMAT "%f" + +#define SIZEOF_TOKEN(a) (sizeof(a) - 1) +#define SKIP_CHAR(str) ((*str)++) +#define SKIP_WHITESPACES(str) while (isspace(**str)) { SKIP_CHAR(str); } +#define MAX(a, b) ((a) > (b) ? (a) : (b)) + +#undef malloc +#undef free + +static JSON_Malloc_Function parson_malloc = malloc; +static JSON_Free_Function parson_free = free; + +#define IS_CONT(b) (((unsigned char)(b) & 0xC0) == 0x80) /* is utf-8 continuation byte */ + +/* Type definitions */ +typedef union json_value_value { + char *string; + double number; + JSON_Object *object; + JSON_Array *array; + int boolean; + int null; +} JSON_Value_Value; + +struct json_value_t { + JSON_Value_Type type; + JSON_Value_Value value; +}; + +struct json_object_t { + char **names; + JSON_Value **values; + size_t count; + size_t capacity; +}; + +struct json_array_t { + JSON_Value **items; + size_t count; + size_t capacity; +}; + +/* Various */ +static char * read_file(const char *filename); +static void remove_comments(char *string, const char *start_token, const char *end_token); +static char * parson_strndup(const char *string, size_t n); +static char * parson_strdup(const char *string); +static int is_utf16_hex(const unsigned char *string); +static int num_bytes_in_utf8_sequence(unsigned char c); +static int verify_utf8_sequence(const unsigned char *string, int *len); +static int is_valid_utf8(const char *string, size_t string_len); +static int is_decimal(const char *string, size_t length); + +/* JSON Object */ +static JSON_Object * json_object_init(void); +static JSON_Status json_object_add(JSON_Object *object, const char *name, JSON_Value *value); +static JSON_Status json_object_resize(JSON_Object *object, size_t new_capacity); +static JSON_Value * json_object_nget_value(const JSON_Object *object, const char *name, size_t n); +static void json_object_free(JSON_Object *object); + +/* JSON Array */ +static JSON_Array * json_array_init(void); +static JSON_Status json_array_add(JSON_Array *array, JSON_Value *value); +static JSON_Status json_array_resize(JSON_Array *array, size_t new_capacity); +static void json_array_free(JSON_Array *array); + +/* JSON Value */ +static JSON_Value * json_value_init_string_no_copy(char *string); + +/* Parser */ +static JSON_Status skip_quotes(const char **string); +static int parse_utf_16(const char **unprocessed, char **processed); +static char * process_string(const char *input, size_t len); +static char * get_quoted_string(const char **string); +static JSON_Value * parse_object_value(const char **string, size_t nesting); +static JSON_Value * parse_array_value(const char **string, size_t nesting); +static JSON_Value * parse_string_value(const char **string); +static JSON_Value * parse_boolean_value(const char **string); +static JSON_Value * parse_number_value(const char **string); +static JSON_Value * parse_null_value(const char **string); +static JSON_Value * parse_value(const char **string, size_t nesting); + +/* Serialization */ +static int json_serialize_to_buffer_r(const JSON_Value *value, char *buf, int level, int is_pretty, char *num_buf); +static int json_serialize_string(const char *string, char *buf); +static int append_indent(char *buf, int level); +static int append_string(char *buf, const char *string); + +/* Various */ +static char * parson_strndup(const char *string, size_t n) { + char *output_string = (char*)parson_malloc(n + 1); + if (!output_string) + return NULL; + output_string[n] = '\0'; + strncpy(output_string, string, n); + return output_string; +} + +static char * parson_strdup(const char *string) { + return parson_strndup(string, strlen(string)); +} + +static int is_utf16_hex(const unsigned char *s) { + return isxdigit(s[0]) && isxdigit(s[1]) && isxdigit(s[2]) && isxdigit(s[3]); +} + +static int num_bytes_in_utf8_sequence(unsigned char c) { + if (c == 0xC0 || c == 0xC1 || c > 0xF4 || IS_CONT(c)) { + return 0; + } else if ((c & 0x80) == 0) { /* 0xxxxxxx */ + return 1; + } else if ((c & 0xE0) == 0xC0) { /* 110xxxxx */ + return 2; + } else if ((c & 0xF0) == 0xE0) { /* 1110xxxx */ + return 3; + } else if ((c & 0xF8) == 0xF0) { /* 11110xxx */ + return 4; + } + return 0; /* won't happen */ +} + +static int verify_utf8_sequence(const unsigned char *string, int *len) { + unsigned int cp = 0; + *len = num_bytes_in_utf8_sequence(string[0]); + + if (*len == 1) { + cp = string[0]; + } else if (*len == 2 && IS_CONT(string[1])) { + cp = string[0] & 0x1F; + cp = (cp << 6) | (string[1] & 0x3F); + } else if (*len == 3 && IS_CONT(string[1]) && IS_CONT(string[2])) { + cp = ((unsigned char)string[0]) & 0xF; + cp = (cp << 6) | (string[1] & 0x3F); + cp = (cp << 6) | (string[2] & 0x3F); + } else if (*len == 4 && IS_CONT(string[1]) && IS_CONT(string[2]) && IS_CONT(string[3])) { + cp = string[0] & 0x7; + cp = (cp << 6) | (string[1] & 0x3F); + cp = (cp << 6) | (string[2] & 0x3F); + cp = (cp << 6) | (string[3] & 0x3F); + } else { + return 0; + } + + /* overlong encodings */ + if ((cp < 0x80 && *len > 1) || + (cp < 0x800 && *len > 2) || + (cp < 0x10000 && *len > 3)) { + return 0; + } + + /* invalid unicode */ + if (cp > 0x10FFFF) { + return 0; + } + + /* surrogate halves */ + if (cp >= 0xD800 && cp <= 0xDFFF) { + return 0; + } + + return 1; +} + +static int is_valid_utf8(const char *string, size_t string_len) { + int len = 0; + const char *string_end = string + string_len; + while (string < string_end) { + if (!verify_utf8_sequence((const unsigned char*)string, &len)) { + return 0; + } + string += len; + } + return 1; +} + +static int is_decimal(const char *string, size_t length) { + if (length > 1 && string[0] == '0' && string[1] != '.') + return 0; + if (length > 2 && !strncmp(string, "-0", 2) && string[2] != '.') + return 0; + while (length--) + if (strchr("xX", string[length])) + return 0; + return 1; +} + +static char * read_file(const char * filename) { + FILE *fp = fopen(filename, "r"); + size_t file_size; + long pos; + char *file_contents; + if (!fp) + return NULL; + fseek(fp, 0L, SEEK_END); + pos = ftell(fp); + if (pos < 0) { + fclose(fp); + return NULL; + } + file_size = pos; + rewind(fp); + file_contents = (char*)parson_malloc(sizeof(char) * (file_size + 1)); + if (!file_contents) { + fclose(fp); + return NULL; + } + if (fread(file_contents, file_size, 1, fp) < 1) { + if (ferror(fp)) { + fclose(fp); + parson_free(file_contents); + return NULL; + } + } + fclose(fp); + file_contents[file_size] = '\0'; + return file_contents; +} + +static void remove_comments(char *string, const char *start_token, const char *end_token) { + int in_string = 0, escaped = 0; + size_t i; + char *ptr = NULL, current_char; + size_t start_token_len = strlen(start_token); + size_t end_token_len = strlen(end_token); + if (start_token_len == 0 || end_token_len == 0) + return; + while ((current_char = *string) != '\0') { + if (current_char == '\\' && !escaped) { + escaped = 1; + string++; + continue; + } else if (current_char == '\"' && !escaped) { + in_string = !in_string; + } else if (!in_string && strncmp(string, start_token, start_token_len) == 0) { + for(i = 0; i < start_token_len; i++) + string[i] = ' '; + string = string + start_token_len; + ptr = strstr(string, end_token); + if (!ptr) + return; + for (i = 0; i < (ptr - string) + end_token_len; i++) + string[i] = ' '; + string = ptr + end_token_len - 1; + } + escaped = 0; + string++; + } +} + +/* JSON Object */ +static JSON_Object * json_object_init(void) { + JSON_Object *new_obj = (JSON_Object*)parson_malloc(sizeof(JSON_Object)); + if (!new_obj) + return NULL; + new_obj->names = (char**)NULL; + new_obj->values = (JSON_Value**)NULL; + new_obj->capacity = 0; + new_obj->count = 0; + return new_obj; +} + +static JSON_Status json_object_add(JSON_Object *object, const char *name, JSON_Value *value) { + size_t index = 0; + if (object == NULL || name == NULL || value == NULL) { + return JSONFailure; + } + if (json_object_get_value(object, name) != NULL) { + return JSONFailure; + } + if (object->count >= object->capacity) { + size_t new_capacity = MAX(object->capacity * 2, STARTING_CAPACITY); + if (new_capacity > OBJECT_MAX_CAPACITY) + return JSONFailure; + if (json_object_resize(object, new_capacity) == JSONFailure) + return JSONFailure; + } + index = object->count; + object->names[index] = parson_strdup(name); + if (object->names[index] == NULL) + return JSONFailure; + object->values[index] = value; + object->count++; + return JSONSuccess; +} + +static JSON_Status json_object_resize(JSON_Object *object, size_t new_capacity) { + char **temp_names = NULL; + JSON_Value **temp_values = NULL; + + if ((object->names == NULL && object->values != NULL) || + (object->names != NULL && object->values == NULL) || + new_capacity == 0) { + return JSONFailure; /* Shouldn't happen */ + } + + temp_names = (char**)parson_malloc(new_capacity * sizeof(char*)); + if (temp_names == NULL) + return JSONFailure; + + temp_values = (JSON_Value**)parson_malloc(new_capacity * sizeof(JSON_Value*)); + if (temp_values == NULL) { + parson_free(temp_names); + return JSONFailure; + } + + if (object->names != NULL && object->values != NULL && object->count > 0) { + memcpy(temp_names, object->names, object->count * sizeof(char*)); + memcpy(temp_values, object->values, object->count * sizeof(JSON_Value*)); + } + parson_free(object->names); + parson_free(object->values); + object->names = temp_names; + object->values = temp_values; + object->capacity = new_capacity; + return JSONSuccess; +} + +static JSON_Value * json_object_nget_value(const JSON_Object *object, const char *name, size_t n) { + size_t i, name_length; + for (i = 0; i < json_object_get_count(object); i++) { + name_length = strlen(object->names[i]); + if (name_length != n) + continue; + if (strncmp(object->names[i], name, n) == 0) + return object->values[i]; + } + return NULL; +} + +static void json_object_free(JSON_Object *object) { + while(object->count--) { + parson_free(object->names[object->count]); + json_value_free(object->values[object->count]); + } + parson_free(object->names); + parson_free(object->values); + parson_free(object); +} + +/* JSON Array */ +static JSON_Array * json_array_init(void) { + JSON_Array *new_array = (JSON_Array*)parson_malloc(sizeof(JSON_Array)); + if (!new_array) + return NULL; + new_array->items = (JSON_Value**)NULL; + new_array->capacity = 0; + new_array->count = 0; + return new_array; +} + +static JSON_Status json_array_add(JSON_Array *array, JSON_Value *value) { + if (array->count >= array->capacity) { + size_t new_capacity = MAX(array->capacity * 2, STARTING_CAPACITY); + if (new_capacity > ARRAY_MAX_CAPACITY) + return JSONFailure; + if (json_array_resize(array, new_capacity) == JSONFailure) + return JSONFailure; + } + array->items[array->count] = value; + array->count++; + return JSONSuccess; +} + +static JSON_Status json_array_resize(JSON_Array *array, size_t new_capacity) { + JSON_Value **new_items = NULL; + if (new_capacity == 0) { + return JSONFailure; + } + new_items = (JSON_Value**)parson_malloc(new_capacity * sizeof(JSON_Value*)); + if (new_items == NULL) { + return JSONFailure; + } + if (array->items != NULL && array->count > 0) { + memcpy(new_items, array->items, array->count * sizeof(JSON_Value*)); + } + parson_free(array->items); + array->items = new_items; + array->capacity = new_capacity; + return JSONSuccess; +} + +static void json_array_free(JSON_Array *array) { + while (array->count--) + json_value_free(array->items[array->count]); + parson_free(array->items); + parson_free(array); +} + +/* JSON Value */ +static JSON_Value * json_value_init_string_no_copy(char *string) { + JSON_Value *new_value = (JSON_Value*)parson_malloc(sizeof(JSON_Value)); + if (!new_value) + return NULL; + new_value->type = JSONString; + new_value->value.string = string; + return new_value; +} + +/* Parser */ +static JSON_Status skip_quotes(const char **string) { + if (**string != '\"') { + return JSONFailure; + } + SKIP_CHAR(string); + while (**string != '\"') { + if (**string == '\0') { + return JSONFailure; + } else if (**string == '\\') { + SKIP_CHAR(string); + if (**string == '\0') { + return JSONFailure; + } + } + SKIP_CHAR(string); + } + SKIP_CHAR(string); + return JSONSuccess; +} + +static int parse_utf_16(const char **unprocessed, char **processed) { + unsigned int cp, lead, trail; + char *processed_ptr = *processed; + const char *unprocessed_ptr = *unprocessed; + unprocessed_ptr++; /* skips u */ + if (!is_utf16_hex((const unsigned char*)unprocessed_ptr) || sscanf(unprocessed_ptr, "%4x", &cp) == EOF) + return JSONFailure; + if (cp < 0x80) { + *processed_ptr = cp; /* 0xxxxxxx */ + } else if (cp < 0x800) { + *processed_ptr++ = ((cp >> 6) & 0x1F) | 0xC0; /* 110xxxxx */ + *processed_ptr = ((cp ) & 0x3F) | 0x80; /* 10xxxxxx */ + } else if (cp < 0xD800 || cp > 0xDFFF) { + *processed_ptr++ = ((cp >> 12) & 0x0F) | 0xE0; /* 1110xxxx */ + *processed_ptr++ = ((cp >> 6) & 0x3F) | 0x80; /* 10xxxxxx */ + *processed_ptr = ((cp ) & 0x3F) | 0x80; /* 10xxxxxx */ + } else if (cp >= 0xD800 && cp <= 0xDBFF) { /* lead surrogate (0xD800..0xDBFF) */ + lead = cp; + unprocessed_ptr += 4; /* should always be within the buffer, otherwise previous sscanf would fail */ + if (*unprocessed_ptr++ != '\\' || *unprocessed_ptr++ != 'u' || /* starts with \u? */ + !is_utf16_hex((const unsigned char*)unprocessed_ptr) || + sscanf(unprocessed_ptr, "%4x", &trail) == EOF || + trail < 0xDC00 || trail > 0xDFFF) { /* valid trail surrogate? (0xDC00..0xDFFF) */ + return JSONFailure; + } + cp = ((((lead-0xD800)&0x3FF)<<10)|((trail-0xDC00)&0x3FF))+0x010000; + *processed_ptr++ = (((cp >> 18) & 0x07) | 0xF0); /* 11110xxx */ + *processed_ptr++ = (((cp >> 12) & 0x3F) | 0x80); /* 10xxxxxx */ + *processed_ptr++ = (((cp >> 6) & 0x3F) | 0x80); /* 10xxxxxx */ + *processed_ptr = (((cp ) & 0x3F) | 0x80); /* 10xxxxxx */ + } else { /* trail surrogate before lead surrogate */ + return JSONFailure; + } + unprocessed_ptr += 3; + *processed = processed_ptr; + *unprocessed = unprocessed_ptr; + return JSONSuccess; +} + + +/* Copies and processes passed string up to supplied length. +Example: "\u006Corem ipsum" -> lorem ipsum */ +static char* process_string(const char *input, size_t len) { + const char *input_ptr = input; + size_t initial_size = (len + 1) * sizeof(char); + size_t final_size = 0; + char *output = (char*)parson_malloc(initial_size); + char *output_ptr = output; + char *resized_output = NULL; + while ((*input_ptr != '\0') && (size_t)(input_ptr - input) < len) { + if (*input_ptr == '\\') { + input_ptr++; + switch (*input_ptr) { + case '\"': *output_ptr = '\"'; break; + case '\\': *output_ptr = '\\'; break; + case '/': *output_ptr = '/'; break; + case 'b': *output_ptr = '\b'; break; + case 'f': *output_ptr = '\f'; break; + case 'n': *output_ptr = '\n'; break; + case 'r': *output_ptr = '\r'; break; + case 't': *output_ptr = '\t'; break; + case 'u': + if (parse_utf_16(&input_ptr, &output_ptr) == JSONFailure) + goto error; + break; + default: + goto error; + } + } else if ((unsigned char)*input_ptr < 0x20) { + goto error; /* 0x00-0x19 are invalid characters for json string (http://www.ietf.org/rfc/rfc4627.txt) */ + } else { + *output_ptr = *input_ptr; + } + output_ptr++; + input_ptr++; + } + *output_ptr = '\0'; + /* resize to new length */ + final_size = (size_t)(output_ptr-output) + 1; + /* todo: don't resize if final_size == initial_size */ + resized_output = (char*)parson_malloc(final_size); + if (resized_output == NULL) + goto error; + memcpy(resized_output, output, final_size); + parson_free(output); + return resized_output; +error: + parson_free(output); + return NULL; +} + +/* Return processed contents of a string between quotes and + skips passed argument to a matching quote. */ +static char * get_quoted_string(const char **string) { + const char *string_start = *string; + size_t string_len = 0; + JSON_Status status = skip_quotes(string); + if (status != JSONSuccess) { + return NULL; + } + string_len = *string - string_start - 2; /* length without quotes */ + return process_string(string_start + 1, string_len); +} + +static JSON_Value * parse_value(const char **string, size_t nesting) { + if (nesting > MAX_NESTING) + return NULL; + SKIP_WHITESPACES(string); + switch (**string) { + case '{': + return parse_object_value(string, nesting + 1); + case '[': + return parse_array_value(string, nesting + 1); + case '\"': + return parse_string_value(string); + case 'f': case 't': + return parse_boolean_value(string); + case '-': + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + return parse_number_value(string); + case 'n': + return parse_null_value(string); + default: + return NULL; + } +} + +static JSON_Value * parse_object_value(const char **string, size_t nesting) { + JSON_Value *output_value = json_value_init_object(), *new_value = NULL; + JSON_Object *output_object = json_value_get_object(output_value); + char *new_key = NULL; + if (output_value == NULL || **string != '{') + return NULL; + SKIP_CHAR(string); + SKIP_WHITESPACES(string); + if (**string == '}') { /* empty object */ + SKIP_CHAR(string); + return output_value; + } + while (**string != '\0') { + new_key = get_quoted_string(string); + SKIP_WHITESPACES(string); + if (new_key == NULL || **string != ':') { + json_value_free(output_value); + return NULL; + } + SKIP_CHAR(string); + new_value = parse_value(string, nesting); + if (new_value == NULL) { + parson_free(new_key); + json_value_free(output_value); + return NULL; + } + if(json_object_add(output_object, new_key, new_value) == JSONFailure) { + parson_free(new_key); + parson_free(new_value); + json_value_free(output_value); + return NULL; + } + parson_free(new_key); + SKIP_WHITESPACES(string); + if (**string != ',') + break; + SKIP_CHAR(string); + SKIP_WHITESPACES(string); + } + SKIP_WHITESPACES(string); + if (**string != '}' || /* Trim object after parsing is over */ + json_object_resize(output_object, json_object_get_count(output_object)) == JSONFailure) { + json_value_free(output_value); + return NULL; + } + SKIP_CHAR(string); + return output_value; +} + +static JSON_Value * parse_array_value(const char **string, size_t nesting) { + JSON_Value *output_value = json_value_init_array(), *new_array_value = NULL; + JSON_Array *output_array = json_value_get_array(output_value); + if (!output_value || **string != '[') + return NULL; + SKIP_CHAR(string); + SKIP_WHITESPACES(string); + if (**string == ']') { /* empty array */ + SKIP_CHAR(string); + return output_value; + } + while (**string != '\0') { + new_array_value = parse_value(string, nesting); + if (!new_array_value) { + json_value_free(output_value); + return NULL; + } + if(json_array_add(output_array, new_array_value) == JSONFailure) { + parson_free(new_array_value); + json_value_free(output_value); + return NULL; + } + SKIP_WHITESPACES(string); + if (**string != ',') + break; + SKIP_CHAR(string); + SKIP_WHITESPACES(string); + } + SKIP_WHITESPACES(string); + if (**string != ']' || /* Trim array after parsing is over */ + json_array_resize(output_array, json_array_get_count(output_array)) == JSONFailure) { + json_value_free(output_value); + return NULL; + } + SKIP_CHAR(string); + return output_value; +} + +static JSON_Value * parse_string_value(const char **string) { + JSON_Value *value = NULL; + char *new_string = get_quoted_string(string); + if (new_string == NULL) + return NULL; + value = json_value_init_string_no_copy(new_string); + if (value == NULL) { + parson_free(new_string); + return NULL; + } + return value; +} + +static JSON_Value * parse_boolean_value(const char **string) { + size_t true_token_size = SIZEOF_TOKEN("true"); + size_t false_token_size = SIZEOF_TOKEN("false"); + if (strncmp("true", *string, true_token_size) == 0) { + *string += true_token_size; + return json_value_init_boolean(1); + } else if (strncmp("false", *string, false_token_size) == 0) { + *string += false_token_size; + return json_value_init_boolean(0); + } + return NULL; +} + +static JSON_Value * parse_number_value(const char **string) { + char *end; + double number = strtod(*string, &end); + JSON_Value *output_value; + if (is_decimal(*string, end - *string)) { + *string = end; + output_value = json_value_init_number(number); + } else { + output_value = NULL; + } + return output_value; +} + +static JSON_Value * parse_null_value(const char **string) { + size_t token_size = SIZEOF_TOKEN("null"); + if (strncmp("null", *string, token_size) == 0) { + *string += token_size; + return json_value_init_null(); + } + return NULL; +} + +/* Serialization */ +#define APPEND_STRING(str) do { written = append_string(buf, (str));\ + if (written < 0) { return -1; }\ + if (buf != NULL) { buf += written; }\ + written_total += written; } while(0) + +#define APPEND_INDENT(level) do { written = append_indent(buf, (level));\ + if (written < 0) { return -1; }\ + if (buf != NULL) { buf += written; }\ + written_total += written; } while(0) + +static int json_serialize_to_buffer_r(const JSON_Value *value, char *buf, int level, int is_pretty, char *num_buf) +{ + const char *key = NULL, *string = NULL; + JSON_Value *temp_value = NULL; + JSON_Array *array = NULL; + JSON_Object *object = NULL; + size_t i = 0, count = 0; + double num = 0.0; + int written = -1, written_total = 0; + + switch (json_value_get_type(value)) { + case JSONArray: + array = json_value_get_array(value); + count = json_array_get_count(array); + APPEND_STRING("["); + if (count > 0 && is_pretty) + APPEND_STRING("\n"); + for (i = 0; i < count; i++) { + if (is_pretty) + APPEND_INDENT(level+1); + temp_value = json_array_get_value(array, i); + written = json_serialize_to_buffer_r(temp_value, buf, level+1, is_pretty, num_buf); + if (written < 0) + return -1; + if (buf != NULL) + buf += written; + written_total += written; + if (i < (count - 1)) + APPEND_STRING(","); + if (is_pretty) + APPEND_STRING("\n"); + } + if (count > 0 && is_pretty) + APPEND_INDENT(level); + APPEND_STRING("]"); + return written_total; + case JSONObject: + object = json_value_get_object(value); + count = json_object_get_count(object); + APPEND_STRING("{"); + if (count > 0 && is_pretty) + APPEND_STRING("\n"); + for (i = 0; i < count; i++) { + key = json_object_get_name(object, i); + if (is_pretty) + APPEND_INDENT(level+1); + written = json_serialize_string(key, buf); + if (written < 0) + return -1; + if (buf != NULL) + buf += written; + written_total += written; + APPEND_STRING(":"); + if (is_pretty) + APPEND_STRING(" "); + temp_value = json_object_get_value(object, key); + written = json_serialize_to_buffer_r(temp_value, buf, level+1, is_pretty, num_buf); + if (written < 0) + return -1; + if (buf != NULL) + buf += written; + written_total += written; + if (i < (count - 1)) + APPEND_STRING(","); + if (is_pretty) + APPEND_STRING("\n"); + } + if (count > 0 && is_pretty) + APPEND_INDENT(level); + APPEND_STRING("}"); + return written_total; + case JSONString: + string = json_value_get_string(value); + written = json_serialize_string(string, buf); + if (written < 0) + return -1; + if (buf != NULL) + buf += written; + written_total += written; + return written_total; + case JSONBoolean: + if (json_value_get_boolean(value)) + APPEND_STRING("true"); + else + APPEND_STRING("false"); + return written_total; + case JSONNumber: + num = json_value_get_number(value); + if (buf != NULL) + num_buf = buf; + if (num == ((double)(int)num)) /* check if num is integer */ + written = sprintf(num_buf, "%d", (int)num); + else + written = sprintf(num_buf, DOUBLE_SERIALIZATION_FORMAT, num); + if (written < 0) + return -1; + if (buf != NULL) + buf += written; + written_total += written; + return written_total; + case JSONNull: + APPEND_STRING("null"); + return written_total; + case JSONError: + return -1; + default: + return -1; + } +} + +static int json_serialize_string(const char *string, char *buf) { + size_t i = 0, len = strlen(string); + char c = '\0'; + int written = -1, written_total = 0; + APPEND_STRING("\""); + for (i = 0; i < len; i++) { + c = string[i]; + switch (c) { + case '\"': APPEND_STRING("\\\""); break; + case '\\': APPEND_STRING("\\\\"); break; + case '/': APPEND_STRING("\\/"); break; /* to make json embeddable in xml\/html */ + case '\b': APPEND_STRING("\\b"); break; + case '\f': APPEND_STRING("\\f"); break; + case '\n': APPEND_STRING("\\n"); break; + case '\r': APPEND_STRING("\\r"); break; + case '\t': APPEND_STRING("\\t"); break; + default: + if (buf != NULL) { + buf[0] = c; + buf += 1; + } + written_total += 1; + break; + } + } + APPEND_STRING("\""); + return written_total; +} + +static int append_indent(char *buf, int level) { + int i; + int written = -1, written_total = 0; + for (i = 0; i < level; i++) { + APPEND_STRING(" "); + } + return written_total; +} + +static int append_string(char *buf, const char *string) { + if (buf == NULL) { + return (int)strlen(string); + } + return sprintf(buf, "%s", string); +} + +#undef APPEND_STRING +#undef APPEND_INDENT + +/* Parser API */ +JSON_Value * json_parse_file(const char *filename) { + char *file_contents = read_file(filename); + JSON_Value *output_value = NULL; + if (file_contents == NULL) + return NULL; + output_value = json_parse_string(file_contents); + parson_free(file_contents); + return output_value; +} + +JSON_Value * json_parse_file_with_comments(const char *filename) { + char *file_contents = read_file(filename); + JSON_Value *output_value = NULL; + if (file_contents == NULL) + return NULL; + output_value = json_parse_string_with_comments(file_contents); + parson_free(file_contents); + return output_value; +} + +JSON_Value * json_parse_string(const char *string) { + if (string == NULL) + return NULL; + return parse_value((const char**)&string, 0); +} + +JSON_Value * json_parse_string_with_comments(const char *string) { + JSON_Value *result = NULL; + char *string_mutable_copy = NULL, *string_mutable_copy_ptr = NULL; + string_mutable_copy = parson_strdup(string); + if (string_mutable_copy == NULL) + return NULL; + remove_comments(string_mutable_copy, "/*", "*/"); + remove_comments(string_mutable_copy, "//", "\n"); + string_mutable_copy_ptr = string_mutable_copy; + result = parse_value((const char**)&string_mutable_copy_ptr, 0); + parson_free(string_mutable_copy); + return result; +} + +/* JSON Object API */ + +JSON_Value * json_object_get_value(const JSON_Object *object, const char *name) { + if (object == NULL || name == NULL) + return NULL; + return json_object_nget_value(object, name, strlen(name)); +} + +const char * json_object_get_string(const JSON_Object *object, const char *name) { + return json_value_get_string(json_object_get_value(object, name)); +} + +double json_object_get_number(const JSON_Object *object, const char *name) { + return json_value_get_number(json_object_get_value(object, name)); +} + +JSON_Object * json_object_get_object(const JSON_Object *object, const char *name) { + return json_value_get_object(json_object_get_value(object, name)); +} + +JSON_Array * json_object_get_array(const JSON_Object *object, const char *name) { + return json_value_get_array(json_object_get_value(object, name)); +} + +int json_object_get_boolean(const JSON_Object *object, const char *name) { + return json_value_get_boolean(json_object_get_value(object, name)); +} + +JSON_Value * json_object_dotget_value(const JSON_Object *object, const char *name) { + const char *dot_position = strchr(name, '.'); + if (!dot_position) + return json_object_get_value(object, name); + object = json_value_get_object(json_object_nget_value(object, name, dot_position - name)); + return json_object_dotget_value(object, dot_position + 1); +} + +const char * json_object_dotget_string(const JSON_Object *object, const char *name) { + return json_value_get_string(json_object_dotget_value(object, name)); +} + +double json_object_dotget_number(const JSON_Object *object, const char *name) { + return json_value_get_number(json_object_dotget_value(object, name)); +} + +JSON_Object * json_object_dotget_object(const JSON_Object *object, const char *name) { + return json_value_get_object(json_object_dotget_value(object, name)); +} + +JSON_Array * json_object_dotget_array(const JSON_Object *object, const char *name) { + return json_value_get_array(json_object_dotget_value(object, name)); +} + +int json_object_dotget_boolean(const JSON_Object *object, const char *name) { + return json_value_get_boolean(json_object_dotget_value(object, name)); +} + +size_t json_object_get_count(const JSON_Object *object) { + return object ? object->count : 0; +} + +const char * json_object_get_name(const JSON_Object *object, size_t index) { + if (object == NULL || index >= json_object_get_count(object)) + return NULL; + return object->names[index]; +} + +JSON_Value * json_object_get_value_at(const JSON_Object *object, size_t index) { + if (object == NULL || index >= json_object_get_count(object)) + return NULL; + return object->values[index]; +} + +int json_object_has_value (const JSON_Object *object, const char *name) { + return json_object_get_value(object, name) != NULL; +} + +int json_object_has_value_of_type(const JSON_Object *object, const char *name, JSON_Value_Type type) { + JSON_Value *val = json_object_get_value(object, name); + return val != NULL && json_value_get_type(val) == type; +} + +int json_object_dothas_value (const JSON_Object *object, const char *name) { + return json_object_dotget_value(object, name) != NULL; +} + +int json_object_dothas_value_of_type(const JSON_Object *object, const char *name, JSON_Value_Type type) { + JSON_Value *val = json_object_dotget_value(object, name); + return val != NULL && json_value_get_type(val) == type; +} + +/* JSON Array API */ +JSON_Value * json_array_get_value(const JSON_Array *array, size_t index) { + if (array == NULL || index >= json_array_get_count(array)) + return NULL; + return array->items[index]; +} + +const char * json_array_get_string(const JSON_Array *array, size_t index) { + return json_value_get_string(json_array_get_value(array, index)); +} + +double json_array_get_number(const JSON_Array *array, size_t index) { + return json_value_get_number(json_array_get_value(array, index)); +} + +JSON_Object * json_array_get_object(const JSON_Array *array, size_t index) { + return json_value_get_object(json_array_get_value(array, index)); +} + +JSON_Array * json_array_get_array(const JSON_Array *array, size_t index) { + return json_value_get_array(json_array_get_value(array, index)); +} + +int json_array_get_boolean(const JSON_Array *array, size_t index) { + return json_value_get_boolean(json_array_get_value(array, index)); +} + +size_t json_array_get_count(const JSON_Array *array) { + return array ? array->count : 0; +} + +/* JSON Value API */ +JSON_Value_Type json_value_get_type(const JSON_Value *value) { + return value ? value->type : JSONError; +} + +JSON_Object * json_value_get_object(const JSON_Value *value) { + return json_value_get_type(value) == JSONObject ? value->value.object : NULL; +} + +JSON_Array * json_value_get_array(const JSON_Value *value) { + return json_value_get_type(value) == JSONArray ? value->value.array : NULL; +} + +const char * json_value_get_string(const JSON_Value *value) { + return json_value_get_type(value) == JSONString ? value->value.string : NULL; +} + +double json_value_get_number(const JSON_Value *value) { + return json_value_get_type(value) == JSONNumber ? value->value.number : 0; +} + +int json_value_get_boolean(const JSON_Value *value) { + return json_value_get_type(value) == JSONBoolean ? value->value.boolean : -1; +} + +void json_value_free(JSON_Value *value) { + switch (json_value_get_type(value)) { + case JSONObject: + json_object_free(value->value.object); + break; + case JSONString: + if (value->value.string) { parson_free(value->value.string); } + break; + case JSONArray: + json_array_free(value->value.array); + break; + default: + break; + } + parson_free(value); +} + +JSON_Value * json_value_init_object(void) { + JSON_Value *new_value = (JSON_Value*)parson_malloc(sizeof(JSON_Value)); + if (!new_value) + return NULL; + new_value->type = JSONObject; + new_value->value.object = json_object_init(); + if (!new_value->value.object) { + parson_free(new_value); + return NULL; + } + return new_value; +} + +JSON_Value * json_value_init_array(void) { + JSON_Value *new_value = (JSON_Value*)parson_malloc(sizeof(JSON_Value)); + if (!new_value) + return NULL; + new_value->type = JSONArray; + new_value->value.array = json_array_init(); + if (!new_value->value.array) { + parson_free(new_value); + return NULL; + } + return new_value; +} + +JSON_Value * json_value_init_string(const char *string) { + char *copy = NULL; + JSON_Value *value; + size_t string_len = 0; + if (string == NULL) + return NULL; + string_len = strlen(string); + if (!is_valid_utf8(string, string_len)) + return NULL; + copy = parson_strndup(string, string_len); + if (copy == NULL) + return NULL; + value = json_value_init_string_no_copy(copy); + if (value == NULL) + parson_free(copy); + return value; +} + +JSON_Value * json_value_init_number(double number) { + JSON_Value *new_value = (JSON_Value*)parson_malloc(sizeof(JSON_Value)); + if (!new_value) + return NULL; + new_value->type = JSONNumber; + new_value->value.number = number; + return new_value; +} + +JSON_Value * json_value_init_boolean(int boolean) { + JSON_Value *new_value = (JSON_Value*)parson_malloc(sizeof(JSON_Value)); + if (!new_value) + return NULL; + new_value->type = JSONBoolean; + new_value->value.boolean = boolean ? 1 : 0; + return new_value; +} + +JSON_Value * json_value_init_null(void) { + JSON_Value *new_value = (JSON_Value*)parson_malloc(sizeof(JSON_Value)); + if (!new_value) + return NULL; + new_value->type = JSONNull; + return new_value; +} + +JSON_Value * json_value_deep_copy(const JSON_Value *value) { + size_t i = 0; + JSON_Value *return_value = NULL, *temp_value_copy = NULL, *temp_value = NULL; + const char *temp_string = NULL, *temp_key = NULL; + char *temp_string_copy = NULL; + JSON_Array *temp_array = NULL, *temp_array_copy = NULL; + JSON_Object *temp_object = NULL, *temp_object_copy = NULL; + + switch (json_value_get_type(value)) { + case JSONArray: + temp_array = json_value_get_array(value); + return_value = json_value_init_array(); + if (return_value == NULL) + return NULL; + temp_array_copy = json_value_get_array(return_value); + for (i = 0; i < json_array_get_count(temp_array); i++) { + temp_value = json_array_get_value(temp_array, i); + temp_value_copy = json_value_deep_copy(temp_value); + if (temp_value_copy == NULL) { + json_value_free(return_value); + return NULL; + } + if (json_array_add(temp_array_copy, temp_value_copy) == JSONFailure) { + json_value_free(return_value); + json_value_free(temp_value_copy); + return NULL; + } + } + return return_value; + case JSONObject: + temp_object = json_value_get_object(value); + return_value = json_value_init_object(); + if (return_value == NULL) + return NULL; + temp_object_copy = json_value_get_object(return_value); + for (i = 0; i < json_object_get_count(temp_object); i++) { + temp_key = json_object_get_name(temp_object, i); + temp_value = json_object_get_value(temp_object, temp_key); + temp_value_copy = json_value_deep_copy(temp_value); + if (temp_value_copy == NULL) { + json_value_free(return_value); + return NULL; + } + if (json_object_add(temp_object_copy, temp_key, temp_value_copy) == JSONFailure) { + json_value_free(return_value); + json_value_free(temp_value_copy); + return NULL; + } + } + return return_value; + case JSONBoolean: + return json_value_init_boolean(json_value_get_boolean(value)); + case JSONNumber: + return json_value_init_number(json_value_get_number(value)); + case JSONString: + temp_string = json_value_get_string(value); + temp_string_copy = parson_strdup(temp_string); + if (temp_string_copy == NULL) + return NULL; + return_value = json_value_init_string_no_copy(temp_string_copy); + if (return_value == NULL) + parson_free(temp_string_copy); + return return_value; + case JSONNull: + return json_value_init_null(); + case JSONError: + return NULL; + default: + return NULL; + } +} + +size_t json_serialization_size(const JSON_Value *value) { + char num_buf[1100]; /* recursively allocating buffer on stack is a bad idea, so let's do it only once */ + int res = json_serialize_to_buffer_r(value, NULL, 0, 0, num_buf); + return res < 0 ? 0 : (size_t)(res + 1); +} + +JSON_Status json_serialize_to_buffer(const JSON_Value *value, char *buf, size_t buf_size_in_bytes) { + int written = -1; + size_t needed_size_in_bytes = json_serialization_size(value); + if (needed_size_in_bytes == 0 || buf_size_in_bytes < needed_size_in_bytes) { + return JSONFailure; + } + written = json_serialize_to_buffer_r(value, buf, 0, 0, NULL); + if (written < 0) { + return JSONFailure; + } + return JSONSuccess; +} + +JSON_Status json_serialize_to_file(const JSON_Value *value, const char *filename) { + JSON_Status return_code = JSONSuccess; + FILE *fp = NULL; + char *serialized_string = json_serialize_to_string(value); + if (serialized_string == NULL) { + return JSONFailure; + } + fp = fopen (filename, "w"); + if (fp == NULL) { + json_free_serialized_string(serialized_string); + return JSONFailure; + } + if (fputs(serialized_string, fp) == EOF) { + return_code = JSONFailure; + } + if (fclose(fp) == EOF) { + return_code = JSONFailure; + } + json_free_serialized_string(serialized_string); + return return_code; +} + +char * json_serialize_to_string(const JSON_Value *value) { + JSON_Status serialization_result = JSONFailure; + size_t buf_size_bytes = json_serialization_size(value); + char *buf = NULL; + if (buf_size_bytes == 0) { + return NULL; + } + buf = (char*)parson_malloc(buf_size_bytes); + if (buf == NULL) + return NULL; + serialization_result = json_serialize_to_buffer(value, buf, buf_size_bytes); + if (serialization_result == JSONFailure) { + json_free_serialized_string(buf); + return NULL; + } + return buf; +} + +size_t json_serialization_size_pretty(const JSON_Value *value) { + char num_buf[1100]; /* recursively allocating buffer on stack is a bad idea, so let's do it only once */ + int res = json_serialize_to_buffer_r(value, NULL, 0, 1, num_buf); + return res < 0 ? 0 : (size_t)(res + 1); +} + +JSON_Status json_serialize_to_buffer_pretty(const JSON_Value *value, char *buf, size_t buf_size_in_bytes) { + int written = -1; + size_t needed_size_in_bytes = json_serialization_size_pretty(value); + if (needed_size_in_bytes == 0 || buf_size_in_bytes < needed_size_in_bytes) + return JSONFailure; + written = json_serialize_to_buffer_r(value, buf, 0, 1, NULL); + if (written < 0) + return JSONFailure; + return JSONSuccess; +} + +JSON_Status json_serialize_to_file_pretty(const JSON_Value *value, const char *filename) { + JSON_Status return_code = JSONSuccess; + FILE *fp = NULL; + char *serialized_string = json_serialize_to_string_pretty(value); + if (serialized_string == NULL) { + return JSONFailure; + } + fp = fopen (filename, "w"); + if (fp == NULL) { + json_free_serialized_string(serialized_string); + return JSONFailure; + } + if (fputs(serialized_string, fp) == EOF) { + return_code = JSONFailure; + } + if (fclose(fp) == EOF) { + return_code = JSONFailure; + } + json_free_serialized_string(serialized_string); + return return_code; +} + +char * json_serialize_to_string_pretty(const JSON_Value *value) { + JSON_Status serialization_result = JSONFailure; + size_t buf_size_bytes = json_serialization_size_pretty(value); + char *buf = NULL; + if (buf_size_bytes == 0) { + return NULL; + } + buf = (char*)parson_malloc(buf_size_bytes); + if (buf == NULL) + return NULL; + serialization_result = json_serialize_to_buffer_pretty(value, buf, buf_size_bytes); + if (serialization_result == JSONFailure) { + json_free_serialized_string(buf); + return NULL; + } + return buf; +} + +void json_free_serialized_string(char *string) { + parson_free(string); +} + +JSON_Status json_array_remove(JSON_Array *array, size_t ix) { + JSON_Value *temp_value = NULL; + size_t last_element_ix = 0; + if (array == NULL || ix >= json_array_get_count(array)) { + return JSONFailure; + } + last_element_ix = json_array_get_count(array) - 1; + json_value_free(json_array_get_value(array, ix)); + if (ix != last_element_ix) { /* Replace value with one from the end of array */ + temp_value = json_array_get_value(array, last_element_ix); + if (temp_value == NULL) { + return JSONFailure; + } + array->items[ix] = temp_value; + } + array->count -= 1; + return JSONSuccess; +} + +JSON_Status json_array_replace_value(JSON_Array *array, size_t ix, JSON_Value *value) { + if (array == NULL || value == NULL || ix >= json_array_get_count(array)) { + return JSONFailure; + } + json_value_free(json_array_get_value(array, ix)); + array->items[ix] = value; + return JSONSuccess; +} + +JSON_Status json_array_replace_string(JSON_Array *array, size_t i, const char* string) { + JSON_Value *value = json_value_init_string(string); + if (value == NULL) + return JSONFailure; + if (json_array_replace_value(array, i, value) == JSONFailure) { + json_value_free(value); + return JSONFailure; + } + return JSONSuccess; +} + +JSON_Status json_array_replace_number(JSON_Array *array, size_t i, double number) { + JSON_Value *value = json_value_init_number(number); + if (value == NULL) + return JSONFailure; + if (json_array_replace_value(array, i, value) == JSONFailure) { + json_value_free(value); + return JSONFailure; + } + return JSONSuccess; +} + +JSON_Status json_array_replace_boolean(JSON_Array *array, size_t i, int boolean) { + JSON_Value *value = json_value_init_boolean(boolean); + if (value == NULL) + return JSONFailure; + if (json_array_replace_value(array, i, value) == JSONFailure) { + json_value_free(value); + return JSONFailure; + } + return JSONSuccess; +} + +JSON_Status json_array_replace_null(JSON_Array *array, size_t i) { + JSON_Value *value = json_value_init_null(); + if (value == NULL) + return JSONFailure; + if (json_array_replace_value(array, i, value) == JSONFailure) { + json_value_free(value); + return JSONFailure; + } + return JSONSuccess; +} + +JSON_Status json_array_clear(JSON_Array *array) { + size_t i = 0; + if (array == NULL) + return JSONFailure; + for (i = 0; i < json_array_get_count(array); i++) { + json_value_free(json_array_get_value(array, i)); + } + array->count = 0; + return JSONSuccess; +} + +JSON_Status json_array_append_value(JSON_Array *array, JSON_Value *value) { + if (array == NULL || value == NULL) + return JSONFailure; + return json_array_add(array, value); +} + +JSON_Status json_array_append_string(JSON_Array *array, const char *string) { + JSON_Value *value = json_value_init_string(string); + if (value == NULL) + return JSONFailure; + if (json_array_append_value(array, value) == JSONFailure) { + json_value_free(value); + return JSONFailure; + } + return JSONSuccess; +} + +JSON_Status json_array_append_number(JSON_Array *array, double number) { + JSON_Value *value = json_value_init_number(number); + if (value == NULL) + return JSONFailure; + if (json_array_append_value(array, value) == JSONFailure) { + json_value_free(value); + return JSONFailure; + } + return JSONSuccess; +} + +JSON_Status json_array_append_boolean(JSON_Array *array, int boolean) { + JSON_Value *value = json_value_init_boolean(boolean); + if (value == NULL) + return JSONFailure; + if (json_array_append_value(array, value) == JSONFailure) { + json_value_free(value); + return JSONFailure; + } + return JSONSuccess; +} + +JSON_Status json_array_append_null(JSON_Array *array) { + JSON_Value *value = json_value_init_null(); + if (value == NULL) + return JSONFailure; + if (json_array_append_value(array, value) == JSONFailure) { + json_value_free(value); + return JSONFailure; + } + return JSONSuccess; +} + +JSON_Status json_object_set_value(JSON_Object *object, const char *name, JSON_Value *value) { + size_t i = 0; + JSON_Value *old_value; + if (object == NULL || name == NULL || value == NULL) + return JSONFailure; + old_value = json_object_get_value(object, name); + if (old_value != NULL) { /* free and overwrite old value */ + json_value_free(old_value); + for (i = 0; i < json_object_get_count(object); i++) { + if (strcmp(object->names[i], name) == 0) { + object->values[i] = value; + return JSONSuccess; + } + } + } + /* add new key value pair */ + return json_object_add(object, name, value); +} + +JSON_Status json_object_set_string(JSON_Object *object, const char *name, const char *string) { + return json_object_set_value(object, name, json_value_init_string(string)); +} + +JSON_Status json_object_set_number(JSON_Object *object, const char *name, double number) { + return json_object_set_value(object, name, json_value_init_number(number)); +} + +JSON_Status json_object_set_boolean(JSON_Object *object, const char *name, int boolean) { + return json_object_set_value(object, name, json_value_init_boolean(boolean)); +} + +JSON_Status json_object_set_null(JSON_Object *object, const char *name) { + return json_object_set_value(object, name, json_value_init_null()); +} + +JSON_Status json_object_dotset_value(JSON_Object *object, const char *name, JSON_Value *value) { + const char *dot_pos = NULL; + char *current_name = NULL; + JSON_Object *temp_obj = NULL; + JSON_Value *new_value = NULL; + if (value == NULL || name == NULL || value == NULL) + return JSONFailure; + dot_pos = strchr(name, '.'); + if (dot_pos == NULL) { + return json_object_set_value(object, name, value); + } else { + current_name = parson_strndup(name, dot_pos - name); + temp_obj = json_object_get_object(object, current_name); + if (temp_obj == NULL) { + new_value = json_value_init_object(); + if (new_value == NULL) { + parson_free(current_name); + return JSONFailure; + } + if (json_object_add(object, current_name, new_value) == JSONFailure) { + json_value_free(new_value); + parson_free(current_name); + return JSONFailure; + } + temp_obj = json_object_get_object(object, current_name); + } + parson_free(current_name); + return json_object_dotset_value(temp_obj, dot_pos + 1, value); + } +} + +JSON_Status json_object_dotset_string(JSON_Object *object, const char *name, const char *string) { + JSON_Value *value = json_value_init_string(string); + if (value == NULL) + return JSONFailure; + if (json_object_dotset_value(object, name, value) == JSONFailure) { + json_value_free(value); + return JSONFailure; + } + return JSONSuccess; +} + +JSON_Status json_object_dotset_number(JSON_Object *object, const char *name, double number) { + JSON_Value *value = json_value_init_number(number); + if (value == NULL) + return JSONFailure; + if (json_object_dotset_value(object, name, value) == JSONFailure) { + json_value_free(value); + return JSONFailure; + } + return JSONSuccess; +} + +JSON_Status json_object_dotset_boolean(JSON_Object *object, const char *name, int boolean) { + JSON_Value *value = json_value_init_boolean(boolean); + if (value == NULL) + return JSONFailure; + if (json_object_dotset_value(object, name, value) == JSONFailure) { + json_value_free(value); + return JSONFailure; + } + return JSONSuccess; +} + +JSON_Status json_object_dotset_null(JSON_Object *object, const char *name) { + JSON_Value *value = json_value_init_null(); + if (value == NULL) + return JSONFailure; + if (json_object_dotset_value(object, name, value) == JSONFailure) { + json_value_free(value); + return JSONFailure; + } + return JSONSuccess; +} + +JSON_Status json_object_remove(JSON_Object *object, const char *name) { + size_t i = 0, last_item_index = 0; + if (object == NULL || json_object_get_value(object, name) == NULL) + return JSONFailure; + last_item_index = json_object_get_count(object) - 1; + for (i = 0; i < json_object_get_count(object); i++) { + if (strcmp(object->names[i], name) == 0) { + parson_free(object->names[i]); + json_value_free(object->values[i]); + if (i != last_item_index) { /* Replace key value pair with one from the end */ + object->names[i] = object->names[last_item_index]; + object->values[i] = object->values[last_item_index]; + } + object->count -= 1; + return JSONSuccess; + } + } + return JSONFailure; /* No execution path should end here */ +} + +JSON_Status json_object_dotremove(JSON_Object *object, const char *name) { + const char *dot_pos = strchr(name, '.'); + char *current_name = NULL; + JSON_Object *temp_obj = NULL; + if (dot_pos == NULL) { + return json_object_remove(object, name); + } else { + current_name = parson_strndup(name, dot_pos - name); + temp_obj = json_object_get_object(object, current_name); + if (temp_obj == NULL) { + parson_free(current_name); + return JSONFailure; + } + parson_free(current_name); + return json_object_dotremove(temp_obj, dot_pos + 1); + } +} + +JSON_Status json_object_clear(JSON_Object *object) { + size_t i = 0; + if (object == NULL) { + return JSONFailure; + } + for (i = 0; i < json_object_get_count(object); i++) { + parson_free(object->names[i]); + json_value_free(object->values[i]); + } + object->count = 0; + return JSONSuccess; +} + +JSON_Status json_validate(const JSON_Value *schema, const JSON_Value *value) { + JSON_Value *temp_schema_value = NULL, *temp_value = NULL; + JSON_Array *schema_array = NULL, *value_array = NULL; + JSON_Object *schema_object = NULL, *value_object = NULL; + JSON_Value_Type schema_type = JSONError, value_type = JSONError; + const char *key = NULL; + size_t i = 0, count = 0; + if (schema == NULL || value == NULL) + return JSONFailure; + schema_type = json_value_get_type(schema); + value_type = json_value_get_type(value); + if (schema_type != value_type && schema_type != JSONNull) /* null represents all values */ + return JSONFailure; + switch (schema_type) { + case JSONArray: + schema_array = json_value_get_array(schema); + value_array = json_value_get_array(value); + count = json_array_get_count(schema_array); + if (count == 0) + return JSONSuccess; /* Empty array allows all types */ + /* Get first value from array, rest is ignored */ + temp_schema_value = json_array_get_value(schema_array, 0); + for (i = 0; i < json_array_get_count(value_array); i++) { + temp_value = json_array_get_value(value_array, i); + if (json_validate(temp_schema_value, temp_value) == JSONFailure) { + return JSONFailure; + } + } + return JSONSuccess; + case JSONObject: + schema_object = json_value_get_object(schema); + value_object = json_value_get_object(value); + count = json_object_get_count(schema_object); + if (count == 0) + return JSONSuccess; /* Empty object allows all objects */ + else if (json_object_get_count(value_object) < count) + return JSONFailure; /* Tested object mustn't have less name-value pairs than schema */ + for (i = 0; i < count; i++) { + key = json_object_get_name(schema_object, i); + temp_schema_value = json_object_get_value(schema_object, key); + temp_value = json_object_get_value(value_object, key); + if (temp_value == NULL) + return JSONFailure; + if (json_validate(temp_schema_value, temp_value) == JSONFailure) + return JSONFailure; + } + return JSONSuccess; + case JSONString: case JSONNumber: case JSONBoolean: case JSONNull: + return JSONSuccess; /* equality already tested before switch */ + case JSONError: default: + return JSONFailure; + } +} + +JSON_Status json_value_equals(const JSON_Value *a, const JSON_Value *b) { + JSON_Object *a_object = NULL, *b_object = NULL; + JSON_Array *a_array = NULL, *b_array = NULL; + const char *a_string = NULL, *b_string = NULL; + const char *key = NULL; + size_t a_count = 0, b_count = 0, i = 0; + JSON_Value_Type a_type, b_type; + a_type = json_value_get_type(a); + b_type = json_value_get_type(b); + if (a_type != b_type) { + return 0; + } + switch (a_type) { + case JSONArray: + a_array = json_value_get_array(a); + b_array = json_value_get_array(b); + a_count = json_array_get_count(a_array); + b_count = json_array_get_count(b_array); + if (a_count != b_count) { + return 0; + } + for (i = 0; i < a_count; i++) { + if (!json_value_equals(json_array_get_value(a_array, i), + json_array_get_value(b_array, i))) { + return 0; + } + } + return 1; + case JSONObject: + a_object = json_value_get_object(a); + b_object = json_value_get_object(b); + a_count = json_object_get_count(a_object); + b_count = json_object_get_count(b_object); + if (a_count != b_count) { + return 0; + } + for (i = 0; i < a_count; i++) { + key = json_object_get_name(a_object, i); + if (!json_value_equals(json_object_get_value(a_object, key), + json_object_get_value(b_object, key))) { + return 0; + } + } + return 1; + case JSONString: + a_string = json_value_get_string(a); + b_string = json_value_get_string(b); + return strcmp(a_string, b_string) == 0; + case JSONBoolean: + return json_value_get_boolean(a) == json_value_get_boolean(b); + case JSONNumber: + return fabs(json_value_get_number(a) - json_value_get_number(b)) < 0.000001; /* EPSILON */ + case JSONError: + return 1; + case JSONNull: + return 1; + default: + return 1; + } +} + +JSON_Value_Type json_type(const JSON_Value *value) { + return json_value_get_type(value); +} + +JSON_Object * json_object (const JSON_Value *value) { + return json_value_get_object(value); +} + +JSON_Array * json_array (const JSON_Value *value) { + return json_value_get_array(value); +} + +const char * json_string (const JSON_Value *value) { + return json_value_get_string(value); +} + +double json_number (const JSON_Value *value) { + return json_value_get_number(value); +} + +int json_boolean(const JSON_Value *value) { + return json_value_get_boolean(value); +} + +void json_set_allocation_functions(JSON_Malloc_Function malloc_fun, JSON_Free_Function free_fun) { + parson_malloc = malloc_fun; + parson_free = free_fun; +} diff --git a/parson-a1c356e/parson.h b/parson-a1c356e/parson.h new file mode 100644 index 0000000..6a89982 --- /dev/null +++ b/parson-a1c356e/parson.h @@ -0,0 +1,231 @@ +/* + Parson ( http://kgabis.github.com/parson/ ) + Copyright (c) 2012 - 2016 Krzysztof Gabis + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ + +#ifndef parson_parson_h +#define parson_parson_h + +#ifdef __cplusplus +extern "C" +{ +#endif + +#include /* size_t */ + +/* Types and enums */ +typedef struct json_object_t JSON_Object; +typedef struct json_array_t JSON_Array; +typedef struct json_value_t JSON_Value; + +enum json_value_type { + JSONError = -1, + JSONNull = 1, + JSONString = 2, + JSONNumber = 3, + JSONObject = 4, + JSONArray = 5, + JSONBoolean = 6 +}; +typedef int JSON_Value_Type; + +enum json_result_t { + JSONSuccess = 0, + JSONFailure = -1 +}; +typedef int JSON_Status; + +typedef void * (*JSON_Malloc_Function)(size_t); +typedef void (*JSON_Free_Function)(void *); + +/* Call only once, before calling any other function from parson API. If not called, malloc and free + from stdlib will be used for all allocations */ +void json_set_allocation_functions(JSON_Malloc_Function malloc_fun, JSON_Free_Function free_fun); + +/* Parses first JSON value in a file, returns NULL in case of error */ +JSON_Value * json_parse_file(const char *filename); + +/* Parses first JSON value in a file and ignores comments (/ * * / and //), + returns NULL in case of error */ +JSON_Value * json_parse_file_with_comments(const char *filename); + +/* Parses first JSON value in a string, returns NULL in case of error */ +JSON_Value * json_parse_string(const char *string); + +/* Parses first JSON value in a string and ignores comments (/ * * / and //), + returns NULL in case of error */ +JSON_Value * json_parse_string_with_comments(const char *string); + +/* Serialization */ +size_t json_serialization_size(const JSON_Value *value); /* returns 0 on fail */ +JSON_Status json_serialize_to_buffer(const JSON_Value *value, char *buf, size_t buf_size_in_bytes); +JSON_Status json_serialize_to_file(const JSON_Value *value, const char *filename); +char * json_serialize_to_string(const JSON_Value *value); + +/* Pretty serialization */ +size_t json_serialization_size_pretty(const JSON_Value *value); /* returns 0 on fail */ +JSON_Status json_serialize_to_buffer_pretty(const JSON_Value *value, char *buf, size_t buf_size_in_bytes); +JSON_Status json_serialize_to_file_pretty(const JSON_Value *value, const char *filename); +char * json_serialize_to_string_pretty(const JSON_Value *value); + +void json_free_serialized_string(char *string); /* frees string from json_serialize_to_string and json_serialize_to_string_pretty */ + +/* Comparing */ +int json_value_equals(const JSON_Value *a, const JSON_Value *b); + +/* Validation + This is *NOT* JSON Schema. It validates json by checking if object have identically + named fields with matching types. + For example schema {"name":"", "age":0} will validate + {"name":"Joe", "age":25} and {"name":"Joe", "age":25, "gender":"m"}, + but not {"name":"Joe"} or {"name":"Joe", "age":"Cucumber"}. + In case of arrays, only first value in schema is checked against all values in tested array. + Empty objects ({}) validate all objects, empty arrays ([]) validate all arrays, + null validates values of every type. + */ +JSON_Status json_validate(const JSON_Value *schema, const JSON_Value *value); + +/* + * JSON Object + */ +JSON_Value * json_object_get_value (const JSON_Object *object, const char *name); +const char * json_object_get_string (const JSON_Object *object, const char *name); +JSON_Object * json_object_get_object (const JSON_Object *object, const char *name); +JSON_Array * json_object_get_array (const JSON_Object *object, const char *name); +double json_object_get_number (const JSON_Object *object, const char *name); /* returns 0 on fail */ +int json_object_get_boolean(const JSON_Object *object, const char *name); /* returns -1 on fail */ + +/* dotget functions enable addressing values with dot notation in nested objects, + just like in structs or c++/java/c# objects (e.g. objectA.objectB.value). + Because valid names in JSON can contain dots, some values may be inaccessible + this way. */ +JSON_Value * json_object_dotget_value (const JSON_Object *object, const char *name); +const char * json_object_dotget_string (const JSON_Object *object, const char *name); +JSON_Object * json_object_dotget_object (const JSON_Object *object, const char *name); +JSON_Array * json_object_dotget_array (const JSON_Object *object, const char *name); +double json_object_dotget_number (const JSON_Object *object, const char *name); /* returns 0 on fail */ +int json_object_dotget_boolean(const JSON_Object *object, const char *name); /* returns -1 on fail */ + +/* Functions to get available names */ +size_t json_object_get_count (const JSON_Object *object); +const char * json_object_get_name (const JSON_Object *object, size_t index); +JSON_Value * json_object_get_value_at(const JSON_Object *object, size_t index); + +/* Functions to check if object has a value with a specific name. Returned value is 1 if object has + * a value and 0 if it doesn't. dothas functions behave exactly like dotget functions. */ +int json_object_has_value (const JSON_Object *object, const char *name); +int json_object_has_value_of_type(const JSON_Object *object, const char *name, JSON_Value_Type type); + +int json_object_dothas_value (const JSON_Object *object, const char *name); +int json_object_dothas_value_of_type(const JSON_Object *object, const char *name, JSON_Value_Type type); + +/* Creates new name-value pair or frees and replaces old value with a new one. + * json_object_set_value does not copy passed value so it shouldn't be freed afterwards. */ +JSON_Status json_object_set_value(JSON_Object *object, const char *name, JSON_Value *value); +JSON_Status json_object_set_string(JSON_Object *object, const char *name, const char *string); +JSON_Status json_object_set_number(JSON_Object *object, const char *name, double number); +JSON_Status json_object_set_boolean(JSON_Object *object, const char *name, int boolean); +JSON_Status json_object_set_null(JSON_Object *object, const char *name); + +/* Works like dotget functions, but creates whole hierarchy if necessary. + * json_object_dotset_value does not copy passed value so it shouldn't be freed afterwards. */ +JSON_Status json_object_dotset_value(JSON_Object *object, const char *name, JSON_Value *value); +JSON_Status json_object_dotset_string(JSON_Object *object, const char *name, const char *string); +JSON_Status json_object_dotset_number(JSON_Object *object, const char *name, double number); +JSON_Status json_object_dotset_boolean(JSON_Object *object, const char *name, int boolean); +JSON_Status json_object_dotset_null(JSON_Object *object, const char *name); + +/* Frees and removes name-value pair */ +JSON_Status json_object_remove(JSON_Object *object, const char *name); + +/* Works like dotget function, but removes name-value pair only on exact match. */ +JSON_Status json_object_dotremove(JSON_Object *object, const char *key); + +/* Removes all name-value pairs in object */ +JSON_Status json_object_clear(JSON_Object *object); + +/* + *JSON Array + */ +JSON_Value * json_array_get_value (const JSON_Array *array, size_t index); +const char * json_array_get_string (const JSON_Array *array, size_t index); +JSON_Object * json_array_get_object (const JSON_Array *array, size_t index); +JSON_Array * json_array_get_array (const JSON_Array *array, size_t index); +double json_array_get_number (const JSON_Array *array, size_t index); /* returns 0 on fail */ +int json_array_get_boolean(const JSON_Array *array, size_t index); /* returns -1 on fail */ +size_t json_array_get_count (const JSON_Array *array); + +/* Frees and removes value at given index, does nothing and returns JSONFailure if index doesn't exist. + * Order of values in array may change during execution. */ +JSON_Status json_array_remove(JSON_Array *array, size_t i); + +/* Frees and removes from array value at given index and replaces it with given one. + * Does nothing and returns JSONFailure if index doesn't exist. + * json_array_replace_value does not copy passed value so it shouldn't be freed afterwards. */ +JSON_Status json_array_replace_value(JSON_Array *array, size_t i, JSON_Value *value); +JSON_Status json_array_replace_string(JSON_Array *array, size_t i, const char* string); +JSON_Status json_array_replace_number(JSON_Array *array, size_t i, double number); +JSON_Status json_array_replace_boolean(JSON_Array *array, size_t i, int boolean); +JSON_Status json_array_replace_null(JSON_Array *array, size_t i); + +/* Frees and removes all values from array */ +JSON_Status json_array_clear(JSON_Array *array); + +/* Appends new value at the end of array. + * json_array_append_value does not copy passed value so it shouldn't be freed afterwards. */ +JSON_Status json_array_append_value(JSON_Array *array, JSON_Value *value); +JSON_Status json_array_append_string(JSON_Array *array, const char *string); +JSON_Status json_array_append_number(JSON_Array *array, double number); +JSON_Status json_array_append_boolean(JSON_Array *array, int boolean); +JSON_Status json_array_append_null(JSON_Array *array); + +/* + *JSON Value + */ +JSON_Value * json_value_init_object (void); +JSON_Value * json_value_init_array (void); +JSON_Value * json_value_init_string (const char *string); /* copies passed string */ +JSON_Value * json_value_init_number (double number); +JSON_Value * json_value_init_boolean(int boolean); +JSON_Value * json_value_init_null (void); +JSON_Value * json_value_deep_copy (const JSON_Value *value); +void json_value_free (JSON_Value *value); + +JSON_Value_Type json_value_get_type (const JSON_Value *value); +JSON_Object * json_value_get_object (const JSON_Value *value); +JSON_Array * json_value_get_array (const JSON_Value *value); +const char * json_value_get_string (const JSON_Value *value); +double json_value_get_number (const JSON_Value *value); +int json_value_get_boolean(const JSON_Value *value); + +/* Same as above, but shorter */ +JSON_Value_Type json_type (const JSON_Value *value); +JSON_Object * json_object (const JSON_Value *value); +JSON_Array * json_array (const JSON_Value *value); +const char * json_string (const JSON_Value *value); +double json_number (const JSON_Value *value); +int json_boolean(const JSON_Value *value); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/parson-a1c356e/tests.c b/parson-a1c356e/tests.c new file mode 100644 index 0000000..819711d --- /dev/null +++ b/parson-a1c356e/tests.c @@ -0,0 +1,572 @@ +/* + Parson ( http://kgabis.github.com/parson/ ) + Copyright (c) 2012 - 2016 Krzysztof Gabis + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ +#ifdef _MSC_VER +#define _CRT_SECURE_NO_WARNINGS +#endif + +#include "parson.h" + +#include +#include +#include +#include + +#define TEST(A) printf("%-72s-",#A);\ + if(A){puts(" OK");tests_passed++;}\ + else{puts(" FAIL");tests_failed++;} +#define STREQ(A, B) ((A) && (B) ? strcmp((A), (B)) == 0 : 0) +#define EPSILON 0.000001 + +void test_suite_1(void); /* Test 3 files from json.org + serialization*/ +void test_suite_2(JSON_Value *value); /* Test correctness of parsed values */ +void test_suite_2_no_comments(void); +void test_suite_2_with_comments(void); +void test_suite_3(void); /* Test parsing valid and invalid strings */ +void test_suite_4(void); /* Test deep copy funtion */ +void test_suite_5(void); /* Test building json values from scratch */ +void test_suite_6(void); /* Test value comparing verification */ +void test_suite_7(void); /* Test schema validation */ +void test_suite_8(void); /* Test serialization */ +void test_suite_9(void); /* Test serialization (pretty) */ + +void print_commits_info(const char *username, const char *repo); +void persistence_example(void); +void serialization_example(void); + +static char * read_file(const char * filename); + +static int tests_passed; +static int tests_failed; + +int main() { + /* Example functions from readme file: */ + /* print_commits_info("torvalds", "linux"); */ + /* serialization_example(); */ + /* persistence_example(); */ + + test_suite_1(); + test_suite_2_no_comments(); + test_suite_2_with_comments(); + test_suite_3(); + test_suite_4(); + test_suite_5(); + test_suite_6(); + test_suite_7(); + test_suite_8(); + test_suite_9(); + printf("Tests failed: %d\n", tests_failed); + printf("Tests passed: %d\n", tests_passed); + return 0; +} + +void test_suite_1(void) { + JSON_Value *val; + TEST((val = json_parse_file("tests/test_1_1.txt")) != NULL); + TEST(json_value_equals(json_parse_string(json_serialize_to_string(val)), val)); + TEST(json_value_equals(json_parse_string(json_serialize_to_string_pretty(val)), val)); + if (val) { json_value_free(val); } + + TEST((val = json_parse_file("tests/test_1_2.txt")) != NULL); + TEST(json_value_equals(json_parse_string(json_serialize_to_string(val)), val)); + TEST(json_value_equals(json_parse_string(json_serialize_to_string_pretty(val)), val)); + if (val) { json_value_free(val); } + + TEST((val = json_parse_file("tests/test_1_3.txt")) != NULL); + TEST(json_value_equals(json_parse_string(json_serialize_to_string(val)), val)); + TEST(json_value_equals(json_parse_string(json_serialize_to_string_pretty(val)), val)); + if (val) { json_value_free(val); } + + TEST((val = json_parse_file_with_comments("tests/test_1_1.txt")) != NULL); + TEST(json_value_equals(json_parse_string(json_serialize_to_string(val)), val)); + TEST(json_value_equals(json_parse_string(json_serialize_to_string_pretty(val)), val)); + if (val) { json_value_free(val); } + + TEST((val = json_parse_file_with_comments("tests/test_1_2.txt")) != NULL); + TEST(json_value_equals(json_parse_string(json_serialize_to_string(val)), val)); + TEST(json_value_equals(json_parse_string(json_serialize_to_string_pretty(val)), val)); + if (val) { json_value_free(val); } + + TEST((val = json_parse_file_with_comments("tests/test_1_3.txt")) != NULL); + TEST(json_value_equals(json_parse_string(json_serialize_to_string(val)), val)); + TEST(json_value_equals(json_parse_string(json_serialize_to_string_pretty(val)), val)); + if (val) { json_value_free(val); } +} + +void test_suite_2(JSON_Value *root_value) { + JSON_Object *root_object; + JSON_Array *array; + size_t i; + TEST(root_value); + TEST(json_value_get_type(root_value) == JSONObject); + root_object = json_value_get_object(root_value); + + TEST(json_object_has_value(root_object, "string")); + TEST(!json_object_has_value(root_object, "_string")); + TEST(json_object_has_value_of_type(root_object, "object", JSONObject)); + TEST(!json_object_has_value_of_type(root_object, "string array", JSONObject)); + TEST(json_object_has_value_of_type(root_object, "string array", JSONArray)); + TEST(!json_object_has_value_of_type(root_object, "object", JSONArray)); + TEST(json_object_has_value_of_type(root_object, "string", JSONString)); + TEST(!json_object_has_value_of_type(root_object, "positive one", JSONString)); + TEST(json_object_has_value_of_type(root_object, "positive one", JSONNumber)); + TEST(!json_object_has_value_of_type(root_object, "string", JSONNumber)); + TEST(json_object_has_value_of_type(root_object, "boolean true", JSONBoolean)); + TEST(!json_object_has_value_of_type(root_object, "positive one", JSONBoolean)); + TEST(json_object_has_value_of_type(root_object, "null", JSONNull)); + TEST(!json_object_has_value_of_type(root_object, "object", JSONNull)); + + TEST(json_object_dothas_value(root_object, "object.nested array")); + TEST(!json_object_dothas_value(root_object, "_object.nested array")); + TEST(json_object_dothas_value_of_type(root_object, "object.nested object", JSONObject)); + TEST(!json_object_dothas_value_of_type(root_object, "object.nested array", JSONObject)); + TEST(json_object_dothas_value_of_type(root_object, "object.nested array", JSONArray)); + TEST(!json_object_dothas_value_of_type(root_object, "object.nested object", JSONArray)); + TEST(json_object_dothas_value_of_type(root_object, "object.nested string", JSONString)); + TEST(!json_object_dothas_value_of_type(root_object, "object.nested number", JSONString)); + TEST(json_object_dothas_value_of_type(root_object, "object.nested number", JSONNumber)); + TEST(!json_object_dothas_value_of_type(root_object, "_object.nested whatever", JSONNumber)); + TEST(json_object_dothas_value_of_type(root_object, "object.nested true", JSONBoolean)); + TEST(!json_object_dothas_value_of_type(root_object, "object.nested number", JSONBoolean)); + TEST(json_object_dothas_value_of_type(root_object, "object.nested null", JSONNull)); + TEST(!json_object_dothas_value_of_type(root_object, "object.nested object", JSONNull)); + + TEST(STREQ(json_object_get_string(root_object, "string"), "lorem ipsum")); + TEST(STREQ(json_object_get_string(root_object, "utf string"), "lorem ipsum")); + TEST(STREQ(json_object_get_string(root_object, "utf-8 string"), "あいうえお")); + TEST(STREQ(json_object_get_string(root_object, "surrogate string"), "lorem𝄞ipsum𝍧lorem")); + + TEST(json_object_get_number(root_object, "positive one") == 1.0); + TEST(json_object_get_number(root_object, "negative one") == -1.0); + TEST(fabs(json_object_get_number(root_object, "hard to parse number") - (-0.000314)) < EPSILON); + TEST(json_object_get_boolean(root_object, "boolean true") == 1); + TEST(json_object_get_boolean(root_object, "boolean false") == 0); + TEST(json_value_get_type(json_object_get_value(root_object, "null")) == JSONNull); + + array = json_object_get_array(root_object, "string array"); + if (array != NULL && json_array_get_count(array) > 1) { + TEST(STREQ(json_array_get_string(array, 0), "lorem")); + TEST(STREQ(json_array_get_string(array, 1), "ipsum")); + } else { + tests_failed++; + } + + array = json_object_get_array(root_object, "x^2 array"); + if (array != NULL) { + for (i = 0; i < json_array_get_count(array); i++) { + TEST(json_array_get_number(array, i) == (i * i)); + } + } else { + tests_failed++; + } + + TEST(json_object_get_array(root_object, "non existent array") == NULL); + TEST(STREQ(json_object_dotget_string(root_object, "object.nested string"), "str")); + TEST(json_object_dotget_boolean(root_object, "object.nested true") == 1); + TEST(json_object_dotget_boolean(root_object, "object.nested false") == 0); + TEST(json_object_dotget_value(root_object, "object.nested null") != NULL); + TEST(json_object_dotget_number(root_object, "object.nested number") == 123); + + TEST(json_object_dotget_value(root_object, "should.be.null") == NULL); + TEST(json_object_dotget_value(root_object, "should.be.null.") == NULL); + TEST(json_object_dotget_value(root_object, ".") == NULL); + TEST(json_object_dotget_value(root_object, "") == NULL); + + array = json_object_dotget_array(root_object, "object.nested array"); + TEST(array != NULL); + TEST(json_array_get_count(array) > 1); + if (array != NULL && json_array_get_count(array) > 1) { + TEST(STREQ(json_array_get_string(array, 0), "lorem")); + TEST(STREQ(json_array_get_string(array, 1), "ipsum")); + } + TEST(json_object_dotget_boolean(root_object, "object.nested true") == 1); + + TEST(STREQ(json_object_get_string(root_object, "/**/"), "comment")); + TEST(STREQ(json_object_get_string(root_object, "//"), "comment")); + TEST(STREQ(json_object_get_string(root_object, "url"), "https://www.example.com/search?q=12345")); + TEST(STREQ(json_object_get_string(root_object, "escaped chars"), "\" \\ /")); + + TEST(json_object_get_object(root_object, "empty object") != NULL); + TEST(json_object_get_array(root_object, "empty array") != NULL); + +} + +void test_suite_2_no_comments(void) { + const char *filename = "tests/test_2.txt"; + JSON_Value *root_value = NULL; + root_value = json_parse_file(filename); + test_suite_2(root_value); + TEST(json_value_equals(root_value, json_parse_string(json_serialize_to_string(root_value)))); + TEST(json_value_equals(root_value, json_parse_string(json_serialize_to_string_pretty(root_value)))); + json_value_free(root_value); +} + +void test_suite_2_with_comments(void) { + const char *filename = "tests/test_2_comments.txt"; + JSON_Value *root_value = NULL; + root_value = json_parse_file_with_comments(filename); + test_suite_2(root_value); + TEST(json_value_equals(root_value, json_parse_string(json_serialize_to_string(root_value)))); + TEST(json_value_equals(root_value, json_parse_string(json_serialize_to_string_pretty(root_value)))); + json_value_free(root_value); +} + +void test_suite_3(void) { + char nested_20x[] = "[[[[[[[[[[[[[[[[[[[[\"hi\"]]]]]]]]]]]]]]]]]]]]"; + + puts("Testing valid strings:"); + TEST(json_parse_string("{\"lorem\":\"ipsum\"}") != NULL); + TEST(json_parse_string("[\"lorem\"]") != NULL); + TEST(json_parse_string("null") != NULL); + TEST(json_parse_string("true") != NULL); + TEST(json_parse_string("false") != NULL); + TEST(json_parse_string("\"string\"") != NULL); + TEST(json_parse_string("123") != NULL); + + puts("Testing invalid strings:"); + TEST(json_parse_string(NULL) == NULL); + TEST(json_parse_string("") == NULL); /* empty string */ + TEST(json_parse_string("[\"lorem\",]") == NULL); + TEST(json_parse_string("{\"lorem\":\"ipsum\",}") == NULL); + TEST(json_parse_string("{lorem:ipsum}") == NULL); + TEST(json_parse_string("[,]") == NULL); + TEST(json_parse_string("[,") == NULL); + TEST(json_parse_string("[") == NULL); + TEST(json_parse_string("]") == NULL); + TEST(json_parse_string("{\"a\":0,\"a\":0}") == NULL); /* duplicate keys */ + TEST(json_parse_string("{:,}") == NULL); + TEST(json_parse_string("{,}") == NULL); + TEST(json_parse_string("{,") == NULL); + TEST(json_parse_string("{:") == NULL); + TEST(json_parse_string("{") == NULL); + TEST(json_parse_string("}") == NULL); + TEST(json_parse_string("x") == NULL); + TEST(json_parse_string("{:\"no name\"}") == NULL); + TEST(json_parse_string("[,\"no first value\"]") == NULL); + TEST(json_parse_string("[\"\\u00zz\"]") == NULL); /* invalid utf value */ + TEST(json_parse_string("[\"\\u00\"]") == NULL); /* invalid utf value */ + TEST(json_parse_string("[\"\\u\"]") == NULL); /* invalid utf value */ + TEST(json_parse_string("[\"\\\"]") == NULL); /* control character */ + TEST(json_parse_string("[\"\"\"]") == NULL); /* control character */ + TEST(json_parse_string("[\"\0\"]") == NULL); /* control character */ + TEST(json_parse_string("[\"\a\"]") == NULL); /* control character */ + TEST(json_parse_string("[\"\b\"]") == NULL); /* control character */ + TEST(json_parse_string("[\"\t\"]") == NULL); /* control character */ + TEST(json_parse_string("[\"\n\"]") == NULL); /* control character */ + TEST(json_parse_string("[\"\f\"]") == NULL); /* control character */ + TEST(json_parse_string("[\"\r\"]") == NULL); /* control character */ + TEST(json_parse_string(nested_20x) == NULL); /* too deep */ + TEST(json_parse_string("[0x2]") == NULL); /* hex */ + TEST(json_parse_string("[0X2]") == NULL); /* HEX */ + TEST(json_parse_string("[07]") == NULL); /* octals */ + TEST(json_parse_string("[0070]") == NULL); + TEST(json_parse_string("[07.0]") == NULL); + TEST(json_parse_string("[-07]") == NULL); + TEST(json_parse_string("[-007]") == NULL); + TEST(json_parse_string("[-07.0]") == NULL); + TEST(json_parse_string("[\"\\uDF67\\uD834\"]") == NULL); /* wrong order surrogate pair */ +} + +void test_suite_4() { + const char *filename = "tests/test_2.txt"; + JSON_Value *a = NULL, *a_copy = NULL; + printf("Testing %s:\n", filename); + a = json_parse_file(filename); + TEST(json_value_equals(a, a)); /* test equality test */ + a_copy = json_value_deep_copy(a); + TEST(a_copy != NULL); + TEST(json_value_equals(a, a_copy)); +} + +void test_suite_5(void) { + JSON_Value *val_from_file = json_parse_file("tests/test_5.txt"); + + JSON_Value *val = NULL; + JSON_Object *obj = NULL; + JSON_Array *interests_arr = NULL; + + val = json_value_init_object(); + TEST(val != NULL); + + obj = json_value_get_object(val); + TEST(obj != NULL); + + TEST(json_object_set_string(obj, "first", "John") == JSONSuccess); + TEST(json_object_set_string(obj, "last", "Doe") == JSONSuccess); + TEST(json_object_set_number(obj, "age", 25) == JSONSuccess); + TEST(json_object_set_boolean(obj, "registered", 1) == JSONSuccess); + + TEST(json_object_set_value(obj, "interests", json_value_init_array()) == JSONSuccess); + interests_arr = json_object_get_array(obj, "interests"); + TEST(interests_arr != NULL); + TEST(json_array_append_string(interests_arr, "Writing") == JSONSuccess); + TEST(json_array_append_string(interests_arr, "Mountain Biking") == JSONSuccess); + TEST(json_array_replace_string(interests_arr, 0, "Reading") == JSONSuccess); + + TEST(json_object_dotset_string(obj, "favorites.color", "blue") == JSONSuccess); + TEST(json_object_dotset_string(obj, "favorites.sport", "running") == JSONSuccess); + TEST(json_object_dotset_string(obj, "favorites.fruit", "apple") == JSONSuccess); + TEST(json_object_dotremove(obj, "favorites.fruit") == JSONSuccess); + TEST(json_object_set_string(obj, "utf string", "lorem ipsum") == JSONSuccess); + TEST(json_object_set_string(obj, "utf-8 string", "あいうえお") == JSONSuccess); + TEST(json_object_set_string(obj, "surrogate string", "lorem𝄞ipsum𝍧lorem") == JSONSuccess); + TEST(json_object_set_string(obj, "windows path", "C:\\Windows\\Path") == JSONSuccess); + TEST(json_value_equals(val_from_file, val)); + + TEST(json_object_set_string(obj, NULL, "") == JSONFailure); + TEST(json_object_set_string(obj, "last", NULL) == JSONFailure); + TEST(json_object_set_string(obj, NULL, NULL) == JSONFailure); + TEST(json_object_set_value(obj, NULL, NULL) == JSONFailure); + + TEST(json_object_dotset_string(obj, NULL, "") == JSONFailure); + TEST(json_object_dotset_string(obj, "favorites.color", NULL) == JSONFailure); + TEST(json_object_dotset_string(obj, NULL, NULL) == JSONFailure); + TEST(json_object_dotset_value(obj, NULL, NULL) == JSONFailure); + + TEST(json_array_append_string(NULL, "lorem") == JSONFailure); + TEST(json_array_append_value(interests_arr, NULL) == JSONFailure); + TEST(json_array_append_value(NULL, NULL) == JSONFailure); + + TEST(json_array_remove(NULL, 0) == JSONFailure); + TEST(json_array_replace_value(interests_arr, 0, NULL) == JSONFailure); + TEST(json_array_replace_string(NULL, 0, "lorem") == JSONFailure); + TEST(json_array_replace_string(interests_arr, 100, "not existing") == JSONFailure); + + TEST(json_array_append_string(json_object_get_array(obj, "interests"), NULL) == JSONFailure); + + TEST(json_array_append_string(interests_arr, "Writing") == JSONSuccess); + TEST(json_array_remove(interests_arr, 0) == JSONSuccess); + TEST(json_array_remove(interests_arr, 1) == JSONSuccess); + TEST(json_array_remove(interests_arr, 0) == JSONSuccess); + TEST(json_array_remove(interests_arr, 0) == JSONFailure); /* should be empty by now */ + + TEST(json_object_remove(obj, "interests") == JSONSuccess); + + /* UTF-8 tests */ + TEST(json_object_set_string(obj, "correct string", "κόσμε") == JSONSuccess); + + TEST(json_object_set_string(obj, "boundary 1", "\xed\x9f\xbf") == JSONSuccess); + TEST(json_object_set_string(obj, "boundary 2", "\xee\x80\x80") == JSONSuccess); + TEST(json_object_set_string(obj, "boundary 3", "\xef\xbf\xbd") == JSONSuccess); + TEST(json_object_set_string(obj, "boundary 4", "\xf4\x8f\xbf\xbf") == JSONSuccess); + + TEST(json_object_set_string(obj, "first continuation byte", "\x80") == JSONFailure); + TEST(json_object_set_string(obj, "last continuation byte", "\xbf") == JSONFailure); + + TEST(json_object_set_string(obj, "impossible sequence 1", "\xfe") == JSONFailure); + TEST(json_object_set_string(obj, "impossible sequence 2", "\xff") == JSONFailure); + TEST(json_object_set_string(obj, "impossible sequence 3", "\xfe\xfe\xff\xff") == JSONFailure); + + TEST(json_object_set_string(obj, "overlong 1", "\xc0\xaf") == JSONFailure); + TEST(json_object_set_string(obj, "overlong 2", "\xc1\xbf") == JSONFailure); + TEST(json_object_set_string(obj, "overlong 3", "\xe0\x80\xaf") == JSONFailure); + TEST(json_object_set_string(obj, "overlong 4", "\xe0\x9f\xbf") == JSONFailure); + TEST(json_object_set_string(obj, "overlong 5", "\xf0\x80\x80\xaf") == JSONFailure); + TEST(json_object_set_string(obj, "overlong 6", "\xf0\x8f\xbf\xbf") == JSONFailure); + TEST(json_object_set_string(obj, "overlong 7", "\xf0\x8f\xbf\xbf") == JSONFailure); + + TEST(json_object_set_string(obj, "overlong null 1", "\xc0\x80") == JSONFailure); + TEST(json_object_set_string(obj, "overlong null 2", "\xe0\x80\x80") == JSONFailure); + TEST(json_object_set_string(obj, "overlong null 3", "\xf0\x80\x80\x80") == JSONFailure); + TEST(json_object_set_string(obj, "overlong null 4", "\xf8\x80\x80\x80\x80") == JSONFailure); + TEST(json_object_set_string(obj, "overlong null 5", "\xfc\x80\x80\x80\x80\x80") == JSONFailure); + + TEST(json_object_set_string(obj, "single surrogate 1", "\xed\xa0\x80") == JSONFailure); + TEST(json_object_set_string(obj, "single surrogate 2", "\xed\xaf\xbf") == JSONFailure); + TEST(json_object_set_string(obj, "single surrogate 3", "\xed\xbf\xbf") == JSONFailure); +} + +void test_suite_6(void) { + const char *filename = "tests/test_2.txt"; + JSON_Value *a = NULL; + JSON_Value *b = NULL; + a = json_parse_file(filename); + b = json_parse_file(filename); + TEST(json_value_equals(a, b)); + json_object_set_string(json_object(a), "string", "eki"); + TEST(!json_value_equals(a, b)); + a = json_value_deep_copy(b); + TEST(json_value_equals(a, b)); + json_array_append_number(json_object_get_array(json_object(b), "string array"), 1337); + TEST(!json_value_equals(a, b)); +} + +void test_suite_7(void) { + JSON_Value *val_from_file = json_parse_file("tests/test_5.txt"); + JSON_Value *schema = json_value_init_object(); + JSON_Object *schema_obj = json_value_get_object(schema); + JSON_Array *interests_arr = NULL; + json_object_set_string(schema_obj, "first", ""); + json_object_set_string(schema_obj, "last", ""); + json_object_set_number(schema_obj, "age", 0); + json_object_set_value(schema_obj, "interests", json_value_init_array()); + interests_arr = json_object_get_array(schema_obj, "interests"); + json_array_append_string(interests_arr, ""); + json_object_set_null(schema_obj, "favorites"); + TEST(json_validate(schema, val_from_file) == JSONSuccess); + json_object_set_string(schema_obj, "age", ""); + TEST(json_validate(schema, val_from_file) == JSONFailure); +} + +void test_suite_8(void) { + const char *filename = "tests/test_2.txt"; + const char *temp_filename = "tests/test_2_serialized.txt"; + JSON_Value *a = NULL; + JSON_Value *b = NULL; + char *buf = NULL; + size_t serialization_size = 0; + a = json_parse_file(filename); + TEST(json_serialize_to_file(a, temp_filename) == JSONSuccess); + b = json_parse_file(temp_filename); + TEST(json_value_equals(a, b)); + remove(temp_filename); + serialization_size = json_serialization_size(a); + buf = json_serialize_to_string(a); + TEST((strlen(buf)+1) == serialization_size); +} + +void test_suite_9(void) { + const char *filename = "tests/test_2_pretty.txt"; + const char *temp_filename = "tests/test_2_serialized_pretty.txt"; + char *file_contents = NULL; + char *serialized = NULL; + JSON_Value *a = NULL; + JSON_Value *b = NULL; + size_t serialization_size = 0; + a = json_parse_file(filename); + TEST(json_serialize_to_file_pretty(a, temp_filename) == JSONSuccess); + b = json_parse_file(temp_filename); + TEST(json_value_equals(a, b)); + remove(temp_filename); + serialization_size = json_serialization_size_pretty(a); + serialized = json_serialize_to_string_pretty(a); + TEST((strlen(serialized)+1) == serialization_size); + + file_contents = read_file(filename); + TEST(STREQ(file_contents, serialized)); +} + +void print_commits_info(const char *username, const char *repo) { + JSON_Value *root_value; + JSON_Array *commits; + JSON_Object *commit; + size_t i; + + char curl_command[512]; + char cleanup_command[256]; + char output_filename[] = "commits.json"; + + /* it ain't pretty, but it's not a libcurl tutorial */ + sprintf(curl_command, + "curl -s \"https://api.github.com/repos/%s/%s/commits\" > %s", + username, repo, output_filename); + sprintf(cleanup_command, "rm -f %s", output_filename); + system(curl_command); + + /* parsing json and validating output */ + root_value = json_parse_file(output_filename); + if (json_value_get_type(root_value) != JSONArray) { + system(cleanup_command); + return; + } + + /* getting array from root value and printing commit info */ + commits = json_value_get_array(root_value); + printf("%-10.10s %-10.10s %s\n", "Date", "SHA", "Author"); + for (i = 0; i < json_array_get_count(commits); i++) { + commit = json_array_get_object(commits, i); + printf("%.10s %.10s %s\n", + json_object_dotget_string(commit, "commit.author.date"), + json_object_get_string(commit, "sha"), + json_object_dotget_string(commit, "commit.author.name")); + } + + /* cleanup code */ + json_value_free(root_value); + system(cleanup_command); +} + +void persistence_example(void) { + JSON_Value *schema = json_parse_string("{\"name\":\"\"}"); + JSON_Value *user_data = json_parse_file("user_data.json"); + char buf[256]; + const char *name = NULL; + if (user_data == NULL || json_validate(schema, user_data) != JSONSuccess) { + puts("Enter your name:"); + scanf("%s", buf); + user_data = json_value_init_object(); + json_object_set_string(json_object(user_data), "name", buf); + json_serialize_to_file(user_data, "user_data.json"); + } + name = json_object_get_string(json_object(user_data), "name"); + printf("Hello, %s.", name); + json_value_free(schema); + json_value_free(user_data); + return; +} + +void serialization_example(void) { + JSON_Value *root_value = json_value_init_object(); + JSON_Object *root_object = json_value_get_object(root_value); + char *serialized_string = NULL; + json_object_set_string(root_object, "name", "John Smith"); + json_object_set_number(root_object, "age", 25); + json_object_dotset_string(root_object, "address.city", "Cupertino"); + json_object_dotset_value(root_object, "contact.emails", + json_parse_string("[\"email@example.com\", \"email2@example.com\"]")); + serialized_string = json_serialize_to_string_pretty(root_value); + puts(serialized_string); + json_free_serialized_string(serialized_string); + json_value_free(root_value); +} + +static char * read_file(const char * filename) { + FILE *fp = fopen(filename, "r"); + size_t file_size; + long pos; + char *file_contents; + if (!fp) + return NULL; + fseek(fp, 0L, SEEK_END); + pos = ftell(fp); + if (pos < 0) { + fclose(fp); + return NULL; + } + file_size = pos; + rewind(fp); + file_contents = (char*)malloc(sizeof(char) * (file_size + 1)); + if (!file_contents) { + fclose(fp); + return NULL; + } + if (fread(file_contents, file_size, 1, fp) < 1) { + if (ferror(fp)) { + fclose(fp); + free(file_contents); + return NULL; + } + } + fclose(fp); + file_contents[file_size] = '\0'; + return file_contents; +} diff --git a/parson-a1c356e/tests/test_1_1.txt b/parson-a1c356e/tests/test_1_1.txt new file mode 100644 index 0000000..da78ef4 --- /dev/null +++ b/parson-a1c356e/tests/test_1_1.txt @@ -0,0 +1,59 @@ + + [ + "JSON Test Pattern pass1", + {"object with 1 member":["array with 1 element"]}, + {}, + [], + -42, + true, + false, + null, + { + "integer": 1234567890, + "real": -9876.543210, + "e": 0.123456789e-12, + "E": 1.234567890E+34, + "": 23456789012E66, + "zero": 0, + "one": 1, + "space": " ", + "quote": "\"", + "backslash": "\\", + "controls": "\b\f\n\r\t", + "slash": "/ & \/", + "alpha": "abcdefghijklmnopqrstuvwyz", + "ALPHA": "ABCDEFGHIJKLMNOPQRSTUVWYZ", + "digit": "0123456789", + "0123456789": "digit", + "special": "`1~!@#$%^&*()_+-={':[,]}|;.?", + "hex": "\u0123\u4567\u89AB\uCDEF\uabcd\uef4A", + "true": true, + "false": false, + "null": null, + "array":[ ], + "object":{ }, + "address": "50 St. James Street", + "url": "http://www.JSON.org/", + "comment": "// /* */": " ", + " s p a c e d " :[1,2 , 3 + +, + +4 , 5 , 6 ,7 ],"compact":[1,2,3,4,5,6,7], + "jsontext": "{\"object with 1 member\":[\"array with 1 element\"]}", + "quotes": "" \u0022 %22 0x22 034 "", + "\/\\\"\uCAFE\uBABE\uAB98\uFCDE\ubcda\uef4A\b\f\n\r\t`1~!@#$%^&*()_+-=[]{}|;:',./<>?" +: "A key can be any string" + }, + 0.5 ,98.6 +, +99.44 +, + +1066, +1e1, +0.1e1, +1e-1, +1e00,2e+00,2e-00 +,"rosebud"] \ No newline at end of file diff --git a/parson-a1c356e/tests/test_1_2.txt b/parson-a1c356e/tests/test_1_2.txt new file mode 100644 index 0000000..d3c63c7 --- /dev/null +++ b/parson-a1c356e/tests/test_1_2.txt @@ -0,0 +1 @@ +[[[[[[[[[[[[[[[[[[["Not too deep"]]]]]]]]]]]]]]]]]]] \ No newline at end of file diff --git a/parson-a1c356e/tests/test_1_3.txt b/parson-a1c356e/tests/test_1_3.txt new file mode 100644 index 0000000..4528d51 --- /dev/null +++ b/parson-a1c356e/tests/test_1_3.txt @@ -0,0 +1,6 @@ +{ + "JSON Test Pattern pass3": { + "The outermost value": "must be an object or array.", + "In this test": "It is an object." + } +} diff --git a/parson-a1c356e/tests/test_2.txt b/parson-a1c356e/tests/test_2.txt new file mode 100644 index 0000000..b8c7350 --- /dev/null +++ b/parson-a1c356e/tests/test_2.txt @@ -0,0 +1,31 @@ +{ + "string" : "lorem ipsum", + "utf string" : "\u006corem\u0020ipsum", + "utf-8 string": "あいうえお", + "surrogate string": "lorem\uD834\uDD1Eipsum\uD834\uDF67lorem", + "positive one" : 1, + "negative one" : -1, + "pi" : 3.14, + "hard to parse number" : -3.14e-4, + "boolean true" : true, + "boolean false" : false, + "null" : null, + "string array" : ["lorem", "ipsum"], + "x^2 array" : [0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100], + "/*" : null, + "object" : { "nested string" : "str", + "nested true" : true, + "nested false" : false, + "nested null" : null, + "nested number" : 123, + "nested array" : ["lorem", "ipsum"], + "nested object" : {"lorem": "ipsum"} + }, + "*/" : null, + "/**/" : "comment", + "//" : "comment", + "url" : "https:\/\/www.example.com\/search?q=12345", + "escaped chars" : "\" \\ \/", + "empty object" : {}, + "empty array" : [] +} diff --git a/parson-a1c356e/tests/test_2_comments.txt b/parson-a1c356e/tests/test_2_comments.txt new file mode 100644 index 0000000..5ecf129 --- /dev/null +++ b/parson-a1c356e/tests/test_2_comments.txt @@ -0,0 +1,41 @@ +/* + *Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor + *ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud + *dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. +*/ +// Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor +{ /* lorem ipsum */ + "string" : "lorem ipsum", // lorem ipsum + "utf string" : "\u006corem\u0020ipsum", // lorem ipsum // + "utf-8 string": "あいうえお", // /* lorem ipsum */ + "surrogate string": "lorem\uD834\uDD1Eipsum\uD834\uDF67lorem", + "positive one" : 1, + "negative one" : -1, + "pi" : 3.14, + "hard to parse number" : -3.14e-4, + "boolean true" : true, + "boolean false" : false, + "null" : null, + "string array" : ["lorem",/*in array*/"ipsum"], + "x^2 array" : [0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100], +/* "x^2 array" : [], +*/ + "/*" : null, + "object" : { "nested string" : "str", + "nested true" : /* lorem ipsum */ true, + "nested false" : false, + "nested null" : null, // lorem ipsum + "nested number" : 123, + "nested array" : ["lorem", "ipsum"], + "nested object" : {"lorem": "ipsum"} + }, + "*/" : null, + "/**/" : "comment", + "//" : "comment", + "url" : "https:\/\/www.example.com\/search?q=12345", + "escaped chars" : "\" \\ \/", + "empty object" : {}, + "empty array" : [] +} +/**/ +// \ No newline at end of file diff --git a/parson-a1c356e/tests/test_2_pretty.txt b/parson-a1c356e/tests/test_2_pretty.txt new file mode 100644 index 0000000..fe0adfd --- /dev/null +++ b/parson-a1c356e/tests/test_2_pretty.txt @@ -0,0 +1,49 @@ +{ + "string": "lorem ipsum", + "utf string": "lorem ipsum", + "utf-8 string": "あいうえお", + "surrogate string": "lorem𝄞ipsum𝍧lorem", + "positive one": 1, + "negative one": -1, + "pi": 3.140000, + "hard to parse number": -0.000314, + "boolean true": true, + "boolean false": false, + "null": null, + "string array": [ + "lorem", + "ipsum" + ], + "x^2 array": [ + 0, + 1, + 4, + 9, + 16, + 25, + 36, + 49, + 64, + 81, + 100 + ], + "\/*": null, + "object": { + "nested string": "str", + "nested true": true, + "nested false": false, + "nested null": null, + "nested number": 123, + "nested array": [ + "lorem", + "ipsum" + ] + }, + "*\/": null, + "\/**\/": "comment", + "\/\/": "comment", + "url": "https:\/\/www.example.com\/search?q=12345", + "escaped chars": "\" \\ \/", + "empty object": {}, + "empty array": [] +} \ No newline at end of file diff --git a/parson-a1c356e/tests/test_5.txt b/parson-a1c356e/tests/test_5.txt new file mode 100644 index 0000000..fc71450 --- /dev/null +++ b/parson-a1c356e/tests/test_5.txt @@ -0,0 +1,15 @@ +{ + "first": "John", + "last": "Doe", + "age": 25, + "registered": true, + "interests": [ "Reading", "Mountain Biking" ], + "favorites": { + "color": "blue", + "sport": "running" + }, + "utf string" : "\u006corem\u0020ipsum", + "utf-8 string": "あいうえお", + "surrogate string": "lorem\uD834\uDD1Eipsum\uD834\uDF67lorem", + "windows path": "C:\\Windows\\Path" +} \ No newline at end of file From b6e9fc1ba757bbfe29d5b84f0e418784a9497dc2 Mon Sep 17 00:00:00 2001 From: Christian Lindig Date: Mon, 8 Aug 2016 20:20:21 +0100 Subject: [PATCH 6/8] CP-18272 change signature of rrd_sample to support testing The commit changes the signature of rrd_sample to int rrd_sample(RRD_PLUGIN * plugin, time_t (*t)(time_t*)); The caller can provide a function to obtain a time stamp or provide NULL (the typical case). This permits to control the time stamps in RRD files for debugging and testing. Signed-off-by: Christian Lindig --- Makefile | 6 ------ README.md | 10 ++++++---- librrd.c | 6 +++--- librrd.h | 10 +++++++--- rrdclient.c | 2 +- rrdtest.c | 6 +++--- 6 files changed, 20 insertions(+), 20 deletions(-) diff --git a/Makefile b/Makefile index 893ea37..8a2d916 100644 --- a/Makefile +++ b/Makefile @@ -55,12 +55,6 @@ indent: librrd.h librrd.c rrdtest.c depend: librrd.c rrdtest.c $(CC) -MM $^ -.PHONY: parson -parson: - # git submodule add https://github.com/kgabis/parson.git - git submodule init - git submodule update - %.o: %.c $(CC) $(CFLAGS) -c -o $@ $< diff --git a/README.md b/README.md index d6e114e..1c485db 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ library: int rrd_close(RRD_PLUGIN * plugin); int rrd_add_src(RRD_PLUGIN * plugin, RRD_SOURCE * source); int rrd_del_src(RRD_PLUGIN * plugin, RRD_SOURCE * source); - int rrd_sample(RRD_PLUGIN * plugin); + int rrd_sample(RRD_PLUGIN * plugin, time_t (*t)(time_t*)); A plugin reports streams of data to the RRD service. Each such stream is represented as an `RRD_SOURCE` value. An `RRD_PLUGIN` @@ -83,7 +83,7 @@ All strings are in UTF8 encoding. The library implements the following policy to manage memory: it does not free any memory that is hasn't itself allocated. This means, if the client passes dynamically allocated data into the library, it is the client's responsibility to de-allocate -it. +it. ## Open, Sample, Close @@ -102,12 +102,14 @@ considered private to the library. <>= RRD_PLUGIN *rrd_open(char *name, rrd_domain_t domain, char *path); int rrd_close(RRD_PLUGIN * plugin); - int rrd_sample(RRD_PLUGIN * plugin); + int rrd_sample(RRD_PLUGIN * plugin, time_t (*t)(time_t*)); The name of the plugin is descriptive, as whether it reports data for a single machine (`RRD_LOCAL_DOMAIN`) or multiple -(`RRD_INTER_DOMAIN`). +(`RRD_INTER_DOMAIN`). The second parameter of `rrd_sample` is typically +NULL. If it isn't, it us used to obtain a timestamp instead of using +time(3). ## Data Sources diff --git a/librrd.c b/librrd.c index b24467d..2da1460 100644 --- a/librrd.c +++ b/librrd.c @@ -26,7 +26,6 @@ #include #include #include -#include #include #include #include @@ -400,7 +399,7 @@ write_exact(int fd, const void *data, size_t size) * first. */ int -rrd_sample(RRD_PLUGIN * plugin) +rrd_sample(RRD_PLUGIN * plugin, time_t (*t)(time_t*)) { assert(plugin); JSON_Value *json; @@ -440,7 +439,8 @@ rrd_sample(RRD_PLUGIN * plugin) /* * update timestamp, calculate crc */ - header->rrd_timestamp = htonll((uint64_t) time(NULL)); + + header->rrd_timestamp = htonll((uint64_t) (t ? t(NULL) : time(NULL))); uint32_t crc = crc32(0L, Z_NULL, 0); crc = crc32(crc, (unsigned char *) &header->rrd_timestamp, diff --git a/librrd.h b/librrd.h index 2f18aad..f91d328 100644 --- a/librrd.h +++ b/librrd.h @@ -23,6 +23,7 @@ #include +#include #include "parson/parson.h" #define RRD_MAX_SOURCES 128 @@ -124,7 +125,10 @@ int rrd_del_src(RRD_PLUGIN * plugin, RRD_SOURCE * source); /* * calling rrd_sample(plugin) triggers that all data sources are sampled - * and the results are reported to the RRD daemon. This function needs to - * be called every 5 seconds by the client, + * and the results are reported to the RRD daemon. This function needs + * to be called every 5 seconds by the client. The second parameter is + * typically NULL. If it isn't, it will be used instead of time(3) to + * obtain the time stamp that is written to the RRD file. This can be + * used to create RRD files that don't depend on the current time. */ -int rrd_sample(RRD_PLUGIN * plugin); +int rrd_sample(RRD_PLUGIN * plugin, time_t (*t)(time_t*)); diff --git a/rrdclient.c b/rrdclient.c index 7431659..ea848e1 100644 --- a/rrdclient.c +++ b/rrdclient.c @@ -73,7 +73,7 @@ main(int argc, char **argv) int rc; while (fgets(line, sizeof(line), stdin) != NULL) { v.int64 = (int64_t) atol(line); - rc = rrd_sample(plugin); + rc = rrd_sample(plugin, NULL); assert(rc == RRD_OK); } diff --git a/rrdtest.c b/rrdtest.c index 5b58b0e..d5571ac 100644 --- a/rrdtest.c +++ b/rrdtest.c @@ -82,7 +82,7 @@ main(int argc, char **argv) printf("adding source: %s\n", src[0].name); rrd_add_src(plugin, &src[0]); - rc = rrd_sample(plugin); + rc = rrd_sample(plugin, NULL); assert(rc == RRD_OK); src[1].name = "second"; @@ -99,12 +99,12 @@ main(int argc, char **argv) printf("adding source: %s\n", src[1].name); rrd_add_src(plugin, &src[1]); - rc = rrd_sample(plugin); + rc = rrd_sample(plugin, NULL); assert(rc == RRD_OK); printf("removing source: %s\n", src[0].name); rrd_del_src(plugin, &src[0]); - rc = rrd_sample(plugin); + rc = rrd_sample(plugin, NULL); assert(rc == RRD_OK); printf("removing source: %s\n", src[1].name); From b0d7513de2ee22a2b202e3f55b58eb4cfcd0b3ed Mon Sep 17 00:00:00 2001 From: Christian Lindig Date: Mon, 8 Aug 2016 20:30:57 +0100 Subject: [PATCH 7/8] CP-18272 split off integration tests into new target test-integration We would like to split tests into those that do not require many external dependencies (test) and those that do (test-integrate). Signed-off-by: Christian Lindig --- .travis.yml | 2 +- Makefile | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 81b8e8d..31caf6a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,6 +8,6 @@ matrix: os: linux script: - - make parson - make + - make test diff --git a/Makefile b/Makefile index 8a2d916..8d2a558 100644 --- a/Makefile +++ b/Makefile @@ -29,9 +29,12 @@ clean: cd ocaml; $(OCB) -clean .PHONY: test -test: rrdtest rrdclient rrdreader +test: rrdtest rrdclient ./rrdtest seq 1 10 | ./rrdclient rrdclient.rrd + +.PHONY: test-integration +test-integration: rrdreader seq 1 10 | while read i; do \ echo $$i | ./rrdclient rrdclient.rrd ;\ ./ocaml/rrdreader.native rrdclient.rrd ;\ From 85747c723ad8dc5d5533030e79bedde6770c38e6 Mon Sep 17 00:00:00 2001 From: Christian Lindig Date: Tue, 9 Aug 2016 13:01:42 +0100 Subject: [PATCH 8/8] CP-18271 remove OCaml code, use external rrdreader tool for testing The rrdreader tool has moved to the ocaml-rrd-transport 1.1.1 package on which we rely on anyway. This allows us to remove all OCaml code in this repository. Signed-off-by: Christian Lindig --- Makefile | 17 +++----- ocaml/rrdreader.ml | 100 --------------------------------------------- 2 files changed, 5 insertions(+), 112 deletions(-) delete mode 100644 ocaml/rrdreader.ml diff --git a/Makefile b/Makefile index 8d2a558..cd62b5a 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ # vim: set ts=8 sw=8 noet: # -# To run the tests you need OCaml and the ocaml-rrd-transport-devel +# To run the integration tests you need the ocaml-rrd-transport-devel # package: # # yum install -y ocaml-rrd-transport-devel @@ -34,15 +34,15 @@ test: rrdtest rrdclient seq 1 10 | ./rrdclient rrdclient.rrd .PHONY: test-integration -test-integration: rrdreader +test-integration: seq 1 10 | while read i; do \ echo $$i | ./rrdclient rrdclient.rrd ;\ - ./ocaml/rrdreader.native rrdclient.rrd ;\ + rrdreader file --once rrdclient.rrd v2 ;\ done seq 1 20 | while read i; do \ echo $$i | ./rrdclient rrdclient.rrd ;\ - sleep 1 ;\ - done & ./ocaml/rrdreader.native -l 2 rrdclient.rrd \ + sleep 4 ;\ + done & rrdreader file rrdclient.rrd v2 \ || echo "a final exception Rrd_protocol.No_update is OK" .PHONY: valgrind @@ -98,10 +98,3 @@ parson/parson.o: parson/parson.h rrdtest.o: parson/parson.h librrd.h librrd.o: parson/parson.h librrd.h -# OCaml test utility -# You need: yum install -y ocaml-rrd-transport-devel - -.PHONY: rrdreader -rrdreader: - cd ocaml; $(OCB) -pkgs rrd-transport,unix -tag thread rrdreader.native - diff --git a/ocaml/rrdreader.ml b/ocaml/rrdreader.ml deleted file mode 100644 index 87ef7f9..0000000 --- a/ocaml/rrdreader.ml +++ /dev/null @@ -1,100 +0,0 @@ -(* - * Copyright (c) 2016 Citrix - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY - * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - *) - -(* This is a small utility to test the librrd library *) - -open Rrd_protocol - -exception Error of string -let error fmt = Printf.kprintf (fun msg -> raise (Error msg)) fmt - -let finally opn cls = - let res = try opn () with exn -> cls (); raise exn in - cls (); - res - -let string_of_data_source owner ds = - let owner_string = match owner with - | Rrd.Host -> "Host" - | Rrd.SR sr -> "SR " ^ sr - | Rrd.VM vm -> "VM " ^ vm - in - let value_string = match ds.Ds.ds_value with - | Rrd.VT_Float f -> Printf.sprintf "float %f" f - | Rrd.VT_Int64 i -> Printf.sprintf "int64 %Ld" i - | Rrd.VT_Unknown -> Printf.sprintf "unknown" - in - let type_string = match ds.Ds.ds_type with - | Rrd.Absolute -> "absolute" - | Rrd.Gauge -> "gauge" - | Rrd.Derive -> "derive" - in - Printf.sprintf - "owner: %s\nname: %s\ntype: %s\nvalue: %s\nunits: %s" - owner_string ds.Ds.ds_name type_string value_string ds.Ds.ds_units - -let print payload = - print_endline "------------ Metadata ------------"; - Printf.printf "timestamp = %Ld\n%!" payload.timestamp; - print_endline "---------- Data sources ----------"; - List.iter - (fun (owner, ds) -> - print_endline (string_of_data_source owner ds); - print_endline "----------") - payload.datasources - -let read path = - let v2 = Rrd_protocol_v2.protocol in - let reader = Rrd_reader.FileReader.create path v2 in - finally - (fun () -> - reader.Rrd_reader.read_payload () - |> print) - (fun () -> - reader.Rrd_reader.cleanup ()) - -let read_loop seconds path = - let v2 = Rrd_protocol_v2.protocol in - let reader = Rrd_reader.FileReader.create path v2 in - let rec loop () = - ( reader.Rrd_reader.read_payload () |> print - ; Unix.sleep seconds - ; loop () - ) in - finally loop reader.Rrd_reader.cleanup - -let atoi str = - try int_of_string str with Failure _ -> error "not an int: %s" str - -let main () = - let args = Array.to_list Sys.argv in - let this = Sys.executable_name in - let printf = Printf.printf in - match args with - | [_;path] -> read path; exit 0 - | [_;"-l";s;path] -> read_loop (atoi s) path - | _ -> printf "usage: [-l secs] %s file.rrd\n" this; exit 1 - -let () = main () - -