Context
GraphQLSwift/GraphQL (opens in a new tab) allow a custom data structure to be passed into all of your field resolver functions.
The context can be in any form, but it is useful for:
- Dependencies injection to each resolver function in the schema
- Providing or deriving an operation specific value
Operation specific contextual value
As for example, your schema resolve prices of coffee for your users which could have subscribed to a membership plan with different benefits and discount for each price of a coffee.
func price(ctx: Any?, args: AmountArgs) async -> Int {
let discount = // What to put here, how to figure out membership
return price * args.amount
}
One approach is to add this value within the arguments, but that comes with issues of security and that fact that this value has to be explicitly given.
Context for derived values
The context value can be used to provide additional values specific to the request that are derived outside the GraphQL request.
struct Context {
var user: User?
}
Using context from resolver
This context struct can be in any form and include any values that may come in useful to be derived outside of the GraphQL request. For this example, it will contain the user who are performing the query.
func price(ctx: Context, args: AmountArgs) async -> Int {
guard let membership = ctx.user?.membership else {
return price * args.amount
}
switch membership {
case .silver(let years):
return price * min(0.75, 1 - (years / 10)) * args.amount
case .gold(let years):
return price * min(0.60, 1 - (years / 10))) * args.amount
case .platinum:
return price * 0.5 * args.amount
}
}
Dependencies injection
Another use case for context is to perform dependency injection to all resolvers.
As an example, you have created a PubSub (opens in a new tab) implementation that uses Redis, which required to be initialised on main.swift
.
import Vapor
import Pioneer
let app = Application(...)
let server = Pioneer(...)
let pubsub: PubSub = MyPubSub(app.redis)
pubsub.start()
Context for dependencies
Passing down this to each one the resolver would require dependency injection, which can be done through Context.
struct Context {
var pubsub: PubSub
}
Passing down through a context builder for Vapor (opens in a new tab)
This is just an example passing down when using Vapor (opens in a new tab). Context are not tied to Vapor (opens in a new tab).
import Vapor
import Pioneer
let app = Application(...)
let server = Pioneer(...)
let pubsub: PubSub = MyPubSub(app.redis)
pubsub.start()
app.middleware.use(
server.vaporMiddleware(
context: { _, _ in
Context(pubsub: pubsub)
}
)
)
Using dependencies from resolver
All of the resolvers would have access to this context and can get the pubsub
property from it and use it.
func onOrder(ctx: Context, args: NoArguments) -> EventStream<Order> {
ctx.pubsub.asyncStream(Order.self, for: "*:order").toEventStream()
}