Skip to content

Commit c371d5d

Browse files
authored
fix: ParseFile id creation should be consistent to ensure proper hashing (#83)
* fix: ParseFile id creation should be consistent to ensure proper hashing * nit * uniqueness with data * don't throw circular dependency error on duplicate file * removed unused uniqueFiles property
1 parent d1feb04 commit c371d5d

13 files changed

+113
-60
lines changed

CHANGELOG.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,15 @@
22
# Parse-Swift Changelog
33

44
### main
5-
[Full Changelog](https://github.com/netreconlab/Parse-Swift/compare/5.3.1...main), [Documentation](https://swiftpackageindex.com/netreconlab/Parse-Swift/main/documentation/parseswift)
5+
[Full Changelog](https://github.com/netreconlab/Parse-Swift/compare/5.3.2...main), [Documentation](https://swiftpackageindex.com/netreconlab/Parse-Swift/main/documentation/parseswift)
66
* _Contributing to this repo? Add info about your change here to be included in the next release_
77

8+
### 5.3.2
9+
[Full Changelog](https://github.com/netreconlab/Parse-Swift/compare/5.3.1...5.3.2), [Documentation](https://swiftpackageindex.com/netreconlab/Parse-Swift/5.3.2/documentation/parseswift)
10+
11+
__Fixes__
12+
* ParseFile id creation should be consistent to ensure proper hashing ([#83](https://github.com/netreconlab/Parse-Swift/pull/83)), thanks to [Corey Baker](https://github.com/cbaker6).
13+
814
### 5.3.1
915
[Full Changelog](https://github.com/netreconlab/Parse-Swift/compare/5.3.0...5.3.1), [Documentation](https://swiftpackageindex.com/netreconlab/Parse-Swift/5.3.1/documentation/parseswift)
1016

Sources/ParseSwift/API/API+Command+async.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ internal extension API.Command {
1717
func prepareURLRequest(options: API.Options,
1818
batching: Bool = false,
1919
childObjects: [String: PointerType]? = nil,
20-
childFiles: [UUID: ParseFile]? = nil) async -> Result<URLRequest, ParseError> {
20+
childFiles: [String: ParseFile]? = nil) async -> Result<URLRequest, ParseError> {
2121
await withCheckedContinuation { continuation in
2222
self.prepareURLRequest(options: options,
2323
batching: batching,
@@ -33,7 +33,7 @@ internal extension API.Command {
3333
callbackQueue: DispatchQueue,
3434
notificationQueue: DispatchQueue? = nil,
3535
childObjects: [String: PointerType]? = nil,
36-
childFiles: [UUID: ParseFile]? = nil,
36+
childFiles: [String: ParseFile]? = nil,
3737
allowIntermediateResponses: Bool = false,
3838
uploadProgress: ((URLSessionTask, Int64, Int64, Int64) -> Void)? = nil,
3939
downloadProgress: ((URLSessionDownloadTask, Int64, Int64, Int64) -> Void)? = nil) async throws -> U {

Sources/ParseSwift/API/API+Command.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ internal extension API {
5353
func executeStream(options: API.Options,
5454
callbackQueue: DispatchQueue,
5555
childObjects: [String: PointerType]? = nil,
56-
childFiles: [UUID: ParseFile]? = nil,
56+
childFiles: [String: ParseFile]? = nil,
5757
uploadProgress: ((URLSessionTask, Int64, Int64, Int64) -> Void)? = nil,
5858
stream: InputStream,
5959
completion: @escaping (ParseError?) -> Void) {
@@ -94,7 +94,7 @@ internal extension API {
9494
callbackQueue: DispatchQueue,
9595
notificationQueue: DispatchQueue? = nil,
9696
childObjects: [String: PointerType]? = nil,
97-
childFiles: [UUID: ParseFile]? = nil,
97+
childFiles: [String: ParseFile]? = nil,
9898
allowIntermediateResponses: Bool = false,
9999
uploadProgress: ((URLSessionTask, Int64, Int64, Int64) -> Void)? = nil,
100100
downloadProgress: ((URLSessionDownloadTask, Int64, Int64, Int64) -> Void)? = nil,
@@ -252,7 +252,7 @@ internal extension API {
252252
func prepareURLRequest(options: API.Options,
253253
batching: Bool = false,
254254
childObjects: [String: PointerType]? = nil,
255-
childFiles: [UUID: ParseFile]? = nil,
255+
childFiles: [String: ParseFile]? = nil,
256256
completion: @escaping(Result<URLRequest, ParseError>) -> Void) {
257257
let params = self.params?.getURLQueryItems()
258258
Task {

Sources/ParseSwift/API/API+NonParseBodyCommand.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ internal extension API.NonParseBodyCommand {
168168
transaction: Bool,
169169
objectsSavedBeforeThisOne: [String: PointerType]?,
170170
// swiftlint:disable:next line_length
171-
filesSavedBeforeThisOne: [UUID: ParseFile]?) async throws -> RESTBatchCommandTypeEncodablePointer<AnyCodable> {
171+
filesSavedBeforeThisOne: [String: ParseFile]?) async throws -> RESTBatchCommandTypeEncodablePointer<AnyCodable> {
172172
let defaultACL = try? await ParseACL.defaultACL()
173173
let batchCommands = try objects.compactMap { (object) -> API.BatchCommand<AnyCodable, PointerType>? in
174174
guard var objectable = object as? Objectable else {

Sources/ParseSwift/Coding/ParseEncoder.swift

Lines changed: 12 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ public struct ParseEncoder {
110110
acl: ParseACL? = nil,
111111
batching: Bool = false,
112112
objectsSavedBeforeThisOne: [String: PointerType]? = nil,
113-
filesSavedBeforeThisOne: [UUID: ParseFile]? = nil) throws -> Data {
113+
filesSavedBeforeThisOne: [String: ParseFile]? = nil) throws -> Data {
114114
var keysToSkip = SkipKeys.none.keys()
115115
if batching {
116116
keysToSkip = SkipKeys.object.keys()
@@ -159,9 +159,9 @@ public struct ParseEncoder {
159159
acl: ParseACL? = nil,
160160
collectChildren: Bool,
161161
objectsSavedBeforeThisOne: [String: PointerType]?,
162-
filesSavedBeforeThisOne: [UUID: ParseFile]?) throws -> (encoded: Data,
163-
unique: PointerType?,
164-
unsavedChildren: [Encodable]) {
162+
filesSavedBeforeThisOne: [String: ParseFile]?) throws -> (encoded: Data,
163+
unique: PointerType?,
164+
unsavedChildren: [Encodable]) {
165165
let keysToSkip: Set<String>!
166166
if !Parse.configuration.isRequiringCustomObjectIds {
167167
keysToSkip = SkipKeys.object.keys()
@@ -188,7 +188,7 @@ public struct ParseEncoder {
188188
batching: Bool,
189189
collectChildren: Bool,
190190
objectsSavedBeforeThisOne: [String: PointerType]?,
191-
filesSavedBeforeThisOne: [UUID: ParseFile]?) throws -> (encoded: Data, unique: PointerType?, unsavedChildren: [Encodable]) {
191+
filesSavedBeforeThisOne: [String: ParseFile]?) throws -> (encoded: Data, unique: PointerType?, unsavedChildren: [Encodable]) {
192192
let keysToSkip: Set<String>!
193193
if !Parse.configuration.isRequiringCustomObjectIds {
194194
keysToSkip = SkipKeys.object.keys()
@@ -218,12 +218,11 @@ internal class _ParseEncoder: JSONEncoder, Encoder {
218218
let dictionary: NSMutableDictionary
219219
let skippedKeys: Set<String>
220220
var uniquePointer: PointerType?
221-
var uniqueFiles = Set<ParseFile>()
222221
var newObjects = [Encodable]()
223222
var collectChildren = false
224223
var batching = false
225224
var objectsSavedBeforeThisOne: [String: PointerType]?
226-
var filesSavedBeforeThisOne: [UUID: ParseFile]?
225+
var filesSavedBeforeThisOne: [String: ParseFile]?
227226
/// The encoder's storage.
228227
var storage: _ParseEncodingStorage
229228
var ignoreSkipKeys = false
@@ -280,7 +279,7 @@ internal class _ParseEncoder: JSONEncoder, Encoder {
280279
collectChildren: Bool,
281280
uniquePointer: PointerType?,
282281
objectsSavedBeforeThisOne: [String: PointerType]?,
283-
filesSavedBeforeThisOne: [UUID: ParseFile]?) throws -> (encoded: Data, unique: PointerType?, unsavedChildren: [Encodable]) {
282+
filesSavedBeforeThisOne: [String: ParseFile]?) throws -> (encoded: Data, unique: PointerType?, unsavedChildren: [Encodable]) {
284283
self.acl = acl
285284
let encoder = _ParseEncoder(codingPath: codingPath, dictionary: dictionary, skippingKeys: skippedKeys)
286285
encoder.outputFormatting = outputFormatting
@@ -364,7 +363,7 @@ internal class _ParseEncoder: JSONEncoder, Encoder {
364363
if let uniquePointer = self.uniquePointer,
365364
uniquePointer.hasSameObjectId(as: pointer) {
366365
throw ParseError(code: .otherCause,
367-
message: "Found a circular dependency when encoding.")
366+
message: "Found a circular dependency when encoding objects. The object: \(pointer) cannot have the same objectId as: \(uniquePointer)")
368367
}
369368
valueToEncode = pointer
370369
} else if let object = value as? Objectable {
@@ -373,7 +372,7 @@ internal class _ParseEncoder: JSONEncoder, Encoder {
373372
if let uniquePointer = self.uniquePointer,
374373
uniquePointer.hasSameObjectId(as: pointer) {
375374
throw ParseError(code: .otherCause,
376-
message: "Found a circular dependency when encoding.")
375+
message: "Found a circular dependency when encoding objects. The object: \(pointer) cannot have the same objectId as: \(uniquePointer)")
377376
}
378377
valueToEncode = pointer
379378
} else {
@@ -401,10 +400,6 @@ internal class _ParseEncoder: JSONEncoder, Encoder {
401400
func deepFindAndReplaceParseFiles(_ value: ParseFile) throws -> Encodable? {
402401
var valueToEncode: Encodable?
403402
if value.isSaved {
404-
if self.uniqueFiles.contains(value) {
405-
throw ParseError(code: .otherCause, message: "Found a circular dependency when encoding.")
406-
}
407-
self.uniqueFiles.insert(value)
408403
if !self.collectChildren {
409404
valueToEncode = value
410405
}
@@ -413,13 +408,12 @@ internal class _ParseEncoder: JSONEncoder, Encoder {
413408
if let updatedFile = self.filesSavedBeforeThisOne?[value.id] {
414409
valueToEncode = updatedFile
415410
} else {
416-
// New object needs to be saved before it can be stored
411+
// New file needs to be saved before it can be stored
417412
self.newObjects.append(value)
418413
}
419414
} else if let currentFile = self.filesSavedBeforeThisOne?[value.id] {
420415
valueToEncode = currentFile
421416
} else if dictionary.count > 0 {
422-
// Only top level objects can be saved without a pointer
423417
throw ParseError(code: .otherCause, message: "Error. Could not resolve unsaved file while encoding.")
424418
}
425419
}
@@ -1035,7 +1029,7 @@ private class _ParseReferencingEncoder: _ParseEncoder {
10351029
// MARK: - Initialization
10361030

10371031
/// Initializes `self` by referencing the given array container in the given encoder.
1038-
init(referencing encoder: _ParseEncoder, at index: Int, wrapping array: NSMutableArray, skippingKeys: Set<String>, collectChildren: Bool, objectsSavedBeforeThisOne: [String: PointerType]?, filesSavedBeforeThisOne: [UUID: ParseFile]?) {
1032+
init(referencing encoder: _ParseEncoder, at index: Int, wrapping array: NSMutableArray, skippingKeys: Set<String>, collectChildren: Bool, objectsSavedBeforeThisOne: [String: PointerType]?, filesSavedBeforeThisOne: [String: ParseFile]?) {
10391033
self.encoder = encoder
10401034
self.reference = .array(array, index)
10411035
super.init(codingPath: encoder.codingPath, dictionary: NSMutableDictionary(), skippingKeys: skippingKeys)
@@ -1046,7 +1040,7 @@ private class _ParseReferencingEncoder: _ParseEncoder {
10461040
}
10471041

10481042
/// Initializes `self` by referencing the given dictionary container in the given encoder.
1049-
init(referencing encoder: _ParseEncoder, key: CodingKey, wrapping dictionary: NSMutableDictionary, skippingKeys: Set<String>, collectChildren: Bool, objectsSavedBeforeThisOne: [String: PointerType]?, filesSavedBeforeThisOne: [UUID: ParseFile]?) {
1043+
init(referencing encoder: _ParseEncoder, key: CodingKey, wrapping dictionary: NSMutableDictionary, skippingKeys: Set<String>, collectChildren: Bool, objectsSavedBeforeThisOne: [String: PointerType]?, filesSavedBeforeThisOne: [String: ParseFile]?) {
10501044
self.encoder = encoder
10511045
self.reference = .dictionary(dictionary, key.stringValue)
10521046
super.init(codingPath: encoder.codingPath, dictionary: dictionary, skippingKeys: skippingKeys)

Sources/ParseSwift/Objects/ParseInstallation+async.swift

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -372,7 +372,7 @@ internal extension Sequence where Element: ParseInstallation {
372372
var options = options
373373
options.insert(.cachePolicy(.reloadIgnoringLocalCacheData))
374374
var childObjects = [String: PointerType]()
375-
var childFiles = [UUID: ParseFile]()
375+
var childFiles = [String: ParseFile]()
376376
var commands = [API.Command<Self.Element, Self.Element>]()
377377
let objects = map { $0 }
378378
for object in objects {
@@ -381,13 +381,15 @@ internal extension Sequence where Element: ParseInstallation {
381381
isShouldReturnIfChildObjectsFound: transaction)
382382
try savedChildObjects.forEach {(key, value) in
383383
guard childObjects[key] == nil else {
384-
throw ParseError(code: .otherCause, message: "circular dependency")
384+
throw ParseError(code: .otherCause,
385+
message: "Found a circular dependency in ParseInstallation.")
385386
}
386387
childObjects[key] = value
387388
}
388389
try savedChildFiles.forEach {(key, value) in
389390
guard childFiles[key] == nil else {
390-
throw ParseError(code: .otherCause, message: "circular dependency")
391+
throw ParseError(code: .otherCause,
392+
message: "Found a circular dependency in ParseInstallation.")
391393
}
392394
childFiles[key] = value
393395
}

Sources/ParseSwift/Objects/ParseObject+async.swift

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -294,15 +294,15 @@ internal extension ParseObject {
294294
// swiftlint:disable:next function_body_length
295295
func ensureDeepSave(options: API.Options = [],
296296
isShouldReturnIfChildObjectsFound: Bool = false) async throws -> ([String: PointerType],
297-
[UUID: ParseFile]) {
297+
[String: ParseFile]) {
298298

299299
var options = options
300300
// Remove any caching policy added by the developer as fresh data
301301
// from the server is needed.
302302
options.remove(.cachePolicy(.reloadIgnoringLocalCacheData))
303303
options.insert(.cachePolicy(.reloadIgnoringLocalCacheData))
304304
var objectsFinishedSaving = [String: PointerType]()
305-
var filesFinishedSaving = [UUID: ParseFile]()
305+
var filesFinishedSaving = [String: ParseFile]()
306306
let defaultACL = try? await ParseACL.defaultACL()
307307
do {
308308
let object = try ParseCoding.parseEncoder()
@@ -414,7 +414,7 @@ internal extension Sequence where Element: ParseObject {
414414
var options = options
415415
options.insert(.cachePolicy(.reloadIgnoringLocalCacheData))
416416
var childObjects = [String: PointerType]()
417-
var childFiles = [UUID: ParseFile]()
417+
var childFiles = [String: ParseFile]()
418418
var commands = [API.Command<Self.Element, Self.Element>]()
419419
let objects = map { $0 }
420420
for object in objects {
@@ -423,13 +423,15 @@ internal extension Sequence where Element: ParseObject {
423423
isShouldReturnIfChildObjectsFound: transaction)
424424
try savedChildObjects.forEach {(key, value) in
425425
guard childObjects[key] == nil else {
426-
throw ParseError(code: .otherCause, message: "circular dependency")
426+
throw ParseError(code: .otherCause,
427+
message: "Found a circular dependency in ParseObject.")
427428
}
428429
childObjects[key] = value
429430
}
430431
try savedChildFiles.forEach {(key, value) in
431432
guard childFiles[key] == nil else {
432-
throw ParseError(code: .otherCause, message: "circular dependency")
433+
throw ParseError(code: .otherCause,
434+
message: "Found a circular dependency in ParseObject.")
433435
}
434436
childFiles[key] = value
435437
}
@@ -478,7 +480,7 @@ internal extension ParseEncodable {
478480
func saveAll(objects: [ParseEncodable],
479481
transaction: Bool = configuration.isUsingTransactions,
480482
objectsSavedBeforeThisOne: [String: PointerType]?,
481-
filesSavedBeforeThisOne: [UUID: ParseFile]?,
483+
filesSavedBeforeThisOne: [String: ParseFile]?,
482484
options: API.Options = [],
483485
callbackQueue: DispatchQueue = .main) async throws -> [(Result<PointerType, ParseError>)] {
484486
try await API.NonParseBodyCommand<AnyCodable, PointerType>

Sources/ParseSwift/Objects/ParseObject.swift

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -167,9 +167,10 @@ public extension ParseObject {
167167
}
168168

169169
/**
170-
A computed property that is the same value as `objectId` and makes it easy to use `ParseObject`'s
170+
A computed property that is a unique identifier and makes it easy to use `ParseObject`'s
171171
as models in MVVM and SwiftUI.
172-
- note: `id` allows `ParseObjects`'s to be used even when they are unsaved and do not have an `objectId`.
172+
- note: `id` allows `ParseObject`'s to be used even when they are not saved and do not have an `objectId`.
173+
- important: `id` will have the same value as `objectId` when a `ParseObject` is saved.
173174
*/
174175
var id: String {
175176
objectId ?? UUID().uuidString

Sources/ParseSwift/Objects/ParseUser+async.swift

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -603,7 +603,7 @@ internal extension Sequence where Element: ParseUser {
603603
var options = options
604604
options.insert(.cachePolicy(.reloadIgnoringLocalCacheData))
605605
var childObjects = [String: PointerType]()
606-
var childFiles = [UUID: ParseFile]()
606+
var childFiles = [String: ParseFile]()
607607
var commands = [API.Command<Self.Element, Self.Element>]()
608608
let objects = map { $0 }
609609
for object in objects {
@@ -612,13 +612,15 @@ internal extension Sequence where Element: ParseUser {
612612
isShouldReturnIfChildObjectsFound: transaction)
613613
try savedChildObjects.forEach {(key, value) in
614614
guard childObjects[key] == nil else {
615-
throw ParseError(code: .otherCause, message: "circular dependency")
615+
throw ParseError(code: .otherCause,
616+
message: "Found a circular dependency in ParseUser.")
616617
}
617618
childObjects[key] = value
618619
}
619620
try savedChildFiles.forEach {(key, value) in
620621
guard childFiles[key] == nil else {
621-
throw ParseError(code: .otherCause, message: "circular dependency")
622+
throw ParseError(code: .otherCause,
623+
message: "Found a circular dependency in ParseUser.")
622624
}
623625
childFiles[key] = value
624626
}

Sources/ParseSwift/ParseConstants.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import Foundation
1010

1111
enum ParseConstants {
1212
static let sdk = "swift"
13-
static let version = "5.3.1"
13+
static let version = "5.3.2"
1414
static let fileManagementDirectory = "parse/"
1515
static let fileManagementPrivateDocumentsDirectory = "Private Documents/"
1616
static let fileManagementLibraryDirectory = "Library/"

0 commit comments

Comments
 (0)