Skip to content

Commit

Permalink
feat(json): default taocpp-json impl for graphqljson
Browse files Browse the repository at this point in the history
  • Loading branch information
wravery committed Sep 17, 2024
1 parent 8eb93c4 commit 7cee86d
Show file tree
Hide file tree
Showing 6 changed files with 290 additions and 20 deletions.
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

0 comments on commit 7cee86d

Please sign in to comment.