diff --git a/.codecov.yml b/.codecov.yml index 1cc5f54a3..a47cd9bf1 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -6,10 +6,10 @@ coverage: status: patch: default: - target: auto + target: 79 changes: false project: default: - target: 87 + target: 89 comment: require_changes: true diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f5c0c14ee..69e3e2141 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,7 +6,7 @@ on: branches: '*' env: CI_XCODE_VER: '/Applications/Xcode_12.5.1.app/Contents/Developer' - CI_XCODE_13: '/Applications/Xcode_13.4.app/Contents/Developer' + CI_XCODE_13: '/Applications/Xcode_13.4.1.app/Contents/Developer' jobs: xcode-test-ios: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index aaa0c470d..cd432526d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -3,7 +3,7 @@ on: release: types: [published] env: - CI_XCODE_13: '/Applications/Xcode_13.4.app/Contents/Developer' + CI_XCODE_13: '/Applications/Xcode_13.4.1.app/Contents/Developer' jobs: cocoapods: diff --git a/CHANGELOG.md b/CHANGELOG.md index c2f776feb..1617ccb48 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ ### main +__New features__ +- Enable query caching by using GET instead of POST. GET is now used by default. To switch back to POST, set usingPostForQuery = true when initializing the SDK which will automatically disable all query caching ([#386](https://github.com/parse-community/Parse-Swift/pull/386)), thanks to [Corey Baker](https://github.com/cbaker6). + __Improvements__ - Added discardableResult to allow developers to choose whether or not certain functions should return a result ([#385](https://github.com/parse-community/Parse-Swift/pull/385)), thanks to [Damian Van de Kauter](https://github.com/vdkdamian). diff --git a/ParseSwift.xcodeproj/project.pbxproj b/ParseSwift.xcodeproj/project.pbxproj index a63834ab2..54601fe20 100644 --- a/ParseSwift.xcodeproj/project.pbxproj +++ b/ParseSwift.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 48; + objectVersion = 54; objects = { /* Begin PBXBuildFile section */ @@ -66,6 +66,9 @@ 7004C24D25B69207005E0AD9 /* ParseRoleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7004C22D25B69077005E0AD9 /* ParseRoleTests.swift */; }; 7004C25725B6920A005E0AD9 /* ParseRoleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7004C22D25B69077005E0AD9 /* ParseRoleTests.swift */; }; 7004C26125B6920B005E0AD9 /* ParseRoleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7004C22D25B69077005E0AD9 /* ParseRoleTests.swift */; }; + 700AFE03289C3508006C1CD9 /* ParseQueryCacheTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 700AFE02289C3508006C1CD9 /* ParseQueryCacheTests.swift */; }; + 700AFE04289C3508006C1CD9 /* ParseQueryCacheTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 700AFE02289C3508006C1CD9 /* ParseQueryCacheTests.swift */; }; + 700AFE05289C3508006C1CD9 /* ParseQueryCacheTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 700AFE02289C3508006C1CD9 /* ParseQueryCacheTests.swift */; }; 70110D52250680140091CC1D /* ParseConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70110D51250680140091CC1D /* ParseConstants.swift */; }; 70110D53250680140091CC1D /* ParseConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70110D51250680140091CC1D /* ParseConstants.swift */; }; 70110D54250680140091CC1D /* ParseConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70110D51250680140091CC1D /* ParseConstants.swift */; }; @@ -519,6 +522,10 @@ 70BDA2B4250536FF00FC2237 /* ParseInstallation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70BDA2B2250536FF00FC2237 /* ParseInstallation.swift */; }; 70BDA2B5250536FF00FC2237 /* ParseInstallation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70BDA2B2250536FF00FC2237 /* ParseInstallation.swift */; }; 70BDA2B6250536FF00FC2237 /* ParseInstallation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70BDA2B2250536FF00FC2237 /* ParseInstallation.swift */; }; + 70C048C12880D7E600401689 /* Dictionary.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70C048C02880D7E500401689 /* Dictionary.swift */; }; + 70C048C22880D7E600401689 /* Dictionary.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70C048C02880D7E500401689 /* Dictionary.swift */; }; + 70C048C32880D7E600401689 /* Dictionary.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70C048C02880D7E500401689 /* Dictionary.swift */; }; + 70C048C42880D7E600401689 /* Dictionary.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70C048C02880D7E500401689 /* Dictionary.swift */; }; 70C167AF27304EE4009F4E30 /* Pointer+combine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70C167AE27304EE4009F4E30 /* Pointer+combine.swift */; }; 70C167B027304EE4009F4E30 /* Pointer+combine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70C167AE27304EE4009F4E30 /* Pointer+combine.swift */; }; 70C167B127304EE4009F4E30 /* Pointer+combine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70C167AE27304EE4009F4E30 /* Pointer+combine.swift */; }; @@ -1134,6 +1141,7 @@ 7003972925A3B0130052CB31 /* ParseURLSessionDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParseURLSessionDelegate.swift; sourceTree = ""; }; 7004C21F25B63C7A005E0AD9 /* ParseRelation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParseRelation.swift; sourceTree = ""; }; 7004C22D25B69077005E0AD9 /* ParseRoleTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParseRoleTests.swift; sourceTree = ""; }; + 700AFE02289C3508006C1CD9 /* ParseQueryCacheTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParseQueryCacheTests.swift; sourceTree = ""; }; 70110D51250680140091CC1D /* ParseConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParseConstants.swift; sourceTree = ""; }; 70110D562506CE890091CC1D /* BaseParseInstallation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseParseInstallation.swift; sourceTree = ""; }; 70110D5B2506ED0E0091CC1D /* ParseInstallationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParseInstallationTests.swift; sourceTree = ""; }; @@ -1261,6 +1269,7 @@ 70BC0B32251903D1001556DB /* ParseGeoPointTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParseGeoPointTests.swift; sourceTree = ""; }; 70BC988F252A5B5C00FF3074 /* Objectable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Objectable.swift; sourceTree = ""; }; 70BDA2B2250536FF00FC2237 /* ParseInstallation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParseInstallation.swift; sourceTree = ""; }; + 70C048C02880D7E500401689 /* Dictionary.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Dictionary.swift; sourceTree = ""; }; 70C167AE27304EE4009F4E30 /* Pointer+combine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Pointer+combine.swift"; sourceTree = ""; }; 70C167B327304F09009F4E30 /* Pointer+async.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Pointer+async.swift"; sourceTree = ""; }; 70C167B827305101009F4E30 /* ParsePointerAsyncTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParsePointerAsyncTests.swift; sourceTree = ""; }; @@ -1627,6 +1636,7 @@ 70385E6328563FD10084D306 /* ParsePushPayloadFirebaseTests.swift */, 70212D172855256F00386163 /* ParsePushTests.swift */, 917BA4252703DB4600F8D747 /* ParseQueryAsyncTests.swift */, + 700AFE02289C3508006C1CD9 /* ParseQueryCacheTests.swift */, 7044C20525C5D6780011F6E7 /* ParseQueryCombineTests.swift */, 70C7DC1F24D20F180050419B /* ParseQueryTests.swift */, 91BB8FD32690D586005A6BA5 /* ParseQueryViewModelTests.swift */, @@ -1635,10 +1645,10 @@ 7050259C2843F0CF008D6624 /* ParseSchemaAsyncTests.swift */, 705025A02843F0E7008D6624 /* ParseSchemaCombineTests.swift */, 705025A4284407C4008D6624 /* ParseSchemaTests.swift */, + 70C5504525B40D5200B5DBC2 /* ParseSessionTests.swift */, 7C995D282861FA0B0077805A /* ParseSpotifyAsyncTests.swift */, 7C995D2C2861FAE40077805A /* ParseSpotifyCombineTests.swift */, 7C995D242861F8330077805A /* ParseSpotifyTests.swift */, - 70C5504525B40D5200B5DBC2 /* ParseSessionTests.swift */, 917BA4512703F55700F8D747 /* ParseTwitterAsyncTests.swift */, 89899D9E26045998002E2043 /* ParseTwitterCombineTests.swift */, 89899CDC2603CE73002E2043 /* ParseTwitterTests.swift */, @@ -1892,6 +1902,7 @@ children = ( 70386A0525D9718C0048EC1B /* Data.swift */, 706436A827341FD0007C6461 /* Date.swift */, + 70C048C02880D7E500401689 /* Dictionary.swift */, 706436A327341F6E007C6461 /* Encodable.swift */, 9116F66E26A35D600082F6D6 /* URLCache.swift */, F97B462C24D9C74400F4A88B /* URLSession.swift */, @@ -2541,6 +2552,7 @@ /* Begin PBXShellScriptBuildPhase section */ 4A6511541F49D53C005237DF /* SwiftLint */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); @@ -2555,6 +2567,7 @@ }; 4A6511551F49D544005237DF /* SwiftLint */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); @@ -2569,6 +2582,7 @@ }; 918CED61268A23A700CFDC83 /* SwiftLint */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); @@ -2587,6 +2601,7 @@ }; 918CED62268A23E600CFDC83 /* SwiftLint */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); @@ -2647,6 +2662,7 @@ 7085DDA326CC8A470033B977 /* ParseHealth+combine.swift in Sources */, 70CE0AC1285FD59B00DAEA86 /* ParseHookFunctionable+async.swift in Sources */, F97B465224D9C78C00F4A88B /* AddUnique.swift in Sources */, + 70C048C12880D7E600401689 /* Dictionary.swift in Sources */, 91B79AC826EE3C5D00073F2C /* API+BatchCommand.swift in Sources */, 70385E712858D2DD0084D306 /* ParseHookTriggerable.swift in Sources */, 91679D64268E596300F71809 /* ParseVersion.swift in Sources */, @@ -2834,6 +2850,7 @@ 70E6B02A28614C5F0043EC4A /* ParseHookFunctionRequestCombineTests.swift in Sources */, 7044C1DF25C5C70D0011F6E7 /* ParseObjectCombineTests.swift in Sources */, 70212D2E2855266400386163 /* ParsePushCombineTests.swift in Sources */, + 700AFE03289C3508006C1CD9 /* ParseQueryCacheTests.swift in Sources */, 70E6B026286132BC0043EC4A /* ParseHookFunctionRequestTests.swift in Sources */, 70F03A622780EADD00E5AFB4 /* ParseLinkedInCombineTests.swift in Sources */, 89899D9F26045998002E2043 /* ParseTwitterCombineTests.swift in Sources */, @@ -2949,6 +2966,7 @@ 7085DDA426CC8A470033B977 /* ParseHealth+combine.swift in Sources */, 70CE0AC2285FD59B00DAEA86 /* ParseHookFunctionable+async.swift in Sources */, F97B465324D9C78C00F4A88B /* AddUnique.swift in Sources */, + 70C048C22880D7E600401689 /* Dictionary.swift in Sources */, 91B79AC926EE3C5D00073F2C /* API+BatchCommand.swift in Sources */, 70385E722858D2DD0084D306 /* ParseHookTriggerable.swift in Sources */, 91679D65268E596300F71809 /* ParseVersion.swift in Sources */, @@ -3145,6 +3163,7 @@ 70E6B02C28614C5F0043EC4A /* ParseHookFunctionRequestCombineTests.swift in Sources */, 7044C1E125C5C70D0011F6E7 /* ParseObjectCombineTests.swift in Sources */, 70212D362855266600386163 /* ParsePushCombineTests.swift in Sources */, + 700AFE05289C3508006C1CD9 /* ParseQueryCacheTests.swift in Sources */, 70E6B028286132BC0043EC4A /* ParseHookFunctionRequestTests.swift in Sources */, 70F03A642780EADD00E5AFB4 /* ParseLinkedInCombineTests.swift in Sources */, 89899DA126045998002E2043 /* ParseTwitterCombineTests.swift in Sources */, @@ -3264,6 +3283,7 @@ 70E6B02B28614C5F0043EC4A /* ParseHookFunctionRequestCombineTests.swift in Sources */, 7044C1E025C5C70D0011F6E7 /* ParseObjectCombineTests.swift in Sources */, 70212D322855266500386163 /* ParsePushCombineTests.swift in Sources */, + 700AFE04289C3508006C1CD9 /* ParseQueryCacheTests.swift in Sources */, 70E6B027286132BC0043EC4A /* ParseHookFunctionRequestTests.swift in Sources */, 70F03A632780EADD00E5AFB4 /* ParseLinkedInCombineTests.swift in Sources */, 89899DA026045998002E2043 /* ParseTwitterCombineTests.swift in Sources */, @@ -3379,6 +3399,7 @@ 703B090026BD953B005A112F /* ParseHealth+async.swift in Sources */, 70CE0AC4285FD59B00DAEA86 /* ParseHookFunctionable+async.swift in Sources */, 7085DDA626CC8A470033B977 /* ParseHealth+combine.swift in Sources */, + 70C048C42880D7E600401689 /* Dictionary.swift in Sources */, F97B45E524D9C6F200F4A88B /* AnyEncodable.swift in Sources */, 70385E742858D2DD0084D306 /* ParseHookTriggerable.swift in Sources */, 91B79ACB26EE3C5D00073F2C /* API+BatchCommand.swift in Sources */, @@ -3562,6 +3583,7 @@ 703B08FF26BD953B005A112F /* ParseHealth+async.swift in Sources */, 70CE0AC3285FD59B00DAEA86 /* ParseHookFunctionable+async.swift in Sources */, 7085DDA526CC8A470033B977 /* ParseHealth+combine.swift in Sources */, + 70C048C32880D7E600401689 /* Dictionary.swift in Sources */, F97B45E424D9C6F200F4A88B /* AnyEncodable.swift in Sources */, 70385E732858D2DD0084D306 /* ParseHookTriggerable.swift in Sources */, 91B79ACA26EE3C5D00073F2C /* API+BatchCommand.swift in Sources */, @@ -3787,7 +3809,10 @@ GCC_GENERATE_TEST_COVERAGE_FILES = NO; INFOPLIST_FILE = TestHost/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 13.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); PRODUCT_BUNDLE_IDENTIFIER = com.parse.TestHost; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; @@ -3804,7 +3829,10 @@ GCC_GENERATE_TEST_COVERAGE_FILES = NO; INFOPLIST_FILE = TestHost/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 13.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); PRODUCT_BUNDLE_IDENTIFIER = com.parse.TestHost; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; @@ -3929,7 +3957,8 @@ MACOSX_DEPLOYMENT_TARGET = 10.13; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; - SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; SWIFT_VERSION = 5.0; VALIDATE_PRODUCT = YES; VERSIONING_SYSTEM = "apple-generic"; @@ -3949,7 +3978,11 @@ INFOPLIST_FILE = "ParseSwift-iOS/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 13.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); MARKETING_VERSION = 1.8.3; PRODUCT_BUNDLE_IDENTIFIER = com.parse.ParseSwift; PRODUCT_NAME = ParseSwift; @@ -3974,7 +4007,11 @@ INFOPLIST_FILE = "ParseSwift-iOS/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 13.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); MARKETING_VERSION = 1.8.3; PRODUCT_BUNDLE_IDENTIFIER = com.parse.ParseSwift; PRODUCT_NAME = ParseSwift; @@ -3998,7 +4035,11 @@ DEVELOPMENT_TEAM = ""; GCC_GENERATE_TEST_COVERAGE_FILES = YES; INFOPLIST_FILE = Tests/ParseSwiftTests/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); PRODUCT_BUNDLE_IDENTIFIER = com.parse.ParseSwiftTests; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -4020,7 +4061,11 @@ DEVELOPMENT_TEAM = ""; GCC_GENERATE_TEST_COVERAGE_FILES = YES; INFOPLIST_FILE = Tests/ParseSwiftTests/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); PRODUCT_BUNDLE_IDENTIFIER = com.parse.ParseSwiftTests; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -4044,7 +4089,11 @@ GCC_GENERATE_TEST_COVERAGE_FILES = YES; INFOPLIST_FILE = "ParseSwift-macOS/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); MACOSX_DEPLOYMENT_TARGET = 10.15; MARKETING_VERSION = 1.8.3; PRODUCT_BUNDLE_IDENTIFIER = com.parse.ParseSwift; @@ -4071,7 +4120,11 @@ GCC_GENERATE_TEST_COVERAGE_FILES = YES; INFOPLIST_FILE = "ParseSwift-macOS/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); MACOSX_DEPLOYMENT_TARGET = 10.15; MARKETING_VERSION = 1.8.3; PRODUCT_BUNDLE_IDENTIFIER = com.parse.ParseSwift; @@ -4095,7 +4148,10 @@ CODE_SIGN_STYLE = Automatic; GCC_GENERATE_TEST_COVERAGE_FILES = NO; INFOPLIST_FILE = TestHostTV/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = com.parse.TestHostTV; @@ -4117,7 +4173,10 @@ CODE_SIGN_STYLE = Automatic; GCC_GENERATE_TEST_COVERAGE_FILES = NO; INFOPLIST_FILE = TestHostTV/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = com.parse.TestHostTV; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -4137,7 +4196,11 @@ DEVELOPMENT_TEAM = ""; GCC_GENERATE_TEST_COVERAGE_FILES = YES; INFOPLIST_FILE = ParseSwiftTeststvOS/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = edu.uky.cs.netrecon.ParseSwiftTeststvOS; @@ -4160,7 +4223,11 @@ DEVELOPMENT_TEAM = ""; GCC_GENERATE_TEST_COVERAGE_FILES = YES; INFOPLIST_FILE = ParseSwiftTeststvOS/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = edu.uky.cs.netrecon.ParseSwiftTeststvOS; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -4184,7 +4251,11 @@ DEVELOPMENT_TEAM = ""; GCC_GENERATE_TEST_COVERAGE_FILES = YES; INFOPLIST_FILE = ParseSwiftTestsmacOS/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/../Frameworks", + ); MACOSX_DEPLOYMENT_TARGET = 10.15; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; @@ -4207,7 +4278,11 @@ DEVELOPMENT_TEAM = ""; GCC_GENERATE_TEST_COVERAGE_FILES = YES; INFOPLIST_FILE = ParseSwiftTestsmacOS/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/../Frameworks", + ); MACOSX_DEPLOYMENT_TARGET = 10.15; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = edu.uky.cs.netrecon.ParseSwiftTestsmacOS; @@ -4232,7 +4307,11 @@ GCC_GENERATE_TEST_COVERAGE_FILES = YES; INFOPLIST_FILE = "ParseSwift-watchOS/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); MARKETING_VERSION = 1.8.3; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; @@ -4262,7 +4341,11 @@ GCC_GENERATE_TEST_COVERAGE_FILES = YES; INFOPLIST_FILE = "ParseSwift-watchOS/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); MARKETING_VERSION = 1.8.3; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = "com.parse.ParseSwift-watchOS"; @@ -4290,7 +4373,11 @@ GCC_GENERATE_TEST_COVERAGE_FILES = YES; INFOPLIST_FILE = "ParseSwift-tvOS/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); MARKETING_VERSION = 1.8.3; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; @@ -4319,7 +4406,11 @@ GCC_GENERATE_TEST_COVERAGE_FILES = YES; INFOPLIST_FILE = "ParseSwift-tvOS/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); MARKETING_VERSION = 1.8.3; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = "com.parse.ParseSwift-tvOS"; diff --git a/Sources/ParseSwift/API/API+Command.swift b/Sources/ParseSwift/API/API+Command.swift index 965dc3e1c..ebb7f6466 100644 --- a/Sources/ParseSwift/API/API+Command.swift +++ b/Sources/ParseSwift/API/API+Command.swift @@ -260,7 +260,7 @@ internal extension API { func prepareURLRequest(options: API.Options, childObjects: [String: PointerType]? = nil, childFiles: [UUID: ParseFile]? = nil) -> Result { - let params = self.params?.getQueryItems() + let params = self.params?.getURLQueryItems() var headers = API.getHeaders(options: options) if method == .GET || method == .DELETE { headers.removeValue(forKey: "X-Parse-Request-Id") diff --git a/Sources/ParseSwift/API/API+NonParseBodyCommand.swift b/Sources/ParseSwift/API/API+NonParseBodyCommand.swift index b96f8557d..64f94ef7c 100644 --- a/Sources/ParseSwift/API/API+NonParseBodyCommand.swift +++ b/Sources/ParseSwift/API/API+NonParseBodyCommand.swift @@ -19,6 +19,7 @@ internal extension API { let path: API.Endpoint let body: T? let mapper: ((Data) throws -> U) + let params: [String: String?]? init(method: API.Method, path: API.Endpoint, @@ -27,6 +28,7 @@ internal extension API { mapper: @escaping ((Data) throws -> U)) { self.method = method self.path = path + self.params = params self.body = body self.mapper = mapper } @@ -83,17 +85,23 @@ internal extension API { // MARK: URL Preperation func prepareURLRequest(options: API.Options) -> Result { + let params = self.params?.getURLQueryItems() var headers = API.getHeaders(options: options) if method == .GET || method == .DELETE { headers.removeValue(forKey: "X-Parse-Request-Id") } let url = ParseSwift.configuration.serverURL.appendingPathComponent(path.urlComponent) - guard let components = URLComponents(url: url, resolvingAgainstBaseURL: false), - let urlComponents = components.url else { + guard var components = URLComponents(url: url, resolvingAgainstBaseURL: false) else { return .failure(ParseError(code: .unknownError, message: "couldn't unrwrap url components for \(url)")) } + components.queryItems = params + + guard let urlComponents = components.url else { + return .failure(ParseError(code: .unknownError, + message: "couldn't create url from components for \(components)")) + } var urlRequest = URLRequest(url: urlComponents) urlRequest.allHTTPHeaderFields = headers diff --git a/Sources/ParseSwift/API/API.swift b/Sources/ParseSwift/API/API.swift index 8a130c7e4..cbd889208 100644 --- a/Sources/ParseSwift/API/API.swift +++ b/Sources/ParseSwift/API/API.swift @@ -254,11 +254,3 @@ public struct API { ParseConstants.sdk+ParseConstants.version } } - -internal extension Dictionary where Key == String, Value == String? { - func getQueryItems() -> [URLQueryItem] { - return map { (key, value) -> URLQueryItem in - return URLQueryItem(name: key, value: value) - } - } -} diff --git a/Sources/ParseSwift/Extensions/Dictionary.swift b/Sources/ParseSwift/Extensions/Dictionary.swift new file mode 100644 index 000000000..0a1e34912 --- /dev/null +++ b/Sources/ParseSwift/Extensions/Dictionary.swift @@ -0,0 +1,17 @@ +// +// Dictionary.swift +// ParseSwift +// +// Created by Corey Baker on 7/14/22. +// Copyright © 2022 Parse Community. All rights reserved. +// + +import Foundation + +internal extension Dictionary where Key == String, Value == String? { + func getURLQueryItems() -> [URLQueryItem] { + sorted { $0.key < $1.key }.map { (key, value) -> URLQueryItem in + URLQueryItem(name: key, value: value) + } + } +} diff --git a/Sources/ParseSwift/Parse.swift b/Sources/ParseSwift/Parse.swift index 051a93951..ee6fe0651 100644 --- a/Sources/ParseSwift/Parse.swift +++ b/Sources/ParseSwift/Parse.swift @@ -37,6 +37,12 @@ public struct ParseConfiguration { /// - warning: This is known not to work for LiveQuery on Parse Servers <= 5.0.0. public internal(set) var isUsingEqualQueryConstraint = false + /// Use **POST** instead of **GET** when making query calls. + /// Defaults to **false**. + /// - warning: **POST** calls are not cached and will require all queries to access the + /// server instead of following the `requestCachePolicy`. + public internal(set) var isUsingPostForQuery = false + /// The default caching policy for all http requests that determines when to /// return a response from the cache. Defaults to `useProtocolCachePolicy`. /// See Apple's [documentation](https://developer.apple.com/documentation/foundation/url_loading_system/accessing_cached_data) @@ -85,6 +91,8 @@ public struct ParseConfiguration { side for each object. Must be enabled on the server to work. - parameter usingTransactions: Use transactions when saving/updating multiple objects. - parameter usingEqualQueryConstraint: Use the **$eq** query constraint when querying. + - parameter usingPostForQuery: Use **POST** instead of **GET** when making query calls. + Defaults to **false**. - parameter keyValueStore: A key/value store that conforms to the `ParseKeyValueStore` protocol. Defaults to `nil` in which one will be created an memory, but never persisted. For Linux, this this is the only store available since there is no Keychain. Linux users should replace this store with an @@ -110,6 +118,7 @@ public struct ParseConfiguration { See Apple's [documentation](https://developer.apple.com/documentation/foundation/urlsessiontaskdelegate/1411595-urlsession) for more for details. - warning: `usingTransactions` is experimental. - warning: It is recomended to only specify `masterKey` when using the SDK on a server. Do not use this key on the client. + - warning: Setting `usingPostForQuery` to **true** will require all queries to access the server instead of following the `requestCachePolicy`. */ public init(applicationId: String, clientKey: String? = nil, @@ -121,6 +130,7 @@ public struct ParseConfiguration { allowingCustomObjectIds: Bool = false, usingTransactions: Bool = false, usingEqualQueryConstraint: Bool = false, + usingPostForQuery: Bool = false, keyValueStore: ParseKeyValueStore? = nil, requestCachePolicy: URLRequest.CachePolicy = .useProtocolCachePolicy, cacheMemoryCapacity: Int = 512_000, @@ -140,6 +150,7 @@ public struct ParseConfiguration { self.isAllowingCustomObjectIds = allowingCustomObjectIds self.isUsingTransactions = usingTransactions self.isUsingEqualQueryConstraint = usingEqualQueryConstraint + self.isUsingPostForQuery = usingPostForQuery self.mountPath = "/" + serverURL.pathComponents .filter { $0 != "/" } .joined(separator: "/") @@ -253,6 +264,8 @@ public struct ParseSwift { side for each object. Must be enabled on the server to work. - parameter usingTransactions: Use transactions when saving/updating multiple objects. - parameter usingEqualQueryConstraint: Use the **$eq** query constraint when querying. + - parameter usingPostForQuery: Use **POST** instead of **GET** when making query calls. + Defaults to **false**. - parameter keyValueStore: A key/value store that conforms to the `ParseKeyValueStore` protocol. Defaults to `nil` in which one will be created an memory, but never persisted. For Linux, this this is the only store available since there is no Keychain. Linux users should replace this store with an @@ -276,6 +289,7 @@ public struct ParseSwift { See Apple's [documentation](https://developer.apple.com/documentation/foundation/urlsessiontaskdelegate/1411595-urlsession) for more for details. - warning: `usingTransactions` is experimental. - warning: It is recomended to only specify `masterKey` when using the SDK on a server. Do not use this key on the client. + - warning: Setting `usingPostForQuery` to **true** will require all queries to access the server instead of following the `requestCachePolicy`. */ static public func initialize( applicationId: String, @@ -286,6 +300,7 @@ public struct ParseSwift { allowingCustomObjectIds: Bool = false, usingTransactions: Bool = false, usingEqualQueryConstraint: Bool = false, + usingPostForQuery: Bool = false, keyValueStore: ParseKeyValueStore? = nil, requestCachePolicy: URLRequest.CachePolicy = .useProtocolCachePolicy, cacheMemoryCapacity: Int = 512_000, @@ -306,6 +321,7 @@ public struct ParseSwift { allowingCustomObjectIds: allowingCustomObjectIds, usingTransactions: usingTransactions, usingEqualQueryConstraint: usingEqualQueryConstraint, + usingPostForQuery: usingPostForQuery, keyValueStore: keyValueStore, requestCachePolicy: requestCachePolicy, cacheMemoryCapacity: cacheMemoryCapacity, @@ -325,6 +341,7 @@ public struct ParseSwift { allowingCustomObjectIds: Bool = false, usingTransactions: Bool = false, usingEqualQueryConstraint: Bool = false, + usingPostForQuery: Bool = false, keyValueStore: ParseKeyValueStore? = nil, requestCachePolicy: URLRequest.CachePolicy = .useProtocolCachePolicy, cacheMemoryCapacity: Int = 512_000, @@ -345,6 +362,7 @@ public struct ParseSwift { allowingCustomObjectIds: allowingCustomObjectIds, usingTransactions: usingTransactions, usingEqualQueryConstraint: usingEqualQueryConstraint, + usingPostForQuery: usingPostForQuery, keyValueStore: keyValueStore, requestCachePolicy: requestCachePolicy, cacheMemoryCapacity: cacheMemoryCapacity, diff --git a/Sources/ParseSwift/Types/Query.swift b/Sources/ParseSwift/Types/Query.swift index 81159e4ff..c1948d873 100644 --- a/Sources/ParseSwift/Types/Query.swift +++ b/Sources/ParseSwift/Types/Query.swift @@ -57,6 +57,23 @@ public struct Query: ParseTypeable where T: ParseObject { explain = query.explain includeReadPreference = query.includeReadPreference } + + func getQueryParameters() throws -> [String: String] { + var dictionary = [String: String]() + dictionary["explain"] = try encodeAsString(\.explain) + dictionary["hint"] = try encodeAsString(\.hint) + dictionary["includeReadPreference"] = try encodeAsString(\.includeReadPreference) + dictionary["pipeline"] = try encodeAsString(\.pipeline) + return dictionary + } + + func encodeAsString(_ key: KeyPath) throws -> String? where W: Encodable { + guard let value = self[keyPath: key] else { + return nil + } + let encoded = try ParseCoding.jsonEncoder().encode(value) + return String(data: encoded, encoding: .utf8) + } } struct DistinctBody: Codable where T: ParseObject { @@ -71,6 +88,23 @@ public struct Query: ParseTypeable where T: ParseObject { explain = query.explain includeReadPreference = query.includeReadPreference } + + func getQueryParameters() throws -> [String: String] { + var dictionary = [String: String]() + dictionary["explain"] = try encodeAsString(\.explain) + dictionary["hint"] = try encodeAsString(\.hint) + dictionary["includeReadPreference"] = try encodeAsString(\.includeReadPreference) + dictionary["distinct"] = try encodeAsString(\.distinct) + return dictionary + } + + func encodeAsString(_ key: KeyPath) throws -> String? where W: Encodable { + guard let value = self[keyPath: key] else { + return nil + } + let encoded = try ParseCoding.jsonEncoder().encode(value) + return String(data: encoded, encoding: .utf8) + } } enum CodingKeys: String, CodingKey { @@ -511,9 +545,17 @@ extension Query: Queryable { } return } - findCommand().executeAsync(options: options, - callbackQueue: callbackQueue) { result in - completion(result) + do { + try findCommand().executeAsync(options: options, + callbackQueue: callbackQueue) { result in + completion(result) + } + } catch { + let parseError = ParseError(code: .unknownError, + message: error.localizedDescription) + callbackQueue.async { + completion(.failure(parseError)) + } } } @@ -543,14 +585,30 @@ extension Query: Queryable { return } if !usingMongoDB { - findExplainCommand().executeAsync(options: options, - callbackQueue: callbackQueue) { result in - completion(result) + do { + try findExplainCommand().executeAsync(options: options, + callbackQueue: callbackQueue) { result in + completion(result) + } + } catch { + let parseError = ParseError(code: .unknownError, + message: error.localizedDescription) + callbackQueue.async { + completion(.failure(parseError)) + } } } else { - findExplainMongoCommand().executeAsync(options: options, - callbackQueue: callbackQueue) { result in - completion(result) + do { + try findExplainMongoCommand().executeAsync(options: options, + callbackQueue: callbackQueue) { result in + completion(result) + } + } catch { + let parseError = ParseError(code: .unknownError, + message: error.localizedDescription) + callbackQueue.async { + completion(.failure(parseError)) + } } } } @@ -696,9 +754,17 @@ extension Query: Queryable { } return } - firstCommand().executeAsync(options: options, - callbackQueue: callbackQueue) { result in - completion(result) + do { + try firstCommand().executeAsync(options: options, + callbackQueue: callbackQueue) { result in + completion(result) + } + } catch { + let parseError = ParseError(code: .unknownError, + message: error.localizedDescription) + callbackQueue.async { + completion(.failure(parseError)) + } } } @@ -732,14 +798,30 @@ extension Query: Queryable { return } if !usingMongoDB { - firstExplainCommand().executeAsync(options: options, - callbackQueue: callbackQueue) { result in - completion(result) + do { + try firstExplainCommand().executeAsync(options: options, + callbackQueue: callbackQueue) { result in + completion(result) + } + } catch { + let parseError = ParseError(code: .unknownError, + message: error.localizedDescription) + callbackQueue.async { + completion(.failure(parseError)) + } } } else { - firstExplainMongoCommand().executeAsync(options: options, - callbackQueue: callbackQueue) { result in - completion(result) + do { + try firstExplainMongoCommand().executeAsync(options: options, + callbackQueue: callbackQueue) { result in + completion(result) + } + } catch { + let parseError = ParseError(code: .unknownError, + message: error.localizedDescription) + callbackQueue.async { + completion(.failure(parseError)) + } } } } @@ -802,9 +884,17 @@ extension Query: Queryable { } return } - countCommand().executeAsync(options: options, - callbackQueue: callbackQueue) { result in - completion(result) + do { + try countCommand().executeAsync(options: options, + callbackQueue: callbackQueue) { result in + completion(result) + } + } catch { + let parseError = ParseError(code: .unknownError, + message: error.localizedDescription) + callbackQueue.async { + completion(.failure(parseError)) + } } } @@ -834,14 +924,30 @@ extension Query: Queryable { return } if !usingMongoDB { - countExplainCommand().executeAsync(options: options, - callbackQueue: callbackQueue) { result in - completion(result) + do { + try countExplainCommand().executeAsync(options: options, + callbackQueue: callbackQueue) { result in + completion(result) + } + } catch { + let parseError = ParseError(code: .unknownError, + message: error.localizedDescription) + callbackQueue.async { + completion(.failure(parseError)) + } } } else { - countExplainMongoCommand().executeAsync(options: options, - callbackQueue: callbackQueue) { result in - completion(result) + do { + try countExplainMongoCommand().executeAsync(options: options, + callbackQueue: callbackQueue) { result in + completion(result) + } + } catch { + let parseError = ParseError(code: .unknownError, + message: error.localizedDescription) + callbackQueue.async { + completion(.failure(parseError)) + } } } } @@ -864,9 +970,17 @@ extension Query: Queryable { } return } - withCountCommand().executeAsync(options: options, - callbackQueue: callbackQueue) { result in - completion(result) + do { + try withCountCommand().executeAsync(options: options, + callbackQueue: callbackQueue) { result in + completion(result) + } + } catch { + let parseError = ParseError(code: .unknownError, + message: error.localizedDescription) + callbackQueue.async { + completion(.failure(parseError)) + } } } @@ -896,14 +1010,30 @@ extension Query: Queryable { return } if !usingMongoDB { - withCountExplainCommand().executeAsync(options: options, - callbackQueue: callbackQueue) { result in - completion(result) + do { + try withCountExplainCommand().executeAsync(options: options, + callbackQueue: callbackQueue) { result in + completion(result) + } + } catch { + let parseError = ParseError(code: .unknownError, + message: error.localizedDescription) + callbackQueue.async { + completion(.failure(parseError)) + } } } else { - withCountExplainMongoCommand().executeAsync(options: options, - callbackQueue: callbackQueue) { result in - completion(result) + do { + try withCountExplainMongoCommand().executeAsync(options: options, + callbackQueue: callbackQueue) { result in + completion(result) + } + } catch { + let parseError = ParseError(code: .unknownError, + message: error.localizedDescription) + callbackQueue.async { + completion(.failure(parseError)) + } } } } @@ -998,11 +1128,18 @@ extension Query: Queryable { } else { query.pipeline = updatedPipeline } - - query.aggregateCommand() - .executeAsync(options: options, - callbackQueue: callbackQueue) { result in - completion(result) + do { + try query.aggregateCommand() + .executeAsync(options: options, + callbackQueue: callbackQueue) { result in + completion(result) + } + } catch { + let parseError = ParseError(code: .unknownError, + message: error.localizedDescription) + callbackQueue.async { + completion(.failure(parseError)) + } } } @@ -1117,14 +1254,30 @@ extension Query: Queryable { query.pipeline = updatedPipeline } if !usingMongoDB { - query.aggregateExplainCommand() - .executeAsync(options: options, callbackQueue: callbackQueue) { result in - completion(result) + do { + try query.aggregateExplainCommand() + .executeAsync(options: options, callbackQueue: callbackQueue) { result in + completion(result) + } + } catch { + let parseError = ParseError(code: .unknownError, + message: error.localizedDescription) + callbackQueue.async { + completion(.failure(parseError)) + } } } else { - query.aggregateExplainMongoCommand() - .executeAsync(options: options, callbackQueue: callbackQueue) { result in - completion(result) + do { + try query.aggregateExplainMongoCommand() + .executeAsync(options: options, callbackQueue: callbackQueue) { result in + completion(result) + } + } catch { + let parseError = ParseError(code: .unknownError, + message: error.localizedDescription) + callbackQueue.async { + completion(.failure(parseError)) + } } } } @@ -1175,10 +1328,18 @@ extension Query: Queryable { } var options = options options.insert(.useMasterKey) - distinctCommand(key: key) - .executeAsync(options: options, - callbackQueue: callbackQueue) { result in - completion(result) + do { + try distinctCommand(key: key) + .executeAsync(options: options, + callbackQueue: callbackQueue) { result in + completion(result) + } + } catch { + let parseError = ParseError(code: .unknownError, + message: error.localizedDescription) + callbackQueue.async { + completion(.failure(parseError)) + } } } @@ -1251,16 +1412,32 @@ extension Query: Queryable { var options = options options.insert(.useMasterKey) if !usingMongoDB { - distinctExplainCommand(key: key) - .executeAsync(options: options, - callbackQueue: callbackQueue) { result in - completion(result) + do { + try distinctExplainCommand(key: key) + .executeAsync(options: options, + callbackQueue: callbackQueue) { result in + completion(result) + } + } catch { + let parseError = ParseError(code: .unknownError, + message: error.localizedDescription) + callbackQueue.async { + completion(.failure(parseError)) + } } } else { - distinctExplainMongoCommand(key: key) - .executeAsync(options: options, - callbackQueue: callbackQueue) { result in - completion(result) + do { + try distinctExplainMongoCommand(key: key) + .executeAsync(options: options, + callbackQueue: callbackQueue) { result in + completion(result) + } + } catch { + let parseError = ParseError(code: .unknownError, + message: error.localizedDescription) + callbackQueue.async { + completion(.failure(parseError)) + } } } } @@ -1268,181 +1445,373 @@ extension Query: Queryable { extension Query { - func findCommand() -> API.NonParseBodyCommand, [ResultType]> { - let query = self - return API.NonParseBodyCommand(method: .POST, path: query.endpoint, body: query) { - try ParseCoding.jsonDecoder().decode(QueryResponse.self, from: $0).results + func findCommand() throws -> API.NonParseBodyCommand, [ResultType]> { + if !ParseSwift.configuration.isUsingPostForQuery { + return API.NonParseBodyCommand(method: .GET, + path: endpoint, + params: try getQueryParameters()) { + try ParseCoding.jsonDecoder().decode(QueryResponse.self, from: $0).results + } + } else { + return API.NonParseBodyCommand(method: .POST, + path: endpoint, + body: self) { + try ParseCoding.jsonDecoder().decode(QueryResponse.self, from: $0).results + } } } - func firstCommand() -> API.NonParseBodyCommand, ResultType> { + func firstCommand() throws -> API.NonParseBodyCommand, ResultType> { var query = self query.limit = 1 - return API.NonParseBodyCommand(method: .POST, path: query.endpoint, body: query) { - if let decoded = try ParseCoding.jsonDecoder().decode(QueryResponse.self, from: $0).results.first { - return decoded + if !ParseSwift.configuration.isUsingPostForQuery { + return API.NonParseBodyCommand(method: .GET, + path: query.endpoint, + params: try getQueryParameters()) { + if let decoded = try ParseCoding.jsonDecoder().decode(QueryResponse.self, from: $0).results.first { + return decoded + } + throw ParseError(code: .objectNotFound, + message: "Object not found on the server.") + } + } else { + return API.NonParseBodyCommand(method: .POST, path: query.endpoint, body: query) { + if let decoded = try ParseCoding.jsonDecoder().decode(QueryResponse.self, from: $0).results.first { + return decoded + } + throw ParseError(code: .objectNotFound, + message: "Object not found on the server.") } - throw ParseError(code: .objectNotFound, - message: "Object not found on the server.") } } - func countCommand() -> API.NonParseBodyCommand, Int> { + func countCommand() throws -> API.NonParseBodyCommand, Int> { var query = self query.limit = 1 query.isCount = true - return API.NonParseBodyCommand(method: .POST, - path: query.endpoint, - body: query) { - try ParseCoding.jsonDecoder().decode(QueryResponse.self, from: $0).count ?? 0 + if !ParseSwift.configuration.isUsingPostForQuery { + return API.NonParseBodyCommand(method: .GET, + path: query.endpoint, + params: try getQueryParameters()) { + try ParseCoding.jsonDecoder().decode(QueryResponse.self, from: $0).count ?? 0 + } + } else { + return API.NonParseBodyCommand(method: .POST, + path: query.endpoint, + body: query) { + try ParseCoding.jsonDecoder().decode(QueryResponse.self, from: $0).count ?? 0 + } } } - func withCountCommand() -> API.NonParseBodyCommand, ([ResultType], Int)> { + func withCountCommand() throws -> API.NonParseBodyCommand, ([ResultType], Int)> { var query = self query.isCount = true - return API.NonParseBodyCommand(method: .POST, - path: query.endpoint, - body: query) { - let decoded = try ParseCoding.jsonDecoder().decode(QueryResponse.self, from: $0) - return (decoded.results, decoded.count ?? 0) + if !ParseSwift.configuration.isUsingPostForQuery { + return API.NonParseBodyCommand(method: .GET, + path: query.endpoint, + params: try getQueryParameters()) { + let decoded = try ParseCoding.jsonDecoder().decode(QueryResponse.self, from: $0) + return (decoded.results, decoded.count ?? 0) + } + } else { + return API.NonParseBodyCommand(method: .POST, + path: query.endpoint, + body: query) { + let decoded = try ParseCoding.jsonDecoder().decode(QueryResponse.self, from: $0) + return (decoded.results, decoded.count ?? 0) + } } } - func aggregateCommand() -> API.NonParseBodyCommand, [ResultType]> { - let query = self - let body = AggregateBody(query: query) - return API.NonParseBodyCommand(method: .POST, path: .aggregate(className: T.className), body: body) { - try ParseCoding.jsonDecoder().decode(QueryResponse.self, from: $0).results + func aggregateCommand() throws -> API.NonParseBodyCommand, [ResultType]> { + let body = AggregateBody(query: self) + if !ParseSwift.configuration.isUsingPostForQuery { + return API.NonParseBodyCommand(method: .GET, + path: .aggregate(className: T.className), + params: try body.getQueryParameters()) { + try ParseCoding.jsonDecoder().decode(QueryResponse.self, from: $0).results + } + } else { + return API.NonParseBodyCommand(method: .POST, + path: .aggregate(className: T.className), + body: body) { + try ParseCoding.jsonDecoder().decode(QueryResponse.self, from: $0).results + } } } - func distinctCommand(key: String) -> API.NonParseBodyCommand, [ResultType]> { + func distinctCommand(key: String) throws -> API.NonParseBodyCommand, [ResultType]> { var query = self query.distinct = key let body = DistinctBody(query: query) - return API.NonParseBodyCommand(method: .POST, path: .aggregate(className: T.className), body: body) { - try ParseCoding.jsonDecoder().decode(QueryResponse.self, from: $0).results + if !ParseSwift.configuration.isUsingPostForQuery { + return API.NonParseBodyCommand(method: .GET, + path: .aggregate(className: T.className), + params: try body.getQueryParameters()) { + try ParseCoding.jsonDecoder().decode(QueryResponse.self, from: $0).results + } + } else { + return API.NonParseBodyCommand(method: .POST, + path: .aggregate(className: T.className), + body: body) { + try ParseCoding.jsonDecoder().decode(QueryResponse.self, from: $0).results + } } } - func findExplainCommand() -> API.NonParseBodyCommand, [U]> { + func findExplainCommand() throws -> API.NonParseBodyCommand, [U]> { var query = self query.explain = true - return API.NonParseBodyCommand(method: .POST, path: query.endpoint, body: query) { - try ParseCoding.jsonDecoder().decode(AnyResultsResponse.self, from: $0).results + if !ParseSwift.configuration.isUsingPostForQuery { + return API.NonParseBodyCommand(method: .GET, + path: query.endpoint, + params: try query.getQueryParameters()) { + try ParseCoding.jsonDecoder().decode(AnyResultsResponse.self, from: $0).results + } + } else { + return API.NonParseBodyCommand(method: .POST, + path: query.endpoint, + body: query) { + try ParseCoding.jsonDecoder().decode(AnyResultsResponse.self, from: $0).results + } } } - func firstExplainCommand() -> API.NonParseBodyCommand, U> { + func firstExplainCommand() throws -> API.NonParseBodyCommand, U> { var query = self query.limit = 1 query.explain = true - return API.NonParseBodyCommand(method: .POST, path: query.endpoint, body: query) { - if let decoded = try ParseCoding.jsonDecoder().decode(AnyResultsResponse.self, from: $0).results.first { - return decoded + if !ParseSwift.configuration.isUsingPostForQuery { + return API.NonParseBodyCommand(method: .GET, + path: query.endpoint, + params: try query.getQueryParameters()) { + if let decoded = try ParseCoding + .jsonDecoder() + .decode(AnyResultsResponse.self, from: $0) + .results.first { + return decoded + } + throw ParseError(code: .objectNotFound, + message: "Object not found on the server.") + } + } else { + return API.NonParseBodyCommand(method: .POST, + path: query.endpoint, + body: query) { + if let decoded = try ParseCoding + .jsonDecoder() + .decode(AnyResultsResponse.self, from: $0) + .results.first { + return decoded + } + throw ParseError(code: .objectNotFound, + message: "Object not found on the server.") } - throw ParseError(code: .objectNotFound, - message: "Object not found on the server.") } } - func countExplainCommand() -> API.NonParseBodyCommand, [U]> { + func countExplainCommand() throws -> API.NonParseBodyCommand, [U]> { var query = self query.limit = 1 query.isCount = true query.explain = true - return API.NonParseBodyCommand(method: .POST, path: query.endpoint, body: query) { - try ParseCoding.jsonDecoder().decode(AnyResultsResponse.self, from: $0).results + if !ParseSwift.configuration.isUsingPostForQuery { + return API.NonParseBodyCommand(method: .GET, + path: query.endpoint, + params: try query.getQueryParameters()) { + try ParseCoding.jsonDecoder().decode(AnyResultsResponse.self, from: $0).results + } + } else { + return API.NonParseBodyCommand(method: .POST, + path: query.endpoint, + body: query) { + try ParseCoding.jsonDecoder().decode(AnyResultsResponse.self, from: $0).results + } } } - func withCountExplainCommand() -> API.NonParseBodyCommand, [U]> { + func withCountExplainCommand() throws -> API.NonParseBodyCommand, [U]> { var query = self query.isCount = true query.explain = true - return API.NonParseBodyCommand(method: .POST, path: query.endpoint, body: query) { - try ParseCoding.jsonDecoder().decode(AnyResultsResponse.self, from: $0).results + if !ParseSwift.configuration.isUsingPostForQuery { + return API.NonParseBodyCommand(method: .GET, + path: query.endpoint, + params: try query.getQueryParameters()) { + try ParseCoding.jsonDecoder().decode(AnyResultsResponse.self, from: $0).results + } + } else { + return API.NonParseBodyCommand(method: .POST, + path: query.endpoint, + body: query) { + try ParseCoding.jsonDecoder().decode(AnyResultsResponse.self, from: $0).results + } } } - func aggregateExplainCommand() -> API.NonParseBodyCommand, [U]> { + func aggregateExplainCommand() throws -> API.NonParseBodyCommand, [U]> { var query = self query.explain = true let body = AggregateBody(query: query) - return API.NonParseBodyCommand(method: .POST, path: .aggregate(className: T.className), body: body) { - try ParseCoding.jsonDecoder().decode(AnyResultsResponse.self, from: $0).results + if !ParseSwift.configuration.isUsingPostForQuery { + return API.NonParseBodyCommand(method: .GET, + path: .aggregate(className: T.className), + params: try body.getQueryParameters()) { + try ParseCoding.jsonDecoder().decode(AnyResultsResponse.self, from: $0).results + } + } else { + return API.NonParseBodyCommand(method: .POST, + path: .aggregate(className: T.className), + body: body) { + try ParseCoding.jsonDecoder().decode(AnyResultsResponse.self, from: $0).results + } } } - func distinctExplainCommand(key: String) -> API.NonParseBodyCommand, [U]> { + // swiftlint:disable:next line_length + func distinctExplainCommand(key: String) throws -> API.NonParseBodyCommand, [U]> { var query = self query.explain = true query.distinct = key let body = DistinctBody(query: query) - return API.NonParseBodyCommand(method: .POST, path: .aggregate(className: T.className), body: body) { - try ParseCoding.jsonDecoder().decode(AnyResultsResponse.self, from: $0).results + if !ParseSwift.configuration.isUsingPostForQuery { + return API.NonParseBodyCommand(method: .GET, + path: .aggregate(className: T.className), + params: try body.getQueryParameters()) { + try ParseCoding.jsonDecoder().decode(AnyResultsResponse.self, from: $0).results + } + } else { + return API.NonParseBodyCommand(method: .POST, path: .aggregate(className: T.className), body: body) { + try ParseCoding.jsonDecoder().decode(AnyResultsResponse.self, from: $0).results + } } } - func findExplainMongoCommand() -> API.NonParseBodyCommand, [U]> { + func findExplainMongoCommand() throws -> API.NonParseBodyCommand, [U]> { var query = self query.explain = true - return API.NonParseBodyCommand(method: .POST, path: query.endpoint, body: query) { - try [ParseCoding.jsonDecoder().decode(AnyResultsMongoResponse.self, from: $0).results] + if !ParseSwift.configuration.isUsingPostForQuery { + return API.NonParseBodyCommand(method: .GET, + path: query.endpoint, + params: try query.getQueryParameters()) { + try [ParseCoding.jsonDecoder().decode(AnyResultsMongoResponse.self, from: $0).results] + } + } else { + return API.NonParseBodyCommand(method: .POST, + path: query.endpoint, + body: query) { + try [ParseCoding.jsonDecoder().decode(AnyResultsMongoResponse.self, from: $0).results] + } } } - func firstExplainMongoCommand() -> API.NonParseBodyCommand, U> { + func firstExplainMongoCommand() throws -> API.NonParseBodyCommand, U> { var query = self query.limit = 1 query.explain = true - return API.NonParseBodyCommand(method: .POST, path: query.endpoint, body: query) { - do { - return try ParseCoding.jsonDecoder().decode(AnyResultsMongoResponse.self, from: $0).results - } catch { - throw ParseError(code: .objectNotFound, - message: "Object not found on the server. Error: \(error)") + if !ParseSwift.configuration.isUsingPostForQuery { + return API.NonParseBodyCommand(method: .GET, + path: query.endpoint, + params: try query.getQueryParameters()) { + do { + return try ParseCoding.jsonDecoder().decode(AnyResultsMongoResponse.self, from: $0).results + } catch { + throw ParseError(code: .objectNotFound, + message: "Object not found on the server. Error: \(error)") + } + } + } else { + return API.NonParseBodyCommand(method: .POST, + path: query.endpoint, + body: query) { + do { + return try ParseCoding.jsonDecoder().decode(AnyResultsMongoResponse.self, from: $0).results + } catch { + throw ParseError(code: .objectNotFound, + message: "Object not found on the server. Error: \(error)") + } } } } - func countExplainMongoCommand() -> API.NonParseBodyCommand, [U]> { + func countExplainMongoCommand() throws -> API.NonParseBodyCommand, [U]> { var query = self query.limit = 1 query.isCount = true query.explain = true - return API.NonParseBodyCommand(method: .POST, path: query.endpoint, body: query) { - try [ParseCoding.jsonDecoder().decode(AnyResultsMongoResponse.self, from: $0).results] + if !ParseSwift.configuration.isUsingPostForQuery { + return API.NonParseBodyCommand(method: .GET, + path: query.endpoint, + params: try query.getQueryParameters()) { + try [ParseCoding.jsonDecoder().decode(AnyResultsMongoResponse.self, from: $0).results] + } + } else { + return API.NonParseBodyCommand(method: .POST, + path: query.endpoint, + body: query) { + try [ParseCoding.jsonDecoder().decode(AnyResultsMongoResponse.self, from: $0).results] + } } } - func withCountExplainMongoCommand() -> API.NonParseBodyCommand, [U]> { + func withCountExplainMongoCommand() throws -> API.NonParseBodyCommand, [U]> { var query = self query.isCount = true query.explain = true - return API.NonParseBodyCommand(method: .POST, path: query.endpoint, body: query) { - try [ParseCoding.jsonDecoder().decode(AnyResultsMongoResponse.self, from: $0).results] + if !ParseSwift.configuration.isUsingPostForQuery { + return API.NonParseBodyCommand(method: .GET, + path: query.endpoint, + params: try query.getQueryParameters()) { + try [ParseCoding.jsonDecoder().decode(AnyResultsMongoResponse.self, from: $0).results] + } + } else { + return API.NonParseBodyCommand(method: .POST, + path: query.endpoint, + body: query) { + try [ParseCoding.jsonDecoder().decode(AnyResultsMongoResponse.self, from: $0).results] + } } } - func aggregateExplainMongoCommand() -> API.NonParseBodyCommand, [U]> { + // swiftlint:disable:next line_length + func aggregateExplainMongoCommand() throws -> API.NonParseBodyCommand, [U]> { var query = self query.explain = true let body = AggregateBody(query: query) - return API.NonParseBodyCommand(method: .POST, path: .aggregate(className: T.className), body: body) { - try [ParseCoding.jsonDecoder().decode(AnyResultsMongoResponse.self, from: $0).results] + if !ParseSwift.configuration.isUsingPostForQuery { + return API.NonParseBodyCommand(method: .GET, + path: .aggregate(className: T.className), + params: try body.getQueryParameters()) { + try [ParseCoding.jsonDecoder().decode(AnyResultsMongoResponse.self, from: $0).results] + } + } else { + return API.NonParseBodyCommand(method: .POST, + path: .aggregate(className: T.className), + body: body) { + try [ParseCoding.jsonDecoder().decode(AnyResultsMongoResponse.self, from: $0).results] + } } } // swiftlint:disable:next line_length - func distinctExplainMongoCommand(key: String) -> API.NonParseBodyCommand, [U]> { + func distinctExplainMongoCommand(key: String) throws -> API.NonParseBodyCommand, [U]> { var query = self query.explain = true query.distinct = key let body = DistinctBody(query: query) - return API.NonParseBodyCommand(method: .POST, path: .aggregate(className: T.className), body: body) { - try [ParseCoding.jsonDecoder().decode(AnyResultsMongoResponse.self, from: $0).results] + if !ParseSwift.configuration.isUsingPostForQuery { + return API.NonParseBodyCommand(method: .GET, + path: .aggregate(className: T.className), + params: try body.getQueryParameters()) { + try [ParseCoding.jsonDecoder().decode(AnyResultsMongoResponse.self, from: $0).results] + } + } else { + return API.NonParseBodyCommand(method: .POST, + path: .aggregate(className: T.className), + body: body) { + try [ParseCoding.jsonDecoder().decode(AnyResultsMongoResponse.self, from: $0).results] + } } } } @@ -1523,4 +1892,38 @@ enum RawCodingKey: CodingKey { } } +internal extension Query { + func getQueryParameters() throws -> [String: String] { + var dictionary = [String: String]() + dictionary["limit"] = try encodeAsString(\.limit) + dictionary["skip"] = try encodeAsString(\.skip) + dictionary["keys"] = try encodeAsString(\.keys) + dictionary["include"] = try encodeAsString(\.include) + dictionary["order"] = try encodeAsString(\.order) + dictionary["count"] = try encodeAsString(\.isCount) + dictionary["explain"] = try encodeAsString(\.explain) + dictionary["hint"] = try encodeAsString(\.hint) + dictionary["where"] = try encodeAsString(\.`where`) + dictionary["excludeKeys"] = try encodeAsString(\.excludeKeys) + dictionary["readPreference"] = try encodeAsString(\.readPreference) + dictionary["includeReadPreference"] = try encodeAsString(\.includeReadPreference) + dictionary["subqueryReadPreference"] = try encodeAsString(\.subqueryReadPreference) + dictionary["distinct"] = try encodeAsString(\.distinct) + dictionary["pipeline"] = try encodeAsString(\.pipeline) + return dictionary + } + + func encodeAsString(_ key: KeyPath) throws -> String? where W: Encodable { + guard let value = self[keyPath: key] else { + return nil + } + let encoded = try ParseCoding.jsonEncoder().encode(value) + return String(data: encoded, encoding: .utf8) + } + + func encodeAsString(_ key: KeyPath) throws -> String? where W: Encodable { + let encoded = try ParseCoding.jsonEncoder().encode(self[keyPath: key]) + return String(data: encoded, encoding: .utf8) + } +} // swiftlint:disable:this file_length diff --git a/Tests/ParseSwiftTests/ParseFileTests.swift b/Tests/ParseSwiftTests/ParseFileTests.swift index 2a23bb92d..c705c8405 100644 --- a/Tests/ParseSwiftTests/ParseFileTests.swift +++ b/Tests/ParseSwiftTests/ParseFileTests.swift @@ -959,7 +959,7 @@ class ParseFileTests: XCTestCase { // swiftlint:disable:this type_body_length // Cache policy flakey on older Swift versions #if compiler(>=5.5.0) - // Remove URL so we can check cache + // Remove URL mocker so we can check cache MockURLProtocol.removeAll() let fetchedFile2 = try parseFile.fetch(options: [.cachePolicy(.returnCacheDataDontLoad)]) diff --git a/Tests/ParseSwiftTests/ParseQueryAsyncTests.swift b/Tests/ParseSwiftTests/ParseQueryAsyncTests.swift index 030f6280b..d0e239a1a 100644 --- a/Tests/ParseSwiftTests/ParseQueryAsyncTests.swift +++ b/Tests/ParseSwiftTests/ParseQueryAsyncTests.swift @@ -59,6 +59,7 @@ class ParseQueryAsyncTests: XCTestCase { // swiftlint:disable:this type_body_len clientKey: "clientKey", masterKey: "masterKey", serverURL: url, + usingPostForQuery: true, testing: true) } diff --git a/Tests/ParseSwiftTests/ParseQueryCacheTests.swift b/Tests/ParseSwiftTests/ParseQueryCacheTests.swift new file mode 100644 index 000000000..8bc24a04b --- /dev/null +++ b/Tests/ParseSwiftTests/ParseQueryCacheTests.swift @@ -0,0 +1,773 @@ +// +// ParseQueryCacheTests.swift +// ParseSwift +// +// Created by Corey Baker on 8/4/22. +// Copyright © 2022 Parse Community. All rights reserved. +// + +import Foundation +import XCTest +@testable import ParseSwift + +// swiftlint:disable line_length + +class ParseQueryCacheTests: 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 + } + } + + struct GameScoreBroken: ParseObject { + //: These are required by ParseObject + var objectId: String? + var createdAt: Date? + var updatedAt: Date? + var ACL: ParseACL? + var originalData: Data? + + var points: Int? + } + + struct AnyResultsResponse: Codable { + let results: [U] + } + + struct AnyResultsMongoResponse: Codable { + let results: U + } + + 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, + usingEqualQueryConstraint: false, + usingPostForQuery: false, + testing: true) + } + + override func tearDownWithError() throws { + try super.tearDownWithError() + MockURLProtocol.removeAll() + ParseSwift.clearCache() + #if !os(Linux) && !os(Android) && !os(Windows) + try KeychainStore.shared.deleteAll() + #endif + try ParseStorage.shared.deleteAll() + } + + func testQueryParameters() throws { + let query = GameScore.query + .order([.ascending("points"), .descending("oldScore")]) + .exclude("hello", "world") + .include("foo", "bar") + .select("yolo", "nolo") + .hint("right") + .readPreference("now") + + let queryParameters = try query.getQueryParameters() + guard let whereParameter = queryParameters["where"], + let orderParameter = queryParameters["order"], + let skipParameter = queryParameters["skip"], + let excludeKeysParameter = queryParameters["excludeKeys"], + let limitParameter = queryParameters["limit"], + let keysParameter = queryParameters["keys"], + let includeParameter = queryParameters["include"], + let hintParameter = queryParameters["hint"], + let readPreferenceParameter = queryParameters["readPreference"] else { + XCTFail("Should have unwrapped") + return + } + XCTAssertTrue(whereParameter.contains("{}")) + XCTAssertTrue(orderParameter.contains("\"points")) + XCTAssertTrue(orderParameter.contains("\"-oldScore")) + XCTAssertTrue(skipParameter.contains("0")) + XCTAssertTrue(excludeKeysParameter.contains("\"hello")) + XCTAssertTrue(excludeKeysParameter.contains("\"world")) + XCTAssertTrue(limitParameter.contains("100")) + XCTAssertTrue(keysParameter.contains("\"nolo")) + XCTAssertTrue(keysParameter.contains("\"yolo")) + XCTAssertTrue(includeParameter.contains("\"foo\"")) + XCTAssertTrue(includeParameter.contains("\"bar\"")) + XCTAssertTrue(hintParameter.contains("\"right\"")) + XCTAssertTrue(readPreferenceParameter.contains("\"now\"")) + } + + func testAggregateQueryParameters() throws { + var query = GameScore.query + .order([.ascending("points"), .descending("oldScore")]) + .exclude("hello", "world") + .include("foo", "bar") + .select("yolo", "nolo") + .hint("right") + + query.includeReadPreference = "now" + query.explain = true + query.pipeline = [["yo": "no"]] + + let aggregate = Query.AggregateBody(query: query) + + let queryParameters = try aggregate.getQueryParameters() + guard let explainParameter = queryParameters["explain"], + let pipelineParameter = queryParameters["pipeline"], + let hintParameter = queryParameters["hint"], + let readPreferenceParameter = queryParameters["includeReadPreference"] else { + XCTFail("Should have unwrapped") + return + } + XCTAssertTrue(explainParameter.contains("true")) + XCTAssertTrue(pipelineParameter.contains("\"yo")) + XCTAssertTrue(pipelineParameter.contains("\"no")) + XCTAssertTrue(hintParameter.contains("\"right\"")) + XCTAssertTrue(readPreferenceParameter.contains("\"now\"")) + } + +#if compiler(>=5.5.2) && canImport(_Concurrency) + @MainActor + func testFind() async throws { + + var scoreOnServer = GameScore(points: 10) + scoreOnServer.points = 11 + scoreOnServer.objectId = "yolo" + scoreOnServer.createdAt = Date() + scoreOnServer.updatedAt = scoreOnServer.createdAt + scoreOnServer.ACL = nil + + let results = QueryResponse(results: [scoreOnServer], count: 1) + MockURLProtocol.mockRequests { _ in + do { + let encoded = try ParseCoding.jsonEncoder().encode(results) + return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) + } catch { + return nil + } + } + + let query = GameScore.query + + let found = try await query.find() + guard let object = found.first else { + XCTFail("Should have unwrapped") + return + } + XCTAssert(object.hasSameObjectId(as: scoreOnServer)) + } + + @MainActor + func testWithCount() async throws { + + var scoreOnServer = GameScore(points: 10) + scoreOnServer.points = 11 + scoreOnServer.objectId = "yolo" + scoreOnServer.createdAt = Date() + scoreOnServer.updatedAt = scoreOnServer.createdAt + scoreOnServer.ACL = nil + + let results = QueryResponse(results: [scoreOnServer], count: 1) + MockURLProtocol.mockRequests { _ in + do { + let encoded = try ParseCoding.jsonEncoder().encode(results) + return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) + } catch { + return nil + } + } + + let query = GameScore.query + + let found = try await query.withCount() + guard let object = found.0.first else { + XCTFail("Should have unwrapped") + return + } + XCTAssertTrue(object.hasSameObjectId(as: scoreOnServer)) + XCTAssertEqual(found.1, 1) + + // Remove URL mocker so we can check cache + MockURLProtocol.removeAll() + let found2 = try await query.withCount(options: [.cachePolicy(.returnCacheDataDontLoad)]) + guard let object2 = found2.0.first else { + XCTFail("Should have unwrapped") + return + } + XCTAssertTrue(object2.hasSameObjectId(as: scoreOnServer)) + XCTAssertEqual(found2.1, 1) + } + + @MainActor + func testWithCountMissingCount() async throws { + + var scoreOnServer = GameScore(points: 10) + scoreOnServer.points = 11 + scoreOnServer.objectId = "yolo" + scoreOnServer.createdAt = Date() + scoreOnServer.updatedAt = scoreOnServer.createdAt + scoreOnServer.ACL = nil + + let results = QueryResponse(results: [scoreOnServer], count: nil) + MockURLProtocol.mockRequests { _ in + do { + let encoded = try ParseCoding.jsonEncoder().encode(results) + return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) + } catch { + return nil + } + } + + let query = GameScore.query + + let found = try await query.withCount() + guard let object = found.0.first else { + XCTFail("Should have unwrapped") + return + } + XCTAssertTrue(object.hasSameObjectId(as: scoreOnServer)) + XCTAssertEqual(found.1, 0) + + // Remove URL mocker so we can check cache + MockURLProtocol.removeAll() + let found2 = try await query.withCount(options: [.cachePolicy(.returnCacheDataDontLoad)]) + guard let object2 = found2.0.first else { + XCTFail("Should have unwrapped") + return + } + XCTAssertTrue(object2.hasSameObjectId(as: scoreOnServer)) + XCTAssertEqual(found2.1, 0) + } + + @MainActor + func testFindAll() async throws { + + var scoreOnServer = GameScore(points: 10) + scoreOnServer.objectId = "yarr" + scoreOnServer.createdAt = Date() + scoreOnServer.updatedAt = scoreOnServer.createdAt + scoreOnServer.ACL = nil + + let results = AnyResultsResponse(results: [scoreOnServer]) + MockURLProtocol.mockRequests { _ in + do { + let encoded = try ParseCoding.jsonEncoder().encode(results) + return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) + } catch { + return nil + } + } + + let found = try await GameScore.query.findAll() + guard let object = found.first else { + XCTFail("Should have unwrapped") + return + } + XCTAssert(object.hasSameObjectId(as: scoreOnServer)) + + // Remove URL mocker so we can check cache + MockURLProtocol.removeAll() + let found2 = try await GameScore.query.findAll(options: [.cachePolicy(.returnCacheDataDontLoad)]) + guard let object2 = found2.first else { + XCTFail("Should have unwrapped") + return + } + XCTAssert(object2.hasSameObjectId(as: scoreOnServer)) + } + + @MainActor + func testFindExplain() async throws { + + let json = AnyResultsResponse(results: [["yolo": "yarr"]]) + + let encoded: Data! + do { + encoded = try JSONEncoder().encode(json) + } catch { + XCTFail("Should encode. Error \(error)") + return + } + + MockURLProtocol.mockRequests { _ in + return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) + } + + let query = GameScore.query + let queryResult: [[String: String]] = try await query.findExplain() + XCTAssertEqual(queryResult, json.results) + + // Remove URL mocker so we can check cache + MockURLProtocol.removeAll() + let queryResult2: [[String: String]] = try await query.findExplain(options: [.cachePolicy(.returnCacheDataDontLoad)]) + XCTAssertEqual(queryResult2, json.results) + } + + @MainActor + func testFindExplainMongo() async throws { + + let json = AnyResultsMongoResponse(results: ["yolo": "yarr"]) + + let encoded: Data! + do { + encoded = try JSONEncoder().encode(json) + } catch { + XCTFail("Should encode. Error \(error)") + return + } + + MockURLProtocol.mockRequests { _ in + return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) + } + + let query = GameScore.query + let queryResult: [[String: String]] = try await query.findExplain(usingMongoDB: true) + XCTAssertEqual(queryResult, [json.results]) + + // Remove URL mocker so we can check cache + MockURLProtocol.removeAll() + let queryResult2: [[String: String]] = try await query.findExplain(usingMongoDB: true, + options: [.cachePolicy(.returnCacheDataDontLoad)]) + XCTAssertEqual(queryResult2, [json.results]) + } + + @MainActor + func testWithCountExplain() async throws { + + let json = AnyResultsResponse(results: [["yolo": "yarr"]]) + + let encoded: Data! + do { + encoded = try JSONEncoder().encode(json) + } catch { + XCTFail("Should encode. Error \(error)") + return + } + + MockURLProtocol.mockRequests { _ in + return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) + } + + let query = GameScore.query + let queryResult: [[String: String]] = try await query.withCountExplain() + XCTAssertEqual(queryResult, json.results) + + // Remove URL mocker so we can check cache + MockURLProtocol.removeAll() + let queryResult2: [[String: String]] = try await query.withCountExplain(options: [.cachePolicy(.returnCacheDataDontLoad)]) + XCTAssertEqual(queryResult2, json.results) + } + + @MainActor + func testWithCountExplainMongo() async throws { + + let json = AnyResultsMongoResponse(results: ["yolo": "yarr"]) + + let encoded: Data! + do { + encoded = try JSONEncoder().encode(json) + } catch { + XCTFail("Should encode. Error \(error)") + return + } + + MockURLProtocol.mockRequests { _ in + return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) + } + + let query = GameScore.query + let queryResult: [[String: String]] = try await query.withCountExplain(usingMongoDB: true) + XCTAssertEqual(queryResult, [json.results]) + + // Remove URL mocker so we can check cache + MockURLProtocol.removeAll() + let queryResult2: [[String: String]] = try await query.withCountExplain(usingMongoDB: true, + options: [.cachePolicy(.returnCacheDataDontLoad)]) + XCTAssertEqual(queryResult2, [json.results]) + } + + @MainActor + func testFirst() async throws { + + var scoreOnServer = GameScore(points: 10) + scoreOnServer.points = 11 + scoreOnServer.objectId = "yolo" + scoreOnServer.createdAt = Date() + scoreOnServer.updatedAt = scoreOnServer.createdAt + scoreOnServer.ACL = nil + + let results = QueryResponse(results: [scoreOnServer], count: 1) + MockURLProtocol.mockRequests { _ in + do { + let encoded = try ParseCoding.jsonEncoder().encode(results) + return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) + } catch { + return nil + } + } + + let query = GameScore.query + let found = try await query.first() + XCTAssert(found.hasSameObjectId(as: scoreOnServer)) + + // Remove URL mocker so we can check cache + MockURLProtocol.removeAll() + let found2 = try await query.first(options: [.cachePolicy(.returnCacheDataDontLoad)]) + XCTAssert(found2.hasSameObjectId(as: scoreOnServer)) + } + + @MainActor + func testFirstExplain() async throws { + + let json = AnyResultsResponse(results: [["yolo": "yarr"]]) + + let encoded: Data! + do { + encoded = try JSONEncoder().encode(json) + } catch { + XCTFail("Should encode. Error \(error)") + return + } + + MockURLProtocol.mockRequests { _ in + return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) + } + + let query = GameScore.query + let queryResult: [String: String] = try await query.firstExplain() + XCTAssertEqual(queryResult, json.results.first) + + // Remove URL mocker so we can check cache + MockURLProtocol.removeAll() + let queryResult2: [String: String] = try await query.firstExplain(options: [.cachePolicy(.returnCacheDataDontLoad)]) + XCTAssertEqual(queryResult2, json.results.first) + } + + @MainActor + func testFirstExplainMongo() async throws { + + let json = AnyResultsMongoResponse(results: ["yolo": "yarr"]) + + let encoded: Data! + do { + encoded = try JSONEncoder().encode(json) + } catch { + XCTFail("Should encode. Error \(error)") + return + } + + MockURLProtocol.mockRequests { _ in + return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) + } + + let query = GameScore.query + let queryResult: [String: String] = try await query.firstExplain(usingMongoDB: true) + XCTAssertEqual(queryResult, json.results) + + // Remove URL mocker so we can check cache + MockURLProtocol.removeAll() + let queryResult2: [String: String] = try await query.firstExplain(usingMongoDB: true, + options: [.cachePolicy(.returnCacheDataDontLoad)]) + XCTAssertEqual(queryResult2, json.results) + } + + @MainActor + func testCount() async throws { + + var scoreOnServer = GameScore(points: 10) + scoreOnServer.points = 11 + scoreOnServer.objectId = "yolo" + scoreOnServer.createdAt = Date() + scoreOnServer.updatedAt = scoreOnServer.createdAt + scoreOnServer.ACL = nil + + let results = QueryResponse(results: [scoreOnServer], count: 1) + MockURLProtocol.mockRequests { _ in + do { + let encoded = try ParseCoding.jsonEncoder().encode(results) + return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) + } catch { + return nil + } + } + + let query = GameScore.query + let found = try await query.count() + XCTAssertEqual(found, 1) + + // Remove URL mocker so we can check cache + MockURLProtocol.removeAll() + let found2 = try await query.count(options: [.cachePolicy(.returnCacheDataDontLoad)]) + XCTAssertEqual(found2, 1) + } + + @MainActor + func testCountExplain() async throws { + + let json = AnyResultsResponse(results: [["yolo": "yarr"]]) + + let encoded: Data! + do { + encoded = try JSONEncoder().encode(json) + } catch { + XCTFail("Should encode. Error \(error)") + return + } + + MockURLProtocol.mockRequests { _ in + return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) + } + + let query = GameScore.query + let queryResult: [[String: String]] = try await query.countExplain() + XCTAssertEqual(queryResult, json.results) + + // Remove URL mocker so we can check cache + MockURLProtocol.removeAll() + let queryResult2: [[String: String]] = try await query.countExplain(options: [.cachePolicy(.returnCacheDataDontLoad)]) + XCTAssertEqual(queryResult2, json.results) + } + + @MainActor + func testCountExplainMongo() async throws { + + let json = AnyResultsMongoResponse(results: ["yolo": "yarr"]) + + let encoded: Data! + do { + encoded = try JSONEncoder().encode(json) + } catch { + XCTFail("Should encode. Error \(error)") + return + } + + MockURLProtocol.mockRequests { _ in + return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) + } + + let query = GameScore.query + let queryResult: [[String: String]] = try await query.countExplain(usingMongoDB: true) + XCTAssertEqual(queryResult, [json.results]) + + // Remove URL mocker so we can check cache + MockURLProtocol.removeAll() + let queryResult2: [[String: String]] = try await query.countExplain(usingMongoDB: true, + options: [.cachePolicy(.returnCacheDataDontLoad)]) + XCTAssertEqual(queryResult2, [json.results]) + } + + @MainActor + func testAggregate() async throws { + + var scoreOnServer = GameScore(points: 10) + scoreOnServer.objectId = "yarr" + scoreOnServer.createdAt = Date() + scoreOnServer.updatedAt = scoreOnServer.createdAt + scoreOnServer.ACL = nil + + let results = QueryResponse(results: [scoreOnServer], count: 1) + MockURLProtocol.mockRequests { _ in + do { + let encoded = try ParseCoding.jsonEncoder().encode(results) + return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) + } catch { + return nil + } + } + + let query = GameScore.query + let pipeline = [[String: AnyEncodable]]() + let found = try await query.aggregate(pipeline) + guard let object = found.first else { + XCTFail("Should have unwrapped") + return + } + XCTAssert(object.hasSameObjectId(as: scoreOnServer)) + + // Remove URL mocker so we can check cache + MockURLProtocol.removeAll() + let found2 = try await query.aggregate(pipeline, + options: [.cachePolicy(.returnCacheDataDontLoad)]) + guard let object2 = found2.first else { + XCTFail("Should have unwrapped") + return + } + XCTAssert(object2.hasSameObjectId(as: scoreOnServer)) + } + + @MainActor + func testAggregateExplain() async throws { + + let json = AnyResultsResponse(results: [["yolo": "yarr"]]) + + let encoded: Data! + do { + encoded = try JSONEncoder().encode(json) + } catch { + XCTFail("Should encode. Error \(error)") + return + } + + MockURLProtocol.mockRequests { _ in + return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) + } + + let query = GameScore.query + let pipeline = [[String: String]]() + let queryResult: [[String: String]] = try await query.aggregateExplain(pipeline) + XCTAssertEqual(queryResult, json.results) + + // Remove URL mocker so we can check cache + MockURLProtocol.removeAll() + let queryResult2: [[String: String]] = try await query.aggregateExplain(pipeline, + options: [.cachePolicy(.returnCacheDataDontLoad)]) + XCTAssertEqual(queryResult2, json.results) + } + + @MainActor + func testAggregateExplainMongo() async throws { + + let json = AnyResultsMongoResponse(results: ["yolo": "yarr"]) + + let encoded: Data! + do { + encoded = try JSONEncoder().encode(json) + } catch { + XCTFail("Should encode. Error \(error)") + return + } + + MockURLProtocol.mockRequests { _ in + return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) + } + + let query = GameScore.query + let pipeline = [[String: String]]() + let queryResult: [[String: String]] = try await query.aggregateExplain(pipeline, + usingMongoDB: true) + XCTAssertEqual(queryResult, [json.results]) + + // Remove URL mocker so we can check cache + MockURLProtocol.removeAll() + let queryResult2: [[String: String]] = try await query.aggregateExplain(pipeline, + usingMongoDB: true, + options: [.cachePolicy(.returnCacheDataDontLoad)]) + XCTAssertEqual(queryResult2, [json.results]) + } + + @MainActor + func testDistinct() async throws { + + var scoreOnServer = GameScore(points: 10) + scoreOnServer.objectId = "yarr" + scoreOnServer.createdAt = Date() + scoreOnServer.updatedAt = scoreOnServer.createdAt + scoreOnServer.ACL = nil + + let results = QueryResponse(results: [scoreOnServer], count: 1) + MockURLProtocol.mockRequests { _ in + do { + let encoded = try ParseCoding.jsonEncoder().encode(results) + return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) + } catch { + return nil + } + } + + let query = GameScore.query + let found = try await query.distinct("hello") + guard let object = found.first else { + XCTFail("Should have unwrapped") + return + } + XCTAssert(object.hasSameObjectId(as: scoreOnServer)) + + // Remove URL mocker so we can check cache + MockURLProtocol.removeAll() + let found2 = try await query.distinct("hello", + options: [.cachePolicy(.returnCacheDataDontLoad)]) + guard let object2 = found2.first else { + XCTFail("Should have unwrapped") + return + } + XCTAssert(object2.hasSameObjectId(as: scoreOnServer)) + } + + @MainActor + func testDistinctExplain() async throws { + + let json = AnyResultsResponse(results: [["yolo": "yarr"]]) + + let encoded: Data! + do { + encoded = try JSONEncoder().encode(json) + } catch { + XCTFail("Should encode. Error \(error)") + return + } + + MockURLProtocol.mockRequests { _ in + return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) + } + + let query = GameScore.query + let queryResult: [[String: String]] = try await query.distinctExplain("hello") + XCTAssertEqual(queryResult, json.results) + + // Remove URL mocker so we can check cache + MockURLProtocol.removeAll() + let queryResult2: [[String: String]] = try await query.distinctExplain("hello", + options: [.cachePolicy(.returnCacheDataDontLoad)]) + XCTAssertEqual(queryResult2, json.results) + } + + @MainActor + func testDistinctExplainMongo() async throws { + + let json = AnyResultsMongoResponse(results: ["yolo": "yarr"]) + + let encoded: Data! + do { + encoded = try JSONEncoder().encode(json) + } catch { + XCTFail("Should encode. Error \(error)") + return + } + + MockURLProtocol.mockRequests { _ in + return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) + } + + let query = GameScore.query + let queryResult: [[String: String]] = try await query.distinctExplain("hello", + usingMongoDB: true) + XCTAssertEqual(queryResult, [json.results]) + + // Remove URL mocker so we can check cache + MockURLProtocol.removeAll() + let queryResult2: [[String: String]] = try await query.distinctExplain("hello", + usingMongoDB: true, + options: [.cachePolicy(.returnCacheDataDontLoad)]) + XCTAssertEqual(queryResult2, [json.results]) + } +#endif +} diff --git a/Tests/ParseSwiftTests/ParseQueryCombineTests.swift b/Tests/ParseSwiftTests/ParseQueryCombineTests.swift index 0950f57d9..9d4d5031c 100644 --- a/Tests/ParseSwiftTests/ParseQueryCombineTests.swift +++ b/Tests/ParseSwiftTests/ParseQueryCombineTests.swift @@ -57,6 +57,7 @@ class ParseQueryCombineTests: XCTestCase { // swiftlint:disable:this type_body_l clientKey: "clientKey", masterKey: "masterKey", serverURL: url, + usingPostForQuery: true, testing: true) } diff --git a/Tests/ParseSwiftTests/ParseQueryTests.swift b/Tests/ParseSwiftTests/ParseQueryTests.swift index 29b2d5691..26aef6c82 100644 --- a/Tests/ParseSwiftTests/ParseQueryTests.swift +++ b/Tests/ParseSwiftTests/ParseQueryTests.swift @@ -64,6 +64,7 @@ class ParseQueryTests: XCTestCase { // swiftlint:disable:this type_body_length masterKey: "masterKey", serverURL: url, usingEqualQueryConstraint: false, + usingPostForQuery: true, testing: true) } @@ -402,7 +403,7 @@ class ParseQueryTests: XCTestCase { // swiftlint:disable:this type_body_length func testFindCommand() throws { let query = GameScore.query - let command = query.findCommand() + let command = try query.findCommand() // swiftlint:disable:next line_length let expected = "{\"body\":{\"_method\":\"GET\",\"limit\":100,\"skip\":0,\"where\":{}},\"method\":\"POST\",\"path\":\"\\/classes\\/GameScore\"}" let encoded = try ParseCoding.jsonEncoder() @@ -421,7 +422,7 @@ class ParseQueryTests: XCTestCase { // swiftlint:disable:this type_body_length func testFindExplainCommand() throws { let query = GameScore.query() let command: API.NonParseBodyCommand, - [GameScore]> = query.findExplainCommand() + [GameScore]> = try query.findExplainCommand() // swiftlint:disable:next line_length let expected = "{\"body\":{\"_method\":\"GET\",\"explain\":true,\"limit\":100,\"skip\":0,\"where\":{}},\"method\":\"POST\",\"path\":\"\\/classes\\/GameScore\"}" let encoded = try ParseCoding.jsonEncoder() @@ -750,7 +751,7 @@ class ParseQueryTests: XCTestCase { // swiftlint:disable:this type_body_length func testFirstCommand() throws { let query = GameScore.query() - let command = query.firstCommand() + let command = try query.firstCommand() // swiftlint:disable:next line_length let expected = "{\"body\":{\"_method\":\"GET\",\"limit\":1,\"skip\":0,\"where\":{}},\"method\":\"POST\",\"path\":\"\\/classes\\/GameScore\"}" let encoded = try ParseCoding.jsonEncoder() @@ -762,7 +763,7 @@ class ParseQueryTests: XCTestCase { // swiftlint:disable:this type_body_length func testFirstExplainCommand() throws { let query = GameScore.query() let command: API.NonParseBodyCommand, - GameScore> = query.firstExplainCommand() + GameScore> = try query.firstExplainCommand() // swiftlint:disable:next line_length let expected = "{\"body\":{\"_method\":\"GET\",\"explain\":true,\"limit\":1,\"skip\":0,\"where\":{}},\"method\":\"POST\",\"path\":\"\\/classes\\/GameScore\"}" let encoded = try ParseCoding.jsonEncoder() @@ -996,7 +997,7 @@ class ParseQueryTests: XCTestCase { // swiftlint:disable:this type_body_length func testCountCommand() throws { let query = GameScore.query() - let command = query.countCommand() + let command = try query.countCommand() // swiftlint:disable:next line_length let expected = "{\"body\":{\"_method\":\"GET\",\"count\":true,\"limit\":1,\"skip\":0,\"where\":{}},\"method\":\"POST\",\"path\":\"\\/classes\\/GameScore\"}" let encoded = try ParseCoding.jsonEncoder() @@ -1008,7 +1009,7 @@ class ParseQueryTests: XCTestCase { // swiftlint:disable:this type_body_length func testCountExplainCommand() throws { let query = GameScore.query() let command: API.NonParseBodyCommand, - [Int]> = query.countExplainCommand() + [Int]> = try query.countExplainCommand() // swiftlint:disable:next line_length let expected = "{\"body\":{\"_method\":\"GET\",\"count\":true,\"explain\":true,\"limit\":1,\"skip\":0,\"where\":{}},\"method\":\"POST\",\"path\":\"\\/classes\\/GameScore\"}" let encoded = try ParseCoding.jsonEncoder() @@ -3496,7 +3497,7 @@ class ParseQueryTests: XCTestCase { // swiftlint:disable:this type_body_length var query = GameScore.query() let value = AnyCodable("world") query.pipeline = [["hello": value]] - let aggregate = query.aggregateCommand() + let aggregate = try query.aggregateCommand() // swiftlint:disable:next line_length let expected = "{\"body\":{\"pipeline\":[{\"hello\":\"\(value)\"}]},\"method\":\"POST\",\"path\":\"\\/aggregate\\/GameScore\"}" let encoded = try ParseCoding.jsonEncoder() @@ -3508,7 +3509,7 @@ class ParseQueryTests: XCTestCase { // swiftlint:disable:this type_body_length func testAggregateExplainCommand() throws { let query = GameScore.query() let command: API.NonParseBodyCommand.AggregateBody, - [String]> = query.aggregateExplainCommand() + [String]> = try query.aggregateExplainCommand() let expected = "{\"body\":{\"explain\":true},\"method\":\"POST\",\"path\":\"\\/aggregate\\/GameScore\"}" let encoded = try ParseCoding.jsonEncoder() .encode(command) @@ -3518,7 +3519,7 @@ class ParseQueryTests: XCTestCase { // swiftlint:disable:this type_body_length func testDistinctCommand() throws { let query = GameScore.query() - let aggregate = query.distinctCommand(key: "hello") + let aggregate = try query.distinctCommand(key: "hello") let expected = "{\"body\":{\"distinct\":\"hello\"},\"method\":\"POST\",\"path\":\"\\/aggregate\\/GameScore\"}" let encoded = try ParseCoding.jsonEncoder() .encode(aggregate) @@ -3529,7 +3530,7 @@ class ParseQueryTests: XCTestCase { // swiftlint:disable:this type_body_length func testDistinctExplainCommand() throws { let query = GameScore.query() let command: API.NonParseBodyCommand.DistinctBody, - [String]> = query.distinctExplainCommand(key: "hello") + [String]> = try query.distinctExplainCommand(key: "hello") // swiftlint:disable:next line_length let expected = "{\"body\":{\"distinct\":\"hello\",\"explain\":true},\"method\":\"POST\",\"path\":\"\\/aggregate\\/GameScore\"}" let encoded = try ParseCoding.jsonEncoder() diff --git a/Tests/ParseSwiftTests/ParseQueryViewModelTests.swift b/Tests/ParseSwiftTests/ParseQueryViewModelTests.swift index aa440d635..3d2ff27a9 100644 --- a/Tests/ParseSwiftTests/ParseQueryViewModelTests.swift +++ b/Tests/ParseSwiftTests/ParseQueryViewModelTests.swift @@ -44,6 +44,7 @@ class ParseQueryViewModelTests: XCTestCase { clientKey: "clientKey", masterKey: "masterKey", serverURL: url, + usingPostForQuery: true, testing: true) }