Skip to content

Conversation

@valbers
Copy link
Collaborator

@valbers valbers commented May 5, 2025

Here I fix the graphql-transport-ws implementation.

  • The payload format in Next messages was wrong: it was not the standard { "data": {}, "errors": [] } (i.e.: ExecutionResult) but just the data object directly.
  • Error is now only sent as a response to a Subscribe message and not elsewhere;
  • Fixed bug of our default websockets endpoint /ws path not being set (not related to the graphql-transport-ws subprotocol, but anyway to websockets configuration).

@valbers valbers requested a review from xperiandri May 5, 2025 17:36
Copy link
Collaborator

@xperiandri xperiandri left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have you found a better spec than this one?
https://github.com/enisdenjo/graphql-ws/blob/master/PROTOCOL.md
It would be nice to follow the naming and add references to the spec as we do in AST code

@valbers
Copy link
Collaborator Author

valbers commented May 6, 2025

Have you found a better spec than this one? https://github.com/enisdenjo/graphql-ws/blob/master/PROTOCOL.md It would be nice to follow the naming and add references to the spec as we do in AST code

References to the spec 👍 .

Well, this is the official specification. As far as I know, this subprotocol is anyway something more like a de facto protocol than a standard. Or what do you mean by "better spec"? In the Apollo GraphQL documentation, for example, you can see a reference to it: https://www.apollographql.com/docs/react/data/subscriptions#supported-subscription-protocols.


type RawServerMessage = { Id : string voption; Type : string; Payload : ServerRawPayload voption }

[<JsonFSharpConverter(unionTagName = "type", SkippableOptionFields = SkippableOptionFields.Always)>]
Copy link
Collaborator Author

@valbers valbers May 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Granted, this approach uses less code, but the error messages are less helpful, especially for a consumer who is using a whole different platform than dotnet. See the failing tests, for example:

           ↓ (pos 0)
      Expected: "Property "id" is required for this messag"···
      Actual:   "Missing field for union type FSharp.Data."···
                 ↑ (pos 0)

I think this approach (with JsonFSharpConverter annotations) is fine for application code, i.e. code you'd use in your own internal application, but since FSharp.Data.GraphQL is a library for broader use, I think we should have more customized error messages. So this is the case for us to have our own JSON converter, like in the previous implementation. What do you think?

open FSharp.Data.GraphQL
open FSharp.Data.GraphQL.Extensions

type GQLWebSocketResponse with
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we really need so many overloads right now? One of the things I like about F# is that you can't overload functions, so it feels like a step backwards to use them via classes in F#. I'd prefer to have a record and fill the record accordingly when instantiating it. It's also more visible to reader. I mean: I prefer reading

{ Data = Include data
  Path = Skip
  HasNext = Skip
  Errors = Skip
  Extensions = Skip }

than reading

GQLWebSocketResponse.Direct(data)

because then, as a reader, I can see at a glance what the line is actually about and with GQLWebSocketResponse.Direct(data) it's hidden away from me, so I have to navigate to the code and maybe get lost among the many overload definitions.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought it is too much to fill and transform and it adds unnecessary cognitive load in the place of usage

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I get it, but, as said, I think this is one of those things which look good at a first sight, but have higher costs in the long run. Anyway, this is just my opinion.

Comment on lines 12 to 27
type GQLResponse =
{ DocumentId: int
Data : Output Skippable
Errors : GQLProblemDetails list Skippable }
static member Direct(documentId, data, errors) =
{ DocumentId = documentId
Data = Include data
Errors = Skippable.ofList errors }
static member Stream(documentId) =
{ DocumentId = documentId
Data = Include null
Errors = Skip }
static member RequestError(documentId, errors) =
{ DocumentId = documentId
Data = Skip
Errors = Include errors }
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think about separating concerns for the sake of pull requests? I mean, this pull request was just supposed to fix the graphql-transport-ws implementation, but with this change we're introducing an important breaking change that affects everything else...
I think we should handle those changes in a PR for redesigning the global GQLResponse format.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, let's return your branch to your changes and mine will go next

yield new RawServerMessageConverter ()
yield! additionalConverters
}
let jsonFSharpOptions = jsonFSharpOptions.WithSkippableOptionFields (SkippableOptionFields.Always, true)
Copy link
Collaborator Author

@valbers valbers May 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can it be the reason why some tests are failing (tests in which the response object now doesn't contain properties that have null values anymore)? I don't know how this configuration here for websockets would leak into these seemingly unrelated tests, though...

@xperiandri xperiandri closed this May 8, 2025
@xperiandri xperiandri reopened this May 8, 2025
valbers added 4 commits May 8, 2025 22:10
  Data should be in property "data" and errors should be in property
"errors"
  "Error" is only intended as a response for "Subscribe" and includes of
course any exception thrown during processing of "Subscribe".
  It was not being set because in the calls in the overloads it was
explicitly being set to `null`.
  Because it's path and not a URL :)
@xperiandri xperiandri force-pushed the fix-graphql-transport-ws-impl branch 2 times, most recently from 5c9cac1 to 2f0ee57 Compare May 8, 2025 18:12
@xperiandri xperiandri changed the title Fix graphql-transport-ws implementation Fixed graphql-transport-ws implementation May 8, 2025
@xperiandri xperiandri merged commit bbc6d3e into fsprojects:dev May 18, 2025
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants