Skip to content

Instantly share code, notes, and snippets.

@peterhirn
Last active January 9, 2022 20:42
Show Gist options
  • Save peterhirn/19eb10467610321111730cd996f8fe9e to your computer and use it in GitHub Desktop.
Save peterhirn/19eb10467610321111730cd996f8fe9e to your computer and use it in GitHub Desktop.
F# Fake Firestore Client
open System
open System.Linq
open System.Reflection
open System.Collections.Generic
open System.Threading
open System.Threading.Tasks
open Grpc.Core
open Google.Protobuf
open Google.Cloud.Firestore
open Google.Cloud.Firestore.V1
open Google.Api.Gax.Grpc
open Google.Api.Gax.Grpc.Testing
type Data = IDictionary<string, obj>
type Documents = IDictionary<string, Data>
/// NOTE: Failed to use internal deserializeMap function (Context requires snapshot)
module Internal =
let assembly = Assembly.GetAssembly typedefof<FirestoreDb>
let staticNonPublic = BindingFlags.Static ||| BindingFlags.NonPublic
let serializationContext =
let context = assembly.GetType "Google.Cloud.Firestore.SerializationContext"
let property = context.GetProperty("Default", staticNonPublic)
property.GetValue(context, null)
let serializeMap' =
let serializer = assembly.GetType "Google.Cloud.Firestore.ValueSerializer"
serializer.GetMethod("SerializeMap", staticNonPublic)
let serializeMap (data: obj) =
let args = [| serializationContext; data |]
serializeMap'.Invoke(null, args) :?> Dictionary<string, Value>
let serializeMap (data: Data) = Internal.serializeMap data
let createProtoTimestamp seconds nanos =
let timestamp = WellKnownTypes.Timestamp()
timestamp.Seconds <- seconds
timestamp.Nanos <- nanos
timestamp
let createDocument name data =
let document = Document()
document.Name <- name
document.CreateTime <- createProtoTimestamp 0 1
document.UpdateTime <- createProtoTimestamp 0 2
serializeMap data
|> Seq.iter (fun (KeyValue (key, value)) -> document.Fields.Add(key, value))
document
let found path data =
let readTime = WellKnownTypes.Timestamp.FromDateTime DateTime.UtcNow
let response = BatchGetDocumentsResponse()
response.Found <- createDocument path data
response.ReadTime <- readTime
response
let missing path =
let readTime = WellKnownTypes.Timestamp.FromDateTime DateTime.UtcNow
let response = BatchGetDocumentsResponse()
response.Missing <- path
response.ReadTime <- readTime
response
/// projects/proj/databases/(default)/documents/chat/chat
/// projects/proj/databases/(default)/documents/collection/document/subcollection/subdocument
let relativePath (path: string) =
path.Split '/'
|> Array.skip 5
|> String.concat "/"
/// see https://github.com/googleapis/google-cloud-dotnet/blob/main/apis/Google.Cloud.Firestore/Google.Cloud.Firestore.Tests/FakeDocumentStream.cs
type FakeDocumentStream(request: BatchGetDocumentsRequest, documents: Documents) =
inherit FirestoreClient.BatchGetDocumentsStream()
let responses =
request.Documents
|> Seq.map (fun path ->
match relativePath path |> documents.TryGetValue with
| true, data -> found path data
| _ -> missing path)
|> Seq.toList
/// see https://github.com/googleapis/gax-dotnet/blob/main/Google.Api.Gax.Grpc.Testing/AsyncStreamAdapter.cs
let adapter =
AsyncStreamAdapter<BatchGetDocumentsResponse>(responses.ToAsyncEnumerable().GetAsyncEnumerator())
let grpcCall =
new AsyncServerStreamingCall<BatchGetDocumentsResponse>(adapter, null, null, null, Action(ignore))
override _.GrpcCall = grpcCall
/// see https://github.com/googleapis/google-cloud-dotnet/blob/main/apis/Google.Cloud.Firestore/Google.Cloud.Firestore.Tests/FakeFirestoreClient.cs
type FakeClient(documents: Documents) =
inherit FirestoreClient()
member val Updates = Dictionary<string, Document>()
override _.Settings = FirestoreSettings.GetDefault()
override _.BatchGetDocuments(request, _) = FakeDocumentStream(request, documents)
override _.BeginTransactionAsync(r: BeginTransactionRequest, _: CallSettings) =
BeginTransactionResponse() |> Task.FromResult
override x.CommitAsync(request: CommitRequest, _: CallSettings) =
request.Writes
|> Seq.iter (fun write ->
let path = relativePath write.Update.Name
x.Updates.Add(path, write.Update))
CommitResponse() |> Task.FromResult
override _.BatchWrite(_: BatchWriteRequest, _) =
raise (NotImplementedException "BatchWrite")
override _.BatchWriteAsync(_: BatchWriteRequest, _: CancellationToken) : Task<BatchWriteResponse> =
raise (NotImplementedException "BatchWriteAsync1")
override _.BatchWriteAsync(_: BatchWriteRequest, _: CallSettings) : Task<BatchWriteResponse> =
raise (NotImplementedException "BatchWriteAsync2")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment