Skip to content

Commit 8d6af0f

Browse files
Copilotxperiandri
andauthored
Document WebSockets public API
Co-authored-by: xperiandri <2365592+xperiandri@users.noreply.github.com>
1 parent bcedd84 commit 8d6af0f

1 file changed

Lines changed: 204 additions & 14 deletions

File tree

‎src/FSharp.Data.GraphQL.Shared/WebSockets.fs‎

Lines changed: 204 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,21 +7,65 @@ open System.Text.Json.Serialization
77
open FSharp.Data.GraphQL
88
open FSharp.Data.GraphQL.Shared
99

10+
/// <summary>
11+
/// Represents an invalid WebSocket protocol message.
12+
/// </summary>
13+
/// <param name="explanation">The validation failure explanation.</param>
1014
type InvalidWebsocketMessageException (explanation : string) =
1115
inherit System.Exception (explanation)
1216

17+
/// <summary>
18+
/// Identifies a GraphQL WebSocket subscription.
19+
/// </summary>
1320
type SubscriptionId = string
21+
22+
/// <summary>
23+
/// Represents a disposable handle for an active subscription.
24+
/// </summary>
1425
type SubscriptionUnsubscriber = IDisposable
26+
27+
/// <summary>
28+
/// Represents a callback invoked when a subscription is removed.
29+
/// </summary>
1530
type OnUnsubscribeAction = SubscriptionId -> unit
31+
32+
/// <summary>
33+
/// Stores active subscriptions keyed by their identifier.
34+
/// </summary>
1635
type SubscriptionsDict = IDictionary<SubscriptionId, SubscriptionUnsubscriber * OnUnsubscribeAction>
1736

18-
type RawMessage = { Id : string voption; Type : string; Payload : JsonDocument voption }
37+
/// <summary>
38+
/// Represents a raw WebSocket message before it is mapped to protocol-specific client messages.
39+
/// </summary>
40+
type RawMessage = {
41+
/// <summary>
42+
/// Gets the message id, when the message is operation-scoped.
43+
/// </summary>
44+
Id : string voption
45+
/// <summary>
46+
/// Gets the protocol message type.
47+
/// </summary>
48+
Type : string
49+
/// <summary>
50+
/// Gets the raw JSON payload.
51+
/// </summary>
52+
Payload : JsonDocument voption
53+
}
1954

2055
/// <summary>
2156
/// Announces a deferred or streamed field for the first time, identifying it by a short id used in every
2257
/// subsequent <see cref="IncrementalResult"/> or <see cref="CompletedResult"/> for the same field.
2358
/// </summary>
24-
type PendingResult = { Id : string; Path : FieldPath }
59+
type PendingResult = {
60+
/// <summary>
61+
/// Gets the short id assigned to the announced field.
62+
/// </summary>
63+
Id : string
64+
/// <summary>
65+
/// Gets the response path of the announced field.
66+
/// </summary>
67+
Path : FieldPath
68+
}
2569

2670
/// <summary>
2771
/// One incremental delivery of a deferred or streamed field, identified by the id from its
@@ -32,9 +76,21 @@ type PendingResult = { Id : string; Path : FieldPath }
3276
/// <c>@stream</c> field's items, in list order.
3377
/// </remarks>
3478
type IncrementalResult = {
79+
/// <summary>
80+
/// Gets the id of the deferred or streamed field this payload belongs to.
81+
/// </summary>
3582
Id : string
83+
/// <summary>
84+
/// Gets the deferred field data, when the payload carries deferred data.
85+
/// </summary>
3686
Data : objnull Skippable
87+
/// <summary>
88+
/// Gets the streamed items, when the payload carries streamed data.
89+
/// </summary>
3790
Items : objnull[] Skippable
91+
/// <summary>
92+
/// Gets the execution errors associated with the payload.
93+
/// </summary>
3894
Errors : GQLProblemDetails list Skippable
3995
}
4096

