From f0f4809cfc5919215343e120dae9c769d3e5b05b Mon Sep 17 00:00:00 2001 From: Corey MacBook Pro Date: Tue, 29 Dec 2020 16:19:40 -0500 Subject: [PATCH 01/44] Add cloud function/job calls and user password reset. - Also adds more command tests for User and cleans up user docs --- ParseSwift.xcodeproj/project.pbxproj | 26 ++ Sources/ParseSwift/API/API.swift | 9 + Sources/ParseSwift/Cloud/ParseCloud.swift | 104 ++++++ Sources/ParseSwift/Coding/ParseCoding.swift | 4 +- Sources/ParseSwift/Coding/ParseEncoder.swift | 13 +- .../Object Protocols/ParseUser.swift | 123 +++++-- Tests/ParseSwiftTests/ParseCloudTests.swift | 305 ++++++++++++++++++ Tests/ParseSwiftTests/ParseUserTests.swift | 151 +++++++-- 8 files changed, 670 insertions(+), 65 deletions(-) create mode 100644 Sources/ParseSwift/Cloud/ParseCloud.swift create mode 100644 Tests/ParseSwiftTests/ParseCloudTests.swift diff --git a/ParseSwift.xcodeproj/project.pbxproj b/ParseSwift.xcodeproj/project.pbxproj index b8d7ced8c..93feb6d65 100644 --- a/ParseSwift.xcodeproj/project.pbxproj +++ b/ParseSwift.xcodeproj/project.pbxproj @@ -93,6 +93,13 @@ 912C9BDC24D3011F009947C3 /* ParseSwift_tvOS.h in Headers */ = {isa = PBXBuildFile; fileRef = 912C9BDA24D3011F009947C3 /* ParseSwift_tvOS.h */; settings = {ATTRIBUTES = (Public, ); }; }; 912C9BE024D302B0009947C3 /* Parse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A82B7EE1F254B820063D731 /* Parse.swift */; }; 912C9BFD24D302B2009947C3 /* Parse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A82B7EE1F254B820063D731 /* Parse.swift */; }; + 916786E2259B7DDA00BB5B4E /* ParseCloud.swift in Sources */ = {isa = PBXBuildFile; fileRef = 916786E1259B7DDA00BB5B4E /* ParseCloud.swift */; }; + 916786E3259B7DDA00BB5B4E /* ParseCloud.swift in Sources */ = {isa = PBXBuildFile; fileRef = 916786E1259B7DDA00BB5B4E /* ParseCloud.swift */; }; + 916786E4259B7DDA00BB5B4E /* ParseCloud.swift in Sources */ = {isa = PBXBuildFile; fileRef = 916786E1259B7DDA00BB5B4E /* ParseCloud.swift */; }; + 916786E5259B7DDA00BB5B4E /* ParseCloud.swift in Sources */ = {isa = PBXBuildFile; fileRef = 916786E1259B7DDA00BB5B4E /* ParseCloud.swift */; }; + 91678706259BC5D400BB5B4E /* ParseCloudTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 916786EF259BC59600BB5B4E /* ParseCloudTests.swift */; }; + 91678710259BC5D600BB5B4E /* ParseCloudTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 916786EF259BC59600BB5B4E /* ParseCloudTests.swift */; }; + 9167871A259BC5D600BB5B4E /* ParseCloudTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 916786EF259BC59600BB5B4E /* ParseCloudTests.swift */; }; 9194657824F16E330070296B /* ACLTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9194657724F16E330070296B /* ACLTests.swift */; }; F971F4F624DE381A006CB79B /* ParseEncoderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F971F4F524DE381A006CB79B /* ParseEncoderTests.swift */; }; F97B45CE24D9C6F200F4A88B /* ParseCoding.swift in Sources */ = {isa = PBXBuildFile; fileRef = F97B45B424D9C6F200F4A88B /* ParseCoding.swift */; }; @@ -330,6 +337,8 @@ 912C9BDA24D3011F009947C3 /* ParseSwift_tvOS.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ParseSwift_tvOS.h; sourceTree = ""; }; 912C9BDB24D3011F009947C3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 9158916A256A07DD0024BE9A /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; + 916786E1259B7DDA00BB5B4E /* ParseCloud.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParseCloud.swift; sourceTree = ""; }; + 916786EF259BC59600BB5B4E /* ParseCloudTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParseCloudTests.swift; sourceTree = ""; }; 9194657724F16E330070296B /* ACLTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ACLTests.swift; sourceTree = ""; }; F971F4F524DE381A006CB79B /* ParseEncoderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParseEncoderTests.swift; sourceTree = ""; }; F97B45B424D9C6F200F4A88B /* ParseCoding.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ParseCoding.swift; sourceTree = ""; }; @@ -491,6 +500,7 @@ 70CE1D882545BF730018D572 /* ParsePointerTests.swift */, 70C7DC1F24D20F180050419B /* ParseQueryTests.swift */, 70C7DC1D24D20E530050419B /* ParseUserTests.swift */, + 916786EF259BC59600BB5B4E /* ParseCloudTests.swift */, 7FFF552A2217E729007C3B4E /* AnyCodableTests */, 911DB12A24C3F7260027F3C7 /* NetworkMocking */, ); @@ -541,6 +551,7 @@ F97B463F24D9C78B00F4A88B /* Mutation Operations */, F97B45C324D9C6F200F4A88B /* Object Protocols */, F97B45BA24D9C6F200F4A88B /* Parse Types */, + 916786E0259B7D0100BB5B4E /* Cloud */, F97B45CB24D9C6F200F4A88B /* Storage */, 4A82B7EE1F254B820063D731 /* Parse.swift */, 70110D51250680140091CC1D /* ParseConstants.swift */, @@ -654,6 +665,14 @@ path = "ParseSwift-tvOS"; sourceTree = ""; }; + 916786E0259B7D0100BB5B4E /* Cloud */ = { + isa = PBXGroup; + children = ( + 916786E1259B7DDA00BB5B4E /* ParseCloud.swift */, + ); + path = Cloud; + sourceTree = ""; + }; F97B45B324D9C6F200F4A88B /* Coding */ = { isa = PBXGroup; children = ( @@ -1125,6 +1144,7 @@ buildActionMask = 2147483647; files = ( F97B463724D9C74400F4A88B /* Responses.swift in Sources */, + 916786E2259B7DDA00BB5B4E /* ParseCloud.swift in Sources */, F97B461624D9C6F200F4A88B /* Queryable.swift in Sources */, F97B45DA24D9C6F200F4A88B /* Extensions.swift in Sources */, F97B465F24D9C7B500F4A88B /* KeychainStore.swift in Sources */, @@ -1178,6 +1198,7 @@ 911DB12C24C3F7720027F3C7 /* MockURLResponse.swift in Sources */, 70110D5C2506ED0E0091CC1D /* ParseInstallationTests.swift in Sources */, 70BC0B33251903D1001556DB /* ParseGeoPointTests.swift in Sources */, + 91678706259BC5D400BB5B4E /* ParseCloudTests.swift in Sources */, 7FFF552E2217E72A007C3B4E /* AnyEncodableTests.swift in Sources */, 7FFF55302217E72A007C3B4E /* AnyDecodableTests.swift in Sources */, 70C7DC2224D20F190050419B /* ParseObjectBatchTests.swift in Sources */, @@ -1196,6 +1217,7 @@ buildActionMask = 2147483647; files = ( F97B463824D9C74400F4A88B /* Responses.swift in Sources */, + 916786E3259B7DDA00BB5B4E /* ParseCloud.swift in Sources */, F97B461724D9C6F200F4A88B /* Queryable.swift in Sources */, F97B45DB24D9C6F200F4A88B /* Extensions.swift in Sources */, F97B466024D9C7B500F4A88B /* KeychainStore.swift in Sources */, @@ -1258,6 +1280,7 @@ 709B984D2556ECAA00507778 /* AnyDecodableTests.swift in Sources */, 709B98572556ECAA00507778 /* ACLTests.swift in Sources */, 709B984F2556ECAA00507778 /* AnyCodableTests.swift in Sources */, + 9167871A259BC5D600BB5B4E /* ParseCloudTests.swift in Sources */, 709B98592556ECAA00507778 /* MockURLResponse.swift in Sources */, 709B98522556ECAA00507778 /* ParseUserTests.swift in Sources */, 709B984E2556ECAA00507778 /* ParseGeoPointTests.swift in Sources */, @@ -1281,6 +1304,7 @@ 70F2E2C2254F283000B2EA5C /* APICommandTests.swift in Sources */, 70F2E2BC254F283000B2EA5C /* ParseObjectTests.swift in Sources */, 70F2E2BD254F283000B2EA5C /* AnyDecodableTests.swift in Sources */, + 91678710259BC5D600BB5B4E /* ParseCloudTests.swift in Sources */, 70F2E2C1254F283000B2EA5C /* AnyCodableTests.swift in Sources */, 70F2E2B3254F283000B2EA5C /* ParseUserTests.swift in Sources */, 70F2E2C0254F283000B2EA5C /* MockURLResponse.swift in Sources */, @@ -1299,6 +1323,7 @@ buildActionMask = 2147483647; files = ( F97B45D524D9C6F200F4A88B /* AnyDecodable.swift in Sources */, + 916786E5259B7DDA00BB5B4E /* ParseCloud.swift in Sources */, F97B45E924D9C6F200F4A88B /* Query.swift in Sources */, F97B463624D9C74400F4A88B /* URLSession+extensions.swift in Sources */, F97B460524D9C6F200F4A88B /* NoBody.swift in Sources */, @@ -1347,6 +1372,7 @@ buildActionMask = 2147483647; files = ( F97B45D424D9C6F200F4A88B /* AnyDecodable.swift in Sources */, + 916786E4259B7DDA00BB5B4E /* ParseCloud.swift in Sources */, F97B45E824D9C6F200F4A88B /* Query.swift in Sources */, F97B463524D9C74400F4A88B /* URLSession+extensions.swift in Sources */, F97B460424D9C6F200F4A88B /* NoBody.swift in Sources */, diff --git a/Sources/ParseSwift/API/API.swift b/Sources/ParseSwift/API/API.swift index ca3329e4f..38e8195f6 100644 --- a/Sources/ParseSwift/API/API.swift +++ b/Sources/ParseSwift/API/API.swift @@ -21,6 +21,9 @@ public struct API { case login case signup case logout + case passwordReset + case functions(name: String) + case jobs(name: String) case any(String) var urlComponent: String { @@ -37,6 +40,12 @@ public struct API { return "/users" case .logout: return "/users/logout" + case .passwordReset: + return "/requestPasswordReset" + case .functions(name: let name): + return "/functions/\(name)" + case .jobs(name: let name): + return "/jobs/\(name)" case .any(let path): return path } diff --git a/Sources/ParseSwift/Cloud/ParseCloud.swift b/Sources/ParseSwift/Cloud/ParseCloud.swift new file mode 100644 index 000000000..02ed7a342 --- /dev/null +++ b/Sources/ParseSwift/Cloud/ParseCloud.swift @@ -0,0 +1,104 @@ +// +// ParseCloud.swift +// ParseSwift +// +// Created by Corey Baker on 12/29/20. +// Copyright © 2020 Parse Community. All rights reserved. +// + +import Foundation + +/** + Objects that conform to the `ParseCloud` protocol are able to call Parse Cloud Functions and Jobs. + An object should be should be instantiated for each function and job type. When conforming to + `ParseCloud`, any properties added will be passed as parameters to your Cloud Function or Job. +*/ +public protocol ParseCloud: Encodable, CustomDebugStringConvertible { + /** + The name of the function or job. + */ + var functionJobName: String { get set } + +} + +// MARK: Functions +extension ParseCloud { + public typealias AnyResultType = [String: AnyCodable] + + /** + *Synchronously* Calls a Cloud Code function and returns a result of it's execution. + * - parameter options: A set of options used to reset the password. Defaults to an empty set. + */ + public func callFunction(options: API.Options = []) throws -> AnyResultType { + try callFunctionCommand().execute(options: options) + } + + /** + *Asynchronously* Calls a Cloud Code function and returns a result of it's execution. + * - parameter options: A set of options used to reset the password. 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 logging out, completes or fails. + */ + public func callFunction(options: API.Options = [], + callbackQueue: DispatchQueue = .main, + completion: @escaping (Result) -> Void) { + callFunctionCommand() + .executeAsync(options: options, + callbackQueue: callbackQueue, completion: completion) + } + + internal func callFunctionCommand() -> API.Command { + + return API.Command(method: .POST, + path: .functions(name: functionJobName), + body: self) { (data) -> AnyResultType in + try ParseCoding.jsonDecoder().decode(AnyResultType.self, from: data) + } + } +} + +// MARK: Jobs +extension ParseCloud { + /** + *Synchronously* Calls a Cloud Code function and returns a result of it's execution. + * - parameter options: A set of options used to reset the password. Defaults to an empty set. + */ + public func callJob(options: API.Options = []) throws -> AnyResultType { + try callFunctionCommand().execute(options: options) + } + + /** + *Asynchronously* Calls a Cloud Code function and returns a result of it's execution. + * - parameter options: A set of options used to reset the password. 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 logging out, completes or fails. + */ + public func callJob(options: API.Options = [], + callbackQueue: DispatchQueue = .main, + completion: @escaping (Result) -> Void) { + callFunctionCommand() + .executeAsync(options: options, + callbackQueue: callbackQueue, completion: completion) + } + + internal func callJobCommand() -> API.Command { + + return API.Command(method: .POST, + path: .jobs(name: functionJobName), + body: self) { (data) -> AnyResultType in + try ParseCoding.jsonDecoder().decode(AnyResultType.self, from: data) + } + } +} + +// MARK: CustomDebugStringConvertible +extension ParseCloud { + public var debugDescription: String { + guard let descriptionData = try? ParseCoding.parseEncoder(skipKeys: false).encode(self), + let descriptionString = String(data: descriptionData, encoding: .utf8) else { + return "\(functionJobName)" + } + + return "\(descriptionString)" + } +} diff --git a/Sources/ParseSwift/Coding/ParseCoding.swift b/Sources/ParseSwift/Coding/ParseCoding.swift index 9be1c2085..773a48f34 100644 --- a/Sources/ParseSwift/Coding/ParseCoding.swift +++ b/Sources/ParseSwift/Coding/ParseCoding.swift @@ -14,6 +14,7 @@ public enum ParseCoding {} // MARK: Coders extension ParseCoding { private static let forbiddenKeys = Set(["createdAt", "updatedAt", "objectId", "className"]) + private static let forbiddenCloudKeys = Set(["functionJobName"]) ///This should only be used for Unit tests, don't use in SDK static func jsonEncoder() -> JSONEncoder { @@ -31,7 +32,8 @@ extension ParseCoding { static func parseEncoder(skipKeys: Bool = true) -> ParseEncoder { ParseEncoder( dateEncodingStrategy: parseDateEncodingStrategy, - skippingKeys: skipKeys ? forbiddenKeys : [] + skippingKeys: skipKeys ? forbiddenKeys : [], + skippingCloudKeys: skipKeys ? forbiddenCloudKeys : [] ) } } diff --git a/Sources/ParseSwift/Coding/ParseEncoder.swift b/Sources/ParseSwift/Coding/ParseEncoder.swift index 827bc015b..03b7f9244 100644 --- a/Sources/ParseSwift/Coding/ParseEncoder.swift +++ b/Sources/ParseSwift/Coding/ParseEncoder.swift @@ -53,15 +53,18 @@ public struct ParseEncoder { let dateEncodingStrategy: AnyCodable.DateEncodingStrategy? let jsonEncoder: JSONEncoder let skippedKeys: Set + let skippedCloudKeys: Set init( dateEncodingStrategy: AnyCodable.DateEncodingStrategy? = nil, jsonEncoder: JSONEncoder = JSONEncoder(), - skippingKeys: Set = [] + skippingKeys: Set = [], + skippingCloudKeys: Set = [] ) { self.dateEncodingStrategy = dateEncodingStrategy self.jsonEncoder = jsonEncoder self.skippedKeys = skippingKeys + self.skippedCloudKeys = skippingCloudKeys if let dateEncodingStrategy = dateEncodingStrategy { self.jsonEncoder.dateEncodingStrategy = .custom(dateEncodingStrategy) } @@ -71,6 +74,14 @@ public struct ParseEncoder { try jsonEncoder.encode(value) } + public func encode(_ value: T) throws -> Data { + let encoder = _ParseEncoder(codingPath: [], dictionary: NSMutableDictionary(), skippingKeys: skippedCloudKeys) + if let dateEncodingStrategy = dateEncodingStrategy { + encoder.dateEncodingStrategy = .custom(dateEncodingStrategy) + } + return try encoder.encodeObject(value, collectChildren: false, objectsSavedBeforeThisOne: nil).encoded + } + public func encode(_ value: T) throws -> Data { let encoder = _ParseEncoder(codingPath: [], dictionary: NSMutableDictionary(), skippingKeys: skippedKeys) if let dateEncodingStrategy = dateEncodingStrategy { diff --git a/Sources/ParseSwift/Object Protocols/ParseUser.swift b/Sources/ParseSwift/Object Protocols/ParseUser.swift index 9fc916478..55fadd31e 100644 --- a/Sources/ParseSwift/Object Protocols/ParseUser.swift +++ b/Sources/ParseSwift/Object Protocols/ParseUser.swift @@ -25,6 +25,17 @@ public protocol ParseUser: ParseObject { var password: String? { get set } } +// MARK: SignupBody +struct SignupBody: Codable { + let username: String + let password: String +} + +// MARK: PasswordResetBody +struct PasswordResetBody: Codable { + let email: String +} + // MARK: Default Implementations public extension ParseUser { static var className: String { @@ -104,8 +115,8 @@ extension ParseUser { If login failed due to either an incorrect password or incorrect username, it throws a `ParseError`. */ public static func login(username: String, - password: String) throws -> Self { - try loginCommand(username: username, password: password).execute(options: []) + password: String, options: API.Options = []) throws -> Self { + try loginCommand(username: username, password: password).execute(options: options) } /** @@ -122,15 +133,16 @@ extension ParseUser { public static func login( username: String, password: String, + options: API.Options = [], callbackQueue: DispatchQueue = .main, completion: @escaping (Result) -> Void ) { return loginCommand(username: username, password: password) - .executeAsync(options: [], callbackQueue: callbackQueue, completion: completion) + .executeAsync(options: options, callbackQueue: callbackQueue, completion: completion) } - private static func loginCommand(username: String, - password: String) -> API.Command { + internal static func loginCommand(username: String, + password: String) -> API.Command { let params = [ "username": username, "password": password @@ -174,35 +186,88 @@ extension ParseUser { - parameter callbackQueue: The queue to return to after completion. Default value of .main. - parameter completion: A block that will be called when logging out, completes or fails. */ - public static func logout(callbackQueue: DispatchQueue = .main, - completion: @escaping (Result) -> Void) { - logoutCommand().executeAsync(options: [], callbackQueue: callbackQueue) { result in - completion(result.map { true }) + public static func logout(options: API.Options = [], callbackQueue: DispatchQueue = .main, + completion: @escaping (ParseError?) -> Void) { + logoutCommand().executeAsync(options: options, callbackQueue: callbackQueue) { result in + switch result { + + case .success: + completion(nil) + case .failure(let error): + completion(error) + } } } - private static func logoutCommand() -> API.Command { - return API.Command(method: .POST, path: .logout) { (_) -> Void in + internal static func logoutCommand() -> API.Command { + return API.Command(method: .POST, path: .logout) { (data) -> NoBody in + let serverResponse = try ParseCoding.jsonDecoder().decode(NoBody.self, from: data) deleteCurrentContainerFromKeychain() currentUserContainer = nil + return serverResponse } } } +// MARK: Password Reset +extension ParseUser { + + /** + *Synchronously* requests 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 Parse site. + * - parameter email: The email address associated with the user that + * forgot their password. + * - parameter options: A set of options used to reset the password. Defaults to an empty set. + */ + public static func passwordReset(email: String, options: API.Options = []) throws { + _ = try passwordResetCommand(email: email).execute(options: options) + } + + /** + *Asynchronously* requests 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 Parse site. + * - parameter email: The email address associated with the user that + * forgot their password. + * - parameter options: A set of options used to reset the password. 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 logging out, completes or fails. + */ + public static func passwordReset(email: String, options: API.Options = [], callbackQueue: DispatchQueue = .main, + completion: @escaping (ParseError?) -> Void) { + passwordResetCommand(email: email).executeAsync(options: options, callbackQueue: callbackQueue) { result in + switch result { + + case .success: + completion(nil) + case .failure(let error): + completion(error) + } + } + } + + internal static func passwordResetCommand(email: String) -> API.Command { + let resetBody = PasswordResetBody(email: email) + return API.Command(method: .POST, path: .passwordReset, body: resetBody) { (data) -> NoBody in + try ParseCoding.jsonDecoder().decode(NoBody.self, from: data) + } + } +} + // MARK: Signing Up extension ParseUser { /** Signs up the user *synchronously*. 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. - returns: Returns whether the sign up was successful. */ public static func signup(username: String, - password: String) throws -> Self { - try signupCommand(username: username, password: password).execute(options: []) + password: String, options: API.Options = []) throws -> Self { + try signupCommand(username: username, password: password).execute(options: options) } /** @@ -211,11 +276,10 @@ extension ParseUser { This will also enforce that the username isn't already taken. - warning: Make sure that password and username are set before calling this method. - - returns: Returns whether the sign up was successful. */ - public func signup() throws -> Self { - try signupCommand().execute(options: []) + public func signup(options: API.Options = []) throws -> Self { + try signupCommand().execute(options: options) } /** @@ -228,9 +292,9 @@ extension ParseUser { - parameter completion: The block to execute. It should have the following argument signature: `(Result)`. */ - public func signup(callbackQueue: DispatchQueue = .main, + public func signup(options: API.Options = [], callbackQueue: DispatchQueue = .main, completion: @escaping (Result) -> Void) { - return signupCommand().executeAsync(options: [], callbackQueue: callbackQueue, completion: completion) + return signupCommand().executeAsync(options: options, callbackQueue: callbackQueue, completion: completion) } /** @@ -238,8 +302,6 @@ extension ParseUser { 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 callbackQueue: The queue to return to after completion. Default value of .main. @@ -249,15 +311,16 @@ extension ParseUser { public static func signup( username: String, password: String, + options: API.Options = [], callbackQueue: DispatchQueue = .main, completion: @escaping (Result) -> Void ) { return signupCommand(username: username, password: password) - .executeAsync(options: [], callbackQueue: callbackQueue, completion: completion) + .executeAsync(options: options, callbackQueue: callbackQueue, completion: completion) } - private static func signupCommand(username: String, - password: String) -> API.Command { + internal static func signupCommand(username: String, + password: String) -> API.Command { let body = SignupBody(username: username, password: password) return API.Command(method: .POST, path: .signup, body: body) { (data) -> Self in @@ -276,7 +339,7 @@ extension ParseUser { } } - private func signupCommand() -> API.Command { + internal func signupCommand() -> API.Command { return API.Command(method: .POST, path: .signup, body: self) { (data) -> Self in let response = try ParseCoding.jsonDecoder().decode(LoginSignupResponse.self, from: data) @@ -294,12 +357,6 @@ extension ParseUser { } } -// MARK: SignupBody -private struct SignupBody: Codable { - let username: String - let password: String -} - // MARK: Fetchable extension ParseUser { internal static func updateKeychainIfNeeded(_ results: [Self], deleting: Bool = false) throws { diff --git a/Tests/ParseSwiftTests/ParseCloudTests.swift b/Tests/ParseSwiftTests/ParseCloudTests.swift new file mode 100644 index 000000000..2720e951d --- /dev/null +++ b/Tests/ParseSwiftTests/ParseCloudTests.swift @@ -0,0 +1,305 @@ +// +// ParseCloudTests.swift +// ParseSwift +// +// Created by Corey Baker on 12/29/20. +// Copyright © 2020 Parse Community. All rights reserved. +// + +import Foundation +import XCTest +@testable import ParseSwift + +class ParseCloudTests: XCTestCase { + + struct Cloud: ParseCloud { + // Those are required for Object + var functionJobName: String + } + + struct Cloud2: ParseCloud { + // Those are required for Object + var functionJobName: String + + // Your custom keys + var customKey: String? + } + + override func setUpWithError() throws { + super.setUp() + guard let url = URL(string: "https://localhost:1337/1") else { + XCTFail("Should create valid URL") + return + } + ParseSwift.initialize(applicationId: "applicationId", + clientKey: "clientKey", + masterKey: "masterKey", + serverURL: url) + } + + override func tearDownWithError() throws { + super.tearDown() + MockURLProtocol.removeAll() + try KeychainStore.shared.deleteAll() + try ParseStorage.shared.deleteAll() + } + + func testJSONEncoding() throws { + let expected = ["functionJobName": "test"] + let cloud = Cloud(functionJobName: "test") + let encoded = try JSONEncoder().encode(cloud) + let decoded = try JSONDecoder().decode([String: String].self, from: encoded) + XCTAssertEqual(decoded, expected, "all keys should show up in JSONEncoder") + } + + func testJSONEncoding2() throws { + let expected = [ + "functionJobName": "test", + "customKey": "parse" + ] + let cloud = Cloud2(functionJobName: "test", customKey: "parse") + let encoded = try JSONEncoder().encode(cloud) + let decoded = try JSONDecoder().decode([String: String].self, from: encoded) + XCTAssertEqual(decoded, expected, "all keys should show up in JSONEncoder") + } + + func testParseEncoding() throws { + let expected = [String: String]() + let cloud = Cloud(functionJobName: "test") + let encoded = try ParseCoding.parseEncoder().encode(cloud) + let decoded = try JSONDecoder().decode([String: String].self, from: encoded) + XCTAssertEqual(decoded, expected, "\"functionJobName\" key should be skipped by ParseEncoder") + } + + func testParseEncoding2() throws { + let expected = [ + "customKey": "parse" + ] + let cloud = Cloud2(functionJobName: "test", customKey: "parse") + let encoded = try ParseCoding.parseEncoder().encode(cloud) + let decoded = try JSONDecoder().decode([String: String].self, from: encoded) + XCTAssertEqual(decoded, expected, "\"functionJobName\" key should be skipped by ParseEncoder") + } + + func testCallFunctionCommand() throws { + let cloud = Cloud(functionJobName: "test") + let command = cloud.callFunctionCommand() + XCTAssertNotNil(command) + XCTAssertEqual(command.path.urlComponent, "/functions/test") + XCTAssertEqual(command.method, API.Method.POST) + XCTAssertNil(command.params) + XCTAssertEqual(command.body?.functionJobName, "test") + XCTAssertNotNil(command.data) + } + + func testCallFunctionWithArgsCommand() throws { + let cloud = Cloud2(functionJobName: "test", customKey: "parse") + let command = cloud.callFunctionCommand() + XCTAssertNotNil(command) + XCTAssertEqual(command.path.urlComponent, "/functions/test") + XCTAssertEqual(command.method, API.Method.POST) + XCTAssertNil(command.params) + XCTAssertEqual(command.body?.functionJobName, "test") + XCTAssertEqual(command.body?.customKey, "parse") + XCTAssertNotNil(command.data) + } + + func testFunction() { + let response = [String: AnyCodable]() + + MockURLProtocol.mockRequests { _ in + do { + let encoded = try ParseCoding.jsonEncoder().encode(response) + return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) + } catch { + return nil + } + } + do { + let cloud = Cloud(functionJobName: "test") + let functionResponse = try cloud.callFunction() + XCTAssertEqual(functionResponse, response) + } catch { + XCTFail(error.localizedDescription) + } + } + + func testFunction2() { + let response: [String: AnyCodable] = ["hello": "world"] + + MockURLProtocol.mockRequests { _ in + do { + let encoded = try ParseCoding.jsonEncoder().encode(response) + return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) + } catch { + return nil + } + } + do { + let cloud = Cloud(functionJobName: "test") + let functionResponse = try cloud.callFunction() + XCTAssertEqual(functionResponse, response) + } catch { + XCTFail(error.localizedDescription) + } + } + + func functionAsync(serverResponse: [String: AnyCodable], callbackQueue: DispatchQueue) { + + let expectation1 = XCTestExpectation(description: "Logout user1") + let cloud = Cloud(functionJobName: "test") + cloud.callFunction(callbackQueue: callbackQueue) { result in + + switch result { + + case .success(let response): + XCTAssertEqual(response, serverResponse) + case .failure(let error): + XCTFail(error.localizedDescription) + } + expectation1.fulfill() + } + wait(for: [expectation1], timeout: 10.0) + } + + func testFunctionMainQueue() { + let response = [String: AnyCodable]() + + MockURLProtocol.mockRequests { _ in + do { + let encoded = try ParseCoding.jsonEncoder().encode(response) + return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) + } catch { + return nil + } + } + + self.functionAsync(serverResponse: response, callbackQueue: .main) + } + + func testFunctionMainQueue2() { + let response: [String: AnyCodable] = ["hello": "world"] + + MockURLProtocol.mockRequests { _ in + do { + let encoded = try ParseCoding.jsonEncoder().encode(response) + return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) + } catch { + return nil + } + } + + self.functionAsync(serverResponse: response, callbackQueue: .main) + } + + func testCallJobCommand() throws { + let cloud = Cloud(functionJobName: "test") + let command = cloud.callJobCommand() + XCTAssertNotNil(command) + XCTAssertEqual(command.path.urlComponent, "/jobs/test") + XCTAssertEqual(command.method, API.Method.POST) + XCTAssertNil(command.params) + XCTAssertEqual(command.body?.functionJobName, "test") + XCTAssertNotNil(command.data) + } + + func testCallJobWithArgsCommand() throws { + let cloud = Cloud2(functionJobName: "test", customKey: "parse") + let command = cloud.callJobCommand() + XCTAssertNotNil(command) + XCTAssertEqual(command.path.urlComponent, "/jobs/test") + XCTAssertEqual(command.method, API.Method.POST) + XCTAssertNil(command.params) + XCTAssertEqual(command.body?.functionJobName, "test") + XCTAssertEqual(command.body?.customKey, "parse") + XCTAssertNotNil(command.data) + } + + func testJob() { + let response = [String: AnyCodable]() + + MockURLProtocol.mockRequests { _ in + do { + let encoded = try ParseCoding.jsonEncoder().encode(response) + return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) + } catch { + return nil + } + } + do { + let cloud = Cloud(functionJobName: "test") + let functionResponse = try cloud.callJob() + XCTAssertEqual(functionResponse, response) + } catch { + XCTFail(error.localizedDescription) + } + } + + func testJob2() { + let response: [String: AnyCodable] = ["hello": "world"] + + MockURLProtocol.mockRequests { _ in + do { + let encoded = try ParseCoding.jsonEncoder().encode(response) + return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) + } catch { + return nil + } + } + do { + let cloud = Cloud(functionJobName: "test") + let functionResponse = try cloud.callJob() + XCTAssertEqual(functionResponse, response) + } catch { + XCTFail(error.localizedDescription) + } + } + + func jobAsync(serverResponse: [String: AnyCodable], callbackQueue: DispatchQueue) { + + let expectation1 = XCTestExpectation(description: "Logout user1") + let cloud = Cloud(functionJobName: "test") + cloud.callJob(callbackQueue: callbackQueue) { result in + + switch result { + + case .success(let response): + XCTAssertEqual(response, serverResponse) + case .failure(let error): + XCTFail(error.localizedDescription) + } + expectation1.fulfill() + } + wait(for: [expectation1], timeout: 10.0) + } + + func testJobMainQueue() { + let response = [String: AnyCodable]() + + MockURLProtocol.mockRequests { _ in + do { + let encoded = try ParseCoding.jsonEncoder().encode(response) + return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) + } catch { + return nil + } + } + + self.jobAsync(serverResponse: response, callbackQueue: .main) + } + + func testJobMainQueue2() { + let response: [String: AnyCodable] = ["hello": "world"] + + MockURLProtocol.mockRequests { _ in + do { + let encoded = try ParseCoding.jsonEncoder().encode(response) + return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) + } catch { + return nil + } + } + + self.jobAsync(serverResponse: response, callbackQueue: .main) + } +} diff --git a/Tests/ParseSwiftTests/ParseUserTests.swift b/Tests/ParseSwiftTests/ParseUserTests.swift index 8082521ec..743ede38e 100644 --- a/Tests/ParseSwiftTests/ParseUserTests.swift +++ b/Tests/ParseSwiftTests/ParseUserTests.swift @@ -159,7 +159,7 @@ class ParseUserTests: XCTestCase { // swiftlint:disable:this type_body_length } func testFetchAndUpdateCurrentUser() { // swiftlint:disable:this function_body_length - testUserLogin() + testLogin() MockURLProtocol.removeAll() XCTAssertNotNil(User.current?.objectId) @@ -223,7 +223,7 @@ class ParseUserTests: XCTestCase { // swiftlint:disable:this type_body_length func testFetchAsyncAndUpdateCurrentUser() { // swiftlint:disable:this function_body_length XCTAssertNil(User.current?.objectId) - testUserLogin() + testLogin() MockURLProtocol.removeAll() XCTAssertNotNil(User.current?.objectId) @@ -407,7 +407,7 @@ class ParseUserTests: XCTestCase { // swiftlint:disable:this type_body_length func testSaveAndUpdateCurrentUser() { // swiftlint:disable:this function_body_length XCTAssertNil(User.current?.objectId) - testUserLogin() + testLogin() MockURLProtocol.removeAll() XCTAssertNotNil(User.current?.objectId) @@ -468,7 +468,7 @@ class ParseUserTests: XCTestCase { // swiftlint:disable:this type_body_length func testSaveAsyncAndUpdateCurrentUser() { // swiftlint:disable:this function_body_length XCTAssertNil(User.current?.objectId) - testUserLogin() + testLogin() MockURLProtocol.removeAll() XCTAssertNotNil(User.current?.objectId) @@ -707,6 +707,18 @@ class ParseUserTests: XCTestCase { // swiftlint:disable:this type_body_length self.updateAsync(user: user, userOnServer: userOnServer, callbackQueue: .main) } + func testSignupCommandWithBody() { + let body = SignupBody(username: "test", password: "user") + let command = User.signupCommand(username: "test", password: "user") + XCTAssertNotNil(command) + XCTAssertEqual(command.path.urlComponent, "/users") + XCTAssertEqual(command.method, API.Method.POST) + XCTAssertNil(command.params) + XCTAssertEqual(command.body?.username, body.username) + XCTAssertEqual(command.body?.password, body.password) + XCTAssertNotNil(command.data) + } + func testUserSignUp() { let loginResponse = LoginSignupResponse() @@ -804,7 +816,21 @@ class ParseUserTests: XCTestCase { // swiftlint:disable:this type_body_length self.signUpAsync(loginResponse: loginResponse, callbackQueue: .main) } - func testUserLogin() { + func testLoginCommand() { + let params = [ + "username": "test", + "password": "user" + ] + let command = User.loginCommand(username: "test", password: "user") + XCTAssertNotNil(command) + XCTAssertEqual(command.path.urlComponent, "/login") + XCTAssertEqual(command.method, API.Method.GET) + XCTAssertEqual(command.params, params) + XCTAssertNil(command.body) + XCTAssertNil(command.data) + } + + func testLogin() { let loginResponse = LoginSignupResponse() MockURLProtocol.mockRequests { _ in @@ -847,7 +873,7 @@ class ParseUserTests: XCTestCase { // swiftlint:disable:this type_body_length } } - func userLoginAsync(loginResponse: LoginSignupResponse, callbackQueue: DispatchQueue) { + func loginAsync(loginResponse: LoginSignupResponse, callbackQueue: DispatchQueue) { let expectation1 = XCTestExpectation(description: "Login user") User.login(username: loginUserName, password: loginPassword, @@ -899,15 +925,25 @@ class ParseUserTests: XCTestCase { // swiftlint:disable:this type_body_length } } - self.userLoginAsync(loginResponse: loginResponse, callbackQueue: .main) + self.loginAsync(loginResponse: loginResponse, callbackQueue: .main) } - func testUserLogout() { - let loginResponse = LoginSignupResponse() + func testLogutCommand() { + let command = User.logoutCommand() + XCTAssertNotNil(command) + XCTAssertEqual(command.path.urlComponent, "/users/logout") + XCTAssertEqual(command.method, API.Method.POST) + XCTAssertNil(command.params) + XCTAssertNil(command.body) + XCTAssertNil(command.data) + } + + func testLogout() { + let logoutResponse = NoBody() MockURLProtocol.mockRequests { _ in do { - let encoded = try loginResponse.getEncoder(skipKeys: false).encode(loginResponse) + let encoded = try ParseCoding.jsonEncoder().encode(logoutResponse) return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) } catch { return nil @@ -928,31 +964,27 @@ class ParseUserTests: XCTestCase { // swiftlint:disable:this type_body_length func logoutAsync(callbackQueue: DispatchQueue) { let expectation1 = XCTestExpectation(description: "Logout user1") - User.logout(callbackQueue: callbackQueue) { result in - - switch result { + User.logout(callbackQueue: callbackQueue) { error in - case .success(let success): - XCTAssertTrue(success) + guard let error = error else { if let userFromKeychain = BaseParseUser.current { XCTFail("\(userFromKeychain) wasn't deleted from Keychain during logout") - expectation1.fulfill() - return } - case .failure(let error): - XCTFail(error.localizedDescription) + expectation1.fulfill() + return } + XCTFail(error.localizedDescription) expectation1.fulfill() } wait(for: [expectation1], timeout: 10.0) } func testLogoutAsyncMainQueue() { - let loginResponse = LoginSignupResponse() + let logoutResponse = NoBody() MockURLProtocol.mockRequests { _ in do { - let encoded = try loginResponse.getEncoder(skipKeys: false).encode(loginResponse) + let encoded = try ParseCoding.jsonEncoder().encode(logoutResponse) return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) } catch { return nil @@ -962,8 +994,67 @@ class ParseUserTests: XCTestCase { // swiftlint:disable:this type_body_length self.logoutAsync(callbackQueue: .main) } + func testPasswordResetCommand() throws { + let body = PasswordResetBody(email: "hello@parse.org") + let command = User.passwordResetCommand(email: body.email) + XCTAssertNotNil(command) + XCTAssertEqual(command.path.urlComponent, "/requestPasswordReset") + XCTAssertEqual(command.method, API.Method.POST) + XCTAssertNil(command.params) + XCTAssertEqual(command.body?.email, body.email) + XCTAssertNotNil(command.data) + } + + func testPasswordReset() { + let response = NoBody() + + MockURLProtocol.mockRequests { _ in + do { + let encoded = try ParseCoding.jsonEncoder().encode(response) + return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) + } catch { + return nil + } + } + do { + try User.passwordReset(email: "hello@parse.org") + } catch { + XCTFail(error.localizedDescription) + } + } + + func passwordResetAsync(callbackQueue: DispatchQueue) { + + let expectation1 = XCTestExpectation(description: "Logout user1") + User.passwordReset(email: "hello@parse.org", callbackQueue: callbackQueue) { error in + + guard let error = error else { + expectation1.fulfill() + return + } + XCTFail(error.localizedDescription) + expectation1.fulfill() + } + wait(for: [expectation1], timeout: 10.0) + } + + func testPasswordResetMainQueue() { + let response = NoBody() + + MockURLProtocol.mockRequests { _ in + do { + let encoded = try ParseCoding.jsonEncoder().encode(response) + return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) + } catch { + return nil + } + } + + self.passwordResetAsync(callbackQueue: .main) + } + func testUserCustomValuesNotSavedToKeychain() { - testUserLogin() + testLogin() User.current?.customKey = "Changed" User.saveCurrentContainerToKeychain() guard let keychainUser: CurrentUserContainer @@ -975,7 +1066,7 @@ class ParseUserTests: XCTestCase { // swiftlint:disable:this type_body_length } func testDelete() { - testUserLogin() + testLogin() let expectation1 = XCTestExpectation(description: "Delete installation1") DispatchQueue.main.async { guard let user = User.current else { @@ -1002,7 +1093,7 @@ class ParseUserTests: XCTestCase { // swiftlint:disable:this type_body_length } func testDeleteAsyncMainQueue() { - testUserLogin() + testLogin() MockURLProtocol.removeAll() let expectation1 = XCTestExpectation(description: "Delete installation1") @@ -1040,7 +1131,7 @@ class ParseUserTests: XCTestCase { // swiftlint:disable:this type_body_length // swiftlint:disable:next function_body_length func testFetchAll() { - testUserLogin() + testLogin() MockURLProtocol.removeAll() let expectation1 = XCTestExpectation(description: "Fetch user1") @@ -1127,7 +1218,7 @@ class ParseUserTests: XCTestCase { // swiftlint:disable:this type_body_length // swiftlint:disable:next function_body_length func testFetchAllAsyncMainQueue() { - testUserLogin() + testLogin() MockURLProtocol.removeAll() let expectation1 = XCTestExpectation(description: "Fetch user1") @@ -1215,7 +1306,7 @@ class ParseUserTests: XCTestCase { // swiftlint:disable:this type_body_length // swiftlint:disable:next function_body_length func testSaveAll() { - testUserLogin() + testLogin() MockURLProtocol.removeAll() let expectation1 = XCTestExpectation(description: "Fetch user1") @@ -1302,7 +1393,7 @@ class ParseUserTests: XCTestCase { // swiftlint:disable:this type_body_length // swiftlint:disable:next function_body_length func testSaveAllAsyncMainQueue() { - testUserLogin() + testLogin() MockURLProtocol.removeAll() let expectation1 = XCTestExpectation(description: "Fetch user1") @@ -1389,7 +1480,7 @@ class ParseUserTests: XCTestCase { // swiftlint:disable:this type_body_length } func testDeleteAll() { - testUserLogin() + testLogin() MockURLProtocol.removeAll() let expectation1 = XCTestExpectation(description: "Delete user1") @@ -1435,7 +1526,7 @@ class ParseUserTests: XCTestCase { // swiftlint:disable:this type_body_length } func testDeleteAllAsyncMainQueue() { - testUserLogin() + testLogin() MockURLProtocol.removeAll() let expectation1 = XCTestExpectation(description: "Delete user1") From b23edca6e4301d4ff525d29e447fac95bbc75098 Mon Sep 17 00:00:00 2001 From: Corey's iMac Date: Tue, 29 Dec 2020 19:53:39 -0500 Subject: [PATCH 02/44] Fixed bug. Make sure the right encoders are always used --- .../Contents.swift | 36 ++++++++ ParseSwift.playground/contents.xcplayground | 3 +- ParseSwift.xcodeproj/project.pbxproj | 10 +-- Sources/ParseSwift/API/API+Commands.swift | 30 +++++-- Sources/ParseSwift/Coding/ParseEncoder.swift | 4 - .../Object Protocols/ParseObject.swift | 8 +- .../Protocols/Objectable.swift | 2 +- .../{Cloud => Parse Types}/ParseCloud.swift | 17 ++-- Tests/ParseSwiftTests/ACLTests.swift | 2 +- Tests/ParseSwiftTests/ParseCloudTests.swift | 4 - Tests/ParseSwiftTests/ParseEncoderTests.swift | 8 +- .../ParseSwiftTests/ParseGeoPointTests.swift | 2 +- .../ParseInstallationTests.swift | 22 ++--- .../ParseObjectBatchTests.swift | 69 +++++++-------- Tests/ParseSwiftTests/ParseObjectTests.swift | 24 ++--- Tests/ParseSwiftTests/ParseQueryTests.swift | 88 +++++++++---------- Tests/ParseSwiftTests/ParseUserTests.swift | 24 +++-- 17 files changed, 190 insertions(+), 163 deletions(-) create mode 100644 ParseSwift.playground/Pages/10 - Cloud Code.xcplaygroundpage/Contents.swift rename Sources/ParseSwift/{Cloud => Parse Types}/ParseCloud.swift (83%) diff --git a/ParseSwift.playground/Pages/10 - Cloud Code.xcplaygroundpage/Contents.swift b/ParseSwift.playground/Pages/10 - Cloud Code.xcplaygroundpage/Contents.swift new file mode 100644 index 000000000..8f94ebfd6 --- /dev/null +++ b/ParseSwift.playground/Pages/10 - Cloud Code.xcplaygroundpage/Contents.swift @@ -0,0 +1,36 @@ +//: [Previous](@previous) + +import PlaygroundSupport +import Foundation +import ParseSwift + +PlaygroundPage.current.needsIndefiniteExecution = true +initializeParse() + +//: Create your own ValueTyped ParseCloud type +struct Cloud: ParseCloud { + //: These are required for Object + var functionJobName: String + + //: If your cloud function takes arguments, they can be passed by creating properties + //var argument1: [String: Int] = ["test": 5] +} + +/*: Assuming you have the Cloud Function named "hello" on your parse-server: + // main.js + Parse.Cloud.define('hello', async () => { + return 'Hello world!'; + }); + */ +let cloud = Cloud(functionJobName: "hello") + +cloud.callFunction { result in + switch result { + case .success(let response): + print("Response from cloud function: \(response)") + case .failure(let error): + assertionFailure("Error calling cloud function: \(error)") + } +} + +//: [Next](@next) diff --git a/ParseSwift.playground/contents.xcplayground b/ParseSwift.playground/contents.xcplayground index bf7611d1f..18a68aa71 100644 --- a/ParseSwift.playground/contents.xcplayground +++ b/ParseSwift.playground/contents.xcplayground @@ -9,5 +9,6 @@ + - \ No newline at end of file + diff --git a/ParseSwift.xcodeproj/project.pbxproj b/ParseSwift.xcodeproj/project.pbxproj index 93feb6d65..045f3f251 100644 --- a/ParseSwift.xcodeproj/project.pbxproj +++ b/ParseSwift.xcodeproj/project.pbxproj @@ -551,7 +551,6 @@ F97B463F24D9C78B00F4A88B /* Mutation Operations */, F97B45C324D9C6F200F4A88B /* Object Protocols */, F97B45BA24D9C6F200F4A88B /* Parse Types */, - 916786E0259B7D0100BB5B4E /* Cloud */, F97B45CB24D9C6F200F4A88B /* Storage */, 4A82B7EE1F254B820063D731 /* Parse.swift */, 70110D51250680140091CC1D /* ParseConstants.swift */, @@ -665,14 +664,6 @@ path = "ParseSwift-tvOS"; sourceTree = ""; }; - 916786E0259B7D0100BB5B4E /* Cloud */ = { - isa = PBXGroup; - children = ( - 916786E1259B7DDA00BB5B4E /* ParseCloud.swift */, - ); - path = Cloud; - sourceTree = ""; - }; F97B45B324D9C6F200F4A88B /* Coding */ = { isa = PBXGroup; children = ( @@ -693,6 +684,7 @@ F97B45C124D9C6F200F4A88B /* File.swift */, F97B45BC24D9C6F200F4A88B /* ParseGeoPoint.swift */, F97B45BF24D9C6F200F4A88B /* ParseError.swift */, + 916786E1259B7DDA00BB5B4E /* ParseCloud.swift */, F97B45BE24D9C6F200F4A88B /* Pointer.swift */, F97B45BB24D9C6F200F4A88B /* Query.swift */, 70110D5D250849B30091CC1D /* Internal */, diff --git a/Sources/ParseSwift/API/API+Commands.swift b/Sources/ParseSwift/API/API+Commands.swift index 1d1c030fc..6254c0e2f 100644 --- a/Sources/ParseSwift/API/API+Commands.swift +++ b/Sources/ParseSwift/API/API+Commands.swift @@ -51,7 +51,7 @@ internal extension API { return try response.get() } - // swiftlint:disable:next function_body_length + // swiftlint:disable:next function_body_length cyclomatic_complexity func executeAsync(options: API.Options, callbackQueue: DispatchQueue?, childObjects: [NSDictionary: PointerType]? = nil, completion: @escaping(Result) -> Void) { @@ -86,12 +86,21 @@ internal extension API { } urlRequest.httpBody = bodyData.encoded } else { - guard let bodyData = try? ParseCoding.parseEncoder().encode(urlBody) else { - completion(.failure(ParseError(code: .unknownError, - message: "couldn't encode body \(urlBody)"))) - return + if (urlBody as? ParseCloud) != nil { + guard let bodyData = try? ParseCoding.parseEncoder().encode(urlBody) else { + completion(.failure(ParseError(code: .unknownError, + message: "couldn't encode body \(urlBody)"))) + return + } + urlRequest.httpBody = bodyData + } else { + guard let bodyData = try? ParseCoding.jsonEncoder().encode(urlBody) else { + completion(.failure(ParseError(code: .unknownError, + message: "couldn't encode body \(urlBody)"))) + return + } + urlRequest.httpBody = bodyData } - urlRequest.httpBody = bodyData } } urlRequest.httpMethod = method.rawValue @@ -143,7 +152,7 @@ internal extension API.Command { } private static func updateCommand(_ object: T) -> API.Command where T: ParseObject { - let mapper = { (data: Data) -> T in + let mapper = { (data) -> T in try ParseCoding.jsonDecoder().decode(UpdateResponse.self, from: data).apply(to: object) } return API.Command(method: .PUT, @@ -315,11 +324,14 @@ extension API.Command where T: ParseObject { } } +//This has been disabled, looking into getting it working in the future. +//It's only needed for sending batches of childObjects which currently isn't being used. +/* extension API.Command where T: Encodable { internal var data: Data? { guard let body = body else { return nil } - return try? ParseCoding.parseEncoder().encode(body) + return try? ParseCoding.jsonEncoder().encode(body) } static func batch(commands: [API.Command]) -> RESTBatchCommandTypeEncodable { @@ -366,4 +378,4 @@ extension API.Command where T: Encodable { let batchCommand = BatchCommand(requests: commands) return RESTBatchCommandTypeEncodable(method: .POST, path: .batch, body: batchCommand, mapper: mapper) } -} +}*/ diff --git a/Sources/ParseSwift/Coding/ParseEncoder.swift b/Sources/ParseSwift/Coding/ParseEncoder.swift index 03b7f9244..eb1ce2975 100644 --- a/Sources/ParseSwift/Coding/ParseEncoder.swift +++ b/Sources/ParseSwift/Coding/ParseEncoder.swift @@ -71,10 +71,6 @@ public struct ParseEncoder { } public func encode(_ value: T) throws -> Data { - try jsonEncoder.encode(value) - } - - public func encode(_ value: T) throws -> Data { let encoder = _ParseEncoder(codingPath: [], dictionary: NSMutableDictionary(), skippingKeys: skippedCloudKeys) if let dateEncodingStrategy = dateEncodingStrategy { encoder.dateEncodingStrategy = .custom(dateEncodingStrategy) diff --git a/Sources/ParseSwift/Object Protocols/ParseObject.swift b/Sources/ParseSwift/Object Protocols/ParseObject.swift index 46c5e161a..22f3a1fcb 100644 --- a/Sources/ParseSwift/Object Protocols/ParseObject.swift +++ b/Sources/ParseSwift/Object Protocols/ParseObject.swift @@ -227,7 +227,7 @@ public extension Sequence where Element: ParseObject { } // MARK: Batch Support -internal extension Sequence where Element: Encodable { +/*internal extension Sequence where Element: Encodable { /** Saves a collection of objects *synchronously* all at once and throws an error if necessary. @@ -243,7 +243,7 @@ internal extension Sequence where Element: Encodable { .batch(commands: commands) .execute(options: options) } -} +}*/ // MARK: CustomDebugStringConvertible extension ParseObject { @@ -449,14 +449,14 @@ internal extension Encodable { func saveCommand() throws -> API.Command { try API.Command.saveCommand(self) } - +/* func saveAll(options: API.Options = [], encodableObjects: [T]) throws -> [(Result)] { let commands = try encodableObjects.map { try $0.saveCommand() } return try API.Command .batch(commands: commands) .execute(options: options) - } + }*/ } // MARK: Deletable diff --git a/Sources/ParseSwift/Object Protocols/Protocols/Objectable.swift b/Sources/ParseSwift/Object Protocols/Protocols/Objectable.swift index 1edf94d0b..5cfecf8e8 100644 --- a/Sources/ParseSwift/Object Protocols/Protocols/Objectable.swift +++ b/Sources/ParseSwift/Object Protocols/Protocols/Objectable.swift @@ -57,7 +57,7 @@ extension Objectable { } internal func getUniqueObject() throws -> UniqueObject { - let encoded = try ParseCoding.parseEncoder(skipKeys: false).encode(self) + let encoded = try ParseCoding.jsonEncoder().encode(self) return try ParseCoding.jsonDecoder().decode(UniqueObject.self, from: encoded) } } diff --git a/Sources/ParseSwift/Cloud/ParseCloud.swift b/Sources/ParseSwift/Parse Types/ParseCloud.swift similarity index 83% rename from Sources/ParseSwift/Cloud/ParseCloud.swift rename to Sources/ParseSwift/Parse Types/ParseCloud.swift index 02ed7a342..e9ada8e12 100644 --- a/Sources/ParseSwift/Cloud/ParseCloud.swift +++ b/Sources/ParseSwift/Parse Types/ParseCloud.swift @@ -27,7 +27,7 @@ extension ParseCloud { /** *Synchronously* Calls a Cloud Code function and returns a result of it's execution. - * - parameter options: A set of options used to reset the password. Defaults to an empty set. + * - parameter options: A set of header options sent to the server. Defaults to an empty set. */ public func callFunction(options: API.Options = []) throws -> AnyResultType { try callFunctionCommand().execute(options: options) @@ -35,7 +35,7 @@ extension ParseCloud { /** *Asynchronously* Calls a Cloud Code function and returns a result of it's execution. - * - parameter options: A set of options used to reset the password. Defaults to an empty set. + * - 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 logging out, completes or fails. */ @@ -60,29 +60,28 @@ extension ParseCloud { // MARK: Jobs extension ParseCloud { /** - *Synchronously* Calls a Cloud Code function and returns a result of it's execution. - * - parameter options: A set of options used to reset the password. Defaults to an empty set. + *Synchronously* Calls a Cloud Code job and returns a result of it's execution. + * - parameter options: A set of header options sent to the server. Defaults to an empty set. */ public func callJob(options: API.Options = []) throws -> AnyResultType { - try callFunctionCommand().execute(options: options) + try callJobCommand().execute(options: options) } /** - *Asynchronously* Calls a Cloud Code function and returns a result of it's execution. - * - parameter options: A set of options used to reset the password. Defaults to an empty set. + *Asynchronously* Calls a Cloud Code job and returns a result of it's execution. + * - 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 logging out, completes or fails. */ public func callJob(options: API.Options = [], callbackQueue: DispatchQueue = .main, completion: @escaping (Result) -> Void) { - callFunctionCommand() + callJobCommand() .executeAsync(options: options, callbackQueue: callbackQueue, completion: completion) } internal func callJobCommand() -> API.Command { - return API.Command(method: .POST, path: .jobs(name: functionJobName), body: self) { (data) -> AnyResultType in diff --git a/Tests/ParseSwiftTests/ACLTests.swift b/Tests/ParseSwiftTests/ACLTests.swift index 9776c9950..80de03a61 100644 --- a/Tests/ParseSwiftTests/ACLTests.swift +++ b/Tests/ParseSwiftTests/ACLTests.swift @@ -130,7 +130,7 @@ class ACLTests: XCTestCase { var encoded: Data? do { - encoded = try ParseCoding.parseEncoder().encode(acl) + encoded = try ParseCoding.jsonEncoder().encode(acl) } catch { XCTFail(error.localizedDescription) } diff --git a/Tests/ParseSwiftTests/ParseCloudTests.swift b/Tests/ParseSwiftTests/ParseCloudTests.swift index 2720e951d..5c5c91757 100644 --- a/Tests/ParseSwiftTests/ParseCloudTests.swift +++ b/Tests/ParseSwiftTests/ParseCloudTests.swift @@ -89,7 +89,6 @@ class ParseCloudTests: XCTestCase { XCTAssertEqual(command.method, API.Method.POST) XCTAssertNil(command.params) XCTAssertEqual(command.body?.functionJobName, "test") - XCTAssertNotNil(command.data) } func testCallFunctionWithArgsCommand() throws { @@ -101,7 +100,6 @@ class ParseCloudTests: XCTestCase { XCTAssertNil(command.params) XCTAssertEqual(command.body?.functionJobName, "test") XCTAssertEqual(command.body?.customKey, "parse") - XCTAssertNotNil(command.data) } func testFunction() { @@ -200,7 +198,6 @@ class ParseCloudTests: XCTestCase { XCTAssertEqual(command.method, API.Method.POST) XCTAssertNil(command.params) XCTAssertEqual(command.body?.functionJobName, "test") - XCTAssertNotNil(command.data) } func testCallJobWithArgsCommand() throws { @@ -212,7 +209,6 @@ class ParseCloudTests: XCTestCase { XCTAssertNil(command.params) XCTAssertEqual(command.body?.functionJobName, "test") XCTAssertEqual(command.body?.customKey, "parse") - XCTAssertNotNil(command.data) } func testJob() { diff --git a/Tests/ParseSwiftTests/ParseEncoderTests.swift b/Tests/ParseSwiftTests/ParseEncoderTests.swift index 95e835dd0..a25c377f8 100644 --- a/Tests/ParseSwiftTests/ParseEncoderTests.swift +++ b/Tests/ParseSwiftTests/ParseEncoderTests.swift @@ -43,7 +43,7 @@ class ParseEncoderTests: XCTestCase { let nicknames: [Name] let phoneNumbers: [String] } - +/* func parseEncoding(for object: T) -> Data { let encoder = ParseEncoder() encoder.jsonEncoder.outputFormatting = .sortedKeys @@ -54,7 +54,7 @@ class ParseEncoderTests: XCTestCase { } return encoding - } + }*/ func referenceEncoding(for object: T) -> Data { let encoder = JSONEncoder() @@ -67,7 +67,7 @@ class ParseEncoderTests: XCTestCase { return encoding } - +/* func test_encodingScalarValue() { let encoded = parseEncoding(for: ["": 5]) let reference = referenceEncoding(for: ["": 5]) @@ -112,7 +112,7 @@ class ParseEncoderTests: XCTestCase { XCTAssertEqual(jsonDecoded["*"]?["read"], true) XCTAssertEqual(parseDecoded["*"]?["read"], true) } - +*/ func testSkipKeysDefaultCodingKeys() throws { var score = GameScore(score: 10) score.objectId = "yarr" diff --git a/Tests/ParseSwiftTests/ParseGeoPointTests.swift b/Tests/ParseSwiftTests/ParseGeoPointTests.swift index 59ec25b9e..1df32312c 100644 --- a/Tests/ParseSwiftTests/ParseGeoPointTests.swift +++ b/Tests/ParseSwiftTests/ParseGeoPointTests.swift @@ -52,7 +52,7 @@ class ParseGeoPointTests: XCTestCase { let point = ParseGeoPoint(latitude: 10, longitude: 20) do { - let encoded = try ParseEncoder().encode(point) + let encoded = try ParseCoding.jsonEncoder().encode(point) let decoded = try JSONDecoder().decode(ParseGeoPoint.self, from: encoded) XCTAssertEqual(point, decoded) } catch { diff --git a/Tests/ParseSwiftTests/ParseInstallationTests.swift b/Tests/ParseSwiftTests/ParseInstallationTests.swift index 30dca716d..382d4db9e 100644 --- a/Tests/ParseSwiftTests/ParseInstallationTests.swift +++ b/Tests/ParseSwiftTests/ParseInstallationTests.swift @@ -441,7 +441,7 @@ class ParseInstallationTests: XCTestCase { // swiftlint:disable:this type_body_l installationOnServer.updatedAt = Date() let encoded: Data! do { - let encodedOriginal = try installation.getEncoder(skipKeys: false).encode(installation) + let encodedOriginal = try ParseCoding.jsonEncoder().encode(installation) //Get dates in correct format from ParseDecoding strategy installation = try installation.getDecoder().decode(Installation.self, from: encodedOriginal) @@ -702,9 +702,9 @@ class ParseInstallationTests: XCTestCase { // swiftlint:disable:this type_body_l let encoded: Data! do { - encoded = try installation.getEncoder(skipKeys: false).encode(installationOnServer) + encoded = try ParseCoding.jsonEncoder().encode(installationOnServer) //Get dates in correct format from ParseDecoding strategy - let encoded1 = try installation.getEncoder(skipKeys: false).encode(installation) + let encoded1 = try ParseCoding.jsonEncoder().encode(installation) installation = try installation.getDecoder().decode(Installation.self, from: encoded1) } catch { XCTFail("Should encode/decode. Error \(error)") @@ -788,9 +788,9 @@ class ParseInstallationTests: XCTestCase { // swiftlint:disable:this type_body_l let encoded: Data! do { - encoded = try installation.getEncoder(skipKeys: false).encode(installationOnServer) + encoded = try ParseCoding.jsonEncoder().encode(installationOnServer) //Get dates in correct format from ParseDecoding strategy - let encoded1 = try installation.getEncoder(skipKeys: false).encode(installation) + let encoded1 = try ParseCoding.jsonEncoder().encode(installation) installation = try installation.getDecoder().decode(Installation.self, from: encoded1) } catch { XCTFail("Should encode/decode. Error \(error)") @@ -878,9 +878,9 @@ class ParseInstallationTests: XCTestCase { // swiftlint:disable:this type_body_l let encoded: Data! do { - encoded = try installation.getEncoder(skipKeys: false).encode(installationOnServer) + encoded = try ParseCoding.jsonEncoder().encode(installationOnServer) //Get dates in correct format from ParseDecoding strategy - let encoded1 = try installation.getEncoder(skipKeys: false).encode(installation) + let encoded1 = try ParseCoding.jsonEncoder().encode(installation) installation = try installation.getDecoder().decode(Installation.self, from: encoded1) } catch { XCTFail("Should encode/decode. Error \(error)") @@ -964,9 +964,9 @@ class ParseInstallationTests: XCTestCase { // swiftlint:disable:this type_body_l let encoded: Data! do { - encoded = try installation.getEncoder(skipKeys: false).encode(installationOnServer) + encoded = try ParseCoding.jsonEncoder().encode(installationOnServer) //Get dates in correct format from ParseDecoding strategy - let encoded1 = try installation.getEncoder(skipKeys: false).encode(installation) + let encoded1 = try ParseCoding.jsonEncoder().encode(installation) installation = try installation.getDecoder().decode(Installation.self, from: encoded1) } catch { XCTFail("Should encode/decode. Error \(error)") @@ -1051,7 +1051,7 @@ class ParseInstallationTests: XCTestCase { // swiftlint:disable:this type_body_l let encoded: Data! do { - encoded = try installation.getEncoder(skipKeys: false).encode(installationOnServer) + encoded = try ParseCoding.jsonEncoder().encode(installationOnServer) } catch { XCTFail("Should encode/decode. Error \(error)") expectation1.fulfill() @@ -1096,7 +1096,7 @@ class ParseInstallationTests: XCTestCase { // swiftlint:disable:this type_body_l let encoded: Data! do { - encoded = try installation.getEncoder(skipKeys: false).encode(installationOnServer) + encoded = try ParseCoding.jsonEncoder().encode(installationOnServer) } catch { XCTFail("Should encode/decode. Error \(error)") expectation1.fulfill() diff --git a/Tests/ParseSwiftTests/ParseObjectBatchTests.swift b/Tests/ParseSwiftTests/ParseObjectBatchTests.swift index 78861fd44..929799c3d 100755 --- a/Tests/ParseSwiftTests/ParseObjectBatchTests.swift +++ b/Tests/ParseSwiftTests/ParseObjectBatchTests.swift @@ -71,11 +71,11 @@ class ParseObjectBatchTests: XCTestCase { // swiftlint:disable:this type_body_le BatchResponseItem(success: scoreOnServer2, error: nil)] let encoded: Data! do { - encoded = try scoreOnServer.getEncoder(skipKeys: false).encode(response) + encoded = try scoreOnServer.getJSONEncoder().encode(response) //Get dates in correct format from ParseDecoding strategy - let encoded1 = try scoreOnServer.getEncoder(skipKeys: false).encode(scoreOnServer) + let encoded1 = try ParseCoding.jsonEncoder().encode(scoreOnServer) scoreOnServer = try scoreOnServer.getDecoder().decode(GameScore.self, from: encoded1) - let encoded2 = try scoreOnServer.getEncoder(skipKeys: false).encode(scoreOnServer2) + let encoded2 = try ParseCoding.jsonEncoder().encode(scoreOnServer2) scoreOnServer2 = try scoreOnServer.getDecoder().decode(GameScore.self, from: encoded2) } catch { @@ -203,7 +203,7 @@ class ParseObjectBatchTests: XCTestCase { // swiftlint:disable:this type_body_le MockURLProtocol.mockRequests { _ in do { - let encoded = try scoreOnServer.getEncoder(skipKeys: false).encode([scoreOnServer, scoreOnServer2]) + let encoded = try ParseCoding.jsonEncoder().encode([scoreOnServer, scoreOnServer2]) return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) } catch { return nil @@ -255,11 +255,11 @@ class ParseObjectBatchTests: XCTestCase { // swiftlint:disable:this type_body_le BatchResponseItem(success: scoreOnServer2, error: nil)] let encoded: Data! do { - encoded = try scoreOnServer.getEncoder(skipKeys: false).encode(response) + encoded = try ParseCoding.jsonEncoder().encode(response) //Get dates in correct format from ParseDecoding strategy - let encoded1 = try scoreOnServer.getEncoder(skipKeys: false).encode(scoreOnServer) + let encoded1 = try ParseCoding.jsonEncoder().encode(scoreOnServer) scoreOnServer = try scoreOnServer.getDecoder().decode(GameScore.self, from: encoded1) - let encoded2 = try scoreOnServer.getEncoder(skipKeys: false).encode(scoreOnServer2) + let encoded2 = try ParseCoding.jsonEncoder().encode(scoreOnServer2) scoreOnServer2 = try scoreOnServer.getDecoder().decode(GameScore.self, from: encoded2) } catch { @@ -397,7 +397,7 @@ class ParseObjectBatchTests: XCTestCase { // swiftlint:disable:this type_body_le MockURLProtocol.mockRequests { _ in do { - let encoded = try scoreOnServer.getEncoder(skipKeys: false).encode([scoreOnServer, scoreOnServer2]) + let encoded = try ParseCoding.jsonEncoder().encode([scoreOnServer, scoreOnServer2]) return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) } catch { return nil @@ -447,11 +447,11 @@ class ParseObjectBatchTests: XCTestCase { // swiftlint:disable:this type_body_le let encoded: Data! do { - encoded = try scoreOnServer.getEncoder(skipKeys: false).encode(response) + encoded = try ParseCoding.jsonEncoder().encode(response) //Get dates in correct format from ParseDecoding strategy - let encoded1 = try scoreOnServer.getEncoder(skipKeys: false).encode(scoreOnServer) + let encoded1 = try ParseCoding.jsonEncoder().encode(scoreOnServer) scoreOnServer = try scoreOnServer.getDecoder().decode(GameScore.self, from: encoded1) - let encoded2 = try scoreOnServer.getEncoder(skipKeys: false).encode(scoreOnServer2) + let encoded2 = try ParseCoding.jsonEncoder().encode(scoreOnServer2) scoreOnServer2 = try scoreOnServer.getDecoder().decode(GameScore.self, from: encoded2) } catch { @@ -702,11 +702,11 @@ class ParseObjectBatchTests: XCTestCase { // swiftlint:disable:this type_body_le BatchResponseItem(success: scoreOnServer2, error: nil)] let encoded: Data! do { - encoded = try scoreOnServer.getEncoder(skipKeys: false).encode(response) + encoded = try ParseCoding.jsonEncoder().encode(response) //Get dates in correct format from ParseDecoding strategy - let encoded1 = try scoreOnServer.getEncoder(skipKeys: false).encode(scoreOnServer) + let encoded1 = try ParseCoding.jsonEncoder().encode(scoreOnServer) scoreOnServer = try scoreOnServer.getDecoder().decode(GameScore.self, from: encoded1) - let encoded2 = try scoreOnServer.getEncoder(skipKeys: false).encode(scoreOnServer2) + let encoded2 = try ParseCoding.jsonEncoder().encode(scoreOnServer2) scoreOnServer2 = try scoreOnServer.getDecoder().decode(GameScore.self, from: encoded2) } catch { @@ -743,11 +743,11 @@ class ParseObjectBatchTests: XCTestCase { // swiftlint:disable:this type_body_le BatchResponseItem(success: scoreOnServer2, error: nil)] let encoded: Data! do { - encoded = try scoreOnServer.getEncoder(skipKeys: false).encode(response) + encoded = try ParseCoding.jsonEncoder().encode(response) //Get dates in correct format from ParseDecoding strategy - let encoded1 = try scoreOnServer.getEncoder(skipKeys: false).encode(scoreOnServer) + let encoded1 = try ParseCoding.jsonEncoder().encode(scoreOnServer) scoreOnServer = try scoreOnServer.getDecoder().decode(GameScore.self, from: encoded1) - let encoded2 = try scoreOnServer.getEncoder(skipKeys: false).encode(scoreOnServer2) + let encoded2 = try ParseCoding.jsonEncoder().encode(scoreOnServer2) scoreOnServer2 = try scoreOnServer.getDecoder().decode(GameScore.self, from: encoded2) } catch { @@ -913,11 +913,11 @@ class ParseObjectBatchTests: XCTestCase { // swiftlint:disable:this type_body_le let encoded: Data! do { - encoded = try scoreOnServer.getEncoder(skipKeys: false).encode(response) + encoded = try ParseCoding.jsonEncoder().encode(response) //Get dates in correct format from ParseDecoding strategy - let encoded1 = try scoreOnServer.getEncoder(skipKeys: false).encode(scoreOnServer) + let encoded1 = try ParseCoding.jsonEncoder().encode(scoreOnServer) scoreOnServer = try scoreOnServer.getDecoder().decode(GameScore.self, from: encoded1) - let encoded2 = try scoreOnServer.getEncoder(skipKeys: false).encode(scoreOnServer2) + let encoded2 = try ParseCoding.jsonEncoder().encode(scoreOnServer2) scoreOnServer2 = try scoreOnServer.getDecoder().decode(GameScore.self, from: encoded2) } catch { @@ -958,11 +958,11 @@ class ParseObjectBatchTests: XCTestCase { // swiftlint:disable:this type_body_le let encoded: Data! do { - encoded = try scoreOnServer.getEncoder(skipKeys: false).encode(response) + encoded = try ParseCoding.jsonEncoder().encode(response) //Get dates in correct format from ParseDecoding strategy - let encoded1 = try scoreOnServer.getEncoder(skipKeys: false).encode(scoreOnServer) + let encoded1 = try ParseCoding.jsonEncoder().encode(scoreOnServer) scoreOnServer = try scoreOnServer.getDecoder().decode(GameScore.self, from: encoded1) - let encoded2 = try scoreOnServer.getEncoder(skipKeys: false).encode(scoreOnServer2) + let encoded2 = try ParseCoding.jsonEncoder().encode(scoreOnServer2) scoreOnServer2 = try scoreOnServer.getDecoder().decode(GameScore.self, from: encoded2) } catch { @@ -997,11 +997,11 @@ class ParseObjectBatchTests: XCTestCase { // swiftlint:disable:this type_body_le let response = FindResult(results: [scoreOnServer, scoreOnServer2], count: 2) let encoded: Data! do { - encoded = try scoreOnServer.getEncoder(skipKeys: false).encode(response) + encoded = try ParseCoding.jsonEncoder().encode(response) //Get dates in correct format from ParseDecoding strategy - let encoded1 = try scoreOnServer.getEncoder(skipKeys: false).encode(scoreOnServer) + let encoded1 = try ParseCoding.jsonEncoder().encode(scoreOnServer) scoreOnServer = try scoreOnServer.getDecoder().decode(GameScore.self, from: encoded1) - let encoded2 = try scoreOnServer.getEncoder(skipKeys: false).encode(scoreOnServer2) + let encoded2 = try ParseCoding.jsonEncoder().encode(scoreOnServer2) scoreOnServer2 = try scoreOnServer.getDecoder().decode(GameScore.self, from: encoded2) } catch { @@ -1172,11 +1172,11 @@ class ParseObjectBatchTests: XCTestCase { // swiftlint:disable:this type_body_le let response = FindResult(results: [scoreOnServer, scoreOnServer2], count: 2) let encoded: Data! do { - encoded = try scoreOnServer.getEncoder(skipKeys: false).encode(response) + encoded = try ParseCoding.jsonEncoder().encode(response) //Get dates in correct format from ParseDecoding strategy - let encoded1 = try scoreOnServer.getEncoder(skipKeys: false).encode(scoreOnServer) + let encoded1 = try ParseCoding.jsonEncoder().encode(scoreOnServer) scoreOnServer = try scoreOnServer.getDecoder().decode(GameScore.self, from: encoded1) - let encoded2 = try scoreOnServer.getEncoder(skipKeys: false).encode(scoreOnServer2) + let encoded2 = try ParseCoding.jsonEncoder().encode(scoreOnServer2) scoreOnServer2 = try scoreOnServer.getDecoder().decode(GameScore.self, from: encoded2) } catch { @@ -1212,11 +1212,11 @@ class ParseObjectBatchTests: XCTestCase { // swiftlint:disable:this type_body_le let response = FindResult(results: [scoreOnServer, scoreOnServer2], count: 2) let encoded: Data! do { - encoded = try scoreOnServer.getEncoder(skipKeys: false).encode(response) + encoded = try ParseCoding.jsonEncoder().encode(response) //Get dates in correct format from ParseDecoding strategy - let encoded1 = try scoreOnServer.getEncoder(skipKeys: false).encode(scoreOnServer) + let encoded1 = try ParseCoding.jsonEncoder().encode(scoreOnServer) scoreOnServer = try scoreOnServer.getDecoder().decode(GameScore.self, from: encoded1) - let encoded2 = try scoreOnServer.getEncoder(skipKeys: false).encode(scoreOnServer2) + let encoded2 = try ParseCoding.jsonEncoder().encode(scoreOnServer2) scoreOnServer2 = try scoreOnServer.getDecoder().decode(GameScore.self, from: encoded2) } catch { @@ -1231,13 +1231,12 @@ class ParseObjectBatchTests: XCTestCase { // swiftlint:disable:this type_body_le } func testDeleteAll() { - let score = GameScore(score: 10) let response = [BatchResponseItem(success: true, error: nil), BatchResponseItem(success: true, error: nil)] let encoded: Data! do { - encoded = try score.getEncoder(skipKeys: false).encode(response) + encoded = try ParseCoding.jsonEncoder().encode(response) } catch { XCTFail("Should have encoded/decoded. Error \(error)") return @@ -1327,7 +1326,7 @@ class ParseObjectBatchTests: XCTestCase { // swiftlint:disable:this type_body_le BatchResponseItem(success: true, error: nil)] do { - let encoded = try score.getEncoder(skipKeys: false).encode(response) + let encoded = try ParseCoding.jsonEncoder().encode(response) MockURLProtocol.mockRequests { _ in return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) } diff --git a/Tests/ParseSwiftTests/ParseObjectTests.swift b/Tests/ParseSwiftTests/ParseObjectTests.swift index 9c1125c1c..07b95a632 100644 --- a/Tests/ParseSwiftTests/ParseObjectTests.swift +++ b/Tests/ParseSwiftTests/ParseObjectTests.swift @@ -217,7 +217,7 @@ class ParseObjectTests: XCTestCase { // swiftlint:disable:this type_body_length scoreOnServer.ACL = nil let encoded: Data! do { - encoded = try scoreOnServer.getEncoder(skipKeys: false).encode(scoreOnServer) + encoded = try ParseCoding.jsonEncoder().encode(scoreOnServer) //Get dates in correct format from ParseDecoding strategy scoreOnServer = try scoreOnServer.getDecoder().decode(GameScore.self, from: encoded) } catch { @@ -280,7 +280,7 @@ class ParseObjectTests: XCTestCase { // swiftlint:disable:this type_body_length scoreOnServer.ACL = nil let encoded: Data! do { - encoded = try scoreOnServer.getEncoder(skipKeys: false).encode(scoreOnServer) + encoded = try ParseCoding.jsonEncoder().encode(scoreOnServer) //Get dates in correct format from ParseDecoding strategy scoreOnServer = try scoreOnServer.getDecoder().decode(GameScore.self, from: encoded) } catch { @@ -387,7 +387,7 @@ class ParseObjectTests: XCTestCase { // swiftlint:disable:this type_body_length let encoded: Data! do { - encoded = try scoreOnServer.getEncoder(skipKeys: false).encode(scoreOnServer) + encoded = try ParseCoding.jsonEncoder().encode(scoreOnServer) //Get dates in correct format from ParseDecoding strategy scoreOnServer = try scoreOnServer.getDecoder().decode(GameScore.self, from: encoded) } catch { @@ -415,7 +415,7 @@ class ParseObjectTests: XCTestCase { // swiftlint:disable:this type_body_length scoreOnServer.ACL = nil let encoded: Data! do { - encoded = try scoreOnServer.getEncoder(skipKeys: false).encode(scoreOnServer) + encoded = try ParseCoding.jsonEncoder().encode(scoreOnServer) //Get dates in correct format from ParseDecoding strategy scoreOnServer = try scoreOnServer.getDecoder().decode(GameScore.self, from: encoded) } catch { @@ -471,7 +471,7 @@ class ParseObjectTests: XCTestCase { // swiftlint:disable:this type_body_length let encoded: Data! do { - encoded = try scoreOnServer.getEncoder(skipKeys: false).encode(scoreOnServer) + encoded = try ParseCoding.jsonEncoder().encode(scoreOnServer) //Get dates in correct format from ParseDecoding strategy scoreOnServer = try scoreOnServer.getDecoder().decode(GameScore.self, from: encoded) } catch { @@ -535,7 +535,7 @@ class ParseObjectTests: XCTestCase { // swiftlint:disable:this type_body_length let encoded: Data! do { - encoded = try scoreOnServer.getEncoder(skipKeys: false).encode(scoreOnServer) + encoded = try ParseCoding.jsonEncoder().encode(scoreOnServer) //Get dates in correct format from ParseDecoding strategy scoreOnServer = try scoreOnServer.getDecoder().decode(GameScore.self, from: encoded) } catch { @@ -655,7 +655,7 @@ class ParseObjectTests: XCTestCase { // swiftlint:disable:this type_body_length let encoded: Data! do { - encoded = try scoreOnServer.getEncoder(skipKeys: false).encode(scoreOnServer) + encoded = try ParseCoding.jsonEncoder().encode(scoreOnServer) //Get dates in correct format from ParseDecoding strategy scoreOnServer = try scoreOnServer.getDecoder().decode(GameScore.self, from: encoded) } catch { @@ -681,7 +681,7 @@ class ParseObjectTests: XCTestCase { // swiftlint:disable:this type_body_length scoreOnServer.ACL = nil let encoded: Data! do { - encoded = try scoreOnServer.getEncoder(skipKeys: false).encode(scoreOnServer) + encoded = try ParseCoding.jsonEncoder().encode(scoreOnServer) //Get dates in correct format from ParseDecoding strategy scoreOnServer = try scoreOnServer.getDecoder().decode(GameScore.self, from: encoded) } catch { @@ -759,7 +759,7 @@ class ParseObjectTests: XCTestCase { // swiftlint:disable:this type_body_length scoreOnServer.updatedAt = Date() let encoded: Data! do { - encoded = try scoreOnServer.getEncoder(skipKeys: false).encode(scoreOnServer) + encoded = try ParseCoding.jsonEncoder().encode(scoreOnServer) //Get dates in correct format from ParseDecoding strategy scoreOnServer = try scoreOnServer.getDecoder().decode(GameScore.self, from: encoded) } catch { @@ -786,7 +786,7 @@ class ParseObjectTests: XCTestCase { // swiftlint:disable:this type_body_length scoreOnServer.updatedAt = Date() let encoded: Data! do { - encoded = try scoreOnServer.getEncoder(skipKeys: false).encode(scoreOnServer) + encoded = try ParseCoding.jsonEncoder().encode(scoreOnServer) //Get dates in correct format from ParseDecoding strategy scoreOnServer = try scoreOnServer.getDecoder().decode(GameScore.self, from: encoded) } catch { @@ -1001,7 +1001,7 @@ class ParseObjectTests: XCTestCase { // swiftlint:disable:this type_body_length let pointer = scoreOnServer.toPointer() let encoded: Data! do { - encoded = try scoreOnServer.getEncoder(skipKeys: false).encode(pointer) + encoded = try ParseCoding.jsonEncoder().encode(pointer) } catch { XCTFail("Should encode/decode. Error \(error)") return @@ -1056,7 +1056,7 @@ class ParseObjectTests: XCTestCase { // swiftlint:disable:this type_body_length scoreOnServer.objectId = "yarr" let encoded: Data! do { - encoded = try scoreOnServer.getEncoder(skipKeys: false).encode(scoreOnServer) + encoded = try scoreOnServer.getEncoder().encode(scoreOnServer) //Get dates in correct format from ParseDecoding strategy scoreOnServer = try scoreOnServer.getDecoder().decode(GameScore.self, from: encoded) XCTFail("Should have thrown encode/decode error because child objects can't have the same objectId") diff --git a/Tests/ParseSwiftTests/ParseQueryTests.swift b/Tests/ParseSwiftTests/ParseQueryTests.swift index d8af0ff88..597252b32 100755 --- a/Tests/ParseSwiftTests/ParseQueryTests.swift +++ b/Tests/ParseSwiftTests/ParseQueryTests.swift @@ -180,7 +180,7 @@ class ParseQueryTests: XCTestCase { // swiftlint:disable:this type_body_length let results = FindResult(results: [scoreOnServer], count: 1) MockURLProtocol.mockRequests { _ in do { - let encoded = try scoreOnServer.getEncoder(skipKeys: false).encode(results) + let encoded = try ParseCoding.jsonEncoder().encode(results) return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) } catch { return nil @@ -207,7 +207,7 @@ class ParseQueryTests: XCTestCase { // swiftlint:disable:this type_body_length let query = GameScore.query("createdAt" > afterDate) let encodedJSON = try ParseCoding.jsonEncoder().encode(query) let decodedJSON = try ParseCoding.jsonDecoder().decode([String: AnyCodable].self, from: encodedJSON) - let encodedParse = try ParseCoding.parseEncoder().encode(query) + let encodedParse = try ParseCoding.jsonEncoder().encode(query) let decodedParse = try ParseCoding.jsonDecoder().decode([String: AnyCodable].self, from: encodedParse) guard let jsonSkipAny = decodedJSON["skip"], @@ -272,7 +272,7 @@ class ParseQueryTests: XCTestCase { // swiftlint:disable:this type_body_length let results = FindResult(results: [scoreOnServer], count: 1) MockURLProtocol.mockRequests { _ in do { - let encoded = try scoreOnServer.getEncoder(skipKeys: false).encode(results) + let encoded = try ParseCoding.jsonEncoder().encode(results) return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) } catch { return nil @@ -294,7 +294,7 @@ class ParseQueryTests: XCTestCase { // swiftlint:disable:this type_body_length let results = FindResult(results: [scoreOnServer], count: 1) MockURLProtocol.mockRequests { _ in do { - let encoded = try scoreOnServer.getEncoder(skipKeys: false).encode(results) + let encoded = try ParseCoding.jsonEncoder().encode(results) return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) } catch { return nil @@ -313,7 +313,7 @@ class ParseQueryTests: XCTestCase { // swiftlint:disable:this type_body_length let results = FindResult(results: [scoreOnServer], count: 1) MockURLProtocol.mockRequests { _ in do { - let encoded = try scoreOnServer.getEncoder(skipKeys: false).encode(results) + let encoded = try ParseCoding.jsonEncoder().encode(results) return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) } catch { return nil @@ -339,7 +339,7 @@ class ParseQueryTests: XCTestCase { // swiftlint:disable:this type_body_length let results = FindResult(results: [GameScore](), count: 0) MockURLProtocol.mockRequests { _ in do { - let encoded = try scoreOnServer.getEncoder(skipKeys: false).encode(results) + let encoded = try ParseCoding.jsonEncoder().encode(results) return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) } catch { return nil @@ -409,7 +409,7 @@ class ParseQueryTests: XCTestCase { // swiftlint:disable:this type_body_length let results = FindResult(results: [scoreOnServer], count: 1) MockURLProtocol.mockRequests { _ in do { - let encoded = try scoreOnServer.getEncoder(skipKeys: false).encode(results) + let encoded = try ParseCoding.jsonEncoder().encode(results) return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) } catch { return nil @@ -431,7 +431,7 @@ class ParseQueryTests: XCTestCase { // swiftlint:disable:this type_body_length let results = FindResult(results: [scoreOnServer], count: 1) MockURLProtocol.mockRequests { _ in do { - let encoded = try scoreOnServer.getEncoder(skipKeys: false).encode(results) + let encoded = try ParseCoding.jsonEncoder().encode(results) return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) } catch { return nil @@ -445,7 +445,7 @@ class ParseQueryTests: XCTestCase { // swiftlint:disable:this type_body_length let results = FindResult(results: [GameScore](), count: 0) MockURLProtocol.mockRequests { _ in do { - let encoded = try scoreOnServer.getEncoder(skipKeys: false).encode(results) + let encoded = try ParseCoding.jsonEncoder().encode(results) return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) } catch { return nil @@ -462,7 +462,7 @@ class ParseQueryTests: XCTestCase { // swiftlint:disable:this type_body_length let results = FindResult(results: [GameScore](), count: 0) MockURLProtocol.mockRequests { _ in do { - let encoded = try scoreOnServer.getEncoder(skipKeys: false).encode(results) + let encoded = try ParseCoding.jsonEncoder().encode(results) return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) } catch { return nil @@ -481,7 +481,7 @@ class ParseQueryTests: XCTestCase { // swiftlint:disable:this type_body_length let results = FindResult(results: [scoreOnServer], count: 1) MockURLProtocol.mockRequests { _ in do { - let encoded = try scoreOnServer.getEncoder(skipKeys: false).encode(results) + let encoded = try ParseCoding.jsonEncoder().encode(results) return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) } catch { return nil @@ -526,7 +526,7 @@ class ParseQueryTests: XCTestCase { // swiftlint:disable:this type_body_length let results = FindResult(results: [scoreOnServer], count: 1) MockURLProtocol.mockRequests { _ in do { - let encoded = try scoreOnServer.getEncoder(skipKeys: false).encode(results) + let encoded = try ParseCoding.jsonEncoder().encode(results) return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) } catch { return nil @@ -548,7 +548,7 @@ class ParseQueryTests: XCTestCase { // swiftlint:disable:this type_body_length let results = FindResult(results: [scoreOnServer], count: 1) MockURLProtocol.mockRequests { _ in do { - let encoded = try scoreOnServer.getEncoder(skipKeys: false).encode(results) + let encoded = try ParseCoding.jsonEncoder().encode(results) return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) } catch { return nil @@ -567,7 +567,7 @@ class ParseQueryTests: XCTestCase { // swiftlint:disable:this type_body_length let queryWhere = query.`where` do { - let encoded = try ParseCoding.parseEncoder().encode(queryWhere) + let encoded = try ParseCoding.jsonEncoder().encode(queryWhere) let decodedDictionary = try JSONDecoder().decode([String: AnyCodable].self, from: encoded) XCTAssertEqual(expected.keys, decodedDictionary.keys) @@ -593,7 +593,7 @@ class ParseQueryTests: XCTestCase { // swiftlint:disable:this type_body_length let queryWhere = query.`where` do { - let encoded = try ParseCoding.parseEncoder().encode(queryWhere) + let encoded = try ParseCoding.jsonEncoder().encode(queryWhere) let decodedDictionary = try JSONDecoder().decode([String: AnyCodable].self, from: encoded) XCTAssertEqual(expected.keys, decodedDictionary.keys) @@ -618,7 +618,7 @@ class ParseQueryTests: XCTestCase { // swiftlint:disable:this type_body_length let queryWhere = query.`where` do { - let encoded = try ParseCoding.parseEncoder().encode(queryWhere) + let encoded = try ParseCoding.jsonEncoder().encode(queryWhere) let decodedDictionary = try JSONDecoder().decode([String: AnyCodable].self, from: encoded) XCTAssertEqual(expected.keys, decodedDictionary.keys) @@ -643,7 +643,7 @@ class ParseQueryTests: XCTestCase { // swiftlint:disable:this type_body_length let queryWhere = query.`where` do { - let encoded = try ParseCoding.parseEncoder().encode(queryWhere) + let encoded = try ParseCoding.jsonEncoder().encode(queryWhere) let decodedDictionary = try JSONDecoder().decode([String: AnyCodable].self, from: encoded) XCTAssertEqual(expected.keys, decodedDictionary.keys) @@ -668,7 +668,7 @@ class ParseQueryTests: XCTestCase { // swiftlint:disable:this type_body_length let queryWhere = query.`where` do { - let encoded = try ParseCoding.parseEncoder().encode(queryWhere) + let encoded = try ParseCoding.jsonEncoder().encode(queryWhere) let decodedDictionary = try JSONDecoder().decode([String: AnyCodable].self, from: encoded) XCTAssertEqual(expected.keys, decodedDictionary.keys) @@ -693,7 +693,7 @@ class ParseQueryTests: XCTestCase { // swiftlint:disable:this type_body_length let queryWhere = query.`where` do { - let encoded = try ParseCoding.parseEncoder().encode(queryWhere) + let encoded = try ParseCoding.jsonEncoder().encode(queryWhere) let decodedDictionary = try JSONDecoder().decode([String: AnyCodable].self, from: encoded) XCTAssertEqual(expected.keys, decodedDictionary.keys) @@ -718,7 +718,7 @@ class ParseQueryTests: XCTestCase { // swiftlint:disable:this type_body_length let queryWhere = query.`where` do { - let encoded = try ParseCoding.parseEncoder().encode(queryWhere) + let encoded = try ParseCoding.jsonEncoder().encode(queryWhere) let decodedDictionary = try JSONDecoder().decode([String: AnyCodable].self, from: encoded) XCTAssertEqual(expected.keys, decodedDictionary.keys) @@ -743,7 +743,7 @@ class ParseQueryTests: XCTestCase { // swiftlint:disable:this type_body_length let queryWhere = query.`where` do { - let encoded = try ParseCoding.parseEncoder().encode(queryWhere) + let encoded = try ParseCoding.jsonEncoder().encode(queryWhere) let decodedDictionary = try JSONDecoder().decode([String: AnyCodable].self, from: encoded) XCTAssertEqual(expected.keys, decodedDictionary.keys) @@ -769,7 +769,7 @@ class ParseQueryTests: XCTestCase { // swiftlint:disable:this type_body_length let queryWhere = query.`where` do { - let encoded = try ParseCoding.parseEncoder().encode(queryWhere) + let encoded = try ParseCoding.jsonEncoder().encode(queryWhere) let decodedDictionary = try JSONDecoder().decode([String: AnyCodable].self, from: encoded) XCTAssertEqual(expected.keys, decodedDictionary.keys) @@ -795,7 +795,7 @@ class ParseQueryTests: XCTestCase { // swiftlint:disable:this type_body_length let queryWhere = query.`where` do { - let encoded = try ParseCoding.parseEncoder().encode(queryWhere) + let encoded = try ParseCoding.jsonEncoder().encode(queryWhere) let decodedDictionary = try JSONDecoder().decode([String: AnyCodable].self, from: encoded) XCTAssertEqual(expected.keys, decodedDictionary.keys) @@ -824,7 +824,7 @@ class ParseQueryTests: XCTestCase { // swiftlint:disable:this type_body_length let queryWhere = query.`where` do { - let encoded = try ParseCoding.parseEncoder().encode(queryWhere) + let encoded = try ParseCoding.jsonEncoder().encode(queryWhere) let decodedDictionary = try JSONDecoder().decode([String: AnyCodable].self, from: encoded) XCTAssertEqual(expected.keys, decodedDictionary.keys) @@ -850,7 +850,7 @@ class ParseQueryTests: XCTestCase { // swiftlint:disable:this type_body_length let queryWhere = query.`where` do { - let encoded = try ParseCoding.parseEncoder().encode(queryWhere) + let encoded = try ParseCoding.jsonEncoder().encode(queryWhere) let decodedDictionary = try JSONDecoder().decode([String: AnyCodable].self, from: encoded) XCTAssertEqual(expected.keys, decodedDictionary.keys) @@ -876,7 +876,7 @@ class ParseQueryTests: XCTestCase { // swiftlint:disable:this type_body_length let queryWhere = query.`where` do { - let encoded = try ParseCoding.parseEncoder().encode(queryWhere) + let encoded = try ParseCoding.jsonEncoder().encode(queryWhere) let decodedDictionary = try JSONDecoder().decode([String: AnyCodable].self, from: encoded) XCTAssertEqual(expected.keys, decodedDictionary.keys) @@ -902,7 +902,7 @@ class ParseQueryTests: XCTestCase { // swiftlint:disable:this type_body_length let queryWhere = query.`where` do { - let encoded = try ParseCoding.parseEncoder().encode(queryWhere) + let encoded = try ParseCoding.jsonEncoder().encode(queryWhere) let decodedDictionary = try JSONDecoder().decode([String: AnyCodable].self, from: encoded) XCTAssertEqual(expected.keys, decodedDictionary.keys) @@ -933,7 +933,7 @@ class ParseQueryTests: XCTestCase { // swiftlint:disable:this type_body_length let queryWhere = query.`where` do { - let encoded = try ParseCoding.parseEncoder().encode(queryWhere) + let encoded = try ParseCoding.jsonEncoder().encode(queryWhere) let decodedDictionary = try JSONDecoder().decode([String: AnyCodable].self, from: encoded) XCTAssertEqual(expected.keys, decodedDictionary.keys) @@ -964,7 +964,7 @@ class ParseQueryTests: XCTestCase { // swiftlint:disable:this type_body_length let queryWhere = query.`where` do { - let encoded = try ParseCoding.parseEncoder().encode(queryWhere) + let encoded = try ParseCoding.jsonEncoder().encode(queryWhere) let decodedDictionary = try JSONDecoder().decode([String: AnyCodable].self, from: encoded) XCTAssertEqual(expected.keys, decodedDictionary.keys) @@ -996,7 +996,7 @@ class ParseQueryTests: XCTestCase { // swiftlint:disable:this type_body_length let queryWhere = query.`where` do { - let encoded = try ParseCoding.parseEncoder().encode(queryWhere) + let encoded = try ParseCoding.jsonEncoder().encode(queryWhere) let decodedDictionary = try JSONDecoder().decode([String: AnyCodable].self, from: encoded) XCTAssertEqual(expected.keys, decodedDictionary.keys) @@ -1042,7 +1042,7 @@ class ParseQueryTests: XCTestCase { // swiftlint:disable:this type_body_length let queryWhere = query.`where` do { - let encoded = try ParseCoding.parseEncoder().encode(queryWhere) + let encoded = try ParseCoding.jsonEncoder().encode(queryWhere) let decodedDictionary = try JSONDecoder().decode([String: AnyCodable].self, from: encoded) XCTAssertEqual(expected.keys, decodedDictionary.keys) @@ -1086,7 +1086,7 @@ class ParseQueryTests: XCTestCase { // swiftlint:disable:this type_body_length let queryWhere = query.`where` do { - let encoded = try ParseCoding.parseEncoder().encode(queryWhere) + let encoded = try ParseCoding.jsonEncoder().encode(queryWhere) let decodedDictionary = try JSONDecoder().decode([String: AnyCodable].self, from: encoded) XCTAssertEqual(expected.keys, decodedDictionary.keys) @@ -1125,7 +1125,7 @@ class ParseQueryTests: XCTestCase { // swiftlint:disable:this type_body_length let queryWhere = query.`where` do { - let encoded = try ParseCoding.parseEncoder().encode(queryWhere) + let encoded = try ParseCoding.jsonEncoder().encode(queryWhere) let decodedDictionary = try JSONDecoder().decode([String: AnyCodable].self, from: encoded) XCTAssertEqual(expected.keys, decodedDictionary.keys) @@ -1160,7 +1160,7 @@ class ParseQueryTests: XCTestCase { // swiftlint:disable:this type_body_length let queryWhere = query.`where` do { - let encoded = try ParseCoding.parseEncoder().encode(queryWhere) + let encoded = try ParseCoding.jsonEncoder().encode(queryWhere) let decodedDictionary = try JSONDecoder().decode([String: AnyCodable].self, from: encoded) XCTAssertEqual(expected.keys, decodedDictionary.keys) @@ -1186,7 +1186,7 @@ class ParseQueryTests: XCTestCase { // swiftlint:disable:this type_body_length let queryWhere = query.`where` do { - let encoded = try ParseCoding.parseEncoder().encode(queryWhere) + let encoded = try ParseCoding.jsonEncoder().encode(queryWhere) let decodedDictionary = try JSONDecoder().decode([String: AnyCodable].self, from: encoded) XCTAssertEqual(expected.keys, decodedDictionary.keys) @@ -1212,7 +1212,7 @@ class ParseQueryTests: XCTestCase { // swiftlint:disable:this type_body_length let queryWhere = query.`where` do { - let encoded = try ParseCoding.parseEncoder().encode(queryWhere) + let encoded = try ParseCoding.jsonEncoder().encode(queryWhere) let decodedDictionary = try JSONDecoder().decode([String: AnyCodable].self, from: encoded) XCTAssertEqual(expected.keys, decodedDictionary.keys) @@ -1242,7 +1242,7 @@ class ParseQueryTests: XCTestCase { // swiftlint:disable:this type_body_length let queryWhere = query.`where` do { - let encoded = try ParseCoding.parseEncoder().encode(queryWhere) + let encoded = try ParseCoding.jsonEncoder().encode(queryWhere) let decodedDictionary = try JSONDecoder().decode([String: AnyCodable].self, from: encoded) XCTAssertEqual(expected.keys, decodedDictionary.keys) @@ -1280,7 +1280,7 @@ class ParseQueryTests: XCTestCase { // swiftlint:disable:this type_body_length let queryWhere = query.`where` do { - let encoded = try ParseCoding.parseEncoder().encode(queryWhere) + let encoded = try ParseCoding.jsonEncoder().encode(queryWhere) let decodedDictionary = try JSONDecoder().decode([String: AnyCodable].self, from: encoded) XCTAssertEqual(expected.keys, decodedDictionary.keys) @@ -1321,7 +1321,7 @@ class ParseQueryTests: XCTestCase { // swiftlint:disable:this type_body_length let queryWhere = query.`where` do { - let encoded = try ParseCoding.parseEncoder().encode(queryWhere) + let encoded = try ParseCoding.jsonEncoder().encode(queryWhere) let decodedDictionary = try JSONDecoder().decode([String: AnyCodable].self, from: encoded) XCTAssertEqual(expected.keys, decodedDictionary.keys) @@ -1367,7 +1367,7 @@ class ParseQueryTests: XCTestCase { // swiftlint:disable:this type_body_length let queryWhere = query.`where` do { - let encoded = try ParseCoding.parseEncoder().encode(queryWhere) + let encoded = try ParseCoding.jsonEncoder().encode(queryWhere) let decodedDictionary = try JSONDecoder().decode([String: AnyCodable].self, from: encoded) XCTAssertEqual(expected.keys, decodedDictionary.keys) @@ -1413,7 +1413,7 @@ class ParseQueryTests: XCTestCase { // swiftlint:disable:this type_body_length let queryWhere = query.`where` do { - let encoded = try ParseCoding.parseEncoder().encode(queryWhere) + let encoded = try ParseCoding.jsonEncoder().encode(queryWhere) let decodedDictionary = try JSONDecoder().decode([String: AnyCodable].self, from: encoded) XCTAssertEqual(expected.keys, decodedDictionary.keys) @@ -1463,7 +1463,7 @@ class ParseQueryTests: XCTestCase { // swiftlint:disable:this type_body_length let queryWhere = query.`where` do { - let encoded = try ParseCoding.parseEncoder().encode(queryWhere) + let encoded = try ParseCoding.jsonEncoder().encode(queryWhere) let decodedDictionary = try JSONDecoder().decode([String: AnyCodable].self, from: encoded) XCTAssertEqual(expected.keys, decodedDictionary.keys) @@ -1521,7 +1521,7 @@ class ParseQueryTests: XCTestCase { // swiftlint:disable:this type_body_length let queryWhere = query.`where` do { - let encoded = try ParseCoding.parseEncoder().encode(queryWhere) + let encoded = try ParseCoding.jsonEncoder().encode(queryWhere) let decodedDictionary = try JSONDecoder().decode([String: AnyCodable].self, from: encoded) XCTAssertEqual(expected.keys, decodedDictionary.keys) @@ -1583,7 +1583,7 @@ class ParseQueryTests: XCTestCase { // swiftlint:disable:this type_body_length let queryWhere = query.`where` do { - let encoded = try ParseCoding.parseEncoder().encode(queryWhere) + let encoded = try ParseCoding.jsonEncoder().encode(queryWhere) let decodedDictionary = try JSONDecoder().decode([String: AnyCodable].self, from: encoded) XCTAssertEqual(expected.keys, decodedDictionary.keys) diff --git a/Tests/ParseSwiftTests/ParseUserTests.swift b/Tests/ParseSwiftTests/ParseUserTests.swift index 743ede38e..ba6f03be5 100644 --- a/Tests/ParseSwiftTests/ParseUserTests.swift +++ b/Tests/ParseSwiftTests/ParseUserTests.swift @@ -716,7 +716,6 @@ class ParseUserTests: XCTestCase { // swiftlint:disable:this type_body_length XCTAssertNil(command.params) XCTAssertEqual(command.body?.username, body.username) XCTAssertEqual(command.body?.password, body.password) - XCTAssertNotNil(command.data) } func testUserSignUp() { @@ -827,7 +826,6 @@ class ParseUserTests: XCTestCase { // swiftlint:disable:this type_body_length XCTAssertEqual(command.method, API.Method.GET) XCTAssertEqual(command.params, params) XCTAssertNil(command.body) - XCTAssertNil(command.data) } func testLogin() { @@ -935,7 +933,6 @@ class ParseUserTests: XCTestCase { // swiftlint:disable:this type_body_length XCTAssertEqual(command.method, API.Method.POST) XCTAssertNil(command.params) XCTAssertNil(command.body) - XCTAssertNil(command.data) } func testLogout() { @@ -1002,7 +999,6 @@ class ParseUserTests: XCTestCase { // swiftlint:disable:this type_body_length XCTAssertEqual(command.method, API.Method.POST) XCTAssertNil(command.params) XCTAssertEqual(command.body?.email, body.email) - XCTAssertNotNil(command.data) } func testPasswordReset() { @@ -1149,9 +1145,9 @@ class ParseUserTests: XCTestCase { // swiftlint:disable:this type_body_length let encoded: Data! do { - encoded = try user.getEncoder(skipKeys: false).encode(userOnServer) + encoded = try ParseCoding.jsonEncoder().encode(userOnServer) //Get dates in correct format from ParseDecoding strategy - let encoded1 = try user.getEncoder(skipKeys: false).encode(user) + let encoded1 = try ParseCoding.jsonEncoder().encode(user) user = try user.getDecoder().decode(User.self, from: encoded1) } catch { XCTFail("Should encode/decode. Error \(error)") @@ -1235,9 +1231,9 @@ class ParseUserTests: XCTestCase { // swiftlint:disable:this type_body_length let encoded: Data! do { - encoded = try user.getEncoder(skipKeys: false).encode(userOnServer) + encoded = try ParseCoding.jsonEncoder().encode(userOnServer) //Get dates in correct format from ParseDecoding strategy - let encoded1 = try user.getEncoder(skipKeys: false).encode(user) + let encoded1 = try ParseCoding.jsonEncoder().encode(user) user = try user.getDecoder().decode(User.self, from: encoded1) } catch { XCTFail("Should encode/decode. Error \(error)") @@ -1324,9 +1320,9 @@ class ParseUserTests: XCTestCase { // swiftlint:disable:this type_body_length let encoded: Data! do { - encoded = try user.getEncoder(skipKeys: false).encode(userOnServer) + encoded = try ParseCoding.jsonEncoder().encode(userOnServer) //Get dates in correct format from ParseDecoding strategy - let encoded1 = try user.getEncoder(skipKeys: false).encode(user) + let encoded1 = try ParseCoding.jsonEncoder().encode(user) user = try user.getDecoder().decode(User.self, from: encoded1) } catch { XCTFail("Should encode/decode. Error \(error)") @@ -1410,9 +1406,9 @@ class ParseUserTests: XCTestCase { // swiftlint:disable:this type_body_length let encoded: Data! do { - encoded = try user.getEncoder(skipKeys: false).encode(userOnServer) + encoded = try ParseCoding.jsonEncoder().encode(userOnServer) //Get dates in correct format from ParseDecoding strategy - let encoded1 = try user.getEncoder(skipKeys: false).encode(user) + let encoded1 = try ParseCoding.jsonEncoder().encode(user) user = try user.getDecoder().decode(User.self, from: encoded1) } catch { XCTFail("Should encode/decode. Error \(error)") @@ -1496,7 +1492,7 @@ class ParseUserTests: XCTestCase { // swiftlint:disable:this type_body_length let encoded: Data! do { - encoded = try user.getEncoder(skipKeys: false).encode(userOnServer) + encoded = try ParseCoding.jsonEncoder().encode(userOnServer) } catch { XCTFail("Should encode/decode. Error \(error)") expectation1.fulfill() @@ -1541,7 +1537,7 @@ class ParseUserTests: XCTestCase { // swiftlint:disable:this type_body_length let encoded: Data! do { - encoded = try user.getEncoder(skipKeys: false).encode(userOnServer) + encoded = try ParseCoding.jsonEncoder().encode(userOnServer) } catch { XCTFail("Should encode/decode. Error \(error)") expectation1.fulfill() From 4a5822abd6e8e5fe1df6f72cda243e41ba895b51 Mon Sep 17 00:00:00 2001 From: Corey's iMac Date: Tue, 29 Dec 2020 21:17:31 -0500 Subject: [PATCH 03/44] Add results response for Cloud Code --- .../Contents.swift | 2 + ParseSwift.playground/contents.xcplayground | 3 +- Sources/ParseSwift/API/Responses.swift | 5 ++ .../ParseSwift/Parse Types/ParseCloud.swift | 59 ++++++++------- Tests/ParseSwiftTests/ParseCloudTests.swift | 72 +++++++++++++------ 5 files changed, 95 insertions(+), 46 deletions(-) diff --git a/ParseSwift.playground/Pages/10 - Cloud Code.xcplaygroundpage/Contents.swift b/ParseSwift.playground/Pages/10 - Cloud Code.xcplaygroundpage/Contents.swift index 8f94ebfd6..d644f8a80 100644 --- a/ParseSwift.playground/Pages/10 - Cloud Code.xcplaygroundpage/Contents.swift +++ b/ParseSwift.playground/Pages/10 - Cloud Code.xcplaygroundpage/Contents.swift @@ -33,4 +33,6 @@ cloud.callFunction { result in } } +//: Jobs can be run the same way by using the method `callJob()` + //: [Next](@next) diff --git a/ParseSwift.playground/contents.xcplayground b/ParseSwift.playground/contents.xcplayground index 18a68aa71..3db5401e4 100644 --- a/ParseSwift.playground/contents.xcplayground +++ b/ParseSwift.playground/contents.xcplayground @@ -9,6 +9,7 @@ + - + \ No newline at end of file diff --git a/Sources/ParseSwift/API/Responses.swift b/Sources/ParseSwift/API/Responses.swift index 04c073d60..e9f90c349 100644 --- a/Sources/ParseSwift/API/Responses.swift +++ b/Sources/ParseSwift/API/Responses.swift @@ -76,3 +76,8 @@ internal struct LoginSignupResponse: Codable { let sessionToken: String var updatedAt: Date? } + +// MARK: AnyResultsResponse +internal struct AnyResultsResponse: Codable { + let result: AnyCodable? +} diff --git a/Sources/ParseSwift/Parse Types/ParseCloud.swift b/Sources/ParseSwift/Parse Types/ParseCloud.swift index e9ada8e12..f29b0724f 100644 --- a/Sources/ParseSwift/Parse Types/ParseCloud.swift +++ b/Sources/ParseSwift/Parse Types/ParseCloud.swift @@ -15,7 +15,7 @@ import Foundation */ public protocol ParseCloud: Encodable, CustomDebugStringConvertible { /** - The name of the function or job. + The name of the function or job. */ var functionJobName: String { get set } @@ -23,36 +23,41 @@ public protocol ParseCloud: Encodable, CustomDebugStringConvertible { // MARK: Functions extension ParseCloud { - public typealias AnyResultType = [String: AnyCodable] /** - *Synchronously* Calls a Cloud Code function and returns a result of it's execution. - * - parameter options: A set of header options sent to the server. Defaults to an empty set. + Calls *synchronously* a Cloud Code function and returns a result of it's execution. + - parameter options: A set of header options sent to the server. Defaults to an empty set. + - returns: Returns a JSON response of `AnyCodable` type. */ - public func callFunction(options: API.Options = []) throws -> AnyResultType { + public func callFunction(options: API.Options = []) throws -> AnyCodable { try callFunctionCommand().execute(options: options) } /** - *Asynchronously* Calls a Cloud Code function and returns a result of it's execution. - * - 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 logging out, completes or fails. + Calls *asynchronously* a Cloud Code function and returns a result of it's execution. + - 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 logging out, completes or fails. + It should have the following argument signature: `(Result, ParseError>)`. */ public func callFunction(options: API.Options = [], callbackQueue: DispatchQueue = .main, - completion: @escaping (Result) -> Void) { + completion: @escaping (Result) -> Void) { callFunctionCommand() .executeAsync(options: options, callbackQueue: callbackQueue, completion: completion) } - internal func callFunctionCommand() -> API.Command { + internal func callFunctionCommand() -> API.Command { return API.Command(method: .POST, path: .functions(name: functionJobName), - body: self) { (data) -> AnyResultType in - try ParseCoding.jsonDecoder().decode(AnyResultType.self, from: data) + body: self) { (data) -> AnyCodable in + let response = try ParseCoding.jsonDecoder().decode(AnyResultsResponse.self, from: data) + guard let result = response.result else { + return AnyCodable() + } + return result } } } @@ -60,32 +65,38 @@ extension ParseCloud { // MARK: Jobs extension ParseCloud { /** - *Synchronously* Calls a Cloud Code job and returns a result of it's execution. - * - parameter options: A set of header options sent to the server. Defaults to an empty set. + Calls *synchronously* a Cloud Code job and returns a result of it's execution. + - parameter options: A set of header options sent to the server. Defaults to an empty set. + - returns: Returns a JSON response of `AnyCodable` type. */ - public func callJob(options: API.Options = []) throws -> AnyResultType { + public func callJob(options: API.Options = []) throws -> AnyCodable { try callJobCommand().execute(options: options) } /** - *Asynchronously* Calls a Cloud Code job and returns a result of it's execution. - * - 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 logging out, completes or fails. + Calls *asynchronously* a Cloud Code job and returns a result of it's execution. + - 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 logging out, completes or fails. + It should have the following argument signature: `(Result, ParseError>)`. */ public func callJob(options: API.Options = [], callbackQueue: DispatchQueue = .main, - completion: @escaping (Result) -> Void) { + completion: @escaping (Result) -> Void) { callJobCommand() .executeAsync(options: options, callbackQueue: callbackQueue, completion: completion) } - internal func callJobCommand() -> API.Command { + internal func callJobCommand() -> API.Command { return API.Command(method: .POST, path: .jobs(name: functionJobName), - body: self) { (data) -> AnyResultType in - try ParseCoding.jsonDecoder().decode(AnyResultType.self, from: data) + body: self) { (data) -> AnyCodable in + let response = try ParseCoding.jsonDecoder().decode(AnyResultsResponse.self, from: data) + guard let result = response.result else { + return AnyCodable() + } + return result } } } diff --git a/Tests/ParseSwiftTests/ParseCloudTests.swift b/Tests/ParseSwiftTests/ParseCloudTests.swift index 5c5c91757..467df7324 100644 --- a/Tests/ParseSwiftTests/ParseCloudTests.swift +++ b/Tests/ParseSwiftTests/ParseCloudTests.swift @@ -10,7 +10,7 @@ import Foundation import XCTest @testable import ParseSwift -class ParseCloudTests: XCTestCase { +class ParseCloudTests: XCTestCase { // swiftlint:disable:this type_body_length struct Cloud: ParseCloud { // Those are required for Object @@ -103,7 +103,7 @@ class ParseCloudTests: XCTestCase { } func testFunction() { - let response = [String: AnyCodable]() + let response = AnyResultsResponse(result: nil) MockURLProtocol.mockRequests { _ in do { @@ -116,18 +116,21 @@ class ParseCloudTests: XCTestCase { do { let cloud = Cloud(functionJobName: "test") let functionResponse = try cloud.callFunction() - XCTAssertEqual(functionResponse, response) + XCTAssertEqual(functionResponse, AnyCodable()) } catch { XCTFail(error.localizedDescription) } } func testFunction2() { - let response: [String: AnyCodable] = ["hello": "world"] + var result: AnyCodable = ["hello": "world"] + let response = AnyResultsResponse(result: result) MockURLProtocol.mockRequests { _ in do { let encoded = try ParseCoding.jsonEncoder().encode(response) + let encodedResult = try ParseCoding.jsonEncoder().encode(result) + result = try ParseCoding.jsonDecoder().decode(AnyCodable.self, from: encodedResult) return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) } catch { return nil @@ -136,13 +139,17 @@ class ParseCloudTests: XCTestCase { do { let cloud = Cloud(functionJobName: "test") let functionResponse = try cloud.callFunction() - XCTAssertEqual(functionResponse, response) + guard let resultAsDictionary = functionResponse.value as? [String: String] else { + XCTFail("Should have casted result to dictionary") + return + } + XCTAssertEqual(resultAsDictionary, ["hello": "world"]) } catch { XCTFail(error.localizedDescription) } } - func functionAsync(serverResponse: [String: AnyCodable], callbackQueue: DispatchQueue) { + func functionAsync(serverResponse: AnyCodable, callbackQueue: DispatchQueue) { let expectation1 = XCTestExpectation(description: "Logout user1") let cloud = Cloud(functionJobName: "test") @@ -151,7 +158,15 @@ class ParseCloudTests: XCTestCase { switch result { case .success(let response): - XCTAssertEqual(response, serverResponse) + if serverResponse == AnyCodable() { + XCTAssertEqual(response, serverResponse) + } else { + guard let resultAsDictionary = serverResponse.value as? [String: String] else { + XCTFail("Should have casted result to dictionary") + return + } + XCTAssertEqual(resultAsDictionary, ["hello": "world"]) + } case .failure(let error): XCTFail(error.localizedDescription) } @@ -161,7 +176,7 @@ class ParseCloudTests: XCTestCase { } func testFunctionMainQueue() { - let response = [String: AnyCodable]() + let response = AnyResultsResponse(result: nil) MockURLProtocol.mockRequests { _ in do { @@ -172,11 +187,12 @@ class ParseCloudTests: XCTestCase { } } - self.functionAsync(serverResponse: response, callbackQueue: .main) + self.functionAsync(serverResponse: AnyCodable(), callbackQueue: .main) } func testFunctionMainQueue2() { - let response: [String: AnyCodable] = ["hello": "world"] + let result: AnyCodable = ["hello": "world"] + let response = AnyResultsResponse(result: result) MockURLProtocol.mockRequests { _ in do { @@ -187,7 +203,7 @@ class ParseCloudTests: XCTestCase { } } - self.functionAsync(serverResponse: response, callbackQueue: .main) + self.functionAsync(serverResponse: result, callbackQueue: .main) } func testCallJobCommand() throws { @@ -212,7 +228,7 @@ class ParseCloudTests: XCTestCase { } func testJob() { - let response = [String: AnyCodable]() + let response = AnyResultsResponse(result: nil) MockURLProtocol.mockRequests { _ in do { @@ -225,14 +241,15 @@ class ParseCloudTests: XCTestCase { do { let cloud = Cloud(functionJobName: "test") let functionResponse = try cloud.callJob() - XCTAssertEqual(functionResponse, response) + XCTAssertEqual(functionResponse, AnyCodable()) } catch { XCTFail(error.localizedDescription) } } func testJob2() { - let response: [String: AnyCodable] = ["hello": "world"] + let result: AnyCodable = ["hello": "world"] + let response = AnyResultsResponse(result: result) MockURLProtocol.mockRequests { _ in do { @@ -245,13 +262,17 @@ class ParseCloudTests: XCTestCase { do { let cloud = Cloud(functionJobName: "test") let functionResponse = try cloud.callJob() - XCTAssertEqual(functionResponse, response) + guard let resultAsDictionary = functionResponse.value as? [String: String] else { + XCTFail("Should have casted result to dictionary") + return + } + XCTAssertEqual(resultAsDictionary, ["hello": "world"]) } catch { XCTFail(error.localizedDescription) } } - func jobAsync(serverResponse: [String: AnyCodable], callbackQueue: DispatchQueue) { + func jobAsync(serverResponse: AnyCodable, callbackQueue: DispatchQueue) { let expectation1 = XCTestExpectation(description: "Logout user1") let cloud = Cloud(functionJobName: "test") @@ -260,7 +281,15 @@ class ParseCloudTests: XCTestCase { switch result { case .success(let response): - XCTAssertEqual(response, serverResponse) + if serverResponse == AnyCodable() { + XCTAssertEqual(response, serverResponse) + } else { + guard let resultAsDictionary = serverResponse.value as? [String: String] else { + XCTFail("Should have casted result to dictionary") + return + } + XCTAssertEqual(resultAsDictionary, ["hello": "world"]) + } case .failure(let error): XCTFail(error.localizedDescription) } @@ -270,7 +299,7 @@ class ParseCloudTests: XCTestCase { } func testJobMainQueue() { - let response = [String: AnyCodable]() + let response = AnyResultsResponse(result: nil) MockURLProtocol.mockRequests { _ in do { @@ -281,11 +310,12 @@ class ParseCloudTests: XCTestCase { } } - self.jobAsync(serverResponse: response, callbackQueue: .main) + self.jobAsync(serverResponse: AnyCodable(), callbackQueue: .main) } func testJobMainQueue2() { - let response: [String: AnyCodable] = ["hello": "world"] + let result: AnyCodable = ["hello": "world"] + let response = AnyResultsResponse(result: result) MockURLProtocol.mockRequests { _ in do { @@ -296,6 +326,6 @@ class ParseCloudTests: XCTestCase { } } - self.jobAsync(serverResponse: response, callbackQueue: .main) + self.jobAsync(serverResponse: result, callbackQueue: .main) } } From 9552ae19a7ff09e80c7179f84c284db5f4868e25 Mon Sep 17 00:00:00 2001 From: Corey's iMac Date: Tue, 29 Dec 2020 23:34:40 -0500 Subject: [PATCH 04/44] Clean up docs --- .../Object Protocols/ParseUser.swift | 18 ++++++++---------- Sources/ParseSwift/Parse Types/ParseACL.swift | 2 +- .../ParseSwift/Parse Types/ParseCloud.swift | 6 +++--- .../ParseSwift/Parse Types/ParseGeoPoint.swift | 2 +- Tests/ParseSwiftTests/ParseEncoderTests.swift | 8 ++++---- .../ParseObjectBatchTests.swift | 1 - Tests/ParseSwiftTests/ParseQueryTests.swift | 2 +- 7 files changed, 18 insertions(+), 21 deletions(-) diff --git a/Sources/ParseSwift/Object Protocols/ParseUser.swift b/Sources/ParseSwift/Object Protocols/ParseUser.swift index 55fadd31e..f818b3a1f 100644 --- a/Sources/ParseSwift/Object Protocols/ParseUser.swift +++ b/Sources/ParseSwift/Object Protocols/ParseUser.swift @@ -213,26 +213,24 @@ extension ParseUser { extension ParseUser { /** - *Synchronously* requests a password reset email to be sent to the specified email address + Requests *synchronously* 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 Parse site. - * - parameter email: The email address associated with the user that - * forgot their password. - * - parameter options: A set of options used to reset the password. Defaults to an empty set. + - parameter email: The email address associated with the user that forgot their password. + - parameter options: A set of options used to reset the password. Defaults to an empty set. */ public static func passwordReset(email: String, options: API.Options = []) throws { _ = try passwordResetCommand(email: email).execute(options: options) } /** - *Asynchronously* requests a password reset email to be sent to the specified email address + 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 Parse site. - * - parameter email: The email address associated with the user that - * forgot their password. - * - parameter options: A set of options used to reset the password. 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 logging out, completes or fails. + - parameter email: The email address associated with the user that forgot their password. + - parameter options: A set of options used to reset the password. 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 logging out, completes or fails. */ public static func passwordReset(email: String, options: API.Options = [], callbackQueue: DispatchQueue = .main, completion: @escaping (ParseError?) -> Void) { diff --git a/Sources/ParseSwift/Parse Types/ParseACL.swift b/Sources/ParseSwift/Parse Types/ParseACL.swift index a1aad370a..8813b93f0 100644 --- a/Sources/ParseSwift/Parse Types/ParseACL.swift +++ b/Sources/ParseSwift/Parse Types/ParseACL.swift @@ -316,7 +316,7 @@ extension ParseACL { extension ParseACL: CustomDebugStringConvertible { public var debugDescription: String { - guard let descriptionData = try? JSONEncoder().encode(self), + guard let descriptionData = try? ParseCoding.jsonEncoder().encode(self), let descriptionString = String(data: descriptionData, encoding: .utf8) else { return "ACL ()" } diff --git a/Sources/ParseSwift/Parse Types/ParseCloud.swift b/Sources/ParseSwift/Parse Types/ParseCloud.swift index f29b0724f..ec1281697 100644 --- a/Sources/ParseSwift/Parse Types/ParseCloud.swift +++ b/Sources/ParseSwift/Parse Types/ParseCloud.swift @@ -38,7 +38,7 @@ extension ParseCloud { - 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 logging out, completes or fails. - It should have the following argument signature: `(Result, ParseError>)`. + It should have the following argument signature: `(Result)`. */ public func callFunction(options: API.Options = [], callbackQueue: DispatchQueue = .main, @@ -78,7 +78,7 @@ extension ParseCloud { - 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 logging out, completes or fails. - It should have the following argument signature: `(Result, ParseError>)`. + It should have the following argument signature: `(Result)`. */ public func callJob(options: API.Options = [], callbackQueue: DispatchQueue = .main, @@ -104,7 +104,7 @@ extension ParseCloud { // MARK: CustomDebugStringConvertible extension ParseCloud { public var debugDescription: String { - guard let descriptionData = try? ParseCoding.parseEncoder(skipKeys: false).encode(self), + guard let descriptionData = try? ParseCoding.parseEncoder().encode(self), let descriptionString = String(data: descriptionData, encoding: .utf8) else { return "\(functionJobName)" } diff --git a/Sources/ParseSwift/Parse Types/ParseGeoPoint.swift b/Sources/ParseSwift/Parse Types/ParseGeoPoint.swift index 7171f3852..9475849b9 100644 --- a/Sources/ParseSwift/Parse Types/ParseGeoPoint.swift +++ b/Sources/ParseSwift/Parse Types/ParseGeoPoint.swift @@ -141,7 +141,7 @@ extension ParseGeoPoint { extension ParseGeoPoint: CustomDebugStringConvertible { public var debugDescription: String { - guard let descriptionData = try? JSONEncoder().encode(self), + guard let descriptionData = try? ParseCoding.jsonEncoder().encode(self), let descriptionString = String(data: descriptionData, encoding: .utf8) else { return "GeoPoint ()" } diff --git a/Tests/ParseSwiftTests/ParseEncoderTests.swift b/Tests/ParseSwiftTests/ParseEncoderTests.swift index a25c377f8..95e835dd0 100644 --- a/Tests/ParseSwiftTests/ParseEncoderTests.swift +++ b/Tests/ParseSwiftTests/ParseEncoderTests.swift @@ -43,7 +43,7 @@ class ParseEncoderTests: XCTestCase { let nicknames: [Name] let phoneNumbers: [String] } -/* + func parseEncoding(for object: T) -> Data { let encoder = ParseEncoder() encoder.jsonEncoder.outputFormatting = .sortedKeys @@ -54,7 +54,7 @@ class ParseEncoderTests: XCTestCase { } return encoding - }*/ + } func referenceEncoding(for object: T) -> Data { let encoder = JSONEncoder() @@ -67,7 +67,7 @@ class ParseEncoderTests: XCTestCase { return encoding } -/* + func test_encodingScalarValue() { let encoded = parseEncoding(for: ["": 5]) let reference = referenceEncoding(for: ["": 5]) @@ -112,7 +112,7 @@ class ParseEncoderTests: XCTestCase { XCTAssertEqual(jsonDecoded["*"]?["read"], true) XCTAssertEqual(parseDecoded["*"]?["read"], true) } -*/ + func testSkipKeysDefaultCodingKeys() throws { var score = GameScore(score: 10) score.objectId = "yarr" diff --git a/Tests/ParseSwiftTests/ParseObjectBatchTests.swift b/Tests/ParseSwiftTests/ParseObjectBatchTests.swift index 929799c3d..6e4690c03 100755 --- a/Tests/ParseSwiftTests/ParseObjectBatchTests.swift +++ b/Tests/ParseSwiftTests/ParseObjectBatchTests.swift @@ -1320,7 +1320,6 @@ class ParseObjectBatchTests: XCTestCase { // swiftlint:disable:this type_body_le } func testDeleteAllAsyncMainQueue() { - let score = GameScore(score: 10) let response = [BatchResponseItem(success: true, error: nil), BatchResponseItem(success: true, error: nil)] diff --git a/Tests/ParseSwiftTests/ParseQueryTests.swift b/Tests/ParseSwiftTests/ParseQueryTests.swift index 597252b32..4037c5907 100755 --- a/Tests/ParseSwiftTests/ParseQueryTests.swift +++ b/Tests/ParseSwiftTests/ParseQueryTests.swift @@ -335,7 +335,7 @@ class ParseQueryTests: XCTestCase { // swiftlint:disable:this type_body_length } func testFirstNoObjectFound() { - let scoreOnServer = GameScore(score: 10) + let results = FindResult(results: [GameScore](), count: 0) MockURLProtocol.mockRequests { _ in do { From e33fc27eb3c629756ac439f50a2b5cbc67c2c2bd Mon Sep 17 00:00:00 2001 From: Corey's iMac Date: Wed, 30 Dec 2020 00:21:04 -0500 Subject: [PATCH 05/44] Fix Password Reset error. Add playgrounds example --- .../Contents.swift | 8 ++++++++ .../ParseSwift/Object Protocols/ParseUser.swift | 17 ++++++++++------- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/ParseSwift.playground/Pages/4 - User - Continued.xcplaygroundpage/Contents.swift b/ParseSwift.playground/Pages/4 - User - Continued.xcplaygroundpage/Contents.swift index 42b9eb0f0..1337afbfb 100644 --- a/ParseSwift.playground/Pages/4 - User - Continued.xcplaygroundpage/Contents.swift +++ b/ParseSwift.playground/Pages/4 - User - Continued.xcplaygroundpage/Contents.swift @@ -76,6 +76,14 @@ do { assertionFailure("Error logging out: \(error)") } +//: Password Reset Request - synchronously +do { + try User.passwordReset(email: "hello@parse.org") + print("Successfully requested password reset") +} catch let error { + print("Error requesting password reset: \(error)") +} + //: Another way to sign up var newUser = User() newUser.username = "hello10" diff --git a/Sources/ParseSwift/Object Protocols/ParseUser.swift b/Sources/ParseSwift/Object Protocols/ParseUser.swift index f818b3a1f..b333293da 100644 --- a/Sources/ParseSwift/Object Protocols/ParseUser.swift +++ b/Sources/ParseSwift/Object Protocols/ParseUser.swift @@ -220,7 +220,9 @@ extension ParseUser { - parameter options: A set of options used to reset the password. Defaults to an empty set. */ public static func passwordReset(email: String, options: API.Options = []) throws { - _ = try passwordResetCommand(email: email).execute(options: options) + if let error = try passwordResetCommand(email: email).execute(options: options) { + throw error + } } /** @@ -232,23 +234,24 @@ extension ParseUser { - parameter callbackQueue: The queue to return to after completion. Default value of .main. - parameter completion: A block that will be called when logging out, completes or fails. */ - public static func passwordReset(email: String, options: API.Options = [], callbackQueue: DispatchQueue = .main, + public static func passwordReset(email: String, options: API.Options = [], + callbackQueue: DispatchQueue = .main, completion: @escaping (ParseError?) -> Void) { passwordResetCommand(email: email).executeAsync(options: options, callbackQueue: callbackQueue) { result in switch result { - case .success: - completion(nil) + case .success(let error): + completion(error) case .failure(let error): completion(error) } } } - internal static func passwordResetCommand(email: String) -> API.Command { + internal static func passwordResetCommand(email: String) -> API.Command { let resetBody = PasswordResetBody(email: email) - return API.Command(method: .POST, path: .passwordReset, body: resetBody) { (data) -> NoBody in - try ParseCoding.jsonDecoder().decode(NoBody.self, from: data) + return API.Command(method: .POST, path: .passwordReset, body: resetBody) { (data) -> ParseError? in + try? ParseCoding.jsonDecoder().decode(ParseError.self, from: data) } } } From 374b7b6dd203f353de328f266188869af23d62d8 Mon Sep 17 00:00:00 2001 From: Corey's iMac Date: Wed, 30 Dec 2020 14:13:55 -0500 Subject: [PATCH 06/44] Fix CloudCode server response error --- ParseSwift.playground/contents.xcplayground | 2 - .../ParseSwift/Parse Types/ParseCloud.swift | 3 + Tests/ParseSwiftTests/ParseUserTests.swift | 58 +++++++++++++++++++ 3 files changed, 61 insertions(+), 2 deletions(-) diff --git a/ParseSwift.playground/contents.xcplayground b/ParseSwift.playground/contents.xcplayground index 3db5401e4..bf7611d1f 100644 --- a/ParseSwift.playground/contents.xcplayground +++ b/ParseSwift.playground/contents.xcplayground @@ -9,7 +9,5 @@ - - \ No newline at end of file diff --git a/Sources/ParseSwift/Parse Types/ParseCloud.swift b/Sources/ParseSwift/Parse Types/ParseCloud.swift index ec1281697..bf90886b3 100644 --- a/Sources/ParseSwift/Parse Types/ParseCloud.swift +++ b/Sources/ParseSwift/Parse Types/ParseCloud.swift @@ -55,6 +55,9 @@ extension ParseCloud { body: self) { (data) -> AnyCodable in let response = try ParseCoding.jsonDecoder().decode(AnyResultsResponse.self, from: data) guard let result = response.result else { + if let error = try? ParseCoding.jsonDecoder().decode(ParseError.self, from: data) { + throw error + } return AnyCodable() } return result diff --git a/Tests/ParseSwiftTests/ParseUserTests.swift b/Tests/ParseSwiftTests/ParseUserTests.swift index ba6f03be5..842040146 100644 --- a/Tests/ParseSwiftTests/ParseUserTests.swift +++ b/Tests/ParseSwiftTests/ParseUserTests.swift @@ -1019,6 +1019,33 @@ class ParseUserTests: XCTestCase { // swiftlint:disable:this type_body_length } } + func testPasswordResetError() { + + let parseError = ParseError(code: .internalServer, message: "Object not found") + + let encoded: Data! + do { + encoded = try ParseCoding.jsonEncoder().encode(parseError) + } catch { + XCTFail("Should encode/decode. Error \(error)") + return + } + + MockURLProtocol.mockRequests { _ in + return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) + } + do { + try User.passwordReset(email: "hello@parse.org") + XCTFail("Should have thrown ParseError") + } catch { + if let error = error as? ParseError { + XCTAssertEqual(error.code, parseError.code) + } else { + XCTFail("Should have thrown ParseError") + } + } + } + func passwordResetAsync(callbackQueue: DispatchQueue) { let expectation1 = XCTestExpectation(description: "Logout user1") @@ -1049,6 +1076,37 @@ class ParseUserTests: XCTestCase { // swiftlint:disable:this type_body_length self.passwordResetAsync(callbackQueue: .main) } + func passwordResetAsyncError(parseError: ParseError, callbackQueue: DispatchQueue) { + + let expectation1 = XCTestExpectation(description: "Logout user1") + User.passwordReset(email: "hello@parse.org", callbackQueue: callbackQueue) { error in + + guard let error = error else { + XCTFail("Should have thrown ParseError") + expectation1.fulfill() + return + } + XCTAssertEqual(error.code, parseError.code) + expectation1.fulfill() + } + wait(for: [expectation1], timeout: 10.0) + } + + func testPasswordResetMainQueueError() { + let parseError = ParseError(code: .internalServer, message: "Object not found") + + MockURLProtocol.mockRequests { _ in + do { + let encoded = try ParseCoding.jsonEncoder().encode(parseError) + return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) + } catch { + return nil + } + } + + self.passwordResetAsyncError(parseError: parseError, callbackQueue: .main) + } + func testUserCustomValuesNotSavedToKeychain() { testLogin() User.current?.customKey = "Changed" From 8200cf00c33c90d93a994b05fdc50b6fc59a9f8f Mon Sep 17 00:00:00 2001 From: Corey's iMac Date: Wed, 30 Dec 2020 14:31:28 -0500 Subject: [PATCH 07/44] Add Cloud function/job error tests --- .../ParseSwift/Parse Types/ParseCloud.swift | 3 + Tests/ParseSwiftTests/ParseCloudTests.swift | 130 +++++++++++++++++- 2 files changed, 132 insertions(+), 1 deletion(-) diff --git a/Sources/ParseSwift/Parse Types/ParseCloud.swift b/Sources/ParseSwift/Parse Types/ParseCloud.swift index bf90886b3..59afceb5d 100644 --- a/Sources/ParseSwift/Parse Types/ParseCloud.swift +++ b/Sources/ParseSwift/Parse Types/ParseCloud.swift @@ -97,6 +97,9 @@ extension ParseCloud { body: self) { (data) -> AnyCodable in let response = try ParseCoding.jsonDecoder().decode(AnyResultsResponse.self, from: data) guard let result = response.result else { + if let error = try? ParseCoding.jsonDecoder().decode(ParseError.self, from: data) { + throw error + } return AnyCodable() } return result diff --git a/Tests/ParseSwiftTests/ParseCloudTests.swift b/Tests/ParseSwiftTests/ParseCloudTests.swift index 467df7324..59630c9e6 100644 --- a/Tests/ParseSwiftTests/ParseCloudTests.swift +++ b/Tests/ParseSwiftTests/ParseCloudTests.swift @@ -149,6 +149,34 @@ class ParseCloudTests: XCTestCase { // swiftlint:disable:this type_body_length } } + func testFunctionError() { + + let parseError = ParseError(code: .scriptError, message: "Error: Invalid function") + + let encoded: Data! + do { + encoded = try ParseCoding.jsonEncoder().encode(parseError) + } catch { + XCTFail("Should encode/decode. Error \(error)") + return + } + + MockURLProtocol.mockRequests { _ in + return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) + } + do { + let cloud = Cloud(functionJobName: "test") + _ = try cloud.callFunction() + XCTFail("Should have thrown ParseError") + } catch { + if let error = error as? ParseError { + XCTAssertEqual(error.code, parseError.code) + } else { + XCTFail("Should have thrown ParseError") + } + } + } + func functionAsync(serverResponse: AnyCodable, callbackQueue: DispatchQueue) { let expectation1 = XCTestExpectation(description: "Logout user1") @@ -163,6 +191,7 @@ class ParseCloudTests: XCTestCase { // swiftlint:disable:this type_body_length } else { guard let resultAsDictionary = serverResponse.value as? [String: String] else { XCTFail("Should have casted result to dictionary") + expectation1.fulfill() return } XCTAssertEqual(resultAsDictionary, ["hello": "world"]) @@ -206,6 +235,41 @@ class ParseCloudTests: XCTestCase { // swiftlint:disable:this type_body_length self.functionAsync(serverResponse: result, callbackQueue: .main) } + func functionAsyncError(parseError: ParseError, callbackQueue: DispatchQueue) { + + let expectation1 = XCTestExpectation(description: "Logout user1") + let cloud = Cloud(functionJobName: "test") + cloud.callFunction(callbackQueue: callbackQueue) { result in + + switch result { + + case .success: + XCTFail("Should have thrown ParseError") + expectation1.fulfill() + + case .failure(let error): + XCTAssertEqual(error.code, parseError.code) + } + expectation1.fulfill() + } + wait(for: [expectation1], timeout: 10.0) + } + + func testFunctionMainQueueError() { + let parseError = ParseError(code: .scriptError, message: "Error: Invalid function") + + MockURLProtocol.mockRequests { _ in + do { + let encoded = try ParseCoding.jsonEncoder().encode(parseError) + return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) + } catch { + return nil + } + } + + self.functionAsyncError(parseError: parseError, callbackQueue: .main) + } + func testCallJobCommand() throws { let cloud = Cloud(functionJobName: "test") let command = cloud.callJobCommand() @@ -272,6 +336,34 @@ class ParseCloudTests: XCTestCase { // swiftlint:disable:this type_body_length } } + func testJobError() { + + let parseError = ParseError(code: .scriptError, message: "Error: Invalid function") + + let encoded: Data! + do { + encoded = try ParseCoding.jsonEncoder().encode(parseError) + } catch { + XCTFail("Should encode/decode. Error \(error)") + return + } + + MockURLProtocol.mockRequests { _ in + return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) + } + do { + let cloud = Cloud(functionJobName: "test") + _ = try cloud.callJob() + XCTFail("Should have thrown ParseError") + } catch { + if let error = error as? ParseError { + XCTAssertEqual(error.code, parseError.code) + } else { + XCTFail("Should have thrown ParseError") + } + } + } + func jobAsync(serverResponse: AnyCodable, callbackQueue: DispatchQueue) { let expectation1 = XCTestExpectation(description: "Logout user1") @@ -286,6 +378,7 @@ class ParseCloudTests: XCTestCase { // swiftlint:disable:this type_body_length } else { guard let resultAsDictionary = serverResponse.value as? [String: String] else { XCTFail("Should have casted result to dictionary") + expectation1.fulfill() return } XCTAssertEqual(resultAsDictionary, ["hello": "world"]) @@ -328,4 +421,39 @@ class ParseCloudTests: XCTestCase { // swiftlint:disable:this type_body_length self.jobAsync(serverResponse: result, callbackQueue: .main) } -} + + func jobAsyncError(parseError: ParseError, callbackQueue: DispatchQueue) { + + let expectation1 = XCTestExpectation(description: "Logout user1") + let cloud = Cloud(functionJobName: "test") + cloud.callJob(callbackQueue: callbackQueue) { result in + + switch result { + + case .success: + XCTFail("Should have thrown ParseError") + expectation1.fulfill() + + case .failure(let error): + XCTAssertEqual(error.code, parseError.code) + } + expectation1.fulfill() + } + wait(for: [expectation1], timeout: 10.0) + } + + func testJobMainQueueError() { + let parseError = ParseError(code: .scriptError, message: "Error: Invalid function") + + MockURLProtocol.mockRequests { _ in + do { + let encoded = try ParseCoding.jsonEncoder().encode(parseError) + return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) + } catch { + return nil + } + } + + self.jobAsyncError(parseError: parseError, callbackQueue: .main) + } +} // swiftlint:disable:this file_length From 2e2a45b12c9efaa730e052a871c279c0a409527a Mon Sep 17 00:00:00 2001 From: Corey's iMac Date: Wed, 30 Dec 2020 17:47:36 -0500 Subject: [PATCH 08/44] Improve server error responses (established priority of errors). - Added ResultsResponse for explain and hint --- ParseSwift.xcodeproj/project.pbxproj | 24 ++--- Sources/ParseSwift/API/API+Commands.swift | 12 +-- Sources/ParseSwift/API/Responses.swift | 9 +- .../API/URLSession+extensions.swift | 48 +++++---- Sources/ParseSwift/Coding/ParseEncoder.swift | 2 +- .../ParseInstallation.swift | 0 .../ParseObject.swift | 0 .../ParseUser.swift | 0 .../Protocols/Deletable.swift | 0 .../Protocols/Fetchable.swift | 0 .../Protocols/Fileable.swift | 0 .../Protocols/Objectable.swift | 0 .../Protocols/Queryable.swift | 0 .../Protocols/Savable.swift | 0 .../Internal/BaseParseInstallation.swift | 0 .../Internal/BaseParseUser.swift | 0 .../Internal/NoBody.swift | 0 .../Internal/ParseHash.swift | 0 .../{Parse Types => Types}/ParseACL.swift | 0 .../{Parse Types => Types}/ParseCloud.swift | 4 +- .../{Parse Types => Types}/ParseError.swift | 0 .../{Parse Types => Types}/ParseFile.swift | 0 .../ParseGeoPoint.swift | 0 .../{Parse Types => Types}/Pointer.swift | 0 .../{Parse Types => Types}/Query.swift | 34 ++++--- Tests/ParseSwiftTests/APICommandTests.swift | 11 ++- Tests/ParseSwiftTests/ParseCloudTests.swift | 16 +-- Tests/ParseSwiftTests/ParseFileTests.swift | 6 -- .../ParseObjectBatchTests.swift | 2 - Tests/ParseSwiftTests/ParseQueryTests.swift | 98 +++++++++---------- 30 files changed, 142 insertions(+), 124 deletions(-) rename Sources/ParseSwift/{Object Protocols => Objects}/ParseInstallation.swift (100%) rename Sources/ParseSwift/{Object Protocols => Objects}/ParseObject.swift (100%) rename Sources/ParseSwift/{Object Protocols => Objects}/ParseUser.swift (100%) rename Sources/ParseSwift/{Object Protocols => Objects}/Protocols/Deletable.swift (100%) rename Sources/ParseSwift/{Object Protocols => Objects}/Protocols/Fetchable.swift (100%) rename Sources/ParseSwift/{Object Protocols => Objects}/Protocols/Fileable.swift (100%) rename Sources/ParseSwift/{Object Protocols => Objects}/Protocols/Objectable.swift (100%) rename Sources/ParseSwift/{Object Protocols => Objects}/Protocols/Queryable.swift (100%) rename Sources/ParseSwift/{Object Protocols => Objects}/Protocols/Savable.swift (100%) rename Sources/ParseSwift/{Parse Types => Types}/Internal/BaseParseInstallation.swift (100%) rename Sources/ParseSwift/{Parse Types => Types}/Internal/BaseParseUser.swift (100%) rename Sources/ParseSwift/{Parse Types => Types}/Internal/NoBody.swift (100%) rename Sources/ParseSwift/{Parse Types => Types}/Internal/ParseHash.swift (100%) rename Sources/ParseSwift/{Parse Types => Types}/ParseACL.swift (100%) rename Sources/ParseSwift/{Parse Types => Types}/ParseCloud.swift (98%) rename Sources/ParseSwift/{Parse Types => Types}/ParseError.swift (100%) rename Sources/ParseSwift/{Parse Types => Types}/ParseFile.swift (100%) rename Sources/ParseSwift/{Parse Types => Types}/ParseGeoPoint.swift (100%) rename Sources/ParseSwift/{Parse Types => Types}/Pointer.swift (100%) rename Sources/ParseSwift/{Parse Types => Types}/Query.swift (97%) diff --git a/ParseSwift.xcodeproj/project.pbxproj b/ParseSwift.xcodeproj/project.pbxproj index 156a81409..a4525b1e5 100644 --- a/ParseSwift.xcodeproj/project.pbxproj +++ b/ParseSwift.xcodeproj/project.pbxproj @@ -512,20 +512,20 @@ children = ( 4AA8076D1F794C1C008CD551 /* Info.plist */, 9194657724F16E330070296B /* ACLTests.swift */, - 705726ED2592C91C00F0ADD5 /* HashTests.swift */, 911DB12D24C4837E0027F3C7 /* APICommandTests.swift */, + 705726ED2592C91C00F0ADD5 /* HashTests.swift */, 4AA8076E1F794C1C008CD551 /* KeychainStoreTests.swift */, + 916786EF259BC59600BB5B4E /* ParseCloudTests.swift */, F971F4F524DE381A006CB79B /* ParseEncoderTests.swift */, + 705A99F8259807F900B3547F /* ParseFileManagerTests.swift */, + 705727882593FF8000F0ADD5 /* ParseFileTests.swift */, 70BC0B32251903D1001556DB /* ParseGeoPointTests.swift */, 70110D5B2506ED0E0091CC1D /* ParseInstallationTests.swift */, 70C7DC2024D20F190050419B /* ParseObjectBatchTests.swift */, 911DB13524C4FC100027F3C7 /* ParseObjectTests.swift */, 70CE1D882545BF730018D572 /* ParsePointerTests.swift */, 70C7DC1F24D20F180050419B /* ParseQueryTests.swift */, - 705727882593FF8000F0ADD5 /* ParseFileTests.swift */, - 705A99F8259807F900B3547F /* ParseFileManagerTests.swift */, 70C7DC1D24D20E530050419B /* ParseUserTests.swift */, - 916786EF259BC59600BB5B4E /* ParseCloudTests.swift */, 7FFF552A2217E729007C3B4E /* AnyCodableTests */, 911DB12A24C3F7260027F3C7 /* NetworkMocking */, ); @@ -574,8 +574,8 @@ F97B45C924D9C6F200F4A88B /* API */, F97B45B324D9C6F200F4A88B /* Coding */, F97B463F24D9C78B00F4A88B /* Mutation Operations */, - F97B45C324D9C6F200F4A88B /* Object Protocols */, - F97B45BA24D9C6F200F4A88B /* Parse Types */, + F97B45C324D9C6F200F4A88B /* Objects */, + F97B45BA24D9C6F200F4A88B /* Types */, F97B45CB24D9C6F200F4A88B /* Storage */, 4A82B7EE1F254B820063D731 /* Parse.swift */, 70110D51250680140091CC1D /* ParseConstants.swift */, @@ -608,10 +608,10 @@ children = ( 708D035125215F9B00646C70 /* Deletable.swift */, F97B45C524D9C6F200F4A88B /* Fetchable.swift */, + 705A9A2E25991C1400B3547F /* Fileable.swift */, 70BC988F252A5B5C00FF3074 /* Objectable.swift */, F97B45C824D9C6F200F4A88B /* Queryable.swift */, F97B45C724D9C6F200F4A88B /* Savable.swift */, - 705A9A2E25991C1400B3547F /* Fileable.swift */, ); path = Protocols; sourceTree = ""; @@ -703,22 +703,22 @@ path = Coding; sourceTree = ""; }; - F97B45BA24D9C6F200F4A88B /* Parse Types */ = { + F97B45BA24D9C6F200F4A88B /* Types */ = { isa = PBXGroup; children = ( F97B45C024D9C6F200F4A88B /* ParseACL.swift */, + 916786E1259B7DDA00BB5B4E /* ParseCloud.swift */, F97B45BF24D9C6F200F4A88B /* ParseError.swift */, F97B45C124D9C6F200F4A88B /* ParseFile.swift */, F97B45BC24D9C6F200F4A88B /* ParseGeoPoint.swift */, - 916786E1259B7DDA00BB5B4E /* ParseCloud.swift */, F97B45BE24D9C6F200F4A88B /* Pointer.swift */, F97B45BB24D9C6F200F4A88B /* Query.swift */, 70110D5D250849B30091CC1D /* Internal */, ); - path = "Parse Types"; + path = Types; sourceTree = ""; }; - F97B45C324D9C6F200F4A88B /* Object Protocols */ = { + F97B45C324D9C6F200F4A88B /* Objects */ = { isa = PBXGroup; children = ( 70BDA2B2250536FF00FC2237 /* ParseInstallation.swift */, @@ -726,7 +726,7 @@ F97B45C424D9C6F200F4A88B /* ParseUser.swift */, 70110D5E25084AF80091CC1D /* Protocols */, ); - path = "Object Protocols"; + path = Objects; sourceTree = ""; }; F97B45C924D9C6F200F4A88B /* API */ = { diff --git a/Sources/ParseSwift/API/API+Commands.swift b/Sources/ParseSwift/API/API+Commands.swift index a1009b46e..1f3a43da9 100644 --- a/Sources/ParseSwift/API/API+Commands.swift +++ b/Sources/ParseSwift/API/API+Commands.swift @@ -282,16 +282,14 @@ internal extension API { } else { if (urlBody as? ParseCloud) != nil { guard let bodyData = try? ParseCoding.parseEncoder().encode(urlBody) else { - completion(.failure(ParseError(code: .unknownError, - message: "couldn't encode body \(urlBody)"))) - return + return .failure(ParseError(code: .unknownError, + message: "couldn't encode body \(urlBody)")) } urlRequest.httpBody = bodyData } else { guard let bodyData = try? ParseCoding.jsonEncoder().encode(urlBody) else { - completion(.failure(ParseError(code: .unknownError, - message: "couldn't encode body \(urlBody)"))) - return + return .failure(ParseError(code: .unknownError, + message: "couldn't encode body \(urlBody)")) } urlRequest.httpBody = bodyData } @@ -614,4 +612,4 @@ extension API.Command where T: Encodable { let batchCommand = BatchCommand(requests: commands) return RESTBatchCommandTypeEncodable(method: .POST, path: .batch, body: batchCommand, mapper: mapper) } -}*/ +}*/ // swiftlint:disable:this file_length diff --git a/Sources/ParseSwift/API/Responses.swift b/Sources/ParseSwift/API/Responses.swift index 9b65393b1..6786f4258 100644 --- a/Sources/ParseSwift/API/Responses.swift +++ b/Sources/ParseSwift/API/Responses.swift @@ -133,7 +133,14 @@ internal struct FileUploadResponse: Decodable { _ = file.localUUID //Ensure file has a localUUID return file } +} + +// MARK: AnyResultResponse +internal struct AnyResultResponse: Codable { + let result: AnyCodable? +} + // MARK: AnyResultsResponse internal struct AnyResultsResponse: Codable { - let result: AnyCodable? + let results: AnyCodable? } diff --git a/Sources/ParseSwift/API/URLSession+extensions.swift b/Sources/ParseSwift/API/URLSession+extensions.swift index bc58fbe48..0d90440f4 100755 --- a/Sources/ParseSwift/API/URLSession+extensions.swift +++ b/Sources/ParseSwift/API/URLSession+extensions.swift @@ -79,24 +79,30 @@ extension URLSession { mapper: @escaping (Data) throws -> U) -> Result { if let responseError = responseError { guard let parseError = responseError as? ParseError else { - return .failure(ParseError(code: .unknownError, + return .failure(ParseError(code: .invalidServerResponse, message: "Unable to sync with parse-server: \(responseError)")) } return .failure(parseError) - } else if let responseData = responseData { + } + + if let responseData = responseData { + if let error = try? ParseCoding.jsonDecoder().decode(ParseError.self, from: responseData) { + return .failure(error) + } do { return try .success(mapper(responseData)) } catch { - let parseError = try? ParseCoding.jsonDecoder().decode(ParseError.self, from: responseData) - return .failure(parseError ?? ParseError(code: .unknownError, - // swiftlint:disable:next line_length - message: "Error decoding parse-server response: \(error.localizedDescription)")) + guard let parseError = error as? ParseError else { + return .failure(ParseError(code: .unknownError, + // swiftlint:disable:next line_length + message: "Error decoding parse-server response: \(String(describing: urlResponse)) with error: \(error.localizedDescription)")) + } + return .failure(parseError) } - } else { - return .failure(ParseError(code: .unknownError, - // swiftlint:disable:next line_length - message: "Unable to sync with parse-server: \(String(describing: urlResponse)).")) } + + return .failure(ParseError(code: .unknownError, + message: "Unable to sync with parse-server: \(String(describing: urlResponse)).")) } internal func makeResult(location: URL?, @@ -105,24 +111,28 @@ extension URLSession { mapper: @escaping (Data) throws -> U) -> Result { if let responseError = responseError { guard let parseError = responseError as? ParseError else { - return .failure(ParseError(code: .unknownError, + return .failure(ParseError(code: .invalidServerResponse, message: "Unable to sync with parse-server: \(responseError)")) } return .failure(parseError) - } else if let location = location { + } + + if let location = location { do { let data = try ParseCoding.jsonEncoder().encode(location) return try .success(mapper(data)) } catch { - return .failure(ParseError(code: .unknownError, - // swiftlint:disable:next line_length - message: "Error decoding parse-server response: \(error.localizedDescription)")) + guard let parseError = error as? ParseError else { + return .failure(ParseError(code: .unknownError, + // swiftlint:disable:next line_length + message: "Error decoding parse-server response: \(String(describing: urlResponse)) with error: \(error.localizedDescription)")) + } + return .failure(parseError) } - } else { - return .failure(ParseError(code: .unknownError, - // swiftlint:disable:next line_length - message: "Unable to sync with parse-server: \(String(describing: urlResponse)).")) } + + return .failure(ParseError(code: .unknownError, + message: "Unable to sync with parse-server: \(String(describing: urlResponse)).")) } internal func dataTask( diff --git a/Sources/ParseSwift/Coding/ParseEncoder.swift b/Sources/ParseSwift/Coding/ParseEncoder.swift index b2a506cee..5e2af2c98 100644 --- a/Sources/ParseSwift/Coding/ParseEncoder.swift +++ b/Sources/ParseSwift/Coding/ParseEncoder.swift @@ -75,7 +75,7 @@ public struct ParseEncoder { if let dateEncodingStrategy = dateEncodingStrategy { encoder.dateEncodingStrategy = .custom(dateEncodingStrategy) } - return try encoder.encodeObject(value, collectChildren: false, objectsSavedBeforeThisOne: nil).encoded + return try encoder.encodeObject(value, collectChildren: false, objectsSavedBeforeThisOne: nil, filesSavedBeforeThisOne: nil).encoded } public func encode(_ value: T) throws -> Data { diff --git a/Sources/ParseSwift/Object Protocols/ParseInstallation.swift b/Sources/ParseSwift/Objects/ParseInstallation.swift similarity index 100% rename from Sources/ParseSwift/Object Protocols/ParseInstallation.swift rename to Sources/ParseSwift/Objects/ParseInstallation.swift diff --git a/Sources/ParseSwift/Object Protocols/ParseObject.swift b/Sources/ParseSwift/Objects/ParseObject.swift similarity index 100% rename from Sources/ParseSwift/Object Protocols/ParseObject.swift rename to Sources/ParseSwift/Objects/ParseObject.swift diff --git a/Sources/ParseSwift/Object Protocols/ParseUser.swift b/Sources/ParseSwift/Objects/ParseUser.swift similarity index 100% rename from Sources/ParseSwift/Object Protocols/ParseUser.swift rename to Sources/ParseSwift/Objects/ParseUser.swift diff --git a/Sources/ParseSwift/Object Protocols/Protocols/Deletable.swift b/Sources/ParseSwift/Objects/Protocols/Deletable.swift similarity index 100% rename from Sources/ParseSwift/Object Protocols/Protocols/Deletable.swift rename to Sources/ParseSwift/Objects/Protocols/Deletable.swift diff --git a/Sources/ParseSwift/Object Protocols/Protocols/Fetchable.swift b/Sources/ParseSwift/Objects/Protocols/Fetchable.swift similarity index 100% rename from Sources/ParseSwift/Object Protocols/Protocols/Fetchable.swift rename to Sources/ParseSwift/Objects/Protocols/Fetchable.swift diff --git a/Sources/ParseSwift/Object Protocols/Protocols/Fileable.swift b/Sources/ParseSwift/Objects/Protocols/Fileable.swift similarity index 100% rename from Sources/ParseSwift/Object Protocols/Protocols/Fileable.swift rename to Sources/ParseSwift/Objects/Protocols/Fileable.swift diff --git a/Sources/ParseSwift/Object Protocols/Protocols/Objectable.swift b/Sources/ParseSwift/Objects/Protocols/Objectable.swift similarity index 100% rename from Sources/ParseSwift/Object Protocols/Protocols/Objectable.swift rename to Sources/ParseSwift/Objects/Protocols/Objectable.swift diff --git a/Sources/ParseSwift/Object Protocols/Protocols/Queryable.swift b/Sources/ParseSwift/Objects/Protocols/Queryable.swift similarity index 100% rename from Sources/ParseSwift/Object Protocols/Protocols/Queryable.swift rename to Sources/ParseSwift/Objects/Protocols/Queryable.swift diff --git a/Sources/ParseSwift/Object Protocols/Protocols/Savable.swift b/Sources/ParseSwift/Objects/Protocols/Savable.swift similarity index 100% rename from Sources/ParseSwift/Object Protocols/Protocols/Savable.swift rename to Sources/ParseSwift/Objects/Protocols/Savable.swift diff --git a/Sources/ParseSwift/Parse Types/Internal/BaseParseInstallation.swift b/Sources/ParseSwift/Types/Internal/BaseParseInstallation.swift similarity index 100% rename from Sources/ParseSwift/Parse Types/Internal/BaseParseInstallation.swift rename to Sources/ParseSwift/Types/Internal/BaseParseInstallation.swift diff --git a/Sources/ParseSwift/Parse Types/Internal/BaseParseUser.swift b/Sources/ParseSwift/Types/Internal/BaseParseUser.swift similarity index 100% rename from Sources/ParseSwift/Parse Types/Internal/BaseParseUser.swift rename to Sources/ParseSwift/Types/Internal/BaseParseUser.swift diff --git a/Sources/ParseSwift/Parse Types/Internal/NoBody.swift b/Sources/ParseSwift/Types/Internal/NoBody.swift similarity index 100% rename from Sources/ParseSwift/Parse Types/Internal/NoBody.swift rename to Sources/ParseSwift/Types/Internal/NoBody.swift diff --git a/Sources/ParseSwift/Parse Types/Internal/ParseHash.swift b/Sources/ParseSwift/Types/Internal/ParseHash.swift similarity index 100% rename from Sources/ParseSwift/Parse Types/Internal/ParseHash.swift rename to Sources/ParseSwift/Types/Internal/ParseHash.swift diff --git a/Sources/ParseSwift/Parse Types/ParseACL.swift b/Sources/ParseSwift/Types/ParseACL.swift similarity index 100% rename from Sources/ParseSwift/Parse Types/ParseACL.swift rename to Sources/ParseSwift/Types/ParseACL.swift diff --git a/Sources/ParseSwift/Parse Types/ParseCloud.swift b/Sources/ParseSwift/Types/ParseCloud.swift similarity index 98% rename from Sources/ParseSwift/Parse Types/ParseCloud.swift rename to Sources/ParseSwift/Types/ParseCloud.swift index 59afceb5d..12993e1b1 100644 --- a/Sources/ParseSwift/Parse Types/ParseCloud.swift +++ b/Sources/ParseSwift/Types/ParseCloud.swift @@ -53,7 +53,7 @@ extension ParseCloud { return API.Command(method: .POST, path: .functions(name: functionJobName), body: self) { (data) -> AnyCodable in - let response = try ParseCoding.jsonDecoder().decode(AnyResultsResponse.self, from: data) + let response = try ParseCoding.jsonDecoder().decode(AnyResultResponse.self, from: data) guard let result = response.result else { if let error = try? ParseCoding.jsonDecoder().decode(ParseError.self, from: data) { throw error @@ -95,7 +95,7 @@ extension ParseCloud { return API.Command(method: .POST, path: .jobs(name: functionJobName), body: self) { (data) -> AnyCodable in - let response = try ParseCoding.jsonDecoder().decode(AnyResultsResponse.self, from: data) + let response = try ParseCoding.jsonDecoder().decode(AnyResultResponse.self, from: data) guard let result = response.result else { if let error = try? ParseCoding.jsonDecoder().decode(ParseError.self, from: data) { throw error diff --git a/Sources/ParseSwift/Parse Types/ParseError.swift b/Sources/ParseSwift/Types/ParseError.swift similarity index 100% rename from Sources/ParseSwift/Parse Types/ParseError.swift rename to Sources/ParseSwift/Types/ParseError.swift diff --git a/Sources/ParseSwift/Parse Types/ParseFile.swift b/Sources/ParseSwift/Types/ParseFile.swift similarity index 100% rename from Sources/ParseSwift/Parse Types/ParseFile.swift rename to Sources/ParseSwift/Types/ParseFile.swift diff --git a/Sources/ParseSwift/Parse Types/ParseGeoPoint.swift b/Sources/ParseSwift/Types/ParseGeoPoint.swift similarity index 100% rename from Sources/ParseSwift/Parse Types/ParseGeoPoint.swift rename to Sources/ParseSwift/Types/ParseGeoPoint.swift diff --git a/Sources/ParseSwift/Parse Types/Pointer.swift b/Sources/ParseSwift/Types/Pointer.swift similarity index 100% rename from Sources/ParseSwift/Parse Types/Pointer.swift rename to Sources/ParseSwift/Types/Pointer.swift diff --git a/Sources/ParseSwift/Parse Types/Query.swift b/Sources/ParseSwift/Types/Query.swift similarity index 97% rename from Sources/ParseSwift/Parse Types/Query.swift rename to Sources/ParseSwift/Types/Query.swift index 1d5962a4f..95eb09c34 100644 --- a/Sources/ParseSwift/Parse Types/Query.swift +++ b/Sources/ParseSwift/Types/Query.swift @@ -702,7 +702,6 @@ public class Query: Encodable, Equatable where T: ParseObject { extension Query: Queryable { public typealias ResultType = T - public typealias AnyResultType = [String: AnyCodable] /** Finds objects *synchronously* based on the constructed query and sets an error if there was one. @@ -726,7 +725,7 @@ extension Query: Queryable { - returns: Returns a dictionary of `AnyResultType` that is the JSON response of the query. */ - public func find(explain: Bool, hint: String? = nil, options: API.Options = []) throws -> AnyResultType { + public func find(explain: Bool, hint: String? = nil, options: API.Options = []) throws -> AnyCodable { try findCommand(explain: explain, hint: hint).execute(options: options) } @@ -755,7 +754,7 @@ extension Query: Queryable { */ public func find(explain: Bool, hint: String? = nil, options: API.Options = [], callbackQueue: DispatchQueue = .main, - completion: @escaping (Result) -> Void) { + completion: @escaping (Result) -> Void) { findCommand(explain: explain, hint: hint).executeAsync(options: options, callbackQueue: callbackQueue, completion: completion) } @@ -784,7 +783,7 @@ extension Query: Queryable { - returns: Returns a dictionary of `AnyResultType` that is the JSON response of the query. */ - public func first(explain: Bool, hint: String? = nil, options: API.Options = []) throws -> AnyResultType { + public func first(explain: Bool, hint: String? = nil, options: API.Options = []) throws -> AnyCodable { try firstCommand(explain: explain, hint: hint).execute(options: options) } @@ -827,7 +826,7 @@ extension Query: Queryable { */ public func first(explain: Bool, hint: String? = nil, options: API.Options = [], callbackQueue: DispatchQueue = .main, - completion: @escaping (Result) -> Void) { + completion: @escaping (Result) -> Void) { firstCommand(explain: explain, hint: hint).executeAsync(options: options, callbackQueue: callbackQueue, completion: completion) } @@ -854,7 +853,7 @@ extension Query: Queryable { - returns: Returns a dictionary of `AnyResultType` that is the JSON response of the query. */ - public func count(explain: Bool, hint: String? = nil, options: API.Options = []) throws -> AnyResultType { + public func count(explain: Bool, hint: String? = nil, options: API.Options = []) throws -> AnyCodable { try countCommand(explain: explain, hint: hint).execute(options: options) } @@ -882,7 +881,7 @@ extension Query: Queryable { */ public func count(explain: Bool, hint: String? = nil, options: API.Options = [], callbackQueue: DispatchQueue = .main, - completion: @escaping (Result) -> Void) { + completion: @escaping (Result) -> Void) { countCommand(explain: explain, hint: hint).executeAsync(options: options, callbackQueue: callbackQueue, completion: completion) } @@ -912,33 +911,42 @@ private extension Query { } } - private func findCommand(explain: Bool, hint: String?) -> API.Command, AnyResultType> { + private func findCommand(explain: Bool, hint: String?) -> API.Command, AnyCodable> { let query = self query.explain = explain query.hint = hint return API.Command(method: .POST, path: endpoint, body: query) { - try JSONDecoder().decode(AnyResultType.self, from: $0) + if let results = try JSONDecoder().decode(AnyResultsResponse.self, from: $0).results { + return results + } + return AnyCodable() } } - private func firstCommand(explain: Bool, hint: String?) -> API.Command, AnyResultType> { + private func firstCommand(explain: Bool, hint: String?) -> API.Command, AnyCodable> { let query = self query.limit = 1 query.explain = explain query.hint = hint return API.Command(method: .POST, path: endpoint, body: query) { - try JSONDecoder().decode(AnyResultType.self, from: $0) + if let results = try JSONDecoder().decode(AnyResultsResponse.self, from: $0).results { + return results + } + return AnyCodable() } } - private func countCommand(explain: Bool, hint: String?) -> API.Command, AnyResultType> { + private func countCommand(explain: Bool, hint: String?) -> API.Command, AnyCodable> { let query = self query.limit = 1 query.isCount = true query.explain = explain query.hint = hint return API.Command(method: .POST, path: endpoint, body: query) { - try JSONDecoder().decode(AnyResultType.self, from: $0) + if let results = try JSONDecoder().decode(AnyResultsResponse.self, from: $0).results { + return results + } + return AnyCodable() } } } diff --git a/Tests/ParseSwiftTests/APICommandTests.swift b/Tests/ParseSwiftTests/APICommandTests.swift index e8cab6681..6c8947965 100644 --- a/Tests/ParseSwiftTests/APICommandTests.swift +++ b/Tests/ParseSwiftTests/APICommandTests.swift @@ -81,6 +81,7 @@ class APICommandTests: XCTestCase { //This is how errors HTTP errors should typically come in func testErrorHTTPJSON() { + let parseError = ParseError(code: .connectionFailed, message: "Connection failed") let errorKey = "error" let errorValue = "yarr" let codeKey = "code" @@ -89,6 +90,7 @@ class APICommandTests: XCTestCase { errorKey: errorValue, codeKey: codeValue ] + MockURLProtocol.mockRequests { _ in do { let json = try JSONSerialization.data(withJSONObject: responseDictionary, options: []) @@ -98,24 +100,25 @@ class APICommandTests: XCTestCase { return nil } } + do { _ = try API.Command(method: .GET, path: .login, params: nil, mapper: { (_) -> NoBody in - throw ParseError(code: .connectionFailed, message: "Connection failed") + throw parseError }).execute(options: []) + XCTFail("Should have thrown an error") } catch { guard let error = error as? ParseError else { XCTFail("should be able unwrap final error to ParseError") return } - let unknownError = ParseError(code: .unknownError, message: "") - XCTAssertEqual(unknownError.code, error.code) + XCTAssertEqual(error.code, parseError.code) } } //This is less common as the HTTP won't be able to produce ParseErrors directly, but used for testing func testErrorHTTPReturnsParseError1() { - let originalError = ParseError(code: .unknownError, message: "Couldn't decode") + let originalError = ParseError(code: .invalidServerResponse, message: "Couldn't decode") MockURLProtocol.mockRequests { _ in return MockURLResponse(error: originalError) } diff --git a/Tests/ParseSwiftTests/ParseCloudTests.swift b/Tests/ParseSwiftTests/ParseCloudTests.swift index 59630c9e6..592d6a6cf 100644 --- a/Tests/ParseSwiftTests/ParseCloudTests.swift +++ b/Tests/ParseSwiftTests/ParseCloudTests.swift @@ -103,7 +103,7 @@ class ParseCloudTests: XCTestCase { // swiftlint:disable:this type_body_length } func testFunction() { - let response = AnyResultsResponse(result: nil) + let response = AnyResultResponse(result: nil) MockURLProtocol.mockRequests { _ in do { @@ -124,7 +124,7 @@ class ParseCloudTests: XCTestCase { // swiftlint:disable:this type_body_length func testFunction2() { var result: AnyCodable = ["hello": "world"] - let response = AnyResultsResponse(result: result) + let response = AnyResultResponse(result: result) MockURLProtocol.mockRequests { _ in do { @@ -205,7 +205,7 @@ class ParseCloudTests: XCTestCase { // swiftlint:disable:this type_body_length } func testFunctionMainQueue() { - let response = AnyResultsResponse(result: nil) + let response = AnyResultResponse(result: nil) MockURLProtocol.mockRequests { _ in do { @@ -221,7 +221,7 @@ class ParseCloudTests: XCTestCase { // swiftlint:disable:this type_body_length func testFunctionMainQueue2() { let result: AnyCodable = ["hello": "world"] - let response = AnyResultsResponse(result: result) + let response = AnyResultResponse(result: result) MockURLProtocol.mockRequests { _ in do { @@ -292,7 +292,7 @@ class ParseCloudTests: XCTestCase { // swiftlint:disable:this type_body_length } func testJob() { - let response = AnyResultsResponse(result: nil) + let response = AnyResultResponse(result: nil) MockURLProtocol.mockRequests { _ in do { @@ -313,7 +313,7 @@ class ParseCloudTests: XCTestCase { // swiftlint:disable:this type_body_length func testJob2() { let result: AnyCodable = ["hello": "world"] - let response = AnyResultsResponse(result: result) + let response = AnyResultResponse(result: result) MockURLProtocol.mockRequests { _ in do { @@ -392,7 +392,7 @@ class ParseCloudTests: XCTestCase { // swiftlint:disable:this type_body_length } func testJobMainQueue() { - let response = AnyResultsResponse(result: nil) + let response = AnyResultResponse(result: nil) MockURLProtocol.mockRequests { _ in do { @@ -408,7 +408,7 @@ class ParseCloudTests: XCTestCase { // swiftlint:disable:this type_body_length func testJobMainQueue2() { let result: AnyCodable = ["hello": "world"] - let response = AnyResultsResponse(result: result) + let response = AnyResultResponse(result: result) MockURLProtocol.mockRequests { _ in do { diff --git a/Tests/ParseSwiftTests/ParseFileTests.swift b/Tests/ParseSwiftTests/ParseFileTests.swift index 6c0812ea0..81de5c6fa 100644 --- a/Tests/ParseSwiftTests/ParseFileTests.swift +++ b/Tests/ParseSwiftTests/ParseFileTests.swift @@ -79,7 +79,6 @@ class ParseFileTests: XCTestCase { // swiftlint:disable:this type_body_length XCTAssertEqual(command.method, API.Method.POST) XCTAssertNil(command.params) XCTAssertNil(command.body) - XCTAssertNil(command.data) let file2 = ParseFile(cloudURL: url) @@ -89,7 +88,6 @@ class ParseFileTests: XCTestCase { // swiftlint:disable:this type_body_length XCTAssertEqual(command2.method, API.Method.POST) XCTAssertNil(command2.params) XCTAssertNil(command2.body) - XCTAssertNil(command2.data) } func testDeleteCommand() { @@ -105,7 +103,6 @@ class ParseFileTests: XCTestCase { // swiftlint:disable:this type_body_length XCTAssertEqual(command.method, API.Method.DELETE) XCTAssertNil(command.params) XCTAssertNil(command.body) - XCTAssertNil(command.data) var file2 = ParseFile(cloudURL: url) file2.url = url @@ -115,7 +112,6 @@ class ParseFileTests: XCTestCase { // swiftlint:disable:this type_body_length XCTAssertEqual(command2.method, API.Method.DELETE) XCTAssertNil(command2.params) XCTAssertNil(command2.body) - XCTAssertNil(command2.data) } func testDownloadCommand() { @@ -131,7 +127,6 @@ class ParseFileTests: XCTestCase { // swiftlint:disable:this type_body_length XCTAssertEqual(command.method, API.Method.GET) XCTAssertNil(command.params) XCTAssertNil(command.body) - XCTAssertNil(command.data) let file2 = ParseFile(cloudURL: url) let command2 = file2.downloadFileCommand() @@ -140,7 +135,6 @@ class ParseFileTests: XCTestCase { // swiftlint:disable:this type_body_length XCTAssertEqual(command2.method, API.Method.GET) XCTAssertNil(command2.params) XCTAssertNil(command2.body) - XCTAssertNil(command2.data) } func testLocalUUID() throws { diff --git a/Tests/ParseSwiftTests/ParseObjectBatchTests.swift b/Tests/ParseSwiftTests/ParseObjectBatchTests.swift index 4a5ecc51c..c2f68e93a 100644 --- a/Tests/ParseSwiftTests/ParseObjectBatchTests.swift +++ b/Tests/ParseSwiftTests/ParseObjectBatchTests.swift @@ -1214,7 +1214,6 @@ class ParseObjectBatchTests: XCTestCase { // swiftlint:disable:this type_body_le } func testDeleteAll() { - let score = GameScore(score: 10) let error: ParseError? = nil let response = [error] @@ -1313,7 +1312,6 @@ class ParseObjectBatchTests: XCTestCase { // swiftlint:disable:this type_body_le } func testDeleteAllAsyncMainQueue() { - let score = GameScore(score: 10) let error: ParseError? = nil let response = [error] diff --git a/Tests/ParseSwiftTests/ParseQueryTests.swift b/Tests/ParseSwiftTests/ParseQueryTests.swift index 023d59d25..93b6b3d4d 100644 --- a/Tests/ParseSwiftTests/ParseQueryTests.swift +++ b/Tests/ParseSwiftTests/ParseQueryTests.swift @@ -336,7 +336,7 @@ class ParseQueryTests: XCTestCase { // swiftlint:disable:this type_body_length func testFirstNoObjectFound() { - let results = FindResult(results: [GameScore](), count: 0) + let results = QueryResponse(results: [GameScore](), count: 0) MockURLProtocol.mockRequests { _ in do { let encoded = try ParseCoding.jsonEncoder().encode(results) @@ -1617,7 +1617,7 @@ class ParseQueryTests: XCTestCase { // swiftlint:disable:this type_body_length // MARK: JSON Responses func testExplainFindSynchronous() { - let json = ["yolo": "yarr"] + let json = AnyResultsResponse(results: ["yolo": "yarr"]) let encoded: Data! do { @@ -1634,19 +1634,19 @@ class ParseQueryTests: XCTestCase { // swiftlint:disable:this type_body_length let query = GameScore.query() do { let queryResult = try query.find(explain: true) - XCTAssertEqual(queryResult.keys.first, json.keys.first) - guard let valueString = queryResult.values.first?.value as? String else { + guard let response = queryResult.value as? [String: String], + let expected = json.results?.value as? [String: String] else { XCTFail("Error: Should cast to string") return } - XCTAssertEqual(valueString, json.values.first) + XCTAssertEqual(response, expected) } catch { XCTFail("Error: \(error)") } } func testExplainFindAsynchronous() { - let json = ["yolo": "yarr"] + let json = AnyResultsResponse(results: ["yolo": "yarr"]) let encoded: Data! do { @@ -1666,13 +1666,13 @@ class ParseQueryTests: XCTestCase { // swiftlint:disable:this type_body_length switch result { case .success(let queryResult): - XCTAssertEqual(queryResult.keys.first, json.keys.first) - guard let valueString = queryResult.values.first?.value as? String else { + guard let response = queryResult.value as? [String: String], + let expected = json.results?.value as? [String: String] else { XCTFail("Error: Should cast to string") expectation.fulfill() return } - XCTAssertEqual(valueString, json.values.first) + XCTAssertEqual(response, expected) case .failure(let error): XCTFail("Error: \(error)") } @@ -1682,7 +1682,7 @@ class ParseQueryTests: XCTestCase { // swiftlint:disable:this type_body_length } func testExplainFirstSynchronous() { - let json = ["yolo": "yarr"] + let json = AnyResultsResponse(results: ["yolo": "yarr"]) let encoded: Data! do { @@ -1699,19 +1699,19 @@ class ParseQueryTests: XCTestCase { // swiftlint:disable:this type_body_length let query = GameScore.query() do { let queryResult = try query.first(explain: true) - XCTAssertEqual(queryResult.keys.first, json.keys.first) - guard let valueString = queryResult.values.first?.value as? String else { + guard let response = queryResult.value as? [String: String], + let expected = json.results?.value as? [String: String] else { XCTFail("Error: Should cast to string") return } - XCTAssertEqual(valueString, json.values.first) + XCTAssertEqual(response, expected) } catch { XCTFail("Error: \(error)") } } func testExplainFirstAsynchronous() { - let json = ["yolo": "yarr"] + let json = AnyResultsResponse(results: ["yolo": "yarr"]) let encoded: Data! do { @@ -1731,13 +1731,13 @@ class ParseQueryTests: XCTestCase { // swiftlint:disable:this type_body_length switch result { case .success(let queryResult): - XCTAssertEqual(queryResult.keys.first, json.keys.first) - guard let valueString = queryResult.values.first?.value as? String else { + guard let response = queryResult.value as? [String: String], + let expected = json.results?.value as? [String: String] else { XCTFail("Error: Should cast to string") expectation.fulfill() return } - XCTAssertEqual(valueString, json.values.first) + XCTAssertEqual(response, expected) case .failure(let error): XCTFail("Error: \(error)") } @@ -1747,7 +1747,7 @@ class ParseQueryTests: XCTestCase { // swiftlint:disable:this type_body_length } func testExplainCountSynchronous() { - let json = ["yolo": "yarr"] + let json = AnyResultsResponse(results: ["yolo": "yarr"]) let encoded: Data! do { @@ -1764,19 +1764,19 @@ class ParseQueryTests: XCTestCase { // swiftlint:disable:this type_body_length let query = GameScore.query() do { let queryResult = try query.count(explain: true) - XCTAssertEqual(queryResult.keys.first, json.keys.first) - guard let valueString = queryResult.values.first?.value as? String else { + guard let response = queryResult.value as? [String: String], + let expected = json.results?.value as? [String: String] else { XCTFail("Error: Should cast to string") return } - XCTAssertEqual(valueString, json.values.first) + XCTAssertEqual(response, expected) } catch { XCTFail("Error: \(error)") } } func testExplainCountAsynchronous() { - let json = ["yolo": "yarr"] + let json = AnyResultsResponse(results: ["yolo": "yarr"]) let encoded: Data! do { @@ -1796,13 +1796,13 @@ class ParseQueryTests: XCTestCase { // swiftlint:disable:this type_body_length switch result { case .success(let queryResult): - XCTAssertEqual(queryResult.keys.first, json.keys.first) - guard let valueString = queryResult.values.first?.value as? String else { + guard let response = queryResult.value as? [String: String], + let expected = json.results?.value as? [String: String] else { XCTFail("Error: Should cast to string") expectation.fulfill() return } - XCTAssertEqual(valueString, json.values.first) + XCTAssertEqual(response, expected) case .failure(let error): XCTFail("Error: \(error)") } @@ -1812,7 +1812,7 @@ class ParseQueryTests: XCTestCase { // swiftlint:disable:this type_body_length } func testHintFindSynchronous() { - let json = ["yolo": "yarr"] + let json = AnyResultsResponse(results: ["yolo": "yarr"]) let encoded: Data! do { @@ -1829,19 +1829,19 @@ class ParseQueryTests: XCTestCase { // swiftlint:disable:this type_body_length let query = GameScore.query() do { let queryResult = try query.find(explain: false, hint: "_id_") - XCTAssertEqual(queryResult.keys.first, json.keys.first) - guard let valueString = queryResult.values.first?.value as? String else { + guard let response = queryResult.value as? [String: String], + let expected = json.results?.value as? [String: String] else { XCTFail("Error: Should cast to string") return } - XCTAssertEqual(valueString, json.values.first) + XCTAssertEqual(response, expected) } catch { XCTFail("Error: \(error)") } } func testHintFindAsynchronous() { - let json = ["yolo": "yarr"] + let json = AnyResultsResponse(results: ["yolo": "yarr"]) let encoded: Data! do { @@ -1861,13 +1861,13 @@ class ParseQueryTests: XCTestCase { // swiftlint:disable:this type_body_length switch result { case .success(let queryResult): - XCTAssertEqual(queryResult.keys.first, json.keys.first) - guard let valueString = queryResult.values.first?.value as? String else { + guard let response = queryResult.value as? [String: String], + let expected = json.results?.value as? [String: String] else { XCTFail("Error: Should cast to string") expectation.fulfill() return } - XCTAssertEqual(valueString, json.values.first) + XCTAssertEqual(response, expected) case .failure(let error): XCTFail("Error: \(error)") } @@ -1877,7 +1877,7 @@ class ParseQueryTests: XCTestCase { // swiftlint:disable:this type_body_length } func testHintFirstSynchronous() { - let json = ["yolo": "yarr"] + let json = AnyResultsResponse(results: ["yolo": "yarr"]) let encoded: Data! do { @@ -1894,19 +1894,19 @@ class ParseQueryTests: XCTestCase { // swiftlint:disable:this type_body_length let query = GameScore.query() do { let queryResult = try query.first(explain: false, hint: "_id_") - XCTAssertEqual(queryResult.keys.first, json.keys.first) - guard let valueString = queryResult.values.first?.value as? String else { + guard let response = queryResult.value as? [String: String], + let expected = json.results?.value as? [String: String] else { XCTFail("Error: Should cast to string") return } - XCTAssertEqual(valueString, json.values.first) + XCTAssertEqual(response, expected) } catch { XCTFail("Error: \(error)") } } func testHintFirstAsynchronous() { - let json = ["yolo": "yarr"] + let json = AnyResultsResponse(results: ["yolo": "yarr"]) let encoded: Data! do { @@ -1926,13 +1926,13 @@ class ParseQueryTests: XCTestCase { // swiftlint:disable:this type_body_length switch result { case .success(let queryResult): - XCTAssertEqual(queryResult.keys.first, json.keys.first) - guard let valueString = queryResult.values.first?.value as? String else { + guard let response = queryResult.value as? [String: String], + let expected = json.results?.value as? [String: String] else { XCTFail("Error: Should cast to string") expectation.fulfill() return } - XCTAssertEqual(valueString, json.values.first) + XCTAssertEqual(response, expected) case .failure(let error): XCTFail("Error: \(error)") } @@ -1942,7 +1942,7 @@ class ParseQueryTests: XCTestCase { // swiftlint:disable:this type_body_length } func testHintCountSynchronous() { - let json = ["yolo": "yarr"] + let json = AnyResultsResponse(results: ["yolo": "yarr"]) let encoded: Data! do { @@ -1959,19 +1959,19 @@ class ParseQueryTests: XCTestCase { // swiftlint:disable:this type_body_length let query = GameScore.query() do { let queryResult = try query.count(explain: false, hint: "_id_") - XCTAssertEqual(queryResult.keys.first, json.keys.first) - guard let valueString = queryResult.values.first?.value as? String else { + guard let response = queryResult.value as? [String: String], + let expected = json.results?.value as? [String: String] else { XCTFail("Error: Should cast to string") return } - XCTAssertEqual(valueString, json.values.first) + XCTAssertEqual(response, expected) } catch { XCTFail("Error: \(error)") } } func testHintCountAsynchronous() { - let json = ["yolo": "yarr"] + let json = AnyResultsResponse(results: ["yolo": "yarr"]) let encoded: Data! do { @@ -1991,13 +1991,13 @@ class ParseQueryTests: XCTestCase { // swiftlint:disable:this type_body_length switch result { case .success(let queryResult): - XCTAssertEqual(queryResult.keys.first, json.keys.first) - guard let valueString = queryResult.values.first?.value as? String else { + guard let response = queryResult.value as? [String: String], + let expected = json.results?.value as? [String: String] else { XCTFail("Error: Should cast to string") expectation.fulfill() return } - XCTAssertEqual(valueString, json.values.first) + XCTAssertEqual(response, expected) case .failure(let error): XCTFail("Error: \(error)") } From 9843145d106a8237e0c81dace907c2a61830f09a Mon Sep 17 00:00:00 2001 From: Corey's iMac Date: Wed, 30 Dec 2020 18:00:33 -0500 Subject: [PATCH 09/44] Bug fix: User logout was calling incorrect endpoint for server --- ParseSwift.playground/contents.xcplayground | 1 - Sources/ParseSwift/API/API.swift | 2 +- Sources/ParseSwift/Types/ParseFile.swift | 2 +- Tests/ParseSwiftTests/ParseUserTests.swift | 2 +- 4 files changed, 3 insertions(+), 4 deletions(-) diff --git a/ParseSwift.playground/contents.xcplayground b/ParseSwift.playground/contents.xcplayground index dba126537..bf7611d1f 100644 --- a/ParseSwift.playground/contents.xcplayground +++ b/ParseSwift.playground/contents.xcplayground @@ -9,6 +9,5 @@ - \ No newline at end of file diff --git a/Sources/ParseSwift/API/API.swift b/Sources/ParseSwift/API/API.swift index 432df8d12..232978f19 100644 --- a/Sources/ParseSwift/API/API.swift +++ b/Sources/ParseSwift/API/API.swift @@ -40,7 +40,7 @@ public struct API { case .signup: return "/users" case .logout: - return "/users/logout" + return "/logout" case .file(let fileName): return "/files/\(fileName)" case .passwordReset: diff --git a/Sources/ParseSwift/Types/ParseFile.swift b/Sources/ParseSwift/Types/ParseFile.swift index d0ed7264d..420b23459 100644 --- a/Sources/ParseSwift/Types/ParseFile.swift +++ b/Sources/ParseSwift/Types/ParseFile.swift @@ -437,7 +437,7 @@ extension ParseFile { } } -// MARK: Downloading +// MARK: Fetching extension ParseFile { /** Fetches a file with given url *synchronously*. diff --git a/Tests/ParseSwiftTests/ParseUserTests.swift b/Tests/ParseSwiftTests/ParseUserTests.swift index f22ae2996..a3018dfd5 100644 --- a/Tests/ParseSwiftTests/ParseUserTests.swift +++ b/Tests/ParseSwiftTests/ParseUserTests.swift @@ -917,7 +917,7 @@ class ParseUserTests: XCTestCase { // swiftlint:disable:this type_body_length func testLogutCommand() { let command = User.logoutCommand() XCTAssertNotNil(command) - XCTAssertEqual(command.path.urlComponent, "/users/logout") + XCTAssertEqual(command.path.urlComponent, "/logout") XCTAssertEqual(command.method, API.Method.POST) XCTAssertNil(command.params) XCTAssertNil(command.body) From 676cce2c6e82f6309be53ad7a085e17851827436 Mon Sep 17 00:00:00 2001 From: Corey's iMac Date: Wed, 30 Dec 2020 19:29:16 -0500 Subject: [PATCH 10/44] Clean up docs --- .../Objects/ParseInstallation.swift | 24 ++++++------ Sources/ParseSwift/Objects/ParseObject.swift | 26 ++++++------- Sources/ParseSwift/Objects/ParseUser.swift | 36 ++++++++--------- Sources/ParseSwift/Types/ParseFile.swift | 39 ++++++++++++------- Sources/ParseSwift/Types/Query.swift | 24 ++++++------ 5 files changed, 79 insertions(+), 70 deletions(-) diff --git a/Sources/ParseSwift/Objects/ParseInstallation.swift b/Sources/ParseSwift/Objects/ParseInstallation.swift index 20fd72c36..2c2884f02 100644 --- a/Sources/ParseSwift/Objects/ParseInstallation.swift +++ b/Sources/ParseSwift/Objects/ParseInstallation.swift @@ -314,7 +314,7 @@ extension ParseInstallation { Fetches the `ParseInstallation` *synchronously* with the current data from the server and sets an error if one occurs. - - parameter options: A set of options used to save installations. Defaults to an empty set. + - parameter options: A set of header options sent to the server. Defaults to an empty set. - throws: An Error of `ParseError` type. - important: If an object fetched has the same objectId as current, it will automatically update the current. */ @@ -327,7 +327,7 @@ extension ParseInstallation { /** Fetches the `ParseInstallation` *asynchronously* and executes the given callback block. - - parameter options: A set of options used to save installations. Defaults to an empty set. + - 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 when completed. @@ -360,7 +360,7 @@ extension ParseInstallation { /** Saves the `ParseInstallation` *synchronously* and throws an error if there's an issue. - - parameter options: A set of options used to save installations. Defaults to an empty set. + - parameter options: A set of header options sent to the server. Defaults to an empty set. - throws: A Error of type `ParseError`. - returns: Returns saved `ParseInstallation`. - important: If an object saved has the same objectId as current, it will automatically update the current. @@ -394,7 +394,7 @@ extension ParseInstallation { /** Saves the `ParseInstallation` *asynchronously* and executes the given callback block. - - parameter options: A set of options used to save installations. Defaults to an empty set. + - 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)`. @@ -429,7 +429,7 @@ extension ParseInstallation { Deletes the `ParseInstallation` *synchronously* with the current data from the server and sets an error if one occurs. - - parameter options: A set of options used to save installations. Defaults to an empty set. + - parameter options: A set of header options sent to the server. Defaults to an empty set. - throws: An Error of `ParseError` type. - important: If an object deleted has the same objectId as current, it will automatically update the current. */ @@ -441,7 +441,7 @@ extension ParseInstallation { /** Deletes the `ParseInstallation` *asynchronously* and executes the given callback block. - - parameter options: A set of options used to save installations. Defaults to an empty set. + - 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 when completed. @@ -478,7 +478,7 @@ public extension Sequence where Element: ParseInstallation { /** Saves a collection of installations *synchronously* all at once and throws an error if necessary. - - parameter options: A set of options used to save installations. Defaults to an empty set. + - parameter options: A set of header options sent to the server. Defaults to an empty set. - returns: Returns a Result enum with the object if a save was successful or a `ParseError` if it failed. - throws: `ParseError` @@ -496,7 +496,7 @@ public extension Sequence where Element: ParseInstallation { /** Saves a collection of installations all at once *asynchronously* and executes the completion block when done. - - parameter options: A set of options used to save installations. Defaults to an empty set. + - 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<[(Result)], ParseError>)`. @@ -525,7 +525,7 @@ public extension Sequence where Element: ParseInstallation { /** Fetches a collection of installations *synchronously* all at once and throws an error if necessary. - - parameter options: A set of options used to fetch installations. Defaults to an empty set. + - parameter options: A set of header options sent to the server. Defaults to an empty set. - returns: Returns a Result enum with the object if a fetch was successful or a `ParseError` if it failed. - throws: `ParseError` @@ -561,7 +561,7 @@ public extension Sequence where Element: ParseInstallation { /** Fetches a collection of installations all at once *asynchronously* and executes the completion block when done. - - parameter options: A set of options used to fetch installations. Defaults to an empty set. + - 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<[(Result)], ParseError>)`. @@ -608,7 +608,7 @@ public extension Sequence where Element: ParseInstallation { /** Deletes a collection of installations *synchronously* all at once and throws an error if necessary. - - parameter options: A set of options used to delete installations. Defaults to an empty set. + - parameter options: A set of header options sent to the server. Defaults to an empty set. - returns: Returns `nil` if the delete successful or a `ParseError` if it failed. 1. A `ParseError.Code.aggregateError`. This object's "errors" property is an @@ -634,7 +634,7 @@ public extension Sequence where Element: ParseInstallation { /** Deletes a collection of installations all at once *asynchronously* and executes the completion block when done. - - parameter options: A set of options used to delete installations. Defaults to an empty set. + - 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<[ParseError?], ParseError>)`. diff --git a/Sources/ParseSwift/Objects/ParseObject.swift b/Sources/ParseSwift/Objects/ParseObject.swift index 455ec2dac..c2ca14bf0 100644 --- a/Sources/ParseSwift/Objects/ParseObject.swift +++ b/Sources/ParseSwift/Objects/ParseObject.swift @@ -55,7 +55,7 @@ public extension Sequence where Element: ParseObject { /** Saves a collection of objects *synchronously* all at once and throws an error if necessary. - - parameter options: A set of options used to save objects. Defaults to an empty set. + - parameter options: A set of header options sent to the server. Defaults to an empty set. - returns: Returns a Result enum with the object if a save was successful or a `ParseError` if it failed. - throws: `ParseError` @@ -70,7 +70,7 @@ public extension Sequence where Element: ParseObject { /** Saves a collection of objects all at once *asynchronously* and executes the completion block when done. - - parameter options: A set of options used to save objects. Defaults to an empty set. + - 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<[(Result)], ParseError>)`. @@ -89,7 +89,7 @@ public extension Sequence where Element: ParseObject { /** Fetches a collection of objects *synchronously* all at once and throws an error if necessary. - - parameter options: A set of options used to fetch objects. Defaults to an empty set. + - parameter options: A set of header options sent to the server. Defaults to an empty set. - returns: Returns a Result enum with the object if a fetch was successful or a `ParseError` if it failed. - throws: `ParseError` @@ -123,7 +123,7 @@ public extension Sequence where Element: ParseObject { /** Fetches a collection of objects all at once *asynchronously* and executes the completion block when done. - - parameter options: A set of options used to fetch objects. Defaults to an empty set. + - 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<[(Result)], ParseError>)`. @@ -168,7 +168,7 @@ public extension Sequence where Element: ParseObject { /** Deletes a collection of objects *synchronously* all at once and throws an error if necessary. - - parameter options: A set of options used to delete objects. Defaults to an empty set. + - parameter options: A set of header options sent to the server. Defaults to an empty set. - returns: Returns `nil` if the delete successful or a `ParseError` if it failed. 1. A `ParseError.Code.aggregateError`. This object's "errors" property is an @@ -190,7 +190,7 @@ public extension Sequence where Element: ParseObject { /** Deletes a collection of objects all at once *asynchronously* and executes the completion block when done. - - parameter options: A set of options used to delete objects. Defaults to an empty set. + - 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<[ParseError?], ParseError>)`. @@ -232,7 +232,7 @@ public extension Sequence where Element: ParseObject { /** Saves a collection of objects *synchronously* all at once and throws an error if necessary. - - parameter options: A set of options used to save objects. Defaults to an empty set. + - parameter options: A set of header options sent to the server. Defaults to an empty set. - returns: Returns a Result enum with the object if a save was successful or a `ParseError` if it failed. - throws: `ParseError` @@ -263,7 +263,7 @@ extension ParseObject { /** Fetches the `ParseObject` *synchronously* with the current data from the server and sets an error if one occurs. - - parameter options: A set of options used to save objects. Defaults to an empty set. + - parameter options: A set of header options sent to the server. Defaults to an empty set. - throws: An Error of `ParseError` type. */ public func fetch(options: API.Options = []) throws -> Self { @@ -273,7 +273,7 @@ extension ParseObject { /** Fetches the `ParseObject` *asynchronously* and executes the given callback block. - - parameter options: A set of options used to save objects. Defaults to an empty set. + - 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 when completed. @@ -327,7 +327,7 @@ extension ParseObject { /** Saves the `ParseObject` *synchronously* and throws an error if there's an issue. - - parameter options: A set of options used to save objects. Defaults to an empty set. + - parameter options: A set of header options sent to the server. Defaults to an empty set. - throws: A Error of type `ParseError`. - returns: Returns saved `ParseObject`. @@ -356,7 +356,7 @@ extension ParseObject { /** Saves the `ParseObject` *asynchronously* and executes the given callback block. - - parameter options: A set of options used to save objects. Defaults to an empty set. + - 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)`. @@ -489,7 +489,7 @@ extension ParseObject { /** Deletes the `ParseObject` *synchronously* with the current data from the server and sets an error if one occurs. - - parameter options: A set of options used to save objects. Defaults to an empty set. + - parameter options: A set of header options sent to the server. Defaults to an empty set. - throws: An Error of `ParseError` type. */ public func delete(options: API.Options = []) throws { @@ -501,7 +501,7 @@ extension ParseObject { /** Deletes the `ParseObject` *asynchronously* and executes the given callback block. - - parameter options: A set of options used to save objects. Defaults to an empty set. + - 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 when completed. diff --git a/Sources/ParseSwift/Objects/ParseUser.swift b/Sources/ParseSwift/Objects/ParseUser.swift index eb09a8a50..79e3ba17e 100644 --- a/Sources/ParseSwift/Objects/ParseUser.swift +++ b/Sources/ParseSwift/Objects/ParseUser.swift @@ -217,7 +217,7 @@ extension ParseUser { associated with the user account. This email allows the user to securely. reset their password on the Parse site. - parameter email: The email address associated with the user that forgot their password. - - parameter options: A set of options used to reset the password. Defaults to an empty set. + - parameter options: A set of header options sent to the server. Defaults to an empty set. */ public static func passwordReset(email: String, options: API.Options = []) throws { if let error = try passwordResetCommand(email: email).execute(options: options) { @@ -230,7 +230,7 @@ extension ParseUser { associated with the user account. This email allows the user to securely. reset their password on the Parse site. - parameter email: The email address associated with the user that forgot their password. - - parameter options: A set of options used to reset the password. Defaults to an empty set. + - 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 logging out, completes or fails. */ @@ -266,7 +266,7 @@ extension ParseUser { - 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 options used to sign up users. Defaults to an empty set. + - parameter options: A set of header options sent to the server. Defaults to an empty set. - returns: Returns whether the sign up was successful. */ public static func signup(username: String, @@ -280,7 +280,7 @@ extension ParseUser { 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 options used to sign up users. Defaults to an empty set. + - parameter options: A set of header options sent to the server. Defaults to an empty set. - returns: Returns whether the sign up was successful. */ public func signup(options: API.Options = []) throws -> Self { @@ -293,7 +293,7 @@ extension ParseUser { 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 options used to sign up users. Defaults to an empty set. + - 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)`. @@ -311,7 +311,7 @@ extension ParseUser { - 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 options used to sign up users. Defaults to an empty set. + - 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)`. @@ -393,7 +393,7 @@ extension ParseUser { /** Fetches the `ParseUser` *synchronously* with the current data from the server and sets an error if one occurs. - - parameter options: A set of options used to save users. Defaults to an empty set. + - parameter options: A set of header options sent to the server. Defaults to an empty set. - throws: An Error of `ParseError` type. - important: If an object fetched has the same objectId as current, it will automatically update the current. */ @@ -406,7 +406,7 @@ extension ParseUser { /** Fetches the `ParseUser` *asynchronously* and executes the given callback block. - - parameter options: A set of options used to save users. Defaults to an empty set. + - 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 when completed. @@ -439,7 +439,7 @@ extension ParseUser { /** Saves the `ParseUser` *synchronously* and throws an error if there's an issue. - - parameter options: A set of options used to save users. Defaults to an empty set. + - parameter options: A set of header options sent to the server. Defaults to an empty set. - throws: A Error of type `ParseError`. - returns: Returns saved `ParseUser`. - important: If an object saved has the same objectId as current, it will automatically update the current. @@ -473,7 +473,7 @@ extension ParseUser { /** Saves the `ParseUser` *asynchronously* and executes the given callback block. - - parameter options: A set of options used to save users. Defaults to an empty set. + - 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)`. @@ -507,7 +507,7 @@ extension ParseUser { /** Deletes the `ParseUser` *synchronously* with the current data from the server and sets an error if one occurs. - - parameter options: A set of options used to save users. Defaults to an empty set. + - parameter options: A set of header options sent to the server. Defaults to an empty set. - throws: An Error of `ParseError` type. - important: If an object deleted has the same objectId as current, it will automatically update the current. */ @@ -519,7 +519,7 @@ extension ParseUser { /** Deletes the `ParseUser` *asynchronously* and executes the given callback block. - - parameter options: A set of options used to save users. Defaults to an empty set. + - 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 when completed. @@ -556,7 +556,7 @@ public extension Sequence where Element: ParseUser { /** Saves a collection of users *synchronously* all at once and throws an error if necessary. - - parameter options: A set of options used to save users. Defaults to an empty set. + - parameter options: A set of header options sent to the server. Defaults to an empty set. - returns: Returns a Result enum with the object if a save was successful or a `ParseError` if it failed. - throws: `ParseError` @@ -574,7 +574,7 @@ public extension Sequence where Element: ParseUser { /** Saves a collection of users all at once *asynchronously* and executes the completion block when done. - - parameter options: A set of options used to save users. Defaults to an empty set. + - 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<[(Result)], ParseError>)`. @@ -603,7 +603,7 @@ public extension Sequence where Element: ParseUser { /** Fetches a collection of users *synchronously* all at once and throws an error if necessary. - - parameter options: A set of options used to fetch users. Defaults to an empty set. + - parameter options: A set of header options sent to the server. Defaults to an empty set. - returns: Returns a Result enum with the object if a fetch was successful or a `ParseError` if it failed. - throws: `ParseError` @@ -639,7 +639,7 @@ public extension Sequence where Element: ParseUser { /** Fetches a collection of users all at once *asynchronously* and executes the completion block when done. - - parameter options: A set of options used to fetch users. Defaults to an empty set. + - 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<[(Result)], ParseError>)`. @@ -686,7 +686,7 @@ public extension Sequence where Element: ParseUser { /** Deletes a collection of users *synchronously* all at once and throws an error if necessary. - - parameter options: A set of options used to delete users. Defaults to an empty set. + - parameter options: A set of header options sent to the server. Defaults to an empty set. - returns: Returns `nil` if the delete successful or a `ParseError` if it failed. 1. A `ParseError.Code.aggregateError`. This object's "errors" property is an @@ -712,7 +712,7 @@ public extension Sequence where Element: ParseUser { /** Deletes a collection of users all at once *asynchronously* and executes the completion block when done. - - parameter options: A set of options used to delete users. Defaults to an empty set. + - 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<[ParseError?], ParseError>)`. diff --git a/Sources/ParseSwift/Types/ParseFile.swift b/Sources/ParseSwift/Types/ParseFile.swift index 420b23459..316b041d8 100644 --- a/Sources/ParseSwift/Types/ParseFile.swift +++ b/Sources/ParseSwift/Types/ParseFile.swift @@ -43,7 +43,7 @@ public struct ParseFile: Fileable, Savable, Fetchable, Deletable, Hashable { public var localURL: URL? /** - The link to the file online that should be downloaded. + The link to the file online that should be fetched before uploading to the parse server. */ public var cloudURL: URL? @@ -55,13 +55,13 @@ public struct ParseFile: Fileable, Savable, Fetchable, Deletable, Hashable { /// The Content-Type header to use for the file. public var mimeType: String? - /// Key value pairs to be stored with file object + /// Key value pairs to be stored with the file object. public var metadata: [String: String]? - /// Key value pairs to be stored with file object + /// Key value pairs to be stored with the file object. public var tags: [String: String]? - /// A set of options used to delete files. + /// A set of header options sent to the server. public var options: API.Options = [] /** @@ -75,6 +75,9 @@ public struct ParseFile: Fileable, Savable, Fetchable, Deletable, Hashable { extention of `name`. - parameter metadata: Optional key value pairs to be stored with file object - parameter tags: Optional key value pairs to be stored with file object + - note: `metadata` and `tags` is file adapter specific and not supported by all file adapters. + For more, see details on the + [S3 adapter](https://github.com/parse-community/parse-server-s3-adapter#adding-metadata-and-tags) */ public init(name: String = "file", data: Data? = nil, mimeType: String? = nil, metadata: [String: String]? = nil, tags: [String: String]? = nil, @@ -97,8 +100,11 @@ public struct ParseFile: Fileable, Savable, Fetchable, Deletable, Hashable { - parameter mimeType: Specify the Content-Type header to use for the file, for example "application/pdf". The default is nil. If no value is specified the file type will be inferred from the file extention of `name`. - - parameter metadata: Optional key value pairs to be stored with file object + - parameter metadata: Optional key value pairs to be stored with file object. - parameter tags: Optional key value pairs to be stored with file object + - note: `metadata` and `tags` is file adapter specific and not supported by all file adapters. + For more, see details on the + [S3 adapter](https://github.com/parse-community/parse-server-s3-adapter#adding-metadata-and-tags) */ public init(name: String = "file", localURL: URL, metadata: [String: String]? = nil, tags: [String: String]? = nil, @@ -122,6 +128,9 @@ public struct ParseFile: Fileable, Savable, Fetchable, Deletable, Hashable { extention of `name`. - parameter metadata: Optional key value pairs to be stored with file object - parameter tags: Optional key value pairs to be stored with file object + - note: `metadata` and `tags` is file adapter specific and not supported by all file adapters. + For more, see details on the + [S3 adapter](https://github.com/parse-community/parse-server-s3-adapter#adding-metadata-and-tags) */ public init(name: String = "file", cloudURL: URL, metadata: [String: String]? = nil, tags: [String: String]? = nil, @@ -146,7 +155,7 @@ extension ParseFile { /** Deletes the file from the Parse cloud. - requires: `.useMasterKey` has to be available and passed as one of the set of `options`. - - parameter options: A set of options used to delete files. + - parameter options: A set of header options sent to the server. Defaults to an empty set. - throws: A `ParseError` if there was an issue deleting the file. Otherwise it was successful. */ public func delete(options: API.Options) throws { @@ -163,7 +172,7 @@ extension ParseFile { /** Deletes the file from the Parse cloud. Completes with `nil` if successful. - requires: `.useMasterKey` has to be available and passed as one of the set of `options`. - - parameter options: A set of options used to delete files. + - 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?)` @@ -233,7 +242,7 @@ extension ParseFile { print(currentProgess) } - - parameter options: A set of options used to save files. Defaults to an empty set. + - 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)`. @@ -262,7 +271,7 @@ extension ParseFile { /** Creates a file with given data *synchronously*. 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 options used to save files. Defaults to an empty set. + - parameter options: A set of header options sent to the server. Defaults to an empty set. - returns: A saved `ParseFile`. */ public func save(options: API.Options = []) throws -> ParseFile { @@ -319,7 +328,7 @@ extension ParseFile { print(currentProgess) } - - parameter options: A set of options used to save files. Defaults to an empty set. + - 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)`. @@ -383,7 +392,7 @@ extension ParseFile { ... }) - - parameter options: A set of options used to save files. Defaults to an empty set. + - 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 progress: A block that will be called when file updates it's progress. It should have the following argument signature: `(task: URLSessionDownloadTask, @@ -441,7 +450,7 @@ extension ParseFile { extension ParseFile { /** Fetches a file with given url *synchronously*. - - parameter options: A set of options used to fetch the file. Defaults to an empty set. + - parameter options: A set of header options sent to the server. Defaults to an empty set. - parameter stream: An input file stream. - returns: A saved `ParseFile`. */ @@ -465,7 +474,7 @@ extension ParseFile { /** Fetches a file with given url *synchronously*. - - parameter options: A set of options used to fetch the file. Defaults to an empty set. + - parameter options: A set of header options sent to the server. Defaults to an empty set. - returns: A saved `ParseFile`. */ public func fetch(options: API.Options = []) throws -> ParseFile { @@ -517,7 +526,7 @@ extension ParseFile { print(currentProgess) } - - parameter options: A set of options used to fetch the file. Defaults to an empty set. + - 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)`. @@ -575,7 +584,7 @@ extension ParseFile { ... } - - parameter options: A set of options used to fetch the file. Defaults to an empty set. + - 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 progress: A block that will be called when file updates it's progress. It should have the following argument signature: `(task: URLSessionDownloadTask, diff --git a/Sources/ParseSwift/Types/Query.swift b/Sources/ParseSwift/Types/Query.swift index 95eb09c34..45ba4cd6e 100644 --- a/Sources/ParseSwift/Types/Query.swift +++ b/Sources/ParseSwift/Types/Query.swift @@ -706,7 +706,7 @@ extension Query: Queryable { /** Finds objects *synchronously* based on the constructed query and sets an error if there was one. - - parameter options: A set of options used to save objects. + - parameter options: A set of header options sent to the server. Defaults to an empty set. - throws: An error of type `ParseError`. - returns: Returns an array of `ParseObject`s that were found. @@ -720,7 +720,7 @@ extension Query: Queryable { - 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 options used to save objects. + - parameter options: A set of header options sent to the server. Defaults to an empty set. - throws: An error of type `ParseError`. - returns: Returns a dictionary of `AnyResultType` that is the JSON response of the query. @@ -732,7 +732,7 @@ extension Query: Queryable { /** Finds objects *asynchronously* and calls the given block with the results. - - parameter options: A set of options used to save objects. + - 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<[ResultType], ParseError>)` @@ -747,7 +747,7 @@ extension Query: Queryable { - 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 options used to save objects. + - 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>)` @@ -763,7 +763,7 @@ extension Query: Queryable { Gets an object *synchronously* based on the constructed query and sets an error if any occurred. - warning: This method mutates the query. It will reset the limit to `1`. - - parameter options: A set of options used to save objects. + - parameter options: A set of header options sent to the server. Defaults to an empty set. - throws: An error of type `ParseError`. - returns: Returns a `ParseObject`, or `nil` if none was found. @@ -778,7 +778,7 @@ extension Query: Queryable { - 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 options used to save objects. + - parameter options: A set of header options sent to the server. Defaults to an empty set. - throws: An error of type `ParseError`. - returns: Returns a dictionary of `AnyResultType` that is the JSON response of the query. @@ -791,7 +791,7 @@ extension Query: Queryable { Gets an object *asynchronously* and calls the given block with the result. - warning: This method mutates the query. It will reset the limit to `1`. - - parameter options: A set of options used to save objects. + - 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)`. @@ -819,7 +819,7 @@ extension Query: Queryable { - 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 options used to save objects. + - 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)`. @@ -834,7 +834,7 @@ extension Query: Queryable { /** Counts objects *synchronously* based on the constructed query and sets an error if there was one. - - parameter options: A set of options used to save objects. + - parameter options: A set of header options sent to the server. Defaults to an empty set. - throws: An error of type `ParseError`. - returns: Returns the number of `ParseObject`s that match the query, or `-1` if there is an error. @@ -848,7 +848,7 @@ extension Query: Queryable { - 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 options used to save objects. + - parameter options: A set of header options sent to the server. Defaults to an empty set. - throws: An error of type `ParseError`. - returns: Returns a dictionary of `AnyResultType` that is the JSON response of the query. @@ -860,7 +860,7 @@ extension Query: Queryable { /** Counts objects *asynchronously* and calls the given block with the counts. - - parameter options: A set of options used to save objects. + - 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)` @@ -874,7 +874,7 @@ extension Query: Queryable { Counts objects *asynchronously* and calls the given block with the counts. - 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 options used to save objects. + - 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)` From d12074906cd654e7aef8bcfa19b68b06990b9ae1 Mon Sep 17 00:00:00 2001 From: Corey's iMac Date: Wed, 30 Dec 2020 23:37:39 -0500 Subject: [PATCH 11/44] Added direct calls to Users and Installations endpoints. Added requestEmiailVerificaiton --- .../Contents.swift | 18 +- Sources/ParseSwift/API/API.swift | 18 +- .../Objects/ParseInstallation.swift | 63 +++++++ Sources/ParseSwift/Objects/ParseUser.swift | 154 ++++++++++++++++-- Sources/ParseSwift/Types/Query.swift | 18 +- .../ParseInstallationTests.swift | 61 +++++++ Tests/ParseSwiftTests/ParseQueryTests.swift | 9 + Tests/ParseSwiftTests/ParseUserTests.swift | 149 ++++++++++++++++- 8 files changed, 459 insertions(+), 31 deletions(-) diff --git a/ParseSwift.playground/Pages/4 - User - Continued.xcplaygroundpage/Contents.swift b/ParseSwift.playground/Pages/4 - User - Continued.xcplaygroundpage/Contents.swift index 1337afbfb..e37676353 100644 --- a/ParseSwift.playground/Pages/4 - User - Continued.xcplaygroundpage/Contents.swift +++ b/ParseSwift.playground/Pages/4 - User - Continued.xcplaygroundpage/Contents.swift @@ -35,7 +35,7 @@ User.current?.save { results in case .success(let updatedUser): print("Successfully save myCustomKey to ParseServer: \(updatedUser)") case .failure(let error): - assertionFailure("Failed to update user: \(error)") + print("Failed to update user: \(error)") } } @@ -44,7 +44,7 @@ do { try User.logout() print("Successfully logged out") } catch let error { - assertionFailure("Error logging out: \(error)") + print("Error logging out: \(error)") } /*: Login - asynchronously - Performs work on background @@ -64,7 +64,7 @@ User.login(username: "hello", password: "world") { results in print("Successfully logged in as user: \(user)") case .failure(let error): - assertionFailure("Error logging in: \(error)") + print("Error logging in: \(error)") } } @@ -73,7 +73,15 @@ do { try User.logout() print("Successfully logged out") } catch let error { - assertionFailure("Error logging out: \(error)") + print("Error logging out: \(error)") +} + +//: Password Reset Request - synchronously +do { + try User.verificationEmailRequest(email: "hello@parse.org") + print("Successfully requested verification email be sent") +} catch let error { + print("Error requesting verification email be sent: \(error)") } //: Password Reset Request - synchronously @@ -96,7 +104,7 @@ newUser.signup { result in print("Parse signup successful: \(user)") case .failure(let error): - assertionFailure("Error logging in: \(error)") + print("Error logging in: \(error)") } } diff --git a/Sources/ParseSwift/API/API.swift b/Sources/ParseSwift/API/API.swift index 232978f19..8b61110ef 100644 --- a/Sources/ParseSwift/API/API.swift +++ b/Sources/ParseSwift/API/API.swift @@ -18,11 +18,15 @@ public struct API { case batch case objects(className: String) case object(className: String, objectId: String) + case users + case user(objectId: String) + case installations + case installation(objectId: String) case login - case signup case logout case file(fileName: String) case passwordReset + case verificationEmailRequest case functions(name: String) case jobs(name: String) case any(String) @@ -35,16 +39,24 @@ public struct API { return "/classes/\(className)" case .object(let className, let objectId): return "/classes/\(className)/\(objectId)" + case .users: + return "/users" + case .user(let objectId): + return "/users/\(objectId)" + case .installations: + return "/installations" + case .installation(let objectId): + return "/installations/\(objectId)" case .login: return "/login" - case .signup: - return "/users" case .logout: return "/logout" case .file(let fileName): return "/files/\(fileName)" case .passwordReset: return "/requestPasswordReset" + case .verificationEmailRequest: + return "/verificationEmailRequest" case .functions(name: let name): return "/functions/\(name)" case .jobs(name: let name): diff --git a/Sources/ParseSwift/Objects/ParseInstallation.swift b/Sources/ParseSwift/Objects/ParseInstallation.swift index 2c2884f02..f661b8be6 100644 --- a/Sources/ParseSwift/Objects/ParseInstallation.swift +++ b/Sources/ParseSwift/Objects/ParseInstallation.swift @@ -84,6 +84,17 @@ public extension ParseInstallation { } } +// MARK: Convenience +extension ParseInstallation { + var endpoint: API.Endpoint { + if let objectId = objectId { + return .installation(objectId: objectId) + } + + return .installations + } +} + // MARK: CurrentInstallationContainer struct CurrentInstallationContainer: Codable { var currentInstallation: T? @@ -352,6 +363,17 @@ extension ParseInstallation { completion(.failure(ParseError(code: .unknownError, message: error.localizedDescription))) } } + + func fetchCommand() throws -> API.Command { + guard isSaved else { + throw ParseError(code: .unknownError, message: "Cannot fetch an object without id") + } + + return API.Command(method: .GET, + path: endpoint) { (data) -> Self in + try ParseCoding.jsonDecoder().decode(Self.self, from: data) + } + } } // MARK: Savable @@ -421,6 +443,34 @@ extension ParseInstallation { completion(.failure(parseError)) } } + + func saveCommand() -> API.Command { + if isSaved { + return updateCommand() + } + return createCommand() + } + + // MARK: Saving ParseObjects - private + private func createCommand() -> API.Command { + let mapper = { (data) -> Self in + try ParseCoding.jsonDecoder().decode(SaveResponse.self, from: data).apply(to: self) + } + return API.Command(method: .POST, + path: endpoint, + body: self, + mapper: mapper) + } + + private func updateCommand() -> API.Command { + let mapper = { (data) -> Self in + try ParseCoding.jsonDecoder().decode(UpdateResponse.self, from: data).apply(to: self) + } + return API.Command(method: .PUT, + path: endpoint, + body: self, + mapper: mapper) + } } // MARK: Deletable @@ -470,6 +520,19 @@ extension ParseInstallation { completion(ParseError(code: .unknownError, message: error.localizedDescription)) } } + + func deleteCommand() throws -> API.Command { + guard isSaved else { + throw ParseError(code: .unknownError, message: "Cannot Delete an object without id") + } + + return API.Command( + method: .DELETE, + path: endpoint + ) { (data) -> ParseError? in + try? ParseCoding.jsonDecoder().decode(ParseError.self, from: data) + } + } } // MARK: Batch Support diff --git a/Sources/ParseSwift/Objects/ParseUser.swift b/Sources/ParseSwift/Objects/ParseUser.swift index 79e3ba17e..88688aa72 100644 --- a/Sources/ParseSwift/Objects/ParseUser.swift +++ b/Sources/ParseSwift/Objects/ParseUser.swift @@ -31,8 +31,8 @@ struct SignupBody: Codable { let password: String } -// MARK: PasswordResetBody -struct PasswordResetBody: Codable { +// MARK: EmailBody +struct EmailBody: Codable { let email: String } @@ -43,6 +43,17 @@ public extension ParseUser { } } +// MARK: Convenience +extension ParseUser { + var endpoint: API.Endpoint { + if let objectId = objectId { + return .user(objectId: objectId) + } + + return .users + } +} + // MARK: CurrentUserContainer struct CurrentUserContainer: Codable { var currentUser: T? @@ -200,11 +211,25 @@ extension ParseUser { } internal static func logoutCommand() -> API.Command { - return API.Command(method: .POST, path: .logout) { (data) -> NoBody in - let serverResponse = try ParseCoding.jsonDecoder().decode(NoBody.self, from: data) + return API.Command(method: .POST, path: .logout) { (data) -> NoBody in + var parseError: ParseError? + var serverResponse = NoBody() + do { + serverResponse = try ParseCoding.jsonDecoder().decode(NoBody.self, from: data) + } catch { + if let foundError = error as? ParseError { + parseError = foundError + } else { + parseError = ParseError(code: .unknownError, message: error.localizedDescription) + } + } + //Always let user logout locally, no matter the error. deleteCurrentContainerFromKeychain() currentUserContainer = nil - return serverResponse + guard let error = parseError else { + return serverResponse + } + throw error } } } @@ -214,8 +239,7 @@ extension ParseUser { /** Requests *synchronously* 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 Parse site. + associated with the user account. This email allows the user to securely reset their password on the Parse site. - 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. */ @@ -227,12 +251,11 @@ extension ParseUser { /** 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 Parse site. + associated with the user account. This email allows the user to securely reset their password on the Parse site. - 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. - parameter callbackQueue: The queue to return to after completion. Default value of .main. - - parameter completion: A block that will be called when logging out, completes or fails. + - parameter completion: A block that will be called when the password reset completes or fails. */ public static func passwordReset(email: String, options: API.Options = [], callbackQueue: DispatchQueue = .main, @@ -248,9 +271,58 @@ extension ParseUser { } } - internal static func passwordResetCommand(email: String) -> API.Command { - let resetBody = PasswordResetBody(email: email) - return API.Command(method: .POST, path: .passwordReset, body: resetBody) { (data) -> ParseError? in + internal static func passwordResetCommand(email: String) -> API.Command { + let emailBody = EmailBody(email: email) + return API.Command(method: .POST, path: .passwordReset, body: emailBody) { (data) -> ParseError? in + try? ParseCoding.jsonDecoder().decode(ParseError.self, from: data) + } + } +} + +// MARK: Verification Email Request +extension ParseUser { + + /** + Requests *synchronously* a verification email be sent to the specified email address + associated with the user account. + - 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) { + throw error + } + } + + /** + Requests *asynchronously* a verification email be sent to the specified email address + associated with the user account. + - 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. + - 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) + .executeAsync(options: options, + callbackQueue: callbackQueue) { result in + switch result { + + case .success(let error): + completion(error) + case .failure(let error): + completion(error) + } + } + } + + internal static func verificationEmailRequestCommand(email: String) -> API.Command { + let emailBody = EmailBody(email: email) + return API.Command(method: .POST, path: .verificationEmailRequest, body: emailBody) { (data) -> ParseError? in try? ParseCoding.jsonDecoder().decode(ParseError.self, from: data) } } @@ -331,7 +403,7 @@ extension ParseUser { password: String) -> API.Command { let body = SignupBody(username: username, password: password) - return API.Command(method: .POST, path: .signup, body: body) { (data) -> Self in + return API.Command(method: .POST, path: .users, body: body) { (data) -> Self in let response = try ParseCoding.jsonDecoder().decode(LoginSignupResponse.self, from: data) var user = try ParseCoding.jsonDecoder().decode(Self.self, from: data) @@ -348,7 +420,7 @@ extension ParseUser { } internal func signupCommand() -> API.Command { - return API.Command(method: .POST, path: .signup, body: self) { (data) -> Self in + return API.Command(method: .POST, path: .users, body: self) { (data) -> Self in let response = try ParseCoding.jsonDecoder().decode(LoginSignupResponse.self, from: data) var user = try ParseCoding.jsonDecoder().decode(Self.self, from: data) @@ -431,6 +503,17 @@ extension ParseUser { completion(.failure(ParseError(code: .unknownError, message: error.localizedDescription))) } } + + func fetchCommand() throws -> API.Command { + guard isSaved else { + throw ParseError(code: .unknownError, message: "Cannot fetch an object without id") + } + + return API.Command(method: .GET, + path: endpoint) { (data) -> Self in + try ParseCoding.jsonDecoder().decode(Self.self, from: data) + } + } } // MARK: Savable @@ -500,6 +583,34 @@ extension ParseUser { completion(.failure(parseError)) } } + + func saveCommand() -> API.Command { + if isSaved { + return updateCommand() + } + return createCommand() + } + + // MARK: Saving ParseObjects - private + private func createCommand() -> API.Command { + let mapper = { (data) -> Self in + try ParseCoding.jsonDecoder().decode(SaveResponse.self, from: data).apply(to: self) + } + return API.Command(method: .POST, + path: endpoint, + body: self, + mapper: mapper) + } + + private func updateCommand() -> API.Command { + let mapper = { (data) -> Self in + try ParseCoding.jsonDecoder().decode(UpdateResponse.self, from: data).apply(to: self) + } + return API.Command(method: .PUT, + path: endpoint, + body: self, + mapper: mapper) + } } // MARK: Deletable @@ -548,6 +659,19 @@ extension ParseUser { completion(ParseError(code: .unknownError, message: error.localizedDescription)) } } + + func deleteCommand() throws -> API.Command { + guard isSaved else { + throw ParseError(code: .unknownError, message: "Cannot Delete an object without id") + } + + return API.Command( + method: .DELETE, + path: endpoint + ) { (data) -> ParseError? in + try? ParseCoding.jsonDecoder().decode(ParseError.self, from: data) + } + } } // MARK: Batch Support diff --git a/Sources/ParseSwift/Types/Query.swift b/Sources/ParseSwift/Types/Query.swift index 45ba4cd6e..c49883228 100644 --- a/Sources/ParseSwift/Types/Query.swift +++ b/Sources/ParseSwift/Types/Query.swift @@ -477,8 +477,9 @@ internal struct QueryWhere: Encodable, Equatable { } } +// MARK: Query /** - The `Query` struct defines a query that is used to query for `ParseObject`s. + The `Query` class defines a query that is used to query for `ParseObject`s. */ public class Query: Encodable, Equatable where T: ParseObject { // interpolate as GET @@ -699,6 +700,7 @@ public class Query: Encodable, Equatable where T: ParseObject { } } +// MARK: Queryable extension Query: Queryable { public typealias ResultType = T @@ -951,6 +953,20 @@ private extension Query { } } +// MARK: ParseUser +extension Query where T: ParseUser { + var endpoint: API.Endpoint { + return .users + } +} + +// MARK: ParseInstallation +extension Query where T: ParseInstallation { + var endpoint: API.Endpoint { + return .installations + } +} + enum RawCodingKey: CodingKey { case key(String) var stringValue: String { diff --git a/Tests/ParseSwiftTests/ParseInstallationTests.swift b/Tests/ParseSwiftTests/ParseInstallationTests.swift index 94a0a4ae1..216b0b3e5 100644 --- a/Tests/ParseSwiftTests/ParseInstallationTests.swift +++ b/Tests/ParseSwiftTests/ParseInstallationTests.swift @@ -447,6 +447,25 @@ class ParseInstallationTests: XCTestCase { // swiftlint:disable:this type_body_l self.updateAsync(installation: installation, installationOnServer: installationOnServer, callbackQueue: .main) } + func testFetchCommand() { + var installation = Installation() + let objectId = "yarr" + installation.objectId = objectId + do { + let command = try installation.fetchCommand() + XCTAssertNotNil(command) + XCTAssertEqual(command.path.urlComponent, "/installations/\(objectId)") + XCTAssertEqual(command.method, API.Method.GET) + XCTAssertNil(command.params) + XCTAssertNil(command.body) + } catch { + XCTFail(error.localizedDescription) + } + + let installation2 = Installation() + XCTAssertThrowsError(try installation2.fetchCommand()) + } + func testFetchUpdatedCurrentInstallation() { // swiftlint:disable:this function_body_length testUpdate() MockURLProtocol.removeAll() @@ -606,6 +625,25 @@ class ParseInstallationTests: XCTestCase { // swiftlint:disable:this type_body_l wait(for: [expectation1], timeout: 20.0) } + func testDeleteCommand() { + var installation = Installation() + let objectId = "yarr" + installation.objectId = objectId + do { + let command = try installation.deleteCommand() + XCTAssertNotNil(command) + XCTAssertEqual(command.path.urlComponent, "/installations/\(objectId)") + XCTAssertEqual(command.method, API.Method.DELETE) + XCTAssertNil(command.params) + XCTAssertNil(command.body) + } catch { + XCTFail(error.localizedDescription) + } + + let installation2 = Installation() + XCTAssertThrowsError(try installation2.deleteCommand()) + } + func testDelete() { testUpdate() let expectation1 = XCTestExpectation(description: "Delete installation1") @@ -846,6 +884,29 @@ class ParseInstallationTests: XCTestCase { // swiftlint:disable:this type_body_l wait(for: [expectation1], timeout: 20.0) } + func testSaveCommand() { + let installation = Installation() + let command = installation.saveCommand() + XCTAssertNotNil(command) + XCTAssertEqual(command.path.urlComponent, "/installations") + XCTAssertEqual(command.method, API.Method.POST) + XCTAssertNil(command.params) + XCTAssertNotNil(command.body) + } + + func testUpdateCommand() { + var installation = Installation() + let objectId = "yarr" + installation.objectId = objectId + + let command = installation.saveCommand() + XCTAssertNotNil(command) + XCTAssertEqual(command.path.urlComponent, "/installations/\(objectId)") + XCTAssertEqual(command.method, API.Method.PUT) + XCTAssertNil(command.params) + XCTAssertNotNil(command.body) + } + // swiftlint:disable:next function_body_length func testSaveAll() { testUpdate() diff --git a/Tests/ParseSwiftTests/ParseQueryTests.swift b/Tests/ParseSwiftTests/ParseQueryTests.swift index 93b6b3d4d..153dad73b 100644 --- a/Tests/ParseSwiftTests/ParseQueryTests.swift +++ b/Tests/ParseSwiftTests/ParseQueryTests.swift @@ -77,6 +77,15 @@ class ParseQueryTests: XCTestCase { // swiftlint:disable:this type_body_length XCTAssertEqual(query4.`where`.constraints.values.count, 2) } + func testEndPoints() { + let query = Query() + let userQuery = Query() + let installationQuery = Query() + XCTAssertEqual(query.endpoint.urlComponent, "/classes/GameScore") + XCTAssertEqual(userQuery.endpoint.urlComponent, "/users") + XCTAssertEqual(installationQuery.endpoint.urlComponent, "/installations") + } + func testStaticProperties() { XCTAssertEqual(Query.className, GameScore.className) } diff --git a/Tests/ParseSwiftTests/ParseUserTests.swift b/Tests/ParseSwiftTests/ParseUserTests.swift index a3018dfd5..9e0712c5a 100644 --- a/Tests/ParseSwiftTests/ParseUserTests.swift +++ b/Tests/ParseSwiftTests/ParseUserTests.swift @@ -79,13 +79,12 @@ class ParseUserTests: XCTestCase { // swiftlint:disable:this type_body_length func testFetchCommand() { var user = User() - let className = user.className let objectId = "yarr" user.objectId = objectId do { let command = try user.fetchCommand() XCTAssertNotNil(command) - XCTAssertEqual(command.path.urlComponent, "/classes/\(className)/\(objectId)") + XCTAssertEqual(command.path.urlComponent, "/users/\(objectId)") XCTAssertEqual(command.method, API.Method.GET) XCTAssertNil(command.params) XCTAssertNil(command.body) @@ -93,6 +92,9 @@ class ParseUserTests: XCTestCase { // swiftlint:disable:this type_body_length } catch { XCTFail(error.localizedDescription) } + + let user2 = User() + XCTAssertThrowsError(try user2.fetchCommand()) } func testFetch() { // swiftlint:disable:this function_body_length @@ -379,11 +381,10 @@ class ParseUserTests: XCTestCase { // swiftlint:disable:this type_body_length func testSaveCommand() { let user = User() - let className = user.className let command = user.saveCommand() XCTAssertNotNil(command) - XCTAssertEqual(command.path.urlComponent, "/classes/\(className)") + XCTAssertEqual(command.path.urlComponent, "/users") XCTAssertEqual(command.method, API.Method.POST) XCTAssertNil(command.params) XCTAssertNotNil(command.body) @@ -392,13 +393,12 @@ class ParseUserTests: XCTestCase { // swiftlint:disable:this type_body_length func testUpdateCommand() { var user = User() - let className = user.className let objectId = "yarr" user.objectId = objectId let command = user.saveCommand() XCTAssertNotNil(command) - XCTAssertEqual(command.path.urlComponent, "/classes/\(className)/\(objectId)") + XCTAssertEqual(command.path.urlComponent, "/users/\(objectId)") XCTAssertEqual(command.method, API.Method.PUT) XCTAssertNil(command.params) XCTAssertNotNil(command.body) @@ -980,7 +980,7 @@ class ParseUserTests: XCTestCase { // swiftlint:disable:this type_body_length } func testPasswordResetCommand() throws { - let body = PasswordResetBody(email: "hello@parse.org") + let body = EmailBody(email: "hello@parse.org") let command = User.passwordResetCommand(email: body.email) XCTAssertNotNil(command) XCTAssertEqual(command.path.urlComponent, "/requestPasswordReset") @@ -1095,6 +1095,122 @@ class ParseUserTests: XCTestCase { // swiftlint:disable:this type_body_length self.passwordResetAsyncError(parseError: parseError, callbackQueue: .main) } + func testVerificationEmailRequestCommand() throws { + let body = EmailBody(email: "hello@parse.org") + let command = User.verificationEmailRequestCommand(email: body.email) + XCTAssertNotNil(command) + XCTAssertEqual(command.path.urlComponent, "/verificationEmailRequest") + XCTAssertEqual(command.method, API.Method.POST) + XCTAssertNil(command.params) + XCTAssertEqual(command.body?.email, body.email) + } + + func testVerificationEmailRequestReset() { + let response = NoBody() + + MockURLProtocol.mockRequests { _ in + do { + let encoded = try ParseCoding.jsonEncoder().encode(response) + return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) + } catch { + return nil + } + } + do { + try User.verificationEmailRequest(email: "hello@parse.org") + } catch { + XCTFail(error.localizedDescription) + } + } + + func testVerificationEmailRequestError() { + + let parseError = ParseError(code: .internalServer, message: "Object not found") + + let encoded: Data! + do { + encoded = try ParseCoding.jsonEncoder().encode(parseError) + } catch { + XCTFail("Should encode/decode. Error \(error)") + return + } + + MockURLProtocol.mockRequests { _ in + return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) + } + do { + try User.verificationEmailRequest(email: "hello@parse.org") + XCTFail("Should have thrown ParseError") + } catch { + if let error = error as? ParseError { + XCTAssertEqual(error.code, parseError.code) + } else { + XCTFail("Should have thrown ParseError") + } + } + } + + func verificationEmailRequestAsync(callbackQueue: DispatchQueue) { + + let expectation1 = XCTestExpectation(description: "Logout user1") + User.verificationEmailRequest(email: "hello@parse.org", callbackQueue: callbackQueue) { error in + + guard let error = error else { + expectation1.fulfill() + return + } + XCTFail(error.localizedDescription) + expectation1.fulfill() + } + wait(for: [expectation1], timeout: 10.0) + } + + func testVerificationEmailRequestMainQueue() { + let response = NoBody() + + MockURLProtocol.mockRequests { _ in + do { + let encoded = try ParseCoding.jsonEncoder().encode(response) + return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) + } catch { + return nil + } + } + + self.verificationEmailRequestAsync(callbackQueue: .main) + } + + func verificationEmailRequestAsyncError(parseError: ParseError, callbackQueue: DispatchQueue) { + + let expectation1 = XCTestExpectation(description: "Logout user1") + User.verificationEmailRequest(email: "hello@parse.org", callbackQueue: callbackQueue) { error in + + guard let error = error else { + XCTFail("Should have thrown ParseError") + expectation1.fulfill() + return + } + XCTAssertEqual(error.code, parseError.code) + expectation1.fulfill() + } + wait(for: [expectation1], timeout: 10.0) + } + + func testVerificationEmailRequestMainQueueError() { + let parseError = ParseError(code: .internalServer, message: "Object not found") + + MockURLProtocol.mockRequests { _ in + do { + let encoded = try ParseCoding.jsonEncoder().encode(parseError) + return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) + } catch { + return nil + } + } + + self.verificationEmailRequestAsyncError(parseError: parseError, callbackQueue: .main) + } + func testUserCustomValuesNotSavedToKeychain() { testLogin() User.current?.customKey = "Changed" @@ -1107,6 +1223,25 @@ class ParseUserTests: XCTestCase { // swiftlint:disable:this type_body_length XCTAssertNil(keychainUser.currentUser?.customKey) } + func testDeleteCommand() { + var user = User() + let objectId = "yarr" + user.objectId = objectId + do { + let command = try user.deleteCommand() + XCTAssertNotNil(command) + XCTAssertEqual(command.path.urlComponent, "/users/\(objectId)") + XCTAssertEqual(command.method, API.Method.DELETE) + XCTAssertNil(command.params) + XCTAssertNil(command.body) + } catch { + XCTFail(error.localizedDescription) + } + + let user2 = User() + XCTAssertThrowsError(try user2.deleteCommand()) + } + func testDelete() { testLogin() let expectation1 = XCTestExpectation(description: "Delete installation1") From 2903c3db41d1fe4b654ef3a6136636a19ed09f4f Mon Sep 17 00:00:00 2001 From: Corey's iMac Date: Thu, 31 Dec 2020 00:10:52 -0500 Subject: [PATCH 12/44] Fixed URL error --- Sources/ParseSwift/API/URLSession+extensions.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/ParseSwift/API/URLSession+extensions.swift b/Sources/ParseSwift/API/URLSession+extensions.swift index 0d90440f4..083517787 100755 --- a/Sources/ParseSwift/API/URLSession+extensions.swift +++ b/Sources/ParseSwift/API/URLSession+extensions.swift @@ -86,12 +86,12 @@ extension URLSession { } if let responseData = responseData { - if let error = try? ParseCoding.jsonDecoder().decode(ParseError.self, from: responseData) { - return .failure(error) - } do { return try .success(mapper(responseData)) } catch { + if let error = try? ParseCoding.jsonDecoder().decode(ParseError.self, from: responseData) { + return .failure(error) + } guard let parseError = error as? ParseError else { return .failure(ParseError(code: .unknownError, // swiftlint:disable:next line_length From 5814335198834ae5c62c7a935963883e18605a82 Mon Sep 17 00:00:00 2001 From: Corey Date: Thu, 31 Dec 2020 00:26:30 -0500 Subject: [PATCH 13/44] Bump codecov --- .codecov.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.codecov.yml b/.codecov.yml index 709600189..c9d8e4ab0 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -9,6 +9,6 @@ coverage: changes: false project: default: - target: 72 + target: 77 comment: require_changes: true From f1d820de2927e5c40840fa10865ff0d80c8056e9 Mon Sep 17 00:00:00 2001 From: Corey Date: Thu, 31 Dec 2020 08:31:29 -0500 Subject: [PATCH 14/44] Update Sources/ParseSwift/Types/ParseFile.swift Co-authored-by: Tom Fox <13188249+TomWFox@users.noreply.github.com> --- Sources/ParseSwift/Types/ParseFile.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/ParseSwift/Types/ParseFile.swift b/Sources/ParseSwift/Types/ParseFile.swift index 316b041d8..b0d868b10 100644 --- a/Sources/ParseSwift/Types/ParseFile.swift +++ b/Sources/ParseSwift/Types/ParseFile.swift @@ -43,7 +43,7 @@ public struct ParseFile: Fileable, Savable, Fetchable, Deletable, Hashable { public var localURL: URL? /** - The link to the file online that should be fetched before uploading to the parse server. + The link to the file online that should be fetched before uploading to the Parse Server. */ public var cloudURL: URL? From 95d9b591cddc66adc325d82b6b4dd6d56bbcda66 Mon Sep 17 00:00:00 2001 From: Corey Date: Thu, 31 Dec 2020 08:32:11 -0500 Subject: [PATCH 15/44] Update Sources/ParseSwift/Types/ParseFile.swift Co-authored-by: Tom Fox <13188249+TomWFox@users.noreply.github.com> --- Sources/ParseSwift/Types/ParseFile.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/ParseSwift/Types/ParseFile.swift b/Sources/ParseSwift/Types/ParseFile.swift index b0d868b10..a65d8a811 100644 --- a/Sources/ParseSwift/Types/ParseFile.swift +++ b/Sources/ParseSwift/Types/ParseFile.swift @@ -101,10 +101,10 @@ public struct ParseFile: Fileable, Savable, Fetchable, Deletable, Hashable { "application/pdf". The default is nil. If no value is specified the file type will be inferred from the file extention of `name`. - parameter metadata: Optional key value pairs to be stored with file object. - - parameter tags: Optional key value pairs to be stored with file object + - parameter tags: Optional key value pairs to be stored with file object. - note: `metadata` and `tags` is file adapter specific and not supported by all file adapters. For more, see details on the - [S3 adapter](https://github.com/parse-community/parse-server-s3-adapter#adding-metadata-and-tags) + [S3 adapter](https://github.com/parse-community/parse-server-s3-adapter#adding-metadata-and-tags). */ public init(name: String = "file", localURL: URL, metadata: [String: String]? = nil, tags: [String: String]? = nil, From 4ca7b3de33bd3af9f031ba159ebad87dcaf2f37d Mon Sep 17 00:00:00 2001 From: Corey Date: Thu, 31 Dec 2020 08:32:31 -0500 Subject: [PATCH 16/44] Update Sources/ParseSwift/Types/ParseFile.swift Co-authored-by: Tom Fox <13188249+TomWFox@users.noreply.github.com> --- Sources/ParseSwift/Types/ParseFile.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/ParseSwift/Types/ParseFile.swift b/Sources/ParseSwift/Types/ParseFile.swift index a65d8a811..df7e34aa0 100644 --- a/Sources/ParseSwift/Types/ParseFile.swift +++ b/Sources/ParseSwift/Types/ParseFile.swift @@ -126,11 +126,11 @@ public struct ParseFile: Fileable, Savable, Fetchable, Deletable, Hashable { - parameter mimeType: Specify the Content-Type header to use for the file, for example "application/pdf". The default is nil. If no value is specified the file type will be inferred from the file extention of `name`. - - parameter metadata: Optional key value pairs to be stored with file object - - parameter tags: Optional key value pairs to be stored with file object + - parameter metadata: Optional key value pairs to be stored with file object. + - parameter tags: Optional key value pairs to be stored with file object. - note: `metadata` and `tags` is file adapter specific and not supported by all file adapters. For more, see details on the - [S3 adapter](https://github.com/parse-community/parse-server-s3-adapter#adding-metadata-and-tags) + [S3 adapter](https://github.com/parse-community/parse-server-s3-adapter#adding-metadata-and-tags). */ public init(name: String = "file", cloudURL: URL, metadata: [String: String]? = nil, tags: [String: String]? = nil, From 40c93bb1abe84068f27c1e5fa12992f2404c8bca Mon Sep 17 00:00:00 2001 From: Corey's iMac Date: Thu, 31 Dec 2020 09:21:11 -0500 Subject: [PATCH 17/44] Resolve review, fix playground project --- ParseSwift.playground/contents.xcplayground | 4 +++- Sources/ParseSwift/Objects/ParseUser.swift | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/ParseSwift.playground/contents.xcplayground b/ParseSwift.playground/contents.xcplayground index bf7611d1f..e8d68dcdc 100644 --- a/ParseSwift.playground/contents.xcplayground +++ b/ParseSwift.playground/contents.xcplayground @@ -9,5 +9,7 @@ + + - \ No newline at end of file + diff --git a/Sources/ParseSwift/Objects/ParseUser.swift b/Sources/ParseSwift/Objects/ParseUser.swift index 88688aa72..e1c477d0d 100644 --- a/Sources/ParseSwift/Objects/ParseUser.swift +++ b/Sources/ParseSwift/Objects/ParseUser.swift @@ -239,7 +239,7 @@ extension ParseUser { /** Requests *synchronously* 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 Parse site. + associated with the user account. This email allows the user to securely reset their password on the web. - 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. */ @@ -251,7 +251,7 @@ extension ParseUser { /** 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 Parse site. + associated with the user account. This email allows the user to securely reset their password on the web. - 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. - parameter callbackQueue: The queue to return to after completion. Default value of .main. From 24c66d9452d517d39ce7af7787e63f8525c83bc6 Mon Sep 17 00:00:00 2001 From: Corey's iMac Date: Thu, 31 Dec 2020 09:35:42 -0500 Subject: [PATCH 18/44] Fix backwards compatability with Swift < 5.3. Add a build to CI for Swift 5.2 --- .github/workflows/ci.yml | 21 ++++++++++++++++++- .../ParseSwift/Storage/ParseFileManager.swift | 6 +++--- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 010cfa3d9..8ff4d8e70 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -66,7 +66,26 @@ jobs: run: xcrun llvm-cov export -format="lcov" .build/debug/ParseSwiftPackageTests.xctest/Contents/MacOS/ParseSwiftPackageTests -instr-profile .build/debug/codecov/default.profdata > info.lcov - name: Send codecov run: bash <(curl https://codecov.io/bash) - + + spm-test-5_2: + runs-on: macos-latest + steps: + - uses: actions/checkout@v2 + - name: Create and set the default keychain + run: | + security create-keychain -p "" temporary + security default-keychain -s temporary + security unlock-keychain -p "" temporary + security set-keychain-settings -lut 7200 temporary + - name: Build + run: swift build -v + env: + DEVELOPER_DIR: ${{ env.CI_XCODE_VER }} + - name: Test + run: swift test --enable-code-coverage -v + env: + DEVELOPER_DIR: ${{ env.CI_XCODE_VER }} + docs: needs: spm-test runs-on: macos-latest diff --git a/Sources/ParseSwift/Storage/ParseFileManager.swift b/Sources/ParseSwift/Storage/ParseFileManager.swift index 5432139df..def6081e6 100644 --- a/Sources/ParseSwift/Storage/ParseFileManager.swift +++ b/Sources/ParseSwift/Storage/ParseFileManager.swift @@ -106,7 +106,7 @@ internal struct ParseFileManager { completion(ParseError(code: .unknownError, message: "Couldn't convert string to utf8")) return } - try data.write(to: filePath, options: defaultDataWritingOptions) + try data.write(to: filePath, options: self.defaultDataWritingOptions) completion(nil) } catch { completion(error) @@ -117,7 +117,7 @@ internal struct ParseFileManager { func writeData(_ data: Data, filePath: URL, completion: @escaping(Error?) -> Void) { synchronizationQueue.async { do { - try data.write(to: filePath, options: defaultDataWritingOptions) + try data.write(to: filePath, options: self.defaultDataWritingOptions) completion(nil) } catch { completion(error) @@ -155,7 +155,7 @@ internal struct ParseFileManager { return } - try createDirectoryIfNeeded(toPath.path) + try self.createDirectoryIfNeeded(toPath.path) let contents = try FileManager.default.contentsOfDirectory(atPath: fromPath.path) if contents.count == 0 { completion(nil) From 6c370b684fd4c92bf42269347a89265080f5b698 Mon Sep 17 00:00:00 2001 From: Corey's iMac Date: Thu, 31 Dec 2020 09:39:24 -0500 Subject: [PATCH 19/44] make Swift 5.2 build go after others --- .github/workflows/ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8ff4d8e70..942f65356 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -68,6 +68,7 @@ jobs: run: bash <(curl https://codecov.io/bash) spm-test-5_2: + needs: spm-test runs-on: macos-latest steps: - uses: actions/checkout@v2 From ab827fdfb12a7e88aca40e03867619cb93415470 Mon Sep 17 00:00:00 2001 From: Corey's iMac Date: Thu, 31 Dec 2020 09:44:32 -0500 Subject: [PATCH 20/44] make other CI builds go after watchOS build since it never tests --- .github/workflows/ci.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 942f65356..51a5d2c87 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -68,7 +68,7 @@ jobs: run: bash <(curl https://codecov.io/bash) spm-test-5_2: - needs: spm-test + needs: xcode-build-watchos runs-on: macos-latest steps: - uses: actions/checkout@v2 @@ -88,7 +88,7 @@ jobs: DEVELOPER_DIR: ${{ env.CI_XCODE_VER }} docs: - needs: spm-test + needs: xcode-build-watchos runs-on: macos-latest steps: - uses: actions/checkout@v2 @@ -114,7 +114,7 @@ jobs: publish_dir: ./docs cocoapods: - needs: spm-test + needs: xcode-build-watchos runs-on: macos-latest steps: - uses: actions/checkout@v2 @@ -122,7 +122,7 @@ jobs: run: pod lib lint --allow-warnings carthage: - needs: spm-test + needs: xcode-build-watchos runs-on: macos-latest steps: - uses: actions/checkout@v2 From bad1648fd860840d5f51f63c4747e10a49623f4a Mon Sep 17 00:00:00 2001 From: Corey's iMac Date: Thu, 31 Dec 2020 11:33:44 -0500 Subject: [PATCH 21/44] Add LocallyIdentifiable protocol --- ParseSwift.xcodeproj/project.pbxproj | 10 ++++++ .../Objects/Protocols/Fileable.swift | 9 +----- .../Protocols/LocallyIdentifiable.swift | 31 +++++++++++++++++++ Sources/ParseSwift/Types/ParseFile.swift | 12 ++----- Tests/ParseSwiftTests/ParseFileTests.swift | 10 +++--- 5 files changed, 49 insertions(+), 23 deletions(-) create mode 100644 Sources/ParseSwift/Objects/Protocols/LocallyIdentifiable.swift diff --git a/ParseSwift.xcodeproj/project.pbxproj b/ParseSwift.xcodeproj/project.pbxproj index a4525b1e5..b70d228f7 100644 --- a/ParseSwift.xcodeproj/project.pbxproj +++ b/ParseSwift.xcodeproj/project.pbxproj @@ -52,6 +52,10 @@ 705A9A3025991C1400B3547F /* Fileable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 705A9A2E25991C1400B3547F /* Fileable.swift */; }; 705A9A3125991C1400B3547F /* Fileable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 705A9A2E25991C1400B3547F /* Fileable.swift */; }; 705A9A3225991C1400B3547F /* Fileable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 705A9A2E25991C1400B3547F /* Fileable.swift */; }; + 70647E8E259E3375004C1004 /* LocallyIdentifiable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70647E8D259E3375004C1004 /* LocallyIdentifiable.swift */; }; + 70647E8F259E3375004C1004 /* LocallyIdentifiable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70647E8D259E3375004C1004 /* LocallyIdentifiable.swift */; }; + 70647E90259E3375004C1004 /* LocallyIdentifiable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70647E8D259E3375004C1004 /* LocallyIdentifiable.swift */; }; + 70647E91259E3375004C1004 /* LocallyIdentifiable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70647E8D259E3375004C1004 /* LocallyIdentifiable.swift */; }; 708D035225215F9B00646C70 /* Deletable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 708D035125215F9B00646C70 /* Deletable.swift */; }; 708D035325215F9B00646C70 /* Deletable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 708D035125215F9B00646C70 /* Deletable.swift */; }; 708D035425215F9B00646C70 /* Deletable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 708D035125215F9B00646C70 /* Deletable.swift */; }; @@ -333,6 +337,7 @@ 705727882593FF8000F0ADD5 /* ParseFileTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParseFileTests.swift; sourceTree = ""; }; 705A99F8259807F900B3547F /* ParseFileManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParseFileManagerTests.swift; sourceTree = ""; }; 705A9A2E25991C1400B3547F /* Fileable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Fileable.swift; sourceTree = ""; }; + 70647E8D259E3375004C1004 /* LocallyIdentifiable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocallyIdentifiable.swift; sourceTree = ""; }; 708D035125215F9B00646C70 /* Deletable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Deletable.swift; sourceTree = ""; }; 709B98302556EC7400507778 /* ParseSwiftTeststvOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ParseSwiftTeststvOS.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 709B98342556EC7400507778 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -612,6 +617,7 @@ 70BC988F252A5B5C00FF3074 /* Objectable.swift */, F97B45C824D9C6F200F4A88B /* Queryable.swift */, F97B45C724D9C6F200F4A88B /* Savable.swift */, + 70647E8D259E3375004C1004 /* LocallyIdentifiable.swift */, ); path = Protocols; sourceTree = ""; @@ -1178,6 +1184,7 @@ F97B464624D9C78B00F4A88B /* ParseMutationContainer.swift in Sources */, 705A9A2F25991C1400B3547F /* Fileable.swift in Sources */, F97B464A24D9C78B00F4A88B /* DeleteOperation.swift in Sources */, + 70647E8E259E3375004C1004 /* LocallyIdentifiable.swift in Sources */, F97B460624D9C6F200F4A88B /* ParseUser.swift in Sources */, F97B465A24D9C78C00F4A88B /* IncrementOperation.swift in Sources */, F97B45E224D9C6F200F4A88B /* AnyEncodable.swift in Sources */, @@ -1256,6 +1263,7 @@ F97B464724D9C78B00F4A88B /* ParseMutationContainer.swift in Sources */, 705A9A3025991C1400B3547F /* Fileable.swift in Sources */, F97B464B24D9C78B00F4A88B /* DeleteOperation.swift in Sources */, + 70647E8F259E3375004C1004 /* LocallyIdentifiable.swift in Sources */, F97B460724D9C6F200F4A88B /* ParseUser.swift in Sources */, F97B465B24D9C78C00F4A88B /* IncrementOperation.swift in Sources */, F97B45E324D9C6F200F4A88B /* AnyEncodable.swift in Sources */, @@ -1370,6 +1378,7 @@ F97B45ED24D9C6F200F4A88B /* ParseGeoPoint.swift in Sources */, 705A9A3225991C1400B3547F /* Fileable.swift in Sources */, F97B45F524D9C6F200F4A88B /* Pointer.swift in Sources */, + 70647E91259E3375004C1004 /* LocallyIdentifiable.swift in Sources */, F97B460924D9C6F200F4A88B /* ParseUser.swift in Sources */, F97B463A24D9C74400F4A88B /* Responses.swift in Sources */, F97B45DD24D9C6F200F4A88B /* Extensions.swift in Sources */, @@ -1421,6 +1430,7 @@ F97B45EC24D9C6F200F4A88B /* ParseGeoPoint.swift in Sources */, 705A9A3125991C1400B3547F /* Fileable.swift in Sources */, F97B45F424D9C6F200F4A88B /* Pointer.swift in Sources */, + 70647E90259E3375004C1004 /* LocallyIdentifiable.swift in Sources */, F97B460824D9C6F200F4A88B /* ParseUser.swift in Sources */, F97B463924D9C74400F4A88B /* Responses.swift in Sources */, F97B45DC24D9C6F200F4A88B /* Extensions.swift in Sources */, diff --git a/Sources/ParseSwift/Objects/Protocols/Fileable.swift b/Sources/ParseSwift/Objects/Protocols/Fileable.swift index f0467a074..add8e8efa 100644 --- a/Sources/ParseSwift/Objects/Protocols/Fileable.swift +++ b/Sources/ParseSwift/Objects/Protocols/Fileable.swift @@ -8,11 +8,10 @@ import Foundation -protocol Fileable: Encodable { +protocol Fileable: LocallyIdentifiable { var __type: String { get } // swiftlint:disable:this identifier_name var name: String { get set } var url: URL? { get set } - var localUUID: UUID { mutating get } } extension Fileable { @@ -30,10 +29,4 @@ extension Fileable { } return lURL == rURL } - - //Hashable - public func hash(into hasher: inout Hasher) { - var fileable = self - hasher.combine(fileable.localUUID) - } } diff --git a/Sources/ParseSwift/Objects/Protocols/LocallyIdentifiable.swift b/Sources/ParseSwift/Objects/Protocols/LocallyIdentifiable.swift new file mode 100644 index 000000000..9b56b8ee3 --- /dev/null +++ b/Sources/ParseSwift/Objects/Protocols/LocallyIdentifiable.swift @@ -0,0 +1,31 @@ +// +// LocallyIdentifiable.swift +// ParseSwift +// +// Created by Corey Baker on 12/31/20. +// Copyright © 2020 Parse Community. All rights reserved. +// + +import Foundation + +protocol LocallyIdentifiable: Encodable, Hashable { + var __localUUID: UUID? { get set } // swiftlint:disable:this identifier_name +} + +extension LocallyIdentifiable { + var localUUID: UUID { + mutating get { + if self.__localUUID == nil { + self.__localUUID = UUID() + } + return __localUUID! + } + } + + // Equatable + public static func == (lhs: Self, rhs: Self) -> Bool { + var lhs = lhs + var rhs = rhs + return lhs.localUUID == rhs.localUUID + } +} diff --git a/Sources/ParseSwift/Types/ParseFile.swift b/Sources/ParseSwift/Types/ParseFile.swift index df7e34aa0..8a44c4d69 100644 --- a/Sources/ParseSwift/Types/ParseFile.swift +++ b/Sources/ParseSwift/Types/ParseFile.swift @@ -4,7 +4,7 @@ import Foundation A `ParseFile` object representes a file of binary data stored on the Parse server. This can be a image, video, or anything else that an application needs to reference in a non-relational way. */ -public struct ParseFile: Fileable, Savable, Fetchable, Deletable, Hashable { +public struct ParseFile: Fileable, Savable, Fetchable, Deletable { internal let __type: String = "File" // swiftlint:disable:this identifier_name @@ -15,15 +15,7 @@ public struct ParseFile: Fileable, Savable, Fetchable, Deletable, Hashable { && data == nil } - internal var _localUUID: UUID? // swiftlint:disable:this identifier_name - internal var localUUID: UUID { - mutating get { - if self._localUUID == nil { - self._localUUID = UUID() - } - return _localUUID! - } - } + internal var __localUUID: UUID? // swiftlint:disable:this identifier_name /** The name of the file. diff --git a/Tests/ParseSwiftTests/ParseFileTests.swift b/Tests/ParseSwiftTests/ParseFileTests.swift index 81de5c6fa..8c34f27c5 100644 --- a/Tests/ParseSwiftTests/ParseFileTests.swift +++ b/Tests/ParseSwiftTests/ParseFileTests.swift @@ -141,11 +141,11 @@ class ParseFileTests: XCTestCase { // swiftlint:disable:this type_body_length 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) - let localUUID = parseFile._localUUID + var parseFile = ParseFile(name: "sampleData.txt", data: sampleData) + let localUUID = parseFile.__localUUID XCTAssertNotNil(localUUID) XCTAssertEqual(localUUID, - parseFile._localUUID, + parseFile.localUUID, "localUUID should remain the same no matter how many times the getter is called") } @@ -171,8 +171,8 @@ class ParseFileTests: XCTestCase { // swiftlint:disable:this type_body_length parseFile2.url = nil XCTAssertNotEqual(parseFile1, parseFile2, "no urls, but localUUIDs shoud be different") let uuid = UUID() - parseFile1._localUUID = uuid - parseFile2._localUUID = uuid + parseFile1.__localUUID = uuid + parseFile2.__localUUID = uuid XCTAssertEqual(parseFile1, parseFile2, "no urls, but localUUIDs shoud be the same") } From d79984f24fa4a37a59560746b2ec4fa2a6281c91 Mon Sep 17 00:00:00 2001 From: Corey's iMac Date: Thu, 31 Dec 2020 17:21:21 -0500 Subject: [PATCH 22/44] wip --- ParseSwift.xcodeproj/project.pbxproj | 10 ++ Sources/ParseSwift/API/API+Commands.swift | 147 +++++++++++++++--- Sources/ParseSwift/API/BatchUtils.swift | 14 +- Sources/ParseSwift/Coding/ParseCoding.swift | 6 +- Sources/ParseSwift/Coding/ParseEncoder.swift | 52 ++++--- .../Objects/ParseInstallation.swift | 4 +- Sources/ParseSwift/Objects/ParseObject.swift | 12 +- Sources/ParseSwift/Objects/ParseUser.swift | 16 +- .../Objects/Protocols/Fileable.swift | 2 +- .../Protocols/LocallyIdentifiable.swift | 6 +- .../Objects/Protocols/Objectable.swift | 2 +- .../Objects/Protocols/ParseType.swift | 11 ++ Sources/ParseSwift/Types/ParseACL.swift | 2 +- Sources/ParseSwift/Types/ParseCloud.swift | 4 +- Sources/ParseSwift/Types/ParseError.swift | 2 +- Sources/ParseSwift/Types/ParseFile.swift | 2 +- Sources/ParseSwift/Types/ParseGeoPoint.swift | 2 +- Sources/ParseSwift/Types/Pointer.swift | 8 +- Sources/ParseSwift/Types/Query.swift | 2 +- Tests/ParseSwiftTests/ACLTests.swift | 4 +- Tests/ParseSwiftTests/APICommandTests.swift | 20 ++- Tests/ParseSwiftTests/ParseCloudTests.swift | 4 +- Tests/ParseSwiftTests/ParseEncoderTests.swift | 12 +- .../ParseInstallationTests.swift | 12 +- .../ParseObjectBatchTests.swift | 6 +- Tests/ParseSwiftTests/ParseObjectTests.swift | 29 ++-- Tests/ParseSwiftTests/ParsePointerTests.swift | 6 +- Tests/ParseSwiftTests/ParseUserTests.swift | 28 ++-- 28 files changed, 286 insertions(+), 139 deletions(-) create mode 100644 Sources/ParseSwift/Objects/Protocols/ParseType.swift diff --git a/ParseSwift.xcodeproj/project.pbxproj b/ParseSwift.xcodeproj/project.pbxproj index b70d228f7..1b936b770 100644 --- a/ParseSwift.xcodeproj/project.pbxproj +++ b/ParseSwift.xcodeproj/project.pbxproj @@ -56,6 +56,10 @@ 70647E8F259E3375004C1004 /* LocallyIdentifiable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70647E8D259E3375004C1004 /* LocallyIdentifiable.swift */; }; 70647E90259E3375004C1004 /* LocallyIdentifiable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70647E8D259E3375004C1004 /* LocallyIdentifiable.swift */; }; 70647E91259E3375004C1004 /* LocallyIdentifiable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70647E8D259E3375004C1004 /* LocallyIdentifiable.swift */; }; + 70647E9C259E3A9A004C1004 /* ParseType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70647E9B259E3A9A004C1004 /* ParseType.swift */; }; + 70647E9D259E3A9A004C1004 /* ParseType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70647E9B259E3A9A004C1004 /* ParseType.swift */; }; + 70647E9E259E3A9A004C1004 /* ParseType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70647E9B259E3A9A004C1004 /* ParseType.swift */; }; + 70647E9F259E3A9A004C1004 /* ParseType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70647E9B259E3A9A004C1004 /* ParseType.swift */; }; 708D035225215F9B00646C70 /* Deletable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 708D035125215F9B00646C70 /* Deletable.swift */; }; 708D035325215F9B00646C70 /* Deletable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 708D035125215F9B00646C70 /* Deletable.swift */; }; 708D035425215F9B00646C70 /* Deletable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 708D035125215F9B00646C70 /* Deletable.swift */; }; @@ -338,6 +342,7 @@ 705A99F8259807F900B3547F /* ParseFileManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParseFileManagerTests.swift; sourceTree = ""; }; 705A9A2E25991C1400B3547F /* Fileable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Fileable.swift; sourceTree = ""; }; 70647E8D259E3375004C1004 /* LocallyIdentifiable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocallyIdentifiable.swift; sourceTree = ""; }; + 70647E9B259E3A9A004C1004 /* ParseType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParseType.swift; sourceTree = ""; }; 708D035125215F9B00646C70 /* Deletable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Deletable.swift; sourceTree = ""; }; 709B98302556EC7400507778 /* ParseSwiftTeststvOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ParseSwiftTeststvOS.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 709B98342556EC7400507778 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -618,6 +623,7 @@ F97B45C824D9C6F200F4A88B /* Queryable.swift */, F97B45C724D9C6F200F4A88B /* Savable.swift */, 70647E8D259E3375004C1004 /* LocallyIdentifiable.swift */, + 70647E9B259E3A9A004C1004 /* ParseType.swift */, ); path = Protocols; sourceTree = ""; @@ -1211,6 +1217,7 @@ F97B45FA24D9C6F200F4A88B /* ParseACL.swift in Sources */, 70BDA2B3250536FF00FC2237 /* ParseInstallation.swift in Sources */, F97B462724D9C72700F4A88B /* API.swift in Sources */, + 70647E9C259E3A9A004C1004 /* ParseType.swift in Sources */, 70110D572506CE890091CC1D /* BaseParseInstallation.swift in Sources */, F97B45DE24D9C6F200F4A88B /* AnyCodable.swift in Sources */, ); @@ -1290,6 +1297,7 @@ F97B45FB24D9C6F200F4A88B /* ParseACL.swift in Sources */, 70BDA2B4250536FF00FC2237 /* ParseInstallation.swift in Sources */, F97B462824D9C72700F4A88B /* API.swift in Sources */, + 70647E9D259E3A9A004C1004 /* ParseType.swift in Sources */, 70110D582506CE890091CC1D /* BaseParseInstallation.swift in Sources */, F97B45DF24D9C6F200F4A88B /* AnyCodable.swift in Sources */, ); @@ -1405,6 +1413,7 @@ F97B463224D9C74400F4A88B /* BatchUtils.swift in Sources */, F97B45F124D9C6F200F4A88B /* BaseParseUser.swift in Sources */, F97B45D924D9C6F200F4A88B /* ParseEncoder.swift in Sources */, + 70647E9F259E3A9A004C1004 /* ParseType.swift in Sources */, 912C9BFD24D302B2009947C3 /* Parse.swift in Sources */, F97B461924D9C6F200F4A88B /* Queryable.swift in Sources */, ); @@ -1457,6 +1466,7 @@ F97B463124D9C74400F4A88B /* BatchUtils.swift in Sources */, F97B45F024D9C6F200F4A88B /* BaseParseUser.swift in Sources */, F97B45D824D9C6F200F4A88B /* ParseEncoder.swift in Sources */, + 70647E9E259E3A9A004C1004 /* ParseType.swift in Sources */, 912C9BE024D302B0009947C3 /* Parse.swift in Sources */, F97B461824D9C6F200F4A88B /* Queryable.swift in Sources */, ); diff --git a/Sources/ParseSwift/API/API+Commands.swift b/Sources/ParseSwift/API/API+Commands.swift index 1f3a43da9..3a113cc3a 100644 --- a/Sources/ParseSwift/API/API+Commands.swift +++ b/Sources/ParseSwift/API/API+Commands.swift @@ -13,7 +13,7 @@ import FoundationNetworking internal extension API { // swiftlint:disable:next type_body_length - struct Command: Encodable where T: Encodable { + struct Command: Encodable where T: ParseType { typealias ReturnType = U // swiftlint:disable:this nesting let method: API.Method let path: API.Endpoint @@ -270,7 +270,13 @@ internal extension API { var urlRequest = URLRequest(url: urlComponents) urlRequest.allHTTPHeaderFields = headers if let urlBody = body { - if let childObjects = childObjects { + if (urlBody as? ParseCloud) != nil { + guard let bodyData = try? ParseCoding.parseEncoder().encode(urlBody, skipKeys: .cloud) else { + return .failure(ParseError(code: .unknownError, + message: "couldn't encode body \(urlBody)")) + } + urlRequest.httpBody = bodyData + } else { guard let bodyData = try? ParseCoding .parseEncoder() .encode(urlBody, collectChildren: false, @@ -279,20 +285,6 @@ internal extension API { message: "couldn't encode body \(urlBody)")) } urlRequest.httpBody = bodyData.encoded - } else { - if (urlBody as? ParseCloud) != nil { - guard let bodyData = try? ParseCoding.parseEncoder().encode(urlBody) else { - return .failure(ParseError(code: .unknownError, - message: "couldn't encode body \(urlBody)")) - } - urlRequest.httpBody = bodyData - } else { - guard let bodyData = try? ParseCoding.jsonEncoder().encode(urlBody) else { - return .failure(ParseError(code: .unknownError, - message: "couldn't encode body \(urlBody)")) - } - urlRequest.httpBody = bodyData - } } } urlRequest.httpMethod = method.rawValue @@ -452,14 +444,122 @@ internal extension API.Command { try ParseCoding.jsonDecoder().decode(T.self, from: data) } } +} + +internal extension API { + struct NonParseBodyCommand: Encodable where T: Encodable { + typealias ReturnType = U // swiftlint:disable:this nesting + let method: API.Method + let path: API.Endpoint + let body: T? + let mapper: ((Data) throws -> U) + let params: [String: String?]? + + init(method: API.Method, + path: API.Endpoint, + params: [String: String]? = nil, + body: T? = nil, + mapper: @escaping ((Data) throws -> U)) { + self.method = method + self.path = path + self.body = body + self.mapper = mapper + self.params = params + } + + func execute(options: API.Options) throws -> U { + var responseResult: Result? + let group = DispatchGroup() + group.enter() + self.executeAsync(options: options, + callbackQueue: nil) { result in + responseResult = result + group.leave() + } + group.wait() + + guard let response = responseResult else { + throw ParseError(code: .unknownError, + message: "couldn't unrwrap server response") + } + return try response.get() + } + + // MARK: Asynchronous Execution + func executeAsync(options: API.Options, callbackQueue: DispatchQueue?, + completion: @escaping(Result) -> Void) { + + switch self.prepareURLRequest(options: options) { + case .success(let urlRequest): + URLSession.shared.dataTask(with: urlRequest, mapper: mapper) { result in + switch result { + + case .success(let decoded): + if let callbackQueue = callbackQueue { + callbackQueue.async { completion(.success(decoded)) } + } else { + completion(.success(decoded)) + } + + case .failure(let error): + if let callbackQueue = callbackQueue { + callbackQueue.async { completion(.failure(error)) } + } else { + completion(.failure(error)) + } + } + } + case .failure(let error): + completion(.failure(error)) + } + } + + // MARK: URL Preperation + func prepareURLRequest(options: API.Options) -> Result { + let params = self.params?.getQueryItems() + let headers = API.getHeaders(options: options) + let url = ParseConfiguration.serverURL.appendingPathComponent(path.urlComponent) + + guard var components = URLComponents(url: url, resolvingAgainstBaseURL: false) else { + return .failure(ParseError(code: .unknownError, + message: "couldn't unrwrap url components for \(url)")) + } + components.queryItems = params + + guard let urlComponents = components.url else { + return .failure(ParseError(code: .unknownError, + message: "couldn't create url from components for \(components)")) + } + + var urlRequest = URLRequest(url: urlComponents) + urlRequest.allHTTPHeaderFields = headers + if let urlBody = body { + guard let bodyData = try? ParseCoding.jsonEncoder().encode(urlBody) else { + return .failure(ParseError(code: .unknownError, + message: "couldn't encode body \(urlBody)")) + } + urlRequest.httpBody = bodyData + } + urlRequest.httpMethod = method.rawValue + + return .success(urlRequest) + } + + enum CodingKeys: String, CodingKey { // swiftlint:disable:this nesting + case method, body, path + } + } +} +internal extension API.NonParseBodyCommand { // MARK: Deleting - static func deleteCommand(_ object: T) throws -> API.Command where T: ParseObject { + // swiftlint:disable:next line_length + 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.Command( + return API.NonParseBodyCommand( method: .DELETE, path: object.endpoint ) { (data) -> ParseError? in @@ -473,7 +573,7 @@ extension API.Command where T: ParseObject { internal var data: Data? { guard let body = body else { return nil } - return try? body.getEncoder().encode(body) + return try? body.getEncoder().encode(body, skipKeys: .object) } static func batch(commands: [API.Command]) -> RESTBatchCommandType { @@ -523,10 +623,11 @@ extension API.Command where T: ParseObject { } // MARK: Batch - Deleting - static func batch(commands: [API.Command]) -> RESTBatchCommandNoBodyType { - let commands = commands.compactMap { (command) -> API.Command? in + // swiftlint:disable:next line_length + static func batch(commands: [API.NonParseBodyCommand]) -> RESTBatchCommandNoBodyType { + let commands = commands.compactMap { (command) -> API.NonParseBodyCommand? in let path = ParseConfiguration.mountPath + command.path.urlComponent - return API.Command( + return API.NonParseBodyCommand( method: command.method, path: .any(path), mapper: command.mapper) } @@ -552,7 +653,7 @@ extension API.Command where T: ParseObject { } } - let batchCommand = BatchCommand(requests: commands) + let batchCommand = BatchCommandNoBody(requests: commands) return RESTBatchCommandNoBodyType(method: .POST, path: .batch, body: batchCommand, mapper: mapper) } } diff --git a/Sources/ParseSwift/API/BatchUtils.swift b/Sources/ParseSwift/API/BatchUtils.swift index 1847caa51..bc8feaa4b 100644 --- a/Sources/ParseSwift/API/BatchUtils.swift +++ b/Sources/ParseSwift/API/BatchUtils.swift @@ -13,17 +13,21 @@ typealias ParseObjectBatchResponse = [(Result)] // swiftlint:disable line_length typealias RESTBatchCommandType = API.Command, ParseObjectBatchResponse> where T: ParseObject -typealias ParseObjectBatchCommandNoBody = BatchCommand +typealias ParseObjectBatchCommandNoBody = BatchCommandNoBody typealias ParseObjectBatchResponseNoBody = [ParseError?] -typealias RESTBatchCommandNoBodyType = API.Command, ParseObjectBatchResponseNoBody> where T: Codable +typealias RESTBatchCommandNoBodyType = API.NonParseBodyCommand, ParseObjectBatchResponseNoBody> where T: Encodable -typealias ParseObjectBatchCommandEncodable = BatchCommand where T: Encodable +typealias ParseObjectBatchCommandEncodable = BatchCommand where T: ParseType typealias ParseObjectBatchResponseEncodable = [(Result)] // swiftlint:disable line_length -typealias RESTBatchCommandTypeEncodable = API.Command, ParseObjectBatchResponseEncodable> where T: Encodable +typealias RESTBatchCommandTypeEncodable = API.Command, ParseObjectBatchResponseEncodable> where T: ParseType // swiftlint:enable line_length -internal struct BatchCommand: Encodable where T: Encodable { +internal struct BatchCommand: ParseType where T: ParseType { let requests: [API.Command] } + +internal struct BatchCommandNoBody: Encodable where T: Encodable { + let requests: [API.NonParseBodyCommand] +} diff --git a/Sources/ParseSwift/Coding/ParseCoding.swift b/Sources/ParseSwift/Coding/ParseCoding.swift index 773a48f34..57d8e84ef 100644 --- a/Sources/ParseSwift/Coding/ParseCoding.swift +++ b/Sources/ParseSwift/Coding/ParseCoding.swift @@ -13,8 +13,6 @@ public enum ParseCoding {} // MARK: Coders extension ParseCoding { - private static let forbiddenKeys = Set(["createdAt", "updatedAt", "objectId", "className"]) - private static let forbiddenCloudKeys = Set(["functionJobName"]) ///This should only be used for Unit tests, don't use in SDK static func jsonEncoder() -> JSONEncoder { @@ -31,9 +29,7 @@ extension ParseCoding { static func parseEncoder(skipKeys: Bool = true) -> ParseEncoder { ParseEncoder( - dateEncodingStrategy: parseDateEncodingStrategy, - skippingKeys: skipKeys ? forbiddenKeys : [], - skippingCloudKeys: skipKeys ? forbiddenCloudKeys : [] + dateEncodingStrategy: parseDateEncodingStrategy ) } } diff --git a/Sources/ParseSwift/Coding/ParseEncoder.swift b/Sources/ParseSwift/Coding/ParseEncoder.swift index 5e2af2c98..bff9066b3 100644 --- a/Sources/ParseSwift/Coding/ParseEncoder.swift +++ b/Sources/ParseSwift/Coding/ParseEncoder.swift @@ -52,33 +52,42 @@ extension Dictionary: _JSONStringDictionaryEncodableMarker where Key == String, public struct ParseEncoder { let dateEncodingStrategy: AnyCodable.DateEncodingStrategy? let jsonEncoder: JSONEncoder - let skippedKeys: Set - let skippedCloudKeys: Set + //var skippedKeys: SkippedKeys + + public enum SkippedKeys { + case object + case cloud + case none + case custom(Set) + + func use() -> Set { + switch self { + + case .object: + return Set(["createdAt", "updatedAt", "objectId", "className"]) + case .cloud: + return Set(["functionJobName"]) + case .none: + return .init() + case .custom(let keys): + return keys + } + } + } init( dateEncodingStrategy: AnyCodable.DateEncodingStrategy? = nil, - jsonEncoder: JSONEncoder = JSONEncoder(), - skippingKeys: Set = [], - skippingCloudKeys: Set = [] + jsonEncoder: JSONEncoder = JSONEncoder() ) { self.dateEncodingStrategy = dateEncodingStrategy self.jsonEncoder = jsonEncoder - self.skippedKeys = skippingKeys - self.skippedCloudKeys = skippingCloudKeys if let dateEncodingStrategy = dateEncodingStrategy { self.jsonEncoder.dateEncodingStrategy = .custom(dateEncodingStrategy) } } - public func encode(_ value: T) throws -> Data { - let encoder = _ParseEncoder(codingPath: [], dictionary: NSMutableDictionary(), skippingKeys: skippedCloudKeys) - if let dateEncodingStrategy = dateEncodingStrategy { - encoder.dateEncodingStrategy = .custom(dateEncodingStrategy) - } - return try encoder.encodeObject(value, collectChildren: false, objectsSavedBeforeThisOne: nil, filesSavedBeforeThisOne: nil).encoded - } - - public func encode(_ value: T) throws -> Data { + public func encode(_ value: T, skipKeys: SkippedKeys) throws -> Data { + let skippedKeys = skipKeys.use() let encoder = _ParseEncoder(codingPath: [], dictionary: NSMutableDictionary(), skippingKeys: skippedKeys) if let dateEncodingStrategy = dateEncodingStrategy { encoder.dateEncodingStrategy = .custom(dateEncodingStrategy) @@ -87,9 +96,10 @@ public struct ParseEncoder { } // swiftlint:disable large_tuple - internal func encode(_ value: Encodable, collectChildren: Bool, + internal func encode(_ value: ParseType, collectChildren: Bool, objectsSavedBeforeThisOne: [NSDictionary: PointerType]?, - filesSavedBeforeThisOne: [UUID: ParseFile]?) throws -> (encoded: Data, unique: Set, unsavedChildren: [Encodable]) { + filesSavedBeforeThisOne: [UUID: ParseFile]?) throws -> (encoded: Data, unique: Set, unsavedChildren: [ParseType]) { + let skippedKeys = SkippedKeys.object.use() let encoder = _ParseEncoder(codingPath: [], dictionary: NSMutableDictionary(), skippingKeys: skippedKeys) if let dateEncodingStrategy = dateEncodingStrategy { encoder.dateEncodingStrategy = .custom(dateEncodingStrategy) @@ -105,7 +115,7 @@ private class _ParseEncoder: JSONEncoder, Encoder { let skippedKeys: Set var uniqueObjects = Set() var uniqueFiles = Set() - var newObjects = [Encodable]() + var newObjects = [ParseType]() var collectChildren = false var objectsSavedBeforeThisOne: [NSDictionary: PointerType]? var filesSavedBeforeThisOne: [UUID: ParseFile]? @@ -158,9 +168,9 @@ private class _ParseEncoder: JSONEncoder, Encoder { } // swiftlint:disable large_tuple - func encodeObject(_ value: Encodable, collectChildren: Bool, + func encodeObject(_ value: ParseType, collectChildren: Bool, objectsSavedBeforeThisOne: [NSDictionary: PointerType]?, - filesSavedBeforeThisOne: [UUID: ParseFile]?) throws -> (encoded: Data, unique: Set, unsavedChildren: [Encodable]) { + filesSavedBeforeThisOne: [UUID: ParseFile]?) throws -> (encoded: Data, unique: Set, unsavedChildren: [ParseType]) { let encoder = _ParseEncoder(codingPath: codingPath, dictionary: dictionary, skippingKeys: skippedKeys) encoder.collectChildren = collectChildren diff --git a/Sources/ParseSwift/Objects/ParseInstallation.swift b/Sources/ParseSwift/Objects/ParseInstallation.swift index f661b8be6..2b078a35e 100644 --- a/Sources/ParseSwift/Objects/ParseInstallation.swift +++ b/Sources/ParseSwift/Objects/ParseInstallation.swift @@ -521,12 +521,12 @@ extension ParseInstallation { } } - func deleteCommand() throws -> API.Command { + func deleteCommand() throws -> API.NonParseBodyCommand { guard isSaved else { throw ParseError(code: .unknownError, message: "Cannot Delete an object without id") } - return API.Command( + return API.NonParseBodyCommand( method: .DELETE, path: endpoint ) { (data) -> ParseError? in diff --git a/Sources/ParseSwift/Objects/ParseObject.swift b/Sources/ParseSwift/Objects/ParseObject.swift index c2ca14bf0..61a9cae42 100644 --- a/Sources/ParseSwift/Objects/ParseObject.swift +++ b/Sources/ParseSwift/Objects/ParseObject.swift @@ -248,7 +248,7 @@ public extension Sequence where Element: ParseObject { // MARK: CustomDebugStringConvertible extension ParseObject { public var debugDescription: String { - guard let descriptionData = try? ParseCoding.parseEncoder(skipKeys: false).encode(self), + guard let descriptionData = try? ParseCoding.parseEncoder().encode(self, skipKeys: .none), let descriptionString = String(data: descriptionData, encoding: .utf8) else { return "\(className) ()" } @@ -403,9 +403,9 @@ extension ParseObject { var waitingToBeSaved = object.unsavedChildren while waitingToBeSaved.count > 0 { - var savableObjects = [Encodable]() + var savableObjects = [ParseType]() var savableFiles = [ParseFile]() - var nextBatch = [Encodable]() + var nextBatch = [ParseType]() try waitingToBeSaved.forEach { parseType in if let parseFile = parseType as? ParseFile { @@ -466,7 +466,7 @@ extension ParseObject { } // MARK: Savable Encodable Version -internal extension Encodable { +internal extension ParseType { func save(options: API.Options = []) throws -> PointerType { try saveCommand().execute(options: options) } @@ -529,7 +529,7 @@ extension ParseObject { } } - internal func deleteCommand() throws -> API.Command { - try API.Command.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.swift b/Sources/ParseSwift/Objects/ParseUser.swift index e1c477d0d..3447a536c 100644 --- a/Sources/ParseSwift/Objects/ParseUser.swift +++ b/Sources/ParseSwift/Objects/ParseUser.swift @@ -26,13 +26,13 @@ public protocol ParseUser: ParseObject { } // MARK: SignupBody -struct SignupBody: Codable { +struct SignupBody: ParseType { let username: String let password: String } // MARK: EmailBody -struct EmailBody: Codable { +struct EmailBody: ParseType { let email: String } @@ -153,13 +153,13 @@ extension ParseUser { } internal static func loginCommand(username: String, - password: String) -> API.Command { + password: String) -> API.NonParseBodyCommand { let params = [ "username": username, "password": password ] - return API.Command(method: .GET, + return API.NonParseBodyCommand(method: .GET, path: .login, params: params) { (data) -> Self in let response = try ParseCoding.jsonDecoder().decode(LoginSignupResponse.self, from: data) @@ -210,8 +210,8 @@ extension ParseUser { } } - internal static func logoutCommand() -> API.Command { - return API.Command(method: .POST, path: .logout) { (data) -> NoBody in + internal static func logoutCommand() -> API.NonParseBodyCommand { + return API.NonParseBodyCommand(method: .POST, path: .logout) { (data) -> NoBody in var parseError: ParseError? var serverResponse = NoBody() do { @@ -660,12 +660,12 @@ extension ParseUser { } } - func deleteCommand() throws -> API.Command { + func deleteCommand() throws -> API.NonParseBodyCommand { guard isSaved else { throw ParseError(code: .unknownError, message: "Cannot Delete an object without id") } - return API.Command( + return API.NonParseBodyCommand( method: .DELETE, path: endpoint ) { (data) -> ParseError? in diff --git a/Sources/ParseSwift/Objects/Protocols/Fileable.swift b/Sources/ParseSwift/Objects/Protocols/Fileable.swift index add8e8efa..72f7a1d25 100644 --- a/Sources/ParseSwift/Objects/Protocols/Fileable.swift +++ b/Sources/ParseSwift/Objects/Protocols/Fileable.swift @@ -8,7 +8,7 @@ import Foundation -protocol Fileable: LocallyIdentifiable { +protocol Fileable: ParseType, Decodable, LocallyIdentifiable { var __type: String { get } // swiftlint:disable:this identifier_name var name: String { get set } var url: URL? { get set } diff --git a/Sources/ParseSwift/Objects/Protocols/LocallyIdentifiable.swift b/Sources/ParseSwift/Objects/Protocols/LocallyIdentifiable.swift index 9b56b8ee3..25a5c243a 100644 --- a/Sources/ParseSwift/Objects/Protocols/LocallyIdentifiable.swift +++ b/Sources/ParseSwift/Objects/Protocols/LocallyIdentifiable.swift @@ -8,11 +8,11 @@ import Foundation -protocol LocallyIdentifiable: Encodable, Hashable { +public protocol LocallyIdentifiable: Encodable, Hashable { var __localUUID: UUID? { get set } // swiftlint:disable:this identifier_name } -extension LocallyIdentifiable { +public extension LocallyIdentifiable { var localUUID: UUID { mutating get { if self.__localUUID == nil { @@ -23,7 +23,7 @@ extension LocallyIdentifiable { } // Equatable - public static func == (lhs: Self, rhs: Self) -> Bool { + static func == (lhs: Self, rhs: Self) -> Bool { var lhs = lhs var rhs = rhs return lhs.localUUID == rhs.localUUID diff --git a/Sources/ParseSwift/Objects/Protocols/Objectable.swift b/Sources/ParseSwift/Objects/Protocols/Objectable.swift index 5cfecf8e8..80528f6ad 100644 --- a/Sources/ParseSwift/Objects/Protocols/Objectable.swift +++ b/Sources/ParseSwift/Objects/Protocols/Objectable.swift @@ -8,7 +8,7 @@ import Foundation -public protocol Objectable: Codable { +public protocol Objectable: ParseType, Decodable { /** The class name of the object. */ diff --git a/Sources/ParseSwift/Objects/Protocols/ParseType.swift b/Sources/ParseSwift/Objects/Protocols/ParseType.swift new file mode 100644 index 000000000..83f53b329 --- /dev/null +++ b/Sources/ParseSwift/Objects/Protocols/ParseType.swift @@ -0,0 +1,11 @@ +// +// ParseType.swift +// ParseSwift +// +// Created by Corey Baker on 12/31/20. +// Copyright © 2020 Parse Community. All rights reserved. +// + +import Foundation + +public protocol ParseType: Encodable {} diff --git a/Sources/ParseSwift/Types/ParseACL.swift b/Sources/ParseSwift/Types/ParseACL.swift index 8813b93f0..6232754e8 100644 --- a/Sources/ParseSwift/Types/ParseACL.swift +++ b/Sources/ParseSwift/Types/ParseACL.swift @@ -15,7 +15,7 @@ import Foundation "the public" so that, for example, any user could read a particular object but only a particular set of users could write to that object. */ -public struct ParseACL: Codable, Equatable, Hashable { +public struct ParseACL: ParseType, Decodable, Equatable, Hashable { private static let publicScope = "*" private var acl: [String: [Access: Bool]]? diff --git a/Sources/ParseSwift/Types/ParseCloud.swift b/Sources/ParseSwift/Types/ParseCloud.swift index 12993e1b1..27410a00f 100644 --- a/Sources/ParseSwift/Types/ParseCloud.swift +++ b/Sources/ParseSwift/Types/ParseCloud.swift @@ -13,7 +13,7 @@ import Foundation An object should be should be instantiated for each function and job type. When conforming to `ParseCloud`, any properties added will be passed as parameters to your Cloud Function or Job. */ -public protocol ParseCloud: Encodable, CustomDebugStringConvertible { +public protocol ParseCloud: ParseType, Decodable, CustomDebugStringConvertible { /** The name of the function or job. */ @@ -110,7 +110,7 @@ extension ParseCloud { // MARK: CustomDebugStringConvertible extension ParseCloud { public var debugDescription: String { - guard let descriptionData = try? ParseCoding.parseEncoder().encode(self), + guard let descriptionData = try? ParseCoding.parseEncoder().encode(self, skipKeys: .none), let descriptionString = String(data: descriptionData, encoding: .utf8) else { return "\(functionJobName)" } diff --git a/Sources/ParseSwift/Types/ParseError.swift b/Sources/ParseSwift/Types/ParseError.swift index 9f4653d3b..c1b45aee9 100644 --- a/Sources/ParseSwift/Types/ParseError.swift +++ b/Sources/ParseSwift/Types/ParseError.swift @@ -8,7 +8,7 @@ import Foundation -public struct ParseError: Swift.Error, Codable { +public struct ParseError: ParseType, Decodable, Swift.Error { public let code: Code public let message: String diff --git a/Sources/ParseSwift/Types/ParseFile.swift b/Sources/ParseSwift/Types/ParseFile.swift index 8a44c4d69..d154d8b53 100644 --- a/Sources/ParseSwift/Types/ParseFile.swift +++ b/Sources/ParseSwift/Types/ParseFile.swift @@ -15,7 +15,7 @@ public struct ParseFile: Fileable, Savable, Fetchable, Deletable { && data == nil } - internal var __localUUID: UUID? // swiftlint:disable:this identifier_name + public var __localUUID: UUID? // swiftlint:disable:this identifier_name /** The name of the file. diff --git a/Sources/ParseSwift/Types/ParseGeoPoint.swift b/Sources/ParseSwift/Types/ParseGeoPoint.swift index 9475849b9..b86173dfe 100644 --- a/Sources/ParseSwift/Types/ParseGeoPoint.swift +++ b/Sources/ParseSwift/Types/ParseGeoPoint.swift @@ -8,7 +8,7 @@ import CoreLocation It could be used to perform queries in a geospatial manner using `ParseQuery.-whereKey:nearGeoPoint:`. Currently, instances of `ParseObject` may only have one key associated with a `ParseGeoPoint` type. */ -public struct ParseGeoPoint: Codable, Hashable, Equatable { +public struct ParseGeoPoint: ParseType, Decodable, Hashable { private let __type: String = "GeoPoint" // swiftlint:disable:this identifier_name static let earthRadiusMiles = 3958.8 static let earthRadiusKilometers = 6371.0 diff --git a/Sources/ParseSwift/Types/Pointer.swift b/Sources/ParseSwift/Types/Pointer.swift index 24f0e095d..b4990abfa 100644 --- a/Sources/ParseSwift/Types/Pointer.swift +++ b/Sources/ParseSwift/Types/Pointer.swift @@ -14,7 +14,7 @@ private func getObjectId(target: Objectable) -> String { return objectId } -public struct Pointer: Fetchable, Codable { +public struct Pointer: ParseType, Fetchable { public typealias FetchingType = T private let __type: String = "Pointer" // swiftlint:disable:this identifier_name @@ -39,7 +39,7 @@ public struct Pointer: Fetchable, Codable { extension Pointer { public func fetch(options: API.Options = []) throws -> T { let path = API.Endpoint.object(className: className, objectId: objectId) - return try API.Command(method: .GET, + return try API.NonParseBodyCommand(method: .GET, path: path) { (data) -> T in try ParseCoding.jsonDecoder().decode(T.self, from: data) }.execute(options: options) @@ -48,14 +48,14 @@ extension Pointer { public func fetch(options: API.Options = [], callbackQueue: DispatchQueue = .main, completion: @escaping (Result) -> Void) { let path = API.Endpoint.object(className: className, objectId: objectId) - API.Command(method: .GET, + API.NonParseBodyCommand(method: .GET, path: path) { (data) -> T in try ParseCoding.jsonDecoder().decode(T.self, from: data) }.executeAsync(options: options, callbackQueue: callbackQueue, completion: completion) } } -internal struct PointerType: Codable { +internal struct PointerType: ParseType { var __type: String = "Pointer" // swiftlint:disable:this identifier_name public var objectId: String diff --git a/Sources/ParseSwift/Types/Query.swift b/Sources/ParseSwift/Types/Query.swift index c49883228..1546e36b0 100644 --- a/Sources/ParseSwift/Types/Query.swift +++ b/Sources/ParseSwift/Types/Query.swift @@ -481,7 +481,7 @@ internal struct QueryWhere: Encodable, Equatable { /** The `Query` class defines a query that is used to query for `ParseObject`s. */ -public class Query: Encodable, Equatable where T: ParseObject { +public class Query: ParseType, Equatable where T: ParseObject { // interpolate as GET private let method: String = "GET" internal var limit: Int = 100 diff --git a/Tests/ParseSwiftTests/ACLTests.swift b/Tests/ParseSwiftTests/ACLTests.swift index 80de03a61..19186e8ab 100644 --- a/Tests/ParseSwiftTests/ACLTests.swift +++ b/Tests/ParseSwiftTests/ACLTests.swift @@ -184,7 +184,7 @@ class ACLTests: XCTestCase { MockURLProtocol.mockRequests { _ in do { - let encoded = try loginResponse.getEncoder(skipKeys: false).encode(loginResponse) + let encoded = try loginResponse.getEncoder().encode(loginResponse, skipKeys: .none) return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) } catch { return nil @@ -227,7 +227,7 @@ class ACLTests: XCTestCase { MockURLProtocol.mockRequests { _ in do { - let encoded = try loginResponse.getEncoder(skipKeys: false).encode(loginResponse) + let encoded = try loginResponse.getEncoder().encode(loginResponse, skipKeys: .none) return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) } catch { return nil diff --git a/Tests/ParseSwiftTests/APICommandTests.swift b/Tests/ParseSwiftTests/APICommandTests.swift index 6c8947965..945a362e9 100644 --- a/Tests/ParseSwiftTests/APICommandTests.swift +++ b/Tests/ParseSwiftTests/APICommandTests.swift @@ -42,7 +42,10 @@ class APICommandTests: XCTestCase { } do { let returnedObject = - try API.Command(method: .GET, path: .login, params: nil, mapper: { (data) -> String in + try API.NonParseBodyCommand(method: .GET, + path: .login, + params: nil, + mapper: { (data) -> String in return try JSONDecoder().decode(String.self, from: data) }).execute(options: []) XCTAssertEqual(originalObject, returnedObject) @@ -66,7 +69,10 @@ class APICommandTests: XCTestCase { } do { - _ = try API.Command(method: .GET, path: .login, params: nil, mapper: { (_) -> NoBody in + _ = try API.NonParseBodyCommand(method: .GET, + path: .login, + params: nil, + mapper: { (_) -> NoBody in throw originalError }).execute(options: []) XCTFail("Should have thrown an error") @@ -102,7 +108,10 @@ class APICommandTests: XCTestCase { } do { - _ = try API.Command(method: .GET, path: .login, params: nil, mapper: { (_) -> NoBody in + _ = try API.NonParseBodyCommand(method: .GET, + path: .login, + params: nil, + mapper: { (_) -> NoBody in throw parseError }).execute(options: []) @@ -123,7 +132,10 @@ class APICommandTests: XCTestCase { return MockURLResponse(error: originalError) } do { - _ = try API.Command(method: .GET, path: .login, params: nil, mapper: { (_) -> NoBody in + _ = try API.NonParseBodyCommand(method: .GET, + path: .login, + params: nil, + mapper: { (_) -> NoBody in throw originalError }).execute(options: []) XCTFail("Should have thrown an error") diff --git a/Tests/ParseSwiftTests/ParseCloudTests.swift b/Tests/ParseSwiftTests/ParseCloudTests.swift index 592d6a6cf..b9d3c3f83 100644 --- a/Tests/ParseSwiftTests/ParseCloudTests.swift +++ b/Tests/ParseSwiftTests/ParseCloudTests.swift @@ -66,7 +66,7 @@ class ParseCloudTests: XCTestCase { // swiftlint:disable:this type_body_length func testParseEncoding() throws { let expected = [String: String]() let cloud = Cloud(functionJobName: "test") - let encoded = try ParseCoding.parseEncoder().encode(cloud) + let encoded = try ParseCoding.parseEncoder().encode(cloud, skipKeys: .cloud) let decoded = try JSONDecoder().decode([String: String].self, from: encoded) XCTAssertEqual(decoded, expected, "\"functionJobName\" key should be skipped by ParseEncoder") } @@ -76,7 +76,7 @@ class ParseCloudTests: XCTestCase { // swiftlint:disable:this type_body_length "customKey": "parse" ] let cloud = Cloud2(functionJobName: "test", customKey: "parse") - let encoded = try ParseCoding.parseEncoder().encode(cloud) + let encoded = try ParseCoding.parseEncoder().encode(cloud, skipKeys: .cloud) let decoded = try JSONDecoder().decode([String: String].self, from: encoded) XCTAssertEqual(decoded, expected, "\"functionJobName\" key should be skipped by ParseEncoder") } diff --git a/Tests/ParseSwiftTests/ParseEncoderTests.swift b/Tests/ParseSwiftTests/ParseEncoderTests.swift index 95e835dd0..70b8fe6ca 100644 --- a/Tests/ParseSwiftTests/ParseEncoderTests.swift +++ b/Tests/ParseSwiftTests/ParseEncoderTests.swift @@ -44,11 +44,11 @@ class ParseEncoderTests: XCTestCase { let phoneNumbers: [String] } - func parseEncoding(for object: T) -> Data { + func parseEncoding(for object: T) -> Data { let encoder = ParseEncoder() encoder.jsonEncoder.outputFormatting = .sortedKeys - guard let encoding = try? encoder.encode(object) else { + guard let encoding = try? encoder.encode(object, skipKeys: .object) else { XCTFail("Couldn't get a Parse encoding.") return Data() } @@ -67,7 +67,7 @@ class ParseEncoderTests: XCTestCase { return encoding } - +/* func test_encodingScalarValue() { let encoded = parseEncoding(for: ["": 5]) let reference = referenceEncoding(for: ["": 5]) @@ -95,7 +95,7 @@ class ParseEncoderTests: XCTestCase { let encoded = parseEncoding(for: value) let reference = referenceEncoding(for: value) XCTAssertEqual(encoded.count, reference.count) - } + }*/ func testNestedContatiner() throws { var newACL = ParseACL() @@ -104,7 +104,7 @@ class ParseEncoderTests: XCTestCase { let jsonEncoded = try JSONEncoder().encode(newACL) let jsonDecoded = try ParseCoding.jsonDecoder().decode([String: [String: Bool]].self, from: jsonEncoded) - let parseEncoded = try ParseCoding.parseEncoder().encode(newACL) + let parseEncoded = try ParseCoding.parseEncoder().encode(newACL, skipKeys: .object) let parseDecoded = try ParseCoding.jsonDecoder().decode([String: [String: Bool]].self, from: parseEncoded) XCTAssertEqual(jsonDecoded.keys.count, parseDecoded.keys.count) @@ -126,7 +126,7 @@ class ParseEncoderTests: XCTestCase { XCTAssertNotNil(decodedJSON["updatedAt"]) //ParseEncoder - let encoded = try ParseCoding.parseEncoder().encode(score) + let encoded = try ParseCoding.parseEncoder().encode(score, skipKeys: .object) let decoded = try ParseCoding.jsonDecoder().decode([String: AnyCodable].self, from: encoded) XCTAssertEqual(decoded["score"]?.value as? Int, score.score) XCTAssertNil(decoded["createdAt"]) diff --git a/Tests/ParseSwiftTests/ParseInstallationTests.swift b/Tests/ParseSwiftTests/ParseInstallationTests.swift index 216b0b3e5..6bfafa4f8 100644 --- a/Tests/ParseSwiftTests/ParseInstallationTests.swift +++ b/Tests/ParseSwiftTests/ParseInstallationTests.swift @@ -108,7 +108,7 @@ class ParseInstallationTests: XCTestCase { // swiftlint:disable:this type_body_l MockURLProtocol.mockRequests { _ in do { - let encoded = try loginResponse.getEncoder(skipKeys: false).encode(loginResponse) + let encoded = try loginResponse.getEncoder().encode(loginResponse, skipKeys: .none) return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) } catch { return nil @@ -304,7 +304,7 @@ class ParseInstallationTests: XCTestCase { // swiftlint:disable:this type_body_l let encoded: Data! do { - encoded = try installationOnServer.getEncoder(skipKeys: false).encode(installationOnServer) + 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 { @@ -433,7 +433,7 @@ class ParseInstallationTests: XCTestCase { // swiftlint:disable:this type_body_l //Get dates in correct format from ParseDecoding strategy installation = try installation.getDecoder().decode(Installation.self, from: encodedOriginal) - encoded = try installationOnServer.getEncoder(skipKeys: false).encode(installationOnServer) + 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 { @@ -486,7 +486,7 @@ class ParseInstallationTests: XCTestCase { // swiftlint:disable:this type_body_l let encoded: Data! do { - encoded = try installationOnServer.getEncoder(skipKeys: false).encode(installationOnServer) + 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 { @@ -564,7 +564,7 @@ class ParseInstallationTests: XCTestCase { // swiftlint:disable:this type_body_l let encoded: Data! do { - encoded = try installationOnServer.getEncoder(skipKeys: false).encode(installationOnServer) + 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 { @@ -688,7 +688,7 @@ class ParseInstallationTests: XCTestCase { // swiftlint:disable:this type_body_l let encoded: Data! do { - encoded = try installationOnServer.getEncoder(skipKeys: false).encode(installationOnServer) + 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 { diff --git a/Tests/ParseSwiftTests/ParseObjectBatchTests.swift b/Tests/ParseSwiftTests/ParseObjectBatchTests.swift index c2f68e93a..4435d80d8 100644 --- a/Tests/ParseSwiftTests/ParseObjectBatchTests.swift +++ b/Tests/ParseSwiftTests/ParseObjectBatchTests.swift @@ -1247,12 +1247,11 @@ class ParseObjectBatchTests: XCTestCase { // swiftlint:disable:this type_body_le } func testDeleteAllError() { - let score = GameScore(score: 10) let parseError = ParseError(code: .objectNotFound, message: "Object not found") let response = [parseError] let encoded: Data! do { - encoded = try score.getEncoder(skipKeys: false).encode(response) + encoded = try ParseCoding.jsonEncoder().encode(response) } catch { XCTFail("Should have encoded/decoded. Error \(error)") return @@ -1361,13 +1360,12 @@ class ParseObjectBatchTests: XCTestCase { // swiftlint:disable:this type_body_le } func testDeleteAllAsyncMainQueueError() { - let score = GameScore(score: 10) let parseError = ParseError(code: .objectNotFound, message: "Object not found") let response = [parseError] do { - let encoded = try score.getEncoder(skipKeys: false).encode(response) + let encoded = try ParseCoding.jsonEncoder().encode(response) MockURLProtocol.mockRequests { _ in return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) } diff --git a/Tests/ParseSwiftTests/ParseObjectTests.swift b/Tests/ParseSwiftTests/ParseObjectTests.swift index 7794c02a3..78a50700b 100644 --- a/Tests/ParseSwiftTests/ParseObjectTests.swift +++ b/Tests/ParseSwiftTests/ParseObjectTests.swift @@ -850,7 +850,7 @@ class ParseObjectTests: XCTestCase { // swiftlint:disable:this type_body_length scoreOnServer.ACL = nil let encoded: Data! do { - encoded = try scoreOnServer.getEncoder(skipKeys: false).encode(scoreOnServer) + encoded = try scoreOnServer.getEncoder().encode(scoreOnServer, skipKeys: .none) //Get dates in correct format from ParseDecoding strategy scoreOnServer = try scoreOnServer.getDecoder().decode(GameScore.self, from: encoded) } catch { @@ -883,7 +883,7 @@ class ParseObjectTests: XCTestCase { // swiftlint:disable:this type_body_length let encoded: Data! do { - encoded = try score.getEncoder(skipKeys: false).encode(parseError) + encoded = try ParseCoding.jsonEncoder().encode(parseError) } catch { XCTFail("Should encode/decode. Error \(error)") return @@ -953,7 +953,7 @@ class ParseObjectTests: XCTestCase { // swiftlint:disable:this type_body_length let encoded: Data! do { - encoded = try scoreOnServer.getEncoder(skipKeys: false).encode(scoreOnServer) + encoded = try scoreOnServer.getEncoder().encode(scoreOnServer, skipKeys: .none) //Get dates in correct format from ParseDecoding strategy scoreOnServer = try scoreOnServer.getDecoder().decode(GameScore.self, from: encoded) } catch { @@ -981,7 +981,7 @@ class ParseObjectTests: XCTestCase { // swiftlint:disable:this type_body_length scoreOnServer.ACL = nil let encoded: Data! do { - encoded = try scoreOnServer.getEncoder(skipKeys: false).encode(scoreOnServer) + encoded = try scoreOnServer.getEncoder().encode(scoreOnServer, skipKeys: .none) //Get dates in correct format from ParseDecoding strategy scoreOnServer = try scoreOnServer.getDecoder().decode(GameScore.self, from: encoded) } catch { @@ -1031,7 +1031,7 @@ class ParseObjectTests: XCTestCase { // swiftlint:disable:this type_body_length let parseError = ParseError(code: .objectNotFound, message: "Object not found") let encoded: Data! do { - encoded = try score.getEncoder(skipKeys: false).encode(parseError) + encoded = try ParseCoding.jsonEncoder().encode(parseError) } catch { XCTFail("Should have encoded/decoded: Error: \(error)") return @@ -1055,7 +1055,7 @@ class ParseObjectTests: XCTestCase { // swiftlint:disable:this type_body_length scoreOnServer.objectId = "yarr" let encoded: Data! do { - encoded = try scoreOnServer.getEncoder(skipKeys: false).encode(scoreOnServer) + encoded = try scoreOnServer.getEncoder().encode(scoreOnServer, skipKeys: .none) //Get dates in correct format from ParseDecoding strategy scoreOnServer = try scoreOnServer.getDecoder().decode(GameScore.self, from: encoded) } catch { @@ -1083,10 +1083,15 @@ class ParseObjectTests: XCTestCase { // swiftlint:disable:this type_body_length } XCTAssertNil(parseError) + guard let savedChild = savedChildObject else { + XCTFail("Should have unwrapped child object") + return + } + //Saved updated info for game let encodedScore: Data do { - encodedScore = try game.getEncoder(skipKeys: false).encode(savedChildObject) + encodedScore = try ParseCoding.jsonEncoder().encode(savedChild) //Decode Pointer as GameScore game.score = try game.getDecoder().decode(GameScore.self, from: encodedScore) } catch { @@ -1104,7 +1109,7 @@ class ParseObjectTests: XCTestCase { // swiftlint:disable:this type_body_length let encodedGamed: Data do { - encodedGamed = try game.getEncoder(skipKeys: false).encode(gameOnServer) + encodedGamed = try game.getEncoder().encode(gameOnServer, skipKeys: .none) //Get dates in correct format from ParseDecoding strategy gameOnServer = try game.getDecoder().decode(Game.self, from: encodedGamed) } catch { @@ -1214,7 +1219,7 @@ class ParseObjectTests: XCTestCase { // swiftlint:disable:this type_body_length scoreOnServer.objectId = "yarr" let encoded: Data! do { - encoded = try scoreOnServer.getEncoder().encode(scoreOnServer) + encoded = try scoreOnServer.getEncoder().encode(scoreOnServer, skipKeys: .none) //Get dates in correct format from ParseDecoding strategy scoreOnServer = try scoreOnServer.getDecoder().decode(GameScore.self, from: encoded) XCTFail("Should have thrown encode/decode error because child objects can't have the same objectId") @@ -1233,7 +1238,7 @@ class ParseObjectTests: XCTestCase { // swiftlint:disable:this type_body_length score.levels = [level1, level2] do { - let encoded = try score.getEncoder(skipKeys: false).encode(score) + let encoded = try score.getEncoder().encode(score, skipKeys: .none) //Get dates in correct format from ParseDecoding strategy guard let scoreOnServer = try (score.getDecoder() .decode([String: AnyCodable].self, @@ -1274,7 +1279,7 @@ class ParseObjectTests: XCTestCase { // swiftlint:disable:this type_body_length let encoded: Data! do { - encoded = try game.getEncoder(skipKeys: false).encode(response) + encoded = try ParseCoding.jsonEncoder().encode(response) } catch { XCTFail("Should encode/decode. Error \(error)") return @@ -1314,7 +1319,7 @@ class ParseObjectTests: XCTestCase { // swiftlint:disable:this type_body_length let encodedGamed: Data do { - encodedGamed = try game.getEncoder(skipKeys: false).encode(gameOnServer) + encodedGamed = try game.getEncoder().encode(gameOnServer, skipKeys: .none) //Get dates in correct format from ParseDecoding strategy gameOnServer = try game.getDecoder().decode(Game2.self, from: encodedGamed) } catch { diff --git a/Tests/ParseSwiftTests/ParsePointerTests.swift b/Tests/ParseSwiftTests/ParsePointerTests.swift index f9f8afbe2..ba006e666 100644 --- a/Tests/ParseSwiftTests/ParsePointerTests.swift +++ b/Tests/ParseSwiftTests/ParsePointerTests.swift @@ -71,7 +71,7 @@ class ParsePointerTests: XCTestCase { scoreOnServer.ACL = nil let encoded: Data! do { - encoded = try scoreOnServer.getEncoder(skipKeys: false).encode(scoreOnServer) + encoded = try scoreOnServer.getEncoder().encode(scoreOnServer, skipKeys: .none) //Get dates in correct format from ParseDecoding strategy scoreOnServer = try scoreOnServer.getDecoder().decode(GameScore.self, from: encoded) } catch { @@ -195,7 +195,7 @@ class ParsePointerTests: XCTestCase { let encoded: Data! do { - encoded = try scoreOnServer.getEncoder(skipKeys: false).encode(scoreOnServer) + encoded = try scoreOnServer.getEncoder().encode(scoreOnServer, skipKeys: .none) //Get dates in correct format from ParseDecoding strategy scoreOnServer = try scoreOnServer.getDecoder().decode(GameScore.self, from: encoded) } catch { @@ -224,7 +224,7 @@ class ParsePointerTests: XCTestCase { scoreOnServer.ACL = nil let encoded: Data! do { - encoded = try scoreOnServer.getEncoder(skipKeys: false).encode(scoreOnServer) + encoded = try scoreOnServer.getEncoder().encode(scoreOnServer, skipKeys: .none) //Get dates in correct format from ParseDecoding strategy scoreOnServer = try scoreOnServer.getDecoder().decode(GameScore.self, from: encoded) } catch { diff --git a/Tests/ParseSwiftTests/ParseUserTests.swift b/Tests/ParseSwiftTests/ParseUserTests.swift index 9e0712c5a..bbd2d50e5 100644 --- a/Tests/ParseSwiftTests/ParseUserTests.swift +++ b/Tests/ParseSwiftTests/ParseUserTests.swift @@ -108,7 +108,7 @@ class ParseUserTests: XCTestCase { // swiftlint:disable:this type_body_length userOnServer.ACL = nil let encoded: Data! do { - encoded = try userOnServer.getEncoder(skipKeys: false).encode(userOnServer) + encoded = try userOnServer.getEncoder().encode(userOnServer, skipKeys: .none) //Get dates in correct format from ParseDecoding strategy userOnServer = try userOnServer.getDecoder().decode(User.self, from: encoded) } catch { @@ -177,7 +177,7 @@ class ParseUserTests: XCTestCase { // swiftlint:disable:this type_body_length let encoded: Data! do { - encoded = try userOnServer.getEncoder(skipKeys: false).encode(userOnServer) + encoded = try userOnServer.getEncoder().encode(userOnServer, skipKeys: .none) //Get dates in correct format from ParseDecoding strategy userOnServer = try userOnServer.getDecoder().decode(User.self, from: encoded) } catch { @@ -241,7 +241,7 @@ class ParseUserTests: XCTestCase { // swiftlint:disable:this type_body_length let encoded: Data! do { - encoded = try userOnServer.getEncoder(skipKeys: false).encode(userOnServer) + encoded = try userOnServer.getEncoder().encode(userOnServer, skipKeys: .none) //Get dates in correct format from ParseDecoding strategy userOnServer = try userOnServer.getDecoder().decode(User.self, from: encoded) } catch { @@ -363,7 +363,7 @@ class ParseUserTests: XCTestCase { // swiftlint:disable:this type_body_length userOnServer.ACL = nil let encoded: Data! do { - encoded = try userOnServer.getEncoder(skipKeys: false).encode(userOnServer) + encoded = try userOnServer.getEncoder().encode(userOnServer, skipKeys: .none) //Get dates in correct format from ParseDecoding strategy userOnServer = try userOnServer.getDecoder().decode(User.self, from: encoded) } catch { @@ -422,7 +422,7 @@ class ParseUserTests: XCTestCase { // swiftlint:disable:this type_body_length let encoded: Data! do { - encoded = try userOnServer.getEncoder(skipKeys: false).encode(userOnServer) + encoded = try userOnServer.getEncoder().encode(userOnServer, skipKeys: .none) //Get dates in correct format from ParseDecoding strategy userOnServer = try userOnServer.getDecoder().decode(User.self, from: encoded) } catch { @@ -483,7 +483,7 @@ class ParseUserTests: XCTestCase { // swiftlint:disable:this type_body_length let encoded: Data! do { - encoded = try userOnServer.getEncoder(skipKeys: false).encode(userOnServer) + encoded = try userOnServer.getEncoder().encode(userOnServer, skipKeys: .none) //Get dates in correct format from ParseDecoding strategy userOnServer = try userOnServer.getDecoder().decode(User.self, from: encoded) } catch { @@ -545,7 +545,7 @@ class ParseUserTests: XCTestCase { // swiftlint:disable:this type_body_length let encoded: Data! do { - encoded = try userOnServer.getEncoder(skipKeys: false).encode(userOnServer) + encoded = try userOnServer.getEncoder().encode(userOnServer, skipKeys: .none) //Get dates in correct format from ParseDecoding strategy userOnServer = try userOnServer.getDecoder().decode(User.self, from: encoded) } catch { @@ -653,7 +653,7 @@ class ParseUserTests: XCTestCase { // swiftlint:disable:this type_body_length userOnServer.updatedAt = Date() let encoded: Data! do { - encoded = try userOnServer.getEncoder(skipKeys: false).encode(userOnServer) + encoded = try userOnServer.getEncoder().encode(userOnServer, skipKeys: .none) //Get dates in correct format from ParseDecoding strategy userOnServer = try userOnServer.getDecoder().decode(User.self, from: encoded) } catch { @@ -681,7 +681,7 @@ class ParseUserTests: XCTestCase { // swiftlint:disable:this type_body_length userOnServer.updatedAt = Date() let encoded: Data! do { - encoded = try userOnServer.getEncoder(skipKeys: false).encode(userOnServer) + encoded = try userOnServer.getEncoder().encode(userOnServer, skipKeys: .none) //Get dates in correct format from ParseDecoding strategy userOnServer = try userOnServer.getDecoder().decode(User.self, from: encoded) } catch { @@ -711,7 +711,7 @@ class ParseUserTests: XCTestCase { // swiftlint:disable:this type_body_length MockURLProtocol.mockRequests { _ in do { - let encoded = try loginResponse.getEncoder(skipKeys: false).encode(loginResponse) + let encoded = try loginResponse.getEncoder().encode(loginResponse, skipKeys: .none) return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) } catch { return nil @@ -793,7 +793,7 @@ class ParseUserTests: XCTestCase { // swiftlint:disable:this type_body_length MockURLProtocol.mockRequests { _ in do { - let encoded = try loginResponse.getEncoder(skipKeys: false).encode(loginResponse) + let encoded = try loginResponse.getEncoder().encode(loginResponse, skipKeys: .none) return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) } catch { return nil @@ -821,7 +821,7 @@ class ParseUserTests: XCTestCase { // swiftlint:disable:this type_body_length MockURLProtocol.mockRequests { _ in do { - let encoded = try loginResponse.getEncoder(skipKeys: false).encode(loginResponse) + let encoded = try loginResponse.getEncoder().encode(loginResponse, skipKeys: .none) return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) } catch { return nil @@ -904,7 +904,7 @@ class ParseUserTests: XCTestCase { // swiftlint:disable:this type_body_length MockURLProtocol.mockRequests { _ in do { - let encoded = try loginResponse.getEncoder(skipKeys: false).encode(loginResponse) + let encoded = try loginResponse.getEncoder().encode(loginResponse, skipKeys: .none) return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) } catch { return nil @@ -1286,7 +1286,7 @@ class ParseUserTests: XCTestCase { // swiftlint:disable:this type_body_length let encoded: Data! do { - encoded = try userOnServer.getEncoder(skipKeys: false).encode(userOnServer) + encoded = try userOnServer.getEncoder().encode(userOnServer, skipKeys: .none) //Get dates in correct format from ParseDecoding strategy userOnServer = try userOnServer.getDecoder().decode(User.self, from: encoded) } catch { From 5ebb08784d645dc0e11818b81408cadbf43476f6 Mon Sep 17 00:00:00 2001 From: Corey's iMac Date: Thu, 31 Dec 2020 20:52:08 -0500 Subject: [PATCH 23/44] Improved batch and encoder --- ParseSwift.playground/contents.xcplayground | 2 +- Sources/ParseSwift/API/API+Commands.swift | 4 +- Sources/ParseSwift/API/BatchUtils.swift | 6 +- Sources/ParseSwift/Coding/ParseEncoder.swift | 4 +- .../Objects/ParseInstallation.swift | 116 ++++++++++++++++-- Sources/ParseSwift/Objects/ParseObject.swift | 103 +++++++++++++++- Sources/ParseSwift/Objects/ParseUser.swift | 115 +++++++++++++++-- .../Objects/Protocols/Objectable.swift | 2 +- .../ParseObjectBatchTests.swift | 1 + 9 files changed, 315 insertions(+), 38 deletions(-) diff --git a/ParseSwift.playground/contents.xcplayground b/ParseSwift.playground/contents.xcplayground index e8d68dcdc..3db5401e4 100644 --- a/ParseSwift.playground/contents.xcplayground +++ b/ParseSwift.playground/contents.xcplayground @@ -12,4 +12,4 @@ - + \ No newline at end of file diff --git a/Sources/ParseSwift/API/API+Commands.swift b/Sources/ParseSwift/API/API+Commands.swift index 3a113cc3a..403defeb4 100644 --- a/Sources/ParseSwift/API/API+Commands.swift +++ b/Sources/ParseSwift/API/API+Commands.swift @@ -660,9 +660,9 @@ extension API.Command where T: ParseObject { //This has been disabled, looking into getting it working in the future. //It's only needed for sending batches of childObjects which currently isn't being used. -/* + // MARK: Batch - Child Objects -extension API.Command where T: Encodable { +/*extension API.Command where T: ParseType { internal var data: Data? { guard let body = body else { return nil } diff --git a/Sources/ParseSwift/API/BatchUtils.swift b/Sources/ParseSwift/API/BatchUtils.swift index bc8feaa4b..e0ee120fa 100644 --- a/Sources/ParseSwift/API/BatchUtils.swift +++ b/Sources/ParseSwift/API/BatchUtils.swift @@ -16,13 +16,13 @@ typealias RESTBatchCommandType = API.Command, Pars typealias ParseObjectBatchCommandNoBody = BatchCommandNoBody typealias ParseObjectBatchResponseNoBody = [ParseError?] typealias RESTBatchCommandNoBodyType = API.NonParseBodyCommand, ParseObjectBatchResponseNoBody> where T: Encodable - +/* typealias ParseObjectBatchCommandEncodable = BatchCommand where T: ParseType typealias ParseObjectBatchResponseEncodable = [(Result)] // swiftlint:disable line_length typealias RESTBatchCommandTypeEncodable = API.Command, ParseObjectBatchResponseEncodable> where T: ParseType - -// swiftlint:enable line_length + // swiftlint:enable line_length + */ internal struct BatchCommand: ParseType where T: ParseType { let requests: [API.Command] diff --git a/Sources/ParseSwift/Coding/ParseEncoder.swift b/Sources/ParseSwift/Coding/ParseEncoder.swift index bff9066b3..adc83d26d 100644 --- a/Sources/ParseSwift/Coding/ParseEncoder.swift +++ b/Sources/ParseSwift/Coding/ParseEncoder.swift @@ -267,7 +267,7 @@ private class _ParseEncoder: JSONEncoder, Encoder { } } else if let pointerForCurrentObject = self.objectsSavedBeforeThisOne?[hashOfCurrentObject] { valueToEncode = pointerForCurrentObject - } else if codingPath.count > 0 { + } else if dictionary.count > 0 { //Only top level objects can be saved without a pointer throw ParseError(code: .unknownError, message: "Error. Couldn't resolve unsaved object while encoding.") } @@ -297,7 +297,7 @@ private class _ParseEncoder: JSONEncoder, Encoder { } } else if let currentFile = self.filesSavedBeforeThisOne?[uuid] { valueToEncode = currentFile - } else if codingPath.count > 0 { + } else if dictionary.count > 0 { //Only top level objects can be saved without a pointer throw ParseError(code: .unknownError, message: "Error. Couldn't resolve unsaved file while encoding.") } diff --git a/Sources/ParseSwift/Objects/ParseInstallation.swift b/Sources/ParseSwift/Objects/ParseInstallation.swift index 2b078a35e..b4e214aa8 100644 --- a/Sources/ParseSwift/Objects/ParseInstallation.swift +++ b/Sources/ParseSwift/Objects/ParseInstallation.swift @@ -548,10 +548,55 @@ public extension Sequence where Element: ParseInstallation { - important: If an object saved has the same objectId as current, it will automatically update the current. */ func saveAll(options: API.Options = []) throws -> [(Result)] { + var childObjects = [NSDictionary: PointerType]() + var childFiles = [UUID: ParseFile]() + var error: ParseError? + + let installations = map { $0 } + for installation in installations { + let group = DispatchGroup() + group.enter() + installation.ensureDeepSave(options: options) { (savedChildObjects, savedChildFiles, parseError) -> Void in + //If an error occurs, everything should be skipped + if parseError != nil { + error = parseError + } + savedChildObjects.forEach {(key, value) in + if error != nil { + return + } + if childObjects[key] == nil { + childObjects[key] = value + } else { + error = ParseError(code: .unknownError, message: "circular dependency") + return + } + } + savedChildFiles.forEach {(key, value) in + if error != nil { + return + } + if childFiles[key] == nil { + childFiles[key] = value + } else { + error = ParseError(code: .unknownError, message: "circular dependency") + return + } + } + group.leave() + } + group.wait() + if let error = error { + throw error + } + } + let commands = map { $0.saveCommand() } let returnResults = try API.Command - .batch(commands: commands) - .execute(options: options) + .batch(commands: commands) + .execute(options: options, + childObjects: childObjects, + childFiles: childFiles) try? Self.Element.updateKeychainIfNeeded(compactMap {$0}) return returnResults } @@ -565,24 +610,71 @@ public extension Sequence where Element: ParseInstallation { It should have the following argument signature: `(Result<[(Result)], ParseError>)`. - important: If an object saved has the same objectId as current, it will automatically update the current. */ - func saveAll( + func saveAll( // swiftlint:disable:this function_body_length options: API.Options = [], callbackQueue: DispatchQueue = .main, completion: @escaping (Result<[(Result)], ParseError>) -> Void ) { + var childObjects = [NSDictionary: PointerType]() + var childFiles = [UUID: ParseFile]() + var error: ParseError? + + let installations = map { $0 } + for installation in installations { + let group = DispatchGroup() + group.enter() + installation.ensureDeepSave(options: options) { (savedChildObjects, savedChildFiles, parseError) -> Void in + //If an error occurs, everything should be skipped + if parseError != nil { + error = parseError + } + savedChildObjects.forEach {(key, value) in + if error != nil { + return + } + if childObjects[key] == nil { + childObjects[key] = value + } else { + error = ParseError(code: .unknownError, message: "circular dependency") + return + } + } + savedChildFiles.forEach {(key, value) in + if error != nil { + return + } + if childFiles[key] == nil { + childFiles[key] = value + } else { + error = ParseError(code: .unknownError, message: "circular dependency") + return + } + } + group.leave() + } + group.wait() + if let error = error { + completion(.failure(error)) + return + } + } + let commands = map { $0.saveCommand() } API.Command .batch(commands: commands) - .executeAsync(options: options, callbackQueue: callbackQueue) { results in - switch results { - - case .success(let saved): - try? Self.Element.updateKeychainIfNeeded(self.compactMap {$0}) - completion(.success(saved)) - case .failure(let error): - completion(.failure(error)) - } + .executeAsync(options: options, + callbackQueue: callbackQueue, + childObjects: childObjects, + childFiles: childFiles) { results in + switch results { + + case .success(let saved): + try? Self.Element.updateKeychainIfNeeded(self.compactMap {$0}) + completion(.success(saved)) + case .failure(let error): + completion(.failure(error)) } + } } /** diff --git a/Sources/ParseSwift/Objects/ParseObject.swift b/Sources/ParseSwift/Objects/ParseObject.swift index 61a9cae42..babbfb51c 100644 --- a/Sources/ParseSwift/Objects/ParseObject.swift +++ b/Sources/ParseSwift/Objects/ParseObject.swift @@ -61,10 +61,55 @@ public extension Sequence where Element: ParseObject { - throws: `ParseError` */ func saveAll(options: API.Options = []) throws -> [(Result)] { + var childObjects = [NSDictionary: PointerType]() + var childFiles = [UUID: ParseFile]() + var error: ParseError? + + let objects = map { $0 } + for object in objects { + let group = DispatchGroup() + group.enter() + object.ensureDeepSave(options: options) { (savedChildObjects, savedChildFiles, parseError) -> Void in + //If an error occurs, everything should be skipped + if parseError != nil { + error = parseError + } + savedChildObjects.forEach {(key, value) in + if error != nil { + return + } + if childObjects[key] == nil { + childObjects[key] = value + } else { + error = ParseError(code: .unknownError, message: "circular dependency") + return + } + } + savedChildFiles.forEach {(key, value) in + if error != nil { + return + } + if childFiles[key] == nil { + childFiles[key] = value + } else { + error = ParseError(code: .unknownError, message: "circular dependency") + return + } + } + group.leave() + } + group.wait() + if let error = error { + throw error + } + } + let commands = map { $0.saveCommand() } return try API.Command .batch(commands: commands) - .execute(options: options) + .execute(options: options, + childObjects: childObjects, + childFiles: childFiles) } /** @@ -80,10 +125,58 @@ public extension Sequence where Element: ParseObject { callbackQueue: DispatchQueue = .main, completion: @escaping (Result<[(Result)], ParseError>) -> Void ) { + var childObjects = [NSDictionary: PointerType]() + var childFiles = [UUID: ParseFile]() + var error: ParseError? + + let objects = map { $0 } + for object in objects { + let group = DispatchGroup() + group.enter() + object.ensureDeepSave(options: options) { (savedChildObjects, savedChildFiles, parseError) -> Void in + //If an error occurs, everything should be skipped + if parseError != nil { + error = parseError + } + savedChildObjects.forEach {(key, value) in + if error != nil { + return + } + if childObjects[key] == nil { + childObjects[key] = value + } else { + error = ParseError(code: .unknownError, message: "circular dependency") + return + } + } + savedChildFiles.forEach {(key, value) in + if error != nil { + return + } + if childFiles[key] == nil { + childFiles[key] = value + } else { + error = ParseError(code: .unknownError, message: "circular dependency") + return + } + } + group.leave() + } + group.wait() + if let error = error { + completion(.failure(error)) + return + } + } + let commands = map { $0.saveCommand() } API.Command .batch(commands: commands) - .executeAsync(options: options, callbackQueue: callbackQueue, completion: completion) + .executeAsync(options: options, + callbackQueue: callbackQueue, + childObjects: childObjects, + childFiles: childFiles, + completion: completion) } /** @@ -227,7 +320,7 @@ public extension Sequence where Element: ParseObject { } // MARK: Batch Support -/*internal extension Sequence where Element: Encodable { +/*internal extension Sequence where Element: ParseType { /** Saves a collection of objects *synchronously* all at once and throws an error if necessary. @@ -440,7 +533,7 @@ extension ParseObject { } //Currently, batch isn't working for Encodable - //savableObjects.saveAll(encodableObjects: savable) + //Self.saveAll(encodableObjects: savableObjects) try savableObjects.forEach { let hash = BaseObjectable.createHash($0) objectsFinishedSaving[hash] = try $0.save(options: options) @@ -475,7 +568,7 @@ internal extension ParseType { try API.Command.saveCommand(self) } /* - func saveAll(options: API.Options = [], + static func saveAll(options: API.Options = [], encodableObjects: [T]) throws -> [(Result)] { let commands = try encodableObjects.map { try $0.saveCommand() } return try API.Command diff --git a/Sources/ParseSwift/Objects/ParseUser.swift b/Sources/ParseSwift/Objects/ParseUser.swift index 3447a536c..5a5bdd864 100644 --- a/Sources/ParseSwift/Objects/ParseUser.swift +++ b/Sources/ParseSwift/Objects/ParseUser.swift @@ -687,10 +687,54 @@ public extension Sequence where Element: ParseUser { - important: If an object saved has the same objectId as current, it will automatically update the current. */ func saveAll(options: API.Options = []) throws -> [(Result)] { + var childObjects = [NSDictionary: PointerType]() + var childFiles = [UUID: ParseFile]() + var error: ParseError? + let users = map { $0 } + for user in users { + let group = DispatchGroup() + group.enter() + user.ensureDeepSave(options: options) { (savedChildObjects, savedChildFiles, parseError) -> Void in + //If an error occurs, everything should be skipped + if parseError != nil { + error = parseError + } + savedChildObjects.forEach {(key, value) in + if error != nil { + return + } + if childObjects[key] == nil { + childObjects[key] = value + } else { + error = ParseError(code: .unknownError, message: "circular dependency") + return + } + } + savedChildFiles.forEach {(key, value) in + if error != nil { + return + } + if childFiles[key] == nil { + childFiles[key] = value + } else { + error = ParseError(code: .unknownError, message: "circular dependency") + return + } + } + group.leave() + } + group.wait() + if let error = error { + throw error + } + } + let commands = map { $0.saveCommand() } let returnResults = try API.Command - .batch(commands: commands) - .execute(options: options) + .batch(commands: commands) + .execute(options: options, + childObjects: childObjects, + childFiles: childFiles) try? Self.Element.updateKeychainIfNeeded(compactMap {$0}) return returnResults } @@ -704,24 +748,71 @@ public extension Sequence where Element: ParseUser { It should have the following argument signature: `(Result<[(Result)], ParseError>)`. - important: If an object saved has the same objectId as current, it will automatically update the current. */ - func saveAll( + func saveAll( // swiftlint:disable:this function_body_length options: API.Options = [], callbackQueue: DispatchQueue = .main, completion: @escaping (Result<[(Result)], ParseError>) -> Void ) { + var childObjects = [NSDictionary: PointerType]() + var childFiles = [UUID: ParseFile]() + var error: ParseError? + + let users = map { $0 } + for user in users { + let group = DispatchGroup() + group.enter() + user.ensureDeepSave(options: options) { (savedChildObjects, savedChildFiles, parseError) -> Void in + //If an error occurs, everything should be skipped + if parseError != nil { + error = parseError + } + savedChildObjects.forEach {(key, value) in + if error != nil { + return + } + if childObjects[key] == nil { + childObjects[key] = value + } else { + error = ParseError(code: .unknownError, message: "circular dependency") + return + } + } + savedChildFiles.forEach {(key, value) in + if error != nil { + return + } + if childFiles[key] == nil { + childFiles[key] = value + } else { + error = ParseError(code: .unknownError, message: "circular dependency") + return + } + } + group.leave() + } + group.wait() + if let error = error { + completion(.failure(error)) + return + } + } + let commands = map { $0.saveCommand() } API.Command .batch(commands: commands) - .executeAsync(options: options, callbackQueue: callbackQueue) { results in - switch results { - - case .success(let saved): - try? Self.Element.updateKeychainIfNeeded(self.compactMap {$0}) - completion(.success(saved)) - case .failure(let error): - completion(.failure(error)) - } + .executeAsync(options: options, + callbackQueue: callbackQueue, + childObjects: childObjects, + childFiles: childFiles) { results in + switch results { + + case .success(let saved): + try? Self.Element.updateKeychainIfNeeded(self.compactMap {$0}) + completion(.success(saved)) + case .failure(let error): + completion(.failure(error)) } + } } /** diff --git a/Sources/ParseSwift/Objects/Protocols/Objectable.swift b/Sources/ParseSwift/Objects/Protocols/Objectable.swift index 80528f6ad..f37e52ba2 100644 --- a/Sources/ParseSwift/Objects/Protocols/Objectable.swift +++ b/Sources/ParseSwift/Objects/Protocols/Objectable.swift @@ -81,7 +81,7 @@ extension Objectable { } } -internal struct UniqueObject: Hashable, Codable { +internal struct UniqueObject: ParseType, Decodable, Hashable { let objectId: String init?(target: Objectable) { diff --git a/Tests/ParseSwiftTests/ParseObjectBatchTests.swift b/Tests/ParseSwiftTests/ParseObjectBatchTests.swift index 4435d80d8..6af7be17d 100644 --- a/Tests/ParseSwiftTests/ParseObjectBatchTests.swift +++ b/Tests/ParseSwiftTests/ParseObjectBatchTests.swift @@ -87,6 +87,7 @@ class ParseObjectBatchTests: XCTestCase { // swiftlint:disable:this type_body_le } do { + let saved = try [score, score2].saveAll() XCTAssertEqual(saved.count, 2) From c4122ef57c5039614b2814b5e7a95844ca05ebde Mon Sep 17 00:00:00 2001 From: Corey's iMac Date: Thu, 31 Dec 2020 21:06:52 -0500 Subject: [PATCH 24/44] Remove json encoder from ParseEncoder --- Sources/ParseSwift/API/API+Commands.swift | 248 +++++++++--------- Sources/ParseSwift/Coding/Extensions.swift | 4 +- Sources/ParseSwift/Coding/ParseCoding.swift | 2 +- Sources/ParseSwift/Coding/ParseEncoder.swift | 9 +- Tests/ParseSwiftTests/ParseEncoderTests.swift | 41 --- 5 files changed, 129 insertions(+), 175 deletions(-) diff --git a/Sources/ParseSwift/API/API+Commands.swift b/Sources/ParseSwift/API/API+Commands.swift index 403defeb4..45c623438 100644 --- a/Sources/ParseSwift/API/API+Commands.swift +++ b/Sources/ParseSwift/API/API+Commands.swift @@ -11,6 +11,7 @@ import Foundation import FoundationNetworking #endif +// MARK: API.Command internal extension API { // swiftlint:disable:next type_body_length struct Command: Encodable where T: ParseType { @@ -446,128 +447,6 @@ internal extension API.Command { } } -internal extension API { - struct NonParseBodyCommand: Encodable where T: Encodable { - typealias ReturnType = U // swiftlint:disable:this nesting - let method: API.Method - let path: API.Endpoint - let body: T? - let mapper: ((Data) throws -> U) - let params: [String: String?]? - - init(method: API.Method, - path: API.Endpoint, - params: [String: String]? = nil, - body: T? = nil, - mapper: @escaping ((Data) throws -> U)) { - self.method = method - self.path = path - self.body = body - self.mapper = mapper - self.params = params - } - - func execute(options: API.Options) throws -> U { - var responseResult: Result? - let group = DispatchGroup() - group.enter() - self.executeAsync(options: options, - callbackQueue: nil) { result in - responseResult = result - group.leave() - } - group.wait() - - guard let response = responseResult else { - throw ParseError(code: .unknownError, - message: "couldn't unrwrap server response") - } - return try response.get() - } - - // MARK: Asynchronous Execution - func executeAsync(options: API.Options, callbackQueue: DispatchQueue?, - completion: @escaping(Result) -> Void) { - - switch self.prepareURLRequest(options: options) { - case .success(let urlRequest): - URLSession.shared.dataTask(with: urlRequest, mapper: mapper) { result in - switch result { - - case .success(let decoded): - if let callbackQueue = callbackQueue { - callbackQueue.async { completion(.success(decoded)) } - } else { - completion(.success(decoded)) - } - - case .failure(let error): - if let callbackQueue = callbackQueue { - callbackQueue.async { completion(.failure(error)) } - } else { - completion(.failure(error)) - } - } - } - case .failure(let error): - completion(.failure(error)) - } - } - - // MARK: URL Preperation - func prepareURLRequest(options: API.Options) -> Result { - let params = self.params?.getQueryItems() - let headers = API.getHeaders(options: options) - let url = ParseConfiguration.serverURL.appendingPathComponent(path.urlComponent) - - guard var components = URLComponents(url: url, resolvingAgainstBaseURL: false) else { - return .failure(ParseError(code: .unknownError, - message: "couldn't unrwrap url components for \(url)")) - } - components.queryItems = params - - guard let urlComponents = components.url else { - return .failure(ParseError(code: .unknownError, - message: "couldn't create url from components for \(components)")) - } - - var urlRequest = URLRequest(url: urlComponents) - urlRequest.allHTTPHeaderFields = headers - if let urlBody = body { - guard let bodyData = try? ParseCoding.jsonEncoder().encode(urlBody) else { - return .failure(ParseError(code: .unknownError, - message: "couldn't encode body \(urlBody)")) - } - urlRequest.httpBody = bodyData - } - urlRequest.httpMethod = method.rawValue - - return .success(urlRequest) - } - - enum CodingKeys: String, CodingKey { // swiftlint:disable:this nesting - case method, body, path - } - } -} - -internal extension API.NonParseBodyCommand { - // MARK: Deleting - // swiftlint:disable:next line_length - 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( - method: .DELETE, - path: object.endpoint - ) { (data) -> ParseError? in - try? ParseCoding.jsonDecoder().decode(ParseError.self, from: data) - } - } -} - // MARK: Batch - Saving, Fetching extension API.Command where T: ParseObject { @@ -713,4 +592,127 @@ extension API.Command where T: ParseObject { let batchCommand = BatchCommand(requests: commands) return RESTBatchCommandTypeEncodable(method: .POST, path: .batch, body: batchCommand, mapper: mapper) } -}*/ // swiftlint:disable:this file_length +}*/ + +// MARK: API.NonParseBodyCommand +internal extension API { + struct NonParseBodyCommand: Encodable where T: Encodable { + typealias ReturnType = U // swiftlint:disable:this nesting + let method: API.Method + let path: API.Endpoint + let body: T? + let mapper: ((Data) throws -> U) + let params: [String: String?]? + + init(method: API.Method, + path: API.Endpoint, + params: [String: String]? = nil, + body: T? = nil, + mapper: @escaping ((Data) throws -> U)) { + self.method = method + self.path = path + self.body = body + self.mapper = mapper + self.params = params + } + + func execute(options: API.Options) throws -> U { + var responseResult: Result? + let group = DispatchGroup() + group.enter() + self.executeAsync(options: options, + callbackQueue: nil) { result in + responseResult = result + group.leave() + } + group.wait() + + guard let response = responseResult else { + throw ParseError(code: .unknownError, + message: "couldn't unrwrap server response") + } + return try response.get() + } + + // MARK: Asynchronous Execution + func executeAsync(options: API.Options, callbackQueue: DispatchQueue?, + completion: @escaping(Result) -> Void) { + + switch self.prepareURLRequest(options: options) { + case .success(let urlRequest): + URLSession.shared.dataTask(with: urlRequest, mapper: mapper) { result in + switch result { + + case .success(let decoded): + if let callbackQueue = callbackQueue { + callbackQueue.async { completion(.success(decoded)) } + } else { + completion(.success(decoded)) + } + + case .failure(let error): + if let callbackQueue = callbackQueue { + callbackQueue.async { completion(.failure(error)) } + } else { + completion(.failure(error)) + } + } + } + case .failure(let error): + completion(.failure(error)) + } + } + + // MARK: URL Preperation + func prepareURLRequest(options: API.Options) -> Result { + let params = self.params?.getQueryItems() + let headers = API.getHeaders(options: options) + let url = ParseConfiguration.serverURL.appendingPathComponent(path.urlComponent) + + guard var components = URLComponents(url: url, resolvingAgainstBaseURL: false) else { + return .failure(ParseError(code: .unknownError, + message: "couldn't unrwrap url components for \(url)")) + } + components.queryItems = params + + guard let urlComponents = components.url else { + return .failure(ParseError(code: .unknownError, + message: "couldn't create url from components for \(components)")) + } + + var urlRequest = URLRequest(url: urlComponents) + urlRequest.allHTTPHeaderFields = headers + if let urlBody = body { + guard let bodyData = try? ParseCoding.jsonEncoder().encode(urlBody) else { + return .failure(ParseError(code: .unknownError, + message: "couldn't encode body \(urlBody)")) + } + urlRequest.httpBody = bodyData + } + urlRequest.httpMethod = method.rawValue + + return .success(urlRequest) + } + + enum CodingKeys: String, CodingKey { // swiftlint:disable:this nesting + case method, body, path + } + } +} + +internal extension API.NonParseBodyCommand { + // MARK: Deleting + // swiftlint:disable:next line_length + 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( + method: .DELETE, + path: object.endpoint + ) { (data) -> ParseError? in + try? ParseCoding.jsonDecoder().decode(ParseError.self, from: data) + } + } +} // swiftlint:disable:this file_length diff --git a/Sources/ParseSwift/Coding/Extensions.swift b/Sources/ParseSwift/Coding/Extensions.swift index 14425606d..0dd4f4ac7 100644 --- a/Sources/ParseSwift/Coding/Extensions.swift +++ b/Sources/ParseSwift/Coding/Extensions.swift @@ -31,8 +31,8 @@ extension JSONEncoder { // MARK: ParseObject public extension ParseObject { - func getEncoder(skipKeys: Bool = true) -> ParseEncoder { - return ParseCoding.parseEncoder(skipKeys: skipKeys) + func getEncoder() -> ParseEncoder { + return ParseCoding.parseEncoder() } func getJSONEncoder() -> JSONEncoder { diff --git a/Sources/ParseSwift/Coding/ParseCoding.swift b/Sources/ParseSwift/Coding/ParseCoding.swift index 57d8e84ef..70289d819 100644 --- a/Sources/ParseSwift/Coding/ParseCoding.swift +++ b/Sources/ParseSwift/Coding/ParseCoding.swift @@ -27,7 +27,7 @@ extension ParseCoding { return decoder } - static func parseEncoder(skipKeys: Bool = true) -> ParseEncoder { + static func parseEncoder() -> ParseEncoder { ParseEncoder( dateEncodingStrategy: parseDateEncodingStrategy ) diff --git a/Sources/ParseSwift/Coding/ParseEncoder.swift b/Sources/ParseSwift/Coding/ParseEncoder.swift index adc83d26d..0cf426ef2 100644 --- a/Sources/ParseSwift/Coding/ParseEncoder.swift +++ b/Sources/ParseSwift/Coding/ParseEncoder.swift @@ -51,8 +51,6 @@ extension Dictionary: _JSONStringDictionaryEncodableMarker where Key == String, // MARK: ParseEncoder public struct ParseEncoder { let dateEncodingStrategy: AnyCodable.DateEncodingStrategy? - let jsonEncoder: JSONEncoder - //var skippedKeys: SkippedKeys public enum SkippedKeys { case object @@ -76,14 +74,9 @@ public struct ParseEncoder { } init( - dateEncodingStrategy: AnyCodable.DateEncodingStrategy? = nil, - jsonEncoder: JSONEncoder = JSONEncoder() + dateEncodingStrategy: AnyCodable.DateEncodingStrategy? = nil ) { self.dateEncodingStrategy = dateEncodingStrategy - self.jsonEncoder = jsonEncoder - if let dateEncodingStrategy = dateEncodingStrategy { - self.jsonEncoder.dateEncodingStrategy = .custom(dateEncodingStrategy) - } } public func encode(_ value: T, skipKeys: SkippedKeys) throws -> Data { diff --git a/Tests/ParseSwiftTests/ParseEncoderTests.swift b/Tests/ParseSwiftTests/ParseEncoderTests.swift index 70b8fe6ca..28d45b636 100644 --- a/Tests/ParseSwiftTests/ParseEncoderTests.swift +++ b/Tests/ParseSwiftTests/ParseEncoderTests.swift @@ -44,18 +44,6 @@ class ParseEncoderTests: XCTestCase { let phoneNumbers: [String] } - func parseEncoding(for object: T) -> Data { - let encoder = ParseEncoder() - encoder.jsonEncoder.outputFormatting = .sortedKeys - - guard let encoding = try? encoder.encode(object, skipKeys: .object) else { - XCTFail("Couldn't get a Parse encoding.") - return Data() - } - - return encoding - } - func referenceEncoding(for object: T) -> Data { let encoder = JSONEncoder() encoder.outputFormatting = .sortedKeys @@ -67,35 +55,6 @@ class ParseEncoderTests: XCTestCase { return encoding } -/* - func test_encodingScalarValue() { - let encoded = parseEncoding(for: ["": 5]) - let reference = referenceEncoding(for: ["": 5]) - XCTAssertEqual(encoded, reference) - } - - func test_encodingComplexValue() { - let value = Person( - addresses: [ - "home": Address(street: "Parse St.", city: "San Francisco"), - "work": Address(street: "Server Ave.", city: "Seattle") - ], - age: 21, - name: Name(first: "Parse", last: "User"), - nicknames: [ - Name(first: "Swift", last: "Developer"), - Name(first: "iOS", last: "Engineer") - ], - phoneNumbers: [ - "1-800-PARSE", - "1-999-SWIFT" - ] - ) - - let encoded = parseEncoding(for: value) - let reference = referenceEncoding(for: value) - XCTAssertEqual(encoded.count, reference.count) - }*/ func testNestedContatiner() throws { var newACL = ParseACL() From b5a8cfa441b94d73049207303e62dd25e95ef976 Mon Sep 17 00:00:00 2001 From: Corey Date: Thu, 31 Dec 2020 21:09:59 -0500 Subject: [PATCH 25/44] Reduce codecov --- .codecov.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.codecov.yml b/.codecov.yml index c9d8e4ab0..5148b8498 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -9,6 +9,6 @@ coverage: changes: false project: default: - target: 77 + target: 76 comment: require_changes: true From 509885a385ab7f168b5fcdea06ee5d14a4695f58 Mon Sep 17 00:00:00 2001 From: Corey Date: Thu, 31 Dec 2020 21:36:48 -0500 Subject: [PATCH 26/44] Update .codecov.yml --- .codecov.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.codecov.yml b/.codecov.yml index 5148b8498..021d41f26 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -5,7 +5,7 @@ coverage: status: patch: default: - target: auto + target: 73 changes: false project: default: From 42c2080dc829f8100e8e5e648ee975c365744f41 Mon Sep 17 00:00:00 2001 From: Corey's iMac Date: Thu, 31 Dec 2020 23:31:53 -0500 Subject: [PATCH 27/44] clean up --- Sources/ParseSwift/Coding/ParseEncoder.swift | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/Sources/ParseSwift/Coding/ParseEncoder.swift b/Sources/ParseSwift/Coding/ParseEncoder.swift index 0cf426ef2..c970e414b 100644 --- a/Sources/ParseSwift/Coding/ParseEncoder.swift +++ b/Sources/ParseSwift/Coding/ParseEncoder.swift @@ -58,7 +58,7 @@ public struct ParseEncoder { case none case custom(Set) - func use() -> Set { + func keys() -> Set { switch self { case .object: @@ -80,8 +80,7 @@ public struct ParseEncoder { } public func encode(_ value: T, skipKeys: SkippedKeys) throws -> Data { - let skippedKeys = skipKeys.use() - let encoder = _ParseEncoder(codingPath: [], dictionary: NSMutableDictionary(), skippingKeys: skippedKeys) + let encoder = _ParseEncoder(codingPath: [], dictionary: NSMutableDictionary(), skippingKeys: skipKeys.keys()) if let dateEncodingStrategy = dateEncodingStrategy { encoder.dateEncodingStrategy = .custom(dateEncodingStrategy) } @@ -92,8 +91,7 @@ public struct ParseEncoder { internal func encode(_ value: ParseType, collectChildren: Bool, objectsSavedBeforeThisOne: [NSDictionary: PointerType]?, filesSavedBeforeThisOne: [UUID: ParseFile]?) throws -> (encoded: Data, unique: Set, unsavedChildren: [ParseType]) { - let skippedKeys = SkippedKeys.object.use() - let encoder = _ParseEncoder(codingPath: [], dictionary: NSMutableDictionary(), skippingKeys: skippedKeys) + let encoder = _ParseEncoder(codingPath: [], dictionary: NSMutableDictionary(), skippingKeys: SkippedKeys.object.keys()) if let dateEncodingStrategy = dateEncodingStrategy { encoder.dateEncodingStrategy = .custom(dateEncodingStrategy) } From 9a95280c724709ebd3ab6bc6c683ad18d4f2f031 Mon Sep 17 00:00:00 2001 From: Corey's iMac Date: Fri, 1 Jan 2021 11:24:05 -0500 Subject: [PATCH 28/44] Improve LocallyIdentifiable protocol, change file structure of project --- ParseSwift.xcodeproj/project.pbxproj | 4 +- Sources/ParseSwift/Coding/ParseEncoder.swift | 2 +- .../Internal/BaseParseInstallation.swift | 0 .../{Types => }/Internal/BaseParseUser.swift | 0 .../{Types => }/Internal/NoBody.swift | 0 .../{Types => }/Internal/ParseHash.swift | 0 Sources/ParseSwift/Objects/ParseObject.swift | 2 +- .../Protocols/LocallyIdentifiable.swift | 31 --------------- .../{Objects => }/Protocols/Deletable.swift | 0 .../{Objects => }/Protocols/Fetchable.swift | 0 .../{Objects => }/Protocols/Fileable.swift | 18 +++++++-- .../Protocols/LocallyIdentifiable.swift | 38 +++++++++++++++++++ .../{Objects => }/Protocols/Objectable.swift | 0 .../{Objects => }/Protocols/ParseType.swift | 0 .../{Objects => }/Protocols/Queryable.swift | 0 .../{Objects => }/Protocols/Savable.swift | 0 Sources/ParseSwift/Types/ParseFile.swift | 8 ++-- Tests/ParseSwiftTests/ParseFileTests.swift | 8 ++-- 18 files changed, 64 insertions(+), 47 deletions(-) rename Sources/ParseSwift/{Types => }/Internal/BaseParseInstallation.swift (100%) rename Sources/ParseSwift/{Types => }/Internal/BaseParseUser.swift (100%) rename Sources/ParseSwift/{Types => }/Internal/NoBody.swift (100%) rename Sources/ParseSwift/{Types => }/Internal/ParseHash.swift (100%) delete mode 100644 Sources/ParseSwift/Objects/Protocols/LocallyIdentifiable.swift rename Sources/ParseSwift/{Objects => }/Protocols/Deletable.swift (100%) rename Sources/ParseSwift/{Objects => }/Protocols/Fetchable.swift (100%) rename Sources/ParseSwift/{Objects => }/Protocols/Fileable.swift (58%) create mode 100644 Sources/ParseSwift/Protocols/LocallyIdentifiable.swift rename Sources/ParseSwift/{Objects => }/Protocols/Objectable.swift (100%) rename Sources/ParseSwift/{Objects => }/Protocols/ParseType.swift (100%) rename Sources/ParseSwift/{Objects => }/Protocols/Queryable.swift (100%) rename Sources/ParseSwift/{Objects => }/Protocols/Savable.swift (100%) diff --git a/ParseSwift.xcodeproj/project.pbxproj b/ParseSwift.xcodeproj/project.pbxproj index 1b936b770..947f80614 100644 --- a/ParseSwift.xcodeproj/project.pbxproj +++ b/ParseSwift.xcodeproj/project.pbxproj @@ -583,8 +583,10 @@ children = ( F97B45C924D9C6F200F4A88B /* API */, F97B45B324D9C6F200F4A88B /* Coding */, + 70110D5D250849B30091CC1D /* Internal */, F97B463F24D9C78B00F4A88B /* Mutation Operations */, F97B45C324D9C6F200F4A88B /* Objects */, + 70110D5E25084AF80091CC1D /* Protocols */, F97B45BA24D9C6F200F4A88B /* Types */, F97B45CB24D9C6F200F4A88B /* Storage */, 4A82B7EE1F254B820063D731 /* Parse.swift */, @@ -725,7 +727,6 @@ F97B45BC24D9C6F200F4A88B /* ParseGeoPoint.swift */, F97B45BE24D9C6F200F4A88B /* Pointer.swift */, F97B45BB24D9C6F200F4A88B /* Query.swift */, - 70110D5D250849B30091CC1D /* Internal */, ); path = Types; sourceTree = ""; @@ -736,7 +737,6 @@ 70BDA2B2250536FF00FC2237 /* ParseInstallation.swift */, F97B45C624D9C6F200F4A88B /* ParseObject.swift */, F97B45C424D9C6F200F4A88B /* ParseUser.swift */, - 70110D5E25084AF80091CC1D /* Protocols */, ); path = Objects; sourceTree = ""; diff --git a/Sources/ParseSwift/Coding/ParseEncoder.swift b/Sources/ParseSwift/Coding/ParseEncoder.swift index c970e414b..8ea3565fc 100644 --- a/Sources/ParseSwift/Coding/ParseEncoder.swift +++ b/Sources/ParseSwift/Coding/ParseEncoder.swift @@ -278,7 +278,7 @@ private class _ParseEncoder: JSONEncoder, Encoder { } } else { var mutableValue = value - let uuid = mutableValue.localUUID + let uuid = mutableValue.establishedLocalUUID if self.collectChildren { if let updatedFile = self.filesSavedBeforeThisOne?[uuid] { valueToEncode = updatedFile diff --git a/Sources/ParseSwift/Types/Internal/BaseParseInstallation.swift b/Sources/ParseSwift/Internal/BaseParseInstallation.swift similarity index 100% rename from Sources/ParseSwift/Types/Internal/BaseParseInstallation.swift rename to Sources/ParseSwift/Internal/BaseParseInstallation.swift diff --git a/Sources/ParseSwift/Types/Internal/BaseParseUser.swift b/Sources/ParseSwift/Internal/BaseParseUser.swift similarity index 100% rename from Sources/ParseSwift/Types/Internal/BaseParseUser.swift rename to Sources/ParseSwift/Internal/BaseParseUser.swift diff --git a/Sources/ParseSwift/Types/Internal/NoBody.swift b/Sources/ParseSwift/Internal/NoBody.swift similarity index 100% rename from Sources/ParseSwift/Types/Internal/NoBody.swift rename to Sources/ParseSwift/Internal/NoBody.swift diff --git a/Sources/ParseSwift/Types/Internal/ParseHash.swift b/Sources/ParseSwift/Internal/ParseHash.swift similarity index 100% rename from Sources/ParseSwift/Types/Internal/ParseHash.swift rename to Sources/ParseSwift/Internal/ParseHash.swift diff --git a/Sources/ParseSwift/Objects/ParseObject.swift b/Sources/ParseSwift/Objects/ParseObject.swift index babbfb51c..9fcdd6dea 100644 --- a/Sources/ParseSwift/Objects/ParseObject.swift +++ b/Sources/ParseSwift/Objects/ParseObject.swift @@ -541,7 +541,7 @@ extension ParseObject { try savableFiles.forEach { var file = $0 - filesFinishedSaving[file.localUUID] = try $0.save(options: options) + filesFinishedSaving[file.establishedLocalUUID] = try $0.save(options: options) } } completion(objectsFinishedSaving, filesFinishedSaving, nil) diff --git a/Sources/ParseSwift/Objects/Protocols/LocallyIdentifiable.swift b/Sources/ParseSwift/Objects/Protocols/LocallyIdentifiable.swift deleted file mode 100644 index 25a5c243a..000000000 --- a/Sources/ParseSwift/Objects/Protocols/LocallyIdentifiable.swift +++ /dev/null @@ -1,31 +0,0 @@ -// -// LocallyIdentifiable.swift -// ParseSwift -// -// Created by Corey Baker on 12/31/20. -// Copyright © 2020 Parse Community. All rights reserved. -// - -import Foundation - -public protocol LocallyIdentifiable: Encodable, Hashable { - var __localUUID: UUID? { get set } // swiftlint:disable:this identifier_name -} - -public extension LocallyIdentifiable { - var localUUID: UUID { - mutating get { - if self.__localUUID == nil { - self.__localUUID = UUID() - } - return __localUUID! - } - } - - // Equatable - static func == (lhs: Self, rhs: Self) -> Bool { - var lhs = lhs - var rhs = rhs - return lhs.localUUID == rhs.localUUID - } -} diff --git a/Sources/ParseSwift/Objects/Protocols/Deletable.swift b/Sources/ParseSwift/Protocols/Deletable.swift similarity index 100% rename from Sources/ParseSwift/Objects/Protocols/Deletable.swift rename to Sources/ParseSwift/Protocols/Deletable.swift diff --git a/Sources/ParseSwift/Objects/Protocols/Fetchable.swift b/Sources/ParseSwift/Protocols/Fetchable.swift similarity index 100% rename from Sources/ParseSwift/Objects/Protocols/Fetchable.swift rename to Sources/ParseSwift/Protocols/Fetchable.swift diff --git a/Sources/ParseSwift/Objects/Protocols/Fileable.swift b/Sources/ParseSwift/Protocols/Fileable.swift similarity index 58% rename from Sources/ParseSwift/Objects/Protocols/Fileable.swift rename to Sources/ParseSwift/Protocols/Fileable.swift index 72f7a1d25..52edb4eb5 100644 --- a/Sources/ParseSwift/Objects/Protocols/Fileable.swift +++ b/Sources/ParseSwift/Protocols/Fileable.swift @@ -19,13 +19,23 @@ extension Fileable { return url != nil } - // Equatable + mutating func hash(into hasher: inout Hasher) { + if let url = url { + hasher.combine(url) + } else { + hasher.combine(self.establishedLocalUUID) + } + } + public static func == (lhs: Self, rhs: Self) -> Bool { guard let lURL = lhs.url, let rURL = rhs.url else { - var lhs = lhs - var rhs = rhs - return lhs.localUUID == rhs.localUUID + guard let lhsUUID = lhs.localUUID, + let rhsUUID = rhs.localUUID else { + //Can only compare objects that have a localUUID + return false + } + return lhsUUID == rhsUUID } return lURL == rURL } diff --git a/Sources/ParseSwift/Protocols/LocallyIdentifiable.swift b/Sources/ParseSwift/Protocols/LocallyIdentifiable.swift new file mode 100644 index 000000000..b843a8b10 --- /dev/null +++ b/Sources/ParseSwift/Protocols/LocallyIdentifiable.swift @@ -0,0 +1,38 @@ +// +// LocallyIdentifiable.swift +// ParseSwift +// +// Created by Corey Baker on 12/31/20. +// Copyright © 2020 Parse Community. All rights reserved. +// + +import Foundation + +public protocol LocallyIdentifiable: Encodable, Hashable { + var localUUID: UUID? { get set } +} + +extension LocallyIdentifiable { + + public var establishedLocalUUID: UUID { + mutating get { + if self.localUUID == nil { + self.localUUID = UUID() + } + return self.localUUID! + } + } + + mutating func hash(into hasher: inout Hasher) { + hasher.combine(self.establishedLocalUUID) + } + + static func == (lhs: Self, rhs: Self) -> Bool { + guard let lhsUUID = lhs.localUUID, + let rhsUUID = rhs.localUUID else { + //Can only compare objects that have a localUUID + return false + } + return lhsUUID == rhsUUID + } +} diff --git a/Sources/ParseSwift/Objects/Protocols/Objectable.swift b/Sources/ParseSwift/Protocols/Objectable.swift similarity index 100% rename from Sources/ParseSwift/Objects/Protocols/Objectable.swift rename to Sources/ParseSwift/Protocols/Objectable.swift diff --git a/Sources/ParseSwift/Objects/Protocols/ParseType.swift b/Sources/ParseSwift/Protocols/ParseType.swift similarity index 100% rename from Sources/ParseSwift/Objects/Protocols/ParseType.swift rename to Sources/ParseSwift/Protocols/ParseType.swift diff --git a/Sources/ParseSwift/Objects/Protocols/Queryable.swift b/Sources/ParseSwift/Protocols/Queryable.swift similarity index 100% rename from Sources/ParseSwift/Objects/Protocols/Queryable.swift rename to Sources/ParseSwift/Protocols/Queryable.swift diff --git a/Sources/ParseSwift/Objects/Protocols/Savable.swift b/Sources/ParseSwift/Protocols/Savable.swift similarity index 100% rename from Sources/ParseSwift/Objects/Protocols/Savable.swift rename to Sources/ParseSwift/Protocols/Savable.swift diff --git a/Sources/ParseSwift/Types/ParseFile.swift b/Sources/ParseSwift/Types/ParseFile.swift index d154d8b53..60fa887f4 100644 --- a/Sources/ParseSwift/Types/ParseFile.swift +++ b/Sources/ParseSwift/Types/ParseFile.swift @@ -15,7 +15,7 @@ public struct ParseFile: Fileable, Savable, Fetchable, Deletable { && data == nil } - public var __localUUID: UUID? // swiftlint:disable:this identifier_name + public var localUUID: UUID? /** The name of the file. @@ -80,7 +80,7 @@ public struct ParseFile: Fileable, Savable, Fetchable, Deletable { self.metadata = metadata self.tags = tags self.options = options - _ = self.localUUID //Need to ensure this creates a uuid + _ = self.establishedLocalUUID //Need to ensure this creates a uuid } /** @@ -106,7 +106,7 @@ public struct ParseFile: Fileable, Savable, Fetchable, Deletable { self.metadata = metadata self.tags = tags self.options = options - _ = self.localUUID //Need to ensure this creates a uuid + _ = self.establishedLocalUUID //Need to ensure this creates a uuid } /** @@ -132,7 +132,7 @@ public struct ParseFile: Fileable, Savable, Fetchable, Deletable { self.metadata = metadata self.tags = tags self.options = options - _ = self.localUUID //Need to ensure this creates a uuid + _ = self.establishedLocalUUID //Need to ensure this creates a uuid } enum CodingKeys: String, CodingKey { diff --git a/Tests/ParseSwiftTests/ParseFileTests.swift b/Tests/ParseSwiftTests/ParseFileTests.swift index 8c34f27c5..4a2f4eeca 100644 --- a/Tests/ParseSwiftTests/ParseFileTests.swift +++ b/Tests/ParseSwiftTests/ParseFileTests.swift @@ -141,8 +141,8 @@ class ParseFileTests: XCTestCase { // swiftlint:disable:this type_body_length guard let sampleData = "Hello World".data(using: .utf8) else { throw ParseError(code: .unknownError, message: "Should have converted to data") } - var parseFile = ParseFile(name: "sampleData.txt", data: sampleData) - let localUUID = parseFile.__localUUID + let parseFile = ParseFile(name: "sampleData.txt", data: sampleData) + let localUUID = parseFile.localUUID XCTAssertNotNil(localUUID) XCTAssertEqual(localUUID, parseFile.localUUID, @@ -171,8 +171,8 @@ class ParseFileTests: XCTestCase { // swiftlint:disable:this type_body_length parseFile2.url = nil XCTAssertNotEqual(parseFile1, parseFile2, "no urls, but localUUIDs shoud be different") let uuid = UUID() - parseFile1.__localUUID = uuid - parseFile2.__localUUID = uuid + parseFile1.localUUID = uuid + parseFile2.localUUID = uuid XCTAssertEqual(parseFile1, parseFile2, "no urls, but localUUIDs shoud be the same") } From 2c31d599620e3fea9c181b659e99f8e9fde01a39 Mon Sep 17 00:00:00 2001 From: Corey's iMac Date: Fri, 1 Jan 2021 16:41:56 -0500 Subject: [PATCH 29/44] Save for later: ParseObject with localID, but doesn't work. This will be reverted back --- Sources/ParseSwift/API/API+Commands.swift | 3 ++- Sources/ParseSwift/Coding/ParseEncoder.swift | 16 +++++++++--- .../Internal/BaseParseInstallation.swift | 1 + .../ParseSwift/Internal/BaseParseUser.swift | 1 + Sources/ParseSwift/Objects/ParseObject.swift | 20 +++++++++----- Sources/ParseSwift/Objects/ParseUser.swift | 19 ++++++++------ Sources/ParseSwift/Protocols/Objectable.swift | 11 +++++++- Sources/ParseSwift/Types/ParseACL.swift | 1 + Sources/ParseSwift/Types/ParseError.swift | 1 + Sources/ParseSwift/Types/ParseGeoPoint.swift | 2 +- Sources/ParseSwift/Types/Pointer.swift | 10 +++++-- Sources/ParseSwift/Types/Query.swift | 26 +++++++++---------- Tests/ParseSwiftTests/ACLTests.swift | 3 +++ Tests/ParseSwiftTests/ParseEncoderTests.swift | 2 ++ .../ParseInstallationTests.swift | 4 +++ .../ParseObjectBatchTests.swift | 2 ++ Tests/ParseSwiftTests/ParseObjectTests.swift | 8 ++++++ Tests/ParseSwiftTests/ParsePointerTests.swift | 2 ++ Tests/ParseSwiftTests/ParseQueryTests.swift | 2 ++ Tests/ParseSwiftTests/ParseUserTests.swift | 2 ++ 20 files changed, 100 insertions(+), 36 deletions(-) diff --git a/Sources/ParseSwift/API/API+Commands.swift b/Sources/ParseSwift/API/API+Commands.swift index 45c623438..e5944e7a2 100644 --- a/Sources/ParseSwift/API/API+Commands.swift +++ b/Sources/ParseSwift/API/API+Commands.swift @@ -281,7 +281,8 @@ internal extension API { guard let bodyData = try? ParseCoding .parseEncoder() .encode(urlBody, collectChildren: false, - objectsSavedBeforeThisOne: childObjects, filesSavedBeforeThisOne: childFiles) else { + objectsSavedBeforeThisOne: childObjects, + filesSavedBeforeThisOne: childFiles) else { return .failure(ParseError(code: .unknownError, message: "couldn't encode body \(urlBody)")) } diff --git a/Sources/ParseSwift/Coding/ParseEncoder.swift b/Sources/ParseSwift/Coding/ParseEncoder.swift index 8ea3565fc..eb8985f60 100644 --- a/Sources/ParseSwift/Coding/ParseEncoder.swift +++ b/Sources/ParseSwift/Coding/ParseEncoder.swift @@ -62,9 +62,9 @@ public struct ParseEncoder { switch self { case .object: - return Set(["createdAt", "updatedAt", "objectId", "className"]) + return Set(["createdAt", "updatedAt", "objectId", "className", "localUUID"]) case .cloud: - return Set(["functionJobName"]) + return Set(["functionJobName", "localUUID"]) case .none: return .init() case .custom(let keys): @@ -87,6 +87,17 @@ public struct ParseEncoder { return try encoder.encodeObject(value, collectChildren: false, objectsSavedBeforeThisOne: nil, filesSavedBeforeThisOne: nil).encoded } + // swiftlint:disable large_tuple + internal func encode(_ value: T, + objectsSavedBeforeThisOne: [NSDictionary: PointerType]?, + filesSavedBeforeThisOne: [UUID: ParseFile]?) throws -> (encoded: Data, unique: Set, unsavedChildren: [ParseType]) { + let encoder = _ParseEncoder(codingPath: [], dictionary: NSMutableDictionary(), skippingKeys: SkippedKeys.object.keys()) + if let dateEncodingStrategy = dateEncodingStrategy { + encoder.dateEncodingStrategy = .custom(dateEncodingStrategy) + } + return try encoder.encodeObject(value, collectChildren: true, objectsSavedBeforeThisOne: objectsSavedBeforeThisOne, filesSavedBeforeThisOne: filesSavedBeforeThisOne) + } + // swiftlint:disable large_tuple internal func encode(_ value: ParseType, collectChildren: Bool, objectsSavedBeforeThisOne: [NSDictionary: PointerType]?, @@ -158,7 +169,6 @@ private class _ParseEncoder: JSONEncoder, Encoder { throw ParseError(code: .unknownError, message: "This method shouldn't be used. Either use the JSONEncoder or if you are encoding a ParseObject use \"encodeObject\"") } - // swiftlint:disable large_tuple func encodeObject(_ value: ParseType, collectChildren: Bool, objectsSavedBeforeThisOne: [NSDictionary: PointerType]?, filesSavedBeforeThisOne: [UUID: ParseFile]?) throws -> (encoded: Data, unique: Set, unsavedChildren: [ParseType]) { diff --git a/Sources/ParseSwift/Internal/BaseParseInstallation.swift b/Sources/ParseSwift/Internal/BaseParseInstallation.swift index 994802555..98737d3a6 100644 --- a/Sources/ParseSwift/Internal/BaseParseInstallation.swift +++ b/Sources/ParseSwift/Internal/BaseParseInstallation.swift @@ -9,6 +9,7 @@ import Foundation internal struct BaseParseInstallation: ParseInstallation { + var localUUID: UUID? var deviceType: String? var installationId: String? var deviceToken: String? diff --git a/Sources/ParseSwift/Internal/BaseParseUser.swift b/Sources/ParseSwift/Internal/BaseParseUser.swift index 8fad515b8..80dda6100 100644 --- a/Sources/ParseSwift/Internal/BaseParseUser.swift +++ b/Sources/ParseSwift/Internal/BaseParseUser.swift @@ -9,6 +9,7 @@ import Foundation /// Used internally to form a concrete type representing `ParseUser`. internal struct BaseParseUser: ParseUser { + var localUUID: UUID? var username: String? var email: String? var password: String? diff --git a/Sources/ParseSwift/Objects/ParseObject.swift b/Sources/ParseSwift/Objects/ParseObject.swift index 9fcdd6dea..9cd5990e2 100644 --- a/Sources/ParseSwift/Objects/ParseObject.swift +++ b/Sources/ParseSwift/Objects/ParseObject.swift @@ -24,7 +24,12 @@ import Foundation and relying on that for `Equatable` and `Hashable`, otherwise it's possible you will get "circular dependency errors" depending on your implementation. */ -public protocol ParseObject: Objectable, Fetchable, Savable, Deletable, Hashable, CustomDebugStringConvertible {} +public protocol ParseObject: LocallyIdentifiable, + Objectable, + Fetchable, + Savable, + Deletable, + CustomDebugStringConvertible {} // MARK: Default Implementations extension ParseObject { @@ -490,13 +495,14 @@ extension ParseObject { do { let object = try ParseCoding.parseEncoder() - .encode(self, collectChildren: true, - objectsSavedBeforeThisOne: nil, filesSavedBeforeThisOne: nil) + .encode(self, + objectsSavedBeforeThisOne: nil, + filesSavedBeforeThisOne: nil) var waitingToBeSaved = object.unsavedChildren while waitingToBeSaved.count > 0 { - var savableObjects = [ParseType]() + var savableObjects = [Objectable]() var savableFiles = [ParseFile]() var nextBatch = [ParseType]() try waitingToBeSaved.forEach { parseType in @@ -504,18 +510,18 @@ extension ParseObject { if let parseFile = parseType as? ParseFile { //ParseFiles can be saved now savableFiles.append(parseFile) - } else { + } else if let parseObject = parseType as? Objectable { //This is a ParseObject let waitingObjectInfo = try ParseCoding .parseEncoder() - .encode(parseType, + .encode(parseObject, collectChildren: true, objectsSavedBeforeThisOne: objectsFinishedSaving, filesSavedBeforeThisOne: filesFinishedSaving) if waitingObjectInfo.unsavedChildren.count == 0 { //If this ParseObject has no additional children, it can be saved now - savableObjects.append(parseType) + savableObjects.append(parseObject) } else { //Else this ParseObject needs to wait until it's children are saved nextBatch.append(parseType) diff --git a/Sources/ParseSwift/Objects/ParseUser.swift b/Sources/ParseSwift/Objects/ParseUser.swift index 5a5bdd864..637e8a399 100644 --- a/Sources/ParseSwift/Objects/ParseUser.swift +++ b/Sources/ParseSwift/Objects/ParseUser.swift @@ -26,13 +26,13 @@ public protocol ParseUser: ParseObject { } // MARK: SignupBody -struct SignupBody: ParseType { +struct SignupBody: Encodable { let username: String let password: String } // MARK: EmailBody -struct EmailBody: ParseType { +struct EmailBody: Encodable { let email: String } @@ -271,9 +271,9 @@ extension ParseUser { } } - internal static func passwordResetCommand(email: String) -> API.Command { + internal static func passwordResetCommand(email: String) -> API.NonParseBodyCommand { let emailBody = EmailBody(email: email) - return API.Command(method: .POST, path: .passwordReset, body: emailBody) { (data) -> ParseError? in + return API.NonParseBodyCommand(method: .POST, path: .passwordReset, body: emailBody) { (data) -> ParseError? in try? ParseCoding.jsonDecoder().decode(ParseError.self, from: data) } } @@ -320,9 +320,12 @@ extension ParseUser { } } - internal static func verificationEmailRequestCommand(email: String) -> API.Command { + // swiftlint:disable:next line_length + internal static func verificationEmailRequestCommand(email: String) -> API.NonParseBodyCommand { let emailBody = EmailBody(email: email) - return API.Command(method: .POST, path: .verificationEmailRequest, body: emailBody) { (data) -> ParseError? in + return API.NonParseBodyCommand(method: .POST, + path: .verificationEmailRequest, + body: emailBody) { (data) -> ParseError? in try? ParseCoding.jsonDecoder().decode(ParseError.self, from: data) } } @@ -400,10 +403,10 @@ extension ParseUser { } internal static func signupCommand(username: String, - password: String) -> API.Command { + password: String) -> API.NonParseBodyCommand { let body = SignupBody(username: username, password: password) - return API.Command(method: .POST, path: .users, body: body) { (data) -> Self in + return API.NonParseBodyCommand(method: .POST, path: .users, body: body) { (data) -> Self in let response = try ParseCoding.jsonDecoder().decode(LoginSignupResponse.self, from: data) var user = try ParseCoding.jsonDecoder().decode(Self.self, from: data) diff --git a/Sources/ParseSwift/Protocols/Objectable.swift b/Sources/ParseSwift/Protocols/Objectable.swift index f37e52ba2..da6f80959 100644 --- a/Sources/ParseSwift/Protocols/Objectable.swift +++ b/Sources/ParseSwift/Protocols/Objectable.swift @@ -33,6 +33,8 @@ public protocol Objectable: ParseType, Decodable { The ACL for this object. */ var ACL: ParseACL? { get set } + + var localUUID: UUID? { get set } } extension Objectable { @@ -60,6 +62,11 @@ extension Objectable { let encoded = try ParseCoding.jsonEncoder().encode(self) return try ParseCoding.jsonDecoder().decode(UniqueObject.self, from: encoded) } + + internal func getLocalUniqueObject() throws -> LocalUniqueObject { + let encoded = try ParseCoding.jsonEncoder().encode(self) + return try ParseCoding.jsonDecoder().decode(LocalUniqueObject.self, from: encoded) + } } // MARK: Convenience @@ -81,7 +88,7 @@ extension Objectable { } } -internal struct UniqueObject: ParseType, Decodable, Hashable { +internal struct UniqueObject: Encodable, Decodable, Hashable { let objectId: String init?(target: Objectable) { @@ -94,6 +101,8 @@ internal struct UniqueObject: ParseType, Decodable, Hashable { } internal struct BaseObjectable: Objectable { + var localUUID: UUID? + var objectId: String? var createdAt: Date? diff --git a/Sources/ParseSwift/Types/ParseACL.swift b/Sources/ParseSwift/Types/ParseACL.swift index 6232754e8..25002a104 100644 --- a/Sources/ParseSwift/Types/ParseACL.swift +++ b/Sources/ParseSwift/Types/ParseACL.swift @@ -16,6 +16,7 @@ import Foundation particular set of users could write to that object. */ public struct ParseACL: ParseType, Decodable, Equatable, Hashable { + public var localUUID: UUID? private static let publicScope = "*" private var acl: [String: [Access: Bool]]? diff --git a/Sources/ParseSwift/Types/ParseError.swift b/Sources/ParseSwift/Types/ParseError.swift index c1b45aee9..dabe7a51a 100644 --- a/Sources/ParseSwift/Types/ParseError.swift +++ b/Sources/ParseSwift/Types/ParseError.swift @@ -9,6 +9,7 @@ import Foundation public struct ParseError: ParseType, Decodable, Swift.Error { + public var localUUID: UUID? public let code: Code public let message: String diff --git a/Sources/ParseSwift/Types/ParseGeoPoint.swift b/Sources/ParseSwift/Types/ParseGeoPoint.swift index b86173dfe..10b0a4672 100644 --- a/Sources/ParseSwift/Types/ParseGeoPoint.swift +++ b/Sources/ParseSwift/Types/ParseGeoPoint.swift @@ -8,7 +8,7 @@ import CoreLocation It could be used to perform queries in a geospatial manner using `ParseQuery.-whereKey:nearGeoPoint:`. Currently, instances of `ParseObject` may only have one key associated with a `ParseGeoPoint` type. */ -public struct ParseGeoPoint: ParseType, Decodable, Hashable { +public struct ParseGeoPoint: Codable, Hashable { private let __type: String = "GeoPoint" // swiftlint:disable:this identifier_name static let earthRadiusMiles = 3958.8 static let earthRadiusKilometers = 6371.0 diff --git a/Sources/ParseSwift/Types/Pointer.swift b/Sources/ParseSwift/Types/Pointer.swift index b4990abfa..417280dcb 100644 --- a/Sources/ParseSwift/Types/Pointer.swift +++ b/Sources/ParseSwift/Types/Pointer.swift @@ -14,7 +14,7 @@ private func getObjectId(target: Objectable) -> String { return objectId } -public struct Pointer: ParseType, Fetchable { +public struct Pointer: Fetchable, Encodable { public typealias FetchingType = T private let __type: String = "Pointer" // swiftlint:disable:this identifier_name @@ -34,6 +34,12 @@ public struct Pointer: ParseType, Fetchable { private enum CodingKeys: String, CodingKey { case __type, objectId, className // swiftlint:disable:this identifier_name } + + public init(from decoder: Decoder) throws { + let values = try decoder.container(keyedBy: CodingKeys.self) + objectId = try values.decode(String.self, forKey: .objectId) + className = try values.decode(String.self, forKey: .className) + } } extension Pointer { @@ -55,7 +61,7 @@ extension Pointer { } } -internal struct PointerType: ParseType { +internal struct PointerType: Encodable { var __type: String = "Pointer" // swiftlint:disable:this identifier_name public var objectId: String diff --git a/Sources/ParseSwift/Types/Query.swift b/Sources/ParseSwift/Types/Query.swift index 1546e36b0..42eb7020f 100644 --- a/Sources/ParseSwift/Types/Query.swift +++ b/Sources/ParseSwift/Types/Query.swift @@ -481,7 +481,7 @@ internal struct QueryWhere: Encodable, Equatable { /** The `Query` class defines a query that is used to query for `ParseObject`s. */ -public class Query: ParseType, Equatable where T: ParseObject { +public class Query: Encodable, Equatable where T: ParseObject { // interpolate as GET private let method: String = "GET" internal var limit: Int = 100 @@ -890,34 +890,34 @@ extension Query: Queryable { } private extension Query { - private func findCommand() -> API.Command, [ResultType]> { - return API.Command(method: .POST, path: endpoint, body: self) { + private func findCommand() -> API.NonParseBodyCommand, [ResultType]> { + return API.NonParseBodyCommand(method: .POST, path: endpoint, body: self) { try ParseCoding.jsonDecoder().decode(QueryResponse.self, from: $0).results } } - private func firstCommand() -> API.Command, ResultType?> { + private func firstCommand() -> API.NonParseBodyCommand, ResultType?> { let query = self query.limit = 1 - return API.Command(method: .POST, path: endpoint, body: query) { + return API.NonParseBodyCommand(method: .POST, path: endpoint, body: query) { try ParseCoding.jsonDecoder().decode(QueryResponse.self, from: $0).results.first } } - private func countCommand() -> API.Command, Int> { + private func countCommand() -> API.NonParseBodyCommand, Int> { let query = self query.limit = 1 query.isCount = true - return API.Command(method: .POST, path: endpoint, body: query) { + return API.NonParseBodyCommand(method: .POST, path: endpoint, body: query) { try ParseCoding.jsonDecoder().decode(QueryResponse.self, from: $0).count ?? 0 } } - private func findCommand(explain: Bool, hint: String?) -> API.Command, AnyCodable> { + private func findCommand(explain: Bool, hint: String?) -> API.NonParseBodyCommand, AnyCodable> { let query = self query.explain = explain query.hint = hint - return API.Command(method: .POST, path: endpoint, body: query) { + return API.NonParseBodyCommand(method: .POST, path: endpoint, body: query) { if let results = try JSONDecoder().decode(AnyResultsResponse.self, from: $0).results { return results } @@ -925,12 +925,12 @@ private extension Query { } } - private func firstCommand(explain: Bool, hint: String?) -> API.Command, AnyCodable> { + private func firstCommand(explain: Bool, hint: String?) -> API.NonParseBodyCommand, AnyCodable> { let query = self query.limit = 1 query.explain = explain query.hint = hint - return API.Command(method: .POST, path: endpoint, body: query) { + return API.NonParseBodyCommand(method: .POST, path: endpoint, body: query) { if let results = try JSONDecoder().decode(AnyResultsResponse.self, from: $0).results { return results } @@ -938,13 +938,13 @@ private extension Query { } } - private func countCommand(explain: Bool, hint: String?) -> API.Command, AnyCodable> { + private func countCommand(explain: Bool, hint: String?) -> API.NonParseBodyCommand, AnyCodable> { let query = self query.limit = 1 query.isCount = true query.explain = explain query.hint = hint - return API.Command(method: .POST, path: endpoint, body: query) { + return API.NonParseBodyCommand(method: .POST, path: endpoint, body: query) { if let results = try JSONDecoder().decode(AnyResultsResponse.self, from: $0).results { return results } diff --git a/Tests/ParseSwiftTests/ACLTests.swift b/Tests/ParseSwiftTests/ACLTests.swift index 19186e8ab..efe08df8a 100644 --- a/Tests/ParseSwiftTests/ACLTests.swift +++ b/Tests/ParseSwiftTests/ACLTests.swift @@ -31,6 +31,8 @@ class ACLTests: XCTestCase { } struct User: ParseUser { + var localUUID: UUID? + //: Those are required for Object var objectId: String? var createdAt: Date? @@ -47,6 +49,7 @@ class ACLTests: XCTestCase { } struct LoginSignupResponse: ParseUser { + var localUUID: UUID? var objectId: String? var createdAt: Date? var sessionToken: String diff --git a/Tests/ParseSwiftTests/ParseEncoderTests.swift b/Tests/ParseSwiftTests/ParseEncoderTests.swift index 28d45b636..dee083113 100644 --- a/Tests/ParseSwiftTests/ParseEncoderTests.swift +++ b/Tests/ParseSwiftTests/ParseEncoderTests.swift @@ -11,6 +11,8 @@ import XCTest class ParseEncoderTests: XCTestCase { struct GameScore: ParseObject { + var localUUID: UUID? + //: Those are required for Object var objectId: String? var createdAt: Date? diff --git a/Tests/ParseSwiftTests/ParseInstallationTests.swift b/Tests/ParseSwiftTests/ParseInstallationTests.swift index 6bfafa4f8..d17d1b235 100644 --- a/Tests/ParseSwiftTests/ParseInstallationTests.swift +++ b/Tests/ParseSwiftTests/ParseInstallationTests.swift @@ -18,6 +18,8 @@ import XCTest class ParseInstallationTests: XCTestCase { // swiftlint:disable:this type_body_length struct User: ParseUser { + var localUUID: UUID? + //: Those are required for Object var objectId: String? var createdAt: Date? @@ -34,6 +36,7 @@ class ParseInstallationTests: XCTestCase { // swiftlint:disable:this type_body_l } struct LoginSignupResponse: ParseUser { + var localUUID: UUID? var objectId: String? var createdAt: Date? var sessionToken: String @@ -61,6 +64,7 @@ class ParseInstallationTests: XCTestCase { // swiftlint:disable:this type_body_l } struct Installation: ParseInstallation { + var localUUID: UUID? var installationId: String? var deviceType: String? var deviceToken: String? diff --git a/Tests/ParseSwiftTests/ParseObjectBatchTests.swift b/Tests/ParseSwiftTests/ParseObjectBatchTests.swift index 6af7be17d..2d433439e 100644 --- a/Tests/ParseSwiftTests/ParseObjectBatchTests.swift +++ b/Tests/ParseSwiftTests/ParseObjectBatchTests.swift @@ -13,6 +13,8 @@ import XCTest class ParseObjectBatchTests: XCTestCase { // swiftlint:disable:this type_body_length struct GameScore: ParseObject { + var localUUID: UUID? + // Those are required for Object var objectId: String? var createdAt: Date? diff --git a/Tests/ParseSwiftTests/ParseObjectTests.swift b/Tests/ParseSwiftTests/ParseObjectTests.swift index 78a50700b..1296affef 100644 --- a/Tests/ParseSwiftTests/ParseObjectTests.swift +++ b/Tests/ParseSwiftTests/ParseObjectTests.swift @@ -12,6 +12,8 @@ import XCTest class ParseObjectTests: XCTestCase { // swiftlint:disable:this type_body_length struct Level: ParseObject { + var localUUID: UUID? + var objectId: String? var createdAt: Date? @@ -24,6 +26,8 @@ class ParseObjectTests: XCTestCase { // swiftlint:disable:this type_body_length } struct GameScore: ParseObject { + var localUUID: UUID? + //: Those are required for Object var objectId: String? var createdAt: Date? @@ -52,6 +56,7 @@ class ParseObjectTests: XCTestCase { // swiftlint:disable:this type_body_length struct Game: ParseObject { //: Those are required for Object + var localUUID: UUID? var objectId: String? var createdAt: Date? var updatedAt: Date? @@ -71,6 +76,7 @@ class ParseObjectTests: XCTestCase { // swiftlint:disable:this type_body_length struct Game2: ParseObject { //: Those are required for Object + var localUUID: UUID? var objectId: String? var createdAt: Date? var updatedAt: Date? @@ -84,6 +90,7 @@ class ParseObjectTests: XCTestCase { // swiftlint:disable:this type_body_length class GameScoreClass: ParseObject { //: Those are required for Object + var localUUID: UUID? var objectId: String? var createdAt: Date? var updatedAt: Date? @@ -138,6 +145,7 @@ class ParseObjectTests: XCTestCase { // swiftlint:disable:this type_body_length class GameClass: ParseObject { //: Those are required for Object + var localUUID: UUID? var objectId: String? var createdAt: Date? var updatedAt: Date? diff --git a/Tests/ParseSwiftTests/ParsePointerTests.swift b/Tests/ParseSwiftTests/ParsePointerTests.swift index ba006e666..f2ce5cc95 100644 --- a/Tests/ParseSwiftTests/ParsePointerTests.swift +++ b/Tests/ParseSwiftTests/ParsePointerTests.swift @@ -14,6 +14,8 @@ import XCTest class ParsePointerTests: XCTestCase { struct GameScore: ParseObject { + var localUUID: UUID? + //: Those are required for Object var objectId: String? var createdAt: Date? diff --git a/Tests/ParseSwiftTests/ParseQueryTests.swift b/Tests/ParseSwiftTests/ParseQueryTests.swift index 153dad73b..a1f1757c1 100644 --- a/Tests/ParseSwiftTests/ParseQueryTests.swift +++ b/Tests/ParseSwiftTests/ParseQueryTests.swift @@ -14,6 +14,7 @@ class ParseQueryTests: XCTestCase { // swiftlint:disable:this type_body_length struct GameScore: ParseObject { //: Those are required for Object + var localUUID: UUID? var objectId: String? var createdAt: Date? var updatedAt: Date? @@ -30,6 +31,7 @@ class ParseQueryTests: XCTestCase { // swiftlint:disable:this type_body_length struct GameType: ParseObject { //: Those are required for Object + var localUUID: UUID? var objectId: String? var createdAt: Date? var updatedAt: Date? diff --git a/Tests/ParseSwiftTests/ParseUserTests.swift b/Tests/ParseSwiftTests/ParseUserTests.swift index bbd2d50e5..d8ef92421 100644 --- a/Tests/ParseSwiftTests/ParseUserTests.swift +++ b/Tests/ParseSwiftTests/ParseUserTests.swift @@ -13,6 +13,7 @@ import XCTest class ParseUserTests: XCTestCase { // swiftlint:disable:this type_body_length struct User: ParseUser { + var localUUID: UUID? //: Those are required for Object var objectId: String? var createdAt: Date? @@ -29,6 +30,7 @@ class ParseUserTests: XCTestCase { // swiftlint:disable:this type_body_length } struct LoginSignupResponse: ParseUser { + var localUUID: UUID? var objectId: String? var createdAt: Date? var sessionToken: String From f8566e8ac722b489478ef1e8bfd7b8a5917c086f Mon Sep 17 00:00:00 2001 From: Corey's iMac Date: Fri, 1 Jan 2021 21:13:30 -0500 Subject: [PATCH 30/44] cleaned up code --- .codecov.yml | 2 +- Sources/ParseSwift/API/API+Commands.swift | 13 +++--- Sources/ParseSwift/API/BatchUtils.swift | 3 +- Sources/ParseSwift/API/Responses.swift | 1 - Sources/ParseSwift/Coding/ParseEncoder.swift | 42 +++++++++++-------- .../Internal/BaseParseInstallation.swift | 1 - .../ParseSwift/Internal/BaseParseUser.swift | 1 - .../Objects/ParseInstallation.swift | 6 +-- Sources/ParseSwift/Objects/ParseObject.swift | 41 +++++++++--------- Sources/ParseSwift/Objects/ParseUser.swift | 6 +-- Sources/ParseSwift/Protocols/Fileable.swift | 9 +--- .../Protocols/LocallyIdentifiable.swift | 20 ++------- Sources/ParseSwift/Protocols/Objectable.swift | 28 +++++-------- Sources/ParseSwift/Types/ParseACL.swift | 1 - Sources/ParseSwift/Types/ParseError.swift | 1 - Sources/ParseSwift/Types/ParseFile.swift | 19 ++++++--- Tests/ParseSwiftTests/ACLTests.swift | 3 -- Tests/ParseSwiftTests/ParseEncoderTests.swift | 2 - Tests/ParseSwiftTests/ParseFileTests.swift | 8 ++-- .../ParseInstallationTests.swift | 4 -- .../ParseObjectBatchTests.swift | 2 - Tests/ParseSwiftTests/ParseObjectTests.swift | 20 +++------ Tests/ParseSwiftTests/ParsePointerTests.swift | 2 - Tests/ParseSwiftTests/ParseQueryTests.swift | 2 - Tests/ParseSwiftTests/ParseUserTests.swift | 2 - 25 files changed, 100 insertions(+), 139 deletions(-) diff --git a/.codecov.yml b/.codecov.yml index 021d41f26..85e35079b 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -9,6 +9,6 @@ coverage: changes: false project: default: - target: 76 + target: 74 comment: require_changes: true diff --git a/Sources/ParseSwift/API/API+Commands.swift b/Sources/ParseSwift/API/API+Commands.swift index e5944e7a2..5da5f3e84 100644 --- a/Sources/ParseSwift/API/API+Commands.swift +++ b/Sources/ParseSwift/API/API+Commands.swift @@ -51,7 +51,7 @@ internal extension API { // MARK: Synchronous Execution func executeStream(options: API.Options, - childObjects: [NSDictionary: PointerType]? = nil, + childObjects: [String: PointerType]? = nil, childFiles: [UUID: ParseFile]? = nil, uploadProgress: ((URLSessionTask, Int64, Int64, Int64) -> Void)? = nil, stream: InputStream) throws { @@ -78,7 +78,7 @@ internal extension API { } func execute(options: API.Options, - childObjects: [NSDictionary: PointerType]? = nil, + childObjects: [String: PointerType]? = nil, childFiles: [UUID: ParseFile]? = nil, uploadProgress: ((URLSessionTask, Int64, Int64, Int64) -> Void)? = nil, downloadProgress: ((URLSessionDownloadTask, Int64, Int64, Int64) -> Void)? = nil) throws -> U { @@ -106,7 +106,7 @@ internal extension API { // MARK: Asynchronous Execution // swiftlint:disable:next function_body_length cyclomatic_complexity func executeAsync(options: API.Options, callbackQueue: DispatchQueue?, - childObjects: [NSDictionary: PointerType]? = nil, + childObjects: [String: PointerType]? = nil, childFiles: [UUID: ParseFile]? = nil, uploadProgress: ((URLSessionTask, Int64, Int64, Int64) -> Void)? = nil, downloadProgress: ((URLSessionDownloadTask, Int64, Int64, Int64) -> Void)? = nil, @@ -250,7 +250,7 @@ internal extension API { // MARK: URL Preperation func prepareURLRequest(options: API.Options, - childObjects: [NSDictionary: PointerType]? = nil, + childObjects: [String: PointerType]? = nil, childFiles: [UUID: ParseFile]? = nil) -> Result { let params = self.params?.getQueryItems() let headers = API.getHeaders(options: options) @@ -347,7 +347,6 @@ internal extension API.Command { try FileManager.default.moveItem(at: tempFileLocation, to: fileLocation) var object = object object.localURL = fileLocation - _ = object.localUUID //Ensure downloaded file has a localUUID return object } } @@ -540,9 +539,9 @@ extension API.Command where T: ParseObject { //This has been disabled, looking into getting it working in the future. //It's only needed for sending batches of childObjects which currently isn't being used. - +/* // MARK: Batch - Child Objects -/*extension API.Command where T: ParseType { +extension API.Command where T: ParseType { internal var data: Data? { guard let body = body else { return nil } diff --git a/Sources/ParseSwift/API/BatchUtils.swift b/Sources/ParseSwift/API/BatchUtils.swift index e0ee120fa..7fe427433 100644 --- a/Sources/ParseSwift/API/BatchUtils.swift +++ b/Sources/ParseSwift/API/BatchUtils.swift @@ -22,8 +22,7 @@ typealias ParseObjectBatchResponseEncodable = [(Result = API.Command, ParseObjectBatchResponseEncodable> where T: ParseType // swiftlint:enable line_length - */ - +*/ internal struct BatchCommand: ParseType where T: ParseType { let requests: [API.Command] } diff --git a/Sources/ParseSwift/API/Responses.swift b/Sources/ParseSwift/API/Responses.swift index 6786f4258..c31dd2309 100644 --- a/Sources/ParseSwift/API/Responses.swift +++ b/Sources/ParseSwift/API/Responses.swift @@ -130,7 +130,6 @@ internal struct FileUploadResponse: Decodable { var file = file file.name = name file.url = url - _ = file.localUUID //Ensure file has a localUUID return file } } diff --git a/Sources/ParseSwift/Coding/ParseEncoder.swift b/Sources/ParseSwift/Coding/ParseEncoder.swift index eb8985f60..fe43b6473 100644 --- a/Sources/ParseSwift/Coding/ParseEncoder.swift +++ b/Sources/ParseSwift/Coding/ParseEncoder.swift @@ -62,9 +62,9 @@ public struct ParseEncoder { switch self { case .object: - return Set(["createdAt", "updatedAt", "objectId", "className", "localUUID"]) + return Set(["createdAt", "updatedAt", "objectId", "className"]) case .cloud: - return Set(["functionJobName", "localUUID"]) + return Set(["functionJobName"]) case .none: return .init() case .custom(let keys): @@ -79,6 +79,14 @@ public struct ParseEncoder { self.dateEncodingStrategy = dateEncodingStrategy } + func encode(_ value: Encodable) throws -> Data { + let encoder = _ParseEncoder(codingPath: [], dictionary: NSMutableDictionary(), skippingKeys: SkippedKeys.none.keys()) + if let dateEncodingStrategy = dateEncodingStrategy { + encoder.dateEncodingStrategy = .custom(dateEncodingStrategy) + } + return try encoder.encodeObject(value, collectChildren: false, objectsSavedBeforeThisOne: nil, filesSavedBeforeThisOne: nil).encoded + } + public func encode(_ value: T, skipKeys: SkippedKeys) throws -> Data { let encoder = _ParseEncoder(codingPath: [], dictionary: NSMutableDictionary(), skippingKeys: skipKeys.keys()) if let dateEncodingStrategy = dateEncodingStrategy { @@ -89,8 +97,8 @@ public struct ParseEncoder { // swiftlint:disable large_tuple internal func encode(_ value: T, - objectsSavedBeforeThisOne: [NSDictionary: PointerType]?, - filesSavedBeforeThisOne: [UUID: ParseFile]?) throws -> (encoded: Data, unique: Set, unsavedChildren: [ParseType]) { + objectsSavedBeforeThisOne: [String: PointerType]?, + filesSavedBeforeThisOne: [UUID: ParseFile]?) throws -> (encoded: Data, unique: Set, unsavedChildren: [Encodable]) { let encoder = _ParseEncoder(codingPath: [], dictionary: NSMutableDictionary(), skippingKeys: SkippedKeys.object.keys()) if let dateEncodingStrategy = dateEncodingStrategy { encoder.dateEncodingStrategy = .custom(dateEncodingStrategy) @@ -100,8 +108,8 @@ public struct ParseEncoder { // swiftlint:disable large_tuple internal func encode(_ value: ParseType, collectChildren: Bool, - objectsSavedBeforeThisOne: [NSDictionary: PointerType]?, - filesSavedBeforeThisOne: [UUID: ParseFile]?) throws -> (encoded: Data, unique: Set, unsavedChildren: [ParseType]) { + objectsSavedBeforeThisOne: [String: PointerType]?, + filesSavedBeforeThisOne: [UUID: ParseFile]?) throws -> (encoded: Data, unique: Set, unsavedChildren: [Encodable]) { let encoder = _ParseEncoder(codingPath: [], dictionary: NSMutableDictionary(), skippingKeys: SkippedKeys.object.keys()) if let dateEncodingStrategy = dateEncodingStrategy { encoder.dateEncodingStrategy = .custom(dateEncodingStrategy) @@ -117,9 +125,9 @@ private class _ParseEncoder: JSONEncoder, Encoder { let skippedKeys: Set var uniqueObjects = Set() var uniqueFiles = Set() - var newObjects = [ParseType]() + var newObjects = [Encodable]() var collectChildren = false - var objectsSavedBeforeThisOne: [NSDictionary: PointerType]? + var objectsSavedBeforeThisOne: [String: PointerType]? var filesSavedBeforeThisOne: [UUID: ParseFile]? /// The encoder's storage. var storage: _ParseEncodingStorage @@ -169,9 +177,9 @@ private class _ParseEncoder: JSONEncoder, Encoder { throw ParseError(code: .unknownError, message: "This method shouldn't be used. Either use the JSONEncoder or if you are encoding a ParseObject use \"encodeObject\"") } - func encodeObject(_ value: ParseType, collectChildren: Bool, - objectsSavedBeforeThisOne: [NSDictionary: PointerType]?, - filesSavedBeforeThisOne: [UUID: ParseFile]?) throws -> (encoded: Data, unique: Set, unsavedChildren: [ParseType]) { + func encodeObject(_ value: Encodable, collectChildren: Bool, + objectsSavedBeforeThisOne: [String: PointerType]?, + filesSavedBeforeThisOne: [UUID: ParseFile]?) throws -> (encoded: Data, unique: Set, unsavedChildren: [Encodable]) { let encoder = _ParseEncoder(codingPath: codingPath, dictionary: dictionary, skippingKeys: skippedKeys) encoder.collectChildren = collectChildren @@ -258,7 +266,7 @@ private class _ParseEncoder: JSONEncoder, Encoder { valueToEncode = value.toPointer() } } else { - let hashOfCurrentObject = BaseObjectable.createHash(value) + let hashOfCurrentObject = try BaseObjectable.createHash(value) if self.collectChildren { if let pointerForCurrentObject = self.objectsSavedBeforeThisOne?[hashOfCurrentObject] { valueToEncode = pointerForCurrentObject @@ -287,16 +295,14 @@ private class _ParseEncoder: JSONEncoder, Encoder { valueToEncode = value } } else { - var mutableValue = value - let uuid = mutableValue.establishedLocalUUID if self.collectChildren { - if let updatedFile = self.filesSavedBeforeThisOne?[uuid] { + if let updatedFile = self.filesSavedBeforeThisOne?[value.localUUID] { valueToEncode = updatedFile } else { //New object needs to be saved before it can be stored self.newObjects.append(value) } - } else if let currentFile = self.filesSavedBeforeThisOne?[uuid] { + } else if let currentFile = self.filesSavedBeforeThisOne?[value.localUUID] { valueToEncode = currentFile } else if dictionary.count > 0 { //Only top level objects can be saved without a pointer @@ -897,7 +903,7 @@ private class _ParseReferencingEncoder: _ParseEncoder { // MARK: - Initialization /// Initializes `self` by referencing the given array container in the given encoder. - init(referencing encoder: _ParseEncoder, at index: Int, wrapping array: NSMutableArray, skippingKeys: Set, collectChildren: Bool, objectsSavedBeforeThisOne: [NSDictionary: PointerType]?, filesSavedBeforeThisOne: [UUID: ParseFile]?) { + init(referencing encoder: _ParseEncoder, at index: Int, wrapping array: NSMutableArray, skippingKeys: Set, collectChildren: Bool, objectsSavedBeforeThisOne: [String: PointerType]?, filesSavedBeforeThisOne: [UUID: ParseFile]?) { self.encoder = encoder self.reference = .array(array, index) super.init(codingPath: encoder.codingPath, dictionary: NSMutableDictionary(), skippingKeys: skippingKeys) @@ -908,7 +914,7 @@ private class _ParseReferencingEncoder: _ParseEncoder { } /// Initializes `self` by referencing the given dictionary container in the given encoder. - init(referencing encoder: _ParseEncoder, key: CodingKey, wrapping dictionary: NSMutableDictionary, skippingKeys: Set, collectChildren: Bool, objectsSavedBeforeThisOne: [NSDictionary: PointerType]?, filesSavedBeforeThisOne: [UUID: ParseFile]?) { + init(referencing encoder: _ParseEncoder, key: CodingKey, wrapping dictionary: NSMutableDictionary, skippingKeys: Set, collectChildren: Bool, objectsSavedBeforeThisOne: [String: PointerType]?, filesSavedBeforeThisOne: [UUID: ParseFile]?) { self.encoder = encoder self.reference = .dictionary(dictionary, key.stringValue) super.init(codingPath: encoder.codingPath, dictionary: dictionary, skippingKeys: skippingKeys) diff --git a/Sources/ParseSwift/Internal/BaseParseInstallation.swift b/Sources/ParseSwift/Internal/BaseParseInstallation.swift index 98737d3a6..994802555 100644 --- a/Sources/ParseSwift/Internal/BaseParseInstallation.swift +++ b/Sources/ParseSwift/Internal/BaseParseInstallation.swift @@ -9,7 +9,6 @@ import Foundation internal struct BaseParseInstallation: ParseInstallation { - var localUUID: UUID? var deviceType: String? var installationId: String? var deviceToken: String? diff --git a/Sources/ParseSwift/Internal/BaseParseUser.swift b/Sources/ParseSwift/Internal/BaseParseUser.swift index 80dda6100..8fad515b8 100644 --- a/Sources/ParseSwift/Internal/BaseParseUser.swift +++ b/Sources/ParseSwift/Internal/BaseParseUser.swift @@ -9,7 +9,6 @@ import Foundation /// Used internally to form a concrete type representing `ParseUser`. internal struct BaseParseUser: ParseUser { - var localUUID: UUID? var username: String? var email: String? var password: String? diff --git a/Sources/ParseSwift/Objects/ParseInstallation.swift b/Sources/ParseSwift/Objects/ParseInstallation.swift index b4e214aa8..516c627ad 100644 --- a/Sources/ParseSwift/Objects/ParseInstallation.swift +++ b/Sources/ParseSwift/Objects/ParseInstallation.swift @@ -388,7 +388,7 @@ extension ParseInstallation { - important: If an object saved has the same objectId as current, it will automatically update the current. */ public func save(options: API.Options = []) throws -> Self { - var childObjects: [NSDictionary: PointerType]? + var childObjects: [String: PointerType]? var childFiles: [UUID: ParseFile]? var error: ParseError? let group = DispatchGroup() @@ -548,7 +548,7 @@ public extension Sequence where Element: ParseInstallation { - important: If an object saved has the same objectId as current, it will automatically update the current. */ func saveAll(options: API.Options = []) throws -> [(Result)] { - var childObjects = [NSDictionary: PointerType]() + var childObjects = [String: PointerType]() var childFiles = [UUID: ParseFile]() var error: ParseError? @@ -615,7 +615,7 @@ public extension Sequence where Element: ParseInstallation { callbackQueue: DispatchQueue = .main, completion: @escaping (Result<[(Result)], ParseError>) -> Void ) { - var childObjects = [NSDictionary: PointerType]() + var childObjects = [String: PointerType]() var childFiles = [UUID: ParseFile]() var error: ParseError? diff --git a/Sources/ParseSwift/Objects/ParseObject.swift b/Sources/ParseSwift/Objects/ParseObject.swift index 9cd5990e2..a6f3c1984 100644 --- a/Sources/ParseSwift/Objects/ParseObject.swift +++ b/Sources/ParseSwift/Objects/ParseObject.swift @@ -24,11 +24,11 @@ import Foundation and relying on that for `Equatable` and `Hashable`, otherwise it's possible you will get "circular dependency errors" depending on your implementation. */ -public protocol ParseObject: LocallyIdentifiable, - Objectable, +public protocol ParseObject: Objectable, Fetchable, Savable, Deletable, + Equatable, CustomDebugStringConvertible {} // MARK: Default Implementations @@ -66,7 +66,7 @@ public extension Sequence where Element: ParseObject { - throws: `ParseError` */ func saveAll(options: API.Options = []) throws -> [(Result)] { - var childObjects = [NSDictionary: PointerType]() + var childObjects = [String: PointerType]() var childFiles = [UUID: ParseFile]() var error: ParseError? @@ -130,7 +130,7 @@ public extension Sequence where Element: ParseObject { callbackQueue: DispatchQueue = .main, completion: @escaping (Result<[(Result)], ParseError>) -> Void ) { - var childObjects = [NSDictionary: PointerType]() + var childObjects = [String: PointerType]() var childFiles = [UUID: ParseFile]() var error: ParseError? @@ -337,7 +337,7 @@ public extension Sequence where Element: ParseObject { */ func saveAll(options: API.Options = []) throws -> [(Result)] { let commands = try map { try $0.saveCommand() } - return try API.Command + return try API.Command .batch(commands: commands) .execute(options: options) } @@ -431,7 +431,7 @@ extension ParseObject { - returns: Returns saved `ParseObject`. */ public func save(options: API.Options = []) throws -> Self { - var childObjects: [NSDictionary: PointerType]? + var childObjects: [String: PointerType]? var childFiles: [UUID: ParseFile]? var error: ParseError? let group = DispatchGroup() @@ -483,14 +483,14 @@ extension ParseObject { // swiftlint:disable:next function_body_length internal func ensureDeepSave(options: API.Options = [], - completion: @escaping ([NSDictionary: PointerType], + completion: @escaping ([String: PointerType], [UUID: ParseFile], ParseError?) -> Void) { let queue = DispatchQueue(label: "com.parse.deepSave", qos: .default, attributes: .concurrent, autoreleaseFrequency: .inherit, target: nil) queue.sync { - var objectsFinishedSaving = [NSDictionary: PointerType]() + var objectsFinishedSaving = [String: PointerType]() var filesFinishedSaving = [UUID: ParseFile]() do { @@ -502,7 +502,7 @@ extension ParseObject { var waitingToBeSaved = object.unsavedChildren while waitingToBeSaved.count > 0 { - var savableObjects = [Objectable]() + var savableObjects = [Encodable]() var savableFiles = [ParseFile]() var nextBatch = [ParseType]() try waitingToBeSaved.forEach { parseType in @@ -524,7 +524,7 @@ extension ParseObject { savableObjects.append(parseObject) } else { //Else this ParseObject needs to wait until it's children are saved - nextBatch.append(parseType) + nextBatch.append(parseObject) } } } @@ -539,15 +539,19 @@ extension ParseObject { } //Currently, batch isn't working for Encodable - //Self.saveAll(encodableObjects: savableObjects) + /*if let parseTypes = savableObjects as? [ParseType] { + let savedChildObjects = try self.saveAll(options: options, objects: parseTypes) + }*/ try savableObjects.forEach { - let hash = BaseObjectable.createHash($0) - objectsFinishedSaving[hash] = try $0.save(options: options) + let hash = try BaseObjectable.createHash($0) + if let parseType = $0 as? ParseType { + objectsFinishedSaving[hash] = try parseType.save(options: options) + } } try savableFiles.forEach { - var file = $0 - filesFinishedSaving[file.establishedLocalUUID] = try $0.save(options: options) + let file = $0 + filesFinishedSaving[file.localUUID] = try $0.save(options: options) } } completion(objectsFinishedSaving, filesFinishedSaving, nil) @@ -574,10 +578,9 @@ internal extension ParseType { try API.Command.saveCommand(self) } /* - static func saveAll(options: API.Options = [], - encodableObjects: [T]) throws -> [(Result)] { - let commands = try encodableObjects.map { try $0.saveCommand() } - return try API.Command + func saveAll(options: API.Options = [], objects: [T]) throws -> [(Result)] { + let commands = try objects.map { try API.Command.saveCommand($0) } + return try API.Command .batch(commands: commands) .execute(options: options) }*/ diff --git a/Sources/ParseSwift/Objects/ParseUser.swift b/Sources/ParseSwift/Objects/ParseUser.swift index 637e8a399..555417abc 100644 --- a/Sources/ParseSwift/Objects/ParseUser.swift +++ b/Sources/ParseSwift/Objects/ParseUser.swift @@ -531,7 +531,7 @@ extension ParseUser { - important: If an object saved has the same objectId as current, it will automatically update the current. */ public func save(options: API.Options = []) throws -> Self { - var childObjects: [NSDictionary: PointerType]? + var childObjects: [String: PointerType]? var childFiles: [UUID: ParseFile]? var error: ParseError? let group = DispatchGroup() @@ -690,7 +690,7 @@ public extension Sequence where Element: ParseUser { - important: If an object saved has the same objectId as current, it will automatically update the current. */ func saveAll(options: API.Options = []) throws -> [(Result)] { - var childObjects = [NSDictionary: PointerType]() + var childObjects = [String: PointerType]() var childFiles = [UUID: ParseFile]() var error: ParseError? let users = map { $0 } @@ -756,7 +756,7 @@ public extension Sequence where Element: ParseUser { callbackQueue: DispatchQueue = .main, completion: @escaping (Result<[(Result)], ParseError>) -> Void ) { - var childObjects = [NSDictionary: PointerType]() + var childObjects = [String: PointerType]() var childFiles = [UUID: ParseFile]() var error: ParseError? diff --git a/Sources/ParseSwift/Protocols/Fileable.swift b/Sources/ParseSwift/Protocols/Fileable.swift index 52edb4eb5..16ee6dac7 100644 --- a/Sources/ParseSwift/Protocols/Fileable.swift +++ b/Sources/ParseSwift/Protocols/Fileable.swift @@ -23,19 +23,14 @@ extension Fileable { if let url = url { hasher.combine(url) } else { - hasher.combine(self.establishedLocalUUID) + hasher.combine(self.localUUID) } } public static func == (lhs: Self, rhs: Self) -> Bool { guard let lURL = lhs.url, let rURL = rhs.url else { - guard let lhsUUID = lhs.localUUID, - let rhsUUID = rhs.localUUID else { - //Can only compare objects that have a localUUID - return false - } - return lhsUUID == rhsUUID + return lhs.localUUID == rhs.localUUID } return lURL == rURL } diff --git a/Sources/ParseSwift/Protocols/LocallyIdentifiable.swift b/Sources/ParseSwift/Protocols/LocallyIdentifiable.swift index b843a8b10..0950459bb 100644 --- a/Sources/ParseSwift/Protocols/LocallyIdentifiable.swift +++ b/Sources/ParseSwift/Protocols/LocallyIdentifiable.swift @@ -9,30 +9,16 @@ import Foundation public protocol LocallyIdentifiable: Encodable, Hashable { - var localUUID: UUID? { get set } + var localUUID: UUID { get set } } extension LocallyIdentifiable { - public var establishedLocalUUID: UUID { - mutating get { - if self.localUUID == nil { - self.localUUID = UUID() - } - return self.localUUID! - } - } - mutating func hash(into hasher: inout Hasher) { - hasher.combine(self.establishedLocalUUID) + hasher.combine(self.localUUID) } static func == (lhs: Self, rhs: Self) -> Bool { - guard let lhsUUID = lhs.localUUID, - let rhsUUID = rhs.localUUID else { - //Can only compare objects that have a localUUID - return false - } - return lhsUUID == rhsUUID + return lhs.localUUID == rhs.localUUID } } diff --git a/Sources/ParseSwift/Protocols/Objectable.swift b/Sources/ParseSwift/Protocols/Objectable.swift index da6f80959..1c84ee0b2 100644 --- a/Sources/ParseSwift/Protocols/Objectable.swift +++ b/Sources/ParseSwift/Protocols/Objectable.swift @@ -33,8 +33,6 @@ public protocol Objectable: ParseType, Decodable { The ACL for this object. */ var ACL: ParseACL? { get set } - - var localUUID: UUID? { get set } } extension Objectable { @@ -53,19 +51,9 @@ extension Objectable { return Self.className } - static func createHash(_ object: Encodable) -> NSDictionary { - let hash: NSDictionary = [ParseConstants.hashingKey: object] - return hash - } - - internal func getUniqueObject() throws -> UniqueObject { - let encoded = try ParseCoding.jsonEncoder().encode(self) - return try ParseCoding.jsonDecoder().decode(UniqueObject.self, from: encoded) - } - - internal func getLocalUniqueObject() throws -> LocalUniqueObject { - let encoded = try ParseCoding.jsonEncoder().encode(self) - return try ParseCoding.jsonDecoder().decode(LocalUniqueObject.self, from: encoded) + static func createHash(_ object: Encodable) throws -> String { + let encoded = try ParseCoding.parseEncoder().encode(object) + return ParseHash.md5HashFromData(encoded) } } @@ -91,6 +79,14 @@ extension Objectable { internal struct UniqueObject: Encodable, Decodable, Hashable { let objectId: String + init?(target: Encodable) { + guard let objectable = target as? Objectable, + let objectId = objectable.objectId else { + return nil + } + self.objectId = objectId + } + init?(target: Objectable) { if let objectId = target.objectId { self.objectId = objectId @@ -101,8 +97,6 @@ internal struct UniqueObject: Encodable, Decodable, Hashable { } internal struct BaseObjectable: Objectable { - var localUUID: UUID? - var objectId: String? var createdAt: Date? diff --git a/Sources/ParseSwift/Types/ParseACL.swift b/Sources/ParseSwift/Types/ParseACL.swift index 25002a104..6232754e8 100644 --- a/Sources/ParseSwift/Types/ParseACL.swift +++ b/Sources/ParseSwift/Types/ParseACL.swift @@ -16,7 +16,6 @@ import Foundation particular set of users could write to that object. */ public struct ParseACL: ParseType, Decodable, Equatable, Hashable { - public var localUUID: UUID? private static let publicScope = "*" private var acl: [String: [Access: Bool]]? diff --git a/Sources/ParseSwift/Types/ParseError.swift b/Sources/ParseSwift/Types/ParseError.swift index dabe7a51a..c1b45aee9 100644 --- a/Sources/ParseSwift/Types/ParseError.swift +++ b/Sources/ParseSwift/Types/ParseError.swift @@ -9,7 +9,6 @@ import Foundation public struct ParseError: ParseType, Decodable, Swift.Error { - public var localUUID: UUID? public let code: Code public let message: String diff --git a/Sources/ParseSwift/Types/ParseFile.swift b/Sources/ParseSwift/Types/ParseFile.swift index 60fa887f4..46a06668c 100644 --- a/Sources/ParseSwift/Types/ParseFile.swift +++ b/Sources/ParseSwift/Types/ParseFile.swift @@ -15,7 +15,7 @@ public struct ParseFile: Fileable, Savable, Fetchable, Deletable { && data == nil } - public var localUUID: UUID? + public var localUUID: UUID /** The name of the file. @@ -71,7 +71,7 @@ public struct ParseFile: Fileable, Savable, Fetchable, Deletable { For more, see details on the [S3 adapter](https://github.com/parse-community/parse-server-s3-adapter#adding-metadata-and-tags) */ - public init(name: String = "file", data: Data? = nil, mimeType: String? = nil, + public init(name: String = "file", data: Data, mimeType: String? = nil, metadata: [String: String]? = nil, tags: [String: String]? = nil, options: API.Options = []) { self.name = name @@ -80,7 +80,7 @@ public struct ParseFile: Fileable, Savable, Fetchable, Deletable { self.metadata = metadata self.tags = tags self.options = options - _ = self.establishedLocalUUID //Need to ensure this creates a uuid + self.localUUID = UUID() } /** @@ -106,7 +106,7 @@ public struct ParseFile: Fileable, Savable, Fetchable, Deletable { self.metadata = metadata self.tags = tags self.options = options - _ = self.establishedLocalUUID //Need to ensure this creates a uuid + self.localUUID = UUID() } /** @@ -132,7 +132,7 @@ public struct ParseFile: Fileable, Savable, Fetchable, Deletable { self.metadata = metadata self.tags = tags self.options = options - _ = self.establishedLocalUUID //Need to ensure this creates a uuid + self.localUUID = UUID() } enum CodingKeys: String, CodingKey { @@ -142,6 +142,15 @@ public struct ParseFile: Fileable, Savable, Fetchable, Deletable { } } +extension ParseFile { + public init(from decoder: Decoder) throws { + let values = try decoder.container(keyedBy: CodingKeys.self) + url = try values.decode(URL.self, forKey: .url) + name = try values.decode(String.self, forKey: .name) + localUUID = UUID() + } +} + // MARK: Deleting extension ParseFile { /** diff --git a/Tests/ParseSwiftTests/ACLTests.swift b/Tests/ParseSwiftTests/ACLTests.swift index efe08df8a..19186e8ab 100644 --- a/Tests/ParseSwiftTests/ACLTests.swift +++ b/Tests/ParseSwiftTests/ACLTests.swift @@ -31,8 +31,6 @@ class ACLTests: XCTestCase { } struct User: ParseUser { - var localUUID: UUID? - //: Those are required for Object var objectId: String? var createdAt: Date? @@ -49,7 +47,6 @@ class ACLTests: XCTestCase { } struct LoginSignupResponse: ParseUser { - var localUUID: UUID? var objectId: String? var createdAt: Date? var sessionToken: String diff --git a/Tests/ParseSwiftTests/ParseEncoderTests.swift b/Tests/ParseSwiftTests/ParseEncoderTests.swift index dee083113..28d45b636 100644 --- a/Tests/ParseSwiftTests/ParseEncoderTests.swift +++ b/Tests/ParseSwiftTests/ParseEncoderTests.swift @@ -11,8 +11,6 @@ import XCTest class ParseEncoderTests: XCTestCase { struct GameScore: ParseObject { - var localUUID: UUID? - //: Those are required for Object var objectId: String? var createdAt: Date? diff --git a/Tests/ParseSwiftTests/ParseFileTests.swift b/Tests/ParseSwiftTests/ParseFileTests.swift index 4a2f4eeca..2695f0836 100644 --- a/Tests/ParseSwiftTests/ParseFileTests.swift +++ b/Tests/ParseSwiftTests/ParseFileTests.swift @@ -378,7 +378,7 @@ class ParseFileTests: XCTestCase { // swiftlint:disable:this type_body_length } try sampleData.write(to: tempFilePath) - let parseFile = ParseFile(name: "sampleData.data") + let parseFile = ParseFile(name: "sampleData.data", localURL: tempFilePath) // swiftlint:disable:next line_length guard let url = URL(string: "http://localhost:1337/1/files/applicationId/89d74fcfa4faa5561799e5076593f67c_sampleData.txt") else { @@ -410,7 +410,7 @@ class ParseFileTests: XCTestCase { // swiftlint:disable:this type_body_length } try sampleData.write(to: tempFilePath) - let parseFile = ParseFile(name: "sampleData.data") + let parseFile = ParseFile(name: "sampleData.data", localURL: tempFilePath) // swiftlint:disable:next line_length guard let url = URL(string: "http://localhost:1337/1/files/applicationId/89d74fcfa4faa5561799e5076593f67c_sampleData.txt") else { @@ -446,7 +446,7 @@ class ParseFileTests: XCTestCase { // swiftlint:disable:this type_body_length } try sampleData.write(to: tempFilePath) - let parseFile = ParseFile(name: "sampleData.data") + let parseFile = ParseFile(name: "sampleData.data", localURL: tempFilePath) // swiftlint:disable:next line_length guard let url = URL(string: "http://localhost:1337/1/files/applicationId/89d74fcfa4faa5561799e5076593f67c_sampleData.txt") else { @@ -543,7 +543,7 @@ class ParseFileTests: XCTestCase { // swiftlint:disable:this type_body_length } try sampleData.write(to: tempFilePath) - let parseFile = ParseFile(name: "sampleData.data") + let parseFile = ParseFile(name: "sampleData.data", localURL: tempFilePath) // swiftlint:disable:next line_length guard let url = URL(string: "http://localhost:1337/1/files/applicationId/89d74fcfa4faa5561799e5076593f67c_sampleData.txt") else { diff --git a/Tests/ParseSwiftTests/ParseInstallationTests.swift b/Tests/ParseSwiftTests/ParseInstallationTests.swift index d17d1b235..6bfafa4f8 100644 --- a/Tests/ParseSwiftTests/ParseInstallationTests.swift +++ b/Tests/ParseSwiftTests/ParseInstallationTests.swift @@ -18,8 +18,6 @@ import XCTest class ParseInstallationTests: XCTestCase { // swiftlint:disable:this type_body_length struct User: ParseUser { - var localUUID: UUID? - //: Those are required for Object var objectId: String? var createdAt: Date? @@ -36,7 +34,6 @@ class ParseInstallationTests: XCTestCase { // swiftlint:disable:this type_body_l } struct LoginSignupResponse: ParseUser { - var localUUID: UUID? var objectId: String? var createdAt: Date? var sessionToken: String @@ -64,7 +61,6 @@ class ParseInstallationTests: XCTestCase { // swiftlint:disable:this type_body_l } struct Installation: ParseInstallation { - var localUUID: UUID? var installationId: String? var deviceType: String? var deviceToken: String? diff --git a/Tests/ParseSwiftTests/ParseObjectBatchTests.swift b/Tests/ParseSwiftTests/ParseObjectBatchTests.swift index 2d433439e..6af7be17d 100644 --- a/Tests/ParseSwiftTests/ParseObjectBatchTests.swift +++ b/Tests/ParseSwiftTests/ParseObjectBatchTests.swift @@ -13,8 +13,6 @@ import XCTest class ParseObjectBatchTests: XCTestCase { // swiftlint:disable:this type_body_length struct GameScore: ParseObject { - var localUUID: UUID? - // Those are required for Object var objectId: String? var createdAt: Date? diff --git a/Tests/ParseSwiftTests/ParseObjectTests.swift b/Tests/ParseSwiftTests/ParseObjectTests.swift index 1296affef..849c53ced 100644 --- a/Tests/ParseSwiftTests/ParseObjectTests.swift +++ b/Tests/ParseSwiftTests/ParseObjectTests.swift @@ -12,8 +12,6 @@ import XCTest class ParseObjectTests: XCTestCase { // swiftlint:disable:this type_body_length struct Level: ParseObject { - var localUUID: UUID? - var objectId: String? var createdAt: Date? @@ -26,8 +24,6 @@ class ParseObjectTests: XCTestCase { // swiftlint:disable:this type_body_length } struct GameScore: ParseObject { - var localUUID: UUID? - //: Those are required for Object var objectId: String? var createdAt: Date? @@ -56,7 +52,6 @@ class ParseObjectTests: XCTestCase { // swiftlint:disable:this type_body_length struct Game: ParseObject { //: Those are required for Object - var localUUID: UUID? var objectId: String? var createdAt: Date? var updatedAt: Date? @@ -76,7 +71,6 @@ class ParseObjectTests: XCTestCase { // swiftlint:disable:this type_body_length struct Game2: ParseObject { //: Those are required for Object - var localUUID: UUID? var objectId: String? var createdAt: Date? var updatedAt: Date? @@ -90,7 +84,6 @@ class ParseObjectTests: XCTestCase { // swiftlint:disable:this type_body_length class GameScoreClass: ParseObject { //: Those are required for Object - var localUUID: UUID? var objectId: String? var createdAt: Date? var updatedAt: Date? @@ -145,7 +138,6 @@ class ParseObjectTests: XCTestCase { // swiftlint:disable:this type_body_length class GameClass: ParseObject { //: Those are required for Object - var localUUID: UUID? var objectId: String? var createdAt: Date? var updatedAt: Date? @@ -1167,12 +1159,12 @@ class ParseObjectTests: XCTestCase { // swiftlint:disable:this type_body_length var game = Game(score: score) game.objectId = "nice" - var scoreOnServer = score - scoreOnServer.createdAt = Date() - scoreOnServer.updatedAt = Date() - scoreOnServer.ACL = nil - scoreOnServer.objectId = "yarr" - let pointer = scoreOnServer.toPointer() + var levelOnServer = score + levelOnServer.createdAt = Date() + levelOnServer.updatedAt = Date() + levelOnServer.ACL = nil + levelOnServer.objectId = "yarr" + let pointer = levelOnServer.toPointer() let encoded: Data! do { encoded = try ParseCoding.jsonEncoder().encode(pointer) diff --git a/Tests/ParseSwiftTests/ParsePointerTests.swift b/Tests/ParseSwiftTests/ParsePointerTests.swift index f2ce5cc95..ba006e666 100644 --- a/Tests/ParseSwiftTests/ParsePointerTests.swift +++ b/Tests/ParseSwiftTests/ParsePointerTests.swift @@ -14,8 +14,6 @@ import XCTest class ParsePointerTests: XCTestCase { struct GameScore: ParseObject { - var localUUID: UUID? - //: Those are required for Object var objectId: String? var createdAt: Date? diff --git a/Tests/ParseSwiftTests/ParseQueryTests.swift b/Tests/ParseSwiftTests/ParseQueryTests.swift index a1f1757c1..153dad73b 100644 --- a/Tests/ParseSwiftTests/ParseQueryTests.swift +++ b/Tests/ParseSwiftTests/ParseQueryTests.swift @@ -14,7 +14,6 @@ class ParseQueryTests: XCTestCase { // swiftlint:disable:this type_body_length struct GameScore: ParseObject { //: Those are required for Object - var localUUID: UUID? var objectId: String? var createdAt: Date? var updatedAt: Date? @@ -31,7 +30,6 @@ class ParseQueryTests: XCTestCase { // swiftlint:disable:this type_body_length struct GameType: ParseObject { //: Those are required for Object - var localUUID: UUID? var objectId: String? var createdAt: Date? var updatedAt: Date? diff --git a/Tests/ParseSwiftTests/ParseUserTests.swift b/Tests/ParseSwiftTests/ParseUserTests.swift index d8ef92421..bbd2d50e5 100644 --- a/Tests/ParseSwiftTests/ParseUserTests.swift +++ b/Tests/ParseSwiftTests/ParseUserTests.swift @@ -13,7 +13,6 @@ import XCTest class ParseUserTests: XCTestCase { // swiftlint:disable:this type_body_length struct User: ParseUser { - var localUUID: UUID? //: Those are required for Object var objectId: String? var createdAt: Date? @@ -30,7 +29,6 @@ class ParseUserTests: XCTestCase { // swiftlint:disable:this type_body_length } struct LoginSignupResponse: ParseUser { - var localUUID: UUID? var objectId: String? var createdAt: Date? var sessionToken: String From 56906d3b3d0a488a1bae7d3e1b35fb5baeb8aec4 Mon Sep 17 00:00:00 2001 From: Corey's iMac Date: Sat, 2 Jan 2021 10:09:25 -0500 Subject: [PATCH 31/44] Switch to localId --- .../9 - Files.xcplaygroundpage/Contents.swift | 4 ++-- Sources/ParseSwift/Coding/ParseEncoder.swift | 4 ++-- Sources/ParseSwift/Objects/ParseObject.swift | 2 +- Sources/ParseSwift/Protocols/Fileable.swift | 4 ++-- .../Protocols/LocallyIdentifiable.swift | 6 +++--- Sources/ParseSwift/Types/ParseFile.swift | 10 +++++----- Tests/ParseSwiftTests/ParseFileTests.swift | 20 +++++++++---------- 7 files changed, 25 insertions(+), 25 deletions(-) diff --git a/ParseSwift.playground/Pages/9 - Files.xcplaygroundpage/Contents.swift b/ParseSwift.playground/Pages/9 - Files.xcplaygroundpage/Contents.swift index 8a4cde3a3..9570d10a6 100644 --- a/ParseSwift.playground/Pages/9 - Files.xcplaygroundpage/Contents.swift +++ b/ParseSwift.playground/Pages/9 - Files.xcplaygroundpage/Contents.swift @@ -90,7 +90,7 @@ score.save { result in } } -/*: Files can also be saved from data. Below is how to do it synchrously, but async is similar to above +/*: Files can also be saved from data. Below is how to do it synchronously, but async is similar to above Create a new ParseFile for your data */ let sampleData = "Hello World".data(using: .utf8)! @@ -116,7 +116,7 @@ do { print("The file is saved to your Parse Server at: \(url)") print("The full details of your data file are: \(myData)") - //: If you need to download your profilePicture + //: If you need to download your file let fetchedFile = try myData.fetch() if fetchedFile.localURL != nil { print("The file is now saved at: \(fetchedFile.localURL!)") diff --git a/Sources/ParseSwift/Coding/ParseEncoder.swift b/Sources/ParseSwift/Coding/ParseEncoder.swift index fe43b6473..04733a5a5 100644 --- a/Sources/ParseSwift/Coding/ParseEncoder.swift +++ b/Sources/ParseSwift/Coding/ParseEncoder.swift @@ -296,13 +296,13 @@ private class _ParseEncoder: JSONEncoder, Encoder { } } else { if self.collectChildren { - if let updatedFile = self.filesSavedBeforeThisOne?[value.localUUID] { + if let updatedFile = self.filesSavedBeforeThisOne?[value.localId] { valueToEncode = updatedFile } else { //New object needs to be saved before it can be stored self.newObjects.append(value) } - } else if let currentFile = self.filesSavedBeforeThisOne?[value.localUUID] { + } else if let currentFile = self.filesSavedBeforeThisOne?[value.localId] { valueToEncode = currentFile } else if dictionary.count > 0 { //Only top level objects can be saved without a pointer diff --git a/Sources/ParseSwift/Objects/ParseObject.swift b/Sources/ParseSwift/Objects/ParseObject.swift index a6f3c1984..e9b4401fb 100644 --- a/Sources/ParseSwift/Objects/ParseObject.swift +++ b/Sources/ParseSwift/Objects/ParseObject.swift @@ -551,7 +551,7 @@ extension ParseObject { try savableFiles.forEach { let file = $0 - filesFinishedSaving[file.localUUID] = try $0.save(options: options) + filesFinishedSaving[file.localId] = try $0.save(options: options) } } completion(objectsFinishedSaving, filesFinishedSaving, nil) diff --git a/Sources/ParseSwift/Protocols/Fileable.swift b/Sources/ParseSwift/Protocols/Fileable.swift index 16ee6dac7..a5465fab6 100644 --- a/Sources/ParseSwift/Protocols/Fileable.swift +++ b/Sources/ParseSwift/Protocols/Fileable.swift @@ -23,14 +23,14 @@ extension Fileable { if let url = url { hasher.combine(url) } else { - hasher.combine(self.localUUID) + hasher.combine(self.localId) } } public static func == (lhs: Self, rhs: Self) -> Bool { guard let lURL = lhs.url, let rURL = rhs.url else { - return lhs.localUUID == rhs.localUUID + return lhs.localId == rhs.localId } return lURL == rURL } diff --git a/Sources/ParseSwift/Protocols/LocallyIdentifiable.swift b/Sources/ParseSwift/Protocols/LocallyIdentifiable.swift index 0950459bb..d6dc08677 100644 --- a/Sources/ParseSwift/Protocols/LocallyIdentifiable.swift +++ b/Sources/ParseSwift/Protocols/LocallyIdentifiable.swift @@ -9,16 +9,16 @@ import Foundation public protocol LocallyIdentifiable: Encodable, Hashable { - var localUUID: UUID { get set } + var localId: UUID { get set } } extension LocallyIdentifiable { mutating func hash(into hasher: inout Hasher) { - hasher.combine(self.localUUID) + hasher.combine(self.localId) } static func == (lhs: Self, rhs: Self) -> Bool { - return lhs.localUUID == rhs.localUUID + return lhs.localId == rhs.localId } } diff --git a/Sources/ParseSwift/Types/ParseFile.swift b/Sources/ParseSwift/Types/ParseFile.swift index 46a06668c..47a112e95 100644 --- a/Sources/ParseSwift/Types/ParseFile.swift +++ b/Sources/ParseSwift/Types/ParseFile.swift @@ -15,7 +15,7 @@ public struct ParseFile: Fileable, Savable, Fetchable, Deletable { && data == nil } - public var localUUID: UUID + public var localId: UUID /** The name of the file. @@ -80,7 +80,7 @@ public struct ParseFile: Fileable, Savable, Fetchable, Deletable { self.metadata = metadata self.tags = tags self.options = options - self.localUUID = UUID() + self.localId = UUID() } /** @@ -106,7 +106,7 @@ public struct ParseFile: Fileable, Savable, Fetchable, Deletable { self.metadata = metadata self.tags = tags self.options = options - self.localUUID = UUID() + self.localId = UUID() } /** @@ -132,7 +132,7 @@ public struct ParseFile: Fileable, Savable, Fetchable, Deletable { self.metadata = metadata self.tags = tags self.options = options - self.localUUID = UUID() + self.localId = UUID() } enum CodingKeys: String, CodingKey { @@ -147,7 +147,7 @@ extension ParseFile { let values = try decoder.container(keyedBy: CodingKeys.self) url = try values.decode(URL.self, forKey: .url) name = try values.decode(String.self, forKey: .name) - localUUID = UUID() + localId = UUID() } } diff --git a/Tests/ParseSwiftTests/ParseFileTests.swift b/Tests/ParseSwiftTests/ParseFileTests.swift index 2695f0836..64a2180bf 100644 --- a/Tests/ParseSwiftTests/ParseFileTests.swift +++ b/Tests/ParseSwiftTests/ParseFileTests.swift @@ -142,11 +142,11 @@ class ParseFileTests: XCTestCase { // swiftlint:disable:this type_body_length throw ParseError(code: .unknownError, message: "Should have converted to data") } let parseFile = ParseFile(name: "sampleData.txt", data: sampleData) - let localUUID = parseFile.localUUID - XCTAssertNotNil(localUUID) - XCTAssertEqual(localUUID, - parseFile.localUUID, - "localUUID should remain the same no matter how many times the getter is called") + let localId = parseFile.localId + XCTAssertNotNil(localId) + XCTAssertEqual(localId, + parseFile.localId, + "localId should remain the same no matter how many times the getter is called") } func testFileEquality() throws { @@ -165,15 +165,15 @@ class ParseFileTests: XCTestCase { // swiftlint:disable:this type_body_length parseFile2.url = url2 var parseFile3 = ParseFile(name: "sampleData3.txt", data: sampleData) parseFile3.url = url1 - XCTAssertNotEqual(parseFile1, parseFile2, "different urls, url takes precedence over localUUID") + XCTAssertNotEqual(parseFile1, parseFile2, "different urls, url takes precedence over localId") XCTAssertEqual(parseFile1, parseFile3, "same urls") parseFile1.url = nil parseFile2.url = nil - XCTAssertNotEqual(parseFile1, parseFile2, "no urls, but localUUIDs shoud be different") + XCTAssertNotEqual(parseFile1, parseFile2, "no urls, but localIds shoud be different") let uuid = UUID() - parseFile1.localUUID = uuid - parseFile2.localUUID = uuid - XCTAssertEqual(parseFile1, parseFile2, "no urls, but localUUIDs shoud be the same") + parseFile1.localId = uuid + parseFile2.localId = uuid + XCTAssertEqual(parseFile1, parseFile2, "no urls, but localIds shoud be the same") } func testSave() throws { From f1de7e4a95d937bcc008bc5ef040ebba3ced7adb Mon Sep 17 00:00:00 2001 From: Corey's iMac Date: Sat, 2 Jan 2021 13:54:30 -0500 Subject: [PATCH 32/44] Batch in waves of 50 by default. --- ParseSwift.xcodeproj/project.pbxproj | 8 ++ Sources/ParseSwift/API/BatchUtils.swift | 18 +++ .../Objects/ParseInstallation.swift | 116 +++++++++++------ Sources/ParseSwift/Objects/ParseObject.swift | 110 ++++++++++++---- Sources/ParseSwift/Objects/ParseUser.swift | 117 ++++++++++++------ Sources/ParseSwift/ParseConstants.swift | 1 + Tests/ParseSwiftTests/BatchUtilsTests.swift | 82 ++++++++++++ 7 files changed, 357 insertions(+), 95 deletions(-) create mode 100644 Tests/ParseSwiftTests/BatchUtilsTests.swift diff --git a/ParseSwift.xcodeproj/project.pbxproj b/ParseSwift.xcodeproj/project.pbxproj index 947f80614..42a75f82e 100644 --- a/ParseSwift.xcodeproj/project.pbxproj +++ b/ParseSwift.xcodeproj/project.pbxproj @@ -17,6 +17,9 @@ 4AB8B5051F254AE10070F682 /* Parse.h in Headers */ = {isa = PBXBuildFile; fileRef = 4AB8B4F71F254AE10070F682 /* Parse.h */; settings = {ATTRIBUTES = (Public, ); }; }; 4AFDA72A1F26DAE1002AE4FC /* Parse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A82B7EE1F254B820063D731 /* Parse.swift */; }; 4AFDA7391F26DAF8002AE4FC /* Parse.h in Headers */ = {isa = PBXBuildFile; fileRef = 4AB8B4F71F254AE10070F682 /* Parse.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 7003957625A0EE770052CB31 /* BatchUtilsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7003957525A0EE770052CB31 /* BatchUtilsTests.swift */; }; + 7003957725A0EE770052CB31 /* BatchUtilsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7003957525A0EE770052CB31 /* BatchUtilsTests.swift */; }; + 7003957825A0EE770052CB31 /* BatchUtilsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7003957525A0EE770052CB31 /* BatchUtilsTests.swift */; }; 70110D52250680140091CC1D /* ParseConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70110D51250680140091CC1D /* ParseConstants.swift */; }; 70110D53250680140091CC1D /* ParseConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70110D51250680140091CC1D /* ParseConstants.swift */; }; 70110D54250680140091CC1D /* ParseConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70110D51250680140091CC1D /* ParseConstants.swift */; }; @@ -325,6 +328,7 @@ 4ACFC2E21F3CA21F0046F3A3 /* ParseSwift.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; path = ParseSwift.playground; sourceTree = ""; }; 4AFDA7121F26D9A5002AE4FC /* ParseSwift.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = ParseSwift.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 4AFDA7151F26D9A5002AE4FC /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 7003957525A0EE770052CB31 /* BatchUtilsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BatchUtilsTests.swift; sourceTree = ""; }; 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 = ""; }; @@ -525,6 +529,7 @@ 911DB12D24C4837E0027F3C7 /* APICommandTests.swift */, 705726ED2592C91C00F0ADD5 /* HashTests.swift */, 4AA8076E1F794C1C008CD551 /* KeychainStoreTests.swift */, + 7003957525A0EE770052CB31 /* BatchUtilsTests.swift */, 916786EF259BC59600BB5B4E /* ParseCloudTests.swift */, F971F4F524DE381A006CB79B /* ParseEncoderTests.swift */, 705A99F8259807F900B3547F /* ParseFileManagerTests.swift */, @@ -1234,6 +1239,7 @@ 70110D5C2506ED0E0091CC1D /* ParseInstallationTests.swift in Sources */, 705727B12593FF8800F0ADD5 /* ParseFileTests.swift in Sources */, 70BC0B33251903D1001556DB /* ParseGeoPointTests.swift in Sources */, + 7003957625A0EE770052CB31 /* BatchUtilsTests.swift in Sources */, 705A99F9259807F900B3547F /* ParseFileManagerTests.swift in Sources */, 91678706259BC5D400BB5B4E /* ParseCloudTests.swift in Sources */, 7FFF552E2217E72A007C3B4E /* AnyEncodableTests.swift in Sources */, @@ -1323,6 +1329,7 @@ 709B98572556ECAA00507778 /* ACLTests.swift in Sources */, 705727BC2593FF8C00F0ADD5 /* ParseFileTests.swift in Sources */, 709B984F2556ECAA00507778 /* AnyCodableTests.swift in Sources */, + 7003957825A0EE770052CB31 /* BatchUtilsTests.swift in Sources */, 705A99FB259807F900B3547F /* ParseFileManagerTests.swift in Sources */, 9167871A259BC5D600BB5B4E /* ParseCloudTests.swift in Sources */, 709B98592556ECAA00507778 /* MockURLResponse.swift in Sources */, @@ -1350,6 +1357,7 @@ 70F2E2BC254F283000B2EA5C /* ParseObjectTests.swift in Sources */, 705727BB2593FF8B00F0ADD5 /* ParseFileTests.swift in Sources */, 70F2E2BD254F283000B2EA5C /* AnyDecodableTests.swift in Sources */, + 7003957725A0EE770052CB31 /* BatchUtilsTests.swift in Sources */, 705A99FA259807F900B3547F /* ParseFileManagerTests.swift in Sources */, 91678710259BC5D600BB5B4E /* ParseCloudTests.swift in Sources */, 70F2E2C1254F283000B2EA5C /* AnyCodableTests.swift in Sources */, diff --git a/Sources/ParseSwift/API/BatchUtils.swift b/Sources/ParseSwift/API/BatchUtils.swift index 7fe427433..7e6dd92bd 100644 --- a/Sources/ParseSwift/API/BatchUtils.swift +++ b/Sources/ParseSwift/API/BatchUtils.swift @@ -30,3 +30,21 @@ internal struct BatchCommand: ParseType where T: ParseType { internal struct BatchCommandNoBody: Encodable where T: Encodable { let requests: [API.NonParseBodyCommand] } + +struct BatchUtils { + static func splitArray(_ array: [U], valuesPerSegment: Int) -> [[U]] { + if array.count < valuesPerSegment { + return [array] + } + + var returnArray = [[U]]() + var index = 0 + while index < array.count { + let length = Swift.min(array.count - index, valuesPerSegment) + let subArray = Array(array[index.. [(Result)] { + func saveAll(batchLimit limit: Int? = nil, // swiftlint:disable:this function_body_length + options: API.Options = []) throws -> [(Result)] { + let batchLimit = limit != nil ? limit! : ParseConstants.batchLimit var childObjects = [String: PointerType]() var childFiles = [UUID: ParseFile]() var error: ParseError? @@ -591,30 +595,39 @@ public extension Sequence where Element: ParseInstallation { } } + var returnBatch = [(Result)]() let commands = map { $0.saveCommand() } - let returnResults = try API.Command - .batch(commands: commands) + let batches = BatchUtils.splitArray(commands, valuesPerSegment: batchLimit) + try batches.forEach { + let currentBatch = try API.Command + .batch(commands: $0) .execute(options: options, childObjects: childObjects, childFiles: childFiles) - try? Self.Element.updateKeychainIfNeeded(compactMap {$0}) - return returnResults + returnBatch.append(contentsOf: currentBatch) + } + try? Self.Element.updateKeychainIfNeeded(returnBatch.compactMap {try? $0.get()}) + return returnBatch } /** Saves a collection of installations all at once *asynchronously* and executes the completion block when done. - + - parameter batchLimit: The amount of objects to send in each batch. If the items to be batched + is greater than the `batchLimit`, the objects will be sent to the server in waves up to the `batchLimit`. + Defaults to 50. - 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<[(Result)], ParseError>)`. - important: If an object saved has the same objectId as current, it will automatically update the current. */ - func saveAll( // swiftlint:disable:this function_body_length + func saveAll( // swiftlint:disable:this function_body_length cyclomatic_complexity + batchLimit limit: Int? = nil, options: API.Options = [], callbackQueue: DispatchQueue = .main, completion: @escaping (Result<[(Result)], ParseError>) -> Void ) { + let batchLimit = limit != nil ? limit! : ParseConstants.batchLimit var childObjects = [String: PointerType]() var childFiles = [UUID: ParseFile]() var error: ParseError? @@ -659,20 +672,30 @@ public extension Sequence where Element: ParseInstallation { } } + var returnBatch = [(Result)]() let commands = map { $0.saveCommand() } - API.Command - .batch(commands: commands) - .executeAsync(options: options, - callbackQueue: callbackQueue, - childObjects: childObjects, - childFiles: childFiles) { results in - switch results { - - case .success(let saved): - try? Self.Element.updateKeychainIfNeeded(self.compactMap {$0}) - completion(.success(saved)) - case .failure(let error): - completion(.failure(error)) + let batches = BatchUtils.splitArray(commands, valuesPerSegment: batchLimit) + var completed = 0 + for batch in batches { + API.Command + .batch(commands: batch) + .executeAsync(options: options, + callbackQueue: callbackQueue, + childObjects: childObjects, + childFiles: childFiles) { results in + switch results { + + case .success(let saved): + returnBatch.append(contentsOf: saved) + if completed == (batches.count - 1) { + try? Self.Element.updateKeychainIfNeeded(returnBatch.compactMap {try? $0.get()}) + completion(.success(returnBatch)) + } + completed += 1 + case .failure(let error): + completion(.failure(error)) + return + } } } } @@ -762,7 +785,9 @@ public extension Sequence where Element: ParseInstallation { /** Deletes a collection of installations *synchronously* all at once and throws an error if necessary. - + - parameter batchLimit: The amount of objects to send in each batch. If the items to be batched + is greater than the `batchLimit`, the objects will be sent to the server in waves up to the `batchLimit`. + Defaults to 50. - parameter options: A set of header options sent to the server. Defaults to an empty set. - returns: Returns `nil` if the delete successful or a `ParseError` if it failed. @@ -776,19 +801,28 @@ public extension Sequence where Element: ParseInstallation { - throws: `ParseError` - important: If an object deleted has the same objectId as current, it will automatically update the current. */ - func deleteAll(options: API.Options = []) throws -> [ParseError?] { + func deleteAll(batchLimit limit: Int? = nil, + options: API.Options = []) throws -> [ParseError?] { + let batchLimit = limit != nil ? limit! : ParseConstants.batchLimit + var returnBatch = [ParseError?]() let commands = try map { try $0.deleteCommand() } - let returnResults = try API.Command - .batch(commands: commands) - .execute(options: options) + let batches = BatchUtils.splitArray(commands, valuesPerSegment: batchLimit) + try batches.forEach { + let currentBatch = try API.Command + .batch(commands: $0) + .execute(options: options) + returnBatch.append(contentsOf: currentBatch) + } try? Self.Element.updateKeychainIfNeeded(compactMap {$0}) - return returnResults + return returnBatch } /** Deletes a collection of installations all at once *asynchronously* and executes the completion block when done. - + - parameter batchLimit: The amount of objects to send in each batch. If the items to be batched + is greater than the `batchLimit`, the objects will be sent to the server in waves up to the `batchLimit`. + Defaults to 50. - 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. @@ -804,25 +838,37 @@ 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 = [], callbackQueue: DispatchQueue = .main, completion: @escaping (Result<[ParseError?], ParseError>) -> Void ) { + let batchLimit = limit != nil ? limit! : ParseConstants.batchLimit do { + var returnBatch = [ParseError?]() let commands = try map({ try $0.deleteCommand() }) - API.Command - .batch(commands: commands) - .executeAsync(options: options, - callbackQueue: callbackQueue) { results in + let batches = BatchUtils.splitArray(commands, valuesPerSegment: batchLimit) + var completed = 0 + for batch in batches { + API.Command + .batch(commands: batch) + .executeAsync(options: options, + callbackQueue: callbackQueue) { results in switch results { - case .success(let deleted): - try? Self.Element.updateKeychainIfNeeded(self.compactMap {$0}) - completion(.success(deleted)) + case .success(let saved): + returnBatch.append(contentsOf: saved) + if completed == (batches.count - 1) { + try? Self.Element.updateKeychainIfNeeded(self.compactMap {$0}) + completion(.success(returnBatch)) + } + completed += 1 case .failure(let error): completion(.failure(error)) + return } } + } } catch { guard let parseError = error as? ParseError else { completion(.failure(ParseError(code: .unknownError, diff --git a/Sources/ParseSwift/Objects/ParseObject.swift b/Sources/ParseSwift/Objects/ParseObject.swift index e9b4401fb..44bc3ca4c 100644 --- a/Sources/ParseSwift/Objects/ParseObject.swift +++ b/Sources/ParseSwift/Objects/ParseObject.swift @@ -59,13 +59,17 @@ public extension Sequence where Element: ParseObject { /** Saves a collection of objects *synchronously* all at once and throws an error if necessary. - + - parameter batchLimit: The amount of objects to send in each batch. If the items to be batched + is greater than the `batchLimit`, the objects will be sent to the server in waves up to the `batchLimit`. + Defaults to 50. - parameter options: A set of header options sent to the server. Defaults to an empty set. - returns: Returns a Result enum with the object if a save was successful or a `ParseError` if it failed. - throws: `ParseError` */ - func saveAll(options: API.Options = []) throws -> [(Result)] { + func saveAll(batchLimit limit: Int? = nil, // swiftlint:disable:this function_body_length + options: API.Options = []) throws -> [(Result)] { + let batchLimit = limit != nil ? limit! : ParseConstants.batchLimit var childObjects = [String: PointerType]() var childFiles = [UUID: ParseFile]() var error: ParseError? @@ -109,27 +113,37 @@ public extension Sequence where Element: ParseObject { } } + var returnBatch = [(Result)]() let commands = map { $0.saveCommand() } - return try API.Command - .batch(commands: commands) + let batches = BatchUtils.splitArray(commands, valuesPerSegment: batchLimit) + try batches.forEach { + let currentBatch = try API.Command + .batch(commands: $0) .execute(options: options, childObjects: childObjects, childFiles: childFiles) + returnBatch.append(contentsOf: currentBatch) + } + return returnBatch } /** Saves a collection of objects all at once *asynchronously* and executes the completion block when done. - + - parameter batchLimit: The amount of objects to send in each batch. If the items to be batched + is greater than the `batchLimit`, the objects will be sent to the server in waves up to the `batchLimit`. + Defaults to 50. - 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<[(Result)], ParseError>)`. */ - func saveAll( + func saveAll( // swiftlint:disable:this function_body_length cyclomatic_complexity + batchLimit limit: Int? = nil, options: API.Options = [], callbackQueue: DispatchQueue = .main, completion: @escaping (Result<[(Result)], ParseError>) -> Void ) { + let batchLimit = limit != nil ? limit! : ParseConstants.batchLimit var childObjects = [String: PointerType]() var childFiles = [UUID: ParseFile]() var error: ParseError? @@ -174,14 +188,31 @@ public extension Sequence where Element: ParseObject { } } + var returnBatch = [(Result)]() let commands = map { $0.saveCommand() } - API.Command - .batch(commands: commands) - .executeAsync(options: options, - callbackQueue: callbackQueue, - childObjects: childObjects, - childFiles: childFiles, - completion: completion) + let batches = BatchUtils.splitArray(commands, valuesPerSegment: batchLimit) + var completed = 0 + for batch in batches { + API.Command + .batch(commands: batch) + .executeAsync(options: options, + callbackQueue: callbackQueue, + childObjects: childObjects, + childFiles: childFiles) { results in + switch results { + + case .success(let saved): + returnBatch.append(contentsOf: saved) + if completed == (batches.count - 1) { + completion(.success(returnBatch)) + } + completed += 1 + case .failure(let error): + completion(.failure(error)) + return + } + } + } } /** @@ -265,7 +296,9 @@ public extension Sequence where Element: ParseObject { /** Deletes a collection of objects *synchronously* all at once and throws an error if necessary. - + - parameter batchLimit: The amount of objects to send in each batch. If the items to be batched + is greater than the `batchLimit`, the objects will be sent to the server in waves up to the `batchLimit`. + Defaults to 50. - parameter options: A set of header options sent to the server. Defaults to an empty set. - returns: Returns `nil` if the delete successful or a `ParseError` if it failed. @@ -278,16 +311,26 @@ public extension Sequence where Element: ParseObject { instance, a connection failure in the middle of the delete). - throws: `ParseError` */ - func deleteAll(options: API.Options = []) throws -> [ParseError?] { + func deleteAll(batchLimit limit: Int? = nil, + options: API.Options = []) throws -> [ParseError?] { + let batchLimit = limit != nil ? limit! : ParseConstants.batchLimit + var returnBatch = [ParseError?]() let commands = try map { try $0.deleteCommand() } - return try API.Command - .batch(commands: commands) - .execute(options: options) + let batches = BatchUtils.splitArray(commands, valuesPerSegment: batchLimit) + try batches.forEach { + let currentBatch = try API.Command + .batch(commands: $0) + .execute(options: options) + returnBatch.append(contentsOf: currentBatch) + } + return returnBatch } /** Deletes a collection of objects all at once *asynchronously* and executes the completion block when done. - + - parameter batchLimit: The amount of objects to send in each batch. If the items to be batched + is greater than the `batchLimit`, the objects will be sent to the server in waves up to the `batchLimit`. + Defaults to 50. - 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. @@ -302,17 +345,36 @@ public extension Sequence where Element: ParseObject { instance, a connection failure in the middle of the delete). */ func deleteAll( + batchLimit limit: Int? = nil, options: API.Options = [], callbackQueue: DispatchQueue = .main, completion: @escaping (Result<[ParseError?], ParseError>) -> Void ) { + let batchLimit = limit != nil ? limit! : ParseConstants.batchLimit do { + var returnBatch = [ParseError?]() let commands = try map({ try $0.deleteCommand() }) - API.Command - .batch(commands: commands) - .executeAsync(options: options, - callbackQueue: callbackQueue, - completion: completion) + let batches = BatchUtils.splitArray(commands, valuesPerSegment: batchLimit) + var completed = 0 + for batch in batches { + API.Command + .batch(commands: batch) + .executeAsync(options: options, + callbackQueue: callbackQueue) { results in + switch results { + + case .success(let saved): + returnBatch.append(contentsOf: saved) + if completed == (batches.count - 1) { + completion(.success(returnBatch)) + } + completed += 1 + case .failure(let error): + completion(.failure(error)) + return + } + } + } } catch { guard let parseError = error as? ParseError else { completion(.failure(ParseError(code: .unknownError, diff --git a/Sources/ParseSwift/Objects/ParseUser.swift b/Sources/ParseSwift/Objects/ParseUser.swift index 555417abc..c0fa6ab28 100644 --- a/Sources/ParseSwift/Objects/ParseUser.swift +++ b/Sources/ParseSwift/Objects/ParseUser.swift @@ -682,14 +682,18 @@ public extension Sequence where Element: ParseUser { /** Saves a collection of users *synchronously* all at once and throws an error if necessary. - + - parameter batchLimit: The amount of objects to send in each batch. If the items to be batched + is greater than the `batchLimit`, the objects will be sent to the server in waves up to the `batchLimit`. + Defaults to 50. - parameter options: A set of header options sent to the server. Defaults to an empty set. - returns: Returns a Result enum with the object if a save was successful or a `ParseError` if it failed. - throws: `ParseError` - important: If an object saved has the same objectId as current, it will automatically update the current. */ - func saveAll(options: API.Options = []) throws -> [(Result)] { + func saveAll(batchLimit limit: Int? = nil, // swiftlint:disable:this function_body_length + options: API.Options = []) throws -> [(Result)] { + let batchLimit = limit != nil ? limit! : ParseConstants.batchLimit var childObjects = [String: PointerType]() var childFiles = [UUID: ParseFile]() var error: ParseError? @@ -732,30 +736,39 @@ public extension Sequence where Element: ParseUser { } } + var returnBatch = [(Result)]() let commands = map { $0.saveCommand() } - let returnResults = try API.Command - .batch(commands: commands) + let batches = BatchUtils.splitArray(commands, valuesPerSegment: batchLimit) + try batches.forEach { + let currentBatch = try API.Command + .batch(commands: $0) .execute(options: options, childObjects: childObjects, childFiles: childFiles) - try? Self.Element.updateKeychainIfNeeded(compactMap {$0}) - return returnResults + returnBatch.append(contentsOf: currentBatch) + } + try? Self.Element.updateKeychainIfNeeded(returnBatch.compactMap {try? $0.get()}) + return returnBatch } /** Saves a collection of users all at once *asynchronously* and executes the completion block when done. - + - parameter batchLimit: The amount of objects to send in each batch. If the items to be batched + is greater than the `batchLimit`, the objects will be sent to the server in waves up to the `batchLimit`. + Defaults to 50. - 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<[(Result)], ParseError>)`. - important: If an object saved has the same objectId as current, it will automatically update the current. */ - func saveAll( // swiftlint:disable:this function_body_length + func saveAll( // swiftlint:disable:this function_body_length cyclomatic_complexity + batchLimit limit: Int? = nil, options: API.Options = [], callbackQueue: DispatchQueue = .main, completion: @escaping (Result<[(Result)], ParseError>) -> Void ) { + let batchLimit = limit != nil ? limit! : ParseConstants.batchLimit var childObjects = [String: PointerType]() var childFiles = [UUID: ParseFile]() var error: ParseError? @@ -800,20 +813,30 @@ public extension Sequence where Element: ParseUser { } } + var returnBatch = [(Result)]() let commands = map { $0.saveCommand() } - API.Command - .batch(commands: commands) - .executeAsync(options: options, - callbackQueue: callbackQueue, - childObjects: childObjects, - childFiles: childFiles) { results in - switch results { - - case .success(let saved): - try? Self.Element.updateKeychainIfNeeded(self.compactMap {$0}) - completion(.success(saved)) - case .failure(let error): - completion(.failure(error)) + let batches = BatchUtils.splitArray(commands, valuesPerSegment: batchLimit) + var completed = 0 + for batch in batches { + API.Command + .batch(commands: batch) + .executeAsync(options: options, + callbackQueue: callbackQueue, + childObjects: childObjects, + childFiles: childFiles) { results in + switch results { + + case .success(let saved): + returnBatch.append(contentsOf: saved) + if completed == (batches.count - 1) { + try? Self.Element.updateKeychainIfNeeded(returnBatch.compactMap {try? $0.get()}) + completion(.success(returnBatch)) + } + completed += 1 + case .failure(let error): + completion(.failure(error)) + return + } } } } @@ -903,7 +926,9 @@ public extension Sequence where Element: ParseUser { /** Deletes a collection of users *synchronously* all at once and throws an error if necessary. - + - parameter batchLimit: The amount of objects to send in each batch. If the items to be batched + is greater than the `batchLimit`, the objects will be sent to the server in waves up to the `batchLimit`. + Defaults to 50. - parameter options: A set of header options sent to the server. Defaults to an empty set. - returns: Returns `nil` if the delete successful or a `ParseError` if it failed. @@ -917,19 +942,27 @@ public extension Sequence where Element: ParseUser { - throws: `ParseError` - important: If an object deleted has the same objectId as current, it will automatically update the current. */ - func deleteAll(options: API.Options = []) throws -> [ParseError?] { + func deleteAll(batchLimit limit: Int? = nil, + options: API.Options = []) throws -> [ParseError?] { + let batchLimit = limit != nil ? limit! : ParseConstants.batchLimit + var returnBatch = [ParseError?]() let commands = try map { try $0.deleteCommand() } - let returnResults = try API.Command - .batch(commands: commands) - .execute(options: options) - + let batches = BatchUtils.splitArray(commands, valuesPerSegment: batchLimit) + try batches.forEach { + let currentBatch = try API.Command + .batch(commands: $0) + .execute(options: options) + returnBatch.append(contentsOf: currentBatch) + } try? Self.Element.updateKeychainIfNeeded(compactMap {$0}) - return returnResults + return returnBatch } /** Deletes a collection of users all at once *asynchronously* and executes the completion block when done. - + - parameter batchLimit: The amount of objects to send in each batch. If the items to be batched + is greater than the `batchLimit`, the objects will be sent to the server in waves up to the `batchLimit`. + Defaults to 50. - 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. @@ -945,25 +978,37 @@ 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 = [], callbackQueue: DispatchQueue = .main, completion: @escaping (Result<[ParseError?], ParseError>) -> Void ) { + let batchLimit = limit != nil ? limit! : ParseConstants.batchLimit do { + var returnBatch = [ParseError?]() let commands = try map({ try $0.deleteCommand() }) - API.Command - .batch(commands: commands) - .executeAsync(options: options, - callbackQueue: callbackQueue) { results in + let batches = BatchUtils.splitArray(commands, valuesPerSegment: batchLimit) + var completed = 0 + for batch in batches { + API.Command + .batch(commands: batch) + .executeAsync(options: options, + callbackQueue: callbackQueue) { results in switch results { - case .success(let deleted): - try? Self.Element.updateKeychainIfNeeded(self.compactMap {$0}) - completion(.success(deleted)) + case .success(let saved): + returnBatch.append(contentsOf: saved) + if completed == (batches.count - 1) { + try? Self.Element.updateKeychainIfNeeded(self.compactMap {$0}) + completion(.success(returnBatch)) + } + completed += 1 case .failure(let error): completion(.failure(error)) + return } } + } } catch { guard let parseError = error as? ParseError else { completion(.failure(ParseError(code: .unknownError, diff --git a/Sources/ParseSwift/ParseConstants.swift b/Sources/ParseSwift/ParseConstants.swift index 8f3572aec..2394e9bbf 100644 --- a/Sources/ParseSwift/ParseConstants.swift +++ b/Sources/ParseSwift/ParseConstants.swift @@ -15,6 +15,7 @@ enum ParseConstants { static let fileManagementPrivateDocumentsDirectory = "Private Documents/" static let fileManagementLibraryDirectory = "Library/" static let fileDownloadsDirectory = "Downloads" + static let batchLimit = 50 #if os(iOS) static let deviceType = "ios" #elseif os(macOS) diff --git a/Tests/ParseSwiftTests/BatchUtilsTests.swift b/Tests/ParseSwiftTests/BatchUtilsTests.swift new file mode 100644 index 000000000..3a8e36bf1 --- /dev/null +++ b/Tests/ParseSwiftTests/BatchUtilsTests.swift @@ -0,0 +1,82 @@ +// +// BatchUtilsTests.swift +// ParseSwift +// +// Created by Corey Baker on 1/2/21. +// Copyright © 2021 Parse Community. All rights reserved. +// + +import Foundation +import XCTest +@testable import ParseSwift + +class BatchUtilsTests: XCTestCase { + override func setUpWithError() throws { + super.setUp() + } + + override func tearDownWithError() throws { + super.tearDown() + } + + func testSplitArrayLessSegments() throws { + let array = [1, 2] + let splitArray = BatchUtils.splitArray(array, valuesPerSegment: 3) + guard let firstSplit = splitArray.first else { + XCTFail("Should have a first item in the array") + return + } + XCTAssertEqual(splitArray.count, 1) + XCTAssertEqual(firstSplit, array) + } + + func testSplitArrayExactSegments() throws { + let array = [1, 2] + let splitArray = BatchUtils.splitArray(array, valuesPerSegment: 2) + guard let firstSplit = splitArray.first else { + XCTFail("Should have a first item in the array") + return + } + XCTAssertEqual(splitArray.count, 1) + XCTAssertEqual(firstSplit, array) + } + + func testSplitArrayMoreSegments() throws { + let array = [1, 2] + let splitArray = BatchUtils.splitArray(array, valuesPerSegment: 1) + guard let firstSplit = splitArray.first, + let lastSplit = splitArray.last else { + XCTFail("Should have a first item in the array") + return + } + XCTAssertEqual(splitArray.count, 2) + XCTAssertEqual(firstSplit, [1]) + XCTAssertEqual(lastSplit, [2]) + } + + func testSplitArrayEvenMoreSegments() throws { + let array = [1, 2, 3, 4, 5] + let splitArray = BatchUtils.splitArray(array, valuesPerSegment: 1) + guard let firstSplit = splitArray.first, + let lastSplit = splitArray.last else { + XCTFail("Should have a first item in the array") + return + } + XCTAssertEqual(splitArray.count, 5) + XCTAssertEqual(firstSplit, [1]) + XCTAssertEqual(lastSplit, [5]) + } + + func testSplitArrayComplexSegments() throws { + let array = [1, 2, 3, 4, 5, 6, 7] + let splitArray = BatchUtils.splitArray(array, valuesPerSegment: 2) + guard let firstSplit = splitArray.first, + let lastSplit = splitArray.last else { + XCTFail("Should have a first item in the array") + return + } + XCTAssertEqual(splitArray.count, 4) + XCTAssertEqual(firstSplit, [1, 2]) + XCTAssertEqual(lastSplit, [7]) + } +} From 616c11c18d1e872eca039455bd89565e17f85dbf Mon Sep 17 00:00:00 2001 From: Corey Date: Sun, 3 Jan 2021 15:46:25 -0500 Subject: [PATCH 33/44] Update Sources/ParseSwift/Objects/ParseObject.swift Co-authored-by: Tom Fox <13188249+TomWFox@users.noreply.github.com> --- Sources/ParseSwift/Objects/ParseObject.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/ParseSwift/Objects/ParseObject.swift b/Sources/ParseSwift/Objects/ParseObject.swift index 44bc3ca4c..e0b025876 100644 --- a/Sources/ParseSwift/Objects/ParseObject.swift +++ b/Sources/ParseSwift/Objects/ParseObject.swift @@ -59,7 +59,7 @@ public extension Sequence where Element: ParseObject { /** Saves a collection of objects *synchronously* all at once and throws an error if necessary. - - parameter batchLimit: The amount of objects to send in each batch. If the items to be batched + - parameter batchLimit: The maximum number of objects to send in each batch. If the items to be batched is greater than the `batchLimit`, the objects will be sent to the server in waves up to the `batchLimit`. Defaults to 50. - parameter options: A set of header options sent to the server. Defaults to an empty set. From 9dedf81392040c1f0289796f87fee4c78c937e73 Mon Sep 17 00:00:00 2001 From: Corey Date: Sun, 3 Jan 2021 15:46:40 -0500 Subject: [PATCH 34/44] Update Sources/ParseSwift/Objects/ParseObject.swift Co-authored-by: Tom Fox <13188249+TomWFox@users.noreply.github.com> --- Sources/ParseSwift/Objects/ParseObject.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/ParseSwift/Objects/ParseObject.swift b/Sources/ParseSwift/Objects/ParseObject.swift index e0b025876..84b2a1e8a 100644 --- a/Sources/ParseSwift/Objects/ParseObject.swift +++ b/Sources/ParseSwift/Objects/ParseObject.swift @@ -296,7 +296,7 @@ public extension Sequence where Element: ParseObject { /** Deletes a collection of objects *synchronously* all at once and throws an error if necessary. - - parameter batchLimit: The amount of objects to send in each batch. If the items to be batched + - parameter batchLimit: The maximum number of objects to send in each batch. If the items to be batched is greater than the `batchLimit`, the objects will be sent to the server in waves up to the `batchLimit`. Defaults to 50. - parameter options: A set of header options sent to the server. Defaults to an empty set. From b0ce82f4d06d486002695a7007c50d01e3ab52db Mon Sep 17 00:00:00 2001 From: Corey Date: Sun, 3 Jan 2021 15:46:49 -0500 Subject: [PATCH 35/44] Update Sources/ParseSwift/Objects/ParseObject.swift Co-authored-by: Tom Fox <13188249+TomWFox@users.noreply.github.com> --- Sources/ParseSwift/Objects/ParseObject.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/ParseSwift/Objects/ParseObject.swift b/Sources/ParseSwift/Objects/ParseObject.swift index 84b2a1e8a..1da3aee97 100644 --- a/Sources/ParseSwift/Objects/ParseObject.swift +++ b/Sources/ParseSwift/Objects/ParseObject.swift @@ -129,7 +129,7 @@ public extension Sequence where Element: ParseObject { /** Saves a collection of objects all at once *asynchronously* and executes the completion block when done. - - parameter batchLimit: The amount of objects to send in each batch. If the items to be batched + - parameter batchLimit: The maximum number of objects to send in each batch. If the items to be batched is greater than the `batchLimit`, the objects will be sent to the server in waves up to the `batchLimit`. Defaults to 50. - parameter options: A set of header options sent to the server. Defaults to an empty set. From 7def6db9332a034309a0239536dfd028b0916ff0 Mon Sep 17 00:00:00 2001 From: Corey Date: Sun, 3 Jan 2021 15:47:09 -0500 Subject: [PATCH 36/44] Update Sources/ParseSwift/Objects/ParseInstallation.swift Co-authored-by: Tom Fox <13188249+TomWFox@users.noreply.github.com> --- Sources/ParseSwift/Objects/ParseInstallation.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/ParseSwift/Objects/ParseInstallation.swift b/Sources/ParseSwift/Objects/ParseInstallation.swift index fffb69e28..3d93ecb78 100644 --- a/Sources/ParseSwift/Objects/ParseInstallation.swift +++ b/Sources/ParseSwift/Objects/ParseInstallation.swift @@ -820,7 +820,7 @@ public extension Sequence where Element: ParseInstallation { /** Deletes a collection of installations all at once *asynchronously* and executes the completion block when done. - - parameter batchLimit: The amount of objects to send in each batch. If the items to be batched + - parameter batchLimit: The maximum number of objects to send in each batch. If the items to be batched is greater than the `batchLimit`, the objects will be sent to the server in waves up to the `batchLimit`. Defaults to 50. - parameter options: A set of header options sent to the server. Defaults to an empty set. From 1859921844638e2a3bafb4a73c6014919ee1309c Mon Sep 17 00:00:00 2001 From: Corey Date: Sun, 3 Jan 2021 15:47:20 -0500 Subject: [PATCH 37/44] Update Sources/ParseSwift/Objects/ParseInstallation.swift Co-authored-by: Tom Fox <13188249+TomWFox@users.noreply.github.com> --- Sources/ParseSwift/Objects/ParseInstallation.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/ParseSwift/Objects/ParseInstallation.swift b/Sources/ParseSwift/Objects/ParseInstallation.swift index 3d93ecb78..62519e4f9 100644 --- a/Sources/ParseSwift/Objects/ParseInstallation.swift +++ b/Sources/ParseSwift/Objects/ParseInstallation.swift @@ -785,7 +785,7 @@ public extension Sequence where Element: ParseInstallation { /** Deletes a collection of installations *synchronously* all at once and throws an error if necessary. - - parameter batchLimit: The amount of objects to send in each batch. If the items to be batched + - parameter batchLimit: The maximum number of objects to send in each batch. If the items to be batched is greater than the `batchLimit`, the objects will be sent to the server in waves up to the `batchLimit`. Defaults to 50. - parameter options: A set of header options sent to the server. Defaults to an empty set. From 75aa62bd9bf44d00de011da61f6c956885016ce8 Mon Sep 17 00:00:00 2001 From: Corey Date: Sun, 3 Jan 2021 15:47:33 -0500 Subject: [PATCH 38/44] Update Sources/ParseSwift/Objects/ParseInstallation.swift Co-authored-by: Tom Fox <13188249+TomWFox@users.noreply.github.com> --- Sources/ParseSwift/Objects/ParseInstallation.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/ParseSwift/Objects/ParseInstallation.swift b/Sources/ParseSwift/Objects/ParseInstallation.swift index 62519e4f9..12d94d261 100644 --- a/Sources/ParseSwift/Objects/ParseInstallation.swift +++ b/Sources/ParseSwift/Objects/ParseInstallation.swift @@ -612,7 +612,7 @@ public extension Sequence where Element: ParseInstallation { /** Saves a collection of installations all at once *asynchronously* and executes the completion block when done. - - parameter batchLimit: The amount of objects to send in each batch. If the items to be batched + - parameter batchLimit: The maximum number of objects to send in each batch. If the items to be batched is greater than the `batchLimit`, the objects will be sent to the server in waves up to the `batchLimit`. Defaults to 50. - parameter options: A set of header options sent to the server. Defaults to an empty set. From e50e1293b3309c0170fd5089bbb116d11e0150be Mon Sep 17 00:00:00 2001 From: Corey Date: Sun, 3 Jan 2021 15:47:41 -0500 Subject: [PATCH 39/44] Update Sources/ParseSwift/Objects/ParseInstallation.swift Co-authored-by: Tom Fox <13188249+TomWFox@users.noreply.github.com> --- Sources/ParseSwift/Objects/ParseInstallation.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/ParseSwift/Objects/ParseInstallation.swift b/Sources/ParseSwift/Objects/ParseInstallation.swift index 12d94d261..f19d0fa7d 100644 --- a/Sources/ParseSwift/Objects/ParseInstallation.swift +++ b/Sources/ParseSwift/Objects/ParseInstallation.swift @@ -540,7 +540,7 @@ public extension Sequence where Element: ParseInstallation { /** Saves a collection of installations *synchronously* all at once and throws an error if necessary. - - parameter batchLimit: The amount of objects to send in each batch. If the items to be batched + - parameter batchLimit: The maximum number of objects to send in each batch. If the items to be batched is greater than the `batchLimit`, the objects will be sent to the server in waves up to the `batchLimit`. Defaults to 50. - parameter options: A set of header options sent to the server. Defaults to an empty set. From 323f4282d2a6f6911b5f661566fc6b3d0d76f191 Mon Sep 17 00:00:00 2001 From: Corey Date: Sun, 3 Jan 2021 15:48:09 -0500 Subject: [PATCH 40/44] Update Sources/ParseSwift/Objects/ParseUser.swift Co-authored-by: Tom Fox <13188249+TomWFox@users.noreply.github.com> --- Sources/ParseSwift/Objects/ParseUser.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/ParseSwift/Objects/ParseUser.swift b/Sources/ParseSwift/Objects/ParseUser.swift index c0fa6ab28..e5f298215 100644 --- a/Sources/ParseSwift/Objects/ParseUser.swift +++ b/Sources/ParseSwift/Objects/ParseUser.swift @@ -960,7 +960,7 @@ public extension Sequence where Element: ParseUser { /** Deletes a collection of users all at once *asynchronously* and executes the completion block when done. - - parameter batchLimit: The amount of objects to send in each batch. If the items to be batched + - parameter batchLimit: The maximum number of objects to send in each batch. If the items to be batched is greater than the `batchLimit`, the objects will be sent to the server in waves up to the `batchLimit`. Defaults to 50. - parameter options: A set of header options sent to the server. Defaults to an empty set. From 306b219498db0be130f782e08468eeb7d149c63a Mon Sep 17 00:00:00 2001 From: Corey Date: Sun, 3 Jan 2021 15:48:29 -0500 Subject: [PATCH 41/44] Update Sources/ParseSwift/Objects/ParseUser.swift Co-authored-by: Tom Fox <13188249+TomWFox@users.noreply.github.com> --- Sources/ParseSwift/Objects/ParseUser.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/ParseSwift/Objects/ParseUser.swift b/Sources/ParseSwift/Objects/ParseUser.swift index e5f298215..babdd1d5c 100644 --- a/Sources/ParseSwift/Objects/ParseUser.swift +++ b/Sources/ParseSwift/Objects/ParseUser.swift @@ -926,7 +926,7 @@ public extension Sequence where Element: ParseUser { /** Deletes a collection of users *synchronously* all at once and throws an error if necessary. - - parameter batchLimit: The amount of objects to send in each batch. If the items to be batched + - parameter batchLimit: The maximum number of objects to send in each batch. If the items to be batched is greater than the `batchLimit`, the objects will be sent to the server in waves up to the `batchLimit`. Defaults to 50. - parameter options: A set of header options sent to the server. Defaults to an empty set. From f305e5bd9850d8ceddffbb481b9ecf2404440daa Mon Sep 17 00:00:00 2001 From: Corey Date: Sun, 3 Jan 2021 15:48:41 -0500 Subject: [PATCH 42/44] Update Sources/ParseSwift/Objects/ParseUser.swift Co-authored-by: Tom Fox <13188249+TomWFox@users.noreply.github.com> --- Sources/ParseSwift/Objects/ParseUser.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/ParseSwift/Objects/ParseUser.swift b/Sources/ParseSwift/Objects/ParseUser.swift index babdd1d5c..7776a845b 100644 --- a/Sources/ParseSwift/Objects/ParseUser.swift +++ b/Sources/ParseSwift/Objects/ParseUser.swift @@ -682,7 +682,7 @@ public extension Sequence where Element: ParseUser { /** Saves a collection of users *synchronously* all at once and throws an error if necessary. - - parameter batchLimit: The amount of objects to send in each batch. If the items to be batched + - parameter batchLimit: The maximum number of objects to send in each batch. If the items to be batched is greater than the `batchLimit`, the objects will be sent to the server in waves up to the `batchLimit`. Defaults to 50. - parameter options: A set of header options sent to the server. Defaults to an empty set. From 59eb3fb0200f0e4695b08fb3c8e8468c94175b7e Mon Sep 17 00:00:00 2001 From: Corey Date: Sun, 3 Jan 2021 15:48:48 -0500 Subject: [PATCH 43/44] Update Sources/ParseSwift/Objects/ParseObject.swift Co-authored-by: Tom Fox <13188249+TomWFox@users.noreply.github.com> --- Sources/ParseSwift/Objects/ParseObject.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/ParseSwift/Objects/ParseObject.swift b/Sources/ParseSwift/Objects/ParseObject.swift index 1da3aee97..834e1ae80 100644 --- a/Sources/ParseSwift/Objects/ParseObject.swift +++ b/Sources/ParseSwift/Objects/ParseObject.swift @@ -328,7 +328,7 @@ public extension Sequence where Element: ParseObject { /** Deletes a collection of objects all at once *asynchronously* and executes the completion block when done. - - parameter batchLimit: The amount of objects to send in each batch. If the items to be batched + - parameter batchLimit: The maximum number of objects to send in each batch. If the items to be batched is greater than the `batchLimit`, the objects will be sent to the server in waves up to the `batchLimit`. Defaults to 50. - parameter options: A set of header options sent to the server. Defaults to an empty set. From 5241554aaef220d8e5598a6d458e9c0e771bbda1 Mon Sep 17 00:00:00 2001 From: Corey Date: Sun, 3 Jan 2021 15:49:04 -0500 Subject: [PATCH 44/44] Update Sources/ParseSwift/Objects/ParseUser.swift Co-authored-by: Tom Fox <13188249+TomWFox@users.noreply.github.com> --- Sources/ParseSwift/Objects/ParseUser.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/ParseSwift/Objects/ParseUser.swift b/Sources/ParseSwift/Objects/ParseUser.swift index 7776a845b..2f4b10bc1 100644 --- a/Sources/ParseSwift/Objects/ParseUser.swift +++ b/Sources/ParseSwift/Objects/ParseUser.swift @@ -753,7 +753,7 @@ public extension Sequence where Element: ParseUser { /** Saves a collection of users all at once *asynchronously* and executes the completion block when done. - - parameter batchLimit: The amount of objects to send in each batch. If the items to be batched + - parameter batchLimit: The maximum number of objects to send in each batch. If the items to be batched is greater than the `batchLimit`, the objects will be sent to the server in waves up to the `batchLimit`. Defaults to 50. - parameter options: A set of header options sent to the server. Defaults to an empty set.