diff --git a/apis/Google.Cloud.Firestore.V1/Google.Cloud.Firestore.V1/FirestoreClientPartial.cs b/apis/Google.Cloud.Firestore.V1/Google.Cloud.Firestore.V1/FirestoreClientPartial.cs index 02f4c58c14f4..5f21535228b7 100644 --- a/apis/Google.Cloud.Firestore.V1/Google.Cloud.Firestore.V1/FirestoreClientPartial.cs +++ b/apis/Google.Cloud.Firestore.V1/Google.Cloud.Firestore.V1/FirestoreClientPartial.cs @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +using Google.Api.Gax; using Google.Api.Gax.Grpc; using System; @@ -141,4 +142,31 @@ internal static string GetDatabaseResourceName(string resource) void ThrowInvalidResource() => throw new ArgumentException($"{resource} is not a valid Firestore resource name", nameof(resource)); } } + + // Support for FirestoreDbBuilder. + public sealed partial class FirestoreClientBuilder : ClientBuilderBase + { + /// + /// Creates a new instance with no settings. + /// + public FirestoreClientBuilder() + { + } + + /// + /// Creates a by copying common settings from another builder. + /// This method is intended for use in Google.Cloud.Firestore with FirestoreDbBuilder. It will + /// work in other scenarios, but is usually not necessary. + /// + /// The client type of the builder. + /// The builder to copy settings from. Must not be null. + /// A FirestoreClientBuilder with common settings from . + public static FirestoreClientBuilder FromOtherBuilder(ClientBuilderBase builder) + { + GaxPreconditions.CheckNotNull(builder, nameof(builder)); + var ret = new FirestoreClientBuilder(); + ret.CopyCommonSettings(builder); + return ret; + } + } } diff --git a/apis/Google.Cloud.Firestore/Google.Cloud.Firestore.Tests/FirestoreDbBuilderTest.cs b/apis/Google.Cloud.Firestore/Google.Cloud.Firestore.Tests/FirestoreDbBuilderTest.cs new file mode 100644 index 000000000000..d855bad531d7 --- /dev/null +++ b/apis/Google.Cloud.Firestore/Google.Cloud.Firestore.Tests/FirestoreDbBuilderTest.cs @@ -0,0 +1,63 @@ +// Copyright 2019, Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using Google.Api.Gax.Testing; +using Google.Cloud.Firestore.V1; +using Grpc.Core; +using System; +using Xunit; + +namespace Google.Cloud.Firestore.Tests +{ + public class FirestoreDbBuilderTest + { + [Fact] + public void MaximalConfiguration() + { + string warning = null; + var builder = new FirestoreDbBuilder + { + ProjectId = "proj", + DatabaseId = "db", + CallInvoker = new FakeCallInvoker(), + WarningLogger = text => warning = text, + Settings = new FirestoreSettings { Clock = new FakeClock() } + }; + var db = builder.Build(); + Assert.Equal("proj", db.ProjectId); + Assert.Equal("db", db.DatabaseId); + db.LogWarning("Test warning"); + Assert.Equal("Test warning", warning); + Assert.IsType(db.Client.Settings.Clock); + } + + private class FakeCallInvoker : CallInvoker + { + public override AsyncClientStreamingCall AsyncClientStreamingCall(Method method, string host, CallOptions options) => + throw new NotImplementedException(); + + public override AsyncDuplexStreamingCall AsyncDuplexStreamingCall(Method method, string host, CallOptions options) => + throw new NotImplementedException(); + + public override AsyncServerStreamingCall AsyncServerStreamingCall(Method method, string host, CallOptions options, TRequest request) => + throw new NotImplementedException(); + + public override AsyncUnaryCall AsyncUnaryCall(Method method, string host, CallOptions options, TRequest request) => + throw new NotImplementedException(); + + public override TResponse BlockingUnaryCall(Method method, string host, CallOptions options, TRequest request) => + throw new NotImplementedException(); + } + } +} diff --git a/apis/Google.Cloud.Firestore/Google.Cloud.Firestore/FirestoreDb.cs b/apis/Google.Cloud.Firestore/Google.Cloud.Firestore/FirestoreDb.cs index eb9a5abc8cba..7032945bb828 100644 --- a/apis/Google.Cloud.Firestore/Google.Cloud.Firestore/FirestoreDb.cs +++ b/apis/Google.Cloud.Firestore/Google.Cloud.Firestore/FirestoreDb.cs @@ -25,10 +25,9 @@ namespace Google.Cloud.Firestore { - // TODO: Make this abstract with all the trimmings when we have actual RPCs to issue. - /// - /// A Firestore database + /// A Firestore database. Create instances using the static and + /// methods, or using a . /// public sealed class FirestoreDb { @@ -97,7 +96,8 @@ private FirestoreDb(string projectId, string databaseId, FirestoreClient client, /// the project will be automatically detected if possible. /// The client to use for RPC operations. May be null, in which case a client will be created with default credentials. /// A new instance. - public static FirestoreDb Create(string projectId = null, FirestoreClient client = null) => Create(projectId, null, client); + public static FirestoreDb Create(string projectId = null, FirestoreClient client = null) => + Create(projectId ?? Platform.Instance().ProjectId, null, client ?? FirestoreClient.Create(), null); /// /// Asynchronously creates an instance for the specified project, using the specified for RPC operations. @@ -107,7 +107,7 @@ private FirestoreDb(string projectId, string databaseId, FirestoreClient client, /// The client to use for RPC operations. May be null, in which case a client will be created with default credentials. /// A task representing the asynchronous operation. When complete, the result of the task is the new instance. public static async Task CreateAsync(string projectId = null, FirestoreClient client = null) => - new FirestoreDb( + Create( projectId ?? (await Platform.InstanceAsync().ConfigureAwait(false)).ProjectId, DefaultDatabaseId, client ?? await FirestoreClient.CreateAsync().ConfigureAwait(false), @@ -117,27 +117,27 @@ private FirestoreDb(string projectId, string databaseId, FirestoreClient client, /// /// Creates an instance for the specified project and database, using the specified /// for RPC operations. + /// Note: this method should never be made public, as it is expected to grow as additional state is required in the client. + /// Additional parameters should be made optional, for source (but not binary) compatibility with tests. + /// This method does not perform any blocking operations, so may be used from async methods. /// - /// The ID of the Google Cloud Platform project that contains the database. May be null, in which case - /// the project will be automatically detected if possible. + /// The ID of the Google Cloud Platform project that contains the database. Must not be null. /// The ID of the database within the project. May be null, in which case the default database will be used. - /// The client to use for RPC operations. May be null, in which case a client will be created with default credentials. + /// The client to use for RPC operations. Must not be null. + /// The warning logger to use, if any. May be null. /// A new instance. - internal static FirestoreDb Create(string projectId, string databaseId, FirestoreClient client) => + internal static FirestoreDb Create(string projectId, string databaseId, FirestoreClient client, Action warningLogger = null) => + // Validation is performed in the constructor. new FirestoreDb( - projectId ?? Platform.Instance().ProjectId, + projectId, databaseId ?? DefaultDatabaseId, - client ?? FirestoreClient.Create(), - null); - + client, + warningLogger); + /// /// Returns a new with the same project, database and client as this one, /// but the given writer for warning logs. /// - /// - /// This method is experimental, in lieu of a more sophisticated logging. It is likely to be removed before - /// this library becomes GA. - /// /// The logger for warnings. May be null. /// A new based on this one, with the given warning logger. public FirestoreDb WithWarningLogger(Action warningLogger) => diff --git a/apis/Google.Cloud.Firestore/Google.Cloud.Firestore/FirestoreDbBuilder.cs b/apis/Google.Cloud.Firestore/Google.Cloud.Firestore/FirestoreDbBuilder.cs new file mode 100644 index 000000000000..6ae87299566c --- /dev/null +++ b/apis/Google.Cloud.Firestore/Google.Cloud.Firestore/FirestoreDbBuilder.cs @@ -0,0 +1,89 @@ +// Copyright 2019, Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using Google.Api.Gax; +using Google.Api.Gax.Grpc; +using Google.Cloud.Firestore.V1; +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Google.Cloud.Firestore +{ + /// + /// Builder class for , providing simple configuration of credentials, + /// endpoint, project ID etc. + /// + public sealed class FirestoreDbBuilder : ClientBuilderBase + { + /// + /// The settings to use for RPCs, or null for the default settings. + /// + public FirestoreSettings Settings { get; set; } + + /// + /// The ID of the Google Cloud Platform project that contains the database. May be null, in which case + /// the project will be automatically detected if possible. + /// + public string ProjectId { get; set; } + + // Note: currently internal as only the default database is supported. + /// + /// The ID of the database within the project. May be null, in which case the default database will be used. + /// + internal string DatabaseId { get; set; } + + /// + /// Action to receive warning messages. May be null, in which case warnings will be ignored. + /// + public Action WarningLogger { get; set; } + + /// + public override FirestoreDb Build() + { + var projectId = ProjectId ?? Platform.Instance().ProjectId; + var clientBuilder = FirestoreClientBuilder.FromOtherBuilder(this); + clientBuilder.Settings = Settings; + var client = clientBuilder.Build(); + return BuildFromClient(projectId, client); + } + + /// + public override async Task BuildAsync(CancellationToken cancellationToken = default) + { + var projectId = ProjectId ?? (await Platform.InstanceAsync().ConfigureAwait(false)).ProjectId; + var clientBuilder = FirestoreClientBuilder.FromOtherBuilder(this); + clientBuilder.Settings = Settings; + var client = await clientBuilder.BuildAsync(cancellationToken).ConfigureAwait(false); + return BuildFromClient(projectId, client); + } + + // We never end up using these methods, at least with the current implementation + /// + protected override ServiceEndpoint GetDefaultEndpoint() => + throw new InvalidOperationException($"This method should never execute in {nameof(FirestoreDbBuilder)}"); + + /// + protected override IReadOnlyList GetDefaultScopes() => + throw new InvalidOperationException($"This method should never execute in {nameof(FirestoreDbBuilder)}"); + + /// + protected override ChannelPool GetChannelPool() => + throw new InvalidOperationException($"This method should never execute in {nameof(FirestoreDbBuilder)}"); + + private FirestoreDb BuildFromClient(string projectId, FirestoreClient client) => + FirestoreDb.Create(projectId, DatabaseId, client, WarningLogger); + } +}