Last active
January 9, 2022 20:42
-
-
Save peterhirn/19eb10467610321111730cd996f8fe9e to your computer and use it in GitHub Desktop.
F# Fake Firestore Client
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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