diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 048858ca..a0b933a3 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -35,7 +35,7 @@ jobs: run: cmake -E make_directory build - name: Configure CMake - run: cmake --preset ${{ matrix.compiler }}-${{ matrix.config }} -DCMAKE_TOOLCHAIN_FILE= + run: cmake --preset ${{ matrix.compiler }}-${{ matrix.config }} -DGRAPHQL_USE_TAOCPP_JSON=OFF -DCMAKE_TOOLCHAIN_FILE= - name: Build run: cmake --build --preset ${{ matrix.compiler }}-${{ matrix.config }} -j -v diff --git a/CMakeLists.txt b/CMakeLists.txt index 753aca8a..0b1f7a31 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -37,7 +37,7 @@ option(GRAPHQL_BUILD_MODULES "Build the C++20 module interface libraries." ON) option(GRAPHQL_BUILD_SCHEMAGEN "Build the schemagen tool." ON) option(GRAPHQL_BUILD_CLIENTGEN "Build the clientgen tool." ON) option(GRAPHQL_BUILD_TESTS "Build the tests and sample schema library." ON) -option(GRAPHQL_USE_RAPIDJSON "Use RapidJSON for JSON serialization." ON) +option(GRAPHQL_USE_TAOCPP_JSON "Use taocpp-json for JSON serialization." ON) if(GRAPHQL_BUILD_SCHEMAGEN) list(APPEND VCPKG_MANIFEST_FEATURES "schemagen") @@ -51,8 +51,13 @@ if(GRAPHQL_BUILD_TESTS) list(APPEND VCPKG_MANIFEST_FEATURES "tests") endif() -if(GRAPHQL_USE_RAPIDJSON) - list(APPEND VCPKG_MANIFEST_FEATURES "rapidjson") +if(GRAPHQL_USE_TAOCPP_JSON) + list(APPEND VCPKG_MANIFEST_FEATURES "taocpp-json") +else() + option(GRAPHQL_USE_RAPIDJSON "Use RapidJSON for JSON serialization." ON) + if(GRAPHQL_USE_RAPIDJSON) + list(APPEND VCPKG_MANIFEST_FEATURES "rapidjson") + endif() endif() if(GRAPHQL_BUILD_SCHEMAGEN AND GRAPHQL_BUILD_CLIENTGEN) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 64d1d21f..bc8f5b07 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -387,21 +387,22 @@ if(WIN32 AND BUILD_SHARED_LIBS) add_version_rc(graphqlclient) endif() -# RapidJSON is the only option for JSON serialization used in this project, but if you want -# to use another JSON library you can implement an alternate version of the functions in -# JSONResponse.cpp to serialize to and from GraphQLResponse and build graphqljson from that. -# You will also need to define how to build the graphqljson library target with your -# implementation, and you should set BUILD_GRAPHQLJSON so that the test dependencies know -# about your version of graphqljson. -if(GRAPHQL_USE_RAPIDJSON) +if(GRAPHQL_USE_TAOCPP_JSON) + find_package(taocpp-json CONFIG REQUIRED) + set(BUILD_GRAPHQLJSON ON) + add_library(graphqljson TaoCppJSONResponse.cpp) + target_link_libraries(graphqljson PRIVATE taocpp::json) +elseif(GRAPHQL_USE_RAPIDJSON) find_package(RapidJSON CONFIG REQUIRED) - set(BUILD_GRAPHQLJSON ON) - add_library(graphqljson JSONResponse.cpp) + add_library(graphqljson RapidJSONResponse.cpp) + target_include_directories(graphqljson SYSTEM PRIVATE ${RAPIDJSON_INCLUDE_DIRS}) +endif() + +if(BUILD_GRAPHQLJSON) add_library(cppgraphqlgen::graphqljson ALIAS graphqljson) target_compile_features(graphqljson PUBLIC cxx_std_20) target_link_libraries(graphqljson PUBLIC graphqlresponse) - target_include_directories(graphqljson SYSTEM PRIVATE ${RAPIDJSON_INCLUDE_DIRS}) target_sources(graphqljson PUBLIC FILE_SET HEADERS BASE_DIRS ${INCLUDE_ROOT} FILES ${INCLUDE_ROOT}/graphqlservice/JSONResponse.h) @@ -456,8 +457,6 @@ endif() # graphqljson if(BUILD_GRAPHQLJSON) - target_link_libraries(graphqljson PUBLIC graphqlresponse) - install(TARGETS graphqljson EXPORT cppgraphqlgen-targets RUNTIME DESTINATION bin diff --git a/src/JSONResponse.cpp b/src/RapidJSONResponse.cpp similarity index 96% rename from src/JSONResponse.cpp rename to src/RapidJSONResponse.cpp index 8f135478..e7067f61 100644 --- a/src/JSONResponse.cpp +++ b/src/RapidJSONResponse.cpp @@ -16,10 +16,10 @@ namespace graphql::response { -class StringWriter +class StreamWriter { public: - StringWriter(rapidjson::StringBuffer& buffer) + StreamWriter(rapidjson::StringBuffer& buffer) : _writer { buffer } { } @@ -81,7 +81,7 @@ class StringWriter std::string toJSON(Value&& response) { rapidjson::StringBuffer buffer; - Writer writer { std::make_unique(buffer) }; + Writer writer { std::make_unique(buffer) }; writer.write(std::move(response)); return buffer.GetString(); diff --git a/src/TaoCppJSONResponse.cpp b/src/TaoCppJSONResponse.cpp new file mode 100644 index 00000000..341327b8 --- /dev/null +++ b/src/TaoCppJSONResponse.cpp @@ -0,0 +1,260 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#include "graphqlservice/JSONResponse.h" + +#include + +#include +#include +#include +#include +#include + +namespace graphql::response { + +class StreamWriter +{ +public: + StreamWriter(std::ostream& stream) + : _writer { stream } + { + } + + void start_object() + { + _scopeStack.push_back(Scope::Object); + _writer.begin_object(); + } + + void add_member(const std::string& key) + { + _writer.key(key); + } + + void end_object() + { + _writer.end_object(); + _scopeStack.pop_back(); + end_value(); + } + + void start_array() + { + _scopeStack.push_back(Scope::Object); + _writer.begin_array(); + } + + void end_arrary() + { + _writer.end_array(); + _scopeStack.pop_back(); + end_value(); + } + + void write_null() + { + _writer.null(); + end_value(); + } + + void write_string(const std::string& value) + { + _writer.string(value); + end_value(); + } + + void write_bool(bool value) + { + _writer.boolean(value); + end_value(); + } + + void write_int(int value) + { + _writer.number(static_cast(value)); + end_value(); + } + + void write_float(double value) + { + _writer.number(value); + end_value(); + } + +private: + enum class Scope + { + Array, + Object, + }; + + void end_value() + { + if (_scopeStack.empty()) + { + return; + } + + switch (_scopeStack.back()) + { + case Scope::Array: + _writer.element(); + break; + + case Scope::Object: + _writer.member(); + break; + } + } + + tao::json::events::to_stream _writer; + std::vector _scopeStack; +}; + +std::string toJSON(Value&& response) +{ + std::ostringstream stream; + Writer writer { std::make_unique(stream) }; + writer.write(std::move(response)); + return stream.str(); +} + +struct ResponseHandler +{ + ResponseHandler() + { + // Start with a single null value. + _responseStack.push_back({}); + } + + Value getResponse() + { + auto response = std::move(_responseStack.back()); + + _responseStack.pop_back(); + + return response; + } + + void null() + { + setValue(Value()); + } + + void boolean(bool b) + { + setValue(Value(b)); + } + + void number(double d) + { + auto value = Value(Type::Float); + + value.set(std::move(d)); + setValue(std::move(value)); + } + + void number(std::int64_t i) + { + if (i < std::numeric_limits::min() + || i > std::numeric_limits::max()) + { + // https://spec.graphql.org/October2021/#sec-Int + number(static_cast(i)); + } + else + { + static_assert(sizeof(std::int32_t) == sizeof(IntType), + "GraphQL only supports 32-bit signed integers"); + auto value = Value(Type::Int); + + value.set(static_cast(i)); + setValue(std::move(value)); + } + } + + void number(std::uint64_t i) + { + if (i > static_cast(std::numeric_limits::max())) + { + // https://spec.graphql.org/October2021/#sec-Int + number(static_cast(i)); + } + else + { + number(static_cast(i)); + } + } + + void string(std::string&& str) + { + setValue(Value(std::move(str)).from_json()); + } + + void begin_array() + { + _responseStack.push_back(Value(Type::List)); + } + + void element() + { + } + + void end_array() + { + setValue(getResponse()); + } + + void begin_object() + { + _responseStack.push_back(Value(Type::Map)); + } + + void key(std::string&& str) + { + _keyStack.push_back(std::move(str)); + } + + void member() + { + } + + void end_object() + { + setValue(getResponse()); + } + +private: + void setValue(Value&& value) + { + switch (_responseStack.back().type()) + { + case Type::Map: + _responseStack.back().emplace_back(std::move(_keyStack.back()), std::move(value)); + _keyStack.pop_back(); + break; + + case Type::List: + _responseStack.back().emplace_back(std::move(value)); + break; + + default: + _responseStack.back() = std::move(value); + break; + } + } + + std::vector _keyStack; + std::vector _responseStack; +}; + +Value parseJSON(const std::string& json) +{ + ResponseHandler handler; + tao::json::events::from_string(handler, json); + + return handler.getResponse(); +} + +} // namespace graphql::response diff --git a/vcpkg.json b/vcpkg.json index 7c62b562..5ed83019 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -20,8 +20,14 @@ "gtest" ] }, + "taocpp-json": { + "description": "Build the graphqljson library with taocpp-json.", + "dependencies": [ + "taocpp-json" + ] + }, "rapidjson": { - "description": "Build the graphqljson library with RapidJSON.", + "description": "Build the graphqljson library with rapidjson.", "dependencies": [ "rapidjson" ]