Skip to content

Commit

Permalink
fix: ParseFile id creation should be consistent to ensure proper hash…
Browse files Browse the repository at this point in the history
…ing (#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
  • Loading branch information
cbaker6 authored Mar 17, 2023
1 parent d1feb04 commit c371d5d
Show file tree
Hide file tree
Showing 13 changed files with 113 additions and 60 deletions.
8 changes: 7 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,15 @@
# Parse-Swift Changelog

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

### 5.3.2
[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)

__Fixes__
* 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).

### 5.3.1
[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)

Expand Down
4 changes: 2 additions & 2 deletions Sources/ParseSwift/API/API+Command+async.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ internal extension API.Command {
func prepareURLRequest(options: API.Options,
batching: Bool = false,
childObjects: [String: PointerType]? = nil,
childFiles: [UUID: ParseFile]? = nil) async -> Result<URLRequest, ParseError> {
childFiles: [String: ParseFile]? = nil) async -> Result<URLRequest, ParseError> {
await withCheckedContinuation { continuation in
self.prepareURLRequest(options: options,
batching: batching,
Expand All @@ -33,7 +33,7 @@ internal extension API.Command {
callbackQueue: DispatchQueue,
notificationQueue: DispatchQueue? = nil,
childObjects: [String: PointerType]? = nil,
childFiles: [UUID: ParseFile]? = nil,
childFiles: [String: ParseFile]? = nil,
allowIntermediateResponses: Bool = false,
uploadProgress: ((URLSessionTask, Int64, Int64, Int64) -> Void)? = nil,
downloadProgress: ((URLSessionDownloadTask, Int64, Int64, Int64) -> Void)? = nil) async throws -> U {
Expand Down
6 changes: 3 additions & 3 deletions Sources/ParseSwift/API/API+Command.swift
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ internal extension API {
func executeStream(options: API.Options,
callbackQueue: DispatchQueue,
childObjects: [String: PointerType]? = nil,
childFiles: [UUID: ParseFile]? = nil,
childFiles: [String: ParseFile]? = nil,
uploadProgress: ((URLSessionTask, Int64, Int64, Int64) -> Void)? = nil,
stream: InputStream,
completion: @escaping (ParseError?) -> Void) {
Expand Down Expand Up @@ -94,7 +94,7 @@ internal extension API {
callbackQueue: DispatchQueue,
notificationQueue: DispatchQueue? = nil,
childObjects: [String: PointerType]? = nil,
childFiles: [UUID: ParseFile]? = nil,
childFiles: [String: ParseFile]? = nil,
allowIntermediateResponses: Bool = false,
uploadProgress: ((URLSessionTask, Int64, Int64, Int64) -> Void)? = nil,
downloadProgress: ((URLSessionDownloadTask, Int64, Int64, Int64) -> Void)? = nil,
Expand Down Expand Up @@ -252,7 +252,7 @@ internal extension API {
func prepareURLRequest(options: API.Options,
batching: Bool = false,
childObjects: [String: PointerType]? = nil,
childFiles: [UUID: ParseFile]? = nil,
childFiles: [String: ParseFile]? = nil,
completion: @escaping(Result<URLRequest, ParseError>) -> Void) {
let params = self.params?.getURLQueryItems()
Task {
Expand Down
2 changes: 1 addition & 1 deletion Sources/ParseSwift/API/API+NonParseBodyCommand.swift
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ internal extension API.NonParseBodyCommand {
transaction: Bool,
objectsSavedBeforeThisOne: [String: PointerType]?,
// swiftlint:disable:next line_length
filesSavedBeforeThisOne: [UUID: ParseFile]?) async throws -> RESTBatchCommandTypeEncodablePointer<AnyCodable> {
filesSavedBeforeThisOne: [String: ParseFile]?) async throws -> RESTBatchCommandTypeEncodablePointer<AnyCodable> {
let defaultACL = try? await ParseACL.defaultACL()
let batchCommands = try objects.compactMap { (object) -> API.BatchCommand<AnyCodable, PointerType>? in
guard var objectable = object as? Objectable else {
Expand Down
30 changes: 12 additions & 18 deletions Sources/ParseSwift/Coding/ParseEncoder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ public struct ParseEncoder {
acl: ParseACL? = nil,
batching: Bool = false,
objectsSavedBeforeThisOne: [String: PointerType]? = nil,
filesSavedBeforeThisOne: [UUID: ParseFile]? = nil) throws -> Data {
filesSavedBeforeThisOne: [String: ParseFile]? = nil) throws -> Data {
var keysToSkip = SkipKeys.none.keys()
if batching {
keysToSkip = SkipKeys.object.keys()
Expand Down Expand Up @@ -159,9 +159,9 @@ public struct ParseEncoder {
acl: ParseACL? = nil,
collectChildren: Bool,
objectsSavedBeforeThisOne: [String: PointerType]?,
filesSavedBeforeThisOne: [UUID: ParseFile]?) throws -> (encoded: Data,
unique: PointerType?,
unsavedChildren: [Encodable]) {
filesSavedBeforeThisOne: [String: ParseFile]?) throws -> (encoded: Data,
unique: PointerType?,
unsavedChildren: [Encodable]) {
let keysToSkip: Set<String>!
if !Parse.configuration.isRequiringCustomObjectIds {
keysToSkip = SkipKeys.object.keys()
Expand All @@ -188,7 +188,7 @@ public struct ParseEncoder {
batching: Bool,
collectChildren: Bool,
objectsSavedBeforeThisOne: [String: PointerType]?,
filesSavedBeforeThisOne: [UUID: ParseFile]?) throws -> (encoded: Data, unique: PointerType?, unsavedChildren: [Encodable]) {
filesSavedBeforeThisOne: [String: ParseFile]?) throws -> (encoded: Data, unique: PointerType?, unsavedChildren: [Encodable]) {
let keysToSkip: Set<String>!
if !Parse.configuration.isRequiringCustomObjectIds {
keysToSkip = SkipKeys.object.keys()
Expand Down Expand Up @@ -218,12 +218,11 @@ internal class _ParseEncoder: JSONEncoder, Encoder {
let dictionary: NSMutableDictionary
let skippedKeys: Set<String>
var uniquePointer: PointerType?
var uniqueFiles = Set<ParseFile>()
var newObjects = [Encodable]()
var collectChildren = false
var batching = false
var objectsSavedBeforeThisOne: [String: PointerType]?
var filesSavedBeforeThisOne: [UUID: ParseFile]?
var filesSavedBeforeThisOne: [String: ParseFile]?
/// The encoder's storage.
var storage: _ParseEncodingStorage
var ignoreSkipKeys = false
Expand Down Expand Up @@ -280,7 +279,7 @@ internal class _ParseEncoder: JSONEncoder, Encoder {
collectChildren: Bool,
uniquePointer: PointerType?,
objectsSavedBeforeThisOne: [String: PointerType]?,
filesSavedBeforeThisOne: [UUID: ParseFile]?) throws -> (encoded: Data, unique: PointerType?, unsavedChildren: [Encodable]) {
filesSavedBeforeThisOne: [String: ParseFile]?) throws -> (encoded: Data, unique: PointerType?, unsavedChildren: [Encodable]) {
self.acl = acl
let encoder = _ParseEncoder(codingPath: codingPath, dictionary: dictionary, skippingKeys: skippedKeys)
encoder.outputFormatting = outputFormatting
Expand Down Expand Up @@ -364,7 +363,7 @@ internal class _ParseEncoder: JSONEncoder, Encoder {
if let uniquePointer = self.uniquePointer,
uniquePointer.hasSameObjectId(as: pointer) {
throw ParseError(code: .otherCause,
message: "Found a circular dependency when encoding.")
message: "Found a circular dependency when encoding objects. The object: \(pointer) cannot have the same objectId as: \(uniquePointer)")
}
valueToEncode = pointer
} else if let object = value as? Objectable {
Expand All @@ -373,7 +372,7 @@ internal class _ParseEncoder: JSONEncoder, Encoder {
if let uniquePointer = self.uniquePointer,
uniquePointer.hasSameObjectId(as: pointer) {
throw ParseError(code: .otherCause,
message: "Found a circular dependency when encoding.")
message: "Found a circular dependency when encoding objects. The object: \(pointer) cannot have the same objectId as: \(uniquePointer)")
}
valueToEncode = pointer
} else {
Expand Down Expand Up @@ -401,10 +400,6 @@ internal class _ParseEncoder: JSONEncoder, Encoder {
func deepFindAndReplaceParseFiles(_ value: ParseFile) throws -> Encodable? {
var valueToEncode: Encodable?
if value.isSaved {
if self.uniqueFiles.contains(value) {
throw ParseError(code: .otherCause, message: "Found a circular dependency when encoding.")
}
self.uniqueFiles.insert(value)
if !self.collectChildren {
valueToEncode = value
}
Expand All @@ -413,13 +408,12 @@ internal class _ParseEncoder: JSONEncoder, Encoder {
if let updatedFile = self.filesSavedBeforeThisOne?[value.id] {
valueToEncode = updatedFile
} else {
// New object needs to be saved before it can be stored
// New file needs to be saved before it can be stored
self.newObjects.append(value)
}
} else if let currentFile = self.filesSavedBeforeThisOne?[value.id] {
valueToEncode = currentFile
} else if dictionary.count > 0 {
// Only top level objects can be saved without a pointer
throw ParseError(code: .otherCause, message: "Error. Could not resolve unsaved file while encoding.")
}
}
Expand Down Expand Up @@ -1035,7 +1029,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<String>, collectChildren: Bool, objectsSavedBeforeThisOne: [String: PointerType]?, filesSavedBeforeThisOne: [UUID: ParseFile]?) {
init(referencing encoder: _ParseEncoder, at index: Int, wrapping array: NSMutableArray, skippingKeys: Set<String>, collectChildren: Bool, objectsSavedBeforeThisOne: [String: PointerType]?, filesSavedBeforeThisOne: [String: ParseFile]?) {
self.encoder = encoder
self.reference = .array(array, index)
super.init(codingPath: encoder.codingPath, dictionary: NSMutableDictionary(), skippingKeys: skippingKeys)
Expand All @@ -1046,7 +1040,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<String>, collectChildren: Bool, objectsSavedBeforeThisOne: [String: PointerType]?, filesSavedBeforeThisOne: [UUID: ParseFile]?) {
init(referencing encoder: _ParseEncoder, key: CodingKey, wrapping dictionary: NSMutableDictionary, skippingKeys: Set<String>, collectChildren: Bool, objectsSavedBeforeThisOne: [String: PointerType]?, filesSavedBeforeThisOne: [String: ParseFile]?) {
self.encoder = encoder
self.reference = .dictionary(dictionary, key.stringValue)
super.init(codingPath: encoder.codingPath, dictionary: dictionary, skippingKeys: skippingKeys)
Expand Down
8 changes: 5 additions & 3 deletions Sources/ParseSwift/Objects/ParseInstallation+async.swift
Original file line number Diff line number Diff line change
Expand Up @@ -372,7 +372,7 @@ internal extension Sequence where Element: ParseInstallation {
var options = options
options.insert(.cachePolicy(.reloadIgnoringLocalCacheData))
var childObjects = [String: PointerType]()
var childFiles = [UUID: ParseFile]()
var childFiles = [String: ParseFile]()
var commands = [API.Command<Self.Element, Self.Element>]()
let objects = map { $0 }
for object in objects {
Expand All @@ -381,13 +381,15 @@ internal extension Sequence where Element: ParseInstallation {
isShouldReturnIfChildObjectsFound: transaction)
try savedChildObjects.forEach {(key, value) in
guard childObjects[key] == nil else {
throw ParseError(code: .otherCause, message: "circular dependency")
throw ParseError(code: .otherCause,
message: "Found a circular dependency in ParseInstallation.")
}
childObjects[key] = value
}
try savedChildFiles.forEach {(key, value) in
guard childFiles[key] == nil else {
throw ParseError(code: .otherCause, message: "circular dependency")
throw ParseError(code: .otherCause,
message: "Found a circular dependency in ParseInstallation.")
}
childFiles[key] = value
}
Expand Down
14 changes: 8 additions & 6 deletions Sources/ParseSwift/Objects/ParseObject+async.swift
Original file line number Diff line number Diff line change
Expand Up @@ -294,15 +294,15 @@ internal extension ParseObject {
// swiftlint:disable:next function_body_length
func ensureDeepSave(options: API.Options = [],
isShouldReturnIfChildObjectsFound: Bool = false) async throws -> ([String: PointerType],
[UUID: ParseFile]) {
[String: ParseFile]) {

var options = options
// Remove any caching policy added by the developer as fresh data
// from the server is needed.
options.remove(.cachePolicy(.reloadIgnoringLocalCacheData))
options.insert(.cachePolicy(.reloadIgnoringLocalCacheData))
var objectsFinishedSaving = [String: PointerType]()
var filesFinishedSaving = [UUID: ParseFile]()
var filesFinishedSaving = [String: ParseFile]()
let defaultACL = try? await ParseACL.defaultACL()
do {
let object = try ParseCoding.parseEncoder()
Expand Down Expand Up @@ -414,7 +414,7 @@ internal extension Sequence where Element: ParseObject {
var options = options
options.insert(.cachePolicy(.reloadIgnoringLocalCacheData))
var childObjects = [String: PointerType]()
var childFiles = [UUID: ParseFile]()
var childFiles = [String: ParseFile]()
var commands = [API.Command<Self.Element, Self.Element>]()
let objects = map { $0 }
for object in objects {
Expand All @@ -423,13 +423,15 @@ internal extension Sequence where Element: ParseObject {
isShouldReturnIfChildObjectsFound: transaction)
try savedChildObjects.forEach {(key, value) in
guard childObjects[key] == nil else {
throw ParseError(code: .otherCause, message: "circular dependency")
throw ParseError(code: .otherCause,
message: "Found a circular dependency in ParseObject.")
}
childObjects[key] = value
}
try savedChildFiles.forEach {(key, value) in
guard childFiles[key] == nil else {
throw ParseError(code: .otherCause, message: "circular dependency")
throw ParseError(code: .otherCause,
message: "Found a circular dependency in ParseObject.")
}
childFiles[key] = value
}
Expand Down Expand Up @@ -478,7 +480,7 @@ internal extension ParseEncodable {
func saveAll(objects: [ParseEncodable],
transaction: Bool = configuration.isUsingTransactions,
objectsSavedBeforeThisOne: [String: PointerType]?,
filesSavedBeforeThisOne: [UUID: ParseFile]?,
filesSavedBeforeThisOne: [String: ParseFile]?,
options: API.Options = [],
callbackQueue: DispatchQueue = .main) async throws -> [(Result<PointerType, ParseError>)] {
try await API.NonParseBodyCommand<AnyCodable, PointerType>
Expand Down
5 changes: 3 additions & 2 deletions Sources/ParseSwift/Objects/ParseObject.swift
Original file line number Diff line number Diff line change
Expand Up @@ -167,9 +167,10 @@ public extension ParseObject {
}

/**
A computed property that is the same value as `objectId` and makes it easy to use `ParseObject`'s
A computed property that is a unique identifier and makes it easy to use `ParseObject`'s
as models in MVVM and SwiftUI.
- note: `id` allows `ParseObjects`'s to be used even when they are unsaved and do not have an `objectId`.
- note: `id` allows `ParseObject`'s to be used even when they are not saved and do not have an `objectId`.
- important: `id` will have the same value as `objectId` when a `ParseObject` is saved.
*/
var id: String {
objectId ?? UUID().uuidString
Expand Down
8 changes: 5 additions & 3 deletions Sources/ParseSwift/Objects/ParseUser+async.swift
Original file line number Diff line number Diff line change
Expand Up @@ -603,7 +603,7 @@ internal extension Sequence where Element: ParseUser {
var options = options
options.insert(.cachePolicy(.reloadIgnoringLocalCacheData))
var childObjects = [String: PointerType]()
var childFiles = [UUID: ParseFile]()
var childFiles = [String: ParseFile]()
var commands = [API.Command<Self.Element, Self.Element>]()
let objects = map { $0 }
for object in objects {
Expand All @@ -612,13 +612,15 @@ internal extension Sequence where Element: ParseUser {
isShouldReturnIfChildObjectsFound: transaction)
try savedChildObjects.forEach {(key, value) in
guard childObjects[key] == nil else {
throw ParseError(code: .otherCause, message: "circular dependency")
throw ParseError(code: .otherCause,
message: "Found a circular dependency in ParseUser.")
}
childObjects[key] = value
}
try savedChildFiles.forEach {(key, value) in
guard childFiles[key] == nil else {
throw ParseError(code: .otherCause, message: "circular dependency")
throw ParseError(code: .otherCause,
message: "Found a circular dependency in ParseUser.")
}
childFiles[key] = value
}
Expand Down
2 changes: 1 addition & 1 deletion Sources/ParseSwift/ParseConstants.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import Foundation

enum ParseConstants {
static let sdk = "swift"
static let version = "5.3.1"
static let version = "5.3.2"
static let fileManagementDirectory = "parse/"
static let fileManagementPrivateDocumentsDirectory = "Private Documents/"
static let fileManagementLibraryDirectory = "Library/"
Expand Down
Loading

0 comments on commit c371d5d

Please sign in to comment.