diff options
-rw-r--r-- | Makefile | 2 | ||||
-rw-r--r-- | README.md | 7 | ||||
-rw-r--r-- | http_connection.h | 2 | ||||
-rw-r--r-- | http_response.h | 2 | ||||
-rw-r--r-- | json.h | 1015 | ||||
-rw-r--r-- | unittest.cpp | 75 |
6 files changed, 971 insertions, 132 deletions
@@ -15,7 +15,7 @@ unittest: unittest.cpp routing.h ./unittest covtest: unittest.cpp routing.h utility.h flask.h http_server.h http_connection.h parser.h http_response.h common.h json.h - g++ -O2 -Wall -g -std=c++11 -o covtest unittest.cpp http-parser/http_parser.c -pthread -lboost_system -lboost_thread -I http-parser/ + g++ -Wall -g -std=c++11 -o covtest --coverage unittest.cpp http-parser/http_parser.c -pthread -lboost_system -lboost_thread -I http-parser/ ./covtest gcov -r unittest.cpp @@ -2,3 +2,10 @@ Flask++ ======= Simple C++ HTTP Server (inspired by Python Flask) + + + +Other JSON library +================== + +https://github.com/d-led/picojson_serializer diff --git a/http_connection.h b/http_connection.h index e2c21ff..9166fb4 100644 --- a/http_connection.h +++ b/http_connection.h @@ -62,7 +62,7 @@ namespace flask if (res.body.empty() && res.json_value.t == json::type::Object) { - res.body = json::encode(res.json_value); + res.body = json::dump(res.json_value); } if (!statusCodes.count(res.code)) diff --git a/http_response.h b/http_response.h index b12c8a5..cf8e2ca 100644 --- a/http_response.h +++ b/http_response.h @@ -15,7 +15,7 @@ namespace flask explicit response(int code) : code(code) {} response(std::string body) : body(std::move(body)) {} response(json::wvalue&& json_value) : json_value(std::move(json_value)) {} - response(const json::wvalue& json_value) : body(json::encode(json_value)) {} + response(const json::wvalue& json_value) : body(json::dump(json_value)) {} response(int code, std::string body) : body(std::move(body)), code(code) {} response(response&& r) { @@ -1,14 +1,47 @@ #pragma once -#include <iostream> + +//#define FLASKPP_JSON_NO_ERROR_CHECK #include <string> #include <unordered_map> +#include <iostream> +#include <algorithm> +#include <memory> +#include <boost/lexical_cast.hpp> +#include <boost/algorithm/string/predicate.hpp> +#include <boost/operators.hpp> + +#ifdef __GNUG__ +#define flask_json_likely(x) __builtin_expect(x, 1) +#define flask_json_unlikely(x) __builtin_expect(x, 0) +#else +#ifdef __clang__ +#define flask_json_likely(x) __builtin_expect(x, 1) +#define flask_json_unlikely(x) __builtin_expect(x, 0) +#else +#define flask_json_likely(x) x +#define flask_json_unlikely(x) x +#endif +#endif + namespace flask { namespace json { - enum class type : intptr_t + std::string escape(const std::string& str) + { + // TODO + return str; + } + std::string unescape(const std::string& str) + { + // TODO + return str; + } + + + enum class type : char { Null, False, @@ -19,192 +52,904 @@ namespace flask Object, }; - const char* json_type_str = - "\0\0\0\0\0\0\0\0" - "\1\0\0\0\0\0\0\0" - "\2\0\0\0\0\0\0\0" - "\3\0\0\0\0\0\0\0" - "\4\0\0\0\0\0\0\0" - "\5\0\0\0\0\0\0\0" - "\6\0\0\0\0\0\0\0"; + class rvalue; + rvalue load_copy(const char* data, size_t size); - struct r_string + namespace detail { - operator std::string () + + struct r_string + : boost::less_than_comparable<r_string>, + boost::less_than_comparable<r_string, std::string>, + boost::equality_comparable<r_string>, + boost::equality_comparable<r_string, std::string> + { + r_string() {}; + r_string(const char* s, uint32_t length, uint8_t has_escaping) + : s_(s), length_(length), has_escaping_(has_escaping) + {}; + ~r_string() + { + if (owned_) + delete[] s_; + } + + r_string(const r_string& r) + { + *this = r; + } + + r_string(r_string&& r) + { + *this = r; + } + + r_string& operator = (r_string&& r) + { + s_ = r.s_; + length_ = r.length_; + has_escaping_ = r.has_escaping_; + owned_ = r.owned_; + return *this; + } + + r_string& operator = (const r_string& r) + { + s_ = r.s_; + length_ = r.length_; + has_escaping_ = r.has_escaping_; + owned_ = 0; + return *this; + } + + operator std::string () const + { + return unescape(); + } + + std::string unescape() const + { + // TODO + return std::string(begin(), end()); + } + + const char* begin() const { return s_; } + const char* end() const { return s_+length_; } + + using iterator = const char*; + using const_iterator = const char*; + + const char* s_; + uint32_t length_; + uint8_t has_escaping_; + uint8_t owned_{0}; + friend std::ostream& operator << (std::ostream& os, const r_string& s) + { + os << (std::string)s; + return os; + } + private: + void force(const char* s, uint32_t length) + { + s_ = s; + length_ = length; + owned_ = 1; + } + friend rvalue flask::json::load_copy(const char* data, size_t size); + }; + + bool operator < (const r_string& l, const r_string& r) { - return std::string(s_, e_); + return boost::lexicographical_compare(l,r); } - char* s_; - char* e_; - }; + bool operator < (const r_string& l, const std::string& r) + { + return boost::lexicographical_compare(l,r); + } + + bool operator > (const r_string& l, const std::string& r) + { + return boost::lexicographical_compare(r,l); + } - struct rvalue + bool operator == (const r_string& l, const r_string& r) + { + return boost::equals(l,r); + } + + bool operator == (const r_string& l, const std::string& r) + { + return boost::equals(l,r); + } + } + + class rvalue { - rvalue() {} - rvalue(type t) - : start_{json_type_str + 8*(int)t}, - end_{json_type_str+8*(int)t+8} + static const int error_bit = 4; + public: + rvalue() noexcept : option_{error_bit} + {} + rvalue(type t) noexcept + : lsize_{}, lremain_{}, t_{t} + {} + rvalue(type t, const char* s, const char* e) noexcept + : start_{s}, + end_{e}, + t_{t} + {} + rvalue(type t, const char* s, const char* e, uint8_t option) noexcept + : start_{s}, + end_{e}, + t_{t}, + option_{option} {} - ~rvalue() + + rvalue(const rvalue& r) + : start_(r.start_), + end_(r.end_), + key_(r.key_), + t_(r.t_), + option_(r.option_) { - deallocate(); + copy_l(r); } - void deallocate() + + rvalue(rvalue&& r) noexcept { - if (mem_) - { - delete[] mem_; - mem_ = nullptr; - } + *this = std::move(r); } - type t() const + + rvalue& operator = (const rvalue& r) { - return *reinterpret_cast<const type*>(start_); + start_ = r.start_; + end_ = r.end_; + key_ = r.key_; + copy_l(r); + t_ = r.t_; + option_ = r.option_; + return *this; } - const void* data() const + rvalue& operator = (rvalue&& r) noexcept { - return reinterpret_cast<const void*>(start_ + sizeof(intptr_t)); + start_ = r.start_; + end_ = r.end_; + key_ = std::move(r.key_); + l_ = std::move(r.l_); + lsize_ = r.lsize_; + lremain_ = r.lremain_; + t_ = r.t_; + option_ = r.option_; + return *this; } - int i() const + explicit operator bool() const noexcept { + return (option_ & error_bit) == 0; + } + + explicit operator int64_t() const + { + return i(); + } + + explicit operator int() const + { + return i(); + } + + type t() const + { +#ifndef FLASKPP_JSON_NO_ERROR_CHECK + if (option_ & error_bit) + { + throw std::runtime_error("invalid json object"); + } +#endif + return t_; + } + + int64_t i() const + { +#ifndef FLASKPP_JSON_NO_ERROR_CHECK if (t() != type::Number) throw std::runtime_error("value is not number"); - return static_cast<int>(*(double*)data()); +#endif + return boost::lexical_cast<int64_t>(start_, end_-start_); } double d() const { +#ifndef FLASKPP_JSON_NO_ERROR_CHECK if (t() != type::Number) throw std::runtime_error("value is not number"); - return *(double*)data(); +#endif + return boost::lexical_cast<double>(start_, end_-start_); } - r_string s() const + detail::r_string s_raw() const { - if (t() != type::Number) - throw std::runtime_error("value is not number"); - return {(char*)data(),(char*)data() + sizeof(char*)}; +#ifndef FLASKPP_JSON_NO_ERROR_CHECK + if (t() != type::String) + throw std::runtime_error("value is not string"); +#endif + return detail::r_string{start_, (uint32_t)(end_-start_), has_escaping()}; } - const char* start_{}; - const char* end_{}; - char* mem_{}; + detail::r_string s() const + { +#ifndef FLASKPP_JSON_NO_ERROR_CHECK + if (t() != type::String) + throw std::runtime_error("value is not string"); +#endif + return detail::r_string{start_, (uint32_t)(end_-start_), has_escaping()}; + } + + bool has(const char* str) const + { + return has(std::string(str)); + } + + bool has(const std::string& str) const + { + struct Pred + { + bool operator()(const rvalue& l, const rvalue& r) const + { + return l.key_ < r.key_; + }; + bool operator()(const rvalue& l, const std::string& r) const + { + return l.key_ < r; + }; + bool operator()(const std::string& l, const rvalue& r) const + { + return l < r.key_; + }; + }; + if (!is_cached()) + { + std::sort(begin(), end(), Pred()); + set_cached(); + } + auto it = lower_bound(begin(), end(), str, Pred()); + return it != end() && it->key_ == str; + } + + rvalue* begin() const + { +#ifndef FLASKPP_JSON_NO_ERROR_CHECK + if (t() != type::Object && t() != type::List) + throw std::runtime_error("value is not a container"); +#endif + return l_.get(); + } + rvalue* end() const + { +#ifndef FLASKPP_JSON_NO_ERROR_CHECK + if (t() != type::Object && t() != type::List) + throw std::runtime_error("value is not a container"); +#endif + return l_.get()+lsize_; + } + + const detail::r_string& key() const + { + return key_; + } + + size_t size() const + { +#ifndef FLASKPP_JSON_NO_ERROR_CHECK + if (t() != type::Object && t() != type::List) + throw std::runtime_error("value is not a container"); +#endif + return lsize_; + } + + const rvalue& operator[](int index) const + { +#ifndef FLASKPP_JSON_NO_ERROR_CHECK + if (t() != type::List) + throw std::runtime_error("value is not a list"); + if (index >= (int)lsize_ || index < 0) + throw std::runtime_error("list out of bound"); +#endif + return l_[index]; + } + + const rvalue& operator[](size_t index) const + { +#ifndef FLASKPP_JSON_NO_ERROR_CHECK + if (t() != type::List) + throw std::runtime_error("value is not a list"); + if (index >= lsize_) + throw std::runtime_error("list out of bound"); +#endif + return l_[index]; + } + + const rvalue& operator[](const char* str) const + { + return this->operator[](std::string(str)); + } + + const rvalue& operator[](const std::string& str) const + { +#ifndef FLASKPP_JSON_NO_ERROR_CHECK + if (t() != type::Object) + throw std::runtime_error("value is not an object"); +#endif + struct Pred + { + bool operator()(const rvalue& l, const rvalue& r) const + { + return l.key_ < r.key_; + }; + bool operator()(const rvalue& l, const std::string& r) const + { + return l.key_ < r; + }; + bool operator()(const std::string& l, const rvalue& r) const + { + return l < r.key_; + }; + }; + if (!is_cached()) + { + std::sort(begin(), end(), Pred()); + set_cached(); + } + auto it = lower_bound(begin(), end(), str, Pred()); + if (it != end() && it->key_ == str) + return *it; +#ifndef FLASKPP_JSON_NO_ERROR_CHECK + throw std::runtime_error("cannot find key"); +#else + static rvalue nullValue; + return nullValue; +#endif + } + + void set_error() + { + option_|=error_bit; + } + + bool error() const + { + return (option_&error_bit)!=0; + } + private: + bool has_escaping() const + { + return (option_&1)!=0; + } + bool is_cached() const + { + return (option_&2)!=0; + } + void set_cached() const + { + option_ |= 2; + } + void copy_l(const rvalue& r) + { + lsize_ = r.lsize_; + lremain_ = 0; + l_.reset(new rvalue[lsize_]); + std::copy(r.begin(), r.end(), begin()); + } + + void emplace_back(rvalue&& v) + { + if (!lremain_) + { + int new_size = lsize_ + lsize_; + if (new_size - lsize_ > 60000) + new_size = lsize_ + 60000; + if (new_size < 4) + new_size = 4; + rvalue* p = new rvalue[new_size]; + rvalue* p2 = p; + for(auto& x : *this) + *p2++ = std::move(x); + l_.reset(p); + lremain_ = new_size - lsize_; + } + l_[lsize_++] = std::move(v); + lremain_ --; + } + + const char* start_; + const char* end_; + detail::r_string key_; + std::unique_ptr<rvalue[]> l_; + uint32_t lsize_; + uint16_t lremain_; + type t_; + mutable uint8_t option_{0}; + + friend rvalue load_nocopy(const char* data, size_t size); + friend rvalue load_copy(const char* data, size_t size); + friend std::ostream& operator <<(std::ostream& os, const rvalue& r) + { + switch(r.t_) + { + + case type::Null: os << "null"; break; + case type::False: os << "false"; break; + case type::True: os << "true"; break; + case type::Number: os << r.d(); break; + case type::String: os << '"' << r.s_raw() << '"'; break; + case type::List: + { + os << '['; + bool first = true; + for(auto& x : r) + { + if (!first) + os << ','; + first = false; + os << x; + } + os << ']'; + } + break; + case type::Object: + { + os << '{'; + bool first = true; + for(auto& x : r) + { + if (!first) + os << ','; + os << '"' << escape(r.key_) << '"'; + first = false; + os << x; + } + os << '}'; + } + break; + } + return os; + } }; + bool operator == (const rvalue& l, const std::string& r) + { + return l.s() == r; + } + + bool operator == (const std::string& l, const rvalue& r) + { + return l == r.s(); + } + + bool operator != (const rvalue& l, const std::string& r) + { + return l.s() != r; + } + + bool operator != (const std::string& l, const rvalue& r) + { + return l != r.s(); + } + + bool operator == (const rvalue& l, double r) + { + return l.d() == r; + } + + bool operator == (double l, const rvalue& r) + { + return l == r.d(); + } + + bool operator != (const rvalue& l, double r) + { + return l.d() != r; + } + + bool operator != (double l, const rvalue& r) + { + return l != r.d(); + } + + //inline rvalue decode(const std::string& s) //{ //} - - /*inline boost::optional<const rvalue> decode(const char* data, size_t size, bool partial = false) + inline rvalue load_nocopy(const char* data, size_t size) { - static char* escaped = "\"\\\/\b\f\n\r\t" - const char* e = data + size; - auto ws_skip = [&] + //static const char* escaped = "\"\\/\b\f\n\r\t"; + struct Parser + { + Parser(const char* data, size_t size) + : data(data) + { + } + + bool consume(char c) { - while(data != e) - { - while(data != e && *data == ' ') ++data; - while(data != e && *data == '\t') ++data; - while(data != e && *data == '\r') ++data; - while(data != e && *data == '\n') ++data; - } + if (flask_json_unlikely(*data != c)) + return false; + data++; + return true; + } + + void ws_skip() + { + while(*data == ' ' || *data == '\t' || *data == '\r' || *data == '\n') ++data; }; - auto decode_string = [&]->boost::optional<rvalue> + + rvalue decode_string() { - ws_skip(); - if (data == e || *data != '"') + if (flask_json_unlikely(!consume('"'))) return {}; - while(data != e) + const char* start = data; + uint8_t has_escaping = 0; + while(1) { - if (*data == '"') + if (flask_json_likely(*data != '"' && *data != '\\')) { + data ++; } - else if (*data == '\\') + else if (*data == '"') { - // TODO - //if (data+1 == e) - //return {}; - //data += 2; + data++; + return {type::String, start, data-1, has_escaping}; } - else + else if (*data == '\\') { - data ++; + has_escaping = 1; + // TODO + data++; + switch(*data) + { + case 'u': + data += 4; + // TODO + break; + case '"': + case '\\': + case '/': + case 'b': + case 'f': + case 'n': + case 'r': + case 't': + data ++; + break; + default: + return {}; + } } } - }; - auto decode_list = [&]->boost::optional<rvalue> + return {}; + } + + rvalue decode_list() { - // TODO - }; - auto decode_number = [&]->boost::optional<rvalue> + rvalue ret(type::List); + if (flask_json_unlikely(!consume('['))) + { + ret.set_error(); + return ret; + } + ws_skip(); + if (flask_json_unlikely(*data == ']')) + { + data++; + return ret; + } + + while(1) + { + auto v = decode_value(); + if (flask_json_unlikely(!v)) + { + ret.set_error(); + break; + } + ws_skip(); + ret.emplace_back(std::move(v)); + if (*data == ']') + { + data++; + break; + } + if (flask_json_unlikely(!consume(','))) + { + ret.set_error(); + break; + } + ws_skip(); + } + return ret; + } + + rvalue decode_number() { - // TODO - }; - auto decode_value = [&]->boost::optional<rvalue> + const char* start = data; + + enum NumberParsingState + { + Minus, + AfterMinus, + ZeroFirst, + Digits, + DigitsAfterPoints, + E, + DigitsAfterE, + Invalid, + } state{Minus}; + while(flask_json_likely(state != Invalid)) + { + switch(*data) + { + case '0': + state = (NumberParsingState)"\2\2\7\3\4\6\6"[state]; + /*if (state == NumberParsingState::Minus || state == NumberParsingState::AfterMinus) + { + state = NumberParsingState::ZeroFirst; + } + else if (state == NumberParsingState::Digits || + state == NumberParsingState::DigitsAfterE || + state == NumberParsingState::DigitsAfterPoints) + { + // ok; pass + } + else if (state == NumberParsingState::E) + { + state = NumberParsingState::DigitsAfterE; + } + else + return {};*/ + break; + case '1': case '2': case '3': + case '4': case '5': case '6': + case '7': case '8': case '9': + state = (NumberParsingState)"\3\3\7\3\4\6\6"[state]; + while(*(data+1) >= '0' && *(data+1) <= '9') data++; + /*if (state == NumberParsingState::Minus || state == NumberParsingState::AfterMinus) + { + state = NumberParsingState::Digits; + } + else if (state == NumberParsingState::Digits || + state == NumberParsingState::DigitsAfterE || + state == NumberParsingState::DigitsAfterPoints) + { + // ok; pass + } + else if (state == NumberParsingState::E) + { + state = NumberParsingState::DigitsAfterE; + } + else + return {};*/ + break; + case '.': + state = (NumberParsingState)"\7\7\7\4\7\7\7"[state]; + /* + if (state == NumberParsingState::Digits) + { + state = NumberParsingState::DigitsAfterPoints; + } + else + return {}; + */ + break; + case '-': + state = (NumberParsingState)"\1\7\7\7\7\6\7"[state]; + /*if (state == NumberParsingState::Minus) + { + state = NumberParsingState::AfterMinus; + } + else if (state == NumberParsingState::E) + { + state = NumberParsingState::DigitsAfterE; + } + else + return {};*/ + break; + case '+': + state = (NumberParsingState)"\7\7\7\7\7\6\7"[state]; + /*if (state == NumberParsingState::E) + { + state = NumberParsingState::DigitsAfterE; + } + else + return {};*/ + break; + case 'e': case 'E': + state = (NumberParsingState)"\7\7\7\5\5\7\7"[state]; + /*if (state == NumberParsingState::Digits || + state == NumberParsingState::DigitsAfterPoints) + { + state = NumberParsingState::E; + } + else + return {};*/ + break; + default: + if (flask_json_likely(state == NumberParsingState::ZeroFirst || + state == NumberParsingState::Digits || + state == NumberParsingState::DigitsAfterPoints || + state == NumberParsingState::DigitsAfterE)) + return {type::Number, start, data}; + else + return {}; + } + data++; + } + + return {}; + } + + rvalue decode_value() { - if (data == e) - return {}; switch(*data) { - case '"': - emit_str_begin(); + case '[': + return decode_list(); case '{': + return decode_object(); + case '"': + return decode_string(); case 't': - if (e-data >= 4 && + if (//e-data >= 4 && data[1] == 'r' && data[2] == 'u' && data[3] == 'e') - return rvalue(type::True); + { + data += 4; + return {type::True}; + } else return {}; case 'f': + if (//e-data >= 5 && + data[1] == 'a' && + data[2] == 'l' && + data[3] == 's' && + data[4] == 'e') + { + data += 5; + return {type::False}; + } + else + return {}; case 'n': + if (//e-data >= 4 && + data[1] == 'u' && + data[2] == 'l' && + data[3] == 'l') + { + data += 4; + return {type::Null}; + } + else + return {}; + //case '1': case '2': case '3': + //case '4': case '5': case '6': + //case '7': case '8': case '9': + //case '0': case '-': + default: + return decode_number(); } - // TODO return {}; - }; - auto decode_object =[&]->boost::optional<rvalue> + } + + rvalue decode_object() { - ws_skip(); - if (data == e || *data != '{') - return {}; + rvalue ret(type::Object); + if (flask_json_unlikely(!consume('{'))) + { + ret.set_error(); + return ret; + } + + ws_skip(); + + if (flask_json_unlikely(*data == '}')) + { + data++; + return ret; + } - ws_skip(); - if (data == e) - return {}; - if (*data == '}') - return rvalue(type::Object); while(1) { auto t = decode_string(); - ws_skip(); - if (data == e) - return {}; - if (data != ':') - return {}; - auto v = decode_value(); - ws_skip(); - if (data == e) - return {}; - if (data == '}') + if (flask_json_unlikely(!t)) + { + ret.set_error(); break; - if (data != ',') - return {}; - } - // TODO build up object - throw std::runtime_error("Not implemented"); - return rvalue(type::Object); - }; + } - return decode_object(); + ws_skip(); + if (flask_json_unlikely(!consume(':'))) + { + ret.set_error(); + break; + } + // TODO caching key to speed up (flyweight?) + auto key = t.s(); + ws_skip(); + auto v = decode_value(); + if (flask_json_unlikely(!v)) + { + ret.set_error(); + break; } + ws_skip(); + + v.key_ = std::move(key); + ret.emplace_back(std::move(v)); + if (flask_json_unlikely(*data == '}')) + { + data++; + break; + } + if (flask_json_unlikely(!consume(','))) + { + ret.set_error(); + break; + } + ws_skip(); + } + return ret; } - } - }*/ + + rvalue parse() + { + //auto ret = decode_object(); + ws_skip(); + auto ret = decode_value(); + ws_skip(); + if (*data != '\0') + ret.set_error(); + return ret; + } + + const char* data; + }; + return Parser(data, size).parse(); + } + inline rvalue load_copy(const char* data, size_t size) + { + char* s = new char[size+1]; + memcpy(s, data, size+1); + auto ret = load_nocopy(s, size); + if (ret) + ret.key_.force(s, size); + else + delete[] s; + return ret; + } + + inline rvalue load_copy(const char* data) + { + return load_copy(data, strlen(data)); + } + + inline rvalue load_copy(const std::string& str) + { + return load_copy(str.data(), str.size()); + } + + inline rvalue load_nocopy(const char* data) + { + return load_nocopy(data, strlen(data)); + } + + inline rvalue load_nocopy(const std::string& str) + { + return load_nocopy(str.data(), str.size()); + } + class wvalue { @@ -335,6 +1080,24 @@ namespace flask return *this; } + template <typename T> + wvalue& operator[](const std::vector<T>& v) + { + if (t != type::List) + reset(); + t = type::List; + if (!l) + l = std::move(std::unique_ptr<std::vector<wvalue>>(new std::vector<wvalue>{})); + l->clear(); + l->resize(v.size()); + size_t idx = 0; + for(auto& x:v) + { + (*l)[idx++] = x; + } + return *this; + } + wvalue& operator[](unsigned index) { if (t != type::List) @@ -342,7 +1105,8 @@ namespace flask t = type::List; if (!l) l = std::move(std::unique_ptr<std::vector<wvalue>>(new std::vector<wvalue>{})); - l->resize(index+1); + if (l->size() < index+1) + l->resize(index+1); return (*l)[index]; } @@ -401,18 +1165,18 @@ namespace flask } - friend void encode_internal(const wvalue& v, std::string& out); - friend std::string encode(const wvalue& v); + friend void dump_internal(const wvalue& v, std::string& out); + friend std::string dump(const wvalue& v); }; - void encode_string(const std::string& str, std::string& out) + void dump_string(const std::string& str, std::string& out) { // TODO escaping out.push_back('"'); out += str; out.push_back('"'); } - void encode_internal(const wvalue& v, std::string& out) + void dump_internal(const wvalue& v, std::string& out) { switch(v.t) { @@ -420,7 +1184,7 @@ namespace flask case type::False: out += "false"; break; case type::True: out += "true"; break; case type::Number: out += boost::lexical_cast<std::string>(v.d); break; - case type::String: encode_string(v.s, out); break; + case type::String: dump_string(v.s, out); break; case type::List: { out.push_back('['); @@ -434,7 +1198,7 @@ namespace flask out.push_back(','); } first = false; - encode_internal(x, out); + dump_internal(x, out); } } out.push_back(']'); @@ -453,9 +1217,9 @@ namespace flask out.push_back(','); } first = false; - encode_string(kv.first, out); + dump_string(kv.first, out); out.push_back(':'); - encode_internal(kv.second, out); + dump_internal(kv.second, out); } } out.push_back('}'); @@ -464,16 +1228,19 @@ namespace flask } } - std::string encode(const wvalue& v) + std::string dump(const wvalue& v) { std::string ret; ret.reserve(v.estimate_length()); - encode_internal(v, ret); + dump_internal(v, ret); return ret; } - //std::vector<boost::asio::const_buffer> encode_ref(wvalue& v) + //std::vector<boost::asio::const_buffer> dump_ref(wvalue& v) //{ //} } } + +#undef flask_json_likely +#undef flask_json_unlikely diff --git a/unittest.cpp b/unittest.cpp index 48798d0..cf9db14 100644 --- a/unittest.cpp +++ b/unittest.cpp @@ -18,7 +18,7 @@ void error_print() } template <typename A, typename ...Args> -void error_print(A a, Args...args) +void error_print(const A& a, Args...args) { cerr<<a; error_print(args...); @@ -27,8 +27,21 @@ void error_print(A a, Args...args) template <typename ...Args> void fail(Args...args) { error_print(args...);failed__ = true; } -#define ASSERT_EQUAL(a, b) if (a != b) fail("Assert fail: expected ", (a), " actual " , b, ", " #a " == " #b ", at " __FILE__ ":",__LINE__) -#define ASSERT_NOTEQUAL(a, b) if (a != b) fail("Assert fail: not expected ", (a), ", " #a " != " #b ", at " __FILE__ ":",__LINE__) +#define ASSERT_TRUE(x) if (!(x)) fail("Assert fail: expected ", #x, " is true, at " __FILE__ ":",__LINE__) +#define ASSERT_EQUAL(a, b) if ((a) != (b)) fail("Assert fail: expected ", (a), " actual " , (b), ", " #a " == " #b ", at " __FILE__ ":",__LINE__) +#define ASSERT_NOTEQUAL(a, b) if ((a) != (b)) fail("Assert fail: not expected ", (a), ", " #a " != " #b ", at " __FILE__ ":",__LINE__) +#define ASSERT_THROW(x) \ + try \ + { \ + x; \ + fail("Assert fail: exception should be thrown"); \ + } \ + catch(std::exception&) \ + { \ + } + + + #define TEST(x) struct test##x:public Test{void test();}x##_; \ void test##x::test() #define DISABLE_TEST(x) struct test##x{void test();}x##_; \ @@ -236,17 +249,69 @@ TEST(multi_server) server2.stop(); } +TEST(json_read) +{ + { + auto x = json::load_copy("{} 3"); + if (x) + fail("should fail to parse"); + } + + auto x = json::load_copy(R"({"message":"hello, world"})"); + if (!x) + fail("fail to parse"); + ASSERT_EQUAL("hello, world", x["message"]); + ASSERT_EQUAL(1, x.size()); + ASSERT_EQUAL(false, x.has("mess")); + ASSERT_THROW(x["mess"]); + ASSERT_THROW(3 == x["message"]); + ASSERT_THROW(x["message"].size()); + + std::string s = R"({"int":3, "ints" :[1,2,3,4,5] })"; + auto y = json::load_nocopy(s); + ASSERT_EQUAL(3, y["int"]); + ASSERT_EQUAL(5, y["ints"].size()); + ASSERT_EQUAL(1, y["ints"][0]); + ASSERT_EQUAL(2, y["ints"][1]); + ASSERT_EQUAL(3, y["ints"][2]); + ASSERT_EQUAL(4, y["ints"][3]); + ASSERT_EQUAL(5, y["ints"][4]); + ASSERT_EQUAL(1u, y["ints"][0]); + ASSERT_EQUAL(1.f, y["ints"][0]); + + int q = (int)y["ints"][1]; + ASSERT_EQUAL(2, q); + q = y["ints"][2].i(); + ASSERT_EQUAL(3, q); + +} + TEST(json_write) { json::wvalue x; x["message"] = "hello world"; - ASSERT_EQUAL(R"({"message":"hello world"})", json::encode(x)); + ASSERT_EQUAL(R"({"message":"hello world"})", json::dump(x)); + x["message"] = std::string("string value"); + ASSERT_EQUAL(R"({"message":"string value"})", json::dump(x)); + x["message"]["x"] = 3; + ASSERT_EQUAL(R"({"message":{"x":3}})", json::dump(x)); + x["message"]["y"] = 5; + ASSERT_TRUE(R"({"message":{"x":3,"y":5}})" == json::dump(x) || R"({"message":{"y":5,"x":3}})" == json::dump(x)); + x["message"] = 5.5; + ASSERT_EQUAL(R"({"message":5.5})", json::dump(x)); json::wvalue y; y["scores"][0] = 1; y["scores"][1] = "king"; y["scores"][2] = 3.5; - ASSERT_EQUAL(R"({"scores":[1,"king",3.5]})", json::encode(y)); + ASSERT_EQUAL(R"({"scores":[1,"king",3.5]})", json::dump(y)); + + y["scores"][2][0] = "real"; + y["scores"][2][1] = false; + ASSERT_EQUAL(R"({"scores":[1,"king",["real",false]]})", json::dump(y)); + + y["scores"]["a"]["b"]["c"] = nullptr; + ASSERT_EQUAL(R"({"scores":{"a":{"b":{"c":null}}}})", json::dump(y)); } int testmain() |