diff --git a/.travis.yml b/.travis.yml index 4bf8fe751..29959019d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,5 @@ osx_image: xcode9 language: objective-c -before_script: brew update && brew upgrade swiftlint stage: Build env: matrix: diff --git a/ParseSwift.playground/Pages/1- Your first Object.xcplaygroundpage/Contents.swift b/ParseSwift.playground/Pages/1- Your first Object.xcplaygroundpage/Contents.swift index 0e0b36a3c..64f7b1114 100644 --- a/ParseSwift.playground/Pages/1- Your first Object.xcplaygroundpage/Contents.swift +++ b/ParseSwift.playground/Pages/1- Your first Object.xcplaygroundpage/Contents.swift @@ -10,6 +10,11 @@ PlaygroundPage.current.needsIndefiniteExecution = true initializeParse() +ParseSwift.initialize(applicationId: "applicationId", + clientKey: "clientKey", + masterKey: "masterKey", + serverURL: URL(string: "http://localhost:1337/1")!) + struct GameScore: ParseSwift.ObjectType { //: Those are required for Object var objectId: String? @@ -28,7 +33,7 @@ struct GameScore: ParseSwift.ObjectType { var score = GameScore(score: 10) -guard let score = try? score.sync.save() else { fatalError() } +guard let score = try? score.save() else { fatalError() } assert(score.objectId != nil) assert(score.createdAt != nil) assert(score.updatedAt != nil) @@ -37,19 +42,18 @@ assert(score.score == 10) // Need to make it a var as Value Types var changedScore = score changedScore.score = 200 -guard let savedScore = try? changedScore.sync.save() else { fatalError() } +guard let savedScore = try? changedScore.save() else { fatalError() } assert(score.score == 10) assert(score.objectId == changedScore.objectId) -// TODO: Add support for sync saveAll let score2 = GameScore(score: 3) -guard let results = try? GameScore.saveAllSync(score, score2) else { fatalError() } +guard let results = try? GameScore.saveAll(score, score2) else { fatalError() } results.forEach { (result) in let (_, error) = result assert(error == nil, "error should be nil") } -guard let otherResults = try? [score, score2].saveAllSync() else { fatalError() } +guard let otherResults = try? [score, score2].saveAll() else { fatalError() } otherResults.forEach { (result) in let (_, error) = result assert(error == nil, "error should be nil") diff --git a/ParseSwift.playground/Pages/2- Finding Objects.xcplaygroundpage/Contents.swift b/ParseSwift.playground/Pages/2- Finding Objects.xcplaygroundpage/Contents.swift index e3f684e07..6f609fddd 100644 --- a/ParseSwift.playground/Pages/2- Finding Objects.xcplaygroundpage/Contents.swift +++ b/ParseSwift.playground/Pages/2- Finding Objects.xcplaygroundpage/Contents.swift @@ -21,12 +21,14 @@ struct GameScore: ParseSwift.ObjectType { var score = GameScore() score.score = 200 - -score.save { _ in - var query = GameScore.query("score" > 100, "createdAt" < Date().addingTimeInterval(-300)) - query.limit(2).find { (scores) in - print(scores) - } +try score.save() + +let afterDate = Date().addingTimeInterval(-300) +var query = GameScore.query("score" > 100, "createdAt" > afterDate) +let results = try query.limit(2).find(options: []) +assert(results.count >= 1) +results.forEach { (score) in + guard let createdAt = score.createdAt else { fatalError() } + assert(createdAt.timeIntervalSince1970 > afterDate.timeIntervalSince1970, "date should be ok") } - //: [Next](@next) diff --git a/ParseSwift.playground/Pages/3- Users.xcplaygroundpage/Contents.swift b/ParseSwift.playground/Pages/3- Users.xcplaygroundpage/Contents.swift index 91acd6224..c7578568e 100644 --- a/ParseSwift.playground/Pages/3- Users.xcplaygroundpage/Contents.swift +++ b/ParseSwift.playground/Pages/3- Users.xcplaygroundpage/Contents.swift @@ -46,31 +46,23 @@ struct User: ParseSwift.UserType { // } //} -User.signup(username: "hello10", password: "world") { (response) in - guard case .success(var user) = response else { return } - print(user) -} - -User.login(username: "hello", password: "world") { (response) in - guard case .success(var user) = response else { return } +do { + let user = try User.signup(username: "hello10", password: "world") + var loggedIn = try User.login(username: "hello", password: "workd") var acl = user.ACL acl?.publicRead = false acl?.publicWrite = true - user.ACL = acl - user.save { response in - switch response { - case .success: - assert(true) - case .error: - assert(false) - default: break - } - } + loggedIn.ACL = acl + try loggedIn.save() +} catch let e { + e + e.localizedDescription + fatalError("\(e.localizedDescription)") } -var acl = ACL() -acl.publicRead = true -acl.setReadAccess(userId: "dsas", value: true) -acl.setWriteAccess(userId: "dsas", value: true) +//var acl = ACL() +//acl.publicRead = true +//acl.setReadAccess(userId: "dsas", value: true) +//acl.setWriteAccess(userId: "dsas", value: true) //: [Next](@next) diff --git a/ParseSwift.xcodeproj/project.pbxproj b/ParseSwift.xcodeproj/project.pbxproj index e8fcf478e..ffa99aa47 100644 --- a/ParseSwift.xcodeproj/project.pbxproj +++ b/ParseSwift.xcodeproj/project.pbxproj @@ -8,8 +8,6 @@ /* Begin PBXBuildFile section */ 4A2D8C811F48B3A900EE1FCC /* ParseEncoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A2D8C801F48B3A900EE1FCC /* ParseEncoder.swift */; }; - 4A2F14911F4A41B900A7A7EF /* Synchronous.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A2F14901F4A41B900A7A7EF /* Synchronous.swift */; }; - 4A2F14921F4A434100A7A7EF /* Synchronous.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A2F14901F4A41B900A7A7EF /* Synchronous.swift */; }; 4A2F14941F4A5E1E00A7A7EF /* ObjectType+Equatable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A2F14931F4A5E1E00A7A7EF /* ObjectType+Equatable.swift */; }; 4A2F14961F4A5F2900A7A7EF /* Responses.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A2F14951F4A5F2900A7A7EF /* Responses.swift */; }; 4A2F14981F4A5F6900A7A7EF /* ObjectType+Batch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A2F14971F4A5F6900A7A7EF /* ObjectType+Batch.swift */; }; @@ -17,7 +15,7 @@ 4A2F149A1F4A5FBA00A7A7EF /* ObjectType+Equatable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A2F14931F4A5E1E00A7A7EF /* ObjectType+Equatable.swift */; }; 4A2F149B1F4A5FBA00A7A7EF /* ObjectType+Batch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A2F14971F4A5F6900A7A7EF /* ObjectType+Batch.swift */; }; 4A65114F1F48E3F3005237DF /* ParseEncoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A2D8C801F48B3A900EE1FCC /* ParseEncoder.swift */; }; - 4A6511501F48E400005237DF /* RESTBatchCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AC397711F488F9E00DEA9D3 /* RESTBatchCommand.swift */; }; + 4A6511501F48E400005237DF /* BatchUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AC397711F488F9E00DEA9D3 /* BatchUtils.swift */; }; 4A6511511F48E406005237DF /* ACL.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AC3976F1F48778900DEA9D3 /* ACL.swift */; }; 4A6511521F48E406005237DF /* GeoPoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A82B7ED1F254B820063D731 /* GeoPoint.swift */; }; 4A6511531F48E410005237DF /* API.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AC397731F488FF900DEA9D3 /* API.swift */; }; @@ -26,8 +24,6 @@ 4A82B7F61F254CCE0063D731 /* Parse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A82B7EE1F254B820063D731 /* Parse.swift */; }; 4A82B7F71F254CCE0063D731 /* ObjectType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A82B7EC1F254B820063D731 /* ObjectType.swift */; }; 4A82B7F81F254CCE0063D731 /* Pointer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A82B7F11F254B820063D731 /* Pointer.swift */; }; - 4A82B7F91F254CCE0063D731 /* Result.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A82B7F21F254B820063D731 /* Result.swift */; }; - 4A82B7FC1F255B5F0063D731 /* RESTCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A82B7FB1F255B5F0063D731 /* RESTCommand.swift */; }; 4A82B7FF1F256A8F0063D731 /* Query.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A82B7FE1F256A8F0063D731 /* Query.swift */; }; 4A82B8011F256B330063D731 /* ObjectType+Query.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A82B8001F256B330063D731 /* ObjectType+Query.swift */; }; 4A99A4691F2650CA00D72A59 /* ParseMutationContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A99A4681F2650CA00D72A59 /* ParseMutationContainer.swift */; }; @@ -37,12 +33,20 @@ 4AB8B5031F254AE10070F682 /* ParseSwiftTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AB8B5021F254AE10070F682 /* ParseSwiftTests.swift */; }; 4AB8B5051F254AE10070F682 /* Parse.h in Headers */ = {isa = PBXBuildFile; fileRef = 4AB8B4F71F254AE10070F682 /* Parse.h */; settings = {ATTRIBUTES = (Public, ); }; }; 4AC397701F48778900DEA9D3 /* ACL.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AC3976F1F48778900DEA9D3 /* ACL.swift */; }; - 4AC397721F488F9E00DEA9D3 /* RESTBatchCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AC397711F488F9E00DEA9D3 /* RESTBatchCommand.swift */; }; + 4AC397721F488F9E00DEA9D3 /* BatchUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AC397711F488F9E00DEA9D3 /* BatchUtils.swift */; }; 4AC397741F488FF900DEA9D3 /* API.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AC397731F488FF900DEA9D3 /* API.swift */; }; 4AEBA5491F26519B00628B17 /* AddOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AEBA5481F26519B00628B17 /* AddOperation.swift */; }; 4AEBA54B1F2651D900628B17 /* RemoveOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AEBA54A1F2651D900628B17 /* RemoveOperation.swift */; }; 4AEBA54D1F26523800628B17 /* DeleteOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AEBA54C1F26523800628B17 /* DeleteOperation.swift */; }; 4AEBA54F1F265A0D00628B17 /* UserType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AEBA54E1F265A0D00628B17 /* UserType.swift */; }; + 4AF85BD31F78011100665264 /* ParseError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AF85BD21F78011100665264 /* ParseError.swift */; }; + 4AF85BD61F7803C100665264 /* ParseError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AF85BD21F78011100665264 /* ParseError.swift */; }; + 4AF85BD81F78144900665264 /* URLSession+sync.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AF85BD71F78144900665264 /* URLSession+sync.swift */; }; + 4AF85BD91F78145C00665264 /* URLSession+sync.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AF85BD71F78144900665264 /* URLSession+sync.swift */; }; + 4AF85BDA1F781ED000665264 /* Asynchronous.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A2F14901F4A41B900A7A7EF /* Asynchronous.swift */; }; + 4AF85BDB1F781ED100665264 /* Asynchronous.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A2F14901F4A41B900A7A7EF /* Asynchronous.swift */; }; + 4AF85BDD1F783B9800665264 /* API+Commands.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AF85BDC1F783B9800665264 /* API+Commands.swift */; }; + 4AF85BDE1F783BB400665264 /* API+Commands.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AF85BDC1F783B9800665264 /* API+Commands.swift */; }; 4AFDA72A1F26DAE1002AE4FC /* Parse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A82B7EE1F254B820063D731 /* Parse.swift */; }; 4AFDA72B1F26DAE1002AE4FC /* ObjectType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A82B7EC1F254B820063D731 /* ObjectType.swift */; }; 4AFDA72C1F26DAE1002AE4FC /* UserType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AEBA54E1F265A0D00628B17 /* UserType.swift */; }; @@ -56,8 +60,6 @@ 4AFDA7341F26DAE1002AE4FC /* Pointer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A82B7F11F254B820063D731 /* Pointer.swift */; }; 4AFDA7351F26DAE1002AE4FC /* Query.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A82B7FE1F256A8F0063D731 /* Query.swift */; }; 4AFDA7361F26DAE1002AE4FC /* ObjectType+Query.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A82B8001F256B330063D731 /* ObjectType+Query.swift */; }; - 4AFDA7371F26DAE1002AE4FC /* Result.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A82B7F21F254B820063D731 /* Result.swift */; }; - 4AFDA7381F26DAF4002AE4FC /* RESTCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A82B7FB1F255B5F0063D731 /* RESTCommand.swift */; }; 4AFDA7391F26DAF8002AE4FC /* Parse.h in Headers */ = {isa = PBXBuildFile; fileRef = 4AB8B4F71F254AE10070F682 /* Parse.h */; settings = {ATTRIBUTES = (Public, ); }; }; /* End PBXBuildFile section */ @@ -74,7 +76,7 @@ /* Begin PBXFileReference section */ 4A1120BF1F49FC3300E32D94 /* LinuxMain.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinuxMain.swift; sourceTree = ""; }; 4A2D8C801F48B3A900EE1FCC /* ParseEncoder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParseEncoder.swift; sourceTree = ""; }; - 4A2F14901F4A41B900A7A7EF /* Synchronous.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Synchronous.swift; sourceTree = ""; }; + 4A2F14901F4A41B900A7A7EF /* Asynchronous.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Asynchronous.swift; sourceTree = ""; }; 4A2F14931F4A5E1E00A7A7EF /* ObjectType+Equatable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ObjectType+Equatable.swift"; sourceTree = ""; }; 4A2F14951F4A5F2900A7A7EF /* Responses.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Responses.swift; sourceTree = ""; }; 4A2F14971F4A5F6900A7A7EF /* ObjectType+Batch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ObjectType+Batch.swift"; sourceTree = ""; }; @@ -83,8 +85,6 @@ 4A82B7EE1F254B820063D731 /* Parse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Parse.swift; sourceTree = ""; }; 4A82B7F01F254B820063D731 /* File.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = File.swift; sourceTree = ""; }; 4A82B7F11F254B820063D731 /* Pointer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Pointer.swift; sourceTree = ""; }; - 4A82B7F21F254B820063D731 /* Result.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Result.swift; sourceTree = ""; }; - 4A82B7FB1F255B5F0063D731 /* RESTCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RESTCommand.swift; sourceTree = ""; }; 4A82B7FE1F256A8F0063D731 /* Query.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Query.swift; sourceTree = ""; }; 4A82B8001F256B330063D731 /* ObjectType+Query.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ObjectType+Query.swift"; sourceTree = ""; }; 4A99A4681F2650CA00D72A59 /* ParseMutationContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParseMutationContainer.swift; sourceTree = ""; }; @@ -97,13 +97,16 @@ 4AB8B5021F254AE10070F682 /* ParseSwiftTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParseSwiftTests.swift; sourceTree = ""; }; 4AB8B5041F254AE10070F682 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 4AC3976F1F48778900DEA9D3 /* ACL.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ACL.swift; sourceTree = ""; }; - 4AC397711F488F9E00DEA9D3 /* RESTBatchCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RESTBatchCommand.swift; sourceTree = ""; }; + 4AC397711F488F9E00DEA9D3 /* BatchUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BatchUtils.swift; sourceTree = ""; }; 4AC397731F488FF900DEA9D3 /* API.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = API.swift; sourceTree = ""; }; 4ACFC2E21F3CA21F0046F3A3 /* ParseSwift.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; path = ParseSwift.playground; sourceTree = ""; }; 4AEBA5481F26519B00628B17 /* AddOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddOperation.swift; sourceTree = ""; }; 4AEBA54A1F2651D900628B17 /* RemoveOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoveOperation.swift; sourceTree = ""; }; 4AEBA54C1F26523800628B17 /* DeleteOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeleteOperation.swift; sourceTree = ""; }; 4AEBA54E1F265A0D00628B17 /* UserType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserType.swift; sourceTree = ""; }; + 4AF85BD21F78011100665264 /* ParseError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParseError.swift; sourceTree = ""; }; + 4AF85BD71F78144900665264 /* URLSession+sync.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URLSession+sync.swift"; sourceTree = ""; }; + 4AF85BDC1F783B9800665264 /* API+Commands.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "API+Commands.swift"; sourceTree = ""; }; 4AFDA7121F26D9A5002AE4FC /* ParseSwift.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = ParseSwift.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 4AFDA7151F26D9A5002AE4FC /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; /* End PBXFileReference section */ @@ -171,6 +174,7 @@ isa = PBXGroup; children = ( 4AC3976F1F48778900DEA9D3 /* ACL.swift */, + 4AF85BD21F78011100665264 /* ParseError.swift */, 4A82B7F01F254B820063D731 /* File.swift */, 4A82B7ED1F254B820063D731 /* GeoPoint.swift */, 4A82B7F11F254B820063D731 /* Pointer.swift */, @@ -219,14 +223,12 @@ isa = PBXGroup; children = ( 4A82B7EE1F254B820063D731 /* Parse.swift */, - 4AC397731F488FF900DEA9D3 /* API.swift */, - 4A2F14901F4A41B900A7A7EF /* Synchronous.swift */, + 4A2F14901F4A41B900A7A7EF /* Asynchronous.swift */, 4AC397761F4895A200DEA9D3 /* Objects Protocols */, 4A82B7FD1F25691B0063D731 /* Types */, - 4A82B7F21F254B820063D731 /* Result.swift */, 4A2F149C1F4A604900A7A7EF /* Encoder */, 4A99A46A1F2650E200D72A59 /* Mutations */, - 4AC397771F4895C000DEA9D3 /* REST */, + 4AC397771F4895C000DEA9D3 /* API */, 4AB8B4F71F254AE10070F682 /* Parse.h */, ); path = ParseSwift; @@ -253,14 +255,16 @@ path = "Objects Protocols"; sourceTree = ""; }; - 4AC397771F4895C000DEA9D3 /* REST */ = { + 4AC397771F4895C000DEA9D3 /* API */ = { isa = PBXGroup; children = ( - 4A82B7FB1F255B5F0063D731 /* RESTCommand.swift */, - 4AC397711F488F9E00DEA9D3 /* RESTBatchCommand.swift */, + 4AC397731F488FF900DEA9D3 /* API.swift */, + 4AF85BDC1F783B9800665264 /* API+Commands.swift */, + 4AC397711F488F9E00DEA9D3 /* BatchUtils.swift */, + 4AF85BD71F78144900665264 /* URLSession+sync.swift */, 4A2F14951F4A5F2900A7A7EF /* Responses.swift */, ); - path = REST; + path = API; sourceTree = ""; }; 4AFDA7131F26D9A5002AE4FC /* ParseSwift-macOS */ = { @@ -429,7 +433,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "if which swiftlint >/dev/null; then\nswiftlint\nelse\necho \"wa rning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi"; + shellScript = "if which swiftlint >/dev/null; then\nswiftlint\nelse\necho \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi"; }; 4A6511551F49D544005237DF /* SwiftLint */ = { isa = PBXShellScriptBuildPhase; @@ -453,7 +457,6 @@ buildActionMask = 2147483647; files = ( 4A2F14961F4A5F2900A7A7EF /* Responses.swift in Sources */, - 4A82B7FC1F255B5F0063D731 /* RESTCommand.swift in Sources */, 4AC397741F488FF900DEA9D3 /* API.swift in Sources */, 4A82B7F71F254CCE0063D731 /* ObjectType.swift in Sources */, 4A82B7F51F254CCE0063D731 /* GeoPoint.swift in Sources */, @@ -462,16 +465,18 @@ 4A99A4691F2650CA00D72A59 /* ParseMutationContainer.swift in Sources */, 4AC397701F48778900DEA9D3 /* ACL.swift in Sources */, 4A2D8C811F48B3A900EE1FCC /* ParseEncoder.swift in Sources */, - 4A2F14911F4A41B900A7A7EF /* Synchronous.swift in Sources */, 4A99A46E1F26512100D72A59 /* IncrementOperation.swift in Sources */, 4A82B8011F256B330063D731 /* ObjectType+Query.swift in Sources */, 4A82B7FF1F256A8F0063D731 /* Query.swift in Sources */, - 4A82B7F91F254CCE0063D731 /* Result.swift in Sources */, + 4AF85BD81F78144900665264 /* URLSession+sync.swift in Sources */, 4A2F14981F4A5F6900A7A7EF /* ObjectType+Batch.swift in Sources */, 4A82B7F41F254CCE0063D731 /* File.swift in Sources */, + 4AF85BD31F78011100665264 /* ParseError.swift in Sources */, + 4AF85BDA1F781ED000665264 /* Asynchronous.swift in Sources */, 4A2F14941F4A5E1E00A7A7EF /* ObjectType+Equatable.swift in Sources */, - 4AC397721F488F9E00DEA9D3 /* RESTBatchCommand.swift in Sources */, + 4AC397721F488F9E00DEA9D3 /* BatchUtils.swift in Sources */, 4AEBA54D1F26523800628B17 /* DeleteOperation.swift in Sources */, + 4AF85BDD1F783B9800665264 /* API+Commands.swift in Sources */, 4A82B7F81F254CCE0063D731 /* Pointer.swift in Sources */, 4AEBA5491F26519B00628B17 /* AddOperation.swift in Sources */, 4A99A46C1F2650FF00D72A59 /* AddUniqueOperation.swift in Sources */, @@ -495,22 +500,23 @@ 4AFDA7301F26DAE1002AE4FC /* DeleteOperation.swift in Sources */, 4AFDA7361F26DAE1002AE4FC /* ObjectType+Query.swift in Sources */, 4AFDA7341F26DAE1002AE4FC /* Pointer.swift in Sources */, - 4A6511501F48E400005237DF /* RESTBatchCommand.swift in Sources */, - 4AFDA7371F26DAE1002AE4FC /* Result.swift in Sources */, + 4A6511501F48E400005237DF /* BatchUtils.swift in Sources */, 4AFDA72A1F26DAE1002AE4FC /* Parse.swift in Sources */, 4AFDA7351F26DAE1002AE4FC /* Query.swift in Sources */, - 4AFDA7381F26DAF4002AE4FC /* RESTCommand.swift in Sources */, - 4A2F14921F4A434100A7A7EF /* Synchronous.swift in Sources */, 4A2F14991F4A5FB500A7A7EF /* Responses.swift in Sources */, 4A2F149A1F4A5FBA00A7A7EF /* ObjectType+Equatable.swift in Sources */, 4A65114F1F48E3F3005237DF /* ParseEncoder.swift in Sources */, 4AFDA72E1F26DAE1002AE4FC /* AddOperation.swift in Sources */, 4AFDA7311F26DAE1002AE4FC /* AddUniqueOperation.swift in Sources */, + 4AF85BD91F78145C00665264 /* URLSession+sync.swift in Sources */, 4A2F149B1F4A5FBA00A7A7EF /* ObjectType+Batch.swift in Sources */, 4A6511521F48E406005237DF /* GeoPoint.swift in Sources */, + 4AF85BD61F7803C100665264 /* ParseError.swift in Sources */, + 4AF85BDB1F781ED100665264 /* Asynchronous.swift in Sources */, 4AFDA7331F26DAE1002AE4FC /* File.swift in Sources */, 4A6511531F48E410005237DF /* API.swift in Sources */, 4AFDA7321F26DAE1002AE4FC /* IncrementOperation.swift in Sources */, + 4AF85BDE1F783BB400665264 /* API+Commands.swift in Sources */, 4AFDA72D1F26DAE1002AE4FC /* ParseMutationContainer.swift in Sources */, 4AFDA72C1F26DAE1002AE4FC /* UserType.swift in Sources */, 4A6511511F48E406005237DF /* ACL.swift in Sources */, diff --git a/Sources/ParseSwift/API/API+Commands.swift b/Sources/ParseSwift/API/API+Commands.swift new file mode 100644 index 000000000..86a458d28 --- /dev/null +++ b/Sources/ParseSwift/API/API+Commands.swift @@ -0,0 +1,140 @@ +// +// API+Commands.swift +// ParseSwift (iOS) +// +// Created by Florent Vilmart on 17-09-24. +// Copyright © 2017 Parse. All rights reserved. +// + +import Foundation + +internal extension API { + internal struct Command: Encodable where T: Encodable { + typealias ReturnType = U + let method: API.Method + let path: API.Endpoint + let body: T? + let mapper: ((Data) throws -> U) + let params: [String: String?]? + + internal var data: Data? { + return try? getJSONEncoder().encode(body) + } + + init(method: API.Method, + path: API.Endpoint, + params: [String: String]? = nil, + body: T? = nil, + mapper: @escaping ((Data) throws -> U)) { + self.method = method + self.path = path + self.body = body + self.mapper = mapper + self.params = params + } + + public func execute(options: API.Options) throws -> U { + let params = self.params?.getQueryItems() + let headers = API.getHeaders(options: options) + let url = ParseConfiguration.serverURL.appendingPathComponent(path.urlComponent) + + var components = URLComponents(url: url, resolvingAgainstBaseURL: false)! + components.queryItems = params + + var urlRequest = URLRequest(url: components.url!) + urlRequest.allHTTPHeaderFields = headers + if let body = data { + urlRequest.httpBody = body + } + urlRequest.httpMethod = method.rawValue + let responseData = try URLSession.shared.syncDataTask(with: urlRequest) + do { + return try mapper(responseData) + } catch _ { + throw try getDecoder().decode(ParseError.self, from: responseData) + } + } + + enum CodingKeys: String, CodingKey { // swiftlint:disable:this nesting + case method, body, path + } + } +} + +internal extension API.Command { + // MARK: Saving + internal static func saveCommand(_ object: T) -> API.Command where T: ObjectType { + if object.isSaved { + return updateCommand(object) + } + return createCommand(object) + } + + // MARK: Saving - private + private static func createCommand(_ object: T) -> API.Command where T: ObjectType { + let mapper = { (data) -> T in + try getDecoder().decode(SaveResponse.self, from: data).apply(object) + } + return API.Command(method: .POST, + path: object.endpoint, + body: object, + mapper: mapper) + } + + private static func updateCommand(_ object: T) -> API.Command where T: ObjectType { + let mapper = { (data: Data) -> T in + try getDecoder().decode(UpdateResponse.self, from: data).apply(object) + } + return API.Command(method: .PUT, + path: object.endpoint, + body: object, + mapper: mapper) + } + + // MARK: Fetching + internal static func fetchCommand(_ object: T) throws -> API.Command where T: ObjectType { + guard object.isSaved else { + throw ParseError(code: .unknownError, message: "Cannot Fetch an object without id") + } + return API.Command(method: .GET, + path: object.endpoint) { (data) -> T in + try getDecoder().decode(T.self, from: data) + } + } +} + +extension API.Command where T: ObjectType { + + internal var data: Data? { + guard let body = body else { return nil } + return try? body.getEncoder().encode(body) + } + + static func batch(commands: [API.Command]) -> RESTBatchCommandType { + let commands = commands.flatMap { (command) -> API.Command? in + let path = ParseConfiguration.mountPath + command.path.urlComponent + guard let body = command.body else { + return nil + } + return API.Command(method: command.method, path: .any(path), + body: body, mapper: command.mapper) + } + let bodies = commands.flatMap { (command) -> T? in + return command.body + } + let mapper = { (data: Data) -> [(T, ParseError?)] in + let decodingType = [BatchResponseItem].self + let responses = try getDecoder().decode(decodingType, from: data) + return bodies.enumerated().map({ (object) -> (T, ParseError?) in + let response = responses[object.0] + if let success = response.success { + return (success.apply(object.1), nil) + } else { + return (object.1, response.error) + } + }) + } + let batchCommand = BatchCommand(requests: commands) + return RESTBatchCommandType(method: .POST, path: .batch, body: batchCommand, mapper: mapper) + } +} diff --git a/Sources/ParseSwift/API.swift b/Sources/ParseSwift/API/API.swift similarity index 51% rename from Sources/ParseSwift/API.swift rename to Sources/ParseSwift/API/API.swift index 7357bd3e0..dac960380 100644 --- a/Sources/ParseSwift/API.swift +++ b/Sources/ParseSwift/API/API.swift @@ -1,6 +1,6 @@ // // API.swift -// Parse (iOS) +// ParseSwift // // Created by Florent Vilmart on 17-08-19. // Copyright © 2017 Parse. All rights reserved. @@ -10,11 +10,11 @@ import Foundation public struct API { - public enum Method: String, Encodable { + internal enum Method: String, Encodable { case GET, POST, PUT, DELETE } - public enum Endpoint: Encodable { + internal enum Endpoint: Encodable { case batch case objects(className: String) case object(className: String, objectId: String) @@ -48,57 +48,60 @@ public struct API { } } - public struct Option: OptionSet { - public let rawValue: UInt - public init(rawValue: UInt) { - self.rawValue = rawValue + public typealias Options = Set + + public enum Option: Hashable { + case useMasterKey + case sessionToken(String) + case installationId(String) + + // use HashValue so we can use in a sets + public var hashValue: Int { + switch self { + case .useMasterKey: + return 1 + case .sessionToken: + return 2 + case .installationId: + return 3 + } + } + + public static func == (lhs: API.Option, rhs: API.Option) -> Bool { + return lhs.hashValue == rhs.hashValue } - static let useMasterKey = Option(rawValue: 1 << 0) } - private static func getHeaders(useMasterKey: Bool = false) -> [String: String] { + internal static func getHeaders(options: API.Options) -> [String: String] { var headers: [String: String] = ["X-Parse-Application-Id": ParseConfiguration.applicationId, "Content-Type": "application/json"] if let clientKey = ParseConfiguration.clientKey { headers["X-Parse-Client-Key"] = clientKey } - if useMasterKey, - let masterKey = ParseConfiguration.masterKey { - headers["X-Parse-Master-Key"] = masterKey - } if let token = CurrentUserInfo.currentSessionToken { headers["X-Parse-Session-Token"] = token } + options.forEach { (option) in + switch option { + case .useMasterKey: + headers["X-Parse-Master-Key"] = ParseConfiguration.masterKey + case .sessionToken(let sessionToken): + headers["X-Parse-Session-Token"] = sessionToken + case .installationId(let installationId): + headers["X-Parse-Installation-Id"] = installationId + } + } + return headers } +} - public typealias Response = (Result) -> Void - - internal static func request(method: Method, - path: Endpoint, - params: [URLQueryItem]? = nil, - body: Data? = nil, - options: Option, - callback: Response? = nil) -> URLSessionDataTask { - - let headers = getHeaders(useMasterKey: options.contains(.useMasterKey)) - let url = ParseConfiguration.serverURL.appendingPathComponent(path.urlComponent) - - var components = URLComponents(url: url, resolvingAgainstBaseURL: false)! - components.queryItems = params - - var urlRequest = URLRequest(url: components.url!) - urlRequest.allHTTPHeaderFields = headers - if let body = body { - urlRequest.httpBody = body - } - urlRequest.httpMethod = method.rawValue - let task = URLSession.shared.dataTask(with: urlRequest) { (data, _, error) in - callback?(Result(data, error)) +internal extension Dictionary where Key == String, Value == String? { + func getQueryItems() -> [URLQueryItem] { + return map { (key, value) -> URLQueryItem in + return URLQueryItem(name: key, value: value) } - task.resume() - return task } } diff --git a/Sources/ParseSwift/API/BatchUtils.swift b/Sources/ParseSwift/API/BatchUtils.swift new file mode 100644 index 000000000..76208b350 --- /dev/null +++ b/Sources/ParseSwift/API/BatchUtils.swift @@ -0,0 +1,56 @@ +// +// RESTBatchCommand.swift +// ParseSwift +// +// Created by Florent Vilmart on 17-08-19. +// Copyright © 2017 Parse. All rights reserved. +// + +import Foundation + +typealias ParseObjectBatchCommand = BatchCommand where T: ObjectType +typealias ParseObjectBatchResponse = [(T, ParseError?)] +// swiftlint:disable line_length +typealias RESTBatchCommandType = API.Command, ParseObjectBatchResponse> where T: ObjectType +// swiftlint:enable line_length + +public struct BatchCommand: Encodable where T: Encodable { + let requests: [API.Command] +} + +public struct BatchResponseItem: Decodable where T: Decodable { + let success: T? + let error: ParseError? +} + +struct SaveOrUpdateResponse: Decodable { + var objectId: String? + var createdAt: Date? + var updatedAt: Date? + + var isCreate: Bool { + return objectId != nil && createdAt != nil + } + + func asSaveResponse() -> SaveResponse { + guard let objectId = objectId, let createdAt = createdAt else { + fatalError("Cannot create a SaveResponse without objectId") + } + return SaveResponse(objectId: objectId, createdAt: createdAt) + } + + func asUpdateResponse() -> UpdateResponse { + guard let updatedAt = updatedAt else { + fatalError("Cannot create an UpdateResponse without updatedAt") + } + return UpdateResponse(updatedAt: updatedAt) + } + + func apply(_ object: T) -> T where T: ObjectType { + if isCreate { + return asSaveResponse().apply(object) + } else { + return asUpdateResponse().apply(object) + } + } +} diff --git a/Sources/ParseSwift/REST/Responses.swift b/Sources/ParseSwift/API/Responses.swift similarity index 97% rename from Sources/ParseSwift/REST/Responses.swift rename to Sources/ParseSwift/API/Responses.swift index bde779832..567724438 100644 --- a/Sources/ParseSwift/REST/Responses.swift +++ b/Sources/ParseSwift/API/Responses.swift @@ -1,6 +1,6 @@ // // Responses.swift -// ParseSwift (iOS) +// ParseSwift // // Created by Florent Vilmart on 17-08-20. // Copyright © 2017 Parse. All rights reserved. diff --git a/Sources/ParseSwift/API/URLSession+sync.swift b/Sources/ParseSwift/API/URLSession+sync.swift new file mode 100644 index 000000000..5fb058841 --- /dev/null +++ b/Sources/ParseSwift/API/URLSession+sync.swift @@ -0,0 +1,33 @@ +// +// URLSession+sync.swift +// ParseSwift +// +// Created by Florent Vilmart on 17-09-24. +// Copyright © 2017 Parse. All rights reserved. +// + +import Foundation + +extension URLSession { + internal func syncDataTask(with request: URLRequest) throws -> Data { + let semaphore = DispatchSemaphore(value: 0) + var data: Data? + var error: Error? + var response: URLResponse? + dataTask(with: request) { (responseData, urlResponse, responseError) in + data = responseData + error = responseError + response = urlResponse + semaphore.signal() + }.resume() + semaphore.wait() + guard let responseData = data else { + guard let error = error else { + throw NSError(domain: "unknown", code: -1, userInfo: ["response": response!]) + } + throw error + } + return responseData + } + +} diff --git a/Sources/ParseSwift/Asynchronous.swift b/Sources/ParseSwift/Asynchronous.swift new file mode 100644 index 000000000..03a284abc --- /dev/null +++ b/Sources/ParseSwift/Asynchronous.swift @@ -0,0 +1,62 @@ +// +// Asynchronous.swift +// ParseSwift +// +// Created by Florent Vilmart on 17-08-20. +// Copyright © 2017 Parse. All rights reserved. +// + +import Foundation + +private let queue = DispatchQueue(label: "com.parse.ParseSwift.async") + +private func runAsync(options: API.Options, + function: @escaping (API.Options) throws -> T?, + callback: @escaping (T?, Error?) -> Void) { + queue.async { + do { + callback(try function(options), nil) + } catch let e { + callback(nil, e) + } + } +} + +extension Saving { + public func save(options: API.Options = [], callback: @escaping (Self.SavingType?, Error?) -> Void) { + runAsync(options: options, function: self.save, callback: callback) + } +} + +extension Fetching { + public func fetch(options: API.Options = [], callback: @escaping (Self.FetchingType?, Error?) -> Void) { + runAsync(options: options, function: self.fetch, callback: callback) + } +} + +extension Querying { + public func find(options: API.Options = [], callback: @escaping ([ResultType]?, Error?) -> Void) { + runAsync(options: options, function: self.find, callback: callback) + } + public func first(options: API.Options = [], callback: @escaping (ResultType?, Error?) -> Void) { + runAsync(options: options, function: self.first, callback: callback) + } + public func count(options: API.Options = [], callback: @escaping (Int?, Error?) -> Void) { + runAsync(options: options, function: self.count, callback: callback) + } +} + +public extension ObjectType { + public static func saveAll(options: API.Options = [], + _ objects: Self..., + callback: @escaping ([(Self, ParseError?)]?, Error?) -> Void) { + objects.saveAll(options: options, callback: callback) + } +} + +public extension Sequence where Element: ObjectType { + public func saveAll(options: API.Options = [], + callback: @escaping ([(Element, ParseError?)]?, Error?) -> Void) { + runAsync(options: options, function: self.saveAll, callback: callback) + } +} diff --git a/Sources/ParseSwift/Objects Protocols/ObjectType+Batch.swift b/Sources/ParseSwift/Objects Protocols/ObjectType+Batch.swift index fec2e0e73..9b38b9bac 100644 --- a/Sources/ParseSwift/Objects Protocols/ObjectType+Batch.swift +++ b/Sources/ParseSwift/Objects Protocols/ObjectType+Batch.swift @@ -1,6 +1,6 @@ // // ObjectType+Batch.swift -// ParseSwift (iOS) +// ParseSwift // // Created by Florent Vilmart on 17-08-20. // Copyright © 2017 Parse. All rights reserved. @@ -8,20 +8,17 @@ import Foundation -public typealias BatchResultCallback = (Result<[(T, ParseError?)]>) -> Void where T: ObjectType public extension ObjectType { - public static func saveAll(_ objects: Self..., - callback: BatchResultCallback?) -> Cancellable { - return objects.saveAll(callback: callback) + public static func saveAll(_ objects: Self...) throws -> [(Self, ParseError?)] { + return try objects.saveAll() } } extension Sequence where Element: ObjectType { - public func saveAll(options: API.Option = [], callback: BatchResultCallback?) -> Cancellable { - return RESTBatchCommand(commands: map { $0.saveCommand() }).execute(options: options, callback) - } - - private func saveAllCommand() -> RESTBatchCommand { - return RESTBatchCommand(commands: map { $0.saveCommand() }) + public func saveAll(options: API.Options = []) throws -> [(Self.Element, ParseError?)] { + let commands = map { $0.saveCommand() } + return try API.Command + .batch(commands: commands) + .execute(options: options) } } diff --git a/Sources/ParseSwift/Objects Protocols/ObjectType+Equatable.swift b/Sources/ParseSwift/Objects Protocols/ObjectType+Equatable.swift index ef3a9cdb9..776096a42 100644 --- a/Sources/ParseSwift/Objects Protocols/ObjectType+Equatable.swift +++ b/Sources/ParseSwift/Objects Protocols/ObjectType+Equatable.swift @@ -1,6 +1,6 @@ // // ObjectType+Equatable.swift -// ParseSwift (iOS) +// ParseSwift // // Created by Florent Vilmart on 17-08-20. // Copyright © 2017 Parse. All rights reserved. diff --git a/Sources/ParseSwift/Objects Protocols/ObjectType+Query.swift b/Sources/ParseSwift/Objects Protocols/ObjectType+Query.swift index 547803480..03fe8d1d5 100644 --- a/Sources/ParseSwift/Objects Protocols/ObjectType+Query.swift +++ b/Sources/ParseSwift/Objects Protocols/ObjectType+Query.swift @@ -9,8 +9,8 @@ import Foundation public extension ObjectType { - public static func find(callback: @escaping ((Result<[Self]>) -> Void)) -> Cancellable { - return query().find(callback: callback) + public static func find() throws -> [Self] { + return try query().find() } public static func query() -> Query { diff --git a/Sources/ParseSwift/Objects Protocols/ObjectType.swift b/Sources/ParseSwift/Objects Protocols/ObjectType.swift index 227be2c64..4693850a5 100644 --- a/Sources/ParseSwift/Objects Protocols/ObjectType.swift +++ b/Sources/ParseSwift/Objects Protocols/ObjectType.swift @@ -1,6 +1,6 @@ // // ParseObjectType.swift -// Parse +// ParseSwift // // Created by Florent Vilmart on 17-07-24. // Copyright © 2017 Parse. All rights reserved. @@ -12,25 +12,25 @@ public struct NoBody: Codable {} public protocol Saving: Codable { associatedtype SavingType - func save(options: API.Option, callback: @escaping ((Result) -> Void)) -> Cancellable - func save(callback: @escaping ((Result) -> Void)) -> Cancellable + func save(options: API.Options) throws -> SavingType + func save() throws -> SavingType } extension Saving { - public func save(callback: @escaping ((Result) -> Void)) -> Cancellable { - return save(options: [], callback: callback) + public func save() throws -> SavingType { + return try save(options: []) } } public protocol Fetching: Codable { associatedtype FetchingType - func fetch(options: API.Option, callback: @escaping ((Result) -> Void)) -> Cancellable? - func fetch(callback: @escaping ((Result) -> Void)) -> Cancellable? + func fetch(options: API.Options) throws -> FetchingType + func fetch() throws -> FetchingType } extension Fetching { - public func fetch(callback: @escaping ((Result) -> Void)) -> Cancellable? { - return fetch(options: [], callback: callback) + public func fetch() throws -> FetchingType { + return try fetch(options: []) } } @@ -75,11 +75,6 @@ public extension ObjectType { } } -public struct ParseError: Error, Decodable { - let code: Int - let error: String -} - enum DateEncodingKeys: String, CodingKey { case iso case type = "__type" @@ -153,7 +148,7 @@ func getParseEncoder() -> ParseEncoder { extension JSONEncoder { func encodeAsString(_ value: T) throws -> String where T: Encodable { guard let string = String(data: try encode(value), encoding: .utf8) else { - throw ParseError(code: -1, error: "Unable to encode object...") + throw ParseError(code: .unknownError, message: "Unable to encode object...") } return string } @@ -166,27 +161,20 @@ func getDecoder() -> JSONDecoder { } public extension ObjectType { - typealias ObjectCallback = (Result) -> Void - - public func save(options: API.Option, callback: @escaping ((Result) -> Void)) -> Cancellable { - return saveCommand().execute(options: options, callback) + public func save(options: API.Options) throws -> Self { + return try saveCommand().execute(options: options) } - public func fetch(options: API.Option, callback: @escaping ((Result) -> Void)) -> Cancellable? { - do { - return try fetchCommand().execute(options: options, callback) - } catch let e { - callback(.error(e)) - } - return nil + public func fetch(options: API.Options) throws -> Self { + return try fetchCommand().execute(options: options) } - internal func saveCommand() -> RESTCommand { - return RESTCommand.save(self) + internal func saveCommand() -> API.Command { + return API.Command.saveCommand(self) } - internal func fetchCommand() throws -> RESTCommand { - return try RESTCommand.fetch(self) + internal func fetchCommand() throws -> API.Command { + return try API.Command.fetchCommand(self) } } diff --git a/Sources/ParseSwift/Objects Protocols/UserType.swift b/Sources/ParseSwift/Objects Protocols/UserType.swift index 17034eb72..bfe1e7588 100644 --- a/Sources/ParseSwift/Objects Protocols/UserType.swift +++ b/Sources/ParseSwift/Objects Protocols/UserType.swift @@ -27,41 +27,37 @@ public extension UserType { } public extension UserType { - public typealias UserTypeCallback = (Result) -> Void - static var current: Self? { return CurrentUserInfo.currentUser as? Self } static func login(username: String, - password: String, - callback: UserTypeCallback? = nil) -> Cancellable { - return loginCommand(username: username, password: password).execute(options: [], callback) + password: String) throws -> Self { + return try loginCommand(username: username, password: password).execute(options: []) } static func signup(username: String, - password: String, - callback: UserTypeCallback? = nil) -> Cancellable { - return signupCommand(username: username, password: password).execute(options: [], callback) + password: String) throws -> Self { + return try signupCommand(username: username, password: password).execute(options: []) } - static func logout(callback: ((Result<()>) -> Void)?) { - _ = logoutCommand().execute(options: [], callback) + static func logout() throws { + _ = try logoutCommand().execute(options: []) } - func signup(callback: UserTypeCallback? = nil) -> Cancellable { - return signupCommand().execute(options: [], callback) + func signup() throws -> Self { + return try signupCommand().execute(options: []) } } private extension UserType { private static func loginCommand(username: String, - password: String) -> RESTCommand { + password: String) -> API.Command { let params = [ "username": username, "password": password ] - return RESTCommand(method: .GET, + return API.Command(method: .GET, path: .login, params: params) { (data) -> Self in let user = try getDecoder().decode(Self.self, from: data) @@ -73,9 +69,9 @@ private extension UserType { } private static func signupCommand(username: String, - password: String) -> RESTCommand { + password: String) -> API.Command { let body = SignupBody(username: username, password: password) - return RESTCommand(method: .POST, path: .signup, body: body) { (data) -> Self in + return API.Command(method: .POST, path: .signup, body: body) { (data) -> Self in let response = try getDecoder().decode(LoginSignupResponse.self, from: data) var user = try getDecoder().decode(Self.self, from: data) user.username = username @@ -89,9 +85,9 @@ private extension UserType { } } - private func signupCommand() -> RESTCommand { + private func signupCommand() -> API.Command { var user = self - return RESTCommand(method: .POST, path: .signup, body: user) { (data) -> Self in + return API.Command(method: .POST, path: .signup, body: user) { (data) -> Self in let response = try getDecoder().decode(LoginSignupResponse.self, from: data) user.updatedAt = response.updatedAt ?? response.createdAt user.createdAt = response.createdAt @@ -102,8 +98,8 @@ private extension UserType { } } - private static func logoutCommand() -> RESTCommand { - return RESTCommand(method: .POST, path: .logout) { (_) -> Void in + private static func logoutCommand() -> API.Command { + return API.Command(method: .POST, path: .logout) { (_) -> Void in CurrentUserInfo.currentUser = nil CurrentUserInfo.currentSessionToken = nil } diff --git a/Sources/ParseSwift/REST/RESTBatchCommand.swift b/Sources/ParseSwift/REST/RESTBatchCommand.swift deleted file mode 100644 index 1d2ce69f9..000000000 --- a/Sources/ParseSwift/REST/RESTBatchCommand.swift +++ /dev/null @@ -1,89 +0,0 @@ -// -// RESTBatchCommand.swift -// Parse (iOS) -// -// Created by Florent Vilmart on 17-08-19. -// Copyright © 2017 Parse. All rights reserved. -// - -import Foundation - -typealias ParseObjectBatchCommand = BatchCommand where T: ObjectType -typealias ParseObjectBatchResponse = [(T, ParseError?)] -// swiftlint:disable line_length -typealias RESTBatchCommandType = RESTCommand, ParseObjectBatchResponse> where T: ObjectType -// swiftlint:enable line_length - -public struct BatchCommand: Encodable where T: Encodable { - let requests: [RESTCommand] -} - -public struct BatchResponseItem: Decodable where T: Decodable { - let success: T? - let error: ParseError? -} - -struct SaveOrUpdateResponse: Decodable { - var objectId: String? - var createdAt: Date? - var updatedAt: Date? - - var isCreate: Bool { - return objectId != nil && createdAt != nil - } - - func asSaveResponse() -> SaveResponse { - guard let objectId = objectId, let createdAt = createdAt else { - fatalError("Cannot create a SaveResponse without objectId") - } - return SaveResponse(objectId: objectId, createdAt: createdAt) - } - - func asUpdateResponse() -> UpdateResponse { - guard let updatedAt = updatedAt else { - fatalError("Cannot create an UpdateResponse without updatedAt") - } - return UpdateResponse(updatedAt: updatedAt) - } - - func apply(_ object: T) -> T where T: ObjectType { - if isCreate { - return asSaveResponse().apply(object) - } else { - return asUpdateResponse().apply(object) - } - } -} - -public class RESTBatchCommand: RESTBatchCommandType where T: ObjectType { - typealias ParseObjectCommand = RESTCommand - typealias ParseObjectBatchCommand = BatchCommand - - init(commands: [ParseObjectCommand]) { - let commands = commands.flatMap { (command) -> RESTCommand? in - let path = ParseConfiguration.mountPath + command.path.urlComponent - guard let body = command.body else { - return nil - } - return RESTCommand(method: command.method, path: .any(path), - body: body, mapper: command.mapper) - } - let bodies = commands.flatMap { (command) -> T? in - return command.body - } - let mapper = { (data: Data) -> [(T, ParseError?)] in - let decodingType = [BatchResponseItem].self - let responses = try getDecoder().decode(decodingType, from: data) - return bodies.enumerated().map({ (object) -> (T, ParseError?) in - let response = responses[object.0] - if let success = response.success { - return (success.apply(object.1), nil) - } else { - return (object.1, response.error) - } - }) - } - let batchCommand = BatchCommand(requests: commands) - super.init(method: .POST, path: .batch, body: batchCommand, mapper: mapper) - } -} diff --git a/Sources/ParseSwift/REST/RESTCommand.swift b/Sources/ParseSwift/REST/RESTCommand.swift deleted file mode 100644 index f47bf75c7..000000000 --- a/Sources/ParseSwift/REST/RESTCommand.swift +++ /dev/null @@ -1,114 +0,0 @@ -import Foundation - -internal extension Dictionary where Key == String, Value == String? { - func getQueryItems() -> [URLQueryItem] { - return map { (key, value) -> URLQueryItem in - return URLQueryItem(name: key, value: value) - } - } -} - -public protocol Cancellable { - func cancel() -} - -private let commandQueue = DispatchQueue(label: "com.parse.ParseSwift.restCommandQueue") - -internal class RESTCommand: Cancellable, Encodable where T: Encodable { - internal struct Empty: Encodable {} - typealias ReturnType = U - let method: API.Method - let path: API.Endpoint - let body: T? - let mapper: ((Data) throws -> U) - let params: [String: String?]? - - var task: URLSessionDataTask? - - init(method: API.Method, - path: API.Endpoint, - params: [String: String]? = nil, - body: T? = nil, - mapper: @escaping ((Data) throws -> U)) { - self.method = method - self.path = path - self.body = body - self.mapper = mapper - self.params = params - } - - public func execute(options: API.Option, _ callback: ((Result) -> Void)? = nil) -> RESTCommand { - let data = try? getJSONEncoder().encode(body) - let params = self.params?.getQueryItems() - task = API.request(method: method, - path: path, - params: params, - body: data, - options: options) { (result) in - callback?(result.map(self.mapper)) - } - return self - } - - public func cancel() { - task?.cancel() - task = nil - } - - enum CodingKeys: String, CodingKey { - case method, body, path - } -} - -internal extension RESTCommand where T: ObjectType { - internal func execute(options: API.Option, _ callback: ((Result) -> Void)? = nil) -> RESTCommand { - let data = try? body?.getEncoder().encode(body) - let params = self.params?.getQueryItems() - task = API.request(method: method, - path: path, - params: params, - body: data!, - options: options) { (result) in - callback?(result.map(self.mapper)) - } - return self - } -} - -internal extension RESTCommand { - // MARK: Saving - internal static func save(_ object: T) -> RESTCommand where T: ObjectType { - if object.isSaved { - return updateCommand(object) - } - return createCommand(object) - } - - // MARK: Saving - private - private static func createCommand(_ object: T) -> RESTCommand where T: ObjectType { - return RESTCommand(method: .POST, - path: object.endpoint, - body: object) { (data) -> T in - try getDecoder().decode(SaveResponse.self, from: data).apply(object) - } - } - - private static func updateCommand(_ object: T) -> RESTCommand where T: ObjectType { - return RESTCommand(method: .PUT, - path: object.endpoint, - body: object) { (data: Data) -> T in - try getDecoder().decode(UpdateResponse.self, from: data).apply(object) - } - } - - // MARK: Fetching - internal static func fetch(_ object: T) throws -> RESTCommand where T: ObjectType { - guard object.isSaved else { - throw ParseError(code: -1, error: "Cannot Fetch an object without id") - } - return RESTCommand(method: .GET, - path: object.endpoint) { (data) -> T in - try getDecoder().decode(T.self, from: data) - } - } -} diff --git a/Sources/ParseSwift/Result.swift b/Sources/ParseSwift/Result.swift deleted file mode 100644 index 876924198..000000000 --- a/Sources/ParseSwift/Result.swift +++ /dev/null @@ -1,70 +0,0 @@ -import Foundation - -public enum Result { - case success(T) - case error(Error) - case unknown - - public init(_ response: T?, _ error: Error?) { - if let error = error { - self = .error(error) - } else if let response = response { - self = .success(response) - } else { - self = .unknown - } - } - - func map(_ transform: (T) throws -> U) -> Result { - switch self { - case .success(let success): - do { - return .success(try transform(success)) - } catch let e { - return .error(e) - } - case .error(let error): - return .error(error) - default: return .unknown - } - } - - public func flatMap(_ transform: (T) throws -> Result) rethrows -> Result { - switch self { - case .success(let success): - do { - return try transform(success) - } catch let e { - return .error(e) - } - case .error(let error): - return .error(error) - default: return .unknown - } - } -} - -extension Result where T == Data { - func decode() -> Result where U: Decodable { - return map { data -> U in - return try getDecoder().decode(U.self, from: data) - } - } - - func map(_ mapper: (T) throws -> U) -> Result { - switch self { - case .success(let data): - do { - return .success(try mapper(data)) - } catch let e { - do { // try default error mapper :) - return .error(try getDecoder().decode(ParseError.self, from: data)) - } catch {} - return .error(e) - } - case .error(let error): - return .error(error) - default: return .unknown - } - } -} diff --git a/Sources/ParseSwift/Synchronous.swift b/Sources/ParseSwift/Synchronous.swift deleted file mode 100644 index b1b0eddb7..000000000 --- a/Sources/ParseSwift/Synchronous.swift +++ /dev/null @@ -1,109 +0,0 @@ -// -// Synchronous.swift -// ParseSwift (iOS) -// -// Created by Florent Vilmart on 17-08-20. -// Copyright © 2017 Parse. All rights reserved. -// - -import Foundation - -typealias ResultCapturing = (Result) -> Void -// Mark it private for now -private func await(block: (@escaping ResultCapturing) -> Void) throws -> T { - let sema = DispatchSemaphore(value: 0) - var r: Result! - block({ - r = $0 - sema.signal() - }) - sema.wait() - switch r! { - case .success(let value): - return value - case .error(let error): - throw error - default: - fatalError() - } -} - -public struct Synchronous { - let object: T -} - -extension Synchronous where T: Saving { - public func save(options: API.Option = []) throws -> T.SavingType { - return try await { done in - _ = object.save(options: options, callback: done) - } - } -} - -extension Synchronous where T: Fetching { - public func fetch(options: API.Option = []) throws -> T.FetchingType { - return try await { done in - _ = object.fetch(options: options, callback: done) - } - } -} - -extension Synchronous where T: Querying { - public func find(options: API.Option = []) throws -> [T.ResultType] { - return try await { done in - _ = object.find(options: options, callback: done) - } - } - public func first(options: API.Option = []) throws -> T.ResultType? { - return try await { done in - _ = object.first(options: options, callback: done) - } - } - public func count(options: API.Option = []) throws -> Int { - return try await { done in - _ = object.count(options: options, callback: done) - } - } -} - -public extension Saving { - var sync: Synchronous { - return Synchronous(object: self) - } -} - -public extension Fetching { - var sync: Synchronous { - return Synchronous(object: self) - } -} - -// Force implementation for ObjectType as Feching and Saving makes it ambiguous -public extension ObjectType { - var sync: Synchronous { - return Synchronous(object: self) - } -} - -public extension Query { - var sync: Synchronous { - return Synchronous(object: self) - } -} - -// Temporary, just for demo -public extension ObjectType { - public static func saveAllSync(options: API.Option = [], _ objects: Self...) throws -> [(Self, ParseError?)] { - return try await { done in - _ = objects.saveAll(options: options, callback: done) - } - } -} - -public extension Sequence where Element: ObjectType { - public func saveAllSync(options: API.Option = []) throws -> [(Element, ParseError?)] { - return try await { done in - _ = self.saveAll(options: options, callback: done) - } - } -} diff --git a/Sources/ParseSwift/Types/ACL.swift b/Sources/ParseSwift/Types/ACL.swift index 901b5c85a..a7af08cc1 100644 --- a/Sources/ParseSwift/Types/ACL.swift +++ b/Sources/ParseSwift/Types/ACL.swift @@ -1,6 +1,6 @@ // // ACL.swift -// Parse (iOS) +// ParseSwift // // Created by Florent Vilmart on 17-08-19. // Copyright © 2017 Parse. All rights reserved. @@ -115,9 +115,7 @@ extension ACL { }.flatMap { pair -> [(String, Access, Bool)] in let (scope, accessValues) = pair return try accessValues.allKeys.flatMap { (access) -> (String, Access, Bool)? in - // swiftlint:disable line_length guard let value = try accessValues.decodeIfPresent(Bool.self, forKey: access) else { - // swiftlint:enable line_length return nil } return (scope, access, value) diff --git a/Sources/ParseSwift/Types/File.swift b/Sources/ParseSwift/Types/File.swift index d83e93c5a..c57e93c5c 100644 --- a/Sources/ParseSwift/Types/File.swift +++ b/Sources/ParseSwift/Types/File.swift @@ -11,7 +11,7 @@ public struct File: Saving, Fetching { self.url = url } - public func save(options: API.Option, callback: @escaping ((Result) -> Void)) -> Cancellable { + public func save(options: API.Options) -> File { // upload file // store in server // callback with the data @@ -33,7 +33,7 @@ public struct File: Saving, Fetching { } } - public func fetch(options: API.Option, callback: @escaping ((Result) -> Void)) -> Cancellable? { + public func fetch(options: API.Options) -> File { fatalError() } diff --git a/Sources/ParseSwift/Types/ParseError.swift b/Sources/ParseSwift/Types/ParseError.swift new file mode 100644 index 000000000..92ae234cd --- /dev/null +++ b/Sources/ParseSwift/Types/ParseError.swift @@ -0,0 +1,242 @@ +// +// ParseError.swift +// ParseSwift +// +// Created by Florent Vilmart on 17-09-24. +// Copyright © 2017 Parse. All rights reserved. +// + +import Foundation + +public struct ParseError: Swift.Error, Decodable { + let code: Code + let message: String + + var localizedDescription: String { + return "ParseError code=\(code) error=\(message)" + } + + /** + `ParseError.Code` enum contains all custom error codes that are used + as `code` for `Error` for callbacks on all classes. + */ + enum Code: Int, Swift.Error, Decodable { + /** + Internal SDK Error. No information available + */ + case unknownError = -1 + + /** + Internal server error. No information available. + */ + case internalServer = 1 + /** + The connection to the Parse servers failed. + */ + case connectionFailed = 100 + /** + Object doesn't exist, or has an incorrect password. + */ + case objectNotFound = 101 + /** + You tried to find values matching a datatype that doesn't + support exact database matching, like an array or a dictionary. + */ + case invalidQuery = 102 + /** + Missing or invalid classname. Classnames are case-sensitive. + They must start with a letter, and `a-zA-Z0-9_` are the only valid characters. + */ + case invalidClassName = 103 + /** + Missing object id. + */ + case missingObjectId = 104 + /** + Invalid key name. Keys are case-sensitive. + They must start with a letter, and `a-zA-Z0-9_` are the only valid characters. + */ + case invalidKeyName = 105 + /** + Malformed pointer. Pointers must be arrays of a classname and an object id. + */ + case invalidPointer = 106 + /** + Malformed json object. A json dictionary is expected. + */ + case invalidJSON = 107 + /** + Tried to access a feature only available internally. + */ + case commandUnavailable = 108 + /** + Field set to incorrect type. + */ + case incorrectType = 111 + /** + Invalid channel name. A channel name is either an empty string (the broadcast channel) + or contains only `a-zA-Z0-9_` characters and starts with a letter. + */ + case invalidChannelName = 112 + /** + Invalid device token. + */ + case invalidDeviceToken = 114 + /** + Push is misconfigured. See details to find out how. + */ + case pushMisconfigured = 115 + /** + The object is too large. + */ + case objectTooLarge = 116 + /** + That operation isn't allowed for clients. + */ + case operationForbidden = 119 + /** + The results were not found in the cache. + */ + case cacheMiss = 120 + /** + Keys in `NSDictionary` values may not include `$` or `.`. + */ + case invalidNestedKey = 121 + /** + Invalid file name. + A file name can contain only `a-zA-Z0-9_.` characters and should be between 1 and 36 characters. + */ + case invalidFileName = 122 + /** + Invalid ACL. An ACL with an invalid format was saved. This should not happen if you use `PFACL`. + */ + case invalidACL = 123 + /** + The request timed out on the server. Typically this indicates the request is too expensive. + */ + case timeout = 124 + /** + The email address was invalid. + */ + case invalidEmailAddress = 125 + /** + A unique field was given a value that is already taken. + */ + case duplicateValue = 137 + /** + Role's name is invalid. + */ + case invalidRoleName = 139 + /** + Exceeded an application quota. Upgrade to resolve. + */ + case exceededQuota = 140 + /** + Cloud Code script had an error. + */ + case scriptError = 141 + /** + Cloud Code validation failed. + */ + case validationError = 142 + /** + Product purchase receipt is missing. + */ + case receiptMissing = 143 + /** + Product purchase receipt is invalid. + */ + case invalidPurchaseReceipt = 144 + /** + Payment is disabled on this device. + */ + case paymentDisabled = 145 + /** + The product identifier is invalid. + */ + case invalidProductIdentifier = 146 + /** + The product is not found in the App Store. + */ + case productNotFoundInAppStore = 147 + /** + The Apple server response is not valid. + */ + case invalidServerResponse = 148 + /** + Product fails to download due to file system error. + */ + case productDownloadFileSystemFailure = 149 + /** + Fail to convert data to image. + */ + case invalidImageData = 150 + /** + Unsaved file. + */ + case unsavedFile = 151 + /** + Fail to delete file. + */ + case fileDeleteFailure = 153 + /** + Application has exceeded its request limit. + */ + case requestLimitExceeded = 155 + /** + Invalid event name. + */ + case invalidEventName = 160 + /** + Username is missing or empty. + */ + case usernameMissing = 200 + /** + Password is missing or empty. + */ + case userPasswordMissing = 201 + /** + Username has already been taken. + */ + case usernameTaken = 202 + /** + Email has already been taken. + */ + case userEmailTaken = 203 + /** + The email is missing, and must be specified. + */ + case userEmailMissing = 204 + /** + A user with the specified email was not found. + */ + case userWithEmailNotFound = 205 + /** + The user cannot be altered by a client without the session. + */ + case userCannotBeAlteredWithoutSession = 206 + /** + Users can only be created through sign up. + */ + case userCanOnlyBeCreatedThroughSignUp = 207 + + /** + An existing account already linked to another user. + */ + case accountAlreadyLinked = 208 + /** + Error code indicating that the current session token is invalid. + */ + case invalidSessionToken = 209 + + /** + Linked id missing from request. + */ + case linkedIdMissing = 250 + + /** + Invalid linked session. + */ + case invalidLinkedSession = 251 + } +} diff --git a/Sources/ParseSwift/Types/Pointer.swift b/Sources/ParseSwift/Types/Pointer.swift index 1dc3190f3..f74f93d55 100644 --- a/Sources/ParseSwift/Types/Pointer.swift +++ b/Sources/ParseSwift/Types/Pointer.swift @@ -30,11 +30,11 @@ public struct Pointer: Fetching, Codable { } extension Pointer { - public func fetch(options: API.Option = [], callback: @escaping ((Result) -> Void)) -> Cancellable? { + public func fetch(options: API.Options = []) throws -> T { let path = API.Endpoint.object(className: className, objectId: objectId) - return RESTCommand(method: .GET, + return try API.Command(method: .GET, path: path) { (data) -> T in try getDecoder().decode(T.self, from: data) - }.execute(options: options, callback) + }.execute(options: options) } } diff --git a/Sources/ParseSwift/Types/Query.swift b/Sources/ParseSwift/Types/Query.swift index c0281c927..2eb2a236d 100644 --- a/Sources/ParseSwift/Types/Query.swift +++ b/Sources/ParseSwift/Types/Query.swift @@ -9,20 +9,20 @@ import Foundation public protocol Querying { associatedtype ResultType - func find(options: API.Option, callback: @escaping ((Result<[ResultType]>) -> Void)) -> Cancellable - func first(options: API.Option, callback: @escaping ((Result) -> Void)) -> Cancellable - func count(options: API.Option, callback: @escaping ((Result) -> Void)) -> Cancellable + func find(options: API.Options) throws -> [ResultType] + func first(options: API.Options) throws -> ResultType? + func count(options: API.Options) throws -> Int } extension Querying { - func find(callback: @escaping ((Result<[ResultType]>) -> Void)) -> Cancellable { - return find(options: [], callback: callback) + func find() throws -> [ResultType] { + return try find(options: []) } - func first(callback: @escaping ((Result) -> Void)) -> Cancellable { - return first(options: [], callback: callback) + func first() throws -> ResultType? { + return try first(options: []) } - func count(callback: @escaping ((Result) -> Void)) -> Cancellable { - return count(options: [], callback: callback) + func count() throws -> Int { + return try count(options: []) } } @@ -195,41 +195,42 @@ public struct Query: Encodable where T: ObjectType { } extension Query: Querying { + public typealias ResultType = T - public func find(options: API.Option, callback: @escaping ((Result<[ResultType]>) -> Void)) -> Cancellable { - return findCommand().execute(options: options, callback) + public func find(options: API.Options) throws -> [ResultType] { + return try findCommand().execute(options: options) } - public func first(options: API.Option, callback: @escaping ((Result) -> Void)) -> Cancellable { - return firstCommand().execute(options: options, callback) + public func first(options: API.Options) throws -> ResultType? { + return try firstCommand().execute(options: options) } - public func count(options: API.Option, callback: @escaping ((Result) -> Void)) -> Cancellable { - return countCommand().execute(options: options, callback) + public func count(options: API.Options) throws -> Int { + return try countCommand().execute(options: options) } } private extension Query { - private func findCommand() -> RESTCommand, [ResultType]> { - return RESTCommand(method: .POST, path: endpoint, body: self) { + private func findCommand() -> API.Command, [ResultType]> { + return API.Command(method: .POST, path: endpoint, body: self) { try getDecoder().decode(FindResult.self, from: $0).results } } - private func firstCommand() -> RESTCommand, ResultType?> { + private func firstCommand() -> API.Command, ResultType?> { var query = self query.limit = 1 - return RESTCommand(method: .POST, path: endpoint, body: query) { + return API.Command(method: .POST, path: endpoint, body: query) { try getDecoder().decode(FindResult.self, from: $0).results.first } } - private func countCommand() -> RESTCommand, Int> { + private func countCommand() -> API.Command, Int> { var query = self query.limit = 1 query.isCount = true - return RESTCommand(method: .POST, path: endpoint, body: query) { + return API.Command(method: .POST, path: endpoint, body: query) { try getDecoder().decode(FindResult.self, from: $0).count ?? 0 } }