Skip to content
This repository has been archived by the owner on Apr 1, 2024. It is now read-only.

Commit

Permalink
WIP: "deserializing" client subscribe request
Browse files Browse the repository at this point in the history
  Coming next: variables (and extensions?)"
  • Loading branch information
valbers committed Feb 7, 2023
1 parent 04f4b14 commit 4a0957e
Show file tree
Hide file tree
Showing 5 changed files with 362 additions and 13 deletions.
27 changes: 26 additions & 1 deletion src/Main/JsonConverters.fs
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,40 @@ type GraphQLWsMessageConverter() =
else
failwithf "was expecting a value for property \"%s\"" propertyName

let readSubscribePayload (reader : byref<Utf8JsonReader>) : GraphQLWsMessageSubscribePayloadRaw =
let mutable operationName : string option = None
let mutable query : string option = None
let mutable variables : string option = None
let mutable extensions : string option = None
while reader.Read() && (not <| reader.TokenType.Equals(JsonTokenType.EndObject)) do
match reader.GetString() with
| "operationName" ->
operationName <- readPropertyValueAsAString "operationName" &reader
| "query" ->
query <- readPropertyValueAsAString "query" &reader
| "variables" ->
variables <- readPropertyValueAsAString "variables" &reader
| "extensions" ->
extensions <- readPropertyValueAsAString "extensions" &reader
| other ->
failwithf "unexpected property \"%s\" in payload object" other
{ OperationName = operationName
Query = query
Variables = variables
Extensions = extensions }

let readPayload (reader : byref<Utf8JsonReader>) : GraphQLWsMessagePayloadRaw option =
if reader.Read() then
if reader.TokenType.Equals(JsonTokenType.String) then
StringPayload (reader.GetString())
|> Some
elif reader.TokenType.Equals(JsonTokenType.StartObject) then
SubscribePayload (readSubscribePayload &reader)
|> Some
elif reader.TokenType.Equals(JsonTokenType.Null) then
failwith "was expecting a value for property \"payload\""
else
None
failwith "Not implemented yet. Uh-oh, this is a bug."
else
failwith "was expecting a value for property \"payload\""

Expand Down
31 changes: 26 additions & 5 deletions src/Main/WebSocketMessagesMappers.fs
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
namespace GraphQLTransportWS

module GraphQLWsMessageRawMapping =
open FSharp.Data.GraphQL

let requireId (raw : GraphQLWsMessageRaw) : string =
match raw.Id with
| Some s -> s
| None -> failwith "property \"id\" is required but was not there"

let requirePayloadToBeAnOptionalString (payload : GraphQLWsMessagePayloadRaw option) : string option =
match payload with
Expand All @@ -10,12 +16,23 @@ module GraphQLWsMessageRawMapping =
| _ -> failwith "payload was expected to be a string, but it wasn't"
| None -> None

