Skip to content

feat(json): default taocpp-json impl for graphqljson #316

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Sep 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/linux.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
11 changes: 8 additions & 3 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand All @@ -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)
Expand Down
23 changes: 11 additions & 12 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -456,8 +457,6 @@ endif()

# graphqljson
if(BUILD_GRAPHQLJSON)
target_link_libraries(graphqljson PUBLIC graphqlresponse)

install(TARGETS graphqljson
EXPORT cppgraphqlgen-targets
RUNTIME DESTINATION bin
Expand Down
6 changes: 3 additions & 3 deletions src/JSONResponse.cpp → src/RapidJSONResponse.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@

namespace graphql::response {

class StringWriter
class StreamWriter
{
public:
StringWriter(rapidjson::StringBuffer& buffer)
StreamWriter(rapidjson::StringBuffer& buffer)
: _writer { buffer }
{
}
Expand Down Expand Up @@ -81,7 +81,7 @@ class StringWriter
std::string toJSON(Value&& response)
{
rapidjson::StringBuffer buffer;
Writer writer { std::make_unique<StringWriter>(buffer) };
Writer writer { std::make_unique<StreamWriter>(buffer) };

writer.write(std::move(response));
return buffer.GetString();
Expand Down
260 changes: 260 additions & 0 deletions src/TaoCppJSONResponse.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,260 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

#include "graphqlservice/JSONResponse.h"

#include <tao/json.hpp>

#include <cstdint>
#include <iostream>
#include <limits>
#include <sstream>
#include <vector>

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<std::int64_t>(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<Scope> _scopeStack;
};

std::string toJSON(Value&& response)
{
std::ostringstream stream;
Writer writer { std::make_unique<StreamWriter>(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<FloatType>(std::move(d));
setValue(std::move(value));
}

void number(std::int64_t i)
{
if (i < std::numeric_limits<std::int32_t>::min()
|| i > std::numeric_limits<std::int32_t>::max())
{
// https://spec.graphql.org/October2021/#sec-Int
number(static_cast<double>(i));
}
else
{
static_assert(sizeof(std::int32_t) == sizeof(IntType),
"GraphQL only supports 32-bit signed integers");
auto value = Value(Type::Int);

value.set<IntType>(static_cast<std::int32_t>(i));
setValue(std::move(value));
}
}

void number(std::uint64_t i)
{
if (i > static_cast<std::uint64_t>(std::numeric_limits<std::int64_t>::max()))
{
// https://spec.graphql.org/October2021/#sec-Int
number(static_cast<double>(i));
}
else
{
number(static_cast<std::int64_t>(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<std::string> _keyStack;
std::vector<Value> _responseStack;
};

Value parseJSON(const std::string& json)
{
ResponseHandler handler;
tao::json::events::from_string(handler, json);

return handler.getResponse();
}

} // namespace graphql::response
8 changes: 7 additions & 1 deletion vcpkg.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
]
Expand Down