The resolveField methods generated by schemagen will unpack any arguments
matching the schema from the query and pass those to the getField method
defined by the implementer. However, the implementer might need to inspect
shared state or directives from the query, so the resolveField method
also packs that information into a graphql::service::FieldParams struct and
passes it to every getField method as the first parameter.
This parameter is optional. The type-erased implementation of graphql::service::Object
for each Object type in the schema declares a pair of methods::ObjectHas::getFieldWithParams
and methods::ObjectHas::getField concepts for each field getter. If the implementation
type supports passing the graphql::service::FieldParams struct as the first parameter,
the type-erased object::Object invokes the getFieldWithParams version, otherwise it
drops the parameter and calls the getField version with whatever other field
arguments the schema specified.
The graphql::service::FieldParams struct is declared in GraphQLService.h:
// Pass a common bundle of parameters to all of the generated Object::getField accessors in a
// SelectionSet
struct [[nodiscard]] SelectionSetParams
{
// Context for this selection set.
const ResolverContext resolverContext;
// The lifetime of each of these borrowed references is guaranteed until the future returned
// by the accessor is resolved or destroyed. They are owned by the OperationData shared pointer.
const std::shared_ptr<RequestState>& state;
const Directives& operationDirectives;
const std::shared_ptr<FragmentDefinitionDirectiveStack> fragmentDefinitionDirectives;
// Fragment directives are shared for all fields in that fragment, but they aren't kept alive
// after the call to the last accessor in the fragment. If you need to keep them alive longer,
// you'll need to explicitly copy them into other instances of Directives.
const std::shared_ptr<FragmentSpreadDirectiveStack> fragmentSpreadDirectives;
const std::shared_ptr<FragmentSpreadDirectiveStack> inlineFragmentDirectives;
// Field error path to this selection set.
std::optional<field_path> errorPath;
// Async launch policy for sub-field resolvers.
const await_async launch {};
};
// Pass a common bundle of parameters to all of the generated Object::getField accessors.
struct [[nodiscard]] FieldParams : SelectionSetParams
{
GRAPHQLSERVICE_EXPORT explicit FieldParams(
SelectionSetParams&& selectionSetParams, Directives directives);
// Each field owns its own field-specific directives. Once the accessor returns it will be
// destroyed, but you can move it into another instance of response::Value to keep it alive
// longer.
Directives fieldDirectives;
};The SelectionSetParams::resolverContext enum member informs the getField
accessors about what type of operation is being resolved:
// Resolvers may be called in multiple different Operation contexts.
enum class [[nodiscard]] ResolverContext {
// Resolving a Query operation.
Query,
// Resolving a Mutation operation.
Mutation,
// Adding a Subscription. If you need to prepare to send events for this Subsciption
// (e.g. registering an event sink of your own), this is a chance to do that.
NotifySubscribe,
// Resolving a Subscription event.
Subscription,
// Removing a Subscription. If there are no more Subscriptions registered this is an
// opportunity to release resources which are no longer needed.
NotifyUnsubscribe,
};The SelectionSetParams::state member is a reference to the
std::shared_ptr<graphql::service::RequestState> parameter passed to
Request::resolve (see resolvers.md for more info):
// The RequestState is nullable, but if you have multiple threads processing requests and there's
// any per-request state that you want to maintain throughout the request (e.g. optimizing or
// batching backend requests), you can inherit from RequestState and pass it to Request::resolve to
// correlate the asynchronous/recursive callbacks and accumulate state in it.
struct [[nodiscard]] RequestState : std::enable_shared_from_this<RequestState>
{
virtual ~RequestState() = default;
};Each of the directives members contains the values of the directives and
any of their arguments which were in effect at that scope of the query.
Implementers may inspect those values in the call to getField and alter their
behavior based on those custom directives:
// Directive order matters, and some of them are repeatable. So rather than passing them in a
// response::Value, pass directives in something like the underlying response::MapType which
// preserves the order of the elements without complete uniqueness.
using Directives = std::vector<std::pair<std::string_view, response::Value>>;
// Traversing a fragment spread adds a new set of directives.
using FragmentDefinitionDirectiveStack = std::list<std::reference_wrapper<const Directives>>;
using FragmentSpreadDirectiveStack = std::list<Directives>;As noted in the comments, the fragmentSpreadDirectives and
inlineFragmentDirectives are stacks of directives passed down through nested
inline fragments and fragment spreads. The Directives object for each frame of
the stack is shared accross calls to multiple getField methods in a single fragment,
but they will be popped from the stack when the last field has been visited. The
fieldDirectives member is passed by value and is not shared with other getField
method calls. It's up to the implementer to capture the values in these directives
which they might need for asynchronous evaulation after the call to the current
getField method has returned.
The implementer does not need to capture the values of operationDirectives
or fragmentDefinitionDirectives because those are kept alive until the
operation and all of its std::future results are resolved. Although they
passed by const reference, the reference should always be valid as long as
there's a pending result from the getField call.
The SelectionSetParams::errorPath member should be considered an opaque
implementation detail by client code. It automatically propagates through the
field resolvers, and if there is a schema exception or one of the getField
accessors throws another exception derived from std::exception, the
graphqlservice library will automatically add the resulting path to the error
report, accoring to the spec.
See the Awaitable document for more information about
service::await_async.
- The
getFieldmethods are discussed in more detail in resolvers.md. - Awaitable types are covered in awaitable.md.
- Built-in and custom
directivesare discussed in directives.md. - Subscription resolvers may be called 2 extra times, inside of
subscribeandunsubscribe. See subscriptions.md for more details.