@@ -43,7 +99,16 @@ type IncrementalResult = {
4399
/// delivered everything it is going to.
44100
/// </summary>
45101
[<Struct>]
46-
type CompletedResult = { Id : string; Errors : GQLProblemDetails list Skippable }
102+
type CompletedResult = {
103+
/// <summary>
104+
/// Gets the id of the deferred or streamed field that completed.
105+
/// </summary>
106+
Id : string
107+
/// <summary>
108+
/// Gets any completion errors associated with the field.
109+
/// </summary>
110+
Errors : GQLProblemDetails list Skippable
111+
}
47112

48113
/// <summary>
49114
/// Payload of a <c>next</c> message of the <c>graphql-transport-ws</c> protocol.
@@ -55,22 +120,42 @@ type CompletedResult = { Id : string; Errors : GQLProblemDetails list Skippable
55120
/// Client's <c>GraphQL17Alpha9Handler</c>.
56121
/// </remarks>
57122
type SubscriptionExecutionResult = {
58-
/// Result data: an object for a complete or initial payload. Always <see cref="Skip"/> for a subsequent
59-
/// payload, whose deltas are carried by <see cref="Incremental"/> and <see cref="Completed"/> instead.
123+
/// <summary>
124+
/// Gets the result data.
125+
/// </summary>
126+
/// <remarks>
127+
/// This is an object for a complete or initial payload. It is always <see cref="Skip" /> for a subsequent
128+
/// payload, whose deltas are carried by <see cref="Incremental" /> and <see cref="Completed" /> instead.
129+
/// </remarks>
60130
Data : obj Skippable
61-
/// Errors raised while producing the payload. Always <see cref="Skip"/> for a subsequent payload.
131+
/// <summary>
132+
/// Gets the errors raised while producing the payload.
133+
/// </summary>
134+
/// <remarks>
135+
/// This is always <see cref="Skip" /> for a subsequent payload.
136+
/// </remarks>
62137
Errors : GQLProblemDetails list Skippable
63-
/// Fields newly announced by this payload.
138+
/// <summary>
139+
/// Gets the fields newly announced by this payload.
140+
/// </summary>
64141
Pending : PendingResult list Skippable
65-
/// Deltas of already-announced fields delivered by this payload.
142+
/// <summary>
143+
/// Gets the deltas of already-announced fields delivered by this payload.
144+
/// </summary>
66145
Incremental : IncrementalResult list Skippable
67-
/// Fields that finished delivering as of this payload.
146+
/// <summary>
147+
/// Gets the fields that finished delivering as of this payload.
148+
/// </summary>
68149
Completed : CompletedResult list Skippable
69-
/// Tells whether more incremental payloads follow.
150+
/// <summary>
151+
/// Gets a value indicating whether more incremental payloads follow.
152+
/// </summary>
70153
HasNext : bool Skippable
71154
} with
72155

156+
/// <summary>
73157
/// Creates a payload of a complete execution result.
158+
/// </summary>
74159
static member Create (data : Output | null, errors : GQLProblemDetails list) = {
75160
Data = Include (box data)
76161
Errors = Include errors
@@ -80,7 +165,9 @@ type SubscriptionExecutionResult = {
80165
HasNext = Skip
81166
}
82167

168+
/// <summary>
83169
/// Creates a payload that carries only errors.
170+
/// </summary>
84171
static member CreateErrors (errors : GQLProblemDetails list) = {
85172
Data = Include null
86173
Errors = Include errors
@@ -90,7 +177,9 @@ type SubscriptionExecutionResult = {
90177
HasNext = Skip
91178
}
92179

180+
/// <summary>
93181
/// Creates the initial payload of an incremental delivery, which is always followed by subsequent payloads.
182+
/// </summary>
94183
static member CreateInitial (data : Output | null, errors : GQLProblemDetails list, pending : PendingResult list) = {
95184
Data = Include (box data)
96185
Errors = Include errors
@@ -100,8 +189,13 @@ type SubscriptionExecutionResult = {
100189
HasNext = Include true
101190
}
102191

103-
/// Creates a subsequent payload of an incremental delivery, carrying the fields it newly announces, the
104-
/// deltas it delivers for already-announced fields, and the fields it completes.
192+
/// <summary>
193+
/// Creates a subsequent payload of an incremental delivery.
194+
/// </summary>
195+
/// <remarks>
196+
/// It carries the fields it newly announces, the deltas it delivers for already-announced fields, and the
197+
/// fields it completes.
198+
/// </remarks>
105199
static member CreateSubsequent
106200
(pending : PendingResult list, incremental : IncrementalResult list, completed : CompletedResult list, hasNext : bool)
107201
= {
@@ -113,34 +207,130 @@ type SubscriptionExecutionResult = {
113207
HasNext = Include hasNext
114208
}
115209

210+
/// <summary>
211+
/// Represents the raw payload of a server WebSocket message.
212+
/// </summary>
116213
type ServerRawPayload =
214+
/// <summary>
215+
/// Contains a GraphQL execution result payload.
216+
/// </summary>
117217
| ExecutionResult of SubscriptionExecutionResult
218+
/// <summary>
219+
/// Contains one or more GraphQL error payloads.
220+
/// </summary>
118221
| ErrorMessages of GQLProblemDetails list
222+
/// <summary>
223+
/// Contains a custom JSON payload.
224+
/// </summary>
119225
| CustomResponse of JsonDocument
120226

121-
type RawServerMessage = { Id : string voption; Type : string; Payload : ServerRawPayload voption }
227+
/// <summary>
228+
/// Represents a raw server WebSocket message.
229+
/// </summary>
230+
type RawServerMessage = {
231+
/// <summary>
232+
/// Gets the message id, when the message is operation-scoped.
233+
/// </summary>
234+
Id : string voption
235+
/// <summary>
236+
/// Gets the protocol message type.
237+
/// </summary>
238+
Type : string
239+
/// <summary>
240+
/// Gets the raw server payload.
241+
/// </summary>
242+
Payload : ServerRawPayload voption
243+
}
122244

245+
/// <summary>
246+
/// Represents a parsed client WebSocket protocol message.
247+
/// </summary>
123248
type ClientMessage =
249+
/// <summary>
250+
/// Initializes a protocol connection.
251+
/// </summary>
124252
| ConnectionInit of payload : JsonDocument voption
253+
/// <summary>
254+
/// Sends a client ping frame.
255+
/// </summary>
125256
| ClientPing of payload : JsonDocument voption
257+
/// <summary>
258+
/// Sends a client pong frame.
259+
/// </summary>
126260
| ClientPong of payload : JsonDocument voption
261+
/// <summary>
262+
/// Starts a GraphQL subscription or operation.
263+
/// </summary>
127264
| Subscribe of id : string * query : GQLRequestContent
265+
/// <summary>
266+
/// Completes a client-side operation.
267+
/// </summary>
128268
| ClientComplete of id : string
129269

130-
type ClientMessageProtocolFailure = InvalidMessage of code : int * explanation : string
270+
/// <summary>
271+
/// Represents a protocol-level validation failure for an incoming client message.
272+
/// </summary>
273+
type ClientMessageProtocolFailure =
274+
/// <summary>
275+
/// Indicates that the client message failed protocol validation.
276+
/// </summary>
277+
| InvalidMessage of code : int * explanation : string
131278

279+
/// <summary>
280+
/// Represents a server WebSocket protocol message.
281+
/// </summary>
132282
type ServerMessage =
283+
/// <summary>
284+
/// Acknowledges a successful connection initialization.
285+
/// </summary>
133286
| ConnectionAck
287+
/// <summary>
288+
/// Sends a server ping frame.
289+
/// </summary>
134290
| ServerPing
291+
/// <summary>
292+
/// Sends a server pong frame.
293+
/// </summary>
135294
| ServerPong of JsonDocument voption
295+
/// <summary>
296+
/// Sends a GraphQL execution payload.
297+
/// </summary>
136298
| Next of id : string * payload : SubscriptionExecutionResult
299+
/// <summary>
300+
/// Sends protocol errors for an operation.
301+
/// </summary>
137302
| Error of id : string * err : GQLProblemDetails list
303+
/// <summary>
304+
/// Marks an operation as complete.
305+
/// </summary>
138306
| Complete of id : string
139307

308+
/// <summary>
309+
/// Defines application-specific GraphQL WebSocket close codes.
310+
/// </summary>
140311
module CustomWebSocketStatus =
141312

313+
/// <summary>
314+
/// The client sent an invalid message.
315+
/// </summary>
142316
let InvalidMessage = 4400
317+
318+
/// <summary>
319+
/// The client is not authorized.
320+
/// </summary>
143321
let Unauthorized = 4401
322+
323+
/// <summary>
324+
/// The client did not initialize the connection in time.
325+
/// </summary>
144326
let ConnectionTimeout = 4408
327+
328+
/// <summary>
329+
/// The requested subscription identifier is already in use.
330+
/// </summary>
145331
let SubscriberAlreadyExists = 4409
332+
333+
/// <summary>
334+
/// The client sent too many initialization requests.
335+
/// </summary>
146336
let TooManyInitializationRequests = 4429

0 commit comments

Comments
 (0)