Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions packages/dart_firebase_admin/lib/src/app/firebase_admin.dart
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,17 @@ class FirebaseAdminApp {
/// Use the Firebase Emulator Suite to run the app locally.
void useEmulator() {
_isUsingEmulator = true;
authApiHost = Uri.http('127.0.0.1:9099', 'identitytoolkit.googleapis.com/');
firestoreApiHost = Uri.http('127.0.0.1:8080', '/');
final env =
Zone.current[envSymbol] as Map<String, String>? ?? Platform.environment;

authApiHost = Uri.http(
env['FIREBASE_AUTH_EMULATOR_HOST'] ?? '127.0.0.1:9099',
'identitytoolkit.googleapis.com/',
);
firestoreApiHost = Uri.http(
env['FIRESTORE_EMULATOR_HOST'] ?? '127.0.0.1:8080',
'/',
);
}

@internal
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,8 @@ class _DocumentReader<T> {
// Only retry if we made progress.
resultCount > 0 &&
// Don't retry permanent errors.
StatusCode.batchGetRetryCodes.contains(firestoreError.errorCode.statusCode);
StatusCode.batchGetRetryCodes
.contains(firestoreError.errorCode.statusCode);
if (shoulRetry) {
return _fetchDocuments();
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ class Transaction {
) {
_firestore = firestore;

_maxAttempts = transactionOptions?.maxAttempts ?? defaultMaxTransactionsAttempts;
_maxAttempts =
transactionOptions?.maxAttempts ?? defaultMaxTransactionsAttempts;

switch (transactionOptions) {
case ReadOnlyTransactionOptions():
Expand All @@ -50,9 +51,11 @@ class Transaction {
static const int defaultMaxTransactionsAttempts = 5;

//Error message for transactional reads that were executed after performing writes.
static const String readAfterWriteErrorMsg = 'Firestore transactions require all reads to be executed before all writes.';
static const String readAfterWriteErrorMsg =
'Firestore transactions require all reads to be executed before all writes.';

static const String readOnlyWriteErrorMsg = 'Firestore read-only transactions cannot execute writes.';
static const String readOnlyWriteErrorMsg =
'Firestore read-only transactions cannot execute writes.';

late final Firestore _firestore;

Expand Down Expand Up @@ -91,7 +94,8 @@ class Transaction {
if (_writeBatch != null && _writeBatch._operations.isNotEmpty) {
throw Exception(readAfterWriteErrorMsg);
}
return _withLazyStartedTransaction<DocumentReference<T>, DocumentSnapshot<T>>(
return _withLazyStartedTransaction<DocumentReference<T>,
DocumentSnapshot<T>>(
docRef,
fieldMask: null,
resultFn: _getSingleFn,
Expand All @@ -115,7 +119,8 @@ class Transaction {
if (_writeBatch != null && _writeBatch._operations.isNotEmpty) {
throw Exception(readAfterWriteErrorMsg);
}
return _withLazyStartedTransaction<List<DocumentReference<T>>, List<DocumentSnapshot<T>>>(
return _withLazyStartedTransaction<List<DocumentReference<T>>,
List<DocumentSnapshot<T>>>(
documentsRefs,
fieldMask: fieldMasks,
resultFn: _getBatchFn<T>,
Expand Down Expand Up @@ -162,15 +167,18 @@ class Transaction {
///
/// A [Precondition] restricting this update.
///
void update(DocumentReference<dynamic> documentRef, Map<Object?, Object?> data, {Precondition? precondition}) {
void update(
DocumentReference<dynamic> documentRef, Map<Object?, Object?> data,
{Precondition? precondition}) {
if (_writeBatch == null) {
throw Exception(readOnlyWriteErrorMsg);
}

_writeBatch.update(
documentRef,
{
for (final entry in data.entries) FieldPath.from(entry.key): entry.value,
for (final entry in data.entries)
FieldPath.from(entry.key): entry.value,
},
precondition: precondition,
);
Expand All @@ -180,7 +188,8 @@ class Transaction {
///
/// A delete for a non-existing document is treated as a success (unless
/// [precondition] is specified, in which case it throws a [FirebaseFirestoreAdminException] with [FirestoreClientErrorCode.notFound]).
void delete(DocumentReference<Map<String, dynamic>> documentRef, {Precondition? precondition}) {
void delete(DocumentReference<Map<String, dynamic>> documentRef,
{Precondition? precondition}) {
if (_writeBatch == null) {
throw Exception(readOnlyWriteErrorMsg);
}
Expand Down Expand Up @@ -233,7 +242,8 @@ class Transaction {
// If there are any locks held, then rollback will eventually release them.
// Rollback can be done concurrently thereby reducing latency caused by
// otherwise blocking.
final rollBackRequest = firestore1.RollbackRequest(transaction: transactionId);
final rollBackRequest =
firestore1.RollbackRequest(transaction: transactionId);
return _firestore._client.v1((client) {
return client.projects.databases.documents
.rollback(
Expand Down Expand Up @@ -264,25 +274,31 @@ class Transaction {
// response because we are not starting a new transaction
return _transactionIdPromise!
.then(
(transactionId) => resultFn(docRef, transactionId: transactionId, fieldMask: fieldMask),
(transactionId) => resultFn(docRef,
transactionId: transactionId, fieldMask: fieldMask),
)
.then((r) => r.result);
} else {
if (_readOnlyReadTime != null) {
// We do not start a transaction for read-only transactions
// do not set _prevTransactionId
return resultFn(docRef, readTime: _readOnlyReadTime, fieldMask: fieldMask).then((r) => r.result);
return resultFn(docRef,
readTime: _readOnlyReadTime, fieldMask: fieldMask)
.then((r) => r.result);
} else {
// This is the first read of the transaction so we create the appropriate
// options for lazily starting the transaction inside this first read op
final opts = firestore1.TransactionOptions();
if (_writeBatch != null) {
opts.readWrite = _prevTransactionId == null ? firestore1.ReadWrite() : firestore1.ReadWrite(retryTransaction: _prevTransactionId);
opts.readWrite = _prevTransactionId == null
? firestore1.ReadWrite()
: firestore1.ReadWrite(retryTransaction: _prevTransactionId);
} else {
opts.readOnly = firestore1.ReadOnly();
}

final resultPromise = resultFn(docRef, transactionOptions: opts, fieldMask: fieldMask);
final resultPromise =
resultFn(docRef, transactionOptions: opts, fieldMask: fieldMask);

// Ensure the _transactionIdPromise is set synchronously so that
// subsequent operations will not race to start another transaction
Expand Down Expand Up @@ -324,7 +340,8 @@ class Transaction {
transactionOptions: transactionOptions,
);
final result = await reader._get();
return _TransactionResult(transaction: result.transaction, result: result.result.single);
return _TransactionResult(
transaction: result.transaction, result: result.result.single);
}

Future<_TransactionResult<List<DocumentSnapshot<T>>>> _getBatchFn<T>(
Expand All @@ -344,7 +361,8 @@ class Transaction {
);

final result = await reader._get();
return _TransactionResult(transaction: result.transaction, result: result.result);
return _TransactionResult(
transaction: result.transaction, result: result.result);
}

Future<T> _runTransaction<T>(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ class WriteResult {

@override
bool operator ==(Object other) {
return identical(this, other) || other is WriteResult && writeTime == other.writeTime;
return identical(this, other) ||
other is WriteResult && writeTime == other.writeTime;
}

@override
Expand Down Expand Up @@ -91,7 +92,8 @@ class WriteBatch {
final response = await _commit(transactionId: transactionId);

return [
for (final writeResult in response.writeResults ?? <firestore1.WriteResult>[])
for (final writeResult
in response.writeResults ?? <firestore1.WriteResult>[])
WriteResult._(
Timestamp._fromString(
writeResult.updateTime ?? response.commitTime!,
Expand Down Expand Up @@ -161,11 +163,13 @@ class WriteBatch {

_verifyNotCommited();

final transform = _DocumentTransform.fromObject(documentReference, firestoreData);
final transform =
_DocumentTransform.fromObject(documentReference, firestoreData);
transform.validate();

firestore1.Write op() {
final document = DocumentSnapshot._fromObject(documentReference, firestoreData);
final document =
DocumentSnapshot._fromObject(documentReference, firestoreData);

final write = document._toWriteProto();
if (transform.transforms.isNotEmpty) {
Expand Down
37 changes: 37 additions & 0 deletions packages/dart_firebase_admin/test/firebase_admin_app_test.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import 'dart:async';

import 'package:dart_firebase_admin/src/app.dart';
import 'package:test/test.dart';

Expand Down Expand Up @@ -34,5 +36,40 @@ void main() {
Uri.http('127.0.0.1:8080', '/'),
);
});

test(
'useEmulator() uses environment variables to set apiHost to the emulator',
() async {
const firebaseAuthEmulatorHost = '127.0.0.1:9000';
const firestoreEmulatorHost = '127.0.0.1:8000';
final testEnv = <String, String>{
'FIREBASE_AUTH_EMULATOR_HOST': firebaseAuthEmulatorHost,
'FIRESTORE_EMULATOR_HOST': firestoreEmulatorHost,
};

await runZoned(
zoneValues: {envSymbol: testEnv},
() async {
final app = FirebaseAdminApp.initializeApp(
'dart-firebase-admin',
Credential.fromApplicationDefaultCredentials(),
);

app.useEmulator();

expect(
app.authApiHost,
Uri.http(
firebaseAuthEmulatorHost,
'identitytoolkit.googleapis.com/',
),
);
expect(
app.firestoreApiHost,
Uri.http(firestoreEmulatorHost, '/'),
);
},
);
});
});
}
Loading
Loading