Skip to content

Commit 1dd066e

Browse files
feat!: onWebSocketInit gets access to Request and Context
This will give access to services (like a database or HTTPClient) in order to validate the init message and do things like auth checks.
1 parent 909c978 commit 1dd066e

4 files changed

Lines changed: 39 additions & 8 deletions

File tree

Sources/GraphQLHummingbird/GraphQLConfig.swift

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import Hummingbird
44

55
/// Configuration options for GraphQLHummingbird
66
public struct GraphQLConfig<
7+
Context: RequestContext,
78
WebSocketInit: Equatable & Codable & Sendable,
89
WebSocketInitResult: Sendable
910
>: Sendable {
@@ -31,7 +32,7 @@ public struct GraphQLConfig<
3132
subscriptionProtocols: Set<SubscriptionProtocol> = [.websocket],
3233
websocket: WebSocket = .init(
3334
// Including this strongly-typed argument is required to avoid compiler failures on Swift 6.2.3.
34-
onWebSocketInit: { (_: EmptyWebSocketInit) in }
35+
onWebSocketInit: { (_: EmptyWebSocketInit, _: Request, _: Context) in }
3536
),
3637
additionalValidationRules: [@Sendable (ValidationContext) -> Visitor] = []
3738
) {
@@ -97,14 +98,14 @@ public struct GraphQLConfig<
9798
}
9899

99100
public struct WebSocket: Sendable {
100-
let onWebSocketInit: @Sendable (WebSocketInit) async throws -> WebSocketInitResult
101+
let onWebSocketInit: @Sendable (WebSocketInit, Request, Context) async throws -> WebSocketInitResult
101102

102103
/// GraphQL over WebSocket configuration
103104
/// - Parameter onWebSocketInit: A custom callback run during `connection_init` resolution that allows
104105
/// authorization using the `payload` field of the `connection_init` message.
105106
/// Throw from this closure to indicate that authorization has failed.
106107
public init(
107-
onWebSocketInit: @Sendable @escaping (WebSocketInit) async throws -> WebSocketInitResult = { (_: EmptyWebSocketInit) in }
108+
onWebSocketInit: @Sendable @escaping (WebSocketInit, Request, Context) async throws -> WebSocketInitResult = { (_: EmptyWebSocketInit, _: Request, _: Context) in }
108109
) {
109110
self.onWebSocketInit = onWebSocketInit
110111
}

Sources/GraphQLHummingbird/GraphQLHandler.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ struct GraphQLHandler<
99
>: Sendable {
1010
let schema: GraphQLSchema
1111
let rootValue: any Sendable
12-
let config: GraphQLConfig<WebSocketInit, WebSocketInitResult>
12+
let config: GraphQLConfig<Context, WebSocketInit, WebSocketInitResult>
1313
let computeContext: @Sendable (GraphQLContextComputationInputs<Context, WebSocketInitResult>) async throws -> GraphQLContext
1414
}
1515

Sources/GraphQLHummingbird/Router+graphql.swift

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ public extension RouterMethods {
1919
_ path: RouterPath = "graphql",
2020
schema: GraphQLSchema,
2121
rootValue: any Sendable = (),
22-
config: GraphQLConfig<EmptyWebSocketInit, Void> = .init(),
22+
config: GraphQLConfig<Context, EmptyWebSocketInit, Void> = .init(),
2323
computeContext: @Sendable @escaping (GraphQLContextComputationInputs<Context, Void>) async throws -> GraphQLContext
2424
) -> Self {
2525
// https://github.com/graphql/graphql-over-http/blob/main/spec/GraphQLOverHTTP.md#request
@@ -84,7 +84,7 @@ public extension RouterMethods where Context: WebSocketRequestContext {
8484
_ path: RouterPath = "graphql",
8585
schema: GraphQLSchema,
8686
rootValue: any Sendable = (),
87-
config: GraphQLConfig<WebSocketInit, WebSocketInitResult> = GraphQLConfig<EmptyWebSocketInit, Void>(),
87+
config: GraphQLConfig<Context, WebSocketInit, WebSocketInitResult>,
8888
computeContext: @Sendable @escaping (GraphQLContextComputationInputs<Context, WebSocketInitResult>) async throws -> GraphQLContext
8989
) -> Self {
9090
let handler = GraphQLHandler<Context, GraphQLContext, WebSocketInit, WebSocketInitResult>(
@@ -108,4 +108,34 @@ public extension RouterMethods where Context: WebSocketRequestContext {
108108
}
109109
return self
110110
}
111+
112+
/// Registers a graphql websocket route that responds using the provided schema.
113+
///
114+
/// WebSocket requests support the
115+
/// [`graphql-transport-ws`](https://github.com/enisdenjo/graphql-ws/blob/master/PROTOCOL.md)
116+
/// and [`graphql-ws`](https://github.com/apollographql/subscriptions-transport-ws/blob/master/PROTOCOL.md)
117+
/// subprotocols.
118+
///
119+
/// - Parameters:
120+
/// - path: The route that should respond to GraphQL requests. Both `GET` and `POST` routes are registered.
121+
/// - schema: The GraphQL schema that should be used to respond to requests.
122+
/// - rootValue: The `rootValue` GraphQL execution arg. This is the object passed to the root resolvers.
123+
/// - computeContext: A closure used to compute the GraphQL context from incoming requests. This must be provided.
124+
@discardableResult
125+
func graphqlWebSocket<GraphQLContext: Sendable>(
126+
_ path: RouterPath = "graphql",
127+
schema: GraphQLSchema,
128+
rootValue: any Sendable = (),
129+
computeContext: @Sendable @escaping (GraphQLContextComputationInputs<Context, Void>) async throws -> GraphQLContext
130+
) -> Self {
131+
// This is just an overload that allows not passing `config`, since we cannot use a default argument that
132+
// uses `Context` without getting `error: generic parameter 'Self' could not be inferred` compilation errors
133+
graphqlWebSocket(
134+
path,
135+
schema: schema,
136+
rootValue: rootValue,
137+
config: GraphQLConfig<Context, EmptyWebSocketInit, Void>(),
138+
computeContext: computeContext
139+
)
140+
}
111141
}

Sources/GraphQLHummingbird/WebSocket/GraphQLHandler+handleWebSocket.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ extension GraphQLHandler where Context: WebSocketRequestContext {
3232
let server = GraphQLTransportWS.Server<WebSocketInit, WebSocketInitResult, AsyncThrowingStream<GraphQLResult, Error>>(
3333
messenger: messenger,
3434
onInit: { initPayload in
35-
try await config.websocket.onWebSocketInit(initPayload)
35+
try await config.websocket.onWebSocketInit(initPayload, context.request, context.requestContext)
3636
},
3737
onExecute: { graphQLRequest, initResult in
3838
let graphQLContextComputationInputs = GraphQLContextComputationInputs<Context, WebSocketInitResult>(
@@ -75,7 +75,7 @@ extension GraphQLHandler where Context: WebSocketRequestContext {
7575
let server = GraphQLWS.Server<WebSocketInit, WebSocketInitResult, AsyncThrowingStream<GraphQLResult, Error>>(
7676
messenger: messenger,
7777
onInit: { initPayload in
78-
try await config.websocket.onWebSocketInit(initPayload)
78+
try await config.websocket.onWebSocketInit(initPayload, context.request, context.requestContext)
7979
},
8080
onExecute: { graphQLRequest, initResult in
8181
let graphQLContextComputationInputs = GraphQLContextComputationInputs<Context, WebSocketInitResult>(

0 commit comments

Comments
 (0)