From be112636c522ccb8945c744d0e94df9abee83cd7 Mon Sep 17 00:00:00 2001 From: Bill Avery Date: Wed, 18 Sep 2024 15:30:47 -0700 Subject: [PATCH] feat(sample): add non-trivial async field resolvers to proxy client --- samples/proxy/client.cpp | 128 ++++++++++++++++++-- samples/proxy/query/ProxyClient.cpp | 34 +++++- samples/proxy/query/ProxyClient.h | 13 +- samples/proxy/query/query.graphql | 5 +- samples/proxy/schema/ProxySchema.cpp | 3 + samples/proxy/schema/ProxySchema.h | 2 + samples/proxy/schema/ProxySchema.ixx | 2 + samples/proxy/schema/QueryObject.cpp | 5 +- samples/proxy/schema/QueryObject.h | 8 +- samples/proxy/schema/ResultsObject.cpp | 91 ++++++++++++++ samples/proxy/schema/ResultsObject.h | 154 ++++++++++++++++++++++++ samples/proxy/schema/ResultsObject.ixx | 16 +++ samples/proxy/schema/proxy_schema_files | 1 + samples/proxy/schema/schema.graphql | 7 +- 14 files changed, 450 insertions(+), 19 deletions(-) create mode 100644 samples/proxy/schema/ResultsObject.cpp create mode 100644 samples/proxy/schema/ResultsObject.h create mode 100644 samples/proxy/schema/ResultsObject.ixx diff --git a/samples/proxy/client.cpp b/samples/proxy/client.cpp index d8be65f5..61893e72 100644 --- a/samples/proxy/client.cpp +++ b/samples/proxy/client.cpp @@ -5,6 +5,7 @@ #include "schema/ProxySchema.h" #include "schema/QueryObject.h" +#include "schema/ResultsObject.h" #include "graphqlservice/JSONResponse.h" @@ -21,6 +22,7 @@ #include #include +#include #include #include #include @@ -45,13 +47,111 @@ constexpr auto c_port = "8080"sv; constexpr auto c_target = "/graphql"sv; constexpr int c_version = 11; // HTTP 1.1 +struct AsyncIoWorker : service::RequestState +{ + AsyncIoWorker() + : worker { std::make_shared() } + { + } + + const service::await_async worker; +}; + +class Results +{ +public: + explicit Results(response::Value&& data, std::vector errors) noexcept; + + service::AwaitableScalar> getData( + service::FieldParams&& fieldParams) const; + service::AwaitableScalar>>> getErrors( + service::FieldParams&& fieldParams) const; + +private: + mutable response::Value m_data; + mutable std::vector m_errors; +}; + +Results::Results(response::Value&& data, std::vector errors) noexcept + : m_data { std::move(data) } + , m_errors { std::move(errors) } +{ +} + +service::AwaitableScalar> Results::getData( + service::FieldParams&& fieldParams) const +{ + auto asyncIoWorker = std::static_pointer_cast(fieldParams.state); + auto data = std::move(m_data); + + // Jump to a worker thread for the resolver where we can run a separate I/O context without + // blocking the I/O context in Query::getRelay. This simulates how you might fan out to + // additional async I/O tasks for sub-field resolvers. + co_await asyncIoWorker->worker; + + net::io_context ioc; + auto future = net::co_spawn( + ioc, + [](response::Value&& data) -> net::awaitable> { + co_return (data.type() == response::Type::Null) + ? std::nullopt + : std::make_optional(response::toJSON(std::move(data))); + }(std::move(data)), + net::use_future); + + ioc.run(); + + co_return future.get(); +} + +service::AwaitableScalar>>> Results::getErrors( + service::FieldParams&& fieldParams) const +{ + auto asyncIoWorker = std::static_pointer_cast(fieldParams.state); + auto errors = std::move(m_errors); + + // Jump to a worker thread for the resolver where we can run a separate I/O context without + // blocking the I/O context in Query::getRelay. This simulates how you might fan out to + // additional async I/O tasks for sub-field resolvers. + co_await asyncIoWorker->worker; + + net::io_context ioc; + auto future = net::co_spawn( + ioc, + [](std::vector errors) + -> net::awaitable>>> { + if (errors.empty()) + { + co_return std::nullopt; + } + + std::vector> results { errors.size() }; + + std::transform(errors.begin(), + errors.end(), + results.begin(), + [](auto& error) noexcept -> std::optional { + return error.message.empty() + ? std::nullopt + : std::make_optional(std::move(error.message)); + }); + + co_return std::make_optional(results); + }(std::move(errors)), + net::use_future); + + ioc.run(); + + co_return future.get(); +} + class Query { public: explicit Query(std::string_view host, std::string_view port, std::string_view target, int version) noexcept; - std::future> getRelay(std::string&& queryArg, + std::future> getRelay(std::string&& queryArg, std::optional&& operationNameArg, std::optional&& variablesArg) const; @@ -73,7 +173,7 @@ Query::Query( // Based on: // https://www.boost.org/doc/libs/1_82_0/libs/beast/example/http/client/awaitable/http_client_awaitable.cpp -std::future> Query::getRelay(std::string&& queryArg, +std::future> Query::getRelay(std::string&& queryArg, std::optional&& operationNameArg, std::optional&& variablesArg) const { response::Value payload { response::Type::Map }; @@ -99,7 +199,7 @@ std::future> Query::getRelay(std::string&& queryArg, const char* port, const char* target, int version, - std::string requestBody) -> net::awaitable> { + std::string requestBody) -> net::awaitable> { // These objects perform our I/O. They use an executor with a default completion token // of use_awaitable. This makes our code easy, but will use exceptions as the default // error handling, i.e. if the connection drops, we might see an exception. @@ -150,7 +250,10 @@ std::future> Query::getRelay(std::string&& queryArg, throw boost::system::system_error(ec, "shutdown"); } - co_return std::make_optional(std::move(res.body())); + auto [data, errors] = client::parseServiceResponse(response::parseJSON(res.body())); + + co_return std::make_shared( + std::make_shared(std::move(data), std::move(errors))); }(m_host.c_str(), m_port.c_str(), m_target.c_str(), m_version, std::move(requestBody)), net::use_future); @@ -179,14 +282,25 @@ int main(int argc, char** argv) auto variables = serializeVariables( { input, ((argc > 1) ? std::make_optional(argv[1]) : std::nullopt) }); auto launch = service::await_async { std::make_shared() }; + auto state = std::make_shared(); auto serviceResponse = client::parseServiceResponse( - service->resolve({ query, GetOperationName(), std::move(variables), launch }).get()); + service->resolve({ query, GetOperationName(), std::move(variables), launch, state }) + .get()); auto result = client::query::relayQuery::parseResponse(std::move(serviceResponse.data)); auto errors = std::move(serviceResponse.errors); - if (result.relay) + if (result.relay.data) + { + std::cout << "Data: " << *result.relay.data << std::endl; + } + + if (result.relay.errors) { - std::cout << *result.relay << std::endl; + for (const auto& message : *result.relay.errors) + { + std::cerr << "Remote Error: " + << (message ? std::string_view { *message } : ""sv) << std::endl; + } } if (!errors.empty()) diff --git a/samples/proxy/query/ProxyClient.cpp b/samples/proxy/query/ProxyClient.cpp index 6124a0ac..30ae06e1 100644 --- a/samples/proxy/query/ProxyClient.cpp +++ b/samples/proxy/query/ProxyClient.cpp @@ -26,7 +26,10 @@ const std::string& GetRequestText() noexcept # Licensed under the MIT License. query relayQuery($query: String!, $operationName: String, $variables: String) { - relay(query: $query, operationName: $operationName, variables: $variables) + relay(query: $query, operationName: $operationName, variables: $variables) { + data + errors + } } )gql"s; @@ -51,6 +54,33 @@ const peg::ast& GetRequestObject() noexcept using namespace proxy; +template <> +query::relayQuery::Response::relay_Results Response::parse(response::Value&& response) +{ + query::relayQuery::Response::relay_Results result; + + if (response.type() == response::Type::Map) + { + auto members = response.release(); + + for (auto& member : members) + { + if (member.first == R"js(data)js"sv) + { + result.data = ModifiedResponse::parse(std::move(member.second)); + continue; + } + if (member.first == R"js(errors)js"sv) + { + result.errors = ModifiedResponse::parse(std::move(member.second)); + continue; + } + } + } + + return result; +} + namespace query::relayQuery { const std::string& GetOperationName() noexcept @@ -83,7 +113,7 @@ Response parseResponse(response::Value&& response) { if (member.first == R"js(relay)js"sv) { - result.relay = ModifiedResponse::parse(std::move(member.second)); + result.relay = ModifiedResponse::parse(std::move(member.second)); continue; } } diff --git a/samples/proxy/query/ProxyClient.h b/samples/proxy/query/ProxyClient.h index a1464f21..a2878c2f 100644 --- a/samples/proxy/query/ProxyClient.h +++ b/samples/proxy/query/ProxyClient.h @@ -32,7 +32,10 @@ namespace graphql::client { /// # Licensed under the MIT License. /// /// query relayQuery($query: String!, $operationName: String, $variables: String) { -/// relay(query: $query, operationName: $operationName, variables: $variables) +/// relay(query: $query, operationName: $operationName, variables: $variables) { +/// data +/// errors +/// } /// } /// namespace proxy { @@ -64,7 +67,13 @@ struct [[nodiscard("unnecessary construction")]] Variables struct [[nodiscard("unnecessary construction")]] Response { - std::optional relay {}; + struct [[nodiscard("unnecessary construction")]] relay_Results + { + std::optional data {}; + std::optional>> errors {}; + }; + + relay_Results relay {}; }; [[nodiscard("unnecessary conversion")]] Response parseResponse(response::Value&& response); diff --git a/samples/proxy/query/query.graphql b/samples/proxy/query/query.graphql index 9d75a165..54ca8e60 100644 --- a/samples/proxy/query/query.graphql +++ b/samples/proxy/query/query.graphql @@ -2,5 +2,8 @@ # Licensed under the MIT License. query relayQuery($query: String!, $operationName: String, $variables: String) { - relay(query: $query, operationName: $operationName, variables: $variables) + relay(query: $query, operationName: $operationName, variables: $variables) { + data + errors + } } diff --git a/samples/proxy/schema/ProxySchema.cpp b/samples/proxy/schema/ProxySchema.cpp index 0a6dc2f9..a4ef637d 100644 --- a/samples/proxy/schema/ProxySchema.cpp +++ b/samples/proxy/schema/ProxySchema.cpp @@ -35,8 +35,11 @@ void AddTypesToSchema(const std::shared_ptr& schema) { auto typeQuery = schema::ObjectType::Make(R"gql(Query)gql"sv, R"md()md"sv); schema->AddType(R"gql(Query)gql"sv, typeQuery); + auto typeResults = schema::ObjectType::Make(R"gql(Results)gql"sv, R"md()md"sv); + schema->AddType(R"gql(Results)gql"sv, typeResults); AddQueryDetails(typeQuery, schema); + AddResultsDetails(typeResults, schema); schema->AddQueryType(typeQuery); } diff --git a/samples/proxy/schema/ProxySchema.h b/samples/proxy/schema/ProxySchema.h index 77eca72d..9d4e544d 100644 --- a/samples/proxy/schema/ProxySchema.h +++ b/samples/proxy/schema/ProxySchema.h @@ -28,6 +28,7 @@ namespace proxy { namespace object { class Query; +class Results; } // namespace object @@ -50,6 +51,7 @@ class [[nodiscard("unnecessary construction")]] Operations final }; void AddQueryDetails(const std::shared_ptr& typeQuery, const std::shared_ptr& schema); +void AddResultsDetails(const std::shared_ptr& typeResults, const std::shared_ptr& schema); std::shared_ptr GetSchema(); diff --git a/samples/proxy/schema/ProxySchema.ixx b/samples/proxy/schema/ProxySchema.ixx index 274f6963..c0b1b5b2 100644 --- a/samples/proxy/schema/ProxySchema.ixx +++ b/samples/proxy/schema/ProxySchema.ixx @@ -10,12 +10,14 @@ module; export module GraphQL.Proxy.ProxySchema; export import GraphQL.Proxy.QueryObject; +export import GraphQL.Proxy.ResultsObject; export namespace graphql::proxy { using proxy::Operations; using proxy::AddQueryDetails; +using proxy::AddResultsDetails; using proxy::GetSchema; diff --git a/samples/proxy/schema/QueryObject.cpp b/samples/proxy/schema/QueryObject.cpp index b07fe96c..d4dfb635 100644 --- a/samples/proxy/schema/QueryObject.cpp +++ b/samples/proxy/schema/QueryObject.cpp @@ -4,6 +4,7 @@ // WARNING! Do not edit this file manually, your changes will be overwritten. #include "QueryObject.h" +#include "ResultsObject.h" #include "graphqlservice/internal/Introspection.h" @@ -65,7 +66,7 @@ service::AwaitableResolver Query::resolveRelay(service::ResolverParams&& params) auto result = _pimpl->getRelay(service::FieldParams { std::move(selectionSetParams), std::move(directives) }, std::move(argQuery), std::move(argOperationName), std::move(argVariables)); resolverLock.unlock(); - return service::ModifiedResult::convert(std::move(result), std::move(params)); + return service::ModifiedResult::convert(std::move(result), std::move(params)); } service::AwaitableResolver Query::resolve_typename(service::ResolverParams&& params) const @@ -92,7 +93,7 @@ service::AwaitableResolver Query::resolve_type(service::ResolverParams&& params) void AddQueryDetails(const std::shared_ptr& typeQuery, const std::shared_ptr& schema) { typeQuery->AddFields({ - schema::Field::Make(R"gql(relay)gql"sv, R"md()md"sv, std::nullopt, schema->LookupType(R"gql(String)gql"sv), { + schema::Field::Make(R"gql(relay)gql"sv, R"md()md"sv, std::nullopt, schema->WrapType(introspection::TypeKind::NON_NULL, schema->LookupType(R"gql(Results)gql"sv)), { schema::InputValue::Make(R"gql(query)gql"sv, R"md()md"sv, schema->WrapType(introspection::TypeKind::NON_NULL, schema->LookupType(R"gql(String)gql"sv)), R"gql()gql"sv), schema::InputValue::Make(R"gql(operationName)gql"sv, R"md()md"sv, schema->LookupType(R"gql(String)gql"sv), R"gql()gql"sv), schema::InputValue::Make(R"gql(variables)gql"sv, R"md()md"sv, schema->LookupType(R"gql(String)gql"sv), R"gql()gql"sv) diff --git a/samples/proxy/schema/QueryObject.h b/samples/proxy/schema/QueryObject.h index f8973f09..0afd51de 100644 --- a/samples/proxy/schema/QueryObject.h +++ b/samples/proxy/schema/QueryObject.h @@ -16,13 +16,13 @@ namespace methods::QueryHas { template concept getRelayWithParams = requires (TImpl impl, service::FieldParams params, std::string queryArg, std::optional operationNameArg, std::optional variablesArg) { - { service::AwaitableScalar> { impl.getRelay(std::move(params), std::move(queryArg), std::move(operationNameArg), std::move(variablesArg)) } }; + { service::AwaitableObject> { impl.getRelay(std::move(params), std::move(queryArg), std::move(operationNameArg), std::move(variablesArg)) } }; }; template concept getRelay = requires (TImpl impl, std::string queryArg, std::optional operationNameArg, std::optional variablesArg) { - { service::AwaitableScalar> { impl.getRelay(std::move(queryArg), std::move(operationNameArg), std::move(variablesArg)) } }; + { service::AwaitableObject> { impl.getRelay(std::move(queryArg), std::move(operationNameArg), std::move(variablesArg)) } }; }; template @@ -58,7 +58,7 @@ class [[nodiscard("unnecessary construction")]] Query final virtual void beginSelectionSet(const service::SelectionSetParams& params) const = 0; virtual void endSelectionSet(const service::SelectionSetParams& params) const = 0; - [[nodiscard("unnecessary call")]] virtual service::AwaitableScalar> getRelay(service::FieldParams&& params, std::string&& queryArg, std::optional&& operationNameArg, std::optional&& variablesArg) const = 0; + [[nodiscard("unnecessary call")]] virtual service::AwaitableObject> getRelay(service::FieldParams&& params, std::string&& queryArg, std::optional&& operationNameArg, std::optional&& variablesArg) const = 0; }; template @@ -70,7 +70,7 @@ class [[nodiscard("unnecessary construction")]] Query final { } - [[nodiscard("unnecessary call")]] service::AwaitableScalar> getRelay(service::FieldParams&& params, std::string&& queryArg, std::optional&& operationNameArg, std::optional&& variablesArg) const override + [[nodiscard("unnecessary call")]] service::AwaitableObject> getRelay(service::FieldParams&& params, std::string&& queryArg, std::optional&& operationNameArg, std::optional&& variablesArg) const override { if constexpr (methods::QueryHas::getRelayWithParams) { diff --git a/samples/proxy/schema/ResultsObject.cpp b/samples/proxy/schema/ResultsObject.cpp new file mode 100644 index 00000000..444f4b81 --- /dev/null +++ b/samples/proxy/schema/ResultsObject.cpp @@ -0,0 +1,91 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +// WARNING! Do not edit this file manually, your changes will be overwritten. + +#include "ResultsObject.h" + +#include "graphqlservice/internal/Schema.h" + +#include "graphqlservice/introspection/IntrospectionSchema.h" + +#include +#include +#include +#include + +using namespace std::literals; + +namespace graphql::proxy { +namespace object { + +Results::Results(std::unique_ptr pimpl) noexcept + : service::Object{ getTypeNames(), getResolvers() } + , _pimpl { std::move(pimpl) } +{ +} + +service::TypeNames Results::getTypeNames() const noexcept +{ + return { + R"gql(Results)gql"sv + }; +} + +service::ResolverMap Results::getResolvers() const noexcept +{ + return { + { R"gql(data)gql"sv, [this](service::ResolverParams&& params) { return resolveData(std::move(params)); } }, + { R"gql(errors)gql"sv, [this](service::ResolverParams&& params) { return resolveErrors(std::move(params)); } }, + { R"gql(__typename)gql"sv, [this](service::ResolverParams&& params) { return resolve_typename(std::move(params)); } } + }; +} + +void Results::beginSelectionSet(const service::SelectionSetParams& params) const +{ + _pimpl->beginSelectionSet(params); +} + +void Results::endSelectionSet(const service::SelectionSetParams& params) const +{ + _pimpl->endSelectionSet(params); +} + +service::AwaitableResolver Results::resolveData(service::ResolverParams&& params) const +{ + std::unique_lock resolverLock(_resolverMutex); + service::SelectionSetParams selectionSetParams { static_cast(params) }; + auto directives = std::move(params.fieldDirectives); + auto result = _pimpl->getData(service::FieldParams { std::move(selectionSetParams), std::move(directives) }); + resolverLock.unlock(); + + return service::ModifiedResult::convert(std::move(result), std::move(params)); +} + +service::AwaitableResolver Results::resolveErrors(service::ResolverParams&& params) const +{ + std::unique_lock resolverLock(_resolverMutex); + service::SelectionSetParams selectionSetParams { static_cast(params) }; + auto directives = std::move(params.fieldDirectives); + auto result = _pimpl->getErrors(service::FieldParams { std::move(selectionSetParams), std::move(directives) }); + resolverLock.unlock(); + + return service::ModifiedResult::convert(std::move(result), std::move(params)); +} + +service::AwaitableResolver Results::resolve_typename(service::ResolverParams&& params) const +{ + return service::Result::convert(std::string{ R"gql(Results)gql" }, std::move(params)); +} + +} // namespace object + +void AddResultsDetails(const std::shared_ptr& typeResults, const std::shared_ptr& schema) +{ + typeResults->AddFields({ + schema::Field::Make(R"gql(data)gql"sv, R"md()md"sv, std::nullopt, schema->LookupType(R"gql(String)gql"sv)), + schema::Field::Make(R"gql(errors)gql"sv, R"md()md"sv, std::nullopt, schema->WrapType(introspection::TypeKind::LIST, schema->LookupType(R"gql(String)gql"sv))) + }); +} + +} // namespace graphql::proxy diff --git a/samples/proxy/schema/ResultsObject.h b/samples/proxy/schema/ResultsObject.h new file mode 100644 index 00000000..51718f38 --- /dev/null +++ b/samples/proxy/schema/ResultsObject.h @@ -0,0 +1,154 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +// WARNING! Do not edit this file manually, your changes will be overwritten. + +#pragma once + +#ifndef RESULTSOBJECT_H +#define RESULTSOBJECT_H + +#include "ProxySchema.h" + +namespace graphql::proxy::object { +namespace methods::ResultsHas { + +template +concept getDataWithParams = requires (TImpl impl, service::FieldParams params) +{ + { service::AwaitableScalar> { impl.getData(std::move(params)) } }; +}; + +template +concept getData = requires (TImpl impl) +{ + { service::AwaitableScalar> { impl.getData() } }; +}; + +template +concept getErrorsWithParams = requires (TImpl impl, service::FieldParams params) +{ + { service::AwaitableScalar>>> { impl.getErrors(std::move(params)) } }; +}; + +template +concept getErrors = requires (TImpl impl) +{ + { service::AwaitableScalar>>> { impl.getErrors() } }; +}; + +template +concept beginSelectionSet = requires (TImpl impl, const service::SelectionSetParams params) +{ + { impl.beginSelectionSet(params) }; +}; + +template +concept endSelectionSet = requires (TImpl impl, const service::SelectionSetParams params) +{ + { impl.endSelectionSet(params) }; +}; + +} // namespace methods::ResultsHas + +class [[nodiscard("unnecessary construction")]] Results final + : public service::Object +{ +private: + [[nodiscard("unnecessary call")]] service::AwaitableResolver resolveData(service::ResolverParams&& params) const; + [[nodiscard("unnecessary call")]] service::AwaitableResolver resolveErrors(service::ResolverParams&& params) const; + + [[nodiscard("unnecessary call")]] service::AwaitableResolver resolve_typename(service::ResolverParams&& params) const; + + struct [[nodiscard("unnecessary construction")]] Concept + { + virtual ~Concept() = default; + + virtual void beginSelectionSet(const service::SelectionSetParams& params) const = 0; + virtual void endSelectionSet(const service::SelectionSetParams& params) const = 0; + + [[nodiscard("unnecessary call")]] virtual service::AwaitableScalar> getData(service::FieldParams&& params) const = 0; + [[nodiscard("unnecessary call")]] virtual service::AwaitableScalar>>> getErrors(service::FieldParams&& params) const = 0; + }; + + template + struct [[nodiscard("unnecessary construction")]] Model final + : Concept + { + explicit Model(std::shared_ptr pimpl) noexcept + : _pimpl { std::move(pimpl) } + { + } + + [[nodiscard("unnecessary call")]] service::AwaitableScalar> getData(service::FieldParams&& params) const override + { + if constexpr (methods::ResultsHas::getDataWithParams) + { + return { _pimpl->getData(std::move(params)) }; + } + else + { + static_assert(methods::ResultsHas::getData, R"msg(Results::getData is not implemented)msg"); + return { _pimpl->getData() }; + } + } + + [[nodiscard("unnecessary call")]] service::AwaitableScalar>>> getErrors(service::FieldParams&& params) const override + { + if constexpr (methods::ResultsHas::getErrorsWithParams) + { + return { _pimpl->getErrors(std::move(params)) }; + } + else + { + static_assert(methods::ResultsHas::getErrors, R"msg(Results::getErrors is not implemented)msg"); + return { _pimpl->getErrors() }; + } + } + + void beginSelectionSet(const service::SelectionSetParams& params) const override + { + if constexpr (methods::ResultsHas::beginSelectionSet) + { + _pimpl->beginSelectionSet(params); + } + } + + void endSelectionSet(const service::SelectionSetParams& params) const override + { + if constexpr (methods::ResultsHas::endSelectionSet) + { + _pimpl->endSelectionSet(params); + } + } + + private: + const std::shared_ptr _pimpl; + }; + + explicit Results(std::unique_ptr pimpl) noexcept; + + [[nodiscard("unnecessary call")]] service::TypeNames getTypeNames() const noexcept; + [[nodiscard("unnecessary call")]] service::ResolverMap getResolvers() const noexcept; + + void beginSelectionSet(const service::SelectionSetParams& params) const override; + void endSelectionSet(const service::SelectionSetParams& params) const override; + + const std::unique_ptr _pimpl; + +public: + template + explicit Results(std::shared_ptr pimpl) noexcept + : Results { std::unique_ptr { std::make_unique>(std::move(pimpl)) } } + { + } + + [[nodiscard("unnecessary call")]] static constexpr std::string_view getObjectType() noexcept + { + return { R"gql(Results)gql" }; + } +}; + +} // namespace graphql::proxy::object + +#endif // RESULTSOBJECT_H diff --git a/samples/proxy/schema/ResultsObject.ixx b/samples/proxy/schema/ResultsObject.ixx new file mode 100644 index 00000000..7f451cc6 --- /dev/null +++ b/samples/proxy/schema/ResultsObject.ixx @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +// WARNING! Do not edit this file manually, your changes will be overwritten. + +module; + +#include "ResultsObject.h" + +export module GraphQL.Proxy.ResultsObject; + +export namespace graphql::proxy::object { + +using object::Results; + +} // namespace graphql::proxy::object diff --git a/samples/proxy/schema/proxy_schema_files b/samples/proxy/schema/proxy_schema_files index dc325096..b2805af2 100644 --- a/samples/proxy/schema/proxy_schema_files +++ b/samples/proxy/schema/proxy_schema_files @@ -1,2 +1,3 @@ ProxySchema.cpp QueryObject.cpp +ResultsObject.cpp diff --git a/samples/proxy/schema/schema.graphql b/samples/proxy/schema/schema.graphql index 197569ae..fd32b986 100644 --- a/samples/proxy/schema/schema.graphql +++ b/samples/proxy/schema/schema.graphql @@ -2,5 +2,10 @@ # Licensed under the MIT License. type Query { - relay(query: String!, operationName: String, variables: String): String + relay(query: String!, operationName: String, variables: String): Results! +} + +type Results { + data: String + errors: [String] }