diff --git a/.spi.yml b/.spi.yml index c47517008..8514ea675 100644 --- a/.spi.yml +++ b/.spi.yml @@ -3,6 +3,7 @@ builder: configs: - platform: ios scheme: "ParseSwift (iOS)" + documentation_targets: ["ParseSwift (iOS)"] - platform: macos-xcodebuild scheme: "ParseSwift (macOS)" - platform: macos-xcodebuild-arm diff --git a/CHANGELOG.md b/CHANGELOG.md index 23189ca0e..ab26825f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,9 @@ [Full Changelog](https://github.com/parse-community/Parse-Swift/compare/4.5.0...main) * _Contributing to this repo? Add info about your change here to be included in the next release_ +__New features__ +- Add ParseSchema, ParseCLP, and ParseFieldOptions. Should only be used when using the Swift SDK on a secured server ([#370](https://github.com/parse-community/Parse-Swift/pull/370)), thanks to [Corey Baker](https://github.com/cbaker6). + ### 4.5.0 [Full Changelog](https://github.com/parse-community/Parse-Swift/compare/4.4.0...4.5.0) diff --git a/ParseSwift.playground/Pages/20 - Schema.xcplaygroundpage/Contents.swift b/ParseSwift.playground/Pages/20 - Schema.xcplaygroundpage/Contents.swift new file mode 100644 index 000000000..50b8595d0 --- /dev/null +++ b/ParseSwift.playground/Pages/20 - Schema.xcplaygroundpage/Contents.swift @@ -0,0 +1,254 @@ +//: [Previous](@previous) + +import PlaygroundSupport +import Foundation +import ParseSwift + +PlaygroundPage.current.needsIndefiniteExecution = true +initializeParse() + +//: Youe specific _User value type. +struct User: ParseUser { + //: These are required by `ParseObject`. + var objectId: String? + var createdAt: Date? + var updatedAt: Date? + var ACL: ParseACL? + var originalData: Data? + + //: These are required by `ParseUser`. + var username: String? + var email: String? + var emailVerified: Bool? + var password: String? + var authData: [String: [String: String]?]? + + //: Your custom keys. + var customKey: String? + + //: Implement your own version of merge + func merge(with object: Self) throws -> Self { + var updated = try mergeParse(with: object) + if updated.shouldRestoreKey(\.customKey, + original: object) { + updated.customKey = object.customKey + } + return updated + } +} + +//: Create your own value typed `ParseObject`. +struct GameScore2: ParseObject { + //: These are required by ParseObject + var objectId: String? + var createdAt: Date? + var updatedAt: Date? + var ACL: ParseACL? + var originalData: Data? + + //: Your own properties. + var points: Int? + var level: Int? + var data: ParseBytes? + var owner: User? + var rivals: [User]? + + //: Implement your own version of merge + func merge(with object: Self) throws -> Self { + var updated = try mergeParse(with: object) + if updated.shouldRestoreKey(\.points, + original: object) { + updated.points = object.points + } + if updated.shouldRestoreKey(\.level, + original: object) { + updated.level = object.level + } + if updated.shouldRestoreKey(\.data, + original: object) { + updated.data = object.data + } + if updated.shouldRestoreKey(\.owner, + original: object) { + updated.owner = object.owner + } + if updated.shouldRestoreKey(\.rivals, + original: object) { + updated.rivals = object.rivals + } + return updated + } +} + +//: It's recommended to place custom initializers in an extension +//: to preserve the memberwise initializer. +extension GameScore2 { + + init(points: Int) { + self.points = points + } + + init(objectId: String?) { + self.objectId = objectId + } +} + +//: First lets create a new CLP for the new schema. +let clp = ParseCLP(requiresAuthentication: true, publicAccess: false) + .setAccessPublic(true, on: .get) + .setAccessPublic(true, on: .find) + +//: Next we use the CLP to create the new schema and add fields to it. +var gameScoreSchema = ParseSchema(classLevelPermissions: clp) + .addField("points", + type: .number, + options: ParseFieldOptions(required: false, defauleValue: nil)) + .addField("level", + type: .number, + options: ParseFieldOptions(required: false, defauleValue: nil)) + .addField("data", + type: .bytes, + options: ParseFieldOptions(required: false, defauleValue: nil)) + +do { + gameScoreSchema = try gameScoreSchema + .addField("owner", + type: .pointer, + options: ParseFieldOptions(required: false, defauleValue: nil)) + .addField("rivals", + type: .array, + options: ParseFieldOptions<[User]>(required: false, defauleValue: nil)) +} catch { + print("Can't add field: \(gameScoreSchema)") +} + +//: Now lets create the schema on the server. +gameScoreSchema.create { result in + switch result { + case .success(let savedSchema): + print("Check GameScore2 in Dashboard. \nThe created schema: \(savedSchema)") + case .failure(let error): + print("Couldn't save schema: \(error)") + } +} + +//: We can update the CLP to only allow access to users specified in the "owner" field. +let clp2 = clp.setPointerFields(Set(["owner"]), on: .get) +gameScoreSchema.classLevelPermissions = clp2 + +//: In addition, we can add an index. +gameScoreSchema = gameScoreSchema.addIndex("myIndex", field: "level", index: 1) + +//: Next, we need to update the schema on the server with the changes. +gameScoreSchema.update { result in + switch result { + case .success(let updatedSchema): + print("Check GameScore2 in Dashboard. \nThe updated schema: \(updatedSchema)") + /*: + Updated the current gameScoreSchema with the newest. + */ + gameScoreSchema = updatedSchema + case .failure(let error): + print("Couldn't update schema: \(error)") + } +} + +//: Indexes can also be deleted. +gameScoreSchema = gameScoreSchema.deleteIndex("myIndex") + +//: Next, we need to update the schema on the server with the changes. +gameScoreSchema.update { result in + switch result { + case .success(let updatedSchema): + print("Check GameScore2 in Dashboard. \nThe updated schema: \(updatedSchema)") + /*: + Updated the current gameScoreSchema with the newest. + */ + gameScoreSchema = updatedSchema + case .failure(let error): + print("Couldn't update schema: \(error)") + } +} + +/*: + Fields can also be deleted on a schema. Lets remove + the **data** field since it's not going being used. +*/ +gameScoreSchema = gameScoreSchema.deleteField("data") + +//: Next, we need to update the schema on the server with the changes. +gameScoreSchema.update { result in + switch result { + case .success(let updatedSchema): + print("Check GameScore2 in Dashboard. \nThe updated schema: \(updatedSchema)") + /*: + Updated the current gameScoreSchema with the newest. + */ + gameScoreSchema = updatedSchema + case .failure(let error): + print("Couldn't update schema: \(error)") + } +} + +/*: + Sets of fields can also be protected from access. Lets protect + some fields from access. +*/ +var clp3 = gameScoreSchema.classLevelPermissions +clp3 = clp3? + .setProtectedFieldsPublic(["owner"]) + .setProtectedFields(["level"], userField: "rivals") +gameScoreSchema.classLevelPermissions = clp3 + +//: Next, we need to update the schema on the server with the changes. +gameScoreSchema.update { result in + switch result { + case .success(let updatedSchema): + print("Check GameScore2 in Dashboard. \nThe updated schema: \(updatedSchema)") + /*: + Updated the current gameScoreSchema with the newest. + */ + gameScoreSchema = updatedSchema + case .failure(let error): + print("Couldn't update schema: \(error)") + } +} + +//: Now lets save a new object to the new schema. +var gameScore = GameScore2() +gameScore.points = 120 +gameScore.owner = User.current + +gameScore.save { result in + switch result { + case .success(let savedGameScore): + print("The saved GameScore is: \(savedGameScore)") + case .failure(let error): + print("Couldn't save schema: \(error)") + } +} + +//: You can delete all objects your schema by purging them. +gameScoreSchema.purge { result in + switch result { + case .success: + print("All objects have been purged from this schema.") + case .failure(let error): + print("Couldn't purge schema: \(error)") + } +} + +/*: + As long as there's no data in your `ParseSchema` you can + delete the schema. +*/ + gameScoreSchema.delete { result in + switch result { + case .success: + print("The schema has been deleted.") + case .failure(let error): + print("Couldn't delete the schema: \(error)") + } +} + +//: [Next](@next) diff --git a/ParseSwift.xcodeproj/project.pbxproj b/ParseSwift.xcodeproj/project.pbxproj index 55ff7dbba..ef10866a0 100644 --- a/ParseSwift.xcodeproj/project.pbxproj +++ b/ParseSwift.xcodeproj/project.pbxproj @@ -274,6 +274,22 @@ 7045769E26BD934000F86F71 /* ParseFile+async.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7045769C26BD934000F86F71 /* ParseFile+async.swift */; }; 7045769F26BD934000F86F71 /* ParseFile+async.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7045769C26BD934000F86F71 /* ParseFile+async.swift */; }; 704576A026BD934000F86F71 /* ParseFile+async.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7045769C26BD934000F86F71 /* ParseFile+async.swift */; }; + 705025992842FD3B008D6624 /* ParseCLPTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 705025982842FD3B008D6624 /* ParseCLPTests.swift */; }; + 7050259A2842FD3B008D6624 /* ParseCLPTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 705025982842FD3B008D6624 /* ParseCLPTests.swift */; }; + 7050259B2842FD3B008D6624 /* ParseCLPTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 705025982842FD3B008D6624 /* ParseCLPTests.swift */; }; + 7050259D2843F0CF008D6624 /* ParseSchemaAsyncTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7050259C2843F0CF008D6624 /* ParseSchemaAsyncTests.swift */; }; + 7050259E2843F0CF008D6624 /* ParseSchemaAsyncTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7050259C2843F0CF008D6624 /* ParseSchemaAsyncTests.swift */; }; + 7050259F2843F0CF008D6624 /* ParseSchemaAsyncTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7050259C2843F0CF008D6624 /* ParseSchemaAsyncTests.swift */; }; + 705025A12843F0E7008D6624 /* ParseSchemaCombineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 705025A02843F0E7008D6624 /* ParseSchemaCombineTests.swift */; }; + 705025A22843F0E7008D6624 /* ParseSchemaCombineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 705025A02843F0E7008D6624 /* ParseSchemaCombineTests.swift */; }; + 705025A32843F0E7008D6624 /* ParseSchemaCombineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 705025A02843F0E7008D6624 /* ParseSchemaCombineTests.swift */; }; + 705025A5284407C4008D6624 /* ParseSchemaTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 705025A4284407C4008D6624 /* ParseSchemaTests.swift */; }; + 705025A6284407C4008D6624 /* ParseSchemaTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 705025A4284407C4008D6624 /* ParseSchemaTests.swift */; }; + 705025A7284407C4008D6624 /* ParseSchemaTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 705025A4284407C4008D6624 /* ParseSchemaTests.swift */; }; + 705025A928441C96008D6624 /* ParseFieldOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 705025A828441C96008D6624 /* ParseFieldOptions.swift */; }; + 705025AA28441C96008D6624 /* ParseFieldOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 705025A828441C96008D6624 /* ParseFieldOptions.swift */; }; + 705025AB28441C96008D6624 /* ParseFieldOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 705025A828441C96008D6624 /* ParseFieldOptions.swift */; }; + 705025AC28441C96008D6624 /* ParseFieldOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 705025A828441C96008D6624 /* ParseFieldOptions.swift */; }; 70510AAC259EE25E00FEA700 /* LiveQuerySocket.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70510AAB259EE25E00FEA700 /* LiveQuerySocket.swift */; }; 70510AAD259EE25E00FEA700 /* LiveQuerySocket.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70510AAB259EE25E00FEA700 /* LiveQuerySocket.swift */; }; 70510AAE259EE25E00FEA700 /* LiveQuerySocket.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70510AAB259EE25E00FEA700 /* LiveQuerySocket.swift */; }; @@ -342,6 +358,30 @@ 708D035325215F9B00646C70 /* Deletable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 708D035125215F9B00646C70 /* Deletable.swift */; }; 708D035425215F9B00646C70 /* Deletable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 708D035125215F9B00646C70 /* Deletable.swift */; }; 708D035525215F9B00646C70 /* Deletable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 708D035125215F9B00646C70 /* Deletable.swift */; }; + 709A147D283949D100BF85E5 /* ParseSchema.swift in Sources */ = {isa = PBXBuildFile; fileRef = 709A147C283949D100BF85E5 /* ParseSchema.swift */; }; + 709A147E283949D100BF85E5 /* ParseSchema.swift in Sources */ = {isa = PBXBuildFile; fileRef = 709A147C283949D100BF85E5 /* ParseSchema.swift */; }; + 709A147F283949D100BF85E5 /* ParseSchema.swift in Sources */ = {isa = PBXBuildFile; fileRef = 709A147C283949D100BF85E5 /* ParseSchema.swift */; }; + 709A1480283949D100BF85E5 /* ParseSchema.swift in Sources */ = {isa = PBXBuildFile; fileRef = 709A147C283949D100BF85E5 /* ParseSchema.swift */; }; + 709A148228395ED100BF85E5 /* ParseSchema+async.swift in Sources */ = {isa = PBXBuildFile; fileRef = 709A148128395ED100BF85E5 /* ParseSchema+async.swift */; }; + 709A148328395ED100BF85E5 /* ParseSchema+async.swift in Sources */ = {isa = PBXBuildFile; fileRef = 709A148128395ED100BF85E5 /* ParseSchema+async.swift */; }; + 709A148428395ED100BF85E5 /* ParseSchema+async.swift in Sources */ = {isa = PBXBuildFile; fileRef = 709A148128395ED100BF85E5 /* ParseSchema+async.swift */; }; + 709A148528395ED100BF85E5 /* ParseSchema+async.swift in Sources */ = {isa = PBXBuildFile; fileRef = 709A148128395ED100BF85E5 /* ParseSchema+async.swift */; }; + 709A148728396B1D00BF85E5 /* ParseField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 709A148628396B1C00BF85E5 /* ParseField.swift */; }; + 709A148828396B1D00BF85E5 /* ParseField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 709A148628396B1C00BF85E5 /* ParseField.swift */; }; + 709A148928396B1D00BF85E5 /* ParseField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 709A148628396B1C00BF85E5 /* ParseField.swift */; }; + 709A148A28396B1D00BF85E5 /* ParseField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 709A148628396B1C00BF85E5 /* ParseField.swift */; }; + 709A148C2839A1DB00BF85E5 /* Operation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 709A148B2839A1DB00BF85E5 /* Operation.swift */; }; + 709A148D2839A1DB00BF85E5 /* Operation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 709A148B2839A1DB00BF85E5 /* Operation.swift */; }; + 709A148E2839A1DB00BF85E5 /* Operation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 709A148B2839A1DB00BF85E5 /* Operation.swift */; }; + 709A148F2839A1DB00BF85E5 /* Operation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 709A148B2839A1DB00BF85E5 /* Operation.swift */; }; + 709A14A02839CABD00BF85E5 /* ParseCLP.swift in Sources */ = {isa = PBXBuildFile; fileRef = 709A149F2839CABD00BF85E5 /* ParseCLP.swift */; }; + 709A14A12839CABD00BF85E5 /* ParseCLP.swift in Sources */ = {isa = PBXBuildFile; fileRef = 709A149F2839CABD00BF85E5 /* ParseCLP.swift */; }; + 709A14A22839CABD00BF85E5 /* ParseCLP.swift in Sources */ = {isa = PBXBuildFile; fileRef = 709A149F2839CABD00BF85E5 /* ParseCLP.swift */; }; + 709A14A32839CABD00BF85E5 /* ParseCLP.swift in Sources */ = {isa = PBXBuildFile; fileRef = 709A149F2839CABD00BF85E5 /* ParseCLP.swift */; }; + 709A14A5283AAF4C00BF85E5 /* ParseSchema+combine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 709A14A4283AAF4C00BF85E5 /* ParseSchema+combine.swift */; }; + 709A14A6283AAF4C00BF85E5 /* ParseSchema+combine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 709A14A4283AAF4C00BF85E5 /* ParseSchema+combine.swift */; }; + 709A14A7283AAF4C00BF85E5 /* ParseSchema+combine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 709A14A4283AAF4C00BF85E5 /* ParseSchema+combine.swift */; }; + 709A14A8283AAF4C00BF85E5 /* ParseSchema+combine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 709A14A4283AAF4C00BF85E5 /* ParseSchema+combine.swift */; }; 709B40C1268F999000ED2EAC /* IOS13Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 709B40C0268F999000ED2EAC /* IOS13Tests.swift */; }; 709B40C2268F999000ED2EAC /* IOS13Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 709B40C0268F999000ED2EAC /* IOS13Tests.swift */; }; 709B40C3268F999000ED2EAC /* IOS13Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 709B40C0268F999000ED2EAC /* IOS13Tests.swift */; }; @@ -950,6 +990,11 @@ 7045769226BD8F8100F86F71 /* ParseInstallation+async.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ParseInstallation+async.swift"; sourceTree = ""; }; 7045769726BD917500F86F71 /* Query+async.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Query+async.swift"; sourceTree = ""; }; 7045769C26BD934000F86F71 /* ParseFile+async.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ParseFile+async.swift"; sourceTree = ""; }; + 705025982842FD3B008D6624 /* ParseCLPTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParseCLPTests.swift; sourceTree = ""; }; + 7050259C2843F0CF008D6624 /* ParseSchemaAsyncTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParseSchemaAsyncTests.swift; sourceTree = ""; }; + 705025A02843F0E7008D6624 /* ParseSchemaCombineTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParseSchemaCombineTests.swift; sourceTree = ""; }; + 705025A4284407C4008D6624 /* ParseSchemaTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParseSchemaTests.swift; sourceTree = ""; }; + 705025A828441C96008D6624 /* ParseFieldOptions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParseFieldOptions.swift; sourceTree = ""; }; 70510AAB259EE25E00FEA700 /* LiveQuerySocket.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveQuerySocket.swift; sourceTree = ""; }; 70572670259033A700F0ADD5 /* ParseFileManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParseFileManager.swift; sourceTree = ""; }; 705727882593FF8000F0ADD5 /* ParseFileTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParseFileTests.swift; sourceTree = ""; }; @@ -968,6 +1013,12 @@ 7085DDA226CC8A470033B977 /* ParseHealth+combine.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "ParseHealth+combine.swift"; sourceTree = ""; }; 7085DDB226D1EC7F0033B977 /* ParseAuthenticationCombineTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParseAuthenticationCombineTests.swift; sourceTree = ""; }; 708D035125215F9B00646C70 /* Deletable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Deletable.swift; sourceTree = ""; }; + 709A147C283949D100BF85E5 /* ParseSchema.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParseSchema.swift; sourceTree = ""; }; + 709A148128395ED100BF85E5 /* ParseSchema+async.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ParseSchema+async.swift"; sourceTree = ""; }; + 709A148628396B1C00BF85E5 /* ParseField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParseField.swift; sourceTree = ""; }; + 709A148B2839A1DB00BF85E5 /* Operation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Operation.swift; sourceTree = ""; }; + 709A149F2839CABD00BF85E5 /* ParseCLP.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParseCLP.swift; sourceTree = ""; }; + 709A14A4283AAF4C00BF85E5 /* ParseSchema+combine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ParseSchema+combine.swift"; sourceTree = ""; }; 709B40C0268F999000ED2EAC /* IOS13Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IOS13Tests.swift; sourceTree = ""; }; 709B98302556EC7400507778 /* ParseSwiftTeststvOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ParseSwiftTeststvOS.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 709B98342556EC7400507778 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -1233,9 +1284,9 @@ 709B40C0268F999000ED2EAC /* IOS13Tests.swift */, 4AA8076E1F794C1C008CD551 /* KeychainStoreTests.swift */, 9194657724F16E330070296B /* ParseACLTests.swift */, - 70170A4D2656EBA50070C905 /* ParseAnalyticsTests.swift */, 917BA4292703E03F00F8D747 /* ParseAnalyticsAsyncTests.swift */, 91CB9536265966DF0043E5D6 /* ParseAnalyticsCombineTests.swift */, + 70170A4D2656EBA50070C905 /* ParseAnalyticsTests.swift */, 917BA4452703EEA700F8D747 /* ParseAnonymousAsyncTests.swift */, 7044C22C25C5E4E90011F6E7 /* ParseAnonymousCombineTests.swift */, 70A2D86A25B3ADB6001BEB7D /* ParseAnonymousTests.swift */, @@ -1250,6 +1301,7 @@ 7044C21225C5DE490011F6E7 /* ParseCloudCombineTests.swift */, 916786EF259BC59600BB5B4E /* ParseCloudTests.swift */, 91F346C2269B88F7005727B6 /* ParseCloudViewModelTests.swift */, + 705025982842FD3B008D6624 /* ParseCLPTests.swift */, 917BA4312703E36800F8D747 /* ParseConfigAsyncTests.swift */, 7044C21F25C5E0160011F6E7 /* ParseConfigCombineTests.swift */, 70D1BE0625BB2BF400A42E7C /* ParseConfigTests.swift */, @@ -1298,6 +1350,9 @@ 91BB8FD32690D586005A6BA5 /* ParseQueryViewModelTests.swift */, 70D1BD8625B8C37200A42E7C /* ParseRelationTests.swift */, 7004C22D25B69077005E0AD9 /* ParseRoleTests.swift */, + 7050259C2843F0CF008D6624 /* ParseSchemaAsyncTests.swift */, + 705025A02843F0E7008D6624 /* ParseSchemaCombineTests.swift */, + 705025A4284407C4008D6624 /* ParseSchemaTests.swift */, 70C5504525B40D5200B5DBC2 /* ParseSessionTests.swift */, 917BA4512703F55700F8D747 /* ParseTwitterAsyncTests.swift */, 89899D9E26045998002E2043 /* ParseTwitterCombineTests.swift */, @@ -1668,6 +1723,7 @@ 703B090126BD9652005A112F /* ParseAnalytics+async.swift */, 70170A482656E2FE0070C905 /* ParseAnalytics+combine.swift */, 91285B122698DBF20051B544 /* ParseBytes.swift */, + 709A149F2839CABD00BF85E5 /* ParseCLP.swift */, 916786E1259B7DDA00BB5B4E /* ParseCloud.swift */, 703B090626BD9764005A112F /* ParseCloud+async.swift */, 7044C17425C4ECFF0011F6E7 /* ParseCloud+combine.swift */, @@ -1675,6 +1731,8 @@ 703B090B26BD984D005A112F /* ParseConfig+async.swift */, 7044C18225C4EFC10011F6E7 /* ParseConfig+combine.swift */, F97B45BF24D9C6F200F4A88B /* ParseError.swift */, + 709A148628396B1C00BF85E5 /* ParseField.swift */, + 705025A828441C96008D6624 /* ParseFieldOptions.swift */, F97B45C124D9C6F200F4A88B /* ParseFile.swift */, 7045769C26BD934000F86F71 /* ParseFile+async.swift */, 7044C19025C4F5B60011F6E7 /* ParseFile+combine.swift */, @@ -1687,6 +1745,9 @@ 7044C19E25C4FA870011F6E7 /* ParseOperation+combine.swift */, 91285B1B26990D7F0051B544 /* ParsePolygon.swift */, 7004C21F25B63C7A005E0AD9 /* ParseRelation.swift */, + 709A147C283949D100BF85E5 /* ParseSchema.swift */, + 709A148128395ED100BF85E5 /* ParseSchema+async.swift */, + 709A14A4283AAF4C00BF85E5 /* ParseSchema+combine.swift */, 91679D63268E596300F71809 /* ParseVersion.swift */, F97B45BE24D9C6F200F4A88B /* Pointer.swift */, 70C167B327304F09009F4E30 /* Pointer+async.swift */, @@ -1755,6 +1816,7 @@ F97B464524D9C78B00F4A88B /* Increment.swift */, F97B464424D9C78B00F4A88B /* Remove.swift */, 70C5509F25B4A9F600B5DBC2 /* RemoveRelation.swift */, + 709A148B2839A1DB00BF85E5 /* Operation.swift */, ); path = Operations; sourceTree = ""; @@ -2198,6 +2260,8 @@ F97B463724D9C74400F4A88B /* Responses.swift in Sources */, 916786E2259B7DDA00BB5B4E /* ParseCloud.swift in Sources */, 91F346B9269B766C005727B6 /* CloudViewModel.swift in Sources */, + 709A148C2839A1DB00BF85E5 /* Operation.swift in Sources */, + 709A14A5283AAF4C00BF85E5 /* ParseSchema+combine.swift in Sources */, F97B461624D9C6F200F4A88B /* Queryable.swift in Sources */, 7028373426BD8883007688C9 /* ParseObject+async.swift in Sources */, 70C5503825B406B800B5DBC2 /* ParseSession.swift in Sources */, @@ -2221,13 +2285,16 @@ 91285B1C26990D7F0051B544 /* ParsePolygon.swift in Sources */, 91BB8FCA2690AC99005A6BA5 /* QueryViewModel.swift in Sources */, 7085DD9426CBF3A70033B977 /* Documentation.docc in Sources */, + 705025A928441C96008D6624 /* ParseFieldOptions.swift in Sources */, F97B45D624D9C6F200F4A88B /* ParseEncoder.swift in Sources */, 700395A325A119430052CB31 /* Operations.swift in Sources */, 91BB8FCF2690BA70005A6BA5 /* QueryObservable.swift in Sources */, 70F03A232780BDE200E5AFB4 /* ParseGoogle.swift in Sources */, + 709A148228395ED100BF85E5 /* ParseSchema+async.swift in Sources */, 7045769826BD917500F86F71 /* Query+async.swift in Sources */, 703B094E26BF47E3005A112F /* ParseTwitter+combine.swift in Sources */, 70386A3825D998D90048EC1B /* ParseLDAP.swift in Sources */, + 709A14A02839CABD00BF85E5 /* ParseCLP.swift in Sources */, 700395F225A171320052CB31 /* LiveQueryable.swift in Sources */, 70F03A252780BDF700E5AFB4 /* ParseGoogle+async.swift in Sources */, F97B45F224D9C6F200F4A88B /* Pointer.swift in Sources */, @@ -2263,10 +2330,12 @@ F97B45E224D9C6F200F4A88B /* AnyEncodable.swift in Sources */, 700396EA25A3892D0052CB31 /* LiveQuerySocketDelegate.swift in Sources */, 9116F66F26A35D610082F6D6 /* URLCache.swift in Sources */, + 709A148728396B1D00BF85E5 /* ParseField.swift in Sources */, 70572671259033A700F0ADD5 /* ParseFileManager.swift in Sources */, 70F03A342780CA4300E5AFB4 /* ParseGitHub.swift in Sources */, 707A3C2025B14BD0000D215C /* ParseApple.swift in Sources */, 703B095D26BF481F005A112F /* ParseFacebook+async.swift in Sources */, + 709A147D283949D100BF85E5 /* ParseSchema.swift in Sources */, F97B462224D9C6F200F4A88B /* ParseKeyValueStore.swift in Sources */, 703B090226BD9652005A112F /* ParseAnalytics+async.swift in Sources */, 703B093F26BF47AC005A112F /* ParseApple+async.swift in Sources */, @@ -2378,6 +2447,7 @@ 917BA42A2703E03F00F8D747 /* ParseAnalyticsAsyncTests.swift in Sources */, 70BC0B33251903D1001556DB /* ParseGeoPointTests.swift in Sources */, 7003957625A0EE770052CB31 /* BatchUtilsTests.swift in Sources */, + 705025992842FD3B008D6624 /* ParseCLPTests.swift in Sources */, 705A99F9259807F900B3547F /* ParseFileManagerTests.swift in Sources */, 70F03A522780DA9200E5AFB4 /* ParseGoogleTests.swift in Sources */, 7044C20625C5D6780011F6E7 /* ParseQueryCombineTests.swift in Sources */, @@ -2395,6 +2465,7 @@ 7FFF552E2217E72A007C3B4E /* AnyEncodableTests.swift in Sources */, 7044C22025C5E0160011F6E7 /* ParseConfigCombineTests.swift in Sources */, 7FFF55302217E72A007C3B4E /* AnyDecodableTests.swift in Sources */, + 7050259D2843F0CF008D6624 /* ParseSchemaAsyncTests.swift in Sources */, 70C7DC2224D20F190050419B /* ParseObjectBatchTests.swift in Sources */, 7044C1BB25C52E410011F6E7 /* ParseInstallationCombineTests.swift in Sources */, 917BA45A2703FD2200F8D747 /* ParseAuthenticationAsyncTests.swift in Sources */, @@ -2403,6 +2474,7 @@ 70F03A5E2780EAC700E5AFB4 /* ParseGitHubTests.swift in Sources */, 4AA807701F794C31008CD551 /* KeychainStoreTests.swift in Sources */, 7085DDB326D1EC7F0033B977 /* ParseAuthenticationCombineTests.swift in Sources */, + 705025A12843F0E7008D6624 /* ParseSchemaCombineTests.swift in Sources */, 7044C1F925C5CFAB0011F6E7 /* ParseFileCombineTests.swift in Sources */, 70C5502225B3D8F700B5DBC2 /* ParseAppleTests.swift in Sources */, 917BA4322703E36800F8D747 /* ParseConfigAsyncTests.swift in Sources */, @@ -2422,6 +2494,7 @@ 91BB8FD42690D586005A6BA5 /* ParseQueryViewModelTests.swift in Sources */, 911DB13324C494390027F3C7 /* MockURLProtocol.swift in Sources */, 917BA44A2703F10400F8D747 /* ParseAppleAsyncTests.swift in Sources */, + 705025A5284407C4008D6624 /* ParseSchemaTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2432,6 +2505,8 @@ F97B463824D9C74400F4A88B /* Responses.swift in Sources */, 916786E3259B7DDA00BB5B4E /* ParseCloud.swift in Sources */, 91F346BA269B766D005727B6 /* CloudViewModel.swift in Sources */, + 709A148D2839A1DB00BF85E5 /* Operation.swift in Sources */, + 709A14A6283AAF4C00BF85E5 /* ParseSchema+combine.swift in Sources */, F97B461724D9C6F200F4A88B /* Queryable.swift in Sources */, 7028373526BD8883007688C9 /* ParseObject+async.swift in Sources */, 70C5503925B406B800B5DBC2 /* ParseSession.swift in Sources */, @@ -2455,13 +2530,16 @@ 91285B1D26990D7F0051B544 /* ParsePolygon.swift in Sources */, 91BB8FCB2690AC99005A6BA5 /* QueryViewModel.swift in Sources */, 7085DD9526CBF3A70033B977 /* Documentation.docc in Sources */, + 705025AA28441C96008D6624 /* ParseFieldOptions.swift in Sources */, F97B45D724D9C6F200F4A88B /* ParseEncoder.swift in Sources */, 700395A425A119430052CB31 /* Operations.swift in Sources */, 91BB8FD02690BA70005A6BA5 /* QueryObservable.swift in Sources */, 7045769926BD917500F86F71 /* Query+async.swift in Sources */, + 709A148328395ED100BF85E5 /* ParseSchema+async.swift in Sources */, 703B094F26BF47E3005A112F /* ParseTwitter+combine.swift in Sources */, 70386A3925D998D90048EC1B /* ParseLDAP.swift in Sources */, 700395F325A171320052CB31 /* LiveQueryable.swift in Sources */, + 709A14A12839CABD00BF85E5 /* ParseCLP.swift in Sources */, F97B45F324D9C6F200F4A88B /* Pointer.swift in Sources */, 703B095926BF480D005A112F /* ParseFacebook+combine.swift in Sources */, 70510AAD259EE25E00FEA700 /* LiveQuerySocket.swift in Sources */, @@ -2497,10 +2575,12 @@ 700396EB25A3892D0052CB31 /* LiveQuerySocketDelegate.swift in Sources */, 9116F67026A35D610082F6D6 /* URLCache.swift in Sources */, 70572672259033A700F0ADD5 /* ParseFileManager.swift in Sources */, + 709A148828396B1D00BF85E5 /* ParseField.swift in Sources */, 707A3C2125B14BD0000D215C /* ParseApple.swift in Sources */, 70F03A352780CA4D00E5AFB4 /* ParseGitHub.swift in Sources */, 703B095E26BF481F005A112F /* ParseFacebook+async.swift in Sources */, F97B462324D9C6F200F4A88B /* ParseKeyValueStore.swift in Sources */, + 709A147E283949D100BF85E5 /* ParseSchema.swift in Sources */, 703B090326BD9652005A112F /* ParseAnalytics+async.swift in Sources */, 703B094026BF47AC005A112F /* ParseApple+async.swift in Sources */, F97B45E724D9C6F200F4A88B /* Query.swift in Sources */, @@ -2621,6 +2701,7 @@ 917BA42C2703E03F00F8D747 /* ParseAnalyticsAsyncTests.swift in Sources */, 709B984F2556ECAA00507778 /* AnyCodableTests.swift in Sources */, 7003957825A0EE770052CB31 /* BatchUtilsTests.swift in Sources */, + 7050259B2842FD3B008D6624 /* ParseCLPTests.swift in Sources */, 705A99FB259807F900B3547F /* ParseFileManagerTests.swift in Sources */, 70F03A542780DA9200E5AFB4 /* ParseGoogleTests.swift in Sources */, 7044C20825C5D6780011F6E7 /* ParseQueryCombineTests.swift in Sources */, @@ -2638,6 +2719,7 @@ 709B98592556ECAA00507778 /* MockURLResponse.swift in Sources */, 7044C22225C5E0160011F6E7 /* ParseConfigCombineTests.swift in Sources */, 709B98522556ECAA00507778 /* ParseUserTests.swift in Sources */, + 7050259F2843F0CF008D6624 /* ParseSchemaAsyncTests.swift in Sources */, 709B984E2556ECAA00507778 /* ParseGeoPointTests.swift in Sources */, 7044C1BD25C52E410011F6E7 /* ParseInstallationCombineTests.swift in Sources */, 917BA45C2703FD2200F8D747 /* ParseAuthenticationAsyncTests.swift in Sources */, @@ -2646,6 +2728,7 @@ 70F03A602780EAC700E5AFB4 /* ParseGitHubTests.swift in Sources */, 709B98552556ECAA00507778 /* ParseQueryTests.swift in Sources */, 7085DDB526D1EC7F0033B977 /* ParseAuthenticationCombineTests.swift in Sources */, + 705025A32843F0E7008D6624 /* ParseSchemaCombineTests.swift in Sources */, 7044C1FB25C5CFAB0011F6E7 /* ParseFileCombineTests.swift in Sources */, 70C5502425B3D8F700B5DBC2 /* ParseAppleTests.swift in Sources */, 917BA4342703E36800F8D747 /* ParseConfigAsyncTests.swift in Sources */, @@ -2665,6 +2748,7 @@ 91BB8FD62690D586005A6BA5 /* ParseQueryViewModelTests.swift in Sources */, 709B98542556ECAA00507778 /* ParseInstallationTests.swift in Sources */, 917BA44C2703F10400F8D747 /* ParseAppleAsyncTests.swift in Sources */, + 705025A7284407C4008D6624 /* ParseSchemaTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2715,6 +2799,7 @@ 917BA42B2703E03F00F8D747 /* ParseAnalyticsAsyncTests.swift in Sources */, 70F2E2BD254F283000B2EA5C /* AnyDecodableTests.swift in Sources */, 7003957725A0EE770052CB31 /* BatchUtilsTests.swift in Sources */, + 7050259A2842FD3B008D6624 /* ParseCLPTests.swift in Sources */, 705A99FA259807F900B3547F /* ParseFileManagerTests.swift in Sources */, 70F03A532780DA9200E5AFB4 /* ParseGoogleTests.swift in Sources */, 7044C20725C5D6780011F6E7 /* ParseQueryCombineTests.swift in Sources */, @@ -2732,6 +2817,7 @@ 70F2E2C1254F283000B2EA5C /* AnyCodableTests.swift in Sources */, 7044C22125C5E0160011F6E7 /* ParseConfigCombineTests.swift in Sources */, 70F2E2B3254F283000B2EA5C /* ParseUserTests.swift in Sources */, + 7050259E2843F0CF008D6624 /* ParseSchemaAsyncTests.swift in Sources */, 70F2E2C0254F283000B2EA5C /* MockURLResponse.swift in Sources */, 7044C1BC25C52E410011F6E7 /* ParseInstallationCombineTests.swift in Sources */, 917BA45B2703FD2200F8D747 /* ParseAuthenticationAsyncTests.swift in Sources */, @@ -2740,6 +2826,7 @@ 70F03A5F2780EAC700E5AFB4 /* ParseGitHubTests.swift in Sources */, 70F2E2BF254F283000B2EA5C /* MockURLProtocol.swift in Sources */, 7085DDB426D1EC7F0033B977 /* ParseAuthenticationCombineTests.swift in Sources */, + 705025A22843F0E7008D6624 /* ParseSchemaCombineTests.swift in Sources */, 7044C1FA25C5CFAB0011F6E7 /* ParseFileCombineTests.swift in Sources */, 70C5502325B3D8F700B5DBC2 /* ParseAppleTests.swift in Sources */, 917BA4332703E36800F8D747 /* ParseConfigAsyncTests.swift in Sources */, @@ -2759,6 +2846,7 @@ 91BB8FD52690D586005A6BA5 /* ParseQueryViewModelTests.swift in Sources */, 70F2E2B9254F283000B2EA5C /* KeychainStoreTests.swift in Sources */, 917BA44B2703F10400F8D747 /* ParseAppleAsyncTests.swift in Sources */, + 705025A6284407C4008D6624 /* ParseSchemaTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2769,6 +2857,8 @@ F97B45D524D9C6F200F4A88B /* AnyDecodable.swift in Sources */, 916786E5259B7DDA00BB5B4E /* ParseCloud.swift in Sources */, 91F346BC269B766D005727B6 /* CloudViewModel.swift in Sources */, + 709A148F2839A1DB00BF85E5 /* Operation.swift in Sources */, + 709A14A8283AAF4C00BF85E5 /* ParseSchema+combine.swift in Sources */, F97B45E924D9C6F200F4A88B /* Query.swift in Sources */, F97B463624D9C74400F4A88B /* URLSession.swift in Sources */, 7028373726BD8883007688C9 /* ParseObject+async.swift in Sources */, @@ -2792,13 +2882,16 @@ 91679D67268E596300F71809 /* ParseVersion.swift in Sources */, 91285B1F26990D7F0051B544 /* ParsePolygon.swift in Sources */, 91BB8FCD2690AC99005A6BA5 /* QueryViewModel.swift in Sources */, + 705025AC28441C96008D6624 /* ParseFieldOptions.swift in Sources */, 7085DD9726CBF3A70033B977 /* Documentation.docc in Sources */, F97B465D24D9C78C00F4A88B /* Increment.swift in Sources */, 700395A625A119430052CB31 /* Operations.swift in Sources */, 91BB8FD22690BA70005A6BA5 /* QueryObservable.swift in Sources */, + 709A148528395ED100BF85E5 /* ParseSchema+async.swift in Sources */, 7045769B26BD917500F86F71 /* Query+async.swift in Sources */, 703B095126BF47E3005A112F /* ParseTwitter+combine.swift in Sources */, 70386A3B25D998D90048EC1B /* ParseLDAP.swift in Sources */, + 709A14A32839CABD00BF85E5 /* ParseCLP.swift in Sources */, 700395F525A171320052CB31 /* LiveQueryable.swift in Sources */, F97B45FD24D9C6F200F4A88B /* ParseACL.swift in Sources */, 703B095B26BF480D005A112F /* ParseFacebook+combine.swift in Sources */, @@ -2834,10 +2927,12 @@ 700396ED25A3892D0052CB31 /* LiveQuerySocketDelegate.swift in Sources */, 9116F67226A35D620082F6D6 /* URLCache.swift in Sources */, 70572674259033A700F0ADD5 /* ParseFileManager.swift in Sources */, + 709A148A28396B1D00BF85E5 /* ParseField.swift in Sources */, 707A3C2325B14BD0000D215C /* ParseApple.swift in Sources */, 70F03A372780CA4E00E5AFB4 /* ParseGitHub.swift in Sources */, 703B096026BF481F005A112F /* ParseFacebook+async.swift in Sources */, F97B462124D9C6F200F4A88B /* ParseStorage.swift in Sources */, + 709A1480283949D100BF85E5 /* ParseSchema.swift in Sources */, 703B090526BD9652005A112F /* ParseAnalytics+async.swift in Sources */, 703B094226BF47AC005A112F /* ParseApple+async.swift in Sources */, F97B466724D9C88600F4A88B /* SecureStorage.swift in Sources */, @@ -2909,6 +3004,8 @@ F97B45D424D9C6F200F4A88B /* AnyDecodable.swift in Sources */, 916786E4259B7DDA00BB5B4E /* ParseCloud.swift in Sources */, 91F346BB269B766D005727B6 /* CloudViewModel.swift in Sources */, + 709A148E2839A1DB00BF85E5 /* Operation.swift in Sources */, + 709A14A7283AAF4C00BF85E5 /* ParseSchema+combine.swift in Sources */, F97B45E824D9C6F200F4A88B /* Query.swift in Sources */, F97B463524D9C74400F4A88B /* URLSession.swift in Sources */, 7028373626BD8883007688C9 /* ParseObject+async.swift in Sources */, @@ -2932,13 +3029,16 @@ 91679D66268E596300F71809 /* ParseVersion.swift in Sources */, 91285B1E26990D7F0051B544 /* ParsePolygon.swift in Sources */, 91BB8FCC2690AC99005A6BA5 /* QueryViewModel.swift in Sources */, + 705025AB28441C96008D6624 /* ParseFieldOptions.swift in Sources */, 7085DD9626CBF3A70033B977 /* Documentation.docc in Sources */, F97B465C24D9C78C00F4A88B /* Increment.swift in Sources */, 700395A525A119430052CB31 /* Operations.swift in Sources */, 91BB8FD12690BA70005A6BA5 /* QueryObservable.swift in Sources */, + 709A148428395ED100BF85E5 /* ParseSchema+async.swift in Sources */, 7045769A26BD917500F86F71 /* Query+async.swift in Sources */, 703B095026BF47E3005A112F /* ParseTwitter+combine.swift in Sources */, 70386A3A25D998D90048EC1B /* ParseLDAP.swift in Sources */, + 709A14A22839CABD00BF85E5 /* ParseCLP.swift in Sources */, 700395F425A171320052CB31 /* LiveQueryable.swift in Sources */, F97B45FC24D9C6F200F4A88B /* ParseACL.swift in Sources */, 703B095A26BF480D005A112F /* ParseFacebook+combine.swift in Sources */, @@ -2974,10 +3074,12 @@ 700396EC25A3892D0052CB31 /* LiveQuerySocketDelegate.swift in Sources */, 9116F67126A35D620082F6D6 /* URLCache.swift in Sources */, 70572673259033A700F0ADD5 /* ParseFileManager.swift in Sources */, + 709A148928396B1D00BF85E5 /* ParseField.swift in Sources */, 707A3C2225B14BD0000D215C /* ParseApple.swift in Sources */, 70F03A362780CA4D00E5AFB4 /* ParseGitHub.swift in Sources */, 703B095F26BF481F005A112F /* ParseFacebook+async.swift in Sources */, F97B462024D9C6F200F4A88B /* ParseStorage.swift in Sources */, + 709A147F283949D100BF85E5 /* ParseSchema.swift in Sources */, 703B090426BD9652005A112F /* ParseAnalytics+async.swift in Sources */, 703B094126BF47AC005A112F /* ParseApple+async.swift in Sources */, F97B466624D9C88600F4A88B /* SecureStorage.swift in Sources */, diff --git a/Sources/ParseSwift/API/API.swift b/Sources/ParseSwift/API/API.swift index 00023b4e1..98f834cdf 100644 --- a/Sources/ParseSwift/API/API.swift +++ b/Sources/ParseSwift/API/API.swift @@ -44,6 +44,11 @@ public struct API { case aggregate(className: String) case config case health + case schemas + case schema(className: String) + case purge(className: String) + case triggers + case trigger(name: String, className: String) case any(String) var urlComponent: String { @@ -94,6 +99,16 @@ public struct API { return "/config" case .health: return "/health" + case .schemas: + return "/schemas" + case .schema(let className): + return "/schemas/\(className)" + case .purge(let className): + return "/purge/\(className)" + case .triggers: + return "/hooks/triggers" + case .trigger(let name, let className): + return "/hooks/triggers/\(className)/\(name)" case .any(let path): return path } diff --git a/Sources/ParseSwift/Authentication/3rd Party/ParseApple/ParseApple.swift b/Sources/ParseSwift/Authentication/3rd Party/ParseApple/ParseApple.swift index 5f8109262..1e678269a 100644 --- a/Sources/ParseSwift/Authentication/3rd Party/ParseApple/ParseApple.swift +++ b/Sources/ParseSwift/Authentication/3rd Party/ParseApple/ParseApple.swift @@ -39,7 +39,7 @@ public struct ParseApple: ParseAuthentication { /// Verifies all mandatory keys are in authData. /// - parameter authData: Dictionary containing key/values. - /// - returns: `true` if all the mandatory keys are present, `false` otherwise. + /// - returns: **true** if all the mandatory keys are present, **false** otherwise. func verifyMandatoryKeys(authData: [String: String]) -> Bool { guard authData[AuthenticationKeys.id.rawValue] != nil, authData[AuthenticationKeys.token.rawValue] != nil else { diff --git a/Sources/ParseSwift/Authentication/3rd Party/ParseFacebook/ParseFacebook.swift b/Sources/ParseSwift/Authentication/3rd Party/ParseFacebook/ParseFacebook.swift index b0fab5c18..4c03525f1 100644 --- a/Sources/ParseSwift/Authentication/3rd Party/ParseFacebook/ParseFacebook.swift +++ b/Sources/ParseSwift/Authentication/3rd Party/ParseFacebook/ParseFacebook.swift @@ -53,7 +53,7 @@ public struct ParseFacebook: ParseAuthentication { /// Verifies all mandatory keys are in authData. /// - parameter authData: Dictionary containing key/values. - /// - returns: `true` if all the mandatory keys are present, `false` otherwise. + /// - returns: **true** if all the mandatory keys are present, **false** otherwise. func verifyMandatoryKeys(authData: [String: String]) -> Bool { guard authData[AuthenticationKeys.id.rawValue] != nil else { return false diff --git a/Sources/ParseSwift/Authentication/3rd Party/ParseGithub/ParseGitHub.swift b/Sources/ParseSwift/Authentication/3rd Party/ParseGithub/ParseGitHub.swift index 74c10b526..57652b6e2 100644 --- a/Sources/ParseSwift/Authentication/3rd Party/ParseGithub/ParseGitHub.swift +++ b/Sources/ParseSwift/Authentication/3rd Party/ParseGithub/ParseGitHub.swift @@ -38,7 +38,7 @@ public struct ParseGitHub: ParseAuthentication { /// Verifies all mandatory keys are in authData. /// - parameter authData: Dictionary containing key/values. - /// - returns: `true` if all the mandatory keys are present, `false` otherwise. + /// - returns: **true** if all the mandatory keys are present, **false** otherwise. func verifyMandatoryKeys(authData: [String: String]) -> Bool { guard authData[AuthenticationKeys.id.rawValue] != nil, authData[AuthenticationKeys.accessToken.rawValue] != nil else { diff --git a/Sources/ParseSwift/Authentication/3rd Party/ParseGoogle/ParseGoogle.swift b/Sources/ParseSwift/Authentication/3rd Party/ParseGoogle/ParseGoogle.swift index 0268e6086..36dea317c 100644 --- a/Sources/ParseSwift/Authentication/3rd Party/ParseGoogle/ParseGoogle.swift +++ b/Sources/ParseSwift/Authentication/3rd Party/ParseGoogle/ParseGoogle.swift @@ -43,7 +43,7 @@ public struct ParseGoogle: ParseAuthentication { /// Verifies all mandatory keys are in authData. /// - parameter authData: Dictionary containing key/values. - /// - returns: `true` if all the mandatory keys are present, `false` otherwise. + /// - returns: **true** if all the mandatory keys are present, **false** otherwise. func verifyMandatoryKeys(authData: [String: String]) -> Bool { guard authData[AuthenticationKeys.id.rawValue] != nil else { return false diff --git a/Sources/ParseSwift/Authentication/3rd Party/ParseLDAP/ParseLDAP.swift b/Sources/ParseSwift/Authentication/3rd Party/ParseLDAP/ParseLDAP.swift index 0d029d058..4cf83313c 100644 --- a/Sources/ParseSwift/Authentication/3rd Party/ParseLDAP/ParseLDAP.swift +++ b/Sources/ParseSwift/Authentication/3rd Party/ParseLDAP/ParseLDAP.swift @@ -33,7 +33,7 @@ public struct ParseLDAP: ParseAuthentication { /// Verifies all mandatory keys are in authData. /// - parameter authData: Dictionary containing key/values. - /// - returns: `true` if all the mandatory keys are present, `false` otherwise. + /// - returns: **true** if all the mandatory keys are present, **false** otherwise. func verifyMandatoryKeys(authData: [String: String]) -> Bool { guard authData[AuthenticationKeys.id.rawValue] != nil, authData[AuthenticationKeys.password.rawValue] != nil else { diff --git a/Sources/ParseSwift/Authentication/3rd Party/ParseLinkedIn/ParseLinkedIn.swift b/Sources/ParseSwift/Authentication/3rd Party/ParseLinkedIn/ParseLinkedIn.swift index 19db7b906..84d762b74 100644 --- a/Sources/ParseSwift/Authentication/3rd Party/ParseLinkedIn/ParseLinkedIn.swift +++ b/Sources/ParseSwift/Authentication/3rd Party/ParseLinkedIn/ParseLinkedIn.swift @@ -41,7 +41,7 @@ public struct ParseLinkedIn: ParseAuthentication { /// Verifies all mandatory keys are in authData. /// - parameter authData: Dictionary containing key/values. - /// - returns: `true` if all the mandatory keys are present, `false` otherwise. + /// - returns: **true** if all the mandatory keys are present, **false** otherwise. func verifyMandatoryKeys(authData: [String: String]) -> Bool { guard authData[AuthenticationKeys.id.rawValue] != nil, authData[AuthenticationKeys.accessToken.rawValue] != nil, diff --git a/Sources/ParseSwift/Authentication/3rd Party/ParseTwitter/ParseTwitter.swift b/Sources/ParseSwift/Authentication/3rd Party/ParseTwitter/ParseTwitter.swift index ca6e9271a..8f065e6a4 100644 --- a/Sources/ParseSwift/Authentication/3rd Party/ParseTwitter/ParseTwitter.swift +++ b/Sources/ParseSwift/Authentication/3rd Party/ParseTwitter/ParseTwitter.swift @@ -54,7 +54,7 @@ public struct ParseTwitter: ParseAuthentication { /// Verifies all mandatory keys are in authData. /// - parameter authData: Dictionary containing key/values. - /// - returns: `true` if all the mandatory keys are present, `false` otherwise. + /// - returns: **true** if all the mandatory keys are present, **false** otherwise. func verifyMandatoryKeys(authData: [String: String]) -> Bool { guard authData[AuthenticationKeys.id.rawValue] != nil, authData[AuthenticationKeys.consumerKey.rawValue] != nil, diff --git a/Sources/ParseSwift/Authentication/Protocols/ParseAuthentication.swift b/Sources/ParseSwift/Authentication/Protocols/ParseAuthentication.swift index bb898e62c..385462581 100644 --- a/Sources/ParseSwift/Authentication/Protocols/ParseAuthentication.swift +++ b/Sources/ParseSwift/Authentication/Protocols/ParseAuthentication.swift @@ -25,7 +25,7 @@ public protocol ParseAuthentication: Codable { /// The type of authentication. static var __type: String { get } // swiftlint:disable:this identifier_name - /// Returns `true` if the *current* user is linked to the respective authentication type. + /// Returns **true** if the *current* user is linked to the respective authentication type. var isLinked: Bool { get } /// The default initializer for this authentication type. @@ -58,8 +58,8 @@ public protocol ParseAuthentication: Codable { /** Whether the `ParseUser` is logged in with the respective authentication type. - parameter user: The `ParseUser` to check authentication type. The user must be logged in on this device. - - returns: `true` if the `ParseUser` is logged in via the repective - authentication type. `false` if the user is not. + - returns: **true** if the `ParseUser` is logged in via the repective + authentication type. **false** if the user is not. */ func isLinked(with user: AuthenticatedUser) -> Bool @@ -306,8 +306,8 @@ public extension ParseUser { /** Whether the `ParseUser` is logged in with the respective authentication string type. - parameter type: The authentication type to check. The user must be logged in on this device. - - returns: `true` if the `ParseUser` is logged in via the repective - authentication type. `false` if the user is not. + - returns: **true** if the `ParseUser` is logged in via the repective + authentication type. **false** if the user is not. */ func isLinked(with type: String) -> Bool { guard let authData = self.authData?[type] else { diff --git a/Sources/ParseSwift/Coding/ParseEncoder.swift b/Sources/ParseSwift/Coding/ParseEncoder.swift index c42fcb7ce..2943deb9d 100644 --- a/Sources/ParseSwift/Coding/ParseEncoder.swift +++ b/Sources/ParseSwift/Coding/ParseEncoder.swift @@ -233,7 +233,7 @@ internal class _ParseEncoder: JSONEncoder, Encoder { /// Returns whether a new element can be encoded at this coding path. /// - /// `true` if an element has not yet been encoded at this coding path; `false` otherwise. + /// **true** if an element has not yet been encoded at this coding path; **false** otherwise. var canEncodeNewValue: Bool { // Every time a new value gets encoded, the key it's encoded for is pushed onto the coding path (even if it's a nil key from an unkeyed container). // At the same time, every time a container is requested, a new value gets pushed onto the storage stack. diff --git a/Sources/ParseSwift/LiveQuery/ParseLiveQuery+async.swift b/Sources/ParseSwift/LiveQuery/ParseLiveQuery+async.swift index e98191070..515f72359 100644 --- a/Sources/ParseSwift/LiveQuery/ParseLiveQuery+async.swift +++ b/Sources/ParseSwift/LiveQuery/ParseLiveQuery+async.swift @@ -14,7 +14,7 @@ extension ParseLiveQuery { /** Manually establish a connection to the `ParseLiveQuery` Server. - - parameter isUserWantsToConnect: Specifies if the user is calling this function. Defaults to `true`. + - parameter isUserWantsToConnect: Specifies if the user is calling this function. Defaults to **true**. - returns: An instance of the logged in `ParseUser`. - throws: An error of type `ParseError`. */ diff --git a/Sources/ParseSwift/LiveQuery/ParseLiveQuery+combine.swift b/Sources/ParseSwift/LiveQuery/ParseLiveQuery+combine.swift index 0e34cf452..c895067c5 100644 --- a/Sources/ParseSwift/LiveQuery/ParseLiveQuery+combine.swift +++ b/Sources/ParseSwift/LiveQuery/ParseLiveQuery+combine.swift @@ -15,7 +15,7 @@ extension ParseLiveQuery { /** Manually establish a connection to the `ParseLiveQuery` Server. Publishes when established. - - parameter isUserWantsToConnect: Specifies if the user is calling this function. Defaults to `true`. + - parameter isUserWantsToConnect: Specifies if the user is calling this function. Defaults to **true**. - returns: A publisher that eventually produces a single value and then finishes or fails. */ public func openPublisher(isUserWantsToConnect: Bool = true) -> Future { diff --git a/Sources/ParseSwift/LiveQuery/ParseLiveQuery.swift b/Sources/ParseSwift/LiveQuery/ParseLiveQuery.swift index 2cf4475c8..5fe754031 100644 --- a/Sources/ParseSwift/LiveQuery/ParseLiveQuery.swift +++ b/Sources/ParseSwift/LiveQuery/ParseLiveQuery.swift @@ -600,7 +600,7 @@ extension ParseLiveQuery: LiveQuerySocketDelegate { extension ParseLiveQuery { /// Manually establish a connection to the `ParseLiveQuery` Server. - /// - parameter isUserWantsToConnect: Specifies if the user is calling this function. Defaults to `true`. + /// - parameter isUserWantsToConnect: Specifies if the user is calling this function. Defaults to **true**. /// - parameter completion: Returns `nil` if successful, an `Error` otherwise. public func open(isUserWantsToConnect: Bool = true, completion: @escaping (Error?) -> Void) { synchronizationQueue.sync { diff --git a/Sources/ParseSwift/Objects/ParseInstallation+async.swift b/Sources/ParseSwift/Objects/ParseInstallation+async.swift index 0cb176c15..095857cea 100644 --- a/Sources/ParseSwift/Objects/ParseInstallation+async.swift +++ b/Sources/ParseSwift/Objects/ParseInstallation+async.swift @@ -13,8 +13,7 @@ public extension ParseInstallation { // MARK: Async/Await /** - Fetches the `ParseInstallation` *aynchronously* with the current data from the server - and sets an error if one occurs. + Fetches the `ParseInstallation` *aynchronously* with the current data from the server. - parameter includeKeys: The name(s) of the key(s) to include that are `ParseObject`s. Use `["*"]` to include all keys one level deep. This is similar to `include` and `includeAll` for `Query`. diff --git a/Sources/ParseSwift/Objects/ParseInstallation+combine.swift b/Sources/ParseSwift/Objects/ParseInstallation+combine.swift index 4265326df..07020cd04 100644 --- a/Sources/ParseSwift/Objects/ParseInstallation+combine.swift +++ b/Sources/ParseSwift/Objects/ParseInstallation+combine.swift @@ -14,8 +14,8 @@ public extension ParseInstallation { // MARK: Combine /** - Fetches the `ParseInstallation` *aynchronously* with the current data from the server - and sets an error if one occurs. Publishes when complete. + Fetches the `ParseInstallation` *aynchronously* with the current data from the server. + Publishes when complete. - parameter includeKeys: The name(s) of the key(s) to include that are `ParseObject`s. Use `["*"]` to include all keys one level deep. This is similar to `include` and `includeAll` for `Query`. diff --git a/Sources/ParseSwift/Objects/ParseInstallation.swift b/Sources/ParseSwift/Objects/ParseInstallation.swift index bdd7979c4..9b3853f35 100644 --- a/Sources/ParseSwift/Objects/ParseInstallation.swift +++ b/Sources/ParseSwift/Objects/ParseInstallation.swift @@ -424,8 +424,7 @@ extension ParseInstallation { } /** - Fetches the `ParseInstallation` *synchronously* with the current data from the server - and sets an error if one occurs. + Fetches the `ParseInstallation` *synchronously* with the current data from the server. - parameter includeKeys: The name(s) of the key(s) to include that are `ParseObject`s. Use `["*"]` to include all keys one level deep. This is similar to `include` and `includeAll` for `Query`. @@ -814,8 +813,7 @@ extension ParseInstallation { // MARK: Deletable extension ParseInstallation { /** - Deletes the `ParseInstallation` *synchronously* with the current data from the server - and sets an error if one occurs. + Deletes the `ParseInstallation` *synchronously* with the current data from the server. - parameter options: A set of header options sent to the server. Defaults to an empty set. - throws: An error of `ParseError` type. diff --git a/Sources/ParseSwift/Objects/ParseObject+async.swift b/Sources/ParseSwift/Objects/ParseObject+async.swift index aacc46fe3..39f8af389 100644 --- a/Sources/ParseSwift/Objects/ParseObject+async.swift +++ b/Sources/ParseSwift/Objects/ParseObject+async.swift @@ -13,7 +13,7 @@ public extension ParseObject { // MARK: Async/Await /** - Fetches the `ParseObject` *aynchronously* with the current data from the server and sets an error if one occurs. + Fetches the `ParseObject` *aynchronously* with the current data from the server. - parameter includeKeys: The name(s) of the key(s) to include that are `ParseObject`s. Use `["*"]` to include all keys one level deep. This is similar to `include` and `includeAll` for `Query`. diff --git a/Sources/ParseSwift/Objects/ParseObject+combine.swift b/Sources/ParseSwift/Objects/ParseObject+combine.swift index cc79fdbc3..c320750e1 100644 --- a/Sources/ParseSwift/Objects/ParseObject+combine.swift +++ b/Sources/ParseSwift/Objects/ParseObject+combine.swift @@ -14,7 +14,7 @@ public extension ParseObject { // MARK: Combine /** - Fetches the `ParseObject` *aynchronously* with the current data from the server and sets an error if one occurs. + Fetches the `ParseObject` *aynchronously* with the current data from the server. Publishes when complete. - parameter includeKeys: The name(s) of the key(s) to include that are `ParseObject`s. Use `["*"]` to include all keys one level deep. This is similar to `include` and diff --git a/Sources/ParseSwift/Objects/ParseObject.swift b/Sources/ParseSwift/Objects/ParseObject.swift index e9b511bca..ce4a4d039 100644 --- a/Sources/ParseSwift/Objects/ParseObject.swift +++ b/Sources/ParseSwift/Objects/ParseObject.swift @@ -85,7 +85,7 @@ public protocol ParseObject: Objectable, Determines if a `KeyPath` of the current `ParseObject` should be restored by comparing it to another `ParseObject`. - parameter original: The original `ParseObject`. - - returns: Returns a `true` if the keyPath should be restored or `false` otherwise. + - returns: Returns a **true** if the keyPath should be restored or **false** otherwise. */ func shouldRestoreKey(_ key: KeyPath, original: Self) -> Bool where W: Equatable @@ -169,7 +169,7 @@ public extension ParseObject { /** 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. + - returns: Returns a **true** if the other object has the same `objectId` or **false** if unsuccessful. */ func hasSameObjectId(as other: T) -> Bool { return other.className == className && other.objectId == objectId && objectId != nil @@ -817,7 +817,7 @@ extension ParseObject { extension ParseObject { /** - Fetches the `ParseObject` *synchronously* with the current data from the server and sets an error if one occurs. + Fetches the `ParseObject` *synchronously* with the current data from the server. - parameter includeKeys: The name(s) of the key(s) to include that are `ParseObject`s. Use `["*"]` to include all keys one level deep. This is similar to `include` and `includeAll` for `Query`. @@ -1211,7 +1211,7 @@ internal extension ParseType { // MARK: Deletable extension ParseObject { /** - Deletes the `ParseObject` *synchronously* with the current data from the server and sets an error if one occurs. + Deletes the `ParseObject` *synchronously* with the current data from the server. - parameter options: A set of header options sent to the server. Defaults to an empty set. - throws: An error of `ParseError` type. diff --git a/Sources/ParseSwift/Objects/ParseUser+async.swift b/Sources/ParseSwift/Objects/ParseUser+async.swift index 725865460..29e8baf18 100644 --- a/Sources/ParseSwift/Objects/ParseUser+async.swift +++ b/Sources/ParseSwift/Objects/ParseUser+async.swift @@ -182,7 +182,7 @@ public extension ParseUser { } /** - Fetches the `ParseUser` *aynchronously* with the current data from the server and sets an error if one occurs. + Fetches the `ParseUser` *aynchronously* with the current data from the server. - parameter includeKeys: The name(s) of the key(s) to include that are `ParseObject`s. Use `["*"]` to include all keys one level deep. This is similar to `include` and `includeAll` for `Query`. diff --git a/Sources/ParseSwift/Objects/ParseUser+combine.swift b/Sources/ParseSwift/Objects/ParseUser+combine.swift index b3245da93..bd4be53ff 100644 --- a/Sources/ParseSwift/Objects/ParseUser+combine.swift +++ b/Sources/ParseSwift/Objects/ParseUser+combine.swift @@ -171,7 +171,7 @@ public extension ParseUser { } /** - Fetches the `ParseUser` *aynchronously* with the current data from the server and sets an error if one occurs. + Fetches the `ParseUser` *aynchronously* with the current data from the server. Publishes when complete. - parameter includeKeys: The name(s) of the key(s) to include that are `ParseObject`s. Use `["*"]` to include all keys one level deep. This is similar to `include` and diff --git a/Sources/ParseSwift/Objects/ParseUser.swift b/Sources/ParseSwift/Objects/ParseUser.swift index e46beb043..89da293ed 100644 --- a/Sources/ParseSwift/Objects/ParseUser.swift +++ b/Sources/ParseSwift/Objects/ParseUser.swift @@ -833,7 +833,7 @@ extension ParseUser { } /** - Fetches the `ParseUser` *synchronously* with the current data from the server and sets an error if one occurs. + Fetches the `ParseUser` *synchronously* with the current data from the server. - parameter includeKeys: The name(s) of the key(s) to include that are `ParseObject`s. Use `["*"]` to include all keys one level deep. This is similar to `include` and `includeAll` for `Query`. @@ -875,7 +875,7 @@ extension ParseUser { ) { var options = options options.insert(.cachePolicy(.reloadIgnoringLocalCacheData)) - do { + do { try fetchCommand(include: includeKeys) .executeAsync(options: options, callbackQueue: callbackQueue) { result in @@ -1245,7 +1245,7 @@ extension ParseUser { // MARK: Deletable extension ParseUser { /** - Deletes the `ParseUser` *synchronously* with the current data from the server and sets an error if one occurs. + Deletes the `ParseUser` *synchronously* with the current data from the server. - parameter options: A set of header options sent to the server. Defaults to an empty set. - throws: An error of `ParseError` type. diff --git a/Sources/ParseSwift/Operations/Add.swift b/Sources/ParseSwift/Operations/Add.swift index 474368925..ebd08c536 100644 --- a/Sources/ParseSwift/Operations/Add.swift +++ b/Sources/ParseSwift/Operations/Add.swift @@ -9,6 +9,6 @@ import Foundation internal struct Add: Encodable where T: Encodable { - let __op: String = "Add" // swiftlint:disable:this identifier_name + let __op: Operation = .add // swiftlint:disable:this identifier_name let objects: [T] } diff --git a/Sources/ParseSwift/Operations/AddRelation.swift b/Sources/ParseSwift/Operations/AddRelation.swift index 840298ef9..e0206a42e 100644 --- a/Sources/ParseSwift/Operations/AddRelation.swift +++ b/Sources/ParseSwift/Operations/AddRelation.swift @@ -9,7 +9,7 @@ import Foundation internal struct AddRelation: Encodable where T: ParseObject { - let __op: String = "AddRelation" // swiftlint:disable:this identifier_name + let __op: Operation = .addRelation // swiftlint:disable:this identifier_name let objects: [Pointer] init(objects: [T]) throws { diff --git a/Sources/ParseSwift/Operations/AddUnique.swift b/Sources/ParseSwift/Operations/AddUnique.swift index 77606c83e..1e2f40d71 100644 --- a/Sources/ParseSwift/Operations/AddUnique.swift +++ b/Sources/ParseSwift/Operations/AddUnique.swift @@ -9,6 +9,6 @@ import Foundation internal struct AddUnique: Encodable where T: Encodable { - let __op: String = "AddUnique" // swiftlint:disable:this identifier_name + let __op: Operation = .addUnique // swiftlint:disable:this identifier_name let objects: [T] } diff --git a/Sources/ParseSwift/Operations/Delete.swift b/Sources/ParseSwift/Operations/Delete.swift index 715a4c823..020f72afd 100644 --- a/Sources/ParseSwift/Operations/Delete.swift +++ b/Sources/ParseSwift/Operations/Delete.swift @@ -9,5 +9,5 @@ import Foundation internal struct Delete: Encodable { - let __op: String = "Delete" // swiftlint:disable:this identifier_name + let __op: Operation = .delete // swiftlint:disable:this identifier_name } diff --git a/Sources/ParseSwift/Operations/Increment.swift b/Sources/ParseSwift/Operations/Increment.swift index d904a6e9f..b52fc4dc6 100644 --- a/Sources/ParseSwift/Operations/Increment.swift +++ b/Sources/ParseSwift/Operations/Increment.swift @@ -9,6 +9,6 @@ import Foundation internal struct Increment: Encodable { - let __op: String = "Increment" // swiftlint:disable:this identifier_name + let __op: Operation = .increment // swiftlint:disable:this identifier_name let amount: Int } diff --git a/Sources/ParseSwift/Operations/Operation.swift b/Sources/ParseSwift/Operations/Operation.swift new file mode 100644 index 000000000..496019696 --- /dev/null +++ b/Sources/ParseSwift/Operations/Operation.swift @@ -0,0 +1,19 @@ +// +// Operation.swift +// ParseSwift +// +// Created by Corey Baker on 5/21/22. +// Copyright © 2022 Parse Community. All rights reserved. +// + +import Foundation + +enum Operation: String, Codable { + case add = "Add" + case addRelation = "AddRelation" + case addUnique = "AddUnique" + case delete = "Delete" + case increment = "Increment" + case remove = "Remove" + case removeRelation = "RemoveRelation" +} diff --git a/Sources/ParseSwift/Operations/Remove.swift b/Sources/ParseSwift/Operations/Remove.swift index 7022b7c24..3455d7a01 100644 --- a/Sources/ParseSwift/Operations/Remove.swift +++ b/Sources/ParseSwift/Operations/Remove.swift @@ -9,6 +9,6 @@ import Foundation internal struct Remove: Encodable where T: Encodable { - let __op: String = "Remove" // swiftlint:disable:this identifier_name + let __op: Operation = .remove // swiftlint:disable:this identifier_name let objects: [T] } diff --git a/Sources/ParseSwift/Operations/RemoveRelation.swift b/Sources/ParseSwift/Operations/RemoveRelation.swift index 2d9b8e142..c72b8e9e8 100644 --- a/Sources/ParseSwift/Operations/RemoveRelation.swift +++ b/Sources/ParseSwift/Operations/RemoveRelation.swift @@ -9,7 +9,7 @@ import Foundation internal struct RemoveRelation: Encodable where T: ParseObject { - let __op: String = "RemoveRelation" // swiftlint:disable:this identifier_name + let __op: Operation = .removeRelation // swiftlint:disable:this identifier_name let objects: [Pointer] init(objects: [T]) throws { diff --git a/Sources/ParseSwift/Parse.swift b/Sources/ParseSwift/Parse.swift index 2381cf360..ee453a7e6 100644 --- a/Sources/ParseSwift/Parse.swift +++ b/Sources/ParseSwift/Parse.swift @@ -52,12 +52,12 @@ public struct ParseConfiguration { public internal(set) var cacheDiskCapacity = 10_000_000 /// If your app previously used the iOS Objective-C SDK, setting this value - /// to `true` will attempt to migrate relevant data stored in the Keychain to - /// ParseSwift. Defaults to `false`. + /// to **true** will attempt to migrate relevant data stored in the Keychain to + /// ParseSwift. Defaults to **false**. public internal(set) var isMigratingFromObjcSDK: Bool = false /// Deletes the Parse Keychain when the app is running for the first time. - /// Defaults to `false`. + /// Defaults to **false**. public internal(set) var isDeletingKeychainIfNeeded: Bool = false /// Maximum number of times to try to connect to Parse Server. @@ -91,9 +91,9 @@ public struct ParseConfiguration { - parameter cacheMemoryCapacity: The memory capacity of the cache, in bytes. Defaults to 512KB. - parameter cacheDiskCapacity: The disk capacity of the cache, in bytes. Defaults to 10MB. - parameter migratingFromObjcSDK: If your app previously used the iOS Objective-C SDK, setting this value - to `true` will attempt to migrate relevant data stored in the Keychain to ParseSwift. Defaults to `false`. + to **true** will attempt to migrate relevant data stored in the Keychain to ParseSwift. Defaults to **false**. - parameter deletingKeychainIfNeeded: Deletes the Parse Keychain when the app is running for the first time. - Defaults to `false`. + Defaults to **false**. - parameter httpAdditionalHeaders: A dictionary of additional headers to send with requests. See Apple's [documentation](https://developer.apple.com/documentation/foundation/urlsessionconfiguration/1411532-httpadditionalheaders) for more info. @@ -256,9 +256,9 @@ public struct ParseSwift { - parameter cacheMemoryCapacity: The memory capacity of the cache, in bytes. Defaults to 512KB. - parameter cacheDiskCapacity: The disk capacity of the cache, in bytes. Defaults to 10MB. - parameter migratingFromObjcSDK: If your app previously used the iOS Objective-C SDK, setting this value - to `true` will attempt to migrate relevant data stored in the Keychain to ParseSwift. Defaults to `false`. + to **true** will attempt to migrate relevant data stored in the Keychain to ParseSwift. Defaults to **false**. - parameter deletingKeychainIfNeeded: Deletes the Parse Keychain when the app is running for the first time. - Defaults to `false`. + Defaults to **false**. - parameter httpAdditionalHeaders: A dictionary of additional headers to send with requests. See Apple's [documentation](https://developer.apple.com/documentation/foundation/urlsessionconfiguration/1411532-httpadditionalheaders) for more info. diff --git a/Sources/ParseSwift/Protocols/QueryObservable.swift b/Sources/ParseSwift/Protocols/QueryObservable.swift index d0a6017ce..5d8ae0891 100644 --- a/Sources/ParseSwift/Protocols/QueryObservable.swift +++ b/Sources/ParseSwift/Protocols/QueryObservable.swift @@ -63,7 +63,9 @@ public protocol QueryObservable: ObservableObject { /** Executes an aggregate query *asynchronously* and updates the view model when complete. - - requires: `.useMasterKey` has to be available. + - requires: `.useMasterKey` has to be available. It is recommended to only + use the master key in server-side applications where the key is kept secure and not + exposed to the public. - parameter pipeline: A pipeline of stages to process query. - parameter options: A set of header options sent to the server. Defaults to an empty set. - warning: This hasn't been tested thoroughly. diff --git a/Sources/ParseSwift/Types/ParseACL.swift b/Sources/ParseSwift/Types/ParseACL.swift index b58fc3cbb..709b79b99 100644 --- a/Sources/ParseSwift/Types/ParseACL.swift +++ b/Sources/ParseSwift/Types/ParseACL.swift @@ -47,6 +47,17 @@ public struct ParseACL: ParseType, /// The default initializer. public init() { } + static func getRoleAccessName(_ role: R) throws -> String where R: ParseRole { + guard let name = role.name else { + throw ParseError(code: .unknownError, message: "Name of ParseRole cannot be nil") + } + return getRoleAccessName(name) + } + + static func getRoleAccessName(_ name: String) -> String { + return "role:\(name)" + } + /** Controls whether the public is allowed to read this object. */ @@ -75,7 +86,7 @@ public struct ParseACL: ParseType, Returns true if a particular key has a specific access level. - parameter key: The key of the `ParseUser` or `ParseRole` for which to retrieve access. - parameter access: The type of access. - - returns: `true` if the `key` has *explicit* access, otherwise `false`. + - returns: **true** if the `key` has *explicit* access, otherwise **false**. */ func get(_ key: String, access: Access) -> Bool { guard let acl = acl else { // no acl, all open! @@ -87,11 +98,11 @@ public struct ParseACL: ParseType, // MARK: ParseUser /** Gets whether the given `objectId` is *explicitly* allowed to read this object. - Even if this returns `false`, the user may still be able to access it if `publicReadAccess` returns `true` + Even if this returns **false**, the user may still be able to access it if `publicReadAccess` returns **true** or if the user belongs to a role that has access. - parameter objectId: The `ParseUser.objectId` of the user for which to retrieve access. - - returns: `true` if the user with this `objectId` has *explicit* read access, otherwise `false`. + - returns: **true** if the user with this `objectId` has *explicit* read access, otherwise **false**. */ public func getReadAccess(objectId: String) -> Bool { get(objectId, access: .read) @@ -99,11 +110,11 @@ public struct ParseACL: ParseType, /** Gets whether the given `ParseUser` is *explicitly* allowed to read this object. - Even if this returns `false`, the user may still be able to access it if `publicReadAccess` returns `true` + Even if this returns **false**, the user may still be able to access it if `publicReadAccess` returns **true** or if the user belongs to a role that has access. - parameter user: The `ParseUser` for which to retrieve access. - - returns: `true` if the user with this `ParseUser` has *explicit* read access, otherwise `false`. + - returns: **true** if the user with this `ParseUser` has *explicit* read access, otherwise **false**. */ public func getReadAccess(user: T) -> Bool where T: ParseUser { if let objectId = user.objectId { @@ -115,11 +126,11 @@ public struct ParseACL: ParseType, /** Gets whether the given `objectId` is *explicitly* allowed to write this object. - Even if this returns false, the user may still be able to write it if `publicWriteAccess` returns `true` + Even if this returns false, the user may still be able to write it if `publicWriteAccess` returns **true** or if the user belongs to a role that has access. - parameter objectId: The `ParseUser.objectId` of the user for which to retrieve access. - - returns: `true` if the user with this `ParseUser.objectId` has *explicit* write access, otherwise `false`. + - returns: **true** if the user with this `ParseUser.objectId` has *explicit* write access, otherwise **false**. */ public func getWriteAccess(objectId: String) -> Bool { return get(objectId, access: .write) @@ -127,11 +138,11 @@ public struct ParseACL: ParseType, /** Gets whether the given `ParseUser` is *explicitly* allowed to write this object. - Even if this returns false, the user may still be able to write it if `publicWriteAccess` returns `true` + Even if this returns false, the user may still be able to write it if `publicWriteAccess` returns **true** or if the user belongs to a role that has access. - parameter user: The `ParseUser` of the user for which to retrieve access. - - returns: `true` if the `ParseUser` has *explicit* write access, otherwise `false`. + - returns: **true** if the `ParseUser` has *explicit* write access, otherwise **false**. */ public func getWriteAccess(user: T) -> Bool where T: ParseUser { if let objectId = user.objectId { @@ -189,48 +200,48 @@ public struct ParseACL: ParseType, /** Get whether users belonging to the role with the given name are allowed to read this object. - Even if this returns `false`, the role may still be able to read it if a parent role has read access. + Even if this returns **false**, the role may still be able to read it if a parent role has read access. - parameter roleName: The name of the role. - - returns: `true` if the role has read access, otherwise `false`. + - returns: **true** if the role has read access, otherwise **false**. */ public func getReadAccess(roleName: String) -> Bool { - get(toRole(roleName: roleName), access: .read) + get(Self.getRoleAccessName(roleName), access: .read) } /** Get whether users belonging to the role are allowed to read this object. - Even if this returns `false`, the role may still be able to read it if a parent role has read access. + Even if this returns **false**, the role may still be able to read it if a parent role has read access. - parameter role: The `ParseRole` to get access for. - - returns: `true` if the `ParseRole` has read access, otherwise `false`. + - returns: **true** if the `ParseRole` has read access, otherwise **false**. */ public func getReadAccess(role: T) -> Bool where T: ParseRole { guard let name = role.name else { return false } - return get(toRole(roleName: name), access: .read) + return get(Self.getRoleAccessName(name), access: .read) } /** Get whether users belonging to the role with the given name are allowed to write this object. - Even if this returns `false`, the role may still be able to write it if a parent role has write access. + Even if this returns **false**, the role may still be able to write it if a parent role has write access. - parameter roleName: The name of the role. - - returns: `true` if the role has read access, otherwise `false`. + - returns: **true** if the role has read access, otherwise **false**. */ public func getWriteAccess(roleName: String) -> Bool { - get(toRole(roleName: roleName), access: .write) + get(Self.getRoleAccessName(roleName), access: .write) } /** Get whether users belonging to the role are allowed to write this object. - Even if this returns `false`, the role may still be able to write it if a parent role has write access. + Even if this returns **false**, the role may still be able to write it if a parent role has write access. - parameter role: The `ParseRole` to get access for. - - returns: `true` if the role has read access, otherwise `false`. + - returns: **true** if the role has read access, otherwise **false**. */ public func getWriteAccess(role: T) -> Bool where T: ParseRole { guard let name = role.name else { return false } - return get(toRole(roleName: name), access: .write) + return get(Self.getRoleAccessName(name), access: .write) } /** @@ -240,7 +251,7 @@ public struct ParseACL: ParseType, - parameter roleName: The name of the role. */ public mutating func setReadAccess(roleName: String, value: Bool) { - set(toRole(roleName: roleName), access: .read, value: value) + set(Self.getRoleAccessName(roleName), access: .read, value: value) } /** @@ -251,7 +262,7 @@ public struct ParseACL: ParseType, */ public mutating func setReadAccess(role: T, value: Bool) where T: ParseRole { guard let name = role.name else { return } - set(toRole(roleName: name), access: .read, value: value) + set(Self.getRoleAccessName(name), access: .read, value: value) } /** @@ -261,7 +272,7 @@ public struct ParseACL: ParseType, - parameter roleName: The name of the role. */ public mutating func setWriteAccess(roleName: String, value: Bool) { - set(toRole(roleName: roleName), access: .write, value: value) + set(Self.getRoleAccessName(roleName), access: .write, value: value) } /** @@ -272,11 +283,7 @@ public struct ParseACL: ParseType, */ public mutating func setWriteAccess(role: T, value: Bool) where T: ParseRole { guard let name = role.name else { return } - set(toRole(roleName: name), access: .write, value: value) - } - - private func toRole(roleName: String) -> String { - "role:\(roleName)" + set(Self.getRoleAccessName(name), access: .write, value: value) } private mutating func set(_ key: String, access: Access, value: Bool) { @@ -365,10 +372,10 @@ extension ParseACL { This value will be copied and used as a template for the creation of new ACLs, so changes to the instance after this method has been called will not be reflected in new instance of `ParseObject`. - - parameter withAccessForCurrentUser: If `true`, the `ACL` that is applied to + - parameter withAccessForCurrentUser: If **true**, the `ACL` that is applied to newly-created instance of `ParseObject` will provide read and write access to the `ParseUser.+currentUser` at the time of creation. - - If `false`, the provided `acl` will be used without modification. + - If **false**, the provided `acl` will be used without modification. - If `acl` is `nil`, this value is ignored. - returns: Updated defaultACL diff --git a/Sources/ParseSwift/Types/ParseCLP.swift b/Sources/ParseSwift/Types/ParseCLP.swift new file mode 100644 index 000000000..7cc90afec --- /dev/null +++ b/Sources/ParseSwift/Types/ParseCLP.swift @@ -0,0 +1,1132 @@ +// +// ParseCLP.swift +// ParseSwift +// +// Created by Corey Baker on 5/21/22. +// Copyright © 2022 Parse Community. All rights reserved. +// + +import Foundation + +/// Class Level Permissions for `ParseSchema`. +public struct ParseCLP: Codable, Equatable { + + var get: [String: AnyCodable]? + var find: [String: AnyCodable]? + var count: [String: AnyCodable]? + var create: [String: AnyCodable]? + var update: [String: AnyCodable]? + var delete: [String: AnyCodable]? + var addField: [String: AnyCodable]? + /** + The users, roles, and access level restrictions who cannot access particular + fields in a Parse class. + */ + public internal(set) var protectedFields: [String: Set]? + /** + Fields of `ParseUser` type or an array of `ParseUser`'s that + can perform get/count/find actions on a Parse class. + */ + public var readUserFields: Set? + /** + Fields of `ParseUser` type or an array of `ParseUser`'s that + can perform create/delete/update/addField actions on a Parse class. + */ + public var writeUserFields: Set? + + /// The avialable actions to perform on a Parse class. + public enum Action { + /// Fetch `ParseObject`'s. + case get + /// Query for `ParseObject`'s. + case find + /// Count `ParseObject`'s. + case count + /// Create new `ParseObject`'s. + case create + /// Update `ParseObject`'s. + case update + /// Delete `ParseObject`'s. + case delete + /// Add fields to the Parse class. + case addField + + internal func keyPath() -> KeyPath { + let keyPath: KeyPath + switch self { + case .get: + keyPath = \.get + case .find: + keyPath = \.find + case .count: + keyPath = \.count + case .create: + keyPath = \.create + case .update: + keyPath = \.update + case .delete: + keyPath = \.delete + case .addField: + keyPath = \.addField + } + return keyPath + } + + internal func writableKeyPath() -> WritableKeyPath { + let keyPath: WritableKeyPath + switch self { + case .get: + keyPath = \.get + case .find: + keyPath = \.find + case .count: + keyPath = \.count + case .create: + keyPath = \.create + case .update: + keyPath = \.update + case .delete: + keyPath = \.delete + case .addField: + keyPath = \.addField + } + return keyPath + } + } + + enum Access: String { + case requiresAuthentication + case publicScope = "*" + case pointerFields + } + + /// Creates an empty instance of CLP. + public init() { } + + /** + Creates an instance of CLP with particular access. + - parameter requiresAuthentication: Read/Write to a Parse class requires users to be authenticated. + - parameter publicAccess:Read/Write to a Parse class can be done by the public. + - warning: Setting `requiresAuthentication` and `publicAccess` does not give **addField** + access. You can set **addField** access after creating an instance of CLP. + - warning: Use of `requiresAuthentication == true` requires Parse Server 2.3.0+. + */ + public init(requiresAuthentication: Bool, publicAccess: Bool) { + let clp = setWriteAccessRequiresAuthentication(requiresAuthentication) + .setReadAccessRequiresAuthentication(requiresAuthentication) + .setWriteAccessPublic(publicAccess) + .setReadAccessPublic(publicAccess) + self = clp + } +} + +// MARK: Default Implementation +extension ParseCLP { + static func getUserFieldAccess(_ field: String) -> String { + "userField:\(field)" + } + + func hasAccess(_ keyPath: KeyPath, + for entity: String) -> Bool { + self[keyPath: keyPath]?[entity]?.value as? Bool ?? false + } + + func setAccess(_ allow: Bool, + on keyPath: WritableKeyPath, + for entity: String) -> Self { + let allowed: Bool? = allow ? allow : nil + var mutableCLP = self + if let allowed = allowed { + let value = AnyCodable(allowed) + if mutableCLP[keyPath: keyPath] != nil { + mutableCLP[keyPath: keyPath]?[entity] = value + } else { + mutableCLP[keyPath: keyPath] = [entity: value] + } + } else { + mutableCLP[keyPath: keyPath]?[entity] = nil + } + return mutableCLP + } + + func getPointerFields(_ keyPath: KeyPath) -> Set { + self[keyPath: keyPath]?[Access.pointerFields.rawValue]?.value as? Set ?? [] + } + + func setPointer(_ fields: Set, + on keyPath: WritableKeyPath) -> Self { + var mutableCLP = self + let value = AnyCodable(fields) + if mutableCLP[keyPath: keyPath] != nil { + mutableCLP[keyPath: keyPath]?[Access.pointerFields.rawValue] = value + } else { + mutableCLP[keyPath: keyPath] = [Access.pointerFields.rawValue: value] + } + return mutableCLP + } + + func addPointer(_ fields: Set, + on keyPath: WritableKeyPath) -> Self { + + if let currentSet = self[keyPath: keyPath]?[Access.pointerFields.rawValue]?.value as? Set { + var mutableCLP = self + mutableCLP[keyPath: keyPath]?[Access.pointerFields.rawValue] = AnyCodable(currentSet.union(fields)) + return mutableCLP + } else { + return setPointer(fields, on: keyPath) + } + } + + func removePointer(_ fields: Set, + on keyPath: WritableKeyPath) -> Self { + var mutableCLP = self + if var currentSet = self[keyPath: keyPath]?[Access.pointerFields.rawValue]?.value as? Set { + fields.forEach { currentSet.remove($0) } + mutableCLP[keyPath: keyPath]?[Access.pointerFields.rawValue] = AnyCodable(currentSet) + } + return mutableCLP + } +} + +// MARK: Standard Access +public extension ParseCLP { + /** + Checks if an `Action` on a Parse class has public access. + - parameter action: An enum value of one of the following actions: + get/find/count/create/update/delete/addField. + - returns: **true** if access is allowed, **false** otherwise. + */ + func hasAccessPublic(_ action: Action) -> Bool { + hasAccess(action.keyPath(), for: Access.publicScope.rawValue) + } + + /** + Checks if an `Action` on a Parse class requires users to be authenticated to access. + - parameter action: An enum value of one of the following actions: + get/find/count/create/update/delete/addField. + - returns: **true** if access is allowed, **false** otherwise. + - warning: Requires Parse Server 2.3.0+. + */ + func hasAccessRequiresAuthentication(_ action: Action) -> Bool { + hasAccess(action.keyPath(), for: Access.requiresAuthentication.rawValue) + } + + /** + Checks if an `Action` on a Parse class provides access to a specific `objectId`. + - parameter action: An enum value of one of the following actions: + get/find/count/create/update/delete/addField. + - parameter objectId: The `ParseUser` objectId to check. + - returns: **true** if access is allowed, **false** otherwise. + */ + func hasAccess(_ action: Action, + for objectId: String) -> Bool { + return hasAccess(action.keyPath(), for: objectId) + } + + /** + Checks if an `Action` on a Parse class provides access to a specific `ParseUser`. + - parameter action: An enum value of one of the following actions: + get/find/count/create/update/delete/addField. + - parameter user: The `ParseUser` to check. + - returns: **true** if access is allowed, **false** otherwise. + - throws: An error of type `ParseError`. + */ + func hasAccess(_ action: Action, + for user: U) throws -> Bool where U: ParseUser { + let objectId = try user.toPointer().objectId + return hasAccess(action.keyPath(), for: objectId) + } + + /** + Checks if an `Action` on a Parse class provides access to a specific `ParseUser` pointer. + - parameter action: An enum value of one of the following actions: + get/find/count/create/update/delete/addField. + - parameter user: The `ParseUser` pointer to check. + - returns: **true** if access is allowed, **false** otherwise. + */ + func hasAccess(_ action: Action, + for user: Pointer) -> Bool where U: ParseUser { + hasAccess(action.keyPath(), for: user.objectId) + } + + /** + Checks if an `Action` on a Parse class provides access to a specific `ParseRole`. + - parameter action: An enum value of one of the following actions: + get/find/count/create/update/delete/addField. + - parameter role: The `ParseRole` to check. + - returns: **true** if access is allowed, **false** otherwise. + - throws: An error of type `ParseError`. + */ + func hasAccess(_ action: Action, + for role: R) throws -> Bool where R: ParseRole { + let roleNameAccess = try ParseACL.getRoleAccessName(role) + return hasAccess(action.keyPath(), for: roleNameAccess) + } + + /** + Set/remove public access to an `Action` on a Parse class. + - parameter allow: **true** to allow access , **false** to remove access. + - parameter action: An enum value of one of the following actions: + get/find/count/create/update/delete/addField. + - returns: A mutated instance of `ParseCLP` for easy chaining. + */ + func setAccessPublic(_ allow: Bool, + on action: Action) -> Self { + setAccess(allow, on: action.writableKeyPath(), for: Access.publicScope.rawValue) + } + + /** + Set/remove require user authentication to access an `Action` on a Parse class. + - parameter allow: **true** to allow access , **false** to remove access. + - parameter action: An enum value of one of the following actions: + get/find/count/create/update/delete/addField. + - returns: A mutated instance of `ParseCLP` for easy chaining. + - warning: Requires Parse Server 2.3.0+. + */ + func setAccessRequiresAuthentication(_ allow: Bool, + on action: Action) -> Self { + setAccess(allow, on: action.writableKeyPath(), for: Access.requiresAuthentication.rawValue) + } + + /** + Set/remove access to an `Action` for a specific user objectId on a Parse class. + - parameter action: An enum value of one of the following actions: + get/find/count/create/update/delete/addField. + - parameter allow: **true** to allow access , **false** to remove access. + - parameter objectId: The `ParseUser` objectId to add/remove access to. + - returns: A mutated instance of `ParseCLP` for easy chaining. + */ + func setAccess(_ allow: Bool, + on action: Action, + for objectId: String) -> Self { + setAccess(allow, on: action.writableKeyPath(), for: objectId) + } + + /** + Set/remove access to an `Action` for a specific user `ParseUser` on a Parse class. + - parameter action: An enum value of one of the following actions: + get/find/count/create/update/delete/addField. + - parameter allow: **true** to allow access , **false** to remove access. + - parameter objectId: The `ParseUser` to add/remove access to. + - returns: A mutated instance of `ParseCLP` for easy chaining. + - throws: An error of type `ParseError`. + */ + func setAccess(_ allow: Bool, + on action: Action, + for user: U) throws -> Self where U: ParseUser { + let objectId = try user.toPointer().objectId + return setAccess(allow, on: action.writableKeyPath(), for: objectId) + } + + /** + Set/remove access to an `Action` for a specific user `ParseUser` pointer on a Parse class. + - parameter action: An enum value of one of the following actions: + get/find/count/create/update/delete/addField. + - parameter allow: **true** to allow access , **false** to remove access. + - parameter user: The `ParseUser` pointer to add/remove access to. + - returns: A mutated instance of `ParseCLP` for easy chaining. + */ + func setAccess(_ allow: Bool, + on action: Action, + for user: Pointer) -> Self where U: ParseUser { + setAccess(allow, on: action.writableKeyPath(), for: user.objectId) + } + + /** + Set/remove access to an `Action` for a specific `ParseRole` on a Parse class. + - parameter action: An enum value of one of the following actions: + get/find/count/create/update/delete/addField. + - parameter allow: **true** to allow access , **false** to remove access. + - parameter objectId: The `ParseRole` to add/remove access to. + - returns: A mutated instance of `ParseCLP` for easy chaining. + - throws: An error of type `ParseError`. + */ + func setAccess(_ allow: Bool, + on action: Action, + for role: R) throws -> Self where R: ParseRole { + let roleNameAccess = try ParseACL.getRoleAccessName(role) + return setAccess(allow, on: action.writableKeyPath(), for: roleNameAccess) + } +} + +// MARK: WriteAccess +public extension ParseCLP { + internal func hasWriteAccess(_ entity: String, + check addField: Bool) -> Bool { + let access = hasAccess(.create, for: entity) + && hasAccess(.update, for: entity) + && hasAccess(.delete, for: entity) + if addField { + return access && hasAccess(.addField, for: entity) + } + return access + } + + /** + Check whether user authentication is required to perform create/update/delete/addField actions on a Parse class. + - parameter checkAddField: **true** if `addField` should be part of the check, **false** otherwise. + Defaults to **false**. + - returns: **true** if has access, **false** otherwise. + - warning: Requires Parse Server 2.3.0+. + */ + func hasWriteAccessRequiresAuthentication(_ checkAddField: Bool = false) -> Bool { + hasWriteAccess(Access.requiresAuthentication.rawValue, check: checkAddField) + } + + /** + Check whether the public has access to perform create/update/delete/addField actions on a Parse class. + - parameter checkAddField: **true** if `addField` should be part of the check, **false** otherwise. + Defaults to **false**. + - returns: **true** if has access, **false** otherwise. + */ + func hasWriteAccessPublic(_ checkAddField: Bool = false) -> Bool { + hasWriteAccess(Access.publicScope.rawValue, check: checkAddField) + } + + /** + Check whether a `ParseUser` objectId has access to perform create/update/delete/addField actions on a Parse class. + - parameter objectId: The `ParseUser` objectId to check. + - parameter checkAddField: **true** if `addField` should be part of the check, **false** otherwise. + Defaults to **false**. + - returns: **true** if has access, **false** otherwise. + - warning: Even if **false** is returned, the `ParseUser`/`ParseRole` may still + have access if they are apart of a `ParseRole` that has access. + - throws: An error of type `ParseError`. + */ + func hasWriteAccess(_ objectId: String, + checkAddField: Bool = false) -> Bool { + hasWriteAccess(objectId, check: checkAddField) + } + + /** + Check whether the `ParseUser` pointer has access to perform create/update/delete/addField actions on a Parse class. + - parameter user: The `ParseUser` pointer to check. + - parameter checkAddField: **true** if `addField` should be part of the check, **false** otherwise. + Defaults to **false**. + - returns: **true** if has access, **false** otherwise. + - warning: Even if **false** is returned, the `ParseUser`/`ParseRole` may still + have access if they are apart of a `ParseRole` that has access. + */ + func hasWriteAccess(_ user: Pointer, + checkAddField: Bool = false) -> Bool where U: ParseUser { + hasWriteAccess(user.objectId, checkAddField: checkAddField) + } + + /** + Check whether the `ParseUser` has access to perform create/update/delete/addField actions on a Parse class. + - parameter user: The `ParseUser` to check. + - parameter checkAddField: **true** if `addField` should be part of the check, **false** otherwise. + Defaults to **false**. + - returns: **true** if has access, **false** otherwise. + - warning: Even if **false** is returned, the `ParseUser`/`ParseRole` may still + have access if they are apart of a `ParseRole` that has access. + - throws: An error of type `ParseError`. + */ + func hasWriteAccess(_ user: U, + checkAddField: Bool = false) throws -> Bool where U: ParseUser { + let objectId = try user.toPointer().objectId + return hasWriteAccess(objectId, checkAddField: checkAddField) + } + + /** + Check whether the `ParseRole` has access to perform create/update/delete/addField actions on a Parse class. + - parameter role: The `ParseRole` to check. + - parameter checkAddField: **true** if `addField` should be part of the check, **false** otherwise. + Defaults to **false**. + - returns: **true** if has access, **false** otherwise. + - warning: Even if **false** is returned, the `ParseUser`/`ParseRole` may still + have access if they are apart of a `ParseRole` that has access. + - throws: An error of type `ParseError`. + */ + func hasWriteAccess(_ role: R, + checkAddField: Bool = false) throws -> Bool where R: ParseRole { + let roleNameAccess = try ParseACL.getRoleAccessName(role) + return hasWriteAccess(roleNameAccess, checkAddField: checkAddField) + } + + /** + Sets whether user authentication is required to perform create/update/delete/addField actions on a Parse class. + - parameter allow: **true** if access should be allowed, **false** otherwise. + - parameter canAddField: **true** if access should be allowed to `addField`, + **false** otherwise. Defaults to **false**. + - returns: A mutated instance of `ParseCLP` for easy chaining. + - warning: Requires Parse Server 2.3.0+. + */ + func setWriteAccessRequiresAuthentication(_ allow: Bool, + canAddField addField: Bool = false) -> Self { + setWriteAccess(allow, for: Access.requiresAuthentication.rawValue, canAddField: addField) + } + + /** + Sets whether the public has access to perform create/update/delete/addField actions on a Parse class. + - parameter allow: **true** if access should be allowed, **false** otherwise. + - parameter canAddField: **true** if access should be allowed to `addField`, + **false** otherwise. Defaults to **false**. + - returns: A mutated instance of `ParseCLP` for easy chaining. + */ + func setWriteAccessPublic(_ allow: Bool, + canAddField addField: Bool = false) -> Self { + setWriteAccess(allow, for: Access.publicScope.rawValue, canAddField: addField) + } + + /** + Sets whether the given `ParseUser` objectId has access to perform + create/update/delete/addField actions on a Parse class. + - parameter objectId: The `ParseUser` objectId to provide/restrict access to. + - parameter to: **true** if access should be allowed, **false** otherwise. + - parameter canAddField: **true** if access should be allowed to `addField`, + **false** otherwise. Defaults to **false**. + - returns: A mutated instance of `ParseCLP` for easy chaining. + */ + func setWriteAccess(_ allow: Bool, + for objectId: String, + canAddField addField: Bool = false) -> Self { + var updatedCLP = self + .setAccess(allow, on: .create, for: objectId) + .setAccess(allow, on: .update, for: objectId) + .setAccess(allow, on: .delete, for: objectId) + if addField { + updatedCLP = updatedCLP.setAccess(allow, on: .addField, for: objectId) + } else { + updatedCLP = updatedCLP.setAccess(false, on: .addField, for: objectId) + } + return updatedCLP + } + + /** + Sets whether the given `ParseUser` has access to perform create/update/delete/addField actions on a Parse class. + - parameter user: The `ParseUser` to provide/restrict access to. + - parameter to: **true** if access should be allowed, **false** otherwise. + - parameter canAddField: **true** if access should be allowed to `addField`, + **false** otherwise. Defaults to **false**. + - returns: A mutated instance of `ParseCLP` for easy chaining. + - throws: An error of type `ParseError`. + */ + func setWriteAccess(_ allow: Bool, + for user: U, + canAddField addField: Bool = false) throws -> Self where U: ParseUser { + let objectId = try user.toPointer().objectId + return setWriteAccess(allow, for: objectId, canAddField: addField) + } + + /** + Sets whether the given `ParseUser`pointer has access to perform + create/update/delete/addField actions on a Parse class. + - parameter user: The `ParseUser` to provide/restrict access to. + - parameter to: **true** if access should be allowed, **false** otherwise. + - parameter canAddField: **true** if access should be allowed to `addField`, + **false** otherwise. Defaults to **false**. + - returns: A mutated instance of `ParseCLP` for easy chaining. + */ + func setWriteAccess(_ allow: Bool, + for user: Pointer, + canAddField addField: Bool = false) -> Self where U: ParseUser { + setWriteAccess(allow, for: user.objectId, canAddField: addField) + } + + /** + Sets whether the given `ParseRole` has access to perform create/update/delete/addField actions on a Parse class. + - parameter role: The `ParseRole` to provide/restrict access to. + - parameter to: **true** if access should be allowed, **false** otherwise. + - parameter canAddField: **true** if access should be allowed to `addField`, + **false** otherwise. Defaults to **false**. + - returns: A mutated instance of `ParseCLP` for easy chaining. + - throws: An error of type `ParseError`. + */ + func setWriteAccess(_ allow: Bool, + for role: R, + canAddField addField: Bool = false) throws -> Self where R: ParseRole { + let roleNameAccess = try ParseACL.getRoleAccessName(role) + return setWriteAccess(allow, for: roleNameAccess, canAddField: addField) + } +} + +// MARK: ReadAccess +public extension ParseCLP { + /** + Check whether user authentication is required to perform get/find/count actions on a Parse class. + - returns: **true** if has access, **false** otherwise. + - warning: Requires Parse Server 2.3.0+. + */ + func hasReadAccessRequiresAuthentication() -> Bool { + hasReadAccess(Access.requiresAuthentication.rawValue) + } + + /** + Check whether the public has access to perform get/find/count actions on a Parse class. + - returns: **true** if has access, **false** otherwise. + */ + func hasReadAccessPublic() -> Bool { + hasReadAccess(Access.publicScope.rawValue) + } + + /** + Check whether the `ParseUser` objectId has access to perform get/find/count actions on a Parse class. + - parameter objectId: The `ParseUser` objectId to check. + - returns: **true** if has access, **false** otherwise. + - warning: Even if **false** is returned, the `ParseUser`/`ParseRole` may still + have access if they are apart of a `ParseRole` that has access. + */ + func hasReadAccess(_ objectId: String) -> Bool { + hasAccess(.get, for: objectId) + && hasAccess(.find, for: objectId) + && hasAccess(.count, for: objectId) + } + + /** + Check whether the `ParseUser` pointer has access to perform get/find/count actions on a Parse class. + - parameter user: The `ParseUser` pointer to check. + - returns: **true** if has access, **false** otherwise. + - warning: Even if **false** is returned, the `ParseUser`/`ParseRole` may still + have access if they are apart of a `ParseRole` that has access. + */ + func hasReadAccess(_ user: Pointer) -> Bool where U: ParseUser { + hasReadAccess(user.objectId) + } + + /** + Check whether the `ParseUser` has access to perform get/find/count actions on a Parse class. + - parameter user: The `ParseUser` to check. + - returns: **true** if has access, **false** otherwise. + - throws: An error of type `ParseError`. + - warning: Even if **false** is returned, the `ParseUser`/`ParseRole` may still + have access if they are apart of a `ParseRole` that has access. + */ + func hasReadAccess(_ user: U) throws -> Bool where U: ParseUser { + let objectId = try user.toPointer().objectId + return hasReadAccess(objectId) + } + + /** + Check whether the `ParseRole` has access to perform get/find/count actions on a Parse class. + - parameter role: The `ParseRole` to check. + - returns: **true** if has access, **false** otherwise. + - throws: An error of type `ParseError`. + - warning: Even if **false** is returned, the `ParseUser`/`ParseRole` may still + have access if they are apart of a `ParseRole` that has access. + */ + func hasReadAccess(_ role: R) throws -> Bool where R: ParseRole { + let roleNameAccess = try ParseACL.getRoleAccessName(role) + return hasReadAccess(roleNameAccess) + } + + /** + Sets whether authentication is required to perform get/find/count actions on a Parse class. + - parameter allow: **true** if access should be allowed, **false** otherwise. + - parameter canAddField: **true** if access should be allowed to `addField`, + **false** otherwise. Defaults to **false**. + - returns: A mutated instance of `ParseCLP` for easy chaining. + - warning: Requires Parse Server 2.3.0+. + */ + func setReadAccessRequiresAuthentication(_ allow: Bool, + canAddField addField: Bool = false) -> Self { + setReadAccess(allow, for: Access.requiresAuthentication.rawValue) + } + + /** + Sets whether the public has access to perform get/find/count actions on a Parse class. + - parameter allow: **true** if access should be allowed, **false** otherwise. + - returns: A mutated instance of `ParseCLP` for easy chaining. + */ + func setReadAccessPublic(_ allow: Bool) -> Self { + setReadAccess(allow, for: Access.publicScope.rawValue) + } + + /** + Sets whether the given `ParseUser` has access to perform get/find/count actions on a Parse class. + - parameter allow: **true** if access should be allowed, **false** otherwise. + - parameter objectId: The `ParseUser` pointer to provide/restrict access to. + - returns: A mutated instance of `ParseCLP` for easy chaining. + */ + func setReadAccess(_ allow: Bool, + for objectId: String) -> Self { + let updatedCLP = self + .setAccess(allow, on: .get, for: objectId) + .setAccess(allow, on: .find, for: objectId) + .setAccess(allow, on: .count, for: objectId) + return updatedCLP + } + + /** + Sets whether the given `ParseUser` has access to perform get/find/count actions on a Parse class. + - parameter allow: **true** if access should be allowed, **false** otherwise. + - parameter user: The `ParseUser` to provide/restrict access to. + - returns: A mutated instance of `ParseCLP` for easy chaining. + - throws: An error of type `ParseError`. + */ + func setReadAccess(_ allow: Bool, + for user: U) throws -> Self where U: ParseUser { + let objectId = try user.toPointer().objectId + return setReadAccess(allow, for: objectId) + } + + /** + Sets whether the given `ParseUser` has access to perform get/find/count actions on a Parse class. + - parameter allow: **true** if access should be allowed, **false** otherwise. + - parameter user: The `ParseUser` pointer to provide/restrict access to. + - returns: A mutated instance of `ParseCLP` for easy chaining. + */ + func setReadAccess(_ allow: Bool, + for user: Pointer) -> Self where U: ParseUser { + return setReadAccess(allow, for: user.objectId) + } + + /** + Sets whether the given `ParseRole` has access to perform get/find/count actions on a Parse class. + - parameter allow: **true** if access should be allowed, **false** otherwise. + - parameter role: The `ParseRole` to provide/restrict access to. + - returns: A mutated instance of `ParseCLP` for easy chaining. + - throws: An error of type `ParseError`. + */ + func setReadAccess(_ allow: Bool, + for role: R) throws -> Self where R: ParseRole { + let roleNameAccess = try ParseACL.getRoleAccessName(role) + return setReadAccess(allow, for: roleNameAccess) + } +} + +// MARK: Pointer Access +public extension ParseCLP { + /** + Retreive the fields in a Parse class that determine access for a specific `Action`. + - parameter action: An enum value of one of the following actions: + get/find/count/create/update/delete/addField. + - returns: The set of fields that are either of`ParseUser` type or + an array of `ParseUser`'s. + */ + func getPointerFields(_ action: Action) -> Set { + getPointerFields(action.keyPath()) + } + + /** + Give access to a set of fields that are either of `ParseUser` type or an array + `ParseUser`'s for a specific `Action` on a Parse class. + - parameter action: An enum value of one of the following actions: + get/find/count/create/update/delete/addField. + - parameter fields: The set of fields that are either of`ParseUser` type or + an array of `ParseUser`'s. + - returns: A mutated instance of `ParseCLP` for easy chaining. + - note: This method replaces the current set of `fields` in the CLP. + - warning: Requires Parse Server 3.1.1+. + */ + func setPointerFields(_ fields: Set, + on action: Action) -> Self { + setPointer(fields, on: action.writableKeyPath()) + } + + /** + Add access to an additional set of fields that are either of `ParseUser` type or an array + `ParseUser`'s for a specific `Action` on a Parse class. + - parameter action: An enum value of one of the following actions: + get/find/count/create/update/delete/addField. + - parameter fields: The set of fields that are either of`ParseUser` type or + an array of `ParseUser`'s. + - returns: A mutated instance of `ParseCLP` for easy chaining. + - note: This method adds on to the current set of `fields` in the CLP. + - warning: Requires Parse Server 3.1.1+. + */ + func addPointerFields(_ fields: Set, + on action: Action) -> Self { + addPointer(fields, on: action.writableKeyPath()) + } + + /** + Remove access for the set of fields that are either of `ParseUser` type or an array + `ParseUser`'s for a specific `Action` on a Parse class. + - parameter action: An enum value of one of the following actions: + get/find/count/create/update/delete/addField. + - parameter fields: The set of fields that are either of`ParseUser` type or + an array of `ParseUser`'s. + - returns: A mutated instance of `ParseCLP` for easy chaining. + - note: This method removes from the current set of `fields` in the CLP. + - warning: Requires Parse Server 3.1.1+. + */ + func removePointerFields(_ fields: Set, + on action: Action) -> Self { + removePointer(fields, on: action.writableKeyPath()) + } +} + +// MARK: Protected Fields +public extension ParseCLP { + internal func getProtected(_ keyPath: KeyPath]?>, + for entity: String) -> Set { + self[keyPath: keyPath]?[entity] ?? [] + } + + internal func setProtected(_ fields: Set, + on keyPath: WritableKeyPath]?>, + for entity: String) -> Self { + var mutableCLP = self + if mutableCLP[keyPath: keyPath] != nil { + mutableCLP[keyPath: keyPath]?[entity] = fields + } else { + mutableCLP[keyPath: keyPath] = [entity: fields] + } + return mutableCLP + } + + internal func addProtected(_ fields: Set, + on keyPath: WritableKeyPath]?>, + for entity: String) -> Self { + if let currentSet = self[keyPath: keyPath]?[entity] { + var mutableCLP = self + mutableCLP[keyPath: keyPath]?[entity] = currentSet.union(fields) + return mutableCLP + } else { + return setProtected(fields, on: keyPath, for: entity) + } + } + + internal func removeProtected(_ fields: Set, + on keyPath: WritableKeyPath]?>, + for entity: String) -> Self { + var mutableCLP = self + fields.forEach { + mutableCLP[keyPath: keyPath]?[entity]?.remove($0) + } + return mutableCLP + } + + /** + Get the fields the publc cannot access. + - returns: The set protected fields that cannot be accessed. + */ + func getProtectedFieldsPublic() -> Set { + getProtectedFields(Access.publicScope.rawValue) + } + + /** + Get the fields the users with authentication cannot access. + - returns: The set protected fields that cannot be accessed. + - warning: Requires Parse Server 2.3.0+. + */ + func getProtectedFieldsRequiresAuthentication() -> Set { + getProtectedFields(Access.requiresAuthentication.rawValue) + } + + /** + Get the protected fields either a field of `ParseUser` type or + an array of `ParseUser`'s in a Parse class cannot access. + - parameter field: A field in a Parse class that is either of `ParseUser` type or + an array of `ParseUser`'s. + - returns: The set protected fields that cannot be accessed. + */ + func getProtectedFieldsUser(_ field: String) -> Set { + getProtectedFields(Self.getUserFieldAccess(field)) + } + + /** + Get the protected fields the given `ParseUser` objectId cannot access. + - parameter objectId: The `ParseUser` objectId access to check. + - returns: The set protected fields that cannot be accessed. + */ + func getProtectedFields(_ objectId: String) -> Set { + getProtected(\.protectedFields, for: objectId) + } + + /** + Get the protected fields the given `ParseUser` cannot access. + - parameter user: The `ParseUser` access to check. + - returns: The set protected fields that cannot be accessed. + - throws: An error of type `ParseError`. + */ + func getProtectedFields(_ user: U) throws -> Set where U: ParseUser { + let objectId = try user.toPointer().objectId + return getProtectedFields(objectId) + } + + /** + Get the protected fields for the given `ParseUser` pointer cannot access. + - parameter user: The `ParseUser` access to check. + - returns: The set protected fields that cannot be accessed. + */ + func getProtectedFields(_ user: Pointer) -> Set where U: ParseUser { + getProtectedFields(user.objectId) + } + + /** + Get the protected fields the given `ParseRole` cannot access. + - parameter role: The `ParseRole` access to check. + - returns: The set protected fields that cannot be accessed. + - throws: An error of type `ParseError`. + */ + func getProtectedFields(_ role: R) throws -> Set where R: ParseRole { + let roleNameAccess = try ParseACL.getRoleAccessName(role) + return getProtectedFields(roleNameAccess) + } + + /** + Set whether the public should not have access to specific fields of a Parse class. + - parameter fields: The set of fields that should be protected from access. + - returns: A mutated instance of `ParseCLP` for easy chaining. + - throws: An error of type `ParseError`. + */ + func setProtectedFieldsPublic(_ fields: Set) -> Self { + setProtected(fields, on: \.protectedFields, for: Access.publicScope.rawValue) + } + + /** + Set whether authenticated users should not have access to specific fields of a Parse class. + - parameter fields: The set of fields that should be protected from access. + - returns: A mutated instance of `ParseCLP` for easy chaining. + - throws: An error of type `ParseError`. + - warning: Requires Parse Server 2.3.0+. + */ + func setProtectedFieldsRequiresAuthentication(_ fields: Set) -> Self { + setProtected(fields, on: \.protectedFields, for: Access.requiresAuthentication.rawValue) + } + + /** + Set whether the given field that is either of `ParseUser` type or an array of `ParseUser`'s + should not have access to specific fields of a Parse class. + - parameter fields: The set of fields that should be protected from access. + - parameter userField: A field in a Parse class that is either of `ParseUser` type or + an array of `ParseUser`'s to restrict access to. + - returns: A mutated instance of `ParseCLP` for easy chaining. + - throws: An error of type `ParseError`. + */ + func setProtectedFields(_ fields: Set, userField: String) -> Self { + setProtected(fields, + on: \.protectedFields, + for: Self.getUserFieldAccess(userField)) + } + + /** + Set whether the given `ParseUser` objectId should not have access to specific fields of a Parse class. + - parameter fields: The set of fields that should be protected from access. + - parameter objectId: The `ParseUser` objectId to restrict access to. + - returns: A mutated instance of `ParseCLP` for easy chaining. + - throws: An error of type `ParseError`. + */ + func setProtectedFields(_ fields: Set, for objectId: String) -> Self { + setProtected(fields, on: \.protectedFields, for: objectId) + } + + /** + Set whether the given `ParseUser` should not have access to specific fields of a Parse class. + - parameter fields: The set of fields that should be protected from access. + - parameter user: The `ParseUser` to restrict access to. + - returns: A mutated instance of `ParseCLP` for easy chaining. + - throws: An error of type `ParseError`. + */ + func setProtectedFields(_ fields: Set, for user: U) throws -> Self where U: ParseUser { + let objectId = try user.toPointer().objectId + return setProtectedFields(fields, for: objectId) + } + + /** + Set whether the given `ParseUser` should not have access to specific fields of a Parse class. + - parameter fields: The set of fields that should be protected from access. + - parameter user: The `ParseUser` to restrict access to. + - returns: A mutated instance of `ParseCLP` for easy chaining. + */ + func setProtectedFields(_ fields: Set, for user: Pointer) -> Self where U: ParseUser { + setProtectedFields(fields, for: user.objectId) + } + + /** + Set whether the given `ParseRole` should not have access to specific fields of a Parse class. + - parameter fields: The set of fields that should be protected from access. + - parameter role: The `ParseRole` to restrict access to. + - returns: A mutated instance of `ParseCLP` for easy chaining. + - throws: An error of type `ParseError`. + */ + func setProtectedFields(_ fields: Set, for role: R) throws -> Self where R: ParseRole { + let roleNameAccess = try ParseACL.getRoleAccessName(role) + return setProtectedFields(fields, for: roleNameAccess) + } + + /** + Add to the set of specific fields the public should not have access to on a Parse class. + - parameter fields: The set of fields that should be protected from access. + - returns: A mutated instance of `ParseCLP` for easy chaining. + - throws: An error of type `ParseError`. + - note: This method adds on to the current set of `fields` in the CLP. + */ + func addProtectedFieldsPublic(_ fields: Set) -> Self { + addProtected(fields, on: \.protectedFields, for: Access.publicScope.rawValue) + } + + /** + Add to the set of specific fields authenticated users should not have access to on a Parse class. + - parameter fields: The set of fields that should be protected from access. + - returns: A mutated instance of `ParseCLP` for easy chaining. + - throws: An error of type `ParseError`. + - note: This method adds on to the current set of `fields` in the CLP. + - warning: Requires Parse Server 2.3.0+. + */ + func addProtectedFieldsRequiresAuthentication(_ fields: Set) -> Self { + addProtected(fields, on: \.protectedFields, for: Access.requiresAuthentication.rawValue) + } + + /** + Add to the set of specific fields the given field that is either of `ParseUser` type or an array of `ParseUser`'s + should not have access to on a Parse class. + - parameter fields: The set of fields that should be protected from access. + - parameter userField: A field in a Parse class that is either of `ParseUser` type or + an array of `ParseUser`'s to restrict access to. + - returns: A mutated instance of `ParseCLP` for easy chaining. + - throws: An error of type `ParseError`. + - note: This method adds on to the current set of `fields` in the CLP. + */ + func addProtectedFieldsUser(_ fields: Set, userField: String) -> Self { + addProtected(fields, on: \.protectedFields, for: Self.getUserFieldAccess(userField)) + } + + /** + Add to the set of specific fields the given `ParseUser` objectId should not have access to on a Parse class. + - parameter fields: The set of fields that should be protected from access. + - parameter objectId: The `ParseUser` objectId to restrict access to. + - returns: A mutated instance of `ParseCLP` for easy chaining. + - throws: An error of type `ParseError`. + - note: This method adds on to the current set of `fields` in the CLP. + */ + func addProtectedFields(_ fields: Set, for objectId: String) -> Self { + addProtected(fields, on: \.protectedFields, for: objectId) + } + + /** + Add to the set of specific fields the given `ParseUser` should not have access to on a Parse class. + - parameter fields: The set of fields that should be protected from access. + - parameter user: The `ParseUser` to restrict access to. + - returns: A mutated instance of `ParseCLP` for easy chaining. + - throws: An error of type `ParseError`. + - note: This method adds on to the current set of `fields` in the CLP. + */ + func addProtectedFields(_ fields: Set, for user: U) throws -> Self where U: ParseUser { + let objectId = try user.toPointer().objectId + return addProtectedFields(fields, for: objectId) + } + + /** + Add to the set of specific fields the given `ParseUser` pointer should not have access to on a Parse class. + - parameter fields: The set of fields that should be protected from access. + - parameter user: The `ParseUser` to restrict access to. + - returns: A mutated instance of `ParseCLP` for easy chaining. + - note: This method adds on to the current set of `fields` in the CLP. + */ + func addProtectedFields(_ fields: Set, for user: Pointer) -> Self where U: ParseUser { + addProtectedFields(fields, for: user.objectId) + } + + /** + Add to the set of specific fields the given `ParseRole` should not have access to on a Parse class. + - parameter fields: The set of fields that should be protected from access. + - parameter role: The `ParseRole` to restrict access to. + - returns: A mutated instance of `ParseCLP` for easy chaining. + - throws: An error of type `ParseError`. + - note: This method adds on to the current set of `fields` in the CLP. + */ + func addProtectedFields(_ fields: Set, for role: R) throws -> Self where R: ParseRole { + let roleNameAccess = try ParseACL.getRoleAccessName(role) + return addProtectedFields(fields, for: roleNameAccess) + } + + /** + Remove the set of specific fields the public should not have access to on a Parse class. + - parameter fields: The set of fields that should be removed from protected access. + - parameter objectId: The `ParseUser` objectId to restrict access to. + - returns: A mutated instance of `ParseCLP` for easy chaining. + - throws: An error of type `ParseError`. + - note: This method removes from the current set of `fields` in the CLP. + */ + func removeProtectedFieldsPublic(_ fields: Set) -> Self { + removeProtected(fields, on: \.protectedFields, for: Access.publicScope.rawValue) + } + + /** + Remove the set of specific fields authenticated users should not have access to + on a Parse class. + - parameter fields: The set of fields that should be removed from protected access. + - returns: A mutated instance of `ParseCLP` for easy chaining. + - throws: An error of type `ParseError`. + - note: This method removes from the current set of `fields` in the CLP. + */ + func removeProtectedFieldsRequiresAuthentication(_ fields: Set) -> Self { + removeProtected(fields, on: \.protectedFields, for: Access.requiresAuthentication.rawValue) + } + + /** + Remove fields from the set of specific fields the given field that is either of `ParseUser` type + or an array of `ParseUser`'s should not have access to on a Parse class. + - parameter fields: The set of fields that should be removed from protected access. + - parameter userField: A field in a Parse class that is either of `ParseUser` type or + an array of `ParseUser`'s to restrict access to. + - returns: A mutated instance of `ParseCLP` for easy chaining. + - throws: An error of type `ParseError`. + - note: This method removes from the current set of `fields` in the CLP. + */ + func removeProtectedFieldsUser(_ fields: Set, userField: String) -> Self { + removeProtected(fields, on: \.protectedFields, for: Self.getUserFieldAccess(userField)) + } + + /** + Remove fields from the set of specific fields the given `ParseUser` objectId + should not have access to on a Parse class. + - parameter fields: The set of fields that should be removed from protected access. + - parameter objectId: The `ParseUser` objectId to restrict access to. + - returns: A mutated instance of `ParseCLP` for easy chaining. + - throws: An error of type `ParseError`. + - note: This method removes from the current set of `fields` in the CLP. + */ + func removeProtectedFields(_ fields: Set, for objectId: String) -> Self { + removeProtected(fields, on: \.protectedFields, for: objectId) + } + + /** + Remove fields from the set of specific fields the given `ParseUser` should not have access to on a Parse class. + - parameter fields: The set of fields that should be removed from protected access. + - parameter user: The `ParseUser` to restrict access to. + - returns: A mutated instance of `ParseCLP` for easy chaining. + - throws: An error of type `ParseError`. + - note: This method removes from the current set of `fields` in the CLP. + */ + func removeProtectedFields(_ fields: Set, for user: U) throws -> Self where U: ParseUser { + let objectId = try user.toPointer().objectId + return removeProtectedFields(fields, for: objectId) + } + + /** + Remove fields from the set of specific fields the given `ParseUser` pointer + should not have access to on a Parse class. + - parameter fields: The set of fields that should be removed from protected access. + - parameter user: The `ParseUser` to restrict access to. + - returns: A mutated instance of `ParseCLP` for easy chaining. + - note: This method removes from the current set of `fields` in the CLP. + */ + func removeProtectedFields(_ fields: Set, for user: Pointer) -> Self where U: ParseUser { + removeProtectedFields(fields, for: user.objectId) + } + + /** + Remove fields from the set of specific fields the given `ParseRole` should not have access to on a Parse class. + - parameter fields: The set of fields that should be removed from protected access. + - parameter role: The `ParseRole` to restrict access to. + - returns: A mutated instance of `ParseCLP` for easy chaining. + - throws: An error of type `ParseError`. + - note: This method removes from the current set of `fields` in the CLP. + */ + func removeProtectedFields(_ fields: Set, for role: R) throws -> Self where R: ParseRole { + let roleNameAccess = try ParseACL.getRoleAccessName(role) + return removeProtectedFields(fields, for: roleNameAccess) + } +} + +// MARK: CustomDebugStringConvertible +extension ParseCLP: CustomDebugStringConvertible { + public var debugDescription: String { + guard let descriptionData = try? ParseCoding.jsonEncoder().encode(self), + let descriptionString = String(data: descriptionData, encoding: .utf8) else { + return "ParseCLP ()" + } + return "ParseCLP (\(descriptionString))" + } +} + +// MARK: CustomStringConvertible +extension ParseCLP: CustomStringConvertible { + public var description: String { + debugDescription + } +} diff --git a/Sources/ParseSwift/Types/ParseConfig+async.swift b/Sources/ParseSwift/Types/ParseConfig+async.swift index 4b35fbae2..a2db0e8df 100644 --- a/Sources/ParseSwift/Types/ParseConfig+async.swift +++ b/Sources/ParseSwift/Types/ParseConfig+async.swift @@ -33,7 +33,7 @@ public extension ParseConfig { /** Update the Config *asynchronously*. - parameter options: A set of header options sent to the server. Defaults to an empty set. - - returns: `true` if saved, `false` if save is unsuccessful. + - returns: **true** if saved, **false** if save is unsuccessful. - throws: An error of type `ParseError`. */ func save(options: API.Options = []) async throws -> Bool { diff --git a/Sources/ParseSwift/Types/ParseConfig+combine.swift b/Sources/ParseSwift/Types/ParseConfig+combine.swift index 347b03f6b..50e85a793 100644 --- a/Sources/ParseSwift/Types/ParseConfig+combine.swift +++ b/Sources/ParseSwift/Types/ParseConfig+combine.swift @@ -29,7 +29,7 @@ public extension ParseConfig { } /** - Update the Config *asynchronously*. + Update the Config *asynchronously*. Publishes when complete. - parameter options: A set of header options sent to the server. Defaults to an empty set. - returns: A publisher that eventually produces a single value and then finishes or fails. */ diff --git a/Sources/ParseSwift/Types/ParseConfig.swift b/Sources/ParseSwift/Types/ParseConfig.swift index 355b95ec8..2cc48e4c4 100644 --- a/Sources/ParseSwift/Types/ParseConfig.swift +++ b/Sources/ParseSwift/Types/ParseConfig.swift @@ -72,7 +72,7 @@ extension ParseConfig { /** Update the Config *synchronously*. - parameter options: A set of header options sent to the server. Defaults to an empty set. - - returns: Returns `true` if updated, `false` otherwise. + - returns: Returns **true** if updated, **false** otherwise. - note: The default cache policy for this method is `.reloadIgnoringLocalCacheData`. If a developer desires a different policy, it should be inserted in `options`. */ diff --git a/Sources/ParseSwift/Types/ParseError.swift b/Sources/ParseSwift/Types/ParseError.swift index 9895c934f..7b6fc8fbe 100644 --- a/Sources/ParseSwift/Types/ParseError.swift +++ b/Sources/ParseSwift/Types/ParseError.swift @@ -11,7 +11,7 @@ import Foundation /** An object with a Parse code and message. */ -public struct ParseError: ParseType, Decodable, Swift.Error { +public struct ParseError: ParseType, Equatable, Decodable, Swift.Error { /// The value representing the error from the Parse Server. public let code: Code /// The text representing the error from the Parse Server. diff --git a/Sources/ParseSwift/Types/ParseField.swift b/Sources/ParseSwift/Types/ParseField.swift new file mode 100644 index 000000000..a0b2c6297 --- /dev/null +++ b/Sources/ParseSwift/Types/ParseField.swift @@ -0,0 +1,88 @@ +// +// ParseField.swift +// ParseSwift +// +// Created by Corey Baker on 5/21/22. +// Copyright © 2022 Parse Community. All rights reserved. +// + +import Foundation + +public struct ParseField: Codable, Equatable { + var __op: Operation? // swiftlint:disable:this identifier_name + var type: FieldType? + var required: Bool? + var defaultValue: AnyCodable? + var targetClass: String? + + /// Field types available in `ParseSchema`. + public enum FieldType: String, Codable { + /// A string type. + case string = "String" + /// A number type. + case number = "Number" + /// A boolean type. + case boolean = "Boolean" + /// A date type. + case date = "Date" + /// A file type. + case file = "File" + /// A geoPoint type. + case geoPoint = "GeoPoint" + /// A polygon type. + case polygon = "Polygon" + /// An array type. + case array = "Array" + /// An object type. + case object = "Object" + /// A pointer type. + case pointer = "Pointer" + /// A relation type. + case relation = "Relation" + /// A bytes type. + case bytes = "Bytes" + /// A acl type. + case acl = "ACL" + } + + init(operation: Operation) { + __op = operation + } + + init(type: FieldType, options: ParseFieldOptions) where V: Codable { + self.type = type + self.required = options.required + if let defaultValue = options.defaultValue { + self.defaultValue = AnyCodable(defaultValue) + } + } + + init(type: FieldType, + options: ParseFieldOptions) where T: ParseObject { + self.type = type + self.targetClass = T.className + self.required = options.required + if let defaultValue = options.defaultValue { + self.defaultValue = AnyCodable(defaultValue) + } + } +} + +// MARK: CustomDebugStringConvertible +extension ParseField { + public var debugDescription: String { + guard let descriptionData = try? ParseCoding.jsonEncoder().encode(self), + let descriptionString = String(data: descriptionData, encoding: .utf8) else { + return "ParseField ()" + } + + return "ParseField (\(descriptionString))" + } +} + +// MARK: CustomStringConvertible +extension ParseField { + public var description: String { + debugDescription + } +} diff --git a/Sources/ParseSwift/Types/ParseFieldOptions.swift b/Sources/ParseSwift/Types/ParseFieldOptions.swift new file mode 100644 index 000000000..ce736d875 --- /dev/null +++ b/Sources/ParseSwift/Types/ParseFieldOptions.swift @@ -0,0 +1,60 @@ +// +// ParseFieldOptions.swift +// ParseSwift +// +// Created by Corey Baker on 5/29/22. +// Copyright © 2022 Parse Community. All rights reserved. +// + +import Foundation + +/// The options for a field in `ParseSchema` +public struct ParseFieldOptions: Codable { + /// Specifies if a field is required. + public var required: Bool = false + + /// The default value for a field. + public var defaultValue: V? + + /** + Create new options for a `ParseSchema` field. + - parameter required: Specify if the field is required. Defaults to **false**. + - parameter defauleValue: The default value for the field. Defaults to **nil**. + */ + public init(required: Bool = false, defauleValue: V? = nil) { + self.required = required + self.defaultValue = defauleValue + } +} + +extension ParseFieldOptions where V: ParseObject { + /** + Create new options for a `ParseSchema` field. + - parameter required: Specify if the field is required. Defaults to **false**. + - parameter defauleValue: The default value for the field. Defaults to **nil**. + - throws: An error of `ParseError` type. + */ + public init(required: Bool = false, defauleValue: V? = nil) throws { + self.required = required + self.defaultValue = try defauleValue?.toPointer().toObject() + } +} + +// MARK: CustomDebugStringConvertible +extension ParseFieldOptions { + public var debugDescription: String { + guard let descriptionData = try? ParseCoding.jsonEncoder().encode(self), + let descriptionString = String(data: descriptionData, encoding: .utf8) else { + return "ParseFieldOptions ()" + } + + return "ParseFieldOptions (\(descriptionString))" + } +} + +// MARK: CustomStringConvertible +extension ParseFieldOptions { + public var description: String { + debugDescription + } +} diff --git a/Sources/ParseSwift/Types/ParseFile+async.swift b/Sources/ParseSwift/Types/ParseFile+async.swift index 1092d8e23..5f03ee5df 100644 --- a/Sources/ParseSwift/Types/ParseFile+async.swift +++ b/Sources/ParseSwift/Types/ParseFile+async.swift @@ -91,7 +91,9 @@ public extension ParseFile { /** Deletes the file from the Parse Server. - - requires: `.useMasterKey` has to be available. + - requires: `.useMasterKey` has to be available. It is recommended to only + use the master key in server-side applications where the key is kept secure and not + exposed to the public. - parameter options: A set of header options sent to the server. Defaults to an empty set. - throws: An error of type `ParseError`. */ diff --git a/Sources/ParseSwift/Types/ParseFile+combine.swift b/Sources/ParseSwift/Types/ParseFile+combine.swift index 4b6b33496..dc996786a 100644 --- a/Sources/ParseSwift/Types/ParseFile+combine.swift +++ b/Sources/ParseSwift/Types/ParseFile+combine.swift @@ -86,7 +86,9 @@ public extension ParseFile { /** Deletes the file from the Parse Server. Publishes when complete. - - requires: `.useMasterKey` has to be available. + - requires: `.useMasterKey` has to be available. It is recommended to only + use the master key in server-side applications where the key is kept secure and not + exposed to the public. - parameter options: A set of header options sent to the server. Defaults to an empty set. - returns: A publisher that eventually produces a single value and then finishes or fails. */ diff --git a/Sources/ParseSwift/Types/ParseFile.swift b/Sources/ParseSwift/Types/ParseFile.swift index a3d76abb6..fab0a4e56 100644 --- a/Sources/ParseSwift/Types/ParseFile.swift +++ b/Sources/ParseSwift/Types/ParseFile.swift @@ -158,7 +158,9 @@ extension ParseFile { extension ParseFile { /** Deletes the file from the Parse cloud. - - requires: `.useMasterKey` has to be available. + - requires: `.useMasterKey` has to be available. It is recommended to only + use the master key in server-side applications where the key is kept secure and not + exposed to the public. - parameter options: A set of header options sent to the server. Defaults to an empty set. - parameter callbackQueue: The queue to return to after synchronous completion. - throws: A `ParseError` if there was an issue deleting the file. Otherwise it was successful. @@ -177,7 +179,9 @@ extension ParseFile { /** Deletes the file from the Parse cloud. - - requires: `.useMasterKey` has to be available. + - requires: `.useMasterKey` has to be available. It is recommended to only + use the master key in server-side applications where the key is kept secure and not + exposed to the public. - parameter options: A set of header options sent to the server. Defaults to an empty set. - throws: A `ParseError` if there was an issue deleting the file. Otherwise it was successful. */ diff --git a/Sources/ParseSwift/Types/ParseHealth+combine.swift b/Sources/ParseSwift/Types/ParseHealth+combine.swift index 660ff4603..e4873fbab 100644 --- a/Sources/ParseSwift/Types/ParseHealth+combine.swift +++ b/Sources/ParseSwift/Types/ParseHealth+combine.swift @@ -15,7 +15,7 @@ public extension ParseHealth { // MARK: Combine /** - Calls the health check function *asynchronously*. + Calls the health check function *asynchronously*. Publishes when complete. - parameter options: A set of header options sent to the server. Defaults to an empty set. - returns: A publisher that eventually produces a single value and then finishes or fails. */ diff --git a/Sources/ParseSwift/Types/ParseOperation+combine.swift b/Sources/ParseSwift/Types/ParseOperation+combine.swift index 988b68a57..c9274faeb 100644 --- a/Sources/ParseSwift/Types/ParseOperation+combine.swift +++ b/Sources/ParseSwift/Types/ParseOperation+combine.swift @@ -15,7 +15,7 @@ public extension ParseOperation { // MARK: Combine /** - Saves the operations on the `ParseObject` *asynchronously* and executes the given callback block. + Saves the operations on the `ParseObject` *asynchronously*. Publishes when complete. - parameter options: A set of header options sent to the server. Defaults to an empty set. - returns: A publisher that eventually produces a single value and then finishes or fails. diff --git a/Sources/ParseSwift/Types/ParseSchema+async.swift b/Sources/ParseSwift/Types/ParseSchema+async.swift new file mode 100644 index 000000000..fb238fcc1 --- /dev/null +++ b/Sources/ParseSwift/Types/ParseSchema+async.swift @@ -0,0 +1,113 @@ +// +// ParseSchema+async.swift +// ParseSwift +// +// Created by Corey Baker on 5/21/22. +// Copyright © 2022 Parse Community. All rights reserved. +// + +#if compiler(>=5.5.2) && canImport(_Concurrency) +import Foundation + +public extension ParseSchema { + /** + Fetches the `ParseSchema` *aynchronously* from the server. + - parameter options: A set of header options sent to the server. Defaults to an empty set. + - returns: Returns the fetched `ParseSchema`. + - throws: An error of type `ParseError`. + - note: The default cache policy for this method is `.reloadIgnoringLocalCacheData`. If a developer + desires a different policy, it should be inserted in `options`. + - requires: `.useMasterKey` has to be available. It is recommended to only + use the master key in server-side applications where the key is kept secure and not + exposed to the public. + */ + func fetch(options: API.Options = []) async throws -> Self { + try await withCheckedThrowingContinuation { continuation in + self.fetch(options: options, + completion: continuation.resume) + } + } + + /** + Creates the `ParseSchema` *aynchronously* on the server. + - parameter options: A set of header options sent to the server. Defaults to an empty set. + - returns: Returns the fetched `ParseSchema`. + - throws: An error of type `ParseError`. + - note: The default cache policy for this method is `.reloadIgnoringLocalCacheData`. If a developer + desires a different policy, it should be inserted in `options`. + - requires: `.useMasterKey` has to be available. It is recommended to only + use the master key in server-side applications where the key is kept secure and not + exposed to the public. + */ + func create(options: API.Options = []) async throws -> Self { + try await withCheckedThrowingContinuation { continuation in + self.create(options: options, + completion: continuation.resume) + } + } + + /** + Updates the `ParseSchema` *aynchronously* on the server. + - parameter options: A set of header options sent to the server. Defaults to an empty set. + - returns: Returns the fetched `ParseSchema`. + - throws: An error of type `ParseError`. + - note: The default cache policy for this method is `.reloadIgnoringLocalCacheData`. If a developer + desires a different policy, it should be inserted in `options`. + - requires: `.useMasterKey` has to be available. It is recommended to only + use the master key in server-side applications where the key is kept secure and not + exposed to the public. + */ + func update(options: API.Options = []) async throws -> Self { + try await withCheckedThrowingContinuation { continuation in + self.update(options: options, + completion: continuation.resume) + } + } + + /** + Deletes all objects in the `ParseSchema` *aynchronously* from the server. + - parameter options: A set of header options sent to the server. Defaults to an empty set. + - returns: Returns the fetched `ParseSchema`. + - throws: An error of type `ParseError`. + - warning: This will delete all objects for this `ParseSchema` and cannot be reversed. + - note: The default cache policy for this method is `.reloadIgnoringLocalCacheData`. If a developer + desires a different policy, it should be inserted in `options`. + - requires: `.useMasterKey` has to be available. It is recommended to only + use the master key in server-side applications where the key is kept secure and not + exposed to the public. + */ + func purge(options: API.Options = []) async throws { + let result = try await withCheckedThrowingContinuation { continuation in + self.purge(options: options, + completion: continuation.resume) + } + if case let .failure(error) = result { + throw error + } + } + + /** + Deletes the `ParseSchema` *aynchronously* from the server. + - parameter options: A set of header options sent to the server. Defaults to an empty set. + - returns: Returns the fetched `ParseSchema`. + - throws: An error of type `ParseError`. + - warning: This can only be used on a `ParseSchema` without objects. If the `ParseSchema` + currently contains objects, run `purge()` first. + - note: The default cache policy for this method is `.reloadIgnoringLocalCacheData`. If a developer + desires a different policy, it should be inserted in `options`. + - requires: `.useMasterKey` has to be available. It is recommended to only + use the master key in server-side applications where the key is kept secure and not + exposed to the public. + */ + func delete(options: API.Options = []) async throws { + let result = try await withCheckedThrowingContinuation { continuation in + self.delete(options: options, + completion: continuation.resume) + } + if case let .failure(error) = result { + throw error + } + } +} + +#endif diff --git a/Sources/ParseSwift/Types/ParseSchema+combine.swift b/Sources/ParseSwift/Types/ParseSchema+combine.swift new file mode 100644 index 000000000..83c5f78c7 --- /dev/null +++ b/Sources/ParseSwift/Types/ParseSchema+combine.swift @@ -0,0 +1,108 @@ +// +// ParseSchema+combine.swift +// ParseSwift +// +// Created by Corey Baker on 5/22/22. +// Copyright © 2022 Parse Community. All rights reserved. +// + +#if canImport(Combine) +import Foundation +import Combine + +public extension ParseSchema { + /** + Fetches the `ParseObject` *aynchronously* from the server. Publishes when complete. + - parameter options: A set of header options sent to the server. Defaults to an empty set. + - returns: A publisher that eventually produces a single value and then finishes or fails. + - note: The default cache policy for this method is `.reloadIgnoringLocalCacheData`. If a developer + desires a different policy, it should be inserted in `options`. + - requires: `.useMasterKey` has to be available. It is recommended to only + use the master key in server-side applications where the key is kept secure and not + exposed to the public. + */ + func fetchPublisher(includeKeys: [String]? = nil, + options: API.Options = []) -> Future { + Future { promise in + self.fetch(options: options, + completion: promise) + } + } + + /** + Creates the `ParseObject` *aynchronously* on the server. Publishes when complete. + - parameter options: A set of header options sent to the server. Defaults to an empty set. + - returns: A publisher that eventually produces a single value and then finishes or fails. + - note: The default cache policy for this method is `.reloadIgnoringLocalCacheData`. If a developer + desires a different policy, it should be inserted in `options`. + - requires: `.useMasterKey` has to be available. It is recommended to only + use the master key in server-side applications where the key is kept secure and not + exposed to the public. + */ + func createPublisher(includeKeys: [String]? = nil, + options: API.Options = []) -> Future { + Future { promise in + self.create(options: options, + completion: promise) + } + } + + /** + Updates the `ParseObject` *aynchronously* on the server. Publishes when complete. + - parameter options: A set of header options sent to the server. Defaults to an empty set. + - returns: A publisher that eventually produces a single value and then finishes or fails. + - note: The default cache policy for this method is `.reloadIgnoringLocalCacheData`. If a developer + desires a different policy, it should be inserted in `options`. + - requires: `.useMasterKey` has to be available. It is recommended to only + use the master key in server-side applications where the key is kept secure and not + exposed to the public. + */ + func updatePublisher(includeKeys: [String]? = nil, + options: API.Options = []) -> Future { + Future { promise in + self.update(options: options, + completion: promise) + } + } + + /** + Deletes all objects in the `ParseObject` *aynchronously* from the server. Publishes when complete. + - parameter options: A set of header options sent to the server. Defaults to an empty set. + - returns: A publisher that eventually produces a single value and then finishes or fails. + - note: The default cache policy for this method is `.reloadIgnoringLocalCacheData`. If a developer + desires a different policy, it should be inserted in `options`. + - warning: This will delete all objects for this `ParseSchema` and cannot be reversed. + - requires: `.useMasterKey` has to be available. It is recommended to only + use the master key in server-side applications where the key is kept secure and not + exposed to the public. + */ + func purgePublisher(includeKeys: [String]? = nil, + options: API.Options = []) -> Future { + Future { promise in + self.purge(options: options, + completion: promise) + } + } + + /** + Deletes the `ParseObject` *aynchronously* from the server. Publishes when complete. + - parameter options: A set of header options sent to the server. Defaults to an empty set. + - returns: A publisher that eventually produces a single value and then finishes or fails. + - note: The default cache policy for this method is `.reloadIgnoringLocalCacheData`. If a developer + desires a different policy, it should be inserted in `options`. + - warning: This can only be used on a `ParseSchema` without objects. If the `ParseSchema` + currently contains objects, run `purge()` first. + - requires: `.useMasterKey` has to be available. It is recommended to only + use the master key in server-side applications where the key is kept secure and not + exposed to the public. + */ + func deletePublisher(includeKeys: [String]? = nil, + options: API.Options = []) -> Future { + Future { promise in + self.delete(options: options, + completion: promise) + } + } +} + +#endif diff --git a/Sources/ParseSwift/Types/ParseSchema.swift b/Sources/ParseSwift/Types/ParseSchema.swift new file mode 100644 index 000000000..5dff34780 --- /dev/null +++ b/Sources/ParseSwift/Types/ParseSchema.swift @@ -0,0 +1,483 @@ +// +// ParseSchema.swift +// ParseSwift +// +// Created by Corey Baker on 5/21/22. +// Copyright © 2022 Parse Community. All rights reserved. +// + +import Foundation + +/** + `ParseSchema` is used for handeling your schemas. + - requires: `.useMasterKey` has to be available. It is recommended to only + use the master key in server-side applications where the key is kept secure and not + exposed to the public. + */ +public struct ParseSchema: ParseType, Decodable { + + /// The class name of the `ParseSchema`. + public var className: String + /// The CLPs of this `ParseSchema`. + public var classLevelPermissions: ParseCLP? + internal var fields: [String: ParseField]? + internal var indexes: [String: [String: AnyCodable]]? + internal var pendingIndexes = [String: [String: AnyCodable]]() + + enum CodingKeys: String, CodingKey { + case className, classLevelPermissions, fields, indexes + } + + /** + Get the current fields for this `ParseSchema`. + - returns: The current fields. + */ + public func getFields() -> [String: String] { + var currentFields = [String: String]() + fields?.forEach { (key, value) in + currentFields[key] = value.description + } + return currentFields + } + + /** + Get the current indexes for this `ParseSchema`. + - returns: The current indexes. + */ + public func getIndexes() -> [String: [String: String]] { + var currentIndexes = [String: [String: String]]() + indexes?.forEach { (name, value) in + value.forEach { (field, index) in + currentIndexes[name] = [field: index.description] + } + } + pendingIndexes.forEach { (name, value) in + value.forEach { (field, index) in + currentIndexes[name] = [field: index.description] + } + } + return currentIndexes + } +} + +// MARK: Default Implementations +public extension ParseSchema { + static var className: String { + SchemaObject.className + } + + /// Create an empty instance of `ParseSchema` type. + init() { + self.init(className: SchemaObject.className) + } + + /** + Create an empty instance of ParseSchema type with a specific CLP. + - parameter classLevelPermissions: The CLP access for this `ParseSchema`. + */ + init(classLevelPermissions: ParseCLP) { + self.init(className: SchemaObject.className) + self.classLevelPermissions = classLevelPermissions + } + + /** + Add a Field to create/update a `ParseSchema`. + + - parameter name: Name of the field that will be created/updated in the schema on Parse Server. + - parameter type: The `ParseField.FieldType` of the field that will be created/updated + in the schema on Parse Server. + - parameter target: The target `ParseObject` of the field that will be created/updated in + the schema on Parse Server. + - parameter options: The `ParseFieldOptions` of the field that will be created/updated in + the schema on Parse Server. + - returns: A mutated instance of `ParseSchema` for easy chaining. + - throws: An error of type `ParseError`. + - warning: The use of `options` requires Parse Server 3.7.0+. + */ + func addField(_ name: String, + type: ParseField.FieldType, + options: ParseFieldOptions) throws -> Self where T: ParseObject { + switch type { + case .pointer: + return addPointer(name, options: options) + case .relation: + return addRelation(name, options: options) + default: + throw ParseError(code: .unknownError, + message: "The type \"\(type)\" isn't supported by this method") + } + } + + /** + Add a Field to create/update a `ParseSchema`. + + - parameter name: Name of the field that will be created/updated in the schema on Parse Server. + - parameter type: The `ParseField.FieldType` of the field that will be created/updated + in the schema on Parse Server. + - parameter options: The `ParseFieldOptions` of the field that will be created/updated in + the schema on Parse Server. + - returns: A mutated instance of `ParseSchema` for easy chaining. + - warning: The use of `options` requires Parse Server 3.7.0+. + */ + func addField(_ name: String, + type: ParseField.FieldType, + options: ParseFieldOptions) -> Self { + var mutableSchema = self + let field = ParseField(type: type, options: options) + if mutableSchema.fields != nil { + mutableSchema.fields?[name] = field + } else { + mutableSchema.fields = [name: field] + } + + return mutableSchema + } + + /** + Add a Pointer field to create/update a `ParseSchema`. + + - parameter name: Name of the field that will be created/updated in the schema on Parse Server. + - parameter target: The target `ParseObject` of the field that will be created/updated in + the schema on Parse Server. + Defaults to **nil**. + - parameter options: The `ParseFieldOptions` of the field that will be created/updated in + the schema on Parse Server. + - returns: A mutated instance of `ParseSchema` for easy chaining. + - throws: An error of type `ParseError`. + - warning: The use of `options` requires Parse Server 3.7.0+. + */ + func addPointer(_ name: String, + options: ParseFieldOptions) -> Self where T: ParseObject { + + let field = ParseField(type: .pointer, options: options) + var mutableSchema = self + if mutableSchema.fields != nil { + mutableSchema.fields?[name] = field + } else { + mutableSchema.fields = [name: field] + } + + return mutableSchema + } + + /** + Add a Relation field to create/update a `ParseSchema`. + + - parameter name: Name of the field that will be created/updated in the schema on Parse Server. + - parameter options: The `ParseFieldOptions` of the field that will be created/updated in + the schema on Parse Server. + Defaults to **nil**. + - returns: A mutated instance of `ParseSchema` for easy chaining. + - throws: An error of type `ParseError`. + */ + func addRelation(_ name: String, + options: ParseFieldOptions) -> Self where T: ParseObject { + + let field = ParseField(type: .relation, options: options) + var mutableSchema = self + if mutableSchema.fields != nil { + mutableSchema.fields?[name] = field + } else { + mutableSchema.fields = [name: field] + } + + return mutableSchema + } + + /** + Delete a field in the `ParseSchema`. + + - parameter name: Name of the field that will be deleted in the schema on Parse Server. + - returns: A mutated instance of `ParseSchema` for easy chaining. + */ + func deleteField(_ name: String) -> Self { + let field = ParseField(operation: .delete) + var mutableSchema = self + if mutableSchema.fields != nil { + mutableSchema.fields?[name] = field + } else { + mutableSchema.fields = [name: field] + } + + return mutableSchema + } + + /** + Add an index to create/update a `ParseSchema`. + + - parameter name: Name of the index that will be created/updated in the schema on Parse Server. + - parameter field: The **field** the index should be added to. + - parameter index: The **index** to create. + - returns: A mutated instance of `ParseSchema` for easy chaining. + */ + func addIndex(_ name: String, + field: String, + index: Encodable) -> Self { + var mutableSchema = self + mutableSchema.pendingIndexes[name] = [field: AnyCodable(index)] + return mutableSchema + } + + /** + Delete an index in the `ParseSchema`. + + - parameter name: Name of the index that will be deleted in the schema on Parse Server. + - returns: A mutated instance of `ParseSchema` for easy chaining. + */ + func deleteIndex(_ name: String) -> Self { + let index = ["__op": AnyCodable(Operation.delete.rawValue)] + var mutableSchema = self + mutableSchema.pendingIndexes[name] = index + return mutableSchema + } +} + +// MARK: Convenience +extension ParseSchema { + var endpoint: API.Endpoint { + .schema(className: className) + } + + var endpointPurge: API.Endpoint { + .purge(className: className) + } +} + +// MARK: Fetchable +extension ParseSchema { + + /** + Fetches the `ParseSchema` *asynchronously* from the server and executes the given callback block. + - parameter options: A set of header options sent to the server. Defaults to an empty set. + - parameter callbackQueue: The queue to return to after completion. Default + value of .main. + - parameter completion: The block to execute when completed. + It should have the following argument signature: `(Result)`. + - note: The default cache policy for this method is `.reloadIgnoringLocalCacheData`. If a developer + desires a different policy, it should be inserted in `options`. + - requires: `.useMasterKey` has to be available. It is recommended to only + use the master key in server-side applications where the key is kept secure and not + exposed to the public. + */ + public func fetch(options: API.Options = [], + callbackQueue: DispatchQueue = .main, + completion: @escaping (Result) -> Void) { + var options = options + options.insert(.useMasterKey) + options.insert(.cachePolicy(.reloadIgnoringLocalCacheData)) + fetchCommand() + .executeAsync(options: options, + callbackQueue: callbackQueue, + completion: completion) + } + + func fetchCommand() -> API.Command { + + return API.Command(method: .GET, + path: endpoint) { (data) -> Self in + try ParseCoding.jsonDecoder().decode(Self.self, from: data) + } + } +} + +// MARK: Savable +extension ParseSchema { + + /** + Creates the `ParseSchema` *asynchronously* on the server and executes the given callback block. + - parameter options: A set of header options sent to the server. Defaults to an empty set. + - parameter callbackQueue: The queue to return to after completion. Default + value of .main. + - parameter completion: The block to execute when completed. + It should have the following argument signature: `(Result)`. + - note: The default cache policy for this method is `.reloadIgnoringLocalCacheData`. If a developer + desires a different policy, it should be inserted in `options`. + - requires: `.useMasterKey` has to be available. It is recommended to only + use the master key in server-side applications where the key is kept secure and not + exposed to the public. + */ + public func create(options: API.Options = [], + callbackQueue: DispatchQueue = .main, + completion: @escaping (Result) -> Void) { + var options = options + options.insert(.useMasterKey) + options.insert(.cachePolicy(.reloadIgnoringLocalCacheData)) + createCommand() + .executeAsync(options: options, + callbackQueue: callbackQueue, + completion: completion) + } + + /** + Updates the `ParseSchema` *asynchronously* on the server and executes the given callback block. + - parameter options: A set of header options sent to the server. Defaults to an empty set. + - parameter callbackQueue: The queue to return to after completion. Default + value of .main. + - parameter completion: The block to execute when completed. + It should have the following argument signature: `(Result)`. + - note: The default cache policy for this method is `.reloadIgnoringLocalCacheData`. If a developer + desires a different policy, it should be inserted in `options`. + - requires: `.useMasterKey` has to be available. It is recommended to only + use the master key in server-side applications where the key is kept secure and not + exposed to the public. + */ + public func update(options: API.Options = [], + callbackQueue: DispatchQueue = .main, + completion: @escaping (Result) -> Void) { + var mutableSchema = self + if !mutableSchema.pendingIndexes.isEmpty { + mutableSchema.indexes = pendingIndexes + } else { + mutableSchema.indexes = nil + } + var options = options + options.insert(.useMasterKey) + options.insert(.cachePolicy(.reloadIgnoringLocalCacheData)) + mutableSchema.updateCommand() + .executeAsync(options: options, + callbackQueue: callbackQueue, + completion: completion) + } + + func createCommand() -> API.Command { + + return API.Command(method: .POST, + path: endpoint, + body: self) { (data) -> Self in + try ParseCoding.jsonDecoder().decode(Self.self, from: data) + } + } + + func updateCommand() -> API.Command { + + API.Command(method: .PUT, + path: endpoint, + body: self) { (data) -> Self in + try ParseCoding.jsonDecoder().decode(Self.self, from: data) + } + } +} + +// MARK: Deletable +extension ParseSchema { + + /** + Deletes all objects in the `ParseSchema` *asynchronously* from the server and executes the given callback block. + + - parameter options: A set of header options sent to the server. Defaults to an empty set. + - parameter callbackQueue: The queue to return to after completion. Default + value of .main. + - parameter completion: The block to execute when completed. + It should have the following argument signature: `(Result)`. + - warning: This will delete all objects for this `ParseSchema` and cannot be reversed. + - note: The default cache policy for this method is `.reloadIgnoringLocalCacheData`. If a developer + desires a different policy, it should be inserted in `options`. + - requires: `.useMasterKey` has to be available. It is recommended to only + use the master key in server-side applications where the key is kept secure and not + exposed to the public. + */ + public func purge( + options: API.Options = [], + callbackQueue: DispatchQueue = .main, + completion: @escaping (Result) -> Void + ) { + var options = options + options.insert(.useMasterKey) + options.insert(.cachePolicy(.reloadIgnoringLocalCacheData)) + purgeCommand().executeAsync(options: options, + callbackQueue: callbackQueue) { result in + switch result { + + case .success: + completion(.success(())) + case .failure(let error): + callbackQueue.async { + completion(.failure(error)) + } + } + } + } + + /** + Deletes the `ParseSchema` *asynchronously* from the server and executes the given callback block. + + - parameter options: A set of header options sent to the server. Defaults to an empty set. + - parameter callbackQueue: The queue to return to after completion. Default + value of .main. + - parameter completion: The block to execute when completed. + It should have the following argument signature: `(Result)`. + - warning: This can only be used on a `ParseSchema` without objects. If the `ParseSchema` + currently contains objects, run `purge()` first. + - note: The default cache policy for this method is `.reloadIgnoringLocalCacheData`. If a developer + desires a different policy, it should be inserted in `options`. + - requires: `.useMasterKey` has to be available. It is recommended to only + use the master key in server-side applications where the key is kept secure and not + exposed to the public. + */ + public func delete( + options: API.Options = [], + callbackQueue: DispatchQueue = .main, + completion: @escaping (Result) -> Void + ) { + var options = options + options.insert(.useMasterKey) + options.insert(.cachePolicy(.reloadIgnoringLocalCacheData)) + deleteCommand().executeAsync(options: options, + callbackQueue: callbackQueue) { result in + switch result { + + case .success: + completion(.success(())) + case .failure(let error): + callbackQueue.async { + completion(.failure(error)) + } + } + } + } + + func purgeCommand() -> API.Command { + + API.Command(method: .DELETE, + path: endpointPurge) { (data) -> NoBody in + let error = try? ParseCoding.jsonDecoder().decode(ParseError.self, from: data) + if let error = error { + throw error + } else { + return NoBody() + } + } + } + + func deleteCommand() -> API.Command { + + API.Command(method: .DELETE, + path: endpoint, + body: self) { (data) -> NoBody in + let error = try? ParseCoding.jsonDecoder().decode(ParseError.self, from: data) + if let error = error { + throw error + } else { + return NoBody() + } + } + } +} + +// MARK: CustomDebugStringConvertible +extension ParseSchema: CustomDebugStringConvertible { + public var debugDescription: String { + guard let descriptionData = try? ParseCoding.jsonEncoder().encode(self), + let descriptionString = String(data: descriptionData, encoding: .utf8) else { + return "ParseSchema ()" + } + return "ParseSchema (\(descriptionString))" + } +} + +// MARK: CustomStringConvertible +extension ParseSchema: CustomStringConvertible { + public var description: String { + debugDescription + } +} diff --git a/Sources/ParseSwift/Types/Pointer+async.swift b/Sources/ParseSwift/Types/Pointer+async.swift index 3bb86e5e6..7a2ddfcff 100644 --- a/Sources/ParseSwift/Types/Pointer+async.swift +++ b/Sources/ParseSwift/Types/Pointer+async.swift @@ -12,7 +12,7 @@ import Foundation // MARK: Async/Await public extension Pointer { /** - Fetches the `ParseObject` *aynchronously* with the current data from the server and sets an error if one occurs. + Fetches the `ParseObject` *aynchronously* with the current data from the server. - parameter includeKeys: The name(s) of the key(s) to include that are `ParseObject`s. Use `["*"]` to include all keys. This is similar to `include` and `includeAll` for `Query`. diff --git a/Sources/ParseSwift/Types/Pointer+combine.swift b/Sources/ParseSwift/Types/Pointer+combine.swift index b4e5380b5..b28311a7d 100644 --- a/Sources/ParseSwift/Types/Pointer+combine.swift +++ b/Sources/ParseSwift/Types/Pointer+combine.swift @@ -13,7 +13,7 @@ import Combine // MARK: Combine public extension Pointer { /** - Fetches the `ParseObject` *aynchronously* with the current data from the server and sets an error if one occurs. + Fetches the `ParseObject` *aynchronously* with the current data from the server. Publishes when complete. - parameter includeKeys: The name(s) of the key(s) to include that are `ParseObject`s. Use `["*"]` to include all keys. This is similar to `include` and diff --git a/Sources/ParseSwift/Types/Pointer.swift b/Sources/ParseSwift/Types/Pointer.swift index 62e8760b0..f61cc6def 100644 --- a/Sources/ParseSwift/Types/Pointer.swift +++ b/Sources/ParseSwift/Types/Pointer.swift @@ -13,7 +13,7 @@ extension ParsePointer { /** 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. + - returns: Returns a **true** if the other object has the same `objectId` or **false** if unsuccessful. */ func hasSameObjectId(as other: ParsePointer) -> Bool { return other.className == className && other.objectId == objectId @@ -81,7 +81,7 @@ public extension Pointer { /** Determines if a `ParseObject` and `Pointer`have the same `objectId`. - parameter as: `ParseObject` to compare. - - returns: Returns a `true` if the other object has the same `objectId` or `false` if unsuccessful. + - returns: Returns a **true** if the other object has the same `objectId` or **false** if unsuccessful. */ func hasSameObjectId(as other: T) -> Bool { return other.className == className && other.objectId == objectId @@ -90,14 +90,14 @@ public extension Pointer { /** Determines if two `Pointer`'s have the same `objectId`. - parameter as: `Pointer` to compare. - - returns: Returns a `true` if the other object has the same `objectId` or `false` if unsuccessful. + - returns: Returns a **true** if the other object has the same `objectId` or **false** if unsuccessful. */ func hasSameObjectId(as other: Self) -> Bool { return other.className == className && other.objectId == objectId } /** - Fetches the `ParseObject` *synchronously* with the current data from the server and sets an error if one occurs. + Fetches the `ParseObject` *synchronously* with the current data from the server. - parameter includeKeys: The name(s) of the key(s) to include that are `ParseObject`s. Use `["*"]` to include all keys. This is similar to `include` and `includeAll` for `Query`. diff --git a/Sources/ParseSwift/Types/Query+async.swift b/Sources/ParseSwift/Types/Query+async.swift index abeda2663..9262d5430 100644 --- a/Sources/ParseSwift/Types/Query+async.swift +++ b/Sources/ParseSwift/Types/Query+async.swift @@ -178,7 +178,9 @@ public extension Query { /** Executes an aggregate query *asynchronously*. - - requires: `.useMasterKey` has to be available. + - requires: `.useMasterKey` has to be available. It is recommended to only + use the master key in server-side applications where the key is kept secure and not + exposed to the public. - parameter pipeline: A pipeline of stages to process query. - parameter options: A set of header options sent to the server. Defaults to an empty set. - returns: An array of ParseObjects. @@ -195,7 +197,9 @@ public extension Query { /** Query plan information for executing an aggregate query *asynchronously*. - - requires: `.useMasterKey` has to be available. + - requires: `.useMasterKey` has to be available. It is recommended to only + use the master key in server-side applications where the key is kept secure and not + exposed to the public. - note: An explain query will have many different underlying types. Since Swift is a strongly typed language, a developer should specify the type expected to be decoded which will be different for MongoDB and PostgreSQL. One way around this is to use a type-erased wrapper @@ -222,7 +226,9 @@ public extension Query { /** Executes a distinct query *asynchronously* and returns unique values when complete. - - requires: `.useMasterKey` has to be available. + - requires: `.useMasterKey` has to be available. It is recommended to only + use the master key in server-side applications where the key is kept secure and not + exposed to the public. - parameter key: A field to find distinct values. - parameter options: A set of header options sent to the server. Defaults to an empty set. - returns: An array of ParseObjects. @@ -239,7 +245,9 @@ public extension Query { /** Query plan information for executing a distinct query *asynchronously* and returns unique values when complete. - - requires: `.useMasterKey` has to be available. + - requires: `.useMasterKey` has to be available. It is recommended to only + use the master key in server-side applications where the key is kept secure and not + exposed to the public. - note: An explain query will have many different underlying types. Since Swift is a strongly typed language, a developer should specify the type expected to be decoded which will be different for MongoDB and PostgreSQL. One way around this is to use a type-erased wrapper diff --git a/Sources/ParseSwift/Types/Query+combine.swift b/Sources/ParseSwift/Types/Query+combine.swift index 8109561a4..2a61b2a07 100644 --- a/Sources/ParseSwift/Types/Query+combine.swift +++ b/Sources/ParseSwift/Types/Query+combine.swift @@ -172,7 +172,9 @@ public extension Query { /** Executes an aggregate query *asynchronously* and publishes when complete. - - requires: `.useMasterKey` has to be available. + - requires: `.useMasterKey` has to be available. It is recommended to only + use the master key in server-side applications where the key is kept secure and not + exposed to the public. - parameter pipeline: A pipeline of stages to process query. - parameter options: A set of header options sent to the server. Defaults to an empty set. - returns: A publisher that eventually produces a single value and then finishes or fails. @@ -188,7 +190,9 @@ public extension Query { /** Query plan information for executing an aggregate query *asynchronously* and publishes when complete. - - requires: `.useMasterKey` has to be available. + - requires: `.useMasterKey` has to be available. It is recommended to only + use the master key in server-side applications where the key is kept secure and not + exposed to the public. - note: An explain query will have many different underlying types. Since Swift is a strongly typed language, a developer should specify the type expected to be decoded which will be different for MongoDB and PostgreSQL. One way around this is to use a type-erased wrapper @@ -214,7 +218,9 @@ public extension Query { /** Executes a distinct query *asynchronously* and publishes unique values when complete. - - requires: `.useMasterKey` has to be available. + - requires: `.useMasterKey` has to be available. It is recommended to only + use the master key in server-side applications where the key is kept secure and not + exposed to the public. - parameter key: A field to find distinct values. - parameter options: A set of header options sent to the server. Defaults to an empty set. - returns: A publisher that eventually produces a single value and then finishes or fails. @@ -230,7 +236,9 @@ public extension Query { /** Query plan information for executing a distinct query *asynchronously* and publishes unique values when complete. - - requires: `.useMasterKey` has to be available. + - requires: `.useMasterKey` has to be available. It is recommended to only + use the master key in server-side applications where the key is kept secure and not + exposed to the public. - note: An explain query will have many different underlying types. Since Swift is a strongly typed language, a developer should specify the type expected to be decoded which will be different for MongoDB and PostgreSQL. One way around this is to use a type-erased wrapper diff --git a/Sources/ParseSwift/Types/Query.swift b/Sources/ParseSwift/Types/Query.swift index eba0d5a94..2d0545bc5 100644 --- a/Sources/ParseSwift/Types/Query.swift +++ b/Sources/ParseSwift/Types/Query.swift @@ -845,7 +845,9 @@ extension Query: Queryable { /** Executes an aggregate query *synchronously*. - - requires: `.useMasterKey` has to be available. + - requires: `.useMasterKey` has to be available. It is recommended to only + use the master key in server-side applications where the key is kept secure and not + exposed to the public. - parameter pipeline: A pipeline of stages to process query. - parameter options: A set of header options sent to the server. Defaults to an empty set. - throws: An error of type `ParseError`. @@ -885,7 +887,9 @@ extension Query: Queryable { /** Executes an aggregate query *asynchronously*. - - requires: `.useMasterKey` has to be available. + - requires: `.useMasterKey` has to be available. It is recommended to only + use the master key in server-side applications where the key is kept secure and not + exposed to the public. - parameter pipeline: A pipeline of stages to process query. - parameter options: A set of header options sent to the server. Defaults to an empty set. - parameter callbackQueue: The queue to return to after completion. Default value of `.main`. @@ -939,7 +943,9 @@ extension Query: Queryable { /** Query plan information for executing an aggregate query *synchronously*. - - requires: `.useMasterKey` has to be available. + - requires: `.useMasterKey` has to be available. It is recommended to only + use the master key in server-side applications where the key is kept secure and not + exposed to the public. - note: An explain query will have many different underlying types. Since Swift is a strongly typed language, a developer should specify the type expected to be decoded which will be different for MongoDB and PostgreSQL. One way around this is to use a type-erased wrapper @@ -991,7 +997,9 @@ extension Query: Queryable { /** Query plan information for executing an aggregate query *asynchronously*. - - requires: `.useMasterKey` has to be available. + - requires: `.useMasterKey` has to be available. It is recommended to only + use the master key in server-side applications where the key is kept secure and not + exposed to the public. - note: An explain query will have many different underlying types. Since Swift is a strongly typed language, a developer should specify the type expected to be decoded which will be different for MongoDB and PostgreSQL. One way around this is to use a type-erased wrapper @@ -1058,7 +1066,9 @@ extension Query: Queryable { /** Executes an aggregate query *synchronously* and calls the given. - - requires: `.useMasterKey` has to be available. + - requires: `.useMasterKey` has to be available. It is recommended to only + use the master key in server-side applications where the key is kept secure and not + exposed to the public. - parameter key: A field to find distinct values. - parameter options: A set of header options sent to the server. Defaults to an empty set. - throws: An error of type `ParseError`. @@ -1078,7 +1088,9 @@ extension Query: Queryable { /** Executes a distinct query *asynchronously* and returns unique values. - - requires: `.useMasterKey` has to be available. + - requires: `.useMasterKey` has to be available. It is recommended to only + use the master key in server-side applications where the key is kept secure and not + exposed to the public. - parameter key: A field to find distinct values. - parameter options: A set of header options sent to the server. Defaults to an empty set. - parameter callbackQueue: The queue to return to after completion. Default value of `.main`. @@ -1107,7 +1119,9 @@ extension Query: Queryable { /** Query plan information for executing an aggregate query *synchronously* and calls the given. - - requires: `.useMasterKey` has to be available. + - requires: `.useMasterKey` has to be available. It is recommended to only + use the master key in server-side applications where the key is kept secure and not + exposed to the public. - note: An explain query will have many different underlying types. Since Swift is a strongly typed language, a developer should specify the type expected to be decoded which will be different for MongoDB and PostgreSQL. One way around this is to use a type-erased wrapper @@ -1141,7 +1155,9 @@ extension Query: Queryable { /** Query plan information for executing a distinct query *asynchronously* and returns unique values. - - requires: `.useMasterKey` has to be available. + - requires: `.useMasterKey` has to be available. It is recommended to only + use the master key in server-side applications where the key is kept secure and not + exposed to the public. - note: An explain query will have many different underlying types. Since Swift is a strongly typed language, a developer should specify the type expected to be decoded which will be different for MongoDB and PostgreSQL. One way around this is to use a type-erased wrapper diff --git a/Sources/ParseSwift/Types/QueryConstraint.swift b/Sources/ParseSwift/Types/QueryConstraint.swift index 75195718f..09fa31ebb 100644 --- a/Sources/ParseSwift/Types/QueryConstraint.swift +++ b/Sources/ParseSwift/Types/QueryConstraint.swift @@ -511,7 +511,7 @@ public func near(key: String, geoPoint: ParseGeoPoint) -> QueryConstraint { - parameter key: The key to be constrained. - parameter geoPoint: The reference point as a `ParseGeoPoint`. - parameter distance: Maximum distance in radians. - - parameter sorted: `true` if results should be sorted by distance ascending, `false` is no sorting is required. + - parameter sorted: **true** if results should be sorted by distance ascending, **false** is no sorting is required. Defaults to true. - returns: The same instance of `QueryConstraint` as the receiver. */ @@ -537,7 +537,7 @@ public func withinRadians(key: String, - parameter key: The key to be constrained. - parameter geoPoint: The reference point represented as a `ParseGeoPoint`. - parameter distance: Maximum distance in miles. - - parameter sorted: `true` if results should be sorted by distance ascending, `false` is no sorting is required. + - parameter sorted: **true** if results should be sorted by distance ascending, **false** is no sorting is required. Defaults to true. - returns: The same instance of `QueryConstraint` as the receiver. */ @@ -558,7 +558,7 @@ public func withinMiles(key: String, - parameter key: The key to be constrained. - parameter geoPoint: The reference point represented as a `ParseGeoPoint`. - parameter distance: Maximum distance in kilometers. - - parameter sorted: `true` if results should be sorted by distance ascending, `false` is no sorting is required. + - parameter sorted: **true** if results should be sorted by distance ascending, **false** is no sorting is required. Defaults to true. - returns: The same instance of `QueryConstraint` as the receiver. */ diff --git a/Tests/ParseSwiftTests/ParseCLPTests.swift b/Tests/ParseSwiftTests/ParseCLPTests.swift new file mode 100644 index 000000000..1c2f9f5df --- /dev/null +++ b/Tests/ParseSwiftTests/ParseCLPTests.swift @@ -0,0 +1,1037 @@ +// +// ParseCLPTests.swift +// ParseSwift +// +// Created by Corey Baker on 5/28/22. +// Copyright © 2022 Parse Community. All rights reserved. +// + +import Foundation +import XCTest +@testable import ParseSwift + +class ParseCLPTests: XCTestCase { // swiftlint:disable:this type_body_length + + struct User: ParseUser { + + //: These are required by ParseObject + var objectId: String? + var createdAt: Date? + var updatedAt: Date? + var ACL: ParseACL? + var originalData: Data? + + // These are required by ParseUser + var username: String? + var email: String? + var emailVerified: Bool? + var password: String? + var authData: [String: [String: String]?]? + + // Your custom keys + var customKey: String? + + init() { } + init(objectId: String) { + self.objectId = objectId + } + } + + struct Role: ParseRole { + + // required by ParseObject + var objectId: String? + var createdAt: Date? + var updatedAt: Date? + var ACL: ParseACL? + var originalData: Data? + + // provided by Role + var name: String? + } + + override func setUpWithError() throws { + try super.setUpWithError() + guard let url = URL(string: "http://localhost:1337/1") else { + XCTFail("Should create valid URL") + return + } + ParseSwift.initialize(applicationId: "applicationId", + clientKey: "clientKey", + masterKey: "masterKey", + serverURL: url, + testing: true) + } + + let objectId = "1234" + let user = User(objectId: "1234") + + override func tearDownWithError() throws { + try super.tearDownWithError() + MockURLProtocol.removeAll() + #if !os(Linux) && !os(Android) && !os(Windows) + try KeychainStore.shared.deleteAll() + #endif + try ParseStorage.shared.deleteAll() + } + + func testInitializerRequiresAuthentication() throws { + let clp = ParseCLP(requiresAuthentication: true, publicAccess: false) + XCTAssertEqual(clp.get?[ParseCLP.Access.requiresAuthentication.rawValue], true) + XCTAssertEqual(clp.find?[ParseCLP.Access.requiresAuthentication.rawValue], true) + XCTAssertEqual(clp.create?[ParseCLP.Access.requiresAuthentication.rawValue], true) + XCTAssertEqual(clp.update?[ParseCLP.Access.requiresAuthentication.rawValue], true) + XCTAssertEqual(clp.delete?[ParseCLP.Access.requiresAuthentication.rawValue], true) + XCTAssertEqual(clp.count?[ParseCLP.Access.requiresAuthentication.rawValue], true) + XCTAssertNil(clp.addField?[ParseCLP.Access.requiresAuthentication.rawValue]) + XCTAssertNil(clp.protectedFields) + XCTAssertNil(clp.readUserFields) + XCTAssertNil(clp.writeUserFields) + XCTAssertNil(clp.get?[ParseCLP.Access.publicScope.rawValue]) + XCTAssertNil(clp.find?[ParseCLP.Access.publicScope.rawValue]) + XCTAssertNil(clp.create?[ParseCLP.Access.publicScope.rawValue]) + XCTAssertNil(clp.update?[ParseCLP.Access.publicScope.rawValue]) + XCTAssertNil(clp.delete?[ParseCLP.Access.publicScope.rawValue]) + XCTAssertNil(clp.count?[ParseCLP.Access.publicScope.rawValue]) + XCTAssertNil(clp.addField?[ParseCLP.Access.publicScope.rawValue]) + } + + func testInitializerPublicAccess() throws { + let clp = ParseCLP(requiresAuthentication: false, publicAccess: true) + XCTAssertEqual(clp.get?[ParseCLP.Access.publicScope.rawValue], true) + XCTAssertEqual(clp.find?[ParseCLP.Access.publicScope.rawValue], true) + XCTAssertEqual(clp.create?[ParseCLP.Access.publicScope.rawValue], true) + XCTAssertEqual(clp.update?[ParseCLP.Access.publicScope.rawValue], true) + XCTAssertEqual(clp.delete?[ParseCLP.Access.publicScope.rawValue], true) + XCTAssertEqual(clp.count?[ParseCLP.Access.publicScope.rawValue], true) + XCTAssertNil(clp.addField?[ParseCLP.Access.publicScope.rawValue]) + XCTAssertNil(clp.protectedFields) + XCTAssertNil(clp.readUserFields) + XCTAssertNil(clp.writeUserFields) + XCTAssertNil(clp.get?[ParseCLP.Access.requiresAuthentication.rawValue]) + XCTAssertNil(clp.find?[ParseCLP.Access.requiresAuthentication.rawValue]) + XCTAssertNil(clp.create?[ParseCLP.Access.requiresAuthentication.rawValue]) + XCTAssertNil(clp.update?[ParseCLP.Access.requiresAuthentication.rawValue]) + XCTAssertNil(clp.delete?[ParseCLP.Access.requiresAuthentication.rawValue]) + XCTAssertNil(clp.count?[ParseCLP.Access.requiresAuthentication.rawValue]) + XCTAssertNil(clp.addField?[ParseCLP.Access.requiresAuthentication.rawValue]) + } + + func testInitializerRequireAndPublicAccess() throws { + let clp = ParseCLP(requiresAuthentication: true, publicAccess: true) + XCTAssertEqual(clp.get?[ParseCLP.Access.publicScope.rawValue], true) + XCTAssertEqual(clp.find?[ParseCLP.Access.publicScope.rawValue], true) + XCTAssertEqual(clp.create?[ParseCLP.Access.publicScope.rawValue], true) + XCTAssertEqual(clp.update?[ParseCLP.Access.publicScope.rawValue], true) + XCTAssertEqual(clp.delete?[ParseCLP.Access.publicScope.rawValue], true) + XCTAssertEqual(clp.count?[ParseCLP.Access.publicScope.rawValue], true) + XCTAssertNil(clp.addField?[ParseCLP.Access.publicScope.rawValue]) + XCTAssertNil(clp.protectedFields) + XCTAssertNil(clp.readUserFields) + XCTAssertNil(clp.writeUserFields) + XCTAssertEqual(clp.get?[ParseCLP.Access.requiresAuthentication.rawValue], true) + XCTAssertEqual(clp.find?[ParseCLP.Access.requiresAuthentication.rawValue], true) + XCTAssertEqual(clp.create?[ParseCLP.Access.requiresAuthentication.rawValue], true) + XCTAssertEqual(clp.update?[ParseCLP.Access.requiresAuthentication.rawValue], true) + XCTAssertEqual(clp.delete?[ParseCLP.Access.requiresAuthentication.rawValue], true) + XCTAssertEqual(clp.count?[ParseCLP.Access.requiresAuthentication.rawValue], true) + XCTAssertNil(clp.addField?[ParseCLP.Access.requiresAuthentication.rawValue]) + } + + func testPointerFields() throws { + let fields = Set(["hello", "world"]) + let clp = ParseCLP().setPointerFields(fields, on: .create) + XCTAssertEqual(clp.getPointerFields(.create), fields) + + let newField = Set(["new"]) + let clp2 = clp.addPointerFields(newField, on: .create) + XCTAssertEqual(clp2.getPointerFields(.create), fields.union(newField)) + + let clp3 = clp2.removePointerFields(newField, on: .create) + XCTAssertEqual(clp3.getPointerFields(.create), fields) + + let clp4 = ParseCLP().addPointerFields(newField, on: .create) + XCTAssertEqual(clp4.getPointerFields(.create), newField) + + let clp5 = ParseCLP().setAccess(true, on: .create, for: "yo") + .setPointerFields(fields, on: .create) + XCTAssertEqual(clp5.getPointerFields(.create), fields) + XCTAssertEqual(clp5.hasAccess(.create, for: "yo"), true) + } + + func testPointerFieldsEncode() throws { + let fields = Set(["world"]) + let clp = ParseCLP().setPointerFields(fields, on: .create) + XCTAssertEqual(clp.description, "ParseCLP ({\"create\":{\"pointerFields\":[\"world\"]}})") + } + + func testPointerAndWriteAccessPublicSetEncode() throws { + let fields = Set(["world"]) + let clp = ParseCLP() + .setPointerFields(fields, on: .create) + .setWriteAccessPublic(true, canAddField: true) + // swiftlint:disable:next line_length + XCTAssertEqual(clp.description, "ParseCLP ({\"addField\":{\"*\":true},\"create\":{\"*\":true,\"pointerFields\":[\"world\"]},\"delete\":{\"*\":true},\"update\":{\"*\":true}})") + } + + func testProtectedFieldsPublic() throws { + let fields = Set(["hello", "world"]) + let clp = ParseCLP().setProtectedFieldsPublic(fields) + XCTAssertEqual(clp.getProtectedFieldsPublic(), fields) + + let newField = Set(["new"]) + let clp2 = clp.addProtectedFieldsPublic(newField) + XCTAssertEqual(clp2.getProtectedFieldsPublic(), fields.union(newField)) + + let clp3 = clp2.removeProtectedFieldsPublic(newField) + XCTAssertEqual(clp3.getProtectedFieldsPublic(), fields) + + let clp4 = ParseCLP().addProtectedFieldsPublic(newField) + XCTAssertEqual(clp4.getProtectedFieldsPublic(), newField) + + let clp5 = clp.setProtectedFieldsRequiresAuthentication(newField) + XCTAssertEqual(clp5.getProtectedFieldsPublic(), fields) + XCTAssertEqual(clp5.getProtectedFieldsRequiresAuthentication(), newField) + } + + func testProtectedFieldsRequiresAuthentication() throws { + let fields = Set(["hello", "world"]) + let clp = ParseCLP().setProtectedFieldsRequiresAuthentication(fields) + XCTAssertEqual(clp.getProtectedFieldsRequiresAuthentication(), fields) + + let newField = Set(["new"]) + let clp2 = clp.addProtectedFieldsRequiresAuthentication(newField) + XCTAssertEqual(clp2.getProtectedFieldsRequiresAuthentication(), fields.union(newField)) + + let clp3 = clp2.removeProtectedFieldsRequiresAuthentication(newField) + XCTAssertEqual(clp3.getProtectedFieldsRequiresAuthentication(), fields) + + let clp4 = ParseCLP().addProtectedFieldsRequiresAuthentication(newField) + XCTAssertEqual(clp4.getProtectedFieldsRequiresAuthentication(), newField) + } + + func testProtectedFieldsUserField() throws { + let fields = Set(["hello", "world"]) + let userField = "peace" + let clp = ParseCLP().setProtectedFields(fields, userField: userField) + XCTAssertEqual(clp.getProtectedFieldsUser(userField), fields) + + let newField = Set(["new"]) + let clp2 = clp.addProtectedFieldsUser(newField, userField: userField) + XCTAssertEqual(clp2.getProtectedFieldsUser(userField), fields.union(newField)) + + let clp3 = clp2.removeProtectedFieldsUser(newField, userField: userField) + XCTAssertEqual(clp3.getProtectedFieldsUser(userField), fields) + + let clp4 = ParseCLP().addProtectedFieldsUser(newField, userField: userField) + XCTAssertEqual(clp4.getProtectedFieldsUser(userField), newField) + } + + func testProtectedFieldsObjectId() throws { + let fields = Set(["hello", "world"]) + let clp = ParseCLP().setProtectedFields(fields, for: objectId) + XCTAssertEqual(clp.getProtectedFields(objectId), fields) + + let newField = Set(["new"]) + let clp2 = clp.addProtectedFields(newField, for: objectId) + XCTAssertEqual(clp2.getProtectedFields(objectId), fields.union(newField)) + + let clp3 = clp2.removeProtectedFields(newField, for: objectId) + XCTAssertEqual(clp3.getProtectedFields(objectId), fields) + + let clp4 = ParseCLP().addProtectedFields(newField, for: objectId) + XCTAssertEqual(clp4.getProtectedFields(objectId), newField) + } + + func testProtectedFieldsUser() throws { + let fields = Set(["hello", "world"]) + let clp = try ParseCLP().setProtectedFields(fields, for: user) + XCTAssertEqual(try clp.getProtectedFields(user), fields) + + let newField = Set(["new"]) + let clp2 = try clp.addProtectedFields(newField, for: user) + XCTAssertEqual(try clp2.getProtectedFields(user), fields.union(newField)) + + let clp3 = try clp2.removeProtectedFields(newField, for: user) + XCTAssertEqual(try clp3.getProtectedFields(user), fields) + + let clp4 = try ParseCLP().addProtectedFields(newField, for: user) + XCTAssertEqual(try clp4.getProtectedFields(user), newField) + } + + func testProtectedFieldsPointer() throws { + let pointer = try user.toPointer() + let fields = Set(["hello", "world"]) + let clp = ParseCLP().setProtectedFields(fields, for: pointer) + XCTAssertEqual(clp.getProtectedFields(pointer), fields) + + let newField = Set(["new"]) + let clp2 = clp.addProtectedFields(newField, for: pointer) + XCTAssertEqual(clp2.getProtectedFields(pointer), fields.union(newField)) + + let clp3 = clp2.removeProtectedFields(newField, for: pointer) + XCTAssertEqual(clp3.getProtectedFields(pointer), fields) + + let clp4 = ParseCLP().addProtectedFields(newField, for: pointer) + XCTAssertEqual(clp4.getProtectedFields(pointer), newField) + } + + func testProtectedFieldsRole() throws { + let role = try Role(name: "hello") + let fields = Set(["hello", "world"]) + let clp = try ParseCLP().setProtectedFields(fields, for: role) + XCTAssertEqual(try clp.getProtectedFields(role), fields) + + let newField = Set(["new"]) + let clp2 = try clp.addProtectedFields(newField, for: role) + XCTAssertEqual(try clp2.getProtectedFields(role), fields.union(newField)) + + let clp3 = try clp2.removeProtectedFields(newField, for: role) + XCTAssertEqual(try clp3.getProtectedFields(role), fields) + + let clp4 = try ParseCLP().addProtectedFields(newField, for: role) + XCTAssertEqual(try clp4.getProtectedFields(role), newField) + } + + func testProtectedFieldsEncode() throws { + let role = try Role(name: "hello") + let fields = Set(["world"]) + let clp = try ParseCLP().setProtectedFields(fields, for: role) + XCTAssertEqual(clp.description, "ParseCLP ({\"protectedFields\":{\"role:hello\":[\"world\"]}})") + } + + func testPublicAccess() throws { + let clp = ParseCLP().setAccessPublic(true, on: .create) + XCTAssertTrue(clp.hasAccessPublic(.create)) + + let clp2 = clp.setAccessPublic(false, on: .create) + XCTAssertFalse(clp2.hasAccessPublic(.create)) + } + + func testRequiresAuthenticationAccess() throws { + let clp = ParseCLP().setAccessRequiresAuthentication(true, on: .create) + XCTAssertTrue(clp.hasAccessRequiresAuthentication(.create)) + + let clp2 = clp.setAccessRequiresAuthentication(false, on: .create) + XCTAssertFalse(clp2.hasAccessRequiresAuthentication(.create)) + } + + func testAccessUser() throws { + let clp = try ParseCLP().setAccess(true, on: .create, for: user) + XCTAssertTrue(try clp.hasAccess(.create, for: user)) + + let clp2 = try clp.setAccess(false, on: .create, for: user) + XCTAssertFalse(try clp2.hasAccess(.create, for: user)) + } + + func testAccessPointer() throws { + let user = try user.toPointer() + let clp = ParseCLP().setAccess(true, on: .create, for: user) + XCTAssertTrue(clp.hasAccess(.create, for: user)) + + let clp2 = clp.setAccess(false, on: .create, for: user) + XCTAssertFalse(clp2.hasAccess(.create, for: user)) + } + + func testAccessRole() throws { + let role = try Role(name: "hello") + let clp = try ParseCLP().setAccess(true, on: .create, for: role) + XCTAssertTrue(try clp.hasAccess(.create, for: role)) + + let clp2 = try clp.setAccess(false, on: .create, for: role) + XCTAssertFalse(try clp2.hasAccess(.create, for: user)) + } + + func testAccessEncode() throws { + let clp = ParseCLP().setAccess(true, on: .create, for: objectId) + XCTAssertEqual(clp.description, "ParseCLP ({\"create\":{\"\(objectId)\":true}})") + } + + func testWriteAccessPublicSet() throws { + let clp = ParseCLP().setWriteAccessPublic(true) + XCTAssertNil(clp.get?[ParseCLP.Access.publicScope.rawValue]) + XCTAssertNil(clp.find?[ParseCLP.Access.publicScope.rawValue]) + XCTAssertEqual(clp.create?[ParseCLP.Access.publicScope.rawValue], true) + XCTAssertEqual(clp.update?[ParseCLP.Access.publicScope.rawValue], true) + XCTAssertEqual(clp.delete?[ParseCLP.Access.publicScope.rawValue], true) + XCTAssertNil(clp.count?[ParseCLP.Access.publicScope.rawValue]) + XCTAssertNil(clp.addField?[ParseCLP.Access.publicScope.rawValue]) + + let clp2 = ParseCLP().setWriteAccessPublic(true, canAddField: true) + XCTAssertNil(clp2.get?[ParseCLP.Access.publicScope.rawValue]) + XCTAssertNil(clp2.find?[ParseCLP.Access.publicScope.rawValue]) + XCTAssertEqual(clp2.create?[ParseCLP.Access.publicScope.rawValue], true) + XCTAssertEqual(clp2.update?[ParseCLP.Access.publicScope.rawValue], true) + XCTAssertEqual(clp2.delete?[ParseCLP.Access.publicScope.rawValue], true) + XCTAssertNil(clp2.count?[ParseCLP.Access.publicScope.rawValue]) + XCTAssertEqual(clp2.addField?[ParseCLP.Access.publicScope.rawValue], true) + + let clp3 = clp.setWriteAccessPublic(false) + XCTAssertNil(clp3.get?[ParseCLP.Access.publicScope.rawValue]) + XCTAssertNil(clp3.find?[ParseCLP.Access.publicScope.rawValue]) + XCTAssertNil(clp3.create?[ParseCLP.Access.publicScope.rawValue]) + XCTAssertNil(clp3.update?[ParseCLP.Access.publicScope.rawValue]) + XCTAssertNil(clp3.delete?[ParseCLP.Access.publicScope.rawValue]) + XCTAssertNil(clp3.count?[ParseCLP.Access.publicScope.rawValue]) + XCTAssertNil(clp3.addField?[ParseCLP.Access.publicScope.rawValue]) + + let clp4 = clp2.setWriteAccessPublic(false) + XCTAssertNil(clp4.get?[ParseCLP.Access.publicScope.rawValue]) + XCTAssertNil(clp4.find?[ParseCLP.Access.publicScope.rawValue]) + XCTAssertNil(clp4.create?[ParseCLP.Access.publicScope.rawValue]) + XCTAssertNil(clp4.update?[ParseCLP.Access.publicScope.rawValue]) + XCTAssertNil(clp4.delete?[ParseCLP.Access.publicScope.rawValue]) + XCTAssertNil(clp4.count?[ParseCLP.Access.publicScope.rawValue]) + XCTAssertNil(clp4.addField?[ParseCLP.Access.publicScope.rawValue]) + } + + func testWriteAccessPublicSetEncode() throws { + let clp = ParseCLP().setWriteAccessPublic(true, canAddField: true) + // swiftlint:disable:next line_length + XCTAssertEqual(clp.description, "ParseCLP ({\"addField\":{\"*\":true},\"create\":{\"*\":true},\"delete\":{\"*\":true},\"update\":{\"*\":true}})") + } + + func testWriteAccessRequiresAuthenticationSet() throws { + let clp = ParseCLP().setWriteAccessRequiresAuthentication(true) + XCTAssertNil(clp.get?[ParseCLP.Access.requiresAuthentication.rawValue]) + XCTAssertNil(clp.find?[ParseCLP.Access.requiresAuthentication.rawValue]) + XCTAssertEqual(clp.create?[ParseCLP.Access.requiresAuthentication.rawValue], true) + XCTAssertEqual(clp.update?[ParseCLP.Access.requiresAuthentication.rawValue], true) + XCTAssertEqual(clp.delete?[ParseCLP.Access.requiresAuthentication.rawValue], true) + XCTAssertNil(clp.count?[ParseCLP.Access.requiresAuthentication.rawValue]) + XCTAssertNil(clp.addField?[ParseCLP.Access.requiresAuthentication.rawValue]) + + let clp2 = ParseCLP().setWriteAccessRequiresAuthentication(true, canAddField: true) + XCTAssertNil(clp2.get?[ParseCLP.Access.requiresAuthentication.rawValue]) + XCTAssertNil(clp2.find?[ParseCLP.Access.requiresAuthentication.rawValue]) + XCTAssertEqual(clp2.create?[ParseCLP.Access.requiresAuthentication.rawValue], true) + XCTAssertEqual(clp2.update?[ParseCLP.Access.requiresAuthentication.rawValue], true) + XCTAssertEqual(clp2.delete?[ParseCLP.Access.requiresAuthentication.rawValue], true) + XCTAssertNil(clp2.count?[ParseCLP.Access.requiresAuthentication.rawValue]) + XCTAssertEqual(clp2.addField?[ParseCLP.Access.requiresAuthentication.rawValue], true) + + let clp3 = clp.setWriteAccessRequiresAuthentication(false) + XCTAssertNil(clp3.get?[ParseCLP.Access.requiresAuthentication.rawValue]) + XCTAssertNil(clp3.find?[ParseCLP.Access.requiresAuthentication.rawValue]) + XCTAssertNil(clp3.create?[ParseCLP.Access.requiresAuthentication.rawValue]) + XCTAssertNil(clp3.update?[ParseCLP.Access.requiresAuthentication.rawValue]) + XCTAssertNil(clp3.delete?[ParseCLP.Access.requiresAuthentication.rawValue]) + XCTAssertNil(clp3.count?[ParseCLP.Access.requiresAuthentication.rawValue]) + XCTAssertNil(clp3.addField?[ParseCLP.Access.requiresAuthentication.rawValue]) + + let clp4 = clp2.setWriteAccessRequiresAuthentication(false) + XCTAssertNil(clp4.get?[ParseCLP.Access.requiresAuthentication.rawValue]) + XCTAssertNil(clp4.find?[ParseCLP.Access.requiresAuthentication.rawValue]) + XCTAssertNil(clp4.create?[ParseCLP.Access.requiresAuthentication.rawValue]) + XCTAssertNil(clp4.update?[ParseCLP.Access.requiresAuthentication.rawValue]) + XCTAssertNil(clp4.delete?[ParseCLP.Access.requiresAuthentication.rawValue]) + XCTAssertNil(clp4.count?[ParseCLP.Access.requiresAuthentication.rawValue]) + XCTAssertNil(clp4.addField?[ParseCLP.Access.requiresAuthentication.rawValue]) + } + + func testWriteAccessRequiresAuthenticationSetEncode() throws { + let clp = ParseCLP().setWriteAccessRequiresAuthentication(true, canAddField: true) + // swiftlint:disable:next line_length + XCTAssertEqual(clp.description, "ParseCLP ({\"addField\":{\"requiresAuthentication\":true},\"create\":{\"requiresAuthentication\":true},\"delete\":{\"requiresAuthentication\":true},\"update\":{\"requiresAuthentication\":true}})") + } + + func testWriteAccessObjectIdSet() throws { + let clp = ParseCLP().setWriteAccess(true, for: objectId) + XCTAssertNil(clp.get?[objectId]) + XCTAssertNil(clp.find?[objectId]) + XCTAssertEqual(clp.create?[objectId], true) + XCTAssertEqual(clp.update?[objectId], true) + XCTAssertEqual(clp.delete?[objectId], true) + XCTAssertNil(clp.count?[objectId]) + XCTAssertNil(clp.addField?[objectId]) + + let clp2 = ParseCLP().setWriteAccess(true, for: objectId, canAddField: true) + XCTAssertNil(clp2.get?[objectId]) + XCTAssertNil(clp2.find?[objectId]) + XCTAssertEqual(clp2.create?[objectId], true) + XCTAssertEqual(clp2.update?[objectId], true) + XCTAssertEqual(clp2.delete?[objectId], true) + XCTAssertNil(clp2.count?[objectId]) + XCTAssertEqual(clp2.addField?[objectId], true) + + let clp3 = clp.setWriteAccess(false, for: objectId) + XCTAssertNil(clp3.get?[objectId]) + XCTAssertNil(clp3.find?[objectId]) + XCTAssertNil(clp3.create?[objectId]) + XCTAssertNil(clp3.update?[objectId]) + XCTAssertNil(clp3.delete?[objectId]) + XCTAssertNil(clp3.count?[objectId]) + XCTAssertNil(clp3.addField?[objectId]) + + let clp4 = clp2.setWriteAccess(false, for: objectId) + XCTAssertNil(clp4.get?[objectId]) + XCTAssertNil(clp4.find?[objectId]) + XCTAssertNil(clp4.create?[objectId]) + XCTAssertNil(clp4.update?[objectId]) + XCTAssertNil(clp4.delete?[objectId]) + XCTAssertNil(clp4.count?[objectId]) + XCTAssertNil(clp4.addField?[objectId]) + } + + func testWriteAccessObjectIdSetEncode() throws { + let clp = ParseCLP().setWriteAccess(true, for: objectId, canAddField: true) + // swiftlint:disable:next line_length + XCTAssertEqual(clp.description, "ParseCLP ({\"addField\":{\"\(objectId)\":true},\"create\":{\"\(objectId)\":true},\"delete\":{\"\(objectId)\":true},\"update\":{\"\(objectId)\":true}})") + } + + func testWriteAccessUserSet() throws { + let clp = try ParseCLP().setWriteAccess(true, for: user) + XCTAssertNil(clp.get?[objectId]) + XCTAssertNil(clp.find?[objectId]) + XCTAssertEqual(clp.create?[objectId], true) + XCTAssertEqual(clp.update?[objectId], true) + XCTAssertEqual(clp.delete?[objectId], true) + XCTAssertNil(clp.count?[objectId]) + XCTAssertNil(clp.addField?[objectId]) + + let clp2 = try ParseCLP().setWriteAccess(true, for: user, canAddField: true) + XCTAssertNil(clp2.get?[objectId]) + XCTAssertNil(clp2.find?[objectId]) + XCTAssertEqual(clp2.create?[objectId], true) + XCTAssertEqual(clp2.update?[objectId], true) + XCTAssertEqual(clp2.delete?[objectId], true) + XCTAssertNil(clp2.count?[objectId]) + XCTAssertEqual(clp2.addField?[objectId], true) + + let clp3 = try clp.setWriteAccess(false, for: user) + XCTAssertNil(clp3.get?[objectId]) + XCTAssertNil(clp3.find?[objectId]) + XCTAssertNil(clp3.create?[objectId]) + XCTAssertNil(clp3.update?[objectId]) + XCTAssertNil(clp3.delete?[objectId]) + XCTAssertNil(clp3.count?[objectId]) + XCTAssertNil(clp3.addField?[objectId]) + + let clp4 = try clp2.setWriteAccess(false, for: user) + XCTAssertNil(clp4.get?[objectId]) + XCTAssertNil(clp4.find?[objectId]) + XCTAssertNil(clp4.create?[objectId]) + XCTAssertNil(clp4.update?[objectId]) + XCTAssertNil(clp4.delete?[objectId]) + XCTAssertNil(clp4.count?[objectId]) + XCTAssertNil(clp4.addField?[objectId]) + } + + func testWriteAccessUserSetEncode() throws { + let clp = try ParseCLP().setWriteAccess(true, for: user, canAddField: true) + // swiftlint:disable:next line_length + XCTAssertEqual(clp.description, "ParseCLP ({\"addField\":{\"\(objectId)\":true},\"create\":{\"\(objectId)\":true},\"delete\":{\"\(objectId)\":true},\"update\":{\"\(objectId)\":true}})") + } + + func testWriteAccessPointerSet() throws { + let clp = ParseCLP().setWriteAccess(true, for: try user.toPointer()) + XCTAssertNil(clp.get?[objectId]) + XCTAssertNil(clp.find?[objectId]) + XCTAssertEqual(clp.create?[objectId], true) + XCTAssertEqual(clp.update?[objectId], true) + XCTAssertEqual(clp.delete?[objectId], true) + XCTAssertNil(clp.count?[objectId]) + XCTAssertNil(clp.addField?[objectId]) + + let clp2 = ParseCLP().setWriteAccess(true, + for: try user.toPointer(), + canAddField: true) + XCTAssertNil(clp2.get?[objectId]) + XCTAssertNil(clp2.find?[objectId]) + XCTAssertEqual(clp2.create?[objectId], true) + XCTAssertEqual(clp2.update?[objectId], true) + XCTAssertEqual(clp2.delete?[objectId], true) + XCTAssertNil(clp2.count?[objectId]) + XCTAssertEqual(clp2.addField?[objectId], true) + + let clp3 = clp.setWriteAccess(false, for: try user.toPointer()) + XCTAssertNil(clp3.get?[objectId]) + XCTAssertNil(clp3.find?[objectId]) + XCTAssertNil(clp3.create?[objectId]) + XCTAssertNil(clp3.update?[objectId]) + XCTAssertNil(clp3.delete?[objectId]) + XCTAssertNil(clp3.count?[objectId]) + XCTAssertNil(clp3.addField?[objectId]) + + let clp4 = clp2.setWriteAccess(false, for: try user.toPointer()) + XCTAssertNil(clp4.get?[objectId]) + XCTAssertNil(clp4.find?[objectId]) + XCTAssertNil(clp4.create?[objectId]) + XCTAssertNil(clp4.update?[objectId]) + XCTAssertNil(clp4.delete?[objectId]) + XCTAssertNil(clp4.count?[objectId]) + XCTAssertNil(clp4.addField?[objectId]) + } + + func testWriteAccessPointerSetEncode() throws { + let clp = ParseCLP().setWriteAccess(true, + for: try user.toPointer(), + canAddField: true) + // swiftlint:disable:next line_length + XCTAssertEqual(clp.description, "ParseCLP ({\"addField\":{\"\(objectId)\":true},\"create\":{\"\(objectId)\":true},\"delete\":{\"\(objectId)\":true},\"update\":{\"\(objectId)\":true}})") + } + + func testWriteAccessRoleSet() throws { + let name = "hello" + let role = try Role(name: name) + let roleName = try ParseACL.getRoleAccessName(role) + let clp = try ParseCLP().setWriteAccess(true, for: role) + XCTAssertNil(clp.get?[roleName]) + XCTAssertNil(clp.find?[roleName]) + XCTAssertEqual(clp.create?[roleName], true) + XCTAssertEqual(clp.update?[roleName], true) + XCTAssertEqual(clp.delete?[roleName], true) + XCTAssertNil(clp.count?[roleName]) + XCTAssertNil(clp.addField?[roleName]) + + let clp2 = try ParseCLP().setWriteAccess(true, + for: role, + canAddField: true) + XCTAssertNil(clp2.get?[roleName]) + XCTAssertNil(clp2.find?[roleName]) + XCTAssertEqual(clp2.create?[roleName], true) + XCTAssertEqual(clp2.update?[roleName], true) + XCTAssertEqual(clp2.delete?[roleName], true) + XCTAssertNil(clp2.count?[roleName]) + XCTAssertEqual(clp2.addField?[roleName], true) + + let clp3 = try clp.setWriteAccess(false, for: role) + XCTAssertNil(clp3.get?[roleName]) + XCTAssertNil(clp3.find?[roleName]) + XCTAssertNil(clp3.create?[roleName]) + XCTAssertNil(clp3.update?[roleName]) + XCTAssertNil(clp3.delete?[roleName]) + XCTAssertNil(clp3.count?[roleName]) + XCTAssertNil(clp3.addField?[roleName]) + + let clp4 = try clp2.setWriteAccess(false, for: role) + XCTAssertNil(clp4.get?[roleName]) + XCTAssertNil(clp4.find?[roleName]) + XCTAssertNil(clp4.create?[roleName]) + XCTAssertNil(clp4.update?[roleName]) + XCTAssertNil(clp4.delete?[roleName]) + XCTAssertNil(clp4.count?[roleName]) + XCTAssertNil(clp4.addField?[roleName]) + } + + func testWriteAccessRoleSetEncode() throws { + let name = "hello" + let role = try Role(name: name) + let roleName = try ParseACL.getRoleAccessName(role) + let clp = try ParseCLP().setWriteAccess(true, + for: role, + canAddField: true) + // swiftlint:disable:next line_length + XCTAssertEqual(clp.description, "ParseCLP ({\"addField\":{\"\(roleName)\":true},\"create\":{\"\(roleName)\":true},\"delete\":{\"\(roleName)\":true},\"update\":{\"\(roleName)\":true}})") + } + + func testWriteAccessPublicHas() throws { + let clp = ParseCLP().setWriteAccessPublic(true) + XCTAssertTrue(clp.hasWriteAccessPublic()) + XCTAssertFalse(clp.hasWriteAccessRequiresAuthentication()) + + let clp2 = ParseCLP().setWriteAccessPublic(false) + XCTAssertFalse(clp2.hasWriteAccessPublic()) + XCTAssertFalse(clp2.hasWriteAccessRequiresAuthentication()) + + let clp3 = clp.setWriteAccessPublic(false) + XCTAssertFalse(clp3.hasWriteAccessPublic()) + XCTAssertFalse(clp3.hasWriteAccessRequiresAuthentication()) + } + + func testWriteAccessRequiresAuthenticationHas() throws { + let clp = ParseCLP().setWriteAccessRequiresAuthentication(true) + XCTAssertTrue(clp.hasWriteAccessRequiresAuthentication()) + XCTAssertFalse(clp.hasWriteAccessPublic()) + + let clp2 = ParseCLP().setWriteAccessRequiresAuthentication(false) + XCTAssertFalse(clp2.hasWriteAccessRequiresAuthentication()) + XCTAssertFalse(clp2.hasWriteAccessPublic()) + + let clp3 = clp.setWriteAccessRequiresAuthentication(false) + XCTAssertFalse(clp3.hasWriteAccessRequiresAuthentication()) + XCTAssertFalse(clp3.hasWriteAccessPublic()) + } + + func testWriteAccessObjectIdHas() throws { + let clp = ParseCLP().setWriteAccess(true, for: objectId) + XCTAssertFalse(clp.hasReadAccess(objectId)) + XCTAssertTrue(clp.hasWriteAccess(objectId)) + XCTAssertFalse(clp.hasWriteAccess(objectId, check: true)) + + let clp2 = ParseCLP().setWriteAccess(false, for: objectId) + XCTAssertFalse(clp2.hasReadAccess(objectId)) + XCTAssertFalse(clp2.hasWriteAccess(objectId)) + + let clp3 = clp.setWriteAccess(false, for: objectId) + XCTAssertFalse(clp3.hasReadAccess(objectId)) + XCTAssertFalse(clp3.hasWriteAccess(objectId)) + + let clp4 = ParseCLP().setWriteAccess(true, for: objectId, canAddField: true) + XCTAssertFalse(clp4.hasReadAccess(objectId)) + XCTAssertTrue(clp4.hasWriteAccess(objectId, check: true)) + } + + func testWriteAccessUserHas() throws { + let clp = try ParseCLP().setWriteAccess(true, for: user) + XCTAssertFalse(try clp.hasReadAccess(user)) + XCTAssertTrue(try clp.hasWriteAccess(user)) + XCTAssertFalse(try clp.hasWriteAccess(user, checkAddField: true)) + + let clp2 = try ParseCLP().setWriteAccess(false, for: user) + XCTAssertFalse(try clp2.hasReadAccess(user)) + XCTAssertFalse(try clp2.hasWriteAccess(user)) + + let clp3 = try clp.setWriteAccess(false, for: user) + XCTAssertFalse(try clp3.hasReadAccess(user)) + XCTAssertFalse(try clp3.hasWriteAccess(user)) + + let clp4 = try ParseCLP().setWriteAccess(true, for: user, canAddField: true) + XCTAssertFalse(try clp4.hasReadAccess(user)) + XCTAssertTrue(try clp4.hasWriteAccess(user, checkAddField: true)) + } + + func testWriteAccessPointerHas() throws { + let clp = ParseCLP().setWriteAccess(true, for: try user.toPointer()) + XCTAssertFalse(clp.hasReadAccess(try user.toPointer())) + XCTAssertTrue(clp.hasWriteAccess(try user.toPointer())) + XCTAssertFalse(clp.hasWriteAccess(try user.toPointer(), checkAddField: true)) + + let clp2 = ParseCLP().setWriteAccess(false, for: try user.toPointer()) + XCTAssertFalse(clp2.hasReadAccess(try user.toPointer())) + XCTAssertFalse(clp2.hasWriteAccess(try user.toPointer())) + + let clp3 = clp.setWriteAccess(false, for: try user.toPointer()) + XCTAssertFalse(clp3.hasReadAccess(try user.toPointer())) + XCTAssertFalse(clp3.hasWriteAccess(try user.toPointer())) + + let clp4 = ParseCLP().setWriteAccess(true, for: try user.toPointer(), canAddField: true) + XCTAssertFalse(clp4.hasReadAccess(try user.toPointer())) + XCTAssertTrue(clp4.hasWriteAccess(try user.toPointer(), checkAddField: true)) + } + + func testWriteAccessRoleHas() throws { + let name = "hello" + let role = try Role(name: name) + let clp = try ParseCLP().setWriteAccess(true, for: role) + XCTAssertFalse(try clp.hasReadAccess(role)) + XCTAssertTrue(try clp.hasWriteAccess(role)) + XCTAssertFalse(try clp.hasWriteAccess(role, checkAddField: true)) + + let clp2 = try ParseCLP().setWriteAccess(false, for: role) + XCTAssertFalse(try clp2.hasReadAccess(role)) + XCTAssertFalse(try clp2.hasWriteAccess(role)) + + let clp3 = try clp.setWriteAccess(false, for: role) + XCTAssertFalse(try clp3.hasReadAccess(role)) + XCTAssertFalse(try clp3.hasWriteAccess(role)) + + let clp4 = try ParseCLP().setWriteAccess(true, for: role, canAddField: true) + XCTAssertFalse(try clp4.hasReadAccess(role)) + XCTAssertTrue(try clp4.hasWriteAccess(role, checkAddField: true)) + } + + func testReadAccessPublicSet() throws { + let clp = ParseCLP().setReadAccessPublic(true) + XCTAssertEqual(clp.get?[ParseCLP.Access.publicScope.rawValue], true) + XCTAssertEqual(clp.find?[ParseCLP.Access.publicScope.rawValue], true) + XCTAssertNil(clp.create?[ParseCLP.Access.publicScope.rawValue]) + XCTAssertNil(clp.update?[ParseCLP.Access.publicScope.rawValue]) + XCTAssertNil(clp.delete?[ParseCLP.Access.publicScope.rawValue]) + XCTAssertEqual(clp.count?[ParseCLP.Access.publicScope.rawValue], true) + XCTAssertNil(clp.addField?[ParseCLP.Access.publicScope.rawValue]) + + let clp2 = ParseCLP().setReadAccessPublic(false) + XCTAssertNil(clp2.get?[ParseCLP.Access.publicScope.rawValue]) + XCTAssertNil(clp2.find?[ParseCLP.Access.publicScope.rawValue]) + XCTAssertNil(clp2.create?[ParseCLP.Access.publicScope.rawValue]) + XCTAssertNil(clp2.update?[ParseCLP.Access.publicScope.rawValue]) + XCTAssertNil(clp2.delete?[ParseCLP.Access.publicScope.rawValue]) + XCTAssertNil(clp2.count?[ParseCLP.Access.publicScope.rawValue]) + XCTAssertNil(clp2.addField?[ParseCLP.Access.publicScope.rawValue]) + + let clp3 = clp.setReadAccessPublic(false) + XCTAssertNil(clp3.get?[ParseCLP.Access.publicScope.rawValue]) + XCTAssertNil(clp3.find?[ParseCLP.Access.publicScope.rawValue]) + XCTAssertNil(clp3.create?[ParseCLP.Access.publicScope.rawValue]) + XCTAssertNil(clp3.update?[ParseCLP.Access.publicScope.rawValue]) + XCTAssertNil(clp3.delete?[ParseCLP.Access.publicScope.rawValue]) + XCTAssertNil(clp3.count?[ParseCLP.Access.publicScope.rawValue]) + XCTAssertNil(clp3.addField?[ParseCLP.Access.publicScope.rawValue]) + } + + func testReadAccessPublicSetEncode() throws { + let clp = ParseCLP().setReadAccessPublic(true) + // swiftlint:disable:next line_length + XCTAssertEqual(clp.description, "ParseCLP ({\"count\":{\"*\":true},\"find\":{\"*\":true},\"get\":{\"*\":true}})") + } + + func testReadAccessRequiresAuthenticationSet() throws { + let clp = ParseCLP().setReadAccessRequiresAuthentication(true) + XCTAssertEqual(clp.get?[ParseCLP.Access.requiresAuthentication.rawValue], true) + XCTAssertEqual(clp.find?[ParseCLP.Access.requiresAuthentication.rawValue], true) + XCTAssertNil(clp.create?[ParseCLP.Access.requiresAuthentication.rawValue]) + XCTAssertNil(clp.update?[ParseCLP.Access.requiresAuthentication.rawValue]) + XCTAssertNil(clp.delete?[ParseCLP.Access.requiresAuthentication.rawValue]) + XCTAssertEqual(clp.count?[ParseCLP.Access.requiresAuthentication.rawValue], true) + XCTAssertNil(clp.addField?[ParseCLP.Access.requiresAuthentication.rawValue]) + + let clp2 = ParseCLP().setReadAccessRequiresAuthentication(false) + XCTAssertNil(clp2.get?[ParseCLP.Access.requiresAuthentication.rawValue]) + XCTAssertNil(clp2.find?[ParseCLP.Access.requiresAuthentication.rawValue]) + XCTAssertNil(clp2.create?[ParseCLP.Access.requiresAuthentication.rawValue]) + XCTAssertNil(clp2.update?[ParseCLP.Access.requiresAuthentication.rawValue]) + XCTAssertNil(clp2.delete?[ParseCLP.Access.requiresAuthentication.rawValue]) + XCTAssertNil(clp2.count?[ParseCLP.Access.requiresAuthentication.rawValue]) + XCTAssertNil(clp2.addField?[ParseCLP.Access.requiresAuthentication.rawValue]) + + let clp3 = clp.setReadAccessRequiresAuthentication(false) + XCTAssertNil(clp3.get?[ParseCLP.Access.requiresAuthentication.rawValue]) + XCTAssertNil(clp3.find?[ParseCLP.Access.requiresAuthentication.rawValue]) + XCTAssertNil(clp3.create?[ParseCLP.Access.requiresAuthentication.rawValue]) + XCTAssertNil(clp3.update?[ParseCLP.Access.requiresAuthentication.rawValue]) + XCTAssertNil(clp3.delete?[ParseCLP.Access.requiresAuthentication.rawValue]) + XCTAssertNil(clp3.count?[ParseCLP.Access.requiresAuthentication.rawValue]) + XCTAssertNil(clp3.addField?[ParseCLP.Access.requiresAuthentication.rawValue]) + } + + func testReadAccessRequiresAuthenticationSetEncode() throws { + let clp = ParseCLP().setReadAccessRequiresAuthentication(true) + // swiftlint:disable:next line_length + XCTAssertEqual(clp.description, "ParseCLP ({\"count\":{\"requiresAuthentication\":true},\"find\":{\"requiresAuthentication\":true},\"get\":{\"requiresAuthentication\":true}})") + } + + func testReadAccessObjectIdSet() throws { + let clp = ParseCLP().setReadAccess(true, for: objectId) + XCTAssertEqual(clp.get?[objectId], true) + XCTAssertEqual(clp.find?[objectId], true) + XCTAssertNil(clp.create?[objectId]) + XCTAssertNil(clp.update?[objectId]) + XCTAssertNil(clp.delete?[objectId]) + XCTAssertEqual(clp.count?[objectId], true) + XCTAssertNil(clp.addField?[objectId]) + + let clp2 = ParseCLP().setReadAccess(false, for: objectId) + XCTAssertNil(clp2.get?[objectId]) + XCTAssertNil(clp2.find?[objectId]) + XCTAssertNil(clp2.create?[objectId]) + XCTAssertNil(clp2.update?[objectId]) + XCTAssertNil(clp2.delete?[objectId]) + XCTAssertNil(clp2.count?[objectId]) + XCTAssertNil(clp2.addField?[objectId]) + + let clp3 = clp.setReadAccess(false, for: objectId) + XCTAssertNil(clp3.get?[objectId]) + XCTAssertNil(clp3.find?[objectId]) + XCTAssertNil(clp3.create?[objectId]) + XCTAssertNil(clp3.update?[objectId]) + XCTAssertNil(clp3.delete?[objectId]) + XCTAssertNil(clp3.count?[objectId]) + XCTAssertNil(clp3.addField?[objectId]) + } + + func testReadAccessObjectIdSetEncode() throws { + let clp = ParseCLP().setReadAccess(true, for: objectId) + // swiftlint:disable:next line_length + XCTAssertEqual(clp.description, "ParseCLP ({\"count\":{\"\(objectId)\":true},\"find\":{\"\(objectId)\":true},\"get\":{\"\(objectId)\":true}})") + } + + func testReadAccessUserSet() throws { + let clp = try ParseCLP().setReadAccess(true, for: user) + XCTAssertEqual(clp.get?[objectId], true) + XCTAssertEqual(clp.find?[objectId], true) + XCTAssertNil(clp.create?[objectId]) + XCTAssertNil(clp.update?[objectId]) + XCTAssertNil(clp.delete?[objectId]) + XCTAssertEqual(clp.count?[objectId], true) + XCTAssertNil(clp.addField?[objectId]) + + let clp2 = try ParseCLP().setReadAccess(false, for: user) + XCTAssertNil(clp2.get?[objectId]) + XCTAssertNil(clp2.find?[objectId]) + XCTAssertNil(clp2.create?[objectId]) + XCTAssertNil(clp2.update?[objectId]) + XCTAssertNil(clp2.delete?[objectId]) + XCTAssertNil(clp2.count?[objectId]) + XCTAssertNil(clp2.addField?[objectId]) + + let clp3 = try clp.setReadAccess(false, for: user) + XCTAssertNil(clp3.get?[objectId]) + XCTAssertNil(clp3.find?[objectId]) + XCTAssertNil(clp3.create?[objectId]) + XCTAssertNil(clp3.update?[objectId]) + XCTAssertNil(clp3.delete?[objectId]) + XCTAssertNil(clp3.count?[objectId]) + XCTAssertNil(clp3.addField?[objectId]) + } + + func testReadAccessUserSetEncode() throws { + let clp = try ParseCLP().setReadAccess(true, for: user) + // swiftlint:disable:next line_length + XCTAssertEqual(clp.description, "ParseCLP ({\"count\":{\"\(objectId)\":true},\"find\":{\"\(objectId)\":true},\"get\":{\"\(objectId)\":true}})") + } + + func testReadAccessPointerSet() throws { + let clp = ParseCLP().setReadAccess(true, for: try user.toPointer()) + XCTAssertEqual(clp.get?[objectId], true) + XCTAssertEqual(clp.find?[objectId], true) + XCTAssertNil(clp.create?[objectId]) + XCTAssertNil(clp.update?[objectId]) + XCTAssertNil(clp.delete?[objectId]) + XCTAssertEqual(clp.count?[objectId], true) + XCTAssertNil(clp.addField?[objectId]) + + let clp2 = ParseCLP().setReadAccess(false, for: try user.toPointer()) + XCTAssertNil(clp2.get?[objectId]) + XCTAssertNil(clp2.find?[objectId]) + XCTAssertNil(clp2.create?[objectId]) + XCTAssertNil(clp2.update?[objectId]) + XCTAssertNil(clp2.delete?[objectId]) + XCTAssertNil(clp2.count?[objectId]) + XCTAssertNil(clp2.addField?[objectId]) + + let clp3 = clp.setReadAccess(false, for: try user.toPointer()) + XCTAssertNil(clp3.get?[objectId]) + XCTAssertNil(clp3.find?[objectId]) + XCTAssertNil(clp3.create?[objectId]) + XCTAssertNil(clp3.update?[objectId]) + XCTAssertNil(clp3.delete?[objectId]) + XCTAssertNil(clp3.count?[objectId]) + XCTAssertNil(clp3.addField?[objectId]) + } + + func testReadAccessPointerSetEncode() throws { + let clp = ParseCLP().setReadAccess(true, + for: try user.toPointer()) + // swiftlint:disable:next line_length + XCTAssertEqual(clp.description, "ParseCLP ({\"count\":{\"\(objectId)\":true},\"find\":{\"\(objectId)\":true},\"get\":{\"\(objectId)\":true}})") + } + + func testReadAccessRoleSet() throws { + let name = "hello" + let role = try Role(name: name) + let roleName = try ParseACL.getRoleAccessName(role) + let clp = try ParseCLP().setReadAccess(true, for: role) + XCTAssertEqual(clp.get?[roleName], true) + XCTAssertEqual(clp.find?[roleName], true) + XCTAssertNil(clp.create?[roleName]) + XCTAssertNil(clp.update?[roleName]) + XCTAssertNil(clp.delete?[roleName]) + XCTAssertEqual(clp.count?[roleName], true) + XCTAssertNil(clp.addField?[roleName]) + + let clp2 = try ParseCLP().setReadAccess(false, for: role) + XCTAssertNil(clp2.get?[roleName]) + XCTAssertNil(clp2.find?[roleName]) + XCTAssertNil(clp2.create?[roleName]) + XCTAssertNil(clp2.update?[roleName]) + XCTAssertNil(clp2.delete?[roleName]) + XCTAssertNil(clp2.count?[roleName]) + XCTAssertNil(clp2.addField?[roleName]) + + let clp3 = try clp.setReadAccess(false, for: role) + XCTAssertNil(clp3.get?[roleName]) + XCTAssertNil(clp3.find?[roleName]) + XCTAssertNil(clp3.create?[roleName]) + XCTAssertNil(clp3.update?[roleName]) + XCTAssertNil(clp3.delete?[roleName]) + XCTAssertNil(clp3.count?[roleName]) + XCTAssertNil(clp3.addField?[roleName]) + } + + func testReadAccessRoleSetEncode() throws { + let name = "hello" + let role = try Role(name: name) + let roleName = try ParseACL.getRoleAccessName(role) + let clp = try ParseCLP().setReadAccess(true, + for: role) + // swiftlint:disable:next line_length + XCTAssertEqual(clp.description, "ParseCLP ({\"count\":{\"\(roleName)\":true},\"find\":{\"\(roleName)\":true},\"get\":{\"\(roleName)\":true}})") + } + + func testReadAccessPublicHas() throws { + let clp = ParseCLP().setReadAccessPublic(true) + XCTAssertTrue(clp.hasReadAccessPublic()) + XCTAssertFalse(clp.hasReadAccessRequiresAuthentication()) + + let clp2 = ParseCLP().setReadAccessPublic(false) + XCTAssertFalse(clp2.hasReadAccessPublic()) + XCTAssertFalse(clp2.hasReadAccessRequiresAuthentication()) + + let clp3 = clp.setReadAccessPublic(false) + XCTAssertFalse(clp3.hasReadAccessPublic()) + XCTAssertFalse(clp3.hasReadAccessRequiresAuthentication()) + } + + func testReadAccessRequiresAuthenticationHas() throws { + let clp = ParseCLP().setReadAccessRequiresAuthentication(true) + XCTAssertTrue(clp.hasReadAccessRequiresAuthentication()) + XCTAssertFalse(clp.hasReadAccessPublic()) + + let clp2 = ParseCLP().setReadAccessRequiresAuthentication(false) + XCTAssertFalse(clp2.hasReadAccessRequiresAuthentication()) + XCTAssertFalse(clp2.hasReadAccessPublic()) + + let clp3 = clp.setReadAccessRequiresAuthentication(false) + XCTAssertFalse(clp3.hasReadAccessRequiresAuthentication()) + XCTAssertFalse(clp3.hasReadAccessPublic()) + } + + func testReadAccessObjectIdHas() throws { + let clp = ParseCLP().setReadAccess(true, for: objectId) + XCTAssertTrue(clp.hasReadAccess(objectId)) + XCTAssertFalse(clp.hasWriteAccess(objectId)) + + let clp2 = ParseCLP().setReadAccess(false, for: objectId) + XCTAssertFalse(clp2.hasReadAccess(objectId)) + XCTAssertFalse(clp2.hasWriteAccess(objectId)) + + let clp3 = clp.setReadAccess(false, for: objectId) + XCTAssertFalse(clp3.hasReadAccess(objectId)) + XCTAssertFalse(clp3.hasWriteAccess(objectId)) + } + + func testReadAccessUserHas() throws { + let clp = try ParseCLP().setReadAccess(true, for: user) + XCTAssertTrue(try clp.hasReadAccess(user)) + XCTAssertFalse(try clp.hasWriteAccess(user)) + + let clp2 = try ParseCLP().setReadAccess(false, for: user) + XCTAssertFalse(try clp2.hasReadAccess(user)) + XCTAssertFalse(try clp2.hasWriteAccess(user)) + + let clp3 = try clp.setReadAccess(false, for: user) + XCTAssertFalse(try clp3.hasReadAccess(user)) + XCTAssertFalse(try clp3.hasWriteAccess(user)) + } + + func testReadAccessPointerHas() throws { + let clp = ParseCLP().setReadAccess(true, for: try user.toPointer()) + XCTAssertTrue(clp.hasReadAccess(try user.toPointer())) + XCTAssertFalse(clp.hasWriteAccess(try user.toPointer())) + + let clp2 = ParseCLP().setReadAccess(false, for: try user.toPointer()) + XCTAssertFalse(clp2.hasReadAccess(try user.toPointer())) + XCTAssertFalse(clp2.hasWriteAccess(try user.toPointer())) + + let clp3 = clp.setReadAccess(false, for: try user.toPointer()) + XCTAssertFalse(clp3.hasReadAccess(try user.toPointer())) + XCTAssertFalse(clp3.hasWriteAccess(try user.toPointer())) + } + + func testReadAccessRoleHas() throws { + let name = "hello" + let role = try Role(name: name) + let clp = try ParseCLP().setReadAccess(true, for: role) + XCTAssertTrue(try clp.hasReadAccess(role)) + XCTAssertFalse(try clp.hasWriteAccess(role)) + + let clp2 = try ParseCLP().setReadAccess(false, for: role) + XCTAssertFalse(try clp2.hasReadAccess(role)) + XCTAssertFalse(try clp2.hasWriteAccess(role)) + + let clp3 = try clp.setReadAccess(false, for: role) + XCTAssertFalse(try clp3.hasReadAccess(role)) + XCTAssertFalse(try clp3.hasWriteAccess(role)) + } +} diff --git a/Tests/ParseSwiftTests/ParseObjectTests.swift b/Tests/ParseSwiftTests/ParseObjectTests.swift index f36d14d56..dfffb7097 100644 --- a/Tests/ParseSwiftTests/ParseObjectTests.swift +++ b/Tests/ParseSwiftTests/ParseObjectTests.swift @@ -156,7 +156,7 @@ class ParseObjectTests: XCTestCase { // swiftlint:disable:this type_body_length - parameter lhs: first object to compare - parameter rhs: second object to compare - - returns: Returns a `true` if the other object has the same `objectId` or `false` if unsuccessful. + - returns: Returns a **true** if the other object has the same `objectId` or **false** if unsuccessful. */ public static func == (lhs: ParseObjectTests.GameScoreClass, rhs: ParseObjectTests.GameScoreClass) -> Bool { @@ -213,7 +213,7 @@ class ParseObjectTests: XCTestCase { // swiftlint:disable:this type_body_length - parameter lhs: first object to compare - parameter rhs: second object to compare - - returns: Returns a `true` if the other object has the same `objectId` or `false` if unsuccessful. + - returns: Returns a **true** if the other object has the same `objectId` or **false** if unsuccessful. */ public static func == (lhs: ParseObjectTests.GameClass, rhs: ParseObjectTests.GameClass) -> Bool { lhs.hasSameObjectId(as: rhs) diff --git a/Tests/ParseSwiftTests/ParseSchemaAsyncTests.swift b/Tests/ParseSwiftTests/ParseSchemaAsyncTests.swift new file mode 100644 index 000000000..23e482a83 --- /dev/null +++ b/Tests/ParseSwiftTests/ParseSchemaAsyncTests.swift @@ -0,0 +1,344 @@ +// +// ParseSchemaAsyncTests.swift +// ParseSwift +// +// Created by Corey Baker on 5/29/22. +// Copyright © 2022 Parse Community. All rights reserved. +// + +#if compiler(>=5.5.2) && canImport(_Concurrency) +import Foundation +#if canImport(FoundationNetworking) +import FoundationNetworking +#endif +import XCTest +@testable import ParseSwift + +class ParseSchemaAsyncTests: XCTestCase { // swiftlint:disable:this type_body_length + struct GameScore: ParseObject, ParseQueryScorable { + //: These are required by ParseObject + var objectId: String? + var createdAt: Date? + var updatedAt: Date? + var ACL: ParseACL? + var score: Double? + var originalData: Data? + + //: Your own properties + var points: Int + var isCounts: Bool? + + //: a custom initializer + init() { + self.points = 5 + } + init(points: Int) { + self.points = points + } + } + + override func setUpWithError() throws { + try super.setUpWithError() + guard let url = URL(string: "http://localhost:1337/1") else { + XCTFail("Should create valid URL") + return + } + ParseSwift.initialize(applicationId: "applicationId", + clientKey: "clientKey", + masterKey: "masterKey", + serverURL: url, + testing: true) + } + + override func tearDownWithError() throws { + try super.tearDownWithError() + MockURLProtocol.removeAll() + #if !os(Linux) && !os(Android) && !os(Windows) + try KeychainStore.shared.deleteAll() + #endif + try ParseStorage.shared.deleteAll() + } + + func createDummySchema() -> ParseSchema { + ParseSchema() + .addField("a", + type: .string, + options: ParseFieldOptions(required: false, defauleValue: nil)) + .addField("b", + type: .number, + options: ParseFieldOptions(required: false, defauleValue: 2)) + .deleteField("c") + .addIndex("hello", field: "world", index: "yolo") + } + + @MainActor + func testCreate() async throws { + + let schema = createDummySchema() + + var serverResponse = schema + serverResponse.indexes = schema.pendingIndexes + serverResponse.pendingIndexes.removeAll() + + MockURLProtocol.mockRequests { _ in + do { + let encoded = try ParseCoding.jsonEncoder().encode(serverResponse) + return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) + } catch { + return nil + } + } + + let saved = try await schema.create() + XCTAssertEqual(saved.fields, serverResponse.fields) + XCTAssertEqual(saved.indexes, serverResponse.indexes) + XCTAssertEqual(saved.classLevelPermissions, serverResponse.classLevelPermissions) + XCTAssertEqual(saved.className, serverResponse.className) + XCTAssertTrue(saved.pendingIndexes.isEmpty) + } + + @MainActor + func testCreateError() async throws { + + let schema = createDummySchema() + + let parseError = ParseError(code: .invalidSchemaOperation, + message: "Problem with schema") + + MockURLProtocol.mockRequests { _ in + do { + let encoded = try ParseCoding.jsonEncoder().encode(parseError) + return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) + } catch { + return nil + } + } + + do { + _ = try await schema.create() + XCTFail("Should have thrown error") + } catch { + XCTAssertTrue(error.equalsTo(.invalidSchemaOperation)) + } + } + + @MainActor + func testUpdate() async throws { + + let schema = createDummySchema() + + var serverResponse = schema + serverResponse.indexes = schema.pendingIndexes + serverResponse.pendingIndexes.removeAll() + + MockURLProtocol.mockRequests { _ in + do { + let encoded = try ParseCoding.jsonEncoder().encode(serverResponse) + return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) + } catch { + return nil + } + } + + let saved = try await schema.update() + XCTAssertEqual(saved.fields, serverResponse.fields) + XCTAssertEqual(saved.indexes, serverResponse.indexes) + XCTAssertEqual(saved.classLevelPermissions, serverResponse.classLevelPermissions) + XCTAssertEqual(saved.className, serverResponse.className) + XCTAssertTrue(saved.pendingIndexes.isEmpty) + } + + @MainActor + func testUpdateOldIndexes() async throws { + + var schema = createDummySchema() + schema.indexes = [ + "meta": ["world": "peace"], + "stop": ["being": "greedy"] + ] + schema.pendingIndexes.removeAll() + + let serverResponse = schema + + MockURLProtocol.mockRequests { _ in + do { + let encoded = try ParseCoding.jsonEncoder().encode(serverResponse) + return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) + } catch { + return nil + } + } + + let saved = try await schema.update() + XCTAssertEqual(saved.fields, serverResponse.fields) + XCTAssertEqual(saved.indexes, serverResponse.indexes) + XCTAssertEqual(saved.classLevelPermissions, serverResponse.classLevelPermissions) + XCTAssertEqual(saved.className, serverResponse.className) + XCTAssertTrue(saved.pendingIndexes.isEmpty) + } + + @MainActor + func testUpdateError() async throws { + + let schema = createDummySchema() + + let parseError = ParseError(code: .invalidSchemaOperation, + message: "Problem with schema") + + MockURLProtocol.mockRequests { _ in + do { + let encoded = try ParseCoding.jsonEncoder().encode(parseError) + return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) + } catch { + return nil + } + } + + do { + _ = try await schema.update() + XCTFail("Should have thrown error") + } catch { + XCTAssertTrue(error.equalsTo(.invalidSchemaOperation)) + } + } + + @MainActor + func testFetch() async throws { + + let schema = createDummySchema() + + var serverResponse = schema + serverResponse.indexes = schema.pendingIndexes + serverResponse.pendingIndexes.removeAll() + + MockURLProtocol.mockRequests { _ in + do { + let encoded = try ParseCoding.jsonEncoder().encode(serverResponse) + return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) + } catch { + return nil + } + } + + let saved = try await schema.fetch() + XCTAssertEqual(saved.fields, serverResponse.fields) + XCTAssertEqual(saved.indexes, serverResponse.indexes) + XCTAssertEqual(saved.classLevelPermissions, serverResponse.classLevelPermissions) + XCTAssertEqual(saved.className, serverResponse.className) + XCTAssertTrue(saved.pendingIndexes.isEmpty) + } + + @MainActor + func testFetchError() async throws { + + let schema = createDummySchema() + + let parseError = ParseError(code: .invalidSchemaOperation, + message: "Problem with schema") + + MockURLProtocol.mockRequests { _ in + do { + let encoded = try ParseCoding.jsonEncoder().encode(parseError) + return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) + } catch { + return nil + } + } + + do { + _ = try await schema.fetch() + XCTFail("Should have thrown error") + } catch { + XCTAssertTrue(error.equalsTo(.invalidSchemaOperation)) + } + } + + @MainActor + func testPurge() async throws { + + let schema = createDummySchema() + + let serverResponse = NoBody() + + MockURLProtocol.mockRequests { _ in + do { + let encoded = try ParseCoding.jsonEncoder().encode(serverResponse) + return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) + } catch { + return nil + } + } + + try await schema.purge() + } + + @MainActor + func testPurgeError() async throws { + + let schema = createDummySchema() + + let parseError = ParseError(code: .invalidSchemaOperation, + message: "Problem with schema") + + MockURLProtocol.mockRequests { _ in + do { + let encoded = try ParseCoding.jsonEncoder().encode(parseError) + return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) + } catch { + return nil + } + } + + do { + _ = try await schema.purge() + XCTFail("Should have thrown error") + } catch { + XCTAssertTrue(error.equalsTo(.invalidSchemaOperation)) + } + } + + @MainActor + func testDelete() async throws { + + let schema = createDummySchema() + + let serverResponse = NoBody() + + MockURLProtocol.mockRequests { _ in + do { + let encoded = try ParseCoding.jsonEncoder().encode(serverResponse) + return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) + } catch { + return nil + } + } + + try await schema.delete() + } + + @MainActor + func testDeleteError() async throws { + + let schema = createDummySchema() + + let parseError = ParseError(code: .invalidSchemaOperation, + message: "Problem with schema") + + MockURLProtocol.mockRequests { _ in + do { + let encoded = try ParseCoding.jsonEncoder().encode(parseError) + return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) + } catch { + return nil + } + } + + do { + _ = try await schema.delete() + XCTFail("Should have thrown error") + } catch { + XCTAssertTrue(error.equalsTo(.invalidSchemaOperation)) + } + } +} +#endif diff --git a/Tests/ParseSwiftTests/ParseSchemaCombineTests.swift b/Tests/ParseSwiftTests/ParseSchemaCombineTests.swift new file mode 100644 index 000000000..7bdfa4d31 --- /dev/null +++ b/Tests/ParseSwiftTests/ParseSchemaCombineTests.swift @@ -0,0 +1,418 @@ +// +// ParseSchemaCombineTests.swift +// ParseSwift +// +// Created by Corey Baker on 5/29/22. +// Copyright © 2022 Parse Community. All rights reserved. +// + +#if canImport(Combine) + +import Foundation +import XCTest +import Combine +@testable import ParseSwift + +class ParseSchemaCombineTests: XCTestCase { // swiftlint:disable:this type_body_length + struct GameScore: ParseObject, ParseQueryScorable { + //: These are required by ParseObject + var objectId: String? + var createdAt: Date? + var updatedAt: Date? + var ACL: ParseACL? + var score: Double? + var originalData: Data? + + //: Your own properties + var points: Int + var isCounts: Bool? + + //: a custom initializer + init() { + self.points = 5 + } + init(points: Int) { + self.points = points + } + } + + override func setUpWithError() throws { + try super.setUpWithError() + guard let url = URL(string: "http://localhost:1337/1") else { + XCTFail("Should create valid URL") + return + } + ParseSwift.initialize(applicationId: "applicationId", + clientKey: "clientKey", + masterKey: "masterKey", + serverURL: url, + testing: true) + } + + override func tearDownWithError() throws { + try super.tearDownWithError() + MockURLProtocol.removeAll() + #if !os(Linux) && !os(Android) && !os(Windows) + try KeychainStore.shared.deleteAll() + #endif + try ParseStorage.shared.deleteAll() + } + + func createDummySchema() -> ParseSchema { + ParseSchema() + .addField("a", + type: .string, + options: ParseFieldOptions(required: false, defauleValue: nil)) + .addField("b", + type: .number, + options: ParseFieldOptions(required: false, defauleValue: 2)) + .deleteField("c") + .addIndex("hello", field: "world", index: "yolo") + } + + func testCreate() throws { + var subscriptions = Set() + let schema = createDummySchema() + + var serverResponse = schema + serverResponse.indexes = schema.pendingIndexes + serverResponse.pendingIndexes.removeAll() + + MockURLProtocol.mockRequests { _ in + do { + let encoded = try ParseCoding.jsonEncoder().encode(serverResponse) + return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) + } catch { + return nil + } + } + + let expectation1 = XCTestExpectation(description: "Save schema") + let publisher = schema.createPublisher() + .sink(receiveCompletion: { result in + + if case let .failure(error) = result { + XCTFail(error.localizedDescription) + } + expectation1.fulfill() + + }, receiveValue: { saved in + + XCTAssertEqual(saved.fields, serverResponse.fields) + XCTAssertEqual(saved.indexes, serverResponse.indexes) + XCTAssertEqual(saved.classLevelPermissions, serverResponse.classLevelPermissions) + XCTAssertEqual(saved.className, serverResponse.className) + XCTAssertTrue(saved.pendingIndexes.isEmpty) + }) + publisher.store(in: &subscriptions) + wait(for: [expectation1], timeout: 20.0) + } + + func testCreateError() throws { + var subscriptions = Set() + let schema = createDummySchema() + + let parseError = ParseError(code: .invalidSchemaOperation, + message: "Problem with schema") + + MockURLProtocol.mockRequests { _ in + do { + let encoded = try ParseCoding.jsonEncoder().encode(parseError) + return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) + } catch { + return nil + } + } + + let expectation1 = XCTestExpectation(description: "Create schema") + let publisher = schema.createPublisher() + .sink(receiveCompletion: { result in + + if case .finished = result { + XCTFail("Should have thrown ParseError") + } + expectation1.fulfill() + + }, receiveValue: { _ in + XCTFail("Should have thrown ParseError") + expectation1.fulfill() + }) + publisher.store(in: &subscriptions) + wait(for: [expectation1], timeout: 20.0) + } + + func testUpdate() throws { + var subscriptions = Set() + let schema = createDummySchema() + + var serverResponse = schema + serverResponse.indexes = schema.pendingIndexes + serverResponse.pendingIndexes.removeAll() + + MockURLProtocol.mockRequests { _ in + do { + let encoded = try ParseCoding.jsonEncoder().encode(serverResponse) + return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) + } catch { + return nil + } + } + + let expectation1 = XCTestExpectation(description: "Update schema") + let publisher = schema.updatePublisher() + .sink(receiveCompletion: { result in + + if case let .failure(error) = result { + XCTFail(error.localizedDescription) + } + expectation1.fulfill() + + }, receiveValue: { saved in + + XCTAssertEqual(saved.fields, serverResponse.fields) + XCTAssertEqual(saved.indexes, serverResponse.indexes) + XCTAssertEqual(saved.classLevelPermissions, serverResponse.classLevelPermissions) + XCTAssertEqual(saved.className, serverResponse.className) + XCTAssertTrue(saved.pendingIndexes.isEmpty) + }) + publisher.store(in: &subscriptions) + wait(for: [expectation1], timeout: 20.0) + } + + func testUpdateError() throws { + var subscriptions = Set() + let schema = createDummySchema() + + let parseError = ParseError(code: .invalidSchemaOperation, + message: "Problem with schema") + + MockURLProtocol.mockRequests { _ in + do { + let encoded = try ParseCoding.jsonEncoder().encode(parseError) + return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) + } catch { + return nil + } + } + + let expectation1 = XCTestExpectation(description: "Update schema") + let publisher = schema.updatePublisher() + .sink(receiveCompletion: { result in + + if case .finished = result { + XCTFail("Should have thrown ParseError") + } + expectation1.fulfill() + + }, receiveValue: { _ in + XCTFail("Should have thrown ParseError") + expectation1.fulfill() + }) + publisher.store(in: &subscriptions) + wait(for: [expectation1], timeout: 20.0) + } + + func testFetch() throws { + var subscriptions = Set() + let schema = createDummySchema() + + var serverResponse = schema + serverResponse.indexes = schema.pendingIndexes + serverResponse.pendingIndexes.removeAll() + + MockURLProtocol.mockRequests { _ in + do { + let encoded = try ParseCoding.jsonEncoder().encode(serverResponse) + return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) + } catch { + return nil + } + } + + let expectation1 = XCTestExpectation(description: "Fetch schema") + let publisher = schema.fetchPublisher() + .sink(receiveCompletion: { result in + + if case let .failure(error) = result { + XCTFail(error.localizedDescription) + } + expectation1.fulfill() + + }, receiveValue: { saved in + + XCTAssertEqual(saved.fields, serverResponse.fields) + XCTAssertEqual(saved.indexes, serverResponse.indexes) + XCTAssertEqual(saved.classLevelPermissions, serverResponse.classLevelPermissions) + XCTAssertEqual(saved.className, serverResponse.className) + XCTAssertTrue(saved.pendingIndexes.isEmpty) + }) + publisher.store(in: &subscriptions) + wait(for: [expectation1], timeout: 20.0) + } + + func testFetchError() throws { + var subscriptions = Set() + let schema = createDummySchema() + + let parseError = ParseError(code: .invalidSchemaOperation, + message: "Problem with schema") + + MockURLProtocol.mockRequests { _ in + do { + let encoded = try ParseCoding.jsonEncoder().encode(parseError) + return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) + } catch { + return nil + } + } + + let expectation1 = XCTestExpectation(description: "Fetch schema") + let publisher = schema.fetchPublisher() + .sink(receiveCompletion: { result in + + if case .finished = result { + XCTFail("Should have thrown ParseError") + } + expectation1.fulfill() + + }, receiveValue: { _ in + XCTFail("Should have thrown ParseError") + expectation1.fulfill() + }) + publisher.store(in: &subscriptions) + wait(for: [expectation1], timeout: 20.0) + } + + func testPurge() throws { + var subscriptions = Set() + let schema = createDummySchema() + + var serverResponse = schema + serverResponse.indexes = schema.pendingIndexes + serverResponse.pendingIndexes.removeAll() + + MockURLProtocol.mockRequests { _ in + do { + let encoded = try ParseCoding.jsonEncoder().encode(serverResponse) + return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) + } catch { + return nil + } + } + + let expectation1 = XCTestExpectation(description: "Purge schema") + let publisher = schema.purgePublisher() + .sink(receiveCompletion: { result in + + if case let .failure(error) = result { + XCTFail(error.localizedDescription) + } + expectation1.fulfill() + + }, receiveValue: { _ in + + }) + publisher.store(in: &subscriptions) + wait(for: [expectation1], timeout: 20.0) + } + + func testPurgeError() throws { + var subscriptions = Set() + let schema = createDummySchema() + + let parseError = ParseError(code: .invalidSchemaOperation, + message: "Problem with schema") + + MockURLProtocol.mockRequests { _ in + do { + let encoded = try ParseCoding.jsonEncoder().encode(parseError) + return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) + } catch { + return nil + } + } + + let expectation1 = XCTestExpectation(description: "Purge schema") + let publisher = schema.purgePublisher() + .sink(receiveCompletion: { result in + + if case .finished = result { + XCTFail("Should have thrown ParseError") + } + expectation1.fulfill() + + }, receiveValue: { _ in + XCTFail("Should have thrown ParseError") + expectation1.fulfill() + }) + publisher.store(in: &subscriptions) + wait(for: [expectation1], timeout: 20.0) + } + + func testDelete() throws { + var subscriptions = Set() + let schema = createDummySchema() + + var serverResponse = schema + serverResponse.indexes = schema.pendingIndexes + serverResponse.pendingIndexes.removeAll() + + MockURLProtocol.mockRequests { _ in + do { + let encoded = try ParseCoding.jsonEncoder().encode(serverResponse) + return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) + } catch { + return nil + } + } + + let expectation1 = XCTestExpectation(description: "Delete schema") + let publisher = schema.deletePublisher() + .sink(receiveCompletion: { result in + + if case let .failure(error) = result { + XCTFail(error.localizedDescription) + } + expectation1.fulfill() + + }, receiveValue: { _ in + + }) + publisher.store(in: &subscriptions) + wait(for: [expectation1], timeout: 20.0) + } + + func testDeleteError() throws { + var subscriptions = Set() + let schema = createDummySchema() + + let parseError = ParseError(code: .invalidSchemaOperation, + message: "Problem with schema") + + MockURLProtocol.mockRequests { _ in + do { + let encoded = try ParseCoding.jsonEncoder().encode(parseError) + return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) + } catch { + return nil + } + } + + let expectation1 = XCTestExpectation(description: "Delete schema") + let publisher = schema.deletePublisher() + .sink(receiveCompletion: { result in + + if case .finished = result { + XCTFail("Should have thrown ParseError") + } + expectation1.fulfill() + + }, receiveValue: { _ in + XCTFail("Should have thrown ParseError") + expectation1.fulfill() + }) + publisher.store(in: &subscriptions) + wait(for: [expectation1], timeout: 20.0) + } +} +#endif diff --git a/Tests/ParseSwiftTests/ParseSchemaTests.swift b/Tests/ParseSwiftTests/ParseSchemaTests.swift new file mode 100644 index 000000000..096916b30 --- /dev/null +++ b/Tests/ParseSwiftTests/ParseSchemaTests.swift @@ -0,0 +1,303 @@ +// +// ParseSchemaTests.swift +// ParseSwift +// +// Created by Corey Baker on 5/29/22. +// Copyright © 2022 Parse Community. All rights reserved. +// + +import Foundation +import XCTest +@testable import ParseSwift + +class ParseSchemaTests: XCTestCase { // swiftlint:disable:this type_body_length + struct GameScore: ParseObject { + //: These are required by ParseObject + var objectId: String? + var createdAt: Date? + var updatedAt: Date? + var ACL: ParseACL? + var score: Double? + var originalData: Data? + + //: Your own properties + var points: Int? + + //: a custom initializer + init() { } + init(points: Int) { + self.points = points + } + } + + struct GameScore2: ParseObject { + //: These are required by ParseObject + var objectId: String? + var createdAt: Date? + var updatedAt: Date? + var ACL: ParseACL? + var score: Double? + var originalData: Data? + + //: Your own properties + var points: Int? + + //: a custom initializer + init() { } + init(points: Int) { + self.points = points + } + init(objectId: String, points: Int) { + self.objectId = objectId + self.points = points + } + } + + override func setUpWithError() throws { + try super.setUpWithError() + guard let url = URL(string: "http://localhost:1337/1") else { + XCTFail("Should create valid URL") + return + } + ParseSwift.initialize(applicationId: "applicationId", + clientKey: "clientKey", + masterKey: "masterKey", + serverURL: url, + testing: true) + } + + override func tearDownWithError() throws { + try super.tearDownWithError() + MockURLProtocol.removeAll() + #if !os(Linux) && !os(Android) && !os(Windows) + try KeychainStore.shared.deleteAll() + #endif + try ParseStorage.shared.deleteAll() + } + + func createDummySchema() -> ParseSchema { + let fields = Set(["world"]) + let clp = ParseCLP() + .setPointerFields(fields, on: .create) + .setWriteAccessPublic(true, canAddField: true) + + let schema = ParseSchema(classLevelPermissions: clp) + .addField("a", + type: .string, + options: ParseFieldOptions(required: false, defauleValue: nil)) + .addField("b", + type: .number, + options: ParseFieldOptions(required: false, defauleValue: 2)) + .deleteField("c") + .addIndex("hello", field: "world", index: "yolo") + return schema + } + + func testInitializer() throws { + let clp = ParseCLP(requiresAuthentication: true, publicAccess: true) + let schema = ParseSchema(classLevelPermissions: clp) + XCTAssertEqual(schema.className, GameScore.className) + XCTAssertEqual(ParseSchema.className, GameScore.className) + XCTAssertEqual(schema.classLevelPermissions, clp) + } + + func testParseFieldOptionsEncode() { + let options = ParseFieldOptions(required: false, defauleValue: 2) + XCTAssertEqual(options.description, + "ParseFieldOptions ({\"defaultValue\":2,\"required\":false})") + } + + func testSchemaEncode() throws { + let schema = createDummySchema() + // swiftlint:disable:next line_length + let expected = "ParseSchema ({\"classLevelPermissions\":{\"addField\":{\"*\":true},\"create\":{\"*\":true,\"pointerFields\":[\"world\"]},\"delete\":{\"*\":true},\"update\":{\"*\":true}},\"className\":\"GameScore\",\"fields\":{\"a\":{\"required\":false,\"type\":\"String\"},\"b\":{\"defaultValue\":2,\"required\":false,\"type\":\"Number\"},\"c\":{\"__op\":\"Delete\"}}})" + XCTAssertEqual(schema.description, expected) + } + + func testAddField() throws { + let options = try ParseFieldOptions(required: false, defauleValue: nil) + let schema = try ParseSchema() + .addField("a", + type: .string, + options: ParseFieldOptions(required: false, defauleValue: nil)) + .addField("b", + type: .pointer, + options: options) + .addField("c", + type: .date, + options: ParseFieldOptions(required: false, defauleValue: nil)) + .addField("d", + type: .acl, + options: ParseFieldOptions(required: false, defauleValue: nil)) + .addField("e", + type: .array, + options: ParseFieldOptions(required: false, defauleValue: nil)) + .addField("f", + type: .bytes, + options: ParseFieldOptions(required: false, defauleValue: nil)) + .addField("g", + type: .object, + options: ParseFieldOptions(required: false, defauleValue: nil)) + .addField("h", + type: .file, + options: ParseFieldOptions(required: false, defauleValue: nil)) + .addField("i", + type: .geoPoint, + options: ParseFieldOptions(required: false, defauleValue: nil)) + .addField("j", + type: .relation, + options: options) + .addField("k", + type: .polygon, + options: ParseFieldOptions(required: false, defauleValue: nil)) + .addField("l", + type: .boolean, + options: ParseFieldOptions(required: false, defauleValue: nil)) + .addField("m", + type: .number, + options: ParseFieldOptions(required: false, defauleValue: nil)) + XCTAssertEqual(schema.fields?["a"]?.type, .string) + XCTAssertEqual(schema.fields?["b"]?.type, .pointer) + XCTAssertEqual(schema.fields?["c"]?.type, .date) + XCTAssertEqual(schema.fields?["d"]?.type, .acl) + XCTAssertEqual(schema.fields?["e"]?.type, .array) + XCTAssertEqual(schema.fields?["f"]?.type, .bytes) + XCTAssertEqual(schema.fields?["g"]?.type, .object) + XCTAssertEqual(schema.fields?["h"]?.type, .file) + XCTAssertEqual(schema.fields?["i"]?.type, .geoPoint) + XCTAssertEqual(schema.fields?["j"]?.type, .relation) + XCTAssertEqual(schema.fields?["k"]?.type, .polygon) + XCTAssertEqual(schema.fields?["l"]?.type, .boolean) + XCTAssertEqual(schema.fields?["m"]?.type, .number) + } + + func testAddFieldWrongOptionsError() throws { + let options = try ParseFieldOptions(required: false, defauleValue: nil) + XCTAssertThrowsError(try ParseSchema() + .addField("b", + type: .string, + options: options)) + } + + func testGetFields() throws { + let schema = ParseSchema() + .addField("a", + type: .string, + options: ParseFieldOptions(required: false, defauleValue: nil)) + .addField("b", + type: .number, + options: ParseFieldOptions(required: false, defauleValue: 2)) + let fields = schema.getFields() + XCTAssertEqual(fields["a"], "ParseField ({\"required\":false,\"type\":\"String\"})") + XCTAssertEqual(fields["b"], "ParseField ({\"defaultValue\":2,\"required\":false,\"type\":\"Number\"})") + } + + func testAddPointer() throws { + let gameScore2 = GameScore2(objectId: "yolo", points: 12) + let options = try ParseFieldOptions(required: false, defauleValue: gameScore2) + let schema = ParseSchema() + .addPointer("a", + options: options) + XCTAssertEqual(schema.fields?["a"]?.type, .pointer) + XCTAssertEqual(schema.fields?["a"]?.targetClass, gameScore2.className) + guard let value = schema.fields?["a"]?.defaultValue?.value as? GameScore2 else { + XCTFail("Should have unwrapped") + return + } + XCTAssertEqual(try value.toPointer(), try gameScore2.toPointer()) + + let schema2 = schema.addPointer("b", + options: options) + XCTAssertEqual(schema2.fields?["b"]?.type, .pointer) + XCTAssertEqual(schema2.fields?["b"]?.targetClass, gameScore2.className) + guard let value2 = schema2.fields?["b"]?.defaultValue?.value as? GameScore2 else { + XCTFail("Should have unwrapped") + return + } + XCTAssertEqual(try value2.toPointer(), try gameScore2.toPointer()) + } + + func testAddRelation() throws { + let options = try ParseFieldOptions(required: false, defauleValue: nil) + let schema = ParseSchema() + .addRelation("a", + options: options) + XCTAssertEqual(schema.fields?["a"]?.type, .relation) + XCTAssertEqual(schema.fields?["a"]?.targetClass, GameScore2.className) + + let schema2 = schema.addRelation("b", + options: options) + XCTAssertEqual(schema2.fields?["b"]?.type, .relation) + XCTAssertEqual(schema2.fields?["b"]?.targetClass, GameScore2.className) + } + + func testDeleteField() throws { + var schema = ParseSchema() + .deleteField("a") + let delete = ParseField(operation: .delete) + XCTAssertEqual(schema.fields?["a"], delete) + + schema = schema.deleteField("b") + XCTAssertEqual(schema.fields?["a"], delete) + XCTAssertEqual(schema.fields?["b"], delete) + } + + func testAddIndexes() throws { + let schema = ParseSchema() + .addIndex("hello", field: "world", index: "yolo") + .addIndex("next", field: "place", index: "home") + let indexes = schema.getIndexes() + guard let firstIndex = indexes["hello"]?["world"], + let secondIndex = indexes["next"]?["place"] else { + XCTFail("Should have unwrapped") + return + } + XCTAssertEqual(firstIndex, "yolo") + XCTAssertEqual(secondIndex, "home") + + let alreadyStoredIndexes: [String: [String: AnyCodable]] = [ + "meta": ["world": "peace"], + "stop": ["being": "greedy"] + ] + var schema2 = ParseSchema() + schema2.indexes = alreadyStoredIndexes + let indexes2 = schema2.getIndexes() + guard let firstIndex2 = indexes2["meta"]?["world"], + let secondIndex2 = indexes2["stop"]?["being"] else { + XCTFail("Should have unwrapped") + return + } + XCTAssertEqual(firstIndex2, "peace") + XCTAssertEqual(secondIndex2, "greedy") + + schema2 = schema2 + .addIndex("hello", field: "world", index: "yolo") + .addIndex("next", field: "place", index: "home") + let indexes3 = schema2.getIndexes() + guard let firstIndex3 = indexes3["meta"]?["world"], + let secondIndex3 = indexes3["stop"]?["being"], + let thirdIndex3 = indexes["hello"]?["world"], + let fourthIndex3 = indexes["next"]?["place"] else { + XCTFail("Should have unwrapped") + return + } + XCTAssertEqual(firstIndex3, "peace") + XCTAssertEqual(secondIndex3, "greedy") + XCTAssertEqual(thirdIndex3, "yolo") + XCTAssertEqual(fourthIndex3, "home") + } + + func testDeleteIndexes() throws { + let schema = ParseSchema() + .deleteIndex("hello") + .addIndex("next", field: "place", index: "home") + let indexes = schema.getIndexes() + guard let firstIndex = indexes["hello"]?["__op"], + let secondIndex = indexes["next"]?["place"] else { + XCTFail("Should have unwrapped") + return + } + XCTAssertEqual(firstIndex, "Delete") + XCTAssertEqual(secondIndex, "home") + } +}