diff --git a/.swiftlint.yml b/.swiftlint.yml index bd0c1932d..ef7dcf148 100644 --- a/.swiftlint.yml +++ b/.swiftlint.yml @@ -4,3 +4,4 @@ disabled_rules: - function_body_length - type_body_length - inclusive_language + - comment_spacing diff --git a/CHANGELOG.md b/CHANGELOG.md index 051a91295..2801cb341 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,9 @@ [Full Changelog](https://github.com/parse-community/Parse-Swift/compare/1.0.2...main) * _Contributing to this repo? Add info about your change here to be included in next release_ +__New features__ +- Idempotency support ([#62](https://github.com/parse-community/Parse-Swift/pull/62)), thanks to [Corey Baker](https://github.com/cbaker6). + ### 1.0.2 [Full Changelog](https://github.com/parse-community/Parse-Swift/compare/1.0.0...1.0.2) diff --git a/ParseSwift.playground/Pages/12 - Roles and Relations.xcplaygroundpage/Contents.swift b/ParseSwift.playground/Pages/12 - Roles and Relations.xcplaygroundpage/Contents.swift index bd0dfdcf6..e7191192d 100644 --- a/ParseSwift.playground/Pages/12 - Roles and Relations.xcplaygroundpage/Contents.swift +++ b/ParseSwift.playground/Pages/12 - Roles and Relations.xcplaygroundpage/Contents.swift @@ -55,7 +55,7 @@ if let currentUser = User.current { do { //: Create the actual Role with a name and ACL. - var adminRole = try Role(name: "Administrator", acl: acl) + let adminRole = try Role(name: "Administrator", acl: acl) adminRole.save { result in switch result { case .success(let saved): @@ -100,26 +100,34 @@ do { //: To retrieve the users who are all Administrators, we need to query the relation. let templateUser = User() -savedRole!.users.query(templateUser).find { result in - switch result { - case .success(let relatedUsers): - print("The following users are part of the \"\(savedRole!.name) role: \(relatedUsers)") +do { + try savedRole!.users.query(templateUser).find { result in + switch result { + case .success(let relatedUsers): + print("The following users are part of the \"\(savedRole!.name) role: \(relatedUsers)") - case .failure(let error): - print("Error saving role: \(error)") + case .failure(let error): + print("Error saving role: \(error)") + } } +} catch { + print(error) } //: Of course, you can remove users from the roles as well. -try savedRole!.users.remove([User.current!]).save { result in - switch result { - case .success(let saved): - print("The role removed successfully: \(saved)") - print("Check \"users\" field in your \"Role\" class in Parse Dashboard.") +do { + try savedRole!.users.remove([User.current!]).save { result in + switch result { + case .success(let saved): + print("The role removed successfully: \(saved)") + print("Check \"users\" field in your \"Role\" class in Parse Dashboard.") - case .failure(let error): - print("Error saving role: \(error)") + case .failure(let error): + print("Error saving role: \(error)") + } } +} catch { + print(error) } //: Additional roles can be created and tied to already created roles. Lets create a "Member" role. @@ -134,7 +142,7 @@ acl.setWriteAccess(user: User.current!, value: false) do { //: Create the actual Role with a name and ACL. - var memberRole = try Role(name: "Member", acl: acl) + let memberRole = try Role(name: "Member", acl: acl) memberRole.save { result in switch result { case .success(let saved): @@ -171,7 +179,6 @@ do { print("Error saving role: \(error)") } } - } catch { print("Error: \(error)") } @@ -189,15 +196,19 @@ savedRole!.queryRoles?.find { result in } //: Of course, you can remove users from the roles as well. -try savedRole!.roles.remove([savedRoleModerator!]).save { result in - switch result { - case .success(let saved): - print("The role removed successfully: \(saved)") - print("Check the \"roles\" field in your \"Role\" class in Parse Dashboard.") +do { + try savedRole!.roles.remove([savedRoleModerator!]).save { result in + switch result { + case .success(let saved): + print("The role removed successfully: \(saved)") + print("Check the \"roles\" field in your \"Role\" class in Parse Dashboard.") - case .failure(let error): - print("Error saving role: \(error)") + case .failure(let error): + print("Error saving role: \(error)") + } } +} catch { + print(error) } //: All `ParseObjects` have a `ParseRelation` attribute that be used on instances. diff --git a/ParseSwift.playground/Pages/13 - Operations.xcplaygroundpage/Contents.swift b/ParseSwift.playground/Pages/13 - Operations.xcplaygroundpage/Contents.swift index 8be899d04..6dc569e34 100644 --- a/ParseSwift.playground/Pages/13 - Operations.xcplaygroundpage/Contents.swift +++ b/ParseSwift.playground/Pages/13 - Operations.xcplaygroundpage/Contents.swift @@ -45,7 +45,7 @@ let incrementOperation = savedScore incrementOperation.save { result in switch result { case .success: - print("Original score: \(savedScore). Check the new score on Parse Dashboard.") + print("Original score: \(String(describing: savedScore)). Check the new score on Parse Dashboard.") case .failure(let error): assertionFailure("Error saving: \(error)") } @@ -54,7 +54,7 @@ incrementOperation.save { result in //: You can increment the score again syncronously. do { _ = try incrementOperation.save() - print("Original score: \(savedScore). Check the new score on Parse Dashboard.") + print("Original score: \(String(describing: savedScore)). Check the new score on Parse Dashboard.") } catch { print(error) } diff --git a/ParseSwift.playground/Pages/14 - Config.xcplaygroundpage/Contents.swift b/ParseSwift.playground/Pages/14 - Config.xcplaygroundpage/Contents.swift index 088d798fe..996847efa 100644 --- a/ParseSwift.playground/Pages/14 - Config.xcplaygroundpage/Contents.swift +++ b/ParseSwift.playground/Pages/14 - Config.xcplaygroundpage/Contents.swift @@ -60,7 +60,7 @@ config.fetch { result in } //: Anytime you fetch or update your Config successfully, it's automatically saved to your Keychain. -print(Config.current) +print(Config.current ?? "No config") PlaygroundPage.current.finishExecution() diff --git a/ParseSwift.playground/Pages/2 - Finding Objects.xcplaygroundpage/Contents.swift b/ParseSwift.playground/Pages/2 - Finding Objects.xcplaygroundpage/Contents.swift index 48e787e40..0d0f709b8 100644 --- a/ParseSwift.playground/Pages/2 - Finding Objects.xcplaygroundpage/Contents.swift +++ b/ParseSwift.playground/Pages/2 - Finding Objects.xcplaygroundpage/Contents.swift @@ -58,7 +58,7 @@ query.first { results in switch results { case .success(let score): - guard let objectId = score.objectId, + guard score.objectId != nil, let createdAt = score.createdAt else { fatalError() } assert(createdAt.timeIntervalSince1970 > afterDate.timeIntervalSince1970, "date should be ok") print("Found score: \(score)") diff --git a/ParseSwift.playground/Pages/4 - User - Continued.xcplaygroundpage/Contents.swift b/ParseSwift.playground/Pages/4 - User - Continued.xcplaygroundpage/Contents.swift index 049027588..284427846 100644 --- a/ParseSwift.playground/Pages/4 - User - Continued.xcplaygroundpage/Contents.swift +++ b/ParseSwift.playground/Pages/4 - User - Continued.xcplaygroundpage/Contents.swift @@ -81,7 +81,7 @@ do { User.anonymous.login { result in switch result { case .success: - print("Successfully logged in \(User.current)") + print("Successfully logged in \(String(describing: User.current))") case .failure(let error): print("Error logging in: \(error)") } diff --git a/ParseSwift.playground/Pages/9 - Files.xcplaygroundpage/Contents.swift b/ParseSwift.playground/Pages/9 - Files.xcplaygroundpage/Contents.swift index 4e0d21467..f156a3980 100644 --- a/ParseSwift.playground/Pages/9 - Files.xcplaygroundpage/Contents.swift +++ b/ParseSwift.playground/Pages/9 - Files.xcplaygroundpage/Contents.swift @@ -74,7 +74,7 @@ score.save { result in picture.fetch { result in switch result { case .success(let fetchedFile): - print("The file is now saved on your device at: \(fetchedFile.localURL)") + print("The file is now saved on your device at: \(String(describing: fetchedFile.localURL))") print("The full details of your profilePicture ParseFile are: \(fetchedFile)") case .failure(let error): assertionFailure("Error fetching: \(error)") @@ -107,7 +107,7 @@ do { //: To get the contents updated `ParseFile`, you need to fetch your GameScore. let fetchedScore = try savedScore.fetch() - if var myData = fetchedScore.myData { + if let myData = fetchedScore.myData { guard let url = myData.url else { fatalError("Error: file should have url.") diff --git a/ParseSwift.playground/contents.xcplayground b/ParseSwift.playground/contents.xcplayground index c855788d3..33cd014ec 100644 --- a/ParseSwift.playground/contents.xcplayground +++ b/ParseSwift.playground/contents.xcplayground @@ -11,9 +11,10 @@ - - - + + + + diff --git a/Sources/ParseSwift/API/API.swift b/Sources/ParseSwift/API/API.swift index 8001178ce..34fec5f91 100644 --- a/Sources/ParseSwift/API/API.swift +++ b/Sources/ParseSwift/API/API.swift @@ -156,6 +156,8 @@ public struct API { headers["X-Parse-Installation-Id"] = installationId } + headers["X-Parse-Request-Id"] = UUID().uuidString.lowercased() + options.forEach { (option) in switch option { case .useMasterKey: diff --git a/Tests/ParseSwiftTests/APICommandTests.swift b/Tests/ParseSwiftTests/APICommandTests.swift index 3b280ced0..94c705e18 100644 --- a/Tests/ParseSwiftTests/APICommandTests.swift +++ b/Tests/ParseSwiftTests/APICommandTests.swift @@ -151,4 +151,8 @@ class APICommandTests: XCTestCase { } } + func testIdempodency() { + let headers = API.getHeaders(options: []) + XCTAssertNotNil(headers["X-Parse-Request-Id"]) + } }