Bladeren bron

Adding Swift API extension methods and making API tweaks.

Mark Duckworth 3 jaren geleden
bovenliggende
commit
e56705984b

+ 2 - 2
Firestore/Source/API/FIRAggregateQuerySnapshot+Internal.h

@@ -36,8 +36,8 @@ NS_ASSUME_NONNULL_BEGIN
 /** Internal FIRDocumentSnapshot API we don't want exposed in our public header files. */
 @interface FIRAggregateQuerySnapshot (Internal)
 
-- (nullable id)valueForField:(id)field
-     serverTimestampBehavior:(FIRServerTimestampBehavior)serverTimestampBehavior;
+- (id)valueForField:(id)field
+    serverTimestampBehavior:(FIRServerTimestampBehavior)serverTimestampBehavior;
 
 @end
 

+ 3 - 3
Firestore/Source/API/FIRAggregateQuerySnapshot.mm

@@ -75,13 +75,13 @@ NS_ASSUME_NONNULL_BEGIN
   return _query;
 }
 
-- (nullable id)valueForAggregation:(FIRAggregateField *)aggregation NS_SWIFT_NAME(get(_:)) {
+- (id)valueForAggregation:(FIRAggregateField *)aggregation NS_SWIFT_NAME(get(_:)) {
   return [self valueForAggregation:aggregation
            serverTimestampBehavior:FIRServerTimestampBehaviorNone];
 }
 
-- (nullable id)valueForAggregation:(FIRAggregateField *)aggregation
-           serverTimestampBehavior:(FIRServerTimestampBehavior)serverTimestampBehavior {
+- (id)valueForAggregation:(FIRAggregateField *)aggregation
+    serverTimestampBehavior:(FIRServerTimestampBehavior)serverTimestampBehavior {
   model::AggregateAlias alias = [aggregation createAlias];
   absl::optional<google_firestore_v1_Value> fieldValue = _result.Get(alias.StringValue());
   if (!fieldValue) {

+ 1 - 1
Firestore/Source/Public/FirebaseFirestore/FIRAggregateQuerySnapshot.h

@@ -51,7 +51,7 @@ NS_SWIFT_NAME(AggregateQuerySnapshot)
  * AggregateQuery.
  * @see FIRAggregateField
  */
-- (nullable id)valueForAggregation:(FIRAggregateField*)aggregation NS_SWIFT_NAME(get(_:));
+- (id)valueForAggregation:(FIRAggregateField*)aggregation NS_SWIFT_NAME(get(_:));
 
 @end
 

+ 20 - 0
Firestore/Swift/Source/Aggregate/AggregateError.swift

@@ -0,0 +1,20 @@
+/*
+ * Copyright 2023 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
+ *
+ *      http://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.
+ */
+
+/// Error type thrown when accessing Firestore aggregate results.
+public enum FirestoreAggregateError: Error {
+  case incompatibleType(String)
+}

+ 58 - 0
Firestore/Swift/Source/Aggregate/AggregateQuerySnapshot+Getters.swift

@@ -0,0 +1,58 @@
+/*
+ * Copyright 2023 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
+ *
+ *      http://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.
+ */
+
+import Foundation
+import FirebaseFirestore
+
+public extension AggregateQuerySnapshot {
+  /// Gets the aggregation result for the specified aggregation, coercing te result to a Double.
+  ///
+  /// See the `AggregateField` class for the expected aggregation result values and types.
+  ///
+  /// - Parameters:
+  ///   - aggregation: An instance of `AggregateField` that specifies which aggregation result to return.
+  /// - Returns: Returns the aggregation result coerced to a `Double?`.
+  /// - Throws: 'FirestoreAggregateError' if the value is non-numeric and non-numeric, and therefore cannot be coerced to a `Double?`.
+  /// - Throws: InvalidArgument exception if the aggregation was not requested in the `AggregateQuery`. This is unrecoverable.
+  func getDouble(_ aggregation: AggregateField) throws -> Double? {
+    if get(aggregation) as? NSNull != nil {
+      return nil
+    } else if let number = get(aggregation) as? NSNumber {
+      return number.doubleValue
+    }
+    throw FirestoreAggregateError
+      .incompatibleType("Unable to represent the result as an Int64.")
+  }
+
+  /// Gets the aggregation result for the specified aggregation, coercing te result to an Int64.
+  ///
+  /// See the `AggregateField` class for the expected aggregration result values and types.
+  ///
+  /// - Parameters:
+  ///   - aggregation: An instance of `AggregateField` that specifies which aggregation result to return.
+  /// - Returns: Returns the aggregation result coerced to an `Int64?`.
+  /// - Throws: 'FirestoreAggregateError' if the value is non-numeric and non-null, and therefore cannot be coerced to an `Int64?`.
+  /// - Throws: InvalidArgument exception if the aggregation was not requested in the `AggregateQuery`. This is unrecoverable.
+  func getInt64(_ aggregation: AggregateField) throws -> Int64? {
+    if get(aggregation) as? NSNull != nil {
+      return nil
+    } else if let number = get(aggregation) as? NSNumber {
+      return number.int64Value
+    }
+    throw FirestoreAggregateError
+      .incompatibleType("Unable to represent the result as an Int64.")
+  }
+}

+ 29 - 1
Firestore/Swift/Tests/Integration/AggregationIntegrationTests.swift

@@ -58,20 +58,27 @@ import Foundation
 
       // Count
       XCTAssertEqual(snapshot.get(AggregateField.count()) as? NSNumber, 2)
+      XCTAssertEqual(try snapshot.getInt64(AggregateField.count()), 2)
 
       // Sum
       XCTAssertEqual(snapshot.get(AggregateField.sum("pages")) as? NSNumber, 150)
       XCTAssertEqual(snapshot.get(AggregateField.sum("pages")) as? Double, 150)
       XCTAssertEqual(snapshot.get(AggregateField.sum("pages")) as? Int64, 150)
+      XCTAssertEqual(try snapshot.getInt64(AggregateField.sum("pages")), 150)
+
       XCTAssertEqual(snapshot.get(AggregateField.sum("weight")) as? NSNumber, 99.6)
       XCTAssertEqual(snapshot.get(AggregateField.sum("weight")) as? Double, 99.6)
+      XCTAssertEqual(try snapshot.getDouble(AggregateField.sum("weight")), 99.6)
 
       // Average
       XCTAssertEqual(snapshot.get(AggregateField.average("pages")) as? NSNumber, 75.0)
       XCTAssertEqual(snapshot.get(AggregateField.average("pages")) as? Double, 75.0)
       XCTAssertEqual(snapshot.get(AggregateField.average("pages")) as? Int64, 75)
+      XCTAssertEqual(try snapshot.getInt64(AggregateField.average("pages")), 75)
+
       XCTAssertEqual(snapshot.get(AggregateField.average("weight")) as? NSNumber, 49.8)
       XCTAssertEqual(snapshot.get(AggregateField.average("weight")) as? Double, 49.8)
+      XCTAssertEqual(try snapshot.getDouble(AggregateField.average("weight")), 49.8)
     }
 
     func testCannotPerformMoreThanMaxAggregations() async throws {
@@ -85,7 +92,7 @@ import Foundation
                                               "bar": 2,
                                               "baz": 3])
 
-      // Max is 5, we're attempting 6. I also like to live dangerously.
+      // Max is 5, we're attempting 6.
       do {
         let snapshot = try await collection.aggregate([
           AggregateField.count(),
@@ -124,11 +131,17 @@ import Foundation
 
       // Sum
       XCTAssertEqual(snapshot.get(AggregateField.sum("pages")) as? NSNumber, 150)
+      XCTAssertEqual(try snapshot.getInt64(AggregateField.sum("pages")), 150)
+
       XCTAssertTrue((snapshot.get(AggregateField.sum("rating")) as? Double)?.isNaN ?? false)
+      XCTAssertTrue(try snapshot.getDouble(AggregateField.sum("rating"))!.isNaN)
 
       // Average
       XCTAssertEqual(snapshot.get(AggregateField.average("pages")) as? NSNumber, 75.0)
+      XCTAssertEqual(try snapshot.getDouble(AggregateField.average("pages")), 75.0)
+
       XCTAssertTrue((snapshot.get(AggregateField.average("rating")) as? Double)?.isNaN ?? false)
+      XCTAssertTrue(try snapshot.getDouble(AggregateField.average("rating"))!.isNaN)
     }
 
     func testThrowsAnErrorWhenGettingTheResultOfAnUnrequestedAggregation() async throws {
@@ -231,6 +244,10 @@ import Foundation
         snapshot.get(AggregateField.sum("longOverflow")) as? Double,
         Double(Int64.max) + Double(Int64.max) + Double(Int64.max)
       )
+      XCTAssertEqual(
+        try snapshot.getDouble(AggregateField.sum("longOverflow")),
+        Double(Int64.max) + Double(Int64.max) + Double(Int64.max)
+      )
       XCTAssertEqual(
         snapshot.get(AggregateField.sum("accumulationOverflow")) as? Int64,
         Int64.max - 100
@@ -239,6 +256,10 @@ import Foundation
         snapshot.get(AggregateField.sum("positiveInfinity")) as? Double,
         Double.infinity
       )
+      XCTAssertEqual(
+        try snapshot.getDouble(AggregateField.sum("positiveInfinity")),
+        Double.infinity
+      )
       XCTAssertEqual(
         snapshot.get(AggregateField.sum("negativeInfinity")) as? Double,
         -Double.infinity
@@ -307,13 +328,20 @@ import Foundation
 
       // Count
       XCTAssertEqual(snapshot.get(AggregateField.count()) as? NSNumber, 0)
+      XCTAssertEqual(try snapshot.getInt64(AggregateField.count()), 0)
 
       // Sum
       XCTAssertEqual(snapshot.get(AggregateField.sum("pages")) as? NSNumber, 0)
+      XCTAssertEqual(try snapshot.getDouble(AggregateField.sum("pages")), 0)
+      XCTAssertEqual(try snapshot.getInt64(AggregateField.sum("pages")), 0)
 
       // Average
       // TODO: (sum/avg) this design is bad, will require and API update
       XCTAssertEqual(snapshot.get(AggregateField.average("pages")) as? NSNull, NSNull())
+
+      if let averagePages = try? snapshot.getInt64(AggregateField.average("pages")) {
+        XCTFail("expected average pages to be NSNull and not convertable to a Double")
+      }
     }
 
     func testPerformsAggregateOverResultSetOfZeroFields() async throws {