From 11d7d0cc73745e4a9f168101a931b5f8fcce23a3 Mon Sep 17 00:00:00 2001 From: Bill Avery Date: Mon, 21 Oct 2024 18:01:37 -0700 Subject: [PATCH 1/2] feat: add ResolverResult::visit and visitErrorValues --- include/graphqlservice/GraphQLService.h | 4 + include/graphqlservice/Service.ixx | 1 + src/GraphQLService.cpp | 107 +++++++++++++++--------- 3 files changed, 74 insertions(+), 38 deletions(-) diff --git a/include/graphqlservice/GraphQLService.h b/include/graphqlservice/GraphQLService.h index a0280fa1..1463739e 100644 --- a/include/graphqlservice/GraphQLService.h +++ b/include/graphqlservice/GraphQLService.h @@ -73,6 +73,9 @@ struct [[nodiscard("unnecessary construction")]] schema_error [[nodiscard("unnecessary memory copy")]] GRAPHQLSERVICE_EXPORT response::Value buildErrorValues( std::list&& structuredErrors); +[[nodiscard("unnecessary memory copy")]] GRAPHQLSERVICE_EXPORT response::ValueTokenStream +visitErrorValues(std::list&& structuredErrors); + // This exception bubbles up 1 or more error messages to the JSON results. class [[nodiscard("unnecessary construction")]] schema_exception : public std::exception { @@ -540,6 +543,7 @@ struct [[nodiscard("unnecessary construction")]] ResolverParams : SelectionSetPa struct [[nodiscard("unnecessary construction")]] ResolverResult { [[nodiscard("unnecessary call")]] GRAPHQLSERVICE_EXPORT response::Value document() &&; + [[nodiscard("unnecessary call")]] GRAPHQLSERVICE_EXPORT response::ValueTokenStream visit() &&; response::ValueTokenStream data {}; std::list errors {}; diff --git a/include/graphqlservice/Service.ixx b/include/graphqlservice/Service.ixx index 63cc8119..74cf357c 100644 --- a/include/graphqlservice/Service.ixx +++ b/include/graphqlservice/Service.ixx @@ -33,6 +33,7 @@ using service::buildErrorPath; using service::schema_error; using service::buildErrorValues; +using service::visitErrorValues; using service::schema_exception; using service::unimplemented_method; diff --git a/src/GraphQLService.cpp b/src/GraphQLService.cpp index b1ce3a2c..d19961c1 100644 --- a/src/GraphQLService.cpp +++ b/src/GraphQLService.cpp @@ -17,59 +17,73 @@ using namespace std::literals; namespace graphql::service { -void addErrorMessage(std::string&& message, response::Value& error) +response::ValueTokenStream addErrorMessage(std::string&& message) { - error.emplace_back(std::string { strMessage }, response::Value(std::move(message))); + response::ValueTokenStream result {}; + + result.push_back(response::ValueToken::AddMember { std::string { strMessage } }); + result.push_back(response::ValueToken::StringValue { std::move(message) }); + + return result; } -void addErrorLocation(const schema_location& location, response::Value& error) +response::ValueTokenStream addErrorLocation(const schema_location& location) { + response::ValueTokenStream result {}; + if (location.line == 0) { - return; + return result; } - response::Value errorLocation(response::Type::Map); + result.push_back(response::ValueToken::AddMember { std::string { strLocations } }); - errorLocation.reserve(2); - errorLocation.emplace_back(std::string { strLine }, - response::Value(static_cast(location.line))); - errorLocation.emplace_back(std::string { strColumn }, - response::Value(static_cast(location.column))); + result.push_back(response::ValueToken::StartArray {}); + result.push_back(response::ValueToken::Reserve { 1 }); + result.push_back(response::ValueToken::StartObject {}); + result.push_back(response::ValueToken::Reserve { 2 }); - response::Value errorLocations(response::Type::List); + result.push_back(response::ValueToken::AddMember { std::string { strLine } }); + result.push_back(response::ValueToken::IntValue { static_cast(location.line) }); + result.push_back(response::ValueToken::AddMember { std::string { strColumn } }); + result.push_back(response::ValueToken::IntValue { static_cast(location.column) }); - errorLocations.reserve(1); - errorLocations.emplace_back(std::move(errorLocation)); + result.push_back(response::ValueToken::EndObject {}); + result.push_back(response::ValueToken::EndArray {}); - error.emplace_back(std::string { strLocations }, std::move(errorLocations)); + return result; } -void addErrorPath(const error_path& path, response::Value& error) +response::ValueTokenStream addErrorPath(const error_path& path) { + response::ValueTokenStream result {}; + if (path.empty()) { - return; + return result; } - response::Value errorPath(response::Type::List); + result.push_back(response::ValueToken::AddMember { std::string { strPath } }); + result.push_back(response::ValueToken::StartArray {}); + result.push_back(response::ValueToken::Reserve { path.size() }); - errorPath.reserve(path.size()); for (const auto& segment : path) { if (std::holds_alternative(segment)) { - errorPath.emplace_back( - response::Value { std::string { std::get(segment) } }); + result.push_back(response::ValueToken::StringValue { + std::string { std::get(segment) } }); } else if (std::holds_alternative(segment)) { - errorPath.emplace_back( - response::Value(static_cast(std::get(segment)))); + result.push_back(response::ValueToken::IntValue { + static_cast(std::get(segment)) }); } } - error.emplace_back(std::string { strPath }, std::move(errorPath)); + result.push_back(response::ValueToken::EndArray {}); + + return result; } error_path buildErrorPath(const std::optional& path) @@ -99,22 +113,30 @@ error_path buildErrorPath(const std::optional& path) response::Value buildErrorValues(std::list&& structuredErrors) { - response::Value errors(response::Type::List); + return visitErrorValues(std::move(structuredErrors)).value(); +} + +response::ValueTokenStream visitErrorValues(std::list&& structuredErrors) +{ + response::ValueTokenStream errors; - errors.reserve(structuredErrors.size()); + errors.push_back(response::ValueToken::StartArray {}); + errors.push_back(response::ValueToken::Reserve { structuredErrors.size() }); for (auto& error : structuredErrors) { - response::Value entry(response::Type::Map); + errors.push_back(response::ValueToken::StartObject {}); + errors.push_back(response::ValueToken::Reserve { 3 }); - entry.reserve(3); - addErrorMessage(std::move(error.message), entry); - addErrorLocation(error.location, entry); - addErrorPath(error.path, entry); + errors.append(addErrorMessage(std::move(error.message))); + errors.append(addErrorLocation(error.location)); + errors.append(addErrorPath(error.path)); - errors.emplace_back(std::move(entry)); + errors.push_back(response::ValueToken::EndObject {}); } + errors.push_back(response::ValueToken::EndArray {}); + return errors; } @@ -245,7 +267,7 @@ void await_worker_queue::resumePending() // Default to immediate synchronous execution. await_async::await_async() : _pimpl { std::static_pointer_cast( - std::make_shared>(std::make_shared())) } + std::make_shared>(std::make_shared())) } { } @@ -253,9 +275,9 @@ await_async::await_async() await_async::await_async(std::launch launch) : _pimpl { ((launch & std::launch::async) == std::launch::async) ? std::static_pointer_cast(std::make_shared>( - std::make_shared())) + std::make_shared())) : std::static_pointer_cast(std::make_shared>( - std::make_shared())) } + std::make_shared())) } { } @@ -623,16 +645,25 @@ schema_location ResolverParams::getLocation() const response::Value ResolverResult::document() && { - response::Value document { response::Type::Map }; + return std::move(*this).visit().value(); +} - document.emplace_back(std::string { strData }, std::move(data).value()); +response::ValueTokenStream ResolverResult::visit() && +{ + response::ValueTokenStream result { response::ValueToken::StartObject {} }; + + result.push_back(response::ValueToken::AddMember { std::string { strData } }); + result.append(std::move(data)); if (!errors.empty()) { - document.emplace_back(std::string { strErrors }, buildErrorValues(std::move(errors))); + result.push_back(response::ValueToken::AddMember { std::string { strErrors } }); + result.append(visitErrorValues(std::move(errors))); } - return document; + result.push_back(response::ValueToken::EndObject {}); + + return result; } template <> From 117120bdc730d9345720fa817f94a854245c9705 Mon Sep 17 00:00:00 2001 From: Bill Avery Date: Mon, 21 Oct 2024 23:16:59 -0700 Subject: [PATCH 2/2] feat: replace response::Writer with response::ValueVisitor --- doc/json.md | 2 +- include/graphqlservice/GraphQLResponse.h | 100 +------------------- include/graphqlservice/Response.ixx | 2 - src/GraphQLResponse.cpp | 112 +++++++++++++---------- src/RapidJSONResponse.cpp | 46 ++++++++-- src/TaoCppJSONResponse.cpp | 47 ++++++++-- 6 files changed, 141 insertions(+), 168 deletions(-) diff --git a/doc/json.md b/doc/json.md index 4227683a..49cd7827 100644 --- a/doc/json.md +++ b/doc/json.md @@ -58,7 +58,7 @@ private: virtual void end_object() const = 0; virtual void start_array() const = 0; - virtual void end_arrary() const = 0; + virtual void end_array() const = 0; virtual void write_null() const = 0; virtual void write_string(const std::string& value) const = 0; diff --git a/include/graphqlservice/GraphQLResponse.h b/include/graphqlservice/GraphQLResponse.h index c4bd7765..9a591e33 100644 --- a/include/graphqlservice/GraphQLResponse.h +++ b/include/graphqlservice/GraphQLResponse.h @@ -500,7 +500,7 @@ class [[nodiscard("unnecessary construction")]] ValueVisitor final template ValueVisitor(std::shared_ptr writer) noexcept : _concept { std::static_pointer_cast( - std::make_shared>(std::move(writer))) } + std::make_shared>(std::move(writer))) } { } @@ -635,6 +635,8 @@ class [[nodiscard("unnecessary construction")]] ValueTokenStream final ValueTokenStream() noexcept = default; ~ValueTokenStream() = default; + GRAPHQLRESPONSE_EXPORT explicit ValueTokenStream(Value&& value); + ValueTokenStream(ValueTokenStream&&) noexcept = default; ValueTokenStream& operator=(ValueTokenStream&&) noexcept = default; @@ -662,102 +664,6 @@ class [[nodiscard("unnecessary construction")]] ValueTokenStream final std::list _tokens; }; -class [[nodiscard("unnecessary construction")]] Writer final -{ -private: - struct Concept - { - virtual ~Concept() = default; - - virtual void start_object() const = 0; - virtual void add_member(const std::string& key) const = 0; - virtual void end_object() const = 0; - - virtual void start_array() const = 0; - virtual void end_arrary() const = 0; - - virtual void write_null() const = 0; - virtual void write_string(const std::string& value) const = 0; - virtual void write_bool(bool value) const = 0; - virtual void write_int(int value) const = 0; - virtual void write_float(double value) const = 0; - }; - - template - struct Model : Concept - { - explicit Model(std::unique_ptr pimpl) noexcept - : _pimpl { std::move(pimpl) } - { - } - - void start_object() const final - { - _pimpl->start_object(); - } - - void add_member(const std::string& key) const final - { - _pimpl->add_member(key); - } - - void end_object() const final - { - _pimpl->end_object(); - } - - void start_array() const final - { - _pimpl->start_array(); - } - - void end_arrary() const final - { - _pimpl->end_arrary(); - } - - void write_null() const final - { - _pimpl->write_null(); - } - - void write_string(const std::string& value) const final - { - _pimpl->write_string(value); - } - - void write_bool(bool value) const final - { - _pimpl->write_bool(value); - } - - void write_int(int value) const final - { - _pimpl->write_int(value); - } - - void write_float(double value) const final - { - _pimpl->write_float(value); - } - - private: - std::unique_ptr _pimpl; - }; - - const std::shared_ptr _concept; - -public: - template - Writer(std::unique_ptr writer) noexcept - : _concept { std::static_pointer_cast( - std::make_shared>(std::move(writer))) } - { - } - - GRAPHQLRESPONSE_EXPORT void write(Value value) const; -}; - } // namespace graphql::response #endif // GRAPHQLRESPONSE_H diff --git a/include/graphqlservice/Response.ixx b/include/graphqlservice/Response.ixx index 4f83da5d..522a4c6e 100644 --- a/include/graphqlservice/Response.ixx +++ b/include/graphqlservice/Response.ixx @@ -31,8 +31,6 @@ using response::AwaitableValue; using response::ValueVisitor; using response::ValueToken; using response::ValueTokenStream; - -using response::Writer; // clang-format on } // namespace graphql::response diff --git a/src/GraphQLResponse.cpp b/src/GraphQLResponse.cpp index 984a2034..cb829ccb 100644 --- a/src/GraphQLResponse.cpp +++ b/src/GraphQLResponse.cpp @@ -101,9 +101,9 @@ bool IdType::operator==(const IdType& rhs) const noexcept return (std::holds_alternative(_data) ? internal::Base64::compareBase64(std::get(_data), - std::get(rhs._data)) + std::get(rhs._data)) : internal::Base64::compareBase64(std::get(rhs._data), - std::get(_data))) + std::get(_data))) == internal::Base64::Comparison::EqualTo; } @@ -146,7 +146,7 @@ bool IdType::operator<(const IdType& rhs) const noexcept return (std::holds_alternative(_data) ? (internal::Base64::compareBase64(std::get(_data), std::get(rhs._data)) - < internal::Base64::Comparison::EqualTo) + < internal::Base64::Comparison::EqualTo) : (internal::Base64::compareBase64(std::get(rhs._data), std::get(_data))) > internal::Base64::Comparison::EqualTo); @@ -1802,111 +1802,127 @@ void ValueTokenStreamVisitor::add_value(Value&& value) } } -void ValueTokenStream::append(ValueTokenStream&& other) -{ - _tokens.splice(_tokens.end(), std::move(other._tokens)); -} - -void ValueTokenStream::visit(const std::shared_ptr& visitor) && +ValueTokenStream::ValueTokenStream(Value&& value) { - for (auto& token : _tokens) - { - std::move(token).visit(visitor); - } - - visitor->complete(); -} - -Value ValueTokenStream::value() && -{ - auto visitor = std::make_shared(); - - std::move(*this).visit(std::make_shared(visitor)); - - return visitor->value(); -} - -void Writer::write(Value response) const -{ - switch (response.type()) + switch (value.type()) { case Type::Map: { - auto members = response.release(); + auto members = value.release(); - _concept->start_object(); + push_back(ValueToken::StartObject {}); + push_back(ValueToken::Reserve { members.size() }); for (auto& entry : members) { - _concept->add_member(entry.first); - write(std::move(entry.second)); + push_back(ValueToken::AddMember { std::move(entry.first) }); + append(ValueTokenStream { std::move(entry.second) }); } - _concept->end_object(); + push_back(ValueToken::EndObject {}); break; } case Type::List: { - auto elements = response.release(); + auto elements = value.release(); - _concept->start_array(); + push_back(ValueToken::StartArray {}); + push_back(ValueToken::Reserve { elements.size() }); for (auto& entry : elements) { - write(std::move(entry)); + append(ValueTokenStream { std::move(entry) }); } - _concept->end_arrary(); + push_back(ValueToken::EndArray {}); break; } case Type::String: - case Type::EnumValue: - case Type::ID: { - auto value = response.release(); + auto stringValue = value.release(); - _concept->write_string(value); + push_back(ValueToken::StringValue { std::move(stringValue) }); break; } case Type::Null: { - _concept->write_null(); + push_back(ValueToken::NullValue {}); break; } case Type::Boolean: { - _concept->write_bool(response.get()); + push_back(ValueToken::BoolValue { value.get() }); break; } case Type::Int: { - _concept->write_int(response.get()); + push_back(ValueToken::IntValue { value.get() }); break; } case Type::Float: { - _concept->write_float(response.get()); + push_back(ValueToken::FloatValue { value.get() }); + break; + } + + case Type::EnumValue: + { + auto enumValue = value.release(); + + push_back(ValueToken::EnumValue { std::move(enumValue) }); + break; + } + + case Type::ID: + { + auto idValue = value.release(); + + push_back(ValueToken::IdValue { std::move(idValue) }); break; } case Type::Scalar: { - write(response.release()); + append(ValueTokenStream { value.release() }); break; } default: { - _concept->write_null(); + push_back(ValueToken::NullValue {}); break; } } } +void ValueTokenStream::append(ValueTokenStream&& other) +{ + _tokens.splice(_tokens.end(), std::move(other._tokens)); +} + +void ValueTokenStream::visit(const std::shared_ptr& visitor) && +{ + for (auto& token : _tokens) + { + std::move(token).visit(visitor); + } + + visitor->complete(); +} + +Value ValueTokenStream::value() && +{ + auto visitor = std::make_shared(); + + std::move(*this).visit(std::make_shared(visitor)); + + return visitor->value(); +} + } // namespace graphql::response diff --git a/src/RapidJSONResponse.cpp b/src/RapidJSONResponse.cpp index e7067f61..abdcaee4 100644 --- a/src/RapidJSONResponse.cpp +++ b/src/RapidJSONResponse.cpp @@ -16,7 +16,7 @@ namespace graphql::response { -class StreamWriter +class StreamWriter : public std::enable_shared_from_this { public: StreamWriter(rapidjson::StringBuffer& buffer) @@ -24,12 +24,23 @@ class StreamWriter { } + void add_value(std::shared_ptr&& value) + { + auto writer = std::make_shared(shared_from_this()); + + ValueTokenStream(Value { *value }).visit(writer); + } + + void reserve(std::size_t /* count */) + { + } + void start_object() { _writer.StartObject(); } - void add_member(const std::string& key) + void add_member(std::string&& key) { _writer.Key(key.c_str()); } @@ -44,36 +55,50 @@ class StreamWriter _writer.StartArray(); } - void end_arrary() + void end_array() { _writer.EndArray(); } - void write_null() + void add_null() { _writer.Null(); } - void write_string(const std::string& value) + void add_string(std::string&& value) { _writer.String(value.c_str()); } - void write_bool(bool value) + void add_enum(std::string&& value) + { + add_string(std::move(value)); + } + + void add_id(IdType&& value) + { + add_string(value.release()); + } + + void add_bool(bool value) { _writer.Bool(value); } - void write_int(int value) + void add_int(int value) { _writer.Int(value); } - void write_float(double value) + void add_float(double value) { _writer.Double(value); } + void complete() + { + } + private: rapidjson::Writer _writer; }; @@ -81,9 +106,10 @@ class StreamWriter std::string toJSON(Value&& response) { rapidjson::StringBuffer buffer; - Writer writer { std::make_unique(buffer) }; + auto writer = std::make_shared(std::make_shared(buffer)); + + ValueTokenStream(std::move(response)).visit(writer); - writer.write(std::move(response)); return buffer.GetString(); } diff --git a/src/TaoCppJSONResponse.cpp b/src/TaoCppJSONResponse.cpp index 341327b8..3616d621 100644 --- a/src/TaoCppJSONResponse.cpp +++ b/src/TaoCppJSONResponse.cpp @@ -13,7 +13,7 @@ namespace graphql::response { -class StreamWriter +class StreamWriter : public std::enable_shared_from_this { public: StreamWriter(std::ostream& stream) @@ -21,13 +21,24 @@ class StreamWriter { } + void add_value(std::shared_ptr&& value) + { + auto writer = std::make_shared(shared_from_this()); + + ValueTokenStream(Value { *value }).visit(writer); + } + + void reserve(std::size_t /* count */) + { + } + void start_object() { _scopeStack.push_back(Scope::Object); _writer.begin_object(); } - void add_member(const std::string& key) + void add_member(std::string&& key) { _writer.key(key); } @@ -45,43 +56,57 @@ class StreamWriter _writer.begin_array(); } - void end_arrary() + void end_array() { _writer.end_array(); _scopeStack.pop_back(); end_value(); } - void write_null() + void add_null() { _writer.null(); end_value(); } - void write_string(const std::string& value) + void add_string(std::string&& value) { _writer.string(value); end_value(); } - void write_bool(bool value) + void add_enum(std::string&& value) + { + add_string(std::move(value)); + } + + void add_id(IdType&& value) + { + add_string(value.release()); + } + + void add_bool(bool value) { _writer.boolean(value); end_value(); } - void write_int(int value) + void add_int(int value) { _writer.number(static_cast(value)); end_value(); } - void write_float(double value) + void add_float(double value) { _writer.number(value); end_value(); } + void complete() + { + } + private: enum class Scope { @@ -115,8 +140,10 @@ class StreamWriter std::string toJSON(Value&& response) { std::ostringstream stream; - Writer writer { std::make_unique(stream) }; - writer.write(std::move(response)); + auto writer = std::make_shared(std::make_shared(stream)); + + ValueTokenStream(std::move(response)).visit(writer); + return stream.str(); }