Skip to content

Commit

Permalink
refact: moving websockets code to .Shared to prepare for client impl.
Browse files Browse the repository at this point in the history
  We intend to implement the client part now. It's useful to share
contract-related code between client and server implementations.
  • Loading branch information
valbers committed Nov 21, 2024
1 parent b904b04 commit 1f51be0
Show file tree
Hide file tree
Showing 40 changed files with 202 additions and 187 deletions.
4 changes: 0 additions & 4 deletions src/FSharp.Data.GraphQL.Server.AspNetCore/Exceptions.fs

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,6 @@

<ItemGroup>
<Compile Include="Helpers.fs" />
<Compile Include="Exceptions.fs" />
<Compile Include="GQLRequest.fs" />
<Compile Include="Messages.fs" />
<Compile Include="Serialization/JsonConverters.fs" />
<Compile Include="Serialization\JSON.fs" />
<Compile Include="GraphQLOptions.fs" />
<Compile Include="GraphQLSubscriptionsManagement.fs" />
<Compile Include="GraphQLWebsocketMiddleware.fs" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ open FSharp.Data.GraphQL
open FsToolkit.ErrorHandling

open FSharp.Data.GraphQL.Server
open FSharp.Data.GraphQL.Shared

type DefaultGraphQLRequestHandler<'Root> (
httpContextAccessor : IHttpContextAccessor,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module internal FSharp.Data.GraphQL.Server.AspNetCore.GraphQLSubscriptionsManagement

open FSharp.Data.GraphQL.Server.AspNetCore.WebSockets
open FSharp.Data.GraphQL.Shared.Websockets

let addSubscription
(id : SubscriptionId, unsubscriber : SubscriptionUnsubscriber, onUnsubscribe : OnUnsubscribeAction)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ open FsToolkit.ErrorHandling

open FSharp.Data.GraphQL
open FSharp.Data.GraphQL.Execution
open FSharp.Data.GraphQL.Server.AspNetCore.WebSockets
open FSharp.Data.GraphQL.Shared.Websockets

