├── TODO ├── lib └── .gitignore ├── tests ├── mocks │ ├── .gitignore │ ├── requirements.txt │ └── rest_webservice.py ├── build │ └── .gitignore ├── test_utf8.cpp ├── CMakeLists.txt ├── test_api.cpp ├── test_query.cpp ├── test_relation.cpp ├── test_json.cpp ├── test_field.cpp ├── test_mapper.cpp └── test_model.cpp ├── vendor ├── gtest │ ├── .gitignore │ └── install.cmake ├── curl │ ├── .gitignore │ └── borland-patch.cmake ├── iconv │ ├── .gitignore │ ├── patch.cmake │ └── CMakeLists.txt ├── yajl │ ├── .gitignore │ ├── patch.cmake │ └── CMakeLists.txt ├── .gitignore └── CMakeLists.txt ├── include ├── restful_mapper.h └── restful_mapper │ ├── meta.h │ ├── internal │ ├── utf8.h │ └── iso8601.h │ ├── helpers.h │ ├── model.h │ ├── query.h │ ├── relation.h │ ├── json.h │ ├── api.h │ ├── mapper.h │ ├── model_collection.h │ └── field.h ├── .travis.yml ├── Makefile ├── CHANGELOG ├── LICENSE ├── src ├── utf8.cpp ├── api.cpp └── json.cpp ├── CMakeLists.txt └── README.md /TODO: -------------------------------------------------------------------------------- 1 | Authorization (Flask-Principal) 2 | -------------------------------------------------------------------------------- /lib/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | 4 | -------------------------------------------------------------------------------- /tests/mocks/.gitignore: -------------------------------------------------------------------------------- 1 | *.db 2 | *.db-journal 3 | -------------------------------------------------------------------------------- /tests/build/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | 4 | -------------------------------------------------------------------------------- /vendor/gtest/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | !install.cmake 4 | -------------------------------------------------------------------------------- /vendor/curl/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | !borland-patch.cmake 4 | -------------------------------------------------------------------------------- /tests/mocks/requirements.txt: -------------------------------------------------------------------------------- 1 | Flask-Restless==0.12.1 2 | Flask-SQLAlchemy==0.16 3 | -------------------------------------------------------------------------------- /vendor/iconv/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | !CMakeLists.txt 4 | !patch.cmake 5 | -------------------------------------------------------------------------------- /vendor/yajl/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | !CMakeLists.txt 4 | !patch.cmake 5 | -------------------------------------------------------------------------------- /vendor/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !curl 3 | !gtest 4 | !iconv 5 | !yajl 6 | !.gitignore 7 | !CMakeLists.txt 8 | -------------------------------------------------------------------------------- /include/restful_mapper.h: -------------------------------------------------------------------------------- 1 | #ifndef RESTFUL_MAPPER_H 2 | #define RESTFUL_MAPPER_H 3 | 4 | #include 5 | 6 | #endif // RESTFUL_MAPPER_H 7 | 8 | -------------------------------------------------------------------------------- /include/restful_mapper/meta.h: -------------------------------------------------------------------------------- 1 | #ifndef RESTFUL_MAPPER_META_H 2 | #define RESTFUL_MAPPER_META_H 3 | 4 | #define VERSION "0.2.2" 5 | 6 | #endif // RESTFUL_MAPPER_META_H 7 | 8 | -------------------------------------------------------------------------------- /vendor/iconv/patch.cmake: -------------------------------------------------------------------------------- 1 | execute_process(COMMAND ${CMAKE_COMMAND} -E copy 2 | ${CMAKE_CURRENT_SOURCE_DIR}/../../CMakeLists.txt 3 | ${CMAKE_CURRENT_SOURCE_DIR}/CMakeLists.txt) 4 | 5 | -------------------------------------------------------------------------------- /vendor/yajl/patch.cmake: -------------------------------------------------------------------------------- 1 | execute_process(COMMAND ${CMAKE_COMMAND} -E copy 2 | ${CMAKE_CURRENT_SOURCE_DIR}/CMakeLists.txt 3 | ${CMAKE_CURRENT_SOURCE_DIR}/CMakeLists.txt.bak) 4 | 5 | execute_process(COMMAND ${CMAKE_COMMAND} -E copy 6 | ${CMAKE_CURRENT_SOURCE_DIR}/../../CMakeLists.txt 7 | ${CMAKE_CURRENT_SOURCE_DIR}/CMakeLists.txt) 8 | 9 | -------------------------------------------------------------------------------- /vendor/curl/borland-patch.cmake: -------------------------------------------------------------------------------- 1 | file(RENAME ${CMAKE_CURRENT_SOURCE_DIR}/CMakeLists.txt ${CMAKE_CURRENT_SOURCE_DIR}/CMakeLists.txt.bak) 2 | file(STRINGS ${CMAKE_CURRENT_SOURCE_DIR}/CMakeLists.txt.bak ORIG_FILE NEWLINE_CONSUME) 3 | file(WRITE ${CMAKE_CURRENT_SOURCE_DIR}/CMakeLists.txt "add_definitions(-DSIZEOF_SIZE_T=4)\n\n") 4 | file(APPEND ${CMAKE_CURRENT_SOURCE_DIR}/CMakeLists.txt ${ORIG_FILE}) 5 | 6 | -------------------------------------------------------------------------------- /include/restful_mapper/internal/utf8.h: -------------------------------------------------------------------------------- 1 | #ifndef RESTFUL_MAPPER_UTF8_H_20130326 2 | #define RESTFUL_MAPPER_UTF8_H_20130326 3 | 4 | #include 5 | #include 6 | 7 | extern std::string local_charset; 8 | std::string iconv_string(const std::string &value, const char *to, const char *from); 9 | std::string local_to_utf8(const std::string &value); 10 | std::string utf8_to_local(const std::string &value); 11 | 12 | #endif // RESTFUL_MAPPER_UTF8_H_20130326 13 | 14 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: cpp 2 | 3 | compiler: 4 | - gcc 5 | - clang 6 | 7 | python: "2.7" 8 | 9 | install: 10 | - sudo apt-get update >/dev/null 11 | - sudo apt-get -q install cmake valgrind 12 | - make vendor 13 | - sudo pip install -r tests/mocks/requirements.txt --use-mirrors 14 | 15 | before_script: 16 | - python tests/mocks/rest_webservice.py & 17 | 18 | script: 19 | - make test 20 | 21 | after_success: 22 | - valgrind --leak-check=yes ./tests/build/tests 23 | 24 | -------------------------------------------------------------------------------- /include/restful_mapper/helpers.h: -------------------------------------------------------------------------------- 1 | #ifndef RESTFUL_MAPPER_HELPERS_H 2 | #define RESTFUL_MAPPER_HELPERS_H 3 | 4 | #include 5 | #include 6 | 7 | #ifdef __GNUG__ 8 | # include 9 | #endif 10 | 11 | inline std::string type_info_name(const std::type_info &info) 12 | { 13 | # ifdef __GNUG__ 14 | int status; 15 | char *res = abi::__cxa_demangle(info.name(), NULL, NULL, &status); 16 | 17 | const char *const demangled_name = (status==0) ? res : info.name(); 18 | std::string ret_val(demangled_name); 19 | free(res); 20 | 21 | return ret_val; 22 | # else 23 | return info.name(); 24 | # endif 25 | } 26 | 27 | #endif // RESTFUL_MAPPER_HELPERS_H 28 | 29 | -------------------------------------------------------------------------------- /vendor/gtest/install.cmake: -------------------------------------------------------------------------------- 1 | execute_process(COMMAND ${CMAKE_COMMAND} -E copy_directory 2 | ${CMAKE_CURRENT_SOURCE_DIR}/../gtest/include 3 | ${CMAKE_CURRENT_SOURCE_DIR}/../../include) 4 | 5 | execute_process(COMMAND ${CMAKE_COMMAND} -E make_directory 6 | ${CMAKE_CURRENT_SOURCE_DIR}/../../lib) 7 | 8 | execute_process(COMMAND ${CMAKE_COMMAND} -E copy 9 | ${CMAKE_CURRENT_SOURCE_DIR}/${CMAKE_STATIC_LIBRARY_PREFIX}gtest${CMAKE_STATIC_LIBRARY_SUFFIX} 10 | ${CMAKE_CURRENT_SOURCE_DIR}/../../lib) 11 | 12 | execute_process(COMMAND ${CMAKE_COMMAND} -E copy 13 | ${CMAKE_CURRENT_SOURCE_DIR}/${CMAKE_STATIC_LIBRARY_PREFIX}gtest_main${CMAKE_STATIC_LIBRARY_SUFFIX} 14 | ${CMAKE_CURRENT_SOURCE_DIR}/../../lib) 15 | 16 | -------------------------------------------------------------------------------- /tests/test_utf8.cpp: -------------------------------------------------------------------------------- 1 | // -------------------------------------------------------------------------------- 2 | // Includes 3 | // -------------------------------------------------------------------------------- 4 | #include 5 | #include 6 | 7 | using namespace std; 8 | 9 | // -------------------------------------------------------------------------------- 10 | // Definitions 11 | // -------------------------------------------------------------------------------- 12 | TEST(Utf8Test, Encode) 13 | { 14 | local_charset = "latin1"; 15 | 16 | string latin1 = "Special \xf8 chars \xc6\xc6\xc6"; 17 | ASSERT_STREQ("Special \xc3\xb8 chars \xc3\x86\xc3\x86\xc3\x86", local_to_utf8(latin1).c_str()); 18 | 19 | latin1 = "\xf9"; 20 | ASSERT_STREQ("\xc3\xb9", local_to_utf8(latin1).c_str()); 21 | } 22 | 23 | TEST(Utf8Test, Decode) 24 | { 25 | local_charset = "latin1"; 26 | 27 | string utf8 = "Special \xc3\xb8 chars"; 28 | ASSERT_STREQ("Special \xf8 chars", utf8_to_local(utf8).c_str()); 29 | } 30 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: all test vendor clean 2 | 3 | all: 4 | cd build; cmake ${CMAKE_ARGS} -DCMAKE_INSTALL_PREFIX=$(CURDIR) ..; make all install 5 | 6 | test: all 7 | cd tests/build; cmake ${CMAKE_ARGS} ..; make 8 | ./tests/build/tests ${TEST_ARGS} 9 | 10 | vendor: 11 | cd vendor; cmake ${CMAKE_ARGS} .; make 12 | 13 | clean: 14 | rm -f build/*.* 15 | rm -f build/Makefile 16 | rm -f -r build/CMake* 17 | rm -f lib/*.* 18 | rm -f tests/build/*.* 19 | rm -f tests/build/Makefile 20 | rm -f -r tests/build/CMake* 21 | rm -f vendor/CMakeCache.txt 22 | rm -f vendor/cmake_install.cmake 23 | rm -f vendor/Makefile 24 | rm -f -r vendor/CMakeFiles 25 | rm -f -r vendor/curl/include 26 | rm -f -r vendor/curl/lib 27 | rm -f -r vendor/curl/src 28 | rm -f -r vendor/curl/tmp 29 | rm -f -r vendor/gtest/include 30 | rm -f -r vendor/gtest/lib 31 | rm -f -r vendor/gtest/src 32 | rm -f -r vendor/gtest/tmp 33 | rm -f -r vendor/iconv/include 34 | rm -f -r vendor/iconv/lib 35 | rm -f -r vendor/iconv/src 36 | rm -f -r vendor/iconv/tmp 37 | rm -f -r vendor/yajl/include 38 | rm -f -r vendor/yajl/lib 39 | rm -f -r vendor/yajl/src 40 | rm -f -r vendor/yajl/tmp 41 | 42 | -------------------------------------------------------------------------------- /CHANGELOG: -------------------------------------------------------------------------------- 1 | Version 0.2.2 2 | * Correct bug when receiving broken JSON for a validation error 3 | 4 | Version 0.2.1 5 | * Support nested validation errors 6 | 7 | Version 0.2.0 8 | * Added `clone` method 9 | * Abstracted `ModelCollection` class used by `HasMany` and `Model` 10 | * Added `find` method on collections for searching on primary key 11 | * Added `find` and `find_first` methods to search on fields 12 | * Switch to official flask-restless repo HEAD for tests 13 | * Changed interface function signatures for const-correctness 14 | * Merged all JSON code into a single module 15 | * Use long long for primary keys 16 | * Support `long long` fields 17 | * Added `reload_one` and `reload_many` methods to `Model` 18 | * Added comparison operators on `Model` 19 | * Added `contains` methods on model collection 20 | * Limit `Field` template class to support only pre-specified types 21 | * Added proper support for copying null values between fields 22 | * Added `emplace_clone` method for in-place cloning of models 23 | * Added `clone` method for model collections 24 | * Added `Foreign` and `BelongsTo` field types 25 | * Do not include parents and foreign keys to parents in json ouput 26 | when mapping child objects 27 | 28 | Version 0.1.2 29 | * First release 30 | 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013, Logan Raarup 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright notice, 10 | this list of conditions and the following disclaimer in the documentation 11 | and/or other materials provided with the distribution. 12 | * The names of the contributors may not be used to endorse or promote products 13 | derived from this software without specific prior written permission. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 19 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 21 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 22 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 23 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | 26 | -------------------------------------------------------------------------------- /tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.6) 2 | project(tests) 3 | 4 | set(BUILD_SHARED_LIBS OFF) 5 | 6 | if (CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUCXX) 7 | set(CMAKE_CXX_FLAGS "${CMAKE_C_FLAGS} -fprofile-arcs -ftest-coverage") 8 | set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fprofile-arcs -ftest-coverage") 9 | endif() 10 | 11 | include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../include) 12 | link_directories(${CMAKE_CURRENT_SOURCE_DIR}/../lib) 13 | 14 | include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../vendor/gtest/include) 15 | link_directories(${CMAKE_CURRENT_SOURCE_DIR}/../vendor/gtest/lib) 16 | 17 | include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../vendor/yajl/include) 18 | link_directories(${CMAKE_CURRENT_SOURCE_DIR}/../vendor/yajl/lib) 19 | 20 | include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../vendor/curl/include) 21 | link_directories(${CMAKE_CURRENT_SOURCE_DIR}/../vendor/curl/lib) 22 | 23 | include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../vendor/iconv/include) 24 | link_directories(${CMAKE_CURRENT_SOURCE_DIR}/../vendor/iconv/lib) 25 | 26 | add_executable( 27 | tests 28 | test_api.cpp 29 | test_field.cpp 30 | test_json.cpp 31 | test_mapper.cpp 32 | test_model.cpp 33 | test_query.cpp 34 | test_relation.cpp 35 | test_utf8.cpp) 36 | target_link_libraries(tests gtest gtest_main restful_mapper yajl) 37 | 38 | if (BORLAND) 39 | target_link_libraries(tests libcurl) 40 | else() 41 | target_link_libraries(tests curl) 42 | endif() 43 | 44 | if (WIN32) 45 | target_link_libraries(tests ws2_32 iconv charset) 46 | else() 47 | target_link_libraries(tests idn pthread) 48 | endif() 49 | 50 | -------------------------------------------------------------------------------- /vendor/yajl/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.6) 2 | project(yajl) 3 | 4 | SET (YAJL_MAJOR 2) 5 | SET (YAJL_MINOR 0) 6 | SET (YAJL_MICRO 4) 7 | 8 | if (BORLAND) 9 | add_definitions(-DLLONG_MAX=_I64_MAX) 10 | add_definitions(-DLLONG_MIN=_I64_MIN) 11 | endif() 12 | 13 | if (BORLAND OR MSYS OR MINGW) 14 | add_definitions(-Dsprintf_s=snprintf) 15 | endif() 16 | 17 | include_directories(${CMAKE_CURRENT_SOURCE_DIR}/src) 18 | 19 | configure_file(${CMAKE_CURRENT_SOURCE_DIR}/src/api/yajl_version.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/include/yajl/yajl_version.h) 20 | configure_file(${CMAKE_CURRENT_SOURCE_DIR}/src/api/yajl_common.h ${CMAKE_CURRENT_BINARY_DIR}/include/yajl/yajl_common.h) 21 | configure_file(${CMAKE_CURRENT_SOURCE_DIR}/src/api/yajl_gen.h ${CMAKE_CURRENT_BINARY_DIR}/include/yajl/yajl_gen.h) 22 | configure_file(${CMAKE_CURRENT_SOURCE_DIR}/src/api/yajl_parse.h ${CMAKE_CURRENT_BINARY_DIR}/include/yajl/yajl_parse.h) 23 | configure_file(${CMAKE_CURRENT_SOURCE_DIR}/src/api/yajl_tree.h ${CMAKE_CURRENT_BINARY_DIR}/include/yajl/yajl_tree.h) 24 | 25 | include_directories(${CMAKE_CURRENT_BINARY_DIR}/include) 26 | 27 | add_library(yajl src/yajl.c src/yajl_lex.c src/yajl_parser.c src/yajl_buf.c src/yajl_encode.c src/yajl_gen.c src/yajl_alloc.c src/yajl_tree.c src/yajl_version.c) 28 | 29 | install(TARGETS yajl DESTINATION lib) 30 | install(FILES ${CMAKE_CURRENT_BINARY_DIR}/include/yajl/yajl_version.h DESTINATION include/yajl) 31 | install(FILES ${CMAKE_CURRENT_BINARY_DIR}/include/yajl/yajl_common.h DESTINATION include/yajl) 32 | install(FILES ${CMAKE_CURRENT_BINARY_DIR}/include/yajl/yajl_gen.h DESTINATION include/yajl) 33 | install(FILES ${CMAKE_CURRENT_BINARY_DIR}/include/yajl/yajl_parse.h DESTINATION include/yajl) 34 | install(FILES ${CMAKE_CURRENT_BINARY_DIR}/include/yajl/yajl_tree.h DESTINATION include/yajl) 35 | 36 | -------------------------------------------------------------------------------- /vendor/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.6) 2 | project(vendor) 3 | include(ExternalProject) 4 | 5 | set(CURL_FLAGS -DBUILD_CURL_EXE=OFF -DBUILD_CURL_TESTS=OFF -DCURL_STATICLIB=ON -DCMAKE_USE_OPENSSL=OFF -DCURL_ZLIB=OFF -DHTTP_ONLY=ON) 6 | 7 | if (BORLAND) 8 | set(CURL_PATCH_COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_SOURCE_DIR}/curl/borland-patch.cmake) 9 | endif() 10 | 11 | if (WIN32) 12 | ExternalProject_Add( 13 | curl 14 | URL http://curl.haxx.se/download/curl-7.29.0.tar.gz 15 | PREFIX curl 16 | PATCH_COMMAND ${CURL_PATCH_COMMAND} 17 | CMAKE_ARGS -DBUILD_SHARED_LIBS=OFF -DCMAKE_INSTALL_PREFIX=${CMAKE_CURRENT_SOURCE_DIR}/curl ${CURL_FLAGS} 18 | ) 19 | endif() 20 | 21 | ExternalProject_Add( 22 | yajl 23 | URL https://github.com/lloyd/yajl/archive/2.0.4.tar.gz 24 | PREFIX yajl 25 | PATCH_COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_SOURCE_DIR}/yajl/patch.cmake 26 | CMAKE_ARGS -DBUILD_SHARED_LIBS=OFF -DCMAKE_INSTALL_PREFIX=${CMAKE_CURRENT_SOURCE_DIR}/yajl 27 | ) 28 | 29 | if (WIN32) 30 | ExternalProject_Add( 31 | iconv 32 | URL http://ftp.gnu.org/pub/gnu/libiconv/libiconv-1.14.tar.gz 33 | PREFIX iconv 34 | PATCH_COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_SOURCE_DIR}/iconv/patch.cmake 35 | CMAKE_ARGS -DBUILD_SHARED_LIBS=OFF -DCMAKE_INSTALL_PREFIX=${CMAKE_CURRENT_SOURCE_DIR}/iconv 36 | ) 37 | endif() 38 | 39 | if (WIN32) 40 | set(GTEST_FLAGS -Dgtest_disable_pthreads=1) 41 | endif() 42 | 43 | ExternalProject_Add( 44 | gtest 45 | URL https://github.com/google/googletest/archive/master.zip 46 | PREFIX gtest 47 | INSTALL_COMMAND ${CMAKE_COMMAND} -DCMAKE_STATIC_LIBRARY_PREFIX=${CMAKE_STATIC_LIBRARY_PREFIX} -DCMAKE_STATIC_LIBRARY_SUFFIX=${CMAKE_STATIC_LIBRARY_SUFFIX} -P ${CMAKE_CURRENT_SOURCE_DIR}/gtest/install.cmake 48 | CMAKE_ARGS -DBUILD_SHARED_LIBS=OFF -DCMAKE_INSTALL_PREFIX=${CMAKE_CURRENT_SOURCE_DIR}/gtest ${GTEST_FLAGS} 49 | ) 50 | 51 | -------------------------------------------------------------------------------- /vendor/iconv/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.6) 2 | project(iconv) 3 | 4 | # Build libcharset 5 | include_directories(${CMAKE_CURRENT_SOURCE_DIR}/libcharset) 6 | include_directories(${CMAKE_CURRENT_SOURCE_DIR}/libcharset/include) 7 | 8 | configure_file(${CMAKE_CURRENT_SOURCE_DIR}/libcharset/config.h.in ${CMAKE_CURRENT_SOURCE_DIR}/libcharset/config.h) 9 | configure_file(${CMAKE_CURRENT_SOURCE_DIR}/libcharset/include/localcharset.h.in ${CMAKE_CURRENT_SOURCE_DIR}/libcharset/include/localcharset.h) 10 | 11 | file(APPEND ${CMAKE_CURRENT_SOURCE_DIR}/libcharset/config.h "\n#define LIBDIR \"\"\n") 12 | file(APPEND ${CMAKE_CURRENT_SOURCE_DIR}/libcharset/config.h "\n#define HAVE_WORKING_O_NOFOLLOW 1\n") 13 | 14 | add_library(charset libcharset/lib/localcharset.c) 15 | 16 | install(TARGETS charset DESTINATION lib) 17 | install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/libcharset/include/localcharset.h DESTINATION include) 18 | 19 | # Build libiconv 20 | add_definitions(-DLIBICONV_PLUG) 21 | if (BORLAND) 22 | add_definitions(-w-par) 23 | add_definitions(-w-csu) 24 | add_definitions(-w-ccc) 25 | add_definitions(-w-rch) 26 | add_definitions(-w-aus) 27 | add_definitions(-w-eff) 28 | SET(EILSEQ 2) 29 | endif() 30 | 31 | include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include) 32 | include_directories(${CMAKE_CURRENT_SOURCE_DIR}/lib) 33 | 34 | configure_file(${CMAKE_CURRENT_SOURCE_DIR}/config.h.in ${CMAKE_CURRENT_SOURCE_DIR}/config.h) 35 | configure_file(${CMAKE_CURRENT_SOURCE_DIR}/include/iconv.h.in ${CMAKE_CURRENT_SOURCE_DIR}/include/iconv.h) 36 | configure_file(${CMAKE_CURRENT_SOURCE_DIR}/lib/config.h.in ${CMAKE_CURRENT_SOURCE_DIR}/lib/config.h) 37 | 38 | file(APPEND ${CMAKE_CURRENT_SOURCE_DIR}/config.h "\n#define ICONV_CONST \"\"\n") 39 | 40 | add_library(iconv lib/iconv.c) 41 | 42 | install(TARGETS iconv DESTINATION lib) 43 | install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/include/iconv.h DESTINATION include) 44 | -------------------------------------------------------------------------------- /src/utf8.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | using namespace std; 7 | 8 | string local_charset = ""; 9 | 10 | string iconv_string(const string &value, const char *to, const char *from) 11 | { 12 | string out; 13 | 14 | // Prepare source buffers 15 | size_t src_len = value.size(); 16 | char *src = new char[src_len]; 17 | value.copy(src, src_len); 18 | 19 | // Prepare destination buffer 20 | size_t buf_len = src_len + 3; 21 | size_t dst_len = buf_len; 22 | char *dst = new char[buf_len]; 23 | 24 | // Initialize iconv 25 | iconv_t conv = iconv_open(to, from); 26 | 27 | if (conv == (iconv_t)(-1)) 28 | { 29 | ostringstream s; 30 | s << "Unable to initialize iconv (error " << errno << ")"; 31 | throw runtime_error(s.str()); 32 | } 33 | 34 | // Perform conversion 35 | char *src_ptr = src; 36 | char *dst_ptr = dst; 37 | size_t *src_len_ptr = &src_len; 38 | size_t *dst_len_ptr = &dst_len; 39 | 40 | while (src_len) 41 | { 42 | size_t status = iconv(conv, &src_ptr, src_len_ptr, &dst_ptr, dst_len_ptr); 43 | 44 | if (status == (size_t)(-1)) 45 | { 46 | if (errno == E2BIG) 47 | { 48 | // Flush to output string 49 | out.append(dst, buf_len - dst_len); 50 | 51 | dst_ptr = dst; 52 | dst_len = buf_len; 53 | } 54 | else 55 | { 56 | iconv_close(conv); 57 | 58 | ostringstream s; 59 | s << "Unable to convert string to " << to << " (error " << errno << "): " << src; 60 | throw runtime_error(s.str()); 61 | } 62 | } 63 | else 64 | { 65 | // Flush to output string 66 | out.append(dst, buf_len - dst_len); 67 | } 68 | } 69 | 70 | iconv_close(conv); 71 | 72 | // Copy to string object 73 | delete[] src; 74 | delete[] dst; 75 | 76 | return out; 77 | } 78 | 79 | string local_to_utf8(const string &value) 80 | { 81 | return iconv_string(value, "UTF-8", local_charset.c_str()); 82 | } 83 | 84 | string utf8_to_local(const string &value) 85 | { 86 | return iconv_string(value, local_charset.c_str(), "UTF-8"); 87 | } 88 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.6) 2 | project(RestfulMapper) 3 | 4 | include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include) 5 | set(BUILD_SHARED_LIBS OFF) 6 | 7 | include_directories(${CMAKE_CURRENT_SOURCE_DIR}/vendor/yajl/include) 8 | link_directories(${CMAKE_CURRENT_SOURCE_DIR}/vendor/yajl/lib) 9 | 10 | add_definitions(-DCURL_STATICLIB) 11 | include_directories(${CMAKE_CURRENT_SOURCE_DIR}/vendor/curl/include) 12 | link_directories(${CMAKE_CURRENT_SOURCE_DIR}/vendor/curl/lib) 13 | 14 | add_definitions(-DLIBICONV_PLUG) 15 | include_directories(${CMAKE_CURRENT_SOURCE_DIR}/vendor/iconv/include) 16 | link_directories(${CMAKE_CURRENT_SOURCE_DIR}/vendor/iconv/lib) 17 | 18 | if (BORLAND) 19 | add_definitions(-w-hid) 20 | endif() 21 | 22 | add_library(restful_mapper src/api.cpp src/json.cpp src/utf8.cpp) 23 | target_link_libraries(restful_mapper curl yajl iconv charset) 24 | 25 | install(TARGETS restful_mapper DESTINATION lib) 26 | install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/include/restful_mapper.h DESTINATION include) 27 | install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/include/restful_mapper/api.h DESTINATION include/restful_mapper) 28 | install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/include/restful_mapper/field.h DESTINATION include/restful_mapper) 29 | install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/include/restful_mapper/helpers.h DESTINATION include/restful_mapper) 30 | install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/include/restful_mapper/internal/iso8601.h DESTINATION include/restful_mapper/internal) 31 | install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/include/restful_mapper/json.h DESTINATION include/restful_mapper) 32 | install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/include/restful_mapper/mapper.h DESTINATION include/restful_mapper) 33 | install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/include/restful_mapper/meta.h DESTINATION include/restful_mapper) 34 | install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/include/restful_mapper/model.h DESTINATION include/restful_mapper) 35 | install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/include/restful_mapper/model_collection.h DESTINATION include/restful_mapper) 36 | install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/include/restful_mapper/query.h DESTINATION include/restful_mapper) 37 | install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/include/restful_mapper/relation.h DESTINATION include/restful_mapper) 38 | install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/include/restful_mapper/internal/utf8.h DESTINATION include/restful_mapper/internal) 39 | 40 | -------------------------------------------------------------------------------- /tests/test_api.cpp: -------------------------------------------------------------------------------- 1 | // -------------------------------------------------------------------------------- 2 | // Includes 3 | // -------------------------------------------------------------------------------- 4 | #include 5 | #include 6 | 7 | using namespace std; 8 | using namespace restful_mapper; 9 | 10 | // -------------------------------------------------------------------------------- 11 | // Definitions 12 | // -------------------------------------------------------------------------------- 13 | TEST(ApiTest, Escape) 14 | { 15 | string escaped = Api::escape("Hej!#&?der"); 16 | ASSERT_STREQ("Hej%21%23%26%3Fder", escaped.c_str()); 17 | } 18 | 19 | TEST(ApiTest, QueryParams) 20 | { 21 | string url = "http://google.com"; 22 | url = Api::query_param(url, "q", "test"); 23 | ASSERT_STREQ("http://google.com/?q=test", url.c_str()); 24 | 25 | url = "/todo"; 26 | url = Api::query_param(url, "q", "test"); 27 | ASSERT_STREQ("/todo?q=test", url.c_str()); 28 | 29 | url = "http://m"; 30 | url = Api::query_param(url, "q", "test"); 31 | ASSERT_STREQ("http://m/?q=test", url.c_str()); 32 | 33 | url = "http://"; 34 | url = Api::query_param(url, "q", "test"); 35 | ASSERT_STREQ("http:///?q=test", url.c_str()); 36 | 37 | url = "http://google.com/search"; 38 | url = Api::query_param(url, "q", "Min s\xD8gning"); // iso-8859-1 39 | ASSERT_STREQ("http://google.com/search?q=Min%20s%D8gning", url.c_str()); 40 | 41 | url = "http://google.com/search"; 42 | url = Api::query_param(url, "q", "Min s\xC3\xB8gning"); // utf-8 43 | ASSERT_STREQ("http://google.com/search?q=Min%20s%C3%B8gning", url.c_str()); 44 | } 45 | 46 | TEST(ApiTest, BadRequestErrorJson) 47 | { 48 | BadRequestError err("{\"message\":\"Some error message...\"}"); 49 | ASSERT_EQ(400, err.code()); 50 | ASSERT_STREQ("Some error message...", err.what()); 51 | 52 | BadRequestError err2("broken json"); 53 | ASSERT_STREQ("", err2.what()); 54 | } 55 | 56 | TEST(ApiTest, ValidationErrorJson) 57 | { 58 | ValidationError err("{\"validation_errors\":{\"age\":\"some error message...\",\"name\":\"not provided\"}}"); 59 | 60 | ASSERT_EQ(400, err.code()); 61 | ASSERT_STREQ("Age some error message...\nName not provided", err.what()); 62 | ASSERT_STREQ("not provided", err["name"].c_str()); 63 | 64 | ValidationError err2("broken json"); 65 | ASSERT_STREQ("", err2.what()); 66 | 67 | ValidationError err3("{\"validation_errors\":{\"phone_no\":\"must have numbers\"," 68 | "\"company\":{\"title\":\"cannot be empty\"},\"addresses\":[{\"street\":\"not provided\"}]}}"); 69 | ASSERT_STREQ("Addresses\n Street not provided\nCompany\n Title cannot be empty\nPhone no must have numbers", err3.what()); 70 | 71 | ValidationError err4("{\"validation_errors\":{\"phone_no\":null}}"); 72 | ASSERT_STREQ("Phone no", err4.what()); 73 | } 74 | 75 | TEST(ApiTest, ProxyValid) 76 | { 77 | Api::set_url("http://localhost:5000/api"); 78 | Api::clear_proxy(); 79 | Api::set_proxy(""); 80 | 81 | ASSERT_NO_THROW(Api::get("/reload")); 82 | } 83 | 84 | TEST(ApiTest, ProxyInvalid) 85 | { 86 | Api::set_url("http://localhost:5000/api"); 87 | Api::set_proxy("an-invalid-hostname"); 88 | 89 | ASSERT_THROW(Api::get("/reload"), ResponseError); 90 | } 91 | 92 | -------------------------------------------------------------------------------- /tests/test_query.cpp: -------------------------------------------------------------------------------- 1 | // -------------------------------------------------------------------------------- 2 | // Includes 3 | // -------------------------------------------------------------------------------- 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | using namespace std; 10 | using namespace restful_mapper; 11 | 12 | // -------------------------------------------------------------------------------- 13 | // Definitions 14 | // -------------------------------------------------------------------------------- 15 | TEST(QueryTest, GenerateQuery) 16 | { 17 | local_charset = "latin1"; 18 | 19 | putenv("TZ=CET-1"); 20 | tzset(); 21 | 22 | Query q; 23 | 24 | q("name").eq("Flaf"); 25 | q("address").neq("England"); 26 | q("id").gt(3).lt(q.field("zipcode")); 27 | q("zipcode").gte(1000); 28 | q("zipcode").lte(7000); 29 | 30 | vector numbers; 31 | numbers.push_back(4); 32 | numbers.push_back(6); 33 | numbers.push_back(11); 34 | q("house").in(numbers); 35 | 36 | numbers.clear(); 37 | q("house").not_in(numbers); 38 | 39 | vector choices; 40 | choices.push_back(true); 41 | choices.push_back(false); 42 | 43 | q("existing").in(choices); 44 | 45 | vector districts; 46 | districts.push_back("Outer \xf9 city"); 47 | districts.push_back("Inner city"); 48 | 49 | q("district").in(districts); 50 | 51 | q("city").is_null(); 52 | q("country").is_not_null(); 53 | 54 | q("region").like("%somewh%"); 55 | q("county").ilike("% Amt"); 56 | 57 | q("residents__name").any("Jimi Hendrix"); 58 | q("residents__id").has(2); 59 | 60 | q("voided").eq(false); 61 | q("profile_completion").gt(3.0); 62 | q("created_on").lte(to_iso8601(10000, false)); 63 | 64 | q.limit(10).offset(2); 65 | q.order_by_asc(q.field("id")); 66 | q.order_by_desc(q.field("created_on")); 67 | q.single(); 68 | 69 | ostringstream s; 70 | s << "{\"filters\":" 71 | << "[" 72 | << "{\"name\":\"name\",\"op\":\"eq\",\"val\":\"Flaf\"}," 73 | << "{\"name\":\"address\",\"op\":\"neq\",\"val\":\"England\"}," 74 | << "{\"name\":\"id\",\"op\":\"gt\",\"val\":3}," 75 | << "{\"name\":\"id\",\"op\":\"lt\",\"field\":\"zipcode\"}," 76 | << "{\"name\":\"zipcode\",\"op\":\"gte\",\"val\":1000}," 77 | << "{\"name\":\"zipcode\",\"op\":\"lte\",\"val\":7000}," 78 | << "{\"name\":\"house\",\"op\":\"in\",\"val\":[4,6,11]}," 79 | << "{\"name\":\"house\",\"op\":\"not_in\",\"val\":[]}," 80 | << "{\"name\":\"existing\",\"op\":\"in\",\"val\":[true,false]}," 81 | << "{\"name\":\"district\",\"op\":\"in\",\"val\":[\"Outer \xc3\xb9 city\",\"Inner city\"]}," 82 | << "{\"name\":\"city\",\"op\":\"is_null\"}," 83 | << "{\"name\":\"country\",\"op\":\"is_not_null\"}," 84 | << "{\"name\":\"region\",\"op\":\"like\",\"val\":\"%somewh%\"}," 85 | << "{\"name\":\"county\",\"op\":\"ilike\",\"val\":\"% Amt\"}," 86 | << "{\"name\":\"residents__name\",\"op\":\"any\",\"val\":\"Jimi Hendrix\"}," 87 | << "{\"name\":\"residents__id\",\"op\":\"has\",\"val\":2}," 88 | << "{\"name\":\"voided\",\"op\":\"eq\",\"val\":false}," 89 | << "{\"name\":\"profile_completion\",\"op\":\"gt\",\"val\":3.0}," 90 | << "{\"name\":\"created_on\",\"op\":\"lte\",\"val\":\"1970-01-01T02:46:40\"}" 91 | << "]," 92 | << "\"limit\":10," 93 | << "\"offset\":2," 94 | << "\"order_by\":[{\"field\":\"id\",\"direction\":\"asc\"},{\"field\":\"created_on\",\"direction\":\"desc\"}]," 95 | << "\"single\":true" 96 | << "}"; 97 | 98 | ASSERT_STREQ(s.str().c_str(), q.dump().c_str()); 99 | } 100 | 101 | TEST(QueryTest, ClearQuery) 102 | { 103 | Query q; 104 | 105 | q("name").eq("Flaf"); 106 | q.clear(); 107 | 108 | ASSERT_STREQ("{}", q.dump().c_str()); 109 | } 110 | -------------------------------------------------------------------------------- /tests/test_relation.cpp: -------------------------------------------------------------------------------- 1 | // -------------------------------------------------------------------------------- 2 | // Includes 3 | // -------------------------------------------------------------------------------- 4 | #include 5 | #include 6 | #include 7 | 8 | using namespace std; 9 | using namespace restful_mapper; 10 | 11 | // -------------------------------------------------------------------------------- 12 | // Definitions 13 | // -------------------------------------------------------------------------------- 14 | class Task 15 | { 16 | public: 17 | int id; 18 | string task; 19 | 20 | static const std::string &class_name() 21 | { 22 | static std::string class_name = "Task"; 23 | return class_name; 24 | } 25 | 26 | void from_json(string values, const int &flags = 0, const bool &exists = false) 27 | { 28 | } 29 | 30 | std::string to_json(const int &flags = 0, const std::string &parent_model = "") const 31 | { 32 | ostringstream s; 33 | 34 | s << "{\"id\":" << id << ",\"task\":\"" << task << "\"}"; 35 | 36 | return s.str(); 37 | } 38 | 39 | bool is_dirty() const 40 | { 41 | return true; 42 | } 43 | }; 44 | 45 | TEST(RelationTest, HasOne) 46 | { 47 | HasOne r; 48 | 49 | ASSERT_TRUE(r.is_null()); 50 | ASSERT_FALSE(r.is_dirty()); 51 | ASSERT_THROW(r.get(), runtime_error); 52 | ASSERT_THROW((Task) r, runtime_error); 53 | ASSERT_THROW(r->id, runtime_error); 54 | 55 | ASSERT_STREQ("null", r.to_json().c_str()); 56 | 57 | HasOne r2(r); 58 | ASSERT_TRUE(r2.is_null()); 59 | ASSERT_FALSE(r2.is_dirty()); 60 | ASSERT_THROW(r2.get(), runtime_error); 61 | ASSERT_THROW((Task) r2, runtime_error); 62 | ASSERT_THROW(r2->id, runtime_error); 63 | 64 | Task t; 65 | t.id = 3; 66 | t.task = "Play!"; 67 | 68 | r2 = t; 69 | ASSERT_FALSE(r2.is_null()); 70 | ASSERT_TRUE(r2.is_dirty()); 71 | ASSERT_EQ(3, r2.get().id); 72 | ASSERT_STREQ("Play!", r2->task.c_str()); 73 | 74 | ASSERT_STREQ("{\"id\":3,\"task\":\"Play!\"}", r2.to_json().c_str()); 75 | 76 | r2->task = "Testing..."; 77 | ASSERT_STREQ("Play!", t.task.c_str()); 78 | 79 | r2.clear(); 80 | ASSERT_TRUE(r2.is_null()); 81 | ASSERT_THROW(r2.get(), runtime_error); 82 | 83 | r.build(); 84 | ASSERT_TRUE(r2.is_dirty()); 85 | r->id = 8; 86 | r->task = "flaf"; 87 | ASSERT_FALSE(r.is_null()); 88 | ASSERT_EQ(8, r.get().id); 89 | ASSERT_STREQ("flaf", r->task.c_str()); 90 | 91 | r2 = r; 92 | ASSERT_FALSE(r2.is_null()); 93 | ASSERT_EQ(8, r2.get().id); 94 | ASSERT_STREQ("flaf", r2->task.c_str()); 95 | 96 | r->id = 3; 97 | ASSERT_EQ(8, r2.get().id); 98 | 99 | HasOne r3; 100 | HasOne r4; 101 | 102 | ASSERT_NO_THROW(r4 = r3); 103 | } 104 | 105 | TEST(RelationTest, BelongsTo) 106 | { 107 | BelongsTo r; 108 | 109 | ASSERT_TRUE(r.is_null()); 110 | ASSERT_FALSE(r.is_dirty()); 111 | ASSERT_THROW(r.get(), runtime_error); 112 | ASSERT_THROW((Task) r, runtime_error); 113 | ASSERT_THROW(r->id, runtime_error); 114 | 115 | ASSERT_STREQ("null", r.to_json().c_str()); 116 | 117 | BelongsTo r2(r); 118 | ASSERT_TRUE(r2.is_null()); 119 | ASSERT_FALSE(r2.is_dirty()); 120 | ASSERT_THROW(r2.get(), runtime_error); 121 | ASSERT_THROW((Task) r2, runtime_error); 122 | ASSERT_THROW(r2->id, runtime_error); 123 | 124 | Task t; 125 | t.id = 3; 126 | t.task = "Play!"; 127 | 128 | r2 = t; 129 | ASSERT_FALSE(r2.is_null()); 130 | ASSERT_TRUE(r2.is_dirty()); 131 | ASSERT_EQ(3, r2.get().id); 132 | ASSERT_STREQ("Play!", r2->task.c_str()); 133 | 134 | ASSERT_STREQ("{\"id\":3,\"task\":\"Play!\"}", r2.to_json().c_str()); 135 | 136 | r2->task = "Testing..."; 137 | ASSERT_STREQ("Play!", t.task.c_str()); 138 | 139 | r2.clear(); 140 | ASSERT_TRUE(r2.is_null()); 141 | ASSERT_THROW(r2.get(), runtime_error); 142 | 143 | r.build(); 144 | ASSERT_TRUE(r2.is_dirty()); 145 | r->id = 8; 146 | r->task = "flaf"; 147 | ASSERT_FALSE(r.is_null()); 148 | ASSERT_EQ(8, r.get().id); 149 | ASSERT_STREQ("flaf", r->task.c_str()); 150 | 151 | r2 = r; 152 | ASSERT_FALSE(r2.is_null()); 153 | ASSERT_EQ(8, r2.get().id); 154 | ASSERT_STREQ("flaf", r2->task.c_str()); 155 | 156 | r->id = 3; 157 | ASSERT_EQ(8, r2.get().id); 158 | 159 | BelongsTo r3; 160 | BelongsTo r4; 161 | 162 | ASSERT_NO_THROW(r4 = r3); 163 | } 164 | 165 | TEST(RelationTest, HasMany) 166 | { 167 | HasMany r; 168 | 169 | ASSERT_FALSE(r.is_dirty()); 170 | ASSERT_TRUE(r.empty()); 171 | ASSERT_EQ(0.0, r.size()); 172 | 173 | Task t; 174 | t.id = 3; 175 | t.task = "Play!"; 176 | 177 | r.push_back(t); 178 | ASSERT_TRUE(r.is_dirty()); 179 | ASSERT_EQ(1, r.size()); 180 | 181 | Task &t2 = r.build(); 182 | t2.id = 5; 183 | 184 | ASSERT_EQ(5, r.back().id); 185 | 186 | reverse(r.begin(), r.end()); 187 | 188 | ASSERT_EQ(5, r.front().id); 189 | 190 | Task t3; 191 | t3.id = 9; 192 | t3.task = "Profit!!!"; 193 | 194 | r.assign(3, t3); 195 | ASSERT_EQ(3, r.size()); 196 | 197 | ASSERT_EQ(9, (r.begin())->id); 198 | ASSERT_EQ(9, (r.begin() + 1)->id); 199 | ASSERT_EQ(9, (r.begin() + 2)->id); 200 | 201 | r.erase(r.begin(), r.end()); 202 | ASSERT_TRUE(r.is_dirty()); 203 | 204 | ASSERT_TRUE(r.empty()); 205 | 206 | ModelCollection m1; 207 | m1.push_back(Task()); 208 | 209 | HasMany r2; 210 | 211 | r2 = m1; 212 | 213 | ASSERT_EQ(1, r2.size()); 214 | } 215 | 216 | TEST(RelationTest, GetClassName) 217 | { 218 | ASSERT_STREQ("Task", HasMany::class_name().c_str()); 219 | ASSERT_STREQ("Task", HasOne::class_name().c_str()); 220 | ASSERT_STREQ("Task", BelongsTo::class_name().c_str()); 221 | } 222 | 223 | -------------------------------------------------------------------------------- /include/restful_mapper/internal/iso8601.h: -------------------------------------------------------------------------------- 1 | #ifndef RESTFUL_MAPPER_ISO8601_H_20130314 2 | #define RESTFUL_MAPPER_ISO8601_H_20130314 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | /** 11 | * @brief Calculate the current UTC time stamp 12 | * 13 | * @return the UTC time stamp 14 | */ 15 | inline std::time_t utc_time() 16 | { 17 | std::time_t now = std::time(NULL); 18 | 19 | std::tm tm_local(*std::localtime(&now)); 20 | std::time_t t_local = std::mktime(&tm_local); 21 | 22 | std::tm tm_utc(*std::gmtime(&t_local)); 23 | std::time_t t_utc = std::mktime(&tm_utc); 24 | 25 | return now - (t_utc - t_local); 26 | } 27 | 28 | /** 29 | * @brief Produces an ISO-8601 string representation of the timestamp 30 | * 31 | * @param timestamp the epoch time in seconds 32 | * @param include_timezone appends Z UTC flag at end of string if true 33 | * 34 | * @return a string representing the timestamp in UTC 35 | */ 36 | inline std::string to_iso8601(std::time_t timestamp, const bool &include_timezone = true) 37 | { 38 | std::tm exploded_time(*std::gmtime(×tamp)); 39 | std::string ret; 40 | 41 | if (include_timezone) 42 | { 43 | char buf[sizeof "1970-01-01T00:00:00Z"]; 44 | std::strftime(buf, sizeof buf, "%Y-%m-%dT%H:%M:%SZ", &exploded_time); 45 | ret = buf; 46 | } 47 | else 48 | { 49 | char buf[sizeof "1970-01-01T00:00:00"]; 50 | std::strftime(buf, sizeof buf, "%Y-%m-%dT%H:%M:%S", &exploded_time); 51 | ret = buf; 52 | } 53 | 54 | return ret; 55 | } 56 | 57 | /** 58 | * @brief Parses an ISO-8601 formatted string into epoch time 59 | * 60 | * @param descriptor the ISO-8601 61 | * 62 | * @return the UTC timestamp 63 | */ 64 | inline std::time_t from_iso8601(std::string descriptor) 65 | { 66 | // Parse according to ISO 8601 67 | std::tm t; 68 | 69 | const char *value = descriptor.c_str(); 70 | const char *c = value; 71 | int year = 0; 72 | int month = 0; 73 | int seconds = 0; 74 | int minutes = 0; 75 | int hours = 0; 76 | int days = 0; 77 | 78 | // NOTE: we have to check for the extended format first, 79 | // because otherwise the separting '-' will be interpreted 80 | // by std::sscanf as signs of a 1 digit integer .... :-( 81 | if (std::sscanf(value, "%4u-%2u-%2u", &year, &month, &days) == 3) 82 | { 83 | c += 10; 84 | } 85 | else if (std::sscanf(value, "%4u%2u%2u", &year, &month, &days) == 3) 86 | { 87 | c += 8; 88 | } 89 | else 90 | { 91 | throw std::runtime_error(std::string("Invalid date format: ") + value); 92 | } 93 | 94 | t.tm_year = year - 1900; 95 | t.tm_mon = month - 1; 96 | t.tm_mday = days; 97 | 98 | // Check if time is supplied 99 | if (*c == '\0') 100 | { 101 | t.tm_hour = 0; 102 | t.tm_sec = 0; 103 | t.tm_min = 0; 104 | } 105 | else if (*c == 'T') 106 | { 107 | // Time of day part 108 | c++; 109 | 110 | if (std::sscanf(c, "%2d%2d", &hours, &minutes) == 2) 111 | { 112 | c += 4; 113 | } 114 | else if (std::sscanf(c, "%2d:%2d", &hours, &minutes) == 2) 115 | { 116 | c += 5; 117 | } 118 | else 119 | { 120 | throw std::runtime_error(std::string("Invalid date format: ") + value); 121 | } 122 | 123 | if (*c == ':') 124 | { 125 | c++; 126 | } 127 | 128 | if (*c != '\0') 129 | { 130 | if (std::sscanf(c, "%2d", &seconds) == 1) 131 | { 132 | c += 2; 133 | } 134 | else 135 | { 136 | throw std::runtime_error(std::string("Invalid date format: ") + value); 137 | } 138 | } 139 | 140 | t.tm_hour = hours; 141 | t.tm_min = minutes; 142 | t.tm_sec = seconds; 143 | } 144 | else 145 | { 146 | throw std::runtime_error(std::string("Invalid date format: ") + value); 147 | } 148 | 149 | // Drop microsecond information 150 | if (*c == '.') 151 | { 152 | c++; 153 | 154 | while (std::isdigit(*c) && *c != '\0') 155 | { 156 | c++; 157 | } 158 | } 159 | 160 | // Parse time zone information 161 | int tz_offset = 0; 162 | 163 | if (*c == 'Z') 164 | { 165 | c++; 166 | } 167 | 168 | if (*c != '\0') 169 | { 170 | int tz_direction = 0; 171 | 172 | if (*c == '+') 173 | { 174 | tz_direction = 1; 175 | } 176 | else if (*c == '-') 177 | { 178 | tz_direction = -1; 179 | } 180 | else 181 | { 182 | throw std::runtime_error(std::string("Invalid date format: ") + value); 183 | } 184 | 185 | // Offset part 186 | int tz_hours = 0; 187 | int tz_minutes = 0; 188 | 189 | c++; 190 | 191 | if (std::sscanf(c, "%2d", &tz_hours) == 1) 192 | { 193 | c += 2; 194 | } 195 | else 196 | { 197 | throw std::runtime_error(std::string("Invalid date format: ") + value); 198 | } 199 | 200 | if (*c == ':') 201 | { 202 | c++; 203 | } 204 | 205 | if (*c != '\0') 206 | { 207 | if (std::sscanf(c, "%2d", &tz_minutes) == 1) 208 | { 209 | c += 2; 210 | } 211 | else 212 | { 213 | throw std::runtime_error(std::string("Invalid date format: ") + value); 214 | } 215 | } 216 | 217 | tz_offset = tz_direction * (tz_hours * 3600 + tz_minutes * 60); 218 | } 219 | 220 | // Determine DST automatically 221 | t.tm_isdst = -1; 222 | 223 | // Correct for local time zone 224 | std::time_t t_local = std::mktime(&t); 225 | std::tm tm_utc(*std::gmtime(&t_local)); 226 | std::time_t t_utc = std::mktime(&tm_utc); 227 | tz_offset += (t_utc - t_local); 228 | 229 | return std::mktime(&t) - tz_offset; 230 | } 231 | 232 | #endif // RESTFUL_MAPPER_ISO8601_H_20130314 233 | 234 | -------------------------------------------------------------------------------- /tests/test_json.cpp: -------------------------------------------------------------------------------- 1 | // -------------------------------------------------------------------------------- 2 | // Includes 3 | // -------------------------------------------------------------------------------- 4 | #include 5 | #include 6 | 7 | using namespace std; 8 | using namespace restful_mapper; 9 | 10 | // -------------------------------------------------------------------------------- 11 | // Definitions 12 | // -------------------------------------------------------------------------------- 13 | TEST(JsonTest, EncodeLiteral) 14 | { 15 | long long a = 1; 16 | double b = 3.1415926535897931; 17 | bool c = false; 18 | string d = "a string"; 19 | 20 | ASSERT_STREQ("1", Json::encode(a).c_str()); 21 | ASSERT_STREQ("3.14", Json::encode(b).substr(0, 4).c_str()); 22 | ASSERT_STREQ("false", Json::encode(c).c_str()); 23 | ASSERT_STREQ("\"a string\"", Json::encode(d).c_str()); 24 | ASSERT_STREQ("\"another string\"", Json::encode("another string").c_str()); 25 | } 26 | 27 | TEST(JsonTest, EncodeVector) 28 | { 29 | vector a; 30 | vector b; 31 | vector c; 32 | vector d; 33 | vector e; 34 | 35 | a.push_back(3); 36 | a.push_back(8); 37 | 38 | b.push_back(3.1415926535897931); 39 | 40 | c.push_back(true); 41 | c.push_back(false); 42 | c.push_back(false); 43 | 44 | d.push_back("hello"); 45 | d.push_back("world"); 46 | 47 | ASSERT_STREQ("[3,8]", Json::encode(a).c_str()); 48 | ASSERT_STREQ("[3.14", Json::encode(b).substr(0, 5).c_str()); 49 | ASSERT_STREQ("[true,false,false]", Json::encode(c).c_str()); 50 | ASSERT_STREQ("[\"hello\",\"world\"]", Json::encode(d).c_str()); 51 | ASSERT_STREQ("[]", Json::encode(e).c_str()); 52 | } 53 | 54 | TEST(JsonTest, EncodeMap) 55 | { 56 | map a; 57 | map b; 58 | map c; 59 | map d; 60 | map e; 61 | 62 | a["test"] = 3; 63 | a["flaf"] = 8; 64 | 65 | b["amount"] = 3.1415926535897931; 66 | 67 | c["1"] = true; 68 | c["2"] = false; 69 | c["3"] = false; 70 | 71 | d["hello"] = ""; 72 | d["to"] = "world"; 73 | 74 | ASSERT_STREQ("{\"flaf\":8,\"test\":3}", Json::encode(a).c_str()); 75 | ASSERT_STREQ("{\"amount\":3.14", Json::encode(b).substr(0, 14).c_str()); 76 | ASSERT_STREQ("{\"1\":true,\"2\":false,\"3\":false}", Json::encode(c).c_str()); 77 | ASSERT_STREQ("{\"hello\":\"\",\"to\":\"world\"}", Json::encode(d).c_str()); 78 | ASSERT_STREQ("{}", Json::encode(e).c_str()); 79 | } 80 | 81 | TEST(JsonTest, Emit) 82 | { 83 | Json::Emitter emitter; 84 | 85 | emitter.emit_map_open(); 86 | 87 | emitter.emit("test", 4ll); 88 | emitter.emit_null("hello"); 89 | 90 | std::vector v; 91 | v.push_back("hello"); 92 | v.push_back("world"); 93 | 94 | emitter.emit("strings", v); 95 | 96 | std::map m; 97 | m["flaf"] = 6; 98 | m["abc"] = 8; 99 | 100 | emitter.emit("numbers", m); 101 | 102 | emitter.emit_map_close(); 103 | 104 | ASSERT_STREQ("{\"test\":4,\"hello\":null,\"strings\":[\"hello\",\"world\"],\"numbers\":{\"abc\":8,\"flaf\":6}}", emitter.dump().c_str()); 105 | ASSERT_NO_THROW(emitter.dump()); 106 | 107 | emitter.reset(); 108 | 109 | ASSERT_STREQ("", emitter.dump().c_str()); 110 | } 111 | 112 | TEST(JsonTest, DecodeLiteral) 113 | { 114 | ASSERT_EQ(1, Json::decode("1")); 115 | ASSERT_DOUBLE_EQ(3.1415926535897931, Json::decode("3.1415926535897931")); 116 | ASSERT_EQ(true, Json::decode("true")); 117 | ASSERT_STREQ("a string", Json::decode("\"a string\"").c_str()); 118 | } 119 | 120 | TEST(JsonTest, DecodeVector) 121 | { 122 | vector a(Json::decode >("[3,8]")); 123 | vector b(Json::decode >("[3.1415926535897931]")); 124 | vector c(Json::decode >("[true,false,false]")); 125 | vector d(Json::decode >("[\"hello\",\"world\"]")); 126 | 127 | ASSERT_EQ(2, a.size()); 128 | ASSERT_EQ(1, b.size()); 129 | ASSERT_EQ(3, c.size()); 130 | ASSERT_EQ(2, d.size()); 131 | 132 | ASSERT_STREQ("hello", d[0].c_str()); 133 | ASSERT_STREQ("world", d[1].c_str()); 134 | } 135 | 136 | TEST(JsonTest, DecodeMap) 137 | { 138 | map a(Json::decode >("{\"flaf\":8,\"test\":3}")); 139 | map b(Json::decode >("{\"amount\":3.1415926535897931}")); 140 | map c(Json::decode >("{\"1\":true,\"2\":false,\"3\":false}")); 141 | map d(Json::decode >("{\"hello\":\"\",\"to\":\"world\"}")); 142 | 143 | ASSERT_EQ(2, a.size()); 144 | ASSERT_EQ(1, b.size()); 145 | ASSERT_EQ(3, c.size()); 146 | ASSERT_EQ(2, d.size()); 147 | 148 | ASSERT_STREQ("", d["hello"].c_str()); 149 | ASSERT_STREQ("world", d["to"].c_str()); 150 | } 151 | 152 | TEST(JsonTest, Parse) 153 | { 154 | Json::Parser parser("{\"test\":4,\"hello\":null,\"strings\":[\"hello\",\"world\"],\"numbers\":{\"abc\":8,\"flaf\":6}}"); 155 | 156 | ASSERT_TRUE(parser.is_loaded()); 157 | ASSERT_TRUE(parser.exists("test")); 158 | ASSERT_FALSE(parser.exists("non-existing")); 159 | ASSERT_TRUE(parser.exists("hello")); 160 | ASSERT_TRUE(parser.empty("hello")); 161 | 162 | ASSERT_EQ(4, parser.find("test").to_int()); 163 | ASSERT_STREQ("test", parser.find("test").name().c_str()); 164 | 165 | const char *path[] = { "numbers", "flaf", (const char *) 0 }; 166 | ASSERT_EQ(6, parser.find(path).to_int()); 167 | ASSERT_STREQ("flaf", parser.find(path).name().c_str()); 168 | 169 | ASSERT_STREQ("[\"hello\",\"world\"]", parser.find("strings").dump().c_str()); 170 | 171 | vector vdump = parser.find("strings").dump_array(); 172 | ASSERT_EQ(2, vdump.size()); 173 | ASSERT_STREQ("\"hello\"", vdump[0].c_str()); 174 | 175 | map mdump = parser.find("numbers").dump_map(); 176 | ASSERT_EQ(2, mdump.size()); 177 | ASSERT_STREQ("8", mdump["abc"].c_str()); 178 | 179 | vector sarray = parser.find("strings").to_array(); 180 | ASSERT_STREQ("strings[1]", sarray[1].name().c_str()); 181 | 182 | map nmap = parser.find("numbers").to_map(); 183 | ASSERT_STREQ("numbers[\"abc\"]", nmap.find("abc")->second.name().c_str()); 184 | } 185 | 186 | -------------------------------------------------------------------------------- /include/restful_mapper/model.h: -------------------------------------------------------------------------------- 1 | #ifndef RESTFUL_MAPPER_MODEL_H 2 | #define RESTFUL_MAPPER_MODEL_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | namespace restful_mapper 9 | { 10 | 11 | template 12 | class Model 13 | { 14 | public: 15 | typedef ModelCollection Collection; 16 | 17 | Model() : exists_(false) {} 18 | 19 | static const std::string &class_name() 20 | { 21 | static std::string class_name = type_info_name(typeid(T)); 22 | 23 | return class_name; 24 | } 25 | 26 | virtual void map_set(Mapper &mapper) const 27 | { 28 | throw std::logic_error(std::string("map_set not implemented for ") + class_name()); 29 | } 30 | 31 | virtual void map_get(const Mapper &mapper) 32 | { 33 | throw std::logic_error(std::string("map_get not implemented for ") + class_name()); 34 | } 35 | 36 | virtual std::string endpoint() const 37 | { 38 | throw std::logic_error(std::string("endpoint not implemented for ") + class_name()); 39 | } 40 | 41 | virtual const Primary &primary() const 42 | { 43 | throw std::logic_error(std::string("primary not implemented for ") + class_name()); 44 | } 45 | 46 | void from_json(std::string values, const int &flags = 0) 47 | { 48 | Mapper mapper(values, flags); 49 | map_get(mapper); 50 | } 51 | 52 | void from_json(std::string values, const int &flags, const bool &exists) 53 | { 54 | from_json(values, flags); 55 | exists_ = exists; 56 | } 57 | 58 | std::string to_json(const int &flags = 0, const std::string &parent_model = "") const 59 | { 60 | Mapper mapper(flags); 61 | mapper.set_current_model(class_name()); 62 | mapper.set_parent_model(parent_model); 63 | 64 | map_set(mapper); 65 | 66 | return mapper.dump(); 67 | } 68 | 69 | std::string read_field(const std::string &field) const 70 | { 71 | Mapper mapper(OUTPUT_SINGLE_FIELD | KEEP_FIELDS_DIRTY | IGNORE_DIRTY_FLAG); 72 | mapper.set_field_filter(field); 73 | map_set(mapper); 74 | 75 | return mapper.dump(); 76 | } 77 | 78 | bool is_dirty() const 79 | { 80 | return to_json(KEEP_FIELDS_DIRTY).size() > 2; 81 | } 82 | 83 | void reload() 84 | { 85 | if (exists()) 86 | { 87 | from_json(Api::get(url())); 88 | } 89 | } 90 | 91 | void destroy() 92 | { 93 | if (exists()) 94 | { 95 | Api::del(url()); 96 | 97 | // Reload all attributes 98 | emplace_clone(); 99 | } 100 | } 101 | 102 | void save() 103 | { 104 | if (exists()) 105 | { 106 | from_json(Api::put(url(), to_json()), IGNORE_MISSING_FIELDS); 107 | } 108 | else 109 | { 110 | from_json(Api::post(url(), to_json()), IGNORE_MISSING_FIELDS); 111 | } 112 | 113 | exists_ = true; 114 | } 115 | 116 | virtual T clone() const 117 | { 118 | T cloned; 119 | 120 | cloned.from_json(to_json(KEEP_FIELDS_DIRTY | IGNORE_DIRTY_FLAG | OUTPUT_SHALLOW), 121 | TOUCH_FIELDS | IGNORE_MISSING_FIELDS); 122 | 123 | return cloned; 124 | } 125 | 126 | void emplace_clone() 127 | { 128 | from_json(to_json(KEEP_FIELDS_DIRTY | IGNORE_DIRTY_FLAG | OUTPUT_SHALLOW), 129 | TOUCH_FIELDS | IGNORE_MISSING_FIELDS, false); 130 | 131 | reset_primary_key(); 132 | } 133 | 134 | void reload_one(const std::string &relationship) 135 | { 136 | if (exists()) 137 | { 138 | Json::Emitter emitter; 139 | 140 | emitter.emit_map_open(); 141 | emitter.emit_json(relationship, Api::get(url(relationship))); 142 | emitter.emit_map_close(); 143 | 144 | from_json(emitter.dump(), IGNORE_MISSING_FIELDS); 145 | } 146 | } 147 | 148 | void reload_many(const std::string &relationship) 149 | { 150 | if (exists()) 151 | { 152 | Json::Parser parser(Api::get(url(relationship))); 153 | 154 | Json::Emitter emitter; 155 | 156 | emitter.emit_map_open(); 157 | emitter.emit(relationship); 158 | emitter.emit_tree(parser.find("objects").json_tree_ptr()); 159 | emitter.emit_map_close(); 160 | 161 | from_json(emitter.dump(), IGNORE_MISSING_FIELDS); 162 | } 163 | } 164 | 165 | static T find(const int &id) 166 | { 167 | T instance; 168 | const_cast(instance.primary()).set(id, true); 169 | instance.exists_ = true; 170 | 171 | instance.reload(); 172 | 173 | return instance; 174 | } 175 | 176 | static Collection find_all() 177 | { 178 | Collection objects; 179 | 180 | Json::Parser collector(Api::get(T().url())); 181 | 182 | std::vector partials = collector.find("objects").dump_array(); 183 | std::vector::const_iterator i, i_end = partials.end(); 184 | 185 | for (i = partials.begin(); i != i_end; ++i) 186 | { 187 | T instance; 188 | instance.from_json(*i, 0, true); 189 | 190 | objects.push_back(instance); 191 | } 192 | 193 | return objects; 194 | } 195 | 196 | static T find(Query &query) 197 | { 198 | T instance; 199 | 200 | std::string url = Api::query_param(instance.url(), "q", query.single().dump()); 201 | instance.from_json(Api::get(url), 0, true); 202 | 203 | return instance; 204 | } 205 | 206 | static Collection find_all(Query &query) 207 | { 208 | Collection objects; 209 | 210 | std::string url = Api::query_param(T().url(), "q", query.dump()); 211 | Json::Parser collector(Api::get(url)); 212 | 213 | std::vector partials = collector.find("objects").dump_array(); 214 | std::vector::const_iterator i, i_end = partials.end(); 215 | 216 | for (i = partials.begin(); i != i_end; ++i) 217 | { 218 | T instance; 219 | instance.from_json(*i, 0, true); 220 | 221 | objects.push_back(instance); 222 | } 223 | 224 | return objects; 225 | } 226 | 227 | std::string url(std::string nested_endpoint = "") const 228 | { 229 | if (exists()) 230 | { 231 | if (!nested_endpoint.empty()) 232 | { 233 | nested_endpoint = "/" + nested_endpoint; 234 | } 235 | 236 | return endpoint() + "/" + std::string(primary()) + nested_endpoint; 237 | } 238 | else 239 | { 240 | return endpoint(); 241 | } 242 | } 243 | 244 | const bool &exists() const 245 | { 246 | return exists_; 247 | } 248 | 249 | bool operator==(const T &other) const 250 | { 251 | return to_json(KEEP_FIELDS_DIRTY | IGNORE_DIRTY_FLAG) == 252 | other.to_json(KEEP_FIELDS_DIRTY | IGNORE_DIRTY_FLAG); 253 | } 254 | 255 | bool operator!=(const T &other) const 256 | { 257 | return !(*this == other); 258 | } 259 | 260 | protected: 261 | bool exists_; 262 | 263 | void reset_primary_key() 264 | { 265 | // Reset and clear primary 266 | const_cast(primary()) = Primary(); 267 | const_cast(primary()).clear(); 268 | } 269 | }; 270 | 271 | } 272 | 273 | #endif // RESTFUL_MAPPER_MODEL_H 274 | 275 | -------------------------------------------------------------------------------- /tests/mocks/rest_webservice.py: -------------------------------------------------------------------------------- 1 | from flask import Flask, Response 2 | from flask import request 3 | from flask.ext import restless 4 | from flask.ext import sqlalchemy 5 | 6 | # Custom column type, enforcing UTC 7 | from sqlalchemy import types 8 | from dateutil.tz import tzutc 9 | from datetime import datetime 10 | from dateutil.parser import parse as parse_datetime 11 | 12 | class UTCDateTime(types.TypeDecorator): 13 | impl = types.DateTime 14 | 15 | def process_bind_param(self, value, engine): 16 | if isinstance(value, str) or isinstance(value, unicode): 17 | if value.strip() == '': 18 | value = None 19 | else: 20 | value = parse_datetime(value).replace(tzinfo=tzutc()) 21 | 22 | if value is not None: 23 | return value.astimezone(tzutc()) 24 | 25 | def process_result_value(self, value, engine): 26 | if value is not None: 27 | return datetime(value.year, value.month, value.day, 28 | value.hour, value.minute, value.second, 29 | value.microsecond, tzinfo=tzutc()) 30 | 31 | class ValidationError(Exception): 32 | def __init__(self, field, message): 33 | Exception.__init__(self, message) 34 | self.errors = { field: message } 35 | 36 | 37 | # Initialize the app 38 | app = Flask(__name__) 39 | db = sqlalchemy.SQLAlchemy(app) 40 | api = restless.APIManager(app, flask_sqlalchemy_db=db) 41 | 42 | # Database configuration 43 | app.config['DEBUG'] = True 44 | app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite://' 45 | #app.config['SQLALCHEMY_ECHO'] = True 46 | 47 | # Monkeypatches 48 | # Disable pagination 49 | #restless.views.API._paginated = lambda self, instances, deep: dict(objects=[restless.views._to_dict(x, deep) for x in instances]) 50 | 51 | # Debugging 52 | @app.after_request 53 | def debug_response_callback(response): 54 | print response.data 55 | return response 56 | 57 | @app.before_request 58 | def debug_request_callback(): 59 | print request, request.headers, request.json 60 | 61 | @app.before_request 62 | def auth_request_callback(): 63 | if request.path == '/api/reload': 64 | return 65 | 66 | auth = request.authorization 67 | 68 | if not auth or auth.username != 'admin' or auth.password != 'test': 69 | return Response(status=401, headers={'WWW-Authenticate': 'Basic realm="mock"'}) 70 | 71 | # Define data models 72 | class Todo(db.Model): 73 | id = db.Column(db.Integer, primary_key=True) 74 | task = db.Column(db.Unicode) 75 | priority = db.Column(db.Integer) 76 | time = db.Column(db.Float) 77 | completed = db.Column(db.Boolean) 78 | completed_on = db.Column(UTCDateTime) 79 | 80 | class Country(db.Model): 81 | id = db.Column(db.Integer, primary_key=True) 82 | name = db.Column(db.Unicode) 83 | 84 | class City(db.Model): 85 | id = db.Column(db.Integer, primary_key=True) 86 | country_id = db.Column(db.Integer, db.ForeignKey('country.id')) 87 | name = db.Column(db.Unicode) 88 | 89 | country = db.relationship('Country', backref=db.backref('cities', lazy='dynamic')) 90 | 91 | class Zipcode(db.Model): 92 | id = db.Column(db.Integer, primary_key=True) 93 | city_id = db.Column(db.Integer, db.ForeignKey('city.id')) 94 | code = db.Column(db.Unicode) 95 | 96 | city = db.relationship('City', backref=db.backref('zipcode', uselist=False)) 97 | 98 | @db.validates('code') 99 | def validate_code(self, key, value): 100 | if not len(value) == 4: raise ValidationError(key, 'must have 4 digits') 101 | return value 102 | 103 | class Citizen(db.Model): 104 | id = db.Column(db.Integer, primary_key=True) 105 | country_id = db.Column(db.Integer, db.ForeignKey('country.id')) 106 | city_id = db.Column(db.Integer, db.ForeignKey('city.id')) 107 | first_name = db.Column(db.Unicode) 108 | last_name = db.Column(db.Unicode) 109 | 110 | country = db.relationship('Country', backref=db.backref('citizens', lazy='dynamic')) 111 | city = db.relationship('City', backref=db.backref('citizens', lazy='dynamic')) 112 | 113 | class PhoneNumber(db.Model): 114 | id = db.Column(db.Integer, primary_key=True) 115 | citizen_id = db.Column(db.Integer, db.ForeignKey('citizen.id')) 116 | number = db.Column(db.Integer) 117 | 118 | citizen = db.relationship('Citizen', backref=db.backref('phone_numbers', lazy='dynamic')) 119 | 120 | # Seed database 121 | def seed_db(): 122 | db.drop_all() 123 | db.create_all() 124 | 125 | todos = [Todo(task=u'Build an API', priority=9, time=10.35, completed=True, completed_on=datetime(2013, 12, 5).replace(tzinfo=tzutc())), 126 | Todo(task=u'???', priority=2, time=4.54, completed=False, completed_on=datetime(2013, 03, 13, 11, 53, 21, 80000).replace(tzinfo=tzutc())), 127 | Todo(task=u'Profit!!!', priority=994, time=0.001, completed=False, completed_on=datetime(2013, 12, 5).replace(tzinfo=tzutc()))] 128 | 129 | for todo in todos: 130 | db.session.add(todo) 131 | 132 | country = Country(name=u'Denmark') 133 | 134 | city = City(name=u'Copenhagen', zipcode=Zipcode(code=u'1200'), country=country) 135 | Citizen(first_name=u'John', last_name=u'Doe', phone_numbers=[PhoneNumber(number=1234),PhoneNumber(number=5678)], city=city) 136 | Citizen(first_name=u'Jane', last_name=u'Doe', phone_numbers=[PhoneNumber(number=865865)], city=city) 137 | 138 | city = City(name=u'Aarhus', zipcode=Zipcode(code=u'8000'), country=country) 139 | Citizen(first_name=u'Bob', last_name=u'Doe', phone_numbers=[PhoneNumber(number=999),PhoneNumber(number=58),PhoneNumber(number=1234)], city=city) 140 | 141 | db.session.add(country) 142 | 143 | country = Country(name=u'Sweden') 144 | 145 | city = City(name=u'Stockholm', country=country) 146 | Citizen(first_name=u'Alfons', last_name=u'Aaberg', city=city) 147 | 148 | db.session.add(country) 149 | 150 | country = Country(name=u'Norway') 151 | db.session.add(country) 152 | 153 | db.session.commit() 154 | 155 | seed_db() 156 | 157 | # Define API 158 | api.create_api(Todo, methods=['GET', 'POST', 'DELETE', 'PUT'], results_per_page=None, validation_exceptions=[ValidationError]) 159 | api.create_api(Country, methods=['GET', 'POST', 'DELETE', 'PUT'], results_per_page=None, validation_exceptions=[ValidationError]) 160 | api.create_api(City, methods=['GET', 'POST', 'DELETE', 'PUT'], results_per_page=None, validation_exceptions=[ValidationError]) 161 | api.create_api(Zipcode, methods=['GET', 'POST', 'DELETE', 'PUT'], results_per_page=None, validation_exceptions=[ValidationError]) 162 | api.create_api(Citizen, methods=['GET', 'POST', 'DELETE', 'PUT'], results_per_page=None, validation_exceptions=[ValidationError]) 163 | api.create_api(PhoneNumber, methods=['GET', 'POST', 'DELETE', 'PUT'], results_per_page=None, validation_exceptions=[ValidationError]) 164 | 165 | # Reloader 166 | @app.route("/api/reload") 167 | def reload_db(): 168 | seed_db() 169 | return "Done!" 170 | 171 | if __name__ == '__main__': 172 | app.run(debug=True) 173 | 174 | -------------------------------------------------------------------------------- /include/restful_mapper/query.h: -------------------------------------------------------------------------------- 1 | #ifndef RESTFUL_MAPPER_QUERY_H 2 | #define RESTFUL_MAPPER_QUERY_H 3 | 4 | #include 5 | 6 | // Some macros. For the sake of brevity... 7 | #define _OP_PAR_NONE(op) \ 8 | Query &op() { return filter(cur_field_, #op); } 9 | 10 | #define _OP_PAR_SINGLE(op) \ 11 | Query &op(const int &value) { return filter(cur_field_, #op, Json::encode(value)); } \ 12 | Query &op(const long long &value) { return filter(cur_field_, #op, Json::encode(value)); } \ 13 | Query &op(const double &value) { return filter(cur_field_, #op, Json::encode(value)); } \ 14 | Query &op(const bool &value) { return filter(cur_field_, #op, Json::encode(value)); } \ 15 | Query &op(const std::string &value) { return filter(cur_field_, #op, Json::encode(value)); } \ 16 | Query &op(const char *value) { return filter(cur_field_, #op, Json::encode(value)); } \ 17 | Query &op(const Query &value) { return filter(cur_field_, #op, value); } 18 | 19 | #define _OP_PAR_LIST(op) \ 20 | Query &op(const std::vector &value) { return filter(cur_field_, #op, Json::encode(value)); } \ 21 | Query &op(const std::vector &value) { return filter(cur_field_, #op, Json::encode(value)); } \ 22 | Query &op(const std::vector &value) { return filter(cur_field_, #op, Json::encode(value)); } \ 23 | Query &op(const std::vector &value) { return filter(cur_field_, #op, Json::encode(value)); } \ 24 | Query &op(const std::vector &value) { return filter(cur_field_, #op, Json::encode(value)); } 25 | 26 | namespace restful_mapper 27 | { 28 | 29 | struct QueryFilter 30 | { 31 | std::string field; 32 | std::string operation; 33 | std::string value; 34 | bool has_value; 35 | bool is_reference; 36 | }; 37 | 38 | struct QueryOrderBy 39 | { 40 | std::string field; 41 | std::string direction; 42 | }; 43 | 44 | class Query 45 | { 46 | public: 47 | const std::string &dump() 48 | { 49 | if (output_.empty()) 50 | { 51 | json_.emit_map_open(); 52 | 53 | if (!filters_.empty()) 54 | { 55 | json_.emit("filters"); 56 | json_.emit_array_open(); 57 | 58 | std::vector::const_iterator i, i_end = filters_.end(); 59 | for (i = filters_.begin(); i != i_end; ++i) 60 | { 61 | json_.emit_map_open(); 62 | 63 | json_.emit("name", i->field); 64 | json_.emit("op", i->operation); 65 | 66 | if (i->is_reference) 67 | { 68 | json_.emit("field", i->value); 69 | } 70 | else if (i->has_value) 71 | { 72 | json_.emit_json("val", i->value); 73 | } 74 | 75 | json_.emit_map_close(); 76 | } 77 | 78 | json_.emit_array_close(); 79 | } 80 | 81 | if (!limit_.empty()) 82 | { 83 | json_.emit("limit", limit_.back()); 84 | } 85 | 86 | if (!offset_.empty()) 87 | { 88 | json_.emit("offset", offset_.back()); 89 | } 90 | 91 | if (!order_by_.empty()) 92 | { 93 | json_.emit("order_by"); 94 | json_.emit_array_open(); 95 | 96 | std::vector::const_iterator i, i_end = order_by_.end(); 97 | for (i = order_by_.begin(); i != i_end; ++i) 98 | { 99 | json_.emit_map_open(); 100 | 101 | json_.emit("field", i->field); 102 | json_.emit("direction", i->direction); 103 | 104 | json_.emit_map_close(); 105 | } 106 | 107 | json_.emit_array_close(); 108 | } 109 | 110 | if (!single_.empty()) 111 | { 112 | json_.emit("single", single_.back()); 113 | } 114 | 115 | json_.emit_map_close(); 116 | 117 | output_ = json_.dump(); 118 | } 119 | 120 | return output_; 121 | } 122 | 123 | Query &operator()(const std::string &name) 124 | { 125 | cur_field_ = name; 126 | return *this; 127 | } 128 | 129 | Query &field(const std::string &name) 130 | { 131 | cur_reference_ = name; 132 | return *this; 133 | } 134 | 135 | Query &filter(std::string name, std::string operation) 136 | { 137 | QueryFilter filter; 138 | 139 | filter.field = name; 140 | filter.operation = operation; 141 | filter.value = ""; 142 | filter.has_value = false; 143 | filter.is_reference = false; 144 | 145 | filters_.push_back(filter); 146 | 147 | return *this; 148 | } 149 | 150 | Query &filter(std::string name, std::string operation, std::string value) 151 | { 152 | QueryFilter filter; 153 | 154 | filter.field = name; 155 | filter.operation = operation; 156 | filter.value = value; 157 | filter.has_value = true; 158 | filter.is_reference = false; 159 | 160 | filters_.push_back(filter); 161 | 162 | return *this; 163 | } 164 | 165 | Query &filter(std::string name, std::string operation, const Query &value) 166 | { 167 | QueryFilter filter; 168 | 169 | filter.field = name; 170 | filter.operation = operation; 171 | filter.value = value.cur_reference_; 172 | filter.has_value = true; 173 | filter.is_reference = true; 174 | 175 | filters_.push_back(filter); 176 | 177 | return *this; 178 | } 179 | 180 | Query &limit(const int &value) 181 | { 182 | limit_.push_back(value); 183 | return *this; 184 | } 185 | 186 | Query &offset(const int &value) 187 | { 188 | offset_.push_back(value); 189 | return *this; 190 | } 191 | 192 | Query &order_by_asc(const Query &value) 193 | { 194 | QueryOrderBy order_by; 195 | 196 | order_by.field = value.cur_reference_; 197 | order_by.direction = "asc"; 198 | 199 | order_by_.push_back(order_by); 200 | 201 | return *this; 202 | } 203 | 204 | Query &order_by_desc(const Query &value) 205 | { 206 | QueryOrderBy order_by; 207 | 208 | order_by.field = value.cur_reference_; 209 | order_by.direction = "desc"; 210 | 211 | order_by_.push_back(order_by); 212 | 213 | return *this; 214 | } 215 | 216 | Query &single() 217 | { 218 | single_.push_back(true); 219 | return *this; 220 | } 221 | 222 | void clear() 223 | { 224 | json_.reset(); 225 | output_.clear(); 226 | cur_field_.clear(); 227 | cur_reference_.clear(); 228 | filters_.clear(); 229 | limit_.clear(); 230 | offset_.clear(); 231 | order_by_.clear(); 232 | single_.clear(); 233 | } 234 | 235 | // Convenience operators 236 | _OP_PAR_NONE(is_null); 237 | _OP_PAR_NONE(is_not_null); 238 | 239 | _OP_PAR_SINGLE(eq); 240 | _OP_PAR_SINGLE(neq); 241 | _OP_PAR_SINGLE(gt); 242 | _OP_PAR_SINGLE(lt); 243 | _OP_PAR_SINGLE(gte); 244 | _OP_PAR_SINGLE(lte); 245 | _OP_PAR_SINGLE(like); 246 | _OP_PAR_SINGLE(ilike); 247 | _OP_PAR_SINGLE(any); 248 | _OP_PAR_SINGLE(has); 249 | 250 | _OP_PAR_LIST(in); 251 | _OP_PAR_LIST(not_in); 252 | 253 | private: 254 | Json::Emitter json_; 255 | std::string output_; 256 | 257 | std::string cur_field_; 258 | std::string cur_reference_; 259 | 260 | std::vector filters_; 261 | std::vector limit_; 262 | std::vector offset_; 263 | std::vector order_by_; 264 | std::vector single_; 265 | }; 266 | 267 | } 268 | 269 | #endif // RESTFUL_MAPPER_QUERY_H 270 | 271 | -------------------------------------------------------------------------------- /include/restful_mapper/relation.h: -------------------------------------------------------------------------------- 1 | #ifndef RESTFUL_MAPPER_RELATION_H 2 | #define RESTFUL_MAPPER_RELATION_H 3 | 4 | #include 5 | 6 | namespace restful_mapper 7 | { 8 | 9 | template 10 | class HasMany : public ModelCollection 11 | { 12 | public: 13 | HasMany() : ModelCollection(), is_dirty_(false) {} 14 | 15 | static const std::string &class_name() 16 | { 17 | return T::class_name(); 18 | } 19 | 20 | void from_json(std::string values, const int &flags = 0) 21 | { 22 | ModelCollection::clear(); 23 | 24 | Json::Parser collector(values); 25 | 26 | std::vector partials = collector.root().dump_array(); 27 | std::vector::const_iterator i, i_end = partials.end(); 28 | 29 | for (i = partials.begin(); i != i_end; ++i) 30 | { 31 | T instance; 32 | instance.from_json(*i, flags, true); 33 | 34 | ModelCollection::push_back(instance); 35 | } 36 | 37 | clean(); 38 | } 39 | 40 | std::string to_json(const int &flags = 0, const std::string &parent_model = "") const 41 | { 42 | std::ostringstream s; 43 | s << "["; 44 | 45 | const_iterator i, i_end = ModelCollection::end(); 46 | 47 | for (i = ModelCollection::begin(); i != i_end; ++i) 48 | { 49 | s << i->to_json(flags, parent_model); 50 | if ((i + 1) != i_end) s << ","; 51 | } 52 | 53 | s << "]"; 54 | 55 | return s.str(); 56 | } 57 | 58 | T &build() 59 | { 60 | push_back(T()); 61 | return ModelCollection::back(); 62 | } 63 | 64 | bool is_dirty() const 65 | { 66 | const_iterator i, i_end = ModelCollection::end(); 67 | 68 | for (i = ModelCollection::begin(); i != i_end; ++i) 69 | { 70 | if (i->is_dirty()) return true; 71 | } 72 | 73 | return is_dirty_; 74 | } 75 | 76 | void touch() 77 | { 78 | is_dirty_ = true; 79 | } 80 | 81 | void clean() const 82 | { 83 | is_dirty_ = false; 84 | } 85 | 86 | const HasMany &operator=(const ModelCollection &other) 87 | { 88 | this->items_ = other.items(); 89 | touch(); 90 | 91 | return *this; 92 | } 93 | 94 | // Inherit typedefs 95 | typedef typename ModelCollection::value_type value_type; 96 | typedef typename ModelCollection::allocator_type allocator_type; 97 | typedef typename ModelCollection::reference reference; 98 | typedef typename ModelCollection::const_reference const_reference; 99 | typedef typename ModelCollection::pointer pointer; 100 | typedef typename ModelCollection::const_pointer const_pointer; 101 | typedef typename ModelCollection::iterator iterator; 102 | typedef typename ModelCollection::const_iterator const_iterator; 103 | typedef typename ModelCollection::reverse_iterator reverse_iterator; 104 | typedef typename ModelCollection::const_reverse_iterator const_reverse_iterator; 105 | typedef typename ModelCollection::difference_type difference_type; 106 | typedef typename ModelCollection::size_type size_type; 107 | 108 | void resize(size_type n, value_type val = value_type()) { touch(); ModelCollection::resize(n, val); } 109 | template void assign(InputIterator first, InputIterator last) { touch(); ModelCollection::assign(first, last); } 110 | void assign(size_type n, const value_type val) { touch(); ModelCollection::assign(n, val); } 111 | void push_back(const value_type val) { touch(); ModelCollection::push_back(val); } 112 | void pop_back() { touch(); ModelCollection::pop_back(); } 113 | iterator insert(iterator position, const value_type val) { touch(); return ModelCollection::insert(position, val); } 114 | void insert(iterator position, size_type n, const value_type val) { touch(); ModelCollection::insert(position, n, val); } 115 | template void insert(iterator position, InputIterator first, InputIterator last) { touch(); ModelCollection::insert(position, first, last); } 116 | iterator erase(iterator position) { touch(); return ModelCollection::erase(position); } 117 | iterator erase(iterator first, iterator last) { touch(); return ModelCollection::erase(first, last); } 118 | void swap(HasMany& x) { touch(); ModelCollection::swap(x); } 119 | void clear() { touch(); ModelCollection::clear(); } 120 | 121 | private: 122 | mutable bool is_dirty_; 123 | }; 124 | 125 | template 126 | class SingleRelationshipBase 127 | { 128 | public: 129 | SingleRelationshipBase() : item_(NULL), is_dirty_(false) {} 130 | 131 | SingleRelationshipBase(const SingleRelationshipBase &other) : item_(NULL), is_dirty_(other.is_dirty_) 132 | { 133 | if (other.item_) 134 | { 135 | item_ = new T(*other.item_); 136 | } 137 | } 138 | 139 | virtual ~SingleRelationshipBase() 140 | { 141 | clear(); 142 | } 143 | 144 | static const std::string &class_name() 145 | { 146 | return T::class_name(); 147 | } 148 | 149 | bool is_null() const 150 | { 151 | return !item_; 152 | } 153 | 154 | bool is_dirty() const 155 | { 156 | if (item_ && item_->is_dirty()) 157 | { 158 | return true; 159 | } 160 | 161 | return is_dirty_; 162 | } 163 | 164 | void touch() 165 | { 166 | is_dirty_ = true; 167 | } 168 | 169 | void clean() const 170 | { 171 | is_dirty_ = false; 172 | } 173 | 174 | T &build() 175 | { 176 | clear(); 177 | 178 | item_ = new T(); 179 | 180 | return *item_; 181 | } 182 | 183 | T &get() 184 | { 185 | check_null(); 186 | 187 | return *item_; 188 | } 189 | 190 | const T &get() const 191 | { 192 | check_null(); 193 | 194 | return *item_; 195 | } 196 | 197 | const T &set(const T &value) 198 | { 199 | clear(); 200 | 201 | item_ = new T(value); 202 | 203 | return *item_; 204 | } 205 | 206 | void clear() 207 | { 208 | if (item_) 209 | { 210 | delete item_; 211 | item_ = NULL; 212 | } 213 | 214 | touch(); 215 | } 216 | 217 | void from_json(std::string values, const int &flags = 0) 218 | { 219 | build(); 220 | clean(); 221 | 222 | item_->from_json(values, flags, true); 223 | } 224 | 225 | std::string to_json(const int &flags = 0, const std::string &parent_model = "") const 226 | { 227 | if (item_) 228 | { 229 | return item_->to_json(flags, parent_model); 230 | } 231 | else 232 | { 233 | return "null"; 234 | } 235 | } 236 | 237 | T *operator->() 238 | { 239 | check_null(); 240 | 241 | return item_; 242 | } 243 | 244 | operator T() 245 | { 246 | check_null(); 247 | 248 | return get(); 249 | } 250 | 251 | const T *operator->() const 252 | { 253 | check_null(); 254 | 255 | return item_; 256 | } 257 | 258 | operator const T() const 259 | { 260 | check_null(); 261 | 262 | return get(); 263 | } 264 | 265 | const SingleRelationshipBase &operator=(const SingleRelationshipBase &value) 266 | { 267 | if (value.is_null()) 268 | { 269 | clear(); 270 | } 271 | else 272 | { 273 | set(value.get()); 274 | } 275 | 276 | is_dirty_ = value.is_dirty_; 277 | 278 | return *this; 279 | } 280 | 281 | private: 282 | T *item_; 283 | mutable bool is_dirty_; 284 | 285 | void check_null() const 286 | { 287 | if (!item_) 288 | { 289 | std::ostringstream s; 290 | s << "Related object \"" << type_info_name(typeid(T)) << "\" does not exist"; 291 | throw std::runtime_error(s.str()); 292 | } 293 | } 294 | }; 295 | 296 | template 297 | class HasOne : public SingleRelationshipBase 298 | { 299 | public: 300 | HasOne() : SingleRelationshipBase() {} 301 | 302 | const T &operator=(const T &value) 303 | { 304 | return this->set(value); 305 | } 306 | }; 307 | 308 | template 309 | class BelongsTo : public SingleRelationshipBase 310 | { 311 | public: 312 | BelongsTo() : SingleRelationshipBase() {} 313 | 314 | const T &operator=(const T &value) 315 | { 316 | return this->set(value); 317 | } 318 | }; 319 | 320 | } 321 | 322 | #endif // RESTFUL_MAPPER_RELATION_H 323 | 324 | -------------------------------------------------------------------------------- /include/restful_mapper/json.h: -------------------------------------------------------------------------------- 1 | #ifndef RESTFUL_MAPPER_JSON_H 2 | #define RESTFUL_MAPPER_JSON_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | namespace restful_mapper 9 | { 10 | 11 | class Json 12 | { 13 | public: 14 | Json(); 15 | ~Json(); 16 | 17 | static std::string encode(const int &value) { Emitter e; e.emit(value); return e.dump(); } 18 | static std::string encode(const long long &value) { Emitter e; e.emit(value); return e.dump(); } 19 | static std::string encode(const double &value) { Emitter e; e.emit(value); return e.dump(); } 20 | static std::string encode(const bool &value) { Emitter e; e.emit(value); return e.dump(); } 21 | static std::string encode(const std::string &value) { Emitter e; e.emit(value); return e.dump(); } 22 | static std::string encode(const char *value) { Emitter e; e.emit(value); return e.dump(); } 23 | 24 | static std::string encode(const std::vector &value) { Emitter e; e.emit(value); return e.dump(); } 25 | static std::string encode(const std::vector &value) { Emitter e; e.emit(value); return e.dump(); } 26 | static std::string encode(const std::vector &value) { Emitter e; e.emit(value); return e.dump(); } 27 | static std::string encode(const std::vector &value) { Emitter e; e.emit(value); return e.dump(); } 28 | static std::string encode(const std::vector &value) { Emitter e; e.emit(value); return e.dump(); } 29 | 30 | static std::string encode(const std::map &value) { Emitter e; e.emit(value); return e.dump(); } 31 | static std::string encode(const std::map &value) { Emitter e; e.emit(value); return e.dump(); } 32 | static std::string encode(const std::map &value) { Emitter e; e.emit(value); return e.dump(); } 33 | static std::string encode(const std::map &value) { Emitter e; e.emit(value); return e.dump(); } 34 | static std::string encode(const std::map &value) { Emitter e; e.emit(value); return e.dump(); } 35 | 36 | static void not_found(const std::string &name); 37 | 38 | template static T decode(const std::string &json_struct) { Parser p(json_struct); return p.root(); } 39 | 40 | class Emitter 41 | { 42 | public: 43 | Emitter(); 44 | ~Emitter(); 45 | 46 | void reset(); 47 | const std::string &dump() const; 48 | 49 | void emit(const int &value); 50 | void emit(const long long &value); 51 | void emit(const double &value); 52 | void emit(const bool &value); 53 | void emit(const std::string &value); 54 | 55 | void emit(const std::vector &value); 56 | void emit(const std::vector &value); 57 | void emit(const std::vector &value); 58 | void emit(const std::vector &value); 59 | void emit(const std::vector &value); 60 | void emit(const char *value); 61 | 62 | void emit(const std::map &value); 63 | void emit(const std::map &value); 64 | void emit(const std::map &value); 65 | void emit(const std::map &value); 66 | void emit(const std::map &value); 67 | 68 | void emit(const std::string &key, const int &value); 69 | void emit(const std::string &key, const long long &value); 70 | void emit(const std::string &key, const double &value); 71 | void emit(const std::string &key, const bool &value); 72 | void emit(const std::string &key, const std::string &value); 73 | void emit(const std::string &key, const char *value); 74 | 75 | void emit(const std::string &key, const std::vector &value); 76 | void emit(const std::string &key, const std::vector &value); 77 | void emit(const std::string &key, const std::vector &value); 78 | void emit(const std::string &key, const std::vector &value); 79 | void emit(const std::string &key, const std::vector &value); 80 | 81 | void emit(const std::string &key, const std::map &value); 82 | void emit(const std::string &key, const std::map &value); 83 | void emit(const std::string &key, const std::map &value); 84 | void emit(const std::string &key, const std::map &value); 85 | void emit(const std::string &key, const std::map &value); 86 | 87 | void emit_tree(void *json_tree); 88 | 89 | void emit_json(const std::string &value); 90 | void emit_json(const std::string &key, const std::string &value); 91 | 92 | void emit_null(); 93 | void emit_null(const std::string &key); 94 | 95 | void emit_map_open(); 96 | void emit_map_close(); 97 | 98 | void emit_array_open(); 99 | void emit_array_close(); 100 | 101 | private: 102 | void *json_gen_ptr_; 103 | mutable std::string output_; 104 | 105 | // Disallow copy 106 | Emitter(Emitter const &); // Don't Implement 107 | void operator=(Emitter const &); // Don't implement 108 | }; 109 | 110 | class Node 111 | { 112 | public: 113 | Node(const std::string &name, void *json_node); 114 | 115 | const std::string &name() 116 | { 117 | return name_; 118 | } 119 | 120 | std::string dump() const; 121 | std::vector dump_array() const; 122 | std::map dump_map() const; 123 | 124 | bool is_null() const; 125 | bool is_string() const; 126 | bool is_int() const; 127 | bool is_double() const; 128 | bool is_bool() const; 129 | bool is_array() const; 130 | bool is_map() const; 131 | 132 | std::string to_string() const; 133 | long long to_int() const; 134 | double to_double() const; 135 | bool to_bool() const; 136 | 137 | std::vector to_array() const; 138 | std::vector to_string_array() const; 139 | std::vector to_int_array() const; 140 | std::vector to_double_array() const; 141 | std::vector to_bool_array() const; 142 | 143 | std::map to_map() const; 144 | std::map to_string_map() const; 145 | std::map to_int_map() const; 146 | std::map to_double_map() const; 147 | std::map to_bool_map() const; 148 | 149 | operator std::string() const { return to_string(); } 150 | operator int() const { return to_int(); } 151 | operator long long() const { return to_int(); } 152 | operator double() const { return to_double(); } 153 | operator bool() const { return to_bool(); } 154 | 155 | operator std::vector() const { return to_array(); } 156 | operator std::vector() const { return to_string_array(); } 157 | operator std::vector() const { return to_int_array(); } 158 | operator std::vector() const { return to_double_array(); } 159 | operator std::vector() const { return to_bool_array(); } 160 | 161 | operator std::map() const { return to_map(); } 162 | operator std::map() const { return to_string_map(); } 163 | operator std::map() const { return to_int_map(); } 164 | operator std::map() const { return to_double_map(); } 165 | operator std::map() const { return to_bool_map(); } 166 | 167 | void *json_tree_ptr() const 168 | { 169 | return json_tree_ptr_; 170 | } 171 | 172 | private: 173 | std::string name_; 174 | void *json_tree_ptr_; 175 | }; 176 | 177 | class Parser 178 | { 179 | public: 180 | Parser(); 181 | Parser(const std::string &json_struct); 182 | ~Parser(); 183 | 184 | bool is_loaded() const; 185 | void load(const std::string &json_struct); 186 | Node root() const; 187 | bool exists(const std::string &key) const; 188 | bool empty(const std::string &key) const; 189 | Node find(const std::string &key) const; 190 | Node find(const char **key) const; 191 | 192 | void *json_tree_ptr() const 193 | { 194 | return json_tree_ptr_; 195 | } 196 | 197 | private: 198 | void *json_tree_ptr_; 199 | 200 | // Disallow copy 201 | Parser(Parser const &); // Don't Implement 202 | void operator=(Parser const &); // Don't implement 203 | }; 204 | }; 205 | 206 | } 207 | 208 | #endif // RESTFUL_MAPPER_JSON_H 209 | 210 | -------------------------------------------------------------------------------- /tests/test_field.cpp: -------------------------------------------------------------------------------- 1 | // -------------------------------------------------------------------------------- 2 | // Includes 3 | // -------------------------------------------------------------------------------- 4 | #include 5 | #include 6 | 7 | using namespace std; 8 | using namespace restful_mapper; 9 | 10 | // -------------------------------------------------------------------------------- 11 | // Definitions 12 | // -------------------------------------------------------------------------------- 13 | TEST(FieldTest, IntField) 14 | { 15 | Field f; 16 | 17 | ASSERT_TRUE(f.is_null()); 18 | ASSERT_FALSE(f.is_dirty()); 19 | ASSERT_EQ(0, (int) f); 20 | 21 | f = 5; 22 | 23 | ASSERT_FALSE(f.is_null()); 24 | ASSERT_TRUE(f.is_dirty()); 25 | ASSERT_EQ(5, int(f)); 26 | ASSERT_TRUE(f == 5); 27 | ASSERT_FALSE(f == 3); 28 | } 29 | 30 | TEST(FieldTest, DoubleField) 31 | { 32 | Field f; 33 | 34 | ASSERT_DOUBLE_EQ(0.0, f.get()); 35 | 36 | f = 5.45; 37 | 38 | ASSERT_FLOAT_EQ(5.45, float(f)); 39 | 40 | Field f2; 41 | 42 | f2 = 5.45; 43 | 44 | ASSERT_TRUE(f == f2); 45 | } 46 | 47 | TEST(FieldTest, BoolField) 48 | { 49 | Field f; 50 | 51 | ASSERT_FALSE(f.get()); 52 | 53 | f = true; 54 | 55 | ASSERT_TRUE(bool(f)); 56 | ASSERT_TRUE(f); 57 | 58 | f.clear(); 59 | 60 | ASSERT_FALSE(bool(f)); 61 | ASSERT_FALSE(f); 62 | } 63 | 64 | TEST(FieldTest, StringField) 65 | { 66 | Field f; 67 | 68 | ASSERT_STREQ("", string(f).c_str()); 69 | 70 | ASSERT_TRUE(f.is_null()); 71 | ASSERT_FALSE(f.is_dirty()); 72 | 73 | f = "Test"; 74 | 75 | ASSERT_FALSE(f.is_null()); 76 | ASSERT_TRUE(f.is_dirty()); 77 | ASSERT_STREQ("Test", string(f).c_str()); 78 | 79 | string s = "Lorem ipsum"; 80 | f = s; 81 | 82 | ASSERT_STREQ("Lorem ipsum", string(f).c_str()); 83 | 84 | f.clear(); 85 | 86 | ASSERT_STREQ("", string(f).c_str()); 87 | 88 | ASSERT_TRUE(f.is_null()); 89 | ASSERT_TRUE(f.is_dirty()); 90 | 91 | f.set("Something else..."); 92 | 93 | ASSERT_FALSE(f.is_null()); 94 | ASSERT_TRUE(f.is_dirty()); 95 | 96 | string s2 = f; 97 | 98 | ASSERT_EQ(s2.size(), f.size()); 99 | 100 | ASSERT_STREQ("Something else...", s2.c_str()); 101 | ASSERT_STREQ("Something else...", f.c_str()); 102 | 103 | ASSERT_TRUE(f == string("Something else...")); 104 | ASSERT_TRUE(f == "Something else..."); 105 | ASSERT_FALSE(f != "Something else..."); 106 | ASSERT_FALSE(f == "Something..."); 107 | ASSERT_TRUE(s2 == f); 108 | 109 | Field f2; 110 | f2 = "Something else..."; 111 | 112 | ASSERT_TRUE(f2 == f); 113 | 114 | string s3 = f + f2; 115 | 116 | ASSERT_STREQ("Something else...Something else...", s3.c_str()); 117 | } 118 | 119 | TEST(FieldTest, TimeField) 120 | { 121 | putenv("TZ=EST5"); 122 | tzset(); 123 | 124 | Field f; 125 | 126 | ASSERT_TRUE(f.is_null()); 127 | ASSERT_FALSE(f.is_dirty()); 128 | 129 | f = 1234567890; 130 | ASSERT_STREQ("2009-02-13T23:31:30", f.to_iso8601(false).c_str()); 131 | 132 | f = "2013-03-14T09:33:59Z"; 133 | 134 | ASSERT_FALSE(f.is_null()); 135 | ASSERT_TRUE(f.is_dirty()); 136 | 137 | ASSERT_EQ(2013, f.local_year()); 138 | ASSERT_EQ(3, f.local_month()); 139 | ASSERT_EQ(14, f.local_day()); 140 | ASSERT_EQ(4, f.local_hour()); 141 | ASSERT_EQ(33, f.local_minute()); 142 | ASSERT_EQ(59, f.local_second()); 143 | 144 | ASSERT_STREQ("2013-03-14T09:33:59", f.to_iso8601(false).c_str()); 145 | 146 | f = "2013-02-28T13:45:02"; 147 | 148 | ASSERT_EQ(2013, f.local_year()); 149 | ASSERT_EQ(2, f.local_month()); 150 | ASSERT_EQ(28, f.local_day()); 151 | ASSERT_EQ(8, f.local_hour()); 152 | ASSERT_EQ(45, f.local_minute()); 153 | ASSERT_EQ(2, f.local_second()); 154 | 155 | f = "2013-02-28"; 156 | 157 | ASSERT_EQ(2013, f.local_year()); 158 | ASSERT_EQ(2, f.local_month()); 159 | ASSERT_EQ(27, f.local_day()); 160 | ASSERT_EQ(19, f.local_hour()); 161 | ASSERT_EQ(0.0, f.local_minute()); 162 | ASSERT_EQ(0.0, f.local_second()); 163 | 164 | f = "2013-02-28T13:45:02Z+0100"; 165 | 166 | ASSERT_EQ(2013, f.local_year()); 167 | ASSERT_EQ(2, f.local_month()); 168 | ASSERT_EQ(28, f.local_day()); 169 | ASSERT_EQ(7, f.local_hour()); 170 | ASSERT_EQ(45, f.local_minute()); 171 | ASSERT_EQ(2, f.local_second()); 172 | 173 | f = "2013-02-28T13:45:02Z+07:30"; 174 | ASSERT_STREQ("2013-02-28T06:15:02", f.to_iso8601(false).c_str()); 175 | 176 | f = "2013-02-28T13:45:02Z-03"; 177 | ASSERT_STREQ("2013-02-28T16:45:02", f.to_iso8601(false).c_str()); 178 | 179 | f = "2013-12-31T23:59"; 180 | ASSERT_STREQ("2013-12-31T23:59:00", f.to_iso8601(false).c_str()); 181 | 182 | f.clear(); 183 | 184 | ASSERT_TRUE(f.is_null()); 185 | ASSERT_TRUE(f.is_dirty()); 186 | 187 | ASSERT_THROW(f = "203-12-31T23:59", runtime_error); 188 | 189 | f = "2013-12-31T23:59:00.068484"; 190 | ASSERT_STREQ("2013-12-31T23:59:00", f.to_iso8601(false).c_str()); 191 | 192 | f = "2013-12-31T23:59:00+0200"; 193 | ASSERT_STREQ("2013-12-31T21:59:00", f.to_iso8601(false).c_str()); 194 | ASSERT_STREQ("31/12/2013 16:59", f.to_local("%d/%m/%Y %H:%M").c_str()); 195 | 196 | f = "2013-12-31T23:59:00-02"; 197 | ASSERT_STREQ("2014-01-01T01:59:00", f.to_iso8601(false).c_str()); 198 | 199 | f = "2013-12-31T23:59:00.005-02:45"; 200 | ASSERT_STREQ("2014-01-01T02:44:00", f.to_iso8601(false).c_str()); 201 | 202 | time_t now_ts = time(NULL); 203 | ASSERT_EQ(now_ts - 18000, utc_time()); 204 | } 205 | 206 | TEST(FieldTest, PrimaryField) 207 | { 208 | Primary f; 209 | 210 | ASSERT_TRUE(f.is_null()); 211 | ASSERT_FALSE(f.is_dirty()); 212 | 213 | f = 3; 214 | 215 | ASSERT_FALSE(f.is_null()); 216 | ASSERT_TRUE(f.is_dirty()); 217 | 218 | ASSERT_THROW(f = 5, runtime_error); 219 | 220 | f = 3; 221 | 222 | ASSERT_STREQ("3", string(f).c_str()); 223 | 224 | Primary f2; 225 | f2.set(3, true); 226 | 227 | ASSERT_FALSE(f2.is_null()); 228 | ASSERT_FALSE(f2.is_dirty()); 229 | 230 | ASSERT_THROW(f2 = 5, runtime_error); 231 | ASSERT_THROW(f2.clear(), runtime_error); 232 | 233 | f2 = Primary(); 234 | 235 | ASSERT_TRUE(f2.is_null()); 236 | ASSERT_FALSE(f2.is_dirty()); 237 | } 238 | 239 | TEST(FieldTest, CleanSet) 240 | { 241 | Field f; 242 | 243 | ASSERT_TRUE(f.is_null()); 244 | ASSERT_FALSE(f.is_dirty()); 245 | 246 | f.set(3, true); 247 | 248 | ASSERT_FALSE(f.is_null()); 249 | ASSERT_FALSE(f.is_dirty()); 250 | 251 | f.clear(); 252 | 253 | ASSERT_TRUE(f.is_null()); 254 | ASSERT_TRUE(f.is_dirty()); 255 | 256 | Field f2; 257 | 258 | ASSERT_TRUE(f2.is_null()); 259 | ASSERT_FALSE(f2.is_dirty()); 260 | 261 | f2.set("flaf", true); 262 | 263 | ASSERT_FALSE(f2.is_null()); 264 | ASSERT_FALSE(f2.is_dirty()); 265 | 266 | Field f3; 267 | f3.clear(true); 268 | ASSERT_FALSE(f3.is_dirty()); 269 | } 270 | 271 | TEST(FieldTest, DirtyFlag) 272 | { 273 | Field f; 274 | 275 | ASSERT_TRUE(f.is_null()); 276 | ASSERT_FALSE(f.is_dirty()); 277 | 278 | f.set(3, true); 279 | 280 | ASSERT_FALSE(f.is_null()); 281 | ASSERT_FALSE(f.is_dirty()); 282 | 283 | f = 3; 284 | ASSERT_FALSE(f.is_dirty()); 285 | 286 | f = 5; 287 | ASSERT_TRUE(f.is_dirty()); 288 | 289 | Field f2; 290 | 291 | ASSERT_TRUE(f2.is_null()); 292 | ASSERT_FALSE(f2.is_dirty()); 293 | 294 | f2.set("flaf", true); 295 | 296 | ASSERT_FALSE(f2.is_null()); 297 | ASSERT_FALSE(f2.is_dirty()); 298 | 299 | f2 = "flaf"; 300 | ASSERT_FALSE(f2.is_dirty()); 301 | 302 | f2 = "blob"; 303 | ASSERT_TRUE(f2.is_dirty()); 304 | 305 | Field f3; 306 | 307 | ASSERT_TRUE(f3.is_null()); 308 | ASSERT_FALSE(f3.is_dirty()); 309 | 310 | f3.set("2030-01-02", true); 311 | 312 | ASSERT_FALSE(f3.is_null()); 313 | ASSERT_FALSE(f3.is_dirty()); 314 | 315 | f3 = "2030-01-02"; 316 | ASSERT_FALSE(f3.is_dirty()); 317 | 318 | f3 = "2030-01-02T01:14:52"; 319 | ASSERT_TRUE(f3.is_dirty()); 320 | 321 | Field f5; 322 | f5.clear(true); 323 | ASSERT_FALSE(f5.is_dirty()); 324 | f5 = 5; 325 | f5.clear(true); 326 | ASSERT_TRUE(f5.is_dirty()); 327 | 328 | Field f6; 329 | 330 | ASSERT_TRUE(f6.is_null()); 331 | ASSERT_FALSE(f6.is_dirty()); 332 | 333 | f6.touch(); 334 | 335 | ASSERT_TRUE(f6.is_null()); 336 | ASSERT_TRUE(f6.is_dirty()); 337 | } 338 | 339 | TEST(FieldTest, Stream) 340 | { 341 | Field f1; 342 | f1 = "flaf"; 343 | 344 | Field f2; 345 | f2 = 3.1; 346 | 347 | ostringstream s; 348 | 349 | s << f1; 350 | s << f2; 351 | 352 | ASSERT_STREQ("flaf3.1", s.str().c_str()); 353 | } 354 | 355 | TEST(FieldTest, FieldAssignment) 356 | { 357 | Field f1; 358 | Field f2; 359 | Field f3; 360 | Field f4; 361 | Field f5; 362 | Field f6; 363 | Primary f7; 364 | Primary f8; 365 | 366 | ASSERT_TRUE(f1.is_null()); 367 | ASSERT_TRUE(f2.is_null()); 368 | 369 | f1 = f2; 370 | 371 | ASSERT_TRUE(f1.is_null()); 372 | ASSERT_TRUE(f2.is_null()); 373 | 374 | f2 = f1; 375 | 376 | ASSERT_TRUE(f1.is_null()); 377 | ASSERT_TRUE(f2.is_null()); 378 | 379 | f3 = f4; 380 | f5 = f6; 381 | 382 | ASSERT_TRUE(f3.is_null()); 383 | ASSERT_TRUE(f5.is_null()); 384 | 385 | f7 = f8; 386 | 387 | ASSERT_TRUE(f7.is_null()); 388 | 389 | f1 = f8; 390 | f2 = f7; 391 | 392 | ASSERT_TRUE(f1.is_null()); 393 | ASSERT_TRUE(f2.is_null()); 394 | } 395 | 396 | -------------------------------------------------------------------------------- /include/restful_mapper/api.h: -------------------------------------------------------------------------------- 1 | #ifndef RESTFUL_MAPPER_API_H 2 | #define RESTFUL_MAPPER_API_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | namespace restful_mapper 12 | { 13 | 14 | enum RequestType { GET, POST, PUT, DEL }; 15 | 16 | /** 17 | * Lazy evaluated singleton class holding global API configuration. 18 | */ 19 | class Api 20 | { 21 | public: 22 | Api(); 23 | ~Api(); 24 | 25 | static std::string get(const std::string &endpoint) 26 | { 27 | return instance().get_(endpoint); 28 | } 29 | 30 | static std::string post(const std::string &endpoint, const std::string &body) 31 | { 32 | return instance().post_(endpoint, body); 33 | } 34 | 35 | static std::string put(const std::string &endpoint, const std::string &body) 36 | { 37 | return instance().put_(endpoint, body); 38 | } 39 | 40 | static std::string del(const std::string &endpoint) 41 | { 42 | return instance().del_(endpoint); 43 | } 44 | 45 | static std::string escape(const std::string &value) 46 | { 47 | return instance().escape_(value); 48 | } 49 | 50 | static std::string query_param(const std::string &url, const std::string ¶m, const std::string &value) 51 | { 52 | return instance().query_param_(url, param, value); 53 | } 54 | 55 | static std::string url() 56 | { 57 | return instance().url_; 58 | } 59 | 60 | static std::string url(const std::string &endpoint) 61 | { 62 | return instance().url_ + endpoint; 63 | } 64 | 65 | static std::string set_url(const std::string &url) 66 | { 67 | return instance().url_ = url; 68 | } 69 | 70 | static std::string proxy() 71 | { 72 | return instance().proxy_; 73 | } 74 | 75 | static std::string set_proxy(const std::string &proxy) 76 | { 77 | return instance().proxy_ = proxy; 78 | } 79 | 80 | static void clear_proxy() 81 | { 82 | instance().read_environment_proxy(); 83 | } 84 | 85 | static std::string username() 86 | { 87 | return instance().username_; 88 | } 89 | 90 | static std::string set_username(const std::string &username) 91 | { 92 | return instance().username_ = username; 93 | } 94 | 95 | static std::string password() 96 | { 97 | return instance().password_; 98 | } 99 | 100 | static std::string set_password(const std::string &password) 101 | { 102 | return instance().password_ = password; 103 | } 104 | 105 | private: 106 | std::string url_; 107 | std::string proxy_; 108 | std::string username_; 109 | std::string password_; 110 | static const char *user_agent_; 111 | static const char *content_type_; 112 | void *curl_handle_; 113 | 114 | // Dont forget to declare these two. You want to make sure they 115 | // are unaccessable otherwise you may accidently get copies of 116 | // your singleton appearing. 117 | Api(Api const &); // Don't Implement 118 | void operator=(Api const &); // Don't implement 119 | 120 | /** 121 | * Return the singleton instance 122 | */ 123 | static Api &instance() 124 | { 125 | static Api instance; // Guaranteed to be destroyed, instantiated on first use 126 | 127 | return instance; 128 | } 129 | 130 | // Curl read callback function 131 | std::string send_request(const RequestType &type, const std::string &endpoint, const std::string &body) const; 132 | 133 | // Curl write callback function 134 | static size_t write_callback(void *ptr, size_t size, size_t nmemb, void *userdata); 135 | 136 | // Curl read callback function 137 | static size_t read_callback(void *ptr, size_t size, size_t nmemb, void *userdata); 138 | 139 | // Check whether an error occurred 140 | static void check_http_error(const RequestType &type, const std::string &endpoint, long &http_code, const std::string &response_body); 141 | 142 | // Get environment proxy into proxy_ 143 | void read_environment_proxy(); 144 | 145 | // Request methods 146 | std::string get_(const std::string &endpoint) const; 147 | std::string post_(const std::string &endpoint, const std::string &body) const; 148 | std::string put_(const std::string &endpoint, const std::string &body) const; 149 | std::string del_(const std::string &endpoint) const; 150 | 151 | // String methods 152 | std::string escape_(const std::string &value) const; 153 | std::string query_param_(const std::string &url, const std::string ¶m, const std::string &value) const; 154 | }; 155 | 156 | class ApiError : public std::runtime_error 157 | { 158 | public: 159 | explicit ApiError(const std::string &what, const int &code) 160 | : std::runtime_error(what), code_(code) {} 161 | virtual ~ApiError() throw() {} 162 | 163 | virtual const int &code() const 164 | { 165 | return code_; 166 | } 167 | 168 | private: 169 | int code_; 170 | }; 171 | 172 | class AuthenticationError : public ApiError 173 | { 174 | public: 175 | explicit AuthenticationError() 176 | : ApiError("Unable to authenticate using the specified credentials", 401) {} 177 | virtual ~AuthenticationError() throw() {} 178 | }; 179 | 180 | class ResponseError : public ApiError 181 | { 182 | public: 183 | explicit ResponseError(const std::string &what, const int &code, const std::string &details) 184 | : ApiError(what, code), details_(details) {} 185 | virtual ~ResponseError() throw() {} 186 | 187 | virtual const char *details() const 188 | { 189 | return details_.c_str(); 190 | } 191 | 192 | private: 193 | std::string details_; 194 | }; 195 | 196 | class BadRequestError : public ApiError 197 | { 198 | public: 199 | explicit BadRequestError(const std::string &json_struct) : ApiError("", 400) 200 | { 201 | try 202 | { 203 | Json::Parser parser(json_struct); 204 | what_ = parser.find("message").to_string(); 205 | } 206 | catch (std::runtime_error &e) 207 | { 208 | // Swallow 209 | } 210 | } 211 | virtual ~BadRequestError() throw() {} 212 | 213 | virtual const char *what() const throw() 214 | { 215 | return what_.c_str(); 216 | } 217 | 218 | private: 219 | std::string what_; 220 | }; 221 | 222 | class ValidationError : public ApiError 223 | { 224 | public: 225 | typedef std::map FieldMap; 226 | 227 | explicit ValidationError(const std::string &json_struct) : ApiError("", 400) 228 | { 229 | try 230 | { 231 | std::string separator = ""; 232 | Json::Parser parser(json_struct); 233 | 234 | std::map errors; 235 | errors = parser.find("validation_errors").to_map(); 236 | 237 | std::map::const_iterator i, i_end = errors.end(); 238 | for (i = errors.begin(); i != i_end; ++i) 239 | { 240 | std::string field_name = i->first; 241 | 242 | errors_[field_name] = parse_errors(i->second, 1, false); 243 | 244 | what_ += separator; 245 | what_ += format_field(field_name); 246 | what_ += parse_errors(i->second); 247 | separator = "\n"; 248 | } 249 | } 250 | catch (std::runtime_error &e) 251 | { 252 | // Swallow 253 | } 254 | } 255 | 256 | virtual ~ValidationError() throw() {} 257 | 258 | const FieldMap &errors() const 259 | { 260 | return errors_; 261 | } 262 | 263 | virtual const char *what() const throw() 264 | { 265 | return what_.c_str(); 266 | } 267 | 268 | std::string operator[](const std::string &field) const 269 | { 270 | FieldMap::const_iterator i = errors_.find(field); 271 | 272 | if (i != errors_.end()) 273 | { 274 | return i->second; 275 | } 276 | 277 | return ""; 278 | } 279 | 280 | private: 281 | FieldMap errors_; 282 | std::string what_; 283 | 284 | std::string parse_errors(const Json::Node &node, const unsigned int &indent = 1, const bool &add_space = true) 285 | { 286 | if (node.is_array()) 287 | { 288 | std::string formatted; 289 | 290 | std::vector errors = node.to_array(); 291 | 292 | std::vector::const_iterator i, i_end = errors.end(); 293 | for (i = errors.begin(); i != i_end; ++i) 294 | { 295 | formatted += parse_errors(*i, indent); 296 | } 297 | 298 | return formatted; 299 | } 300 | else if (node.is_map()) 301 | { 302 | std::string formatted; 303 | 304 | std::map errors = node.to_map(); 305 | 306 | std::map::const_iterator i, i_end = errors.end(); 307 | for (i = errors.begin(); i != i_end; ++i) 308 | { 309 | std::string field_name = i->first; 310 | std::string field_value = parse_errors(i->second, indent + 1); 311 | 312 | formatted += "\n"; 313 | for (unsigned int j = 0; j < indent; j++) 314 | { 315 | formatted += " "; 316 | } 317 | formatted += format_field(field_name); 318 | formatted += field_value; 319 | } 320 | 321 | return formatted; 322 | } 323 | else if (node.is_string()) 324 | { 325 | std::string formatted; 326 | 327 | if (add_space) 328 | { 329 | formatted += " "; 330 | } 331 | 332 | formatted += node.to_string(); 333 | 334 | return formatted; 335 | } 336 | 337 | return ""; 338 | } 339 | 340 | std::string format_field(const std::string &field) 341 | { 342 | std::string formatted(field); 343 | std::replace(formatted.begin(), formatted.end(), '_', ' '); 344 | formatted[0] = std::toupper(formatted[0]); 345 | return formatted; 346 | } 347 | }; 348 | 349 | } 350 | 351 | #endif // RESTFUL_MAPPER_API_H 352 | 353 | -------------------------------------------------------------------------------- /src/api.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | using namespace std; 9 | using namespace restful_mapper; 10 | 11 | // Initialize user agent string 12 | const char *Api::user_agent_ = "restful_mapper/" VERSION; 13 | 14 | // Initialize content type string 15 | const char *Api::content_type_ = "application/json"; 16 | 17 | // Struct used for sending data 18 | typedef struct 19 | { 20 | const char *data; 21 | size_t length; 22 | } RequestBody; 23 | 24 | // Helper macros 25 | #define MAKE_HEADER(name, value) (std::string(name) + ": " + std::string(value)).c_str() 26 | #define CURL_HANDLE static_cast(instance().curl_handle_) 27 | 28 | // Initialize curl 29 | Api::Api() 30 | { 31 | curl_handle_ = static_cast(curl_easy_init()); 32 | 33 | if (!curl_handle_) 34 | { 35 | throw ApiError("Unable to initialize libcurl", 0); 36 | } 37 | 38 | // Read environment proxy 39 | read_environment_proxy(); 40 | } 41 | 42 | // Free curl 43 | Api::~Api() 44 | { 45 | if (curl_handle_) 46 | { 47 | curl_easy_cleanup(CURL_HANDLE); 48 | } 49 | } 50 | 51 | /** 52 | * @brief HTTP GET method 53 | * 54 | * @param endpoint url to query 55 | * 56 | * @return response body 57 | */ 58 | string Api::get_(const string &endpoint) const 59 | { 60 | return send_request(GET, endpoint, ""); 61 | } 62 | 63 | /** 64 | * @brief HTTP POST method 65 | * 66 | * @param endpoint url to query 67 | * @param data HTTP POST body 68 | * 69 | * @return response body 70 | */ 71 | string Api::post_(const string &endpoint, const string &body) const 72 | { 73 | return send_request(POST, endpoint, body); 74 | } 75 | 76 | /** 77 | * @brief HTTP PUT method 78 | * 79 | * @param endpoint url to query 80 | * @param data HTTP PUT body 81 | * 82 | * @return response body 83 | */ 84 | string Api::put_(const string &endpoint, const string &body) const 85 | { 86 | return send_request(PUT, endpoint, body); 87 | } 88 | 89 | /** 90 | * @brief HTTP DEL method 91 | * 92 | * @param endpoint url to query 93 | * 94 | * @return response body 95 | */ 96 | string Api::del_(const string &endpoint) const 97 | { 98 | return send_request(DEL, endpoint, ""); 99 | } 100 | 101 | /** 102 | * @brief URL encode the specified string 103 | * 104 | * @param value the string to URL encode 105 | * 106 | * @return the URL encoded string 107 | */ 108 | string Api::escape_(const string &value) const 109 | { 110 | char *curl_esc = curl_easy_escape(CURL_HANDLE, value.c_str(), value.size()); 111 | string escaped(curl_esc); 112 | curl_free(curl_esc); 113 | 114 | return escaped; 115 | } 116 | 117 | /** 118 | * @brief Add a query parameter to the specified URL 119 | * 120 | * @param url the URL to add the parameter to 121 | * @param param the parameter name 122 | * @param value the parameter value, will be URL encoded 123 | * 124 | * @return the new URL that includes the query parameter 125 | */ 126 | string Api::query_param_(const string &url, const string ¶m, const string &value) const 127 | { 128 | string query_part; 129 | 130 | if (url.find("http://") == 0 && url.find('/', 7) == string::npos) 131 | { 132 | query_part += "/"; 133 | } 134 | 135 | if (url.find('?') != string::npos) 136 | { 137 | query_part += "&"; 138 | } 139 | else 140 | { 141 | query_part += "?"; 142 | } 143 | 144 | query_part += param; 145 | query_part += "="; 146 | query_part += escape_(value); 147 | 148 | return url + query_part; 149 | } 150 | 151 | /** 152 | * @brief wrapper to perform curl request 153 | * 154 | * @param type the request type 155 | * @param endpoint url to query 156 | * @param data HTTP PUT body 157 | * 158 | * @return 159 | */ 160 | string Api::send_request(const RequestType &type, const string &endpoint, const string &body) const 161 | { 162 | curl_slist *header = NULL; 163 | 164 | // Create return struct 165 | string response_body; 166 | 167 | // Initialize request body 168 | RequestBody request_body; 169 | request_body.data = body.c_str(); 170 | request_body.length = body.size(); 171 | 172 | // Reset libcurl 173 | curl_easy_reset(CURL_HANDLE); 174 | 175 | // Debug output 176 | //curl_easy_setopt(CURL_HANDLE, CURLOPT_VERBOSE, 1); 177 | 178 | // Set user agent 179 | curl_easy_setopt(CURL_HANDLE, CURLOPT_USERAGENT, Api::user_agent_); 180 | 181 | // Set query URL 182 | curl_easy_setopt(CURL_HANDLE, CURLOPT_URL, url(endpoint).c_str()); 183 | 184 | // Set proxy 185 | curl_easy_setopt(CURL_HANDLE, CURLOPT_PROXY, proxy_.c_str()); 186 | 187 | switch (type) 188 | { 189 | case POST: 190 | // Now specify we want to POST data 191 | curl_easy_setopt(CURL_HANDLE, CURLOPT_POST, 1L); 192 | 193 | // Set data size 194 | curl_easy_setopt(CURL_HANDLE, CURLOPT_POSTFIELDSIZE, request_body.length); 195 | 196 | break; 197 | 198 | case PUT: 199 | // Now specify we want to PUT data 200 | curl_easy_setopt(CURL_HANDLE, CURLOPT_POST, 1L); 201 | curl_easy_setopt(CURL_HANDLE, CURLOPT_CUSTOMREQUEST, "PUT"); 202 | 203 | // Set data size 204 | curl_easy_setopt(CURL_HANDLE, CURLOPT_POSTFIELDSIZE, request_body.length); 205 | 206 | break; 207 | 208 | case DEL: 209 | // Set HTTP DEL METHOD 210 | curl_easy_setopt(CURL_HANDLE, CURLOPT_CUSTOMREQUEST, "DELETE"); 211 | 212 | break; 213 | } 214 | 215 | switch (type) 216 | { 217 | case POST: 218 | case PUT: 219 | // Set read callback function 220 | curl_easy_setopt(CURL_HANDLE, CURLOPT_READFUNCTION, Api::read_callback); 221 | 222 | // Set data object to pass to callback function 223 | curl_easy_setopt(CURL_HANDLE, CURLOPT_READDATA, &request_body); 224 | 225 | // Set content-type header 226 | header = curl_slist_append(header, MAKE_HEADER("Content-Type", content_type_)); 227 | 228 | break; 229 | } 230 | 231 | // Set callback function 232 | curl_easy_setopt(CURL_HANDLE, CURLOPT_WRITEFUNCTION, Api::write_callback); 233 | 234 | // Set data object to pass to callback function 235 | curl_easy_setopt(CURL_HANDLE, CURLOPT_WRITEDATA, &response_body); 236 | 237 | // Specify authentication information 238 | if (!username().empty()) 239 | { 240 | curl_easy_setopt(CURL_HANDLE, CURLOPT_HTTPAUTH, CURLAUTH_BASIC); 241 | curl_easy_setopt(CURL_HANDLE, CURLOPT_USERNAME, username().c_str()); 242 | curl_easy_setopt(CURL_HANDLE, CURLOPT_PASSWORD, password().c_str()); 243 | } 244 | 245 | // Set content negotiation header 246 | header = curl_slist_append(header, MAKE_HEADER("Accept", content_type_)); 247 | curl_easy_setopt(CURL_HANDLE, CURLOPT_HTTPHEADER, header); 248 | 249 | // Prepare buffer for error messages 250 | char errors[CURL_ERROR_SIZE]; 251 | curl_easy_setopt(CURL_HANDLE, CURLOPT_ERRORBUFFER, &errors); 252 | 253 | // Perform the actual query 254 | CURLcode res = curl_easy_perform(CURL_HANDLE); 255 | 256 | // Free header list 257 | curl_slist_free_all(header); 258 | 259 | // Handle unexpected internal errors 260 | if (res != 0) 261 | { 262 | throw ResponseError(curl_easy_strerror(res), res, errors); 263 | } 264 | 265 | // Handle server-side erros 266 | long http_code = 0; 267 | curl_easy_getinfo(CURL_HANDLE, CURLINFO_RESPONSE_CODE, &http_code); 268 | 269 | check_http_error(type, endpoint, http_code, response_body); 270 | 271 | return response_body; 272 | } 273 | 274 | /** 275 | * @brief write callback function for libcurl 276 | * 277 | * @param data returned data of size (size*nmemb) 278 | * @param size size parameter 279 | * @param nmemb memblock parameter 280 | * @param userdata pointer to user data to save/work with return data 281 | * 282 | * @return (size * nmemb) 283 | */ 284 | size_t Api::write_callback(void *data, size_t size, size_t nmemb, void *userdata) 285 | { 286 | string *r = reinterpret_cast(userdata); 287 | r->append(reinterpret_cast(data), size * nmemb); 288 | 289 | return (size * nmemb); 290 | } 291 | 292 | /** 293 | * @brief read callback function for libcurl 294 | * 295 | * @param pointer of max size (size*nmemb) to write data to 296 | * @param size size parameter 297 | * @param nmemb memblock parameter 298 | * @param userdata pointer to user data to read data from 299 | * 300 | * @return (size * nmemb) 301 | */ 302 | size_t Api::read_callback(void *data, size_t size, size_t nmemb, void *userdata) 303 | { 304 | // Get upload struct 305 | RequestBody *body = reinterpret_cast(userdata); 306 | 307 | // Set correct sizes 308 | size_t curl_size = size * nmemb; 309 | size_t copy_size = (body->length < curl_size) ? body->length : curl_size; 310 | 311 | // Copy data to buffer 312 | memcpy(data, body->data, copy_size); 313 | 314 | // Decrement length and increment data pointer 315 | body->length -= copy_size; 316 | body->data += copy_size; 317 | 318 | // Return copied size 319 | return copy_size; 320 | } 321 | 322 | /** 323 | * @brief Check whether an error occurred 324 | * 325 | * @param type the request type 326 | * @param http_code the response code 327 | * @param response_body the response body 328 | */ 329 | void Api::check_http_error(const RequestType &type, const string &endpoint, long &http_code, const string &response_body) 330 | { 331 | // Handle BadRequestError and ValidationError 332 | if (http_code == 400) 333 | { 334 | if (response_body.find("validation_errors") != string::npos) 335 | { 336 | throw ValidationError(response_body); 337 | } 338 | else 339 | { 340 | throw BadRequestError(response_body); 341 | } 342 | } 343 | 344 | // Handle AuthenticationError 345 | if (http_code == 401) 346 | { 347 | throw AuthenticationError(); 348 | } 349 | 350 | // Handle other errors 351 | bool http_ok = false; 352 | 353 | switch (type) 354 | { 355 | case GET: 356 | http_ok = (http_code == 200); 357 | 358 | break; 359 | 360 | case POST: 361 | http_ok = (http_code == 201); 362 | 363 | break; 364 | 365 | case PUT: 366 | http_ok = (http_code == 200 || http_code == 201); 367 | 368 | break; 369 | 370 | case DEL: 371 | http_ok = (http_code == 204); 372 | 373 | break; 374 | } 375 | 376 | if (!http_ok) 377 | { 378 | ostringstream s; 379 | s << "Server at \"" << url(endpoint) << "\" responded with code " << http_code; 380 | 381 | throw ResponseError(s.str(), http_code, response_body); 382 | } 383 | } 384 | 385 | void Api::read_environment_proxy() 386 | { 387 | if (getenv("HTTP_PROXY")) 388 | { 389 | proxy_ = getenv("HTTP_PROXY"); 390 | } 391 | else 392 | { 393 | proxy_.clear(); 394 | } 395 | } 396 | 397 | -------------------------------------------------------------------------------- /include/restful_mapper/mapper.h: -------------------------------------------------------------------------------- 1 | #ifndef RESTFUL_MAPPER_MAPPER_H 2 | #define RESTFUL_MAPPER_MAPPER_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | namespace restful_mapper 9 | { 10 | 11 | enum MapperConfig 12 | { 13 | IGNORE_MISSING_FIELDS = 1, // Ignore missing fields in input 14 | INCLUDE_PRIMARY_KEY = 2, // Include primary key in output, if it is not null 15 | IGNORE_DIRTY_FLAG = 4, // Include non-dirty fields in output 16 | TOUCH_FIELDS = 8, // Touch fields after reading input 17 | KEEP_FIELDS_DIRTY = 16, // Do not clean fields after outputting 18 | OUTPUT_SINGLE_FIELD = 32, // Output only a single field (set in field_filter_) 19 | OUTPUT_SHALLOW = 64, // Do not recurse into relationships 20 | OMIT_PARENT_KEYS = 128 // Omit foreign keys for child objects 21 | }; 22 | 23 | class Mapper 24 | { 25 | public: 26 | Mapper(const int &flags = 0) 27 | { 28 | flags_ = flags; 29 | 30 | if (!should_output_single_field()) 31 | { 32 | emitter_.emit_map_open(); 33 | } 34 | } 35 | 36 | Mapper(std::string json_struct, const int &flags = 0) 37 | { 38 | flags_ = flags; 39 | 40 | if (!should_output_single_field()) 41 | { 42 | emitter_.emit_map_open(); 43 | } 44 | 45 | parser_.load(json_struct); 46 | } 47 | 48 | const int &flags() const 49 | { 50 | return flags_; 51 | } 52 | 53 | void set_flags(const int &flags) 54 | { 55 | flags_ = flags; 56 | } 57 | 58 | const std::string &field_filter() const 59 | { 60 | return field_filter_; 61 | } 62 | 63 | void set_field_filter(const std::string &field_filter) 64 | { 65 | field_filter_ = field_filter; 66 | } 67 | 68 | std::string dump() 69 | { 70 | if (!should_output_single_field()) 71 | { 72 | emitter_.emit_map_close(); 73 | } 74 | 75 | return emitter_.dump(); 76 | } 77 | 78 | std::string get(const char *key) const 79 | { 80 | return parser_.find(key).dump(); 81 | } 82 | 83 | void set(const char *key, std::string json_struct) 84 | { 85 | if (should_output_single_field() && field_filter_ != key) return; 86 | 87 | if (!should_output_single_field()) 88 | { 89 | emitter_.emit(key); 90 | } 91 | 92 | emitter_.emit_json(json_struct); 93 | } 94 | 95 | template void get(const char *key, Field &attr) const 96 | { 97 | if (parser_.exists(key)) 98 | { 99 | Json::Node node = parser_.find(key); 100 | 101 | if (node.is_null()) 102 | { 103 | attr.clear(true); 104 | } 105 | else 106 | { 107 | attr.set(node, true); 108 | } 109 | } 110 | else if (!should_ignore_missing_fields()) 111 | { 112 | Json::not_found(key); 113 | } 114 | 115 | if (should_touch_fields()) 116 | { 117 | attr.touch(); 118 | } 119 | } 120 | 121 | template void set(const char *key, const Field &attr) 122 | { 123 | if (should_output_single_field() && field_filter_ != key) return; 124 | if (!should_ignore_dirty_flag() && !attr.is_dirty()) return; 125 | 126 | if (!should_output_single_field()) 127 | { 128 | emitter_.emit(key); 129 | } 130 | 131 | if (attr.is_null()) 132 | { 133 | emitter_.emit_null(); 134 | } 135 | else 136 | { 137 | emitter_.emit(attr); 138 | } 139 | 140 | if (!should_keep_fields_dirty()) attr.clean(); 141 | } 142 | 143 | void get(const char *key, Field &attr) const 144 | { 145 | if (parser_.exists(key)) 146 | { 147 | Json::Node node = parser_.find(key); 148 | 149 | if (node.is_null()) 150 | { 151 | attr.clear(true); 152 | } 153 | else 154 | { 155 | attr.set(node.to_string(), true); 156 | } 157 | } 158 | else if (!should_ignore_missing_fields()) 159 | { 160 | Json::not_found(key); 161 | } 162 | 163 | if (should_touch_fields()) 164 | { 165 | attr.touch(); 166 | } 167 | } 168 | 169 | void set(const char *key, const Field &attr) 170 | { 171 | if (should_output_single_field() && field_filter_ != key) return; 172 | if (!should_ignore_dirty_flag() && !attr.is_dirty()) return; 173 | 174 | if (!should_output_single_field()) 175 | { 176 | emitter_.emit(key); 177 | } 178 | 179 | if (attr.is_null()) 180 | { 181 | emitter_.emit_null(); 182 | } 183 | else 184 | { 185 | emitter_.emit(attr.to_iso8601(true)); 186 | } 187 | 188 | if (!should_keep_fields_dirty()) attr.clean(); 189 | } 190 | 191 | void get(const char *key, Primary &attr) const 192 | { 193 | if (parser_.exists(key)) 194 | { 195 | Json::Node node = parser_.find(key); 196 | 197 | attr = Primary(); 198 | 199 | if (node.is_null()) 200 | { 201 | attr.clear(true); 202 | } 203 | else 204 | { 205 | attr.set(node.to_int(), true); 206 | } 207 | } 208 | else if (!should_ignore_missing_fields()) 209 | { 210 | Json::not_found(key); 211 | } 212 | 213 | if (should_touch_fields()) 214 | { 215 | attr.touch(); 216 | } 217 | } 218 | 219 | void set(const char *key, const Primary &attr) 220 | { 221 | if (should_output_single_field() && field_filter_ != key) return; 222 | if (!should_include_primary_key() || attr.is_null()) return; 223 | 224 | if (!should_output_single_field()) 225 | { 226 | emitter_.emit(key); 227 | } 228 | 229 | emitter_.emit(attr.get()); 230 | 231 | if (!should_keep_fields_dirty()) attr.clean(); 232 | } 233 | 234 | template void get(const char *key, Foreign &attr) const 235 | { 236 | if (parser_.exists(key)) 237 | { 238 | Json::Node node = parser_.find(key); 239 | 240 | if (node.is_null()) 241 | { 242 | attr.clear(true); 243 | } 244 | else 245 | { 246 | attr.set(node, true); 247 | } 248 | } 249 | else if (!should_ignore_missing_fields()) 250 | { 251 | Json::not_found(key); 252 | } 253 | 254 | if (should_touch_fields()) 255 | { 256 | attr.touch(); 257 | } 258 | } 259 | 260 | template void set(const char *key, const Foreign &attr) 261 | { 262 | if (should_output_single_field() && field_filter_ != key) return; 263 | if (!should_ignore_dirty_flag() && !attr.is_dirty()) return; 264 | if (should_omit_parent_keys() && parent_model_ == attr.class_name()) return; 265 | 266 | if (!should_output_single_field()) 267 | { 268 | emitter_.emit(key); 269 | } 270 | 271 | if (attr.is_null()) 272 | { 273 | emitter_.emit_null(); 274 | } 275 | else 276 | { 277 | emitter_.emit(attr); 278 | } 279 | 280 | if (!should_keep_fields_dirty()) attr.clean(); 281 | } 282 | 283 | template void get(const char *key, BelongsTo &attr) const 284 | { 285 | if (parser_.empty(key)) return; 286 | 287 | attr.from_json(get(key), (flags_ | INCLUDE_PRIMARY_KEY) & ~TOUCH_FIELDS); 288 | } 289 | 290 | template void set(const char *key, const BelongsTo &attr) 291 | { 292 | if (should_output_shallow()) return; 293 | if (should_output_single_field() && field_filter_ != key) return; 294 | if (!should_ignore_dirty_flag() && !attr.is_dirty()) return; 295 | if (should_omit_parent_keys() && parent_model_ == attr.class_name()) return; 296 | 297 | set(key, attr.to_json((flags_ | INCLUDE_PRIMARY_KEY | OMIT_PARENT_KEYS) & ~OUTPUT_SINGLE_FIELD, 298 | current_model_)); 299 | 300 | if (!should_keep_fields_dirty()) attr.clean(); 301 | } 302 | 303 | template void get(const char *key, HasOne &attr) const 304 | { 305 | if (parser_.empty(key)) return; 306 | 307 | attr.from_json(get(key), (flags_ | INCLUDE_PRIMARY_KEY) & ~TOUCH_FIELDS); 308 | } 309 | 310 | template void set(const char *key, const HasOne &attr) 311 | { 312 | if (should_output_shallow()) return; 313 | if (should_output_single_field() && field_filter_ != key) return; 314 | if (!should_ignore_dirty_flag() && !attr.is_dirty()) return; 315 | 316 | set(key, attr.to_json((flags_ | INCLUDE_PRIMARY_KEY | OMIT_PARENT_KEYS) & ~OUTPUT_SINGLE_FIELD, 317 | current_model_)); 318 | 319 | if (!should_keep_fields_dirty()) attr.clean(); 320 | } 321 | 322 | template void get(const char *key, HasMany &attr) const 323 | { 324 | if (parser_.empty(key)) return; 325 | 326 | attr.from_json(get(key), (flags_ | INCLUDE_PRIMARY_KEY) & ~TOUCH_FIELDS); 327 | } 328 | 329 | template void set(const char *key, const HasMany &attr) 330 | { 331 | if (should_output_shallow()) return; 332 | if (should_output_single_field() && field_filter_ != key) return; 333 | if (!should_ignore_dirty_flag() && !attr.is_dirty()) return; 334 | 335 | set(key, attr.to_json((flags_ | INCLUDE_PRIMARY_KEY | OMIT_PARENT_KEYS) & ~OUTPUT_SINGLE_FIELD, 336 | current_model_)); 337 | 338 | if (!should_keep_fields_dirty()) attr.clean(); 339 | } 340 | 341 | const std::string ¤t_model() const 342 | { 343 | return current_model_; 344 | } 345 | 346 | void set_current_model(const std::string &value) 347 | { 348 | current_model_ = value; 349 | } 350 | 351 | const std::string &parent_model() const 352 | { 353 | return parent_model_; 354 | } 355 | 356 | void set_parent_model(const std::string &value) 357 | { 358 | parent_model_ = value; 359 | } 360 | 361 | private: 362 | Json::Emitter emitter_; 363 | Json::Parser parser_; 364 | 365 | int flags_; 366 | std::string field_filter_; 367 | std::string current_model_; 368 | std::string parent_model_; 369 | 370 | inline bool should_ignore_missing_fields() const 371 | { 372 | return (flags_ & IGNORE_MISSING_FIELDS) == IGNORE_MISSING_FIELDS; 373 | } 374 | 375 | inline bool should_include_primary_key() const 376 | { 377 | return (flags_ & INCLUDE_PRIMARY_KEY) == INCLUDE_PRIMARY_KEY; 378 | } 379 | 380 | inline bool should_ignore_dirty_flag() const 381 | { 382 | return (flags_ & IGNORE_DIRTY_FLAG) == IGNORE_DIRTY_FLAG; 383 | } 384 | 385 | inline bool should_touch_fields() const 386 | { 387 | return (flags_ & TOUCH_FIELDS) == TOUCH_FIELDS; 388 | } 389 | 390 | inline bool should_keep_fields_dirty() const 391 | { 392 | return (flags_ & KEEP_FIELDS_DIRTY) == KEEP_FIELDS_DIRTY; 393 | } 394 | 395 | inline bool should_output_single_field() const 396 | { 397 | return (flags_ & OUTPUT_SINGLE_FIELD) == OUTPUT_SINGLE_FIELD; 398 | } 399 | 400 | inline bool should_output_shallow() const 401 | { 402 | return (flags_ & OUTPUT_SHALLOW) == OUTPUT_SHALLOW; 403 | } 404 | 405 | inline bool should_omit_parent_keys() const 406 | { 407 | return (flags_ & OMIT_PARENT_KEYS) == OMIT_PARENT_KEYS; 408 | } 409 | }; 410 | 411 | } 412 | 413 | #endif // RESTFUL_MAPPER_MAPPER_H 414 | 415 | -------------------------------------------------------------------------------- /include/restful_mapper/model_collection.h: -------------------------------------------------------------------------------- 1 | #ifndef RESTFUL_MAPPER_MODEL_COLLECTION_H 2 | #define RESTFUL_MAPPER_MODEL_COLLECTION_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | namespace restful_mapper 12 | { 13 | 14 | template 15 | class ModelCollection 16 | { 17 | public: 18 | ModelCollection() {} 19 | virtual ~ModelCollection() {} 20 | 21 | const std::vector &items() const 22 | { 23 | return items_; 24 | } 25 | 26 | ModelCollection clone() const 27 | { 28 | ModelCollection cloned_list; 29 | const_iterator i, i_end = end(); 30 | 31 | for (i = begin(); i != i_end; ++i) 32 | { 33 | cloned_list.push_back(i->clone()); 34 | } 35 | 36 | return cloned_list; 37 | } 38 | 39 | T &find(const long long &id) 40 | { 41 | iterator i, i_end = end(); 42 | 43 | for (i = begin(); i != i_end; ++i) 44 | { 45 | if (i->primary() == id) return *i; 46 | } 47 | 48 | std::ostringstream s; 49 | s << "Cannot find " << type_info_name(typeid(T)) << " with id " << id; 50 | throw std::out_of_range(s.str()); 51 | } 52 | 53 | T &find(const int &id) 54 | { 55 | return find((long long) id); 56 | } 57 | 58 | const T &find(const long long &id) const 59 | { 60 | const_iterator i, i_end = end(); 61 | 62 | for (i = begin(); i != i_end; ++i) 63 | { 64 | if (i->primary() == id) return *i; 65 | } 66 | 67 | std::ostringstream s; 68 | s << "Cannot find " << type_info_name(typeid(T)) << " with id " << id; 69 | throw std::out_of_range(s.str()); 70 | } 71 | 72 | const T &find(const int &id) const 73 | { 74 | return find((long long) id); 75 | } 76 | 77 | ModelCollection find(const std::string &field, const int &value) const 78 | { 79 | return find_by_field(field, Json::encode(value)); 80 | } 81 | 82 | T &find_first(const std::string &field, const int &value) 83 | { 84 | return find_first_by_field(field, Json::encode(value)); 85 | } 86 | 87 | const T &find_first(const std::string &field, const int &value) const 88 | { 89 | return find_first_by_field(field, Json::encode(value)); 90 | } 91 | 92 | ModelCollection find(const std::string &field, const long long &value) const 93 | { 94 | return find_by_field(field, Json::encode(value)); 95 | } 96 | 97 | T &find_first(const std::string &field, const long long &value) 98 | { 99 | return find_first_by_field(field, Json::encode(value)); 100 | } 101 | 102 | const T &find_first(const std::string &field, const long long &value) const 103 | { 104 | return find_first_by_field(field, Json::encode(value)); 105 | } 106 | 107 | ModelCollection find(const std::string &field, const double &value) const 108 | { 109 | return find_by_field(field, Json::encode(value)); 110 | } 111 | 112 | T &find_first(const std::string &field, const double &value) 113 | { 114 | return find_first_by_field(field, Json::encode(value)); 115 | } 116 | 117 | const T &find_first(const std::string &field, const double &value) const 118 | { 119 | return find_first_by_field(field, Json::encode(value)); 120 | } 121 | 122 | ModelCollection find(const std::string &field, const bool &value) const 123 | { 124 | return find_by_field(field, Json::encode(value)); 125 | } 126 | 127 | T &find_first(const std::string &field, const bool &value) 128 | { 129 | return find_first_by_field(field, Json::encode(value)); 130 | } 131 | 132 | const T &find_first(const std::string &field, const bool &value) const 133 | { 134 | return find_first_by_field(field, Json::encode(value)); 135 | } 136 | 137 | ModelCollection find(const std::string &field, const std::string &value) const 138 | { 139 | return find_by_field(field, Json::encode(value)); 140 | } 141 | 142 | T &find_first(const std::string &field, const std::string &value) 143 | { 144 | return find_first_by_field(field, Json::encode(value)); 145 | } 146 | 147 | const T &find_first(const std::string &field, const std::string &value) const 148 | { 149 | return find_first_by_field(field, Json::encode(value)); 150 | } 151 | 152 | ModelCollection find(const std::string &field, const char *value) const 153 | { 154 | return find_by_field(field, Json::encode(value)); 155 | } 156 | 157 | T &find_first(const std::string &field, const char *value) 158 | { 159 | return find_first_by_field(field, Json::encode(value)); 160 | } 161 | 162 | const T &find_first(const std::string &field, const char *value) const 163 | { 164 | return find_first_by_field(field, Json::encode(value)); 165 | } 166 | 167 | bool contains(const long long &id) const 168 | { 169 | const_iterator i, i_end = end(); 170 | 171 | for (i = begin(); i != i_end; ++i) 172 | { 173 | if (i->primary() == id) return true; 174 | } 175 | 176 | return false; 177 | } 178 | 179 | bool contains(const int &id) const 180 | { 181 | return contains((long long) id); 182 | } 183 | 184 | bool contains(const std::string &field, const int &value) const 185 | { 186 | return contains_by_field(field, Json::encode(value)); 187 | } 188 | 189 | bool contains(const std::string &field, const long long &value) const 190 | { 191 | return contains_by_field(field, Json::encode(value)); 192 | } 193 | 194 | bool contains(const std::string &field, const double &value) const 195 | { 196 | return contains_by_field(field, Json::encode(value)); 197 | } 198 | 199 | bool contains(const std::string &field, const bool &value) const 200 | { 201 | return contains_by_field(field, Json::encode(value)); 202 | } 203 | 204 | bool contains(const std::string &field, const std::string &value) const 205 | { 206 | return contains_by_field(field, Json::encode(value)); 207 | } 208 | 209 | bool contains(const std::string &field, const char *value) const 210 | { 211 | return contains_by_field(field, Json::encode(value)); 212 | } 213 | 214 | // Reimplement std::vector for convenience 215 | typedef typename std::vector::value_type value_type; 216 | typedef typename std::vector::allocator_type allocator_type; 217 | typedef typename std::vector::reference reference; 218 | typedef typename std::vector::const_reference const_reference; 219 | typedef typename std::vector::pointer pointer; 220 | typedef typename std::vector::const_pointer const_pointer; 221 | typedef typename std::vector::iterator iterator; 222 | typedef typename std::vector::const_iterator const_iterator; 223 | typedef typename std::vector::reverse_iterator reverse_iterator; 224 | typedef typename std::vector::const_reverse_iterator const_reverse_iterator; 225 | typedef typename std::vector::difference_type difference_type; 226 | typedef typename std::vector::size_type size_type; 227 | 228 | iterator begin() { return items_.begin(); } 229 | const_iterator begin() const { return items_.begin(); } 230 | iterator end() { return items_.end(); } 231 | const_iterator end() const { return items_.end(); } 232 | reverse_iterator rbegin() { return items_.rbegin(); } 233 | const_reverse_iterator rbegin() const { return items_.rbegin(); } 234 | reverse_iterator rend() { return items_.rend(); } 235 | const_reverse_iterator rend() const { return items_.rend(); } 236 | size_type size() const { return items_.size(); } 237 | size_type max_size() const { return items_.max_size(); } 238 | void resize(size_type n, value_type val = value_type()) { items_.resize(n, val); } 239 | size_type capacity() const { return items_.capacity(); } 240 | bool empty() const { return items_.empty(); } 241 | void reserve(size_type n) { items_.reserve(n); } 242 | reference operator[](size_type n) { return items_[n]; } 243 | const_reference operator[](size_type n) const { return items_[n]; } 244 | reference at(size_type n) { return items_.at(n); } 245 | const_reference at(size_type n) const { return items_.at(n); } 246 | reference front() { return items_.front(); } 247 | const_reference front() const { return items_.front(); } 248 | reference back() { return items_.back(); } 249 | const_reference back() const { return items_.back(); } 250 | template void assign(InputIterator first, InputIterator last) { items_.assign(first, last); } 251 | void assign(size_type n, const value_type val) { items_.assign(n, val); } 252 | void push_back(const value_type val) { items_.push_back(val); } 253 | void pop_back() { items_.pop_back(); } 254 | iterator insert(iterator position, const value_type val) { return items_.insert(position, val); } 255 | void insert(iterator position, size_type n, const value_type val) { items_.insert(position, n, val); } 256 | template void insert(iterator position, InputIterator first, InputIterator last) { items_.insert(position, first, last); } 257 | iterator erase(iterator position) { return items_.erase(position); } 258 | iterator erase(iterator first, iterator last) { return items_.erase(first, last); } 259 | void swap(ModelCollection& x) { items_.swap(x); } 260 | void clear() { items_.clear(); } 261 | allocator_type get_allocator() const { return items_.get_allocator(); } 262 | 263 | protected: 264 | std::vector items_; 265 | 266 | ModelCollection find_by_field(const std::string &field, const std::string &json_value) const 267 | { 268 | ModelCollection results; 269 | const_iterator i, i_end = end(); 270 | 271 | for (i = begin(); i != i_end; ++i) 272 | { 273 | if (i->read_field(field) == json_value) results.push_back(*i); 274 | } 275 | 276 | return results; 277 | } 278 | 279 | T &find_first_by_field(const std::string &field, const std::string &json_value) 280 | { 281 | iterator i, i_end = end(); 282 | 283 | for (i = begin(); i != i_end; ++i) 284 | { 285 | if (i->read_field(field) == json_value) return *i; 286 | } 287 | 288 | std::ostringstream s; 289 | s << "Cannot find " << type_info_name(typeid(T)) << " with " << field << " " << json_value; 290 | throw std::out_of_range(s.str()); 291 | } 292 | 293 | const T &find_first_by_field(const std::string &field, const std::string &json_value) const 294 | { 295 | const_iterator i, i_end = end(); 296 | 297 | for (i = begin(); i != i_end; ++i) 298 | { 299 | if (i->read_field(field) == json_value) return *i; 300 | } 301 | 302 | std::ostringstream s; 303 | s << "Cannot find " << type_info_name(typeid(T)) << " with " << field << " " << json_value; 304 | throw std::out_of_range(s.str()); 305 | } 306 | 307 | bool contains_by_field(const std::string &field, const std::string &json_value) const 308 | { 309 | const_iterator i, i_end = end(); 310 | 311 | for (i = begin(); i != i_end; ++i) 312 | { 313 | if (i->read_field(field) == json_value) return true; 314 | } 315 | 316 | return false; 317 | } 318 | }; 319 | 320 | } 321 | 322 | #endif // RESTFUL_MAPPER_MODEL_COLLECTION_H 323 | 324 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # restful_mapper # 2 | 3 | ORM for consuming RESTful APIs in C++ 4 | 5 | [![Build status](https://secure.travis-ci.org/logandk/restful_mapper.png)](http://travis-ci.org/logandk/restful_mapper) 6 | 7 | # Introduction # 8 | 9 | **restful\_mapper** connects business objects and Representational State 10 | Transfer (REST) web services. It implements object-relational mapping for REST 11 | web services to provide transparent proxying capabilities between a client 12 | (using **restful_mapper**) and a RESTful web service (that follows the 13 | conventions outlined below). 14 | 15 | The ideas and design philosophy are directly adopted from [Active Resource][3]. 16 | However, the API and functionality differ in some areas, where it makes sense. 17 | 18 | ## Design goals ## 19 | 20 | * _Simple_: Employ the principle of least surprise -- APIs should be clean and 21 | intuitive 22 | * _Portable_: Support old compilers -- all code is C++03 and C 23 | * _Robust_: Keep the code base small -- rely on proven and stable libraries to 24 | provide core functionality 25 | * _Internationalized_: Handle timezones and character sets automatically 26 | 27 | ## RESTful web service conventions ## 28 | 29 | RESTful web services come in many different forms, which are not all suitable 30 | for an ORM-style mapper such as **restful_mapper**. In order to be compatible 31 | with as many web services as possible, **restful_mapper** complies with the 32 | current best-practice for creating RESTful web services with JSON. 33 | 34 | Specifically, **restful_mapper** is modelled against the [Flask-Restless][4] 35 | library, which provides full-featured RESTful web services that follow best- 36 | practice design methods. 37 | 38 | The following basic conventions must be followed by the web service: 39 | 40 | * Uses standard `DELETE`, `GET`, `POST` and `PUT` requests for CRUD-operations 41 | * Collection of objects should use `objects` as the root JSON key 42 | * Objects in a relationship should be represented as nested JSON structures 43 | * If authentication is used, it must be HTTP basic authentication 44 | 45 | For exact details on expected request and response formats, see [Format of requests and responses][5]. 46 | 47 | # Building # 48 | 49 | **restful\_mapper** is built using [CMake][6]. 50 | 51 | ## Dependencies ## 52 | 53 | The following libraries are required to build **restful_mapper**. 54 | 55 | * [libcurl][7] - used for comminucating with the web service over HTTP 56 | * [yajl][8] - used to parse and emit JSON 57 | * [libiconv][9] - used to convert between character sets 58 | * [googletest][10] - required for tests only 59 | 60 | Invoking the following command will download and build these libraries. 61 | 62 | ```shell 63 | make vendor 64 | ``` 65 | 66 | On UNIX platforms, [libcurl][7] and [libiconv][9] are typically present on the 67 | system, and will not be built by the make command. If they are not present, 68 | they should be installed using the system package manager. 69 | 70 | ## Library ## 71 | 72 | After building the dependencies, invoke the following command to build **restful_mapper**. 73 | 74 | ```shell 75 | make 76 | ``` 77 | 78 | This will install **restful_mapper** as a static library in the `lib` folder. 79 | 80 | ## Tests ## 81 | 82 | The test suite can be built and run using the following command. 83 | 84 | ```shell 85 | make test 86 | ``` 87 | 88 | # Usage # 89 | 90 | ## API configuration ## 91 | 92 | Before making any requests to the web service, it must be configured using the 93 | following methods. 94 | 95 | The root URL of the web service is specified using the `set_url` method: 96 | 97 | ```c++ 98 | Api::set_url("http://localhost:5000/api"); 99 | ``` 100 | 101 | If the web service requires authentication, provide the username and password: 102 | 103 | ```c++ 104 | Api::set_username("admin"); 105 | Api::set_password("test"); 106 | ``` 107 | 108 | If you are using a proxy server, it can be specified through the `HTTP_PROXY` 109 | environment variable or using the `set_proxy` method: 110 | 111 | ```c++ 112 | Api::set_proxy("http://myproxy"); 113 | ``` 114 | 115 | ## Mapper configuration ## 116 | 117 | This example illustrates a complete object mapping: 118 | 119 | ```c++ 120 | using namespace std; 121 | using namespace restful_mapper; 122 | 123 | class User; 124 | class Alert; 125 | 126 | class Todo : public Model 127 | { 128 | public: 129 | Primary id; 130 | Field task; 131 | Field priority; 132 | Field time; 133 | Field completed; 134 | Field completed_on; 135 | Foreign user_id; 136 | BelongsTo user; 137 | HasOne alert; 138 | 139 | virtual void map_set(Mapper &mapper) const 140 | { 141 | mapper.set("id", id); 142 | mapper.set("task", task); 143 | mapper.set("priority", priority); 144 | mapper.set("time", time); 145 | mapper.set("completed", completed); 146 | mapper.set("completed_on", completed_on); 147 | mapper.set("user_id", user_id); 148 | mapper.set("user", user); 149 | mapper.set("alert", alert); 150 | } 151 | 152 | virtual void map_get(const Mapper &mapper) 153 | { 154 | mapper.get("id", id); 155 | mapper.get("task", task); 156 | mapper.get("priority", priority); 157 | mapper.get("time", time); 158 | mapper.get("completed", completed); 159 | mapper.get("completed_on", completed_on); 160 | mapper.get("user_id", user_id); 161 | mapper.get("user", user); 162 | mapper.get("alert", alert); 163 | } 164 | 165 | virtual std::string endpoint() const 166 | { 167 | return "/todo"; 168 | } 169 | 170 | virtual const Primary &primary() const 171 | { 172 | return id; 173 | } 174 | }; 175 | 176 | class User: public Model 177 | { 178 | public: 179 | Primary id; 180 | HasMany todos; 181 | ... 182 | }; 183 | ``` 184 | 185 | An API entity is declared by creating a class that inherits from and follows the 186 | interface defined in `restful_mapper::Model`. 187 | 188 | Each entity can hold a number of fields and relationships: 189 | 190 | * `restful_mapper::Primary` -- maps a primary key (integer) 191 | * `restful_mapper::Foreign` -- maps a foreign key (integer) 192 | * `restful_mapper::Field` -- maps a string literal (represented in locale charset) 193 | * `restful_mapper::Field` -- maps an integer value 194 | * `restful_mapper::Field` -- maps a floating point value 195 | * `restful_mapper::Field` -- maps a boolean value 196 | * `restful_mapper::Field` -- maps a datetime value (represented in the local timezone) 197 | * `restful_mapper::HasOne` -- maps a one-to-one or many-to-one relationship on the referenced side 198 | * `restful_mapper::BelongsTo` -- maps a one-to-one or many-to-one relationship on the foreign key side 199 | * `restful_mapper::HasMany` -- maps a one-to-many relationship 200 | 201 | The interface specified the following methods, which must be overriden: 202 | 203 | * `virtual void restful_mapper::Model::map_set(Mapper &mapper) const`
204 | Invoked upon saving an object to the web service. 205 | * `virtual void restful_mapper::Model::map_get(const Mapper &mapper)`
206 | Invoked upon requesting an object from the web service. 207 | * `virtual std::string restful_mapper::Model::endpoint() const`
208 | Specifies the API endpoint for this particular model. Relative to the web service 209 | root URL. 210 | * `virtual const Primary &restful_mapper::Model::primary()` const
211 | Specifies the primary key of the model. 212 | 213 | ## Working with objects ## 214 | 215 | Using the models defined above, the following operations are made available by 216 | **restful_mapper**. 217 | 218 | ### Requesting data ### 219 | 220 | ```c++ 221 | // Find a single item by id 222 | Todo t = Todo::find(2); 223 | 224 | // Outputting fields 225 | cout << t.task.get(); 226 | 227 | // Explicit conversions 228 | cout << (string) t.task; 229 | 230 | // Reload data from server 231 | t.reload(); 232 | 233 | // Get all items in collection 234 | Todo::Collection todos = Todo::find_all(); 235 | 236 | // Find an item in the collection by id 237 | todos.find(4); 238 | 239 | // Find all items in collection where task is "Do something" 240 | todos.find("task", "Do something"); 241 | 242 | // Find the first item in collection that has been completed 243 | todos.find_first("completed", true); 244 | ``` 245 | 246 | ### Saving data ### 247 | 248 | ```c++ 249 | // Create a new item 250 | Todo new_todo; 251 | new_todo.task = "Use restful_mapper"; 252 | new_todo.save(); 253 | 254 | // Update an existing item 255 | Todo old_todo = Todo::find(2); 256 | old_todo.completed = true; 257 | old_todo.save(); 258 | 259 | // Deleting an item 260 | old_todo.destroy(); 261 | 262 | // Create a clone with no id set (i.e. a new database object) 263 | Todo todo_clone = old_todo.clone(); 264 | todo_clone.save(); 265 | ``` 266 | 267 | ### Relationships ### 268 | 269 | ```c++ 270 | // Find an item including related items 271 | User u = User::find(1); 272 | 273 | // Get a related todo 274 | u.todos[2].task = "Do something else"; 275 | 276 | // Delete last item 277 | u.todos.pop_back(); 278 | 279 | // Add a new related item 280 | Todo new_todo; 281 | new_todo.task = "Use restful_mapper"; 282 | u.todos.push_back(new_todo); 283 | 284 | // Save user including all changes to related todos - will delete one, update one and add one todo 285 | u.save(); 286 | 287 | // Objects in one-to-one and many-to-one relationships are managed pointers 288 | Todo t = Todo::find(2); 289 | cout << t.user->email.get(); 290 | ``` 291 | 292 | ### Querying ### 293 | 294 | Supports the query operations [specified][11] by [Flask-Restless][4]. 295 | 296 | ```c++ 297 | // Query a single item 298 | Query q; 299 | q("task").like("Do someth%"); 300 | q("completed").eq(true); 301 | 302 | Todo todo = Todo::find(q); 303 | 304 | // Query a collection of items 305 | Query q; 306 | q("time").gt("1.45").lt("3.0"); 307 | q.order_by_asc(q.field("priority")); 308 | 309 | Todo::Collection todos = Todo::find_all(q); 310 | ``` 311 | 312 | ### Exceptions ### 313 | 314 | Some API errors are caught using custom exceptions. 315 | 316 | * Internal [libcurl][7] errors are represented as a `restful_mapper::ApiError`, 317 | which has adds a `code()` method to `std::runtime_error`. 318 | * `restful_mapper::AuthenticationError` is thrown when authentication fails, 319 | and has the same properties as `restful_mapper::ApiError`. 320 | * `restful_mapper::BadRequestError` is thrown when an error occurs on the API 321 | side, and has the same properties as `restful_mapper::ApiError`. 322 | * `restful_mapper::ValidationError` is thrown when one or more validation 323 | errors are thrown by the API. It has the same properties as 324 | `restful_mapper::ApiError`, but adds an `errors()` method, which returns a 325 | `restful_mapper::ValidationError::FieldMap` map with field names as keys and 326 | error messages as values. This map may also be accessed directly through the 327 | overloaded `operator[]`. 328 | 329 | # Contributing # 330 | 331 | Pull requests on [GitHub][1] are very welcome! Please try to follow these simple rules: 332 | 333 | * Please create a topic branch for every separate change you make. 334 | * Make sure your patches are well tested. All tests must pass on [Travis CI][2]. 335 | * Update this [`README.md`](http://github.com/logandk/restful_mapper/blob/master/README.md) if applicable. 336 | 337 | # License # 338 | 339 | This code is copyright 2013 Logan Raarup, and is released under the revised BSD License. 340 | 341 | For more information, see [`LICENSE`](http://github.com/logandk/restful_mapper/blob/master/LICENSE). 342 | 343 | 344 | [1]: http://github.com/logandk/restful_mapper 345 | [2]: http://travis-ci.org/logandk/restful_mapper 346 | [3]: https://github.com/rails/activeresource 347 | [4]: https://github.com/jfinkels/flask-restless 348 | [5]: https://flask-restless.readthedocs.org/en/latest/requestformat.html 349 | [6]: http://www.cmake.org 350 | [7]: http://curl.haxx.se/libcurl 351 | [8]: http://lloyd.github.io/yajl 352 | [9]: http://www.gnu.org/software/libiconv 353 | [10]: https://code.google.com/p/googletest 354 | [11]: https://flask-restless.readthedocs.org/en/latest/searchformat.html#queryformat 355 | -------------------------------------------------------------------------------- /tests/test_mapper.cpp: -------------------------------------------------------------------------------- 1 | // -------------------------------------------------------------------------------- 2 | // Includes 3 | // -------------------------------------------------------------------------------- 4 | #include 5 | #include 6 | 7 | using namespace std; 8 | using namespace restful_mapper; 9 | 10 | // -------------------------------------------------------------------------------- 11 | // Definitions 12 | // -------------------------------------------------------------------------------- 13 | class Item 14 | { 15 | public: 16 | Primary id; 17 | Field revision; 18 | Field task; 19 | BelongsTo parent; 20 | Foreign parent_id; 21 | 22 | static const std::string &class_name() 23 | { 24 | static std::string class_name = "Item"; 25 | return class_name; 26 | } 27 | 28 | void from_json(string values, const int &flags = 0, const bool &exists = false) 29 | { 30 | Mapper m(values, flags); 31 | 32 | m.get("id", id); 33 | m.get("revision", revision); 34 | m.get("task", task); 35 | m.get("parent", parent); 36 | m.get("parent_id", parent_id); 37 | } 38 | 39 | std::string to_json(const int &flags = 0, const std::string &parent_model = "") const 40 | { 41 | Mapper m(flags); 42 | m.set_current_model("Item"); 43 | m.set_parent_model(parent_model); 44 | 45 | m.set("id", id); 46 | m.set("revision", revision); 47 | m.set("task", task); 48 | m.set("parent", parent); 49 | m.set("parent_id", parent_id); 50 | 51 | return m.dump(); 52 | } 53 | 54 | bool is_dirty() const 55 | { 56 | return id.is_dirty() || revision.is_dirty() || task.is_dirty(); 57 | } 58 | }; 59 | 60 | TEST(MapperTest, ParseJson) 61 | { 62 | Field f_int; 63 | Field f_double; 64 | Field f_bool; 65 | Field f_string; 66 | Field f_time; 67 | Primary f_primary; 68 | 69 | Mapper m("{ \"priority\": 994, \"task\": \"Profit!!!\", \"completed_on\": \"2013-12-05T00:00:00\", \"time\": 0.001, \"completed\": false, \"id\": 3 }"); 70 | 71 | m.get("priority", f_int); 72 | ASSERT_EQ(994, int(f_int)); 73 | 74 | m.get("time", f_double); 75 | ASSERT_DOUBLE_EQ(0.001, double(f_double)); 76 | 77 | m.get("completed", f_bool); 78 | ASSERT_FALSE(f_bool); 79 | 80 | m.get("task", f_string); 81 | ASSERT_STREQ("Profit!!!", f_string.c_str()); 82 | 83 | m.get("completed_on", f_time); 84 | ASSERT_EQ(2013, f_time.local_year()); 85 | 86 | m.get("id", f_primary); 87 | ASSERT_EQ(3, int(f_primary)); 88 | 89 | ASSERT_FALSE(f_int.is_dirty()); 90 | ASSERT_FALSE(f_double.is_dirty()); 91 | ASSERT_FALSE(f_bool.is_dirty()); 92 | ASSERT_FALSE(f_string.is_dirty()); 93 | ASSERT_FALSE(f_time.is_dirty()); 94 | ASSERT_FALSE(f_primary.is_dirty()); 95 | } 96 | 97 | TEST(MapperTest, EmitJson) 98 | { 99 | Field f_int; 100 | Field f_double; 101 | Field f_bool; 102 | Field f_string; 103 | Field f_time; 104 | Primary f_primary; 105 | 106 | Mapper m; 107 | 108 | f_int = 654; 109 | f_double = 8.0; 110 | f_bool = true; 111 | f_string = "Stoned and starving"; 112 | f_time = "2021-12-31"; 113 | f_primary = 7; 114 | 115 | ASSERT_TRUE(f_int.is_dirty()); 116 | ASSERT_TRUE(f_double.is_dirty()); 117 | ASSERT_TRUE(f_bool.is_dirty()); 118 | ASSERT_TRUE(f_string.is_dirty()); 119 | ASSERT_TRUE(f_time.is_dirty()); 120 | ASSERT_TRUE(f_primary.is_dirty()); 121 | 122 | m.set("priority", f_int); 123 | m.set("time", f_double); 124 | m.set("completed", f_bool); 125 | m.set("task", f_string); 126 | m.set("completed_on", f_time); 127 | m.set("id", f_primary); 128 | 129 | ASSERT_FALSE(f_int.is_dirty()); 130 | ASSERT_FALSE(f_double.is_dirty()); 131 | ASSERT_FALSE(f_bool.is_dirty()); 132 | ASSERT_FALSE(f_string.is_dirty()); 133 | ASSERT_FALSE(f_time.is_dirty()); 134 | ASSERT_TRUE(f_primary.is_dirty()); 135 | 136 | ASSERT_STREQ("{\"priority\":654,\"time\":8.0,\"completed\":true,\"task\":\"Stoned and starving\",\"completed_on\":\"2021-12-31T00:00:00Z\"}", m.dump().c_str()); 137 | } 138 | 139 | TEST(MapperTest, IsRelation) 140 | { 141 | Field f_string; 142 | Primary f_primary; 143 | Primary f_primary2; 144 | 145 | Mapper m("{ \"task\": \"Profit!!!\", \"id\": 3 }", INCLUDE_PRIMARY_KEY); 146 | 147 | m.get("id", f_primary); 148 | m.get("task", f_string); 149 | 150 | ASSERT_EQ(3, int(f_primary)); 151 | ASSERT_STREQ("Profit!!!", string(f_string).c_str()); 152 | 153 | Mapper m2(INCLUDE_PRIMARY_KEY); 154 | 155 | f_primary2 = 10; 156 | f_string = "Cool"; 157 | 158 | m2.set("task", f_string); 159 | m2.set("id", f_primary2); 160 | 161 | ASSERT_STREQ("{\"task\":\"Cool\",\"id\":10}", m2.dump().c_str()); 162 | 163 | Mapper m3(INCLUDE_PRIMARY_KEY); 164 | 165 | f_string.touch(); 166 | m3.set("task", f_string); 167 | m3.set("id", f_primary); 168 | 169 | ASSERT_STREQ("{\"task\":\"Cool\",\"id\":3}", m3.dump().c_str()); 170 | 171 | Mapper m4(INCLUDE_PRIMARY_KEY); 172 | Primary f_primary3; 173 | 174 | f_string.touch(); 175 | m4.set("task", f_string); 176 | m4.set("id", f_primary3); 177 | 178 | ASSERT_STREQ("{\"task\":\"Cool\"}", m4.dump().c_str()); 179 | } 180 | 181 | TEST(MapperTest, IgnoreMissing) 182 | { 183 | Field f_int; 184 | Field f_double; 185 | Field f_bool; 186 | Field f_string; 187 | Field f_time; 188 | Primary f_primary; 189 | 190 | Mapper m("{ \"priority\": 994, \"completed_on\": \"2013-12-05T00:00:00\" }", IGNORE_MISSING_FIELDS); 191 | 192 | m.get("priority", f_int); 193 | ASSERT_EQ(994, int(f_int)); 194 | 195 | m.get("time", f_double); 196 | ASSERT_TRUE(f_double.is_null()); 197 | 198 | m.get("completed", f_bool); 199 | ASSERT_TRUE(f_bool.is_null()); 200 | 201 | m.get("task", f_string); 202 | ASSERT_TRUE(f_string.is_null()); 203 | 204 | m.get("completed_on", f_time); 205 | ASSERT_EQ(2013, f_time.local_year()); 206 | 207 | m.get("id", f_primary); 208 | ASSERT_TRUE(f_primary.is_null()); 209 | 210 | Mapper m2("{ \"priority\": 994, \"completed_on\": \"2013-12-05T00:00:00\" }"); 211 | m2.set_flags(IGNORE_MISSING_FIELDS); 212 | 213 | m2.get("priority", f_int); 214 | ASSERT_EQ(994, int(f_int)); 215 | 216 | m2.get("time", f_double); 217 | ASSERT_TRUE(f_double.is_null()); 218 | } 219 | 220 | TEST(MapperTest, MissingKey) 221 | { 222 | Field f_int; 223 | 224 | Mapper m("{ \"priority\": 994 }"); 225 | 226 | ASSERT_THROW(m.get("priority2", f_int), runtime_error); 227 | } 228 | 229 | TEST(MapperTest, InvalidType) 230 | { 231 | Field f_int; 232 | 233 | Mapper m("{ \"priority\": 994.354 }"); 234 | 235 | ASSERT_THROW(m.get("priority", f_int), runtime_error); 236 | 237 | try 238 | { 239 | m.get("priority", f_int); 240 | } 241 | catch (runtime_error &e) 242 | { 243 | ASSERT_STREQ("Expected JSON node \"priority\" to be INTEGER, found DOUBLE: 994.354", e.what()); 244 | } 245 | } 246 | 247 | TEST(MapperTest, InvalidJson) 248 | { 249 | ASSERT_THROW(Mapper m(""), runtime_error); 250 | ASSERT_THROW(Mapper m("xxx"), runtime_error); 251 | } 252 | 253 | TEST(MapperTest, GetJsonField) 254 | { 255 | Mapper m("{ \"priority\": [1, 2, 3] }"); 256 | 257 | ASSERT_STREQ("[1,2,3]", m.get("priority").c_str()); 258 | ASSERT_THROW(m.get("id"), runtime_error); 259 | 260 | Mapper m2("{ \"priority\": null }"); 261 | 262 | ASSERT_STREQ("null", m2.get("priority").c_str()); 263 | } 264 | 265 | TEST(MapperTest, SetJsonField) 266 | { 267 | Mapper m; 268 | 269 | m.set("child", "{ \"name\": \"Flaf\", \"id\": 2 }"); 270 | ASSERT_STREQ("{\"child\":{\"name\":\"Flaf\",\"id\":2}}", m.dump().c_str()); 271 | } 272 | 273 | TEST(MapperTest, NullValue) 274 | { 275 | Field f_string; 276 | Field f_int; 277 | Primary f_primary; 278 | 279 | Mapper m; 280 | 281 | f_string = "Stoned and starving"; 282 | f_primary.clear(); 283 | 284 | f_int.clear(); 285 | f_string.clear(); 286 | 287 | m.set("task", f_string); 288 | m.set("priority", f_int); 289 | m.set("id", f_primary); 290 | 291 | ASSERT_STREQ("{\"task\":null,\"priority\":null}", m.dump().c_str()); 292 | } 293 | 294 | TEST(MapperTest, SingleField) 295 | { 296 | Field f_string; 297 | Field f_int; 298 | Field f_bool; 299 | Field f_double; 300 | Field f_time; 301 | Primary f_primary; 302 | 303 | f_int = 6; 304 | f_double = 3.0; 305 | f_primary = 4; 306 | 307 | Mapper m(OUTPUT_SINGLE_FIELD); 308 | m.set_field_filter("priority"); 309 | 310 | m.set("task", f_string); 311 | m.set("priority", f_int); 312 | m.set("completed", f_bool); 313 | m.set("time", f_double); 314 | m.set("due", f_time); 315 | m.set("id", f_primary); 316 | 317 | ASSERT_STREQ("6", m.dump().c_str()); 318 | 319 | HasOne parent; 320 | parent.build(); 321 | parent->revision = 4; 322 | 323 | Mapper m2(OUTPUT_SINGLE_FIELD); 324 | m2.set_field_filter("parent"); 325 | 326 | m2.set("parent", parent); 327 | 328 | ASSERT_STREQ("{\"revision\":4}", m2.dump().c_str()); 329 | } 330 | 331 | TEST(MapperTest, SetForceDirty) 332 | { 333 | Field f_string; 334 | Field f_int; 335 | Field f_bool; 336 | Field f_double; 337 | Field f_time; 338 | Primary f_primary; 339 | 340 | Mapper m(IGNORE_DIRTY_FLAG); 341 | 342 | f_int.clear(); 343 | f_double = 3.0; 344 | f_primary = 4; 345 | 346 | m.set("task", f_string); 347 | m.set("priority", f_int); 348 | m.set("completed", f_bool); 349 | m.set("time", f_double); 350 | m.set("due", f_time); 351 | m.set("id", f_primary); 352 | 353 | ASSERT_STREQ("{\"task\":null,\"priority\":null,\"completed\":null,\"time\":3.0,\"due\":null}", m.dump().c_str()); 354 | } 355 | 356 | TEST(MapperTest, GetTouchFields) 357 | { 358 | Field f_string; 359 | Field f_int; 360 | Field f_bool; 361 | Field f_double; 362 | Primary f_primary; 363 | 364 | Mapper m("{\"task\":null,\"priority\":null,\"completed\":null,\"time\":3.0}", TOUCH_FIELDS | IGNORE_MISSING_FIELDS); 365 | 366 | f_int.clear(); 367 | f_double = 3.0; 368 | 369 | m.get("task", f_string); 370 | m.get("priority", f_int); 371 | m.get("completed", f_bool); 372 | m.get("time", f_double); 373 | m.get("id", f_primary); 374 | 375 | ASSERT_TRUE(f_string.is_null()); 376 | ASSERT_TRUE(f_int.is_null()); 377 | ASSERT_TRUE(f_bool.is_null()); 378 | ASSERT_FALSE(f_double.is_null()); 379 | ASSERT_TRUE(f_primary.is_null()); 380 | 381 | ASSERT_DOUBLE_EQ(3.0, double(f_double)); 382 | 383 | ASSERT_TRUE(f_int.is_dirty()); 384 | ASSERT_TRUE(f_double.is_dirty()); 385 | ASSERT_TRUE(f_bool.is_dirty()); 386 | ASSERT_TRUE(f_string.is_dirty()); 387 | ASSERT_TRUE(f_primary.is_dirty()); 388 | } 389 | 390 | TEST(MapperTest, HasOneNull) 391 | { 392 | Item item; 393 | HasOne child; 394 | ASSERT_FALSE(child.is_dirty()); 395 | 396 | Mapper m("{\"revision\":5,\"id\":3,\"task\":\"do something\",\"child\":null}"); 397 | 398 | m.get("id", item.id); 399 | m.get("revision", item.revision); 400 | m.get("task", item.task); 401 | m.get("child", child); 402 | 403 | ASSERT_TRUE(child.is_null()); 404 | ASSERT_FALSE(child.is_dirty()); 405 | 406 | child.build(); 407 | 408 | ASSERT_FALSE(child.is_null()); 409 | ASSERT_TRUE(child.is_dirty()); 410 | 411 | HasOne child2; 412 | ASSERT_FALSE(child2.is_dirty()); 413 | 414 | Mapper m2; 415 | m2.set("child", child2); 416 | ASSERT_STREQ("{}", m2.dump().c_str()); 417 | 418 | Mapper m3(IGNORE_DIRTY_FLAG); 419 | m3.set("child", child2); 420 | ASSERT_STREQ("{\"child\":null}", m3.dump().c_str()); 421 | } 422 | 423 | TEST(MapperTest, HasOneWithValue) 424 | { 425 | Item item; 426 | HasOne child; 427 | 428 | Mapper m("{\"revision\":5,\"id\":3,\"task\":\"do something\",\"child\":{\"revision\":8,\"id\":1,\"task\":\"do something first\",\"parent_id\":2}}"); 429 | 430 | m.get("id", item.id); 431 | m.get("revision", item.revision); 432 | m.get("task", item.task); 433 | m.get("child", child); 434 | 435 | ASSERT_FALSE(child.is_null()); 436 | ASSERT_FALSE(child.is_dirty()); 437 | 438 | ASSERT_EQ(1, child->id.get()); 439 | ASSERT_EQ(8, child->revision.get()); 440 | 441 | Mapper m2; 442 | m2.set("child", child); 443 | ASSERT_STREQ("{}", m2.dump().c_str()); 444 | 445 | Mapper m3(IGNORE_DIRTY_FLAG); 446 | m3.set("child", child); 447 | ASSERT_STREQ("{\"child\":{\"id\":1,\"revision\":8,\"task\":\"do something first\",\"parent\":null,\"parent_id\":2}}", m3.dump().c_str()); 448 | 449 | ASSERT_FALSE(child.is_dirty()); 450 | ASSERT_FALSE(child->parent.is_dirty()); 451 | 452 | child->revision = 9; 453 | 454 | ASSERT_TRUE(child.is_dirty()); 455 | 456 | Mapper m4; 457 | m4.set("child", child); 458 | ASSERT_STREQ("{\"child\":{\"id\":1,\"revision\":9}}", m4.dump().c_str()); 459 | } 460 | 461 | TEST(MapperTest, HasManyNull) 462 | { 463 | Item item; 464 | HasMany children; 465 | ASSERT_FALSE(children.is_dirty()); 466 | 467 | Mapper m("{\"revision\":5,\"id\":3,\"task\":\"do something\",\"children\":null}"); 468 | 469 | m.get("id", item.id); 470 | m.get("revision", item.revision); 471 | m.get("task", item.task); 472 | m.get("children", children); 473 | 474 | ASSERT_FALSE(children.is_dirty()); 475 | 476 | Item i = children.build(); 477 | 478 | ASSERT_TRUE(children.is_dirty()); 479 | 480 | Mapper m4; 481 | m4.set("children", children); 482 | ASSERT_STREQ("{\"children\":[{}]}", m4.dump().c_str()); 483 | } 484 | 485 | TEST(MapperTest, HasManyWithValue) 486 | { 487 | Item item; 488 | HasMany children; 489 | 490 | Mapper m("{\"revision\":5,\"id\":3,\"task\":\"do something\",\"children\":[{\"revision\":1,\"id\":1},{\"revision\":4,\"id\":7},{\"revision\":51,\"id\":3}]}", IGNORE_MISSING_FIELDS); 491 | 492 | m.get("id", item.id); 493 | m.get("revision", item.revision); 494 | m.get("task", item.task); 495 | m.get("children", children); 496 | 497 | ASSERT_FALSE(children.is_dirty()); 498 | ASSERT_EQ(3, children.size()); 499 | 500 | HasMany children2(children); 501 | 502 | ASSERT_FALSE(children2.is_dirty()); 503 | ASSERT_EQ(3, children2.size()); 504 | 505 | children[1].task = "Sometask"; 506 | 507 | ASSERT_TRUE(children.is_dirty()); 508 | 509 | Item i; 510 | children.push_back(i); 511 | 512 | ASSERT_TRUE(children.is_dirty()); 513 | 514 | Mapper m4; 515 | m4.set("children", children); 516 | ASSERT_STREQ("{\"children\":[{\"id\":1},{\"id\":7,\"task\":\"Sometask\"},{\"id\":3},{}]}", m4.dump().c_str()); 517 | } 518 | 519 | TEST(MapperTest, OmitParentKeys) 520 | { 521 | Item item; 522 | 523 | Mapper m(OMIT_PARENT_KEYS); 524 | m.set_parent_model("Item"); 525 | 526 | item.id = 2; 527 | item.task = "Play"; 528 | item.parent_id = 1; 529 | 530 | m.set("id", item.id); 531 | m.set("revision", item.revision); 532 | m.set("task", item.task); 533 | m.set("parent", item.parent); 534 | m.set("parent_id", item.parent_id); 535 | 536 | ASSERT_STREQ("{\"task\":\"Play\"}", m.dump().c_str()); 537 | } 538 | 539 | -------------------------------------------------------------------------------- /tests/test_model.cpp: -------------------------------------------------------------------------------- 1 | // -------------------------------------------------------------------------------- 2 | // Includes 3 | // -------------------------------------------------------------------------------- 4 | #include 5 | #include 6 | #include 7 | 8 | using namespace std; 9 | using namespace restful_mapper; 10 | 11 | // -------------------------------------------------------------------------------- 12 | // Declarations 13 | // -------------------------------------------------------------------------------- 14 | class Todo : public Model 15 | { 16 | public: 17 | Primary id; 18 | Field task; 19 | Field priority; 20 | Field time; 21 | Field completed; 22 | Field completed_on; 23 | 24 | virtual void map_set(Mapper &mapper) const 25 | { 26 | mapper.set("id", id); 27 | mapper.set("task", task); 28 | mapper.set("priority", priority); 29 | mapper.set("time", time); 30 | mapper.set("completed", completed); 31 | mapper.set("completed_on", completed_on); 32 | } 33 | 34 | virtual void map_get(const Mapper &mapper) 35 | { 36 | mapper.get("id", id); 37 | mapper.get("task", task); 38 | mapper.get("priority", priority); 39 | mapper.get("time", time); 40 | mapper.get("completed", completed); 41 | mapper.get("completed_on", completed_on); 42 | } 43 | 44 | virtual std::string endpoint() const 45 | { 46 | return "/todo"; 47 | } 48 | 49 | virtual const Primary &primary() const 50 | { 51 | return id; 52 | } 53 | }; 54 | 55 | class City; 56 | class Zipcode; 57 | class Citizen; 58 | class PhoneNumber; 59 | 60 | class Country : public Model 61 | { 62 | public: 63 | Primary id; 64 | Field name; 65 | HasMany cities; 66 | 67 | virtual void map_set(Mapper &mapper) const 68 | { 69 | mapper.set("id", id); 70 | mapper.set("name", name); 71 | mapper.set("cities", cities); 72 | } 73 | 74 | virtual void map_get(const Mapper &mapper) 75 | { 76 | mapper.get("id", id); 77 | mapper.get("name", name); 78 | mapper.get("cities", cities); 79 | } 80 | 81 | virtual std::string endpoint() const 82 | { 83 | return "/country"; 84 | } 85 | 86 | virtual const Primary &primary() const 87 | { 88 | return id; 89 | } 90 | }; 91 | 92 | class City : public Model 93 | { 94 | public: 95 | Primary id; 96 | Foreign country_id; 97 | Field name; 98 | BelongsTo country; 99 | HasOne zipcode; 100 | HasMany citizens; 101 | 102 | virtual void map_set(Mapper &mapper) const 103 | { 104 | mapper.set("id", id); 105 | mapper.set("country_id", country_id); 106 | mapper.set("name", name); 107 | mapper.set("country", country); 108 | mapper.set("zipcode", zipcode); 109 | mapper.set("citizens", citizens); 110 | } 111 | 112 | virtual void map_get(const Mapper &mapper) 113 | { 114 | mapper.get("id", id); 115 | mapper.get("country_id", country_id); 116 | mapper.get("name", name); 117 | mapper.get("country", country); 118 | mapper.get("zipcode", zipcode); 119 | mapper.get("citizens", citizens); 120 | } 121 | 122 | virtual std::string endpoint() const 123 | { 124 | return "/city"; 125 | } 126 | 127 | virtual const Primary &primary() const 128 | { 129 | return id; 130 | } 131 | }; 132 | 133 | class Zipcode : public Model 134 | { 135 | public: 136 | Primary id; 137 | Foreign city_id; 138 | Field code; 139 | BelongsTo city; 140 | 141 | virtual void map_set(Mapper &mapper) const 142 | { 143 | mapper.set("id", id); 144 | mapper.set("city_id", city_id); 145 | mapper.set("code", code); 146 | mapper.set("city", city); 147 | } 148 | 149 | virtual void map_get(const Mapper &mapper) 150 | { 151 | mapper.get("id", id); 152 | mapper.get("city_id", city_id); 153 | mapper.get("code", code); 154 | mapper.get("city", city); 155 | } 156 | 157 | virtual std::string endpoint() const 158 | { 159 | return "/zipcode"; 160 | } 161 | 162 | virtual const Primary &primary() const 163 | { 164 | return id; 165 | } 166 | }; 167 | 168 | class Citizen : public Model 169 | { 170 | public: 171 | Primary id; 172 | Foreign country_id; 173 | Foreign city_id; 174 | Field first_name; 175 | Field last_name; 176 | BelongsTo country; 177 | BelongsTo city; 178 | HasMany phone_numbers; 179 | 180 | virtual void map_set(Mapper &mapper) const 181 | { 182 | mapper.set("id", id); 183 | mapper.set("country_id", country_id); 184 | mapper.set("city_id", city_id); 185 | mapper.set("first_name", first_name); 186 | mapper.set("last_name", last_name); 187 | mapper.set("country", country); 188 | mapper.set("city", city); 189 | mapper.set("phone_numbers", phone_numbers); 190 | } 191 | 192 | virtual void map_get(const Mapper &mapper) 193 | { 194 | mapper.get("id", id); 195 | mapper.get("country_id", country_id); 196 | mapper.get("city_id", city_id); 197 | mapper.get("first_name", first_name); 198 | mapper.get("last_name", last_name); 199 | mapper.get("country", country); 200 | mapper.get("city", city); 201 | mapper.get("phone_numbers", phone_numbers); 202 | } 203 | 204 | virtual std::string endpoint() const 205 | { 206 | return "/citizen"; 207 | } 208 | 209 | virtual const Primary &primary() const 210 | { 211 | return id; 212 | } 213 | }; 214 | 215 | class PhoneNumber : public Model 216 | { 217 | public: 218 | Primary id; 219 | Foreign citizen_id; 220 | Field number; 221 | BelongsTo citizen; 222 | 223 | virtual void map_set(Mapper &mapper) const 224 | { 225 | mapper.set("id", id); 226 | mapper.set("citizen_id", citizen_id); 227 | mapper.set("number", number); 228 | mapper.set("citizen", citizen); 229 | } 230 | 231 | virtual void map_get(const Mapper &mapper) 232 | { 233 | mapper.get("id", id); 234 | mapper.get("citizen_id", citizen_id); 235 | mapper.get("number", number); 236 | mapper.get("citizen", citizen); 237 | } 238 | 239 | virtual std::string endpoint() const 240 | { 241 | return "/phone_number"; 242 | } 243 | 244 | virtual const Primary &primary() const 245 | { 246 | return id; 247 | } 248 | }; 249 | 250 | // -------------------------------------------------------------------------------- 251 | // Definitions 252 | // -------------------------------------------------------------------------------- 253 | class ModelTest : public ::testing::Test 254 | { 255 | protected: 256 | virtual void SetUp() 257 | { 258 | Api::set_url("http://localhost:5000/api"); 259 | Api::set_username("admin"); 260 | Api::set_password("test"); 261 | Api::set_proxy(""); 262 | Api::get("/reload"); 263 | 264 | putenv("TZ=EST5"); 265 | tzset(); 266 | } 267 | }; 268 | 269 | TEST_F(ModelTest, ClassName) 270 | { 271 | ASSERT_STREQ("Todo", Todo::class_name().c_str()); 272 | ASSERT_STREQ("Zipcode", Zipcode::class_name().c_str()); 273 | } 274 | 275 | TEST_F(ModelTest, GetItem) 276 | { 277 | Todo t = Todo::find(2); 278 | 279 | ASSERT_EQ(2, int(t.id)); 280 | ASSERT_STREQ("???", string(t.task).c_str()); 281 | ASSERT_EQ(2, int(t.priority)); 282 | ASSERT_DOUBLE_EQ(4.54, double(t.time)); 283 | ASSERT_FALSE(bool(t.completed)); 284 | ASSERT_EQ(2013, t.completed_on.utc_year()); 285 | ASSERT_EQ(3, t.completed_on.utc_month()); 286 | ASSERT_EQ(13, t.completed_on.utc_day()); 287 | ASSERT_EQ(11, t.completed_on.utc_hour()); 288 | ASSERT_EQ(53, t.completed_on.utc_minute()); 289 | ASSERT_EQ(21, t.completed_on.utc_second()); 290 | ASSERT_STREQ("2013-03-13T11:53:21Z", string(t.completed_on).c_str()); 291 | } 292 | 293 | TEST_F(ModelTest, GetNonExistingItem) 294 | { 295 | try 296 | { 297 | Todo t = Todo::find(10); 298 | } 299 | catch (ApiError &e) 300 | { 301 | ASSERT_EQ(404, e.code()); 302 | } 303 | } 304 | 305 | TEST_F(ModelTest, CreateItem) 306 | { 307 | Todo t; 308 | t.id = 8345; // Should be ignored 309 | t.task = "Newly created..."; 310 | t.save(); 311 | 312 | Todo t2 = Todo::find(4); 313 | ASSERT_STREQ("Newly created...", string(t2.task).c_str()); 314 | } 315 | 316 | TEST_F(ModelTest, UpdateItem) 317 | { 318 | Todo t = Todo::find(2); 319 | t.task = "It works!"; 320 | t.save(); 321 | 322 | Todo t2 = Todo::find(2); 323 | ASSERT_STREQ("It works!", string(t2.task).c_str()); 324 | 325 | time_t now = utc_time(); 326 | 327 | t2.completed_on = now; 328 | 329 | ASSERT_TRUE(t2.is_dirty()); 330 | 331 | t2.save(); 332 | 333 | Todo t3 = Todo::find(2); 334 | ASSERT_EQ(int(now), int(t2.completed_on)); 335 | 336 | t = Todo::find(2); 337 | 338 | t.task = "Do something else..."; 339 | 340 | Todo t4(t); 341 | 342 | ASSERT_TRUE(t4.task.is_dirty()); 343 | 344 | t4.save(); 345 | 346 | ASSERT_FALSE(t4.id.is_dirty()); 347 | ASSERT_FALSE(t4.task.is_dirty()); 348 | ASSERT_FALSE(t4.priority.is_dirty()); 349 | ASSERT_FALSE(t4.time.is_dirty()); 350 | ASSERT_FALSE(t4.completed.is_dirty()); 351 | } 352 | 353 | TEST_F(ModelTest, ReadField) 354 | { 355 | Todo t = Todo::find(2); 356 | 357 | ASSERT_STREQ("\"???\"", t.read_field("task").c_str()); 358 | 359 | City c = City::find(2); 360 | 361 | ASSERT_STREQ("{\"id\":2,\"city_id\":2,\"code\":\"8000\",\"city\":null}", c.read_field("zipcode").c_str()); 362 | } 363 | 364 | TEST_F(ModelTest, CloneItem) 365 | { 366 | Todo t = Todo::find(2); 367 | 368 | Todo t2(t); 369 | 370 | ASSERT_EQ(2, int(t2.id)); 371 | ASSERT_STREQ("???", string(t2.task).c_str()); 372 | 373 | Todo t3 = t.clone(); 374 | 375 | ASSERT_FALSE(t3.exists()); 376 | ASSERT_TRUE(t3.id.is_null()); 377 | ASSERT_STREQ("???", string(t3.task).c_str()); 378 | ASSERT_EQ(2, int(t3.priority)); 379 | ASSERT_DOUBLE_EQ(4.54, double(t3.time)); 380 | ASSERT_FALSE(bool(t3.completed)); 381 | ASSERT_EQ(2013, t3.completed_on.utc_year()); 382 | ASSERT_EQ(3, t3.completed_on.utc_month()); 383 | ASSERT_EQ(13, t3.completed_on.utc_day()); 384 | ASSERT_EQ(11, t3.completed_on.utc_hour()); 385 | ASSERT_EQ(53, t3.completed_on.utc_minute()); 386 | ASSERT_EQ(21, t3.completed_on.utc_second()); 387 | ASSERT_STREQ("2013-03-13T11:53:21Z", string(t3.completed_on).c_str()); 388 | ASSERT_TRUE(t3.priority.is_dirty()); 389 | ASSERT_TRUE(t3.completed_on.is_dirty()); 390 | 391 | t3.save(); 392 | 393 | ASSERT_EQ(4, int(t3.id)); 394 | 395 | City c = City::find(1).clone(); 396 | 397 | ASSERT_TRUE(c.name.is_dirty()); 398 | ASSERT_TRUE(c.country_id.is_dirty()); 399 | ASSERT_TRUE(c.country.is_null()); 400 | ASSERT_TRUE(c.citizens.empty()); 401 | 402 | c.save(); 403 | 404 | City c3 = City::find(4); 405 | 406 | ASSERT_EQ(0, c3.citizens.size()); 407 | ASSERT_STREQ("Copenhagen", c3.name.c_str()); 408 | ASSERT_STREQ("Denmark", c3.country->name.c_str()); 409 | } 410 | 411 | TEST_F(ModelTest, CloneCollection) 412 | { 413 | City c = City::find(1); 414 | 415 | Citizen::Collection new_citizens = c.citizens.clone(); 416 | 417 | ASSERT_EQ(2, new_citizens.size()); 418 | ASSERT_FALSE(new_citizens[0].exists()); 419 | ASSERT_FALSE(new_citizens[1].exists()); 420 | ASSERT_STREQ("Jane", new_citizens[1].first_name.c_str()); 421 | ASSERT_EQ(1, new_citizens[0].city_id.get()); 422 | ASSERT_TRUE(new_citizens[0].id.is_null()); 423 | } 424 | 425 | TEST_F(ModelTest, EmplaceClone) 426 | { 427 | Todo t = Todo::find(2); 428 | 429 | ASSERT_FALSE(t.priority.is_dirty()); 430 | ASSERT_FALSE(t.completed_on.is_dirty()); 431 | 432 | t.emplace_clone(); 433 | 434 | ASSERT_TRUE(t.priority.is_dirty()); 435 | ASSERT_TRUE(t.completed_on.is_dirty()); 436 | 437 | ASSERT_FALSE(t.exists()); 438 | ASSERT_TRUE(t.id.is_null()); 439 | ASSERT_STREQ("???", string(t.task).c_str()); 440 | ASSERT_EQ(2, int(t.priority)); 441 | ASSERT_DOUBLE_EQ(4.54, double(t.time)); 442 | ASSERT_FALSE(bool(t.completed)); 443 | ASSERT_EQ(2013, t.completed_on.utc_year()); 444 | ASSERT_EQ(3, t.completed_on.utc_month()); 445 | ASSERT_EQ(13, t.completed_on.utc_day()); 446 | ASSERT_EQ(11, t.completed_on.utc_hour()); 447 | ASSERT_EQ(53, t.completed_on.utc_minute()); 448 | ASSERT_EQ(21, t.completed_on.utc_second()); 449 | ASSERT_STREQ("2013-03-13T11:53:21Z", string(t.completed_on).c_str()); 450 | 451 | t.save(); 452 | 453 | ASSERT_EQ(4, int(t.id)); 454 | 455 | City c = City::find(1); 456 | c.emplace_clone(); 457 | 458 | ASSERT_FALSE(c.citizens.empty()); 459 | ASSERT_EQ(2, c.citizens.size()); 460 | ASSERT_STREQ("Jane", c.citizens[1].first_name.c_str()); 461 | ASSERT_EQ(1, c.citizens[0].city_id.get()); 462 | ASSERT_EQ(1, c.citizens[0].id.get()); 463 | ASSERT_TRUE(c.citizens[1].exists()); 464 | } 465 | 466 | TEST_F(ModelTest, DeleteItem) 467 | { 468 | Todo t = Todo::find(2); 469 | t.destroy(); 470 | 471 | ASSERT_THROW(Todo::find(2), ApiError); 472 | 473 | ASSERT_TRUE(t.id.is_null()); 474 | ASSERT_STREQ("???", string(t.task).c_str()); 475 | ASSERT_EQ(2, int(t.priority)); 476 | ASSERT_DOUBLE_EQ(4.54, double(t.time)); 477 | ASSERT_FALSE(bool(t.completed)); 478 | 479 | t.time.clear(); 480 | 481 | ASSERT_THROW(t.id = 3, runtime_error); 482 | 483 | ASSERT_TRUE(t.id.is_dirty()); 484 | ASSERT_TRUE(t.task.is_dirty()); 485 | ASSERT_TRUE(t.priority.is_dirty()); 486 | ASSERT_TRUE(t.time.is_dirty()); 487 | ASSERT_TRUE(t.completed.is_dirty()); 488 | 489 | t.save(); 490 | 491 | ASSERT_EQ(4, int(t.id)); 492 | ASSERT_STREQ("???", string(t.task).c_str()); 493 | ASSERT_EQ(2, int(t.priority)); 494 | ASSERT_TRUE(t.time.is_null()); 495 | ASSERT_FALSE(bool(t.completed)); 496 | 497 | ASSERT_FALSE(t.id.is_dirty()); 498 | ASSERT_FALSE(t.task.is_dirty()); 499 | ASSERT_FALSE(t.priority.is_dirty()); 500 | ASSERT_FALSE(t.time.is_dirty()); 501 | ASSERT_FALSE(t.completed.is_dirty()); 502 | } 503 | 504 | TEST_F(ModelTest, GetCollection) 505 | { 506 | Todo::Collection todos = Todo::find_all(); 507 | Todo::Collection todos_copy; 508 | 509 | ASSERT_EQ(3, todos.size()); 510 | 511 | ASSERT_STREQ("Build an API", string(todos[0].task).c_str()); 512 | ASSERT_STREQ("???", string(todos[1].task).c_str()); 513 | ASSERT_STREQ("Profit!!!", string(todos[2].task).c_str()); 514 | 515 | todos_copy = todos; 516 | 517 | ASSERT_EQ(3, todos_copy.size()); 518 | } 519 | 520 | TEST_F(ModelTest, CollectionFind) 521 | { 522 | Todo::Collection todos = Todo::find_all(); 523 | ASSERT_EQ(3, todos.size()); 524 | 525 | Todo t = todos.find(2); 526 | ASSERT_STREQ("???", t.task.c_str()); 527 | 528 | t = todos.find(3); 529 | ASSERT_STREQ("Profit!!!", t.task.c_str()); 530 | 531 | todos.find(3).task = "What?"; 532 | t = todos.find(3); 533 | ASSERT_STREQ("What?", t.task.c_str()); 534 | 535 | ASSERT_THROW(todos.find(4), out_of_range); 536 | 537 | Todo::Collection found_todos = todos.find("completed", false); 538 | 539 | ASSERT_EQ(2, found_todos.size()); 540 | 541 | Todo t2 = todos.find_first("completed", false); 542 | ASSERT_STREQ("???", t2.task.c_str()); 543 | 544 | ASSERT_THROW(todos.find_first("priority", 5), out_of_range); 545 | 546 | ASSERT_TRUE(todos.contains("completed", false)); 547 | ASSERT_FALSE(todos.contains("priority", 5)); 548 | ASSERT_TRUE(todos.contains(3)); 549 | ASSERT_FALSE(todos.contains(5)); 550 | } 551 | 552 | TEST_F(ModelTest, GetHasOne) 553 | { 554 | City c = City::find(2); 555 | 556 | ASSERT_EQ(2, c.zipcode->city_id.get()); 557 | ASSERT_EQ(2, c.zipcode->id.get()); 558 | ASSERT_STREQ("8000", c.zipcode->code.c_str()); 559 | 560 | City c2 = City::find(3); 561 | ASSERT_TRUE(c2.zipcode.is_null()); 562 | } 563 | 564 | TEST_F(ModelTest, SaveHasOne) 565 | { 566 | City c = City::find(2); 567 | c.zipcode->code = "3453"; 568 | 569 | ASSERT_TRUE(c.is_dirty()); 570 | ASSERT_TRUE(c.zipcode.is_dirty()); 571 | ASSERT_FALSE(c.zipcode->city.is_dirty()); 572 | ASSERT_FALSE(c.citizens.is_dirty()); 573 | 574 | c.save(); 575 | 576 | City c2 = City::find(2); 577 | ASSERT_STREQ("3453", c.zipcode->code.c_str()); 578 | } 579 | 580 | TEST_F(ModelTest, GetHasMany) 581 | { 582 | City c = City::find(1); 583 | 584 | ASSERT_FALSE(c.citizens.empty()); 585 | ASSERT_EQ(2, c.citizens.size()); 586 | ASSERT_STREQ("Jane", c.citizens[1].first_name.c_str()); 587 | ASSERT_EQ(1, c.citizens[0].city_id.get()); 588 | ASSERT_EQ(1, c.citizens[0].id.get()); 589 | ASSERT_TRUE(c.citizens[1].exists()); 590 | 591 | PhoneNumber ph = PhoneNumber::find(2); 592 | ASSERT_EQ(5678, ph.number.get()); 593 | ASSERT_STREQ("John", ph.citizen->first_name.c_str()); 594 | 595 | Country ct = Country::find(3); 596 | ASSERT_STREQ("Norway", ct.name.c_str()); 597 | ASSERT_TRUE(ct.cities.empty()); 598 | } 599 | 600 | TEST_F(ModelTest, SaveHasMany) 601 | { 602 | Country c = Country::find(1); 603 | 604 | c.cities[0].name = "Gothenburg"; 605 | 606 | c.cities.build(); 607 | c.cities[2].name = "Detroit"; 608 | c.cities[2].country_id = 200; 609 | 610 | c.save(); 611 | 612 | Country c2 = Country::find(1); 613 | 614 | ASSERT_EQ(3, c2.cities.size()); 615 | ASSERT_STREQ("Gothenburg", c2.cities[0].name.c_str()); 616 | ASSERT_STREQ("Detroit", c2.cities[2].name.c_str()); 617 | ASSERT_EQ(1, c2.cities[2].country_id); 618 | 619 | ASSERT_TRUE(c.id.get() == c2.id.get()); 620 | ASSERT_TRUE(c.name.get() == c2.name.get()); 621 | } 622 | 623 | TEST_F(ModelTest, QuerySingle) 624 | { 625 | Query q; 626 | q("task").like("%?%"); 627 | 628 | Todo todo = Todo::find(q); 629 | 630 | ASSERT_STREQ("???", todo.task.c_str()); 631 | 632 | q.clear(); 633 | q("time").gt("1.45"); 634 | 635 | try 636 | { 637 | Todo::find(q); 638 | } 639 | catch (BadRequestError &e) 640 | { 641 | ASSERT_STREQ("Multiple results found", e.what()); 642 | } 643 | } 644 | 645 | TEST_F(ModelTest, QueryMany) 646 | { 647 | Query q; 648 | q("time").gt("1.45"); 649 | q.order_by_asc(q.field("priority")); 650 | 651 | Todo::Collection todos = Todo::find_all(q); 652 | 653 | ASSERT_EQ(2, todos.size()); 654 | ASSERT_STREQ("???", todos[0].task.c_str()); 655 | ASSERT_STREQ("Build an API", todos[1].task.c_str()); 656 | 657 | q.clear(); 658 | q("time").gt("100000"); 659 | 660 | Todo::Collection todos2 = Todo::find_all(q); 661 | 662 | ASSERT_TRUE(todos2.empty()); 663 | } 664 | 665 | TEST_F(ModelTest, IsDirty) 666 | { 667 | Todo t; 668 | 669 | ASSERT_FALSE(t.is_dirty()); 670 | 671 | t.task = "Some task..."; 672 | 673 | ASSERT_TRUE(t.is_dirty()); 674 | } 675 | 676 | TEST_F(ModelTest, FailedAuthentication) 677 | { 678 | Api::set_username("admin2"); 679 | 680 | ASSERT_THROW(Todo::find(1), AuthenticationError); 681 | } 682 | 683 | TEST_F(ModelTest, FailedValidation) 684 | { 685 | Zipcode z; 686 | z.code = "42"; 687 | 688 | try 689 | { 690 | z.save(); 691 | } 692 | catch (ValidationError &e) 693 | { 694 | ASSERT_STREQ("Code must have 4 digits", e.what()); 695 | } 696 | } 697 | 698 | TEST_F(ModelTest, Encoding) 699 | { 700 | local_charset = "latin1"; 701 | 702 | Todo t; 703 | t.task = "Strange characters \xF8 here"; 704 | t.save(); 705 | 706 | Todo t2 = Todo::find(4); 707 | ASSERT_STREQ("Strange characters \xF8 here", string(t2.task).c_str()); 708 | } 709 | 710 | TEST_F(ModelTest, ReloadOneRelated) 711 | { 712 | Citizen c = Citizen::find(1); 713 | 714 | ASSERT_TRUE(c.city->zipcode.is_null()); 715 | 716 | c.city.clear(); 717 | ASSERT_TRUE(c.city.is_dirty()); 718 | 719 | c.reload_one("city"); 720 | 721 | ASSERT_FALSE(c.city->zipcode.is_null()); 722 | ASSERT_FALSE(c.city.is_dirty()); 723 | } 724 | 725 | TEST_F(ModelTest, ReloadManyRelated) 726 | { 727 | Country c = Country::find(1); 728 | 729 | ASSERT_EQ(1, c.id.get()); 730 | ASSERT_STREQ("Denmark", c.name.c_str()); 731 | ASSERT_TRUE(c.cities[0].zipcode.is_null()); 732 | 733 | c.cities.clear(); 734 | ASSERT_TRUE(c.cities.is_dirty()); 735 | 736 | c.reload_many("cities"); 737 | 738 | ASSERT_FALSE(c.cities[0].zipcode.is_null()); 739 | ASSERT_FALSE(c.cities.is_dirty()); 740 | 741 | ASSERT_EQ(1, c.id.get()); 742 | ASSERT_STREQ("Denmark", c.name.c_str()); 743 | ASSERT_FALSE(c.id.is_null()); 744 | ASSERT_FALSE(c.id.is_dirty()); 745 | ASSERT_FALSE(c.name.is_null()); 746 | ASSERT_FALSE(c.name.is_dirty()); 747 | } 748 | 749 | TEST_F(ModelTest, Comparison) 750 | { 751 | Country c1_1 = Country::find(1); 752 | Country c1_2 = Country::find(1); 753 | 754 | Country c2_1 = Country::find(2); 755 | 756 | ASSERT_TRUE(c1_1 == c1_2); 757 | ASSERT_FALSE(c1_1 == c2_1); 758 | ASSERT_TRUE(c1_2 != c2_1); 759 | } 760 | 761 | TEST_F(ModelTest, OmitParentKeys) 762 | { 763 | Country c = Country::find(1); 764 | c.reload_many("cities"); 765 | 766 | c.cities[0].name = "Gothenburg"; 767 | 768 | c.cities[1].name = "Detroit"; 769 | c.cities[1].country_id = 200; 770 | c.cities[1].country->name = "US"; 771 | 772 | c.cities[0].citizens[1].first_name = "Rick"; 773 | c.cities[0].citizens[1].country_id = 3; 774 | c.cities[0].citizens[1].city_id = 950; 775 | 776 | ASSERT_STREQ("{\"cities\":[{\"id\":1,\"name\":\"Gothenburg\",\"citizens\":[{\"id\":1},{\"id\":2,\"country_id\":3,\"first_name\":\"Rick\"}]},{\"id\":2,\"name\":\"Detroit\"}]}", c.to_json().c_str()); 777 | 778 | Country c2 = c.clone(); 779 | c2.cities = c.cities.clone(); 780 | 781 | ASSERT_STREQ("{\"name\":\"Denmark\",\"cities\":[{\"name\":\"Gothenburg\"},{\"name\":\"Detroit\"}]}", c2.to_json().c_str()); 782 | } 783 | 784 | -------------------------------------------------------------------------------- /src/json.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | extern "C" { 7 | #include 8 | #include 9 | } 10 | 11 | using namespace std; 12 | using namespace restful_mapper; 13 | 14 | // Helper macros 15 | #define JSON_GEN_HANDLE static_cast(json_gen_ptr_) 16 | #define JSON_TREE_HANDLE static_cast(json_tree_ptr_) 17 | #define YAJL_IS_BOOLEAN(v) (((v) != NULL) && ((v)->type == yajl_t_true || (v)->type == yajl_t_false)) 18 | #define YAJL_GET_BOOLEAN(v) ((v)->type == yajl_t_true) 19 | 20 | // yajl helpers 21 | void yajl_gen_error(const yajl_gen_status &status) 22 | { 23 | switch (status) 24 | { 25 | case yajl_gen_keys_must_be_strings: 26 | throw runtime_error("A non-string JSON key was passed"); 27 | 28 | case yajl_max_depth_exceeded: 29 | throw runtime_error("YAJL's maximum generation depth was exceeded"); 30 | 31 | case yajl_gen_in_error_state: 32 | throw runtime_error("A JSON generator function was called while in an error state"); 33 | 34 | case yajl_gen_generation_complete: 35 | throw runtime_error("A complete JSON document has been generated"); 36 | 37 | case yajl_gen_invalid_number: 38 | throw runtime_error("An invalid floating point value was passed to the JSON generator (infinity or NaN)"); 39 | 40 | case yajl_gen_no_buf: 41 | throw runtime_error("A print callback was passed in, so there is no internal buffer to get from"); 42 | 43 | case yajl_gen_invalid_string: 44 | throw runtime_error("An invalid UTF-8 string was passed to the JSON generator"); 45 | } 46 | } 47 | 48 | void yajl_wrong_type(const string &key, yajl_val value, const string &expected_type) 49 | { 50 | ostringstream s; 51 | 52 | s << "Expected JSON node \"" << key << "\" to be " << expected_type << ", found"; 53 | 54 | if (YAJL_IS_STRING(value)) s << " STRING: " << YAJL_GET_STRING(value); 55 | else if (YAJL_IS_INTEGER(value)) s << " INTEGER: " << YAJL_GET_INTEGER(value); 56 | else if (YAJL_IS_DOUBLE(value)) s << " DOUBLE: " << YAJL_GET_DOUBLE(value); 57 | else if (YAJL_IS_OBJECT(value)) s << " OBJECT"; 58 | else if (YAJL_IS_ARRAY(value)) s << " ARRAY"; 59 | else if (YAJL_IS_BOOLEAN(value)) s << " BOOLEAN: " << (YAJL_GET_BOOLEAN(value) ? "true" : "false"); 60 | 61 | throw runtime_error(s.str()); 62 | } 63 | 64 | void Json::not_found(const string &name) 65 | { 66 | ostringstream s; 67 | s << "JSON node \"" << name << "\" not found"; 68 | throw runtime_error(s.str()); 69 | } 70 | 71 | Json::Emitter::Emitter() 72 | { 73 | json_gen_ptr_ = NULL; 74 | reset(); 75 | } 76 | 77 | Json::Emitter::~Emitter() 78 | { 79 | if (json_gen_ptr_) 80 | { 81 | yajl_gen_free(JSON_GEN_HANDLE); 82 | json_gen_ptr_ = NULL; 83 | } 84 | } 85 | 86 | void Json::Emitter::reset() 87 | { 88 | output_.clear(); 89 | 90 | if (json_gen_ptr_) 91 | { 92 | yajl_gen_free(JSON_GEN_HANDLE); 93 | } 94 | 95 | // Allocate JSON_GEN_HANDLE 96 | json_gen_ptr_ = static_cast(yajl_gen_alloc(NULL)); 97 | yajl_gen_config(JSON_GEN_HANDLE, yajl_gen_validate_utf8, 1); 98 | } 99 | 100 | const string &Json::Emitter::dump() const 101 | { 102 | if (output_.empty()) 103 | { 104 | const unsigned char *buf; 105 | size_t len; 106 | 107 | yajl_gen_error(yajl_gen_get_buf(JSON_GEN_HANDLE, &buf, &len)); 108 | 109 | output_ = string(reinterpret_cast(buf), len); 110 | } 111 | 112 | return output_; 113 | } 114 | 115 | void Json::Emitter::emit(const int &value) 116 | { 117 | yajl_gen_error(yajl_gen_integer(JSON_GEN_HANDLE, value)); 118 | } 119 | 120 | void Json::Emitter::emit(const long long &value) 121 | { 122 | yajl_gen_error(yajl_gen_integer(JSON_GEN_HANDLE, value)); 123 | } 124 | 125 | void Json::Emitter::emit(const double &value) 126 | { 127 | yajl_gen_error(yajl_gen_double(JSON_GEN_HANDLE, value)); 128 | } 129 | 130 | void Json::Emitter::emit(const bool &value) 131 | { 132 | yajl_gen_error(yajl_gen_bool(JSON_GEN_HANDLE, value)); 133 | } 134 | 135 | void Json::Emitter::emit(const string &value) 136 | { 137 | string converted = local_to_utf8(value); 138 | yajl_gen_error(yajl_gen_string(JSON_GEN_HANDLE, reinterpret_cast(converted.c_str()), converted.size())); 139 | } 140 | 141 | void Json::Emitter::emit(const char *value) 142 | { 143 | emit(string(value)); 144 | } 145 | 146 | void Json::Emitter::emit(const vector &value) 147 | { 148 | emit_array_open(); 149 | 150 | vector::const_iterator i, i_end = value.end(); 151 | for (i = value.begin(); i != i_end; ++i) 152 | { 153 | emit(*i); 154 | } 155 | 156 | emit_array_close(); 157 | } 158 | 159 | void Json::Emitter::emit(const vector &value) 160 | { 161 | emit_array_open(); 162 | 163 | vector::const_iterator i, i_end = value.end(); 164 | for (i = value.begin(); i != i_end; ++i) 165 | { 166 | emit(*i); 167 | } 168 | 169 | emit_array_close(); 170 | } 171 | 172 | void Json::Emitter::emit(const vector &value) 173 | { 174 | emit_array_open(); 175 | 176 | vector::const_iterator i, i_end = value.end(); 177 | for (i = value.begin(); i != i_end; ++i) 178 | { 179 | emit(*i); 180 | } 181 | 182 | emit_array_close(); 183 | } 184 | 185 | void Json::Emitter::emit(const vector &value) 186 | { 187 | emit_array_open(); 188 | 189 | vector::const_iterator i, i_end = value.end(); 190 | for (i = value.begin(); i != i_end; ++i) 191 | { 192 | emit(*i); 193 | } 194 | 195 | emit_array_close(); 196 | } 197 | 198 | void Json::Emitter::emit(const vector &value) 199 | { 200 | emit_array_open(); 201 | 202 | vector::const_iterator i, i_end = value.end(); 203 | for (i = value.begin(); i != i_end; ++i) 204 | { 205 | emit(*i); 206 | } 207 | 208 | emit_array_close(); 209 | } 210 | 211 | void Json::Emitter::emit(const map &value) 212 | { 213 | emit_map_open(); 214 | 215 | map::const_iterator i, i_end = value.end(); 216 | for (i = value.begin(); i != i_end; ++i) 217 | { 218 | emit(i->first, i->second); 219 | } 220 | 221 | emit_map_close(); 222 | } 223 | 224 | void Json::Emitter::emit(const map &value) 225 | { 226 | emit_map_open(); 227 | 228 | map::const_iterator i, i_end = value.end(); 229 | for (i = value.begin(); i != i_end; ++i) 230 | { 231 | emit(i->first, i->second); 232 | } 233 | 234 | emit_map_close(); 235 | } 236 | 237 | void Json::Emitter::emit(const map &value) 238 | { 239 | emit_map_open(); 240 | 241 | map::const_iterator i, i_end = value.end(); 242 | for (i = value.begin(); i != i_end; ++i) 243 | { 244 | emit(i->first, i->second); 245 | } 246 | 247 | emit_map_close(); 248 | } 249 | 250 | void Json::Emitter::emit(const map &value) 251 | { 252 | emit_map_open(); 253 | 254 | map::const_iterator i, i_end = value.end(); 255 | for (i = value.begin(); i != i_end; ++i) 256 | { 257 | emit(i->first, i->second); 258 | } 259 | 260 | emit_map_close(); 261 | } 262 | 263 | void Json::Emitter::emit(const map &value) 264 | { 265 | emit_map_open(); 266 | 267 | map::const_iterator i, i_end = value.end(); 268 | for (i = value.begin(); i != i_end; ++i) 269 | { 270 | emit(i->first, i->second); 271 | } 272 | 273 | emit_map_close(); 274 | } 275 | 276 | void Json::Emitter::emit(const string &key, const int &value) 277 | { 278 | emit(key); 279 | emit(value); 280 | } 281 | 282 | void Json::Emitter::emit(const string &key, const long long &value) 283 | { 284 | emit(key); 285 | emit(value); 286 | } 287 | 288 | void Json::Emitter::emit(const string &key, const double &value) 289 | { 290 | emit(key); 291 | emit(value); 292 | } 293 | 294 | void Json::Emitter::emit(const string &key, const bool &value) 295 | { 296 | emit(key); 297 | emit(value); 298 | } 299 | 300 | void Json::Emitter::emit(const string &key, const string &value) 301 | { 302 | emit(key); 303 | emit(value); 304 | } 305 | 306 | void Json::Emitter::emit(const string &key, const char *value) 307 | { 308 | emit(key); 309 | emit(value); 310 | } 311 | 312 | void Json::Emitter::emit(const string &key, const vector &value) 313 | { 314 | emit(key); 315 | emit(value); 316 | } 317 | 318 | void Json::Emitter::emit(const string &key, const vector &value) 319 | { 320 | emit(key); 321 | emit(value); 322 | } 323 | 324 | void Json::Emitter::emit(const string &key, const vector &value) 325 | { 326 | emit(key); 327 | emit(value); 328 | } 329 | 330 | void Json::Emitter::emit(const string &key, const vector &value) 331 | { 332 | emit(key); 333 | emit(value); 334 | } 335 | 336 | void Json::Emitter::emit(const string &key, const vector &value) 337 | { 338 | emit(key); 339 | emit(value); 340 | } 341 | 342 | void Json::Emitter::emit(const string &key, const map &value) 343 | { 344 | emit(key); 345 | emit(value); 346 | } 347 | 348 | void Json::Emitter::emit(const string &key, const map &value) 349 | { 350 | emit(key); 351 | emit(value); 352 | } 353 | 354 | void Json::Emitter::emit(const string &key, const map &value) 355 | { 356 | emit(key); 357 | emit(value); 358 | } 359 | 360 | void Json::Emitter::emit(const string &key, const map &value) 361 | { 362 | emit(key); 363 | emit(value); 364 | } 365 | 366 | void Json::Emitter::emit(const string &key, const map &value) 367 | { 368 | emit(key); 369 | emit(value); 370 | } 371 | 372 | void Json::Emitter::emit_tree(void *json_tree) 373 | { 374 | yajl_val value = static_cast(json_tree); 375 | 376 | if (YAJL_IS_STRING(value)) 377 | { 378 | yajl_gen_error(yajl_gen_string(JSON_GEN_HANDLE, reinterpret_cast(YAJL_GET_STRING(value)), strlen(YAJL_GET_STRING(value)))); 379 | } 380 | else if (YAJL_IS_INTEGER(value)) 381 | { 382 | emit(YAJL_GET_INTEGER(value)); 383 | } 384 | else if (YAJL_IS_DOUBLE(value)) 385 | { 386 | emit(YAJL_GET_DOUBLE(value)); 387 | } 388 | else if (YAJL_IS_OBJECT(value)) 389 | { 390 | emit_map_open(); 391 | 392 | for (size_t i = 0; i < YAJL_GET_OBJECT(value)->len; i++) 393 | { 394 | yajl_gen_error(yajl_gen_string(JSON_GEN_HANDLE, reinterpret_cast(YAJL_GET_OBJECT(value)->keys[i]), strlen(YAJL_GET_OBJECT(value)->keys[i]))); 395 | emit_tree(static_cast(YAJL_GET_OBJECT(value)->values[i])); 396 | } 397 | 398 | emit_map_close(); 399 | } 400 | else if (YAJL_IS_ARRAY(value)) 401 | { 402 | emit_array_open(); 403 | 404 | for (size_t i = 0; i < YAJL_GET_ARRAY(value)->len; i++) 405 | { 406 | emit_tree(static_cast(YAJL_GET_ARRAY(value)->values[i])); 407 | } 408 | 409 | emit_array_close(); 410 | } 411 | else if (YAJL_IS_TRUE(value)) 412 | { 413 | emit(true); 414 | } 415 | else if (YAJL_IS_FALSE(value)) 416 | { 417 | emit(false); 418 | } 419 | else if (YAJL_IS_NULL(value)) 420 | { 421 | emit_null(); 422 | } 423 | } 424 | 425 | void Json::Emitter::emit_json(const string &value) 426 | { 427 | Parser p(value); 428 | 429 | emit_tree(p.json_tree_ptr()); 430 | } 431 | 432 | void Json::Emitter::emit_json(const string &key, const string &value) 433 | { 434 | emit(key); 435 | emit_json(value); 436 | } 437 | 438 | void Json::Emitter::emit_null() 439 | { 440 | yajl_gen_error(yajl_gen_null(JSON_GEN_HANDLE)); 441 | } 442 | 443 | void Json::Emitter::emit_null(const string &key) 444 | { 445 | emit(key); 446 | emit_null(); 447 | } 448 | 449 | void Json::Emitter::emit_map_open() 450 | { 451 | yajl_gen_error(yajl_gen_map_open(JSON_GEN_HANDLE)); 452 | } 453 | 454 | void Json::Emitter::emit_map_close() 455 | { 456 | yajl_gen_error(yajl_gen_map_close(JSON_GEN_HANDLE)); 457 | } 458 | 459 | void Json::Emitter::emit_array_open() 460 | { 461 | yajl_gen_error(yajl_gen_array_open(JSON_GEN_HANDLE)); 462 | } 463 | 464 | void Json::Emitter::emit_array_close() 465 | { 466 | yajl_gen_error(yajl_gen_array_close(JSON_GEN_HANDLE)); 467 | } 468 | 469 | Json::Node::Node(const string &name, void *json_node) 470 | { 471 | name_ = name; 472 | json_tree_ptr_ = json_node; 473 | 474 | if (!json_tree_ptr_) 475 | { 476 | Json::not_found(name_); 477 | } 478 | } 479 | 480 | string Json::Node::dump() const 481 | { 482 | Emitter e; 483 | 484 | e.emit_tree(json_tree_ptr_); 485 | 486 | return e.dump(); 487 | } 488 | 489 | vector Json::Node::dump_array() const 490 | { 491 | vector nodes = to_array(); 492 | vector r; 493 | r.reserve(nodes.size()); 494 | 495 | vector::const_iterator i, i_end = nodes.end(); 496 | for (i = nodes.begin(); i != i_end; ++i) 497 | { 498 | r.push_back(i->dump()); 499 | } 500 | 501 | return r; 502 | } 503 | 504 | map Json::Node::dump_map() const 505 | { 506 | map nodes = to_map(); 507 | map r; 508 | 509 | map::const_iterator i, i_end = nodes.end(); 510 | for (i = nodes.begin(); i != i_end; ++i) 511 | { 512 | r.insert(pair(i->first, i->second.dump())); 513 | } 514 | 515 | return r; 516 | } 517 | 518 | bool Json::Node::is_null() const 519 | { 520 | return YAJL_IS_NULL(JSON_TREE_HANDLE); 521 | } 522 | 523 | bool Json::Node::is_string() const 524 | { 525 | return YAJL_IS_STRING(JSON_TREE_HANDLE); 526 | } 527 | 528 | bool Json::Node::is_int() const 529 | { 530 | return YAJL_IS_INTEGER(JSON_TREE_HANDLE); 531 | } 532 | 533 | bool Json::Node::is_double() const 534 | { 535 | return YAJL_IS_DOUBLE(JSON_TREE_HANDLE); 536 | } 537 | 538 | bool Json::Node::is_bool() const 539 | { 540 | return YAJL_IS_BOOLEAN(JSON_TREE_HANDLE); 541 | } 542 | 543 | bool Json::Node::is_array() const 544 | { 545 | return YAJL_IS_ARRAY(JSON_TREE_HANDLE); 546 | } 547 | 548 | bool Json::Node::is_map() const 549 | { 550 | return YAJL_IS_OBJECT(JSON_TREE_HANDLE); 551 | } 552 | 553 | string Json::Node::to_string() const 554 | { 555 | if (!YAJL_IS_STRING(JSON_TREE_HANDLE)) 556 | { 557 | yajl_wrong_type(name_, JSON_TREE_HANDLE, "STRING"); 558 | } 559 | 560 | return utf8_to_local(YAJL_GET_STRING(JSON_TREE_HANDLE)); 561 | } 562 | 563 | long long Json::Node::to_int() const 564 | { 565 | if (!YAJL_IS_INTEGER(JSON_TREE_HANDLE)) 566 | { 567 | yajl_wrong_type(name_, JSON_TREE_HANDLE, "INTEGER"); 568 | } 569 | 570 | return YAJL_GET_INTEGER(JSON_TREE_HANDLE); 571 | } 572 | 573 | double Json::Node::to_double() const 574 | { 575 | if (!YAJL_IS_DOUBLE(JSON_TREE_HANDLE)) 576 | { 577 | yajl_wrong_type(name_, JSON_TREE_HANDLE, "DOUBLE"); 578 | } 579 | 580 | return YAJL_GET_DOUBLE(JSON_TREE_HANDLE); 581 | } 582 | 583 | bool Json::Node::to_bool() const 584 | { 585 | if (!YAJL_IS_BOOLEAN(JSON_TREE_HANDLE)) 586 | { 587 | yajl_wrong_type(name_, JSON_TREE_HANDLE, "BOOLEAN"); 588 | } 589 | 590 | return YAJL_GET_BOOLEAN(JSON_TREE_HANDLE); 591 | } 592 | 593 | vector Json::Node::to_array() const 594 | { 595 | if (!YAJL_IS_ARRAY(JSON_TREE_HANDLE)) 596 | { 597 | yajl_wrong_type(name_, JSON_TREE_HANDLE, "ARRAY"); 598 | } 599 | 600 | vector r; 601 | r.reserve(YAJL_GET_ARRAY(JSON_TREE_HANDLE)->len); 602 | 603 | for (size_t i = 0; i < YAJL_GET_ARRAY(JSON_TREE_HANDLE)->len; i++) 604 | { 605 | void *node_ptr = static_cast(YAJL_GET_ARRAY(JSON_TREE_HANDLE)->values[i]); 606 | 607 | ostringstream name; 608 | name << name_ << "[" << i << "]"; 609 | 610 | r.push_back(Node(name.str(), node_ptr)); 611 | } 612 | 613 | return r; 614 | } 615 | 616 | vector Json::Node::to_string_array() const 617 | { 618 | vector nodes = to_array(); 619 | vector r; 620 | r.reserve(nodes.size()); 621 | 622 | vector::const_iterator i, i_end = nodes.end(); 623 | for (i = nodes.begin(); i != i_end; ++i) 624 | { 625 | r.push_back(i->to_string()); 626 | } 627 | 628 | return r; 629 | } 630 | 631 | vector Json::Node::to_int_array() const 632 | { 633 | vector nodes = to_array(); 634 | vector r; 635 | r.reserve(nodes.size()); 636 | 637 | vector::const_iterator i, i_end = nodes.end(); 638 | for (i = nodes.begin(); i != i_end; ++i) 639 | { 640 | r.push_back(i->to_int()); 641 | } 642 | 643 | return r; 644 | } 645 | 646 | vector Json::Node::to_double_array() const 647 | { 648 | vector nodes = to_array(); 649 | vector r; 650 | r.reserve(nodes.size()); 651 | 652 | vector::const_iterator i, i_end = nodes.end(); 653 | for (i = nodes.begin(); i != i_end; ++i) 654 | { 655 | r.push_back(i->to_double()); 656 | } 657 | 658 | return r; 659 | } 660 | 661 | vector Json::Node::to_bool_array() const 662 | { 663 | vector nodes = to_array(); 664 | vector r; 665 | r.reserve(nodes.size()); 666 | 667 | vector::const_iterator i, i_end = nodes.end(); 668 | for (i = nodes.begin(); i != i_end; ++i) 669 | { 670 | r.push_back(i->to_bool()); 671 | } 672 | 673 | return r; 674 | } 675 | 676 | map Json::Node::to_map() const 677 | { 678 | if (!YAJL_IS_OBJECT(JSON_TREE_HANDLE)) 679 | { 680 | yajl_wrong_type(name_, JSON_TREE_HANDLE, "OBJECT"); 681 | } 682 | 683 | map r; 684 | 685 | for (size_t i = 0; i < YAJL_GET_OBJECT(JSON_TREE_HANDLE)->len; i++) 686 | { 687 | const char *key = YAJL_GET_OBJECT(JSON_TREE_HANDLE)->keys[i]; 688 | void *node_ptr = static_cast(YAJL_GET_OBJECT(JSON_TREE_HANDLE)->values[i]); 689 | 690 | ostringstream name; 691 | name << name_ << "[\"" << key << "\"]"; 692 | 693 | r.insert(pair(key, Node(name.str(), node_ptr))); 694 | } 695 | 696 | return r; 697 | } 698 | 699 | map Json::Node::to_string_map() const 700 | { 701 | map nodes = to_map(); 702 | map r; 703 | 704 | map::const_iterator i, i_end = nodes.end(); 705 | for (i = nodes.begin(); i != i_end; ++i) 706 | { 707 | r.insert(pair(i->first, i->second.to_string())); 708 | } 709 | 710 | return r; 711 | } 712 | 713 | map Json::Node::to_int_map() const 714 | { 715 | map nodes = to_map(); 716 | map r; 717 | 718 | map::const_iterator i, i_end = nodes.end(); 719 | for (i = nodes.begin(); i != i_end; ++i) 720 | { 721 | r.insert(pair(i->first, i->second.to_int())); 722 | } 723 | 724 | return r; 725 | } 726 | 727 | map Json::Node::to_double_map() const 728 | { 729 | map nodes = to_map(); 730 | map r; 731 | 732 | map::const_iterator i, i_end = nodes.end(); 733 | for (i = nodes.begin(); i != i_end; ++i) 734 | { 735 | r.insert(pair(i->first, i->second.to_double())); 736 | } 737 | 738 | return r; 739 | } 740 | 741 | map Json::Node::to_bool_map() const 742 | { 743 | map nodes = to_map(); 744 | map r; 745 | 746 | map::const_iterator i, i_end = nodes.end(); 747 | for (i = nodes.begin(); i != i_end; ++i) 748 | { 749 | r.insert(pair(i->first, i->second.to_bool())); 750 | } 751 | 752 | return r; 753 | } 754 | 755 | Json::Parser::Parser() 756 | { 757 | json_tree_ptr_ = NULL; 758 | } 759 | 760 | Json::Parser::Parser(const string &json_struct) 761 | { 762 | json_tree_ptr_ = NULL; 763 | 764 | load(json_struct); 765 | } 766 | 767 | Json::Parser::~Parser() 768 | { 769 | if (json_tree_ptr_) 770 | { 771 | yajl_tree_free(JSON_TREE_HANDLE); 772 | json_tree_ptr_ = NULL; 773 | } 774 | } 775 | 776 | bool Json::Parser::is_loaded() const 777 | { 778 | return json_tree_ptr_ != NULL; 779 | } 780 | 781 | void Json::Parser::load(const string &json_struct) 782 | { 783 | if (json_tree_ptr_) 784 | { 785 | yajl_tree_free(JSON_TREE_HANDLE); 786 | } 787 | 788 | char errors[1024]; 789 | json_tree_ptr_ = static_cast(yajl_tree_parse(json_struct.c_str(), errors, sizeof(errors))); 790 | 791 | if (json_tree_ptr_ == NULL) 792 | { 793 | throw runtime_error(string("JSON parse error:\n") + errors); 794 | } 795 | } 796 | 797 | Json::Node Json::Parser::root() const 798 | { 799 | if (!is_loaded()) 800 | { 801 | throw runtime_error("No JSON loaded in parser"); 802 | } 803 | 804 | return Node("root", json_tree_ptr_); 805 | } 806 | 807 | bool Json::Parser::exists(const string &key) const 808 | { 809 | if (!is_loaded()) 810 | { 811 | throw runtime_error("No JSON loaded in parser"); 812 | } 813 | 814 | const char *path[] = { key.c_str(), (const char *) 0 }; 815 | return yajl_tree_get(JSON_TREE_HANDLE, path, yajl_t_any) != NULL; 816 | } 817 | 818 | bool Json::Parser::empty(const string &key) const 819 | { 820 | if (!is_loaded()) 821 | { 822 | throw runtime_error("No JSON loaded in parser"); 823 | } 824 | 825 | const char *path[] = { key.c_str(), (const char *) 0 }; 826 | yajl_val v = yajl_tree_get(JSON_TREE_HANDLE, path, yajl_t_any); 827 | 828 | if (!v) 829 | { 830 | return true; 831 | } 832 | 833 | return YAJL_IS_NULL(v); 834 | } 835 | 836 | Json::Node Json::Parser::find(const string &key) const 837 | { 838 | const char *path[] = { key.c_str(), (const char *) 0 }; 839 | 840 | return find(path); 841 | } 842 | 843 | Json::Node Json::Parser::find(const char **key) const 844 | { 845 | if (!is_loaded()) 846 | { 847 | throw runtime_error("No JSON loaded in parser"); 848 | } 849 | 850 | yajl_val v = yajl_tree_get(JSON_TREE_HANDLE, key, yajl_t_any); 851 | 852 | // Find name 853 | size_t i = 0; 854 | string name; 855 | 856 | while (key[i] != (const char *) 0) 857 | { 858 | name = key[i++]; 859 | } 860 | 861 | return Node(name, static_cast(v)); 862 | } 863 | 864 | -------------------------------------------------------------------------------- /include/restful_mapper/field.h: -------------------------------------------------------------------------------- 1 | #ifndef RESTFUL_MAPPER_FIELD_H 2 | #define RESTFUL_MAPPER_FIELD_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | namespace restful_mapper 11 | { 12 | 13 | template 14 | class FieldBase 15 | { 16 | public: 17 | FieldBase() : is_dirty_(false), is_null_(true) {} 18 | 19 | virtual const T &get() const 20 | { 21 | return value_; 22 | } 23 | 24 | virtual const T &set(const T &value, const bool &keep_clean = false) 25 | { 26 | if (!keep_clean && (is_null_ || value != value_)) 27 | { 28 | touch(); 29 | } 30 | 31 | is_null_ = false; 32 | 33 | return value_ = value; 34 | } 35 | 36 | virtual void touch() const 37 | { 38 | is_dirty_ = true; 39 | } 40 | 41 | virtual void clean() const 42 | { 43 | is_dirty_ = false; 44 | } 45 | 46 | virtual void clear(const bool &keep_clean = false) = 0; 47 | 48 | virtual const bool &is_dirty() const 49 | { 50 | return is_dirty_; 51 | } 52 | 53 | virtual const bool &is_null() const 54 | { 55 | return is_null_; 56 | } 57 | 58 | virtual std::string name() = 0; 59 | 60 | operator T() const 61 | { 62 | return get(); 63 | } 64 | 65 | protected: 66 | T value_; 67 | mutable bool is_dirty_; 68 | bool is_null_; 69 | 70 | virtual void clear_(const T &null_value, const bool &keep_clean = false) 71 | { 72 | set(null_value, keep_clean); 73 | is_null_ = true; 74 | } 75 | 76 | }; 77 | 78 | template 79 | std::ostream &operator<<(std::ostream &out, const FieldBase &field) 80 | { 81 | return out << field.get(); 82 | } 83 | 84 | template 85 | class Field : public FieldBase {}; 86 | 87 | template <> 88 | class Field : public FieldBase 89 | { 90 | public: 91 | // Inherit from FieldBase 92 | Field() : FieldBase() { value_ = false; } 93 | const bool &operator=(const bool &value) { return set(value); } 94 | const Field &operator=(const FieldBase &value) { set(value); is_null_ = value.is_null(); return *this; } 95 | virtual std::string name() { return type_info_name(typeid(bool)); } 96 | virtual void clear(const bool &keep_clean = false) { clear_(false, keep_clean); }; 97 | }; 98 | 99 | template <> 100 | class Field : public FieldBase 101 | { 102 | public: 103 | // Inherit from FieldBase 104 | Field() : FieldBase() { value_ = 0; } 105 | const int &operator=(const int &value) { return set(value); } 106 | const Field &operator=(const FieldBase &value) { set(value); is_null_ = value.is_null(); return *this; } 107 | const Field &operator=(const FieldBase &value) { set(value); is_null_ = value.is_null(); return *this; } 108 | virtual std::string name() { return type_info_name(typeid(int)); } 109 | virtual void clear(const bool &keep_clean = false) { clear_(0, keep_clean); }; 110 | }; 111 | 112 | template <> 113 | class Field : public FieldBase 114 | { 115 | public: 116 | // Inherit from FieldBase 117 | Field() : FieldBase() { value_ = 0; } 118 | const long long &operator=(const long long &value) { return set(value); } 119 | const Field &operator=(const FieldBase &value) { set(value); is_null_ = value.is_null(); return *this; } 120 | const Field &operator=(const FieldBase &value) { set(value); is_null_ = value.is_null(); return *this; } 121 | virtual std::string name() { return type_info_name(typeid(long long)); } 122 | virtual void clear(const bool &keep_clean = false) { clear_(0, keep_clean); }; 123 | }; 124 | 125 | template <> 126 | class Field : public FieldBase 127 | { 128 | public: 129 | // Inherit from FieldBase 130 | Field() : FieldBase() { value_ = 0.0; } 131 | const double &operator=(const double &value) { return set(value); } 132 | const Field &operator=(const FieldBase &value) { set(value); is_null_ = value.is_null(); return *this; } 133 | virtual std::string name() { return type_info_name(typeid(double)); } 134 | virtual void clear(const bool &keep_clean = false) { clear_(0.0, keep_clean); }; 135 | }; 136 | 137 | template <> 138 | class Field : public FieldBase 139 | { 140 | public: 141 | // Inherit from FieldBase 142 | Field() : FieldBase() { value_ = ""; } 143 | const std::string &operator=(const std::string &value) { return set(value); } 144 | const Field &operator=(const FieldBase &value) { set(value); is_null_ = value.is_null(); return *this; } 145 | virtual std::string name() { return type_info_name(typeid(std::string)); } 146 | virtual void clear(const bool &keep_clean = false) { clear_("", keep_clean); }; 147 | 148 | // Reimplement std::string 149 | typedef std::string::value_type value_type; 150 | typedef std::string::traits_type traits_type; 151 | typedef std::string::allocator_type allocator_type; 152 | typedef std::string::reference reference; 153 | typedef std::string::const_reference const_reference; 154 | typedef std::string::pointer pointer; 155 | typedef std::string::const_pointer const_pointer; 156 | typedef std::string::iterator iterator; 157 | typedef std::string::const_iterator const_iterator; 158 | typedef std::string::reverse_iterator reverse_iterator; 159 | typedef std::string::const_reverse_iterator const_reverse_iterator; 160 | typedef std::string::difference_type difference_type; 161 | typedef std::string::size_type size_type; 162 | 163 | iterator begin() { return value_.begin(); } 164 | const_iterator begin() const { return value_.begin(); } 165 | iterator end() { return value_.end(); } 166 | const_iterator end() const { return value_.end(); } 167 | reverse_iterator rbegin() { return value_.rbegin(); } 168 | const_reverse_iterator rbegin() const { return value_.rbegin(); } 169 | reverse_iterator rend() { return value_.rend(); } 170 | const_reverse_iterator rend() const { return value_.rend(); } 171 | size_t size() const { return value_.size(); } 172 | size_t length() const { return value_.length(); } 173 | size_t max_size() const { return value_.max_size(); } 174 | void resize(size_t n) { touch(); value_.resize(n); } 175 | void resize(size_t n, char c) { touch(); value_.resize(n, c); } 176 | size_t capacity() const { return value_.capacity(); } 177 | void reserve(size_t n = 0) { touch(); value_.reserve(n); } 178 | bool empty() const { return value_.empty(); } 179 | char &operator[](size_t pos) { return value_[pos]; } 180 | const char &operator[](size_t pos) const { return value_[pos]; } 181 | char &at(size_t pos) { return value_.at(pos); } 182 | const char &at(size_t pos) const { return value_.at(pos); } 183 | Field &operator+=(const std::string &str) { touch(); value_ += str; return *this; } 184 | Field &operator+=(const char* s) { touch(); value_ += s; return *this; } 185 | Field &operator+=(char c) { touch(); value_ += c; return *this; } 186 | Field &append(const std::string &str) { touch(); value_.append(str); return *this; } 187 | Field &append(const std::string &str, size_t subpos, size_t sublen) { touch(); value_.append(str, subpos, sublen); return *this; } 188 | Field &append(const char* s) { touch(); value_.append(s); return *this; } 189 | Field &append(const char* s, size_t n) { touch(); value_.append(s, n); return *this; } 190 | Field &append(size_t n, char c) { touch(); value_.append(n, c); return *this; } 191 | template Field &append(InputIterator first, InputIterator last) { value_.append(first, last); return *this; } 192 | void push_back(char c) { touch(); value_.push_back(c); } 193 | Field &assign(const std::string &str) { touch(); value_.assign(str); return *this; } 194 | Field &assign(const std::string &str, size_t subpos, size_t sublen) { touch(); value_.assign(str, subpos, sublen); return *this; } 195 | Field &assign(const char* s) { touch(); value_.assign(s); return *this; } 196 | Field &assign(const char* s, size_t n) { touch(); value_.assign(s, n); return *this; } 197 | Field &assign(size_t n, char c) { touch(); value_.assign(n, c); return *this; } 198 | template Field &assign(InputIterator first, InputIterator last) { value_.assign(first, last); return *this; } 199 | Field &insert(size_t pos, const std::string &str) { touch(); value_.insert(pos, str); return *this; } 200 | Field &insert(size_t pos, const std::string &str, size_t subpos, size_t sublen) { touch(); value_.insert(pos, str, subpos, sublen); return *this; } 201 | Field &insert(size_t pos, const char* s) { touch(); value_.insert(pos, s); return *this; } 202 | Field &insert(size_t pos, const char* s, size_t n) { touch(); value_.insert(pos, s, n); return *this; } 203 | Field &insert(size_t pos, size_t n, char c) { touch(); value_.insert(pos, n, c); return *this; } 204 | void insert(iterator p, size_t n, char c) { touch(); value_.insert(p, n, c); } 205 | iterator insert(iterator p, char c) { touch(); return value_.insert(p, c); } 206 | template void insert(iterator p, InputIterator first, InputIterator last) { return value_.insert(first, last); } 207 | Field &erase(size_t pos = 0, size_t len = npos) { touch(); value_.erase(pos, len); return *this; } 208 | iterator erase(iterator p) { touch(); return value_.erase(p); } 209 | iterator erase(iterator first, iterator last) { touch(); return value_.erase(first, last); } 210 | Field &replace(size_t pos, size_t len, const std::string &str) { touch(); value_.replace(pos, len, str); return *this; } 211 | Field &replace(iterator i1, iterator i2, const std::string &str) { touch(); value_.replace(i1, i2, str); return *this; } 212 | Field &replace(size_t pos, size_t len, const std::string &str, size_t subpos, size_t sublen) { touch(); value_.replace(pos, len, str, subpos, sublen); return *this; } 213 | Field &replace(size_t pos, size_t len, const char* s) { touch(); value_.replace(pos, len, s); return *this; } 214 | Field &replace(iterator i1, iterator i2, const char* s) { touch(); value_.replace(i1, i2, s); return *this; } 215 | Field &replace(size_t pos, size_t len, const char* s, size_t n) { touch(); value_.replace(pos, len, s, n); return *this; } 216 | Field &replace(iterator i1, iterator i2, const char* s, size_t n) { touch(); value_.replace(i1, i2, s, n); return *this; } 217 | Field &replace(size_t pos, size_t len, size_t n, char c) { touch(); value_.replace(pos, len, n, c); return *this; } 218 | Field &replace(iterator i1, iterator i2, size_t n, char c) { touch(); value_.replace(i1, i2, n, c); return *this; } 219 | template Field &replace(iterator i1, iterator i2, InputIterator first, InputIterator last) { value_.replace(i1, i2, first, last); return *this; } 220 | void swap(std::string &str) { touch(); value_.swap(str); } 221 | const char* c_str() const { return value_.c_str(); } 222 | const char* data() const { return value_.data(); } 223 | allocator_type get_allocator() const { return value_.get_allocator(); } 224 | size_t copy(char* s, size_t n, size_t pos = 0) const { return value_.copy(s, n, pos); } 225 | size_t find(const std::string &str, size_t pos = 0) const { return value_.find(str, pos); } 226 | size_t find(const char* s, size_t pos = 0) const { return value_.find(s, pos); } 227 | size_t find(const char* s, size_t pos, size_t n) const { return value_.find(s, pos, n); } 228 | size_t find(char c, size_t pos = 0) const { return value_.find(c, pos); } 229 | size_t rfind(const std::string &str, size_t pos = npos) const { return value_.rfind(str, pos); } 230 | size_t rfind(const char* s, size_t pos = npos) const { return value_.rfind(s, pos); } 231 | size_t rfind(const char* s, size_t pos, size_t n) const { return value_.rfind(s, pos, n); } 232 | size_t rfind(char c, size_t pos = npos) const { return value_.rfind(c, pos); } 233 | size_t find_first_of(const std::string &str, size_t pos = 0) const { return value_.find_first_of(str, pos); } 234 | size_t find_first_of(const char* s, size_t pos = 0) const { return value_.find_first_of(s, pos); } 235 | size_t find_first_of(const char* s, size_t pos, size_t n) const { return value_.find_first_of(s, pos, n); } 236 | size_t find_first_of(char c, size_t pos = 0) const { return value_.find_first_of(c, pos); } 237 | size_t find_last_of(const std::string &str, size_t pos = npos) const { return value_.find_last_of(str, pos); } 238 | size_t find_last_of(const char* s, size_t pos = npos) const { return value_.find_last_of(s, pos); } 239 | size_t find_last_of(const char* s, size_t pos, size_t n) const { return value_.find_last_of(s, pos, n); } 240 | size_t find_last_of(char c, size_t pos = npos) const { return value_.find_last_of(c, pos); } 241 | size_t find_first_not_of(const std::string &str, size_t pos = 0) const { return value_.find_first_not_of(str, pos); } 242 | size_t find_first_not_of(const char* s, size_t pos = 0) const { return value_.find_first_not_of(s, pos); } 243 | size_t find_first_not_of(const char* s, size_t pos, size_t n) const { return value_.find_first_not_of(s, pos, n); } 244 | size_t find_first_not_of(char c, size_t pos = 0) const { return value_.find_first_not_of(c, pos); } 245 | size_t find_last_not_of(const std::string &str, size_t pos = npos) const { return value_.find_last_not_of(str, pos); } 246 | size_t find_last_not_of(const char* s, size_t pos = npos) const { return value_.find_last_not_of(s, pos); } 247 | size_t find_last_not_of(const char* s, size_t pos, size_t n) const { return value_.find_last_not_of(s, pos, n); } 248 | size_t find_last_not_of(char c, size_t pos = npos) const { return value_.find_last_not_of(c, pos); } 249 | std::string substr(size_t pos = 0, size_t len = npos) const { return value_.substr(pos, len); } 250 | int compare(const std::string &str) const { return value_.compare(str); } 251 | int compare(size_t pos, size_t len, const std::string &str) const { return value_.compare(pos, len, str); } 252 | int compare(size_t pos, size_t len, const std::string &str, size_t subpos, size_t sublen) const { return value_.compare(pos, len, str, subpos, sublen); } 253 | int compare(const char* s) const { return value_.compare(s); } 254 | int compare(size_t pos, size_t len, const char* s) const { return value_.compare(pos, len, s); } 255 | int compare(size_t pos, size_t len, const char* s, size_t n) const { return value_.compare(pos, len, s, n); } 256 | 257 | static const size_t npos = -1; 258 | }; 259 | 260 | inline std::string operator+(const std::string &lhs, const Field &rhs) { return lhs + rhs.get(); } 261 | inline std::string operator+(const Field &lhs, const Field &rhs) { return lhs.get() + rhs.get(); } 262 | inline std::string operator+(const Field &lhs, const std::string &rhs) { return lhs.get() + rhs; } 263 | 264 | inline bool operator==(const Field &lhs, const Field &rhs) { return lhs.get() == rhs.get(); } 265 | inline bool operator!=(const Field &lhs, const Field &rhs) { return lhs.get() != rhs.get(); } 266 | inline bool operator< (const Field &lhs, const Field &rhs) { return lhs.get() < rhs.get(); } 267 | inline bool operator<=(const Field &lhs, const Field &rhs) { return lhs.get() <= rhs.get(); } 268 | inline bool operator> (const Field &lhs, const Field &rhs) { return lhs.get() > rhs.get(); } 269 | inline bool operator>=(const Field &lhs, const Field &rhs) { return lhs.get() >= rhs.get(); } 270 | 271 | inline bool operator==(const Field &lhs, const std::string &rhs) { return lhs.get() == rhs; } 272 | inline bool operator!=(const Field &lhs, const std::string &rhs) { return lhs.get() != rhs; } 273 | inline bool operator< (const Field &lhs, const std::string &rhs) { return lhs.get() < rhs; } 274 | inline bool operator<=(const Field &lhs, const std::string &rhs) { return lhs.get() <= rhs; } 275 | inline bool operator> (const Field &lhs, const std::string &rhs) { return lhs.get() > rhs; } 276 | inline bool operator>=(const Field &lhs, const std::string &rhs) { return lhs.get() >= rhs; } 277 | 278 | inline bool operator==(const std::string &lhs, const Field &rhs) { return lhs == rhs.get(); } 279 | inline bool operator!=(const std::string &lhs, const Field &rhs) { return lhs != rhs.get(); } 280 | inline bool operator< (const std::string &lhs, const Field &rhs) { return lhs < rhs.get(); } 281 | inline bool operator<=(const std::string &lhs, const Field &rhs) { return lhs <= rhs.get(); } 282 | inline bool operator> (const std::string &lhs, const Field &rhs) { return lhs > rhs.get(); } 283 | inline bool operator>=(const std::string &lhs, const Field &rhs) { return lhs >= rhs.get(); } 284 | 285 | template <> 286 | class Field : public FieldBase 287 | { 288 | public: 289 | // Inherit from FieldBase 290 | Field() : FieldBase() { value_ = 0; } 291 | const std::time_t &operator=(const std::time_t &value) { return set(value); } 292 | const Field &operator=(const FieldBase &value) { set(value); is_null_ = value.is_null(); return *this; } 293 | virtual std::string name() { return type_info_name(typeid(std::time_t)); } 294 | virtual void clear(const bool &keep_clean = false) { clear_(0, keep_clean); }; 295 | 296 | const std::time_t &set(const std::string &value, const bool &keep_clean = false) 297 | { 298 | return FieldBase::set(from_iso8601(value), keep_clean); 299 | } 300 | using FieldBase::set; 301 | 302 | std::string to_iso8601(const bool &include_timezone = true) const 303 | { 304 | return ::to_iso8601(get(), include_timezone); 305 | } 306 | 307 | std::string to_local(const std::string &format) const 308 | { 309 | std::tm exploded_time(*std::localtime(&get())); 310 | 311 | char buf[1024]; 312 | std::strftime(buf, sizeof buf, format.c_str(), &exploded_time); 313 | 314 | return std::string(buf); 315 | } 316 | 317 | operator std::string() const 318 | { 319 | return to_iso8601(); 320 | } 321 | 322 | const std::time_t &operator=(const std::string &value) 323 | { 324 | return set(value); 325 | } 326 | 327 | int local_year() const 328 | { 329 | std::tm value(*std::localtime(&get())); 330 | 331 | return value.tm_year + 1900; 332 | } 333 | 334 | int local_month() const 335 | { 336 | std::tm value(*std::localtime(&get())); 337 | 338 | return value.tm_mon + 1; 339 | } 340 | 341 | int local_day() const 342 | { 343 | std::tm value(*std::localtime(&get())); 344 | 345 | return value.tm_mday; 346 | } 347 | 348 | int local_hour() const 349 | { 350 | std::tm value(*std::localtime(&get())); 351 | 352 | return value.tm_hour; 353 | } 354 | 355 | int local_minute() const 356 | { 357 | std::tm value(*std::localtime(&get())); 358 | 359 | return value.tm_min; 360 | } 361 | 362 | int local_second() const 363 | { 364 | std::tm value(*std::localtime(&get())); 365 | 366 | return value.tm_sec; 367 | } 368 | 369 | int utc_year() const 370 | { 371 | std::tm value(*std::gmtime(&get())); 372 | 373 | return value.tm_year + 1900; 374 | } 375 | 376 | int utc_month() const 377 | { 378 | std::tm value(*std::gmtime(&get())); 379 | 380 | return value.tm_mon + 1; 381 | } 382 | 383 | int utc_day() const 384 | { 385 | std::tm value(*std::gmtime(&get())); 386 | 387 | return value.tm_mday; 388 | } 389 | 390 | int utc_hour() const 391 | { 392 | std::tm value(*std::gmtime(&get())); 393 | 394 | return value.tm_hour; 395 | } 396 | 397 | int utc_minute() const 398 | { 399 | std::tm value(*std::gmtime(&get())); 400 | 401 | return value.tm_min; 402 | } 403 | 404 | int utc_second() const 405 | { 406 | std::tm value(*std::gmtime(&get())); 407 | 408 | return value.tm_sec; 409 | } 410 | }; 411 | 412 | /** 413 | * @brief Represents the primary key column of a database object. 414 | * 415 | * Can only be set once, otherwise it will throw. 416 | */ 417 | class Primary : public FieldBase 418 | { 419 | public: 420 | // Inherit from FieldBase 421 | Primary() : FieldBase(), is_assigned_(false) { value_ = 0; } 422 | const long long &operator=(const long long &value) { return set(value); } 423 | virtual std::string name() { return type_info_name(typeid(long long)); } 424 | virtual void clear(const bool &keep_clean = false) { clear_(0, keep_clean); }; 425 | 426 | operator std::string() const 427 | { 428 | std::ostringstream s; 429 | s << get(); 430 | return s.str(); 431 | } 432 | 433 | const long long &set(const long long &value, const bool &keep_clean = false) 434 | { 435 | if (is_assigned_ && value != value_) 436 | { 437 | throw std::runtime_error("Primary field is read-only"); 438 | } 439 | 440 | is_assigned_ = true; 441 | return FieldBase::set(value, keep_clean); 442 | } 443 | 444 | protected: 445 | bool is_assigned_; 446 | }; 447 | 448 | /** 449 | * @brief Represents the foreign key column of a database object. 450 | */ 451 | template 452 | class Foreign : public FieldBase 453 | { 454 | public: 455 | // Inherit from FieldBase 456 | Foreign() : FieldBase() { value_ = 0; } 457 | const long long &operator=(const long long &value) { return set(value); } 458 | const Foreign &operator=(const FieldBase &value) { set(value); is_null_ = value.is_null(); return *this; } 459 | const Foreign &operator=(const FieldBase &value) { set(value); is_null_ = value.is_null(); return *this; } 460 | virtual std::string name() { return type_info_name(typeid(long long)); } 461 | virtual void clear(const bool &keep_clean = false) { clear_(0, keep_clean); }; 462 | 463 | const std::string &class_name() const 464 | { 465 | return T::class_name(); 466 | } 467 | }; 468 | 469 | } 470 | 471 | #endif // RESTFUL_MAPPER_FIELD_H 472 | 473 | --------------------------------------------------------------------------------