d-exclaimation / pioneer

GraphQL for Swift.
https://pioneer.dexclaimation.com
Apache License 2.0
40 stars 8 forks source link

Better typing for payload #113

Closed d-exclaimation closed 1 year ago

d-exclaimation commented 1 year ago

Is your feature request related to a problem? Please describe. It's not a pleasant experience having to work with Map for the WebSocket payload, especially if the user already know what to expect.

Describe the solution you'd like 2 approach:

d-exclaimation commented 1 year ago

A draft on how I could do the second approach:

import enum GraphQL.Map
import class JSONEncoder
import class JSONDecoder

extension Map {
    public func into<T: Decodable>(_ dataType: T.Type) throws -> T {
        let data = try JSONEncoder().encode(self)
        return try JSONDecoder().decode(dataType, from: data)
    }
}

// ...

server.vaporMiddleware(
    websocketContext: { req, payload, gql in
        let myPayload = payload.into(MyPayload.self)
        // do something with it
    }
)

With this I can have somewhat implement the first with

public typealias VaporCustomWebSocketContext<P: Decodable> = @Sendable (Request, P, GraphQLRequest) async throws -> Context

public typealias VaporCustomWebSocketGuard<P: Decodable> = @Sendable (Request, P) async throws -> Void

public func vaporMiddleware<CustomPayload: Decodable>(
    ...,
    websocketContext: VaporCustomWebSocketContext<CustomPayload>,
    websocketGuard: VaporCustomWebSocketGuard<CustomPayload>
) -> VaporGraphQLMiddleware {
    vaporMiddleware(
        ...,
        websocketContext: { req, payload, gql in 
            let custom = payload.into(CustomPayload.self) 
            return try await websocketContext(req, custom, gql)
        },
        websocketGuard: { req, payload in 
            let custom = payload.into(CustomPayload.self) 
            return try await websocketGuard(req, custom)
        }
    )
}

I am not particularly a big fan of the above, as I feel if I were to do the first, I need to somewhat rewrite the WebSocket client so that it doesn't have to re-parse the payload for each websocket operation, maybe that advantage of the websocket guard to compute the payload object.

I think the best way is to implement the Map.into(_) first and later see if I can do the first without adding breaking changes