Skip to content

Commit

Permalink
fix: Improve Equatable and Hashable conformance (#148)
Browse files Browse the repository at this point in the history
* fix: Improve Equatable and Hashable conformance

* add changelog

* revert hasSameObjectId

* Improve playgrounds

* Improve user login playgrounds
  • Loading branch information
cbaker6 authored Jan 14, 2024
1 parent dc037bd commit b3169f2
Show file tree
Hide file tree
Showing 38 changed files with 330 additions and 245 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ __New features__
* Add fetchAll method to array of Parse Pointer Object's ([#141](https://github.com/netreconlab/Parse-Swift/pull/141)), thanks to [Corey Baker](https://github.com/cbaker6).

__Fixes__
* Updates to ParseUser password now persist the updated sessionToken from the server to the client Keychain ([#147](https://github.com/netreconlab/Parse-Swift/pull/147)), thanks to [Corey Baker](https://github.com/cbaker6).
* Improve mutiple types conformance to Equatable and Hashable ([#148](https://github.com/netreconlab/Parse-Swift/pull/147)), thanks to [Corey Baker](https://github.com/cbaker6).
* Updates to ParseUser password now persist the updated sessionToken from the server to the client Keychain ([#147](https://github.com/netreconlab/Parse-Swift/pull/148)), thanks to [Corey Baker](https://github.com/cbaker6).

### 5.8.2
[Full Changelog](https://github.com/netreconlab/Parse-Swift/compare/5.8.1...5.8.2), [Documentation](https://swiftpackageindex.com/netreconlab/Parse-Swift/5.8.2/documentation/parseswift)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,8 @@ score.save { result in
}

//: This will store the second batch score to be used later.
var score2ForFetchedLater: GameScore?
var scoreToFetchLater: GameScore?
var score2ToFetchLater: GameScore?

//: Saving multiple GameScores at once with batching.
[score, score2].saveAll { results in
Expand All @@ -179,8 +180,10 @@ var score2ForFetchedLater: GameScore?
Saved \"\(savedScore.className)\" with
points \(String(describing: savedScore.points)) successfully
""")
if index == 1 {
score2ForFetchedLater = savedScore
if index == 0 {
scoreToFetchLater = savedScore
} else if index == 1 {
score2ToFetchLater = savedScore
}
index += 1
case .failure(let error):
Expand All @@ -203,8 +206,10 @@ var score2ForFetchedLater: GameScore?
switch otherResult {
case .success(let savedScore):
print("Saved \"\(savedScore.className)\" with points \(savedScore.points) successfully")
if index == 1 {
score2ForFetchedLater = savedScore
if index == 0 {
scoreToFetchLater = savedScore
} else if index == 1 {
score2ToFetchLater = savedScore
}
index += 1
case .failure(let error):
Expand Down Expand Up @@ -299,7 +304,7 @@ Task {
}

//: Now we will fetch a ParseObject that has already been saved based on its' objectId.
let scoreToFetch = GameScore(objectId: score2ForFetchedLater?.objectId)
let scoreToFetch = GameScore(objectId: scoreToFetchLater?.objectId)

//: Asynchronous completion block fetch this GameScore based on it is objectId alone.
scoreToFetch.fetch { result in
Expand All @@ -322,7 +327,7 @@ Task {
}

//: Now we will fetch `ParseObject`'s in batch that have already been saved based on its' objectId.
let score2ToFetch = GameScore(objectId: score2ForFetchedLater?.objectId)
let score2ToFetch = GameScore(objectId: score2ToFetchLater?.objectId)

//: Asynchronous completion block fetch GameScores based on it is objectId alone.
[scoreToFetch, score2ToFetch].fetchAll { result in
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,6 @@ import ParseSwift

PlaygroundPage.current.needsIndefiniteExecution = true

Task {
do {
try await initializeParse()
} catch {
assertionFailure("Error initializing Parse-Swift: \(error)")
}
}

struct User: ParseUser {
//: These are required by `ParseObject`.
var objectId: String?
Expand Down Expand Up @@ -52,6 +44,19 @@ struct User: ParseUser {
}
}

Task {
do {
try await initializeParse()
} catch {
assertionFailure("Error initializing Parse-Swift: \(error)")
}
do {
try await User.logout()
} catch let error {
print("Error logging out: \(error)")
}
}

/*:
Sign up user asynchronously - Performs work on background
queue and returns to specified callbackQueue.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -182,8 +182,8 @@ Task {
let currentUser = try? await User.current()
currentUser?.fetch(includeKeys: ["gameScore"]) { result in
switch result {
case .success:
print("Successfully fetched user with gameScore key: \(String(describing: User.current))")
case .success(let user):
print("Successfully fetched user with gameScore key: \(user)")
case .failure(let error):
print("Error fetching User: \(error)")
}
Expand All @@ -196,8 +196,8 @@ Task {
let currentUser = try? await User.current()
currentUser?.fetch(includeKeys: ["*"]) { result in
switch result {
case .success:
print("Successfully fetched user with all keys: \(String(describing: User.current))")
case .success(let user):
print("Successfully fetched user with all keys: \(user)")
case .failure(let error):
print("Error fetching User: \(error)")
}
Expand Down
16 changes: 11 additions & 5 deletions Sources/ParseSwift/API/API+Command.swift
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,9 @@ internal extension API {
uploadProgress: ((URLSessionTask, Int64, Int64, Int64) -> Void)? = nil,
stream: InputStream,
completion: @escaping (ParseError?) -> Void) {
guard method == .POST || method == .PUT || method == .PATCH else {
guard method == .POST ||
method == .PUT ||
method == .PATCH else {
callbackQueue.async {
completion(nil)
}
Expand Down Expand Up @@ -133,7 +135,9 @@ internal extension API {
}
} else {
// ParseFiles are handled with a dedicated URLSession
if method == .POST || method == .PUT || method == .PATCH {
if method == .POST ||
method == .PUT ||
method == .PATCH {
switch await self.prepareURLRequest(options: options,
batching: batching,
childObjects: childObjects,
Expand Down Expand Up @@ -258,7 +262,8 @@ internal extension API {
Task {
do {
var headers = try await API.getHeaders(options: options)
if method == .GET || method == .DELETE {
if method == .GET ||
method == .DELETE {
headers.removeValue(forKey: "X-Parse-Request-Id")
}
let url = parseURL == nil ?
Expand Down Expand Up @@ -405,8 +410,9 @@ internal extension API.Command {
original data: Data?,
ignoringCustomObjectIdConfig: Bool,
batching: Bool = false) async throws -> API.Command<V, V> where V: ParseObject {
if Parse.configuration.isRequiringCustomObjectIds
&& object.objectId == nil && !ignoringCustomObjectIdConfig {
if Parse.configuration.isRequiringCustomObjectIds &&
object.objectId == nil &&
!ignoringCustomObjectIdConfig {
throw ParseError(code: .missingObjectId, message: "objectId must not be nil")
}
if try await object.isSaved() {
Expand Down
3 changes: 2 additions & 1 deletion Sources/ParseSwift/API/API+NonParseBodyCommand.swift
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,8 @@ internal extension API {
let params = self.params?.getURLQueryItems()
do {
var headers = try await API.getHeaders(options: options)
if method == .GET || method == .DELETE {
if method == .GET ||
method == .DELETE {
headers.removeValue(forKey: "X-Parse-Request-Id")
}
let url = API.serverURL(options: options).appendingPathComponent(path.urlComponent)
Expand Down
37 changes: 31 additions & 6 deletions Sources/ParseSwift/API/API.swift
Original file line number Diff line number Diff line change
Expand Up @@ -203,8 +203,22 @@ public struct API {
}
}

// swiftlint:disable:next cyclomatic_complexity
public static func == (lhs: API.Option, rhs: API.Option) -> Bool {
lhs.hashValue == rhs.hashValue
switch (lhs, rhs) {
case (.usePrimaryKey, .usePrimaryKey): return true
case (.removeMimeType, .removeMimeType): return true
case (.sessionToken(let object1), .sessionToken(let object2)): return object1 == object2
case (.installationId(let object1), .installationId(let object2)): return object1 == object2
case (.mimeType(let object1), .mimeType(let object2)): return object1 == object2
case (.fileSize(let object1), .fileSize(let object2)): return object1 == object2
case (.metadata(let object1), .metadata(let object2)): return object1 == object2
case (.tags(let object1), .tags(let object2)): return object1 == object2
case (.context(let object1), .context(let object2)): return object1.isEqual(object2)
case (.cachePolicy(let object1), .cachePolicy(let object2)): return object1 == object2
case (.serverURL(let object1), .serverURL(let object2)): return object1 == object2
default: return false
}
}
}

Expand All @@ -228,7 +242,7 @@ public struct API {
headers["X-Parse-Client-Version"] = clientVersion()
headers["X-Parse-Request-Id"] = Self.createUniqueRequestId()

options.forEach { (option) in
options.forEach { option in
switch option {
case .usePrimaryKey:
headers["X-Parse-Master-Key"] = Parse.configuration.primaryKey
Expand Down Expand Up @@ -272,11 +286,22 @@ public struct API {
}

internal static func serverURL(options: API.Options) -> URL {
guard let differentServerURLOption = options.first(where: { $0 == .serverURL("") }),
case .serverURL(let differentServerURLString) = differentServerURLOption,
let differentURL = URL(string: differentServerURLString) else {
var optionURL: URL?
// BAKER: Currently have to step through all options and
// break to get the current URL.
for option in options {
switch option {
case .serverURL(let url):
optionURL = URL(string: url)
default:
continue
}
break
}

guard let currentURL = optionURL else {
return Parse.configuration.serverURL
}
return differentURL
return currentURL
}
}
1 change: 1 addition & 0 deletions Sources/ParseSwift/Coding/AnyCodable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -110,5 +110,6 @@ extension AnyCodable: ExpressibleByBooleanLiteral {}
extension AnyCodable: ExpressibleByIntegerLiteral {}
extension AnyCodable: ExpressibleByFloatLiteral {}
extension AnyCodable: ExpressibleByStringLiteral {}
extension AnyCodable: ExpressibleByStringInterpolation {}
extension AnyCodable: ExpressibleByArrayLiteral {}
extension AnyCodable: ExpressibleByDictionaryLiteral {}
19 changes: 12 additions & 7 deletions Sources/ParseSwift/Coding/ParseEncoder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -367,7 +367,8 @@ internal class _ParseEncoder: JSONEncoder, Encoder {
}
valueToEncode = pointer
} else if let object = value as? Objectable {
if !batching || (batching && codingPath.last?.stringValue == "body"),
if !batching ||
(batching && codingPath.last?.stringValue == "body"),
let pointer = try? PointerType(object) {
if let uniquePointer = self.uniquePointer,
uniquePointer.hasSameObjectId(as: pointer) {
Expand Down Expand Up @@ -507,8 +508,8 @@ private struct _ParseEncoderKeyedEncodingContainer<Key: CodingKey>: KeyedEncodin
guard !shouldSkipKey(key) else { return }

var valueToEncode: Encodable = value
if ((value as? Objectable) != nil)
|| ((value as? ParsePointer) != nil) {
if ((value as? Objectable) != nil) ||
((value as? ParsePointer) != nil) {
if let replacedObject = try self.encoder.deepFindAndReplaceParseObjects(value) {
valueToEncode = replacedObject
}
Expand Down Expand Up @@ -964,18 +965,22 @@ extension _ParseEncoder {
// Disambiguation between variable and function is required due to
// issue tracked at: https://bugs.swift.org/browse/SR-1846
let type = Swift.type(of: value)
if type == Date.self || type == NSDate.self {
if type == Date.self ||
type == NSDate.self {
// Respect Date encoding strategy
return try self.box((value as! Date))
} else if type == Data.self || type == NSData.self {
} else if type == Data.self ||
type == NSData.self {
// Respect Data encoding strategy
// swiftlint:disable:next force_cast
return try self.box((value as! Data))
} else if type == URL.self || type == NSURL.self {
} else if type == URL.self ||
type == NSURL.self {
// Encode URLs as single strings.
// swiftlint:disable:next force_cast
return self.box((value as! URL).absoluteString)
} else if type == Decimal.self || type == NSDecimalNumber.self {
} else if type == Decimal.self ||
type == NSDecimalNumber.self {
// JSONSerialization can natively handle NSDecimalNumber.
// swiftlint:disable:next force_cast
return (value as! NSDecimalNumber)
Expand Down
23 changes: 17 additions & 6 deletions Sources/ParseSwift/Extensions/Encodable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,25 @@ import Foundation

internal extension Encodable {
func isEqual(_ other: Encodable?) -> Bool {
guard let lhsData = try? ParseCoding.parseEncoder().encode(self,
acl: nil),
guard let lhsData = try? ParseCoding
.parseEncoder()
.encode(
self,
acl: nil
),
let lhsString = String(data: lhsData, encoding: .utf8),
let other = other,
let rhsData = try? ParseCoding.parseEncoder().encode(other,
acl: nil),
let rhsString = String(data: rhsData, encoding: .utf8) else {
return false
let rhsData = try? ParseCoding
.parseEncoder()
.encode(
other,
acl: nil
),
let rhsString = String(
data: rhsData,
encoding: .utf8
) else {
return false
}
return lhsString == rhsString
}
Expand Down
10 changes: 5 additions & 5 deletions Sources/ParseSwift/LiveQuery/LiveQueryConstants.swift
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,11 @@ public enum Event<T: ParseObject>: Equatable {

public static func == <U>(lhs: Event<U>, rhs: Event<U>) -> Bool {
switch (lhs, rhs) {
case (.entered(let obj1), .entered(let obj2)): return obj1 == obj2
case (.left(let obj1), .left(let obj2)): return obj1 == obj2
case (.created(let obj1), .created(let obj2)): return obj1 == obj2
case (.updated(let obj1), .updated(let obj2)): return obj1 == obj2
case (.deleted(let obj1), .deleted(let obj2)): return obj1 == obj2
case (.entered(let object1), .entered(let object2)): return object1 == object2
case (.left(let object1), .left(let object2)): return object1 == object2
case (.created(let object1), .created(let object2)): return object1 == object2
case (.updated(let object1), .updated(let object2)): return object1 == object2
case (.deleted(let object1), .deleted(let object2)): return object1 == object2
default: return false
}
}
Expand Down
7 changes: 4 additions & 3 deletions Sources/ParseSwift/LiveQuery/ParseLiveQuery.swift
Original file line number Diff line number Diff line change
Expand Up @@ -631,7 +631,8 @@ extension ParseLiveQuery {
if isUserWantsToConnect {
self.isDisconnectedByUser = false
}
if self.status == .connected || self.isDisconnectedByUser {
if self.status == .connected ||
self.isDisconnectedByUser {
completion(nil)
return
}
Expand Down Expand Up @@ -769,8 +770,8 @@ extension ParseLiveQuery {
}

static func == (lhs: SubscriptionRecord, rhs: SubscriptionRecord) -> Bool {
lhs.messageData == rhs.messageData
&& lhs.queryData == rhs.queryData
lhs.messageData == rhs.messageData &&
lhs.queryData == rhs.queryData
}
}
}
Expand Down
3 changes: 2 additions & 1 deletion Sources/ParseSwift/Objects/ParseInstallation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,8 @@ extension ParseInstallation {

func endpoint(_ method: API.Method) async throws -> API.Endpoint {
try await yieldIfNotInitialized()
if !Parse.configuration.isRequiringCustomObjectIds || method != .POST {
if !Parse.configuration.isRequiringCustomObjectIds ||
method != .POST {
return endpoint
} else {
return .installations
Expand Down
7 changes: 5 additions & 2 deletions Sources/ParseSwift/Objects/ParseObject+async.swift
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,8 @@ internal extension ParseObject {
objectsSavedBeforeThisOne: nil,
filesSavedBeforeThisOne: nil)
var waitingToBeSaved = object.unsavedChildren
if isShouldReturnIfChildObjectsFound && waitingToBeSaved.count > 0 {
if isShouldReturnIfChildObjectsFound &&
waitingToBeSaved.count > 0 {
let error = ParseError(code: .otherCause,
message: """
When using transactions, all child ParseObjects have to originally
Expand Down Expand Up @@ -349,7 +350,9 @@ or disable transactions for this call.
}
}
waitingToBeSaved = nextBatch
if waitingToBeSaved.count > 0 && savableObjects.count == 0 && savableFiles.count == 0 {
if waitingToBeSaved.count > 0 &&
savableObjects.count == 0 &&
savableFiles.count == 0 {
throw ParseError(code: .otherCause,
message: "Found a circular dependency in ParseObject.")
}
Expand Down
Loading

0 comments on commit b3169f2

Please sign in to comment.