Skip to content

Commit 2e4a740

Browse files
Copilotxperiandri
andcommitted
Use deterministic SHA-256 based documentId generation
Agent-Logs-Url: https://github.com/fsprojects/FSharp.Data.GraphQL/sessions/020d0296-28b1-416d-bd12-9dd0790516ba Co-authored-by: xperiandri <2365592+xperiandri@users.noreply.github.com>
1 parent 50b4569 commit 2e4a740

2 files changed

Lines changed: 26 additions & 3 deletions

File tree

src/FSharp.Data.GraphQL.Server/Executor.fs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,19 @@
11
namespace FSharp.Data.GraphQL
22

3+
open System
34
open System.Collections.Concurrent
45
open System.Collections.Immutable
6+
open System.Buffers.Binary
7+
open System.Security.Cryptography
58
open System.Runtime.InteropServices
9+
open System.Text
610
open System.Text.Json
711
open FsToolkit.ErrorHandling
812

913
open FSharp.Data.GraphQL.Types
1014
open FSharp.Data.GraphQL.Execution
1115
open FSharp.Data.GraphQL.Ast
16+
open FSharp.Data.GraphQL.Ast.Extensions
1217
open FSharp.Data.GraphQL.Validation
1318
open FSharp.Data.GraphQL.Parser
1419
open FSharp.Data.GraphQL.Planning
@@ -77,6 +82,12 @@ type Executor<'Root>(schema: ISchema<'Root>, middlewares : IExecutorMiddleware s
7782

7883
let middlewaresList = Seq.toList middlewares
7984

85+
let getDocumentId (document : Document) =
86+
let canonicalQuery = document.ToQueryString()
87+
let queryBytes = Encoding.UTF8.GetBytes canonicalQuery
88+
let hash = SHA256.HashData queryBytes
89+
BinaryPrimitives.ReadInt32BigEndian(ReadOnlySpan<byte>(hash, 0, 4))
90+
8091
let rec runMiddlewares (phaseSel : IExecutorMiddleware -> ('ctx -> ('ctx -> 'res) -> 'res) option)
8192
(initialCtx : 'ctx)
8293
(onComplete : 'ctx -> 'res)
@@ -137,7 +148,7 @@ type Executor<'Root>(schema: ISchema<'Root>, middlewares : IExecutorMiddleware s
137148
eval (executionPlan, data, variables, getInputContext)
138149

139150
let createExecutionPlan (ast: Document, operationName: string option, meta : Metadata) =
140-
let documentId = ast.GetHashCode()
151+
let documentId = getDocumentId ast
141152
result {
142153
match findOperation ast operationName with
143154
| Some operation ->

tests/FSharp.Data.GraphQL.Tests/ExecutionTests.fs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ module FSharp.Data.GraphQL.Tests.ExecutionTests
55

66
open Xunit
77
open System
8+
open System.Buffers.Binary
9+
open System.Security.Cryptography
10+
open System.Text
811
open System.Text.Json
912
open System.Text.Json.Serialization
1013
open System.Collections.Immutable
@@ -17,6 +20,7 @@ open FSharp.Data.GraphQL.Shared
1720
open FSharp.Data.GraphQL.Types
1821
open FSharp.Data.GraphQL.Parser
1922
open FSharp.Data.GraphQL.Execution
23+
open FSharp.Data.GraphQL.Ast.Extensions
2024

2125
type TestSubject = {
2226
a: string
@@ -383,9 +387,17 @@ let ``Execution when querying returns unique document id with response`` () =
383387
Define.Field("a", StringType, fun _ x -> x.A)
384388
Define.Field("b", IntType, fun _ x -> x.B)
385389
]))
386-
let result1 = sync <| Executor(schema).AsyncExecute("query Example { a, b, a }", getMockInputContext, { A = "aa"; B = 2 })
387-
let result2 = sync <| Executor(schema).AsyncExecute("query Example { a, b, a }", getMockInputContext, { A = "aa"; B = 2 })
390+
let query = "query Example { a, b, a }"
391+
let expectedDocumentId =
392+
let canonicalQuery =
393+
let ast = parse query
394+
ast.ToQueryString()
395+
let hash = SHA256.HashData(Encoding.UTF8.GetBytes canonicalQuery)
396+
BinaryPrimitives.ReadInt32BigEndian(ReadOnlySpan<byte>(hash, 0, 4))
397+
let result1 = sync <| Executor(schema).AsyncExecute(query, getMockInputContext, { A = "aa"; B = 2 })
398+
let result2 = sync <| Executor(schema).AsyncExecute(query, getMockInputContext, { A = "aa"; B = 2 })
388399
result1.DocumentId |> notEquals Unchecked.defaultof<int>
400+
result1.DocumentId |> equals expectedDocumentId
389401
result1.DocumentId |> equals result2.DocumentId
390402
match result1,result2 with
391403
| Direct(data1, errors1), Direct(data2, errors2) ->

0 commit comments

Comments
 (0)