diff --git a/CHANGELOG.md b/CHANGELOG.md index 89e8b43d6..64dd758ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,9 @@ [Full Changelog](https://github.com/parse-community/Parse-Swift/compare/1.9.8...main) * _Contributing to this repo? Add info about your change here to be included in the next release_ +__Improvements__ +- Added createWithCustomObjectId parameter in .save() functions to remove the need of setting isIgnoreCustomObjectIdConfig = true and allowCustomObjectId = true for a mixed custom objectId environment. ([#225](https://github.com/parse-community/Parse-Swift/pull/225)), thanks to [Lukas Smilek](https://github.com/lsmilek1) + ### 1.9.8 [Full Changelog](https://github.com/parse-community/Parse-Swift/compare/1.9.7...1.9.8) diff --git a/Sources/ParseSwift/API/API+Commands.swift b/Sources/ParseSwift/API/API+Commands.swift index 565621ff1..f8062803b 100644 --- a/Sources/ParseSwift/API/API+Commands.swift +++ b/Sources/ParseSwift/API/API+Commands.swift @@ -345,11 +345,11 @@ internal extension API.Command { // MARK: Saving ParseObjects static func save(_ object: T, - isIgnoreCustomObjectIdConfig: Bool) throws -> API.Command where T: ParseObject { - if ParseSwift.configuration.allowCustomObjectId && object.objectId == nil && !isIgnoreCustomObjectIdConfig { + isIgnoreCustomObjectIdConfig: Bool, createWithCustomObjectId: Bool) throws -> API.Command where T: ParseObject { + if (ParseSwift.configuration.allowCustomObjectId && object.objectId == nil && !isIgnoreCustomObjectIdConfig) || (object.objectId == nil && createWithCustomObjectId) { throw ParseError(code: .missingObjectId, message: "objectId must not be nil") } - if object.isSaved { + if object.isSaved && !createWithCustomObjectId { return update(object) } return create(object) diff --git a/Sources/ParseSwift/Objects/ParseInstallation+combine.swift b/Sources/ParseSwift/Objects/ParseInstallation+combine.swift index 3836ba4f4..050daf168 100644 --- a/Sources/ParseSwift/Objects/ParseInstallation+combine.swift +++ b/Sources/ParseSwift/Objects/ParseInstallation+combine.swift @@ -38,14 +38,30 @@ public extension ParseInstallation { /** Saves the `ParseInstallation` *asynchronously* and publishes when complete. + - parameter isIgnoreCustomObjectIdConfig: Ignore checking for `objectId` + when `ParseConfiguration.allowCustomObjectId = true` to allow for mixed + `objectId` environments. Defaults to false. + - parameter createWithCustomObjectId: Ignore checking `ParseConfiguration.allowCustomObjectId = true` + and try to create object with given custom `objectId` to allow for mixed `objectId` environments. Defaults to false. - parameter options: A set of header options sent to the server. Defaults to an empty set. - returns: A publisher that eventually produces a single value and then finishes or fails. - important: If an object saved has the same objectId as current, it will automatically update the current. + - warning: If you are using `ParseConfiguration.allowCustomObjectId = true` + and plan to generate all of your `objectId`'s on the client-side then you should leave + `isIgnoreCustomObjectIdConfig = false`. Setting + `ParseConfiguration.allowCustomObjectId = true` and + `isIgnoreCustomObjectIdConfig = true` means the client will generate `objectId`'s + and the server will generate an `objectId` only when the client does not provide one. This can + increase the probability of colliiding `objectId`'s as the client and server `objectId`'s may be generated using + different algorithms. This can also lead to overwriting of `ParseObject`'s by accident as the + client-side checks are disabled. Developers are responsible for handling such cases. */ func savePublisher(isIgnoreCustomObjectIdConfig: Bool = false, + createWithCustomObjectId: Bool = false, options: API.Options = []) -> Future { Future { promise in self.save(isIgnoreCustomObjectIdConfig: isIgnoreCustomObjectIdConfig, + createWithCustomObjectId: createWithCustomObjectId, options: options, completion: promise) } @@ -95,21 +111,37 @@ public extension Sequence where Element: ParseInstallation { Defaults to 50. - parameter transaction: Treat as an all-or-nothing operation. If some operation failure occurs that prevents the transaction from completing, then none of the objects are committed to the Parse Server database. + - parameter isIgnoreCustomObjectIdConfig: Ignore checking for `objectId` + when `ParseConfiguration.allowCustomObjectId = true` to allow for mixed + `objectId` environments. Defaults to false. + - parameter createWithCustomObjectId: Ignore checking `ParseConfiguration.allowCustomObjectId = true` + and try to create object with given custom `objectId` to allow for mixed `objectId` environments. Defaults to false. - parameter options: A set of header options sent to the server. Defaults to an empty set. - returns: A publisher that eventually produces a single value and then finishes or fails. - important: If an object saved has the same objectId as current, it will automatically update the current. - warning: If `transaction = true`, then `batchLimit` will be automatically be set to the amount of the objects in the transaction. The developer should ensure their respective Parse Servers can handle the limit or else the transactions can fail. + - warning: If you are using `ParseConfiguration.allowCustomObjectId = true` + and plan to generate all of your `objectId`'s on the client-side then you should leave + `isIgnoreCustomObjectIdConfig = false`. Setting + `ParseConfiguration.allowCustomObjectId = true` and + `isIgnoreCustomObjectIdConfig = true` means the client will generate `objectId`'s + and the server will generate an `objectId` only when the client does not provide one. This can + increase the probability of colliiding `objectId`'s as the client and server `objectId`'s may be generated using + different algorithms. This can also lead to overwriting of `ParseObject`'s by accident as the + client-side checks are disabled. Developers are responsible for handling such cases. */ func saveAllPublisher(batchLimit limit: Int? = nil, transaction: Bool = false, isIgnoreCustomObjectIdConfig: Bool = false, + createWithCustomObjectId: Bool = false, options: API.Options = []) -> Future<[(Result)], ParseError> { Future { promise in self.saveAll(batchLimit: limit, transaction: transaction, isIgnoreCustomObjectIdConfig: isIgnoreCustomObjectIdConfig, + createWithCustomObjectId: createWithCustomObjectId, options: options, completion: promise) } diff --git a/Sources/ParseSwift/Objects/ParseInstallation.swift b/Sources/ParseSwift/Objects/ParseInstallation.swift index eb4f0ad5f..65e6f16f3 100644 --- a/Sources/ParseSwift/Objects/ParseInstallation.swift +++ b/Sources/ParseSwift/Objects/ParseInstallation.swift @@ -427,6 +427,7 @@ extension ParseInstallation { */ public func save(options: API.Options = []) throws -> Self { try save(isIgnoreCustomObjectIdConfig: false, + createWithCustomObjectId: false, options: options) } @@ -436,6 +437,8 @@ extension ParseInstallation { - parameter isIgnoreCustomObjectIdConfig: Ignore checking for `objectId` when `ParseConfiguration.allowCustomObjectId = true` to allow for mixed `objectId` environments. Defaults to false. + - parameter createWithCustomObjectId: Ignore checking `ParseConfiguration.allowCustomObjectId = true` + and try to create object with given custom `objectId` to allow for mixed `objectId` environments. Defaults to false. - parameter options: A set of header options sent to the server. Defaults to an empty set. - throws: An error of type `ParseError`. - returns: Returns saved `ParseInstallation`. @@ -450,7 +453,8 @@ extension ParseInstallation { different algorithms. This can also lead to overwriting of `ParseObject`'s by accident as the client-side checks are disabled. Developers are responsible for handling such cases. */ - public func save(isIgnoreCustomObjectIdConfig: Bool, + public func save(isIgnoreCustomObjectIdConfig: Bool = false, + createWithCustomObjectId: Bool = false, options: API.Options = []) throws -> Self { var options = options options.insert(.cachePolicy(.reloadIgnoringLocalCacheData)) @@ -471,7 +475,7 @@ extension ParseInstallation { throw error } - let result: Self = try saveCommand(isIgnoreCustomObjectIdConfig: isIgnoreCustomObjectIdConfig) + let result: Self = try saveCommand(isIgnoreCustomObjectIdConfig: isIgnoreCustomObjectIdConfig, createWithCustomObjectId: createWithCustomObjectId) .execute(options: options, callbackQueue: .main, childObjects: childObjects, @@ -486,6 +490,8 @@ extension ParseInstallation { - parameter isIgnoreCustomObjectIdConfig: Ignore checking for `objectId` when `ParseConfiguration.allowCustomObjectId = true` to allow for mixed `objectId` environments. Defaults to false. + - parameter createWithCustomObjectId: Ignore checking `ParseConfiguration.allowCustomObjectId = true` + and try to create object with given custom `objectId` to allow for mixed `objectId` environments. Defaults to false. - 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. @@ -503,6 +509,7 @@ extension ParseInstallation { */ public func save( isIgnoreCustomObjectIdConfig: Bool = false, + createWithCustomObjectId: Bool = false, options: API.Options = [], callbackQueue: DispatchQueue = .main, completion: @escaping (Result) -> Void @@ -512,7 +519,7 @@ extension ParseInstallation { self.ensureDeepSave(options: options) { (savedChildObjects, savedChildFiles, error) in guard let parseError = error else { do { - try self.saveCommand(isIgnoreCustomObjectIdConfig: isIgnoreCustomObjectIdConfig) + try self.saveCommand(isIgnoreCustomObjectIdConfig: isIgnoreCustomObjectIdConfig, createWithCustomObjectId: createWithCustomObjectId) .executeAsync(options: options, callbackQueue: callbackQueue, childObjects: savedChildObjects, @@ -554,11 +561,11 @@ extension ParseInstallation { } } - func saveCommand(isIgnoreCustomObjectIdConfig: Bool = false) throws -> API.Command { - if ParseSwift.configuration.allowCustomObjectId && objectId == nil && !isIgnoreCustomObjectIdConfig { + func saveCommand(isIgnoreCustomObjectIdConfig: Bool = false, createWithCustomObjectId: Bool = false) throws -> API.Command { + if (ParseSwift.configuration.allowCustomObjectId && objectId == nil && !isIgnoreCustomObjectIdConfig) || (objectId == nil && createWithCustomObjectId) { throw ParseError(code: .missingObjectId, message: "objectId must not be nil") } - if isSaved { + if isSaved && !createWithCustomObjectId{ return updateCommand() } return createCommand() @@ -685,6 +692,8 @@ public extension Sequence where Element: ParseInstallation { - parameter isIgnoreCustomObjectIdConfig: Ignore checking for `objectId` when `ParseConfiguration.allowCustomObjectId = true` to allow for mixed `objectId` environments. Defaults to false. + - parameter createWithCustomObjectId: Ignore checking `ParseConfiguration.allowCustomObjectId = true` + and try to create object with given custom `objectId` to allow for mixed `objectId` environments. Defaults to false. - parameter options: A set of header options sent to the server. Defaults to an empty set. - parameter transaction: Treat as an all-or-nothing operation. If some operation failure occurs that prevents the transaction from completing, then none of the objects are committed to the Parse Server database. @@ -708,6 +717,7 @@ public extension Sequence where Element: ParseInstallation { func saveAll(batchLimit limit: Int? = nil, // swiftlint:disable:this function_body_length transaction: Bool = false, isIgnoreCustomObjectIdConfig: Bool = false, + createWithCustomObjectId: Bool = false, options: API.Options = []) throws -> [(Result)] { var options = options options.insert(.cachePolicy(.reloadIgnoringLocalCacheData)) @@ -756,7 +766,7 @@ public extension Sequence where Element: ParseInstallation { var returnBatch = [(Result)]() let commands = try map { - try $0.saveCommand(isIgnoreCustomObjectIdConfig: isIgnoreCustomObjectIdConfig) + try $0.saveCommand(isIgnoreCustomObjectIdConfig: isIgnoreCustomObjectIdConfig, createWithCustomObjectId: createWithCustomObjectId) } let batchLimit: Int! if transaction { @@ -788,6 +798,8 @@ public extension Sequence where Element: ParseInstallation { - parameter isIgnoreCustomObjectIdConfig: Ignore checking for `objectId` when `ParseConfiguration.allowCustomObjectId = true` to allow for mixed `objectId` environments. Defaults to false. + - parameter createWithCustomObjectId: Ignore checking `ParseConfiguration.allowCustomObjectId = true` + and try to create object with given custom `objectId` to allow for mixed `objectId` environments. Defaults to false. - 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. @@ -810,6 +822,7 @@ public extension Sequence where Element: ParseInstallation { batchLimit limit: Int? = nil, transaction: Bool = false, isIgnoreCustomObjectIdConfig: Bool = false, + createWithCustomObjectId: Bool = false, options: API.Options = [], callbackQueue: DispatchQueue = .main, completion: @escaping (Result<[(Result)], ParseError>) -> Void @@ -873,7 +886,7 @@ public extension Sequence where Element: ParseInstallation { do { var returnBatch = [(Result)]() let commands = try map { - try $0.saveCommand(isIgnoreCustomObjectIdConfig: isIgnoreCustomObjectIdConfig) + try $0.saveCommand(isIgnoreCustomObjectIdConfig: isIgnoreCustomObjectIdConfig, createWithCustomObjectId: createWithCustomObjectId) } let batchLimit: Int! if transaction { diff --git a/Sources/ParseSwift/Objects/ParseObject+combine.swift b/Sources/ParseSwift/Objects/ParseObject+combine.swift index c64a59f10..88b10bc4b 100644 --- a/Sources/ParseSwift/Objects/ParseObject+combine.swift +++ b/Sources/ParseSwift/Objects/ParseObject+combine.swift @@ -35,14 +35,21 @@ public extension ParseObject { /** Saves the `ParseObject` *asynchronously* and publishes when complete. + - parameter isIgnoreCustomObjectIdConfig: Ignore checking for `objectId` + when `ParseConfiguration.allowCustomObjectId = true` to allow for mixed + `objectId` environments. Defaults to false. + - parameter createWithCustomObjectId: Ignore checking `ParseConfiguration.allowCustomObjectId = true` + and try to create object with given custom `objectId` to allow for mixed `objectId` environments. Defaults to false. - parameter options: A set of header options sent to the server. Defaults to an empty set. - returns: A publisher that eventually produces a single value and then finishes or fails. - important: If an object saved has the same objectId as current, it will automatically update the current. */ func savePublisher(isIgnoreCustomObjectIdConfig: Bool = false, + createWithCustomObjectId: Bool = false, options: API.Options = []) -> Future { Future { promise in self.save(isIgnoreCustomObjectIdConfig: isIgnoreCustomObjectIdConfig, + createWithCustomObjectId: createWithCustomObjectId, options: options, completion: promise) } @@ -94,6 +101,8 @@ public extension Sequence where Element: ParseObject { - parameter isIgnoreCustomObjectIdConfig: Ignore checking for `objectId` when `ParseConfiguration.allowCustomObjectId = true` to allow for mixed `objectId` environments. Defaults to false. + - parameter createWithCustomObjectId: Ignore checking `ParseConfiguration.allowCustomObjectId = true` + and try to create object with given custom `objectId` to allow for mixed `objectId` environments. Defaults to false. - parameter options: A set of header options sent to the server. Defaults to an empty set. - returns: A publisher that eventually produces a single value and then finishes or fails. - important: If an object saved has the same objectId as current, it will automatically update the current. @@ -113,11 +122,13 @@ public extension Sequence where Element: ParseObject { func saveAllPublisher(batchLimit limit: Int? = nil, transaction: Bool = false, isIgnoreCustomObjectIdConfig: Bool = false, + createWithCustomObjectId: Bool = false, options: API.Options = []) -> Future<[(Result)], ParseError> { Future { promise in self.saveAll(batchLimit: limit, transaction: transaction, isIgnoreCustomObjectIdConfig: isIgnoreCustomObjectIdConfig, + createWithCustomObjectId: createWithCustomObjectId, options: options, completion: promise) } diff --git a/Sources/ParseSwift/Objects/ParseObject.swift b/Sources/ParseSwift/Objects/ParseObject.swift index ae1bb64ff..ea71183a0 100644 --- a/Sources/ParseSwift/Objects/ParseObject.swift +++ b/Sources/ParseSwift/Objects/ParseObject.swift @@ -70,6 +70,8 @@ public extension Sequence where Element: ParseObject { - parameter isIgnoreCustomObjectIdConfig: Ignore checking for `objectId` when `ParseConfiguration.allowCustomObjectId = true` to allow for mixed `objectId` environments. Defaults to false. + - parameter createWithCustomObjectId: Ignore checking `ParseConfiguration.allowCustomObjectId = true` + and try to create object with given custom `objectId` to allow for mixed `objectId` environments. Defaults to false. - 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. @@ -90,6 +92,7 @@ public extension Sequence where Element: ParseObject { func saveAll(batchLimit limit: Int? = nil, // swiftlint:disable:this function_body_length transaction: Bool = false, isIgnoreCustomObjectIdConfig: Bool = false, + createWithCustomObjectId: Bool = false, options: API.Options = []) throws -> [(Result)] { var options = options options.insert(.cachePolicy(.reloadIgnoringLocalCacheData)) @@ -137,7 +140,7 @@ public extension Sequence where Element: ParseObject { } var returnBatch = [(Result)]() - let commands = try map { try $0.saveCommand(isIgnoreCustomObjectIdConfig: isIgnoreCustomObjectIdConfig) } + let commands = try map { try $0.saveCommand(isIgnoreCustomObjectIdConfig: isIgnoreCustomObjectIdConfig, createWithCustomObjectId: createWithCustomObjectId) } let batchLimit: Int! if transaction { batchLimit = commands.count @@ -167,6 +170,8 @@ public extension Sequence where Element: ParseObject { - parameter isIgnoreCustomObjectIdConfig: Ignore checking for `objectId` when `ParseConfiguration.allowCustomObjectId = true` to allow for mixed `objectId` environments. Defaults to false. + - parameter createWithCustomObjectId: Ignore checking `ParseConfiguration.allowCustomObjectId = true` + and try to create object with given custom `objectId` to allow for mixed `objectId` environments. Defaults to false. - 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. @@ -188,6 +193,7 @@ public extension Sequence where Element: ParseObject { batchLimit limit: Int? = nil, transaction: Bool = false, isIgnoreCustomObjectIdConfig: Bool = false, + createWithCustomObjectId: Bool = false, options: API.Options = [], callbackQueue: DispatchQueue = .main, completion: @escaping (Result<[(Result)], ParseError>) -> Void @@ -251,7 +257,7 @@ public extension Sequence where Element: ParseObject { do { var returnBatch = [(Result)]() let commands = try map { - try $0.saveCommand(isIgnoreCustomObjectIdConfig: isIgnoreCustomObjectIdConfig) + try $0.saveCommand(isIgnoreCustomObjectIdConfig: isIgnoreCustomObjectIdConfig, createWithCustomObjectId: createWithCustomObjectId) } let batchLimit: Int! if transaction { @@ -608,7 +614,7 @@ extension ParseObject { - returns: Returns saved `ParseObject`. */ public func save(options: API.Options = []) throws -> Self { - try save(isIgnoreCustomObjectIdConfig: false, options: options) + try save(isIgnoreCustomObjectIdConfig: false, createWithCustomObjectId: false, options: options) } /** @@ -616,6 +622,8 @@ extension ParseObject { - parameter isIgnoreCustomObjectIdConfig: Ignore checking for `objectId` when `ParseConfiguration.allowCustomObjectId = true` to allow for mixed `objectId` environments. Defaults to false. + - parameter createWithCustomObjectId: Ignore checking `ParseConfiguration.allowCustomObjectId = true` + and try to create object with given custom `objectId` to allow for mixed `objectId` environments. Defaults to false. - parameter options: A set of header options sent to the server. Defaults to an empty set. - throws: An error of type `ParseError`. @@ -631,6 +639,7 @@ extension ParseObject { client-side checks are disabled. Developers are responsible for handling such cases. */ public func save(isIgnoreCustomObjectIdConfig: Bool = false, + createWithCustomObjectId: Bool = false, options: API.Options = []) throws -> Self { var childObjects: [String: PointerType]? var childFiles: [UUID: ParseFile]? @@ -651,7 +660,7 @@ extension ParseObject { throw error } - return try saveCommand(isIgnoreCustomObjectIdConfig: isIgnoreCustomObjectIdConfig) + return try saveCommand(isIgnoreCustomObjectIdConfig: isIgnoreCustomObjectIdConfig, createWithCustomObjectId: createWithCustomObjectId) .execute(options: options, callbackQueue: .main, childObjects: childObjects, @@ -664,6 +673,8 @@ extension ParseObject { - parameter isIgnoreCustomObjectIdConfig: Ignore checking for `objectId` when `ParseConfiguration.allowCustomObjectId = true` to allow for mixed `objectId` environments. Defaults to false. + - parameter createWithCustomObjectId: Ignore checking `ParseConfiguration.allowCustomObjectId = true` + and try to create object with given custom `objectId` to allow for mixed `objectId` environments. Defaults to false. - 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. @@ -680,6 +691,7 @@ extension ParseObject { */ public func save( isIgnoreCustomObjectIdConfig: Bool = false, + createWithCustomObjectId: Bool = false, options: API.Options = [], callbackQueue: DispatchQueue = .main, completion: @escaping (Result) -> Void @@ -687,7 +699,7 @@ extension ParseObject { self.ensureDeepSave(options: options) { (savedChildObjects, savedChildFiles, error) in guard let parseError = error else { do { - try self.saveCommand(isIgnoreCustomObjectIdConfig: isIgnoreCustomObjectIdConfig) + try self.saveCommand(isIgnoreCustomObjectIdConfig: isIgnoreCustomObjectIdConfig, createWithCustomObjectId: createWithCustomObjectId) .executeAsync(options: options, callbackQueue: callbackQueue, childObjects: savedChildObjects, @@ -713,8 +725,8 @@ extension ParseObject { } } - internal func saveCommand(isIgnoreCustomObjectIdConfig: Bool = false) throws -> API.Command { - try API.Command.save(self, isIgnoreCustomObjectIdConfig: isIgnoreCustomObjectIdConfig) + internal func saveCommand(isIgnoreCustomObjectIdConfig: Bool = false, createWithCustomObjectId: Bool = false) throws -> API.Command { + try API.Command.save(self, isIgnoreCustomObjectIdConfig: isIgnoreCustomObjectIdConfig, createWithCustomObjectId: createWithCustomObjectId) } // swiftlint:disable:next function_body_length diff --git a/Sources/ParseSwift/Objects/ParseUser+combine.swift b/Sources/ParseSwift/Objects/ParseUser+combine.swift index b660f8461..72259855e 100644 --- a/Sources/ParseSwift/Objects/ParseUser+combine.swift +++ b/Sources/ParseSwift/Objects/ParseUser+combine.swift @@ -163,6 +163,8 @@ public extension ParseUser { - parameter isIgnoreCustomObjectIdConfig: Ignore checking for `objectId` when `ParseConfiguration.allowCustomObjectId = true` to allow for mixed `objectId` environments. Defaults to false. + - parameter createWithCustomObjectId: Ignore checking `ParseConfiguration.allowCustomObjectId = true` + and try to create object with given custom `objectId` to allow for mixed `objectId` environments. Defaults to false. - parameter options: A set of header options sent to the server. Defaults to an empty set. - returns: A publisher that eventually produces a single value and then finishes or fails. - important: If an object saved has the same objectId as current, it will automatically update the current. @@ -177,9 +179,10 @@ public extension ParseUser { client-side checks are disabled. Developers are responsible for handling such cases. */ func savePublisher(options: API.Options = [], - isIgnoreCustomObjectIdConfig: Bool = false) -> Future { + isIgnoreCustomObjectIdConfig: Bool = false, createWithCustomObjectId: Bool = false) -> Future { Future { promise in self.save(isIgnoreCustomObjectIdConfig: isIgnoreCustomObjectIdConfig, + createWithCustomObjectId: createWithCustomObjectId, options: options, completion: promise) } @@ -232,6 +235,8 @@ public extension Sequence where Element: ParseUser { - parameter isIgnoreCustomObjectIdConfig: Ignore checking for `objectId` when `ParseConfiguration.allowCustomObjectId = true` to allow for mixed `objectId` environments. Defaults to false. + - parameter createWithCustomObjectId: Ignore checking `ParseConfiguration.allowCustomObjectId = true` + and try to create object with given custom `objectId` to allow for mixed `objectId` environments. Defaults to false. - parameter options: A set of header options sent to the server. Defaults to an empty set. - returns: A publisher that eventually produces a single value and then finishes or fails. - important: If an object saved has the same objectId as current, it will automatically update the current. @@ -251,11 +256,13 @@ public extension Sequence where Element: ParseUser { func saveAllPublisher(batchLimit limit: Int? = nil, transaction: Bool = false, isIgnoreCustomObjectIdConfig: Bool = false, + createWithCustomObjectId: Bool = false, options: API.Options = []) -> Future<[(Result)], ParseError> { Future { promise in self.saveAll(batchLimit: limit, transaction: transaction, isIgnoreCustomObjectIdConfig: isIgnoreCustomObjectIdConfig, + createWithCustomObjectId: createWithCustomObjectId, options: options, completion: promise) } diff --git a/Sources/ParseSwift/Objects/ParseUser.swift b/Sources/ParseSwift/Objects/ParseUser.swift index 4b85e5243..cebbb5f60 100644 --- a/Sources/ParseSwift/Objects/ParseUser.swift +++ b/Sources/ParseSwift/Objects/ParseUser.swift @@ -808,7 +808,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 { - try save(isIgnoreCustomObjectIdConfig: false, options: options) + try save(isIgnoreCustomObjectIdConfig: false, createWithCustomObjectId: false, options: options) } /** @@ -817,6 +817,8 @@ extension ParseUser { - parameter isIgnoreCustomObjectIdConfig: Ignore checking for `objectId` when `ParseConfiguration.allowCustomObjectId = true` to allow for mixed `objectId` environments. Defaults to false. + - parameter createWithCustomObjectId: Ignore checking `ParseConfiguration.allowCustomObjectId = true` + and try to create object with given custom `objectId` to allow for mixed `objectId` environments. Defaults to false. - parameter options: A set of header options sent to the server. Defaults to an empty set. - throws: An error of type `ParseError`. - returns: Returns saved `ParseUser`. @@ -831,7 +833,8 @@ extension ParseUser { different algorithms. This can also lead to overwriting of `ParseObject`'s by accident as the client-side checks are disabled. Developers are responsible for handling such cases. */ - public func save(isIgnoreCustomObjectIdConfig: Bool, + public func save(isIgnoreCustomObjectIdConfig: Bool = false, + createWithCustomObjectId: Bool = false, options: API.Options = []) throws -> Self { var childObjects: [String: PointerType]? var childFiles: [UUID: ParseFile]? @@ -852,7 +855,7 @@ extension ParseUser { throw error } - let result: Self = try saveCommand(isIgnoreCustomObjectIdConfig: isIgnoreCustomObjectIdConfig) + let result: Self = try saveCommand(isIgnoreCustomObjectIdConfig: isIgnoreCustomObjectIdConfig, createWithCustomObjectId: createWithCustomObjectId) .execute(options: options, callbackQueue: .main, childObjects: childObjects, @@ -867,6 +870,8 @@ extension ParseUser { - parameter isIgnoreCustomObjectIdConfig: Ignore checking for `objectId` when `ParseConfiguration.allowCustomObjectId = true` to allow for mixed `objectId` environments. Defaults to false. + - parameter createWithCustomObjectId: Ignore checking `ParseConfiguration.allowCustomObjectId = true` + and try to create object with given custom `objectId` to allow for mixed `objectId` environments. Defaults to false. - 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. @@ -884,6 +889,7 @@ extension ParseUser { */ public func save( isIgnoreCustomObjectIdConfig: Bool = false, + createWithCustomObjectId: Bool = false, options: API.Options = [], callbackQueue: DispatchQueue = .main, completion: @escaping (Result) -> Void @@ -893,7 +899,7 @@ extension ParseUser { self.ensureDeepSave(options: options) { (savedChildObjects, savedChildFiles, error) in guard let parseError = error else { do { - try self.saveCommand(isIgnoreCustomObjectIdConfig: isIgnoreCustomObjectIdConfig) + try self.saveCommand(isIgnoreCustomObjectIdConfig: isIgnoreCustomObjectIdConfig, createWithCustomObjectId: createWithCustomObjectId) .executeAsync(options: options, callbackQueue: callbackQueue, childObjects: savedChildObjects, @@ -922,11 +928,11 @@ extension ParseUser { } } - func saveCommand(isIgnoreCustomObjectIdConfig: Bool = false) throws -> API.Command { - if ParseSwift.configuration.allowCustomObjectId && objectId == nil && !isIgnoreCustomObjectIdConfig { + func saveCommand(isIgnoreCustomObjectIdConfig: Bool = false, createWithCustomObjectId: Bool = false) throws -> API.Command { + if (ParseSwift.configuration.allowCustomObjectId && objectId == nil && !isIgnoreCustomObjectIdConfig) || (objectId == nil && createWithCustomObjectId) { throw ParseError(code: .missingObjectId, message: "objectId must not be nil") } - if isSaved { + if isSaved && !createWithCustomObjectId { return updateCommand() } return createCommand() @@ -1045,6 +1051,8 @@ public extension Sequence where Element: ParseUser { - parameter isIgnoreCustomObjectIdConfig: Ignore checking for `objectId` when `ParseConfiguration.allowCustomObjectId = true` to allow for mixed `objectId` environments. Defaults to false. + - parameter createWithCustomObjectId: Ignore checking `ParseConfiguration.allowCustomObjectId = true` + and try to create object with given custom `objectId` to allow for mixed `objectId` environments. Defaults to false. - 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. @@ -1066,6 +1074,7 @@ public extension Sequence where Element: ParseUser { func saveAll(batchLimit limit: Int? = nil, // swiftlint:disable:this function_body_length transaction: Bool = false, isIgnoreCustomObjectIdConfig: Bool = false, + createWithCustomObjectId: Bool = false, options: API.Options = []) throws -> [(Result)] { var childObjects = [String: PointerType]() var childFiles = [UUID: ParseFile]() @@ -1113,7 +1122,7 @@ public extension Sequence where Element: ParseUser { var returnBatch = [(Result)]() let commands = try map { - try $0.saveCommand(isIgnoreCustomObjectIdConfig: isIgnoreCustomObjectIdConfig) + try $0.saveCommand(isIgnoreCustomObjectIdConfig: isIgnoreCustomObjectIdConfig, createWithCustomObjectId: createWithCustomObjectId) } let batchLimit: Int! if transaction { @@ -1145,6 +1154,8 @@ public extension Sequence where Element: ParseUser { - parameter isIgnoreCustomObjectIdConfig: Ignore checking for `objectId` when `ParseConfiguration.allowCustomObjectId = true` to allow for mixed `objectId` environments. Defaults to false. + - parameter createWithCustomObjectId: Ignore checking `ParseConfiguration.allowCustomObjectId = true` + and try to create object with given custom `objectId` to allow for mixed `objectId` environments. Defaults to false. - 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. @@ -1167,6 +1178,7 @@ public extension Sequence where Element: ParseUser { batchLimit limit: Int? = nil, transaction: Bool = false, isIgnoreCustomObjectIdConfig: Bool = false, + createWithCustomObjectId: Bool = false, options: API.Options = [], callbackQueue: DispatchQueue = .main, completion: @escaping (Result<[(Result)], ParseError>) -> Void @@ -1229,7 +1241,7 @@ public extension Sequence where Element: ParseUser { do { var returnBatch = [(Result)]() let commands = try map { - try $0.saveCommand(isIgnoreCustomObjectIdConfig: isIgnoreCustomObjectIdConfig) + try $0.saveCommand(isIgnoreCustomObjectIdConfig: isIgnoreCustomObjectIdConfig, createWithCustomObjectId: createWithCustomObjectId) } let batchLimit: Int! if transaction { diff --git a/Tests/ParseSwiftTests/ParseObjectCustomObjectIdTests.swift b/Tests/ParseSwiftTests/ParseObjectCustomObjectIdTests.swift index c3c498613..5998a385e 100644 --- a/Tests/ParseSwiftTests/ParseObjectCustomObjectIdTests.swift +++ b/Tests/ParseSwiftTests/ParseObjectCustomObjectIdTests.swift @@ -436,12 +436,34 @@ class ParseObjectCustomObjectIdTests: XCTestCase { // swiftlint:disable:this typ let score = GameScore(score: 10) _ = try score.saveCommand(isIgnoreCustomObjectIdConfig: true) } + + func testSaveCommandNoObjectIdCreateWithCustomId() throws { + let score = GameScore(score: 10) + _ = try score.saveCommand(createWithCustomObjectId: true) + } + + func testSaveCommandNoObjectIdIgnoreConfigCreateWithCustomId() throws { + let score = GameScore(score: 10) + _ = try score.saveCommand(isIgnoreCustomObjectIdConfig: true, createWithCustomObjectId: true) + } func testUpdateCommandNoObjectId() throws { var score = GameScore(score: 10) score.createdAt = Date() XCTAssertThrowsError(try score.saveCommand()) } + + func testUpdateCommandNoObjectIdCreateWithCustomId() throws { + var score = GameScore(score: 10) + score.createdAt = Date() + _ = try score.saveCommand(createWithCustomObjectId: true) + } + + func testUpdateCommandNoObjectIdIgnoreConfigCreateWithCustomId() throws { + var score = GameScore(score: 10) + score.createdAt = Date() + _ = try score.saveCommand(isIgnoreCustomObjectIdConfig: true, createWithCustomObjectId: true) + } func testUpdateCommandNoObjectIdIgnoreConfig() throws { var score = GameScore(score: 10) @@ -474,6 +496,16 @@ class ParseObjectCustomObjectIdTests: XCTestCase { // swiftlint:disable:this typ let user = User() _ = try user.saveCommand(isIgnoreCustomObjectIdConfig: true) } + + func testUserSaveCommandNoObjectIdCreateWithCustomId() throws { + let user = User() + _ = try user.saveCommand(createWithCustomObjectId: true) + } + + func testUserSaveCommandNoObjectIdIgnoreConfigCreateWithCustomId() throws { + let user = User() + _ = try user.saveCommand(isIgnoreCustomObjectIdConfig: true, createWithCustomObjectId: true) + } func testUserUpdateCommandNoObjectId() throws { var user = User() @@ -486,6 +518,18 @@ class ParseObjectCustomObjectIdTests: XCTestCase { // swiftlint:disable:this typ user.createdAt = Date() _ = try user.saveCommand(isIgnoreCustomObjectIdConfig: true) } + + func testUserUpdateCommandNoObjectIdCreateWithCustomId() throws { + var user = User() + user.createdAt = Date() + _ = try user.saveCommand(createWithCustomObjectId: true) + } + + func testUserUpdateCommandNoObjectIdIgnoreConfigCreateWithCustomId() throws { + var user = User() + user.createdAt = Date() + _ = try user.saveCommand(isIgnoreCustomObjectIdConfig: true, createWithCustomObjectId: true) + } func testUserSaveAllNoObjectIdCommand() throws { let user = User() @@ -512,6 +556,16 @@ class ParseObjectCustomObjectIdTests: XCTestCase { // swiftlint:disable:this typ let installation = Installation() _ = try installation.saveCommand(isIgnoreCustomObjectIdConfig: true) } + + func testInstallationSaveCommandNoObjectIdCreateWithCustomId() throws { + let installation = Installation() + _ = try installation.saveCommand(createWithCustomObjectId: true) + } + + func testInstallationSaveCommandNoObjectIdIgnoreConfigCreateWithCustomId() throws { + let installation = Installation() + _ = try installation.saveCommand(isIgnoreCustomObjectIdConfig: true, createWithCustomObjectId: true) + } func testInstallationUpdateCommandNoObjectId() throws { var installation = Installation() @@ -524,6 +578,18 @@ class ParseObjectCustomObjectIdTests: XCTestCase { // swiftlint:disable:this typ installation.createdAt = Date() _ = try installation.saveCommand(isIgnoreCustomObjectIdConfig: true) } + + func testInstallationUpdateCommandNoObjectIdCreateWithCustomId() throws { + var installation = Installation() + installation.createdAt = Date() + _ = try installation.saveCommand(createWithCustomObjectId: true) + } + + func testInstallationUpdateCommandNoObjectIdIgnoreConfigCreateWithCustomId() throws { + var installation = Installation() + installation.createdAt = Date() + _ = try installation.saveCommand(isIgnoreCustomObjectIdConfig: true, createWithCustomObjectId: true) + } func testInstallationSaveAllNoObjectIdCommand() throws { let installation = Installation() @@ -603,6 +669,66 @@ class ParseObjectCustomObjectIdTests: XCTestCase { // swiftlint:disable:this typ XCTFail(error.localizedDescription) } } + + func testSaveNoObjectIdCreateWithCustomId() { // swiftlint:disable:this function_body_length + let score = GameScore(score: 10) + + var scoreOnServer = score + scoreOnServer.objectId = "yarr" + scoreOnServer.createdAt = Date() + scoreOnServer.updatedAt = scoreOnServer.createdAt + + let encoded: Data! + do { + encoded = try ParseCoding.jsonEncoder().encode(scoreOnServer) + //Get dates in correct format from ParseDecoding strategy + scoreOnServer = try scoreOnServer.getDecoder().decode(GameScore.self, from: encoded) + } catch { + XCTFail("Should encode/decode. Error \(error)") + return + } + + MockURLProtocol.mockRequests { _ in + return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) + } + do { + let saved = try score.save(createWithCustomObjectId: true) + //objectId should be generated by server and new object created + XCTAssert(!saved.hasSameObjectId(as: scoreOnServer)) + } catch { + XCTFail(error.localizedDescription) + } + } + + func testSaveNoObjectIdIgnoreConfigCreateWithCustomId() { // swiftlint:disable:this function_body_length + let score = GameScore(score: 10) + + var scoreOnServer = score + scoreOnServer.objectId = "yarr" + scoreOnServer.createdAt = Date() + scoreOnServer.updatedAt = scoreOnServer.createdAt + + let encoded: Data! + do { + encoded = try ParseCoding.jsonEncoder().encode(scoreOnServer) + //Get dates in correct format from ParseDecoding strategy + scoreOnServer = try scoreOnServer.getDecoder().decode(GameScore.self, from: encoded) + } catch { + XCTFail("Should encode/decode. Error \(error)") + return + } + + MockURLProtocol.mockRequests { _ in + return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) + } + do { + let saved = try score.save(isIgnoreCustomObjectIdConfig: true, createWithCustomObjectId: true) + //objectId should be generated by server and new object created + XCTAssert(!saved.hasSameObjectId(as: scoreOnServer)) + } catch { + XCTFail(error.localizedDescription) + } + } func testUpdate() { var score = GameScore(score: 10) @@ -671,16 +797,82 @@ class ParseObjectCustomObjectIdTests: XCTestCase { // swiftlint:disable:this typ XCTFail(error.localizedDescription) } } + + func testUpdateNoObjectIdCreateWithCustomId() { + var score = GameScore(score: 10) + score.createdAt = Calendar.current.date(byAdding: .init(day: -1), to: Date()) + score.updatedAt = Calendar.current.date(byAdding: .init(day: -1), to: Date()) + score.ACL = nil + + var scoreOnServer = score + scoreOnServer.objectId = "yarr" + scoreOnServer.updatedAt = Date() + + let encoded: Data! + do { + encoded = try ParseCoding.jsonEncoder().encode(scoreOnServer) + //Get dates in correct format from ParseDecoding strategy + scoreOnServer = try scoreOnServer.getDecoder().decode(GameScore.self, from: encoded) + } catch { + XCTFail("Should encode/decode. Error \(error)") + return + } + + MockURLProtocol.mockRequests { _ in + return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) + } + do { + let saved = try score.save(createWithCustomObjectId: true) + //objectId should be generated by server and new object created + XCTAssertTrue(!saved.hasSameObjectId(as: scoreOnServer)) + } catch { + XCTFail(error.localizedDescription) + } + } + + func testUpdateNoObjectIdIgnoreConfigCreateWithCustomId() { + var score = GameScore(score: 10) + score.createdAt = Calendar.current.date(byAdding: .init(day: -1), to: Date()) + score.updatedAt = Calendar.current.date(byAdding: .init(day: -1), to: Date()) + score.ACL = nil + + var scoreOnServer = score + scoreOnServer.objectId = "yarr" + scoreOnServer.updatedAt = Date() + + let encoded: Data! + do { + encoded = try ParseCoding.jsonEncoder().encode(scoreOnServer) + //Get dates in correct format from ParseDecoding strategy + scoreOnServer = try scoreOnServer.getDecoder().decode(GameScore.self, from: encoded) + } catch { + XCTFail("Should encode/decode. Error \(error)") + return + } + + MockURLProtocol.mockRequests { _ in + return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) + } + do { + let saved = try score.save(isIgnoreCustomObjectIdConfig: true, createWithCustomObjectId: true) + //objectId should be generated by server and new object created + XCTAssertTrue(!saved.hasSameObjectId(as: scoreOnServer)) + } catch { + XCTFail(error.localizedDescription) + } + } // swiftlint:disable:next function_body_length func saveAsync(score: GameScore, scoreOnServer: GameScore, callbackQueue: DispatchQueue, - isIgnoreCustomObjectIdConfig: Bool = false) { + isIgnoreCustomObjectIdConfig: Bool = false, + createWithCustomObjectId: Bool = false) { let expectation1 = XCTestExpectation(description: "Save object1") score.save(isIgnoreCustomObjectIdConfig: isIgnoreCustomObjectIdConfig, + createWithCustomObjectId: createWithCustomObjectId, options: [], callbackQueue: callbackQueue) { result in @@ -777,15 +969,72 @@ class ParseObjectCustomObjectIdTests: XCTestCase { // swiftlint:disable:this typ callbackQueue: .main, isIgnoreCustomObjectIdConfig: true) } + + func testSaveNoObjectIdCreateWithCustomIdAsyncMainQueue() { + let score = GameScore(score: 10) + + var scoreOnServer = score + scoreOnServer.objectId = "yarr" + scoreOnServer.createdAt = Date() + scoreOnServer.updatedAt = scoreOnServer.createdAt + scoreOnServer.ACL = nil + let encoded: Data! + do { + encoded = try ParseCoding.jsonEncoder().encode(scoreOnServer) + //Get dates in correct format from ParseDecoding strategy + scoreOnServer = try scoreOnServer.getDecoder().decode(GameScore.self, from: encoded) + } catch { + XCTFail("Should have encoded/decoded: Error: \(error)") + return + } + MockURLProtocol.mockRequests { _ in + return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) + } + + self.saveAsync(score: score, + scoreOnServer: scoreOnServer, + callbackQueue: .main, + createWithCustomObjectId: true) + } + + func testSaveNoObjectIdIgnoreConfigCreateWithCustomIdAsyncMainQueue() { + let score = GameScore(score: 10) + + var scoreOnServer = score + scoreOnServer.objectId = "yarr" + scoreOnServer.createdAt = Date() + scoreOnServer.updatedAt = scoreOnServer.createdAt + scoreOnServer.ACL = nil + let encoded: Data! + do { + encoded = try ParseCoding.jsonEncoder().encode(scoreOnServer) + //Get dates in correct format from ParseDecoding strategy + scoreOnServer = try scoreOnServer.getDecoder().decode(GameScore.self, from: encoded) + } catch { + XCTFail("Should have encoded/decoded: Error: \(error)") + return + } + MockURLProtocol.mockRequests { _ in + return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) + } + + self.saveAsync(score: score, + scoreOnServer: scoreOnServer, + callbackQueue: .main, + isIgnoreCustomObjectIdConfig: true, + createWithCustomObjectId: true) + } func updateAsync(score: GameScore, scoreOnServer: GameScore, isIgnoreCustomObjectIdConfig: Bool = false, + createWithCustomObjectId: Bool = false, callbackQueue: DispatchQueue) { let expectation1 = XCTestExpectation(description: "Update object1") score.save(isIgnoreCustomObjectIdConfig: isIgnoreCustomObjectIdConfig, + createWithCustomObjectId: createWithCustomObjectId, options: [], callbackQueue: callbackQueue) { result in @@ -883,6 +1132,61 @@ class ParseObjectCustomObjectIdTests: XCTestCase { // swiftlint:disable:this typ isIgnoreCustomObjectIdConfig: true, callbackQueue: .main) } + + func testUpdateNoObjectIdCreateWithCustomIdAsyncMainQueue() { + var score = GameScore(score: 10) + score.createdAt = Calendar.current.date(byAdding: .init(day: -1), to: Date()) + score.updatedAt = Calendar.current.date(byAdding: .init(day: -1), to: Date()) + score.ACL = nil + + var scoreOnServer = score + scoreOnServer.objectId = "yarr" + scoreOnServer.updatedAt = Date() + let encoded: Data! + do { + encoded = try ParseCoding.jsonEncoder().encode(scoreOnServer) + //Get dates in correct format from ParseDecoding strategy + scoreOnServer = try scoreOnServer.getDecoder().decode(GameScore.self, from: encoded) + } catch { + XCTFail("Should have encoded/decoded: Error: \(error)") + return + } + MockURLProtocol.mockRequests { _ in + return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) + } + self.updateAsync(score: score, + scoreOnServer: scoreOnServer, + createWithCustomObjectId: true, + callbackQueue: .main) + } + + func testUpdateNoObjectIdIgnoreConfigCreateWithCustomIdAsyncMainQueue() { + var score = GameScore(score: 10) + score.createdAt = Calendar.current.date(byAdding: .init(day: -1), to: Date()) + score.updatedAt = Calendar.current.date(byAdding: .init(day: -1), to: Date()) + score.ACL = nil + + var scoreOnServer = score + scoreOnServer.objectId = "yarr" + scoreOnServer.updatedAt = Date() + let encoded: Data! + do { + encoded = try ParseCoding.jsonEncoder().encode(scoreOnServer) + //Get dates in correct format from ParseDecoding strategy + scoreOnServer = try scoreOnServer.getDecoder().decode(GameScore.self, from: encoded) + } catch { + XCTFail("Should have encoded/decoded: Error: \(error)") + return + } + MockURLProtocol.mockRequests { _ in + return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) + } + self.updateAsync(score: score, + scoreOnServer: scoreOnServer, + isIgnoreCustomObjectIdConfig: true, + createWithCustomObjectId: true, + callbackQueue: .main) + } func testSaveAll() { // swiftlint:disable:this function_body_length cyclomatic_complexity var score = GameScore(score: 10) @@ -944,6 +1248,67 @@ class ParseObjectCustomObjectIdTests: XCTestCase { // swiftlint:disable:this typ XCTFail(error.localizedDescription) } } + + func testSaveAllCreateWithCustomId() { // swiftlint:disable:this function_body_length cyclomatic_complexity + var score = GameScore(score: 10) + score.objectId = "yarr" + var score2 = GameScore(score: 20) + score2.objectId = "yolo" + + var scoreOnServer = score + scoreOnServer.createdAt = Date() + scoreOnServer.updatedAt = scoreOnServer.createdAt + scoreOnServer.ACL = nil + + var scoreOnServer2 = score2 + scoreOnServer2.createdAt = Calendar.current.date(byAdding: .init(day: -1), to: Date()) + scoreOnServer2.updatedAt = scoreOnServer2.createdAt + scoreOnServer2.ACL = nil + + let response = [BatchResponseItem(success: scoreOnServer, error: nil), + BatchResponseItem(success: scoreOnServer2, error: nil)] + let encoded: Data! + do { + encoded = try scoreOnServer.getJSONEncoder().encode(response) + //Get dates in correct format from ParseDecoding strategy + let encoded1 = try ParseCoding.jsonEncoder().encode(scoreOnServer) + scoreOnServer = try scoreOnServer.getDecoder().decode(GameScore.self, from: encoded1) + let encoded2 = try ParseCoding.jsonEncoder().encode(scoreOnServer2) + scoreOnServer2 = try scoreOnServer.getDecoder().decode(GameScore.self, from: encoded2) + + } catch { + XCTFail("Should have encoded/decoded. Error \(error)") + return + } + MockURLProtocol.mockRequests { _ in + return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) + } + + do { + + let saved = try [score, score2].saveAll(createWithCustomObjectId: true) + + XCTAssertEqual(saved.count, 0) + switch saved[0] { + + case .success(let first): + XCTFail("Duplicate error should be thrown") + case .failure(let error): + XCTAssert(error.localizedDescription.contains("objectId must not be nil")) + } + + switch saved[1] { + + case .success(let second): + XCTFail("Duplicate error should be thrown") + case .failure(let error): + XCTAssert(error.localizedDescription.contains("objectId must not be nil")) + } + + } catch { + XCTFail(error.localizedDescription) + } + } func testSaveAllNoObjectId() throws { let score = GameScore(score: 10) @@ -1011,6 +1376,36 @@ class ParseObjectCustomObjectIdTests: XCTestCase { // swiftlint:disable:this typ XCTFail(error.localizedDescription) } } + + func testSaveAllNoObjectIdCreateWithCustomId() { // swiftlint:disable:this function_body_length cyclomatic_complexity + let score = GameScore(score: 10) + let score2 = GameScore(score: 20) + + do { + + let saved = try [score, score2].saveAll(createWithCustomObjectId: true) + + XCTAssertEqual(saved.count, 0) + switch saved[0] { + + case .success(let first): + XCTFail("ObjectId should not be nil before save") + case .failure(let error): + XCTAssert(error.localizedDescription.contains("objectId must not be nil")) + } + + switch saved[1] { + + case .success(let second): + XCTFail("ObjectId should not be nil before save") + case .failure(let error): + XCTAssert(error.localizedDescription.contains("objectId must not be nil")) + } + + } catch { + XCTFail(error.localizedDescription) + } + } func testSaveAllNoObjectIdAsync() throws { let score = GameScore(score: 10) @@ -1216,6 +1611,12 @@ class ParseObjectCustomObjectIdTests: XCTestCase { // swiftlint:disable:this typ user.createdAt = Date() XCTAssertThrowsError(try user.save()) } + + func testUserUpdateNoObjectIdCreateWithCustomId() throws { + var user = User() + user.createdAt = Date() + XCTAssertThrowsError(try user.save(createWithCustomObjectId: true)) + } func testUserUpdateNoObjectIdIgnoreConfig() { var user = User() @@ -1251,11 +1652,13 @@ class ParseObjectCustomObjectIdTests: XCTestCase { // swiftlint:disable:this typ // swiftlint:disable:next function_body_length func saveUserAsync(user: User, userOnServer: User, isIgnoreCustomObjectIdConfig: Bool = false, + createWithCustomObjectId: Bool = false, callbackQueue: DispatchQueue) { let expectation1 = XCTestExpectation(description: "Update object1") user.save(isIgnoreCustomObjectIdConfig: isIgnoreCustomObjectIdConfig, + createWithCustomObjectId: createWithCustomObjectId, options: [], callbackQueue: callbackQueue) { result in @@ -1335,6 +1738,32 @@ class ParseObjectCustomObjectIdTests: XCTestCase { // swiftlint:disable:this typ isIgnoreCustomObjectIdConfig: true, callbackQueue: .main) } + + func testUserSaveNoObjectIdCreateWithCustomIdAsyncMainQueue() { + var user = User() + user.ACL = nil + + var userOnServer = user + userOnServer.objectId = "yarr" + userOnServer.createdAt = Calendar.current.date(byAdding: .init(day: -1), to: Date()) + userOnServer.updatedAt = Calendar.current.date(byAdding: .init(day: -1), to: Date()) + let encoded: Data! + do { + encoded = try ParseCoding.jsonEncoder().encode(userOnServer) + //Get dates in correct format from ParseDecoding strategy + userOnServer = try userOnServer.getDecoder().decode(User.self, from: encoded) + } catch { + XCTFail("Should have encoded/decoded: Error: \(error)") + return + } + MockURLProtocol.mockRequests { _ in + return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) + } + XCTAssertThrowsError(self.saveUserAsync(user: user, + userOnServer: userOnServer, + createWithCustomObjectId: true, + callbackQueue: .main)) + } func updateUserAsync(user: User, userOnServer: User, isIgnoreCustomObjectIdConfig: Bool = false, @@ -1611,6 +2040,39 @@ class ParseObjectCustomObjectIdTests: XCTestCase { // swiftlint:disable:this typ XCTFail(error.localizedDescription) } } + + // swiftlint:disable:next function_body_length cyclomatic_complexity + func testUserUpdateAllNoObjectIdCreateWithCustomId() { + var user = User() + user.createdAt = Date() + var user2 = User() + user2.createdAt = Calendar.current.date(byAdding: .init(day: -1), to: Date()) + + do { + + let saved = try [user, user2].saveAll(createWithCustomObjectId: true) + + XCTAssertEqual(saved.count, 2) + switch saved[0] { + + case .success(let first): + XCTFail("ObjectId should not be nil before save") + case .failure(let error): + XCTAssert(error.localizedDescription.contains("objectId must not be nil")) + } + + switch saved[1] { + + case .success(let second): + XCTFail("ObjectId should not be nil before save") + case .failure(let error): + XCTAssert(error.localizedDescription.contains("objectId must not be nil")) + } + + } catch { + XCTFail(error.localizedDescription) + } + } func testUserUpdateAllNoObjectIdAsync() throws { var user = User() @@ -1664,7 +2126,12 @@ class ParseObjectCustomObjectIdTests: XCTestCase { // swiftlint:disable:this typ let score = GameScore(score: 10) XCTAssertThrowsError(try score.save()) } - + + func testInstallationSaveNoObjectIdCreateWithCustomId() throws { + let score = GameScore(score: 10) + XCTAssertThrowsError(try score.save(createWithCustomObjectId: true)) + } + func testInstallationSaveNoObjectIdIgnoreConfig() { // swiftlint:disable:this function_body_length var installation = Installation() installation.ACL = nil @@ -1762,6 +2229,19 @@ class ParseObjectCustomObjectIdTests: XCTestCase { // swiftlint:disable:this typ XCTFail(error.localizedDescription) } } + + func testInstallationUpdateNoObjectIdCreateWithCustomId() { + var installation = Installation() + installation.createdAt = Calendar.current.date(byAdding: .init(day: -1), to: Date()) + installation.updatedAt = Calendar.current.date(byAdding: .init(day: -1), to: Date()) + installation.ACL = nil + + do { + XCTAssertThrowsError(try installation.save(createWithCustomObjectId: true)) + } catch { + XCTFail(error.localizedDescription) + } + } // swiftlint:disable:next function_body_length func saveInstallationAsync(installation: Installation, @@ -2037,6 +2517,39 @@ class ParseObjectCustomObjectIdTests: XCTestCase { // swiftlint:disable:this typ XCTFail(error.localizedDescription) } } + + func testInstallationSaveAllCreateWithCustomId() { // swiftlint:disable:this function_body_length cyclomatic_complexity + var installation = Installation() + installation.objectId = "yarr" + + var installation2 = Installation() + installation2.objectId = "yolo" + + do { + + let saved = try [installation, installation2].saveAll(createWithCustomObjectId: true) + + XCTAssertEqual(saved.count, 2) + switch saved[0] { + + case .success(let first): + XCTAssert(first.hasSameObjectId(as: installation)) + case .failure(let error): + XCTFail(error.localizedDescription) + } + + switch saved[1] { + + case .success(let second): + XCTAssert(second.hasSameObjectId(as: installation2)) + case .failure(let error): + XCTFail(error.localizedDescription) + } + + } catch { + XCTFail(error.localizedDescription) + } + } func testInstallationSaveAllNoObjectId() throws { let installation = Installation() @@ -2195,6 +2708,67 @@ class ParseObjectCustomObjectIdTests: XCTestCase { // swiftlint:disable:this typ XCTFail(error.localizedDescription) } } + + func testInstallationUpdateAllCreateWithCustomId() { // swiftlint:disable:this function_body_length cyclomatic_complexity + var installation = Installation() + installation.objectId = "yarr" + installation.createdAt = Date() + var installation2 = Installation() + installation2.objectId = "yolo" + installation2.createdAt = Calendar.current.date(byAdding: .init(day: -1), to: Date()) + + var installationOnServer = installation + installationOnServer.updatedAt = installationOnServer.createdAt + installationOnServer.ACL = nil + + var installationOnServer2 = installation2 + installationOnServer2.updatedAt = installationOnServer2.createdAt + installationOnServer2.ACL = nil + + let response = [BatchResponseItem(success: installationOnServer, error: nil), + BatchResponseItem(success: installationOnServer2, error: nil)] + let encoded: Data! + do { + encoded = try installationOnServer.getJSONEncoder().encode(response) + //Get dates in correct format from ParseDecoding strategy + let encoded1 = try ParseCoding.jsonEncoder().encode(installationOnServer) + installationOnServer = try installationOnServer.getDecoder().decode(Installation.self, from: encoded1) + let encoded2 = try ParseCoding.jsonEncoder().encode(installationOnServer2) + installationOnServer2 = try installationOnServer.getDecoder().decode(Installation.self, from: encoded2) + + } catch { + XCTFail("Should have encoded/decoded. Error \(error)") + return + } + MockURLProtocol.mockRequests { _ in + return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) + } + + do { + + let saved = try [installation, installation2].saveAll(createWithCustomObjectId: true) + + XCTAssertEqual(saved.count, 0) + switch saved[0] { + + case .success(let first): + XCTFail("Duplicate error should be thrown") + case .failure(let error): + XCTAssert(error.localizedDescription.contains("objectId must not be nil")) + } + + switch saved[1] { + + case .success(let second): + XCTFail("Duplicate error should be thrown") + case .failure(let error): + XCTAssert(error.localizedDescription.contains("objectId must not be nil")) + } + + } catch { + XCTFail(error.localizedDescription) + } + } func testInstallationUpdateAllNoObjectId() throws { var installation = Installation()