Skip to content

Commit

Permalink
fix: Prevent crash when the SDK has not been initialized (#95)
Browse files Browse the repository at this point in the history
  • Loading branch information
cbaker6 committed May 1, 2023
1 parent 1c4d8c3 commit a746db0
Show file tree
Hide file tree
Showing 18 changed files with 150 additions and 109 deletions.
6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,14 @@
# Parse-Swift Changelog

### main
[Full Changelog](https://github.com/netreconlab/Parse-Swift/compare/5.4.1...main), [Documentation](https://swiftpackageindex.com/netreconlab/Parse-Swift/main/documentation/parseswift)
[Full Changelog](https://github.com/netreconlab/Parse-Swift/compare/5.4.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.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)

__Fixes__
* Prevent crash when the developer makes query, fetch, etc. calls before the SDK is properly initialized ([#95](https://github.com/netreconlab/Parse-Swift/pull/95)), thanks to [Corey Baker](https://github.com/cbaker6).
* Add backwards compatability to Xcode 13.2. Building on Xcode <13.3 only works for SPM ([#92](https://github.com/netreconlab/Parse-Swift/pull/92)), thanks to [Corey Baker](https://github.com/cbaker6).

### 5.4.1
Expand Down
8 changes: 4 additions & 4 deletions Sources/ParseSwift/API/API+Command.swift
Original file line number Diff line number Diff line change
Expand Up @@ -409,15 +409,15 @@ internal extension API.Command {
&& object.objectId == nil && !ignoringCustomObjectIdConfig {
throw ParseError(code: .missingObjectId, message: "objectId must not be nil")
}
if object.isSaved {
if try await object.isSaved() {
// MARK: Should be switched to "update" when server supports PATCH.
return try replace(object,
original: data)
}
return await create(object)
return try await create(object)
}

static func create<T>(_ object: T) async -> API.Command<T, T> where T: ParseObject {
static func create<T>(_ object: T) async throws -> API.Command<T, T> where T: ParseObject {
var object = object
if object.ACL == nil,
let acl = try? await ParseACL.defaultACL() {
Expand All @@ -427,7 +427,7 @@ internal extension API.Command {
try ParseCoding.jsonDecoder().decode(CreateResponse.self, from: data).apply(to: object)
}
return API.Command<T, T>(method: .POST,
path: object.endpoint(.POST),
path: try await object.endpoint(.POST),
body: object,
mapper: mapper)
}
Expand Down
1 change: 1 addition & 0 deletions Sources/ParseSwift/API/API+NonParseBodyCommand.swift
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@ internal extension API.NonParseBodyCommand {
objectsSavedBeforeThisOne: [String: PointerType]?,
// swiftlint:disable:next line_length
filesSavedBeforeThisOne: [String: ParseFile]?) async throws -> RESTBatchCommandTypeEncodablePointer<AnyCodable> {
try await yieldIfNotInitialized()
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
4 changes: 2 additions & 2 deletions Sources/ParseSwift/Objects/ParseInstallation+async.swift
Original file line number Diff line number Diff line change
Expand Up @@ -341,7 +341,7 @@ internal extension ParseInstallation {
case .save:
command = try await self.saveCommand(ignoringCustomObjectIdConfig: ignoringCustomObjectIdConfig)
case .create:
command = await self.createCommand()
command = try await self.createCommand()
case .replace:
command = try self.replaceCommand()
case .update:
Expand Down Expand Up @@ -400,7 +400,7 @@ internal extension Sequence where Element: ParseInstallation {
try await object.saveCommand(ignoringCustomObjectIdConfig: ignoringCustomObjectIdConfig)
)
case .create:
commands.append(await object.createCommand())
commands.append(try await object.createCommand())
case .replace:
commands.append(try object.replaceCommand())
case .update:
Expand Down
17 changes: 10 additions & 7 deletions Sources/ParseSwift/Objects/ParseInstallation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,8 @@ public extension ParseInstallation {
// MARK: Convenience
extension ParseInstallation {

func endpoint(_ method: API.Method) -> API.Endpoint {
func endpoint(_ method: API.Method) async throws -> API.Endpoint {
try await yieldIfNotInitialized()
if !Parse.configuration.isRequiringCustomObjectIds || method != .POST {
return endpoint
} else {
Expand Down Expand Up @@ -727,17 +728,19 @@ extension ParseInstallation {
}

func saveCommand(ignoringCustomObjectIdConfig: Bool = false) async throws -> API.Command<Self, Self> {
try await yieldIfNotInitialized()
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 replaceCommand() // MARK: Should be switched to "updateCommand" when server supports PATCH.
}
return await createCommand()
return try await createCommand()
}

// MARK: Saving ParseObjects - private
func createCommand() async -> API.Command<Self, Self> {
func createCommand() async throws -> API.Command<Self, Self> {
try await yieldIfNotInitialized()
var object = self
if object.ACL == nil,
let acl = try? await ParseACL.defaultACL() {
Expand All @@ -747,7 +750,7 @@ extension ParseInstallation {
try ParseCoding.jsonDecoder().decode(CreateResponse.self, from: data).apply(to: object)
}
return API.Command<Self, Self>(method: .POST,
path: endpoint(.POST),
path: try await endpoint(.POST),
body: object,
mapper: mapper)
}
Expand Down Expand Up @@ -854,8 +857,8 @@ extension ParseInstallation {
}
}

func deleteCommand() throws -> API.NonParseBodyCommand<NoBody, NoBody> {
guard isSaved else {
func deleteCommand() async throws -> API.NonParseBodyCommand<NoBody, NoBody> {
guard try await isSaved() else {
throw ParseError(code: .otherCause, message: "Cannot Delete an object without id")
}

Expand Down
4 changes: 2 additions & 2 deletions Sources/ParseSwift/Objects/ParseObject+async.swift
Original file line number Diff line number Diff line change
Expand Up @@ -385,7 +385,7 @@ or disable transactions for this call.
case .save:
command = try await self.saveCommand(ignoringCustomObjectIdConfig: ignoringCustomObjectIdConfig)
case .create:
command = await self.createCommand()
command = try await self.createCommand()
case .replace:
command = try self.replaceCommand()
case .update:
Expand Down Expand Up @@ -442,7 +442,7 @@ internal extension Sequence where Element: ParseObject {
try await object.saveCommand(ignoringCustomObjectIdConfig: ignoringCustomObjectIdConfig)
)
case .create:
commands.append(await object.createCommand())
commands.append(try await object.createCommand())
case .replace:
commands.append(try object.replaceCommand())
case .update:
Expand Down
4 changes: 2 additions & 2 deletions Sources/ParseSwift/Objects/ParseObject.swift
Original file line number Diff line number Diff line change
Expand Up @@ -909,8 +909,8 @@ extension ParseObject {
ignoringCustomObjectIdConfig: ignoringCustomObjectIdConfig)
}

internal func createCommand() async -> API.Command<Self, Self> {
await API.Command<Self, Self>.create(self)
internal func createCommand() async throws -> API.Command<Self, Self> {
try await API.Command<Self, Self>.create(self)
}

internal func replaceCommand() throws -> API.Command<Self, Self> {
Expand Down
4 changes: 2 additions & 2 deletions Sources/ParseSwift/Objects/ParseUser+async.swift
Original file line number Diff line number Diff line change
Expand Up @@ -572,7 +572,7 @@ internal extension ParseUser {
case .save:
command = try await self.saveCommand(ignoringCustomObjectIdConfig: ignoringCustomObjectIdConfig)
case .create:
command = await self.createCommand()
command = try await self.createCommand()
case .replace:
command = try await self.replaceCommand()
case .update:
Expand Down Expand Up @@ -631,7 +631,7 @@ internal extension Sequence where Element: ParseUser {
try await object.saveCommand(ignoringCustomObjectIdConfig: ignoringCustomObjectIdConfig)
)
case .create:
commands.append(await object.createCommand())
commands.append(try await object.createCommand())
case .replace:
commands.append(try await object.replaceCommand())
case .update:
Expand Down
10 changes: 6 additions & 4 deletions Sources/ParseSwift/Objects/ParseUser.swift
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,8 @@ public extension ParseUser {
// MARK: Convenience
extension ParseUser {

func endpoint(_ method: API.Method) -> API.Endpoint {
func endpoint(_ method: API.Method) async throws -> API.Endpoint {
try await yieldIfNotInitialized()
if !Parse.configuration.isRequiringCustomObjectIds || method != .POST {
return endpoint
} else {
Expand Down Expand Up @@ -1079,17 +1080,18 @@ extension ParseUser {
}

func saveCommand(ignoringCustomObjectIdConfig: Bool = false) async throws -> API.Command<Self, Self> {
try await yieldIfNotInitialized()
if Parse.configuration.isRequiringCustomObjectIds && objectId == nil && !ignoringCustomObjectIdConfig {
throw ParseError(code: .missingObjectId, message: "objectId must not be nil")
}
if isSaved {
return try await replaceCommand() // MARK: Should be switched to "updateCommand" when server supports PATCH.
}
return await createCommand()
return try await createCommand()
}

// MARK: Saving ParseObjects - private
func createCommand() async -> API.Command<Self, Self> {
func createCommand() async throws -> API.Command<Self, Self> {
var object = self
if object.ACL == nil,
let acl = try? await ParseACL.defaultACL() {
Expand All @@ -1099,7 +1101,7 @@ extension ParseUser {
try ParseCoding.jsonDecoder().decode(CreateResponse.self, from: data).apply(to: object)
}
return API.Command<Self, Self>(method: .POST,
path: endpoint(.POST),
path: try await endpoint(.POST),
body: object,
mapper: mapper)
}
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.4.1"
static let version = "5.4.2"
static let fileManagementDirectory = "parse/"
static let fileManagementPrivateDocumentsDirectory = "Private Documents/"
static let fileManagementLibraryDirectory = "Library/"
Expand Down
15 changes: 14 additions & 1 deletion Sources/ParseSwift/Protocols/Objectable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,8 @@ extension Objectable {
}

/// 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
Expand All @@ -88,11 +90,22 @@ extension Objectable {
}
}

/// Specifies if a `ParseObject` has been saved.
public func isSaved() async throws -> Bool {
try await yieldIfNotInitialized()
if !Parse.configuration.isRequiringCustomObjectIds {
return objectId != nil
} else {
return objectId != nil && createdAt != nil
}
}

func toPointer() throws -> PointerType {
return try PointerType(self)
}

func endpoint(_ method: API.Method) -> API.Endpoint {
func endpoint(_ method: API.Method) async throws -> API.Endpoint {
try await yieldIfNotInitialized()
if !Parse.configuration.isRequiringCustomObjectIds || method != .POST {
return endpoint
} else {
Expand Down
33 changes: 17 additions & 16 deletions Sources/ParseSwift/Types/ParseFile.swift
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,8 @@ extension ParseFile {
return options
}

func checkDownloadsForFile(options: API.Options) throws -> ParseFile? {
func checkDownloadsForFile(options: API.Options) async throws -> ParseFile? {
try await yieldIfNotInitialized()
var cachePolicy: URLRequest.CachePolicy = Parse.configuration.requestCachePolicy
var shouldBreak = false
for option in options {
Expand Down Expand Up @@ -481,23 +482,23 @@ extension ParseFile {
progress: ((URLSessionDownloadTask, Int64, Int64, Int64) -> Void)? = nil,
completion: @escaping (Result<Self, ParseError>) -> Void) {
let options = setDefaultOptions(options)
do {
guard let file = try checkDownloadsForFile(options: options) else {
throw ParseError(code: .unsavedFileFailure,
message: "File not downloaded")
}
callbackQueue.async {
completion(.success(file))
}
} catch {
let parseError = error as? ParseError ?? ParseError(swift: error)
guard parseError.code != .unsavedFileFailure else {
Task {
do {
guard let file = try await checkDownloadsForFile(options: options) else {
throw ParseError(code: .unsavedFileFailure,
message: "File not downloaded")
}
callbackQueue.async {
completion(.failure(parseError))
completion(.success(file))
}
} catch {
let parseError = error as? ParseError ?? ParseError(swift: error)
guard parseError.code != .unsavedFileFailure else {
callbackQueue.async {
completion(.failure(parseError))
}
return
}
return
}
Task {
await downloadFileCommand()
.execute(options: options,
callbackQueue: callbackQueue,
Expand Down
Loading

0 comments on commit a746db0

Please sign in to comment.