diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index c777f1e43..d78b8077e 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -109,9 +109,9 @@ jobs:
uses: actions/cache@v2
with:
path: vendor/bundle
- key: ${{ runner.os }}-gem-v2-${{ hashFiles('**/Gemfile.lock') }}
+ key: ${{ runner.os }}-gem-v3-${{ hashFiles('**/Gemfile.lock') }}
restore-keys: |
- ${{ runner.os }}-gem-v2
+ ${{ runner.os }}-gem-v3
- name: Install Bundle
run: |
bundle config path vendor/bundle
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index b75b7c192..b4ab29e6b 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -26,9 +26,9 @@ jobs:
uses: actions/cache@v2
with:
path: vendor/bundle
- key: ${{ runner.os }}-gem-v2-${{ hashFiles('**/Gemfile.lock') }}
+ key: ${{ runner.os }}-gem-v3-${{ hashFiles('**/Gemfile.lock') }}
restore-keys: |
- ${{ runner.os }}-gem-v2
+ ${{ runner.os }}-gem-v3
- name: Install Bundle
run: |
bundle config path vendor/bundle
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 89c3ba161..c1854a690 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,9 +1,18 @@
# Parse-Swift Changelog
### main
-[Full Changelog](https://github.com/parse-community/Parse-Swift/compare/1.1.2...main)
+[Full Changelog](https://github.com/parse-community/Parse-Swift/compare/1.1.3...main)
* _Contributing to this repo? Add info about your change here to be included in next release_
+### 1.1.3
+[Full Changelog](https://github.com/parse-community/Parse-Swift/compare/1.1.2...1.1.3)
+
+__New features__
+- SwiftUI ready! ([#73](https://github.com/parse-community/Parse-Swift/pull/73)), thanks to [Corey Baker](https://github.com/cbaker6).
+
+__Fixes__
+- Fixes some issues with `ParseUser.logout` ([#73](https://github.com/parse-community/Parse-Swift/pull/73)), thanks to [Corey Baker](https://github.com/cbaker6).
+
### 1.1.2
[Full Changelog](https://github.com/parse-community/Parse-Swift/compare/1.1.1...1.1.2)
diff --git a/Package.swift b/Package.swift
index 1db075989..2d267e373 100644
--- a/Package.swift
+++ b/Package.swift
@@ -4,7 +4,7 @@ import PackageDescription
let package = Package(
name: "ParseSwift",
- platforms: [.iOS(.v11), .macOS(.v10_13), .tvOS(.v11), .watchOS(.v4)],
+ platforms: [.iOS(.v12), .macOS(.v10_13), .tvOS(.v12), .watchOS(.v5)],
products: [
.library(
name: "ParseSwift",
diff --git a/ParseSwift.playground/Pages/1 - Your first Object.xcplaygroundpage/Contents.swift b/ParseSwift.playground/Pages/1 - Your first Object.xcplaygroundpage/Contents.swift
index b9688ba34..98581d432 100644
--- a/ParseSwift.playground/Pages/1 - Your first Object.xcplaygroundpage/Contents.swift
+++ b/ParseSwift.playground/Pages/1 - Your first Object.xcplaygroundpage/Contents.swift
@@ -238,12 +238,13 @@ do {
[scoreToFetch, score2ToFetch].deleteAll { result in
switch result {
case .success(let deletedScores):
- deletedScores.forEach { error in
- guard let error = error else {
- print("Successfully deleted scores")
- return
+ deletedScores.forEach { result in
+ switch result {
+ case .success:
+ print("Successfully deleted score")
+ case .failure(let error):
+ print("Error deleting: \(error)")
}
- print("Error deleting: \(error)")
}
case .failure(let error):
assertionFailure("Error deleting: \(error)")
diff --git a/ParseSwift.playground/Pages/4 - User - Continued.xcplaygroundpage/Contents.swift b/ParseSwift.playground/Pages/4 - User - Continued.xcplaygroundpage/Contents.swift
index 284427846..f282f6d7d 100644
--- a/ParseSwift.playground/Pages/4 - User - Continued.xcplaygroundpage/Contents.swift
+++ b/ParseSwift.playground/Pages/4 - User - Continued.xcplaygroundpage/Contents.swift
@@ -101,9 +101,17 @@ User.current?.signup { result in
}
}
+//: Logging out - synchronously.
+do {
+ try User.logout()
+ print("Successfully logged out")
+} catch let error {
+ print("Error logging out: \(error)")
+}
+
//: Password Reset Request - synchronously.
do {
- try User.verificationEmailRequest(email: "hello@parse.org")
+ try User.verificationEmail(email: "hello@parse.org")
print("Successfully requested verification email be sent")
} catch let error {
print("Error requesting verification email be sent: \(error)")
diff --git a/ParseSwift.playground/contents.xcplayground b/ParseSwift.playground/contents.xcplayground
index 992108bbe..ab9557970 100644
--- a/ParseSwift.playground/contents.xcplayground
+++ b/ParseSwift.playground/contents.xcplayground
@@ -11,9 +11,9 @@
-
-
-
+
+
+
-
+
\ No newline at end of file
diff --git a/ParseSwift.podspec b/ParseSwift.podspec
index f81957ee8..fa204bd7b 100644
--- a/ParseSwift.podspec
+++ b/ParseSwift.podspec
@@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = "ParseSwift"
- s.version = "1.1.2"
+ s.version = "1.1.3"
s.summary = "Parse Pure Swift SDK"
s.homepage = "https://github.com/parse-community/Parse-Swift"
s.authors = {
@@ -10,10 +10,10 @@ Pod::Spec.new do |s|
:git => "#{s.homepage}.git",
:tag => "#{s.version}",
}
- s.ios.deployment_target = "11.0"
+ s.ios.deployment_target = "12.0"
s.osx.deployment_target = "10.13"
- s.tvos.deployment_target = "11.0"
- s.watchos.deployment_target = "4.0"
+ s.tvos.deployment_target = "12.0"
+ s.watchos.deployment_target = "5.0"
s.swift_versions = ['5.1', '5.2', '5.3']
s.source_files = "Sources/ParseSwift/**/*.swift"
s.license = {
diff --git a/ParseSwift.xcodeproj/project.pbxproj b/ParseSwift.xcodeproj/project.pbxproj
index 502d489d1..0d0bcdfd6 100644
--- a/ParseSwift.xcodeproj/project.pbxproj
+++ b/ParseSwift.xcodeproj/project.pbxproj
@@ -75,11 +75,77 @@
70110D592506CE890091CC1D /* BaseParseInstallation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70110D562506CE890091CC1D /* BaseParseInstallation.swift */; };
70110D5A2506CE890091CC1D /* BaseParseInstallation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70110D562506CE890091CC1D /* BaseParseInstallation.swift */; };
70110D5C2506ED0E0091CC1D /* ParseInstallationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70110D5B2506ED0E0091CC1D /* ParseInstallationTests.swift */; };
+ 7016ED3225C3BA2000038648 /* ParseUser+combine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7016ED3125C3BA2000038648 /* ParseUser+combine.swift */; };
+ 7016ED3325C3BA2000038648 /* ParseUser+combine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7016ED3125C3BA2000038648 /* ParseUser+combine.swift */; };
+ 7016ED3425C3BA2000038648 /* ParseUser+combine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7016ED3125C3BA2000038648 /* ParseUser+combine.swift */; };
+ 7016ED3525C3BA2000038648 /* ParseUser+combine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7016ED3125C3BA2000038648 /* ParseUser+combine.swift */; };
+ 7016ED4025C4A25A00038648 /* ParseUserCombineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7016ED3F25C4A25A00038648 /* ParseUserCombineTests.swift */; };
+ 7016ED4125C4A25A00038648 /* ParseUserCombineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7016ED3F25C4A25A00038648 /* ParseUserCombineTests.swift */; };
+ 7016ED4225C4A25A00038648 /* ParseUserCombineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7016ED3F25C4A25A00038648 /* ParseUserCombineTests.swift */; };
+ 7016ED5625C4C32B00038648 /* ParseInstallation+combine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7016ED5525C4C32B00038648 /* ParseInstallation+combine.swift */; };
+ 7016ED5725C4C32B00038648 /* ParseInstallation+combine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7016ED5525C4C32B00038648 /* ParseInstallation+combine.swift */; };
+ 7016ED5825C4C32B00038648 /* ParseInstallation+combine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7016ED5525C4C32B00038648 /* ParseInstallation+combine.swift */; };
+ 7016ED5925C4C32B00038648 /* ParseInstallation+combine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7016ED5525C4C32B00038648 /* ParseInstallation+combine.swift */; };
+ 7016ED6425C4C46B00038648 /* ParseObject+combine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7016ED6325C4C46B00038648 /* ParseObject+combine.swift */; };
+ 7016ED6525C4C46B00038648 /* ParseObject+combine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7016ED6325C4C46B00038648 /* ParseObject+combine.swift */; };
+ 7016ED6625C4C46B00038648 /* ParseObject+combine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7016ED6325C4C46B00038648 /* ParseObject+combine.swift */; };
+ 7016ED6725C4C46B00038648 /* ParseObject+combine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7016ED6325C4C46B00038648 /* ParseObject+combine.swift */; };
7033ECB325584A83009770F3 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7033ECB225584A83009770F3 /* AppDelegate.swift */; };
7033ECB525584A83009770F3 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7033ECB425584A83009770F3 /* ViewController.swift */; };
7033ECB825584A83009770F3 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 7033ECB625584A83009770F3 /* Main.storyboard */; };
7033ECBA25584A85009770F3 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 7033ECB925584A85009770F3 /* Assets.xcassets */; };
7033ECBD25584A85009770F3 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 7033ECBB25584A85009770F3 /* LaunchScreen.storyboard */; };
+ 7044C17525C4ECFF0011F6E7 /* ParseCloud+combine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7044C17425C4ECFF0011F6E7 /* ParseCloud+combine.swift */; };
+ 7044C17625C4ECFF0011F6E7 /* ParseCloud+combine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7044C17425C4ECFF0011F6E7 /* ParseCloud+combine.swift */; };
+ 7044C17725C4ECFF0011F6E7 /* ParseCloud+combine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7044C17425C4ECFF0011F6E7 /* ParseCloud+combine.swift */; };
+ 7044C17825C4ECFF0011F6E7 /* ParseCloud+combine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7044C17425C4ECFF0011F6E7 /* ParseCloud+combine.swift */; };
+ 7044C18325C4EFC10011F6E7 /* ParseConfig+combine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7044C18225C4EFC10011F6E7 /* ParseConfig+combine.swift */; };
+ 7044C18425C4EFC10011F6E7 /* ParseConfig+combine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7044C18225C4EFC10011F6E7 /* ParseConfig+combine.swift */; };
+ 7044C18525C4EFC10011F6E7 /* ParseConfig+combine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7044C18225C4EFC10011F6E7 /* ParseConfig+combine.swift */; };
+ 7044C18625C4EFC10011F6E7 /* ParseConfig+combine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7044C18225C4EFC10011F6E7 /* ParseConfig+combine.swift */; };
+ 7044C19125C4F5B60011F6E7 /* ParseFile+combine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7044C19025C4F5B60011F6E7 /* ParseFile+combine.swift */; };
+ 7044C19225C4F5B60011F6E7 /* ParseFile+combine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7044C19025C4F5B60011F6E7 /* ParseFile+combine.swift */; };
+ 7044C19325C4F5B60011F6E7 /* ParseFile+combine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7044C19025C4F5B60011F6E7 /* ParseFile+combine.swift */; };
+ 7044C19425C4F5B60011F6E7 /* ParseFile+combine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7044C19025C4F5B60011F6E7 /* ParseFile+combine.swift */; };
+ 7044C19F25C4FA870011F6E7 /* ParseOperation+combine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7044C19E25C4FA870011F6E7 /* ParseOperation+combine.swift */; };
+ 7044C1A025C4FA870011F6E7 /* ParseOperation+combine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7044C19E25C4FA870011F6E7 /* ParseOperation+combine.swift */; };
+ 7044C1A125C4FA870011F6E7 /* ParseOperation+combine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7044C19E25C4FA870011F6E7 /* ParseOperation+combine.swift */; };
+ 7044C1A225C4FA870011F6E7 /* ParseOperation+combine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7044C19E25C4FA870011F6E7 /* ParseOperation+combine.swift */; };
+ 7044C1AD25C4FC080011F6E7 /* Query+combine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7044C1AC25C4FC080011F6E7 /* Query+combine.swift */; };
+ 7044C1AE25C4FC080011F6E7 /* Query+combine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7044C1AC25C4FC080011F6E7 /* Query+combine.swift */; };
+ 7044C1AF25C4FC080011F6E7 /* Query+combine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7044C1AC25C4FC080011F6E7 /* Query+combine.swift */; };
+ 7044C1B025C4FC080011F6E7 /* Query+combine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7044C1AC25C4FC080011F6E7 /* Query+combine.swift */; };
+ 7044C1BB25C52E410011F6E7 /* ParseInstallationCombineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7044C1BA25C52E410011F6E7 /* ParseInstallationCombineTests.swift */; };
+ 7044C1BC25C52E410011F6E7 /* ParseInstallationCombineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7044C1BA25C52E410011F6E7 /* ParseInstallationCombineTests.swift */; };
+ 7044C1BD25C52E410011F6E7 /* ParseInstallationCombineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7044C1BA25C52E410011F6E7 /* ParseInstallationCombineTests.swift */; };
+ 7044C1C825C5B2B10011F6E7 /* ParseAuthentication+combine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7044C1C725C5B2B10011F6E7 /* ParseAuthentication+combine.swift */; };
+ 7044C1C925C5B2B10011F6E7 /* ParseAuthentication+combine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7044C1C725C5B2B10011F6E7 /* ParseAuthentication+combine.swift */; };
+ 7044C1CA25C5B2B10011F6E7 /* ParseAuthentication+combine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7044C1C725C5B2B10011F6E7 /* ParseAuthentication+combine.swift */; };
+ 7044C1CB25C5B2B10011F6E7 /* ParseAuthentication+combine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7044C1C725C5B2B10011F6E7 /* ParseAuthentication+combine.swift */; };
+ 7044C1DF25C5C70D0011F6E7 /* ParseObjectCombine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7044C1DE25C5C70D0011F6E7 /* ParseObjectCombine.swift */; };
+ 7044C1E025C5C70D0011F6E7 /* ParseObjectCombine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7044C1DE25C5C70D0011F6E7 /* ParseObjectCombine.swift */; };
+ 7044C1E125C5C70D0011F6E7 /* ParseObjectCombine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7044C1DE25C5C70D0011F6E7 /* ParseObjectCombine.swift */; };
+ 7044C1EC25C5CC930011F6E7 /* ParseOperationCombineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7044C1EB25C5CC930011F6E7 /* ParseOperationCombineTests.swift */; };
+ 7044C1ED25C5CC930011F6E7 /* ParseOperationCombineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7044C1EB25C5CC930011F6E7 /* ParseOperationCombineTests.swift */; };
+ 7044C1EE25C5CC930011F6E7 /* ParseOperationCombineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7044C1EB25C5CC930011F6E7 /* ParseOperationCombineTests.swift */; };
+ 7044C1F925C5CFAB0011F6E7 /* ParseFileCombineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7044C1F825C5CFAB0011F6E7 /* ParseFileCombineTests.swift */; };
+ 7044C1FA25C5CFAB0011F6E7 /* ParseFileCombineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7044C1F825C5CFAB0011F6E7 /* ParseFileCombineTests.swift */; };
+ 7044C1FB25C5CFAB0011F6E7 /* ParseFileCombineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7044C1F825C5CFAB0011F6E7 /* ParseFileCombineTests.swift */; };
+ 7044C20625C5D6780011F6E7 /* ParseQueryCombineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7044C20525C5D6780011F6E7 /* ParseQueryCombineTests.swift */; };
+ 7044C20725C5D6780011F6E7 /* ParseQueryCombineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7044C20525C5D6780011F6E7 /* ParseQueryCombineTests.swift */; };
+ 7044C20825C5D6780011F6E7 /* ParseQueryCombineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7044C20525C5D6780011F6E7 /* ParseQueryCombineTests.swift */; };
+ 7044C21325C5DE490011F6E7 /* ParseCloudCombineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7044C21225C5DE490011F6E7 /* ParseCloudCombineTests.swift */; };
+ 7044C21425C5DE490011F6E7 /* ParseCloudCombineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7044C21225C5DE490011F6E7 /* ParseCloudCombineTests.swift */; };
+ 7044C21525C5DE490011F6E7 /* ParseCloudCombineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7044C21225C5DE490011F6E7 /* ParseCloudCombineTests.swift */; };
+ 7044C22025C5E0160011F6E7 /* ParseConfigCombineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7044C21F25C5E0160011F6E7 /* ParseConfigCombineTests.swift */; };
+ 7044C22125C5E0160011F6E7 /* ParseConfigCombineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7044C21F25C5E0160011F6E7 /* ParseConfigCombineTests.swift */; };
+ 7044C22225C5E0160011F6E7 /* ParseConfigCombineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7044C21F25C5E0160011F6E7 /* ParseConfigCombineTests.swift */; };
+ 7044C22D25C5E4E90011F6E7 /* ParseAnonymousCombineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7044C22C25C5E4E90011F6E7 /* ParseAnonymousCombineTests.swift */; };
+ 7044C22E25C5E4E90011F6E7 /* ParseAnonymousCombineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7044C22C25C5E4E90011F6E7 /* ParseAnonymousCombineTests.swift */; };
+ 7044C22F25C5E4E90011F6E7 /* ParseAnonymousCombineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7044C22C25C5E4E90011F6E7 /* ParseAnonymousCombineTests.swift */; };
+ 7044C24325C5EA360011F6E7 /* ParseAppleCombineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7044C24225C5EA360011F6E7 /* ParseAppleCombineTests.swift */; };
+ 7044C24425C5EA360011F6E7 /* ParseAppleCombineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7044C24225C5EA360011F6E7 /* ParseAppleCombineTests.swift */; };
+ 7044C24525C5EA360011F6E7 /* ParseAppleCombineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7044C24225C5EA360011F6E7 /* ParseAppleCombineTests.swift */; };
70510AAC259EE25E00FEA700 /* LiveQuerySocket.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70510AAB259EE25E00FEA700 /* LiveQuerySocket.swift */; };
70510AAD259EE25E00FEA700 /* LiveQuerySocket.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70510AAB259EE25E00FEA700 /* LiveQuerySocket.swift */; };
70510AAE259EE25E00FEA700 /* LiveQuerySocket.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70510AAB259EE25E00FEA700 /* LiveQuerySocket.swift */; };
@@ -459,6 +525,10 @@
70110D51250680140091CC1D /* ParseConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParseConstants.swift; sourceTree = ""; };
70110D562506CE890091CC1D /* BaseParseInstallation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseParseInstallation.swift; sourceTree = ""; };
70110D5B2506ED0E0091CC1D /* ParseInstallationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParseInstallationTests.swift; sourceTree = ""; };
+ 7016ED3125C3BA2000038648 /* ParseUser+combine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ParseUser+combine.swift"; sourceTree = ""; };
+ 7016ED3F25C4A25A00038648 /* ParseUserCombineTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParseUserCombineTests.swift; sourceTree = ""; };
+ 7016ED5525C4C32B00038648 /* ParseInstallation+combine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ParseInstallation+combine.swift"; sourceTree = ""; };
+ 7016ED6325C4C46B00038648 /* ParseObject+combine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ParseObject+combine.swift"; sourceTree = ""; };
7033ECB025584A83009770F3 /* TestHostTV.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = TestHostTV.app; sourceTree = BUILT_PRODUCTS_DIR; };
7033ECB225584A83009770F3 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
7033ECB425584A83009770F3 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; };
@@ -466,6 +536,21 @@
7033ECB925584A85009770F3 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
7033ECBC25584A85009770F3 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
7033ECBE25584A85009770F3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
+ 7044C17425C4ECFF0011F6E7 /* ParseCloud+combine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ParseCloud+combine.swift"; sourceTree = ""; };
+ 7044C18225C4EFC10011F6E7 /* ParseConfig+combine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ParseConfig+combine.swift"; sourceTree = ""; };
+ 7044C19025C4F5B60011F6E7 /* ParseFile+combine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ParseFile+combine.swift"; sourceTree = ""; };
+ 7044C19E25C4FA870011F6E7 /* ParseOperation+combine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ParseOperation+combine.swift"; sourceTree = ""; };
+ 7044C1AC25C4FC080011F6E7 /* Query+combine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Query+combine.swift"; sourceTree = ""; };
+ 7044C1BA25C52E410011F6E7 /* ParseInstallationCombineTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParseInstallationCombineTests.swift; sourceTree = ""; };
+ 7044C1C725C5B2B10011F6E7 /* ParseAuthentication+combine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ParseAuthentication+combine.swift"; sourceTree = ""; };
+ 7044C1DE25C5C70D0011F6E7 /* ParseObjectCombine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParseObjectCombine.swift; sourceTree = ""; };
+ 7044C1EB25C5CC930011F6E7 /* ParseOperationCombineTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParseOperationCombineTests.swift; sourceTree = ""; };
+ 7044C1F825C5CFAB0011F6E7 /* ParseFileCombineTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParseFileCombineTests.swift; sourceTree = ""; };
+ 7044C20525C5D6780011F6E7 /* ParseQueryCombineTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParseQueryCombineTests.swift; sourceTree = ""; };
+ 7044C21225C5DE490011F6E7 /* ParseCloudCombineTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParseCloudCombineTests.swift; sourceTree = ""; };
+ 7044C21F25C5E0160011F6E7 /* ParseConfigCombineTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParseConfigCombineTests.swift; sourceTree = ""; };
+ 7044C22C25C5E4E90011F6E7 /* ParseAnonymousCombineTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParseAnonymousCombineTests.swift; sourceTree = ""; };
+ 7044C24225C5EA360011F6E7 /* ParseAppleCombineTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParseAppleCombineTests.swift; sourceTree = ""; };
70510AAB259EE25E00FEA700 /* LiveQuerySocket.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveQuerySocket.swift; sourceTree = ""; };
70572670259033A700F0ADD5 /* ParseFileManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParseFileManager.swift; sourceTree = ""; };
705726DF2592C2A800F0ADD5 /* ParseHash.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParseHash.swift; sourceTree = ""; };
@@ -678,25 +763,35 @@
705726ED2592C91C00F0ADD5 /* HashTests.swift */,
4AA8076E1F794C1C008CD551 /* KeychainStoreTests.swift */,
9194657724F16E330070296B /* ParseACLTests.swift */,
+ 7044C22C25C5E4E90011F6E7 /* ParseAnonymousCombineTests.swift */,
70A2D86A25B3ADB6001BEB7D /* ParseAnonymousTests.swift */,
+ 7044C24225C5EA360011F6E7 /* ParseAppleCombineTests.swift */,
70C5502125B3D8F700B5DBC2 /* ParseAppleTests.swift */,
70A2D81E25B36A7D001BEB7D /* ParseAuthenticationTests.swift */,
+ 7044C21225C5DE490011F6E7 /* ParseCloudCombineTests.swift */,
916786EF259BC59600BB5B4E /* ParseCloudTests.swift */,
+ 7044C21F25C5E0160011F6E7 /* ParseConfigCombineTests.swift */,
70D1BE0625BB2BF400A42E7C /* ParseConfigTests.swift */,
F971F4F524DE381A006CB79B /* ParseEncoderTests.swift */,
+ 7044C1F825C5CFAB0011F6E7 /* ParseFileCombineTests.swift */,
705A99F8259807F900B3547F /* ParseFileManagerTests.swift */,
705727882593FF8000F0ADD5 /* ParseFileTests.swift */,
70BC0B32251903D1001556DB /* ParseGeoPointTests.swift */,
+ 7044C1BA25C52E410011F6E7 /* ParseInstallationCombineTests.swift */,
70110D5B2506ED0E0091CC1D /* ParseInstallationTests.swift */,
7003963A25A288100052CB31 /* ParseLiveQueryTests.swift */,
70C7DC2024D20F190050419B /* ParseObjectBatchTests.swift */,
+ 7044C1DE25C5C70D0011F6E7 /* ParseObjectCombine.swift */,
911DB13524C4FC100027F3C7 /* ParseObjectTests.swift */,
+ 7044C1EB25C5CC930011F6E7 /* ParseOperationCombineTests.swift */,
70C5508425B4A68700B5DBC2 /* ParseOperationTests.swift */,
70CE1D882545BF730018D572 /* ParsePointerTests.swift */,
+ 7044C20525C5D6780011F6E7 /* ParseQueryCombineTests.swift */,
70C7DC1F24D20F180050419B /* ParseQueryTests.swift */,
70D1BD8625B8C37200A42E7C /* ParseRelationTests.swift */,
7004C22D25B69077005E0AD9 /* ParseRoleTests.swift */,
70C5504525B40D5200B5DBC2 /* ParseSessionTests.swift */,
+ 7016ED3F25C4A25A00038648 /* ParseUserCombineTests.swift */,
70C7DC1D24D20E530050419B /* ParseUserTests.swift */,
7FFF552A2217E729007C3B4E /* AnyCodableTests */,
911DB12A24C3F7260027F3C7 /* NetworkMocking */,
@@ -850,6 +945,7 @@
isa = PBXGroup;
children = (
707A3BF025B0A4F0000D215C /* ParseAuthentication.swift */,
+ 7044C1C725C5B2B10011F6E7 /* ParseAuthentication+combine.swift */,
);
path = Protocols;
sourceTree = "";
@@ -949,13 +1045,17 @@
children = (
F97B45C024D9C6F200F4A88B /* ParseACL.swift */,
916786E1259B7DDA00BB5B4E /* ParseCloud.swift */,
+ 7044C17425C4ECFF0011F6E7 /* ParseCloud+combine.swift */,
70D1BDB925BB17A600A42E7C /* ParseConfig.swift */,
+ 7044C18225C4EFC10011F6E7 /* ParseConfig+combine.swift */,
F97B45BF24D9C6F200F4A88B /* ParseError.swift */,
F97B45C124D9C6F200F4A88B /* ParseFile.swift */,
+ 7044C19025C4F5B60011F6E7 /* ParseFile+combine.swift */,
F97B45BC24D9C6F200F4A88B /* ParseGeoPoint.swift */,
7004C21F25B63C7A005E0AD9 /* ParseRelation.swift */,
F97B45BE24D9C6F200F4A88B /* Pointer.swift */,
F97B45BB24D9C6F200F4A88B /* Query.swift */,
+ 7044C1AC25C4FC080011F6E7 /* Query+combine.swift */,
);
path = Types;
sourceTree = "";
@@ -964,10 +1064,13 @@
isa = PBXGroup;
children = (
70BDA2B2250536FF00FC2237 /* ParseInstallation.swift */,
+ 7016ED5525C4C32B00038648 /* ParseInstallation+combine.swift */,
F97B45C624D9C6F200F4A88B /* ParseObject.swift */,
+ 7016ED6325C4C46B00038648 /* ParseObject+combine.swift */,
70C5507625B49D3A00B5DBC2 /* ParseRole.swift */,
70C5503725B406B800B5DBC2 /* ParseSession.swift */,
F97B45C424D9C6F200F4A88B /* ParseUser.swift */,
+ 7016ED3125C3BA2000038648 /* ParseUser+combine.swift */,
);
path = Objects;
sourceTree = "";
@@ -1006,6 +1109,7 @@
F97B464124D9C78B00F4A88B /* Delete.swift */,
F97B464524D9C78B00F4A88B /* Increment.swift */,
F97B464024D9C78B00F4A88B /* ParseOperation.swift */,
+ 7044C19E25C4FA870011F6E7 /* ParseOperation+combine.swift */,
F97B464424D9C78B00F4A88B /* Remove.swift */,
70C5509F25B4A9F600B5DBC2 /* RemoveRelation.swift */,
);
@@ -1413,6 +1517,7 @@
F97B461624D9C6F200F4A88B /* Queryable.swift in Sources */,
F97B45DA24D9C6F200F4A88B /* Extensions.swift in Sources */,
70C5503825B406B800B5DBC2 /* ParseSession.swift in Sources */,
+ 7044C1C825C5B2B10011F6E7 /* ParseAuthentication+combine.swift in Sources */,
707A3BF125B0A4F0000D215C /* ParseAuthentication.swift in Sources */,
70D1BE7325BB43EB00A42E7C /* BaseConfig.swift in Sources */,
F97B465F24D9C7B500F4A88B /* KeychainStore.swift in Sources */,
@@ -1425,7 +1530,10 @@
700395F225A171320052CB31 /* LiveQueryable.swift in Sources */,
F97B45F224D9C6F200F4A88B /* Pointer.swift in Sources */,
70510AAC259EE25E00FEA700 /* LiveQuerySocket.swift in Sources */,
+ 7044C19125C4F5B60011F6E7 /* ParseFile+combine.swift in Sources */,
+ 7044C19F25C4FA870011F6E7 /* ParseOperation+combine.swift in Sources */,
F97B461E24D9C6F200F4A88B /* ParseStorage.swift in Sources */,
+ 7044C1AD25C4FC080011F6E7 /* Query+combine.swift in Sources */,
F97B45D224D9C6F200F4A88B /* AnyDecodable.swift in Sources */,
70C550A025B4A9F600B5DBC2 /* RemoveRelation.swift in Sources */,
F97B463B24D9C74400F4A88B /* API+Commands.swift in Sources */,
@@ -1437,6 +1545,7 @@
700396F825A394AE0052CB31 /* ParseLiveQueryDelegate.swift in Sources */,
F97B465A24D9C78C00F4A88B /* Increment.swift in Sources */,
7003960925A184EF0052CB31 /* ParseLiveQuery.swift in Sources */,
+ 7044C17525C4ECFF0011F6E7 /* ParseCloud+combine.swift in Sources */,
F97B45E224D9C6F200F4A88B /* AnyEncodable.swift in Sources */,
700396EA25A3892D0052CB31 /* LiveQuerySocketDelegate.swift in Sources */,
70572671259033A700F0ADD5 /* ParseFileManager.swift in Sources */,
@@ -1455,6 +1564,7 @@
F97B45EA24D9C6F200F4A88B /* ParseGeoPoint.swift in Sources */,
F97B460224D9C6F200F4A88B /* NoBody.swift in Sources */,
700395BA25A1470F0052CB31 /* Subscription.swift in Sources */,
+ 7016ED5625C4C32B00038648 /* ParseInstallation+combine.swift in Sources */,
7003972A25A3B0140052CB31 /* ParseURLSessionDelegate.swift in Sources */,
700395D125A147BE0052CB31 /* ParseSubscription.swift in Sources */,
F97B45F624D9C6F200F4A88B /* ParseError.swift in Sources */,
@@ -1469,6 +1579,7 @@
F97B45CE24D9C6F200F4A88B /* ParseCoding.swift in Sources */,
F97B465624D9C78C00F4A88B /* Remove.swift in Sources */,
F97B45FA24D9C6F200F4A88B /* ParseACL.swift in Sources */,
+ 7016ED6425C4C46B00038648 /* ParseObject+combine.swift in Sources */,
70BDA2B3250536FF00FC2237 /* ParseInstallation.swift in Sources */,
F97B462724D9C72700F4A88B /* API.swift in Sources */,
70647E9C259E3A9A004C1004 /* ParseType.swift in Sources */,
@@ -1476,6 +1587,8 @@
70110D572506CE890091CC1D /* BaseParseInstallation.swift in Sources */,
F97B45DE24D9C6F200F4A88B /* AnyCodable.swift in Sources */,
70C5507725B49D3A00B5DBC2 /* ParseRole.swift in Sources */,
+ 7044C18325C4EFC10011F6E7 /* ParseConfig+combine.swift in Sources */,
+ 7016ED3225C3BA2000038648 /* ParseUser+combine.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -1487,27 +1600,37 @@
70CE1D892545BF730018D572 /* ParsePointerTests.swift in Sources */,
911DB12E24C4837E0027F3C7 /* APICommandTests.swift in Sources */,
911DB12C24C3F7720027F3C7 /* MockURLResponse.swift in Sources */,
+ 7044C24325C5EA360011F6E7 /* ParseAppleCombineTests.swift in Sources */,
+ 7044C1DF25C5C70D0011F6E7 /* ParseObjectCombine.swift in Sources */,
70C5504625B40D5200B5DBC2 /* ParseSessionTests.swift in Sources */,
70110D5C2506ED0E0091CC1D /* ParseInstallationTests.swift in Sources */,
+ 7016ED4025C4A25A00038648 /* ParseUserCombineTests.swift in Sources */,
705727B12593FF8800F0ADD5 /* ParseFileTests.swift in Sources */,
70BC0B33251903D1001556DB /* ParseGeoPointTests.swift in Sources */,
7003957625A0EE770052CB31 /* BatchUtilsTests.swift in Sources */,
705A99F9259807F900B3547F /* ParseFileManagerTests.swift in Sources */,
+ 7044C20625C5D6780011F6E7 /* ParseQueryCombineTests.swift in Sources */,
70C5508525B4A68700B5DBC2 /* ParseOperationTests.swift in Sources */,
7004C24D25B69207005E0AD9 /* ParseRoleTests.swift in Sources */,
91678706259BC5D400BB5B4E /* ParseCloudTests.swift in Sources */,
70D1BD8725B8C37200A42E7C /* ParseRelationTests.swift in Sources */,
7003963B25A288100052CB31 /* ParseLiveQueryTests.swift in Sources */,
7FFF552E2217E72A007C3B4E /* AnyEncodableTests.swift in Sources */,
+ 7044C22025C5E0160011F6E7 /* ParseConfigCombineTests.swift in Sources */,
7FFF55302217E72A007C3B4E /* AnyDecodableTests.swift in Sources */,
70C7DC2224D20F190050419B /* ParseObjectBatchTests.swift in Sources */,
+ 7044C1BB25C52E410011F6E7 /* ParseInstallationCombineTests.swift in Sources */,
7FFF552F2217E72A007C3B4E /* AnyCodableTests.swift in Sources */,
4AA807701F794C31008CD551 /* KeychainStoreTests.swift in Sources */,
+ 7044C1F925C5CFAB0011F6E7 /* ParseFileCombineTests.swift in Sources */,
70C5502225B3D8F700B5DBC2 /* ParseAppleTests.swift in Sources */,
F971F4F624DE381A006CB79B /* ParseEncoderTests.swift in Sources */,
70C7DC2124D20F190050419B /* ParseQueryTests.swift in Sources */,
+ 7044C22D25C5E4E90011F6E7 /* ParseAnonymousCombineTests.swift in Sources */,
9194657824F16E330070296B /* ParseACLTests.swift in Sources */,
+ 7044C21325C5DE490011F6E7 /* ParseCloudCombineTests.swift in Sources */,
70A2D86B25B3ADB6001BEB7D /* ParseAnonymousTests.swift in Sources */,
+ 7044C1EC25C5CC930011F6E7 /* ParseOperationCombineTests.swift in Sources */,
70C7DC1E24D20E530050419B /* ParseUserTests.swift in Sources */,
70A2D81F25B36A7D001BEB7D /* ParseAuthenticationTests.swift in Sources */,
70D1BE4B25BB312700A42E7C /* ParseConfigTests.swift in Sources */,
@@ -1525,6 +1648,7 @@
F97B461724D9C6F200F4A88B /* Queryable.swift in Sources */,
F97B45DB24D9C6F200F4A88B /* Extensions.swift in Sources */,
70C5503925B406B800B5DBC2 /* ParseSession.swift in Sources */,
+ 7044C1C925C5B2B10011F6E7 /* ParseAuthentication+combine.swift in Sources */,
707A3BF225B0A4F0000D215C /* ParseAuthentication.swift in Sources */,
70D1BE7425BB43EB00A42E7C /* BaseConfig.swift in Sources */,
F97B466024D9C7B500F4A88B /* KeychainStore.swift in Sources */,
@@ -1537,7 +1661,10 @@
700395F325A171320052CB31 /* LiveQueryable.swift in Sources */,
F97B45F324D9C6F200F4A88B /* Pointer.swift in Sources */,
70510AAD259EE25E00FEA700 /* LiveQuerySocket.swift in Sources */,
+ 7044C19225C4F5B60011F6E7 /* ParseFile+combine.swift in Sources */,
+ 7044C1A025C4FA870011F6E7 /* ParseOperation+combine.swift in Sources */,
F97B461F24D9C6F200F4A88B /* ParseStorage.swift in Sources */,
+ 7044C1AE25C4FC080011F6E7 /* Query+combine.swift in Sources */,
F97B45D324D9C6F200F4A88B /* AnyDecodable.swift in Sources */,
70C550A125B4A9F600B5DBC2 /* RemoveRelation.swift in Sources */,
F97B463C24D9C74400F4A88B /* API+Commands.swift in Sources */,
@@ -1549,6 +1676,7 @@
700396F925A394AE0052CB31 /* ParseLiveQueryDelegate.swift in Sources */,
F97B465B24D9C78C00F4A88B /* Increment.swift in Sources */,
7003960A25A184EF0052CB31 /* ParseLiveQuery.swift in Sources */,
+ 7044C17625C4ECFF0011F6E7 /* ParseCloud+combine.swift in Sources */,
F97B45E324D9C6F200F4A88B /* AnyEncodable.swift in Sources */,
700396EB25A3892D0052CB31 /* LiveQuerySocketDelegate.swift in Sources */,
70572672259033A700F0ADD5 /* ParseFileManager.swift in Sources */,
@@ -1567,6 +1695,7 @@
F97B45EB24D9C6F200F4A88B /* ParseGeoPoint.swift in Sources */,
F97B460324D9C6F200F4A88B /* NoBody.swift in Sources */,
700395BB25A1470F0052CB31 /* Subscription.swift in Sources */,
+ 7016ED5725C4C32B00038648 /* ParseInstallation+combine.swift in Sources */,
7003972B25A3B0140052CB31 /* ParseURLSessionDelegate.swift in Sources */,
700395D225A147BE0052CB31 /* ParseSubscription.swift in Sources */,
F97B45F724D9C6F200F4A88B /* ParseError.swift in Sources */,
@@ -1581,6 +1710,7 @@
F97B45CF24D9C6F200F4A88B /* ParseCoding.swift in Sources */,
F97B465724D9C78C00F4A88B /* Remove.swift in Sources */,
F97B45FB24D9C6F200F4A88B /* ParseACL.swift in Sources */,
+ 7016ED6525C4C46B00038648 /* ParseObject+combine.swift in Sources */,
70BDA2B4250536FF00FC2237 /* ParseInstallation.swift in Sources */,
F97B462824D9C72700F4A88B /* API.swift in Sources */,
70647E9D259E3A9A004C1004 /* ParseType.swift in Sources */,
@@ -1588,6 +1718,8 @@
70110D582506CE890091CC1D /* BaseParseInstallation.swift in Sources */,
F97B45DF24D9C6F200F4A88B /* AnyCodable.swift in Sources */,
70C5507825B49D3A00B5DBC2 /* ParseRole.swift in Sources */,
+ 7044C18425C4EFC10011F6E7 /* ParseConfig+combine.swift in Sources */,
+ 7016ED3325C3BA2000038648 /* ParseUser+combine.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -1608,27 +1740,37 @@
709B98532556ECAA00507778 /* ParsePointerTests.swift in Sources */,
709B984C2556ECAA00507778 /* APICommandTests.swift in Sources */,
709B984D2556ECAA00507778 /* AnyDecodableTests.swift in Sources */,
+ 7044C24525C5EA360011F6E7 /* ParseAppleCombineTests.swift in Sources */,
+ 7044C1E125C5C70D0011F6E7 /* ParseObjectCombine.swift in Sources */,
70C5504825B40D5200B5DBC2 /* ParseSessionTests.swift in Sources */,
709B98572556ECAA00507778 /* ParseACLTests.swift in Sources */,
+ 7016ED4225C4A25A00038648 /* ParseUserCombineTests.swift in Sources */,
705727BC2593FF8C00F0ADD5 /* ParseFileTests.swift in Sources */,
709B984F2556ECAA00507778 /* AnyCodableTests.swift in Sources */,
7003957825A0EE770052CB31 /* BatchUtilsTests.swift in Sources */,
705A99FB259807F900B3547F /* ParseFileManagerTests.swift in Sources */,
+ 7044C20825C5D6780011F6E7 /* ParseQueryCombineTests.swift in Sources */,
70C5508725B4A68700B5DBC2 /* ParseOperationTests.swift in Sources */,
7004C26125B6920B005E0AD9 /* ParseRoleTests.swift in Sources */,
9167871A259BC5D600BB5B4E /* ParseCloudTests.swift in Sources */,
70D1BD8925B8C37200A42E7C /* ParseRelationTests.swift in Sources */,
7003963D25A288100052CB31 /* ParseLiveQueryTests.swift in Sources */,
709B98592556ECAA00507778 /* MockURLResponse.swift in Sources */,
+ 7044C22225C5E0160011F6E7 /* ParseConfigCombineTests.swift in Sources */,
709B98522556ECAA00507778 /* ParseUserTests.swift in Sources */,
709B984E2556ECAA00507778 /* ParseGeoPointTests.swift in Sources */,
+ 7044C1BD25C52E410011F6E7 /* ParseInstallationCombineTests.swift in Sources */,
709B984B2556ECAA00507778 /* MockURLProtocol.swift in Sources */,
709B98552556ECAA00507778 /* ParseQueryTests.swift in Sources */,
+ 7044C1FB25C5CFAB0011F6E7 /* ParseFileCombineTests.swift in Sources */,
70C5502425B3D8F700B5DBC2 /* ParseAppleTests.swift in Sources */,
709B98502556ECAA00507778 /* KeychainStoreTests.swift in Sources */,
709B98562556ECAA00507778 /* ParseObjectTests.swift in Sources */,
+ 7044C22F25C5E4E90011F6E7 /* ParseAnonymousCombineTests.swift in Sources */,
709B985A2556ECAA00507778 /* ParseObjectBatchTests.swift in Sources */,
+ 7044C21525C5DE490011F6E7 /* ParseCloudCombineTests.swift in Sources */,
70A2D86D25B3ADB6001BEB7D /* ParseAnonymousTests.swift in Sources */,
+ 7044C1EE25C5CC930011F6E7 /* ParseOperationCombineTests.swift in Sources */,
709B98582556ECAA00507778 /* AnyEncodableTests.swift in Sources */,
70A2D82125B36A7D001BEB7D /* ParseAuthenticationTests.swift in Sources */,
70D1BE5F25BB312A00A42E7C /* ParseConfigTests.swift in Sources */,
@@ -1645,27 +1787,37 @@
70F2E2B7254F283000B2EA5C /* ParsePointerTests.swift in Sources */,
70F2E2B5254F283000B2EA5C /* ParseEncoderTests.swift in Sources */,
70F2E2C2254F283000B2EA5C /* APICommandTests.swift in Sources */,
+ 7044C24425C5EA360011F6E7 /* ParseAppleCombineTests.swift in Sources */,
+ 7044C1E025C5C70D0011F6E7 /* ParseObjectCombine.swift in Sources */,
70C5504725B40D5200B5DBC2 /* ParseSessionTests.swift in Sources */,
70F2E2BC254F283000B2EA5C /* ParseObjectTests.swift in Sources */,
+ 7016ED4125C4A25A00038648 /* ParseUserCombineTests.swift in Sources */,
705727BB2593FF8B00F0ADD5 /* ParseFileTests.swift in Sources */,
70F2E2BD254F283000B2EA5C /* AnyDecodableTests.swift in Sources */,
7003957725A0EE770052CB31 /* BatchUtilsTests.swift in Sources */,
705A99FA259807F900B3547F /* ParseFileManagerTests.swift in Sources */,
+ 7044C20725C5D6780011F6E7 /* ParseQueryCombineTests.swift in Sources */,
70C5508625B4A68700B5DBC2 /* ParseOperationTests.swift in Sources */,
7004C25725B6920A005E0AD9 /* ParseRoleTests.swift in Sources */,
91678710259BC5D600BB5B4E /* ParseCloudTests.swift in Sources */,
70D1BD8825B8C37200A42E7C /* ParseRelationTests.swift in Sources */,
7003963C25A288100052CB31 /* ParseLiveQueryTests.swift in Sources */,
70F2E2C1254F283000B2EA5C /* AnyCodableTests.swift in Sources */,
+ 7044C22125C5E0160011F6E7 /* ParseConfigCombineTests.swift in Sources */,
70F2E2B3254F283000B2EA5C /* ParseUserTests.swift in Sources */,
70F2E2C0254F283000B2EA5C /* MockURLResponse.swift in Sources */,
+ 7044C1BC25C52E410011F6E7 /* ParseInstallationCombineTests.swift in Sources */,
70F2E2BE254F283000B2EA5C /* ParseObjectBatchTests.swift in Sources */,
70F2E2BF254F283000B2EA5C /* MockURLProtocol.swift in Sources */,
+ 7044C1FA25C5CFAB0011F6E7 /* ParseFileCombineTests.swift in Sources */,
70C5502325B3D8F700B5DBC2 /* ParseAppleTests.swift in Sources */,
70F2E2BB254F283000B2EA5C /* ParseGeoPointTests.swift in Sources */,
70F2E2B8254F283000B2EA5C /* AnyEncodableTests.swift in Sources */,
+ 7044C22E25C5E4E90011F6E7 /* ParseAnonymousCombineTests.swift in Sources */,
70F2E2B4254F283000B2EA5C /* ParseQueryTests.swift in Sources */,
+ 7044C21425C5DE490011F6E7 /* ParseCloudCombineTests.swift in Sources */,
70A2D86C25B3ADB6001BEB7D /* ParseAnonymousTests.swift in Sources */,
+ 7044C1ED25C5CC930011F6E7 /* ParseOperationCombineTests.swift in Sources */,
70F2E2BA254F283000B2EA5C /* ParseInstallationTests.swift in Sources */,
70A2D82025B36A7D001BEB7D /* ParseAuthenticationTests.swift in Sources */,
70D1BE5525BB312900A42E7C /* ParseConfigTests.swift in Sources */,
@@ -1683,6 +1835,7 @@
F97B45E924D9C6F200F4A88B /* Query.swift in Sources */,
F97B463624D9C74400F4A88B /* URLSession+extensions.swift in Sources */,
70C5503B25B406B800B5DBC2 /* ParseSession.swift in Sources */,
+ 7044C1CB25C5B2B10011F6E7 /* ParseAuthentication+combine.swift in Sources */,
707A3BF425B0A4F0000D215C /* ParseAuthentication.swift in Sources */,
70D1BE7625BB43EB00A42E7C /* BaseConfig.swift in Sources */,
F97B460524D9C6F200F4A88B /* NoBody.swift in Sources */,
@@ -1695,7 +1848,10 @@
700395F525A171320052CB31 /* LiveQueryable.swift in Sources */,
F97B45FD24D9C6F200F4A88B /* ParseACL.swift in Sources */,
70510AAF259EE25E00FEA700 /* LiveQuerySocket.swift in Sources */,
+ 7044C19425C4F5B60011F6E7 /* ParseFile+combine.swift in Sources */,
+ 7044C1A225C4FA870011F6E7 /* ParseOperation+combine.swift in Sources */,
F97B465124D9C78C00F4A88B /* Add.swift in Sources */,
+ 7044C1B025C4FC080011F6E7 /* Query+combine.swift in Sources */,
F97B461124D9C6F200F4A88B /* ParseObject.swift in Sources */,
70C550A325B4A9F600B5DBC2 /* RemoveRelation.swift in Sources */,
F97B460D24D9C6F200F4A88B /* Fetchable.swift in Sources */,
@@ -1707,6 +1863,7 @@
700396FB25A394AE0052CB31 /* ParseLiveQueryDelegate.swift in Sources */,
F97B463A24D9C74400F4A88B /* Responses.swift in Sources */,
7003960C25A184EF0052CB31 /* ParseLiveQuery.swift in Sources */,
+ 7044C17825C4ECFF0011F6E7 /* ParseCloud+combine.swift in Sources */,
F97B45DD24D9C6F200F4A88B /* Extensions.swift in Sources */,
700396ED25A3892D0052CB31 /* LiveQuerySocketDelegate.swift in Sources */,
70572674259033A700F0ADD5 /* ParseFileManager.swift in Sources */,
@@ -1725,6 +1882,7 @@
70110D5A2506CE890091CC1D /* BaseParseInstallation.swift in Sources */,
F97B45F924D9C6F200F4A88B /* ParseError.swift in Sources */,
700395BD25A1470F0052CB31 /* Subscription.swift in Sources */,
+ 7016ED5925C4C32B00038648 /* ParseInstallation+combine.swift in Sources */,
7003972D25A3B0140052CB31 /* ParseURLSessionDelegate.swift in Sources */,
700395D425A147BE0052CB31 /* ParseSubscription.swift in Sources */,
F97B460124D9C6F200F4A88B /* ParseFile.swift in Sources */,
@@ -1739,6 +1897,7 @@
F97B463E24D9C74400F4A88B /* API+Commands.swift in Sources */,
F97B462A24D9C72700F4A88B /* API.swift in Sources */,
F97B463224D9C74400F4A88B /* BatchUtils.swift in Sources */,
+ 7016ED6725C4C46B00038648 /* ParseObject+combine.swift in Sources */,
F97B45F124D9C6F200F4A88B /* BaseParseUser.swift in Sources */,
F97B45D924D9C6F200F4A88B /* ParseEncoder.swift in Sources */,
70647E9F259E3A9A004C1004 /* ParseType.swift in Sources */,
@@ -1746,6 +1905,8 @@
912C9BFD24D302B2009947C3 /* Parse.swift in Sources */,
F97B461924D9C6F200F4A88B /* Queryable.swift in Sources */,
70C5507A25B49D3A00B5DBC2 /* ParseRole.swift in Sources */,
+ 7044C18625C4EFC10011F6E7 /* ParseConfig+combine.swift in Sources */,
+ 7016ED3525C3BA2000038648 /* ParseUser+combine.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -1758,6 +1919,7 @@
F97B45E824D9C6F200F4A88B /* Query.swift in Sources */,
F97B463524D9C74400F4A88B /* URLSession+extensions.swift in Sources */,
70C5503A25B406B800B5DBC2 /* ParseSession.swift in Sources */,
+ 7044C1CA25C5B2B10011F6E7 /* ParseAuthentication+combine.swift in Sources */,
707A3BF325B0A4F0000D215C /* ParseAuthentication.swift in Sources */,
70D1BE7525BB43EB00A42E7C /* BaseConfig.swift in Sources */,
F97B460424D9C6F200F4A88B /* NoBody.swift in Sources */,
@@ -1770,7 +1932,10 @@
700395F425A171320052CB31 /* LiveQueryable.swift in Sources */,
F97B45FC24D9C6F200F4A88B /* ParseACL.swift in Sources */,
70510AAE259EE25E00FEA700 /* LiveQuerySocket.swift in Sources */,
+ 7044C19325C4F5B60011F6E7 /* ParseFile+combine.swift in Sources */,
+ 7044C1A125C4FA870011F6E7 /* ParseOperation+combine.swift in Sources */,
F97B465024D9C78B00F4A88B /* Add.swift in Sources */,
+ 7044C1AF25C4FC080011F6E7 /* Query+combine.swift in Sources */,
F97B461024D9C6F200F4A88B /* ParseObject.swift in Sources */,
70C550A225B4A9F600B5DBC2 /* RemoveRelation.swift in Sources */,
F97B460C24D9C6F200F4A88B /* Fetchable.swift in Sources */,
@@ -1782,6 +1947,7 @@
700396FA25A394AE0052CB31 /* ParseLiveQueryDelegate.swift in Sources */,
F97B463924D9C74400F4A88B /* Responses.swift in Sources */,
7003960B25A184EF0052CB31 /* ParseLiveQuery.swift in Sources */,
+ 7044C17725C4ECFF0011F6E7 /* ParseCloud+combine.swift in Sources */,
F97B45DC24D9C6F200F4A88B /* Extensions.swift in Sources */,
700396EC25A3892D0052CB31 /* LiveQuerySocketDelegate.swift in Sources */,
70572673259033A700F0ADD5 /* ParseFileManager.swift in Sources */,
@@ -1800,6 +1966,7 @@
70110D592506CE890091CC1D /* BaseParseInstallation.swift in Sources */,
F97B45F824D9C6F200F4A88B /* ParseError.swift in Sources */,
700395BC25A1470F0052CB31 /* Subscription.swift in Sources */,
+ 7016ED5825C4C32B00038648 /* ParseInstallation+combine.swift in Sources */,
7003972C25A3B0140052CB31 /* ParseURLSessionDelegate.swift in Sources */,
700395D325A147BE0052CB31 /* ParseSubscription.swift in Sources */,
F97B460024D9C6F200F4A88B /* ParseFile.swift in Sources */,
@@ -1814,6 +1981,7 @@
F97B463D24D9C74400F4A88B /* API+Commands.swift in Sources */,
F97B462924D9C72700F4A88B /* API.swift in Sources */,
F97B463124D9C74400F4A88B /* BatchUtils.swift in Sources */,
+ 7016ED6625C4C46B00038648 /* ParseObject+combine.swift in Sources */,
F97B45F024D9C6F200F4A88B /* BaseParseUser.swift in Sources */,
F97B45D824D9C6F200F4A88B /* ParseEncoder.swift in Sources */,
70647E9E259E3A9A004C1004 /* ParseType.swift in Sources */,
@@ -1821,6 +1989,8 @@
912C9BE024D302B0009947C3 /* Parse.swift in Sources */,
F97B461824D9C6F200F4A88B /* Queryable.swift in Sources */,
70C5507925B49D3A00B5DBC2 /* ParseRole.swift in Sources */,
+ 7044C18525C4EFC10011F6E7 /* ParseConfig+combine.swift in Sources */,
+ 7016ED3425C3BA2000038648 /* ParseUser+combine.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -2055,13 +2225,15 @@
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
- MARKETING_VERSION = 1.1.2;
+ MARKETING_VERSION = 1.1.3;
PRODUCT_BUNDLE_IDENTIFIER = com.parse.ParseSwift;
PRODUCT_NAME = ParseSwift;
SKIP_INSTALL = YES;
SWIFT_SWIFT3_OBJC_INFERENCE = Off;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
+ TVOS_DEPLOYMENT_TARGET = 12.0;
+ WATCHOS_DEPLOYMENT_TARGET = 5.0;
};
name = Debug;
};
@@ -2077,13 +2249,15 @@
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
- MARKETING_VERSION = 1.1.2;
+ MARKETING_VERSION = 1.1.3;
PRODUCT_BUNDLE_IDENTIFIER = com.parse.ParseSwift;
PRODUCT_NAME = ParseSwift;
SKIP_INSTALL = YES;
SWIFT_SWIFT3_OBJC_INFERENCE = Off;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
+ TVOS_DEPLOYMENT_TARGET = 12.0;
+ WATCHOS_DEPLOYMENT_TARGET = 5.0;
};
name = Release;
};
@@ -2141,13 +2315,15 @@
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks";
MACOSX_DEPLOYMENT_TARGET = 10.13;
- MARKETING_VERSION = 1.1.2;
+ MARKETING_VERSION = 1.1.3;
PRODUCT_BUNDLE_IDENTIFIER = com.parse.ParseSwift;
PRODUCT_NAME = ParseSwift;
SDKROOT = macosx;
SKIP_INSTALL = YES;
SWIFT_SWIFT3_OBJC_INFERENCE = Off;
SWIFT_VERSION = 5.0;
+ TVOS_DEPLOYMENT_TARGET = 12.0;
+ WATCHOS_DEPLOYMENT_TARGET = 5.0;
};
name = Debug;
};
@@ -2165,13 +2341,15 @@
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks";
MACOSX_DEPLOYMENT_TARGET = 10.13;
- MARKETING_VERSION = 1.1.2;
+ MARKETING_VERSION = 1.1.3;
PRODUCT_BUNDLE_IDENTIFIER = com.parse.ParseSwift;
PRODUCT_NAME = ParseSwift;
SDKROOT = macosx;
SKIP_INSTALL = YES;
SWIFT_SWIFT3_OBJC_INFERENCE = Off;
SWIFT_VERSION = 5.0;
+ TVOS_DEPLOYMENT_TARGET = 12.0;
+ WATCHOS_DEPLOYMENT_TARGET = 5.0;
};
name = Release;
};
@@ -2310,7 +2488,7 @@
INFOPLIST_FILE = "ParseSwift-watchOS/Info.plist";
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
- MARKETING_VERSION = 1.1.2;
+ MARKETING_VERSION = 1.1.3;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = "com.parse.ParseSwift-watchOS";
@@ -2320,7 +2498,8 @@
SKIP_INSTALL = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = 4;
- WATCHOS_DEPLOYMENT_TARGET = 4.0;
+ TVOS_DEPLOYMENT_TARGET = 12.0;
+ WATCHOS_DEPLOYMENT_TARGET = 5.0;
};
name = Debug;
};
@@ -2338,7 +2517,7 @@
INFOPLIST_FILE = "ParseSwift-watchOS/Info.plist";
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
- MARKETING_VERSION = 1.1.2;
+ MARKETING_VERSION = 1.1.3;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = "com.parse.ParseSwift-watchOS";
PRODUCT_NAME = ParseSwift;
@@ -2347,7 +2526,8 @@
SKIP_INSTALL = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = 4;
- WATCHOS_DEPLOYMENT_TARGET = 4.0;
+ TVOS_DEPLOYMENT_TARGET = 12.0;
+ WATCHOS_DEPLOYMENT_TARGET = 5.0;
};
name = Release;
};
@@ -2364,7 +2544,7 @@
INFOPLIST_FILE = "ParseSwift-tvOS/Info.plist";
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
- MARKETING_VERSION = 1.1.2;
+ MARKETING_VERSION = 1.1.3;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = "com.parse.ParseSwift-tvOS";
@@ -2375,6 +2555,7 @@
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = 3;
TVOS_DEPLOYMENT_TARGET = 12.0;
+ WATCHOS_DEPLOYMENT_TARGET = 5.0;
};
name = Debug;
};
@@ -2391,7 +2572,7 @@
INFOPLIST_FILE = "ParseSwift-tvOS/Info.plist";
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
- MARKETING_VERSION = 1.1.2;
+ MARKETING_VERSION = 1.1.3;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = "com.parse.ParseSwift-tvOS";
PRODUCT_NAME = ParseSwift;
@@ -2401,6 +2582,7 @@
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = 3;
TVOS_DEPLOYMENT_TARGET = 12.0;
+ WATCHOS_DEPLOYMENT_TARGET = 5.0;
};
name = Release;
};
diff --git a/Scripts/jazzy.sh b/Scripts/jazzy.sh
index f7cdfedc8..63ca4af7c 100755
--- a/Scripts/jazzy.sh
+++ b/Scripts/jazzy.sh
@@ -5,7 +5,7 @@ bundle exec jazzy \
--author_url http://parseplatform.org \
--github_url https://github.com/parse-community/Parse-Swift \
--root-url http://parseplatform.org/Parse-Swift/api/ \
- --module-version 1.1.2 \
+ --module-version 1.1.3 \
--theme fullwidth \
--skip-undocumented \
--output ./docs/api \
diff --git a/Sources/ParseSwift/API/API+Commands.swift b/Sources/ParseSwift/API/API+Commands.swift
index 60b94abfa..a626a430a 100644
--- a/Sources/ParseSwift/API/API+Commands.swift
+++ b/Sources/ParseSwift/API/API+Commands.swift
@@ -452,38 +452,41 @@ extension API.Command where T: ParseObject {
}
// MARK: Batch - Deleting
- // swiftlint:disable:next line_length
- static func batch(commands: [API.NonParseBodyCommand]) -> RESTBatchCommandNoBodyType {
- let commands = commands.compactMap { (command) -> API.NonParseBodyCommand? in
+ static func batch(commands: [API.NonParseBodyCommand]) -> RESTBatchCommandNoBodyType {
+ let commands = commands.compactMap { (command) -> API.NonParseBodyCommand? in
let path = ParseConfiguration.mountPath + command.path.urlComponent
- return API.NonParseBodyCommand(
+ return API.NonParseBodyCommand(
method: command.method,
path: .any(path), mapper: command.mapper)
}
- let mapper = { (data: Data) -> [ParseError?] in
+ let mapper = { (data: Data) -> [(Result)] in
- let decodingType = [ParseError?].self
+ let decodingType = [BatchResponseItem].self
do {
let responses = try ParseCoding.jsonDecoder().decode(decodingType, from: data)
- return responses.enumerated().map({ (object) -> ParseError? in
+ return responses.enumerated().map({ (object) -> (Result) in
let response = responses[object.offset]
- if let error = response {
- return error
+ if response.success != nil {
+ return .success(())
} else {
- return nil
+ guard let parseError = response.error else {
+ return .failure(ParseError(code: .unknownError, message: "unknown error"))
+ }
+
+ return .failure(parseError)
}
})
} catch {
- guard (try? ParseCoding.jsonDecoder().decode(NoBody.self, from: data)) != nil else {
- return [ParseError(code: .unknownError, message: "decoding error: \(error)")]
+ guard let parseError = error as? ParseError else {
+ return [(.failure(ParseError(code: .unknownError, message: "decoding error: \(error)")))]
}
- return [nil]
+ return [(.failure(parseError))]
}
}
let batchCommand = BatchCommandNoBody(requests: commands)
- return RESTBatchCommandNoBodyType(method: .POST, path: .batch, body: batchCommand, mapper: mapper)
+ return RESTBatchCommandNoBodyType(method: .POST, path: .batch, body: batchCommand, mapper: mapper)
}
}
@@ -642,17 +645,21 @@ internal extension API {
internal extension API.NonParseBodyCommand {
// MARK: Deleting
- // swiftlint:disable:next line_length
- static func deleteCommand(_ object: T) throws -> API.NonParseBodyCommand where T: ParseObject {
+ static func deleteCommand(_ object: T) throws -> API.NonParseBodyCommand where T: ParseObject {
guard object.isSaved else {
throw ParseError(code: .unknownError, message: "Cannot Delete an object without id")
}
- return API.NonParseBodyCommand(
+ return API.NonParseBodyCommand(
method: .DELETE,
path: object.endpoint
- ) { (data) -> ParseError? in
- try? ParseCoding.jsonDecoder().decode(ParseError.self, from: data)
+ ) { (data) -> NoBody in
+ let error = try? ParseCoding.jsonDecoder().decode(ParseError.self, from: data)
+ if let error = error {
+ throw error
+ } else {
+ return NoBody()
+ }
}
}
} // swiftlint:disable:this file_length
diff --git a/Sources/ParseSwift/API/API.swift b/Sources/ParseSwift/API/API.swift
index 34fec5f91..f6088e118 100644
--- a/Sources/ParseSwift/API/API.swift
+++ b/Sources/ParseSwift/API/API.swift
@@ -31,7 +31,7 @@ public struct API {
case logout
case file(fileName: String)
case passwordReset
- case verificationEmailRequest
+ case verificationEmail
case functions(name: String)
case jobs(name: String)
case aggregate(className: String)
@@ -70,7 +70,7 @@ public struct API {
return "/files/\(fileName)"
case .passwordReset:
return "/requestPasswordReset"
- case .verificationEmailRequest:
+ case .verificationEmail:
return "/verificationEmailRequest"
case .functions(name: let name):
return "/functions/\(name)"
diff --git a/Sources/ParseSwift/API/BatchUtils.swift b/Sources/ParseSwift/API/BatchUtils.swift
index 7e6dd92bd..4d01208d6 100644
--- a/Sources/ParseSwift/API/BatchUtils.swift
+++ b/Sources/ParseSwift/API/BatchUtils.swift
@@ -13,8 +13,8 @@ typealias ParseObjectBatchResponse = [(Result)]
// swiftlint:disable line_length
typealias RESTBatchCommandType = API.Command, ParseObjectBatchResponse> where T: ParseObject
-typealias ParseObjectBatchCommandNoBody = BatchCommandNoBody
-typealias ParseObjectBatchResponseNoBody = [ParseError?]
+typealias ParseObjectBatchCommandNoBody = BatchCommandNoBody
+typealias ParseObjectBatchResponseNoBody = [(Result)]
typealias RESTBatchCommandNoBodyType = API.NonParseBodyCommand, ParseObjectBatchResponseNoBody> where T: Encodable
/*
typealias ParseObjectBatchCommandEncodable = BatchCommand where T: ParseType
diff --git a/Sources/ParseSwift/Authentication/3rd Party/ParseApple.swift b/Sources/ParseSwift/Authentication/3rd Party/ParseApple.swift
index bf9c3289f..fa872a43e 100644
--- a/Sources/ParseSwift/Authentication/3rd Party/ParseApple.swift
+++ b/Sources/ParseSwift/Authentication/3rd Party/ParseApple.swift
@@ -7,6 +7,9 @@
//
import Foundation
+#if canImport(Combine)
+import Combine
+#endif
// swiftlint:disable line_length
@@ -90,6 +93,41 @@ public extension ParseApple {
callbackQueue: callbackQueue,
completion: completion)
}
+
+ #if canImport(Combine)
+
+ /**
+ Login a `ParseUser` *asynchronously* using Apple authentication. Publishes when complete.
+ - parameter user: The `user` from `ASAuthorizationAppleIDCredential`.
+ - parameter identityToken: The `identityToken` from `ASAuthorizationAppleIDCredential`.
+ - parameter options: A set of header options sent to the server. Defaults to an empty set.
+ - returns: A publisher that eventually produces a single value and then finishes or fails.
+ */
+ @available(macOS 10.15, iOS 13.0, macCatalyst 13.0, watchOS 6.0, tvOS 13.0, *)
+ func loginPublisher(user: String,
+ identityToken: String,
+ options: API.Options = []) -> Future {
+ loginPublisher(authData: AuthenticationKeys.id.makeDictionary(user: user, identityToken: identityToken),
+ options: options)
+ }
+
+ @available(macOS 10.15, iOS 13.0, macCatalyst 13.0, watchOS 6.0, tvOS 13.0, *)
+ func loginPublisher(authData: [String: String]?,
+ options: API.Options = []) -> Future {
+ guard AuthenticationKeys.id.verifyMandatoryKeys(authData: authData),
+ let authData = authData else {
+ let error = ParseError(code: .unknownError,
+ message: "Should have authData in consisting of keys \"id\" and \"token\".")
+ return Future { promise in
+ promise(.failure(error))
+ }
+ }
+ return AuthenticatedUser.loginPublisher(Self.__type,
+ authData: authData,
+ options: options)
+ }
+
+ #endif
}
// MARK: Link
@@ -133,6 +171,41 @@ public extension ParseApple {
callbackQueue: callbackQueue,
completion: completion)
}
+
+ #if canImport(Combine)
+
+ /**
+ Link the *current* `ParseUser` *asynchronously* using Apple authentication. Publishes when complete.
+ - parameter user: The `user` from `ASAuthorizationAppleIDCredential`.
+ - parameter identityToken: The `identityToken` from `ASAuthorizationAppleIDCredential`.
+ - parameter options: A set of header options sent to the server. Defaults to an empty set.
+ - returns: A publisher that eventually produces a single value and then finishes or fails.
+ */
+ @available(macOS 10.15, iOS 13.0, macCatalyst 13.0, watchOS 6.0, tvOS 13.0, *)
+ func linkPublisher(user: String,
+ identityToken: String,
+ options: API.Options = []) -> Future {
+ linkPublisher(authData: AuthenticationKeys.id.makeDictionary(user: user, identityToken: identityToken),
+ options: options)
+ }
+
+ @available(macOS 10.15, iOS 13.0, macCatalyst 13.0, watchOS 6.0, tvOS 13.0, *)
+ func linkPublisher(authData: [String: String]?,
+ options: API.Options = []) -> Future {
+ guard AuthenticationKeys.id.verifyMandatoryKeys(authData: authData),
+ let authData = authData else {
+ let error = ParseError(code: .unknownError,
+ message: "Should have authData in consisting of keys \"id\" and \"token\".")
+ return Future { promise in
+ promise(.failure(error))
+ }
+ }
+ return AuthenticatedUser.linkPublisher(Self.__type,
+ authData: authData,
+ options: options)
+ }
+
+ #endif
}
// MARK: 3rd Party Authentication - ParseApple
diff --git a/Sources/ParseSwift/Authentication/Internal/ParseAnonymous.swift b/Sources/ParseSwift/Authentication/Internal/ParseAnonymous.swift
index 523c47969..f8b84b1b3 100644
--- a/Sources/ParseSwift/Authentication/Internal/ParseAnonymous.swift
+++ b/Sources/ParseSwift/Authentication/Internal/ParseAnonymous.swift
@@ -7,6 +7,9 @@
//
import Foundation
+#if canImport(Combine)
+import Combine
+#endif
/**
Provides utility functions for working with Anonymously logged-in users.
@@ -68,6 +71,18 @@ public extension ParseAnonymous {
callbackQueue: callbackQueue,
completion: completion)
}
+
+ #if canImport(Combine)
+
+ @available(macOS 10.15, iOS 13.0, macCatalyst 13.0, watchOS 6.0, tvOS 13.0, *)
+ func loginPublisher(authData: [String: String]? = nil,
+ options: API.Options = []) -> Future {
+ AuthenticatedUser.loginPublisher(__type,
+ authData: AuthenticationKeys.id.makeDictionary(),
+ options: options)
+ }
+
+ #endif
}
// MARK: Link
@@ -81,6 +96,18 @@ public extension ParseAnonymous {
completion(.failure(ParseError(code: .unknownError, message: "Not supported")))
}
}
+
+ #if canImport(Combine)
+
+ @available(macOS 10.15, iOS 13.0, macCatalyst 13.0, watchOS 6.0, tvOS 13.0, *)
+ func linkPublisher(authData: [String: String]?,
+ options: API.Options) -> Future {
+ Future { promise in
+ promise(.failure(ParseError(code: .unknownError, message: "Not supported")))
+ }
+ }
+
+ #endif
}
// MARK: ParseAnonymous
diff --git a/Sources/ParseSwift/Authentication/Protocols/ParseAuthentication+combine.swift b/Sources/ParseSwift/Authentication/Protocols/ParseAuthentication+combine.swift
new file mode 100644
index 000000000..558d0bfc3
--- /dev/null
+++ b/Sources/ParseSwift/Authentication/Protocols/ParseAuthentication+combine.swift
@@ -0,0 +1,97 @@
+//
+// ParseAuthentication+combine.swift
+// ParseSwift
+//
+// Created by Corey Baker on 1/30/21.
+// Copyright © 2021 Parse Community. All rights reserved.
+//
+
+#if canImport(Combine)
+import Foundation
+import Combine
+
+// MARK: Convenience Implementations - Combine
+@available(macOS 10.15, iOS 13.0, macCatalyst 13.0, watchOS 6.0, tvOS 13.0, *)
+public extension ParseAuthentication {
+
+ func unlinkPublisher(_ user: AuthenticatedUser,
+ options: API.Options = []) -> Future {
+ user.unlinkPublisher(__type, options: options)
+ }
+
+ func unlinkPublisher(options: API.Options = []) -> Future {
+ guard let current = AuthenticatedUser.current else {
+ let error = ParseError(code: .invalidLinkedSession, message: "No current ParseUser.")
+ return Future { promise in
+ promise(.failure(error))
+ }
+ }
+ return unlinkPublisher(current, options: options)
+ }
+}
+
+@available(macOS 10.15, iOS 13.0, macCatalyst 13.0, watchOS 6.0, tvOS 13.0, *)
+public extension ParseUser {
+
+ // MARK: 3rd Party Authentication - Login Combine
+
+ /**
+ Makes an *asynchronous* request to log in a user with specified credentials.
+ Publishes an instance of the successfully logged in `ParseUser`.
+
+ This also caches the user locally so that calls to *current* will use the latest logged in user.
+ - parameter type: The authentication type.
+ - parameter authData: The data that represents the authentication.
+ - parameter options: A set of header options sent to the server. Defaults to an empty set.
+ - returns: A publisher that eventually produces a single value and then finishes or fails.
+ */
+ static func loginPublisher(_ type: String,
+ authData: [String: String],
+ options: API.Options = []) -> Future {
+ Future { promise in
+ Self.login(type,
+ authData: authData,
+ options: options,
+ completion: promise)
+ }
+ }
+
+ /**
+ Unlink the authentication type *asynchronously*. Publishes when complete.
+ - parameter type: The type to unlink. The user must be logged in on this device.
+ - parameter options: A set of header options sent to the server. Defaults to an empty set.
+ - returns: A publisher that eventually produces a single value and then finishes or fails.
+ */
+ func unlinkPublisher(_ type: String,
+ options: API.Options = []) -> Future {
+ Future { promise in
+ self.unlink(type,
+ options: options,
+ completion: promise)
+ }
+ }
+
+ /**
+ Makes an *asynchronous* request to link a user with specified credentials. The user should already be logged in.
+ Publishes an instance of the successfully linked `ParseUser`.
+
+ This also caches the user locally so that calls to *current* will use the latest logged in user.
+ - parameter type: The authentication type.
+ - parameter authData: The data that represents the authentication.
+ - parameter options: A set of header options sent to the server. Defaults to an empty set.
+ - returns: A publisher that eventually produces a single value and then finishes or fails.
+ */
+ static func linkPublisher(_ type: String,
+ authData: [String: String],
+ options: API.Options = []) -> Future {
+ Future { promise in
+ Self.link(type,
+ authData: authData,
+ options: options,
+ completion: promise)
+ }
+ }
+
+}
+
+#endif
diff --git a/Sources/ParseSwift/Authentication/Protocols/ParseAuthentication.swift b/Sources/ParseSwift/Authentication/Protocols/ParseAuthentication.swift
index 412d17734..01957b0cf 100644
--- a/Sources/ParseSwift/Authentication/Protocols/ParseAuthentication.swift
+++ b/Sources/ParseSwift/Authentication/Protocols/ParseAuthentication.swift
@@ -7,6 +7,9 @@
//
import Foundation
+#if canImport(Combine)
+import Combine
+#endif
/**
Objects that conform to the `ParseAuthentication` protocol provide
@@ -74,7 +77,7 @@ public protocol ParseAuthentication: Codable {
- parameter options: A set of header options sent to the server. Defaults to an empty set.
- parameter callbackQueue: The queue to return to after completion. Default value of .main.
- parameter completion: The block to execute.
- It should have the following argument signature: `(Result)`.
+ It should have the following argument signature: `(Result)`.
*/
func unlink(options: API.Options,
callbackQueue: DispatchQueue,
@@ -93,6 +96,53 @@ public protocol ParseAuthentication: Codable {
- returns: The user whose autentication type was stripped. This modified user has not been saved.
*/
func strip(_ user: AuthenticatedUser) -> AuthenticatedUser
+
+ #if canImport(Combine)
+ /**
+ Login a `ParseUser` *asynchronously* using the respective authentication type.
+ - parameter authData: The authData for the respective authentication type.
+ - parameter options: A set of header options sent to the server. Defaults to an empty set.
+ - parameter callbackQueue: The queue to return to after completion. Default value of .main.
+ - parameter completion: The block to execute.
+ */
+ @available(macOS 10.15, iOS 13.0, macCatalyst 13.0, watchOS 6.0, tvOS 13.0, *)
+ func loginPublisher(authData: [String: String]?,
+ options: API.Options) -> Future
+
+ /**
+ Link the *current* `ParseUser` *asynchronously* using the respective authentication type.
+ - parameter authData: The authData for the respective authentication type.
+ - parameter options: A set of header options sent to the server. Defaults to an empty set.
+ - parameter callbackQueue: The queue to return to after completion. Default value of .main.
+ - parameter completion: The block to execute.
+ */
+ @available(macOS 10.15, iOS 13.0, macCatalyst 13.0, watchOS 6.0, tvOS 13.0, *)
+ func linkPublisher(authData: [String: String]?,
+ options: API.Options) -> Future
+
+ /**
+ Unlink the `ParseUser` *asynchronously* from the respective authentication type.
+ - parameter user: The `ParseUser` to unlink. The user must be logged in on this device.
+ - parameter options: A set of header options sent to the server. Defaults to an empty set.
+ - parameter callbackQueue: The queue to return to after completion. Default value of .main.
+ - parameter completion: The block to execute.
+ It should have the following argument signature: `(Result)`.
+ */
+ @available(macOS 10.15, iOS 13.0, macCatalyst 13.0, watchOS 6.0, tvOS 13.0, *)
+ func unlinkPublisher(_ user: AuthenticatedUser,
+ options: API.Options) -> Future
+
+ /**
+ Unlink the *current* `ParseUser` *asynchronously* from the respective authentication type.
+ - parameter options: A set of header options sent to the server. Defaults to an empty set.
+ - parameter callbackQueue: The queue to return to after completion. Default value of .main.
+ - parameter completion: The block to execute.
+ It should have the following argument signature: `(Result)`.
+ */
+ @available(macOS 10.15, iOS 13.0, macCatalyst 13.0, watchOS 6.0, tvOS 13.0, *)
+ func unlinkPublisher(options: API.Options) -> Future
+
+ #endif
}
// MARK: Convenience Implementations
@@ -188,7 +238,7 @@ public extension ParseUser {
static func login(_ type: String,
authData: [String: String],
options: API.Options,
- callbackQueue: DispatchQueue,
+ callbackQueue: DispatchQueue = .main,
completion: @escaping (Result) -> Void) {
let body = SignupLoginBody(authData: [type: authData])
@@ -201,6 +251,12 @@ public extension ParseUser {
}
// MARK: 3rd Party Authentication - Link
+ /**
+ Whether the `ParseUser` is logged in with the respective authentication string type.
+ - parameter type: The authentication type to check. The user must be logged in on this device.
+ - returns: `true` if the `ParseUser` is logged in via the repective
+ authentication type. `false` if the user is not.
+ */
func isLinked(with type: String) -> Bool {
guard let authData = self.authData?[type] else {
return false
@@ -208,9 +264,17 @@ public extension ParseUser {
return authData != nil
}
+ /**
+ Unlink the authentication type *asynchronously*.
+ - parameter type: The type to unlink. The user must be logged in on this device.
+ - parameter options: A set of header options sent to the server. Defaults to an empty set.
+ - parameter callbackQueue: The queue to return to after completion. Default value of .main.
+ - parameter completion: The block to execute.
+ It should have the following argument signature: `(Result)`.
+ */
func unlink(_ type: String,
- options: API.Options,
- callbackQueue: DispatchQueue,
+ options: API.Options = [],
+ callbackQueue: DispatchQueue = .main,
completion: @escaping (Result) -> Void) {
guard let current = Self.current,
@@ -281,8 +345,8 @@ public extension ParseUser {
*/
static func link(_ type: String,
authData: [String: String],
- options: API.Options,
- callbackQueue: DispatchQueue,
+ options: API.Options = [],
+ callbackQueue: DispatchQueue = .main,
completion: @escaping (Result) -> Void) {
guard let current = Self.current else {
let error = ParseError(code: .unknownError, message: "Must be logged in to link user")
diff --git a/Sources/ParseSwift/LiveQuery/LiveQuerySocket.swift b/Sources/ParseSwift/LiveQuery/LiveQuerySocket.swift
index 2de4525ce..20ef4af09 100644
--- a/Sources/ParseSwift/LiveQuery/LiveQuerySocket.swift
+++ b/Sources/ParseSwift/LiveQuery/LiveQuerySocket.swift
@@ -11,7 +11,7 @@ import Foundation
import FoundationNetworking
#endif
-@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
+@available(macOS 10.15, iOS 13.0, macCatalyst 13.0, watchOS 6.0, tvOS 13.0, *)
final class LiveQuerySocket: NSObject {
private var session: URLSession!
var delegates = [URLSessionWebSocketTask: LiveQuerySocketDelegate]()
@@ -36,7 +36,7 @@ final class LiveQuerySocket: NSObject {
}
// MARK: Status
-@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
+@available(macOS 10.15, iOS 13.0, macCatalyst 13.0, watchOS 6.0, tvOS 13.0, *)
extension LiveQuerySocket {
enum Status: String {
case open
@@ -45,7 +45,7 @@ extension LiveQuerySocket {
}
// MARK: Connect
-@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
+@available(macOS 10.15, iOS 13.0, macCatalyst 13.0, watchOS 6.0, tvOS 13.0, *)
extension LiveQuerySocket {
func connect(task: URLSessionWebSocketTask,
completion: @escaping (Error?) -> Void) throws {
@@ -63,7 +63,7 @@ extension LiveQuerySocket {
}
// MARK: Send
-@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
+@available(macOS 10.15, iOS 13.0, macCatalyst 13.0, watchOS 6.0, tvOS 13.0, *)
extension LiveQuerySocket {
func send(_ data: Data, task: URLSessionWebSocketTask, completion: @escaping (Error?) -> Void) {
guard let encodedAsString = String(data: data, encoding: .utf8) else {
@@ -80,7 +80,7 @@ extension LiveQuerySocket {
}
// MARK: Receive
-@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
+@available(macOS 10.15, iOS 13.0, macCatalyst 13.0, watchOS 6.0, tvOS 13.0, *)
extension LiveQuerySocket {
func receive(_ task: URLSessionWebSocketTask) {
@@ -105,13 +105,13 @@ extension LiveQuerySocket {
}
// MARK: URLSession
-@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
+@available(macOS 10.15, iOS 13.0, macCatalyst 13.0, watchOS 6.0, tvOS 13.0, *)
extension URLSession {
static let liveQuery = LiveQuerySocket()
}
// MARK: URLSessionWebSocketDelegate
-@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
+@available(macOS 10.15, iOS 13.0, macCatalyst 13.0, watchOS 6.0, tvOS 13.0, *)
extension LiveQuerySocket: URLSessionWebSocketDelegate {
func urlSession(_ session: URLSession,
webSocketTask: URLSessionWebSocketTask,
diff --git a/Sources/ParseSwift/LiveQuery/ParseLiveQuery.swift b/Sources/ParseSwift/LiveQuery/ParseLiveQuery.swift
index 7e7361c80..a71d42a26 100644
--- a/Sources/ParseSwift/LiveQuery/ParseLiveQuery.swift
+++ b/Sources/ParseSwift/LiveQuery/ParseLiveQuery.swift
@@ -44,7 +44,7 @@ import FoundationNetworking
running. Initializing new instances will create a new task/connection to the `ParseLiveQuery` server.
When an instance is deinitialized it will automatically close it's connection gracefully.
*/
-@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
+@available(macOS 10.15, iOS 13.0, macCatalyst 13.0, watchOS 6.0, tvOS 13.0, *)
public final class ParseLiveQuery: NSObject {
// Queues
let synchronizationQueue: DispatchQueue
@@ -211,7 +211,7 @@ public final class ParseLiveQuery: NSObject {
}
// MARK: Helpers
-@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
+@available(macOS 10.15, iOS 13.0, macCatalyst 13.0, watchOS 6.0, tvOS 13.0, *)
extension ParseLiveQuery {
static var client = try? ParseLiveQuery()
@@ -293,7 +293,7 @@ extension ParseLiveQuery {
}
// MARK: Delegate
-@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
+@available(macOS 10.15, iOS 13.0, macCatalyst 13.0, watchOS 6.0, tvOS 13.0, *)
extension ParseLiveQuery: LiveQuerySocketDelegate {
func status(_ status: LiveQuerySocket.Status) {
@@ -483,7 +483,7 @@ extension ParseLiveQuery: LiveQuerySocketDelegate {
}
// MARK: Connection
-@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
+@available(macOS 10.15, iOS 13.0, macCatalyst 13.0, watchOS 6.0, tvOS 13.0, *)
extension ParseLiveQuery {
/// Manually establish a connection to the `ParseLiveQuery` Server.
@@ -557,7 +557,7 @@ extension ParseLiveQuery {
}
// MARK: SubscriptionRecord
-@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
+@available(macOS 10.15, iOS 13.0, macCatalyst 13.0, watchOS 6.0, tvOS 13.0, *)
extension ParseLiveQuery {
class SubscriptionRecord {
@@ -612,7 +612,7 @@ extension ParseLiveQuery {
}
// MARK: Subscribing
-@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
+@available(macOS 10.15, iOS 13.0, macCatalyst 13.0, watchOS 6.0, tvOS 13.0, *)
extension ParseLiveQuery {
func subscribe(_ query: Query) throws -> Subscription {
@@ -641,7 +641,7 @@ extension ParseLiveQuery {
}
// MARK: Unsubscribing
-@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
+@available(macOS 10.15, iOS 13.0, macCatalyst 13.0, watchOS 6.0, tvOS 13.0, *)
extension ParseLiveQuery {
func unsubscribe(_ query: Query) throws where T: ParseObject {
@@ -674,7 +674,7 @@ extension ParseLiveQuery {
}
// MARK: Updating
-@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
+@available(macOS 10.15, iOS 13.0, macCatalyst 13.0, watchOS 6.0, tvOS 13.0, *)
extension ParseLiveQuery {
func update(_ handler: T) throws where T: ParseSubscription {
@@ -691,9 +691,9 @@ extension ParseLiveQuery {
}
// MARK: ParseLiveQuery - Subscribe
-@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
+@available(macOS 10.15, iOS 13.0, macCatalyst 13.0, watchOS 6.0, tvOS 13.0, *)
public extension Query {
- #if !os(Linux)
+ #if canImport(Combine)
/**
Registers the query for live updates, using the default subscription handler,
and the default `ParseLiveQuery` client. Suitable for `ObjectObserved`
@@ -760,7 +760,7 @@ public extension Query {
}
// MARK: ParseLiveQuery - Unsubscribe
-@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
+@available(macOS 10.15, iOS 13.0, macCatalyst 13.0, watchOS 6.0, tvOS 13.0, *)
public extension Query {
/**
Unsubscribes all current subscriptions for a given query on the default
@@ -800,7 +800,7 @@ public extension Query {
}
// MARK: ParseLiveQuery - Update
-@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
+@available(macOS 10.15, iOS 13.0, macCatalyst 13.0, watchOS 6.0, tvOS 13.0, *)
public extension Query {
/**
Updates an existing subscription with a new query on the default `ParseLiveQuery` client.
diff --git a/Sources/ParseSwift/LiveQuery/Protocols/LiveQuerySocketDelegate.swift b/Sources/ParseSwift/LiveQuery/Protocols/LiveQuerySocketDelegate.swift
index 1ab766170..e19070e67 100644
--- a/Sources/ParseSwift/LiveQuery/Protocols/LiveQuerySocketDelegate.swift
+++ b/Sources/ParseSwift/LiveQuery/Protocols/LiveQuerySocketDelegate.swift
@@ -11,7 +11,7 @@ import Foundation
import FoundationNetworking
#endif
-@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
+@available(macOS 10.15, iOS 13.0, macCatalyst 13.0, watchOS 6.0, tvOS 13.0, *)
protocol LiveQuerySocketDelegate: AnyObject {
func status(_ status: LiveQuerySocket.Status)
func close(useDedicatedQueue: Bool)
diff --git a/Sources/ParseSwift/LiveQuery/Protocols/ParseLiveQueryDelegate.swift b/Sources/ParseSwift/LiveQuery/Protocols/ParseLiveQueryDelegate.swift
index 9a717802a..1580fcadc 100644
--- a/Sources/ParseSwift/LiveQuery/Protocols/ParseLiveQueryDelegate.swift
+++ b/Sources/ParseSwift/LiveQuery/Protocols/ParseLiveQueryDelegate.swift
@@ -14,7 +14,7 @@ import FoundationNetworking
// swiftlint:disable line_length
///Receive/respond to notifications from the ParseLiveQuery Server.
-@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
+@available(macOS 10.15, iOS 13.0, macCatalyst 13.0, watchOS 6.0, tvOS 13.0, *)
public protocol ParseLiveQueryDelegate: AnyObject {
/**
@@ -59,7 +59,7 @@ public protocol ParseLiveQueryDelegate: AnyObject {
#endif
}
-@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
+@available(macOS 10.15, iOS 13.0, macCatalyst 13.0, watchOS 6.0, tvOS 13.0, *)
extension ParseLiveQueryDelegate {
func received(_ challenge: URLAuthenticationChallenge,
completionHandler: @escaping (URLSession.AuthChallengeDisposition,
diff --git a/Sources/ParseSwift/LiveQuery/Subscription.swift b/Sources/ParseSwift/LiveQuery/Subscription.swift
index 6e69c9bb6..e23264987 100644
--- a/Sources/ParseSwift/LiveQuery/Subscription.swift
+++ b/Sources/ParseSwift/LiveQuery/Subscription.swift
@@ -56,14 +56,13 @@ private func == (lhs: Event, rhs: Event) -> Bool {
}
}
-#if !os(Linux)
+#if canImport(Combine)
/**
A default implementation of the `ParseSubscription` protocol. Suitable for `ObjectObserved`
as the subscription can be used as a SwiftUI publisher. Meaning it can serve
- indepedently as a ViewModel in MVVM. Also provides a publisher for pull responses of query such as:
- `find`, `first`, `count`, and `aggregate`.
+ indepedently as a ViewModel in MVVM.
*/
-@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
+@available(macOS 10.15, iOS 13.0, macCatalyst 13.0, watchOS 6.0, tvOS 13.0, *)
open class Subscription: ParseSubscription, ObservableObject {
//The query subscribed to.
public var query: Query
@@ -103,57 +102,6 @@ open class Subscription: ParseSubscription, ObservableObject {
}
}
- /// The objects found in a `find`, `first`, or `aggregate`
- /// query.
- /// - note: this will only countain one item for `first`.
- public internal(set) var results: [T]? {
- willSet {
- if newValue != nil {
- resultsCodable = nil
- count = nil
- error = nil
- objectWillChange.send()
- }
- }
- }
-
- /// The number of items found in a `count` query.
- public internal(set) var count: Int? {
- willSet {
- if newValue != nil {
- results = nil
- resultsCodable = nil
- error = nil
- objectWillChange.send()
- }
- }
- }
-
- /// Results of a `explain` or `hint` query.
- public internal(set) var resultsCodable: AnyCodable? {
- willSet {
- if newValue != nil {
- results = nil
- count = nil
- error = nil
- objectWillChange.send()
- }
- }
- }
-
- /// If an error occured during a `find`, `first`, `count`, or `aggregate`
- /// query.
- public internal(set) var error: ParseError? {
- willSet {
- if newValue != nil {
- count = nil
- results = nil
- resultsCodable = nil
- objectWillChange.send()
- }
- }
- }
-
/**
Creates a new subscription that can be used to handle updates.
*/
@@ -180,154 +128,6 @@ open class Subscription: ParseSubscription, ObservableObject {
open func didUnsubscribe() {
self.unsubscribed = query
}
-
- /**
- Finds objects and publishes them as `results` afterwards.
-
- - parameter options: A set of header options sent to the server. Defaults to an empty set.
- - parameter callbackQueue: The queue to return to after completion. Default value of `.main`.
- */
- open func find(options: API.Options = [], callbackQueue: DispatchQueue = .main) {
- query.find(options: options, callbackQueue: callbackQueue) { result in
- switch result {
-
- case .success(let results):
- self.results = results
- case .failure(let error):
- self.error = error
- }
- }
- }
-
- /**
- Finds objects and publishes them as `resultsCodable` afterwards.
-
- - parameter explain: Used to toggle the information on the query plan.
- - parameter hint: String or Object of index that should be used when executing query.
- - parameter options: A set of header options sent to the server. Defaults to an empty set.
- - parameter callbackQueue: The queue to return to after completion. Default value of `.main`.
- */
- open func find(explain: Bool,
- hint: String? = nil,
- options: API.Options = [],
- callbackQueue: DispatchQueue = .main) {
- query.find(explain: explain, hint: hint, options: options, callbackQueue: callbackQueue) { result in
- switch result {
-
- case .success(let results):
- self.resultsCodable = results
- case .failure(let error):
- self.error = error
- }
- }
- }
-
- /**
- Gets an object and publishes them as `results` afterwards.
-
- - warning: This method mutates the query. It will reset the limit to `1`.
- - parameter options: A set of header options sent to the server. Defaults to an empty set.
- - parameter callbackQueue: The queue to return to after completion. Default value of `.main`.
- */
- open func first(options: API.Options = [], callbackQueue: DispatchQueue = .main) {
- query.first(options: options, callbackQueue: callbackQueue) { result in
- switch result {
-
- case .success(let results):
- self.results = [results]
- case .failure(let error):
- self.error = error
- }
- }
- }
-
- /**
- Gets an object and publishes them as `resultsCodable` afterwards.
-
- - warning: This method mutates the query. It will reset the limit to `1`.
- - parameter explain: Used to toggle the information on the query plan.
- - parameter hint: String or Object of index that should be used when executing query.
- - parameter options: A set of header options sent to the server. Defaults to an empty set.
- - parameter callbackQueue: The queue to return to after completion. Default value of `.main`.
- */
- open func first(explain: Bool,
- hint: String? = nil,
- options: API.Options = [],
- callbackQueue: DispatchQueue = .main) {
- query.first(explain: explain, hint: hint, options: options, callbackQueue: callbackQueue) { result in
- switch result {
-
- case .success(let results):
- self.resultsCodable = results
- case .failure(let error):
- self.error = error
- }
- }
- }
-
- /**
- Counts objects and publishes them as `count` afterwards.
-
- - parameter options: A set of header options sent to the server. Defaults to an empty set.
- - parameter callbackQueue: The queue to return to after completion. Default value of `.main`.
- */
- open func count(options: API.Options = [], callbackQueue: DispatchQueue = .main) {
- query.count(options: options, callbackQueue: callbackQueue) { result in
- switch result {
-
- case .success(let results):
- self.count = results
- case .failure(let error):
- self.error = error
- }
- }
- }
-
- /**
- Counts objects and publishes them as `resultsCodable` afterwards.
-
- - parameter explain: Used to toggle the information on the query plan.
- - parameter hint: String or Object of index that should be used when executing query.
- - parameter options: A set of header options sent to the server. Defaults to an empty set.
- - parameter callbackQueue: The queue to return to after completion. Default value of `.main`.
- */
- open func count(explain: Bool,
- hint: String? = nil,
- options: API.Options = [],
- callbackQueue: DispatchQueue = .main) {
- query.count(explain: explain, hint: hint, options: options) { result in
- switch result {
-
- case .success(let results):
- self.resultsCodable = results
- case .failure(let error):
- self.error = error
- }
- }
- }
-
- /**
- Executes an aggregate query and publishes the results as `results` afterwards.
-
- - requires: `.useMasterKey` has to be available and passed as one of the set of `options`.
- - parameter pipeline: A pipeline of stages to process query.
- - parameter options: A set of header options sent to the server. Defaults to an empty set.
- - parameter callbackQueue: The queue to return to after completion. Default value of `.main`.
- - warning: This hasn't been tested thoroughly.
- */
- open func aggregate(_ pipeline: Query.AggregateType,
- options: API.Options = [],
- callbackQueue: DispatchQueue = .main) {
- query.aggregate(pipeline, options: options, callbackQueue: callbackQueue) { result in
- switch result {
-
- case .success(let results):
- self.results = results
- case .failure(let error):
- self.error = error
- }
- }
- }
}
#endif
diff --git a/Sources/ParseSwift/Objects/ParseInstallation+combine.swift b/Sources/ParseSwift/Objects/ParseInstallation+combine.swift
new file mode 100644
index 000000000..a3ab82a76
--- /dev/null
+++ b/Sources/ParseSwift/Objects/ParseInstallation+combine.swift
@@ -0,0 +1,109 @@
+//
+// ParseInstallation+combine.swift
+// ParseSwift
+//
+// Created by Corey Baker on 1/29/21.
+// Copyright © 2021 Parse Community. All rights reserved.
+//
+
+#if canImport(Combine)
+import Foundation
+import Combine
+
+// MARK: Combine
+@available(macOS 10.15, iOS 13.0, macCatalyst 13.0, watchOS 6.0, tvOS 13.0, *)
+public extension ParseInstallation {
+
+ // MARK: Fetchable - Combine
+ /**
+ Fetches the `ParseInstallation` *aynchronously* with the current data from the server
+ and sets an error if one occurs. Publishes when complete.
+
+ - parameter options: A set of header options sent to the server. Defaults to an empty set.
+ - returns: A publisher that eventually produces a single value and then finishes or fails.
+ - important: If an object fetched has the same objectId as current, it will automatically update the current.
+ */
+ func fetchPublisher(options: API.Options = []) -> Future {
+ Future { promise in
+ self.fetch(options: options,
+ completion: promise)
+ }
+ }
+
+ // MARK: Savable - Combine
+ /**
+ Saves the `ParseInstallation` *asynchronously* and publishes when complete.
+
+ - parameter options: A set of header options sent to the server. Defaults to an empty set.
+ - returns: A publisher that eventually produces a single value and then finishes or fails.
+ - important: If an object saved has the same objectId as current, it will automatically update the current.
+ */
+ func savePublisher(options: API.Options = []) -> Future {
+ Future { promise in
+ self.save(options: options,
+ completion: promise)
+ }
+ }
+
+ // MARK: Deletable - Combine
+ /**
+ Deletes the `ParseInstallation` *asynchronously* and publishes when complete.
+
+ - parameter options: A set of header options sent to the server. Defaults to an empty set.
+ - returns: A publisher that eventually produces a single value and then finishes or fails.
+ - important: If an object deleted has the same objectId as current, it will automatically update the current.
+ */
+ func deletePublisher(options: API.Options = []) -> Future {
+ Future { promise in
+ self.delete(options: options, completion: promise)
+ }
+ }
+}
+
+// MARK: Batch Support - Combine
+@available(macOS 10.15, iOS 13.0, macCatalyst 13.0, watchOS 6.0, tvOS 13.0, *)
+public extension Sequence where Element: ParseInstallation {
+ /**
+ Fetches a collection of installations *aynchronously* with the current data from the server and sets
+ an error if one occurs. Publishes when complete.
+
+ - parameter options: A set of header options sent to the server. Defaults to an empty set.
+ - returns: A publisher that eventually produces a single value and then finishes or fails.
+ - important: If an object fetched has the same objectId as current, it will automatically update the current.
+ */
+ func fetchAllPublisher(options: API.Options = []) -> Future<[(Result)], ParseError> {
+ Future { promise in
+ self.fetchAll(options: options,
+ completion: promise)
+ }
+ }
+
+ /**
+ Saves a collection of installations *asynchronously* and publishes when complete.
+
+ - parameter options: A set of header options sent to the server. Defaults to an empty set.
+ - returns: A publisher that eventually produces a single value and then finishes or fails.
+ - important: If an object saved has the same objectId as current, it will automatically update the current.
+ */
+ func saveAllPublisher(options: API.Options = []) -> Future<[(Result)], ParseError> {
+ Future { promise in
+ self.saveAll(options: options,
+ completion: promise)
+ }
+ }
+
+ /**
+ Deletes a collection of installations *asynchronously* and publishes when complete.
+
+ - parameter options: A set of header options sent to the server. Defaults to an empty set.
+ - returns: A publisher that eventually produces a single value and then finishes or fails.
+ - important: If an object deleted has the same objectId as current, it will automatically update the current.
+ */
+ func deleteAllPublisher(options: API.Options = []) -> Future<[(Result)], ParseError> {
+ Future { promise in
+ self.deleteAll(options: options, completion: promise)
+ }
+ }
+}
+
+#endif
diff --git a/Sources/ParseSwift/Objects/ParseInstallation.swift b/Sources/ParseSwift/Objects/ParseInstallation.swift
index 412e5113e..6da8db305 100644
--- a/Sources/ParseSwift/Objects/ParseInstallation.swift
+++ b/Sources/ParseSwift/Objects/ParseInstallation.swift
@@ -525,13 +525,13 @@ extension ParseInstallation {
- parameter callbackQueue: The queue to return to after completion. Default
value of .main.
- parameter completion: The block to execute when completed.
- It should have the following argument signature: `(ParseError?)`.
+ It should have the following argument signature: `(Result)`.
- important: If an object deleted has the same objectId as current, it will automatically update the current.
*/
public func delete(
options: API.Options = [],
callbackQueue: DispatchQueue = .main,
- completion: @escaping (ParseError?) -> Void
+ completion: @escaping (Result) -> Void
) {
do {
try deleteCommand()
@@ -539,35 +539,40 @@ extension ParseInstallation {
callbackQueue.async {
switch result {
- case .success(let error):
+ case .success:
Self.updateKeychainIfNeeded([self], deleting: true)
- completion(error)
+ completion(.success(()))
case .failure(let error):
- completion(error)
+ completion(.failure(error))
}
}
}
} catch let error as ParseError {
callbackQueue.async {
- completion(error)
+ completion(.failure(error))
}
} catch {
callbackQueue.async {
- completion(ParseError(code: .unknownError, message: error.localizedDescription))
+ completion(.failure(ParseError(code: .unknownError, message: error.localizedDescription)))
}
}
}
- func deleteCommand() throws -> API.NonParseBodyCommand {
+ func deleteCommand() throws -> API.NonParseBodyCommand {
guard isSaved else {
throw ParseError(code: .unknownError, message: "Cannot Delete an object without id")
}
- return API.NonParseBodyCommand(
+ return API.NonParseBodyCommand(
method: .DELETE,
path: endpoint
- ) { (data) -> ParseError? in
- try? ParseCoding.jsonDecoder().decode(ParseError.self, from: data)
+ ) { (data) -> NoBody in
+ let error = try? ParseCoding.jsonDecoder().decode(ParseError.self, from: data)
+ if let error = error {
+ throw error
+ } else {
+ return NoBody()
+ }
}
}
}
@@ -858,13 +863,13 @@ public extension Sequence where Element: ParseInstallation {
- important: If an object deleted has the same objectId as current, it will automatically update the current.
*/
func deleteAll(batchLimit limit: Int? = nil,
- options: API.Options = []) throws -> [ParseError?] {
+ options: API.Options = []) throws -> [(Result)] {
let batchLimit = limit != nil ? limit! : ParseConstants.batchLimit
- var returnBatch = [ParseError?]()
+ var returnBatch = [(Result)]()
let commands = try map { try $0.deleteCommand() }
let batches = BatchUtils.splitArray(commands, valuesPerSegment: batchLimit)
try batches.forEach {
- let currentBatch = try API.Command
+ let currentBatch = try API.Command)>
.batch(commands: $0)
.execute(options: options)
returnBatch.append(contentsOf: currentBatch)
@@ -897,11 +902,11 @@ public extension Sequence where Element: ParseInstallation {
batchLimit limit: Int? = nil,
options: API.Options = [],
callbackQueue: DispatchQueue = .main,
- completion: @escaping (Result<[ParseError?], ParseError>) -> Void
+ completion: @escaping (Result<[(Result)], ParseError>) -> Void
) {
let batchLimit = limit != nil ? limit! : ParseConstants.batchLimit
do {
- var returnBatch = [ParseError?]()
+ var returnBatch = [(Result)]()
let commands = try map({ try $0.deleteCommand() })
let batches = BatchUtils.splitArray(commands, valuesPerSegment: batchLimit)
var completed = 0
diff --git a/Sources/ParseSwift/Objects/ParseObject+combine.swift b/Sources/ParseSwift/Objects/ParseObject+combine.swift
new file mode 100644
index 000000000..973fe4e43
--- /dev/null
+++ b/Sources/ParseSwift/Objects/ParseObject+combine.swift
@@ -0,0 +1,106 @@
+//
+// ParseObject+combine.swift
+// ParseSwift
+//
+// Created by Corey Baker on 1/29/21.
+// Copyright © 2021 Parse Community. All rights reserved.
+//
+
+#if canImport(Combine)
+import Foundation
+import Combine
+
+// MARK: Combine
+@available(macOS 10.15, iOS 13.0, macCatalyst 13.0, watchOS 6.0, tvOS 13.0, *)
+public extension ParseObject {
+
+ /**
+ Fetches the `ParseObject` *aynchronously* with the current data from the server and sets an error if one occurs.
+ Publishes when complete.
+
+ - parameter options: A set of header options sent to the server. Defaults to an empty set.
+ - returns: A publisher that eventually produces a single value and then finishes or fails.
+ - important: If an object fetched has the same objectId as current, it will automatically update the current.
+ */
+ func fetchPublisher(options: API.Options = []) -> Future {
+ Future { promise in
+ self.fetch(options: options,
+ completion: promise)
+ }
+ }
+
+ /**
+ Saves the `ParseObject` *asynchronously* and publishes when complete.
+
+ - parameter options: A set of header options sent to the server. Defaults to an empty set.
+ - returns: A publisher that eventually produces a single value and then finishes or fails.
+ - important: If an object saved has the same objectId as current, it will automatically update the current.
+ */
+ func savePublisher(options: API.Options = []) -> Future {
+ Future { promise in
+ self.save(options: options,
+ completion: promise)
+ }
+ }
+
+ /**
+ Deletes the `ParseObject` *asynchronously* and publishes when complete.
+
+ - parameter options: A set of header options sent to the server. Defaults to an empty set.
+ - returns: A publisher that eventually produces a single value and then finishes or fails.
+ - important: If an object deleted has the same objectId as current, it will automatically update the current.
+ */
+ func deletePublisher(options: API.Options = []) -> Future {
+ Future { promise in
+ self.delete(options: options, completion: promise)
+ }
+ }
+}
+
+// MARK: Batch Support - Combine
+@available(macOS 10.15, iOS 13.0, macCatalyst 13.0, watchOS 6.0, tvOS 13.0, *)
+public extension Sequence where Element: ParseObject {
+ /**
+ Fetches a collection of objects *aynchronously* with the current data from the server and sets
+ an error if one occurs. Publishes when complete.
+
+ - parameter options: A set of header options sent to the server. Defaults to an empty set.
+ - returns: A publisher that eventually produces a single value and then finishes or fails.
+ - important: If an object fetched has the same objectId as current, it will automatically update the current.
+ */
+ func fetchAllPublisher(options: API.Options = []) -> Future<[(Result)], ParseError> {
+ Future { promise in
+ self.fetchAll(options: options,
+ completion: promise)
+ }
+ }
+
+ /**
+ Saves a collection of objects *asynchronously* and publishes when complete.
+
+ - parameter options: A set of header options sent to the server. Defaults to an empty set.
+ - returns: A publisher that eventually produces a single value and then finishes or fails.
+ - important: If an object saved has the same objectId as current, it will automatically update the current.
+ */
+ func saveAllPublisher(options: API.Options = []) -> Future<[(Result)], ParseError> {
+ Future { promise in
+ self.saveAll(options: options,
+ completion: promise)
+ }
+ }
+
+ /**
+ Deletes a collection of objects *asynchronously* and publishes when complete.
+
+ - parameter options: A set of header options sent to the server. Defaults to an empty set.
+ - returns: A publisher that eventually produces a single value and then finishes or fails.
+ - important: If an object deleted has the same objectId as current, it will automatically update the current.
+ */
+ func deleteAllPublisher(options: API.Options = []) -> Future<[(Result)], ParseError> {
+ Future { promise in
+ self.deleteAll(options: options, completion: promise)
+ }
+ }
+}
+
+#endif
diff --git a/Sources/ParseSwift/Objects/ParseObject.swift b/Sources/ParseSwift/Objects/ParseObject.swift
index 3df6eece9..8703ae1c5 100644
--- a/Sources/ParseSwift/Objects/ParseObject.swift
+++ b/Sources/ParseSwift/Objects/ParseObject.swift
@@ -331,13 +331,13 @@ public extension Sequence where Element: ParseObject {
- throws: `ParseError`
*/
func deleteAll(batchLimit limit: Int? = nil,
- options: API.Options = []) throws -> [ParseError?] {
+ options: API.Options = []) throws -> [(Result)] {
let batchLimit = limit != nil ? limit! : ParseConstants.batchLimit
- var returnBatch = [ParseError?]()
+ var returnBatch = [(Result)]()
let commands = try map { try $0.deleteCommand() }
let batches = BatchUtils.splitArray(commands, valuesPerSegment: batchLimit)
try batches.forEach {
- let currentBatch = try API.Command
+ let currentBatch = try API.Command)>
.batch(commands: $0)
.execute(options: options)
returnBatch.append(contentsOf: currentBatch)
@@ -367,11 +367,11 @@ public extension Sequence where Element: ParseObject {
batchLimit limit: Int? = nil,
options: API.Options = [],
callbackQueue: DispatchQueue = .main,
- completion: @escaping (Result<[ParseError?], ParseError>) -> Void
+ completion: @escaping (Result<[(Result)], ParseError>) -> Void
) {
let batchLimit = limit != nil ? limit! : ParseConstants.batchLimit
do {
- var returnBatch = [ParseError?]()
+ var returnBatch = [(Result)]()
let commands = try map({ try $0.deleteCommand() })
let batches = BatchUtils.splitArray(commands, valuesPerSegment: batchLimit)
var completed = 0
@@ -703,9 +703,7 @@ extension ParseObject {
- throws: An error of `ParseError` type.
*/
public func delete(options: API.Options = []) throws {
- if let error = try deleteCommand().execute(options: options) {
- throw error
- }
+ _ = try deleteCommand().execute(options: options)
}
/**
@@ -715,37 +713,37 @@ extension ParseObject {
- parameter callbackQueue: The queue to return to after completion. Default
value of .main.
- parameter completion: The block to execute when completed.
- It should have the following argument signature: `(ParseError?)`.
+ It should have the following argument signature: `(Result)`.
*/
public func delete(
options: API.Options = [],
callbackQueue: DispatchQueue = .main,
- completion: @escaping (ParseError?) -> Void
+ completion: @escaping (Result) -> Void
) {
do {
try deleteCommand().executeAsync(options: options) { result in
callbackQueue.async {
switch result {
- case .success(let error):
- completion(error)
+ case .success:
+ completion(.success(()))
case .failure(let error):
- completion(error)
+ completion(.failure(error))
}
}
}
} catch let error as ParseError {
callbackQueue.async {
- completion(error)
+ completion(.failure(error))
}
} catch {
callbackQueue.async {
- completion(ParseError(code: .unknownError, message: error.localizedDescription))
+ completion(.failure(ParseError(code: .unknownError, message: error.localizedDescription)))
}
}
}
- internal func deleteCommand() throws -> API.NonParseBodyCommand {
- try API.NonParseBodyCommand.deleteCommand(self)
+ internal func deleteCommand() throws -> API.NonParseBodyCommand {
+ try API.NonParseBodyCommand.deleteCommand(self)
}
}// swiftlint:disable:this file_length
diff --git a/Sources/ParseSwift/Objects/ParseUser+combine.swift b/Sources/ParseSwift/Objects/ParseUser+combine.swift
new file mode 100644
index 000000000..3b02f554a
--- /dev/null
+++ b/Sources/ParseSwift/Objects/ParseUser+combine.swift
@@ -0,0 +1,230 @@
+//
+// ParseUser+publisher.swift
+// ParseSwift
+//
+// Created by Corey Baker on 1/28/21.
+// Copyright © 2021 Parse Community. All rights reserved.
+//
+
+#if canImport(Combine)
+import Foundation
+import Combine
+
+@available(macOS 10.15, iOS 13.0, macCatalyst 13.0, watchOS 6.0, tvOS 13.0, *)
+public extension ParseUser {
+
+ // MARK: Signing Out - Combine
+ /**
+ Signs up the user *asynchronously* and publishes value.
+
+ This will also enforce that the username isn't already taken.
+
+ - warning: Make sure that password and username are set before calling this method.
+ - parameter username: The username of the user.
+ - parameter password: The password of the user.
+ - parameter options: A set of header options sent to the server. Defaults to an empty set.
+ - returns: A publisher that eventually produces a single value and then finishes or fails.
+ */
+ static func signupPublisher(username: String,
+ password: String,
+ options: API.Options = []) -> Future {
+ Future { promise in
+ Self.signup(username: username,
+ password: password,
+ options: options,
+ completion: promise)
+ }
+ }
+
+ /**
+ Signs up the user *asynchronously* and publishes value.
+
+ This will also enforce that the username isn't already taken.
+
+ - warning: Make sure that password and username are set before calling this method.
+ - parameter options: A set of header options sent to the server. Defaults to an empty set.
+ - returns: A publisher that eventually produces a single value and then finishes or fails.
+ */
+ func signupPublisher(options: API.Options = []) -> Future {
+ Future { promise in
+ self.signup(options: options,
+ completion: promise)
+ }
+ }
+
+ // MARK: Logging In - Combine
+ /**
+ Makes an *asynchronous* request to log in a user with specified credentials.
+ Publishes an instance of the successfully logged in `ParseUser`.
+
+ This also caches the user locally so that calls to *current* will use the latest logged in user.
+ - parameter username: The username of the user.
+ - parameter password: The password of the user.
+ - parameter options: A set of header options sent to the server. Defaults to an empty set.
+ - returns: A publisher that eventually produces a single value and then finishes or fails.
+ */
+ static func loginPublisher(username: String,
+ password: String,
+ options: API.Options = []) -> Future {
+ Future { promise in
+ Self.login(username: username,
+ password: password,
+ options: options,
+ completion: promise)
+ }
+ }
+
+ /**
+ Logs in a `ParseUser` *asynchronously* with a session token.
+ Publishes an instance of the successfully logged in `ParseUser`.
+ If successful, this saves the session to the keychain, so you can retrieve the currently logged in user
+ using *current*.
+
+ - parameter sessionToken: The sessionToken of the user to login.
+ - parameter options: A set of header options sent to the server. Defaults to an empty set.
+ - returns: A publisher that eventually produces a single value and then finishes or fails.
+ */
+ func becomePublisher(sessionToken: String, options: API.Options = []) -> Future {
+ Future { promise in
+ self.become(sessionToken: sessionToken, options: options, completion: promise)
+ }
+ }
+
+ // MARK: Logging Out - Combine
+ /**
+ Logs out the currently logged in user *asynchronously*. Publishes when complete.
+
+ This will also remove the session from the Keychain, log out of linked services
+ and all future calls to `current` will return `nil`.
+ - parameter options: A set of header options sent to the server. Defaults to an empty set.
+ - returns: A publisher that eventually produces a single value and then finishes or fails.
+ */
+ static func logoutPublisher(options: API.Options = []) -> Future {
+ Future { promise in
+ Self.logout(options: options, completion: promise)
+ }
+ }
+
+ // MARK: Password Reset - Combine
+ /**
+ Requests *asynchronously* a password reset email to be sent to the specified email address
+ associated with the user account. This email allows the user to securely reset their password on the web.
+ Publishes when complete.
+ - parameter email: The email address associated with the user that forgot their password.
+ - parameter options: A set of header options sent to the server. Defaults to an empty set.
+ - returns: A publisher that eventually produces a single value and then finishes or fails.
+ */
+ static func passwordResetPublisher(email: String,
+ options: API.Options = []) -> Future {
+ Future { promise in
+ Self.passwordReset(email: email, options: options, completion: promise)
+ }
+ }
+
+ // MARK: Verification Email Request - Combine
+ /**
+ Requests *asynchronously* a verification email be sent to the specified email address
+ associated with the user account. Publishes when complete.
+ - parameter email: The email address associated with the user.
+ - parameter options: A set of header options sent to the server. Defaults to an empty set.
+ - returns: A publisher that eventually produces a single value and then finishes or fails.
+ */
+ static func verificationEmailPublisher(email: String,
+ options: API.Options = []) -> Future {
+ Future { promise in
+ Self.verificationEmail(email: email, options: options, completion: promise)
+ }
+ }
+
+ // MARK: Fetchable - Combine
+ /**
+ Fetches the `ParseUser` *aynchronously* with the current data from the server and sets an error if one occurs.
+ Publishes when complete.
+
+ - parameter options: A set of header options sent to the server. Defaults to an empty set.
+ - returns: A publisher that eventually produces a single value and then finishes or fails.
+ - important: If an object fetched has the same objectId as current, it will automatically update the current.
+ */
+ func fetchPublisher(options: API.Options = []) -> Future {
+ Future { promise in
+ self.fetch(options: options,
+ completion: promise)
+ }
+ }
+
+ // MARK: Savable - Combine
+ /**
+ Saves the `ParseUser` *asynchronously* and publishes when complete.
+
+ - parameter options: A set of header options sent to the server. Defaults to an empty set.
+ - returns: A publisher that eventually produces a single value and then finishes or fails.
+ - important: If an object saved has the same objectId as current, it will automatically update the current.
+ */
+ func savePublisher(options: API.Options = []) -> Future {
+ Future { promise in
+ self.save(options: options,
+ completion: promise)
+ }
+ }
+
+ // MARK: Deletable - Combine
+ /**
+ Deletes the `ParseUser` *asynchronously* and publishes when complete.
+
+ - parameter options: A set of header options sent to the server. Defaults to an empty set.
+ - returns: A publisher that eventually produces a single value and then finishes or fails.
+ - important: If an object deleted has the same objectId as current, it will automatically update the current.
+ */
+ func deletePublisher(options: API.Options = []) -> Future {
+ Future { promise in
+ self.delete(options: options, completion: promise)
+ }
+ }
+}
+
+// MARK: Batch Support - Combine
+@available(macOS 10.15, iOS 13.0, macCatalyst 13.0, watchOS 6.0, tvOS 13.0, *)
+public extension Sequence where Element: ParseUser {
+ /**
+ Fetches a collection of users *aynchronously* with the current data from the server and sets
+ an error if one occurs. Publishes when complete.
+
+ - parameter options: A set of header options sent to the server. Defaults to an empty set.
+ - returns: A publisher that eventually produces a single value and then finishes or fails.
+ - important: If an object fetched has the same objectId as current, it will automatically update the current.
+ */
+ func fetchAllPublisher(options: API.Options = []) -> Future<[(Result)], ParseError> {
+ Future { promise in
+ self.fetchAll(options: options,
+ completion: promise)
+ }
+ }
+
+ /**
+ Saves a collection of users *asynchronously* and publishes when complete.
+
+ - parameter options: A set of header options sent to the server. Defaults to an empty set.
+ - returns: A publisher that eventually produces a single value and then finishes or fails.
+ - important: If an object saved has the same objectId as current, it will automatically update the current.
+ */
+ func saveAllPublisher(options: API.Options = []) -> Future<[(Result)], ParseError> {
+ Future { promise in
+ self.saveAll(options: options,
+ completion: promise)
+ }
+ }
+
+ /**
+ Deletes a collection of users *asynchronously* and publishes when complete.
+
+ - parameter options: A set of header options sent to the server. Defaults to an empty set.
+ - returns: A publisher that eventually produces a single value and then finishes or fails.
+ - important: If an object deleted has the same objectId as current, it will automatically update the current.
+ */
+ func deleteAllPublisher(options: API.Options = []) -> Future<[(Result)], ParseError> {
+ Future { promise in
+ self.deleteAll(options: options, completion: promise)
+ }
+ }
+}
+#endif
diff --git a/Sources/ParseSwift/Objects/ParseUser.swift b/Sources/ParseSwift/Objects/ParseUser.swift
index 8a47102d5..b55adfbef 100644
--- a/Sources/ParseSwift/Objects/ParseUser.swift
+++ b/Sources/ParseSwift/Objects/ParseUser.swift
@@ -235,7 +235,6 @@ extension ParseUser {
value of .main.
- parameter completion: The block to execute when completed.
It should have the following argument signature: `(Result)`.
- - important: If an object fetched has the same objectId as current, it will automatically update the current.
*/
public func become(sessionToken: String,
options: API.Options = [],
@@ -299,7 +298,15 @@ extension ParseUser {
Logs out the currently logged in user in Keychain *synchronously*.
*/
public static func logout(options: API.Options = []) throws {
- _ = try logoutCommand().execute(options: options)
+ let error = try? logoutCommand().execute(options: options)
+ //Always let user logout locally, no matter the error.
+ deleteCurrentContainerFromKeychain()
+ BaseParseInstallation.deleteCurrentContainerFromKeychain()
+ BaseConfig.deleteCurrentContainerFromKeychain()
+ //Wait to throw error
+ if let parseError = error {
+ throw parseError
+ }
}
/**
@@ -313,43 +320,38 @@ extension ParseUser {
- parameter completion: A block that will be called when logging out, completes or fails.
*/
public static func logout(options: API.Options = [], callbackQueue: DispatchQueue = .main,
- completion: @escaping (ParseError?) -> Void) {
- callbackQueue.async {
- logoutCommand().executeAsync(options: options) { result in
+ completion: @escaping (Result) -> Void) {
+ logoutCommand().executeAsync(options: options) { result in
+ callbackQueue.async {
+
+ //Always let user logout locally, no matter the error.
+ deleteCurrentContainerFromKeychain()
+ BaseParseInstallation.deleteCurrentContainerFromKeychain()
+ BaseConfig.deleteCurrentContainerFromKeychain()
switch result {
- case .success:
- completion(nil)
+ case .success(let error):
+ if let error = error {
+ completion(.failure(error))
+ } else {
+ completion(.success(()))
+ }
case .failure(let error):
- completion(error)
+ completion(.failure(error))
}
}
}
}
- internal static func logoutCommand() -> API.NonParseBodyCommand {
- return API.NonParseBodyCommand(method: .POST, path: .logout) { (data) -> NoBody in
- var parseError: ParseError?
- var serverResponse = NoBody()
- //Always let user logout locally, no matter the error.
- deleteCurrentContainerFromKeychain()
- BaseParseInstallation.deleteCurrentContainerFromKeychain()
- BaseConfig.deleteCurrentContainerFromKeychain()
-
+ internal static func logoutCommand() -> API.NonParseBodyCommand {
+ return API.NonParseBodyCommand(method: .POST, path: .logout) { (data) -> ParseError? in
do {
- serverResponse = try ParseCoding.jsonDecoder().decode(NoBody.self, from: data)
+ let parseError = try ParseCoding.jsonDecoder().decode(ParseError.self, from: data)
+ return parseError
} catch {
- if let foundError = error as? ParseError {
- parseError = foundError
- } else {
- parseError = ParseError(code: .unknownError, message: error.localizedDescription)
- }
- }
- guard let error = parseError else {
- return serverResponse
+ return nil
}
- throw error
}
}
}
@@ -379,15 +381,19 @@ extension ParseUser {
*/
public static func passwordReset(email: String, options: API.Options = [],
callbackQueue: DispatchQueue = .main,
- completion: @escaping (ParseError?) -> Void) {
+ completion: @escaping (Result) -> Void) {
passwordResetCommand(email: email).executeAsync(options: options) { result in
callbackQueue.async {
switch result {
case .success(let error):
- completion(error)
+ if let error = error {
+ completion(.failure(error))
+ } else {
+ completion(.success(()))
+ }
case .failure(let error):
- completion(error)
+ completion(.failure(error))
}
}
}
@@ -410,9 +416,9 @@ extension ParseUser {
- parameter email: The email address associated with the user.
- parameter options: A set of header options sent to the server. Defaults to an empty set.
*/
- public static func verificationEmailRequest(email: String,
- options: API.Options = []) throws {
- if let error = try verificationEmailRequestCommand(email: email).execute(options: options) {
+ public static func verificationEmail(email: String,
+ options: API.Options = []) throws {
+ if let error = try verificationEmailCommand(email: email).execute(options: options) {
throw error
}
}
@@ -425,30 +431,33 @@ extension ParseUser {
- parameter callbackQueue: The queue to return to after completion. Default value of .main.
- parameter completion: A block that will be called when the verification request completes or fails.
*/
- public static func verificationEmailRequest(email: String,
- options: API.Options = [],
- callbackQueue: DispatchQueue = .main,
- completion: @escaping (ParseError?) -> Void) {
- verificationEmailRequestCommand(email: email)
+ public static func verificationEmail(email: String,
+ options: API.Options = [],
+ callbackQueue: DispatchQueue = .main,
+ completion: @escaping (Result) -> Void) {
+ verificationEmailCommand(email: email)
.executeAsync(options: options) { result in
callbackQueue.async {
switch result {
case .success(let error):
- completion(error)
+ if let error = error {
+ completion(.failure(error))
+ } else {
+ completion(.success(()))
+ }
case .failure(let error):
- completion(error)
+ completion(.failure(error))
}
}
}
}
- // swiftlint:disable:next line_length
- internal static func verificationEmailRequestCommand(email: String) -> API.NonParseBodyCommand {
+ internal static func verificationEmailCommand(email: String) -> API.NonParseBodyCommand {
let emailBody = EmailBody(email: email)
return API.NonParseBodyCommand(method: .POST,
- path: .verificationEmailRequest,
+ path: .verificationEmail,
body: emailBody) { (data) -> ParseError? in
try? ParseCoding.jsonDecoder().decode(ParseError.self, from: data)
}
@@ -838,50 +847,55 @@ extension ParseUser {
- parameter callbackQueue: The queue to return to after completion. Default
value of .main.
- parameter completion: The block to execute when completed.
- It should have the following argument signature: `(ParseError?)`.
+ It should have the following argument signature: `(Result)`.
- important: If an object deleted has the same objectId as current, it will automatically update the current.
*/
public func delete(
options: API.Options = [],
callbackQueue: DispatchQueue = .main,
- completion: @escaping (ParseError?) -> Void
+ completion: @escaping (Result) -> Void
) {
do {
try deleteCommand().executeAsync(options: options) { result in
switch result {
- case .success(let error):
+ case .success:
callbackQueue.async {
try? Self.updateKeychainIfNeeded([self], deleting: true)
- completion(error)
+ completion(.success(()))
}
case .failure(let error):
callbackQueue.async {
- completion(error)
+ completion(.failure(error))
}
}
}
} catch let error as ParseError {
callbackQueue.async {
- completion(error)
+ completion(.failure(error))
}
} catch {
callbackQueue.async {
- completion(ParseError(code: .unknownError, message: error.localizedDescription))
+ completion(.failure(ParseError(code: .unknownError, message: error.localizedDescription)))
}
}
}
- func deleteCommand() throws -> API.NonParseBodyCommand {
+ func deleteCommand() throws -> API.NonParseBodyCommand {
guard isSaved else {
throw ParseError(code: .unknownError, message: "Cannot Delete an object without id")
}
- return API.NonParseBodyCommand(
+ return API.NonParseBodyCommand(
method: .DELETE,
path: endpoint
- ) { (data) -> ParseError? in
- try? ParseCoding.jsonDecoder().decode(ParseError.self, from: data)
+ ) { (data) -> NoBody in
+ let error = try? ParseCoding.jsonDecoder().decode(ParseError.self, from: data)
+ if let error = error {
+ throw error
+ } else {
+ return NoBody()
+ }
}
}
}
@@ -1170,9 +1184,9 @@ public extension Sequence where Element: ParseUser {
- important: If an object deleted has the same objectId as current, it will automatically update the current.
*/
func deleteAll(batchLimit limit: Int? = nil,
- options: API.Options = []) throws -> [ParseError?] {
+ options: API.Options = []) throws -> [(Result)] {
let batchLimit = limit != nil ? limit! : ParseConstants.batchLimit
- var returnBatch = [ParseError?]()
+ var returnBatch = [(Result)]()
let commands = try map { try $0.deleteCommand() }
let batches = BatchUtils.splitArray(commands, valuesPerSegment: batchLimit)
try batches.forEach {
@@ -1208,11 +1222,11 @@ public extension Sequence where Element: ParseUser {
batchLimit limit: Int? = nil,
options: API.Options = [],
callbackQueue: DispatchQueue = .main,
- completion: @escaping (Result<[ParseError?], ParseError>) -> Void
+ completion: @escaping (Result<[(Result)], ParseError>) -> Void
) {
let batchLimit = limit != nil ? limit! : ParseConstants.batchLimit
do {
- var returnBatch = [ParseError?]()
+ var returnBatch = [(Result)]()
let commands = try map({ try $0.deleteCommand() })
let batches = BatchUtils.splitArray(commands, valuesPerSegment: batchLimit)
var completed = 0
diff --git a/Sources/ParseSwift/Operations/ParseOperation+combine.swift b/Sources/ParseSwift/Operations/ParseOperation+combine.swift
new file mode 100644
index 000000000..cef9b79e2
--- /dev/null
+++ b/Sources/ParseSwift/Operations/ParseOperation+combine.swift
@@ -0,0 +1,32 @@
+//
+// ParseOperation+combine.swift
+// ParseSwift
+//
+// Created by Corey Baker on 1/29/21.
+// Copyright © 2021 Parse Community. All rights reserved.
+//
+
+#if canImport(Combine)
+import Foundation
+import Combine
+
+@available(macOS 10.15, iOS 13.0, macCatalyst 13.0, watchOS 6.0, tvOS 13.0, *)
+public extension ParseOperation {
+
+ // MARK: Savable - Combine
+
+ /**
+ Saves the operations on the `ParseObject` *asynchronously* and executes the given callback block.
+
+ - parameter options: A set of header options sent to the server. Defaults to an empty set.
+ - returns: A publisher that eventually produces a single value and then finishes or fails.
+ */
+ func savePublisher(options: API.Options = []) -> Future {
+ Future { promise in
+ self.save(options: options,
+ completion: promise)
+ }
+ }
+}
+
+#endif
diff --git a/Sources/ParseSwift/Types/ParseCloud+combine.swift b/Sources/ParseSwift/Types/ParseCloud+combine.swift
new file mode 100644
index 000000000..ef3405af5
--- /dev/null
+++ b/Sources/ParseSwift/Types/ParseCloud+combine.swift
@@ -0,0 +1,48 @@
+//
+// ParseCloud+combine.swift
+// ParseSwift
+//
+// Created by Corey Baker on 1/29/21.
+// Copyright © 2021 Parse Community. All rights reserved.
+//
+
+#if canImport(Combine)
+import Foundation
+import Combine
+
+// MARK: Combine
+@available(macOS 10.15, iOS 13.0, macCatalyst 13.0, watchOS 6.0, tvOS 13.0, *)
+public extension ParseCloud {
+
+ // MARK: Functions - Combine
+
+ /**
+ Calls a Cloud Code function *asynchronously* and returns a result of it's execution.
+ Publishes when complete.
+ - parameter options: A set of header options sent to the server. Defaults to an empty set.
+ - returns: A publisher that eventually produces a single value and then finishes or fails.
+ */
+ func runFunctionPublisher(options: API.Options = []) -> Future {
+ Future { promise in
+ self.runFunction(options: options,
+ completion: promise)
+ }
+ }
+
+ // MARK: Jobs - Combine
+
+ /**
+ Starts a Cloud Code job *asynchronously* and returns a result with the jobStatusId of the job.
+ Publishes when complete.
+ - parameter options: A set of header options sent to the server. Defaults to an empty set.
+ - returns: A publisher that eventually produces a single value and then finishes or fails.
+ */
+ func startJobPublisher(options: API.Options = []) -> Future {
+ Future { promise in
+ self.startJob(options: options,
+ completion: promise)
+ }
+ }
+}
+
+#endif
diff --git a/Sources/ParseSwift/Types/ParseConfig+combine.swift b/Sources/ParseSwift/Types/ParseConfig+combine.swift
new file mode 100644
index 000000000..13ea16656
--- /dev/null
+++ b/Sources/ParseSwift/Types/ParseConfig+combine.swift
@@ -0,0 +1,46 @@
+//
+// ParseConfig+combine.swift
+// ParseSwift
+//
+// Created by Corey Baker on 1/29/21.
+// Copyright © 2021 Parse Community. All rights reserved.
+//
+
+#if canImport(Combine)
+import Foundation
+import Combine
+
+// MARK: Combine
+@available(macOS 10.15, iOS 13.0, macCatalyst 13.0, watchOS 6.0, tvOS 13.0, *)
+public extension ParseConfig {
+
+ // MARK: Fetchable - Combine
+
+ /**
+ Fetch the Config *asynchronously*.
+ - parameter options: A set of header options sent to the server. Defaults to an empty set.
+ - returns: A publisher that eventually produces a single value and then finishes or fails.
+ */
+ func fetchPublisher(options: API.Options = []) -> Future {
+ Future { promise in
+ self.fetch(options: options,
+ completion: promise)
+ }
+ }
+
+ // MARK: Savable - Combine
+
+ /**
+ Update the Config *asynchronously*.
+ - parameter options: A set of header options sent to the server. Defaults to an empty set.
+ - returns: A publisher that eventually produces a single value and then finishes or fails.
+ */
+ func savePublisher(options: API.Options = []) -> Future {
+ Future { promise in
+ self.save(options: options,
+ completion: promise)
+ }
+ }
+}
+
+#endif
diff --git a/Sources/ParseSwift/Types/ParseFile+combine.swift b/Sources/ParseSwift/Types/ParseFile+combine.swift
new file mode 100644
index 000000000..d22be7d9e
--- /dev/null
+++ b/Sources/ParseSwift/Types/ParseFile+combine.swift
@@ -0,0 +1,94 @@
+//
+// ParseFile+combine.swift
+// ParseSwift
+//
+// Created by Corey Baker on 1/29/21.
+// Copyright © 2021 Parse Community. All rights reserved.
+//
+
+#if canImport(Combine)
+import Foundation
+import Combine
+
+// MARK: Combine
+@available(macOS 10.15, iOS 13.0, macCatalyst 13.0, watchOS 6.0, tvOS 13.0, *)
+public extension ParseFile {
+
+ /**
+ Fetches a file with given url *synchronously*. Publishes when complete.
+ - parameter options: A set of header options sent to the server. Defaults to an empty set.
+ - returns: A publisher that eventually produces a single value and then finishes or fails.
+ */
+ func fetchPublisher(options: API.Options = []) -> Future {
+ Future { promise in
+ self.fetch(options: options,
+ completion: promise)
+ }
+ }
+
+ /**
+ Fetches a file with given url *synchronously*. Publishes when complete.
+ - parameter options: A set of header options sent to the server. Defaults to an empty set.
+ - parameter progress: A block that will be called when file updates it's progress.
+ It should have the following argument signature: `(task: URLSessionDownloadTask,
+ bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64)`.
+ - returns: A publisher that eventually produces a single value and then finishes or fails.
+ */
+ func fetchPublisher(options: API.Options = [],
+ progress: @escaping ((URLSessionDownloadTask,
+ Int64, Int64, Int64) -> Void)) -> Future {
+ Future { promise in
+ self.fetch(options: options,
+ progress: progress,
+ completion: promise)
+ }
+ }
+
+ /**
+ Creates a file with given data *asynchronously* and executes the given callback block.
+ Publishes when complete.
+ A name will be assigned to it by the server. If the file hasn't been downloaded, it will automatically
+ be downloaded before saved.
+ - parameter options: A set of header options sent to the server. Defaults to an empty set.
+ - returns: A publisher that eventually produces a single value and then finishes or fails.
+ */
+ func savePublisher(options: API.Options = []) -> Future {
+ Future { promise in
+ self.save(options: options,
+ completion: promise)
+ }
+ }
+
+ /**
+ Creates a file with given data *asynchronously* and executes the given callback block.
+ A name will be assigned to it by the server. If the file hasn't been downloaded, it will automatically
+ be downloaded before saved. Publishes when complete.
+ - parameter options: A set of header options sent to the server. Defaults to an empty set.
+ - parameter progress: A block that will be called when file updates it's progress.
+ It should have the following argument signature: `(task: URLSessionDownloadTask,
+ bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64)`.
+ - returns: A publisher that eventually produces a single value and then finishes or fails.
+ */
+ func savePublisher(options: API.Options = [],
+ progress: ((URLSessionTask, Int64, Int64, Int64) -> Void)? = nil) -> Future {
+ Future { promise in
+ self.save(options: options,
+ progress: progress,
+ completion: promise)
+ }
+ }
+
+ /**
+ Deletes the file from the Parse Server. Publishes when complete.
+ - requires: `.useMasterKey` has to be available and passed as one of the set of `options`.
+ - parameter options: A set of header options sent to the server. Defaults to an empty set.
+ - returns: A publisher that eventually produces a single value and then finishes or fails.
+ */
+ func deletePublisher(options: API.Options = []) -> Future {
+ Future { promise in
+ self.delete(options: options, completion: promise)
+ }
+ }
+}
+
+#endif
diff --git a/Sources/ParseSwift/Types/ParseFile.swift b/Sources/ParseSwift/Types/ParseFile.swift
index 8d56bf9e1..6679eaba0 100644
--- a/Sources/ParseSwift/Types/ParseFile.swift
+++ b/Sources/ParseSwift/Types/ParseFile.swift
@@ -179,19 +179,19 @@ extension ParseFile {
- parameter options: A set of header options sent to the server. Defaults to an empty set.
- parameter callbackQueue: The queue to return to after completion. Default value of .main.
- parameter completion: A block that will be called when file deletes or fails.
- It should have the following argument signature: `(ParseError?)`
+ It should have the following argument signature: `(Result)`
*/
public func delete(options: API.Options,
callbackQueue: DispatchQueue = .main,
- completion: @escaping (ParseError?) -> Void) {
+ completion: @escaping (Result) -> Void) {
var options = options
options = options.union(self.options)
if !options.contains(.useMasterKey) {
callbackQueue.async {
- completion(ParseError(code: .unknownError,
+ completion(.failure(ParseError(code: .unknownError,
// swiftlint:disable:next line_length
- message: "You must specify \"useMasterKey\" in \"options\" in order to delete a file."))
+ message: "You must specify \"useMasterKey\" in \"options\" in order to delete a file.")))
}
return
}
@@ -200,9 +200,9 @@ extension ParseFile {
switch result {
case .success:
- completion(nil)
+ completion(.success(()))
case .failure(let error):
- completion(error)
+ completion(.failure(error))
}
}
}
@@ -413,7 +413,7 @@ extension ParseFile {
It should have the following argument signature: `(task: URLSessionDownloadTask,
bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64)`.
- parameter completion: A block that will be called when file saves or fails.
- It should have the following argument signature: `(Result)`
+ It should have the following argument signature: `(Result)`.
*/
public func save(options: API.Options = [],
callbackQueue: DispatchQueue = .main,
diff --git a/Sources/ParseSwift/Types/Query+combine.swift b/Sources/ParseSwift/Types/Query+combine.swift
new file mode 100644
index 000000000..d7b591231
--- /dev/null
+++ b/Sources/ParseSwift/Types/Query+combine.swift
@@ -0,0 +1,126 @@
+//
+// Query+combine.swift
+// ParseSwift
+//
+// Created by Corey Baker on 1/29/21.
+// Copyright © 2021 Parse Community. All rights reserved.
+//
+
+#if canImport(Combine)
+import Foundation
+import Combine
+
+// MARK: Combine
+@available(macOS 10.15, iOS 13.0, macCatalyst 13.0, watchOS 6.0, tvOS 13.0, *)
+public extension Query {
+
+ // MARK: Queryable - Combine
+
+ /**
+ Finds objects *asynchronously* and publishes when complete.
+ - parameter options: A set of header options sent to the server. Defaults to an empty set.
+ - returns: A publisher that eventually produces a single value and then finishes or fails.
+ */
+ func findPublisher(options: API.Options = []) -> Future<[ResultType], ParseError> {
+ Future { promise in
+ self.find(options: options,
+ completion: promise)
+ }
+ }
+
+ /**
+ Finds objects *asynchronously* and publishes when complete.
+ - parameter explain: Used to toggle the information on the query plan.
+ - parameter hint: String or Object of index that should be used when executing query.
+ - parameter options: A set of header options sent to the server. Defaults to an empty set.
+ - returns: A publisher that eventually produces a single value and then finishes or fails.
+ */
+ func findPublisher(explain: Bool,
+ hint: String? = nil,
+ options: API.Options = []) -> Future {
+ Future { promise in
+ self.find(explain: explain,
+ hint: hint,
+ options: options,
+ completion: promise)
+ }
+ }
+
+ /**
+ Gets an object *asynchronously* and publishes when complete.
+ - parameter options: A set of header options sent to the server. Defaults to an empty set.
+ - returns: A publisher that eventually produces a single value and then finishes or fails.
+ */
+ func firstPublisher(options: API.Options = []) -> Future {
+ Future { promise in
+ self.first(options: options,
+ completion: promise)
+ }
+ }
+
+ /**
+ Gets an object *asynchronously* and publishes when complete.
+ - parameter explain: Used to toggle the information on the query plan.
+ - parameter hint: String or Object of index that should be used when executing query.
+ - parameter options: A set of header options sent to the server. Defaults to an empty set.
+ - returns: A publisher that eventually produces a single value and then finishes or fails.
+ */
+ func firstPublisher(explain: Bool,
+ hint: String? = nil,
+ options: API.Options = []) -> Future {
+ Future { promise in
+ self.first(explain: explain,
+ hint: hint,
+ options: options,
+ completion: promise)
+ }
+ }
+
+ /**
+ Count objects *asynchronously* and publishes when complete.
+ - parameter options: A set of header options sent to the server. Defaults to an empty set.
+ - returns: A publisher that eventually produces a single value and then finishes or fails.
+ */
+ func countPublisher(options: API.Options = []) -> Future {
+ Future { promise in
+ self.count(options: options,
+ completion: promise)
+ }
+ }
+
+ /**
+ Count objects *asynchronously* and publishes when complete.
+ - parameter explain: Used to toggle the information on the query plan.
+ - parameter hint: String or Object of index that should be used when executing query.
+ - parameter options: A set of header options sent to the server. Defaults to an empty set.
+ - returns: A publisher that eventually produces a single value and then finishes or fails.
+ */
+ func countPublisher(explain: Bool,
+ hint: String? = nil,
+ options: API.Options = []) -> Future {
+ Future { promise in
+ self.count(explain: explain,
+ hint: hint,
+ options: options,
+ completion: promise)
+ }
+ }
+
+ /**
+ Executes an aggregate query *asynchronously* and publishes when complete.
+ - requires: `.useMasterKey` has to be available and passed as one of the set of `options`.
+ - parameter pipeline: A pipeline of stages to process query.
+ - parameter options: A set of header options sent to the server. Defaults to an empty set.
+ - returns: A publisher that eventually produces a single value and then finishes or fails.
+ */
+ func aggregatePublisher(_ pipeline: AggregateType,
+ options: API.Options = []) -> Future<[ResultType], ParseError> {
+ Future { promise in
+ self.aggregate(pipeline,
+ options: options,
+ completion: promise)
+ }
+ }
+}
+
+#endif
diff --git a/Sources/ParseSwift/Types/Query.swift b/Sources/ParseSwift/Types/Query.swift
index 219ff4757..4bed40fbe 100644
--- a/Sources/ParseSwift/Types/Query.swift
+++ b/Sources/ParseSwift/Types/Query.swift
@@ -754,7 +754,7 @@ public struct Query: Encodable, Equatable where T: ParseObject {
- warning: This is only for `ParseLiveQuery`.
- parameter keys: A variadic list of fields to receive back instead of the whole `ParseObject`.
*/
- @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
+ @available(macOS 10.15, iOS 13.0, macCatalyst 13.0, watchOS 6.0, tvOS 13.0, *)
public func fields(_ keys: String...) -> Query {
var mutableQuery = self
mutableQuery.fields = keys
@@ -771,7 +771,7 @@ public struct Query: Encodable, Equatable where T: ParseObject {
- warning: This is only for `ParseLiveQuery`.
- parameter keys: An array of fields to receive back instead of the whole `ParseObject`.
*/
- @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
+ @available(macOS 10.15, iOS 13.0, macCatalyst 13.0, watchOS 6.0, tvOS 13.0, *)
public func fields(_ keys: [String]) -> Query {
var mutableQuery = self
mutableQuery.fields = keys
@@ -872,7 +872,7 @@ extension Query: Queryable {
- parameter options: A set of header options sent to the server. Defaults to an empty set.
- parameter callbackQueue: The queue to return to after completion. Default value of .main.
- parameter completion: The block to execute.
- It should have the following argument signature: `(Result<[AnyResultType], ParseError>)`.
+ It should have the following argument signature: `(Result)`.
*/
public func find(explain: Bool, hint: String? = nil, options: API.Options = [],
callbackQueue: DispatchQueue = .main,
diff --git a/Tests/ParseSwiftTests/ParseAnonymousCombineTests.swift b/Tests/ParseSwiftTests/ParseAnonymousCombineTests.swift
new file mode 100644
index 000000000..f1dff0e3e
--- /dev/null
+++ b/Tests/ParseSwiftTests/ParseAnonymousCombineTests.swift
@@ -0,0 +1,136 @@
+//
+// ParseAnonymousCombineTests.swift
+// ParseSwift
+//
+// Created by Corey Baker on 1/30/21.
+// Copyright © 2021 Parse Community. All rights reserved.
+//
+
+#if canImport(Combine)
+
+import Foundation
+import XCTest
+import Combine
+@testable import ParseSwift
+
+@available(macOS 10.15, iOS 13.0, macCatalyst 13.0, watchOS 6.0, tvOS 13.0, *)
+class ParseAuthenticationCombineTests: XCTestCase { // swiftlint:disable:this type_body_length
+
+ struct User: ParseUser {
+
+ //: Those are required for Object
+ var objectId: String?
+ var createdAt: Date?
+ var updatedAt: Date?
+ var ACL: ParseACL?
+
+ // provided by User
+ var username: String?
+ var email: String?
+ var password: String?
+ var authData: [String: [String: String]?]?
+ }
+
+ struct LoginSignupResponse: ParseUser {
+
+ var objectId: String?
+ var createdAt: Date?
+ var sessionToken: String
+ var updatedAt: Date?
+ var ACL: ParseACL?
+
+ // provided by User
+ var username: String?
+ var email: String?
+ var password: String?
+ var authData: [String: [String: String]?]?
+
+ // Your custom keys
+ var customKey: String?
+
+ init() {
+ self.createdAt = Date()
+ self.updatedAt = Date()
+ self.objectId = "yarr"
+ self.ACL = nil
+ self.customKey = "blah"
+ self.sessionToken = "myToken"
+ self.username = "hello10"
+ self.email = "hello@parse.com"
+ }
+ }
+
+ override func setUpWithError() throws {
+ super.setUp()
+ guard let url = URL(string: "http://localhost:1337/1") else {
+ XCTFail("Should create valid URL")
+ return
+ }
+ ParseSwift.initialize(applicationId: "applicationId",
+ clientKey: "clientKey",
+ masterKey: "masterKey",
+ serverURL: url,
+ testing: true)
+ }
+
+ override func tearDownWithError() throws {
+ super.tearDown()
+ MockURLProtocol.removeAll()
+ #if !os(Linux)
+ try KeychainStore.shared.deleteAll()
+ #endif
+ try ParseStorage.shared.deleteAll()
+ }
+
+ func testLogin() {
+ var subscriptions = Set()
+ let expectation1 = XCTestExpectation(description: "Save")
+
+ var serverResponse = LoginSignupResponse()
+ let authData = ParseAnonymous.AuthenticationKeys.id.makeDictionary()
+ serverResponse.username = "hello"
+ serverResponse.password = "world"
+ serverResponse.objectId = "yarr"
+ serverResponse.sessionToken = "myToken"
+ serverResponse.authData = [serverResponse.anonymous.__type: authData]
+ serverResponse.createdAt = Date()
+ serverResponse.updatedAt = serverResponse.createdAt?.addingTimeInterval(+300)
+
+ var userOnServer: User!
+
+ let encoded: Data!
+ do {
+ encoded = try serverResponse.getEncoder().encode(serverResponse, skipKeys: .none)
+ //Get dates in correct format from ParseDecoding strategy
+ userOnServer = try serverResponse.getDecoder().decode(User.self, from: encoded)
+ } catch {
+ XCTFail("Should encode/decode. Error \(error)")
+ return
+ }
+ MockURLProtocol.mockRequests { _ in
+ return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0)
+ }
+
+ let publisher = User.anonymous.loginPublisher()
+ .sink(receiveCompletion: { result in
+
+ if case let .failure(error) = result {
+ XCTFail(error.localizedDescription)
+ }
+ expectation1.fulfill()
+
+ }, receiveValue: { user in
+
+ XCTAssertEqual(user, User.current)
+ XCTAssertEqual(user, userOnServer)
+ XCTAssertEqual(user.username, "hello")
+ XCTAssertEqual(user.password, "world")
+ XCTAssertTrue(user.anonymous.isLinked)
+ })
+ publisher.store(in: &subscriptions)
+
+ wait(for: [expectation1], timeout: 20.0)
+ }
+}
+
+#endif
diff --git a/Tests/ParseSwiftTests/ParseAppleCombineTests.swift b/Tests/ParseSwiftTests/ParseAppleCombineTests.swift
new file mode 100644
index 000000000..feebc740c
--- /dev/null
+++ b/Tests/ParseSwiftTests/ParseAppleCombineTests.swift
@@ -0,0 +1,249 @@
+//
+// ParseAppleCombineTests.swift
+// ParseSwift
+//
+// Created by Corey Baker on 1/30/21.
+// Copyright © 2021 Parse Community. All rights reserved.
+//
+
+#if canImport(Combine)
+
+import Foundation
+import XCTest
+import Combine
+@testable import ParseSwift
+
+@available(macOS 10.15, iOS 13.0, macCatalyst 13.0, watchOS 6.0, tvOS 13.0, *)
+class ParseAppleCombineTests: XCTestCase { // swiftlint:disable:this type_body_length
+
+ struct User: ParseUser {
+
+ //: Those are required for Object
+ var objectId: String?
+ var createdAt: Date?
+ var updatedAt: Date?
+ var ACL: ParseACL?
+
+ // provided by User
+ var username: String?
+ var email: String?
+ var password: String?
+ var authData: [String: [String: String]?]?
+ }
+
+ struct LoginSignupResponse: ParseUser {
+
+ var objectId: String?
+ var createdAt: Date?
+ var sessionToken: String
+ var updatedAt: Date?
+ var ACL: ParseACL?
+
+ // provided by User
+ var username: String?
+ var email: String?
+ var password: String?
+ var authData: [String: [String: String]?]?
+
+ // Your custom keys
+ var customKey: String?
+
+ init() {
+ self.createdAt = Date()
+ self.updatedAt = Date()
+ self.objectId = "yarr"
+ self.ACL = nil
+ self.customKey = "blah"
+ self.sessionToken = "myToken"
+ self.username = "hello10"
+ self.email = "hello@parse.com"
+ }
+ }
+
+ override func setUpWithError() throws {
+ super.setUp()
+ guard let url = URL(string: "http://localhost:1337/1") else {
+ XCTFail("Should create valid URL")
+ return
+ }
+ ParseSwift.initialize(applicationId: "applicationId",
+ clientKey: "clientKey",
+ masterKey: "masterKey",
+ serverURL: url,
+ testing: true)
+ }
+
+ override func tearDownWithError() throws {
+ super.tearDown()
+ MockURLProtocol.removeAll()
+ #if !os(Linux)
+ try KeychainStore.shared.deleteAll()
+ #endif
+ try ParseStorage.shared.deleteAll()
+ }
+
+ func testLogin() {
+ var subscriptions = Set()
+ let expectation1 = XCTestExpectation(description: "Save")
+
+ var serverResponse = LoginSignupResponse()
+ let authData = ParseAnonymous.AuthenticationKeys.id.makeDictionary()
+ serverResponse.username = "hello"
+ serverResponse.password = "world"
+ serverResponse.objectId = "yarr"
+ serverResponse.sessionToken = "myToken"
+ serverResponse.authData = [serverResponse.apple.__type: authData]
+ serverResponse.createdAt = Date()
+ serverResponse.updatedAt = serverResponse.createdAt?.addingTimeInterval(+300)
+
+ var userOnServer: User!
+
+ let encoded: Data!
+ do {
+ encoded = try serverResponse.getEncoder().encode(serverResponse, skipKeys: .none)
+ //Get dates in correct format from ParseDecoding strategy
+ userOnServer = try serverResponse.getDecoder().decode(User.self, from: encoded)
+ } catch {
+ XCTFail("Should encode/decode. Error \(error)")
+ return
+ }
+ MockURLProtocol.mockRequests { _ in
+ return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0)
+ }
+
+ let publisher = User.apple.loginPublisher(user: "testing", identityToken: "this")
+ .sink(receiveCompletion: { result in
+
+ if case let .failure(error) = result {
+ XCTFail(error.localizedDescription)
+ }
+ expectation1.fulfill()
+
+ }, receiveValue: { user in
+
+ XCTAssertEqual(user, User.current)
+ XCTAssertEqual(user, userOnServer)
+ XCTAssertEqual(user.username, "hello")
+ XCTAssertEqual(user.password, "world")
+ XCTAssertTrue(user.apple.isLinked)
+ })
+ publisher.store(in: &subscriptions)
+
+ wait(for: [expectation1], timeout: 20.0)
+ }
+
+ func loginNormally() throws -> User {
+ let loginResponse = LoginSignupResponse()
+
+ MockURLProtocol.mockRequests { _ in
+ do {
+ let encoded = try loginResponse.getEncoder().encode(loginResponse, skipKeys: .none)
+ return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0)
+ } catch {
+ return nil
+ }
+ }
+ return try User.login(username: "parse", password: "user")
+ }
+
+ func testLink() throws {
+ var subscriptions = Set()
+ let expectation1 = XCTestExpectation(description: "Save")
+
+ _ = try loginNormally()
+ MockURLProtocol.removeAll()
+
+ var serverResponse = LoginSignupResponse()
+ serverResponse.updatedAt = Date()
+
+ var userOnServer: User!
+
+ let encoded: Data!
+ do {
+ encoded = try serverResponse.getEncoder().encode(serverResponse, skipKeys: .none)
+ //Get dates in correct format from ParseDecoding strategy
+ userOnServer = try serverResponse.getDecoder().decode(User.self, from: encoded)
+ } catch {
+ XCTFail("Should encode/decode. Error \(error)")
+ return
+ }
+ MockURLProtocol.mockRequests { _ in
+ return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0)
+ }
+
+ let publisher = User.apple.linkPublisher(user: "testing", identityToken: "this")
+ .sink(receiveCompletion: { result in
+
+ if case let .failure(error) = result {
+ XCTFail(error.localizedDescription)
+ }
+ expectation1.fulfill()
+
+ }, receiveValue: { user in
+
+ XCTAssertEqual(user, User.current)
+ XCTAssertEqual(user.updatedAt, userOnServer.updatedAt)
+ XCTAssertEqual(user.username, "parse")
+ XCTAssertNil(user.password)
+ XCTAssertTrue(user.apple.isLinked)
+ XCTAssertFalse(user.anonymous.isLinked)
+ })
+ publisher.store(in: &subscriptions)
+
+ wait(for: [expectation1], timeout: 20.0)
+ }
+
+ func testUnlink() throws {
+ var subscriptions = Set()
+ let expectation1 = XCTestExpectation(description: "Save")
+
+ _ = try loginNormally()
+ MockURLProtocol.removeAll()
+
+ let authData = ParseApple
+ .AuthenticationKeys.id.makeDictionary(user: "testing",
+ identityToken: "this")
+ User.current?.authData = [User.apple.__type: authData]
+ XCTAssertTrue(User.apple.isLinked)
+
+ var serverResponse = LoginSignupResponse()
+ serverResponse.updatedAt = Date()
+
+ var userOnServer: User!
+
+ let encoded: Data!
+ do {
+ encoded = try serverResponse.getEncoder().encode(serverResponse, skipKeys: .none)
+ //Get dates in correct format from ParseDecoding strategy
+ userOnServer = try serverResponse.getDecoder().decode(User.self, from: encoded)
+ } catch {
+ XCTFail("Should encode/decode. Error \(error)")
+ return
+ }
+ MockURLProtocol.mockRequests { _ in
+ return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0)
+ }
+
+ let publisher = User.apple.unlinkPublisher()
+ .sink(receiveCompletion: { result in
+
+ if case let .failure(error) = result {
+ XCTFail(error.localizedDescription)
+ }
+ expectation1.fulfill()
+
+ }, receiveValue: { user in
+
+ XCTAssertEqual(user, User.current)
+ XCTAssertEqual(user.updatedAt, userOnServer.updatedAt)
+ XCTAssertEqual(user.username, "parse")
+ XCTAssertNil(user.password)
+ XCTAssertFalse(user.apple.isLinked)
+ })
+ publisher.store(in: &subscriptions)
+
+ wait(for: [expectation1], timeout: 20.0)
+ }
+}
+
+#endif
diff --git a/Tests/ParseSwiftTests/ParseAuthenticationTests.swift b/Tests/ParseSwiftTests/ParseAuthenticationTests.swift
index 827eb358b..e7d95475f 100644
--- a/Tests/ParseSwiftTests/ParseAuthenticationTests.swift
+++ b/Tests/ParseSwiftTests/ParseAuthenticationTests.swift
@@ -9,6 +9,9 @@
import Foundation
import XCTest
@testable import ParseSwift
+#if canImport(Combine)
+import Combine
+#endif
class ParseAuthenticationTests: XCTestCase {
@@ -46,6 +49,26 @@ class ParseAuthenticationTests: XCTestCase {
let error = ParseError(code: .unknownError, message: "Not implemented")
completion(.failure(error))
}
+
+ #if canImport(Combine)
+ @available(macOS 10.15, iOS 13.0, macCatalyst 13.0, watchOS 6.0, tvOS 13.0, *)
+ func loginPublisher(authData: [String: String]?,
+ options: API.Options) -> Future {
+ let error = ParseError(code: .unknownError, message: "Not implemented")
+ return Future { promise in
+ promise(.failure(error))
+ }
+ }
+
+ @available(macOS 10.15, iOS 13.0, macCatalyst 13.0, watchOS 6.0, tvOS 13.0, *)
+ func linkPublisher(authData: [String: String]?,
+ options: API.Options) -> Future {
+ let error = ParseError(code: .unknownError, message: "Not implemented")
+ return Future { promise in
+ promise(.failure(error))
+ }
+ }
+ #endif
}
override func setUpWithError() throws {
diff --git a/Tests/ParseSwiftTests/ParseCloudCombineTests.swift b/Tests/ParseSwiftTests/ParseCloudCombineTests.swift
new file mode 100644
index 000000000..367d79bf1
--- /dev/null
+++ b/Tests/ParseSwiftTests/ParseCloudCombineTests.swift
@@ -0,0 +1,113 @@
+//
+// ParseCloudCombineTests.swift
+// ParseSwift
+//
+// Created by Corey Baker on 1/30/21.
+// Copyright © 2021 Parse Community. All rights reserved.
+//
+
+#if canImport(Combine)
+
+import Foundation
+import XCTest
+import Combine
+@testable import ParseSwift
+
+@available(macOS 10.15, iOS 13.0, macCatalyst 13.0, watchOS 6.0, tvOS 13.0, *)
+class ParseCloudCombineTests: XCTestCase { // swiftlint:disable:this type_body_length
+
+ struct Cloud: ParseCloud {
+ // Those are required for Object
+ var functionJobName: String
+ }
+
+ override func setUpWithError() throws {
+ super.setUp()
+ guard let url = URL(string: "http://localhost:1337/1") else {
+ XCTFail("Should create valid URL")
+ return
+ }
+ ParseSwift.initialize(applicationId: "applicationId",
+ clientKey: "clientKey",
+ masterKey: "masterKey",
+ serverURL: url,
+ testing: true)
+ }
+
+ override func tearDownWithError() throws {
+ super.tearDown()
+ MockURLProtocol.removeAll()
+ #if !os(Linux)
+ try KeychainStore.shared.deleteAll()
+ #endif
+ try ParseStorage.shared.deleteAll()
+ }
+
+ func testFunction() {
+ var subscriptions = Set()
+ let expectation1 = XCTestExpectation(description: "Save")
+
+ let response = AnyResultResponse(result: nil)
+
+ MockURLProtocol.mockRequests { _ in
+ do {
+ let encoded = try ParseCoding.jsonEncoder().encode(response)
+ return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0)
+ } catch {
+ return nil
+ }
+ }
+
+ let cloud = Cloud(functionJobName: "test")
+ let publisher = cloud.runFunctionPublisher()
+ .sink(receiveCompletion: { result in
+
+ if case let .failure(error) = result {
+ XCTFail(error.localizedDescription)
+ }
+ expectation1.fulfill()
+
+ }, receiveValue: { functionResponse in
+
+ XCTAssertEqual(functionResponse, AnyCodable())
+ })
+ publisher.store(in: &subscriptions)
+
+ wait(for: [expectation1], timeout: 20.0)
+ }
+
+ func testJob() {
+ var subscriptions = Set()
+ let expectation1 = XCTestExpectation(description: "Save")
+
+ let response = AnyResultResponse(result: nil)
+
+ MockURLProtocol.mockRequests { _ in
+ do {
+ let encoded = try ParseCoding.jsonEncoder().encode(response)
+ return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0)
+ } catch {
+ return nil
+ }
+ }
+
+ let cloud = Cloud(functionJobName: "test")
+ let publisher = cloud.startJobPublisher()
+ .sink(receiveCompletion: { result in
+
+ if case let .failure(error) = result {
+ XCTFail(error.localizedDescription)
+ }
+ expectation1.fulfill()
+
+ }, receiveValue: { functionResponse in
+
+ XCTAssertEqual(functionResponse, AnyCodable())
+ })
+ publisher.store(in: &subscriptions)
+
+ wait(for: [expectation1], timeout: 20.0)
+ }
+}
+
+#endif
diff --git a/Tests/ParseSwiftTests/ParseConfigCombineTests.swift b/Tests/ParseSwiftTests/ParseConfigCombineTests.swift
new file mode 100644
index 000000000..7b2eea0e9
--- /dev/null
+++ b/Tests/ParseSwiftTests/ParseConfigCombineTests.swift
@@ -0,0 +1,216 @@
+//
+// ParseConfigCombineTests.swift
+// ParseSwift
+//
+// Created by Corey Baker on 1/30/21.
+// Copyright © 2021 Parse Community. All rights reserved.
+//
+
+#if canImport(Combine)
+
+import Foundation
+import XCTest
+import Combine
+@testable import ParseSwift
+
+@available(macOS 10.15, iOS 13.0, macCatalyst 13.0, watchOS 6.0, tvOS 13.0, *)
+class ParseConfigCombineTests: XCTestCase { // swiftlint:disable:this type_body_length
+
+ struct Config: ParseConfig {
+ var welcomeMessage: String?
+ var winningNumber: Int?
+ }
+
+ struct User: ParseUser {
+
+ //: Those are required for Object
+ var objectId: String?
+ var createdAt: Date?
+ var updatedAt: Date?
+ var ACL: ParseACL?
+
+ // provided by User
+ var username: String?
+ var email: String?
+ var password: String?
+ var authData: [String: [String: String]?]?
+
+ // Your custom keys
+ var customKey: String?
+ }
+
+ struct LoginSignupResponse: ParseUser {
+
+ var objectId: String?
+ var createdAt: Date?
+ var sessionToken: String
+ var updatedAt: Date?
+ var ACL: ParseACL?
+
+ // provided by User
+ var username: String?
+ var email: String?
+ var password: String?
+ var authData: [String: [String: String]?]?
+
+ // Your custom keys
+ var customKey: String?
+
+ init() {
+ self.createdAt = Date()
+ self.updatedAt = Date()
+ self.objectId = "yarr"
+ self.ACL = nil
+ self.customKey = "blah"
+ self.sessionToken = "myToken"
+ self.username = "hello10"
+ self.email = "hello@parse.com"
+ }
+ }
+
+ override func setUpWithError() throws {
+ super.setUp()
+ guard let url = URL(string: "http://localhost:1337/1") else {
+ XCTFail("Should create valid URL")
+ return
+ }
+ ParseSwift.initialize(applicationId: "applicationId",
+ clientKey: "clientKey",
+ masterKey: "masterKey",
+ serverURL: url,
+ testing: true)
+ }
+
+ override func tearDownWithError() throws {
+ super.tearDown()
+ MockURLProtocol.removeAll()
+ #if !os(Linux)
+ try KeychainStore.shared.deleteAll()
+ #endif
+ try ParseStorage.shared.deleteAll()
+ }
+
+ func userLogin() {
+ let loginResponse = LoginSignupResponse()
+ let loginUserName = "hello10"
+ let loginPassword = "world"
+
+ MockURLProtocol.mockRequests { _ in
+ do {
+ let encoded = try loginResponse.getEncoder().encode(loginResponse, skipKeys: .none)
+ return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0)
+ } catch {
+ return nil
+ }
+ }
+ do {
+ _ = try User.login(username: loginUserName, password: loginPassword)
+ MockURLProtocol.removeAll()
+ } catch {
+ XCTFail("Should login")
+ }
+ }
+
+ func testFetch() {
+ var subscriptions = Set()
+ let expectation1 = XCTestExpectation(description: "Save")
+
+ userLogin()
+ let config = Config()
+
+ var configOnServer = config
+ configOnServer.welcomeMessage = "Hello"
+ let serverResponse = ConfigFetchResponse(params: configOnServer)
+ let encoded: Data!
+ do {
+ encoded = try ParseCoding.jsonEncoder().encode(serverResponse)
+ } catch {
+ XCTFail("Should encode/decode. Error \(error)")
+ return
+ }
+
+ MockURLProtocol.mockRequests { _ in
+ return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0)
+ }
+
+ let publisher = config.fetchPublisher()
+ .sink(receiveCompletion: { result in
+
+ if case let .failure(error) = result {
+ XCTFail(error.localizedDescription)
+ }
+ expectation1.fulfill()
+
+ }, receiveValue: { fetched in
+
+ XCTAssertEqual(fetched.welcomeMessage, configOnServer.welcomeMessage)
+
+ #if !os(Linux)
+ //Should be updated in Keychain
+ guard let keychainConfig: CurrentConfigContainer
+ = try? KeychainStore.shared.get(valueFor: ParseStorage.Keys.currentConfig) else {
+ XCTFail("Should get object from Keychain")
+ return
+ }
+ XCTAssertEqual(keychainConfig.currentConfig?.welcomeMessage, configOnServer.welcomeMessage)
+ #endif
+
+ XCTAssertEqual(Config.current?.welcomeMessage, configOnServer.welcomeMessage)
+ })
+ publisher.store(in: &subscriptions)
+
+ wait(for: [expectation1], timeout: 20.0)
+ }
+
+ func testSave() {
+ var subscriptions = Set()
+ let expectation1 = XCTestExpectation(description: "Save")
+
+ userLogin()
+ var config = Config()
+ config.welcomeMessage = "Hello"
+
+ let serverResponse = ConfigUpdateResponse(result: true)
+ let encoded: Data!
+ do {
+ encoded = try ParseCoding.jsonEncoder().encode(serverResponse)
+ } catch {
+ XCTFail("Should encode/decode. Error \(error)")
+ return
+ }
+
+ MockURLProtocol.mockRequests { _ in
+ return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0)
+ }
+
+ let publisher = config.savePublisher()
+ .sink(receiveCompletion: { result in
+
+ if case let .failure(error) = result {
+ XCTFail(error.localizedDescription)
+ }
+ expectation1.fulfill()
+
+ }, receiveValue: { saved in
+
+ XCTAssertTrue(saved)
+
+ #if !os(Linux)
+ //Should be updated in Keychain
+ guard let keychainConfig: CurrentConfigContainer
+ = try? KeychainStore.shared.get(valueFor: ParseStorage.Keys.currentConfig) else {
+ XCTFail("Should get object from Keychain")
+ return
+ }
+ XCTAssertEqual(keychainConfig.currentConfig?.welcomeMessage, config.welcomeMessage)
+ #endif
+
+ XCTAssertEqual(Config.current?.welcomeMessage, config.welcomeMessage)
+ })
+ publisher.store(in: &subscriptions)
+
+ wait(for: [expectation1], timeout: 20.0)
+ }
+}
+
+#endif
diff --git a/Tests/ParseSwiftTests/ParseFileCombineTests.swift b/Tests/ParseSwiftTests/ParseFileCombineTests.swift
new file mode 100644
index 000000000..4a8162d02
--- /dev/null
+++ b/Tests/ParseSwiftTests/ParseFileCombineTests.swift
@@ -0,0 +1,296 @@
+//
+// ParseFileCombineTests.swift
+// ParseSwift
+//
+// Created by Corey Baker on 1/30/21.
+// Copyright © 2021 Parse Community. All rights reserved.
+//
+
+#if canImport(Combine)
+
+import Foundation
+import XCTest
+import Combine
+@testable import ParseSwift
+
+@available(macOS 10.15, iOS 13.0, macCatalyst 13.0, watchOS 6.0, tvOS 13.0, *)
+class ParseFileCombineTests: XCTestCase { // swiftlint:disable:this type_body_length
+
+ let temporaryDirectory = "\(NSTemporaryDirectory())test/"
+
+ struct FileUploadResponse: Codable {
+ let name: String
+ let url: URL
+ }
+
+ override func setUpWithError() throws {
+ super.setUp()
+ guard let url = URL(string: "http://localhost:1337/1") else {
+ XCTFail("Should create valid URL")
+ return
+ }
+ ParseSwift.initialize(applicationId: "applicationId",
+ clientKey: "clientKey",
+ masterKey: "masterKey",
+ serverURL: url,
+ testing: true)
+ guard let fileManager = ParseFileManager() else {
+ throw ParseError(code: .unknownError, message: "Should have initialized file manage")
+ }
+ try fileManager.createDirectoryIfNeeded(temporaryDirectory)
+ }
+
+ override func tearDownWithError() throws {
+ super.tearDown()
+ MockURLProtocol.removeAll()
+ #if !os(Linux)
+ try KeychainStore.shared.deleteAll()
+ #endif
+ try ParseStorage.shared.deleteAll()
+
+ guard let fileManager = ParseFileManager(),
+ let defaultDirectoryPath = fileManager.defaultDataDirectoryPath else {
+ throw ParseError(code: .unknownError, message: "Should have initialized file manage")
+ }
+ let directory = URL(fileURLWithPath: temporaryDirectory, isDirectory: true)
+ let expectation1 = XCTestExpectation(description: "Delete files1")
+ fileManager.removeDirectoryContents(directory) { error in
+ guard let error = error else {
+ expectation1.fulfill()
+ return
+ }
+ XCTFail(error.localizedDescription)
+ expectation1.fulfill()
+ }
+ let directory2 = defaultDirectoryPath
+ .appendingPathComponent(ParseConstants.fileDownloadsDirectory, isDirectory: true)
+ let expectation2 = XCTestExpectation(description: "Delete files2")
+ fileManager.removeDirectoryContents(directory2) { _ in
+ expectation2.fulfill()
+ }
+ wait(for: [expectation1, expectation2], timeout: 20.0)
+ }
+
+ func testFetch() {
+ var subscriptions = Set()
+ let expectation1 = XCTestExpectation(description: "Fetch")
+
+ // swiftlint:disable:next line_length
+ guard let parseFileURL = URL(string: "http://localhost:1337/1/files/applicationId/d3a37aed0672a024595b766f97133615_logo.svg") else {
+ XCTFail("Should create URL")
+ return
+ }
+ var parseFile = ParseFile(name: "d3a37aed0672a024595b766f97133615_logo.svg", cloudURL: parseFileURL)
+ parseFile.url = parseFileURL
+
+ let response = FileUploadResponse(name: "d3a37aed0672a024595b766f97133615_logo.svg",
+ url: parseFileURL)
+ let encoded: Data!
+ do {
+ encoded = try ParseCoding.jsonEncoder().encode(response)
+ } catch {
+ XCTFail("Should encode/decode. Error \(error)")
+ return
+ }
+ MockURLProtocol.mockRequests { _ in
+ return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0)
+ }
+
+ let publisher = parseFile.fetchPublisher()
+ .sink(receiveCompletion: { result in
+
+ if case let .failure(error) = result {
+ XCTFail(error.localizedDescription)
+ }
+ expectation1.fulfill()
+
+ }, receiveValue: { fetched in
+
+ XCTAssertEqual(fetched.name, response.name)
+ XCTAssertEqual(fetched.url, response.url)
+ XCTAssertNotNil(fetched.localURL)
+ })
+ publisher.store(in: &subscriptions)
+
+ wait(for: [expectation1], timeout: 20.0)
+ }
+
+ func testFetchFileProgress() {
+ var subscriptions = Set()
+ let expectation1 = XCTestExpectation(description: "Fetch")
+
+ // swiftlint:disable:next line_length
+ guard let parseFileURL = URL(string: "http://localhost:1337/1/files/applicationId/d3a37aed0672a024595b766f97133615_logo.svg") else {
+ XCTFail("Should create URL")
+ return
+ }
+ var parseFile = ParseFile(name: "d3a37aed0672a024595b766f97133615_logo.svg", cloudURL: parseFileURL)
+ parseFile.url = parseFileURL
+
+ let response = FileUploadResponse(name: "d3a37aed0672a024595b766f97133615_logo.svg",
+ url: parseFileURL)
+ let encoded: Data!
+ do {
+ encoded = try ParseCoding.jsonEncoder().encode(response)
+ } catch {
+ XCTFail("Should encode/decode. Error \(error)")
+ return
+ }
+ MockURLProtocol.mockRequests { _ in
+ return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0)
+ }
+
+ let publisher = parseFile.fetchPublisher(progress: { (_, _, totalDownloaded, totalExpected) in
+ let currentProgess = Double(totalDownloaded)/Double(totalExpected) * 100
+ XCTAssertGreaterThan(currentProgess, -1)
+ }).sink(receiveCompletion: { result in
+
+ if case let .failure(error) = result {
+ XCTFail(error.localizedDescription)
+ }
+ expectation1.fulfill()
+
+ }, receiveValue: { fetched in
+
+ XCTAssertEqual(fetched.name, response.name)
+ XCTAssertEqual(fetched.url, response.url)
+ XCTAssertNotNil(fetched.localURL)
+ })
+ publisher.store(in: &subscriptions)
+
+ wait(for: [expectation1], timeout: 20.0)
+ }
+
+ func testSave() throws {
+ var subscriptions = Set()
+ let expectation1 = XCTestExpectation(description: "Fetch")
+
+ guard let sampleData = "Hello World".data(using: .utf8) else {
+ throw ParseError(code: .unknownError, message: "Should have converted to data")
+ }
+ let parseFile = ParseFile(name: "sampleData.txt", data: sampleData)
+
+ // swiftlint:disable:next line_length
+ guard let url = URL(string: "http://localhost:1337/1/files/applicationId/89d74fcfa4faa5561799e5076593f67c_sampleData.txt") else {
+ XCTFail("Should create URL")
+ return
+ }
+ let response = FileUploadResponse(name: "89d74fcfa4faa5561799e5076593f67c_\(parseFile.name)", url: url)
+ let encoded: Data!
+ do {
+ encoded = try ParseCoding.jsonEncoder().encode(response)
+ } catch {
+ XCTFail("Should encode/decode. Error \(error)")
+ return
+ }
+ MockURLProtocol.mockRequests { _ in
+ return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0)
+ }
+
+ let publisher = parseFile.savePublisher()
+ .sink(receiveCompletion: { result in
+
+ if case let .failure(error) = result {
+ XCTFail(error.localizedDescription)
+ }
+ expectation1.fulfill()
+
+ }, receiveValue: { fetched in
+
+ XCTAssertEqual(fetched.name, response.name)
+ XCTAssertEqual(fetched.url, response.url)
+ })
+ publisher.store(in: &subscriptions)
+
+ wait(for: [expectation1], timeout: 20.0)
+ }
+
+ func testSaveFileProgress() throws {
+ var subscriptions = Set()
+ let expectation1 = XCTestExpectation(description: "Fetch")
+
+ guard let sampleData = "Hello World".data(using: .utf8) else {
+ throw ParseError(code: .unknownError, message: "Should have converted to data")
+ }
+ let parseFile = ParseFile(name: "sampleData.txt", data: sampleData)
+
+ // swiftlint:disable:next line_length
+ guard let url = URL(string: "http://localhost:1337/1/files/applicationId/89d74fcfa4faa5561799e5076593f67c_sampleData.txt") else {
+ XCTFail("Should create URL")
+ return
+ }
+ let response = FileUploadResponse(name: "89d74fcfa4faa5561799e5076593f67c_\(parseFile.name)", url: url)
+ let encoded: Data!
+ do {
+ encoded = try ParseCoding.jsonEncoder().encode(response)
+ } catch {
+ XCTFail("Should encode/decode. Error \(error)")
+ return
+ }
+ MockURLProtocol.mockRequests { _ in
+ return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0)
+ }
+
+ let publisher = parseFile.savePublisher(progress: { (_, _, totalDownloaded, totalExpected) in
+ let currentProgess = Double(totalDownloaded)/Double(totalExpected) * 100
+ XCTAssertGreaterThan(currentProgess, -1)
+ }).sink(receiveCompletion: { result in
+
+ if case let .failure(error) = result {
+ XCTFail(error.localizedDescription)
+ }
+ expectation1.fulfill()
+
+ }, receiveValue: { fetched in
+
+ XCTAssertEqual(fetched.name, response.name)
+ XCTAssertEqual(fetched.url, response.url)
+ })
+ publisher.store(in: &subscriptions)
+
+ wait(for: [expectation1], timeout: 20.0)
+ }
+
+ func testDelete() throws {
+ var subscriptions = Set()
+ let expectation1 = XCTestExpectation(description: "Fetch")
+
+ // swiftlint:disable:next line_length
+ guard let parseFileURL = URL(string: "http://localhost:1337/1/files/applicationId/d3a37aed0672a024595b766f97133615_logo.svg") else {
+ XCTFail("Should create URL")
+ return
+ }
+ var parseFile = ParseFile(name: "d3a37aed0672a024595b766f97133615_logo.svg", cloudURL: parseFileURL)
+ parseFile.url = parseFileURL
+
+ let response = FileUploadResponse(name: "d3a37aed0672a024595b766f97133615_logo.svg",
+ url: parseFileURL)
+ let encoded: Data!
+ do {
+ encoded = try ParseCoding.jsonEncoder().encode(response)
+ } catch {
+ XCTFail("Should encode/decode. Error \(error)")
+ return
+ }
+ MockURLProtocol.mockRequests { _ in
+ return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0)
+ }
+
+ let publisher = parseFile.deletePublisher(options: [.useMasterKey])
+ .sink(receiveCompletion: { result in
+
+ if case let .failure(error) = result {
+ XCTFail(error.localizedDescription)
+ }
+ expectation1.fulfill()
+
+ }, receiveValue: { _ in
+
+ })
+ publisher.store(in: &subscriptions)
+
+ wait(for: [expectation1], timeout: 20.0)
+ }
+}
+
+#endif
diff --git a/Tests/ParseSwiftTests/ParseFileTests.swift b/Tests/ParseSwiftTests/ParseFileTests.swift
index dfe60ed60..51d271894 100644
--- a/Tests/ParseSwiftTests/ParseFileTests.swift
+++ b/Tests/ParseSwiftTests/ParseFileTests.swift
@@ -1068,13 +1068,11 @@ class ParseFileTests: XCTestCase { // swiftlint:disable:this type_body_length
}
let expectation1 = XCTestExpectation(description: "ParseFile async")
- parseFile.delete(options: [.useMasterKey]) { error in
+ parseFile.delete(options: [.useMasterKey]) { result in
- guard let error = error else {
- expectation1.fulfill()
- return
+ if case let .failure(error) = result {
+ XCTFail(error.localizedDescription)
}
- XCTFail(error.localizedDescription)
expectation1.fulfill()
}
wait(for: [expectation1], timeout: 20.0)
@@ -1104,12 +1102,10 @@ class ParseFileTests: XCTestCase { // swiftlint:disable:this type_body_length
}
let expectation1 = XCTestExpectation(description: "ParseFile async")
- parseFile.delete(options: [.removeMimeType]) { error in
+ parseFile.delete(options: [.removeMimeType]) { result in
- guard error != nil else {
+ if case .success = result {
XCTFail("Should have thrown error")
- expectation1.fulfill()
- return
}
expectation1.fulfill()
}
diff --git a/Tests/ParseSwiftTests/ParseInstallationCombineTests.swift b/Tests/ParseSwiftTests/ParseInstallationCombineTests.swift
new file mode 100644
index 000000000..01655f78a
--- /dev/null
+++ b/Tests/ParseSwiftTests/ParseInstallationCombineTests.swift
@@ -0,0 +1,548 @@
+//
+// ParseInstallationCombineTests.swift
+// ParseSwift
+//
+// Created by Corey Baker on 1/30/21.
+// Copyright © 2021 Parse Community. All rights reserved.
+//
+
+#if canImport(Combine)
+
+import Foundation
+import XCTest
+import Combine
+@testable import ParseSwift
+
+@available(macOS 10.15, iOS 13.0, macCatalyst 13.0, watchOS 6.0, tvOS 13.0, *)
+class ParseInstallationCombineTests: XCTestCase { // swiftlint:disable:this type_body_length
+
+ struct User: ParseUser {
+
+ //: Those are required for Object
+ var objectId: String?
+ var createdAt: Date?
+ var updatedAt: Date?
+ var ACL: ParseACL?
+
+ // provided by User
+ var username: String?
+ var email: String?
+ var password: String?
+ var authData: [String: [String: String]?]?
+
+ // Your custom keys
+ var customKey: String?
+ }
+
+ struct LoginSignupResponse: ParseUser {
+
+ var objectId: String?
+ var createdAt: Date?
+ var sessionToken: String
+ var updatedAt: Date?
+ var ACL: ParseACL?
+
+ // provided by User
+ var username: String?
+ var email: String?
+ var password: String?
+ var authData: [String: [String: String]?]?
+
+ // Your custom keys
+ var customKey: String?
+
+ init() {
+ self.createdAt = Date()
+ self.updatedAt = Date()
+ self.objectId = "yarr"
+ self.ACL = nil
+ self.customKey = "blah"
+ self.sessionToken = "myToken"
+ self.username = "hello10"
+ self.email = "hello@parse.com"
+ }
+ }
+
+ struct Installation: ParseInstallation {
+ var installationId: String?
+ var deviceType: String?
+ var deviceToken: String?
+ var badge: Int?
+ var timeZone: String?
+ var channels: [String]?
+ var appName: String?
+ var appIdentifier: String?
+ var appVersion: String?
+ var parseVersion: String?
+ var localeIdentifier: String?
+ var objectId: String?
+ var createdAt: Date?
+ var updatedAt: Date?
+ var ACL: ParseACL?
+ var customKey: String?
+ }
+
+ let testInstallationObjectId = "yarr"
+
+ let loginUserName = "hello10"
+ let loginPassword = "world"
+
+ override func setUpWithError() throws {
+ super.setUp()
+ guard let url = URL(string: "http://localhost:1337/1") else {
+ XCTFail("Should create valid URL")
+ return
+ }
+ ParseSwift.initialize(applicationId: "applicationId",
+ clientKey: "clientKey",
+ masterKey: "masterKey",
+ serverURL: url,
+ testing: true)
+ login()
+ }
+
+ override func tearDownWithError() throws {
+ super.tearDown()
+ MockURLProtocol.removeAll()
+ #if !os(Linux)
+ try KeychainStore.shared.deleteAll()
+ #endif
+ try ParseStorage.shared.deleteAll()
+ }
+
+ func login() {
+ let loginResponse = LoginSignupResponse()
+
+ MockURLProtocol.mockRequests { _ in
+ do {
+ let encoded = try loginResponse.getEncoder().encode(loginResponse, skipKeys: .none)
+ return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0)
+ } catch {
+ return nil
+ }
+ }
+ do {
+ _ = try User.login(username: loginUserName, password: loginPassword)
+
+ } catch {
+ XCTFail(error.localizedDescription)
+ }
+ }
+
+ func update() {
+ var installation = Installation()
+ installation.objectId = testInstallationObjectId
+ installation.createdAt = Calendar.current.date(byAdding: .init(day: -1), to: Date())
+ installation.updatedAt = Calendar.current.date(byAdding: .init(day: -1), to: Date())
+ installation.ACL = nil
+
+ var installationOnServer = installation
+ installationOnServer.updatedAt = Date()
+
+ let encoded: Data!
+ do {
+ encoded = try installationOnServer.getEncoder().encode(installationOnServer, skipKeys: .none)
+ //Get dates in correct format from ParseDecoding strategy
+ installationOnServer = try installationOnServer.getDecoder().decode(Installation.self, from: encoded)
+ } catch {
+ XCTFail("Should encode/decode. Error \(error)")
+ return
+ }
+ MockURLProtocol.mockRequests { _ in
+ return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0)
+ }
+ let expectation1 = XCTestExpectation(description: "Update installation1")
+ DispatchQueue.main.async {
+ do {
+ let saved = try installation.save()
+ guard let savedUpdatedAt = saved.updatedAt else {
+ XCTFail("Should unwrap dates")
+ expectation1.fulfill()
+ return
+ }
+ guard let originalUpdatedAt = installation.updatedAt else {
+ XCTFail("Should unwrap dates")
+ expectation1.fulfill()
+ return
+ }
+ XCTAssertGreaterThan(savedUpdatedAt, originalUpdatedAt)
+ XCTAssertNil(saved.ACL)
+ } catch {
+ XCTFail(error.localizedDescription)
+ }
+ expectation1.fulfill()
+ }
+ wait(for: [expectation1], timeout: 20.0)
+ }
+
+ func testFetch() {
+ update()
+ MockURLProtocol.removeAll()
+
+ var subscriptions = Set()
+ let expectation1 = XCTestExpectation(description: "Update installation1")
+ DispatchQueue.main.async {
+ guard let installation = Installation.current,
+ let savedObjectId = installation.objectId else {
+ XCTFail("Should unwrap")
+ expectation1.fulfill()
+ return
+ }
+ XCTAssertEqual(savedObjectId, self.testInstallationObjectId)
+
+ var serverResponse = installation
+ serverResponse.updatedAt = installation.updatedAt?.addingTimeInterval(+300)
+ serverResponse.customKey = "newValue"
+
+ MockURLProtocol.mockRequests { _ in
+ do {
+ let encoded = try serverResponse.getEncoder().encode(serverResponse, skipKeys: .none)
+ return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0)
+ } catch {
+ return nil
+ }
+ }
+
+ let publisher = installation.fetchPublisher()
+ .sink(receiveCompletion: { result in
+
+ if case let .failure(error) = result {
+ XCTFail(error.localizedDescription)
+ }
+ expectation1.fulfill()
+
+ }, receiveValue: { fetched in
+
+ XCTAssert(fetched.hasSameObjectId(as: serverResponse))
+ XCTAssertEqual(Installation.current?.customKey, serverResponse.customKey)
+ })
+ publisher.store(in: &subscriptions)
+ }
+ wait(for: [expectation1], timeout: 20.0)
+ }
+
+ func testSave() {
+ update()
+ MockURLProtocol.removeAll()
+
+ var subscriptions = Set()
+ let expectation1 = XCTestExpectation(description: "Update installation1")
+ DispatchQueue.main.async {
+ guard var installation = Installation.current,
+ let savedObjectId = installation.objectId else {
+ XCTFail("Should unwrap")
+ expectation1.fulfill()
+ return
+ }
+ installation.customKey = "newValue"
+ XCTAssertEqual(savedObjectId, self.testInstallationObjectId)
+
+ var serverResponse = installation
+ serverResponse.updatedAt = installation.updatedAt?.addingTimeInterval(+300)
+
+ MockURLProtocol.mockRequests { _ in
+ do {
+ let encoded = try serverResponse.getEncoder().encode(serverResponse, skipKeys: .none)
+ return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0)
+ } catch {
+ return nil
+ }
+ }
+
+ let publisher = installation.savePublisher()
+ .sink(receiveCompletion: { result in
+
+ if case let .failure(error) = result {
+ XCTFail(error.localizedDescription)
+ }
+ expectation1.fulfill()
+
+ }, receiveValue: { fetched in
+
+ XCTAssert(fetched.hasSameObjectId(as: serverResponse))
+ XCTAssertEqual(Installation.current?.customKey, serverResponse.customKey)
+ })
+ publisher.store(in: &subscriptions)
+ }
+ wait(for: [expectation1], timeout: 20.0)
+ }
+
+ func testDelete() {
+ update()
+ MockURLProtocol.removeAll()
+
+ var subscriptions = Set()
+ let expectation1 = XCTestExpectation(description: "Update installation1")
+ DispatchQueue.main.async {
+ guard let installation = Installation.current,
+ let savedObjectId = installation.objectId else {
+ XCTFail("Should unwrap")
+ expectation1.fulfill()
+ return
+ }
+ XCTAssertEqual(savedObjectId, self.testInstallationObjectId)
+
+ var serverResponse = installation
+ serverResponse.updatedAt = installation.updatedAt?.addingTimeInterval(+300)
+ serverResponse.customKey = "newValue"
+
+ MockURLProtocol.mockRequests { _ in
+ do {
+ let encoded = try serverResponse.getEncoder().encode(serverResponse, skipKeys: .none)
+ return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0)
+ } catch {
+ return nil
+ }
+ }
+
+ let publisher = installation.deletePublisher()
+ .sink(receiveCompletion: { result in
+
+ if case let .failure(error) = result {
+ XCTFail(error.localizedDescription)
+ }
+ expectation1.fulfill()
+
+ }, receiveValue: { _ in
+
+ })
+ publisher.store(in: &subscriptions)
+ }
+ wait(for: [expectation1], timeout: 20.0)
+ }
+
+ func testFetchAll() {
+ update()
+ MockURLProtocol.removeAll()
+ var subscriptions = Set()
+ let expectation1 = XCTestExpectation(description: "Fetch")
+
+ DispatchQueue.main.async {
+ guard var installation = Installation.current else {
+ XCTFail("Should unwrap dates")
+ expectation1.fulfill()
+ return
+ }
+
+ installation.updatedAt = installation.updatedAt?.addingTimeInterval(+300)
+ installation.customKey = "newValue"
+ let installationOnServer = QueryResponse(results: [installation], count: 1)
+
+ let encoded: Data!
+ do {
+ encoded = try ParseCoding.jsonEncoder().encode(installationOnServer)
+ //Get dates in correct format from ParseDecoding strategy
+ let encoded1 = try ParseCoding.jsonEncoder().encode(installation)
+ installation = try installation.getDecoder().decode(Installation.self, from: encoded1)
+ } catch {
+ XCTFail("Should encode/decode. Error \(error)")
+ expectation1.fulfill()
+ return
+ }
+ MockURLProtocol.mockRequests { _ in
+ return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0)
+ }
+
+ let publisher = [installation].fetchAllPublisher()
+ .sink(receiveCompletion: { result in
+
+ if case let .failure(error) = result {
+ XCTFail(error.localizedDescription)
+ }
+ expectation1.fulfill()
+
+ }, receiveValue: { fetched in
+
+ fetched.forEach {
+ switch $0 {
+ case .success(let fetched):
+ XCTAssert(fetched.hasSameObjectId(as: installation))
+ guard let fetchedCreatedAt = fetched.createdAt,
+ let fetchedUpdatedAt = fetched.updatedAt else {
+ XCTFail("Should unwrap dates")
+ expectation1.fulfill()
+ return
+ }
+ guard let originalCreatedAt = installation.createdAt,
+ let originalUpdatedAt = installation.updatedAt,
+ let serverUpdatedAt = installation.updatedAt else {
+ XCTFail("Should unwrap dates")
+ expectation1.fulfill()
+ return
+ }
+ XCTAssertEqual(fetchedCreatedAt, originalCreatedAt)
+ XCTAssertEqual(fetchedUpdatedAt, originalUpdatedAt)
+ XCTAssertEqual(fetchedUpdatedAt, serverUpdatedAt)
+ XCTAssertEqual(Installation.current?.customKey, installation.customKey)
+
+ //Should be updated in memory
+ guard let updatedCurrentDate = Installation.current?.updatedAt else {
+ XCTFail("Should unwrap current date")
+ expectation1.fulfill()
+ return
+ }
+ XCTAssertEqual(updatedCurrentDate, serverUpdatedAt)
+
+ #if !os(Linux)
+ //Should be updated in Keychain
+ guard let keychainInstallation: CurrentInstallationContainer
+ = try? KeychainStore.shared.get(valueFor: ParseStorage.Keys.currentInstallation),
+ let keychainUpdatedCurrentDate = keychainInstallation.currentInstallation?.updatedAt else {
+ XCTFail("Should get object from Keychain")
+ expectation1.fulfill()
+ return
+ }
+ XCTAssertEqual(keychainUpdatedCurrentDate, serverUpdatedAt)
+ #endif
+ case .failure(let error):
+ XCTFail("Should have fetched: \(error.localizedDescription)")
+ }
+ }
+ })
+ publisher.store(in: &subscriptions)
+ }
+ wait(for: [expectation1], timeout: 20.0)
+ }
+
+ func testSaveAll() {
+ update()
+ MockURLProtocol.removeAll()
+ var subscriptions = Set()
+ let expectation1 = XCTestExpectation(description: "Save")
+
+ DispatchQueue.main.async {
+ guard var installation = Installation.current else {
+ XCTFail("Should unwrap dates")
+ expectation1.fulfill()
+ return
+ }
+
+ installation.updatedAt = installation.updatedAt?.addingTimeInterval(+300)
+ installation.customKey = "newValue"
+ let installationOnServer = [BatchResponseItem(success: installation, error: nil)]
+
+ let encoded: Data!
+ do {
+ encoded = try ParseCoding.jsonEncoder().encode(installationOnServer)
+ //Get dates in correct format from ParseDecoding strategy
+ let encoded1 = try ParseCoding.jsonEncoder().encode(installation)
+ installation = try installation.getDecoder().decode(Installation.self, from: encoded1)
+ } catch {
+ XCTFail("Should encode/decode. Error \(error)")
+ expectation1.fulfill()
+ return
+ }
+ MockURLProtocol.mockRequests { _ in
+ return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0)
+ }
+
+ let publisher = [installation].saveAllPublisher()
+ .sink(receiveCompletion: { result in
+
+ if case let .failure(error) = result {
+ XCTFail(error.localizedDescription)
+ }
+ expectation1.fulfill()
+
+ }, receiveValue: { saved in
+
+ saved.forEach {
+ switch $0 {
+ case .success(let saved):
+ XCTAssert(saved.hasSameObjectId(as: installation))
+ guard let savedCreatedAt = saved.createdAt,
+ let savedUpdatedAt = saved.updatedAt else {
+ XCTFail("Should unwrap dates")
+ expectation1.fulfill()
+ return
+ }
+ guard let originalCreatedAt = installation.createdAt,
+ let originalUpdatedAt = installation.updatedAt,
+ let serverUpdatedAt = installation.updatedAt else {
+ XCTFail("Should unwrap dates")
+ expectation1.fulfill()
+ return
+ }
+ XCTAssertEqual(savedCreatedAt, originalCreatedAt)
+ XCTAssertEqual(savedUpdatedAt, originalUpdatedAt)
+ XCTAssertEqual(savedUpdatedAt, serverUpdatedAt)
+ XCTAssertEqual(Installation.current?.customKey, installation.customKey)
+
+ //Should be updated in memory
+ guard let updatedCurrentDate = Installation.current?.updatedAt else {
+ XCTFail("Should unwrap current date")
+ expectation1.fulfill()
+ return
+ }
+ XCTAssertEqual(updatedCurrentDate, serverUpdatedAt)
+
+ #if !os(Linux)
+ //Should be updated in Keychain
+ guard let keychainInstallation: CurrentInstallationContainer
+ = try? KeychainStore.shared.get(valueFor: ParseStorage.Keys.currentInstallation),
+ let keychainUpdatedCurrentDate = keychainInstallation.currentInstallation?.updatedAt else {
+ XCTFail("Should get object from Keychain")
+ expectation1.fulfill()
+ return
+ }
+ XCTAssertEqual(keychainUpdatedCurrentDate, serverUpdatedAt)
+ #endif
+ case .failure(let error):
+ XCTFail("Should have fetched: \(error.localizedDescription)")
+ }
+ }
+ })
+ publisher.store(in: &subscriptions)
+ }
+ wait(for: [expectation1], timeout: 20.0)
+ }
+
+ func testDeleteAll() {
+ update()
+ MockURLProtocol.removeAll()
+ var subscriptions = Set()
+ let expectation1 = XCTestExpectation(description: "Save")
+
+ DispatchQueue.main.async {
+ guard let installation = Installation.current else {
+ XCTFail("Should unwrap dates")
+ expectation1.fulfill()
+ return
+ }
+
+ let installationOnServer = [BatchResponseItem(success: NoBody(), error: nil)]
+
+ let encoded: Data!
+ do {
+ encoded = try ParseCoding.jsonEncoder().encode(installationOnServer)
+ } catch {
+ XCTFail("Should encode/decode. Error \(error)")
+ expectation1.fulfill()
+ return
+ }
+ MockURLProtocol.mockRequests { _ in
+ return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0)
+ }
+
+ let publisher = [installation].deleteAllPublisher()
+ .sink(receiveCompletion: { result in
+
+ if case let .failure(error) = result {
+ XCTFail(error.localizedDescription)
+ }
+ expectation1.fulfill()
+
+ }, receiveValue: { deleted in
+ deleted.forEach {
+ if case let .failure(error) = $0 {
+ XCTFail("Should have deleted: \(error.localizedDescription)")
+ }
+ }
+ })
+ publisher.store(in: &subscriptions)
+ }
+ wait(for: [expectation1], timeout: 20.0)
+ }
+}
+
+#endif
diff --git a/Tests/ParseSwiftTests/ParseInstallationTests.swift b/Tests/ParseSwiftTests/ParseInstallationTests.swift
index 53380d9e2..dbac1044e 100644
--- a/Tests/ParseSwiftTests/ParseInstallationTests.swift
+++ b/Tests/ParseSwiftTests/ParseInstallationTests.swift
@@ -690,8 +690,10 @@ class ParseInstallationTests: XCTestCase { // swiftlint:disable:this type_body_l
return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0)
}
- installation.delete { error in
- XCTAssertNil(error)
+ installation.delete { result in
+ if case let .failure(error) = result {
+ XCTFail(error.localizedDescription)
+ }
expectation1.fulfill()
}
}
@@ -1093,8 +1095,7 @@ class ParseInstallationTests: XCTestCase { // swiftlint:disable:this type_body_l
return
}
- let error: ParseError? = nil
- let installationOnServer = [error]
+ let installationOnServer = [BatchResponseItem(success: NoBody(), error: nil)]
let encoded: Data!
do {
@@ -1111,7 +1112,7 @@ class ParseInstallationTests: XCTestCase { // swiftlint:disable:this type_body_l
do {
let deleted = try [installation].deleteAll()
deleted.forEach {
- if let error = $0 {
+ if case let .failure(error) = $0 {
XCTFail("Should have deleted: \(error.localizedDescription)")
}
}
@@ -1136,8 +1137,7 @@ class ParseInstallationTests: XCTestCase { // swiftlint:disable:this type_body_l
return
}
- let error: ParseError? = nil
- let installationOnServer = [error]
+ let installationOnServer = [BatchResponseItem(success: NoBody(), error: nil)]
let encoded: Data!
do {
@@ -1156,7 +1156,7 @@ class ParseInstallationTests: XCTestCase { // swiftlint:disable:this type_body_l
case .success(let deleted):
deleted.forEach {
- if let error = $0 {
+ if case let .failure(error) = $0 {
XCTFail("Should have deleted: \(error.localizedDescription)")
}
}
diff --git a/Tests/ParseSwiftTests/ParseLiveQueryTests.swift b/Tests/ParseSwiftTests/ParseLiveQueryTests.swift
index 2d38712a1..9de6683dd 100644
--- a/Tests/ParseSwiftTests/ParseLiveQueryTests.swift
+++ b/Tests/ParseSwiftTests/ParseLiveQueryTests.swift
@@ -10,7 +10,7 @@ import Foundation
import XCTest
@testable import ParseSwift
-@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
+@available(macOS 10.15, iOS 13.0, macCatalyst 13.0, watchOS 6.0, tvOS 13.0, *)
class ParseLiveQueryTests: XCTestCase {
struct GameScore: ParseObject {
//: Those are required for Object
@@ -1426,674 +1426,5 @@ class ParseLiveQueryTests: XCTestCase {
wait(for: [expectation1, expectation2], timeout: 20.0)
}
-
- func testFind() throws {
- let query = GameScore.query("score" > 9)
- guard let subscription = query.subscribe else {
- XCTFail("Should create subscription")
- return
- }
- XCTAssertEqual(subscription.query, query)
-
- subscription.error = ParseError(code: .objectNotFound, message: "Error")
- subscription.count = 5
- subscription.resultsCodable = AnyCodable()
-
- let expectation1 = XCTestExpectation(description: "Subscribe Handler")
-
- var scoreOnServer = GameScore(score: 10)
- scoreOnServer.objectId = "yarr"
- scoreOnServer.createdAt = Date()
- scoreOnServer.updatedAt = Date()
- scoreOnServer.ACL = nil
-
- let results = QueryResponse(results: [scoreOnServer], count: 1)
- MockURLProtocol.mockRequests { _ in
- do {
- let encoded = try ParseCoding.jsonEncoder().encode(results)
- return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0)
- } catch {
- return nil
- }
- }
-
- subscription.find(options: [], callbackQueue: .main)
-
- DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
-
- guard let score = subscription.results?.first else {
- XCTFail("Should unwrap subscribed.")
- expectation1.fulfill()
- return
- }
-
- XCTAssertNil(subscription.resultsCodable)
- XCTAssertNil(subscription.error)
- XCTAssertNil(subscription.count)
- XCTAssert(score.hasSameObjectId(as: scoreOnServer))
- expectation1.fulfill()
- }
-
- wait(for: [expectation1], timeout: 20.0)
- }
-
- func testFirst() throws {
- let query = GameScore.query("score" > 9)
- guard let subscription = query.subscribe else {
- XCTFail("Should create subscription")
- return
- }
- XCTAssertEqual(subscription.query, query)
-
- subscription.error = ParseError(code: .objectNotFound, message: "Error")
- subscription.count = 5
- subscription.resultsCodable = AnyCodable()
-
- let expectation1 = XCTestExpectation(description: "Subscribe Handler")
-
- var scoreOnServer = GameScore(score: 10)
- scoreOnServer.objectId = "yarr"
- scoreOnServer.createdAt = Date()
- scoreOnServer.updatedAt = Date()
- scoreOnServer.ACL = nil
-
- let results = QueryResponse(results: [scoreOnServer], count: 1)
- MockURLProtocol.mockRequests { _ in
- do {
- let encoded = try ParseCoding.jsonEncoder().encode(results)
- return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0)
- } catch {
- return nil
- }
- }
-
- subscription.first(options: [], callbackQueue: .main)
-
- DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
-
- guard let score = subscription.results?.first else {
- XCTFail("Should unwrap subscribed.")
- expectation1.fulfill()
- return
- }
-
- XCTAssertNil(subscription.resultsCodable)
- XCTAssertNil(subscription.error)
- XCTAssertNil(subscription.count)
- XCTAssert(score.hasSameObjectId(as: scoreOnServer))
- expectation1.fulfill()
- }
-
- wait(for: [expectation1], timeout: 20.0)
- }
-
- func testCount() throws {
- let query = GameScore.query("score" > 9)
- guard let subscription = query.subscribe else {
- XCTFail("Should create subscription")
- return
- }
- XCTAssertEqual(subscription.query, query)
-
- let expectation1 = XCTestExpectation(description: "Subscribe Handler")
-
- var scoreOnServer = GameScore(score: 10)
- scoreOnServer.objectId = "yarr"
- scoreOnServer.createdAt = Date()
- scoreOnServer.updatedAt = Date()
- scoreOnServer.ACL = nil
-
- subscription.error = ParseError(code: .objectNotFound, message: "Error")
- subscription.results = [scoreOnServer]
- subscription.resultsCodable = AnyCodable()
-
- let results = QueryResponse(results: [scoreOnServer], count: 1)
- MockURLProtocol.mockRequests { _ in
- do {
- let encoded = try ParseCoding.jsonEncoder().encode(results)
- return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0)
- } catch {
- return nil
- }
- }
-
- subscription.count(options: [], callbackQueue: .main)
-
- DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
-
- guard let count = subscription.count else {
- XCTFail("Should unwrap subscribed.")
- expectation1.fulfill()
- return
- }
-
- XCTAssertNil(subscription.resultsCodable)
- XCTAssertNil(subscription.error)
- XCTAssertNil(subscription.results)
- XCTAssertEqual(count, 1)
- expectation1.fulfill()
- }
-
- wait(for: [expectation1], timeout: 20.0)
- }
-
- func testAggregate() throws {
- let query = GameScore.query("score" > 9)
- guard let subscription = query.subscribe else {
- XCTFail("Should create subscription")
- return
- }
- XCTAssertEqual(subscription.query, query)
-
- subscription.error = ParseError(code: .objectNotFound, message: "Error")
- subscription.count = 5
- subscription.resultsCodable = AnyCodable()
-
- let expectation1 = XCTestExpectation(description: "Subscribe Handler")
-
- var scoreOnServer = GameScore(score: 10)
- scoreOnServer.objectId = "yarr"
- scoreOnServer.createdAt = Date()
- scoreOnServer.updatedAt = Date()
- scoreOnServer.ACL = nil
-
- let results = QueryResponse(results: [scoreOnServer], count: 1)
- MockURLProtocol.mockRequests { _ in
- do {
- let encoded = try ParseCoding.jsonEncoder().encode(results)
- return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0)
- } catch {
- return nil
- }
- }
-
- subscription.aggregate([["hello": "world"]], options: [], callbackQueue: .main)
-
- DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
-
- guard let score = subscription.results?.first else {
- XCTFail("Should unwrap subscribed.")
- expectation1.fulfill()
- return
- }
-
- XCTAssertNil(subscription.resultsCodable)
- XCTAssertNil(subscription.error)
- XCTAssertNil(subscription.count)
- XCTAssert(score.hasSameObjectId(as: scoreOnServer))
- expectation1.fulfill()
- }
-
- wait(for: [expectation1], timeout: 20.0)
- }
-
- func testFindExplain() throws {
- let query = GameScore.query("score" > 9)
- guard let subscription = query.subscribe else {
- XCTFail("Should create subscription")
- return
- }
- XCTAssertEqual(subscription.query, query)
-
- let expectation1 = XCTestExpectation(description: "Subscribe Handler")
-
- var scoreOnServer = GameScore(score: 10)
- scoreOnServer.objectId = "yarr"
- scoreOnServer.createdAt = Date()
- scoreOnServer.updatedAt = Date()
- scoreOnServer.ACL = nil
-
- subscription.error = ParseError(code: .objectNotFound, message: "Error")
- subscription.results = [scoreOnServer]
- subscription.count = 5
-
- let json = AnyResultsResponse(results: ["yolo": "yarr"])
-
- let encoded: Data!
- do {
- encoded = try JSONEncoder().encode(json)
- } catch {
- XCTFail("Should encode. Error \(error)")
- return
- }
- MockURLProtocol.mockRequests { _ in
- return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0)
- }
-
- subscription.find(explain: true)
-
- DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
-
- guard let response = subscription.resultsCodable?.value as? [String: String],
- let expected = json.results?.value as? [String: String] else {
- XCTFail("Should unwrap subscribed.")
- expectation1.fulfill()
- return
- }
-
- XCTAssertNil(subscription.count)
- XCTAssertNil(subscription.error)
- XCTAssertNil(subscription.results)
- XCTAssertEqual(response, expected)
- expectation1.fulfill()
- }
-
- wait(for: [expectation1], timeout: 20.0)
- }
-
- func testFirstExplain() throws {
- let query = GameScore.query("score" > 9)
- guard let subscription = query.subscribe else {
- XCTFail("Should create subscription")
- return
- }
- XCTAssertEqual(subscription.query, query)
-
- let expectation1 = XCTestExpectation(description: "Subscribe Handler")
-
- var scoreOnServer = GameScore(score: 10)
- scoreOnServer.objectId = "yarr"
- scoreOnServer.createdAt = Date()
- scoreOnServer.updatedAt = Date()
- scoreOnServer.ACL = nil
-
- subscription.error = ParseError(code: .objectNotFound, message: "Error")
- subscription.results = [scoreOnServer]
- subscription.count = 5
-
- let json = AnyResultsResponse(results: ["yolo": "yarr"])
-
- let encoded: Data!
- do {
- encoded = try JSONEncoder().encode(json)
- } catch {
- XCTFail("Should encode. Error \(error)")
- return
- }
- MockURLProtocol.mockRequests { _ in
- return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0)
- }
-
- subscription.first(explain: true)
-
- DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
-
- guard let response = subscription.resultsCodable?.value as? [String: String],
- let expected = json.results?.value as? [String: String] else {
- XCTFail("Should unwrap subscribed.")
- expectation1.fulfill()
- return
- }
-
- XCTAssertNil(subscription.count)
- XCTAssertNil(subscription.error)
- XCTAssertNil(subscription.results)
- XCTAssertEqual(response, expected)
- expectation1.fulfill()
- }
-
- wait(for: [expectation1], timeout: 20.0)
- }
-
- func testCountExplain() throws {
- let query = GameScore.query("score" > 9)
- guard let subscription = query.subscribe else {
- XCTFail("Should create subscription")
- return
- }
- XCTAssertEqual(subscription.query, query)
-
- let expectation1 = XCTestExpectation(description: "Subscribe Handler")
-
- var scoreOnServer = GameScore(score: 10)
- scoreOnServer.objectId = "yarr"
- scoreOnServer.createdAt = Date()
- scoreOnServer.updatedAt = Date()
- scoreOnServer.ACL = nil
-
- subscription.error = ParseError(code: .objectNotFound, message: "Error")
- subscription.results = [scoreOnServer]
- subscription.count = 5
-
- let json = AnyResultsResponse(results: ["yolo": "yarr"])
-
- let encoded: Data!
- do {
- encoded = try JSONEncoder().encode(json)
- } catch {
- XCTFail("Should encode. Error \(error)")
- return
- }
- MockURLProtocol.mockRequests { _ in
- return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0)
- }
-
- subscription.count(explain: true)
-
- DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
-
- guard let response = subscription.resultsCodable?.value as? [String: String],
- let expected = json.results?.value as? [String: String] else {
- XCTFail("Should unwrap subscribed.")
- expectation1.fulfill()
- return
- }
-
- XCTAssertNil(subscription.count)
- XCTAssertNil(subscription.error)
- XCTAssertNil(subscription.results)
- XCTAssertEqual(response, expected)
- expectation1.fulfill()
- }
-
- wait(for: [expectation1], timeout: 20.0)
- }
-
- func testFindError() throws {
- let query = GameScore.query("score" > 9)
- guard let subscription = query.subscribe else {
- XCTFail("Should create subscription")
- return
- }
- XCTAssertEqual(subscription.query, query)
-
- let expectation1 = XCTestExpectation(description: "Subscribe Handler")
-
- var scoreOnServer = GameScore(score: 10)
- scoreOnServer.objectId = "yarr"
- scoreOnServer.createdAt = Date()
- scoreOnServer.updatedAt = Date()
- scoreOnServer.ACL = nil
-
- subscription.count = 5
- subscription.results = [scoreOnServer]
- subscription.resultsCodable = AnyCodable()
-
- let serverError = ParseError(code: .invalidServerResponse, message: "Error")
- MockURLProtocol.mockRequests { _ in
- return MockURLResponse(error: serverError)
- }
-
- subscription.find()
-
- DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
-
- guard let error = subscription.error else {
- XCTFail("Should unwrap subscribed.")
- expectation1.fulfill()
- return
- }
- XCTAssertNil(subscription.resultsCodable)
- XCTAssertNil(subscription.count)
- XCTAssertNil(subscription.results)
- XCTAssertEqual(error.code, serverError.code)
- expectation1.fulfill()
- }
-
- wait(for: [expectation1], timeout: 20.0)
- }
-
- func testFirstError() throws {
- let query = GameScore.query("score" > 9)
- guard let subscription = query.subscribe else {
- XCTFail("Should create subscription")
- return
- }
- XCTAssertEqual(subscription.query, query)
-
- let expectation1 = XCTestExpectation(description: "Subscribe Handler")
-
- var scoreOnServer = GameScore(score: 10)
- scoreOnServer.objectId = "yarr"
- scoreOnServer.createdAt = Date()
- scoreOnServer.updatedAt = Date()
- scoreOnServer.ACL = nil
-
- subscription.count = 5
- subscription.results = [scoreOnServer]
- subscription.resultsCodable = AnyCodable()
-
- let serverError = ParseError(code: .invalidServerResponse, message: "Error")
- MockURLProtocol.mockRequests { _ in
- return MockURLResponse(error: serverError)
- }
-
- subscription.first()
-
- DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
-
- guard let error = subscription.error else {
- XCTFail("Should unwrap subscribed.")
- expectation1.fulfill()
- return
- }
- XCTAssertNil(subscription.resultsCodable)
- XCTAssertNil(subscription.count)
- XCTAssertNil(subscription.results)
- XCTAssertEqual(error.code, serverError.code)
- expectation1.fulfill()
- }
-
- wait(for: [expectation1], timeout: 20.0)
- }
-
- func testCountError() throws {
- let query = GameScore.query("score" > 9)
- guard let subscription = query.subscribe else {
- XCTFail("Should create subscription")
- return
- }
- XCTAssertEqual(subscription.query, query)
-
- let expectation1 = XCTestExpectation(description: "Subscribe Handler")
-
- var scoreOnServer = GameScore(score: 10)
- scoreOnServer.objectId = "yarr"
- scoreOnServer.createdAt = Date()
- scoreOnServer.updatedAt = Date()
- scoreOnServer.ACL = nil
-
- subscription.count = 5
- subscription.results = [scoreOnServer]
- subscription.resultsCodable = AnyCodable()
-
- let serverError = ParseError(code: .invalidServerResponse, message: "Error")
- MockURLProtocol.mockRequests { _ in
- return MockURLResponse(error: serverError)
- }
-
- subscription.count()
-
- DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
-
- guard let error = subscription.error else {
- XCTFail("Should unwrap subscribed.")
- expectation1.fulfill()
- return
- }
- XCTAssertNil(subscription.resultsCodable)
- XCTAssertNil(subscription.count)
- XCTAssertNil(subscription.results)
- XCTAssertEqual(error.code, serverError.code)
- expectation1.fulfill()
- }
-
- wait(for: [expectation1], timeout: 20.0)
- }
-
- func testAggregateError() throws {
- let query = GameScore.query("score" > 9)
- guard let subscription = query.subscribe else {
- XCTFail("Should create subscription")
- return
- }
- XCTAssertEqual(subscription.query, query)
-
- let expectation1 = XCTestExpectation(description: "Subscribe Handler")
-
- var scoreOnServer = GameScore(score: 10)
- scoreOnServer.objectId = "yarr"
- scoreOnServer.createdAt = Date()
- scoreOnServer.updatedAt = Date()
- scoreOnServer.ACL = nil
-
- subscription.count = 5
- subscription.results = [scoreOnServer]
- subscription.resultsCodable = AnyCodable()
-
- let serverError = ParseError(code: .invalidServerResponse, message: "Error")
- MockURLProtocol.mockRequests { _ in
- return MockURLResponse(error: serverError)
- }
-
- subscription.aggregate([["hello": "world"]])
-
- DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
-
- guard let error = subscription.error else {
- XCTFail("Should unwrap subscribed.")
- expectation1.fulfill()
- return
- }
- XCTAssertNil(subscription.resultsCodable)
- XCTAssertNil(subscription.count)
- XCTAssertNil(subscription.results)
- XCTAssertEqual(error.code, serverError.code)
- expectation1.fulfill()
- }
-
- wait(for: [expectation1], timeout: 20.0)
- }
-
- func testFindExplainError() throws {
- let query = GameScore.query("score" > 9)
- guard let subscription = query.subscribe else {
- XCTFail("Should create subscription")
- return
- }
- XCTAssertEqual(subscription.query, query)
-
- let expectation1 = XCTestExpectation(description: "Subscribe Handler")
-
- var scoreOnServer = GameScore(score: 10)
- scoreOnServer.objectId = "yarr"
- scoreOnServer.createdAt = Date()
- scoreOnServer.updatedAt = Date()
- scoreOnServer.ACL = nil
-
- subscription.count = 5
- subscription.results = [scoreOnServer]
- subscription.resultsCodable = AnyCodable()
-
- let serverError = ParseError(code: .invalidServerResponse, message: "Error")
- MockURLProtocol.mockRequests { _ in
- return MockURLResponse(error: serverError)
- }
-
- subscription.find(explain: true)
-
- DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
-
- guard let error = subscription.error else {
- XCTFail("Should unwrap subscribed.")
- expectation1.fulfill()
- return
- }
- XCTAssertNil(subscription.resultsCodable)
- XCTAssertNil(subscription.count)
- XCTAssertNil(subscription.results)
- XCTAssertEqual(error.code, serverError.code)
- expectation1.fulfill()
- }
-
- wait(for: [expectation1], timeout: 20.0)
- }
-
- func testFirstExplainError() throws {
- let query = GameScore.query("score" > 9)
- guard let subscription = query.subscribe else {
- XCTFail("Should create subscription")
- return
- }
- XCTAssertEqual(subscription.query, query)
-
- let expectation1 = XCTestExpectation(description: "Subscribe Handler")
-
- var scoreOnServer = GameScore(score: 10)
- scoreOnServer.objectId = "yarr"
- scoreOnServer.createdAt = Date()
- scoreOnServer.updatedAt = Date()
- scoreOnServer.ACL = nil
-
- subscription.count = 5
- subscription.results = [scoreOnServer]
- subscription.resultsCodable = AnyCodable()
-
- let serverError = ParseError(code: .invalidServerResponse, message: "Error")
- MockURLProtocol.mockRequests { _ in
- return MockURLResponse(error: serverError)
- }
-
- subscription.first(explain: true)
-
- DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
-
- guard let error = subscription.error else {
- XCTFail("Should unwrap subscribed.")
- expectation1.fulfill()
- return
- }
- XCTAssertNil(subscription.resultsCodable)
- XCTAssertNil(subscription.count)
- XCTAssertNil(subscription.results)
- XCTAssertEqual(error.code, serverError.code)
- expectation1.fulfill()
- }
-
- wait(for: [expectation1], timeout: 20.0)
- }
- func testCountExplainError() throws {
- let query = GameScore.query("score" > 9)
- guard let subscription = query.subscribe else {
- XCTFail("Should create subscription")
- return
- }
- XCTAssertEqual(subscription.query, query)
-
- let expectation1 = XCTestExpectation(description: "Subscribe Handler")
-
- var scoreOnServer = GameScore(score: 10)
- scoreOnServer.objectId = "yarr"
- scoreOnServer.createdAt = Date()
- scoreOnServer.updatedAt = Date()
- scoreOnServer.ACL = nil
-
- subscription.count = 5
- subscription.results = [scoreOnServer]
- subscription.resultsCodable = AnyCodable()
-
- let serverError = ParseError(code: .invalidServerResponse, message: "Error")
- MockURLProtocol.mockRequests { _ in
- return MockURLResponse(error: serverError)
- }
-
- subscription.count(explain: true)
-
- DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
-
- guard let error = subscription.error else {
- XCTFail("Should unwrap subscribed.")
- expectation1.fulfill()
- return
- }
- XCTAssertNil(subscription.resultsCodable)
- XCTAssertNil(subscription.count)
- XCTAssertNil(subscription.results)
- XCTAssertEqual(error.code, serverError.code)
- expectation1.fulfill()
- }
-
- wait(for: [expectation1], timeout: 20.0)
- }
}
#endif
diff --git a/Tests/ParseSwiftTests/ParseObjectBatchTests.swift b/Tests/ParseSwiftTests/ParseObjectBatchTests.swift
index 17fd0854f..15289aed8 100644
--- a/Tests/ParseSwiftTests/ParseObjectBatchTests.swift
+++ b/Tests/ParseSwiftTests/ParseObjectBatchTests.swift
@@ -1280,8 +1280,8 @@ class ParseObjectBatchTests: XCTestCase { // swiftlint:disable:this type_body_le
}
func testDeleteAll() {
- let error: ParseError? = nil
- let response = [error]
+ let response = [BatchResponseItem(success: NoBody(), error: nil),
+ BatchResponseItem(success: NoBody(), error: nil)]
let encoded: Data!
do {
@@ -1295,18 +1295,26 @@ class ParseObjectBatchTests: XCTestCase { // swiftlint:disable:this type_body_le
}
do {
- let fetched = try [GameScore(objectId: "yarr"), GameScore(objectId: "yolo")].deleteAll()
+ let deleted = try [GameScore(objectId: "yarr"), GameScore(objectId: "yolo")].deleteAll()
- XCTAssertEqual(fetched.count, 1)
- guard let firstObject = fetched.first else {
+ XCTAssertEqual(deleted.count, 2)
+ guard let firstObject = deleted.first else {
XCTFail("Should unwrap")
return
}
- if let error = firstObject {
+ if case let .failure(error) = firstObject {
XCTFail(error.localizedDescription)
}
+ guard let lastObject = deleted.last else {
+ XCTFail("Should unwrap")
+ return
+ }
+
+ if case let .failure(error) = lastObject {
+ XCTFail(error.localizedDescription)
+ }
} catch {
XCTFail(error.localizedDescription)
}
@@ -1314,7 +1322,8 @@ class ParseObjectBatchTests: XCTestCase { // swiftlint:disable:this type_body_le
func testDeleteAllError() {
let parseError = ParseError(code: .objectNotFound, message: "Object not found")
- let response = [parseError]
+ let response = [BatchResponseItem(success: nil, error: parseError),
+ BatchResponseItem(success: nil, error: parseError)]
let encoded: Data!
do {
encoded = try ParseCoding.jsonEncoder().encode(response)
@@ -1327,15 +1336,26 @@ class ParseObjectBatchTests: XCTestCase { // swiftlint:disable:this type_body_le
}
do {
- let fetched = try [GameScore(objectId: "yarr"), GameScore(objectId: "yolo")].deleteAll()
+ let deleted = try [GameScore(objectId: "yarr"), GameScore(objectId: "yolo")].deleteAll()
+
+ XCTAssertEqual(deleted.count, 2)
+ guard let firstObject = deleted.first else {
+ XCTFail("Should have thrown ParseError")
+ return
+ }
- XCTAssertEqual(fetched.count, 1)
- guard let firstObject = fetched.first else {
+ if case let .failure(error) = firstObject {
+ XCTAssertEqual(error.code, parseError.code)
+ } else {
+ XCTFail("Should have thrown ParseError")
+ }
+
+ guard let lastObject = deleted.last else {
XCTFail("Should have thrown ParseError")
return
}
- if let error = firstObject {
+ if case let .failure(error) = lastObject {
XCTAssertEqual(error.code, parseError.code)
} else {
XCTFail("Should have thrown ParseError")
@@ -1355,15 +1375,25 @@ class ParseObjectBatchTests: XCTestCase { // swiftlint:disable:this type_body_le
switch result {
- case .success(let fetched):
- XCTAssertEqual(fetched.count, 1)
- guard let firstObject = fetched.first else {
+ case .success(let deleted):
+ XCTAssertEqual(deleted.count, 2)
+ guard let firstObject = deleted.first else {
XCTFail("Should unwrap")
expectation1.fulfill()
return
}
- if let error = firstObject {
+ if case let .failure(error) = firstObject {
+ XCTFail(error.localizedDescription)
+ }
+
+ guard let lastObject = deleted.last else {
+ XCTFail("Should unwrap")
+ expectation1.fulfill()
+ return
+ }
+
+ if case let .failure(error) = lastObject {
XCTFail(error.localizedDescription)
}
@@ -1377,8 +1407,8 @@ class ParseObjectBatchTests: XCTestCase { // swiftlint:disable:this type_body_le
}
func testDeleteAllAsyncMainQueue() {
- let error: ParseError? = nil
- let response = [error]
+ let response = [BatchResponseItem(success: NoBody(), error: nil),
+ BatchResponseItem(success: NoBody(), error: nil)]
do {
let encoded = try ParseCoding.jsonEncoder().encode(response)
@@ -1402,15 +1432,27 @@ class ParseObjectBatchTests: XCTestCase { // swiftlint:disable:this type_body_le
switch result {
- case .success(let fetched):
- XCTAssertEqual(fetched.count, 1)
- guard let firstObject = fetched.first else {
+ case .success(let deleted):
+ XCTAssertEqual(deleted.count, 2)
+ guard let firstObject = deleted.first else {
+ XCTFail("Should have thrown ParseError")
+ expectation1.fulfill()
+ return
+ }
+
+ if case let .failure(error) = firstObject {
+ XCTAssertEqual(error.code, parseError.code)
+ } else {
+ XCTFail("Should have thrown ParseError")
+ }
+
+ guard let lastObject = deleted.last else {
XCTFail("Should have thrown ParseError")
expectation1.fulfill()
return
}
- if let error = firstObject {
+ if case let .failure(error) = lastObject {
XCTAssertEqual(error.code, parseError.code)
} else {
XCTFail("Should have thrown ParseError")
@@ -1428,7 +1470,8 @@ class ParseObjectBatchTests: XCTestCase { // swiftlint:disable:this type_body_le
func testDeleteAllAsyncMainQueueError() {
let parseError = ParseError(code: .objectNotFound, message: "Object not found")
- let response = [parseError]
+ let response = [BatchResponseItem(success: nil, error: parseError),
+ BatchResponseItem(success: nil, error: parseError)]
do {
let encoded = try ParseCoding.jsonEncoder().encode(response)
diff --git a/Tests/ParseSwiftTests/ParseObjectCombine.swift b/Tests/ParseSwiftTests/ParseObjectCombine.swift
new file mode 100644
index 000000000..2d70b0666
--- /dev/null
+++ b/Tests/ParseSwiftTests/ParseObjectCombine.swift
@@ -0,0 +1,467 @@
+//
+// ParseObjectCombine.swift
+// ParseSwift
+//
+// Created by Corey Baker on 1/30/21.
+// Copyright © 2021 Parse Community. All rights reserved.
+//
+
+#if canImport(Combine)
+
+import Foundation
+import XCTest
+import Combine
+@testable import ParseSwift
+
+@available(macOS 10.15, iOS 13.0, macCatalyst 13.0, watchOS 6.0, tvOS 13.0, *)
+class ParseObjectCombineTests: XCTestCase { // swiftlint:disable:this type_body_length
+
+ struct GameScore: ParseObject {
+ //: Those are required for Object
+ var objectId: String?
+ var createdAt: Date?
+ var updatedAt: Date?
+ var ACL: ParseACL?
+
+ //: Your own properties
+ var score: Int?
+ var player: String?
+
+ //custom initializers
+ init (objectId: String?) {
+ self.objectId = objectId
+ }
+ init(score: Int) {
+ self.score = score
+ self.player = "Jen"
+ }
+ init(score: Int, name: String) {
+ self.score = score
+ self.player = name
+ }
+ }
+
+ override func setUpWithError() throws {
+ super.setUp()
+ guard let url = URL(string: "http://localhost:1337/1") else {
+ XCTFail("Should create valid URL")
+ return
+ }
+ ParseSwift.initialize(applicationId: "applicationId",
+ clientKey: "clientKey",
+ masterKey: "masterKey",
+ serverURL: url,
+ testing: true)
+ }
+
+ override func tearDownWithError() throws {
+ super.tearDown()
+ MockURLProtocol.removeAll()
+ #if !os(Linux)
+ try KeychainStore.shared.deleteAll()
+ #endif
+ try ParseStorage.shared.deleteAll()
+ }
+
+ func testFetch() {
+ var score = GameScore(score: 10)
+ let objectId = "yarr"
+ score.objectId = objectId
+
+ var scoreOnServer = score
+ scoreOnServer.createdAt = Date()
+ scoreOnServer.updatedAt = scoreOnServer.createdAt
+ scoreOnServer.ACL = nil
+
+ var subscriptions = Set()
+ let expectation1 = XCTestExpectation(description: "Fetch")
+
+ let encoded: Data!
+ do {
+ encoded = try ParseCoding.jsonEncoder().encode(scoreOnServer)
+ //Get dates in correct format from ParseDecoding strategy
+ scoreOnServer = try scoreOnServer.getDecoder().decode(GameScore.self, from: encoded)
+ } catch {
+ XCTFail("Should encode/decode. Error \(error)")
+ return
+ }
+
+ MockURLProtocol.mockRequests { _ in
+ return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0)
+ }
+
+ let publisher = score.fetchPublisher()
+ .sink(receiveCompletion: { result in
+
+ if case let .failure(error) = result {
+ XCTFail(error.localizedDescription)
+ }
+ expectation1.fulfill()
+
+ }, receiveValue: { fetched in
+
+ XCTAssert(fetched.hasSameObjectId(as: scoreOnServer))
+ guard let fetchedCreatedAt = fetched.createdAt,
+ let fetchedUpdatedAt = fetched.updatedAt else {
+ XCTFail("Should unwrap dates")
+ return
+ }
+ guard let originalCreatedAt = scoreOnServer.createdAt,
+ let originalUpdatedAt = scoreOnServer.updatedAt else {
+ XCTFail("Should unwrap dates")
+ return
+ }
+ XCTAssertEqual(fetchedCreatedAt, originalCreatedAt)
+ XCTAssertEqual(fetchedUpdatedAt, originalUpdatedAt)
+ XCTAssertNil(fetched.ACL)
+ })
+ publisher.store(in: &subscriptions)
+
+ wait(for: [expectation1], timeout: 20.0)
+ }
+
+ func testSave() {
+ let score = GameScore(score: 10)
+
+ var scoreOnServer = score
+ scoreOnServer.objectId = "yarr"
+ scoreOnServer.createdAt = Date()
+ scoreOnServer.updatedAt = scoreOnServer.createdAt
+ scoreOnServer.ACL = nil
+
+ var subscriptions = Set()
+ let expectation1 = XCTestExpectation(description: "Save")
+
+ let encoded: Data!
+ do {
+ encoded = try ParseCoding.jsonEncoder().encode(scoreOnServer)
+ //Get dates in correct format from ParseDecoding strategy
+ scoreOnServer = try scoreOnServer.getDecoder().decode(GameScore.self, from: encoded)
+ } catch {
+ XCTFail("Should encode/decode. Error \(error)")
+ return
+ }
+
+ MockURLProtocol.mockRequests { _ in
+ return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0)
+ }
+
+ let publisher = score.savePublisher()
+ .sink(receiveCompletion: { result in
+
+ if case let .failure(error) = result {
+ XCTFail(error.localizedDescription)
+ }
+ expectation1.fulfill()
+
+ }, receiveValue: { saved in
+
+ XCTAssert(saved.hasSameObjectId(as: scoreOnServer))
+ guard let savedCreatedAt = saved.createdAt,
+ let savedUpdatedAt = saved.updatedAt else {
+ XCTFail("Should unwrap dates")
+ return
+ }
+ guard let originalCreatedAt = scoreOnServer.createdAt,
+ let originalUpdatedAt = scoreOnServer.updatedAt else {
+ XCTFail("Should unwrap dates")
+ return
+ }
+ XCTAssertEqual(savedCreatedAt, originalCreatedAt)
+ XCTAssertEqual(savedUpdatedAt, originalUpdatedAt)
+ XCTAssertNil(saved.ACL)
+ })
+ publisher.store(in: &subscriptions)
+
+ wait(for: [expectation1], timeout: 20.0)
+ }
+
+ func testDelete() {
+ var score = GameScore(score: 10)
+ score.objectId = "yarr"
+
+ let scoreOnServer = NoBody()
+
+ var subscriptions = Set()
+ let expectation1 = XCTestExpectation(description: "Save")
+
+ let encoded: Data!
+ do {
+ encoded = try ParseCoding.jsonEncoder().encode(scoreOnServer)
+ } catch {
+ XCTFail("Should encode/decode. Error \(error)")
+ return
+ }
+
+ MockURLProtocol.mockRequests { _ in
+ return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0)
+ }
+
+ let publisher = score.deletePublisher()
+ .sink(receiveCompletion: { result in
+
+ if case let .failure(error) = result {
+ XCTFail(error.localizedDescription)
+ }
+ expectation1.fulfill()
+
+ }, receiveValue: { _ in
+
+ })
+ publisher.store(in: &subscriptions)
+
+ wait(for: [expectation1], timeout: 20.0)
+ }
+
+ func testFetchAll() {
+ var subscriptions = Set()
+ let expectation1 = XCTestExpectation(description: "Fetch")
+
+ let score = GameScore(score: 10)
+ let score2 = GameScore(score: 20)
+
+ var scoreOnServer = score
+ scoreOnServer.objectId = "yarr"
+ scoreOnServer.createdAt = Date()
+ scoreOnServer.updatedAt = scoreOnServer.createdAt
+ scoreOnServer.ACL = nil
+
+ var scoreOnServer2 = score2
+ scoreOnServer2.objectId = "yolo"
+ scoreOnServer2.createdAt = Calendar.current.date(byAdding: .init(day: -2), to: Date())
+ scoreOnServer2.updatedAt = scoreOnServer2.createdAt
+ scoreOnServer2.ACL = nil
+
+ let response = QueryResponse(results: [scoreOnServer, scoreOnServer2], count: 2)
+ let encoded: Data!
+ do {
+ encoded = try ParseCoding.jsonEncoder().encode(response)
+ //Get dates in correct format from ParseDecoding strategy
+ let encoded1 = try ParseCoding.jsonEncoder().encode(scoreOnServer)
+ scoreOnServer = try scoreOnServer.getDecoder().decode(GameScore.self, from: encoded1)
+ let encoded2 = try ParseCoding.jsonEncoder().encode(scoreOnServer2)
+ scoreOnServer2 = try scoreOnServer.getDecoder().decode(GameScore.self, from: encoded2)
+
+ } catch {
+ XCTFail("Should have encoded/decoded. Error \(error)")
+ return
+ }
+ MockURLProtocol.mockRequests { _ in
+ return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0)
+ }
+
+ let publisher = [GameScore(objectId: "yarr"), GameScore(objectId: "yolo")].fetchAllPublisher()
+ .sink(receiveCompletion: { result in
+
+ if case let .failure(error) = result {
+ XCTFail(error.localizedDescription)
+ }
+ expectation1.fulfill()
+
+ }, receiveValue: { fetched in
+
+ XCTAssertEqual(fetched.count, 2)
+ guard let firstObject = try? fetched.first(where: {try $0.get().objectId == "yarr"}),
+ let secondObject = try? fetched.first(where: {try $0.get().objectId == "yolo"}) else {
+ XCTFail("Should unwrap")
+ return
+ }
+
+ switch firstObject {
+
+ case .success(let first):
+ XCTAssert(first.hasSameObjectId(as: scoreOnServer))
+ guard let fetchedCreatedAt = first.createdAt,
+ let fetchedUpdatedAt = first.updatedAt else {
+ XCTFail("Should unwrap dates")
+ return
+ }
+ guard let originalCreatedAt = scoreOnServer.createdAt,
+ let originalUpdatedAt = scoreOnServer.updatedAt else {
+ XCTFail("Should unwrap dates")
+ return
+ }
+ XCTAssertEqual(fetchedCreatedAt, originalCreatedAt)
+ XCTAssertEqual(fetchedUpdatedAt, originalUpdatedAt)
+ XCTAssertNil(first.ACL)
+ XCTAssertEqual(first.score, scoreOnServer.score)
+ case .failure(let error):
+ XCTFail(error.localizedDescription)
+ }
+
+ switch secondObject {
+
+ case .success(let second):
+ XCTAssert(second.hasSameObjectId(as: scoreOnServer2))
+ guard let savedCreatedAt = second.createdAt,
+ let savedUpdatedAt = second.updatedAt else {
+ XCTFail("Should unwrap dates")
+ return
+ }
+ guard let originalCreatedAt = scoreOnServer2.createdAt,
+ let originalUpdatedAt = scoreOnServer2.updatedAt else {
+ XCTFail("Should unwrap dates")
+ return
+ }
+ XCTAssertEqual(savedCreatedAt, originalCreatedAt)
+ XCTAssertEqual(savedUpdatedAt, originalUpdatedAt)
+ XCTAssertNil(second.ACL)
+ XCTAssertEqual(second.score, scoreOnServer2.score)
+ case .failure(let error):
+ XCTFail(error.localizedDescription)
+ }
+ })
+ publisher.store(in: &subscriptions)
+
+ wait(for: [expectation1], timeout: 20.0)
+ }
+
+ func testSaveAll() {
+ var subscriptions = Set()
+ let expectation1 = XCTestExpectation(description: "Save")
+
+ let score = GameScore(score: 10)
+ let score2 = GameScore(score: 20)
+
+ var scoreOnServer = score
+ scoreOnServer.objectId = "yarr"
+ scoreOnServer.createdAt = Date()
+ scoreOnServer.updatedAt = scoreOnServer.createdAt
+ scoreOnServer.ACL = nil
+
+ var scoreOnServer2 = score2
+ scoreOnServer2.objectId = "yolo"
+ scoreOnServer2.createdAt = Calendar.current.date(byAdding: .init(day: -1), to: Date())
+ scoreOnServer2.updatedAt = scoreOnServer2.createdAt
+ scoreOnServer2.ACL = nil
+
+ let response = [BatchResponseItem(success: scoreOnServer, error: nil),
+ BatchResponseItem(success: scoreOnServer2, error: nil)]
+ let encoded: Data!
+ do {
+ encoded = try ParseCoding.jsonEncoder().encode(response)
+ //Get dates in correct format from ParseDecoding strategy
+ let encoded1 = try ParseCoding.jsonEncoder().encode(scoreOnServer)
+ scoreOnServer = try scoreOnServer.getDecoder().decode(GameScore.self, from: encoded1)
+ let encoded2 = try ParseCoding.jsonEncoder().encode(scoreOnServer2)
+ scoreOnServer2 = try scoreOnServer.getDecoder().decode(GameScore.self, from: encoded2)
+
+ } catch {
+ XCTFail("Should have encoded/decoded. Error \(error)")
+ return
+ }
+ MockURLProtocol.mockRequests { _ in
+ return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0)
+ }
+
+ let publisher = [score, score2].saveAllPublisher()
+ .sink(receiveCompletion: { result in
+
+ if case let .failure(error) = result {
+ XCTFail(error.localizedDescription)
+ }
+ expectation1.fulfill()
+
+ }, receiveValue: { saved in
+
+ XCTAssertEqual(saved.count, 2)
+ switch saved[0] {
+
+ case .success(let first):
+ XCTAssert(first.hasSameObjectId(as: scoreOnServer))
+ guard let savedCreatedAt = first.createdAt,
+ let savedUpdatedAt = first.updatedAt else {
+ XCTFail("Should unwrap dates")
+ return
+ }
+ guard let originalCreatedAt = scoreOnServer.createdAt,
+ let originalUpdatedAt = scoreOnServer.updatedAt else {
+ XCTFail("Should unwrap dates")
+ return
+ }
+ XCTAssertEqual(savedCreatedAt, originalCreatedAt)
+ XCTAssertEqual(savedUpdatedAt, originalUpdatedAt)
+ XCTAssertNil(first.ACL)
+ case .failure(let error):
+ XCTFail(error.localizedDescription)
+ }
+
+ switch saved[1] {
+
+ case .success(let second):
+ XCTAssert(second.hasSameObjectId(as: scoreOnServer2))
+ guard let savedCreatedAt = second.createdAt,
+ let savedUpdatedAt = second.updatedAt else {
+ XCTFail("Should unwrap dates")
+ return
+ }
+ guard let originalCreatedAt = scoreOnServer2.createdAt,
+ let originalUpdatedAt = scoreOnServer2.updatedAt else {
+ XCTFail("Should unwrap dates")
+ return
+ }
+ XCTAssertEqual(savedCreatedAt, originalCreatedAt)
+ XCTAssertEqual(savedUpdatedAt, originalUpdatedAt)
+ XCTAssertNil(second.ACL)
+ case .failure(let error):
+ XCTFail(error.localizedDescription)
+ }
+ })
+ publisher.store(in: &subscriptions)
+
+ wait(for: [expectation1], timeout: 20.0)
+ }
+
+ func testDeleteAll() {
+ var subscriptions = Set()
+ let expectation1 = XCTestExpectation(description: "Save")
+
+ let response = [BatchResponseItem(success: NoBody(), error: nil),
+ BatchResponseItem(success: NoBody(), error: nil)]
+
+ let encoded: Data!
+ do {
+ encoded = try ParseCoding.jsonEncoder().encode(response)
+ } catch {
+ XCTFail("Should have encoded/decoded. Error \(error)")
+ return
+ }
+ MockURLProtocol.mockRequests { _ in
+ return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0)
+ }
+
+ let publisher = [GameScore(objectId: "yarr"), GameScore(objectId: "yolo")].deleteAllPublisher()
+ .sink(receiveCompletion: { result in
+
+ if case let .failure(error) = result {
+ XCTFail(error.localizedDescription)
+ }
+ expectation1.fulfill()
+
+ }, receiveValue: { deleted in
+ XCTAssertEqual(deleted.count, 2)
+ guard let firstObject = deleted.first else {
+ XCTFail("Should unwrap")
+ return
+ }
+
+ if case let .failure(error) = firstObject {
+ XCTFail(error.localizedDescription)
+ }
+
+ guard let lastObject = deleted.last else {
+ XCTFail("Should unwrap")
+ return
+ }
+
+ if case let .failure(error) = lastObject {
+ XCTFail(error.localizedDescription)
+ }
+ })
+ publisher.store(in: &subscriptions)
+
+ wait(for: [expectation1], timeout: 20.0)
+ }
+}
+
+#endif
diff --git a/Tests/ParseSwiftTests/ParseObjectTests.swift b/Tests/ParseSwiftTests/ParseObjectTests.swift
index 5062e7e77..5665a9e21 100644
--- a/Tests/ParseSwiftTests/ParseObjectTests.swift
+++ b/Tests/ParseSwiftTests/ParseObjectTests.swift
@@ -247,7 +247,7 @@ class ParseObjectTests: XCTestCase { // swiftlint:disable:this type_body_length
var scoreOnServer = score
scoreOnServer.createdAt = Date()
- scoreOnServer.updatedAt = Date()
+ scoreOnServer.updatedAt = scoreOnServer.createdAt
scoreOnServer.ACL = nil
let encoded: Data!
do {
@@ -947,24 +947,20 @@ class ParseObjectTests: XCTestCase { // swiftlint:disable:this type_body_length
func deleteAsync(score: GameScore, scoreOnServer: GameScore, callbackQueue: DispatchQueue) {
let expectation1 = XCTestExpectation(description: "Delete object1")
- score.delete(options: [], callbackQueue: callbackQueue) { error in
+ score.delete(options: [], callbackQueue: callbackQueue) { result in
- guard let error = error else {
- expectation1.fulfill()
- return
+ if case let .failure(error) = result {
+ XCTFail(error.localizedDescription)
}
- XCTFail(error.localizedDescription)
expectation1.fulfill()
}
let expectation2 = XCTestExpectation(description: "Delete object2")
- score.delete(options: [.useMasterKey], callbackQueue: callbackQueue) { error in
+ score.delete(options: [.useMasterKey], callbackQueue: callbackQueue) { result in
- guard let error = error else {
- expectation2.fulfill()
- return
+ if case let .failure(error) = result {
+ XCTFail(error.localizedDescription)
}
- XCTFail(error.localizedDescription)
expectation2.fulfill()
}
wait(for: [expectation1, expectation2], timeout: 20.0)
@@ -1027,26 +1023,24 @@ class ParseObjectTests: XCTestCase { // swiftlint:disable:this type_body_length
func deleteAsyncError(score: GameScore, parseError: ParseError, callbackQueue: DispatchQueue) {
let expectation1 = XCTestExpectation(description: "Delete object1")
- score.delete(options: [], callbackQueue: callbackQueue) { error in
+ score.delete(options: [], callbackQueue: callbackQueue) { result in
- guard let error = error else {
+ if case let .failure(error) = result {
+ XCTAssertEqual(error.code, parseError.code)
+ } else {
XCTFail("Should have thrown ParseError")
- expectation1.fulfill()
- return
}
- XCTAssertEqual(error.code, parseError.code)
expectation1.fulfill()
}
let expectation2 = XCTestExpectation(description: "Delete object2")
- score.delete(options: [.useMasterKey], callbackQueue: callbackQueue) { error in
+ score.delete(options: [.useMasterKey], callbackQueue: callbackQueue) { result in
- guard let error = error else {
+ if case let .failure(error) = result {
+ XCTAssertEqual(error.code, parseError.code)
+ } else {
XCTFail("Should have thrown ParseError")
- expectation2.fulfill()
- return
}
- XCTAssertEqual(error.code, parseError.code)
expectation2.fulfill()
}
wait(for: [expectation1, expectation2], timeout: 20.0)
diff --git a/Tests/ParseSwiftTests/ParseOperationCombineTests.swift b/Tests/ParseSwiftTests/ParseOperationCombineTests.swift
new file mode 100644
index 000000000..8067b6437
--- /dev/null
+++ b/Tests/ParseSwiftTests/ParseOperationCombineTests.swift
@@ -0,0 +1,122 @@
+//
+// ParseOperationCombineTests.swift
+// ParseSwift
+//
+// Created by Corey Baker on 1/30/21.
+// Copyright © 2021 Parse Community. All rights reserved.
+//
+
+#if canImport(Combine)
+
+import Foundation
+import XCTest
+import Combine
+@testable import ParseSwift
+
+@available(macOS 10.15, iOS 13.0, macCatalyst 13.0, watchOS 6.0, tvOS 13.0, *)
+class ParseOperationCombineTests: XCTestCase { // swiftlint:disable:this type_body_length
+
+ struct GameScore: ParseObject {
+ //: Those are required for Object
+ var objectId: String?
+ var createdAt: Date?
+ var updatedAt: Date?
+ var ACL: ParseACL?
+
+ //: Your own properties
+ var score: Int?
+ var player: String?
+
+ //custom initializers
+ init (objectId: String?) {
+ self.objectId = objectId
+ }
+ init(score: Int) {
+ self.score = score
+ self.player = "Jen"
+ }
+ init(score: Int, name: String) {
+ self.score = score
+ self.player = name
+ }
+ }
+
+ override func setUpWithError() throws {
+ super.setUp()
+ guard let url = URL(string: "http://localhost:1337/1") else {
+ XCTFail("Should create valid URL")
+ return
+ }
+ ParseSwift.initialize(applicationId: "applicationId",
+ clientKey: "clientKey",
+ masterKey: "masterKey",
+ serverURL: url,
+ testing: true)
+ }
+
+ override func tearDownWithError() throws {
+ super.tearDown()
+ MockURLProtocol.removeAll()
+ #if !os(Linux)
+ try KeychainStore.shared.deleteAll()
+ #endif
+ try ParseStorage.shared.deleteAll()
+ }
+
+ func testSave() {
+ var subscriptions = Set()
+ let expectation1 = XCTestExpectation(description: "Save")
+
+ var score = GameScore(score: 10)
+ score.objectId = "yarr"
+ let operations = score.operation
+ .increment("score", by: 1)
+
+ var scoreOnServer = score
+ scoreOnServer.score = 11
+ scoreOnServer.updatedAt = Date()
+ scoreOnServer.ACL = nil
+
+ let encoded: Data!
+ do {
+ encoded = try ParseCoding.jsonEncoder().encode(scoreOnServer)
+ //Get dates in correct format from ParseDecoding strategy
+ scoreOnServer = try scoreOnServer.getDecoder().decode(GameScore.self, from: encoded)
+ } catch {
+ XCTFail("Should encode/decode. Error \(error)")
+ return
+ }
+
+ MockURLProtocol.mockRequests { _ in
+ return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0)
+ }
+
+ let publisher = operations.savePublisher()
+ .sink(receiveCompletion: { result in
+
+ if case let .failure(error) = result {
+ XCTFail(error.localizedDescription)
+ }
+ expectation1.fulfill()
+
+ }, receiveValue: { saved in
+
+ XCTAssert(saved.hasSameObjectId(as: scoreOnServer))
+ guard let savedUpdatedAt = saved.updatedAt else {
+ XCTFail("Should unwrap dates")
+ return
+ }
+ guard let originalUpdatedAt = scoreOnServer.updatedAt else {
+ XCTFail("Should unwrap dates")
+ return
+ }
+ XCTAssertEqual(savedUpdatedAt, originalUpdatedAt)
+ XCTAssertNil(saved.ACL)
+ })
+ publisher.store(in: &subscriptions)
+
+ wait(for: [expectation1], timeout: 20.0)
+ }
+}
+
+#endif
diff --git a/Tests/ParseSwiftTests/ParseOperationTests.swift b/Tests/ParseSwiftTests/ParseOperationTests.swift
index b8ae8abde..f784d9305 100644
--- a/Tests/ParseSwiftTests/ParseOperationTests.swift
+++ b/Tests/ParseSwiftTests/ParseOperationTests.swift
@@ -103,8 +103,7 @@ class ParseOperationTests: XCTestCase {
var scoreOnServer = score
scoreOnServer.score = 11
- scoreOnServer.createdAt = Date()
- scoreOnServer.updatedAt = scoreOnServer.createdAt
+ scoreOnServer.updatedAt = Date()
let encoded: Data!
do {
diff --git a/Tests/ParseSwiftTests/ParseQueryCombineTests.swift b/Tests/ParseSwiftTests/ParseQueryCombineTests.swift
new file mode 100644
index 000000000..f176fc76a
--- /dev/null
+++ b/Tests/ParseSwiftTests/ParseQueryCombineTests.swift
@@ -0,0 +1,360 @@
+//
+// ParseQueryCombineTests.swift
+// ParseSwift
+//
+// Created by Corey Baker on 1/30/21.
+// Copyright © 2021 Parse Community. All rights reserved.
+//
+
+#if canImport(Combine)
+
+import Foundation
+import XCTest
+import Combine
+@testable import ParseSwift
+
+@available(macOS 10.15, iOS 13.0, macCatalyst 13.0, watchOS 6.0, tvOS 13.0, *)
+class ParseQueryCombineTests: XCTestCase { // swiftlint:disable:this type_body_length
+
+ struct GameScore: ParseObject {
+ //: Those are required for Object
+ var objectId: String?
+ var createdAt: Date?
+ var updatedAt: Date?
+ var ACL: ParseACL?
+
+ //: Your own properties
+ var score: Int?
+ var player: String?
+
+ //custom initializers
+ init (objectId: String?) {
+ self.objectId = objectId
+ }
+ init(score: Int) {
+ self.score = score
+ self.player = "Jen"
+ }
+ init(score: Int, name: String) {
+ self.score = score
+ self.player = name
+ }
+ }
+
+ override func setUpWithError() throws {
+ super.setUp()
+ guard let url = URL(string: "http://localhost:1337/1") else {
+ XCTFail("Should create valid URL")
+ return
+ }
+ ParseSwift.initialize(applicationId: "applicationId",
+ clientKey: "clientKey",
+ masterKey: "masterKey",
+ serverURL: url,
+ testing: true)
+ }
+
+ override func tearDownWithError() throws {
+ super.tearDown()
+ MockURLProtocol.removeAll()
+ #if !os(Linux)
+ try KeychainStore.shared.deleteAll()
+ #endif
+ try ParseStorage.shared.deleteAll()
+ }
+
+ func testFind() {
+ var subscriptions = Set