let requireId (raw : GraphQLWsMessageRaw) : string =
match raw.Id with
| Some s -> s
| None -> failwith "property \"id\" is required but was not there"
let requireSubscribePayload (executor : Executor<'a>) (payload : GraphQLWsMessagePayloadRaw option) : GraphQLQuery =
match payload with
| Some p ->
match p with
| SubscribePayload rawSubsPayload ->
match rawSubsPayload.Query with
| Some query ->
{ ExecutionPlan = executor.CreateExecutionPlan(query)
Variables = Map.empty }
| None ->
failwith "there was no query in subscribe message!"
| _ ->
failwith "payload was expected to be a subscribe payload object, but it wasn't."
| None ->
failwith "payload is required for this message, but none was available"

let toWebSocketClientMessage (raw : GraphQLWsMessageRaw) : WebSocketClientMessage =
let toWebSocketClientMessage (executor : Executor<'a>) (raw : GraphQLWsMessageRaw) : WebSocketClientMessage =
match raw.Type with
| None ->
failwithf "property \"type\" was not found in the client message"
Expand All @@ -27,6 +44,10 @@ module GraphQLWsMessageRawMapping =
ClientPong (raw.Payload |> requirePayloadToBeAnOptionalString)
| Some "complete" ->
ClientComplete (raw |> requireId)
| Some "subscribe" ->
let id = raw |> requireId
let payload = raw.Payload |> requireSubscribePayload executor
Subscribe (id, payload)
| Some other ->
failwithf "type \"%s\" is not supported as a client message type" other

1 change: 1 addition & 0 deletions tests/unit-tests/Main.UnitTests.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
</PropertyGroup>

<ItemGroup>
<Compile Include="TestSchema.fs" />
<Compile Include="SerializationTests.fs" />
<Compile Include="Program.fs" />
</ItemGroup>
Expand Down
87 changes: 80 additions & 7 deletions tests/unit-tests/SerializationTests.fs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
module Tests

open UnitTest
open GraphQLTransportWS
open System
open System.Text.Json
open Xunit
open FSharp.Data.GraphQL.Ast

[<Fact>]
let ``Serializes ServerPing correctly`` () =
Expand Down Expand Up @@ -37,7 +39,9 @@ let ``Deserializes ConnectionInit correctly`` () =
let input = "{\"type\":\"connection_init\"}"

let resultRaw = JsonSerializer.Deserialize<GraphQLWsMessageRaw>(input, serializerOptions)
let result = resultRaw |> GraphQLWsMessageRawMapping.toWebSocketClientMessage
let result =
resultRaw
|> GraphQLWsMessageRawMapping.toWebSocketClientMessage (TestSchema.executor)

match result with
| ConnectionInit None -> () // <-- expected
Expand All @@ -52,7 +56,9 @@ let ``Deserializes ConnectionInit with payload correctly`` () =
let input = "{\"type\":\"connection_init\", \"payload\":\"hello\"}"

let resultRaw = JsonSerializer.Deserialize<GraphQLWsMessageRaw>(input, serializerOptions)
let result = resultRaw |> GraphQLWsMessageRawMapping.toWebSocketClientMessage
let result =
resultRaw
|> GraphQLWsMessageRawMapping.toWebSocketClientMessage (TestSchema.executor)

match result with
| ConnectionInit (Some "hello") -> () // <-- expected
Expand All @@ -67,7 +73,9 @@ let ``Deserializes ClientPing correctly`` () =
let input = "{\"type\":\"ping\"}"

let resultRaw = JsonSerializer.Deserialize<GraphQLWsMessageRaw>(input, serializerOptions)
let result = resultRaw |> GraphQLWsMessageRawMapping.toWebSocketClientMessage
let result =
resultRaw
|> GraphQLWsMessageRawMapping.toWebSocketClientMessage (TestSchema.executor)

match result with
| ClientPing None -> () // <-- expected
Expand All @@ -82,7 +90,9 @@ let ``Deserializes ClientPing with payload correctly`` () =
let input = "{\"type\":\"ping\", \"payload\":\"ping!\"}"

let resultRaw = JsonSerializer.Deserialize<GraphQLWsMessageRaw>(input, serializerOptions)
let result = resultRaw |> GraphQLWsMessageRawMapping.toWebSocketClientMessage
let result =
resultRaw
|> GraphQLWsMessageRawMapping.toWebSocketClientMessage (TestSchema.executor)

match result with
| ClientPing (Some "ping!") -> () // <-- expected
Expand All @@ -97,7 +107,9 @@ let ``Deserializes ClientPong correctly`` () =
let input = "{\"type\":\"pong\"}"

let resultRaw = JsonSerializer.Deserialize<GraphQLWsMessageRaw>(input, serializerOptions)
let result = resultRaw |> GraphQLWsMessageRawMapping.toWebSocketClientMessage
let result =
resultRaw
|> GraphQLWsMessageRawMapping.toWebSocketClientMessage (TestSchema.executor)

match result with
| ClientPong None -> () // <-- expected
Expand All @@ -112,7 +124,9 @@ let ``Deserializes ClientPong with payload correctly`` () =
let input = "{\"type\":\"pong\", \"payload\": \"pong!\"}"

let resultRaw = JsonSerializer.Deserialize<GraphQLWsMessageRaw>(input, serializerOptions)
let result = resultRaw |> GraphQLWsMessageRawMapping.toWebSocketClientMessage
let result =
resultRaw
|> GraphQLWsMessageRawMapping.toWebSocketClientMessage (TestSchema.executor)

match result with
| ClientPong (Some "pong!") -> () // <-- expected
Expand All @@ -127,10 +141,69 @@ let ``Deserializes ClientComplete correctly``() =
let input = "{\"id\": \"65fca2b5-f149-4a70-a055-5123dea4628f\", \"type\":\"complete\"}"

let resultRaw = JsonSerializer.Deserialize<GraphQLWsMessageRaw>(input, serializerOptions)
let result = resultRaw |> GraphQLWsMessageRawMapping.toWebSocketClientMessage
let result =
resultRaw
|> GraphQLWsMessageRawMapping.toWebSocketClientMessage (TestSchema.executor)

match result with
| ClientComplete id ->
Assert.Equal("65fca2b5-f149-4a70-a055-5123dea4628f", id)
| other ->
Assert.Fail(sprintf "unexpected actual value: '%A'" other)

[<Fact>]
let ``Deserializes client subscription correctly`` () =
let serializerOptions = new JsonSerializerOptions()
serializerOptions.Converters.Add(new GraphQLWsMessageConverter())

let input =
"""{
"id": "b5d4d2ff-d262-4882-a7b9-d6aec5e4faa6",
"type": "subscribe",
"payload" : {
"query": "subscription { watchMoon(id: \"1\") { id name isMoon } }"
}
}
"""

let resultRaw = JsonSerializer.Deserialize<GraphQLWsMessageRaw>(input, serializerOptions)
let result =
resultRaw
|> GraphQLWsMessageRawMapping.toWebSocketClientMessage (TestSchema.executor)

match result with
| Subscribe (id, payload) ->
Assert.Equal("b5d4d2ff-d262-4882-a7b9-d6aec5e4faa6", id)
Assert.Equal(1, payload.ExecutionPlan.Operation.SelectionSet.Length)
let watchMoonSelection = payload.ExecutionPlan.Operation.SelectionSet |> List.head
match watchMoonSelection with
| Field watchMoonField ->
Assert.Equal("watchMoon", watchMoonField.Name)
Assert.Equal(1, watchMoonField.Arguments.Length)
let watchMoonFieldArg = watchMoonField.Arguments |> List.head
Assert.Equal("id", watchMoonFieldArg.Name)
match watchMoonFieldArg.Value with
| StringValue theValue ->
Assert.Equal("1", theValue)
| other ->
Assert.Fail(sprintf "expected arg to be a StringValue, but it was: %A" other)
Assert.Equal(3, watchMoonField.SelectionSet.Length)
match watchMoonField.SelectionSet.[0] with
| Field firstField ->
Assert.Equal("id", firstField.Name)
| other ->
Assert.Fail(sprintf "expected field to be a Field, but it was: %A" other)
match watchMoonField.SelectionSet.[1] with
| Field secondField ->
Assert.Equal("name", secondField.Name)
| other ->
Assert.Fail(sprintf "expected field to be a Field, but it was: %A" other)
match watchMoonField.SelectionSet.[2] with
| Field thirdField ->
Assert.Equal("isMoon", thirdField.Name)
| other ->
Assert.Fail(sprintf "expected field to be a Field, but it was: %A" other)
| somethingElse ->
Assert.Fail(sprintf "expected it to be a field, but it was: %A" somethingElse)
| other ->
Assert.Fail(sprintf "unexpected actual value: '%A" other)
Loading

0 comments on commit 4a0957e

Please sign in to comment.