From 0e409291a0a81b47dbea722a48aba61ef9ddafcd Mon Sep 17 00:00:00 2001 From: Corey Date: Tue, 2 May 2023 13:22:36 -0400 Subject: [PATCH] refactor: Make some ParseObject methods required (#96) * refactor: Make some ParseObject methods and properties required * nit * nits * api nits * remove unused code --- CHANGELOG.md | 8 +- .../API/API+NonParseBodyCommand.swift | 25 ----- Sources/ParseSwift/API/API.swift | 2 +- Sources/ParseSwift/API/Responses.swift | 32 +++---- Sources/ParseSwift/Objects/ParseObject.swift | 71 +++++++------- Sources/ParseSwift/Objects/ParseUser.swift | 2 +- Sources/ParseSwift/ParseConstants.swift | 2 +- Sources/ParseSwift/Protocols/Objectable.swift | 93 ++++++++++--------- .../ParseSwift/Protocols/ParseTypeable.swift | 12 --- 9 files changed, 117 insertions(+), 130 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c3cad14a..9c0c083dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,9 +2,15 @@ # Parse-Swift Changelog ### main -[Full Changelog](https://github.com/netreconlab/Parse-Swift/compare/5.4.2...main), [Documentation](https://swiftpackageindex.com/netreconlab/Parse-Swift/main/documentation/parseswift) +[Full Changelog](https://github.com/netreconlab/Parse-Swift/compare/5.4.3...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.4.3 +[Full Changelog](https://github.com/netreconlab/Parse-Swift/compare/5.4.2...5.4.3), [Documentation](https://swiftpackageindex.com/netreconlab/Parse-Swift/5.4.3/documentation/parseswift) + +__Fixes__ +* Move some ParseObject methods and properties to required to leverage developer implementations ([#96](https://github.com/netreconlab/Parse-Swift/pull/96)), thanks to [Corey Baker](https://github.com/cbaker6). + ### 5.4.2 [Full Changelog](https://github.com/netreconlab/Parse-Swift/compare/5.4.1...5.4.2), [Documentation](https://swiftpackageindex.com/netreconlab/Parse-Swift/5.4.2/documentation/parseswift) diff --git a/Sources/ParseSwift/API/API+NonParseBodyCommand.swift b/Sources/ParseSwift/API/API+NonParseBodyCommand.swift index 551e0cd5f..d1fb47d43 100644 --- a/Sources/ParseSwift/API/API+NonParseBodyCommand.swift +++ b/Sources/ParseSwift/API/API+NonParseBodyCommand.swift @@ -33,31 +33,6 @@ internal extension API { self.mapper = mapper } - /* - func execute(options: API.Options) throws -> U { - var responseResult: Result? - let synchronizationQueue = DispatchQueue(label: "com.parse.NonParseBodyCommand.sync.\(UUID().uuidString)", - qos: .default, - attributes: .concurrent, - autoreleaseFrequency: .inherit, - target: nil) - let group = DispatchGroup() - group.enter() - self.executeAsync(options: options, - callbackQueue: synchronizationQueue, - allowIntermediateResponses: false) { result in - responseResult = result - group.leave() - } - group.wait() - - guard let response = responseResult else { - throw ParseError(code: .otherCause, - message: "Could not unrwrap server response") - } - return try response.get() - } */ - // MARK: Asynchronous Execution func execute(options: API.Options, callbackQueue: DispatchQueue, diff --git a/Sources/ParseSwift/API/API.swift b/Sources/ParseSwift/API/API.swift index edfafa246..8d970f2d2 100644 --- a/Sources/ParseSwift/API/API.swift +++ b/Sources/ParseSwift/API/API.swift @@ -16,7 +16,7 @@ import FoundationNetworking /// The REST API for communicating with a Parse Server. public struct API { - internal enum Method: String, Encodable { + public enum Method: String, Encodable { case GET, POST, PUT, PATCH, DELETE } diff --git a/Sources/ParseSwift/API/Responses.swift b/Sources/ParseSwift/API/Responses.swift index 9ffee9d06..b7d76e982 100644 --- a/Sources/ParseSwift/API/Responses.swift +++ b/Sources/ParseSwift/API/Responses.swift @@ -8,7 +8,7 @@ import Foundation -internal struct CreateResponse: Decodable { +struct CreateResponse: Decodable { var objectId: String var createdAt: Date var updatedAt: Date { @@ -24,7 +24,7 @@ internal struct CreateResponse: Decodable { } } -internal struct ReplaceResponse: Decodable { +struct ReplaceResponse: Decodable { var createdAt: Date? var updatedAt: Date? @@ -45,7 +45,7 @@ internal struct ReplaceResponse: Decodable { } } -internal struct UpdateResponse: Decodable { +struct UpdateResponse: Decodable { var updatedAt: Date func apply(to object: T) -> T where T: ParseObject { @@ -55,18 +55,18 @@ internal struct UpdateResponse: Decodable { } } -internal struct UpdateSessionTokenResponse: Decodable { +struct UpdateSessionTokenResponse: Decodable { var updatedAt: Date let sessionToken: String? } // MARK: ParseObject Batch -internal struct BatchResponseItem: Codable where T: Codable { +struct BatchResponseItem: Codable where T: Codable { let success: T? let error: ParseError? } -internal struct BatchResponse: Codable { +struct BatchResponse: Codable { var objectId: String? var createdAt: Date? var updatedAt: Date? @@ -112,13 +112,13 @@ internal struct BatchResponse: Codable { } // MARK: Query -internal struct QueryResponse: Codable where T: ParseObject { +struct QueryResponse: Codable where T: ParseObject { let results: [T] let count: Int? } // MARK: ParseUser -internal struct LoginSignupResponse: Codable { +struct LoginSignupResponse: Codable { let createdAt: Date let objectId: String let sessionToken: String @@ -137,7 +137,7 @@ internal struct LoginSignupResponse: Codable { } // MARK: ParseFile -internal struct FileUploadResponse: Codable { +struct FileUploadResponse: Codable { let name: String let url: URL @@ -150,35 +150,35 @@ internal struct FileUploadResponse: Codable { } // MARK: AnyResultResponse -internal struct AnyResultResponse: Decodable { +struct AnyResultResponse: Decodable { let result: U } // MARK: AnyResultsResponse -internal struct AnyResultsResponse: Decodable { +struct AnyResultsResponse: Decodable { let results: [U] } -internal struct AnyResultsMongoResponse: Decodable { +struct AnyResultsMongoResponse: Decodable { let results: U } // MARK: ConfigResponse -internal struct ConfigFetchResponse: Codable where T: ParseConfig { +struct ConfigFetchResponse: Codable where T: ParseConfig { let params: T } -internal struct BooleanResponse: Codable { +struct BooleanResponse: Codable { let result: Bool } // MARK: HealthResponse -internal struct HealthResponse: Codable { +struct HealthResponse: Codable { let status: ParseHealth.Status } // MARK: PushResponse -internal struct PushResponse: Codable { +struct PushResponse: Codable { let data: Data let statusId: String } diff --git a/Sources/ParseSwift/Objects/ParseObject.swift b/Sources/ParseSwift/Objects/ParseObject.swift index e92456b89..314cdb5a3 100644 --- a/Sources/ParseSwift/Objects/ParseObject.swift +++ b/Sources/ParseSwift/Objects/ParseObject.swift @@ -82,6 +82,30 @@ public protocol ParseObject: ParseTypeable, */ init() + /** + Creates a `ParseObject` with a specified `objectId`. Can be used to create references or associations + between `ParseObject`'s. + - warning: It is required that all added properties be optional properties so they can eventually be used as + Parse `Pointer`'s. If a developer really wants to have a required key, they should require it on the server-side or + create methods to check the respective properties on the client-side before saving objects. See + [here](https://github.com/parse-community/Parse-Swift/pull/315#issuecomment-1014701003) + for more information. + */ + init(objectId: String) + + /** + Determines if two objects have the same objectId. + - parameter as: Object to compare. + - returns: Returns a **true** if the other object has the same `objectId` or **false** if unsuccessful. + */ + func hasSameObjectId(as other: T) -> Bool + + /** + Converts this `ParseObject` to a Parse Pointer. + - returns: The pointer version of the `ParseObject`, Pointer. + */ + func toPointer() throws -> Pointer + /** Determines if a `KeyPath` of the current `ParseObject` should be restored by comparing it to another `ParseObject`. @@ -148,29 +172,11 @@ public protocol ParseObject: ParseTypeable, // MARK: Default Implementations public extension ParseObject { - /** - Creates a `ParseObject` with a specified `objectId`. Can be used to create references or associations - between `ParseObject`'s. - - warning: It is required that all added properties be optional properties so they can eventually be used as - Parse `Pointer`'s. If a developer really wants to have a required key, they should require it on the server-side or - create methods to check the respective properties on the client-side before saving objects. See - [here](https://github.com/parse-community/Parse-Swift/pull/315#issuecomment-1014701003) - for more information. - */ - init(objectId: String) { - self.init() - self.objectId = objectId - } - - func hash(into hasher: inout Hasher) { - hasher.combine(self.id) - } - /** 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 `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. + - note: `id` allows `ParseObject`'s to be used even if they have not been saved and/or missing an `objectId`. + - important: `id` will have the same value as `objectId` when a `ParseObject` contains an `objectId`. */ var id: String { objectId ?? UUID().uuidString @@ -188,21 +194,17 @@ public extension ParseObject { return object } - /** - Determines if two objects have the same objectId. - - parameter as: Object to compare. - - returns: Returns a **true** if the other object has the same `objectId` or **false** if unsuccessful. - */ + init(objectId: String) { + self.init() + self.objectId = objectId + } + func hasSameObjectId(as other: T) -> Bool { - return other.className == className && other.objectId == objectId && objectId != nil + other.className == className && other.objectId == objectId && objectId != nil } - /** - Converts this `ParseObject` to a Parse Pointer. - - returns: The pointer version of the `ParseObject`, Pointer. - */ func toPointer() throws -> Pointer { - return try Pointer(self) + try Pointer(self) } func shouldRestoreKey(_ keyPath: KeyPath, @@ -240,6 +242,13 @@ extension ParseObject { } } +// MARK: Hashable +public extension ParseObject { + func hash(into hasher: inout Hasher) { + hasher.combine(self.id) + } +} + // MARK: Helper Methods public extension ParseObject { /** diff --git a/Sources/ParseSwift/Objects/ParseUser.swift b/Sources/ParseSwift/Objects/ParseUser.swift index 348e363de..961fc5cb9 100644 --- a/Sources/ParseSwift/Objects/ParseUser.swift +++ b/Sources/ParseSwift/Objects/ParseUser.swift @@ -1084,7 +1084,7 @@ extension ParseUser { if Parse.configuration.isRequiringCustomObjectIds && objectId == nil && !ignoringCustomObjectIdConfig { throw ParseError(code: .missingObjectId, message: "objectId must not be nil") } - if isSaved { + if try await isSaved() { return try await replaceCommand() // MARK: Should be switched to "updateCommand" when server supports PATCH. } return try await createCommand() diff --git a/Sources/ParseSwift/ParseConstants.swift b/Sources/ParseSwift/ParseConstants.swift index 3393f3b5c..aa5621952 100644 --- a/Sources/ParseSwift/ParseConstants.swift +++ b/Sources/ParseSwift/ParseConstants.swift @@ -10,7 +10,7 @@ import Foundation enum ParseConstants { static let sdk = "swift" - static let version = "5.4.2" + static let version = "5.4.3" static let fileManagementDirectory = "parse/" static let fileManagementPrivateDocumentsDirectory = "Private Documents/" static let fileManagementLibraryDirectory = "Library/" diff --git a/Sources/ParseSwift/Protocols/Objectable.swift b/Sources/ParseSwift/Protocols/Objectable.swift index 3c3319fe6..1166a80ee 100644 --- a/Sources/ParseSwift/Protocols/Objectable.swift +++ b/Sources/ParseSwift/Protocols/Objectable.swift @@ -37,52 +37,46 @@ public protocol Objectable: ParseEncodable, Decodable { var ACL: ParseACL? { get set } /** - The Parse Server endpoint for this ParseObject. + The Parse Server endpoint for this object. */ var endpoint: API.Endpoint { get } -} -extension Objectable { /** - The class name of the object. - */ - public static var className: String { + The Parse Server endpoint for this object. + - returns: Returns the `API.Endpoint` for this object. + - throws: An error of `ParseError` type. + */ + func endpoint(_ method: API.Method) async throws -> API.Endpoint + + /** + Specifies if this object has been saved. + - returns: Returns **true** if this object is saved, **false** otherwise. + - throws: An error of `ParseError` type. + */ + func isSaved() async throws -> Bool +} + +// MARK: Default Implementation +public extension Objectable { + + static var className: String { let classType = "\(type(of: self))" return classType.components(separatedBy: ".").first ?? "" // strip .Type } - /** - The class name of the object. - */ - public var className: String { - return Self.className + var className: String { + Self.className } - static func createHash(_ object: Encodable) throws -> String { - let encoded = try ParseCoding.parseEncoder().encode(object, - acl: nil, - batching: false) - guard let hashString = String(data: encoded, encoding: .utf8) else { - throw ParseError(code: .otherCause, message: "Could not create hash") - } - return hashString - } -} - -// MARK: Convenience -extension Objectable { - public var endpoint: API.Endpoint { + var endpoint: API.Endpoint { if let objectId = objectId { return .object(className: className, objectId: objectId) } - return .objects(className: className) } - /// Specifies if a `ParseObject` has been saved. - /// - warning: This will not be available in ParseSwift 6.0.0. Use `isSaved()` instead. - /// BAKER mark this internal. - public var isSaved: Bool { + func isSaved() async throws -> Bool { + try await yieldIfNotInitialized() if !Parse.configuration.isRequiringCustomObjectIds { return objectId != nil } else { @@ -90,28 +84,43 @@ extension Objectable { } } - /// Specifies if a `ParseObject` has been saved. - public func isSaved() async throws -> Bool { + func endpoint(_ method: API.Method) async throws -> API.Endpoint { try await yieldIfNotInitialized() - if !Parse.configuration.isRequiringCustomObjectIds { - return objectId != nil + if !Parse.configuration.isRequiringCustomObjectIds || method != .POST { + return endpoint } else { - return objectId != nil && createdAt != nil + return .objects(className: className) } } +} - func toPointer() throws -> PointerType { - return try PointerType(self) +// MARK: Convenience +extension Objectable { + + static func createHash(_ object: Encodable) throws -> String { + let encoded = try ParseCoding.parseEncoder().encode(object, + acl: nil, + batching: false) + guard let hashString = String(data: encoded, encoding: .utf8) else { + throw ParseError(code: .otherCause, message: "Could not create hash") + } + return hashString } - func endpoint(_ method: API.Method) async throws -> API.Endpoint { - try await yieldIfNotInitialized() - if !Parse.configuration.isRequiringCustomObjectIds || method != .POST { - return endpoint + /// Specifies if a `ParseObject` has been saved. + /// - warning: This will not be available in ParseSwift 6.0.0. Use `isSaved()` instead. + /// BAKER mark this internal. + public var isSaved: Bool { + if !Parse.configuration.isRequiringCustomObjectIds { + return objectId != nil } else { - return .objects(className: className) + return objectId != nil && createdAt != nil } } + + func toPointer() throws -> PointerType { + try PointerType(self) + } } internal struct BaseObjectable: Objectable { diff --git a/Sources/ParseSwift/Protocols/ParseTypeable.swift b/Sources/ParseSwift/Protocols/ParseTypeable.swift index 318d54935..63f4e40cb 100644 --- a/Sources/ParseSwift/Protocols/ParseTypeable.swift +++ b/Sources/ParseSwift/Protocols/ParseTypeable.swift @@ -34,15 +34,3 @@ extension ParseTypeable { debugDescription } } - -extension ParseTypeable { - - static func createSynchronizationQueue(_ label: String) -> DispatchQueue { - DispatchQueue(label: "parse.\(label).\(UUID().uuidString)", - qos: .default, - attributes: .concurrent, - autoreleaseFrequency: .inherit, - target: nil) - } - -}