type GraphQLWebSocketMiddleware<'Root>
(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ module ServiceCollectionExtensions =
SchemaExecutor = executor
RootFactory = rootFactory
ReadBufferSize = GraphQLOptionsDefaults.ReadBufferSize
SerializerOptions = Json.getWSSerializerOptions additionalConverters
SerializerOptions = Shared.Json.getWSSerializerOptions additionalConverters
WebsocketOptions = {
EndpointUrl = endpointUrl
ConnectionInitTimeout = TimeSpan.FromMilliseconds (GraphQLOptionsDefaults.WebSocketConnectionInitTimeoutInMs)
Expand Down Expand Up @@ -67,7 +67,7 @@ module ServiceCollectionExtensions =
// Use if you want to return HTTP responses using minmal APIs IResult interface
.Configure<HttpClientJsonOptions>(
Action<HttpClientJsonOptions>(fun o ->
Json.configureDefaultSerializerOptions additionalConverters o.SerializerOptions
Shared.Json.configureDefaultSerializerOptions additionalConverters o.SerializerOptions
)
)
.AddSingleton<IOptionsFactory<GraphQLOptions<'Root>>>(
Expand Down
142 changes: 1 addition & 141 deletions src/FSharp.Data.GraphQL.Server/Execution.fs
Original file line number Diff line number Diff line change
Expand Up @@ -13,158 +13,18 @@ open FSharp.Data.GraphQL.Ast
open FSharp.Data.GraphQL.Errors
open FSharp.Data.GraphQL.Extensions
open FSharp.Data.GraphQL.Helpers
open FSharp.Data.GraphQL.Shared
open FSharp.Data.GraphQL.Types
open FSharp.Data.GraphQL.Types.Patterns
open FSharp.Data.GraphQL

type Output = IDictionary<string, obj>

let (|RequestError|Direct|Deferred|Stream|) (response : GQLExecutionResult) =
match response.Content with
| RequestError errs -> RequestError errs
| Direct (data, errors) -> Direct (data, errors)
| Deferred (data, errors, deferred) -> Deferred (data, errors, deferred)
| Stream data -> Stream data


/// Name value lookup used as output to be serialized into JSON.
/// It has a form of a dictionary with fixed set of keys. Values under keys
/// can be set, but no new entry can be added or removed, once lookup
/// has been initialized.
/// This dictionary implements structural equality.
type NameValueLookup(keyValues: KeyValuePair<string, obj> []) =

let kvals = keyValues |> Array.distinctBy (fun kv -> kv.Key)

let setValue key value =
let mutable i = 0
while i < kvals.Length do
if kvals.[i].Key = key then
kvals.[i] <- KeyValuePair<string, obj>(key, value)
i <- Int32.MaxValue
else i <- i+1
let getValue key = (kvals |> Array.find (fun kv -> kv.Key = key)).Value

let (|BoxedSeq|_|) (xs : obj) =
match xs with
| (:? System.Collections.IEnumerable as enumerable) -> Some (Seq.cast<obj> enumerable)
| _ -> None

let rec structEq (x: NameValueLookup) (y: NameValueLookup) =
if Object.ReferenceEquals(x, y) then true
elif Object.ReferenceEquals(y, null) then false
elif Object.ReferenceEquals(x, null) then false
elif x.Count <> y.Count then false
else
x.Buffer
|> Array.forall2 (fun (a: KeyValuePair<string, obj>) (b: KeyValuePair<string, obj>) ->
if a.Key <> b.Key then false
else
match a.Value, b.Value with
| (:? NameValueLookup as x), (:? NameValueLookup as y) -> structEq x y
| (BoxedSeq x), (BoxedSeq y) ->
if Seq.length x <> Seq.length y then false else Seq.forall2 (=) x y
| a1, b1 -> a1 = b1) y.Buffer

let pad (sb: System.Text.StringBuilder) times =
for _ in 0..times do sb.Append("\t") |> ignore

let rec stringify (sb: System.Text.StringBuilder) deep (o:obj) =
match o with
| :? NameValueLookup as lookup ->
if lookup.Count > 0 then
sb.Append("{ ") |> ignore
lookup.Buffer
|> Array.iter (fun kv ->
sb.Append(kv.Key).Append(": ") |> ignore
stringify sb (deep+1) kv.Value
sb.Append(",\r\n") |> ignore
pad sb deep)
sb.Remove(sb.Length - 4 - deep, 4 + deep).Append(" }") |> ignore
| :? string as s ->
sb.Append("\"").Append(s).Append("\"") |> ignore
| :? System.Collections.IEnumerable as s ->
sb.Append("[") |> ignore
for i in s do
stringify sb (deep + 1) i
sb.Append(", ") |> ignore
sb.Append("]") |> ignore
| other ->
if isNull other |> not
then sb.Append(other.ToString()) |> ignore
else sb.Append("null") |> ignore
()
/// Returns raw content of the current lookup.
member _.Buffer : KeyValuePair<string, obj> [] = kvals

/// Return a number of entries stored in current lookup. It's fixed size.
member _.Count = kvals.Length

/// Updates an entry's value under given key. It will throw an exception
/// if provided key cannot be found in provided lookup.
member _.Update key value = setValue key value

override x.Equals(other) =
match other with
| :? NameValueLookup as lookup -> structEq x lookup
| _ -> false

override _.GetHashCode() =
let mutable hash = 0
for kv in kvals do
hash <- (hash*397) ^^^ (kv.Key.GetHashCode()) ^^^ (if isNull kv.Value then 0 else kv.Value.GetHashCode())
hash

override x.ToString() =
let sb = Text.StringBuilder()
stringify sb 1 x
sb.ToString()

interface IEquatable<NameValueLookup> with
member x.Equals(other) = structEq x other

interface System.Collections.IEnumerable with
member _.GetEnumerator() = (kvals :> System.Collections.IEnumerable).GetEnumerator()

interface IEnumerable<KeyValuePair<string, obj>> with
member _.GetEnumerator() = (kvals :> IEnumerable<KeyValuePair<string, obj>>).GetEnumerator()

interface IDictionary<string, obj> with
member _.Add(_, _) = raise (NotSupportedException "NameValueLookup doesn't allow to add/remove entries")
member _.Add(_) = raise (NotSupportedException "NameValueLookup doesn't allow to add/remove entries")
member _.Clear() = raise (NotSupportedException "NameValueLookup doesn't allow to add/remove entries")
member _.Contains(item) = kvals |> Array.exists ((=) item)
member _.ContainsKey(key) = kvals |> Array.exists (fun kv -> kv.Key = key)
member _.CopyTo(array, arrayIndex) = kvals.CopyTo(array, arrayIndex)
member x.Count = x.Count
member _.IsReadOnly = true
member _.Item
with get (key) = getValue key
and set (key) v = setValue key v
member _.Keys = upcast (kvals |> Array.map (fun kv -> kv.Key))
member _.Values = upcast (kvals |> Array.map (fun kv -> kv.Value))
member _.Remove(_:string) =
raise (NotSupportedException "NameValueLookup doesn't allow to add/remove entries")
false
member _.Remove(_:KeyValuePair<string,obj>) =
raise (NotSupportedException "NameValueLookup doesn't allow to add/remove entries")
false
member _.TryGetValue(key, value) =
match kvals |> Array.tryFind (fun kv -> kv.Key = key) with
| Some kv -> value <- kv.Value; true
| None -> value <- null; false

new(t: (string * obj) list) =
NameValueLookup(t |> List.map (fun (k, v) -> KeyValuePair<string,obj>(k, v)) |> List.toArray)

new(t: string []) =
NameValueLookup(t |> Array.map (fun k -> KeyValuePair<string,obj>(k, null)))

module NameValueLookup =
/// Create new NameValueLookup from given list of key-value tuples.
let ofList (l: (string * obj) list) = NameValueLookup(l)


let private collectDefaultArgValue acc (argdef: InputFieldDef) =
match argdef.DefaultValue with
| Some defVal -> Map.add argdef.Name defVal acc
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,16 @@
</ItemGroup>

<ItemGroup>
<Compile Include="NameValueLookup.fs" />
<Compile Include="Output.fs" />
<Compile Include="Helpers\Diagnostics.fs" />
<Compile Include="Helpers\ObjAndStructConversions.fs" />
<Compile Include="Helpers\Extensions.fs" />
<Compile Include="Helpers\Reflection.fs" />
<Compile Include="Helpers\MemoryCache.fs" />
<Compile Include="Errors.fs" />
<Compile Include="Exception.fs" />
<Compile Include="Websockets/Exceptions.fs" />
<Compile Include="ValidationTypes.fs" />
<Compile Include="AsyncVal.fs" />
<Compile Include="Ast.fs" />
Expand All @@ -58,6 +61,10 @@
<Compile Include="Validation.fs" />
<Compile Include="ValidationResultCache.fs" />
<Compile Include="Parser.fs" />
<Compile Include="GQLRequest.fs" />
<Compile Include="Websockets/Messages.fs" />
<Compile Include="Serialization/JsonConverters.fs" />
<Compile Include="Serialization/JSON.fs" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
namespace FSharp.Data.GraphQL.Server.AspNetCore
namespace FSharp.Data.GraphQL.Shared

open System.Collections.Immutable
open System.Text.Json
Expand Down
141 changes: 141 additions & 0 deletions src/FSharp.Data.GraphQL.Shared/NameValueLookup.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
namespace FSharp.Data.GraphQL.Shared

open System.Collections.Generic
open System

/// Name value lookup used as output to be serialized into JSON.
/// It has a form of a dictionary with fixed set of keys. Values under keys
/// can be set, but no new entry can be added or removed, once lookup
/// has been initialized.
/// This dictionary implements structural equality.
type NameValueLookup(keyValues: KeyValuePair<string, obj> []) =

let kvals = keyValues |> Array.distinctBy (fun kv -> kv.Key)

let setValue key value =
let mutable i = 0
while i < kvals.Length do
if kvals.[i].Key = key then
kvals.[i] <- KeyValuePair<string, obj>(key, value)
i <- Int32.MaxValue
else i <- i+1
let getValue key = (kvals |> Array.find (fun kv -> kv.Key = key)).Value

let (|BoxedSeq|_|) (xs : obj) =
match xs with
| (:? System.Collections.IEnumerable as enumerable) -> Some (Seq.cast<obj> enumerable)
| _ -> None

let rec structEq (x: NameValueLookup) (y: NameValueLookup) =
if Object.ReferenceEquals(x, y) then true
elif Object.ReferenceEquals(y, null) then false
elif Object.ReferenceEquals(x, null) then false
elif x.Count <> y.Count then false
else
x.Buffer
|> Array.forall2 (fun (a: KeyValuePair<string, obj>) (b: KeyValuePair<string, obj>) ->
if a.Key <> b.Key then false
else
match a.Value, b.Value with
| (:? NameValueLookup as x), (:? NameValueLookup as y) -> structEq x y
| (BoxedSeq x), (BoxedSeq y) ->
if Seq.length x <> Seq.length y then false else Seq.forall2 (=) x y
| a1, b1 -> a1 = b1) y.Buffer

let pad (sb: System.Text.StringBuilder) times =
for _ in 0..times do sb.Append("\t") |> ignore

let rec stringify (sb: System.Text.StringBuilder) deep (o:obj) =
match o with
| :? NameValueLookup as lookup ->
if lookup.Count > 0 then
sb.Append("{ ") |> ignore
lookup.Buffer
|> Array.iter (fun kv ->
sb.Append(kv.Key).Append(": ") |> ignore
stringify sb (deep+1) kv.Value
sb.Append(",\r\n") |> ignore
pad sb deep)
sb.Remove(sb.Length - 4 - deep, 4 + deep).Append(" }") |> ignore
| :? string as s ->
sb.Append("\"").Append(s).Append("\"") |> ignore
| :? System.Collections.IEnumerable as s ->
sb.Append("[") |> ignore
for i in s do
stringify sb (deep + 1) i
sb.Append(", ") |> ignore
sb.Append("]") |> ignore
| other ->
if isNull other |> not
then sb.Append(other.ToString()) |> ignore
else sb.Append("null") |> ignore
()
/// Returns raw content of the current lookup.
member _.Buffer : KeyValuePair<string, obj> [] = kvals

/// Return a number of entries stored in current lookup. It's fixed size.
member _.Count = kvals.Length

/// Updates an entry's value under given key. It will throw an exception
/// if provided key cannot be found in provided lookup.
member _.Update key value = setValue key value

override x.Equals(other) =
match other with
| :? NameValueLookup as lookup -> structEq x lookup
| _ -> false

override _.GetHashCode() =
let mutable hash = 0
for kv in kvals do
hash <- (hash*397) ^^^ (kv.Key.GetHashCode()) ^^^ (if isNull kv.Value then 0 else kv.Value.GetHashCode())
hash

override x.ToString() =
let sb = Text.StringBuilder()
stringify sb 1 x
sb.ToString()

interface IEquatable<NameValueLookup> with
member x.Equals(other) = structEq x other

interface System.Collections.IEnumerable with
member _.GetEnumerator() = (kvals :> System.Collections.IEnumerable).GetEnumerator()

interface IEnumerable<KeyValuePair<string, obj>> with
member _.GetEnumerator() = (kvals :> IEnumerable<KeyValuePair<string, obj>>).GetEnumerator()

interface IDictionary<string, obj> with
member _.Add(_, _) = raise (NotSupportedException "NameValueLookup doesn't allow to add/remove entries")
member _.Add(_) = raise (NotSupportedException "NameValueLookup doesn't allow to add/remove entries")
member _.Clear() = raise (NotSupportedException "NameValueLookup doesn't allow to add/remove entries")
member _.Contains(item) = kvals |> Array.exists ((=) item)
member _.ContainsKey(key) = kvals |> Array.exists (fun kv -> kv.Key = key)
member _.CopyTo(array, arrayIndex) = kvals.CopyTo(array, arrayIndex)
member x.Count = x.Count
member _.IsReadOnly = true
member _.Item
with get (key) = getValue key
and set (key) v = setValue key v
member _.Keys = upcast (kvals |> Array.map (fun kv -> kv.Key))
member _.Values = upcast (kvals |> Array.map (fun kv -> kv.Value))
member _.Remove(_:string) =
raise (NotSupportedException "NameValueLookup doesn't allow to add/remove entries")
false
member _.Remove(_:KeyValuePair<string,obj>) =
raise (NotSupportedException "NameValueLookup doesn't allow to add/remove entries")
false
member _.TryGetValue(key, value) =
match kvals |> Array.tryFind (fun kv -> kv.Key = key) with
| Some kv -> value <- kv.Value; true
| None -> value <- null; false

new(t: (string * obj) list) =
NameValueLookup(t |> List.map (fun (k, v) -> KeyValuePair<string,obj>(k, v)) |> List.toArray)

new(t: string []) =
NameValueLookup(t |> Array.map (fun k -> KeyValuePair<string,obj>(k, null)))

module NameValueLookup =
/// Create new NameValueLookup from given list of key-value tuples.
let ofList (l: (string * obj) list) = NameValueLookup(l)
Loading

0 comments on commit 1f51be0

Please sign in to comment.